From 9f0bd6f08de07960e2c47a0d4b65b416c68c3f8a Mon Sep 17 00:00:00 2001 From: Joe Wreschnig Date: Sat, 25 Apr 2015 00:30:36 +0200 Subject: [PATCH] Special-case yuu.Instrument for single-sample sounds. Simplify Envelope as it no longer needs to special-case optimize constants itself; FastInstruments have no envelopes at all. --- src/yuu/audio.js | 135 ++++++++++++++++++++++++----------------------- 1 file changed, 69 insertions(+), 66 deletions(-) diff --git a/src/yuu/audio.js b/src/yuu/audio.js index 7a66e70..d127fb8 100644 --- a/src/yuu/audio.js +++ b/src/yuu/audio.js @@ -141,47 +141,31 @@ } 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); } }); @@ -199,7 +183,7 @@ 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; }, @@ -218,13 +202,43 @@ } }); - 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( @@ -236,11 +250,7 @@ }, 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; @@ -255,7 +265,7 @@ 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( @@ -270,7 +280,7 @@ 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); @@ -290,22 +300,15 @@ 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 }, -- 2.30.2