0.0.0 • Published 1 year ago

melo v0.0.0

Weekly downloads
-
License
MIT
Repository
github
Last release
1 year ago

melo stability test

Micro language for floatbeats and audio with smooth operator and organic sugar. Compiles to compact 0-runtime WASM with linear memory.

Reference

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

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

\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ variables
foo=1, bar=2.0;              \\ declare vars
AbCF, $0, Δx, _;             \\ names permit alnum, unicodes, _$
fooBar123 != FooBar123;      \\ case-sensitive
default=1, eval=fn, else=0;  \\ freedom of speech
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;        \\ useful for sample indexes
10.1k, 2pi;                  \\ 10100, 6.283...
2m35s;                       \\ combinations

\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ statements
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 (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 = [i = 0..4 |> i ** 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
i = a, b, c |> f(i);         \\ for each item in a, b, c do f(item)
i = 10.. |> (                \\ descend over range
  i < 5 ? ^;                 \\ if item < 5 continue
  i < 0 ? ^^;                \\ if item < 0 break
);                           \\
x[..] |> f(%) |> g(%);       \\ sequence of ops
x[..] |> % * 2 |> y[..];     \\ write to destination
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

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
    |> block[..]
);

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

gain                              \\ export gain function

Minifies as gain(b,v)=b[..]|>%*v|>b[..]

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

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
  out[..] = (
    i = out[..]
      |> oscillator[shape](phase)
      |> adsr(i, 0, 0, .06, .24)
      |> curve(i, 1.82)
  );

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

See all examples

Usage

melo is available as CLI or JS package.

npm i melo

CLI

melo source.melo -o dest.melo

This produces compiled WASM binary.

JS

import melo from 'melo'

// create memory buffer (optional)
const memory = new WebAssembly.Memory({
  initial: 10,
  maximum: 100,
});

// create wasm arrayBuffer
const buffer = melo.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.melo'
  },

  memory,

  // target: `wat` for text or `wasm`
  target: 'wasm'
})

// create wasm instance
const module = new WebAssembly.Module(buffer)
const instance = new WebAssembly.Instance(module, {
  imports: {math: Math}
})

// use API
const { mult, n, arr } = instance.exports

// number exported as global
n.value = 2;

// function exported directly
mult(108) // 216

// array is a number pointer to memory
const arrValues = new Float64Array(memory, arr.value, 3)

Motivation

Melo is personal take on what would well designed language look like. It has narrow focus - audio processing & DSP, mainly to give advantage over JS / Web Audio in terms of performance & memory.

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. Good old audio code gets dated, in 20 years most of the soft is unable to run.

So melo attempts to fill that gap, trying to provide a standard layer. WASM enables it for browsers, audio/worklets, web-workers, nodejs, embedded systems and any other envs. In the future it aims at GL and JS as compile targets.

Inspiration

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

Acknowledgement

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

1 year ago

0.0.2

7 years ago

0.0.1

7 years ago