I've been trying to create an 88 key piano with the Web Audio API. The plan is to run all the 88 oscillators first in the appropriate frequencies and then use Oscillator.connect() and Oscillator.disconnect() methods on the respective oscillators as the piano keys are pressed and released. The state of the AudioContext will be "running" all the time. Now, I have two questions here,

  1. Is this the right way to do it?
  2. I get a clicking noise at the start and end of the sounds when I play them. Why is this happening and how to get rid of it?

PS: The reason for creating a piano like this is to indulge myself in the delight of having created something from scratch. So using prerecorded sounds is not an option.

2

There are 2 best solutions below

5
MikeHelland On

IF you wanted to do it that way, add a gain node to each oscillator, and then turn the gain off and on, instead of disconnect and reconnect.

That's probably what's causing your clicks and snaps. More below.

BUT... that's still pretty overkill, having 88 oscillators. The standard way keyboards do this is with a limited polyphony.

Create an array of ten oscillators, all hooked to their own gain, each gain hooked to the destination.

Keep track of how many keys are being pressed, and how many oscillators are in use.

keysPressed = {}

// on key down
keysPressed["60"] = nextAvailableOsc()

At any given time there are ten oscillators ready to go, one for each finger. If for some reason you require more, add them dynamically.


The clicking sound is because you're hard disconnecting and reconnecting running oscillators. Use a gain node in between the osc and destination, and turn that on and off.

Also, you might get clicks when changing the values hard such as

gainNode.gain.value = 0

That can create a glitch in the sound stream.. It should be:

gainNode.gain.setValueAtTime(0, ctx.currentTime + 1)

Maybe the + 1 is necessary. There's also setTargetAtTime and rampToAtTime methods that make things even smoother:

https://developer.mozilla.org/en-US/docs/Web/API/AudioParam

3
Raymond Toy On

An alternative approach is to create the oscillators on demand. When a key is pressed, create one or more oscillators (for harmonics). These can feed into one (or more?) gain nodes which has an automation for the attack and sustain phase. When the key is released, automate the gain for a release phase and schedule the oscillator(s) to stop after the release phase has ended. Drop the reference to all the oscillators now.

I find this easier to reason about than having an array of oscillators, and there's no limit to the polyphony. But this approach generates more garbage that has to be handled eventually by the collector.