From 25ec5dbd0ba83824f7309c104c7292046b80c0a4 Mon Sep 17 00:00:00 2001 From: felipetovarhenao <55326565+felipetovarhenao@users.noreply.github.com> Date: Mon, 22 May 2023 22:22:16 -0500 Subject: [PATCH 1/4] More detailed usage example in README.md The current code snippet in README.md is rather confusing and doesn't reflect relevant implementation details, such as how to instantiate the BasicPitch class. The provided example (which is admittedly lengthly) is ready to use, and includes docstring + explanatory comments. --- README.md | 156 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 122 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 8f51871..cbab969 100644 --- a/README.md +++ b/README.md @@ -41,47 +41,135 @@ To add to your project, run ```sh yarn add @spotify/basic-pitch ``` +or +```sh +npm install @spotify/basic-pitch +``` -From there you can look at `src/inference.test.ts` for examples of how to use Basic Pitch. To summarize how to use it, +From there you can look at `src/inference.test.ts` for examples of how to use Basic Pitch. + +For instance, here's a detailed example of a typescript function called `audioToNoteEvents`, that allows you to manipulate the detected notes through a input callback function (see `onSuccess`): ```typescript -const audioCtx = new AudioContext(); -let audioBuffer = undefined; - -audioCtx.decodeAudioData( - fs.readFileSync(/* Path to audio file */), - async (_audioBuffer: AudioBuffer) => { - audioBuffer = _audioBuffer; - }, - () => {}, -); - -while (audioBuffer === undefined) { - await new Promise(r => setTimeout(r, 1)); +// audioToNoteEvents.ts + +import { BasicPitch, noteFramesToTime, addPitchBendsToNoteEvents, outputToNotesPoly } from "@spotify/basic-pitch"; + +/** + * Converts audio to note events using the BasicPitch library. + * + * @param audioURL - The URL of the audio file. + * @param onSuccess - A callback function called when the conversion is successful. It receives an array of note events. + * @param onError - An optional callback function called if an error occurs during the conversion. + * @param options - Optional detection options. + * @returns A promise that resolves when the conversion is complete. + */ +export default async function audioToNoteEvents( + audioURL: string, + onSuccess: onSuccessCallback, + onError?: onErrorCallback, + onProgress?: onProgressCallback, + options?: detectionOptions +): Promise { + const audioContext = new AudioContext({ sampleRate: 22050 }); + + const frames: number[][] = []; + const onsets: number[][] = []; + const contours: number[][] = []; + + try { + const response = await fetch(audioURL); + const arrayBuffer = await response.arrayBuffer(); + const decodedData = await audioContext.decodeAudioData(arrayBuffer); + + // Instantiate the BasicPitch model + const basicPitch = new BasicPitch("https://raw.githubusercontent.com/spotify/basic-pitch-ts/main/model/model.json"); + + // Evaluate the model on the decoded audio data + await basicPitch.evaluateModel( + decodedData, + (frame: number[][], onset: number[][], contour: number[][]) => { + // Collect the frame, onset, and contour data + frames.push(...frame); + onsets.push(...onset); + contours.push(...contour); + }, + (pct: number) => { + if (onProgress) { + onProgress(pct); + } + } + ); + + // Destructure the options with default values + const { + onsetThresh = 5, + frameThresh = 3, + minNoteLen = 5, + inferOnsets = true, + maxFreq = null, + minFreq = null, + melodiaTrick = true, + energyTolerance = 11, + } = options || {}; + + // Convert the collected data to note events + const notes = noteFramesToTime( + addPitchBendsToNoteEvents( + contours, + outputToNotesPoly(frames, onsets, onsetThresh, frameThresh, minNoteLen, inferOnsets, maxFreq, minFreq, melodiaTrick, energyTolerance) + ) + ); + const noteEvents = notes.map((n) => ({ + pitch: n.pitchMidi, + duration: n.durationSeconds, + onset: n.startTimeSeconds, + pitchBends: n.pitchBends, + velocity: n.amplitude, + })); + + // Sort the note events by onset time and pitch + noteEvents.sort((a, b) => a.onset - b.onset || a.pitch - b.pitch); + + // Call the success callback with the resulting note events + onSuccess(noteEvents); + } catch (error) { + // Call the error callback if provided + if (onError) { + onError(error); + } + } } -const basicPitch = new BasicPitch(model); -await basicPitch.evaluateModel( - audioBuffer as unknown as AudioBuffer, - (f: number[][], o: number[][], c: number[][]) => { - frames.push(...f); - onsets.push(...o); - contours.push(...c); - }, - (p: number) => { - pct = p; - }, -); - -const notes = noteFramesToTime( - addPitchBendsToNoteEvents( - contours, - outputToNotesPoly(frames, onsets, 0.25, 0.25, 5), - ), -); +// Define note event +type NoteEvent = { + pitch: number; + onset: number; + duration: number; + velocity?: number; + pitchBends?: number[]; +}; + +// Define the options for audio detection +type detectionOptions = { + onsetThresh?: number; + frameThresh?: number; + minNoteLen?: number; + inferOnsets?: boolean; + maxFreq?: number | null; + minFreq?: number | null; + melodiaTrick?: boolean; + energyTolerance?: number; +}; + +// Define the callback types +type onSuccessCallback = (notes: NoteEvent[]) => void; +type onErrorCallback = (error: any) => void; +type onProgressCallback = (percent: number) => void; + ``` -You can then use `notes` in your application however you wish! +You can then use this function in your application however you wish! ### Scripts From eb65d698b002df1e2a89441f7e59be308fdc6ddd Mon Sep 17 00:00:00 2001 From: felipetovarhenao <55326565+felipetovarhenao@users.noreply.github.com> Date: Mon, 22 May 2023 22:40:22 -0500 Subject: [PATCH 2/4] Fixed bug values in default options --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cbab969..8949a60 100644 --- a/README.md +++ b/README.md @@ -103,8 +103,8 @@ export default async function audioToNoteEvents( // Destructure the options with default values const { - onsetThresh = 5, - frameThresh = 3, + onsetThresh = 0.5, + frameThresh = 0.3, minNoteLen = 5, inferOnsets = true, maxFreq = null, From cbf64d1d58edd51af9ada58d41a6b8be3eadb9b7 Mon Sep 17 00:00:00 2001 From: felipetovarhenao <55326565+felipetovarhenao@users.noreply.github.com> Date: Mon, 22 May 2023 22:43:12 -0500 Subject: [PATCH 3/4] Added onProgress info to docstring --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8949a60..90fed05 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ import { BasicPitch, noteFramesToTime, addPitchBendsToNoteEvents, outputToNotesP * @param audioURL - The URL of the audio file. * @param onSuccess - A callback function called when the conversion is successful. It receives an array of note events. * @param onError - An optional callback function called if an error occurs during the conversion. + * @param onProcess - An optional callback function called to get progress percentage value. * @param options - Optional detection options. * @returns A promise that resolves when the conversion is complete. */ From 16552b78b286fe6f8d608f354cbaa55ee6c3638b Mon Sep 17 00:00:00 2001 From: felipetovarhenao <55326565+felipetovarhenao@users.noreply.github.com> Date: Mon, 22 May 2023 22:44:04 -0500 Subject: [PATCH 4/4] minor typo fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 90fed05..9c0ae6a 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ import { BasicPitch, noteFramesToTime, addPitchBendsToNoteEvents, outputToNotesP * @param audioURL - The URL of the audio file. * @param onSuccess - A callback function called when the conversion is successful. It receives an array of note events. * @param onError - An optional callback function called if an error occurs during the conversion. - * @param onProcess - An optional callback function called to get progress percentage value. + * @param onProgress - An optional callback function called to get progress percentage value. * @param options - Optional detection options. * @returns A promise that resolves when the conversion is complete. */