bluesharp-pitch-detection
Version:
High-accuracy pitch detection algorithms for musical applications
115 lines (101 loc) • 4.93 kB
JavaScript
/*
* Copyright (c) 2023 Christian Kierdorf
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the “Software”),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
/**
* A utility class for looking up musical note names and their corresponding frequencies.
* Provides methods to calculate note names for given frequencies and to calculate frequencies for given note names.
*/
class NoteLookup {
/**
* An immutable array containing the names of musical notes in the chromatic scale.
* The array represents the 12 pitch classes for one octave, starting from "C" to "B".
*
* This array is frozen to prevent any modifications to it, ensuring consistency
* across its usage in the application.
*
* @constant {string[]} NOTE_NAMES
*/
static NOTE_NAMES = Object.freeze(["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]);
/**
* Represents the default frequency (in Hertz) of the concert pitch,
* commonly referred to as A440. This is the standard pitch tuning
* for musical instruments and serves as the reference point where
* the A above middle C (A4) is set to 440 Hz.
*/
static DEFAULT_CONCERT_PITCH_FREQUENCY = 440.0;
/**
* Represents the reference frequency for tuning musical instruments, commonly known as concert pitch.
* This variable is typically set to a standard frequency, such as 440 Hz, which corresponds to the pitch of A4.
* It is used as the baseline frequency against which other notes and instruments are tuned.
*
* The value is based on the default concert pitch frequency defined in NoteLookup.
*/
static concertPitch = NoteLookup.DEFAULT_CONCERT_PITCH_FREQUENCY;
/**
* Computes the note name based on a given frequency.
*
* @param {number} frequency - The frequency of the note in hertz. Must be a positive value.
* @return {string|null} The name of the note (e.g., "A4") if the frequency is valid and corresponds to a MIDI value
* within the acceptable range (0-127), or null if the frequency is invalid.
*/
static getNoteName(frequency) {
if (frequency <= 0) {
return null; // Ignore invalid frequencies
}
// Calculate the MIDI number of the tone
const midiNumber = Math.round(69 + (12 * Math.log2(frequency / NoteLookup.concertPitch)));
// Check if the MIDI value lies within the valid range (MIDI values typically range from 0 to 127)
if (midiNumber < 0 || midiNumber > 127) {
return null;
}
// Calculate the note index and octave
const noteIndex = midiNumber % 12;
const octave = Math.floor(midiNumber / 12) - 1;
return `${NoteLookup.NOTE_NAMES[noteIndex]}${octave}`;
}
/**
* Calculates the frequency of a given musical note based on its name and octave.
*
* @param {string} noteName - The name of the musical note (e.g., "A4", "C#3", "Bb2").
* The format should include the note, optional sharp (#) or flat (b), and octave.
* @return {number|null} The frequency of the note in hertz, or null if the input is invalid.
*/
static getFrequency(noteName) {
const match = noteName.match(/^([A-G]#?|[A-G]b?)(-?\d+)$/);
if (!match) {
return null; // Ignore invalid input
}
const note = match[1];
const octave = parseInt(match[2], 10);
// Find the note index in the chromatic array
const noteIndex = NoteLookup.NOTE_NAMES.indexOf(note);
if (noteIndex === -1) {
return null;
}
// Calculate the MIDI number based on the note and the octave
const midiNumber = noteIndex + (octave + 1) * 12;
// Calculate the frequency based on the MIDI number
return NoteLookup.concertPitch * Math.pow(2, (midiNumber - 69) / 12);
}
}
export default NoteLookup;