On Tuesday, December 7, 2021 at 8:42:11 PM UTC-6, luserdroog wrote:
> I'm making some progress at reconceiving my pianoroll app
> to be a more coherent object based design. But also kinda
> not making progress on it. I doodled up a sketch of the overall
> structure of the MVC example but instead of working on that,
> here's a "robust" function for decoding note names from letters
> and producing a MIDI note number.
>
> It uses two minor tetrachords to construct the intervals of an
> aeolian scale then takes a running sum to get "fretstops" for
> each of the scale degrees. Then you index the resulting array
> with 0 for 'a', 1 for 'b' etc, a little more twiddling and poof kazow
> presto! The desired number comes out the other end.
Revised and expanded code from OP. It can now produce an array
of midi note numbers given a chord name (like "Bbm" or "F#7").
Some explanation of how I'm using the scale and the chord template
is in this post:
https://music.stackexchange.com/a/6181/1344
Adding Major 7th chords would require some reworking like starting
with a different mode. Another nice extension would be an option
for closed vs. open voicing or a spectrum or palette of voicings.
const major_tetrachord = [ 2, 2, 1 ];
const minor_tetrachord = [ 2, 1, 2 ];
//mode names according to Boethius
const ionian_mode = [ ...major_tetrachord, //defined by Nicomachus as two disjunct
2, //tetrachords separated by a tone
...major_tetrachord ];
const mixolydian_mode = [ ...major_tetrachord, //need this for 7th chords and stuff
...major_tetrachord, //like A/E and C/G chords
2 ];
const aeolian_mode = [ ...minor_tetrachord, //defined by Nicomachus as two conjoint
...minor_tetrachord ]; //tetrachords
const long_mix_scale = [ ...mixolydian_mode,
...mixolydian_mode,
...major_tetrachord ];
const long_scale = ( scale )=>[ ...scale, ...scale, ...scale ];
Array.prototype.scan = function( f ){
return this.map( (e,i,a)=>a.slice(0,i+1).reduce(f) );
}
Array.prototype.scan_v2 = function( f ){
var r = [];
this.reduce( (left,right)=>{ var t = f(left,right); r.push(t); return t; }, 0 );
return r;
}
const add = (left,right) => left + right
const running_sum = ( a ) => [ 0, ...a.scan( add ) ]
console.log(running_sum(aeolian_mode));
console.log(running_sum(long_mix_scale));
console.log(running_sum(long_scale(mixolydian_mode)));
function note( str, octave=0 ){
var num = str.toUpperCase().charCodeAt(0) - 'A'.charCodeAt(0);
var fromA = running_sum(aeolian_mode)[ num ] +
(str.length > 1 ? (str[1]=='#' ? 1 : -1)
: 0);
var fromC = fromA + 9;
return octave*12 + (fromC >= 12 ? fromC - 12 : fromC);
}
function chord( str, octave=0 ){
var off = 0;
var tweak = 0;
var is7th = 0;
var twiddle = -3; //+9 (midi number of 'A') -12 (lower octave)
var num = str.toUpperCase().charCodeAt(0) - 'A'.charCodeAt(0);
var base = running_sum(aeolian_mode)[ num ];
str = str.slice(1);
if( str.length > 0 ){
off = str[0]=='#' ? 1
: str[0]=='b' ? -1
: 0;
if( off ){ str = str.slice(1); }
}
if( str.length > 0 && str[0]=='m' ){
tweak = -1;
str = str.slice(1);
}
if( str.length > 0 && str[0]=='7' ){
is7th = 1;
}
var template = is7th ? [ 1,5,7,8,10,12,14,15 ] //figured bass for chord shape
: [ 1,5, 8,10,12, 15 ];
var mode = [ ...mixolydian_mode ];
mode[1] += tweak; //minor chord uses a flat 3rd
mode[2] -= tweak; //balance the adjacent interval
//console.log( mode );
//console.log( running_sum( mode ) );
var scale = running_sum( long_scale( mode ) );
//console.log( scale );
var chord = template.map( x=> scale[x-1] + base + off + twiddle );
chord = chord.map( x=> x+octave*12 );
chord = chord.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:
9
1
3
Array(5) [ 4, 9, 13, 16, 21 ]
Array(5) [ 4, 9, 12, 16, 21 ]
Array(7) [ 4, 7, 9, 13, 16, 19, 21 ]
Array(8) [ 51, 58, 61, 63, 66, 70, 73, 75 ]