diff --git a/front/components/PartitionCoord.tsx b/front/components/PartitionCoord.tsx index 210382e..15cdcdd 100644 --- a/front/components/PartitionCoord.tsx +++ b/front/components/PartitionCoord.tsx @@ -12,6 +12,7 @@ type PartitionCoordProps = { onPause: () => void; // Timestamp of the play session, in milisecond timestamp: number; + pressedKeys: Map; }; const PartitionCoord = ({ @@ -21,6 +22,7 @@ const PartitionCoord = ({ onPause, onResume, timestamp, + pressedKeys, }: PartitionCoordProps) => { const [partitionData, setPartitionData] = React.useState< [string, PianoCursorPosition[]] | null @@ -48,6 +50,7 @@ const PartitionCoord = ({ timestamp={timestamp} onPause={onPause} onResume={onResume} + pressedKeys={pressedKeys} onEndReached={() => { onEndReached(); }} diff --git a/front/components/PartitionVisualizer/PhaserCanvas.tsx b/front/components/PartitionVisualizer/PhaserCanvas.tsx index eb7b4fa..6a63da9 100644 --- a/front/components/PartitionVisualizer/PhaserCanvas.tsx +++ b/front/components/PartitionVisualizer/PhaserCanvas.tsx @@ -11,6 +11,7 @@ import { SplendidGrandPiano, CacheStorage } from 'smplr'; import { Note } from 'opensheetmusicdisplay'; let globalTimestamp = 0; +let globalPressedKeys: Map = new Map(); const globalStatus: 'playing' | 'paused' | 'stopped' = 'playing'; const isValidSoundPlayer = (soundPlayer: SplendidGrandPiano | undefined) => { @@ -48,17 +49,42 @@ const getPianoScene = ( private cursorPositionsIdx = -1; private partition!: Phaser.GameObjects.Image; private cursor!: Phaser.GameObjects.Rectangle; + private emitter!: Phaser.GameObjects.Particles.ParticleEmitter; + private emitzone!: Phaser.GameObjects.Particles.Zones.EdgeZone; + private nbTextureTolad!: number; create() { + this.textures.addBase64( + 'star', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEQAAAApCAYAAACMeY82AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMDE0IDc5LjE1Njc5NywgMjAxNC8wOC8yMC0wOTo1MzowMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjYyNzNEOEU4NzUxMzExRTRCN0ZCODQ1QUJCREFFQzA4IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjYyNzNEOEU5NzUxMzExRTRCN0ZCODQ1QUJCREFFQzA4Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NjI3M0Q4RTY3NTEzMTFFNEI3RkI4NDVBQkJEQUVDMDgiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NjI3M0Q4RTc3NTEzMTFFNEI3RkI4NDVBQkJEQUVDMDgiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4vDiTDAAAB4UlEQVR42uxagY2DMAw0VRdghayQFVghs3SF/1XaEdoVGIGswAj5OK0rkwfalx5wiY2smAik+OrYFxcAAWLABFQJazmAykCOEhZRx0sBYdLHS0VFk6omVU2qOwRktS1jwYY5QKSAslqEtNBWM0lVt4xUQERUmdrWSV+VZi27xVYZU1OimYyOmJTRDB58VVyE5BUJQYhJGZYGYzMH87nuqwuoRSRVdLyB5hcoWIZxjs9P2buT3LkI0PP+BKcQriEp2jjnwO0XjDFwUDFRouNXF5HoQlK0iwKDVw10/GzPdzBIoo1zBApF1prb57iUw/zQxkdkpY1rwNhoOQMDkhpt62wqw+ZisMTiOwNQqLu2VMWpxhggOcBbe/zwlTvKqTfZxDyJYyAAfEyPTTF2/1AcWj8Ye39fU98+geHlebBuvv4xX8Zal5WoCEGnvn1y/na5JQdz55aOkM3knRyS5xwpbcZ/BSG//2uVWTrB6uFOAjHjoT9GzIojZzlYdJaRQNcPW0cMby3OtRl32w950PbU2yAAiGPk22qL0rp4hOSlEkFAfvGi6RwenCXsLkLGfuUcDGKf/J2tIkTGv/9t/xaQxQDCzyPFJdU1AYns5kO3jKAPZhQQPctohHweIJK+D/kRYAAaWClvtE6otAAAAABJRU5ErkJggg==' + ); this.textures.addBase64('partition', partitionB64); this.cursorPositionsIdx = -1; + this.nbTextureTolad = 2; this.cameras.main.setBackgroundColor(colorScheme === 'light' ? '#FFFFFF' : '#000000'); this.textures.on('onload', () => { + this.nbTextureTolad--; + if (this.nbTextureTolad > 0) return; this.partition = this.add.image(0, 0, 'partition').setOrigin(0, 0); this.cameras.main.setBounds(0, 0, this.partition.width, this.partition.height); 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); + + + // create an emitter the once called later will spawn 15 particules all around the sprite that it is attached to + this.emitter = this.add.particles(0, 0, 'star', { + lifespan: 700, + duration: 100, + quantity: 2, + follow: this.cursor, + speed: { min: 10, max: 20 }, + scale: { start: 0, end: 0.4 }, + // rotate: { start: 0, end: 360 }, + emitZone: { type: 'edge', source: this.cursor.getBounds(), quantity: 50 }, + + emitting: false + }); }); } @@ -76,6 +102,14 @@ const getPianoScene = ( this.cursorPositionsIdx = idx; return true; } + if (globalPressedKeys.size > 0) { + // add particles at the position of the cursor + this.emitter.start(1); + this.cursor.fillAlpha = 0.9; + } else if (this.cursor) { + this.cursor.fillAlpha = 0.5; + } + return false; }); if (cP) { @@ -127,6 +161,7 @@ export type PhaserCanvasProps = { onResume: () => void; // Timestamp of the play session, in milisecond timestamp: number; + pressedKeys: Map; }; const PhaserCanvas = ({ @@ -134,6 +169,7 @@ const PhaserCanvas = ({ cursorPositions, onEndReached, timestamp, + pressedKeys, }: PhaserCanvasProps) => { const colorScheme = useColorScheme(); const dispatch = useDispatch(); @@ -141,6 +177,7 @@ const PhaserCanvas = ({ const [game, setGame] = React.useState(null); globalTimestamp = timestamp; + globalPressedKeys = pressedKeys; useEffect(() => { if (isValidSoundPlayer(soundPlayer)) { diff --git a/front/views/PlayView.tsx b/front/views/PlayView.tsx index 2648052..aaec2b4 100644 --- a/front/views/PlayView.tsx +++ b/front/views/PlayView.tsx @@ -87,6 +87,8 @@ const PlayView = ({ songId, type, route }: RouteProps) => { ); const getElapsedTime = () => stopwatch.getElapsedRunningTime() - 3000; const [midiKeyboardFound, setMidiKeyboardFound] = useState(); + // first number is the note, the other is the time when pressed on release the key is removed + const [pressedKeys, setPressedKeys] = useState>(new Map()); // [note, time] const onPause = () => { stopwatch.pause(); @@ -210,17 +212,30 @@ const PlayView = ({ songId, type, route }: RouteProps) => { } }; inputs.forEach((input) => { - if (inputIndex != 0) { - return; - } + // if (inputIndex != 0) { + + // return; + // } input.onmidimessage = (message) => { - const { command } = parseMidiMessage(message); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { command, channel, note, velocity } = parseMidiMessage(message); const keyIsPressed = command == 9; - const keyCode = message.data[1]; + if (keyIsPressed) { + setPressedKeys((prev) => { + prev.set(note, getElapsedTime()); + return prev; + }); + } else { + setPressedKeys((prev) => { + prev.delete(note); + return prev; + }); + } + webSocket.current?.send( JSON.stringify({ type: keyIsPressed ? 'note_on' : 'note_off', - note: keyCode, + note: note, id: song.data!.id, time: getElapsedTime(), }) @@ -293,6 +308,7 @@ const PlayView = ({ songId, type, route }: RouteProps) => { onEndReached={onEnd} onPause={onPause} onResume={onResume} + pressedKeys={pressedKeys} onPartitionReady={() => setPartitionRendered(true)} /> {!partitionRendered && }