Aptary Aptary - 1 month ago 12
Javascript Question

Variable defined within function causing reference error: not defined

I'm working my way through some web audio tutorials in this O'Reilly Book: http://chimera.labs.oreilly.com/books/1234000001552/ch02.html#s02_2

The following code is supposed to create a system to pause an audio file and resume play.

// Assume context is a web audio context, buffer is a pre-loaded audio buffer.
var startOffset = 0;
var startTime = 0;

function pause() {
source.stop();
// Measure how much time passed since the last pause.
startOffset += context.currentTime - startTime;
}

function play() {
startTime = context.currentTime;
var source = context.createBufferSource();
// Connect graph
source.buffer = this.buffer;
source.loop = true;
source.connect(context.destination);
// Start playback, but make sure we stay in bound of the buffer.
source.start(0, startOffset % buffer.duration);
}


However, running the
pause()
function results in the following error:

Uncaught ReferenceError: source is not defined


Now from my point of view, this is caused because
source
has been defined with the
var
keyword making it scoped to the
play()
function and therefore inaccessible to
pause()
. Removing the
var
keyword does indeed solve the problem. Can someone just reassure me that my reasoning is correct? Is this just a typo, or is there some underlying principle that I'm not understanding? (I've checked the errata for the book and there's no mention of it there.)

Answer

Declaring a variable in a function makes it a local variable, i.e. it only exists in that function and thus can only be referenced in that function. Declaring it as a global variable will make it available to any Javascript function, but you generally want to pollute the global namespace as little as possible:

function AudioPlayer(buffer) {
  this.startOffset = 0;
  this.startTime = 0;      
  this.source = null;
  this.buffer = buffer;
}

AudioPlayer.prototype.pause = function() {
  if (!this.source) {
    return;
  }
  this.source.stop();
  // Measure how much time passed since the last pause.
  this.startOffset += context.currentTime - this.startTime;
}

AudioPlayer.prototype.play = function() {
  this.startTime = context.currentTime;
  this.source = context.createBufferSource();
  // Connect graph
  this.source.buffer = this.buffer;
  this.source.loop = true;
  this.source.connect(context.destination);
  // Start playback, but make sure we stay in bound of the buffer.
  this.source.start(0, this.startOffset % this.buffer.duration);
}

Which allows you to call these functions like so:

var player = new AudioPlayer(buffer);
player.play();
player.pause();