saebekassebil / teoria

Javascript taught Music Theory
http://saebekassebil.github.io/teoria
MIT License
1.32k stars 114 forks source link

Repeatedly adding intervals to a note results in the note's coord going out of bounds #56

Closed benmurrell closed 10 years ago

benmurrell commented 10 years ago

Here's a snippet that brings the problem to light:

var note = teoria.note( 'c2' );
for( var i = 0; i < 12; i++ ) {
    console.log( "Note: " + note.toString() + ", coord: " + note.coord + ", semitones from c2: " + teoria.note( 'c2' ).interval( note ).semitones() );
    note = note.interval( 'm2' );
}

Output:

Note: c2, coord: -1,-3, semitones from c2: 0 
Note: db2, coord: 2,-8, semitones from c2: 1 
Note: ebb2, coord: 5,-13, semitones from c2: 2 
Note: fbb2, coord: 8,-18, semitones from c2: 3 
Note: gundefined2, coord: 11,-23, semitones from c2: 4 
Note: aundefined2, coord: 14,-28, semitones from c2: 5 
Note: bundefined2, coord: 17,-33, semitones from c2: 6 
Note: cundefined3, coord: 20,-38, semitones from c2: 7 
Note: dundefined3, coord: 23,-43, semitones from c2: 8 
Note: eundefined3, coord: 26,-48, semitones from c2: 9 
Note: fundefined3, coord: 29,-53, semitones from c2: 10 
Note: gundefined3, coord: 32,-58, semitones from c2: 11 

As you can see, the interval is the correct number of semitones, but the coord of the resulting note differs significantly from what we would get if we just asked for it via teoria.note(). The out of bound coords breaks the calculation of accidentals, octaves, and note names - the last note logged should be b2.

I'm not sure what the coord represents exactly, so this is difficult for me to fix.

benmurrell commented 10 years ago

Modifying the add function as follows fixes my immediate use case:

function add(note, interval) {
    var ret = [note[0] + interval[0], note[1] + interval[1]];

    while( ret[1] <= -9 ) {
      ret[1] += 12;
      ret[0] -= 7;
    }

    return ret;
  }

Similar changes are probably needed for sub and mul, and checking for out of bounds on the other side would probably be good too. Or modify the various functions that use coords to do some sort of normalization before using the data - for instance, TeoriaNote.accidental() only looks at coord[1] and expects it to be in a certain range.

saebekassebil commented 10 years ago

Cheers Ben,

So what you are trying to do (I assume) is to generate a chromatic collection of note. In this case a chromatic scale from the note C.

The thing is, that theoretically this cannot be generated by continuously moving a minor second up (or down). Although it seems (on a piano/guitar) to have the same effect, you're actually generating more and more "far out" notes. This is because a minor second only "moves" a semitone, but at the same time "moves" a whole note in the scale.

As you can see teoria is trying its best in the beginning. C -> Db -> Ebb -> Fbb (notice how the note name changes every time you apply a second). But from here stuff gets difficult. This is because the next note would be called Gbbb and this is (in almost every case) completely useless.

If you want to generate a chromatic scale of notes you can do several things:

var chromatic = teoria.scale('C2', 'chromatic');
chromatic.notes(); // returns an array of twelve chromatic notes.

or if you want to be able to generate chromatic scales of arbitrary length (with no control of the theoretical intervals but just the semitone distance of 1), try something like:

function chromaticSequence(note, length) {
  var startkey = note.key(), sequence = [note];
  for (var i = 0; i < length; i++) {
    sequence.push(teoria.note.fromKey(startkey + i + 1);
  }

  return sequence;
}

chromaticSequence(teoria.note('C3'), 12).map(function(n) { return n.toString(); })
// -> ["c3", "db3", "d3", "eb3", "e3", "f3", "f#3", "g3", "g#3", "a3", "a#3", "b3", "c4"]
chromaticSequence(teoria.note('C3', 24).map(function(n) { return n.toString(); })
["c3", "db3", "d3", "eb3", "e3", "f3", "f#3", "g3", "g#3", "a3", "a#3", "b3", "c4", "db4", "d4", "eb4", "e4", "f4", "f#4", "g4", "g#4", "a4", "a#4", "b4", "c5"]

The internal .coord is the representation of the note, relative to the note A4. It's represented in the format [octaves, fifths]. This means that "to get to the note from A4" you would move octaves number of octaves and fifths number of fifths. I should probably document this better!

I'm closing this, as it's not an issue with the library (as far as I can tell), but feel free to reopen or question!

benmurrell commented 10 years ago

That makes sense - thanks!