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 ]
--- SoupGate-Win32 v1.05
* Origin: fsxNet Usenet Gateway (21:1/5)