├── .github ├── FUNDING.yaml └── workflows │ └── ci.yml ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── js ├── LICENSE ├── README.md ├── assets │ ├── 2json.js │ ├── 808bd.wav │ ├── 808ch.wav │ ├── 808clap.wav │ ├── 808ht.wav │ ├── 808lt.wav │ ├── 808oh.wav │ ├── 808sd.wav │ ├── bass3.wav │ ├── bin.wav │ ├── blip.wav │ ├── boip.wav │ ├── bong.wav │ ├── bpblart.wav │ ├── can.wav │ ├── casiohigh.wav │ ├── casiolow.wav │ ├── casionoise.wav │ ├── cb.wav │ ├── closedhh.wav │ ├── clubkick.wav │ ├── crash.wav │ ├── echo.wav │ ├── fat808sub.wav │ ├── giveit.wav │ ├── glass.wav │ ├── guitar.wav │ ├── hit1.wav │ ├── hit2.wav │ ├── hit3.wav │ ├── industrial.wav │ ├── junglesine.wav │ ├── kick1.wav │ ├── kick2.wav │ ├── latibro.wav │ ├── moog.wav │ ├── openhh.wav │ ├── pad.wav │ ├── perc1.wav │ ├── perc2.wav │ ├── pluck.wav │ ├── ride.wav │ ├── rm.wav │ ├── sax.wav │ ├── sid.wav │ ├── snare1.wav │ ├── snare2.wav │ ├── stab.wav │ ├── talk1.wav │ ├── talk2.wav │ ├── tink.wav │ ├── tok.wav │ ├── ufo.wav │ ├── whoosh.wav │ └── wind.wav ├── index.html ├── main.js ├── neo.css ├── npm │ ├── README.md │ ├── detect.js │ ├── glicol-engine.js │ ├── glicol_wasm.js │ ├── glicol_wasm_bg.wasm │ ├── index.js │ ├── nodechain.js │ ├── nosab.js │ ├── package-lock.json │ ├── package.json │ └── ringbuf.js ├── package-lock.json ├── package.json ├── pnpm-lock.yaml ├── src │ ├── glicol-api.json │ ├── glicol-engine.js │ ├── glicol-mode.js │ ├── glicol.js │ ├── glicol_wasm.js │ ├── glicol_wasm_bg.wasm │ ├── sample-list.json │ └── utils.js ├── style.css └── vite.config.js └── rs ├── Cargo.toml ├── LICENSE ├── README.md ├── main ├── Cargo.toml ├── LICENSE ├── README.md ├── examples │ ├── expr.rs │ ├── hello.rs │ ├── input.rs │ ├── msg_synth.rs │ ├── p_synth.rs │ ├── plot.rs │ ├── plpf.rs │ ├── raw.rs │ └── update.rs ├── src │ ├── error.rs │ ├── lib.rs │ └── util.rs └── tests │ └── sin.rs ├── parser ├── Cargo.toml ├── LICENSE ├── examples │ ├── choose.rs │ ├── hello.rs │ ├── lcs.rs │ ├── pattern.rs │ ├── psampler.rs │ ├── seq.rs │ └── test.rs ├── src │ ├── glicol.pest │ ├── lib.rs │ ├── nodes.rs │ └── util.rs └── tests │ ├── all_nodes.rs │ └── sin.rs ├── synth ├── Cargo.toml ├── LICENSE ├── README.md ├── benches │ ├── fm.rs │ └── next_block.rs ├── examples │ ├── chain.rs │ ├── connect.rs │ ├── freeverb.rs │ ├── gnode.rs │ ├── hello.rs │ ├── plot-am.rs │ ├── plot-g.rs │ ├── plot-imp.rs │ ├── plot.rs │ ├── plot2.rs │ ├── plot3.rs │ ├── plot_psynth.rs │ └── sin.rs └── src │ ├── buffer.rs │ ├── context.rs │ ├── graph.rs │ ├── lib.rs │ └── node │ ├── boxed.rs │ ├── compound │ ├── bd.rs │ ├── hh.rs │ ├── mod.rs │ ├── sawsynth.rs │ ├── sn.rs │ ├── squsynth.rs │ └── trisynth.rs │ ├── delay │ ├── delayms.rs │ ├── delayn.rs │ └── mod.rs │ ├── dynamic │ ├── eval.rs │ ├── expr.rs │ ├── meta.rs │ └── mod.rs │ ├── effect │ ├── balance.rs │ ├── mod.rs │ ├── pan.rs │ ├── plate.rs │ └── reverb.rs │ ├── envelope │ ├── adsr.rs │ ├── envperc.rs │ └── mod.rs │ ├── filter │ ├── apfmsgain.rs │ ├── mod.rs │ ├── onepole.rs │ ├── rhpf.rs │ └── rlpf.rs │ ├── mod.rs │ ├── operator │ ├── add.rs │ ├── mod.rs │ └── mul.rs │ ├── oscillator │ ├── mod.rs │ ├── saw_osc.rs │ ├── sin_osc.rs │ ├── squ_osc.rs │ └── tri_osc.rs │ ├── pass.rs │ ├── sampling │ ├── mod.rs │ ├── psampler.rs │ └── sampler.rs │ ├── sequencer │ ├── arrange.rs │ ├── choose.rs │ ├── mod.rs │ ├── seq.rs │ └── speed.rs │ ├── signal │ ├── constsig.rs │ ├── imp.rs │ ├── mod.rs │ ├── noise.rs │ ├── phasor.rs │ └── points.rs │ ├── sum.rs │ └── synth │ ├── mod.rs │ ├── msgsynth.rs │ └── pattern_synth.rs └── wasm ├── Cargo.toml ├── LICENSE ├── build.bat ├── build.sh └── src └── lib.rs /.github/FUNDING.yaml: -------------------------------------------------------------------------------- 1 | github: [chaosprint] -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | clippy-test: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | # - name: Update apt 17 | # run: sudo apt update 18 | # - name: Install alsa 19 | # run: sudo apt-get install libasound2-dev 20 | # - name: Install libjack 21 | # run: sudo apt-get install libjack-jackd2-dev libjack-jackd2-0 22 | - name: Install stable 23 | uses: dtolnay/rust-toolchain@stable 24 | with: 25 | components: clippy 26 | - name: Run clippy 27 | working-directory: rs 28 | run: cargo clippy --workspace --exclude glicol-wasm 29 | # --exclude glicol-wasm 30 | rustfmt-check: 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v4 34 | - name: Install stable 35 | uses: dtolnay/rust-toolchain@stable 36 | with: 37 | components: rustfmt 38 | - name: Run rustfmt 39 | working-directory: rs 40 | run: cargo fmt --all -- --check 41 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.linkedProjects": ["rs/Cargo.toml"] 3 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020-present Qichao Lan (chaopsrint) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /js/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020-present Qichao Lan (chaopsrint) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /js/README.md: -------------------------------------------------------------------------------- 1 | ## What's this? 2 | 3 | This folder contains the JavaScript bindings for [Glicol](https://glicol.org) language and audio engine. 4 | 5 | So, you can now use Glicol as the audio engien for your own browser-based music app. 6 | 7 | There are two usages: `NPM` or `CDN`. 8 | 9 | > Note that you need to have `cross-origin isolation` enabled on the web server to use Glicol. For vite dev server, you can use my plugin [here](https://github.com/chaosprint/vite-plugin-cross-origin-isolation). For deployment on Netlify or Firebase, check their docs for editing the header files. If you use a customised server, you have to figure it out yourself. 10 | 11 | ## Usage - NPM 12 | 13 | See the [https://glicol.js.org/](https://glicol.js.org/) for detailed introduction. 14 | 15 | ## Usage - CDN 16 | 17 | This mode exposes all the methods such as `run` or `stop` to the `window` Object. 18 | 19 | Just include the following line into your `index.html`: 20 | 21 | ``` 22 | 23 | ``` 24 | 25 | The `run()` function is bind to the window. 26 | 27 | You can map it to buttons on the page or even do live coding in the browser console. 28 | 29 | Call it for the first time will run the code: 30 | ```run(`hello: sin 440`)``` 31 | 32 | Glicol engine knows you are updating the code if you call the func again. 33 | 34 | Call `stop()` function will restart the engine. 35 | 36 | To run the demo in this folder: 37 | ``` 38 | npm i 39 | npm run dev 40 | ``` 41 | 42 | ## License 43 | 44 | The MIT License (MIT) 45 | 46 | Copyright (c) 2020 - present Qichao Lan (chaosprint) 47 | -------------------------------------------------------------------------------- /js/assets/2json.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const folder = './'; 3 | var info = Object(); 4 | var source = "https://github.com/chaosprint/Dirt-Samples" 5 | fs.readdir(folder, (_err, files) => { 6 | // info["selectedFromDirtSamples"] = files.filter(x=>x!=='.DS_Store') 7 | files.forEach(file => { 8 | if (file !== '.DS_Store' && file !== "2json.js") { 9 | info[file.replace(".wav", "")] = source 10 | } 11 | }); 12 | let json = JSON.stringify(info) 13 | var outputFilename = '../src/sample-list.json'; 14 | fs.writeFile(outputFilename, json, function(err) { 15 | if(err) { 16 | console.log(err); 17 | } else { 18 | console.log("JSON saved to " + outputFilename); 19 | } 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /js/assets/808bd.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/808bd.wav -------------------------------------------------------------------------------- /js/assets/808ch.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/808ch.wav -------------------------------------------------------------------------------- /js/assets/808clap.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/808clap.wav -------------------------------------------------------------------------------- /js/assets/808ht.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/808ht.wav -------------------------------------------------------------------------------- /js/assets/808lt.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/808lt.wav -------------------------------------------------------------------------------- /js/assets/808oh.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/808oh.wav -------------------------------------------------------------------------------- /js/assets/808sd.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/808sd.wav -------------------------------------------------------------------------------- /js/assets/bass3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/bass3.wav -------------------------------------------------------------------------------- /js/assets/bin.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/bin.wav -------------------------------------------------------------------------------- /js/assets/blip.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/blip.wav -------------------------------------------------------------------------------- /js/assets/boip.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/boip.wav -------------------------------------------------------------------------------- /js/assets/bong.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/bong.wav -------------------------------------------------------------------------------- /js/assets/bpblart.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/bpblart.wav -------------------------------------------------------------------------------- /js/assets/can.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/can.wav -------------------------------------------------------------------------------- /js/assets/casiohigh.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/casiohigh.wav -------------------------------------------------------------------------------- /js/assets/casiolow.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/casiolow.wav -------------------------------------------------------------------------------- /js/assets/casionoise.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/casionoise.wav -------------------------------------------------------------------------------- /js/assets/cb.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/cb.wav -------------------------------------------------------------------------------- /js/assets/closedhh.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/closedhh.wav -------------------------------------------------------------------------------- /js/assets/clubkick.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/clubkick.wav -------------------------------------------------------------------------------- /js/assets/crash.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/crash.wav -------------------------------------------------------------------------------- /js/assets/echo.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/echo.wav -------------------------------------------------------------------------------- /js/assets/fat808sub.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/fat808sub.wav -------------------------------------------------------------------------------- /js/assets/giveit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/giveit.wav -------------------------------------------------------------------------------- /js/assets/glass.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/glass.wav -------------------------------------------------------------------------------- /js/assets/guitar.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/guitar.wav -------------------------------------------------------------------------------- /js/assets/hit1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/hit1.wav -------------------------------------------------------------------------------- /js/assets/hit2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/hit2.wav -------------------------------------------------------------------------------- /js/assets/hit3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/hit3.wav -------------------------------------------------------------------------------- /js/assets/industrial.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/industrial.wav -------------------------------------------------------------------------------- /js/assets/junglesine.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/junglesine.wav -------------------------------------------------------------------------------- /js/assets/kick1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/kick1.wav -------------------------------------------------------------------------------- /js/assets/kick2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/kick2.wav -------------------------------------------------------------------------------- /js/assets/latibro.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/latibro.wav -------------------------------------------------------------------------------- /js/assets/moog.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/moog.wav -------------------------------------------------------------------------------- /js/assets/openhh.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/openhh.wav -------------------------------------------------------------------------------- /js/assets/pad.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/pad.wav -------------------------------------------------------------------------------- /js/assets/perc1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/perc1.wav -------------------------------------------------------------------------------- /js/assets/perc2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/perc2.wav -------------------------------------------------------------------------------- /js/assets/pluck.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/pluck.wav -------------------------------------------------------------------------------- /js/assets/ride.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/ride.wav -------------------------------------------------------------------------------- /js/assets/rm.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/rm.wav -------------------------------------------------------------------------------- /js/assets/sax.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/sax.wav -------------------------------------------------------------------------------- /js/assets/sid.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/sid.wav -------------------------------------------------------------------------------- /js/assets/snare1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/snare1.wav -------------------------------------------------------------------------------- /js/assets/snare2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/snare2.wav -------------------------------------------------------------------------------- /js/assets/stab.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/stab.wav -------------------------------------------------------------------------------- /js/assets/talk1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/talk1.wav -------------------------------------------------------------------------------- /js/assets/talk2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/talk2.wav -------------------------------------------------------------------------------- /js/assets/tink.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/tink.wav -------------------------------------------------------------------------------- /js/assets/tok.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/tok.wav -------------------------------------------------------------------------------- /js/assets/ufo.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/ufo.wav -------------------------------------------------------------------------------- /js/assets/whoosh.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/whoosh.wav -------------------------------------------------------------------------------- /js/assets/wind.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/assets/wind.wav -------------------------------------------------------------------------------- /js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | glicol.js minimal demo 7 | 8 | 9 |
10 |
11 |
12 | 13 |
14 |

