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.
;; Numbers
16, 0x10, 0b0; ;; int, hex or binary
16.0, .1, 1e3, 2e-3; ;; float
;; Operators
- * / % -- ;; arithmetical (float)
** %% // ;; power, unsigned mod, flooring div
& | ^ ~ >> << ;; binary (integer)
<<< >>> ;; rotate left, right
&& || ! ;; logical
> >= < <= == != ;; comparisons (boolean)
?: ? ;; conditions
x[i] x[] ;; member access, length
a..b a.. ..b .. ;; ranges
./ ../ .../ . ;; skip, break, return, end
|> _ ;; 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.); ;; void return
(a = b 1; a,b,c); ;; return multiple values
(a ? ./b; c); ;; break current scope, return b
((a ? ../; c); d.); ;; break 2 scopes, void return
(((a ? .../b; c); d); e); ;; break to the root scope, func return
;; Conditions
a ? b; ;; if a then b (void)
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=d, b=d, c=d);
(a,,b) = (c,d,e); ;; skip: (a=c, d, b=e);
(a,b) (c,d); ;; group binary: (a c, b d)
(a, b, c) ; ;; group unary: (a , b , c )
(a,b)[1] = c[2,3]; ;; props: (a[1]=c[2], b[1]=c[3])
(a,b,..) = (c,d,e,f); ;; pick: a=c,b=d;
a = (b,c,d); ;; loop single: a=b; a=c; a=d;
(a,b) = (c,d,e,f); ;; loop pairs: a=c,b=d; a=e,b=f;
;; 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 && a < 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 ; ;; hack: while
((a,b) = 0..10 |> (a b)); ;; loop pairs
;; 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
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
Basic algorithm of compilation:
- Parse with set of instructions/precedences into lispy tree.
- Precompile - clean up, normalize, validate, unroll groups, prepare for compiler.
- Compile into wasm via code builder methods with stdlib includes.
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.
mono, zzfx, bytebeat, glitch, hxos, min, roland, porffor
- @stagas for initial drive & ideas
- for package name