Allow negatives and multi-accidentals

This commit is contained in:
Aria
2025-08-22 13:17:13 -05:00
parent b08890bd9e
commit 79453ac2c3
4 changed files with 9 additions and 23 deletions
+2 -2
View File
@@ -8,12 +8,12 @@ import { logger } from './logger.mjs';
// returns true if the given string is a note
export const isNoteWithOctave = (name) => /^[a-gA-G][#bs]*[0-9]$/.test(name);
export const isNote = (name) => /^[a-gA-G][#bsf]*[0-9]?$/.test(name);
export const isNote = (name) => /^[a-gA-G][#bsf]*-?[0-9]?$/.test(name);
export const tokenizeNote = (note) => {
if (typeof note !== 'string') {
return [];
}
const [pc, acc = '', oct] = note.match(/^([a-gA-G])([#bsf]*)([0-9]*)$/)?.slice(1) || [];
const [pc, acc = '', oct] = note.match(/^([a-gA-G])([#bsf]*)(-?[0-9]*)$/)?.slice(1) || [];
if (!pc) {
return [];
}
+1 -1
View File
@@ -7,7 +7,7 @@ export const tokenizeNote = (note) => {
if (typeof note !== 'string') {
return [];
}
const [pc, acc = '', oct] = note.match(/^([a-gA-G])([#bsf]*)([0-9]*)$/)?.slice(1) || [];
const [pc, acc = '', oct] = note.match(/^([a-gA-G])([#bsf]*)(-?[0-9]*)$/)?.slice(1) || [];
if (!pc) {
return [];
}
+1 -1
View File
@@ -80,7 +80,7 @@ describe('tonal', () => {
});
it('snaps notes to the correct octave', () => {
const inputNotes = ['Cb0', 'Eb4', 'G1', 'A#19', 'Bb8'];
const expectedNotes = ['B#0', 'D#4', 'G#1', 'A#19', 'A#8'];
const expectedNotes = ['B#-1', 'D#4', 'G#1', 'A#19', 'A#8'];
expect(
note(seq(inputNotes))
+5 -19
View File
@@ -128,10 +128,7 @@ export const { transpose, trans } = register(['transpose', 'trans'], function tr
const interval = !isNaN(Number(intervalOrSemitones))
? Interval.fromSemitones(intervalOrSemitones)
: String(intervalOrSemitones);
// TODO: move simplify to player to preserve enharmonics
// tone.js doesn't understand multiple sharps flats e.g. F##3 has to be turned into G3
// TODO: check if this is still relevant..
const targetNote = Note.simplify(Note.transpose(note, interval));
const targetNote = Note.transpose(note, interval);
if (typeof hap.value === 'object') {
return hap.withValue(() => ({ ...hap.value, note: targetNote }));
}
@@ -208,7 +205,6 @@ let scaleToMidisAndNotes = {};
// Finds the nearest scale note to `note`
function _getNearestScaleNote(scaleName, note, preferHigher = true) {
let noteMidi = typeof note === 'string' ? noteToMidi(note) : note;
noteMidi = Math.max(noteMidi, 24); // we will not play notes below C0
if (scaleToMidisAndNotes[scaleName] === undefined) {
const { intervals, tonic } = getScale(scaleName);
const { pc } = Note.get(tonic);
@@ -221,20 +217,10 @@ function _getNearestScaleNote(scaleName, note, preferHigher = true) {
const [scaleMidis, scaleNotes] = scaleToMidisAndNotes[scaleName];
const rootMidi = scaleMidis[0];
const octaveDiff = Math.floor((noteMidi - rootMidi) / 12);
let filteredNotes = []; // we must filter the notes to avoid negative octave values
let filteredMidis = [];
for (let i = 0; i < scaleMidis.length; i++) {
const newMidi = scaleMidis[i] + 12 * octaveDiff;
if (newMidi < 24) {
continue;
}
filteredMidis.push(newMidi);
const oldNote = scaleNotes[i];
const newNote = Note.transpose(oldNote, Interval.fromSemitones(12 * octaveDiff));
filteredNotes.push(newNote);
}
const noteIdx = nearestNumberIndex(noteMidi, filteredMidis, preferHigher);
return filteredNotes[noteIdx];
const alignedMidis = scaleMidis.map((m) => m + 12 * octaveDiff);
const noteIdx = nearestNumberIndex(noteMidi, alignedMidis, preferHigher);
const noteMatch = scaleNotes[noteIdx];
return Note.transpose(noteMatch, Interval.fromSemitones(12 * octaveDiff));
}
/**