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
π, ∞; # constants
1k=1000; 1s=44100; 1m=60s; # units
10.1k, 2π, 1m30s; # 10100, 6.283..., 66150
# Variables
foo=1, bar=2.0; # declare vars
AbC, $0, Δx, x_1; # 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+bbecomes(a,b | (a,b)->a+b), (b,c | (a,b)->a+b). - fold operator −
a,b,c |: fnacts 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 <| expracts 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.wasmThis 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 envs don't have audio processing at all.
The Web Audio API has unpredictable pauses, glitches and so on, so audio is better handled in WASM worklet
(@stagas).
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.
- 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.
sampleRatevssamplerate). - 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