UNPKG

@sebastbake/music-tempo

Version:

Finding out tempo of the music

147 lines (139 loc) 4.87 kB
import Agent from "./Agent"; /** * Perform beat tracking on a array of onsets * @param {Array} events - the array of onsets to beat track * @param {Array} eventsScores - the array of corresponding salience values * @param {Array} tempoList - the array of tempo hypothesis * @param {Object} [params={}] - parameters * @param {Number} [params.initPeriod=5] - duration of the initial section * @param {Number} [params.thresholdBI=0.02] - for the purpose of removing duplicate agents, the default JND of IBI * @param {Number} [params.thresholdBT=0.04] - for the purpose of removing duplicate agents, the default JND of phase * @param {Number} [params.expiryTime=10] - the time after which an Agent that has not accepted any beat will be destroyed * @param {Number} [params.toleranceWndInner=0.04] - the maximum time that a beat can deviate from the predicted beat time without a fork occurring * @param {Number} [params.toleranceWndPre=0.15] - the maximum amount by which a beat can be earlier than the predicted beat time, expressed as a fraction of the beat period * @param {Number} [params.toleranceWndPost=0.3] - the maximum amount by which a beat can be later than the predicted beat time, expressed as a fraction of the beat period * @param {Number} [params.correctionFactor=50] - correction factor for updating beat period * @param {Number} [params.maxChange=0.2] - the maximum allowed deviation from the initial tempo, expressed as a fraction of the initial beat period * @param {Number} [params.penaltyFactor=0.5] - factor for correcting score, if onset do not coincide precisely with predicted beat time * @return {Array} agents - agents array */ type trackBeatOptions = { initPeriod?: number; thresholdBI?: number; thresholdBT?: number; expiryTime?: number; toleranceWndInner?: number; toleranceWndPre?: number; toleranceWndPost?: number; correctionFactor?: number; maxChange?: number; penaltyFactor?: number; }; const defaultBeatTrackingOptions = { initPeriod: 5, thresholdBI: 0.02, thresholdBT: 0.04, expiryTime: 10, toleranceWndInner: 0.04, toleranceWndPre: 0.15, toleranceWndPost: 0.3, correctionFactor: 50, maxChange: 0.2, penaltyFactor: 0.5, } export function trackBeat( events, eventsScores, tempoList, { initPeriod = 5, thresholdBI = 0.02, thresholdBT = 0.04, expiryTime = 10, toleranceWndInner = 0.04, toleranceWndPre = 0.15, toleranceWndPost = 0.3, correctionFactor = 50, maxChange = 0.2, penaltyFactor = 0.5, }: trackBeatOptions = defaultBeatTrackingOptions ) { let agents: Array<Agent> = []; function removeSimilarAgents() { agents.sort((a1, a2) => a1.beatInterval - a2.beatInterval); const length = agents.length; for (let i = 0; i < length; i++) { if (agents[i].score < 0) continue; for (let j = i + 1; j < length; j++) { if (agents[j].beatInterval - agents[i].beatInterval > thresholdBI) { break; } if (Math.abs(agents[j].beatTime - agents[i].beatTime) > thresholdBT) { continue; } if (agents[i].score < agents[j].score) { agents[i].score = -1; } else { agents[j].score = -1; } } } for (let i = length - 1; i >= 0; i--) { if (agents[i].score < 0) { agents.splice(i, 1); } } } for (let i = 0; i < tempoList.length; i++) { agents.push( new Agent(tempoList[i], events[0], eventsScores[0], agents, { expiryTime, toleranceWndInner, toleranceWndPre, toleranceWndPost, correctionFactor, maxChange, penaltyFactor, }) ); } let j = 1; removeSimilarAgents(); while (events[j] < initPeriod) { let agentsLength = agents.length; let prevBeatInterval = -1; let isEventAccepted = true; for (let k = 0; k < agentsLength; k++) { if (agents[k].beatInterval != prevBeatInterval) { if (!isEventAccepted) { agents.push( new Agent(prevBeatInterval, events[j], eventsScores[j], agents, { expiryTime, toleranceWndInner, toleranceWndPre, toleranceWndPost, correctionFactor, maxChange, penaltyFactor, }) ); } prevBeatInterval = agents[k].beatInterval; isEventAccepted = false; } isEventAccepted = agents[k].considerEvent(events[j], eventsScores[j]) || isEventAccepted; } removeSimilarAgents(); j++; } const eventsLength = events.length; for (let i = j; i < eventsLength; i++) { let agentsLength = agents.length; for (let j = 0; j < agentsLength; j++) { agents[j].considerEvent(events[i], eventsScores[i]); } removeSimilarAgents(); } return agents; }