1use super::{error, from_lines};
6use crate::error::Result;
7use crate::stream::Stream;
8
9const COMPRESSED: &[u8] = b"[NWZ]\x00";
11
12pub fn from_nwc(bytes: &[u8]) -> Result<Stream> {
31 let lines = nwc_lines(bytes)?;
32 from_lines(lines.iter().map(String::as_str))
33}
34
35pub fn nwc_lines(bytes: &[u8]) -> Result<Vec<String>> {
43 if bytes.starts_with(COMPRESSED) {
44 return Err(error(
45 "a compressed .nwc file: inflate the zlib stream after its first six bytes first",
46 ));
47 }
48 let mut reader = Bytes {
49 data: bytes,
50 at: 0,
51 version: 200,
52 alterations: Vec::new(),
53 };
54 let header = reader.header()?;
55 let mut lines = Vec::new();
56 let mut info = String::new();
57 if !header.title.is_empty() {
58 info.push_str("|SongInfo|Title:");
59 info.push_str(&latin1(&header.title));
60 }
61 if !header.author.is_empty() {
62 info.push_str("|Author:");
63 info.push_str(&latin1(&header.author));
64 }
65 lines.push(info);
66 for _ in 0..header.staves {
67 let staff = reader.staff()?;
68 lines.extend(staff);
69 }
70 Ok(lines)
71}
72
73fn latin1(bytes: &[u8]) -> String {
75 bytes.iter().map(|byte| char::from(*byte)).collect()
76}
77
78fn python_index<T: Copy>(table: &[T], index: i32) -> Option<T> {
81 let length = i32::try_from(table.len()).ok()?;
82 let index = if index < 0 { index + length } else { index };
83 usize::try_from(index)
84 .ok()
85 .and_then(|index| table.get(index).copied())
86}
87
88const CLEF_NAMES: [&str; 5] = ["Treble", "Bass", "Alto", "Tenor", "Percussion"];
90
91const OCTAVE_SHIFT_NAMES: [Option<&str>; 3] = [None, Some("Octave Up"), Some("Octave Down")];
93
94const FLAT_MASK: [(u8, &str); 8] = [
96 (0x00, ""),
97 (0x02, "Bb"),
98 (0x12, "Bb,Eb"),
99 (0x13, "Bb,Eb,Ab"),
100 (0x1B, "Bb,Eb,Ab,Db"),
101 (0x5B, "Bb,Eb,Ab,Db,Gb"),
102 (0x5F, "Bb,Eb,Ab,Db,Gb,Cb"),
103 (0x7F, "Bb,Eb,Ab,Db,Gb,Cb,Fb"),
104];
105
106const SHARP_MASK: [(u8, &str); 8] = [
108 (0x00, ""),
109 (0x20, "F#"),
110 (0x24, "F#,C#"),
111 (0x64, "F#,C#,G#"),
112 (0x6C, "F#,C#,G#,D#"),
113 (0x6D, "F#,C#,G#,D#,A#"),
114 (0x7D, "F#,C#,G#,D#,A#,E#"),
115 (0x7F, "F#,C#,G#,D#,A#,E#,B#"),
116];
117
118const ALTERATION_TEXTS: [&str; 6] = ["#", "b", "n", "x", "v", ""];
121
122const BAR_STYLES: [&str; 8] = [
124 "Single",
125 "Double",
126 "SectionOpen",
127 "SectionClose",
128 "LocalRepeatOpen",
129 "LocalRepeatClose",
130 "MasterRepeatOpen",
131 "MasterRepeatClose",
132];
133
134const DURATION_VALUES: [&str; 7] = ["Whole", "Half", "4th", "8th", "16th", "32nd", "64th"];
136
137const MIDI_INSTRUMENTS: [&str; 128] = [
139 "Acoustic Grand Piano",
140 "Bright Acoustic Piano",
141 "Electric Grand Piano",
142 "Honky-tonk Piano",
143 "Electric Piano 1",
144 "Electric Piano 2",
145 "Harpsichord",
146 "Clavi",
147 "Celesta",
148 "Glockenspiel",
149 "Music Box",
150 "Vibraphone",
151 "Marimba",
152 "Xylophone",
153 "Tubular Bells",
154 "Dulcimer",
155 "Drawbar Organ",
156 "Percussive Organ",
157 "Rock Organ",
158 "Church Organ",
159 "Reed Organ",
160 "Accordion",
161 "Harmonica",
162 "Tango Accordion",
163 "Acoustic Guitar (nylon)",
164 "Acoustic Guitar (steel)",
165 "Electric Guitar (jazz)",
166 "Electric Guitar (clean)",
167 "Electric Guitar (muted)",
168 "Overdriven Guitar",
169 "Distortion Guitar",
170 "Guitar harmonics",
171 "Acoustic Bass",
172 "Electric Bass (finger)",
173 "Electric Bass (pick)",
174 "Fretless Bass",
175 "Slap Bass 1",
176 "Slap Bass 2",
177 "Synth Bass 1",
178 "Synth Bass 2",
179 "Violin",
180 "Viola",
181 "Cello",
182 "Contrabass",
183 "Tremolo Strings",
184 "Pizzicato Strings",
185 "Orchestral Harp",
186 "Timpani",
187 "String Ensemble 1",
188 "String Ensemble 2",
189 "SynthStrings 1",
190 "SynthStrings 2",
191 "Choir Aahs",
192 "Voice Oohs",
193 "Synth Voice",
194 "Orchestra Hit",
195 "Trumpet",
196 "Trombone",
197 "Tuba",
198 "Muted Trumpet",
199 "French Horn",
200 "Brass Section",
201 "SynthBrass 1",
202 "SynthBrass 2",
203 "Soprano Sax",
204 "Alto Sax",
205 "Tenor Sax",
206 "Baritone Sax",
207 "Oboe",
208 "English Horn",
209 "Bassoon",
210 "Clarinet",
211 "Piccolo",
212 "Flute",
213 "Recorder",
214 "Pan Flute",
215 "Blown Bottle",
216 "Shakuhachi",
217 "Whistle",
218 "Ocarina",
219 "Lead 1 (square)",
220 "Lead 2 (sawtooth)",
221 "Lead 3 (calliope)",
222 "Lead 4 (chiff)",
223 "Lead 5 (charang)",
224 "Lead 6 (voice)",
225 "Lead 7 (fifths)",
226 "Lead 8 (bass + lead)",
227 "Pad 1 (new age)",
228 "Pad 2 (warm)",
229 "Pad 3 (polysynth)",
230 "Pad 4 (choir)",
231 "Pad 5 (bowed)",
232 "Pad 6 (metallic)",
233 "Pad 7 (halo)",
234 "Pad 8 (sweep)",
235 "FX 1 (rain)",
236 "FX 2 (soundtrack)",
237 "FX 3 (crystal)",
238 "FX 4 (atmosphere)",
239 "FX 5 (brightness)",
240 "FX 6 (goblins)",
241 "FX 7 (echoes)",
242 "FX 8 (sci-fi)",
243 "Sitar",
244 "Banjo",
245 "Shamisen",
246 "Koto",
247 "Kalimba",
248 "Bag pipe",
249 "Fiddle",
250 "Shanai",
251 "Tinkle Bell",
252 "Agogo",
253 "Steel Drums",
254 "Woodblock",
255 "Taiko Drum",
256 "Melodic Tom",
257 "Synth Drum",
258 "Reverse Cymbal",
259 "Guitar Fret Noise",
260 "Breath Noise",
261 "Seashore",
262 "Bird Tweet",
263 "Telephone Ring",
264 "Helicopter",
265 "Applause",
266 "Gunshot",
267];
268
269struct Header {
271 title: Vec<u8>,
272 author: Vec<u8>,
273 staves: u8,
274}
275
276enum Object {
278 Silent,
280 Clef {
281 name: Option<&'static str>,
282 shift: Option<&'static str>,
283 },
284 Key(&'static str),
285 Bar(u8),
286 Ending(u8),
287 TimeSig(i32, i64),
288 Tempo(i32),
289 Note(Member),
290 Rest(String),
291 Chord(Vec<Member>),
293 RestChord {
295 members: Vec<Member>,
296 rest: String,
297 },
298 Text(Vec<u8>, i32),
299}
300
301#[derive(Clone)]
303struct Member {
304 duration: String,
305 position: String,
306}
307
308impl Object {
309 fn line(&self) -> String {
311 match self {
312 Self::Silent => String::new(),
313 Self::Clef { name, shift } => {
314 let mut line = "|Clef|".to_string();
315 if let Some(name) = name {
316 line.push_str(&format!("Type:{name}|"));
317 }
318 if let Some(shift) = shift {
319 line.push_str(&format!("OctaveShift:{shift}|"));
320 }
321 line
322 }
323 Self::Key(signature) => format!("|Key|Signature:{signature}"),
324 Self::Bar(style) => {
325 let mut line = "|Bar|".to_string();
326 if *style > 0
327 && let Some(name) = BAR_STYLES.get(usize::from(*style))
328 {
329 line.push_str(&format!("|Style:{name}"));
330 }
331 line
332 }
333 Self::Ending(style) => format!("|Ending|Endings:{style}"),
334 Self::TimeSig(numerator, denominator) => {
335 format!("|TimeSig|Signature:{numerator}/{denominator}")
336 }
337 Self::Tempo(value) => format!("|Tempo|Tempo:{value}"),
338 Self::Note(member) => format!("|Note|Dur:{}|Pos:{}|", member.duration, member.position),
339 Self::Rest(duration) => format!("|Rest|Dur:{duration}|"),
340 Self::Chord(members) => {
341 let mut line = "|Chord".to_string();
342 for (index, (duration, positions)) in by_duration(members).iter().enumerate() {
343 let suffix = if index == 0 { "" } else { "2" };
344 line.push_str(&format!("|Dur{suffix}:{duration}|Pos{suffix}:{positions}"));
345 }
346 line
347 }
348 Self::RestChord { members, rest } if members.is_empty() => format!("|Rest|Dur:{rest}"),
349 Self::RestChord { members, rest } => {
350 let mut line = format!("|RestChord|Dur:{rest}");
351 for (duration, positions) in by_duration(members) {
352 line.push_str(&format!("|Dur2:{duration}|Pos2:{positions}"));
353 }
354 line
355 }
356 Self::Text(text, position) => {
357 format!("|Text|Text:{}|Pos:{position}", latin1(text))
358 }
359 }
360 }
361}
362
363struct Bytes<'a> {
365 data: &'a [u8],
366 at: usize,
367 version: u32,
368 alterations: Vec<(i32, String)>,
371}
372
373impl Bytes<'_> {
374 fn ended() -> crate::error::Error {
375 error("the file ends before its last object")
376 }
377
378 fn short(&mut self) -> Result<i32> {
380 let bytes = self
381 .data
382 .get(self.at..self.at + 2)
383 .ok_or_else(Self::ended)?;
384 self.at += 2;
385 Ok(i32::from(i16::from_le_bytes([bytes[0], bytes[1]])))
386 }
387
388 fn byte(&mut self) -> Result<u8> {
390 let byte = *self.data.get(self.at).ok_or_else(Self::ended)?;
391 self.at += 1;
392 Ok(byte)
393 }
394
395 fn signed(&mut self) -> Result<i32> {
397 Ok(i32::from(self.byte()? as i8))
398 }
399
400 fn take(&mut self, count: usize) -> Vec<u8> {
402 let start = self.at.min(self.data.len());
403 let end = (self.at + count).min(self.data.len());
404 self.at += count;
405 self.data[start..end].to_vec()
406 }
407
408 fn skip(&mut self, count: usize) {
409 self.at += count;
410 }
411
412 fn until_nul(&mut self) -> Vec<u8> {
416 let start = self.at.min(self.data.len());
417 match self.data[start..].iter().position(|byte| *byte == 0) {
418 Some(length) => {
419 self.at = start + length + 1;
420 self.data[start..start + length].to_vec()
421 }
422 None => {
423 self.at = 0;
424 self.data[start..].to_vec()
425 }
426 }
427 }
428
429 fn past_nuls(&mut self) {
431 while self.data.get(self.at) == Some(&0) {
432 self.at += 1;
433 }
434 }
435
436 fn header(&mut self) -> Result<Header> {
438 self.at = 45;
439 self.version = match self.short()? {
440 0x0114 => 120,
441 0x011E => 130,
442 0x0132 => 150,
443 0x0137 => 155,
444 0x0146 => 170,
445 0x014B => 175,
446 0x0200 => 200,
447 0x0201 => 201,
448 _ => 201,
450 };
451 self.skip(4);
452 let _user = self.until_nul();
453 let _unknown = self.until_nul();
454 self.skip(10);
455 let title = self.until_nul();
456 let author = self.until_nul();
457 if self.version >= 200 {
458 let _lyricist = self.until_nul();
459 }
460 let _copyright = self.until_nul();
461 let _second_copyright = self.until_nul();
462 let _comment = self.until_nul();
463 let _extend_last_system = self.byte()?;
464 let _increase_note_spacing = self.byte()?;
465 self.take(5);
466 let _measure_numbers = self.byte()?;
467 self.take(1);
468 let _measure_start = self.short()?;
469 if self.version >= 130 {
470 let _margins = self.until_nul();
471 }
472 let _unused = self.byte()?;
473 self.take(2);
474 if self.version >= 130 {
475 self.take(32);
476 let _allow_layering = self.byte()?;
477 }
478 if self.version >= 200 {
479 let _typeface = self.until_nul();
480 }
481 let _staff_height = self.short()?;
482 let fonts = if self.version > 170 {
483 12
484 } else if self.version > 130 {
485 10
486 } else {
487 0
488 };
489 self.past_nuls();
490 self.skip(2);
491 for _ in 0..fonts {
492 let _name = self.until_nul();
493 let _style = self.byte()?;
494 let _size = self.byte()?;
495 let _unused = self.byte()?;
496 let _charset = self.byte()?;
497 }
498 let _title_page = self.byte()?;
499 let _staff_labels = self.byte()?;
500 let _page_number_start = self.short()?;
501 if self.version >= 200 {
502 self.skip(1);
503 }
504 let staves = self.byte()?;
505 self.skip(1);
506 Ok(Header {
507 title,
508 author,
509 staves,
510 })
511 }
512
513 fn staff(&mut self) -> Result<Vec<String>> {
516 let _name = self.until_nul();
517 let mut label = Vec::new();
518 let mut instrument = Vec::new();
519 if self.version >= 200 {
520 label = self.until_nul();
521 instrument = self.until_nul();
522 }
523 let _group = self.until_nul();
524 let mut transposition = None;
525 let mut lyrics = 0;
526 if self.version >= 200 {
527 self.skip(27);
528 let _lines = self.byte()?;
529 let _layer_with_next = self.short()?;
530 transposition = Some(self.short()?);
531 let _volume = self.short()?;
532 let _pan = self.short()?;
533 let _color = self.byte()?;
534 let _align_syllable = self.short()?;
535 lyrics = self.short()?;
536 } else if self.version == 175 {
537 self.skip(11);
538 let patch = usize::from(self.byte()?);
539 let index = if patch > 0 && patch < MIDI_INSTRUMENTS.len() {
540 patch - 1
541 } else {
542 0
543 };
544 instrument = MIDI_INSTRUMENTS[index].bytes().collect();
545 self.skip(10);
546 transposition = Some(self.signed()?);
547 self.skip(6);
548 let _align_syllable = self.short()?;
549 lyrics = self.short()?;
550 }
551 if lyrics > 0 {
552 let _alignment = self.short()?;
553 let _offset = self.short()?;
554 }
555
556 for _ in 0..lyrics {
558 let size = self.short().unwrap_or(0);
559 if size > 0 {
560 let _length = self.short()?;
561 let start = self.at;
562 let _junk = self.short()?;
563 for _ in 0..1000 {
564 if self.until_nul().is_empty() {
565 break;
566 }
567 }
568 self.at = start.saturating_add_signed(size as isize);
569 }
570 }
571 if lyrics > 0 {
572 let _junk = self.short()?;
573 }
574 let _junk = self.short()?;
575
576 let mut count = self.short()?;
577 if self.version > 150 {
578 count -= 2;
579 }
580 let mut objects = Vec::new();
581 for _ in 0..count.max(0) {
582 objects.push(self.object()?);
583 }
584
585 let instrument = if instrument.is_empty() {
586 "Acoustic Grand Piano".to_string()
587 } else {
588 latin1(&instrument)
589 };
590 let label = if label.is_empty() {
591 instrument.clone()
592 } else {
593 latin1(&label)
594 };
595 let mut lines = vec![format!("|AddStaff|Name:{label}")];
596 let mut staff_instrument = format!("|StaffInstrument|Name:{instrument}");
597 if let Some(patch) = MIDI_INSTRUMENTS.iter().position(|name| *name == instrument) {
598 staff_instrument.push_str(&format!("|Patch:{patch}"));
599 }
600 staff_instrument.push_str(&format!(
601 "|Trans:{}",
602 transposition.map_or("None".to_string(), |semitones| semitones.to_string())
603 ));
604 lines.push(staff_instrument);
605 lines.extend(
606 objects
607 .iter()
608 .map(Object::line)
609 .filter(|line| !line.is_empty()),
610 );
611 Ok(lines)
612 }
613
614 fn object(&mut self) -> Result<Object> {
616 let kind = self.short()?;
617 if !(0..19).contains(&kind) {
618 return Err(error(format!(
619 "Cannot translate objectType: {kind}; max is 19"
620 )));
621 }
622 if self.version >= 170 {
623 let _visible = self.byte()?;
624 }
625 Ok(match kind {
626 0 => {
627 let clef = self.short()?;
628 let shift = self.short()?;
629 let clef_count = i32::try_from(CLEF_NAMES.len()).unwrap_or(0);
630 let shift_count = i32::try_from(OCTAVE_SHIFT_NAMES.len()).unwrap_or(0);
631 Object::Clef {
632 name: if clef < clef_count {
633 Some(
634 python_index(&CLEF_NAMES, clef)
635 .ok_or_else(|| error(format!("no clef numbered {clef}")))?,
636 )
637 } else {
638 None
639 },
640 shift: if shift < shift_count {
641 python_index(&OCTAVE_SHIFT_NAMES, shift)
642 .ok_or_else(|| error(format!("no octave shift numbered {shift}")))?
643 } else {
644 None
645 },
646 }
647 }
648 1 => {
649 let flats = self.byte()?;
650 self.skip(1);
651 let sharps = self.byte()?;
652 self.skip(7);
653 let mask = |table: &[(u8, &'static str)], key: u8| {
654 table
655 .iter()
656 .find(|(known, _)| *known == key)
657 .map(|(_, text)| *text)
658 };
659 Object::Key(
660 if flats > 0
661 && let Some(text) = mask(&FLAT_MASK, flats)
662 {
663 text
664 } else if sharps > 0
665 && let Some(text) = mask(&SHARP_MASK, sharps)
666 {
667 text
668 } else {
669 ""
670 },
671 )
672 }
673 2 => {
674 let style = self.byte()?;
675 let _repeats = self.byte()?;
676 self.alterations.clear();
677 Object::Bar(style)
678 }
679 3 => {
680 let style = self.byte()?;
681 self.skip(1);
682 Object::Ending(style)
683 }
684 4 => {
685 self.skip(8);
686 Object::Silent
687 }
688 5 => {
689 let numerator = self.short()?;
690 let bits = self.short()?;
691 let _style = self.short()?;
692 let denominator = u32::try_from(bits)
693 .ok()
694 .and_then(|bits| 1_i64.checked_shl(bits))
695 .ok_or_else(|| error(format!("a meter of 2 to the power {bits}")))?;
696 Object::TimeSig(numerator, denominator)
697 }
698 6 => {
699 let _position = self.byte()?;
700 let _placement = self.byte()?;
701 let value = self.short()?;
702 let _base = self.byte()?;
703 if self.version < 170 {
704 let _junk = self.short()?;
705 }
706 let _text = self.until_nul();
707 Object::Tempo(value)
708 }
709 7 => {
710 if self.version >= 170 {
711 self.take(3);
712 let _velocity = self.short()?;
713 let _volume = self.short()?;
714 }
715 Object::Silent
716 }
717 8 => Object::Note(self.note()?),
718 9 => {
719 if self.version <= 150 {
720 return Err(error("a rest in a file older than version 1.70"));
721 }
722 let duration = self.byte()?;
723 let data = self.take(5);
724 let _offset = self.short()?;
725 Object::Rest(duration_text(duration, &data, None)?)
726 }
727 10 => Object::Chord(self.chord()?.0),
728 11 => {
729 if self.version >= 170 {
730 self.take(3);
731 }
732 Object::Silent
733 }
734 12 => {
735 if self.version >= 170 {
736 self.take(2);
737 }
738 let _style = self.short()?;
739 Object::Silent
740 }
741 13 => {
742 self.take(2);
743 if self.version == 175 || self.version <= 155 {
744 self.take(32);
745 } else {
746 self.take(31);
747 }
748 Object::Silent
749 }
750 14 => {
751 self.take(4);
752 Object::Silent
753 }
754 15 | 16 => {
755 let _position = self.byte()?;
756 if self.version >= 170 {
757 let _placement = self.byte()?;
758 }
759 let _style = self.byte()?;
760 Object::Silent
761 }
762 17 => {
763 let position = self.signed()?;
764 let _data = self.byte()?;
765 let _font = self.byte()?;
766 Object::Text(self.until_nul(), position)
767 }
768 _ => {
769 let (members, data) = if 170 < self.version && self.version < 200 {
773 self.chord()?
774 } else {
775 let data = self.take(8);
776 let notes = usize::try_from(self.short()?).unwrap_or(0);
777 (self.members(notes)?, data)
778 };
779 let duration = *data.first().ok_or_else(Self::ended)?;
780 let rest_data = data.get(1..6).ok_or_else(Self::ended)?;
781 Object::RestChord {
782 members,
783 rest: duration_text(duration, rest_data, None)?,
784 }
785 }
786 })
787 }
788
789 fn note(&mut self) -> Result<Member> {
792 if self.version < 170 {
793 return Err(error("a note in a file older than version 1.70"));
794 }
795 let duration = self.byte()?;
796 let data = self.take(3);
797 let attributes = self.take(2);
798 let position = -self.signed()?;
799 let second = self.byte()?;
800 if self.version <= 170 {
801 self.take(2);
802 }
803 if self.version >= 200 && second & 0x40 != 0 {
804 let _stem = self.byte()?;
805 }
806 let duration = duration_text(duration, &data, Some(&attributes))?;
807 let mut alteration = ALTERATION_TEXTS
808 .get(usize::from(second & 0x07))
809 .copied()
810 .unwrap_or("")
811 .to_string();
812 let place = position.rem_euclid(7);
813 if alteration.is_empty() {
814 alteration = self
815 .alterations
816 .iter()
817 .find(|(known, _)| *known == place)
818 .map(|(_, text)| text.clone())
819 .unwrap_or_default();
820 }
821 self.alterations.retain(|(known, _)| *known != place);
822 self.alterations.push((place, alteration.clone()));
823 let tie = if attributes.first().is_some_and(|first| first & 0x10 != 0) {
824 "^"
825 } else {
826 ""
827 };
828 Ok(Member {
829 duration,
830 position: format!("{alteration}{position}{tie}"),
831 })
832 }
833
834 fn chord(&mut self) -> Result<(Vec<Member>, Vec<u8>)> {
836 let mut notes = 0;
837 let data = if self.version <= 170 {
838 let data = self.take(12);
839 notes = usize::try_from(self.short()?).unwrap_or(0);
840 data
841 } else if self.version == 175 {
842 let data = self.take(10);
843 notes = usize::from(*data.get(8).ok_or_else(Self::ended)?);
844 data
845 } else {
846 self.take(8)
847 };
848 if self.version >= 200 {
849 if data.get(7).ok_or_else(Self::ended)? & 0x40 != 0 {
850 let _stem = self.byte()?;
851 }
852 notes = usize::try_from(self.short()?).unwrap_or(0);
853 }
854 Ok((self.members(notes)?, data))
855 }
856
857 fn members(&mut self, notes: usize) -> Result<Vec<Member>> {
859 let mut members = Vec::new();
860 for _ in 0..notes {
861 match self.object()? {
862 Object::Note(member) => members.push(member),
863 Object::Rest(duration) => members.push(Member {
866 duration,
867 position: "0".to_string(),
868 }),
869 _ => return Err(error("a chord holding something other than a note")),
870 }
871 }
872 Ok(members)
873 }
874}
875
876fn by_duration(members: &[Member]) -> Vec<(&str, String)> {
879 let mut groups: Vec<(&str, Vec<&str>)> = Vec::new();
880 for member in members {
881 match groups
882 .iter_mut()
883 .find(|(duration, _)| *duration == member.duration)
884 {
885 Some((_, positions)) => positions.push(&member.position),
886 None => groups.push((&member.duration, vec![&member.position])),
887 }
888 }
889 groups
890 .into_iter()
891 .map(|(duration, positions)| (duration, positions.join(",")))
892 .collect()
893}
894
895fn duration_text(duration: u8, data: &[u8], attributes: Option<&[u8]>) -> Result<String> {
899 let mut text = DURATION_VALUES
900 .get(usize::from(duration))
901 .ok_or_else(|| error(format!("no note length numbered {duration}")))?
902 .to_string();
903 let (dots, grace) = match attributes {
904 Some(attributes) => (
905 *attributes.first().ok_or_else(Bytes::ended)?,
906 attributes.get(1).ok_or_else(Bytes::ended)? & 0x20,
907 ),
908 None => (*data.get(3).ok_or_else(Bytes::ended)?, 0),
909 };
910 let triplet = data.get(1).ok_or_else(Bytes::ended)? & 0x0c;
911 if dots & 0x01 != 0 {
912 text.push_str(",DblDotted");
913 } else if dots & 0x04 != 0 {
914 text.push_str(",Dotted");
915 }
916 if grace != 0 {
917 text.push_str(",Grace");
918 }
919 if triplet != 0 {
920 text.push_str(",Triplet");
921 }
922 Ok(text)
923}
924
925#[cfg(test)]
926mod tests {
927 use super::*;
928
929 #[test]
930 fn a_compressed_file_is_left_to_the_caller_to_inflate() {
931 let error = nwc_lines(b"[NWZ]\x00\x78\x9c").unwrap_err();
932 assert!(error.to_string().contains("inflate"), "{error}");
933 }
934
935 #[test]
936 fn a_table_is_indexed_as_python_indexes_one() {
937 assert_eq!(python_index(&CLEF_NAMES, 1), Some("Bass"));
938 assert_eq!(python_index(&CLEF_NAMES, -1), Some("Percussion"));
939 assert_eq!(python_index(&CLEF_NAMES, 5), None);
940 assert_eq!(python_index(&CLEF_NAMES, -6), None);
941 }
942
943 fn read(data: &[u8]) -> String {
944 let mut bytes = Bytes {
945 data,
946 at: 0,
947 version: 201,
948 alterations: Vec::new(),
949 };
950 bytes.object().unwrap().line()
951 }
952
953 #[test]
954 fn a_tenor_and_a_percussion_clef_are_named() {
955 assert_eq!(read(&[0, 0, 0, 3, 0, 0, 0]), "|Clef|Type:Tenor|");
957 assert_eq!(read(&[0, 0, 0, 4, 0, 0, 0]), "|Clef|Type:Percussion|");
958 }
959
960 #[test]
961 fn a_version_two_chord_reads_its_notes() {
962 let mut chord = vec![10, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0];
965 for position in [0u8, (-2i8) as u8] {
966 chord.extend([8, 0, 0, 2, 0, 0, 0, 0, 0, position, 5]);
967 }
968 assert_eq!(read(&chord), "|Chord|Dur:4th|Pos:0,2");
969 }
970
971 #[test]
972 fn a_chord_groups_its_notes_by_length() {
973 let member = |duration: &str, position: &str| Member {
974 duration: duration.to_string(),
975 position: position.to_string(),
976 };
977 let chord = Object::Chord(vec![
978 member("4th", "1"),
979 member("Half", "3"),
980 member("4th", "#5^"),
981 ]);
982 assert_eq!(chord.line(), "|Chord|Dur:4th|Pos:1,#5^|Dur2:Half|Pos2:3");
983 }
984
985 #[test]
986 fn a_version_two_rest_chord_gives_its_rest_and_then_its_notes() {
987 let mut chord = vec![18, 0, 0, 1, 0, 0, 0, 0, 0, 0xfe, 0xff, 1, 0];
990 chord.extend([8, 0, 0, 2, 0, 0, 0, 0, 0, 0, 5]);
991 assert_eq!(read(&chord), "|RestChord|Dur:Half|Dur2:4th|Pos2:0");
992 assert_eq!(
994 read(&[18, 0, 0, 1, 0, 0, 0, 0x04, 0, 0, 0, 0, 0]),
995 "|Rest|Dur:Half,Dotted"
996 );
997 }
998
999 #[test]
1000 fn a_length_says_its_dots_grace_and_triplet() {
1001 assert_eq!(
1002 duration_text(2, &[0, 0, 0, 0x04], None).unwrap(),
1003 "4th,Dotted"
1004 );
1005 assert_eq!(
1006 duration_text(3, &[0, 0x04, 0], Some(&[0x01, 0x20])).unwrap(),
1007 "8th,DblDotted,Grace,Triplet"
1008 );
1009 assert!(duration_text(7, &[0, 0, 0, 0], None).is_err());
1010 }
1011}