all files / sample-player/ index.js

14.13% Statements 13/92
0% Branches 0/60
0% Functions 0/16
17.57% Lines 13/74
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121                                                                                                                                                                                                                        
var parser = require('note-parser')
var isAudioBuffer = require('is-audio-buffer')
var context = require('audio-context')
 
function Player (buffers, defaults) {
  if (!defaults) defaults = {}
  var select = bufferSelector(buffers)
 
  return function (options) {
    var opts = validOptions(options, defaults)
    opts.buffer = select(opts.midi !== null ? Math.floor(opts.midi) : opts.name)
 
    var player = {
      source: source(opts),
      env: opts.context.createGain(),
      filter: filter(opts),
      output: gain(opts)
    }
    player.source.connect(player.env)
    player.env.connect(player.filter)
    player.filter.connect(player.output)
 
    player.start = function (when, dur) {
      var time = Math.max(opts.context.currentTime, when || 0)
      if (opts.attack) {
        player.env.gain.setValueAtTime(0, time)
        player.env.gain.linearRampToValueAtTime(opts.gain, time + opts.attack)
      } else {
        player.env.gain.setValueAtTime(1, time)
      }
      player.source.start(time)
      if (dur) player.stop(time + dur)
      else if (opts.duration) player.stop(time + opts.duration)
      return player
    }
    player.stop = function (when) {
      var time = Math.max(opts.context.currentTime, when || 0)
      var release = opts.release || 0
      if (release) {
        player.env.gain.setValueAtTime(opts.gain, time)
        player.env.gain.linearRampToValueAtTime(0, time + release)
      }
      player.source.stop(time + release)
    }
    player.connect = function (dest) {
      if (!dest) dest = opts.context.destination
      player.output.connect(dest)
      return player
    }
 
    return player
  }
}
 
// Create a Gain node with the given options
function gain (opts) {
  var gain = opts.context.createGain()
  gain.gain.value = opts.gain
  return gain
}
 
// Create a buffer source with given options
function source (opts) {
  var source = opts.context.createBufferSource()
  source.buffer = opts.buffer
  if (opts.detune) source.detune.value = opts.detune
  if (opts.loop === true) source.loop = true
  return source
}
 
function filter (opts) {
  if (!opts.filter) return opts.context.createGain()
  var filter = opts.context.createBiquadFilter()
  if (opts.filter) filter.type = opts.filter
  if (opts.filterFreq) filter.frequency.value = opts.filterFreq
  if (opts.filterQ) filter.Q.value = opts.filterQ
  return filter
}
 
function cents (midi) {
  return parseInt(midi.toFixed(2).split('.')[1], 10)
}
 
function isValue (x) { var t = typeof x; return t === 'string' || t === 'number' }
 
function validOptions (options, defaults) {
  var opts = !options ? Object.assign({}, defaults)
    : isValue(options) ? Object.assign({}, defaults, { name: options })
    : Object.assign({}, defaults, options)
 
  opts.context = opts.context || context
  opts.gain = opts.gain || opts.gain === 0 ? opts.gain : 1
  opts.name = opts.name || opts.note || opts.midi || opts.pitch
  opts.midi = parser.midi(opts.name) || +opts.name
  if (!opts.detune && opts.midi) opts.detune = cents(opts.midi)
  return opts
}
 
/**
 * Given an object of names to buffers, returns a function that
 * returns the buffer
 */
function bufferSelector (buffers) {
  if (isAudioBuffer(buffers)) return function () { return buffers }
  if (typeof buffers === 'object') buffers = midify(buffers)
  return function (name) { return buffers[name] }
}
 
// Given an object that maps names to buffers, returns an objec that maps
// midi numbers to buffers
function midify (buffers) {
  return Object.keys(buffers).reduce(function (midified, name) {
    var midi = parser.midi(name)
    if (midi !== null) midified[midi] = buffers[name]
    else midified[name] = buffers[name]
    return midified
  }, {})
}
 
module.exports = Player