├── .gitignore
├── jsfx.png
├── .editorconfig
├── package.json
├── music.html
├── LICENSE-MIT
├── synth.html
├── README.markdown
├── jsmusic.js
├── index.html
└── jsfx.js
/.gitignore:
--------------------------------------------------------------------------------
1 | *.~*
2 | ~*
--------------------------------------------------------------------------------
/jsfx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/loov/jsfx/HEAD/jsfx.png
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | end_of_line = lf
6 | max_line_length = 110
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 |
10 | [*.js]
11 | charset = utf-8
12 | indent_size = 4
13 | indent_style = tab
14 |
15 | [*.css]
16 | charset = utf-8
17 | indent_size = 4
18 | indent_style = tab
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "loov-jsfx",
3 | "version": "1.2.0",
4 | "description": "Package for sound effect generation.",
5 | "main": "jsfx.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/loov/jsfx.git"
12 | },
13 | "keywords": [
14 | "audio",
15 | "sound",
16 | "effects",
17 | "jsfx",
18 | "gamedev"
19 | ],
20 | "author": "Egon Elbre",
21 | "license": "MIT",
22 | "bugs": {
23 | "url": "https://github.com/loov/jsfx/issues"
24 | },
25 | "homepage": "http://loov.io/jsfx"
26 | }
27 |
--------------------------------------------------------------------------------
/music.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
30 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 Egon Elbre
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/synth.html:
--------------------------------------------------------------------------------
1 | Note this is broken on Chrome ATM.
2 |
Play with AWSEDFTGYHUJK keys.
3 |
4 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | 
2 |
3 | This is a JavaScript library for sound effect generation and is supported on
4 | [most current browsers](http://caniuse.com/#feat=audio).
5 |
6 | Generation speed is approximately 1s audio = 10ms processing. Of course that
7 | value can vary a lot, depending on the settings or browser that you use.
8 |
9 | ### How to use it?
10 |
11 | Open [index.html](http://loov.github.io/jsfx/) - this helps to pick out your samples.
12 |
13 | Try clicking the presets and tweaking all the options. Once you are satisified
14 | with your result click add button at top-right.
15 |
16 | Enter a name for the sound e.g. "select", repeat that as many times as you like. Tip: You can save your settings by making a bookmark of the page.
17 |
18 | At the bottom of the page there is a Library section. There you can relisten
19 | or remove sounds that you do not like.
20 |
21 | Once you are satisfied with your selection copy the JSON description
22 | (it's inside the input box).
23 |
24 | It will look something like:
25 |
26 | {"select":{"Volume":{"Sustain":0.1,"Decay":0.15,"Punch":0.55}}}
27 |
28 | To use that library, you need to include `jsfx.js` in your code and use `jsfx.Sounds(libarry)` to initialize it. For example:
29 |
30 | ```html
31 |
32 |
39 | Select
40 | Long
41 | ```
42 |
43 | Note that it will load with a delay to avoid blocking the page load for too
44 | long, so calling those function immediately may result in silence.
45 |
46 | ### Using with AudioContext (experimental)
47 |
48 | You can use AudioContext to procedurally generate the sounds, for example:
49 |
50 | ```html
51 |
52 |
62 | Static
63 | Dynamic
64 | Coin
65 | ```
66 |
67 | ### Few notes...
68 |
69 | It's recommended to copy the jsfx.js to your own project instead of
70 | automatically downloading the latest version. Since every slight adjustment
71 | to the audio generation code can affect the resulting audio significantly.
72 |
73 | The stable API is what is described in the README, everything else is
74 | subject to change.
75 |
76 | ### Thanks to
77 |
78 | This project was inspired by [sfxr](http://www.drpetter.se/project_sfxr.html)
79 | and was used as a reference for some algorithms and modes.
--------------------------------------------------------------------------------
/jsmusic.js:
--------------------------------------------------------------------------------
1 | (function(jsmusic){
2 | var scale = "c d ef g ha bC D EF G HA B";
3 |
4 | jsmusic.Simple = Simple;
5 | function Simple(songtext, instrument, bpm, modules){
6 | var tokens = songtext.split("");
7 | var notes = [];
8 | for(var i = 0; i < tokens.length; i+=1){
9 | var note = scale.indexOf(tokens[i]) + 3;
10 |
11 | var more = true;
12 | while(more){
13 | switch(tokens[i+1]){
14 | case "+": i++; note += 1; break;
15 | case "-": i++; note -= 1; break;
16 | case "^": i++; note += 12; break;
17 | case ".": i++; note -= 12; break;
18 | default:
19 | more = false;
20 | }
21 | }
22 | var freq = 220 * Math.pow(2, 1 + note/12);
23 | notes.push({Frequency: { Start: freq }});
24 | }
25 | return GenerateSong(notes, instrument, bpm, modules);
26 | };
27 |
28 |
29 | function GenerateSong(notes, instrument, bpm, modules){
30 | bpm = bpm || 120;
31 | var processor = new jsfx.Processor(instrument, modules);
32 | var sampleRate = processor.state.SampleRate;
33 |
34 | var beatSamples = sampleRate / (bpm / 60);
35 | var songSamples = notes.length * beatSamples;
36 |
37 | var beatBuffer = jsfx._createFloatArray(beatSamples);
38 | var playing = [];
39 |
40 | var songBuffer = jsfx._createFloatArray(songSamples + processor.getSamplesLeft());
41 | var currentStart = 0;
42 |
43 | // fill the beats
44 | for(var i = 0; i < notes.length; i += 1){
45 | var note = notes[i];
46 | var params = MergeParams(instrument, note);
47 | var proc = new jsfx.Processor(params, modules);
48 | playing.push(proc);
49 |
50 | playing.map(function(proc){
51 | proc.generate(beatBuffer);
52 | for(var i = 0; i < beatBuffer.length; i++){
53 | songBuffer[currentStart + i] += beatBuffer[i];
54 | };
55 | });
56 | currentStart += beatBuffer.length;
57 | playing = playing.filter(function(proc){ return !proc.finished; });
58 | }
59 |
60 | // fill the decay
61 | while(currentStart < songBuffer.length){
62 | if(playing.length == 0){break;}
63 | playing.map(function(proc){
64 | proc.generate(beatBuffer);
65 | var N = Math.min(beatBuffer.length, songBuffer.length - currentStart);
66 | for(var i = 0; i < N; i++){
67 | songBuffer[currentStart + i] += beatBuffer[i];
68 | };
69 | });
70 | currentStart += beatBuffer.length;
71 | playing = playing.filter(function(proc){ return !proc.finished; });
72 | }
73 |
74 | return jsfx.CreateAudio(songBuffer);
75 | }
76 |
77 |
78 | // modifies the override
79 | function MergeParams(base, override){
80 | if(typeof base === 'function'){
81 | base = base();
82 | } else {
83 | base = JSON.parse(JSON.stringify(base));
84 | }
85 |
86 | if(typeof override === 'function'){
87 | override = override();
88 | }
89 |
90 | for(var name in override){
91 | if(typeof base[name] === 'undefined'){
92 | base[name] = {};
93 | }
94 | for(var param in override[name]){
95 | base[name][param] = override[name][param];
96 | }
97 | }
98 |
99 | return base;
100 | }
101 |
102 |
103 | })(this.jsmusic = {});
104 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | jsfx
8 |
14 |
15 |
16 |
17 |
18 |
jsfx
19 |
20 |
21 |
22 |
Control
23 |
24 |
Play
25 |
Add
26 |
Download
27 |
28 |
29 |
30 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
Library
40 |
41 |
42 |
43 |
44 |
285 |
442 |
443 |
--------------------------------------------------------------------------------
/jsfx.js:
--------------------------------------------------------------------------------
1 | (function(root, factory) {
2 | if (typeof module === "object" && typeof module.exports === "object") {
3 | module.exports = factory();
4 | } else {
5 | root.jsfx = factory();
6 | }
7 | }(this, function() {
8 | 'use strict';
9 |
10 | var chr = String.fromCharCode;
11 | var TAU = +Math.PI * 2;
12 | var bitsPerSample = 16 | 0;
13 | var numChannels = 1 | 0;
14 | var sin = Math.sin;
15 | var pow = Math.pow;
16 | var abs = Math.abs;
17 | var EPSILON = 0.000001;
18 |
19 | var jsfx = {};
20 | var AudioContext = window.AudioContext || window.webkitAudioContext;
21 |
22 | jsfx.SampleRate = 0 | 0;
23 | jsfx.Sec = 0 | 0;
24 |
25 | jsfx.SetSampleRate = function(sampleRate) {
26 | jsfx.SampleRate = sampleRate | 0;
27 | jsfx.Sec = sampleRate | 0;
28 | };
29 | jsfx.SetSampleRate(getDefaultSampleRate());
30 |
31 | // MAIN API
32 |
33 | // Creates a new Audio object based on the params
34 | // params can be a params generating function or the actual parameters
35 | jsfx.Sound = function(params) {
36 | var processor = new Processor(params, jsfx.DefaultModules);
37 | var block = createFloatArray(processor.getSamplesLeft());
38 | processor.generate(block);
39 | return CreateAudio(block);
40 | };
41 |
42 | // Same as Sounds, but avoids locking the browser for too long
43 | // in case you have a large amount of sounds to generate
44 | jsfx.Sounds = function(library, ondone, onprogress) {
45 | var audio = {};
46 | var player = {};
47 | player._audio = audio;
48 |
49 | var toLoad = [];
50 |
51 | // create playing functions
52 | map_object(library, function(_, name) {
53 | player[name] = function() {
54 | if (typeof audio[name] !== "undefined") {
55 | audio[name].currentTime = 0.0;
56 | audio[name].play();
57 | }
58 | };
59 | toLoad.push(name);
60 | });
61 |
62 | var loaded = 0,
63 | total = toLoad.length;
64 |
65 | function next() {
66 | if (toLoad.length == 0) {
67 | ondone && ondone(sounds);
68 | return;
69 | }
70 | var name = toLoad.shift();
71 | audio[name] = jsfx.Sound(library[name]);
72 | loaded++;
73 | onprogress && onprogress(name, loaded, total);
74 |
75 | window.setTimeout(next, 30);
76 | }
77 | next();
78 |
79 | return player;
80 | }
81 |
82 | // SoundsImmediate takes a named set of params, and generates multiple
83 | // sound objects at once.
84 | jsfx.SoundsImmediate = function(library) {
85 | var audio = {};
86 | var player = {};
87 | player._audio = audio;
88 | map_object(library, function(params, name) {
89 | audio[name] = jsfx.Sound(params);
90 | player[name] = function() {
91 | if (typeof audio[name] !== "undefined") {
92 | audio[name].currentTime = 0.0;
93 | audio[name].play();
94 | }
95 | };
96 | })
97 | return player;
98 | };
99 |
100 | // FloatBuffer creates a FloatArray filled with audio
101 | jsfx.FloatBuffer = function(params, modules) {
102 | var processor = new Processor(params, jsfx.DefaultModules);
103 | var block = createFloatArray(processor.getSamplesLeft());
104 | processor.generate(block);
105 | return block;
106 | };
107 |
108 | if (typeof AudioContext !== "undefined") {
109 | // Node creates a new AudioContext ScriptProcessor that outputs the
110 | // sound. It will automatically disconnect, unless otherwise specified.
111 | jsfx.Node = function(audioContext, params, modules, bufferSize, stayConnected) {
112 | var node = audioContext.createScriptProcessor(bufferSize, 0, 1);
113 | var gen = new Processor(params, modules || jsfx.DefaultModules);
114 | node.onaudioprocess = function(ev) {
115 | var block = ev.outputBuffer.getChannelData(0);
116 | gen.generate(block);
117 | if (!stayConnected && gen.finished) {
118 | // we need to do an async disconnect, otherwise Chrome may
119 | // glitch
120 | setTimeout(function() {
121 | node.disconnect();
122 | }, 30);
123 | }
124 | }
125 | return node;
126 | }
127 |
128 | // AudioBuffer creates a buffer filled with the proper audio
129 | // This is useful, when you want to use AudioContext.BufferSource
130 | jsfx.AudioBuffer = function(audioContext, params, modules) {
131 | var processor = new Processor(params, modules || jsfx.DefaultModules);
132 | var buffer = audioContext.createBuffer(numChannels, processor.getSamplesLeft(), jsfx.SampleRate);
133 | var block = buffer.getChannelData(0);
134 | processor.generate(block);
135 | return buffer;
136 | };
137 |
138 | // Live creates an managed AudioContext for playing.
139 | // This is useful, when you want to use procedurally generated sounds.
140 | jsfx.Live = function(library, modules, BufferSize) {
141 | //TODO: add limit for number of notes played at the same time
142 | BufferSize = BufferSize || 2048;
143 | var player = {};
144 |
145 | var context = new AudioContext();
146 | var volume = context.createGain();
147 | volume.connect(context.destination);
148 |
149 | player._context = context;
150 | player._volume = volume;
151 |
152 | map_object(library, function(params, name) {
153 | player[name] = function() {
154 | var node = jsfx.Node(context, params, modules, BufferSize);
155 | node.connect(volume);
156 | };
157 | });
158 |
159 | player._close = function() {
160 | context.close();
161 | };
162 |
163 | player._play = function(params) {
164 | var node = jsfx.Node(context, params, modules, BufferSize);
165 | node.connect(volume);
166 | };
167 |
168 | return player;
169 | }
170 | } else {
171 | jsfx.Live = jsfx.Sounds;
172 | }
173 |
174 | // SOUND GENERATION
175 | jsfx.Module = {};
176 |
177 | // generators
178 | jsfx.G = {};
179 |
180 | var stage = jsfx.stage = {
181 | PhaseSpeed: 0,
182 | PhaseSpeedMod: 10,
183 | Generator: 20,
184 | SampleMod: 30,
185 | Volume: 40
186 | };
187 |
188 | function byStage(a, b) {
189 | return a.stage - b.stage;
190 | }
191 |
192 | jsfx.InitDefaultParams = InitDefaultParams;
193 |
194 | function InitDefaultParams(params, modules) {
195 | // setup modules
196 | for (var i = 0; i < modules.length; i += 1) {
197 | var M = modules[i];
198 | var P = params[M.name] || {};
199 |
200 | // add missing parameters
201 | map_object(M.params, function(def, name) {
202 | if (typeof P[name] === 'undefined') {
203 | P[name] = def.D;
204 | }
205 | });
206 |
207 | params[M.name] = P;
208 | }
209 | }
210 |
211 | // Generates a stateful sound effect processor
212 | // params can be a function that creates a parameter set
213 | jsfx.Processor = Processor;
214 |
215 | function Processor(params, modules) {
216 | params = params || {};
217 | modules = modules || jsfx.DefaultModules;
218 |
219 | if (typeof params === 'function') {
220 | params = params();
221 | } else {
222 | params = JSON.parse(JSON.stringify(params))
223 | }
224 | this.finished = false;
225 |
226 | this.state = {
227 | SampleRate: params.SampleRate || jsfx.SampleRate
228 | };
229 |
230 | // sort modules
231 | modules = modules.slice();
232 | modules.sort(byStage)
233 | this.modules = modules;
234 |
235 | // init missing params
236 | InitDefaultParams(params, modules);
237 |
238 | // setup modules
239 | for (var i = 0; i < this.modules.length; i += 1) {
240 | var M = this.modules[i];
241 | this.modules[i].setup(this.state, params[M.name]);
242 | }
243 | }
244 | Processor.prototype = {
245 | //TODO: see whether this can be converted to a module
246 | generate: function(block) {
247 | for (var i = 0 | 0; i < block.length; i += 1) {
248 | block[i] = 0;
249 | }
250 | if (this.finished) {
251 | return;
252 | }
253 |
254 | var $ = this.state,
255 | N = block.length | 0;
256 | for (var i = 0; i < this.modules.length; i += 1) {
257 | var M = this.modules[i];
258 | var n = M.process($, block.subarray(0, N)) | 0;
259 | N = Math.min(N, n);
260 | }
261 | if (N < block.length) {
262 | this.finished = true;
263 | }
264 | for (var i = N; i < block.length; i++) {
265 | block[i] = 0;
266 | }
267 | },
268 | getSamplesLeft: function() {
269 | var samples = 0;
270 | for (var i = 0; i < this.state.envelopes.length; i += 1) {
271 | samples += this.state.envelopes[i].N;
272 | }
273 | if (samples === 0) {
274 | samples = 3 * this.state.SampleRate;
275 | }
276 | return samples;
277 | }
278 | };
279 |
280 | // Frequency
281 | jsfx.Module.Frequency = {
282 | name: 'Frequency',
283 | params: {
284 | /* beautify preserve:start */
285 | Start: { L:30, H:1800, D:440 },
286 |
287 | Min: { L:30, H:1800, D:30 },
288 | Max: { L:30, H:1800, D:1800 },
289 |
290 | Slide: { L:-1, H:1, D:0 },
291 | DeltaSlide: { L:-1, H:1, D:0 },
292 |
293 | RepeatSpeed: { L:0, H: 3.0, D: 0 },
294 |
295 | ChangeAmount: { L:-12, H:12, D:0 },
296 | ChangeSpeed : { L: 0, H:1, D:0 }
297 | /* beautify preserve:end */
298 | },
299 | stage: stage.PhaseSpeed,
300 | setup: function($, P) {
301 | var SR = $.SampleRate;
302 |
303 | $.phaseParams = P;
304 |
305 | $.phaseSpeed = P.Start * TAU / SR;
306 | $.phaseSpeedMax = P.Max * TAU / SR;
307 | $.phaseSpeedMin = P.Min * TAU / SR;
308 |
309 | $.phaseSpeedMin = Math.min($.phaseSpeedMin, $.phaseSpeed);
310 | $.phaseSpeedMax = Math.max($.phaseSpeedMax, $.phaseSpeed);
311 |
312 | $.phaseSlide = 1.0 + pow(P.Slide, 3.0) * 64.0 / SR;
313 | $.phaseDeltaSlide = pow(P.DeltaSlide, 3.0) / (SR * 1000);
314 |
315 | $.repeatTime = 0;
316 | $.repeatLimit = Infinity;
317 | if (P.RepeatSpeed > 0) {
318 | $.repeatLimit = P.RepeatSpeed * SR;
319 | }
320 |
321 | $.arpeggiatorTime = 0;
322 | $.arpeggiatorLimit = P.ChangeSpeed * SR;
323 | if (P.ChangeAmount == 0) {
324 | $.arpeggiatorLimit = Infinity;
325 | }
326 | $.arpeggiatorMod = 1 + P.ChangeAmount / 12.0;
327 | },
328 | process: function($, block) {
329 | var speed = +$.phaseSpeed,
330 | min = +$.phaseSpeedMin,
331 | max = +$.phaseSpeedMax,
332 | slide = +$.phaseSlide,
333 | deltaSlide = +$.phaseDeltaSlide;
334 |
335 | var repeatTime = $.repeatTime,
336 | repeatLimit = $.repeatLimit;
337 |
338 | var arpTime = $.arpeggiatorTime,
339 | arpLimit = $.arpeggiatorLimit,
340 | arpMod = $.arpeggiatorMod;
341 |
342 | for (var i = 0; i < block.length; i++) {
343 | slide += deltaSlide;
344 | speed *= slide;
345 | speed = speed < min ? min : speed > max ? max : speed;
346 |
347 | if (repeatTime > repeatLimit) {
348 | this.setup($, $.phaseParams);
349 | return i + this.process($, block.subarray(i)) - 1;
350 | }
351 | repeatTime++;
352 |
353 | if (arpTime > arpLimit) {
354 | speed *= arpMod;
355 | arpTime = 0;
356 | arpLimit = Infinity;
357 | }
358 | arpTime++;
359 |
360 | block[i] += speed;
361 | }
362 |
363 | $.repeatTime = repeatTime;
364 | $.arpeggiatorTime = arpTime;
365 | $.arpeggiatorLimit = arpLimit;
366 |
367 | $.phaseSpeed = speed;
368 | $.phaseSlide = slide;
369 |
370 | return block.length;
371 | }
372 | };
373 |
374 | // Vibrato
375 | jsfx.Module.Vibrato = {
376 | name: 'Vibrato',
377 | params: {
378 | /* beautify preserve:start */
379 | Depth: {L: 0, H:1, D:0},
380 | DepthSlide: {L:-1, H:1, D:0},
381 |
382 | Frequency: {L: 0.01, H:48, D:0},
383 | FrequencySlide: {L: -1.00, H: 1, D:0}
384 | /* beautify preserve:end */
385 | },
386 | stage: stage.PhaseSpeedMod,
387 | setup: function($, P) {
388 | var SR = $.SampleRate;
389 | $.vibratoPhase = 0;
390 | $.vibratoDepth = P.Depth;
391 | $.vibratoPhaseSpeed = P.Frequency * TAU / SR;
392 |
393 | $.vibratoPhaseSpeedSlide = 1.0 + pow(P.FrequencySlide, 3.0) * 3.0 / SR;
394 | $.vibratoDepthSlide = P.DepthSlide / SR;
395 | },
396 | process: function($, block) {
397 | var phase = +$.vibratoPhase,
398 | depth = +$.vibratoDepth,
399 | speed = +$.vibratoPhaseSpeed,
400 | slide = +$.vibratoPhaseSpeedSlide,
401 | depthSlide = +$.vibratoDepthSlide;
402 |
403 | if ((depth == 0) && (depthSlide <= 0)) {
404 | return block.length;
405 | }
406 |
407 | for (var i = 0; i < block.length; i++) {
408 | phase += speed;
409 | if (phase > TAU) {
410 | phase -= TAU
411 | };
412 | block[i] += block[i] * sin(phase) * depth;
413 |
414 | speed *= slide;
415 | depth += depthSlide;
416 | depth = clamp1(depth);
417 | }
418 |
419 | $.vibratoPhase = phase;
420 | $.vibratoDepth = depth;
421 | $.vibratoPhaseSpeed = speed;
422 | return block.length;
423 | }
424 | };
425 |
426 | // Generator
427 | jsfx.Module.Generator = {
428 | name: 'Generator',
429 | params: {
430 | /* beautify preserve:start */
431 | // C = choose
432 | Func: {C: jsfx.G, D:'square'},
433 |
434 | A: {L: 0, H: 1, D: 0},
435 | B: {L: 0, H: 1, D: 0},
436 |
437 | ASlide: {L: -1, H: 1, D: 0},
438 | BSlide: {L: -1, H: 1, D: 0}
439 | /* beautify preserve:end */
440 | },
441 | stage: stage.Generator,
442 | setup: function($, P) {
443 | $.generatorPhase = 0;
444 |
445 | if (typeof P.Func === 'string') {
446 | $.generator = jsfx.G[P.Func];
447 | } else {
448 | $.generator = P.Func;
449 | }
450 | if (typeof $.generator === 'object') {
451 | $.generator = $.generator.create();
452 | }
453 | assert(typeof $.generator === 'function', 'generator must be a function')
454 |
455 | $.generatorA = P.A;
456 | $.generatorASlide = P.ASlide;
457 | $.generatorB = P.B;
458 | $.generatorBSlide = P.BSlide;
459 | },
460 | process: function($, block) {
461 | return $.generator($, block);
462 | }
463 | };
464 |
465 | // Karplus Strong algorithm for string sound
466 | var GuitarBufferSize = 1 << 16;
467 | jsfx.Module.Guitar = {
468 | name: 'Guitar',
469 | params: {
470 | /* beautify preserve:start */
471 | A: {L:0.0, H:1.0, D: 1},
472 | B: {L:0.0, H:1.0, D: 1},
473 | C: {L:0.0, H:1.0, D: 1}
474 | /* beautify preserve:end */
475 | },
476 | stage: stage.Generator,
477 | setup: function($, P) {
478 | $.guitarA = P.A;
479 | $.guitarB = P.B;
480 | $.guitarC = P.C;
481 |
482 | $.guitarBuffer = createFloatArray(GuitarBufferSize);
483 | $.guitarHead = 0;
484 | var B = $.guitarBuffer;
485 | for (var i = 0; i < B.length; i++) {
486 | B[i] = Math.random() * 2 - 1;
487 | }
488 | },
489 | process: function($, block) {
490 | var BS = GuitarBufferSize,
491 | BM = BS - 1;
492 |
493 | var A = +$.guitarA,
494 | B = +$.guitarB,
495 | C = +$.guitarC;
496 | var T = A + B + C;
497 | var h = $.guitarHead;
498 |
499 | var buffer = $.guitarBuffer;
500 | for (var i = 0; i < block.length; i++) {
501 | // buffer size
502 | var n = (TAU / block[i]) | 0;
503 | n = n > BS ? BS : n;
504 |
505 | // tail
506 | var t = ((h - n) + BS) & BM;
507 | buffer[h] =
508 | (buffer[(t - 0 + BS) & BM] * A +
509 | buffer[(t - 1 + BS) & BM] * B +
510 | buffer[(t - 2 + BS) & BM] * C) / T;
511 |
512 | block[i] = buffer[h];
513 | h = (h + 1) & BM;
514 | }
515 |
516 | $.guitarHead = h;
517 | return block.length;
518 | }
519 | }
520 |
521 | // Low/High-Pass Filter
522 | jsfx.Module.Filter = {
523 | name: 'Filter',
524 | params: {
525 | /* beautify preserve:start */
526 | LP: {L: 0, H:1, D:1},
527 | LPSlide: {L:-1, H:1, D:0},
528 | LPResonance: {L: 0, H:1, D:0},
529 | HP: {L: 0, H:1, D:0},
530 | HPSlide: {L:-1, H:1, D:0}
531 | /* beautify preserve:end */
532 | },
533 | stage: stage.SampleMod + 0,
534 | setup: function($, P) {
535 | $.FilterEnabled = (P.HP > EPSILON) || (P.LP < 1 - EPSILON);
536 |
537 | $.LPEnabled = P.LP < 1 - EPSILON;
538 | $.LP = pow(P.LP, 3.0) / 10;
539 | $.LPSlide = 1.0 + P.LPSlide * 100 / $.SampleRate;
540 | $.LPPos = 0;
541 | $.LPPosSlide = 0;
542 |
543 | $.LPDamping = 5.0 / (1.0 + pow(P.LPResonance, 2) * 20) * (0.01 + P.LP);
544 | $.LPDamping = 1.0 - Math.min($.LPDamping, 0.8);
545 |
546 | $.HP = pow(P.HP, 2.0) / 10;
547 | $.HPPos = 0;
548 | $.HPSlide = 1.0 + P.HPSlide * 100 / $.SampleRate;
549 | },
550 | enabled: function($) {
551 | return $.FilterEnabled;
552 | },
553 | process: function($, block) {
554 | if (!this.enabled($)) {
555 | return block.length;
556 | }
557 |
558 | var lp = +$.LP;
559 | var lpPos = +$.LPPos;
560 | var lpPosSlide = +$.LPPosSlide;
561 | var lpSlide = +$.LPSlide;
562 | var lpDamping = +$.LPDamping;
563 | var lpEnabled = +$.LPEnabled;
564 |
565 | var hp = +$.HP;
566 | var hpPos = +$.HPPos;
567 | var hpSlide = +$.HPSlide;
568 |
569 | for (var i = 0; i < block.length; i++) {
570 | if ((hp > EPSILON) || (hp < -EPSILON)) {
571 | hp *= hpSlide;
572 | hp = hp < EPSILON ? EPSILON : hp > 0.1 ? 0.1 : hp;
573 | }
574 |
575 | var lpPos_ = lpPos;
576 |
577 | lp *= lpSlide;
578 | lp = lp < 0 ? lp = 0 : lp > 0.1 ? 0.1 : lp;
579 |
580 | var sample = block[i];
581 | if (lpEnabled) {
582 | lpPosSlide += (sample - lpPos) * lp;
583 | lpPosSlide *= lpDamping;
584 | } else {
585 | lpPos = sample;
586 | lpPosSlide = 0;
587 | }
588 | lpPos += lpPosSlide;
589 |
590 | hpPos += lpPos - lpPos_;
591 | hpPos *= 1.0 - hp;
592 |
593 | block[i] = hpPos;
594 | }
595 |
596 | $.LPPos = lpPos;
597 | $.LPPosSlide = lpPosSlide;
598 | $.LP = lp;
599 | $.HP = hp;
600 | $.HPPos = hpPos;
601 |
602 | return block.length;
603 | }
604 | };
605 |
606 | // Phaser Effect
607 | var PhaserBufferSize = 1 << 10;
608 | jsfx.Module.Phaser = {
609 | name: 'Phaser',
610 | params: {
611 | /* beautify preserve:start */
612 | Offset: {L:-1, H:1, D:0},
613 | Sweep: {L:-1, H:1, D:0}
614 | /* beautify preserve:end */
615 | },
616 | stage: stage.SampleMod + 1,
617 | setup: function($, P) {
618 | $.phaserBuffer = createFloatArray(PhaserBufferSize);
619 | $.phaserPos = 0;
620 | $.phaserOffset = pow(P.Offset, 2.0) * (PhaserBufferSize - 4);
621 | $.phaserOffsetSlide = pow(P.Sweep, 3.0) * 4000 / $.SampleRate;
622 | },
623 | enabled: function($) {
624 | return (abs($.phaserOffsetSlide) > EPSILON) ||
625 | (abs($.phaserOffset) > EPSILON);
626 | },
627 | process: function($, block) {
628 | if (!this.enabled($)) {
629 | return block.length;
630 | }
631 |
632 | var BS = PhaserBufferSize,
633 | BM = BS - 1;
634 |
635 | var buffer = $.phaserBuffer,
636 | pos = $.phaserPos | 0,
637 | offset = +$.phaserOffset,
638 | offsetSlide = +$.phaserOffsetSlide;
639 |
640 | for (var i = 0; i < block.length; i++) {
641 | offset += offsetSlide;
642 | //TODO: check whether this is correct
643 | if (offset < 0) {
644 | offset = -offset;
645 | offsetSlide = -offsetSlide;
646 | }
647 | if (offset > BM) {
648 | offset = BM;
649 | offsetSlide = 0;
650 | }
651 |
652 | buffer[pos] = block[i];
653 | var p = (pos - (offset | 0) + BS) & BM;
654 | block[i] += buffer[p];
655 |
656 | pos = ((pos + 1) & BM) | 0;
657 | }
658 |
659 | $.phaserPos = pos;
660 | $.phaserOffset = offset;
661 | return block.length;
662 | }
663 | };
664 |
665 | // Volume dynamic control with Attack-Sustain-Decay
666 | // ATTACK | 0 - Volume + Punch
667 | // SUSTAIN | Volume + Punch - Volume
668 | // DECAY | Volume - 0
669 | jsfx.Module.Volume = {
670 | name: 'Volume',
671 | params: {
672 | /* beautify preserve:start */
673 | Master: { L: 0, H: 1, D: 0.5 },
674 | Attack: { L: 0.001, H: 1, D: 0.01 },
675 | Sustain: { L: 0, H: 2, D: 0.3 },
676 | Punch: { L: 0, H: 3, D: 1.0 },
677 | Decay: { L: 0.001, H: 2, D: 1.0 }
678 | /* beautify preserve:end */
679 | },
680 | stage: stage.Volume,
681 | setup: function($, P) {
682 | var SR = $.SampleRate;
683 | var V = P.Master;
684 | var VP = V * (1 + P.Punch);
685 | $.envelopes = [
686 | // S = start volume, E = end volume, N = duration in samples
687 | {
688 | S: 0,
689 | E: V,
690 | N: (P.Attack * SR) | 0
691 | }, // Attack
692 | {
693 | S: VP,
694 | E: V,
695 | N: (P.Sustain * SR) | 0
696 | }, // Sustain
697 | {
698 | S: V,
699 | E: 0,
700 | N: (P.Decay * SR) | 0
701 | } // Decay
702 | ];
703 | // G = volume gradient
704 | for (var i = 0; i < $.envelopes.length; i += 1) {
705 | var e = $.envelopes[i];
706 | e.G = (e.E - e.S) / e.N;
707 | }
708 | },
709 | process: function($, block) {
710 | var i = 0;
711 | while (($.envelopes.length > 0) && (i < block.length)) {
712 | var E = $.envelopes[0];
713 | var vol = E.S,
714 | grad = E.G;
715 |
716 | var N = Math.min(block.length - i, E.N) | 0;
717 | var end = (i + N) | 0;
718 | for (; i < end; i += 1) {
719 | block[i] *= vol;
720 | vol += grad;
721 | vol = clamp(vol, 0, 10);
722 | }
723 | E.S = vol;
724 | E.N -= N;
725 | if (E.N <= 0) {
726 | $.envelopes.shift();
727 | }
728 | }
729 | return i;
730 | }
731 | };
732 |
733 | // PRESETS
734 |
735 | jsfx.DefaultModules = [
736 | jsfx.Module.Frequency,
737 | jsfx.Module.Vibrato,
738 | jsfx.Module.Generator,
739 | jsfx.Module.Filter,
740 | jsfx.Module.Phaser,
741 | jsfx.Module.Volume
742 | ];
743 | jsfx.DefaultModules.sort(byStage);
744 |
745 | jsfx.EmptyParams = EmptyParams;
746 |
747 | function EmptyParams() {
748 | return map_object(jsfx.Module, function() {
749 | return {}
750 | });
751 | }
752 |
753 | jsfx._RemoveEmptyParams = RemoveEmptyParams;
754 |
755 | function RemoveEmptyParams(params) {
756 | for (var name in params) {
757 | if (Object_keys(params[name]).length == 0) {
758 | delete params[name];
759 | }
760 | }
761 | };
762 |
763 | jsfx.Preset = {
764 | Reset: function() {
765 | return EmptyParams();
766 | },
767 | Coin: function() {
768 | var p = EmptyParams();
769 | p.Frequency.Start = runif(880, 660);
770 | p.Volume.Sustain = runif(0.1);
771 | p.Volume.Decay = runif(0.4, 0.1);
772 | p.Volume.Punch = runif(0.3, 0.3);
773 | if (runif() < 0.5) {
774 | p.Frequency.ChangeSpeed = runif(0.15, 0.1);
775 | p.Frequency.ChangeAmount = runif(8, 4);
776 | }
777 | RemoveEmptyParams(p);
778 | return p;
779 | },
780 | Laser: function() {
781 | var p = EmptyParams();
782 | p.Generator.Func = rchoose(['square', 'saw', 'sine']);
783 |
784 | if (runif() < 0.33) {
785 | p.Frequency.Start = runif(880, 440);
786 | p.Frequency.Min = runif(0.1);
787 | p.Frequency.Slide = runif(0.3, -0.8);
788 | } else {
789 | p.Frequency.Start = runif(1200, 440);
790 | p.Frequency.Min = p.Frequency.Start - runif(880, 440);
791 | if (p.Frequency.Min < 110) {
792 | p.Frequency.Min = 110;
793 | }
794 | p.Frequency.Slide = runif(0.3, -1);
795 | }
796 |
797 | if (runif() < 0.5) {
798 | p.Generator.A = runif(0.5);
799 | p.Generator.ASlide = runif(0.2);
800 | } else {
801 | p.Generator.A = runif(0.5, 0.4);
802 | p.Generator.ASlide = runif(0.7);
803 | }
804 |
805 | p.Volume.Sustain = runif(0.2, 0.1);
806 | p.Volume.Decay = runif(0.4);
807 | if (runif() < 0.5) {
808 | p.Volume.Punch = runif(0.3);
809 | }
810 | if (runif() < 0.33) {
811 | p.Phaser.Offset = runif(0.2);
812 | p.Phaser.Sweep = runif(0.2);
813 | }
814 | if (runif() < 0.5) {
815 | p.Filter.HP = runif(0.3);
816 | }
817 | RemoveEmptyParams(p);
818 | return p;
819 | },
820 | Explosion: function() {
821 | var p = EmptyParams();
822 | p.Generator.Func = 'noise';
823 | if (runif() < 0.5) {
824 | p.Frequency.Start = runif(440, 40);
825 | p.Frequency.Slide = runif(0.4, -0.1);
826 | } else {
827 | p.Frequency.Start = runif(1600, 220);
828 | p.Frequency.Slide = runif(-0.2, -0.2);
829 | }
830 |
831 | if (runif() < 0.2) {
832 | p.Frequency.Slide = 0;
833 | }
834 | if (runif() < 0.3) {
835 | p.Frequency.RepeatSpeed = runif(0.5, 0.3);
836 | }
837 |
838 | p.Volume.Sustain = runif(0.3, 0.1);
839 | p.Volume.Decay = runif(0.5);
840 | p.Volume.Punch = runif(0.6, 0.2);
841 |
842 | if (runif() < 0.5) {
843 | p.Phaser.Offset = runif(0.9, -0.3);
844 | p.Phaser.Sweep = runif(-0.3);
845 | }
846 |
847 | if (runif() < 0.33) {
848 | p.Frequency.ChangeSpeed = runif(0.3, 0.6);
849 | p.Frequency.ChangeAmount = runif(24, -12);
850 | }
851 | RemoveEmptyParams(p);
852 | return p;
853 | },
854 | Powerup: function() {
855 | var p = EmptyParams();
856 | if (runif() < 0.5) {
857 | p.Generator.Func = 'saw';
858 | } else {
859 | p.Generator.A = runif(0.6);
860 | }
861 |
862 | p.Frequency.Start = runif(220, 440);
863 | if (runif() < 0.5) {
864 | p.Frequency.Slide = runif(0.5, 0.2);
865 | p.Frequency.RepeatSpeed = runif(0.4, 0.4);
866 | } else {
867 | p.Frequency.Slide = runif(0.2, 0.05);
868 | if (runif() < 0.5) {
869 | p.Vibrato.Depth = runif(0.6, 0.1);
870 | p.Vibrato.Frequency = runif(30, 10);
871 | }
872 | }
873 |
874 | p.Volume.Sustain = runif(0.4);
875 | p.Volume.Decay = runif(0.4, 0.1);
876 |
877 | RemoveEmptyParams(p);
878 | return p;
879 | },
880 | Hit: function() {
881 | var p = EmptyParams();
882 | p.Generator.Func = rchoose(['square', 'saw', 'noise']);
883 | p.Generator.A = runif(0.6);
884 | p.Generator.ASlide = runif(1, -0.5);
885 |
886 | p.Frequency.Start = runif(880, 220);
887 | p.Frequency.Slide = -runif(0.4, 0.3);
888 |
889 | p.Volume.Sustain = runif(0.1);
890 | p.Volume.Decay = runif(0.2, 0.1);
891 |
892 | if (runif() < 0.5) {
893 | p.Filter.HP = runif(0.3);
894 | }
895 |
896 | RemoveEmptyParams(p);
897 | return p;
898 | },
899 | Jump: function() {
900 | var p = EmptyParams();
901 | p.Generator.Func = 'square';
902 | p.Generator.A = runif(0.6);
903 |
904 | p.Frequency.Start = runif(330, 330);
905 | p.Frequency.Slide = runif(0.4, 0.2);
906 |
907 | p.Volume.Sustain = runif(0.3, 0.1);
908 | p.Volume.Decay = runif(0.2, 0.1);
909 |
910 | if (runif() < 0.5) {
911 | p.Filter.HP = runif(0.3);
912 | }
913 | if (runif() < 0.3) {
914 | p.Filter.LP = runif(-0.6, 1);
915 | }
916 |
917 | RemoveEmptyParams(p);
918 | return p;
919 | },
920 | Select: function() {
921 | var p = EmptyParams();
922 | p.Generator.Func = rchoose(['square', 'saw']);
923 | p.Generator.A = runif(0.6);
924 |
925 | p.Frequency.Start = runif(660, 220);
926 |
927 | p.Volume.Sustain = runif(0.1, 0.1);
928 | p.Volume.Decay = runif(0.2);
929 |
930 | p.Filter.HP = 0.2;
931 | RemoveEmptyParams(p);
932 | return p;
933 | },
934 | Lucky: function() {
935 | var p = EmptyParams();
936 | map_object(p, function(out, moduleName) {
937 | var defs = jsfx.Module[moduleName].params;
938 | map_object(defs, function(def, name) {
939 | if (def.C) {
940 | var values = Object_keys(def.C);
941 | out[name] = values[(values.length * Math.random()) | 0];
942 | } else {
943 | out[name] = Math.random() * (def.H - def.L) + def.L;
944 | }
945 | });
946 | });
947 | p.Volume.Master = 0.4;
948 | p.Filter = {}; // disable filter, as it usually will clip everything
949 | RemoveEmptyParams(p);
950 | return p;
951 | }
952 | };
953 |
954 | // GENERATORS
955 |
956 | // uniform noise
957 | jsfx.G.unoise = newGenerator("sample = Math.random();");
958 | // sine wave
959 | jsfx.G.sine = newGenerator("sample = Math.sin(phase);");
960 | // saw wave
961 | jsfx.G.saw = newGenerator("sample = 2*(phase/TAU - ((phase/TAU + 0.5)|0));");
962 | // triangle wave
963 | jsfx.G.triangle = newGenerator("sample = Math.abs(4 * ((phase/TAU - 0.25)%1) - 2) - 1;");
964 | // square wave
965 | jsfx.G.square = newGenerator("var s = Math.sin(phase); sample = s > A ? 1.0 : s < A ? -1.0 : A;");
966 | // simple synth
967 | jsfx.G.synth = newGenerator("sample = Math.sin(phase) + .5*Math.sin(phase/2) + .3*Math.sin(phase/4);");
968 |
969 | // STATEFUL
970 | var __noiseLast = 0;
971 | jsfx.G.noise = newGenerator("if(phase % TAU < 4){__noiseLast = Math.random() * 2 - 1;} sample = __noiseLast;");
972 |
973 | // Karplus-Strong string
974 | jsfx.G.string = {
975 | create: function() {
976 | var BS = 1 << 16;
977 | var BM = BS - 1;
978 |
979 | var buffer = createFloatArray(BS);
980 | for (var i = 0; i < buffer.length; i++) {
981 | buffer[i] = Math.random() * 2 - 1;
982 | }
983 |
984 | var head = 0;
985 | return function($, block) {
986 | var TAU = Math.PI * 2;
987 | var A = +$.generatorA,
988 | ASlide = +$.generatorASlide,
989 | B = +$.generatorB,
990 | BSlide = +$.generatorBSlide;
991 | var buf = buffer;
992 |
993 | for (var i = 0; i < block.length; i++) {
994 | var phaseSpeed = block[i];
995 | var n = (TAU / phaseSpeed) | 0;
996 | A += ASlide;
997 | B += BSlide;
998 | A = A < 0 ? 0 : A > 1 ? 1 : A;
999 | B = B < 0 ? 0 : B > 1 ? 1 : B;
1000 |
1001 | var t = ((head - n) + BS) & BM;
1002 | var sample = (
1003 | buf[(t - 0 + BS) & BM] * 1 +
1004 | buf[(t - 1 + BS) & BM] * A +
1005 | buf[(t - 2 + BS) & BM] * B) / (1 + A + B);
1006 |
1007 | buf[head] = sample;
1008 | block[i] = buf[head];
1009 | head = (head + 1) & BM;
1010 | }
1011 |
1012 | $.generatorA = A;
1013 | $.generatorB = B;
1014 | return block.length;
1015 | }
1016 | }
1017 | };
1018 |
1019 | // Generates samples using given frequency and generator
1020 | function newGenerator(line) {
1021 | return new Function("$", "block", "" +
1022 | "var TAU = Math.PI * 2;\n" +
1023 | "var sample;\n" +
1024 | "var phase = +$.generatorPhase,\n" +
1025 | " A = +$.generatorA, ASlide = +$.generatorASlide,\n" +
1026 | " B = +$.generatorB, BSlide = +$.generatorBSlide;\n" +
1027 | "\n" +
1028 | "for(var i = 0; i < block.length; i++){\n" +
1029 | " var phaseSpeed = block[i];\n" +
1030 | " phase += phaseSpeed;\n" +
1031 | " if(phase > TAU){ phase -= TAU };\n" +
1032 | " A += ASlide; B += BSlide;\n" +
1033 | " A = A < 0 ? 0 : A > 1 ? 1 : A;\n" +
1034 | " B = B < 0 ? 0 : B > 1 ? 1 : B;\n" +
1035 | line +
1036 | " block[i] = sample;\n" +
1037 | "}\n" +
1038 | "\n" +
1039 | "$.generatorPhase = phase;\n" +
1040 | "$.generatorA = A;\n" +
1041 | "$.generatorB = B;\n" +
1042 | "return block.length;\n" +
1043 | "");
1044 | }
1045 |
1046 | // WAVE SUPPORT
1047 |
1048 | // Creates an Wave byte array from audio data [-1.0 .. 1.0]
1049 | jsfx.CreateWave = CreateWave;
1050 |
1051 | function CreateWave(data) {
1052 | if (typeof Float32Array !== "undefined") {
1053 | assert(data instanceof Float32Array, 'data must be an Float32Array');
1054 | }
1055 |
1056 | var blockAlign = numChannels * bitsPerSample >> 3;
1057 | var byteRate = jsfx.SampleRate * blockAlign;
1058 |
1059 | var output = createByteArray(8 + 36 + data.length * 2);
1060 | var p = 0;
1061 |
1062 | // emits string to output
1063 | function S(value) {
1064 | for (var i = 0; i < value.length; i += 1) {
1065 | output[p] = value.charCodeAt(i);
1066 | p++;
1067 | }
1068 | }
1069 |
1070 | // emits integer value to output
1071 | function V(value, nBytes) {
1072 | if (nBytes <= 0) {
1073 | return;
1074 | }
1075 | output[p] = value & 0xFF;
1076 | p++;
1077 | V(value >> 8, nBytes - 1);
1078 | }
1079 | /* beautify preserve:start */
1080 | S('RIFF'); V(36 + data.length * 2, 4);
1081 |
1082 | S('WAVEfmt '); V(16, 4); V(1, 2);
1083 | V(numChannels, 2); V(jsfx.SampleRate, 4);
1084 | V(byteRate, 4); V(blockAlign, 2); V(bitsPerSample, 2);
1085 |
1086 | S('data'); V(data.length * 2, 4);
1087 | CopyFToU8(output.subarray(p), data);
1088 | /* beautify preserve:end */
1089 |
1090 | return output;
1091 | };
1092 |
1093 | // Creates an Audio element from audio data [-1.0 .. 1.0]
1094 | jsfx.CreateAudio = CreateAudio;
1095 |
1096 | function CreateAudio(data) {
1097 | var wave = CreateWave(data);
1098 | return new Audio('data:audio/wav;base64,' + U8ToB64(wave));
1099 | };
1100 |
1101 | jsfx.DownloadAsFile = function(audio) {
1102 | assert(audio instanceof Audio, 'input must be an Audio object');
1103 | document.location.href = audio.src;
1104 | };
1105 |
1106 | // HELPERS
1107 | jsfx.Util = {};
1108 |
1109 | // Copies array of Floats to a Uint8Array with 16bits per sample
1110 | jsfx.Util.CopyFToU8 = CopyFToU8;
1111 |
1112 | function CopyFToU8(into, floats) {
1113 | assert(into.length / 2 == floats.length,
1114 | 'the target buffer must be twice as large as the iinput');
1115 |
1116 | var k = 0;
1117 | for (var i = 0; i < floats.length; i++) {
1118 | var v = +floats[i];
1119 | var a = (v * 0x7FFF) | 0;
1120 | a = a < -0x8000 ? -0x8000 : 0x7FFF < a ? 0x7FFF : a;
1121 | a += a < 0 ? 0x10000 : 0;
1122 | into[k] = a & 0xFF;
1123 | k++;
1124 | into[k] = a >> 8;
1125 | k++;
1126 | }
1127 | }
1128 |
1129 | // Encodes Uint8Array with base64
1130 | jsfx.Util.U8ToB64 = U8ToB64;
1131 |
1132 | function U8ToB64(data) {
1133 | var CHUNK = 0x8000;
1134 | var result = '';
1135 | for (var start = 0; start < data.length; start += CHUNK) {
1136 | var end = Math.min(start + CHUNK, data.length);
1137 | result += String.fromCharCode.apply(null, data.subarray(start, end));
1138 | }
1139 | return btoa(result);
1140 | }
1141 |
1142 | // uses AudioContext sampleRate or 44100;
1143 | function getDefaultSampleRate() {
1144 | if (typeof AudioContext !== 'undefined') {
1145 | return (new AudioContext()).sampleRate;
1146 | }
1147 | return 44100;
1148 | }
1149 |
1150 | // for checking pre/post conditions
1151 | function assert(condition, message) {
1152 | if (!condition) {
1153 | throw new Error(message);
1154 | }
1155 | }
1156 |
1157 | function clamp(v, min, max) {
1158 | v = +v;
1159 | min = +min;
1160 | max = +max;
1161 | if (v < min) {
1162 | return +min;
1163 | }
1164 | if (v > max) {
1165 | return +max;
1166 | }
1167 | return +v;
1168 | }
1169 |
1170 | function clamp1(v) {
1171 | v = +v;
1172 | if (v < +0.0) {
1173 | return +0.0;
1174 | }
1175 | if (v > +1.0) {
1176 | return +1.0;
1177 | }
1178 | return +v;
1179 | }
1180 |
1181 | function map_object(obj, fn) {
1182 | var r = {};
1183 | for (var name in obj) {
1184 | if (obj.hasOwnProperty(name)) {
1185 | r[name] = fn(obj[name], name);
1186 | }
1187 | }
1188 | return r;
1189 | }
1190 |
1191 | // uniform random
1192 | function runif(scale, offset) {
1193 | var a = Math.random();
1194 | if (scale !== undefined)
1195 | a *= scale;
1196 | if (offset !== undefined)
1197 | a += offset;
1198 | return a;
1199 | }
1200 |
1201 | function rchoose(gens) {
1202 | return gens[(gens.length * Math.random()) | 0];
1203 | }
1204 |
1205 | function Object_keys(obj) {
1206 | var r = [];
1207 | for (var name in obj) {
1208 | r.push(name);
1209 | }
1210 | return r;
1211 | }
1212 |
1213 | jsfx._createFloatArray = createFloatArray;
1214 |
1215 | function createFloatArray(N) {
1216 | if (typeof Float32Array === "undefined") {
1217 | var r = new Array(N);
1218 | for (var i = 0; i < r.length; i++) {
1219 | r[i] = 0.0;
1220 | }
1221 | }
1222 | return new Float32Array(N);
1223 | }
1224 |
1225 | function createByteArray(N) {
1226 | if (typeof Uint8Array === "undefined") {
1227 | var r = new Array(N);
1228 | for (var i = 0; i < r.length; i++) {
1229 | r[i] = 0 | 0;
1230 | }
1231 | }
1232 | return new Uint8Array(N);
1233 | }
1234 |
1235 | return jsfx;
1236 | }));
1237 |
--------------------------------------------------------------------------------