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