/* eslint-disable no-mixed-spaces-and-tabs */ // Inspired from OSMD example project // https://github.com/opensheetmusicdisplay/react-opensheetmusicdisplay/blob/master/src/lib/OpenSheetMusicDisplay.jsx import React, { MutableRefObject, useEffect } from 'react'; import { CursorType, Fraction, OpenSheetMusicDisplay as OSMD, IOSMDOptions, Note, } from 'opensheetmusicdisplay'; import useColorScheme from '../hooks/colorScheme'; import { PianoCursorPosition } from '../models/PianoGame'; type PartitionViewProps = { // The Buffer of the MusicXML file retreived from the API file: string; onPartitionReady: ( dims: [number, number], base64data: string, cursorInfos: PianoCursorPosition[] ) => void; bpmRef: MutableRefObject; onEndReached: () => void; // Timestamp of the play session, in milisecond timestamp: number; }; const PartitionView = (props: PartitionViewProps) => { const colorScheme = useColorScheme(); const OSMD_DIV_ID = 'osmd-div'; const options: IOSMDOptions = { darkMode: colorScheme == 'dark', backend: 'canvas', drawComposer: false, drawCredits: false, drawLyrics: false, drawPartNames: false, followCursor: false, renderSingleHorizontalStaffline: true, cursorsOptions: [{ type: CursorType.Standard, color: 'green', alpha: 0.5, follow: false }], autoResize: false, }; // Turns note.Length or timestamp in ms const timestampToMs = (timestamp: Fraction, wholeNoteLength: number) => { return timestamp.RealValue * wholeNoteLength; }; const getActualNoteLength = (note: Note, wholeNoteLength: number) => { let duration = timestampToMs(note.Length, wholeNoteLength); if (note.NoteTie) { const firstNote = note.NoteTie.Notes.at(1); if (Object.is(note.NoteTie.StartNote, note) && firstNote) { duration += timestampToMs(firstNote.Length, wholeNoteLength); } else { duration = 0; } } return duration; }; useEffect(() => { const _osmd = new OSMD(OSMD_DIV_ID, options); Promise.all([_osmd.load(props.file)]).then(() => { _osmd.render(); _osmd.cursor.show(); const bpm = _osmd.Sheet.HasBPMInfo ? _osmd.Sheet.getExpressionsStartTempoInBPM() : 60; props.bpmRef.current = bpm; const wholeNoteLength = Math.round((60 / bpm) * 4000); const curPos = []; while (!_osmd.cursor.iterator.EndReached) { const notesToPlay = _osmd.cursor .NotesUnderCursor() .filter((note) => { return note.isRest() == false && note.Pitch; }) .map((note) => { return { note: note, duration: getActualNoteLength(note, wholeNoteLength), }; }); const shortestNotes = _osmd!.cursor .NotesUnderCursor() .sort((n1, n2) => n1.Length.CompareTo(n2.Length)) .at(0); const ts = timestampToMs( shortestNotes?.getAbsoluteTimestamp() ?? new Fraction(-1), wholeNoteLength ); const sNL = timestampToMs( shortestNotes?.Length ?? new Fraction(-1), wholeNoteLength ); curPos.push({ offset: _osmd.cursor.cursorElement.offsetLeft, notes: notesToPlay, shortedNotes: shortestNotes, sNinfos: { ts, sNL, isRest: shortestNotes?.isRest(), }, }); _osmd.cursor.next(); } _osmd.cursor.reset(); _osmd.cursor.hide(); // console.log('timestamp cursor', _osmd.cursor.iterator.CurrentSourceTimestamp); // console.log('timestamp cursor', _osmd.cursor.iterator.CurrentVoiceEntries); // console.log('current measure index', _osmd.cursor.iterator.CurrentMeasureIndex); const osmdCanvas = document.querySelector( '#' + OSMD_DIV_ID + ' canvas' ); // this should never happen this is done to silent ts linter about maybe null if (!osmdCanvas) { throw new Error('No canvas found'); } let scale = osmdCanvas.width / parseFloat(osmdCanvas.style.width); if (Number.isNaN(scale)) { console.error('Scale is NaN setting it to 1'); scale = 1; } // Ty https://github.com/jimutt/osmd-audio-player/blob/ec205a6e46ee50002c1fa8f5999389447bba7bbf/src/PlaybackEngine.ts#LL77C12-L77C63 props.onPartitionReady( [osmdCanvas.width, osmdCanvas.height], osmdCanvas.toDataURL(), curPos.map((pos) => { return { x: pos.offset * scale, timing: pos.sNinfos.sNL, timestamp: pos.sNinfos.ts, notes: pos.notes, }; }) ); // Do not show cursor before actuall start }); }, []); return
; }; export default PartitionView;