main.bend source
main.bend on the hub · documented module
# ezaudio: a Clip is a sample rate, a channel count, a sample format, and# interleaved PCM frames. WAV reads and writes RIFF PCM. MP3 reads and writes# MPEG-1 Layer III.import Baseimport ./wav.bend as Wavimport ./mp3.bend as Mp3# signed 16-bit PCM, or IEEE 754 binary32type Format is Data: S16{} Flt{}# one clip. pcm is interleaved. s16 lives in the low 16 bits.type Clip is Data: Clip{hz: U32, nch: U32, fmt: Format, pcm: List<&2, U32>}# signed 16-bit little-endian PCMdef s16() -> Format: S16{}# IEEE 754 binary32, stored as its bitsdef f32() -> Format: Flt{}def kind.of(fmt: Format) -> U32: match fmt: case S16{}: 1 case Flt{}: 3def fmt.of(+kind: U32) -> Format: match kind: case 1: S16{} case _: Flt{}def wav.clip(w: Wav.Wav) -> Clip: match w: case Wav.Wav{+rate, +channels, +kind, frames}: Clip{rate, channels, fmt.of(kind), frames}def fmt.same(aa: Format, bb: Format) -> Bool: match aa bb: case S16{} S16{}: True{} case Flt{} Flt{}: True{} case S16{} Flt{}: False{} case Flt{} S16{}: False{}# a clip from its rate, channel count, format, and interleaved framesdef clip(+rate: U32, +channels: U32, format: Format, frames: List<&2, U32>) -> Clip: Clip{rate, channels, format, frames}# the sample rate, in hertzdef rate(c: Clip) -> U32: match c: case Clip{+hz, _nch, _fmt, _pcm}: hz# how many channels are interleaveddef channels(c: Clip) -> U32: match c: case Clip{_hz, +nch, _fmt, _pcm}: nch# s16 or f32def format(c: Clip) -> Format: match c: case Clip{_hz, _nch, +fmt, _pcm}: fmt# the interleaved framesdef frames(c: Clip) -> List<&2, U32>: match c: case Clip{_hz, _nch, _fmt, +pcm}: pcmdef count.of(+channels: U32, +nsamples: U32) -> U32: match channels: case 0: 0 case _: U32.div(nsamples, channels)# how many frames. zero channels count as none.def count(c: Clip) -> U32: match c: case Clip{_hz, +nch, _fmt, +pcm}: count.of(nch, U32.from_nat(List.length(&2, U32, pcm)))# one sample, repeated across nframes framesdef fill(+rate: U32, +channels: U32, format: Format, +sample: U32, +nframes: U32) -> Clip: Clip{rate, channels, format, List.replicate(U32, U32.to_nat((nframes * channels : U32)), sample)}def ls.drop(hop: Nat, xs: List<&2, U32>) -> List<&2, U32>: match hop: case 0n: xs case 1n+p: match xs: case Nil{}: [] case _hd <> tl: ls.drop(p, tl)def ls.take(hop: Nat, xs: List<&2, U32>, acc: List<&2, U32>) -> List<&2, U32>: match hop: case 0n: List.reverse(&2, U32, acc) case 1n+p: match xs: case Nil{}: List.reverse(&2, U32, acc) case +hd <> tl: ls.take(p, tl, hd <> acc)def ls.slice(xs: List<&2, U32>, +dropn: U32, +taken: U32) -> List<&2, U32>: ls.take(U32.to_nat(taken), ls.drop(U32.to_nat(dropn), xs), [])def trim.go( ok: Bool, +rate: U32, +channels: U32, format: Format, frames: List<&2, U32>, +start: U32, +nframes: U32) -> Maybe<&2, Clip>: match ok: case False{}: None{} case True{}: Some{Clip{rate, channels, format, ls.slice(frames, (start * channels : U32), (nframes * channels : U32))}}# frames [start, start + nframes). none when that span passes the end.def trim(c: Clip, +start: U32, +nframes: U32) -> Maybe<&2, Clip>: match c: case Clip{+hz, +nch, fmt, +pcm}: trim.go(U32.is_le((start + nframes : U32), count.of(nch, U32.from_nat(List.length(&2, U32, pcm)))), hz, nch, fmt, pcm, start, nframes)def eq.all(ar: Bool, ac: Bool, af: Bool) -> Bool: match ar: case False{}: False{} case True{}: match ac: case False{}: False{} case True{}: afdef concat.go( ok: Bool, +rate: U32, +channels: U32, format: Format, left: List<&2, U32>, right: List<&2, U32>) -> Maybe<&2, Clip>: match ok: case False{}: None{} case True{}: Some{Clip{rate, channels, format, List.append(&2, U32, left, right)}}def concat.pair(+rate: U32, +channels: U32, +format: Format, left: List<&2, U32>, other: Clip) -> Maybe<&2, Clip>: match other: case Clip{+rb, +cb, fb, right}: concat.go(eq.all(U32.is_eq(rate, rb), U32.is_eq(channels, cb), fmt.same(format, fb)), rate, channels, format, left, right)# the frames of both clips, or none when rate, channels, or format differdef concat(left: Clip, right: Clip) -> Maybe<&2, Clip>: match left: case Clip{+hz, +nch, +fmt, pcm}: concat.pair(hz, nch, fmt, pcm, right)def list.empty(xs: List<&2, U32>) -> Bool: match xs: case Nil{}: True{} case _hd <> _tl: False{}def wav.bytes(empty: Bool, +rate: U32, +channels: U32, +kind: U32, frames: List<&2, U32>) -> Maybe<&2, List<&2, U32>>: match empty: case True{}: None{} case False{}: Wav.wav.write(rate, channels, kind, frames)def wav.parsed(got: Maybe<&2, Wav.Wav>) -> Maybe<&2, Clip>: match got: case None{}: None{} case Some{w}: Some{wav.clip(w)}# a RIFF WAVE clip, or none when the bytes are not mono or stereo PCM or floatdef decode_wav(xs: List<&2, U32>) -> Maybe<&2, Clip>: wav.parsed(Wav.wav.parse(xs))# a RIFF WAVE file. none when there are no frames, or the rate or channels are unfit.def encode_wav(c: Clip) -> Maybe<&2, List<&2, U32>>: match c: case Clip{+hz, +nch, fmt, +pcm}: wav.bytes(list.empty(pcm), hz, nch, kind.of(fmt), pcm)def mp3.clip(p: Mp3.Pcm) -> Clip: match p: case Mp3.Pcm{+hz, +ch, pcm}: Clip{hz, ch, Flt{}, pcm}def mp3.parsed(got: Maybe<&2, Mp3.Pcm>) -> Maybe<&2, Clip>: match got: case None{}: None{} case Some{p}: Some{mp3.clip(p)}# MPEG-1 Layer III to f32 frames. Layer I, Layer II, joint stereo, and a CRC are none.def decode_mp3(xs: List<&2, U32>) -> Maybe<&2, Clip>: mp3.parsed(Mp3.mp3.decode(xs))# MPEG-1 Layer III at 320 kbit/s. rates are 32000, 44100, and 48000. none when unfit or empty.def encode_mp3(c: Clip) -> Maybe<&2, List<&2, U32>>: match c: case Clip{+hz, +nch, fmt, pcm}: Mp3.mp3.encode(hz, nch, kind.of(fmt), pcm)