mirror of
https://github.com/storytold/soundcraft.git
synced 2026-10-24 02:26:46 -06:00
* Build: line tables only for workspace crates, no debug info for dependencies Full debug info made most of the dev and test target size and link time. Line tables keep file and line numbers in backtraces; dependency frames lose them. * Tests: build each crate's integration tests as one binary Each tests/*.rs file was its own test binary: 98 binaries, each linking the crate and its dependencies. The files are now modules of tests/it/main.rs, one binary per crate, which cuts link time and target size. Filter by module: `cargo test -p <crate> --test it <module>::`. Binaries with their own main (harness = false) in clap-host and vst3-host stay separate, as do the ui-egui tests that switch the process-wide Light/Dark palette and recovery.rs, whose autosave locks leak into child processes other tests start. * Xtask: run tests with nextest, doctests apart, and no examples in the test build cargo xtask ci runs the tests with cargo nextest when it is installed, else cargo test with a notice, and doctests with cargo test --doc. The test build leaves out examples (--lib --bins --tests); clippy --all-targets still compiles them. Test binaries with harness = false run under cargo test: their own main cannot answer nextest's --list. clippy and the wasm check keep going past a failing crate. cargo xtask test runs the test step alone.
88 lines
3.5 KiB
Rust
88 lines
3.5 KiB
Rust
//! A WAV whose data chunk is empty and followed by metadata decodes to no audio; a recording
|
|
//! that was never finalised (RIFF and data sizes left at 0) still yields what was written (#466).
|
|
|
|
use soundcraft_audio_io::{decode, probe};
|
|
|
|
fn fmt16(channels: u16, sr: u32) -> Vec<u8> {
|
|
let align = channels * 2;
|
|
let mut f = Vec::new();
|
|
f.extend_from_slice(&1u16.to_le_bytes());
|
|
f.extend_from_slice(&channels.to_le_bytes());
|
|
f.extend_from_slice(&sr.to_le_bytes());
|
|
f.extend_from_slice(&(sr * u32::from(align)).to_le_bytes());
|
|
f.extend_from_slice(&align.to_le_bytes());
|
|
f.extend_from_slice(&16u16.to_le_bytes());
|
|
f
|
|
}
|
|
|
|
fn chunk(id: &[u8; 4], data: &[u8]) -> Vec<u8> {
|
|
let mut c = id.to_vec();
|
|
c.extend_from_slice(&(data.len() as u32).to_le_bytes());
|
|
c.extend_from_slice(data);
|
|
if data.len() % 2 == 1 {
|
|
c.push(0);
|
|
}
|
|
c
|
|
}
|
|
|
|
/// A finished file: correct RIFF size, chunks as given.
|
|
fn riff(chunks: &[Vec<u8>]) -> Vec<u8> {
|
|
let mut body = b"WAVE".to_vec();
|
|
for c in chunks {
|
|
body.extend_from_slice(c);
|
|
}
|
|
let mut out = b"RIFF".to_vec();
|
|
out.extend_from_slice(&(body.len() as u32).to_le_bytes());
|
|
out.extend_from_slice(&body);
|
|
out
|
|
}
|
|
|
|
fn list_info() -> Vec<u8> {
|
|
let mut l = b"INFO".to_vec();
|
|
l.extend_from_slice(&chunk(b"INAM", b"Silence"));
|
|
chunk(b"LIST", &l)
|
|
}
|
|
|
|
#[test]
|
|
fn an_empty_data_chunk_followed_by_metadata_has_no_frames() {
|
|
let file = riff(&[chunk(b"fmt ", &fmt16(2, 48_000)), chunk(b"data", &[]), list_info(), chunk(b"bext", &[0u8; 602])]);
|
|
let info = probe(&file, Some("wav")).unwrap();
|
|
assert_eq!(info.frames, 0, "the LIST and bext chunks are not samples");
|
|
let (info, buf) = decode(&file, Some("wav")).unwrap();
|
|
assert_eq!((info.frames, buf.frames()), (0, 0));
|
|
// The same with the metadata first.
|
|
let file = riff(&[chunk(b"fmt ", &fmt16(1, 44_100)), list_info(), chunk(b"data", &[])]);
|
|
assert_eq!(probe(&file, Some("wav")).unwrap().frames, 0);
|
|
// And with a single trailing chunk of odd size and no pad byte at the very end.
|
|
let mut file = riff(&[chunk(b"fmt ", &fmt16(1, 44_100)), chunk(b"data", &[]), chunk(b"note", b"abc")]);
|
|
file.pop();
|
|
let riff_size = (file.len() - 8) as u32;
|
|
file[4..8].copy_from_slice(&riff_size.to_le_bytes());
|
|
assert_eq!(probe(&file, Some("wav")).unwrap().frames, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn a_recording_that_was_never_finalised_still_gives_its_audio() {
|
|
let samples: Vec<u8> = (0..400i16).flat_map(|i| (i * 40).to_le_bytes()).collect();
|
|
// RIFF size and data size both left at 0, as a recorder writes them until it finishes.
|
|
let mut file = riff(&[chunk(b"fmt ", &fmt16(2, 48_000)), chunk(b"data", &[])]);
|
|
file.extend_from_slice(&samples);
|
|
file[4..8].copy_from_slice(&0u32.to_le_bytes());
|
|
let (info, buf) = decode(&file, Some("wav")).unwrap();
|
|
assert_eq!(info.frames, 200);
|
|
assert_eq!(buf.frames(), 200);
|
|
// Even if the RIFF size is the file's, audio that is not a run of chunks is still audio.
|
|
let mut file = riff(&[chunk(b"fmt ", &fmt16(2, 48_000)), chunk(b"data", &[])]);
|
|
file.extend_from_slice(&samples);
|
|
let riff_size = (file.len() - 8) as u32;
|
|
file[4..8].copy_from_slice(&riff_size.to_le_bytes());
|
|
assert_eq!(probe(&file, Some("wav")).unwrap().frames, 200);
|
|
}
|
|
|
|
#[test]
|
|
fn a_data_chunk_with_audio_is_unchanged() {
|
|
let samples: Vec<u8> = (0..100i16).flat_map(|i| i.to_le_bytes()).collect();
|
|
let file = riff(&[chunk(b"fmt ", &fmt16(1, 44_100)), chunk(b"data", &samples), list_info()]);
|
|
assert_eq!(probe(&file, Some("wav")).unwrap().frames, 100);
|
|
}
|