Thoughts on the Analysis of the Violin
In-depth explanation of the signal processing, mathematics, and file formats used in ObieApp. Originally written by Chris Rogers; equations rendered from the source LaTeX.
Contents
1 ยท Frequency Response Functions
1.1 What is a FRF?
We want to express the violin mathematically as a transfer function (also called the frequency response function) โ it converts some input (in this case a force from the hammer) to some output (in this case sound or a pressure fluctuation):
$$f(t) \;\to\; p(t)$$Taking the Fourier Transform, we can find the transfer function for the violin as:
$$H(f) = \frac{P(f)}{F(f)}$$where \(P(f)\) and \(F(f)\) are the Fourier transforms of pressure and force and have real and imaginary parts. The Fourier Transform is defined as:
$$G(f) = \int_{-\infty}^{\infty} g(t)\, e^{-i2\pi ft}\, dt$$We can simplify โ get the denominator to be real โ by multiplying numerator and denominator by \(F^*\):
$$H(f) = \frac{P(f)}{F(f)} = \frac{PF^*}{FF^*} = H_1 \qquad \frac{PP^*}{FP^*} = H_2$$1.2 Least Squares Fit โ H1 and H2
In reality, we take 5โ10 hits per position so that we can get a "best fit" reading. We know that:
$$P(f) = H(f)\, F(f)$$So the error of the best fit for measured values \(F_i\) and \(P_i\) is:
$$\epsilon_i(f) = P_i - H(f)F_i$$Realizing that \(H(f)\) is a complex number \((a+ib)\) and that \(F, P\) are complex as well:
$$\begin{aligned} \epsilon_i^2(f) &= (P_i - H(f)F_i)(P_i - H(f)F_i)^*\\ &= |P_i|^2 - H(f)F_iP^*_i - P_iH^*(f)F^*_i + |H(f)F_i|^2 \\ &= |P_i|^2 -(a+ib)F_iP^*_i - P_i(a-ib)F^*_i + |(a+ib)F_i|^2\\ &= |P_i|^2 -(a+ib)F_iP^*_i - P_i(a-ib)F^*_i + (a^2+b^2)F_iF_i^* \end{aligned}$$Summing over all readings and finding the minimum by differentiating with respect to \(a\) and \(b\) and setting equal to zero:
$$\begin{aligned} \frac{\partial \sum \epsilon_i^2(f)}{\partial a} &= 0 = -\sum F_iP^*_i - \sum P_iF^*_i + 2a\sum F_iF_i^*\\ \frac{\partial \sum \epsilon_i^2(f)}{\partial b} &= 0 = -i\sum F_iP^*_i + i\sum P_iF^*_i +2b\sum F_iF_i^* \end{aligned}$$Multiplying the second equation by \(i\) and then adding:
$$\begin{aligned} 0 &= -\sum F_iP^*_i - \sum P_iF^*_i + 2a\sum F_iF_i^*\\ 0 &= \sum F_iP^*_i -\sum P_iF^*_i +2bi\sum F_iF_i^* \end{aligned}$$Adding them together:
$$\begin{aligned} 0 &= -2 \sum P_iF^*_i + 2a\sum F_iF_i^* +2bi\sum F_iF_i^*\\ \sum P_iF^*_i &= H(f)\sum F_iF_i^* \end{aligned}$$This gives the H1 estimator (minimises output noise):
$$\boxed{H_1(f) = \frac{\sum P_iF^*_i}{\sum F_iF_i^*}}$$Alternatively, we can minimise the effect of noise on the input by using as the error:
$$\epsilon_i(f) = F_i - \frac{P_i}{H(f)}$$Expanding the squared error:
$$\begin{aligned} \epsilon_i^2(f) &= \left(F_i - \frac{P_i}{H(f)}\right)\!\left(F_i - \frac{P_i}{H(f)}\right)^*\\ &= F_iF_i^* - F_i\frac{P_i^*}{H^*} - F^*_i\frac{P_i}{H} + \frac{P_i^*}{H^*}\frac{P_i}{H}\\ &= F_iF_i^* - F_iP_i^*(a-ib)^{-1} - F^*_iP_i(a+ib)^{-1} + P_iP_i^*(a^2+b^2)^{-1} \end{aligned}$$Differentiating and setting to zero:
$$\begin{aligned} \frac{\partial \sum \epsilon_i^2}{\partial a} &= 0 = (a-ib)^{-2}\sum F_iP^*_i +(a+ib)^{-2}\sum P_iF^*_i -(a^2+b^2)^{-2}2a\sum P_iP_i^*\\ \frac{\partial \sum \epsilon_i^2}{\partial b} &= 0 = -i(a-ib)^{-2}\sum F_iP^*_i +i(a+ib)^{-2}\sum P_iF^*_i -(a^2+b^2)^{-2}2b\sum P_iP_i^* \end{aligned}$$Multiplying the second by \(i\) and adding:
$$\begin{aligned} 0 &= (a-ib)^{-2}\sum F_iP^*_i +(a+ib)^{-2}\sum P_iF^*_i -(a^2+b^2)^{-2}2a\sum P_iP_i^*\\ 0 &= (a-ib)^{-2}\sum F_iP^*_i -(a+ib)^{-2}\sum P_iF^*_i -(a^2+b^2)^{-2}2ib\sum P_iP_i^* \end{aligned}$$Adding and rearranging:
$$\begin{aligned} H(a^2+b^2)^{-2}\sum P_iP_i^* &= (a-ib)^{-2}\sum F_iP^*_i \\ H\sum P_iP_i^* &= \frac{(a+ib)^2(a-ib)^2}{(a-ib)^2}\sum F_iP^*_i \\ \sum P_iP_i^* &= H\sum F_iP^*_i \end{aligned}$$This gives the H2 estimator (minimises input noise):
$$\boxed{H_2(f) = \frac{\sum P_iP_i^*}{\sum F_iP^*_i}}$$If the data is clean and the noise is truly random, H1 and H2 should be very close and their ratio โ the coherence โ should be 1. George used the geometric mean as the working estimate:
$$H \sim \sqrt{H_1 H_2}$$1.3 Coherence
The coherence \(C\) measures how consistently the system behaves across repeated measurements:
$$C = \frac{H_1}{H_2} = \frac{\left|\sum P_i F_i^*\right|^2}{\sum F_i F_i^* \cdot \sum P_i P_i^*}$$The numerator is the cross-spectrum squared โ how much \(F\) and \(P\) move together. The denominator is the product of the individual power spectra โ how much each signal varies on its own. It is the frequency-domain analogue of \(R^2\) from linear regression, and answers: "how much of the output can be explained by the input?"
- \(C \approx 1\): output is well explained by input โ clean measurement, linear system.
- \(C \approx 0\): output has little to do with input at that frequency โ noise dominated, or the system simply doesn't respond there.
- \(0 < C < 1\): partial explanation โ could be noise, nonlinearity, or multiple inputs contributing to the output.
2 ยท Band Averages
We can look at the average distribution of energy by breaking the FRF into frequency bands and averaging the magnitudes over each band:
$$\begin{aligned} \bar{b} &= \frac{1}{N}\sum_{f=f_{\mathrm{lo}}}^{f_{\mathrm{hi}}} \sqrt{H(f)H^*(f)} \\[6pt] \bar{b}_{\mathrm{dB}} &= 20 \log_{10}\!\left(\bar{b}\right) \end{aligned}$$We also calculate the spectral centroid of the band โ the first moment โ which tells you where the energy is concentrated within the band:
$$c = \frac{\displaystyle\sum_{f=f_{\mathrm{lo}}}^{f_{\mathrm{hi}}} f\,\sqrt{H(f)H^*(f)}} {\displaystyle\sum_{f=f_{\mathrm{lo}}}^{f_{\mathrm{hi}}} \sqrt{H(f)H^*(f)}}$$In Python (Python/processing/bands.py):
f_lo = float(band['start']) f_hi = float(band['end']) mask = (freq >= f_lo) & (freq <= f_hi) H_band = H_mag[mask] # linear magnitude, NOT in dB f_band = freq[mask] avg_db = 20.0 * math.log10(max(float(np.mean(H_band)), 1e-12)) centroid = float(np.sum(f_band * H_band) / np.sum(H_band))
3 ยท Convolutions
We can estimate what an instrument will sound like by convolving its FRF with a recording โ ideally captured with a force sensor at the bridge rather than a microphone. The convolution of two functions \(f(t)\) and \(g(t)\) is:
$$y(t) = \int_{-\infty}^\infty f(\tau)\, g(t-\tau)\, d\tau$$What it does
A violin has resonances โ certain frequencies ring longer and louder than others. The FRF is a fingerprint of exactly how the violin shapes sound. Convolution applies that fingerprint to any audio signal. Feed in a Tchaikovsky recording; get out what it would sound like coloured by that violin's resonance structure.
FFT acceleration
Computing the integral directly is slow. The convolution theorem says we can instead multiply the two signals in the frequency domain:
$$\begin{aligned} y(t) &= \mathrm{iFFT}\!\bigl(\mathrm{FFT}(g(t))\times \mathrm{FFT}(f(t))\bigr)\\[4pt] &= \int_{-\infty}^\infty \left[\int_{-\infty}^\infty g(\tau)e^{-i2\pi f\tau}\,d\tau\right] \left[\int_{-\infty}^\infty f(\tau)e^{-i2\pi f\tau}\,d\tau\right] e^{i2\pi ft}\,df \end{aligned}$$In Python (Python/processing/convolution.py):
y = np.fft.irfft(
np.fft.rfft(wav, n_fft) * np.fft.rfft(ir.astype(np.float64), n_fft),
n_fft,
)[:N].real
The output is the input signal with the violin's character stamped onto it โ its resonant peaks amplified, its dead spots suppressed, its natural decay times imposed on every note.
IR length
The impulse response (IR) length determines frequency resolution. ObieApp uses an adaptive length: the next power of 2 above \(\text{sample\_rate} / \Delta f\), capped at 65536:
def _adaptive_ir_length(freqs, sample_rate):
df = freqs[1] - freqs[0]
natural = int(sample_rate / df)
next_pow2 = 1 << (natural - 1).bit_length()
return min(next_pow2, 65536)
Minimum-phase reconstruction
For real-only FRF files (AvR format, no phase information), the phase can be estimated by treating the system as minimum-phase โ for such systems the log-magnitude and phase are a Hilbert transform pair, so knowing one determines the other. In practice ObieApp stores all FRFs as complex (AvC or TRF), so minimum-phase reconstruction is not required.
4 ยท File Formats
All multi-byte fields are little-endian. Fields prefixed f in
the MtxVec TSingleArray descriptor are 32-bit single-precision floats; all others are
as specified. Sources: george.pas (Dew Research / DSP Master) and the LabVIEW
writer code.
4.1 TRF / TRV โ Transfer Function Format
Binary format used by Rational Acoustics / MtxVec. The header is 110 bytes, followed immediately by the data payload.
TFRFHeader (110 bytes, packed โ no padding)
| Field | Bytes | Type | Description |
|---|---|---|---|
| TFRFHeader packed record | |||
| index | 4 | Integer | Channel or measurement index. โฅ 0. |
| Xr | 8 | Double | Reference (input) channel or physical X location. |
| Yr | 8 | Double | Response (output) channel or physical Y location. |
| XActual | 8 | Double | Physical X-axis value (excitation location). |
| YActual | 8 | Double | Physical Y-axis value (response location). |
| FRFType | 1 | TFRFType (U8) | 0 = ftTransfer (.trf), 1 = ftPoint (.pnt). |
| DataType | 1 | TDataType (U8) | 0 = Accelerance, 1 = Mobility, 2 = Receptance, 3 = Microphone. |
| HzRes | 8 | Double | Frequency resolution ฮf (Hz/bin). > 0. |
| StartFrequency | 8 | Double | Start frequency (Hz). โฅ 0. |
| EndFrequency | 8 | Double | End frequency (Hz). > StartFrequency. |
| TSingleArray (MtxVec array descriptor โ all fields 4 bytes) | |||
| fComplex | 4 | Single | 0.0 = real magnitude, 1.0 = complex Re/Im, 2.0 = complex + coherence ฮณยฒ (Acquire format). |
| fLength | 4 | Single | Number of frequency bins N (stored as float). |
| a[2] | 4 | U32 | Reserved / alignment padding. |
| fConditionCheck | 4 | Single | Internal integrity value. |
| Precision | 4 | U32 | 0 = Single (32-bit), 1 = Double (64-bit). |
| MtxVecVersion | 4 | U32 | Version of MtxVec library that wrote the file. |
| SizeOf(TSample) | 4 | U32 | Byte size of each data sample. |
| Tag | 4 | U32 | User-defined tag or identifier. |
| MtxVecFileCode | 4 | U32 | Magic number identifying a MtxVec file. |
| a[9], a[10] | 4+4 | U32 | Reserved / alignment padding. |
| Payload | |||
| caption | 4 | String | Short label, null-padded. |
| FRF data (fComplex=0) | N ร 8 | float64[] | N real magnitude values, little-endian. |
| FRF data (fComplex=1) | N ร 16 | float64[] | N complex samples as interleaved Re/Im pairs. |
| FRF data (fComplex=2) | N ร 24 | float64[] | N samples as interleaved Re/Im/ฮณยฒ triples (Acquire). |
| Optional metadata block (Acquire-written files only) | |||
| sentinel | 11 | bytes | b'\x00OBIE_META\n' โ marks start of metadata. Old readers stop before this and ignore it. |
| key: value lines | var | UTF-8 | sample_rate, bit_depth, n_hits, threshold, ham_cutoff, mic_cutoff, device. |
4.2 AvR / AvC โ Averaged Spectrum Format
AvR files store a real-valued averaged spectrum; AvC files store a
complex-valued averaged spectrum. The file layout is identical except for the data
section, which is \(N \times 8\) bytes for AvR and \(N \times 16\) bytes for AvC.
\(N\) is given by fLength in the TSingleArray descriptor.
| Field | Bytes | Type | Description |
|---|---|---|---|
| Header | |||
| DataType | 1 | TDataType (U8) | Response quantity (same enum as TRF above). |
| HzRes | 8 | Double | Frequency resolution ฮf (Hz/bin). > 0. |
| StartFrequency | 8 | Double | Start frequency (Hz). โฅ 0. |
| EndFrequency | 8 | Double | End frequency (Hz). > StartFrequency. |
| ScaleFactor | 8 | Double | Scale factor applied to the averaged data. |
| nAverages | 4 | U32 | Number of averages used. Typically 1โ1000. |
| AverageType | 1 | Enum (U8) | 0 = RMS, 1 = Mean, 2 = Complex, 3 = Geometric, 4 = None. |
| TSingleArray (MtxVec array descriptor โ all fields 4 bytes) | |||
| fComplex | 4 | Single | 0.0 = real (AvR), 1.0 = complex (AvC). |
| fLength | 4 | Single | Number of data points N (stored as float). |
| a[2] | 4 | U32 | Reserved. |
| fConditionCheck | 4 | Single | Internal integrity value. |
| Precision | 4 | U32 | 0 = Single, 1 = Double. |
| MtxVecVersion | 4 | U32 | MtxVec library version. |
| SizeOf(TSample) | 4 | U32 | Byte size of each sample. |
| Tag | 4 | U32 | User-defined tag. |
| MtxVecFileCode | 4 | U32 | Magic number. |
| a[9], a[10] | 4+4 | U32 | Reserved. |
| caption | 4 | String | Short label, null-padded. |
| Data section | |||
| AvR data | N ร 8 | float64[] | N real-valued doubles, little-endian. Present when fComplex = 0. |
| AvC data | N ร 16 | float64[] | N complex samples as interleaved Re/Im double pairs. Present when fComplex = 1. |
| Notes (appended after data) | |||
| notes | var | String | Free-text annotation starting immediately after the data section. |
5 ยท Acoustic Wave Equation
This section derives the linearised acoustic wave equation from first principles and applies it to the mechanics of violin plates and the sound hole.
5.1 Wave Equation Derivation
Assume that the pressure is \(p\), the speed of a fluid particle is \(u\) with a mean of zero, and the density everywhere is \(\rho_0 + \rho\). The 1D continuity equation gives:
$$\frac{\partial(\rho_0 + \rho)}{\partial t} + \frac{\partial(\rho_0 + \rho)u}{\partial x} = 0$$Expanding and dropping the nonlinear term \(\partial(\rho u)/\partial x\):
$$\frac{\partial \rho}{\partial t} + \rho_0\frac{\partial u}{\partial x} = 0$$Differentiating again with respect to \(t\):
$$\frac{\partial^2 \rho}{\partial t^2} + \rho_0\frac{\partial^2 u}{\partial x\,\partial t} = 0$$The momentum equation (1D, no gravity or friction) linearised for small fluctuations:
$$\rho_0 \frac{\partial u}{\partial t} + \rho_0 u\frac{\partial u}{\partial x} + \frac{\partial p}{\partial x} = 0$$Differentiating with respect to \(x\) and dropping the middle (nonlinear) term:
$$\rho_0 \frac{\partial^2 u}{\partial t\,\partial x} + \frac{\partial^2 p}{\partial x^2} = 0$$Combining the continuity and momentum equations:
$$\frac{\partial^2 p}{\partial x^2} = \frac{\partial^2 \rho}{\partial t^2}$$Adiabatic closure
For an adiabatic process \(p = K\rho^\gamma\), so:
$$\frac{\partial p}{\partial x} = K\gamma\rho^{\gamma-1}\frac{\partial \rho}{\partial x} = c^2\frac{\partial \rho}{\partial x}$$Linearising the second time derivative:
$$\frac{\partial^2 p}{\partial t^2} = \gamma K\rho^{\gamma-1}\frac{\partial^2 \rho}{\partial t^2} = \gamma RT\frac{\partial^2 \rho}{\partial t^2}$$Substituting into the combined equation gives the acoustic wave equation:
$$\boxed{\frac{\partial^2 p}{\partial t^2} = \gamma RT\,\frac{\partial^2 p}{\partial x^2}}$$where the speed of sound \(c = \sqrt{\gamma RT}\).
5.2 Acoustic Impedance
Specific acoustic impedance \(z\) is the ratio of pressure to acoustic particle velocity: \(p = zu\), analogous to voltage driving a current.
For a plate with mass \(m\), stiffness \(k\), and damping \(\beta\), area \(A\), the equation of motion is:
$$\sum F = ma = pA - kx - \beta u$$Solving for impedance:
$$z = \frac{ma + kx + \beta u}{Au} = \frac{m}{A}\frac{\ddot{x}}{\dot{x}} + \frac{\beta}{A} + \frac{k}{A}\frac{x}{\dot{x}}$$Assuming a harmonic solution \(u = \dot{x} = Ce^{i\omega t}\):
$$z = r + i\omega M - \frac{i}{\omega C}$$where the lumped parameters are:
$$r = \frac{\beta}{A} \qquad M = \frac{m}{A} \qquad C = \frac{A}{k}$$This is the direct mechanical analogue of an electrical RLC circuit: resistance \(r\), inductance (mass) \(M\), and compliance (capacitance) \(C\).
For the sound hole, modelled with frictional resistance \(R_h\), and the top plate with restoring force:
$$m_{\text{hole}}\,\ddot{x}_h = A_h\,\Delta p - R_h\,\dot{x}_h$$ $$m_{\text{plate}}\,\ddot{x}_p = F - k_p x_p - R_p\,\dot{x}_p + A_p\,\Delta p$$5.3 Sound Intensity
Sound intensity is defined as the sound power per unit surface area. For a point source of power \(P\):
$$I = \frac{P}{4\pi r^2}$$Sound Power Level and Sound Intensity Level are referenced to \(P_0 = 10^{-12}\,\mathrm{W}\) and \(I_0 = 10^{-12}\,\mathrm{W/m^2}\) respectively:
$$\mathrm{SWL} = 10\log_{10}\!\left(\frac{P}{P_0}\right) \qquad \mathrm{SIL} = 10\log_{10}\!\left(\frac{I}{I_0}\right)$$Since \(I = pu = p^2/z\), the Sound Pressure Level is:
$$\mathrm{SPL} = 10\log_{10}\!\left(\frac{p^2}{p_0^2}\right) = 20\log_{10}\!\left(\frac{p}{p_0}\right)$$referenced to \(p_0 = 20\,\mu\mathrm{Pa}\).
5.4 Quality Factor
The quality factor \(Q\) characterises the sharpness of a resonance peak. For the damped oscillator \(m\ddot{x} + R\dot{x} + kx = 0\):
$$Q = \frac{f_{\text{center}}}{\Delta f} = \frac{\sqrt{mk}}{R} = \frac{\omega_0}{2r}$$where \(\Delta f\) is the half-power bandwidth (the โ3 dB width of the peak), \(r\) is the exponential decay rate, and \(R\) is the damping coefficient. Higher \(Q\) means a sharper, more persistent resonance.
User Guide
For instructions on using Explore, Acquire, and Convolve, see the ObieWebApp User Guide.