diff --git a/front/components/PartitionCoord.tsx b/front/components/PartitionCoord.tsx index 76216b1..1ad6a28 100644 --- a/front/components/PartitionCoord.tsx +++ b/front/components/PartitionCoord.tsx @@ -31,7 +31,9 @@ const PartitionCoord = ({ setPartitionData([base64data, a]); onPartitionReady(); }} - onEndReached={onEndReached} + onEndReached={() => { + console.log('osmd end reached'); + }} timestamp={timestamp} /> )} @@ -39,6 +41,9 @@ const PartitionCoord = ({ { + onEndReached(); + }} /> )} diff --git a/front/components/PartitionView.tsx b/front/components/PartitionView.tsx index c985daf..20e3781 100644 --- a/front/components/PartitionView.tsx +++ b/front/components/PartitionView.tsx @@ -28,7 +28,7 @@ const PartitionView = (props: PartitionViewProps) => { const [osmd, setOsmd] = useState(); const [soundPlayer, setSoundPlayer] = useState(); const audioContext = new SAC.AudioContext(); - const [wholeNoteLength, setWholeNoteLength] = useState(0); // Length of Whole note, in ms (?) + // const [wholeNoteLength, setWholeNoteLength] = useState(0); // Length of Whole note, in ms (?) const colorScheme = useColorScheme(); const dimensions = useWindowDimensions(); const OSMD_DIV_ID = 'osmd-div'; @@ -45,15 +45,15 @@ const PartitionView = (props: PartitionViewProps) => { autoResize: false, }; // Turns note.Length or timestamp in ms - const timestampToMs = (timestamp: Fraction) => { + const timestampToMs = (timestamp: Fraction, wholeNoteLength: number) => { return timestamp.RealValue * wholeNoteLength; }; - const getActualNoteLength = (note: Note) => { - let duration = timestampToMs(note.Length); + 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); + duration += timestampToMs(firstNote.Length, wholeNoteLength); } else { duration = 0; } @@ -97,9 +97,38 @@ const PartitionView = (props: PartitionViewProps) => { _osmd.render(); _osmd.cursor.show(); // get the current cursor position + const bpm = _osmd.Sheet.HasBPMInfo ? _osmd.Sheet.getExpressionsStartTempoInBPM() : 60; + // setWholeNoteLength(Math.round((60 / bpm) * 4000)); + const wholeNoteLength = Math.round((60 / bpm) * 4000); const curPos = []; while (!_osmd.cursor.iterator.EndReached) { - curPos.push(_osmd.cursor.cursorElement.offsetLeft); + 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(); } console.log('curPos', curPos); @@ -110,14 +139,14 @@ const PartitionView = (props: PartitionViewProps) => { console.log('current measure index', _osmd.cursor.iterator.CurrentMeasureIndex); const osmdCanvas = document.querySelector('#' + OSMD_DIV_ID + ' canvas'); // Ty https://github.com/jimutt/osmd-audio-player/blob/ec205a6e46ee50002c1fa8f5999389447bba7bbf/src/PlaybackEngine.ts#LL77C12-L77C63 - const bpm = _osmd.Sheet.HasBPMInfo ? _osmd.Sheet.getExpressionsStartTempoInBPM() : 60; - setWholeNoteLength(Math.round((60 / bpm) * 4000)); props.onPartitionReady( osmdCanvas.toDataURL(), curPos.map((pos) => { return { - x: pos, - timing: Math.floor(Math.random() * 600) + 100, + x: pos.offset, + timing: pos.sNinfos.sNL, + timestamp: pos.sNinfos.ts, + notes: pos.notes, }; }) ); diff --git a/front/components/PartitionVisualizer/PhaserCanvas.tsx b/front/components/PartitionVisualizer/PhaserCanvas.tsx index 2cf2b74..dccc9b2 100644 --- a/front/components/PartitionVisualizer/PhaserCanvas.tsx +++ b/front/components/PartitionVisualizer/PhaserCanvas.tsx @@ -4,16 +4,16 @@ import * as React from 'react'; import { useEffect, useRef } from 'react'; import Phaser from 'phaser'; import useColorScheme from '../../hooks/colorScheme'; +import { PartitionContext } from '../../views/PlayView'; +import { on } from 'events'; -const b64data = - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAQAAAGKDAGaAAAFgUlEQVRYw7WXa2wUVRTH/20p7fZBW0p5iAplaUELCqEFlUCMYKwJKMYgaEwIUpQYNCIWRYgvQtTS6AeiKEqIQDBIAEFAEEm1DUVUoIqPVqhCC/IoammhC9vt/vywM7szu7NLLfHMl7n3nP/533vuPefMSHap8wNIoG57pxIYSErZDyCgB8qogTYUeKT+v2C+SlLanqA3v4EBwSdAasAI4CUUcA7wRsiBMbDIjQDQd4M0ogimtkrbMWSgAVzibUIouTIwfc4kJaVIkpT+PTxLiGplkHCo6Wq+uSOydlrYl3jD1xOSAQ5zdzcA1CJJK/ygOEk91kIhAMsQmeMDq9MRg7ojGExIINkrOAm8D4DHSg9QwiQApiDmeQzFWaDE2LUQ4DXjedimsAQaShgbqXj4NM4ISZpmVeTbdt6vGISrWtElfs76Fuig9+9hij7qFjbTs6GecEnxSdKa4DjbK0nFrzWFTI4yE/jVsvDsMaauP0JxX0ARviBgOg0A3GnZaVpO4N3llZRYYZrez8dhC/KRxrBVYSt37TPVgSh6GQN8FFxSQPLOqbsBSP3Wy2gLwJIRQcC91mPIOGyaTaTKARBxbllH7EtqZkJsgCRlvrTbF21JQiS3KN7pkFNH/BMOiJ+nq8vQTTNI8ChZnZabXJs7bxzf9zjAO/8kTLfNxynXwTpze6PlnG8/o16B+fzTfqCRgq1W62nXtYff1X1orjRiqzn2M8TgySlqAFjNuqDxFdIRKpWeag/Vo7zfJWXXei1ep3AaKDYDWypNvhLQnETk1+kWf/hC3iUrdGil0i3lAE8iRM/r1cpm3g0aNzAdgB8sACnvm8Bo0AuSLgLwKMeBQjosTEuCAJu0BSPgjsjo5WS0KyUM4Amqf6aO2cFRPUKU0cLAOhvgsgUAUM4u/PQz9lAGwC5/75eDgFlhAHuxKzNmynGtMwDtvMWOmIBahEjbYAACBWYCzY6A18kx3jI2WXolQJvRKMLqb6g5bbO0AnMPlbwSFdBrpwMA4FkOOwJ6mw08q/J8jChZnhtCR9HdfTo2IP6JyGQrfKbVGZBeETWdB723PwzQ/ZISr1IDchs9IcCtnasbWWMvCC3SNUhm2ophJ2raq9pyjycvjbju1yTje/604IzHllMXmH0256BGRBoPTipaO/jv4ktTPaNaC+rd02I5dqUsdTdUXiaGfOYbdKbPi2bnLkjLO3cszGJx+00fRLoemXlg1qkLVDOOxVyM6v4MM4kLlpQh6cObI22OIcatNh0nJj3X/8S2S3aTFhZwJ9/Z5raTZ71npZKUOWVxhPtDuBD5jZI7c+8DJ8/Giga7GcObzCMxMkFKJWlUYm6ztU0cYbihH7lccbv3cw9z+Tuq+yrG8gptNDGHBEcCSXGDjz7NDG6zWQyYLylhTyDSl1nK7VRYHLfxKmOpjCDcaH5dR5T1obPddekel9f95/BPs/ubwa+wB76acTzPRJ6hOWbYGnkEEb/wqlc96eu2CHAJ1cznLg5Fdf8lBQjxBqv87qbcx2MQJFd5HAjMyrSD0bxN6ABbWUiSQ9f4jQnNA7epjwNBSvWVGATmrX+M+xjjUGbLbHbtLLvU7w/dYf/0OrCV0ZTjjUEQ/WOxzJYh+QiRut5GkP6dz/jBms0kartA0ByWIekbbQQ9DnXYHG2gkIJOE7gc5jK32AgyavwOZ/A504N/AjG/px2entvtxf5Hoh5yByspZMN/JMjZbQ/RwRrf1W5RLZMY3Pkd7Ii8q5N71y9rae/CLbI/PY5qfKyk7ttvy13nj3aBIN6XslwZnW2TcX1KMlre8vk7RZB6QsVd7ccD3dUPXTwVhSCuI+lD80fi2iQhb1H+X5ssBEmn9KD+B7k54yut0XX/HfgvpUkmTvPggOsAAAAASUVORK5CYII='; - -const cursorB64 = - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAAJYCAIAAABdEXdWAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTM4IDc5LjE1OTgyNCwgMjAxNi8wOS8xNC0wMTowOTowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTcgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjY0NDUxRDFFOTUwODExRTdCMUJEQjg0NTEzM0EzMTY2IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjY0NDUxRDFGOTUwODExRTdCMUJEQjg0NTEzM0EzMTY2Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NjQ0NTFEMUM5NTA4MTFFN0IxQkRCODQ1MTMzQTMxNjYiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NjQ0NTFEMUQ5NTA4MTFFN0IxQkRCODQ1MTMzQTMxNjYiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4+DXQ3AAAy2UlEQVR42qxd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'; +let globalTimestamp = 0; +let globalStatus: 'playing' | 'paused' | 'stopped' = 'playing'; const getPianoScene = ( partitionB64: string, cursorPositions: PianoCursorPosition[], + onEndReached: () => void, colorScheme: 'light' | 'dark' ) => { class PianoScene extends Phaser.Scene { @@ -30,63 +30,39 @@ const getPianoScene = ( this.cursor = this.add.rectangle(0, 0, 30, 350, 0x31ef8c, 0.5).setOrigin(0, 0); this.cameras.main.startFollow(this.cursor, true, 0.05, 0.05); - - this.cursorMoveNext(); - - // // const raster = this.add.image(0, 0, 'raster'); - // const group = this.add.group(); - - // group.createMultiple({ key: 'expo', repeat: 8 }); - - // let ci = 0; - // const colors = [ - // 0xef658c, 0xff9a52, 0xffdf00, 0x31ef8c, 0x21dfff, 0x31aade, 0x5275de, 0x9c55ad, - // 0xbd208c, - // ]; - - // const _this = this; - - // group.children.iterate((child) => { - // child.x = 100; - // child.y = 300; - // child.depth = 9 - ci; - - // child.tint = colors[ci]; - - // ci++; - - // _this.tweens.add({ - // targets: child, - // x: 900, - // yoyo: true, - // repeat: -1, - // ease: 'Sine.easeInOut', - // duration: 1500, - // delay: 100 * ci, - // }); - // }); }); } - cursorMoveNext(gameobject) { - if (this.cursorPositionsIdx >= cursorPositions.length) { - return; - } - this.cursorPositionsIdx++; - const cursorPosition = cursorPositions[this.cursorPositionsIdx]; - if (cursorPosition) { - this.tweens.add({ - targets: this.cursor, - x: cursorPosition.x, - duration: 200, - ease: 'Sine.easeInOut', - onComplete: () => { - // wait cursorPosition.timing - setTimeout(() => { - this.cursorMoveNext(); - }, cursorPosition.timing); - }, + override update() { + const currentTimestamp = globalTimestamp; + const status = globalStatus; + + if (status === 'playing') { + const transitionTime = 75; + const cP = cursorPositions.findLast((cP, idx) => { + if ( + cP.timestamp < currentTimestamp + transitionTime && + idx > this.cursorPositionsIdx + ) { + this.cursorPositionsIdx = idx; + return true; + } + return false; }); + if (cP) { + const tw = { + targets: this!.cursor, + x: cP!.x, + duration: transitionTime, + ease: 'Sine.easeInOut', + }; + if (this.cursorPositionsIdx === cursorPositions.length - 1) { + tw.onComplete = () => { + onEndReached(); + }; + } + this.tweens.add(tw); + } } } } @@ -98,25 +74,39 @@ export type PianoCursorPosition = { x: number; // timestamp in ms timing: number; + timestamp: number; + notes: any[]; }; -type PhaserCanvasProps = { +export type UpdateInfo = { + currentTimestamp: number; + status: 'playing' | 'paused' | 'stopped'; +}; + +export type PhaserCanvasProps = { partitionB64: string; cursorPositions: PianoCursorPosition[]; + onEndReached: () => void; }; -const PhaserCanvas = ({ partitionB64, cursorPositions }: PhaserCanvasProps) => { +const PhaserCanvas = ({ + partitionB64, + cursorPositions, + onEndReached, +}: PhaserCanvasProps) => { const colorScheme = useColorScheme(); + const { timestamp } = React.useContext(PartitionContext); const [game, setGame] = React.useState(null); + globalTimestamp = timestamp; useEffect(() => { - const pianoScene = getPianoScene(partitionB64, cursorPositions, colorScheme); + const pianoScene = getPianoScene(partitionB64, cursorPositions, onEndReached, colorScheme); const config = { type: Phaser.AUTO, parent: 'phaser-canvas', width: 1000, - height: 900, + height: 400, scene: pianoScene, scale: { mode: Phaser.Scale.FIT, diff --git a/front/views/PlayView.tsx b/front/views/PlayView.tsx index 1920cf4..f85e044 100644 --- a/front/views/PlayView.tsx +++ b/front/views/PlayView.tsx @@ -21,7 +21,6 @@ import { transformQuery, useQuery } from '../Queries'; import API from '../API'; import LoadingComponent, { LoadingView } from '../components/Loading'; import Constants from 'expo-constants'; -import VirtualPiano from '../components/VirtualPiano/VirtualPiano'; import { strToKey, keyToStr, Note } from '../models/Piano'; import { useSelector } from 'react-redux'; import { RootState } from '../state/Store'; @@ -70,6 +69,14 @@ function parseMidiMessage(message: MIDIMessageEvent) { }; } +export const PartitionContext = React.createContext<{ + // Timestamp of the play session, in milisecond + timestamp: number; +}>({ + timestamp: 0, +}); + + const PlayView = ({ songId, type, route }: RouteProps) => { const accessToken = useSelector((state: RootState) => state.user.accessToken); const navigation = useNavigation(); @@ -79,7 +86,6 @@ const PlayView = ({ songId, type, route }: RouteProps) => { const webSocket = useRef(); const [paused, setPause] = useState(true); const stopwatch = useStopwatch(); - const [isVirtualPianoVisible, setVirtualPianoVisible] = useState(false); const [time, setTime] = useState(0); const [partitionRendered, setPartitionRendered] = useState(false); // Used to know when partitionview can render const [score, setScore] = useState(0); // Between 0 and 100 @@ -218,7 +224,7 @@ const PlayView = ({ songId, type, route }: RouteProps) => { useEffect(() => { ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE).catch(() => {}); const interval = setInterval(() => { - setTime(() => getElapsedTime()); // Countdown + setTime(() =>getElapsedTime()); // Countdown }, 1); return () => { @@ -253,6 +259,11 @@ const PlayView = ({ songId, type, route }: RouteProps) => { } return ( + ) => { /> */} { onEnd(); }} @@ -288,39 +300,6 @@ const PlayView = ({ songId, type, route }: RouteProps) => { {!partitionRendered && } - {isVirtualPianoVisible && ( - - { - console.log('On note down', keyToStr(note)); - }} - onNoteUp={(note) => { - console.log('On note up', keyToStr(note)); - }} - showOctaveNumbers={true} - startNote={Note.C} - endNote={Note.B} - startOctave={2} - endOctave={5} - style={{ - width: '80%', - height: '100%', - }} - highlightedNotes={[ - { key: strToKey('D3') }, - { key: strToKey('A#'), bgColor: '#00FF00' }, - ]} - /> - - )} ) => { } }} /> - - } - onPress={() => { - setVirtualPianoVisible(!isVirtualPianoVisible); - }} - /> {time < 0 ? paused @@ -406,6 +371,7 @@ const PlayView = ({ songId, type, route }: RouteProps) => { + ); };