Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to generate Piano sound with slight delay and how to play next midi file when user in play mode #190

Open
ibrarmunircoder opened this issue Mar 14, 2024 · 0 comments

Comments

@ibrarmunircoder
Copy link

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;
@ibrarmunircoder ibrarmunircoder changed the title How to generate Piano sound with slight delay and how to play next midi file when use in play mode How to generate Piano sound with slight delay and how to play next midi file when user in play mode Mar 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant