diff --git a/sound_algo/palette/note.scd b/sound_algo/palette/note.scd new file mode 100644 index 0000000..c7f6ac4 --- /dev/null +++ b/sound_algo/palette/note.scd @@ -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; +) diff --git a/sound_algo/tests/test_note.scd b/sound_algo/tests/test_note.scd new file mode 100644 index 0000000..af55f5b --- /dev/null +++ b/sound_algo/tests/test_note.scd @@ -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; +)