feat: unified note/scale source of truth

This commit is contained in:
clement
2026-06-28 15:43:57 +02:00
parent 2aac8f57f6
commit fab35a5520
2 changed files with 63 additions and 0 deletions
+40
View File
@@ -0,0 +1,40 @@
// =====================================================================
// palette/note.scd -- Source de verite : gammes + degree->note
//
// Une seule definition de la resolution degree->note MIDI, consommee par
// les resolveurs (~ccNote concert, ~lpNote launchpad, ~fpResolve air-piano).
// Independant : aucun pre-requis, chargeable dans LES DEUX chemins de boot
// (LIVE et data-only) AVANT tout consommateur.
//
// NB : les arrays ~scaleXxx de palette/scales.scd (melodies LIVE, 8 notes
// sur-mesure) sont distincts et NON touches ici.
// Un seul bloc top-level (compatible .load).
// =====================================================================
(
// Gammes en OFFSETS de demi-tons depuis la fondamentale.
~scales = IdentityDictionary[
\minor -> [0, 2, 3, 5, 7, 8, 10],
\dorian -> [0, 2, 3, 5, 7, 9, 10],
\phrygian -> [0, 1, 3, 5, 7, 8, 10],
\penta -> [0, 2, 4, 7, 9], // pentatonique majeure (concert)
\minorPent -> [0, 3, 5, 7, 10],
\blues -> [0, 3, 5, 6, 7, 10],
\japanese -> [0, 2, 5, 7, 9],
\hijaz -> [0, 1, 4, 5, 7, 8, 10],
\hirajoshi -> [0, 2, 3, 7, 8]
];
// degree -> note MIDI, avec report d'octave (mod plancher gere les degres
// negatifs : -1 descend). root en MIDI, octave en octaves entieres.
~noteFor = { |degree, scaleName = \minor, root = 60, octave = 0|
var sc = ~scales[scaleName] ? ~scales[\minor];
var d = degree.asInteger;
(root + ((octave + (d div: sc.size)) * 12) + sc[d % sc.size]).asInteger;
};
~freqFor = { |degree, scaleName = \minor, root = 60, octave = 0|
~noteFor.(degree, scaleName, root, octave).midicps;
};
"[OK] palette/note.scd : ~scales, ~noteFor, ~freqFor".postln;
)
+23
View File
@@ -0,0 +1,23 @@
// Headless unit test for palette/note.scd.
// Run: sclang sound_algo/tests/test_note.scd
(
var ok = true, base;
base = thisProcess.nowExecutingPath.dirname.dirname ++ "/";
(base ++ "palette/note.scd").load;
// root note of minor pentatonic at root 60
if(~noteFor.(0, \minorPent, 60, 0) != 60) { ok = false; "FAIL: root".postln };
// degree 5 wraps one octave on a 5-note scale -> 72
if(~noteFor.(5, \minorPent, 60, 0) != 72) { ok = false; "FAIL: wrap".postln };
// minor degree 2 = +3 semitones -> 63
if(~noteFor.(2, \minor, 60, 0) != 63) { ok = false; "FAIL: minor".postln };
// concert parity: minor root 45 degree 2 -> 48 (old ~ccNote value)
if(~noteFor.(2, \minor, 45, 0) != 48) { ok = false; "FAIL: cc".postln };
// negative degree descends with floored mod: -1 on minor -> 58 (Bb, one step below C)
if(~noteFor.(-1, \minor, 60, 0) != 58) { ok = false; "FAIL: neg".postln };
// freq matches midicps
if(~freqFor.(0, \minorPent, 60, 0) != 60.midicps) { ok = false; "FAIL: freq".postln };
if(ok) { "PASS test_note".postln } { "TESTS FAILED".postln };
0.exit;
)