flocking
Version:
Creative audio synthesis for the Web
400 lines (343 loc) • 12 kB
JavaScript
/*
* Flocking Oscillator Unit Generators
* https://github.com/continuing-creativity/flocking
*
* Copyright 2011-2015, 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";
flock.ugen.osc = function (inputs, output, options) {
var that = flock.ugen(inputs, output, options);
that.gen = function (numSamps) {
var m = that.model,
inputs = that.inputs,
freq = inputs.freq.output,
phaseOffset = inputs.phase.output,
table = inputs.table,
tableLen = m.tableLen,
tableIncHz = m.tableIncHz,
tableIncRad = m.tableIncRad,
out = that.output,
phase = m.phase,
i,
j,
k,
idx,
val;
for (i = 0, j = 0, k = 0; i < numSamps; i++, j += m.strides.phase, k += m.strides.freq) {
idx = phase + phaseOffset[j] * tableIncRad;
if (idx >= tableLen) {
idx -= tableLen;
} else if (idx < 0) {
idx += tableLen;
}
out[i] = val = that.interpolate(idx, table);
phase += freq[k] * tableIncHz;
if (phase >= tableLen) {
phase -= tableLen;
} else if (phase < 0) {
phase += tableLen;
}
}
m.phase = phase;
m.unscaledValue = val;
that.mulAdd(numSamps);
m.value = flock.ugen.lastOutputValue(numSamps, out);
};
that.onInputChanged = function (inputName) {
flock.ugen.osc.onInputChanged(that);
// Precalculate table-related values.
if (!inputName || inputName === "table") {
var m = that.model,
table = that.inputs.table;
if (table.length < 1) {
table = that.inputs.table = flock.ugen.osc.emptyTable;
}
m.tableLen = table.length;
m.tableIncHz = m.tableLen / m.sampleRate;
m.tableIncRad = m.tableLen / flock.TWOPI;
}
};
that.onInputChanged();
return that;
};
flock.ugen.osc.emptyTable = new Float32Array([0, 0, 0]);
flock.ugen.osc.onInputChanged = function (that) {
that.calculateStrides();
flock.onMulAddInputChanged(that);
};
flock.ugenDefaults("flock.ugen.osc", {
rate: "audio",
inputs: {
freq: 440.0,
phase: 0.0,
table: [],
mul: null,
add: null
},
ugenOptions: {
interpolation: "linear",
model: {
phase: 0.0,
unscaledValue: 0.0,
value: 0.0
},
strideInputs: [
"freq",
"phase"
]
},
tableSize: 8192
});
flock.ugen.osc.define = function (name, tableFillFn) {
var lastSegIdx = name.lastIndexOf("."),
namespace = name.substring(0, lastSegIdx),
oscName = name.substring(lastSegIdx + 1),
namespaceObj = flock.get(namespace);
namespaceObj[oscName] = function (inputs, output, options) {
// TODO: Awkward options pre-merging. Refactor osc API.
var defaults = flock.ugenDefaults("flock.ugen.osc"),
merged = fluid.merge(null, defaults, options),
s = merged.tableSize;
inputs.table = flock.fillTable(s, tableFillFn);
return flock.ugen.osc(inputs, output, options);
};
flock.ugenDefaults(name, flock.ugenDefaults("flock.ugen.osc"));
};
flock.ugen.osc.define("flock.ugen.sinOsc", flock.tableGenerators.sin);
flock.ugen.osc.define("flock.ugen.triOsc", flock.tableGenerators.tri);
flock.ugen.osc.define("flock.ugen.sawOsc", flock.tableGenerators.saw);
flock.ugen.osc.define("flock.ugen.squareOsc", flock.tableGenerators.square);
flock.ugen.sin = function (inputs, output, options) {
var that = flock.ugen(inputs, output, options);
that.gen = function (numSamps) {
var m = that.model,
freq = that.inputs.freq.output,
phaseOffset = that.inputs.phase.output,
out = that.output,
phase = m.phase,
sampleRate = m.sampleRate,
i,
j,
k,
val;
for (i = 0, j = 0, k = 0; i < numSamps; i++, j += m.strides.phase, k += m.strides.freq) {
out[i] = val = Math.sin(phase + phaseOffset[j]);
phase += freq[k] / sampleRate * flock.TWOPI;
}
m.phase = phase;
m.unscaledValue = val;
that.mulAdd(numSamps);
m.value = flock.ugen.lastOutputValue(numSamps, out);
};
that.onInputChanged = function () {
flock.ugen.osc.onInputChanged(that);
};
that.onInputChanged();
return that;
};
flock.ugenDefaults("flock.ugen.sin", {
rate: "audio",
inputs: {
freq: 440.0,
phase: 0.0,
mul: null,
add: null
},
ugenOptions: {
model: {
phase: 0.0,
unscaledValue: 0.0,
value: 0.0
},
strideInputs: [
"freq",
"phase"
]
}
});
flock.ugen.lfSaw = function (inputs, output, options) {
var that = flock.ugen(inputs, output, options);
that.gen = function (numSamps) {
var m = that.model,
freq = that.inputs.freq.output,
out = that.output,
scale = m.scale,
phaseOffset = that.inputs.phase.output[0], // Phase is control rate
phase = m.phase, // TODO: Prime synth graph on instantiation.
i,
j,
val;
for (i = 0, j = 0; i < numSamps; i++, j += m.strides.freq) {
out[i] = val = phase + phaseOffset;
phase += freq[j] * scale;
if (phase >= 1.0) {
phase -= 2.0;
} else if (phase <= -1.0) {
phase += 2.0;
}
}
m.phase = phase;
m.unscaledValue = val;
that.mulAdd(numSamps);
m.value = flock.ugen.lastOutputValue(numSamps, out);
};
that.onInputChanged = function () {
var m = that.model;
m.freqInc = that.inputs.freq.rate === flock.rates.AUDIO ? 1 : 0;
m.phase = 0.0;
that.calculateStrides();
flock.onMulAddInputChanged(that);
};
that.init = function () {
that.model.scale = 2 * (1 / that.options.sampleRate);
that.onInputChanged();
};
that.init();
return that;
};
flock.ugenDefaults("flock.ugen.lfSaw", {
rate: "audio",
inputs: {
freq: 440,
phase: 0.0,
mul: null,
add: null
},
ugenOptions: {
model: {
phase: 0.0,
freqInc: 1,
unscaledValue: 0.0,
value: 0.0
},
strideInputs: ["freq"]
}
});
flock.ugen.lfPulse = function (inputs, output, options) {
var that = flock.ugen(inputs, output, options);
that.gen = function (numSamps) {
var inputs = that.inputs,
m = that.model,
freq = inputs.freq.output,
freqInc = m.freqInc,
width = inputs.width.output[0], // TODO: Are we handling width correctly here?
out = that.output,
scale = m.scale,
phase = m.phase !== undefined ? m.phase : inputs.phase.output[0], // TODO: Unnecessary if we knew the synth graph had been primed.
i,
j,
val;
for (i = 0, j = 0; i < numSamps; i++, j += freqInc) {
if (phase >= 1.0) {
phase -= 1.0;
out[i] = val = width < 0.5 ? 1.0 : -1.0;
} else {
out[i] = val = phase < width ? 1.0 : -1.0;
}
phase += freq[j] * scale;
}
m.phase = phase;
m.unscaledValue = val;
that.mulAdd(numSamps);
m.value = flock.ugen.lastOutputValue(numSamps, out);
};
that.onInputChanged = function () {
that.model.freqInc = that.inputs.freq.rate === flock.rates.AUDIO ? 1 : 0;
flock.onMulAddInputChanged(that);
};
that.init = function () {
that.model.scale = 1 / that.options.sampleRate;
that.onInputChanged();
};
that.init();
return that;
};
flock.ugenDefaults("flock.ugen.lfPulse", {
rate: "audio",
inputs: {
freq: 440,
phase: 0.0,
width: 0.5,
mul: null,
add: null
},
ugenOptions: {
model: {
phase: 0.0,
freqInc: 1,
unscaledValue: 0.0,
value: 0.0
}
}
});
flock.ugen.impulse = function (inputs, output, options) {
var that = flock.ugen(inputs, output, options);
that.gen = function (numSamps) {
var inputs = that.inputs,
m = that.model,
out = that.output,
freq = inputs.freq.output,
freqInc = m.strides.freq,
phaseOffset = inputs.phase.output[0],
phase = m.phase,
scale = m.scale,
i,
j,
val;
phase += phaseOffset;
for (i = 0, j = 0; i < numSamps; i++, j += freqInc) {
if (phase >= 1.0) {
phase -= 1.0;
val = 1.0;
} else {
val = 0.0;
}
out[i] = val;
phase += freq[j] * scale;
}
m.phase = phase - phaseOffset;
m.unscaledValue = val;
that.mulAdd(numSamps);
m.value = flock.ugen.lastOutputValue(numSamps, out);
};
that.onInputChanged = function () {
that.calculateStrides();
flock.onMulAddInputChanged(that);
};
that.init = function () {
that.model.scale = 1.0 / that.model.sampleRate;
that.onInputChanged();
};
that.init();
return that;
};
flock.ugenDefaults("flock.ugen.impulse", {
rate: "audio",
inputs: {
freq: 440,
phase: 0.0,
mul: null,
add: null
},
ugenOptions: {
model: {
phase: 0.0,
scale: 0.0,
unscaledValue: 0.0,
value: 0.0
},
strideInputs: ["freq"]
}
});
}());