diff --git a/repl/src/tonal.mjs b/repl/src/tonal.mjs index 11e9952a..30cc126c 100644 --- a/repl/src/tonal.mjs +++ b/repl/src/tonal.mjs @@ -4,21 +4,27 @@ import { mod, tokenizeNote } from '../../util.mjs'; const Pattern = _Pattern; // as any; -export function scaleOffset(scale, offset, index = 0) { +// transpose note inside scale by offset steps +// function scaleTranspose(scale: string, offset: number, note: string) { +export function scaleTranspose(scale, offset, note) { let [tonic, scaleName] = Scale.tokenize(scale); - const [pc, acc, oct = 3] = tokenizeNote(tonic); let { notes } = Scale.get(`${tonic} ${scaleName}`); notes = notes.map((note) => Note.get(note).pc); // use only pc! offset = Number(offset); if (isNaN(offset)) { throw new Error(`scale offset "${offset}" not a number`); } - let i = index, + const { pc: fromPc, oct = 3 } = Note.get(note); + const noteIndex = notes.indexOf(fromPc); + if (noteIndex === -1) { + throw new Error(`note "${note}" is not in scale "${scale}"`); + } + let i = noteIndex, o = oct, - n = notes[0]; + n = fromPc; const direction = Math.sign(offset); // TODO: find way to do this smarter - while (Math.abs(i) < Math.abs(offset)) { + while (Math.abs(i - noteIndex) < Math.abs(offset)) { i += direction; const index = mod(i, notes.length); if (direction < 0 && n[0] === 'C') { @@ -31,19 +37,6 @@ export function scaleOffset(scale, offset, index = 0) { } return n + o; } -// transpose note inside scale by offset steps -// function scaleTranspose(scale: string, offset: number, note: string) { -export function scaleTranspose(scale, offset, note) { - let [tonic, scaleName] = Scale.tokenize(scale); - const { pc: fromPc } = Note.get(note); - let { notes } = Scale.get(`${tonic} ${scaleName}`); - const scalePcs = notes.map((n) => Note.get(n).pc); - const noteIndex = scalePcs.indexOf(fromPc); - if (noteIndex === -1) { - throw new Error(`note "${fromPc}" is not in scale "${scale}". Use one of ${scalePcs.join('|')}`); - } - return scaleOffset(scale, offset, noteIndex); -} // Pattern.prototype._transpose = function (intervalOrSemitones: string | number) { Pattern.prototype._transpose = function (intervalOrSemitones) { diff --git a/test/tonal.test.mjs b/test/tonal.test.mjs index 0b85d9f8..d2ec57fd 100644 --- a/test/tonal.test.mjs +++ b/test/tonal.test.mjs @@ -1,42 +1,12 @@ import { strict as assert } from 'assert'; -import { scaleTranspose, scaleOffset } from '../repl/src/tonal.mjs'; - -describe('scaleOffset', () => { - it('should transpose positive numbers', () => { - const c3Minor = ['C3', 'D3', 'Eb3', 'F3', 'G3', 'Ab3', 'Bb3', 'C4', 'D4', 'Eb4', 'F4', 'G4', 'Ab4', 'Bb4']; - c3Minor.forEach((n, i) => { - assert.equal(scaleOffset('C minor', i), n); - }); - const gMinor = ['G3', 'A3', 'Bb3', 'C4', 'D4', 'Eb4', 'F4', 'G4', 'A4', 'Bb4', 'C5', 'D5', 'Eb5', 'F5', 'G5']; - gMinor.forEach((n, i) => { - assert.equal(scaleOffset('G minor', i), n); - }); - }); - it('should transpose negative numbers', () => { - const c3MinorDown = ['C3', 'Bb2', 'Ab2', 'G2', 'F2', 'Eb2', 'D2', 'C2']; - c3MinorDown.forEach((n, i) => { - assert.equal(scaleOffset('C minor', -i), n); - }); - }); - it('should transpose scales with octave', () => { - const c4Minor = ['C4', 'D4', 'Eb4', 'F4', 'G4', 'Ab4', 'Bb4', 'C5', 'D5', 'Eb5', 'F5', 'G5', 'Ab5', 'Bb5']; - c4Minor.forEach((n, i) => { - assert.equal(scaleOffset('C4 minor', i), n); - }); - }); -}); +import { scaleTranspose } from '../repl/src/tonal.mjs'; describe('scaleTranspose', () => { it('should transpose inside scale', () => { - scaleTranspose('C minor', 0, 'C3'); - scaleTranspose('C minor', 1, 'D3'); - scaleTranspose('C minor', -1, 'Bb2'); - scaleTranspose('C minor', 8, 'C4'); - scaleTranspose('C4 minor', 8, 'C5'); - scaleTranspose('C# major', 8, 'C#4'); - scaleTranspose('C# major', -1, 'B#2'); - scaleTranspose('C# major', -2, 'A#2'); + assert.equal(scaleTranspose('C major', 1, 'C3'), 'D3'); + assert.equal(scaleTranspose('C major', 2, 'E3'), 'G3'); + assert.equal(scaleTranspose('C major', 1, 'E3'), 'F3'); + assert.equal(scaleTranspose('C major', 1, 'G3'), 'A3'); + assert.equal(scaleTranspose('C major', 1, 'C4'), 'D4'); }); }); - -// TODO: test tonal Pattern methods