From d3a28404541aaffdb1130b4b64640be0b5185a41 Mon Sep 17 00:00:00 2001 From: vadimwit Date: Thu, 5 Mar 2026 23:25:40 +0000 Subject: [PATCH] new song button, chord-boosted key detection, double-confirm key changes --- src/App.jsx | 85 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 67 insertions(+), 18 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 90e340c..d894fec 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -8,12 +8,13 @@ import Fretboard from './components/Fretboard' import ProgressionSuggestions from './components/ProgressionSuggestions' import Tuner from './components/Tuner' import Piano from './components/Piano' -import { NOTES, detectKey, detectTopKeys, matchChordFromChroma, detectRepeatingProgression } from './lib/theory' +import { NOTES, detectKey, detectTopKeys, matchChordFromChroma, detectRepeatingProgression, getChordTones } from './lib/theory' // Key detection tuning -const NOTE_HISTORY_SIZE = 80 +const NOTE_HISTORY_SIZE = 160 // larger = chord boosts dominate over melody runs const KEY_VOTE_WINDOW = 12 -const KEY_VOTE_THRESHOLD = 9 // out of 12 — very stable +const KEY_VOTE_THRESHOLD = 9 // out of 12 +const CHORD_NOTE_BOOST = 3 // confirmed chord tones injected N× into note history // Chord detection tuning const CHROMA_SMOOTH = 12 // frames to average (~200ms at 60fps) @@ -52,6 +53,7 @@ export default function App() { const chromaIdxRef = useRef(0) const chordVotesRef = useRef([]) const progressionVoteRef = useRef(null) // stabilise loop display + const pendingKeyRef = useRef(null) // proposed key change — require 2 consecutive wins // Keep ref in sync useEffect(() => { effectiveKeyRef.current = effectiveKey }, [effectiveKey]) @@ -68,6 +70,23 @@ export default function App() { } }, [chordHistory]) + // ── New song — full reset ───────────────────────────────────────────────── + function newSong() { + noteHistoryRef.current = [] + keyVotesRef.current = [] + chordVotesRef.current = [] + progressionVoteRef.current = null + pendingKeyRef.current = null + chromaIdxRef.current = 0 + chromaRingRef.current = Array.from({ length: CHROMA_SMOOTH }, () => new Float32Array(12)) + setKeyInfo(null) + setLockedKey(null) + effectiveKeyRef.current = null + setChordHistory([]) + setDetectedProgression(null) + setTopKeyCandidates([]) + } + // ── Key lock handlers ───────────────────────────────────────────────────── function applyLock() { const info = { root: lockRoot, mode: lockMode, confidence: 1 } @@ -110,15 +129,26 @@ export default function App() { if (count >= KEY_VOTE_THRESHOLD) { const [root, mode] = winner.split('_') + const candidateKey = `${root}_${mode}` + setKeyInfo(prev => { - if (prev?.root === root && prev?.mode === mode) { + const currentKey = prev ? `${prev.root}_${prev.mode}` : null + + if (currentKey === candidateKey) { + // Same key — just refresh confidence, clear pending + pendingKeyRef.current = null return { root, mode, confidence: result.confidence } } - // Key changed — reset chord votes but keep history visible - if (!lockedKey) { - chordVotesRef.current = [] + + // Different key — require a second consecutive win before committing + if (pendingKeyRef.current === candidateKey) { + pendingKeyRef.current = null + if (!lockedKey) chordVotesRef.current = [] + return { root, mode, confidence: result.confidence } } - return { root, mode, confidence: result.confidence } + + pendingKeyRef.current = candidateKey + return prev // hold current key until next confirmation }) } }, [lockedKey]) @@ -156,6 +186,17 @@ export default function App() { if (prev[prev.length - 1] === winner) return prev return [...prev.slice(-30), winner] }) + + // Inject chord tones into note history with boost weight so key detection + // anchors to confirmed chords rather than transient melody notes + const chordPCs = getChordTones(winner) + .map(n => NOTES.indexOf(n)) + .filter(i => i >= 0) + const history = noteHistoryRef.current + for (let j = 0; j < CHORD_NOTE_BOOST; j++) { + for (const pc of chordPCs) history.push(pc) + } + while (history.length > NOTE_HISTORY_SIZE) history.shift() } }, []) @@ -172,16 +213,24 @@ export default function App() {

Real-time key detection for real humans

- +
+ + +
{/* ── Controls bar ── */}