diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..739dde8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.*.swp +workspace +node_modules diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..ba9177f --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +sfxr.me diff --git a/Procfile b/Procfile deleted file mode 100644 index ae7366d..0000000 --- a/Procfile +++ /dev/null @@ -1 +0,0 @@ -web: node web.js diff --git a/README.md b/README.md new file mode 100644 index 0000000..61e0672 --- /dev/null +++ b/README.md @@ -0,0 +1,147 @@ +Quick 'n' easy game sound effects generator. + +App 👉 https://sfxr.me + +[About](#about) | [Use as a library](#library) | [Jsfxr Pro](#jsfxr-pro) + +# About + +A port of [sfxr](http://www.drpetter.se/project_sfxr.html) to HTML5 by Eric Fredricksen. +Maintained by Chris McCormick. + +![jsfxr screenshot](screenshot.png) + +# Library + +You can use the jsxfr library to generate and play sounds in your JavaScript game code. + +## Node + +Install it: + +``` +npm i jsfxr +``` + +Require it: + +``` +const sfxr = require("jsfxr").sfxr; +``` + +See [API](#API) below for usage examples. + +## Browser + +Include the scripts in your page: + +``` + + +``` + +You can then directly use the `sfxr` namespace without requiring it. + +## API + +Generate a sound effect using a preset algorithm and play it using webaudio API. + +```javascript +const sfxr = require("jsfxr").sfxr; + +const preset = "pickupCoin"; +const sound = sfxr.generate(preset); + +sfxr.play(sound); +``` + +Available presets are `pickupCoin`, `laserShoot`, `explosion`, `powerUp`, `hitHurt`, `jump`, `blipSelect`, `synth`, `tone`, `click`, and `random`. + +You can also use the interface at https://sfxr.me to find the sound you want and then use the sound definition. +Click the "serialize" button and copy the JSON code for the sound you want. +You will get a datastructure that you can use like this: + +```javascript +var sound = { + "oldParams": true, + "wave_type": 1, + "p_env_attack": 0, + "p_env_sustain": 0.31718502829007483, + "p_env_punch": 0, + "p_env_decay": 0.2718540993592685, + "p_base_freq": 0.26126191208337196, + "p_freq_limit": 0, + "p_freq_ramp": 0.43787689856926615, + "p_freq_dramp": 0, + "p_vib_strength": 0, + "p_vib_speed": 0, + "p_arp_mod": 0, + "p_arp_speed": 0, + "p_duty": 1, + "p_duty_ramp": 0, + "p_repeat_speed": 0.7558565452384385, + "p_pha_offset": 0, + "p_pha_ramp": 0, + "p_lpf_freq": 1, + "p_lpf_ramp": 0, + "p_lpf_resonance": 0, + "p_hpf_freq": 0, + "p_hpf_ramp": 0, + "sound_vol": 0.25, + "sample_rate": 44100, + "sample_size": 8 +}; + +var a = sfxr.toAudio(sound); +a.play(); +``` + +You can also use the short URL compressed version of the sound: + +```javascript +var a = sfxr.toAudio("5EoyNVSymuxD8s7HP1ixqdaCn5uVGEgwQ3kJBR7bSoApFQzm7E4zZPW2EcXm3jmNdTtTPeDuvwjY8z4exqaXz3NGBHRKBx3igYfBBMRBxDALhBSvzkF6VE2Pv"); +a.play(); +``` + +You can also access an array of samples. +By default the buffer contains audio rendered at a sample rate of `44100`. + +``` +var buffer = sfxr.toBuffer(sound); +console.log(buffer); +``` + +Additionally you can get a dataURI for a wav file of the sound: + +``` +var a = sfxr.toWave(sound); +console.log(a.dataURI); +``` + +You can convert between the base58 short-url encoded format and JSON using `b58encode` and `b58decode`: + +``` +var b58string = sfxr.b58encode(sound); +var sound = sfxr.b58decode(b58string); +``` + +You can also access the lower level classes `SoundEffect` and `Params` if you need to. +This can be useful for caching the internal representation for efficiency, or mutating the sound with `params.mutate()`. + +# Jsfxr Pro + +A [Pro version of Jsfxr](https://pro.sfxr.me/) is available with enhanced features like the ability to save sounds to packs and download a zip file of all wavs. + +[![Jsfxr Pro Screenshot](./jsfxr-pro-screenshot.png)](https://pro.sfxr.me) + +# Links + +* Application: http://sfxr.me/ +* Source code: https://github.com/chr15m/jsfxr/ + +# Thanks + +* Dr. Petter for inventing sfxr. +* Eric Fredricksen for creating this port. +* riffwave.js: http://www.codebase.es/riffwave/ +* jquery-ui: http://jqueryui.com/ diff --git a/README.rst b/README.rst deleted file mode 100644 index 6973ed4..0000000 --- a/README.rst +++ /dev/null @@ -1,22 +0,0 @@ -jsfxr -===== - -Quick 'n' easy game sound effects generator. - -A port of sfxr: http://www.drpetter.se/project_sfxr.html to HTML5. - - -Links ------ - -Application: http://github.grumdrig.com/jsfxr/ - -Source code: https://github.com/grumdrig/jsfxr/ - - -Thanks ------- - - riffwave.js: http://www.codebase.es/riffwave/ - - jquery-ui: http://jqueryui.com/ diff --git a/index.html b/index.html index 14254ab..c87498f 100644 --- a/index.html +++ b/index.html @@ -1,9 +1,11 @@ -jsfxr +jsfxr - 8 bit sound maker and online sfx generator + + @@ -34,8 +36,16 @@ PARAMS.sound_vol = SOUND_VOL; PARAMS.sample_rate = SAMPLE_RATE; PARAMS.sample_size = SAMPLE_SIZE; - PARAMS[fx](); - $("#wav").text(fx + ".wav"); + var name; + if (fx.indexOf("#") == 0) { + PARAMS.fromB58(fx.slice(1)); + name = "random"; + } else { + PARAMS[fx](); + name = fx; + } + $("#wav").text(name + ".wav").attr("download", name + ".wav"); + $("#json").text(name + ".json").attr("download", name + ".json"); updateUi(); play(); } @@ -47,20 +57,75 @@ } function play(noregen) { - setTimeout(function () { - var audio = new Audio(); if (!noregen) { + var b58 = PARAMS.toB58(); + if (document.location.href.indexOf("#") != -1) { + document.location.hash = PARAMS.toB58(); + } + //$("#permalink").attr("href", "#" + b58) + //$("#permalink").text(b58) + $("#copybuffer").val(b58); + $("#share").attr("href", "#" + b58) SOUND = new SoundEffect(PARAMS).generate(); $("#file_size").text(Math.round(SOUND.wav.length / 1024) + "kB"); $("#num_samples").text(SOUND.header.subChunk2Size / (SOUND.header.bitsPerSample >> 3)); $("#clipping").text(SOUND.clipping); } - audio.src = SOUND.dataURI; + $("#wav").attr("href", SOUND.dataURI); + $("#json").attr("href", 'data:text/plain;charset=UTF-8,' + encodeURIComponent(serialize_params_to_string())); $("#sfx").attr("href", "sfx.wav?" + PARAMS.query()); - audio.play(); - }, 0); + + SOUND.getAudio().play(); +} + +function copy() { + var b = $("#copybuffer"); + b.css("display", "block"); + //b.val($("#permalink").text()); + b.select(); + document.execCommand("copy"); + b.css("display", "none"); +} + +function serialize_params_to_string() { + return JSON.stringify(PARAMS, null, 2); +} + +function serialize_params() { + $("textarea").val(serialize_params_to_string()); + $("#serialize").show(); +} + +function deserialize_params_from_string(json_string) { + var newPARAMS = JSON.parse(json_string); + PARAMS.fromJSON(newPARAMS); + play(); + updateUi(); +} + +function deserialize_params() { + deserialize_params_from_string($("textarea").val()); +} + +function upload_params_from_file() { + $("#open_save_impl").click(); +} + +function on_upload_file_selected(e) { + var file = e.target.files[0]; + if (!file) { + return; + } + var reader = new FileReader(); + reader.onload = function(e) { + var contents = e.target.result; + $("textarea").val(contents); + deserialize_params_from_string(contents); + $("#open_save_impl").val(""); + }; + reader.readAsText(file); } function disenable() { @@ -133,104 +198,13 @@ {for: is})); }); - var UNITS = { - p_env_attack: function (v) { return (v / 44100).toPrecision(4) + ' sec' }, - p_env_sustain: function (v) { return (v / 44100).toPrecision(4) + ' sec' }, - p_env_punch: function (v) { return '+' + (v * 100).toPrecision(4) + '%'}, - p_env_decay: function (v) { return (v / 44100).toPrecision(4) + ' sec' }, - - p_base_freq: 'Hz', - p_freq_limit: 'Hz', - p_freq_ramp: function (v) { - return (44100*Math.log(v)/Math.log(0.5)).toPrecision(4) + ' 8va/sec'; }, - p_freq_dramp: function (v) { - return (v*44100 / Math.pow(2, -44101./44100)).toPrecision(4) + - ' 8va/sec^2?'; }, - - p_vib_speed: function (v) { return v === 0 ? 'OFF' : - (441000/64. * v).toPrecision(4) + ' Hz'}, - p_vib_strength: function (v) { return v === 0 ? 'OFF' : - '± ' + (v*100).toPrecision(4) + '%' }, - - p_arp_mod: function (v) { return ((v === 1) ? 'OFF' : - 'x ' + (1./v).toPrecision(4)) }, - p_arp_speed: function (v) { return (v === 0 ? 'OFF' : - (v / 44100).toPrecision(4) +' sec') }, - - p_duty: function (v) { return (100 * v).toPrecision(4) + '%'; }, - p_duty_ramp: function (v) { return (8 * 44100 * v).toPrecision(4) +'%/sec'}, - - p_repeat_speed: function (v) { return v === 0 ? 'OFF' : - (44100/v).toPrecision(4) + ' Hz' }, - - p_pha_offset: function (v) { return v === 0 ? 'OFF' : - (1000*v/44100).toPrecision(4) + ' msec' }, - // Not so sure about this: - p_pha_ramp: function (v) { return v === 0 ? 'OFF' : - (1000*v).toPrecision(4) + ' msec/sec' }, - - p_lpf_freq: function (v) { - return (v === .1) ? 'OFF' : Math.round(8 * 44100 * v / (1-v)) + ' Hz'; }, - p_lpf_ramp: function (v) { if (v === 1) return 'OFF'; - return Math.pow(v, 44100).toPrecision(4) + ' ^sec'; }, - p_lpf_resonance: function (v) { return (100*(1-v*.11)).toPrecision(4)+'%';}, - - p_hpf_freq: function (v) { - return (v === 0) ? 'OFF' : Math.round(8 * 44100 * v / (1-v)) + ' Hz'; }, - p_hpf_ramp: function (v) { if (v === 1) return 'OFF'; - return Math.pow(v, 44100).toPrecision(4) + ' ^sec'; }, - - sound_vol: function (v) { - v = 10 * Math.log(v*v) / Math.log(10); - var sign = v >= 0 ? '+' : ''; - return sign + v.toPrecision(4) + ' dB'; - } - }; - - var CONVERSIONS = { - p_env_attack: function (v) { return v * v * 100000.0 }, - p_env_sustain: function (v) { return v * v * 100000.0 }, - p_env_punch: function (v) { return v }, - p_env_decay: function (v) { return v * v * 100000.0 }, - - p_base_freq: function (v) { return 8 * 44100 * (v * v + 0.001) / 100 }, - p_freq_limit: function (v) { return 8 * 44100 * (v * v + 0.001) / 100 }, - p_freq_ramp: function (v) { return 1.0 - Math.pow(v, 3.0) * 0.01 }, - p_freq_dramp: function (v) { return -Math.pow(v, 3.0) * 0.000001 }, - - p_vib_speed: function (v) { return Math.pow(v, 2.0) * 0.01 }, - p_vib_strength: function (v) { return v * 0.5 }, - - p_arp_mod: function (v) { - return v >= 0 ? 1.0 - Math.pow(v, 2) * 0.9 : 1.0 + Math.pow(v, 2) * 10; }, - p_arp_speed: function (v) { return (v === 1.0) ? 0 : - Math.floor(Math.pow(1.0 - v, 2.0) * 20000 +32)}, - - p_duty: function (v) { return 0.5 - v * 0.5; }, - p_duty_ramp: function (v) { return -v * 0.00005 }, - - p_repeat_speed: function (v) { return (v === 0) ? 0 : - Math.floor(Math.pow(1-v, 2) * 20000) + 32 }, - - p_pha_offset: function (v) { return (v < 0 ? -1 : 1) * Math.pow(v,2)*1020 }, - p_pha_ramp: function (v) { return (v < 0 ? -1 : 1) * Math.pow(v,2) }, - - p_lpf_freq: function (v) { return Math.pow(v, 3) * 0.1 }, - p_lpf_ramp: function (v) { return 1.0 + v * 0.0001 }, - p_lpf_resonance: function (v) { return 5.0 / (1.0 + Math.pow(v, 2) * 20) }, // * (0.01 + fltw); - - p_hpf_freq: function (v) { return Math.pow(v, 2) * 0.1 }, - p_hpf_ramp: function (v) { return 1.0 + v * 0.0003 }, - - sound_vol: function (v) { return Math.exp(v) - 1; } - }; - for (var p in CONVERSIONS) { + for (var p in sliders) { var control = $('#' + p)[0]; - control.convert = CONVERSIONS[p]; - control.units = UNITS[p]; + control.convert = sliders[p]; + control.units = units[p]; } - - gen("pickupCoin"); + + gen(document.location.hash || "pickupCoin"); }); function convert(control, v) { @@ -249,7 +223,7 @@ @@ -365,19 +434,24 @@

