Hiyve Components - v1.0.0
    Preparing search index...

    Module @hiyve/react-music-theory

    @hiyve/react-music-theory — React hooks and components for music theory drills: a note on the staff to name, the answers to choose from, and the score at the end.

    • useDrill — a drill in a component: the round being asked, answering, moving on, starting over
    • useNoteSynth — sound a note or a chord with the built-in voice
    • TheoryDrillPanel — the settings of a drill: notes, intervals, chords, key signatures or scales
    • TheoryDrillGuide — what the chosen exercise is, how it goes and what its options do, in plain words
    • DrillCard — one round: the staff — or, asked by ear, the sound first — the answers, right or wrong, next; for a question answered by playing, what to play, with a panel of your own that listens
    • DrillScoreCard — how the drill went
    • StaffSnippet / AnswerChoices — the pieces on their own
    import { generateTheoryQuestion, newSeed } from '@hiyve/music-theory';
    import { useDrill, DrillCard, DrillScoreCard } from '@hiyve/react-music-theory';

    const drill = useDrill({ generate: (random) => generateTheoryQuestion(settings, random), length: 10, seed });
    return drill.round
    ? <DrillCard round={drill.round} index={drill.index} total={drill.total} loadEngine={loadEngine} onAnswer={drill.answer} onNext={drill.next} />
    : <DrillScoreCard summary={drill.summary} onAgain={() => drill.restart()} onAnother={() => setSeed(newSeed())} />;

    @hiyve/react-music-theory

    React hooks and components for music theory drills: a note, an interval, a chord, a key signature or a scale on the staff to name — or heard first and named by ear — the answers to choose from — or the answer played on an instrument — the question played once answered, and the score at the end. The questions come from @hiyve/music-theory; the staff is engraved by the notation engine you supply, as with @hiyve/react-music-notation.

    npm install @hiyve/react-music-theory @hiyve/music-theory @hiyve/react-music-notation
    

    Peer dependencies: react, react-dom, @mui/material, @mui/icons-material, @emotion/react, @emotion/styled.

    import { useState } from 'react';
    import { defaultTheoryDrillSettings, generateTheoryQuestion, newSeed } from '@hiyve/music-theory';
    import { useDrill, DrillCard, DrillScoreCard, TheoryDrillPanel } from '@hiyve/react-music-theory';

    const loadEngine = () => import('opensheetmusicdisplay');

    function TheoryDrill() {
    const [settings, setSettings] = useState(defaultTheoryDrillSettings);
    const [length, setLength] = useState(10);
    const [seed, setSeed] = useState(() => newSeed());
    const drill = useDrill({ generate: (random) => generateTheoryQuestion(settings, random), length, seed });
    return (
    <>
    <TheoryDrillPanel settings={settings} onChange={setSettings} length={length} onLengthChange={setLength} onStart={() => setSeed(newSeed())} locked={!drill.finished && drill.summary.answered > 0} />
    {drill.round ? (
    <DrillCard round={drill.round} index={drill.index} total={drill.total} summary={drill.summary} loadEngine={loadEngine} onAnswer={drill.answer} onNext={drill.next} />
    ) : (
    <DrillScoreCard summary={drill.summary} onAgain={() => drill.restart()} onAnother={() => setSeed(newSeed())} />
    )}
    </>
    );
    }

    A drill is drawn from its seed, so "Try the same one again" asks the same questions and a new seed asks new ones. Each question is played as an answer is chosen — a chord at once, a melodic interval or a scale note by note — so it is heard as well as seen, and the card keeps the score as the drill goes. Answers show by name: "Major 3rd", "Half-diminished 7th", "Dorian", "E♭ major".

    Turn on the panel's By ear switch — or set byEar: true in a drill's settings — and every question of notes, intervals, chords and scales is heard before it is seen. DrillCard plays it as it appears, with the staff hidden and a button to hear it again; a note is played after a C, to name it from ("First you hear C, then the question"). Key signatures are read — there is nothing in a signature to hear — so the switch is not offered for them. Once answered, the staff shows and the question plays again, each note lit as it sounds. No two answers offered sound alike, so F♯ and G♭, or an augmented 4th and a diminished 5th, are never both there.

    The first question plays as the card appears, after the click that started the drill; if the browser keeps the sound from starting, the "Hear it again" button plays it.

    Turn on the panel's Play the answer switch — or set playAnswer: true in a drill's settings, for any kind — and each question is answered on an instrument instead of by choosing. DrillCard says what to play ("Play or sing this note. Then the Major 3rd above it.", "Play the Minor chord in first inversion, with this note at the bottom."), shows and sounds the note to start from — a note to read and play is shown alone, giving nothing away — and once answered shows the notes to play marked as a graded take's are — each coloured by how it was played, with the note played in a wrong one's place drawn beside it. With By ear on too, the question is heard and played back.

    The listening itself is PlayDrillCard, from @hiyve/react-music-performance/theory: it hears the playing through the microphone or a MIDI instrument, with the same note following as a practice take, and judges it note by note. A card of your own can listen any other way through DrillCard's answerArea, noteStyles and onSoundingChange; without an answerArea, a question set to be played is answered by choosing, like any other.

    Hook Description
    useDrill({ generate, length?, seed? }) A drill in a component: drill, round, question, index, total, length (the questions it was drawn with), finished, summary, and answer(choice, correct?), next(), finish(), restart({ seed?, length? }). finish() ends the drill early, its summary of the rounds answered; restart() then asks all of it again. The drill starts anew whenever seed or length change; the time each answer takes is recorded with it. correct is for an answer judged elsewhere — one played on an instrument
    useNoteSynth({ gain?, durationMs?, samples?, sampleWaitMs?, onError? }) Sound notes — on recorded samples when samples names a host, otherwise with the built-in voice: play(midi | midi[], durationMs?, listener?) at once, playInOrder(midis, stepMs?, durationMs?, listener?) one after another, preload(midis), supported, and needsGesture. needsGesture is true when the browser held the sound back until a tap, as iOS can, so nothing was heard; play again from a tap. Both plays return a function that stops them early; the optional NoteSynthListener (from @hiyve/music-theory) is told as each note starts and stops sounding, to light it on the staff. The sound starts on the first play — call it from a click, as browsers require — and stops when the component goes, after a ringing note finishes; without Web Audio nothing plays
    Component Description
    TheoryDrillPanel The settings of a drill: which drill — notes, intervals, chords, key signatures or scales — whether it is asked by ear (all but key signatures) and answered by playing, the clef, and that drill's own options, the choices, the questions, and a Start button
    TheoryDrillGuide What the exercise chosen in the panel is and how it goes — numbered steps, as By ear and Play the answer are set — in plain words for anyone new to music; with a Start button when onStart is given. Made for the space beside the panel before an exercise starts
    DrillCard One round: where it is in the drill and the score so far, the staff — or, asked by ear, the sound first and the staff once answered — the answers, right or wrong once answered — with the note played as the choice is made and a button to hear it again — and the button to the next round. Answered by playing: what to play, the note to start from, and a panel of yours that listens. When the staff cannot be drawn, a question read off it waits, with a button to try again; when the browser holds the sound back until a tap, a question asked by ear asks for one
    DrillScoreCard How the drill went: the share right as a ring with a word on it, the count, the time per answer, the questions missed, and buttons for the same drill again or another
    StaffSnippet A question's one-bar MusicXML, engraved and nothing else, centred in its box, with any of its notes lit. A staff that cannot be drawn says so, with a button to try again
    AnswerChoices The answers to choose from, as buttons, shown as formatChoice writes them; the right one shows green and a wrong choice red once answered
    Prop Type Default Description
    settings TheoryDrillSettings required The settings chosen in the panel: the guide follows them
    onStart () => void — Start the exercise; the button shows when given
    panelLabels Partial<TheoryDrillPanelLabels> — The panel's labels, when changed, so the guide names the exercise as the panel does
    sx, labels MUI sx; Partial<TheoryDrillGuideLabels> — the steps are one to a line
    <TheoryDrillPanel settings={settings} onChange={setSettings} length={length} onLengthChange={setLength} onStart={start} />
    {!started && <TheoryDrillGuide settings={settings} onStart={start} />}

    The default words are short and plain, for anyone new to music: "Two notes. How far apart are they?", "Play or sing your answer." drillHint(settings, labels) is the one line the panel and the guide both show for what an exercise asks.

    Prop Type Default Description
    settings TheoryDrillSettings required Every drill's settings and the drill asked, from @hiyve/music-theory (defaultTheoryDrillSettings)
    onChange (next: TheoryDrillSettings) => void required A setting changed. Each drill keeps its own settings; a change of clef, the By ear switch or the Play the answer switch moves them all (key signatures are always read). Answered by playing, there are no choices to count
    length / onLengthChange number / (length) => void required Questions in a drill
    lengthChoices number[] [5, 10, 20] Lengths offered
    onStart () => void — The Start button, shown when given
    locked boolean false A drill is running: the settings hold still
    names TheoryNamesOverrides — The names the intervals, chords and scales are listed by
    sx, labels MUI sx; Partial<TheoryDrillPanelLabels>

    Each drill's options: notes — on the staff or out to the ledger lines, sharps and flats; intervals — which intervals, together or one note after the other, sharps and flats; chords — which qualities, inversions, sharps and flats; key signatures — up to how many sharps or flats, major or minor keys or both; scales — which scales, sharps and flats. For intervals, chords and scales, "sharps and flats" lets the notes they are built on be sharps and flats too. A list always keeps at least one entry.

    Prop Type Default Description
    round DrillRound required The round being asked, from useDrill
    index / total number required Where the round is in the drill
    loadEngine NotationEngineLoader required Supplies the notation engine for the staff
    onAnswer (choice: string, correct?: boolean) => void required A choice was made — or an answer played, with whether it was right — as useDrill's answer takes it
    onNext () => void required Move on
    onFinish () => void — End the drill early, keeping what was answered (useDrill's finish): a Finish button beside the score, until the last round is answered
    summary DrillSummary — useDrill's summary: given, the card shows the running count of right and wrong answers and a bar for how far through the drill it is
    sound boolean true Play the question as a choice is made, with a "Hear it again" button once answered. Each note lights up on the staff while it sounds — one after another for a scale or a melodic interval, together for a chord. A question asked by ear is also played as it appears, its staff hidden until answered; with sound off, or without Web Audio, it shows its staff like any other
    samples SampleSource — Play it on recorded samples — a piano, say — instead of the built-in voice; the question's notes download as it appears. See below
    onError (error: Error) => void — The staff failed to draw or the sound failed to start
    children ReactNode — Anything to show under the staff
    answerArea ReactNode — For a question answered by playing: shown in place of the answer buttons — a panel that listens and calls onAnswer(played, correct). PlayDrillCard from @hiyve/react-music-performance/theory is one
    noteStyles ReadonlyMap<string, MusicXmlNoteStyle> — Answered by playing: how the notes to play were played, drawn on the staff once answered — the note styles MusicXmlPlayer takes, keyed by musicXmlNoteKey; a graded take's (reportNoteStyles from @hiyve/react-music-performance) show the note played in a wrong one's place beside it
    onSoundingChange (sounding: boolean) => void — Told as the card starts and stops sounding — the question, the note to start from — so a microphone can wait for it to fall quiet
    names TheoryNamesOverrides — The names answers are shown by
    sx, labels, colors MUI sx; Partial<DrillCardLabels>; Partial<DrillColors>
    Prop Type Default Description
    summary DrillSummary required From useDrill or drillSummary
    onAgain / onAnother () => void — The buttons, shown when given
    actions ReactNode — Buttons of your own in place of the usual row
    names TheoryNamesOverrides — The names the questions missed are listed by, each with its notes
    sx, labels, colors MUI sx; Partial<DrillScoreCardLabels>; Partial<DrillColors>

    drillPraise(accuracy, labels) and drillTone(accuracy, colors) give the word and the colour a score earns.

    Prop Type Default Description
    musicxml string required A question's musicxml
    loadEngine NotationEngineLoader required Supplies the notation engine
    height number 176 Height of the box the staff is centred in, in pixels
    zoom number 1.1 Size of the staff relative to the engine's own
    highlight readonly number[] — Notes to light, by their place in the question's notes: lowest first in a chord or harmonic interval, left to right otherwise
    highlightColor string 'primary.main' Colour of a lit note: MUI palette path or CSS colour
    noteStyles ReadonlyMap<string, MusicXmlNoteStyle> — Note styles as MusicXmlPlayer takes them — a colour, and the note played in a wrong one's place drawn beside it — drawn by @hiyve/react-music-notation's own note styling
    onError (error: Error) => void — The engine or the score failed to load
    onStatusChange (status: StaffStatus) => void — Told as the staff is drawn: 'drawing', then 'drawn', or 'failed' when it cannot be. A failed staff says so in its box, with a button to try again
    labels Partial<StaffSnippetLabels> — unavailable and retry
    Prop Type Default Description
    choices readonly string[] required As the question gives them
    chosen string | null null The choice made
    answer string | null null The right answer, once the round is answered
    onChoose (choice: string) => void required A choice was made
    disabled boolean false Nothing can be chosen
    formatChoice (choice: string) => string — How a choice is shown on its button
    sx, labels, colors MUI sx; Partial<AnswerChoicesLabels>; right and wrong

    By default the notes play on a small built-in voice that needs no download. To hear a real piano, give DrillCard (or useNoteSynth) a sample host laid out like the midi-js-soundfonts collection:

    const PIANO = { baseUrl: 'https://cdn.example.com/soundfonts/FluidR3_GM/' };
    <DrillCard samples={PIANO} … />

    The SDK ships no samples: you choose the host, and that set's licence applies — FluidR3_GM, for one, is Creative Commons Attribution 3.0 and asks to be credited, while the University of Iowa Electronic Music Studios piano recordings may be used for any project without restrictions, once trimmed, encoded and hosted in this layout yourself. Each note is about 25 KB and is downloaded once, while its question is on screen. A note that cannot be fetched in time plays on the built-in voice, so a note always sounds. The layout and the SampleSource fields are described in the @hiyve/music-theory README.

    Answers are codes (M3, halfDiminished7, dorian, D:major); the components show them by name. defaultTheoryNames holds the English names — intervals, chords and scales by code, and keyMajor / keyMinor templates with {tonic} — and names overrides any of them, name by name:

    <DrillCard names={{ intervals: { P8: 'Perfect 8th' }, keyMinor: '{tonic}m' }} … />
    

    Notes are named by their letters ("F♯"). names.note names them another way, such as solfège, wherever a note is named: in the answers, the key names, the note to start from, and the questions missed:

    const SOLFEGE: Record<string, string> = { C: 'Do', D: 'Re', E: 'Mi', F: 'Fa', G: 'Sol', A: 'La', B: 'Si' };
    <DrillCard names={{ note: (name) => SOLFEGE[name[0]] + name.slice(1) }} … />

    For views of your own:

    • answerLabel(kind, answer, names) gives the name of any answer.
    • noteLabel(pitch, names, octave?) gives a note's name.
    • questionNotesText(question, names?) gives the notes behind a question ("C4 E4", "D F A").
    • mergeTheoryNames(overrides?) fills in the rest.

    Every component takes labels. The drill components also take colors, as MUI palette paths or CSS colours:

    • right, wrong, good, fair, poor
    • playing: a note on the staff while it sounds
    • staff: behind the staff, which is drawn in black (default white)

    The text on a right or wrong answer is picked to read on its colour. The defaults are exported (defaultDrillCardLabels, defaultTheoryDrillPanelLabels, defaultDrillColors, …) with merge functions (mergeDrillCardLabels, mergeTheoryDrillPanelLabels, mergeTheoryDrillGuideLabels, mergeDrillColors, …). withClef(settings, clef), withByEar(settings, byEar), withPlayAnswer(settings, playAnswer), noteIdRangeOf(settings) and withNoteIdRange(settings, clef, range) are the helpers behind the panel's clef, by-ear, play and range choices.

    • React 18 or 19, and MUI 9.
    • A notation engine, supplied through loadEngine, as for @hiyve/react-music-notation's player. The public opensheetmusicdisplay build is enough: the drills only draw, and play their notes with their own voice.
    • Sound needs Web Audio; browsers start it only after a user gesture — choosing an answer is one, and so is the click that starts a drill, for a question asked by ear.
    • With samples, the sample host must allow your page to fetch from it (CORS) and be allowed by your connect-src.

    Components

    AnswerChoices
    DrillCard
    DrillScoreCard
    StaffSnippet
    TheoryDrillGuide
    TheoryDrillPanel

    Other

    AnswerChoicesLabels
    AnswerChoicesProps
    DrillCardLabels
    DrillCardProps
    DrillColors
    DrillScoreCardLabels
    DrillScoreCardProps
    StaffSnippetLabels
    StaffSnippetProps
    TheoryDrillGuideLabels
    TheoryDrillGuideProps
    TheoryDrillPanelLabels
    TheoryDrillPanelProps
    TheoryNames
    TheoryNamesOverrides
    UseDrillOptions
    UseDrillResult
    UseNoteSynthOptions
    UseNoteSynthResult
    NoteIdRange
    StaffStatus
    defaultAnswerChoicesLabels
    defaultDrillCardLabels
    defaultDrillColors
    defaultDrillScoreCardLabels
    defaultStaffSnippetLabels
    defaultTheoryDrillGuideLabels
    defaultTheoryDrillPanelLabels
    defaultTheoryNames
    answerLabel
    drillHint
    drillPraise
    drillTone
    mergeAnswerChoicesLabels
    mergeDrillCardLabels
    mergeDrillColors
    mergeDrillScoreCardLabels
    mergeStaffSnippetLabels
    mergeTheoryDrillGuideLabels
    mergeTheoryDrillPanelLabels
    mergeTheoryNames
    noteIdRangeOf
    noteLabel
    questionNotesText
    useDrill
    useNoteSynth
    withByEar
    withClef
    withNoteIdRange
    withPlayAnswer