Tonejs / Tone.js

A Web Audio framework for making interactive music in the browser.
https://tonejs.github.io
MIT License
13.24k stars 963 forks source link

Start time must be strictly greater #1256

Open handeyeco opened 3 weeks ago

handeyeco commented 3 weeks ago

Describe the bug

ToneJS keeps crashing:

Uncaught Error: Start time must be strictly greater than previous start time

When I log, Tone.now() is the same thing even when 30+ milliseconds have passed.

To Reproduce

This example crashes for me intermittently (code below): https://glitch.com/edit/#!/married-lean-castanet?path=src%2Fapp.jsx%3A59%3A8

Seems to be most consistent after doing a full page refresh. It also seems to be more of a problem that faster I run through the graph.

Expected behavior ToneJS shouldn't crash, it should just keep on truckin'. It also shouldn't have the same value when running Tone.now() several milliseconds apart, but I could be wrong about that.

What I've tried I have a workaround:

      try {
        const y = yFromX(playheadPosition);
        const freq = map(y, -10, 10, 200, 1000);
        synth.triggerAttackRelease(freq, 0.1);
      } catch (err) {}

Just drop the error and keep going.

Additional context

import { Mafs, Coordinates, Line, Theme, vec } from "mafs";
import { useState, useEffect } from "react";
import * as Tone from "tone";
import "mafs/core.css";

// min: inclusive
// max: exclusive
function getRandomInt(min, max) {
  const minCeiled = Math.ceil(min);
  const maxFloored = Math.floor(max);
  return Math.floor(Math.random() * (maxFloored - minCeiled) + minCeiled);
}

function map(
  n,
  start1,
  stop1,
  start2,
  stop2
) {
  return ((n - start1) / (stop1 - start1)) * (stop2 - start2) + start2;
}

const point1 = [getRandomInt(-9, 0), getRandomInt(-9, 0)];
const point2 = [getRandomInt(1, 10), getRandomInt(1, 10)];

function yFromX(x) {
  const gradient = (point2[1] - point1[1]) / (point2[0] - point1[0]);
  const intercept = point1[1] - gradient * point1[0];
  return gradient * x + intercept;
}

const synth = new Tone.Synth().toDestination();

export default function LineChart() {
  const [playheadPosition, setPlayheadPosition] = useState(-10);
  const [playing, setPlaying] = useState(false);

  useEffect(() => {
    if (!playing) {
      return;
    }

    if (playheadPosition > 10) {
      setPlaying(false);
      return;
    }

    if (playheadPosition > point1[0] && playheadPosition < point2[0]) {
      const y = yFromX(playheadPosition);
      const now = Tone.now();
      const freq = map(y, -10, 10, 200, 1000);
      synth.triggerAttack(freq, now);
      synth.triggerRelease(now + 0.001);
    }

    setTimeout(() => {
      setPlayheadPosition((prev) => prev + 0.1);
    }, 10);
  }, [playheadPosition, playing]);

  function handlePressPlay() {
    setPlayheadPosition(-10);
    setPlaying(true);
  }

  return (
    <>
      <Mafs
        preserveAspectRatio={false}
        viewBox={{
          x: [-10, 10],
          y: [-10, 10],
          padding: 0,
        }}
        width={600}
        height={600}
      >
        <Coordinates.Cartesian />
        <Line.Segment point1={point1} point2={point2} color={Theme.blue} />
        {playing && (
          <Line.Segment
            point1={[playheadPosition, -10]}
            point2={[playheadPosition, 10]}
            color={Theme.red}
          />
        )}
      </Mafs>
      <button onClick={handlePressPlay} disabled={playing}>
        Play
      </button>
    </>
  );
}