๐ŸŽป

ObieWebApp

ยท Technical Reference
Tips โ† User Guide

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?"

Coherence is frequency-dependent. A violin might have \(C \approx 1\) near a resonance (where it responds strongly and predictably) but low coherence in a range where it barely responds and the output is dominated by noise. Always plot it across the full frequency range.

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)

FieldBytesTypeDescription
TFRFHeader packed record
index4IntegerChannel or measurement index. โ‰ฅ 0.
Xr8DoubleReference (input) channel or physical X location.
Yr8DoubleResponse (output) channel or physical Y location.
XActual8DoublePhysical X-axis value (excitation location).
YActual8DoublePhysical Y-axis value (response location).
FRFType1TFRFType (U8)0 = ftTransfer (.trf), 1 = ftPoint (.pnt).
DataType1TDataType (U8)0 = Accelerance, 1 = Mobility, 2 = Receptance, 3 = Microphone.
HzRes8DoubleFrequency resolution ฮ”f (Hz/bin). > 0.
StartFrequency8DoubleStart frequency (Hz). โ‰ฅ 0.
EndFrequency8DoubleEnd frequency (Hz). > StartFrequency.
TSingleArray (MtxVec array descriptor โ€” all fields 4 bytes)
fComplex4Single0.0 = real magnitude, 1.0 = complex Re/Im, 2.0 = complex + coherence ฮณยฒ (Acquire format).
fLength4SingleNumber of frequency bins N (stored as float).
a[2]4U32Reserved / alignment padding.
fConditionCheck4SingleInternal integrity value.
Precision4U320 = Single (32-bit), 1 = Double (64-bit).
MtxVecVersion4U32Version of MtxVec library that wrote the file.
SizeOf(TSample)4U32Byte size of each data sample.
Tag4U32User-defined tag or identifier.
MtxVecFileCode4U32Magic number identifying a MtxVec file.
a[9], a[10]4+4U32Reserved / alignment padding.
Payload
caption4StringShort label, null-padded.
FRF data (fComplex=0)N ร— 8float64[]N real magnitude values, little-endian.
FRF data (fComplex=1)N ร— 16float64[]N complex samples as interleaved Re/Im pairs.
FRF data (fComplex=2)N ร— 24float64[]N samples as interleaved Re/Im/ฮณยฒ triples (Acquire).
Optional metadata block (Acquire-written files only)
sentinel11bytesb'\x00OBIE_META\n' โ€” marks start of metadata. Old readers stop before this and ignore it.
key: value linesvarUTF-8sample_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.

$$\text{AvR: } N \times 8\text{ bytes} \qquad \text{AvC: } N \times 16\text{ bytes} \qquad N = \texttt{int(fLength)}$$
FieldBytesTypeDescription
Header
DataType1TDataType (U8)Response quantity (same enum as TRF above).
HzRes8DoubleFrequency resolution ฮ”f (Hz/bin). > 0.
StartFrequency8DoubleStart frequency (Hz). โ‰ฅ 0.
EndFrequency8DoubleEnd frequency (Hz). > StartFrequency.
ScaleFactor8DoubleScale factor applied to the averaged data.
nAverages4U32Number of averages used. Typically 1โ€“1000.
AverageType1Enum (U8)0 = RMS, 1 = Mean, 2 = Complex, 3 = Geometric, 4 = None.
TSingleArray (MtxVec array descriptor โ€” all fields 4 bytes)
fComplex4Single0.0 = real (AvR), 1.0 = complex (AvC).
fLength4SingleNumber of data points N (stored as float).
a[2]4U32Reserved.
fConditionCheck4SingleInternal integrity value.
Precision4U320 = Single, 1 = Double.
MtxVecVersion4U32MtxVec library version.
SizeOf(TSample)4U32Byte size of each sample.
Tag4U32User-defined tag.
MtxVecFileCode4U32Magic number.
a[9], a[10]4+4U32Reserved.
caption4StringShort label, null-padded.
Data section
AvR dataN ร— 8float64[]N real-valued doubles, little-endian. Present when fComplex = 0.
AvC dataN ร— 16float64[]N complex samples as interleaved Re/Im double pairs. Present when fComplex = 1.
Notes (appended after data)
notesvarStringFree-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.