use super::codec::{self, MAX_ORDER, MAX_WIDTH, MIN_WIDTH, OVERLAP};
use super::{
be32, block_bytes, midi_key, Bank, Library, Stroke, CNSP_MAGIC, DAMPER_TOP_AT, DECAYS,
DIRECTORY_AT, FINE_TUNE_AT, FORMAT, GAIN_AT, KEY_MAP_AT, KIND_AT, LADDER_UNITY, MARKS, NOTES,
RECORD, REC_BANK, REC_BLOCKS, REC_DECAY, REC_DECAYS, REC_FRAMES, REC_ID, REC_LAYER, REC_MARKS,
REC_MARK_BLOCK, REC_SEEDS, REC_START, REC_TRIM, REC_WINDOW, SEEDS, UNCOVERED, VERSION_AT,
VERSION_ECHO_AT,
};
use crate::cbin::Header;
use crate::error::{Error, ParseError};
use crate::formats::nsmp::kernel;
use crate::formats::predictor;
use std::borrow::Cow;
use std::collections::{BTreeMap, BTreeSet};
const FULL_SCALE: f64 = 8192.0;
const WIDTHS: usize = MAX_WIDTH as usize + 1;
#[derive(Debug, Clone)]
pub struct Options {
pub name: String,
pub variant: String,
}
impl Options {
pub fn new(name: &str) -> Options {
Options {
name: name.to_owned(),
variant: String::new(),
}
}
pub fn variant(mut self, variant: &str) -> Options {
self.variant = variant.to_owned();
self
}
}
#[derive(Debug, Clone)]
pub enum Donor<'a> {
Template(&'a Library<'a>),
Rules(Rules),
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Kind {
ElectricGrand,
ElectricPiano,
Wurlitzer,
Clavinet,
#[default]
Grand,
Upright,
Harpsichord,
DigitalPiano,
Hybrid,
Mallet,
}
impl Kind {
pub const ALL: [Kind; 10] = [
Kind::ElectricGrand,
Kind::ElectricPiano,
Kind::Wurlitzer,
Kind::Clavinet,
Kind::Grand,
Kind::Upright,
Kind::Harpsichord,
Kind::DigitalPiano,
Kind::Hybrid,
Kind::Mallet,
];
pub fn from_code(code: u8) -> Option<Kind> {
match code {
1 => Some(Kind::ElectricGrand),
2 => Some(Kind::ElectricPiano),
3 => Some(Kind::Wurlitzer),
4 => Some(Kind::Clavinet),
5 => Some(Kind::Grand),
6 => Some(Kind::Upright),
7 => Some(Kind::Harpsichord),
14 => Some(Kind::DigitalPiano),
15 => Some(Kind::Hybrid),
16 => Some(Kind::Mallet),
_ => None,
}
}
pub fn code(self) -> u8 {
match self {
Kind::ElectricGrand => 1,
Kind::ElectricPiano => 2,
Kind::Wurlitzer => 3,
Kind::Clavinet => 4,
Kind::Grand => 5,
Kind::Upright => 6,
Kind::Harpsichord => 7,
Kind::DigitalPiano => 14,
Kind::Hybrid => 15,
Kind::Mallet => 16,
}
}
pub fn damper_top(self) -> u8 {
match self {
Kind::Grand | Kind::Upright => 90,
Kind::Wurlitzer => 97,
_ => ALL_KEYS_DAMPED,
}
}
}
pub const ALL_KEYS_DAMPED: u8 = 109;
pub const DEFAULT_GAIN: i8 = 50;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Rules {
pub kind: Kind,
pub gain: i8,
pub damper_top: u8,
}
impl Rules {
pub fn new(kind: Kind) -> Rules {
Rules {
kind,
gain: DEFAULT_GAIN,
damper_top: kind.damper_top(),
}
}
}
impl Default for Rules {
fn default() -> Rules {
Rules::new(Kind::default())
}
}
#[derive(Debug, Clone)]
pub struct Recording {
pub root: u8,
pub bank: Bank,
pub layer: u8,
pub channels: Vec<Vec<i16>>,
}
pub const SOFTEST_LAYER: u8 = 27;
pub const HIGHEST_PLAYED_LAYER: u8 = ((127 - 1) * 31 / 127) as u8;
pub fn layer_value(index: usize, layers: usize) -> u8 {
let last = layers.saturating_sub(1);
if last == 0 {
return 0;
}
let index = index.min(last);
let scale = usize::from(SOFTEST_LAYER);
((index * scale * 2 + last) / (last * 2)) as u8
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum LayerTag {
Index(u8),
Value(u8),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Stem {
None,
Any,
}
pub fn parse_stroke_name(name: &str, stem: Stem) -> Option<(u8, Bank, LayerTag)> {
let mut parts = name.rsplit('-');
let third = parts.next()?;
let layer = match (third.strip_prefix('l'), third.strip_prefix('v')) {
(Some(index), _) => LayerTag::Index(index.parse().ok()?),
(None, Some(value)) => LayerTag::Value(value.parse().ok()?),
(None, None) => return None,
};
let bank = Bank::from_code(parts.next()?.strip_prefix('b')?.parse().ok()?)?;
let root: u8 = parts.next()?.parse().ok()?;
if stem == Stem::None && parts.next().is_some() {
return None;
}
(usize::from(root) < NOTES).then_some((root, bank, layer))
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct LayerClash {
pub root: u8,
pub bank: Bank,
pub how: Clash,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Clash {
BothForms,
Twice,
}
pub fn layer_values(strokes: &[(u8, Bank, LayerTag)]) -> Result<Vec<u8>, LayerClash> {
let mut groups: BTreeMap<(u8, Bank), Vec<usize>> = BTreeMap::new();
for (index, &(root, bank, _)) in strokes.iter().enumerate() {
groups.entry((root, bank)).or_default().push(index);
}
let mut values = vec![0u8; strokes.len()];
for ((root, bank), mut members) in groups {
let clash = |how| LayerClash { root, bank, how };
let stated = members
.iter()
.filter(|&&i| matches!(strokes[i].2, LayerTag::Value(_)))
.count();
if stated != 0 && stated != members.len() {
return Err(clash(Clash::BothForms));
}
members.sort_by_key(|&i| strokes[i].2);
if members
.windows(2)
.any(|pair| strokes[pair[0]].2 == strokes[pair[1]].2)
{
return Err(clash(Clash::Twice));
}
let layers = members.len();
for (rank, index) in members.into_iter().enumerate() {
values[index] = match strokes[index].2 {
LayerTag::Value(value) => value,
LayerTag::Index(_) => layer_value(rank, layers),
};
}
}
Ok(values)
}
pub struct Rebuilt {
pub library: Library<'static>,
pub strokes: Vec<Recoded>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Recoded {
pub blocks: usize,
pub identical: usize,
pub restated: usize,
}
impl Recoded {
pub fn recoded(&self) -> usize {
self.blocks - self.identical - self.restated
}
}
pub fn build(
donor: &Donor<'_>,
options: &Options,
recordings: &[Recording],
) -> Result<Library<'static>, Error> {
let channels = check_recordings(recordings)?;
let (header, mut prefix) = match donor {
Donor::Template(template) => (template.header.clone(), template.prefix.clone()),
Donor::Rules(rules) => (
Header::new(FORMAT, (0, 0), CONTENT_VERSION),
rules_prefix(rules),
),
};
prefix[FINE_TUNE_AT..FINE_TUNE_AT + NOTES].fill(0);
let roots: BTreeSet<u8> = recordings.iter().map(|r| r.root).collect();
prefix[KEY_MAP_AT..KEY_MAP_AT + NOTES].copy_from_slice(&key_map(&roots));
let mut library = Library {
header,
prefix,
channels,
strokes: Vec::new(),
};
library.set_name_and_variant(&options.name, &options.variant)?;
let mut order: Vec<&Recording> = recordings.iter().collect();
order.sort_by_key(|r| (r.root, r.bank.code(), r.layer));
let mut donors = Vec::with_capacity(order.len());
for (index, recording) in order.iter().enumerate() {
donors.push(match donor {
Donor::Template(template) => *donor_record(template, recording)?,
Donor::Rules(_) => rules_record(recording, index),
});
}
let unique: BTreeSet<u32> = donors.iter().map(|d| be32(d, REC_ID)).collect();
let keep_ids = unique.len() == donors.len();
for (index, (recording, donor)) in order.iter().zip(&donors).enumerate() {
let seeds = seeds_for(&recording.channels);
let target = recording.channels[0].len();
let coded = code(&recording.channels, &seeds, target)?;
let id = if keep_ids {
be32(donor, REC_ID)
} else {
index as u32 + 1
};
library.strokes.push(Stroke {
root: recording.root,
record: record(
donor,
&coded,
recording.bank.code(),
recording.layer,
&seeds,
id,
)?,
audio: Cow::Owned(coded.audio),
});
}
Ok(library)
}
const CONTENT_VERSION: u32 = 540;
const RULES_VERSION: u16 = 0x450;
const FILE_ID_AT: usize = 0x06;
const FILE_ID: u32 = 1;
const VERSION_REPEAT_AT: usize = 0x16;
const KIND_TRAILER: [u8; 3] = [0, 0, 2];
const PER_NOTE_TABLES: [(usize, u8); 6] = [
(0x10c, 0),
(FINE_TUNE_AT, 0),
(0x20c, 57),
(0x28c, 0),
(0x30c, 0),
(0x38c, 0),
];
const PARAMETERS: std::ops::Range<usize> = 0x40c..0x60f;
const PARAMETER_TAIL_AT: usize = 0x40e;
const PARAMETER_TAIL: [u8; 3] = [10, 108, 1];
const BEFORE_DAMPER_CUT_AT: usize = 0x489;
const BEFORE_DAMPER_CUT: [u8; 19] = [128; 19];
const DAMPER_CUT_AT: usize = 0x49d;
const _: () = assert!(DAMPER_CUT_AT + NOTES <= PARAMETERS.end);
fn rules_prefix(rules: &Rules) -> Vec<u8> {
let mut prefix = vec![0u8; DIRECTORY_AT];
prefix[..CNSP_MAGIC.len()].copy_from_slice(CNSP_MAGIC);
for at in [VERSION_AT, VERSION_REPEAT_AT, VERSION_ECHO_AT] {
prefix[at..at + 2].copy_from_slice(&RULES_VERSION.to_be_bytes());
}
prefix[FILE_ID_AT..FILE_ID_AT + 4].copy_from_slice(&FILE_ID.to_be_bytes());
prefix[KIND_AT] = rules.kind.code();
prefix[KIND_AT + 1..KIND_AT + 1 + KIND_TRAILER.len()].copy_from_slice(&KIND_TRAILER);
for (at, value) in PER_NOTE_TABLES {
prefix[at..at + NOTES].fill(value);
}
prefix[GAIN_AT] = rules.gain as u8;
prefix[DAMPER_TOP_AT] = rules.damper_top;
prefix[PARAMETER_TAIL_AT..PARAMETER_TAIL_AT + PARAMETER_TAIL.len()]
.copy_from_slice(&PARAMETER_TAIL);
prefix[BEFORE_DAMPER_CUT_AT..BEFORE_DAMPER_CUT_AT + BEFORE_DAMPER_CUT.len()]
.copy_from_slice(&BEFORE_DAMPER_CUT);
for note in 0..NOTES {
prefix[DAMPER_CUT_AT + note] = damper_cut(note);
}
prefix
}
fn damper_cut(note: usize) -> u8 {
const FLAT_TO: usize = 24;
const TOP: usize = 108;
const PLATEAU: f64 = 79.0;
const FALL: f64 = 59.0;
const PAST_TOP: u8 = 30;
if note < FLAT_TO {
PLATEAU as u8
} else if note <= TOP {
(PLATEAU - FALL * (note - FLAT_TO) as f64 / (TOP - FLAT_TO) as f64).round_ties_even() as u8
} else {
PAST_TOP
}
}
fn rules_record(recording: &Recording, index: usize) -> [u8; RECORD] {
let mut out = [0u8; RECORD];
let (window, trim) = velocity_window(recording.bank, recording.layer);
out[REC_WINDOW..REC_WINDOW + 2].copy_from_slice(&window.to_be_bytes());
out[REC_TRIM..REC_TRIM + 2].copy_from_slice(&trim.to_be_bytes());
for entry in 0..DECAYS {
let at = REC_DECAYS + entry * 4;
out[at..at + 4].copy_from_slice(&LADDER_UNITY.to_be_bytes());
}
out[REC_ID..REC_ID + 4].copy_from_slice(&(index as u32 + 1).to_be_bytes());
out
}
const RELEASE_TRIM: u16 = 12;
const WIDEST_TRIM: u16 = 31;
fn velocity_window(bank: Bank, layer: u8) -> (u16, u16) {
match bank {
Bank::Release => (0, RELEASE_TRIM),
Bank::Attack | Bank::Resonance => (
u16::from(layer),
u16::from(layer).saturating_add(3).min(WIDEST_TRIM),
),
}
}
pub fn rebuild(library: &Library<'_>) -> Result<Rebuilt, Error> {
let block = library.block_bytes();
let mut strokes = Vec::new();
let mut report = Vec::new();
for stroke in library.strokes() {
let audio = codec::decode(stroke, library.channels())?;
if audio.clipped > 0 {
return Err(refuse(format!(
"{stroke:?}: {} sample(s) left int16 in the decode; coding a stroke this \
codec does not describe would write the saturated frames as new audio",
audio.clipped
)));
}
let target = audio.frames();
let mut source = audio.lanes;
for (channel, tail) in source.iter_mut().zip(&audio.tail) {
channel.extend_from_slice(tail);
}
let seeds = stroke.seeds();
let coded = code(&source, &seeds, target)?;
report.push(compare(stroke.audio(), &coded.audio, block));
strokes.push(Stroke {
root: stroke.root,
record: record(
stroke.record(),
&coded,
stroke.bank_code(),
stroke.layer(),
&seeds,
stroke.id(),
)?,
audio: Cow::Owned(coded.audio),
});
}
Ok(Rebuilt {
library: Library {
header: library.header.clone(),
prefix: library.prefix.clone(),
channels: library.channels(),
strokes,
},
strokes: report,
})
}
pub struct Resampled {
pub channels: Vec<Vec<i16>>,
pub clipped: usize,
}
pub fn resample(samples: &[i16], channels: usize, rate: u32) -> Result<Resampled, Error> {
if channels == 0 || rate == 0 || !samples.len().is_multiple_of(channels) {
return Err(ParseError::OutOfBounds {
value: format!(
"{} sample(s) of {channels} channel(s) at {rate} Hz",
samples.len()
),
bound: "whole frames of at least one channel at a positive rate".into(),
}
.into());
}
if rate == codec::RATE {
let mut lanes = vec![Vec::new(); channels];
for (i, &sample) in samples.iter().enumerate() {
lanes[i % channels].push(sample);
}
return Ok(Resampled {
channels: lanes,
clipped: 0,
});
}
let frames = samples.len() / channels;
let stretched = frames as u128 * u128::from(codec::RATE) / u128::from(rate);
let fields = usize::try_from(stretched)
.ok()
.filter(|&fields| u32::try_from(fields).is_ok())
.ok_or_else(|| ParseError::OutOfBounds {
value: format!("{frames} frame(s) at {rate} Hz, which is {stretched} on the lattice"),
bound: "the u32 frame count a stroke record holds".into(),
})?;
let kernel = kernel::Kernel::new(rate, codec::RATE);
let mut clipped = 0;
let mut lanes = Vec::with_capacity(channels);
for channel in 0..channels {
let lane: Vec<i16> = samples
.iter()
.skip(channel)
.step_by(channels)
.copied()
.collect();
let mut out = Vec::new();
out.try_reserve_exact(fields)
.map_err(|_| ParseError::OutOfBounds {
value: format!("{fields} frame(s)"),
bound: "an allocation that fits memory".into(),
})?;
out.extend((0..fields).map(|f| {
let value = kernel.field(&lane, f);
let narrow = value.clamp(i64::from(i16::MIN), i64::from(i16::MAX)) as i16;
clipped += usize::from(i64::from(narrow) != value);
narrow
}));
lanes.push(out);
}
Ok(Resampled {
channels: lanes,
clipped,
})
}
fn check_recordings(recordings: &[Recording]) -> Result<u16, Error> {
let Some(first) = recordings.first() else {
return Err(refuse(
"a library with no recordings at all has nothing to play",
));
};
let channels = first.channels.len();
if !(1..=2).contains(&channels) {
return Err(ParseError::OutOfBounds {
value: format!("{channels} channels"),
bound: "1 or 2, which is what a library states".into(),
}
.into());
}
let mut seen = BTreeSet::new();
for recording in recordings {
let what = describe(recording);
midi_key("root", recording.root)?;
if recording.channels.len() != channels {
return Err(refuse(format!(
"{what} has {} channel(s) where another has {channels}; one library plays \
one channel count",
recording.channels.len()
)));
}
let frames = recording.channels[0].len();
if recording.channels.iter().any(|c| c.len() != frames) {
return Err(refuse(format!(
"{what} has channels of unequal length; a frame is one sample of each"
)));
}
if frames == 0 {
return Err(refuse(format!("{what} has no frames")));
}
if recording.layer > HIGHEST_PLAYED_LAYER {
return Err(refuse(format!(
"{what} states a layer value no velocity selects; {HIGHEST_PLAYED_LAYER} is \
the largest a key ever sounds"
)));
}
if !seen.insert((recording.root, recording.bank.code(), recording.layer)) {
return Err(refuse(format!(
"{what} is recorded twice; a root's layers are numbered within one bank"
)));
}
}
Ok(channels as u16)
}
fn describe(recording: &Recording) -> String {
format!(
"root {} {} layer {}",
recording.root, recording.bank, recording.layer
)
}
fn refuse(what: impl Into<String>) -> Error {
ParseError::AssertFail(what.into()).into()
}
fn key_map(roots: &BTreeSet<u8>) -> [u8; NOTES] {
let mut map = [UNCOVERED; NOTES];
for (key, slot) in map.iter_mut().enumerate() {
if let Some(&root) = roots.range((key as u8).saturating_sub(1)..).next() {
*slot = root;
}
}
map
}
fn donor_record<'a>(
template: &'a Library<'_>,
recording: &Recording,
) -> Result<&'a [u8; RECORD], Error> {
let release = Bank::Release.code();
let wanted = recording.bank.code();
let same: Vec<&Stroke<'_>> = template
.strokes()
.iter()
.filter(|s| s.bank_code() == wanted)
.collect();
let pool: Vec<&Stroke<'_>> = match (same.is_empty(), recording.bank) {
(false, _) => same,
(true, Bank::Release) => template.strokes().iter().collect(),
(true, _) => template
.strokes()
.iter()
.filter(|s| s.bank_code() != release)
.collect(),
};
pool.iter()
.min_by_key(|s| {
(
s.root.abs_diff(recording.root),
s.layer().abs_diff(recording.layer),
)
})
.map(|s| s.record())
.ok_or_else(|| {
refuse(format!(
"the template records no stroke to take {}'s length marks and decay \
coefficients from, and nothing in the audio predicts them",
describe(recording)
))
})
}
struct Coded {
audio: Vec<u8>,
owned: usize,
starts: Vec<usize>,
}
#[derive(Debug, Clone, Copy)]
struct Placed {
at: usize,
order: u8,
width: u8,
}
fn code(source: &[Vec<i16>], seeds: &[[i16; SEEDS]; 2], target: usize) -> Result<Coded, Error> {
let channels = source.len();
let block = block_bytes(channels as u16);
let counts = frame_counts(block, channels);
let widest = counts[usize::from(MIN_WIDTH)];
let total = target
.checked_add(widest)
.and_then(|total| total.checked_mul(channels).map(|_| total))
.ok_or_else(|| refuse("a stroke longer than this platform can address"))?;
let planes = planes(source, seeds, total)?;
let mut placed = lay_capped(&planes, channels, &counts, target);
if placed.is_empty() || owned_by(&placed, &counts) != target {
placed = lay_uncapped(&planes, channels, &counts, target);
}
let mut audio = Vec::new();
for block_at in &placed {
let frames = counts[usize::from(block_at.width)];
let span = block_at.at * channels..(block_at.at + frames) * channels;
let peak = planes[0][span.clone()]
.iter()
.map(|&v| i64::from(v).abs())
.max()
.unwrap_or(0);
pack(
&mut audio,
block_at.order,
block_at.width,
attenuation(peak),
&planes[usize::from(block_at.order)][span],
block,
);
}
Ok(Coded {
audio,
owned: owned_by(&placed, &counts),
starts: placed.iter().map(|b| b.at).collect(),
})
}
fn owned_by(placed: &[Placed], counts: &[usize; WIDTHS]) -> usize {
placed
.last()
.map_or(0, |b| b.at + counts[usize::from(b.width)] - OVERLAP)
}
fn lay_capped(
planes: &[Vec<i32>],
channels: usize,
counts: &[usize; WIDTHS],
target: usize,
) -> Vec<Placed> {
let shortest = shortest_block(counts);
let mut out = Vec::new();
let mut at = 0usize;
while target - at >= shortest {
let block = place(planes, at, channels, counts, Some(target - at));
at += counts[usize::from(block.width)] - OVERLAP;
out.push(block);
}
out
}
fn lay_uncapped(
planes: &[Vec<i32>],
channels: usize,
counts: &[usize; WIDTHS],
target: usize,
) -> Vec<Placed> {
let mut out = Vec::new();
let mut at = 0usize;
loop {
let block = place(planes, at, channels, counts, None);
at += counts[usize::from(block.width)] - OVERLAP;
out.push(block);
if at >= target {
return out;
}
}
}
fn place(
planes: &[Vec<i32>],
at: usize,
channels: usize,
counts: &[usize; WIDTHS],
room: Option<usize>,
) -> Placed {
let (order, width) = choose(planes, at, channels, counts, room)
.expect("order zero states a sample outright, which always fits sixteen bits");
Placed { at, order, width }
}
fn shortest_block(counts: &[usize; WIDTHS]) -> usize {
counts[usize::from(MAX_WIDTH)] - OVERLAP
}
fn frame_counts(block: usize, channels: usize) -> [usize; WIDTHS] {
let mut out = [0; WIDTHS];
for width in MIN_WIDTH..=MAX_WIDTH {
out[usize::from(width)] = codec::block_frames(width, block, channels);
}
out
}
fn planes(
source: &[Vec<i16>],
seeds: &[[i16; SEEDS]; 2],
total: usize,
) -> Result<Vec<Vec<i32>>, Error> {
let channels = source.len();
let sample = |channel: usize, n: isize| -> i64 {
match usize::try_from(n) {
Ok(n) => i64::from(source[channel].get(n).copied().unwrap_or(0)),
Err(_) => i64::from(seeds[channel][(SEEDS as isize + n) as usize]),
}
};
let mut out = Vec::with_capacity(MAX_ORDER + 1);
for order in 0..=MAX_ORDER {
let mut plane = residuals(total * channels)?;
for n in 0..total {
for channel in 0..channels {
let mut acc = 0i64;
for j in 0..=order {
let term =
predictor::binomial(order, j) * sample(channel, n as isize - j as isize);
acc += if j.is_multiple_of(2) { term } else { -term };
}
plane[n * channels + channel] = acc as i32;
}
}
out.push(plane);
}
Ok(out)
}
fn residuals(len: usize) -> Result<Vec<i32>, Error> {
let mut out = Vec::new();
out.try_reserve_exact(len)
.map_err(|_| ParseError::OutOfBounds {
value: format!("{len} residual(s)"),
bound: "an allocation that fits memory".into(),
})?;
out.resize(len, 0);
Ok(out)
}
fn choose(
planes: &[Vec<i32>],
at: usize,
channels: usize,
counts: &[usize; WIDTHS],
owned_left: Option<usize>,
) -> Option<(u8, u8)> {
let mut best: Option<(u8, u8)> = None;
for (order, plane) in planes.iter().enumerate() {
let mut lo = 0i32;
let mut hi = 0i32;
let mut scanned = at;
let mut narrowest = None;
for width in (MIN_WIDTH..=MAX_WIDTH).rev() {
let frames = counts[usize::from(width)];
for &value in &plane[scanned * channels..(at + frames) * channels] {
lo = lo.min(value);
hi = hi.max(value);
}
scanned = at + frames;
let bound = 1i32 << (width - 1);
if lo < -bound || hi >= bound {
break;
}
if owned_left.is_none_or(|left| frames - OVERLAP <= left) {
narrowest = Some(width);
}
}
if let Some(width) = narrowest {
if best.is_none_or(|(_, reached)| width < reached) {
best = Some((order as u8, width));
}
}
}
best
}
fn pack(out: &mut Vec<u8>, order: u8, width: u8, stat: u8, residuals: &[i32], block: usize) {
let start = out.len();
let header = (u16::from(stat) << 8) | (u16::from(order) << 5) | u16::from(width);
out.extend_from_slice(&header.to_be_bytes());
let mask = (1u64 << width) - 1;
let mut reservoir = 0u64;
let mut held = 0u32;
for &value in residuals {
reservoir |= (i64::from(value) as u64 & mask) << held;
held += u32::from(width);
while held >= 16 {
out.extend_from_slice(&((reservoir & 0xffff) as u16).to_be_bytes());
reservoir >>= 16;
held -= 16;
}
}
if held > 0 {
out.extend_from_slice(&((reservoir & 0xffff) as u16).to_be_bytes());
}
out.resize(start + block, 0);
}
fn attenuation(peak: i64) -> u8 {
if peak == 0 {
return 100;
}
let db = -20.0 * (peak as f64 / FULL_SCALE).log10();
(db + 0.5).floor().clamp(0.0, 100.0) as u8
}
fn seeds_for(source: &[Vec<i16>]) -> [[i16; SEEDS]; 2] {
let mut out = [[0i16; SEEDS]; 2];
for (channel, group) in source.iter().zip(out.iter_mut()) {
for (i, slot) in group.iter_mut().skip(1).enumerate() {
*slot = channel.get(i).copied().unwrap_or(0);
}
}
out
}
fn record(
donor: &[u8; RECORD],
coded: &Coded,
bank: u8,
layer: u8,
seeds: &[[i16; SEEDS]; 2],
id: u32,
) -> Result<[u8; RECORD], Error> {
let owned = u32::try_from(coded.owned).map_err(|_| ParseError::OutOfBounds {
value: format!("{} frames", coded.owned),
bound: "the u32 frame count a stroke record holds".into(),
})?;
let blocks = u16::try_from(coded.starts.len()).map_err(|_| ParseError::OutOfBounds {
value: format!("{} blocks", coded.starts.len()),
bound: "the u16 block count a stroke record holds".into(),
})?;
let mut out = *donor;
out[REC_START..REC_START + 4].fill(0);
out[REC_BANK] = bank;
out[REC_LAYER] = layer;
out[REC_FRAMES..REC_FRAMES + 4].copy_from_slice(&owned.to_be_bytes());
out[REC_BLOCKS..REC_BLOCKS + 2].copy_from_slice(&blocks.to_be_bytes());
for (channel, group) in seeds.iter().enumerate() {
for (i, seed) in group.iter().enumerate() {
let at = REC_SEEDS + (channel * SEEDS + i) * 2;
out[at..at + 2].copy_from_slice(&seed.to_be_bytes());
}
}
let silent = bank == Bank::Release.code();
let donor_frames = be32(donor, REC_FRAMES);
let mut first = 0u32;
for mark in 0..MARKS {
let at = REC_MARKS + mark * 4;
let scaled = match (silent, donor_frames) {
(true, _) | (_, 0) => 0,
_ => rescale(be32(donor, at), owned, donor_frames),
};
out[at..at + 4].copy_from_slice(&scaled.to_be_bytes());
if mark == 0 {
first = scaled;
}
}
let holding = coded
.starts
.iter()
.rposition(|&start| start as u64 <= u64::from(first))
.unwrap_or(0);
out[REC_MARK_BLOCK..REC_MARK_BLOCK + 2].copy_from_slice(&(holding as u16).to_be_bytes());
if silent {
out[REC_DECAY..REC_DECAY + 4].fill(0);
}
out[REC_ID..REC_ID + 4].copy_from_slice(&id.to_be_bytes());
Ok(out)
}
fn rescale(mark: u32, owned: u32, donor_frames: u32) -> u32 {
let moved = u64::from(mark) * u64::from(owned) / u64::from(donor_frames);
moved.min(u64::from(owned.saturating_sub(1))) as u32
}
fn compare(before: &[u8], coded: &[u8], block: usize) -> Recoded {
let mut out = Recoded {
blocks: coded.len() / block,
..Recoded::default()
};
for (index, now) in coded.chunks_exact(block).enumerate() {
let Some(was) = before.get(index * block..(index + 1) * block) else {
continue;
};
if was == now {
out.identical += 1;
} else if was[1..] == now[1..] {
out.restated += 1;
}
}
out
}
#[cfg(test)]
mod tests {
use super::super::synthetic::{take, Build};
use super::*;
use crate::formats::npno::{be16, Piano, DECAYS};
fn template(channels: u16) -> Piano {
let mut build = Build::new();
build.channels = channels;
build.map = vec![(60, 60)];
build.takes = vec![take(60, Bank::Attack, 0, 1)
.marks(std::array::from_fn(|mark| (mark as u32 + 6) * 100))
.decay(0x0000_2000)
.ladder(std::array::from_fn(|entry| 0x0000_1000 + entry as u32))
.id(77)
.silent()];
build.piano()
}
fn tone(frames: usize, hertz: f64, channels: usize) -> Vec<Vec<i16>> {
(0..channels)
.map(|c| {
(0..frames)
.map(|n| {
let t = n as f64 / f64::from(codec::RATE);
let envelope = (-3.0 * t).exp() * (1.0 - (-400.0 * t).exp());
let phase = std::f64::consts::TAU * hertz * (c as f64 * 0.01 + 1.0) * t;
(9000.0 * envelope * phase.sin()) as i16
})
.collect()
})
.collect()
}
fn one(root: u8, bank: Bank, layer: u8, channels: Vec<Vec<i16>>) -> Recording {
Recording {
root,
bank,
layer,
channels,
}
}
fn round_trip(channels: u16, recordings: &[Recording]) -> Piano {
let donor = template(channels);
let built = build(
&Donor::Template(&donor.library().unwrap()),
&Options::new("Synth").variant("Test"),
recordings,
)
.unwrap();
let bytes = {
let piano = built.to_piano().unwrap();
let mut out = std::io::Cursor::new(Vec::new());
piano.write_to(&mut out).unwrap();
out.into_inner()
};
Piano::read_from(&mut std::io::Cursor::new(bytes)).unwrap()
}
#[test]
fn a_built_library_decodes_back_to_the_frames_it_was_given() {
let source = tone(20_000, 220.0, 2);
let piano = round_trip(2, &[one(60, Bank::Attack, 0, source.clone())]);
let library = piano.library().unwrap();
assert_eq!(library.name(), ("Synth".into(), "Test".into()));
assert_eq!(library.channels(), 2);
let stroke = &library.strokes()[0];
let audio = codec::decode(stroke, 2).unwrap();
assert_eq!(audio.clipped, 0);
let longest = codec::block_frames(MIN_WIDTH, library.block_bytes(), 2) - OVERLAP;
let padding = audio
.frames()
.checked_sub(source[0].len())
.unwrap_or_else(|| {
panic!(
"the stroke states {} frames of a {} frame recording",
audio.frames(),
source[0].len()
)
});
assert!(
padding < longest,
"the stroke states {padding} frames of silence, a whole block or more"
);
for (channel, given) in audio.lanes.iter().zip(&source) {
assert_eq!(&channel[..given.len()], &given[..]);
assert!(channel[given.len()..].iter().all(|&s| s == 0));
}
}
#[test]
fn a_recording_that_does_not_fill_its_last_block_keeps_every_frame() {
let mut late = vec![vec![0i16; 892]; 2];
for (channel, lane) in late.iter_mut().enumerate() {
let signal = tone(64, 262.0, 2);
lane[892 - 64..].copy_from_slice(&signal[channel]);
}
let cases: [(&str, Vec<Vec<i16>>); 3] = [
("a recording a block and a half long", tone(700, 262.0, 2)),
(
"a recording cut between block lengths",
tone(9_133, 440.0, 2),
),
("a recording whose signal is all at the end", late),
];
for (what, source) in cases {
let frames = source[0].len();
let signal: i64 = source[0].iter().map(|&s| i64::from(s).abs()).sum();
assert!(signal > 0, "{what}: the case states no signal");
let piano = round_trip(2, &[one(60, Bank::Attack, 0, source.clone())]);
let library = piano.library().unwrap();
let audio = codec::decode(&library.strokes()[0], 2).unwrap();
assert!(
audio.frames() >= frames,
"{what}: the stroke states {} of {frames} frames",
audio.frames()
);
for (channel, given) in audio.lanes.iter().zip(&source) {
assert_eq!(
&channel[..frames],
&given[..],
"{what}: frames came back changed"
);
assert!(
channel[frames..].iter().all(|&s| s == 0),
"{what}: the stroke states something other than silence past the recording"
);
}
let again = rebuild(&library).unwrap();
for recoded in &again.strokes {
assert_eq!(
(recoded.identical, recoded.recoded()),
(recoded.blocks, 0),
"{what}: the rebuild laid the stroke out differently"
);
}
assert_eq!(
again.library.to_body().unwrap(),
piano.file.body.0,
"{what}: the rebuild is a different file"
);
}
}
#[test]
fn a_recording_of_any_length_codes_to_a_stroke_that_holds_it() {
for frames in [
1, 63, 64, 65, 445, 446, 447, 509, 891, 892, 893, 1_102, 2_658,
] {
for channels in [1u16, 2] {
let source = tone(frames, 262.0, usize::from(channels));
let piano = round_trip(channels, &[one(60, Bank::Attack, 0, source.clone())]);
let library = piano.library().unwrap();
let audio = codec::decode(&library.strokes()[0], channels).unwrap();
let what = format!("{frames} frame(s) over {channels} channel(s)");
assert!(
audio.frames() >= frames,
"{what}: the stroke states {}",
audio.frames()
);
for (channel, given) in audio.lanes.iter().zip(&source) {
assert_eq!(&channel[..frames], &given[..], "{what}: frames changed");
assert!(
channel[frames..].iter().all(|&s| s == 0),
"{what}: not silent"
);
}
assert_eq!(
rebuild(&library).unwrap().library.to_body().unwrap(),
piano.file.body.0,
"{what}: the rebuild is a different file"
);
}
}
}
#[test]
fn a_stroke_whose_decode_saturates_is_not_coded_again() {
let donor = template(1);
let mut library = donor.library().unwrap();
let block = library.block_bytes();
let frames = codec::block_frames(MAX_WIDTH, block, 1);
let mut audio = Vec::new();
pack(&mut audio, 1, MAX_WIDTH, 0, &vec![20_000i32; frames], block);
library.strokes[0].audio = Cow::Owned(audio);
let decoded = codec::decode(&library.strokes()[0], 1).unwrap();
assert!(decoded.clipped > 0, "the case does not saturate");
let error = match rebuild(&library) {
Err(error) => error.to_string(),
Ok(_) => panic!("expected a refusal"),
};
assert!(error.contains("left int16"), "{error}");
assert!(
error.contains("coding a stroke this codec does not describe"),
"the refusal does not read as the sentence it states: {error}"
);
}
#[test]
fn a_mono_library_codes_and_decodes_on_its_own_block_size() {
let source = tone(9_000, 440.0, 1);
let piano = round_trip(1, &[one(48, Bank::Attack, 0, source.clone())]);
let library = piano.library().unwrap();
assert_eq!(library.channels(), 1);
let audio = codec::decode(&library.strokes()[0], 1).unwrap();
assert_eq!(audio.lanes[0][..source[0].len()], source[0][..]);
assert!(audio.lanes[0][source[0].len()..].iter().all(|&s| s == 0));
}
#[test]
fn the_directory_orders_strokes_by_root_then_bank_then_layer() {
let short = tone(6_000, 300.0, 1);
let piano = round_trip(
1,
&[
one(72, Bank::Attack, 0, short.clone()),
one(60, Bank::Release, 0, short.clone()),
one(60, Bank::Attack, 4, short.clone()),
one(60, Bank::Attack, 0, short.clone()),
],
);
let library = piano.library().unwrap();
let seen: Vec<(u8, Option<Bank>, u8)> = library
.strokes()
.iter()
.map(|s| (s.root, s.bank(), s.layer()))
.collect();
assert_eq!(
seen,
[
(60, Some(Bank::Attack), 0),
(60, Some(Bank::Attack), 4),
(60, Some(Bank::Release), 0),
(72, Some(Bank::Attack), 0),
]
);
}
#[test]
fn the_default_layer_values_spread_a_root_over_the_selection_range() {
let spread = |layers| {
(0..layers)
.map(|i| layer_value(i, layers))
.collect::<Vec<_>>()
};
assert_eq!(spread(1), vec![0], "a lone layer plays at every velocity");
assert_eq!(spread(2), vec![0, 27]);
assert_eq!(spread(3), vec![0, 14, 27]);
assert_eq!(spread(9), vec![0, 3, 7, 10, 14, 17, 20, 24, 27]);
assert_eq!(layer_value(9, 9), 27, "an index past the last is the last");
assert_eq!(layer_value(0, 0), 0);
}
#[test]
fn a_rebuild_keeps_the_layer_value_every_stroke_states() {
let short = tone(6_000, 300.0, 1);
let piano = round_trip(
1,
&[
one(60, Bank::Attack, 0, short.clone()),
one(60, Bank::Attack, 6, short.clone()),
one(60, Bank::Attack, 12, short),
],
);
let library = piano.library().unwrap();
let again = rebuild(&library).unwrap();
let values: Vec<u8> = again.library.strokes().iter().map(|s| s.layer()).collect();
assert_eq!(values, [0, 6, 12]);
}
#[test]
fn a_release_stroke_declares_no_marks_and_zeroes_one_decay_coefficient() {
let short = tone(6_000, 300.0, 1);
let piano = round_trip(
1,
&[
one(60, Bank::Attack, 0, short.clone()),
one(60, Bank::Release, 0, short.clone()),
],
);
let library = piano.library().unwrap();
let donated: [u32; DECAYS] = std::array::from_fn(|c| 0x0000_1000u32 + c as u32);
for stroke in library.strokes() {
let record = stroke.record();
let marks: Vec<u32> = (0..MARKS)
.map(|m| be32(record, REC_MARKS + m * 4))
.collect();
assert_eq!(
stroke.ladder(),
donated,
"a stroke of any bank inherits the donor's ladder unchanged"
);
if stroke.bank() == Some(Bank::Release) {
assert_eq!(marks, [0; MARKS], "a release stroke declares no marks");
assert_eq!(
stroke.decay(),
0,
"a release stroke zeroes the coefficient at +0x2e"
);
} else {
assert!(marks.iter().all(|&m| m > 0 && m < stroke.frames()));
assert_ne!(
stroke.decay(),
0,
"a stroke of another bank inherits the donor's coefficient at +0x2e"
);
}
}
}
#[test]
fn every_kind_reads_back_from_the_code_it_writes() {
for kind in Kind::ALL {
assert_eq!(Kind::from_code(kind.code()), Some(kind));
}
let named: Vec<Kind> = (0..=u8::MAX).filter_map(Kind::from_code).collect();
assert_eq!(named, Kind::ALL, "a code names a kind ALL does not list");
}
#[test]
fn a_library_built_from_rules_states_them_and_needs_no_template() {
let short = tone(6_000, 300.0, 1);
let rules = Rules {
kind: Kind::Wurlitzer,
gain: -20,
damper_top: 97,
};
let built = build(
&Donor::Rules(rules),
&Options::new("Reeds"),
&[
one(60, Bank::Attack, 0, short.clone()),
one(60, Bank::Attack, 17, short.clone()),
one(60, Bank::Release, 0, short.clone()),
],
)
.unwrap();
assert_eq!(Kind::from_code(built.kind_code()), Some(Kind::Wurlitzer));
assert_eq!(built.gain(), -20);
assert_eq!(built.damper_top(), 97);
assert_eq!(built.stream_version(), RULES_VERSION);
assert_eq!(
Rules::new(Kind::Wurlitzer).damper_top,
rules.damper_top,
"the kind names its own damper limit"
);
let stated: Vec<(u8, u16, u16, u32)> = built
.strokes()
.iter()
.map(|s| {
let record = s.record();
(
s.layer(),
be16(record, REC_WINDOW),
s.trim(),
be32(record, REC_ID),
)
})
.collect();
assert_eq!(stated, [(0, 0, 3, 1), (17, 17, 20, 2), (0, 0, 12, 3)]);
for stroke in built.strokes() {
let record = stroke.record();
assert!((0..MARKS).all(|m| be32(record, REC_MARKS + m * 4) == 0));
assert_eq!(
(stroke.decay(), stroke.ladder()),
(0, [LADDER_UNITY; DECAYS]),
"a rule-written stroke of any bank applies no decay over the recording"
);
}
let again = rebuild(&built).unwrap();
assert_eq!(
again.library.to_body().unwrap(),
built.to_body().unwrap(),
"a rule-written library is not a fixed point of a recode"
);
}
#[test]
fn the_damper_cut_is_a_plateau_then_a_straight_fall_to_the_top_key() {
let curve: Vec<u8> = (0..NOTES).map(damper_cut).collect();
assert_eq!(curve[..25], [79; 25]);
assert_eq!((curve[66], curve[108], curve[109]), (50, 20, 30));
assert!(
curve[24..=108].windows(2).all(|w| w[0] >= w[1]),
"the fall never rises"
);
assert!(curve[109..].iter().all(|&v| v == 30));
}
#[test]
fn the_highest_played_layer_is_the_rule_at_the_softest_velocity() {
let selected = |velocity: u32| ((127 - velocity) * 31 / 127) as u8;
assert_eq!(HIGHEST_PLAYED_LAYER, selected(1));
assert!((1..=127).all(|v| selected(v) <= HIGHEST_PLAYED_LAYER));
const { assert!(SOFTEST_LAYER <= HIGHEST_PLAYED_LAYER) };
let donor = template(1);
let library = donor.library().unwrap();
let short = tone(6_000, 300.0, 1);
build(
&Donor::Template(&library),
&Options::new("Synth"),
&[one(60, Bank::Attack, HIGHEST_PLAYED_LAYER, short)],
)
.expect("the bound itself is a value a key sounds");
}
#[test]
fn a_wav_name_states_its_root_bank_and_layer() {
use LayerTag::{Index, Value};
assert_eq!(
parse_stroke_name("060-b0-l00", Stem::None),
Some((60, Bank::Attack, Index(0)))
);
assert_eq!(
parse_stroke_name("36-b2-l7", Stem::None),
Some((36, Bank::Release, Index(7)))
);
assert_eq!(
parse_stroke_name("101-b1-v12", Stem::None),
Some((101, Bank::Resonance, Value(12)))
);
assert_eq!(
parse_stroke_name("127-b0-l00", Stem::None),
Some((127, Bank::Attack, Index(0)))
);
for bad in [
"060-b3-l00",
"300-b0-l00",
"128-b0-l00",
"060-0-l00",
"060-b0-x2",
"060-b0",
"060-b0-l00-take2",
"C4-b0-l00",
] {
assert_eq!(parse_stroke_name(bad, Stem::None), None, "{bad}");
}
}
#[test]
fn a_stem_before_the_stroke_is_taken_only_where_the_caller_takes_one() {
assert_eq!(
parse_stroke_name("Grand-060-b0-l00", Stem::Any),
Some((60, Bank::Attack, LayerTag::Index(0)))
);
assert_eq!(parse_stroke_name("Grand-060-b0-l00", Stem::None), None);
assert_eq!(
parse_stroke_name("060-b0-l00", Stem::Any),
Some((60, Bank::Attack, LayerTag::Index(0))),
"a name with no stem states the same stroke either way"
);
assert_eq!(parse_stroke_name("Grand-060-b0-take2", Stem::Any), None);
}
#[test]
fn indexed_layers_spread_over_their_own_root_and_bank() {
let named = [
(60, Bank::Attack, LayerTag::Index(2)),
(60, Bank::Attack, LayerTag::Index(0)),
(60, Bank::Attack, LayerTag::Index(1)),
(60, Bank::Release, LayerTag::Index(0)),
(72, Bank::Attack, LayerTag::Index(0)),
(72, Bank::Attack, LayerTag::Index(1)),
];
assert_eq!(
layer_values(&named).unwrap(),
[27, 0, 14, 0, 0, 27],
"the order given is kept; the rank is the layer's own"
);
}
#[test]
fn a_named_layer_value_is_written_as_it_stands() {
let named = [
(60, Bank::Attack, LayerTag::Value(0)),
(60, Bank::Attack, LayerTag::Value(6)),
(60, Bank::Attack, LayerTag::Value(12)),
];
assert_eq!(layer_values(&named).unwrap(), [0, 6, 12]);
}
#[test]
fn one_roots_bank_names_its_layers_one_way_and_each_of_them_once() {
let mixed = [
(60, Bank::Attack, LayerTag::Index(0)),
(60, Bank::Attack, LayerTag::Value(12)),
];
assert_eq!(
layer_values(&mixed),
Err(LayerClash {
root: 60,
bank: Bank::Attack,
how: Clash::BothForms,
})
);
let twice = [
(60, Bank::Attack, LayerTag::Index(0)),
(60, Bank::Attack, LayerTag::Index(0)),
];
assert_eq!(
layer_values(&twice),
Err(LayerClash {
root: 60,
bank: Bank::Attack,
how: Clash::Twice,
})
);
let apart = [
(60, Bank::Attack, LayerTag::Index(0)),
(60, Bank::Release, LayerTag::Value(12)),
];
assert_eq!(
layer_values(&apart).unwrap(),
[0, 12],
"a bank of its own names its layers its own way"
);
}
#[test]
fn every_key_up_to_the_highest_roots_own_plays_the_root_above_it() {
let roots: BTreeSet<u8> = [25, 30, 60].into_iter().collect();
let map = key_map(&roots);
assert_eq!(map[0], 25, "the lowest root takes everything under it");
assert_eq!(map[26], 25, "a key one semitone above its root");
assert_eq!(map[27], 30, "and the next one belongs to the root above");
assert_eq!(map[31], 30, "a root reaches one semitone above itself");
assert_eq!(map[32], 60, "and the key after that is the next root's");
assert_eq!(map[61], 60, "the highest root reaches one semitone up");
assert_eq!(map[62], UNCOVERED);
assert_eq!(map[NOTES - 1], UNCOVERED);
}
#[test]
fn the_attenuation_states_decibels_below_full_scale() {
assert_eq!(attenuation(8192), 0);
assert_eq!(
attenuation(9000),
0,
"louder than full scale clamps at zero"
);
assert_eq!(attenuation(819), 20);
assert_eq!(attenuation(82), 40);
assert_eq!(attenuation(1), 78);
assert_eq!(attenuation(0), 100, "silence is not 78 dB down");
}
#[test]
fn a_recording_a_library_cannot_state_is_refused_by_name() {
let donor = template(1);
let library = donor.library().unwrap();
let options = Options::new("Synth");
let short = tone(6_000, 300.0, 1);
let error = |recordings: &[Recording]| {
build(&Donor::Template(&library), &options, recordings)
.expect_err("expected a refusal")
.to_string()
};
assert!(error(&[]).contains("no recordings"));
assert!(error(&[one(60, Bank::Attack, 0, vec![])]).contains("1 or 2"));
assert!(error(&[one(60, Bank::Attack, 0, vec![vec![]])]).contains("no frames"));
assert!(
error(&[one(60, Bank::Attack, 0, vec![short[0].clone(), vec![0; 3]])])
.contains("unequal length")
);
assert!(error(&[
one(60, Bank::Attack, 0, short.clone()),
one(60, Bank::Attack, 0, short.clone()),
])
.contains("recorded twice"));
let unplayable = error(&[one(
60,
Bank::Attack,
HIGHEST_PLAYED_LAYER + 1,
short.clone(),
)]);
assert!(unplayable.contains("no velocity selects"), "{unplayable}");
assert!(
unplayable.contains("30 is the largest a key ever sounds"),
"{unplayable}"
);
assert!(error(&[
one(60, Bank::Attack, 0, short.clone()),
one(
60,
Bank::Attack,
1,
vec![short[0].clone(), short[0].clone()]
),
])
.contains("one channel count"));
}
#[test]
fn a_template_donates_the_same_library_with_its_audio_dropped() {
let donor = template(1);
let library = donor.library().unwrap();
let skeleton = library.without_audio();
assert!(skeleton.strokes().iter().all(|s| s.audio().is_empty()));
let options = Options::new("Synth").variant("Test");
let recordings = [
one(60, Bank::Attack, 0, tone(6_000, 300.0, 1)),
one(72, Bank::Release, 4, tone(3_000, 500.0, 1)),
];
let whole = build(&Donor::Template(&library), &options, &recordings).unwrap();
let stripped = build(&Donor::Template(&skeleton), &options, &recordings).unwrap();
assert_eq!(stripped.to_body().unwrap(), whole.to_body().unwrap());
}
#[test]
fn a_name_that_fits_beside_the_variant_it_is_given_is_built() {
let name = "Studio Nine";
let donated = "Concert Grand Sml XL";
let donor = template(1);
let mut library = donor.library().unwrap();
library.set_variant(donated).unwrap();
assert!(
library.clone().set_name(name).is_err(),
"the name fits beside the template's variant, so the case states nothing"
);
let built = build(
&Donor::Template(&library),
&Options::new(name),
&[one(60, Bank::Attack, 0, tone(6_000, 300.0, 1))],
)
.expect("a name and an empty variant that fit the field they share");
assert_eq!(built.name(), (name.to_string(), String::new()));
}
#[test]
fn a_template_with_only_release_strokes_cannot_donate_to_an_attack_stroke() {
let donor = template(1);
let mut library = donor.library().unwrap();
library.strokes[0].record[REC_BANK] = Bank::Release.code();
let short = tone(6_000, 300.0, 1);
let error = build(
&Donor::Template(&library),
&Options::new("Synth"),
&[one(60, Bank::Attack, 0, short)],
)
.expect_err("expected a refusal")
.to_string();
assert!(
error.contains("length marks and decay coefficients"),
"{error}"
);
}
fn trailing_silence(frames: usize, silence: usize, channels: usize) -> Vec<Vec<i16>> {
let mut source = tone(frames, 262.0, channels);
for channel in &mut source {
channel.resize(frames + silence, 0);
}
source
}
#[test]
fn rebuilding_a_library_this_module_wrote_reproduces_every_block() {
let cases: [(&str, u16, Vec<Recording>); 4] = [
(
"a source shorter than one block",
2,
vec![one(60, Bank::Attack, 0, tone(509, 262.0, 2))],
),
(
"two stereo strokes cut mid-decay",
2,
vec![
one(60, Bank::Attack, 0, tone(12_000, 262.0, 2)),
one(72, Bank::Attack, 0, tone(9_000, 523.0, 2)),
],
),
(
"a mono stroke cut mid-decay",
1,
vec![one(48, Bank::Attack, 0, tone(9_133, 440.0, 1))],
),
(
"a stroke that reaches silence before its source ends",
2,
vec![one(60, Bank::Attack, 0, trailing_silence(6_000, 5_000, 2))],
),
];
for (what, channels, recordings) in cases {
let piano = round_trip(channels, &recordings);
let library = piano.library().unwrap();
let again = rebuild(&library).unwrap();
assert_eq!(again.strokes.len(), recordings.len());
for (index, recoded) in again.strokes.iter().enumerate() {
assert_eq!(
(recoded.identical, recoded.recoded()),
(recoded.blocks, 0),
"{what}: stroke {index} came back with different blocks"
);
}
assert_eq!(
again.library.to_body().unwrap(),
piano.file.body.0,
"{what}: the library came back a different file"
);
}
}
#[test]
fn the_resampler_leaves_audio_already_on_the_lattice_alone() {
let interleaved: Vec<i16> = (0..64).map(|n| (n * 100 - 3000) as i16).collect();
let out = resample(&interleaved, 2, codec::RATE).unwrap();
assert_eq!(out.clipped, 0);
assert_eq!(out.channels[0][..3], [-3000, -2800, -2600]);
assert_eq!(out.channels[1][..3], [-2900, -2700, -2500]);
assert!(resample(&[1, 2, 3], 2, codec::RATE).is_err());
assert!(resample(&[1, 2], 1, 0).is_err());
}
#[test]
fn a_rate_that_stretches_a_source_past_a_strokes_frame_count_is_refused() {
let frames = u32::MAX as usize / codec::RATE as usize + 1;
let error = match resample(&vec![0i16; frames], 1, 1) {
Err(error) => error.to_string(),
Ok(_) => panic!("expected a refusal"),
};
assert!(error.contains("at 1 Hz"), "{error}");
assert!(error.contains("u32 frame count"), "{error}");
}
#[test]
fn resampling_a_faster_source_drops_what_the_lattice_cannot_hold() {
let rate = 96_000;
let tone_at = |hertz: f64| -> Vec<i16> {
(0..rate as usize / 4)
.map(|n| {
let t = n as f64 / f64::from(rate);
(8000.0 * (std::f64::consts::TAU * hertz * t).sin()) as i16
})
.collect()
};
let peak = |lane: &[i16]| {
lane[400..lane.len() - 400]
.iter()
.map(|&s| i32::from(s).abs())
.max()
.unwrap_or(0)
};
let above = resample(&tone_at(24_000.0), 1, rate).unwrap();
assert_eq!(above.clipped, 0);
let level = peak(&above.channels[0]);
assert!(level < 400, "a 24 kHz tone came through at {level} of 8000");
let inside = resample(&tone_at(1_000.0), 1, rate).unwrap();
let level = peak(&inside.channels[0]);
assert!(
level > 7_900,
"a 1 kHz tone came through at {level} of 8000"
);
}
#[test]
fn resampling_a_slower_source_stretches_it_onto_the_lattice() {
let rate = 22_050;
let frames = 4_000;
let source: Vec<i16> = (0..frames)
.map(|n| {
let t = n as f64 / f64::from(rate);
(8000.0 * (std::f64::consts::TAU * 100.0 * t).sin()) as i16
})
.collect();
let out = resample(&source, 1, rate).unwrap();
assert_eq!(
out.channels[0].len(),
frames * codec::RATE as usize / rate as usize
);
let crossings = |signal: &[i16]| {
signal
.windows(2)
.filter(|w| (w[0] < 0) != (w[1] < 0))
.count()
};
assert_eq!(
crossings(&out.channels[0][100..]),
crossings(&source[100..])
);
}
}