Dissonance Generate dissonance curves from roughness analysis 


Inherits from: Object


Dissonance curves are made by calculating the psychoacoustic roughness between two sets of partials, usually both sets coming from the same source. The partials are weighted against each other according to a critical band model of auditory roughness; this makes one point of the curve, the rest of the points are arrived at by transposing one of the sets. Once the dissonance curve is calculated, a PitchSet is formed out of the rationalized local minima. This makes it possible to compose with harmonic material derived from timbre.


See also: LoudnessModel, IntervalTable, PitchSet, HarmonicVector, HarmonicMetric 

Topic: Roughness and sensory dissonance. En español: CNA


External methods required:


This class uses lots of extensions to SimpleNumber, Integer and SequenceableCollection available in numberExtras.sc which contain the formulas for harmonicity, unit conversion, rationalization, etc. There are other methods in plusSignal.sc handy for calculating dissonance curves from FFT data.



Creation / Class Methods


*make(f, a, start = 0.99, end = 2.01, inc = 0.01, method = \parncutt, max = false)

Make a dissonance curve for the array of partials f with array of amplitudes a. A set of partials is measured against transpositions of itself, yielding a profile of its sensory dissonance behavior. The time of the calculations will slow down exponentially as the number of partials increases, 8-10 gives good results, depending on the source sound and the kind of application.

f -  An array of partials in Hz. 

a  - Array of amplitudes. If the method \parncutt is used then they should be expressed as loudnesses in sones. See LoudnessModel.

start  - Start frequency factor for the transpositions of the analysis. It is usually set a little lower than the lowest interval of interest, e.g. 1 would correspond to unison, so its good to start a bit before, at 0.99.  

end  - End frequency factor for the transposition of the analysis. Here it ends a bit after and octave, e.g. 2.01. 

inc  - Increment of the frequency factor, default 0.01, a hundredth of an octave. 

method  - Either \parncutt or \sethares. This class was first made out of code from W. Sethares' book Tuning, Timbre, Spectrum, Scale. This model is based on frequency and amplitude. Later on, the algorithm was refined to use Richard Parncutt's model based on subjective units of barks and sones. This method gives more precise curves with more meaningful roughness measures and is also a bit faster, so there really is no need to use the \sethares setting.

max - Either true or false. This is to tell the analysis which partial it should consider to be the fundamental of the spectrum. If true then the fundamental will be the partial with the highest amplitude, otherwise the first one in the frequency array. 


// example, execute line by line

d = Dissonance.make([100,200,300,400,500],[32, 16, 8, 4, 2], 0.49, 4.01, 0.01);

//-> [1/2, 3/5, 2/3, 3/4, 4/5, 1/1, 5/4, 4/3, 3/2, 5/3, 2/1, 5/2, 3/1, 4/1]

d.plot; // plot the curve, x = interval, y = roughness, based on Plotter, so needs SC 3.4

d.play; //listen to the scale

Instance variables:


d.scale; // the scale derived from the minima of the curve, as frequency factors

d.ratios; // the scale expressed as ratios in the form of [p,q] array pairs

d.roughness.round(0.01); // the roughness at each scale degree 

d.fund * d.scale; // the scale in Hz

d.harmonicAnalysis;

d.pitchSet; // the ratios are part of a PitchSet to have proper harmonic use of them.

// For more, see below and its related helpfile.

d.partials; // the original freqs and amps

d.postln; // pitch sets are represented as strings with the oblique p/q



d.harmonicity.round(0.001); // The harmonicity measure for each ratio. Its helpful for 

// filtering out notes of the scale according to their harmonic intensity.


*make2(f, g, a, b, start = 0.99, end = 2.01, inc = 0.01, method = \parncutt, max = false)

Make a dissonance curve derived from the roughness measure between two different sets of partials f and g with amplitude arrays a and b. The array g will be swept (transposed) relative to f, which remains fixed.


f, g -  Arrays of partials in Hz.

a, b  - Arrays of amplitudes. If the method \parncutt is used then they have to be expressed in sones. For this use the class LoudnessModel.

Other arguments are the same as in *make. In the case of the max argument, the fundamental will be chosen from the first set of partials.


// example

f = [100,200,300,400,500];  // a harmonic spectrum

g = [133, 144, 233, 566, 788]; // a non harmonic one

a  = Array.fill(5, {|i| (i + 1).reciprocal}); // amplitude array 1/n

b = Array.rand(5, 0.125, 1.0); // random amplitudes

// convert amplitudes into sones: 

