audio-slot-param
Advanced tools
Comparing version 2.0.1 to 2.1.0
144
apply.js
@@ -1,49 +0,34 @@ | ||
var PseudoAudioParam = require('pseudo-audio-param') | ||
var interpolate = require('./lib/interpolate.js') | ||
module.exports = ApplyParam | ||
var nextId = 0 | ||
function ApplyParam(context, target, param){ | ||
function ApplyParam (context, target, param) { | ||
var id = nextId++ | ||
var release = null | ||
var lastValue = null | ||
var currentTime = context.audio.currentTime | ||
var fallback = null | ||
if (!target.getValueAtTime) { | ||
var audioParam = new PseudoAudioParam() | ||
audioParam.applyTo(target) | ||
fallback = target | ||
target = audioParam | ||
} | ||
var events = [] | ||
var maxSchedule = 0 | ||
if (param.onSchedule) { | ||
if (param.onSchedule){ | ||
release = param.onSchedule(schedule) | ||
if (param.getValueAt) { | ||
send('setValueAtTime', [ | ||
param.getValueAt(currentTime), | ||
currentTime] | ||
) | ||
if (param.getValueAt){ | ||
lastValue = param.getValueAt(currentTime) | ||
} | ||
} else if (typeof param === 'function') { | ||
} else if (typeof param === 'function'){ | ||
release = param(schedule) | ||
send('setValueAtTime', [ | ||
param(), | ||
currentTime | ||
]) | ||
lastValue = param() | ||
} | ||
if (currentTime != null && isFinite(lastValue)) { | ||
target.setValueAtTime(lastValue, currentTime) | ||
} | ||
return release | ||
// scoped | ||
function send (method, args) { | ||
if (fallback) { | ||
fallback[method].apply(fallback, args) | ||
} | ||
target[method].apply(target, args) | ||
} | ||
function schedule (descriptor) { | ||
function schedule(descriptor){ | ||
if (!(descriptor instanceof Object)) { | ||
@@ -54,42 +39,87 @@ descriptor = { value: descriptor, at: context.audio.currentTime, duration: 0.1, mode: 'log' } | ||
var toTime = descriptor.at + (descriptor.duration || 0) | ||
var fromTime = Math.max(descriptor.at, context.audio.currentTime) | ||
toTime = Math.max(fromTime, toTime) | ||
lastValue = descriptor.value | ||
var fromValue = descriptor.fromValue != null | ||
? descriptor.fromValue | ||
: target.getValueAtTime(fromTime) | ||
descriptor.at = Math.max(descriptor.at, context.audio.currentTime) | ||
toTime = Math.max(toTime, context.audio.currentTime) | ||
var toValue = descriptor.value | ||
var fromValue = getValueAt(descriptor.at) | ||
descriptor.fromValue = descriptor.fromValue != null ? | ||
descriptor.fromValue : | ||
fromValue | ||
if (descriptor.mode === 'cancel') { | ||
send('cancelScheduledValues', [fromTime]) | ||
} else { | ||
if (maxSchedule > fromTime) { | ||
send('cancelScheduledValues', [fromTime]) | ||
maxSchedule = fromTime | ||
target.cancelScheduledValues(descriptor.at) | ||
truncate(descriptor.at) | ||
} else if (descriptor.duration) { | ||
if (maxSchedule > descriptor.at){ | ||
target.cancelScheduledValues(descriptor.at) | ||
maxSchedule = descriptor.at | ||
} | ||
if (isFinite(fromValue) && (typeof descriptor.fromValue === 'number' || descriptor.mode !== 'log') && descriptor.mode !== 'curve') { | ||
send('setValueAtTime', [fromValue, fromTime]) | ||
if (isRampingAt(descriptor.at)){ | ||
target.setValueAtTime(fromValue, descriptor.at) | ||
} | ||
if (descriptor.mode === 'curve') { | ||
send('setValueCurveAtTime', [toValue, fromTime, descriptor.duration || 0.0001]) | ||
} else if (descriptor.duration) { | ||
if (descriptor.mode === 'exp') { | ||
send('exponentialRampToValueAtTime', [toValue, toTime]) | ||
} else if (descriptor.mode === 'log') { | ||
send('setTargetAtTime', [toValue, fromTime, descriptor.duration / 8]) | ||
} else { | ||
send('linearRampToValueAtTime', [toValue, toTime]) | ||
} | ||
truncate(descriptor.at) | ||
events.push(descriptor) | ||
if (descriptor.mode === 'exp'){ | ||
target.exponentialRampToValueAtTime(descriptor.value, toTime) | ||
} else if (descriptor.mode === 'log'){ | ||
target.setTargetAtTime(descriptor.value, descriptor.at, descriptor.duration / 8) | ||
} else { | ||
send('setValueAtTime', [toValue, fromTime]) | ||
target.linearRampToValueAtTime(descriptor.value, toTime) | ||
} | ||
} else if (descriptor.mode !== 'init' || !maxSchedule) { | ||
truncate(descriptor.at) | ||
events.push(descriptor) | ||
target.cancelScheduledValues(descriptor.at) | ||
target.setValueAtTime(descriptor.value, descriptor.at) | ||
maxSchedule = descriptor.at | ||
} | ||
if (maxSchedule < toTime) { | ||
if (maxSchedule < toTime){ | ||
maxSchedule = toTime | ||
} | ||
} | ||
function truncate(at){ | ||
var currentTime = context.audio.currentTime | ||
for (var i=events.length-1;i>=0;i--){ | ||
var to = events[i].at + (events[i].duration || 0) | ||
if (events[i].at > at || to < currentTime){ | ||
events.splice(i, 1) | ||
} | ||
} | ||
} | ||
function getEndTime(){ | ||
var lastEvent = events[events.length-1] | ||
if (lastEvent){ | ||
return lastEvent.at + (lastEvent.duration || 0) | ||
} | ||
} | ||
function getValueAt(time){ | ||
for (var i=0;i<events.length;i++){ | ||
var event = events[i] | ||
var next = events[i+1] | ||
if (!next || next.at > time){ | ||
return interpolate(event, time) | ||
} | ||
} | ||
return lastValue | ||
} | ||
function isRampingAt(time){ | ||
for (var i=0;i<events.length;i++){ | ||
var event = events[i] | ||
if (event.at >= time && (event.at + event.duration||0) <= time){ | ||
return event.duration && event.mode !== 'log' | ||
} | ||
} | ||
return false | ||
} | ||
} |
29
index.js
var ObservNode = require('observ-node-array/single') | ||
var Event = require('geval') | ||
var setImmediate = require('setimmediate2').setImmediate | ||
var deepEqual = require('deep-equal') | ||
@@ -21,16 +22,18 @@ module.exports = Param | ||
obs.defaultValue = defaultValue | ||
obs.set = function(v){ | ||
set(v == null ? defaultValue : v) | ||
if (typeof obs() === 'number'){ | ||
var msg = { | ||
mode: 'log', | ||
duration: 0.1, | ||
value: obs(), | ||
at: context.audio.currentTime | ||
obs.set = function (v) { | ||
if (!deepEqual(v, obs())) { | ||
set(v == null ? defaultValue : v) | ||
if (typeof obs() === 'number') { | ||
var msg = { | ||
mode: 'log', | ||
duration: 0.1, | ||
value: obs(), | ||
at: context.audio.currentTime | ||
} | ||
if (initial) { | ||
queued.push(msg) | ||
} else { | ||
broadcast(msg) | ||
} | ||
} | ||
if (initial) { | ||
queued.push(msg) | ||
} else { | ||
broadcast(msg) | ||
} | ||
} | ||
@@ -37,0 +40,0 @@ } |
{ | ||
"name": "audio-slot-param", | ||
"version": "2.0.1", | ||
"version": "2.1.0", | ||
"description": "Link and transform schedule-based observables and connect to AudioParams (Web Audio API).", | ||
@@ -30,8 +30,8 @@ "main": "index.js", | ||
"dependencies": { | ||
"deep-equal": "^1.0.1", | ||
"geval": "^2.1.1", | ||
"observ": "^0.2.0", | ||
"observ-node-array": "^1.10.0", | ||
"pseudo-audio-param": "^1.1.0", | ||
"setimmediate2": "^2.0.1" | ||
} | ||
} |
19
proxy.js
var watch = require('observ/watch') | ||
var Event = require('geval') | ||
var deepEqual = require('deep-equal') | ||
@@ -9,2 +10,3 @@ module.exports = ParamProxy | ||
var release = null | ||
var lastValue = null | ||
@@ -35,4 +37,4 @@ var obs = proxy() | ||
broadcast({ | ||
at: context.audio.currentTime, | ||
value: value.getValueAt(context.audio.currentTime) | ||
at: context.audio.currentTime, | ||
value: value.getValueAt(context.audio.currentTime) | ||
}) | ||
@@ -43,6 +45,9 @@ } | ||
release = watch(value, function(data) { | ||
broadcast({ | ||
value: data, | ||
at: context.audio.currentTime | ||
}) | ||
if (!deepEqual(lastValue, data)) { | ||
lastValue = data | ||
broadcast({ | ||
value: data, | ||
at: context.audio.currentTime | ||
}) | ||
} | ||
}) | ||
@@ -86,2 +91,2 @@ } | ||
} | ||
} | ||
} |
168
transform.js
var Event = require('geval') | ||
var PseudoAudioParam = require('pseudo-audio-param') | ||
var apply = require('./apply') | ||
var setImmediate = require('setimmediate2').setImmediate | ||
var interpolate = require('./lib/interpolate.js') | ||
@@ -12,7 +10,5 @@ module.exports = ParamTransform | ||
var transforms = [] | ||
var values = [] | ||
var lastValues = [] | ||
var interpolateChannel = [] | ||
var rescheduling = false | ||
var scheduleFrom = context.audio.currentTime | ||
params.forEach(function (container, i) { | ||
@@ -27,12 +23,18 @@ if (container.onSchedule) { | ||
var param = container.param | ||
if (param.onSchedule) { | ||
var channel = channels[i] = new PseudoAudioParam() | ||
releases.push(apply(context, channel, param)) | ||
releases.push(param.onSchedule(onSchedule)) | ||
releases.push(param.onSchedule(schedule.bind(this, i))) | ||
interpolateChannel[i] = true | ||
} else if (typeof param === 'function') { | ||
releases.push(param(onChange.bind(this, i))) | ||
values[i] = param() | ||
releases.push(param(schedule.bind(this, i))) | ||
} | ||
if (param.getValueAt) { | ||
lastValues[i] = param.getValueAt(context.audio.currentTime) | ||
} else if (typeof param === 'function') { | ||
lastValues[i] = param() | ||
} | ||
channels[i] = [] | ||
} else if (container.value != null) { | ||
values[i] = container.value | ||
lastValues[i] = container.value | ||
} | ||
@@ -71,55 +73,62 @@ | ||
// scoped | ||
function onSchedule (descriptor) { | ||
schedule(Math.max(context.audio.currentTime, descriptor.at)) | ||
} | ||
function onChange (i, value) { | ||
values[i] = value | ||
schedule(context.audio.currentTime) | ||
} | ||
function schedule (index, descriptor) { | ||
if (!interpolateChannel[index]) { | ||
descriptor = { value: descriptor, at: context.audio.currentTime } | ||
} | ||
function schedule (from) { | ||
scheduleFrom = Math.max(context.audio.currentTime, Math.min(from, scheduleFrom)) | ||
if (!rescheduling) { | ||
rescheduling = true | ||
setImmediate(doSchedule) | ||
if (descriptor.mode === 'cancel') { | ||
var currentValue = getChannelValueAt(index, descriptor.at) | ||
if (isFinite(currentValue) && getEndTime() > context.audio.currentTime) { | ||
descriptor.value = currentValue | ||
truncate(index, descriptor.at) | ||
channels[index].push(descriptor) | ||
} else { | ||
return false | ||
} | ||
} | ||
} | ||
function doSchedule () { | ||
rescheduling = false | ||
var endTime = Math.max(scheduleFrom, getEndTime()) | ||
var duration = endTime - scheduleFrom | ||
var toTime = descriptor.at + (descriptor.duration || 0) | ||
lastValues[index] = descriptor.value | ||
var steps = Math.ceil(duration * context.audio.sampleRate / 100) || 1 | ||
var stepTime = duration / steps | ||
var curve = new Float32Array(steps + 1) | ||
for (var i = 0; i < curve.length; i++) { | ||
var time = stepTime * i + scheduleFrom | ||
curve[i] = getValueAt(time) | ||
} | ||
descriptor.fromValue = descriptor.fromValue != null | ||
? descriptor.fromValue | ||
: getChannelValueAt(index, descriptor.at) | ||
broadcast({ | ||
at: scheduleFrom, | ||
mode: 'curve', | ||
value: curve, | ||
duration: duration | ||
truncate(index, descriptor.at) | ||
channels[index].push(descriptor) | ||
broadcastIfValid({ | ||
at: descriptor.at, | ||
mode: descriptor.mode, | ||
value: getValueAt(toTime), | ||
duration: descriptor.duration | ||
}) | ||
scheduleFrom = endTime | ||
var endTime = getEndTime() | ||
if (endTime > toTime) { | ||
broadcastIfValid({ | ||
at: toTime, | ||
value: getValueAt(endTime), | ||
duration: endTime - toTime | ||
}) | ||
} | ||
} | ||
function getValueAt (at) { | ||
var lastValue = 1 | ||
for (var i = 0; i < params.length; i++) { | ||
var value = channels[i] | ||
? channels[i].getValueAtTime(at) | ||
: values[i] | ||
if (transforms[i]) { | ||
lastValue = transforms[i](lastValue, value) | ||
} else { | ||
lastValue = value | ||
function broadcastIfValid (descriptor) { | ||
if (descriptor && isFinite(descriptor.value)) { | ||
broadcast(descriptor) | ||
} | ||
} | ||
function truncate (index, at) { | ||
var events = channels[index] | ||
var currentTime = context.audio.currentTime | ||
for (var i = events.length - 1; i >= 0; i--) { | ||
var to = events[i].at + (events[i].duration || 0) | ||
if (events[i].at > at || to < currentTime) { | ||
events.splice(i, 1) | ||
} | ||
} | ||
return lastValue | ||
} | ||
@@ -130,8 +139,7 @@ | ||
for (var i = 0; i < params.length; i++) { | ||
if (channels[i] && channels[i].events) { | ||
var events = channels[i].events | ||
var events = channels[i] | ||
if (events) { | ||
var lastEvent = events[events.length - 1] | ||
if (lastEvent) { | ||
var duration = lastEvent.timeConstant ? lastEvent.timeConstant * 8 : lastEvent.duration | ||
var endAt = lastEvent.time + (duration || 0) | ||
var endAt = lastEvent.at + (lastEvent.duration || 0) | ||
if (endAt > maxTime) { | ||
@@ -145,2 +153,46 @@ maxTime = endAt | ||
} | ||
function getValueAt (time) { | ||
var lastValue = 1 | ||
for (var i = 0; i < params.length; i++) { | ||
var value = getChannelValueAt(i, time) | ||
var l = lastValue | ||
if (transforms[i]) { | ||
lastValue = transforms[i](lastValue, value) | ||
} else { | ||
lastValue = value | ||
} | ||
if (typeof lastValue === 'number' && isNaN(lastValue)) { | ||
getChannelValueAt(i, time) | ||
transforms[i](l, value) | ||
} | ||
} | ||
return lastValue | ||
} | ||
function getChannelValueAt (index, time) { | ||
var events = channels[index] | ||
if (events) { | ||
for (var i = 0; i < events.length; i++) { | ||
var event = events[i] | ||
var next = events[i + 1] | ||
if (!next || next.at > time) { | ||
if (interpolateChannel[index]) { | ||
return interpolate(event, time) | ||
} else { | ||
return event.value | ||
} | ||
} | ||
} | ||
} | ||
return lastValues[index] | ||
} | ||
} |
16371
8
471
+ Addeddeep-equal@^1.0.1
- Removedpseudo-audio-param@^1.1.0
- Removedpseudo-audio-param@1.3.1(transitive)