| 
					
				 | 
			
			
				@@ -0,0 +1,212 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+/* global AudioBuffer */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+'use strict' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+var ADSR = require('adsr') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+var EMPTY = {} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+var DEFAULTS = { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  gain: 1, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  attack: 0.01, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  decay: 0.1, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  sustain: 0.9, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  release: 0.3, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  loop: false, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  cents: 0, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  loopStart: 0, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  loopEnd: 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+/** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * Create a sample player. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * @param {AudioContext} ac - the audio context 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * @param {ArrayBuffer|Object<String,ArrayBuffer>} source 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * @param {Onject} options - (Optional) an options object 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * @return {player} the player 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * @example 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * var SamplePlayer = require('sample-player') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * var ac = new AudioContext() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * var snare = SamplePlayer(ac, <AudioBuffer>) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * snare.play() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+function SamplePlayer (ac, source, options) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  var connected = false 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  var nextId = 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  var tracked = {} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  var out = ac.createGain() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  out.gain.value = 1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  var opts = Object.assign({}, DEFAULTS, options) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  /** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * @namespace 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  var player = { context: ac, out: out, opts: opts } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  if (source instanceof AudioBuffer) player.buffer = source 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  else player.buffers = source 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  /** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * Start a sample buffer. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * The returned object has a function `stop(when)` to stop the sound. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * @param {String} name - the name of the buffer. If the source of the 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * SamplePlayer is one sample buffer, this parameter is not required 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * @param {Float} when - (Optional) when to start (current time if by default) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * @param {Object} options - additional sample playing options 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * @return {AudioNode} an audio node with a `stop` function 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * @example 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * var sample = player(ac, <AudioBuffer>).connect(ac.destination) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * sample.start() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * sample.start(5, { gain: 0.7 }) // name not required since is only one AudioBuffer 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * @example 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * var drums = player(ac, { snare: <AudioBuffer>, kick: <AudioBuffer>, ... }).connect(ac.destination) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * drums.start('snare') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * drums.start('snare', 0, { gain: 0.3 }) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  player.start = function (name, when, options) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    // if only one buffer, reorder arguments 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if (player.buffer && name !== null) return player.start(null, name, when) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    var buffer = name ? player.buffers[name] : player.buffer 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if (!buffer) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      console.warn('Buffer ' + name + ' not found.') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      return 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } else if (!connected) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      console.warn('SamplePlayer not connected to any node.') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      return 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    var opts = options || EMPTY 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    when = Math.max(ac.currentTime, when || 0) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    player.emit('start', when, name, opts) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    var node = createNode(name, buffer, opts) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    node.id = track(name, node) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    node.env.start(when) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    node.source.start(when) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    player.emit('started', when, node.id, node) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if (opts.duration) node.stop(when + opts.duration) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    return node 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  // NOTE: start will be override so we can't copy the function reference 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  // this is obviously not a good design, so this code will be gone soon. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  /** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * An alias for `player.start` 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * @see player.start 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * @since 0.3.0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  player.play = function (name, when, options) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    return player.start(name, when, options) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  /** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * Stop some or all samples 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * @param {Float} when - (Optional) an absolute time in seconds (or currentTime 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * if not specified) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * @param {Array} nodes - (Optional) an array of nodes or nodes ids to stop 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * @return {Array} an array of ids of the stoped samples 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * @example 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * var longSound = player(ac, <AudioBuffer>).connect(ac.destination) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * longSound.start(ac.currentTime) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * longSound.start(ac.currentTime + 1) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * longSound.start(ac.currentTime + 2) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * longSound.stop(ac.currentTime + 3) // stop the three sounds 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  player.stop = function (when, ids) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    var node 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    ids = ids || Object.keys(tracked) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    return ids.map(function (id) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      node = tracked[id] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      if (!node) return null 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      node.stop(when) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      return node.id 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    }) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  /** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * Connect the player to a destination node 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * @param {AudioNode} destination - the destination node 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * @return {AudioPlayer} the player 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * @chainable 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * @example 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   * var sample = player(ac, <AudioBuffer>).connect(ac.destination) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  player.connect = function (dest) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    connected = true 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    out.connect(dest) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    return player 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  player.emit = function (event, when, obj, opts) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if (player.onevent) player.onevent(event, when, obj, opts) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    var fn = player['on' + event] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if (fn) fn(when, obj, opts) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  return player 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  // =============== PRIVATE FUNCTIONS ============== // 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  function track (name, node) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    node.id = nextId++ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    tracked[node.id] = node 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    node.source.onended = function () { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      var now = ac.currentTime 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      node.source.disconnect() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      node.env.disconnect() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      node.disconnect() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      player.emit('ended', now, node.id, node) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    return node.id 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  function createNode (name, buffer, options) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    var node = ac.createGain() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    node.gain.value = 0 // the envelope will control the gain 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    node.connect(out) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    node.env = envelope(ac, options, opts) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    node.env.connect(node.gain) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    node.source = ac.createBufferSource() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    node.source.buffer = buffer 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    node.source.connect(node) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    node.source.loop = options.loop || opts.loop 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    node.source.playbackRate.value = centsToRate(options.cents || opts.cents) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    node.source.loopStart = options.loopStart || opts.loopStart 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    node.source.loopEnd = options.loopEnd || opts.loopEnd 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    node.stop = function (when) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      var time = when || ac.currentTime 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      player.emit('stop', time, name) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      var stopAt = node.env.stop(time) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      node.source.stop(stopAt) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    return node 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+function isNum (x) { return typeof x === 'number' } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+var PARAMS = ['attack', 'decay', 'sustain', 'release'] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+function envelope (ac, options, opts) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  var env = ADSR(ac) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  var adsr = options.adsr || opts.adsr 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  PARAMS.forEach(function (name, i) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if (adsr) env[name] = adsr[i] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    else env[name] = options[name] || opts[name] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  }) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  env.value.value = isNum(options.gain) ? options.gain 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    : isNum(opts.gain) ? opts.gain : 1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  return env 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+/* 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * Get playback rate for a given pitch change (in cents) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * Basic [math](http://www.birdsoft.demon.co.uk/music/samplert.htm): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * f2 = f1 * 2^( C / 1200 ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+function centsToRate (cents) { return cents ? Math.pow(2, cents / 1200) : 1 } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+module.exports = SamplePlayer 
			 |