t = [f, a.ampdb].flop.asSone(90).round(0.01);

u = [g, b.ampdb].flop.asSone(90).round(0.01);

d = Dissonance.make2(f, g, t, u, 0.49, 4.01, 0.01); // analysis over 3 octaves

d.plot // the relation resulting from the two roughness profiles

d.play // and a strange scale



The following two methods are usefull when spectrums are in the format of a Signal.stft. Before doing the same things as *make, they derive the frequencies from the bins and select the n strongest partials. 


*makeFromSpectrum(f, start = 1.0, end = 2.3, inc = 0.01, method = \parcutt, max = false, 

numPartials = 10, win = 256, sr = 44100)


*makeFrom2Spectrums(f, g, start = 1.0, end = 2.3, inc = 0.01, method = \parcutt, max = false, 

numPartials = 10, win = 256, sr = 44100)


Instance methods: 


Local minima of dissonance curves frequently fall close to harmonically strong interval ratios. Due to the limitations of floating-point math as well as the coarse intervallic resolution of the curves, the rationalization can result in either incorrect ratios, or ones that fall very close to harmonically interesting ones. The following instance methods permit analysis and correction of the derived scales. The analysis is done by making a list of candidates lying within a certain tolerance range from each interval and picking the one with the highest harmonic intensity. See class IntervalTable and method rationalize from numberExtras.

harmonicAnalysis(tolerance = 16, metric, type = \size, max, unisonvector, post = false)

Completes the dissonance analysis by performing a harmonic analysis on the ratios. It rationalizes the ratios according to tolerance, metric, type and max (see rationalize) and then makes a PitchSet with those intervals. The PitchSet in turn converts ratios into HarmonicVectors in order to partition them (according to unisonvector) into timbral and harmonic sets. The Dissonance object keeps track of all this in its instance variables pitchSet; its ratios variable is replaced with the new rationalized intervals. 

tolerance (cents) defines the range over which the search for intervals will be made. The default 

is the deviation in cents of the 6/5 just minor third with respect to a tempered one. 

metric is either an instance of HarmonicMetric or a symbol: 

\harmonicity, \harmonicDistance or \gradusSuavitatis

If no metric is specified, \harmonicity will be used. 

type is either \size or \prime. If \size is chosen, intervals with numerator and denominator larger than

max (which defaults to [729, 512]) will not be used. If \prime is chosen, intervals containing prime 

factors larger than max (which defaults to 19) will be ignored. Defaults to \size.


unisonvector one of the several unison vector matrices that are part of PitchSet.unisons. They are used to partition and interval set into harmonic and timbral intervals according to which intervals are to be considered 'unisons' (like 81/64 and 5/4, which differ by a syntonic comma). The default one is PitchSet.unisons.dim3.et12[1] which partitions into 12 tones per octave in 3 dimensional harmonic space(corresponding to primes 3, 5 and 7). See PitchSet

post if true, will post the intervals before and after rationalization as well as showing the pitch set.

// example, execute line by line:

f = Array.series(18, 98, 98); // test tone, 18 partials of G2

f = f.collect{|x| x = [x*rrand(0.98,1.02), x].choose}; // shuffle some partials a little

a = Array.fill(18, {|x| (x+1).reciprocal}); // tone amps (1/n)

// convert amps to sones (90 dB spl reference):

t = [f, a.ampdb].flop.asSone(90).round(0.01);

d = Dissonance.make(f, t, 0.249, 8.01, 0.01); // make curve

// check which intervals are replaced into more harmonically meaningful ones

d.harmonicAnalysis(post: true); 

d.plot; // the curve itself

d.play; // listen as scale




reduce(maxDenom = 18)

'reduces' the scale by eliminating denominators higher than (but not including) maxDenom. Instance variables scale, ratios and harmonicity will be updated.

// following from the previous example

d.reduce(32); // things like 81/64 and 256/135 are deleted

d.play;

factor(maxPrime = 7)

'factorizes' the scale by eliminating ratios with prime factors higher than maxPrime. Instance variables scale, ratios and harmonicity will be updated. 

// following from previous example

d.factor(7); // any 11-based or higher intervals are gone 

d.play;

filter(aPrime = 5)

filters out all ratios which have aPrime as a factor. The instance variables scale, ratios and harmonicity will be updated.

// following from the previous example, to eliminate all the 5's: 

d.filter(5); // all intervals derived from just major thirds are gone

d.play;

...


Dissonance: (2006-2007), juan sebastián lach lau http://web.me.com/jslach/