Allow negatives and multi-accidentals
This commit is contained in:
parent
b08890bd9e
commit
79453ac2c3
4 changed files with 9 additions and 23 deletions
|
|
@ -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 [];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 [];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue