Skip to content

dy/ylang

Repository files navigation

🌿 ylang stability test

Essential language for audio processing and floatbeats exports.
Compiles to compact 0-runtime WASM with linear memory.
It has implicit types, organic sugar and smooth operator.

Reference

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

//////////////////////////////// operators
+ - * / % -- ++               // arithmetical (float)
** %%                         // power, unsigned mod
& | ^ ~ >> <<                 // binary (integer)
<<< >>>                       // rotate left, right
&& || !                       // logical
> >= < <= == !=               // comparisons (boolean)
?: ?                          // conditions
x[i] x[]                      // member access, length
a..b a.. ..b ..               // ranges
./ ../ .../                   // skip, break, return
|> _                          // pipe / loop
~ ~= ~/ ~* ~~/ ~~*            // clamp, normalize, lerp

//////////////////////////////// variables
foo=1, bar=2.0;               // declare vars
AbC, $0, Δx, x@1, A#;         // names permit alnum, unicodes, _$#@
foo != Foo, bar == bAr;       // capfirst-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;         // as sample indexes
10.1k, 2pi;                   // 10100, 6.283...
2m35s;                        // combinations

//////////////////////////////// statements & scopes
a, b=1, c=2;                  // declare vars in C style
foo();                        // semi-colons are mandatory
(c = a + b; c);               // group returns last statement
(a = b+1; a,b,c);             // return multiple values
(a ? ./b; c);                 // break current scope, return b
((a ? ../; c); d);            // break 2 scopes
(((a ? .../; c); d); e);      // break to the root scope

//////////////////////////////// conditions
a ? b;                        // if a then b else 0 (question operator)
a ?: b;                       // if not a then b (elvis operator)
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

//////////////////////////////// groups
(a,b,c) = (d,e,f);            // assign (a=d, b=e, c=f)
(a,b) = (b,a);                // swap
(a,b,c) = d;                  // duplicate: (a, b, c) = (d, d, d);
(a,,b) = (c,d,e);             // skip: (a=c, d, b=e);
(a,b) + (c,d);                // any operator: (a+c, b+d)
(a, b, c)++;                  // (a++, b++, c++)
(a,b)[1] = c[2,3];            // props: (a[1]=c[2], b[1]=c[3])
a = (b,c,d);                  // a=b; a=c; a=d; (see loops)

//////////////////////////////// ranges
0..10;                        // from 1 to 9 (10 exclusive)
0.., ..10, ..;                // open ranges
10..1;                        // reverse range
1.08..108.0;                  // float range
(a-1)..(a+1);                 // computed range
(a,b,c) = 0..3 * 2;           // a=0, b=2, c=4
a ~ 0..10; a ~= 0..10;        // clamp(a, 0, 10); a = clamp(a, 0, 10);
a ~/ 0..10; a ~* 0..10;       // normalize(a, 0, 10); lerp(a, 0, 10);
a ~~/ 0..10; a ~~* 0..10;     // smoothstep(a, 0, 10); ismoothstep(a, 0, 10);

//////////////////////////////// arrays
m = [..10];                   // array of 10 elements
m = [..10 |> 2];              // filled with 2
m = [1,2,3,4];                // array of 4 elements
m = [n[..]];                  // 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 from range
m[1,2] = m[2,1];              // swap
m[0..] = m[-1..0];            // reverse order
m[0..] = m[1..,0];            // rotate
min~= ..m[..], max~= m[..]..; // find min/max in array

//////////////////////////////// loops
(a, b, c) |> f(_);            // for each item in a, b, c do f(item)
(i = 10..) |> (               // descend over range
  i < 5 ? ./;                 // if item < 5 skip (continue)
  i < 0 ? ../;                // if item < 0 break
);                            //
x[..] |> f(_) |> g(_);        // sequence of ops
x[..] |>= _ * 2;              // overwrite source
(i = 0..w) |> (               // nest iterations
  (j = 0..h) |> f(i, j);      // f(x,y)
);                            //
(x,,y) = (a,b,c) |> _ * 2;    // x = a * 2, y = c * 2;
.. |> i < 10 ? i++ : ./;      // while i < 10 i++
..(i < 10) / 0 |> i++;        // alternative while

//////////////////////////////// functions
double(n) = n*2;              // define a function
times(m = 1, n ~ 1..) = (     // optional, clamped arg
  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);               // multiple 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 fn scope
fib(); c();                   // 5; 1, 2, 3;
d(fn) = (fib(), fn());        // to get external state, pass fn as argument
d(c);                         // 1, 8;

//////////////////////////////// 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 array argument
  volume ~ 0..100                 // volume is limited to 0..100 range
) = (
  block[..] |>= _ * volume        // multiply each sample by volume value
);

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

gain                              // export gain function
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 = 100 ~ 1..10k,            // filter frequency, float
  Q = 1.0 ~ 0.001..3.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
);

// i = [0, .1, .3] |> lpf(i, 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=[..1024];
  *i=0;
  *phase = 0;                   // current phase
  t = i / 1s;

  // generate samples block, apply adsr/curve, write result to out
  ..  |> oscillator[shape](phase)
      |> adsr(_, 0, 0, .06, .24)
      |> curve(_, 1.82)
      |> out[..] = _;

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

See all examples

Compiler

Basic algorithm of compilation:

  1. Parse with set of instructions/precedences into lispy tree.
  2. Precompile - clean up, normalize, validate, unroll groups, prepare for compiler.
  3. Compile into wasm via code builder methods with stdlib includes.

Motivation

Web Audio is unreliable - it has unpredictable pauses, glitches and so on, so audio is better handled in WASM worklet (@stagas). Besides, audio processing in general has no cross-platform solution, various environments deal with audio differently, some don't have audio processing at all.

Ylang attempts to fill that gap, providing a common layer for audio processing. It is personal attempt of language design - what if JS had groups, ranges and had no clutter? WASM target gives max performance and compatibility - browsers, audio/worklets, web-workers, nodejs, embedded systems etc.

Inspiration

mono, zzfx, bytebeat, glitch, hxos, min, roland, porffor

Acknowledgement

  • @stagas for initial drive & ideas
  • for package name

🕉

About

Microlanguage for audio-processing

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published