1use super::{
2 duration::Duration,
3 notation::{
4 Articulation, Barline, BeamState, ChordSymbol, Clef, CrossStaff, Dynamic, GuitarTechnique,
5 HairpinKind, KeySignature, Lyric, NoteHead, OttavaKind, StyledText, TimeSignature,
6 TupletInfo,
7 },
8 pitch::Pitch,
9};
10use crate::Error;
11use serde::{Deserialize, Serialize};
12use uuid::Uuid;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct ScoreMetadata {
16 pub title: String,
17 pub composer: String,
18 pub lyricist: String,
19 pub copyright: String,
20 pub work_number: String,
21 pub movement_title: String,
22}
23
24impl Default for ScoreMetadata {
25 fn default() -> Self {
26 Self {
27 title: "Untitled Score".to_string(),
28 composer: String::new(),
29 lyricist: String::new(),
30 copyright: String::new(),
31 work_number: String::new(),
32 movement_title: String::new(),
33 }
34 }
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct ScoreSettings {
39 pub tempo_bpm: u16,
40 pub time_signature: TimeSignature,
41 pub key_signature: KeySignature,
42}
43
44impl Default for ScoreSettings {
45 fn default() -> Self {
46 Self {
47 tempo_bpm: 120,
48 time_signature: TimeSignature::default(),
49 key_signature: KeySignature::default(),
50 }
51 }
52}
53
54#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
56pub enum PartGroupSymbol {
57 Bracket, Brace, Line, }
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct PartGroup {
65 pub first_part: usize,
67 pub last_part: usize,
69 pub symbol: PartGroupSymbol,
70 #[serde(default)]
72 pub barlines_connect: bool,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct Score {
77 pub id: String,
78 #[serde(default)]
80 pub schema_version: u32,
81 pub metadata: ScoreMetadata,
82 pub settings: ScoreSettings,
83 pub parts: Vec<Part>,
84 #[serde(default)]
85 pub part_groups: Vec<PartGroup>,
86 #[serde(default)]
88 pub texts: Vec<StyledText>,
89}
90
91impl Default for Score {
92 fn default() -> Self {
93 let mut part = Part::new("Piano", "Pno.");
94 part.staves.push(Staff::new(Clef::Treble));
95 for _ in 0..4 {
96 part.staves[0].measures.push(Measure::empty(4, 4));
97 }
98 Self {
99 id: Uuid::new_v4().to_string(),
100 schema_version: 1,
101 metadata: ScoreMetadata::default(),
102 settings: ScoreSettings::default(),
103 parts: vec![part],
104 part_groups: Vec::new(),
105 texts: Vec::new(),
106 }
107 }
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
112pub enum ScoreTemplate {
113 Solo,
115 Piano,
117 StringQuartet,
119 StringOrchestra,
121 BrassQuintet,
123}
124
125impl Score {
126 pub fn new(
127 title: &str,
128 tempo_bpm: u16,
129 numerator: u8,
130 denominator: u8,
131 fifths: i8,
132 measure_count: u32,
133 ) -> Self {
134 let mut score = Score::default();
135 score.metadata.title = title.to_string();
136 score.settings.tempo_bpm = tempo_bpm;
137 score.settings.time_signature = TimeSignature {
138 numerator,
139 denominator,
140 };
141 score.settings.key_signature = KeySignature {
142 fifths,
143 mode: "major".to_string(),
144 };
145
146 score.parts[0].staves[0].measures.clear();
147 for i in 0..measure_count {
148 let mut m = Measure::empty(numerator, denominator);
149 m.number = i + 1;
150 score.parts[0].staves[0].measures.push(m);
151 }
152 score
153 }
154
155 pub fn template(kind: ScoreTemplate) -> Self {
160 fn measures(num: u8, den: u8, count: u32) -> Vec<Measure> {
161 (0..count)
162 .map(|i| {
163 let mut m = Measure::empty(num, den);
164 m.number = i + 1;
165 m
166 })
167 .collect()
168 }
169 fn part(name: &str, short: &str, clef: Clef, program: u8) -> Part {
170 let mut p = Part::new(name, short);
171 p.midi_program = program;
172 let mut s = Staff::new(clef);
173 s.measures = measures(4, 4, 4);
174 p.staves.push(s);
175 p
176 }
177
178 let mut score = Score {
179 id: uuid::Uuid::new_v4().to_string(),
180 schema_version: 1,
181 metadata: ScoreMetadata::default(),
182 settings: ScoreSettings::default(),
183 parts: Vec::new(),
184 part_groups: Vec::new(),
185 texts: Vec::new(),
186 };
187
188 match kind {
189 ScoreTemplate::Solo => {
190 score.parts.push(part("Piano", "Pno.", Clef::Treble, 0));
191 }
192 ScoreTemplate::Piano => {
193 let mut p = Part::new("Piano", "Pno.");
194 p.midi_program = 0;
195 let mut treble = Staff::new(Clef::Treble);
196 treble.measures = measures(4, 4, 4);
197 let mut bass = Staff::new(Clef::Bass);
198 bass.measures = measures(4, 4, 4);
199 p.staves.push(treble);
200 p.staves.push(bass);
201 score.parts.push(p);
202 }
203 ScoreTemplate::StringQuartet => {
204 score
205 .parts
206 .push(part("Violin I", "Vn. I", Clef::Treble, 40));
207 score
208 .parts
209 .push(part("Violin II", "Vn. II", Clef::Treble, 40));
210 score.parts.push(part("Viola", "Va.", Clef::Alto, 41));
211 score.parts.push(part("Cello", "Vc.", Clef::Bass, 42));
212 }
213 ScoreTemplate::StringOrchestra => {
214 score
215 .parts
216 .push(part("Violin I", "Vn. I", Clef::Treble, 40));
217 score
218 .parts
219 .push(part("Violin II", "Vn. II", Clef::Treble, 40));
220 score.parts.push(part("Viola", "Va.", Clef::Alto, 41));
221 score.parts.push(part("Cello", "Vc.", Clef::Bass, 42));
222 score.parts.push(part("Contrabass", "Cb.", Clef::Bass, 43));
223 }
224 ScoreTemplate::BrassQuintet => {
225 score
226 .parts
227 .push(part("Trumpet I", "Tpt. I", Clef::Treble, 56));
228 score
229 .parts
230 .push(part("Trumpet II", "Tpt. II", Clef::Treble, 56));
231 score
232 .parts
233 .push(part("French Horn", "Hn.", Clef::Treble, 60));
234 score.parts.push(part("Trombone", "Tbn.", Clef::Bass, 57));
235 score.parts.push(part("Tuba", "Tba.", Clef::Bass, 58));
236 }
237 }
238 score
239 }
240
241 pub fn measure_count(&self) -> usize {
242 self.parts
243 .first()
244 .and_then(|p| p.staves.first())
245 .map(|s| s.measures.len())
246 .unwrap_or(0)
247 }
248
249 pub fn statistics(&self) -> ScoreStats {
251 let measure_count = self.measure_count();
252 let part_count = self.parts.len();
253
254 let seq = measure_sequence(self);
256 let total_beats: f64 = self
257 .parts
258 .first()
259 .and_then(|p| p.staves.first())
260 .map(|s| {
261 seq.iter()
262 .filter_map(|&idx| s.measures.get(idx))
263 .flat_map(|m| m.voices.iter().flat_map(|v| v.iter()))
264 .map(|n| n.beats())
265 .sum()
266 })
267 .unwrap_or(0.0);
268
269 let mut note_count = 0usize;
270 let mut rest_count = 0usize;
271 for part in &self.parts {
272 for staff in &part.staves {
273 for measure in &staff.measures {
274 for voice in &measure.voices {
275 for note in voice {
276 if note.is_rest {
277 rest_count += 1;
278 } else {
279 note_count += 1;
280 }
281 }
282 }
283 }
284 }
285 }
286
287 let bpm = self.settings.tempo_bpm as f64;
288 let estimated_duration_secs = if bpm > 0.0 {
289 total_beats / bpm * 60.0
290 } else {
291 0.0
292 };
293
294 ScoreStats {
295 measure_count,
296 note_count,
297 rest_count,
298 part_count,
299 estimated_duration_secs,
300 }
301 }
302
303 pub fn extract_part(&self, part_index: usize) -> Option<Score> {
306 let part = self.parts.get(part_index)?.clone();
307 Some(Score {
308 id: Uuid::new_v4().to_string(),
309 schema_version: 1,
310 metadata: self.metadata.clone(),
311 settings: self.settings.clone(),
312 parts: vec![part],
313 part_groups: Vec::new(),
314 texts: self.texts.clone(),
315 })
316 }
317
318 pub fn merge(&self, other: &Score) -> Score {
322 let self_count = self.measure_count();
323 let other_count = other.measure_count();
324 let max_count = self_count.max(other_count);
325 let ts = self.settings.time_signature.clone();
326
327 let pad = |mut part: Part, from: usize| -> Part {
328 for staff in &mut part.staves {
329 for i in from..max_count {
330 let mut m = Measure::empty(ts.numerator, ts.denominator);
331 m.number = i as u32 + 1;
332 staff.measures.push(m);
333 }
334 }
335 part
336 };
337
338 let mut parts: Vec<Part> = self
339 .parts
340 .iter()
341 .cloned()
342 .map(|p| pad(p, self_count))
343 .collect();
344 for p in &other.parts {
345 parts.push(pad(p.clone(), other_count));
346 }
347
348 Score {
349 id: Uuid::new_v4().to_string(),
350 schema_version: 1,
351 metadata: self.metadata.clone(),
352 settings: self.settings.clone(),
353 parts,
354 part_groups: Vec::new(),
355 texts: self.texts.clone(),
356 }
357 }
358}
359
360#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
362pub struct ScoreStats {
363 pub measure_count: usize,
364 pub note_count: usize,
366 pub rest_count: usize,
367 pub part_count: usize,
368 pub estimated_duration_secs: f64,
370}
371
372use super::pitch::Step;
375use super::repeat::measure_sequence;
376
377pub fn transpose(score: &Score, semitones: i8) -> Score {
381 if semitones == 0 {
382 return score.clone();
383 }
384 let mut out = score.clone();
385 out.settings.key_signature.fifths = transpose_fifths(
386 score.settings.key_signature.fifths,
387 &score.settings.key_signature.mode,
388 semitones,
389 );
390 for part in &mut out.parts {
391 for staff in &mut part.staves {
392 for measure in &mut staff.measures {
393 if let Some(ref mut ks) = measure.key_sig {
394 ks.fifths = transpose_fifths(ks.fifths, &ks.mode, semitones);
395 }
396 for voice in &mut measure.voices {
397 for note in voice.iter_mut() {
398 for pitch in note.pitches.iter_mut() {
399 *pitch = transpose_pitch(pitch, semitones);
400 }
401 }
402 }
403 }
404 }
405 }
406 out
407}
408
409fn transpose_pitch(pitch: &Pitch, semitones: i8) -> Pitch {
410 let new_midi = (pitch.to_midi() + semitones as i16).clamp(0, 127) as u8;
411 let pc = new_midi % 12;
412 let oct = (new_midi / 12) as i8 - 1;
413 let (step, alter): (Step, i8) = if semitones >= 0 {
414 match pc {
415 0 => (Step::C, 0),
416 1 => (Step::C, 1),
417 2 => (Step::D, 0),
418 3 => (Step::D, 1),
419 4 => (Step::E, 0),
420 5 => (Step::F, 0),
421 6 => (Step::F, 1),
422 7 => (Step::G, 0),
423 8 => (Step::G, 1),
424 9 => (Step::A, 0),
425 10 => (Step::A, 1),
426 11 => (Step::B, 0),
427 _ => (Step::C, 0),
428 }
429 } else {
430 match pc {
431 0 => (Step::C, 0),
432 1 => (Step::D, -1),
433 2 => (Step::D, 0),
434 3 => (Step::E, -1),
435 4 => (Step::E, 0),
436 5 => (Step::F, 0),
437 6 => (Step::G, -1),
438 7 => (Step::G, 0),
439 8 => (Step::A, -1),
440 9 => (Step::A, 0),
441 10 => (Step::B, -1),
442 11 => (Step::B, 0),
443 _ => (Step::C, 0),
444 }
445 };
446 Pitch::with_alter(step, oct, alter)
447}
448
449fn transpose_fifths(fifths: i8, mode: &str, semitones: i8) -> i8 {
455 let tonic_major_pc = ((fifths as i32 * 7).rem_euclid(12)) as u8;
456 let tonic_pc = if mode == "minor" {
457 ((tonic_major_pc as i32 + 9).rem_euclid(12)) as u8
458 } else {
459 tonic_major_pc
460 };
461 let new_tonic = ((tonic_pc as i32 + semitones as i32).rem_euclid(12)) as u8;
462 let major_tonic = if mode == "minor" {
463 ((new_tonic as i32 + 3).rem_euclid(12)) as u8
464 } else {
465 new_tonic
466 };
467 let raw = ((major_tonic as i32 * 7).rem_euclid(12)) as i8;
468 if raw > 6 { raw - 12 } else { raw }
469}
470
471#[derive(Debug, Clone, Serialize, Deserialize)]
472pub struct Part {
473 pub id: String,
474 pub name: String,
475 pub short_name: String,
476 pub staves: Vec<Staff>,
477 #[serde(default)]
479 pub midi_channel: u8,
480 #[serde(default)]
482 pub midi_program: u8,
483}
484
485impl Part {
486 pub fn new(name: &str, short_name: &str) -> Self {
487 Self {
488 id: Uuid::new_v4().to_string(),
489 name: name.to_string(),
490 short_name: short_name.to_string(),
491 staves: Vec::new(),
492 midi_channel: 0,
493 midi_program: 0,
494 }
495 }
496}
497
498#[derive(Debug, Clone, Serialize, Deserialize)]
499pub struct Staff {
500 pub clef: Clef,
501 pub measures: Vec<Measure>,
502 #[serde(default)]
505 pub transpose_semitones: i8,
506}
507
508impl Staff {
509 pub fn new(clef: Clef) -> Self {
510 Self {
511 clef,
512 measures: Vec::new(),
513 transpose_semitones: 0,
514 }
515 }
516}
517
518#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
519pub struct VoltaBracket {
520 pub number: u8,
522 pub kind: String,
524}
525
526#[derive(Debug, Clone, Serialize, Deserialize)]
527pub struct Measure {
528 pub number: u32,
529 pub time_sig: Option<TimeSignature>,
530 pub key_sig: Option<KeySignature>,
531 pub clef: Option<Clef>,
532 pub tempo: Option<u16>,
533 pub barline_left: Barline,
534 pub barline_right: Barline,
535 #[serde(default)]
536 pub volta: Option<VoltaBracket>,
537 #[serde(default)]
538 pub tempo_text: Option<String>,
539 #[serde(default)]
540 pub rehearsal: Option<String>,
541 #[serde(default)]
544 pub navigation: Option<String>,
545 #[serde(default)]
547 pub expression_text: Option<String>,
548 #[serde(default)]
549 pub texts: Vec<StyledText>,
550 #[serde(default)]
552 pub multi_rest_count: Option<u8>,
553 #[serde(default)]
555 pub system_break: bool,
556 #[serde(default)]
558 pub page_break: bool,
559 pub voices: [Vec<Note>; 4],
561}
562
563impl Measure {
564 pub fn empty(numerator: u8, denominator: u8) -> Self {
565 let total_beats = TimeSignature {
566 numerator,
567 denominator,
568 }
569 .total_beats();
570 let mut voice0: Vec<Note> = Vec::new();
571 let mut remaining = total_beats;
572 while remaining > 1e-9 {
573 let dur = Duration::whole_filling_beats(remaining);
574 remaining -= dur.beats(0);
575 voice0.push(Note::rest(dur));
576 }
577 Self {
578 number: 0,
579 time_sig: None,
580 key_sig: None,
581 clef: None,
582 tempo: None,
583 barline_left: Barline::Normal,
584 barline_right: Barline::Normal,
585 volta: None,
586 tempo_text: None,
587 rehearsal: None,
588 navigation: None,
589 expression_text: None,
590 texts: Vec::new(),
591 multi_rest_count: None,
592 system_break: false,
593 page_break: false,
594 voices: [voice0, vec![], vec![], vec![]],
595 }
596 }
597
598 pub fn renumber(&mut self, n: u32) {
599 self.number = n;
600 }
601}
602
603#[derive(Debug, Clone, Serialize, Deserialize)]
604pub struct Note {
605 pub id: String,
606 pub is_rest: bool,
607 pub pitches: Vec<Pitch>,
609 pub duration: Duration,
610 pub dot_count: u8,
611 pub tie_start: bool,
612 pub tie_end: bool,
613 pub beam: BeamState,
614 pub articulations: Vec<Articulation>,
615 pub dynamic: Option<Dynamic>,
616 pub stem_up: Option<bool>,
617 #[serde(default)]
618 pub hairpin_start: Option<HairpinKind>,
619 #[serde(default)]
620 pub hairpin_end: bool,
621 #[serde(default)]
622 pub tuplet: Option<TupletInfo>,
623 #[serde(default)]
624 pub chord_symbol: Option<ChordSymbol>,
625 #[serde(default)]
626 pub is_grace: bool,
627 #[serde(default)]
629 pub grace_slash: bool,
630 #[serde(default)]
631 pub ottava_start: Option<OttavaKind>,
632 #[serde(default)]
633 pub ottava_end: bool,
634 #[serde(default)]
635 pub lyric: Option<Lyric>,
636 #[serde(default)]
637 pub pedal_start: bool,
638 #[serde(default)]
639 pub pedal_end: bool,
640 #[serde(default)]
641 pub slur_start: bool,
642 #[serde(default)]
643 pub slur_end: bool,
644 #[serde(default)]
646 pub arpeggiate: Option<bool>,
647 #[serde(default)]
649 pub technique_text: Option<String>,
650 #[serde(default)]
651 pub glissando_start: bool,
652 #[serde(default)]
653 pub glissando_end: bool,
654 #[serde(default)]
655 pub cross_staff: Option<CrossStaff>,
656 #[serde(default)]
658 pub fingering: Option<u8>,
659 #[serde(default)]
661 pub string_number: Option<u8>,
662 #[serde(default)]
663 pub note_head: NoteHead,
664 #[serde(default)]
666 pub is_cue: bool,
667 #[serde(default)]
669 pub trill_line_start: bool,
670 #[serde(default)]
672 pub trill_line_end: bool,
673 #[serde(default)]
675 pub guitar_technique: Option<GuitarTechnique>,
676}
677
678impl Note {
679 pub fn new(pitch: Pitch, duration: Duration) -> Self {
680 Self {
681 id: Uuid::new_v4().to_string(),
682 is_rest: false,
683 pitches: vec![pitch],
684 duration,
685 dot_count: 0,
686 tie_start: false,
687 tie_end: false,
688 beam: BeamState::None,
689 articulations: Vec::new(),
690 dynamic: None,
691 stem_up: None,
692 hairpin_start: None,
693 hairpin_end: false,
694 tuplet: None,
695 chord_symbol: None,
696 is_grace: false,
697 grace_slash: false,
698 ottava_start: None,
699 ottava_end: false,
700 lyric: None,
701 pedal_start: false,
702 pedal_end: false,
703 slur_start: false,
704 slur_end: false,
705 arpeggiate: None,
706 technique_text: None,
707 glissando_start: false,
708 glissando_end: false,
709 cross_staff: None,
710 fingering: None,
711 string_number: None,
712 note_head: NoteHead::Normal,
713 is_cue: false,
714 trill_line_start: false,
715 trill_line_end: false,
716 guitar_technique: None,
717 }
718 }
719
720 pub fn rest(duration: Duration) -> Self {
721 Self {
722 id: Uuid::new_v4().to_string(),
723 is_rest: true,
724 pitches: Vec::new(),
725 duration,
726 dot_count: 0,
727 tie_start: false,
728 tie_end: false,
729 beam: BeamState::None,
730 articulations: Vec::new(),
731 dynamic: None,
732 stem_up: None,
733 hairpin_start: None,
734 hairpin_end: false,
735 tuplet: None,
736 chord_symbol: None,
737 is_grace: false,
738 grace_slash: false,
739 ottava_start: None,
740 ottava_end: false,
741 lyric: None,
742 pedal_start: false,
743 pedal_end: false,
744 slur_start: false,
745 slur_end: false,
746 arpeggiate: None,
747 technique_text: None,
748 glissando_start: false,
749 glissando_end: false,
750 cross_staff: None,
751 fingering: None,
752 string_number: None,
753 note_head: NoteHead::Normal,
754 is_cue: false,
755 trill_line_start: false,
756 trill_line_end: false,
757 guitar_technique: None,
758 }
759 }
760
761 pub fn beats(&self) -> f64 {
762 if self.is_grace || self.is_cue {
763 return 0.0;
764 }
765 let base = self.duration.beats(self.dot_count);
766 if let Some(ref t) = self.tuplet {
767 base * (t.normal_notes as f64) / (t.actual_notes as f64)
768 } else {
769 base
770 }
771 }
772}
773
774impl Duration {
775 pub fn whole_filling_beats(beats: f64) -> Duration {
777 if beats >= 4.0 {
778 Duration::Whole
779 } else if beats >= 2.0 {
780 Duration::Half
781 } else if beats >= 1.0 {
782 Duration::Quarter
783 } else if beats >= 0.5 {
784 Duration::Eighth
785 } else if beats >= 0.25 {
786 Duration::Sixteenth
787 } else if beats >= 0.125 {
788 Duration::ThirtySecond
789 } else {
790 Duration::SixtyFourth
791 }
792 }
793}
794
795#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
799pub struct NoteAddr {
800 pub part: usize,
801 pub staff: usize,
802 pub measure: usize,
803 pub voice: usize,
804 pub note: usize,
805}
806
807#[derive(Debug, Clone, Serialize, Deserialize)]
811pub enum ScoreChange {
812 MetadataChanged {
813 field: String,
814 old: String,
815 new: String,
816 },
817 TempoChanged {
818 old: u16,
819 new: u16,
820 },
821 KeySignatureChanged {
822 old: KeySignature,
823 new: KeySignature,
824 },
825 PartAdded {
826 part_index: usize,
827 },
828 PartRemoved {
829 part_index: usize,
830 name: String,
831 },
832 NoteAdded {
833 part: usize,
834 staff: usize,
835 measure: usize,
836 voice: usize,
837 note_index: usize,
838 },
839 NoteRemoved {
840 part: usize,
841 staff: usize,
842 measure: usize,
843 voice: usize,
844 note: Box<Note>,
845 },
846 NoteModified {
847 part: usize,
848 staff: usize,
849 measure: usize,
850 voice: usize,
851 note_index: usize,
852 old: Box<Note>,
853 new: Box<Note>,
854 },
855 TimeSigChanged {
856 part: usize,
857 staff: usize,
858 measure: usize,
859 old: Option<TimeSignature>,
860 new: Option<TimeSignature>,
861 },
862 MeasureTempoChanged {
863 part: usize,
864 staff: usize,
865 measure: usize,
866 old: Option<u16>,
867 new: Option<u16>,
868 },
869 BarlineChanged {
870 part: usize,
871 staff: usize,
872 measure: usize,
873 },
874 RehearsalMarkChanged {
875 part: usize,
876 staff: usize,
877 measure: usize,
878 old: Option<String>,
879 new: Option<String>,
880 },
881 VoltaChanged {
882 part: usize,
883 staff: usize,
884 measure: usize,
885 },
886}
887
888pub fn diff(a: &Score, b: &Score) -> Vec<ScoreChange> {
894 let mut changes: Vec<ScoreChange> = Vec::new();
895
896 macro_rules! meta {
897 ($field:ident, $name:literal) => {
898 if a.metadata.$field != b.metadata.$field {
899 changes.push(ScoreChange::MetadataChanged {
900 field: $name.to_string(),
901 old: a.metadata.$field.clone(),
902 new: b.metadata.$field.clone(),
903 });
904 }
905 };
906 }
907 meta!(title, "title");
908 meta!(composer, "composer");
909 meta!(lyricist, "lyricist");
910 meta!(copyright, "copyright");
911 meta!(work_number, "work_number");
912 meta!(movement_title, "movement_title");
913
914 if a.settings.tempo_bpm != b.settings.tempo_bpm {
915 changes.push(ScoreChange::TempoChanged {
916 old: a.settings.tempo_bpm,
917 new: b.settings.tempo_bpm,
918 });
919 }
920 if a.settings.key_signature != b.settings.key_signature {
921 changes.push(ScoreChange::KeySignatureChanged {
922 old: a.settings.key_signature.clone(),
923 new: b.settings.key_signature.clone(),
924 });
925 }
926
927 let a_len = a.parts.len();
928 let b_len = b.parts.len();
929 for i in b_len..a_len {
930 changes.push(ScoreChange::PartRemoved {
931 part_index: i,
932 name: a.parts[i].name.clone(),
933 });
934 }
935 for i in a_len..b_len {
936 changes.push(ScoreChange::PartAdded { part_index: i });
937 }
938
939 for pi in 0..a_len.min(b_len) {
940 let ap = &a.parts[pi];
941 let bp = &b.parts[pi];
942 for si in 0..ap.staves.len().min(bp.staves.len()) {
943 let a_staff = &ap.staves[si];
944 let b_staff = &bp.staves[si];
945 for mi in 0..a_staff.measures.len().min(b_staff.measures.len()) {
946 let am = &a_staff.measures[mi];
947 let bm = &b_staff.measures[mi];
948 for vi in 0..4usize {
949 let av = &am.voices[vi];
950 let bv = &bm.voices[vi];
951 for (ni, (a_note, b_note)) in av.iter().zip(bv.iter()).enumerate() {
952 if !note_content_eq(a_note, b_note) {
953 changes.push(ScoreChange::NoteModified {
954 part: pi,
955 staff: si,
956 measure: mi,
957 voice: vi,
958 note_index: ni,
959 old: Box::new(a_note.clone()),
960 new: Box::new(b_note.clone()),
961 });
962 }
963 }
964 for note in av.iter().skip(bv.len()) {
965 changes.push(ScoreChange::NoteRemoved {
966 part: pi,
967 staff: si,
968 measure: mi,
969 voice: vi,
970 note: Box::new(note.clone()),
971 });
972 }
973 for ni in av.len()..bv.len() {
974 changes.push(ScoreChange::NoteAdded {
975 part: pi,
976 staff: si,
977 measure: mi,
978 voice: vi,
979 note_index: ni,
980 });
981 }
982 }
983 if am.time_sig != bm.time_sig {
984 changes.push(ScoreChange::TimeSigChanged {
985 part: pi,
986 staff: si,
987 measure: mi,
988 old: am.time_sig.clone(),
989 new: bm.time_sig.clone(),
990 });
991 }
992 if am.tempo != bm.tempo {
993 changes.push(ScoreChange::MeasureTempoChanged {
994 part: pi,
995 staff: si,
996 measure: mi,
997 old: am.tempo,
998 new: bm.tempo,
999 });
1000 }
1001 if am.barline_left != bm.barline_left || am.barline_right != bm.barline_right {
1002 changes.push(ScoreChange::BarlineChanged {
1003 part: pi,
1004 staff: si,
1005 measure: mi,
1006 });
1007 }
1008 if am.rehearsal != bm.rehearsal {
1009 changes.push(ScoreChange::RehearsalMarkChanged {
1010 part: pi,
1011 staff: si,
1012 measure: mi,
1013 old: am.rehearsal.clone(),
1014 new: bm.rehearsal.clone(),
1015 });
1016 }
1017 if am.volta != bm.volta {
1018 changes.push(ScoreChange::VoltaChanged {
1019 part: pi,
1020 staff: si,
1021 measure: mi,
1022 });
1023 }
1024 }
1025 }
1026 }
1027
1028 changes
1029}
1030
1031#[derive(Debug, Clone, Serialize, Deserialize)]
1038pub enum ScorePatch {
1039 SetMetadata {
1040 field: String,
1041 value: String,
1042 },
1043 SetTempo {
1044 value: u16,
1045 },
1046 SetKeySignature {
1047 part: usize,
1048 staff: usize,
1049 measure: usize,
1050 value: Option<KeySignature>,
1051 },
1052 SetTimeSignature {
1053 part: usize,
1054 staff: usize,
1055 measure: usize,
1056 value: Option<TimeSignature>,
1057 },
1058 SetBarlines {
1059 part: usize,
1060 staff: usize,
1061 measure: usize,
1062 left: Barline,
1063 right: Barline,
1064 },
1065 SetRehearsal {
1066 part: usize,
1067 staff: usize,
1068 measure: usize,
1069 value: Option<String>,
1070 },
1071 SetVolta {
1072 part: usize,
1073 staff: usize,
1074 measure: usize,
1075 value: Option<VoltaBracket>,
1076 },
1077 AddNote {
1079 part: usize,
1080 staff: usize,
1081 measure: usize,
1082 voice: usize,
1083 #[serde(default = "legacy_append_index")]
1085 note_index: usize,
1086 note: Box<Note>,
1087 },
1088 RemoveNote {
1089 part: usize,
1090 staff: usize,
1091 measure: usize,
1092 voice: usize,
1093 note_index: usize,
1094 },
1095 ReplaceNote {
1097 part: usize,
1098 staff: usize,
1099 measure: usize,
1100 voice: usize,
1101 note_index: usize,
1102 note: Box<Note>,
1103 },
1104 SetMeasureTempo {
1105 part: usize,
1106 staff: usize,
1107 measure: usize,
1108 value: Option<u16>,
1109 },
1110 ReplaceScore {
1113 score: Box<Score>,
1114 },
1115}
1116
1117fn legacy_append_index() -> usize {
1118 usize::MAX
1119}
1120
1121fn patch_requires_replace(a: &Score, b: &Score) -> bool {
1125 if a.settings.time_signature != b.settings.time_signature
1126 || a.settings.key_signature != b.settings.key_signature
1127 || a.parts.len() != b.parts.len()
1128 || a.part_groups.len() != b.part_groups.len()
1129 {
1130 return true;
1131 }
1132 if a.part_groups.iter().zip(&b.part_groups).any(|(x, y)| {
1133 x.first_part != y.first_part
1134 || x.last_part != y.last_part
1135 || x.symbol != y.symbol
1136 || x.barlines_connect != y.barlines_connect
1137 }) {
1138 return true;
1139 }
1140 for (ap, bp) in a.parts.iter().zip(&b.parts) {
1141 if ap.name != bp.name
1142 || ap.short_name != bp.short_name
1143 || ap.midi_channel != bp.midi_channel
1144 || ap.midi_program != bp.midi_program
1145 || ap.staves.len() != bp.staves.len()
1146 {
1147 return true;
1148 }
1149 for (as_, bs) in ap.staves.iter().zip(&bp.staves) {
1150 if as_.clef != bs.clef
1151 || as_.transpose_semitones != bs.transpose_semitones
1152 || as_.measures.len() != bs.measures.len()
1153 {
1154 return true;
1155 }
1156 for (am, bm) in as_.measures.iter().zip(&bs.measures) {
1157 if am.number != bm.number
1158 || am.clef != bm.clef
1159 || am.tempo_text != bm.tempo_text
1160 || am.navigation != bm.navigation
1161 || am.expression_text != bm.expression_text
1162 || am.multi_rest_count != bm.multi_rest_count
1163 || am.system_break != bm.system_break
1164 || am.page_break != bm.page_break
1165 {
1166 return true;
1167 }
1168 }
1169 }
1170 }
1171 false
1172}
1173
1174pub fn score_patch(a: &Score, b: &Score) -> Vec<ScorePatch> {
1179 let mut patches: Vec<ScorePatch> = Vec::new();
1180
1181 if patch_requires_replace(a, b) {
1182 return vec![ScorePatch::ReplaceScore {
1183 score: Box::new(b.clone()),
1184 }];
1185 }
1186
1187 macro_rules! meta {
1188 ($field:ident, $name:literal) => {
1189 if a.metadata.$field != b.metadata.$field {
1190 patches.push(ScorePatch::SetMetadata {
1191 field: $name.to_string(),
1192 value: b.metadata.$field.clone(),
1193 });
1194 }
1195 };
1196 }
1197 meta!(title, "title");
1198 meta!(composer, "composer");
1199 meta!(lyricist, "lyricist");
1200 meta!(copyright, "copyright");
1201 meta!(work_number, "work_number");
1202 meta!(movement_title, "movement_title");
1203
1204 if a.settings.tempo_bpm != b.settings.tempo_bpm {
1205 patches.push(ScorePatch::SetTempo {
1206 value: b.settings.tempo_bpm,
1207 });
1208 }
1209
1210 for pi in 0..a.parts.len().min(b.parts.len()) {
1211 let ap = &a.parts[pi];
1212 let bp = &b.parts[pi];
1213 for si in 0..ap.staves.len().min(bp.staves.len()) {
1214 let a_staff = &ap.staves[si];
1215 let b_staff = &bp.staves[si];
1216 for mi in 0..a_staff.measures.len().min(b_staff.measures.len()) {
1217 let am = &a_staff.measures[mi];
1218 let bm = &b_staff.measures[mi];
1219
1220 if am.key_sig != bm.key_sig {
1221 patches.push(ScorePatch::SetKeySignature {
1222 part: pi,
1223 staff: si,
1224 measure: mi,
1225 value: bm.key_sig.clone(),
1226 });
1227 }
1228 if am.time_sig != bm.time_sig {
1229 patches.push(ScorePatch::SetTimeSignature {
1230 part: pi,
1231 staff: si,
1232 measure: mi,
1233 value: bm.time_sig.clone(),
1234 });
1235 }
1236 if am.barline_left != bm.barline_left || am.barline_right != bm.barline_right {
1237 patches.push(ScorePatch::SetBarlines {
1238 part: pi,
1239 staff: si,
1240 measure: mi,
1241 left: bm.barline_left.clone(),
1242 right: bm.barline_right.clone(),
1243 });
1244 }
1245 if am.rehearsal != bm.rehearsal {
1246 patches.push(ScorePatch::SetRehearsal {
1247 part: pi,
1248 staff: si,
1249 measure: mi,
1250 value: bm.rehearsal.clone(),
1251 });
1252 }
1253 if am.volta != bm.volta {
1254 patches.push(ScorePatch::SetVolta {
1255 part: pi,
1256 staff: si,
1257 measure: mi,
1258 value: bm.volta.clone(),
1259 });
1260 }
1261 if am.tempo != bm.tempo {
1262 patches.push(ScorePatch::SetMeasureTempo {
1263 part: pi,
1264 staff: si,
1265 measure: mi,
1266 value: bm.tempo,
1267 });
1268 }
1269
1270 for vi in 0..4usize {
1271 let av = &am.voices[vi];
1272 let bv = &bm.voices[vi];
1273 for (ni, (a_note, b_note)) in av.iter().zip(bv.iter()).enumerate() {
1274 if !note_content_eq(a_note, b_note) {
1275 patches.push(ScorePatch::ReplaceNote {
1276 part: pi,
1277 staff: si,
1278 measure: mi,
1279 voice: vi,
1280 note_index: ni,
1281 note: Box::new(b_note.clone()),
1282 });
1283 }
1284 }
1285 for ni in (bv.len()..av.len()).rev() {
1287 patches.push(ScorePatch::RemoveNote {
1288 part: pi,
1289 staff: si,
1290 measure: mi,
1291 voice: vi,
1292 note_index: ni,
1293 });
1294 }
1295 for (offset, note) in bv.iter().skip(av.len()).enumerate() {
1297 patches.push(ScorePatch::AddNote {
1298 part: pi,
1299 staff: si,
1300 measure: mi,
1301 voice: vi,
1302 note_index: av.len() + offset,
1303 note: Box::new(note.clone()),
1304 });
1305 }
1306 }
1307 }
1308 }
1309 }
1310
1311 patches
1312}
1313
1314pub fn apply_patch(score: &Score, patches: &[ScorePatch]) -> Result<Score, Error> {
1319 let mut s = score.clone();
1320 for patch in patches {
1321 match patch {
1322 ScorePatch::ReplaceScore { score } => {
1323 s = (**score).clone();
1324 }
1325 ScorePatch::SetMetadata { field, value } => match field.as_str() {
1326 "title" => s.metadata.title = value.clone(),
1327 "composer" => s.metadata.composer = value.clone(),
1328 "lyricist" => s.metadata.lyricist = value.clone(),
1329 "copyright" => s.metadata.copyright = value.clone(),
1330 "work_number" => s.metadata.work_number = value.clone(),
1331 "movement_title" => s.metadata.movement_title = value.clone(),
1332 other => {
1333 return Err(Error::InvalidPatch(format!(
1334 "unknown metadata field: {other}"
1335 )));
1336 }
1337 },
1338 ScorePatch::SetTempo { value } => {
1339 s.settings.tempo_bpm = *value;
1340 }
1341 ScorePatch::SetKeySignature {
1342 part,
1343 staff,
1344 measure,
1345 value,
1346 } => {
1347 s.parts
1348 .get_mut(*part)
1349 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1350 .staves
1351 .get_mut(*staff)
1352 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1353 .measures
1354 .get_mut(*measure)
1355 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1356 .key_sig = value.clone();
1357 }
1358 ScorePatch::SetTimeSignature {
1359 part,
1360 staff,
1361 measure,
1362 value,
1363 } => {
1364 s.parts
1365 .get_mut(*part)
1366 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1367 .staves
1368 .get_mut(*staff)
1369 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1370 .measures
1371 .get_mut(*measure)
1372 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1373 .time_sig = value.clone();
1374 }
1375 ScorePatch::SetBarlines {
1376 part,
1377 staff,
1378 measure,
1379 left,
1380 right,
1381 } => {
1382 let m = s
1383 .parts
1384 .get_mut(*part)
1385 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1386 .staves
1387 .get_mut(*staff)
1388 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1389 .measures
1390 .get_mut(*measure)
1391 .ok_or_else(|| {
1392 Error::InvalidPatch(format!("measure {measure} out of range"))
1393 })?;
1394 m.barline_left = left.clone();
1395 m.barline_right = right.clone();
1396 }
1397 ScorePatch::SetRehearsal {
1398 part,
1399 staff,
1400 measure,
1401 value,
1402 } => {
1403 s.parts
1404 .get_mut(*part)
1405 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1406 .staves
1407 .get_mut(*staff)
1408 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1409 .measures
1410 .get_mut(*measure)
1411 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1412 .rehearsal = value.clone();
1413 }
1414 ScorePatch::SetVolta {
1415 part,
1416 staff,
1417 measure,
1418 value,
1419 } => {
1420 s.parts
1421 .get_mut(*part)
1422 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1423 .staves
1424 .get_mut(*staff)
1425 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1426 .measures
1427 .get_mut(*measure)
1428 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1429 .volta = value.clone();
1430 }
1431 ScorePatch::AddNote {
1432 part,
1433 staff,
1434 measure,
1435 voice,
1436 note_index,
1437 note,
1438 } => {
1439 let v = s
1440 .parts
1441 .get_mut(*part)
1442 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1443 .staves
1444 .get_mut(*staff)
1445 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1446 .measures
1447 .get_mut(*measure)
1448 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1449 .voices
1450 .get_mut(*voice)
1451 .ok_or_else(|| Error::InvalidPatch(format!("voice {voice} out of range")))?;
1452 let insert_at = if *note_index == usize::MAX {
1453 v.len()
1454 } else {
1455 *note_index
1456 };
1457 if insert_at > v.len() {
1458 return Err(Error::InvalidPatch(format!(
1459 "note_index {note_index} out of range"
1460 )));
1461 }
1462 v.insert(insert_at, *note.clone());
1463 }
1464 ScorePatch::RemoveNote {
1465 part,
1466 staff,
1467 measure,
1468 voice,
1469 note_index,
1470 } => {
1471 let v = s
1472 .parts
1473 .get_mut(*part)
1474 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1475 .staves
1476 .get_mut(*staff)
1477 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1478 .measures
1479 .get_mut(*measure)
1480 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1481 .voices
1482 .get_mut(*voice)
1483 .ok_or_else(|| Error::InvalidPatch(format!("voice {voice} out of range")))?;
1484 if *note_index >= v.len() {
1485 return Err(Error::InvalidPatch(format!(
1486 "note_index {note_index} out of range"
1487 )));
1488 }
1489 v.remove(*note_index);
1490 }
1491 ScorePatch::ReplaceNote {
1492 part,
1493 staff,
1494 measure,
1495 voice,
1496 note_index,
1497 note,
1498 } => {
1499 let v = s
1500 .parts
1501 .get_mut(*part)
1502 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1503 .staves
1504 .get_mut(*staff)
1505 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1506 .measures
1507 .get_mut(*measure)
1508 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1509 .voices
1510 .get_mut(*voice)
1511 .ok_or_else(|| Error::InvalidPatch(format!("voice {voice} out of range")))?;
1512 if *note_index >= v.len() {
1513 return Err(Error::InvalidPatch(format!(
1514 "note_index {note_index} out of range"
1515 )));
1516 }
1517 v[*note_index] = *note.clone();
1518 }
1519 ScorePatch::SetMeasureTempo {
1520 part,
1521 staff,
1522 measure,
1523 value,
1524 } => {
1525 s.parts
1526 .get_mut(*part)
1527 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1528 .staves
1529 .get_mut(*staff)
1530 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1531 .measures
1532 .get_mut(*measure)
1533 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1534 .tempo = *value;
1535 }
1536 }
1537 }
1538 Ok(s)
1539}
1540
1541pub fn respell_score(score: &mut Score, prefer_flat: bool) {
1545 for part in &mut score.parts {
1546 for staff in &mut part.staves {
1547 for measure in &mut staff.measures {
1548 for voice in &mut measure.voices {
1549 for note in voice.iter_mut() {
1550 for pitch in &mut note.pitches {
1551 *pitch = pitch.respell(prefer_flat);
1552 }
1553 }
1554 }
1555 }
1556 }
1557 }
1558}
1559
1560pub fn respell_score_to_key(score: &mut Score) {
1564 let prefer_flat = score.settings.key_signature.fifths < 0;
1565 respell_score(score, prefer_flat);
1566}
1567
1568pub fn score_duration_secs(score: &Score) -> f64 {
1573 if score.settings.tempo_bpm == 0 {
1574 return 0.0;
1575 }
1576 let seq = measure_sequence(score);
1577 let mut total_secs = 0.0f64;
1578 let mut current_bpm = score.settings.tempo_bpm as f64;
1579 if let Some(staff) = score.parts.first().and_then(|p| p.staves.first()) {
1580 for &idx in &seq {
1581 if let Some(m) = staff.measures.get(idx) {
1582 if let Some(b) = m.tempo {
1583 current_bpm = b as f64;
1584 }
1585 if current_bpm == 0.0 {
1586 continue;
1587 }
1588 let beats: f64 = m.voices[0].iter().map(|n| n.beats()).sum();
1589 total_secs += beats / current_bpm * 60.0;
1590 }
1591 }
1592 }
1593 total_secs
1594}
1595
1596pub fn score_duration_secs_region(score: &Score, region: (usize, usize)) -> f64 {
1601 if score.settings.tempo_bpm == 0 {
1602 return 0.0;
1603 }
1604 let seq: Vec<usize> = measure_sequence(score)
1605 .into_iter()
1606 .filter(|&idx| idx >= region.0 && idx <= region.1)
1607 .collect();
1608 let mut total_secs = 0.0f64;
1609 let mut current_bpm = score.settings.tempo_bpm as f64;
1610 if let Some(staff) = score.parts.first().and_then(|p| p.staves.first()) {
1611 for &idx in &seq {
1612 if let Some(m) = staff.measures.get(idx) {
1613 if let Some(b) = m.tempo {
1614 current_bpm = b as f64;
1615 }
1616 if current_bpm == 0.0 {
1617 continue;
1618 }
1619 let beats: f64 = m.voices[0].iter().map(|n| n.beats()).sum();
1620 total_secs += beats / current_bpm * 60.0;
1621 }
1622 }
1623 }
1624 total_secs
1625}
1626
1627pub fn measure_beats_remaining(
1632 score: &Score,
1633 part_index: usize,
1634 staff_index: usize,
1635 measure_index: usize,
1636 voice_index: usize,
1637) -> Result<f64, Error> {
1638 let part = score
1639 .parts
1640 .get(part_index)
1641 .ok_or(Error::PartNotFound(part_index))?;
1642 let staff = part
1643 .staves
1644 .get(staff_index)
1645 .ok_or(Error::StaffNotFound(staff_index))?;
1646 let measure = staff
1647 .measures
1648 .get(measure_index)
1649 .ok_or(Error::MeasureNotFound(measure_index))?;
1650 let voice = measure
1651 .voices
1652 .get(voice_index)
1653 .ok_or(Error::VoiceOutOfRange(voice_index))?;
1654 let ts = measure
1655 .time_sig
1656 .as_ref()
1657 .unwrap_or(&score.settings.time_signature);
1658 let used: f64 = voice.iter().map(|n| n.beats()).sum();
1659 Ok((ts.total_beats() - used).max(0.0))
1660}
1661
1662pub fn suggested_stem_up(pitches: &[Pitch], clef: &Clef) -> bool {
1668 if pitches.is_empty() {
1669 return true;
1670 }
1671 let avg = pitches.iter().map(|p| p.to_midi() as f64).sum::<f64>() / pitches.len() as f64;
1672 avg < clef.middle_line_midi() as f64
1673}
1674
1675fn beam_beat_size(ts: &TimeSignature) -> f64 {
1676 if ts.numerator.is_multiple_of(3) && ts.numerator >= 6 && ts.denominator >= 8 {
1677 3.0 * 4.0 / ts.denominator as f64
1678 } else {
1679 4.0 / ts.denominator as f64
1680 }
1681}
1682
1683pub fn compute_beams(notes: &[Note], time_sig: &TimeSignature) -> Vec<BeamState> {
1688 let beat_size = beam_beat_size(time_sig);
1689 let n = notes.len();
1690 let mut result = vec![BeamState::None; n];
1691
1692 let is_beamable = |note: &Note| -> bool {
1693 !note.is_rest
1694 && matches!(
1695 note.duration,
1696 Duration::Eighth
1697 | Duration::Sixteenth
1698 | Duration::ThirtySecond
1699 | Duration::SixtyFourth
1700 )
1701 };
1702
1703 let mut starts = Vec::with_capacity(n);
1705 let mut pos = 0.0f64;
1706 for note in notes {
1707 starts.push(pos);
1708 pos += note.beats();
1709 }
1710
1711 let group_id = |i: usize| -> i64 { (starts[i] / beat_size).floor() as i64 };
1713
1714 let mut i = 0;
1715 while i < n {
1716 if !is_beamable(¬es[i]) {
1717 i += 1;
1718 continue;
1719 }
1720 let g = group_id(i);
1721 let mut j = i;
1723 while j < n && is_beamable(¬es[j]) && group_id(j) == g {
1724 j += 1;
1725 }
1726 let run = j - i;
1727 if run == 1 {
1728 result[i] = BeamState::None;
1729 } else {
1730 result[i] = BeamState::Begin;
1731 result[i + 1..j - 1].fill(BeamState::Continue);
1732 result[j - 1] = BeamState::End;
1733 }
1734 i = j;
1735 }
1736 result
1737}
1738
1739fn note_content_eq(a: &Note, b: &Note) -> bool {
1740 a.is_rest == b.is_rest
1741 && a.pitches == b.pitches
1742 && a.duration == b.duration
1743 && a.dot_count == b.dot_count
1744 && a.tie_start == b.tie_start
1745 && a.tie_end == b.tie_end
1746 && a.beam == b.beam
1747 && a.articulations == b.articulations
1748 && a.dynamic == b.dynamic
1749 && a.stem_up == b.stem_up
1750 && a.hairpin_start == b.hairpin_start
1751 && a.hairpin_end == b.hairpin_end
1752 && a.tuplet == b.tuplet
1753 && a.chord_symbol == b.chord_symbol
1754 && a.is_grace == b.is_grace
1755 && a.grace_slash == b.grace_slash
1756 && a.ottava_start == b.ottava_start
1757 && a.ottava_end == b.ottava_end
1758 && a.lyric == b.lyric
1759 && a.pedal_start == b.pedal_start
1760 && a.pedal_end == b.pedal_end
1761 && a.slur_start == b.slur_start
1762 && a.slur_end == b.slur_end
1763 && a.arpeggiate == b.arpeggiate
1764}
1765
1766#[cfg(test)]
1767mod tests {
1768 use super::*;
1769 use crate::model::pitch::Step;
1770
1771 #[test]
1772 fn default_score_has_one_part_four_measures() {
1773 let score = Score::default();
1774 assert_eq!(score.parts.len(), 1);
1775 assert_eq!(score.parts[0].staves.len(), 1);
1776 assert_eq!(score.parts[0].staves[0].measures.len(), 4);
1777 }
1778
1779 #[test]
1780 fn new_score_measure_count() {
1781 let score = Score::new("Test", 120, 4, 4, 0, 8);
1782 assert_eq!(score.measure_count(), 8);
1783 }
1784
1785 #[test]
1786 fn note_beats_quarter() {
1787 let note = Note::new(Pitch::new(Step::C, 4), Duration::Quarter);
1788 assert!((note.beats() - 1.0).abs() < 1e-9);
1789 }
1790
1791 #[test]
1792 fn note_beats_dotted_quarter() {
1793 let mut note = Note::new(Pitch::new(Step::C, 4), Duration::Quarter);
1794 note.dot_count = 1;
1795 assert!((note.beats() - 1.5).abs() < 1e-9);
1796 }
1797
1798 #[test]
1799 fn grace_note_beats_zero() {
1800 let mut note = Note::new(Pitch::new(Step::C, 4), Duration::Eighth);
1801 note.is_grace = true;
1802 assert_eq!(note.beats(), 0.0);
1803 }
1804
1805 #[test]
1806 fn measure_empty_4_4_fills_four_beats() {
1807 let m = Measure::empty(4, 4);
1808 let total: f64 = m.voices[0].iter().map(|n| n.beats()).sum();
1809 assert!((total - 4.0).abs() < 1e-9);
1810 }
1811
1812 #[test]
1813 fn measure_empty_3_4_fills_three_beats() {
1814 let m = Measure::empty(3, 4);
1815 let total: f64 = m.voices[0].iter().map(|n| n.beats()).sum();
1816 assert!((total - 3.0).abs() < 1e-9);
1817 }
1818
1819 #[test]
1820 fn whole_filling_beats() {
1821 assert_eq!(Duration::whole_filling_beats(4.0), Duration::Whole);
1822 assert_eq!(Duration::whole_filling_beats(2.0), Duration::Half);
1823 assert_eq!(Duration::whole_filling_beats(1.0), Duration::Quarter);
1824 }
1825
1826 #[test]
1829 fn statistics_default_score_all_rests() {
1830 let score = Score::default();
1831 let s = score.statistics();
1832 assert_eq!(s.part_count, 1);
1833 assert_eq!(s.measure_count, 4);
1834 assert_eq!(s.note_count, 0);
1835 assert!(s.rest_count > 0);
1836 }
1837
1838 #[test]
1839 fn statistics_duration_estimate() {
1840 let score = Score::new("T", 120, 4, 4, 0, 1);
1842 let s = score.statistics();
1843 assert!((s.estimated_duration_secs - 2.0).abs() < 0.01);
1844 }
1845
1846 #[test]
1847 fn score_duration_secs_matches_statistics() {
1848 use super::score_duration_secs;
1849 let score = Score::new("T", 120, 4, 4, 0, 4);
1850 let secs = score_duration_secs(&score);
1851 assert!((secs - 8.0).abs() < 0.01, "expected ~8.0 s, got {secs}");
1853 }
1854
1855 #[test]
1856 fn score_duration_secs_zero_bpm_returns_zero() {
1857 use super::score_duration_secs;
1858 let mut score = Score::new("T", 120, 4, 4, 0, 1);
1859 score.settings.tempo_bpm = 0;
1860 assert_eq!(score_duration_secs(&score), 0.0);
1861 }
1862
1863 #[test]
1864 fn score_duration_secs_per_measure_tempo() {
1865 use super::score_duration_secs;
1866 let mut score = Score::new("T", 120, 4, 4, 0, 2);
1868 score.parts[0].staves[0].measures[1].tempo = Some(60);
1869 let secs = score_duration_secs(&score);
1870 assert!((secs - 6.0).abs() < 0.01, "expected ~6.0 s, got {secs}");
1871 }
1872
1873 #[test]
1876 fn extract_part_returns_single_part_score() {
1877 let mut score = Score::default();
1878 let mut p2 = Part::new("Violin", "Vln.");
1879 p2.staves.push(Staff::new(Clef::Treble));
1880 score.parts.push(p2);
1881 let ex = score.extract_part(0).unwrap();
1882 assert_eq!(ex.parts.len(), 1);
1883 assert_ne!(ex.id, score.id);
1884 assert_eq!(ex.metadata.title, score.metadata.title);
1885 }
1886
1887 #[test]
1888 fn extract_part_out_of_range_is_none() {
1889 let score = Score::default();
1890 assert!(score.extract_part(99).is_none());
1891 }
1892
1893 #[test]
1896 fn transpose_zero_is_clone() {
1897 let score = Score::new("T", 120, 4, 4, 0, 1);
1898 let t = transpose(&score, 0);
1899 assert_eq!(t.settings.key_signature.fifths, 0);
1900 }
1901
1902 #[test]
1903 fn transpose_c_major_up_2_to_d_major() {
1904 let score = Score::new("T", 120, 4, 4, 0, 1);
1905 assert_eq!(transpose(&score, 2).settings.key_signature.fifths, 2);
1906 }
1907
1908 #[test]
1909 fn transpose_d_major_up_5_to_g_major() {
1910 let score = Score::new("T", 120, 4, 4, 2, 1);
1911 assert_eq!(transpose(&score, 5).settings.key_signature.fifths, 1);
1912 }
1913
1914 #[test]
1915 fn transpose_c4_up_1_to_csharp4() {
1916 let p = transpose_pitch(&Pitch::new(Step::C, 4), 1);
1917 assert_eq!(p.to_midi(), 61);
1918 assert_eq!(p.step, Step::C);
1919 assert_eq!(p.alter, 1);
1920 }
1921
1922 #[test]
1923 fn transpose_c4_down_1_to_b3() {
1924 let p = transpose_pitch(&Pitch::new(Step::C, 4), -1);
1925 assert_eq!(p.to_midi(), 59);
1926 assert_eq!(p.step, Step::B);
1927 assert_eq!(p.alter, 0);
1928 }
1929
1930 #[test]
1931 fn transpose_up_octave_keeps_step() {
1932 let p = transpose_pitch(&Pitch::new(Step::A, 4), 12);
1933 assert_eq!(p.to_midi(), 81);
1934 assert_eq!(p.step, Step::A);
1935 assert_eq!(p.octave, 5);
1936 }
1937
1938 #[test]
1939 fn statistics_with_repeat_doubles_duration() {
1940 let mut score = Score::new("T", 120, 4, 4, 0, 2);
1942 score.parts[0].staves[0].measures[0].barline_left =
1943 crate::model::notation::Barline::RepeatStart;
1944 score.parts[0].staves[0].measures[1].barline_right =
1945 crate::model::notation::Barline::RepeatEnd;
1946 let s = score.statistics();
1947 assert!((s.estimated_duration_secs - 8.0).abs() < 0.01);
1949 }
1950
1951 #[test]
1952 fn transpose_octave_boundary_b4_to_c5() {
1953 let p = transpose_pitch(&Pitch::new(Step::B, 4), 1);
1955 assert_eq!(p.to_midi(), 72);
1956 assert_eq!(p.step, Step::C);
1957 assert_eq!(p.octave, 5);
1958 }
1959
1960 #[test]
1961 fn transpose_clamp_at_midi_127() {
1962 let p = transpose_pitch(&Pitch::new(Step::G, 9), 3);
1964 assert_eq!(p.to_midi(), 127);
1965 }
1966
1967 #[test]
1970 fn merge_combines_parts() {
1971 let mut a = Score::new("A", 120, 4, 4, 0, 2);
1972 let b = Score::new("B", 120, 4, 4, 0, 2);
1973 let mut p2 = Part::new("Violin", "Vln.");
1975 p2.staves.push(Staff::new(Clef::Treble));
1976 for i in 0..2usize {
1977 let mut m = Measure::empty(4, 4);
1978 m.number = i as u32 + 1;
1979 p2.staves[0].measures.push(m);
1980 }
1981 a.parts.push(p2);
1982 let merged = a.merge(&b);
1983 assert_eq!(merged.parts.len(), 3);
1985 }
1986
1987 #[test]
1988 fn merge_pads_shorter_score() {
1989 let a = Score::new("A", 120, 4, 4, 0, 4);
1990 let b = Score::new("B", 120, 4, 4, 0, 2);
1991 let merged = a.merge(&b);
1992 assert_eq!(merged.parts[0].staves[0].measures.len(), 4);
1994 assert_eq!(merged.parts[1].staves[0].measures.len(), 4);
1995 }
1996
1997 #[test]
1998 fn merge_uses_self_metadata() {
1999 let mut a = Score::new("Title A", 120, 4, 4, 0, 2);
2000 a.metadata.composer = "Composer A".to_string();
2001 let b = Score::new("Title B", 120, 4, 4, 0, 2);
2002 let merged = a.merge(&b);
2003 assert_eq!(merged.metadata.title, "Title A");
2004 assert_eq!(merged.metadata.composer, "Composer A");
2005 }
2006
2007 #[test]
2008 fn merge_new_id_differs_from_both() {
2009 let a = Score::new("A", 120, 4, 4, 0, 2);
2010 let b = Score::new("B", 120, 4, 4, 0, 2);
2011 let merged = a.merge(&b);
2012 assert_ne!(merged.id, a.id);
2013 assert_ne!(merged.id, b.id);
2014 }
2015
2016 #[test]
2019 fn staff_default_transpose_is_zero() {
2020 let s = Staff::new(Clef::Treble);
2021 assert_eq!(s.transpose_semitones, 0);
2022 }
2023
2024 #[test]
2027 fn score_default_has_schema_version_1() {
2028 let score = Score::default();
2029 assert_eq!(score.schema_version, 1);
2030 }
2031
2032 #[test]
2033 fn score_new_has_schema_version_1() {
2034 let score = Score::new("T", 120, 4, 4, 0, 4);
2035 assert_eq!(score.schema_version, 1);
2036 }
2037
2038 #[test]
2039 fn score_without_schema_version_deserializes_to_zero() {
2040 let json = r#"{"id":"abc","metadata":{"title":"T","composer":"","lyricist":"","copyright":"","work_number":"","movement_title":""},"settings":{"tempo_bpm":120,"time_signature":{"numerator":4,"denominator":4},"key_signature":{"fifths":0,"mode":"major"}},"parts":[]}"#;
2041 let score: Score = serde_json::from_str(json).unwrap();
2042 assert_eq!(score.schema_version, 0);
2043 }
2044
2045 #[test]
2048 fn score_template_solo_has_one_part_treble() {
2049 let score = Score::template(ScoreTemplate::Solo);
2050 assert_eq!(score.parts.len(), 1);
2051 assert_eq!(score.parts[0].staves.len(), 1);
2052 assert_eq!(score.parts[0].staves[0].clef, Clef::Treble);
2053 assert_eq!(score.parts[0].midi_program, 0);
2054 }
2055
2056 #[test]
2057 fn score_template_piano_has_two_staves() {
2058 let score = Score::template(ScoreTemplate::Piano);
2059 assert_eq!(score.parts.len(), 1);
2060 assert_eq!(score.parts[0].staves.len(), 2);
2061 assert_eq!(score.parts[0].staves[0].clef, Clef::Treble);
2062 assert_eq!(score.parts[0].staves[1].clef, Clef::Bass);
2063 }
2064
2065 #[test]
2066 fn score_template_string_quartet_has_four_parts() {
2067 let score = Score::template(ScoreTemplate::StringQuartet);
2068 assert_eq!(score.parts.len(), 4);
2069 assert_eq!(score.parts[2].staves[0].clef, Clef::Alto); assert_eq!(score.parts[3].staves[0].clef, Clef::Bass); assert_eq!(score.parts[0].midi_program, 40);
2072 assert_eq!(score.parts[3].midi_program, 42);
2073 }
2074
2075 #[test]
2076 fn score_template_string_orchestra_has_five_parts() {
2077 let score = Score::template(ScoreTemplate::StringOrchestra);
2078 assert_eq!(score.parts.len(), 5);
2079 assert_eq!(score.parts[4].midi_program, 43); }
2081
2082 #[test]
2083 fn score_template_brass_quintet_has_five_parts() {
2084 let score = Score::template(ScoreTemplate::BrassQuintet);
2085 assert_eq!(score.parts.len(), 5);
2086 assert_eq!(score.parts[2].midi_program, 60); }
2088
2089 #[test]
2090 fn score_template_default_measures_are_four() {
2091 let score = Score::template(ScoreTemplate::StringQuartet);
2092 for part in &score.parts {
2093 for staff in &part.staves {
2094 assert_eq!(staff.measures.len(), 4);
2095 }
2096 }
2097 }
2098
2099 #[test]
2102 fn measure_empty_has_no_breaks() {
2103 let m = Measure::empty(4, 4);
2104 assert!(!m.system_break);
2105 assert!(!m.page_break);
2106 }
2107
2108 #[test]
2109 fn system_break_survives_json_roundtrip() {
2110 let mut m = Measure::empty(4, 4);
2111 m.system_break = true;
2112 let json = serde_json::to_string(&m).unwrap();
2113 let m2: Measure = serde_json::from_str(&json).unwrap();
2114 assert!(m2.system_break);
2115 assert!(!m2.page_break);
2116 }
2117
2118 #[test]
2121 fn diff_identical_scores_is_empty() {
2122 let s = Score::new("T", 120, 4, 4, 0, 2);
2123 assert!(diff(&s, &s).is_empty());
2124 }
2125
2126 #[test]
2127 fn score_patch_covers_measure_semantics_and_note_insert_index() {
2128 let a = Score::new("T", 120, 4, 4, 0, 1);
2129 let mut b = a.clone();
2130 let measure = &mut b.parts[0].staves[0].measures[0];
2131 measure.key_sig = Some(KeySignature {
2132 fifths: -2,
2133 mode: "major".to_string(),
2134 });
2135 measure.time_sig = Some(TimeSignature {
2136 numerator: 3,
2137 denominator: 4,
2138 });
2139 measure.barline_left = Barline::RepeatStart;
2140 measure.barline_right = Barline::RepeatEnd;
2141 measure.rehearsal = Some("A".to_string());
2142 measure.volta = Some(VoltaBracket {
2143 number: 1,
2144 kind: "begin_end".to_string(),
2145 });
2146 measure.voices[0].insert(0, Note::new(Pitch::new(Step::C, 4), Duration::Quarter));
2147 let expected = b.parts[0].staves[0].measures[0].clone();
2148
2149 let patches = score_patch(&a, &b);
2150 assert!(
2151 patches
2152 .iter()
2153 .any(|p| matches!(p, ScorePatch::SetTimeSignature { .. }))
2154 );
2155 assert!(
2156 patches
2157 .iter()
2158 .any(|p| matches!(p, ScorePatch::SetBarlines { .. }))
2159 );
2160 assert!(
2161 patches
2162 .iter()
2163 .any(|p| matches!(p, ScorePatch::SetRehearsal { .. }))
2164 );
2165 assert!(
2166 patches
2167 .iter()
2168 .any(|p| matches!(p, ScorePatch::SetVolta { .. }))
2169 );
2170 let result = apply_patch(&a, &patches).expect("patch application failed");
2171 let result_measure = &result.parts[0].staves[0].measures[0];
2172 assert_eq!(result_measure.key_sig, expected.key_sig);
2173 assert_eq!(result_measure.time_sig, expected.time_sig);
2174 assert_eq!(result_measure.barline_left, expected.barline_left);
2175 assert_eq!(result_measure.barline_right, expected.barline_right);
2176 assert_eq!(result_measure.rehearsal, expected.rehearsal);
2177 assert_eq!(result_measure.volta, expected.volta);
2178 assert_eq!(result_measure.voices[0].len(), expected.voices[0].len());
2179 }
2180
2181 #[test]
2182 fn score_patch_replaces_when_structure_or_uncovered_fields_change() {
2183 let a = Score::new("T", 120, 4, 4, 0, 1);
2184 let mut b = a.clone();
2185 b.parts[0].name = "Piano".to_string();
2186 b.parts[0].staves[0].measures[0].expression_text = Some("dolce".to_string());
2187 let patches = score_patch(&a, &b);
2188 assert!(matches!(
2189 patches.as_slice(),
2190 [ScorePatch::ReplaceScore { .. }]
2191 ));
2192 let result = apply_patch(&a, &patches).expect("replacement failed");
2193 assert_eq!(result.parts[0].name, "Piano");
2194 assert_eq!(
2195 result.parts[0].staves[0].measures[0].expression_text,
2196 Some("dolce".to_string())
2197 );
2198 }
2199
2200 #[test]
2201 fn diff_detects_tempo_change() {
2202 let a = Score::new("T", 120, 4, 4, 0, 1);
2203 let mut b = a.clone();
2204 b.settings.tempo_bpm = 90;
2205 let changes = diff(&a, &b);
2206 assert_eq!(changes.len(), 1);
2207 assert!(matches!(
2208 changes[0],
2209 ScoreChange::TempoChanged { old: 120, new: 90 }
2210 ));
2211 }
2212
2213 #[test]
2214 fn diff_detects_title_change() {
2215 let a = Score::new("Old Title", 120, 4, 4, 0, 1);
2216 let mut b = a.clone();
2217 b.metadata.title = "New Title".to_string();
2218 let changes = diff(&a, &b);
2219 assert!(
2220 changes.iter().any(
2221 |c| matches!(c, ScoreChange::MetadataChanged { field, .. } if field == "title")
2222 )
2223 );
2224 }
2225
2226 #[test]
2227 fn diff_detects_note_modification() {
2228 let mut a = Score::new("T", 120, 4, 4, 0, 1);
2229 a.parts[0].staves[0].measures[0].voices[0] =
2230 vec![Note::new(Pitch::new(Step::C, 4), Duration::Quarter)];
2231 let mut b = a.clone();
2232 b.parts[0].staves[0].measures[0].voices[0][0] =
2233 Note::new(Pitch::new(Step::D, 4), Duration::Quarter);
2234 let changes = diff(&a, &b);
2235 assert!(
2236 changes
2237 .iter()
2238 .any(|c| matches!(c, ScoreChange::NoteModified { .. }))
2239 );
2240 }
2241
2242 #[test]
2243 fn diff_detects_part_added() {
2244 let a = Score::new("T", 120, 4, 4, 0, 1);
2245 let mut b = a.clone();
2246 let mut p = Part::new("Violin", "Vln.");
2247 p.staves.push(Staff::new(Clef::Treble));
2248 b.parts.push(p);
2249 let changes = diff(&a, &b);
2250 assert!(
2251 changes
2252 .iter()
2253 .any(|c| matches!(c, ScoreChange::PartAdded { part_index: 1 }))
2254 );
2255 }
2256
2257 #[test]
2258 fn diff_detects_measure_tempo_change() {
2259 let a = Score::new("T", 120, 4, 4, 0, 2);
2260 let mut b = a.clone();
2261 b.parts[0].staves[0].measures[1].tempo = Some(60);
2262 let changes = diff(&a, &b);
2263 assert!(changes.iter().any(|c| matches!(
2264 c,
2265 ScoreChange::MeasureTempoChanged {
2266 measure: 1,
2267 old: None,
2268 new: Some(60),
2269 ..
2270 }
2271 )));
2272 }
2273
2274 #[test]
2275 fn diff_detects_barline_change() {
2276 use crate::model::notation::Barline;
2277 let a = Score::new("T", 120, 4, 4, 0, 2);
2278 let mut b = a.clone();
2279 b.parts[0].staves[0].measures[0].barline_left = Barline::RepeatStart;
2280 let changes = diff(&a, &b);
2281 assert!(
2282 changes
2283 .iter()
2284 .any(|c| matches!(c, ScoreChange::BarlineChanged { measure: 0, .. }))
2285 );
2286 }
2287
2288 #[test]
2289 fn diff_detects_rehearsal_change() {
2290 let a = Score::new("T", 120, 4, 4, 0, 2);
2291 let mut b = a.clone();
2292 b.parts[0].staves[0].measures[0].rehearsal = Some("A".to_string());
2293 let changes = diff(&a, &b);
2294 assert!(
2295 changes
2296 .iter()
2297 .any(|c| matches!(c, ScoreChange::RehearsalMarkChanged { measure: 0, .. }))
2298 );
2299 }
2300
2301 #[test]
2302 fn diff_detects_volta_change() {
2303 use super::VoltaBracket;
2304 let a = Score::new("T", 120, 4, 4, 0, 2);
2305 let mut b = a.clone();
2306 b.parts[0].staves[0].measures[0].volta = Some(VoltaBracket {
2307 number: 1,
2308 kind: "begin_end".into(),
2309 });
2310 let changes = diff(&a, &b);
2311 assert!(
2312 changes
2313 .iter()
2314 .any(|c| matches!(c, ScoreChange::VoltaChanged { measure: 0, .. }))
2315 );
2316 }
2317
2318 #[test]
2319 fn diff_detects_key_signature_change() {
2320 let a = Score::new("T", 120, 4, 4, 0, 1);
2321 let mut b = a.clone();
2322 b.settings.key_signature.fifths = 2; let changes = diff(&a, &b);
2324 assert!(
2325 changes
2326 .iter()
2327 .any(|c| matches!(c, ScoreChange::KeySignatureChanged { .. }))
2328 );
2329 }
2330
2331 #[test]
2332 fn diff_same_key_signature_no_change() {
2333 let a = Score::new("T", 120, 4, 4, 2, 1);
2334 let changes = diff(&a, &a);
2335 assert!(changes.is_empty());
2336 }
2337
2338 #[test]
2339 fn score_duration_secs_region_partial() {
2340 use super::score_duration_secs_region;
2341 let score = Score::new("T", 120, 4, 4, 0, 4);
2343 let secs = score_duration_secs_region(&score, (1, 2));
2344 assert!((secs - 4.0).abs() < 0.01, "expected ~4.0 s, got {secs}");
2345 }
2346
2347 #[test]
2348 fn score_duration_secs_region_single_measure() {
2349 use super::score_duration_secs_region;
2350 let score = Score::new("T", 120, 4, 4, 0, 4);
2352 let secs = score_duration_secs_region(&score, (0, 0));
2353 assert!((secs - 2.0).abs() < 0.01, "expected ~2.0 s, got {secs}");
2354 }
2355
2356 #[test]
2359 fn measure_beats_remaining_empty_voice_returns_full() {
2360 use super::measure_beats_remaining;
2361 let mut score = Score::new("T", 120, 4, 4, 0, 1);
2362 score.parts[0].staves[0].measures[0].voices[0].clear();
2363 let rem = measure_beats_remaining(&score, 0, 0, 0, 0).unwrap();
2364 assert!(
2365 (rem - 4.0).abs() < 1e-9,
2366 "expected 4.0 remaining, got {rem}"
2367 );
2368 }
2369
2370 #[test]
2371 fn measure_beats_remaining_half_full_returns_half() {
2372 use super::measure_beats_remaining;
2373 use crate::model::pitch::Step;
2374 let mut score = Score::new("T", 120, 4, 4, 0, 1);
2375 score.parts[0].staves[0].measures[0].voices[0] = vec![
2376 Note::new(Pitch::new(Step::C, 4), Duration::Quarter),
2377 Note::new(Pitch::new(Step::D, 4), Duration::Quarter),
2378 ];
2379 let rem = measure_beats_remaining(&score, 0, 0, 0, 0).unwrap();
2380 assert!(
2381 (rem - 2.0).abs() < 1e-9,
2382 "expected 2.0 remaining, got {rem}"
2383 );
2384 }
2385
2386 #[test]
2387 fn measure_beats_remaining_full_voice_returns_zero() {
2388 use super::measure_beats_remaining;
2389 use crate::model::pitch::Step;
2390 let mut score = Score::new("T", 120, 4, 4, 0, 1);
2391 score.parts[0].staves[0].measures[0].voices[0] =
2392 vec![Note::new(Pitch::new(Step::C, 4), Duration::Whole)];
2393 let rem = measure_beats_remaining(&score, 0, 0, 0, 0).unwrap();
2394 assert!((rem).abs() < 1e-9, "expected 0.0 remaining, got {rem}");
2395 }
2396
2397 #[test]
2398 fn measure_beats_remaining_tuplet_accounting() {
2399 use super::measure_beats_remaining;
2400 use crate::model::notation::TupletInfo;
2401 use crate::model::pitch::Step;
2402 let mut score = Score::new("T", 120, 4, 4, 0, 1);
2404 let tuplet = TupletInfo {
2405 actual_notes: 3,
2406 normal_notes: 2,
2407 };
2408 let mk = |step| {
2409 let mut n = Note::new(Pitch::new(step, 4), Duration::Quarter);
2410 n.tuplet = Some(tuplet.clone());
2411 n
2412 };
2413 score.parts[0].staves[0].measures[0].voices[0] =
2414 vec![mk(Step::C), mk(Step::D), mk(Step::E)];
2415 let rem = measure_beats_remaining(&score, 0, 0, 0, 0).unwrap();
2416 assert!(
2417 (rem - 2.0).abs() < 1e-9,
2418 "expected 2.0 remaining (triplets used 2.0), got {rem}"
2419 );
2420 }
2421
2422 #[test]
2423 fn measure_beats_remaining_out_of_range_returns_err() {
2424 use super::measure_beats_remaining;
2425 let score = Score::new("T", 120, 4, 4, 0, 1);
2426 assert!(measure_beats_remaining(&score, 99, 0, 0, 0).is_err());
2427 assert!(measure_beats_remaining(&score, 0, 99, 0, 0).is_err());
2428 assert!(measure_beats_remaining(&score, 0, 0, 99, 0).is_err());
2429 assert!(measure_beats_remaining(&score, 0, 0, 0, 4).is_err());
2430 }
2431
2432 #[test]
2433 fn note_content_eq_ignores_id() {
2434 use crate::model::pitch::Step;
2435 let mut a = Note::new(Pitch::new(Step::C, 4), Duration::Quarter);
2436 let mut b = a.clone();
2437 b.id = "different-id".to_string();
2438 assert!(note_content_eq(&a, &b));
2439 b.pitches[0] = Pitch::new(Step::D, 4);
2441 assert!(!note_content_eq(&a, &b));
2442 let mut c = a.clone();
2444 a.stem_up = Some(true);
2445 c.stem_up = Some(false);
2446 assert!(!note_content_eq(&a, &c));
2447 }
2448
2449 #[test]
2450 fn suggested_stem_up_below_middle() {
2451 use crate::model::notation::Clef;
2452 let pitches = vec![Pitch::new(Step::C, 4)];
2454 assert!(suggested_stem_up(&pitches, &Clef::Treble));
2455 }
2456
2457 #[test]
2458 fn suggested_stem_up_above_middle() {
2459 use crate::model::notation::Clef;
2460 let pitches = vec![Pitch::new(Step::G, 5)];
2462 assert!(!suggested_stem_up(&pitches, &Clef::Treble));
2463 }
2464
2465 #[test]
2466 fn suggested_stem_up_at_middle_line() {
2467 use crate::model::notation::Clef;
2468 let pitches = vec![Pitch::new(Step::B, 4)];
2470 assert!(!suggested_stem_up(&pitches, &Clef::Treble));
2471 }
2472
2473 #[test]
2474 fn suggested_stem_up_chord() {
2475 use crate::model::notation::Clef;
2476 let pitches = vec![Pitch::new(Step::C, 4), Pitch::new(Step::G, 4)];
2478 assert!(suggested_stem_up(&pitches, &Clef::Treble));
2479 }
2480
2481 #[test]
2482 fn suggested_stem_up_bass_clef() {
2483 use crate::model::notation::Clef;
2484 let pitches = vec![Pitch::new(Step::D, 3)];
2486 assert!(!suggested_stem_up(&pitches, &Clef::Bass));
2487 let pitches2 = vec![Pitch::new(Step::C, 3)];
2489 assert!(suggested_stem_up(&pitches2, &Clef::Bass));
2490 }
2491
2492 #[test]
2493 fn suggested_stem_up_empty_pitches() {
2494 use crate::model::notation::Clef;
2495 assert!(suggested_stem_up(&[], &Clef::Treble));
2496 }
2497
2498 fn eighth(pitch: Pitch) -> Note {
2499 Note::new(pitch, Duration::Eighth)
2500 }
2501 fn quarter(pitch: Pitch) -> Note {
2502 Note::new(pitch, Duration::Quarter)
2503 }
2504 fn rest_eighth() -> Note {
2505 Note::rest(Duration::Eighth)
2506 }
2507
2508 #[test]
2509 fn compute_beams_4_4_four_eighths() {
2510 use crate::model::notation::{Clef, TimeSignature};
2511 let _ = Clef::Treble; let ts = TimeSignature {
2513 numerator: 4,
2514 denominator: 4,
2515 };
2516 let c4 = Pitch::new(Step::C, 4);
2517 let notes = vec![
2518 eighth(c4.clone()),
2519 eighth(c4.clone()),
2520 eighth(c4.clone()),
2521 eighth(c4.clone()),
2522 ];
2523 let beams = compute_beams(¬es, &ts);
2524 assert_eq!(beams[0], BeamState::Begin);
2526 assert_eq!(beams[1], BeamState::End);
2527 assert_eq!(beams[2], BeamState::Begin);
2528 assert_eq!(beams[3], BeamState::End);
2529 }
2530
2531 #[test]
2532 fn compute_beams_4_4_all_eighth_one_group() {
2533 use crate::model::notation::TimeSignature;
2534 let ts = TimeSignature {
2535 numerator: 4,
2536 denominator: 4,
2537 };
2538 let c4 = Pitch::new(Step::C, 4);
2539 let notes = vec![eighth(c4.clone()), eighth(c4.clone())];
2541 let beams = compute_beams(¬es, &ts);
2542 assert_eq!(beams[0], BeamState::Begin);
2543 assert_eq!(beams[1], BeamState::End);
2544 }
2545
2546 #[test]
2547 fn compute_beams_quarter_not_beamed() {
2548 use crate::model::notation::TimeSignature;
2549 let ts = TimeSignature {
2550 numerator: 4,
2551 denominator: 4,
2552 };
2553 let c4 = Pitch::new(Step::C, 4);
2554 let notes = vec![quarter(c4.clone()), quarter(c4.clone())];
2555 let beams = compute_beams(¬es, &ts);
2556 assert_eq!(beams[0], BeamState::None);
2557 assert_eq!(beams[1], BeamState::None);
2558 }
2559
2560 #[test]
2561 fn compute_beams_rest_breaks_beam() {
2562 use crate::model::notation::TimeSignature;
2563 let ts = TimeSignature {
2564 numerator: 4,
2565 denominator: 4,
2566 };
2567 let c4 = Pitch::new(Step::C, 4);
2568 let notes = vec![eighth(c4.clone()), rest_eighth(), eighth(c4.clone())];
2569 let beams = compute_beams(¬es, &ts);
2570 assert_eq!(beams[0], BeamState::None);
2572 assert_eq!(beams[1], BeamState::None);
2573 assert_eq!(beams[2], BeamState::None);
2574 }
2575
2576 #[test]
2577 fn compute_beams_6_8_compound() {
2578 use crate::model::notation::TimeSignature;
2579 let ts = TimeSignature {
2580 numerator: 6,
2581 denominator: 8,
2582 };
2583 let c4 = Pitch::new(Step::C, 4);
2584 let notes: Vec<Note> = (0..6).map(|_| eighth(c4.clone())).collect();
2586 let beams = compute_beams(¬es, &ts);
2587 assert_eq!(beams[0], BeamState::Begin);
2588 assert_eq!(beams[1], BeamState::Continue);
2589 assert_eq!(beams[2], BeamState::End);
2590 assert_eq!(beams[3], BeamState::Begin);
2591 assert_eq!(beams[4], BeamState::Continue);
2592 assert_eq!(beams[5], BeamState::End);
2593 }
2594
2595 #[test]
2596 fn compute_beams_single_eighth() {
2597 use crate::model::notation::TimeSignature;
2598 let ts = TimeSignature {
2599 numerator: 4,
2600 denominator: 4,
2601 };
2602 let c4 = Pitch::new(Step::C, 4);
2603 let notes = vec![eighth(c4.clone())];
2604 let beams = compute_beams(¬es, &ts);
2605 assert_eq!(beams[0], BeamState::None);
2606 }
2607}