17
votes

I'm working through this awesome article: https://jackschaedler.github.io/circles-sines-signals/dft_introduction.html

I want to use the Web Audio API's PeriodicWave object to implement this demo: enter image description here

However, when I set a periodic wave with these settings:

 var real = new Float32Array([0,0,1,0,1]);
 var imag = new Float32Array(real.length);
 var customWave = context.createPeriodicWave(real,imag);
 osc.setPeriodicWave(customWave);

I output a wave that looks like this:

enter image description here Here is full code: http://jsbin.com/zaqojavixo/4/edit To see the waveform, please press play the sound a few times.

I believe these should match up, so here are my questions:

  1. Am I missing something fundamental about the theory here or am I just implementing it incorrectly? Is the PeriodicWave object supposed to do the same thing as illustrated in the article?
  2. If I am taking the wrong approach, how would I implement this diagram in Web Audio API? I have been able to match below by connecting two different sine waves of different frequencies to the same gain node - how is this different than using the PeriodicWave object?
  3. I'm new to DSP and Web Audio API - any suggested reading would be appreciated!
  4. Secondarily, in my example, I have to push the 'play the sound' button a couple of times before correct data is drawn to the canvas - the analyser seems to be behind the oscillator, even though analyser.getFloatTimeDomainData() is called after I start the oscillator any thoughts on what's going on here?

Edit: As noted in comments, my graph is upside down (on the canvas 0,0 is the upper left corner).

3

3 Answers

15
votes

Note that the first array defines the cosine terms, the second the sine terms:

The real parameter represents an array of cosine terms (traditionally the A terms). In audio terminology, the first element (index 0) is the DC-offset of the periodic waveform. The second element (index 1) represents the fundamental frequency. The third element represents the first overtone, and so on. The first element is ignored and implementations must set it to zero internally.

The imag parameter represents an array of sine terms (traditionally the B terms). The first element (index 0) should be set to zero (and will be ignored) since this term does not exist in the Fourier series. The second element (index 1) represents the fundamental frequency. The third element represents the first overtone, and so on.

Source

You will see you get the expected waveform but "reversed" (drawn upside down thanks to @Julian for pointing that out in his answer - fixed below):

snap

(I inlined your code here with the arrays swapped around:)
updated fixed drawing issues in original code

//setup audio context
window.AudioContext = window.AudioContext || window.webkitAudioContext;
var context = new window.AudioContext();

//create nodes
var osc; //create in event listener so we can press the button more than once
var masterGain = context.createGain();
var analyser = context.createAnalyser();

//routing
masterGain.connect(analyser);
analyser.connect(context.destination);

var isPlaying = false;

//draw function for canvas
function drawWave(analyser, ctx) {
  
  var buffer = new Float32Array(1024),
      w = ctx.canvas.width;
  
  ctx.strokeStyle = "#777";
  ctx.setTransform(1,0,0,-1,0,100.5); // flip y-axis and translate to center
  ctx.lineWidth = 2;
  
  (function loop() {
    analyser.getFloatTimeDomainData(buffer);
    
    ctx.clearRect(0, -100, w, ctx.canvas.height);

    ctx.beginPath();
    ctx.moveTo(0, buffer[0] * 90);
    for (var x = 2; x < w; x += 2) ctx.lineTo(x, buffer[x] * 90);
    ctx.stroke();
    
    if (isPlaying) requestAnimationFrame(loop)
  })();
}

//button trigger
$(function() {  
  var c = document.getElementById('scope'),
      ctx = c.getContext("2d");
  
  c.height = 200;
  c.width = 600;
  
  // make 0-line permanent as background
  ctx.moveTo(0, 100.5);
  ctx.lineTo(c.width, 100.5);
  ctx.stroke();
  c.style.backgroundImage = "url(" + c.toDataURL() + ")";
  
  $('button').on('mousedown', function() {
    osc = context.createOscillator();
    //osc settings
    osc.frequency.value = 220;
    var imag= new Float32Array([0,0,1,0,1]);   // sine
    var real = new Float32Array(imag.length);  // cos
    var customWave = context.createPeriodicWave(real, imag);  // cos,sine
    osc.setPeriodicWave(customWave);

    osc.connect(masterGain);
    osc.start();
    isPlaying = true;
    
    drawWave(analyser, ctx);
  });

  $('button').on('mouseup', function() {
    isPlaying = false;
    osc.stop();
  }); 
});
button {position:fixed;left:10px;top:10px}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button>Play the sound</button>
<canvas id='scope'></canvas>
2
votes

The only difference between the demo and your output is the phase relationship between the two tones. The demo is y=sin(x) + sin(2x) and yours is y=sin(x) + sin(2x + pi/2). It's not apparent to me where this phase shift is coming from but I don't think it's anything you have done.

Here are some plots from wolfram alpha:

y=sin(x) + sin(2x)

y=sin(x) + sin(2x + pi/2)

1
votes

In createPeriodicWave the real array is the cosine part (or a term), while the imag array (the b term) is the sine part.

See: http://en.wikipedia.org/wiki/Fourier_series

I think you've just inverted the two. In the article it says:

Where Ai is the Amplitude of the ith sine and fi is the frequency of the ith sine.

So it's your imag array (sine part) that should be [0,0,1,0,1] and your real array should be [0,0,0,0,0].

Like this:

var imag = new Float32Array([0,0,1,0,1]);
 var real = new Float32Array(real.length);

Try it in your jsbin you'll it works, only thing it'll be inverted because you're drawing in negative (meaning 0 coordinate is the top, not the bottom). To have what's in the article, either draw in reverse or have your imag array like this: [0,0,-1,0,-1].

If you want to play a bit with different imag and real configuration and see the results, you can see here: http://themusictoolbox.net/waves/