1use std::path::PathBuf;
13use std::sync::Arc;
14use std::time::{Duration, Instant};
15
16pub const RATE: u32 = 44_100;
18pub const SLOTS: usize = 256;
19pub const CHANNELS: usize = 16;
20pub const FIRST_APC_CHANNEL: usize = 2;
23pub const BASE_DB: f32 = -12.0;
25const MAX_FRAMES: usize = RATE as usize * 600;
27
28pub type Pcm = Arc<[i16]>;
30
31#[derive(Clone)]
35pub struct Sound(Arc<SoundData>);
36
37struct SoundData {
38 frames: usize,
39 pcm: std::sync::OnceLock<Pcm>,
40}
41
42impl Sound {
43 pub fn ready(pcm: Pcm) -> Sound {
44 let data = SoundData { frames: pcm.len() / 2, pcm: std::sync::OnceLock::new() };
45 let _ = data.pcm.set(pcm);
46 Sound(Arc::new(data))
47 }
48
49 fn decoded_later(frames: usize, decode: impl FnOnce() -> Option<Vec<i16>> + Send + 'static) -> Sound {
52 let sound = Sound(Arc::new(SoundData { frames, pcm: std::sync::OnceLock::new() }));
53 let fill = sound.0.clone();
54 let spawned = std::thread::Builder::new().name("decode".into()).spawn(move || {
55 let _ = fill.pcm.set(decode().unwrap_or_default().into());
56 });
57 if let Err(e) = spawned {
58 log::warn!("decoding a sound: {e}");
59 let _ = sound.0.pcm.set(Vec::new().into());
60 }
61 sound
62 }
63
64 pub fn frames(&self) -> usize {
66 self.pcm().map_or(self.0.frames, |p| p.len() / 2)
67 }
68
69 pub fn pcm(&self) -> Option<&Pcm> {
71 self.0.pcm.get()
72 }
73
74 pub fn wait(&self) -> &Pcm {
76 loop {
77 if let Some(p) = self.pcm() {
78 return p;
79 }
80 std::thread::sleep(Duration::from_millis(5));
81 }
82 }
83}
84
85impl PartialEq for Sound {
86 fn eq(&self, other: &Sound) -> bool {
87 Arc::ptr_eq(&self.0, &other.0)
88 }
89}
90
91impl std::fmt::Debug for Sound {
92 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
93 write!(f, "Sound({} frames{})", self.0.frames, if self.pcm().is_some() { "" } else { ", decoding" })
94 }
95}
96
97#[derive(Clone, Debug, PartialEq)]
99pub enum AudioOp {
100 Queue {
104 channel: usize,
105 sound: Sound,
106 fade_in: usize,
107 fade_out: usize,
108 crossfade: bool,
109 looped: bool,
110 volume: (f32, f32),
111 },
112 Flush { channel: usize, fade_out: usize },
114 Volume { channel: usize, left: Option<f32>, right: Option<f32>, ramp: usize },
116}
117
118#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
120enum Busy {
121 #[default]
122 Idle,
123 Until(Instant),
124 Looping,
125}
126
127pub struct Audio {
128 slots: Vec<Option<Sound>>,
129 busy: [Busy; CHANNELS],
130 notify: [bool; CHANNELS],
132}
133
134impl Default for Audio {
135 fn default() -> Audio {
136 Audio { slots: vec![None; SLOTS], busy: [Busy::Idle; CHANNELS], notify: [false; CHANNELS] }
137 }
138}
139
140fn duration(s: &str, hz: Option<f64>) -> usize {
143 let digits = s.bytes().take_while(u8::is_ascii_digit).count();
144 let Ok(n) = s[..digits].parse::<u64>() else { return 0 };
145 match &s[digits..] {
146 "" | "ms" => (n * RATE as u64 / 1000) as usize,
147 "f" => n as usize,
148 "p" => hz.filter(|&f| f > 0.0).map_or(0, |f| (n as f64 * RATE as f64 / f + 0.5) as usize),
149 _ => 0,
150 }
151}
152
153fn volume(s: &str) -> f32 {
155 let lower = s.to_ascii_lowercase();
156 if let Some(db) = lower.strip_suffix("db") {
157 return db.trim().parse().unwrap_or(0.0);
158 }
159 match s.trim().parse::<f32>() {
160 Ok(v) if v <= 0.0 => -60.0,
161 Ok(v) => 20.0 * (v.min(100.0) / 100.0).log10(),
162 Err(_) => 0.0,
163 }
164}
165
166fn field<'a>(fields: &[&'a str], key: &str) -> Option<&'a str> {
168 fields.iter().find_map(|f| f.strip_prefix(key)?.strip_prefix('='))
169}
170
171fn number(fields: &[&str], key: &str) -> Option<usize> {
172 field(fields, key)?.trim().parse().ok()
173}
174
175impl Audio {
176 pub fn apc(&mut self, fields: &[&str], cache: Option<&PathBuf>, ops: &mut Vec<AudioOp>) -> Option<Instant> {
179 let (&verb, rest) = fields.split_first()?;
180 let slot = number(rest, "S").filter(|&s| s < SLOTS);
181 let channel = number(rest, "C").filter(|&c| c < CHANNELS);
182 match verb {
183 "Load" | "LoadBlob" => {
184 let data = rest.iter().rfind(|f| !f.starts_with("S="))?;
186 let bytes = if verb == "Load" {
187 let name = crate::syncterm::safe_name(data)?;
188 std::fs::read(cache?.join(name)).ok()?
189 } else {
190 crate::syncterm::decode_b64(data)?
191 };
192 match decode(&bytes) {
193 Some(sound) => self.slots[slot?] = Some(sound),
194 None => log::info!("audio {verb}: can't decode it"),
195 }
196 }
197 "Synth" => {
198 let shape = field(rest, "W").and_then(Shape::parse)?;
199 let hz: f64 = field(rest, "F").and_then(|f| f.trim().parse().ok()).unwrap_or(0.0);
200 let frames = duration(field(rest, "T")?, Some(hz)).min(MAX_FRAMES);
201 if frames > 0 {
202 self.slots[slot?] = Some(Sound::ready(synth(shape, hz, frames).into()));
203 }
204 }
205 "Copy" => {
206 let to = number(rest, "D").filter(|&d| d < SLOTS)?;
207 self.slots[to] = self.slots[slot?].clone();
208 }
209 "Queue" => {
210 let channel = channel.filter(|&c| c >= FIRST_APC_CHANNEL)?;
211 let sound = self.slots[slot?].take().filter(|s| s.frames() > 0)?;
212 let both = field(rest, "V").map(volume).unwrap_or(0.0);
213 let left = field(rest, "VL").map(volume).unwrap_or(both);
214 let right = field(rest, "VR").map(volume).unwrap_or(both);
215 let fade_in = field(rest, "I").map_or(0, |d| duration(d, None));
216 let crossfade = rest.contains(&"X");
217 let looped = rest.contains(&"L");
218 let now = Instant::now();
219 let length = Duration::from_secs_f64(sound.frames() as f64 / RATE as f64);
220 self.busy[channel] = match (looped, self.busy[channel]) {
221 (true, _) => Busy::Looping,
222 (false, Busy::Until(t)) if !crossfade && t > now => Busy::Until(t + length),
223 _ => Busy::Until(now + length),
224 };
225 ops.push(AudioOp::Queue {
226 channel,
227 sound,
228 fade_in,
229 fade_out: field(rest, "O").map_or(0, |d| duration(d, None)),
230 crossfade,
231 looped,
232 volume: (left, right),
233 });
234 }
235 "Flush" => {
236 let channel = channel?;
237 let fade_out = field(rest, "O").map_or(0, |d| duration(d, None));
238 self.busy[channel] = match fade_out {
239 0 => Busy::Idle,
240 f => Busy::Until(Instant::now() + Duration::from_secs_f64(f as f64 / RATE as f64)),
241 };
242 ops.push(AudioOp::Flush { channel, fade_out });
243 }
244 "Volume" => {
245 let channel = channel?;
246 let both = field(rest, "V").map(volume);
247 let left = field(rest, "VL").map(volume).or(both);
248 let right = field(rest, "VR").map(volume).or(both);
249 if left.is_some() || right.is_some() {
250 let ramp = field(rest, "T").map_or(0, |d| duration(d, None));
251 ops.push(AudioOp::Volume { channel, left, right, ramp });
252 }
253 }
254 "Wait" => {
255 if let Busy::Until(t) = self.busy[channel?] {
256 return Some(t);
257 }
258 }
259 "Update" => self.notify[channel?] = true,
260 _ => log::debug!("unhandled audio APC {verb}"),
261 }
262 None
263 }
264
265 pub fn music(&mut self, length: Duration) {
267 let now = Instant::now();
268 self.busy[0] = match self.busy[0] {
269 Busy::Until(t) if t > now => Busy::Until(t + length),
270 _ => Busy::Until(now + length),
271 };
272 }
273
274 fn playing(&self, channel: usize, now: Instant) -> bool {
275 match self.busy[channel] {
276 Busy::Idle => false,
277 Busy::Until(t) => t > now,
278 Busy::Looping => true,
279 }
280 }
281
282 pub fn report(&self, channel: Option<usize>) -> String {
284 let now = Instant::now();
285 match channel {
286 Some(c) => format!("\x1b[=7;{c};{}n", (c < CHANNELS && self.playing(c, now)) as u8),
287 None => {
288 let pairs: String =
289 (0..CHANNELS).filter(|&c| self.playing(c, now)).map(|c| format!(";{c};1")).collect();
290 format!("\x1b[=7{pairs}n")
291 }
292 }
293 }
294
295 pub fn poll(&mut self, now: Instant, replies: &mut Vec<u8>) {
298 for c in 0..CHANNELS {
299 if self.notify[c] && !self.playing(c, now) {
300 self.notify[c] = false;
301 replies.extend_from_slice(format!("\x1b[=7;{c};0n").as_bytes());
302 }
303 }
304 }
305
306 pub fn next_due(&self) -> Option<Instant> {
308 (0..CHANNELS)
309 .filter(|&c| self.notify[c])
310 .filter_map(|c| match self.busy[c] {
311 Busy::Until(t) => Some(t),
312 _ => None,
313 })
314 .min()
315 }
316}
317
318pub fn format_supported(pm: u32, ps: u32) -> bool {
321 const WAV: u32 = 1;
322 const AIFF: u32 = 2;
323 const VOC: u32 = 8;
324 const WAVEX: u32 = 19;
325 const FLAC: u32 = 23;
326 const OGG: u32 = 32;
327 const VORBIS: u32 = 96;
328 const OPUS: u32 = 100;
329 match pm {
330 WAV | WAVEX => matches!(ps, 2..=7),
331 AIFF => matches!(ps, 1..=4 | 6),
332 VOC => matches!(ps, 2 | 5),
333 FLAC => matches!(ps, 1..=3),
334 OGG => matches!(ps, VORBIS | OPUS),
335 _ => false,
336 }
337}
338
339pub fn parse_id(s: &str) -> u32 {
341 let s = s.trim();
342 match s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
343 Some(h) => u32::from_str_radix(h, 16).unwrap_or(0),
344 None => s.parse().unwrap_or(0),
345 }
346}
347
348#[derive(Clone, Copy, Debug, PartialEq, Eq)]
350enum Shape {
351 Sine,
352 Saw,
353 Square,
354 SineHarm,
355 SineSaw,
356 SineSawChord,
357 SineSawHarm,
358 Silence,
359}
360
361impl Shape {
362 fn parse(s: &str) -> Option<Shape> {
363 Some(match s.trim() {
364 "SIN" => Shape::Sine,
365 "SAW" => Shape::Saw,
366 "SQ" => Shape::Square,
367 "SINE_HARM" => Shape::SineHarm,
368 "SINE_SAW" => Shape::SineSaw,
369 "SINE_SAW_CHORD" => Shape::SineSawChord,
370 "SINE_SAW_HARM" => Shape::SineSawHarm,
371 "SILENCE" => Shape::Silence,
372 _ => return None,
373 })
374 }
375}
376
377fn synth(shape: Shape, hz: f64, frames: usize) -> Vec<i16> {
382 if hz <= 0.0 || shape == Shape::Silence {
383 return vec![0; frames * 2];
384 }
385 use std::f64::consts::{PI, TAU};
386 let inc = TAU * hz / RATE as f64;
387 let u8c = |v: f64| (v as i64) as u8;
389 let mut mono: Vec<i16> = (0..frames)
390 .map(|i| {
391 let i = i as f64;
392 let pos = (inc * i) % TAU;
393 let noisy = |u: u8| (((u as i32) - 128) << 8) as i16;
394 match shape {
395 Shape::Sine => (pos.sin() * 32767.0) as i16,
396 Shape::Saw => (32767.0 - (pos / TAU) * 65535.0) as i16,
397 Shape::Square => {
398 if pos < PI {
399 32767
400 } else {
401 -32768
402 }
403 }
404 Shape::SineHarm => noisy(u8c((inc * 3.0 * i).sin() * 16.0)),
405 Shape::SineSaw => noisy(u8c(((pos.sin() * 127.0 + 128.0) + ((TAU - pos) * 40.5)) / 2.0)),
406 Shape::SineSawChord | Shape::SineSawHarm => {
407 let (a, b) = if shape == Shape::SineSawChord { (0.5, 1.0 / 3.0) } else { (2.0, 3.0) };
408 let mut u = u8c(((pos.sin() * 64.0 + 128.0) + ((TAU - pos) * 6.2)) / 2.0);
409 u = u8c(u as f64 + (inc * a * i).sin() * 24.0);
410 u = u8c(u as f64 + (inc * b * i).sin() * 16.0);
411 noisy(u)
412 }
413 Shape::Silence => 0,
414 }
415 })
416 .collect();
417 soften(&mut mono, hz);
418 mono.iter().flat_map(|&v| [v, v]).collect()
419}
420
421fn soften(mono: &mut [i16], hz: f64) {
424 let n = mono.len();
425 if n < 4 {
426 return;
427 }
428 let mid = n / 2;
429 let end_high = mono[n - 1] > 0;
430 if mono[n - 1] != 0 {
431 let mut i = n - 1;
432 while i > mid {
433 if end_high && mono[i] < 0 || !end_high && mono[i] > 0 {
434 break;
435 }
436 mono[i] = 0;
437 i -= 1;
438 }
439 }
440 let crossings_wanted = (hz / 100.0) as i32;
441 if crossings_wanted == 0 {
442 return;
443 }
444 let mut crossings = 0;
445 for i in 0..n - 1 {
446 if ((mono[i] < 0 && mono[i + 1] >= 0) || (mono[i] > 0 && mono[i + 1] <= 0)) && i > 2 {
447 crossings += 1;
448 if crossings >= crossings_wanted {
449 break;
450 }
451 }
452 let d = crossings_wanted - crossings;
453 mono[i] = (mono[i] as i32 / (d * d)) as i16;
454 }
455 crossings = 0;
456 for i in (1..n).rev() {
457 if ((mono[i] < 0 && mono[i - 1] >= 0) || (mono[i] > 0 && mono[i - 1] <= 0)) && i > 2 {
458 crossings += 1;
459 if crossings >= crossings_wanted {
460 break;
461 }
462 }
463 let d = crossings_wanted - crossings;
464 mono[i] = (mono[i] as i32 / (d * d)) as i16;
465 }
466}
467
468pub fn decode(data: &[u8]) -> Option<Sound> {
471 let (rate, channels, samples) = if data.starts_with(b"RIFF") && data.get(8..12) == Some(b"WAVE") {
472 decode_wav(data)?
473 } else if data.starts_with(b"FORM") && matches!(data.get(8..12), Some(b"AIFF" | b"AIFC")) {
474 decode_aiff(data)?
475 } else if data.starts_with(b"Creative Voice File\x1a") {
476 decode_voc(data)?
477 } else if data.starts_with(b"OggS") || data.starts_with(b"fLaC") {
478 let (frames, decode) = later(data)?;
479 let frames = frames.min(MAX_FRAMES);
480 return Some(Sound::decoded_later(frames, move || {
481 let (rate, channels, samples) = decode()?;
482 Some(to_stereo(rate, channels, &samples))
483 }));
484 } else {
485 return None;
486 };
487 Some(Sound::ready(to_stereo(rate, channels, &samples).into()))
488}
489
490type Decoded = (u32, usize, Vec<f32>);
492
493#[allow(clippy::type_complexity)]
496fn later(data: &[u8]) -> Option<(usize, Box<dyn FnOnce() -> Option<Decoded> + Send>)> {
497 let at_rate = |n: u64, rate: u32| (n as f64 * RATE as f64 / rate.max(1) as f64) as usize;
498 if data.starts_with(b"fLaC") {
499 let reader = claxon::FlacReader::new(std::io::Cursor::new(data.to_vec())).ok()?;
500 let info = reader.streaminfo();
501 let frames = at_rate(info.samples.unwrap_or(0), info.sample_rate);
502 return Some((frames, Box::new(move || decode_flac(reader))));
503 }
504 let mut reader = ogg::PacketReader::new(std::io::Cursor::new(data));
507 let head = reader.read_packet().ok()??.data;
508 let mut last = 0;
509 while let Ok(Some(p)) = reader.read_packet() {
510 last = p.absgp_page();
511 }
512 let owned = data.to_vec();
513 if head.starts_with(b"OpusHead") {
514 let pre_skip = u16le(&head, 10)? as u64;
515 Some((at_rate(last.saturating_sub(pre_skip), 48_000), Box::new(move || decode_opus(&owned))))
516 } else if head.starts_with(b"\x01vorbis") {
517 let rate = u32le(&head, 12)?;
518 Some((at_rate(last, rate), Box::new(move || decode_vorbis(owned))))
519 } else {
520 None
521 }
522}
523
524fn decode_flac(mut reader: claxon::FlacReader<std::io::Cursor<Vec<u8>>>) -> Option<Decoded> {
525 let info = reader.streaminfo();
526 let scale = 1.0 / (1u64 << (info.bits_per_sample.clamp(1, 32) - 1)) as f32;
527 let mut samples = Vec::new();
528 for s in reader.samples() {
529 samples.push(s.ok()? as f32 * scale);
530 if samples.len() > MAX_FRAMES * 2 * info.channels as usize / 2 {
531 break;
532 }
533 }
534 Some((info.sample_rate, info.channels as usize, samples))
535}
536
537fn decode_vorbis(data: Vec<u8>) -> Option<Decoded> {
538 let mut reader = lewton::inside_ogg::OggStreamReader::new(std::io::Cursor::new(data)).ok()?;
539 let (rate, channels) = (reader.ident_hdr.audio_sample_rate, reader.ident_hdr.audio_channels as usize);
540 let mut samples = Vec::new();
541 while let Ok(Some(packet)) = reader.read_dec_packet_itl() {
542 samples.extend(packet.iter().map(|&s| s as f32 / 32768.0));
543 if samples.len() > MAX_FRAMES * channels.max(1) {
544 break;
545 }
546 }
547 Some((rate, channels, samples))
548}
549
550fn to_stereo(rate: u32, channels: usize, samples: &[f32]) -> Vec<i16> {
553 let channels = channels.max(1);
554 let frames = samples.len() / channels;
555 let frame = |i: usize| -> (f32, f32) {
556 let f = &samples[i.min(frames - 1) * channels..][..channels];
557 (f[0], if channels > 1 { f[1] } else { f[0] })
558 };
559 if frames == 0 || rate == 0 {
560 return Vec::new();
561 }
562 let out_frames = ((frames as u64 * RATE as u64 / rate as u64) as usize).min(MAX_FRAMES);
563 let step = rate as f64 / RATE as f64;
564 let mut out = Vec::with_capacity(out_frames * 2);
565 for i in 0..out_frames {
566 let pos = i as f64 * step;
567 let (j, t) = (pos as usize, (pos.fract()) as f32);
568 let ((l0, r0), (l1, r1)) = (frame(j), frame(j + 1));
569 let s = |a: f32, b: f32| ((a + (b - a) * t) * 32768.0).round().clamp(-32768.0, 32767.0) as i16;
570 out.push(s(l0, l1));
571 out.push(s(r0, r1));
572 }
573 out
574}
575
576fn u16le(d: &[u8], at: usize) -> Option<u16> {
577 Some(u16::from_le_bytes(d.get(at..at + 2)?.try_into().ok()?))
578}
579
580fn u32le(d: &[u8], at: usize) -> Option<u32> {
581 Some(u32::from_le_bytes(d.get(at..at + 4)?.try_into().ok()?))
582}
583
584fn u16be(d: &[u8], at: usize) -> Option<u16> {
585 Some(u16::from_be_bytes(d.get(at..at + 2)?.try_into().ok()?))
586}
587
588fn u32be(d: &[u8], at: usize) -> Option<u32> {
589 Some(u32::from_be_bytes(d.get(at..at + 4)?.try_into().ok()?))
590}
591
592fn pcm_samples(data: &[u8], bits: u16, float: bool, big_endian: bool, unsigned8: bool) -> Option<Vec<f32>> {
595 let bytes = bits as usize / 8;
596 if bytes == 0 || bytes > 8 {
597 return None;
598 }
599 let limit = MAX_FRAMES * 2 * bytes;
600 let data = &data[..data.len().min(limit) / bytes * bytes];
601 Some(
602 data.chunks_exact(bytes)
603 .map(|c| {
604 let mut b = [0u8; 8];
605 if big_endian {
606 for (i, &v) in c.iter().rev().enumerate() {
607 b[i] = v;
608 }
609 } else {
610 b[..bytes].copy_from_slice(c);
611 }
612 match (float, bytes) {
613 (true, 4) => f32::from_le_bytes([b[0], b[1], b[2], b[3]]),
614 (true, 8) => f64::from_le_bytes(b) as f32,
615 (_, 1) if unsigned8 => (b[0] as f32 - 128.0) / 128.0,
616 (_, 1) => b[0] as i8 as f32 / 128.0,
617 (_, 2) => i16::from_le_bytes([b[0], b[1]]) as f32 / 32768.0,
618 (_, 3) => (i32::from_le_bytes([0, b[0], b[1], b[2]]) >> 8) as f32 / 8_388_608.0,
619 (_, 4) => i32::from_le_bytes([b[0], b[1], b[2], b[3]]) as f32 / 2_147_483_648.0,
620 _ => 0.0,
621 }
622 })
623 .collect(),
624 )
625}
626
627fn decode_wav(d: &[u8]) -> Option<(u32, usize, Vec<f32>)> {
628 let (mut at, mut fmt, mut data) = (12, None, None);
629 while at + 8 <= d.len() {
630 let size = u32le(d, at + 4)? as usize;
631 let body = d.get(at + 8..(at + 8 + size).min(d.len()))?;
632 match &d[at..at + 4] {
633 b"fmt " => fmt = Some(body),
634 b"data" => data = Some(body),
635 _ => {}
636 }
637 at += 8 + size + size % 2;
638 }
639 let (fmt, data) = (fmt?, data?);
640 let mut tag = u16le(fmt, 0)?;
641 let channels = u16le(fmt, 2)? as usize;
642 let rate = u32le(fmt, 4)?;
643 let bits = u16le(fmt, 14)?;
644 if tag == 0xFFFE {
645 tag = u16le(fmt, 24)?;
647 }
648 let float = match tag {
649 1 => false,
650 3 => true,
651 _ => return None,
652 };
653 Some((rate, channels, pcm_samples(data, bits, float, false, true)?))
654}
655
656fn extended(b: &[u8]) -> Option<f64> {
658 let exp = (u16be(b, 0)? & 0x7FFF) as i32;
659 let mant = u64::from_be_bytes(b.get(2..10)?.try_into().ok()?);
660 Some(mant as f64 * 2f64.powi(exp - 16383 - 63))
661}
662
663fn decode_aiff(d: &[u8]) -> Option<(u32, usize, Vec<f32>)> {
664 let aifc = &d[8..12] == b"AIFC";
665 let (mut at, mut comm, mut ssnd) = (12, None, None);
666 while at + 8 <= d.len() {
667 let size = u32be(d, at + 4)? as usize;
668 let body = d.get(at + 8..(at + 8 + size).min(d.len()))?;
669 match &d[at..at + 4] {
670 b"COMM" => comm = Some(body),
671 b"SSND" => ssnd = Some(body),
672 _ => {}
673 }
674 at += 8 + size + size % 2;
675 }
676 let (comm, ssnd) = (comm?, ssnd?);
677 let channels = u16be(comm, 0)? as usize;
678 let bits = u16be(comm, 6)?;
679 let rate = extended(comm.get(8..18)?)? as u32;
680 let (float, big) = match comm.get(18..22).filter(|_| aifc) {
681 None | Some(b"NONE") | Some(b"twos") => (false, true),
682 Some(b"sowt") => (false, false),
683 Some(b"fl32") | Some(b"FL32") => (true, true),
684 _ => return None,
685 };
686 let offset = u32be(ssnd, 0)? as usize;
687 let data = ssnd.get(8 + offset..)?;
688 let bits = if float { 32 } else { bits.div_ceil(8) * 8 };
689 Some((rate, channels, pcm_samples(data, bits, float, big, false)?))
690}
691
692fn decode_voc(d: &[u8]) -> Option<(u32, usize, Vec<f32>)> {
693 let mut at = u16le(d, 20)? as usize;
694 let (mut rate, mut channels, mut bits) = (0u32, 1usize, 8u16);
695 let mut samples = Vec::new();
696 while let Some(&kind) = d.get(at) {
697 if kind == 0 {
698 break;
699 }
700 let size = u32le(d, at)? as usize >> 8;
701 let body = d.get(at + 4..(at + 4 + size).min(d.len()))?;
702 at += 4 + size;
703 let pcm = match kind {
704 1 => {
706 if rate == 0 {
707 rate = 1_000_000 / (256 - *body.first()? as u32);
708 }
709 (*body.get(1)? == 0).then(|| body.get(2..)).flatten()
710 }
711 2 => Some(body),
713 9 => {
715 rate = u32le(body, 0)?;
716 bits = *body.get(4)? as u16;
717 channels = *body.get(5)? as usize;
718 matches!(u16le(body, 6)?, 0 | 4).then(|| body.get(12..)).flatten()
719 }
720 _ => None,
721 };
722 if let Some(p) = pcm {
723 samples.extend(pcm_samples(p, bits, false, false, true)?);
724 if samples.len() > MAX_FRAMES * 2 {
725 break;
726 }
727 }
728 }
729 (rate > 0).then_some((rate, channels, samples))
730}
731
732fn decode_opus(d: &[u8]) -> Option<(u32, usize, Vec<f32>)> {
734 let mut reader = ogg::PacketReader::new(std::io::Cursor::new(d));
735 let head = reader.read_packet().ok()??;
736 if !head.data.starts_with(b"OpusHead") || head.data.get(18).copied().unwrap_or(0) != 0 {
737 return None;
739 }
740 let pre_skip = u16le(&head.data, 10)? as usize;
741 let gain = i16::from_le_bytes(head.data.get(16..18)?.try_into().ok()?);
742 let gain = 10f32.powf(gain as f32 / 256.0 / 20.0);
743 let mut decoder = rusty_opus::OpusDecoder::new(48_000, 2).ok()?;
744 let mut buf = vec![0f32; 5760 * 2];
745 let mut samples = Vec::new();
746 let mut skipped = 0;
747 while let Ok(Some(packet)) = reader.read_packet() {
748 if packet.data.starts_with(b"OpusTags") {
749 continue;
750 }
751 let Ok(n) = decoder.decode(&packet.data, 5760, &mut buf) else { continue };
752 let mut frames = &buf[..n * 2];
753 if skipped < pre_skip {
754 let skip = (pre_skip - skipped).min(n);
755 skipped += skip;
756 frames = &frames[skip * 2..];
757 }
758 samples.extend(frames.iter().map(|s| s * gain));
759 if samples.len() > MAX_FRAMES * 2 {
760 break;
761 }
762 }
763 Some((48_000, 2, samples))
764}
765
766#[cfg(test)]
767mod tests {
768 use super::*;
769
770 fn wav(rate: u32, channels: u16, bits: u16, data: &[u8]) -> Vec<u8> {
771 let mut f = b"RIFF\0\0\0\0WAVEfmt ".to_vec();
772 f.extend(16u32.to_le_bytes());
773 f.extend(1u16.to_le_bytes());
774 f.extend(channels.to_le_bytes());
775 f.extend(rate.to_le_bytes());
776 f.extend((rate * channels as u32 * bits as u32 / 8).to_le_bytes());
777 f.extend((channels * bits / 8).to_le_bytes());
778 f.extend(bits.to_le_bytes());
779 f.extend(b"data");
780 f.extend((data.len() as u32).to_le_bytes());
781 f.extend(data);
782 f
783 }
784
785 #[test]
786 fn durations_and_volumes() {
787 assert_eq!(duration("500", None), 22050);
788 assert_eq!(duration("500ms", None), 22050);
789 assert_eq!(duration("100f", None), 100);
790 assert_eq!(duration("2p", Some(441.0)), 200);
791 assert_eq!(duration("2p", None), 0);
792 assert_eq!(duration("x", None), 0);
793 assert_eq!(volume("-6.5dB"), -6.5);
794 assert_eq!(volume("100"), 0.0);
795 assert_eq!(volume("0"), -60.0);
796 assert!((volume("50") + 6.02).abs() < 0.01);
797 }
798
799 #[test]
800 fn files_decode_to_stereo_at_44k() {
801 let pcm = decode(&wav(22050, 1, 8, &[128, 255, 0, 128])).unwrap().wait().clone();
803 assert_eq!(pcm.len(), 8 * 2);
804 assert_eq!(pcm[0], 0);
805 assert_eq!(pcm[2], pcm[3], "mono goes to both sides");
806 assert!((16000..16500).contains(&pcm[2]), "{}", pcm[2]);
808 assert!(pcm[4] > 32000);
809 let pcm = decode(&wav(44100, 2, 16, &[0, 0x40, 0, 0xC0])).unwrap().wait().clone();
811 assert_eq!(&pcm[..], &[16384, -16384]);
812 let mut voc = b"Creative Voice File\x1a\x1a\x00\x0a\x01\x29\x11".to_vec();
814 voc.extend([1, 6, 0, 0, 233, 0, 128, 255, 128, 0, 0]);
815 let pcm = decode(&voc).unwrap().wait().clone();
816 assert!(pcm.len() >= 8 && pcm.iter().any(|&s| s > 30000));
817 let mut comm = 1u16.to_be_bytes().to_vec();
819 comm.extend(2u32.to_be_bytes());
820 comm.extend(16u16.to_be_bytes());
821 comm.extend([0x40, 0x0E, 0xAC, 0x44, 0, 0, 0, 0, 0, 0]);
822 let mut aiff = b"FORM\0\0\0\0AIFFCOMM".to_vec();
823 aiff.extend((comm.len() as u32).to_be_bytes());
824 aiff.extend(&comm);
825 aiff.extend(b"SSND");
826 aiff.extend(12u32.to_be_bytes());
827 aiff.extend([0u8; 8]);
828 aiff.extend([0x40, 0x00, 0xC0, 0x00]);
829 assert_eq!(&decode(&aiff).unwrap().wait()[..], &[16384, 16384, -16384, -16384]);
830 assert!(decode(b"not a sound").is_none());
831 }
832
833 #[test]
834 fn slots_channels_and_reports() {
835 let mut a = Audio::default();
836 let mut ops = Vec::new();
837 let apc = |a: &mut Audio, ops: &mut Vec<AudioOp>, s: &str| {
838 let fields: Vec<&str> = s.split(';').collect();
839 a.apc(&fields, None, ops)
840 };
841 apc(&mut a, &mut ops, "Synth;S=3;W=SQ;F=441;T=10p");
842 apc(&mut a, &mut ops, "Copy;S=3;D=4");
843 apc(&mut a, &mut ops, "Queue;C=1;S=3");
845 assert!(ops.is_empty());
846 apc(&mut a, &mut ops, "Queue;C=2;S=3;V=50;I=5;L");
847 let Some(AudioOp::Queue { channel: 2, sound, looped: true, fade_in: 220, volume, .. }) = ops.first() else {
848 panic!("{ops:?}")
849 };
850 assert_eq!(sound.frames(), 1000);
851 assert!((volume.0 + 6.02).abs() < 0.01);
852 apc(&mut a, &mut ops, "Queue;C=3;S=3");
854 assert_eq!(ops.len(), 1);
855 apc(&mut a, &mut ops, "Queue;C=3;S=4");
856 assert_eq!(a.report(None), "\x1b[=7;2;1;3;1n");
857 assert_eq!(a.report(Some(5)), "\x1b[=7;5;0n");
858 let until = apc(&mut a, &mut ops, "Wait;C=3").expect("a hold");
860 assert!(until > Instant::now() && until < Instant::now() + Duration::from_millis(100));
861 apc(&mut a, &mut ops, "Update;C=3");
863 let mut replies = Vec::new();
864 a.poll(Instant::now(), &mut replies);
865 assert!(replies.is_empty());
866 a.poll(Instant::now() + Duration::from_secs(1), &mut replies);
867 assert_eq!(replies, b"\x1b[=7;3;0n");
868 apc(&mut a, &mut ops, "Flush;C=2");
869 assert_eq!(a.report(Some(2)), "\x1b[=7;2;0n");
870 assert_eq!(ops.last(), Some(&AudioOp::Flush { channel: 2, fade_out: 0 }));
871 }
872
873 #[test]
874 fn tones_match_synctermss_shapes() {
875 let sq = synth(Shape::Square, 441.0, 1000);
876 assert_eq!(sq[425 * 2], 32767);
878 assert_eq!(sq[475 * 2], -32768);
879 assert!(sq[0].abs() < 32767 / 4);
880 assert_eq!(synth(Shape::Silence, 441.0, 10), vec![0; 20]);
881 let noisy = synth(Shape::SineSawHarm, 441.0, 1000);
882 assert!(noisy.iter().any(|&v| v != 0));
883 assert!(
884 format_supported(32, 100)
885 && format_supported(1, 2)
886 && format_supported(23, 2)
887 && format_supported(32, 96)
888 && !format_supported(35, 130)
889 );
890 assert_eq!(parse_id("0x20"), 32);
891 }
892
893 #[test]
895 #[ignore]
896 fn sound_file_decodes() {
897 let data = std::fs::read(std::env::var("BADTERM_SOUND").expect("set BADTERM_SOUND")).unwrap();
898 let t = Instant::now();
899 let sound = decode(&data).expect("decodes");
900 let frames = sound.frames();
901 let pcm = sound.wait().clone();
902 eprintln!("length from the headers {frames}, decoded {}", pcm.len() / 2);
903 let secs = pcm.len() as f64 / 2.0 / RATE as f64;
904 let peak = pcm.iter().map(|s| s.unsigned_abs()).max().unwrap_or(0);
905 let rms = (pcm.iter().map(|&s| (s as f64).powi(2)).sum::<f64>() / pcm.len() as f64).sqrt();
906 let zc = pcm.chunks(2).collect::<Vec<_>>().windows(2).filter(|w| (w[0][0] < 0) != (w[1][0] < 0)).count();
908 eprintln!(
909 "{secs:.2} s, peak {peak}, rms {rms:.0}, {:.0} crossings/s, decoded in {:?}",
910 zc as f64 / secs,
911 t.elapsed()
912 );
913 if let Ok(out) = std::env::var("BADTERM_WAV_OUT") {
914 let bytes: Vec<u8> = pcm.iter().flat_map(|s| s.to_le_bytes()).collect();
915 let mut f = b"RIFF\0\0\0\0WAVEfmt ".to_vec();
916 f.extend(16u32.to_le_bytes());
917 f.extend([1, 0, 2, 0]);
918 f.extend(RATE.to_le_bytes());
919 f.extend((RATE * 4).to_le_bytes());
920 f.extend([4, 0, 16, 0]);
921 f.extend(b"data");
922 f.extend((bytes.len() as u32).to_le_bytes());
923 f.extend(bytes);
924 std::fs::write(out, f).unwrap();
925 }
926 }
927}