diff --git a/src/components/PhraseTrainer.tsx b/src/components/PhraseTrainer.tsx index 3a97fb9..9f8ff30 100644 --- a/src/components/PhraseTrainer.tsx +++ b/src/components/PhraseTrainer.tsx @@ -19,7 +19,9 @@ import { audioEngine } from '../audio/AudioEngine'; import { computePlayableNotes, isFrettedInstrument, positionsWithinWindow, fretWindowNotes } from '../music/playableRange'; import { generateMelody } from '../music/melodyGenerator'; import { generateScaleDrill } from '../music/scaleDrills'; +import { generateArpeggio, type ArpeggioDegree } from '../music/arpeggioGenerator'; import { isMode, type ModeId } from '../music/scales'; +import { ARPEGGIO_DEGREE_LABELS } from '../music/arpeggioGenerator'; import { scoreEvents, type Result, type Score, type ScoreEvent } from '../score/model'; import { getNoteDetails } from '../music/NoteUtils'; import { EXERCISES, loadExercise } from '../exercises/library'; @@ -200,6 +202,16 @@ export const PhraseTrainer = forwardRef( meter: { numerator: phrase.meterNumerator, denominator: 4 }, }, pool); } + if (phrase.material === 'arpeggio') { + return generateArpeggio({ + keyTonic: phrase.keyTonic, + keyMode: (isMode(phrase.keyMode) ? phrase.keyMode : 'major') as ModeId, + degree: (phrase.arpeggioDegree as ArpeggioDegree) || 'I', + pattern: phrase.arpeggioPattern, + coverage: phrase.arpeggioCoverage, + meter: { numerator: phrase.meterNumerator, denominator: 4 }, + }, pool); + } if (phrase.material === 'library') { const id = phrase.libraryId || EXERCISES[0]?.id; if (!id) return { ok: false, error: 'No exercises available.' }; @@ -212,6 +224,7 @@ export const PhraseTrainer = forwardRef( return null; }, [phrase.material, phrase.keyTonic, phrase.keyMode, phrase.bars, phrase.meterNumerator, phrase.rhythmLevel, phrase.scaleDirection, phrase.scaleCoverage, phrase.libraryId, + phrase.arpeggioDegree, phrase.arpeggioPattern, phrase.arpeggioCoverage, melodySeed, pool, importedScore]); const fullScore: Result | null = useMemo(() => { @@ -546,12 +559,13 @@ export const PhraseTrainer = forwardRef( - {(phrase.material === 'melody' || phrase.material === 'scale') && ( + {(phrase.material === 'melody' || phrase.material === 'scale' || phrase.material === 'arpeggio') && ( Key/mode: use the "Target key" controls below the sheet — shared across modes. @@ -605,6 +619,38 @@ export const PhraseTrainer = forwardRef( )} + {phrase.material === 'arpeggio' && ( + <> + + + + + + )} + {phrase.material === 'library' && (