}
var Envelope = yuu.Envelope = yT({
- constructor: yf.argcd(
- function (pairs) {
- Envelope.call(this, pairs, 1);
- },
- function (pairs, scale) {
- pairs = pairs || { "0": 1, "100%": 1 };
- this.ts = Object.keys(pairs);
- this.vs = yf.map.call(pairs, yf.getter, this.ts);
- this.scale = scale;
- var a = 0, b = 0;
- var unlimited = false;
- yf.each(function (t) {
- if (+t) {
- a = Math.max(+t, a);
- b = Math.min(+t, b);
- }
- unlimited = unlimited || (t[t.length - 1] === "%");
- }, this.ts);
- this.minDuration = a - b;
- this.maxDuration = (unlimited || a === b)
- ? Infinity
- : this.minDuration;
- var vMin = Math.min.apply(Math, this.vs);
- var vMax = Math.max.apply(Math, this.vs);
- this.constant = vMin === vMax && this.vs[0] * this.scale;
- }
- ),
+ constructor: function (timeline) {
+ timeline = timeline || { 0: 1 };
+ this.ts = Object.keys(timeline);
+ this.vs = yf.map.call(timeline, yf.getter, this.ts);
+ var ts = this.ts.filter(isFinite);
+ this.duration = (Math.max.apply(Math, ts)
+ - Math.min.apply(Math, ts));
+ this.unlimited = !this.duration || ts.length !== this.ts.length;
+ },
+
+ clampDuration: function (duration) {
+ return this.unlimited
+ ? Math.max(duration, this.duration)
+ : this.duration;
+ },
schedule: function (param, t0, scale, duration) {
- if (this.constant !== false) {
- param.setValueAtTime(scale * this.constant, t0);
- } else {
- yf.each.call(this, function (s, v) {
- v = v * scale * this.scale;
- var t = t0 + applyMod(s, duration);
- if (t === t0)
- param.setValueAtTime(v, t);
- else
- param.linearRampToValueAtTime(v, t);
- }, this.ts, this.vs);
- }
+ yf.each(function (s, v) {
+ v = v * scale;
+ var t = t0 + applyMod(s, duration);
+ if (t === t0)
+ param.setValueAtTime(v, t);
+ else
+ param.linearRampToValueAtTime(v, t);
+ }, this.ts, this.vs);
}
});
yuu.Modulator = yT({
constructor: function (dfn) {
- this.envelope = new yuu.Envelope(dfn.envelope);
+ this.envelope = new Envelope(dfn.envelope);
this.frequency = dfn.frequency;
this.index = dfn.index || 1.0;
},
}
});
- yuu.Instrument = yT({
+ var BaseInstrument = yT({
+ play: yf.argcd(
+ function () {
+ return this.play(null, 0, 0, 1, 1);
+ },
+ function (ctx, t, freq, amp, duration) {
+ ctx = ctx || yuu.audio;
+ t = t || ctx.currentTime;
+ var g = this.createSound(ctx, t, freq, amp, duration);
+ g.connect(ctx.destination);
+ return g;
+ }
+ )
+ });
+
+ var FastInstrument = yT(BaseInstrument, {
constructor: function (dfn) {
- if (yf.isString(dfn)) {
- var sampleName = dfn;
- dfn = { sample: {} };
- dfn.sample[sampleName] = {};
+ this.sample = new yuu.AudioSample(dfn);
+ this.ready = this.sample.ready.then(yf.K(this));
+ },
+
+ createSound: function (ctx, t0, fundamental, amplitude, duration) {
+ var src = ctx.createBufferSource(this.sample);
+ var ret = src;
+ src.start(t0, 0);
+ src.stop(t0 + duration);
+ if (amplitude !== 1.0) {
+ ret = ctx.createGain();
+ src.connect(ret);
+ ret.gain.value = amplitude;
}
+ return ret;
+ }
+ });
+
+ var Instrument = yT(BaseInstrument, {
+ constructor: function (dfn) {
this.envelope = new yuu.Envelope(dfn.envelope);
this.frequency = dfn.frequency || (dfn.sample ? {} : { "x1": 1.0 });
this.modulator = yf.map(
},
createSound: function (ctx, t0, fundamental, amplitude, duration) {
- // TODO: In the case of exactly one sample with a constant
- // envelope, optimize out the extra gain node.
- duration = yf.clamp(duration || 0,
- this.envelope.minDuration,
- this.envelope.maxDuration);
+ duration = this.envelope.clampDuration(duration || 0);
var ret = ctx.createGain();
var dst = ret;
ctx, t0, fundamental, duration);
}, this.modulator);
- yf.ipairs.call(this, function (name, params) {
+ yf.ipairs(function (name, params) {
var src = ctx.createBufferSource(name);
src.loop = params.loop || false;
src.playbackRate.value = applyMod(
src.connect(dst);
}, this.sample);
- yf.ipairs.call(this, function (mfreq, mamp) {
+ yf.ipairs(function (mfreq, mamp) {
var osc = ctx.createOscillator();
osc.frequency.value = applyMod(mfreq, fundamental);
osc.start(t0);
ret.gain.value = 0;
this.envelope.schedule(ret.gain, t0, amplitude, duration);
return ret;
- },
-
- play: yf.argcd(
- function () {
- return this.play(null, 0, 0, 1, 1);
- },
- function (ctx, t, freq, amp, duration) {
- ctx = ctx || yuu.audio;
- t = t || ctx.currentTime;
- var g = this.createSound(ctx, t, freq, amp, duration);
- g.connect(ctx.destination);
- return g;
- }
- )
+ }
});
+ yuu.Instrument = function (dfn) {
+ return yf.isString(dfn)
+ ? new FastInstrument(dfn)
+ : new Instrument(dfn);
+ };
+
yuu.Instruments = yf.mapValues(yf.new_(yuu.Instrument), {
SINE: {
envelope: { "0": 0, "0.016": 1, "-0.016": 1, "100%": 0 },