-1

I am a bit struggling to create a sound from a buffer object with Tone.js

It should be simple, but I am in unfamiliar territory. And increasingly unuseful suggestions from chatGPT discombobulated me very.

Here is the code (I know the code is a bit messy. Sorry for that, I couldn't make it even simplified)

import React, { useEffect, useState } from 'react';
import * as Tone from 'tone';

function AudioPlayer() {
  const [isPlaying, setIsPlaying] = useState(false);
  const chunkSize = 1024;
  const bufferArray = new Float32Array(chunkSize);
  const sampleRate = 16384;
  let t = 0;
  let x = 0;
  const context = Tone.context;
  const buffer = context.createBuffer(1, chunkSize, sampleRate);
// const source = context.createBufferSource(); <= do I really need this!
  const player = new Tone.Player(buffer).toDestination();

  useEffect(() => {

    while (isPlaying){
      for (let i = 0; i < chunkSize; i++) {
        bufferArray[i] = Math.sin(2 * Math.PI * 440 * x);
        t++;
        x = t / sampleRate;
      }
      // buffer.fromArray(bufferArray); <= is there a simple method that just override on buffer? 
    }

    
  }, [isPlaying]);

  const handlePlayClick = async () => {
    setIsPlaying(true);
    player.start()
    
  };

  const handleStopClick = () => {
    setIsPlaying(false);
    player.stop()
  };

  return (
    <div>
      {!isPlaying && (
        <button onClick={handlePlayClick}>Play</button>
      )}
      {isPlaying && (
        <button onClick={handleStopClick}>Stop</button>
      )}
    </div>
  );
}

export default AudioPlayer;


I just want to fill the buffer(bufferArray[chunkSize]) and override the bufferArray and play it continuously.

merkwur
  • 51
  • 5

2 Answers2

1

I have close to zero idea what I'm doing and I'm not sure I understand the question, but I played around a bit; does the following help at all?:

const sampleRate = 16384;

const get_audio_buffer = () => {
  return Tone.context.createBuffer(
    1,
    sampleRate,
    sampleRate
  );
};

const populate_audio_buffer = (buffer) => {
  const nowBuffering = buffer.getChannelData(0);
  
  for(let i = 0; i < buffer.length; i++) {
    nowBuffering[i] = Math.sin(2 * Math.PI * i * 440 / sampleRate);
  }
};

function AudioPlayer() {
  const [isPlaying, setIsPlaying] = React.useState(false);
  const player = React.useRef(null);
  
  if(!player.current) {
    const buffer = get_audio_buffer();
    populate_audio_buffer(buffer);
    
    player.current = new Tone.Player(buffer).toDestination();
    player.current.loop = true;
  }

  const handlePlayClick = () => {
    setIsPlaying(true);
    player.current.start();
  };

  const handleStopClick = () => {
    setIsPlaying(false);
    player.current.stop();
  };

  return (
    <div>
      {!isPlaying && (
        <button onClick={handlePlayClick}>Play</button>
      )}
      {isPlaying && (
        <button onClick={handleStopClick}>Stop</button>
      )}
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById('app'));
root.render(<AudioPlayer />);
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.min.js"></script>

<div id="app"></div>

Things I looked at:

Ben Stephens
  • 3,303
  • 1
  • 4
  • 8
  • That was exactly what I was asking for, thanks. – merkwur Apr 15 '23 at 17:50
  • Sorry for the extended comment: This looks like what I want but, not quite. In your example, you created one complete cycle of a sine wave and playing it in a loop. Therefore, it heards like a continuous wave. But I want this cycle: create the bufferArray[chunkSize] => Override on bufferObject => play the buffer. Cycle this until the user hit the stop button. With this, I can create my own oscilloscope with my own wave and manipulate it (modulate, frequency shift, phase shift, etc...) on the fly. I think this should be the way to achieve it. Otherwise, any other suggestions are welcome. – merkwur Apr 15 '23 at 18:19
0

Here is an answer -not quite, but works- for the question that I asked.

import React, { useEffect, useState } from 'react';

function AudioPlayer() {
  const [isPlaying, setIsPlaying] = useState(false);
  const [audioCtx, setAudioCtx] = useState(false);
  const [oscillator, setOscillator] = useState(null);
  // const audioDataArray = [-1, 1]//[...Array(4)].map(e => Math.random(-1, 1));
  const [audioDataArray, setAudioDataArray] = useState([-1, 1]);

  useEffect(() => {
    // Create an oscillator and set the waveform to the custom audio data array
    if (!isPlaying){
      const ac = new AudioContext();
      setAudioCtx(ac)
      const oscillator = ac.createOscillator();
      const real = new Float32Array(audioDataArray);
      const imag = new Float32Array(audioDataArray.length).fill(0);
      const wave = ac.createPeriodicWave(real, imag);
      oscillator.setPeriodicWave(wave);
      console.log(oscillator)
      oscillator.connect(ac.destination);
      setOscillator(oscillator);
    }

  }, [isPlaying]);

  useEffect(() => {
    // Update the audio data array with new random values every 100 milliseconds
    const intervalId = setInterval(() => {
      const newAudioDataArray = [...Array(4)].map(e => Math.random(-1, 1));
      setAudioDataArray(newAudioDataArray);
    }, 100);

    return () => {
      clearInterval(intervalId);
    };
  }, []);

  const handlePlayClick = () => {
    if (!isPlaying && oscillator) {
      setIsPlaying(true);
      oscillator.start();
    }
  };

  const handleStopClick = () => {
    if (isPlaying && oscillator) {
      setIsPlaying(false);
      oscillator.stop();
      oscillator.disconnect();
      setOscillator(null);
    }
  };

  const handleUpdateClick = () => {
    if (oscillator) {
      const real = new Float32Array(audioDataArray);
      const imag = new Float32Array(audioDataArray.length).fill(0);
      const wave = audioCtx.createPeriodicWave(real, imag);
      oscillator.setPeriodicWave(wave);
    }
  };

  return (
    <div>
      <button onClick={handlePlayClick}>Play</button>
      <button onClick={handleStopClick}>Stop</button>
      <button onClick={handleUpdateClick}>Update Waveform</button>
    </div>
  );
}

export default AudioPlayer;
merkwur
  • 51
  • 5