diff --git a/src/AppConfig.ts b/src/AppConfig.ts
index 4f85e7a..48b0b25 100644
--- a/src/AppConfig.ts
+++ b/src/AppConfig.ts
@@ -3,4 +3,23 @@
*/
// How long (in ms) a note must be held to be accepted as correct
-export const NOTE_MATCH_THRESHOLD_MS = 100;
+// How long (in ms) a note must be held to be accepted as correct
+export const NOTE_MATCH_THRESHOLD_MS = 50;
+
+// Microphone Sensitivity Configuration
+// Sensitivity Scale: 0.0 (Low) to 1.0 (High)
+// Linear mapping to dB thresholds
+export const MIC_SENSITIVITY_DB_RANGE = {
+ min: -10, // 0.0 Sensitivity: Loudest input required (e.g. loud singing/instrument)
+ max: -100 // 1.0 Sensitivity: Quietest input accepted (mic floor)
+};
+
+export const MIC_DEFAULT_SENSITIVITY = 0.75; // Bias towards high sensitivity for ease of use
+
+// Audio Constraints for getUserMedia
+// Disabled processing is generally better for musical pitch detection
+export const AUDIO_CONSTRAINTS = {
+ echoCancellation: false,
+ noiseSuppression: false,
+ autoGainControl: false,
+};
diff --git a/src/audio/PitchAnalyzer.ts b/src/audio/PitchAnalyzer.ts
index 54c0dce..153f11b 100644
--- a/src/audio/PitchAnalyzer.ts
+++ b/src/audio/PitchAnalyzer.ts
@@ -1,4 +1,5 @@
import { YIN } from "pitchfinder";
+import { MIC_SENSITIVITY_DB_RANGE, AUDIO_CONSTRAINTS } from "../AppConfig";
export interface MicrophoneDebugInfo {
audioContextState: AudioContextState | 'inactive';
@@ -34,17 +35,15 @@ export class PitchAnalyzer {
/**
* Set sensitivity from 0.0 (least sensitive) to 1.0 (most sensitive).
- * Maps to approximate dB Thresholds:
- * 0.0 -> -20 dB (0.1 RMS) - Requires loud input
- * 1.0 -> -60 dB (0.001 RMS) - Very sensitive, picks up background noise
+ * Maps to approximate dB Thresholds based on AppConfig
*/
setSensitivity(value: number) {
// Clamp value 0-1
const v = Math.max(0, Math.min(1, value));
- // Linear map to dB: -20dB to -60dB
- // High sensitivity (1.0) = Lower Threshold (-60dB)
- const db = -20 - (v * 40);
+ // Linear map to dB using Config Range
+ const { min, max } = MIC_SENSITIVITY_DB_RANGE;
+ const db = min + (v * (max - min));
// Convert dB to RMS amplitude
this.sensitivityThreshold = Math.pow(10, db / 20);
@@ -68,13 +67,9 @@ export class PitchAnalyzer {
this.detector = YIN({ sampleRate: this.audioContext.sampleRate });
try {
- // Use more explicit audio constraints for better Android compatibility
+ // Use configured audio constraints
const constraints: MediaStreamConstraints = {
- audio: {
- echoCancellation: false,
- noiseSuppression: false,
- autoGainControl: false,
- }
+ audio: AUDIO_CONSTRAINTS
};
this.mediaStream = await navigator.mediaDevices.getUserMedia(constraints);
diff --git a/src/components/Fretboard.tsx b/src/components/Fretboard.tsx
index e1103d8..dec4f1b 100644
--- a/src/components/Fretboard.tsx
+++ b/src/components/Fretboard.tsx
@@ -1,5 +1,5 @@
-import { useState } from 'react';
-import type { Tuning, FretPosition } from '../music/Tunings';
+import { useState, useMemo } from 'react';
+import { getFretboardPositions, type Tuning, type FretPosition } from '../music/Tunings';
import { getNoteDetails } from '../music/NoteUtils';
interface FretboardProps {
@@ -27,6 +27,14 @@ export function Fretboard({
}: FretboardProps) {
const [hoverPos, setHoverPos] = useState<{ stringIndex: number, fret: number } | null>(null);
+ const alternatePositions = useMemo(() => {
+ if (!showHints || !hoverPos) return [];
+ const midi = tuning.strings[hoverPos.stringIndex] + hoverPos.fret;
+ return getFretboardPositions(midi, tuning, maxFrets).filter(
+ p => p.stringIndex !== hoverPos.stringIndex || p.fret !== hoverPos.fret
+ );
+ }, [hoverPos, tuning, maxFrets, showHints]);
+
// Config
const numStrings = tuning.strings.length;
// Visual params
@@ -199,12 +207,29 @@ export function Fretboard({
{/* Hover Highlight (Ghost Dot) */}
{interactive && hoverPos && (
- (() => {
- const cx = getNoteX(hoverPos.fret);
- const cy = getStringY(hoverPos.stringIndex);
+