jevstrudel.git / packages / tonal / test / tonleiter.test.mjs

tonleiter.test.mjs - <short description TODO> Copyright (C) 2022 Strudel contributors - see https://codeberg.org/uzu/strudel/src/branch/main/packages/tonal/test/tonleiter.test.mjs This 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/.

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';
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});