1pub struct ZoneAudio<'a> {
16 pub root_key: u8,
17 pub top_note: u8,
18 pub low_note: Option<u8>,
21 pub at: usize,
24 pub stream: &'a [u8],
25}
26
27pub mod codec;
28pub mod encode;
29pub mod kernel;
30pub mod keymap;
31pub mod meta;
32pub mod section;
33pub mod stroke;
34pub mod sty;
35pub mod zone;
36
37pub use keymap::{KeyTable, Level};
38pub use meta::Meta;
39pub use section::Section;
40pub use stroke::Stroke;
41pub use sty::{velocity_level, EqBand, Sty, StyV2, StyV3};
42pub use zone::Zone;
43pub use zone::ZoneV3;
44
45use crate::cbin::{self, BodyReader, BodyWriter, Cbin, Header};
46use crate::error::{Error, ParseError};
47use std::fmt;
48use std::io::{Read, Seek, Write};
49
50pub const FORMAT: &str = "nsmp";
51
52pub const V3_FROM_VERSION: u32 = 300;
57
58pub const V4_FROM_VERSION: u32 = 400;
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub enum Chain {
73 Early,
77 Library2,
80 Wide,
82}
83
84impl Chain {
85 pub fn from_map_version(version: u8) -> Result<Chain, ParseError> {
87 match version {
88 keymap::VERSION_EARLY => Ok(Chain::Early),
89 keymap::VERSION => Ok(Chain::Library2),
90 other => Err(ParseError::AssertFail(format!(
91 "map section version {other} has no zone table layout derived from a specimen"
92 ))),
93 }
94 }
95
96 pub const fn zone_record_len(self) -> usize {
100 match self {
101 Chain::Early => 12,
102 Chain::Library2 | Chain::Wide => 15,
103 }
104 }
105
106 pub const fn written_for(layout: codec::Layout) -> Chain {
109 match layout {
110 codec::Layout::V2 => Chain::Library2,
111 codec::Layout::V3 | codec::Layout::V4 => Chain::Wide,
112 }
113 }
114
115 pub const fn names_instrument(self) -> bool {
117 !matches!(self, Chain::Early)
118 }
119
120 pub const fn flags_the_marked_record(self) -> bool {
124 !matches!(self, Chain::Early)
125 }
126}
127
128#[derive(Debug)]
137pub enum AnyBody {
138 V2(Sample),
139 V3(SampleV3),
140}
141
142impl cbin::Body for AnyBody {
143 fn read<R: Read + Seek>(r: &mut BodyReader<'_, R>, header: &Header) -> Result<Self, Error> {
144 if header.version >= V3_FROM_VERSION {
145 Ok(AnyBody::V3(<SampleV3 as cbin::Body>::read(r, header)?))
146 } else {
147 Ok(AnyBody::V2(<Sample as cbin::Body>::read(r, header)?))
148 }
149 }
150
151 fn write<W: Write + Seek>(&self, w: &mut BodyWriter<'_, W>) -> Result<(), Error> {
152 match self {
153 AnyBody::V2(s) => <Sample as cbin::Body>::write(s, w),
154 AnyBody::V3(s) => <SampleV3 as cbin::Body>::write(s, w),
155 }
156 }
157}
158
159#[derive(Clone, Copy)]
169pub(super) struct StringField {
170 at: usize,
171 next: usize,
172}
173
174impl StringField {
175 pub(super) const NAME: StringField = StringField { at: 12, next: 44 };
177
178 pub(super) const NAME_V3: StringField = StringField { at: 10, next: 76 };
180
181 pub(super) const fn capacity(self) -> usize {
183 self.next - self.at - 1
184 }
185
186 fn read(self, payload: &[u8]) -> String {
192 let span = self.at.min(payload.len())..self.next.min(payload.len());
193 nul_terminated(&payload[span])
194 }
195
196 pub(super) fn write(self, payload: &mut [u8], value: &str) -> Result<(), Error> {
198 if value.len() > self.capacity() {
199 return Err(ParseError::OutOfBounds {
200 value: format!("{value:?} ({} bytes)", value.len()),
201 bound: format!("a name of at most {} bytes", self.capacity()),
202 }
203 .into());
204 }
205 let field = payload
206 .get_mut(self.at..self.next)
207 .ok_or_else(|| ParseError::AssertFail("hdr section holds no name field".into()))?;
208 field.fill(0);
209 field[..value.len()].copy_from_slice(value.as_bytes());
210 Ok(())
211 }
212}
213
214fn nul_terminated(bytes: &[u8]) -> String {
217 let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
218 String::from_utf8_lossy(&bytes[..end]).into_owned()
219}
220
221pub const MAX_NAME_LEN: usize = StringField::NAME.capacity();
223
224pub struct Sample {
230 pub sections: Vec<Section>,
231}
232
233impl cbin::Body for Sample {
234 fn read<R: Read + Seek>(r: &mut BodyReader<'_, R>, _: &Header) -> Result<Self, Error> {
235 let remaining = r.remaining();
236 Ok(Sample {
237 sections: section::read_chain(r, remaining)?,
238 })
239 }
240
241 fn write<W: Write + Seek>(&self, w: &mut BodyWriter<'_, W>) -> Result<(), Error> {
242 for s in &self.sections {
243 s.write_to(w)?;
244 }
245 Ok(())
246 }
247}
248
249pub fn read_from(reader: &mut (impl Read + Seek)) -> Result<Cbin<Sample>, Error> {
251 cbin::read(reader, FORMAT)
252}
253
254#[derive(Debug)]
266pub struct SampleV3 {
267 pub sections: Vec<section::Section4>,
268}
269
270impl cbin::Body for SampleV3 {
271 fn read<R: Read + Seek>(r: &mut BodyReader<'_, R>, _: &Header) -> Result<Self, Error> {
272 let remaining = r.remaining();
273 Ok(SampleV3 {
274 sections: section::read_chain4(r, remaining)?,
275 })
276 }
277
278 fn write<W: Write + Seek>(&self, w: &mut BodyWriter<'_, W>) -> Result<(), Error> {
279 for s in &self.sections {
280 s.write_to(w)?;
281 }
282 Ok(())
283 }
284}
285
286pub const MAX_NAME_V3_LEN: usize = StringField::NAME_V3.capacity();
290
291impl Cbin<SampleV3> {
292 fn hdr(&self) -> Result<§ion::Section4, Error> {
293 section::find4(&self.body.sections, section::HDR4)
294 .ok_or_else(|| ParseError::AssertFail("no hdr section".into()).into())
295 }
296
297 pub fn name(&self) -> Result<String, Error> {
299 Ok(StringField::NAME_V3.read(&self.hdr()?.payload))
300 }
301
302 pub fn sub_name(&self) -> Result<String, Error> {
308 let payload = &self.hdr()?.payload;
309 let from = StringField::NAME_V3.next.min(payload.len());
310 Ok(nul_terminated(&payload[from..]))
311 }
312
313 pub fn stroke_count(&self) -> usize {
315 self.body
316 .sections
317 .iter()
318 .filter(|s| s.is(section::STK4))
319 .count()
320 }
321
322 fn stroke_ids(&self) -> Result<Vec<(u32, u8)>, Error> {
326 self.body
327 .sections
328 .iter()
329 .filter(|s| s.is(section::STK4))
330 .map(|s| match (stroke_gid(s), s.payload.get(5)) {
331 (Some(gid), Some(&root)) => Ok((gid, root)),
332 _ => Err(ParseError::AssertFail(format!(
333 "stroke payload is {} bytes, too short for its id fields",
334 s.payload.len()
335 ))
336 .into()),
337 })
338 .collect()
339 }
340
341 pub fn zones(&self) -> Result<Vec<ZoneV3>, Error> {
345 let map = self.map()?;
346 Ok(zone::read_v3(
347 map.version,
348 &map.payload,
349 &self.stroke_ids()?,
350 )?)
351 }
352
353 fn map(&self) -> Result<§ion::Section4, Error> {
354 section::find4(&self.body.sections, section::MAP4)
355 .ok_or_else(|| ParseError::AssertFail("no map section".into()).into())
356 }
357
358 pub fn sty(&self) -> Result<Sty, Error> {
361 let s = section::find4(&self.body.sections, section::STY4)
362 .ok_or_else(|| ParseError::AssertFail("no sty section".into()))?;
363 Ok(Sty::parse_wide(s.version, &s.payload)?)
364 }
365
366 pub fn meta(&self) -> Result<Meta, Error> {
368 let s = section::find4(&self.body.sections, section::META4)
369 .ok_or_else(|| ParseError::AssertFail("no meta section".into()))?;
370 Ok(Meta::parse(s.version, &s.payload)?)
371 }
372
373 pub fn chain_len_before_meta(&self) -> usize {
375 self.body
376 .sections
377 .iter()
378 .take_while(|s| !s.is(section::META4))
379 .map(section::Section4::encoded_len)
380 .sum()
381 }
382
383 fn map_mut(&mut self) -> Result<&mut section::Section4, Error> {
384 section::find_mut4(&mut self.body.sections, section::MAP4)
385 .ok_or_else(|| ParseError::AssertFail("no map section".into()).into())
386 }
387
388 pub fn zone_table(&self) -> Result<zone::Table, Error> {
393 let map = self.map()?;
394 Ok(zone::Table::locate(
395 map.version,
396 &map.payload,
397 &self.stroke_ids()?,
398 )?)
399 }
400
401 pub fn set_name(&mut self, name: &str) -> Result<(), Error> {
404 let hdr = section::find_mut4(&mut self.body.sections, section::HDR4)
405 .ok_or_else(|| ParseError::AssertFail("no hdr section".into()))?;
406 StringField::NAME_V3.write(&mut hdr.payload, name)
407 }
408
409 pub fn zones_are_editable(&self) -> bool {
414 match (self.zone_table(), self.map(), self.zones()) {
415 (Ok(table), Ok(map), Ok(zones)) => table.validate_key_map(&map.payload, &zones).is_ok(),
416 _ => false,
417 }
418 }
419
420 fn edit_zone(&mut self, index: usize, field: zone::Field, note: u8) -> Result<(), Error> {
426 let table = self.zone_table()?;
427 let mut zones = self.zones()?;
428 let map = self.map()?;
429 table.validate_key_map(&map.payload, &zones)?;
430 let zone = zones
431 .get_mut(index)
432 .ok_or_else(|| ParseError::AssertFail(format!("no zone {index}")))?;
433 match field {
434 zone::Field::Root => zone.root_key = note,
435 zone::Field::Top => zone.top_note = note,
436 zone::Field::Low => zone.low_note = Some(note),
437 }
438 let plan = table.plan_key_map(&map.payload, &zones)?;
439 let map = self.map_mut()?;
440 table.set(&mut map.payload, index, field, note)?;
441 for (at, quad) in plan {
442 map.payload[at..at + quad.len()].copy_from_slice(&quad);
443 }
444 Ok(())
445 }
446
447 pub fn set_zone_top_note(&mut self, index: usize, note: u8) -> Result<(), Error> {
449 self.edit_zone(index, zone::Field::Top, note)
450 }
451
452 pub fn set_zone_low_note(&mut self, index: usize, note: u8) -> Result<(), Error> {
454 self.edit_zone(index, zone::Field::Low, note)
455 }
456
457 pub fn set_root_key(&mut self, index: usize, note: u8) -> Result<(), Error> {
463 let gid = self
464 .zones()?
465 .get(index)
466 .ok_or_else(|| ParseError::AssertFail(format!("no zone {index}")))?
467 .stroke_gid;
468 let at = self
471 .body
472 .sections
473 .iter()
474 .position(|s| s.is(section::STK4) && stroke_gid(s) == Some(gid))
475 .ok_or_else(|| {
476 ParseError::AssertFail(format!(
477 "zone {index} names stroke {gid}, which the file does not contain"
478 ))
479 })?;
480 self.edit_zone(index, zone::Field::Root, note)?;
481 stroke::set_root_key(&mut self.body.sections[at].payload, note)?;
482 Ok(())
483 }
484
485 pub fn stroke_streams(&self) -> Vec<(usize, &[u8])> {
493 let mut at = 0;
494 let mut out = Vec::new();
495 for section in &self.body.sections {
496 if section.is(section::STK4) {
497 out.push((at + section::HEADER4_LEN, section.payload.as_slice()));
498 }
499 at += section.encoded_len();
500 }
501 out
502 }
503
504 pub fn zone_stream(&self, index: usize) -> Result<(usize, &[u8]), Error> {
510 let zones = self.zones()?;
511 let zone = zones
512 .get(index)
513 .ok_or_else(|| ParseError::AssertFail(format!("no zone {index}")))?;
514 let mut at = 0;
515 for section in &self.body.sections {
516 if section.is(section::STK4) && stroke_gid(section) == Some(zone.stroke_gid) {
517 return Ok((at + section::HEADER4_LEN, section.payload.as_slice()));
518 }
519 at += section.encoded_len();
520 }
521 Err(ParseError::AssertFail(format!(
522 "zone {index} names stroke {}, which the file does not contain",
523 zone.stroke_gid
524 ))
525 .into())
526 }
527}
528
529pub fn from_bytes(bytes: &[u8]) -> Result<Cbin<Sample>, Error> {
530 read_from(&mut std::io::Cursor::new(bytes))
531}
532
533fn stroke_id(section: &Section) -> Option<u32> {
535 let b = section.payload.get(0..4)?;
536 Some(u32::from_be_bytes(b.try_into().ok()?))
537}
538
539fn stroke_gid(section: §ion::Section4) -> Option<u32> {
543 let b = section.payload.get(0..4)?;
544 Some(u32::from_be_bytes(b.try_into().ok()?))
545}
546
547fn names_stroke(id: u32, named: u8) -> bool {
553 id as u8 == named
554}
555
556impl Cbin<Sample> {
557 pub fn to_bytes(&self) -> Result<Vec<u8>, Error> {
559 let mut out = std::io::Cursor::new(Vec::new());
560 self.write_to(&mut out)?;
561 Ok(out.into_inner())
562 }
563
564 pub fn name(&self) -> Result<String, Error> {
573 Ok(StringField::NAME.read(&self.hdr()?.payload))
574 }
575
576 pub fn set_name(&mut self, name: &str) -> Result<(), Error> {
578 let hdr = section::find_mut(&mut self.body.sections, section::HDR)
579 .ok_or_else(|| ParseError::AssertFail("no hdr section".into()))?;
580 StringField::NAME.write(&mut hdr.payload, name)
581 }
582
583 pub fn chain(&self) -> Result<Chain, Error> {
587 Ok(Chain::from_map_version(self.map()?.version)?)
588 }
589
590 pub fn zones(&self) -> Result<Vec<Zone>, Error> {
592 Ok(zone::read(self.chain()?, &self.map()?.payload)?)
593 }
594
595 pub fn sty(&self) -> Result<StyV2, Error> {
597 let s = section::find(&self.body.sections, section::STY)
598 .ok_or_else(|| ParseError::AssertFail("no sty section".into()))?;
599 if s.version != sty::VERSION_V2 {
600 return Err(ParseError::AssertFail(format!(
601 "sty section version {} has no preset layout derived from a specimen",
602 s.version
603 ))
604 .into());
605 }
606 Ok(StyV2::parse(&s.payload)?)
607 }
608
609 pub fn set_zone_top_note(&mut self, index: usize, note: u8) -> Result<(), Error> {
611 let chain = self.chain()?;
612 let map = section::find_mut(&mut self.body.sections, section::MAP)
613 .ok_or_else(|| ParseError::AssertFail("no map section".into()))?;
614 zone::set_top_note(chain, &mut map.payload, index, note)?;
615 Ok(())
616 }
617
618 pub fn key_table(&self) -> Result<KeyTable, Error> {
621 self.chain()?;
624 Ok(KeyTable::read(&self.map()?.payload)?)
625 }
626
627 pub fn set_key_table(&mut self, table: &KeyTable) -> Result<(), Error> {
629 self.chain()?;
631 let map = section::find_mut(&mut self.body.sections, section::MAP)
632 .ok_or_else(|| ParseError::AssertFail("no map section".into()))?;
633 table.write(&mut map.payload)?;
634 Ok(())
635 }
636
637 pub fn strokes(&self) -> Result<Vec<Stroke>, Error> {
643 let zones = self.zones()?;
644 let by_id = self.strokes_in_file_order()?;
645 zones
646 .iter()
647 .map(|z| {
648 by_id
649 .iter()
650 .find(|(id, _)| names_stroke(*id, z.stroke_id))
651 .map(|(_, s)| *s)
652 .ok_or_else(|| {
653 ParseError::AssertFail(format!(
654 "zone reaching up to note {} names stroke {}, which the file \
655 does not contain",
656 z.top_note, z.stroke_id
657 ))
658 .into()
659 })
660 })
661 .collect()
662 }
663
664 pub fn stroke_streams(&self) -> Vec<(usize, &[u8])> {
671 let mut at = 0;
672 let mut out = Vec::new();
673 for section in &self.body.sections {
674 if section.is(section::STK) {
675 out.push((at + section::HEADER_LEN, section.payload.as_slice()));
676 }
677 at += section.encoded_len();
678 }
679 out
680 }
681
682 pub fn zone_stream(&self, index: usize) -> Result<(usize, &[u8]), Error> {
688 let zones = self.zones()?;
689 let zone = zones
690 .get(index)
691 .ok_or_else(|| ParseError::AssertFail(format!("no zone {index}")))?;
692 let wanted = zone.stroke_id;
693 let mut at = 0;
694 for section in &self.body.sections {
695 if section.is(section::STK)
696 && stroke_id(section).is_some_and(|id| names_stroke(id, wanted))
697 {
698 return Ok((at + section::HEADER_LEN, section.payload.as_slice()));
699 }
700 at += section.encoded_len();
701 }
702 Err(ParseError::AssertFail(format!(
703 "zone {index} names stroke {wanted}, which the file does not contain"
704 ))
705 .into())
706 }
707
708 fn strokes_in_file_order(&self) -> Result<Vec<(u32, Stroke)>, Error> {
713 let chain = self.chain()?;
717 let map_len = self.map()?.payload.len();
718 let cat_len =
719 section::find(&self.body.sections, section::CAT).map_or(0, |s| s.payload.len());
720 self.stroke_sections()
721 .enumerate()
722 .map(|(i, s)| {
723 let id = s
724 .payload
725 .get(0..4)
726 .map(|b| u32::from_be_bytes(b.try_into().unwrap()))
727 .ok_or_else(|| {
728 ParseError::AssertFail(format!(
729 "stroke {i} is {} bytes, too short for its id",
730 s.payload.len()
731 ))
732 })?;
733 Ok((id, stroke::read(&s.payload, chain, i, cat_len, map_len)?))
734 })
735 .collect()
736 }
737
738 pub fn set_root_key(&mut self, index: usize, note: u8) -> Result<(), Error> {
744 let zones = self.zones()?;
745 let zone = zones
746 .get(index)
747 .ok_or_else(|| ParseError::AssertFail(format!("no zone {index}")))?;
748 let wanted = zone.stroke_id;
749 let section = self
750 .body
751 .sections
752 .iter_mut()
753 .filter(|s| s.is(section::STK))
754 .find(|s| stroke_id(s).is_some_and(|id| names_stroke(id, wanted)))
755 .ok_or_else(|| {
756 ParseError::AssertFail(format!(
757 "zone {index} names stroke {wanted}, which the file does not contain"
758 ))
759 })?;
760 stroke::set_root_key(&mut section.payload, note)?;
761 Ok(())
762 }
763
764 pub fn categories(&self) -> Vec<String> {
766 let Some(cat) = section::find(&self.body.sections, section::CAT) else {
767 return Vec::new();
768 };
769 let mut out = Vec::new();
770 let mut i = 0;
771 while i < cat.payload.len() {
772 let len = cat.payload[i] as usize;
773 let from = i + 1;
774 match cat.payload.get(from..from + len) {
777 Some(s) if len > 0 && s.iter().all(|&b| (0x20..0x7f).contains(&b)) => {
778 out.push(String::from_utf8_lossy(s).into_owned());
779 i = from + len;
780 }
781 _ => i += 1,
782 }
783 }
784 out
785 }
786
787 fn stroke_sections(&self) -> impl Iterator<Item = &Section> {
788 self.body.sections.iter().filter(|s| s.is(section::STK))
789 }
790
791 fn hdr(&self) -> Result<&Section, Error> {
792 section::find(&self.body.sections, section::HDR)
793 .ok_or_else(|| ParseError::AssertFail("no hdr section".into()).into())
794 }
795
796 fn map(&self) -> Result<&Section, Error> {
797 section::find(&self.body.sections, section::MAP)
798 .ok_or_else(|| ParseError::AssertFail("no map section".into()).into())
799 }
800}
801
802impl fmt::Debug for Sample {
803 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
804 f.debug_struct("Sample")
805 .field("sections", &self.sections)
806 .finish()
807 }
808}
809
810#[cfg(test)]
811mod tests {
812 use super::*;
813
814 #[test]
817 fn the_map_version_selects_the_chain_and_an_unknown_one_refuses() {
818 assert_eq!(Chain::from_map_version(9).unwrap(), Chain::Early);
819 assert_eq!(Chain::from_map_version(10).unwrap(), Chain::Library2);
820 assert!(Chain::from_map_version(11).is_err());
821 }
822
823 #[test]
824 fn an_unknown_map_version_cannot_use_the_zone_setter() {
825 let crate::Sample::V2(mut sample) =
826 encode::instrument(&[0i16; encode::MIN_FRAMES], &encode::Options::new("Test")).unwrap()
827 else {
828 panic!("the default options build the narrow chain");
829 };
830 let map = section::find_mut(&mut sample.body.sections, section::MAP).unwrap();
831 map.version = keymap::VERSION + 1;
832 let before = map.payload.clone();
833 assert!(sample.zones().is_err());
834 assert!(sample.set_zone_top_note(0, 60).is_err());
835 assert_eq!(sample.map().unwrap().payload, before);
836 }
837
838 #[test]
839 fn an_unknown_map_version_cannot_use_the_keyboard_table() {
840 let crate::Sample::V2(mut sample) =
841 encode::instrument(&[0i16; encode::MIN_FRAMES], &encode::Options::new("Test")).unwrap()
842 else {
843 panic!("the default options build the narrow chain");
844 };
845 let map = section::find_mut(&mut sample.body.sections, section::MAP).unwrap();
846 map.version = keymap::VERSION + 1;
847 let before = map.payload.clone();
848 assert!(sample.key_table().is_err());
849 assert!(sample.set_key_table(&KeyTable::NEUTRAL).is_err());
850 assert_eq!(sample.map().unwrap().payload, before);
851 }
852
853 #[test]
854 fn a_name_field_holds_its_whole_span_less_the_terminator() {
855 assert_eq!(MAX_NAME_LEN, 31);
856 assert_eq!(MAX_NAME_V3_LEN, 65);
857 }
858
859 #[test]
860 fn a_rename_leaves_nothing_of_the_name_it_replaced() {
861 for field in [StringField::NAME, StringField::NAME_V3] {
862 let mut payload = vec![0u8; field.next];
863 let long = "M".repeat(field.capacity());
864 field.write(&mut payload, &long).unwrap();
865 field.write(&mut payload, "Short").unwrap();
866 assert_eq!(field.read(&payload), "Short");
867 assert!(payload[field.at + 5..field.next].iter().all(|&b| b == 0));
868 }
869 }
870
871 #[test]
872 fn a_name_one_byte_past_the_field_is_refused() {
873 let field = StringField::NAME;
874 let mut payload = vec![0xffu8; field.next + 8];
875 let error = field
876 .write(&mut payload, &"M".repeat(field.capacity() + 1))
877 .unwrap_err()
878 .to_string();
879 assert!(error.contains("at most 31 bytes"), "{error}");
880 assert!(payload[field.at..].iter().all(|&b| b == 0xff));
881 }
882
883 #[test]
884 fn a_name_filling_its_field_stops_at_the_field_that_follows() {
885 let field = StringField::NAME_V3;
886 let mut payload = vec![0u8; 112];
887 payload[field.next..field.next + 7].copy_from_slice(b"KG mono");
888 let long = "M".repeat(field.capacity());
889 field.write(&mut payload, &long).unwrap();
890 assert_eq!(field.read(&payload), long);
891 assert_eq!(nul_terminated(&payload[field.next..]), "KG mono");
892 }
893
894 #[test]
896 fn a_header_with_no_name_field_reads_back_empty_and_refuses_a_rename() {
897 assert_eq!(StringField::NAME.read(&[0u8; 18]), "");
898 assert_eq!(StringField::NAME.read(&[]), "");
899 assert!(StringField::NAME.write(&mut [0u8; 18], "Name").is_err());
900 }
901}