dy / mell

Microlanguage for audio-processing

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

mell stability

Microlanguage with refined syntax, linear memory and compiling to compact 0-runtime WASM.
Made for the purpose of float/bytebeats and DSP.

Reference

///////////////////////////////// numbers
16, 0x10, 0b0;                 // int, hex or binary
16.0, .1, 1e3, 2e-3;           // float

///////////////////////////////// operators
+ - * / % -- ++                // arithmetical (float)
** %%                          // power, unsigned mod
&& || ! ?:                     // logical (boolean)
> >= < <= == != ~=             // comparisons (boolean)
& | ^ ~ >> <<                  // binary (integer)
<<< >>>                        // rotate left, right
[i] []                         // member access, length
<? <=? ..                      // clamp/min/max, range
./ ../ .../                    // continue, break, return
|> |>= #                       // loop, map, item

///////////////////////////////// variables
foo=1, bar=2.0;                // declare vars
AbCF#, $0, Δx, _;              // names permit alnum, unicodes, #_$
fooBar123 != FooBar123;        // names are case-sensitive
default=1, eval=fn, else=0;    // no reserved words
true = 0b1, false = 0b0;       // alias bools
inf = 1/0, nan = 0/0;          // alias infinity, NaN

///////////////////////////////// units
1k = 1000; 1pi = 3.1415926;    // define units
1s = 44100; 1m = 60s;          // useful for sample indexes
10.1k, 2pi;                    // units deconstruct to numbers: 10100, 6.283
2m35s;                         // allow combinations

///////////////////////////////// statements
foo();                         // semi-colons at end of line are mandatory
(c = a + b; c);                // parens return last statement
(a = b+1; a,b,c);              // can return multiple values
(a ? ./b; c);                  // break current scope (return b)
((a ? ../b; c); d);            // break 2 scopes
(((a ? .../b; c); d); e);      // break to the root scope

///////////////////////////////// conditions
a ? b;                         // if a then b (single-branch conditional)
sign = a < 0 ? -1 : +1;        // ternary conditional
(2+2 >= 4) ? log(1) :          // multiline/switch
  3 <= 1..2 ? log(2) :         // else if
  log(3);                      // else
a && b || c;                   // (a and b) or c
a ~= b;                        // if a almost equal b (f32 step tolerance)

///////////////////////////////// groups
a, b=1, c=2;                   // define multiple values
(a, b, c)++;                   // increment multiple: (a++, b++, c++)
(a,b,c) = (d,e,f);             // assign multiple: a=d, b=e, c=f
(a,b) = (b,a);                 // swap
(a,b) + (c,d);                 // operator for multiple members: (a+c, b+d)
(a,b)[1] = c[2,3];             // multiple props: (a[1]=c[2], b[1]=c[3])
(a,b,c) = d;                   // duplicate: (a, b, c) = (d, d, d);
(a,,b) = (c,d,e);              // skip: (a=c, d, b=e);

///////////////////////////////// ranges
0..10;                         // from 1 to 9 (10 exclusive)
0.., ..10, ..;                 // open ranges
10..1;                         // reverse range
1.08..108.0;                   // float range
(x-1)..(x+1);                  // calculated ranges
x < 0..10;                     // is x in 0..10 range, 10 exclusive
x > 0..10;                     // is x out 0..10 range, 10 exclusive
x <= 0..10;                    // is x in 0..10 range, 10 inclusive
x >= 0..10;                    // is x out 0..10 range, 10 inclusive
x <? 0..10;                    // clamp(x, 0..10), 10 exclusive
x <=? 0..10;                   // clamp(x, 0..10), 10 inclusive
x <?= 0..10;                   // x = clamp(x, 0, 10)
x <=?= 0..10;                  // x = clamp(x, 0, 10)
a,b,c = 0..3;                  // a==0, b==1, c==2
(-10..10)[];                   // span is 20

///////////////////////////////// functions
double(n) = n*2;               // define function
times(m = 1, n <?= 1..) = (    // optional, clamped args
  n == 0 ? ./n;                // early return
  m * n                        // default return
);                             //
times(3,2);                    // 6
times(5);                      // 5. optional argument
times(,10);                    // 10. skipped argument
copy = triple;                 // capture function
copy(10);                      // also 30
dup(x) = (x,x);                // return multiple values
(a,b) = dup(b);                // get returns

///////////////////////////////// state vars
a() = ( *i=0; ++i );           // i persists value between calls
a(), a();                      // 1, 2
fib() = (                      //
  *i=[1,0,0];                  // local memory of 3 items
  i[1..] = i[0..];             // shift memory
  i[0] = i[1] + i[2];          // sum prev 2 items
);                             //
fib(), fib(), fib();           // 1, 2, 3
c() = (fib(), fib(), fib());   // state is defined by function scope
fib(); c();                    // 5; 1, 2, 3;
d(_fn) = (fib(), _fn());       // to get external state, pass fn as argument
d(c);                          // 1, 8;

