-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
BPM tracking improvement #1657
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
BPM tracking improvement #1657
Changes from all commits
af8d3a5
a5c7dca
d4c8726
51abc92
f220bfa
8104525
01143cd
8a87aee
9cd336d
31ff4ff
d65bdc3
a6f1855
9de0bec
6e606c2
8223f7e
93e34bd
24a88bc
b500c77
1015724
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,8 +21,13 @@ import { | |
| createContext, | ||
| } from './eventa' | ||
|
|
||
| // Add to the top of detector.ts | ||
| import { RealTimeBpmAnalyzer } from 'realtime-bpm-analyzer'; | ||
|
MintKeyphase marked this conversation as resolved.
|
||
|
|
||
| export const inputAnalyserFFTSize = 1024 | ||
|
|
||
|
|
||
|
|
||
| export interface BeatSyncDetector { | ||
| start: (createSource: (context: AudioContext) => Promise<AudioNode>) => Promise<void> | ||
| updateParameters: (params: Partial<AnalyserWorkletParameters>) => void | ||
|
|
@@ -54,12 +59,45 @@ export function createBeatSyncDetector(options: CreateBeatSyncDetectorOptions): | |
|
|
||
| let inputAnalyserNode: AnalyserNode | undefined | ||
| let inputAnalyserBuffer: Uint8Array<ArrayBuffer> | undefined | ||
| let beatInterval: any | undefined | ||
| const rhythmAnalyzer = new (RealTimeBpmAnalyzer as any)({ | ||
| continuousAnalysis: true, | ||
| stabilizationTime: 1000, | ||
| importSharedOptions: true, | ||
| sampleRate: 44100 | ||
| }) | ||
|
|
||
| const listeners: { [K in keyof BeatSyncDetectorEventMap]: Array<(...args: any) => void> } = { | ||
| stateChange: [], | ||
| beat: [], | ||
| } | ||
|
|
||
| const syncMetronome = (bpm: number, isLocked: boolean) => { | ||
| if (beatInterval) clearInterval(beatInterval) | ||
|
|
||
| const ms = (60 / bpm) * 1000 | ||
| const intervalInSeconds = 60 / bpm | ||
|
|
||
| beatInterval = setInterval(() => { | ||
| // Calculate real energy from the current buffer so jumps stay reactive | ||
| let currentEnergy = 0 | ||
| if (inputAnalyserNode && inputAnalyserBuffer) { | ||
| inputAnalyserNode.getByteFrequencyData(inputAnalyserBuffer) | ||
| // Simple RMS-like average of the current frequency buffer | ||
| const sum = inputAnalyserBuffer.reduce((a, b) => a + b, 0) | ||
| currentEnergy = sum / inputAnalyserBuffer.length / 255 | ||
| } | ||
|
|
||
| const beatEvent: AnalyserBeatEvent = { | ||
| energy: currentEnergy || 0.5, // Fallback to 0.5 if no audio | ||
| interval: intervalInSeconds // Time since last beat in seconds | ||
| } | ||
|
|
||
| emit('beat', beatEvent) | ||
| }, ms) | ||
| } | ||
|
|
||
|
|
||
| const emit = <E extends keyof BeatSyncDetectorEventMap>(event: E, ...args: Parameters<BeatSyncDetectorEventMap[E]>) => { | ||
| listeners[event].forEach(listener => listener(...args)) | ||
| } | ||
|
|
@@ -79,6 +117,9 @@ export function createBeatSyncDetector(options: CreateBeatSyncDetectorOptions): | |
| inputAnalyserBuffer = undefined | ||
| } | ||
|
|
||
| clearInterval(beatInterval) | ||
| beatInterval = undefined | ||
|
|
||
| source?.disconnect() | ||
| source = undefined | ||
|
|
||
|
|
@@ -97,10 +138,31 @@ export function createBeatSyncDetector(options: CreateBeatSyncDetectorOptions): | |
| context, | ||
| worklet: analyserWorklet, | ||
| listeners: { | ||
| onBeat: e => emit('beat', e), | ||
| onBeat: () => {}, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Changing Useful? React with 👍 / 👎.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fallback emits 40 BPM beat events |
||
| }, | ||
| }) | ||
|
|
||
|
|
||
|
|
||
| analyser.workletNode.port.onmessage = (e) => { | ||
| if (e.data.type === 'audioData') { | ||
| // According to docs/v3, we pass a single options object. | ||
| (rhythmAnalyzer as any).analyzeChunck({ | ||
| channelData: e.data.data, | ||
| audioSampleRate: e.data.outputSampleRate, | ||
| bufferSize: e.data.data.length, | ||
| postMessage: (message: any) => { | ||
| // The library emits 'BPM' or 'BPM_STABLE' in the .message property | ||
| const isBpmEvent = message.message === 'BPM' || message.message === 'BPM_STABLE'; | ||
|
|
||
| if (isBpmEvent && message.data.bpm) { | ||
| lockBpm = message.data.bpm; | ||
| syncMetronome(lockBpm, true); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
| }; | ||
| const node = await createSource(context) | ||
|
|
||
| inputAnalyserNode = context.createAnalyser() | ||
|
|
@@ -220,8 +282,38 @@ export function createBeatSyncDetector(options: CreateBeatSyncDetectorOptions): | |
| inputAnalyserNode?.getByteFrequencyData(inputAnalyserBuffer!) | ||
| return inputAnalyserBuffer! | ||
| } | ||
|
|
||
|
|
||
| }; | ||
|
|
||
| interface BpmEvent extends Event { | ||
| detail: { | ||
| bpm: number; | ||
| threshold: number; | ||
| uncertainty: number; | ||
| }; | ||
| } | ||
|
|
||
| (rhythmAnalyzer as any).onNext = (bpm: number, threshold: number) => { | ||
| lockBpm = bpm; | ||
| if (lockBpm > 0) { | ||
| syncMetronome(lockBpm, true); | ||
| } | ||
| }; | ||
| // ---------------------- | ||
|
|
||
| return { | ||
| start: async () => { | ||
| // ✅ START THE FALLBACK ONLY WHEN START IS CALLED | ||
| syncMetronome(40, false); | ||
|
|
||
| state.isActive = true; | ||
| // ... rest of your port.onmessage and wiring logic ... | ||
| }, | ||
| stop: () => { | ||
| state.isActive = false; | ||
| // Ensure you stop the timer here too! | ||
| } | ||
| start, | ||
| updateParameters, | ||
| startScreenCapture, | ||
|
|
@@ -333,3 +425,4 @@ export async function getBeatSyncInputByteFrequencyData() { | |
|
|
||
| throw new Error('Unknown environment for getBeatSyncInputByteFrequencyData()') | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Increasing the bufferSize to 16384 increases the memory allocated for inputBuffer and outputBuffer (lines 38-39), but these buffers are not utilized in the process method. If they are indeed redundant, it is recommended to remove them and revert this change to minimize memory overhead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the increase in necessary due to the increase in sample rate, which in turn allows for a broader spectrum to be recognized by the BPM detector