15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | import mode from './src/glicol-mode' 2 | import './neo.css' 3 | import './style.css' 4 | let myTextarea = document.getElementById("code"); 5 | CodeMirror.defineSimpleMode("simplemode", mode); 6 | window.editor = CodeMirror.fromTextArea(myTextarea, { 7 | lineNumbers: true, 8 | theme: "neo", 9 | extraKeys: { 10 | "Ctrl-Enter": function(cm) { 11 | window.run(cm.getValue()) 12 | }, 13 | "Ctrl-Shift-Enter": function(cm) { 14 | window.run(cm.getValue()) 15 | cm.setValue(window.code) 16 | }, 17 | "Cmd-Enter": function(cm) { 18 | window.run(cm.getValue()) 19 | }, 20 | "Cmd-Shift-Enter": function(cm) { 21 | window.run(cm.getValue()) 22 | cm.setValue(window.code) 23 | }, 24 | "Alt-D": function(editor) { 25 | let A1 = editor.getCursor().line; 26 | let A2 = editor.getCursor().ch; 27 | let B1 = editor.findWordAt({line: A1, ch: A2}).anchor.ch; 28 | let B2 = editor.findWordAt({line: A1, ch: A2}).head.ch; 29 | window.help(editor.getRange({line: A1,ch: B1}, {line: A1,ch: B2})) 30 | }, 31 | "Ctrl-Alt-.": function() { 32 | window.stop() 33 | }, 34 | "Cmd-Alt-.": function() { 35 | window.stop() 36 | }, 37 | 'Ctrl-/': cm => {cm.execCommand('toggleComment')}, 38 | 'Cmd-/': cm => {cm.execCommand('toggleComment')} 39 | } 40 | 41 | }); 42 | editor.setValue(window.code) 43 | document.getElementById("run").addEventListener("click", ()=>{ 44 | // let currentCode = document.getElementById("code").value; 45 | window.run(window.editor.getValue()) 46 | }) 47 | document.getElementById("stop").addEventListener("click", ()=>{ 48 | window.stop(); // stop function binding to window 49 | }) 50 | -------------------------------------------------------------------------------- /js/neo.css: -------------------------------------------------------------------------------- 1 | /* neo theme for codemirror */ 2 | 3 | /* Color scheme */ 4 | 5 | .cm-s-neo.CodeMirror { 6 | background-color:#ffffff; 7 | color:#2e383c; 8 | line-height:1.4375; 9 | } 10 | .cm-s-neo .cm-comment { color:#9b9999; } 11 | .cm-s-neo .cm-error { color:#505050; } 12 | .cm-s-neo .cm-keyword, .cm-s-neo .cm-property { color:#7176a0; } 13 | .cm-s-neo .cm-atom,.cm-s-neo .cm-number { color:#75438a; } 14 | .cm-s-neo .cm-node,.cm-s-neo .cm-tag { color:#9c3328; } 15 | .cm-s-neo .cm-string { color:#c24088; } 16 | .cm-s-neo .cm-variable,.cm-s-neo .cm-qualifier { color:#3b7074; } 17 | .cm-s-neo .cm-variable-2,.cm-s-neo .cm-qualifier { color:#166aaf; } 18 | 19 | /* Editor styling */ 20 | 21 | .cm-s-neo pre { 22 | padding:0; 23 | } 24 | 25 | .cm-s-neo .CodeMirror-gutters { 26 | border:none; 27 | border-right:10px solid transparent; 28 | background-color:transparent; 29 | } 30 | 31 | .cm-s-neo .CodeMirror-linenumber { 32 | padding:0; 33 | color:#e0e2e5; 34 | } 35 | 36 | .cm-s-neo .CodeMirror-guttermarker { color: #1d75b3; } 37 | .cm-s-neo .CodeMirror-guttermarker-subtle { color: #e0e2e5; } 38 | 39 | .cm-s-neo .CodeMirror-cursor { 40 | width: auto; 41 | border: 0; 42 | background: rgba(155,157,162,0.37); 43 | z-index: 1; 44 | } -------------------------------------------------------------------------------- /js/npm/README.md: -------------------------------------------------------------------------------- 1 | ## What's this? 2 | 3 | See: https://glicol.js.org 4 | 5 | ## Feedback 6 | 7 | There are many todos for this package. Please let me know your thoughts and suggestions here: 8 | 9 | https://github.com/chaosprint/glicol 10 | 11 | `Issues` or `Discussion` are both fine. 12 | 13 | ## Dev note (not for users) 14 | ``` 15 | sudo pnpm link --dir /usr/local/lib/ glicol 16 | ``` -------------------------------------------------------------------------------- /js/npm/detect.js: -------------------------------------------------------------------------------- 1 | export const detectOs = () => { 2 | var userAgent = window.navigator.userAgent, 3 | platform = window.navigator.platform, 4 | macosPlatforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'], 5 | windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE'], 6 | iosPlatforms = ['iPhone', 'iPad', 'iPod'], 7 | os = null; 8 | if (macosPlatforms.indexOf(platform) !== -1) { 9 | os = 'Mac OS'; 10 | } else if (iosPlatforms.indexOf(platform) !== -1) { 11 | os = 'iOS'; 12 | } else if (windowsPlatforms.indexOf(platform) !== -1) { 13 | os = 'Windows'; 14 | } else if (/Android/.test(userAgent)) { 15 | os = 'Android'; 16 | } else if (!os && /Linux/.test(platform)) { 17 | os = 'Linux'; 18 | } 19 | return os; 20 | } 21 | 22 | export const detectBrowser = () => { 23 | const { userAgent } = navigator 24 | // alert(userAgent) 25 | // alert(detectOs()); 26 | let name = ""; 27 | let version = "0.0"; 28 | if (userAgent.includes('Firefox/')) { 29 | // Firefox 30 | name = detectOs() === "Android" ? "Firefox for Android": "Firefox" 31 | version = userAgent.split("Firefox/")[1] 32 | // } else if (userAgent.includes('Edg/')) { 33 | // name = "Edge" 34 | } else if (userAgent.includes('Chrome/')) { 35 | name = detectOs() === "Android" ? "Chrome for Android": "Chrome" 36 | version = userAgent.split("Chrome/")[1].split(" ")[0].split(".")[0] 37 | } else if (userAgent.includes('Safari/') && userAgent.includes('Version/') ) { 38 | name = detectOs() === "iOS" ? "Safari on iOS": "Safari" 39 | version = userAgent.split("Version/")[1].split(" ")[0] 40 | } 41 | return { 42 | name: name, 43 | version: parseFloat(version) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /js/npm/glicol_wasm_bg.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/npm/glicol_wasm_bg.wasm -------------------------------------------------------------------------------- /js/npm/nodechain.js: -------------------------------------------------------------------------------- 1 | const isRef = s => String(s).includes('~') 2 | 3 | export function sin(freq) { 4 | if (!isNaN(freq) || isRef(freq)) { 5 | return new NodeChain(`sin ${freq}`) 6 | } 7 | } 8 | 9 | export function saw(freq) { 10 | if (!isNaN(freq) || isRef(freq)) { 11 | return new NodeChain(`saw ${freq}`) 12 | } 13 | } 14 | export function tri(freq) { 15 | if (!isNaN(freq) || isRef(freq)) { 16 | return new NodeChain(`tri ${freq}`) 17 | } 18 | } 19 | 20 | export function squ(freq) { 21 | if (!isNaN(freq) || isRef(freq)) { 22 | return new NodeChain(`squ ${freq}`) 23 | } 24 | } 25 | 26 | export function imp(freq) { 27 | if (!isNaN(freq)) { 28 | return new NodeChain(`imp ${freq}`) 29 | } 30 | } 31 | 32 | export function noise(seed) { 33 | if (!isNaN(seed)) { 34 | return new NodeChain(`noise ${seed}`) 35 | } 36 | } 37 | 38 | export function speed(val) { 39 | if (!isNaN(val)) { 40 | return new NodeChain(`speed ${val}`) 41 | } 42 | } 43 | 44 | export function seq(str) { 45 | // if (!isNaN(seed)) { 46 | return new NodeChain(`seq ${str}`) 47 | // } 48 | } 49 | 50 | export function psynth(str, span) { 51 | if (!isNaN(span)) { 52 | return new NodeChain(`p_synth \`${str} ${span}`) 53 | } 54 | } 55 | 56 | export function psampler(str) { 57 | return new NodeChain(`psampler ${str}`) 58 | } 59 | 60 | export function sig(param) { 61 | return new NodeChain(`constsig ${param}`) 62 | } 63 | 64 | export function mix(str) { 65 | // var result; 66 | // if (typeof str === "Array") { 67 | // result = str.join(" ") 68 | // } else if (typeof str === "String") { 69 | // result = str 70 | // } 71 | return new NodeChain(`mix ${str}`) 72 | } 73 | 74 | export class NodeChain { 75 | constructor(code) { 76 | this.code = code 77 | } 78 | 79 | toString() { 80 | return `${this.code}`; 81 | }; 82 | 83 | mul(val) { 84 | if (!isNaN(val) || isRef(val)) { 85 | this.code += ` >> mul ${val}` 86 | } 87 | return this 88 | } 89 | add(val) { 90 | if (!isNaN(val) || isRef(val)) { 91 | this.code += ` >> add ${val}` 92 | } 93 | return this 94 | } 95 | 96 | delayms(val) { 97 | if (!isNaN(val) || isRef(val)) { 98 | this.code += ` >> delayms ${val}` 99 | } 100 | return this 101 | } 102 | 103 | delayn(val) { 104 | if (!isNaN(val)) { 105 | this.code += ` >> delayn ${parseInt(val)}` 106 | } 107 | return this 108 | } 109 | 110 | lpf(cutoff, qvalue) { 111 | // if ( (!isNaN(cutoff) || isRef(cutoff)) && (!isNaN(qvalue))) { 112 | this.code += ` >> lpf ${cutoff} ${qvalue}` 113 | // } 114 | return this 115 | } 116 | 117 | hpf(cutoff, qvalue) { 118 | if ( (!isNaN(cutoff) || isRef(cutoff)) && (!isNaN(qvalue))) { 119 | this.code += ` >> hpf ${cutoff} ${qvalue}` 120 | } 121 | return this 122 | } 123 | 124 | plate(val) { 125 | if (!isNaN(val)) { 126 | this.code += ` >> plate ${val}` 127 | } 128 | return this 129 | } 130 | 131 | bd(val) { 132 | if (!isNaN(val)) { 133 | this.code += ` >> bd ${val}` 134 | } 135 | return this 136 | } 137 | 138 | sn(val) { 139 | if (!isNaN(val)) { 140 | this.code += ` >> sn ${val}` 141 | } 142 | return this 143 | } 144 | 145 | hh(val) { 146 | if (!isNaN(val)) { 147 | this.code += ` >> hh ${val}` 148 | } 149 | return this 150 | } 151 | 152 | sawsynth(att, dec) { 153 | if (!isNaN(att) && !isNaN(dec)) { 154 | this.code += ` >> sawsynth ${att} ${dec}` 155 | } 156 | return this 157 | } 158 | 159 | squsynth(att, dec) { 160 | if (!isNaN(att) && !isNaN(dec)) { 161 | this.code += ` >> squsynth ${att} ${dec}` 162 | } 163 | return this 164 | } 165 | 166 | trisynth(att, dec) { 167 | if (!isNaN(att) && !isNaN(dec)) { 168 | this.code += ` >> trisynth ${att} ${dec}` 169 | } 170 | return this 171 | } 172 | 173 | seq(str) { 174 | // if (!isNaN(str)) { 175 | this.code += ` >> seq ${str}` 176 | // } 177 | return this 178 | } 179 | 180 | adsr(a, d, s, r) { 181 | // if (!isNaN(str)) { 182 | this.code += ` >> adsr ${a} ${d} ${s} ${r}` 183 | // } 184 | return this 185 | } 186 | 187 | sp(sampleName) { 188 | // if (!isNaN(str)) { 189 | this.code += ` >> sp \\${sampleName}` 190 | // } 191 | return this 192 | } 193 | 194 | envperc(attack, decay) { 195 | // if (!isNaN(str)) { 196 | this.code += ` >> envperc ${attack} ${decay}` 197 | // } 198 | return this 199 | } 200 | 201 | } -------------------------------------------------------------------------------- /js/npm/nosab.js: -------------------------------------------------------------------------------- 1 | export default `To get the best audio performance for browsers, we need SharedArrayBuffer (SAB). However, it may not be supported in current browser (see https://caniuse.com/?search=sharedarraybuffer). Also, SharedArrayBuffer requires 'cross-origin isolation', either on the dev or deployment server (see https://web.dev/coop-coep/).` -------------------------------------------------------------------------------- /js/npm/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "glicol", 3 | "version": "0.2.11", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "glicol", 9 | "version": "0.2.11", 10 | "license": "MIT" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /js/npm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "glicol", 3 | "version": "0.4.0", 4 | "description": "A light-weight, audio-thread-GC-free, memory-safe, and easy-to-use audio engine for browser-based music apps.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/chaosprint/glicol.git" 12 | }, 13 | "keywords": [ 14 | "audio", 15 | "music", 16 | "live", 17 | "coding", 18 | "synth", 19 | "sound", 20 | "tone", 21 | "glicol" 22 | ], 23 | "author": "chaosprint (Qichao Lan)", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/chaosprint/glicol/issues" 27 | }, 28 | "homepage": "https://glicol.js.org" 29 | } -------------------------------------------------------------------------------- /js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "glicol", 3 | "version": "0.0.0", 4 | "description": "Glicol as an independent JavaScript library.", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vite build", 8 | "serve": "vite preview" 9 | }, 10 | "devDependencies": { 11 | "vite": "^2.3.0" 12 | }, 13 | "dependencies": { 14 | "install": "^0.13.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /js/src/glicol-engine.js: -------------------------------------------------------------------------------- 1 | import { initSync, set_sr, set_seed, add_sample, set_bpm, set_track_amp, update, process } from "./glicol_wasm.js" 2 | 3 | class GlicolEngine extends AudioWorkletProcessor { 4 | static get parameterDescriptors() { 5 | return [] 6 | } 7 | constructor(options) { 8 | super(options) 9 | const { wasmBlob } = options.processorOptions; 10 | initSync(wasmBlob); 11 | this.port.onmessage = async e => { 12 | if (e.data.type === "load") { 13 | set_sr(sampleRate) 14 | set_seed(Math.random() * 4096) 15 | this.port.postMessage({type: 'ready'}) 16 | } else if (e.data.type === "loadsample") { 17 | // console.log("data: ", e.data) 18 | let channels = e.data.channels; 19 | let sr = e.data.sr; 20 | let name = e.data.name 21 | 22 | add_sample(name, e.data.sample, channels, sr) 23 | 24 | // recall this to ensure 25 | } else if (e.data.type === "run") { 26 | this.update(e.data.value) 27 | } else if (e.data.type === "bpm") { 28 | set_bpm(e.data.value); 29 | } else if (e.data.type === "amp") { 30 | set_track_amp(e.data.value); 31 | // } else if (e.data.type === "sab") { 32 | 33 | } else { 34 | throw `Unexpected data type ${e.data.type}`; 35 | } 36 | } 37 | } 38 | update(code) { 39 | let result = update(code) 40 | if (result[0] !== 0) { 41 | this.port.postMessage({type: 'e', info: result}) 42 | } 43 | } 44 | process(_, outputs, _parameters) { 45 | // if (midiSize) { 46 | // let codeUint8ArrayPtr = this._wasm.exports.alloc_uint8array(size); 47 | // let codeUint8Array = new Uint8Array(this._wasm.exports.memory.buffer, codeUint8ArrayPtr, size); 48 | // codeUint8Array.set(this._codeArray.slice(0, size)); 49 | 50 | let outBuf = process(256) 51 | 52 | outputs[0][0].set(outBuf.slice(0, outBuf.length / 2)) 53 | outputs[0][1].set(outBuf.slice(outBuf.length / 2, outBuf.length)) 54 | return true 55 | } 56 | } 57 | 58 | registerProcessor('glicol-engine', GlicolEngine) 59 | -------------------------------------------------------------------------------- /js/src/glicol-mode.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // The start state contains the rules that are initially used 3 | start: [ 4 | {regex: /(delayms|true|false|let|const|else|switch|do|loop|until|continue|break|fn|this|return|throw|try|catch|import|export|as|global|print|debug|eval|map|for|while|if|sin|exp|in|expr|seq|squsynth|sawsynth|trisynth|bd|hh|sn|speed|choose|mul|add|linrange|apfdecay|delayn|sin|saw|squ|imp|envperc|sampler|noiz|shape|tri|noise|noiz|rlpf|plate|onepole|rhpf|pha|buf|state|freeverb|pan|delay|apfgain|lpf|hpf|comb|mix|monosum|const_sig|sp|spd|amplfo|balance|meta|script|pad|in)(?![a-z])/, 5 | token: "string"}, 6 | // The regex matches the token, the token property contains the type 7 | // {regex: /~([a-z]+(_)?)+/, token: "variable-3"}, 8 | // You can match multiple tokens at once. Note that the captured 9 | // groups must span the whole string in this case 10 | // {regex: /##([\S\n\t\v ]+?)#/, token: "error"}, 11 | {regex: /[-+]?([0-9]{1,}[.][0-9]+)/, token: "variable"}, 12 | {regex: /PI/, token: "variable"}, 13 | // {regex: /\\(\S)*/, token: "number"}, 14 | {regex: /([0-9]{1,3}|(~[a-z](?![a-z0-9\.]))|_)+/, token: "variable"}, 15 | // {regex: /(~)([a-z])(?!([a-z]))/, token: "variable"}, 16 | {regex: /^~[a-z][0-9a-z\_\.]+/, token: "variable-2"}, 17 | 18 | 19 | {regex: /\/\/.*/, token: "comment"}, 20 | // {regex: /`[\s\S\n\t]+`/, token: "meta"}, 21 | {regex: /\}|\{|\;|\`|\-|\/|:|>>|,|\*|\+|\=|\||\(|\)/, token: "error"}, 22 | {regex: /^\\[0-9a-z\_]+/, token: "variable"}, 23 | {regex: /^[a-z][0-9a-z\_]*/, token: "keyword"}, 24 | // A next property will cause the mode to move to a different state 25 | {regex: /\/\*/, token: "comment", next: "comment"}, 26 | // {regex: /[-+\/*=<>!]+/, token: "operator"}, 27 | // indent and dedent properties guide autoindentation 28 | {regex: /[\{\[\(]/, indent: true}, 29 | {regex: /[\}\]\)]/, dedent: true}, 30 | {regex: /##/, token: "error", next: "error"}, //js is error 31 | // {regex: /`/, token: "meta", next: "meta"}, //js is error 32 | // {regex: /(?<=##)[^#]*(?=#)/, token: "meta"} 33 | ], 34 | // The multi-line comment state. 35 | comment: [ 36 | {regex: /.*?\*\//, token: "comment", next: "start"}, 37 | {regex: /.*/, token: "comment"} 38 | ], 39 | // meta: [ 40 | // {regex: /.*?`/, token: "meta", next: "start"}, 41 | // {regex: /.*/, token: "meta"} 42 | // ], 43 | error: [ 44 | {regex: /.*?#/, token: "error", next: "start"}, 45 | {regex: /.*/, token: "error"} 46 | ], 47 | // The meta property contains global information about the mode. It 48 | // can contain properties like lineComment, which are supported by 49 | // all modes, and also directives like dontIndentStates, which are 50 | // specific to simple modes. 51 | meta: { 52 | dontIndentStates: ["comment"], 53 | lineComment: "//" 54 | } 55 | }; -------------------------------------------------------------------------------- /js/src/glicol_wasm_bg.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/js/src/glicol_wasm_bg.wasm -------------------------------------------------------------------------------- /js/src/sample-list.json: -------------------------------------------------------------------------------- 1 | {"808bd":"https://github.com/chaosprint/Dirt-Samples","808ch":"https://github.com/chaosprint/Dirt-Samples","808clap":"https://github.com/chaosprint/Dirt-Samples","808ht":"https://github.com/chaosprint/Dirt-Samples","808lt":"https://github.com/chaosprint/Dirt-Samples","808oh":"https://github.com/chaosprint/Dirt-Samples","808sd":"https://github.com/chaosprint/Dirt-Samples","bass3":"https://github.com/chaosprint/Dirt-Samples","bin":"https://github.com/chaosprint/Dirt-Samples","blip":"https://github.com/chaosprint/Dirt-Samples","boip":"https://github.com/chaosprint/Dirt-Samples","bong":"https://github.com/chaosprint/Dirt-Samples","bpblart":"https://github.com/chaosprint/Dirt-Samples","can":"https://github.com/chaosprint/Dirt-Samples","casiohigh":"https://github.com/chaosprint/Dirt-Samples","casiolow":"https://github.com/chaosprint/Dirt-Samples","casionoise":"https://github.com/chaosprint/Dirt-Samples","cb":"https://github.com/chaosprint/Dirt-Samples","closedhh":"https://github.com/chaosprint/Dirt-Samples","clubkick":"https://github.com/chaosprint/Dirt-Samples","crash":"https://github.com/chaosprint/Dirt-Samples","echo":"https://github.com/chaosprint/Dirt-Samples","fat808sub":"https://github.com/chaosprint/Dirt-Samples","giveit":"https://github.com/chaosprint/Dirt-Samples","glass":"https://github.com/chaosprint/Dirt-Samples","guitar":"https://github.com/chaosprint/Dirt-Samples","hit1":"https://github.com/chaosprint/Dirt-Samples","hit2":"https://github.com/chaosprint/Dirt-Samples","hit3":"https://github.com/chaosprint/Dirt-Samples","industrial":"https://github.com/chaosprint/Dirt-Samples","junglesine":"https://github.com/chaosprint/Dirt-Samples","kick1":"https://github.com/chaosprint/Dirt-Samples","kick2":"https://github.com/chaosprint/Dirt-Samples","latibro":"https://github.com/chaosprint/Dirt-Samples","moog":"https://github.com/chaosprint/Dirt-Samples","openhh":"https://github.com/chaosprint/Dirt-Samples","pad":"https://github.com/chaosprint/Dirt-Samples","perc1":"https://github.com/chaosprint/Dirt-Samples","perc2":"https://github.com/chaosprint/Dirt-Samples","pluck":"https://github.com/chaosprint/Dirt-Samples","ride":"https://github.com/chaosprint/Dirt-Samples","rm":"https://github.com/chaosprint/Dirt-Samples","sax":"https://github.com/chaosprint/Dirt-Samples","sid":"https://github.com/chaosprint/Dirt-Samples","snare1":"https://github.com/chaosprint/Dirt-Samples","snare2":"https://github.com/chaosprint/Dirt-Samples","stab":"https://github.com/chaosprint/Dirt-Samples","talk1":"https://github.com/chaosprint/Dirt-Samples","talk2":"https://github.com/chaosprint/Dirt-Samples","tink":"https://github.com/chaosprint/Dirt-Samples","tok":"https://github.com/chaosprint/Dirt-Samples","ufo":"https://github.com/chaosprint/Dirt-Samples","whoosh":"https://github.com/chaosprint/Dirt-Samples","wind":"https://github.com/chaosprint/Dirt-Samples"} -------------------------------------------------------------------------------- /js/style.css: -------------------------------------------------------------------------------- 1 | html, body, main { 2 | height:100%; 3 | width:100%; 4 | display: flex; 5 | justify-content: center; 6 | align-items: center; 7 | } 8 | * { 9 | font-size:large 10 | } -------------------------------------------------------------------------------- /js/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | 3 | export default defineConfig({ 4 | plugins: [ 5 | { 6 | name: "configure-response-headers", 7 | configureServer: (server) => { 8 | server.middlewares.use((_req, res, next) => { 9 | res.setHeader("Cross-Origin-Embedder-Policy", "require-corp"); 10 | res.setHeader("Cross-Origin-Opener-Policy", "same-origin"); 11 | next(); 12 | }); 13 | }, 14 | }, 15 | ], 16 | }); 17 | -------------------------------------------------------------------------------- /rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | default-members = ["main"] 3 | members = ["main", "parser", "synth", "wasm"] 4 | resolver = "1" 5 | 6 | [workspace.package] 7 | version = "0.14.0-dev" 8 | edition = "2021" 9 | repository = "https://github.com/chaosprint/glicol.git" 10 | authors = ["Qichao Lan "] 11 | 12 | [workspace.dependencies] 13 | petgraph = { version = "0.6", default-features = false, features = ["stable_graph"] } 14 | dasp_slice = { version = "0.11.0", default-features = false, features = [ 15 | "std", 16 | ] } 17 | 18 | dasp_ring_buffer = { version = "0.11.0", default-features = false } 19 | # default-features = false can't be used here; we need std 20 | dasp_signal = { version = "0.11.0" } 21 | dasp_interpolate = { version = "0.11.0", features = ["linear", "sinc"] } 22 | hashbrown = "0.14.3" 23 | rhai = { version = "1.12.0", default-features = false, features = [ 24 | "sync", 25 | "f32_float", 26 | "only_i32", 27 | ] } 28 | fasteval = "0.2.4" 29 | pest = "2.7.9" 30 | pest_derive = "2.7.9" 31 | yoke = { version = "0.7.3", default-features = false, features = ["derive", "alloc"] } 32 | 33 | [profile.wasm-release] 34 | inherits = "release" 35 | opt-level = 'z' # Optimize for size. 36 | lto = "fat" # Enable Link Time Optimization 37 | codegen-units = 1 # Reduce number of codegen units to increase optimizations. 38 | panic = 'abort' # Abort on panic 39 | -------------------------------------------------------------------------------- /rs/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020-present Qichao Lan (chaopsrint) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /rs/README.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | This folder contains files for the engine of Glicol written in Rust. 4 | 5 | The `main` provides an `Engine` that takes the code as input, stores samples, and yield `next_block` of audio constantly. 6 | 7 | The `parser` offers the `get_ast` function for the `Engine` to parse the code. 8 | 9 | The `wasm` crate exports the `Engine` to a WebAssembly file which can be used in web browsers. 10 | 11 | The `synth` provides all the audio support. 12 | 13 | ## Glicol synth as a standalone Rust audio library 14 | 15 | You can write an audio project like this: 16 | 17 | ``` 18 | use glicol_synth::{AudioContextBuilder, signal::ConstSig, Message}; 19 | 20 | fn main() { 21 | let mut context = AudioContextBuilder::<128>::new() 22 | .sr(44100).channels(1).build(); 23 | 24 | let node_a = context.add_mono_node(ConstSig::new(42.)); 25 | context.connect(node_a, context.destination); 26 | println!("first block {:?}", context.next_block()); 27 | 28 | context.send_msg(node_a, Message::SetToNumber((0, 100.)) ); 29 | println!("second block, after msg {:?}", context.next_block()); 30 | } 31 | ``` 32 | 33 | See [./synth](./synth) folder for more details. 34 | 35 | ## Try it out 36 | 37 | First you should have Rust compiler installed. Make sure you can call `cargo` in your terminal. 38 | 39 | Then: 40 | ``` 41 | git clone https://github.com/chaosprint/glicol.git 42 | cd glicol/rs/synth 43 | cargo run --example hello 44 | cargo run --example chain 45 | ``` 46 | 47 | You can explore more examples in the `./rs/synth/` folder. 48 | 49 | ## License 50 | 51 | The MIT License (MIT) 52 | 53 | Copyright (c) 2020 - present Qichao Lan (chaosprint) 54 | -------------------------------------------------------------------------------- /rs/main/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "glicol" 3 | version = { workspace = true } 4 | edition = { workspace = true } 5 | keywords = ["audio", "music", "DSP", "synth", "synthesizer"] 6 | 7 | license-file = "LICENSE" 8 | description = "Glicol language main entry point." 9 | repository = "https://github.com/chaosprint/glicol.git" 10 | authors = ["Qichao Lan "] 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [features] 15 | default = [] 16 | use-samples = [] 17 | use-meta = [] 18 | bela = [] 19 | wasm-bindgen = ["glicol_synth/wasm-bindgen"] 20 | 21 | [dependencies] 22 | petgraph = { workspace = true } 23 | glicol_parser = { path = "../parser", version = "0.14.0-dev" } 24 | glicol_synth = { path = "../synth", version = "0.14.0-dev", features = [ 25 | "use-samples", 26 | "use-meta", 27 | ] } 28 | pest = { workspace = true } 29 | hashbrown = { workspace = true } 30 | yoke = { workspace = true } 31 | 32 | [dev-dependencies] 33 | gnuplot = "0.0.43" 34 | # petgraph = { version = "0.6", features = ["stable_graph"] } 35 | # cpal = "0.15.3" 36 | anyhow = "1.0.63" 37 | -------------------------------------------------------------------------------- /rs/main/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020-present Qichao Lan (chaopsrint) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /rs/main/README.md: -------------------------------------------------------------------------------- 1 | ## glicol-rs 2 | 3 | You can use this crate to build audio apps with Glicol syntax. 4 | 5 | ```rust 6 | use glicol::Engine; 7 | fn main() { 8 | let mut engine = Engine::<32>::new(); 9 | engine.update_with_code(r#"o: sin 440"#); 10 | println!("next block {:?}", engine.next_block(vec![])); 11 | } 12 | ``` 13 | 14 | More examples [here](https://github.com/chaosprint/glicol/tree/main/rs/main/examples). 15 | 16 | Learn Glicol syntax [here](https://glicol.org). 17 | 18 | It compiles to WebAssembly and runs in browsers. 19 | 20 | It can also be used on VST and Bela, but these are all experimental. 21 | 22 | See the GitHub repository for details. -------------------------------------------------------------------------------- /rs/main/examples/expr.rs: -------------------------------------------------------------------------------- 1 | use glicol::Engine; 2 | // use glicol::{EngineError, get_error_info}; 3 | 4 | // use glicol::GlicolNodeInfo; 5 | // use std::collections::HashMap; 6 | 7 | fn main() { 8 | let mut engine = Engine::<32>::new(); 9 | engine 10 | .update_with_code(r#"o: eval `x:=x>1*(x-1.0)+x*x<=1;x`"#) 11 | .unwrap(); // y=math::sin(2*PI*x);x+=440.0/sr;y 12 | println!("next block {:?}", engine.next_block(vec![])); 13 | } 14 | -------------------------------------------------------------------------------- /rs/main/examples/hello.rs: -------------------------------------------------------------------------------- 1 | use glicol::Engine; 2 | fn main() { 3 | let mut engine = Engine::<8>::new(); 4 | engine 5 | .update_with_code(r#"o: constsig 42 >> pan 0.9"#) 6 | .unwrap(); 7 | println!("next block {:?}", engine.next_block(vec![])); 8 | } 9 | -------------------------------------------------------------------------------- /rs/main/examples/input.rs: -------------------------------------------------------------------------------- 1 | use glicol::Engine; 2 | // use glicol::{EngineError, get_error_info}; 3 | 4 | // use glicol::GlicolNodeInfo; 5 | // use std::collections::HashMap; 6 | 7 | fn main() { 8 | let mut engine = Engine::<8>::new(); 9 | engine 10 | .update_with_code(r#"out: ~input >> mul 2.0"#) 11 | .unwrap(); 12 | println!( 13 | "next block {:?}", 14 | engine.next_block(vec![&[0.1; 8], &[0.2; 8]]) 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /rs/main/examples/msg_synth.rs: -------------------------------------------------------------------------------- 1 | use glicol::Engine; 2 | fn main() { 3 | let mut engine = Engine::<8>::new(); 4 | engine 5 | .update_with_code(r#"o: msgsynth \saw 0.01 0.1"#) 6 | .unwrap(); 7 | println!("next block {:?}", engine.next_block(vec![])); 8 | } 9 | -------------------------------------------------------------------------------- /rs/main/examples/p_synth.rs: -------------------------------------------------------------------------------- 1 | use glicol::Engine; 2 | // use glicol::{EngineError, get_error_info}; 3 | 4 | // use glicol::GlicolNodeInfo; 5 | // use std::collections::HashMap; 6 | 7 | fn main() { 8 | let mut engine = Engine::<8>::new(); 9 | engine.update_with_code(r#"o: pattern_synth `` 1"#).unwrap(); 10 | println!("next block {:?}", engine.next_block(vec![])); 11 | } 12 | -------------------------------------------------------------------------------- /rs/main/examples/plot.rs: -------------------------------------------------------------------------------- 1 | use glicol::Engine; 2 | use gnuplot::*; 3 | 4 | fn main() { 5 | let mut engine = Engine::<128>::new(); 6 | engine 7 | .update_with_code(r#"o: [0.1=>100, 1/4=> 10.0, 1/3=>50]*(1/2).."#) 8 | .unwrap(); 9 | 10 | // plot part 11 | let mut x = Vec::::new(); 12 | let mut y = Vec::::new(); 13 | let mut n = 0; 14 | 15 | for _ in 0..(220500 / 128) { 16 | let buf = engine.next_block(vec![]); 17 | for i in 0..128 { 18 | x.push(n); 19 | n += 1; 20 | y.push(buf[0][i]); // use the buf here 21 | } 22 | } 23 | 24 | let mut fg = Figure::new(); 25 | fg.axes2d() 26 | .set_title("Glicol output", &[]) 27 | .set_legend(Graph(0.5), Graph(0.9), &[], &[]) 28 | .lines(&x, &y, &[Caption("left")]); 29 | fg.show().unwrap(); 30 | } 31 | -------------------------------------------------------------------------------- /rs/main/examples/plpf.rs: -------------------------------------------------------------------------------- 1 | // o: saw 400 >> lpf "100@0.0 200@0.5"(1) 1.0 2 | 3 | use glicol::Engine; 4 | fn main() { 5 | let mut engine = Engine::<8>::new(); 6 | engine 7 | .update_with_code(r#"o: saw 400 >> lpf "100@0.0 200@0.5"(1) 1.0"#) 8 | .unwrap(); 9 | println!("next block {:?}", engine.next_block(vec![])); 10 | } 11 | -------------------------------------------------------------------------------- /rs/main/examples/raw.rs: -------------------------------------------------------------------------------- 1 | use glicol::Engine; 2 | 3 | fn main() { 4 | let mut engine = Engine::<128>::new(); 5 | // engine.set_code("o: sin 440.0 >> mul 0.5 "); 6 | // engine.update(); 7 | // engine.next_block(); 8 | // engine.set_code("a: constsig 42 >> mul 0.1"); 9 | // engine.update(); 10 | engine.next_block(vec![]); 11 | // println!("index_info {:?}", engine.index_info); 12 | // engine.send_msg("o", 0, (0, "440.")); 13 | // engine.next_block(); 14 | } 15 | -------------------------------------------------------------------------------- /rs/main/examples/update.rs: -------------------------------------------------------------------------------- 1 | use glicol::Engine; 2 | // use glicol::GlicolNodeInfo; 3 | // use std::collections::HashMap; 4 | 5 | fn main() { 6 | let mut engine = Engine::<16>::new(); 7 | engine.livecoding = false; 8 | // engine.update(r#"o: constsig 42 >> mul 0.3"#); 9 | // engine.next_block(); 10 | // engine.update(r#"o: constsig 42 >> mul ~mod; ~mod: constsig 0.9"#); 11 | // engine.next_block(); 12 | // engine.update(r#"o: constsig 42 >> mul 0.3"#); 13 | // engine.next_block(); 14 | // engine.update(r#"o: constsig 42 >> mul ~mod; ~mod: constsig 0.9"#); 15 | // engine.next_block(); 16 | 17 | // engine.update(r#"o: constsig 42. >> add 0 >> mul ~mod; ~mod: constsig 0.5"#).unwrap(); 18 | // engine.next_block(); 19 | // engine.update(r#"o: constsig 42. >> mul ~mod; ~mod: constsig 0.5"#).unwrap(); 20 | // engine.next_block(); 21 | 22 | // engine.update(r#"o: imp 10 >> mul 0.1"#).unwrap(); 23 | // engine.next_block(); 24 | // engine.update(r#"o: saw 56"#).unwrap(); 25 | // engine.next_block(); 26 | // engine.update(r#"o: imp 100 >> mul 0.1"#).unwrap(); 27 | // engine.next_block(); 28 | // engine.update(r#"o: imp 100 >> mul ~mod 29 | // ~mo: sin 10 >> add 1.0"#).unwrap(); 30 | // engine.next_block(); 31 | 32 | // engine.update(r#"o: sin 110 >> mul 0.1"#).unwrap(); 33 | // println!(" engine.next_block() {:?}", engine.next_block()); 34 | // engine.update(r#"o: sin 110 >> add 0.0"#).unwrap(); 35 | // println!(" engine.next_block() {:?}", engine.next_block()); 36 | // engine.update(r#"o: sin 110 >> mul 0.1"#).unwrap(); 37 | // println!(" engine.next_block() {:?}", engine.next_block()); 38 | 39 | // engine.update(r#"o: seq ~a; ~a: choose 60"#).unwrap(); 40 | // println!(" engine.next_block() {:?}", engine.next_block()); 41 | // engine.update(r#"o: seq ~a; ~a: choose 70"#).unwrap(); 42 | // println!(" engine.next_block() {:?}", engine.next_block()); 43 | 44 | // engine.update(r#"o: sin 10"#).unwrap(); 45 | // println!(" engine.next_block() {:?}", engine.next_block()); 46 | // engine.update(r#"o: saw 12"#).unwrap(); 47 | // println!(" engine.next_block() {:?}", engine.next_block()); 48 | 49 | // engine.update_with_code(r#"a: constsig 10 >> lpf 300 0.1"#); 50 | // println!(" engine.next_block() 0 {:?}", engine.next_block().0); 51 | // engine.update_with_code(r#"a: constsig 10 >> lpf ~m 0.1; ~m: constsig 0.5"#); 52 | // println!(" engine.next_block() 1 {:?}", engine.next_block().0); 53 | // engine.add_sample(r#"\test"#, &[0.9, 0.8, 0.7, 0.6, 0.5], 1, 44100); 54 | engine 55 | .update_with_code( 56 | r#" 57 | ~t1: sig 10 58 | ~t2: sig 31 59 | ~t3: sig 42 60 | o: balance ~t1 ~t2"#, 61 | ) 62 | .unwrap(); 63 | println!(" engine.next_block() 0 {:?}", engine.next_block(vec![])); 64 | engine 65 | .update_with_code( 66 | r#" 67 | ~t1: sig 10 68 | ~t2: sig 31 69 | ~t3: sig 42 70 | o: balance ~t1 ~t3"#, 71 | ) 72 | .unwrap(); 73 | println!(" engine.next_block() 1 {:?}", engine.next_block(vec![])); 74 | } 75 | -------------------------------------------------------------------------------- /rs/main/src/error.rs: -------------------------------------------------------------------------------- 1 | use glicol_parser::Rule; 2 | use pest::error::Error; 3 | pub use pest::error::ErrorVariant; 4 | 5 | #[derive(Debug, PartialEq)] 6 | pub enum EngineError { 7 | ParsingError(Box>), 8 | NonExistReference(String), 9 | NonExistSample(String), 10 | } 11 | 12 | impl From>> for EngineError { 13 | fn from(err: Box>) -> EngineError { 14 | EngineError::ParsingError(err) 15 | } 16 | } 17 | 18 | pub fn get_error_info(e: Error) -> (Vec, Vec) { 19 | match e.variant { 20 | ErrorVariant::ParsingError { 21 | positives, 22 | negatives, 23 | } => (positives, negatives), 24 | _ => unimplemented!(), 25 | } 26 | } 27 | 28 | impl std::fmt::Display for EngineError { 29 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 30 | match self { 31 | Self::ParsingError(err) => writeln!(f, "Parsing error: {err}"), 32 | EngineError::NonExistSample(v) => writeln!(f, "There is no sample named {v}s"), 33 | EngineError::NonExistReference(v) => writeln!(f, "There is no reference named {v}"), 34 | } 35 | } 36 | } 37 | 38 | impl std::error::Error for EngineError {} 39 | -------------------------------------------------------------------------------- /rs/main/tests/sin.rs: -------------------------------------------------------------------------------- 1 | use glicol::*; 2 | 3 | #[test] 4 | fn pan() { 5 | let mut engine = Engine::<128>::new(); 6 | assert_eq!(engine.update_with_code(r#"o: sin 440 >> pan 0.5"#), Ok(())); 7 | } 8 | -------------------------------------------------------------------------------- /rs/parser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "glicol_parser" 3 | version = { workspace = true } 4 | edition = { workspace = true } 5 | keywords = ["audio", "music", "DSP", "synth", "synthesizer"] 6 | 7 | license-file = "LICENSE" 8 | description = "Parser for Glicol language." 9 | repository = "https://github.com/chaosprint/glicol.git" 10 | authors = ["Qichao Lan "] 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | pest = { workspace = true } 16 | pest_derive = { workspace = true } 17 | hashbrown = { workspace = true } 18 | fasteval = { workspace = true } 19 | yoke = { workspace = true } 20 | 21 | [dev-dependencies] 22 | trace = "0.1.7" 23 | -------------------------------------------------------------------------------- /rs/parser/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020-present Qichao Lan (chaopsrint) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /rs/parser/examples/choose.rs: -------------------------------------------------------------------------------- 1 | // choose is quite unique as it takes unlimited number of notes 2 | use glicol_parser::*; 3 | 4 | fn main() { 5 | println!("{:?}", get_ast("o: choose 60 50 80 70").unwrap()); 6 | } 7 | -------------------------------------------------------------------------------- /rs/parser/examples/hello.rs: -------------------------------------------------------------------------------- 1 | // use pest::Parser; 2 | // use pest::iterators::Pairs; 3 | use glicol_parser::*; 4 | 5 | fn main() { 6 | println!( 7 | "{:?}", 8 | get_ast("a: [0.1 => 0.2, 1/2 - 100_ms => 0.3]").unwrap() 9 | ); 10 | // println!("{:?}", get_ast("o: arrange ~t1 1 ~t2 3")); 11 | // println!("{:?}", get_ast("o: envperc 1.0 2.0")); 12 | // get_ast(input); 13 | // let line = GlicolParser::parse(Rule::block, input); 14 | // println!("{:?}", line); 15 | // } 16 | } 17 | -------------------------------------------------------------------------------- /rs/parser/examples/lcs.rs: -------------------------------------------------------------------------------- 1 | use glicol_parser::*; 2 | use pest::error::ErrorVariant; 3 | use pest::Parser; 4 | // use lcs_diff::*; 5 | 6 | // fn main() { 7 | // let a = [1, 3, 5]; 8 | // let b = [3, 4, 5]; 9 | // // println!("result {:?}", diff(&a, &b)); 10 | // let result = diff(&a, &b); 11 | // for r in result { 12 | // println!("result {:?}", r); 13 | // } 14 | // } 15 | 16 | fn main() { 17 | let mut block = match GlicolParser::parse( 18 | Rule::block, 19 | r#"out: sin 440 >> mul 0.1 >> add 0.1; b: seq 60 _60 >> sp \808;"#, 20 | ) { 21 | Ok(v) => v, 22 | Err(e) => { 23 | // println!("in location: {:?}; line_col: {:?}", e.location, e.line_col); 24 | 25 | match e.variant { 26 | ErrorVariant::ParsingError { 27 | positives, 28 | negatives, 29 | } => { 30 | if !positives.is_empty() { 31 | print!("\n\nexpecting "); 32 | for possible in positives { 33 | print!("{:?} ", possible) 34 | } 35 | print!("\n\n"); 36 | } 37 | if !negatives.is_empty() { 38 | print!("\n\nunexpected element: "); 39 | for possible in negatives { 40 | print!("{:?} ", possible) 41 | } 42 | print!("\n\n"); 43 | } 44 | panic!(); 45 | } 46 | _ => panic!("unknown parsing error"), 47 | } 48 | } 49 | }; 50 | let lines = block.next().unwrap(); // this can be a comment though, but we call it a line 51 | let mut ast = std::collections::HashMap::new(); 52 | for line in lines.into_inner() { 53 | if line.as_rule() == Rule::line { 54 | println!("each line {:?}", line.as_str()); 55 | let mut key = ""; 56 | let mut chain_node_names = vec![]; 57 | let mut chain_paras = vec![]; 58 | for line_component in line.into_inner() { 59 | match line_component.as_rule() { 60 | Rule::reference => { 61 | println!("ref {:?}", line_component.as_str()); 62 | key = line_component.as_str(); 63 | } 64 | Rule::chain => { 65 | println!("chain {:?}", line_component.as_str()); 66 | let chain = line_component; 67 | for node_pair in chain.into_inner() { 68 | let node = node_pair.into_inner().next().unwrap(); 69 | match node.as_rule() { 70 | Rule::sin => { 71 | println!("node {:?}", node.as_str()); //"sin 440" 72 | let paras = node.into_inner().next().unwrap(); 73 | println!("paras {:?}", paras.as_str()); //"440" 74 | chain_node_names.push("sin"); 75 | chain_paras.push(paras.as_str()); 76 | } 77 | Rule::mul => { 78 | println!("node {:?}", node.as_str()); 79 | let paras = node.into_inner().next().unwrap(); 80 | println!("paras {:?}", paras.as_str()); 81 | chain_node_names.push("mul"); 82 | chain_paras.push(paras.as_str()); 83 | } 84 | Rule::add => { 85 | println!("node {:?}", node.as_str()); 86 | let paras = node.into_inner().next().unwrap(); 87 | println!("paras {:?}", paras.as_str()); 88 | chain_node_names.push("add"); 89 | chain_paras.push(paras.as_str()); 90 | } 91 | Rule::seq => { 92 | println!("node {:?}", node.as_str()); 93 | let paras = node.into_inner().next().unwrap(); 94 | println!("paras {:?}", paras.as_str()); 95 | chain_node_names.push("seq"); 96 | chain_paras.push(paras.as_str()); 97 | } 98 | Rule::sp => { 99 | println!("node {:?}", node.as_str()); 100 | let paras = node.into_inner().next().unwrap(); 101 | println!("paras {:?}", paras.as_str()); 102 | chain_node_names.push("sp"); 103 | chain_paras.push(paras.as_str()); 104 | } 105 | _ => {} 106 | } 107 | } 108 | // println!("chain.into_inner(); {:?}", chain.into_inner()); 109 | } 110 | _ => {} 111 | } 112 | } 113 | ast.insert(key, (chain_node_names, chain_paras)); 114 | }; 115 | } 116 | println!("ast {:?}", ast); 117 | } 118 | -------------------------------------------------------------------------------- /rs/parser/examples/pattern.rs: -------------------------------------------------------------------------------- 1 | // choose is quite unique as it takes unlimited number of notes 2 | use glicol_parser::*; 3 | 4 | fn main() { 5 | println!("{:?}", get_ast(r#"o: sig "60@0.0 72@0.5"(1)"#).unwrap()); 6 | } 7 | -------------------------------------------------------------------------------- /rs/parser/examples/psampler.rs: -------------------------------------------------------------------------------- 1 | // choose is quite unique as it takes unlimited number of notes 2 | use glicol_parser::*; 3 | 4 | fn main() { 5 | println!( 6 | "{:?}", 7 | get_ast(r#"o: psampler "'bd'@0.0 'sd'@0.5""#).unwrap() 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /rs/parser/examples/seq.rs: -------------------------------------------------------------------------------- 1 | use glicol_parser::*; 2 | 3 | fn main() { 4 | println!("{:?}", get_ast("o: seq 60 _60 __60 ~a >> mul 0.5").unwrap()); 5 | } 6 | -------------------------------------------------------------------------------- /rs/parser/examples/test.rs: -------------------------------------------------------------------------------- 1 | // use pest::Parser; 2 | // use pest::iterators::Pairs; 3 | use glicol_parser::*; 4 | 5 | fn main() { 6 | println!("{:?}", get_ast(r#"o: lpf "400@0.5 600@0.9"(1) 1"#).unwrap()); 7 | // get_ast(input); 8 | // let line = GlicolParser::parse(Rule::block, input); 9 | // println!("{:?}", line); 10 | // } 11 | } 12 | -------------------------------------------------------------------------------- /rs/parser/src/util.rs: -------------------------------------------------------------------------------- 1 | use pest::{ 2 | error::{Error, ErrorVariant}, 3 | iterators::{Pair, Pairs}, 4 | RuleType, Span, 5 | }; 6 | 7 | use crate::{ 8 | nodes::{NumberOrRef, TimeList, UsizeOrRef}, 9 | Rule, 10 | }; 11 | 12 | pub trait ToPestErrWithPositives { 13 | fn to_err_with_positives(self, positives: [R; N]) 14 | -> Box>; 15 | } 16 | 17 | impl ToPestErrWithPositives for Span<'_> { 18 | fn to_err_with_positives( 19 | self, 20 | positives: [R; N], 21 | ) -> Box> { 22 | Box::new(Error::new_from_span( 23 | ErrorVariant::ParsingError { 24 | positives: positives.to_vec(), 25 | negatives: vec![], 26 | }, 27 | self, 28 | )) 29 | } 30 | } 31 | 32 | pub trait RuleRepresentable: std::str::FromStr { 33 | const RULE: Rule; 34 | } 35 | 36 | impl RuleRepresentable for f32 { 37 | const RULE: Rule = Rule::number; 38 | } 39 | 40 | impl RuleRepresentable for usize { 41 | const RULE: Rule = Rule::integer; 42 | } 43 | 44 | impl RuleRepresentable for u32 { 45 | const RULE: Rule = Rule::integer; 46 | } 47 | 48 | pub trait TryToParse { 49 | fn try_to_parse(&self) -> Result>> 50 | where 51 | T: RuleRepresentable; 52 | } 53 | 54 | impl TryToParse for Pair<'_, Rule> { 55 | fn try_to_parse(&self) -> Result>> 56 | where 57 | T: RuleRepresentable, 58 | { 59 | self.as_str() 60 | .parse::() 61 | .map_err(|_| self.as_span().to_err_with_positives([T::RULE])) 62 | } 63 | } 64 | 65 | pub trait GetNextParsed { 66 | fn next_parsed(&mut self, start_span: Span<'_>) -> Result>> 67 | where 68 | T: RuleRepresentable; 69 | } 70 | 71 | impl GetNextParsed for Pairs<'_, Rule> { 72 | fn next_parsed(&mut self, start_span: Span<'_>) -> Result>> 73 | where 74 | T: RuleRepresentable, 75 | { 76 | self.next() 77 | .ok_or_else(|| start_span.to_err_with_positives([T::RULE])) 78 | .and_then(|p| p.try_to_parse()) 79 | } 80 | } 81 | 82 | pub trait EndSpan<'ast> { 83 | fn as_end_span(&self) -> Span<'ast>; 84 | } 85 | 86 | impl<'ast, R> EndSpan<'ast> for Pair<'ast, R> 87 | where 88 | R: pest::RuleType, 89 | { 90 | fn as_end_span(&self) -> Span<'ast> { 91 | self.as_span().as_end_span() 92 | } 93 | } 94 | 95 | impl<'ast> EndSpan<'ast> for Span<'ast> { 96 | fn as_end_span(&self) -> Span<'ast> { 97 | // This is safe to unwrap 'cause we know it's valid due to the indexes we pass in 98 | self.get(self.end() - self.start()..).unwrap() 99 | } 100 | } 101 | 102 | pub trait ToInnerOwned { 103 | type Owned; 104 | fn to_inner_owned(&self) -> Self::Owned; 105 | } 106 | 107 | impl ToInnerOwned for NumberOrRef<&T> 108 | where 109 | T: AsRef + ToOwned + ?Sized, 110 | ::Owned: AsRef, 111 | { 112 | type Owned = NumberOrRef<::Owned>; 113 | fn to_inner_owned(&self) -> Self::Owned { 114 | match self { 115 | Self::Ref(s) => NumberOrRef::Ref((*s).to_owned()), 116 | Self::Number(n) => NumberOrRef::Number(*n), 117 | } 118 | } 119 | } 120 | 121 | impl ToInnerOwned for UsizeOrRef<&T> 122 | where 123 | T: AsRef + ToOwned + ?Sized, 124 | ::Owned: AsRef, 125 | { 126 | type Owned = UsizeOrRef<::Owned>; 127 | fn to_inner_owned(&self) -> Self::Owned { 128 | match self { 129 | Self::Ref(s) => UsizeOrRef::Ref((*s).to_owned()), 130 | Self::Usize(u) => UsizeOrRef::Usize(*u), 131 | } 132 | } 133 | } 134 | 135 | impl ToInnerOwned for Vec<(T, U)> 136 | where 137 | T: ToInnerOwned, 138 | U: ToInnerOwned, 139 | { 140 | type Owned = Vec<(::Owned, ::Owned)>; 141 | fn to_inner_owned(&self) -> Self::Owned { 142 | self.iter() 143 | .map(|(t, u)| (t.to_inner_owned(), u.to_inner_owned())) 144 | .collect() 145 | } 146 | } 147 | 148 | impl ToInnerOwned for Vec 149 | where 150 | T: ToInnerOwned, 151 | { 152 | type Owned = Vec<::Owned>; 153 | fn to_inner_owned(&self) -> Self::Owned { 154 | self.iter().map(ToInnerOwned::to_inner_owned).collect() 155 | } 156 | } 157 | 158 | impl ToInnerOwned for f32 { 159 | type Owned = f32; 160 | fn to_inner_owned(&self) -> Self::Owned { 161 | *self 162 | } 163 | } 164 | 165 | impl ToInnerOwned for TimeList { 166 | type Owned = TimeList; 167 | fn to_inner_owned(&self) -> Self::Owned { 168 | self.clone() 169 | } 170 | } 171 | 172 | #[macro_export] 173 | macro_rules! match_or_return_err{ 174 | ($pair:ident, $($variant:path => $arm:tt,)+) => { 175 | match $pair.as_rule() { 176 | $($variant => $arm),* 177 | _ => return ::core::result::Result::Err(::std::boxed::Box::new(::pest::error::Error::new_from_span( 178 | ::pest::error::ErrorVariant::ParsingError { 179 | positives: vec![$($variant,)*], 180 | negatives: vec![] 181 | }, 182 | $pair.as_span() 183 | ))) 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /rs/parser/tests/sin.rs: -------------------------------------------------------------------------------- 1 | use glicol_parser::*; 2 | 3 | #[test] 4 | fn minimal() { 5 | let res = get_ast("o: sin 440"); 6 | assert!(res.is_ok()); 7 | } 8 | 9 | #[test] 10 | fn comment_within_chain() { 11 | let res = get_ast( 12 | "o: sin 440 13 | // >> mul 0.5 14 | >> mul 0.6", 15 | ); 16 | assert!(res.is_ok()); 17 | 18 | let res = get_ast( 19 | "o: sin 440 20 | // ooooh this is nonsense 21 | >> add 6.00", 22 | ); 23 | assert!(res.is_ok()); 24 | } 25 | -------------------------------------------------------------------------------- /rs/synth/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "glicol_synth" 3 | version = { workspace = true } 4 | edition = { workspace = true } 5 | keywords = [ 6 | "audio", 7 | "music", 8 | "DSP", 9 | "synth", 10 | "synthesizer" 11 | ] 12 | readme = "README.md" 13 | license-file = "LICENSE" 14 | description = "A graph-based music DSP library written in Rust" 15 | repository = { workspace = true } 16 | authors = { workspace = true } 17 | 18 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 19 | [features] 20 | default = ["node-boxed", "node-pass", "node-sum"] 21 | use-samples = ["node-sampling"] 22 | use-meta = ["node-dynamic"] 23 | node-boxed = [] 24 | node-pass = [] 25 | node-sampling = [] 26 | node-dynamic = ["rhai"] 27 | node-sum = ["dasp_slice"] 28 | wasm-bindgen = ["rhai/wasm-bindgen"] 29 | 30 | [[bench]] 31 | name = "next_block" 32 | harness = false 33 | 34 | [[bench]] 35 | name = "fm" 36 | harness = false 37 | 38 | [dependencies] 39 | petgraph = { workspace = true } 40 | dasp_slice = { workspace = true, optional = true } 41 | dasp_ring_buffer = { workspace = true } 42 | dasp_signal = { workspace = true } 43 | dasp_interpolate = { workspace = true } 44 | hashbrown = { workspace = true } 45 | rhai = { workspace = true, optional = true } 46 | fasteval = { workspace = true } 47 | glicol_parser = { path = "../parser", version = "0.14.0-dev"} 48 | 49 | [dev-dependencies] 50 | gnuplot = "0.0.43" 51 | criterion = "0.5.1" 52 | # petgraph = { version = "0.6", features = ["stable_graph"] } 53 | # cpal = "0.14.0" 54 | # anyhow = "1.0.63" 55 | -------------------------------------------------------------------------------- /rs/synth/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020-present Qichao Lan (chaopsrint) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /rs/synth/README.md: -------------------------------------------------------------------------------- 1 | # glicol_synth: a graph-based audio DSP library written in Rust 2 | 3 | `glicol_synth` is the audio engine of glicol computer music language. 4 | It can be used as a standalone audio library, with quite intuitive APIs: 5 | 6 | ```rust 7 | use glicol_synth::{AudioContextBuilder, signal::ConstSig, Message}; 8 | 9 | let mut context = AudioContextBuilder::<16>::new() 10 | .sr(44100).channels(1).build(); 11 | 12 | let node_a = context.add_mono_node(ConstSig::new(42.)); 13 | context.connect(node_a, context.destination); 14 | println!("first block {:?}", context.next_block()); 15 | 16 | context.send_msg(node_a, Message::SetToNumber(0, 100.)); 17 | println!("second block, after msg {:?}", context.next_block()); 18 | ``` 19 | 20 | ## Overview 21 | 22 | `glicol_synth` begins with a fork of the `dasp_graph` crate, written by @mitchmindtree. 23 | many features and contents are added: 24 | 25 | - use const generics for a customisable buffer size 26 | - replace the input from vec to a map, so users can use a node id to select input 27 | - users can send message to each node in real-time for interaction 28 | - add a higher level audiocontext for easier APIs 29 | - many useful audio nodes from oscillators, filters, etc. 30 | 31 | See the [examples on GitHub](https://github.com/chaosprint/glicol/tree/main/rs/synth/examples) for the basic usage. 32 | -------------------------------------------------------------------------------- /rs/synth/benches/fm.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 2 | use glicol_synth::{ 3 | operator::{Add, Mul}, 4 | oscillator::SinOsc, 5 | AudioContextBuilder, 6 | }; 7 | 8 | fn fm_benchmark(c: &mut Criterion) { 9 | let mut context = AudioContextBuilder::<128>::new() 10 | .sr(44100) 11 | .channels(2) 12 | .build(); //black_box(200.) 13 | let sin1 = context.add_stereo_node(SinOsc::new()); 14 | let mul1 = context.add_stereo_node(Mul::new(0.1)); 15 | let sin2 = context.add_stereo_node(SinOsc::new().freq(black_box(200.))); 16 | let mul2 = context.add_stereo_node(Mul::new(300.)); 17 | let add2 = context.add_stereo_node(Add::new(600.)); 18 | context.connect(sin1, mul1); 19 | context.connect(sin2, mul2); 20 | context.connect(mul2, add2); 21 | context.connect(add2, sin1); 22 | context.connect(mul1, context.destination); 23 | 24 | c.bench_function("fm", |b| { 25 | b.iter(|| { 26 | context.next_block(); 27 | }) 28 | }); 29 | } 30 | 31 | criterion_group!(benches, fm_benchmark); 32 | criterion_main!(benches); 33 | -------------------------------------------------------------------------------- /rs/synth/benches/next_block.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 2 | use glicol_synth::{oscillator::SinOsc, AudioContextBuilder}; 3 | 4 | fn next_block_benchmark(c: &mut Criterion) { 5 | let mut context = AudioContextBuilder::<128>::new() 6 | .sr(44100) 7 | .channels(2) 8 | .build(); 9 | let node_a = context.add_stereo_node(SinOsc::new().freq(black_box(440.))); 10 | context.connect(node_a, context.destination); 11 | 12 | c.bench_function("next_block", |b| { 13 | b.iter(|| { 14 | context.next_block(); 15 | }) 16 | }); 17 | } 18 | 19 | criterion_group!(benches, next_block_benchmark); 20 | criterion_main!(benches); 21 | -------------------------------------------------------------------------------- /rs/synth/examples/chain.rs: -------------------------------------------------------------------------------- 1 | use glicol_synth::{operator::Mul, signal::ConstSig, AudioContextBuilder}; 2 | 3 | fn main() { 4 | let mut context = AudioContextBuilder::<8>::new() 5 | .sr(44100) 6 | .channels(1) 7 | .build(); 8 | 9 | let node_a = context.add_mono_node(ConstSig::new(24.)); 10 | let node_b = context.add_mono_node(Mul::new(0.5)); 11 | // context.chain(vec![node_a, node_b, context.destination]); 12 | 13 | let node_c = context.add_mono_node(ConstSig::new(0.1)); 14 | context.chain(vec![node_c, node_b]); 15 | 16 | context.chain(vec![node_a, node_b, context.destination]); 17 | println!("first block {:?}", context.next_block()); 18 | } 19 | -------------------------------------------------------------------------------- /rs/synth/examples/connect.rs: -------------------------------------------------------------------------------- 1 | use glicol_synth::{operator::Mul, signal::ConstSig, AudioContextBuilder, Message}; 2 | 3 | fn main() { 4 | let mut context = AudioContextBuilder::<8>::new() 5 | .sr(44100) 6 | .channels(2) 7 | .build(); 8 | 9 | let node_a = context.add_mono_node(ConstSig::new(42.)); 10 | let node_b = context.add_stereo_node(Mul::new(0.1)); 11 | context.connect(node_a, node_b); 12 | context.connect(node_b, context.destination); 13 | 14 | println!("first block {:?}", context.next_block()); 15 | // message 16 | context.send_msg(node_a, Message::SetToNumber(0, 100.)); 17 | println!("second block, after msg {:?}", context.next_block()); 18 | } 19 | -------------------------------------------------------------------------------- /rs/synth/examples/freeverb.rs: -------------------------------------------------------------------------------- 1 | use glicol_synth::{ 2 | operator::Mul, 3 | // effect::FreeverbNode, 4 | signal::Impulse, 5 | AudioContextBuilder, 6 | }; 7 | /// deprecated! 8 | use gnuplot::*; 9 | 10 | fn main() { 11 | let mut context = AudioContextBuilder::<128>::new() 12 | .sr(44100) 13 | .channels(2) 14 | .build(); 15 | let node_a = context.add_mono_node(Impulse::new().freq(0.1)); 16 | let node_a2 = context.add_stereo_node(Mul::new(0.9)); 17 | // let node_b = context.add_stereo_node( FreeverbNode::new() ); 18 | // context.connect(node_a, context.destination); 19 | context.chain(vec![node_a, node_a2, context.destination]); 20 | 21 | // plot part 22 | let mut x = Vec::::new(); 23 | let mut y = Vec::::new(); 24 | let mut y2 = Vec::::new(); 25 | let mut n = 0; 26 | 27 | for _ in 0..(44100 / 128) { 28 | let buf = context.next_block(); 29 | for i in 0..128 { 30 | x.push(n); 31 | n += 1; 32 | y.push(buf[0][i]); // use the buf here 33 | y2.push(buf[1][i]); 34 | } 35 | } 36 | 37 | let mut fg = Figure::new(); 38 | fg.axes2d() 39 | .set_title("Impulse response", &[Font("Courier", 8.)]) 40 | .set_legend(Graph(0.8), Graph(0.9), &[], &[Font("Courier", 8.)]) 41 | .lines(&x, &y, &[Caption("left"), Color("#e879f9"), LineWidth(2.0)]) 42 | .lines( 43 | &x, 44 | &y2, 45 | &[Caption("right"), Color("#38bdf8"), LineWidth(2.0)], 46 | ); 47 | fg.show().unwrap(); 48 | } 49 | -------------------------------------------------------------------------------- /rs/synth/examples/gnode.rs: -------------------------------------------------------------------------------- 1 | use glicol_synth::{effect::Plate, signal::ConstSig, AudioContextBuilder}; 2 | 3 | fn main() { 4 | let mut context = AudioContextBuilder::<8>::new() 5 | .sr(44100) 6 | .channels(2) 7 | .build(); 8 | 9 | let c = context.add_stereo_node(ConstSig::new(1.)); 10 | let node_a = context.add_stereo_node(Plate::new(0.5)); 11 | 12 | // all the process will happen to the destination node 13 | context.chain(vec![c, node_a, context.destination]); 14 | 15 | // that's all, you can use this graph.next_block() in a callback loop 16 | println!("first block {:?}", context.next_block()); 17 | 18 | // message 19 | // context.send_msg(node_a, Message::SetToNumber(0, 1.) ); 20 | // println!("second block, after msg {:?}", context.next_block()); 21 | } 22 | -------------------------------------------------------------------------------- /rs/synth/examples/hello.rs: -------------------------------------------------------------------------------- 1 | use glicol_synth::{signal::ConstSig, AudioContextBuilder, Message}; 2 | 3 | fn main() { 4 | let mut context = AudioContextBuilder::<128>::new() 5 | .sr(44100) 6 | .channels(1) 7 | .build(); 8 | 9 | let node_a = context.add_mono_node(ConstSig::new(42.)); 10 | 11 | // all the process will happen to the destination node 12 | context.connect(node_a, context.destination); 13 | 14 | // that's all, you can use this graph.next_block() in a callback loop 15 | println!("first block {:?}", context.next_block()); 16 | 17 | // message 18 | context.send_msg(node_a, Message::SetToNumber(0, 100.)); 19 | println!("second block, after msg {:?}", context.next_block()); 20 | } 21 | -------------------------------------------------------------------------------- /rs/synth/examples/plot-am.rs: -------------------------------------------------------------------------------- 1 | use glicol_synth::{operator::Add, operator::Mul, oscillator::SinOsc, AudioContextBuilder}; 2 | use gnuplot::*; 3 | 4 | fn main() { 5 | let mut context = AudioContextBuilder::<128>::new() 6 | .sr(44100) 7 | .channels(1) 8 | .build(); 9 | let node_a = context.add_mono_node(SinOsc::new().freq(10.)); 10 | let node_b = context.add_mono_node(Mul::new(0.5)); 11 | // context.connect(node_a, context.destination); 12 | 13 | // let node_c = context.add_mono_node( SquOsc::new().freq(10.) ); 14 | let node_d = context.add_mono_node(Mul::new(8.0)); 15 | let node_e = context.add_mono_node(Add::new(1.0)); 16 | let node_f = context.add_mono_node(SinOsc::new().freq(100.)); 17 | context.chain(vec![node_a, node_b, context.destination]); 18 | context.chain(vec![node_f, node_d, node_e, node_b]); 19 | // context.connect_with_order(node_e, node_b, 1); 20 | 21 | // plot part 22 | let mut x = Vec::::new(); 23 | let mut y = Vec::::new(); 24 | let mut n = 0; 25 | 26 | for _ in 0..(44100 / 128) { 27 | let buf = context.next_block(); 28 | for i in 0..128 { 29 | x.push(n); 30 | n += 1; 31 | y.push(buf[0][i]); // use the buf here 32 | } 33 | } 34 | 35 | let mut fg = Figure::new(); 36 | fg.axes2d() 37 | .set_title("Glicol output", &[]) 38 | .set_legend(Graph(0.5), Graph(0.9), &[], &[]) 39 | .lines(&x, &y, &[Caption("left")]); 40 | fg.show().unwrap(); 41 | } 42 | -------------------------------------------------------------------------------- /rs/synth/examples/plot-g.rs: -------------------------------------------------------------------------------- 1 | use glicol_synth::{effect::Plate, signal::Impulse, AudioContextBuilder}; 2 | use gnuplot::*; 3 | 4 | fn main() { 5 | let mut context = AudioContextBuilder::<128>::new() 6 | .sr(44100) 7 | .channels(1) 8 | .build(); 9 | 10 | let c = context.add_mono_node(Impulse::new().freq(10.)); 11 | let node_a = context.add_stereo_node(Plate::new(0.5)); 12 | 13 | // all the process will happen to the destination node 14 | context.chain(vec![c, node_a, context.destination]); 15 | 16 | // plot part 17 | let mut x = Vec::::new(); 18 | let mut y = Vec::::new(); 19 | let mut n = 0; 20 | 21 | for _ in 0..(44100 / 128) { 22 | let buf = context.next_block(); 23 | for i in 0..128 { 24 | x.push(n); 25 | n += 1; 26 | y.push(buf[0][i]); // use the buf here 27 | } 28 | } 29 | 30 | let mut fg = Figure::new(); 31 | fg.axes2d() 32 | .set_title("Glicol output", &[]) 33 | .set_legend(Graph(0.5), Graph(0.9), &[], &[]) 34 | .lines(&x, &y, &[Caption("left")]); 35 | fg.show().unwrap(); 36 | } 37 | -------------------------------------------------------------------------------- /rs/synth/examples/plot-imp.rs: -------------------------------------------------------------------------------- 1 | use glicol_synth::{effect::Plate, signal::Impulse, AudioContextBuilder}; 2 | use gnuplot::*; 3 | 4 | fn main() { 5 | let mut context = AudioContextBuilder::<128>::new() 6 | .sr(44100) 7 | .channels(2) 8 | .build(); 9 | let node_a = context.add_mono_node(Impulse::new().freq(0.1)); 10 | let node_b = context.add_stereo_node(Plate::new(0.1)); 11 | // context.connect(node_a, context.destination); 12 | context.chain(vec![node_a, node_b, context.destination]); 13 | 14 | // plot part 15 | let mut x = Vec::::new(); 16 | let mut y = Vec::::new(); 17 | let mut y2 = Vec::::new(); 18 | let mut n = 0; 19 | 20 | for _ in 0..(44100 / 128) { 21 | let buf = context.next_block(); 22 | for i in 0..128 { 23 | x.push(n); 24 | n += 1; 25 | y.push(buf[0][i]); // use the buf here 26 | y2.push(buf[1][i]); 27 | } 28 | } 29 | 30 | let mut fg = Figure::new(); 31 | fg.axes2d() 32 | .set_title("Impulse response", &[Font("Courier", 8.)]) 33 | .set_legend(Graph(0.8), Graph(0.9), &[], &[Font("Courier", 8.)]) 34 | .lines(&x, &y, &[Caption("left"), Color("#e879f9"), LineWidth(2.0)]) 35 | .lines( 36 | &x, 37 | &y2, 38 | &[Caption("right"), Color("#38bdf8"), LineWidth(2.0)], 39 | ); 40 | fg.show().unwrap(); 41 | } 42 | -------------------------------------------------------------------------------- /rs/synth/examples/plot.rs: -------------------------------------------------------------------------------- 1 | use glicol_synth::{operator::Mul, oscillator::SinOsc, AudioContextBuilder}; 2 | use gnuplot::*; 3 | 4 | fn main() { 5 | let mut context = AudioContextBuilder::<128>::new() 6 | .sr(44100) 7 | .channels(1) 8 | .build(); 9 | let node_a = context.add_mono_node(SinOsc::new().freq(2.)); 10 | let node_b = context.add_mono_node(Mul::new(0.5)); 11 | // context.connect(node_a, context.destination); 12 | context.chain(vec![node_a, node_b, context.destination]); 13 | 14 | // plot part 15 | let mut x = Vec::::new(); 16 | let mut y = Vec::::new(); 17 | let mut n = 0; 18 | 19 | for _ in 0..(88000 / 128) { 20 | let buf = context.next_block(); 21 | for i in 0..128 { 22 | x.push(n); 23 | n += 1; 24 | y.push(buf[0][i]); // use the buf here 25 | } 26 | } 27 | 28 | let mut fg = Figure::new(); 29 | fg.axes2d() 30 | .set_title("Glicol output", &[]) 31 | .set_legend(Graph(0.5), Graph(0.9), &[], &[]) 32 | .lines(&x, &y, &[Caption("left")]); 33 | fg.show().unwrap(); 34 | } 35 | -------------------------------------------------------------------------------- /rs/synth/examples/plot2.rs: -------------------------------------------------------------------------------- 1 | use glicol_synth::{ 2 | filter::ResonantLowPassFilter, oscillator::SawOsc, signal::ConstSig, AudioContextBuilder, 3 | }; 4 | use gnuplot::*; 5 | 6 | fn main() { 7 | let mut context = AudioContextBuilder::<128>::new() 8 | .sr(44100) 9 | .channels(1) 10 | .build(); 11 | let node_a = context.add_mono_node(SawOsc::new().freq(30.)); 12 | let node_b = context.add_mono_node(ResonantLowPassFilter::new().cutoff(100.0)); 13 | let node_c = context.add_mono_node(ConstSig::new(50.0)); 14 | context.chain(vec![node_a, node_b, context.destination]); 15 | context.connect(node_c, node_b); 16 | 17 | // plot part 18 | let mut x = Vec::::new(); 19 | let mut y = Vec::::new(); 20 | let mut n = 0; 21 | 22 | for _ in 0..(44100 / 128) { 23 | let buf = context.next_block(); 24 | for i in 0..128 { 25 | x.push(n); 26 | n += 1; 27 | y.push(buf[0][i]); // use the buf here 28 | } 29 | } 30 | 31 | let mut fg = Figure::new(); 32 | fg.axes2d() 33 | .set_title("Glicol output", &[]) 34 | .set_legend(Graph(0.5), Graph(0.9), &[], &[]) 35 | .lines(&x, &y, &[Caption("left")]); 36 | fg.show().unwrap(); 37 | } 38 | -------------------------------------------------------------------------------- /rs/synth/examples/plot3.rs: -------------------------------------------------------------------------------- 1 | use glicol_synth::{ 2 | delay::DelayMs, 3 | operator::{Add, Mul}, 4 | // sampling::{Sampler}, 5 | oscillator::SinOsc, 6 | signal::Impulse, 7 | AudioContextBuilder, 8 | }; 9 | use gnuplot::*; 10 | 11 | fn main() { 12 | let mut context = AudioContextBuilder::<128>::new() 13 | .sr(44100) 14 | .channels(1) 15 | .build(); 16 | let node_a = context.add_mono_node(Impulse::new().freq(1000.0)); 17 | // let node_b = context.add_stereo_node( Sampler::new((&[1.0, 0.0, 0.0], 1)) ); 18 | // let node_c = context.add_stereo_node( Mul::new(0.5) ); 19 | let node_b = context.add_mono_node(DelayMs::new().delay(0.0, 1)); 20 | let (node_d, node_e, node_f) = ( 21 | context.add_mono_node(SinOsc::new()), 22 | context.add_mono_node(Mul::new(100.)), 23 | context.add_mono_node(Add::new(200.)), 24 | ); 25 | // let node_j = context.add_mono_node(Mul::new(100.)); 26 | 27 | context.chain(vec![ 28 | node_a, 29 | node_b, 30 | // node_j, 31 | context.destination, 32 | ]); 33 | context.chain(vec![node_d, node_e, node_f, node_b]); 34 | 35 | // plot part 36 | let mut x = Vec::::new(); 37 | let mut y = Vec::::new(); 38 | let mut n = 0; 39 | 40 | for _ in 0..(512 / 128) { 41 | let buf = context.next_block(); 42 | for i in 0..128 { 43 | x.push(n); 44 | n += 1; 45 | y.push(buf[0][i]); // use the buf here 46 | } 47 | } 48 | 49 | let mut fg = Figure::new(); 50 | fg.axes2d() 51 | .set_title("Glicol output", &[]) 52 | .set_legend(Graph(0.5), Graph(0.9), &[], &[]) 53 | .lines(&x, &y, &[Caption("left")]); 54 | fg.show().unwrap(); 55 | } 56 | -------------------------------------------------------------------------------- /rs/synth/examples/plot_psynth.rs: -------------------------------------------------------------------------------- 1 | use glicol_synth::{synth::PatternSynth, AudioContextBuilder}; 2 | use gnuplot::*; 3 | 4 | fn main() { 5 | let mut context = AudioContextBuilder::<128>::new() 6 | .sr(44100) 7 | .channels(1) 8 | .build(); 9 | 10 | let node_a = context.add_mono_node(PatternSynth::new(vec![(0.0, 60.), (0.5, 72.)])); 11 | 12 | context.chain(vec![node_a, context.destination]); 13 | 14 | // plot part 15 | let mut x = Vec::::new(); 16 | let mut y = Vec::::new(); 17 | let mut n = 0; 18 | 19 | for _ in 0..(88200 / 128) { 20 | let buf = context.next_block(); 21 | for i in 0..128 { 22 | x.push(n); 23 | n += 1; 24 | y.push(buf[0][i]); // use the buf here 25 | } 26 | } 27 | 28 | let mut fg = Figure::new(); 29 | fg.axes2d() 30 | .set_title("Glicol output", &[]) 31 | .set_legend(Graph(0.5), Graph(0.9), &[], &[]) 32 | .lines(&x, &y, &[Caption("left")]); 33 | fg.show().unwrap(); 34 | } 35 | -------------------------------------------------------------------------------- /rs/synth/examples/sin.rs: -------------------------------------------------------------------------------- 1 | use glicol_synth::{operator::Mul, oscillator::SinOsc, AudioContextBuilder, Message}; 2 | 3 | fn main() { 4 | let mut context = AudioContextBuilder::<8>::new() 5 | .sr(44100) 6 | .channels(2) 7 | .build(); 8 | 9 | let node_a = context.add_mono_node(SinOsc::new().freq(440.0)); 10 | let node_b = context.add_stereo_node(Mul::new(0.1)); 11 | context.connect(node_a, node_b); 12 | context.connect(node_b, context.destination); 13 | 14 | println!("first block {:?}", context.next_block()); 15 | // message 16 | context.send_msg(node_a, Message::SetToNumber(0, 100.)); 17 | println!("second block, after msg {:?}", context.next_block()); 18 | } 19 | -------------------------------------------------------------------------------- /rs/synth/src/buffer.rs: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | 3 | // Copyright (c) 2016 RustAudio Developers 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | // modified: add const generics 24 | 25 | use core::fmt; 26 | use core::ops::{Deref, DerefMut}; 27 | 28 | /// The fixed-size buffer used for processing the graph. 29 | #[derive(Clone)] 30 | pub struct Buffer { 31 | data: [f32; N], 32 | } 33 | 34 | impl Buffer { 35 | pub const LEN: usize = N; 36 | /// A silent **Buffer**. 37 | pub const SILENT: Self = Buffer { data: [0.0; N] }; 38 | 39 | /// Short-hand for writing silence to the whole buffer. 40 | pub fn silence(&mut self) { 41 | self.data.copy_from_slice(&Self::SILENT) 42 | } 43 | } 44 | 45 | impl Default for Buffer { 46 | fn default() -> Self { 47 | Self::SILENT 48 | } 49 | } 50 | 51 | impl From<[f32; N]> for Buffer { 52 | fn from(data: [f32; N]) -> Self { 53 | Buffer { data } 54 | } 55 | } 56 | 57 | impl fmt::Debug for Buffer { 58 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 59 | fmt::Debug::fmt(&self.data[..], f) 60 | } 61 | } 62 | 63 | impl PartialEq for Buffer { 64 | fn eq(&self, other: &Self) -> bool { 65 | self[..] == other[..] 66 | } 67 | } 68 | 69 | impl Deref for Buffer { 70 | type Target = [f32]; 71 | fn deref(&self) -> &Self::Target { 72 | &self.data[..] 73 | } 74 | } 75 | 76 | impl DerefMut for Buffer { 77 | fn deref_mut(&mut self) -> &mut Self::Target { 78 | &mut self.data[..] 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /rs/synth/src/lib.rs: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | 3 | // Copyright (c) 2016 RustAudio Developers 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #![doc = include_str!("../README.md")] 24 | mod context; 25 | 26 | pub use context::*; 27 | 28 | mod graph; 29 | use glicol_parser::{ 30 | nodes::{TimeList, UsizeOrRef}, 31 | ToInnerOwned, 32 | }; 33 | pub use graph::*; 34 | 35 | mod node; 36 | pub use node::{ 37 | compound, delay, effect, envelope, filter, operator, oscillator, sequencer, signal, synth, 38 | }; 39 | pub use node::{Input, Node}; 40 | // pub use node::*; // TODO: Do not expose every struct here 41 | 42 | mod buffer; 43 | pub use buffer::Buffer; 44 | 45 | #[cfg(feature = "node-sampling")] 46 | pub use node::sampling; 47 | 48 | #[cfg(feature = "node-dynamic")] 49 | pub use node::dynamic; 50 | 51 | #[cfg(feature = "node-boxed")] 52 | pub use node::{BoxedNode, BoxedNodeSend}; 53 | 54 | #[cfg(feature = "node-sum")] 55 | pub use node::{Sum, Sum2}; 56 | 57 | #[cfg(feature = "node-pass")] 58 | pub use node::Pass; 59 | 60 | use hashbrown::HashMap; 61 | 62 | #[derive(Debug, Clone)] 63 | pub enum Message { 64 | SetToNumber(u8, f32), 65 | SetToNumberList(u8, Vec), 66 | SetToSymbol(u8, String), 67 | SetToSamples(u8, (&'static [f32], usize, usize)), 68 | SetSamplePattern( 69 | Vec<(String, f32)>, 70 | f32, 71 | HashMap, 72 | ), 73 | SetPattern(Vec<(f32, f32)>, f32), 74 | SetToSeq(u8, Vec<(f32, UsizeOrRef)>), 75 | SetRefOrder(HashMap), 76 | SetBPM(f32), 77 | SetSampleRate(usize), 78 | MainInput(petgraph::graph::NodeIndex), 79 | SidechainInput(petgraph::graph::NodeIndex), 80 | Index(usize), 81 | IndexOrder(usize, usize), 82 | ResetOrder, 83 | SetParam(u8, GlicolPara), 84 | SetToBool(u8, bool), 85 | } 86 | 87 | #[derive(Debug, PartialEq, PartialOrd, Clone)] 88 | pub enum GlicolPara 89 | where 90 | S: AsRef, 91 | { 92 | Number(f32), 93 | Bool(bool), 94 | NumberList(Vec), 95 | Reference(S), 96 | SampleSymbol(S), // symbol is for sample only 97 | Symbol(S), 98 | Sequence(Vec<(f32, UsizeOrRef)>), 99 | Pattern(Vec<(Self, f32)>, f32), 100 | Event(Vec<(Self, f32)>), 101 | Points(Vec<(TimeList, f32)>), 102 | Bar(f32), 103 | Second(f32), 104 | Millisecond(f32), 105 | } 106 | 107 | impl ToInnerOwned for GlicolPara<&S> 108 | where 109 | S: AsRef + ToOwned + ?Sized, 110 | ::Owned: AsRef, 111 | { 112 | type Owned = GlicolPara<::Owned>; 113 | fn to_inner_owned(&self) -> Self::Owned { 114 | match self { 115 | Self::Number(num) => GlicolPara::Number(*num), 116 | Self::Bool(bool) => GlicolPara::Bool(*bool), 117 | Self::NumberList(vec) => GlicolPara::NumberList(vec.to_vec()), 118 | Self::Reference(s) => GlicolPara::Reference((*s).to_owned()), 119 | Self::SampleSymbol(s) => GlicolPara::SampleSymbol((*s).to_owned()), 120 | Self::Symbol(s) => GlicolPara::Symbol((*s).to_owned()), 121 | Self::Sequence(seq) => GlicolPara::Sequence(seq.to_inner_owned()), 122 | Self::Pattern(vec, num) => GlicolPara::Pattern(vec.to_inner_owned(), *num), 123 | Self::Event(vec) => GlicolPara::Event(vec.to_inner_owned()), 124 | Self::Points(vec) => GlicolPara::Points(vec.to_inner_owned()), 125 | Self::Bar(num) => GlicolPara::Bar(*num), 126 | Self::Second(num) => GlicolPara::Second(*num), 127 | Self::Millisecond(num) => GlicolPara::Millisecond(*num), 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /rs/synth/src/node/boxed.rs: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | 3 | // Copyright (c) 2016 RustAudio Developers 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | use crate::{Buffer, Input, Message, Node}; 24 | use core::fmt; 25 | use core::ops::{Deref, DerefMut}; 26 | use hashbrown::HashMap; 27 | 28 | /// A wrapper around a `Box`. 29 | /// 30 | /// Provides the necessary `Sized` implementation to allow for compatibility with the graph process 31 | /// function. 32 | pub struct BoxedNode(pub Box>); 33 | 34 | /// A wrapper around a `Box`. 35 | /// 36 | /// Provides the necessary `Sized` implementation to allow for compatibility with the graph process 37 | /// function. 38 | /// 39 | /// Useful when the ability to send nodes from one thread to another is required. E.g. this is 40 | /// common when initialising nodes or the audio graph itself on one thread before sending them to 41 | /// the audio thread. 42 | pub struct BoxedNodeSend(pub Box + Send>); 43 | 44 | impl BoxedNode { 45 | /// Create a new `BoxedNode` around the given `node`. 46 | /// 47 | /// This is short-hand for `BoxedNode::from(Box::new(node))`. 48 | pub fn new(node: T) -> Self 49 | where 50 | T: 'static + Node, 51 | { 52 | Self::from(Box::new(node)) 53 | } 54 | } 55 | 56 | impl BoxedNodeSend { 57 | /// Create a new `BoxedNode` around the given `node`. 58 | /// 59 | /// This is short-hand for `BoxedNode::from(Box::new(node))`. 60 | pub fn new(node: T) -> Self 61 | where 62 | T: 'static + Node + Send, 63 | { 64 | Self::from(Box::new(node)) 65 | } 66 | } 67 | 68 | impl Node for BoxedNode { 69 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 70 | self.0.process(inputs, output) 71 | } 72 | fn send_msg(&mut self, info: Message) { 73 | self.0.send_msg(info) 74 | } 75 | } 76 | 77 | impl Node for BoxedNodeSend { 78 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 79 | self.0.process(inputs, output) 80 | } 81 | fn send_msg(&mut self, info: Message) { 82 | self.0.send_msg(info) 83 | } 84 | } 85 | 86 | impl From> for BoxedNode 87 | where 88 | T: 'static + Node, 89 | { 90 | fn from(n: Box) -> Self { 91 | BoxedNode(n as Box>) 92 | } 93 | } 94 | 95 | impl From> for BoxedNodeSend 96 | where 97 | T: 'static + Node + Send, 98 | { 99 | fn from(n: Box) -> Self { 100 | BoxedNodeSend(n as Box + Send>) 101 | } 102 | } 103 | 104 | impl From> for Box> { 105 | fn from(value: BoxedNode) -> Self { 106 | value.0 107 | } 108 | } 109 | 110 | impl From> for Box + Send> { 111 | fn from(value: BoxedNodeSend) -> Self { 112 | value.0 113 | } 114 | } 115 | 116 | impl fmt::Debug for BoxedNode { 117 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 118 | f.debug_struct("BoxedNode").finish() 119 | } 120 | } 121 | 122 | impl fmt::Debug for BoxedNodeSend { 123 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 124 | f.debug_struct("BoxedNodeSend").finish() 125 | } 126 | } 127 | 128 | impl Deref for BoxedNode { 129 | type Target = Box>; 130 | fn deref(&self) -> &Self::Target { 131 | &self.0 132 | } 133 | } 134 | 135 | impl Deref for BoxedNodeSend { 136 | type Target = Box + Send>; 137 | fn deref(&self) -> &Self::Target { 138 | &self.0 139 | } 140 | } 141 | 142 | impl DerefMut for BoxedNode { 143 | fn deref_mut(&mut self) -> &mut Self::Target { 144 | &mut self.0 145 | } 146 | } 147 | 148 | impl DerefMut for BoxedNodeSend { 149 | fn deref_mut(&mut self) -> &mut Self::Target { 150 | &mut self.0 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /rs/synth/src/node/compound/bd.rs: -------------------------------------------------------------------------------- 1 | // ~env: imp 1 >> envperc 0.003 0.3 2 | 3 | // ~p: ~ep >> mul 50 >> add 60 4 | 5 | // o: sin ~p >> mul ~env 6 | 7 | // ~ep: imp 1 >> envperc 0.01 0.1; 8 | 9 | use crate::{ 10 | envelope::EnvPerc, 11 | operator::{Add, Mul}, 12 | oscillator::SinOsc, 13 | AudioContext, Pass, 14 | }; 15 | use crate::{Buffer, Input, Message, Node}; 16 | use hashbrown::HashMap; 17 | 18 | use petgraph::graph::NodeIndex; 19 | 20 | use super::process_compound; 21 | 22 | pub struct Bd { 23 | input: NodeIndex, 24 | context: AudioContext, 25 | input_order: Vec, 26 | } 27 | 28 | impl Bd { 29 | pub fn new(decay: f32) -> Self { 30 | Self::from(decay) 31 | } 32 | } 33 | 34 | impl From for Bd { 35 | fn from(decay: f32) -> Self { 36 | let mut context = crate::AudioContextBuilder::::new().channels(2).build(); 37 | let input = context.add_mono_node(Pass {}); 38 | let env_amp = context.add_mono_node(EnvPerc::new().attack(0.003).decay(decay)); 39 | context.tags.insert("d", env_amp); 40 | let env_pitch = context.add_mono_node(EnvPerc::new().attack(0.01).decay(0.1)); 41 | let mul = context.add_stereo_node(Mul::new(50.)); 42 | let add = context.add_stereo_node(Add::new(60.)); 43 | let sin = context.add_mono_node(SinOsc::new()); 44 | let sin_amp = context.add_mono_node(Mul::new(0.)); 45 | context.chain(vec![sin, sin_amp, context.destination]); 46 | context.chain(vec![input, env_amp, sin_amp]); 47 | context.chain(vec![input, env_pitch, mul, add, sin]); 48 | 49 | Self { 50 | context, 51 | input, 52 | input_order: vec![], 53 | } 54 | } 55 | } 56 | 57 | impl Node for Bd { 58 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 59 | process_compound( 60 | inputs, 61 | &self.input_order, 62 | self.input, 63 | &mut self.context, 64 | output, 65 | ) 66 | } 67 | 68 | fn send_msg(&mut self, info: Message) { 69 | match info { 70 | Message::SetToNumber(0, value) => self.context.graph[self.context.tags["d"]] 71 | .node 72 | .send_msg(Message::SetToNumber(1, value)), 73 | Message::Index(i) => self.input_order.push(i), 74 | Message::IndexOrder(pos, index) => self.input_order.insert(pos, index), 75 | _ => {} 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /rs/synth/src/node/compound/hh.rs: -------------------------------------------------------------------------------- 1 | // output: noiz 42 >> mul ~env >> hpf 15000 1.0 >> mul 0.8; 2 | // ~env: ~trigger >> envperc 0.001 #decay; 3 | // ~trigger: ~input; 4 | use crate::{ 5 | envelope::EnvPerc, filter::ResonantHighPassFilter, operator::Mul, signal::Noise, AudioContext, 6 | Pass, 7 | }; 8 | use crate::{Buffer, Input, Message, Node}; 9 | use hashbrown::HashMap; 10 | 11 | use petgraph::graph::NodeIndex; 12 | 13 | use super::process_compound; 14 | 15 | pub struct Hh { 16 | input: NodeIndex, 17 | context: AudioContext, 18 | input_order: Vec, 19 | } 20 | 21 | impl Hh { 22 | pub fn new(decay: f32) -> Self { 23 | Self::from(decay) 24 | } 25 | } 26 | 27 | impl From for Hh { 28 | fn from(decay: f32) -> Self { 29 | let mut context = crate::AudioContextBuilder::::new().channels(2).build(); 30 | let input = context.add_mono_node(Pass {}); 31 | 32 | let source = context.add_mono_node(Noise::new(42)); 33 | let filter = context.add_mono_node(ResonantHighPassFilter::new().cutoff(15000.)); 34 | let amp = context.add_stereo_node(Mul::new(0.)); 35 | 36 | context.chain(vec![source, filter, amp, context.destination]); 37 | 38 | let env_amp = context.add_mono_node(EnvPerc::new().attack(0.003).decay(decay)); 39 | context.tags.insert("d", env_amp); 40 | context.chain(vec![input, env_amp, amp]); 41 | 42 | Self { 43 | context, 44 | input, 45 | input_order: vec![], 46 | } 47 | } 48 | } 49 | 50 | impl Node for Hh { 51 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 52 | process_compound( 53 | inputs, 54 | &self.input_order, 55 | self.input, 56 | &mut self.context, 57 | output, 58 | ); 59 | } 60 | 61 | fn send_msg(&mut self, info: Message) { 62 | match info { 63 | Message::SetToNumber(0, value) => self.context.graph[self.context.tags["d"]] 64 | .node 65 | .send_msg(Message::SetToNumber(1, value)), 66 | Message::Index(i) => self.input_order.push(i), 67 | Message::IndexOrder(pos, index) => self.input_order.insert(pos, index), 68 | _ => {} 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /rs/synth/src/node/compound/mod.rs: -------------------------------------------------------------------------------- 1 | mod bd; 2 | pub use bd::*; 3 | mod hh; 4 | pub use hh::*; 5 | mod sn; 6 | pub use sn::*; 7 | mod sawsynth; 8 | pub use sawsynth::*; 9 | mod squsynth; 10 | pub use squsynth::*; 11 | mod trisynth; 12 | pub use trisynth::*; 13 | 14 | use crate::{AudioContext, Buffer, Input}; 15 | use hashbrown::HashMap; 16 | use petgraph::graph::NodeIndex; 17 | 18 | fn process_compound( 19 | inputs: &mut HashMap>, 20 | input_order: &[usize], 21 | input: NodeIndex, 22 | context: &mut AudioContext, 23 | output: &mut [Buffer], 24 | ) { 25 | if inputs.len() == 1 { 26 | let main_input = inputs[&input_order[0]].buffers(); 27 | context.graph[input].buffers[0] = main_input[0].clone(); 28 | // self.context.graph[self.input].buffers[1] = main_input[1].clone(); 29 | let cout = context.next_block(); 30 | 31 | output[0][..N].copy_from_slice(&cout[0][..N]); 32 | output[1][..N].copy_from_slice(&cout[1][..N]); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /rs/synth/src/node/compound/sawsynth.rs: -------------------------------------------------------------------------------- 1 | // output: saw ~pitch >> mul ~env; 2 | // ~trigger: ~input; 3 | // ~pitch: ~trigger >> mul 261.626; 4 | // ~env: ~trigger >> envperc #attack #decay; 5 | use crate::{ 6 | envelope::EnvPerc, 7 | operator::Mul, 8 | // filter::ResonantHighPassFilter, 9 | oscillator::SawOsc, 10 | AudioContext, 11 | Pass, 12 | }; 13 | use crate::{Buffer, Input, Message, Node}; 14 | use hashbrown::HashMap; 15 | 16 | use petgraph::graph::NodeIndex; 17 | 18 | use super::process_compound; 19 | 20 | pub struct SawSynth { 21 | input: NodeIndex, 22 | context: AudioContext, 23 | input_order: Vec, 24 | } 25 | 26 | impl SawSynth { 27 | pub fn new(attack: f32, decay: f32) -> Self { 28 | let mut context = crate::AudioContextBuilder::::new().channels(2).build(); 29 | let input = context.add_mono_node(Pass {}); 30 | 31 | let source = context.add_mono_node(SawOsc::new()); 32 | let amp = context.add_stereo_node(Mul::new(0.)); 33 | 34 | context.chain(vec![source, amp, context.destination]); 35 | 36 | let env_amp = context.add_mono_node(EnvPerc::new().attack(attack).decay(decay)); 37 | context.tags.insert("env_amp", env_amp); 38 | context.chain(vec![input, env_amp, amp]); 39 | 40 | let pitch = context.add_stereo_node(Mul::new(261.63)); 41 | context.chain(vec![input, pitch, source]); 42 | 43 | Self { 44 | context, 45 | input, 46 | input_order: vec![], 47 | } 48 | } 49 | } 50 | 51 | impl Node for SawSynth { 52 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 53 | process_compound( 54 | inputs, 55 | &self.input_order, 56 | self.input, 57 | &mut self.context, 58 | output, 59 | ); 60 | } 61 | 62 | fn send_msg(&mut self, info: Message) { 63 | match info { 64 | Message::SetToNumber(pos, value) => match pos { 65 | 0 => self.context.graph[self.context.tags["env_amp"]] 66 | .node 67 | .send_msg(Message::SetToNumber(0, value)), 68 | 1 => self.context.graph[self.context.tags["env_amp"]] 69 | .node 70 | .send_msg(Message::SetToNumber(1, value)), 71 | _ => {} 72 | }, 73 | Message::Index(i) => self.input_order.push(i), 74 | Message::IndexOrder(pos, index) => self.input_order.insert(pos, index), 75 | _ => {} 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /rs/synth/src/node/compound/sn.rs: -------------------------------------------------------------------------------- 1 | // output: sin ~snpitch >> add ~no >> mul ~snenv >> hpf 5000 1.0; 2 | // ~no: noiz 42 >> mul 0.3; 3 | // ~snenv: ~sntriggee >> envperc 0.001 #decay; 4 | // ~snpitch: ~sntriggee >> envperc 0.001 0.1 >> mul 60 >> add 60; 5 | // ~sntriggee: ~input; 6 | 7 | use crate::{ 8 | // filter::ResonantHighPassFilter, 9 | envelope::EnvPerc, 10 | operator::{Add, Mul}, 11 | oscillator::SinOsc, 12 | signal::Noise, 13 | AudioContext, 14 | Pass, 15 | }; 16 | use crate::{Buffer, Input, Message, Node}; 17 | use hashbrown::HashMap; 18 | 19 | use petgraph::graph::NodeIndex; 20 | 21 | use super::process_compound; 22 | 23 | pub struct Sn { 24 | input: NodeIndex, 25 | context: AudioContext, 26 | input_order: Vec, 27 | } 28 | 29 | impl Sn { 30 | pub fn new(decay: f32) -> Self { 31 | Self::from(decay) 32 | } 33 | } 34 | 35 | impl From for Sn { 36 | fn from(decay: f32) -> Self { 37 | let mut context = crate::AudioContextBuilder::::new().channels(2).build(); 38 | let input = context.add_mono_node(Pass {}); 39 | let env_amp = context.add_mono_node(EnvPerc::new().attack(0.001).decay(decay)); 40 | context.tags.insert("d", env_amp); 41 | let env_pitch = context.add_mono_node(EnvPerc::new().attack(0.001).decay(0.1)); 42 | let mul = context.add_stereo_node(Mul::new(55.)); 43 | let add = context.add_stereo_node(Add::new(60.)); 44 | 45 | let sin = context.add_mono_node(SinOsc::new()); 46 | let mix = context.add_stereo_node(Add::new(0.)); 47 | let filter = context.add_stereo_node(Add::new(0.)); 48 | let amp = context.add_stereo_node(Mul::new(0.)); 49 | 50 | let noise = context.add_mono_node(Noise::new(42)); 51 | let noise_amp = context.add_stereo_node(Mul::new(0.3)); 52 | 53 | context.chain(vec![sin, mix, filter, amp, context.destination]); 54 | context.chain(vec![noise, noise_amp, mix]); 55 | context.chain(vec![input, env_amp, amp]); 56 | context.chain(vec![input, env_pitch, mul, add, sin]); 57 | 58 | Self { 59 | context, 60 | input, 61 | input_order: vec![], 62 | } 63 | } 64 | } 65 | 66 | impl Node for Sn { 67 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 68 | process_compound( 69 | inputs, 70 | &self.input_order, 71 | self.input, 72 | &mut self.context, 73 | output, 74 | ) 75 | } 76 | 77 | fn send_msg(&mut self, info: Message) { 78 | match info { 79 | Message::SetToNumber(0, value) => self.context.graph[self.context.tags["d"]] 80 | .node 81 | .send_msg(Message::SetToNumber(1, value)), 82 | Message::Index(i) => self.input_order.push(i), 83 | Message::IndexOrder(pos, index) => self.input_order.insert(pos, index), 84 | _ => {} 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /rs/synth/src/node/compound/squsynth.rs: -------------------------------------------------------------------------------- 1 | // output: saw ~pitch >> mul ~env; 2 | // ~trigger: ~input; 3 | // ~pitch: ~trigger >> mul 261.626; 4 | // ~env: ~trigger >> envperc #attack #decay; 5 | use crate::{envelope::EnvPerc, operator::Mul, oscillator::SquOsc, AudioContext, Pass}; 6 | use crate::{Buffer, Input, Message, Node}; 7 | use hashbrown::HashMap; 8 | 9 | use petgraph::graph::NodeIndex; 10 | 11 | use super::process_compound; 12 | 13 | pub struct SquSynth { 14 | input: NodeIndex, 15 | context: AudioContext, 16 | input_order: Vec, 17 | } 18 | 19 | impl SquSynth { 20 | pub fn new(attack: f32, decay: f32) -> Self { 21 | let mut context = crate::AudioContextBuilder::::new().channels(2).build(); 22 | let input = context.add_mono_node(Pass {}); 23 | 24 | let source = context.add_mono_node(SquOsc::new()); 25 | let amp = context.add_stereo_node(Mul::new(0.)); 26 | 27 | context.chain(vec![source, amp, context.destination]); 28 | 29 | let env_amp = context.add_mono_node(EnvPerc::new().attack(attack).decay(decay)); 30 | context.tags.insert("d", env_amp); 31 | context.chain(vec![input, env_amp, amp]); 32 | 33 | let pitch = context.add_stereo_node(Mul::new(261.63)); 34 | context.chain(vec![input, pitch, source]); 35 | 36 | Self { 37 | context, 38 | input, 39 | input_order: vec![], 40 | } 41 | } 42 | } 43 | 44 | impl Node for SquSynth { 45 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 46 | process_compound( 47 | inputs, 48 | &self.input_order, 49 | self.input, 50 | &mut self.context, 51 | output, 52 | ); 53 | } 54 | 55 | fn send_msg(&mut self, info: Message) { 56 | match info { 57 | Message::SetToNumber(pos @ 0..=1, value) => self.context.graph[self.context.tags["d"]] 58 | .node 59 | .send_msg(Message::SetToNumber(pos, value)), 60 | 61 | Message::Index(i) => self.input_order.push(i), 62 | Message::IndexOrder(pos, index) => self.input_order.insert(pos, index), 63 | _ => {} 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /rs/synth/src/node/compound/trisynth.rs: -------------------------------------------------------------------------------- 1 | // output: saw ~pitch >> mul ~env; 2 | // ~trigger: ~input; 3 | // ~pitch: ~trigger >> mul 261.626; 4 | // ~env: ~trigger >> envperc #attack #decay; 5 | use crate::{envelope::EnvPerc, operator::Mul, oscillator::TriOsc, AudioContext, Pass}; 6 | use crate::{Buffer, Input, Message, Node}; 7 | use hashbrown::HashMap; 8 | 9 | use petgraph::graph::NodeIndex; 10 | 11 | use super::process_compound; 12 | 13 | pub struct TriSynth { 14 | input: NodeIndex, 15 | context: AudioContext, 16 | input_order: Vec, 17 | } 18 | 19 | impl TriSynth { 20 | pub fn new(attack: f32, decay: f32) -> Self { 21 | let mut context = crate::AudioContextBuilder::::new().channels(2).build(); 22 | let input = context.add_mono_node(Pass {}); 23 | 24 | let source = context.add_mono_node(TriOsc::new()); 25 | let amp = context.add_stereo_node(Mul::new(0.)); 26 | 27 | context.chain(vec![source, amp, context.destination]); 28 | 29 | let env_amp = context.add_mono_node(EnvPerc::new().attack(attack).decay(decay)); 30 | context.tags.insert("d", env_amp); 31 | context.chain(vec![input, env_amp, amp]); 32 | 33 | let pitch = context.add_stereo_node(Mul::new(261.63)); 34 | context.chain(vec![input, pitch, source]); 35 | 36 | Self { 37 | context, 38 | input, 39 | input_order: vec![], 40 | } 41 | } 42 | } 43 | 44 | impl Node for TriSynth { 45 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 46 | process_compound( 47 | inputs, 48 | &self.input_order, 49 | self.input, 50 | &mut self.context, 51 | output, 52 | ); 53 | } 54 | 55 | fn send_msg(&mut self, info: Message) { 56 | match info { 57 | Message::SetToNumber(pos @ 0..=1, value) => self.context.graph[self.context.tags["d"]] 58 | .node 59 | .send_msg(Message::SetToNumber(pos, value)), 60 | 61 | Message::Index(i) => self.input_order.push(i), 62 | Message::IndexOrder(pos, index) => self.input_order.insert(pos, index), 63 | _ => {} 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /rs/synth/src/node/delay/delayms.rs: -------------------------------------------------------------------------------- 1 | use crate::{Buffer, Input, Message, Node}; 2 | use dasp_ring_buffer as ring_buffer; 3 | type Fixed = ring_buffer::Fixed>; 4 | use hashbrown::HashMap; 5 | #[derive(Debug, Clone)] 6 | pub struct DelayMs { 7 | buf: Vec, 8 | sr: usize, 9 | input_order: Vec, 10 | delay_n: usize, 11 | } 12 | 13 | impl Default for DelayMs { 14 | fn default() -> Self { 15 | Self::new() 16 | } 17 | } 18 | 19 | impl DelayMs { 20 | pub fn new() -> Self { 21 | Self { 22 | buf: vec![], 23 | delay_n: 1, 24 | sr: 44100, 25 | input_order: vec![], 26 | } 27 | } 28 | 29 | pub fn delay(self, delay: f32, chan: u8) -> Self { 30 | let delay_n = ((delay / 1000. * self.sr as f32) as usize).max(1); 31 | let buf = vec![Fixed::from(vec![0.0; delay_n]); chan as usize]; 32 | 33 | Self { 34 | buf, 35 | delay_n, 36 | ..self 37 | } 38 | } 39 | 40 | pub fn sr(self, sr: usize) -> Self { 41 | Self { sr, ..self } 42 | } 43 | } 44 | 45 | impl Node for DelayMs { 46 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 47 | let main_input = inputs.values().next().unwrap(); 48 | if !(1..=2).contains(&main_input.buffers().len()) { 49 | return; 50 | } 51 | 52 | match inputs.len() { 53 | 1 => { 54 | // no modulation 55 | match self.delay_n { 56 | // equal to a pass node 57 | 0 => output[0].copy_from_slice(&main_input.buffers()[0]), 58 | _ => { 59 | let iter = self 60 | .buf 61 | .iter_mut() 62 | .zip(output.iter_mut()) 63 | .zip(main_input.buffers()); 64 | 65 | for ((fixed, out_buf), main_buf) in iter { 66 | for (out, main) in out_buf.iter_mut().zip(main_buf.iter()) { 67 | *out = fixed.push(*main); 68 | } 69 | } 70 | } 71 | } 72 | } 73 | 2 => { 74 | let main_input = &inputs[&self.input_order[0]]; // can panic if there is no id 75 | let ref_input = &inputs[&self.input_order[1]]; // can panic if there is no id 76 | 77 | let mod_buf = &mut ref_input.buffers(); 78 | for i in 0..N { 79 | let mut pos = -mod_buf[0][i] / 1000. * self.sr as f32; 80 | while pos < 0. { 81 | pos += self.buf[0].len() as f32; 82 | } 83 | let pos_int = pos.floor() as usize; 84 | let pos_frac = pos.fract(); 85 | 86 | let iter = self 87 | .buf 88 | .iter_mut() 89 | .zip(output.iter_mut()) 90 | .zip(main_input.buffers()); 91 | 92 | for ((fixed, out_buf), main_buf) in iter { 93 | out_buf[i] = fixed.get(pos_int) * pos_frac 94 | + fixed.get(pos_int + 1) * (1. - pos_frac); 95 | fixed.push(main_buf[i]); 96 | } 97 | } 98 | 99 | // output[1][i] = self.buf2.get(pos_int) * pos_frac + self.buf2.get(pos_int+1) * (1.-pos_frac); 100 | 101 | // self.buf2.push(main_input.buffers()[1][i]); 102 | } 103 | _ => (), 104 | } 105 | } 106 | 107 | fn send_msg(&mut self, info: Message) { 108 | match info { 109 | Message::SetToNumber(0, value) => { 110 | let delay_n = (value / 1000. * self.sr as f32) as usize; 111 | self.delay_n = delay_n; 112 | 113 | if delay_n == 0 { 114 | self.buf.clear(); 115 | } else { 116 | let chan = self.buf.len(); 117 | self.buf = vec![Fixed::from(vec![0.0; delay_n]); chan]; 118 | }; 119 | } 120 | Message::Index(i) => self.input_order.push(i), 121 | Message::IndexOrder(pos, index) => self.input_order.insert(pos, index), 122 | Message::ResetOrder => { 123 | self.input_order.clear(); 124 | } 125 | _ => {} 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /rs/synth/src/node/delay/delayn.rs: -------------------------------------------------------------------------------- 1 | use crate::{Buffer, Input, Message, Node}; 2 | use dasp_ring_buffer as ring_buffer; 3 | use hashbrown::HashMap; 4 | type Fixed = ring_buffer::Fixed>; 5 | 6 | #[derive(Debug, Clone)] 7 | pub struct DelayN { 8 | buf: Fixed, 9 | delay_n: usize, 10 | input_order: Vec, 11 | } 12 | 13 | impl DelayN { 14 | pub fn new(n: usize) -> Self { 15 | let delay_n = n; 16 | let init_n = match n { 17 | 0 => 1, 18 | _ => n, 19 | }; 20 | let buf = ring_buffer::Fixed::from(vec![0.0; init_n]); 21 | Self { 22 | buf, 23 | delay_n, 24 | input_order: Vec::new(), 25 | } 26 | } 27 | } 28 | 29 | impl Node for DelayN { 30 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 31 | if inputs.len() == 1 { 32 | let main_input = inputs.values_mut().next().unwrap(); 33 | if self.delay_n != 0 { 34 | for i in 0..N { 35 | output[0][i] = self.buf.push(main_input.buffers()[0][i]); 36 | if main_input.buffers().len() == 1 && output.len() == 2 { 37 | output[1][i] = output[0][i]; 38 | } 39 | } 40 | } else { 41 | // same as Pass node 42 | let Some(input) = inputs.values().next() else { 43 | return; 44 | }; 45 | 46 | match (input.buffers(), &mut *output) { 47 | ([in_buf], [ref mut out_a, ref mut out_b]) => { 48 | out_a.copy_from_slice(in_buf); 49 | out_b.copy_from_slice(in_buf); 50 | } 51 | _ => { 52 | for (out_buf, in_buf) in output.iter_mut().zip(input.buffers()) { 53 | out_buf.copy_from_slice(in_buf); 54 | } 55 | } 56 | } 57 | } 58 | } 59 | } 60 | 61 | fn send_msg(&mut self, info: Message) { 62 | match info { 63 | Message::SetToNumber(0, value) => { 64 | self.delay_n = value as usize; 65 | self.buf = Fixed::from(vec![0.0; self.delay_n]); 66 | // buf2 = Fixed::from(vec![0.0; delay_n]); 67 | // self.buf.set_first(self.delay_n); 68 | // self.buf2.set_first(delay_n); 69 | } 70 | Message::Index(i) => self.input_order.push(i), 71 | Message::IndexOrder(pos, index) => self.input_order.insert(pos, index), 72 | Message::ResetOrder => { 73 | self.input_order.clear(); 74 | } 75 | _ => {} 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /rs/synth/src/node/delay/mod.rs: -------------------------------------------------------------------------------- 1 | mod delayn; 2 | pub use delayn::*; 3 | mod delayms; 4 | pub use delayms::*; 5 | -------------------------------------------------------------------------------- /rs/synth/src/node/dynamic/eval.rs: -------------------------------------------------------------------------------- 1 | use crate::{BoxedNodeSend, Buffer, Input, Message, Node, NodeData}; 2 | use hashbrown::HashMap; 3 | // use evalexpr::*; 4 | use fasteval::Compiler; 5 | use fasteval::Evaler; // use this trait so we can call eval(). 6 | use std::collections::BTreeMap; // use this trait so we can call compile(). 7 | // use fasteval::eval_compiled; 8 | 9 | pub struct Eval { 10 | pub sr: usize, 11 | pub bpm: f32, 12 | phase: usize, 13 | // code: String, 14 | // precompiled: evalexpr::Node, 15 | var: Vec, 16 | map: BTreeMap, 17 | compiled: Vec, 18 | parser: fasteval::Parser, 19 | slab: Vec, 20 | input_order: Vec, 21 | } 22 | 23 | impl Default for Eval { 24 | fn default() -> Self { 25 | Self::new() 26 | } 27 | } 28 | 29 | impl Eval { 30 | pub fn new() -> Self { 31 | let parser = fasteval::Parser::new(); 32 | // let mut slab = fasteval::Slab::new(); 33 | 34 | let mut map: BTreeMap = BTreeMap::new(); 35 | map.insert("x".to_string(), 0.0); 36 | map.insert("y".to_string(), 0.0); 37 | map.insert("z".to_string(), 0.0); 38 | map.insert("sr".to_string(), 44100.); 39 | 40 | // let compiled = parser.parse("0.0", &mut slab.ps).unwrap().from(&slab.ps).compile(&slab.ps, &mut slab.cs); 41 | 42 | Self { 43 | sr: 44100, 44 | bpm: 120., 45 | phase: 0, 46 | map, 47 | parser, 48 | var: vec![], 49 | slab: vec![], 50 | compiled: vec![], 51 | input_order: Vec::new(), 52 | } 53 | } 54 | 55 | pub fn sr(mut self, sr: usize) -> Self { 56 | self.map.insert("sr".to_string(), sr as f64); 57 | // self.context.set_value("sr".to_owned(), Value::Int(sr as i64)).unwrap(); 58 | Self { sr, ..self } 59 | } 60 | 61 | pub fn bpm(self, bpm: f32) -> Self { 62 | Self { bpm, ..self } 63 | } 64 | 65 | pub fn code(mut self, code: &str) -> Self { 66 | self.apply_code(code); 67 | Self { ..self } 68 | } 69 | 70 | fn apply_code(&mut self, code: &str) { 71 | let lines = code.split(';'); 72 | for line in lines { 73 | let mut slab = fasteval::Slab::new(); 74 | let mut assign = line.split(":="); 75 | if line.contains(":=") { 76 | self.var 77 | .push(assign.next().unwrap().replace([' ', '\t', '\n'], "")); 78 | } 79 | let compiled = self 80 | .parser 81 | .parse(assign.next().unwrap(), &mut slab.ps) 82 | .unwrap() 83 | .from(&slab.ps) 84 | .compile(&slab.ps, &mut slab.cs); 85 | self.slab.push(slab); 86 | self.compiled.push(compiled); 87 | } 88 | } 89 | } 90 | 91 | impl Node for Eval { 92 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 93 | for i in 0..N { 94 | if !inputs.is_empty() { 95 | self.map.insert( 96 | "in".to_owned(), 97 | inputs[&self.input_order[0]].buffers()[0][i] as f64, 98 | ); 99 | } 100 | self.map.insert("phase".to_owned(), self.phase as f64); 101 | // output[0][i] = self.compiled.eval(&self.slab, &mut self.map).unwrap() as f32; 102 | // output[0][i] = fasteval::ez_eval(&self.code, &mut self.map).unwrap() as f32; 103 | 104 | for (j, ins) in self.compiled.iter().enumerate() { 105 | // println!("i is {}", j); 106 | if j < self.compiled.len() - 1 { 107 | let v = ins.eval(&self.slab[j], &mut self.map).unwrap(); 108 | self.map.insert(self.var[j].clone(), v); 109 | } else { 110 | let v = ins.eval(&self.slab[j], &mut self.map).unwrap(); 111 | output[0][i] = v as f32; 112 | } 113 | } 114 | self.phase += 1; 115 | } 116 | } 117 | 118 | fn send_msg(&mut self, info: Message) { 119 | match info { 120 | Message::SetToSymbol(0, code) => { 121 | self.slab.clear(); 122 | self.var.clear(); 123 | self.compiled.clear(); 124 | self.apply_code(&code); 125 | // self.code(s); 126 | // self.compiled = self.parser.parse(&s, &mut self.slab.ps).unwrap().from(&self.slab.ps) 127 | // .compile(&self.slab.ps, &mut self.slab.cs); 128 | // self.code = s 129 | // self.precompiled = build_operator_tree(&s).unwrap() 130 | } 131 | Message::Index(i) => self.input_order.push(i), 132 | Message::IndexOrder(pos, index) => self.input_order.insert(pos, index), 133 | Message::ResetOrder => { 134 | self.input_order.clear(); 135 | } 136 | _ => {} 137 | } 138 | } 139 | 140 | fn to_boxed_nodedata(self, channels: usize) -> NodeData, N> { 141 | // self.scope.push("sr", self.sr as f32); 142 | NodeData::multi_chan_node(channels, BoxedNodeSend::::new(self)) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /rs/synth/src/node/dynamic/expr.rs: -------------------------------------------------------------------------------- 1 | use crate::{Buffer, Input, Node, BoxedNodeSend, NodeData, Message}; 2 | use hashbrown::HashMap; 3 | use evalexpr::*; 4 | 5 | pub struct Expr { 6 | sr: usize, 7 | bpm: f32, 8 | phase_n: usize, 9 | precompiled: evalexpr::Node, 10 | context: HashMapContext, 11 | input_order: Vec 12 | } 13 | 14 | impl Expr { 15 | 16 | pub fn new() -> Self { 17 | 18 | let mut context = math_consts_context!( 19 | PI, 20 | E 21 | ).unwrap(); 22 | 23 | context.set_value("sr".to_owned(), Value::Int(44100)).unwrap(); 24 | context.set_value("x".to_owned(), Value::Float(0.0)).unwrap(); 25 | context.set_value("y".to_owned(), Value::Float(0.0)).unwrap(); 26 | // context.set_value("z".to_owned(), Value::Float(0.0)).unwrap(); 27 | 28 | // let context = HashMapContext::new(); 29 | 30 | let precompiled = build_operator_tree("").unwrap(); 31 | 32 | Self { 33 | sr: 44100, 34 | bpm: 120., 35 | phase_n: 0, 36 | context, 37 | precompiled, 38 | input_order: Vec::new() 39 | } 40 | } 41 | 42 | pub fn sr(mut self, sr:usize) -> Self { 43 | self.context.set_value("sr".to_owned(), Value::Int(sr as i64)).unwrap(); 44 | Self {sr, ..self} 45 | } 46 | 47 | pub fn bpm(self, bpm: f32) -> Self { 48 | Self {bpm, ..self} 49 | } 50 | 51 | pub fn code(self, code: String) -> Self { 52 | let precompiled = build_operator_tree(&code).unwrap(); 53 | Self {precompiled, ..self} 54 | } 55 | } 56 | 57 | impl Node for Expr { 58 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 59 | 60 | // self.context.set_value("phase".to_owned(), Value::Float(self.phase_n as f64)).unwrap(); 61 | 62 | for i in 0..N { 63 | if inputs.len() > 0 { 64 | self.context.set_value("in".to_owned(), 65 | evalexpr::Value::Float( 66 | inputs[&self.input_order[0]].buffers()[0][i] as f64 )).unwrap(); 67 | } 68 | output[0][i] = self.precompiled.eval_float_with_context_mut(&mut self.context).unwrap() as f32; 69 | self.phase_n += 1; 70 | } 71 | } 72 | 73 | fn send_msg(&mut self, info: Message) { 74 | 75 | match info { 76 | Message::SetToSymbol(0, s) => self.precompiled = build_operator_tree(&s).unwrap(), 77 | Message::Index(i) => self.input_order.push(i), 78 | Message::IndexOrder(pos, index) => self.input_order.insert(pos, index), 79 | Message::ResetOrder => self.input_order.clear(), 80 | _ => {} 81 | } 82 | } 83 | 84 | fn to_boxed_nodedata(mut self, channels: usize) -> NodeData, N> { 85 | // self.scope.push("sr", self.sr as f32); 86 | NodeData::multi_chan_node(channels, BoxedNodeSend::::new( self ) ) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /rs/synth/src/node/dynamic/meta.rs: -------------------------------------------------------------------------------- 1 | use crate::{BoxedNodeSend, Buffer, Input, Message, Node, NodeData}; 2 | use hashbrown::HashMap; 3 | use rhai::{Array, Dynamic, Engine, OptimizationLevel, Scope, AST}; 4 | 5 | pub struct Meta { 6 | sr: usize, 7 | // phase: usize, 8 | pub backup: String, 9 | ast: AST, 10 | scope: Scope<'static>, 11 | engine: Engine, 12 | input_order: Vec, 13 | } 14 | 15 | impl Default for Meta { 16 | fn default() -> Self { 17 | Self::new() 18 | } 19 | } 20 | 21 | impl Meta { 22 | pub fn new() -> Self { 23 | let phase: usize = 0; 24 | let mut scope = Scope::new(); 25 | let output = Vec::::with_capacity(N); 26 | 27 | scope 28 | .push("phase", phase as f32) 29 | .push("x0", 0.0_f32) 30 | .push("x1", 0.0_f32) 31 | .push("x2", 0.0_f32) 32 | .push("x3", 0.0_f32) 33 | .push("y0", 0.0_f32) 34 | .push("y1", 0.0_f32) 35 | .push("y2", 0.0_f32) 36 | .push("y3", 0.0_f32) 37 | .push("z0", 0.0_f32) 38 | .push("z1", 0.0_f32) 39 | .push("z2", 0.0_f32) 40 | .push("z3", 0.0_f32) 41 | .push("a", 0.0_f32) 42 | .push("b", 0.0_f32) 43 | .push("c", 0.0_f32) 44 | .push("d", 0.0_f32) 45 | .push("e", 0.0_f32) 46 | .push("f", 0.0_f32) 47 | .push("g", 0.0_f32) 48 | .push("h", 0.0_f32) 49 | .push("i", 0.0_f32) 50 | .push("j", 0.0_f32) 51 | .push("k", 0.0_f32) 52 | .push("l", 0.0_f32) 53 | .push("m", 0.0_f32) 54 | .push("n", 0.0_f32) 55 | .push("o", 0.0_f32) 56 | .push("p", 0.0_f32) 57 | .push("q", 0.0_f32) 58 | .push("r", 0.0_f32) 59 | .push("s", 0.0_f32) 60 | .push("t", 0.0_f32) 61 | .push("u", 0.0_f32) 62 | .push("v", 0.0_f32) 63 | .push("w", 0.0_f32) 64 | .push("x", 0.0_f32) 65 | .push("y", 0.0_f32) 66 | .push("z", 0.0_f32) 67 | .push("freq", 0.0_f32) 68 | .push("freq2", 0.0_f32) 69 | .push("output", output); 70 | 71 | let mut engine = Engine::new(); 72 | engine.set_optimization_level(OptimizationLevel::Full); 73 | let ast = engine.compile("").unwrap(); 74 | 75 | Self { 76 | sr: 44100, 77 | engine, 78 | scope, 79 | backup: "".to_owned(), 80 | ast, 81 | // phase, 82 | input_order: Vec::new(), 83 | } 84 | } 85 | 86 | pub fn sr(self, sr: usize) -> Self { 87 | Self { sr, ..self } 88 | } 89 | 90 | pub fn code(mut self, code: &str) -> Self { 91 | if let Ok(a) = self.engine.compile(code) { 92 | self.ast = a; 93 | }; 94 | self 95 | } 96 | } 97 | 98 | impl Node for Meta { 99 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 100 | if !inputs.is_empty() { 101 | let arr: Array = inputs[&self.input_order[0]].buffers()[0] 102 | .iter() 103 | .map(|f| Dynamic::from_float(*f)) 104 | .collect(); 105 | 106 | self.scope.set_or_push("input", arr); 107 | // self.engine.optimize_ast(); 108 | } 109 | 110 | match self 111 | .engine 112 | .eval_ast_with_scope::(&mut self.scope, &self.ast) 113 | { 114 | Ok(result) => { 115 | for (out, res) in output[0].iter_mut().zip(result.iter()) { 116 | if let Ok(v) = res.as_float() { 117 | *out = v; 118 | }; 119 | } 120 | } 121 | Err(e) => { 122 | // TODO What do we do with this Box? 123 | _ = e; 124 | } 125 | } 126 | // self.phase += N; 127 | } 128 | fn send_msg(&mut self, info: Message) { 129 | match info { 130 | Message::SetToSymbol(0, s) => { 131 | if let Ok(a) = self.engine.compile(s) { 132 | self.ast = a; 133 | } 134 | } 135 | Message::Index(i) => self.input_order.push(i), 136 | Message::IndexOrder(pos, index) => self.input_order.insert(pos, index), 137 | Message::ResetOrder => { 138 | self.input_order.clear(); 139 | } 140 | _ => {} 141 | } 142 | } 143 | 144 | fn to_boxed_nodedata(mut self, channels: usize) -> NodeData, N> { 145 | self.scope.push("sr", self.sr as f32); 146 | NodeData::multi_chan_node(channels, BoxedNodeSend::::new(self)) 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /rs/synth/src/node/dynamic/mod.rs: -------------------------------------------------------------------------------- 1 | mod meta; 2 | pub use meta::*; 3 | // mod expr; pub use expr::*; 4 | mod eval; 5 | pub use eval::*; 6 | -------------------------------------------------------------------------------- /rs/synth/src/node/effect/balance.rs: -------------------------------------------------------------------------------- 1 | use crate::{Buffer, Input, Message, Node}; 2 | use hashbrown::HashMap; 3 | #[derive(Debug, Clone)] 4 | pub struct Balance { 5 | input_order: Vec, 6 | } 7 | 8 | impl Default for Balance { 9 | fn default() -> Self { 10 | Self::new() 11 | } 12 | } 13 | 14 | impl Balance { 15 | pub fn new() -> Self { 16 | Self { 17 | input_order: vec![], 18 | } 19 | } 20 | } 21 | 22 | impl Node for Balance { 23 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 24 | // println!("inputs {:?} self.input_order {:?}", inputs, self.input_order); 25 | // panic!(); 26 | if inputs.len() == 2 { 27 | let left = &inputs[&self.input_order[0]]; 28 | let right = &inputs[&self.input_order[1]]; 29 | output[0] = left.buffers()[0].clone(); 30 | output[1] = right.buffers()[0].clone(); 31 | } 32 | } 33 | fn send_msg(&mut self, info: Message) { 34 | match info { 35 | Message::Index(i) => self.input_order.push(i), 36 | Message::IndexOrder(pos, index) => self.input_order.insert(pos, index), 37 | Message::ResetOrder => { 38 | self.input_order.clear(); 39 | } 40 | _ => {} 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /rs/synth/src/node/effect/mod.rs: -------------------------------------------------------------------------------- 1 | mod plate; 2 | pub use plate::*; 3 | mod balance; 4 | pub use balance::*; 5 | mod pan; 6 | pub use pan::*; 7 | 8 | // mod reverb; pub use reverb::*; 9 | // pub mod reverb; pub use reverb::*; 10 | -------------------------------------------------------------------------------- /rs/synth/src/node/effect/pan.rs: -------------------------------------------------------------------------------- 1 | use crate::{Buffer, Input, Message, Node}; 2 | use hashbrown::HashMap; 3 | 4 | #[derive(Debug, Clone)] 5 | pub struct Pan { 6 | pan_pos: f32, // Pan position (-1.0 to 1.0) 7 | input_order: Vec, 8 | } 9 | 10 | impl Pan { 11 | pub fn new(pan_pos: f32) -> Self { 12 | Self { 13 | pan_pos, 14 | input_order: vec![], 15 | } 16 | } 17 | } 18 | 19 | impl Node for Pan { 20 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 21 | // no modulation 22 | if inputs.len() == 1 { 23 | let main_input = inputs.values_mut().next().unwrap(); 24 | let input_buffers = main_input.buffers(); 25 | 26 | let pan_norm = (self.pan_pos + 1.0) / 2.0; // Normalize pan position to 0.0 to 1.0 27 | let left_gain = (1.0 - pan_norm).sqrt(); // Left channel gain 28 | let right_gain = pan_norm.sqrt(); // Right channel gain 29 | 30 | let (left, right) = output.split_at_mut(1); 31 | for ((left, right), sample) in left[0] 32 | .iter_mut() 33 | .zip(right[0].iter_mut()) 34 | .zip(input_buffers[0].iter()) 35 | { 36 | *left = left_gain * sample; // Left channel 37 | *right = right_gain * sample; // Right channel 38 | } 39 | } else { 40 | let ref_input = &inputs[&self.input_order[1]]; // can panic if there is no id 41 | let main_input = &inputs[&self.input_order[0]]; // can panic if there is no id 42 | 43 | let ref_main = ref_input.buffers()[0] 44 | .iter() 45 | .zip(main_input.buffers()[0].iter()); 46 | let (left, right) = output.split_at_mut(1); 47 | let outs = left[0].iter_mut().zip(right[0].iter_mut()); 48 | 49 | for ((left, right), (mod_pan, main)) in outs.zip(ref_main) { 50 | let pan_norm = (mod_pan + 1.0) / 2.0; 51 | let left_gain = (1.0 - pan_norm).sqrt(); // Left channel gain 52 | let right_gain = pan_norm.sqrt(); // Right channel gain 53 | *left = left_gain * main; // Left channel 54 | *right = right_gain * main; // Right channel 55 | } 56 | } 57 | } 58 | 59 | fn send_msg(&mut self, info: Message) { 60 | match info { 61 | // Clamp pan position within valid range 62 | Message::SetToNumber(0, value) => self.pan_pos = value.clamp(-1.0, 1.0), 63 | Message::Index(i) => self.input_order.push(i), 64 | Message::IndexOrder(pos, index) => self.input_order.insert(pos, index), 65 | Message::ResetOrder => { 66 | self.input_order.clear(); 67 | } 68 | _ => {} 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /rs/synth/src/node/effect/reverb.rs: -------------------------------------------------------------------------------- 1 | use crate::{Buffer, Input, Node, BoxedNodeSend, NodeData, Message}; 2 | use freeverb::*; 3 | use hashbrown::HashMap; 4 | use petgraph::graph::NodeIndex; 5 | 6 | pub struct Reverb { 7 | fv: freeverb::Freeverb, 8 | input_order: Vec, 9 | } 10 | 11 | impl Reverb { 12 | pub fn new() -> Self { 13 | let fv = freeverb::Freeverb::new(44100); 14 | Self { 15 | fv, 16 | input_order: Vec::new(), 17 | } 18 | } 19 | pub fn sr(self, sr: usize) -> Self { 20 | let fv = freeverb::Freeverb::new(sr); 21 | Self { 22 | fv, ..self 23 | } 24 | } 25 | } 26 | 27 | impl Node for Reverb { 28 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 29 | // output 30 | for i in 0..N { 31 | // pass test 32 | // output[0][i] = inputs[&self.input_order[0]].buffers()[0][i]; 33 | let out = self.fv.tick((inputs[&self.input_order[0]].buffers()[0][i] as f64, inputs[&self.input_order[0]].buffers()[1][i] as f64)); 34 | output[0][i] = out.0 as f32; 35 | output[1][i] = out.1 as f32; 36 | } 37 | } 38 | 39 | fn send_msg(&mut self, info: Message) { 40 | match info { 41 | Message::SetToNumber(pos, value) => { 42 | match pos { 43 | 0 => { 44 | self.fv.set_dampening(value as f64) 45 | }, 46 | 1 => { // set_room_size 47 | self.fv.set_room_size(value as f64) 48 | }, 49 | // 2 => { 50 | // self.fv.set_freeze(value as f64) 51 | // }, 52 | 2 => { 53 | self.fv.set_width(value as f64) 54 | }, 55 | 3 => { 56 | self.fv.set_wet(value as f64) 57 | }, 58 | 4 => { 59 | self.fv.set_dry(value as f64) 60 | }, 61 | _ => {} 62 | } 63 | }, 64 | Message::Index(i) => { 65 | self.input_order.push(i) 66 | }, 67 | Message::IndexOrder(pos, index) => { 68 | self.input_order.insert(pos, index) 69 | }, 70 | Message::ResetOrder => { 71 | self.input_order.clear(); 72 | }, 73 | _ => {} 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /rs/synth/src/node/envelope/envperc.rs: -------------------------------------------------------------------------------- 1 | use crate::{Buffer, Input, Message, Node}; 2 | use hashbrown::HashMap; 3 | #[derive(Debug, Clone)] 4 | pub struct EnvPerc { 5 | attack: f32, 6 | decay: f32, 7 | pos: usize, 8 | scale: f32, 9 | sr: usize, 10 | input_order: Vec, 11 | } 12 | 13 | impl Default for EnvPerc { 14 | fn default() -> Self { 15 | Self::new() 16 | } 17 | } 18 | 19 | impl EnvPerc { 20 | pub fn new() -> Self { 21 | Self { 22 | attack: 0.01, 23 | decay: 0.1, 24 | pos: 0, 25 | scale: 1.0, 26 | sr: 44100, 27 | input_order: vec![], 28 | } 29 | } 30 | 31 | pub fn attack(self, attack: f32) -> Self { 32 | Self { attack, ..self } 33 | } 34 | pub fn decay(self, decay: f32) -> Self { 35 | Self { decay, ..self } 36 | } 37 | pub fn scale(self, scale: f32) -> Self { 38 | Self { scale, ..self } 39 | } 40 | pub fn sr(self, sr: usize) -> Self { 41 | Self { sr, ..self } 42 | } 43 | } 44 | 45 | impl Node for EnvPerc { 46 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 47 | if inputs.len() == 1 { 48 | let attack_len = (self.attack * self.sr as f32) as usize; 49 | let decay_len = (self.decay * self.sr as f32) as usize; 50 | let dur = attack_len + decay_len; 51 | let buf = &mut inputs[&self.input_order[0]].buffers(); 52 | 53 | for (input, out) in buf[0].iter().zip(output[0].iter_mut()) { 54 | if *input > 0.0 { 55 | self.pos = 0; 56 | self.scale = *input; 57 | } 58 | if self.pos <= attack_len { 59 | if attack_len == 0 { 60 | *out = 0.0; 61 | } else { 62 | *out = self.pos as f32 / attack_len as f32; 63 | } 64 | } else if self.pos > attack_len && self.pos <= dur { 65 | if decay_len == 0 { 66 | *out = 0.0; 67 | } else { 68 | *out = (dur - self.pos) as f32 / decay_len as f32; 69 | } 70 | } else { 71 | *out = 0.0 72 | } 73 | // println!("{}", output[0][i]); 74 | *out *= self.scale; 75 | self.pos += 1; 76 | } 77 | } 78 | } 79 | 80 | fn send_msg(&mut self, info: Message) { 81 | match info { 82 | Message::SetToNumber(pos, value) => match pos { 83 | 0 => self.attack = value, 84 | 1 => self.decay = value, 85 | _ => {} 86 | }, 87 | Message::Index(i) => self.input_order.push(i), 88 | Message::IndexOrder(pos, index) => self.input_order.insert(pos, index), 89 | Message::ResetOrder => { 90 | self.input_order.clear(); 91 | } 92 | _ => {} 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /rs/synth/src/node/envelope/mod.rs: -------------------------------------------------------------------------------- 1 | mod envperc; 2 | pub use envperc::*; 3 | mod adsr; 4 | pub use adsr::*; 5 | -------------------------------------------------------------------------------- /rs/synth/src/node/filter/apfmsgain.rs: -------------------------------------------------------------------------------- 1 | use crate::{Buffer, Input, Message, Node}; 2 | use dasp_ring_buffer as ring_buffer; 3 | use hashbrown::HashMap; 4 | type Fixed = ring_buffer::Fixed>; 5 | 6 | pub struct AllPassFilterGain { 7 | gain: f32, 8 | bufx: Fixed, 9 | bufy: Fixed, 10 | sr: usize, 11 | input_order: Vec, 12 | } 13 | 14 | impl Default for AllPassFilterGain { 15 | fn default() -> Self { 16 | Self::new() 17 | } 18 | } 19 | 20 | impl AllPassFilterGain { 21 | pub fn new() -> Self { 22 | Self { 23 | gain: 0.5, 24 | sr: 44100, 25 | bufx: ring_buffer::Fixed::from(vec![0.0]), 26 | bufy: ring_buffer::Fixed::from(vec![0.0]), 27 | input_order: Vec::new(), 28 | } 29 | } 30 | 31 | pub fn delay(self, delay: f32) -> Self { 32 | let size = if delay == 0.0 { 33 | 3. * self.sr as f32 34 | } else { 35 | delay / 1000. * self.sr as f32 36 | } as usize; 37 | Self { 38 | bufx: ring_buffer::Fixed::from(vec![0.0; size]), 39 | bufy: ring_buffer::Fixed::from(vec![0.0; size]), 40 | ..self 41 | } 42 | } 43 | 44 | pub fn gain(self, gain: f32) -> Self { 45 | Self { gain, ..self } 46 | } 47 | 48 | pub fn sr(self, sr: usize) -> Self { 49 | Self { sr, ..self } 50 | } 51 | } 52 | 53 | impl Node for AllPassFilterGain { 54 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 55 | // println!("inputs[1] {:?}", inputs[1].buffers()); 56 | match inputs.len() { 57 | 1 => { 58 | for i in 0..N { 59 | // println!("{:?}", self.buf); 60 | let xn = inputs[&self.input_order[0]].buffers()[0][i]; 61 | let yn = -self.gain * xn + self.bufx[0] + self.gain * self.bufy[0]; 62 | 63 | // save new input to ring buffer 64 | self.bufx.push(xn); 65 | self.bufy.push(yn); 66 | output[0][i] = yn; 67 | } 68 | } 69 | 2 => { 70 | let main_input = &inputs[&self.input_order[0]]; // can panic if there is no id 71 | let ref_input = &inputs[&self.input_order[1]]; // can panic if there is no id 72 | 73 | for ((out, xn), mod_buf) in output[0] 74 | .iter_mut() 75 | .zip(main_input.buffers()[0].iter()) 76 | .zip(ref_input.buffers()[0].iter()) 77 | { 78 | let mut pos = -mod_buf / 1000. * self.sr as f32; 79 | while pos < 0. { 80 | pos += self.bufx.len() as f32; 81 | } 82 | let pos_int = pos.floor() as usize; 83 | let pos_frac = pos.fract(); 84 | 85 | let xdelay = self.bufx.get(pos_int) * pos_frac 86 | + self.bufx.get(pos_int + 1) * (1. - pos_frac); 87 | let ydelay = self.bufy.get(pos_int) * pos_frac 88 | + self.bufy.get(pos_int + 1) * (1. - pos_frac); 89 | 90 | let yn = -self.gain * xn + xdelay + self.gain * ydelay; 91 | 92 | self.bufx.push(*xn); 93 | self.bufy.push(yn); 94 | *out = yn; 95 | } 96 | } 97 | _ => (), 98 | } 99 | } 100 | 101 | fn send_msg(&mut self, info: Message) { 102 | match info { 103 | Message::SetToNumber(pos, value) => match pos { 104 | 0 => { 105 | let delay_n = (value / 1000. * self.sr as f32) as usize; 106 | self.bufx.set_first(delay_n); 107 | self.bufy.set_first(delay_n); 108 | } 109 | 1 => self.gain = value, 110 | _ => {} 111 | }, 112 | Message::Index(i) => self.input_order.push(i), 113 | Message::IndexOrder(pos, index) => self.input_order.insert(pos, index), 114 | 115 | Message::ResetOrder => { 116 | self.input_order.clear(); 117 | } 118 | _ => {} 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /rs/synth/src/node/filter/mod.rs: -------------------------------------------------------------------------------- 1 | mod rlpf; 2 | pub use rlpf::*; 3 | mod onepole; 4 | pub use onepole::*; 5 | mod apfmsgain; 6 | pub use apfmsgain::*; 7 | mod rhpf; 8 | pub use rhpf::*; 9 | -------------------------------------------------------------------------------- /rs/synth/src/node/filter/onepole.rs: -------------------------------------------------------------------------------- 1 | use crate::{Buffer, Input, Message, Node}; 2 | use hashbrown::HashMap; 3 | #[derive(Debug, Clone)] 4 | pub struct OnePole { 5 | pub a: f32, 6 | pub b: f32, 7 | y1: f32, 8 | input_order: Vec, 9 | } 10 | 11 | impl OnePole { 12 | pub fn new(rate: f32) -> Self { 13 | Self::from(rate) 14 | } 15 | } 16 | 17 | impl From for OnePole { 18 | fn from(rate: f32) -> Self { 19 | let b = (-2.0 * std::f32::consts::PI * rate).exp(); 20 | let a = 1.0 - b; 21 | Self { 22 | a, 23 | b, 24 | y1: 0.0, 25 | input_order: vec![], 26 | } 27 | } 28 | } 29 | 30 | impl Node for OnePole { 31 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 32 | // println!("inputs[1] {:?}", inputs[1].buffers()); 33 | match inputs.len() { 34 | 1 => { 35 | let main_input = inputs.values_mut().next().unwrap(); 36 | for (out, main_in) in output[0].iter_mut().zip(main_input.buffers()[0].iter()) { 37 | let y = main_in * self.a + self.b * self.y1; 38 | *out = y; 39 | self.y1 = y; 40 | } 41 | } 42 | 2 => { 43 | let main_input = &inputs[&self.input_order[0]]; // can panic if there is no id 44 | let ref_input = &inputs[&self.input_order[1]]; // can panic if there is no id 45 | 46 | for ((out, main_in), ref_in) in output[0] 47 | .iter_mut() 48 | .zip(main_input.buffers()[0].iter()) 49 | .zip(ref_input.buffers()[0].iter()) 50 | { 51 | self.b = (-2.0 * std::f32::consts::PI * main_in).exp(); 52 | self.a = 1. - self.b; 53 | let y = ref_in * self.a + self.b * self.y1; 54 | *out = y; 55 | self.y1 = y; 56 | } 57 | } 58 | _ => (), 59 | } 60 | } 61 | 62 | fn send_msg(&mut self, info: Message) { 63 | match info { 64 | Message::SetToNumber(0, value) => { 65 | self.b = (-2.0 * std::f32::consts::PI * value).exp(); 66 | self.a = 1. - self.b 67 | } 68 | Message::Index(i) => self.input_order.push(i), 69 | Message::IndexOrder(pos, index) => self.input_order.insert(pos, index), 70 | Message::ResetOrder => { 71 | self.input_order.clear(); 72 | } 73 | _ => {} 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /rs/synth/src/node/filter/rhpf.rs: -------------------------------------------------------------------------------- 1 | use crate::{Buffer, Input, Message, Node}; 2 | use hashbrown::HashMap; 3 | #[derive(Debug, Clone)] 4 | pub struct ResonantHighPassFilter { 5 | cutoff: f32, 6 | q: f32, 7 | x0: f32, 8 | x1: f32, 9 | x2: f32, 10 | y1: f32, 11 | y2: f32, 12 | sr: usize, 13 | input_order: Vec, 14 | } 15 | 16 | impl Default for ResonantHighPassFilter { 17 | fn default() -> Self { 18 | Self::new() 19 | } 20 | } 21 | 22 | impl ResonantHighPassFilter { 23 | pub fn new() -> Self { 24 | Self { 25 | cutoff: 20., 26 | q: 1.0, 27 | x0: 0., 28 | x1: 0., 29 | x2: 0., 30 | y1: 0., 31 | y2: 0., 32 | sr: 44100, 33 | input_order: vec![], 34 | } 35 | } 36 | pub fn cutoff(self, cutoff: f32) -> Self { 37 | Self { cutoff, ..self } 38 | } 39 | 40 | pub fn q(self, q: f32) -> Self { 41 | Self { q, ..self } 42 | } 43 | 44 | pub fn sr(self, sr: usize) -> Self { 45 | Self { sr, ..self } 46 | } 47 | } 48 | 49 | impl Node for ResonantHighPassFilter { 50 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 51 | // println!("\n\ninputs[1] \n\n {:?}\n\n", inputs[1].buffers()); 52 | match inputs.len() { 53 | 1 => { 54 | let main_input = inputs.values_mut().next().unwrap(); 55 | let theta_c = 2.0 * std::f32::consts::PI * self.cutoff / self.sr as f32; 56 | let d = 1.0 / self.q; 57 | let beta = 0.5 * (1.0 - d * theta_c.sin() / 2.0) / (1.0 + d * theta_c.sin() / 2.0); 58 | let gama = (0.5 + beta) * theta_c.cos(); 59 | let a0 = (0.5 + beta + gama) / 2.0; 60 | let a1 = -0.5 - beta - gama; 61 | let a2 = (0.5 + beta + gama) / 2.0; 62 | let b1 = -2.0 * gama; 63 | let b2 = 2.0 * beta; 64 | for (out, x0) in output[0].iter_mut().zip(main_input.buffers()[0].iter()) { 65 | let y = 66 | a0 * self.x0 + a1 * self.x1 + a2 * self.x2 - b1 * self.y1 - b2 * self.y2; 67 | *out = y; 68 | self.x2 = self.x1; 69 | self.x1 = *x0; 70 | self.y2 = self.y1; 71 | self.y1 = y; 72 | } 73 | } 74 | 2 => { 75 | let main_input = &inputs[&self.input_order[0]]; // can panic if there is no id 76 | let ref_input = &inputs[&self.input_order[1]]; // can panic if there is no id 77 | 78 | let theta_c = 79 | 2.0 * std::f32::consts::PI * ref_input.buffers()[0][0] / self.sr as f32; 80 | let d = 1.0 / self.q; 81 | let beta = 0.5 * (1.0 - d * theta_c.sin() / 2.0) / (1.0 + d * theta_c.sin() / 2.0); 82 | let gama = (0.5 + beta) * theta_c.cos(); 83 | let a0 = (0.5 + beta + gama) / 2.0; 84 | let a1 = -0.5 - beta - gama; 85 | let a2 = (0.5 + beta + gama) / 2.0; 86 | let b1 = -2.0 * gama; 87 | let b2 = 2.0 * beta; 88 | 89 | for (out, x0) in output[0].iter_mut().zip(main_input.buffers()[0].iter()) { 90 | let y = 91 | a0 * self.x0 + a1 * self.x1 + a2 * self.x2 - b1 * self.y1 - b2 * self.y2; 92 | *out = y; 93 | self.x2 = self.x1; 94 | self.x1 = *x0; 95 | self.y2 = self.y1; 96 | self.y1 = y; 97 | } 98 | } 99 | _ => (), 100 | } 101 | } 102 | 103 | fn send_msg(&mut self, info: Message) { 104 | match info { 105 | Message::SetToNumber(pos, value) => match pos { 106 | 0 => self.cutoff = value, 107 | 1 => self.q = value, 108 | _ => {} 109 | }, 110 | Message::Index(i) => self.input_order.push(i), 111 | Message::IndexOrder(pos, index) => self.input_order.insert(pos, index), 112 | 113 | Message::ResetOrder => { 114 | self.input_order.clear(); 115 | } 116 | _ => {} 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /rs/synth/src/node/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::buffer::Buffer; 2 | use hashbrown::HashMap; 3 | 4 | #[cfg(feature = "node-boxed")] 5 | mod boxed; 6 | pub use boxed::*; 7 | #[cfg(feature = "node-pass")] 8 | mod pass; 9 | pub use pass::*; 10 | #[cfg(feature = "node-sum")] 11 | mod sum; 12 | pub use sum::*; 13 | 14 | pub mod oscillator; 15 | // pub use oscillator::*; 16 | pub mod operator; 17 | // pub use operator::*; 18 | pub mod signal; 19 | // pub use signal::*; 20 | pub mod filter; 21 | // pub use filter::*; 22 | 23 | pub mod sequencer; 24 | // pub use sequencer::*; 25 | pub mod delay; 26 | // pub use delay::*; 27 | pub mod envelope; 28 | // pub use envelope::*; 29 | pub mod effect; 30 | // pub use effect::*; 31 | pub mod compound; 32 | // pub use compound::*; 33 | 34 | pub mod synth; 35 | // pub use synth::*; 36 | 37 | #[cfg(feature = "use-samples")] 38 | pub mod sampling; 39 | 40 | // #[cfg(feature = "use-samples")] 41 | // pub use sampling::*; 42 | 43 | #[cfg(feature = "use-meta")] 44 | pub mod dynamic; 45 | 46 | // #[cfg(feature = "use-meta")] 47 | // pub use dynamic::*; 48 | 49 | pub trait Node { 50 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]); 51 | fn send_msg(&mut self, info: crate::Message); 52 | 53 | fn to_boxed_nodedata(self, channels: usize) -> crate::NodeData, N> 54 | where 55 | Self: Send + 'static + Sized, 56 | { 57 | crate::NodeData::multi_chan_node(channels, BoxedNodeSend::new(self)) 58 | } 59 | } 60 | 61 | /// An important part of the `Node` trait; each `Input` contains the relevant node id as `usize` 62 | pub struct Input { 63 | buffers_ptr: *const Buffer, 64 | buffers_len: usize, 65 | pub node_id: usize, 66 | } 67 | 68 | impl Input { 69 | // Constructor solely for use within the graph `process` function. 70 | pub(crate) fn new(slice: &[Buffer], node_id: usize) -> Self { 71 | let buffers_ptr = slice.as_ptr(); 72 | let buffers_len = slice.len(); 73 | Input { 74 | buffers_ptr, 75 | buffers_len, 76 | node_id, 77 | } 78 | } 79 | 80 | /// A reference to the buffers of the input node. 81 | pub fn buffers(&self) -> &[Buffer] { 82 | // As we know that an `Input` can only be constructed during a call to the graph `process` 83 | // function, we can be sure that our slice is still valid as long as the input itself is 84 | // alive. 85 | unsafe { std::slice::from_raw_parts(self.buffers_ptr, self.buffers_len) } 86 | } 87 | } 88 | 89 | // Inputs can only be created by the `dasp_graph::process` implementation and only ever live as 90 | // long as the lifetime of the call to the function. Thus, it's safe to implement this so that 91 | // `Send` closures can be stored within the graph and sent between threads. 92 | unsafe impl Send for Input {} 93 | 94 | impl core::fmt::Debug for Input { 95 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 96 | core::fmt::Debug::fmt(self.buffers(), f) 97 | } 98 | } 99 | 100 | impl<'a, T, const N: usize> Node for &'a mut T 101 | where 102 | T: Node, 103 | { 104 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 105 | (**self).process(inputs, output) 106 | } 107 | fn send_msg(&mut self, info: crate::Message) { 108 | (**self).send_msg(info) 109 | } 110 | } 111 | 112 | impl Node for Box 113 | where 114 | T: Node, 115 | { 116 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 117 | (**self).process(inputs, output) 118 | } 119 | fn send_msg(&mut self, _info: crate::Message) {} 120 | } 121 | 122 | impl Node for dyn Fn(&HashMap>, &mut [Buffer]) { 123 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 124 | (*self)(inputs, output) 125 | } 126 | fn send_msg(&mut self, _info: crate::Message) {} 127 | } 128 | 129 | impl Node for dyn FnMut(&HashMap>, &mut [Buffer]) { 130 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 131 | (*self)(inputs, output) 132 | } 133 | 134 | fn send_msg(&mut self, _info: crate::Message) {} 135 | } 136 | 137 | impl Node for fn(&HashMap>, &mut [Buffer]) { 138 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 139 | (*self)(inputs, output) 140 | } 141 | fn send_msg(&mut self, _info: crate::Message) {} 142 | } 143 | -------------------------------------------------------------------------------- /rs/synth/src/node/operator/add.rs: -------------------------------------------------------------------------------- 1 | use crate::{Buffer, Input, Message, Node}; 2 | use hashbrown::HashMap; 3 | 4 | use super::apply_op; 5 | #[derive(Debug, Clone)] 6 | pub struct Add { 7 | val: f32, 8 | input_order: Vec, 9 | } 10 | 11 | impl Add { 12 | pub fn new(val: f32) -> Self { 13 | Self { 14 | val, 15 | input_order: Vec::::new(), 16 | } 17 | } 18 | } 19 | 20 | impl From for Add { 21 | fn from(value: f32) -> Self { 22 | Self::new(value) 23 | } 24 | } 25 | 26 | impl Node for Add { 27 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 28 | // println!("inputs of add {:?} {}", inputs, inputs.len()); 29 | apply_op( 30 | inputs, 31 | &self.input_order, 32 | output, 33 | self.val, 34 | std::ops::Add::add, 35 | ) 36 | } 37 | 38 | fn send_msg(&mut self, info: Message) { 39 | match info { 40 | Message::SetToNumber(0, value) => self.val = value, 41 | Message::Index(i) => self.input_order.push(i), 42 | Message::IndexOrder(pos, index) => self.input_order.insert(pos, index), 43 | Message::ResetOrder => { 44 | self.input_order.clear(); 45 | } 46 | _ => {} 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /rs/synth/src/node/operator/mod.rs: -------------------------------------------------------------------------------- 1 | mod mul; 2 | pub use mul::Mul; 3 | mod add; 4 | pub use add::*; 5 | 6 | use crate::{Buffer, Input}; 7 | use hashbrown::HashMap; 8 | 9 | fn apply_op( 10 | inputs: &mut HashMap>, 11 | input_order: &[usize], 12 | output: &mut [Buffer], 13 | val: f32, 14 | op: impl Fn(f32, f32) -> f32 + Copy, 15 | ) { 16 | let (out_left, out_right) = output.split_at_mut(1); 17 | match inputs.len() { 18 | 1 => { 19 | let main_input = inputs.values_mut().next().unwrap().buffers(); 20 | 21 | for (idx, (out_left, main_in)) in 22 | out_left[0].iter_mut().zip(main_input[0].iter()).enumerate() 23 | { 24 | *out_left = op(*main_in, val); 25 | 26 | if let [out_right, ..] = out_right { 27 | out_right[idx] = op(main_input.get(1).map_or(*main_in, |m| m[idx]), val); 28 | } 29 | } 30 | } 31 | 2 => { 32 | let main_input = inputs[&input_order[0]].buffers(); 33 | let ref_input = inputs[&input_order[1]].buffers(); 34 | 35 | for (idx, ((out_left, ref_in), main_in)) in out_left[0] 36 | .iter_mut() 37 | .zip(ref_input[0].iter()) 38 | .zip(main_input[0].iter()) 39 | .enumerate() 40 | { 41 | *out_left = op(*main_in, *ref_in); 42 | 43 | if let [out_right, ..] = out_right { 44 | out_right[idx] = op( 45 | main_input.get(1).map_or(*main_in, |m| m[idx]), 46 | ref_input.get(1).map_or(*ref_in, |r| r[idx]), 47 | ); 48 | }; 49 | } 50 | } 51 | _ => {} 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /rs/synth/src/node/operator/mul.rs: -------------------------------------------------------------------------------- 1 | use crate::{Buffer, Input, Message, Node}; 2 | use hashbrown::HashMap; 3 | 4 | use super::apply_op; 5 | #[derive(Debug, Clone)] 6 | pub struct Mul { 7 | val: f32, 8 | input_order: Vec, 9 | } 10 | 11 | impl Mul { 12 | pub fn new(val: f32) -> Self { 13 | Self { 14 | val, 15 | input_order: vec![], 16 | } 17 | } 18 | } 19 | 20 | impl Node for Mul { 21 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 22 | // println!("inputs {:?} self.input_order {:?}", inputs, self.input_order); 23 | // panic!(); 24 | apply_op( 25 | inputs, 26 | &self.input_order, 27 | output, 28 | self.val, 29 | std::ops::Mul::mul, 30 | ); 31 | } 32 | fn send_msg(&mut self, info: Message) { 33 | match info { 34 | Message::SetToNumber(0, value) => self.val = value, 35 | Message::Index(i) => self.input_order.push(i), 36 | Message::IndexOrder(pos, index) => self.input_order.insert(pos, index), 37 | Message::ResetOrder => { 38 | self.input_order.clear(); 39 | } 40 | _ => {} 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /rs/synth/src/node/oscillator/mod.rs: -------------------------------------------------------------------------------- 1 | pub use sin_osc::SinOsc; 2 | mod sin_osc; 3 | pub use saw_osc::SawOsc; 4 | mod saw_osc; 5 | pub use tri_osc::TriOsc; 6 | mod tri_osc; 7 | pub use squ_osc::SquOsc; 8 | mod squ_osc; 9 | 10 | use crate::{Buffer, Input}; 11 | use hashbrown::HashMap; 12 | 13 | fn process_oscillation( 14 | inputs: &mut HashMap>, 15 | input_order: &mut [usize], 16 | output: &mut [Buffer], 17 | freq: f32, 18 | inc: &mut f32, 19 | mut osc: impl FnMut(&mut f32, f32), 20 | ) { 21 | match inputs.len() { 22 | 0 => { 23 | for out in &mut *output[0] { 24 | osc(out, freq); 25 | } 26 | } 27 | 1 => { 28 | let mod_input = match input_order { 29 | [] => &mut *inputs.values_mut().next().unwrap(), 30 | [ref first_input, ..] => &inputs[first_input], 31 | }; 32 | 33 | for (out, mod_buf) in output[0].iter_mut().zip(mod_input.buffers()[0].iter()) { 34 | if *mod_buf != 0. { 35 | *inc = *mod_buf; 36 | }; 37 | 38 | osc(out, *inc); 39 | } 40 | } 41 | _ => {} 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /rs/synth/src/node/oscillator/saw_osc.rs: -------------------------------------------------------------------------------- 1 | use crate::{Buffer, Input, Message, Node}; 2 | use hashbrown::HashMap; 3 | 4 | use super::process_oscillation; 5 | #[derive(Debug, Clone)] 6 | pub struct SawOsc { 7 | pub freq: f32, 8 | pub phase: f32, 9 | pub sr: usize, 10 | inc: f32, 11 | input_order: Vec, 12 | } 13 | 14 | impl std::default::Default for SawOsc { 15 | fn default() -> Self { 16 | Self { 17 | freq: 1.0, 18 | phase: 0.0, 19 | sr: 44100, 20 | inc: 0., 21 | input_order: vec![], 22 | } 23 | } 24 | } 25 | 26 | impl SawOsc { 27 | pub fn new() -> Self { 28 | Self::default() 29 | } 30 | pub fn freq(self, freq: f32) -> Self { 31 | Self { freq, ..self } 32 | } 33 | pub fn sr(self, sr: usize) -> Self { 34 | Self { sr, ..self } 35 | } 36 | pub fn phase(self, phase: f32) -> Self { 37 | Self { phase, ..self } 38 | } 39 | } 40 | 41 | impl Node for SawOsc { 42 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 43 | process_oscillation( 44 | inputs, 45 | &mut self.input_order, 46 | output, 47 | self.freq, 48 | &mut self.inc, 49 | |out, freq| { 50 | *out = self.phase * 2. - 1.; 51 | self.phase += freq / self.sr as f32; 52 | if self.phase > 1. { 53 | self.phase -= 1. 54 | } 55 | }, 56 | ); 57 | } 58 | 59 | fn send_msg(&mut self, info: Message) { 60 | match info { 61 | Message::SetToNumber(0, value) => self.freq = value, 62 | Message::Index(i) => self.input_order.push(i), 63 | Message::IndexOrder(pos, index) => self.input_order.insert(pos, index), 64 | Message::ResetOrder => { 65 | self.input_order.clear(); 66 | } 67 | _ => {} 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /rs/synth/src/node/oscillator/sin_osc.rs: -------------------------------------------------------------------------------- 1 | use crate::{Buffer, Input, Message, Node}; 2 | use hashbrown::HashMap; 3 | #[derive(Debug, Clone)] 4 | pub struct SinOsc { 5 | pub freq: f32, 6 | pub phase: f32, 7 | pub sr: usize, 8 | input_order: Vec, 9 | } 10 | 11 | impl std::default::Default for SinOsc { 12 | fn default() -> Self { 13 | Self { 14 | freq: 1.0, 15 | phase: 0.0, 16 | sr: 44100, 17 | input_order: vec![], 18 | } 19 | } 20 | } 21 | 22 | impl SinOsc { 23 | pub fn new() -> Self { 24 | Self::default() 25 | } 26 | pub fn freq(self, freq: f32) -> Self { 27 | Self { freq, ..self } 28 | } 29 | pub fn sr(self, sr: usize) -> Self { 30 | Self { sr, ..self } 31 | } 32 | pub fn phase(self, phase: f32) -> Self { 33 | Self { phase, ..self } 34 | } 35 | } 36 | 37 | impl Node for SinOsc { 38 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 39 | match inputs.len() { 40 | 0 => { 41 | for i in 0..N { 42 | for buf in output.iter_mut() { 43 | buf[i] = (self.phase * 2.0 * std::f32::consts::PI).sin(); 44 | } 45 | self.phase += self.freq / self.sr as f32; 46 | if self.phase > 1.0 { 47 | self.phase -= 1.0 48 | } 49 | } 50 | } 51 | 1 => { 52 | let mod_input = match *self.input_order { 53 | [] => &mut *inputs.values_mut().next().unwrap(), 54 | [ref first_input, ..] => &inputs[first_input], 55 | }; 56 | 57 | for (i, mod_buf) in mod_input.buffers()[0].iter().enumerate() { 58 | for buf in output.iter_mut() { 59 | buf[i] = (self.phase * 2.0 * std::f32::consts::PI).sin(); 60 | } 61 | 62 | self.phase += mod_buf / self.sr as f32; 63 | if self.phase > 1.0 { 64 | self.phase -= 1.0 65 | } 66 | } 67 | } 68 | _ => (), 69 | } 70 | } 71 | fn send_msg(&mut self, info: Message) { 72 | match info { 73 | Message::SetToNumber(0, value) => self.freq = value, 74 | Message::Index(i) => self.input_order.push(i), 75 | Message::IndexOrder(pos, index) => self.input_order.insert(pos, index), 76 | Message::ResetOrder => { 77 | self.input_order.clear(); 78 | } 79 | _ => {} 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /rs/synth/src/node/oscillator/squ_osc.rs: -------------------------------------------------------------------------------- 1 | use crate::{oscillator::process_oscillation, Buffer, Input, Message, Node}; 2 | use hashbrown::HashMap; 3 | #[derive(Debug, Clone)] 4 | pub struct SquOsc { 5 | pub freq: f32, 6 | pub phase: f32, 7 | pub sr: usize, 8 | inc: f32, 9 | input_order: Vec, 10 | } 11 | 12 | impl std::default::Default for SquOsc { 13 | fn default() -> Self { 14 | Self { 15 | freq: 1.0, 16 | phase: 0.0, 17 | sr: 44100, 18 | inc: 0., 19 | input_order: vec![], 20 | } 21 | } 22 | } 23 | 24 | impl SquOsc { 25 | pub fn new() -> Self { 26 | Self::default() 27 | } 28 | pub fn freq(self, freq: f32) -> Self { 29 | Self { freq, ..self } 30 | } 31 | pub fn sr(self, sr: usize) -> Self { 32 | Self { sr, ..self } 33 | } 34 | pub fn phase(self, phase: f32) -> Self { 35 | Self { phase, ..self } 36 | } 37 | } 38 | 39 | impl Node for SquOsc { 40 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 41 | process_oscillation( 42 | inputs, 43 | &mut self.input_order, 44 | output, 45 | self.freq, 46 | &mut self.inc, 47 | |out, freq| { 48 | if self.phase <= 0.5 { 49 | *out = 1.0; 50 | } else { 51 | *out = -1.0; 52 | } 53 | 54 | self.phase += freq / self.sr as f32; 55 | if self.phase > 1. { 56 | self.phase -= 1. 57 | } 58 | }, 59 | ); 60 | } 61 | 62 | fn send_msg(&mut self, info: Message) { 63 | match info { 64 | Message::SetToNumber(0, value) => self.freq = value, 65 | Message::Index(i) => self.input_order.push(i), 66 | Message::IndexOrder(pos, index) => self.input_order.insert(pos, index), 67 | Message::ResetOrder => { 68 | self.input_order.clear(); 69 | } 70 | _ => {} 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /rs/synth/src/node/oscillator/tri_osc.rs: -------------------------------------------------------------------------------- 1 | use crate::{oscillator::process_oscillation, Buffer, Input, Message, Node}; 2 | use hashbrown::HashMap; 3 | #[derive(Debug, Clone)] 4 | pub struct TriOsc { 5 | pub freq: f32, 6 | pub phase: f32, 7 | pub sr: usize, 8 | inc: f32, 9 | input_order: Vec, 10 | } 11 | 12 | impl std::default::Default for TriOsc { 13 | fn default() -> Self { 14 | Self { 15 | freq: 1.0, 16 | phase: 0.0, 17 | sr: 44100, 18 | inc: 0., 19 | input_order: vec![], 20 | } 21 | } 22 | } 23 | 24 | impl TriOsc { 25 | pub fn new() -> Self { 26 | Self::default() 27 | } 28 | pub fn freq(self, freq: f32) -> Self { 29 | Self { freq, ..self } 30 | } 31 | pub fn sr(self, sr: usize) -> Self { 32 | Self { sr, ..self } 33 | } 34 | pub fn phase(self, phase: f32) -> Self { 35 | Self { phase, ..self } 36 | } 37 | } 38 | 39 | impl Node for TriOsc { 40 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 41 | process_oscillation( 42 | inputs, 43 | &mut self.input_order, 44 | output, 45 | self.freq, 46 | &mut self.inc, 47 | |out, freq| { 48 | let v = -1.0 + (self.phase * 2.); 49 | 50 | *out = 2.0 * (v.abs() - 0.5); 51 | self.phase += freq / self.sr as f32; 52 | 53 | if self.phase > 1. { 54 | self.phase -= 1. 55 | } 56 | }, 57 | ); 58 | } 59 | fn send_msg(&mut self, info: Message) { 60 | match info { 61 | Message::SetToNumber(0, value) => self.freq = value, 62 | Message::Index(i) => self.input_order.push(i), 63 | Message::IndexOrder(pos, index) => self.input_order.insert(pos, index), 64 | Message::ResetOrder => { 65 | self.input_order.clear(); 66 | } 67 | _ => {} 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /rs/synth/src/node/pass.rs: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | 3 | // Copyright (c) 2016 RustAudio Developers 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | use crate::{Buffer, Input, Message, Node}; 24 | use hashbrown::HashMap; 25 | 26 | /// A simple node that passes an input directly to the output. 27 | /// 28 | /// Works by mem-copying each buffer of the first input to each buffer of the output respectively. 29 | /// 30 | /// This can be useful as an intermediary node when feeding the output of a node back into one of 31 | /// its inputs. It can also be useful for discarding excess input channels by having a `Pass` with 32 | /// less output buffers than its input. 33 | #[derive(Clone, Debug, PartialEq)] 34 | pub struct Pass; 35 | 36 | impl Node for Pass { 37 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 38 | let Some(input) = inputs.values().next() else { 39 | return; 40 | }; 41 | 42 | match (input.buffers(), &mut *output) { 43 | ([ref in_buf], [out_left, out_right]) => { 44 | out_left.copy_from_slice(in_buf); 45 | out_right.copy_from_slice(in_buf); 46 | } 47 | _ => { 48 | for (out_buf, in_buf) in output.iter_mut().zip(input.buffers()) { 49 | out_buf.copy_from_slice(in_buf); 50 | } 51 | } 52 | } 53 | } 54 | fn send_msg(&mut self, _info: Message) {} 55 | } 56 | -------------------------------------------------------------------------------- /rs/synth/src/node/sampling/mod.rs: -------------------------------------------------------------------------------- 1 | mod sampler; 2 | pub use sampler::*; 3 | mod psampler; 4 | pub use psampler::*; 5 | -------------------------------------------------------------------------------- /rs/synth/src/node/sequencer/arrange.rs: -------------------------------------------------------------------------------- 1 | use crate::{Buffer, Input, Message, Node}; 2 | use glicol_parser::nodes::NumberOrRef; 3 | use hashbrown::HashMap; 4 | 5 | #[derive(Debug)] 6 | pub struct Arrange { 7 | _current_bar: usize, 8 | events: Vec>, 9 | speed: f32, 10 | pub bpm: f32, 11 | sr: usize, 12 | pub step: usize, 13 | input_order: Vec, 14 | // sidechain_lib: HashMap, 15 | } 16 | 17 | impl Arrange { 18 | pub fn new(events: Vec>) -> Self { 19 | // let mut total_circles = 0; 20 | // for j in 0..(self.events.len()/2) { 21 | // match self.event[j*2] { 22 | // GlicolPara::Number(value) => total_circles += *value, 23 | // _ => {} 24 | // }; 25 | // }; 26 | Self { 27 | _current_bar: 0, 28 | events, 29 | input_order: Vec::new(), 30 | speed: 1.0, 31 | bpm: 120., 32 | sr: 44100, 33 | step: 0, 34 | } 35 | } 36 | pub fn sr(self, sr: usize) -> Self { 37 | Self { sr, ..self } 38 | } 39 | pub fn bpm(self, bpm: f32) -> Self { 40 | Self { bpm, ..self } 41 | } 42 | } 43 | 44 | impl Node for Arrange { 45 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 46 | let bar_length = 240.0 / self.bpm * self.sr as f32 / self.speed; 47 | // calculate which bar we are and which input to pass to theoutput 48 | for (i, out) in output[0].iter_mut().enumerate() { 49 | let pos = self.step as f32 / bar_length; 50 | let mut bar_count = 0.0; 51 | for j in 0..(self.events.len() / 2) { 52 | let NumberOrRef::Number(bar) = self.events[j * 2 + 1] else { 53 | return; 54 | }; 55 | 56 | bar_count += bar; 57 | if pos < bar_count { 58 | let source = &inputs[&self.input_order[j]]; 59 | *out = source.buffers()[0][i]; 60 | // match &self.events[j*2] { 61 | // GlicolPara::Reference(s) => { 62 | // let source = &inputs[&self.input_order[j]]; 63 | // output[0][i] = source.buffers()[0][i]; 64 | // }, 65 | // _ => return () 66 | // }; 67 | break; 68 | } else if j == self.events.len() / 2 - 1 { 69 | self.step = 0; 70 | let source = &inputs[&self.input_order[0]]; 71 | *out = source.buffers()[0][i]; 72 | // match &self.events[0] { 73 | // GlicolPara::Reference(s) => { 74 | // let source = &inputs[&self.input_order[0]]; 75 | // output[0][i] = source.buffers()[0][i]; 76 | // }, 77 | // _ => return () 78 | // }; 79 | } 80 | } 81 | self.step += 1; 82 | } 83 | } 84 | fn send_msg(&mut self, info: Message) { 85 | match info { 86 | Message::SetToNumber(i, value) => { 87 | self.step = 0; 88 | let to_push = i as usize - self.events.len(); 89 | 90 | self.events.reserve(to_push + 1); 91 | self.events 92 | .extend(std::iter::from_fn(|| Some(NumberOrRef::Number(0.0))).take(to_push)); 93 | self.events.push(NumberOrRef::Number(value)); 94 | } 95 | Message::Index(i) => self.input_order.push(i), 96 | Message::IndexOrder(pos, index) => self.input_order.insert(pos, index), 97 | Message::ResetOrder => { 98 | self.input_order.clear(); 99 | } 100 | _ => {} 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /rs/synth/src/node/sequencer/choose.rs: -------------------------------------------------------------------------------- 1 | use crate::{Buffer, Input, Message, Node}; 2 | use dasp_signal::{self as signal, Signal}; 3 | use hashbrown::HashMap; 4 | 5 | pub struct Choose { 6 | sig: Box + Send>, 7 | note_list: Vec, 8 | input_order: Vec, 9 | } 10 | 11 | impl Choose { 12 | pub fn new(note_list: Vec, seed: u64) -> Self { 13 | Self { 14 | sig: Box::new(signal::noise(seed)), 15 | note_list, 16 | input_order: Vec::new(), 17 | } 18 | } 19 | } 20 | 21 | impl Node for Choose { 22 | fn process(&mut self, _inputs: &mut HashMap>, output: &mut [Buffer]) { 23 | // TODO: better picking algo? 24 | let mut id = ((self.sig.next() * 0.5 + 0.5) * self.note_list.len() as f64) as usize; 25 | if id == self.note_list.len() { 26 | id = 0 27 | }; 28 | output[0].iter_mut().for_each(|s| *s = self.note_list[id]); 29 | } 30 | fn send_msg(&mut self, info: Message) { 31 | match info { 32 | Message::SetToNumberList(0, list) => self.note_list = list, 33 | Message::Index(i) => self.input_order.push(i), 34 | Message::IndexOrder(pos, index) => self.input_order.insert(pos, index), 35 | Message::ResetOrder => { 36 | self.input_order.clear(); 37 | } 38 | _ => {} 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /rs/synth/src/node/sequencer/mod.rs: -------------------------------------------------------------------------------- 1 | mod seq; 2 | pub use seq::*; 3 | mod choose; 4 | pub use choose::*; 5 | mod speed; 6 | pub use speed::*; 7 | mod arrange; 8 | pub use arrange::*; 9 | -------------------------------------------------------------------------------- /rs/synth/src/node/sequencer/seq.rs: -------------------------------------------------------------------------------- 1 | use crate::{Buffer, Input, Message, Node}; 2 | use glicol_parser::nodes::UsizeOrRef; 3 | use hashbrown::HashMap; 4 | 5 | #[derive(Debug)] 6 | pub struct Sequencer { 7 | events: Vec<(f32, UsizeOrRef)>, 8 | ref_order: HashMap, 9 | speed: f32, 10 | pub bpm: f32, 11 | sr: usize, 12 | pub step: usize, 13 | input_order: Vec, 14 | // sidechain_lib: HashMap, 15 | } 16 | 17 | impl Sequencer { 18 | pub fn new(events: Vec<(f32, UsizeOrRef)>) -> Self { 19 | Self { 20 | events, 21 | ref_order: HashMap::new(), 22 | input_order: Vec::new(), 23 | speed: 1.0, 24 | bpm: 120., 25 | sr: 44100, 26 | step: 0, 27 | } 28 | } 29 | pub fn ref_order(self, ref_order: HashMap) -> Self { 30 | Self { ref_order, ..self } 31 | } 32 | pub fn sr(self, sr: usize) -> Self { 33 | Self { sr, ..self } 34 | } 35 | pub fn bpm(self, bpm: f32) -> Self { 36 | Self { bpm, ..self } 37 | } 38 | } 39 | 40 | impl Node for Sequencer { 41 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 42 | // println!("seq inputs info {:?} ; self.input_order {:?}", inputs, self.input_order); 43 | match inputs.len() { 44 | 0 => { 45 | let bar_length = 240.0 / self.bpm as f64 * self.sr as f64 / self.speed as f64; 46 | for out in &mut *output[0] { 47 | *out = 0.0; 48 | for event in &self.events { 49 | if (self.step % (bar_length as usize)) 50 | == ((event.0 as f64 * bar_length) as usize) 51 | { 52 | let midi = match event.1 { 53 | UsizeOrRef::Usize(value) => value, 54 | UsizeOrRef::Ref(_) => 0, 55 | }; 56 | 57 | if midi == 0 { 58 | *out = 0.0 59 | } else { 60 | *out = 2.0f32.powf((midi as f32 - 60.0) / 12.0) 61 | } 62 | } 63 | } 64 | self.step += 1; 65 | } 66 | } 67 | _ => { 68 | // println!("{:?} {:?}", inputs, self.input_order); 69 | let possible_speed = &inputs[&self.input_order[0]].buffers()[0]; 70 | let has_speed = possible_speed[0] > 0. && possible_speed[1] == 0.; 71 | 72 | if has_speed { 73 | self.speed = possible_speed[0]; 74 | } 75 | 76 | let bar_length = 240.0 / self.bpm as f64 * self.sr as f64 / self.speed as f64; 77 | for (idx, out) in output[0].iter_mut().enumerate() { 78 | *out = 0.0; 79 | 80 | for event in &self.events { 81 | if (self.step % (bar_length as usize)) 82 | == ((event.0 as f64 * bar_length) as usize) 83 | { 84 | let midi = match &event.1 { 85 | UsizeOrRef::Usize(value) => *value as f32, 86 | UsizeOrRef::Ref(s) => { 87 | let source = &inputs 88 | [&self.input_order[self.ref_order[s] + has_speed as usize]]; //panic? 89 | source.buffers()[0][idx] 90 | } 91 | }; 92 | 93 | if midi == 0.0 { 94 | *out = 0.0 95 | } else { 96 | *out = 2.0f32.powf((midi - 60.0) / 12.0) 97 | } 98 | } 99 | } 100 | self.step += 1; 101 | } 102 | } 103 | } 104 | } 105 | fn send_msg(&mut self, info: Message) { 106 | match info { 107 | Message::SetBPM(bpm) => self.bpm = bpm, 108 | Message::SetToSeq(0, events) => self.events = events, 109 | Message::SetRefOrder(ref_order) => { 110 | self.ref_order = ref_order; 111 | } 112 | Message::Index(i) => self.input_order.push(i), 113 | Message::IndexOrder(pos, index) => self.input_order.insert(pos, index), 114 | Message::ResetOrder => { 115 | self.input_order.clear(); 116 | } 117 | _ => {} 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /rs/synth/src/node/sequencer/speed.rs: -------------------------------------------------------------------------------- 1 | use crate::{Buffer, Input, Message, Node}; 2 | use hashbrown::HashMap; 3 | 4 | #[derive(Debug, Clone)] 5 | pub struct Speed { 6 | val: f32, 7 | input_order: Vec, 8 | } 9 | 10 | impl From for Speed { 11 | fn from(val: f32) -> Self { 12 | Self { 13 | val, 14 | input_order: Vec::new(), 15 | } 16 | } 17 | } 18 | 19 | impl Node for Speed { 20 | fn process(&mut self, _inputs: &mut HashMap>, output: &mut [Buffer]) { 21 | output[0].silence(); 22 | output[0][0] = self.val; 23 | } 24 | fn send_msg(&mut self, info: Message) { 25 | match info { 26 | Message::SetToNumber(0, value) => self.val = value, 27 | Message::Index(i) => self.input_order.push(i), 28 | Message::IndexOrder(pos, index) => self.input_order.insert(pos, index), 29 | Message::ResetOrder => { 30 | self.input_order.clear(); 31 | } 32 | _ => {} 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /rs/synth/src/node/signal/constsig.rs: -------------------------------------------------------------------------------- 1 | use crate::{Buffer, Input, Message, Node}; 2 | use hashbrown::HashMap; 3 | #[derive(Debug, Clone)] 4 | pub struct ConstSig { 5 | val: f32, 6 | events: Vec<(f32, f32)>, 7 | pattern: Vec<(f32, f32)>, 8 | span: f32, 9 | bpm: f32, 10 | sr: usize, 11 | step: usize, 12 | input_order: Vec, 13 | } 14 | 15 | impl ConstSig { 16 | pub fn new(val: f32) -> Self { 17 | Self { 18 | val, 19 | events: vec![], 20 | pattern: vec![], 21 | span: 1., 22 | bpm: 120., 23 | sr: 44100, 24 | step: 0, 25 | input_order: Vec::new(), 26 | } 27 | } 28 | 29 | pub fn events(self, events: Vec<(f32, f32)>) -> Self { 30 | Self { events, ..self } 31 | } 32 | 33 | pub fn pattern(self, pattern: Vec<(f32, f32)>) -> Self { 34 | Self { pattern, ..self } 35 | } 36 | 37 | pub fn span(self, span: f32) -> Self { 38 | Self { span, ..self } 39 | } 40 | 41 | pub fn bpm(self, bpm: f32) -> Self { 42 | Self { bpm, ..self } 43 | } 44 | 45 | pub fn sr(self, sr: usize) -> Self { 46 | Self { sr, ..self } 47 | } 48 | } 49 | 50 | impl Node for ConstSig { 51 | fn process(&mut self, _inputs: &mut HashMap>, output: &mut [Buffer]) { 52 | let cycle_dur = 60. / self.bpm * 4.; 53 | let bar_dur = cycle_dur * self.span * self.sr as f32; 54 | 55 | for out in &mut *output[0] { 56 | for event in &self.events { 57 | if (self.step % (bar_dur as usize)) 58 | == ((event.1 * cycle_dur * self.sr as f32) as usize) 59 | { 60 | self.val = event.0 61 | } 62 | } 63 | 64 | for event in &self.pattern { 65 | if (self.step % (bar_dur as usize)) 66 | == ((event.1 * cycle_dur * self.sr as f32) as usize) 67 | { 68 | self.val = event.0 69 | } 70 | } 71 | 72 | *out = self.val; 73 | self.step += 1; 74 | } 75 | } 76 | fn send_msg(&mut self, info: Message) { 77 | match info { 78 | Message::SetPattern(p, span) => { 79 | self.pattern = p; 80 | self.span = span; 81 | } 82 | Message::SetToNumber(0, value) => self.val = value, 83 | Message::SetBPM(bpm) => self.bpm = bpm, 84 | Message::Index(i) => self.input_order.push(i), 85 | Message::IndexOrder(pos, index) => self.input_order.insert(pos, index), 86 | _ => {} 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /rs/synth/src/node/signal/imp.rs: -------------------------------------------------------------------------------- 1 | use crate::{Buffer, Input, Message, Node}; 2 | use hashbrown::HashMap; 3 | 4 | #[derive(Debug, Clone)] 5 | pub struct Impulse { 6 | clock: usize, 7 | period: usize, 8 | sr: usize, 9 | input_order: Vec, 10 | } 11 | 12 | impl Default for Impulse { 13 | fn default() -> Self { 14 | Self::new() 15 | } 16 | } 17 | 18 | impl Impulse { 19 | pub fn new() -> Self { 20 | Self { 21 | clock: 0, 22 | period: 44100, 23 | sr: 44100, 24 | input_order: Vec::new(), 25 | } 26 | } 27 | pub fn freq(self, freq: f32) -> Self { 28 | let period = (self.sr as f32 / freq) as usize; 29 | Self { period, ..self } 30 | } 31 | pub fn sr(self, sr: usize) -> Self { 32 | Self { sr, ..self } 33 | } 34 | } 35 | 36 | impl Node for Impulse { 37 | fn process(&mut self, _inputs: &mut HashMap>, output: &mut [Buffer]) { 38 | // if inputs.len() > 0 { 39 | // self.clock = inputs[0].buffers()[0][0] as usize; 40 | // } 41 | // println!("processed"); 42 | // for o in output { 43 | // o.iter_mut().for_each(|s| *s = self.sig.next() as f32); 44 | // } 45 | for out in &mut *output[0] { 46 | *out = (self.clock % self.period == 0) as u8 as f32; 47 | self.clock += 1; 48 | } 49 | } 50 | 51 | fn send_msg(&mut self, info: Message) { 52 | match info { 53 | Message::SetToNumber(0, value) => self.period = (self.sr as f32 / value) as usize, 54 | Message::Index(i) => self.input_order.push(i), 55 | Message::IndexOrder(pos, index) => self.input_order.insert(pos, index), 56 | _ => {} 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /rs/synth/src/node/signal/mod.rs: -------------------------------------------------------------------------------- 1 | mod constsig; 2 | pub use constsig::ConstSig; 3 | mod imp; 4 | pub use imp::*; 5 | mod noise; 6 | pub use noise::*; 7 | mod points; 8 | pub use points::*; 9 | -------------------------------------------------------------------------------- /rs/synth/src/node/signal/noise.rs: -------------------------------------------------------------------------------- 1 | use crate::{Buffer, Input, Message, Node}; 2 | use hashbrown::HashMap; 3 | 4 | use dasp_signal::{self as signal, Signal}; 5 | 6 | pub struct Noise { 7 | sig: Box + Send>, 8 | input_order: Vec, 9 | } 10 | 11 | impl Noise { 12 | pub fn new(seed: usize) -> Self { 13 | Self { 14 | sig: Box::new(signal::noise(seed as u64)), 15 | input_order: Vec::new(), 16 | } 17 | } 18 | } 19 | 20 | impl Node for Noise { 21 | fn process(&mut self, _inputs: &mut HashMap>, output: &mut [Buffer]) { 22 | for out in output { 23 | out.iter_mut().for_each(|s| *s = self.sig.next() as f32); 24 | } 25 | } 26 | fn send_msg(&mut self, info: Message) { 27 | match info { 28 | Message::SetToNumber(0, value) => self.sig = Box::new(signal::noise(value as u64)), 29 | Message::Index(i) => self.input_order.push(i), 30 | Message::IndexOrder(pos, index) => self.input_order.insert(pos, index), 31 | _ => {} 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /rs/synth/src/node/signal/phasor.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosprint/glicol/0317db2e3f157b911bfc28c72ad5db90db963f24/rs/synth/src/node/signal/phasor.rs -------------------------------------------------------------------------------- /rs/synth/src/node/sum.rs: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | 3 | // Copyright (c) 2016 RustAudio Developers 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | use crate::{Buffer, Input, Message, Node}; 24 | use hashbrown::HashMap; 25 | 26 | /// A stateless node that sums each of the inputs onto the output. 27 | /// 28 | /// Assumes that the number of buffers per input is equal to the number of output buffers. 29 | #[derive(Clone, Debug, PartialEq)] 30 | pub struct Sum; 31 | 32 | /// A stateless node that sums all of the buffers of all of the inputs onto each of the output 33 | /// buffers. 34 | /// 35 | /// E.g. Given two inputs with three buffers each, all 6 input buffers will be summed onto the 36 | /// first output buffer. If there is more than one output buffer, the result is copied to the 37 | /// remaining output buffers. 38 | /// 39 | /// After a call to `Node::process`, each of the output buffers will always have the same contents. 40 | /// 41 | /// Common use cases: 42 | /// 43 | /// - Summing multiple input channels down to a single output channel. 44 | /// - Writing a single input channel to multiple output channels. 45 | #[derive(Clone, Debug, PartialEq)] 46 | pub struct SumBuffers; 47 | 48 | #[derive(Clone, Debug, PartialEq)] 49 | pub struct Sum2; 50 | 51 | impl Node for Sum { 52 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 53 | // Fill the output with silence. 54 | for out_buffer in output.iter_mut() { 55 | out_buffer.silence(); 56 | } 57 | // Sum the inputs onto the output. 58 | for (channel, out_buffer) in output.iter_mut().enumerate() { 59 | for input in inputs.values() { 60 | let in_buffers = input.buffers(); 61 | if let Some(in_buffer) = in_buffers.get(channel) { 62 | dasp_slice::add_in_place(out_buffer, in_buffer); 63 | } 64 | } 65 | } 66 | // println!("{:?}", output); 67 | } 68 | fn send_msg(&mut self, _info: Message) {} 69 | } 70 | 71 | impl Node for Sum2 { 72 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 73 | // Fill the output with silence. 74 | for out_buffer in output.iter_mut() { 75 | out_buffer.silence(); 76 | } 77 | // Sum the inputs onto the output. 78 | for (channel, out_buffer) in output.iter_mut().enumerate() { 79 | for input in inputs.values() { 80 | let in_buffers = input.buffers(); 81 | match in_buffers.get(channel) { 82 | Some(in_buffer) => { 83 | dasp_slice::add_in_place(out_buffer, in_buffer); 84 | } 85 | None => { 86 | dasp_slice::add_in_place(out_buffer, &in_buffers[0]); 87 | } 88 | }; 89 | } 90 | } 91 | } 92 | fn send_msg(&mut self, _info: Message) {} 93 | } 94 | 95 | impl Node for SumBuffers { 96 | fn process(&mut self, inputs: &mut HashMap>, output: &mut [Buffer]) { 97 | // Get the first output buffer. 98 | let mut out_buffers = output.iter_mut(); 99 | let out_buffer_first = match out_buffers.next() { 100 | None => return, 101 | Some(buffer) => buffer, 102 | }; 103 | // Fill it with silence. 104 | out_buffer_first.silence(); 105 | // Sum all input buffers onto the first output buffer. 106 | for input in inputs.values() { 107 | for in_buffer in input.buffers() { 108 | dasp_slice::add_in_place(out_buffer_first, in_buffer); 109 | } 110 | } 111 | // Write the first output buffer to the rest. 112 | for out_buffer in out_buffers { 113 | out_buffer.copy_from_slice(out_buffer_first); 114 | } 115 | } 116 | fn send_msg(&mut self, _info: Message) {} 117 | } 118 | -------------------------------------------------------------------------------- /rs/synth/src/node/synth/mod.rs: -------------------------------------------------------------------------------- 1 | mod pattern_synth; 2 | pub use pattern_synth::*; 3 | mod msgsynth; 4 | pub use msgsynth::*; 5 | -------------------------------------------------------------------------------- /rs/wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "glicol-wasm" 3 | version = "0.1.0" 4 | authors = ["chaosprint "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [lib] 10 | crate-type = ["cdylib"] 11 | 12 | [dependencies] 13 | lazy_static = "1.2.0" 14 | glicol = { path = "../main", features = ["use-samples", "use-meta", "wasm-bindgen"] } 15 | pest = { workspace = true } 16 | wasm-bindgen = { version = "0.2.92", default-features = false } 17 | -------------------------------------------------------------------------------- /rs/wasm/build.bat: -------------------------------------------------------------------------------- 1 | cargo build --target wasm32-unknown-unknown --release 2 | xcopy /Y .\target\wasm32-unknown-unknown\release\glicol_wasm.wasm ..\..\js\src\ -------------------------------------------------------------------------------- /rs/wasm/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | which wasm-pack 2>/dev/null || { echo "Please install wasm-pack with 'cargo install wasm-pack'" && exit 1; } 4 | 5 | if [[ "$1" == "--release" ]] 6 | then 7 | RUSTFLAGS="-Cpanic=abort -Ccodegen-units=1 -Cembed-bitcode=yes -Clto=fat -Cstrip=symbols -Copt-level=z" wasm-pack build --target web --no-typescript --no-pack --release 8 | else 9 | wasm-pack build --target web --no-typescript --no-pack --debug 10 | fi 11 | 12 | which wasm-opt >/dev/null 2>&1 && wasm-opt -Oz ./pkg/glicol_wasm_bg.wasm -o ./pkg/glicol_wasm_bg.wasm 13 | 14 | cp -f ./pkg/glicol_wasm{.js,_bg.wasm} ../../js/src/ 15 | cp -f ./pkg/glicol_wasm{.js,_bg.wasm} ../../js/npm/ 16 | -------------------------------------------------------------------------------- /rs/wasm/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate lazy_static; 3 | 4 | use std::sync::{Mutex, MutexGuard}; 5 | 6 | use glicol::{Engine, EngineError}; 7 | use wasm_bindgen::prelude::wasm_bindgen; 8 | 9 | lazy_static! { 10 | static ref ENGINE: Mutex> = Mutex::new(Engine::<128>::new()); 11 | } 12 | 13 | fn get_engine() -> MutexGuard<'static, Engine<128>> { 14 | ENGINE.lock().unwrap_or_else(|e| e.into_inner()) 15 | } 16 | 17 | #[wasm_bindgen] 18 | pub fn process(size: usize) -> Vec { 19 | let mut engine = get_engine(); 20 | 21 | let engine_out = engine.next_block(vec![]); 22 | 23 | let mut out_buf = vec![0.; size]; 24 | let half_size = size / 2; 25 | out_buf[..half_size].copy_from_slice(&engine_out[0][..half_size]); 26 | out_buf[half_size..].copy_from_slice(&engine_out[1][..half_size]); 27 | 28 | out_buf 29 | } 30 | 31 | #[wasm_bindgen] 32 | pub fn add_sample(name: String, sample: Box<[f32]>, channels: usize, sr: usize) { 33 | let mut engine = ENGINE.lock().unwrap(); 34 | let leaked_sample = Box::leak(sample); 35 | engine.add_sample(&name, leaked_sample, channels, sr); 36 | // engine.update(code); 37 | } 38 | 39 | /// # Safety 40 | /// - `code` must be valid utf-8. If it is not, this function invokes undefined behavior. 41 | #[wasm_bindgen] 42 | pub unsafe fn update(code_buf: &[u8]) -> Vec { 43 | let code = std::str::from_utf8_unchecked(code_buf); 44 | 45 | let mut res = vec![0; RES_BUFFER_SIZE]; 46 | if let Err(e) = get_engine().update_with_code(code) { 47 | write_err_to_buf(e, &mut res); 48 | } 49 | 50 | res 51 | } 52 | 53 | #[wasm_bindgen] 54 | pub fn send_msg(msg: String) { 55 | //, result_ptr: *mut u8 56 | get_engine().send_msg(&msg); 57 | } 58 | 59 | #[wasm_bindgen] 60 | pub fn live_coding_mode(io: bool) { 61 | get_engine().livecoding = io; 62 | } 63 | 64 | #[wasm_bindgen] 65 | pub fn set_bpm(bpm: f32) { 66 | get_engine().set_bpm(bpm); 67 | // engine.reset(); 68 | } 69 | 70 | #[wasm_bindgen] 71 | pub fn set_track_amp(amp: f32) { 72 | get_engine().set_track_amp(amp); 73 | } 74 | 75 | #[wasm_bindgen] 76 | pub fn set_sr(sr: f32) { 77 | get_engine().set_sr(sr as usize); 78 | } 79 | 80 | #[wasm_bindgen] 81 | pub fn set_seed(seed: f32) { 82 | get_engine().set_seed(seed as usize); 83 | } 84 | 85 | #[wasm_bindgen] 86 | pub fn reset() { 87 | get_engine().reset(); 88 | } 89 | 90 | const RES_BUFFER_SIZE: usize = 256; 91 | 92 | fn write_err_to_buf(err: EngineError, result: &mut [u8]) { 93 | result[0] = match err { 94 | EngineError::ParsingError(_) => 1, 95 | EngineError::NonExistSample(_) => 2, 96 | EngineError::NonExistReference(_) => 3, 97 | }; 98 | 99 | let error = match err { 100 | EngineError::ParsingError(v) => { 101 | let location = match v.location { 102 | pest::error::InputLocation::Pos(u) => u, 103 | pest::error::InputLocation::Span((s, _)) => s, 104 | }; 105 | let (line, col) = match v.line_col { 106 | pest::error::LineColLocation::Pos(u) => u, 107 | pest::error::LineColLocation::Span(u, _) => u, 108 | }; 109 | let (positives, negatives) = match &v.variant { 110 | pest::error::ErrorVariant::ParsingError { 111 | positives, 112 | negatives, 113 | } => (positives, negatives), 114 | _ => panic!("unknown parsing error"), 115 | }; 116 | 117 | format!( 118 | "pos[{:?}], line[{:?}], col[{:?}], positives{:?}, negatives{:?}", 119 | location, line, col, positives, negatives 120 | ) 121 | } 122 | EngineError::NonExistSample(v) => format!("There is no sample named {v}"), 123 | EngineError::NonExistReference(v) => format!("There is no reference named {v}"), 124 | }; 125 | 126 | let s = error.as_bytes(); 127 | let max_bytes_len = s.len().min(RES_BUFFER_SIZE - 2); 128 | result[2..][..max_bytes_len].copy_from_slice(&s[..max_bytes_len]); 129 | } 130 | --------------------------------------------------------------------------------