chord names to midi note numbers
From
luserdroog@21:1/5 to
All on Sun Dec 12 19:47:08 2021
This is the same code from my previous thread, but with the sweet-
as-pie two liner *scan* function and copious pretentious comments.
Also, better variable names and more `const` everywhere.
I added a helper function `up()` to write the figured bass representation
of chord shapes with having to count up so high. OTOH seeing the
numbers up to 15 gives you a reasonable intuition about fancy chords
like 9-, 11-, 13- chords. It still looks prettier to me using `up()`.
//
//midi note number[s] for a note(name,octave) or chord(name,octave)
//a la luser droog la la la
//a note name is a case-insensitive note letter 'a'..'g', 'A'..'G'
// optionally followed by '#' for sharp or 'b' for flat
//a chord name is a case-insensitive note letter
// optionally followed by an accidental '#'|'b'
// optionally followed by 'm' for minor
// optionally followed by '7' for a 7th chord
const major_tetrachord = [ 2, 2, 1 ];
const minor_tetrachord = [ 2, 1, 2 ];
//there are 2 other ancient, weird tetrachords with odd intervals
//(enharmonic and chromatic) that don't fit with our schema here
//and cannot reasonably be accommodated in a system of linear half-steps IMO
//mode names according to Boethius
const ionian_mode = [ ...major_tetrachord, //defined by Nicomachus as two disjunct
2, //tetrachords separated by a tone
...major_tetrachord ];
//these are intervals between adjacent notes of the mode, ie.
//ionian_mode[0] is the interval between the root and the major second (2 half steps)
//ionian_mode[1] " " " " the second and the major third (2 more half steps)
//ionian_mode[2] " " " " the third and the fourth (1 half step) //...
//use repeat() to make longer sequences of intervals
//use running_sum() to convert to a sequence of note numbers
// which can be indexed by scale degree, ie.
//running_sum(ionian_mode)[0] = 0 the root
//running_sum(ionian_mode)[1] = 2 the major second //running_sum(ionian_mode)[2] = 4 the major third //running_sum(ionian_mode)[3] = 5 the perfect fourth (note these are also fret numbers)
//...
const mixolydian_mode = [ ...major_tetrachord, //need this for 7th chords and stuff
...major_tetrachord, //like A/E and C/G chords
2 ]; //defined by Nicomachus as two conjoint tetrachords
const aeolian_mode = [ ...minor_tetrachord, //need this for decoding note names ['A'..'G']
...minor_tetrachord ]; //add another 2 to fill the octave if needed
const scan = (fn) => (init) => (xs) =>
xs.reduce( (ys,x)=> ( (ys.push( fn(ys.at(-1),x) )), ys ), [init] );
const running_sum = scan( (a,b)=> a+b )( 0 );
const long_scale = (scale)=> repeat( 4, scale );
const repeat = (n, scale)=>
n==0 ? [] :
n==1 ? scale :
n%2 ? [ ...repeat(n/2,scale), ...repeat(n/2,scale), ...scale ] :
[ ...repeat(n/2,scale), ...repeat(n/2,scale) ] ;
const up = (...x)=> x.map(x=> x+7);
console.log(running_sum(aeolian_mode)); console.log(running_sum(long_scale(mixolydian_mode)));
function note( str, octave=0 ){
const num = str.toUpperCase().charCodeAt(0) - 'A'.charCodeAt(0);
const accidental = (str.length > 1 ? (str[1]=='#' ? 1 : -1) : 0);
const fromA = running_sum(aeolian_mode)[ num ] + accidental;
const fromC = fromA + 9;
const thenote = (fromC >= 12 ? fromC - 12 : fromC);
return thenote + octave*12;
}
function chord( str, octave=0 ){
var accidental = 0;
var minor = 0;
var is7th = 0;
const midiA = -3; //+9 (midi number of 'A') -12 (lower octave)
const num = str.toUpperCase().charCodeAt(0) - 'A'.charCodeAt(0);
const root = running_sum(aeolian_mode)[ num ];
str = str.slice(1);
if( str.length > 0 ){
accidental = str[0]=='#' ? 1
: str[0]=='b' ? -1 : 0;
if( accidental ){ str = str.slice(1); }
}
if( str.length > 0 && str[0]=='m' ){
minor = 1;
str = str.slice(1);
}
if( str.length > 0 && str[0]=='7' ){
is7th = 1;
}
const template = is7th ? [ 1,5,7,8,...up(3,5,7,8) ] //figured bass for chord shape
: [ 1,5, 8,...up(3,5, 8) ];
var mode = [ ...mixolydian_mode ];
mode[1] -= minor; //minor chord uses a flat 3rd, ie. the 2nd interval
mode[2] += minor; //balance the adjacent interval
const scale = running_sum( long_scale( mode ) );
const chord = template.map( x=> scale[x-1]
+ root + accidental + midiA
+ octave*12 )
.filter( x=> x>=0 );
return chord;
}
console.log(note("a",0));
console.log(note("c#",0));
console.log(note("eb",0));
console.log(chord("a"));
console.log(chord("am"));
console.log(chord("a7"));
console.log(chord("ebm7",4));
Output (firefox console copy+paste):
Array(7) [ 0, 2, 3, 5, 7, 8, 10 ]
fim.js:56:9
Array(29) [ 0, 2, 4, 5, 7, 9, 10, 12, 14, 16, … ]
fim.js:57:9
9 fim.js:106:9
1 fim.js:107:9
3 fim.js:108:9
Array(5) [ 4, 9, 13, 16, 21 ]
fim.js:109:9
Array(5) [ 4, 9, 12, 16, 21 ]
fim.js:110:9
Array(7) [ 4, 7, 9, 13, 16, 19, 21 ]
fim.js:111:9
Array(8) [ 51, 58, 61, 63, 66, 70, 73, 75 ]
fim.js:112:9
--- SoupGate-Win32 v1.05
* Origin: fsxNet Usenet Gateway (21:1/5)