Events, pitch, Tuning

It’s not very common for me to have a question of this sort, but…

Is there a way to use Tuning on chromatic notes or diatonic notes?

It appears that Tuning applies only to \degree but not to \note.

So… one must have two Scales (one chromatic and one diatonic), and always use \degree?

I suspect there are some documentation improvements needed here. I don’t mind to handle that, but the design is not clear to me (and I’m relatively expert with the default event prototype!).

// from the chart
r = [1, 2187/2048, 9/8, 1968/1630, 81/64, 1771/1311, 729/512, 3/2, 6561/4096, 27/16, 5905/3277, 243/128];

// convert to semitones with offsets
// btw this was rather inconvenient to work out
c = log2(
	// ratio difference between r and et12
	r / (2 ** ((0..11) / 12))
)
* 12
+ (0..11);

t = Tuning(c, 2, \zh1);

(
p = Pbind(
	\degree, Pn(Pseries(0, 1, 12), 1),
	\dur, 0.5,
	\scale, Scale((0..11), 12, t),
	\callback, {
		[~freq, ~freq.cpsmidi - 60].debug("Hz, semitones above mid C");
	}
).play;
)

Hz, semitones above mid C: [261.6255653006, 0.0]
Hz, semitones above mid C: [279.38237857051, 1.1368500605771]  // OK
Hz, semitones above mid C: [294.32876096317, 2.0391000173077]

(
p = Pbind(
	\note, Pn(Pseries(0, 1, 12), 1),
	\dur, 0.5,
	\scale, Scale.major.copy.tuning_(t),
	\callback, {
		[~freq, ~freq.cpsmidi - 60].debug("Hz, semitones above mid C");
	}
).play;
)

Hz, semitones above mid C: [261.6255653006, 0.0]
Hz, semitones above mid C: [277.18263097687, 1.0]  // ...?
Hz, semitones above mid C: [293.66476791741, 2.0]

hjh

Isn’t \degree broken for chromatic scales, I remember something about IndexInBetween being broken causing \degree to produce wrong results? Not that it in any answers your question…

Looking at the relevant part of pitchEvent:

note: #{
    (~degree + ~mtranspose).degreeToKey(
        ~scale,
        ~scale.respondsTo(\stepsPerOctave).if(
            { ~scale.stepsPerOctave },
            ~stepsPerOctave
        )
    );
},
midinote: #{
    (((~note.value + ~gtranspose + ~root) /
        ~scale.respondsTo(\stepsPerOctave).if(
            { ~scale.stepsPerOctave },
            ~stepsPerOctave) + ~octave - 5.0) *
    (12.0 * ~scale.respondsTo(\octaveRatio).if
        ({ ~scale.octaveRatio }, ~octaveRatio).log2) + 60.0);
},

It appears that the conversion from \note to \midinote completely ignores tuning (only stepsPerOctave and octaveRatio are used).

The conversion from \degree to \note accesses the tuning through the degreeToKey message via scale.performDegreeToKey:

performDegreeToKey { | scaleDegree, stepsPerOctave, accidental = 0 |
    var baseKey;
    stepsPerOctave = stepsPerOctave ? tuning.stepsPerOctave;
    baseKey = (stepsPerOctave * (scaleDegree div: this.size)) + this.wrapAt(scaleDegree);
    ^if(accidental == 0) { baseKey } { baseKey + (accidental * (stepsPerOctave / 12.0)) }
}

this.wrapAt(scaleDegree) is where the tuning is accessed.

It is strange to me that we have degreeToKey, but not noteToKey, since the tuning is associated with all the steps in the scale, not just the degrees. I imagine a proper solution would need to add a Scale.performNoteToKey method, as well as a noteToKey method for every class that already has degreeToKey. And then you’d have to rewrite the \midinote key in pitchEvent to use ~note.value.noteToKey. I might try to implement this myself when I have time.

Yes, you’ve caught my point exactly. Tuning is written for a chromatic gamut but in the default event prototype, it applies only to scale degrees.

With the benefit of hindsight, I might have done it such that \degree accesses the diatonic items of the Scale, and \note accesses the chromatic items of the Tuning, and \midinote always assumes 12ET as it does now – basically the suggestion above.

Instead, AFAICS, there are only two ways to access chromatic tuning elements: 1. Use sharp/flat +0.1 / -0.1 accidentals. 2. Create a chromatic Scale.

The suggestion there was to divide scale degrees equally based on the number of semitones in that degree. This would make scale degree math highly inconvenient however, so I would decidedly not agree that it’s “broken.” JMc put in accidentals for a good reason.

hjh

1 Like

I wasn’t clear about this – keyToDegree was broken and may never have been fixed (I forget) – so the above at face value isn’t true.

The accidental concept is better than the alternative.

But this thread isn’t about keyToDegree anyway.

hjh

This should do it:

(
var makeScaleFrom, tuningWrapAt, degreeToNote, noteToMidinote;

makeScaleFrom = { arg scale;
    scale.isKindOf(Scale).if(
        {scale},
        {Scale(scale)}
    );
};

tuningWrapAt = { arg index, tuning;
    var extendedTuning;
    extendedTuning = tuning.tuning ++ [tuning.stepsPerOctave];
    extendedTuning.blendAt(index % tuning.size);
};

degreeToNote = { arg scaleDegree;
    var scale, degree, accidental, baseNote;
    scale = makeScaleFrom.(~scale);
    degree = scaleDegree.round.asInteger;
    accidental = (scaleDegree - degree) * 10.0;
    baseNote = (scale.pitchesPerOctave * (degree div: scale.size)) + scale.degrees.wrapAt(degree);
    baseNote + accidental;
};

noteToMidinote = {arg note;
    var scale, tunedNote, midiOctave;
    scale = makeScaleFrom.(~scale);
    tunedNote = scale.stepsPerOctave * (note div: scale.pitchesPerOctave) + tuningWrapAt.(note, scale.tuning);
    midiOctave = tunedNote / scale.stepsPerOctave + ~octave - 5.0;
    midiOctave * (12.0 * scale.octaveRatio.log2) + 60.0;
};


Event.addParentType(\note,
    (
        note: { degreeToNote.(~degree + ~mtranspose) },
        midinote: { noteToMidinote.(~note.value + ~gtranspose + ~root) },
    )
);
)

// test different tunings:
(
p = Pbind(
    \note, Pn(Pseries(-1, 1, 16), 1),
	\dur, 0.5,
    // \scale, Scale.major, // normal 12ET: OK
    \scale, Scale.major.tuning_(Tuning.just), // different tuning: OK
    // \scale, Scale.new((0, 2 .. 12), 14, Tuning.et(14)), // more than 12 pitches per octave: OK
    // \scale, Scale.new((0..6), 7, Tuning.et(7)), // less than 12 pitches per octave: OK
    // \scale, Scale.new([0, 2, 4, 5, 7, 9, 11], 12, Tuning.new((0..11) * 19/12, 3.0)), // different octave ratio: OK
    // \scale, Scale.new((0..5), 6, Tuning.new((0..5) * 19/6, 3)), // different octave ratio and pitches per octave: OK
	\callback, {
		[~freq, ~freq.cpsmidi - 60].debug("Hz, semitones above mid C");
	}
).play;
)