mirror of
https://github.com/9x/sheetmusictrainer.git
synced 2026-09-02 09:34:33 +02:00
custom note sets
This commit is contained in:
50
src/App.tsx
50
src/App.tsx
@@ -10,7 +10,9 @@ import {
|
|||||||
} from './music/NoteUtils';
|
} from './music/NoteUtils';
|
||||||
import {
|
import {
|
||||||
TUNINGS,
|
TUNINGS,
|
||||||
getFretboardPositions
|
getFretboardPositions,
|
||||||
|
getOpenStringNotes,
|
||||||
|
getFirstPositionNotes,
|
||||||
} from './music/Tunings';
|
} from './music/Tunings';
|
||||||
import { INSTRUMENT_DEFINITIONS } from './music/InstrumentConfigs';
|
import { INSTRUMENT_DEFINITIONS } from './music/InstrumentConfigs';
|
||||||
import { FretboardHint } from './components/FretboardHint';
|
import { FretboardHint } from './components/FretboardHint';
|
||||||
@@ -44,7 +46,9 @@ function App() {
|
|||||||
volume: 0.5
|
volume: 0.5
|
||||||
},
|
},
|
||||||
zenMode: false,
|
zenMode: false,
|
||||||
gameMode: 'sight_reading'
|
gameMode: 'sight_reading',
|
||||||
|
customMinFret: 0,
|
||||||
|
customMaxFret: 12
|
||||||
});
|
});
|
||||||
|
|
||||||
const [matchStartTime, setMatchStartTime] = useState<number | null>(null);
|
const [matchStartTime, setMatchStartTime] = useState<number | null>(null);
|
||||||
@@ -61,8 +65,46 @@ function App() {
|
|||||||
|
|
||||||
const getNotesFromConfig = (config: typeof rangeConfig) => {
|
const getNotesFromConfig = (config: typeof rangeConfig) => {
|
||||||
if (!config) return [];
|
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<number>();
|
||||||
|
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;
|
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) {
|
if (rangeConfig) {
|
||||||
@@ -74,7 +116,7 @@ function App() {
|
|||||||
const fallbackRange = currentInstrumentDef.ranges[0];
|
const fallbackRange = currentInstrumentDef.ranges[0];
|
||||||
return getNotesFromConfig(fallbackRange);
|
return getNotesFromConfig(fallbackRange);
|
||||||
|
|
||||||
}, [settings.difficulty, currentInstrumentDef]);
|
}, [settings.difficulty, currentInstrumentDef, currentTuning, settings.customMinFret, settings.customMaxFret]);
|
||||||
|
|
||||||
const generateNewNote = useCallback(() => {
|
const generateNewNote = useCallback(() => {
|
||||||
// Determine min/max based on available notes to avoid infinite loops if validNotes empty
|
// Determine min/max based on available notes to avoid infinite loops if validNotes empty
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ export interface AppSettings {
|
|||||||
rhythm: RhythmSettings;
|
rhythm: RhythmSettings;
|
||||||
zenMode: boolean;
|
zenMode: boolean;
|
||||||
gameMode: 'sight_reading' | 'ear_training';
|
gameMode: 'sight_reading' | 'ear_training';
|
||||||
|
customMinFret?: number;
|
||||||
|
customMaxFret?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ControlsProps {
|
interface ControlsProps {
|
||||||
@@ -125,6 +127,41 @@ export const Controls: React.FC<ControlsProps> = ({ settings, onUpdateSettings }
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Custom Fret Range Inputs */}
|
||||||
|
{currentInstrumentDef.ranges.find(r => r.id === settings.difficulty)?.type === 'custom_fret' && (
|
||||||
|
<div className="control-group">
|
||||||
|
<label className="control-label">
|
||||||
|
<span>Fret Range</span>
|
||||||
|
</label>
|
||||||
|
<div style={{ display: 'flex', gap: '8px', width: '100%' }}>
|
||||||
|
<div style={{ flex: 1, display: 'flex', alignItems: 'center', gap: '4px' }}>
|
||||||
|
<span style={{ fontSize: '12px', opacity: 0.7 }}>Min</span>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="24"
|
||||||
|
value={settings.customMinFret ?? 0}
|
||||||
|
onChange={(e) => 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' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ flex: 1, display: 'flex', alignItems: 'center', gap: '4px' }}>
|
||||||
|
<span style={{ fontSize: '12px', opacity: 0.7 }}>Max</span>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="24"
|
||||||
|
value={settings.customMaxFret ?? 12}
|
||||||
|
onChange={(e) => 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' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="control-group">
|
<div className="control-group">
|
||||||
<label className="control-label">
|
<label className="control-label">
|
||||||
<span>Key</span>
|
<span>Key</span>
|
||||||
|
|||||||
@@ -2,12 +2,17 @@ import type { Instrument } from './Tunings';
|
|||||||
|
|
||||||
export type ClefMode = 'treble' | 'bass' | 'grand';
|
export type ClefMode = 'treble' | 'bass' | 'grand';
|
||||||
|
|
||||||
|
export type NoteSetType = 'static' | 'open_strings' | 'first_position' | 'custom_fret';
|
||||||
|
|
||||||
export interface NoteSetConfig {
|
export interface NoteSetConfig {
|
||||||
id: string;
|
id: string;
|
||||||
label: string;
|
label: string;
|
||||||
min: number; // MIDI number
|
type: NoteSetType;
|
||||||
max: number; // MIDI number
|
min?: number; // MIDI number for static
|
||||||
notes?: number[]; // Explicit list of notes (overrides min/max for generation if present)
|
max?: number; // MIDI number for static
|
||||||
|
notes?: number[]; // Explicit list for static
|
||||||
|
defaultMinFret?: number;
|
||||||
|
defaultMaxFret?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InstrumentDefinition {
|
export interface InstrumentDefinition {
|
||||||
@@ -30,17 +35,29 @@ export const INSTRUMENT_DEFINITIONS: Record<string, InstrumentDefinition> = {
|
|||||||
{
|
{
|
||||||
id: 'open',
|
id: 'open',
|
||||||
label: 'Open Strings',
|
label: 'Open Strings',
|
||||||
min: 40,
|
type: 'open_strings',
|
||||||
max: 64,
|
// Legacy fallback if needed, but App should use type
|
||||||
notes: [40, 45, 50, 55, 59, 64] // E2, A2, D3, G3, B3, E4
|
min: 40, max: 64
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'first_pos',
|
id: 'first_pos',
|
||||||
label: 'First Position',
|
label: 'First Position',
|
||||||
min: 40,
|
type: 'first_position',
|
||||||
max: 68 // E2 to G#4 (First 4 frets on high E string e(64) -> g#(68))
|
min: 40, max: 68
|
||||||
},
|
},
|
||||||
{ id: 'all', label: 'All Notes', min: 40, max: 76 }
|
{
|
||||||
|
id: 'all',
|
||||||
|
label: 'All Notes',
|
||||||
|
type: 'static',
|
||||||
|
min: 40, max: 76
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'custom',
|
||||||
|
label: 'Custom Fret Range',
|
||||||
|
type: 'custom_fret',
|
||||||
|
defaultMinFret: 0,
|
||||||
|
defaultMaxFret: 12
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
bass: {
|
bass: {
|
||||||
@@ -51,7 +68,7 @@ export const INSTRUMENT_DEFINITIONS: Record<string, InstrumentDefinition> = {
|
|||||||
showTuning: true,
|
showTuning: true,
|
||||||
ranges: [
|
ranges: [
|
||||||
// Bass Standard: E1 (28) -> G3 approx (55)
|
// Bass Standard: E1 (28) -> G3 approx (55)
|
||||||
{ id: 'all', label: 'All Notes', min: 28, max: 55 }
|
{ id: 'all', label: 'All Notes', type: 'static', min: 28, max: 55 }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
piano: {
|
piano: {
|
||||||
@@ -61,9 +78,9 @@ export const INSTRUMENT_DEFINITIONS: Record<string, InstrumentDefinition> = {
|
|||||||
transpose: 0,
|
transpose: 0,
|
||||||
showTuning: false,
|
showTuning: false,
|
||||||
ranges: [
|
ranges: [
|
||||||
{ id: 'middle_c', label: 'Middle C Area', min: 53, max: 67 }, // F3 to G4
|
{ id: 'middle_c', label: 'Middle C Area', type: 'static', min: 53, max: 67 }, // F3 to G4
|
||||||
{ id: 'two_octave', label: 'Two Octaves', min: 48, max: 72 }, // C3 to C5
|
{ id: 'two_octave', label: 'Two Octaves', type: 'static', min: 48, max: 72 }, // C3 to C5
|
||||||
{ id: 'grand_staff', label: 'Grand Staff Wide', min: 36, max: 84 } // C2 to C6
|
{ id: 'grand_staff', label: 'Grand Staff Wide', type: 'static', min: 36, max: 84 } // C2 to C6
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
voice: {
|
voice: {
|
||||||
@@ -76,10 +93,10 @@ export const INSTRUMENT_DEFINITIONS: Record<string, InstrumentDefinition> = {
|
|||||||
transpose: 0,
|
transpose: 0,
|
||||||
showTuning: false,
|
showTuning: false,
|
||||||
ranges: [
|
ranges: [
|
||||||
{ id: 'soprano', label: 'Soprano (C4-A5)', min: 60, max: 81 },
|
{ id: 'soprano', label: 'Soprano (C4-A5)', type: 'static', min: 60, max: 81 },
|
||||||
{ id: 'alto', label: 'Alto (G3-E5)', min: 55, max: 76 },
|
{ id: 'alto', label: 'Alto (G3-E5)', type: 'static', min: 55, max: 76 },
|
||||||
{ id: 'tenor', label: 'Tenor (C3-A4)', min: 48, max: 69 },
|
{ id: 'tenor', label: 'Tenor (C3-A4)', type: 'static', min: 48, max: 69 },
|
||||||
{ id: 'bass_voice', label: 'Bass (E2-E4)', min: 40, max: 64 }
|
{ id: 'bass_voice', label: 'Bass (E2-E4)', type: 'static', min: 40, max: 64 }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
whistle: {
|
whistle: {
|
||||||
@@ -90,9 +107,9 @@ export const INSTRUMENT_DEFINITIONS: Record<string, InstrumentDefinition> = {
|
|||||||
showTuning: false,
|
showTuning: false,
|
||||||
ranges: [
|
ranges: [
|
||||||
// "starts around E5 for men" -> E5 = 76.
|
// "starts around E5 for men" -> E5 = 76.
|
||||||
{ id: 'basic', label: 'Basic (E5-E6)', min: 76, max: 88 },
|
{ id: 'basic', label: 'Basic (E5-E6)', type: 'static', min: 76, max: 88 },
|
||||||
{ id: 'extended', label: 'Extended (E5-E7)', min: 76, max: 100 },
|
{ id: 'extended', label: 'Extended (E5-E7)', type: 'static', min: 76, max: 100 },
|
||||||
{ id: 'extreme', label: 'Whistle Register (C6-C8)', min: 84, max: 108 }
|
{ id: 'extreme', label: 'Whistle Register (C6-C8)', type: 'static', min: 84, max: 108 }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,6 +19,15 @@ export const DROP_D_TUNING: Tuning = {
|
|||||||
strings: [38, 45, 50, 55, 59, 64]
|
strings: [38, 45, 50, 55, 59, 64]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// D G D G B E - Drop D & G (D2, G2, D3, G3, B3, E4)
|
||||||
|
// Standard: E2 A2 D3 G3 B3 E4
|
||||||
|
// Drop D: D2 A2 D3 G3 B3 E4
|
||||||
|
// Drop DG: D2 G2 D3 G3 B3 E4 (A string dropped to G)
|
||||||
|
export const DROP_DG_TUNING: Tuning = {
|
||||||
|
name: "Drop D & G (Guitar)",
|
||||||
|
strings: [38, 43, 50, 55, 59, 64]
|
||||||
|
};
|
||||||
|
|
||||||
// Bass Standard: E1 (28), A1 (33), D2 (38), G2 (43)
|
// Bass Standard: E1 (28), A1 (33), D2 (38), G2 (43)
|
||||||
export const BASS_STANDARD_TUNING: Tuning = {
|
export const BASS_STANDARD_TUNING: Tuning = {
|
||||||
name: "Standard (Bass)",
|
name: "Standard (Bass)",
|
||||||
@@ -28,11 +37,12 @@ export const BASS_STANDARD_TUNING: Tuning = {
|
|||||||
export const TUNINGS: Record<string, Tuning> = {
|
export const TUNINGS: Record<string, Tuning> = {
|
||||||
"standard": STANDARD_TUNING,
|
"standard": STANDARD_TUNING,
|
||||||
"drop_d": DROP_D_TUNING,
|
"drop_d": DROP_D_TUNING,
|
||||||
|
"drop_dg": DROP_DG_TUNING,
|
||||||
"bass_standard": BASS_STANDARD_TUNING,
|
"bass_standard": BASS_STANDARD_TUNING,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const INSTRUMENT_TUNINGS: Record<Instrument, string[]> = {
|
export const INSTRUMENT_TUNINGS: Record<Instrument, string[]> = {
|
||||||
'guitar': ['standard', 'drop_d'],
|
'guitar': ['standard', 'drop_d', 'drop_dg'],
|
||||||
'bass': ['bass_standard']
|
'bass': ['bass_standard']
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user