+
Skip to content

dy/piezo

Repository files navigation

piezo stability test

Prototype language designed for signal processing, synthesis and analysis.
Project is early experimental stage, design decisions must be consolidated.

Reference

\\ 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

Examples

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 operatora,b,c |: fn acts as reduce(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 operatorcond <| 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 operatoritems[] returns total number of items of either an array, group, string or range.

Usage

piezo is available as CLI or JS package.

npm i -g piezo

CLI

piezo source.z -o dest.wasm

This produces compiled WASM binary.

JS

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)

Motivation

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.

Principles

  • 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 vs samplerate).
  • 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.

Inspiration

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

Acknowledgement

  • @stagas for initial drive & ideas

🕉

About

Prototype language for signal processing

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载