1use super::{
2 duration::Duration,
3 notation::{
4 Articulation, Barline, BeamState, ChordSymbol, Clef, CrossStaff, Dynamic, GuitarTechnique,
5 HairpinKind, KeySignature, Lyric, NoteHead, OttavaKind, StyledText, TablatureConfig,
6 TimeSignature, 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_microtone(step, oct, alter, pitch.microtone_cents)
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 #[serde(default)]
507 pub tablature: Option<TablatureConfig>,
508}
509
510impl Staff {
511 pub fn new(clef: Clef) -> Self {
512 Self {
513 clef,
514 measures: Vec::new(),
515 transpose_semitones: 0,
516 tablature: None,
517 }
518 }
519}
520
521#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
522pub struct VoltaBracket {
523 pub number: u8,
525 pub kind: String,
527}
528
529#[derive(Debug, Clone, Serialize, Deserialize)]
530pub struct Measure {
531 pub number: u32,
532 pub time_sig: Option<TimeSignature>,
533 pub key_sig: Option<KeySignature>,
534 pub clef: Option<Clef>,
535 pub tempo: Option<u16>,
536 pub barline_left: Barline,
537 pub barline_right: Barline,
538 #[serde(default)]
539 pub volta: Option<VoltaBracket>,
540 #[serde(default)]
541 pub tempo_text: Option<String>,
542 #[serde(default)]
543 pub rehearsal: Option<String>,
544 #[serde(default)]
547 pub navigation: Option<String>,
548 #[serde(default)]
550 pub expression_text: Option<String>,
551 #[serde(default)]
552 pub texts: Vec<StyledText>,
553 #[serde(default)]
555 pub multi_rest_count: Option<u8>,
556 #[serde(default)]
558 pub system_break: bool,
559 #[serde(default)]
561 pub page_break: bool,
562 pub voices: [Vec<Note>; 4],
564}
565
566impl Measure {
567 pub fn empty(numerator: u8, denominator: u8) -> Self {
568 let total_beats = TimeSignature {
569 numerator,
570 denominator,
571 }
572 .total_beats();
573 let mut voice0: Vec<Note> = Vec::new();
574 let mut remaining = total_beats;
575 while remaining > 1e-9 {
576 let dur = Duration::whole_filling_beats(remaining);
577 remaining -= dur.beats(0);
578 voice0.push(Note::rest(dur));
579 }
580 Self {
581 number: 0,
582 time_sig: None,
583 key_sig: None,
584 clef: None,
585 tempo: None,
586 barline_left: Barline::Normal,
587 barline_right: Barline::Normal,
588 volta: None,
589 tempo_text: None,
590 rehearsal: None,
591 navigation: None,
592 expression_text: None,
593 texts: Vec::new(),
594 multi_rest_count: None,
595 system_break: false,
596 page_break: false,
597 voices: [voice0, vec![], vec![], vec![]],
598 }
599 }
600
601 pub fn renumber(&mut self, n: u32) {
602 self.number = n;
603 }
604}
605
606#[derive(Debug, Clone, Serialize, Deserialize)]
607pub struct Note {
608 pub id: String,
609 pub is_rest: bool,
610 pub pitches: Vec<Pitch>,
612 #[serde(default)]
613 pub tab_position: Option<super::notation::TabPosition>,
614 pub duration: Duration,
615 pub dot_count: u8,
616 pub tie_start: bool,
617 pub tie_end: bool,
618 pub beam: BeamState,
619 pub articulations: Vec<Articulation>,
620 pub dynamic: Option<Dynamic>,
621 pub stem_up: Option<bool>,
622 #[serde(default)]
623 pub hairpin_start: Option<HairpinKind>,
624 #[serde(default)]
625 pub hairpin_end: bool,
626 #[serde(default)]
627 pub tuplet: Option<TupletInfo>,
628 #[serde(default)]
629 pub chord_symbol: Option<ChordSymbol>,
630 #[serde(default)]
631 pub is_grace: bool,
632 #[serde(default)]
634 pub grace_slash: bool,
635 #[serde(default)]
636 pub ottava_start: Option<OttavaKind>,
637 #[serde(default)]
638 pub ottava_end: bool,
639 #[serde(default)]
640 pub lyric: Option<Lyric>,
641 #[serde(default)]
642 pub pedal_start: bool,
643 #[serde(default)]
644 pub pedal_end: bool,
645 #[serde(default)]
646 pub slur_start: bool,
647 #[serde(default)]
648 pub slur_end: bool,
649 #[serde(default)]
651 pub arpeggiate: Option<bool>,
652 #[serde(default)]
654 pub technique_text: Option<String>,
655 #[serde(default)]
656 pub glissando_start: bool,
657 #[serde(default)]
658 pub glissando_end: bool,
659 #[serde(default)]
660 pub cross_staff: Option<CrossStaff>,
661 #[serde(default)]
663 pub fingering: Option<u8>,
664 #[serde(default)]
666 pub string_number: Option<u8>,
667 #[serde(default)]
668 pub note_head: NoteHead,
669 #[serde(default)]
671 pub is_cue: bool,
672 #[serde(default)]
674 pub trill_line_start: bool,
675 #[serde(default)]
677 pub trill_line_end: bool,
678 #[serde(default)]
680 pub guitar_technique: Option<GuitarTechnique>,
681}
682
683impl Note {
684 pub fn new(pitch: Pitch, duration: Duration) -> Self {
685 Self {
686 id: Uuid::new_v4().to_string(),
687 is_rest: false,
688 pitches: vec![pitch],
689 tab_position: None,
690 duration,
691 dot_count: 0,
692 tie_start: false,
693 tie_end: false,
694 beam: BeamState::None,
695 articulations: Vec::new(),
696 dynamic: None,
697 stem_up: None,
698 hairpin_start: None,
699 hairpin_end: false,
700 tuplet: None,
701 chord_symbol: None,
702 is_grace: false,
703 grace_slash: false,
704 ottava_start: None,
705 ottava_end: false,
706 lyric: None,
707 pedal_start: false,
708 pedal_end: false,
709 slur_start: false,
710 slur_end: false,
711 arpeggiate: None,
712 technique_text: None,
713 glissando_start: false,
714 glissando_end: false,
715 cross_staff: None,
716 fingering: None,
717 string_number: None,
718 note_head: NoteHead::Normal,
719 is_cue: false,
720 trill_line_start: false,
721 trill_line_end: false,
722 guitar_technique: None,
723 }
724 }
725
726 pub fn rest(duration: Duration) -> Self {
727 Self {
728 id: Uuid::new_v4().to_string(),
729 is_rest: true,
730 pitches: Vec::new(),
731 tab_position: None,
732 duration,
733 dot_count: 0,
734 tie_start: false,
735 tie_end: false,
736 beam: BeamState::None,
737 articulations: Vec::new(),
738 dynamic: None,
739 stem_up: None,
740 hairpin_start: None,
741 hairpin_end: false,
742 tuplet: None,
743 chord_symbol: None,
744 is_grace: false,
745 grace_slash: false,
746 ottava_start: None,
747 ottava_end: false,
748 lyric: None,
749 pedal_start: false,
750 pedal_end: false,
751 slur_start: false,
752 slur_end: false,
753 arpeggiate: None,
754 technique_text: None,
755 glissando_start: false,
756 glissando_end: false,
757 cross_staff: None,
758 fingering: None,
759 string_number: None,
760 note_head: NoteHead::Normal,
761 is_cue: false,
762 trill_line_start: false,
763 trill_line_end: false,
764 guitar_technique: None,
765 }
766 }
767
768 pub fn beats(&self) -> f64 {
769 if self.is_grace || self.is_cue {
770 return 0.0;
771 }
772 let base = self.duration.beats(self.dot_count);
773 if let Some(ref t) = self.tuplet {
774 base * (t.normal_notes as f64) / (t.actual_notes as f64)
775 } else {
776 base
777 }
778 }
779}
780
781impl Duration {
782 pub fn whole_filling_beats(beats: f64) -> Duration {
784 if beats >= 4.0 {
785 Duration::Whole
786 } else if beats >= 2.0 {
787 Duration::Half
788 } else if beats >= 1.0 {
789 Duration::Quarter
790 } else if beats >= 0.5 {
791 Duration::Eighth
792 } else if beats >= 0.25 {
793 Duration::Sixteenth
794 } else if beats >= 0.125 {
795 Duration::ThirtySecond
796 } else {
797 Duration::SixtyFourth
798 }
799 }
800}
801
802#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
806pub struct NoteAddr {
807 pub part: usize,
808 pub staff: usize,
809 pub measure: usize,
810 pub voice: usize,
811 pub note: usize,
812}
813
814#[derive(Debug, Clone, Serialize, Deserialize)]
818pub enum ScoreChange {
819 MetadataChanged {
820 field: String,
821 old: String,
822 new: String,
823 },
824 TempoChanged {
825 old: u16,
826 new: u16,
827 },
828 KeySignatureChanged {
829 old: KeySignature,
830 new: KeySignature,
831 },
832 PartAdded {
833 part_index: usize,
834 },
835 PartRemoved {
836 part_index: usize,
837 name: String,
838 },
839 NoteAdded {
840 part: usize,
841 staff: usize,
842 measure: usize,
843 voice: usize,
844 note_index: usize,
845 },
846 NoteRemoved {
847 part: usize,
848 staff: usize,
849 measure: usize,
850 voice: usize,
851 note: Box<Note>,
852 },
853 NoteModified {
854 part: usize,
855 staff: usize,
856 measure: usize,
857 voice: usize,
858 note_index: usize,
859 old: Box<Note>,
860 new: Box<Note>,
861 },
862 TimeSigChanged {
863 part: usize,
864 staff: usize,
865 measure: usize,
866 old: Option<TimeSignature>,
867 new: Option<TimeSignature>,
868 },
869 MeasureTempoChanged {
870 part: usize,
871 staff: usize,
872 measure: usize,
873 old: Option<u16>,
874 new: Option<u16>,
875 },
876 BarlineChanged {
877 part: usize,
878 staff: usize,
879 measure: usize,
880 },
881 RehearsalMarkChanged {
882 part: usize,
883 staff: usize,
884 measure: usize,
885 old: Option<String>,
886 new: Option<String>,
887 },
888 VoltaChanged {
889 part: usize,
890 staff: usize,
891 measure: usize,
892 },
893}
894
895pub fn diff(a: &Score, b: &Score) -> Vec<ScoreChange> {
901 let mut changes: Vec<ScoreChange> = Vec::new();
902
903 macro_rules! meta {
904 ($field:ident, $name:literal) => {
905 if a.metadata.$field != b.metadata.$field {
906 changes.push(ScoreChange::MetadataChanged {
907 field: $name.to_string(),
908 old: a.metadata.$field.clone(),
909 new: b.metadata.$field.clone(),
910 });
911 }
912 };
913 }
914 meta!(title, "title");
915 meta!(composer, "composer");
916 meta!(lyricist, "lyricist");
917 meta!(copyright, "copyright");
918 meta!(work_number, "work_number");
919 meta!(movement_title, "movement_title");
920
921 if a.settings.tempo_bpm != b.settings.tempo_bpm {
922 changes.push(ScoreChange::TempoChanged {
923 old: a.settings.tempo_bpm,
924 new: b.settings.tempo_bpm,
925 });
926 }
927 if a.settings.key_signature != b.settings.key_signature {
928 changes.push(ScoreChange::KeySignatureChanged {
929 old: a.settings.key_signature.clone(),
930 new: b.settings.key_signature.clone(),
931 });
932 }
933
934 let a_len = a.parts.len();
935 let b_len = b.parts.len();
936 for i in b_len..a_len {
937 changes.push(ScoreChange::PartRemoved {
938 part_index: i,
939 name: a.parts[i].name.clone(),
940 });
941 }
942 for i in a_len..b_len {
943 changes.push(ScoreChange::PartAdded { part_index: i });
944 }
945
946 for pi in 0..a_len.min(b_len) {
947 let ap = &a.parts[pi];
948 let bp = &b.parts[pi];
949 for si in 0..ap.staves.len().min(bp.staves.len()) {
950 let a_staff = &ap.staves[si];
951 let b_staff = &bp.staves[si];
952 for mi in 0..a_staff.measures.len().min(b_staff.measures.len()) {
953 let am = &a_staff.measures[mi];
954 let bm = &b_staff.measures[mi];
955 for vi in 0..4usize {
956 let av = &am.voices[vi];
957 let bv = &bm.voices[vi];
958 for (ni, (a_note, b_note)) in av.iter().zip(bv.iter()).enumerate() {
959 if !note_content_eq(a_note, b_note) {
960 changes.push(ScoreChange::NoteModified {
961 part: pi,
962 staff: si,
963 measure: mi,
964 voice: vi,
965 note_index: ni,
966 old: Box::new(a_note.clone()),
967 new: Box::new(b_note.clone()),
968 });
969 }
970 }
971 for note in av.iter().skip(bv.len()) {
972 changes.push(ScoreChange::NoteRemoved {
973 part: pi,
974 staff: si,
975 measure: mi,
976 voice: vi,
977 note: Box::new(note.clone()),
978 });
979 }
980 for ni in av.len()..bv.len() {
981 changes.push(ScoreChange::NoteAdded {
982 part: pi,
983 staff: si,
984 measure: mi,
985 voice: vi,
986 note_index: ni,
987 });
988 }
989 }
990 if am.time_sig != bm.time_sig {
991 changes.push(ScoreChange::TimeSigChanged {
992 part: pi,
993 staff: si,
994 measure: mi,
995 old: am.time_sig.clone(),
996 new: bm.time_sig.clone(),
997 });
998 }
999 if am.tempo != bm.tempo {
1000 changes.push(ScoreChange::MeasureTempoChanged {
1001 part: pi,
1002 staff: si,
1003 measure: mi,
1004 old: am.tempo,
1005 new: bm.tempo,
1006 });
1007 }
1008 if am.barline_left != bm.barline_left || am.barline_right != bm.barline_right {
1009 changes.push(ScoreChange::BarlineChanged {
1010 part: pi,
1011 staff: si,
1012 measure: mi,
1013 });
1014 }
1015 if am.rehearsal != bm.rehearsal {
1016 changes.push(ScoreChange::RehearsalMarkChanged {
1017 part: pi,
1018 staff: si,
1019 measure: mi,
1020 old: am.rehearsal.clone(),
1021 new: bm.rehearsal.clone(),
1022 });
1023 }
1024 if am.volta != bm.volta {
1025 changes.push(ScoreChange::VoltaChanged {
1026 part: pi,
1027 staff: si,
1028 measure: mi,
1029 });
1030 }
1031 }
1032 }
1033 }
1034
1035 changes
1036}
1037
1038#[derive(Debug, Clone, Serialize, Deserialize)]
1045pub enum ScorePatch {
1046 SetMetadata {
1047 field: String,
1048 value: String,
1049 },
1050 SetTempo {
1051 value: u16,
1052 },
1053 SetKeySignature {
1054 part: usize,
1055 staff: usize,
1056 measure: usize,
1057 value: Option<KeySignature>,
1058 },
1059 SetTimeSignature {
1060 part: usize,
1061 staff: usize,
1062 measure: usize,
1063 value: Option<TimeSignature>,
1064 },
1065 SetBarlines {
1066 part: usize,
1067 staff: usize,
1068 measure: usize,
1069 left: Barline,
1070 right: Barline,
1071 },
1072 SetRehearsal {
1073 part: usize,
1074 staff: usize,
1075 measure: usize,
1076 value: Option<String>,
1077 },
1078 SetVolta {
1079 part: usize,
1080 staff: usize,
1081 measure: usize,
1082 value: Option<VoltaBracket>,
1083 },
1084 AddNote {
1086 part: usize,
1087 staff: usize,
1088 measure: usize,
1089 voice: usize,
1090 #[serde(default = "legacy_append_index")]
1092 note_index: usize,
1093 note: Box<Note>,
1094 },
1095 RemoveNote {
1096 part: usize,
1097 staff: usize,
1098 measure: usize,
1099 voice: usize,
1100 note_index: usize,
1101 },
1102 ReplaceNote {
1104 part: usize,
1105 staff: usize,
1106 measure: usize,
1107 voice: usize,
1108 note_index: usize,
1109 note: Box<Note>,
1110 },
1111 SetMeasureTempo {
1112 part: usize,
1113 staff: usize,
1114 measure: usize,
1115 value: Option<u16>,
1116 },
1117 ReplaceScore {
1120 score: Box<Score>,
1121 },
1122}
1123
1124fn legacy_append_index() -> usize {
1125 usize::MAX
1126}
1127
1128fn patch_requires_replace(a: &Score, b: &Score) -> bool {
1132 if a.settings.time_signature != b.settings.time_signature
1133 || a.settings.key_signature != b.settings.key_signature
1134 || a.parts.len() != b.parts.len()
1135 || a.part_groups.len() != b.part_groups.len()
1136 {
1137 return true;
1138 }
1139 if a.part_groups.iter().zip(&b.part_groups).any(|(x, y)| {
1140 x.first_part != y.first_part
1141 || x.last_part != y.last_part
1142 || x.symbol != y.symbol
1143 || x.barlines_connect != y.barlines_connect
1144 }) {
1145 return true;
1146 }
1147 for (ap, bp) in a.parts.iter().zip(&b.parts) {
1148 if ap.name != bp.name
1149 || ap.short_name != bp.short_name
1150 || ap.midi_channel != bp.midi_channel
1151 || ap.midi_program != bp.midi_program
1152 || ap.staves.len() != bp.staves.len()
1153 {
1154 return true;
1155 }
1156 for (as_, bs) in ap.staves.iter().zip(&bp.staves) {
1157 if as_.clef != bs.clef
1158 || as_.transpose_semitones != bs.transpose_semitones
1159 || as_.measures.len() != bs.measures.len()
1160 {
1161 return true;
1162 }
1163 for (am, bm) in as_.measures.iter().zip(&bs.measures) {
1164 if am.number != bm.number
1165 || am.clef != bm.clef
1166 || am.tempo_text != bm.tempo_text
1167 || am.navigation != bm.navigation
1168 || am.expression_text != bm.expression_text
1169 || am.multi_rest_count != bm.multi_rest_count
1170 || am.system_break != bm.system_break
1171 || am.page_break != bm.page_break
1172 {
1173 return true;
1174 }
1175 }
1176 }
1177 }
1178 false
1179}
1180
1181pub fn score_patch(a: &Score, b: &Score) -> Vec<ScorePatch> {
1186 let mut patches: Vec<ScorePatch> = Vec::new();
1187
1188 if patch_requires_replace(a, b) {
1189 return vec![ScorePatch::ReplaceScore {
1190 score: Box::new(b.clone()),
1191 }];
1192 }
1193
1194 macro_rules! meta {
1195 ($field:ident, $name:literal) => {
1196 if a.metadata.$field != b.metadata.$field {
1197 patches.push(ScorePatch::SetMetadata {
1198 field: $name.to_string(),
1199 value: b.metadata.$field.clone(),
1200 });
1201 }
1202 };
1203 }
1204 meta!(title, "title");
1205 meta!(composer, "composer");
1206 meta!(lyricist, "lyricist");
1207 meta!(copyright, "copyright");
1208 meta!(work_number, "work_number");
1209 meta!(movement_title, "movement_title");
1210
1211 if a.settings.tempo_bpm != b.settings.tempo_bpm {
1212 patches.push(ScorePatch::SetTempo {
1213 value: b.settings.tempo_bpm,
1214 });
1215 }
1216
1217 for pi in 0..a.parts.len().min(b.parts.len()) {
1218 let ap = &a.parts[pi];
1219 let bp = &b.parts[pi];
1220 for si in 0..ap.staves.len().min(bp.staves.len()) {
1221 let a_staff = &ap.staves[si];
1222 let b_staff = &bp.staves[si];
1223 for mi in 0..a_staff.measures.len().min(b_staff.measures.len()) {
1224 let am = &a_staff.measures[mi];
1225 let bm = &b_staff.measures[mi];
1226
1227 if am.key_sig != bm.key_sig {
1228 patches.push(ScorePatch::SetKeySignature {
1229 part: pi,
1230 staff: si,
1231 measure: mi,
1232 value: bm.key_sig.clone(),
1233 });
1234 }
1235 if am.time_sig != bm.time_sig {
1236 patches.push(ScorePatch::SetTimeSignature {
1237 part: pi,
1238 staff: si,
1239 measure: mi,
1240 value: bm.time_sig.clone(),
1241 });
1242 }
1243 if am.barline_left != bm.barline_left || am.barline_right != bm.barline_right {
1244 patches.push(ScorePatch::SetBarlines {
1245 part: pi,
1246 staff: si,
1247 measure: mi,
1248 left: bm.barline_left.clone(),
1249 right: bm.barline_right.clone(),
1250 });
1251 }
1252 if am.rehearsal != bm.rehearsal {
1253 patches.push(ScorePatch::SetRehearsal {
1254 part: pi,
1255 staff: si,
1256 measure: mi,
1257 value: bm.rehearsal.clone(),
1258 });
1259 }
1260 if am.volta != bm.volta {
1261 patches.push(ScorePatch::SetVolta {
1262 part: pi,
1263 staff: si,
1264 measure: mi,
1265 value: bm.volta.clone(),
1266 });
1267 }
1268 if am.tempo != bm.tempo {
1269 patches.push(ScorePatch::SetMeasureTempo {
1270 part: pi,
1271 staff: si,
1272 measure: mi,
1273 value: bm.tempo,
1274 });
1275 }
1276
1277 for vi in 0..4usize {
1278 let av = &am.voices[vi];
1279 let bv = &bm.voices[vi];
1280 for (ni, (a_note, b_note)) in av.iter().zip(bv.iter()).enumerate() {
1281 if !note_content_eq(a_note, b_note) {
1282 patches.push(ScorePatch::ReplaceNote {
1283 part: pi,
1284 staff: si,
1285 measure: mi,
1286 voice: vi,
1287 note_index: ni,
1288 note: Box::new(b_note.clone()),
1289 });
1290 }
1291 }
1292 for ni in (bv.len()..av.len()).rev() {
1294 patches.push(ScorePatch::RemoveNote {
1295 part: pi,
1296 staff: si,
1297 measure: mi,
1298 voice: vi,
1299 note_index: ni,
1300 });
1301 }
1302 for (offset, note) in bv.iter().skip(av.len()).enumerate() {
1304 patches.push(ScorePatch::AddNote {
1305 part: pi,
1306 staff: si,
1307 measure: mi,
1308 voice: vi,
1309 note_index: av.len() + offset,
1310 note: Box::new(note.clone()),
1311 });
1312 }
1313 }
1314 }
1315 }
1316 }
1317
1318 patches
1319}
1320
1321pub fn apply_patch(score: &Score, patches: &[ScorePatch]) -> Result<Score, Error> {
1326 let mut s = score.clone();
1327 for patch in patches {
1328 match patch {
1329 ScorePatch::ReplaceScore { score } => {
1330 s = (**score).clone();
1331 }
1332 ScorePatch::SetMetadata { field, value } => match field.as_str() {
1333 "title" => s.metadata.title = value.clone(),
1334 "composer" => s.metadata.composer = value.clone(),
1335 "lyricist" => s.metadata.lyricist = value.clone(),
1336 "copyright" => s.metadata.copyright = value.clone(),
1337 "work_number" => s.metadata.work_number = value.clone(),
1338 "movement_title" => s.metadata.movement_title = value.clone(),
1339 other => {
1340 return Err(Error::InvalidPatch(format!(
1341 "unknown metadata field: {other}"
1342 )));
1343 }
1344 },
1345 ScorePatch::SetTempo { value } => {
1346 s.settings.tempo_bpm = *value;
1347 }
1348 ScorePatch::SetKeySignature {
1349 part,
1350 staff,
1351 measure,
1352 value,
1353 } => {
1354 s.parts
1355 .get_mut(*part)
1356 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1357 .staves
1358 .get_mut(*staff)
1359 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1360 .measures
1361 .get_mut(*measure)
1362 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1363 .key_sig = value.clone();
1364 }
1365 ScorePatch::SetTimeSignature {
1366 part,
1367 staff,
1368 measure,
1369 value,
1370 } => {
1371 s.parts
1372 .get_mut(*part)
1373 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1374 .staves
1375 .get_mut(*staff)
1376 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1377 .measures
1378 .get_mut(*measure)
1379 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1380 .time_sig = value.clone();
1381 }
1382 ScorePatch::SetBarlines {
1383 part,
1384 staff,
1385 measure,
1386 left,
1387 right,
1388 } => {
1389 let m = s
1390 .parts
1391 .get_mut(*part)
1392 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1393 .staves
1394 .get_mut(*staff)
1395 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1396 .measures
1397 .get_mut(*measure)
1398 .ok_or_else(|| {
1399 Error::InvalidPatch(format!("measure {measure} out of range"))
1400 })?;
1401 m.barline_left = left.clone();
1402 m.barline_right = right.clone();
1403 }
1404 ScorePatch::SetRehearsal {
1405 part,
1406 staff,
1407 measure,
1408 value,
1409 } => {
1410 s.parts
1411 .get_mut(*part)
1412 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1413 .staves
1414 .get_mut(*staff)
1415 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1416 .measures
1417 .get_mut(*measure)
1418 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1419 .rehearsal = value.clone();
1420 }
1421 ScorePatch::SetVolta {
1422 part,
1423 staff,
1424 measure,
1425 value,
1426 } => {
1427 s.parts
1428 .get_mut(*part)
1429 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1430 .staves
1431 .get_mut(*staff)
1432 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1433 .measures
1434 .get_mut(*measure)
1435 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1436 .volta = value.clone();
1437 }
1438 ScorePatch::AddNote {
1439 part,
1440 staff,
1441 measure,
1442 voice,
1443 note_index,
1444 note,
1445 } => {
1446 let v = s
1447 .parts
1448 .get_mut(*part)
1449 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1450 .staves
1451 .get_mut(*staff)
1452 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1453 .measures
1454 .get_mut(*measure)
1455 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1456 .voices
1457 .get_mut(*voice)
1458 .ok_or_else(|| Error::InvalidPatch(format!("voice {voice} out of range")))?;
1459 let insert_at = if *note_index == usize::MAX {
1460 v.len()
1461 } else {
1462 *note_index
1463 };
1464 if insert_at > v.len() {
1465 return Err(Error::InvalidPatch(format!(
1466 "note_index {note_index} out of range"
1467 )));
1468 }
1469 v.insert(insert_at, *note.clone());
1470 }
1471 ScorePatch::RemoveNote {
1472 part,
1473 staff,
1474 measure,
1475 voice,
1476 note_index,
1477 } => {
1478 let v = s
1479 .parts
1480 .get_mut(*part)
1481 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1482 .staves
1483 .get_mut(*staff)
1484 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1485 .measures
1486 .get_mut(*measure)
1487 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1488 .voices
1489 .get_mut(*voice)
1490 .ok_or_else(|| Error::InvalidPatch(format!("voice {voice} out of range")))?;
1491 if *note_index >= v.len() {
1492 return Err(Error::InvalidPatch(format!(
1493 "note_index {note_index} out of range"
1494 )));
1495 }
1496 v.remove(*note_index);
1497 }
1498 ScorePatch::ReplaceNote {
1499 part,
1500 staff,
1501 measure,
1502 voice,
1503 note_index,
1504 note,
1505 } => {
1506 let v = s
1507 .parts
1508 .get_mut(*part)
1509 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1510 .staves
1511 .get_mut(*staff)
1512 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1513 .measures
1514 .get_mut(*measure)
1515 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1516 .voices
1517 .get_mut(*voice)
1518 .ok_or_else(|| Error::InvalidPatch(format!("voice {voice} out of range")))?;
1519 if *note_index >= v.len() {
1520 return Err(Error::InvalidPatch(format!(
1521 "note_index {note_index} out of range"
1522 )));
1523 }
1524 v[*note_index] = *note.clone();
1525 }
1526 ScorePatch::SetMeasureTempo {
1527 part,
1528 staff,
1529 measure,
1530 value,
1531 } => {
1532 s.parts
1533 .get_mut(*part)
1534 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1535 .staves
1536 .get_mut(*staff)
1537 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1538 .measures
1539 .get_mut(*measure)
1540 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1541 .tempo = *value;
1542 }
1543 }
1544 }
1545 Ok(s)
1546}
1547
1548pub fn respell_score(score: &mut Score, prefer_flat: bool) {
1552 for part in &mut score.parts {
1553 for staff in &mut part.staves {
1554 for measure in &mut staff.measures {
1555 for voice in &mut measure.voices {
1556 for note in voice.iter_mut() {
1557 for pitch in &mut note.pitches {
1558 *pitch = pitch.respell(prefer_flat);
1559 }
1560 }
1561 }
1562 }
1563 }
1564 }
1565}
1566
1567pub fn respell_score_to_key(score: &mut Score) {
1571 let prefer_flat = score.settings.key_signature.fifths < 0;
1572 respell_score(score, prefer_flat);
1573}
1574
1575pub fn score_duration_secs(score: &Score) -> f64 {
1580 if score.settings.tempo_bpm == 0 {
1581 return 0.0;
1582 }
1583 let seq = measure_sequence(score);
1584 let mut total_secs = 0.0f64;
1585 let mut current_bpm = score.settings.tempo_bpm as f64;
1586 if let Some(staff) = score.parts.first().and_then(|p| p.staves.first()) {
1587 for &idx in &seq {
1588 if let Some(m) = staff.measures.get(idx) {
1589 if let Some(b) = m.tempo {
1590 current_bpm = b as f64;
1591 }
1592 if current_bpm == 0.0 {
1593 continue;
1594 }
1595 let beats: f64 = m.voices[0].iter().map(|n| n.beats()).sum();
1596 total_secs += beats / current_bpm * 60.0;
1597 }
1598 }
1599 }
1600 total_secs
1601}
1602
1603pub fn score_duration_secs_region(score: &Score, region: (usize, usize)) -> f64 {
1608 if score.settings.tempo_bpm == 0 {
1609 return 0.0;
1610 }
1611 let seq: Vec<usize> = measure_sequence(score)
1612 .into_iter()
1613 .filter(|&idx| idx >= region.0 && idx <= region.1)
1614 .collect();
1615 let mut total_secs = 0.0f64;
1616 let mut current_bpm = score.settings.tempo_bpm as f64;
1617 if let Some(staff) = score.parts.first().and_then(|p| p.staves.first()) {
1618 for &idx in &seq {
1619 if let Some(m) = staff.measures.get(idx) {
1620 if let Some(b) = m.tempo {
1621 current_bpm = b as f64;
1622 }
1623 if current_bpm == 0.0 {
1624 continue;
1625 }
1626 let beats: f64 = m.voices[0].iter().map(|n| n.beats()).sum();
1627 total_secs += beats / current_bpm * 60.0;
1628 }
1629 }
1630 }
1631 total_secs
1632}
1633
1634pub fn measure_beats_remaining(
1639 score: &Score,
1640 part_index: usize,
1641 staff_index: usize,
1642 measure_index: usize,
1643 voice_index: usize,
1644) -> Result<f64, Error> {
1645 let part = score
1646 .parts
1647 .get(part_index)
1648 .ok_or(Error::PartNotFound(part_index))?;
1649 let staff = part
1650 .staves
1651 .get(staff_index)
1652 .ok_or(Error::StaffNotFound(staff_index))?;
1653 let measure = staff
1654 .measures
1655 .get(measure_index)
1656 .ok_or(Error::MeasureNotFound(measure_index))?;
1657 let voice = measure
1658 .voices
1659 .get(voice_index)
1660 .ok_or(Error::VoiceOutOfRange(voice_index))?;
1661 let ts = measure
1662 .time_sig
1663 .as_ref()
1664 .unwrap_or(&score.settings.time_signature);
1665 let used: f64 = voice.iter().map(|n| n.beats()).sum();
1666 Ok((ts.total_beats() - used).max(0.0))
1667}
1668
1669pub fn suggested_stem_up(pitches: &[Pitch], clef: &Clef) -> bool {
1675 if pitches.is_empty() {
1676 return true;
1677 }
1678 let avg = pitches.iter().map(|p| p.to_midi() as f64).sum::<f64>() / pitches.len() as f64;
1679 avg < clef.middle_line_midi() as f64
1680}
1681
1682fn beam_beat_size(ts: &TimeSignature) -> f64 {
1683 if ts.numerator.is_multiple_of(3) && ts.numerator >= 6 && ts.denominator >= 8 {
1684 3.0 * 4.0 / ts.denominator as f64
1685 } else {
1686 4.0 / ts.denominator as f64
1687 }
1688}
1689
1690pub fn compute_beams(notes: &[Note], time_sig: &TimeSignature) -> Vec<BeamState> {
1695 let beat_size = beam_beat_size(time_sig);
1696 let n = notes.len();
1697 let mut result = vec![BeamState::None; n];
1698
1699 let is_beamable = |note: &Note| -> bool {
1700 !note.is_rest
1701 && matches!(
1702 note.duration,
1703 Duration::Eighth
1704 | Duration::Sixteenth
1705 | Duration::ThirtySecond
1706 | Duration::SixtyFourth
1707 )
1708 };
1709
1710 let mut starts = Vec::with_capacity(n);
1712 let mut pos = 0.0f64;
1713 for note in notes {
1714 starts.push(pos);
1715 pos += note.beats();
1716 }
1717
1718 let group_id = |i: usize| -> i64 { (starts[i] / beat_size).floor() as i64 };
1720
1721 let mut i = 0;
1722 while i < n {
1723 if !is_beamable(¬es[i]) {
1724 i += 1;
1725 continue;
1726 }
1727 let g = group_id(i);
1728 let mut j = i;
1730 while j < n && is_beamable(¬es[j]) && group_id(j) == g {
1731 j += 1;
1732 }
1733 let run = j - i;
1734 if run == 1 {
1735 result[i] = BeamState::None;
1736 } else {
1737 result[i] = BeamState::Begin;
1738 result[i + 1..j - 1].fill(BeamState::Continue);
1739 result[j - 1] = BeamState::End;
1740 }
1741 i = j;
1742 }
1743 result
1744}
1745
1746fn note_content_eq(a: &Note, b: &Note) -> bool {
1747 a.is_rest == b.is_rest
1748 && a.pitches == b.pitches
1749 && a.duration == b.duration
1750 && a.dot_count == b.dot_count
1751 && a.tie_start == b.tie_start
1752 && a.tie_end == b.tie_end
1753 && a.beam == b.beam
1754 && a.articulations == b.articulations
1755 && a.dynamic == b.dynamic
1756 && a.stem_up == b.stem_up
1757 && a.hairpin_start == b.hairpin_start
1758 && a.hairpin_end == b.hairpin_end
1759 && a.tuplet == b.tuplet
1760 && a.chord_symbol == b.chord_symbol
1761 && a.is_grace == b.is_grace
1762 && a.grace_slash == b.grace_slash
1763 && a.ottava_start == b.ottava_start
1764 && a.ottava_end == b.ottava_end
1765 && a.lyric == b.lyric
1766 && a.pedal_start == b.pedal_start
1767 && a.pedal_end == b.pedal_end
1768 && a.slur_start == b.slur_start
1769 && a.slur_end == b.slur_end
1770 && a.arpeggiate == b.arpeggiate
1771}
1772
1773#[cfg(test)]
1774mod tests {
1775 use super::*;
1776 use crate::model::pitch::Step;
1777
1778 #[test]
1779 fn default_score_has_one_part_four_measures() {
1780 let score = Score::default();
1781 assert_eq!(score.parts.len(), 1);
1782 assert_eq!(score.parts[0].staves.len(), 1);
1783 assert_eq!(score.parts[0].staves[0].measures.len(), 4);
1784 }
1785
1786 #[test]
1787 fn new_score_measure_count() {
1788 let score = Score::new("Test", 120, 4, 4, 0, 8);
1789 assert_eq!(score.measure_count(), 8);
1790 }
1791
1792 #[test]
1793 fn note_beats_quarter() {
1794 let note = Note::new(Pitch::new(Step::C, 4), Duration::Quarter);
1795 assert!((note.beats() - 1.0).abs() < 1e-9);
1796 }
1797
1798 #[test]
1799 fn note_beats_dotted_quarter() {
1800 let mut note = Note::new(Pitch::new(Step::C, 4), Duration::Quarter);
1801 note.dot_count = 1;
1802 assert!((note.beats() - 1.5).abs() < 1e-9);
1803 }
1804
1805 #[test]
1806 fn grace_note_beats_zero() {
1807 let mut note = Note::new(Pitch::new(Step::C, 4), Duration::Eighth);
1808 note.is_grace = true;
1809 assert_eq!(note.beats(), 0.0);
1810 }
1811
1812 #[test]
1813 fn measure_empty_4_4_fills_four_beats() {
1814 let m = Measure::empty(4, 4);
1815 let total: f64 = m.voices[0].iter().map(|n| n.beats()).sum();
1816 assert!((total - 4.0).abs() < 1e-9);
1817 }
1818
1819 #[test]
1820 fn measure_empty_3_4_fills_three_beats() {
1821 let m = Measure::empty(3, 4);
1822 let total: f64 = m.voices[0].iter().map(|n| n.beats()).sum();
1823 assert!((total - 3.0).abs() < 1e-9);
1824 }
1825
1826 #[test]
1827 fn whole_filling_beats() {
1828 assert_eq!(Duration::whole_filling_beats(4.0), Duration::Whole);
1829 assert_eq!(Duration::whole_filling_beats(2.0), Duration::Half);
1830 assert_eq!(Duration::whole_filling_beats(1.0), Duration::Quarter);
1831 }
1832
1833 #[test]
1836 fn statistics_default_score_all_rests() {
1837 let score = Score::default();
1838 let s = score.statistics();
1839 assert_eq!(s.part_count, 1);
1840 assert_eq!(s.measure_count, 4);
1841 assert_eq!(s.note_count, 0);
1842 assert!(s.rest_count > 0);
1843 }
1844
1845 #[test]
1846 fn statistics_duration_estimate() {
1847 let score = Score::new("T", 120, 4, 4, 0, 1);
1849 let s = score.statistics();
1850 assert!((s.estimated_duration_secs - 2.0).abs() < 0.01);
1851 }
1852
1853 #[test]
1854 fn score_duration_secs_matches_statistics() {
1855 use super::score_duration_secs;
1856 let score = Score::new("T", 120, 4, 4, 0, 4);
1857 let secs = score_duration_secs(&score);
1858 assert!((secs - 8.0).abs() < 0.01, "expected ~8.0 s, got {secs}");
1860 }
1861
1862 #[test]
1863 fn score_duration_secs_zero_bpm_returns_zero() {
1864 use super::score_duration_secs;
1865 let mut score = Score::new("T", 120, 4, 4, 0, 1);
1866 score.settings.tempo_bpm = 0;
1867 assert_eq!(score_duration_secs(&score), 0.0);
1868 }
1869
1870 #[test]
1871 fn score_duration_secs_per_measure_tempo() {
1872 use super::score_duration_secs;
1873 let mut score = Score::new("T", 120, 4, 4, 0, 2);
1875 score.parts[0].staves[0].measures[1].tempo = Some(60);
1876 let secs = score_duration_secs(&score);
1877 assert!((secs - 6.0).abs() < 0.01, "expected ~6.0 s, got {secs}");
1878 }
1879
1880 #[test]
1883 fn extract_part_returns_single_part_score() {
1884 let mut score = Score::default();
1885 let mut p2 = Part::new("Violin", "Vln.");
1886 p2.staves.push(Staff::new(Clef::Treble));
1887 score.parts.push(p2);
1888 let ex = score.extract_part(0).unwrap();
1889 assert_eq!(ex.parts.len(), 1);
1890 assert_ne!(ex.id, score.id);
1891 assert_eq!(ex.metadata.title, score.metadata.title);
1892 }
1893
1894 #[test]
1895 fn extract_part_out_of_range_is_none() {
1896 let score = Score::default();
1897 assert!(score.extract_part(99).is_none());
1898 }
1899
1900 #[test]
1903 fn transpose_zero_is_clone() {
1904 let score = Score::new("T", 120, 4, 4, 0, 1);
1905 let t = transpose(&score, 0);
1906 assert_eq!(t.settings.key_signature.fifths, 0);
1907 }
1908
1909 #[test]
1910 fn transpose_c_major_up_2_to_d_major() {
1911 let score = Score::new("T", 120, 4, 4, 0, 1);
1912 assert_eq!(transpose(&score, 2).settings.key_signature.fifths, 2);
1913 }
1914
1915 #[test]
1916 fn transpose_d_major_up_5_to_g_major() {
1917 let score = Score::new("T", 120, 4, 4, 2, 1);
1918 assert_eq!(transpose(&score, 5).settings.key_signature.fifths, 1);
1919 }
1920
1921 #[test]
1922 fn transpose_c4_up_1_to_csharp4() {
1923 let p = transpose_pitch(&Pitch::new(Step::C, 4), 1);
1924 assert_eq!(p.to_midi(), 61);
1925 assert_eq!(p.step, Step::C);
1926 assert_eq!(p.alter, 1);
1927 }
1928
1929 #[test]
1930 fn transpose_c4_down_1_to_b3() {
1931 let p = transpose_pitch(&Pitch::new(Step::C, 4), -1);
1932 assert_eq!(p.to_midi(), 59);
1933 assert_eq!(p.step, Step::B);
1934 assert_eq!(p.alter, 0);
1935 }
1936
1937 #[test]
1938 fn transpose_up_octave_keeps_step() {
1939 let p = transpose_pitch(&Pitch::new(Step::A, 4), 12);
1940 assert_eq!(p.to_midi(), 81);
1941 assert_eq!(p.step, Step::A);
1942 assert_eq!(p.octave, 5);
1943 }
1944
1945 #[test]
1946 fn statistics_with_repeat_doubles_duration() {
1947 let mut score = Score::new("T", 120, 4, 4, 0, 2);
1949 score.parts[0].staves[0].measures[0].barline_left =
1950 crate::model::notation::Barline::RepeatStart;
1951 score.parts[0].staves[0].measures[1].barline_right =
1952 crate::model::notation::Barline::RepeatEnd;
1953 let s = score.statistics();
1954 assert!((s.estimated_duration_secs - 8.0).abs() < 0.01);
1956 }
1957
1958 #[test]
1959 fn transpose_octave_boundary_b4_to_c5() {
1960 let p = transpose_pitch(&Pitch::new(Step::B, 4), 1);
1962 assert_eq!(p.to_midi(), 72);
1963 assert_eq!(p.step, Step::C);
1964 assert_eq!(p.octave, 5);
1965 }
1966
1967 #[test]
1968 fn transpose_clamp_at_midi_127() {
1969 let p = transpose_pitch(&Pitch::new(Step::G, 9), 3);
1971 assert_eq!(p.to_midi(), 127);
1972 }
1973
1974 #[test]
1977 fn merge_combines_parts() {
1978 let mut a = Score::new("A", 120, 4, 4, 0, 2);
1979 let b = Score::new("B", 120, 4, 4, 0, 2);
1980 let mut p2 = Part::new("Violin", "Vln.");
1982 p2.staves.push(Staff::new(Clef::Treble));
1983 for i in 0..2usize {
1984 let mut m = Measure::empty(4, 4);
1985 m.number = i as u32 + 1;
1986 p2.staves[0].measures.push(m);
1987 }
1988 a.parts.push(p2);
1989 let merged = a.merge(&b);
1990 assert_eq!(merged.parts.len(), 3);
1992 }
1993
1994 #[test]
1995 fn merge_pads_shorter_score() {
1996 let a = Score::new("A", 120, 4, 4, 0, 4);
1997 let b = Score::new("B", 120, 4, 4, 0, 2);
1998 let merged = a.merge(&b);
1999 assert_eq!(merged.parts[0].staves[0].measures.len(), 4);
2001 assert_eq!(merged.parts[1].staves[0].measures.len(), 4);
2002 }
2003
2004 #[test]
2005 fn merge_uses_self_metadata() {
2006 let mut a = Score::new("Title A", 120, 4, 4, 0, 2);
2007 a.metadata.composer = "Composer A".to_string();
2008 let b = Score::new("Title B", 120, 4, 4, 0, 2);
2009 let merged = a.merge(&b);
2010 assert_eq!(merged.metadata.title, "Title A");
2011 assert_eq!(merged.metadata.composer, "Composer A");
2012 }
2013
2014 #[test]
2015 fn merge_new_id_differs_from_both() {
2016 let a = Score::new("A", 120, 4, 4, 0, 2);
2017 let b = Score::new("B", 120, 4, 4, 0, 2);
2018 let merged = a.merge(&b);
2019 assert_ne!(merged.id, a.id);
2020 assert_ne!(merged.id, b.id);
2021 }
2022
2023 #[test]
2026 fn staff_default_transpose_is_zero() {
2027 let s = Staff::new(Clef::Treble);
2028 assert_eq!(s.transpose_semitones, 0);
2029 }
2030
2031 #[test]
2034 fn score_default_has_schema_version_1() {
2035 let score = Score::default();
2036 assert_eq!(score.schema_version, 1);
2037 }
2038
2039 #[test]
2040 fn score_new_has_schema_version_1() {
2041 let score = Score::new("T", 120, 4, 4, 0, 4);
2042 assert_eq!(score.schema_version, 1);
2043 }
2044
2045 #[test]
2046 fn score_without_schema_version_deserializes_to_zero() {
2047 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":[]}"#;
2048 let score: Score = serde_json::from_str(json).unwrap();
2049 assert_eq!(score.schema_version, 0);
2050 }
2051
2052 #[test]
2055 fn score_template_solo_has_one_part_treble() {
2056 let score = Score::template(ScoreTemplate::Solo);
2057 assert_eq!(score.parts.len(), 1);
2058 assert_eq!(score.parts[0].staves.len(), 1);
2059 assert_eq!(score.parts[0].staves[0].clef, Clef::Treble);
2060 assert_eq!(score.parts[0].midi_program, 0);
2061 }
2062
2063 #[test]
2064 fn score_template_piano_has_two_staves() {
2065 let score = Score::template(ScoreTemplate::Piano);
2066 assert_eq!(score.parts.len(), 1);
2067 assert_eq!(score.parts[0].staves.len(), 2);
2068 assert_eq!(score.parts[0].staves[0].clef, Clef::Treble);
2069 assert_eq!(score.parts[0].staves[1].clef, Clef::Bass);
2070 }
2071
2072 #[test]
2073 fn score_template_string_quartet_has_four_parts() {
2074 let score = Score::template(ScoreTemplate::StringQuartet);
2075 assert_eq!(score.parts.len(), 4);
2076 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);
2079 assert_eq!(score.parts[3].midi_program, 42);
2080 }
2081
2082 #[test]
2083 fn score_template_string_orchestra_has_five_parts() {
2084 let score = Score::template(ScoreTemplate::StringOrchestra);
2085 assert_eq!(score.parts.len(), 5);
2086 assert_eq!(score.parts[4].midi_program, 43); }
2088
2089 #[test]
2090 fn score_template_brass_quintet_has_five_parts() {
2091 let score = Score::template(ScoreTemplate::BrassQuintet);
2092 assert_eq!(score.parts.len(), 5);
2093 assert_eq!(score.parts[2].midi_program, 60); }
2095
2096 #[test]
2097 fn score_template_default_measures_are_four() {
2098 let score = Score::template(ScoreTemplate::StringQuartet);
2099 for part in &score.parts {
2100 for staff in &part.staves {
2101 assert_eq!(staff.measures.len(), 4);
2102 }
2103 }
2104 }
2105
2106 #[test]
2109 fn measure_empty_has_no_breaks() {
2110 let m = Measure::empty(4, 4);
2111 assert!(!m.system_break);
2112 assert!(!m.page_break);
2113 }
2114
2115 #[test]
2116 fn system_break_survives_json_roundtrip() {
2117 let mut m = Measure::empty(4, 4);
2118 m.system_break = true;
2119 let json = serde_json::to_string(&m).unwrap();
2120 let m2: Measure = serde_json::from_str(&json).unwrap();
2121 assert!(m2.system_break);
2122 assert!(!m2.page_break);
2123 }
2124
2125 #[test]
2128 fn diff_identical_scores_is_empty() {
2129 let s = Score::new("T", 120, 4, 4, 0, 2);
2130 assert!(diff(&s, &s).is_empty());
2131 }
2132
2133 #[test]
2134 fn score_patch_covers_measure_semantics_and_note_insert_index() {
2135 let a = Score::new("T", 120, 4, 4, 0, 1);
2136 let mut b = a.clone();
2137 let measure = &mut b.parts[0].staves[0].measures[0];
2138 measure.key_sig = Some(KeySignature {
2139 fifths: -2,
2140 mode: "major".to_string(),
2141 });
2142 measure.time_sig = Some(TimeSignature {
2143 numerator: 3,
2144 denominator: 4,
2145 });
2146 measure.barline_left = Barline::RepeatStart;
2147 measure.barline_right = Barline::RepeatEnd;
2148 measure.rehearsal = Some("A".to_string());
2149 measure.volta = Some(VoltaBracket {
2150 number: 1,
2151 kind: "begin_end".to_string(),
2152 });
2153 measure.voices[0].insert(0, Note::new(Pitch::new(Step::C, 4), Duration::Quarter));
2154 let expected = b.parts[0].staves[0].measures[0].clone();
2155
2156 let patches = score_patch(&a, &b);
2157 assert!(
2158 patches
2159 .iter()
2160 .any(|p| matches!(p, ScorePatch::SetTimeSignature { .. }))
2161 );
2162 assert!(
2163 patches
2164 .iter()
2165 .any(|p| matches!(p, ScorePatch::SetBarlines { .. }))
2166 );
2167 assert!(
2168 patches
2169 .iter()
2170 .any(|p| matches!(p, ScorePatch::SetRehearsal { .. }))
2171 );
2172 assert!(
2173 patches
2174 .iter()
2175 .any(|p| matches!(p, ScorePatch::SetVolta { .. }))
2176 );
2177 let result = apply_patch(&a, &patches).expect("patch application failed");
2178 let result_measure = &result.parts[0].staves[0].measures[0];
2179 assert_eq!(result_measure.key_sig, expected.key_sig);
2180 assert_eq!(result_measure.time_sig, expected.time_sig);
2181 assert_eq!(result_measure.barline_left, expected.barline_left);
2182 assert_eq!(result_measure.barline_right, expected.barline_right);
2183 assert_eq!(result_measure.rehearsal, expected.rehearsal);
2184 assert_eq!(result_measure.volta, expected.volta);
2185 assert_eq!(result_measure.voices[0].len(), expected.voices[0].len());
2186 }
2187
2188 #[test]
2189 fn score_patch_replaces_when_structure_or_uncovered_fields_change() {
2190 let a = Score::new("T", 120, 4, 4, 0, 1);
2191 let mut b = a.clone();
2192 b.parts[0].name = "Piano".to_string();
2193 b.parts[0].staves[0].measures[0].expression_text = Some("dolce".to_string());
2194 let patches = score_patch(&a, &b);
2195 assert!(matches!(
2196 patches.as_slice(),
2197 [ScorePatch::ReplaceScore { .. }]
2198 ));
2199 let result = apply_patch(&a, &patches).expect("replacement failed");
2200 assert_eq!(result.parts[0].name, "Piano");
2201 assert_eq!(
2202 result.parts[0].staves[0].measures[0].expression_text,
2203 Some("dolce".to_string())
2204 );
2205 }
2206
2207 #[test]
2208 fn diff_detects_tempo_change() {
2209 let a = Score::new("T", 120, 4, 4, 0, 1);
2210 let mut b = a.clone();
2211 b.settings.tempo_bpm = 90;
2212 let changes = diff(&a, &b);
2213 assert_eq!(changes.len(), 1);
2214 assert!(matches!(
2215 changes[0],
2216 ScoreChange::TempoChanged { old: 120, new: 90 }
2217 ));
2218 }
2219
2220 #[test]
2221 fn diff_detects_title_change() {
2222 let a = Score::new("Old Title", 120, 4, 4, 0, 1);
2223 let mut b = a.clone();
2224 b.metadata.title = "New Title".to_string();
2225 let changes = diff(&a, &b);
2226 assert!(
2227 changes.iter().any(
2228 |c| matches!(c, ScoreChange::MetadataChanged { field, .. } if field == "title")
2229 )
2230 );
2231 }
2232
2233 #[test]
2234 fn diff_detects_note_modification() {
2235 let mut a = Score::new("T", 120, 4, 4, 0, 1);
2236 a.parts[0].staves[0].measures[0].voices[0] =
2237 vec![Note::new(Pitch::new(Step::C, 4), Duration::Quarter)];
2238 let mut b = a.clone();
2239 b.parts[0].staves[0].measures[0].voices[0][0] =
2240 Note::new(Pitch::new(Step::D, 4), Duration::Quarter);
2241 let changes = diff(&a, &b);
2242 assert!(
2243 changes
2244 .iter()
2245 .any(|c| matches!(c, ScoreChange::NoteModified { .. }))
2246 );
2247 }
2248
2249 #[test]
2250 fn diff_detects_part_added() {
2251 let a = Score::new("T", 120, 4, 4, 0, 1);
2252 let mut b = a.clone();
2253 let mut p = Part::new("Violin", "Vln.");
2254 p.staves.push(Staff::new(Clef::Treble));
2255 b.parts.push(p);
2256 let changes = diff(&a, &b);
2257 assert!(
2258 changes
2259 .iter()
2260 .any(|c| matches!(c, ScoreChange::PartAdded { part_index: 1 }))
2261 );
2262 }
2263
2264 #[test]
2265 fn diff_detects_measure_tempo_change() {
2266 let a = Score::new("T", 120, 4, 4, 0, 2);
2267 let mut b = a.clone();
2268 b.parts[0].staves[0].measures[1].tempo = Some(60);
2269 let changes = diff(&a, &b);
2270 assert!(changes.iter().any(|c| matches!(
2271 c,
2272 ScoreChange::MeasureTempoChanged {
2273 measure: 1,
2274 old: None,
2275 new: Some(60),
2276 ..
2277 }
2278 )));
2279 }
2280
2281 #[test]
2282 fn diff_detects_barline_change() {
2283 use crate::model::notation::Barline;
2284 let a = Score::new("T", 120, 4, 4, 0, 2);
2285 let mut b = a.clone();
2286 b.parts[0].staves[0].measures[0].barline_left = Barline::RepeatStart;
2287 let changes = diff(&a, &b);
2288 assert!(
2289 changes
2290 .iter()
2291 .any(|c| matches!(c, ScoreChange::BarlineChanged { measure: 0, .. }))
2292 );
2293 }
2294
2295 #[test]
2296 fn diff_detects_rehearsal_change() {
2297 let a = Score::new("T", 120, 4, 4, 0, 2);
2298 let mut b = a.clone();
2299 b.parts[0].staves[0].measures[0].rehearsal = Some("A".to_string());
2300 let changes = diff(&a, &b);
2301 assert!(
2302 changes
2303 .iter()
2304 .any(|c| matches!(c, ScoreChange::RehearsalMarkChanged { measure: 0, .. }))
2305 );
2306 }
2307
2308 #[test]
2309 fn diff_detects_volta_change() {
2310 use super::VoltaBracket;
2311 let a = Score::new("T", 120, 4, 4, 0, 2);
2312 let mut b = a.clone();
2313 b.parts[0].staves[0].measures[0].volta = Some(VoltaBracket {
2314 number: 1,
2315 kind: "begin_end".into(),
2316 });
2317 let changes = diff(&a, &b);
2318 assert!(
2319 changes
2320 .iter()
2321 .any(|c| matches!(c, ScoreChange::VoltaChanged { measure: 0, .. }))
2322 );
2323 }
2324
2325 #[test]
2326 fn diff_detects_key_signature_change() {
2327 let a = Score::new("T", 120, 4, 4, 0, 1);
2328 let mut b = a.clone();
2329 b.settings.key_signature.fifths = 2; let changes = diff(&a, &b);
2331 assert!(
2332 changes
2333 .iter()
2334 .any(|c| matches!(c, ScoreChange::KeySignatureChanged { .. }))
2335 );
2336 }
2337
2338 #[test]
2339 fn diff_same_key_signature_no_change() {
2340 let a = Score::new("T", 120, 4, 4, 2, 1);
2341 let changes = diff(&a, &a);
2342 assert!(changes.is_empty());
2343 }
2344
2345 #[test]
2346 fn score_duration_secs_region_partial() {
2347 use super::score_duration_secs_region;
2348 let score = Score::new("T", 120, 4, 4, 0, 4);
2350 let secs = score_duration_secs_region(&score, (1, 2));
2351 assert!((secs - 4.0).abs() < 0.01, "expected ~4.0 s, got {secs}");
2352 }
2353
2354 #[test]
2355 fn score_duration_secs_region_single_measure() {
2356 use super::score_duration_secs_region;
2357 let score = Score::new("T", 120, 4, 4, 0, 4);
2359 let secs = score_duration_secs_region(&score, (0, 0));
2360 assert!((secs - 2.0).abs() < 0.01, "expected ~2.0 s, got {secs}");
2361 }
2362
2363 #[test]
2366 fn measure_beats_remaining_empty_voice_returns_full() {
2367 use super::measure_beats_remaining;
2368 let mut score = Score::new("T", 120, 4, 4, 0, 1);
2369 score.parts[0].staves[0].measures[0].voices[0].clear();
2370 let rem = measure_beats_remaining(&score, 0, 0, 0, 0).unwrap();
2371 assert!(
2372 (rem - 4.0).abs() < 1e-9,
2373 "expected 4.0 remaining, got {rem}"
2374 );
2375 }
2376
2377 #[test]
2378 fn measure_beats_remaining_half_full_returns_half() {
2379 use super::measure_beats_remaining;
2380 use crate::model::pitch::Step;
2381 let mut score = Score::new("T", 120, 4, 4, 0, 1);
2382 score.parts[0].staves[0].measures[0].voices[0] = vec![
2383 Note::new(Pitch::new(Step::C, 4), Duration::Quarter),
2384 Note::new(Pitch::new(Step::D, 4), Duration::Quarter),
2385 ];
2386 let rem = measure_beats_remaining(&score, 0, 0, 0, 0).unwrap();
2387 assert!(
2388 (rem - 2.0).abs() < 1e-9,
2389 "expected 2.0 remaining, got {rem}"
2390 );
2391 }
2392
2393 #[test]
2394 fn measure_beats_remaining_full_voice_returns_zero() {
2395 use super::measure_beats_remaining;
2396 use crate::model::pitch::Step;
2397 let mut score = Score::new("T", 120, 4, 4, 0, 1);
2398 score.parts[0].staves[0].measures[0].voices[0] =
2399 vec![Note::new(Pitch::new(Step::C, 4), Duration::Whole)];
2400 let rem = measure_beats_remaining(&score, 0, 0, 0, 0).unwrap();
2401 assert!((rem).abs() < 1e-9, "expected 0.0 remaining, got {rem}");
2402 }
2403
2404 #[test]
2405 fn measure_beats_remaining_tuplet_accounting() {
2406 use super::measure_beats_remaining;
2407 use crate::model::notation::TupletInfo;
2408 use crate::model::pitch::Step;
2409 let mut score = Score::new("T", 120, 4, 4, 0, 1);
2411 let tuplet = TupletInfo {
2412 actual_notes: 3,
2413 normal_notes: 2,
2414 };
2415 let mk = |step| {
2416 let mut n = Note::new(Pitch::new(step, 4), Duration::Quarter);
2417 n.tuplet = Some(tuplet.clone());
2418 n
2419 };
2420 score.parts[0].staves[0].measures[0].voices[0] =
2421 vec![mk(Step::C), mk(Step::D), mk(Step::E)];
2422 let rem = measure_beats_remaining(&score, 0, 0, 0, 0).unwrap();
2423 assert!(
2424 (rem - 2.0).abs() < 1e-9,
2425 "expected 2.0 remaining (triplets used 2.0), got {rem}"
2426 );
2427 }
2428
2429 #[test]
2430 fn measure_beats_remaining_out_of_range_returns_err() {
2431 use super::measure_beats_remaining;
2432 let score = Score::new("T", 120, 4, 4, 0, 1);
2433 assert!(measure_beats_remaining(&score, 99, 0, 0, 0).is_err());
2434 assert!(measure_beats_remaining(&score, 0, 99, 0, 0).is_err());
2435 assert!(measure_beats_remaining(&score, 0, 0, 99, 0).is_err());
2436 assert!(measure_beats_remaining(&score, 0, 0, 0, 4).is_err());
2437 }
2438
2439 #[test]
2440 fn note_content_eq_ignores_id() {
2441 use crate::model::pitch::Step;
2442 let mut a = Note::new(Pitch::new(Step::C, 4), Duration::Quarter);
2443 let mut b = a.clone();
2444 b.id = "different-id".to_string();
2445 assert!(note_content_eq(&a, &b));
2446 b.pitches[0] = Pitch::new(Step::D, 4);
2448 assert!(!note_content_eq(&a, &b));
2449 let mut c = a.clone();
2451 a.stem_up = Some(true);
2452 c.stem_up = Some(false);
2453 assert!(!note_content_eq(&a, &c));
2454 }
2455
2456 #[test]
2457 fn suggested_stem_up_below_middle() {
2458 use crate::model::notation::Clef;
2459 let pitches = vec![Pitch::new(Step::C, 4)];
2461 assert!(suggested_stem_up(&pitches, &Clef::Treble));
2462 }
2463
2464 #[test]
2465 fn suggested_stem_up_above_middle() {
2466 use crate::model::notation::Clef;
2467 let pitches = vec![Pitch::new(Step::G, 5)];
2469 assert!(!suggested_stem_up(&pitches, &Clef::Treble));
2470 }
2471
2472 #[test]
2473 fn suggested_stem_up_at_middle_line() {
2474 use crate::model::notation::Clef;
2475 let pitches = vec![Pitch::new(Step::B, 4)];
2477 assert!(!suggested_stem_up(&pitches, &Clef::Treble));
2478 }
2479
2480 #[test]
2481 fn suggested_stem_up_chord() {
2482 use crate::model::notation::Clef;
2483 let pitches = vec![Pitch::new(Step::C, 4), Pitch::new(Step::G, 4)];
2485 assert!(suggested_stem_up(&pitches, &Clef::Treble));
2486 }
2487
2488 #[test]
2489 fn suggested_stem_up_bass_clef() {
2490 use crate::model::notation::Clef;
2491 let pitches = vec![Pitch::new(Step::D, 3)];
2493 assert!(!suggested_stem_up(&pitches, &Clef::Bass));
2494 let pitches2 = vec![Pitch::new(Step::C, 3)];
2496 assert!(suggested_stem_up(&pitches2, &Clef::Bass));
2497 }
2498
2499 #[test]
2500 fn suggested_stem_up_empty_pitches() {
2501 use crate::model::notation::Clef;
2502 assert!(suggested_stem_up(&[], &Clef::Treble));
2503 }
2504
2505 fn eighth(pitch: Pitch) -> Note {
2506 Note::new(pitch, Duration::Eighth)
2507 }
2508 fn quarter(pitch: Pitch) -> Note {
2509 Note::new(pitch, Duration::Quarter)
2510 }
2511 fn rest_eighth() -> Note {
2512 Note::rest(Duration::Eighth)
2513 }
2514
2515 #[test]
2516 fn compute_beams_4_4_four_eighths() {
2517 use crate::model::notation::{Clef, TimeSignature};
2518 let _ = Clef::Treble; let ts = TimeSignature {
2520 numerator: 4,
2521 denominator: 4,
2522 };
2523 let c4 = Pitch::new(Step::C, 4);
2524 let notes = vec![
2525 eighth(c4.clone()),
2526 eighth(c4.clone()),
2527 eighth(c4.clone()),
2528 eighth(c4.clone()),
2529 ];
2530 let beams = compute_beams(¬es, &ts);
2531 assert_eq!(beams[0], BeamState::Begin);
2533 assert_eq!(beams[1], BeamState::End);
2534 assert_eq!(beams[2], BeamState::Begin);
2535 assert_eq!(beams[3], BeamState::End);
2536 }
2537
2538 #[test]
2539 fn compute_beams_4_4_all_eighth_one_group() {
2540 use crate::model::notation::TimeSignature;
2541 let ts = TimeSignature {
2542 numerator: 4,
2543 denominator: 4,
2544 };
2545 let c4 = Pitch::new(Step::C, 4);
2546 let notes = vec![eighth(c4.clone()), eighth(c4.clone())];
2548 let beams = compute_beams(¬es, &ts);
2549 assert_eq!(beams[0], BeamState::Begin);
2550 assert_eq!(beams[1], BeamState::End);
2551 }
2552
2553 #[test]
2554 fn compute_beams_quarter_not_beamed() {
2555 use crate::model::notation::TimeSignature;
2556 let ts = TimeSignature {
2557 numerator: 4,
2558 denominator: 4,
2559 };
2560 let c4 = Pitch::new(Step::C, 4);
2561 let notes = vec![quarter(c4.clone()), quarter(c4.clone())];
2562 let beams = compute_beams(¬es, &ts);
2563 assert_eq!(beams[0], BeamState::None);
2564 assert_eq!(beams[1], BeamState::None);
2565 }
2566
2567 #[test]
2568 fn compute_beams_rest_breaks_beam() {
2569 use crate::model::notation::TimeSignature;
2570 let ts = TimeSignature {
2571 numerator: 4,
2572 denominator: 4,
2573 };
2574 let c4 = Pitch::new(Step::C, 4);
2575 let notes = vec![eighth(c4.clone()), rest_eighth(), eighth(c4.clone())];
2576 let beams = compute_beams(¬es, &ts);
2577 assert_eq!(beams[0], BeamState::None);
2579 assert_eq!(beams[1], BeamState::None);
2580 assert_eq!(beams[2], BeamState::None);
2581 }
2582
2583 #[test]
2584 fn compute_beams_6_8_compound() {
2585 use crate::model::notation::TimeSignature;
2586 let ts = TimeSignature {
2587 numerator: 6,
2588 denominator: 8,
2589 };
2590 let c4 = Pitch::new(Step::C, 4);
2591 let notes: Vec<Note> = (0..6).map(|_| eighth(c4.clone())).collect();
2593 let beams = compute_beams(¬es, &ts);
2594 assert_eq!(beams[0], BeamState::Begin);
2595 assert_eq!(beams[1], BeamState::Continue);
2596 assert_eq!(beams[2], BeamState::End);
2597 assert_eq!(beams[3], BeamState::Begin);
2598 assert_eq!(beams[4], BeamState::Continue);
2599 assert_eq!(beams[5], BeamState::End);
2600 }
2601
2602 #[test]
2603 fn compute_beams_single_eighth() {
2604 use crate::model::notation::TimeSignature;
2605 let ts = TimeSignature {
2606 numerator: 4,
2607 denominator: 4,
2608 };
2609 let c4 = Pitch::new(Step::C, 4);
2610 let notes = vec![eighth(c4.clone())];
2611 let beams = compute_beams(¬es, &ts);
2612 assert_eq!(beams[0], BeamState::None);
2613 }
2614}