1 # ezaudio: audio for Bend 2, reading and writing WAV (RIFF PCM) and MP3 (MPEG-1 Layer III). 2 # 3 # A Clip is a sample rate, a channel count, a sample format, and 4 # interleaved PCM frames. WAV reads and writes RIFF PCM. MP3 reads and writes 5 # MPEG-1 Layer III. 6 import Base 7 import ./wav.bend as Wav 8 import ./mp3.bend as Mp3 9 10 # signed 16-bit PCM, or IEEE 754 binary32 11 type Format is Data: 12 S16{} 13 Flt{} 14 15 # one clip. pcm is interleaved. s16 lives in the low 16 bits. 16 type Clip is Data: 17 Clip{hz: U32, nch: U32, fmt: Format, pcm: List<&2, U32>} 18 19 # signed 16-bit little-endian PCM 20 def s16() -> Format: 21 S16{} 22 23 # IEEE 754 binary32, stored as its bits 24 def f32() -> Format: 25 Flt{} 26 27 def kind.of(fmt: Format) -> U32: 28 match fmt: 29 case S16{}: 30 1 31 case Flt{}: 32 3 33 34 def fmt.of(+kind: U32) -> Format: 35 match kind: 36 case 1: 37 S16{} 38 case _: 39 Flt{} 40 41 def wav.clip(w: Wav.Wav) -> Clip: 42 match w: 43 case Wav.Wav{+rate, +channels, +kind, frames}: 44 Clip{rate, channels, fmt.of(kind), frames} 45 46 def fmt.same(aa: Format, bb: Format) -> Bool: 47 match aa bb: 48 case S16{} S16{}: 49 True{} 50 case Flt{} Flt{}: 51 True{} 52 case S16{} Flt{}: 53 False{} 54 case Flt{} S16{}: 55 False{} 56 57 # a clip from its rate, channel count, format, and interleaved frames 58 def clip(+rate: U32, +channels: U32, format: Format, frames: List<&2, U32>) -> Clip: 59 Clip{rate, channels, format, frames} 60 61 # the sample rate, in hertz 62 def rate(c: Clip) -> U32: 63 match c: 64 case Clip{+hz, _nch, _fmt, _pcm}: 65 hz 66 67 # how many channels are interleaved 68 def channels(c: Clip) -> U32: 69 match c: 70 case Clip{_hz, +nch, _fmt, _pcm}: 71 nch 72 73 # s16 or f32 74 def format(c: Clip) -> Format: 75 match c: 76 case Clip{_hz, _nch, +fmt, _pcm}: 77 fmt 78 79 # the interleaved frames 80 def frames(c: Clip) -> List<&2, U32>: 81 match c: 82 case Clip{_hz, _nch, _fmt, +pcm}: 83 pcm 84 85 def count.of(+channels: U32, +nsamples: U32) -> U32: 86 match channels: 87 case 0: 88 0 89 case _: 90 U32.div(nsamples, channels) 91 92 # how many frames. zero channels count as none. 93 def count(c: Clip) -> U32: 94 match c: 95 case Clip{_hz, +nch, _fmt, +pcm}: 96 count.of(nch, U32.from_nat(List.length(&2, U32, pcm))) 97 98 # one sample, repeated across nframes frames 99 def fill(+rate: U32, +channels: U32, format: Format, +sample: U32, +nframes: U32) -> Clip: 100 Clip{rate, channels, format, List.replicate(U32, U32.to_nat((nframes * channels : U32)), sample)} 101 102 def ls.drop(hop: Nat, xs: List<&2, U32>) -> List<&2, U32>: 103 match hop: 104 case 0n: 105 xs 106 case 1n+p: 107 match xs: 108 case Nil{}: 109 [] 110 case _hd <> tl: 111 ls.drop(p, tl) 112 113 def ls.take(hop: Nat, xs: List<&2, U32>, acc: List<&2, U32>) -> List<&2, U32>: 114 match hop: 115 case 0n: 116 List.reverse(&2, U32, acc) 117 case 1n+p: 118 match xs: 119 case Nil{}: 120 List.reverse(&2, U32, acc) 121 case +hd <> tl: 122 ls.take(p, tl, hd <> acc) 123 124 def ls.slice(xs: List<&2, U32>, +dropn: U32, +taken: U32) -> List<&2, U32>: 125 ls.take(U32.to_nat(taken), ls.drop(U32.to_nat(dropn), xs), []) 126 127 def trim.go( 128 ok: Bool, 129 +rate: U32, 130 +channels: U32, 131 format: Format, 132 frames: List<&2, U32>, 133 +start: U32, 134 +nframes: U32 135 ) -> Maybe<&2, Clip>: 136 match ok: 137 case False{}: 138 None{} 139 case True{}: 140 Some{Clip{rate, channels, format, 141 ls.slice(frames, (start * channels : U32), (nframes * channels : U32))}} 142 143 # frames [start, start + nframes). none when that span passes the end. 144 def trim(c: Clip, +start: U32, +nframes: U32) -> Maybe<&2, Clip>: 145 match c: 146 case Clip{+hz, +nch, fmt, +pcm}: 147 trim.go(U32.is_le((start + nframes : U32), count.of(nch, U32.from_nat(List.length(&2, U32, pcm)))), hz, nch, 148 fmt, pcm, start, nframes) 149 150 def eq.all(ar: Bool, ac: Bool, af: Bool) -> Bool: 151 match ar: 152 case False{}: 153 False{} 154 case True{}: 155 match ac: 156 case False{}: 157 False{} 158 case True{}: 159 af 160 161 def concat.go( 162 ok: Bool, 163 +rate: U32, 164 +channels: U32, 165 format: Format, 166 left: List<&2, U32>, 167 right: List<&2, U32> 168 ) -> Maybe<&2, Clip>: 169 match ok: 170 case False{}: 171 None{} 172 case True{}: 173 Some{Clip{rate, channels, format, List.append(&2, U32, left, right)}} 174 175 def concat.pair(+rate: U32, +channels: U32, +format: Format, left: List<&2, U32>, other: Clip) -> Maybe<&2, Clip>: 176 match other: 177 case Clip{+rb, +cb, fb, right}: 178 concat.go(eq.all(U32.is_eq(rate, rb), U32.is_eq(channels, cb), fmt.same(format, fb)), rate, channels, format, 179 left, right) 180 181 # the frames of both clips, or none when rate, channels, or format differ 182 def concat(left: Clip, right: Clip) -> Maybe<&2, Clip>: 183 match left: 184 case Clip{+hz, +nch, +fmt, pcm}: 185 concat.pair(hz, nch, fmt, pcm, right) 186 187 def list.empty(xs: List<&2, U32>) -> Bool: 188 match xs: 189 case Nil{}: 190 True{} 191 case _hd <> _tl: 192 False{} 193 194 def wav.bytes(empty: Bool, +rate: U32, +channels: U32, +kind: U32, frames: List<&2, U32>) -> Maybe<&2, List<&2, U32>>: 195 match empty: 196 case True{}: 197 None{} 198 case False{}: 199 Wav.wav.write(rate, channels, kind, frames) 200 201 def wav.parsed(got: Maybe<&2, Wav.Wav>) -> Maybe<&2, Clip>: 202 match got: 203 case None{}: 204 None{} 205 case Some{w}: 206 Some{wav.clip(w)} 207 208 # a RIFF WAVE clip, or none when the bytes are not mono or stereo PCM or float 209 def decode_wav(xs: List<&2, U32>) -> Maybe<&2, Clip>: 210 wav.parsed(Wav.wav.parse(xs)) 211 212 # a RIFF WAVE file. none when there are no frames, or the rate or channels are unfit. 213 def encode_wav(c: Clip) -> Maybe<&2, List<&2, U32>>: 214 match c: 215 case Clip{+hz, +nch, fmt, +pcm}: 216 wav.bytes(list.empty(pcm), hz, nch, kind.of(fmt), pcm) 217 218 def mp3.clip(p: Mp3.Pcm) -> Clip: 219 match p: 220 case Mp3.Pcm{+hz, +ch, pcm}: 221 Clip{hz, ch, Flt{}, pcm} 222 223 def mp3.parsed(got: Maybe<&2, Mp3.Pcm>) -> Maybe<&2, Clip>: 224 match got: 225 case None{}: 226 None{} 227 case Some{p}: 228 Some{mp3.clip(p)} 229 230 # MPEG-1 Layer III to f32 frames. Layer I, Layer II, joint stereo, and a CRC are none. 231 def decode_mp3(xs: List<&2, U32>) -> Maybe<&2, Clip>: 232 mp3.parsed(Mp3.mp3.decode(xs)) 233 234 # MPEG-1 Layer III at 320 kbit/s. rates are 32000, 44100, and 48000. none when unfit or empty. 235 def encode_mp3(c: Clip) -> Maybe<&2, List<&2, U32>>: 236 match c: 237 case Clip{+hz, +nch, fmt, pcm}: 238 Mp3.mp3.encode(hz, nch, kind.of(fmt), pcm)