jsfxr

Generator

+
+






+

-
+


-
-

+

+
+

+
@@ -434,15 +508,14 @@

Manual Settings

-
+

Sound

-
- -

-To save the sound, right- or control-click the following link, select -"Save As..." and choose a name with a ".wav" extension.
+
+

+ Download:
sfx.wav

- sfx.wav
+ Save As:
sfx.json
+ @@ -480,36 +553,37 @@

Sound

- -
-
-
+

🔗 permalink

+

+ -
- - - - +
+ + + + +
+ + + +
+ diff --git a/jsfxr-pro-screenshot.png b/jsfxr-pro-screenshot.png new file mode 100644 index 0000000..31d955f Binary files /dev/null and b/jsfxr-pro-screenshot.png differ diff --git a/package.json b/package.json index 86e6b15..a594662 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,30 @@ { "name": "jsfxr", - "version": "0.0.1", - "dependencies": { - "express": "2.2.0" - } + "version": "1.2.2", + "description": "8-bit sound effects generator based on sfxr", + "main": "sfxr.js", + "exports": [ + "./sfxr.js", + "./riffwave.js" + ], + "homepage": "https://github.com/chr15m/jsfxr", + "repository": { + "type": "git", + "url": "https://github.com/chr15m/jsfxr.git", + "web": "https://github.com/chr15m/jsfxr" + }, + "bugs": { + "url": "https://github.com/chr15m/jsfxr/issues/" + }, + "keywords": ["sfxr", "jsfxr", "8-bit", "sfx", "sound effects", "synth", "8bit", "retro", "chiptune", "audio", "music", "sound", "game development"], + "devDependencies": { + "live-server": "1.2.1", + "tape": "5.6.1" + }, + "scripts": { + "serve": "live-server", + "test": "./tests.js" + }, + "files": ["sfxr.js", "riffwave.js", "UNLICENSE", "README.md", "sfxr-to-wav"], + "bin": {"sfxr-to-wav": "./sfxr-to-wav"} } diff --git a/riffwave.js b/riffwave.js index 325a6ed..54dfaee 100644 --- a/riffwave.js +++ b/riffwave.js @@ -1,22 +1,19 @@ /* - * RIFFWAVE.js v0.02 - Audio encoder for HTML5