Dissonance curve computation is the numerical procedure for measuring the sensory dissonance (roughness) of a sound spectrum and plotting how that dissonance varies as a spectrum is transposed against itself over an interval. The source supplies a reference implementation — dissmeasure.m in MATLAB — plus BASIC, C, LISP and Python ports, together with small utility routines for converting between musical ratios and cents. The algorithm and variable meanings are documented in the paper Relating Tuning and Timbre.
Key Points
A single function dissmeasure(fvec, amp) takes a list of partial frequencies and their amplitudes and returns a scalar dissonance value.
Dissonance curves are produced by calling that function repeatedly for alpha (the transposition ratio) swept from 1 up to roughly 2.3, doubling the partial list each time (f = [freq alpha*freq]).
The measure is a pairwise sum over partials, using an exponentially-approximated Plomp–Levelt roughness curve weighted by the softer of the two partial amplitudes.
The core constants are fixed: Dstar=0.24, S1=0.0207, S2=18.96, C1=5, C2=-5, A1=-3.51, A2=-5.75.
Companion routines rat2cent.m and cent2rat.m convert between frequency ratios and cents (1200 cents per octave); both accept scalars or vectors.
Implementations exist beyond MATLAB: BASIC (original), C (disscurve.cpp, later updated by Prof. Frink), LISP (Mauricio Rodriguez), and Python (endolith).
Concepts
Sensory dissonance / roughness — the beating-based unpleasantness of simultaneous partials, distinct from tonal or musical dissonance.
Critical bandwidth — encoded via the S = Dstar/(S1*Fmin + S2) term, which scales the frequency difference by a bandwidth that grows with frequency.
Dissonance curve — dissonance plotted as a function of the transposition interval alpha; its minima suggest consonant intervals for that timbre.
Cents — logarithmic pitch interval unit, 1200 per octave; the bridge between ratios and perceptual scale.
Partial (fvec, amp) — a spectral component's frequency and amplitude; the input representation of a timbre.
Details
The dissonance measure
dissmeasure(fvec, amp) sorts the partials by frequency, carries the amplitudes along (ams = amp(ind)), and then accumulates pairwise contributions. For each pair, the frequency of the lower partial Fmin and their absolute difference Fdif determine a scale factor S; the pair's contribution is
S = Dstar / (S1*Fmin + S2)
a = min(amp_i, amp_j)
Dnew = a * (C1*exp(A1*S*Fdif) + C2*exp(A2*S*Fdif))
The sum of Dnew over all pairs is the dissonance d. The two exponentials approximate the rising and falling flanks of the roughness curve: a positive term (C1=5) near zero frequency difference and a negative term (C2=-5) that decays the contribution at wider separations. Weighting by min(amp_i, amp_j) reflects that a partial can only beat against the weaker partner.
Generating a curve
freq = 500*[1 2 3 4 5 6 7]; amp = ones(size(freq));
range = 2.3; inc = 0.01; diss = [0];
for alpha = 1+inc:inc:range
f = [freq alpha*freq];
a = [amp, amp];
diss = [diss dissmeasure(f, a)];
end
plot(1:inc:range, diss)
Each step evaluates the dissonance of the timbre against a transposed copy of itself; plotting against alpha yields the classic curve whose troughs correspond to preferred intervals (an octave-ish harmonic spectrum shows a dip at 2).
Rendering diagram…
Ratio ↔ cents conversion
function cents = rat2cent(ratios)
cents = 1200/log10(2)*log10(ratio); % note: variable name mismatch in source
function ratios = cent2rat(cents)
ratio = 10.^((log10(2)/1200)*cents); % note: returned name mismatch in source
The formulas are the standard 1200-per-octave conversions; as listed in the source the argument/return variable names are inconsistent (ratios vs ratio), so the routines need trivial renaming to run.
BASIC reference implementation
The BASIC listing performs the same computation without vectorization: constants dstar=.24: s1=.0207: s2=18.96: c1=5.0: c2=-5.0, a1=-3.51: a2=-5.75; nested loops over the original partials freq(i) and the transposed partials g(j)=alpha*freq(k); the pair contribution uses MIN(amp(i),amp(j))*(c1*exp1+c2*exp2) with an underflow guard (IF arg < -88 THEN exp = 0) before each EXP. Results are printed per interval from startint=1 to endint=2.3 in steps of .01, and stored in a diss array for plotting. The BASIC form makes the pairwise nature of the measure explicit: every original partial is compared with every transposed partial, and the individual contributions are summed.
Portability notes
Because the measure is a simple double loop over partial pairs, it ports easily; the C, LISP and Python translations preserve the constants and structure of the MATLAB original. Any implementation needs only the partial frequency/amplitude vectors, a loop over the transposition ratio, and a plotting facility for the resulting curve.