From 03a5e8e3c1248e8e82fc337937133dc921e3b19b Mon Sep 17 00:00:00 2001
From: Jens Mohrmann
Date: Sat, 27 Dec 2025 18:43:19 +0100
Subject: [PATCH] mic sensitivity setting
---
src/App.tsx | 4 +++-
src/audio/PitchAnalyzer.ts | 29 ++++++++++++++++++++++++++---
src/components/Controls.tsx | 1 +
src/components/SettingsModal.tsx | 25 +++++++++++++++++++++++++
src/hooks/usePitchDetector.ts | 14 +++++++++++++-
5 files changed, 68 insertions(+), 5 deletions(-)
diff --git a/src/App.tsx b/src/App.tsx
index 3e72345..6c80290 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -26,7 +26,6 @@ const NOTE_MATCH_THRESHOLD_MS = 300; // How long to convert hold note to confirm
function App() {
const [listening, setListening] = useState(false);
- const { pitchData, error } = usePitchDetector(listening);
const { playNote } = useAudioPlayer();
const [targetMidi, setTargetMidi] = useState(60); // Start with C4
@@ -55,9 +54,12 @@ function App() {
autoPlayVolume: 0.5,
virtualGuitarVolume: 0.5,
virtualGuitarMute: false,
+ micSensitivity: 0.5,
disableAnimation: false
});
+ const { pitchData, error } = usePitchDetector(listening, settings.micSensitivity);
+
const [matchStartTime, setMatchStartTime] = useState(null);
const [feedbackMessage, setFeedbackMessage] = useState("");
const [revealed, setRevealed] = useState(false);
diff --git a/src/audio/PitchAnalyzer.ts b/src/audio/PitchAnalyzer.ts
index f8ed40b..2a80dda 100644
--- a/src/audio/PitchAnalyzer.ts
+++ b/src/audio/PitchAnalyzer.ts
@@ -13,6 +13,28 @@ export class PitchAnalyzer {
this.buffer = new Float32Array(2048); // Standard size
}
+ private sensitivityThreshold = 0.03; // Default
+
+ /**
+ * Set sensitivity from 0.0 (least sensitive) to 1.0 (most sensitive).
+ * Maps to RMS threshold:
+ * 0.0 -> 0.1 (Requires loud input)
+ * 0.5 -> 0.03 (Default)
+ * 1.0 -> 0.005 (Very sensitive)
+ */
+ setSensitivity(value: number) {
+ // Clamp value 0-1
+ const v = Math.max(0, Math.min(1, value));
+
+ // Linear interpolation or something that feels right
+ // Let's do a simple mapping:
+ // 1.0 -> 0.002
+ // 0.0 -> 0.1
+ // linear: 0.1 - (0.098 * v) roughly
+
+ this.sensitivityThreshold = 0.1 - (0.095 * v);
+ }
+
async start(): Promise {
if (this.audioContext) return;
@@ -58,12 +80,13 @@ export class PitchAnalyzer {
}
rms = Math.sqrt(rms / this.buffer.length);
- if (rms < 0.05) return null; // Silence threshold increased to 0.05 for robustness because 0.01 picked up noise
+ if (rms < this.sensitivityThreshold) return null;
const pitch = this.detector(this.buffer);
- // Widen range for Bass (E1 ~41Hz) and Whistle (C8 ~4186Hz)
- if (pitch && (pitch < 30 || pitch > 5000)) return null;
+ // Widen range for Bass (E1 ~41Hz, but A0 is 27.5Hz) and Whistle (C8 ~4186Hz, Harmonics go higher)
+ // Range 25Hz - 8000Hz covers Piano A0 to well above highest fundamental
+ if (pitch && (pitch < 25 || pitch > 8000)) return null;
return pitch;
}
diff --git a/src/components/Controls.tsx b/src/components/Controls.tsx
index 0c9b182..c0b5a49 100644
--- a/src/components/Controls.tsx
+++ b/src/components/Controls.tsx
@@ -32,6 +32,7 @@ export interface AppSettings {
autoPlaySightReading?: boolean;
autoPlayVolume?: number;
virtualGuitarVolume?: number;
+ micSensitivity?: number; // 0.0 to 1.0 (0=least sensitive, 1=most)
virtualGuitarMute?: boolean;
disableAnimation?: boolean;
}
diff --git a/src/components/SettingsModal.tsx b/src/components/SettingsModal.tsx
index 1aadf7d..147d387 100644
--- a/src/components/SettingsModal.tsx
+++ b/src/components/SettingsModal.tsx
@@ -111,6 +111,31 @@ export const SettingsModal: React.FC = ({ isOpen, onClose, s
? "Automatically plays the note audio (Required for Ear Training)."
: "Automatically plays the note audio when a new note appears."}
+
+
+
+ {/* Mic Sensitivity */}
+
+
+
+ Low
+ onUpdateSettings({ ...settings, micSensitivity: parseFloat(e.target.value) })}
+ style={{ flex: 1 }}
+ />
+ High
+
+
+ Adjust if notes are not detected (increase) or if background noise triggers notes (decrease).
+
+
diff --git a/src/hooks/usePitchDetector.ts b/src/hooks/usePitchDetector.ts
index c798005..ba1e65f 100644
--- a/src/hooks/usePitchDetector.ts
+++ b/src/hooks/usePitchDetector.ts
@@ -10,16 +10,27 @@ interface PitchData {
clarity: number; // Placeholder for now, maybe uses probability if YIN exposes it
}
-export function usePitchDetector(active: boolean) {
+export function usePitchDetector(active: boolean, sensitivity: number = 0.5) {
const analyzerRef = useRef(null);
const [pitchData, setPitchData] = useState(null);
const [isListening, setIsListening] = useState(false);
const [error, setError] = useState(null);
const animationRef = useRef(null);
+ // Update sensitivity when it changes
+ useEffect(() => {
+ if (analyzerRef.current) {
+ analyzerRef.current.setSensitivity(sensitivity);
+ }
+ }, [sensitivity]);
+
const updatePitch = useCallback(() => {
if (!analyzerRef.current) return;
+ // Ensure sensitivity is set on start
+ // (Though the effect above handles updates, safely re-asserting here doesn't hurt,
+ // but let's trust the effect and the init sequence).
+
const freq = analyzerRef.current.getPitch();
if (freq) {
const midi = frequencyToMidi(freq);
@@ -46,6 +57,7 @@ export function usePitchDetector(active: boolean) {
if (active) {
if (!analyzerRef.current) {
analyzerRef.current = new PitchAnalyzer();
+ analyzerRef.current.setSensitivity(sensitivity);
}
analyzerRef.current.start()