1/* 2tonal.test.mjs - <short description TODO> 3Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/tonal/test/tonal.test.mjs> 4This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. 5*/ 6 7// import { strict as assert } from 'assert'; 8 9import '../tonal.mjs'; // need to import this to add prototypes 10import { pure, n, seq, note, noteToMidi } from '@strudel/core'; 11import { describe, it, expect } from 'vitest'; 12import { mini } from '../../mini/mini.mjs'; 13 14describe('tonal', () => { 15 describe('scaleTranspose', () => { 16 it('transposes notes by scale degrees', () => { 17 expect(pure('c3').scale('C major').scaleTranspose(1).firstCycleValues).toEqual(['D3']); 18 }); 19 }); 20 describe('scale', () => { 21 it('converts plain values', () => { 22 expect( 23 seq(0, 1, 2) 24 .scale('C major') 25 .note() 26 .firstCycleValues.map((h) => h.note), 27 ).toEqual(['C3', 'D3', 'E3']); 28 }); 29 it('converts n values', () => { 30 expect( 31 n(seq(0, 1, 2)) 32 .scale('C major') 33 .firstCycleValues.map((h) => h.note), 34 ).toEqual(['C3', 'D3', 'E3']); 35 }); 36 it('converts n values (mini notation)', () => { 37 expect( 38 n(seq(0, 1, 2)) 39 .scale('C:major') 40 .firstCycleValues.map((h) => h.note), 41 ).toEqual(['C3', 'D3', 'E3']); 42 }); 43 it('converts n values (no tonic)', () => { 44 expect( 45 n(seq(0, 1, 2)) 46 .scale('major') 47 .firstCycleValues.map((h) => h.note), 48 ).toEqual(['C3', 'D3', 'E3']); 49 }); 50 it('converts n values (explicit mini notation)', () => { 51 expect( 52 n(seq(0, 1, 2)) 53 .scale(mini('C:major')) 54 .firstCycleValues.map((h) => h.note), 55 ).toEqual(['C3', 'D3', 'E3']); 56 }); 57 it('converts decorated n values', () => { 58 expect( 59 n(seq('0b', '1#', '-2', '3##', '4bb')) 60 .scale('C major') 61 .firstCycleValues.map((h) => h.note), 62 ).toEqual(['B2', 'Eb3', 'A2', 'G3', 'F3']); 63 }); 64 it('snaps notes (upwards) to scale', () => { 65 const inputNotes = ['Cb', 'Eb', 'G', 'A#', 'Bb']; 66 const expectedNotes = ['B2', 'E3', 'G3', 'B3', 'B3']; 67 68 expect( 69 note(seq(inputNotes)) 70 .scale('C major') 71 .firstCycleValues.map((h) => h.note), 72 ).toEqual(expectedNotes); 73 }); 74 it('snaps notes to the correct octave', () => { 75 const inputNotes = ['Cb0', 'Eb4', 'G1', 'A#19', 'Bb8']; 76 const expectedNotes = ['B#-1', 'D#4', 'G#1', 'A#19', 'A#8']; 77 78 expect( 79 note(seq(inputNotes)) 80 .scale('A# minor') // A#, B#, C#, D#, E#, F#, G# 81 .firstCycleValues.map((h) => h.note), 82 ).toEqual(expectedNotes); 83 }); 84 it('handles scale names provided with colons', () => { 85 const inputNotes = ['Cb', 'E', 'G', 'A#', 'Bb']; 86 const expectedNotes = ['A#2', 'D#3', 'G#3', 'A#3', 'A#3']; 87 88 expect( 89 note(seq(inputNotes)) 90 .scale('F#:pentatonic') // F#, G#, A#, C#, and D# 91 .firstCycleValues.map((h) => h.note), 92 ).toEqual(expectedNotes); 93 }); 94 }); 95 describe('transpose', () => { 96 it('transposes note numbers with interval numbers', () => { 97 expect( 98 note(seq(40, 40, 40)) 99 .transpose(0, 1, 2) 100 .firstCycleValues.map((h) => h.note), 101 ).toEqual([40, 41, 42]); 102 expect(seq(40, 40, 40).transpose(0, 1, 2).firstCycleValues).toEqual([40, 41, 42]); 103 }); 104 it('transposes note numbers with interval strings', () => { 105 expect( 106 note(seq(40, 40, 40)) 107 .transpose('1P', '2M', '3m') 108 .firstCycleValues.map((h) => h.note), 109 ).toEqual([40, 42, 43]); 110 expect(seq(40, 40, 40).transpose('1P', '2M', '3m').firstCycleValues).toEqual([40, 42, 43]); 111 }); 112 it('transposes note strings with interval numbers', () => { 113 expect( 114 note(seq('c', 'c', 'c')) 115 .transpose(0, 1, 2) 116 .firstCycleValues.map((h) => h.note), 117 ).toEqual(['C', 'Db', 'D']); 118 expect(seq('c', 'c', 'c').transpose(0, 1, 2).firstCycleValues).toEqual(['C', 'Db', 'D']); 119 }); 120 it('transposes note strings with interval strings', () => { 121 expect( 122 note(seq('c', 'c', 'c')) 123 .transpose('1P', '2M', '3m') 124 .firstCycleValues.map((h) => h.note), 125 ).toEqual(['C', 'D', 'Eb']); 126 expect(seq('c', 'c', 'c').transpose('1P', '2M', '3m').firstCycleValues).toEqual(['C', 'D', 'Eb']); 127 }); 128 }); 129});