• 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)