Search code examples
javascriptweb-audio-apitone.js

Tone.js app - audio won't start until second click


I'm aware that I need a user action to start Web Audio on mobile. However I have followed the recommendation to resume (in the case of Tone.js call Tone.start()) on user action but it still don't work. Here are the relevant parts of my code:

keyElement.addEventListener("touchstart", notePressed, false);
keyElement.addEventListener("touchend", noteReleased, false);

function notePressed(event) {
        let dataset = event.target.dataset;
        if (!dataset["pressed"]) {
            let octave = +dataset["octave"];
            oscList[octave][dataset["note"]] = playTone(dataset["frequency"]);
            dataset["pressed"] = "yes";
        }
        event.preventDefault(); event.stopPropagation();
    }
function noteReleased(event) {
      let dataset = event.target.dataset;

      if (dataset && dataset["pressed"]) {
        let octave = +dataset["octave"];
        oscList[octave][dataset["note"]].triggerRelease();
        delete oscList[octave][dataset["note"]];
        delete dataset["pressed"];
      }
    } 
function playTone(freq) {
        let synth = new Tone.Synth().toDestination();
        let now = Tone.now();
        Tone.start();
        synth.triggerAttack(freq, now);
        return synth;
    } 

It works fine on the second touch, but after the first touch I get a warning about the audio context being suspended, even though the standard is that it is resumed when start() is called by a user action. I'm stumped here.


Solution

  • That Tone code looks correct. I simplified it in a CodePen and it worked on mobile on first click (iOS).

    var keyElement = document.getElementById("play");
    
    keyElement.addEventListener("touchstart", notePressed, false);
    keyElement.addEventListener("touchend", noteReleased, false);
    
    // Added mouse ELs to test on non-mobile.
    keyElement.addEventListener("mousedown", notePressed, false); 
    keyElement.addEventListener("mouseup", noteReleased, false);
    
    var pressedTone;
    
    function notePressed(event) {
      console.log("notePressed");
      pressedTone = playTone(440);
      event.preventDefault();
      event.stopPropagation();
    }
    function noteReleased(event) {
      pressedTone.triggerRelease();
    }
    function playTone(freq) {
      console.log("playTone", freq);
      let synth = new Tone.Synth().toDestination();
      let now = Tone.now();
      Tone.start();
      synth.triggerAttack(freq, now);
      return synth;
    }
    
    

    Here's the link to the CodePen: https://codepen.io/joeweiss/pen/gOmeQVW