use alloc::string::String;
use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
use crate::core::fixed::units::Volume;
use crate::core::module::{ChannelDefault, Module, PatternHighlight};
use crate::tracker::period::FrequencyType;
use super::super::ChunkId;
pub const MHDR: ChunkId = ChunkId::new(b"MHDR");
pub const QRKS: ChunkId = ChunkId::new(b"QRKS");
pub const INST: ChunkId = ChunkId::new(b"INST");
pub const TRKS: ChunkId = ChunkId::new(b"TRKS");
pub const CLIP: ChunkId = ChunkId::new(b"CLIP");
pub const AUTO: ChunkId = ChunkId::new(b"AUTO");
pub const TMAP: ChunkId = ChunkId::new(b"TMAP");
pub const LOOP: ChunkId = ChunkId::new(b"LOOP");
pub const GRAF: ChunkId = ChunkId::new(b"GRAF");
pub const MIDI: ChunkId = ChunkId::new(b"MIDI");
pub const ASET: ChunkId = ChunkId::new(b"ASET");
pub const PCM: ChunkId = ChunkId::new(b"PCM ");
pub const ORGN: ChunkId = ChunkId::new(b"orgn");
pub const GENR: ChunkId = ChunkId::new(b"genr");
pub const HSTC: ChunkId = ChunkId::new(b"hstc");
pub const KNOWN: &[ChunkId] = &[
MHDR, QRKS, INST, TRKS, CLIP, AUTO, TMAP, LOOP, GRAF, MIDI, ASET, PCM, ORGN, GENR, HSTC,
];
pub fn min_reader_version(_module: &Module) -> u32 {
1
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct Provenance {
pub writer_name: String,
pub writer_version: String,
}
impl Provenance {
pub fn current() -> Self {
Self {
writer_name: env!("CARGO_PKG_NAME").into(),
writer_version: env!("CARGO_PKG_VERSION").into(),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct MHdr {
pub name: String,
pub comment: String,
pub frequency_type: FrequencyType,
pub default_tempo: u32,
pub default_bpm: u32,
pub pattern_highlight: PatternHighlight,
pub pitch_wheel_depth: u8,
pub mix_volume: Volume,
pub channel_names: Vec<String>,
pub channel_defaults: Vec<ChannelDefault>,
}
impl MHdr {
pub fn from_module(m: &Module) -> Self {
Self {
name: m.name.clone(),
comment: m.comment.clone(),
frequency_type: m.frequency_type,
default_tempo: m.default_tempo as u32,
default_bpm: m.default_bpm as u32,
pattern_highlight: m.pattern_highlight,
pitch_wheel_depth: m.pitch_wheel_depth,
mix_volume: m.mix_volume,
channel_names: m.channel_names.clone(),
channel_defaults: m.channel_defaults.clone(),
}
}
pub fn apply(self, m: &mut Module) {
m.name = self.name;
m.comment = self.comment;
m.frequency_type = self.frequency_type;
m.default_tempo = self.default_tempo as usize;
m.default_bpm = self.default_bpm as usize;
m.pattern_highlight = self.pattern_highlight;
m.pitch_wheel_depth = self.pitch_wheel_depth;
m.mix_volume = self.mix_volume;
m.channel_names = self.channel_names;
m.channel_defaults = self.channel_defaults;
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum Origin {
Mod,
S3m,
Xm,
It,
Sid,
Dw,
}
pub fn origin_of(m: &Module) -> Option<Origin> {
origin_bridge::read(m)
}
#[must_use]
pub fn apply_origin(origin: Origin, m: &mut Module) -> bool {
origin_bridge::write(origin, m)
}
#[cfg(any(
feature = "import_mod",
feature = "import_xm",
feature = "import_s3m",
feature = "import_it",
feature = "import_sid",
feature = "import_dw",
))]
mod origin_bridge {
use super::{Module, Origin};
use crate::tracker::format::ModuleFormat;
pub fn read(m: &Module) -> Option<Origin> {
match m.origin {
None | Some(ModuleFormat::Unknown) => None,
Some(ModuleFormat::Mod) => Some(Origin::Mod),
Some(ModuleFormat::S3m) => Some(Origin::S3m),
Some(ModuleFormat::Xm) => Some(Origin::Xm),
Some(ModuleFormat::It) => Some(Origin::It),
Some(ModuleFormat::Sid) => Some(Origin::Sid),
Some(ModuleFormat::Dw) => Some(Origin::Dw),
}
}
pub fn write(origin: Origin, m: &mut Module) -> bool {
m.origin = Some(match origin {
Origin::Mod => ModuleFormat::Mod,
Origin::S3m => ModuleFormat::S3m,
Origin::Xm => ModuleFormat::Xm,
Origin::It => ModuleFormat::It,
Origin::Sid => ModuleFormat::Sid,
Origin::Dw => ModuleFormat::Dw,
});
true
}
}
#[cfg(not(any(
feature = "import_mod",
feature = "import_xm",
feature = "import_s3m",
feature = "import_it",
feature = "import_sid",
feature = "import_dw",
)))]
mod origin_bridge {
use super::{Module, Origin};
pub fn read(_m: &Module) -> Option<Origin> {
None
}
pub fn write(_origin: Origin, _m: &mut Module) -> bool {
false
}
}
#[cfg(test)]
mod tests {
use super::super::{from_cbor, to_cbor};
use super::*;
use alloc::vec;
#[test]
fn registry_ids_well_cased() {
assert!(MHDR.is_critical() && PCM.is_critical());
assert!(ORGN.is_ancillary() && GENR.is_ancillary() && HSTC.is_ancillary());
assert!(ORGN.is_safe_to_copy() && GENR.is_safe_to_copy());
assert!(KNOWN.contains(&PCM) && KNOWN.contains(&ASET));
assert!(KNOWN.contains(&MHDR) && KNOWN.contains(&INST));
}
#[test]
fn mhdr_round_trips_through_cbor() {
let m = Module {
name: "hello".into(),
comment: "world".into(),
default_tempo: 6,
default_bpm: 125,
pitch_wheel_depth: 12,
channel_names: vec!["L".into(), "R".into()],
..Module::default()
};
let w = MHdr::from_module(&m);
let bytes = to_cbor(&w).unwrap();
let back: MHdr = from_cbor(&bytes).unwrap();
assert_eq!(w, back);
let mut m2 = Module::default();
back.apply(&mut m2);
assert_eq!(m2.name, "hello");
assert_eq!(m2.comment, "world");
assert_eq!(m2.default_tempo, 6);
assert_eq!(m2.default_bpm, 125);
assert_eq!(m2.pitch_wheel_depth, 12);
assert_eq!(m2.channel_names, vec!["L".to_string(), "R".to_string()]);
}
#[test]
fn provenance_carries_this_crate() {
let p = Provenance::current();
assert_eq!(p.writer_name, "xmrs");
assert!(!p.writer_version.is_empty());
let bytes = to_cbor(&p).unwrap();
assert_eq!(from_cbor::<Provenance>(&bytes).unwrap(), p);
}
#[test]
fn v1_min_reader_is_one() {
assert_eq!(min_reader_version(&Module::default()), 1);
}
#[test]
fn origin_enum_round_trips_through_cbor() {
for o in [
Origin::Mod,
Origin::S3m,
Origin::Xm,
Origin::It,
Origin::Sid,
Origin::Dw,
] {
let bytes = to_cbor(&o).unwrap();
assert_eq!(from_cbor::<Origin>(&bytes).unwrap(), o);
}
}
#[cfg(any(
feature = "import_mod",
feature = "import_xm",
feature = "import_s3m",
feature = "import_it",
feature = "import_sid",
feature = "import_dw",
))]
#[test]
fn origin_bridge_round_trips_with_importers() {
let mut m = Module::default();
assert_eq!(origin_of(&m), None); assert!(
apply_origin(Origin::It, &mut m),
"with an importer compiled in, the field exists and takes the value"
);
assert_eq!(origin_of(&m), Some(Origin::It));
}
}