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

View file

@ -8,12 +8,12 @@ import { logger } from './logger.mjs';
// returns true if the given string is a note // returns true if the given string is a note
export const isNoteWithOctave = (name) => /^[a-gA-G][#bs]*[0-9]$/.test(name); 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) => { export const tokenizeNote = (note) => {
if (typeof note !== 'string') { if (typeof note !== 'string') {
return []; 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) { if (!pc) {
return []; return [];
} }

View file

@ -7,7 +7,7 @@ export const tokenizeNote = (note) => {
if (typeof note !== 'string') { if (typeof note !== 'string') {
return []; 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) { if (!pc) {
return []; return [];
} }

View file

@ -80,7 +80,7 @@ describe('tonal', () => {
}); });
it('snaps notes to the correct octave', () => { it('snaps notes to the correct octave', () => {
const inputNotes = ['Cb0', 'Eb4', 'G1', 'A#19', 'Bb8']; 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( expect(
note(seq(inputNotes)) note(seq(inputNotes))

View file

@ -128,10 +128,7 @@ export const { transpose, trans } = register(['transpose', 'trans'], function tr
const interval = !isNaN(Number(intervalOrSemitones)) const interval = !isNaN(Number(intervalOrSemitones))
? Interval.fromSemitones(intervalOrSemitones) ? Interval.fromSemitones(intervalOrSemitones)
: String(intervalOrSemitones); : String(intervalOrSemitones);
// TODO: move simplify to player to preserve enharmonics const targetNote = Note.transpose(note, interval);
// 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));
if (typeof hap.value === 'object') { if (typeof hap.value === 'object') {
return hap.withValue(() => ({ ...hap.value, note: targetNote })); return hap.withValue(() => ({ ...hap.value, note: targetNote }));
} }
@ -208,7 +205,6 @@ let scaleToMidisAndNotes = {};
// Finds the nearest scale note to `note` // Finds the nearest scale note to `note`
function _getNearestScaleNote(scaleName, note, preferHigher = true) { function _getNearestScaleNote(scaleName, note, preferHigher = true) {
let noteMidi = typeof note === 'string' ? noteToMidi(note) : note; let noteMidi = typeof note === 'string' ? noteToMidi(note) : note;
noteMidi = Math.max(noteMidi, 24); // we will not play notes below C0
if (scaleToMidisAndNotes[scaleName] === undefined) { if (scaleToMidisAndNotes[scaleName] === undefined) {
const { intervals, tonic } = getScale(scaleName); const { intervals, tonic } = getScale(scaleName);
const { pc } = Note.get(tonic); const { pc } = Note.get(tonic);
@ -221,20 +217,10 @@ function _getNearestScaleNote(scaleName, note, preferHigher = true) {
const [scaleMidis, scaleNotes] = scaleToMidisAndNotes[scaleName]; const [scaleMidis, scaleNotes] = scaleToMidisAndNotes[scaleName];
const rootMidi = scaleMidis[0]; const rootMidi = scaleMidis[0];
const octaveDiff = Math.floor((noteMidi - rootMidi) / 12); const octaveDiff = Math.floor((noteMidi - rootMidi) / 12);
let filteredNotes = []; // we must filter the notes to avoid negative octave values const alignedMidis = scaleMidis.map((m) => m + 12 * octaveDiff);
let filteredMidis = []; const noteIdx = nearestNumberIndex(noteMidi, alignedMidis, preferHigher);
for (let i = 0; i < scaleMidis.length; i++) { const noteMatch = scaleNotes[noteIdx];
const newMidi = scaleMidis[i] + 12 * octaveDiff; return Note.transpose(noteMatch, Interval.fromSemitones(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];
} }
/** /**