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

    Module @hiyve/react-music-performance

    @hiyve/react-music-performance

    React hooks and components for practising from a score, on @hiyve/music-performance: record a take, follow the performer on the sheet music as they play, grade the take, keep it, and write sight-reading exercises.

    React hooks and a component for recording a take — unprocessed microphone audio plus MIDI from a connected instrument, on one clock — so a performance can be saved and analysed against a score later.

    import { TakeRecorder } from '@hiyve/react-music-performance';

    function Practice() {
    return (
    <TakeRecorder
    score={{ name: 'Minuet in G' }}
    onTakeCompleted={(bundle) => save(bundle)}
    onError={(error) => console.error(error)}
    />
    );
    }

    Press Record, play, press Stop. The finished take arrives in onTakeCompleted as a TakeBundle — a JSON record plus a WAV blob — and the component offers both as downloads.

    import { useTakeRecorder } from '@hiyve/react-music-performance';

    function CustomRecorder() {
    const recorder = useTakeRecorder({ profile: 'instrument', onTakeCompleted: save });

    return (
    <button onClick={recorder.isRecording ? recorder.stop : recorder.start}>
    {recorder.isRecording ? 'Stop' : 'Record'}
    </button>
    );
    }
    import { gradeTake } from '@hiyve/music-performance';
    import { TakeRecorder, TakeReport, useScore } from '@hiyve/react-music-performance';

    function Practice() {
    const { score, takeScore, loadFile, error } = useScore();
    const [report, setReport] = useState(null);

    return (
    <>
    <input type="file" accept=".musicxml,.xml" onChange={(e) => e.target.files?.[0] && void loadFile(e.target.files[0])} />
    {error && <p>{error.message}</p>}
    <TakeRecorder
    score={takeScore}
    onTakeCompleted={(bundle) => score && setReport(gradeTake(score, bundle.take))}
    />
    {report && <TakeReport report={report} />}
    </>
    );
    }

    useScore reads a .musicxml file (compressed .mxl is not supported yet), gradeTake compares the take's MIDI with the score, and TakeReport shows the result: accuracy, counts of correct / wrong / missing / extra notes, the tempo the performer held against the score's, and a per-note table with early/late timing.

    import { MusicXmlPlayer } from '@hiyve/react-music-notation';
    import { TakeRecorder, TakeReport, liveNoteStyles, useLivePerformance, useScore } from '@hiyve/react-music-performance';

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

    function Practice({ scoreUrl }: { scoreUrl: string }) {
    const { score, takeScore } = useScore();
    const live = useLivePerformance({ score, source: 'audio' }); // 'midi' for a MIDI instrument

    return (
    <>
    <TakeRecorder
    score={takeScore}
    onRecordingStarted={live.reset}
    onAudioChunk={live.pushAudio}
    onMidiEvent={live.pushMidi}
    onTakeCompleted={() => live.finish()}
    />
    <MusicXmlPlayer
    variant="inline"
    fileUrl={scoreUrl}
    loadEngine={loadEngine}
    noteStyles={liveNoteStyles(live.snapshot)}
    position={live.position}
    />
    {live.report && <TakeReport report={live.report} />}
    </>
    );
    }

    useLivePerformance runs a LiveSession (from @hiyve/music-performance) on the audio — or the MIDI — as it is captured. Playing freely, every note is compared with the next one written and the session moves on, right or wrong; each expected note gets a verdict in snapshot.verdicts, keyed by score-note id: a quick provisional one about 150 ms after the attack, then a confirmed one about 300 ms after. snapshot.cursor and snapshot.next say where the performer is, snapshot.tempoBpm what tempo they are holding, snapshot.extras what was heard that the score does not explain. finish() grades the whole session as a PerformanceReport for TakeReport.

    By default the session follows the performer's own tempo. To judge against a set one — a metronome's — add useMetronome and tell the live hook where the first beat falls and what the click sounds like, so a click the microphone hears through speakers is found during the count-in and taken out of the audio before it is read (snapshot.clickOffsetMs says how late the clicks arrive, and the beat is judged where they were heard; through headphones none is found and nothing is touched):

    const metronome = useMetronome({ bpm: 100, beatsPerBar: 4, countInBars: 1 });
    const live = useLivePerformance({ score });

    <TakeRecorder
    onRecordingStarted={({ nowMs }) => {
    live.reset();
    const started = metronome.start(); // a one-bar count-in, then the piece
    if (started) {
    live.setTempo({
    bpm: 100,
    startTakeMs: nowMs() + (started.firstBeatPerformanceMs - performance.now()),
    nowMs,
    click: { ...started.click, countInBeats: started.countInBeats },
    });
    }
    }}
    onAudioChunk={live.pushAudio}
    onTakeCompleted={() => {
    metronome.stop();
    live.finish();
    }}
    />

    Timing is then early or late against the beat, a note whose beat passes unplayed turns orange at once (and green if it arrives late), and the position line glides at the beat. How closely a note must sit on its beat is the strictness — 'relaxed', 'normal' (the default) or 'strict', windows of 150, 80 and 40 ms (timingStrictness): inside the window a note is simply on time; outside it the score shows where it was played, and the longer window also means a beat has to pass by more before an unplayed note is called missing. Without a set tempo timing is not judged at all — a note is right or wrong by its pitch. The clicks are noise rather than tones, so a microphone that hears them does not take them for notes — but headphones keep them out of the recording altogether.

    On the score itself, liveNoteStyles(snapshot, options?) turns the verdicts into the noteStyles the MusicXmlPlayer from @hiyve/react-music-notation colours its note heads by — green, red and orange by default (the TakeReport colours), fainter while a verdict is still provisional — and, under a set tempo, gives every note played outside the strictness window (strictness, or an explicit markTimingAfterMs) a mark where it was actually played, beside the engraved head. live.position is the player's position line: on the note expected next while following the performer, and under a set tempo a line gliding at the beat, which the player animates between renders (livePositionQuarters(snapshot) gives the plain note position for other views). The same score file must be given to both: useScore for the engine that follows, and fileUrl for the engine that draws.

    usePracticeSession is recording, following and grading in one. Give it the score and how the take is paced, hand its styles and position to the score player, put the toolbar controls beside the player's own, and the take ends when the piece does — graded as surely as it can be: MIDI, then the audio, then real time.

    import { useRef, useState } from 'react';
    import { MusicXmlPlayer } from '@hiyve/react-music-notation';
    import type { MusicXmlPlayerHandle } from '@hiyve/react-music-notation';
    import {
    LiveStrip,
    PacingSwitch,
    PracticeSettingsButton,
    RecordButton,
    TakeScoreCard,
    defaultPracticeSettings,
    practiceRecorderOptions,
    takeResultView,
    useAudioInputs,
    useMidiInputs,
    usePracticeSession,
    useScore,
    useScoreUrl,
    useTakeHistory,
    } from '@hiyve/react-music-performance';
    import type { PacingMode } from '@hiyve/react-music-performance';

    function PracticePage({ loadEngine }: { loadEngine: NotationEngineLoader }) {
    const { score, name, musicxml } = useScore();
    const url = useScoreUrl(musicxml);
    const playerRef = useRef<MusicXmlPlayerHandle>(null);
    const [mode, setMode] = useState<PacingMode>('free');
    const [settings, setSettings] = useState(defaultPracticeSettings);
    const history = useTakeHistory();
    const midiInputs = useMidiInputs();
    const audioInputs = useAudioInputs();
    const session = usePracticeSession({
    score,
    musicxml,
    title: name,
    mode,
    bpm: 100,
    strictness: settings.strictness,
    instrument: settings.instrument,
    liveFromMidi: settings.liveFromMidi,
    ...practiceRecorderOptions(settings),
    playerRef,
    onTakeGraded: history.save,
    });
    return (
    <>
    {url && (
    <MusicXmlPlayer
    ref={playerRef}
    fileUrl={url}
    loadEngine={loadEngine}
    noteStyles={session.noteStyles}
    position={session.position}
    transportActions={<RecordButton recorder={session.recorder} />}
    toolbarActions={
    <>
    <PacingSwitch mode={mode} onChange={setMode} disabled={session.recorder.isRecording} />
    <PracticeSettingsButton
    settings={settings}
    onChange={(next) => setSettings((current) => ({ ...current, ...next }))}
    recorder={session.recorder}
    midiInputs={midiInputs}
    audioInputs={audioInputs}
    paced={mode !== 'free'}
    />
    </>
    }
    onPlaybackEnded={session.handlePlaybackEnded}
    />
    )}
    {session.live.snapshot && <LiveStrip snapshot={session.live.snapshot} expectClicks={mode === 'metronome'} note={session.takeNote} />}
    {session.latest && <TakeScoreCard view={takeResultView(session.latest)} saved />}
    </>
    );
    }

    The instrument chosen in the settings decides how the microphone is listened to (instrumentProfiles: keyboard, sustained, voice). A take ends on its own at ten minutes, and a free take that has heard nothing for two minutes ends too; maxTakeMs and idleEndMs change that.

    SightReadingPanel is the settings of an exercise as a form; the page writes the tune with generateSightReading and loads it like any score. Every exercise has a code — its settings and seed in sixteen characters — shown in the panel and, with ExerciseCodeChip, beside the score. A take of the exercise keeps the code when the session is told which exercise is on the card, so a saved take can be tried again from its code later.

    const [settings, setSettings] = useState(defaultSightReadingSettings);
    const exercise = useMemo(() => generateSightReading({ ...settings, tempoBpm: 80 }), [settings]);
    useEffect(() => {
    loadText(exercise.musicxml, exercise.title);
    }, [exercise, loadText]);
    const session = usePracticeSession({ …, exercise: { code: exercise.code, musicxml: exercise.musicxml } });

    <SightReadingPanel settings={settings} onChange={setSettings} locked={session.recorder.isRecording} />
    <ExerciseCodeChip code={exercise.code} />

    To try a saved take again, parseSightReadingCode(row.exercise) gives the settings back; a take saved before exercises had codes can still be found from its title and stored tune with recoverSightReadingSettings.

    useTakeHistory keeps graded takes in this browser — the numbers in a list, the marked score alongside. TakeScoreCard shows a take's score, MarkedScore its score marked note by note, and SavedTakesTable the takes kept, each with Try again.

    const history = useTakeHistory();
    const [picked, setPicked] = useState<{ row: SavedTakeResult; stored?: StoredTakeScore } | null>(null);
    const view = picked ? savedTakeResultView(picked.row, picked.stored) : session.latest ? takeResultView(session.latest) : null;

    {view && <TakeScoreCard view={view} saved={picked !== null} onTryAgain={() => tryAgain(view)} />}
    {view?.musicxml && <MarkedScore view={view} loadEngine={loadEngine} />}
    <SavedTakesTable
    rows={history.results}
    selectedId={picked?.row.id ?? null}
    onOpen={(row) => void history.getStoredScore(row.id).then((stored) => setPicked({ row, stored }))}
    onTryAgain={(target) => tryAgain(target)}
    onClear={history.clear}
    />

    Saving takes to a room's file storage — the /room entry

    import { TakeRecorder } from '@hiyve/react-music-performance';
    import { useSaveTake } from '@hiyve/react-music-performance/room';

    function Practice() {
    const { saveTake, isSaving, lastSaved } = useSaveTake({ location: '/Takes' });

    return (
    <>
    <TakeRecorder onTakeCompleted={(bundle) => void saveTake(bundle)} />
    {isSaving && <p>Saving…</p>}
    {lastSaved && <p>Saved as {lastSaved.takeFileId}</p>}
    </>
    );
    }

    useSaveTake uploads the WAV and the JSON record to the performer's file storage and links the two through their metadata, so either file leads to the other. It needs <HiyveProvider> with a signed-in user; no room connection is required. It is the one part of the package that needs @hiyve/react, and lives behind the /room entry so everything else works without it.

    pnpm --filter @hiyve/react-music-performance playground
    

    Opens a page with the recorder wired to your real MIDI keyboard and microphone, and the score drawn by @hiyve/react-music-notation's player. With the public opensheetmusicdisplay build the player renders and steps; to have its playback, tempo slider and per-instrument mixer as well — and to pace a take with the score's own playback — drop the engine build with audio into packages/react-music-performance/dev/public/vendor/opensheetmusicdisplay.min.js. That folder is git-ignored: the build is licensed to the app, not the SDK, and is never committed. The playground says which engine it found.

    • Picks a MIDI input (one, all, or none), a microphone, and a capture profile
    • Records audio unprocessed by default — no echo cancellation, noise suppression, or automatic gain
    • Shows which processing flags the browser actually honoured, and warns when it kept some on
    • Live level meter, elapsed time, and MIDI event counter
    • Measures the take's own MIDI-to-audio offset from the notes played and stores it as take.alignment (shown after each take)
    • Hands audio chunks and MIDI events out as they arrive, so useLivePerformance can follow the score while recording
    • Downloads the take as WAV and JSON
    • Releases the microphone and MIDI ports if unmounted mid-take
    • usePracticeSession: a take that ends when the piece does, graded as surely as it can be, with the controls, the live readout and the score card to go with it
    • Sight-reading exercises with a code that makes the same tune again anywhere
    • Takes kept in the browser with their marked scores, and tried again from the list
    Component Description
    TakeRecorder Device pickers, record/stop, level meter, processing-flag chips, and download buttons
    TakeReport A graded take: summary chips and a per-note table of verdicts and timing
    RecordButton Record and stop, with the time and the microphone level while a take runs; for a score player's transportActions
    PacingSwitch Free, metronome or the score's own playback; for a score player's toolbarActions
    PracticeSettingsButton A gear opening the instrument, microphone, MIDI input, capture profile, what is followed and the timing strictness
    LiveStrip What the session makes of a take as it goes: counts, where it is, whether clicks are being taken out, details a click away
    TakeScoreCard A take's score: the ring, a word on it, the numbers as pills, and Save / Try again / Back
    ScoreRing A ring filled to a score, with the percentage inside
    ExerciseCodeChip A sight-reading exercise's code; a click copies it
    MarkedScore The score as it was played, every note coloured by the final grade, with a switch for the timing marks
    SavedTakesTable The takes kept, newest first, each with Try again
    SightReadingPanel The settings of a sight-reading exercise, with its code
    Hook Description
    useTakeRecorder(options) The capture flow: start(), stop(), status, elapsed time, MIDI count, level, applied settings, and the finished bundle
    useMidiInputs() Connected MIDI inputs, refreshed when devices change
    useAudioInputs() Connected microphones, refreshed when devices change
    useSaveTake(options) — from /room Store takes in the performer's file storage: saveTake(bundle), isSaving, lastSaved, error
    useScore(options) Load a MusicXML file or text: score, name, takeScore (for TakeRecorder), loadFile, loadText, error
    useLivePerformance(options) Follow the score while recording: pushAudio / pushMidi (wire to the recorder), snapshot with a verdict per note and the cursor, setTempo() to judge against a set tempo, finish() → report
    useMetronome(options) A click track with a count-in: start() (returns when the first beat falls), stop(), beat, bar
    usePracticeSession(options) Recording, following and grading in one: recorder, live, metronome, latest (the graded take), takeNote, noteStyles and position for the score player, handlePlaybackEnded, clearLatest
    useTakeHistory(options) The takes kept in this browser: results, save(take), update(id, patch), clear(), getStoredScore(id)
    useScoreUrl(musicxml) MusicXML text as a URL the score player can fetch, released a while after it changes
    Prop Type Default Description
    report PerformanceReport required From gradeTake or matchPerformance
    showTable boolean true Show the per-note table under the summary
    onTimeThresholdMs number 30 Deviations under this read as "on time"
    sx SxProps<Theme> — Root styling
    labels Partial<TakeReportLabels> — Label overrides (every string, plus formatNote)
    colors Partial<TakeReportColors> — Verdict colours: correct, wrong, missing, extra
    Function Description
    downloadTakeAudio(bundle) Save the WAV through the browser's download flow; returns false if the take has no audio
    downloadTakeJson(bundle) Save the JSON record
    toTakeFiles(bundle) The take as File objects (<id>.wav, <id>.take.json) for your own upload path
    liveNoteStyles(snapshot, options?) Note-head styles for the score viewer (MusicXmlPlayer's noteStyles), one per judged note, coloured by verdict, fainter while provisional, and under a set tempo with a mark where a note outside the strictness window was played; a wrong note shows the note that was played in its place; options: colors, strictness ('normal'), markTimingAfterMs, playedNotes (true)
    timingStrictness / strictnessLevel(name?) What each strictness level allows: { toleranceMs, missAfterMs } for relaxed (150 / 600), normal (80 / 400) and strict (40 / 250)
    reportNoteStyles(report, options?) The same styles for a finished grade — gradeTake, gradeTakeFromAudio or gradeTakeFinal's report — to show the score with its markings once the take is in; marks timing only given bpm, the set tempo the take was played to; a wrong note shows the note that was played in its place unless playedNotes is false
    playedPitchOf(playedMidi, expected) How the note played in a wrong note's place is written beside it: its own letter, or the expected note sharpened when above and flattened when below, with a natural sign when it is the expected note's letter with the accidental off
    livePositionQuarters(snapshot) Where the score viewer's position mark belongs: the note expected next, or the beat under a set tempo
    noteKeyOf(note) The score viewer's key for a score note
    gradeFinishedTake(options) Grade a finished take as surely as it can be — MIDI, then audio, then real time — timed against the beat as it was heard under a set tempo: { final, report, scored, markings }
    takeResultView(take) / savedTakeResultView(row, stored?) What TakeScoreCard and MarkedScore show of the latest take, or of one kept
    savedTakeResultFrom(take) A graded take as it is kept between visits, or null when there was nothing to grade from
    takeGradeOf(take) A take's grade as it goes into its record when saved with useSaveTake
    withoutTimingMarks(markings) The same markings without the place each note was played
    practiceRecorderOptions(settings) The recorder options a PracticeSettings stands for, for usePracticeSession
    tryAgainTargetOf(row) A saved take as something to have another go at
    instrumentProfiles / instrumentFamilies How each family of instrument is listened to, and the families in order
    defaultSightReadingSettings() / newSightReadingSeed() The settings to start with, and a fresh seed
    recoverSightReadingSettings(title, musicxml) The settings of a sight-reading take saved before exercises had codes, or null
    sightReadingClefBase / sightReadingRanges / sightReadingRhythmsByLevel The bottom of each clef's range, the ranges offered, and the rhythms each level allows
    scoreTone(value, colors) / scorePraise(value, labels) The colour and the word a score earns
    formatWhenDefault(date) / formatElapsedShort(ms) "Today 6:17 pm" and m:ss
    Option Type Default Description
    score Score | null required The score being practised; nothing records until one is set
    musicxml string | null required The score's MusicXML, kept with each take
    title string | null required The piece's name, kept with each take
    mode PacingMode required 'free', 'metronome' or 'playback'
    bpm number required For the metronome and the clock the take is judged against
    strictness LiveStrictness 'normal' How closely a note must sit on its beat under a set tempo
    instrument InstrumentFamily 'keyboard' How the microphone is listened to
    liveFromMidi boolean false Follow MIDI instead of the microphone while recording
    profile, captureMidi, midiInputId, audioDeviceId as useTakeRecorder — How the take is recorded; practiceRecorderOptions(settings) gives them from a PracticeSettings
    playerRef RefObject<MusicXmlPlayerHandle | null> — The score player, for playback pacing: its clock anchors the take and Play is pressed for the performer
    exercise { code, musicxml } | null — The sight-reading exercise on the card, so a take of it keeps the code
    countInBars number 1 Bars of clicks before the first beat
    maxTakeMs number 10 minutes A take ends on its own at this length
    idleEndMs number 2 minutes A free take that has heard nothing for this long ends on its own
    endGraceMs number 1000 How long after the piece ends the take keeps recording, so the last note rings out
    onTakeGraded (take: TakeResult) => void — Every finished take, graded
    onError (error: Error) => void — Microphone, MIDI, audio and grading failures
    labels Partial<PracticeSessionLabels> — The status lines: count-in, anchored, piece ended, track ended, idle ended, limit ended
    Field Type Description
    recorder UseTakeRecorderResult The recorder behind the session, for RecordButton
    live UseLivePerformanceResult The live session, for LiveStrip
    metronome UseMetronomeResult The metronome, for a beat display
    latest TakeResult | null The most recent finished take, graded
    takeNote string | null What the take is doing: counting in, anchoring, ending
    noteStyles ReadonlyMap<string, MusicXmlNoteStyle> Live verdicts while a take runs, the final grade's markings once it is in — for the score player
    position MusicXmlPlayerPosition | null Where the score player's position line belongs, or null while the score's own playback leads
    beatsPerBar number Beats in a bar of the score
    handlePlaybackEnded () => void Wire to the score player's onPlaybackEnded
    clearLatest () => void Forget the latest take
    Option Type Default Description
    key string 'hiyve-music-performance-takes' Where the list is kept in this browser; an app with several lists gives each its own
    limit number 100 How many takes are kept, newest first
    scoreStore string 'hiyve-music-performance' Where marked scores are kept in this browser

    Returns results (newest first), save(take), update(id, patch), clear() and getStoredScore(id). Nothing leaves the browser.

    Prop Type Default Description
    view TakeResultView required From takeResultView or savedTakeResultView
    saved boolean false The take is kept in history
    onSave () => void — Keep the take; the button shows when given
    onTryAgain () => void — Have another go at the same piece; the button shows when given
    onBackToLatest () => void — Shown for a take opened from history while there is a latest take to go back to
    actions ReactNode — Buttons of your own in place of the usual row
    compact boolean false A smaller card, for a popup
    sx SxProps<Theme> — Root styling
    labels Partial<TakeScoreCardLabels> — Every string, the praise words included, plus formatWhen
    colors Partial<TakeScoreCardColors> — pitch, timing, right, wrong, missing, good, fair, poor

    ScoreRing takes value (0–1 or null), size (150), label ("score") and the good / fair / poor colours; ExerciseCodeChip takes code, sx and labels (copy, copied).

    Prop Type Default Description
    view TakeResultView required The take shown; nothing is drawn without its music, markings and source
    loadEngine NotationEngineLoader required Loads the notation engine for the score viewer
    showTimingMarks boolean true Draw a mark where each early or late note was played; given, the switch follows it
    onShowTimingMarksChange (show: boolean) => void — The switch was flipped
    height number | string most of the window Height of the score viewer
    playerColors Partial<MusicXmlPlayerColors> — Colours for the score viewer
    sx SxProps<Theme> — Root styling
    labels Partial<MarkedScoreLabels> — Title, legend, switch, captions
    colors Partial<Pick<TakeScoreCardColors, 'right' | 'wrong' | 'missing'>> — Legend colours
    Prop Type Default Description
    rows SavedTakeResult[] required The takes kept, as useTakeHistory gives them
    selectedId string | null null The take shown at the moment, highlighted
    onOpen (row: SavedTakeResult) => void required A row was chosen
    onTryAgain (target: TryAgainTarget) => void — Have another go at a take; the buttons show when given
    onClear () => void — Forget every take; the button shows when given
    sx SxProps<Theme> — Root styling
    labels Partial<SavedTakesTableLabels> — Title, columns, names, formatWhen
    colors Partial<Pick<TakeScoreCardColors, 'good' | 'fair' | 'poor'>> — Score chip colours
    Prop Type Default Description
    settings SightReadingSettings required The exercise's settings: exactly what its code carries
    onChange (next: SightReadingSettings) => void required The settings changed — or a code was pasted in
    locked boolean false A take is running: the settings hold still
    measureChoices number[] [4, 8, 12, 16] Bar counts offered
    sx SxProps<Theme> — Root styling
    labels Partial<SightReadingPanelLabels> — Every string, the level names and chord rules included
    Prop Type Default Description
    snapshot LiveSnapshot required From usePracticeSession's live or useLivePerformance
    expectClicks boolean false A metronome is sounding, so clicks may reach the microphone
    note string | null — What the take is doing: counting in, anchoring, ending
    sx SxProps<Theme> — Root styling
    labels Partial<LiveStripLabels> — Counts, states, chips, formatNote
    colors Partial<Pick<TakeScoreCardColors, 'right' | 'wrong' | 'missing'>> — Counter colours
    Component Props
    PacingSwitch mode, onChange(mode), disabled (false), playbackAvailable (true), sx, labels
    RecordButton recorder (from usePracticeSession or useTakeRecorder), sx, labels (record, stop, cannotRecord, level, formatElapsed), colors (recording)
    PracticeSettingsButton settings (PracticeSettings), onChange(partial), recorder, midiInputs, audioInputs, paced (false), enableDownload (true), instruments (profile overrides), sx, labels

    A PracticeSettings holds profile, midiSelection ('all', 'none' or an input id), audioSelection ('default' or a device id), liveFromMidi, strictness and instrument; defaultPracticeSettings is the place to start and practiceRecorderOptions turns one into recorder options.

    useSaveTake Options (/room entry)

    Option Type Default Description
    location string '/Takes' Storage folder
    audioResourceType string 'audio' Resource type for the WAV
    takeResourceType string 'data' Resource type for the JSON record
    userId string signed-in user Upload as this user when the file list has not been loaded yet
    retry SaveRetryOptions 3 retries on network errors Retry behaviour
    onSaved (saved: SavedTake) => void — Both files stored and linked
    onError (error: Error) => void — Save failed after retries

    The stored JSON record carries metadata (takeId, audioFileId, scoreName, durationMs, noteOnCount, midiToAudioOffsetMs, score, gradeSource) and the WAV carries takeId and takeFileId. saveTake(bundle, { grade: takeGradeOf(take) }) writes the take's grade into the record itself, so the file says how the take went.

    Prop Type Default Description
    score TakeScoreReference — The score the take is performed against, stored with the take
    defaultProfile 'voice' | 'music' | 'instrument' 'instrument' Initial capture profile
    defaultMidiInputId string all inputs Initially selected MIDI input
    defaultAudioDeviceId string browser default Initially selected microphone
    notes string — Free-text notes stored with the take
    showMidiSelector boolean true Show the MIDI picker
    showAudioSelector boolean true Show the microphone picker
    showProfileSelector boolean true Show the profile picker
    enableDownload boolean true Offer download buttons after a take
    showAlignment boolean true Show the measured MIDI-to-audio offset after a take
    onRecordingStarted (take: { nowMs: () => number }) => void — Capture is running; nowMs reads the take's clock
    onAudioChunk (chunk: AudioChunk) => void — Every captured audio chunk as it arrives, for useLivePerformance
    onMidiEvent (event: MidiEvent) => void — Every MIDI event as it arrives, for useLivePerformance
    onTakeCompleted (bundle: TakeBundle) => void — The finished take
    onError (error: Error) => void — Microphone, MIDI, and audio-context failures
    sx SxProps<Theme> — Root styling
    labels Partial<TakeRecorderLabels> — Label overrides
    colors Partial<TakeRecorderColors> — Color overrides
    icons Partial<TakeRecorderIcons> — Icon overrides
    Option Type Default Description
    profile 'voice' | 'music' | 'instrument' 'instrument' How the microphone is opened
    captureMidi boolean true Record MIDI when the browser supports it
    midiInputId string all inputs Record from one input only
    audioDeviceId string browser default Microphone to use
    score TakeScoreReference — Stored with the take
    notes string — Stored with the take
    alignTake boolean true Measure the MIDI-to-audio offset from the take's notes and store it as take.alignment
    maxDurationMs number no limit Stop the take on its own at this length. A take holds every sample until it ends — about 11 MB a minute per channel at 48 kHz, and as much again to build the take — so one left running fills memory until the browser refuses it
    onRecordingStarted (take: { nowMs: () => number }) => void — Capture is running; nowMs reads the take's clock
    onAudioChunk (chunk: AudioChunk) => void — Every captured audio chunk as it arrives: channels, sampleRate, frame, timeMs
    onMidiEvent (event: MidiEvent) => void — Every MIDI event as it arrives
    onTakeCompleted (bundle: TakeBundle) => void — The finished take
    onError (error: Error) => void — Failures
    Property Type Description
    status 'idle' | 'starting' | 'recording' | 'stopping' Lifecycle
    isRecording boolean status === 'recording'
    start () => Promise<void> Start capture — call from a click so audio is allowed to run
    stop () => Promise<TakeBundle | null> Stop and build the take
    elapsedMs number Time since recording started
    nowMs () => number The take's clock, exact; 0 when not recording
    midiEventCount number Events recorded so far
    level number Microphone level 0–1
    settings AudioCaptureSettings | null What the browser applied
    comparison CaptureSettingsComparison | null Which flags were honoured
    bundle TakeBundle | null The most recent take
    error Error | null The most recent failure
    isMidiSupported / isAudioSupported boolean Browser capability
    Option Type Default Description
    score Score | null — The score to follow; nothing happens until one is set
    source 'audio' | 'midi' 'audio' Which input to listen to; the other is ignored so nothing is counted twice
    fromQuarters / toQuarters number — Follow only a region of the score
    session Pick<LiveSessionOptions, 'analysis' | 'provisional' | 'follower' | 'chordWindowMs'> core defaults Analysis, quick-look, follower and chord-window settings
    strictSequence boolean true Without a set tempo, compare every note with the next one written and move on, right or wrong — nothing is held for the next note, and a rolled chord is one chord. Under a set tempo the follower always searches, so a performer who stops is picked up at the beat they rejoin
    onUpdate (update: LiveUpdate) => void — Every change, with the attack concerned and a full snapshot
    onError (error: Error) => void — A listener threw, or audio could not be consumed
    Property Type Description
    snapshot LiveSnapshot | null Cursor, next event, verdicts by note id, extras, attacks, tempo; null before the first input
    isActive boolean Input has arrived and finish() has not been called
    report PerformanceReport | null The graded session, once finished
    tempo LiveTempoSetting | null The set tempo the next session is judged against
    position MusicXmlPlayerPosition | null For MusicXmlPlayer's position: the note expected next, or under a set tempo a line gliding at the beat
    setTempo (tempo: { bpm, startTakeMs, atQuarters?, heardLateMs?, strictness?, missAfterMs?, nowMs?, click? } | null) => void Judge the session against a set tempo, or follow the performer again. startTakeMs is the take-clock time of a known score position — the first beat, or atQuarters quarter notes in, for a backing track's clock read once it runs. Applies to the next session, and to the current one while it has placed nothing yet. heardLateMs shifts the clock by how much later than scheduled the beat is heard — the output's latency, which useMetronome's start() reports — so playing to what you hear is on time; strictness sets how long a beat may pass unplayed; with nowMs (the recorder's clock) the beat keeps moving between notes; with click (the metronome's, from start()) clicks the microphone hears are taken out of the audio
    pushAudio (chunk: AudioChunk) => void Wire to onAudioChunk
    pushMidi (event: MidiEvent) => void Wire to onMidiEvent
    finish () => PerformanceReport | null End and grade the session; the next input starts a new one
    reset () => void Discard the session and its results
    estimateQuartersAt (timeMs: number) => number | null Where the running tempo places a stream time, for a smooth cursor

    A session starts with the first input after a score is set and after each finish() or reset(), so one hook serves take after take; changing the score, region or source discards the current one.

    Option Type Default Description
    bpm number — Quarter notes per minute
    beatsPerBar number 4 Beats in a bar; the first is accented
    countInBars number 1 Bars of clicks before the first beat of the piece
    accentFirstBeat boolean true Accent the first beat of each bar
    volume number 0.5 Click level, 0–1
    onError (error: Error) => void — Audio could not be started
    Property Type Description
    isRunning boolean Clicks are sounding
    beat / bar number | null The beat within the bar (1-based) and the bar as each click sounds; bars are negative during the count-in
    start () => MetronomeStart | null Start the clicks (from a user gesture); returns when the first count-in click and the first beat of the piece fall on the performance.now() clock, how many clicks the count-in has, the click itself and the output's latency for setTempo, or null without Web Audio
    stop () => void Silence the clicks
    isSupported boolean Whether this browser has Web Audio

    Every visible string is a label; pass a partial object to override any of them.

    <TakeRecorder
    labels={{ record: 'Start', stop: 'Finish', midiEventCount: '{count} notes' }}
    colors={{ recording: '#c62828', levelMeter: '#2e7d32' }}
    icons={{ record: <MyIcon /> }}
    />
    Defaults Merge function
    defaultTakeRecorderLabels mergeTakeRecorderLabels
    defaultTakeRecorderColors mergeTakeRecorderColors
    defaultTakeRecorderIcons mergeTakeRecorderIcons
    defaultTakeReportLabels mergeTakeReportLabels
    defaultTakeReportColors mergeTakeReportColors
    • React 18 or 19
    • @mui/material and @mui/icons-material v9 with Emotion
    • @hiyve/music-performance 0.1 or later and @hiyve/react-music-notation 0.2 or later (with a notation engine of your own); @hiyve/utilities and @hiyve/rtc-client
    • A browser with getUserMedia and AudioWorklet; Web MIDI for MIDI capture (not available in Safari)
    • TakeRecorder, useTakeRecorder, useMidiInputs and useAudioInputs work without HiyveProvider; the component opens its own microphone track, so use it when no call is in progress.
    • useSaveTake, from the /room entry, must be inside <HiyveProvider> with a signed-in user. @hiyve/react is needed for that entry only.
    • MarkedScore draws the score with @hiyve/react-music-notation's player, so it needs a notation engine through loadEngine, as the player does.

    Modules

    @hiyve/react-music-performance
    @hiyve/react-music-performance/room