source: main/trunk/model-sites-dev/respooled/collect/popup-video-respooled/js/audiosynth.js@ 29863

Last change on this file since 29863 was 29863, checked in by davidb, 9 years ago

First cut at respooled site/collection

File size: 11.3 KB
Line 
1var 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
220Synth.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
233Synth.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});
Note: See TracBrowser for help on using the repository browser.