How to generate Piano sound with slight delay and how to play next midi file when user in play mode
ibrarmunircoder opened this issue · 0 comments
ibrarmunircoder commented
Hi tonejs team!
Can you please tell me how to generate a piano sound effect with a slight delay? I am also facing an issue playing the next midi file when the user clicks on the next button to play the next midi file.
I am going to share my code here, please check and let me know the solution:
'use client';
import React, { useLayoutEffect, useRef, useState } from 'react';
import { MidiFileDoc } from '@/database/models/MidiFile.model';
import Image from 'next/image';
import * as Tone from 'tone';
import { Midi } from '@tonejs/midi';
import { shuffleArray } from '@/shared/utils/shuffleArray';
import { Session } from 'next-auth';
import { SubscriptionDoc } from '@/database/models/Subscription.model';
import { useRouter } from 'next/navigation';
type AudioPlayerProps = {
midiFiles: MidiFileDoc[];
session: Session | null;
subscription: Partial<SubscriptionDoc> | null;
};
const AudioPlayer = ({
midiFiles,
session,
subscription,
}: AudioPlayerProps) => {
const router = useRouter();
const [midi, setMidi] = useState<null | Midi>(null);
const [pause, setPause] = useState(true);
const polySynthsRef = useRef<Tone.PolySynth[]>([]);
const [currentFileIndex, setCurrentFileIndex] = useState(() => {
if (midiFiles?.length) {
return 0;
}
return -1;
});
const fileName =
currentFileIndex < 0 ? null : midiFiles.at(currentFileIndex)?.fileName;
useLayoutEffect(() => {
if (midiFiles.length) {
Midi.fromUrl(midiFiles[0].key)
.then((midi) => {
setMidi(midi);
})
.catch((error) => {
console.error(error);
});
}
}, [midiFiles]);
const handleClearTransport = () => {
if (Tone.Transport.state === 'started') {
Tone.Transport.stop();
Tone.Transport.cancel();
}
};
const handleClearSynth = () => {
while (polySynthsRef.current.length) {
const synth = polySynthsRef.current.shift();
// @ts-ignore
synth?.disconnect();
// @ts-ignore
synth?.releaseAll();
}
polySynthsRef.current = [];
};
const handleNext = async () => {
handleClearTransport();
handleClearSynth();
setPause(true);
if (currentFileIndex < midiFiles.length - 1) {
const file = midiFiles[currentFileIndex + 1];
const midi = await Midi.fromUrl(file.key);
setMidi(midi);
setCurrentFileIndex((prev) => ++prev);
} else {
shuffleArray(midiFiles);
setCurrentFileIndex(0);
}
};
const handleBack = async () => {
handleClearTransport();
handleClearSynth();
setPause(true);
if (currentFileIndex > 0) {
const file = midiFiles[currentFileIndex - 1];
const midi = await Midi.fromUrl(file.key);
setMidi(midi);
setCurrentFileIndex((prev) => --prev);
}
};
const handlePlay = async () => {
setPause(false);
const now = Tone.now() + 0.2;
midi?.tracks.forEach((track) => {
const synth = new Tone.PolySynth(Tone.Synth, {
detune: 0,
portamento: 0,
volume: 0,
oscillator: {
type: 'triangle',
partialCount: 0,
},
envelope: {
attack: 0.02,
decay: 0.1,
sustain: 0.3,
release: 1,
},
}).toDestination();
polySynthsRef.current.push(synth);
track.notes.forEach((note) => {
const startTime = Tone.Time(note.time).toSeconds();
const duration = Tone.Time(note.duration).toSeconds();
Tone.Transport.scheduleOnce(() => {
synth.triggerAttackRelease(
note.name,
duration,
startTime + now,
note.velocity
);
}, startTime + 0.2);
});
});
Tone.Transport.toggle();
};
const handlePause = () => {
handleClearTransport();
handleClearSynth();
setPause(true);
};
const triggerDownload = () => {
const file = midiFiles[currentFileIndex];
const anchor = document.createElement('a');
anchor.href = file.key;
anchor.download = file.fileName;
anchor.click();
};
const handleDownload = async () => {
if (!session) {
router.push('/login');
}
if (currentFileIndex >= 0) {
if (subscription?.status === 'active') {
return triggerDownload();
}
const response = await fetch('/api/user/update-free-limit', {
method: 'POST',
});
const data = await response.json();
if (+data > -1 && +data <= 5) {
return triggerDownload();
}
if (!subscription) {
return router.push('/price');
}
}
};
return (
<>
<div className="max-w-[700px] flex flex-col items-center mx-auto mb-5 p-5 gap-5">
<div className="text-white">{fileName}</div>
<div
className={`flex items-center gap-5 h-12 ${
currentFileIndex < 0 && 'pointer-events-none'
}`}
>
<Image
className="cursor-pointer"
src="/assets/images/audio-left.svg"
width={25}
height={25}
alt="Player Back"
onClick={handleBack}
/>
<Image
className={`${pause ? 'inline-block' : 'hidden'} cursor-pointer`}
src="/assets/images/play.svg"
width={35}
height={35}
alt="Player"
onClick={handlePlay}
/>
<Image
className={`${!pause ? 'inline-block' : 'hidden'} cursor-pointer`}
src="/assets/images/pause.svg"
width={35}
height={35}
alt="Pause"
onClick={handlePause}
/>
<Image
className="cursor-pointer"
src="/assets/images/audio-right.svg"
width={25}
height={25}
alt="Player Next"
onClick={handleNext}
/>
</div>
</div>
<div className="flex justify-center">
<button onClick={handleDownload} className="download">
Download midi file
</button>
</div>
</>
);
};
export default AudioPlayer;