///////////////////////////////// arrays
m = [..10];                    // array of 10 elements
m = [..10 |> 2];               // filled with 2
m = [1,2,3,4];                 // array of 4 elements
m = [n[0..]];                  // copy n
m = [1, 2..4, 5];              // mixed definition
m = [1, [2, 3, [4]]];          // nested arrays (tree)
m = [0..4 |> # * 2];           // list comprehension
(first, last) = (m[0], m[-1]); // get by index
(second, ..last) = m[1, 2..];  // get multiple values
length = m[];                  // get length
m[0] = 1;                      // set value
m[2..] = (1, 2..4, n[1..3]);   // set multiple values from offset 2
m[0..] = 0..4 |> # * 2         // set via iteration
m[1,2] = m[2,1];               // rearrange
m[0..] = m[-1..0];             // reverse order
m[0..] = m[1..,0];             // rotate

///////////////////////////////// loops
a, b, c |> f(#);               // for each a, b, c do f(item)
10.. |> (                      // descend over range
  # < 5 ? ./;                  // if item < 5 continue
  # < 0 ? ../;                 // if item < 0 break
);                             //
items |> f(#);                 // iterate over array
items |> f(#) |> g(#);         // pipe
0..w |> (                      // nest iterations
  x = #;                       // assign top-level item
  0..h |> f(x,#);              // f(x,y)
);                             //
(x,,y) = a, b, c |> #;         // x = a, y = c;
x[3..5] |>= # * 2;             // map items in range
.. |> (i >= 10 ? ./; f(i++));  // while i < 10 do f(i++)

///////////////////////////////// export
x, y, z                        // exports last statement

Examples

Gain

Provides k-rate amplification for block of samples.

gain(                               // define a function with block, volume arguments.
  block,                            // block is a list argument
  volume <?= 0..100                 // volume is limited to 0..100 range
) = (
  block |>= # * volume;             // map each sample by multiplying by value
);

gain([0..5 |> # * 0.1], 2);         // 0, .2, .4, .6, .8, 1

gain                                // export gain function

Minifies as gain(b,v)=b|>=#*v.

Biquad Filter

A-rate (per-sample) biquad filter processor.

1pi = pi;                           // define pi units
1s = 44100;                         // define time units in samples
1k = 10000;                         // basic si units

lpf(                                // per-sample processing function
  x0,                               // input sample value
  freq <?= 1..10k = 100,            // filter frequency, float
  Q <?= 0.001..3.0 = 1.0            // quality factor, float
) = (
  *(x1, y1, x2, y2) = 0;            // define filter state

  // lpf formula
  w = 2pi * freq / 1s;
  (sin_w, cos_w) = (sin(w), cos(w));
  a = sin_w / (2.0 * Q);

  (b0, b1, b2) = ((1.0 - cos_w) / 2.0, 1.0 - cos_w, b0);
  (a0, a1, a2) = (1.0 + a, -2.0 * cos_w, 1.0 - a);

  (b0, b1, b2, a1, a2) *= 1.0 / a0;

  y0 = b0*x0 + b1*x1 + b2*x2 - a1*y1 - a2*y2;

  (x1, x2) = (x0, x1);              // shift state
  (y1, y2) = (y0, y1);

  y0                                // return y0
);

// (0, .1, .3) |> lpf(#, 108, 5);

lpf.                                // export lpf function, end program
ZZFX

Generates ZZFX's coin sound zzfx(...[,,1675,,.06,.24,1,1.82,,,837,.06]).

1pi = pi;
1s = 44100;
1ms = 1s / 1000;

// define waveform generators
oscillator = [
  saw(phase) = (1 - 4 * abs( round(phase/2pi) - phase/2pi )),
  sine(phase) = sin(phase)
];

// applies adsr curve to sequence of samples
adsr(
  x,
  a <= 1ms..,                     // prevent click
  d,
  (s, sv=1),                      // optional group-argument
  r
) = (
  *i = 0;                         // internal counter, increments after fn body
  t = i / 1s;

  total = a + d + s + r;

  y = t >= total ? 0 : (
    t < a ? t/a :                 // attack
    t < a + d ?                   // decay
    1-((t-a)/d)*(1-sv) :          // decay falloff
    t < a  + d + s ?              // sustain
    sv :                          // sustain volume
    (total - t)/r * sv
  ) * x;
  i++;
  y
);

// curve effect
curve(x, amt<=0..10=1.82) = (sign(x) * abs(x)) ** amt;

// coin = triangle with pitch jump, produces block
coin(freq=1675, jump=freq/2, delay=0.06, shape=0) = (
  out[1023];                      // output block of 1024 samples
  *i=0;
  *phase = 0;                     // current phase
  t = i / 1s;

  out |>= oscillator[shape](phase)
      |> adsr(#, 0, 0, .06, .24)
      |> curve(#, 1.82);

  i++;
  phase += (freq + (t > delay && jump)) * 2pi / 1s;
).

See all examples

Usage

mell is available as CLI or JS package.

npm i mell

CLI

mell source.mel -o dest.wasm

This produces compiled WASM binary.

JS

import mell from 'mell'

// create wasm arrayBuffer
const buffer = mell.compile(`
  n=1;
  mult(x) = x*PI;
  arr=[1, 2, sin(1.08)];
  mult, n, arr;
`, {
  // js objects or paths to files
  imports: {
    math: Math,
    mylib: './path/to/my/lib.mel'
  },

  // name of imported/exported memory
  memory: '__memory',

  // target: `wat` for text or `wasm`
  target: 'wasm'
})

// create wasm instance
const module = new WebAssembly.Module(buffer)
const instance = new WebAssembly.Instance(module, {
  imports: {math: Math}
})

// use API
const { mult, n, arr, __memory } = instance.exports

// number exported as global
n.value = 2;

// function exported directly
mult(108) // 216

// array is a number pointer to memory
const arrValues = new Float64Array(__memory, arr.value, 3)

Motivation

Audio processing doesn't have general cross-platform solution, many environments lack audio features. JS Web Audio API in particular is not suitable for audio purposes: unpredictable pauses, glitches and so on. It's better handled in worklet with WASM.
mell addresses these points, making audio code more accessible and robust.

That's personal attempt to rethink some JS parts and secure language ground. Someone may find it a line noise, but I find it beautiful.

Inspiration

🕉

About

Microlanguage for audio-processing

License:MIT License


Languages

Language:JavaScript 99.7%Language:HTML 0.3%