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
|