flocking
Version:
Creative audio synthesis for the Web
1,109 lines (920 loc) • 34.3 kB
JavaScript
/*
* Flocking Envelopes
* https://github.com/continuing-creativity/flocking
*
* Copyright 2011-2014, Colin Clark
* Dual licensed under the MIT and GPL Version 2 licenses.
*/
/*global require*/
/*jshint white: false, newcap: true, regexp: true, browser: true,
forin: false, nomen: true, bitwise: false, maxerr: 100,
indent: 4, plusplus: false, curly: true, eqeqeq: true,
freeze: true, latedef: true, noarg: true, nonew: true, quotmark: double, undef: true,
unused: true, strict: true, asi: false, boss: false, evil: false, expr: false,
funcscope: false*/
var fluid = fluid || require("infusion"),
flock = fluid.registerNamespace("flock");
(function () {
"use strict";
var $ = fluid.registerNamespace("jQuery"),
ArrayMath = flock.requireModule("webarraymath", "ArrayMath");
/*********************
* Envelope Creators *
*********************/
flock.envelope = {};
// Unsupported API.
flock.envelope.makeCreator = function (name, envelopeOptionsTransformer) {
return function (options) {
var defaults = flock.ugenDefaults(name),
merged = $.extend(true, {}, defaults, options);
return envelopeOptionsTransformer(merged);
};
};
// Unsupported API.
flock.envelope.registerCreators = function (inNamespace, creatorSpecs) {
var path, creatorSpec;
for (var pathSuffix in creatorSpecs) {
path = fluid.pathUtil.composePath(inNamespace, pathSuffix);
creatorSpec = creatorSpecs[pathSuffix];
flock.ugenDefaults(path, creatorSpec.defaults);
fluid.setGlobalValue(path, flock.envelope.makeCreator(path, creatorSpec.transformer));
}
};
// Unsupported API.
flock.envelope.creatorSpecs = {
line: {
transformer: function (o) {
return {
levels: [o.start, o.end],
times: [o.duration]
};
},
defaults: {
start: 0.0,
end: 1.0,
duration: 1.0
}
},
linear: {
transformer: function (o) {
return {
levels: [0, o.level, o.level, 0],
times: [o.attack, o.sustain, o.release]
};
},
defaults: {
level: 1.0,
attack: 0.01,
sustain: 1.0,
release: 1.0
}
},
tri: {
transformer: function (o) {
return {
levels: [0, o.level, 0],
times: [o.duration, o.duration]
};
},
defaults: {
level: 1.0,
duration: 1.0
}
},
sin: {
transformer: function (o) {
return {
levels: [0, o.level, 0],
times: [o.duration, o.duration],
curve: "sin"
};
},
defaults: {
level: 1.0,
duration: 1.0
}
},
asr: {
transformer: function (o) {
return {
levels: [0, o.sustain, 0],
times: [o.attack, o.release],
sustainPoint: 1,
curve: -4.0
};
},
defaults: {
sustain: 1.0,
attack: 0.01,
release: 1.0
}
},
dadsr: {
transformer: function (o) {
var levels = [0, 0, o.peak, o.peak * o.sustain, 0];
ArrayMath.add(levels, o.bias, levels);
return {
levels: levels,
times: [o.delay, o.attack, o.decay, o.release],
sustainPoint: 3,
curve: -4.0
};
},
defaults: {
delay: 0.1,
attack: 0.01,
decay: 0.3,
sustain: 0.5,
release: 1.0,
peak: 1.0,
bias: 0.0
}
},
adsr: {
transformer: function (o) {
var levels = [0, o.peak, o.peak * o.sustain, 0];
ArrayMath.add(levels, o.bias, levels);
return {
levels: levels,
times: [o.attack, o.decay, o.release],
sustainPoint: 2,
curve: -4.0
};
},
defaults: {
attack: 0.01,
decay: 0.3,
sustain: 0.5,
release: 1.0,
peak: 1.0,
bias: 0.0
}
}
};
flock.envelope.registerCreators("flock.envelope", flock.envelope.creatorSpecs);
flock.envelope.validate = function (envelope, failOnError) {
var levels = envelope.levels,
report = {};
if (!envelope.times) {
report.times = "An array containing at least one time value must be specified.";
} else if (!levels || levels.length < 2) {
report.levels = "An array containing at least two levels must be specified.";
} else {
flock.envelope.validate.times(envelope.times, levels, report);
flock.envelope.validate.levels(levels, report);
flock.envelope.validate.curves(envelope.curve, levels, report);
flock.envelope.validate.sustainPoint(envelope.sustainPoint, levels, report);
}
if (failOnError !== false) {
for (var errorProp in report) {
flock.fail(report[errorProp]);
}
}
return report;
};
flock.envelope.validate.times = function (times, levels, report) {
if (times.length !== levels.length - 1) {
report.times = "The envelope specification should provide one fewer time value " +
"than the number of level values. times: " + times + " levels: " + levels;
}
for (var i = 0; i < times.length; i++) {
var time = times[i];
if (isNaN(time)) {
report.times = "A NaN time value was specified at index " +
i + ". times: " + times;
}
if (time < 0) {
report.times = "All times should be positive values. times: " + times;
}
}
};
flock.envelope.validate.levels = function (levels, report) {
for (var i = 0; i < levels.length; i++) {
if (isNaN(levels[i])) {
report.levels = "A NaN level value was specified at index " +
i + ". levels: " + levels;
}
}
};
flock.envelope.validate.curves = function (curve, levels, report) {
if (!curve) {
return report;
}
if (flock.isIterable(curve)) {
if (curve.length !== levels.length - 1) {
report.curve = "When curve is specified as an array, " +
"there should be one fewer curve value " +
"than the number of level values. curve: " +
curve + " levels: " + levels;
}
fluid.each(curve, function (curveName) {
var lineGen = flock.lineGenerator(curveName);
if (!lineGen) {
report.curve = "'" + curveName + "' is not a valid curve type. curve: " + curve;
}
});
}
var lineGen = flock.lineGenerator(curve);
if (!lineGen) {
report.curve = "'" + curve + "' is not a valid curve type.";
}
};
flock.envelope.validate.sustainPoint = function (sustainPoint, levels, report) {
if (sustainPoint < 0 || sustainPoint >= levels.length) {
report.sustainPoint = "The specified sustainPoint index is out range for the levels array. " +
"sustainPoint: " + sustainPoint + " levels: " + levels;
}
};
/**
* Takes an envelope specification and expands it,
* producing an envelope object.
*/
flock.envelope.expand = function (envSpec) {
var envelope = typeof envSpec === "string" ? fluid.invokeGlobalFunction(envSpec) :
envSpec.type ? fluid.invokeGlobalFunction(envSpec.type, [envSpec]) : envSpec;
// Catch a common naming mistake and alias it to the correct name.
if (envelope.curves && !envelope.curve) {
envelope.curve = envelope.curves;
}
if (!flock.isIterable(envelope.curve)) {
var numCurves = envelope.levels.length - 1;
envelope.curve = flock.fillBufferWithValue(new Array(numCurves), envelope.curve);
}
flock.envelope.validate(envelope, true);
return envelope;
};
// TODO: Unit tests!
// e.g. flock.fillBufferWithLine("linear", new Float32Array(64), 0, 1);
flock.fillBufferWithLine = function (type, buffer, start, end, startIdx, endIdx) {
startIdx = startIdx === undefined ? 0 : startIdx;
endIdx = endIdx === undefined ? buffer.length : endIdx;
var numSamps = endIdx - startIdx,
m = flock.fillBufferWithLine.singletonModel;
m.unscaledValue = start;
m.destination = end;
m.numSegmentSamps = numSamps - 1;
if (typeof type === "number") {
m.currentCurve = type;
type = "curve";
}
var generator = flock.line[type];
if (!generator) {
flock.fail("No line generator could be found for type " + type);
}
generator.init(m);
return generator.gen(numSamps, startIdx, buffer, m);
};
// Unsupported API.
flock.fillBufferWithLine.singletonModel = {
unscaledValue: 0.0,
value: 0.0,
destination: 1.0
};
flock.lineGenerator = function (lineType) {
var type = typeof lineType;
return type === "string" ? flock.line[lineType] :
type === "number" ? flock.line.curve : flock.line.linear;
};
/****************************
* Line Generator Functions *
****************************/
flock.line = {
constant: {
init: function (m) {
m.stepSize = 0;
},
gen: function (numSamps, idx, buffer, m) {
var val = m.unscaledValue;
for (var i = idx; i < numSamps + idx; i++) {
buffer[i] = val;
}
return buffer;
}
},
step: {
init: function (m) {
m.arrived = false;
},
gen: function (numSamps, idx, buffer, m) {
for (var i = idx; i < numSamps + idx; i++) {
buffer[i] = m.unscaledValue;
if (!m.arrived) {
m.arrived = true;
m.unscaledValue = m.destination;
}
}
return buffer;
}
},
linear: {
init: function (m) {
m.stepSize = (m.destination - m.unscaledValue) / m.numSegmentSamps;
},
gen: function (numSamps, idx, buffer, m) {
var val = m.unscaledValue,
stepSize = m.stepSize;
for (var i = idx; i < numSamps + idx; i++) {
buffer[i] = val;
val += stepSize;
}
m.unscaledValue = val;
return buffer;
}
},
exponential: {
init: function (m) {
if (m.unscaledValue === 0) {
m.unscaledValue = 0.0000000000000001;
}
m.stepSize = m.numSegmentSamps === 0 ? 0 :
Math.pow(m.destination / m.unscaledValue, 1.0 / m.numSegmentSamps);
},
gen: function (numSamps, idx, buffer, m) {
var val = m.unscaledValue,
stepSize = m.stepSize;
for (var i = idx; i < numSamps + idx; i++) {
buffer[i] = val;
val *= stepSize;
}
m.unscaledValue = val;
m.stepSize = stepSize;
return buffer;
}
},
curve: {
init: function (m) {
if (Math.abs(m.currentCurve) < 0.001) {
// A curve value this small might as well be linear.
return flock.line.linear.init(m);
} else {
var a1 = (m.destination - m.unscaledValue) / (1.0 - Math.exp(m.currentCurve));
m.a2 = m.unscaledValue + a1;
m.b1 = a1;
m.stepSize = Math.exp(m.currentCurve / m.numSegmentSamps);
}
},
gen: function (numSamps, idx, buffer, m) {
var val = m.unscaledValue,
b1 = m.b1;
for (var i = idx; i < numSamps + idx; i++) {
buffer[i] = val;
b1 *= m.stepSize;
val = m.a2 - b1;
}
m.unscaledValue = val;
m.b1 = b1;
return buffer;
}
},
sin: {
init: function (m) {
var w = Math.PI / m.numSegmentSamps;
m.a2 = (m.destination + m.unscaledValue) * 0.5;
m.b1 = 2.0 * Math.cos(w);
m.y1 = (m.destination - m.unscaledValue) * 0.5;
m.y2 = m.y1 * Math.sin(flock.HALFPI - w);
m.unscaledValue = m.a2 - m.y1;
},
gen: function (numSamps, idx, buffer, m) {
var val = m.unscaledValue,
y1 = m.y1,
y2 = m.y2,
y0;
for (var i = idx; i < numSamps + idx; i++) {
buffer[i] = val;
y0 = m.b1 * y1 - y2;
val = m.a2 - y0;
y2 = y1;
y1 = y0;
}
m.unscaledValue = val;
m.y1 = y1;
m.y2 = y2;
return buffer;
}
},
welsh: {
init: function (m) {
var w = flock.HALFPI / m.numSegmentSamps,
cosW = Math.cos(w);
m.b1 = 2.0 * cosW;
if (m.destination >= m.unscaledValue) {
m.a2 = m.unscaledValue;
m.y1 = 0.0;
m.y2 = -Math.sin(w) * (m.destination - m.unscaledValue);
} else {
m.a2 = m.destination;
m.y1 = m.unscaledValue - m.destination;
m.y2 = cosW * (m.unscaledValue - m.destination);
}
m.unscaledValue = m.a2 + m.y1;
},
gen: function (numSamps, idx, buffer, m) {
var val = m.unscaledValue,
y1 = m.y1,
y2 = m.y2,
y0;
for (var i = idx; i < numSamps + idx; i++) {
buffer[i] = val;
y0 = m.b1 * y1 - y2;
y2 = y1;
y1 = y0;
val = m.a2 + y0;
}
m.unscaledValue = val;
m.y1 = y1;
m.y2 = y2;
return buffer;
}
},
squared: {
init: function (m) {
m.y1 = Math.sqrt(m.unscaledValue);
m.y2 = Math.sqrt(m.destination);
m.stepSize = (m.y2 - m.y1) / m.numSegmentSamps;
},
gen: function (numSamps, idx, buffer, m) {
var val = m.unscaledValue,
y1 = m.y1;
for (var i = idx; i < numSamps + idx; i++) {
buffer[i] = val;
y1 += m.stepSize;
val = y1 * y1;
}
m.y1 = y1;
m.unscaledValue = val;
return buffer;
}
},
cubed: {
init: function (m) {
var third = 0.3333333333333333;
m.y1 = Math.pow(m.unscaledValue, third);
m.y2 = Math.pow(m.destination, third);
m.stepSize = (m.y2 - m.y1) / m.numSegmentSamps;
},
gen: function (numSamps, idx, buffer, m) {
var val = m.unscaledValue,
y1 = m.y1;
for (var i = idx; i < numSamps + idx; i++) {
buffer[i] = val;
y1 += m.stepSize;
val = y1 * y1 * y1;
}
m.y1 = y1;
m.unscaledValue = val;
return buffer;
}
}
};
/****************************
* Envelope Unit Generators *
****************************/
flock.ugen.line = function (inputs, output, options) {
var that = flock.ugen(inputs, output, options);
that.gen = function (numSamps) {
var m = that.model,
stepSize = m.stepSize,
numSteps = m.numSteps,
numLevelVals = numSteps >= numSamps ? numSamps : numSteps,
numEndVals = numSamps - numLevelVals,
level = m.level,
out = that.output,
i;
for (i = 0; i < numLevelVals; i++) {
out[i] = level;
numSteps--;
level += stepSize;
}
// TODO: Implement a more efficient gen algorithm when the line has finished.
if (numEndVals > 0) {
for (i = 0; i < numEndVals; i++) {
out[i] = level;
}
}
// TODO: "level" should be deprecated in favour of "unscaledValue"
m.level = m.unscaledValue = level;
m.numSteps = numSteps;
that.mulAdd(numSamps);
m.value = flock.ugen.lastOutputValue(numSamps, out);
};
that.onInputChanged = function () {
var m = that.model;
// Any change in input value will restart the line.
m.start = that.inputs.start.output[0];
m.end = that.inputs.end.output[0];
m.numSteps = Math.round(that.inputs.duration.output[0] * m.sampleRate);
if (m.numSteps === 0) {
m.stepSize = 0.0;
m.level = m.end;
} else {
m.stepSize = (m.end - m.start) / m.numSteps;
m.level = m.start;
}
flock.onMulAddInputChanged(that);
};
that.onInputChanged();
return that;
};
flock.ugenDefaults("flock.ugen.line", {
rate: "control",
inputs: {
start: 0.0,
end: 1.0,
duration: 1.0,
mul: null,
add: null
},
ugenOptions: {
model: {
start: 0.0,
end: 1.0,
numSteps: 0,
stepSize: 0,
level: 0.0,
unscaledValue: 0.0,
value: 0.0
}
}
});
flock.ugen.xLine = function (inputs, output, options) {
var that = flock.ugen(inputs, output, options);
that.gen = function (numSamps) {
var m = that.model,
multiplier = m.multiplier,
numSteps = m.numSteps,
numLevelVals = numSteps >= numSamps ? numSamps : numSteps,
numEndVals = numSamps - numLevelVals,
level = m.level,
out = that.output,
i;
for (i = 0; i < numLevelVals; i++) {
out[i] = level;
numSteps--;
level *= multiplier;
}
// TODO: Implement a more efficient gen algorithm when the line has finished.
if (numEndVals > 0) {
for (i = 0; i < numEndVals; i++) {
out[i] = level;
}
}
// TODO: "level" should be deprecated in favour of "unscaledValue"
m.level = m.unscaledValue = level;
m.numSteps = numSteps;
that.mulAdd(numSamps);
m.value = flock.ugen.lastOutputValue(numSamps, out);
};
that.onInputChanged = function () {
var m = that.model;
flock.onMulAddInputChanged(that);
// Any change in input value will restart the line.
m.start = that.inputs.start.output[0];
if (m.start === 0.0) {
m.start = 1e-101; // Guard against divide by zero.
}
m.end = that.inputs.end.output[0];
m.numSteps = Math.round(that.inputs.duration.output[0] * m.sampleRate);
m.multiplier = Math.pow(m.end / m.start, 1.0 / m.numSteps);
m.level = m.start;
};
that.onInputChanged();
return that;
};
flock.ugenDefaults("flock.ugen.xLine", {
rate: "control",
inputs: {
start: 0.0,
end: 1.0,
duration: 1.0,
mul: null,
add: null
},
ugenOptions: {
model: {
start: 0.0,
end: 1.0,
numSteps: 0,
multiplier: 0,
level: 0.0,
unscaledValue: 0.0,
value: 0.0
}
}
});
flock.ugen.asr = function (inputs, output, options) {
var that = flock.ugen(inputs, output, options);
that.gen = function (numSamps) {
var m = that.model,
out = that.output,
prevGate = m.previousGate,
gate = that.inputs.gate.output[0],
level = m.level,
stage = m.stage,
currentStep = stage.currentStep,
stepInc = stage.stepInc,
numSteps = stage.numSteps,
targetLevel = m.targetLevel,
stepsNeedRecalc = false,
stageTime,
i;
// Recalculate the step state if necessary.
if (prevGate <= 0 && gate > 0) {
// Starting a new attack stage.
targetLevel = that.inputs.sustain.output[0];
stageTime = that.inputs.attack.output[0];
stepsNeedRecalc = true;
} else if (prevGate >= 0 && gate <= 0 && currentStep >= numSteps) {
// Starting a new release stage.
targetLevel = that.inputs.start.output[0];
stageTime = that.inputs.release.output[0];
stepsNeedRecalc = true;
}
// TODO: Can we get rid of this extra branch without introducing code duplication?
if (stepsNeedRecalc) {
numSteps = Math.round(stageTime * m.sampleRate);
stepInc = numSteps > 0 ? (targetLevel - level) / numSteps : 0;
currentStep = 0;
if (numSteps < 1) {
level = targetLevel;
}
}
// Output the the envelope's sample data.
for (i = 0; i < numSamps; i++) {
out[i] = level;
currentStep++;
// Hold the last value if the stage is complete, otherwise increment.
level = currentStep < numSteps ?
level + stepInc : currentStep === numSteps ?
targetLevel : level;
}
// Store instance state.
// TODO: "level" should be deprecated in favour of "unscaledValue"
m.level = m.unscaledValue = level;
m.targetLevel = targetLevel;
m.previousGate = gate;
stage.currentStep = currentStep;
stage.stepInc = stepInc;
stage.numSteps = numSteps;
that.mulAdd(numSamps);
m.value = flock.ugen.lastOutputValue(numSamps, out);
};
that.init = function () {
var m = that.model;
m.level = m.unscaledValue = that.inputs.start.output[0];
m.targetLevel = that.inputs.sustain.output[0];
that.onInputChanged();
};
that.init();
return that;
};
flock.ugenDefaults("flock.ugen.asr", {
rate: "control",
inputs: {
start: 0.0,
attack: 0.01,
sustain: 1.0,
release: 1.0,
gate: 0.0,
mul: null,
add: null
},
ugenOptions: {
model: {
level: 0.0,
targetLevel: 0.0,
previousGate: 0.0,
unscaledValue: 0.0,
value: 0.0,
stage: {
currentStep: 0,
stepInc: 0,
numSteps: 0
}
}
}
});
// Included for backwards compatibility.
// The name "flock.ugen.env.simpleASR is deprecated.
// Please use flock.ugen.asr instead.
// This will be removed in Flocking 0.3.0.
flock.copyUGenDefinition("flock.ugen.asr", "flock.ugen.env.simpleASR");
flock.ugen.envGen = function (inputs, output, options) {
var that = flock.ugen(inputs, output, options);
that.krGen = function (numSamps) {
var m = that.model,
out = that.output,
inputs = that.inputs,
gate = inputs.gate.output[0],
timeScale = inputs.timeScale.output[0],
i = 0,
sampsToGen;
flock.ugen.envGen.checkGate(that, gate, timeScale);
while (i < numSamps) {
sampsToGen = Math.min(numSamps - i, m.numSegmentSamps);
that.lineGen.gen(sampsToGen, i, out, m);
i += sampsToGen;
m.numSegmentSamps -= sampsToGen;
if (m.numSegmentSamps <= 0) {
flock.ugen.envGen.nextStage(that, timeScale);
}
}
that.mulAdd(numSamps);
m.value = flock.ugen.lastOutputValue(numSamps, out);
};
that.arGen = function (numSamps) {
var m = that.model,
out = that.output,
inputs = that.inputs,
gate = inputs.gate.output,
timeScale = inputs.timeScale.output[0],
i;
for (i = 0; i < numSamps; i++) {
flock.ugen.envGen.checkGate(that, gate[i], timeScale);
that.lineGen.gen(1, i, out, m);
m.numSegmentSamps--;
if (m.numSegmentSamps <= 0) {
flock.ugen.envGen.nextStage(that, timeScale);
}
}
that.mulAdd(numSamps);
m.value = flock.ugen.lastOutputValue(numSamps, out);
};
that.onInputChanged = function (inputName) {
if (!inputName || inputName === "envelope") {
that.envelope = flock.ugen.envGen.initEnvelope(that, that.inputs.envelope);
}
if (!inputName || inputName === "gate") {
that.gen = that.inputs.gate.rate === flock.rates.AUDIO ? that.arGen : that.krGen;
}
flock.onMulAddInputChanged(that);
};
that.onInputChanged();
return that;
};
// Unsupported API.
flock.ugen.envGen.initEnvelope = function (that, envSpec) {
var m = that.model,
envelope = flock.envelope.expand(envSpec);
m.stage = 0;
m.numStages = envelope.times.length;
that.lineGen = flock.line.constant;
flock.ugen.envGen.lineGenForStage(that.inputs.timeScale.output[0], envelope, m);
m.unscaledValue = envelope.levels[m.stage];
return envelope;
};
// Unsupported API.
flock.ugen.envGen.checkGate = function (that, gate, timeScale) {
var m = that.model,
envelope = that.envelope;
if (gate !== m.previousGate) {
if (gate > 0.0 && m.previousGate <= 0.0) {
// Gate has opened.
m.stage = 1;
that.lineGen = flock.ugen.envGen.lineGenForStage(timeScale, envelope, m);
} else if (gate <= 0.0 && m.previousGate > 0) {
// Gate has closed.
m.stage = m.numStages;
that.lineGen = flock.ugen.envGen.lineGenForStage(timeScale, envelope, m);
}
}
m.previousGate = gate;
};
// Unsupported API.
flock.ugen.envGen.nextStage = function (that, timeScale) {
var m = that.model,
envelope = that.envelope;
// We've hit the end of the current transition.
if (m.stage === envelope.sustainPoint) {
// We're at the sustain point.
// Output a constant value.
that.lineGen = flock.line.constant;
m.numSegmentSamps = Infinity;
m.destination = m.unscaledValue;
} else {
// Move on to the next breakpoint stage.
m.stage++;
that.lineGen = flock.ugen.envGen.lineGenForStage(timeScale, envelope, m);
}
};
// Unsupported API.
flock.ugen.envGen.setupStage = function (timeScale, envelope, m) {
var dest = envelope.levels[m.stage],
dur,
durSamps;
if (m.stage === 0 || m.stage > m.numStages) {
durSamps = Infinity;
} else {
dur = envelope.times[m.stage - 1] * timeScale;
durSamps = Math.max(1, Math.round(dur * m.sampleRate));
}
m.numSegmentSamps = durSamps;
m.destination = dest;
};
// Unsupported API.
flock.ugen.envGen.lineGenForStage = function (timeScale, envelope, m) {
var curve = envelope.curve,
lineGen,
curveValue;
if (m.stage === 0 || m.stage > m.numStages) {
lineGen = flock.line.constant;
} else {
curveValue = curve[m.stage - 1];
m.currentCurve = curveValue;
lineGen = flock.lineGenerator(curveValue);
}
flock.ugen.envGen.setupStage(timeScale, envelope, m);
lineGen.init(m);
return lineGen;
};
flock.ugenDefaults("flock.ugen.envGen", {
rate: "audio",
inputs: {
envelope: "flock.envelope.adsr",
gate: 0.0,
timeScale: 1.0, // Scales the durations of the segments.
// Timescale is control-rate (or lower) only.
mul: null, // Scales the levels of the breakpoints;
// (this is equivalent to SC's levelScale parameter)
add: null // Offsets the levels of the breakpoints.
// (this is equivalent to SC's levelBias)
},
ugenOptions: {
model: {
previousGate: 0.0,
stepSize: 0.0,
destination: 0.0,
numSegmentSamps: 1.0,
unscaledValue: 0.0,
value: 0.0,
stage: 0.0,
numStages: 0.0
}
}
});
/**
* Loops through a linear ramp from start to end, incrementing the output by step.
* Equivalent to SuperCollider's or CSound's Phasor unit generator.
*
* Inputs:
* start: the value to start ramping from
* end: the value to ramp to
* step: the value to increment per sample
* reset: the value to return to when the loop is reset by a trigger signal
* trigger: a trigger signal that, when it cross the zero line, will reset the loop back to the reset point
*/
flock.ugen.phasor = function (inputs, output, options) {
var that = flock.ugen(inputs, output, options);
that.gen = function (numSamps) {
var m = that.model,
inputs = that.inputs,
out = that.output,
step = inputs.step.output,
trig = inputs.trigger.output,
i,
j,
k;
// TODO: Add sample priming to the ugen graph to remove this conditional.
if (m.unscaledValue === undefined) {
m.unscaledValue = inputs.start.output[0];
}
for (i = 0, j = 0, k = 0; i < numSamps; i++, j += m.strides.trigger, k += m.strides.step) {
if ((trig[j] > 0.0 && m.prevTrig <= 0.0)) {
m.unscaledValue = inputs.reset.output[0];
}
m.prevTrig = trig[j];
if (m.unscaledValue >= inputs.end.output[0]) {
m.unscaledValue = inputs.start.output[0];
}
out[i] = m.unscaledValue;
m.unscaledValue += step[k]; // TODO: Model out of sync with last output sample.
}
that.mulAdd(numSamps);
m.value = flock.ugen.lastOutputValue(numSamps, out);
};
that.onInputChanged();
return that;
};
flock.ugenDefaults("flock.ugen.phasor", {
rate: "control",
inputs: {
start: 0.0,
end: 1.0,
reset: 0.0,
step: 0.1,
trigger: 0.0,
mul: null,
add: null
},
ugenOptions: {
model: {
unscaledValue: undefined,
value: 0.0
},
strideInputs: [
"trigger",
"step"
]
}
});
}());