diff --git a/src/App.vue b/src/App.vue index 2258f77..5a21418 100644 --- a/src/App.vue +++ b/src/App.vue @@ -13,6 +13,12 @@ const STORAGE_KEY_PRESETS = "ClickPresets" const STORAGE_KEY_VIEW = "ClickView" const STORAGE_KEY_SORT = "ClickSort" +// TAP +const TAP_RESET_MS = 2000; // nach dieser Pause beginnt eine neue Messung +const TAP_MAX_SAMPLES = 8; // so viele letzte Taps werden gemittelt + +let tapTimes = []; + // Reaktiver Zustand (für die UI) const bpm = ref(120); const isRunning = ref(false); @@ -81,20 +87,7 @@ async function start() { if (isRunning.value) return; clampBpm(); - - try { - // AudioContext erst durch User-Interaktion starten - if (!audioContext) { - audioContext = new AudioContext(); - } - if (!clickBuffer) { - await loadClickSound(); - } - if (audioContext.state === "suspended") { - await audioContext.resume(); - } - } catch (error) { - console.error("Metronom konnte nicht gestartet werden:", error); + if (!initAudioInterface()) { return; } @@ -112,6 +105,25 @@ function stop() { } } +function initAudioInterface() { + try { + // AudioContext erst durch User-Interaktion starten + if (!audioContext) { + audioContext = new AudioContext(); + } + if (!clickBuffer) { + loadClickSound(); + } + if (audioContext.state === "suspended") { + audioContext.resume(); + } + } catch (error) { + console.error("Metronom konnte nicht gestartet werden:", error); + return false; + } + return true; +} + onBeforeUnmount(() => { stop(); if (audioContext) { @@ -122,6 +134,9 @@ onBeforeUnmount(() => { //PRESETS function loadPresets() { + + initAudioInterface(); + try { const raw = localStorage.getItem(STORAGE_KEY_PRESETS); return raw ? JSON.parse(raw) : []; @@ -204,6 +219,35 @@ function setSort(val) { localStorage.setItem(STORAGE_KEY_SORT, val); } +// TAP + +function tap() { + stop(); + selectedPreset.value = null; + playClick(0); + const now = performance.now(); + + // Lange Pause seit dem letzten Tap: neu beginnen + if (tapTimes.length && now - tapTimes[tapTimes.length - 1] > TAP_RESET_MS) { + tapTimes = []; + } + + tapTimes.push(now); + if (tapTimes.length > TAP_MAX_SAMPLES) { + tapTimes.shift(); + } + + // Mindestens zwei Taps nötig, um einen Abstand zu haben + if (tapTimes.length < 2) return; + + // Durchschnittlicher Abstand zwischen den Taps in ms + const intervals = tapTimes.slice(1).map((t, i) => t - tapTimes[i]); + const avg = intervals.reduce((sum, v) => sum + v, 0) / intervals.length; + + bpm.value = Math.round(60000 / avg); + clampBpm(); +} + // EXPORT / IMPORT function exportPresets() { const json = JSON.stringify(presets.value, null, 2); @@ -318,7 +362,7 @@ watch(