Tap function fix #7
This commit is contained in:
+63
-17
@@ -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(
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
<div class="left">
|
||||
<div class="grow left">
|
||||
<button @click="$refs.fileInput.click()">
|
||||
<font-awesome-icon :icon="faUpload" size="xl" />
|
||||
</button>
|
||||
@@ -326,8 +370,10 @@ watch(
|
||||
<font-awesome-icon :icon="faDownload" size="xl" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="grow"></div>
|
||||
<div class="right">
|
||||
<div class="grow center">
|
||||
<button id="tapButton" @click="tap">TAP</button>
|
||||
</div>
|
||||
<div class="grow right">
|
||||
<button v-if="view == 'grid'" @click="setView('list')">
|
||||
<font-awesome-icon :icon="faList" size="xl" />
|
||||
</button>
|
||||
|
||||
@@ -223,6 +223,36 @@ input[type="number"] {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.left,
|
||||
.center,
|
||||
.right {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.left {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
#tapButton {
|
||||
border-radius: 32px;
|
||||
height: 64px;
|
||||
width: 64px;
|
||||
margin: -8px;
|
||||
background: var(--secondary);
|
||||
box-shadow: 0 0px 4px var(--shadow);
|
||||
}
|
||||
|
||||
/* DIALOG */
|
||||
#dialog {
|
||||
position: fixed;
|
||||
|
||||
Reference in New Issue
Block a user