Prototype language designed for signal processing, synthesis and analysis.
Project is early experimental stage, design decisions must be consolidated.
\\ Operators
+ - * / % -- ++ \\ arithmetical (float)
** %% // \\ power, unsigned mod, flooring div
& | ^ ~ >> << \\ binary (integer)
<<< >>> \\ rotate left, right
&& || ! \\ logical
> >= < <= == != \\ comparisons (boolean)
?: \\ condition, switch
x[i] x[] \\ member access, length
a..b a.. ..b .. \\ ranges
|> # \\ pipe/loop/map, topic reference
./ ../ .../ \\ continue/skip, break/stop, root return
>< <> \\ inside, outside
-< -/ -* \\ clamp, normalize, lerp
\\ Numbers
16, 0x10, 0o755, 0b0; \\ int, hex, oct or binary
16.0, .1, 2e-3; \\ float
1k = 1000; 1pi = 3.1415926; \\ units
1s = 44100; 1m = 60s; \\ eg: sample indexes
10.1k, 2pi, 1m30s; \\ 10100, 6.283..., 66150
\\ Variables
foo=1, bar=2.0; \\ declare vars
AbC, $0, Δx, x@1, A#; \\ names permit alnum, unicodes, _$#@
foo == Foo, bar == bAr; \\ case-insensitive
default=1, eval=fn, else=0; \\ no reserved words
true = 0b1, false = 0b0; \\ eg: alias bools
inf = 1/0, nan = 0/0; \\ eg: alias infinity, NaN
\\ 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
0..3 * 2; \\ mapped range: 0*2, 1*2, 2*2
(a,b,c) = 0..3 * 2; \\ destructure: a=0, b=2, c=4
a >< 0..10, a <> 0..10; \\ inside(a, 0, 10), outside(a, 0, 10);
a -< 0..10, a -<= 0..10; \\ clamp(a, 0, 10), a = clamp(a, 0, 10)
a -< ..10, a -< 10..; \\ min(a, 10), max(a, 10)
a -* 0..10, a -/ 0..10; \\ lerp(a, 0, 10), normalize(a, 0, 10)
\\ Groups
(a,b,c) = (1,2,3); \\ assign: a=1, b=2, c=3
(a,b) = (b,a); \\ swap
(a,b,c) = d; \\ duplicate: a=d, b=d, c=d
(a,,b) = (c,d,e); \\ skip: a=c, 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,..,z) = (1,2,3,4); \\ pick: a=1, z=4
a = (b,c,d); \\ pick first: a=b; see loops
(a,(b,(c))) == (a,b,c); \\ groups are always flat
\\ 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, m]]]; \\ nested arrays (tree)
m = [0..4 |> # ** 2]; \\ list comprehension
(a, z) = (m[0], m[-1]); \\ get by index
(b, .., z) = 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[1,2] = m[2,1]; \\ swap
m[0..] = m[-1..]; \\ reverse
m[0..] = m[1..,0]; \\ rotate
\\ Strings
hi="Hello"; \\ creates static array
string="$<hi>, world!"; \\ interpolate: "hello world"
string[1, 3..5, -2]; \\ pick elements: 'e', 'lo', 'd'
string[0..5]; \\ substring: 'Hello'
string[-1..0]; \\ reversed: '!dlrow ,olleH'
string[]; \\ length: 13
\\ Conditions
a ? b : c; \\ if a then b else c
a ? b; \\ if a then b (else 0)
a ?: b; \\ if (a then 0) else b
val = ( \\ switch
a == 1 ? ./1; \\ if a == 1 then val = 1
a >< 2..4 ? ./2; \\ if a in 2..4 then val = 2
3 \\ otherwise 3
);
a ? ./b; \\ early return: if a then return b
\\ Loops
(a, b, c) |> f(#); \\ for each item in a, b, c do f(item)
(i = 10..) |> ( \\ descend over range
i < 5 ? ./a; \\ if item < 5 skip (continue)
i < 0 ? ../a; \\ if item < 0 stop (break)
);
x[..] |> f(#) |> g(#); \\ pipeline sequence
(i = 0..w) |> ( \\ nest iterations
(j = 0..h) |> f(i, j); \\ f(x,y)
);
((a,b) = 0..10) |> a+b; \\ iterate pairs
(x,,y) = (a,b,c) |> # * 2; \\ capture result x = a*2, y = c*2;
.. |> i < 10 ? i++ : ../; \\ while i < 10 i++
\\ Functions
double(n) = n*2; \\ define a function
times(m = 1, n -< 1..) = ( \\ optional, clamped arg
n == 0 ? ./n; \\ early return
m * n; \\ returns last statement
);
times(3,2); \\ 6
times(4), times(,5); \\ 4, 5: optional, skipped arg
dup(x) = (x,x); \\ return multiple
(a,b) = dup(b); \\ destructure
a,b; x()=(a=1;b=1); x(); \\ first expr declares locals, last returns
fn() = ( x ;; log(x) ); \\ defer: log(x) after returning x
f(a, cb) = cb(a[0]); \\ array, func args
\\ State vars
a() = ( *i=0; i++ ); \\ i persists value
a(), a(); \\ 0, 1
a.i = 0; \\ reset state
*a1 = a; \\ clone function
a(), a(); a1(), a1(); \\ 0, 1; 0, 1;
f() = ( *i=0;; i++; ... ); \\ couples with defer
\\ Export
x, y, z; \\ exports last statement
Gain
Amplify k-rate block of samples.
gain(
block, \\ block is a array argument
volume -< 0..100 \\ volume is limited to 0..100 range
) = (
..block[] |> block[#] *= volume;
);
gain([0..5 * 0.1], 2); \\ 0, .2, .4, .6, .8, 1
Biquad Filter
A-rate (per-sample) biquad filter processor.
1pi = 3.1415;
1s = 44100;
1k = 10000;
lpf(
x0,
freq = 100 -< 1..10k,
Q = 1.0 -< 0.001..3.0
) = (
\\ filter state
*(x1, y1, x2, y2) = 0;
\\ shift state
;; (x1, x2) = (x0, x1), (y1, y2) = (y0, y1);
\\ 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) /= a0;
y0 = b0*x0 + b1*x1 + b2*x2 - a1*y1 - a2*y2
);
[0, .1, .3, ...] |> lpf(#, 108, 5);
ZZFX
Generates ZZFX's coin sound zzfx(...[,,1675,,.06,.24,1,1.82,,,837,.06])
.
1pi = 3.1415;
1s = 44100;
1ms = 1s / 1000;
\\ 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 ;; i++; \\ internal counter
t = i / 1s;
total = a + d + s + r;
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
);
\\ 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;;i++;
*phase = 0;; phase += (freq + (t > delay && jump)) * 2pi / 1s;
t = i / 1s;
\\ generate samples block, apply adsr/curve, write result to out
..1024 |> oscillator[shape](phase)
|> adsr(#, 0, 0, .06, .24)
|> curve(#, 1.82)
|> out[..] = #;
)
Freeverb
<./combfilter.z#comb>;
<./allpass.z#allpass>;
1s = 44100;
(a1,a2,a3,a4) = (1116,1188,1277,1356);
(b1,b2,b3,b4) = (1422,1491,1557,1617);
(p1,p2,p3,p4) = (225,556,441,341);
\\ TODO: stretch
reverb(input, room=0.5, damp=0.5) = (
*combs_a = a0,a1,a2,a3 | a: stretch(a),
*combs_b = b0,b1,b2,b3 | b: stretch(b),
*aps = p0,p1,p2,p3 | p: stretch(p);
combs = (
(combs_a | x -> comb(x, input, room, damp) |: (a,b) -> a+b) +
(combs_b | x -> comb(x, input, room, damp) |: (a,b) -> a+b)
);
(combs, aps) | (input, coef) -> p + allpass(p, coef, room, damp)
);
Features:
- 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)
. - fold operator −
a,b,c |: fn
acts asreduce(a,b,c, fn)
, provides efficient way to reduce a group or array to a single value.
Floatbeat
Transpiled floatbeat/bytebeat song:
<math#asin,sin,pi>;
1s = 44100;
fract(x) = x % 1;
mix(a, b, c) = (a * (1 - c)) + (b * c);
tri(x) = 2 * asin(sin(x)) / pi;
noise(x) = sin((x + 10) * sin((x + 10) ** (fract(x) + 10)));
melodytest(time) = (
melodyString = "00040008",
melody = 0;
0..5 <| (
melody += tri(
time * mix(
200 + (# * 900),
500 + (# * 900),
melodyString[floor(time * 2) % melodyString[]] / 16
)
) * (1 - fract(time * 4))
);
melody
)
hihat(time) = noise(time) * (1 - fract(time * 4)) ** 10;
kick(time) = sin((1 - fract(time * 2)) ** 17 * 100);
snare(time) = noise(floor((time) * 108000)) * (1 - fract(time + 0.5)) ** 12;
melody(time) = melodytest(time) * fract(time * 2) ** 6 * 1;
song() = (
*t=0; @t++; time = t / 1s;
(kick(time) + snare(time)*.15 + hihat(time)*.05 + melody(time)) / 4
)
Features:
- loop operator −
cond <| expr
acts as while loop, calling expression until condition holds true. Produces sequence as result. - string literal −
"abc"
acts as array with ASCII codes. - length operator −
items[]
returns total number of items of either an array, group, string or range.
piezo is available as CLI or JS package.
npm i -g piezo
piezo source.z -o dest.wasm
This produces compiled WASM binary.
import piezo from 'piezo'
// create wasm arrayBuffer
const buffer = piezo.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.z'
},
// optional: import memory
memory: true
})
// create wasm instance
const module = new WebAssembly.Module(buffer)
const instance = new WebAssembly.Instance(module, {
imports: {
math: Math,
// imported memory
memory: new WebAssembly.Memory({
initial: 10,
maximum: 100,
})
}
})
// use API
const { mult, n, arr, memory } = instance.exports
// number exported as global
n.value = 2;
// function exported directly
mult(108)
// array is a pointer to memory, get values via
const arrValues = new Float64Array(arr, memory)
Audio processing has no cross-platform solution, every environment deals with audio differently, many don't have audio processing at all. The Web Audio API is unreliable – unpredictable pauses, glitches and so on, so audio is better handled in WASM worklet
(@stagas).
Piezo attempts to fill that gap, providing a common layer. It is also a personal take in language design - rethinking parts, adding missing features, and providing safe haven. It explores groups as syntax sugar, multiple returns, ranges, pipes, state vars, no-OOP functional style.
- Minimal: maximal expressivity with short syntax.
- Intuitive: common base, familiar patterns, visual hints.
- No keywords: chars for vars, symbols for operators, real i18l code.
- Case-agnostic: case changes don't break code (eg.
sampleRate
vssamplerate
). - Space-agnostic: spaces and newlines can be removed or added freely.
- Explicit: no implicit globals, no wildcard imports, no hidden file conventions (eg.
package.json
). - Inferred types: derived by usage, focus on logic over language.
- Normalized AST: no complex parsing rules, just unary, binary or n-ary operators.
- Performant: fast compile, fast execution, good for live envs.
- No runtime: statically analyzable, no OOP, no dynamic structures, no lamdas.
- No waste: linear memory, fixed heap, no GC.
- Low-level: no fancy features beyond math and buffers, embeddable.
- Readable output: produces readable WebAssembly text, can serve as meta-language.
- Minimal footprint: minimally possible produced WASM output, no heavy workarounds.
mono, zzfx, bytebeat, glitch, hxos, min, roland, porffor
- @stagas for initial drive & ideas