X-Git-Url: https://git.yukkurigames.com/?p=pwl6.git;a=blobdiff_plain;f=src%2Fyuu%2Faudio.js;h=cc00cf7424ada80841e8b8b32f160090804d6c56;hp=fb352aeb9a7686beb2e4d924ab030eadc3c66cac;hb=5deeff54e19003703e7ffb3ee1d3406c3092ebf9;hpb=1b36f379858d1f88eb709ebdd59465957081c455 diff --git a/src/yuu/audio.js b/src/yuu/audio.js index fb352ae..cc00cf7 100644 --- a/src/yuu/audio.js +++ b/src/yuu/audio.js @@ -29,7 +29,6 @@ this._masterVolume.gain.value = 0.5; this._musicVolume.gain.value = 0.5; - this._bufferCache = {}; this._mute = false; this._storage = null; this._volume = this._masterVolume.gain.value; @@ -90,7 +89,7 @@ currentTime: { alias: "_ctx.currentTime" }, - decodeAudioData: function (data) { + decodeAudioData: function (data, hint) { var ctx = this._ctx; try { return ctx.decodeAudioData(data); @@ -99,7 +98,8 @@ ctx.decodeAudioData(data, function (buffer) { resolve(buffer); }, function () { - reject(new Error("Error decoding audio buffer")); + reject(new Error("Error decoding audio buffer" + + (hint ? ": " + hint : ""))); }); }); } @@ -107,7 +107,9 @@ createBufferSource: function (path) { var source = this._ctx.createBufferSource(); - var sample = new yuu.AudioSample(path, this); + var sample = yf.isString(path) + ? new yuu.AudioSample(path, this) + : path; if ((source.buffer = sample.buffer) === null) { sample.ready.then(function () { source.buffer = sample.buffer; @@ -139,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); } }); @@ -189,7 +175,9 @@ var url = yuu.resourcePath(path, "sound", "wav"); this.data = null; this.ready = yuu.GET(url, { responseType: "arraybuffer" }) - .then(ctx.decodeAudioData.bind(ctx)) + .then(function (data) { + return ctx.decodeAudioData(data, url); + }) .then(yf.setter.bind(this, "buffer")) .then(yf.K(this)); } @@ -197,7 +185,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; }, @@ -216,13 +204,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( @@ -234,11 +252,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; @@ -253,7 +267,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( @@ -268,7 +282,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); @@ -288,22 +302,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 },