DissonanceCurve Analyzes timbres in order to create tunings
Uses Bill Seathares' Dissoncance Curve algorithm in order to compute tunings for a given timbre.
See also:
http://eceserv0.ece.wisc.edu/~sethares/consemi.html
Creation / Class Methods
*new (freqs, amps, highInterval )
Create a new curve based on arrays of data. This would mostly be used for additive synthesis.
freqs - An array of signficant frequencies of a timbre.
amps - An array of amplitudes for the given frequencies.
highInterval - The largest interval to compute in cents. For octave-based systems, you would usually use 1200. Default value is 1902.
*fm (carrier, modulator, depth, highInterval )
Create a curve based on the timbre of a given FM specification
carrier - The carrier frequency in Hz.
modulator - The modulation frequency in Hz.
depth - The depth (or delta range) of the modulator, in Hz.
highInterval - The largest interval to compute in cents. For octave-based systems, you would usually use 1200. Default value is 1902.
// Example
// This example shows how a fm synthdef might be related to a curve
(
SynthDef("fm", {arg out, amp, carrier, modulator, depth;
var sin;
sin = SinOsc.ar(SinOsc.ar(modulator, 0, depth, carrier));
Out.ar(out, sin * amp);
}).memStore;
)
(
var carrier, modulator, depth, curve, scale;
carrier = 440;
modulator = 600;
depth = 100;
curve = DissonanceCurve.fm(carrier, modulator, depth, 1200);
scale = curve.scale;
)
*fft (buffer, size, cutoff , highInterval , action)
Create a curve based on the timbre of a given FFT buffer. THIS IS VERY SLOW and uses a lot of system resources
buffer - An FFT Buffer.
size - The size of the Bufer in Frames.
cutoff - The lower cutoff amplitude for a bin. Defaults to -60 dB.
highInterval - The largest interval to compute in cents. For octave-based systems, you would usually use 1200. Default value is 1902.
Accessing Instance and Class Variables
tuning
Return a Tuning based on the minima of the Dissonance Curve
scale (size)
Returns a Scale in which the Tuning is the minima of the curve.
size - The number of degrees in the scale. For size n, it picks the n most consonant degrees. Defaults
to inf, which makes a Scale degree for every degree of the Tuning.
// Example
(
d = DissonanceCurve.fm(1563, 560, 1200, 1200);
c = d.scale(3);
c.degrees.postln;
)
plot
Plots the DissonanceCurve
// Example
(
d = DissonanceCurve.fm(1563, 560, 1200, 1200);
d.plot;
)
digestibleTuning (window)
Returns a Tuning related to Sethares' algorithm, but calculated using a modification of Calrence
Barlow's Digestibility Formula.
Basically, it does a comparison of every partial at every tuning, but instead of looking at Plomp and
Levit's ideas of local consonance, it compares the ratio relationships between the partials. The
numerator and denominator of this ratio is summed and then multiplied by the amplitude of the
quieter partial.
This algorythm just gives a list of the relative dissonance at all possible tunings. To figure out scale
degrees, the most consonant tuning is picked from it's neighbors, given the window size.
The tuning generated by this way and by the Sethares / Plomp and Levit algorithms may have
nothing to do with each other.
window - The idealized step size, in cents. To do normal-ish semitones, this would be set to 100,
which is the default.
// Example
(
d = DissonanceCurve.fm(100, 200, 300, 1200);
t = d.digestibleTuning(100);
t.cents.postln;
)
digestibleScale (window, size)
Returns a Scale based on the digestibleTuning.
window - The window size used to compute the digestibleTuning. Defaults to 100.
size - The number of degrees in the scale. For size n, it picks the n most consonant degrees. Defaults
to inf, which makes a Scale degree for every degree of the Tuning.
// Example
(
d = DissonanceCurve.fm(100, 200, 300, 1200);
c = d.digestibleScale(100, 5);
c.degrees.postln;
)
Examples
// Use this SynthDef for all examples
(
SynthDef("fm", {arg out = 0, amp = 0.2, dur = 1, midinote = 0, carrier, modulator, depth;
var sin, env, ratio;
// the ratios between the carrier, modulaor and depth should stay the same
// so multiply all of them by the current note
ratio = midinote.midiratio;
carrier = carrier * ratio;
modulator = modulator * ratio;
depth = depth * ratio;
env = EnvGen.kr(Env.sine(dur, amp), doneAction: 2);
sin = SinOsc.ar(SinOsc.ar(modulator, 0, depth, carrier), 0, env);
Out.ar(out, sin);
}).memStore;
)
// Using a Tuning from a DissonanceCurve
(
var carrier, modulator, depth, curve, tuning, note;
carrier = 700;
modulator = 600;
depth = 800;
curve = DissonanceCurve.fm(carrier, modulator, depth, 1200);
tuning = curve.tuning;
Task({
6.do({
note = tuning.semitones.choose;
Synth(\fm, [\dur, 0.7, \midinote, note,
\carrier, carrier,
\modulator, modulator,
\depth, depth]);
0.5.wait;
});
}).play;
)
// Use a Scale from a DissonanceCurve with a Pbind
(
var carrier, modulator, depth, curve, scale, degrees;
carrier = 440;
modulator = 600;
depth = 100;
curve = DissonanceCurve.fm(carrier, modulator, depth, 1200);
scale = curve.scale(5);
degrees = (0..scale.size); // make an array of all the scale degrees
// We don't know how many pitches per octave will be until after the
// DissonanceCurve is calculated. However, deprees outside of the range
// will be mapped accordingly.
Pbind(
\instrument, \fm,
\octave, 0,
\scale, scale,
\degree, Pseq([
Pseq(degrees, 1), // play one octave
Pseq([-3, 2, 0, -1, 3, 1], 1)], 1), // play other notes
\carrier, carrier,
\modulator, modulator,
\depth, depth
).play
)
// Use the digestible Scale
(
var carrier, modulator, depth, curve, scale, degrees;
carrier = 350;
modulator = 710;
depth = 505;
curve = DissonanceCurve.fm(carrier, modulator, depth, 1200);
scale = curve.digestibleScale(100, 7); // pick the 7 most consonant tunings
degrees = (0..(scale.size - 1)); // make an array of all the scale degrees
Pbind(
\instrument, \fm,
\octave, 0,
\scale, scale,
\degree, Pseq([
Pseq(degrees, 1), // play one octave
Pseq([-7, 2, 0, -5, 4, 1], 1)], 1), // play other notes
\carrier, carrier,
\modulator, modulator,
\depth, depth
).play
)