3

I want to update my UI when a note has finished playing with tone.js

const now = Tone.now()
synth.triggerAttack("G3", now);
synth.triggerRelease("G3", now + 60 / this.tempo)

How can I get a callback or add a listener to the triggerRelease so I can update the UI?

etayluz
  • 15,920
  • 23
  • 106
  • 151
  • won't a wrapper function help? something like your own function that calls triggerRelease and sets an interval that will call the passed callback? – Aniket Kariya Jun 02 '22 at 17:20

1 Answers1

2

I have never used the library but reviewing its documentation it seems that the API defines an onsilence callback that you could use for your purpose.

You can provide that callback when creating the Synth instance passing the appropriate configuration option:

const synth = new Tone.Synth({
  onsilence: () => console.log('I am invoked after the release has finished') // update the UI as appropriate
}).toDestination();
const now = Tone.now()
synth.triggerAttack("G3", now);
synth.triggerRelease(now + 60 / this.tempo);

The API provides some tests related to it as well.

As you indicated in your comments the onsilence callback seems to be invoked only when the instrument is completely in silence, but you need to perform some action when a note ends.

Probably the way to go would be working with the different events offered by the library.

The library documentation provides different related examples that exemplifies how to use them.

Consider for instance review this one and the companion wiki page that try explaining how to sync visuals, or this other one, close to the per note callback you are looking for.

These two examples can be found in the tone.js repository.

With these in mind, consider for instance this code, based on the second example provided when describing the Part event and the snippet of code provided in the aforementioned article about performance and how to sync visuals:

import * as Tone from 'tone';

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, '8n', time);

    Tone.Draw.schedule(function () {
      //this callback is invoked from a requestAnimationFrame
      //and will be invoked close to AudioContext time
      document.getElementById('app').innerHTML += ' ' + value.note;
    }, time);
  },
  [
    { time: 0, note: 'C3' },
    { time: '0:2', note: 'C4' },
  ]
).start(0);
Tone.Transport.start();

Please, reload the preview page in Stackblitz, you should see every note.

Finally, you have several ways if you need to access the parent context this object as indicated in your comments: the exact way will depend on your actual code. Please, consider for instance review this related SO question, I think it could be of help.

jccampanero
  • 50,989
  • 3
  • 20
  • 49
  • The `onsilence` event is apparently only invoked after all notes have played and the synth is completely quiet. There is also delay between the release of the last note and the call to the callback. This does not work properly. – etayluz Jun 07 '22 at 21:21
  • Thank you very much for the feedback @etayluz. I am very sorry to hear that the proposed solution doesn't work properly, as I mentioned in my answer I have never used the library before. I tried reproducing myself your example, but it didn't work properly: please, could you update the question to provide a reproducible example, illustrating the way you are using tone.js? I will be glad to help if I can. Thank you very much. – jccampanero Jun 07 '22 at 21:52
  • I included some additional information in the answer. I hope it will be of help. – jccampanero Jun 07 '22 at 22:35
  • Thanks! Can you show how Tone.Draw.schedule can be used with Tone.Synth? – etayluz Jun 08 '22 at 00:26
  • Basically, I have a series of notes that I loop over and call synth.triggerAttack and synth.triggerRelease on each one. I need an event handler for every triggerRelease. But the provided solution will only fire on the very last triggerRelease and that is also following a small delay after the last note has gone silent. – etayluz Jun 08 '22 at 03:02
  • I am experimenting with onsilence. How do I bind `this` to the onsilence callback so I can call on my class methods? – etayluz Jun 08 '22 at 04:11
  • I've experimented with `onsilence` event while scheduling only a single note - there is a delay between the time the note stops playing and the time onsilence callback is invoked. – etayluz Jun 08 '22 at 04:22
  • 1
    Thank you very much for the feedback @etayluz. I reviewed the documentation and the examples of the library: as I told you, I only have knowledge of the library by your question, but I tried providing an example according to the requirements you expressed in your comments, different from the original `onsilence` approach. I hope it helps. Thank you for introducing me to this fantastic library. – jccampanero Jun 08 '22 at 22:38