What is the simplest way of giving Tone.js arrays of notes and durations in seconds to play back?

Viewed 397

I would like to give Tone.js a list of notes and corresponding durations for each note and have it play back the sequence. As far as I can see, there is no easy way to do this.

In the following, the corresponding time values are not the ones I entered (i.e 0.25, 0.5, 0.25), as evidenced by the console.log:

var part = new Tone.Part(function(time, note){
    console.log(time);
    console.log(note);
    synth.triggerAttackRelease(note, time);
}, [[0.25, "C2"], [0.5, "C3"], [0.25, "G2"]]);

part.start(0).loop = false;
Tone.Transport.start();

How can I give Tone.js notes and corresponding ms for playback?

1 Answers

I'm not familiar with Tone.js, so there's probably a better way of doing this. The official example for the array shorthand that you're using doesn't seem to work, so it might be a library issue.

As for what you're trying to achieve, I fiddled with it out of curiosity and here's what I've come to:

function timeFromDurations(value, i, arr) {
  const prevTime = arr[i - 1]?.time;
  value.time = prevTime + arr[i - 1]?.duration || 0;
  return value;
}

const notesAndDurations = [
  { note: 'C3', duration: .25 },
  { note: 'C4', duration: .5 },
  { note: 'G2', duration: 1 },
].map(timeFromDurations);
console.log(notesAndDurations);

const synth = new Tone.Synth().toDestination();
// use an array of objects as long as the object has a "time" attribute
const part = new Tone.Part((time, value) => {
  // the value is an object which contains both the note and the velocity
  synth.triggerAttackRelease(value.note, value.duration, time);
}, notesAndDurations).start(0);
Tone.Transport.start();

The idea is that you need to set start time of each note based on previous note start time + duration. That removes the need to set the start time(not optional) manually.

Edit

For your second case where the durations and the notes come in separate arrays you can use the following reduce function:

const notes = ['C3', 'C4', 'G2'];
const durations = [0.25, 0.5, 1];

const noteDurationTime = notes.reduce((acc, note, i) => {
  const prevTime = acc[i - 1]?.time;
  const time = prevTime + acc[i - 1]?.duration || 0;
  const duration = durations[i];

  acc.push({ note, duration, time });
  return acc;
}, []);

The idea is the same, you're building an array of objects that have all the needed properties(note, duration, time), but this time from different sources(notes array and durations array).
You want to make sure that both these arrays are the same length.

Related