0.0.1 • Published 3 years ago

unisons v0.0.1

Weekly downloads
-
License
ISC
Repository
github
Last release
3 years ago

unisons

Hi-level composable sound functions for expressive and compact sound description.

import {$, sin, adsr, note} from 'unisons'

sin(note.A4 + sin(5, 10)) * adsr(0, 0, .4, 0) | speaker()

// === zzfx.play(...[,0,925,,.4,0,,,,,,,,,5]);

Main goals:

  • expressing complex sounds with tiny footprint (serializable to string);
  • reproduce basic synthesizers, alternative to heavy soundfonts (like this);
  • variable sounds (by analogy with variable fonts) − in reality sounds are variable;
  • just fun.

Initially inspired by zzfx, decomposed to more generic and descriptive form with operators overloading.

Examples

// zzfx(...[,,1675,,.06,.24,1,1.82,,,837,.06]); // coin
tri(t => 1675 + ( t > 0.06 ? 837 : 0 )) * adsr(0,0,.06,.24) | shape(1.82) | speaker()

API

import { src, osc, vol, sin, adsr, $ } from 'sones'

// define sound expression
$(440)                    // constant signal
$()                       // silence
$(a + b + c)              // sound expression (see below)
$(time => value)          // generate from time function, null = end

// oscillators
sin(f), tri(f), saw(f), rect(f), tan(f) // params: f, duration, amp
osc(seed, freq=220)       // oscillate periodic waveform, eg. o(4321) === ac.createPeriodicWave([1,.75,.5,.25])

// noises
white(), purple(), brown(), gray()

// randoms
rand(from, to)
gauss(mean, sdev)
choice(arr)

// sources
src('https://url.to/my-sound.mp3')    // remote/local source
src('base64')                         // base64 source

from(new Uint8Array([2,5,123,...]))   // uint8 samples 0..255
from(new Float32Array([.1, .25, .43, ...])) // float samples -1..+1

mic(vol | params)       // signal from microphone
waa(node)       // signal from web audio api node
midi()

// mixing
mix(a, b, c, ...) // sum + normalize signals
seq(a, b, c, ...) // sequence of signals
// mux([a, b, c], selector) // multiplexor, unlike sampler, selects input signal
// NOTE: in fact overloading can be employed to provide these ops


// transitions (useful to multiply source)
line(from, to)
fade(from, to)
adsr(a, d?, s?, r?)

// windows
blackman()
rect()

// ZZFX adapter
zzfx(...params)

// transforms
vol(amp)       // multiply signal
// gain(amp)      // multiply signal in decibels
dur(t)          // limit signal by the duration
map((x, t) => y)     // map samples

// filters
lp(f, Q)
hp(f, Q)
bp(f, Q)
ls()
hs()
peak()
notch()
allpass()
biquad()
loudness()

// fx
shape(curve, oversample)  // waveshaper node / zzfx curve
delay(t=0, mix=1)         // zzfx delay, or pitch jump: delay(1)
reverb(profile)
repeat(frame)             // zzfx repeat
compress()

// audio
pan(-1..+1)               // direct sound to one of the channels

// musical
note(n='A4'), note`A4`, note.A4
chord(type, root)
sampler(dict, controller)

// output
speaker(amp=1)
file(name)
array(arr)

// plotting: waveform=1, spectrum=2, spectrorgam=3
plot(type=1, options)

// primitives
10 // static number
[10, 20, 30] // multiple signals in one to mix? channels? sequence?
B1, A3 // scientific notation notes

//  operators
sin(220) + sin(440) - sin(880) + .5   // add/subtract signals + constant shift
sin(440) * adsr(.5, .2, .1, .1) * .5  // multiply: modulate amplitude, amplify
sin(440) | shape(.4) | speaker(.75)   // pipe signals

Operator overloading

Unisons support limited operator overloading expressions:

ExpressionLimitGoodBadSolution
±a ± b? ± c?... ± numbernumber < 100000sin(220) + sin(440) + .5sin(220) + sin(440) + sin(880) + 192000use mix to merge 2+ signals and big constants: mix(sin(220), sin(440), sin(880), 192000)
±a * b? * c? * number0.001 < number < 1000sin(440) * .001sin(440) * .0001use vol for precise amp: sin(440) | vol(.1234)
±a * m ± b? * n? + numbercoef is known rational fraction < 10, number < 100000sin(220)/3 + sin(440)/3 + 1/3sin(220)*.171 + sin(440)*.231 + .741use map for arbitrary calc sin(440) | map(x => x*.171 + 1) or mix to precise weighting mix(sin(220)*.171, sin(440)*.231, .741)
a | b | c ...b, c are transforms; pipe loses static constant shift part of expression; pipe must be wrapped into $() or connected to output.white() | lpf(440) | speaker()sin(440) * .5 + .5 | lpf()

Motivation

  • Slang: we don't need new language, we have JS.
  • ZZFX: params are unreadable, can be replaced with fn composition.
  • WAA: hairy.
  • Wasgen: wraps WAA.
  • Soundfunts: heavy.