678 |
679 |
Generator
680 |
681 |
682 |
683 |
684 |
685 |
686 |
687 |
688 |
689 |
690 |
691 |
692 |
693 |
694 |
695 |
696 |
697 |
698 |
699 |
700 |
770 |
771 |
772 |
Sound
773 |
774 |
775 |
776 | Download:
777 | sfx.wav
778 |
779 | Save As:
780 | sfx.json
781 |
782 |
783 |
784 |
785 | | File size: |
786 | |
787 |
788 |
789 | | Samples: |
790 | |
791 |
792 |
793 | | Clipped: |
794 | |
795 |
796 |
797 |
798 |
799 |
800 |
801 |
802 |
803 |
804 |
805 |
806 |
807 |
808 |
809 |
810 |
811 |
812 |
813 |
814 |
815 |
816 |
817 |
818 |
819 |
820 |
821 |
822 |
823 |
824 |
825 |
🔗 permalink
826 |
827 |
828 |
829 |
830 |
831 |
864 |
865 |
866 |
867 |
--------------------------------------------------------------------------------
/sfxr.js:
--------------------------------------------------------------------------------
1 | // Wave shapes
2 | var SQUARE = 0;
3 | var SAWTOOTH = 1;
4 | var SINE = 2;
5 | var NOISE = 3;
6 |
7 | // Playback volume
8 | var masterVolume = 1;
9 |
10 | var OVERSAMPLING = 8;
11 |
12 | /*** Core data structure ***/
13 |
14 | // Sound generation parameters are on [0,1] unless noted SIGNED & thus
15 | // on [-1,1]
16 | function Params() {
17 | this.oldParams = true; // Note what structure this is
18 |
19 | // Wave shape
20 | this.wave_type = SQUARE;
21 |
22 | // Envelope
23 | this.p_env_attack = 0; // Attack time
24 | this.p_env_sustain = 0.3; // Sustain time
25 | this.p_env_punch = 0; // Sustain punch
26 | this.p_env_decay = 0.4; // Decay time
27 |
28 | // Tone
29 | this.p_base_freq = 0.3; // Start frequency
30 | this.p_freq_limit = 0; // Min frequency cutoff
31 | this.p_freq_ramp = 0; // Slide (SIGNED)
32 | this.p_freq_dramp = 0; // Delta slide (SIGNED)
33 | // Vibrato
34 | this.p_vib_strength = 0; // Vibrato depth
35 | this.p_vib_speed = 0; // Vibrato speed
36 |
37 | // Tonal change
38 | this.p_arp_mod = 0; // Change amount (SIGNED)
39 | this.p_arp_speed = 0; // Change speed
40 |
41 | // Square wave duty (proportion of time signal is high vs. low)
42 | this.p_duty = 0; // Square duty
43 | this.p_duty_ramp = 0; // Duty sweep (SIGNED)
44 |
45 | // Repeat
46 | this.p_repeat_speed = 0; // Repeat speed
47 |
48 | // Flanger
49 | this.p_pha_offset = 0; // Flanger offset (SIGNED)
50 | this.p_pha_ramp = 0; // Flanger sweep (SIGNED)
51 |
52 | // Low-pass filter
53 | this.p_lpf_freq = 1; // Low-pass filter cutoff
54 | this.p_lpf_ramp = 0; // Low-pass filter cutoff sweep (SIGNED)
55 | this.p_lpf_resonance = 0;// Low-pass filter resonance
56 | // High-pass filter
57 | this.p_hpf_freq = 0; // High-pass filter cutoff
58 | this.p_hpf_ramp = 0; // High-pass filter cutoff sweep (SIGNED)
59 |
60 | // Sample parameters
61 | this.sound_vol = 0.5;
62 | this.sample_rate = 44100;
63 | this.sample_size = 8;
64 | }
65 |
66 | /*** Helper functions ***/
67 |
68 | function sqr(x) { return x * x }
69 | function cube(x) { return x * x * x }
70 | function sign(x) { return x < 0 ? -1 : 1 }
71 | function log(x, b) { return Math.log(x) / Math.log(b); }
72 | var pow = Math.pow;
73 |
74 | function frnd(range) {
75 | return Math.random() * range;
76 | }
77 |
78 | function rndr(from, to) {
79 | return Math.random() * (to - from) + from;
80 | }
81 |
82 | function rnd(max) {
83 | return Math.floor(Math.random() * (max + 1));
84 | }
85 |
86 | /*** Import/export functions ***/
87 |
88 | // http://stackoverflow.com/questions/3096646/how-to-convert-a-floating-point-number-to-its-binary-representation-ieee-754-i
89 | function assembleFloat(sign, exponent, mantissa)
90 | {
91 | return (sign << 31) | (exponent << 23) | (mantissa);
92 | }
93 |
94 | function floatToNumber(flt)
95 | {
96 | if (isNaN(flt)) // Special case: NaN
97 | return assembleFloat(0, 0xFF, 0x1337); // Mantissa is nonzero for NaN
98 |
99 | var sign = (flt < 0) ? 1 : 0;
100 | flt = Math.abs(flt);
101 | if (flt == 0.0) // Special case: +-0
102 | return assembleFloat(sign, 0, 0);
103 |
104 | var exponent = Math.floor(Math.log(flt) / Math.LN2);
105 | if (exponent > 127 || exponent < -126) // Special case: +-Infinity (and huge numbers)
106 | return assembleFloat(sign, 0xFF, 0); // Mantissa is zero for +-Infinity
107 |
108 | var mantissa = flt / Math.pow(2, exponent);
109 | return assembleFloat(sign, exponent + 127, (mantissa * Math.pow(2, 23)) & 0x7FFFFF);
110 | }
111 |
112 | // http://stackoverflow.com/a/16001019
113 | function numberToFloat(bytes) {
114 | var sign = (bytes & 0x80000000) ? -1 : 1;
115 | var exponent = ((bytes >> 23) & 0xFF) - 127;
116 | var significand = (bytes & ~(-1 << 23));
117 |
118 | if (exponent == 128)
119 | return sign * ((significand) ? Number.NaN : Number.POSITIVE_INFINITY);
120 |
121 | if (exponent == -127) {
122 | if (significand == 0) return sign * 0.0;
123 | exponent = -126;
124 | significand /= (1 << 22);
125 | } else significand = (significand | (1 << 23)) / (1 << 23);
126 |
127 | return sign * significand * Math.pow(2, exponent);
128 | }
129 |
130 | // export parameter list to URL friendly base58 string
131 | // https://gist.github.com/diafygi/90a3e80ca1c2793220e5/
132 | var b58alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
133 | var params_order = [
134 | "wave_type",
135 | "p_env_attack",
136 | "p_env_sustain",
137 | "p_env_punch",
138 | "p_env_decay",
139 | "p_base_freq",
140 | "p_freq_limit",
141 | "p_freq_ramp",
142 | "p_freq_dramp",
143 | "p_vib_strength",
144 | "p_vib_speed",
145 | "p_arp_mod",
146 | "p_arp_speed",
147 | "p_duty",
148 | "p_duty_ramp",
149 | "p_repeat_speed",
150 | "p_pha_offset",
151 | "p_pha_ramp",
152 | "p_lpf_freq",
153 | "p_lpf_ramp",
154 | "p_lpf_resonance",
155 | "p_hpf_freq",
156 | "p_hpf_ramp"
157 | ];
158 |
159 | var params_signed = ["p_freq_ramp", "p_freq_dramp", "p_arp_mod", "p_duty_ramp", "p_pha_offset", "p_pha_ramp", "p_lpf_ramp", "p_hpf_ramp"];
160 |
161 | Params.prototype.toB58 = function() {
162 | var convert = [];
163 | for (var pi in params_order) {
164 | var p = params_order[pi];
165 | if (p == "wave_type") {
166 | convert.push(this[p]);
167 | } else if (p.indexOf("p_") == 0) {
168 | var val = this[p];
169 | val = floatToNumber(val);
170 | convert.push(0xff & val);
171 | convert.push(0xff & (val >> 8))
172 | convert.push(0xff & (val >> 16))
173 | convert.push(0xff & (val >> 24))
174 | }
175 | }
176 | return function(B,A){var d=[],s="",i,j,c,n;for(i in B){j=0,c=B[i];s+=c||s.length^i?"":1;while(j in d||c){n=d[j];n=n?n*256+c:c;c=n/58|0;d[j]=n%58;j++}}while(j--)s+=A[d[j]];return s}(convert, b58alphabet);
177 | }
178 |
179 | Params.prototype.fromB58 = function(b58encoded) {
180 | this.fromJSON(sfxr.b58decode(b58encoded));
181 | return this;
182 | }
183 |
184 | Params.prototype.fromJSON = function(struct) {
185 | for (var p in struct) {
186 | if (struct.hasOwnProperty(p)) {
187 | this[p] = struct[p];
188 | }
189 | }
190 | return this;
191 | }
192 |
193 | /*** Presets ***/
194 |
195 | // These functions roll up random sounds appropriate to various
196 | // typical game events:
197 |
198 | Params.prototype.pickupCoin = function () {
199 | this.wave_type = SAWTOOTH;
200 | this.p_base_freq = 0.4 + frnd(0.5);
201 | this.p_env_attack = 0;
202 | this.p_env_sustain = frnd(0.1);
203 | this.p_env_decay = 0.1 + frnd(0.4);
204 | this.p_env_punch = 0.3 + frnd(0.3);
205 | if (rnd(1)) {
206 | this.p_arp_speed = 0.5 + frnd(0.2);
207 | this.p_arp_mod = 0.2 + frnd(0.4);
208 | }
209 | return this;
210 | }
211 |
212 | Params.prototype.laserShoot = function () {
213 | this.wave_type = rnd(2);
214 | if(this.wave_type === SINE && rnd(1))
215 | this.wave_type = rnd(1);
216 | if (rnd(2) === 0) {
217 | this.p_base_freq = 0.3 + frnd(0.6);
218 | this.p_freq_limit = frnd(0.1);
219 | this.p_freq_ramp = -0.35 - frnd(0.3);
220 | } else {
221 | this.p_base_freq = 0.5 + frnd(0.5);
222 | this.p_freq_limit = this.p_base_freq - 0.2 - frnd(0.6);
223 | if (this.p_freq_limit < 0.2) this.p_freq_limit = 0.2;
224 | this.p_freq_ramp = -0.15 - frnd(0.2);
225 | }
226 | if (this.wave_type === SAWTOOTH)
227 | this.p_duty = 1;
228 | if (rnd(1)) {
229 | this.p_duty = frnd(0.5);
230 | this.p_duty_ramp = frnd(0.2);
231 | } else {
232 | this.p_duty = 0.4 + frnd(0.5);
233 | this.p_duty_ramp = -frnd(0.7);
234 | }
235 | this.p_env_attack = 0;
236 | this.p_env_sustain = 0.1 + frnd(0.2);
237 | this.p_env_decay = frnd(0.4);
238 | if (rnd(1))
239 | this.p_env_punch = frnd(0.3);
240 | if (rnd(2) === 0) {
241 | this.p_pha_offset = frnd(0.2);
242 | this.p_pha_ramp = -frnd(0.2);
243 | }
244 | //if (rnd(1))
245 | this.p_hpf_freq = frnd(0.3);
246 |
247 | return this;
248 | }
249 |
250 | Params.prototype.explosion = function () {
251 | this.wave_type = NOISE;
252 | if (rnd(1)) {
253 | this.p_base_freq = sqr(0.1 + frnd(0.4));
254 | this.p_freq_ramp = -0.1 + frnd(0.4);
255 | } else {
256 | this.p_base_freq = sqr(0.2 + frnd(0.7));
257 | this.p_freq_ramp = -0.2 - frnd(0.2);
258 | }
259 | if (rnd(4) === 0)
260 | this.p_freq_ramp = 0;
261 | if (rnd(2) === 0)
262 | this.p_repeat_speed = 0.3 + frnd(0.5);
263 | this.p_env_attack = 0;
264 | this.p_env_sustain = 0.1 + frnd(0.3);
265 | this.p_env_decay = frnd(0.5);
266 | if (rnd(1)) {
267 | this.p_pha_offset = -0.3 + frnd(0.9);
268 | this.p_pha_ramp = -frnd(0.3);
269 | }
270 | this.p_env_punch = 0.2 + frnd(0.6);
271 | if (rnd(1)) {
272 | this.p_vib_strength = frnd(0.7);
273 | this.p_vib_speed = frnd(0.6);
274 | }
275 | if (rnd(2) === 0) {
276 | this.p_arp_speed = 0.6 + frnd(0.3);
277 | this.p_arp_mod = 0.8 - frnd(1.6);
278 | }
279 |
280 | return this;
281 | }
282 |
283 | Params.prototype.powerUp = function () {
284 | if (rnd(1)) {
285 | this.wave_type = SAWTOOTH;
286 | this.p_duty = 1;
287 | } else {
288 | this.p_duty = frnd(0.6);
289 | }
290 | this.p_base_freq = 0.2 + frnd(0.3);
291 | if (rnd(1)) {
292 | this.p_freq_ramp = 0.1 + frnd(0.4);
293 | this.p_repeat_speed = 0.4 + frnd(0.4);
294 | } else {
295 | this.p_freq_ramp = 0.05 + frnd(0.2);
296 | if (rnd(1)) {
297 | this.p_vib_strength = frnd(0.7);
298 | this.p_vib_speed = frnd(0.6);
299 | }
300 | }
301 | this.p_env_attack = 0;
302 | this.p_env_sustain = frnd(0.4);
303 | this.p_env_decay = 0.1 + frnd(0.4);
304 |
305 | return this;
306 | }
307 |
308 | Params.prototype.hitHurt = function () {
309 | this.wave_type = rnd(2);
310 | if (this.wave_type === SINE)
311 | this.wave_type = NOISE;
312 | if (this.wave_type === SQUARE)
313 | this.p_duty = frnd(0.6);
314 | if (this.wave_type === SAWTOOTH)
315 | this.p_duty = 1;
316 | this.p_base_freq = 0.2 + frnd(0.6);
317 | this.p_freq_ramp = -0.3 - frnd(0.4);
318 | this.p_env_attack = 0;
319 | this.p_env_sustain = frnd(0.1);
320 | this.p_env_decay = 0.1 + frnd(0.2);
321 | if (rnd(1))
322 | this.p_hpf_freq = frnd(0.3);
323 | return this;
324 | }
325 |
326 | Params.prototype.jump = function () {
327 | this.wave_type = SQUARE;
328 | this.p_duty = frnd(0.6);
329 | this.p_base_freq = 0.3 + frnd(0.3);
330 | this.p_freq_ramp = 0.1 + frnd(0.2);
331 | this.p_env_attack = 0;
332 | this.p_env_sustain = 0.1 + frnd(0.3);
333 | this.p_env_decay = 0.1 + frnd(0.2);
334 | if (rnd(1))
335 | this.p_hpf_freq = frnd(0.3);
336 | if (rnd(1))
337 | this.p_lpf_freq = 1 - frnd(0.6);
338 | return this;
339 | }
340 |
341 | Params.prototype.blipSelect = function () {
342 | this.wave_type = rnd(1);
343 | if (this.wave_type === SQUARE)
344 | this.p_duty = frnd(0.6);
345 | else
346 | this.p_duty = 1;
347 | this.p_base_freq = 0.2 + frnd(0.4);
348 | this.p_env_attack = 0;
349 | this.p_env_sustain = 0.1 + frnd(0.1);
350 | this.p_env_decay = frnd(0.2);
351 | this.p_hpf_freq = 0.1;
352 | return this;
353 | }
354 |
355 | Params.prototype.synth = function () {
356 | this.wave_type = rnd(1);
357 | this.p_base_freq = [0.2723171360931539, 0.19255692561524382, 0.13615778746815113][rnd(2)];
358 | this.p_env_attack = rnd(4) > 3 ? frnd(0.5) : 0;
359 | this.p_env_sustain = frnd(1);
360 | this.p_env_punch = frnd(1);
361 | this.p_env_decay = frnd(0.9) + 0.1;
362 | this.p_arp_mod = [0, 0, 0, 0, -0.3162, 0.7454, 0.7454][rnd(6)];
363 | this.p_arp_speed = frnd(0.5) + 0.4;
364 | this.p_duty = frnd(1);
365 | this.p_duty_ramp = rnd(2) == 2 ? frnd(1) : 0;
366 | this.p_lpf_freq = [1, 0.9 * frnd(1) * frnd(1) + 0.1][rnd(1)];
367 | this.p_lpf_ramp = rndr(-1, 1);
368 | this.p_lpf_resonance = frnd(1);
369 | this.p_hpf_freq = rnd(3) == 3 ? frnd(1) : 0;
370 | this.p_hpf_ramp = rnd(3) == 3 ? frnd(1) : 0;
371 | return this;
372 | }
373 |
374 | Params.prototype.tone = function () {
375 | this.wave_type = SINE;
376 | this.p_base_freq = 0.35173364; // 440 Hz
377 | this.p_env_attack = 0;
378 | this.p_env_sustain = 0.6641; // 1 sec
379 | this.p_env_decay = 0;
380 | this.p_env_punch = 0;
381 | return this;
382 | }
383 |
384 | Params.prototype.click = function() {
385 | const base = ["explosion", "hitHurt"][rnd(1)];
386 | this[base]();
387 | if (rnd(1)) {
388 | this.p_freq_ramp = -0.5 + frnd(1.0);
389 | }
390 | if (rnd(1)) {
391 | this.p_env_sustain = (frnd(0.4) + 0.2) * this.p_env_sustain;
392 | this.p_env_decay = (frnd(0.4) + 0.2) * this.p_env_decay;
393 | }
394 | if (rnd(3) == 0) {
395 | this.p_env_attack = frnd(0.3);
396 | }
397 | this.p_base_freq = 1 - frnd(0.25);
398 | this.p_hpf_freq = 1 - frnd(0.1);
399 | return this;
400 | }
401 |
402 | Params.prototype.random = function () {
403 | this.wave_type = rnd(3);
404 | if (rnd(1))
405 | this.p_base_freq = cube(frnd(2) - 1) + 0.5;
406 | else
407 | this.p_base_freq = sqr(frnd(1));
408 | this.p_freq_limit = 0;
409 | this.p_freq_ramp = Math.pow(frnd(2) - 1, 5);
410 | if (this.p_base_freq > 0.7 && this.p_freq_ramp > 0.2)
411 | this.p_freq_ramp = -this.p_freq_ramp;
412 | if (this.p_base_freq < 0.2 && this.p_freq_ramp < -0.05)
413 | this.p_freq_ramp = -this.p_freq_ramp;
414 | this.p_freq_dramp = Math.pow(frnd(2) - 1, 3);
415 | this.p_duty = frnd(2) - 1;
416 | this.p_duty_ramp = Math.pow(frnd(2) - 1, 3);
417 | this.p_vib_strength = Math.pow(frnd(2) - 1, 3);
418 | this.p_vib_speed = rndr(-1, 1);
419 | this.p_env_attack = cube(rndr(-1, 1));
420 | this.p_env_sustain = sqr(rndr(-1, 1));
421 | this.p_env_decay = rndr(-1, 1);
422 | this.p_env_punch = Math.pow(frnd(0.8), 2);
423 | if (this.p_env_attack + this.p_env_sustain + this.p_env_decay < 0.2) {
424 | this.p_env_sustain += 0.2 + frnd(0.3);
425 | this.p_env_decay += 0.2 + frnd(0.3);
426 | }
427 | this.p_lpf_resonance = rndr(-1, 1);
428 | this.p_lpf_freq = 1 - Math.pow(frnd(1), 3);
429 | this.p_lpf_ramp = Math.pow(frnd(2) - 1, 3);
430 | if (this.p_lpf_freq < 0.1 && this.p_lpf_ramp < -0.05)
431 | this.p_lpf_ramp = -this.p_lpf_ramp;
432 | this.p_hpf_freq = Math.pow(frnd(1), 5);
433 | this.p_hpf_ramp = Math.pow(frnd(2) - 1, 5);
434 | this.p_pha_offset = Math.pow(frnd(2) - 1, 3);
435 | this.p_pha_ramp = Math.pow(frnd(2) - 1, 3);
436 | this.p_repeat_speed = frnd(2) - 1;
437 | this.p_arp_speed = frnd(2) - 1;
438 | this.p_arp_mod = frnd(2) - 1;
439 | return this;
440 | }
441 |
442 | Params.prototype.mutate = function () {
443 | if (rnd(1)) this.p_base_freq += frnd(0.1) - 0.05;
444 | if (rnd(1)) this.p_freq_ramp += frnd(0.1) - 0.05;
445 | if (rnd(1)) this.p_freq_dramp += frnd(0.1) - 0.05;
446 | if (rnd(1)) this.p_duty += frnd(0.1) - 0.05;
447 | if (rnd(1)) this.p_duty_ramp += frnd(0.1) - 0.05;
448 | if (rnd(1)) this.p_vib_strength += frnd(0.1) - 0.05;
449 | if (rnd(1)) this.p_vib_speed += frnd(0.1) - 0.05;
450 | if (rnd(1)) this.p_vib_delay += frnd(0.1) - 0.05;
451 | if (rnd(1)) this.p_env_attack += frnd(0.1) - 0.05;
452 | if (rnd(1)) this.p_env_sustain += frnd(0.1) - 0.05;
453 | if (rnd(1)) this.p_env_decay += frnd(0.1) - 0.05;
454 | if (rnd(1)) this.p_env_punch += frnd(0.1) - 0.05;
455 | if (rnd(1)) this.p_lpf_resonance += frnd(0.1) - 0.05;
456 | if (rnd(1)) this.p_lpf_freq += frnd(0.1) - 0.05;
457 | if (rnd(1)) this.p_lpf_ramp += frnd(0.1) - 0.05;
458 | if (rnd(1)) this.p_hpf_freq += frnd(0.1) - 0.05;
459 | if (rnd(1)) this.p_hpf_ramp += frnd(0.1) - 0.05;
460 | if (rnd(1)) this.p_pha_offset += frnd(0.1) - 0.05;
461 | if (rnd(1)) this.p_pha_ramp += frnd(0.1) - 0.05;
462 | if (rnd(1)) this.p_repeat_speed += frnd(0.1) - 0.05;
463 | if (rnd(1)) this.p_arp_speed += frnd(0.1) - 0.05;
464 | if (rnd(1)) this.p_arp_mod += frnd(0.1) - 0.05;
465 | return this;
466 | }
467 |
468 | /*** Simpler namespaced functional API ***/
469 |
470 | sfxr = {};
471 |
472 | sfxr.toBuffer = function(synthdef) {
473 | return (new SoundEffect(synthdef)).getRawBuffer()["buffer"];
474 | };
475 |
476 | sfxr.toWebAudio = function(synthdef, audiocontext) {
477 | var sfx = new SoundEffect(synthdef);
478 | var buffer = sfx.getRawBuffer()["normalized"];
479 | if (audiocontext) {
480 | var buff = audiocontext.createBuffer(1, buffer.length, sfx.sampleRate);
481 | var nowBuffering = buff.getChannelData(0);
482 | for (var i = 0; i < buffer.length; i++) {
483 | nowBuffering[i] = buffer[i];
484 | }
485 | var proc = audiocontext.createBufferSource();
486 | proc.buffer = buff;
487 | return proc;
488 | }
489 | };
490 |
491 | sfxr.toWave = function(synthdef) {
492 | return (new SoundEffect(synthdef)).generate();
493 | };
494 |
495 | sfxr.toAudio = function(synthdef) {
496 | return sfxr.toWave(synthdef).getAudio();
497 | }
498 |
499 | sfxr.play = function(synthdef) {
500 | return sfxr.toAudio(synthdef).play();
501 | }
502 |
503 | sfxr.b58decode = function(b58encoded) {
504 | var decoded = function(S,A){var d=[],b=[],i,j,c,n;for(i in S){j=0,c=A.indexOf(S[i]);if(c<0)return undefined;c||b.length^i?i:b.push(0);while(j in d||c){n=d[j];n=n?n*58+c:c;c=n>>8;d[j]=n%256;j++}}while(j--)b.push(d[j]);return new Uint8Array(b)}(b58encoded,b58alphabet);
505 | var result = {};
506 | for (var pi in params_order) {
507 | var p = params_order[pi];
508 | var offset = (pi - 1) * 4 + 1;
509 | if (p == "wave_type") {
510 | result[p] = decoded[0];
511 | } else {
512 | var val = (decoded[offset] | (decoded[offset + 1] << 8) | (decoded[offset + 2] << 16) | (decoded[offset + 3] << 24));
513 | result[p] = numberToFloat(val);
514 | }
515 | }
516 | return result;
517 | }
518 |
519 | sfxr.b58encode = function(synthdef) {
520 | var p = new Params();
521 | p.fromJSON(synthdef);
522 | return p.toB58();
523 | }
524 |
525 | sfxr.generate = function(algorithm, options) {
526 | const p = new Params();
527 | const opts = options || {};
528 | p.sound_vol = opts["sound_vol"] || 0.25;
529 | p.sample_rate = opts["sample_rate"] || 44100;
530 | p.sample_size = opts["sample_size"] || 8;
531 | return p[algorithm]();
532 | }
533 |
534 | /*** Main entry point ***/
535 |
536 | function SoundEffect(ps) {
537 | if (typeof(ps) == "string") {
538 | var PARAMS = new Params();
539 | if (ps.indexOf("#") == 0) {
540 | ps = ps.slice(1);
541 | }
542 | ps = PARAMS.fromB58(ps);
543 | }
544 | this.init(ps);
545 | }
546 |
547 | SoundEffect.prototype.init = function (ps) {
548 | this.parameters = ps;
549 | this.initForRepeat(); // First time through, this is a bit of a misnomer
550 |
551 | // Waveform shape
552 | this.waveShape = parseInt(ps.wave_type);
553 |
554 | // Filter
555 | this.fltw = Math.pow(ps.p_lpf_freq, 3) * 0.1;
556 | this.enableLowPassFilter = (ps.p_lpf_freq != 1);
557 | this.fltw_d = 1 + ps.p_lpf_ramp * 0.0001;
558 | this.fltdmp = 5 / (1 + Math.pow(ps.p_lpf_resonance, 2) * 20) *
559 | (0.01 + this.fltw);
560 | if (this.fltdmp > 0.8) this.fltdmp=0.8;
561 | this.flthp = Math.pow(ps.p_hpf_freq, 2) * 0.1;
562 | this.flthp_d = 1 + ps.p_hpf_ramp * 0.0003;
563 |
564 | // Vibrato
565 | this.vibratoSpeed = Math.pow(ps.p_vib_speed, 2) * 0.01;
566 | this.vibratoAmplitude = ps.p_vib_strength * 0.5;
567 |
568 | // Envelope
569 | this.envelopeLength = [
570 | Math.floor(ps.p_env_attack * ps.p_env_attack * 100000),
571 | Math.floor(ps.p_env_sustain * ps.p_env_sustain * 100000),
572 | Math.floor(ps.p_env_decay * ps.p_env_decay * 100000)
573 | ];
574 | this.envelopePunch = ps.p_env_punch;
575 |
576 | // Flanger
577 | this.flangerOffset = Math.pow(ps.p_pha_offset, 2) * 1020;
578 | if (ps.p_pha_offset < 0) this.flangerOffset = -this.flangerOffset;
579 | this.flangerOffsetSlide = Math.pow(ps.p_pha_ramp, 2) * 1;
580 | if (ps.p_pha_ramp < 0) this.flangerOffsetSlide = -this.flangerOffsetSlide;
581 |
582 | // Repeat
583 | this.repeatTime = Math.floor(Math.pow(1 - ps.p_repeat_speed, 2) * 20000
584 | + 32);
585 | if (ps.p_repeat_speed === 0)
586 | this.repeatTime = 0;
587 |
588 | this.gain = Math.exp(ps.sound_vol) - 1;
589 |
590 | this.sampleRate = ps.sample_rate;
591 | this.bitsPerChannel = ps.sample_size;
592 | }
593 |
594 | SoundEffect.prototype.initForRepeat = function() {
595 | var ps = this.parameters;
596 | this.elapsedSinceRepeat = 0;
597 |
598 | this.period = 100 / (ps.p_base_freq * ps.p_base_freq + 0.001);
599 | this.periodMax = 100 / (ps.p_freq_limit * ps.p_freq_limit + 0.001);
600 | this.enableFrequencyCutoff = (ps.p_freq_limit > 0);
601 | this.periodMult = 1 - Math.pow(ps.p_freq_ramp, 3) * 0.01;
602 | this.periodMultSlide = -Math.pow(ps.p_freq_dramp, 3) * 0.000001;
603 |
604 | this.dutyCycle = 0.5 - ps.p_duty * 0.5;
605 | this.dutyCycleSlide = -ps.p_duty_ramp * 0.00005;
606 |
607 | if (ps.p_arp_mod >= 0)
608 | this.arpeggioMultiplier = 1 - Math.pow(ps.p_arp_mod, 2) * .9;
609 | else
610 | this.arpeggioMultiplier = 1 + Math.pow(ps.p_arp_mod, 2) * 10;
611 | this.arpeggioTime = Math.floor(Math.pow(1 - ps.p_arp_speed, 2) * 20000 + 32);
612 | if (ps.p_arp_speed === 1)
613 | this.arpeggioTime = 0;
614 | }
615 |
616 | SoundEffect.prototype.getRawBuffer = function () {
617 | var fltp = 0;
618 | var fltdp = 0;
619 | var fltphp = 0;
620 |
621 | var noise_buffer = Array(32);
622 | for (var i = 0; i < 32; ++i)
623 | noise_buffer[i] = Math.random() * 2 - 1;
624 |
625 | var envelopeStage = 0;
626 | var envelopeElapsed = 0;
627 |
628 | var vibratoPhase = 0;
629 |
630 | var phase = 0;
631 | var ipp = 0;
632 | var flanger_buffer = Array(1024);
633 | for (var i = 0; i < 1024; ++i)
634 | flanger_buffer[i] = 0;
635 |
636 | var num_clipped = 0;
637 |
638 | var buffer = [];
639 | var normalized = [];
640 |
641 | var sample_sum = 0;
642 | var num_summed = 0;
643 | var summands = Math.floor(44100 / this.sampleRate);
644 |
645 | for(var t = 0; ; ++t) {
646 |
647 | // Repeats
648 | if (this.repeatTime != 0 && ++this.elapsedSinceRepeat >= this.repeatTime)
649 | this.initForRepeat();
650 |
651 | // Arpeggio (single)
652 | if(this.arpeggioTime != 0 && t >= this.arpeggioTime) {
653 | this.arpeggioTime = 0;
654 | this.period *= this.arpeggioMultiplier;
655 | }
656 |
657 | // Frequency slide, and frequency slide slide!
658 | this.periodMult += this.periodMultSlide;
659 | this.period *= this.periodMult;
660 | if(this.period > this.periodMax) {
661 | this.period = this.periodMax;
662 | if (this.enableFrequencyCutoff)
663 | break;
664 | }
665 |
666 | // Vibrato
667 | var rfperiod = this.period;
668 | if (this.vibratoAmplitude > 0) {
669 | vibratoPhase += this.vibratoSpeed;
670 | rfperiod = this.period * (1 + Math.sin(vibratoPhase) * this.vibratoAmplitude);
671 | }
672 | var iperiod = Math.floor(rfperiod);
673 | if (iperiod < OVERSAMPLING) iperiod = OVERSAMPLING;
674 |
675 | // Square wave duty cycle
676 | this.dutyCycle += this.dutyCycleSlide;
677 | if (this.dutyCycle < 0) this.dutyCycle = 0;
678 | if (this.dutyCycle > 0.5) this.dutyCycle = 0.5;
679 |
680 | // Volume envelope
681 | if (++envelopeElapsed > this.envelopeLength[envelopeStage]) {
682 | envelopeElapsed = 0;
683 | if (++envelopeStage > 2)
684 | break;
685 | }
686 | var env_vol;
687 | var envf = envelopeElapsed / this.envelopeLength[envelopeStage];
688 | if (envelopeStage === 0) { // Attack
689 | env_vol = envf;
690 | } else if (envelopeStage === 1) { // Sustain
691 | env_vol = 1 + (1 - envf) * 2 * this.envelopePunch;
692 | } else { // Decay
693 | env_vol = 1 - envf;
694 | }
695 |
696 | // Flanger step
697 | this.flangerOffset += this.flangerOffsetSlide;
698 | var iphase = Math.abs(Math.floor(this.flangerOffset));
699 | if (iphase > 1023) iphase = 1023;
700 |
701 | if (this.flthp_d != 0) {
702 | this.flthp *= this.flthp_d;
703 | if (this.flthp < 0.00001)
704 | this.flthp = 0.00001;
705 | if (this.flthp > 0.1)
706 | this.flthp = 0.1;
707 | }
708 |
709 | // 8x oversampling
710 | var sample = 0;
711 | for (var si = 0; si < OVERSAMPLING; ++si) {
712 | var sub_sample = 0;
713 | phase++;
714 | if (phase >= iperiod) {
715 | phase %= iperiod;
716 | if (this.waveShape === NOISE)
717 | for(var i = 0; i < 32; ++i)
718 | noise_buffer[i] = Math.random() * 2 - 1;
719 | }
720 |
721 | // Base waveform
722 | var fp = phase / iperiod;
723 | if (this.waveShape === SQUARE) {
724 | if (fp < this.dutyCycle)
725 | sub_sample=0.5;
726 | else
727 | sub_sample=-0.5;
728 | } else if (this.waveShape === SAWTOOTH) {
729 | if (fp < this.dutyCycle)
730 | sub_sample = -1 + 2 * fp/this.dutyCycle;
731 | else
732 | sub_sample = 1 - 2 * (fp-this.dutyCycle)/(1-this.dutyCycle);
733 | } else if (this.waveShape === SINE) {
734 | sub_sample = Math.sin(fp * 2 * Math.PI);
735 | } else if (this.waveShape === NOISE) {
736 | sub_sample = noise_buffer[Math.floor(phase * 32 / iperiod)];
737 | } else {
738 | throw "ERROR: Bad wave type: " + this.waveShape;
739 | }
740 |
741 | // Low-pass filter
742 | var pp = fltp;
743 | this.fltw *= this.fltw_d;
744 | if (this.fltw < 0) this.fltw = 0;
745 | if (this.fltw > 0.1) this.fltw = 0.1;
746 | if (this.enableLowPassFilter) {
747 | fltdp += (sub_sample - fltp) * this.fltw;
748 | fltdp -= fltdp * this.fltdmp;
749 | } else {
750 | fltp = sub_sample;
751 | fltdp = 0;
752 | }
753 | fltp += fltdp;
754 |
755 | // High-pass filter
756 | fltphp += fltp - pp;
757 | fltphp -= fltphp * this.flthp;
758 | sub_sample = fltphp;
759 |
760 | // Flanger
761 | flanger_buffer[ipp & 1023] = sub_sample;
762 | sub_sample += flanger_buffer[(ipp - iphase + 1024) & 1023];
763 | ipp = (ipp + 1) & 1023;
764 |
765 | // final accumulation and envelope application
766 | sample += sub_sample * env_vol;
767 | }
768 |
769 | // Accumulate samples appropriately for sample rate
770 | sample_sum += sample;
771 | if (++num_summed >= summands) {
772 | num_summed = 0;
773 | sample = sample_sum / summands;
774 | sample_sum = 0;
775 | } else {
776 | continue;
777 | }
778 |
779 | sample = sample / OVERSAMPLING * masterVolume;
780 | sample *= this.gain;
781 |
782 | // store the original normalized floating point sample
783 | normalized.push(sample);
784 |
785 | if (this.bitsPerChannel === 8) {
786 | // Rescale [-1, 1) to [0, 256)
787 | sample = Math.floor((sample + 1) * 128);
788 | if (sample > 255) {
789 | sample = 255;
790 | ++num_clipped;
791 | } else if (sample < 0) {
792 | sample = 0;
793 | ++num_clipped;
794 | }
795 | buffer.push(sample);
796 | } else {
797 | // Rescale [-1, 1) to [-32768, 32768)
798 | sample = Math.floor(sample * (1<<15));
799 | if (sample >= (1<<15)) {
800 | sample = (1 << 15)-1;
801 | ++num_clipped;
802 | } else if (sample < -(1<<15)) {
803 | sample = -(1 << 15);
804 | ++num_clipped;
805 | }
806 | buffer.push(sample & 0xFF);
807 | buffer.push((sample >> 8) & 0xFF);
808 | }
809 | }
810 |
811 | return {
812 | "buffer": buffer,
813 | "normalized": normalized,
814 | "clipped": num_clipped,
815 | }
816 | }
817 |
818 | SoundEffect.prototype.generate = function() {
819 | var rendered = this.getRawBuffer();
820 | var wave = new RIFFWAVE();
821 | wave.header.sampleRate = this.sampleRate;
822 | wave.header.bitsPerSample = this.bitsPerChannel;
823 | wave.Make(rendered.buffer);
824 | wave.clipping = rendered.clipped;
825 | wave.buffer = rendered.normalized;
826 | wave.getAudio = _sfxr_getAudioFn(wave);
827 | return wave;
828 | }
829 |
830 | var _actx = null;
831 | var _sfxr_getAudioFn = function(wave) {
832 | return function() {
833 | // check for procedural audio
834 | var actx = null;
835 | if (!_actx) {
836 | if ('AudioContext' in window) {
837 | _actx = new AudioContext();
838 | } else if ('webkitAudioContext' in window) {
839 | _actx = new webkitAudioContext();
840 | }
841 | }
842 | actx = _actx;
843 |
844 | if (actx) {
845 | var buff = actx.createBuffer(1, wave.buffer.length, wave.header.sampleRate);
846 | var nowBuffering = buff.getChannelData(0);
847 | for (var i=0;i