2

Im coming from max/msp and trying to figure out the best practices of programming web audio in terms of optimising my code / getting better performance.

I'm reading about the fact that one cannot call .start(), then .stop() then .start() on an oscillator for optimisation reasons. I would like to know what the best design pattern is if I would like to make a simple 1 oscillator synth class.

I would like to instantiate the synth before I need to play it. This way I imagine I get the best timing, if I would like to play the synth at a later time, so the system don't have to create the oscillator / synth pattern, every time I hit 'play note'.

But it would be great to not use processing power on oscillators I don't hear because e.g. the amplitude envelope is not open.

Here is a simple synth, without amplitude envelope. How could I make a similar pattern, where I only use processing power when the synth is actually playing?

Best, Lasse

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>How to optimize CPU usage</title>
</head>
<body>

  <a href="#" id="playButton">Play Note</a> <br><br>
  <a href="#" id="stopButton">Stop Note</a>

  <script>
    class Synth {
      constructor () {
        this.context = new AudioContext();
        this.osc = this.context.createOscillator();
        this.osc.connect(this.context.destination);
      }

      play(freq) {
        this.osc.frequency.value = freq;
        this.osc.start(0);
      }

      stop() {
        this.osc.stop(0);
      }
    }

    let synth = new Synth();

    const playButton = document.getElementById('playButton')
      .addEventListener('click', () => {synth.play(440)});
    
      const stopButton = document.getElementById('stopButton')
      .addEventListener('click', () => {synth.stop()}); 
  </script>
</body>
</html>
Lasse Munk
  • 23
  • 2
  • How would you know about the phase of the oscillator if it isn't running when you don't hit a note? – Johannes Klauß Apr 12 '21 at 11:59
  • That is a good question. I'm coming from max/msp where there are build in methods for using an oscilator as a lookup table and ramping through it which gives you the phase :) – Lasse Munk Jul 07 '21 at 16:09

1 Answers1

1

I think this is a little closer to what you want:

let context = new AudioContext();

class Synth {
  constructor () {}

  play(freq) {
    this.osc = context.createOscillator();
    this.osc.connect(context.destination);
    this.osc.frequency.value = freq;
    this.osc.start(0);
  }

  stop() {
    this.osc.stop(0);
  }
}

First of all, you only need one audioContext. You shouldn't be creating a new one each time the oscillator plays. As far as your concerns about the performance, I wouldn't worry too hard about that. Oscillators are designed to be cheap and disposable. It's fine to create them on the fly, and forget about them after they play once.

Raphael Serota
  • 2,157
  • 10
  • 17