Tone.js 14,743 (https://github.com/Tonejs/Tone.js 14,743 ) (npm 2,036 i tone, MIT, version 15.1.22) puts a digital audio workstation on Web Audio: synths, samplers, effects, and a Transport that schedules loops and sequences in musical time ('8n' is an eighth note). Tone objects wrap native nodes through the standardized-audio-context package and schedule events 0.1 s ahead (the context's lookAhead) so a busy main thread does not delay them.
startButton.onclick = async () => {
await Tone.start(); // unlock audio inside a click
const synth = new Tone.PolySynth(Tone.Synth).connect(new Tone.Reverb(1.5).toDestination());
new Tone.Sequence((time, note) => synth.triggerAttackRelease(note, '8n', time),
['C4', 'E4', 'G4', ['A4', 'G4'], 'E4', 'D4', 'C4', null]).start(0);
Tone.getTransport().bpm.value = 96;
Tone.getTransport().start();
};The nested array plays two notes in one step. Callbacks fire imprecisely, so always pass their time argument on.