Compare commits

...

4 Commits

Author SHA1 Message Date
Felix Roos 14184993d0 Merge remote-tracking branch 'origin/main' into fix-scale-offset 2023-02-09 19:06:42 +01:00
Felix Roos b92d1d09a2 tonleiter experiment 2023-02-03 19:43:10 +01:00
Felix Roos c79b2653e7 revert change for now
(because tonal degree does not support floats)
2023-02-03 19:42:35 +01:00
Felix Roos 554b72c916 fix scale degrees
fixes https://github.com/tidalcycles/strudel/issues/378
2023-02-02 22:11:56 +01:00
3 changed files with 85 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
import { describe, it, expect } from 'vitest';
import { Note } from '../tonleiter.mjs';
describe('tonleiter', () => {
it('Should tokenize notes', () => {
expect(Note.tokenize('c')).toEqual(['c', '']);
expect(Note.tokenize('C')).toEqual(['C', '']);
expect(Note.tokenize('c#')).toEqual(['c', '#']);
expect(Note.tokenize('Bb')).toEqual(['B', 'b']);
});
});
+3
View File
@@ -148,6 +148,9 @@ export const scale = register('scale', function (scale /* : string */, pat) {
let [tonic, scaleName] = Scale.tokenize(scale);
const { pc, oct = 3 } = Note.get(tonic);
note = scaleOffset(pc + ' ' + scaleName, asNumber, pc + oct);
/* const scaleWIthOctave = `${pc + oct} ${scaleName}`;
const degree = Scale.degrees(scaleWIthOctave);
note = degree(asNumber + (asNumber >= 0 ? 1 : 0)); */
}
return hap.withValue(() => (isObject ? { ...hap.value, note } : note)).setContext({ ...hap.context, scale });
});
+71
View File
@@ -0,0 +1,71 @@
const steps = [1, 0, 2, 0, 3, 4, 0, 5, 0, 6, 0, 7];
const notes = ['C', '', 'D', '', 'E', 'F', '', 'G', '', 'A', '', 'B'];
const noteLetters = ['C', 'D', 'E', 'F', 'G', 'A', 'B'];
const accidentalOffset = (accidentals) => {
return accidentals.split('#').length - accidentals.split('b').length;
};
const accidentalString = (offset) => {
if (offset < 0) {
return 'b'.repeat(-offset);
}
if (offset > 0) {
return '#'.repeat(offset);
}
return '';
};
export const Step = {
tokenize(step) {
const [accidentals, stepNumber] = step.match(/^([#b]*)([1-9]+)$/).slice(1);
return [accidentals, parseInt(stepNumber)];
},
accidentals(step) {
return accidentalOffset(Step.tokenize(step)[0]);
},
};
Step.tokenize('#11');
Step.tokenize('b13');
Step.tokenize('bb6');
Step.accidentals('3');
Step.accidentals('b3');
export const Note = {
// TODO: support octave numbers
tokenize(note) {
return [note[0], note.slice(1)];
},
accidentals(note) {
return accidentalOffset(this.tokenize(note)[1]);
},
};
Note.tokenize('C##');
Note.accidentals('C#');
Note.accidentals('C##');
Note.accidentals('Eb');
Note.accidentals('Bbb');
// TODO: support octave numbers
function transpose(note, step) {
// example: E, 3
const stepNumber = Step.tokenize(step)[1]; // 3
const noteLetter = Note.tokenize(note)[0]; // E
const noteIndex = noteLetters.indexOf(noteLetter); // 2 "E is C+2"
const targetNote = noteLetters[(noteIndex + stepNumber - 1) % 8]; // G "G is a third above E"
const rootIndex = notes.indexOf(noteLetter); // 4 "E is 4 semitones above C"
const targetIndex = notes.indexOf(targetNote); // 7 "G is 7 semitones above C"
const indexOffset = targetIndex - rootIndex; // 3 (E to G is normally a 3 semitones)
const stepIndex = steps.indexOf(stepNumber); // 4 ("3" is normally 4 semitones)
const offsetAccidentals = accidentalString(Step.accidentals(step) + Note.accidentals(note) + stepIndex - indexOffset); // "we need to add a # to to the G to make it a major third from E"
return [targetNote, offsetAccidentals].join('');
}
transpose('F#', '3');
transpose('C', '3');
transpose('D', '3');
transpose('E', '3');
transpose('Eb', '3');
transpose('Ebb', '3');