1/* 2tonleiter.test.mjs - <short description TODO> 3Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/tonal/test/tonleiter.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 7import { describe, test, expect } from 'vitest'; 8import { 9 Step, 10 Note, 11 transpose, 12 pc2chroma, 13 rotateChroma, 14 chroma2pc, 15 tokenizeChord, 16 note2pc, 17 note2oct, 18 midi2note, 19 renderVoicing, 20 scaleStep, 21 stepInNamedScale, 22 nearestNumberIndex, 23 note2midi, 24} from '../tonleiter.mjs'; 25 26describe('tonleiter', () => { 27 test('Step ', () => { 28 expect(Step.tokenize('#11')).toEqual(['#', 11]); 29 expect(Step.tokenize('b13')).toEqual(['b', 13]); 30 expect(Step.tokenize('bb6')).toEqual(['bb', 6]); 31 expect(Step.tokenize('b3')).toEqual(['b', 3]); 32 expect(Step.tokenize('3')).toEqual(['', 3]); 33 expect(Step.tokenize('10')).toEqual(['', 10]); 34 // expect(Step.tokenize('asdasd')).toThrow(); 35 expect(Step.accidentals('b3')).toEqual(-1); 36 expect(Step.accidentals('#11')).toEqual(1); 37 }); 38 test('Note', () => { 39 expect(Note.tokenize('C##')).toEqual(['C', '##']); 40 expect(Note.tokenize('Bb')).toEqual(['B', 'b']); 41 expect(Note.accidentals('C#')).toEqual(1); 42 expect(Note.accidentals('C##')).toEqual(2); 43 expect(Note.accidentals('Eb')).toEqual(-1); 44 expect(Note.accidentals('Bbb')).toEqual(-2); 45 }); 46 test('transpose', () => { 47 expect(transpose('F#', '3')).toEqual('A#'); 48 expect(transpose('C', '3')).toEqual('E'); 49 expect(transpose('D', '3')).toEqual('F#'); 50 expect(transpose('E', '3')).toEqual('G#'); 51 expect(transpose('Eb', '3')).toEqual('G'); 52 expect(transpose('Ebb', '3')).toEqual('Gb'); 53 }); 54 test('pc2chroma', () => { 55 expect(pc2chroma('C')).toBe(0); 56 expect(pc2chroma('C#')).toBe(1); 57 expect(pc2chroma('C##')).toBe(2); 58 expect(pc2chroma('D')).toBe(2); 59 expect(pc2chroma('Db')).toBe(1); 60 expect(pc2chroma('Dbb')).toBe(0); 61 expect(pc2chroma('bb')).toBe(10); 62 expect(pc2chroma('f')).toBe(5); 63 expect(pc2chroma('c')).toBe(0); 64 }); 65 test('rotateChroma', () => { 66 expect(rotateChroma(0, 1)).toBe(1); 67 expect(rotateChroma(0, -1)).toBe(11); 68 expect(rotateChroma(11, 1)).toBe(0); 69 expect(rotateChroma(11, 13)).toBe(0); 70 }); 71 test('chroma2pc', () => { 72 expect(chroma2pc(0)).toBe('C'); 73 expect(chroma2pc(1)).toBe('Db'); 74 expect(chroma2pc(1, true)).toBe('C#'); 75 expect(chroma2pc(2)).toBe('D'); 76 expect(chroma2pc(3)).toBe('Eb'); 77 }); 78 test('tokenizeChord', () => { 79 expect(tokenizeChord('Cm7')).toEqual(['C', 'm7', undefined]); 80 expect(tokenizeChord('C#m7')).toEqual(['C#', 'm7', undefined]); 81 expect(tokenizeChord('Bb^7')).toEqual(['Bb', '^7', undefined]); 82 expect(tokenizeChord('Bb^7/F')).toEqual(['Bb', '^7', 'F']); 83 }); 84 test('note2pc', () => { 85 expect(note2pc('C5')).toBe('C'); 86 expect(note2pc('C52')).toBe('C'); 87 expect(note2pc('Bb3')).toBe('Bb'); 88 expect(note2pc('F')).toBe('F'); 89 }); 90 test('note2oct', () => { 91 expect(note2oct('C5')).toBe(5); 92 expect(note2oct('Bb3')).toBe(3); 93 expect(note2oct('C7')).toBe(7); 94 expect(note2oct('C10')).toBe(10); 95 }); 96 test('midi2note', () => { 97 expect(midi2note(60)).toBe('C4'); 98 expect(midi2note(61)).toBe('Db4'); 99 expect(midi2note(61, true)).toBe('C#4'); 100 }); 101 test('scaleStep', () => { 102 expect(scaleStep([60, 63, 67], 0)).toBe(60); 103 expect(scaleStep([60, 63, 67], 1)).toBe(63); 104 expect(scaleStep([60, 63, 67], 2)).toBe(67); 105 expect(scaleStep([60, 63, 67], 3)).toBe(72); 106 expect(scaleStep([60, 63, 67], 4)).toBe(75); 107 expect(scaleStep([60, 63, 67], -1)).toBe(55); 108 expect(scaleStep([60, 63, 67], -2)).toBe(51); 109 expect(scaleStep([60, 63, 67], -3)).toBe(48); 110 expect(scaleStep([60, 63, 67], -4)).toBe(43); 111 }); 112 test('renderVoicing', () => { 113 const dictionary = { 114 m7: [ 115 '3 7 10 14', // b3 5 b7 9 116 '10 14 15 19', // b7 9 b3 5 117 ], 118 }; 119 expect(renderVoicing({ chord: 'Em7', anchor: 'Bb4', dictionary, mode: 'below' })).toEqual([ 120 'G3', 121 'B3', 122 'D4', 123 'Gb4', 124 ]); 125 expect(renderVoicing({ chord: 'Cm7', anchor: 'D5', dictionary, mode: 'below' })).toEqual([ 126 'Eb4', 127 'G4', 128 'Bb4', 129 'D5', 130 ]); 131 expect(renderVoicing({ chord: 'Cm7', anchor: 'G5', dictionary, mode: 'below' })).toEqual([ 132 'Bb4', 133 'D5', 134 'Eb5', 135 'G5', 136 ]); 137 expect(renderVoicing({ chord: 'Cm7', anchor: 'g5', dictionary, mode: 'below' })).toEqual([ 138 'Bb4', 139 'D5', 140 'Eb5', 141 'G5', 142 ]); 143 expect(renderVoicing({ chord: 'Cm7', anchor: 'g5', dictionary, mode: 'below', n: 0 })).toEqual([70]); // Bb4 144 expect(renderVoicing({ chord: 'Cm7', anchor: 'g5', dictionary, mode: 'below', n: 1 })).toEqual([74]); // D5 145 expect(renderVoicing({ chord: 'Cm7', anchor: 'g5', dictionary, mode: 'below', n: 4 })).toEqual([82]); // Bb5 146 expect(renderVoicing({ chord: 'Cm7', anchor: 'g5', dictionary, mode: 'below', offset: 1 })).toEqual([ 147 'Eb5', 148 'G5', 149 'Bb5', 150 'D6', 151 ]); 152 // expect(voiceBelow('G4', 'Cm7', voicingDictionary)).toEqual(['Bb3', 'D4', 'Eb4', 'G4']); 153 // TODO: test with offset 154 }); 155 test('nearestNumber', () => { 156 expect(nearestNumberIndex(0, [0, 2, 4, 5])).toEqual(0); 157 expect(nearestNumberIndex(1, [0, 2, 4, 5])).toEqual(0); 158 expect(nearestNumberIndex(1, [0, 2, 4, 5], true)).toEqual(1); 159 expect(nearestNumberIndex(2, [0, 2, 4, 5])).toEqual(1); 160 expect(nearestNumberIndex(2, [0, 2, 4, 5]), true).toEqual(1); 161 expect(nearestNumberIndex(3, [0, 2, 4, 5])).toEqual(1); 162 expect(nearestNumberIndex(3, [0, 2, 4, 5], true)).toEqual(2); 163 expect(nearestNumberIndex(4, [0, 2, 4, 5])).toEqual(2); 164 }); 165 test('stepInNamedScale', () => { 166 expect(stepInNamedScale(1, 'D major')).toEqual(note2midi('E3')); 167 expect(stepInNamedScale(2, 'E major')).toEqual(note2midi('G#3')); 168 expect(stepInNamedScale(0, 'D major', 'E3')).toEqual(note2midi('E3')); 169 expect(stepInNamedScale(0, 'D major', 'E4')).toEqual(note2midi('E4')); 170 expect(stepInNamedScale(0, 'D major', 'Eb4')).toEqual(note2midi('D4')); 171 expect(stepInNamedScale(0, 'D major', 'F4')).toEqual(note2midi('E4')); 172 expect(stepInNamedScale(0, 'D major', 'F#4')).toEqual(note2midi('F#4')); 173 expect(stepInNamedScale(0, 'D major', 'G4')).toEqual(note2midi('G4')); 174 175 expect(stepInNamedScale(0, 'F major', 'F4')).toEqual(note2midi('F4')); 176 expect(stepInNamedScale(0, 'F major', 'G4')).toEqual(note2midi('G4')); 177 expect(stepInNamedScale(0, 'F major', 'A4')).toEqual(note2midi('A4')); 178 expect(stepInNamedScale(0, 'F major', 'Bb4')).toEqual(note2midi('Bb4')); 179 expect(stepInNamedScale(0, 'F major', 'C4')).toEqual(note2midi('C4')); 180 181 expect(stepInNamedScale(1, 'F major', 'C4')).toEqual(note2midi('D4')); 182 expect(stepInNamedScale(1, 'F major', 'C3')).toEqual(note2midi('D3')); 183 expect(stepInNamedScale(1, 'F minor', 'D4')).toEqual(note2midi('Eb4')); 184 }); 185});