From b8e8623024ad594a61838acdfc3c49ede75d6a03 Mon Sep 17 00:00:00 2001 From: Jens Mohrmann Date: Fri, 26 Dec 2025 18:25:55 +0100 Subject: [PATCH] custom note sets --- src/App.tsx | 50 ++++++++++++++++++++++++++--- src/components/Controls.tsx | 37 ++++++++++++++++++++++ src/music/InstrumentConfigs.ts | 57 ++++++++++++++++++++++------------ src/music/Tunings.ts | 12 ++++++- 4 files changed, 131 insertions(+), 25 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index d9a5b6a..41cc6e0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,7 +10,9 @@ import { } from './music/NoteUtils'; import { TUNINGS, - getFretboardPositions + getFretboardPositions, + getOpenStringNotes, + getFirstPositionNotes, } from './music/Tunings'; import { INSTRUMENT_DEFINITIONS } from './music/InstrumentConfigs'; import { FretboardHint } from './components/FretboardHint'; @@ -44,7 +46,9 @@ function App() { volume: 0.5 }, zenMode: false, - gameMode: 'sight_reading' + gameMode: 'sight_reading', + customMinFret: 0, + customMaxFret: 12 }); const [matchStartTime, setMatchStartTime] = useState(null); @@ -61,8 +65,46 @@ function App() { const getNotesFromConfig = (config: typeof rangeConfig) => { if (!config) return []; + + // Dynamic logic based on type + if (config.type === 'open_strings') { + // Use current tuning if applicable (Guitar/Bass) + if (currentTuning) { + return getOpenStringNotes(currentTuning); + } + // Fallback for non-fretted if they happen to use this type (unlikely) + return config.notes || []; + } + + if (config.type === 'first_position') { + if (currentTuning) { + return getFirstPositionNotes(currentTuning); + } + return config.notes || []; + } + + if (config.type === 'custom_fret') { + if (currentTuning) { + const minFret = settings.customMinFret ?? config.defaultMinFret ?? 0; + const maxFret = settings.customMaxFret ?? config.defaultMaxFret ?? 12; + + const notes = new Set(); + currentTuning.strings.forEach(stringMidi => { + for (let fret = minFret; fret <= maxFret; fret++) { + notes.add(stringMidi + fret); + } + }); + return Array.from(notes).sort((a, b) => a - b); + } + return []; + } + + // Static fallback if (config.notes) return config.notes; - return Array.from({ length: config.max - config.min + 1 }, (_, i) => config.min + i); + if (config.min !== undefined && config.max !== undefined) { + return Array.from({ length: config.max - config.min + 1 }, (_, i) => config.min! + i); + } + return []; }; if (rangeConfig) { @@ -74,7 +116,7 @@ function App() { const fallbackRange = currentInstrumentDef.ranges[0]; return getNotesFromConfig(fallbackRange); - }, [settings.difficulty, currentInstrumentDef]); + }, [settings.difficulty, currentInstrumentDef, currentTuning, settings.customMinFret, settings.customMaxFret]); const generateNewNote = useCallback(() => { // Determine min/max based on available notes to avoid infinite loops if validNotes empty diff --git a/src/components/Controls.tsx b/src/components/Controls.tsx index 5b9ddcf..17b560a 100644 --- a/src/components/Controls.tsx +++ b/src/components/Controls.tsx @@ -24,6 +24,8 @@ export interface AppSettings { rhythm: RhythmSettings; zenMode: boolean; gameMode: 'sight_reading' | 'ear_training'; + customMinFret?: number; + customMaxFret?: number; } interface ControlsProps { @@ -125,6 +127,41 @@ export const Controls: React.FC = ({ settings, onUpdateSettings } + {/* Custom Fret Range Inputs */} + {currentInstrumentDef.ranges.find(r => r.id === settings.difficulty)?.type === 'custom_fret' && ( +
+ +
+
+ Min + onUpdateSettings({ ...settings, customMinFret: parseInt(e.target.value) || 0 })} + className="control-input" + style={{ width: '100%', padding: '4px', borderRadius: '4px', border: '1px solid rgba(255,255,255,0.2)', background: 'rgba(0,0,0,0.2)', color: 'white' }} + /> +
+
+ Max + onUpdateSettings({ ...settings, customMaxFret: parseInt(e.target.value) || 0 })} + className="control-input" + style={{ width: '100%', padding: '4px', borderRadius: '4px', border: '1px solid rgba(255,255,255,0.2)', background: 'rgba(0,0,0,0.2)', color: 'white' }} + /> +
+
+
+ )} +