Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Playing a chord with OscillatorNodes using the Web Audio API

I would like to play a chord using OscillatorNodes:

var ac = new (window.AudioContext || window.webkitAudioContext);
// C4, E4, G4
var freqs = [261.63, 329.63, 392.00];
for(var i=0;i<freqs.length;i++) {
  var o = ac.createOscillator();
  o.frequency.value = freqs[i];
  o.connect(ac.destination);
  o.noteOn(0);
  setTimeout(function() {o.noteOff(0)}, 1000);
}

But this approach sounds like a mess (here's what it sounds like). If I try creating new AudioContexts for each note in the chord, then it sounds fine (like this):

// C4, E4, G4
var freqs = [261.63, 329.63, 392.00];
for(var i=0;i<freqs.length;i++) {
  var ac = new (window.AudioContext || window.webkitAudioContext);
  var o = ac.createOscillator();
  o.frequency.value = freqs[i];
  o.connect(ac.destination);
  o.noteOn(0);
  setTimeout(function() {o.noteOff(0)}, 1000);
}

But I read that you're only supposed to have one AudioContext. What am I doing wrong?

like image 313
Labbekak Avatar asked Jan 13 '13 20:01

Labbekak


2 Answers

Not sure this can be a solution, but I found out inserting a GainNode and setting it's value so that the gain will sum to 1 eliminates the issue:

var ac = new (window.AudioContext || window.webkitAudioContext);
// C4, E4, G4
var freqs = [261.63, 329.63, 392.00];
for(var i=0;i<freqs.length;i++) {
  var o = ac.createOscillator();
  var g = ac.createGainNode();
  o.frequency.value = freqs[i];
  o.connect(g);
  g.gain.value = 1/freqs.length;
  g.connect(ac.destination);
  o.start(0);
  setTimeout(function(s) {s.stop(0)}, 1000, o);
}

I tried this on Chrome 23.0.1271.101

Updated to use the new start and stop methods: createOscillator noteOn not working

like image 114
kuu Avatar answered Oct 16 '22 20:10

kuu


noteOn(0) starts the oscillator / note immediately.

Since your for loop takes time to create the oscillators, the start time is slightly delayed for every note after the first. I would initialize each oscillator, put them in an array, then call noteOn() on every one of them in another for loop.

And you don't need to call setTimeout, which is unreliable: noteOff will be executed after in a second by calling noteOff(1).

var ac = new (window.AudioContext || window.webkitAudioContext);
// C4, E4, G4
var freqs = [261.63, 329.63, 392.00];
var oscs = [];
// initialize the oscillators
for(var i=0;i<freqs.length;i++) {
    var o = ac.createOscillator();
    o.frequency.value = freqs[i];
    o.connect(ac.destination);
    oscs.push(o);
}
// schedule noteOn and noteOff (deprecated: the methods will be renamed to start() and   stop() soon)
for (i = 0; i < oscs.length; i +=1) {
    oscs[i].noteOn(0);
    oscs[i].noteOff(1);
}
like image 4
janesconference Avatar answered Oct 16 '22 19:10

janesconference