sidetrack

55 views
Skip to first unread message

luserdroog

unread,
Dec 7, 2021, 9:42:11 PM12/7/21
to
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.

Is there a better way to do the running_sum()? I was trying to
do an APL "scan" and doing a reduce() while building the result
backwards was the best I could come up with.

const major_tetrachord = [ 2, 2, 1 ];
const mixolydian_mode = [ ...major_tetrachord,
...major_tetrachord,
2 ];
const minor_tetrachord = [ 2, 1, 2 ];
const aeolian_mode = [ ...minor_tetrachord,
...minor_tetrachord ];
const scale = [ ...mixolydian_mode,
...mixolydian_mode,
...major_tetrachord ];

function running_sum( a ){
return a.reduce( (pre,cur)=>[ cur+pre[0], ...pre ], [0] ).reverse();
}
console.log(running_sum(aeolian_mode));
console.log(running_sum(scale));

function note( str, octave=0 ){
var num = str.toLowerCase().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);
}

console.log(note("a",0));
console.log(note("c#",0));
console.log(note("eb",0));

Michael Haufe (TNO)

unread,
Dec 8, 2021, 4:03:49 PM12/8/21
to
On Tuesday, December 7, 2021 at 8:42:11 PM UTC-6, luser...@gmail.com wrote:

> Is there a better way to do the running_sum()? I was trying to
> do an APL "scan" and doing a reduce() while building the result
> backwards was the best I could come up with.

A `reduceRight` method exists on arrays:

<https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/ReduceRight>

luserdroog

unread,
Dec 8, 2021, 7:17:07 PM12/8/21
to
Hmm. The parameter names make that seem like the right choice. At least it gives
you an `accumulator`. But on closer inspection, reverse()'s `previousValue` behaves
exactly the same, and might have been (better IMO) called *accumulator*, too.

I could do it without reversing by just pushing each intermediate result (that's all
an APL scan is: an array of the intermediate results from a reduce operation, probably
ought to have defined it more precisely in OP).

function running_sum( a ){
var t = [];
a.reduce( (pre,cur)=>{t.push(pre+cur),pre+cur} );
return t;
}

It's not a "one liner", but this is probably a bit more efficient AFAICT.
In APL, it's just `+\`.

luserdroog

unread,
Dec 8, 2021, 7:23:12 PM12/8/21
to
On Wednesday, December 8, 2021 at 6:17:07 PM UTC-6, luserdroog wrote:

> function running_sum( a ){
> var t = [];
> a.reduce( (pre,cur)=>{t.push(pre+cur),pre+cur}

, 0

> );
> return t;
> }
>
> It's not a "one liner", but this is probably a bit more efficient AFAICT.
> In APL, it's just `+\`.

Need to use initial value zero (or prepend the original first element).
I really think that's right, now.

Michael Haufe (TNO)

unread,
Dec 9, 2021, 12:38:30 AM12/9/21
to
functionally:

const add = (left, right) => left + right

const sum = (xs) => xs.reduce(add,0)

sum([1,2,3,4,5]) // 15

luserdroog

unread,
Dec 9, 2021, 8:39:22 PM12/9/21
to
That does an APL reduce (coincidentally named) which looks like:

+/

But I'm after the subtly different function does a "scan" or piece-wise
reduce. In APL, it's almost imperceptibly different:

+\

(There's a joke that APL\360 was named with the backslash character
in there so the product name means "APL extends 360" if treated as
an APL program. \ means "extend" if the left and right are values, but
in the snippets under consideration \ means "scan" because the left
(+) is a function and not a value. I will endeavor to stop talking about
APL after this message.)

To be more functional, I think it would look like this:

Array.prototype.scan = function( f ){
return this.map( (e,i,a)=>a.slice(0,i+1).reduce(f,0) );
}
const add = (left,right) => left + right

function running_sum( a ){
return [ 0, ...a.scan( add )];
}

(I forgot I also needed the result of running_sum() to start with a zero,
in addition to the zero that gets fed to reduce() to ensure the first
element of the input array shows up in the output array.)

luserdroog

unread,
Dec 9, 2021, 8:44:39 PM12/9/21
to
dangitall.

> }
> const add = (left,right) => left + right
>
> function running_sum( a ){
> return [ 0, ...a.scan( add )];
> }
>
> (I forgot I also needed the result of running_sum() to start with a zero,
> in addition to the zero that gets fed to reduce() to ensure the first
> element of the input array shows up in the output array.)

Erhm. We don't need that zero in reduce() anymore. The map() takes
care of that.

luserdroog

unread,
Dec 9, 2021, 10:23:00 PM12/9/21
to
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 ]

Scott Sauyet

unread,
Dec 10, 2021, 10:26:48 AM12/10/21
to
luser wrote:

> But I'm after the subtly different function does a "scan" or piece-wise
> reduce. In APL, it's almost imperceptibly different:
> [ ...]
> To be more functional, I think it would look like this:
>
> Array.prototype.scan = function( f ){
> return this.map( (e,i,a)=>a.slice(0,i+1).reduce(f,0) );
> }
> const add = (left,right) => left + right
>
> function running_sum( a ){
> return [ 0, ...a.scan( add )];
> }

I find this much cleaner:

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)

running_sum ([1, 2, 3, 4, 5]) //=> [0, 1, 3, 6, 10, 15]


While this is more elegant:

const scan = (fn) => (init) => (xs) =>
xs .reduce ((ys, x) => [...ys, fn (ys .at (-1), x)], [init])

It will almost certainly be less efficient as it has to build a new
array on each iteration.

If `Array.prototype.at` is not available in your environment, it's
trivial enough to write your own `last` function and replace
`ys .at (-1)` with `last (ys)`.


-- Scott

Elhwen Dico

unread,
Dec 10, 2021, 10:35:14 AM12/10/21
to

function running_sum(a: number[]) {
return a.reduce(
(acc: number, current: number) => acc + current,
0);
}

console.log(running_sum([1, 2, 3]));
console.log(running_sum([]));

Elhwen Dico

unread,
Dec 10, 2021, 10:42:18 AM12/10/21
to

const running_sum = (a) => a.reduce((acc, curr) => acc + curr, 0);

console.log(running_sum([1, 2, 3]));
console.log(running_sum([]));


Thomas 'PointedEars' Lahn

unread,
Dec 10, 2021, 7:56:19 PM12/10/21
to
Elhwen Dico wrote:

> [top post]

Bats are supposed to post next door.

--
PointedEars
FAQ: <http://PointedEars.de/faq> | <http://PointedEars.de/es-matrix>
<https://github.com/PointedEars> | <http://PointedEars.de/wsvn/>
Twitter: @PointedEars2 | Please do not cc me./Bitte keine Kopien per E-Mail.

Julio Di Egidio

unread,
Dec 11, 2021, 4:12:32 AM12/11/21
to
On 11/12/2021 01:56, Thomas 'PointedEars' Lahn wrote:
> Elhwen Dico wrote:
>
>> [top post]
>
> Bats are supposed to post next door.

Since of course he has posted the best code so far in this thread...

And that's all you do, you incompetent spamming retarded piece of shit.

*Troll Alert*

Julio
Reply all
Reply to author
Forward
0 new messages