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: KeySignature,
1028 },
1029 AddNote {
1031 part: usize,
1032 staff: usize,
1033 measure: usize,
1034 voice: usize,
1035 note: Box<Note>,
1036 },
1037 RemoveNote {
1038 part: usize,
1039 staff: usize,
1040 measure: usize,
1041 voice: usize,
1042 note_index: usize,
1043 },
1044 ReplaceNote {
1046 part: usize,
1047 staff: usize,
1048 measure: usize,
1049 voice: usize,
1050 note_index: usize,
1051 note: Box<Note>,
1052 },
1053 SetMeasureTempo {
1054 part: usize,
1055 staff: usize,
1056 measure: usize,
1057 value: Option<u16>,
1058 },
1059}
1060
1061pub fn score_patch(a: &Score, b: &Score) -> Vec<ScorePatch> {
1066 let mut patches: Vec<ScorePatch> = Vec::new();
1067
1068 macro_rules! meta {
1069 ($field:ident, $name:literal) => {
1070 if a.metadata.$field != b.metadata.$field {
1071 patches.push(ScorePatch::SetMetadata {
1072 field: $name.to_string(),
1073 value: b.metadata.$field.clone(),
1074 });
1075 }
1076 };
1077 }
1078 meta!(title, "title");
1079 meta!(composer, "composer");
1080 meta!(lyricist, "lyricist");
1081 meta!(copyright, "copyright");
1082 meta!(work_number, "work_number");
1083 meta!(movement_title, "movement_title");
1084
1085 if a.settings.tempo_bpm != b.settings.tempo_bpm {
1086 patches.push(ScorePatch::SetTempo {
1087 value: b.settings.tempo_bpm,
1088 });
1089 }
1090
1091 for pi in 0..a.parts.len().min(b.parts.len()) {
1092 let ap = &a.parts[pi];
1093 let bp = &b.parts[pi];
1094 for si in 0..ap.staves.len().min(bp.staves.len()) {
1095 let a_staff = &ap.staves[si];
1096 let b_staff = &bp.staves[si];
1097 for mi in 0..a_staff.measures.len().min(b_staff.measures.len()) {
1098 let am = &a_staff.measures[mi];
1099 let bm = &b_staff.measures[mi];
1100
1101 if am.key_sig != bm.key_sig
1102 && let Some(ref ks) = bm.key_sig
1103 {
1104 patches.push(ScorePatch::SetKeySignature {
1105 part: pi,
1106 staff: si,
1107 measure: mi,
1108 value: ks.clone(),
1109 });
1110 }
1111 if am.tempo != bm.tempo {
1112 patches.push(ScorePatch::SetMeasureTempo {
1113 part: pi,
1114 staff: si,
1115 measure: mi,
1116 value: bm.tempo,
1117 });
1118 }
1119
1120 for vi in 0..4usize {
1121 let av = &am.voices[vi];
1122 let bv = &bm.voices[vi];
1123 for (ni, (a_note, b_note)) in av.iter().zip(bv.iter()).enumerate() {
1124 if !note_content_eq(a_note, b_note) {
1125 patches.push(ScorePatch::ReplaceNote {
1126 part: pi,
1127 staff: si,
1128 measure: mi,
1129 voice: vi,
1130 note_index: ni,
1131 note: Box::new(b_note.clone()),
1132 });
1133 }
1134 }
1135 for ni in (bv.len()..av.len()).rev() {
1137 patches.push(ScorePatch::RemoveNote {
1138 part: pi,
1139 staff: si,
1140 measure: mi,
1141 voice: vi,
1142 note_index: ni,
1143 });
1144 }
1145 for note in bv.iter().skip(av.len()) {
1147 patches.push(ScorePatch::AddNote {
1148 part: pi,
1149 staff: si,
1150 measure: mi,
1151 voice: vi,
1152 note: Box::new(note.clone()),
1153 });
1154 }
1155 }
1156 }
1157 }
1158 }
1159
1160 patches
1161}
1162
1163pub fn apply_patch(score: &Score, patches: &[ScorePatch]) -> Result<Score, Error> {
1168 let mut s = score.clone();
1169 for patch in patches {
1170 match patch {
1171 ScorePatch::SetMetadata { field, value } => match field.as_str() {
1172 "title" => s.metadata.title = value.clone(),
1173 "composer" => s.metadata.composer = value.clone(),
1174 "lyricist" => s.metadata.lyricist = value.clone(),
1175 "copyright" => s.metadata.copyright = value.clone(),
1176 "work_number" => s.metadata.work_number = value.clone(),
1177 "movement_title" => s.metadata.movement_title = value.clone(),
1178 other => {
1179 return Err(Error::InvalidPatch(format!(
1180 "unknown metadata field: {other}"
1181 )));
1182 }
1183 },
1184 ScorePatch::SetTempo { value } => {
1185 s.settings.tempo_bpm = *value;
1186 }
1187 ScorePatch::SetKeySignature {
1188 part,
1189 staff,
1190 measure,
1191 value,
1192 } => {
1193 s.parts
1194 .get_mut(*part)
1195 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1196 .staves
1197 .get_mut(*staff)
1198 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1199 .measures
1200 .get_mut(*measure)
1201 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1202 .key_sig = Some(value.clone());
1203 }
1204 ScorePatch::AddNote {
1205 part,
1206 staff,
1207 measure,
1208 voice,
1209 note,
1210 } => {
1211 let v = s
1212 .parts
1213 .get_mut(*part)
1214 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1215 .staves
1216 .get_mut(*staff)
1217 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1218 .measures
1219 .get_mut(*measure)
1220 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1221 .voices
1222 .get_mut(*voice)
1223 .ok_or_else(|| Error::InvalidPatch(format!("voice {voice} out of range")))?;
1224 v.push(*note.clone());
1225 }
1226 ScorePatch::RemoveNote {
1227 part,
1228 staff,
1229 measure,
1230 voice,
1231 note_index,
1232 } => {
1233 let v = s
1234 .parts
1235 .get_mut(*part)
1236 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1237 .staves
1238 .get_mut(*staff)
1239 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1240 .measures
1241 .get_mut(*measure)
1242 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1243 .voices
1244 .get_mut(*voice)
1245 .ok_or_else(|| Error::InvalidPatch(format!("voice {voice} out of range")))?;
1246 if *note_index >= v.len() {
1247 return Err(Error::InvalidPatch(format!(
1248 "note_index {note_index} out of range"
1249 )));
1250 }
1251 v.remove(*note_index);
1252 }
1253 ScorePatch::ReplaceNote {
1254 part,
1255 staff,
1256 measure,
1257 voice,
1258 note_index,
1259 note,
1260 } => {
1261 let v = s
1262 .parts
1263 .get_mut(*part)
1264 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1265 .staves
1266 .get_mut(*staff)
1267 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1268 .measures
1269 .get_mut(*measure)
1270 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1271 .voices
1272 .get_mut(*voice)
1273 .ok_or_else(|| Error::InvalidPatch(format!("voice {voice} out of range")))?;
1274 if *note_index >= v.len() {
1275 return Err(Error::InvalidPatch(format!(
1276 "note_index {note_index} out of range"
1277 )));
1278 }
1279 v[*note_index] = *note.clone();
1280 }
1281 ScorePatch::SetMeasureTempo {
1282 part,
1283 staff,
1284 measure,
1285 value,
1286 } => {
1287 s.parts
1288 .get_mut(*part)
1289 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1290 .staves
1291 .get_mut(*staff)
1292 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1293 .measures
1294 .get_mut(*measure)
1295 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1296 .tempo = *value;
1297 }
1298 }
1299 }
1300 Ok(s)
1301}
1302
1303pub fn respell_score(score: &mut Score, prefer_flat: bool) {
1307 for part in &mut score.parts {
1308 for staff in &mut part.staves {
1309 for measure in &mut staff.measures {
1310 for voice in &mut measure.voices {
1311 for note in voice.iter_mut() {
1312 for pitch in &mut note.pitches {
1313 *pitch = pitch.respell(prefer_flat);
1314 }
1315 }
1316 }
1317 }
1318 }
1319 }
1320}
1321
1322pub fn respell_score_to_key(score: &mut Score) {
1326 let prefer_flat = score.settings.key_signature.fifths < 0;
1327 respell_score(score, prefer_flat);
1328}
1329
1330pub fn score_duration_secs(score: &Score) -> f64 {
1335 if score.settings.tempo_bpm == 0 {
1336 return 0.0;
1337 }
1338 let seq = measure_sequence(score);
1339 let mut total_secs = 0.0f64;
1340 let mut current_bpm = score.settings.tempo_bpm as f64;
1341 if let Some(staff) = score.parts.first().and_then(|p| p.staves.first()) {
1342 for &idx in &seq {
1343 if let Some(m) = staff.measures.get(idx) {
1344 if let Some(b) = m.tempo {
1345 current_bpm = b as f64;
1346 }
1347 if current_bpm == 0.0 {
1348 continue;
1349 }
1350 let beats: f64 = m.voices[0].iter().map(|n| n.beats()).sum();
1351 total_secs += beats / current_bpm * 60.0;
1352 }
1353 }
1354 }
1355 total_secs
1356}
1357
1358pub fn score_duration_secs_region(score: &Score, region: (usize, usize)) -> f64 {
1363 if score.settings.tempo_bpm == 0 {
1364 return 0.0;
1365 }
1366 let seq: Vec<usize> = measure_sequence(score)
1367 .into_iter()
1368 .filter(|&idx| idx >= region.0 && idx <= region.1)
1369 .collect();
1370 let mut total_secs = 0.0f64;
1371 let mut current_bpm = score.settings.tempo_bpm as f64;
1372 if let Some(staff) = score.parts.first().and_then(|p| p.staves.first()) {
1373 for &idx in &seq {
1374 if let Some(m) = staff.measures.get(idx) {
1375 if let Some(b) = m.tempo {
1376 current_bpm = b as f64;
1377 }
1378 if current_bpm == 0.0 {
1379 continue;
1380 }
1381 let beats: f64 = m.voices[0].iter().map(|n| n.beats()).sum();
1382 total_secs += beats / current_bpm * 60.0;
1383 }
1384 }
1385 }
1386 total_secs
1387}
1388
1389pub fn measure_beats_remaining(
1394 score: &Score,
1395 part_index: usize,
1396 staff_index: usize,
1397 measure_index: usize,
1398 voice_index: usize,
1399) -> Result<f64, Error> {
1400 let part = score
1401 .parts
1402 .get(part_index)
1403 .ok_or(Error::PartNotFound(part_index))?;
1404 let staff = part
1405 .staves
1406 .get(staff_index)
1407 .ok_or(Error::StaffNotFound(staff_index))?;
1408 let measure = staff
1409 .measures
1410 .get(measure_index)
1411 .ok_or(Error::MeasureNotFound(measure_index))?;
1412 let voice = measure
1413 .voices
1414 .get(voice_index)
1415 .ok_or(Error::VoiceOutOfRange(voice_index))?;
1416 let ts = measure
1417 .time_sig
1418 .as_ref()
1419 .unwrap_or(&score.settings.time_signature);
1420 let used: f64 = voice.iter().map(|n| n.beats()).sum();
1421 Ok((ts.total_beats() - used).max(0.0))
1422}
1423
1424pub fn suggested_stem_up(pitches: &[Pitch], clef: &Clef) -> bool {
1430 if pitches.is_empty() {
1431 return true;
1432 }
1433 let avg = pitches.iter().map(|p| p.to_midi() as f64).sum::<f64>() / pitches.len() as f64;
1434 avg < clef.middle_line_midi() as f64
1435}
1436
1437fn beam_beat_size(ts: &TimeSignature) -> f64 {
1438 if ts.numerator.is_multiple_of(3) && ts.numerator >= 6 && ts.denominator >= 8 {
1439 3.0 * 4.0 / ts.denominator as f64
1440 } else {
1441 4.0 / ts.denominator as f64
1442 }
1443}
1444
1445pub fn compute_beams(notes: &[Note], time_sig: &TimeSignature) -> Vec<BeamState> {
1450 let beat_size = beam_beat_size(time_sig);
1451 let n = notes.len();
1452 let mut result = vec![BeamState::None; n];
1453
1454 let is_beamable = |note: &Note| -> bool {
1455 !note.is_rest
1456 && matches!(
1457 note.duration,
1458 Duration::Eighth
1459 | Duration::Sixteenth
1460 | Duration::ThirtySecond
1461 | Duration::SixtyFourth
1462 )
1463 };
1464
1465 let mut starts = Vec::with_capacity(n);
1467 let mut pos = 0.0f64;
1468 for note in notes {
1469 starts.push(pos);
1470 pos += note.beats();
1471 }
1472
1473 let group_id = |i: usize| -> i64 { (starts[i] / beat_size).floor() as i64 };
1475
1476 let mut i = 0;
1477 while i < n {
1478 if !is_beamable(¬es[i]) {
1479 i += 1;
1480 continue;
1481 }
1482 let g = group_id(i);
1483 let mut j = i;
1485 while j < n && is_beamable(¬es[j]) && group_id(j) == g {
1486 j += 1;
1487 }
1488 let run = j - i;
1489 if run == 1 {
1490 result[i] = BeamState::None;
1491 } else {
1492 result[i] = BeamState::Begin;
1493 result[i + 1..j - 1].fill(BeamState::Continue);
1494 result[j - 1] = BeamState::End;
1495 }
1496 i = j;
1497 }
1498 result
1499}
1500
1501fn note_content_eq(a: &Note, b: &Note) -> bool {
1502 a.is_rest == b.is_rest
1503 && a.pitches == b.pitches
1504 && a.duration == b.duration
1505 && a.dot_count == b.dot_count
1506 && a.tie_start == b.tie_start
1507 && a.tie_end == b.tie_end
1508 && a.beam == b.beam
1509 && a.articulations == b.articulations
1510 && a.dynamic == b.dynamic
1511 && a.stem_up == b.stem_up
1512 && a.hairpin_start == b.hairpin_start
1513 && a.hairpin_end == b.hairpin_end
1514 && a.tuplet == b.tuplet
1515 && a.chord_symbol == b.chord_symbol
1516 && a.is_grace == b.is_grace
1517 && a.grace_slash == b.grace_slash
1518 && a.ottava_start == b.ottava_start
1519 && a.ottava_end == b.ottava_end
1520 && a.lyric == b.lyric
1521 && a.pedal_start == b.pedal_start
1522 && a.pedal_end == b.pedal_end
1523 && a.slur_start == b.slur_start
1524 && a.slur_end == b.slur_end
1525 && a.arpeggiate == b.arpeggiate
1526}
1527
1528#[cfg(test)]
1529mod tests {
1530 use super::*;
1531 use crate::model::pitch::Step;
1532
1533 #[test]
1534 fn default_score_has_one_part_four_measures() {
1535 let score = Score::default();
1536 assert_eq!(score.parts.len(), 1);
1537 assert_eq!(score.parts[0].staves.len(), 1);
1538 assert_eq!(score.parts[0].staves[0].measures.len(), 4);
1539 }
1540
1541 #[test]
1542 fn new_score_measure_count() {
1543 let score = Score::new("Test", 120, 4, 4, 0, 8);
1544 assert_eq!(score.measure_count(), 8);
1545 }
1546
1547 #[test]
1548 fn note_beats_quarter() {
1549 let note = Note::new(Pitch::new(Step::C, 4), Duration::Quarter);
1550 assert!((note.beats() - 1.0).abs() < 1e-9);
1551 }
1552
1553 #[test]
1554 fn note_beats_dotted_quarter() {
1555 let mut note = Note::new(Pitch::new(Step::C, 4), Duration::Quarter);
1556 note.dot_count = 1;
1557 assert!((note.beats() - 1.5).abs() < 1e-9);
1558 }
1559
1560 #[test]
1561 fn grace_note_beats_zero() {
1562 let mut note = Note::new(Pitch::new(Step::C, 4), Duration::Eighth);
1563 note.is_grace = true;
1564 assert_eq!(note.beats(), 0.0);
1565 }
1566
1567 #[test]
1568 fn measure_empty_4_4_fills_four_beats() {
1569 let m = Measure::empty(4, 4);
1570 let total: f64 = m.voices[0].iter().map(|n| n.beats()).sum();
1571 assert!((total - 4.0).abs() < 1e-9);
1572 }
1573
1574 #[test]
1575 fn measure_empty_3_4_fills_three_beats() {
1576 let m = Measure::empty(3, 4);
1577 let total: f64 = m.voices[0].iter().map(|n| n.beats()).sum();
1578 assert!((total - 3.0).abs() < 1e-9);
1579 }
1580
1581 #[test]
1582 fn whole_filling_beats() {
1583 assert_eq!(Duration::whole_filling_beats(4.0), Duration::Whole);
1584 assert_eq!(Duration::whole_filling_beats(2.0), Duration::Half);
1585 assert_eq!(Duration::whole_filling_beats(1.0), Duration::Quarter);
1586 }
1587
1588 #[test]
1591 fn statistics_default_score_all_rests() {
1592 let score = Score::default();
1593 let s = score.statistics();
1594 assert_eq!(s.part_count, 1);
1595 assert_eq!(s.measure_count, 4);
1596 assert_eq!(s.note_count, 0);
1597 assert!(s.rest_count > 0);
1598 }
1599
1600 #[test]
1601 fn statistics_duration_estimate() {
1602 let score = Score::new("T", 120, 4, 4, 0, 1);
1604 let s = score.statistics();
1605 assert!((s.estimated_duration_secs - 2.0).abs() < 0.01);
1606 }
1607
1608 #[test]
1609 fn score_duration_secs_matches_statistics() {
1610 use super::score_duration_secs;
1611 let score = Score::new("T", 120, 4, 4, 0, 4);
1612 let secs = score_duration_secs(&score);
1613 assert!((secs - 8.0).abs() < 0.01, "expected ~8.0 s, got {secs}");
1615 }
1616
1617 #[test]
1618 fn score_duration_secs_zero_bpm_returns_zero() {
1619 use super::score_duration_secs;
1620 let mut score = Score::new("T", 120, 4, 4, 0, 1);
1621 score.settings.tempo_bpm = 0;
1622 assert_eq!(score_duration_secs(&score), 0.0);
1623 }
1624
1625 #[test]
1626 fn score_duration_secs_per_measure_tempo() {
1627 use super::score_duration_secs;
1628 let mut score = Score::new("T", 120, 4, 4, 0, 2);
1630 score.parts[0].staves[0].measures[1].tempo = Some(60);
1631 let secs = score_duration_secs(&score);
1632 assert!((secs - 6.0).abs() < 0.01, "expected ~6.0 s, got {secs}");
1633 }
1634
1635 #[test]
1638 fn extract_part_returns_single_part_score() {
1639 let mut score = Score::default();
1640 let mut p2 = Part::new("Violin", "Vln.");
1641 p2.staves.push(Staff::new(Clef::Treble));
1642 score.parts.push(p2);
1643 let ex = score.extract_part(0).unwrap();
1644 assert_eq!(ex.parts.len(), 1);
1645 assert_ne!(ex.id, score.id);
1646 assert_eq!(ex.metadata.title, score.metadata.title);
1647 }
1648
1649 #[test]
1650 fn extract_part_out_of_range_is_none() {
1651 let score = Score::default();
1652 assert!(score.extract_part(99).is_none());
1653 }
1654
1655 #[test]
1658 fn transpose_zero_is_clone() {
1659 let score = Score::new("T", 120, 4, 4, 0, 1);
1660 let t = transpose(&score, 0);
1661 assert_eq!(t.settings.key_signature.fifths, 0);
1662 }
1663
1664 #[test]
1665 fn transpose_c_major_up_2_to_d_major() {
1666 let score = Score::new("T", 120, 4, 4, 0, 1);
1667 assert_eq!(transpose(&score, 2).settings.key_signature.fifths, 2);
1668 }
1669
1670 #[test]
1671 fn transpose_d_major_up_5_to_g_major() {
1672 let score = Score::new("T", 120, 4, 4, 2, 1);
1673 assert_eq!(transpose(&score, 5).settings.key_signature.fifths, 1);
1674 }
1675
1676 #[test]
1677 fn transpose_c4_up_1_to_csharp4() {
1678 let p = transpose_pitch(&Pitch::new(Step::C, 4), 1);
1679 assert_eq!(p.to_midi(), 61);
1680 assert_eq!(p.step, Step::C);
1681 assert_eq!(p.alter, 1);
1682 }
1683
1684 #[test]
1685 fn transpose_c4_down_1_to_b3() {
1686 let p = transpose_pitch(&Pitch::new(Step::C, 4), -1);
1687 assert_eq!(p.to_midi(), 59);
1688 assert_eq!(p.step, Step::B);
1689 assert_eq!(p.alter, 0);
1690 }
1691
1692 #[test]
1693 fn transpose_up_octave_keeps_step() {
1694 let p = transpose_pitch(&Pitch::new(Step::A, 4), 12);
1695 assert_eq!(p.to_midi(), 81);
1696 assert_eq!(p.step, Step::A);
1697 assert_eq!(p.octave, 5);
1698 }
1699
1700 #[test]
1701 fn statistics_with_repeat_doubles_duration() {
1702 let mut score = Score::new("T", 120, 4, 4, 0, 2);
1704 score.parts[0].staves[0].measures[0].barline_left =
1705 crate::model::notation::Barline::RepeatStart;
1706 score.parts[0].staves[0].measures[1].barline_right =
1707 crate::model::notation::Barline::RepeatEnd;
1708 let s = score.statistics();
1709 assert!((s.estimated_duration_secs - 8.0).abs() < 0.01);
1711 }
1712
1713 #[test]
1714 fn transpose_octave_boundary_b4_to_c5() {
1715 let p = transpose_pitch(&Pitch::new(Step::B, 4), 1);
1717 assert_eq!(p.to_midi(), 72);
1718 assert_eq!(p.step, Step::C);
1719 assert_eq!(p.octave, 5);
1720 }
1721
1722 #[test]
1723 fn transpose_clamp_at_midi_127() {
1724 let p = transpose_pitch(&Pitch::new(Step::G, 9), 3);
1726 assert_eq!(p.to_midi(), 127);
1727 }
1728
1729 #[test]
1732 fn merge_combines_parts() {
1733 let mut a = Score::new("A", 120, 4, 4, 0, 2);
1734 let b = Score::new("B", 120, 4, 4, 0, 2);
1735 let mut p2 = Part::new("Violin", "Vln.");
1737 p2.staves.push(Staff::new(Clef::Treble));
1738 for i in 0..2usize {
1739 let mut m = Measure::empty(4, 4);
1740 m.number = i as u32 + 1;
1741 p2.staves[0].measures.push(m);
1742 }
1743 a.parts.push(p2);
1744 let merged = a.merge(&b);
1745 assert_eq!(merged.parts.len(), 3);
1747 }
1748
1749 #[test]
1750 fn merge_pads_shorter_score() {
1751 let a = Score::new("A", 120, 4, 4, 0, 4);
1752 let b = Score::new("B", 120, 4, 4, 0, 2);
1753 let merged = a.merge(&b);
1754 assert_eq!(merged.parts[0].staves[0].measures.len(), 4);
1756 assert_eq!(merged.parts[1].staves[0].measures.len(), 4);
1757 }
1758
1759 #[test]
1760 fn merge_uses_self_metadata() {
1761 let mut a = Score::new("Title A", 120, 4, 4, 0, 2);
1762 a.metadata.composer = "Composer A".to_string();
1763 let b = Score::new("Title B", 120, 4, 4, 0, 2);
1764 let merged = a.merge(&b);
1765 assert_eq!(merged.metadata.title, "Title A");
1766 assert_eq!(merged.metadata.composer, "Composer A");
1767 }
1768
1769 #[test]
1770 fn merge_new_id_differs_from_both() {
1771 let a = Score::new("A", 120, 4, 4, 0, 2);
1772 let b = Score::new("B", 120, 4, 4, 0, 2);
1773 let merged = a.merge(&b);
1774 assert_ne!(merged.id, a.id);
1775 assert_ne!(merged.id, b.id);
1776 }
1777
1778 #[test]
1781 fn staff_default_transpose_is_zero() {
1782 let s = Staff::new(Clef::Treble);
1783 assert_eq!(s.transpose_semitones, 0);
1784 }
1785
1786 #[test]
1789 fn score_default_has_schema_version_1() {
1790 let score = Score::default();
1791 assert_eq!(score.schema_version, 1);
1792 }
1793
1794 #[test]
1795 fn score_new_has_schema_version_1() {
1796 let score = Score::new("T", 120, 4, 4, 0, 4);
1797 assert_eq!(score.schema_version, 1);
1798 }
1799
1800 #[test]
1801 fn score_without_schema_version_deserializes_to_zero() {
1802 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":[]}"#;
1803 let score: Score = serde_json::from_str(json).unwrap();
1804 assert_eq!(score.schema_version, 0);
1805 }
1806
1807 #[test]
1810 fn score_template_solo_has_one_part_treble() {
1811 let score = Score::template(ScoreTemplate::Solo);
1812 assert_eq!(score.parts.len(), 1);
1813 assert_eq!(score.parts[0].staves.len(), 1);
1814 assert_eq!(score.parts[0].staves[0].clef, Clef::Treble);
1815 assert_eq!(score.parts[0].midi_program, 0);
1816 }
1817
1818 #[test]
1819 fn score_template_piano_has_two_staves() {
1820 let score = Score::template(ScoreTemplate::Piano);
1821 assert_eq!(score.parts.len(), 1);
1822 assert_eq!(score.parts[0].staves.len(), 2);
1823 assert_eq!(score.parts[0].staves[0].clef, Clef::Treble);
1824 assert_eq!(score.parts[0].staves[1].clef, Clef::Bass);
1825 }
1826
1827 #[test]
1828 fn score_template_string_quartet_has_four_parts() {
1829 let score = Score::template(ScoreTemplate::StringQuartet);
1830 assert_eq!(score.parts.len(), 4);
1831 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);
1834 assert_eq!(score.parts[3].midi_program, 42);
1835 }
1836
1837 #[test]
1838 fn score_template_string_orchestra_has_five_parts() {
1839 let score = Score::template(ScoreTemplate::StringOrchestra);
1840 assert_eq!(score.parts.len(), 5);
1841 assert_eq!(score.parts[4].midi_program, 43); }
1843
1844 #[test]
1845 fn score_template_brass_quintet_has_five_parts() {
1846 let score = Score::template(ScoreTemplate::BrassQuintet);
1847 assert_eq!(score.parts.len(), 5);
1848 assert_eq!(score.parts[2].midi_program, 60); }
1850
1851 #[test]
1852 fn score_template_default_measures_are_four() {
1853 let score = Score::template(ScoreTemplate::StringQuartet);
1854 for part in &score.parts {
1855 for staff in &part.staves {
1856 assert_eq!(staff.measures.len(), 4);
1857 }
1858 }
1859 }
1860
1861 #[test]
1864 fn measure_empty_has_no_breaks() {
1865 let m = Measure::empty(4, 4);
1866 assert!(!m.system_break);
1867 assert!(!m.page_break);
1868 }
1869
1870 #[test]
1871 fn system_break_survives_json_roundtrip() {
1872 let mut m = Measure::empty(4, 4);
1873 m.system_break = true;
1874 let json = serde_json::to_string(&m).unwrap();
1875 let m2: Measure = serde_json::from_str(&json).unwrap();
1876 assert!(m2.system_break);
1877 assert!(!m2.page_break);
1878 }
1879
1880 #[test]
1883 fn diff_identical_scores_is_empty() {
1884 let s = Score::new("T", 120, 4, 4, 0, 2);
1885 assert!(diff(&s, &s).is_empty());
1886 }
1887
1888 #[test]
1889 fn diff_detects_tempo_change() {
1890 let a = Score::new("T", 120, 4, 4, 0, 1);
1891 let mut b = a.clone();
1892 b.settings.tempo_bpm = 90;
1893 let changes = diff(&a, &b);
1894 assert_eq!(changes.len(), 1);
1895 assert!(matches!(
1896 changes[0],
1897 ScoreChange::TempoChanged { old: 120, new: 90 }
1898 ));
1899 }
1900
1901 #[test]
1902 fn diff_detects_title_change() {
1903 let a = Score::new("Old Title", 120, 4, 4, 0, 1);
1904 let mut b = a.clone();
1905 b.metadata.title = "New Title".to_string();
1906 let changes = diff(&a, &b);
1907 assert!(
1908 changes.iter().any(
1909 |c| matches!(c, ScoreChange::MetadataChanged { field, .. } if field == "title")
1910 )
1911 );
1912 }
1913
1914 #[test]
1915 fn diff_detects_note_modification() {
1916 let mut a = Score::new("T", 120, 4, 4, 0, 1);
1917 a.parts[0].staves[0].measures[0].voices[0] =
1918 vec![Note::new(Pitch::new(Step::C, 4), Duration::Quarter)];
1919 let mut b = a.clone();
1920 b.parts[0].staves[0].measures[0].voices[0][0] =
1921 Note::new(Pitch::new(Step::D, 4), Duration::Quarter);
1922 let changes = diff(&a, &b);
1923 assert!(
1924 changes
1925 .iter()
1926 .any(|c| matches!(c, ScoreChange::NoteModified { .. }))
1927 );
1928 }
1929
1930 #[test]
1931 fn diff_detects_part_added() {
1932 let a = Score::new("T", 120, 4, 4, 0, 1);
1933 let mut b = a.clone();
1934 let mut p = Part::new("Violin", "Vln.");
1935 p.staves.push(Staff::new(Clef::Treble));
1936 b.parts.push(p);
1937 let changes = diff(&a, &b);
1938 assert!(
1939 changes
1940 .iter()
1941 .any(|c| matches!(c, ScoreChange::PartAdded { part_index: 1 }))
1942 );
1943 }
1944
1945 #[test]
1946 fn diff_detects_measure_tempo_change() {
1947 let a = Score::new("T", 120, 4, 4, 0, 2);
1948 let mut b = a.clone();
1949 b.parts[0].staves[0].measures[1].tempo = Some(60);
1950 let changes = diff(&a, &b);
1951 assert!(changes.iter().any(|c| matches!(
1952 c,
1953 ScoreChange::MeasureTempoChanged {
1954 measure: 1,
1955 old: None,
1956 new: Some(60),
1957 ..
1958 }
1959 )));
1960 }
1961
1962 #[test]
1963 fn diff_detects_barline_change() {
1964 use crate::model::notation::Barline;
1965 let a = Score::new("T", 120, 4, 4, 0, 2);
1966 let mut b = a.clone();
1967 b.parts[0].staves[0].measures[0].barline_left = Barline::RepeatStart;
1968 let changes = diff(&a, &b);
1969 assert!(
1970 changes
1971 .iter()
1972 .any(|c| matches!(c, ScoreChange::BarlineChanged { measure: 0, .. }))
1973 );
1974 }
1975
1976 #[test]
1977 fn diff_detects_rehearsal_change() {
1978 let a = Score::new("T", 120, 4, 4, 0, 2);
1979 let mut b = a.clone();
1980 b.parts[0].staves[0].measures[0].rehearsal = Some("A".to_string());
1981 let changes = diff(&a, &b);
1982 assert!(
1983 changes
1984 .iter()
1985 .any(|c| matches!(c, ScoreChange::RehearsalMarkChanged { measure: 0, .. }))
1986 );
1987 }
1988
1989 #[test]
1990 fn diff_detects_volta_change() {
1991 use super::VoltaBracket;
1992 let a = Score::new("T", 120, 4, 4, 0, 2);
1993 let mut b = a.clone();
1994 b.parts[0].staves[0].measures[0].volta = Some(VoltaBracket {
1995 number: 1,
1996 kind: "begin_end".into(),
1997 });
1998 let changes = diff(&a, &b);
1999 assert!(
2000 changes
2001 .iter()
2002 .any(|c| matches!(c, ScoreChange::VoltaChanged { measure: 0, .. }))
2003 );
2004 }
2005
2006 #[test]
2007 fn diff_detects_key_signature_change() {
2008 let a = Score::new("T", 120, 4, 4, 0, 1);
2009 let mut b = a.clone();
2010 b.settings.key_signature.fifths = 2; let changes = diff(&a, &b);
2012 assert!(
2013 changes
2014 .iter()
2015 .any(|c| matches!(c, ScoreChange::KeySignatureChanged { .. }))
2016 );
2017 }
2018
2019 #[test]
2020 fn diff_same_key_signature_no_change() {
2021 let a = Score::new("T", 120, 4, 4, 2, 1);
2022 let changes = diff(&a, &a);
2023 assert!(changes.is_empty());
2024 }
2025
2026 #[test]
2027 fn score_duration_secs_region_partial() {
2028 use super::score_duration_secs_region;
2029 let score = Score::new("T", 120, 4, 4, 0, 4);
2031 let secs = score_duration_secs_region(&score, (1, 2));
2032 assert!((secs - 4.0).abs() < 0.01, "expected ~4.0 s, got {secs}");
2033 }
2034
2035 #[test]
2036 fn score_duration_secs_region_single_measure() {
2037 use super::score_duration_secs_region;
2038 let score = Score::new("T", 120, 4, 4, 0, 4);
2040 let secs = score_duration_secs_region(&score, (0, 0));
2041 assert!((secs - 2.0).abs() < 0.01, "expected ~2.0 s, got {secs}");
2042 }
2043
2044 #[test]
2047 fn measure_beats_remaining_empty_voice_returns_full() {
2048 use super::measure_beats_remaining;
2049 let mut score = Score::new("T", 120, 4, 4, 0, 1);
2050 score.parts[0].staves[0].measures[0].voices[0].clear();
2051 let rem = measure_beats_remaining(&score, 0, 0, 0, 0).unwrap();
2052 assert!(
2053 (rem - 4.0).abs() < 1e-9,
2054 "expected 4.0 remaining, got {rem}"
2055 );
2056 }
2057
2058 #[test]
2059 fn measure_beats_remaining_half_full_returns_half() {
2060 use super::measure_beats_remaining;
2061 use crate::model::pitch::Step;
2062 let mut score = Score::new("T", 120, 4, 4, 0, 1);
2063 score.parts[0].staves[0].measures[0].voices[0] = vec![
2064 Note::new(Pitch::new(Step::C, 4), Duration::Quarter),
2065 Note::new(Pitch::new(Step::D, 4), Duration::Quarter),
2066 ];
2067 let rem = measure_beats_remaining(&score, 0, 0, 0, 0).unwrap();
2068 assert!(
2069 (rem - 2.0).abs() < 1e-9,
2070 "expected 2.0 remaining, got {rem}"
2071 );
2072 }
2073
2074 #[test]
2075 fn measure_beats_remaining_full_voice_returns_zero() {
2076 use super::measure_beats_remaining;
2077 use crate::model::pitch::Step;
2078 let mut score = Score::new("T", 120, 4, 4, 0, 1);
2079 score.parts[0].staves[0].measures[0].voices[0] =
2080 vec![Note::new(Pitch::new(Step::C, 4), Duration::Whole)];
2081 let rem = measure_beats_remaining(&score, 0, 0, 0, 0).unwrap();
2082 assert!((rem).abs() < 1e-9, "expected 0.0 remaining, got {rem}");
2083 }
2084
2085 #[test]
2086 fn measure_beats_remaining_tuplet_accounting() {
2087 use super::measure_beats_remaining;
2088 use crate::model::notation::TupletInfo;
2089 use crate::model::pitch::Step;
2090 let mut score = Score::new("T", 120, 4, 4, 0, 1);
2092 let tuplet = TupletInfo {
2093 actual_notes: 3,
2094 normal_notes: 2,
2095 };
2096 let mk = |step| {
2097 let mut n = Note::new(Pitch::new(step, 4), Duration::Quarter);
2098 n.tuplet = Some(tuplet.clone());
2099 n
2100 };
2101 score.parts[0].staves[0].measures[0].voices[0] =
2102 vec![mk(Step::C), mk(Step::D), mk(Step::E)];
2103 let rem = measure_beats_remaining(&score, 0, 0, 0, 0).unwrap();
2104 assert!(
2105 (rem - 2.0).abs() < 1e-9,
2106 "expected 2.0 remaining (triplets used 2.0), got {rem}"
2107 );
2108 }
2109
2110 #[test]
2111 fn measure_beats_remaining_out_of_range_returns_err() {
2112 use super::measure_beats_remaining;
2113 let score = Score::new("T", 120, 4, 4, 0, 1);
2114 assert!(measure_beats_remaining(&score, 99, 0, 0, 0).is_err());
2115 assert!(measure_beats_remaining(&score, 0, 99, 0, 0).is_err());
2116 assert!(measure_beats_remaining(&score, 0, 0, 99, 0).is_err());
2117 assert!(measure_beats_remaining(&score, 0, 0, 0, 4).is_err());
2118 }
2119
2120 #[test]
2121 fn note_content_eq_ignores_id() {
2122 use crate::model::pitch::Step;
2123 let mut a = Note::new(Pitch::new(Step::C, 4), Duration::Quarter);
2124 let mut b = a.clone();
2125 b.id = "different-id".to_string();
2126 assert!(note_content_eq(&a, &b));
2127 b.pitches[0] = Pitch::new(Step::D, 4);
2129 assert!(!note_content_eq(&a, &b));
2130 let mut c = a.clone();
2132 a.stem_up = Some(true);
2133 c.stem_up = Some(false);
2134 assert!(!note_content_eq(&a, &c));
2135 }
2136
2137 #[test]
2138 fn suggested_stem_up_below_middle() {
2139 use crate::model::notation::Clef;
2140 let pitches = vec![Pitch::new(Step::C, 4)];
2142 assert!(suggested_stem_up(&pitches, &Clef::Treble));
2143 }
2144
2145 #[test]
2146 fn suggested_stem_up_above_middle() {
2147 use crate::model::notation::Clef;
2148 let pitches = vec![Pitch::new(Step::G, 5)];
2150 assert!(!suggested_stem_up(&pitches, &Clef::Treble));
2151 }
2152
2153 #[test]
2154 fn suggested_stem_up_at_middle_line() {
2155 use crate::model::notation::Clef;
2156 let pitches = vec![Pitch::new(Step::B, 4)];
2158 assert!(!suggested_stem_up(&pitches, &Clef::Treble));
2159 }
2160
2161 #[test]
2162 fn suggested_stem_up_chord() {
2163 use crate::model::notation::Clef;
2164 let pitches = vec![Pitch::new(Step::C, 4), Pitch::new(Step::G, 4)];
2166 assert!(suggested_stem_up(&pitches, &Clef::Treble));
2167 }
2168
2169 #[test]
2170 fn suggested_stem_up_bass_clef() {
2171 use crate::model::notation::Clef;
2172 let pitches = vec![Pitch::new(Step::D, 3)];
2174 assert!(!suggested_stem_up(&pitches, &Clef::Bass));
2175 let pitches2 = vec![Pitch::new(Step::C, 3)];
2177 assert!(suggested_stem_up(&pitches2, &Clef::Bass));
2178 }
2179
2180 #[test]
2181 fn suggested_stem_up_empty_pitches() {
2182 use crate::model::notation::Clef;
2183 assert!(suggested_stem_up(&[], &Clef::Treble));
2184 }
2185
2186 fn eighth(pitch: Pitch) -> Note {
2187 Note::new(pitch, Duration::Eighth)
2188 }
2189 fn quarter(pitch: Pitch) -> Note {
2190 Note::new(pitch, Duration::Quarter)
2191 }
2192 fn rest_eighth() -> Note {
2193 Note::rest(Duration::Eighth)
2194 }
2195
2196 #[test]
2197 fn compute_beams_4_4_four_eighths() {
2198 use crate::model::notation::{Clef, TimeSignature};
2199 let _ = Clef::Treble; let ts = TimeSignature {
2201 numerator: 4,
2202 denominator: 4,
2203 };
2204 let c4 = Pitch::new(Step::C, 4);
2205 let notes = vec![
2206 eighth(c4.clone()),
2207 eighth(c4.clone()),
2208 eighth(c4.clone()),
2209 eighth(c4.clone()),
2210 ];
2211 let beams = compute_beams(¬es, &ts);
2212 assert_eq!(beams[0], BeamState::Begin);
2214 assert_eq!(beams[1], BeamState::End);
2215 assert_eq!(beams[2], BeamState::Begin);
2216 assert_eq!(beams[3], BeamState::End);
2217 }
2218
2219 #[test]
2220 fn compute_beams_4_4_all_eighth_one_group() {
2221 use crate::model::notation::TimeSignature;
2222 let ts = TimeSignature {
2223 numerator: 4,
2224 denominator: 4,
2225 };
2226 let c4 = Pitch::new(Step::C, 4);
2227 let notes = vec![eighth(c4.clone()), eighth(c4.clone())];
2229 let beams = compute_beams(¬es, &ts);
2230 assert_eq!(beams[0], BeamState::Begin);
2231 assert_eq!(beams[1], BeamState::End);
2232 }
2233
2234 #[test]
2235 fn compute_beams_quarter_not_beamed() {
2236 use crate::model::notation::TimeSignature;
2237 let ts = TimeSignature {
2238 numerator: 4,
2239 denominator: 4,
2240 };
2241 let c4 = Pitch::new(Step::C, 4);
2242 let notes = vec![quarter(c4.clone()), quarter(c4.clone())];
2243 let beams = compute_beams(¬es, &ts);
2244 assert_eq!(beams[0], BeamState::None);
2245 assert_eq!(beams[1], BeamState::None);
2246 }
2247
2248 #[test]
2249 fn compute_beams_rest_breaks_beam() {
2250 use crate::model::notation::TimeSignature;
2251 let ts = TimeSignature {
2252 numerator: 4,
2253 denominator: 4,
2254 };
2255 let c4 = Pitch::new(Step::C, 4);
2256 let notes = vec![eighth(c4.clone()), rest_eighth(), eighth(c4.clone())];
2257 let beams = compute_beams(¬es, &ts);
2258 assert_eq!(beams[0], BeamState::None);
2260 assert_eq!(beams[1], BeamState::None);
2261 assert_eq!(beams[2], BeamState::None);
2262 }
2263
2264 #[test]
2265 fn compute_beams_6_8_compound() {
2266 use crate::model::notation::TimeSignature;
2267 let ts = TimeSignature {
2268 numerator: 6,
2269 denominator: 8,
2270 };
2271 let c4 = Pitch::new(Step::C, 4);
2272 let notes: Vec<Note> = (0..6).map(|_| eighth(c4.clone())).collect();
2274 let beams = compute_beams(¬es, &ts);
2275 assert_eq!(beams[0], BeamState::Begin);
2276 assert_eq!(beams[1], BeamState::Continue);
2277 assert_eq!(beams[2], BeamState::End);
2278 assert_eq!(beams[3], BeamState::Begin);
2279 assert_eq!(beams[4], BeamState::Continue);
2280 assert_eq!(beams[5], BeamState::End);
2281 }
2282
2283 #[test]
2284 fn compute_beams_single_eighth() {
2285 use crate::model::notation::TimeSignature;
2286 let ts = TimeSignature {
2287 numerator: 4,
2288 denominator: 4,
2289 };
2290 let c4 = Pitch::new(Step::C, 4);
2291 let notes = vec![eighth(c4.clone())];
2292 let beams = compute_beams(¬es, &ts);
2293 assert_eq!(beams[0], BeamState::None);
2294 }
2295}