Tonejs / Tone.js

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

Starting the Tone.js schedule at a specific start timestamp #896

Closed MattTheRed closed 3 years ago

MattTheRed commented 3 years ago

This is more a question on if whether or not this is currently possible than a feature request so feel free to close this issue if this is not a good venue for it.

The feature you'd like I'm looking for the ability to schedule a loop to start at a specific timestamp based on a device's system time.

The use case is an interactive art project where we would like to have a sequencer playing music and allow participants use their own mobile phones to interact and create complimentary sounds in time with the main sequencer.

To accomplish this I'm thinking of having the main sequencer/server choose the start time and using the system clock on each individual mobile device to control the Tone.js start time and schedule notes in the future. I'll most likely be using Firebase realtime database for the communication between clients.

I'm wondering if Tone.js already has an API that allows you to set the start time or if you have guidance on where would be a good place to add that capability if I fork the project. Or if you know of others who have attempted something similar it would be great to learn from their successes/failures

Thank you for your help

Any alternatives you've considered Instead of using Tone.js directly, using the AudioContext API to manipulate the start time of the web page before initializing Tone.js

Additional context N/A

Feature Requests will eventually be closed if inactive N/A

MattTheRed commented 3 years ago

I figured out a way to do something pretty close to what I'm trying to accomplish.

If you load this example on two separate devices and click the button when it loads, both will wait until until the same time to start playing.

<button id="play">CLICK ME TO PLAY</button>
var playButton = document.getElementById('play');
var nextStartTime;
var tickTimeout;

let synth = new Tone.Synth()
synth.chain(Tone.Master);

Tone.Transport.bpm.value = 120;

var part = new Tone.Part(
  function(time, note) {
    synth.triggerAttackRelease(note, "8n", time);
  },
  [
    ["0:0", "D4"],
    ["0:1", "A4"],
    ["0:2", "G4"],
    ["0:3", "E4"],

  ]
).start(Tone.now());

  part.loop = true;
  part.loopEnd = '1m';

  const playBtn = document.getElementById("play");
  playBtn.onclick = startAtNextStartTime;

function startAtNextStartTime(){
  playButton.disabled = true;
  nextStartTime = (Math.floor(Date.now()/10000) * 10000) + 10000; // Round the current time to the nearest 10 seconds and add 10 seconds in the future
 // Start and stop immediately so the click event starts Tone.js
  Tone.Transport.start(Tone.now());
  Tone.Transport.stop();
  tick();
}

function tick(){
  var now = Date.now();
  if (now >= nextStartTime){
    clearTimeout(tickTimeout);
    Tone.Transport.start(Tone.now());
    playButton.innerHTML = 'MUSIC IS PLAYING';
  } else {
    var delta = (nextStartTime - now)/1000;
    playButton.innerHTML = 'STARTING MUSIC IN : ' + delta + ' SECONDS';
    tickTimeout = setTimeout(tick, 10);
  }
  var delta = nextStartTime - Date
}
tambien commented 3 years ago

Glad you found something that works.

This is similar to how i would have suggested you do it. Instead of polling for the right time to start the Transport, you can also schedule the start time in the future

const futureStartDate = new Date("May 24 2021 14:00:00 GMT-0400");
// the time difference in seconds
const delta = (futureStartDate - Date.now()) / 1000;
// schedule the transport to start that many seconds from now
Tone.Transport.start(Tone.now() + delta);
MattTheRed commented 3 years ago

That's great, I didn't realize you could pass an actual timestamp. Thanks for the help!