├── .gitattributes ├── index.js ├── test ├── case │ ├── export.js │ ├── comments.js │ ├── strings.js │ ├── memory.js │ ├── break.js │ ├── ranges.js │ ├── import.js │ ├── units.js │ ├── defer.js │ ├── number.js │ ├── examples.js │ ├── scope.js │ ├── funcs.js │ ├── static.js │ ├── cond.js │ ├── readme.js │ ├── vars.js │ ├── perf.js │ ├── operators.js │ ├── groups.js │ ├── loops.js │ ├── array.js │ └── parse.js ├── index.js ├── util.js └── index.html ├── examples ├── sobel.z ├── processor.js ├── Wiebe-Marten Wijnja - Predestined Fate.z └── mono.z ├── .github └── workflows │ └── test.yml ├── LICENSE ├── package.json ├── .gitignore ├── cli.js ├── src ├── util.js ├── parse.js ├── build.js ├── stdlib.js └── precompile.js ├── docs ├── cases.md ├── todo.md └── examples.md ├── README.md └── lib └── wat-compiler.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import parse from './src/parse.js' 2 | import precompile from './src/precompile.js' 3 | import compile from './src/compile.js' 4 | 5 | export { parse, precompile, compile } 6 | export default compile -------------------------------------------------------------------------------- /test/case/export.js: -------------------------------------------------------------------------------- 1 | import t, { almost, is, not, ok, same, throws } from 'tst' 2 | import compileZ from '../../src/compile.js' 3 | import { compileWat } from '../util.js' 4 | 5 | t.skip('export: - no junk exports', () => { 6 | let wat = compileZ(`w()=(); y=[1]; x()=(*i=0), z=[y[0], v=[1]]`) 7 | let mod = compileWat(wat) 8 | same(Object.keys(mod.instance.exports), ['memory', 'x', 'z']) 9 | }) 10 | -------------------------------------------------------------------------------- /test/case/comments.js: -------------------------------------------------------------------------------- 1 | import t, { almost, is, not, ok, same, throws } from 'tst' 2 | import compileZ from '../../src/compile.js' 3 | import { compileWat } from '../util.js' 4 | 5 | t('comments: base', t => { 6 | let wat = compileZ(`x(a,w,h)=( 7 | a=1 8 | ;; a=2 9 | ), y()=( 10 | ;; ( 11 | )`) 12 | let mod = compileWat(wat) 13 | let { x } = mod.instance.exports 14 | is(x(), 1) 15 | }) 16 | -------------------------------------------------------------------------------- /test/case/strings.js: -------------------------------------------------------------------------------- 1 | import t, { almost, is, not, ok, same, throws } from 'tst' 2 | import compileZ from '../../src/compile.js' 3 | import { compileWat } from '../util.js' 4 | 5 | t.todo('strings: core', t => { 6 | let wat = compileZ(`x = "abc"`) 7 | // console.log(wat) 8 | let mod = compileWat(wat) 9 | let { memory, x } = mod.instance.exports 10 | 11 | let xarr = new Uint8Array(memory.buffer, x.value, 3) 12 | 13 | is(xarr[0], 21, 'a') 14 | is(xarr[1], 22, 'b') 15 | }) 16 | -------------------------------------------------------------------------------- /test/case/memory.js: -------------------------------------------------------------------------------- 1 | import t, { almost, is, not, ok, same, throws } from 'tst' 2 | import compileZ from '../../src/compile.js' 3 | import { compileWat } from '../util.js' 4 | 5 | t.skip('memory: grow', t => { 6 | // FIXME: possibly add option to export internals 7 | let wat = compileZ(`grow()=[..8192]`) 8 | let mod = compileWat(wat) 9 | let { memory, __mem, grow } = mod.instance.exports 10 | for (let i = 1; i < 100; i++) { 11 | is(__mem.value, 65536 * i) 12 | grow() 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /test/case/break.js: -------------------------------------------------------------------------------- 1 | // various break, skip, continue tests 2 | 3 | `(a ? ./ : b)` // throws 4 | `(a ? ./1 : b)` // ok 5 | `(a ? ./ : b.)` // ok 6 | `(a ? ./;)` // ok 7 | `(a ? ./)` // ok 8 | 9 | `(a ? ../ : b)` // throws 10 | `(a ? ../1 : b)` // ok 11 | `(a ? ../ : b.)` // ok 12 | `(a ? ../;)` // ok 13 | `(a ? ../)` // ok 14 | 15 | `((a ? ../) : b)` // throws 16 | `((a ? ../1) : b)` // ok 17 | `((a ? ../) : b.)` // ok 18 | `((a ? ../);)` // ok 19 | `((a ? ../))` // ok 20 | 21 | `((((a ? .../))))` // ok 22 | `((((a ? .../))) b;)` // throws 23 | -------------------------------------------------------------------------------- /test/case/ranges.js: -------------------------------------------------------------------------------- 1 | import t, { almost, is, not, ok, same, throws } from 'tst' 2 | import compileZ from '../../src/compile.js' 3 | import { compileWat } from '../util.js' 4 | 5 | 6 | t('ranges: clamp', t => { 7 | let wat = compileZ(`x = 11 ~ 0..10`) 8 | let mod = compileWat(wat) 9 | is(mod.instance.exports.x.value, 10) 10 | 11 | wat = compileZ(`x = 0 ~ 1..10`) 12 | mod = compileWat(wat) 13 | is(mod.instance.exports.x.value, 1) 14 | 15 | wat = compileZ(`clamp(x) = (x ~ 0..10)`) 16 | mod = compileWat(wat) 17 | is(mod.instance.exports.clamp(11), 10) 18 | is(mod.instance.exports.clamp(-1), 0) 19 | }) 20 | -------------------------------------------------------------------------------- /test/case/import.js: -------------------------------------------------------------------------------- 1 | import t, { almost, is, not, ok, same, throws } from 'tst' 2 | import compileZ from '../../src/compile.js' 3 | import { compileWat } from '../util.js' 4 | 5 | t.todo('import: simple', t => { 6 | // FIXME: need to use external imports, not internal 7 | const imports = { math: { sin: Math.sin, pi: Math.PI } }; 8 | let wat = compileZ(`; pi, sinpi(n=1)=sin(pi*n)`, { imports }) 9 | let mod = compileWat(wat, imports) 10 | let { pi, sinpi } = mod.instance.exports 11 | is(pi.value, Math.PI) 12 | is(sinpi(1 / 2), Math.sin(Math.PI / 2)) 13 | is(sinpi(2), Math.sin(Math.PI * 2)) 14 | }) 15 | 16 | t.todo('import: non-existent', t => { 17 | }) 18 | -------------------------------------------------------------------------------- /test/case/units.js: -------------------------------------------------------------------------------- 1 | import t, { almost, is, not, ok, same, throws } from 'tst' 2 | import compileZ from '../../src/compile.js' 3 | import { compileWat } from '../util.js' 4 | 5 | t('units: core', t => { 6 | let wat = compileZ(` 7 | pi = 3.1415; 8 | 1k = 1000; 1pi = pi; 9 | 1s = 44100; 1m=60s; 1h=60m; 1ms = 0.001s; 10 | a=10.1k, b=2pi, c=1h2m3.5s 11 | `) 12 | 13 | let mod = compileWat(wat) 14 | is(mod.instance.exports.a.value, 10100) 15 | is(mod.instance.exports.b.value, 3.1415 * 2) 16 | is(mod.instance.exports.c.value, 60 * 60 * 44100 + 2 * 60 * 44100 + 3.5 * 44100) 17 | }) 18 | 19 | t.todo('units: units - errors', t => { 20 | // bad expressions 21 | // 22 | compileZ(`1h=1s;1s=44800;`) 23 | compileZ(`1k=x();`) 24 | }) 25 | -------------------------------------------------------------------------------- /examples/sobel.z: -------------------------------------------------------------------------------- 1 | process(data, width, height) = ( 2 | (y = 0..height) |> ( 3 | (y1, y2) = (y+1, y+2); 4 | (y1, y2) >= height ? (y1, y2) = height - 1; 5 | (ptr0, ptr1, ptr2) = (y, y1, y2) * width; 6 | 7 | (x = 0..width) |> ( 8 | (x1, x2) = (x+1, x+2); 9 | (x1, x2) >= width ? (x1, x2) = width - 1; 10 | 11 | ;; Extract values from image 12 | (val0, val1, val2, val3, val5, val6, val7, val8) = 13 | data[ptr0 + (x, x1, x2, x, x2, x, x1, x2)]; 14 | 15 | ;; Apply Sobel kernel 16 | (gx, gy) = ( 17 | (val5 - val3) * 2 + (val2 + val8) - (val0 + val6), 18 | (val7 - val1) * 2 + (val6 + val8) - (val0 + val2) 19 | ); 20 | 21 | mag ~= ((gx * gx + gy * gy) ** 0.5) .. 1; 22 | data[ptr0 + x] = mag; 23 | ) 24 | ) 25 | ) 26 | -------------------------------------------------------------------------------- /test/case/defer.js: -------------------------------------------------------------------------------- 1 | import t, { almost, is, not, ok, same, throws } from 'tst' 2 | import compileZ from '../../src/compile.js' 3 | import { compileWat } from '../util.js' 4 | 5 | t('defer: - basics', () => { 6 | let wat = compileZ(`f()=(*t=0;^t++;t)`) 7 | let mod = compileWat(wat), f = mod.instance.exports.f; 8 | is([f(), f(), f()], [0, 1, 2]) 9 | 10 | wat = compileZ(`f()=(*t=0;/t;^++t)`) 11 | mod = compileWat(wat), f = mod.instance.exports.f; 12 | is([f(), f(), f()], [0, 1, 2], 'after return') 13 | 14 | wat = compileZ(`f()=(*t=0;t;^++t;t%=256)`) 15 | mod = compileWat(wat), f = mod.instance.exports.f; 16 | is([f(), f(), f()], [0, 1, 2], 'after return') 17 | }) 18 | 19 | t.skip('defer: - errors', () => { 20 | // throws(() => compileZ(`f()=(*t=0;/t;^++t)`), 'defer after return') 21 | // throws(() => compileZ(`f()=(^++t;*t=0;)`), 'defer before stateful') 22 | }) 23 | -------------------------------------------------------------------------------- /test/case/number.js: -------------------------------------------------------------------------------- 1 | import t, { almost, is, not, ok, same, throws } from 'tst' 2 | import compileZ from '../../src/compile.js' 3 | import { compileWat } from '../util.js' 4 | 5 | t('number: float', t => { 6 | let wat = compileZ(`a = 3.140, b = 0.00123, c = -.5`) 7 | let mod = compileWat(wat) 8 | let { a, b, c, d, e } = mod.instance.exports 9 | is(a.value, 3.14) 10 | is(b.value, 0.00123) 11 | is(c.value, -0.5) 12 | }) 13 | 14 | t('number: scientific', t => { 15 | let wat = compileZ(`a = 3.14e2, b = 2.5e-3, c = 5e3;`) 16 | let mod = compileWat(wat) 17 | let { a, b, c } = mod.instance.exports 18 | is(a.value, 3.14e2) 19 | is(b.value, 2.5e-3) 20 | is(c.value, 5e3) 21 | }) 22 | 23 | t('number: int', t => { 24 | let wat = compileZ(`a = 42, b = 0b101, c = 0o052, d = 0x2A;`) 25 | let mod = compileWat(wat) 26 | let { a, b, c, d } = mod.instance.exports 27 | is(a.value, 42) 28 | is(b.value, 0b101) 29 | is(c.value, 0o052) 30 | is(d.value, 0x2a) 31 | }) 32 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs 3 | 4 | name: test 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [16.x, 18.x] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v3 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | cache: 'npm' 29 | - run: npm ci 30 | - run: npm run build --if-present 31 | - run: npm test 32 | -------------------------------------------------------------------------------- /test/case/examples.js: -------------------------------------------------------------------------------- 1 | import t, { almost, is, not, ok, same, throws } from 'tst' 2 | import compileZ from '../../src/compile.js' 3 | import { compileWat } from '../util.js' 4 | 5 | t.todo('example: audio-gain', t => { 6 | let wat = compileZ(` 7 | blockSize = 1024; 8 | gain = ([blockSize]data, volume ~ 0..1000) -> [data | x -> x * volume]; 9 | `) 10 | let mod = compileWat(wat) 11 | let { gain } = mod.instance.exports 12 | is(gain([1, 2, 3], 2), [2, 4, 6]) 13 | 14 | // let wat = compileZ(` 15 | // blockSize = 1024; 16 | // gain = ([2, blockSize]data, volume ~ 0..1000) -> [data | ch -> (ch | x -> x * volume)]; 17 | // `) 18 | }) 19 | 20 | t.todo('example: sine gen', t => { 21 | let wat = compileZ(analyse(parse(` 22 | pi = 3.14; 23 | pi2 = pi*2; 24 | sampleRate = 44100; 25 | 26 | sine(freq) = ( 27 | *phase=0; 28 | phase += freq * pi2 / sampleRate; 29 | [sin(phase)]. 30 | ) 31 | `))) 32 | console.log(wat) 33 | 34 | is(wat, []) 35 | }) 36 | -------------------------------------------------------------------------------- /test/case/scope.js: -------------------------------------------------------------------------------- 1 | import t, { almost, is, not, ok, same, throws } from 'tst' 2 | import compileZ from '../../src/compile.js' 3 | import { compileWat } from '../util.js' 4 | 5 | t('scope: early returns', t => { 6 | let wat = compileZ(`x(a)=(a ?/-a; 123), y(a)=(a?/12;13.4)`) 7 | let mod = compileWat(wat) 8 | let { memory, x, y, z } = mod.instance.exports 9 | is(x(0), 123); 10 | is(x(1), -1); 11 | is(y(0), 13.4); 12 | is(y(1), 12); 13 | 14 | wat = compileZ(`z(a)=(a ? /11 : /12.1; /13)`) 15 | mod = compileWat(wat); 16 | z = mod.instance.exports.z 17 | is(z(0), 12.1); 18 | is(z(1), 11); 19 | 20 | wat = compileZ(`z(a)=(/ a ? 11 : 12.1; /13)`) 21 | mod = compileWat(wat); 22 | z = mod.instance.exports.z 23 | is(z(0), 12.1); 24 | is(z(1), 11); 25 | 26 | wat = compileZ(`y(a,b)=(a ? /b; a,b)`) 27 | mod = compileWat(wat) 28 | y = mod.instance.exports.y 29 | is(y(1), [NaN, NaN]) 30 | is(y(1, 2), [2, NaN]) 31 | is(y(0, 1), [0, 1]) 32 | }) 33 | 34 | t.todo('scope: break/continue', t => { 35 | 36 | }) 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Dmitry Ivanov 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "esbuild": "^0.24.0", 4 | "sprae": "^10.11.2", 5 | "tst": "^7.2.0" 6 | }, 7 | "dependencies": { 8 | "arg": "^5.0.2", 9 | "subscript": "^8.6.0", 10 | "watr": "^2.4.1" 11 | }, 12 | "name": "piezo", 13 | "description": "Low-level language for audio/signal purposes", 14 | "version": "0.0.0", 15 | "main": "index.js", 16 | "bin": "cli.js", 17 | "directories": { 18 | "doc": "docs", 19 | "test": "test" 20 | }, 21 | "type": "module", 22 | "scripts": { 23 | "test": "node test", 24 | "watch": "esbuild --bundle --watch ./index.js --outfile=./dist/piezo.js --format=esm", 25 | "build": "esbuild --bundle --minify ./index.js --outfile=./dist/piezo.js --format=esm" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "git+https://github.com/dy/piezo.git" 30 | }, 31 | "keywords": [ 32 | "sound", 33 | "audio", 34 | "dsp" 35 | ], 36 | "author": "dmitry iv", 37 | "license": "MIT", 38 | "bugs": { 39 | "url": "https://github.com/dy/piezo/issues" 40 | }, 41 | "homepage": "https://github.com/dy/piezo#readme" 42 | } 43 | -------------------------------------------------------------------------------- /examples/processor.js: -------------------------------------------------------------------------------- 1 | import piezo from '../dist/piezo.js' 2 | // import watr from '../node_modules/watr/watr.js' 3 | import watr from 'https://unpkg.com/watr' 4 | 5 | class MeloProcessor extends AudioWorkletProcessor { 6 | f() { return Math.random() * 2 - 1 } 7 | // f() { return t * (((t >> 12) | (t >> 8)) & (63 & (t >> 4))) } 8 | constructor(...args) { 9 | super(...args); 10 | this.port.onmessage = async (e) => { 11 | console.log('received', e.data); 12 | const wast = piezo(e.data) 13 | const buffer = watr(wast) 14 | const module = await WebAssembly.compile(buffer) 15 | const instance = await WebAssembly.instantiate(module); 16 | this.f = instance.exports.f 17 | console.log('compiled', this.f(), this.f(), this.f(), this.f(), this.f(), this.f(), this.f(), this.f(), this.f(), this.f(), this.f(), this.f(), this.f(), this.f(), this.f(), this.f(), this.f(), this.f(), this.f(), this.f()) 18 | }; 19 | } 20 | process(inputs, outputs, parameters) { 21 | const output = outputs[0]; 22 | output.forEach((channel) => { 23 | for (let i = 0; i < channel.length; i++) { 24 | channel[i] = this.f() 25 | } 26 | }); 27 | return true; 28 | } 29 | } 30 | 31 | registerProcessor("processor", MeloProcessor); 32 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import './case/array.js' 2 | import './case/comments.js' 3 | import './case/defer.js' 4 | import './case/examples.js' 5 | import './case/export.js' 6 | import './case/funcs.js' 7 | import './case/groups.js' 8 | import './case/import.js' 9 | import './case/loops.js' 10 | import './case/number.js' 11 | import './case/memory.js' 12 | import './case/operators.js' 13 | import './case/parse.js' 14 | import './case/perf.js' 15 | import './case/ranges.js' 16 | import './case/readme.js' 17 | import './case/scope.js' 18 | import './case/static.js' 19 | import './case/strings.js' 20 | import './case/units.js' 21 | import './case/vars.js' 22 | 23 | import t from 'tst' 24 | import { compileWat } from './util.js' 25 | 26 | 27 | t('debugs', t => { 28 | const memory = new WebAssembly.Memory({ initial: 1 }); 29 | const importObject = { x: { y: () => 123, z: 123 } }; 30 | let { instance } = compileWat(` 31 | ;; Define a function that returns an i32 by default, 32 | ;; but may return an f32 early 33 | (func $test (param $a f64)(result i32) 34 | (i32.const nan) 35 | ) 36 | 37 | ;; Export the function 38 | (export "test" (func $test)) 39 | `, importObject) 40 | console.log(instance.exports.test()) 41 | // instance.exports.x(instance.exports.x(instance.exports.cb)) 42 | }) 43 | -------------------------------------------------------------------------------- /test/case/funcs.js: -------------------------------------------------------------------------------- 1 | import t, { almost, is, not, ok, same, throws } from 'tst' 2 | import compileZ from '../../src/compile.js' 3 | import { compileWat } from '../util.js' 4 | 5 | t.todo('funcs: oneliners', t => { 6 | let wat, mod 7 | 8 | wat = compileZ(`mult(a, b=2) = a * b`) 9 | mod = compileWat(wat) 10 | is(mod.instance.exports.mult(2, 4), 8) 11 | is(mod.instance.exports.mult(2), 4) 12 | 13 | mod = compileWat(compileZ(` mult(a, b) = (a * b)`)) 14 | is(mod.instance.exports.mult(2, 4), 8) 15 | 16 | console.log('------') 17 | mod = compileWat(compileZ(` mult(a, b) = (b; a * b)`)) 18 | is(mod.instance.exports.mult(2, 4), 8) 19 | 20 | mod = compileWat(compileZ(` mult(a, b) = (b; a * b;)`)) 21 | is(mod.instance.exports.mult(2, 4), 8) 22 | 23 | mod = compileWat(compileZ(` mult(a, b) = (b; a * b; /)`)) 24 | is(mod.instance.exports.mult(2, 4), undefined) 25 | 26 | mod = compileWat(compileZ(`f()=( *t; ^t++; t; );`)) 27 | is([mod.instance.exports.f(), mod.instance.exports.f()], [0, 1]) 28 | }) 29 | 30 | t('funcs: first line inits local', t => { 31 | let mod = compileWat(compileZ(`a=1, x() = (a=2)`)) 32 | is(mod.instance.exports.a.value, 1) 33 | is(mod.instance.exports.x(), 2) 34 | is(mod.instance.exports.a.value, 1) 35 | 36 | mod = compileWat(compileZ(`a=1, x() = (;a=2)`)) 37 | is(mod.instance.exports.a.value, 1) 38 | is(mod.instance.exports.x(), 2) 39 | is(mod.instance.exports.a.value, 2) 40 | }) 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # parcel-bundler cache (https://parceljs.org/) 61 | .cache 62 | 63 | # next.js build output 64 | .next 65 | 66 | # nuxt.js build output 67 | .nuxt 68 | 69 | # vuepress build output 70 | .vuepress/dist 71 | 72 | # Serverless directories 73 | .serverless 74 | 75 | # FuseBox cache 76 | .fusebox/ 77 | .DS_Store 78 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import arg from "arg"; 4 | import { readFile, writeFile } from "fs/promises"; 5 | import { compile } from "./index.js"; 6 | import Wabt from './lib/wabt.js' 7 | 8 | const argv = arg({ 9 | '--output': String, 10 | '--text': Boolean, 11 | '-o': '--output', 12 | '-t': '--text' 13 | }); 14 | 15 | const help = 16 | ` 17 | Usage: mell -o 18 | 19 | Description: 20 | Compiles mell source file to wasm binary output. 21 | 22 | Repository: 23 | https://github.com/audio-lab/mell 24 | 25 | Options: 26 | --output, -o Output file path 27 | --text, -t [TODO] Output WASM text instead of binary 28 | 29 | Example: 30 | mell input.s -o output.wasm 31 | ` 32 | 33 | const path = argv._?.[0], outpath = argv['--output'] 34 | 35 | if (!path || !outpath) { 36 | console.log(help); 37 | process.exit(1); // 1 indicates an error occurred, any non-zero value can be used 38 | } 39 | 40 | const code = await readFile(path, 'utf8'); 41 | 42 | const wabt = await Wabt(); 43 | const wasmCode = compile(code); 44 | 45 | const wasmModule = wabt.parseWat('inline', wasmCode, { 46 | // simd: true, 47 | // reference_types: true, 48 | // gc: true, 49 | bulk_memory: true, 50 | // function_references: true 51 | }) 52 | 53 | const binary = wasmModule.toBinary({ 54 | log: true, 55 | canonicalize_lebs: true, 56 | relocatable: false, 57 | write_debug_names: false, 58 | }) 59 | 60 | writeFile(outpath, binary.buffer) 61 | 62 | -------------------------------------------------------------------------------- /test/case/static.js: -------------------------------------------------------------------------------- 1 | import t, { almost, is, not, ok, same, throws } from 'tst' 2 | import compileCode from '../../src/compile.js' 3 | import { compileWat } from '../util.js' 4 | 5 | t('static: basic', t => { 6 | let wat = compileCode(`x()=(*i=0;i++)`) 7 | let mod = compileWat(wat) 8 | let { x } = mod.instance.exports 9 | is(x(), 0) 10 | is(x(), 1) 11 | is(x(), 2) 12 | 13 | wat = compileCode(`x()=(*i;i++;i)`) 14 | mod = compileWat(wat) 15 | x = mod.instance.exports.x 16 | is(x(), NaN) 17 | is(x(), NaN) 18 | }) 19 | 20 | 21 | t('static: array init', t => { 22 | let wat = compileCode(`x()=(*i=[..2]; i[0]++ + i[1]++), y()=x()`) 23 | let mod = compileWat(wat) 24 | let { x, y, memory } = mod.instance.exports 25 | is(x(), 0) 26 | is(x(), 2) 27 | is(x(), 4) 28 | is(y(), 6) 29 | is(y(), 8) 30 | is(y(), 10) 31 | }) 32 | 33 | t('static: group init', t => { 34 | let wat = compileCode(`x()=(*(i=0,j=1,k=2);i,j,k;^(i,j,k)++);`) 35 | let mod = compileWat(wat) 36 | let { x } = mod.instance.exports 37 | is(x(), [0, 1, 2]) 38 | is(x(), [1, 2, 3]) 39 | }) 40 | 41 | t('static: multiple states', t => { 42 | let wat = compileCode(`x()=(*i=0,*j=1,*a=[..2]; i++ + j++ + a[0]++ + a[1]++);`) 43 | let mod = compileWat(wat) 44 | let { x } = mod.instance.exports 45 | is(x(), 1) 46 | is(x(), 5) 47 | is(x(), 9) 48 | is(x(), 13) 49 | }) 50 | 51 | t.todo('static: mixed deps', t => { 52 | let wat = compileCode(`x()=(*i=0,i++); y()=(*a=[0,1];x()+a[0]+a[1]++); z()=(x()+y());`) 53 | let mod = compileWat(wat) 54 | let { x, y, z } = mod.instance.exports 55 | }) 56 | -------------------------------------------------------------------------------- /test/util.js: -------------------------------------------------------------------------------- 1 | import Wabt from '../lib/wabt.js' 2 | import watr from 'watr'; 3 | 4 | 5 | // convert wast code to binary 6 | const wabt = await Wabt() 7 | export function compileWat(code, imports = {}) { 8 | code = 9 | '(func $i32.log (import "imports" "log") (param i32))\n' + 10 | '(func $i32.log2 (import "imports" "log") (param i32 i32))\n' + 11 | '(func $i32.log3 (import "imports" "log") (param i32 i32 i32))\n' + 12 | '(func $f64.log (import "imports" "log") (param f64))\n' + 13 | '(func $f64.log2 (import "imports" "log") (param f64 f64))\n' + 14 | '(func $f64.log3 (import "imports" "log") (param f64 f64 f64))\n' + 15 | '(func $i64.log (import "imports" "log") (param i64))\n' + 16 | code 17 | 18 | // WABT compilation 19 | // const wasmModule = wabt.parseWat('inline', code, { 20 | // simd: true, 21 | // reference_types: true, 22 | // gc: true, 23 | // bulk_memory: true 24 | // // function_references: true 25 | // }) 26 | // const { buffer } = wasmModule.toBinary({ 27 | // log: true, 28 | // canonicalize_lebs: true, 29 | // relocatable: false, 30 | // write_debug_names: false, 31 | // }) 32 | // wasmModule.destroy() 33 | 34 | // WATR compilation 35 | const buffer = watr(code) 36 | 37 | const config = { 38 | imports: { 39 | ...(imports.imports || {}), 40 | log(...args) { console.log(...args); }, 41 | }, 42 | ...imports 43 | } 44 | 45 | // sync instance - limits buffer size to 4kb 46 | const module = new WebAssembly.Module(buffer) 47 | return { module, instance: new WebAssembly.Instance(module, config) } 48 | 49 | // async instance 50 | // return WebAssembly.instantiate(binary.buffer, config) 51 | } 52 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 29 | 30 | -------------------------------------------------------------------------------- /test/case/cond.js: -------------------------------------------------------------------------------- 1 | import t, { almost, is, not, ok, same, throws } from 'tst' 2 | import compileZ from '../../src/compile.js' 3 | import { compileWat } from '../util.js' 4 | 5 | t('condition: base', t => { 6 | let wat, mod 7 | wat = compileZ(`a=1;b=2;c=a?1:2`) 8 | mod = compileWat(wat) 9 | is(mod.instance.exports.c.value, 1) 10 | 11 | wat = compileZ(`a=1;b=2;a?c=b;c`) 12 | mod = compileWat(wat) 13 | is(mod.instance.exports.c.value, 2) 14 | 15 | wat = compileZ(`a=0;b=2;a?c=b;c`) 16 | mod = compileWat(wat) 17 | is(mod.instance.exports.c.value, 0) 18 | 19 | wat = compileZ(`a=0.0;b=2.1;a?c=b;c`) 20 | mod = compileWat(wat) 21 | is(mod.instance.exports.c.value, 0) 22 | 23 | wat = compileZ(`a=0.1;b=2.1;a?c=b;c`) 24 | mod = compileWat(wat) 25 | is(mod.instance.exports.c.value, 2.1) 26 | 27 | wat = compileZ(`a=0.0;b=2.1;c=a?b`) 28 | mod = compileWat(wat) 29 | is(mod.instance.exports.c.value, 0) 30 | 31 | wat = compileZ(`a=0.1;b=2.1;c=a?b`) 32 | mod = compileWat(wat) 33 | is(mod.instance.exports.c.value, 2.1) 34 | 35 | console.log('--------------') 36 | wat = compileZ(`x(px) = (px < 0 ? px = 0; px)`) 37 | mod = compileWat(wat) 38 | is(mod.instance.exports.x(-10), 0) 39 | is(mod.instance.exports.x(10), 10) 40 | }) 41 | 42 | t('compile: conditions - or/and', t => { 43 | let wat, mod 44 | wat = compileZ(`z=1||0`) 45 | mod = compileWat(wat) 46 | is(mod.instance.exports.z.value, 1) 47 | wat = compileZ(`z=1.2||0.0`) 48 | mod = compileWat(wat) 49 | is(mod.instance.exports.z.value, 1.2) 50 | wat = compileZ(`z=1.2||0`) 51 | mod = compileWat(wat) 52 | is(mod.instance.exports.z.value, 1.2) 53 | wat = compileZ(`z=1||0.0`) 54 | mod = compileWat(wat) 55 | is(mod.instance.exports.z.value, 1) 56 | wat = compileZ(`z=1.2&&0.2`) 57 | mod = compileWat(wat) 58 | is(mod.instance.exports.z.value, 0.2) 59 | wat = compileZ(`z=1&&2`) 60 | mod = compileWat(wat) 61 | is(mod.instance.exports.z.value, 2) 62 | }) 63 | -------------------------------------------------------------------------------- /test/case/readme.js: -------------------------------------------------------------------------------- 1 | import t, { almost, is, not, ok, same, throws } from 'tst' 2 | import compileZ from '../../src/compile.js' 3 | import { compileWat } from '../util.js' 4 | 5 | t('readme: numbers', t => { 6 | let numbers = compileZ(` 7 | a=16, b=0x10, c=0b0; ;; int, hex or binary 8 | d=16.0, e=.1, f=1e3, g=2e-3; ;; float 9 | a,b,c,d,e,f,g 10 | `, { imports: {} }) 11 | let { a, b, c, d, e, f, g } = compileWat(numbers, {}).instance.exports 12 | is(a.value, 16), is(b.value, 0x10), is(c.value, 0b0), is(d.value, 16), is(e.value, 0.1), is(f.value, 1e3), is(g.value, 2e-3) 13 | }) 14 | 15 | t('readme: standard operators', t => { 16 | let ops = compileZ(` 17 | a=3,b=2,c=1; 18 | ( 19 | o0, o1, o2, o3, o4, o5, o5a, o6, o6a, 20 | o7, o8, o9, o10, 21 | o11, o12, o13, o14, o15, o16, 22 | o17, o18, o19, o20, o21, o22, 23 | o23, o24, o25, o26 24 | ) = ( 25 | a + b, a-b, a*b, a/b, a%b, --a, ++a, b++, b--, ;; arithmetical (float) 26 | a&&b, a||b, !a, a?b:c, ;; logical (boolean) 27 | a>b, a>=b, a>b, a<>> b 30 | ) 31 | `, {}) 32 | 33 | let { o0, o1, o2, o3, o4, o5, o5a, o6, o6a, o7, o8, o9, o10, o11, o12, o13, o14, o15, o16, o17, o18, o19, o20, o21, o22, o23, o24, o25, o26 } = compileWat(ops, {}).instance.exports 34 | is(o0.value, 5), is(o1.value, 1), is(o2.value, 6), is(o3.value, 3 / 2), is(o4.value, 3 % 2) 35 | is(o5.value, 2), is(o5a.value, 3), is(o6.value, 2), is(o6a.value, 3) 36 | is(o7.value, 2), is(o8.value, 3), is(o9.value, 0), is(o10.value, 2) 37 | is(o11.value, 1), is(o12.value, 1), is(o13.value, 0), is(o14.value, 0), is(o15.value, 0), is(o16.value, 1) 38 | is(o17.value, 3 & 2), is(o18.value, 3 | 2), is(o19.value, 3 ^ 2, 'a^b'), is(o20.value, ~3), is(o21.value, 3 >> 2), is(o22.value, 3 << 2) 39 | is(o23.value, 3 ** 2, 'a**b'), is(o24.value, 1, '-a %%%% b'), is(o25.value, rleft(3, 2)), is(o26.value, rright(3, 2)) 40 | function rleft(value, numBits) { 41 | numBits = numBits % 32; 42 | return (value << numBits) | (value >>> (32 - numBits)); 43 | } 44 | function rright(value, numBits) { 45 | numBits = numBits % 32; 46 | return (value >>> numBits) | (value << (32 - numBits)); 47 | } 48 | }) 49 | -------------------------------------------------------------------------------- /test/case/vars.js: -------------------------------------------------------------------------------- 1 | import t, { almost, is, not, ok, same, throws } from 'tst' 2 | import compileZ from '../../src/compile.js' 3 | import { compileWat } from '../util.js' 4 | 5 | t('vars: globals basic', t => { 6 | // TODO: single global 7 | // TODO: multiply wrong types 8 | // TODO: define globals via group (a,b,c). 9 | 10 | // FIXME: undefined variable throws error 11 | // throws(() => compileZ(analyse(parse(`pi2 = pi*2.0;`))), /pi is not defined/) 12 | 13 | let wat = compileZ(` 14 | pi = 3.14; 15 | pi2 = pi*2.0; 16 | sampleRate = 44100; 17 | sampleRate, pi, pi2 18 | `) 19 | let mod = compileWat(wat) 20 | is(mod.instance.exports.pi.value, 3.14) 21 | is(mod.instance.exports.pi2.value, 3.14 * 2) 22 | is(mod.instance.exports.sampleRate.value, 44100) 23 | }) 24 | 25 | t('vars: globals multiple', () => { 26 | // FIXME: must throw 27 | // let wat = compileZ(`pi, pi2, sampleRate = 3.14, 3.14*2, 44100`) 28 | let wat = compileZ(`(pi, pi2, sampleRate) = (3.14, 3.14*2, 44100)`) 29 | let mod = compileWat(wat) 30 | is(mod.instance.exports.pi.value, 3.14) 31 | is(mod.instance.exports.pi2.value, 3.14 * 2) 32 | is(mod.instance.exports.sampleRate.value, 44100) 33 | 34 | wat = compileZ(`(a,b) = (-1, -1.0)`) 35 | mod = compileWat(wat) 36 | is(mod.instance.exports.a.value, -1) 37 | is(mod.instance.exports.b.value, -1) 38 | 39 | wat = compileZ(`(a,b) = (-1, -1.0)`) 40 | mod = compileWat(wat) 41 | is(mod.instance.exports.a.value, -1) 42 | is(mod.instance.exports.b.value, -1) 43 | }) 44 | 45 | 46 | t('vars: globals misc', t => { 47 | let wat, x; 48 | x = compileWat(compileZ(`x;x`)).instance.exports.x // unknown type falls to f64 49 | x = compileWat(compileZ(`x=1;x`)).instance.exports.x // int type 50 | x = compileWat(compileZ(`x=1.0;x`)).instance.exports.x // float type 51 | x = compileWat(compileZ(`x()=1;x`)).instance.exports.x // func type 52 | x = compileWat(compileZ(`x=[];x`)).instance.exports.x // arr type 53 | x = compileWat(compileZ(`x;x=1;x`)).instance.exports.x // late-int type 54 | x = compileWat(compileZ(`x;x=1.0;x`)).instance.exports.x // late-float type 55 | // x = compileWat(compileZ(`x;x()=1;x`)).instance.exports.x // late-func type 56 | x = compileWat(compileZ(`x;x=[];x`)).instance.exports.x // late-arr type 57 | }) 58 | 59 | t('vars: scoped globals', t => { 60 | is(compileWat(compileZ(`((x = 2); x)`)).instance.exports.x.value, 2) 61 | }) 62 | -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | 2 | import { FLOAT, INT } from "./parse.js" 3 | import { print } from "watr" 4 | 5 | // show error meaningfully 6 | // FIXME: display line, fn name and other useful info 7 | export function err(msg) { 8 | // Promise.resolve().then(() => { 9 | throw Error((msg || 'Bad syntax')) 10 | // }) 11 | } 12 | 13 | export function pretty(str) { 14 | return print(str + '', { indent: ' ', newline: '\n' }) 15 | } 16 | 17 | // collect ids used within node 18 | export function ids(node, set = new Set) { 19 | if (node?.ids) return node.ids 20 | if (typeof node === 'string') set.add(node) 21 | else if (Array.isArray(node)) { 22 | // 1k, 1.2 - just ignore 23 | if (node[0] === INT || node[0] === FLOAT) return 24 | 25 | // f(x) = (...ignore...) 26 | if (node[0] === '=' && node[1][0] === '(') return set.add(node[1][1]), set 27 | 28 | // ignore[ignore] 29 | // if (node[0]==='[]') return set 30 | 31 | // for each child - collect ids within it 32 | for (let i = 1; i < node.length; i++) ids(node[i], set) 33 | node.ids = set 34 | } 35 | return set 36 | } 37 | 38 | // test if 2 sets have common members 39 | export function intersect(set1, set2) { 40 | for (const item of set1) if (set2.has(item)) return true; 41 | return false; 42 | } 43 | 44 | // wast code from tree 45 | export function stringify(tree) { 46 | if (typeof tree === 'string') return tree 47 | let [op, a, ...args] = tree 48 | if (op === INT || op === FLOAT) return a + args.join('') 49 | return `${stringify(a)} ${op} ${args.length ? stringify(args[0]) : ''}` 50 | } 51 | function fetchSource(path) { 52 | let fullPath = import.meta.resolve(coreModules[path]) 53 | let xhr = new XMLHttpRequest() 54 | xhr.open('GET', fullPath, false /* SYNCHRONOUS XHR FTW :) */) 55 | xhr.send(null) 56 | // result = (nodeRequire ('fs').readFileSync (path, { encoding: 'utf8' })) 57 | return xhr.responseText 58 | } 59 | 60 | 61 | // uint8 array to string 62 | export function u82s(uint8Array) { 63 | let result = ''; 64 | for (const byte of uint8Array) { 65 | // Convert uint8 value to its ASCII character equivalent 66 | const asciiChar = String.fromCharCode(byte); 67 | // Handle special characters that need escaping 68 | if (asciiChar === '"' || asciiChar === '\\') { 69 | result += '\\' + asciiChar; 70 | } else if (byte >= 32 && byte <= 126) { 71 | result += asciiChar; // Regular printable ASCII characters 72 | } else { 73 | result += `\\${byte.toString(16).padStart(2, '0')}`; // Non-printable characters 74 | } 75 | } 76 | return result; 77 | } 78 | -------------------------------------------------------------------------------- /test/case/perf.js: -------------------------------------------------------------------------------- 1 | 2 | import t, { is, not, ok, same, throws } from 'tst' 3 | import compile from '../../src/compile.js' 4 | import { compileWat } from '../util.js' 5 | 6 | t.todo('perf: sobel', t => { 7 | // Reference: 8 | // https://github.com/mattdesl/wasm-bench/blob/main/src/javascript/process.js 9 | compile(` 10 | get_pixel(data, px, py, columns, rows) = ( 11 | px < 0 ? px = 0; 12 | py < 0 ? py = 0; 13 | px >= columns ? px = columns - 1; 14 | py >= rows ? py = rows - 1; 15 | data[px + py * columns]; 16 | ), 17 | 18 | process(data, width, height) = ( 19 | 0..height |> ( y=_; 20 | 0..width |> ( x=_; 21 | ;; Extract values from image 22 | (val0, val1, val2, val3, val5, val6, val7, val8) = ( 23 | get_pixel(data, x, y, width, height), 24 | get_pixel(data, x + 1, y, width, height), 25 | get_pixel(data, x + 2, y, width, height), 26 | get_pixel(data, x, y + 1, width, height), 27 | get_pixel(data, x + 2, y + 1, width, height), 28 | get_pixel(data, x, y + 2, width, height), 29 | get_pixel(data, x + 1, y + 2, width, height), 30 | get_pixel(data, x + 2, y + 2, width, height), 31 | ); 32 | ;; Apply Sobel kernel 33 | gx = -1 * val0 + -2 * val3 + -1 * val6 + val2 + 2 * val5 + val8; 34 | gy = -1 * val0 + -2 * val1 + -1 * val2 + val6 + 2 * val7 + val8; 35 | mag = (gx * gx + gy * gy) ** .5; 36 | mag ~= 0..1; 37 | data[x + y * width] = mag; 38 | ) 39 | ) 40 | ).` 41 | ) 42 | }) 43 | 44 | t.todo('perf: biquad', t => { 45 | compile(` 46 | @math: pi,cos,sin; ;; import pi, sin, cos from math 47 | 48 | 1pi = pi; ;; define pi units 49 | 1s = 44100; ;; define time units in samples 50 | 1k = 10000; ;; basic si units 51 | 52 | lpf( ;; per-sample processing function 53 | x0, ;; input sample value 54 | freq <= 1..10k = 100, ;; filter frequency, float 55 | Q <= 0.001..3.0 = 1.0 ;; quality factor, float 56 | ) = ( 57 | *(x1, y1, x2, y2) = 0; ;; define filter state 58 | 59 | ;; lpf formula 60 | w = 2pi * freq / 1s; 61 | (sin_w, cos_w) = (sin(w), cos(w)); 62 | a = sin_w / (2.0 * Q); 63 | 64 | (b0, b1, b2) = ((1.0 - cos_w) / 2.0, 1.0 - cos_w, b0); 65 | (a0, a1, a2) = (1.0 + a, -2.0 * cos_w, 1.0 - a); 66 | 67 | (b0, b1, b2, a1, a2) *= 1.0 / a0; 68 | 69 | y0 = b0*x0 + b1*x1 + b2*x2 - a1*y1 - a2*y2; 70 | 71 | (x1, x2) = (x0, x1); ;; shift state 72 | (y1, y2) = (y0, y1); 73 | 74 | y0 ;; return y0 75 | ); 76 | 77 | ;; (0, .1, .3) <| x -> lpf(x, 108, 5) 78 | 79 | lpf. ;; export lpf function, end program 80 | `) 81 | }) 82 | -------------------------------------------------------------------------------- /test/case/operators.js: -------------------------------------------------------------------------------- 1 | import t, { almost, is, not, ok, same, throws } from 'tst' 2 | import compileZ from '../../src/compile.js' 3 | import { compileWat } from '../util.js' 4 | 5 | 6 | t('operator: negatives', t => { 7 | let wat = compileZ(`x=-1`) 8 | let mod = compileWat(wat) 9 | is(mod.instance.exports.x.value, -1) 10 | 11 | wat = compileZ(`x=-1.0`) 12 | mod = compileWat(wat) 13 | is(mod.instance.exports.x.value, -1) 14 | }) 15 | 16 | t('operator: inc/dec', t => { 17 | let wat = compileZ(`x=1; y=x++; x,y`) 18 | let mod = compileWat(wat) 19 | is(mod.instance.exports.x.value, 2) 20 | is(mod.instance.exports.y.value, 1) 21 | 22 | wat = compileZ(`x=1; y=++x; x,y`) 23 | mod = compileWat(wat) 24 | is(mod.instance.exports.x.value, 2) 25 | is(mod.instance.exports.y.value, 2) 26 | 27 | wat = compileZ(`x=0; y=x--; x,y`) 28 | mod = compileWat(wat) 29 | is(mod.instance.exports.x.value, -1) 30 | 31 | wat = compileZ(`x=1; y=x+=2; x,y`) 32 | mod = compileWat(wat) 33 | is(mod.instance.exports.x.value, 3) 34 | is(mod.instance.exports.y.value, 3) 35 | 36 | wat = compileZ(`x=1; y=x-=2; x,y`) 37 | mod = compileWat(wat) 38 | is(mod.instance.exports.x.value, -1) 39 | is(mod.instance.exports.y.value, -1) 40 | }) 41 | 42 | 43 | t('operators: pow', t => { 44 | // static 45 | let wat = compileZ(`x=2**(1/2),y=x**3,z=x**-2`) 46 | let mod = compileWat(wat) 47 | is(mod.instance.exports.x.value, Math.sqrt(2)) 48 | is(mod.instance.exports.y.value, mod.instance.exports.x.value ** 3) 49 | almost(mod.instance.exports.z.value, mod.instance.exports.x.value ** -2) 50 | 51 | // complex 52 | wat = compileZ(`pow(x,y)=(x**y)`) 53 | mod = compileWat(wat) 54 | is(mod.instance.exports.pow(1, 0), 1, '1**0') 55 | is(mod.instance.exports.pow(-1, 0), 1, `-1**0`) 56 | is(mod.instance.exports.pow(1, 1), 1, `1**1`) 57 | is(mod.instance.exports.pow(1, 108), 1, `1**108`) 58 | 59 | is(mod.instance.exports.pow(-1, 1), -1, `-1**1`) 60 | is(mod.instance.exports.pow(-1, 2), 1, `-1**2`) 61 | is(mod.instance.exports.pow(-1, 3), -1, `-1**3`) 62 | 63 | is(mod.instance.exports.pow(0, 10), 0, `0**10`) 64 | is(mod.instance.exports.pow(0, -10), Infinity, `0**-10`) 65 | 66 | is(mod.instance.exports.pow(Infinity, 10), Infinity, `+inf**10`) 67 | is(mod.instance.exports.pow(Infinity, -10), 0, `inf**-10`) 68 | 69 | is(mod.instance.exports.pow(-1.2, 3.4), NaN, `-1.2 ** 3.4`) 70 | is(mod.instance.exports.pow(-1.2, -3.4), NaN, `-1.2 ** -3.4`) 71 | 72 | is(mod.instance.exports.pow(2, Infinity), Infinity, `2**inf`) 73 | is(mod.instance.exports.pow(2, -Infinity), 0, `2**-inf`) 74 | 75 | is(mod.instance.exports.pow(1.2, 3.4), 1.2 ** 3.4, `1.2**3.4`) 76 | is(mod.instance.exports.pow(1.2, -3.4), 1.2 ** -3.4, `1.2**-3.4`) 77 | is(mod.instance.exports.pow(1.23456789, 9.87654321), 1.23456789 ** 9.87654321, `1.23456789 ** 9.87654321`) 78 | }) 79 | 80 | 81 | t('operators: % and %%', t => { 82 | let wat = compileZ(` 83 | x(a,b)=( 84 | a%b, 85 | a%%b 86 | ); 87 | `, {}) 88 | compileWat(wat) 89 | }) 90 | -------------------------------------------------------------------------------- /docs/cases.md: -------------------------------------------------------------------------------- 1 | 2 | * [bytebeat machine](https://dollchan.net/bytebeat/index.html#v3b64q1ZKzk9JVbJS0ijRMlUrsbMz16wBMo1BTEMDTaVaAA==) 3 | * [pelulamu collection](https://web.archive.org/web/20171108183310/http://pelulamu.net/countercomplex/music_formula_collection.txt) 4 | * [erlehmann collection](https://github.com/erlehmann/algorithmic-symphonies) 5 | * [viznut collection](https://github.com/kragen/viznut-music) 6 | * https://www.youtube.com/watch?v=cZrnjp0-Ba8 - for mridanga player 7 | * color-space 8 | * https://github.com/AndrewBelt/WaveEdit 9 | * for mridangam - generate base wave by formula from wavedit, apply adsr filter 10 | * https://github.com/naivesound/glitch 11 | * tinyrave 12 | * https://cowbell.lol/ 13 | * Loudness meters https://github.com/x42/meters.lv2 14 | * Essentia algorithms http://essentia.upf.edu/algorithms_reference.html 15 | * fourier-transform 16 | * TTS obtaining audio 17 | * monolib 18 | * ZZFX 19 | * web-audio-api for node 20 | * [musicdsp](https://github.com/bdejong/musicdsp/tree/master/source) 21 | * [sndkit](https://github.com/paulbatchelor/sndkit) 22 | * [soul](https://soul.dev/) 23 | * https://github.com/gleitz/midi-js-soundfonts 24 | * pink trombone cases https://github.com/zakaton/Pink-Trombone, https://dood.al/pinktrombone/ 25 | * https://github.com/usdivad/mesing, 26 | * https://github.com/tidalcycles/strudel 27 | * https://github.com/felixroos/kabelsalat 28 | * LUFS and other meters https://github.com/klangfreund/LUFSMeter/blob/master/Ebu128LoudnessMeter.cpp 29 | * https://phoboslab.org/log/2023/02/qoa-time-domain-audio-compression 30 | * decoders from audio-decode 31 | * sonic https://github.com/waywardgeek/sonic 32 | * https://github.com/madskjeldgaard/Drums.quark 33 | * https://github.com/austintheriot/hand-crafted-wasm 34 | * https://github.com/ClickHouse/NoiSQL 35 | * https://github.com/LinusU/pokemon-synthesizer 36 | * https://github.com/renzol2/fx 37 | * https://github.com/JoepVanlier/JSFX 38 | * https://github.com/survivejs/audio-katas 39 | * neural networklets 40 | * Alex Rome cases https://www.youtube.com/watch?v=mbnLqOI1yQA 41 | * https://github.com/gmoe/voder/ 42 | * https://github.com/petersalomonsen/javascriptmusic#webassembly-music-in-the-browser 43 | * https://github.com/TimDaub/wasm-synth 44 | * https://github.com/ryohey/signal 45 | * https://github.com/austintheriot/hand-crafted-wasm 46 | * https://github.com/hzdgopher/4klang 47 | * https://github.com/maximecb/noisecraft 48 | * https://github.com/ad-si/awesome-music-production 49 | * https://github.com/Auburn/FastNoiseLite 50 | * pick sound params to match specific sound https://www.youtube.com/watch?v=Hd0KYxotzv8 51 | * https://github.com/felixroos/doughbat?tab=readme-ov-file 52 | * https://github.com/ijc8/alternator?tab=readme-ov-file 53 | * https://github.com/charlieroberts/genish.js 54 | * https://github.com/pac-dev/Teasynth 55 | * [eel2](https://www.cockos.com/EEL2/) 56 | * [jsfx](https://github.com/JoepVanlier/JSFX) 57 | * [BitCrusher](https://github.com/jaz303/bitcrusher) 58 | * [ogg decoder](https://en.wikipedia.org/wiki/HTML5_audio#Supported_audio_coding_formats) 59 | * [comodoro scene database](https://csdb.dk/) 60 | * https://sfbgames.itch.io/chiptone 61 | -------------------------------------------------------------------------------- /test/case/groups.js: -------------------------------------------------------------------------------- 1 | import t, { almost, is, not, ok, same, throws } from 'tst' 2 | import compileZ from '../../src/compile.js' 3 | import { compileWat } from '../util.js' 4 | 5 | t('group: assign cases', t => { 6 | let wat, mod 7 | wat = compileZ(`a=1;b=2;c=(a,b)`) 8 | mod = compileWat(wat) 9 | is(mod.instance.exports.c.value, 2, 'c=(a,b)') 10 | 11 | wat = compileZ(`a=1;b=2,c=3;(b,a)=(c,b)`) 12 | mod = compileWat(wat) 13 | is(mod.instance.exports.b.value, 3, '(b,a)=(c,b)') 14 | is(mod.instance.exports.a.value, 2) 15 | 16 | wat = compileZ(`a=1;b=2;(c,b)=(a,b);a,b,c`) 17 | mod = compileWat(wat) 18 | is(mod.instance.exports.c.value, 1, '(c,b)=(a,b)') 19 | is(mod.instance.exports.b.value, 2) 20 | is(mod.instance.exports.a.value, 1) 21 | 22 | throws(() => { 23 | wat = compileZ(`a=1;b=2;(c,a,b)=(a,b)`) 24 | mod = compileWat(wat) 25 | is(mod.instance.exports.c.value, 1, '(c,a,b)=(a,b)') 26 | is(mod.instance.exports.a.value, 2) 27 | is(mod.instance.exports.b.value, 2) 28 | }, /Mismatch/) 29 | 30 | wat = compileZ(`a=1;b=2;(c,d)=(a,b)`) 31 | mod = compileWat(wat) 32 | is(mod.instance.exports.c.value, 1, '(c,d)=(a,b)') 33 | is(mod.instance.exports.d.value, 2) 34 | 35 | wat = compileZ(`a=1;b=2;a,(b,b)=a`) 36 | mod = compileWat(wat) 37 | is(mod.instance.exports.b.value, 1, '(b,b)=a') 38 | is(mod.instance.exports.a.value, 1) 39 | 40 | wat = compileZ(`a=1;b=2,c;a,(b,c)=a`) 41 | mod = compileWat(wat) 42 | is(mod.instance.exports.b.value, 1, '(b,c)=a') 43 | is(mod.instance.exports.c.value, 1) 44 | 45 | wat = compileZ(`a=1;b=2,c=3;(a,,c)=(b,b,b)`) 46 | mod = compileWat(wat) 47 | is(mod.instance.exports.a.value, 2, '(a,,c)=(b,b,b)') 48 | is(mod.instance.exports.b.value, 2, '(a,,c)=(b,b,b)') 49 | is(mod.instance.exports.c.value, 2) 50 | }) 51 | 52 | t('group: ops cases', t => { 53 | let wat, mod 54 | 55 | wat = compileZ(`f(a) = ((x, y) = (a+2,a-2); x,y)`) 56 | mod = compileWat(wat); 57 | is(mod.instance.exports.f(4), [6, 2], `(a,b)=(c+1,c-1)`); 58 | 59 | wat = compileZ(`f(a,b,h) = (a, b) * h`) 60 | mod = compileWat(wat) 61 | is(mod.instance.exports.f(2, 3, 3), [6, 9], `(a, b) * h`); 62 | 63 | wat = compileZ(`f(a,b,h) = h * (a, b)`) 64 | mod = compileWat(wat) 65 | is(mod.instance.exports.f(2, 3, 3), [6, 9], `h * (a, b)`); 66 | 67 | wat = compileZ(`f(a,b,c,d) = (a, b) * (c, d)`) 68 | mod = compileWat(wat) 69 | is(mod.instance.exports.f(2, 3, 4, 5), [8, 15], `(a,b)*(c,d)`); 70 | 71 | wat = compileZ(`f(a,b) = 2 * (a, b) * 3`) 72 | mod = compileWat(wat) 73 | is(mod.instance.exports.f(2, 3), [12, 18], `2 * (a, b) * 3`); 74 | 75 | wat = compileZ(`f(a,b,c,d) = (2 * (a, b)) * (c, d)`) 76 | mod = compileWat(wat) 77 | is(mod.instance.exports.f(2, 3, 4, 5), [16, 30], `(2 * (a, b)) * (c, d)`); 78 | 79 | wat = compileZ(`f(a,b,c,d) = ((2 * (a, b) * 3) * (c,d))`) 80 | mod = compileWat(wat) 81 | is(mod.instance.exports.f(2, 3, 3, 2), [36, 36], `(2 * (a, b) * 3) * (c, d)`); 82 | 83 | wat = compileZ(`f(a,b,h) = ((a>=h, b>=h) ? (a--, b--); a,b)`) 84 | mod = compileWat(wat) 85 | is(mod.instance.exports.f(2, 3, 3), [2, 2], `(a>=h, b>=h) ? (a--, b--)`); 86 | 87 | wat = compileZ(`f(a,b,h) = ((a,b) * (h + 1))`) 88 | mod = compileWat(wat) 89 | is(mod.instance.exports.f(2, 3, 1), [4, 6], `((a,b) * (h + 1))`); 90 | 91 | wat = compileZ(`f(a,b,h) = ((a,b) >= h ? (a,b) = h-1; (a,b))`) 92 | mod = compileWat(wat) 93 | is(mod.instance.exports.f(1, 3, 3), [1, 2], `(a,b) >= h ? (a,b) = h-1`); 94 | 95 | wat = compileZ(`x=[1,2,3]; (a, b, c) = x[0,1,2]`) 96 | mod = compileWat(wat) 97 | is([mod.instance.exports.a.value, mod.instance.exports.b.value, mod.instance.exports.c.value], [1, 2, 3], `(a,b,c)=x[0,1,2]`); 98 | }) 99 | -------------------------------------------------------------------------------- /test/case/loops.js: -------------------------------------------------------------------------------- 1 | import t, { almost, is, not, ok, same, throws } from 'tst' 2 | import compileZ from '../../src/compile.js' 3 | import { compileWat } from '../util.js' 4 | 5 | t('loops: range global', t => { 6 | let wat, mod, arr 7 | wat = compileZ(`x=[1..3]; 0..x[] |> x[#]=#+1; x`) 8 | mod = compileWat(wat); 9 | let { memory, x } = mod.instance.exports 10 | arr = new Float64Array(memory.buffer, x.value, 3) 11 | is(arr[0], 1) 12 | is(arr[1], 2) 13 | is(arr[2], 0, 'unitialized') 14 | }) 15 | 16 | t('loops: range local', t => { 17 | let wat, mod 18 | wat = compileZ(`x=[1..3], c = 0, fill() = (0..x[] |> (x[#]++;c++))`) 19 | mod = compileWat(wat) 20 | let { memory, x, fill, c } = mod.instance.exports 21 | 22 | let arr = new Float64Array(memory.buffer, x.value, 3) 23 | is(arr[0], 1) 24 | is(arr[1], 2) 25 | 26 | // is(fill(), 3); 27 | fill() 28 | 29 | is(c.value, 2, 'length is ok') 30 | is(arr[0], 2) 31 | is(arr[1], 3) 32 | is(arr[2], 0, 'unitialized members') 33 | }) 34 | 35 | t.todo('loop: current item write', t => { 36 | let wat, mod 37 | wat = compileZ(`x=[1..3]; x[..] |> #+=1; x`) 38 | mod = compileWat(wat) 39 | let { memory, x } = mod.instance.exports 40 | arr = new Float64Array(memory.buffer, x.value, 3) 41 | is(arr[0], 1) 42 | is(arr[1], 2) 43 | is(arr[2], 0, 'unitialized') 44 | }) 45 | 46 | t('loop: range in range', t => { 47 | let wat = compileZ(`a=[..9], f(a,w,h)=( 48 | 0..w |> (x=#; 49 | 0..h |> (y=#; 50 | a[y*w + x] = x+y*w; 51 | ); 52 | ) 53 | )`) 54 | let mod = compileWat(wat) 55 | let { memory, a, f } = mod.instance.exports 56 | 57 | let arr = new Float64Array(memory.buffer, a.value, 9) 58 | is(arr, [0, 0, 0, 0, 0, 0, 0, 0, 0]) 59 | f(a, 3, 3) 60 | is(arr, [0, 1, 2, 3, 4, 5, 6, 7, 8]) 61 | }) 62 | 63 | t.todo('loop: as fn return', () => { 64 | let wat = compileZ(`x=[1..3]; fill() = (0..x[] |> x[#]=#+1); fill, x`) 65 | }) 66 | 67 | t 68 | 69 | t.todo('compile: current item assignment', t => { 70 | let wat = compileZ(` 71 | x=[..4]; 72 | (i=..3) |> x[i]=i*2`) 73 | let mod = compileWat(wat) 74 | let { memory, x } = mod.instance.exports 75 | 76 | let arr = new Float64Array(memory.buffer, x.value, 4) 77 | 78 | is(arr[0], 0) 79 | is(arr[1], 2) 80 | is(arr[2], 4) 81 | is(arr[3], 0, 'undefined') 82 | }) 83 | 84 | t.todo('loop: in loop', t => { 85 | let wat = compileZ(` 86 | x=[..4]; 87 | (i=..2) |> ( 88 | (j=..2) |> ( 89 | x[i*2+j]=i*2+j; 90 | j++ 91 | ); 92 | i++; 93 | ); 94 | x`) 95 | let mod = compileWat(wat) 96 | let { memory, x } = mod.instance.exports 97 | 98 | let arr = new Float64Array(memory.buffer, x.value, 4) 99 | 100 | is(arr[0], 0) 101 | is(arr[1], 1) 102 | is(arr[2], 2) 103 | is(arr[3], 3) 104 | }) 105 | 106 | t.todo('loop: over list', t => { 107 | let wat = compileZ(`x = [1,2,3]; y = x <| x -> x * 2`) 108 | let mod = compileWat(wat) 109 | let { memory, y } = mod.instance.exports 110 | let arr = new Float64Array(memory.buffer, 0, 3), ptr = y.value 111 | is(arr[ptr], 2) 112 | is(arr[ptr + 1], 4) 113 | not(arr[ptr + 2], 6) 114 | }) 115 | 116 | t.todo('loop: iterate over range', () => { 117 | let wat, mod, memory, a, b 118 | 119 | wat = compileZ(`a = (x = (1, 3..5, a[1..])) |> x;`) 120 | // supposed to compile to 121 | // 0.out ;; output of 0 group 122 | // 0.idx = 0 ;; 0 group element index 123 | // 0.cur ;; 0 group range cursor (current item) 124 | // 0.cur.null = 1 ;; if 0.cur is not active indicator 125 | // while() { 126 | // if (0.idx == 0) 0.out = 1, 0.idx++ 127 | // else if (0.idx == 1) { 128 | // if (0.cur.null) 0.cur = 3, 0.cur.null = 0 129 | // 0.out = 0.cur++ 130 | // if (0.cur >= 5) 0.idx++, 0.cur.null = 1 131 | // } 132 | // else if (0.idx == 2) { 133 | // if (0.cur.null) 0.cur = 1, 0.cur.null = 0 134 | // 0.out = a[0.cur++] 135 | // if (0.cur >= a[]) 0.idx++, 0.cur.null = 1 136 | // } 137 | // else break 138 | // x = 0.out 139 | // x 140 | // } 141 | mod = compileWat(wat); 142 | ; ({ memory, a, b } = mod.instance) 143 | 144 | is(new Float64Array(memory.buffer, b.value, 1), [2], `[(a,b..c) |> _]`); 145 | 146 | // 147 | 148 | // `a..b |> _` 149 | // `a.. |> _` 150 | // `..b |> _` 151 | // `.. |> _` 152 | // `(a,b..c) |> _` 153 | // `(a,b..) |> _` 154 | // `(a,..c) |> _` 155 | // `(a,..) |> _` 156 | // `(a,b[c..]) |> _` 157 | // `(a,b[c..],..d|>_*2) |> _` 158 | 159 | // `a[b..c] |> _` 160 | // `a[b..] |> _` 161 | // `a[..c] |> _` 162 | // `a[..] |> _` 163 | 164 | // `a[a..b,c] |> _` 165 | // `a[a..b,c..d] |> _` 166 | 167 | // `a..b + c.. |> _` 168 | // `(x = (a,b..c) + d..) |> _` 169 | }) 170 | -------------------------------------------------------------------------------- /test/case/array.js: -------------------------------------------------------------------------------- 1 | import t, { almost, is, not, ok, same, throws } from 'tst' 2 | import compileZ from '../../src/compile.js' 3 | import { compileWat } from '../util.js' 4 | 5 | t('array: basic', t => { 6 | let wat = compileZ(`x = [1]`) 7 | let mod = compileWat(wat) 8 | 9 | wat = compileZ(`x = [1.1, 2.22, 3.333], y = [4.1234,5.54321,654321.123456,7.7777777]; x,y,xl=x[],yl=y[]`) 10 | mod = compileWat(wat) 11 | let { memory, x, y, xl, yl } = mod.instance.exports 12 | let xarr = new Float64Array(memory.buffer, x.value, 3) 13 | is(xarr[0], 1.1, 'x0') 14 | is(xarr[1], 2.22, 'x1') 15 | is(xarr[2], 3.333, 'x2') 16 | is(xl.value, 3, 'xlen') 17 | let yarr = new Float64Array(memory.buffer, y.value, 4) 18 | is(yarr[0], 4.1234, 'y0') 19 | is(yarr[1], 5.54321, 'y1') 20 | is(yarr[2], 654321.123456, 'y2') 21 | is(yarr[3], 7.7777777, 'y3') 22 | is(yl.value, 4, 'ylen') 23 | }) 24 | 25 | t('array: basic local', t => { 26 | let wat = compileZ(`x() = [1, 2]`) 27 | let mod = compileWat(wat) 28 | let { memory, x } = mod.instance.exports 29 | let x0 = new Float64Array(memory.buffer, x(), 2) 30 | is(x0[0], 1, 'x0') 31 | is(x0[1], 2, 'x1') 32 | not(x(), x(), 'each instance is new') 33 | // console.log(new Float64Array(memory.buffer)) 34 | }) 35 | 36 | t.todo('array: from static range', t => { 37 | let wat = compileZ(`x=[..3], y=[0..4]; z=[4..0], x,y,xl=x[],yl=y[]`) 38 | // console.log(wat) 39 | let mod = compileWat(wat) 40 | let { memory, x, y, xl, yl, z } = mod.instance.exports 41 | let xarr = new Float64Array(memory.buffer, x.value, 10) 42 | is(xarr[0], 0, 'x0') 43 | is(xarr[1], 0, 'x1') 44 | is(xarr[2], 0, 'x2') 45 | is(xl.value, 3, 'xlen') 46 | let yarr = new Float64Array(memory.buffer, y.value, 4) 47 | is(yarr[0], 0, 'y0') 48 | is(yarr[1], 1, 'y1') 49 | is(yarr[2], 2, 'y2') 50 | is(yarr[3], 3, 'y3') 51 | is(yl.value, 4, 'ylen') 52 | let zarr = new Float64Array(memory.buffer, z.value, 4) 53 | is(zarr[0], 4, 'z0') 54 | is(zarr[1], 3, 'z1') 55 | is(zarr[2], 2, 'z2') 56 | is(zarr[3], 1, 'z3') 57 | }) 58 | 59 | t('array: from dynamic range', t => { 60 | let wat = compileZ(`a=3, x=[0..a], xl=x[]`) 61 | // , y=[1, x[0]..x[2], 2..-2]; x,y, xl=x[],yl=y[]`) 62 | // console.log(wat) 63 | let mod = compileWat(wat) 64 | let { memory, x, y, xl, yl } = mod.instance.exports 65 | let xarr = new Float64Array(memory.buffer, x.value, 3) 66 | is(xarr[0], 0, 'x0') 67 | is(xarr[1], 1, 'x1') 68 | is(xarr[2], 2, 'x2') 69 | is(xl.value, 3, 'xlen') 70 | }) 71 | 72 | t('array: from invalid ranges', t => { 73 | let wat 74 | throws(() => { wat = compileZ(`x=[2..]`) }, /range/) 75 | throws(() => { wat = compileZ(`x=[..]`) }, /range/) 76 | throws(() => { wat = compileZ(`x=[..-2]`) }, /range/) 77 | }) 78 | 79 | t('array: nested static', t => { 80 | let wat = compileZ(`x=[1, y=[2, [3,3.14]]], w=[4,5]`) 81 | // let wat = compileZ(`y=[2], x=[1, y], w=[4,5]`) 82 | // console.log(wat) 83 | let mod = compileWat(wat) 84 | let { memory, x, y, w } = mod.instance.exports 85 | console.log(new Float64Array(memory.buffer)) 86 | let xarr = new Float64Array(memory.buffer, x.value, 10) 87 | is(xarr[0], 1, 'x0') 88 | is(xarr[1], y.value, 'x1') 89 | is(len(x.value), 2, 'xlen') 90 | let yarr = new Float64Array(memory.buffer, y.value, 3) 91 | is(yarr[0], 2, 'y0') 92 | is(len(y.value), 2, 'ylen') 93 | let zarr = new Float64Array(memory.buffer, yarr[1], 3) 94 | is(zarr[0], 3, 'z0') 95 | is(zarr[1], 3.14, 'z1') 96 | is(len(yarr[1]), 2, 'zlen') 97 | let warr = new Float64Array(memory.buffer, w.value, 3) 98 | is(len(w.value), 2, 'wlen') 99 | is(warr[0], 4, 'w0') 100 | is(warr[1], 5, 'w1') 101 | }) 102 | 103 | t.todo('array: comprehension', t => { 104 | let wat = compileZ(`x = [1..3 |> _ * 2]`) 105 | let mod = compileWat(wat) 106 | let { memory, x } = mod.instance.exports 107 | let xarr = new Float64Array(memory.buffer, x, 2) 108 | is(xarr, [1, 2]) 109 | }) 110 | 111 | t.todo('array: nested comprehension', t => { 112 | let wat = compileZ(`x = [1..3 <| [0.._ <| _ * 2]]`) 113 | }) 114 | 115 | t('array: simple write', t => { 116 | let wat = compileZ(`x=[..3]; x[0]=1; x[1]=2; x[-1]=x[]; x`) 117 | // console.log(wat) 118 | let mod = compileWat(wat) 119 | let { memory, x } = mod.instance.exports 120 | let xarr = new Float64Array(memory.buffer, x.value, 3) 121 | is(xarr[0], 1, 'x0') 122 | is(xarr[1], 2, 'x1') 123 | is(xarr[2], 3, 'x2') 124 | }) 125 | 126 | t('array: simple read', t => { 127 | let wat = compileZ(`x = [1, 2, 3]; a=x[0],b=x[1],c=x[2],d=x[-1]`) 128 | // console.log(wat) 129 | let mod = compileWat(wat) 130 | let { a, b, c, d } = mod.instance.exports 131 | is(a.value, 1) 132 | is(b.value, 2) 133 | is(c.value, 3) 134 | is(d.value, 3) 135 | }) 136 | 137 | t('array: group read', t => { 138 | let wat = compileZ(`x = [1, 2, 3]; (a,b,c)=x[0,1,2]`) 139 | // console.log(wat) 140 | let mod = compileWat(wat) 141 | let { a, b, c } = mod.instance.exports 142 | is(a.value, 1) 143 | is(b.value, 2) 144 | is(c.value, 3) 145 | }) 146 | 147 | t('array: group write', t => { 148 | let wat = compileZ(`x = [..3]; x[0,1,2]=(1,2,3)`) 149 | // console.log(wat) 150 | let mod = compileWat(wat) 151 | let { x, memory } = mod.instance.exports 152 | const mem = new Float64Array(memory.buffer, x.value, 3) 153 | is(mem[0], 1) 154 | is(mem[1], 2) 155 | is(mem[2], 3) 156 | }) 157 | 158 | t('compile: sublist', t => { 159 | let wat = compileZ(`x = [1,2,3], y = [x]`) 160 | let mod = compileWat(wat) 161 | let { memory, x, y } = mod.instance.exports 162 | let xarr = new Float64Array(memory.buffer, x, 3), yarr = new Float64Array(memory.buffer, y, 1) 163 | is(xarr[0], 1) 164 | is(xarr[1], 2) 165 | is(xarr[2], 3) 166 | is(yarr[0], x.value) 167 | }) 168 | 169 | 170 | 171 | // get length or an array 172 | export function len(n) { 173 | let data = new DataView(new ArrayBuffer(8)) 174 | data.setFloat64(0, n) 175 | return data.getInt32(4) 176 | } 177 | -------------------------------------------------------------------------------- /examples/Wiebe-Marten Wijnja - Predestined Fate.z: -------------------------------------------------------------------------------- 1 | // Ref: https://souleyedigitalmusic.bandcamp.com/track/predestined-fate 2 | // 3 | // ████████████████ 4 | // ████████████████████ 5 | // ██ ████ ██████ 6 | // ██ ████ ██████ ____ _ _ _ _ _____ _ 7 | // ████████████████████ | _ \ _ __ ___ __| | ___ ___| |_(_)_ __ ___ __| | | ___|_ _| |_ ___ 8 | // ████████████████████ | |_) | '__/ _ \/ _` |/ _ \/ __| __| | '_ \ / _ \/ _` | | |_ / _` | __/ _ \ 9 | // ██ ██████ | __/| | | __/ (_| | __/\__ \ |_| | | | | __/ (_| | | _| (_| | || __/ 10 | // ████ ████████ |_| |_| \___|\__,_|\___||___/\__|_|_| |_|\___|\__,_| |_| \__,_|\__\___| 11 | // ████████████████ 12 | // ████████ From VVVVVV 13 | // ████████████████ 14 | // ████████████████████ Written by: 15 | // ████████████████████ Souleye / Magnus Pålsson (http://souleye.se) 16 | // ████████████████████ 17 | // ████████████████████ 18 | // ████ ████████ ████ Bytebeat adaption: 19 | // ████ ████████ ████ W-Mcode / Wiebe-Marten Wijnja (http://wmmusic.nl/wmcode) 20 | // ████████████ 21 | // ████ ████ 22 | // ██████ ██████ 23 | // ██████ ██████ 24 | // ██████ ██████ 25 | // Version 1.1 26 | // __________________________________________________________________________________ 27 | // 28 | // VVVVVV is one of my favourite games of all time. 29 | // Because of the gameplay, but for a big part because of the music. 30 | // 'Predestined Fate' is one of the best chiptunes I know of. Souleye is an amazing composer. 31 | // 32 | // And thus, here is a bytebeat rendition of this song, compressing it to less than a kilobyte. 33 | // I hope you like it as much as I enjoyed making it. 34 | // 35 | // ~W-M 36 | // 37 | // Oh, and feel free to reach me on Twitter ( @W_Mcode ) or send me a mail at ( W-M@gmx.us ). 38 | // 39 | // FEATURES: 40 | // - The complete song, as written by Souleye 41 | // - Four different synthesizer shapes, as well as three kinds of percussion sounds. 42 | // - Looping, in the way it was intended by Souleye, by skipping the intro. 43 | // 44 | // WHAT IS THIS? 45 | // Bytebeat is making music using only code. Each sample, a function is run. 46 | // This function(you're reading it now) outputs a speaker position(between -1 an 1) for each sample in the song. 47 | // This way, it is possible to make synthesizers using only a few (or many) lines of code. 48 | // 49 | // More information on Bytebeat can be found at: 50 | // = My bytebeat cover of 'Please Exist' from Knytt Underground (written by Nifflas): 51 | // http://tinyurl.com/pleaseexist 52 | // = My 2014 JS1K contest entry: 53 | // http://js1k.com/2014-dragons/demo/1953 (and a mirror: http://wmmusic.nl/code/js1k/got/ ) 54 | // = My earlier Bytebeat cover of 'Focus' from SuperHexagon (written by Chipzel): 55 | // http://tinyurl.com/focushexagon1-4 56 | // = Kragen's long blog post with much info: 57 | // http://canonical.org/~kragen/bytebeat/ 58 | // = The original Bytebeat topic: 59 | // http://www.pouet.net/topic.php?which=8357 60 | // = Blog article from Viznut, the re-introducer of Bytebeat: 61 | // http://countercomplex.blogspot.nl/2011/10/algorithmic-symphonies-from-one-line-of.html 62 | // 63 | // All right. That's all I have to say. 64 | // Below starts the source code. 65 | // Enjoy your day =) 66 | // 67 | 68 | // Get Melody function. This is the real synthesizer. 69 | // p = pitch offset. 70 | // o = octave offset. 71 | // q = current place in pattern. This is changed to change the speed at which the pattern is playing. 72 | // (or play backwards, etc) 73 | // m = the melody string used. 74 | // s = the wave shape: 0=pulse, 1=saw, 2=triangle, 3=sine 75 | // 76 | // m2 = second melody string (optional) 77 | // j = jumpspeed (optional) 78 | 79 | M(p, o, q, m, s, m2, j) = ( 80 | j ||= 0x2000, 81 | r = m[q] || 0, 82 | q = m2 != null ? m2[q] || 0 : 0, 83 | r = q === 0 ? r : (r - q) * ((t % j) / j) + q, 84 | // Get absolute pitch from semitone. 85 | g = r < 33 ? 0 : ((t % j) / ratio) * 2 ** ((r + p) / 12 - o); 86 | // This section is used by both saw and triangle wave (as tri is nothing more than abs(saw)) 87 | x = (g % 255) / 128 - 1; 88 | // The real magic: decide between pulse, saw and triangle and synthesize them. 89 | s ? s < 2 ? x : s < 3 ? abs(x) * 3 : sin(PI * x) : (g & 128) / 64 - 1 90 | ); 91 | 92 | // Main arpeggio 93 | m = "5:=5:=5:<5:<5:<:16:18:161:168:68", 94 | // First bell-like sound. 95 | m2 = ": ", 96 | // First melody 97 | m3 = ": : 5 8 ::::: :<= < : 8 ::::::: ", 98 | // Second melody 99 | m4b = ":: 55 ::6666 6:<<<<<88AA66666 ", 100 | // Second melody 101 | m4 = ":: 55 ::6666 6:<==<<::AA66666 ", 102 | // Fill in at end of second melody 103 | m5 = " ?A? = < ", 104 | // Intermezzo melody 105 | m6 = ":51...55:::::::<===<<<8811111111", 106 | // Intermezzo bass 107 | m7 = ": ::: ::: ::: ::6 666 666 666 66", 108 | 109 | // Basedrum 110 | btime = 2 << 12, 111 | bm = (80 - 40) * (1 - (t % btime) / btime) ** 10 - 80, 112 | bm2 = 0x01, 113 | bd = (bm2 >> (t / btime) % 2) & 1 ? 114 | sin(PI * (t % btime) * pow(2, bm / 12 - 1)) * pow(1 - (t % btime) / btime, 10) : 0, 115 | 116 | // High tom 117 | btime = 2 << 11, 118 | btm = (80 - 15) * pow(1 - (t % btime) / btime, 10) - 80, 119 | btm2 = 0x1111010111010111, 120 | bt = (btm2 >> (t / btime) % 16) & 1 ? 121 | sin(PI * (t % btime) * pow(2, btm / 12 - 1)) * pow(1 - (t % btime) / btime, 10) * 0.3 : 0, 122 | 123 | // main generating function 124 | f(t) = ( 125 | t *= 5.6, 126 | // Match the speed that the original song has. 127 | ratio = 0.78, 128 | // ratio is multiplied here and removed again inside the get melody function, so the pitch wont increase. 129 | t *= ratio, 130 | // v is used in many places to check how far we are in the song. It is incremented each 4096 samples, roughly. 131 | v = t >> 12, 132 | // Song looping. When past 768, repeat, skipping the first 128. 133 | v = (v % 768) + (v > 767 ? 128 : 0); 134 | 135 | v < 640 ? 136 | // Arpeggio 137 | M(6, 5, (t >> 12) % 32, m, 3) * 0.3 + 138 | M(6, 3, (t >> 12) % 32, m, 3) * 0.01 + 139 | (v < 64 ? 0 : M(6, 4, (t >> 12) % 32, m, 2) * 0.05) + 140 | // Bell 141 | (v < 128 ? 0 : ( 142 | M(6, 3, (t >> 16) % 2, m2, 2) + 143 | M(9, 4, (t >> 16) % 2, m2, 2) + 144 | M(13, 4, (t >> 16) % 2, m2, 2) 145 | ) * (1 - (t % 65535) / 65535) * 0.05) + 146 | // First melody 147 | (v < 196 ? 0 : M(6, 4, (t >> 12) % 32, m3, (t >> 17) % 2 ? 0 : 1) * 0.05) + 148 | // This part only between 256 and 480?, then a pause until 512 and then play again 149 | (v > 255 && (v < 448 || v > 511) ? 150 | // Drums 151 | (v < 256 ? 0 : bd + bt) + 152 | // Second melody 153 | (v < 20 ? 0 : M(6, 3, (t >> 13) % 32, m4, 2, m4b, 0x8000) * 0.1 + 154 | M(6, 4, (t >> 13) % 32, m4, 1, m4b, 0x8000) * 0.05) + 155 | (v < 320 ? 0 : M(6, 3, (t >> 12) % 32, (t >> 17) % 2 ? m5 : ' ', 3) * 0.2) : 0) : 156 | // Outro 157 | // Intermezzo melody 158 | M(6, 4, (t >> 13) % 32, m6, 3) * 0.05 + 159 | // Intermezzo bass 160 | M(6, 5, (t >> 12) % 32, m7, 2) * (1 - (t % (2 << 11)) / (2 << 11)) * 0.05 + 161 | // Distorted drum effect 162 | ((t >> 15) % 4 ? 0 : ((((sqrt(t % 0x2000) << 6 & 255) / 127 - 1)) / ((t >> 13) % 4 + 1)) * 0.15) 163 | ) 164 | -------------------------------------------------------------------------------- /src/parse.js: -------------------------------------------------------------------------------- 1 | // parser converts syntax into AST/calltree 2 | // NOTE: we don't import subscript features because we don't need its compiler 3 | // also we may not need certain default operators like . or comments 4 | // also we need adjusted precedences and parsing order 5 | import parse, { lookup, skip, next, cur, idx, err, expr, token, unary, binary, access, group, nary, id } from 'subscript/parse' 6 | 7 | export const INT = 'int', FLOAT = 'flt' 8 | 9 | export default parse 10 | 11 | // char codes 12 | const OPAREN = 40, CPAREN = 41, OBRACK = 91, CBRACK = 93, SPACE = 32, QUOTE = 39, DQUOTE = 34, PERIOD = 46, BSLASH = 92, SLASH = 47, _0 = 48, _1 = 49, _8 = 56, _9 = 57, _A = 65, _F = 70, _a = 97, _f = 102, _E = 69, _e = 101, _b = 98, _o = 111, _x = 120, COLON = 58, SEMICOLON = 59, HASH = 35, AT = 64, PAREN_OPEN = 40, PAREN_CLOSE = 41, PLUS = 43, MINUS = 45, GT = 62 13 | 14 | // precedences 15 | const PREC_SEMI = 1, // a; b; 16 | PREC_RETURN = 4, // x ? ./a,b : y 17 | PREC_SEQ = 6, // ./a,b,c; a, b ? (c,d) : (e,f); a,b,c |> d 18 | PREC_STATE = 7, // *x=1, x=2; 19 | PREC_IF = 8, // a ? b=c; a = b?c; 20 | // FIXME: should pipe be lower than if? a |> b?c; 21 | PREC_PIPE = 8, // |> should match JS pipe, a = b|>c; a, b|>c, d; a|>b ? c 22 | PREC_ASSIGN = 8.25, // a=b, c=d, a = b||c, a = b | c, a = b&c 23 | PREC_LOR = 11, 24 | PREC_LAND = 12, 25 | PREC_BOR = 13, // a|b , c|d, a = b|c 26 | PREC_XOR = 14, 27 | PREC_BAND = 15, 28 | PREC_EQ = 16, 29 | PREC_COMP = 17, 30 | PREC_SHIFT = 18, 31 | PREC_CLAMP = 19, // pre-arithmetical: a+b*c ~ 10; BUT a < b~c, a << b~c 32 | PREC_ADD = 20, 33 | PREC_RANGE = 22, // +a .. -b, a**2 .. b**3, a*2 .. b*3; BUT a + 2..b + 3 34 | PREC_MULT = 23, 35 | PREC_POW = 24, 36 | PREC_UNARY = 26, 37 | PREC_CALL = 27, // a(b), a.b, a[b], a[] 38 | PREC_TOKEN = 28 // [a,b] etc 39 | 40 | // make id support #@ 41 | const isId = parse.id = char => id(char) || char === HASH 42 | 43 | // numbers 44 | const isNum = c => c >= _0 && c <= _9 45 | const num = (a) => { 46 | if (a) err(); // abc 023 - wrong 47 | 48 | let n, t = INT, unit, node; // numerator, separator, denominator, unit 49 | 50 | // parse prefix 51 | n = next(c => c === _0 || c === _x || c === _o || c === _b) 52 | 53 | // 0x000 54 | if (n === '0x') n = parseInt(next(c => isNum(c) || (c >= _a && c <= _f) || (c >= _A && c <= _F)), 16) 55 | // 0o000 56 | else if (n === '0o') n = parseInt(next(c => c >= _0 && c <= _8), 8) 57 | // 0b000 58 | else if (n === '0b') n = parseInt(next(c => c === _1 || c === _0), 2) 59 | // 1.2, 1e3, -1e-3 60 | else { 61 | n += next(isNum) 62 | if (cur.charCodeAt(idx) === PERIOD && isNum(cur.charCodeAt(idx + 1))) n += skip() + next(isNum), t = FLOAT 63 | if (cur.charCodeAt(idx) === _E || cur.charCodeAt(idx) === _e) n += skip(2) + next(isNum) 64 | n = +n 65 | if (n != n) err(`Bad number ${n}`) 66 | } 67 | node = [t, n] 68 | 69 | // parse units, eg. 1s 70 | if (unit = next(c => !isNum(c) && isId(c))) node.push(unit) 71 | 72 | // parse unit combinations, eg. 1h2m3s 73 | if (isNum(cur.charCodeAt(idx))) node.push(num()) 74 | 75 | return node 76 | } 77 | 78 | // .1 79 | lookup[PERIOD] = a => !a && num(); 80 | 81 | // 0-9 82 | for (let i = _0; i <= _9; i++) lookup[i] = num; 83 | 84 | 85 | // strings 86 | const escape = { n: '\n', r: '\r', t: '\t', b: '\b', f: '\f', v: '\v' }, 87 | string = q => (qc, c, str = '') => { 88 | qc && err('Unexpected string') // must not follow another token 89 | skip() // first quote 90 | while (c = cur.charCodeAt(idx), c - q) { 91 | if (c === BSLASH) skip(), c = skip(), str += escape[c] || c 92 | else str += skip() 93 | } 94 | skip() || err('Bad string') 95 | return [, str] 96 | } 97 | 98 | 99 | // "' with / 100 | lookup[DQUOTE] = string(DQUOTE) 101 | lookup[QUOTE] = string(QUOTE) 102 | 103 | 104 | // a(b,c,d), a() 105 | access('()', PREC_CALL) 106 | 107 | // a[b] 108 | access('[]', PREC_CALL) 109 | 110 | // (a,b,c), (a) 111 | group('()', PREC_CALL) 112 | // [a,b,c] 113 | group('[]', PREC_TOKEN) 114 | 115 | // a,b,,c 116 | nary(',', PREC_SEQ, true) 117 | // a;b;;c 118 | nary(';', PREC_SEMI, true) 119 | 120 | // mults 121 | binary('*', PREC_MULT) 122 | binary('/', PREC_MULT) 123 | binary('%', PREC_MULT) 124 | 125 | // static 126 | unary('*', PREC_STATE) 127 | 128 | // adds 129 | binary('+', PREC_ADD) 130 | binary('-', PREC_ADD) 131 | unary('+', PREC_UNARY) 132 | unary('-', PREC_UNARY) 133 | 134 | // increments 135 | token('++', PREC_UNARY, a => a ? ['++', a] : ['+=', expr(PREC_UNARY - 1), [INT, 1]]) 136 | token('--', PREC_UNARY, a => a ? ['--', a] : ['-=', expr(PREC_UNARY - 1), [INT, 1]]) 137 | 138 | // bitwises 139 | unary('~', PREC_UNARY) 140 | binary('~', PREC_CLAMP) 141 | binary('|', PREC_BOR) 142 | binary('&', PREC_BAND) 143 | binary('^', PREC_XOR) 144 | 145 | // logic 146 | binary('||', PREC_LOR) 147 | binary('&&', PREC_LAND) 148 | unary('!', PREC_UNARY) 149 | 150 | // assigns 151 | binary('=', PREC_ASSIGN, true) 152 | binary('*=', PREC_ASSIGN, true) 153 | binary('/=', PREC_ASSIGN, true) 154 | binary('%=', PREC_ASSIGN, true) 155 | binary('+=', PREC_ASSIGN, true) 156 | binary('-=', PREC_ASSIGN, true) 157 | binary('~=', PREC_ASSIGN, true) 158 | 159 | // compares 160 | binary('==', PREC_EQ) 161 | binary('!=', PREC_EQ) 162 | binary('>', PREC_COMP) 163 | binary('<', PREC_COMP) 164 | binary('>=', PREC_COMP) 165 | binary('<=', PREC_COMP) 166 | 167 | // shifts 168 | binary('>>', PREC_SHIFT) 169 | binary('<<', PREC_SHIFT) 170 | binary('>>>', PREC_SHIFT) 171 | binary('<<<', PREC_SHIFT) 172 | binary('>>=', PREC_SHIFT) 173 | binary('<<=', PREC_SHIFT) 174 | binary('>>>=', PREC_SHIFT) 175 | binary('<<<=', PREC_SHIFT) 176 | 177 | // pow, mod 178 | binary('**', PREC_POW, true) 179 | binary('%%', PREC_MULT, true) 180 | 181 | // clamps 182 | binary('~<', PREC_CLAMP) 183 | binary('~/', PREC_CLAMP) 184 | binary('~*', PREC_CLAMP) 185 | binary('~//', PREC_CLAMP) 186 | binary('~**', PREC_CLAMP) 187 | 188 | // loop a[..] |> # 189 | nary('|>', PREC_PIPE) 190 | // binary('|>=', PREC_PIPE, true) 191 | 192 | // end a.; ;.; 193 | // token('.', PREC_END, a => ['.', a]) 194 | 195 | // range a..b, ..b, a.. 196 | token('..', PREC_RANGE, a => (['..', a, expr(PREC_RANGE)])) 197 | 198 | // returns 199 | token('./', PREC_TOKEN, (a, b) => !a && (b = expr(PREC_RETURN), b ? ['./', b] : ['./'])) // continue: ./ 200 | token('../', PREC_TOKEN, (a, b) => !a && (b = expr(PREC_RETURN), b ? ['../', b] : ['../'])) // break: ../ 201 | token('/', PREC_TOKEN, (a, b) => !a && (b = expr(PREC_RETURN), b ? ['/', b] : ['/'])) // return: / 202 | 203 | // defer ^a,b,c 204 | unary('^', PREC_RETURN); 205 | 206 | // a.b 207 | // NOTE: we don't parse expression to avoid 0..1 recognizing as 0[.1] 208 | // NOTE: for now we disable prop access since we don't have aliases and just static index can be done via x[0] 209 | // token('.', PREC_CALL, (a,b) => a && (b=next(isId)) && ['.', a, b]) 210 | 211 | // conditions & ternary 212 | // a?b - returns b if a is true, else returns a 213 | binary('?', PREC_IF, true) 214 | // binary('?:', PREC_IF, true) 215 | 216 | // ?: - we use modified ternary for our cases 217 | token('?', PREC_IF, (a, b, c) => a && (b = expr(PREC_IF - 0.5)) && next(c => c === COLON) && (c = expr(PREC_IF - 0.5), ['?:', a, b, c])) 218 | 219 | // token('(;', PREC_TOKEN, (a, prec) => (next(c => c !== SEMICOLON && cur.charCodeAt(idx + 1) !== PAREN_CLOSE), skip(2), a || expr(prec) || [])) 220 | // ;; comments 221 | token(';;', PREC_TOKEN, (a, prec) => (next(c => c >= SPACE), a || expr(prec) || [])) 222 | -------------------------------------------------------------------------------- /src/build.js: -------------------------------------------------------------------------------- 1 | // builder actually generates wast code from params / current context 2 | import { globals, locals, funcs, func, initing, returns, RETURN } from "./compile.js" 3 | import { err } from "./util.js" 4 | import stdlib from "./stdlib.js" 5 | import { FLOAT, INT } from "./parse.js" 6 | 7 | export const i32 = { 8 | const: a => op(`(i32.const ${a})`, 'i32'), 9 | load: a => op(`(i32.load ${a})`, 'i32'), 10 | store: (a, v) => op(`(i32.store ${a} ${v})`), 11 | add: (a, b) => op(`(i32.add ${a} ${b})`, 'i32'), 12 | sub: (a, b) => op(`(i32.sub ${a} ${b})`, 'i32'), 13 | eqz: (a) => op(`(i32.eqz ${a})`, 'i32') 14 | } 15 | 16 | export const f64 = { 17 | const: a => op(`(f64.const ${a})`, 'f64'), 18 | load: a => op(`(f64.load ${a})`, 'f64'), 19 | store: (a, v) => op(`(f64.store ${a} ${v})`), 20 | add: (a, b) => op(`(f64.add ${a} ${b})`, 'f64'), 21 | sub: (a, b) => op(`(f64.sub ${a} ${b})`, 'f64'), 22 | lt: (a, b) => op(`(f64.lt ${a} ${b})`, 'i32') 23 | } 24 | 25 | // if then else? 26 | export function cond(i, a, b) { 27 | let result = a.type ? `(result ${a.type.join(' ')})` : `` 28 | return op(`(if ${result} ${i} (then ${a}) ${b ? `(else ${b})` : ``})`, a.type) 29 | } 30 | 31 | // (loop) 32 | export function loop(body) { 33 | return op(`(loop ${body})`) 34 | } 35 | 36 | // create op result, a string with extra info like types 37 | // holds number of returns (ops) 38 | // makes sure it stringifies properly into wasm expression 39 | // provides any additional info: types, static, min/max etc 40 | export function op(str = '', type) { 41 | str = new String(str) 42 | if (!type) type = [] 43 | else if (typeof type === 'string') type = [type] 44 | return Object.assign(str, { type }) 45 | } 46 | 47 | // (local.get) or (global.get) 48 | export function get(name) { 49 | // global 50 | if ((!func && name[0] !== '_') || globals[name]) return globals[name] ||= { type: 'f64' }, op(`(global.get $${name})`, globals[name].type) 51 | 52 | // static 53 | if (locals[name].static) return op(`(global.get $${locals[name].static})`, locals[name].type) 54 | 55 | // local 56 | return locals[name] ||= { type: 'f64' }, op(`(local.get $${name})`, locals[name].type) 57 | } 58 | 59 | // (local.set) or (global.set) (if no init - takes from stack) 60 | export function set(name, init = '') { 61 | // global 62 | // TODO: disable for the first fn expression 63 | if ((!func && name[0] !== '_') || (!initing && globals[name])) return globals[name] ||= { type: init.type || 'f64' }, op(`(global.set $${name} ${init})`) 64 | 65 | // static 66 | if (locals[name].static) return op(`(global.set $${locals[name].static} ${init})`) 67 | 68 | // local 69 | return locals[name] ||= { type: init.type || 'f64' }, op(`(local.set $${name} ${init})`) 70 | } 71 | 72 | // (local.tee) or (global.set)(global.get) 73 | export function tee(name, init = '') { 74 | // global 75 | if ((!func && name[0] !== '_') || (!initing && globals[name])) return globals[name] ||= { type: init.type || 'f64' }, op(`(global.set $${name} ${init})(global.get $${name})`, init.type) 76 | 77 | // static 78 | if (locals[name].static) return op(`(global.set $${locals[name].static} ${init})(global.get $${locals[name].static})`, locals[name].type) 79 | 80 | return locals[name] ||= { type: init.type || 'f64' }, op(`(local.tee $${name} ${init})`, init.type) 81 | } 82 | 83 | // produce function call method 84 | export function call(name, ...args) { 85 | if (!funcs[name]) err(`Unknown function call '${name}'`) 86 | return op(`(call $${name} ${args.join(' ')})`, funcs[name].type) 87 | } 88 | 89 | // add include from stdlib and return call 90 | // NOTE: we cannot remove it due to circular deps 91 | export function include(name) { 92 | if (!stdlib[name]) err('Unknown include `' + name + '`') 93 | // parse type from first (result) token 94 | // FIXME: must be done better 95 | let code = stdlib[name] 96 | let type = code.match(/\(result\s+([^\)]+)\)/i)?.[1].trim().split(/\s+/) 97 | if (!funcs[name]) defineFn(name, code, type) 98 | } 99 | 100 | // define a function 101 | export function defineFn(name, body, type) { 102 | if (funcs[name]) err(`Redefine func \`${name}\``) 103 | funcs[name] = new String(body) 104 | funcs[name].type = type 105 | } 106 | 107 | // upgrade/extend type of a to include type b (length and f64) 108 | export function uptype(a, b) { 109 | // we upgrade type to f64 if it mismatches 110 | if (a.length < b.length) a.length = b.length 111 | for (let i = 0, l = b.length; i < l; i++) 112 | if (a[i] !== b[i]) a[i] = 'f64' 113 | } 114 | 115 | // make sure operation has provided type in stack 116 | // unlike float/int it provides generic type enforcement 117 | // eg. 1,2 + f64, 118 | export function type(opStr, type) { 119 | let dif = type.findIndex((t, i) => opStr.type[i] != t) // first different type 120 | 121 | if (dif < 0) return opStr 122 | 123 | // if dif type is last in op - upgrade it and fill rest with nans, eg. 124 | // /1; /1.1 - single type mismatch 125 | // /1,2; /1,2.2 - last type mismatch 126 | if (dif >= opStr.type.length - 1) { 127 | opStr = float(opStr) 128 | // fill up stack with 0 129 | // /1; /1,2 - length mismatch 130 | // /1,2; /1,2.2,3 - length and last type mismatch 131 | for (let i = opStr.type.length; i < type.length; i++) opStr += `(${type[i]}.const nan)` 132 | return op(opStr, type) 133 | } 134 | // /1; /1.1,2 - type and/or length mismatch - needs temp vars 135 | else { 136 | // /1,2; /1.1,2 - one var type mismatch 137 | err('Unimplemented return type mismatch') 138 | } 139 | } 140 | 141 | // wrap expression to float, if needed 142 | export function float(opStr) { 143 | if (opStr.type[0] === 'f64') return opStr 144 | if (opStr == RETURN) return opStr // ignore return placeholders 145 | if (opStr.startsWith('(i32.const')) return op(opStr.replace('(i32.const', '(f64.const'), 'f64') 146 | return op(`(f64.convert_i32_s ${opStr})`, 'f64') 147 | } 148 | // cast expr to int 149 | export function int(opStr) { 150 | if (opStr.type[0] === 'i32') return opStr 151 | if (opStr == RETURN) return opStr // ignore return placeholders 152 | return op(`(i32.trunc_f64_s ${opStr})`, 'i32') 153 | } 154 | 155 | /** 156 | * Pick N input args into stack, like (a,b,c) -> (a,b) 157 | * 158 | * @param {number} count - number of elements to pick 159 | * @param {string} input - stringified op to pick elements from 160 | * @returns {string} 161 | */ 162 | export function pick(count, input) { 163 | // (a,b,c) = d - we duplicate d to stack N times 164 | if (input.type.length === 1) { 165 | // a = b - skip picking 166 | if (count === 1) return input 167 | // (a,b,c) = d - duplicating via tmp var is tentatively faster & more compact than calling a dup function 168 | // FIXME: can be single global variable 169 | const name = `dup:${input.type[0]}` 170 | locals[name] ||= { type: input.type[0] } 171 | return op( 172 | `(local.set $${name} ${input})${`(local.get $${name})`.repeat(count)}`, 173 | Array(count).fill(input.type[0]) 174 | ) 175 | } 176 | 177 | // (a,b) = (c,d) - avoid picking since result is directly put into stack 178 | if (input.type.length === count) return input 179 | 180 | // (a,b) = (c,d,e) – drop redundant members 181 | if (count < input.type.length) return op(input + `(drop)`.repeat(input.type.length - count), input.type.slice(0, count)) 182 | 183 | // (a,b,c) = (d,e) – pick a & b, skip c 184 | if (count > input.type.length) err('Picking more members than available') 185 | 186 | // NOTE: repeating els like (a,b,c)=(b,c,a) are not a threat, we don't need a function 187 | // putting them directly to stack is identical to passing to pick(b,c,a) as args 188 | } 189 | 190 | // check if node is statically precalculable (see extended constants expressions) 191 | export const isConstExpr = (a) => //(typeof a === 'string' && globals[a]) || 192 | a[0] === INT || a[0] === FLOAT 193 | // || ((a[0] === '+' || a[0] === '-' || a[0] === '*') && a.slice(1).every(isConstExpr)) 194 | -------------------------------------------------------------------------------- /docs/todo.md: -------------------------------------------------------------------------------- 1 | ## 0 2 | 3 | * [x] make subscript generate lispy tree (wasm, wat, js, ast targets are possible) 4 | . we need not immediately compile, we need intermediary state to codegenerate after. 5 | . parsing time can be improved via wasm version; 6 | * [x] ~~make subscript generate wasm binary for evaluator.~~ nah, later 7 | * Example: https://crypto.stanford.edu/~blynn/asm/wasm.html 8 | * [x] Watr 9 | * [x] compiler 10 | * [x] parser: very basic commands, no legacy 11 | * [x] Compare vs wabt, wat-compiler 12 | * [x] Parser 13 | 14 | * [ ] jasm - JS-compatible min surface 15 | 16 | ## 0.1 17 | 18 | * [x] update subscript 19 | * [x] fix parsing 20 | * [x] a++, a--, a+=1, a-=n 21 | * [x] fix compiling 22 | * [x] account ops for expected number of returns instead of forcing drops 23 | * [x] make `;;` for comments 24 | * [x] make `.` for 'return nothing' 25 | * [x] undo `.` - make semicolon-based void 26 | * [x] make globals readable, but locals writable 27 | * [x] first bytebeat: viznut 28 | * [x] make `#` as topic placeholder 29 | * [x] make writable globals, init on the first line 30 | * [x] make simple static variables as `*b=a` 31 | * [x] ~~clone fn static `a()=(), b()=(*clone=a;clone())`~~ 32 | * [x] init detection 33 | * [x] `nan:` and constant expressions for watr 34 | * [x] static groups `*(a,b,c)` 35 | * [ ] Decide about way to import 36 | * [ ] Car signalisation gen 37 | * [ ] Metronome gen 38 | * [ ] Other famous sounds/tropes 39 | * [ ] -<, -*, -/ 40 | * [x] `^` for deferred 41 | * [x] preliminary return with defer `f()=(^i++;i>10?./1;)` - likely via block as `f()=(ret;(i>10?ret=1);i++;ret)` 42 | * [x] conciliate returns types 43 | * [x] remove op `info` 44 | * [x] unvoid `a ? b;` 45 | * [x] make `/`, `/a` as function return 46 | * [ ] make intersect `a >< b` 47 | * [ ] make all viznut bytebeats work (ints rotate) 48 | * [ ] make nan as null in fn arguments (`config(a,,b,c)`) 49 | * [ ] range iterator via pipe 50 | * [ ] convert all string instructions to builder calls 51 | * [ ] list comprehension 52 | * [ ] make use of `./`, `../` for loops only (not parent)\ 53 | * [ ] all readmes test 54 | * [ ] case-insensitive variables 55 | * [ ] strings 56 | * [x] make use of watr compiler 57 | * [x] fix watr discrepancies with wabt in lino context 58 | * [x] Add proper scope mechanism (streamline compiler) 59 | * [x] merge analyzer into compile 60 | + the difference between them is unclear, compiler can map expression results 61 | + compiler follows scopes anyways, easier to merge scope vars if needed 62 | + we use desc anyways from analyser all the time 63 | + we don't test analyser anyways, it's not stable 64 | + simplifies parent state management 65 | * [x] precompile 66 | * [x] static precalc (no FLOAT/INT in compiler) 67 | * [x] units 68 | * [x] normalize args/array cases (), (a), (a,b) 69 | * [x] static arrays 70 | * [x] unwrap static ranges as plain sequences 71 | * [x] group read with prop access 72 | * [x] group write with prop access 73 | * [x] pow operator 74 | * [x] use f64 everywhere 75 | * [x] implement array pointer as float64 76 | * [x] ~~`x=1; x=1.0` - add float dup~~ 77 | * [x] arity of ops, for proper `(drop)` amount, eg. `a ? b;` has 0 items in stack; 78 | * [x] early return detection 79 | * [ ] while loop 80 | * [x] Readable generated code 81 | * [x] Per-function vars scope: we don't need parens as scope 82 | * [x] Change `,` precedence to be under `=` to allow `^a,b,c`, `x?1,2,3:4,5,6`, `a,b<|x,i->x,i` 83 | * [ ] Include config params properly 84 | * [ ] Sobel https://github.com/mattdesl/wasm-bench 85 | * [x] `(y1, y2) >= height ? (y1, y2) = height - 1` group ops 86 | * [ ] `(val0, val1) = data[ptr0 + (x, x1)];` 87 | * [x] ~~`\` comments~~ 88 | * [ ] optimized array prop access (track static vars / static-length arrays) 89 | * [ ] Biquad 90 | * [ ] Clamp args 91 | * [x] Imports - nice `<>` syntax with href internals and options 92 | * [x] Units 93 | * [x] basic maths 94 | * [x] Comments 95 | * [ ] Oka encoder/decoder 96 | * [x] Group operators 97 | * [x] multiply 98 | * [x] floats 99 | * [x] ints 100 | * [x] binary 101 | * [x] logical 102 | * [x] conditions 103 | * [ ] Group operators with unmatching number of args 104 | * [ ] Group operators with skipped values 105 | * [ ] Group operators with range 106 | * [ ] Complex group operators (via heap) 107 | * [ ] Internalize memory: only imported memory is available 108 | * [x] CLI 109 | * [x] get rid of a.1: we can do that via static knowledge 110 | * [ ] dispose static arrays once ref is lost `[1,2,3]` 111 | * [ ] better error messages phrasing (via chatgpt) 112 | * [x] Indent/format output (like mono via watr) 113 | * [ ] static arrays 114 | * [ ] list comprehension 115 | * [x] static expressions optimization 116 | * [x] static expressions for units: dynamic units 117 | * [ ] static constants with usage count (ignore from output if usage is 0) 118 | * [x] a+0 -> a 119 | * [x] a**0.5 120 | * [ ] optimize pick for simple arg as `pick(global.get x)` - just do `(global.get)` multiple times 121 | * [x] static arrays via `data` section 122 | * [ ] loops 123 | * [x] range loop in range loop 124 | * [x] comments: must put comment into a previous token, instead of stripping 125 | * [ ] release static arrays if ref is lost, to allocate memory eg `[..100]` 126 | * [x] implement no-ptr buffer lengths: either i64 or 2 values in stack 127 | * [x] ~~refactor `pick(N,arg)` into `dup(arg,3)`~~ 128 | * [x] implement computed ranges in lists creation 129 | * [x] ~~implement scopes as either tmp functions or blocks~~ -> just resolve variables 130 | * [x] Implement storing buffer items 131 | * [x] Make all `local.set` / `global.set` a `set(name, value)` function: we don't need to know about tmp/define etc. 132 | * [x] ~~Think if we need to expose fully-js API for building wasm code, similar to wasmati~~ no 133 | * [x] ~~Test nested scopes variables `(x=0;(y=1);y)`~~ 134 | * [x] ~~`(a=1,c; (b=2; c=a+b;); c)` test~~ 135 | * [x] ~~Extend i32 to i64 whereever possible~~ why? 136 | * [ ] Test optional arguments 137 | * [ ] Test a,,c = d,e,f; a,b,c = d,,f 138 | * [x] Test if memory grows properly 139 | * [x] Simple loops 140 | * [x] Track memory via runtime variable: check against array len/address 141 | * [ ] All lang reference cases 142 | * [ ] Nice errors: line number to AST nodes? 143 | * [ ] Stub all wrong syntax error cases, like `++a=123;` etc - any operators on the lhs of `=` etc., all permutations 144 | * [ ] maths: sin, cos, log, pow etc. 145 | * [x] *state 146 | * [x] `*x=[0..10]` 147 | * [x] `x()=(*i);y()=(x())` 148 | * [ ] `x()=(*(a,b,c))` 149 | * [ ] `x(_sin)=(sin() + _sin())` 150 | * [ ] Make `br_if` loops everywhere - it doesn't create control block (more lightweight) 151 | * [ ] Pass fns by reference `x(a,_osc)=(osc(a)+_osc(a))` 152 | * [ ] Readme examples 153 | * [ ] Audio gain example 154 | * [ ] Sine gen example 155 | * [ ] Ignore unassigned immediate constructs like `[1,2,3]` or `"abc"`. 156 | * [ ] All FIXMEs 157 | * [ ] customizable float type, int type, array 158 | 159 | ## 1.0: good stable release 160 | 161 | * [ ] All Bytebeats/Floatbeats 162 | * [ ] classics http://viznut.fi/demos/unix/bytebeat_formulas.txt 163 | * [ ] https://github.com/darius/bytebeat 164 | * [ ] https://dollchan.net/bytebeat 165 | * [ ] http://canonical.org/~kragen/bytebeat/ 166 | * [ ] https://tinyrave.com/ 167 | * [ ] https://llllllll.co/t/bytebeats-a-beginner-s-guide/16491 168 | * [ ] https://www.reddit.com/r/bytebeat/ 169 | * [ ] https://games.greggman.com/game/html5-bytebeat/ 170 | * [ ] https://github.com/greggman/html5bytebeat#rant 171 | * [ ] https://github.com/Butterroach/jstebeat 172 | * [ ] https://butterroach.github.io/jstebeat/ 173 | * [ ] https://sarpnt.github.io/bytebeat-composer 174 | * [ ] http://wavepot.com/ 175 | * [ ] https://github.com/stellartux/websynth 176 | * [ ] https://github.com/radavis/bytebeat 177 | * [ ] ZZFX 178 | * [ ] other bytebeat-like library (forgot name) 179 | * [ ] Web-Audio-API 180 | * [ ] Metronome app: variable speed, variable tone 181 | 182 | ## Studio 183 | 184 | * [ ] Code editor 185 | * [ ] Playback 186 | * [ ] Display spectrogram, like glitch 187 | * [ ] Display waveform, like glitch 188 | * [ ] Choose output context: 8k, 44k 189 | * [ ] Select & lock params knobs 190 | 191 | ## Backlog 192 | 193 | * [ ] extend internal arrays as separate `addr` and `len` variables: speeds up perf, allows enhanced precision 194 | * [ ] Processing modules collection 195 | 196 | * [ ] Demo page in style of assembly AI with wavefont marquee player 197 | 198 | * [ ] @audio/gain module 199 | * compiles basic son file to multicased function `gain` 200 | * [ ] wasm gain node: browser 201 | * [ ] wasm gain node: node 202 | 203 | * [ ] sonr expressions 204 | * `sin(432 + 10*sin(8)) * adsr(0, 0, .4, 0)` 205 | 206 | * [ ] Loudness meter for waveplay 207 | 208 | * [ ] Repl with compiler selector 209 | * [ ] Web-audio-api 210 | * [ ] musi 211 | * [ ] latr 212 | * [ ] Neural audio effects 213 | * [ ] Essentia 214 | 215 | ## Test cases 216 | 217 | * sonr expressions 218 | * FM, AM signals sin(f + sin(m) * 100), sin(f) * (cos(m) + 1) 219 | * weighted mixing signals sin(a) * .75 + cos(b) * .25 220 | * periodic wave eg. wave(1204) - seed indicates overtone weights 221 | * mixing: `+`, `-`, `*` and `mix(a,b,c)` 222 | * amplifying: `a*x` and `| gain(amt)` 223 | * shifting: `a | delay(±1s)` 224 | * stretching (pitch): `a | rate(1.05)` 225 | * inverting: `-a` 226 | * reversing direction: `a | reverse(x)`? 227 | * join/concat: `a|trim(..2s) + b|delay(2s) + c|delay(4s))`? 228 | * static shift: `a+c` 229 | * multiplexing: `a > 2 ? x : a > 1 ? y : z` and `mux(select, a, b, c, ...)`? 230 | * input: recording, file etc: `mic()`, `file()` 231 | * loop: `%`? `a | repeat(2)`? `a.repeat(2)`? `a(t % 2)`? 232 | * trim: `a / dur`? `dur&&a`? `a(min(t, dur))`? 233 | * cap/limit/compress max/min/clamp? 234 | * pan, channels: `a & b`? 235 | * math stdlib: common function names exposed from `Math`? 236 | * adsr: `a * adsr(params)`? `a | adsr(params)`? 237 | * convolve: `a ^ b`? 238 | * filters `a | lp(f, q)`? `a | filter(type, f, q)`? 239 | * effects: crush, curve, unisson, flanger etc. `a | crush(param)`? 240 | * map `a | abs(it)`? `a | abs($)`? 241 | * get sample: `a[x]`? `a.at(x)`? or sample-less fns? 242 | * get channel: `a[0]`? 243 | * get value at a time `a(t)`? 244 | * integrate: `∫(a, dt)`? `acc(a, dt)`? 245 | 246 | ## Real Use Cases 247 | 248 | * Speech enhancer for any videos (from Prabhupada to...) 249 | * Dereverb 250 | * Feedback suppressor, soft loudness processor for live stream, compressor 251 | * 252 | -------------------------------------------------------------------------------- /src/stdlib.js: -------------------------------------------------------------------------------- 1 | export const std = { 2 | // signed min/max (better be called i32.max_s) 3 | "i32.smax": "(func $i32.smax (param i32 i32) (result i32) (select (local.get 0) (local.get 1) (i32.ge_s (local.get 0) (local.get 1))))", 4 | "i32.smin": "(func $i32.smin (param i32 i32) (result i32) (select (local.get 0) (local.get 1) (i32.le_s (local.get 0) (local.get 1))))", 5 | "i32.dup": "(func $i32.dup (param i32) (result i32) (local.get 0)(local.get 0))", 6 | 7 | // just for reference - easier to just `f64.ne x x` directly 8 | "f64.isnan": "(func $f64.isnan (param f64) (result i32) (f64.ne (local.get 0) (local.get 0)))", 9 | 10 | // a ** b generic case 11 | // ref: https://github.com/jdh8/metallic/blob/master/src/math/double/pow.c 12 | "f64.pow": `(func $f64.pow(param f64 f64)(result f64)(local f64 i64 i64 i64 f64 f64 f64 f64 f64 f64)(local.set 2(f64.const 0x1p+0))(block(br_if 0(f64.eq(local.get 1)(f64.const 0x0p+0)))(local.set 3(i64.const 0))(block(br_if 0(i64.gt_s(i64.reinterpret_f64(local.get 0))(i64.const -1)))(br_if 0(f64.ne(f64.nearest(local.get 1))(local.get 1)))(local.set 3(i64.shl(i64.extend_i32_u(f64.ne(f64.nearest(local.tee 2(f64.mul(local.get 1)(f64.const 0x1p-1))))(local.get 2)))(i64.const 63)))(local.set 0(f64.neg(local.get 0))))(local.set 2(f64.const 0x1p+0))(block(br_if 0(f64.eq(local.get 0)(f64.const 0x1p+0)))(block(br_if 0(f64.ne(local.get 0)(f64.const 0x0p+0)))(local.set 2(select(f64.const inf)(f64.const 0x0p+0)(i64.lt_s(i64.reinterpret_f64(local.get 1))(i64.const 0))))(br 1))(block(br_if 0(f64.ne(f64.abs(local.get 0))(f64.const inf)))(local.set 2(select(f64.const 0x0p+0)(f64.const inf)(i64.lt_s(i64.reinterpret_f64(local.get 1))(i64.const 0))))(br 1))(block(br_if 0(i64.ge_s(local.tee 4(i64.reinterpret_f64(local.get 0)))(i64.const 0)))(local.set 2(f64.const nan))(br 1))(block(br_if 0(f64.ne(f64.abs(local.get 1))(f64.const inf)))(local.set 2(select(f64.const inf)(f64.const 0x0p+0)(i32.eq(i32.wrap_i64(i64.shr_u(i64.reinterpret_f64(local.get 1))(i64.const 63)))(f64.lt(local.get 0)(f64.const 0x1p+0)))))(br 1))(block(br_if 0(i64.gt_u(local.get 4)(i64.const 4503599627370495)))(local.set 4(i64.sub(i64.shl(local.get 4)(local.tee 5(i64.add(i64.clz(local.get 4))(i64.const -11))))(i64.shl(local.get 5)(i64.const 52)))))(local.set 2(f64.const inf))(br_if 0(f64.gt(local.tee 1(f64.add(local.tee 10(f64.mul(local.tee 6(f64.reinterpret_i64(i64.and(i64.reinterpret_f64(local.get 1))(i64.const -4294967296))))(local.tee 0(f64.reinterpret_i64(i64.and(i64.reinterpret_f64(f64.add(f64.add(local.tee 7(f64.mul(local.tee 0(f64.reinterpret_i64(i64.and(i64.reinterpret_f64(f64.add(local.tee 11(f64.mul(local.tee 0(f64.reinterpret_i64(i64.and(i64.reinterpret_f64(local.tee 9(f64.div(local.tee 7(f64.add(f64.reinterpret_i64(i64.sub(local.get 4)(i64.and(local.tee 5(i64.add(local.get 4)(i64.const -4604544271217802189)))(i64.const -4503599627370496))))(f64.const -0x1p+0)))(local.tee 8(f64.add(local.get 7)(f64.const 0x1p+1))))))(i64.const -134217728))))(local.tee 0(f64.reinterpret_i64(i64.and(i64.reinterpret_f64(f64.add(f64.add(local.tee 10(f64.mul(local.get 0)(local.get 0)))(local.tee 8(f64.add(f64.mul(local.tee 7(f64.div(f64.sub(f64.sub(local.get 7)(f64.mul(local.get 0)(local.tee 11(f64.reinterpret_i64(i64.and(i64.reinterpret_f64(local.get 8))(i64.const -4294967296))))))(f64.mul(local.get 0)(f64.add(local.get 7)(f64.sub(f64.const 0x1p+1)(local.get 11)))))(local.get 8)))(f64.add(local.get 9)(local.get 0)))(f64.mul(f64.mul(local.tee 0(f64.mul(local.get 9)(local.get 9)))(local.get 0))(f64.add(f64.mul(f64.add(f64.mul(f64.add(f64.mul(f64.add(f64.mul(f64.add(f64.mul(f64.add(f64.mul(local.get 0)(f64.const 0x1.91a4911cbce5ap-3))(f64.const 0x1.97a897f8e6cap-3))(local.get 0))(f64.const 0x1.d8a9d6a7940bp-3))(local.get 0))(f64.const 0x1.1745bc213e72fp-2))(local.get 0))(f64.const 0x1.5555557cccac1p-2))(local.get 0))(f64.const 0x1.b6db6db6b8d5fp-2))(local.get 0))(f64.const 0x1.3333333333385p-1))))))(f64.const 0x1.8p+1)))(i64.const -67108864))))))(local.tee 9(f64.add(f64.mul(local.get 7)(local.get 0))(f64.mul(local.get 9)(f64.add(local.get 8)(f64.add(local.get 10)(f64.sub(f64.const 0x1.8p+1)(local.get 0)))))))))(i64.const -4294967296))))(f64.const 0x1.ec709dc4p-1)))(local.tee 9(f64.add(f64.mul(local.get 0)(f64.const -0x1.7f00a2d80faabp-35))(f64.mul(f64.add(local.get 9)(f64.sub(local.get 11)(local.get 0)))(f64.const 0x1.ec709dc3a03fdp-1)))))(local.tee 8(f64.convert_i64_s(i64.shr_s(local.get 5)(i64.const 52))))))(i64.const -2097152))))))(local.tee 0(f64.add(f64.mul(f64.sub(local.get 1)(local.get 6))(local.get 0))(f64.mul(f64.add(local.get 9)(f64.add(local.get 7)(f64.sub(local.get 8)(local.get 0))))(local.get 1))))))(f64.const 0x1p+10)))(local.set 9(f64.sub(local.get 1)(local.get 10)))(block(br_if 0(f64.ne(local.get 1)(f64.const 0x1p+10)))(br_if 1(f64.lt(local.get 9)(local.get 0))))(local.set 2(f64.const 0x0p+0))(br_if 0(f64.lt(local.get 1)(f64.const -0x1.0ccp+10)))(block(br_if 0(f64.ne(local.get 1)(f64.const -0x1.0ccp+10)))(br_if 1(f64.gt(local.get 9)(local.get 0))))(local.set 4(i64.reinterpret_f64(f64.add(f64.add(local.tee 8(f64.mul(local.tee 7(f64.reinterpret_i64(i64.and(i64.reinterpret_f64(local.tee 2(f64.sub(local.get 1)(local.tee 9(f64.nearest(local.get 1))))))(i64.const -4294967296))))(f64.const 0x1.62e42ffp-1)))(f64.add(local.tee 2(f64.add(f64.mul(local.get 2)(f64.const -0x1.718432a1b0e26p-35))(f64.mul(f64.add(local.get 0)(f64.sub(local.get 10)(f64.add(local.get 9)(local.get 7))))(f64.const 0x1.62e42ffp-1))))(f64.div(f64.mul(local.tee 0(f64.add(local.get 8)(local.get 2)))(local.tee 2(f64.sub(local.get 0)(f64.mul(local.tee 2(f64.mul(local.get 0)(local.get 0)))(f64.add(f64.mul(local.get 2)(f64.add(f64.mul(local.get 2)(f64.add(f64.mul(local.get 2)(f64.add(f64.mul(local.get 2)(f64.const 0x1.63f2a09c94b4cp-25))(f64.const -0x1.bbd53273e8fb7p-20)))(f64.const 0x1.1566ab5c2ba0dp-14)))(f64.const -0x1.6c16c16c0ac3cp-9)))(f64.const 0x1.5555555555553p-3))))))(f64.sub(f64.const 0x1p+1)(local.get 2)))))(f64.const 0x1p+0))))(block(block(br_if 0(i32.eqz(f64.lt(f64.abs(local.get 9))(f64.const 0x1p+63))))(local.set 5(i64.trunc_f64_s(local.get 9)))(br 1))(local.set 5(i64.const -9223372036854775808)))(local.set 2(select(f64.mul(f64.reinterpret_i64(i64.add(local.tee 4(i64.add(i64.shl(local.get 5)(i64.const 52))(local.get 4)))(i64.const 4593671619917905920)))(f64.const 0x1p-1020))(f64.reinterpret_i64(local.get 4))(f64.lt(local.get 1)(f64.const -0x1.fep+9)))))(local.set 2(f64.reinterpret_i64(i64.or(local.get 3)(i64.reinterpret_f64(local.get 2))))))(local.get 2))`, 13 | 14 | // a %% b, also used to access buffer 15 | "i32.modwrap": `(func $i32.modwrap (param i32 i32) (result i32) (local $rem i32) 16 | (local.set $rem (i32.rem_s (local.get 0) (local.get 1))) 17 | (if (result i32) (i32.and (local.get $rem) (i32.const 0x80000000)) 18 | (then (i32.add (local.get 1) (local.get $rem))) 19 | (else (local.get $rem) 20 | ) 21 | ))`, 22 | "f64.modwrap": `(func $f64.modwrap (param f64 f64) (result f64) (local $rem f64) 23 | (local.set $rem (call $f64.rem (local.get 0) (local.get 1))) 24 | (if (result f64) (f64.lt (local.get $rem) (f64.const 0)) 25 | (then (f64.add (local.get 1) (local.get $rem))) 26 | (else (local.get $rem)) 27 | ) 28 | )`, 29 | // divident % divisor => dividend - divisor * floor(dividend / divisor) 30 | "f64.rem": `(func $f64.rem (param f64 f64) (result f64) 31 | (f64.sub (local.get 0) (f64.mul (f64.floor (f64.div (local.get 0) (local.get 1))) (local.get 1))) 32 | )`, 33 | 34 | // increase available memory to N bytes, grow if necessary; returns ptr to allocated block 35 | "malloc": `(func $malloc (param i32) (result i32) (local i32 i32)\n` + 36 | `(local.set 1 (global.get $__mem))\n` + // beginning of free memory 37 | `(global.set $__mem (i32.add (global.get $__mem) (local.get 0)))\n` + // move memory pointer 38 | `(local.set 2 (i32.shl (memory.size) (i32.const 16)) )\n` + // max available memory 39 | // 2^12 is how many f64 fits into 64Kb memory page 40 | `(if (i32.ge_u (global.get $__mem) (local.get 2)) (then\n` + 41 | // grow memory by the amount of pages needed to accomodate full data 42 | `(memory.grow (i32.add (i32.shr_u (i32.sub (global.get $__mem) (local.get 2))(i32.sub (i32.const 1)) (i32.const 16)) (i32.const 1)) )(drop)\n` + 43 | `))\n` + 44 | `(local.get 1)\n` + 45 | `)`, 46 | 47 | // fill mem area at $offset with range values $from, $to via $step param; returns ptr to address after range 48 | "range": ` 49 | (func $range.dsc (param i32 f64 f64 f64) (result i32) 50 | (loop 51 | (if (f64.gt (local.get 1)(local.get 2)) 52 | (then 53 | (f64.store (local.get 0) (local.get 1)) 54 | (local.set 0 (i32.add (local.get 0) (i32.const 8))) 55 | (local.set 1 (f64.sub (local.get 1) (local.get 3))) 56 | (br 1) 57 | ) 58 | ) 59 | ) 60 | (local.get 0) 61 | )`, 62 | 63 | // create reference to mem address (in bytes) with length (# of f64 items) - doesn't allocate memory, just creates ref 64 | "arr.ref": 65 | `(func $arr.ref (param i32 i32) (result f64)\n` + 66 | `(f64.reinterpret_i64 (i64.or\n` + 67 | // array address is int part of f64, safe up to i32 ints 68 | `(i64.reinterpret_f64 (f64.convert_i32_u (local.get 0)))\n` + 69 | // array length is last 24 bits of f64 - it doesn't affect address i32 part 70 | `(i64.extend_i32_u (i32.and (i32.const 0x00ffffff) (local.get 1)))` + 71 | `))\n` + 72 | `(return))`, 73 | 74 | // reads array address from ref (likely not needed to use since can be just converted float to int) 75 | // address is float number truncated to int 76 | "arr.adr": `(func $arr.adr (param f64) (result i32) (i32.trunc_f64_u (local.get 0)) (return))`, 77 | 78 | // reads array length as last 24 bits of f64 number 79 | "arr.len": `(func $arr.len (param f64) (result i32) (i32.wrap_i64 (i64.and (i64.const 0x0000000000ffffff) (i64.reinterpret_f64 (local.get 0)))))`, 80 | 81 | // arr.set(ref, pos, val): writes $val into array, $idx is position in array (not mem address). Returns array ref (for chaining). 82 | // FIXME: throw if setting value < length 83 | "arr.set": `(func $arr.set (param f64 i32 f64) (result f64)\n` + 84 | // wrap negative idx: if idx < 0 idx = idx %% ref[] 85 | `(if (i32.lt_s (local.get 1) (i32.const 0)) (then (local.set 1 (call $i32.modwrap (local.get 1) (call $arr.len (local.get 0))))))\n` + 86 | `(f64.store (i32.add (i32.trunc_f64_u (local.get 0)) (i32.shl (local.get 1) (i32.const 3))) (local.get 2))\n` + 87 | `(local.get 0)\n` + 88 | `(return))\n`, 89 | 90 | // same as arr.set, but returns assigned value 91 | "arr.tee": `(func $arr.tee (param f64 i32 f64) (result f64) (call $arr.set (local.get 0)(local.get 1)(local.get 2))(drop) (return (local.get 2)))`, 92 | 93 | // arr.get(ref, pos): reads value at position from array 94 | "arr.get": `(func $arr.get (param f64 i32) (result f64)\n` + 95 | // wrap negative idx 96 | `(if (i32.lt_s (local.get 1) (i32.const 0)) (then (local.set 1 (call $i32.modwrap (local.get 1) (call $arr.len (local.get 0))))))\n` + 97 | `(f64.load (i32.add (i32.trunc_f64_u (local.get 0)) (i32.shl (local.get 1) (i32.const 3))))\n` + 98 | `)`, 99 | 100 | math: `(global pi f64 (f64.const 3.141592653589793))` 101 | } 102 | 103 | 104 | export default std 105 | -------------------------------------------------------------------------------- /src/precompile.js: -------------------------------------------------------------------------------- 1 | // Prepares/streamlines tree for compiler, to reduce unnecessary tree checks and transforms 2 | // Doesn't define compilation logic 3 | 4 | import { INT, FLOAT } from './parse.js'; 5 | import { err, ids, intersect } from './util.js'; 6 | 7 | let units, prev 8 | 9 | export default function precompile(node) { 10 | prev = { units }; 11 | units = {}; // currently defined units 12 | const result = expr(node); 13 | ({ units } = prev); 14 | return result 15 | } 16 | 17 | // we need top-bottom mapping, imagine `1k = expression;` - it would need 18 | function expr(node) { 19 | if (Array.isArray(node)) { 20 | return expr[node[0]] ? expr[node[0]](node) : node 21 | } 22 | 23 | return node 24 | } 25 | 26 | // convert unit node to value 27 | function applyUnits(n, unit, ext) { 28 | if (unit) n = expr(['*', [FLOAT, n], units[unit] || err(`Unknown unit \`${unit}\``)]); 29 | if (ext) n = expr(['+', n, applyUnits(...ext.slice(1))]) 30 | return n 31 | } 32 | 33 | // takes input node, maps to processed output node 34 | // takes handle of recursion: result is replaced as-is, no external recursion is performed 35 | // + fixes units cases like 1k = expr 36 | // + allows discarding unknow syntax nodes 37 | // + allows simpler optimized walk-through 38 | // + allows more precise errors 39 | Object.assign(expr, { 40 | [FLOAT]([, n, unit, ext]) { 41 | if (unit) return applyUnits(n, unit, ext) 42 | return [FLOAT, n] 43 | }, 44 | [INT]([, n, unit, ext]) { 45 | if (unit) return applyUnits(n, unit, ext) 46 | return [INT, n] 47 | }, 48 | 49 | ';'([, ...list]) { 50 | // remove empty elements 51 | return [';', ...list.filter((s, i) => !i || s).map(s => expr(s))] 52 | }, 53 | ','(node) { 54 | return node.flatMap((a, i) => { 55 | if (!i || !a) return [a] 56 | a = expr(a) 57 | // if (a[0] === '(') { 58 | // FIXME: if internal expr returns groups - must turn full group into heap expr 59 | // } 60 | return [a] 61 | }) 62 | }, 63 | './'([, a]) { 64 | return ['./', expr(a)] 65 | }, 66 | '../'([, a]) { 67 | return ['../', expr(a)] 68 | }, 69 | 70 | '()'([, a]) { 71 | // we get rid of parens for compiler 72 | a = expr(a) 73 | // a=() -> a=(), ignore comment stubs 74 | if (!a || !a.length) return 75 | // ((x)) -> (x) 76 | if (a[0] === '()') return a 77 | // (0.5) -> 0.5 78 | if (typeof a[1] === 'number') return a 79 | // (0..10) -> 0..10 80 | if (a[1] === '..') return a 81 | // (a,b,c) -> a,b,c 82 | if (a[0] === ',') return a 83 | return a 84 | }, 85 | 86 | // f(a,b,c) 87 | '('([, name, args]) { 88 | args = !args ? [,] : args[0] === ',' ? args : [',', args] 89 | return ['(', name, args] 90 | }, 91 | 92 | // [1,2,3] 93 | '[]'([, inits]) { 94 | inits = expr(inits) 95 | 96 | // normalize to [,] form 97 | inits = !inits ? [,] : inits[0] === ',' ? inits : [',', inits] 98 | 99 | // FIXME: [0..10 <| xxx, (1,2,3) <| xxx] - can be converted to static inits 100 | inits = inits.flatMap((el, i) => { 101 | if (!i) return [el] 102 | // [1..4] -> [1,2,3] 103 | if (el[0] === '..' && typeof el[1][1] === 'number' && typeof el[2][1] === 'number' && Math.abs(el[2][1] - el[1][1]) < 108) { 104 | let from = el[1][1], to = el[2][1], step = 1, els = [] 105 | if (from === -Infinity) for (let i = 0; i < to; i += step) els.push([FLOAT, 0]) 106 | else if (from < to) for (let i = from; i < to; i += step) els.push([FLOAT, i]) 107 | else for (let i = from; i > to; i -= step) els.push([FLOAT, i]) 108 | return els 109 | } 110 | return [el] 111 | }) 112 | 113 | return ['[]', inits] 114 | }, 115 | 116 | '..'([, a, b]) { 117 | // .. 118 | if (!a && !b) return ['..', [FLOAT, -Infinity], [FLOAT, Infinity]] 119 | // ..b 120 | if (!a) return ['..', [FLOAT, -Infinity], expr(b)] 121 | // a.. 122 | if (!b) return ['..', expr(a), [FLOAT, Infinity]] 123 | // a..b 124 | a = expr(a), b = expr(b) 125 | return ['..', a, b] 126 | }, 127 | 128 | '|>'([, a, b]) { 129 | a = expr(a), b = expr(b) 130 | 131 | // FIXME: 1..10 -> 1,2,3,4,5,6,7,8,9 132 | // if (typeof a[1] === 'number' && typeof b[1] === 'number' && Math.abs(b[1]-a[1]) < 108) { 133 | // } 134 | return ['|>', a, b] 135 | }, 136 | 137 | // a[0] 138 | '['([, a, b]) { 139 | a = expr(a) 140 | 141 | if (!b) return ['[', a] 142 | 143 | b = expr(b) 144 | 145 | // a[0,1] -> a[0], a[1] 146 | return unroll('[', a, b) || ( 147 | ['[', a, b] 148 | ) 149 | }, 150 | 151 | '='([, a, b]) { 152 | b = expr(b) 153 | 154 | // a() = ... 155 | // FIXME: can handle better: collect args, returns etc. 156 | if (a[0] === '(') { 157 | // normalize body to b; form 158 | b = !b ? [';'] : b[0] === ';' ? b : [';', b] 159 | 160 | // normalize args list 161 | let [, name, args] = a 162 | args = !args ? [,] : args[0] === ',' ? args : [',', args]; 163 | 164 | return ['=', ['(', name, args], b] 165 | } 166 | 167 | // 1k = expr - define units 168 | if ((a[0] === INT || a[0] === FLOAT) && a[2]) { 169 | let [, n, unit] = a 170 | units[unit] = expr(['/', b, [FLOAT, n]]) 171 | return 172 | } 173 | 174 | // *a, *(a,b) 175 | if (a[0] === '*') { 176 | return ['=', a, b] 177 | } 178 | 179 | a = expr(a) 180 | 181 | // if b contains some members of a 182 | // (a,b)=(b,a) -> (t0=b,t1=a;a=t0,b=t1); 183 | if (a[0] === ',' && intersect(ids(a), ids(b))) { 184 | const n = a.length - 1 185 | return [';', 186 | unroll('=', [',', ...Array.from({ length: n }, (b, i) => `t:${i}`)], b), 187 | unroll('=', a, [',', ...Array.from({ length: n }, (a, i) => `t:${i}`)]) 188 | ] 189 | } 190 | 191 | // x[b]=b 192 | if (a[0] === '[]') { 193 | let [, arr, idx] = a 194 | idx = expr(idx) 195 | a = ['[]', arr, idx] 196 | } 197 | 198 | return unroll('=', a, b) || ['=', a, b] 199 | // return ['=', a, expr(b)] 200 | }, 201 | 202 | '+='([, a, b]) { return expr(['=', a, ['+', a, b]]) }, 203 | '-='([, a, b]) { return expr(['=', a, ['-', a, b]]) }, 204 | '*='([, a, b]) { return expr(['=', a, ['*', a, b]]) }, 205 | '/='([, a, b]) { return expr(['=', a, ['/', a, b]]) }, 206 | '%='([, a, b]) { return expr(['=', a, ['%', a, b]]) }, 207 | '**='([, a, b]) { return expr(['=', a, ['**', a, b]]) }, 208 | '~='([, a, b]) { return expr(['=', a, ['~', a, b]]) }, 209 | 210 | '++'([, a, b]) { return unroll('++', expr(a)) || ['++', expr(a)] }, 211 | '--'([, a, b]) { return unroll('--', expr(a)) || ['--', expr(a)] }, 212 | 213 | '+'([, a, b]) { 214 | a = expr(a), b = expr(b); 215 | return unroll('+', a, b) || ( 216 | typeof b[1] === 'number' && typeof a[1] === 'number' ? [a[0] === INT && b[0] === INT ? INT : FLOAT, a[1] + b[1]] : 217 | b[1] === 0 ? a : 218 | a[1] === 0 ? b : 219 | ['+', a, b] 220 | ) 221 | }, 222 | '-'([, a, b]) { 223 | // [-,[INT,1]] -> [INT,-1] 224 | if (!b) { 225 | a = expr(a) 226 | if (typeof a[1] === 'number') return [a[0], -a[1]] 227 | return ['-', a] 228 | } 229 | 230 | a = expr(a), b = expr(b); 231 | return unroll('-', a, b) || ( 232 | typeof a[1] === 'number' && typeof b[1] === 'number' ? [a[0] === INT && b[0] === INT ? INT : FLOAT, a[1] - b[1]] : 233 | a[1] === 0 ? ['-', b] : 234 | b[1] === 0 ? a : 235 | ['-', a, b] 236 | ) 237 | }, 238 | '*'([, a, b]) { 239 | if (!b) { 240 | return unroll('*', expr(a)) || ['*', expr(a)] 241 | } 242 | a = expr(a) 243 | b = expr(b); 244 | return unroll('*', a, b) || ( 245 | (typeof a[1] === 'number' && typeof b[1] === 'number') ? [FLOAT, a[1] * b[1]] : 246 | a[1] === 0 || b[1] === 0 ? [FLOAT, 0] : 247 | b[1] === 1 ? a : 248 | a[1] === 1 ? b : 249 | ['*', a, b] 250 | ) 251 | }, 252 | '/'([, a, b]) { 253 | // /a - return 254 | if (!b) return ['/', expr(a)] 255 | 256 | a = expr(a), b = expr(b); 257 | return unroll('/', a, b) || ( 258 | (typeof a[1] === 'number' && typeof b[1] === 'number') ? [FLOAT, a[1] / b[1]] : 259 | a[1] === 0 ? [FLOAT, 0] : // 0 / x 260 | b[1] === 1 ? a : // x / 1 261 | ['/', a, b] 262 | ) 263 | }, 264 | '%'([, a, b]) { 265 | a = expr(a), b = expr(b); 266 | return unroll('%', a, b) || ( 267 | (typeof a[1] === 'number' && typeof b[1] === 'number') ? [FLOAT, a[1] % b[1]] : 268 | ['%', a, b] 269 | ) 270 | }, 271 | 272 | '%%'([, a, b]) { 273 | a = expr(a), b = expr(b); 274 | return unroll('%%', a, b) || ( 275 | // FIXME 276 | // (typeof a[1] === 'number' && typeof b[1] === 'number') ? [FLOAT, a[1] %% b[1]] : 277 | ['%%', a, b] 278 | ) 279 | }, 280 | '**'([, a, b]) { 281 | a = expr(a), b = expr(b); 282 | return unroll('**', a, b) || ( 283 | (typeof a[1] === 'number' && typeof b[1] === 'number') ? [FLOAT, a[1] ** b[1]] : 284 | (b[1] === 0) ? [FLOAT, 1] : 285 | (a[1] === 1) ? [FLOAT, 1] : 286 | (b[1] === 1) ? a : 287 | (b[1] === -1) ? ['/', [FLOAT, 1], b] : 288 | (typeof b[1] === 'number' && b[1] < 0) ? ['/', [FLOAT, 1], expr(['**', a, [FLOAT, Math.abs(b[1])]])] : 289 | // a ** 3 -> a*a*a 290 | (typeof a === 'string' && typeof b[1] === 'number' && b[1] % 1 === 0 && b[1] <= 3) ? Array(b[1]).fill(a).reduce((prev, a) => ['*', a, prev]) : 291 | ['**', a, b] 292 | ); 293 | }, 294 | '//'([, a, b]) { 295 | a = expr(a), b = expr(b); 296 | return unroll('//', a, b) || ( 297 | (typeof a[1] === 'number' && typeof b[1] === 'number') ? [FLOAT, Math.floor(a[1] / b[1])] : 298 | a[1] === 0 ? [FLOAT, 0] : // 0 // x 299 | b[1] === 1 ? a : // x // 1 300 | ['//', a, b] 301 | ) 302 | }, 303 | 304 | '!'([, a]) { 305 | a = expr(a); return unroll('!', a) || (typeof a[1] === 'number' ? [INT, !a[1]] : ['!', a]) 306 | }, 307 | '&'([, a, b]) { 308 | a = expr(a), b = expr(b); return unroll('&', a, b) || ( 309 | typeof a[1] === 'number' && typeof b[1] === 'number' ? [INT, a[1] & b[1]] : 310 | // a & 0 -> 0 311 | a[1] === 0 || b[1] === 0 ? [INT, 0] : 312 | ['&', a, b] 313 | ) 314 | }, 315 | '|'([, a, b]) { 316 | a = expr(a), b = expr(b); return unroll('|', a, b) || ( 317 | (typeof a[1] === 'number' && typeof b[1] === 'number') ? [INT, a[1] | b[1]] : 318 | // FIXME: a | 0 -> asInt(a) 319 | // (a[1] === 0 || b[1] === 0) ? ['|', b, a] : 320 | ['|', a, b] 321 | ) 322 | }, 323 | '^'([, a, b]) { 324 | // ^b 325 | if (!b) return ['^', expr(a)] 326 | 327 | // a ^ b 328 | a = expr(a), b = expr(b) 329 | 330 | return unroll('^', a, b) || ( 331 | typeof a[1] === 'number' && typeof b[1] === 'number' ? [INT, a[1] ^ b[1]] : 332 | ['^', a, b] 333 | ) 334 | }, 335 | 336 | '<<'([, a, b]) { 337 | a = expr(a), b = expr(b); return unroll('<<', a, b) || ( 338 | typeof a[1] === 'number' && typeof b[1] === 'number' ? [INT, a[1] << b[1]] : 339 | ['<<', a, b] 340 | ) 341 | }, 342 | '>>'([, a, b]) { 343 | a = expr(a), b = expr(b); return unroll('>>', a, b) || ( 344 | typeof a[1] === 'number' && typeof b[1] === 'number' ? [INT, a[1] >> b[1]] : 345 | ['>>', a, b] 346 | ) 347 | }, 348 | 349 | '~'([, a, b]) { 350 | if (!b) { 351 | a = expr(a); return unroll('~', a) || (typeof a[1] === 'number' ? [INT, ~a[1]] : ['~', a]) 352 | } 353 | 354 | a = expr(a), b = expr(b); return unroll('~', a, b) || ['~', a, b] 355 | }, 356 | 357 | '>'([, a, b]) { a = expr(a), b = expr(b); return unroll('>', a, b) || ['>', a, b] }, 358 | '>='([, a, b]) { a = expr(a), b = expr(b); return unroll('>=', a, b) || ['>=', a, b] }, 359 | '<'([, a, b]) { a = expr(a), b = expr(b); return unroll('<', a, b) || ['<', a, b] }, 360 | '<='([, a, b]) { a = expr(a), b = expr(b); return unroll('<=', a, b) || ['<=', a, b] }, 361 | '=='([, a, b]) { a = expr(a), b = expr(b); return unroll('==', a, b) || ['==', a, b] }, 362 | '!='([, a, b]) { a = expr(a), b = expr(b); return unroll('!=', a, b) || ['!=', a, b] }, 363 | 364 | '&&'([, a, b]) { 365 | a = expr(a), b = expr(b); return unroll('&&', a, b) || ( 366 | // 0 && b 367 | a[1] === 0 ? a : 368 | // a && 0 369 | b[1] === 0 ? b : 370 | // 1 && b 371 | (typeof a[1] === 'number' && a[1]) ? b : 372 | // a && 1 373 | (typeof b[1] === 'number' && b[1]) ? a : 374 | ['&&', a, b] 375 | ) 376 | }, 377 | 378 | '||'([, a, b]) { 379 | a = expr(a), b = expr(b); return unroll('||', a, b) || ( 380 | // 0 || b 381 | a[1] === 0 ? b : 382 | // a || 0 383 | b[1] === 0 ? a : 384 | // 1 || b 385 | typeof a[1] === 'number' && a[1] ? b : 386 | // a || 1 387 | typeof b[1] === 'number' && b[1] ? a : 388 | ['||', a, b] 389 | ) 390 | }, 391 | '?'([, a, b]) { a = expr(a), b = expr(b); return unroll('?', a, b) || ['?', a, b] }, 392 | '?:'([, a, b, c]) { a = expr(a); return a[1] === 0 ? expr(c) : (typeof a[1] === 'number' && a[1]) ? expr(b) : ['?:', a, expr(b), expr(c)] } 393 | }) 394 | 395 | // if a,b contain multiple elements - try regrouping to simple ops 396 | // FIXME: not sure if there's much sense to unroll like that, mb easier be done in compiler 397 | function unroll(op, a, b) { 398 | // -(a,b) -> (-a,-b) 399 | if (!b) { 400 | if (a[0] === ',') { 401 | const [, ...as] = a 402 | return [',', ...as.map((a) => [op, expr(a)])] 403 | } 404 | return 405 | } 406 | 407 | if (a[0] === ',') { 408 | // (a0,a1) * (b0,b1); -> (a0 * b0, a1 * b1) 409 | // (a0,a1,a2) * (b0,b1) -> (a0*b0, a1*b1, a2) 410 | // (a0,a1) * (b0,b1,b2) -> (a0*b0, a1*b1, b2) 411 | // (a0,,a2) * (b0,b1,b2) -> (a0*b0, b1, a2*b2) 412 | const [, ...as] = a 413 | if (b[0] === ',') { 414 | const [, ...bs] = b 415 | if (as.length !== bs.length) err(`Mismatching number of elements in \`${op}\` operation`) 416 | return [',', 417 | ...Array.from({ length: Math.max(as.length, bs.length) }, (_, i) => [op, expr(as[i] || bs[i]), expr(bs[i] || as[i])]) 418 | ] 419 | } 420 | 421 | // (a0, a1) * b -> (a0 * b, a1 * b) 422 | return (b = expr(b), [',', ...as.map(a => [op, expr(a), b])]) 423 | 424 | // FIXME: to make more complex mapping we have to know arity of internal result 425 | // (a0, a1) * expr() -> tmp=b; (a0 * tmp, a1 * tmp) 426 | // return [';',['=','__tmp',b], ['(', [',', ...as.map(a => [op, a, '__tmp'])]]] 427 | } 428 | 429 | // a * (b0, b1) -> tmp=a; (tmp * b0, tmp * b1) 430 | if (b[0] === ',') { 431 | const [, ...bs] = b 432 | return a = expr(a), [',', ...bs.map(b => [op, a, expr(b)])] 433 | } 434 | } 435 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ⚡︎ piezo ![stability](https://img.shields.io/badge/stability-experimental-black) [![test](https://github.com/dy/piezo/actions/workflows/test.yml/badge.svg)](https://github.com/dy/piezo/actions/workflows/test.yml) 2 | 3 | Prototype language designed for signal processing, synthesis and analysis.
4 | Project is early experimental stage, design decisions must be consolidated. 5 | 6 | 7 | 8 | 9 | ## Reference 10 | 11 | ``` 12 | # Operators 13 | + - * / % -- ++ # arithmetical (float) 14 | ** %% // # power, unsigned mod, flooring div 15 | & | ^ ~ >> << # binary (integer) 16 | <<< >>> # rotate left, right 17 | && || ! # logical 18 | > >= < <= == != # comparisons (boolean) 19 | ?: # condition, switch 20 | x[i] x[] # member access, length 21 | a..b a.. ..b .. # ranges 22 | |> # # pipe/loop/map, topic reference 23 | ./ ../ .../ # continue/skip, break/stop, root return 24 | >< <> # inside, outside 25 | -< -/ -* # clamp, normalize, lerp 26 | 27 | # Numbers 28 | 16, 0x10, 0o755, 0b0; # int, hex, oct or binary 29 | 16.0, .1, 2e-3; # float 30 | π, ∞; # constants 31 | 1k=1000; 1s=44100; 1m=60s; # units 32 | 10.1k, 2π, 1m30s; # 10100, 6.283..., 66150 33 | 34 | # Variables 35 | foo=1, bar=2.0; # declare vars 36 | AbC, $0, Δx, x_1; # names permit alnum, unicodes, _$@ 37 | foo == Foo, bar == bAr; # case-insensitive 38 | default=1, eval=fn, else=0; # no reserved words 39 | true = 0b1, false = 0b0; # eg: alias bools 40 | inf = 1/0, nan = 0/0; # eg: alias infinity, NaN 41 | 42 | # Ranges 43 | 0..10; # from 1 to 9 (10 exclusive) 44 | 0.., ..10, ..; # open ranges 45 | 10..1; # reverse range 46 | 1.08..108.0; # float range 47 | (a-1)..(a+1); # computed range 48 | 0..3 * 2; # mapped range: 0*2, 1*2, 2*2 49 | (a,b,c) = 0..3 * 2; # destructure: a=0, b=2, c=4 50 | a >< 0..10, a <> 0..10; # inside(a, 0, 10), outside(a, 0, 10); 51 | a -< 0..10, a -<= 0..10; # clamp(a, 0, 10), a = clamp(a, 0, 10) 52 | a -< ..10, a -< 10..; # min(a, 10), max(a, 10) 53 | a -* 0..10, a -/ 0..10; # lerp(a, 0, 10), normalize(a, 0, 10) 54 | 55 | # Groups 56 | (a,b,c) = (1,2,3); # assign: a=1, b=2, c=3 57 | (a,b) = (b,a); # swap 58 | (a,b,c) = d; # duplicate: a=d, b=d, c=d 59 | (a,,b) = (c,d,e); # skip: a=c, b=e 60 | (a,b) + (c,d); # group binary: a+c, b+d 61 | (a, b, c)++; # group unary: a++, b++, c++ 62 | (a,b)[1] = c[2,3]; # props: a[1]=c[2], b[1]=c[3] 63 | (a,..,z) = (1,2,3,4); # pick: a=1, z=4 64 | a = (b,c,d); # pick first: a=b; see loops 65 | (a,(b,(c))) == (a,b,c); # groups are always flat 66 | 67 | # Arrays 68 | m = [..10]; # array of 10 elements 69 | m = [..10 |> 2]; # filled with 2 70 | m = [1,2,3,4]; # array of 4 elements 71 | m = [n[..]]; # copy n 72 | m = [1, 2..4, 5]; # mixed definition 73 | m = [1, [2, 3, [4, m]]]; # nested arrays (tree) 74 | m = [0..4 |> $ ** 2]; # list comprehension 75 | (a, z) = (m[0], m[-1]); # get by index 76 | (b, .., z) = m[1, 2..]; # get multiple values 77 | length = m[]; # get length 78 | m[0] = 1; # set value 79 | m[2..] = (1, 2..4, n[1..3]); # set multiple values from offset 2 80 | m[1,2] = m[2,1]; # swap 81 | m[0..] = m[-1..]; # reverse 82 | m[0..] = m[1..,0]; # rotate 83 | 84 | # Strings 85 | hi="Hello"; # creates static array 86 | string="$, world!"; # interpolate: "hello world" 87 | string[1, 3..5, -2]; # pick elements: 'e', 'lo', 'd' 88 | string[0..5]; # substring: 'Hello' 89 | string[-1..0]; # reversed: '!dlrow ,olleH' 90 | string[]; # length: 13 91 | 92 | # Conditions 93 | a ? b : c; # if a then b else c 94 | a ? b; # if a then b (else 0) 95 | a ?: b; # if (a then 0) else b 96 | val = ( # switch 97 | a == 1 ? ./1; # if a == 1 then val = 1 98 | a >< 2..4 ? ./2; # if a in 2..4 then val = 2 99 | 3 # otherwise 3 100 | ); 101 | a ? ./b; # early return: if a then return b 102 | 103 | # Loops 104 | (a, b, c) |> f($); # for each item in a, b, c do f(item) 105 | (i = 10..) |> ( # descend over range 106 | i < 5 ? ./a; # if item < 5 skip (continue) 107 | i < 0 ? ../a; # if item < 0 stop (break) 108 | ); 109 | x[..] |> f($) |> g($); # pipeline sequence 110 | (i = 0..w) |> ( # nest iterations 111 | (j = 0..h) |> f(i, j); # f(x,y) 112 | ); 113 | ((a,b) = 0..10) |> a+b; # iterate pairs 114 | (x,,y) = (a,b,c) |> $ * 2; # capture result x = a*2, y = c*2; 115 | .. |> i < 10 ? i++ : ../; # while i < 10 i++ 116 | 117 | # Functions 118 | double(n) = n*2; # define a function 119 | times(m = 1, n -< 1..) = ( # optional, clamped arg 120 | n == 0 ? ./n; # early return 121 | m * n; # returns last statement 122 | ); 123 | times(3,2); # 6 124 | times(4), times(,5); # 4, 5: optional, skipped arg 125 | dup(x) = (x,x); # return multiple 126 | (a,b) = dup(b); # destructure 127 | a,b; x()=(a=1;b=1); x(); # first expr declares locals, last returns 128 | fn() = ( x ;; log(x) ); # defer: log(x) after returning x 129 | f(a, cb) = cb(a[0]); # array, func args 130 | 131 | # State vars 132 | a() = ( *i=0; i++ ); # i persists value 133 | a(), a(); # 0, 1 134 | a.i = 0; # reset state 135 | *a1 = a; # clone function 136 | a(), a(); a1(), a1(); # 0, 1; 0, 1; 137 | f() = ( *i=0;; i++; ... ); # couples with defer 138 | 139 | # Export 140 | x, y, z; # exports last statement 141 | ``` 142 | 143 | 144 | ## Examples 145 | 146 |
147 | Gain 148 | 149 | Amplify k-rate block of samples. 150 | 151 | ``` 152 | gain( 153 | block, # block is a array argument 154 | volume -< 0..100 # volume is limited to 0..100 range 155 | ) = ( 156 | ..block[] |> block[$] *= volume; 157 | ); 158 | 159 | gain([0..5 * 0.1], 2); # 0, .2, .4, .6, .8, 1 160 | ``` 161 | 162 |
163 | 164 | 165 |
166 | Biquad Filter 167 | 168 | A-rate (per-sample) biquad filter processor. 169 | 170 | ``` 171 | 1pi = 3.1415; 172 | 1s = 44100; 173 | 1k = 10000; 174 | 175 | lpf( 176 | x0, 177 | freq = 100 -< 1..10k, 178 | Q = 1.0 -< 0.001..3.0 179 | ) = ( 180 | # filter state 181 | *(x1, y1, x2, y2) = 0; 182 | 183 | # shift state 184 | ;; (x1, x2) = (x0, x1), (y1, y2) = (y0, y1); 185 | 186 | # lpf formula 187 | w = 2pi * freq / 1s; 188 | (sin_w, cos_w) = (sin(w), cos(w)); 189 | a = sin_w / (2.0 * Q); 190 | 191 | (b0, b1, b2) = ((1.0 - cos_w) / 2.0, 1.0 - cos_w, b0); 192 | (a0, a1, a2) = (1.0 + a, -2.0 * cos_w, 1.0 - a); 193 | (b0, b1, b2, a1, a2) /= a0; 194 | 195 | y0 = b0*x0 + b1*x1 + b2*x2 - a1*y1 - a2*y2 196 | ); 197 | 198 | [0, .1, .3, ...] |> lpf($, 108, 5); 199 | ``` 200 | 201 |
202 | 203 |
204 | ZZFX 205 | 206 | Generates ZZFX's [coin sound](https://codepen.io/KilledByAPixel/full/BaowKzv) `zzfx(...[,,1675,,.06,.24,1,1.82,,,837,.06])`. 207 | 208 | ``` 209 | 1pi = 3.1415; 210 | 1s = 44100; 211 | 1ms = 1s / 1000; 212 | 213 | # waveform generators 214 | oscillator = [ 215 | saw(phase) = (1 - 4 * abs( round(phase/2pi) - phase/2pi )), 216 | sine(phase) = sin(phase) 217 | ]; 218 | 219 | # applies adsr curve to sequence of samples 220 | adsr( 221 | x, 222 | a -< 1ms.., # prevent click 223 | d, 224 | (s, sv=1), # optional group-argument 225 | r 226 | ) = ( 227 | *i = 0 ;; i++; # internal counter 228 | t = i / 1s; 229 | 230 | total = a + d + s + r; 231 | 232 | t >= total ? 0 : ( 233 | t < a ? t/a : # attack 234 | t < a + d ? # decay 235 | 1-((t-a)/d)*(1-sv) : # decay falloff 236 | t < a + d + s ? # sustain 237 | sv : # sustain volume 238 | (total - t)/r * sv 239 | ) * x 240 | ); 241 | 242 | # curve effect 243 | curve(x, amt -< 0..10 = 1.82) = (sign(x) * abs(x)) ** amt; 244 | 245 | # coin = triangle with pitch jump, produces block 246 | coin(freq=1675, jump=freq/2, delay=0.06, shape=0) = ( 247 | *out=[..1024]; 248 | *i=0;;i++; 249 | *phase = 0;; phase += (freq + (t > delay && jump)) * 2pi / 1s; 250 | t = i / 1s; 251 | 252 | # generate samples block, apply adsr/curve, write result to out 253 | ..1024 |> oscillator[shape](phase) 254 | |> adsr($, 0, 0, .06, .24) 255 | |> curve($, 1.82) 256 | |> out[..] = $; 257 | ) 258 | ``` 259 | 260 |
261 | 262 | 263 |
264 | Freeverb 265 | 266 | ## [Freeverb](https://github.com/opendsp/freeverb/blob/master/index.js) 267 | 268 | ``` 269 | <./combfilter.z#comb>; 270 | <./allpass.z#allpass>; 271 | 272 | 1s = 44100; 273 | 274 | (a1,a2,a3,a4) = (1116,1188,1277,1356); 275 | (b1,b2,b3,b4) = (1422,1491,1557,1617); 276 | (p1,p2,p3,p4) = (225,556,441,341); 277 | 278 | # TODO: stretch 279 | 280 | reverb(input, room=0.5, damp=0.5) = ( 281 | *combs_a = a0,a1,a2,a3 | a: stretch(a), 282 | *combs_b = b0,b1,b2,b3 | b: stretch(b), 283 | *aps = p0,p1,p2,p3 | p: stretch(p); 284 | 285 | combs = ( 286 | (combs_a | x -> comb(x, input, room, damp) |: (a,b) -> a+b) + 287 | (combs_b | x -> comb(x, input, room, damp) |: (a,b) -> a+b) 288 | ); 289 | 290 | (combs, aps) | (input, coef) -> p + allpass(p, coef, room, damp) 291 | ); 292 | ``` 293 | 294 | Features: 295 | 296 | * _multiarg pipes_ − pipe can consume groups. Depending on arity of target it can act as convolver: `a,b,c | (a,b) -> a+b` becomes `(a,b | (a,b)->a+b), (b,c | (a,b)->a+b)`. 297 | * _fold operator_ − `a,b,c |: fn` acts as `reduce(a,b,c, fn)`, provides efficient way to reduce a group or array to a single value. 298 | 299 |
300 | 301 | 302 |
303 | Floatbeat 304 | 305 | ### [Floatbeat](https://dollchan.net/bytebeat/index.html#v3b64fVNRS+QwEP4rQ0FMtnVNS9fz9E64F8E38blwZGvWDbaptCP2kP3vziTpumVPH0qZyXzfzHxf8p7U3aNJrhK0rYHfgHAOZZkrlVVu0+saKbd5dTXazolRwnvlKuwNvvYORjiB/LpyO6pt7XhYqTNYZ1DP64WGBYgczuhAQgpiTXEtIwP29pteBZXqwTrB30jwc7i/i0jX2cF8g2WIGKlhriTRcPjSvcVMBn5NxvgCOc3TmqZ7/IdmmEnAMkX2UPB3oMHdE9WcKqVK+i5Prz+PKa98uOl60RgE6zP0+wUr+qVpZNsDUjKhtyLkKvS+LID0FYVSrJql8KdSMptKKlx9eTIbcllvdf8HxabpaJrIXEiycV7WGPeEW9Y4v5CBS07WBbUitvRqVbg7UDtQRRG3dqtZv3C7bsBbFUVcALvwH86MfSDws62fD7CTb0eIghE/mDAPyw9O9+aoa9h63zxXl2SW/GKOFNRyxbyF3N+FA8bPyzFb5misC9+J/XCC14nVKfgRQ7RY5ivKeKmmjOJMaBJSbEZJoiZZMuj2pTEPGunZhqeatOEN3zadxrXRmOw+AA==) 306 | 307 | Transpiled floatbeat/bytebeat song: 308 | 309 | ``` 310 | ; 311 | 312 | 1s = 44100; 313 | 314 | fract(x) = x % 1; 315 | mix(a, b, c) = (a * (1 - c)) + (b * c); 316 | tri(x) = 2 * asin(sin(x)) / pi; 317 | noise(x) = sin((x + 10) * sin((x + 10) ** (fract(x) + 10))); 318 | melodytest(time) = ( 319 | melodyString = "00040008", 320 | melody = 0; 321 | 322 | 0..5 <| ( 323 | melody += tri( 324 | time * mix( 325 | 200 + ($ * 900), 326 | 500 + ($ * 900), 327 | melodyString[floor(time * 2) % melodyString[]] / 16 328 | ) 329 | ) * (1 - fract(time * 4)) 330 | ); 331 | 332 | melody 333 | ) 334 | hihat(time) = noise(time) * (1 - fract(time * 4)) ** 10; 335 | kick(time) = sin((1 - fract(time * 2)) ** 17 * 100); 336 | snare(time) = noise(floor((time) * 108000)) * (1 - fract(time + 0.5)) ** 12; 337 | melody(time) = melodytest(time) * fract(time * 2) ** 6 * 1; 338 | 339 | song() = ( 340 | *t=0;; t++; time = t / 1s; 341 | (kick(time) + snare(time)*.15 + hihat(time)*.05 + melody(time)) / 4 342 | ) 343 | ``` 344 | 345 | Features: 346 | 347 | * _loop operator_ − `cond <| expr` acts as _while_ loop, calling expression until condition holds true. Produces sequence as result. 348 | * _string literal_ − `"abc"` acts as array with ASCII codes. 349 | * _length operator_ − `items[]` returns total number of items of either an array, group, string or range. 350 | 351 | 352 |
353 | 354 | 360 | 361 | 362 | ## Usage 363 | 364 | _piezo_ is available as CLI or JS package. 365 | 366 | `npm i -g piezo` 367 | 368 | ### CLI 369 | 370 | ```sh 371 | piezo source.z -o dest.wasm 372 | ``` 373 | 374 | This produces compiled WASM binary. 375 | 376 | ### JS 377 | 378 | ```js 379 | import piezo from 'piezo' 380 | 381 | // create wasm arrayBuffer 382 | const buffer = piezo.compile(` 383 | n=1; 384 | mult(x) = x*PI; 385 | arr=[1, 2, sin(1.08)]; 386 | mult, n, arr; 387 | `, { 388 | // js objects or paths to files 389 | imports: { 390 | math: Math, 391 | mylib: './path/to/my/lib.z' 392 | }, 393 | // optional: import memory 394 | memory: true 395 | }) 396 | 397 | // create wasm instance 398 | const module = new WebAssembly.Module(buffer) 399 | const instance = new WebAssembly.Instance(module, { 400 | imports: { 401 | math: Math, 402 | // imported memory 403 | memory: new WebAssembly.Memory({ 404 | initial: 10, 405 | maximum: 100, 406 | }) 407 | } 408 | }) 409 | 410 | // use API 411 | const { mult, n, arr, memory } = instance.exports 412 | 413 | // number exported as global 414 | n.value = 2; 415 | 416 | // function exported directly 417 | mult(108) 418 | 419 | // array is a pointer to memory, get values via 420 | const arrValues = new Float64Array(arr, memory) 421 | ``` 422 | 423 | 424 | ## Motivation 425 | 426 | Audio processing has no cross-platform solution, every environment deals with audio differently, many envs don't have audio processing at all. 427 | The _Web Audio API_ has unpredictable pauses, glitches and so on, so audio is better handled in WASM worklet ([@stagas](https://github.com/stagas)). 428 | 429 | _Piezo_ attempts to provide a common layer. It is also a personal take in language design - grounded in common syntax, exploring new features like syntax groups, ranges, multiple returns, pipeline, state vars, no-OOP functional style. 430 | 431 | 432 | 433 | 434 | 440 | 441 | 442 | ## Principles 443 | 444 | * _Minimal_: maximal expressivity with short syntax. 445 | * _Intuitive_: common base, familiar patterns, visual hints. 446 | * _No keywords_: chars for vars, symbols for operators, real i18l code. 447 | * _Case-agnostic_: case changes don't break code (eg. `sampleRate` vs `samplerate`). 448 | * _Space-agnostic_: spaces and newlines can be removed or added freely. 449 | * _Explicit_: no implicit globals, no wildcard imports, no hidden file conventions (eg. `package.json`). 450 | * _Inferred types_: derived by usage, focus on logic over language. 451 | * _Normalized AST_: no complex parsing rules, just unary, binary or n-ary operators. 452 | * _Performant_: fast compile, fast execution, good for live envs. 453 | * _No runtime_: statically analyzable, no OOP, no dynamic structures, no lamdas. 454 | * _No waste_: linear memory, fixed heap, no GC. 455 | * _Low-level_: no fancy features beyond math and buffers, embeddable. 456 | * _Readable output_: produces readable WebAssembly text, can serve as meta-language. 457 | * _Minimal footprint_: minimally possible produced WASM output, no heavy workarounds. 458 | 459 | 460 | 461 | ### Inspiration 462 | 463 | [_mono_](https://github.com/stagas/mono), [_zzfx_](https://killedbyapixel.github.io/ZzFX/), [_bytebeat_](https://sarpnt.github.io/bytebeat-composer/), [_glitch_](https://github.com/naivesound/glitch), [_hxos_](https://github.com/stagas/hxos), [_min_](https://github.com/r-lyeh/min), [_roland_](https://github.com/DenialAdams/roland), [_porffor_](https://github.com/CanadaHonk/porffor) 464 | 465 | ### Acknowledgement 466 | 467 | * @stagas for initial drive & ideas 468 | 469 |

🕉

470 | -------------------------------------------------------------------------------- /docs/examples.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | 3 | # Filter 4 | 5 | ``` 6 | ; 7 | ; 8 | 9 | pi2 = pi*2; 10 | 11 | // by default input/params are a-rate 12 | lp(x, freq = 100 ~ 1..1k, Q = 1.0 ~ 0.001..3.0) = ( 13 | *(x1, x2, y1, y2); 14 | 15 | w = pi2 * freq / sampleRate; 16 | (sin_w, cos_w) = (sin(w), cos(w)); 17 | a = sin_w / (2.0 * Q); 18 | 19 | (b0, b1, b2) = ((1.0 - cos_w) / 2.0, 1.0 - cos_w, b0); 20 | (a0, a1, a2) = (1.0 + a, -2.0 * cos_w, 1.0 - a); 21 | 22 | (b0, b1, b2, a1, a2) *= 1.0 / a0; 23 | 24 | y0 = b0*x0 + b1*x1 + b2*x2 - a1*y1 - a2*y2; 25 | 26 | (x1, x2) = (x0, x1); 27 | (y1, y2) = (y0, y1); 28 | 29 | y0 30 | ); 31 | 32 | default(channel, gain) = channel[..] |>= lp(_, freq, Q) |> amp(_, gain). 33 | ``` 34 | 35 | ## Bytebeat drone 36 | 37 | ```fs 38 | ; 39 | 40 | sampleRate = 44100; 41 | 42 | fract(x) = x % 1; 43 | mix(a, b, c) = (a * (1 - c)) + (b * c); 44 | noise(x) = sin((x + 10.0) * sin((x + 10.0) ** fract(x) + 10.0)); 45 | main(x) = ( 46 | *t=0; time = ++t / sampleRate / 4.0; 47 | a = 0, j = 0; 48 | ..(j++ < 13)/0 |> 49 | a += sin((2100 + (noise((j + 2) + floor(time)) * 2500)) * time) * 50 | (1 - fract(time * floor(mix(1, 5, noise((j + 5.24) + floor(time)))))); 51 | 52 | a / 9.0. 53 | ). 54 | ``` 55 | 56 | ## Subscript fragment 57 | 58 | ```fs 59 | ; 60 | 61 | skip(n=1, from=idx, l) = cur[from..(idx+=n)]; 62 | skip(fn, from=idx, l) = ( cond(cur[idx]) ? idx++; cur.slice(from, idx) ); 63 | 64 | // a + b - c 65 | expr = (prec=0, end, cc, token, newNode, fn) => ( 66 | cc=space() ? 67 | newToken=( 68 | lookup[cc]?.(token, prec) || // if operator with higher precedence isn't found 69 | !token && lookup[0]() // parse literal or quit. token seqs are forbidden: `a b`, `a "b"`, `1.32 a` 70 | ) 71 | -< token = newToken; 72 | 73 | // check end character 74 | // FIXME: can't show "Unclose paren", because can be unknown operator within group as well 75 | end && cc==end ? idx++ : err(); 76 | 77 | token. 78 | ); 79 | 80 | // skip space chars, return first non-space character 81 | space(cc) = ((cc = cur.charCodeAt(idx)) <= SPACE -< idx++; cc). 82 | 83 | ``` 84 | 85 | ## AudioGain 86 | 87 | ```fs 88 | gain(frame, amp=1 ~ 0..1) = frame*amp. 89 | ``` 90 | 91 | ## Delay 92 | 93 | 94 | ```fs 95 | // opendsp version: https://github.com/opendsp/delay/blob/master/index.js 96 | 97 | dly(delay ~ 1..10, feedback ~ 1..10) = ( 98 | *(size=512, buffer=[..size], count=0); 99 | 100 | back = count - delay * sampleRate; 101 | back < 0 ? back = size + back; 102 | i0, i_1, i1, i2 = floor(back), i0-1, i0+1, i0+2; 103 | 104 | i_1 < 0 ? i_1 = size - 1; 105 | i1 >= size && i1 = 0; 106 | i2 >= size && i2 = 0; 107 | 108 | y_1, y0, y1, y2 = buffer[i_1], buffer[i0], buffer[i1], buffer[i2]; 109 | 110 | x = back - i0; 111 | 112 | c0, c1, c2, c3 = y0, 0.5 * (y1 - y_1), y_1 - 2.5 * y0 + 2.0 * y1 - 0.5 * y2, 0.5 * (y2 - y_1) + 1.5 * (y0 - y1); 113 | 114 | out = ((c3*x+c2)*x+c1)*x+c0; 115 | 116 | buffer[count] = input + output * feedback; 117 | 118 | ++count >= size ? count = 0; 119 | 120 | [out] 121 | ). 122 | ``` 123 | 124 | ## Oscillator 125 | 126 | ```fs 127 | sine(f=432 ~ 0..20k) = sin(f * t * 2pi); 128 | 129 | saw(f=432 ~ 0..20k) = 1 - 2 * (t % (1 / f)) * f; 130 | 131 | ramp(f) = 2 * (t % (1 / f)) * f - 1; 132 | 133 | tri(f) = abs(1 - (2 * t * f) % 2) * 2 - 1; 134 | 135 | sqr(f) = (t*f % 1/f < 1/f/2) * 2 - 1; 136 | 137 | pulse(f, w) = (t*f % 1/f < 1/f/2*w) * 2 - 1; 138 | 139 | noise() = rand() * 2 - 1; 140 | ``` 141 | 142 | ## nopop 143 | 144 | https://github.com/opendsp/nopop/blob/master/index.js 145 | ```fs 146 | ladder(threshold=0.05, amount=0.12) = ( 147 | *next, *prev; 148 | diff=next-prev; 149 | abs(diff) > threshold ? prev += diff * amount : prev = next; 150 | prev 151 | ); 152 | ``` 153 | 154 | ## step 155 | 156 | https://github.com/opendsp/step/blob/master/index.js 157 | 158 | ```fs 159 | sampleRate = 44100; 160 | 161 | step(bpm, sig, offset=0) = ( 162 | *offset = round((60 / bpm * 4) * sampleRate * offset); 163 | *max = round((60 / bpm * 4) * sampleRate * sig); 164 | *acc = 0, prev = 0; 165 | 166 | frame += offset; 167 | acc = frame - prev; 168 | acc === 0 || acc === max ? (prev = frame; acc = 0; 1) : 0; 169 | ). 170 | ``` 171 | 172 | ## envelope 173 | 174 | ```fs 175 | ; 176 | 177 | envelope(measure, decay, release) = ( 178 | #t=0; 179 | (t / 4 % measure) | v -> exp(-v.l * decay * exp(v.l * release)); 180 | ). 181 | ``` 182 | 183 | ## [freeverb](https://github.com/opendsp/freeverb/blob/master/index.js) 184 | 185 | See [fold](https://en.wikipedia.org/wiki/Fold_(higher-order_function)#In_various_languages) in different langs. 186 | 187 | ```fs 188 | <./combfilter.>on#comb'; 189 | <./allpass.>on#allpass'; 190 | ; 191 | 192 | sampleRate = 44100; 193 | 194 | a1,a2,a3,a4 = 1116,1188,1277,1356; 195 | b1,b2,b3,b4 = 1422,1491,1557,1617; 196 | p1,p2,p3,p4 = 225,556,441,341; 197 | 198 | stretch(n, rate) = floor(n * rate / 44100); 199 | sum(a, b) = a + b; 200 | waterfall(p, apcoef) = p + allpass(p, apcoef, room, damp); 201 | 202 | // Comb(coef) = (input, room, damp) -> comb(input, coef, room, damp). 203 | // Allpass(coef) = (input, room, damp) -> allpass(input, coef, room, damp). 204 | 205 | // pipes 206 | // + implicitly passed arg works well 207 | // + it also saves output space 208 | // - reducer has a bit of stretch here - it doesn't simply extend pipe as |> → ||>, but adds "fold" meaning 209 | reverb((..input), room=0.5, damp=0.5) = ( 210 | *combs_a = a0,a1,a2,a3 |> stretch(sampleRate); 211 | *combs_b = b0,b1,b2,b3 |> stretch(sampleRate); 212 | *aps = p0,p1,p2,p3 |> stretch(sampleRate); 213 | 214 | output = ..combs_a |> comb(input, room, damp) ||> sum() + ..combs_b |> comb(input, room, damp) ||> sum(); 215 | 216 | output, ..aps ||> waterfall(); 217 | ). 218 | 219 | // lambdas 220 | // + only >- new operator 221 | // + holds conventions: lambda, pipe 222 | // + resolves syntactically merged scope issue 223 | // + no implicit args, indicates apparently what's going on under the hood 224 | // + plays well with >- 225 | // - introduces fn overload, which might be ok for funcrefs 226 | // - introduces lambdas, which might be unavoidable anyways 227 | // - introduces operator precedence issue, | being above `,` which can be mitigated by raising , precedence 228 | // - overuses lambdas and/or curried functions constructors, if say we want `source | filter(freq, Q)`, can be mitigated by |> operator 229 | reverb((..input), room=0.5, damp=0.5) = ( 230 | *combs_a = a0,a1,a2,a3 | coef -> stretch(coef, sampleRate); 231 | *combs_b = b0,b1,b2,b3 | coef -> stretch(coef, sampleRate); 232 | *aps = p0,p1,p2,p3 | coef -> stretch(coef, sampleRate); 233 | 234 | combs = ..combs_a | a -> comb(a, input, room, damp) >- sum + ..combs_b | b -> comb(b, input, room, damp) >- sum; 235 | 236 | ..combs, ..aps >- waterfall; 237 | 238 | // loops 239 | // + only >- new operator 240 | // + holds conventions: lambda, pipe 241 | // + resolves syntactically merged scope issue 242 | // + no implicit args, indicates apparently what's going on under the hood 243 | // + plays well with >- 244 | // - introduces fn overload, which might be ok for funcrefs 245 | // - introduces lambdas, which might be unavoidable anyways 246 | // - introduces operator precedence issue, | being above `,` which can be mitigated by raising , precedence 247 | // - overuses lambdas and/or curried functions constructors, if say we want `source | filter(freq, Q)`, can be mitigated by |> operator 248 | reverb((..input), room=0.5, damp=0.5) = ( 249 | *combs_a = a ~ a0,a1,a2,a3 -< stretch(a, sampleRate); 250 | *combs_b = b ~ b0,b1,b2,b3 -< stretch(b, sampleRate); 251 | *aps = p ~ p0,p1,p2,p3 -< stretch(p, sampleRate); 252 | 253 | a ~ combs_a -< comb(a, input, room, damp) | sum + b ~ combs_b -< comb(b, input, room, damp) | sum; 254 | 255 | _, ..aps >- waterfall; 256 | ). 257 | ``` 258 | 259 | ## dynamic processors 260 | 261 | ``` 262 | ``` 263 | 264 | # Extra 265 | 266 | 267 | # [floatbeat 1](https://dollchan.net/bytebeat/index.html#v3b64xVlrctvIEb5Kx1UxQYsECb5EM6K18q4VK46yrshr/WEVMwSHJCS8jBmIola7v3OC3CAnyU1yknw9GIDUy7vOOhVYAoGZfvfX3UP5x2d+MpfPRs9aLyYx4fr3P/7+RT/M9aU8j/Pf1/64NV9Kz6s0xUV3r+m95/v7xWLFOP2tfrKQW5Y6meA25Z8pK7jFqn3k56kzrZtt2iW4LbaxwST8/HXMuZ3Wca9Npy1jGO5/o9vqeXpr1deM1bs0hpfsc8l+NzXl9XQopq3KtxY50/JxMpkafbfW9Xsk/HJbPluO+CESPodlYwAE2DsrnN7yvVF8TG9vp9aQ26kxo1y3RFs+y2F3/+sK+kVzf+N1nCURfTTX/6fMrR3nWaC1jGm2GX0lieV1luSh3Ehg8lQs41wRvf/XP0OlEqhxVlqno1ZLFTSukvWv17a+Dv/nsv+I1683Ws6k0CTmItVBEj8I5xcKLK/z5ikPBITxPICG5qnIOF/nwUV8IapArqMoV4HvxiEemb5egvjX+n6f9v77Uyh9fPV/T/01ro8yU0gVea43iadf/ZrEk7gocQoUJbGkZEHRhhbiKslRd5KWIpKKV0UYkg4i6U7i19IXuTK0elWQpKHYNGiWa1okGQmaBUtKgQOa3aUtMDCJa+8zOZdKB7Gc07HQsrZjABPOsEn+Kkh1HsOAE7qMkzU23apqQS9iEpG4CeIl+UmUJkpmLrt0FM8hJFcNWsmsoET7sPDPZDwPGP+FKmyi4JcNIyGTSrG0QJNOKMQbKFgLXQZhwiIg/4RWSSppk+QUBpeSiYWCZ/6KP09IxhfJBm5F4rKQZWz6GWXCn9+vGrB7TgspQ1pkUrKmTApwR/A+pg9r7ncZOfTN+bSorDohqAp2M4mA4CAkeOJw6X2zjK5d9K660XL85ujDD399c4babtIHxJG9CiUSWTgJA9dVPy1DybTHyDfNg8UCAYs1qU2MLKjgBoaolUilKnglUCA4KGw43JsbbKQy83NlgIp+iUWXRf45SdKAlQaxSelabDhWa/AHMSyYI0ZbIxr8rC6DlHkMPYiyxHh1/vboA52c0Ye3J2eHwF+ZSuTOBtnginKTvCQON8Rxc+kNh1UJDgHsp0Ue+ybzYMzyGLI/cP7LZQcprQEvyMbcogCgQ/BzneaaQaRSKS4REkDNQMiZSb2WCGbTYzB6dQN/udVa+s7RL9WtuVQCYz3kqGAWGgzAE7kbd7XrDmyXa3IgPBLxpg7gxUVZGj85RqcJIz2G/kgU6I63Ld+HcTMJ45AcIAfoGNPpZlsTfnIFryCu9j6UAuX65jpQugZ84gzwLt5oTT8gX9kyMxKcHQz9BZgJhaqbaWJ7Pcp6k2ehC/C1UiNQsjyrtdP2evSnM+8d1AIHqHMgLtvsCrhQ3qXhZtrmPBPLJFatuYySlvey3yWHKwiFEGRZko3o4YjhsBgprWWiW1S3qqXIwgCevn7o+HECCFuPz3Ig+q28ZrV3nP0WDelGhk86u2Ahq4LRa/ZY6zsYL+OaohAQoFmYLDnrKINAr4qmwUnbFYhkJXHgi9BNsmXr50sjoFXmqsVCubKTLFgGsQi3zmjUm78rab1eu2mSS+3GUrfMtpuu0sP1KvBX42G3v8/CXrNN6NWBDyAa/z8GN3GuGwa5mWyaQpznfhGpUt0dkwEL9Kyi2Vy77KVKE82ZQAa9ltduiXAJi/UqCvym2kTpCj5K1WR9TfT9JiO6if6+0lFoejg6DTxcaRfeCo0A8gRC7xVXplyU2JhRFGIsKA3zlS20PPNlWRZvuBVzp0ZvQ/cZ11nyixbfNb0YU98dNCZxiw+AGpkwAlIpeXpwPHejzBUM5dzaMi4wGlPb3R8W7MUKt6M81EEKjM2L0cM4zQDbKyyIpUA3CGIVoKWbwSk1GnqYzDdVC2pAj9lLAzZojQIBh59xDbnWaKOs0HvFOjFe59xnuDMQBrGPzoAA+SvpX2JUrTHNM3RupPhuO6IT04OM+AglCCmmc/XaLwe2faHro+KXq5BjfQWXNb16RV6n0H7GIQmLLu/S+Qo1kgpAe38wBJ9ErwSG7rT0RZBh3+sMrTjnin7P5HXa4+dXeN6nQyagEbXrDU4UFP0RgTq9GyhGBRuvLEg5Q9veCfH8x4kUKopAJouFkjyIOW+JrxlE1donrPl5ZgafiR+HKRU8hXf0+DgILBkaiX3cgQvAYmqqSF3Bafo7jkWB6fxkmje/00z4l2uRzRFcqX1AMuLA8uGocFHpjOPFaQWjsptrNtmMYkRmnOahwkjzxkqsG9QZgwUm8ZTrjjE1JAcu6oBVSTTZ+T3RTmK+AIgQyi9AdJFHaeHI7g7XySl2q/GYNihp0KcGRUgr7p0GXdTpR+jSRgp/t25fd9rtNidOZ1iLXMQq+xbVeKSdT3VDYTY55jDwd2OKc9T1IV4eIWUUVLLAMkbRgTbDupNRk0D2ghxHA0WwpMW3PRjIHBY2YqaSMNdlQZn2pmQUaPQcnpt6CckZHVC3C8GscEecqTRWkSZrB95C5x6lvON1oD2p/8FqMhhBqMvDhSlJzItZovkksDZ9oMxRkUqHj1FZwNQxqMwhhkd4cd6cKQdsde5X+poLZQmbOv1+oXsI5d5Wt8V/JJZo/zSXPjeY8mRikfLACH7ZVgxDLDLxyKTOAV6FaCiEpYPPa0SFnzlCbNk1h6TLi0HsvD/BC1ZGbONzNo5tHPSsiT+VNXzKzU9kqVwug6TAfK0/Gpufg+Jn5A1G3hB3D7/D0WBYK/rMsekaM5w9m+bAbc6YrgV4bUR3yAqkY7NbbI6oT+gmfNHoYAw3RuX7qGI9260S8PZmhhm8fZAOcNFgdMDXcHh0NDAL9CTzY7zj8cHBaPSA9zgA+DkwfAia83hV96T1WdpTXw8Pjw6JXbLSTngIR/LmJtnyD4w1fc913X7fug1rxsYXz14P+WdCKXDv2yCOdn/h0GD3t1bm+DWm1DzLo0k846+K4O3QwUExL2accWfYBi567bKmPLyZcjP0DBzzgMYGkiYN24aR04wziFNrw1Dq8FiYzVkab2EeQUKrlIAqqTMM6ZDBXAF0V0lVzjDJVrJX/1UW2W7Err7F0QSj4IGrHhunt756/S/yVW+d5by08a+8V65r47p+zHdv8Oud11/q/QsceLpVDNq0N4kxsw9Q6+1CIcJyVBX4RJ86gwb1GyzQHBrYwG7HTI9uKW2vIux+jrDtFZRWn+3Uhq/3KF/H8iH6e9Y0HBXDSgb3UNvuee2BDQODpGLGdawMJnq5q/ApIq/7NBXTmKFVhXrQ73dNX7cP9+2+29ZK+3FE+4UgbJ3ZNxZYeu+hBjO9zF9rzHfNcnJ0+gMzJXrD9qH5LsB/B0nNX3Nw2g9C6gM9ZqaYkx8fbcwR1xr5iucVPX9OxuBeb8gTnZf7Hmw4LIIF7d+hYajizVCy2sJUFPkeAFhF9mGv3TK1d8Oxdb1bhQMhMh+zBs4oQ5xRijh4pXALgd7jrN4jrFUIrQ3dp4yocnI/H2juGEHo3iXOO6bH4GYz832Ob172+ZHm/hmbB9va6VeJftDfP1Ok+7aItkB1bIerM1h3Xu4p+Q7f9JOMv03wKCC5WOBcZDBhdfRZR686bTmO+pRpo6E4PNZZ8gB9rDrv7Nsm1SJn19Ee4FEaAKmTmM9jzxrPouK/J4/DRGj+pvrsp/8A) 268 | 269 | ```fs 270 | in,abs'; 271 | 272 | // Main arpeggio 273 | m = "5:=5:=5:<5:<5:<:16:18:161:168:68"; 274 | // First bell-like sound. 275 | m2 = ": "; 276 | // First melody 277 | m3 = ": : 5 8 ::::: :<= < : 8 ::::::: "; 278 | // Second melody 279 | m4b = ":: 55 ::6666 6:<<<<<88AA66666 "; 280 | // Second melody 281 | m4 = ":: 55 ::6666 6:<==<<::AA66666 "; 282 | // Fill in at end of second melody 283 | m5 = " ?A? = < "; 284 | // Intermezzo melody 285 | m6 = ":51...55:::::::<===<<<8811111111"; 286 | // Intermezzo bass 287 | m7 = ": ::: ::: ::: ::6 666 666 666 66"; 288 | 289 | // Get Melody function. This is the real synthesizer. 290 | M(p, o, q, m, s, m2, j) = ( 291 | j = j || 0x2000; 292 | r = m[q] || 0; 293 | q = m2 != null ? m2[q] || 0 : 0; 294 | r = q === 0 ? r : (r - q) * ((t % j) / j) + q; 295 | // Get absolute pitch from semitone. 296 | g = r < 33 ? 0 : ((t % j) / ratio) * pow(2, (r + p) / 12 - o); 297 | // This section is used by both saw and triangle wave (as tri is nothing more than abs(saw)) 298 | x = (g % 255) / 128 - 1; 299 | // The real magic: decide between pulse, saw and triangle and synthesize them. 300 | s ? s < 2 ? x : s < 3 ? abs(x) * 3 : sin(PI * x) : (g & 128) / 64 - 1; 301 | ); 302 | 303 | 304 | // Base drum 305 | bd() = ( 306 | btime = 2 << 12; 307 | bm = (80 - 40) * (1 - (t % btime) / btime) ** 10 - 80; 308 | bm2 = 0b01; 309 | (bm2 >> (t / btime) % 2) & 1 ? 310 | sin(PI * (t % btime) * 2 ** (bm / 12 - 1)) * (1 - (t % btime) / btime) ** 10 : 0; 311 | ); 312 | 313 | // Hi tom 314 | bt() = ( 315 | btime = 2 << 11; 316 | btm = (80 - 15) * (1 - (t % btime) / btime) ** 10 - 80; 317 | btm2 = 0b1111010111010111; 318 | (btm2 >> (t / btime) % 16) & 1 ? 319 | sin(PI * (t % btime) * 2 ** (btm / 12 - 1)) * (1 - (t % btime) / btime) ** 10 * 0.3 : 0; 320 | ); 321 | 322 | song() = ( 323 | #t=0; 324 | t++; 325 | t *= 5.6; // Match the speed that the original song has. 326 | ratio = 0.78; // ratio is multiplied here and removed again inside the get melody function, so the pitch wont increase. 327 | t *= ratio; // v is used in many places to check how far we are in the song. It is incremented each 4096 samples, roughly. 328 | v = t >> 12; // Song looping. When past 768, repeat, skipping the first 128. 329 | v = (v % 768) + (v > 767 ? 128 : 0); 330 | 331 | (v < 640 ? 332 | // Arpeggio 333 | M(6, 5, (t >> 12) % 32, m, 3) * 0.3 + 334 | M(6, 3, (t >> 12) % 32, m, 3) * 0.01 + 335 | (v < 64 ? 0 : M(6, 4, (t >> 12) % 32, m, 2) * 0.05) + 336 | // Bell 337 | (v < 128 ? 0 : ( 338 | M(6, 3, (t >> 16) % 2, m2, 2) + 339 | M(9, 4, (t >> 16) % 2, m2, 2) + 340 | M(13, 4, (t >> 16) % 2, m2, 2) 341 | ) * (1 - (t % 65535) / 65535) * 0.05) + 342 | // First melody 343 | (v < 196 ? 0 : M(6, 4, (t >> 12) % 32, m3, (t >> 17) % 2 ? 0 : 1) * 0.05) + 344 | // This part only between 256 and 480?, then a pause until 512 and then play again 345 | (v > 255 && (v < 448 || v > 511) ? 346 | // Drums 347 | (v < 256 ? 0 : bd(t) + bt(t)) + 348 | // Second melody 349 | (v < 20 ? 0 : M(6, 3, (t >> 13) % 32, m4, 2, m4b, 0x8000) * 0.1 + 350 | M(6, 4, (t >> 13) % 32, m4, 1, m4b, 0x8000) * 0.05) + 351 | (v < 320 ? 0 : M(6, 3, (t >> 12) % 32, (t >> 17) % 2 ? m5 : ' ', 3) * 0.2) : 0) : 352 | // Outro 353 | // Intermezzo melody 354 | M(6, 4, (t >> 13) % 32, m6, 3) * 0.05 + 355 | // Intermezzo bass 356 | M(6, 5, (t >> 12) % 32, m7, 2) * (1 - (t % (2 << 11)) / (2 << 11)) * 0.05 + 357 | // Distorted drum effect 358 | ((t >> 15) % 4 ? 0 : ((((sqrt(t % 0x2000) << 6 & 255) / 127 - 1)) / ((t >> 13) % 4 + 1)) * 0.15) 359 | ); 360 | ) 361 | 362 | ``` 363 | 364 | 365 | https://dollchan.net/bytebeat/index.html#v3b64fVNRS+QwEP4rQ0FMtnVNS9fz9E64F8E38blwZGvWDbaptCP2kP3vziTpumVPH0qZyXzfzHxf8p7U3aNJrhK0rYHfgHAOZZkrlVVu0+saKbd5dTXazolRwnvlKuwNvvYORjiB/LpyO6pt7XhYqTNYZ1DP64WGBYgczuhAQgpiTXEtIwP29pteBZXqwTrB30jwc7i/i0jX2cF8g2WIGKlhriTRcPjSvcVMBn5NxvgCOc3TmqZ7/IdmmEnAMkX2UPB3oMHdE9WcKqVK+i5Prz+PKa98uOl60RgE6zP0+wUr+qVpZNsDUjKhtyLkKvS+LID0FYVSrJql8KdSMptKKlx9eTIbcllvdf8HxabpaJrIXEiycV7WGPeEW9Y4v5CBS07WBbUitvRqVbg7UDtQRRG3dqtZv3C7bsBbFUVcALvwH86MfSDws62fD7CTb0eIghE/mDAPyw9O9+aoa9h63zxXl2SW/GKOFNRyxbyF3N+FA8bPyzFb5misC9+J/XCC14nVKfgRQ7RY5ivKeKmmjOJMaBJSbEZJoiZZMuj2pTEPGunZhqeatOEN3zadxrXRmOw+AA== 366 | 367 | ```fs 368 | sin,sin'; 369 | 370 | sampleRate = 44100; 371 | 372 | fract(x) = x % 1; 373 | mix(a, b, c) = (a * (1 - c)) + (b * c); 374 | tri(x) = 2 * asin(sin(x)) / pi; 375 | noise(x) = sin((x + 10) * sin((x + 10) ** (fract(x) + 10))); 376 | melodytest(time) = ( 377 | melodyString = "00040008"; 378 | melody = 0; 379 | i = 0; 380 | i++ < 5 -< ( 381 | melody += tri( 382 | time * mix( 383 | 200 + (i * 900), 384 | 500 + (i * 900), 385 | melodyString[floor(time * 2) % #melodyString] / 16 386 | ) 387 | ) * (1 - fract(time * 4)); 388 | ) 389 | melody; 390 | ) 391 | hihat(time) = noise(time) * (1 - fract(time * 4)) ** 10; 392 | kick(time) = sin((1 - fract(time * 2)) ** 17 * 100); 393 | snare(time) = noise(floor((time) * 108000)) * (1 - fract(time + 0.5)) ** 12; 394 | melody(time) = melodytest(time) * fract(time * 2) ** 6 * 1; 395 | 396 | song() = ( 397 | *t=0; time = t++ / sampleRate; 398 | ((kick(time) + snare(time)*.15 + hihat(time)*.05 + melody(time)) / 4) 399 | ); 400 | ``` 401 | 402 | 403 | ## Sampler 404 | 405 | Plays samples when a signal from inputs come. 406 | 407 | ```fs 408 | ; 409 | 410 | // global signals if corresponding sample must start playing 411 | t = ( t1,t2,t3,t4,t5,t6,t7,t8 ) = 0; 412 | 413 | // how would we implement loading assets not compiling them into code? 414 | // passing by memory from JS, sonr would require JS counterpart 415 | s = ( 416 | s1=src('./karatal.mp3'), 417 | s2=src('./low.mp3'), 418 | s3=src('./hi.mp3') 419 | ) 420 | 421 | play() = ( 422 | *i = 0; 423 | 424 | x = 0; 425 | 426 | // how would we organize statically compiling loops with dynamic access? 427 | // similar to GLSL, fully unrolling into linear instructions? 428 | c ~ 0..#s -< 429 | sc = s[c], tc = t[c] 430 | 431 | tc > 0 ? ( // if trigger c-th is active 432 | 433 | i >= #sc ? ( // if end of c-th sound 434 | t[c] = 0 // reset trigger 435 | ) : 436 | x += sc[tc] // add sample to output 437 | 438 | ) : tc = 0 439 | 440 | i++; 441 | [x]. 442 | ) 443 | ``` 444 | -------------------------------------------------------------------------------- /lib/wat-compiler.js: -------------------------------------------------------------------------------- 1 | var e=new RegExp([/(?;;.*|\(;[^]*?;\))/,/"(?(?:\\"|[^"])*?)"/,/(?offset|align|shared|funcref)=?/,/(?([+-]?nan:)?[+-]?0x[0-9a-f.p+-_]+)/,/(?[+-]?inf|[+-]?nan|[+-]?\d[\d.e_+-]*)/,/(?[a-z][a-z0-9!#$%&'*+\-./:<=>?@\\^_`|~]+)/,/\$(?