Files
Piano/PianoPhysicsData.h
2026-01-10 10:54:35 +00:00

314 lines
12 KiB
C++

/*
* PianoPhysicsData.h
*
* Implementation-ready parameter values for piano physical modeling
* Extracted from peer-reviewed acoustic research papers
*
* Sources:
* - Chaigne et al., JASA 133(4), 2013 - Soundboard dynamics
* - Hall & Askenfelt, JASA 83(4), 1988 - Hammer-string interaction
* - Russell & Rossing, Acta Acustica 84, 1998 - Hammer nonlinearity
* - Giordano, JASA 103(4), 1998 - Bridge impedance
* - Conklin, JASA 99-100, 1996 - Piano design
* - Fletcher, JASA 36(1), 1964 - String inharmonicity
*
* This data is scientific fact and not subject to copyright.
*/
#pragma once
#include <cmath>
#include <array>
namespace PianoPhysics {
//==============================================================================
// MATERIAL PROPERTIES
//==============================================================================
// European Spruce (Picea excelsa) - Soundboard
namespace Spruce {
constexpr float density = 440.0f; // kg/m³
constexpr float E_longitudinal = 15.9e9f; // Pa (along grain)
constexpr float E_radial = 0.69e9f; // Pa
constexpr float E_tangential = 0.39e9f; // Pa
constexpr float G_LR = 0.62e9f; // Pa (shear modulus)
constexpr float G_LT = 0.77e9f; // Pa
constexpr float G_RT = 0.0036e9f; // Pa
constexpr float nu_LR = 0.44f; // Poisson's ratio
constexpr float nu_LT = 0.38f;
constexpr float nu_RT = 0.47f;
}
// Beech - Bridge material
namespace Beech {
constexpr float density = 674.0f; // kg/m³
constexpr float E_longitudinal = 14.0e9f; // Pa
constexpr float E_radial = 2.28e9f; // Pa
constexpr float E_tangential = 1.16e9f; // Pa
}
// Steel - Strings
namespace Steel {
constexpr float density = 7800.0f; // kg/m³
constexpr float youngsModulus = 210.0e9f; // Pa
}
// Copper - Bass string wrapping
namespace Copper {
constexpr float density = 8960.0f; // kg/m³
}
//==============================================================================
// SOUNDBOARD PARAMETERS
//==============================================================================
namespace Soundboard {
// Modal damping (loss factor eta)
constexpr float lossFactor_low = 0.007f; // < 300 Hz (0.7%)
constexpr float lossFactor_mid = 0.012f; // 300-1000 Hz (1.2%)
constexpr float lossFactor_high = 0.020f; // 1000-3000 Hz (2.0%)
// Typical loss factor for good overall match
constexpr float typicalLossFactor = 0.015f; // 1.5%
// Waveguide transition frequency (ribs become waveguides above this)
constexpr float waveguideTransitionHz = 1100.0f;
// Mean rib spacing (Pleyel P131 measurements)
constexpr float meanRibSpacing_m = 0.132f; // 13.2 cm
// T60 values by frequency band (seconds)
inline float getT60(float freqHz) {
if (freqHz < 120.0f) return 1.8f - (freqHz - 60.0f) * 0.012f;
if (freqHz < 500.0f) return 1.1f - (freqHz - 120.0f) * 0.002f;
if (freqHz < 2000.0f) return 0.7f - (freqHz - 500.0f) * 0.0003f;
return 0.25f - (freqHz - 2000.0f) * 0.00003f;
}
}
//==============================================================================
// STRING PARAMETERS (Broadwood Grand measurements)
//==============================================================================
struct StringData {
int midiNote;
float frequency; // Hz (equal temperament)
float length; // m
float coreDiameter; // mm
float wrapDiameter; // mm (0 = unwrapped)
float mass; // g (total vibrating mass)
float tension; // N
float strikingRatio; // hammer position / string length
};
// Measured string parameters for C notes
constexpr std::array<StringData, 7> stringDataC = {{
// MIDI, freq, length, core, wrap, mass, tension, strike
{ 24, 32.7f, 1.945f, 1.00f, 4.55f, 142.0f, 680.0f, 0.117f }, // C1
{ 36, 65.4f, 1.425f, 1.00f, 3.60f, 52.0f, 710.0f, 0.102f }, // C2
{ 48, 130.8f, 0.975f, 1.025f,1.75f, 21.0f, 740.0f, 0.109f }, // C3
{ 60, 261.6f, 0.620f, 1.025f,0.0f, 15.0f, 770.0f, 0.125f }, // C4
{ 72, 523.3f, 0.337f, 1.00f, 0.0f, 8.1f, 810.0f, 0.134f }, // C5
{ 84, 1046.5f, 0.172f, 0.925f,0.0f, 3.9f, 680.0f, 0.151f }, // C6
{ 96, 2093.0f, 0.089f, 0.850f,0.0f, 1.7f, 560.0f, 0.180f }, // C7
}};
//==============================================================================
// INHARMONICITY
//==============================================================================
namespace Inharmonicity {
// Measured B values at specific points
// f_n = n * f1 * sqrt(1 + B * n²)
constexpr float B_A0 = 0.00020f; // MIDI 21
constexpr float B_C2 = 0.00030f; // MIDI 36 (wound)
constexpr float B_C3 = 0.00070f; // MIDI 48
constexpr float B_C4 = 0.0015f; // MIDI 60
constexpr float B_C5 = 0.0050f; // MIDI 72
constexpr float B_C6 = 0.018f; // MIDI 84
constexpr float B_C7 = 0.080f; // MIDI 96
constexpr float B_C8 = 0.40f; // MIDI 108
// Interpolation function for any MIDI note
inline float getB(int midiNote) {
// Bass wound strings (MIDI 21-52): slow exponential growth
if (midiNote <= 52) {
float norm = static_cast<float>(midiNote - 21) / 31.0f;
return 0.00018f * std::pow(10.0f, norm * 0.7f);
}
// Plain steel strings (MIDI 53-108): faster growth
float norm = static_cast<float>(midiNote - 53) / 55.0f;
return 0.0008f * std::pow(10.0f, norm * 2.7f);
}
// Compute partial frequency with inharmonicity
inline float partialFreq(float f1, int n, float B) {
return static_cast<float>(n) * f1 * std::sqrt(1.0f + B * n * n);
}
}
//==============================================================================
// HAMMER PARAMETERS
//==============================================================================
namespace Hammer {
// Nonlinear felt model: F = K * C^p
// Measured values for specific notes (Chaigne & Askenfelt)
struct HammerData {
int midiNote;
float K; // Stiffness (SI units, varies with p)
float p; // Nonlinearity exponent
float mass_kg; // Mass in kg
};
constexpr std::array<HammerData, 3> measuredHammers = {{
{ 36, 4.0e8f, 2.3f, 0.0110f }, // C2
{ 60, 4.5e9f, 2.8f, 0.0092f }, // C4
{ 84, 1.0e10f, 3.0f, 0.0052f }, // C6
}};
// Exponent p varies smoothly from bass to treble
// Source: Russell & Rossing (1998)
inline float getExponent(int midiNote) {
// Bass: p ≈ 2.0-2.3
// Middle: p ≈ 2.6-3.0
// Treble: p ≈ 3.0-4.0
float norm = static_cast<float>(midiNote - 21) / 87.0f;
return 2.0f + norm * 2.0f; // 2.0 to 4.0
}
// Stiffness coefficient K (log-interpolated)
inline float getStiffness(int midiNote) {
float norm = static_cast<float>(midiNote - 21) / 87.0f;
return 4.0e8f * std::pow(10.0f, norm * 1.4f);
}
// Hammer mass (kg)
inline float getMass(int midiNote) {
float norm = static_cast<float>(midiNote - 21) / 87.0f;
return 0.012f - norm * 0.008f; // 12g to 4g
}
// Hysteresis (reduces force on return stroke)
// Source: Stulov
inline float getHysteresis(int midiNote) {
float norm = static_cast<float>(midiNote - 21) / 87.0f;
return 0.18f - norm * 0.10f; // 0.18 to 0.08
}
// Striking position as fraction of string length
inline float getStrikingRatio(int midiNote) {
// Varies from ~1/8.5 in bass to ~1/5.5 in treble
float norm = static_cast<float>(midiNote - 21) / 87.0f;
return 0.10f + norm * 0.08f; // 0.10 to 0.18
}
// Contact duration scaling with velocity
// τ ∝ F_max^((1-p)/(2p))
// At higher velocity, contact is shorter
inline float getContactDurationScale(float velocity01, float p) {
// velocity01: 0 = pp, 1 = ff
// Returns multiplier relative to pp duration
float Fscale = 0.2f + 0.8f * velocity01; // Force scales with velocity
float exponent = (1.0f - p) / (2.0f * p);
return std::pow(Fscale, exponent);
}
}
//==============================================================================
// BRIDGE IMPEDANCE
//==============================================================================
namespace Bridge {
// Mean impedance values (kg/s)
// Source: Wogram, Conklin, Giordano
constexpr float Z_low = 1000.0f; // < 1 kHz
constexpr float Z_mid = 700.0f; // 1-2 kHz
constexpr float Z_high = 400.0f; // 2-5 kHz
constexpr float Z_veryHigh = 250.0f; // > 5 kHz
// Typical fluctuation around mean: ±12 dB
// High-pass cutoff frequencies
constexpr float bassBridgeHP_Hz = 70.0f;
constexpr float trebleBridgeHP_Hz = 140.0f;
// Bridge/soundboard coupling strength
constexpr float bassCoupling = 0.06f;
constexpr float trebleCoupling = 0.04f;
// Bass/treble split point
constexpr int splitMidi = 52; // E3
inline float getImpedance(float freqHz) {
if (freqHz < 1000.0f) return Z_low;
if (freqHz < 2000.0f) return Z_low - (freqHz - 1000.0f) * 0.0003f;
if (freqHz < 5000.0f) return Z_mid - (freqHz - 2000.0f) * 0.0001f;
return Z_veryHigh;
}
}
//==============================================================================
// DAMPER PARAMETERS
//==============================================================================
namespace Damper {
// Damping time constant when damper engages (approximate)
constexpr float engageTime_s = 0.015f; // 15 ms
// Final damping coefficient (very high = quick stop)
constexpr float maxDamping = 0.90f;
// Bass strings have more mass, damp slower
inline float getDampingRate(int midiNote) {
float norm = static_cast<float>(midiNote - 21) / 87.0f;
return 0.85f + norm * 0.10f; // 0.85 to 0.95
}
}
//==============================================================================
// OVERALL STRING DECAY (T60)
//==============================================================================
namespace StringDecay {
// Frequency-dependent T60 for string modes
// Higher partials decay faster
inline float getT60(float freqHz, int midiNote) {
// Base T60 varies with register
float norm = static_cast<float>(midiNote - 21) / 87.0f;
float baseT60 = 20.0f - norm * 16.5f; // 20s bass to 3.5s treble
// High frequencies decay faster (loss filter effect)
float freqFactor = 1.0f / (1.0f + freqHz / 5000.0f);
return baseT60 * freqFactor;
}
}
//==============================================================================
// VELOCITY MAPPING
//==============================================================================
namespace Velocity {
// MIDI velocity to hammer velocity (m/s)
// Typical range: 0.5 m/s (pp) to 5.5 m/s (fff)
inline float midiToHammerVelocity(int midiVel) {
float norm = static_cast<float>(midiVel) / 127.0f;
// Logarithmic-ish mapping for musical response
return 0.5f + 5.0f * norm * norm;
}
// Brightness tilt with velocity
// Louder = brighter (more high frequency content)
inline float getBrightnessTilt(float hammerVel) {
// 0 to 1 range
return std::min(1.0f, hammerVel / 5.0f);
}
}
} // namespace PianoPhysics