A JavaScript/TypeScript library for analyzing dissonance curves using William Sethares' sensory dissonance model. Dissonance curves show how sensory dissonance varies across a range of musical intervals, based on the spectral content of sounds.
For the theoretical foundation and detailed explanation of the model, see the book Tuning, Timbre, Spectrum, Scale by William Sethares.
bun add sethares-dissonance
# or
npm install sethares-dissonanceimport { DissonanceCurve } from "sethares-dissonance";
import { Spectrum } from "tuning-core";
const context = Spectrum.harmonic(10, 440);
const complement = Spectrum.harmonic(10, 440);
const curve = new DissonanceCurve({
context,
complement,
start: 1,
end: 2,
maxGapCents: 20,
firstOrderContribution: 1,
secondOrderContribution: 0.25,
thirdOrderContribution: 0.1,
phantomHarmonicsNumber: 3,
});
const points = curve.points;
const plotData = curve.plot(); // [ratio, dissonance] tuples
const plotDataCents = curve.plotCents(); // [cents, dissonance] tuplesBecause DissonanceCurve class mutates data in place, it is not directly suited for React applications as reference to the instance does not change. React will not detect dissonance curve mutations in dependency arrays or state and thus will not rerender. You can solve it in two ways.
Create a fresh DissonanceCurve whenever options change. useMemo ensures a new instance is created when dependencies change, so React detects the update and re-renders.
import { useMemo } from "react";
import { DissonanceCurve } from "sethares-dissonance";
function DissonanceChart({ context, complement, start = 1, end = 2 }) {
const curve = useMemo(
() =>
new DissonanceCurve({
context,
complement,
start,
end,
maxGapCents: 20,
firstOrderContribution: 1,
secondOrderContribution: 0.25,
thirdOrderContribution: 0.1,
phantomHarmonicsNumber: 3,
}),
[context, complement, start, end],
);
const plotData = curve.plot();
return <svg>{/* render plotData */}</svg>;
}NOTE: that curve object contains methods that can mutate the internal state of DissonanceCurve which will not be registered by React and will not cause a rerender.