-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.html
385 lines (343 loc) · 15.1 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
<!DOCTYPE html>
<html>
<!--
Warning: this code is just a prototype and really messy, it also only works with Firefox 4.0
-->
<head>
<title>Audio Data API Synth</title>
<!-- jQuery is just for UI, it's not used for synth/audio support -->
<link type="text/css" href="css/ui-lightness/jquery-ui-1.8.11.custom.css" rel="stylesheet" />
<script type="text/javascript" src="js/jquery-1.5.1.min.js"></script>
<script type="text/javascript" src="js/jquery-ui-1.8.11.custom.min.js"></script>
<script>
$(function() {
//
// Initialize jQuery UI elements (sliders)
//
oscSliderParams = {
value: 0,
min: -48,
max: 48,
step: 1
}
function uiHandler(elem) {
return function( event, ui ) {
$( "#" + elem ).val( ui.value );
}
}
$( "#slider1" ).slider($.extend(oscSliderParams, { slide: uiHandler("freq1") }));
$( "#slider2" ).slider($.extend(oscSliderParams, { slide: uiHandler("freq2") }));
$( "#slider3" ).slider($.extend(oscSliderParams, { slide: uiHandler("freq3") }));
$( "#freq1" ).val( $( "#slider1" ).slider( "value" ) );
$( "#freq2" ).val( $( "#slider2" ).slider( "value" ) );
$( "#freq3" ).val( $( "#slider3" ).slider( "value" ) );
adsrParams = { value: .05, min: 0.0, max: 1.0, step: 0.01 }
$( "#sliderAttack" ).slider($.extend(adsrParams, { slide: uiHandler("attack") }));
$( "#attack" ).val( $( "#sliderAttack" ).slider( "value" ) );
adsrParams = { value: .25, min: 0.0, max: 1.0, step: 0.01 }
$( "#sliderDecay" ).slider($.extend(adsrParams, { slide: uiHandler("decay") }));
$( "#decay" ).val( $( "#sliderDecay" ).slider( "value" ) );
adsrParams = { value: .5, min: 0.0, max: 1.0, step: 0.01 }
$( "#sliderSustain" ).slider($.extend(adsrParams, { slide: uiHandler("sustain") }));
$( "#sustain" ).val( $( "#sliderSustain" ).slider( "value" ) );
adsrParams = { value: .5, min: 0.0, max: 1.0, step: 0.01 }
$( "#sliderRelease" ).slider($.extend(adsrParams, { slide: uiHandler("release") }));
$( "#release" ).val( $( "#sliderRelease" ).slider( "value" ) );
});
</script>
<style>
body { font-family: helvetica, verdana, sans; font-size: 0.9em;}
.ojText { border:0; color:#f6931f; font-weight:bold; }
</style>
</head>
<body id="body" onKeyDown="return keyDown(event)" onKeyUp="return keyUp(event)">
<div style="margin-top: 20px;float: right; font-size: 0.7em; color: #999;">Created by <a href="http://visualcore.com">Jeremy Cowles</a></div>
<h1 style="font-size: 1.8em">HTML5 Synthesizer</h1>
<div style="display: none;">
<input id="doLfo" type="checkbox" value="0">
<input id="lfo" type="text" size="1" value="0" class="ojText"><label for="doLfo">super-cheesy lfo</label>
<br/><br/>
</div>
<div style="border: solid 1px #f6931f; padding: 10px; background-color: #fdf5ce;">
NOTICE: This page requires <strong>Firefox 4.0</strong> because it uses the Firefox Audio Data API.<br/>
</div>
<br/>
The following is a very simple synthesizer built using the Firefox Audio Data API. It generates wave
data by using oscillators and a volume envelope (see source for details). jQuery is used strictly for
UI elements and does not play a part in the generation of audio.
<br/>
<h3>Keyboard:</h3>
Black keys: 2 4 5 7 9 -<br/>
White keys: q w e r t y u i o p<br style="clear: both"/>
<div style="margin-right: 100px; float: left">
<h3>Oscillators:</h3>
<input id="osc1" type="checkbox" checked><label for="osc1">Osc-1</label>
<input type="text" size="1" id="freq1" value="0" class="ojText"><label for="freq1">Semitones (sine)</label>
<div style="float: left; width: 200px;" id="slider1"></div>
<br/><br/>
<input id="osc2" type="checkbox"><label for="osc2">Osc-2</label>
<input type="text" size="1" id="freq2" value="0" class="ojText"><label for="freq2">Semitones (square)</label>
<div style="float: left; width: 200px;" id="slider2"></div>
<br/><br/>
<input id="osc3" type="checkbox"><label for="osc3">Osc-3</label>
<input type="text" size="1" id="freq3" value="0" class="ojText"><label for="freq3">Semitones (square)</label>
<div style="float: left; width: 200px;" id="slider3"></div>
<br/><br/>
<br/><br/>
</div>
<div style="float: none">
<h3>Volume Envelope:</h3>
<input type="text" size="3" id="attack" value="0" class="ojText">
<label for="attack">Attack</label>
<div style="float: left; width: 200px; margin-right: 20px;" id="sliderAttack"></div>
<br/> <br/>
<input type="text" size="3" id="decay" value="0" class="ojText">
<label for="decay">Decay</label>
<div style="float: left; width: 200px; margin-right: 20px;" id="sliderDecay"></div>
<br/>
<br/>
<input type="text" size="3" id="sustain" value="0" class="ojText">
<label for="sustain">Sustain</label>
<div style="float: left; width: 200px; margin-right: 20px;" id="sliderSustain"></div>
<br/>
<br/>
<input type="text" size="3" id="release" value="0" class="ojText">
<label for="release">Release</label>
<div style="float: left; width: 200px; margin-right: 20px;" id="sliderRelease"></div>
</div>
<br/>
<br/>
<strong>What?! It doesn't work?</strong>
<ul>
<li>Are you using Firefox 4.0?</li>
<li>Does the page have focus? (That is, try cliking on the page and then pressing the hot keys)</li>
<li>Try reloading the page! The audio data api is still experimental and this may fix the problem.</li>
<li>And sadly, there is a possibility the audio buffer is too small for your hardware. If you can't get it to work, please email me: jeremy dot cowles at gmail.
</ul>
<br/>
<br/>
<br/>
<script type="text/javascript">
var activeKeys = {};
var activeEnv = {};
function keyDown(e) {
// XXX firefox/opera only (ie wants keyCode, window.event)
keynum = e.which;
keychar = String.fromCharCode(keynum);
if (activeEnv[keychar] && activeEnv[keychar].releasing)
delete activeEnv[keychar];
activeKeys[keychar] = true;
return true;
}
function keyUp(e) {
// XXX firefox/opera only (ie wants keyCode, window.event)
keynum = e.which;
keychar = String.fromCharCode(keynum);
delete activeKeys[keychar];
return true;
}
function AudioDataDestination(sampleRate, readFn) {
// Initialize the audio output.
var audio = new Audio();
audio.mozSetup(1, sampleRate);
var currentWritePosition = 0;
var prebufferSize = sampleRate / 2; // buffer 500ms
var tail = null, tailPosition;
// The function called with regular interval to populate
// the audio output buffer.
setInterval(function() {
var written;
// Check if some data was not written in previous attempts.
if(tail) {
written = audio.mozWriteAudio(tail.subarray(tailPosition));
currentWritePosition += written;
tailPosition += written;
if(tailPosition < tail.length) {
// Not all the data was written, saving the tail...
return; // ... and exit the function.
}
tail = null;
}
// XXX if curentPosition == 0, the buffer size is too small
// Check if we need add some data to the audio output.
var currentPosition = audio.mozCurrentSampleOffset();
var available = currentPosition + prebufferSize - currentWritePosition;
if(available > 0) {
// Request some sound data from the callback function.
var soundData = new Float32Array(available);
readFn(soundData);
// Writting the data.
written = audio.mozWriteAudio(soundData);
currentPosition = audio.mozCurrentSampleOffset();
if(written < soundData.length) {
// Not all the data was written, saving the tail.
tail = soundData;
tailPosition = written;
}
currentWritePosition += written;
}
}, 100);
}
// various parameters
// XXX clean up unused variables
var currentSoundSample = 0;
var sampleRate = 44100;
var lfo = 0;
var t = 0;
var dt = 0;
var notes = {
"Q": 440.00, // A 4th octave
"2": 466.16, // A#
"W": 493.88, // B
"E": 523.25, // C
"4": 554.37, // C#
"R": 587.33, // D
"5": 622.25, // D#
"T": 659.26, // E
"Y": 698.46, // F
"7": 739.99, // F#
"U": 783.99, // G
"I": 880.00, // A 5th octave
"9": 932.33, // A#
"O": 987.77, // B
"P":1046.50, // C
"-":1108.73, // C#
"[":1174.66, // D
"=":1244.51, // D#
"]":1381.51, // E
"\\":1396.91, // F
// "7": 739.99, // F#
//"U": 783.99, // G
}
function sawTooth(t) {
return t - Math.floor(t);
}
function semiToFreq(semitones, rootFreq) {
if (semitones == 0) return rootFreq;
return rootFreq * (Math.pow(Math.pow(2.0,1.0 / 12.0), semitones));
}
function sineOsc(sample, f, sampleRate, offset) {
return Math.sin(2*Math.PI*(f+offset)/sampleRate*sample);
}
function squareOsc(sample, f, sampleRate, offset) {
return Math.round(sineOsc(sample,f,sampleRate,offset));
}
AdsrEnvelope = function (attack, decay, sustain, release, t0) {
this.t0 = t0;
this.A = attack;
this.D = decay;
this.S = sustain;
this.R = release;
this.t0 = t0;
this.s0 = -1;
this.releasing = false;
this.level = 0;
this.down = function(t) {
var dt = t - this.t0;
var ret = 0;
if (dt > this.A + this.D) ret = this.S;
else if (dt > this.A) {
var Dpct = (dt - this.A) / this.D;
ret = (1-Dpct) + (Dpct)*this.S;
}
else ret = dt / this.A;
this.level = ret;
return ret;
}
this.up = function(t) {
if (!this.releasing) {
this.s0 = t;
this.releasing = true;
}
var dt = t - this.s0;
if (dt > (this.R)) return 0;
var retVal = this.level * (1 - dt / this.R);
if (isNaN(retVal)) {
this.R = 0;
retVal = 0;
}
return retVal;
}
};
function requestSoundData(soundData) {
dt = 1 / sampleRate;
var lfoUi = document.getElementById("lfo");
var doLfo = document.getElementById("doLfo").checked;
var osc1On = document.getElementById("osc1").checked;
var osc2On = document.getElementById("osc2").checked;
var osc3On = document.getElementById("osc3").checked;
var f1 = (parseFloat(document.getElementById("freq1").value));
var f2 = (parseFloat(document.getElementById("freq2").value));
var f3 = (parseFloat(document.getElementById("freq3").value));
var osc1 = function(sample, f) { return !osc1On ? 0 : sineOsc(sample, f, sampleRate, semiToFreq(f1,f)); }
var osc2 = function(sample, f) { return !osc2On ? 0 : squareOsc(sample, f, sampleRate, semiToFreq(f2,f)); }
var osc3 = function(sample, f) { return !osc3On ? 0 : squareOsc(sample, f, sampleRate, semiToFreq(f3,f)); }
// var k = 2 * Math.PI * (f) / sampleRate;
for (var i=0, size=soundData.length; i<size; i++) {
var n = 1; //Math.random();
currentSoundSample++;
var cnt = 0;
// sanitize keys
toRemove = {}
for (var key in activeKeys) {
if (!notes[key]) { toRemove[key] = true; }
}
for (var key in toRemove) { delete activeKeys[key]; }
toRemove = {}
// process all active keys
for ( var key in activeKeys ) {
if (!notes[key]) {
delete activeKeys[key];
}
var env = activeEnv[key];
if (!env) {
env = new AdsrEnvelope(parseFloat($("#attack").val()),
parseFloat($("#decay").val()),
parseFloat($("#sustain").val()),
parseFloat($("#release").val()),currentSoundSample/sampleRate);
activeEnv[key] = env;
}
var f = notes[key] / 4;
var envValue = env.down(currentSoundSample/sampleRate);
soundData[i] += (1/10)*envValue *
(osc1(currentSoundSample+lfo, f) +
osc2(currentSoundSample, f) +
osc3(currentSoundSample, f)) ;
cnt += 1;
}
// process all released keys
for ( var key in activeEnv ) {
// filter out keys actively pressed, they have already been processed
if (activeKeys[key]) continue;
var env = activeEnv[key];
var f = notes[key] / 4;
var envValue = env.up(currentSoundSample/sampleRate);
if (envValue <= 0) {
toRemove[key] = true;
continue;
}
soundData[i] += (1/10)*envValue *
(osc1(currentSoundSample+lfo, f) +
osc2(currentSoundSample, f) +
osc3(currentSoundSample, f)) ;
cnt += 1;
}
for (var key in toRemove) { delete activeEnv[key]; }
lfo = doLfo ? (lfo + (0.01 + 0.5/sampleRate)*Math.sin(t)) : lfo ;
if (doLfo) t += dt;
}
lfoUi.value = lfo;
}
var audioDestination = new AudioDataDestination(sampleRate, requestSoundData);
</script>
<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
var pageTracker = _gat._getTracker("UA-3537030-1");
pageTracker._initData();
pageTracker._trackPageview();
</script>
</body>
</html>