1pub mod accept;
31pub mod bank;
32pub mod bits;
33pub mod cbin;
34pub mod components;
35pub mod crc;
36pub mod error;
37pub mod fields;
38pub mod formats;
39pub mod layout;
40pub mod note;
41pub mod panel;
42pub mod types;
43pub mod util;
44pub mod wav;
45
46use crate::cbin::{Cbin, RawBody};
47use crate::formats::{
48 cn3, midi, nc2, nc2d, nd2, nd3, ne3, ne4, ne5, ne6, ne7, ng2, nl4, nla1, no3, np, np2, np3,
49 np4, np5, npip, npno, ns2, ns3, ns4, nsclassic, nsmp, nsmpproj, nw, nw2, sysex,
50};
51use std::fs::File;
52use std::io::{BufReader, Read, Seek};
53use std::path::Path;
54use util::{peek, FileType};
55
56use crate::error::{Error, ParseError};
57
58#[cfg(feature = "bundle")]
60#[derive(Debug)]
61pub enum Bundle {
62 Drum2Bank(nd2::bank::Bank),
63 Drum3KitBank(nd3::kit_bank::KitBank),
64 Electro5(ne5::Bundle),
65 Members(Vec<(String, Cbin<RawBody>)>),
70}
71
72#[allow(clippy::large_enum_variant)]
77#[derive(Debug)]
78pub enum Program {
79 C2(Cbin<RawBody>),
80 C2D(Cbin<RawBody>),
81 Drum2(Cbin<RawBody>),
83 Drum3(Cbin<RawBody>),
85 Electro3(Cbin<RawBody>),
87 Electro4(Cbin<RawBody>),
89 Electro5(Cbin<ne5::Program>),
90 Electro6(Cbin<RawBody>),
91 Electro7(Cbin<RawBody>),
92 Grand(Cbin<RawBody>),
93 Lead4(Cbin<RawBody>),
94 LeadA1(Cbin<RawBody>),
95 Organ3(Cbin<RawBody>),
96 Piano1(Cbin<RawBody>),
97 Piano2(Cbin<RawBody>),
98 Piano3(Cbin<RawBody>),
99 Piano4(Cbin<RawBody>),
100 Piano5(Cbin<RawBody>),
101 Stage2(Cbin<ns2::Program>),
103 Stage3(Cbin<ns3::Program>),
104 Stage4(Cbin<ns4::Program>),
105 StageClassic(Cbin<RawBody>),
107 Wave(Cbin<RawBody>),
108 Wave2(Cbin<RawBody>),
109}
110
111#[allow(clippy::large_enum_variant)]
116#[derive(Debug)]
117pub enum Live {
118 Electro4(Cbin<RawBody>),
119 Electro5(Cbin<ne5::Program>),
120 Electro6(Cbin<RawBody>),
121 Electro7(Cbin<RawBody>),
122 Grand(Cbin<RawBody>),
123 Piano1(Cbin<RawBody>),
124 Piano2(Cbin<RawBody>),
125 Piano3(Cbin<RawBody>),
126 Piano4(Cbin<RawBody>),
127 Piano5(Cbin<RawBody>),
128 Stage2(Cbin<ns2::Program>),
129 Stage3(Cbin<ns3::Program>),
130 Stage4(Cbin<ns4::Program>),
131 Wave2(Cbin<RawBody>),
132}
133
134#[derive(Debug)]
137pub enum Song {
138 Electro5(Cbin<ne5::Song>),
139 Stage3(Cbin<RawBody>),
140}
141
142#[derive(Debug)]
145pub enum Settings {
146 C2(Cbin<RawBody>),
147 C2D(Cbin<RawBody>),
148 Electro4(Cbin<RawBody>),
149 Electro5(Cbin<ne5::Settings>),
150 Electro6(Cbin<RawBody>),
151 Electro7(Cbin<RawBody>),
152 Grand(Cbin<RawBody>),
153 Lead4(Cbin<RawBody>),
154 LeadA1(Cbin<RawBody>),
155 Organ3(Cbin<RawBody>),
156 Piano1(Cbin<RawBody>),
157 Piano2(Cbin<RawBody>),
158 Piano3(Cbin<RawBody>),
159 Piano4(Cbin<RawBody>),
160 Piano5(Cbin<RawBody>),
161 Stage2(Cbin<RawBody>),
162 Stage3(Cbin<RawBody>),
163 Stage4(Cbin<RawBody>),
164 Wave(Cbin<RawBody>),
165 Wave2(Cbin<RawBody>),
166}
167
168#[allow(clippy::large_enum_variant)]
173#[derive(Debug)]
174pub enum Synth {
175 Stage2(Cbin<RawBody>),
176 Stage3(Cbin<ns3::SynthPreset>),
177 Stage4(Cbin<ns4::synth::SynthPreset>),
178 StageClassic(Cbin<RawBody>),
179}
180
181#[derive(Debug)]
183pub enum Performance {
184 Lead4(Cbin<RawBody>),
185 LeadA1(Cbin<RawBody>),
186}
187
188#[allow(clippy::large_enum_variant)]
192#[derive(Debug)]
193pub enum OrganPreset {
194 Electro3(Cbin<RawBody>),
196 Stage4(Cbin<ns4::organ_preset::OrganPreset>),
198}
199
200#[derive(Debug)]
202pub enum PianoPreset {
203 Stage4(Cbin<ns4::piano_preset::PianoPreset>),
205}
206
207#[derive(Debug)]
210pub enum Sample {
211 V2(Cbin<nsmp::Sample>),
212 V3(Cbin<nsmp::SampleV3>),
215}
216
217impl Sample {
218 pub fn name(&self) -> Result<String, Error> {
219 match self {
220 Sample::V2(s) => s.name(),
221 Sample::V3(s) => s.name(),
222 }
223 }
224
225 pub fn max_name_len(&self) -> usize {
227 match self {
228 Sample::V2(_) => nsmp::MAX_NAME_LEN,
229 Sample::V3(_) => nsmp::MAX_NAME_V3_LEN,
230 }
231 }
232
233 pub fn set_name(&mut self, name: &str) -> Result<(), Error> {
234 match self {
235 Sample::V2(s) => s.set_name(name),
236 Sample::V3(s) => s.set_name(name),
237 }
238 }
239
240 pub fn set_root_key(&mut self, index: usize, note: u8) -> Result<(), Error> {
242 match self {
243 Sample::V2(s) => s.set_root_key(index, note),
244 Sample::V3(s) => s.set_root_key(index, note),
245 }
246 }
247
248 pub fn set_zone_top_note(&mut self, index: usize, note: u8) -> Result<(), Error> {
249 match self {
250 Sample::V2(s) => s.set_zone_top_note(index, note),
251 Sample::V3(s) => s.set_zone_top_note(index, note),
252 }
253 }
254
255 pub fn has_low_note(&self) -> bool {
261 matches!(self, Sample::V3(_))
262 }
263
264 pub fn set_zone_low_note(&mut self, index: usize, note: u8) -> Result<(), Error> {
267 match self {
268 Sample::V2(_) => Err(ParseError::AssertFail("v2 stores no low note".into()).into()),
269 Sample::V3(s) => s.set_zone_low_note(index, note),
270 }
271 }
272
273 pub fn zones_are_editable(&self) -> bool {
280 match self {
281 Sample::V2(s) => s.zones().is_ok() && s.strokes().is_ok(),
282 Sample::V3(s) => s.zones_are_editable(),
283 }
284 }
285
286 pub fn chain(&self) -> Result<nsmp::Chain, Error> {
289 match self {
290 Sample::V2(s) => s.chain(),
291 Sample::V3(_) => Ok(nsmp::Chain::Wide),
292 }
293 }
294
295 pub fn layout(&self) -> Result<nsmp::codec::Layout, Error> {
298 match self {
299 Sample::V2(_) => Ok(nsmp::codec::Layout::V2),
300 Sample::V3(s) => nsmp::codec::Layout::from_version(s.header.version).ok_or_else(|| {
301 ParseError::OutOfBounds {
302 value: format!("content version {}", s.header.version),
303 bound: format!(
304 "the generations this codec describes, below {}",
305 nsmp::codec::V5_FROM_VERSION
306 ),
307 }
308 .into()
309 }),
310 }
311 }
312
313 pub fn generation(&self) -> &'static str {
316 match self {
317 Sample::V2(_) => "v2",
318 Sample::V3(s) if s.header.version >= nsmp::V4_FROM_VERSION => "v4",
319 Sample::V3(_) => "v3",
320 }
321 }
322
323 pub fn zones(&self) -> Result<Vec<nsmp::ZoneAudio<'_>>, Error> {
328 match self {
329 Sample::V2(s) => {
330 let zones = s.zones()?;
331 let strokes = s.strokes()?;
332 zones
333 .iter()
334 .zip(&strokes)
335 .enumerate()
336 .map(|(i, (zone, stroke))| {
337 let (at, stream) = s.zone_stream(i)?;
338 Ok(nsmp::ZoneAudio {
339 root_key: stroke.root_key,
340 top_note: zone.top_note,
341 low_note: None,
342 at,
343 stream,
344 })
345 })
346 .collect()
347 }
348 Sample::V3(s) => {
349 let zones = s.zones()?;
350 zones
351 .iter()
352 .enumerate()
353 .map(|(i, zone)| {
354 let (at, stream) = s.zone_stream(i)?;
355 Ok(nsmp::ZoneAudio {
356 root_key: zone.root_key,
357 top_note: zone.top_note,
358 low_note: zone.low_note,
359 at,
360 stream,
361 })
362 })
363 .collect()
364 }
365 }
366 }
367
368 pub fn stroke_streams(&self) -> Vec<(usize, &[u8])> {
370 match self {
371 Sample::V2(s) => s.stroke_streams(),
372 Sample::V3(s) => s.stroke_streams(),
373 }
374 }
375
376 pub fn to_bytes(&self) -> Result<Vec<u8>, Error> {
378 let mut out = std::io::Cursor::new(Vec::new());
379 match self {
380 Sample::V2(s) => s.write_to(&mut out),
381 Sample::V3(s) => s.write_to(&mut out),
382 }?;
383 Ok(out.into_inner())
384 }
385}
386
387#[allow(clippy::large_enum_variant)]
393#[derive(Debug)]
394pub enum Entity {
395 Cne3(cn3::Cne3),
397 Live(Live),
398 Midi(midi::Midi),
400 OrganPreset(OrganPreset),
401 Piano(npno::Piano),
403 PianoLibrary(Cbin<RawBody>),
406 PianoPreset(PianoPreset),
407 PipeLibrary(Cbin<RawBody>),
409 Performance(Performance),
410 Program(Program),
411 Sample(Sample),
412 SampleProject(nsmpproj::Project),
415 Settings(Settings),
416 Song(Song),
417 Synth(Synth),
418 Sysex(sysex::Sysex),
420 #[cfg(feature = "bundle")]
421 Bundle(Bundle),
422}
423
424pub fn from_stream(reader: &mut (impl Read + Seek + Sized)) -> Result<Entity, Error> {
428 let header = peek(reader)?;
429
430 match header.file_type {
431 #[cfg(feature = "bundle")]
432 FileType::Zip => read_zip(reader),
433 #[cfg(not(feature = "bundle"))]
434 FileType::Zip => {
435 Err(ParseError::UnknownFileType("zip (bundle feature disabled)".to_string()).into())
436 }
437 FileType::Sysex => Ok(Entity::Sysex(sysex::Sysex::read_from(reader)?)),
438 FileType::Midi => Ok(Entity::Midi(midi::Midi::read_from(reader)?)),
439 FileType::Cne3 => Ok(Entity::Cne3(cn3::Cne3::read_from(reader)?)),
440 FileType::SampleProject => Ok(Entity::SampleProject(nsmpproj::Project::read_from(reader)?)),
441 FileType::Cbin => read_cbin(reader, header.format.as_str()),
442 e => Err(ParseError::UnknownFileType(e.as_str().to_string()).into()),
443 }
444}
445
446fn read_cbin(reader: &mut (impl Read + Seek), tag: &str) -> Result<Entity, Error> {
448 use Entity as E;
449
450 Ok(match tag {
451 nsmp::FORMAT => {
452 let file: Cbin<nsmp::AnyBody> = cbin::read(reader, nsmp::FORMAT)?;
453 let header = file.header;
454 E::Sample(match file.body {
455 nsmp::AnyBody::V2(body) => Sample::V2(Cbin { header, body }),
456 nsmp::AnyBody::V3(body) => Sample::V3(Cbin { header, body }),
457 })
458 }
459 npno::FORMAT => E::Piano(npno::Piano::read_from(reader)?),
460 npip::pipe_library::FORMAT => E::PipeLibrary(npip::pipe_library::read_from(reader)?),
461 nsclassic::piano_library::FORMAT => {
462 E::PianoLibrary(nsclassic::piano_library::read_from(reader)?)
463 }
464
465 ne3::program::FORMAT => E::Program(Program::Electro3(ne3::program::read_from(reader)?)),
466 ne3::organ_preset::FORMAT => {
467 E::OrganPreset(OrganPreset::Electro3(ne3::organ_preset::read_from(reader)?))
468 }
469 ne4::program::FORMAT => E::Program(Program::Electro4(ne4::program::read_from(reader)?)),
470 ne4::live::FORMAT => E::Live(Live::Electro4(ne4::live::read_from(reader)?)),
471 ne4::settings::FORMAT => E::Settings(Settings::Electro4(ne4::settings::read_from(reader)?)),
472 ne5::program::FORMAT => E::Program(Program::Electro5(ne5::program::read_from(reader)?)),
473 ne5::live::FORMAT => E::Live(Live::Electro5(ne5::live::read_from(reader)?)),
474 ne5::song::FORMAT => E::Song(Song::Electro5(ne5::song::read_from(reader)?)),
475 ne5::settings::FORMAT => E::Settings(Settings::Electro5(ne5::settings::read_from(reader)?)),
476 ne6::program::FORMAT => E::Program(Program::Electro6(ne6::program::read_from(reader)?)),
477 ne6::live::FORMAT => E::Live(Live::Electro6(ne6::live::read_from(reader)?)),
478 ne6::settings::FORMAT => E::Settings(Settings::Electro6(ne6::settings::read_from(reader)?)),
479 ne7::program::FORMAT => E::Program(Program::Electro7(ne7::program::read_from(reader)?)),
480 ne7::live::FORMAT => E::Live(Live::Electro7(ne7::live::read_from(reader)?)),
481 ne7::settings::FORMAT => E::Settings(Settings::Electro7(ne7::settings::read_from(reader)?)),
482
483 nsclassic::program::FORMAT => E::Program(Program::StageClassic(
484 nsclassic::program::read_from(reader)?,
485 )),
486 nsclassic::synth::FORMAT => {
487 E::Synth(Synth::StageClassic(nsclassic::synth::read_from(reader)?))
488 }
489 ns2::program::FORMAT => E::Program(Program::Stage2(ns2::program::read_from(reader)?)),
490 ns2::live::FORMAT => E::Live(Live::Stage2(ns2::live::read_from(reader)?)),
491 ns2::synth::FORMAT => E::Synth(Synth::Stage2(ns2::synth::read_from(reader)?)),
492 ns2::settings::FORMAT => E::Settings(Settings::Stage2(ns2::settings::read_from(reader)?)),
493 ns3::program::FORMAT => E::Program(Program::Stage3(ns3::program::read_from(reader)?)),
494 ns3::live::FORMAT => E::Live(Live::Stage3(ns3::live::read_from(reader)?)),
495 ns3::song::FORMAT => E::Song(Song::Stage3(ns3::song::read_from(reader)?)),
496 ns3::synth::FORMAT => E::Synth(Synth::Stage3(ns3::synth::read_from(reader)?)),
497 ns3::settings::FORMAT => E::Settings(Settings::Stage3(ns3::settings::read_from(reader)?)),
498 ns4::program::FORMAT => E::Program(Program::Stage4(ns4::program::read_from(reader)?)),
499 ns4::live::FORMAT => E::Live(Live::Stage4(ns4::live::read_from(reader)?)),
500 ns4::synth::FORMAT => E::Synth(Synth::Stage4(ns4::synth::read_from(reader)?)),
501 ns4::piano_preset::FORMAT => {
502 E::PianoPreset(PianoPreset::Stage4(ns4::piano_preset::read_from(reader)?))
503 }
504 ns4::organ_preset::FORMAT => {
505 E::OrganPreset(OrganPreset::Stage4(ns4::organ_preset::read_from(reader)?))
506 }
507 ns4::settings::FORMAT => E::Settings(Settings::Stage4(ns4::settings::read_from(reader)?)),
508
509 np::program::FORMAT => E::Program(Program::Piano1(np::program::read_from(reader)?)),
510 np::live::FORMAT => E::Live(Live::Piano1(np::live::read_from(reader)?)),
511 np::settings::FORMAT => E::Settings(Settings::Piano1(np::settings::read_from(reader)?)),
512 np2::program::FORMAT => E::Program(Program::Piano2(np2::program::read_from(reader)?)),
513 np2::live::FORMAT => E::Live(Live::Piano2(np2::live::read_from(reader)?)),
514 np2::settings::FORMAT => E::Settings(Settings::Piano2(np2::settings::read_from(reader)?)),
515 np3::program::FORMAT => E::Program(Program::Piano3(np3::program::read_from(reader)?)),
516 np3::live::FORMAT => E::Live(Live::Piano3(np3::live::read_from(reader)?)),
517 np3::settings::FORMAT => E::Settings(Settings::Piano3(np3::settings::read_from(reader)?)),
518 np4::program::FORMAT => E::Program(Program::Piano4(np4::program::read_from(reader)?)),
519 np4::live::FORMAT => E::Live(Live::Piano4(np4::live::read_from(reader)?)),
520 np4::settings::FORMAT => E::Settings(Settings::Piano4(np4::settings::read_from(reader)?)),
521 np5::program::FORMAT => E::Program(Program::Piano5(np5::program::read_from(reader)?)),
522 np5::live::FORMAT => E::Live(Live::Piano5(np5::live::read_from(reader)?)),
523 np5::settings::FORMAT => E::Settings(Settings::Piano5(np5::settings::read_from(reader)?)),
524 ng2::program::FORMAT => E::Program(Program::Grand(ng2::program::read_from(reader)?)),
525 ng2::live::FORMAT => E::Live(Live::Grand(ng2::live::read_from(reader)?)),
526 ng2::settings::FORMAT => E::Settings(Settings::Grand(ng2::settings::read_from(reader)?)),
527
528 nw::program::FORMAT => E::Program(Program::Wave(nw::program::read_from(reader)?)),
529 nw::settings::FORMAT => E::Settings(Settings::Wave(nw::settings::read_from(reader)?)),
530 nw2::program::FORMAT => E::Program(Program::Wave2(nw2::program::read_from(reader)?)),
531 nw2::live::FORMAT => E::Live(Live::Wave2(nw2::live::read_from(reader)?)),
532 nw2::settings::FORMAT => E::Settings(Settings::Wave2(nw2::settings::read_from(reader)?)),
533
534 nc2::program::FORMAT => E::Program(Program::C2(nc2::program::read_from(reader)?)),
535 nc2::settings::FORMAT => E::Settings(Settings::C2(nc2::settings::read_from(reader)?)),
536 nc2d::program::FORMAT => E::Program(Program::C2D(nc2d::program::read_from(reader)?)),
537 nc2d::settings::FORMAT => E::Settings(Settings::C2D(nc2d::settings::read_from(reader)?)),
538 no3::program::FORMAT => E::Program(Program::Organ3(no3::program::read_from(reader)?)),
539 no3::settings::FORMAT => E::Settings(Settings::Organ3(no3::settings::read_from(reader)?)),
540
541 nl4::program::FORMAT => E::Program(Program::Lead4(nl4::program::read_from(reader)?)),
543 nl4::performance::FORMAT => {
544 E::Performance(Performance::Lead4(nl4::performance::read_from(reader)?))
545 }
546 nl4::settings::FORMAT => E::Settings(Settings::Lead4(nl4::settings::read_from(reader)?)),
547 nla1::program::FORMAT => E::Program(Program::LeadA1(nla1::program::read_from(reader)?)),
548 nla1::performance::FORMAT => {
549 E::Performance(Performance::LeadA1(nla1::performance::read_from(reader)?))
550 }
551 nla1::settings::FORMAT => E::Settings(Settings::LeadA1(nla1::settings::read_from(reader)?)),
552
553 nd2::program::FORMAT => E::Program(Program::Drum2(nd2::program::read_from(reader)?)),
554 nd3::kit::FORMAT => E::Program(Program::Drum3(nd3::kit::read_from(reader)?)),
555
556 e => return Err(ParseError::UnknownFormat(e.to_string()).into()),
557 })
558}
559
560#[cfg(feature = "bundle")]
562enum ZipKind {
563 Electro5,
564 Drum2,
565 Drum3,
566 Members,
567}
568
569#[cfg(feature = "bundle")]
572fn read_zip(reader: &mut (impl Read + Seek)) -> Result<Entity, Error> {
573 let start = reader.stream_position()?;
574 let kind = {
575 let zip = zip::ZipArchive::new(&mut *reader)?;
576 let names: Vec<&str> = zip
581 .file_names()
582 .filter(|name| !is_dir_entry(name) && !name.ends_with("meta.xml"))
583 .collect();
584 if names.is_empty() {
587 return Err(ParseError::AssertFail("the archive holds no members".into()).into());
588 }
589 if names.iter().any(|n| {
592 std::path::Path::new(n)
593 .extension()
594 .is_some_and(|e| e.to_string_lossy().starts_with("ne5"))
595 }) {
596 ZipKind::Electro5
597 } else if names.iter().all(|n| n.ends_with(".nd2p")) {
598 ZipKind::Drum2
599 } else if names.iter().all(|n| n.ends_with(".nd3k")) {
600 ZipKind::Drum3
601 } else {
602 ZipKind::Members
605 }
606 };
607 reader.seek(std::io::SeekFrom::Start(start))?;
608
609 Ok(Entity::Bundle(match kind {
610 ZipKind::Drum2 => Bundle::Drum2Bank(nd2::bank::read_from(reader)?),
611 ZipKind::Drum3 => Bundle::Drum3KitBank(nd3::kit_bank::read_from(reader)?),
612 ZipKind::Members => Bundle::Members(formats::zip_raw_members(reader)?),
613 ZipKind::Electro5 => Bundle::Electro5(ne5::Bundle::read_from(reader)?),
614 }))
615}
616
617#[cfg(feature = "bundle")]
620fn is_dir_entry(name: &str) -> bool {
621 name.ends_with('/') || name.ends_with('\\')
622}
623
624pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Entity, Error> {
626 from_stream(&mut BufReader::new(File::open(path)?))
627}
628
629#[cfg(test)]
630mod registry_tests {
631 use super::*;
632
633 #[test]
636 fn the_entity_registry_reads_and_writes_the_body() {
637 let mut entity = Entity::Program(Program::Electro5(ne5::program::new(
638 (0, 0).try_into().unwrap(),
639 )));
640
641 let before = entity.registry().unwrap().fields();
642 assert!(before.iter().any(|f| f.path == "center_panel.transpose"));
643
644 entity
645 .registry_mut()
646 .unwrap()
647 .set_field("center_panel.transpose", "-5")
648 .unwrap();
649 let after = entity.registry().unwrap().fields();
650 let transpose = after
651 .iter()
652 .find(|f| f.path == "center_panel.transpose")
653 .unwrap();
654 assert_eq!(transpose.value, "-5");
655 }
656
657 #[test]
660 fn a_stub_has_no_registry() {
661 let file = Cbin {
662 header: cbin::Header::new("ne6p", (0, 0), 1),
663 body: RawBody(vec![0; 16]),
664 };
665 let mut entity = Entity::Program(Program::Electro6(file));
666 assert!(entity.registry().is_none());
667 assert!(entity.registry_mut().is_none());
668 }
669
670 #[test]
673 fn a_song_is_not_a_registry_entity() {
674 let song = ne5::song::new(
675 (0, 0).try_into().unwrap(),
676 ne5::song::DEFAULT_VERSION,
677 [(0, 0).try_into().unwrap(); 4],
678 )
679 .unwrap();
680 assert!(Entity::Song(Song::Electro5(song)).registry().is_none());
681 }
682}
683
684#[cfg(all(test, feature = "bundle"))]
685mod bundle_tests {
686 use super::*;
687 use crate::cbin::{Cbin, Header, RawBody};
688 use std::io::{Cursor, Write};
689
690 fn member(tag: &str) -> Vec<u8> {
691 let file = Cbin {
692 header: Header::new(tag, (0, 0), 4),
693 body: RawBody(vec![0x5A; 16]),
694 };
695 let mut out = Cursor::new(Vec::new());
696 file.write_to(&mut out).unwrap();
697 out.into_inner()
698 }
699
700 fn archive(members: &[(&str, &[u8])]) -> Vec<u8> {
702 let mut zip = zip::ZipWriter::new(Cursor::new(Vec::new()));
703 let stored = zip::write::SimpleFileOptions::default()
704 .compression_method(zip::CompressionMethod::Stored);
705 for (name, bytes) in members {
706 match name.strip_suffix('/') {
707 Some(directory) => zip.add_directory(directory, stored).unwrap(),
708 None => {
709 zip.start_file(name.to_string(), stored).unwrap();
710 zip.write_all(bytes).unwrap();
711 }
712 }
713 }
714 zip.finish().unwrap().into_inner()
715 }
716
717 #[test]
720 fn a_zip_of_mixed_cbin_members_is_a_bundle() {
721 let a = member("ns3f");
722 let b = member("ns3y");
723 let bytes = archive(&[("Bank A/One.ns3f", &a), ("presets/Two.ns3y", &b)]);
724
725 let entity = from_stream(&mut Cursor::new(bytes)).unwrap();
726 let Entity::Bundle(Bundle::Members(members)) = entity else {
727 panic!("decoded to something other than a member bundle");
728 };
729 assert_eq!(members.len(), 2);
730 assert_eq!(members[0].0, "Bank A/One.ns3f");
731 assert_eq!(&members[0].1.header.tag, b"ns3f");
732 assert_eq!(&members[1].1.header.tag, b"ns3y");
733 }
734
735 #[test]
738 fn an_empty_zip_is_refused() {
739 let bytes = archive(&[]);
740 assert!(from_stream(&mut Cursor::new(bytes)).is_err());
741 }
742
743 #[test]
747 fn a_zip_of_directories_and_a_manifest_is_refused() {
748 let bytes = archive(&[("kits/", b""), ("meta.xml", b"<meta/>")]);
749 let err = from_stream(&mut Cursor::new(bytes)).unwrap_err();
750 assert!(
751 err.to_string().contains("no members"),
752 "refused for the wrong reason: {err}"
753 );
754 }
755
756 #[test]
759 fn a_directory_entry_does_not_hide_a_drum_bank() {
760 let program = member("nd2p");
761 let bytes = archive(&[("kits/", b""), ("kits/One.nd2p", &program)]);
762 let entity = from_stream(&mut Cursor::new(bytes)).unwrap();
763 assert!(
764 matches!(entity, Entity::Bundle(Bundle::Drum2Bank(_))),
765 "a `kits/` entry left it classified as {}",
766 entity.identity().kind,
767 );
768 }
769
770 #[test]
772 fn a_zip_with_a_non_cbin_member_is_refused() {
773 let a = member("ns3f");
774 let bytes = archive(&[("One.ns3f", &a), ("readme.txt", b"hello")]);
775 assert!(from_stream(&mut Cursor::new(bytes)).is_err());
776 }
777
778 #[test]
779 fn a_zip_is_read_from_the_callers_current_position() {
780 let member = member("ns3f");
781 let bytes = archive(&[("Bank A/One.ns3f", &member)]);
782 let prefix_len = 7;
783 let mut prefixed = vec![0xa5; prefix_len];
784 prefixed.extend(bytes);
785 let mut reader = Cursor::new(prefixed);
786 reader.set_position(prefix_len as u64);
787
788 let entity = from_stream(&mut reader).unwrap();
789 assert!(matches!(entity, Entity::Bundle(Bundle::Members(_))));
790 }
791}
792
793pub fn to_bytes(entity: &Entity) -> Result<Vec<u8>, Error> {
806 use std::io::Cursor;
807
808 let mut out = Cursor::new(Vec::new());
809 entity.write_to(&mut out)?;
810 Ok(out.into_inner())
811}
812
813#[derive(Debug, Clone, Copy, PartialEq, Eq)]
815pub struct Identity {
816 pub kind: &'static str,
818 pub format: &'static str,
820}
821
822macro_rules! registry_bodies {
823 ($($body:ty),* $(,)?) => {$(
824 impl fields::Registry for Cbin<$body> {
825 fn fields(&self) -> Vec<fields::Field> {
826 self.body.fields()
827 }
828 fn field_values(&self) -> Vec<fields::FieldValue> {
829 self.body.field_values()
830 }
831 fn set_field(&mut self, path: &str, value: &str) -> Result<(), fields::FieldError> {
832 self.body.set_field(path, value)
833 }
834 }
835 )*};
836}
837
838registry_bodies!(
839 ne5::Program,
840 ne5::Settings,
841 ns2::Program,
842 ns3::Program,
843 ns3::SynthPreset,
844 ns4::Program,
845 ns4::organ_preset::OrganPreset,
846 ns4::piano_preset::PianoPreset,
847 ns4::synth::SynthPreset,
848);
849
850macro_rules! with_registry {
855 ($entity:expr, $($reference:tt)*) => {
856 match $entity {
857 Entity::Program(Program::Electro5(f)) | Entity::Live(Live::Electro5(f)) => {
858 Some(f as $($reference)* dyn fields::Registry)
859 }
860 Entity::Program(Program::Stage2(f)) | Entity::Live(Live::Stage2(f)) => {
861 Some(f as $($reference)* dyn fields::Registry)
862 }
863 Entity::Program(Program::Stage3(f)) | Entity::Live(Live::Stage3(f)) => {
864 Some(f as $($reference)* dyn fields::Registry)
865 }
866 Entity::Program(Program::Stage4(f)) | Entity::Live(Live::Stage4(f)) => {
867 Some(f as $($reference)* dyn fields::Registry)
868 }
869 Entity::Settings(Settings::Electro5(f)) => Some(f as $($reference)* dyn fields::Registry),
870 Entity::Synth(Synth::Stage3(f)) => Some(f as $($reference)* dyn fields::Registry),
871 Entity::Synth(Synth::Stage4(f)) => Some(f as $($reference)* dyn fields::Registry),
872 Entity::OrganPreset(OrganPreset::Stage4(f)) => {
873 Some(f as $($reference)* dyn fields::Registry)
874 }
875 Entity::PianoPreset(PianoPreset::Stage4(f)) => {
876 Some(f as $($reference)* dyn fields::Registry)
877 }
878 _ => None,
879 }
880 };
881}
882
883impl Entity {
884 pub fn raw(&self) -> Option<&Cbin<RawBody>> {
888 use {Live as L, OrganPreset as OP, Program as P, Settings as St, Synth as Sy};
889 match self {
890 Entity::Program(
891 P::C2(f)
892 | P::C2D(f)
893 | P::Drum2(f)
894 | P::Drum3(f)
895 | P::Electro3(f)
896 | P::Electro4(f)
897 | P::Electro6(f)
898 | P::Electro7(f)
899 | P::Grand(f)
900 | P::Lead4(f)
901 | P::LeadA1(f)
902 | P::Organ3(f)
903 | P::Piano1(f)
904 | P::Piano2(f)
905 | P::Piano3(f)
906 | P::Piano4(f)
907 | P::Piano5(f)
908 | P::StageClassic(f)
909 | P::Wave(f)
910 | P::Wave2(f),
911 )
912 | Entity::Live(
913 L::Electro4(f)
914 | L::Electro6(f)
915 | L::Electro7(f)
916 | L::Grand(f)
917 | L::Piano1(f)
918 | L::Piano2(f)
919 | L::Piano3(f)
920 | L::Piano4(f)
921 | L::Piano5(f)
922 | L::Wave2(f),
923 )
924 | Entity::Settings(
925 St::C2(f)
926 | St::C2D(f)
927 | St::Electro4(f)
928 | St::Electro6(f)
929 | St::Electro7(f)
930 | St::Grand(f)
931 | St::Lead4(f)
932 | St::LeadA1(f)
933 | St::Organ3(f)
934 | St::Piano1(f)
935 | St::Piano2(f)
936 | St::Piano3(f)
937 | St::Piano4(f)
938 | St::Piano5(f)
939 | St::Stage2(f)
940 | St::Stage3(f)
941 | St::Stage4(f)
942 | St::Wave(f)
943 | St::Wave2(f),
944 )
945 | Entity::Song(Song::Stage3(f))
946 | Entity::Synth(Sy::Stage2(f) | Sy::StageClassic(f))
947 | Entity::Performance(Performance::Lead4(f) | Performance::LeadA1(f))
948 | Entity::OrganPreset(OP::Electro3(f))
949 | Entity::PianoLibrary(f)
950 | Entity::PipeLibrary(f) => Some(f),
951 _ => None,
952 }
953 }
954
955 pub fn registry(&self) -> Option<&dyn fields::Registry> {
958 with_registry!(self, &)
959 }
960
961 pub fn registry_mut(&mut self) -> Option<&mut dyn fields::Registry> {
965 with_registry!(self, &mut)
966 }
967
968 pub fn identity(&self) -> Identity {
970 use {Live as L, Program as P, Settings as St};
971 let id = |kind, format| Identity { kind, format };
972 match self {
973 Entity::Program(p) => match p {
974 P::C2(_) => id("C2 program", nc2::program::FORMAT),
975 P::C2D(_) => id("C2D program", nc2d::program::FORMAT),
976 P::Drum2(_) => id("Drum 2 program", nd2::program::FORMAT),
977 P::Drum3(_) => id("Drum 3P kit", nd3::kit::FORMAT),
978 P::Electro3(_) => id("Electro 3 program", ne3::program::FORMAT),
979 P::Electro4(_) => id("Electro 4 program", ne4::program::FORMAT),
980 P::Electro5(_) => id("Electro 5 program", ne5::program::FORMAT),
981 P::Electro6(_) => id("Electro 6 program", ne6::program::FORMAT),
982 P::Electro7(_) => id("Electro 7 program", ne7::program::FORMAT),
983 P::Grand(_) => id("Grand program", ng2::program::FORMAT),
984 P::Lead4(_) => id("Lead 4 program", nl4::program::FORMAT),
985 P::LeadA1(_) => id("Lead A1 program", nla1::program::FORMAT),
986 P::Organ3(_) => id("no3 organ program", no3::program::FORMAT),
987 P::Piano1(_) => id("Piano program", np::program::FORMAT),
988 P::Piano2(_) => id("Piano 2 program", np2::program::FORMAT),
989 P::Piano3(_) => id("Piano 3 program", np3::program::FORMAT),
990 P::Piano4(_) => id("Piano 4 program", np4::program::FORMAT),
991 P::Piano5(_) => id("Piano 5 program", np5::program::FORMAT),
992 P::Stage2(_) => id("Stage 2 program", ns2::program::FORMAT),
993 P::Stage3(_) => id("Stage 3 program", ns3::program::FORMAT),
994 P::Stage4(_) => id("Stage 4 program", ns4::program::FORMAT),
995 P::StageClassic(_) => id("Stage Classic program", nsclassic::program::FORMAT),
996 P::Wave(_) => id("Wave program", nw::program::FORMAT),
997 P::Wave2(_) => id("Wave 2 program", nw2::program::FORMAT),
998 },
999 Entity::Live(l) => match l {
1000 L::Electro4(_) => id("Electro 4 live slot", ne4::live::FORMAT),
1001 L::Electro5(_) => id("Electro 5 live slot", ne5::live::FORMAT),
1002 L::Electro6(_) => id("Electro 6 live slot", ne6::live::FORMAT),
1003 L::Electro7(_) => id("Electro 7 live slot", ne7::live::FORMAT),
1004 L::Grand(_) => id("Grand live slot", ng2::live::FORMAT),
1005 L::Piano1(_) => id("Piano live slot", np::live::FORMAT),
1006 L::Piano2(_) => id("Piano 2 live slot", np2::live::FORMAT),
1007 L::Piano3(_) => id("Piano 3 live slot", np3::live::FORMAT),
1008 L::Piano4(_) => id("Piano 4 live slot", np4::live::FORMAT),
1009 L::Piano5(_) => id("Piano 5 live slot", np5::live::FORMAT),
1010 L::Stage2(_) => id("Stage 2 live slot", ns2::live::FORMAT),
1011 L::Stage3(_) => id("Stage 3 live slot", ns3::live::FORMAT),
1012 L::Stage4(_) => id("Stage 4 live slot", ns4::live::FORMAT),
1013 L::Wave2(_) => id("Wave 2 live slot", nw2::live::FORMAT),
1014 },
1015 Entity::Settings(s) => match s {
1016 St::C2(_) => id("C2 settings", nc2::settings::FORMAT),
1017 St::C2D(_) => id("C2D settings", nc2d::settings::FORMAT),
1018 St::Electro4(_) => id("Electro 4 settings", ne4::settings::FORMAT),
1019 St::Electro5(_) => id("Electro 5 settings", ne5::settings::FORMAT),
1020 St::Electro6(_) => id("Electro 6 settings", ne6::settings::FORMAT),
1021 St::Electro7(_) => id("Electro 7 settings", ne7::settings::FORMAT),
1022 St::Grand(_) => id("Grand settings", ng2::settings::FORMAT),
1023 St::Lead4(_) => id("Lead 4 settings", nl4::settings::FORMAT),
1024 St::LeadA1(_) => id("Lead A1 settings", nla1::settings::FORMAT),
1025 St::Organ3(_) => id("no3 organ settings", no3::settings::FORMAT),
1026 St::Piano1(_) => id("Piano settings", np::settings::FORMAT),
1027 St::Piano2(_) => id("Piano 2 settings", np2::settings::FORMAT),
1028 St::Piano3(_) => id("Piano 3 settings", np3::settings::FORMAT),
1029 St::Piano4(_) => id("Piano 4 settings", np4::settings::FORMAT),
1030 St::Piano5(_) => id("Piano 5 settings", np5::settings::FORMAT),
1031 St::Stage2(_) => id("Stage 2 settings", ns2::settings::FORMAT),
1032 St::Stage3(_) => id("Stage 3 settings", ns3::settings::FORMAT),
1033 St::Stage4(_) => id("Stage 4 settings", ns4::settings::FORMAT),
1034 St::Wave(_) => id("Wave settings", nw::settings::FORMAT),
1035 St::Wave2(_) => id("Wave 2 settings", nw2::settings::FORMAT),
1036 },
1037 Entity::Song(Song::Electro5(_)) => id("Electro 5 song / set", ne5::song::FORMAT),
1038 Entity::Song(Song::Stage3(_)) => id("Stage 3 song", ns3::song::FORMAT),
1039 Entity::Synth(Synth::Stage2(_)) => id("Stage 2 synth patch", ns2::synth::FORMAT),
1040 Entity::Synth(Synth::Stage3(_)) => id("Stage 3 synth patch", ns3::synth::FORMAT),
1041 Entity::Synth(Synth::Stage4(_)) => id("Stage 4 synth preset", ns4::synth::FORMAT),
1042 Entity::Synth(Synth::StageClassic(_)) => {
1043 id("Stage Classic synth patch", nsclassic::synth::FORMAT)
1044 }
1045 Entity::Performance(Performance::Lead4(_)) => {
1046 id("Lead 4 performance", nl4::performance::FORMAT)
1047 }
1048 Entity::Performance(Performance::LeadA1(_)) => {
1049 id("Lead A1 performance", nla1::performance::FORMAT)
1050 }
1051 Entity::OrganPreset(OrganPreset::Electro3(_)) => {
1052 id("Electro 3 organ preset", ne3::organ_preset::FORMAT)
1053 }
1054 Entity::OrganPreset(OrganPreset::Stage4(_)) => {
1055 id("Stage 4 organ preset", ns4::organ_preset::FORMAT)
1056 }
1057 Entity::PianoPreset(PianoPreset::Stage4(_)) => {
1058 id("Stage 4 piano preset", ns4::piano_preset::FORMAT)
1059 }
1060 Entity::Piano(_) => id("piano library", npno::FORMAT),
1061 Entity::PianoLibrary(_) => id(
1062 "Stage Classic piano library",
1063 nsclassic::piano_library::FORMAT,
1064 ),
1065 Entity::PipeLibrary(_) => id("C2 pipe library", npip::pipe_library::FORMAT),
1066 Entity::Sample(Sample::V2(_)) => id("sample instrument", nsmp::FORMAT),
1067 Entity::Sample(Sample::V3(_)) => id("sample instrument (nsmp3/nsmp4)", nsmp::FORMAT),
1068 Entity::SampleProject(_) => id("Sample Editor project", nsmpproj::FORMAT),
1069 Entity::Sysex(_) => id("SysEx dump", "syx"),
1070 Entity::Midi(_) => id("MIDI file", "mid"),
1071 Entity::Cne3(_) => id("Electro 2 library", "cn3"),
1072 #[cfg(feature = "bundle")]
1073 Entity::Bundle(_) => id("bundle", "zip"),
1074 }
1075 }
1076
1077 pub fn write_to(&self, w: &mut (impl std::io::Write + Seek)) -> Result<(), Error> {
1082 match self {
1083 Entity::Cne3(f) => f.write_to(w),
1084 Entity::Live(l) => match l {
1085 Live::Electro4(f)
1086 | Live::Electro6(f)
1087 | Live::Electro7(f)
1088 | Live::Grand(f)
1089 | Live::Piano1(f)
1090 | Live::Piano2(f)
1091 | Live::Piano3(f)
1092 | Live::Piano4(f)
1093 | Live::Piano5(f)
1094 | Live::Wave2(f) => f.write_to(w),
1095 Live::Electro5(f) => f.write_to(w),
1096 Live::Stage4(f) => f.write_to(w),
1097 Live::Stage2(f) => f.write_to(w),
1098 Live::Stage3(f) => f.write_to(w),
1099 },
1100 Entity::Midi(f) => f.write_to(w),
1101 Entity::OrganPreset(OrganPreset::Electro3(f))
1102 | Entity::PianoLibrary(f)
1103 | Entity::PipeLibrary(f) => f.write_to(w),
1104 Entity::OrganPreset(OrganPreset::Stage4(f)) => f.write_to(w),
1105 Entity::PianoPreset(PianoPreset::Stage4(f)) => f.write_to(w),
1106 Entity::Piano(f) => f.write_to(w),
1107 Entity::Performance(Performance::Lead4(f))
1108 | Entity::Performance(Performance::LeadA1(f)) => f.write_to(w),
1109 Entity::Program(p) => match p {
1110 Program::C2(f)
1111 | Program::C2D(f)
1112 | Program::Drum2(f)
1113 | Program::Drum3(f)
1114 | Program::Electro3(f)
1115 | Program::Electro4(f)
1116 | Program::Electro6(f)
1117 | Program::Electro7(f)
1118 | Program::Grand(f)
1119 | Program::Lead4(f)
1120 | Program::LeadA1(f)
1121 | Program::Organ3(f)
1122 | Program::Piano1(f)
1123 | Program::Piano2(f)
1124 | Program::Piano3(f)
1125 | Program::Piano4(f)
1126 | Program::Piano5(f)
1127 | Program::StageClassic(f)
1128 | Program::Wave(f)
1129 | Program::Wave2(f) => f.write_to(w),
1130 Program::Electro5(f) => f.write_to(w),
1131 Program::Stage2(f) => f.write_to(w),
1132 Program::Stage3(f) => f.write_to(w),
1133 Program::Stage4(f) => f.write_to(w),
1134 },
1135 Entity::Sample(Sample::V2(f)) => f.write_to(w),
1136 Entity::Sample(Sample::V3(f)) => f.write_to(w),
1137 Entity::SampleProject(f) => f.write_to(w),
1138 Entity::Settings(s) => match s {
1139 Settings::C2(f)
1140 | Settings::C2D(f)
1141 | Settings::Electro4(f)
1142 | Settings::Electro6(f)
1143 | Settings::Electro7(f)
1144 | Settings::Grand(f)
1145 | Settings::Lead4(f)
1146 | Settings::LeadA1(f)
1147 | Settings::Organ3(f)
1148 | Settings::Piano1(f)
1149 | Settings::Piano2(f)
1150 | Settings::Piano3(f)
1151 | Settings::Piano4(f)
1152 | Settings::Piano5(f)
1153 | Settings::Stage2(f)
1154 | Settings::Stage3(f)
1155 | Settings::Stage4(f)
1156 | Settings::Wave(f)
1157 | Settings::Wave2(f) => f.write_to(w),
1158 Settings::Electro5(f) => f.write_to(w),
1159 },
1160 Entity::Song(Song::Electro5(f)) => f.write_to(w),
1161 Entity::Song(Song::Stage3(f)) => f.write_to(w),
1162 Entity::Synth(Synth::Stage2(f)) | Entity::Synth(Synth::StageClassic(f)) => {
1163 f.write_to(w)
1164 }
1165 Entity::Synth(Synth::Stage3(f)) => f.write_to(w),
1166 Entity::Synth(Synth::Stage4(f)) => f.write_to(w),
1167 Entity::Sysex(f) => f.write_to(w),
1168 #[cfg(feature = "bundle")]
1169 Entity::Bundle(_) => Err(ParseError::AssertFail(
1170 "bundles are archives; re-encoding one is not supported".into(),
1171 )
1172 .into()),
1173 }
1174 }
1175}