1 | var Synth, AudioSynth, AudioSynthInstrument;
|
---|
2 | !function(){
|
---|
3 |
|
---|
4 | var URL = window.URL || window.webkitURL;
|
---|
5 | var Blob = window.Blob;
|
---|
6 |
|
---|
7 | if(!URL || !Blob) {
|
---|
8 | throw new Error('This browser does not support AudioSynth');
|
---|
9 | }
|
---|
10 |
|
---|
11 | var _encapsulated = false;
|
---|
12 | var AudioSynthInstance = null;
|
---|
13 | var pack = function(c,arg){ return [new Uint8Array([arg, arg >> 8]), new Uint8Array([arg, arg >> 8, arg >> 16, arg >> 24])][c]; };
|
---|
14 | var setPrivateVar = function(n,v,w,e){Object.defineProperty(this,n,{value:v,writable:!!w,enumerable:!!e});};
|
---|
15 | var setPublicVar = function(n,v,w){setPrivateVar.call(this,n,v,w,true);};
|
---|
16 | AudioSynthInstrument = function AudioSynthInstrument(){this.__init__.apply(this,arguments);};
|
---|
17 | var setPriv = setPrivateVar.bind(AudioSynthInstrument.prototype);
|
---|
18 | var setPub = setPublicVar.bind(AudioSynthInstrument.prototype);
|
---|
19 | setPriv('__init__', function(a,b,c) {
|
---|
20 | if(!_encapsulated) { throw new Error('AudioSynthInstrument can only be instantiated from the createInstrument method of the AudioSynth object.'); }
|
---|
21 | setPrivateVar.call(this, '_parent', a);
|
---|
22 | setPublicVar.call(this, 'name', b);
|
---|
23 | setPrivateVar.call(this, '_soundID', c);
|
---|
24 | });
|
---|
25 | setPub('play', function(note, octave, duration) {
|
---|
26 | return this._parent.play(this._soundID, note, octave, duration);
|
---|
27 | });
|
---|
28 | setPub('generate', function(note, octave, duration) {
|
---|
29 | return this._parent.generate(this._soundID, note, octave, duration);
|
---|
30 | });
|
---|
31 | AudioSynth = function AudioSynth(){if(AudioSynthInstance instanceof AudioSynth){return AudioSynthInstance;}else{ this.__init__(); return this; }};
|
---|
32 | setPriv = setPrivateVar.bind(AudioSynth.prototype);
|
---|
33 | setPub = setPublicVar.bind(AudioSynth.prototype);
|
---|
34 | setPriv('_debug',false,true);
|
---|
35 | setPriv('_bitsPerSample',16);
|
---|
36 | setPriv('_channels',1);
|
---|
37 | setPriv('_sampleRate',44100,true);
|
---|
38 | setPub('setSampleRate', function(v) {
|
---|
39 | this._sampleRate = Math.max(Math.min(v|0,44100), 4000);
|
---|
40 | this._clearCache();
|
---|
41 | return this._sampleRate;
|
---|
42 | });
|
---|
43 | setPub('getSampleRate', function() { return this._sampleRate; });
|
---|
44 | setPriv('_volume',32768,true);
|
---|
45 | setPub('setVolume', function(v) {
|
---|
46 | v = parseFloat(v); if(isNaN(v)) { v = 0; }
|
---|
47 | v = Math.round(v*32768);
|
---|
48 | this._volume = Math.max(Math.min(v|0,32768), 0);
|
---|
49 | this._clearCache();
|
---|
50 | return this._volume;
|
---|
51 | });
|
---|
52 | setPub('getVolume', function() { return Math.round(this._volume/32768*10000)/10000; });
|
---|
53 | setPriv('_notes',{'C':261.63,'C#':277.18,'D':293.66,'D#':311.13,'E':329.63,'F':346.23,'F#':369.99,'G':392.00,'G#':415.30,'A':440.00,'A#':466.16,'B':493.88});
|
---|
54 | setPriv('_fileCache',[],true);
|
---|
55 | setPriv('_temp',{},true);
|
---|
56 | setPriv('_sounds',[],true);
|
---|
57 | setPriv('_mod',[function(i,s,f,x){return Math.sin((2 * Math.PI)*(i/s)*f+x);}]);
|
---|
58 | setPriv('_resizeCache', function() {
|
---|
59 | var f = this._fileCache;
|
---|
60 | var l = this._sounds.length;
|
---|
61 | while(f.length<l) {
|
---|
62 | var octaveList = [];
|
---|
63 | for(var i = 0; i < 8; i++) {
|
---|
64 | var noteList = {};
|
---|
65 | for(var k in this._notes) {
|
---|
66 | noteList[k] = {};
|
---|
67 | }
|
---|
68 | octaveList.push(noteList);
|
---|
69 | }
|
---|
70 | f.push(octaveList);
|
---|
71 | }
|
---|
72 | });
|
---|
73 | setPriv('_clearCache', function() {
|
---|
74 | this._fileCache = [];
|
---|
75 | this._resizeCache();
|
---|
76 | });
|
---|
77 | setPub('generate', function(sound, note, octave, duration) {
|
---|
78 | var thisSound = this._sounds[sound];
|
---|
79 | if(!thisSound) {
|
---|
80 | for(var i=0;i<this._sounds.length;i++) {
|
---|
81 | if(this._sounds[i].name==sound) {
|
---|
82 | thisSound = this._sounds[i];
|
---|
83 | sound = i;
|
---|
84 | break;
|
---|
85 | }
|
---|
86 | }
|
---|
87 | }
|
---|
88 | if(!thisSound) { throw new Error('Invalid sound or sound ID: ' + sound); }
|
---|
89 | var t = (new Date).valueOf();
|
---|
90 | this._temp = {};
|
---|
91 | octave |= 0;
|
---|
92 | octave = Math.min(8, Math.max(1, octave));
|
---|
93 | var time = !duration?2:parseFloat(duration);
|
---|
94 | if(typeof(this._notes[note])=='undefined') { throw new Error(note + ' is not a valid note.'); }
|
---|
95 | if(typeof(this._fileCache[sound][octave-1][note][time])!='undefined') {
|
---|
96 | if(this._debug) { console.log((new Date).valueOf() - t, 'ms to retrieve (cached)'); }
|
---|
97 | return this._fileCache[sound][octave-1][note][time];
|
---|
98 | } else {
|
---|
99 | var frequency = this._notes[note] * Math.pow(2,octave-4);
|
---|
100 | var sampleRate = this._sampleRate;
|
---|
101 | var volume = this._volume;
|
---|
102 | var channels = this._channels;
|
---|
103 | var bitsPerSample = this._bitsPerSample;
|
---|
104 | var attack = thisSound.attack(sampleRate, frequency, volume);
|
---|
105 | var dampen = thisSound.dampen(sampleRate, frequency, volume);
|
---|
106 | var waveFunc = thisSound.wave;
|
---|
107 | var waveBind = {modulate: this._mod, vars: this._temp};
|
---|
108 | var val = 0;
|
---|
109 | var curVol = 0;
|
---|
110 |
|
---|
111 | var data = new Uint8Array(new ArrayBuffer(Math.ceil(sampleRate * time * 2)));
|
---|
112 | var attackLen = (sampleRate * attack) | 0;
|
---|
113 | var decayLen = (sampleRate * time) | 0;
|
---|
114 |
|
---|
115 | for (var i = 0 | 0; i !== attackLen; i++) {
|
---|
116 |
|
---|
117 | val = volume * (i/(sampleRate*attack)) * waveFunc.call(waveBind, i, sampleRate, frequency, volume);
|
---|
118 |
|
---|
119 | data[i << 1] = val;
|
---|
120 | data[(i << 1) + 1] = val >> 8;
|
---|
121 |
|
---|
122 | }
|
---|
123 |
|
---|
124 | for (; i !== decayLen; i++) {
|
---|
125 |
|
---|
126 | val = volume * Math.pow((1-((i-(sampleRate*attack))/(sampleRate*(time-attack)))),dampen) * waveFunc.call(waveBind, i, sampleRate, frequency, volume);
|
---|
127 |
|
---|
128 | data[i << 1] = val;
|
---|
129 | data[(i << 1) + 1] = val >> 8;
|
---|
130 |
|
---|
131 | }
|
---|
132 |
|
---|
133 | var out = [
|
---|
134 | 'RIFF',
|
---|
135 | pack(1, 4 + (8 + 24/* chunk 1 length */) + (8 + 8/* chunk 2 length */)), // Length
|
---|
136 | 'WAVE',
|
---|
137 | // chunk 1
|
---|
138 | 'fmt ', // Sub-chunk identifier
|
---|
139 | pack(1, 16), // Chunk length
|
---|
140 | pack(0, 1), // Audio format (1 is linear quantization)
|
---|
141 | pack(0, channels),
|
---|
142 | pack(1, sampleRate),
|
---|
143 | pack(1, sampleRate * channels * bitsPerSample / 8), // Byte rate
|
---|
144 | pack(0, channels * bitsPerSample / 8),
|
---|
145 | pack(0, bitsPerSample),
|
---|
146 | // chunk 2
|
---|
147 | 'data', // Sub-chunk identifier
|
---|
148 | pack(1, data.length * channels * bitsPerSample / 8), // Chunk length
|
---|
149 | data
|
---|
150 | ];
|
---|
151 | var blob = new Blob(out, {type: 'audio/wav'});
|
---|
152 | var dataURI = URL.createObjectURL(blob);
|
---|
153 | this._fileCache[sound][octave-1][note][time] = dataURI;
|
---|
154 | if(this._debug) { console.log((new Date).valueOf() - t, 'ms to generate'); }
|
---|
155 | return dataURI;
|
---|
156 | }
|
---|
157 | });
|
---|
158 | setPub('play', function(sound, note, octave, duration) {
|
---|
159 | var src = this.generate(sound, note, octave, duration);
|
---|
160 | var audio = new Audio(src);
|
---|
161 | audio.play();
|
---|
162 | return true;
|
---|
163 | });
|
---|
164 | setPub('debug', function() { this._debug = true; });
|
---|
165 | setPub('createInstrument', function(sound) {
|
---|
166 | var n = 0;
|
---|
167 | var found = false;
|
---|
168 | if(typeof(sound)=='string') {
|
---|
169 | for(var i=0;i<this._sounds.length;i++) {
|
---|
170 | if(this._sounds[i].name==sound) {
|
---|
171 | found = true;
|
---|
172 | n = i;
|
---|
173 | break;
|
---|
174 | }
|
---|
175 | }
|
---|
176 | } else {
|
---|
177 | if(this._sounds[sound]) {
|
---|
178 | n = sound;
|
---|
179 | sound = this._sounds[n].name;
|
---|
180 | found = true;
|
---|
181 | }
|
---|
182 | }
|
---|
183 | if(!found) { throw new Error('Invalid sound or sound ID: ' + sound); }
|
---|
184 | _encapsulated = true;
|
---|
185 | var ins = new AudioSynthInstrument(this, sound, n);
|
---|
186 | _encapsulated = false;
|
---|
187 | return ins;
|
---|
188 | });
|
---|
189 | setPub('listSounds', function() {
|
---|
190 | var r = [];
|
---|
191 | for(var i=0;i<this._sounds.length;i++) {
|
---|
192 | r.push(this._sounds[i].name);
|
---|
193 | }
|
---|
194 | return r;
|
---|
195 | });
|
---|
196 | setPriv('__init__', function(){
|
---|
197 | this._resizeCache();
|
---|
198 | });
|
---|
199 | setPub('loadSoundProfile', function() {
|
---|
200 | for(var i=0,len=arguments.length;i<len;i++) {
|
---|
201 | o = arguments[i];
|
---|
202 | if(!(o instanceof Object)) { throw new Error('Invalid sound profile.'); }
|
---|
203 | this._sounds.push(o);
|
---|
204 | }
|
---|
205 | this._resizeCache();
|
---|
206 | return true;
|
---|
207 | });
|
---|
208 | setPub('loadModulationFunction', function() {
|
---|
209 | for(var i=0,len=arguments.length;i<len;i++) {
|
---|
210 | f = arguments[i];
|
---|
211 | if(typeof(f)!='function') { throw new Error('Invalid modulation function.'); }
|
---|
212 | this._mod.push(f);
|
---|
213 | }
|
---|
214 | return true;
|
---|
215 | });
|
---|
216 | AudioSynthInstance = new AudioSynth();
|
---|
217 | Synth = AudioSynthInstance;
|
---|
218 | }();
|
---|
219 |
|
---|
220 | Synth.loadModulationFunction(
|
---|
221 | function(i, sampleRate, frequency, x) { return 1 * Math.sin(2 * Math.PI * ((i / sampleRate) * frequency) + x); },
|
---|
222 | function(i, sampleRate, frequency, x) { return 1 * Math.sin(4 * Math.PI * ((i / sampleRate) * frequency) + x); },
|
---|
223 | function(i, sampleRate, frequency, x) { return 1 * Math.sin(8 * Math.PI * ((i / sampleRate) * frequency) + x); },
|
---|
224 | function(i, sampleRate, frequency, x) { return 1 * Math.sin(0.5 * Math.PI * ((i / sampleRate) * frequency) + x); },
|
---|
225 | function(i, sampleRate, frequency, x) { return 1 * Math.sin(0.25 * Math.PI * ((i / sampleRate) * frequency) + x); },
|
---|
226 | function(i, sampleRate, frequency, x) { return 0.5 * Math.sin(2 * Math.PI * ((i / sampleRate) * frequency) + x); },
|
---|
227 | function(i, sampleRate, frequency, x) { return 0.5 * Math.sin(4 * Math.PI * ((i / sampleRate) * frequency) + x); },
|
---|
228 | function(i, sampleRate, frequency, x) { return 0.5 * Math.sin(8 * Math.PI * ((i / sampleRate) * frequency) + x); },
|
---|
229 | function(i, sampleRate, frequency, x) { return 0.5 * Math.sin(0.5 * Math.PI * ((i / sampleRate) * frequency) + x); },
|
---|
230 | function(i, sampleRate, frequency, x) { return 0.5 * Math.sin(0.25 * Math.PI * ((i / sampleRate) * frequency) + x); }
|
---|
231 | );
|
---|
232 |
|
---|
233 | Synth.loadSoundProfile({
|
---|
234 | name: 'piano',
|
---|
235 | attack: function() { return 0.002; },
|
---|
236 | dampen: function(sampleRate, frequency, volume) {
|
---|
237 | return Math.pow(0.5*Math.log((frequency*volume)/sampleRate),2);
|
---|
238 | },
|
---|
239 | wave: function(i, sampleRate, frequency, volume) {
|
---|
240 | var base = this.modulate[0];
|
---|
241 | return this.modulate[1](
|
---|
242 | i,
|
---|
243 | sampleRate,
|
---|
244 | frequency,
|
---|
245 | Math.pow(base(i, sampleRate, frequency, 0), 2) +
|
---|
246 | (0.75 * base(i, sampleRate, frequency, 0.25)) +
|
---|
247 | (0.1 * base(i, sampleRate, frequency, 0.5))
|
---|
248 | );
|
---|
249 | }
|
---|
250 | },
|
---|
251 | {
|
---|
252 | name: 'organ',
|
---|
253 | attack: function() { return 0.3 },
|
---|
254 | dampen: function(sampleRate, frequency) { return 1+(frequency * 0.01); },
|
---|
255 | wave: function(i, sampleRate, frequency) {
|
---|
256 | var base = this.modulate[0];
|
---|
257 | return this.modulate[1](
|
---|
258 | i,
|
---|
259 | sampleRate,
|
---|
260 | frequency,
|
---|
261 | base(i, sampleRate, frequency, 0) +
|
---|
262 | 0.5*base(i, sampleRate, frequency, 0.25) +
|
---|
263 | 0.25*base(i, sampleRate, frequency, 0.5)
|
---|
264 | );
|
---|
265 | }
|
---|
266 | },
|
---|
267 | {
|
---|
268 | name: 'acoustic',
|
---|
269 | attack: function() { return 0.002; },
|
---|
270 | dampen: function() { return 1; },
|
---|
271 | wave: function(i, sampleRate, frequency) {
|
---|
272 |
|
---|
273 | var vars = this.vars;
|
---|
274 | vars.valueTable = !vars.valueTable?[]:vars.valueTable;
|
---|
275 | if(typeof(vars.playVal)=='undefined') { vars.playVal = 0; }
|
---|
276 | if(typeof(vars.periodCount)=='undefined') { vars.periodCount = 0; }
|
---|
277 |
|
---|
278 | var valueTable = vars.valueTable;
|
---|
279 | var playVal = vars.playVal;
|
---|
280 | var periodCount = vars.periodCount;
|
---|
281 |
|
---|
282 | var period = sampleRate/frequency;
|
---|
283 | var p_hundredth = Math.floor((period-Math.floor(period))*100);
|
---|
284 |
|
---|
285 | var resetPlay = false;
|
---|
286 |
|
---|
287 | if(valueTable.length<=Math.ceil(period)) {
|
---|
288 |
|
---|
289 | valueTable.push(Math.round(Math.random())*2-1);
|
---|
290 |
|
---|
291 | return valueTable[valueTable.length-1];
|
---|
292 |
|
---|
293 | } else {
|
---|
294 |
|
---|
295 | valueTable[playVal] = (valueTable[playVal>=(valueTable.length-1)?0:playVal+1] + valueTable[playVal]) * 0.5;
|
---|
296 |
|
---|
297 | if(playVal>=Math.floor(period)) {
|
---|
298 | if(playVal<Math.ceil(period)) {
|
---|
299 | if((periodCount%100)>=p_hundredth) {
|
---|
300 | // Reset
|
---|
301 | resetPlay = true;
|
---|
302 | valueTable[playVal+1] = (valueTable[0] + valueTable[playVal+1]) * 0.5;
|
---|
303 | vars.periodCount++;
|
---|
304 | }
|
---|
305 | } else {
|
---|
306 | resetPlay = true;
|
---|
307 | }
|
---|
308 | }
|
---|
309 |
|
---|
310 | var _return = valueTable[playVal];
|
---|
311 | if(resetPlay) { vars.playVal = 0; } else { vars.playVal++; }
|
---|
312 |
|
---|
313 | return _return;
|
---|
314 |
|
---|
315 | }
|
---|
316 | }
|
---|
317 | },
|
---|
318 | {
|
---|
319 | name: 'edm',
|
---|
320 | attack: function() { return 0.002; },
|
---|
321 | dampen: function() { return 1; },
|
---|
322 | wave: function(i, sampleRate, frequency) {
|
---|
323 | var base = this.modulate[0];
|
---|
324 | var mod = this.modulate.slice(1);
|
---|
325 | return mod[0](
|
---|
326 | i,
|
---|
327 | sampleRate,
|
---|
328 | frequency,
|
---|
329 | mod[9](
|
---|
330 | i,
|
---|
331 | sampleRate,
|
---|
332 | frequency,
|
---|
333 | mod[2](
|
---|
334 | i,
|
---|
335 | sampleRate,
|
---|
336 | frequency,
|
---|
337 | Math.pow(base(i, sampleRate, frequency, 0), 3) +
|
---|
338 | Math.pow(base(i, sampleRate, frequency, 0.5), 5) +
|
---|
339 | Math.pow(base(i, sampleRate, frequency, 1), 7)
|
---|
340 | )
|
---|
341 | ) +
|
---|
342 | mod[8](
|
---|
343 | i,
|
---|
344 | sampleRate,
|
---|
345 | frequency,
|
---|
346 | base(i, sampleRate, frequency, 1.75)
|
---|
347 | )
|
---|
348 | );
|
---|
349 | }
|
---|
350 | });
|
---|