use alloc::vec::Vec;
use crate::core::daw::device::{Bus, DeviceChain, Send};
use crate::core::daw::loop_region::ChannelLoop;
use crate::core::daw::track::{AudioSource, Track};
use crate::core::instrument::{Instrument, InstrumentType};
use crate::core::module::Module;
use crate::core::sample::Sample;
use super::container::{self, Writer};
use super::pcm::{AssetSampleRef, InstSampleRef, PcmRegion, PcmResolver, TrackSampleRef};
use super::wire::v1;
use super::wire::{from_cbor, to_cbor};
use super::{FormatError, SCHEMA_VERSION};
#[derive(Debug, Default, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct LoadReport {
pub retained: Vec<OpaqueChunk>,
pub dropped: Vec<[u8; 4]>,
pub inconsistency: Option<crate::core::module::LayerInconsistency>,
}
impl LoadReport {
pub fn is_clean(&self) -> bool {
self.retained.is_empty() && self.dropped.is_empty() && self.inconsistency.is_none()
}
pub fn rewrite_is_lossless(&self) -> bool {
self.dropped.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OpaqueChunk {
pub id: crate::format::ChunkId,
pub payload: Vec<u8>,
}
pub fn write(module: &Module) -> Result<Vec<u8>, FormatError> {
write_preserving(module, &LoadReport::default())
}
pub fn write_preserving(module: &Module, report: &LoadReport) -> Result<Vec<u8>, FormatError> {
let mut w = Writer::new(SCHEMA_VERSION, v1::min_reader_version(module));
w.push(v1::MHDR, to_cbor(&v1::MHdr::from_module(module))?)?;
if module.quirks != Default::default() {
w.push(v1::QRKS, to_cbor(&module.quirks)?)?;
}
let mut region = PcmRegion::default();
if !module.instrument.is_empty() {
let mut instruments = module.instrument.clone();
let refs = strip_instrument_pcm(&mut instruments, &mut region);
w.push(v1::INST, to_cbor(&(&instruments, &refs))?)?;
}
if !module.tracks.is_empty() {
let mut tracks = module.tracks.clone();
let refs = strip_track_pcm(&mut tracks, &mut region);
w.push(v1::TRKS, to_cbor(&(&tracks, &refs))?)?;
}
if !module.assets.is_empty() {
let mut assets = module.assets.clone();
let refs = strip_asset_pcm(&mut assets, &mut region);
w.push(v1::ASET, to_cbor(&(&assets, &refs))?)?;
}
if !module.clips.is_empty() {
w.push(v1::CLIP, to_cbor(&module.clips)?)?;
}
if !module.automation.is_empty() {
w.push(v1::AUTO, to_cbor(&module.automation)?)?;
}
if !module.timeline_map.entries.is_empty() {
w.push(v1::TMAP, to_cbor(&module.timeline_map)?)?;
}
if module.song_loop_to.is_some() || !module.channel_loops.is_empty() {
w.push(
v1::LOOP,
to_cbor(&(module.song_loop_to, &module.channel_loops))?,
)?;
}
if !module.channel_inserts.is_empty()
|| !module.buses.is_empty()
|| !module.channel_sends.is_empty()
|| !module.master_chain.devices.is_empty()
{
let graf = (
&module.channel_inserts,
&module.buses,
&module.channel_sends,
&module.master_chain,
);
w.push(v1::GRAF, to_cbor(&graf)?)?;
}
if let Some(mm) = &module.midi_macros {
w.push(v1::MIDI, to_cbor(mm)?)?;
}
if let Some(origin) = v1::origin_of(module) {
w.push(v1::ORGN, to_cbor(&origin)?)?;
}
if !region.is_empty() {
w.push(v1::PCM, region.into_bytes())?;
}
for c in &report.retained {
w.push(c.id, c.payload.clone())?;
}
w.push(v1::GENR, to_cbor(&v1::Provenance::current())?)?;
Ok(w.into_bytes())
}
fn strip_instrument_pcm(
instruments: &mut [Instrument],
region: &mut PcmRegion,
) -> Vec<InstSampleRef> {
let mut refs = Vec::new();
for (i, instr) in instruments.iter_mut().enumerate() {
if let InstrumentType::Default(d) = &mut instr.instr_type {
for (j, slot) in d.sample.iter_mut().enumerate() {
if let Some(sample) = slot {
if let Some(data) = sample.data.take() {
refs.push(InstSampleRef {
instrument: i as u32,
sample: j as u32,
pcm: region.intern(&data),
});
}
}
}
}
}
refs
}
fn strip_track_pcm(tracks: &mut [Track], region: &mut PcmRegion) -> Vec<TrackSampleRef> {
let mut refs = Vec::new();
for (t, track) in tracks.iter_mut().enumerate() {
if let Track::Audio {
source: AudioSource::Inline(sample),
..
} = track
{
if let Some(data) = sample.data.take() {
refs.push(TrackSampleRef {
track: t as u32,
pcm: region.intern(&data),
});
}
}
}
refs
}
fn strip_asset_pcm(assets: &mut [Sample], region: &mut PcmRegion) -> Vec<AssetSampleRef> {
let mut refs = Vec::new();
for (k, sample) in assets.iter_mut().enumerate() {
if let Some(data) = sample.data.take() {
refs.push(AssetSampleRef {
asset: k as u32,
pcm: region.intern(&data),
});
}
}
refs
}
pub fn read(bytes: &[u8]) -> Result<(Module, LoadReport), FormatError> {
let container = container::read(bytes)?;
container.require_known(v1::KNOWN)?;
let mut m = Module::default();
let mut report = LoadReport::default();
let mut inst_refs: Vec<InstSampleRef> = Vec::new();
let mut track_refs: Vec<TrackSampleRef> = Vec::new();
let mut asset_refs: Vec<AssetSampleRef> = Vec::new();
let mut pcm_region: &[u8] = &[];
for chunk in &container.chunks {
let id = chunk.id;
let p = chunk.payload;
if id == v1::MHDR {
from_cbor::<v1::MHdr>(p)?.apply(&mut m);
} else if id == v1::QRKS {
m.quirks = from_cbor(p)?;
} else if id == v1::INST {
let (instruments, refs): (Vec<Instrument>, Vec<InstSampleRef>) = from_cbor(p)?;
m.instrument = instruments;
inst_refs = refs;
} else if id == v1::TRKS {
let (tracks, refs): (Vec<Track>, Vec<TrackSampleRef>) = from_cbor(p)?;
m.tracks = tracks;
track_refs = refs;
} else if id == v1::ASET {
let (assets, refs): (Vec<Sample>, Vec<AssetSampleRef>) = from_cbor(p)?;
m.assets = assets;
asset_refs = refs;
} else if id == v1::PCM {
pcm_region = p;
} else if id == v1::CLIP {
m.clips = from_cbor(p)?;
} else if id == v1::AUTO {
m.automation = from_cbor(p)?;
} else if id == v1::TMAP {
m.timeline_map = from_cbor(p)?;
} else if id == v1::LOOP {
let (song_loop_to, channel_loops): (Option<u32>, Vec<ChannelLoop>) = from_cbor(p)?;
m.song_loop_to = song_loop_to;
m.channel_loops = channel_loops;
} else if id == v1::GRAF {
let (inserts, buses, sends, master): (
Vec<DeviceChain>,
Vec<Bus>,
Vec<Vec<Send>>,
DeviceChain,
) = from_cbor(p)?;
m.channel_inserts = inserts;
m.buses = buses;
m.channel_sends = sends;
m.master_chain = master;
} else if id == v1::MIDI {
m.midi_macros = Some(from_cbor(p)?);
} else if id == v1::ORGN {
if !v1::apply_origin(from_cbor(p)?, &mut m) {
report.retained.push(OpaqueChunk {
id,
payload: p.to_vec(),
});
}
} else if id == v1::GENR {
} else if id.is_ancillary() {
if id.is_safe_to_copy() {
report.retained.push(OpaqueChunk {
id,
payload: p.to_vec(),
});
} else {
report.dropped.push(id.0);
}
} else {
return Err(FormatError::UnknownCritical(id.0));
}
}
reattach_pcm(&mut m, pcm_region, &inst_refs, &track_refs, &asset_refs)?;
report.inconsistency = m.verify_layers_consistent().err();
Ok((m, report))
}
fn reattach_pcm(
m: &mut Module,
region: &[u8],
inst_refs: &[InstSampleRef],
track_refs: &[TrackSampleRef],
asset_refs: &[AssetSampleRef],
) -> Result<(), FormatError> {
let mut res = PcmResolver::new(region);
for r in inst_refs {
let data = res.resolve(r.pcm)?;
let slot = m
.instrument
.get_mut(r.instrument as usize)
.and_then(|instr| match &mut instr.instr_type {
InstrumentType::Default(d) => d.sample.get_mut(r.sample as usize),
_ => None,
})
.and_then(|slot| slot.as_mut())
.ok_or(FormatError::Pcm("instrument sample ref has no target"))?;
slot.data = Some(data);
}
for r in track_refs {
let data = res.resolve(r.pcm)?;
match m.tracks.get_mut(r.track as usize) {
Some(Track::Audio {
source: AudioSource::Inline(sample),
..
}) => sample.data = Some(data),
_ => return Err(FormatError::Pcm("track sample ref has no target")),
}
}
for r in asset_refs {
let data = res.resolve(r.pcm)?;
let sample = m
.assets
.get_mut(r.asset as usize)
.ok_or(FormatError::Pcm("asset sample ref has no target"))?;
sample.data = Some(data);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_module_round_trips_byte_stable() {
let m = Module::default();
let bytes1 = write(&m).unwrap();
let (m2, report) = read(&bytes1).unwrap();
assert!(report.is_clean());
let bytes2 = write(&m2).unwrap();
assert_eq!(bytes1, bytes2, "load/save must be byte-stable");
}
#[test]
fn structural_fields_survive_round_trip() {
let m = Module {
name: "song".into(),
comment: "notes".into(),
default_bpm: 140,
channel_names: alloc::vec!["kick".into(), "snare".into()],
..Module::default()
};
let (m2, _) = read(&write(&m).unwrap()).unwrap();
assert_eq!(m2.name, "song");
assert_eq!(m2.comment, "notes");
assert_eq!(m2.default_bpm, 140);
assert_eq!(m2.channel_names.len(), 2);
}
#[test]
fn every_instrument_kind_survives_a_round_trip() {
use crate::core::instr_midi::InstrMidi;
use crate::core::instr_opl::InstrOpl;
use crate::core::instr_robsid::{InstrRobSid, WaveShape};
use crate::core::instr_sid::InstrSid;
let midi = InstrMidi {
channel: 9,
program: 42,
..Default::default()
};
let opl = InstrOpl {
volume: 61,
relative_pitch: -12,
..Default::default()
};
let sid = InstrSid {
fc: 1234,
band_pass: true,
..Default::default()
};
let mut robsid = InstrRobSid::default();
robsid.fx.two_phase.attack_shape = WaveShape {
noise: true,
gate: true,
..Default::default()
};
robsid.fx.two_phase.attack_frames = 4;
robsid.fx.filter.mode.band_pass = true;
robsid.fx.filter.routing.voice2 = true;
use crate::core::fixed::units::{ChannelVolume, Panning, Volume};
use crate::core::sample::{LoopType, Sample, SampleDataType};
const WAVE: [i8; 6] = [0, 40, -40, 127, -128, 7];
let pcm = crate::core::instr_default::InstrDefault {
sample: alloc::vec![Some(Sample {
name: "wave".into(),
relative_pitch: 0,
finetune: crate::core::fixed::units::Finetune::ZERO,
volume: ChannelVolume::FULL,
default_note_volume: Volume::FULL,
panning: Panning::CENTER,
loop_flag: LoopType::No,
loop_start: 0,
loop_length: 0,
sustain_loop_flag: LoopType::No,
sustain_loop_start: 0,
sustain_loop_length: 0,
data: Some(SampleDataType::Mono8(WAVE.to_vec().into())),
})],
..Default::default()
};
let kinds = [
InstrumentType::Empty,
InstrumentType::Default(pcm),
InstrumentType::Midi(midi),
InstrumentType::Opl(opl),
InstrumentType::Sid(sid),
InstrumentType::RobSid(robsid),
];
let m = Module {
instrument: kinds
.into_iter()
.enumerate()
.map(|(i, t)| Instrument {
name: alloc::format!("i{i}"),
instr_type: t,
..Default::default()
})
.collect(),
..Module::default()
};
let bytes = write(&m).expect("write");
let (m2, report) = read(&bytes).expect("read");
assert!(report.is_clean());
assert_eq!(m2.instrument.len(), 6);
assert_eq!(write(&m2).expect("rewrite"), bytes, "byte-stable");
assert!(matches!(m2.instrument[0].instr_type, InstrumentType::Empty));
assert!(
super::super::container::read(&bytes)
.unwrap()
.chunk(super::super::wire::v1::PCM)
.is_some(),
"wave data belongs in the PCM region, not inline in INST"
);
match &m2.instrument[1].instr_type {
InstrumentType::Default(i) => {
let s = i.sample[0].as_ref().expect("the sample survived");
assert_eq!(s.name, "wave");
match s.data.as_ref().expect("its PCM survived") {
SampleDataType::Mono8(v) => assert_eq!(&v[..], &WAVE[..]),
other => panic!("PCM came back as {other:?}"),
}
}
other => panic!("sample instrument became {other:?}"),
}
match &m2.instrument[2].instr_type {
InstrumentType::Midi(i) => assert_eq!((i.channel, i.program), (9, 42)),
other => panic!("midi became {other:?}"),
}
match &m2.instrument[3].instr_type {
InstrumentType::Opl(i) => assert_eq!((i.volume, i.relative_pitch), (61, -12)),
other => panic!("opl became {other:?}"),
}
match &m2.instrument[4].instr_type {
InstrumentType::Sid(i) => assert_eq!((i.fc, i.band_pass), (1234, true)),
other => panic!("sid became {other:?}"),
}
match &m2.instrument[5].instr_type {
InstrumentType::RobSid(i) => {
let a = i.fx.two_phase.attack_shape;
assert!(a.noise && a.gate && !a.is_silent());
assert_eq!(i.fx.two_phase.attack_frames, 4);
assert!(i.fx.filter.mode.band_pass && i.fx.filter.routing.voice2);
}
other => panic!("robsid became {other:?}"),
}
}
#[test]
fn read_reports_cross_chunk_inconsistency() {
use crate::core::daw::clip::Clip;
use crate::core::daw::sorted_clips::SortedClips;
let m = Module {
clips: SortedClips::from_unsorted(alloc::vec![Clip {
track: 99,
song: 0,
target_channel: 0,
position_tick: 0,
speed_at_start: 6,
track_row_offset: 0,
source_start_row: 0,
end_tick: 64,
}]),
..Module::default()
};
let (m2, report) = read(&write(&m).expect("write")).expect("read");
assert!(
report.retained.is_empty() && report.dropped.is_empty(),
"structurally the file is impeccable"
);
assert!(
matches!(
report.inconsistency,
Some(
crate::core::module::LayerInconsistency::ClipTrackOutOfRange { track: 99, .. }
)
),
"…and semantically it is not, which the report says: {:?}",
report.inconsistency
);
assert!(!report.is_clean());
assert_eq!(m2.clips.len(), 1);
}
fn file_from_the_future(m: &Module) -> Vec<u8> {
use super::super::container;
let raw = write(m).expect("write");
let ours = container::read(&raw).expect("read back");
let mut w = container::Writer::new(ours.header.schema_version, 1);
for c in &ours.chunks {
w.push(c.id, c.payload.to_vec()).unwrap();
}
w.push(
crate::format::ChunkId::new(b"xtra"),
alloc::vec![1, 2, 3, 4],
)
.unwrap();
w.push(crate::format::ChunkId::new(b"xtrA"), alloc::vec![9, 9])
.unwrap();
w.into_bytes()
}
#[test]
fn unknown_safe_to_copy_chunks_survive_a_rewrite() {
let m = Module {
name: "future".into(),
..Module::default()
};
let bytes = file_from_the_future(&m);
let (m2, report) = read(&bytes).expect("an unknown ancillary chunk is not fatal");
assert_eq!(report.retained.len(), 1);
assert_eq!(report.retained[0].id.0, *b"xtra");
assert_eq!(report.retained[0].payload, alloc::vec![1, 2, 3, 4]);
assert_eq!(report.dropped, alloc::vec![*b"xtrA"]);
assert!(!report.is_clean());
assert!(!report.rewrite_is_lossless());
let saved = write_preserving(&m2, &report).expect("rewrite");
let (_, report2) = read(&saved).expect("read back");
assert_eq!(
report2.retained, report.retained,
"the opaque payload came through byte for byte"
);
assert!(
report2.dropped.is_empty(),
"the not-safe-to-copy chunk is gone, as its own id demanded"
);
let (_, report3) = read(&write(&m2).expect("write")).expect("read back");
assert!(report3.is_clean(), "nothing unknown left — nothing kept");
}
fn file_with_orgn(origin: super::super::wire::v1::Origin) -> Vec<u8> {
use super::super::container;
let raw = write(&Module::default()).expect("write");
let base = container::read(&raw).expect("read back");
let mut w = container::Writer::new(base.header.schema_version, 1);
for c in &base.chunks {
if c.id == v1::ORGN {
continue; }
w.push(c.id, c.payload.to_vec()).unwrap();
}
w.push(v1::ORGN, to_cbor(&origin).unwrap()).unwrap();
w.into_bytes()
}
#[cfg(not(any(
feature = "import_mod",
feature = "import_xm",
feature = "import_s3m",
feature = "import_it",
feature = "import_sid",
feature = "import_dw",
)))]
#[test]
fn orgn_is_carried_when_this_build_cannot_store_it() {
use super::super::container;
let bytes = file_with_orgn(super::super::wire::v1::Origin::It);
let (m, report) = read(&bytes).expect("read");
assert_eq!(report.retained.len(), 1, "orgn must be carried, not lost");
assert_eq!(report.retained[0].id, v1::ORGN);
assert!(
!report.is_clean(),
"a chunk we could not apply is a reservation"
);
assert!(
report.rewrite_is_lossless(),
"carried, so saving loses nothing"
);
let saved = write_preserving(&m, &report).expect("rewrite");
let c = container::read(&saved).expect("read back");
assert_eq!(
c.chunk(v1::ORGN).map(|c| c.payload.to_vec()),
Some(to_cbor(&super::super::wire::v1::Origin::It).unwrap()),
"the provenance came through byte for byte"
);
let plain_bytes = write(&m).expect("write");
let plain = container::read(&plain_bytes).expect("read back");
assert!(plain.chunk(v1::ORGN).is_none());
}
#[cfg(any(
feature = "import_mod",
feature = "import_xm",
feature = "import_s3m",
feature = "import_it",
feature = "import_sid",
feature = "import_dw",
))]
#[test]
fn orgn_is_applied_not_carried_when_the_field_exists() {
use super::super::container;
let bytes = file_with_orgn(super::super::wire::v1::Origin::It);
let (m, report) = read(&bytes).expect("read");
assert!(
report.retained.is_empty(),
"applied to the model, so nothing to carry"
);
assert!(report.is_clean());
assert_eq!(m.origin, Some(crate::tracker::format::ModuleFormat::It));
for saved in [
write(&m).expect("write"),
write_preserving(&m, &report).expect("rewrite"),
] {
let c = container::read(&saved).expect("read back");
assert_eq!(
c.chunks.iter().filter(|c| c.id == v1::ORGN).count(),
1,
"exactly one provenance chunk"
);
}
}
#[cfg(feature = "import_xm")]
#[test]
fn imported_xm_round_trips_end_to_end() {
use super::super::container;
use super::super::wire::v1;
let data = include_bytes!("../../examples/note.xm");
let m = Module::load_xm(data).expect("load note.xm");
let bytes1 = write(&m).expect("write");
let c = container::read(&bytes1).unwrap();
assert!(
c.chunk(v1::PCM).is_some(),
"note.xm carries sample data → a separate PCM chunk"
);
let (m2, report) = read(&bytes1).expect("read");
assert!(report.is_clean());
let bytes2 = write(&m2).expect("rewrite");
assert_eq!(bytes1, bytes2, "round-trip must be byte-stable");
assert_eq!(m.name, m2.name);
assert_eq!(m.instrument.len(), m2.instrument.len());
assert_eq!(m.tracks.len(), m2.tracks.len());
assert_eq!(m.timeline_map.entries.len(), m2.timeline_map.entries.len());
}
}