Skip to main content

music21_rs/noteworthy/
binary.rs

1//! NoteWorthy Composer's binary format, `.nwc`: music21's
2//! `noteworthy.binaryTranslate`, which reads a file's staves and objects and
3//! writes each out as a line of the text format for the text reader.
4
5use super::{error, from_lines};
6use crate::error::Result;
7use crate::stream::Stream;
8
9/// What a compressed file starts with; zlib's stream follows.
10const COMPRESSED: &[u8] = b"[NWZ]\x00";
11
12/// Reads a NoteWorthy Composer `.nwc` file's bytes into a score, as music21's
13/// `converter.parse` reads one: each staff and object written out as a line
14/// of the text format ([`nwc_lines`]) and those read by
15/// [`super::from_noteworthy`]'s reader.
16///
17/// The file must be uncompressed. A compressed one starts `[NWZ]` and a nul
18/// byte, and what follows is a zlib stream: inflate it first, as this crate
19/// unpacks nothing.
20///
21/// music21 calls this reader beta, and what it leaves out is left out here:
22/// lyrics, dynamics, flow marks and performance marks are read past and not
23/// written.
24///
25/// # Errors
26///
27/// A compressed file, a file that ends early, an object of a kind music21
28/// does not know, a note or rest in a file older than music21 reads, or
29/// anything the text reader refuses.
30pub fn from_nwc(bytes: &[u8]) -> Result<Stream> {
31    let lines = nwc_lines(bytes)?;
32    from_lines(lines.iter().map(String::as_str))
33}
34
35/// The lines of NoteWorthy text a `.nwc` file's bytes stand for, as
36/// music21's `dumpToNWCText` writes them: the title and author, then for
37/// each staff its name, its instrument and its objects.
38///
39/// # Errors
40///
41/// As [`from_nwc`], but for what only the text reader refuses.
42pub 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
73/// Bytes read as latin-1, as music21 decodes every string in the file.
74fn latin1(bytes: &[u8]) -> String {
75    bytes.iter().map(|byte| char::from(*byte)).collect()
76}
77
78/// An index into a table as Python takes one: a negative one counts from
79/// the end.
80fn 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
88/// music21's `constants.ClefNames`.
89const CLEF_NAMES: [&str; 5] = ["Treble", "Bass", "Alto", "Tenor", "Percussion"];
90
91/// music21's `constants.OctaveShiftNames`.
92const OCTAVE_SHIFT_NAMES: [Option<&str>; 3] = [None, Some("Octave Up"), Some("Octave Down")];
93
94/// music21's `constants.FlatMask`.
95const 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
106/// music21's `constants.SharpMask`.
107const 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
118/// music21's `constants.AlterationTexts`: `x` is a double sharp, `v` a
119/// double flat.
120const ALTERATION_TEXTS: [&str; 6] = ["#", "b", "n", "x", "v", ""];
121
122/// music21's `constants.BarStyles`.
123const BAR_STYLES: [&str; 8] = [
124    "Single",
125    "Double",
126    "SectionOpen",
127    "SectionClose",
128    "LocalRepeatOpen",
129    "LocalRepeatClose",
130    "MasterRepeatOpen",
131    "MasterRepeatClose",
132];
133
134/// music21's `constants.DurationValues`.
135const DURATION_VALUES: [&str; 7] = ["Whole", "Half", "4th", "8th", "16th", "32nd", "64th"];
136
137/// music21's `constants.MidiInstruments`, the General MIDI names by program.
138const 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
269/// What the header says that the lines are written from.
270struct Header {
271    title: Vec<u8>,
272    author: Vec<u8>,
273    staves: u8,
274}
275
276/// One object of a staff, as much of it as is written out.
277enum Object {
278    /// Read past and written as nothing.
279    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    /// A chord's notes.
292    Chord(Vec<Member>),
293    /// A rest's length and the notes sounding with it.
294    RestChord {
295        members: Vec<Member>,
296        rest: String,
297    },
298    Text(Vec<u8>, i32),
299}
300
301/// A note as a chord writes it: its `Dur` and its `Pos`.
302#[derive(Clone)]
303struct Member {
304    duration: String,
305    position: String,
306}
307
308impl Object {
309    /// music21's `dumpMethod`: the text line, or nothing.
310    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
363/// music21's `NWCConverter`: the file and where it has got to in it.
364struct Bytes<'a> {
365    data: &'a [u8],
366    at: usize,
367    version: u32,
368    /// The alteration last written on each staff position, by the position
369    /// modulo seven, until a barline.
370    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    /// A little-endian signed short: `readLEShort`.
379    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    /// `byteToInt`.
389    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    /// `byteToSignedInt`.
396    fn signed(&mut self) -> Result<i32> {
397        Ok(i32::from(self.byte()? as i8))
398    }
399
400    /// `readBytes`: as many of the next `count` bytes as there are.
401    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    /// `readToNUL`: the bytes up to the next nul, which is passed. With no
413    /// nul left, the rest -- and music21 then starts again from the file's
414    /// first byte.
415    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    /// `advanceToNotNUL`.
430    fn past_nuls(&mut self) {
431        while self.data.get(self.at) == Some(&0) {
432            self.at += 1;
433        }
434    }
435
436    /// `parseHeader`, with `fileVersion` read where music21 reads it.
437    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            // Most likely a newer version.
449            _ => 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    /// One staff, `NWCStaff.parse`, written out as `NWCStaff.dump` writes
514    /// it.
515    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        // `parseLyrics`: read past, as music21 writes none of them out.
557        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    /// `NWCObject.parse`: an object of the type its first short names.
615    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                // Only a file of version 1.75 writes a rest chord as it
770                // writes a chord; the others give the rest's length, its
771                // five bytes and its offset, then how many notes follow.
772                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    /// `note`: a note's length, its position on the staff with the
790    /// alteration in force there, and whether it is tied on.
791    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    /// `noteChordMember`: the chord's own bytes and its notes.
835    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    /// `_readChordNotes`: the notes of a chord.
858    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                // A rest is written at position nought, as music21's
864                // object starts out.
865                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
876/// `_positionsByDuration`: a chord's notes grouped by length, in the order
877/// each length first appears, with their positions joined.
878fn 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
895/// `setDurationForObject`: a note's or rest's `Dur` text, from its length
896/// and the bytes saying its dots, whether it is a grace note (a note's
897/// `attributes`) and whether it is a triplet.
898fn 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        // An object of kind 0, shown, a clef type and no octave shift.
956        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        // A chord of quarters and its count, then two notes on positions
963        // nought and minus two.
964        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        // A half rest, its five bytes, an offset of minus two and a count,
988        // then a quarter on position nought.
989        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        // With no notes it is a rest, dotted where its fourth byte says so.
993        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}