UNPKG

strumming-metronome

Version:

A simple JavaScript metronome for strumming practice.

113 lines (92 loc) 4.76 kB
import { beatIds, upQuarterBeatIds, downQuarterBeatIds, subdivisionConfigs } from './constants.js'; import { playBuffer, getDownStrumBuffer, getUpStrumBuffer } from './audio.js'; import { resetVisualStates, setPlayingState, setStoppedState, setControlsEnabled, setupButtonToggles, injectTable } from './ui.js'; export function createMetronomeEngine(timeSelection) { const oldButton = document.getElementById("metronome-btn"); const button = oldButton.cloneNode(true); oldButton.parentNode.replaceChild(button, oldButton); const metronomeTable = injectTable(timeSelection); setupButtonToggles(metronomeTable); const container = document.querySelector("#metronome-container"); const bpmInput = container.querySelector("#bpm-input"); const icon = container.querySelector("#metronome-icon"); const text = container.querySelector("#metronome-text"); const timeSignature = container.querySelector("#metronome-time-signature"); const subdivision = container.querySelector("#metronome-subdivision"); const beats = beatIds[timeSelection].map(id => document.getElementById(id)); const upQuarterBeatButtons = upQuarterBeatIds[timeSelection].map(id => document.getElementById(id)); const downQuarterBeatButtons = downQuarterBeatIds[timeSelection].map(id => document.getElementById(id)); let currentBeatIndex = 0; let interval; function playMetronome() { if (interval) { clearInterval(interval); interval = null; setStoppedState(icon, text, button); resetVisualStates(beats, downQuarterBeatButtons, upQuarterBeatButtons); currentBeatIndex = 0; setControlsEnabled(true, timeSignature, bpmInput, upQuarterBeatButtons, downQuarterBeatButtons, subdivision); } else { const bpm = parseInt(bpmInput.value, 10); let subBeat = 0; let activeDownButton = document.getElementById(downQuarterBeatIds[timeSelection][0]); let activeUpButton = document.getElementById(upQuarterBeatIds[timeSelection][0]); if (activeDownButton.classList.contains("metronome-selected-button")) { activeDownButton.firstElementChild.className = "metronome-arrow-down-white-icon"; activeDownButton.classList.add("metronome-active-btn"); playBuffer(getDownStrumBuffer()); } if (activeUpButton.classList.contains("metronome-selected-button")) { activeUpButton.firstElementChild.className = "metronome-arrow-up-white-icon"; activeUpButton.classList.add("metronome-active-btn"); playBuffer(getUpStrumBuffer()); } beats[currentBeatIndex].style.color = "red"; const baseTimeSignature = timeSelection.replace("-triplet", ""); const subdivisionType = timeSelection.includes("-triplet") ? "triplet" : "sixteenth"; const config = subdivisionConfigs[baseTimeSignature][subdivisionType]; let beatDivide = config.beatDivide; let subBeatMultiply = config.subBeatMultiply; let effectiveBpm = bpm * (config.bpmMultiplier || 1); interval = setInterval(() => { resetVisualStates(beats, downQuarterBeatButtons, upQuarterBeatButtons); beats[currentBeatIndex].style.color = "black"; subBeat = (subBeat + 1) % beatDivide; if (subBeat === 0) { currentBeatIndex = (currentBeatIndex + 1) % beats.length; } beats[currentBeatIndex].style.color = "red"; let subBeatValue; if (subdivisionType === "triplet") { const tripletMap = [0, 33, 67]; subBeatValue = tripletMap[subBeat]; } else { subBeatValue = subBeat * subBeatMultiply; } const subBeatId = `${currentBeatIndex + 1}.${subBeatValue}`; activeDownButton = document.getElementById(`down-${subBeatId}`); activeUpButton = document.getElementById(`up-${subBeatId}`); if (activeDownButton && activeDownButton.classList.contains("metronome-selected-button")) { activeDownButton.firstElementChild.className = "metronome-arrow-down-white-icon"; activeDownButton.classList.add("metronome-active-btn"); playBuffer(getDownStrumBuffer()); } if (activeUpButton && activeUpButton.classList.contains("metronome-selected-button")) { activeUpButton.firstElementChild.className = "metronome-arrow-up-white-icon"; activeUpButton.classList.add("metronome-active-btn"); playBuffer(getUpStrumBuffer()); } }, (60 / effectiveBpm) * 1000 / beatDivide); setPlayingState(icon, text, button); setControlsEnabled(false, timeSignature, bpmInput, upQuarterBeatButtons, downQuarterBeatButtons, subdivision); } } button.addEventListener("click", playMetronome); }