├── .DS_Store ├── .cursorignore.txt ├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── .vscode └── launch.json ├── DEVELOPMENT.md ├── LICENSE ├── README.md ├── bin ├── favicon.ico ├── favicon.png ├── img │ ├── bg.png │ ├── delete.png │ ├── locked.png │ ├── logo_bfxr.png │ ├── logo_footsteppr.png │ ├── logo_transfxr.png │ └── unlocked.png └── index.html ├── compile.js ├── css ├── index.css ├── slider.css └── third_party │ ├── bootstrap-slider.css │ └── bootstrap-tooltip.css ├── favicon.ico ├── favicon.png ├── gzipper ├── img ├── bg.png ├── delete.png ├── locked.png ├── logo_bfxr.png ├── logo_footsteppr.png ├── logo_transfxr.png └── unlocked.png ├── index.html ├── insert_templates.js ├── js ├── .DS_Store ├── SaveLoad.js ├── Tab.js ├── audio │ ├── AKWF.js │ ├── Bfxr_DSP.js │ ├── RealizedSound.js │ ├── audio_globals.js │ ├── puredata.js │ ├── puredata_modules.js │ ├── puredata_parser.js │ └── riffwave.js ├── globals.js ├── index.js ├── synths │ ├── Bfxr.js │ ├── Footsteppr.js │ ├── SynthBase.js │ ├── Transfxr.js │ └── templates.js └── third_party │ ├── bootstrap-slider.js │ ├── input-knobs.js │ ├── jszip.js │ └── jszip.min.js ├── package-lock.json ├── package.json ├── template_links.txt ├── templates └── Bfxr │ └── pickup_coin.bcol └── upload /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/increpare/bfxr2/9e38f296ec80b8010a87443a682cfcbf6c6df06f/.DS_Store -------------------------------------------------------------------------------- /.cursorignore.txt: -------------------------------------------------------------------------------- 1 | bin/ -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [increpare] 4 | patreon: increpare 5 | custom: ['paypal.me/increparegames'] 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | *.gz 4 | /node_modules 5 | /node_modules 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "name": "Run Current File", 10 | "request": "launch", 11 | "program": "c:\\Users\\Anwender\\Documents\\GitHub\\bfxr2\\insert_prefabs.js" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | ## How to run 2 | 3 | Uh, it should work just fine. There's an optional compilation step if you want to make everything tiny, but as a default just hosting a local http server and loading index.html should work... 4 | 5 | ## How to compile 6 | 7 | ```node compile.js```, then everything should be in the bin directory. 8 | 9 | ## How to add new sound templates. 10 | 11 | So, a template sound effect ('jump', say) is specified in Bfxr (any of the synths) as a .bcol file. This is then stored in "./templates/[Synth Name]/[Template_Name].bcol" to be referenced in the templates list of the synthesizer you are using. 12 | 13 | Sound names in a template look like "varietyname_suffix". 14 | 15 | ![image](https://github.com/user-attachments/assets/55b3f8ad-0ea1-415a-8de3-a7a46da356c5) 16 | 17 | The sounds are grouped together into varieties, with the idea being that their range of values is the range of possible values of that variety. 18 | 19 | ![image](https://github.com/user-attachments/assets/2db70ccc-65c5-45a2-9d71-63cdecd033a0) 20 | 21 | (Duplicate values are combined already at this stage.) 22 | 23 | So in the end we have that a template is a group of 'varieties'. In Bfxr when you hit the generate button, Bfxr picks a variety at at random, then generates a sound with paramaters within the ranges it finds in the exemplar sounds. 24 | 25 | (The only reason you'd ever _need_ more than 2 example sounds for a given variety is to allow for more than two BUTTONSELECT values (wave shapes, terrains or what have yous)). 26 | 27 | So ok once you've save the .bcol files in the folder, you need to run ```node insert_templates.js" to generate ```js/synths/tempaltes.js```. It's a bit annoying, but the whole point is being able to easily load/save/tweak .bcol files, which IMO makes the annoyance worthwhile. 28 | 29 | If you wish to weight a variety so it appears more often than others, just put a number at the start (multiple digits allowed, e.g. "22coin" will appear ten times more often than "2coin"). 30 | 31 | (There are some hard-coded tempaltes, i.e. Randomize and Mutate - but any ones defined in ./templates will overwrite existing ones - e.g. pickup_coin.bcol will override any existing generate_pickup_coin method in Bfxr.js) 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Stephen Lavelle 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## About 2 | [Bfxr2](https://www.bfxr.net/) a tool for making sound effects for games, a rewrite/refresh of the flash tool Bfxr in javascript. 3 | 4 | ![image](https://github.com/user-attachments/assets/1701bccc-82d6-47e1-881c-ec6ca228dc89) 5 | 6 | It's currently BETA, and new things should be coming to it, but the main addition right now is Obiwannabe's subtly wonderful footstep generator, ported from puredata to javsacript: 7 | 8 | ![image](https://github.com/user-attachments/assets/1f338401-d2e4-4100-ad4d-471ad6e56f6f) 9 | 10 | ## Development 11 | cf. [DEVELOPMENT.md](https://github.com/increpare/bfxr2/blob/master/DEVELOPMENT.md). 12 | 13 | ## Archaeology 14 | 15 | I don't know if it's genetically related, but I believe that _why made a program called sound foley (which I haven't been able to get to work) which looks quite similar to Sfxr in design based on what I've seen of his _why's presentation of it. 16 | The darling DrPetter made the program this is based on, Sfxr: 17 | 18 | * http://drpetter.se/ 19 | * http://drpetter.se/article_sound.html 20 | * http://drpetter.se/project_sfxr.html 21 | The fabulous Tom Vian did a flash port of this, called as3sfxr 22 | * http://www.tomvian.com/ 23 | * http://www.superflashbros.net/as3sfxr/ 24 | There's also a port of Sfxr to OS X that's quite loved by people (and was a little influential) called cfxr: 25 | * http://thirdcog.eu/apps/cfxr 26 | I did a mod of as3sfxr that introduced some new features, and called it as3sfxr-b: 27 | * http://ded.increpare.com/~locus/as3sfxr-b/ 28 | After asking for feedback, I spent some time adding and changing more things, making something new. Which is Bfxr. Which is what you see on this page. 29 | 30 | Code from 31 | Bulk of coding of this version done by poor little me. In addition to code from Tom/DrPetter, code snippits were taken from 32 | 33 | * http://www.firstpr.com.au/dsp/pink-noise/#Filtering for pink-noise related synthesis 34 | 35 | Thanks + Acknolwedgements 36 | * DrPetter, for Sfxr. 37 | * Tom Vian, for his elegantly constructed as3sfxr port. 38 | * @docky, @Draknek, @KommanderKlobb, @eigenbo, @GrimFang4, @nyarla, @bfod, @jasperbyrne, @draknek, @hybridmind, @RinkuHero, DustinGunn, mcc, and agj for feedback + suggestions. And Derek for some early encouragement. 39 | The people from the #flex irc room on freenode for technical help in my hour of need, especially J_A_X. 40 | 41 | Other software that can be software for making sounds: 42 | * Audacity - http://audacity.sourceforge.net/ 43 | * HighC - http://highc.org/ 44 | * PXTone - http://buzinkai.net/PXTone/tutorial/ 45 | * Sound Effects Generator - http://www.windowsgames.co.uk/effects.html (windows only. I nicked a couple of things from this. ) 46 | * Freesound - http://www.freesound.org - Okay, not sound software, but a really amazing resource. 47 | * ChipTone - https://sfbgames.itch.io/chiptone 48 | -------------------------------------------------------------------------------- /bin/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/increpare/bfxr2/9e38f296ec80b8010a87443a682cfcbf6c6df06f/bin/favicon.ico -------------------------------------------------------------------------------- /bin/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/increpare/bfxr2/9e38f296ec80b8010a87443a682cfcbf6c6df06f/bin/favicon.png -------------------------------------------------------------------------------- /bin/img/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/increpare/bfxr2/9e38f296ec80b8010a87443a682cfcbf6c6df06f/bin/img/bg.png -------------------------------------------------------------------------------- /bin/img/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/increpare/bfxr2/9e38f296ec80b8010a87443a682cfcbf6c6df06f/bin/img/delete.png -------------------------------------------------------------------------------- /bin/img/locked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/increpare/bfxr2/9e38f296ec80b8010a87443a682cfcbf6c6df06f/bin/img/locked.png -------------------------------------------------------------------------------- /bin/img/logo_bfxr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/increpare/bfxr2/9e38f296ec80b8010a87443a682cfcbf6c6df06f/bin/img/logo_bfxr.png -------------------------------------------------------------------------------- /bin/img/logo_footsteppr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/increpare/bfxr2/9e38f296ec80b8010a87443a682cfcbf6c6df06f/bin/img/logo_footsteppr.png -------------------------------------------------------------------------------- /bin/img/logo_transfxr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/increpare/bfxr2/9e38f296ec80b8010a87443a682cfcbf6c6df06f/bin/img/logo_transfxr.png -------------------------------------------------------------------------------- /bin/img/unlocked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/increpare/bfxr2/9e38f296ec80b8010a87443a682cfcbf6c6df06f/bin/img/unlocked.png -------------------------------------------------------------------------------- /compile.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | var UglifyJS = require("uglify-js"); 3 | var CleanCSS = require('clean-css'); 4 | const { exec, execSync } = require('child_process'); 5 | 6 | //bring the templates up to date 7 | var output = execSync("node insert_templates.js"); 8 | //print stdout of output 9 | console.log(output.toString()); 10 | 11 | //load index.html 12 | var index = fs.readFileSync('index.html', 'utf8'); 13 | console.log("processing index.html..."); 14 | 15 | //all the scripts are contained between and 16 | var script_start_tag = ''; 17 | var script_end_tag = ''; 18 | const script_start = index.indexOf(script_start_tag); 19 | const script_end = index.indexOf(script_end_tag); 20 | const scripts = index.substring(script_start, script_end+script_end_tag.length); 21 | 22 | //all the css is contained between and 23 | var css_start_tag = ''; 24 | var css_end_tag = ''; 25 | const css_start = index.indexOf(css_start_tag); 26 | const css_end = index.indexOf(css_end_tag); 27 | var css = index.substring(css_start, css_end+css_end_tag.length); 28 | 29 | //extract those sections 30 | const css_includes = css.split('\n'); 31 | const script_includes = scripts.split('\n'); 32 | 33 | // console.log(css_includes); 34 | // console.log(script_includes); 35 | 36 | 37 | //construct css file list 38 | var css_files = []; 39 | for (var i = 0; i < css_includes.length; i++) { 40 | const line = css_includes[i]; 41 | var match = line.match(/'; 115 | var css_include_line = ''; 116 | 117 | //replace the old includes with the new ones 118 | index = index.replace( scripts, js_include_line); 119 | index = index.replace( css, css_include_line); 120 | 121 | //write the new index.html 122 | fs.writeFileSync('bin/index.html', index); 123 | 124 | console.log("crushing images..."); 125 | fs.mkdirSync('bin/img'); 126 | fs.readdirSync('img').forEach(file => { 127 | // First copy the file to bin/img 128 | fs.copyFileSync(`img/${file}`, `bin/img/${file}`); 129 | var command = `pngcrush -rem allb -brute -reduce -ow ./bin/img/${file}`; 130 | exec(command); 131 | }); 132 | 133 | //run ./gzipper perl script 134 | exec("perl gzipper"); 135 | 136 | //move favicon.icon and favicon.png to bin 137 | fs.copyFileSync('favicon.ico', 'bin/favicon.ico'); 138 | fs.copyFileSync('favicon.png', 'bin/favicon.png'); 139 | 140 | -------------------------------------------------------------------------------- /css/index.css: -------------------------------------------------------------------------------- 1 | /*the structure of the page is HEADER, CONTENT, FOOTER */ 2 | 3 | :root { 4 | --page-background-color: #ccbda1; 5 | --panel-background-color: #ccbda1; 6 | --panel-border-color: #5c574c; 7 | --panel-text-color: #black; 8 | --panel-disabled-text-color: #675f51; 9 | --tab-background-color-unselected: #887c66; 10 | --tab-unselected-border-color: #3c3831; 11 | --tab-background-color-selected: var(--panel-background-color); 12 | --tab-selected-border-color: var(--panel-border-color); 13 | --panel-border-thickness: 1px; 14 | --ui-margin: 5px; 15 | --font-size: 16px; 16 | --font-size-small: 12px; 17 | --font-size-bigger: 24px; 18 | --file-item-selected-background-color:#a8c6ee; 19 | --tick-width: 2px; 20 | 21 | 22 | --slider-border-color: var(--tab-selected-border-color); 23 | --slider-tick-color: #0000002c; 24 | --slider-tick-defaultval-color: #5c574c; 25 | --slider-background-color: transparent; 26 | --col-sliderhandle: black; 27 | 28 | --alternating-table-row-color: #e7d1a7; 29 | --button-background-color: var(--panel-background-color); 30 | --button-pressed-background-color: #e7d1a7; 31 | 32 | --tween-canvas-width: 75; 33 | --tween-canvas-height: 32; 34 | 35 | --buttongrid-pressed-background-color: grey; 36 | 37 | --about-dialog-width: 300px; 38 | --about-dialog-backdrop-color: #ccbda188; 39 | } 40 | /* kludge - cf https://stackoverflow.com/questions/7492062/css-overflow-scroll-always-show-vertical-scroll-bar */ 41 | /* ::-webkit-scrollbar { 42 | -webkit-appearance: none; 43 | width: 7px; 44 | } 45 | 46 | ::-webkit-scrollbar-thumb { 47 | border-radius: 4px; 48 | background-color: rgba(0, 0, 0, .5); 49 | box-shadow: 0 0 1px rgba(255, 255, 255, .5); 50 | } */ 51 | /* end kludge */ 52 | 53 | body { 54 | user-select: none; 55 | width:100%; 56 | height:100%; 57 | margin:0; 58 | padding:0; 59 | background-color: var(--page-background-color); 60 | background-image: url("../img/bg.png") ; 61 | font-size: var(--font-size); 62 | /* min-height: 1000px; */ 63 | overflow-y:auto; 64 | /*vertically center content*/ 65 | display: flex; 66 | flex-direction: column; 67 | justify-content: center; 68 | } 69 | 70 | #main_container { 71 | width:fit-content; 72 | background-color: transparent; 73 | 74 | display: flex; 75 | flex-direction: column; 76 | 77 | 78 | /*horizontally center content*/ 79 | margin: 0 auto; 80 | 81 | } 82 | 83 | #tab_bar { 84 | padding-left: 10px; 85 | height: 50px; 86 | width: 100%; 87 | background-color: transparent; 88 | display: flex; 89 | flex-direction: row; 90 | 91 | } 92 | 93 | .tab_button { 94 | /* width: 100px; */ 95 | margin-top: 15px; 96 | padding-left: 10px; 97 | padding-right: 10px; 98 | font-size: var(--font-size-bigger); 99 | 100 | background-color: var(--tab-background-color-unselected); 101 | 102 | border-style:solid; 103 | border-color: var(--tab-unselected-border-color); 104 | border-width: var(--panel-border-thickness); 105 | border-bottom:none; 106 | 107 | margin-left: 5px; 108 | margin-right: 5px; 109 | 110 | padding-top: 3px; 111 | cursor: pointer; 112 | 113 | } 114 | .tab_button:first-child { 115 | margin-left: 0; 116 | } 117 | .tab_button:last-child { 118 | margin-right: 0; 119 | } 120 | 121 | .tab_button.active_tab { 122 | margin-top: 10px; 123 | 124 | background-color: var(--tab-background-color-selected); 125 | 126 | border-style:solid; 127 | border-color: var(--tab-selected-border-color); 128 | border-width: var(--panel-border-thickness); 129 | border-bottom:none; 130 | 131 | /*to remove the border between the active tab 132 | and the main panel, we extend it down a bit: */ 133 | margin-bottom: calc(-1*var(--panel-border-thickness)); 134 | /* margin-left: calc(-1*var(--panel-border-thickness)); */ 135 | /* margin-right: calc(-1*var(--panel-border-thickness)); */ 136 | /*expand the width to overlap the next/previous tabs a bit*/ 137 | z-index: 1; 138 | 139 | padding-top: 6px; 140 | 141 | cursor: default; 142 | } 143 | 144 | 145 | .tab_page { 146 | height: auto; 147 | /* max-height: 1200px; */ 148 | width: 100%; 149 | background-color: var(--panel-background-color); 150 | border-style:solid; 151 | border-color: var(--panel-border-color); 152 | border-width: var(--panel-border-thickness); 153 | display: none; 154 | flex-direction: row; 155 | /*big thick drop-shadow*/ 156 | filter: drop-shadow(10px 10px 20px rgba(0, 0, 0, 0.5)); 157 | 158 | 159 | height: 606px; 160 | overflow-y: clip; 161 | overflow-x: visible; 162 | } 163 | 164 | .tab_page.active_tab_page { 165 | display: flex; 166 | } 167 | 168 | #footer { 169 | height: fit-content; 170 | width: fit-content; 171 | padding-top:5px; 172 | text-align: center; 173 | /*align center*/ 174 | margin: 0 auto; 175 | background-color: var(--alternating-table-row-color); 176 | border-radius: 5px; 177 | padding: 5px; 178 | padding-left: 10px; 179 | padding-right: 10px; 180 | margin-top:5px; 181 | box-shadow: 0 0 3px 0 var(--alternating-table-row-color); 182 | } 183 | 184 | .left_panel { 185 | /* height: 100%; */ 186 | width: 150px; 187 | display: flex; 188 | flex-direction: column; 189 | gap: var(--ui-margin); 190 | } 191 | 192 | .template_list { 193 | display: flex; 194 | flex-direction: column; 195 | gap: var(--ui-margin); 196 | margin: var(--ui-margin); 197 | } 198 | 199 | .right_panel_button_list{ 200 | display: flex; 201 | flex-direction: column; 202 | gap: var(--ui-margin); 203 | margin: var(--ui-margin); 204 | margin-left: 0; 205 | 206 | } 207 | 208 | .save_commands { 209 | display: flex; 210 | flex-direction: column; 211 | gap: var(--ui-margin); 212 | margin: var(--ui-margin); 213 | } 214 | 215 | .centre_panel { 216 | width: 350px; 217 | max-height: 600px; 218 | 219 | margin-top: var(--ui-margin); 220 | margin-bottom: 0; 221 | margin-left: 0; 222 | margin-right: var(--ui-margin); 223 | 224 | background-color: transparent; 225 | border: var(--panel-border-thickness) solid var(--panel-border-color); 226 | border-bottom:none; 227 | 228 | display: flex; 229 | flex-direction: column; 230 | } 231 | 232 | .centre_header { 233 | border-bottom: var(--panel-border-thickness) solid var(--panel-border-color); 234 | margin-top: var(--ui-margin); 235 | padding-bottom: var(--ui-margin); 236 | padding-right: var(--ui-margin); 237 | } 238 | 239 | .right_panel { 240 | width: 120px; 241 | background-color: transparent; 242 | } 243 | 244 | .scroll_container { 245 | /* overflow-x:scroll; 246 | overflow-y:scroll; */ 247 | overflow-x: hidden; 248 | overflow-y:auto; 249 | } 250 | 251 | .filelist { 252 | position:relative; 253 | padding-top: var(--ui-margin); 254 | /* margin-bottom: var(--ui-margin); */ 255 | /* padding: var(--ui-margin); */ 256 | display: flex; 257 | flex-direction: column; 258 | /* gap: var(--ui-margin); */ 259 | height:100%; 260 | 261 | border-style:solid; 262 | border-width:0; 263 | border-color: var(--panel-border-color); 264 | border-top-width: var(--panel-border-thickness); 265 | 266 | /* min-height: 100px; 267 | max-height: 200px; */ 268 | scrollbar-gutter: stable; 269 | padding-right:13px; 270 | } 271 | 272 | .file_item { 273 | display: flex; 274 | padding:0; 275 | padding-right: var(--ui-margin); 276 | 277 | /* height: 25px; */ 278 | /*align the text vertically*/ 279 | /* padding-top: 5px; */ 280 | } 281 | 282 | /*unselected: */ 283 | .file_item.modified { 284 | color: var(--panel-disabled-text-color); 285 | } 286 | 287 | .file_item.file_selected { 288 | background-color: var(--file-item-selected-background-color); 289 | } 290 | 291 | 292 | .file_item_name { 293 | padding-left: 3px; 294 | display:inline-block; 295 | text-overflow: ellipsis; 296 | overflow: hidden; 297 | white-space: nowrap; 298 | width: calc(100% - 18px); 299 | height:100%; 300 | } 301 | 302 | .file_item_name.modified_filename { 303 | color:rgb(78, 78, 78); 304 | font-style: italic; 305 | } 306 | 307 | .file_item_name.modified_filename::after { 308 | content: "*"; 309 | } 310 | 311 | .delete_button { 312 | display:flex; 313 | /* float:right; */ 314 | font-size: var(--font-size); 315 | padding:0; 316 | margin:0; 317 | 318 | width:15px; 319 | height:15px; 320 | 321 | text-align: center; 322 | } 323 | 324 | .delete_button img { 325 | width:10px; 326 | height:10px; 327 | } 328 | table.paramtable { 329 | width:100%; 330 | border-collapse: collapse; 331 | } 332 | 333 | .centre_params .paramtable tr { 334 | border-bottom: var(--panel-border-thickness) solid var(--panel-border-color); 335 | } 336 | 337 | /* alternating row colors - cludged to deal with the fact that there are two tables...*/ 338 | .paramtable tr:nth-child(even) { 339 | background-color: var(--alternating-table-row-color); 340 | } 341 | 342 | .centre_params .paramtable tr:nth-child(even) { 343 | background-color: var(--panel-background-color); 344 | } 345 | .centre_params .paramtable tr:nth-child(odd) { 346 | background-color: var(--alternating-table-row-color); 347 | } 348 | /* cludge end */ 349 | 350 | .padded_item { 351 | padding-left: var(--ui-margin); 352 | padding-right: var(--ui-margin); 353 | white-space: nowrap; 354 | } 355 | 356 | /* 3 column grid */ 357 | .button_grid_3c { 358 | width:100%; 359 | display: grid; 360 | grid-template-columns: repeat(4, 1fr); 361 | gap: var(--ui-margin); 362 | 363 | } 364 | 365 | /* 4 column grid */ 366 | .button_grid_4c { 367 | width:100%; 368 | display: grid; 369 | grid-template-columns: repeat(4, 1fr); 370 | gap: var(--ui-margin); 371 | } 372 | 373 | /* 5 column grid */ 374 | .button_grid_5c { 375 | width:100%; 376 | display: grid; 377 | grid-template-columns: repeat(5, 1fr); 378 | gap: var(--ui-margin); 379 | } 380 | 381 | .button_grid_button.selected { 382 | /* background-color: var(--buttongrid-pressed-background-color); */ 383 | /* set disabled */ 384 | /* opacity: 1.0; */ 385 | /* color: var(--panel-text-color); */ 386 | /* border-color: transparent; */ 387 | /* border-radius:2px; */ 388 | } 389 | .lock_icon.unlocked { 390 | opacity: 0.4; 391 | } 392 | 393 | 394 | .slider_container { 395 | position: relative; 396 | width: var(--sliderwidth); 397 | padding-right: var(--ui-margin); 398 | padding-left: var(--ui-margin); 399 | } 400 | 401 | .transition_container { 402 | position: relative; 403 | width: var(--sliderwidth); 404 | padding-right: var(--ui-margin); 405 | padding-left: var(--ui-margin); 406 | } 407 | 408 | .parameter_slider { 409 | width: 100%; 410 | margin: 0; 411 | position: relative; 412 | z-index: 2; /* Place above ticks for interaction */ 413 | } 414 | 415 | .slider_ticks { 416 | position: absolute; 417 | top: 20px; /* Position below the slider */ 418 | left: 0; 419 | right: 0; 420 | display: flex; 421 | justify-content: space-between; /* Even spacing */ 422 | padding: 0 10px; /* Adjust based on browser's track start/end */ 423 | } 424 | 425 | .slider_tick { 426 | width: var(--tick-width); 427 | height: 10px; 428 | background-color: var(--panel-border-color); 429 | } 430 | 431 | 432 | .tooltip{ 433 | position: absolute; 434 | } 435 | 436 | td.lockcolumn { 437 | width: 15px; 438 | } 439 | 440 | td.labelcolumn { 441 | width:max-content; 442 | 443 | } 444 | 445 | .centre_params { 446 | position:relative; 447 | height:100%; 448 | width:100%; 449 | } 450 | 451 | 452 | 453 | .parameter_name { 454 | float: right; 455 | font-size: var(--font-size-small); 456 | width: 100%; 457 | text-align: right; 458 | cursor: default; 459 | user-select: none; 460 | } 461 | 462 | .display_canmvas { 463 | } 464 | .display_canvas_container { 465 | margin-top: var(--ui-margin); 466 | margin-right: var(--ui-margin); 467 | margin-bottom: var(--ui-margin); 468 | border: var(--panel-border-thickness) solid var(--panel-border-color); 469 | background-size: cover; 470 | /* background-position: center; */ 471 | background-repeat: no-repeat; 472 | image-rendering: pixelated; 473 | } 474 | /* 475 | button { 476 | background-color: var(--button-background-color); 477 | border: var(--panel-border-thickness) solid var(--panel-border-color); 478 | border-radius: var(--ui-margin); 479 | } 480 | 481 | button:active { 482 | background-color: var(--button-pressed-background-color); 483 | } 484 | */ 485 | 486 | .lockcolumn { 487 | /*not selectable*/ 488 | user-select: none; 489 | } 490 | 491 | .normie_checkbox { 492 | margin:0; 493 | margin-right: var(--ui-margin); 494 | } 495 | 496 | .padleft { 497 | padding-left: var(--ui-margin); 498 | } 499 | 500 | .lockimage { 501 | width: 15px; 502 | height: 15px; 503 | margin:0; 504 | padding:0; 505 | margin-left: var(--ui-margin); 506 | background-image: url("../img/locked.png"); 507 | background-repeat: no-repeat; 508 | background-position: center; 509 | } 510 | 511 | .lockimage.unlocked { 512 | background-image: url("../img/unlocked.png"); 513 | opacity: 0.4; 514 | } 515 | 516 | 517 | 518 | input[type=range].input-knob:focus,input[type=range].input-slider:focus{ 519 | outline:none; 520 | box-shadow:none; 521 | } 522 | 523 | .tween_select_canvas { 524 | background-color: transparent; 525 | display: inline-block; 526 | width: var(--tween-canvas-width); 527 | height: var(--tween-canvas-height); 528 | border: var(--panel-border-thickness) solid var(--panel-border-color); 529 | margin-top: var(--ui-margin); 530 | /*stretch the image to fit the width*/ 531 | object-fit: contain; 532 | object-position: center; 533 | } 534 | 535 | 536 | 537 | 538 | 539 | /* Dropdown Button */ 540 | .dropbtn { 541 | background-color: #04AA6D; 542 | color: white; 543 | padding: 16px; 544 | font-size: 16px; 545 | border: none; 546 | cursor: pointer; 547 | } 548 | 549 | /* Dropdown button on hover & focus */ 550 | .dropbtn:hover, .dropbtn:focus { 551 | background-color: #3e8e41; 552 | } 553 | 554 | /* The search field */ 555 | #myInput { 556 | box-sizing: border-box; 557 | background-image: url('searchicon.png'); 558 | background-position: 14px 12px; 559 | background-repeat: no-repeat; 560 | font-size: 16px; 561 | padding: 14px 20px 12px 45px; 562 | border: none; 563 | border-bottom: 1px solid #ddd; 564 | } 565 | 566 | /* The search field when it gets focus/clicked on */ 567 | #myInput:focus {outline: 3px solid #ddd;} 568 | 569 | /* The container
- needed to position the dropdown content */ 570 | .dropdown { 571 | position: relative; 572 | display: inline-block; 573 | } 574 | 575 | /* Dropdown Content (Hidden by Default) */ 576 | .dropdown-content { 577 | padding: 0; 578 | display: none; 579 | position: absolute; 580 | background-color: var(--panel-background-color) ; 581 | /* min-width: 230px; */ 582 | /* border: var(--panel-border-thickness) solid var(--panel-border-color); */ 583 | z-index: 1; 584 | /*no gaps between the images*/ 585 | flex-direction: row; 586 | gap: 0; 587 | } 588 | 589 | .dropdown-content .tween_select_canvas { 590 | padding:0; 591 | margin:0; 592 | } 593 | 594 | .dropdown-content img:hover { 595 | background-color: var(--button-pressed-background-color); 596 | } 597 | /* Links inside the dropdown */ 598 | .dropdown-content a { 599 | color: black; 600 | padding: 12px 16px; 601 | text-decoration: none; 602 | display: block; 603 | } 604 | 605 | /* Change color of dropdown links on hover */ 606 | .dropdown-content a:hover {background-color: #f1f1f1} 607 | 608 | /* Show the dropdown menu (use JS to add this class to the .dropdown-content container when the user clicks on the dropdown button) */ 609 | .dropdown-content.show { 610 | left:0; 611 | transform: translateX(50%); 612 | width:var(--tween-canvas-width); 613 | display:flex; 614 | flex-direction: column; 615 | gap: 0; 616 | } 617 | 618 | .dropdown-content img { 619 | display:block; 620 | } 621 | 622 | /*don't double-up borders between two adjacent images in a dropdown-content*/ 623 | .dropdown-content img:not(:last-child) { 624 | border-bottom: none; 625 | } 626 | 627 | form{ 628 | display:inline; 629 | padding:0; 630 | margin:0; 631 | display:flex; 632 | flex-direction: column; 633 | } 634 | 635 | 636 | /* MODAL DIALOG STUFF BEGIN */ 637 | ::backdrop { 638 | background-color: var(--about-dialog-backdrop-color); 639 | backdrop-filter: blur(5px); 640 | } 641 | 642 | 643 | dialog { 644 | background-color: var(--panel-background-color); 645 | border: var(--panel-border-thickness) solid var(--panel-border-color); 646 | padding: var(--ui-margin); 647 | /*drop shadow*/ 648 | filter: drop-shadow(10px 10px 20px rgba(0, 0, 0, 0.5)); 649 | width: fit-content; 650 | } 651 | 652 | dialog button { 653 | display: block; 654 | margin-left: auto; 655 | margin-right: auto; 656 | margin-bottom: var(--ui-margin); 657 | } 658 | 659 | dialog h2 { 660 | margin-top: var(--ui-margin); 661 | } 662 | 663 | /* About Dialog Tabs */ 664 | #about-dialog { 665 | width: fit-content; 666 | height: fit-content; 667 | padding: var(--ui-margin); 668 | overflow: hidden; 669 | } 670 | 671 | .dialog-tabs { 672 | display: flex; 673 | padding-left: 10px; 674 | background-color: transparent; 675 | } 676 | 677 | .dialog-tab { 678 | margin-top: 0; 679 | padding-left: 10px; 680 | padding-right: 10px; 681 | font-size: var(--font-size); 682 | background-color: var(--tab-background-color-unselected); 683 | border-style: solid; 684 | border-color: var(--tab-unselected-border-color); 685 | border-width: var(--panel-border-thickness); 686 | border-bottom: none; 687 | margin-left: 5px; 688 | margin-right: 5px; 689 | padding-top: 3px; 690 | cursor: pointer; 691 | } 692 | 693 | .dialog-tab:first-child { 694 | margin-left: 0; 695 | } 696 | 697 | .dialog-tab:last-child { 698 | margin-right: 0; 699 | } 700 | 701 | /*non-active tabs*/ 702 | .dialog-tab:not(.active) { 703 | /* transform: translateY(var(--panel-border-thickness)); */ 704 | transform: translateY(var(--ui-margin)); 705 | } 706 | 707 | .dialog-tab.active { 708 | background-color: var(--tab-background-color-selected); 709 | border-color: var(--tab-selected-border-color); 710 | margin-bottom: calc(-1*var(--panel-border-thickness)); 711 | z-index: 1; 712 | padding-top: 6px; 713 | cursor: default; 714 | } 715 | 716 | .dialog-content { 717 | height: 320px; 718 | width: 400px; 719 | overflow-y: auto; 720 | overflow-x: hidden; 721 | border-style: solid; 722 | border-color: var(--panel-border-color); 723 | border-width: var(--panel-border-thickness); 724 | padding: var(--ui-margin); 725 | } 726 | 727 | .tab-content { 728 | display: none; 729 | } 730 | 731 | .tab-content.active { 732 | display: block; 733 | } 734 | 735 | #about-dialog button[autofocus] { 736 | margin: var(--ui-margin) auto; 737 | } 738 | /* MODAL DIALOG STUFF END */ 739 | 740 | /* SHORTCUTS DL STUFF BEGIN */ 741 | .shortcuts-list { 742 | display: grid; 743 | grid-template-columns: max-content auto; 744 | grid-gap: 10px; 745 | } 746 | 747 | .shortcuts-list dt { 748 | grid-column-start: 1; 749 | margin: 0; 750 | } 751 | 752 | .shortcuts-list dd { 753 | grid-column-start: 2; 754 | margin: 0; 755 | } 756 | /* SHORTCUTS DL STUFF END */ 757 | 758 | 759 | img { 760 | /*pixelated*/ 761 | image-rendering: pixelated; 762 | } 763 | 764 | .dropzone { 765 | box-sizing: border-box; 766 | position: fixed; 767 | z-index: 99999; 768 | width: 20em; 769 | height: 20em; 770 | left: 50%; 771 | top: 50%; 772 | transform: translate(-50%, -50%); 773 | background-color: var(--panel-background-color); 774 | border: 5px dashed var(--panel-border-color); 775 | border-radius: 10px; 776 | opacity: 0.8; 777 | /*center text vertically and horizontally*/ 778 | display: none;/*flex to make visible*/ 779 | align-items: center; 780 | justify-content: center; 781 | font-size: var(--font-size-bigger); 782 | /*mouse passthrough*/ 783 | pointer-events: none; 784 | } -------------------------------------------------------------------------------- /css/slider.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --sliderheight: 15px; 3 | --sliderwidth: 150px; 4 | --sliderwidthnarrow: 106px; 5 | --col-white: #ffffff; 6 | --col-tanlight: #dbaf85; 7 | --col-tan: #c89057; 8 | --col-lightbrown: #a96742; 9 | --col-mediumbrown: #8f563b; 10 | --col-shadowbrown: #824e37; 11 | --col-shadowbrowner: #7a4836; 12 | --col-darkbrown: #663931; 13 | --col-lipflesh: #964e41; 14 | --col-black: #201010; 15 | --border-thickness: 1px; 16 | 17 | --slider-border-color: var(--col-darkbrown); 18 | --slider-tick-defaultval-color: var(--col-tan); 19 | --slider-tick-color: var(--col-lipflesh); 20 | --slider-background-color: var(--col-tanlight); 21 | --col-sliderhandle: var(--col-darkbrown); 22 | 23 | --tooltip-background-color: #fdfdcc; 24 | 25 | } 26 | 27 | .slidercell .btn-check:checked+.btn-outline-primary, 28 | .slidercell .btn-check:active+.btn-outline-primary, 29 | .btn-outline-primary:active, 30 | .btn-outline-primary.active, 31 | .btn-outline-primary.dropdown-toggle.show { 32 | background-color: var(--col-lipflesh); 33 | /* outline-color: var(--col-black); */ 34 | } 35 | 36 | 37 | .slider.slider-horizontal { 38 | margin-left: 1.5px; 39 | margin-right: 1.5px; 40 | position: relative; 41 | top: -4px; 42 | } 43 | 44 | .slider-track { 45 | background-image: none; 46 | background-color: transparent; 47 | border-radius: 0px; 48 | /* outline: 3px solid red; */ 49 | outline-offset: -3px; 50 | box-shadow: none; 51 | } 52 | 53 | .slider.slider-disabled { 54 | opacity: 0.3; 55 | } 56 | .slider.slider-disabled .slider-track { 57 | background-color: transparent; 58 | background-image: none; 59 | } 60 | 61 | .slider.slider-disabled .slider-tick { 62 | cursor: not-allowed; 63 | } 64 | 65 | .slider-handle.round {} 66 | 67 | .slider.slider-horizontal .slider-handle { 68 | border-radius: 0px; 69 | width: 6px; 70 | margin-left: 0px; 71 | box-shadow: none; 72 | } 73 | 74 | .slider.slider-horizontal { 75 | width: var(--sliderwidth); 76 | height: var(--sliderheight); 77 | } 78 | 79 | .slider.slider-horizontal.slidernarrow { 80 | width: var(--sliderwidthnarrow); 81 | height: var(--sliderheight); 82 | } 83 | 84 | .slider-handle { 85 | height: var(--sliderheight); 86 | } 87 | 88 | .slider.slider-horizontal .min-slider-handle { 89 | background-image: none; 90 | background-color: transparent; 91 | border-left: var(--border-thickness) solid var(--col-black); 92 | border-top: var(--border-thickness) solid var(--col-black); 93 | border-bottom: var(--border-thickness) solid var(--col-black); 94 | margin-left: -4.5px; 95 | } 96 | 97 | .slider.slider-horizontal .max-slider-handle { 98 | background-image: none; 99 | background-color: transparent; 100 | border-right: var(--border-thickness) solid var(--col-black); 101 | border-top: var(--border-thickness) solid var(--col-black); 102 | border-bottom: var(--border-thickness) solid var(--col-black); 103 | margin-left: -1.5px; 104 | } 105 | 106 | .slider.slider-horizontal .slider-track { 107 | width: 100%; 108 | margin-top: calc(var(--sliderheight) / -2); 109 | top: 50%; 110 | left: 0; 111 | } 112 | 113 | .slider.slider-horizontal .slider-tick.round { 114 | border-radius: 0; 115 | background-color: var(--slider-tick-color); 116 | background-image: none; 117 | width: 3px; 118 | margin-left: -1.5px; 119 | margin-top: var(--sliderheight); 120 | box-shadow: none; 121 | height: 4px; 122 | opacity: 1.0; 123 | } 124 | 125 | .slider.slider-horizontal .slider-tick.round.defaulttick { 126 | background-color: var(--slider-tick-defaultval-color); 127 | height: 4px; 128 | } 129 | 130 | .slider-track-high { 131 | background-color: transparent; 132 | border-radius: 0; 133 | display: none; 134 | box-shadow: none; 135 | } 136 | 137 | .slider-track-low { 138 | background-color: transparent; 139 | border-radius: 0; 140 | display: none; 141 | box-shadow: none; 142 | } 143 | 144 | .slider-selection { 145 | background: transparent; 146 | border-radius: 0; 147 | display: none; 148 | box-shadow: none; 149 | } 150 | 151 | .slider.slider-horizontal .slider-selection { 152 | background: var(--col-lipflesh); 153 | border-radius: 0; 154 | display: block; 155 | top: 5px; 156 | height: calc(var(--sliderheight) - 10px); 157 | outline: 2.2px solid var(--col-lipflesh); 158 | } 159 | 160 | .slider.slider-horizontal.singleselect .slider-selection { 161 | background: #2b242457; 162 | border-radius: 0; 163 | display: block; 164 | top: 5px; 165 | height: calc(var(--sliderheight) - 10px); 166 | outline: 2.2px solid transparent; 167 | } 168 | 169 | .slider.slider-horizontal.singleselect .min-slider-handle { 170 | background-image: none; 171 | background-color: var(--col-sliderhandle); 172 | border: none; 173 | margin-left: -1.5px; 174 | top: 3px; 175 | width: 3px; 176 | height: calc(var(--sliderheight) - 6px); 177 | } 178 | 179 | .slider.slider-horizontal .slider-track { 180 | height: calc(var(--sliderheight)); 181 | } 182 | 183 | .slider.slider-horizontal .slider-rangeHighlight.category1 { 184 | background: var(--col-tanlight); 185 | border: 1.5px solid var(--col-tanlight); 186 | transform: translate(-1.5px, 0); 187 | outline: 0; 188 | top: 3px; 189 | height: calc(var(--sliderheight) - 6px); 190 | } 191 | 192 | .slider-border { 193 | position: relative; 194 | float: left; 195 | width: calc(var(--sliderwidth) + 9px); 196 | height: 100%; 197 | background-color: var(--slider-background-color); 198 | left: -4.5px; 199 | border: var(--border-thickness) solid var(--slider-border-color); 200 | box-sizing: border-box; 201 | } 202 | 203 | .slidernarrow>.slider-border { 204 | position: relative; 205 | float: left; 206 | width: calc(var(--sliderwidthnarrow) + 9px); 207 | height: 100%; 208 | /* background-color: blue; */ 209 | left: -4.5px; 210 | border: var(--border-thickness) solid var(--slider-border-color); 211 | } 212 | 213 | 214 | .slider-horizontal { 215 | margin-bottom: 3px; 216 | margin-top: 6px; 217 | } 218 | 219 | 220 | .slidercell { 221 | width: calc( var(--sliderwidth) + 13px); 222 | padding-right: 0 !important; 223 | } 224 | 225 | 226 | .globalvolsliderdiv>.fieldtext { 227 | font-weight: 500; 228 | color: var(--col-black); 229 | } 230 | 231 | .tableborder.slidercell { 232 | border-left: 0; 233 | border-right: 0; 234 | } 235 | 236 | 237 | 238 | .slidercell { 239 | border-right: none; 240 | } 241 | 242 | 243 | .globalvolsliderdiv { 244 | margin-left: 3px; 245 | margin-bottom: 6px; 246 | } 247 | 248 | .slider .tooltip.bs-tooltip-top { 249 | margin-top: -36px; 250 | } 251 | 252 | .timevarying_expanded>.slidercell>div { 253 | display: none; 254 | } 255 | 256 | 257 | /* .data-tooltip is a tooltip that is displayed when its parent 258 | is hovered over.*/ 259 | 260 | .data-tooltip{ 261 | position: absolute; 262 | display: block; 263 | 264 | max-width: 200px; 265 | 266 | opacity: 0; 267 | content: attr(data-tooltip); 268 | pointer-events: none; 269 | transform: translate(-50%,calc(-100% - 10px)); 270 | width:max-content; 271 | background: var(--tooltip-background-color); 272 | color: var(--tooltip-text-color); 273 | z-index: 100; 274 | 275 | border-radius: 3px; 276 | padding: 5px; 277 | margin-left: 4px; 278 | 279 | transition: opacity 0.3s ease-in-out; 280 | transition-delay: 0.0s; 281 | 282 | /*add drop shadow*/ 283 | box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.5); 284 | } 285 | 286 | /*when parent hovered*/ 287 | .parameter_name:hover > .data-tooltip{ 288 | opacity: 1; 289 | transition-delay: 0.5s; 290 | } 291 | 292 | /*when parent hovered*/ 293 | .lockcolumn:hover > .data-tooltip{ 294 | opacity: 1; 295 | transition-delay: 0.5s; 296 | } 297 | 298 | button:hover > .data-tooltip{ 299 | opacity: 1; 300 | transition-delay: 0.5s; 301 | } 302 | 303 | .right_col_label{ 304 | text-align: left; 305 | width:100%; 306 | } 307 | 308 | .tooltip-inner{ 309 | background-color: var(--tooltip-background-color); 310 | color: var(--tooltip-text-color); 311 | } 312 | 313 | .slidernarrow{ 314 | padding-left:2px; 315 | } 316 | -------------------------------------------------------------------------------- /css/third_party/bootstrap-slider.css: -------------------------------------------------------------------------------- 1 | /*! ======================================================= 2 | VERSION 11.0.2 3 | ========================================================= */ 4 | /*! ========================================================= 5 | * bootstrap-slider.js 6 | * 7 | * Maintainers: 8 | * Kyle Kemp 9 | * - Twitter: @seiyria 10 | * - Github: seiyria 11 | * Rohit Kalkur 12 | * - Twitter: @Rovolutionary 13 | * - Github: rovolution 14 | * 15 | * ========================================================= 16 | * 17 | * bootstrap-slider is released under the MIT License 18 | * Copyright (c) 2019 Kyle Kemp, Rohit Kalkur, and contributors 19 | * 20 | * Permission is hereby granted, free of charge, to any person 21 | * obtaining a copy of this software and associated documentation 22 | * files (the "Software"), to deal in the Software without 23 | * restriction, including without limitation the rights to use, 24 | * copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | * copies of the Software, and to permit persons to whom the 26 | * Software is furnished to do so, subject to the following 27 | * conditions: 28 | * 29 | * The above copyright notice and this permission notice shall be 30 | * included in all copies or substantial portions of the Software. 31 | * 32 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 33 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 34 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 35 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 36 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 37 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 38 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 39 | * OTHER DEALINGS IN THE SOFTWARE. 40 | * 41 | * ========================================================= */ 42 | .slider { 43 | display: inline-block; 44 | vertical-align: middle; 45 | position: relative; 46 | } 47 | 48 | .slider.slider-horizontal { 49 | width: 210px; 50 | height: 20px; 51 | } 52 | 53 | .slider.slider-horizontal .slider-track { 54 | height: 10px; 55 | width: 100%; 56 | margin-top: -5px; 57 | top: 50%; 58 | left: 0; 59 | } 60 | 61 | .slider.slider-horizontal .slider-selection, .slider.slider-horizontal .slider-track-low, .slider.slider-horizontal .slider-track-high { 62 | height: 100%; 63 | top: 0; 64 | bottom: 0; 65 | } 66 | 67 | .slider.slider-horizontal .slider-tick, 68 | .slider.slider-horizontal .slider-handle { 69 | margin-left: -10px; 70 | } 71 | 72 | .slider.slider-horizontal .slider-tick.triangle, 73 | .slider.slider-horizontal .slider-handle.triangle { 74 | position: relative; 75 | top: 50%; 76 | transform: translateY(-50%); 77 | border-width: 0 10px 10px 10px; 78 | width: 0; 79 | height: 0; 80 | border-bottom-color: #036fa5; 81 | margin-top: 0; 82 | } 83 | 84 | .slider.slider-horizontal .slider-tick-container { 85 | white-space: nowrap; 86 | position: absolute; 87 | top: 0; 88 | left: 0; 89 | width: 100%; 90 | } 91 | 92 | .slider.slider-horizontal .slider-tick-label-container { 93 | white-space: nowrap; 94 | margin-top: 20px; 95 | } 96 | 97 | .slider.slider-horizontal .slider-tick-label-container .slider-tick-label { 98 | display: inline-block; 99 | text-align: center; 100 | } 101 | 102 | .slider.slider-horizontal.slider-rtl .slider-track { 103 | left: initial; 104 | right: 0; 105 | } 106 | 107 | .slider.slider-horizontal.slider-rtl .slider-tick, 108 | .slider.slider-horizontal.slider-rtl .slider-handle { 109 | margin-left: initial; 110 | margin-right: -10px; 111 | } 112 | 113 | .slider.slider-horizontal.slider-rtl .slider-tick-container { 114 | left: initial; 115 | right: 0; 116 | } 117 | 118 | .slider.slider-vertical { 119 | height: 210px; 120 | width: 20px; 121 | } 122 | 123 | .slider.slider-vertical .slider-track { 124 | width: 10px; 125 | height: 100%; 126 | left: 25%; 127 | top: 0; 128 | } 129 | 130 | .slider.slider-vertical .slider-selection { 131 | width: 100%; 132 | left: 0; 133 | top: 0; 134 | bottom: 0; 135 | } 136 | 137 | .slider.slider-vertical .slider-track-low, .slider.slider-vertical .slider-track-high { 138 | width: 100%; 139 | left: 0; 140 | right: 0; 141 | } 142 | 143 | .slider.slider-vertical .slider-tick, 144 | .slider.slider-vertical .slider-handle { 145 | margin-top: -10px; 146 | } 147 | 148 | .slider.slider-vertical .slider-tick.triangle, 149 | .slider.slider-vertical .slider-handle.triangle { 150 | border-width: 10px 0 10px 10px; 151 | width: 1px; 152 | height: 1px; 153 | border-left-color: #036fa5; 154 | margin-left: 0; 155 | } 156 | 157 | .slider.slider-vertical .slider-tick-label-container { 158 | white-space: nowrap; 159 | } 160 | 161 | .slider.slider-vertical .slider-tick-label-container .slider-tick-label { 162 | padding-left: 4px; 163 | } 164 | 165 | .slider.slider-vertical.slider-rtl .slider-track { 166 | left: initial; 167 | right: 25%; 168 | } 169 | 170 | .slider.slider-vertical.slider-rtl .slider-selection { 171 | left: initial; 172 | right: 0; 173 | } 174 | 175 | .slider.slider-vertical.slider-rtl .slider-tick.triangle, 176 | .slider.slider-vertical.slider-rtl .slider-handle.triangle { 177 | border-width: 10px 10px 10px 0; 178 | } 179 | 180 | .slider.slider-vertical.slider-rtl .slider-tick-label-container .slider-tick-label { 181 | padding-left: initial; 182 | padding-right: 4px; 183 | } 184 | 185 | .slider.slider-disabled .slider-handle { 186 | background-color: #cfcfcf; 187 | background-image: -moz-linear-gradient(top, #DFDFDF, #BEBEBE); 188 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#DFDFDF), to(#BEBEBE)); 189 | background-image: -webkit-linear-gradient(top, #DFDFDF, #BEBEBE); 190 | background-image: -o-linear-gradient(top, #DFDFDF, #BEBEBE); 191 | background-image: linear-gradient(to bottom, #DFDFDF, #BEBEBE); 192 | background-repeat: repeat-x; 193 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#DFDFDF', endColorstr='#BEBEBE',GradientType=0); 194 | } 195 | 196 | .slider.slider-disabled .slider-track { 197 | background-color: #e7e7e7; 198 | background-image: -moz-linear-gradient(top, #E5E5E5, #E9E9E9); 199 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#E5E5E5), to(#E9E9E9)); 200 | background-image: -webkit-linear-gradient(top, #E5E5E5, #E9E9E9); 201 | background-image: -o-linear-gradient(top, #E5E5E5, #E9E9E9); 202 | background-image: linear-gradient(to bottom, #E5E5E5, #E9E9E9); 203 | background-repeat: repeat-x; 204 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#E5E5E5', endColorstr='#E9E9E9',GradientType=0); 205 | cursor: not-allowed; 206 | } 207 | 208 | .slider input { 209 | display: none; 210 | } 211 | 212 | .slider .tooltip-inner { 213 | white-space: nowrap; 214 | max-width: none; 215 | } 216 | 217 | .slider .bs-tooltip-top .tooltip-inner, 218 | .slider .bs-tooltip-bottom .tooltip-inner { 219 | position: relative; 220 | left: -50%; 221 | } 222 | 223 | .slider.bs-tooltip-left .tooltip-inner, .slider.bs-tooltip-right .tooltip-inner { 224 | position: relative; 225 | top: -100%; 226 | } 227 | 228 | .slider .tooltip { 229 | pointer-events: none; 230 | } 231 | 232 | .slider .tooltip.bs-tooltip-top .arrow, .slider .tooltip.bs-tooltip-bottom .arrow { 233 | left: -.4rem; 234 | } 235 | 236 | .slider .tooltip.bs-tooltip-top { 237 | margin-top: -44px; 238 | } 239 | 240 | .slider .tooltip.bs-tooltip-bottom { 241 | margin-top: 2px; 242 | } 243 | 244 | .slider .tooltip.bs-tooltip-left, .slider .tooltip.bs-tooltip-right { 245 | margin-top: -14px; 246 | } 247 | 248 | .slider .tooltip.bs-tooltip-left .arrow, .slider .tooltip.bs-tooltip-right .arrow { 249 | top: 8px; 250 | } 251 | 252 | .slider .hide { 253 | display: none; 254 | } 255 | 256 | .slider-track { 257 | background-color: #f7f7f7; 258 | background-image: -moz-linear-gradient(top, #F5F5F5, #F9F9F9); 259 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#F5F5F5), to(#F9F9F9)); 260 | background-image: -webkit-linear-gradient(top, #F5F5F5, #F9F9F9); 261 | background-image: -o-linear-gradient(top, #F5F5F5, #F9F9F9); 262 | background-image: linear-gradient(to bottom, #F5F5F5, #F9F9F9); 263 | background-repeat: repeat-x; 264 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#F5F5F5', endColorstr='#F9F9F9',GradientType=0); 265 | -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); 266 | -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); 267 | box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); 268 | -webkit-border-radius: 4px; 269 | -moz-border-radius: 4px; 270 | border-radius: 4px; 271 | position: absolute; 272 | cursor: pointer; 273 | } 274 | 275 | .slider-selection { 276 | background-color: #f7f7f7; 277 | background-image: -moz-linear-gradient(top, #F9F9F9, #F5F5F5); 278 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#F9F9F9), to(#F5F5F5)); 279 | background-image: -webkit-linear-gradient(top, #F9F9F9, #F5F5F5); 280 | background-image: -o-linear-gradient(top, #F9F9F9, #F5F5F5); 281 | background-image: linear-gradient(to bottom, #F9F9F9, #F5F5F5); 282 | background-repeat: repeat-x; 283 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#F9F9F9', endColorstr='#F5F5F5',GradientType=0); 284 | -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); 285 | -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); 286 | box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); 287 | -webkit-box-sizing: border-box; 288 | -moz-box-sizing: border-box; 289 | box-sizing: border-box; 290 | -webkit-border-radius: 4px; 291 | -moz-border-radius: 4px; 292 | border-radius: 4px; 293 | position: absolute; 294 | } 295 | 296 | .slider-selection.tick-slider-selection { 297 | background-color: #46c1fe; 298 | background-image: -moz-linear-gradient(top, #52c5ff, #3abcfd); 299 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#52c5ff), to(#3abcfd)); 300 | background-image: -webkit-linear-gradient(top, #52c5ff, #3abcfd); 301 | background-image: -o-linear-gradient(top, #52c5ff, #3abcfd); 302 | background-image: linear-gradient(to bottom, #52c5ff, #3abcfd); 303 | background-repeat: repeat-x; 304 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#52c5ff', endColorstr='#3abcfd',GradientType=0); 305 | } 306 | 307 | .slider-track-low, .slider-track-high { 308 | -webkit-box-sizing: border-box; 309 | -moz-box-sizing: border-box; 310 | box-sizing: border-box; 311 | -webkit-border-radius: 4px; 312 | -moz-border-radius: 4px; 313 | border-radius: 4px; 314 | position: absolute; 315 | background: transparent; 316 | } 317 | 318 | .slider-handle { 319 | background-color: #0478b2; 320 | background-image: -moz-linear-gradient(top, #0480BE, #036fa5); 321 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0480BE), to(#036fa5)); 322 | background-image: -webkit-linear-gradient(top, #0480BE, #036fa5); 323 | background-image: -o-linear-gradient(top, #0480BE, #036fa5); 324 | background-image: linear-gradient(to bottom, #0480BE, #036fa5); 325 | background-repeat: repeat-x; 326 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0480BE', endColorstr='#036fa5',GradientType=0); 327 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); 328 | -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); 329 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); 330 | position: absolute; 331 | top: 0; 332 | width: 20px; 333 | height: 20px; 334 | background-color: #0480BE; 335 | border: 0px solid transparent; 336 | } 337 | 338 | .slider-handle:hover { 339 | cursor: pointer; 340 | } 341 | 342 | .slider-handle.round { 343 | -webkit-border-radius: 20px; 344 | -moz-border-radius: 20px; 345 | border-radius: 20px; 346 | } 347 | 348 | .slider-handle.triangle { 349 | background: transparent none; 350 | } 351 | 352 | .slider-handle.custom { 353 | background: transparent none; 354 | } 355 | 356 | .slider-handle.custom::before { 357 | line-height: 20px; 358 | font-size: 20px; 359 | content: '\2605'; 360 | color: #726204; 361 | } 362 | 363 | .slider-tick { 364 | background-color: #f7f7f7; 365 | background-image: -moz-linear-gradient(top, #F5F5F5, #F9F9F9); 366 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#F5F5F5), to(#F9F9F9)); 367 | background-image: -webkit-linear-gradient(top, #F5F5F5, #F9F9F9); 368 | background-image: -o-linear-gradient(top, #F5F5F5, #F9F9F9); 369 | background-image: linear-gradient(to bottom, #F5F5F5, #F9F9F9); 370 | background-repeat: repeat-x; 371 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#F5F5F5', endColorstr='#F9F9F9',GradientType=0); 372 | -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); 373 | -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); 374 | box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); 375 | -webkit-box-sizing: border-box; 376 | -moz-box-sizing: border-box; 377 | box-sizing: border-box; 378 | position: absolute; 379 | cursor: pointer; 380 | width: 20px; 381 | height: 20px; 382 | filter: none; 383 | opacity: 0.8; 384 | border: 0px solid transparent; 385 | } 386 | 387 | .slider-tick.round { 388 | border-radius: 50%; 389 | } 390 | 391 | .slider-tick.triangle { 392 | background: transparent none; 393 | } 394 | 395 | .slider-tick.custom { 396 | background: transparent none; 397 | } 398 | 399 | .slider-tick.custom::before { 400 | line-height: 20px; 401 | font-size: 20px; 402 | content: '\2605'; 403 | color: #726204; 404 | } 405 | 406 | .slider-tick.in-selection { 407 | background-color: #46c1fe; 408 | background-image: -moz-linear-gradient(top, #52c5ff, #3abcfd); 409 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#52c5ff), to(#3abcfd)); 410 | background-image: -webkit-linear-gradient(top, #52c5ff, #3abcfd); 411 | background-image: -o-linear-gradient(top, #52c5ff, #3abcfd); 412 | background-image: linear-gradient(to bottom, #52c5ff, #3abcfd); 413 | background-repeat: repeat-x; 414 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#52c5ff', endColorstr='#3abcfd',GradientType=0); 415 | opacity: 1; 416 | } -------------------------------------------------------------------------------- /css/third_party/bootstrap-tooltip.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 3 | } 4 | 5 | .input-group> :not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback) { 6 | margin-left: -1px; 7 | border-top-left-radius: 0; 8 | border-bottom-left-radius: 0; 9 | } 10 | 11 | 12 | .valid-tooltip { 13 | position: absolute; 14 | top: 100%; 15 | z-index: 5; 16 | display: none; 17 | max-width: 100%; 18 | padding: 0.25rem 0.5rem; 19 | margin-top: 0.1rem; 20 | font-size: 0.875rem; 21 | color: #fff; 22 | background-color: rgba(25, 135, 84, 0.9); 23 | border-radius: 0.25rem; 24 | } 25 | 26 | .was-validated :valid~.valid-feedback, 27 | .was-validated :valid~.valid-tooltip, 28 | .is-valid~.valid-feedback, 29 | .is-valid~.valid-tooltip { 30 | display: block; 31 | } 32 | 33 | 34 | 35 | .invalid-tooltip { 36 | position: absolute; 37 | top: 100%; 38 | z-index: 5; 39 | display: none; 40 | max-width: 100%; 41 | padding: 0.25rem 0.5rem; 42 | margin-top: 0.1rem; 43 | font-size: 0.875rem; 44 | color: #fff; 45 | background-color: rgba(220, 53, 69, 0.9); 46 | border-radius: 0.25rem; 47 | } 48 | 49 | .was-validated :invalid~.invalid-feedback, 50 | .was-validated :invalid~.invalid-tooltip, 51 | .is-invalid~.invalid-feedback, 52 | .is-invalid~.invalid-tooltip { 53 | display: block; 54 | } 55 | 56 | 57 | .tooltip { 58 | position: absolute; 59 | z-index: 1070; 60 | display: block; 61 | margin: 0; 62 | font-family: var(--bs-font-sans-serif); 63 | font-style: normal; 64 | font-weight: 400; 65 | line-height: 1.5; 66 | text-align: left; 67 | text-align: start; 68 | text-decoration: none; 69 | text-shadow: none; 70 | text-transform: none; 71 | letter-spacing: normal; 72 | word-break: normal; 73 | word-spacing: normal; 74 | white-space: normal; 75 | line-break: auto; 76 | font-size: 0.875rem; 77 | word-wrap: break-word; 78 | opacity: 0; 79 | } 80 | 81 | .tooltip.show { 82 | opacity: 0.9; 83 | } 84 | 85 | .tooltip .tooltip-arrow { 86 | position: absolute; 87 | display: block; 88 | width: 0.8rem; 89 | height: 0.4rem; 90 | } 91 | 92 | .tooltip .tooltip-arrow::before { 93 | position: absolute; 94 | content: ""; 95 | border-color: transparent; 96 | border-style: solid; 97 | } 98 | 99 | .bs-tooltip-top, 100 | .bs-tooltip-auto[data-popper-placement^=top] { 101 | padding: 0.4rem 0; 102 | } 103 | 104 | .bs-tooltip-top .tooltip-arrow, 105 | .bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow { 106 | bottom: 0; 107 | } 108 | 109 | .bs-tooltip-top .tooltip-arrow::before, 110 | .bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before { 111 | top: -1px; 112 | border-width: 0.4rem 0.4rem 0; 113 | border-top-color: #000; 114 | } 115 | 116 | .bs-tooltip-end, 117 | .bs-tooltip-auto[data-popper-placement^=right] { 118 | padding: 0 0.4rem; 119 | } 120 | 121 | .bs-tooltip-end .tooltip-arrow, 122 | .bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow { 123 | left: 0; 124 | width: 0.4rem; 125 | height: 0.8rem; 126 | } 127 | 128 | .bs-tooltip-end .tooltip-arrow::before, 129 | .bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before { 130 | right: -1px; 131 | border-width: 0.4rem 0.4rem 0.4rem 0; 132 | border-right-color: #000; 133 | } 134 | 135 | .bs-tooltip-bottom, 136 | .bs-tooltip-auto[data-popper-placement^=bottom] { 137 | padding: 0.4rem 0; 138 | } 139 | 140 | .bs-tooltip-bottom .tooltip-arrow, 141 | .bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow { 142 | top: 0; 143 | } 144 | 145 | .bs-tooltip-bottom .tooltip-arrow::before, 146 | .bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before { 147 | bottom: -1px; 148 | border-width: 0 0.4rem 0.4rem; 149 | border-bottom-color: #000; 150 | } 151 | 152 | .bs-tooltip-start, 153 | .bs-tooltip-auto[data-popper-placement^=left] { 154 | padding: 0 0.4rem; 155 | } 156 | 157 | .bs-tooltip-start .tooltip-arrow, 158 | .bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow { 159 | right: 0; 160 | width: 0.4rem; 161 | height: 0.8rem; 162 | } 163 | 164 | .bs-tooltip-start .tooltip-arrow::before, 165 | .bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before { 166 | left: -1px; 167 | border-width: 0.4rem 0 0.4rem 0.4rem; 168 | border-left-color: #000; 169 | } 170 | 171 | .tooltip-inner { 172 | max-width: 200px; 173 | padding: 0.25rem 0.5rem; 174 | color: #fff; 175 | text-align: center; 176 | background-color: #000; 177 | border-radius: 0.25rem; 178 | } -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/increpare/bfxr2/9e38f296ec80b8010a87443a682cfcbf6c6df06f/favicon.ico -------------------------------------------------------------------------------- /favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/increpare/bfxr2/9e38f296ec80b8010a87443a682cfcbf6c6df06f/favicon.png -------------------------------------------------------------------------------- /gzipper: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | # This script should be uploaded to the web server. 4 | 5 | use warnings; 6 | use strict; 7 | use File::Find; 8 | system("rm bin/*.gz"); 9 | find (\&wanted, ("bin")); 10 | sub wanted 11 | { 12 | if (/(.*\.(?:html|css|txt|js)$)/i) { 13 | print "Compressing $File::Find::name\n"; 14 | if (! -f "$_.gz") { 15 | system ("gzip -cf --best \"$_\" > \"$_.gz\""); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /img/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/increpare/bfxr2/9e38f296ec80b8010a87443a682cfcbf6c6df06f/img/bg.png -------------------------------------------------------------------------------- /img/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/increpare/bfxr2/9e38f296ec80b8010a87443a682cfcbf6c6df06f/img/delete.png -------------------------------------------------------------------------------- /img/locked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/increpare/bfxr2/9e38f296ec80b8010a87443a682cfcbf6c6df06f/img/locked.png -------------------------------------------------------------------------------- /img/logo_bfxr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/increpare/bfxr2/9e38f296ec80b8010a87443a682cfcbf6c6df06f/img/logo_bfxr.png -------------------------------------------------------------------------------- /img/logo_footsteppr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/increpare/bfxr2/9e38f296ec80b8010a87443a682cfcbf6c6df06f/img/logo_footsteppr.png -------------------------------------------------------------------------------- /img/logo_transfxr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/increpare/bfxr2/9e38f296ec80b8010a87443a682cfcbf6c6df06f/img/logo_transfxr.png -------------------------------------------------------------------------------- /img/unlocked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/increpare/bfxr2/9e38f296ec80b8010a87443a682cfcbf6c6df06f/img/unlocked.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Bfxr. Make sound effects for your games. 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 |
Drag file to load
53 |
54 |
55 |
56 |
57 |
58 | 61 |
62 | 63 |
64 | 65 | 66 |
67 |
68 |
69 |

About Bfxr

70 |

Make sound effects for your games!

71 |

Bfxr is an open-source tool based on drpetter's sfxr, written by your friendly neighbourhood increpare.

72 |

Footsteppr is a port of obiwannabe's footstep generator, originally written in puredata.

73 |

You can find out much more about where bfxr came from here.

74 |
75 |
76 |

Keyboard Shortcuts

77 |
78 |
SPACE/ENTER
79 |
Play currently selected sound
80 |
CTRL+O
81 |
Open .bfxr/.bcol file from disk
82 |
CTRL+E
83 |
Export current sound as WAV file.
84 |
CTRL+S
85 |
Save current sound as .bfxr file
86 |
CTRL+C
87 |
Copy current sound
88 |
CTRL+V
89 |
Paste current sound
90 |
L
91 |
Lock/unlock all parameters
92 |
R
93 |
Randomize all parameters
94 |
M
95 |
Mutate current sound
96 |
97 |
98 |
99 | 100 |
101 | 102 | 103 |

Generating Catalogue...

104 | 105 | 0% 106 |
0/0 files generated
107 | Download 108 | 109 |
110 | 111 | 112 | -------------------------------------------------------------------------------- /insert_templates.js: -------------------------------------------------------------------------------- 1 | /* this script loads up all the json files 2 | templates/[synth]/templatesname.bcol 3 | and generates a javscript dictionary of the form 4 | templates = { 5 | "synthname": { 6 | "templatesname": data 7 | } 8 | } 9 | 10 | */ 11 | 12 | const fs = require('fs'); 13 | const path = require('path'); 14 | 15 | const templates_dir = './templates'; 16 | const templates = {}; 17 | 18 | console.log("Inserting templates..."); 19 | 20 | // Read all synth directories 21 | const synthDirs = fs.readdirSync(templates_dir, { withFileTypes: true }) 22 | .filter(dirent => dirent.isDirectory()) 23 | .map(dirent => dirent.name); 24 | 25 | // Process each synth directory 26 | synthDirs.forEach(synthDir => { 27 | const synthPath = path.join(templates_dir, synthDir); 28 | templates[synthDir] = {}; 29 | 30 | // Read all templates files in the synth directory 31 | const templatesFiles = fs.readdirSync(synthPath, { withFileTypes: true }) 32 | .filter(dirent => dirent.isFile()) 33 | .filter(dirent => path.extname(dirent.name) === '.bcol') 34 | .map(dirent => dirent.name); 35 | 36 | // Process each templates file 37 | templatesFiles.forEach(templatesFile => { 38 | const filePath = path.join(synthPath, templatesFile); 39 | const fileContent = fs.readFileSync(filePath, 'utf8'); 40 | try { 41 | const jsonContent = JSON.parse(fileContent); 42 | // Extract templates name without extension 43 | const templatesName = path.parse(templatesFile).name; 44 | var files = jsonContent[synthDir].files; 45 | // a file is an array of [name, data, data] 46 | //delete the third element 47 | 48 | //then convert to a dictionary 49 | var dict = {}; 50 | files.forEach(file => { 51 | dict[file[0]] = JSON.parse(file[1]); 52 | }); 53 | 54 | // dict is a dictionary of variety_suffix: dict 55 | // we want to collate all the data for each variety to give 56 | // [ variety, merged_dict (with arrays as keys)] 57 | // (we don't care about the suffix) 58 | var varieties = {}; 59 | for (var variety_name_extended in dict) { 60 | var variety_name = variety_name_extended.split("_")[0]; 61 | var variety_data = dict[variety_name_extended]; 62 | var this_variety = {}; 63 | if (!varieties[variety_name]) { 64 | varieties[variety_name] = {}; 65 | this_variety = varieties[variety_name]; 66 | for (var key in variety_data) { 67 | this_variety[key] = [variety_data[key]]; 68 | } 69 | continue; 70 | } 71 | this_variety = varieties[variety_name]; 72 | for (var key in variety_data) { 73 | var list = this_variety[key] 74 | var new_value = variety_data[key]; 75 | if (list.indexOf(new_value) === -1) { 76 | this_variety[key].push(new_value); 77 | } 78 | } 79 | } 80 | templates[synthDir][templatesName] = varieties; 81 | 82 | } catch (error) { 83 | console.error(`Error parsing ${filePath}: ${error.message}`); 84 | } 85 | }); 86 | }); 87 | 88 | // Write the templates to a file 89 | fs.writeFileSync('./js/synths/templates.js', `const TEMPLATES_JSON = ${JSON.stringify(templates, null, 2)};`); 90 | 91 | console.log("Templates inserted."); -------------------------------------------------------------------------------- /js/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/increpare/bfxr2/9e38f296ec80b8010a87443a682cfcbf6c6df06f/js/.DS_Store -------------------------------------------------------------------------------- /js/SaveLoad.js: -------------------------------------------------------------------------------- 1 | class SaveLoad { 2 | static collection_save_enabled=true; 3 | 4 | static loaded_data = {}; 5 | 6 | static save_all_collections(){ 7 | if (!SaveLoad.collection_save_enabled){ 8 | return; 9 | } 10 | //collect all file info together 11 | var save_str = SaveLoad.serialize_collection(); 12 | //save to local storage 13 | localStorage.setItem("save_data", save_str); 14 | console.log("saved all collections (length " + save_str.length + ")"); 15 | } 16 | 17 | 18 | static load_all_collections(){ 19 | //check if there is any save data 20 | if (!localStorage.getItem("save_data")){ 21 | return; 22 | } 23 | SaveLoad.loaded_data = JSON.parse(localStorage.getItem("save_data")); 24 | } 25 | 26 | static check_url_for_sfxr_params(){ 27 | var url = window.location.href; 28 | var querystring = window.location.search; 29 | var params = new URLSearchParams(querystring); 30 | if (!params.has("sfx")){ 31 | return; 32 | } 33 | var synth_dat_str = params.get("sfx"); 34 | var [synth_name,filename,params] = SaveLoad.shallow_dict_deserialize(synth_dat_str); 35 | var tab = tabs.find(tab => tab.synth.name == synth_name); 36 | if (!tab){ 37 | console.error("No tab found for synth_name: " + synth_name); 38 | return; 39 | } 40 | tab.set_active_tab(); 41 | tab.create_new_sound_from_params(filename,params,true); 42 | //having loaded it, we can update the url to remove the sfx parameter 43 | var new_url = window.location.href.split("?")[0]; 44 | window.history.replaceState({}, '', new_url); 45 | } 46 | 47 | static shallow_dict_serialize(synth_name,filename,dict){ 48 | //instead of returning a csv string, returns a csv string (with commas delimited by \) 49 | var result = synth_name + "~" + filename + "~"; 50 | var keys = Object.keys(dict); 51 | console.log("exporting keys: " + keys); 52 | keys.sort(); 53 | for (var i = 0; i < keys.length; i++){ 54 | result += dict[keys[i]] + "~"; 55 | } 56 | //trim final "," 57 | result = result.slice(0, -1); 58 | return result; 59 | } 60 | 61 | static shallow_dict_deserialize(str){ 62 | var entries = str.split("~"); 63 | var synth_name = entries[0]; 64 | var filename = entries[1]; 65 | //need to find the tab that matches the synth_name 66 | var tab = tabs.find(tab => tab.synth.name == synth_name); 67 | if (!tab){ 68 | console.error("No tab found for synth_name: " + synth_name); 69 | return; 70 | } 71 | var default_params = tab.synth.default_params(); 72 | var keys = Object.keys(default_params); 73 | keys.sort(); 74 | console.log("importing keys: " + keys); 75 | var dict = {}; 76 | for (var i = 0; i < keys.length; i++){ 77 | dict[keys[i]] = parseFloat(entries[i+2]); 78 | } 79 | return [synth_name,filename,dict]; 80 | } 81 | 82 | 83 | static load_serialized_synth(str){ 84 | var data = JSON.parse(str); 85 | 86 | var synth_name = data.synth_type; 87 | var synth_version = data.version; 88 | var file_name = data.file_name; 89 | var params = data.params; 90 | 91 | var tab = tabs.find(tab => tab.synth.name == synth_name); 92 | if (!tab){ 93 | console.error("No tab found for synth_name: " + synth_name); 94 | return; 95 | } 96 | tab.set_active_tab(); 97 | tab.create_new_sound_from_params(file_name,params,true); 98 | } 99 | 100 | static load_serialized_collection(str){ 101 | var data = JSON.parse(str); 102 | var active_tab_index = data.active_tab_index; 103 | for (var i = 0; i < tabs.length; i++){ 104 | var tab = tabs[i]; 105 | var files = data[tab.synth.name].files; 106 | var selected_file_index = data[tab.synth.name].selected_file_index; 107 | var create_new_sound = data[tab.synth.name].create_new_sound; 108 | var play_on_change = data[tab.synth.name].play_on_change; 109 | var locked_params = data[tab.synth.name].locked_params; 110 | tab.files = files; 111 | tab.selected_file_index = -1; 112 | tab.create_new_sound = create_new_sound; 113 | tab.play_on_change = play_on_change; 114 | tab.synth.locked_params = locked_params; 115 | tab.update_ui(); 116 | if (files[selected_file_index]!=null && files[selected_file_index].length>0){ 117 | tab.set_selected_file(files[selected_file_index][0]); 118 | tab.synth.generate_sound(); 119 | tab.redraw_waveform(); 120 | } 121 | tab.update_ui(); 122 | } 123 | tabs[active_tab_index].set_active_tab(); 124 | } 125 | 126 | static serialize_collection(){ 127 | var save_data = {}; 128 | var active_tab_index=-1; 129 | for (var i = 0; i < tabs.length; i++){ 130 | var tab = tabs[i]; 131 | var files = tab.files; 132 | var selected_file_index = tab.selected_file_index; 133 | var compiled_data = { 134 | files: files, 135 | selected_file_index: selected_file_index, 136 | create_new_sound: tab.create_new_sound, 137 | play_on_change: tab.play_on_change, 138 | locked_params: tab.synth.locked_params 139 | } 140 | save_data[tab.synth.name] = compiled_data; 141 | if (tab.active){ 142 | active_tab_index = i; 143 | } 144 | } 145 | save_data.active_tab_index = active_tab_index; 146 | var serialized_str = JSON.stringify(save_data); 147 | return serialized_str; 148 | } 149 | 150 | } -------------------------------------------------------------------------------- /js/audio/AKWF.js: -------------------------------------------------------------------------------- 1 | class AKWF { 2 | 3 | /* Adventure Kid Waveforms (AKWF) converted for use with Teensy Audio Library 4 | * 5 | * Adventure Kid Waveforms(AKWF) Open waveforms library 6 | * https://www.adventurekid.se/akrt/waveforms/adventure-kid-waveforms/ 7 | * 8 | * This code is in the public domain, CC0 1.0 Universal (CC0 1.0) 9 | * https://creativecommons.org/publicdomain/zero/1.0/ 10 | * 11 | * Converted by Brad Roy, https://github.com/prosper00 12 | */ 13 | 14 | /* AKWF_hvoice_0012 256 samples 15 | 16 | +-----------------------------------------------------------------------------------------------------------------+ 17 | | *** ** | 18 | | *** *** | 19 | | *** * ***** ******* | 20 | | ** ** ** ***** *** ** | 21 | | ** * *** **** ** ** | 22 | | ** ** ** ****** * ** | 23 | |** * ** ** ** ** | 24 | | * ** *** ** ** **| 25 | | * * ** ** ** *** | 26 | | ** ** ** * ** ** | 27 | | ** * * ** *** *** | 28 | | * ** *** ** ******** | 29 | | *** ** **** | 30 | | ** ** | 31 | | * *** | 32 | +-----------------------------------------------------------------------------------------------------------------+ 33 | 34 | 35 | */ 36 | 37 | //suited for lower notes 38 | static hvoice_0012 = [ 39 | 33078, 34077, 35044, 36007, 36938, 37805, 38590, 39410, 40186, 40979, 41828, 42751, 43765, 44750, 45625, 46506, 40 | 47377, 48459, 49580, 50412, 51254, 52132, 53054, 53969, 55064, 56224, 57157, 57762, 58474, 59326, 60025, 60421, 41 | 60707, 60933, 61192, 61199, 60011, 57771, 55825, 55237, 55020, 53932, 51409, 48089, 45399, 44422, 44106, 42490, 42 | 39350, 35232, 30729, 27122, 25230, 23798, 21402, 18034, 14910, 12701, 11362, 10543, 9717, 8561, 7008, 4596, 43 | 1765, 131, 201, 1146, 2023, 2735, 3299, 3911, 5049, 7131, 9717, 11930, 13324, 14032, 14722, 16044, 44 | 18235, 21043, 24261, 27245, 29408, 31047, 33083, 35737, 38158, 39841, 40886, 41556, 42213, 43245, 44552, 45917, 45 | 47119, 47870, 48264, 48770, 49539, 49925, 49668, 49229, 48873, 48432, 47913, 47384, 46902, 46605, 46302, 45760, 46 | 45142, 44704, 44318, 43802, 43332, 43042, 42636, 41996, 41337, 40780, 40348, 40072, 39783, 39377, 39035, 38729, 47 | 38329, 37958, 37837, 37831, 37639, 37223, 36573, 35566, 34312, 33093, 32117, 31428, 30844, 30079, 29059, 27979, 48 | 27098, 26276, 25428, 24454, 23049, 21274, 19381, 17785, 16557, 15675, 15000, 14446, 13983, 13647, 13422, 13168, 49 | 12982, 12753, 12429, 12068, 11998, 12294, 12943, 13955, 15251, 16750, 18381, 20097, 21804, 23476, 25102, 26674, 50 | 28171, 29696, 31260, 32930, 34804, 36952, 39192, 41282, 43127, 44699, 46099, 47372, 48514, 49476, 50175, 50618, 51 | 50888, 51099, 51305, 51404, 51383, 51221, 50812, 50133, 49166, 47971, 46660, 45347, 44055, 42645, 40991, 39107, 52 | 37176, 35376, 33764, 32251, 30646, 28956, 27272, 25659, 24192, 22866, 21633, 20368, 19107, 17971, 16966, 16123, 53 | 15408, 14840, 14468, 14309, 14374, 14559, 14792, 14960, 15096, 15298, 15643, 16099, 16589, 17068, 17606, 18341, 54 | 19312, 20471, 21661, 22740, 23624, 24392, 25151, 25945, 26715, 27439, 28051, 28700, 29403, 30342, 31272, 32180, 55 | ]; 56 | 57 | 58 | /* AKWF_hvoice_0008 256 samples 59 | 60 | +-----------------------------------------------------------------------------------------------------------------+ 61 | | ** ** | 62 | | ** ** | 63 | | ** * | 64 | | ** * ** | 65 | | * ** ******* | 66 | | ** ** ** **** | 67 | | * * *** ** | 68 | |* ** **** ** ** | 69 | |* * ******* *** *** * | 70 | | * *** *** *** ** | 71 | | ** ********** ** ** ** *| 72 | | * *** *** ** ** *| 73 | | ** ** ** ** ** * | 74 | | ** *** *** *** *** ** | 75 | | ** ***** ******* *** ** | 76 | +-----------------------------------------------------------------------------------------------------------------+ 77 | 78 | 79 | */ 80 | 81 | 82 | //suited for higher notes 83 | static hvoice_0008 = [ 84 | 33181, 35486, 37968, 40376, 42771, 45080, 47364, 49564, 51743, 53835, 55881, 57791, 59589, 61183, 62575, 63700, 85 | 64584, 65156, 65464, 65532, 65397, 65033, 64481, 63756, 62857, 61795, 60589, 59244, 57769, 56187, 54493, 52700, 86 | 50804, 48810, 46710, 44526, 42252, 39909, 37506, 35065, 32606, 30170, 27780, 25481, 23312, 21314, 19526, 17987, 87 | 16727, 15759, 15085, 14680, 14507, 14529, 14701, 14981, 15335, 15729, 16131, 16520, 16883, 17220, 17530, 17823, 88 | 18107, 18405, 18742, 19133, 19600, 20152, 20793, 21528, 22346, 23219, 24124, 25037, 25940, 26826, 27679, 28455, 89 | 29125, 29658, 30037, 30271, 30376, 30375, 30291, 30150, 29969, 29778, 29596, 29441, 29340, 29318, 29391, 29570, 90 | 29865, 30266, 30762, 31336, 31959, 32594, 33218, 33807, 34355, 34865, 35333, 35749, 36106, 36392, 36606, 36762, 91 | 36877, 36973, 37079, 37206, 37368, 37560, 37770, 37977, 38155, 38274, 38317, 38275, 38137, 37905, 37573, 37132, 92 | 36575, 35905, 35127, 34265, 33341, 32369, 31357, 30322, 29276, 28227, 27188, 26149, 25114, 24089, 23079, 22088, 93 | 21128, 20203, 19329, 18525, 17801, 17168, 16639, 16221, 15920, 15747, 15693, 15744, 15903, 16162, 16516, 16965, 94 | 17495, 18088, 18756, 19498, 20308, 21208, 22190, 23241, 24387, 25619, 26888, 28221, 29593, 30838, 31899, 32887, 95 | 33833, 34687, 35481, 36256, 37010, 37776, 38593, 39464, 40400, 41413, 42484, 43599, 44747, 45894, 47011, 48063, 96 | 48999, 49776, 50378, 50781, 50983, 50998, 50824, 50475, 49983, 49374, 48691, 47998, 47342, 46758, 46270, 45870, 97 | 45527, 45218, 44900, 44544, 44118, 43587, 42913, 42071, 41043, 39822, 38423, 36863, 35174, 33393, 31568, 29740, 98 | 27974, 26304, 24772, 23384, 22152, 21048, 20083, 19228, 18476, 17792, 17155, 16532, 15920, 15312, 14725, 14184, 99 | 13722, 13384, 13196, 13215, 13436, 13915, 14619, 15604, 16811, 18301, 19969, 21884, 23902, 26136, 28375, 30869, 100 | ]; 101 | 102 | 103 | 104 | 105 | /* AKWF_fmsynth_0012 256 samples 106 | 107 | +-----------------------------------------------------------------------------------------------------------------+ 108 | | ***** ****** *** ** | 109 | | *** **** *** ** | 110 | | *** *** ** * | 111 | |*** **** *** ** | 112 | |* **** *** * | 113 | | **** *** * **| 114 | | **** *** ** ** | 115 | | ****** ***** * *** | 116 | | *********** * ** | 117 | | ** ** | 118 | | * ** | 119 | | * ** | 120 | | * ** | 121 | | ** ** | 122 | | * *** | 123 | +-----------------------------------------------------------------------------------------------------------------+ 124 | 125 | 126 | */ 127 | 128 | 129 | static fmsynth_0012 = [ 130 | 33063, 33949, 34766, 35596, 36388, 37173, 37929, 38666, 39379, 40067, 40731, 41364, 41976, 42552, 43106, 43625, 131 | 44121, 44580, 45016, 45417, 45794, 46139, 46456, 46745, 47012, 47248, 47464, 47656, 47829, 47980, 48113, 48229, 132 | 48329, 48416, 48490, 48551, 48601, 48643, 48676, 48703, 48723, 48736, 48745, 48748, 48747, 48741, 48728, 48714, 133 | 48693, 48667, 48635, 48595, 48550, 48497, 48436, 48364, 48284, 48193, 48090, 47974, 47846, 47706, 47550, 47380, 134 | 47192, 46991, 46774, 46540, 46289, 46023, 45740, 45441, 45125, 44794, 44446, 44084, 43708, 43316, 42915, 42499, 135 | 42073, 41634, 41187, 40727, 40263, 39791, 39311, 38826, 38337, 37843, 37348, 36850, 36350, 35850, 35350, 34852, 136 | 34356, 33862, 33369, 32882, 32401, 31923, 31451, 30983, 30524, 30072, 29626, 29187, 28759, 28338, 27928, 27527, 137 | 27135, 26754, 26385, 26024, 25678, 25343, 25020, 24708, 24412, 24128, 23858, 23602, 23360, 23134, 22922, 22727, 138 | 22547, 22383, 22235, 22105, 21991, 21894, 21816, 21755, 21711, 21687, 21680, 21693, 21724, 21775, 21845, 21934, 139 | 22044, 22172, 22320, 22487, 22677, 22885, 23113, 23363, 23631, 23921, 24229, 24558, 24908, 25279, 25671, 26081, 140 | 26514, 26965, 27437, 27932, 28443, 28979, 29533, 30108, 30703, 31318, 31954, 32607, 33281, 33975, 34687, 35416, 141 | 36163, 36925, 37703, 38490, 39289, 40096, 40909, 41719, 42527, 43324, 44108, 44867, 45598, 46286, 46925, 47500, 142 | 47996, 48401, 48694, 48858, 48869, 48707, 48340, 47746, 46886, 45734, 44239, 42380, 40091, 37352, 34077, 30264, 143 | 25772, 21005, 16784, 13147, 10049, 7453, 5312, 3592, 2252, 1258, 572, 166, 4, 62, 308, 722, 144 | 1277, 1954, 2735, 3599, 4533, 5522, 6558, 7622, 8712, 9817, 10931, 12049, 13166, 14275, 15381, 16475, 145 | 17558, 18628, 19685, 20730, 21760, 22777, 23780, 24757, 25723, 26697, 27638, 28588, 29498, 30430, 31300, 32236, 146 | ]; 147 | 148 | /* Adventure Kid Waveforms (AKWF) converted for use with Teensy Audio Library 149 | * 150 | * Adventure Kid Waveforms(AKWF) Open waveforms library 151 | * https://www.adventurekid.se/akrt/waveforms/adventure-kid-waveforms/ 152 | * 153 | * This code is in the public domain, CC0 1.0 Universal (CC0 1.0) 154 | * https://creativecommons.org/publicdomain/zero/1.0/ 155 | * 156 | * Converted by Brad Roy, https://github.com/prosper00 157 | */ 158 | 159 | /* AKWF_granular_0044 256 samples 160 | 161 | +-----------------------------------------------------------------------------------------------------------------+ 162 | | ** ** | 163 | | ** * *** | 164 | | * * ** *** | 165 | | ** * * *** | 166 | | * * ** * | 167 | | *** * * * * | 168 | | *** * * * ** * | 169 | |* * ** * * * ********************************************************| 170 | | * * * * * ** | 171 | | ** * * * * **** | 172 | | **** ** * * ***** | 173 | | **** * ** ** | 174 | | * * | 175 | | * ** | 176 | | ** ** | 177 | +-----------------------------------------------------------------------------------------------------------------+ 178 | 179 | 180 | */ 181 | 182 | 183 | static granular_0044 = [ 184 | 32869, 33554, 33950, 34774, 35134, 36047, 36319, 37297, 37454, 38494, 38511, 39617, 39443, 40664, 40161, 41814, 185 | 38882, 23368, 22518, 21974, 20997, 20545, 19523, 19169, 18210, 17982, 17163, 17128, 16552, 16803, 16594, 17281, 186 | 17607, 18928, 20002, 22162, 24166, 27270, 30129, 33900, 37194, 41086, 44210, 47679, 50274, 53080, 55123, 57263, 187 | 58843, 60380, 61646, 62635, 63750, 64193, 65294, 65000, 65535, 64171, 65114, 62655, 64487, 59956, 65114, 36210, 188 | 212, 3832, 99, 1558, 1, 510, 0, 197, 120, 547, 1094, 1697, 2901, 3914, 5892, 7586, 189 | 10533, 13157, 17216, 20988, 26107, 30361, 35515, 39386, 43777, 46670, 49891, 51621, 53703, 54463, 55644, 55730, 190 | 56241, 55895, 55900, 55320, 54914, 54268, 53486, 52949, 51761, 51561, 49784, 50399, 47306, 51139, 28999, 14730, 191 | 19959, 17550, 20023, 18982, 20690, 20318, 21655, 21737, 22859, 23286, 24286, 24994, 25914, 26840, 27701, 28756, 192 | 29549, 30623, 31268, 32196, 32566, 33184, 33197, 33422, 33100, 32970, 32677, 32843, 32704, 32820, 32726, 32801, 193 | 32745, 32785, 32759, 32771, 32771, 32760, 32779, 32753, 32784, 32751, 32787, 32749, 32786, 32751, 32785, 32753, 194 | 32782, 32756, 32779, 32759, 32775, 32762, 32773, 32765, 32771, 32768, 32768, 32769, 32767, 32769, 32766, 32770, 195 | 32766, 32769, 32767, 32770, 32767, 32769, 32767, 32770, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 196 | 32768, 32767, 32767, 32768, 32767, 32768, 32767, 32768, 32769, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 197 | 32767, 32768, 32768, 32768, 32769, 32768, 32768, 32768, 32767, 32768, 32768, 32769, 32768, 32768, 32767, 32769, 198 | 32767, 32769, 32768, 32769, 32767, 32768, 32767, 32768, 32768, 32768, 32768, 32767, 32770, 32767, 32770, 32766, 199 | 32771, 32765, 32772, 32762, 32775, 32757, 32786, 32653, 32303, 32073, 32011, 32104, 32434, 32756, 32768, 32767, 200 | ]; 201 | 202 | 203 | } -------------------------------------------------------------------------------- /js/audio/RealizedSound.js: -------------------------------------------------------------------------------- 1 | class RealizedSound { 2 | static MIN_SAMPLE_RATE = SAMPLE_RATE; 3 | 4 | constructor(length, sample_rate) { 5 | this._buffer = AUDIO_CONTEXT.createBuffer(1, length, sample_rate); 6 | } 7 | 8 | 9 | getBuffer() { 10 | return this._buffer.getChannelData(0); 11 | } 12 | 13 | 14 | source = null; 15 | play() { 16 | ULBS(); 17 | 18 | if (this.source!=null){ 19 | this.stop(); 20 | } 21 | this.source = AUDIO_CONTEXT.createBufferSource(); 22 | 23 | this.source.buffer = this._buffer; 24 | this.source.connect(AUDIO_CONTEXT.destination); 25 | 26 | var t = AUDIO_CONTEXT.currentTime; 27 | if (typeof this.source.start != 'undefined') { 28 | this.source.start(t); 29 | } else { 30 | this.source.noteOn(t); 31 | } 32 | this.source.onended = function() { 33 | if (this.source){ 34 | this.source.disconnect() 35 | } 36 | } 37 | } 38 | 39 | stop() { 40 | if (this.source==null){ 41 | return; 42 | } 43 | this.source.stop(); 44 | this.source.disconnect(); 45 | this.source = null; 46 | } 47 | 48 | getDataUri() { 49 | const BIT_DEPTH=16; 50 | var raw_buffer = this.getBuffer(); 51 | var output_buffer = new Array(raw_buffer.length); 52 | for (var i = 0; i < raw_buffer.length; i++) { 53 | // bit_depth is always 16, rescale [-1.0, 1.0) to [0, 65536) 54 | // Use 32768 (2^15) for 16-bit audio conversion (range: -32768 to 32767) 55 | output_buffer[i] = Math.floor(32768 * Math.max(-1, Math.min(raw_buffer[i], 1)))|0; 56 | } 57 | var wav = MakeRiff(SAMPLE_RATE, BIT_DEPTH, output_buffer); 58 | return wav.dataURI; 59 | } 60 | 61 | static from_buffer(buffer) { 62 | var sound = new RealizedSound(buffer.length, RealizedSound.MIN_SAMPLE_RATE); 63 | sound._buffer.copyToChannel(buffer, 0); 64 | return sound; 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /js/audio/audio_globals.js: -------------------------------------------------------------------------------- 1 | const SAMPLE_RATE = 44100; 2 | const CONVERSION_FACTOR = (2*Math.PI)/SAMPLE_RATE; 3 | 4 | var AUDIO_CONTEXT; 5 | function checkAudioContextExists() { 6 | try { 7 | if (AUDIO_CONTEXT == null) { 8 | if (typeof AudioContext != 'undefined') { 9 | AUDIO_CONTEXT = new AudioContext(); 10 | } else if (typeof webkitAudioContext != 'undefined') { 11 | AUDIO_CONTEXT = new webkitAudioContext(); 12 | } 13 | } 14 | } catch (ex) { 15 | window.console.log(ex) 16 | } 17 | } 18 | 19 | checkAudioContextExists(); 20 | //unlock bullshit 21 | function ULBS() { 22 | if (AUDIO_CONTEXT.state === 'suspended') { 23 | var unlock = function() { 24 | AUDIO_CONTEXT.resume().then(function() { 25 | document.body.removeEventListener('touchstart', unlock); 26 | document.body.removeEventListener('touchend', unlock); 27 | document.body.removeEventListener('mousedown', unlock); 28 | document.body.removeEventListener('mouseup', unlock); 29 | document.body.removeEventListener('keydown', unlock); 30 | document.body.removeEventListener('keyup', unlock); 31 | }); 32 | }; 33 | 34 | document.body.addEventListener('touchstart', unlock, false); 35 | document.body.addEventListener('touchend', unlock, false); 36 | document.body.addEventListener('mousedown', unlock, false); 37 | document.body.addEventListener('mouseup', unlock, false); 38 | document.body.addEventListener('keydown', unlock, false); 39 | document.body.addEventListener('keyup', unlock, false); 40 | } 41 | } -------------------------------------------------------------------------------- /js/audio/puredata.js: -------------------------------------------------------------------------------- 1 | /* 2 | This file implements PureData DSP functions to operate on audio buffers. 3 | */ 4 | 5 | const PD_COSTABLESIZE = 2048; 6 | const PD_UNITBIT32 = 1572864; /* 3*2^19; bit 32 has place value 1 */ 7 | const PD_HIOFFSET = 1; 8 | const PD_LOWOFFSET = 0; 9 | 10 | var COSTABLENAME; 11 | 12 | function generate_tables(){ 13 | COSTABLENAME = new Float32Array(PD_COSTABLESIZE+1); 14 | for (let i = 0; i < PD_COSTABLESIZE; i++) { 15 | COSTABLENAME[i] = Math.cos( 2 * Math.PI * i / PD_COSTABLESIZE); 16 | } 17 | COSTABLENAME[0] = 1; 18 | COSTABLENAME[PD_COSTABLESIZE] = 1; 19 | COSTABLENAME[(PD_COSTABLESIZE/4)|0] = 0; 20 | COSTABLENAME[(3*PD_COSTABLESIZE/4)|0] = 0; 21 | COSTABLENAME[(PD_COSTABLESIZE/2)|0] = -1; 22 | } 23 | 24 | function gt(signal_a,signal_b){ 25 | var result = new Float32Array(signal_a.length); 26 | for (let i = 0; i < signal_a.length; i++) { 27 | result[i] = signal_a[i] < signal_b[i] ? 1 : 0; 28 | } 29 | return result; 30 | } 31 | 32 | generate_tables(); 33 | var puredata_stream_length = 0; 34 | function pd_set_stream_length_seconds(seconds){ 35 | puredata_stream_length = seconds * SAMPLE_RATE; 36 | } 37 | 38 | function pd_fn(fn){ 39 | var result = new Float32Array(puredata_stream_length); 40 | for (let i = 0; i < result.length; i++) { 41 | result[i] = fn(i/SAMPLE_RATE); 42 | } 43 | return result; 44 | } 45 | 46 | // white noise signal (in the range from -1 to 1). 47 | // https://pd.iem.sh/objects/noise~/ 48 | // https://github.com/pure-data/pure-data/blob/12de13067aee29e332a34eb3539fa3cb967b63a1/src/d_osc.c#L372 49 | function pd_noise(){ 50 | var result = new Float32Array(puredata_stream_length); 51 | for (let i = 0; i < result.length; i++) { 52 | result[i] = Math.random() * 2 - 1; 53 | } 54 | return result; 55 | } 56 | 57 | function pd_clip(buffer, min_signal, max_signal){ 58 | var result = new Float32Array(buffer.length); 59 | for (let i = 0; i < buffer.length; i++) { 60 | result[i] = Math.max(min_signal[i], Math.min(buffer[i], max_signal[i])); 61 | } 62 | return result; 63 | } 64 | 65 | // cosine wave oscillator 66 | // https://pd.iem.sh/objects/osc~/ 67 | // https://github.com/pure-data/pure-data/blob/12de13067aee29e332a34eb3539fa3cb967b63a1/src/d_osc.h#L73C1-L99C6 68 | function pd_osc(freq_signal){ 69 | /* original code: 70 | t_osc *x = (t_osc *)(w[1]); 71 | t_sample *in = (t_sample *)(w[2]); 72 | t_sample *out = (t_sample *)(w[3]); 73 | int n = (int)(w[4]); 74 | float *tab = COSTABLENAME, *addr; 75 | t_float f1, f2, frac; 76 | double dphase = x->x_phase + UNITBIT32; 77 | int normhipart; 78 | union tabfudge tf; 79 | float conv = x->x_conv; 80 | 81 | tf.tf_d = UNITBIT32; 82 | normhipart = tf.tf_i[HIOFFSET]; 83 | #if 0 84 | while (n--) 85 | { 86 | tf.tf_d = dphase; 87 | dphase += *in++ * conv; 88 | addr = tab + (tf.tf_i[HIOFFSET] & (COSTABLESIZE-1)); 89 | tf.tf_i[HIOFFSET] = normhipart; 90 | frac = tf.tf_d - UNITBIT32; 91 | f1 = addr[0]; 92 | f2 = addr[1]; 93 | *out++ = f1 + frac * (f2 - f1); 94 | } 95 | */ 96 | var result = new Float32Array(freq_signal.length); 97 | let dphase = PD_UNITBIT32; 98 | let conv = 2 * Math.PI / SAMPLE_RATE; 99 | 100 | // The bit manipulation in C is not directly translatable to JavaScript 101 | // Instead, we'll use a simpler approach that achieves the same result 102 | let phase = 0; 103 | 104 | for (let i = 0; i < freq_signal.length; i++) { 105 | // Update phase based on frequency 106 | phase += freq_signal[i] * conv; 107 | 108 | // Keep phase in [-π, π] for both positive and negative frequencies 109 | while (phase >= Math.PI) { 110 | phase -= 2 * Math.PI; 111 | } 112 | while (phase < -Math.PI) { 113 | phase += 2 * Math.PI; 114 | } 115 | 116 | // Get table index and fractional part 117 | let index = (((phase + Math.PI) / (2 * Math.PI)) * PD_COSTABLESIZE)|0; 118 | let idx1 = Math.floor(index) % PD_COSTABLESIZE; 119 | let idx2 = (idx1 + 1) % PD_COSTABLESIZE; 120 | let frac = index - idx1; 121 | 122 | // Linear interpolation between adjacent table values 123 | let f1 = COSTABLENAME[idx1]; 124 | let f2 = COSTABLENAME[idx2]; 125 | result[i] = f1 + frac * (f2 - f1); 126 | } 127 | 128 | return result; 129 | } 130 | 131 | function pd_mul(buffer, multiplier_signal){ 132 | var result = new Float32Array(buffer.length); 133 | for (let i = 0; i < buffer.length; i++) { 134 | result[i] = buffer[i] * multiplier_signal[i]; 135 | } 136 | return result; 137 | } 138 | 139 | function pd_div(buffer, divisor_signal){ 140 | var result = new Float32Array(buffer.length); 141 | for (let i = 0; i < buffer.length; i++) { 142 | result[i] = buffer[i] / divisor_signal[i]; 143 | } 144 | return result; 145 | } 146 | 147 | function pd_add(buffer, addend_signal){ 148 | var result = new Float32Array(buffer.length); 149 | for (let i = 0; i < buffer.length; i++) { 150 | result[i] = buffer[i] + addend_signal[i]; 151 | } 152 | return result; 153 | } 154 | 155 | function pd_polyadd(...buffers){ 156 | var result = new Float32Array(buffers[0].length); 157 | for (let i = 0; i < result.length; i++) { 158 | result[i] = buffers[0][i]; 159 | for (let j = 1; j < buffers.length; j++) { 160 | result[i] += buffers[j][i]; 161 | } 162 | } 163 | return result; 164 | } 165 | 166 | function pd_c(value){ 167 | var result = new Float32Array(puredata_stream_length); 168 | result.fill(value); 169 | return result; 170 | } 171 | 172 | function pd_abs(buffer){ 173 | var result = new Float32Array(buffer.length); 174 | for (let i = 0; i < buffer.length; i++) { 175 | result[i] = Math.abs(buffer[i]); 176 | } 177 | return result; 178 | } 179 | 180 | function pd_sqrt(buffer){ 181 | var result = new Float32Array(buffer.length); 182 | for (let i = 0; i < buffer.length; i++) { 183 | result[i] = Math.sqrt(buffer[i]); 184 | } 185 | return result; 186 | } 187 | 188 | function sigbp_qcos(f) 189 | { 190 | if (f >= -(0.5*Math.PI) && f <= 0.5*Math.PI) 191 | { 192 | var g = f*f; 193 | return (((g*g*g * (-1.0/720.0) + g*g*(1.0/24.0)) - g*0.5) + 1); 194 | } 195 | else { 196 | return 0; 197 | } 198 | } 199 | 200 | /* 201 | // ---------------- bp~ - 2-pole bandpass filter. ----------------- 202 | 203 | typedef struct bpctl 204 | { 205 | t_sample c_x1; 206 | t_sample c_x2; 207 | t_sample c_coef1; 208 | t_sample c_coef2; 209 | t_sample c_gain; 210 | } t_bpctl; 211 | 212 | typedef struct sigbp 213 | { 214 | t_object x_obj; 215 | t_float x_sr; 216 | t_float x_freq; 217 | t_float x_q; 218 | t_bpctl x_cspace; 219 | t_float x_f; 220 | } t_sigbp; 221 | 222 | t_class *sigbp_class; 223 | 224 | static void sigbp_docoef(t_sigbp *x, t_floatarg f, t_floatarg q); 225 | 226 | static void *sigbp_new(t_floatarg f, t_floatarg q) 227 | { 228 | t_sigbp *x = (t_sigbp *)pd_new(sigbp_class); 229 | inlet_new(&x->x_obj, &x->x_obj.ob_pd, gensym("float"), gensym("ft1")); 230 | inlet_new(&x->x_obj, &x->x_obj.ob_pd, gensym("float"), gensym("ft2")); 231 | outlet_new(&x->x_obj, &s_signal); 232 | x->x_sr = 44100; 233 | x->x_cspace.c_x1 = 0; 234 | x->x_cspace.c_x2 = 0; 235 | sigbp_docoef(x, f, q); 236 | x->x_f = 0; 237 | return (x); 238 | } 239 | 240 | static t_float sigbp_qcos(t_float f) 241 | { 242 | if (f >= -(0.5f*3.14159f) && f <= 0.5f*3.14159f) 243 | { 244 | t_float g = f*f; 245 | return (((g*g*g * (-1.0f/720.0f) + g*g*(1.0f/24.0f)) - g*0.5) + 1); 246 | } 247 | else return (0); 248 | } 249 | 250 | static void sigbp_docoef(t_sigbp *x, t_floatarg f, t_floatarg q) 251 | { 252 | t_float r, oneminusr, omega; 253 | if (f < 0.001) f = 10; 254 | if (q < 0) q = 0; 255 | x->x_freq = f; 256 | x->x_q = q; 257 | omega = f * (2.0f * 3.14159f) / x->x_sr; 258 | if (q < 0.001) oneminusr = 1.0f; 259 | else oneminusr = omega/q; 260 | if (oneminusr > 1.0f) oneminusr = 1.0f; 261 | r = 1.0f - oneminusr; 262 | x->x_cspace.c_coef1 = 2.0f * sigbp_qcos(omega) * r; 263 | x->x_cspace.c_coef2 = - r * r; 264 | x->x_cspace.c_gain = 2 * oneminusr * (oneminusr + r * omega); 265 | } 266 | 267 | static void sigbp_ft1(t_sigbp *x, t_floatarg f) 268 | { 269 | sigbp_docoef(x, f, x->x_q); 270 | } 271 | 272 | static void sigbp_ft2(t_sigbp *x, t_floatarg q) 273 | { 274 | sigbp_docoef(x, x->x_freq, q); 275 | } 276 | 277 | static void sigbp_clear(t_sigbp *x, t_floatarg q) 278 | { 279 | x->x_cspace.c_x1 = x->x_cspace.c_x2 = 0; 280 | } 281 | 282 | static t_int *sigbp_perform(t_int *w) 283 | { 284 | t_sample *in = (t_sample *)(w[1]); 285 | t_sample *out = (t_sample *)(w[2]); 286 | t_bpctl *c = (t_bpctl *)(w[3]); 287 | int n = (int)w[4]; 288 | int i; 289 | t_sample last = c->c_x1; 290 | t_sample prev = c->c_x2; 291 | t_sample coef1 = c->c_coef1; 292 | t_sample coef2 = c->c_coef2; 293 | t_sample gain = c->c_gain; 294 | for (i = 0; i < n; i++) 295 | { 296 | t_sample output = *in++ + coef1 * last + coef2 * prev; 297 | *out++ = gain * output; 298 | prev = last; 299 | last = output; 300 | } 301 | if (PD_BIGORSMALL(last)) 302 | last = 0; 303 | if (PD_BIGORSMALL(prev)) 304 | prev = 0; 305 | c->c_x1 = last; 306 | c->c_x2 = prev; 307 | return (w+5); 308 | } 309 | 310 | */ 311 | 312 | // 2-pole bandpass filter 313 | // https://pd.iem.sh/objects/bp~/ 314 | // https://github.com/pure-data/pure-data/blob/12de13067aee29e332a34eb3539fa3cb967b63a1/src/d_filter.c#L325 315 | function pd_bp(buffer, center_freq_signal, q_signal) { 316 | let output_buffer = new Float32Array(buffer.length); 317 | let last = buffer[0]; 318 | let prev = buffer[0]; 319 | 320 | for (let i = 0; i < buffer.length; i++) { 321 | let f = center_freq_signal[i]; 322 | let q = q_signal[i]; 323 | if (f < 0.001) f = 10; 324 | if (q < 0) q = 0; 325 | 326 | let omega = f * (2.0 * Math.PI) / SAMPLE_RATE; 327 | let oneminusr; 328 | 329 | if (q < 0.001) { 330 | oneminusr = 1.0; 331 | } else { 332 | oneminusr = omega/q; 333 | } 334 | if (oneminusr > 1.0) oneminusr = 1.0; 335 | 336 | let r = 1.0 - oneminusr; 337 | let coef1 = 2.0 * sigbp_qcos(omega) * r; 338 | let coef2 = -r * r; 339 | let gain = 2 * oneminusr * (oneminusr + r * omega); 340 | 341 | let input = buffer[i]; 342 | let output = input + coef1 * last + coef2 * prev; 343 | output_buffer[i] = gain * output; 344 | prev = last; 345 | last = output; 346 | } 347 | return output_buffer; 348 | } 349 | 350 | 351 | // lop one-pole low pass filter. 352 | // https://pd.iem.sh/objects/lop~/ 353 | // https://github.com/pure-data/pure-data/blob/12de13067aee29e332a34eb3539fa3cb967b63a1/src/d_filter.c#L139 354 | function pd_lop(buffer, filter_coeff_signal) { 355 | let output_buffer = new Float32Array(buffer.length); 356 | let last = buffer[0]; 357 | 358 | for (let i = 0; i < buffer.length; i++) { 359 | let coef = filter_coeff_signal[i]*CONVERSION_FACTOR; 360 | if (coef > 1){ 361 | coef = 1; 362 | } 363 | if (coef < 0){ 364 | coef = 0; 365 | } 366 | last = coef * buffer[i] + (1-coef) * last; 367 | output_buffer[i] = last; 368 | } 369 | 370 | return output_buffer; 371 | } 372 | 373 | // hip one-pole high pass filter. 374 | // https://pd.iem.sh/objects/hip~/ 375 | // https://github.com/pure-data/pure-data/blob/12de13067aee29e332a34eb3539fa3cb967b63a1/src/d_filter.c#L9 376 | function pd_hip(buffer, filter_coeff_signal) { 377 | let output_buffer = new Float32Array(buffer.length); 378 | let last = buffer[0]; 379 | 380 | for (let i = 0; i < buffer.length; i++) { 381 | let f = filter_coeff_signal[i]; 382 | let coef = 1 - f*CONVERSION_FACTOR; 383 | if (coef< 0){ 384 | coef = 0; 385 | } 386 | else if (coef > 1){ 387 | coef = 1; 388 | } 389 | 390 | if (coef < 1){ 391 | const normal = 0.5*(1+coef); 392 | const cur = buffer[i] + coef*last; 393 | output_buffer[i] = normal*(cur-last); 394 | last = cur; 395 | } 396 | else { 397 | output_buffer[i] = buffer[i]; 398 | } 399 | } 400 | 401 | return output_buffer; 402 | } 403 | 404 | 405 | /* voltage-controlled band/low-pass filter 406 | 1st 407 | signal - audio signal to be filtered. 408 | 2nd 409 | signal - resonant frequency in Hz. 410 | 3rd 411 | float - set Q. 412 | */ 413 | // https://pd.iem.sh/objects/vcf~/ 414 | // https://github.com/pure-data/pure-data/blob/12de13067aee29e332a34eb3539fa3cb967b63a1/src/d_osc.c#L289 415 | // https://github.com/pure-data/pure-data/blob/12de13067aee29e332a34eb3539fa3cb967b63a1/src/d_osc.h#L131 416 | // absolute black magic. Don't understand it - did my best, then let cursor fix it. 417 | function pd_vcf(buffer, res_freq_signal, q_signal) { 418 | let output_buffer = new Float32Array(buffer.length); 419 | let re = 0; 420 | let im = 0; 421 | 422 | // Create a proper tabfudge-like structure to match the original C code 423 | let tf = { 424 | d: 0, 425 | i: new Uint32Array(2) 426 | }; 427 | 428 | // Get the normhipart constant similar to the C code 429 | tf.d = PD_UNITBIT32; 430 | const normhipart = tf.i[PD_HIOFFSET]; 431 | 432 | for (let i = 0; i < buffer.length; i++) { 433 | let q = q_signal[i]; 434 | let qinv = q > 0 ? (1/q) : 0; 435 | let ampcorrect = 2 - 2/(q+2); 436 | let coefr = 0; 437 | let coefi = 0; 438 | let tab = COSTABLENAME; 439 | 440 | // Get the frequency coefficient - use the right conversion factor 441 | let cf = res_freq_signal[i] * CONVERSION_FACTOR; 442 | if (cf < 0) cf = 0; 443 | 444 | // Use the same conversion as in the original C code 445 | let cfindx = cf * (PD_COSTABLESIZE/6.28318); 446 | 447 | // Calculate resonance factor 448 | let r = (qinv > 0) ? (1 - cf * qinv) : 0; 449 | if (r < 0) r = 0; 450 | let oneminusr = 1 - r; 451 | 452 | // Bit-twiddling to get the table index and fraction - similar to original code 453 | let dphase = cfindx + PD_UNITBIT32; 454 | tf.d = dphase; 455 | let tabindex = tf.i[PD_HIOFFSET] & (PD_COSTABLESIZE-1); 456 | 457 | tf.i[PD_HIOFFSET] = normhipart; 458 | let frac = tf.d - PD_UNITBIT32; 459 | 460 | // Get the real coefficient using interpolation 461 | let f1 = tab[tabindex]; 462 | let f2 = tab[tabindex+1]; 463 | coefr = r * (f1 + frac * (f2 - f1)); 464 | 465 | // Get the imaginary coefficient using interpolation 466 | tabindex = ((tabindex - (PD_COSTABLESIZE/4)) & (PD_COSTABLESIZE-1)); 467 | f1 = tab[tabindex]; 468 | f2 = tab[tabindex+1]; 469 | coefi = r * (f1 + frac * (f2 - f1)); 470 | 471 | // Apply the filter 472 | let inputSample = buffer[i]; 473 | let re2 = re; 474 | re = ampcorrect * oneminusr * inputSample + coefr * re2 - coefi * im; 475 | im = coefi * re2 + coefr * im; 476 | 477 | // Handle numerical instability 478 | if (Math.abs(re) < 1e-10) re = 0; 479 | if (Math.abs(im) < 1e-10) im = 0; 480 | 481 | output_buffer[i] = re; 482 | } 483 | 484 | return output_buffer; 485 | } 486 | -------------------------------------------------------------------------------- /js/audio/puredata_modules.js: -------------------------------------------------------------------------------- 1 | 2 | const puredata_modules = {"dirt":"#N canvas 283 84 1002 691 12;\r\n#X obj 54 309 *~;\r\n#X obj 72 47 inlet~;\r\n#X obj 76 443 outlet~;\r\n#X obj 138 59 switch~;\r\n#X obj 138 33 inlet;\r\n#X obj 128 221 osc~;\r\n#X obj 127 157 *~;\r\n#X obj 150 82 noise~;\r\n#X obj 150 127 *~ 70;\r\n#X obj 150 104 lop~ 80;\r\n#X obj 100 127 +~ 0.3;\r\n#X obj 128 242 hip~ 200;\r\n#X obj 71 126 *~;\r\n#X obj 70 148 *~;\r\n#X obj 18 250 osc~ 80;\r\n#X obj 18 228 +~ 40;\r\n#X obj 19 202 *~ 500;\r\n#X obj 77 394 +~;\r\n#X obj 127 264 clip~ -1 1;\r\n#X obj 128 179 *~ 70;\r\n#X obj 126 330 *~ 0.04;\r\n#X obj 55 332 *~ 0.5;\r\n#X obj 128 200 +~ 70;\r\n#X connect 0 0 21 0;\r\n#X connect 1 0 10 0;\r\n#X connect 1 0 12 0;\r\n#X connect 1 0 12 1;\r\n#X connect 4 0 3 0;\r\n#X connect 5 0 11 0;\r\n#X connect 6 0 19 0;\r\n#X connect 7 0 9 0;\r\n#X connect 8 0 6 1;\r\n#X connect 9 0 8 0;\r\n#X connect 10 0 6 0;\r\n#X connect 11 0 18 0;\r\n#X connect 12 0 13 0;\r\n#X connect 12 0 13 1;\r\n#X connect 13 0 0 1;\r\n#X connect 13 0 16 0;\r\n#X connect 14 0 0 0;\r\n#X connect 15 0 14 0;\r\n#X connect 16 0 15 0;\r\n#X connect 17 0 2 0;\r\n#X connect 18 0 20 0;\r\n#X connect 19 0 22 0;\r\n#X connect 20 0 17 1;\r\n#X connect 21 0 17 0;\r\n#X connect 22 0 5 0;\r\n","grass":"#N canvas 136 86 1002 691 12;\r\n#X obj 293 399 *~;\r\n#X obj 311 137 inlet~;\r\n#X obj 401 564 outlet~;\r\n#X obj 310 216 *~;\r\n#X obj 309 238 *~;\r\n#X obj 257 340 osc~ 80;\r\n#X obj 401 527 +~;\r\n#X obj 294 422 *~ 0.8;\r\n#X obj 257 318 +~ 30;\r\n#X obj 257 292 *~ 600;\r\n#X obj 257 364 clip~ 0 0.5;\r\n#X obj 591 98 noise~;\r\n#X obj 598 269 lop~ 16;\r\n#X obj 493 231 *~;\r\n#X obj 493 255 *~;\r\n#X obj 599 295 *~ 23800;\r\n#X obj 492 159 /~;\r\n#X obj 521 132 lop~ 2000;\r\n#X obj 459 132 lop~ 300;\r\n#X obj 492 298 clip~ -0.9 0.9;\r\n#X obj 599 317 +~ 3400;\r\n#X obj 492 276 *~ 1e-05;\r\n#X obj 492 196 hip~ 2500;\r\n#X obj 477 472 *~;\r\n#X obj 599 342 clip~ 2000 10000;\r\n#X obj 493 418 hip~ 900;\r\n#X obj 493 450 *~ 0.3;\r\n#X obj 493 388 vcf~ 3333 1;\r\n#X connect 0 0 7 0;\r\n#X connect 1 0 3 0;\r\n#X connect 1 0 3 1;\r\n#X connect 1 0 23 0;\r\n#X connect 3 0 4 0;\r\n#X connect 3 0 4 1;\r\n#X connect 4 0 0 1;\r\n#X connect 4 0 9 0;\r\n#X connect 5 0 10 0;\r\n#X connect 6 0 2 0;\r\n#X connect 7 0 6 0;\r\n#X connect 8 0 5 0;\r\n#X connect 9 0 8 0;\r\n#X connect 10 0 0 0;\r\n#X connect 11 0 12 0;\r\n#X connect 11 0 17 0;\r\n#X connect 11 0 18 0;\r\n#X connect 12 0 15 0;\r\n#X connect 13 0 14 0;\r\n#X connect 13 0 14 1;\r\n#X connect 14 0 21 0;\r\n#X connect 15 0 20 0;\r\n#X connect 16 0 22 0;\r\n#X connect 17 0 16 1;\r\n#X connect 18 0 16 0;\r\n#X connect 19 0 27 0;\r\n#X connect 20 0 24 0;\r\n#X connect 21 0 19 0;\r\n#X connect 22 0 13 0;\r\n#X connect 22 0 13 1;\r\n#X connect 23 0 6 1;\r\n#X connect 24 0 27 1;\r\n#X connect 25 0 26 0;\r\n#X connect 26 0 23 1;\r\n#X connect 27 0 25 0;\r\n","gravel":"#N canvas 772 379 1002 691 12;\n#X obj 123 19 inlet~;\n#X obj 123 499 outlet~;\n#X obj 252 14 noise~;\n#X obj 136 160 *~;\n#X obj 136 107 /~;\n#X obj 166 70 lop~ 2000;\n#X obj 104 70 lop~ 300;\n#X obj 137 241 clip~ -0.9 0.9;\n#X obj 122 475 *~;\n#X obj 156 380 clip~ 500 10000;\n#X obj 154 271 lop~ 50;\n#X obj 137 134 hip~ 400;\n#X obj 137 219 *~ 0.01;\n#X obj 155 297 *~ 50000;\n#X obj 138 430 hip~ 200;\n#X obj 138 453 *~ 2;\n#X obj 156 355 +~;\n#X obj 173 330 *~ 1000;\n#X obj 138 406 vcf~ 200 3;\n#X connect 0 0 8 0;\n#X connect 0 0 17 0;\n#X connect 2 0 5 0;\n#X connect 2 0 6 0;\n#X connect 2 0 10 0;\n#X connect 3 0 12 0;\n#X connect 4 0 11 0;\n#X connect 5 0 4 1;\n#X connect 6 0 4 0;\n#X connect 7 0 18 0;\n#X connect 8 0 1 0;\n#X connect 9 0 18 1;\n#X connect 10 0 13 0;\n#X connect 11 0 3 0;\n#X connect 11 0 3 1;\n#X connect 12 0 7 0;\n#X connect 13 0 16 0;\n#X connect 14 0 15 0;\n#X connect 15 0 8 1;\n#X connect 16 0 9 0;\n#X connect 17 0 16 1;\n#X connect 18 0 14 0;\n","snow":"#N canvas 768 303 857 606 12;\r\n#X obj 161 453 *~;\r\n#X obj 109 225 noise~;\r\n#X obj 102 270 /~;\r\n#X obj 103 343 clip~ -1 1;\r\n#X obj 60 247 lop~ 110;\r\n#X obj 121 246 lop~ 900;\r\n#X obj 166 169 noise~;\r\n#X obj 165 219 /~;\r\n#X obj 123 196 lop~ 50;\r\n#X obj 184 195 lop~ 70;\r\n#X obj 103 290 *~;\r\n#X obj 192 220 lop~ 10;\r\n#X obj 191 241 *~ 17;\r\n#X obj 190 264 *~;\r\n#X obj 103 313 *~;\r\n#X obj 189 286 +~ 0.5;\r\n#X obj 424 130 inlet~;\r\n#X obj 160 507 outlet~;\r\n#X obj 102 365 hip~ 300;\r\n#X obj 185 389 +~ 700;\r\n#X obj 185 365 *~ 9000;\r\n#X obj 162 476 *~ 0.2;\r\n#X obj 146 412 vcf~ 200 0.5;\r\n#X connect 0 0 21 0;\r\n#X connect 1 0 4 0;\r\n#X connect 1 0 5 0;\r\n#X connect 2 0 10 0;\r\n#X connect 3 0 18 0;\r\n#X connect 4 0 2 0;\r\n#X connect 5 0 2 1;\r\n#X connect 6 0 8 0;\r\n#X connect 6 0 9 0;\r\n#X connect 6 0 11 0;\r\n#X connect 7 0 10 1;\r\n#X connect 8 0 7 0;\r\n#X connect 9 0 7 1;\r\n#X connect 10 0 14 0;\r\n#X connect 11 0 12 0;\r\n#X connect 12 0 13 0;\r\n#X connect 12 0 13 1;\r\n#X connect 13 0 15 0;\r\n#X connect 14 0 3 0;\r\n#X connect 15 0 14 1;\r\n#X connect 16 0 0 1;\r\n#X connect 16 0 20 0;\r\n#X connect 18 0 22 0;\r\n#X connect 19 0 22 1;\r\n#X connect 20 0 19 0;\r\n#X connect 21 0 17 0;\r\n#X connect 22 0 0 0;\r\n","wood":"#N canvas 945 155 1270 731 12;\r\n#X obj 470 445 *~;\r\n#X obj 181 142 inlet~;\r\n#X obj 401 564 outlet~;\r\n#X obj 180 221 *~;\r\n#X obj 401 527 +~;\r\n#X obj 466 255 noise~;\r\n#X obj 419 322 *~ 6;\r\n#X obj 801 262 noise~;\r\n#X obj 355 423 *~;\r\n#X obj 737 287 bp~ 123 20;\r\n#X obj 180 251 *~ 2;\r\n#X obj 336 293 bp~ 95 90;\r\n#X obj 411 291 bp~ 134 90;\r\n#X obj 489 290 bp~ 139 90;\r\n#X obj 567 290 bp~ 154 90;\r\n#X obj 815 286 bp~ 156 90;\r\n#X obj 893 286 bp~ 189 90;\r\n#X obj 745 318 *~ 8;\r\n#X obj 662 289 bp~ 201 70;\r\n#X obj 243 258 sqrt~;\r\n#X obj 356 449 *~ 0.5;\r\n#X obj 469 469 *~ 0.6;\r\n#X connect 0 0 21 0;\r\n#X connect 1 0 3 0;\r\n#X connect 1 0 3 1;\r\n#X connect 1 0 19 0;\r\n#X connect 3 0 10 0;\r\n#X connect 4 0 2 0;\r\n#X connect 5 0 11 0;\r\n#X connect 5 0 12 0;\r\n#X connect 5 0 13 0;\r\n#X connect 5 0 14 0;\r\n#X connect 6 0 8 1;\r\n#X connect 7 0 9 0;\r\n#X connect 7 0 15 0;\r\n#X connect 7 0 16 0;\r\n#X connect 7 0 18 0;\r\n#X connect 8 0 20 0;\r\n#X connect 9 0 17 0;\r\n#X connect 10 0 0 0;\r\n#X connect 11 0 6 0;\r\n#X connect 12 0 6 0;\r\n#X connect 13 0 6 0;\r\n#X connect 14 0 6 0;\r\n#X connect 15 0 17 0;\r\n#X connect 16 0 17 0;\r\n#X connect 17 0 0 1;\r\n#X connect 18 0 17 0;\r\n#X connect 19 0 8 0;\r\n#X connect 20 0 4 0;\r\n#X connect 21 0 4 1;\r\n"}; 3 | -------------------------------------------------------------------------------- /js/audio/puredata_parser.js: -------------------------------------------------------------------------------- 1 | var puredata_functions = {}; 2 | 3 | //for each source file - object of puredata_modules 4 | var puredata_function_names = Object.keys(puredata_modules); 5 | 6 | function pd_compile(src){ 7 | //replace all ";" with "" 8 | src = src.replace(/;/g, ""); 9 | src = src.replace(/\r/g, ""); 10 | var lines = src.split("\n"); 11 | 12 | //split each line at space 13 | lines = lines.map(line => line.split(" ")); 14 | 15 | /* 16 | so we need to parse the lines, then build up the function 17 | lines look like 18 | #N canvas 151 188 450 300 12; 19 | (can ignore everything starting with #N) 20 | 21 | #X obj 411 291 bp~ 134 90; 22 | (bp~ 134 90 is the function call with its arguments - important!) 23 | 24 | #X connect 5 0 11 0; 25 | (connect is the connection between the two objects - very important!) 26 | */ 27 | 28 | var function_calls = []; 29 | var connections = []; 30 | for (let i = 0; i < lines.length; i++) { 31 | var toks = lines[i]; 32 | switch (toks[1]) { 33 | case "obj": 34 | function_calls.push(toks.slice(4)); 35 | break; 36 | case "msg": 37 | //basically the same as sig~ 38 | function_calls.push(["sig~",toks[4]]); 39 | break; 40 | case "connect": 41 | var connection = { 42 | from_ob: parseInt(toks[2]), 43 | from_slot: parseInt(toks[3]), 44 | to_ob: parseInt(toks[4]), 45 | to_slot: parseInt(toks[5]) 46 | }; 47 | connections.push(connection); 48 | break; 49 | } 50 | } 51 | 52 | var function_body = ""; 53 | var output_node_idx = function_calls.findIndex(call => call[0] === "outlet~"); 54 | 55 | var function_tree = build_function_tree(function_calls, connections,output_node_idx); 56 | var function_body = build_function_body(function_tree); 57 | // console.log(function_body); 58 | var fn = new Function("envelope_signal", function_body); 59 | return fn; 60 | } 61 | 62 | var function_info = { 63 | "outlet~":{ 64 | input_slots:1, 65 | parameter_indices:[], 66 | js_name:"outlet~" 67 | }, 68 | "inlet~":{ 69 | input_slots:0, 70 | parameter_indices:[], 71 | js_name:"inlet~" 72 | }, 73 | "*~":{ 74 | input_slots:2, 75 | parameter_indices:[1], 76 | js_name:"pd_mul" 77 | }, 78 | "/~":{ 79 | input_slots:2, 80 | parameter_indices:[1], 81 | js_name:"pd_div" 82 | }, 83 | "+~":{ 84 | input_slots:2, 85 | parameter_indices:[1], 86 | js_name:"pd_add" 87 | }, 88 | "sig~":{ 89 | input_slots:1, 90 | parameter_indices:[0], 91 | js_name:"pd_c" 92 | }, 93 | "vcf~":{ 94 | input_slots:3, 95 | parameter_indices:[2], 96 | js_name:"pd_vcf" 97 | }, 98 | "hip~":{ 99 | input_slots:2, 100 | parameter_indices:[1], 101 | js_name:"pd_hip" 102 | }, 103 | "lop~":{ 104 | input_slots:2, 105 | parameter_indices:[1], 106 | js_name:"pd_lop" 107 | }, 108 | "noise~":{ 109 | input_slots:0, 110 | parameter_indices:[], 111 | js_name:"pd_noise" 112 | }, 113 | "clip~":{ 114 | input_slots:3, 115 | parameter_indices:[1,2], 116 | js_name:"pd_clip" 117 | }, 118 | "osc~":{ 119 | input_slots:1, 120 | parameter_indices:[0], 121 | js_name:"pd_osc" 122 | }, 123 | "bp~":{ 124 | input_slots:3, 125 | parameter_indices:[1,2], 126 | js_name:"pd_bp" 127 | }, 128 | "sqrt~":{ 129 | input_slots:1, 130 | parameter_indices:[0], 131 | js_name:"pd_sqrt" 132 | } 133 | 134 | } 135 | 136 | function build_function_tree(function_calls, connections, cur_node_idx){ 137 | var function_call = function_calls[cur_node_idx]; 138 | var node_name = function_call[0]; 139 | var constructor_arguments = function_call.slice(1); 140 | if (!function_info.hasOwnProperty(node_name)){ 141 | console.error("Unknown function: " + node_name); 142 | } 143 | var fn_info = function_info[node_name]; 144 | var node_input_slots = fn_info.input_slots; 145 | 146 | // Fix: Create unique arrays for each slot instead of references to the same array 147 | var input_connection_array = Array(node_input_slots).fill().map(() => []); 148 | 149 | //all functions that connect to the current node 150 | var input_connections = connections.filter(connection => connection.to_ob === cur_node_idx); 151 | for (let i = 0; i < input_connections.length; i++) { 152 | var connection = input_connections[i]; 153 | // this would be more complicated if any of our functions had multiple outputs, 154 | // but in practice this, happily, never happens! 155 | var connection_data = build_function_tree(function_calls, connections, connection.from_ob); 156 | input_connection_array[connection.to_slot].push(connection_data); 157 | } 158 | var call_data = { 159 | node_name: node_name, 160 | inputs: input_connection_array, 161 | constructor_arguments: constructor_arguments, 162 | node_id: cur_node_idx 163 | } 164 | return call_data; 165 | } 166 | 167 | 168 | function assign_variable_names(syntax_tree_branch,idx,name_dictionary){ 169 | if (name_dictionary.hasOwnProperty(syntax_tree_branch.node_id)){ 170 | if (name_dictionary[syntax_tree_branch.node_id] b.node_idx - a.node_idx); 247 | // console.log(flattened_tree); 248 | 249 | var function_body = ""; 250 | for (let i = 0; i < flattened_tree.length; i++) { 251 | var node = flattened_tree[i]; 252 | //inputs is a array of arrays - we need to have the slots like just s_1 if it's one input, or summing if it's multiple, like 253 | // pd_polyadd(s_1,s_2,s_3) 254 | var args = ""; 255 | for (let j = 0; j < node.inputs.length; j++) { 256 | if (j>0){ 257 | args += ","; 258 | } 259 | var slot_input = node.inputs[j]; 260 | if (slot_input.length === 0){ 261 | if (node.constructor_arguments[j]===-1){ 262 | args += "NULL"; 263 | } else { 264 | args += `pd_c(${node.constructor_arguments[j]})`; 265 | } 266 | } else if (slot_input.length === 1){ 267 | args += `s_${slot_input[0]}`; 268 | } else { 269 | args += `pd_polyadd(${slot_input.map(input => `s_${input}`).join(",")})`; 270 | } 271 | } 272 | 273 | // Fix: Convert PureData function names to valid JavaScript function names 274 | var js_funcname = node.node_name; 275 | if (js_funcname==="inlet~"){ 276 | function_body += `const s_${node.node_idx} = envelope_signal;\n`; 277 | } else if (js_funcname==="outlet~"){ 278 | function_body += `const s_${node.node_idx} = ${args};\n`; 279 | } else { 280 | if (function_info.hasOwnProperty(node.node_name)){ 281 | js_funcname = function_info[node.node_name].js_name; 282 | } else { 283 | console.error("Unknown function: " + node.node_name); 284 | } 285 | function_body += `const s_${node.node_idx} = ${js_funcname}(${args});\n`; 286 | } 287 | } 288 | function_body += `return s_0;\n`; 289 | return function_body; 290 | } 291 | 292 | for (let i = 0; i < puredata_function_names.length; i++) { 293 | var function_name = puredata_function_names[i]; 294 | var pd_source = puredata_modules[function_name]; 295 | // console.log("loading "+function_name); 296 | var pd_compiled = pd_compile(pd_source); 297 | puredata_functions[function_name] = pd_compiled; 298 | } 299 | -------------------------------------------------------------------------------- /js/audio/riffwave.js: -------------------------------------------------------------------------------- 1 | /* 2 | * RIFFWAVE.js v0.02 - Audio encoder for HTML5