1use super::{
2 duration::Duration,
3 notation::{
4 Articulation, Barline, BeamState, ChordSymbol, Clef, CrossStaff, Dynamic, GuitarTechnique,
5 HairpinKind, KeySignature, Lyric, NoteHead, OttavaKind, StyledText, TabPosition,
6 TablatureConfig, 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 assign_tablature_positions(score: &mut Score) -> usize {
389 const MAX_FRET: i16 = 24;
390 let mut assigned = 0;
391
392 for part in &mut score.parts {
393 for staff in &mut part.staves {
394 let Some(tab) = &staff.tablature else {
395 continue;
396 };
397 let tuning = tab.tuning_midi.clone();
398 let lines = tab.lines as usize;
399 let capo = i16::from(tab.capo);
400
401 for measure in &mut staff.measures {
402 for voice in &mut measure.voices {
403 for note in voice.iter_mut() {
404 if note.is_rest
405 || note.tab_position.is_some()
406 || !note.tab_positions.is_empty()
407 || note.pitches.is_empty()
408 {
409 continue;
410 }
411 let pitches: Vec<i16> = note.pitches.iter().map(Pitch::to_midi).collect();
412 let Some(positions) =
413 best_tablature_assignment(&pitches, &tuning, lines, capo, MAX_FRET)
414 else {
415 continue;
416 };
417
418 note.tab_position = positions.first().cloned();
419 note.tab_positions = positions;
420 note.string_number = note.tab_position.as_ref().map(|p| p.string);
421 assigned += 1;
422 }
423 }
424 }
425 }
426 }
427
428 assigned
429}
430
431pub fn optimize_tablature_positions(score: &mut Score) -> usize {
438 const MAX_FRET: i16 = 24;
439 let mut assigned = 0;
440
441 for part in &mut score.parts {
442 for staff in &mut part.staves {
443 let Some(tab) = &staff.tablature else {
444 continue;
445 };
446 let tuning = tab.tuning_midi.clone();
447 let lines = tab.lines as usize;
448 let capo = i16::from(tab.capo);
449 for voice in 0..4 {
450 let mut locations = Vec::new();
452 for (measure_index, measure) in staff.measures.iter().enumerate() {
453 for (note_index, note) in measure.voices[voice].iter().enumerate() {
454 if !note.is_rest && !note.pitches.is_empty() {
455 locations.push((measure_index, note_index, note.clone()));
456 }
457 }
458 }
459 let candidates: Vec<Vec<Vec<TabPosition>>> = locations
460 .iter()
461 .map(|(_, _, note)| {
462 if let Some(positions) = if !note.tab_positions.is_empty() {
463 Some(note.tab_positions.clone())
464 } else {
465 note.tab_position.clone().map(|position| vec![position])
466 } {
467 vec![positions]
468 } else {
469 tablature_assignments(
470 ¬e.pitches.iter().map(Pitch::to_midi).collect::<Vec<_>>(),
471 &tuning,
472 lines,
473 capo,
474 MAX_FRET,
475 )
476 }
477 })
478 .collect();
479 if candidates.iter().any(Vec::is_empty) {
480 continue;
481 }
482
483 let mut costs: Vec<Vec<(u32, Option<usize>)>> = candidates
484 .iter()
485 .map(|events| vec![(u32::MAX, None); events.len()])
486 .collect();
487 for (candidate_index, candidate) in candidates[0].iter().enumerate() {
488 costs[0][candidate_index] = (tablature_load(candidate), None);
489 }
490 for event_index in 1..candidates.len() {
491 for (candidate_index, candidate) in candidates[event_index].iter().enumerate() {
492 let load = tablature_load(candidate);
493 for (previous_index, previous) in
494 candidates[event_index - 1].iter().enumerate()
495 {
496 let previous_cost = costs[event_index - 1][previous_index].0;
497 let cost = previous_cost
498 .saturating_add(load)
499 .saturating_add(tablature_movement(previous, candidate));
500 if cost < costs[event_index][candidate_index].0 {
501 costs[event_index][candidate_index] = (cost, Some(previous_index));
502 }
503 }
504 }
505 }
506 let mut selected = vec![0; candidates.len()];
507 if let Some((last, _)) = costs.last().and_then(|row| {
508 row.iter()
509 .enumerate()
510 .min_by_key(|(index, (cost, _))| (*cost, *index))
511 }) {
512 selected[candidates.len() - 1] = last;
513 for event_index in (1..candidates.len()).rev() {
514 selected[event_index - 1] =
515 costs[event_index][selected[event_index]].1.unwrap_or(0);
516 }
517 }
518
519 for (((measure_index, note_index, original), event_candidates), selected_index) in
520 locations.into_iter().zip(candidates).zip(selected)
521 {
522 if original.tab_position.is_none() && original.tab_positions.is_empty() {
523 let note = &mut staff.measures[measure_index].voices[voice][note_index];
524 let positions = event_candidates[selected_index].clone();
525 note.tab_position = positions.first().cloned();
526 note.tab_positions = positions;
527 note.string_number = note.tab_position.as_ref().map(|p| p.string);
528 assigned += 1;
529 }
530 }
531 }
532 }
533 }
534
535 assigned
536}
537
538fn tablature_load(positions: &[TabPosition]) -> u32 {
539 let sum: u32 = positions
540 .iter()
541 .map(|position| u32::from(position.fret))
542 .sum();
543 let min = positions
544 .iter()
545 .map(|position| position.fret)
546 .min()
547 .unwrap_or(0);
548 let max = positions
549 .iter()
550 .map(|position| position.fret)
551 .max()
552 .unwrap_or(0);
553 let span = max - min;
554 let stretch_penalty = span.saturating_sub(4) as u32 * 12;
557 sum + u32::from(span) * 2 + stretch_penalty
558}
559
560fn tablature_movement(previous: &[TabPosition], current: &[TabPosition]) -> u32 {
561 previous
562 .iter()
563 .zip(current)
564 .map(|(a, b)| u32::from(a.fret.abs_diff(b.fret)) + u32::from(a.string.abs_diff(b.string)))
565 .sum()
566}
567
568fn tablature_assignments(
569 pitches: &[i16],
570 tuning: &[i16],
571 lines: usize,
572 capo: i16,
573 max_fret: i16,
574) -> Vec<Vec<TabPosition>> {
575 #[allow(clippy::too_many_arguments)]
576 fn visit(
577 pitches: &[i16],
578 tuning: &[i16],
579 lines: usize,
580 capo: i16,
581 max_fret: i16,
582 index: usize,
583 used: &mut [bool],
584 current: &mut Vec<TabPosition>,
585 output: &mut Vec<Vec<TabPosition>>,
586 ) {
587 if index == pitches.len() {
588 output.push(current.clone());
589 return;
590 }
591 for (string, open) in tuning.iter().enumerate().take(lines) {
592 if used[string] {
593 continue;
594 }
595 let fret = pitches[index] - *open - capo;
596 if !(0..=max_fret).contains(&fret) {
597 continue;
598 }
599 used[string] = true;
600 current.push(TabPosition {
601 string: (string + 1) as u8,
602 fret: fret as u8,
603 });
604 visit(
605 pitches,
606 tuning,
607 lines,
608 capo,
609 max_fret,
610 index + 1,
611 used,
612 current,
613 output,
614 );
615 current.pop();
616 used[string] = false;
617 }
618 }
619
620 if pitches.is_empty() || pitches.len() > lines {
621 return Vec::new();
622 }
623 let mut output = Vec::new();
624 visit(
625 pitches,
626 tuning,
627 lines,
628 capo,
629 max_fret,
630 0,
631 &mut vec![false; lines],
632 &mut Vec::new(),
633 &mut output,
634 );
635 output
636}
637
638fn best_tablature_assignment(
639 pitches: &[i16],
640 tuning: &[i16],
641 lines: usize,
642 capo: i16,
643 max_fret: i16,
644) -> Option<Vec<TabPosition>> {
645 type Assignment = (i16, i16, i16, Vec<u8>, Vec<TabPosition>);
646
647 #[allow(clippy::too_many_arguments)]
648 fn search(
649 pitches: &[i16],
650 tuning: &[i16],
651 lines: usize,
652 capo: i16,
653 max_fret: i16,
654 index: usize,
655 used: &mut [bool],
656 current: &mut Vec<TabPosition>,
657 best: &mut Option<Assignment>,
658 ) {
659 if index == pitches.len() {
660 let sum: i16 = current.iter().map(|p| i16::from(p.fret)).sum();
661 let min = current.iter().map(|p| p.fret).min().unwrap_or(0);
662 let max = current.iter().map(|p| p.fret).max().unwrap_or(0);
663 let strings: Vec<u8> = current.iter().map(|p| p.string).collect();
664 let candidate = (
665 sum,
666 i16::from(max) - i16::from(min),
667 i16::from(max),
668 strings,
669 current.clone(),
670 );
671 if best.as_ref().is_none_or(|existing| {
672 (candidate.0, candidate.1, candidate.2, &candidate.3)
673 < (existing.0, existing.1, existing.2, &existing.3)
674 }) {
675 *best = Some(candidate);
676 }
677 return;
678 }
679
680 for (string, open) in tuning.iter().enumerate().take(lines) {
681 if used[string] {
682 continue;
683 }
684 let fret = pitches[index] - *open - capo;
685 if !(0..=max_fret).contains(&fret) {
686 continue;
687 }
688 used[string] = true;
689 current.push(TabPosition {
690 string: (string + 1) as u8,
691 fret: fret as u8,
692 });
693 search(
694 pitches,
695 tuning,
696 lines,
697 capo,
698 max_fret,
699 index + 1,
700 used,
701 current,
702 best,
703 );
704 current.pop();
705 used[string] = false;
706 }
707 }
708
709 if pitches.len() > lines {
710 return None;
711 }
712 let mut used = vec![false; lines];
713 let mut current = Vec::with_capacity(pitches.len());
714 let mut best = None;
715 search(
716 pitches,
717 tuning,
718 lines,
719 capo,
720 max_fret,
721 0,
722 &mut used,
723 &mut current,
724 &mut best,
725 );
726 best.map(|(_, _, _, _, positions)| positions)
727}
728
729pub fn transpose(score: &Score, semitones: i8) -> Score {
733 if semitones == 0 {
734 return score.clone();
735 }
736 let mut out = score.clone();
737 out.settings.key_signature.fifths = transpose_fifths(
738 score.settings.key_signature.fifths,
739 &score.settings.key_signature.mode,
740 semitones,
741 );
742 for part in &mut out.parts {
743 for staff in &mut part.staves {
744 for measure in &mut staff.measures {
745 if let Some(ref mut ks) = measure.key_sig {
746 ks.fifths = transpose_fifths(ks.fifths, &ks.mode, semitones);
747 }
748 for voice in &mut measure.voices {
749 for note in voice.iter_mut() {
750 for pitch in note.pitches.iter_mut() {
751 *pitch = transpose_pitch(pitch, semitones);
752 }
753 }
754 }
755 }
756 }
757 }
758 out
759}
760
761fn transpose_pitch(pitch: &Pitch, semitones: i8) -> Pitch {
762 let new_midi = (pitch.to_midi() + semitones as i16).clamp(0, 127) as u8;
763 let pc = new_midi % 12;
764 let oct = (new_midi / 12) as i8 - 1;
765 let (step, alter): (Step, i8) = if semitones >= 0 {
766 match pc {
767 0 => (Step::C, 0),
768 1 => (Step::C, 1),
769 2 => (Step::D, 0),
770 3 => (Step::D, 1),
771 4 => (Step::E, 0),
772 5 => (Step::F, 0),
773 6 => (Step::F, 1),
774 7 => (Step::G, 0),
775 8 => (Step::G, 1),
776 9 => (Step::A, 0),
777 10 => (Step::A, 1),
778 11 => (Step::B, 0),
779 _ => (Step::C, 0),
780 }
781 } else {
782 match pc {
783 0 => (Step::C, 0),
784 1 => (Step::D, -1),
785 2 => (Step::D, 0),
786 3 => (Step::E, -1),
787 4 => (Step::E, 0),
788 5 => (Step::F, 0),
789 6 => (Step::G, -1),
790 7 => (Step::G, 0),
791 8 => (Step::A, -1),
792 9 => (Step::A, 0),
793 10 => (Step::B, -1),
794 11 => (Step::B, 0),
795 _ => (Step::C, 0),
796 }
797 };
798 Pitch::with_microtone(step, oct, alter, pitch.microtone_cents)
799}
800
801fn transpose_fifths(fifths: i8, mode: &str, semitones: i8) -> i8 {
807 let tonic_major_pc = ((fifths as i32 * 7).rem_euclid(12)) as u8;
808 let tonic_pc = if mode == "minor" {
809 ((tonic_major_pc as i32 + 9).rem_euclid(12)) as u8
810 } else {
811 tonic_major_pc
812 };
813 let new_tonic = ((tonic_pc as i32 + semitones as i32).rem_euclid(12)) as u8;
814 let major_tonic = if mode == "minor" {
815 ((new_tonic as i32 + 3).rem_euclid(12)) as u8
816 } else {
817 new_tonic
818 };
819 let raw = ((major_tonic as i32 * 7).rem_euclid(12)) as i8;
820 if raw > 6 { raw - 12 } else { raw }
821}
822
823#[derive(Debug, Clone, Serialize, Deserialize)]
824pub struct Part {
825 pub id: String,
826 pub name: String,
827 pub short_name: String,
828 pub staves: Vec<Staff>,
829 #[serde(default)]
831 pub midi_channel: u8,
832 #[serde(default)]
834 pub midi_program: u8,
835 #[serde(default)]
837 pub midi_pitch_bends: Vec<MidiPitchBend>,
838}
839
840#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
841pub struct MidiPitchBend {
842 pub tick: u64,
843 pub channel: u8,
844 pub value: i16,
846}
847
848impl Part {
849 pub fn new(name: &str, short_name: &str) -> Self {
850 Self {
851 id: Uuid::new_v4().to_string(),
852 name: name.to_string(),
853 short_name: short_name.to_string(),
854 staves: Vec::new(),
855 midi_channel: 0,
856 midi_program: 0,
857 midi_pitch_bends: Vec::new(),
858 }
859 }
860}
861
862#[derive(Debug, Clone, Serialize, Deserialize)]
863pub struct Staff {
864 pub clef: Clef,
865 pub measures: Vec<Measure>,
866 #[serde(default)]
869 pub transpose_semitones: i8,
870 #[serde(default)]
871 pub tablature: Option<TablatureConfig>,
872}
873
874impl Staff {
875 pub fn new(clef: Clef) -> Self {
876 Self {
877 clef,
878 measures: Vec::new(),
879 transpose_semitones: 0,
880 tablature: None,
881 }
882 }
883}
884
885#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
886pub struct VoltaBracket {
887 pub number: u8,
889 pub kind: String,
891}
892
893#[derive(Debug, Clone, Serialize, Deserialize)]
894pub struct Measure {
895 pub number: u32,
896 pub time_sig: Option<TimeSignature>,
897 pub key_sig: Option<KeySignature>,
898 pub clef: Option<Clef>,
899 pub tempo: Option<u16>,
900 pub barline_left: Barline,
901 pub barline_right: Barline,
902 #[serde(default)]
903 pub volta: Option<VoltaBracket>,
904 #[serde(default)]
905 pub tempo_text: Option<String>,
906 #[serde(default)]
907 pub rehearsal: Option<String>,
908 #[serde(default)]
911 pub navigation: Option<String>,
912 #[serde(default)]
914 pub expression_text: Option<String>,
915 #[serde(default)]
916 pub texts: Vec<StyledText>,
917 #[serde(default)]
919 pub multi_rest_count: Option<u8>,
920 #[serde(default)]
922 pub system_break: bool,
923 #[serde(default)]
925 pub page_break: bool,
926 pub voices: [Vec<Note>; 4],
928}
929
930impl Measure {
931 pub fn empty(numerator: u8, denominator: u8) -> Self {
932 let total_beats = TimeSignature {
933 numerator,
934 denominator,
935 }
936 .total_beats();
937 let mut voice0: Vec<Note> = Vec::new();
938 let mut remaining = total_beats;
939 while remaining > 1e-9 {
940 let dur = Duration::whole_filling_beats(remaining);
941 remaining -= dur.beats(0);
942 voice0.push(Note::rest(dur));
943 }
944 Self {
945 number: 0,
946 time_sig: None,
947 key_sig: None,
948 clef: None,
949 tempo: None,
950 barline_left: Barline::Normal,
951 barline_right: Barline::Normal,
952 volta: None,
953 tempo_text: None,
954 rehearsal: None,
955 navigation: None,
956 expression_text: None,
957 texts: Vec::new(),
958 multi_rest_count: None,
959 system_break: false,
960 page_break: false,
961 voices: [voice0, vec![], vec![], vec![]],
962 }
963 }
964
965 pub fn renumber(&mut self, n: u32) {
966 self.number = n;
967 }
968}
969
970#[derive(Debug, Clone, Serialize, Deserialize)]
971pub struct Note {
972 pub id: String,
973 pub is_rest: bool,
974 pub pitches: Vec<Pitch>,
976 #[serde(default)]
977 pub tab_position: Option<super::notation::TabPosition>,
978 #[serde(default)]
981 pub tab_positions: Vec<super::notation::TabPosition>,
982 pub duration: Duration,
983 pub dot_count: u8,
984 pub tie_start: bool,
985 pub tie_end: bool,
986 pub beam: BeamState,
987 pub articulations: Vec<Articulation>,
988 pub dynamic: Option<Dynamic>,
989 pub stem_up: Option<bool>,
990 #[serde(default)]
991 pub hairpin_start: Option<HairpinKind>,
992 #[serde(default)]
993 pub hairpin_end: bool,
994 #[serde(default)]
995 pub tuplet: Option<TupletInfo>,
996 #[serde(default)]
997 pub chord_symbol: Option<ChordSymbol>,
998 #[serde(default)]
999 pub is_grace: bool,
1000 #[serde(default)]
1002 pub grace_slash: bool,
1003 #[serde(default)]
1004 pub ottava_start: Option<OttavaKind>,
1005 #[serde(default)]
1006 pub ottava_end: bool,
1007 #[serde(default)]
1008 pub lyric: Option<Lyric>,
1009 #[serde(default)]
1010 pub pedal_start: bool,
1011 #[serde(default)]
1012 pub pedal_end: bool,
1013 #[serde(default)]
1014 pub slur_start: bool,
1015 #[serde(default)]
1016 pub slur_end: bool,
1017 #[serde(default)]
1019 pub arpeggiate: Option<bool>,
1020 #[serde(default)]
1022 pub technique_text: Option<String>,
1023 #[serde(default)]
1024 pub glissando_start: bool,
1025 #[serde(default)]
1026 pub glissando_end: bool,
1027 #[serde(default)]
1028 pub cross_staff: Option<CrossStaff>,
1029 #[serde(default)]
1031 pub fingering: Option<u8>,
1032 #[serde(default)]
1034 pub string_number: Option<u8>,
1035 #[serde(default)]
1036 pub note_head: NoteHead,
1037 #[serde(default)]
1039 pub is_cue: bool,
1040 #[serde(default)]
1042 pub trill_line_start: bool,
1043 #[serde(default)]
1045 pub trill_line_end: bool,
1046 #[serde(default)]
1048 pub guitar_technique: Option<GuitarTechnique>,
1049}
1050
1051impl Note {
1052 pub fn new(pitch: Pitch, duration: Duration) -> Self {
1053 Self {
1054 id: Uuid::new_v4().to_string(),
1055 is_rest: false,
1056 pitches: vec![pitch],
1057 tab_position: None,
1058 tab_positions: Vec::new(),
1059 duration,
1060 dot_count: 0,
1061 tie_start: false,
1062 tie_end: false,
1063 beam: BeamState::None,
1064 articulations: Vec::new(),
1065 dynamic: None,
1066 stem_up: None,
1067 hairpin_start: None,
1068 hairpin_end: false,
1069 tuplet: None,
1070 chord_symbol: None,
1071 is_grace: false,
1072 grace_slash: false,
1073 ottava_start: None,
1074 ottava_end: false,
1075 lyric: None,
1076 pedal_start: false,
1077 pedal_end: false,
1078 slur_start: false,
1079 slur_end: false,
1080 arpeggiate: None,
1081 technique_text: None,
1082 glissando_start: false,
1083 glissando_end: false,
1084 cross_staff: None,
1085 fingering: None,
1086 string_number: None,
1087 note_head: NoteHead::Normal,
1088 is_cue: false,
1089 trill_line_start: false,
1090 trill_line_end: false,
1091 guitar_technique: None,
1092 }
1093 }
1094
1095 pub fn rest(duration: Duration) -> Self {
1096 Self {
1097 id: Uuid::new_v4().to_string(),
1098 is_rest: true,
1099 pitches: Vec::new(),
1100 tab_position: None,
1101 tab_positions: Vec::new(),
1102 duration,
1103 dot_count: 0,
1104 tie_start: false,
1105 tie_end: false,
1106 beam: BeamState::None,
1107 articulations: Vec::new(),
1108 dynamic: None,
1109 stem_up: None,
1110 hairpin_start: None,
1111 hairpin_end: false,
1112 tuplet: None,
1113 chord_symbol: None,
1114 is_grace: false,
1115 grace_slash: false,
1116 ottava_start: None,
1117 ottava_end: false,
1118 lyric: None,
1119 pedal_start: false,
1120 pedal_end: false,
1121 slur_start: false,
1122 slur_end: false,
1123 arpeggiate: None,
1124 technique_text: None,
1125 glissando_start: false,
1126 glissando_end: false,
1127 cross_staff: None,
1128 fingering: None,
1129 string_number: None,
1130 note_head: NoteHead::Normal,
1131 is_cue: false,
1132 trill_line_start: false,
1133 trill_line_end: false,
1134 guitar_technique: None,
1135 }
1136 }
1137
1138 pub fn beats(&self) -> f64 {
1139 if self.is_grace || self.is_cue {
1140 return 0.0;
1141 }
1142 let base = self.duration.beats(self.dot_count);
1143 if let Some(ref t) = self.tuplet {
1144 base * (t.normal_notes as f64) / (t.actual_notes as f64)
1145 } else {
1146 base
1147 }
1148 }
1149}
1150
1151impl Duration {
1152 pub fn whole_filling_beats(beats: f64) -> Duration {
1154 if beats >= 4.0 {
1155 Duration::Whole
1156 } else if beats >= 2.0 {
1157 Duration::Half
1158 } else if beats >= 1.0 {
1159 Duration::Quarter
1160 } else if beats >= 0.5 {
1161 Duration::Eighth
1162 } else if beats >= 0.25 {
1163 Duration::Sixteenth
1164 } else if beats >= 0.125 {
1165 Duration::ThirtySecond
1166 } else {
1167 Duration::SixtyFourth
1168 }
1169 }
1170}
1171
1172#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1176pub struct NoteAddr {
1177 pub part: usize,
1178 pub staff: usize,
1179 pub measure: usize,
1180 pub voice: usize,
1181 pub note: usize,
1182}
1183
1184#[derive(Debug, Clone, Serialize, Deserialize)]
1188pub enum ScoreChange {
1189 MetadataChanged {
1190 field: String,
1191 old: String,
1192 new: String,
1193 },
1194 TempoChanged {
1195 old: u16,
1196 new: u16,
1197 },
1198 KeySignatureChanged {
1199 old: KeySignature,
1200 new: KeySignature,
1201 },
1202 PartAdded {
1203 part_index: usize,
1204 },
1205 PartRemoved {
1206 part_index: usize,
1207 name: String,
1208 },
1209 NoteAdded {
1210 part: usize,
1211 staff: usize,
1212 measure: usize,
1213 voice: usize,
1214 note_index: usize,
1215 },
1216 NoteRemoved {
1217 part: usize,
1218 staff: usize,
1219 measure: usize,
1220 voice: usize,
1221 note: Box<Note>,
1222 },
1223 NoteModified {
1224 part: usize,
1225 staff: usize,
1226 measure: usize,
1227 voice: usize,
1228 note_index: usize,
1229 old: Box<Note>,
1230 new: Box<Note>,
1231 },
1232 TimeSigChanged {
1233 part: usize,
1234 staff: usize,
1235 measure: usize,
1236 old: Option<TimeSignature>,
1237 new: Option<TimeSignature>,
1238 },
1239 MeasureTempoChanged {
1240 part: usize,
1241 staff: usize,
1242 measure: usize,
1243 old: Option<u16>,
1244 new: Option<u16>,
1245 },
1246 BarlineChanged {
1247 part: usize,
1248 staff: usize,
1249 measure: usize,
1250 },
1251 RehearsalMarkChanged {
1252 part: usize,
1253 staff: usize,
1254 measure: usize,
1255 old: Option<String>,
1256 new: Option<String>,
1257 },
1258 VoltaChanged {
1259 part: usize,
1260 staff: usize,
1261 measure: usize,
1262 },
1263}
1264
1265pub fn diff(a: &Score, b: &Score) -> Vec<ScoreChange> {
1271 let mut changes: Vec<ScoreChange> = Vec::new();
1272
1273 macro_rules! meta {
1274 ($field:ident, $name:literal) => {
1275 if a.metadata.$field != b.metadata.$field {
1276 changes.push(ScoreChange::MetadataChanged {
1277 field: $name.to_string(),
1278 old: a.metadata.$field.clone(),
1279 new: b.metadata.$field.clone(),
1280 });
1281 }
1282 };
1283 }
1284 meta!(title, "title");
1285 meta!(composer, "composer");
1286 meta!(lyricist, "lyricist");
1287 meta!(copyright, "copyright");
1288 meta!(work_number, "work_number");
1289 meta!(movement_title, "movement_title");
1290
1291 if a.settings.tempo_bpm != b.settings.tempo_bpm {
1292 changes.push(ScoreChange::TempoChanged {
1293 old: a.settings.tempo_bpm,
1294 new: b.settings.tempo_bpm,
1295 });
1296 }
1297 if a.settings.key_signature != b.settings.key_signature {
1298 changes.push(ScoreChange::KeySignatureChanged {
1299 old: a.settings.key_signature.clone(),
1300 new: b.settings.key_signature.clone(),
1301 });
1302 }
1303
1304 let a_len = a.parts.len();
1305 let b_len = b.parts.len();
1306 for i in b_len..a_len {
1307 changes.push(ScoreChange::PartRemoved {
1308 part_index: i,
1309 name: a.parts[i].name.clone(),
1310 });
1311 }
1312 for i in a_len..b_len {
1313 changes.push(ScoreChange::PartAdded { part_index: i });
1314 }
1315
1316 for pi in 0..a_len.min(b_len) {
1317 let ap = &a.parts[pi];
1318 let bp = &b.parts[pi];
1319 for si in 0..ap.staves.len().min(bp.staves.len()) {
1320 let a_staff = &ap.staves[si];
1321 let b_staff = &bp.staves[si];
1322 for mi in 0..a_staff.measures.len().min(b_staff.measures.len()) {
1323 let am = &a_staff.measures[mi];
1324 let bm = &b_staff.measures[mi];
1325 for vi in 0..4usize {
1326 let av = &am.voices[vi];
1327 let bv = &bm.voices[vi];
1328 for (ni, (a_note, b_note)) in av.iter().zip(bv.iter()).enumerate() {
1329 if !note_content_eq(a_note, b_note) {
1330 changes.push(ScoreChange::NoteModified {
1331 part: pi,
1332 staff: si,
1333 measure: mi,
1334 voice: vi,
1335 note_index: ni,
1336 old: Box::new(a_note.clone()),
1337 new: Box::new(b_note.clone()),
1338 });
1339 }
1340 }
1341 for note in av.iter().skip(bv.len()) {
1342 changes.push(ScoreChange::NoteRemoved {
1343 part: pi,
1344 staff: si,
1345 measure: mi,
1346 voice: vi,
1347 note: Box::new(note.clone()),
1348 });
1349 }
1350 for ni in av.len()..bv.len() {
1351 changes.push(ScoreChange::NoteAdded {
1352 part: pi,
1353 staff: si,
1354 measure: mi,
1355 voice: vi,
1356 note_index: ni,
1357 });
1358 }
1359 }
1360 if am.time_sig != bm.time_sig {
1361 changes.push(ScoreChange::TimeSigChanged {
1362 part: pi,
1363 staff: si,
1364 measure: mi,
1365 old: am.time_sig.clone(),
1366 new: bm.time_sig.clone(),
1367 });
1368 }
1369 if am.tempo != bm.tempo {
1370 changes.push(ScoreChange::MeasureTempoChanged {
1371 part: pi,
1372 staff: si,
1373 measure: mi,
1374 old: am.tempo,
1375 new: bm.tempo,
1376 });
1377 }
1378 if am.barline_left != bm.barline_left || am.barline_right != bm.barline_right {
1379 changes.push(ScoreChange::BarlineChanged {
1380 part: pi,
1381 staff: si,
1382 measure: mi,
1383 });
1384 }
1385 if am.rehearsal != bm.rehearsal {
1386 changes.push(ScoreChange::RehearsalMarkChanged {
1387 part: pi,
1388 staff: si,
1389 measure: mi,
1390 old: am.rehearsal.clone(),
1391 new: bm.rehearsal.clone(),
1392 });
1393 }
1394 if am.volta != bm.volta {
1395 changes.push(ScoreChange::VoltaChanged {
1396 part: pi,
1397 staff: si,
1398 measure: mi,
1399 });
1400 }
1401 }
1402 }
1403 }
1404
1405 changes
1406}
1407
1408#[derive(Debug, Clone, Serialize, Deserialize)]
1415pub enum ScorePatch {
1416 SetMetadata {
1417 field: String,
1418 value: String,
1419 },
1420 SetTempo {
1421 value: u16,
1422 },
1423 SetKeySignature {
1424 part: usize,
1425 staff: usize,
1426 measure: usize,
1427 value: Option<KeySignature>,
1428 },
1429 SetTimeSignature {
1430 part: usize,
1431 staff: usize,
1432 measure: usize,
1433 value: Option<TimeSignature>,
1434 },
1435 SetBarlines {
1436 part: usize,
1437 staff: usize,
1438 measure: usize,
1439 left: Barline,
1440 right: Barline,
1441 },
1442 SetRehearsal {
1443 part: usize,
1444 staff: usize,
1445 measure: usize,
1446 value: Option<String>,
1447 },
1448 SetVolta {
1449 part: usize,
1450 staff: usize,
1451 measure: usize,
1452 value: Option<VoltaBracket>,
1453 },
1454 AddNote {
1456 part: usize,
1457 staff: usize,
1458 measure: usize,
1459 voice: usize,
1460 #[serde(default = "legacy_append_index")]
1462 note_index: usize,
1463 note: Box<Note>,
1464 },
1465 RemoveNote {
1466 part: usize,
1467 staff: usize,
1468 measure: usize,
1469 voice: usize,
1470 note_index: usize,
1471 },
1472 ReplaceNote {
1474 part: usize,
1475 staff: usize,
1476 measure: usize,
1477 voice: usize,
1478 note_index: usize,
1479 note: Box<Note>,
1480 },
1481 SetMeasureTempo {
1482 part: usize,
1483 staff: usize,
1484 measure: usize,
1485 value: Option<u16>,
1486 },
1487 ReplaceScore {
1490 score: Box<Score>,
1491 },
1492}
1493
1494fn legacy_append_index() -> usize {
1495 usize::MAX
1496}
1497
1498fn patch_requires_replace(a: &Score, b: &Score) -> bool {
1502 if a.settings.time_signature != b.settings.time_signature
1503 || a.settings.key_signature != b.settings.key_signature
1504 || a.parts.len() != b.parts.len()
1505 || a.part_groups.len() != b.part_groups.len()
1506 {
1507 return true;
1508 }
1509 if a.part_groups.iter().zip(&b.part_groups).any(|(x, y)| {
1510 x.first_part != y.first_part
1511 || x.last_part != y.last_part
1512 || x.symbol != y.symbol
1513 || x.barlines_connect != y.barlines_connect
1514 }) {
1515 return true;
1516 }
1517 for (ap, bp) in a.parts.iter().zip(&b.parts) {
1518 if ap.name != bp.name
1519 || ap.short_name != bp.short_name
1520 || ap.midi_channel != bp.midi_channel
1521 || ap.midi_program != bp.midi_program
1522 || ap.midi_pitch_bends != bp.midi_pitch_bends
1523 || ap.staves.len() != bp.staves.len()
1524 {
1525 return true;
1526 }
1527 for (as_, bs) in ap.staves.iter().zip(&bp.staves) {
1528 if as_.clef != bs.clef
1529 || as_.transpose_semitones != bs.transpose_semitones
1530 || as_.measures.len() != bs.measures.len()
1531 {
1532 return true;
1533 }
1534 for (am, bm) in as_.measures.iter().zip(&bs.measures) {
1535 if am.number != bm.number
1536 || am.clef != bm.clef
1537 || am.tempo_text != bm.tempo_text
1538 || am.navigation != bm.navigation
1539 || am.expression_text != bm.expression_text
1540 || am.multi_rest_count != bm.multi_rest_count
1541 || am.system_break != bm.system_break
1542 || am.page_break != bm.page_break
1543 {
1544 return true;
1545 }
1546 }
1547 }
1548 }
1549 false
1550}
1551
1552pub fn score_patch(a: &Score, b: &Score) -> Vec<ScorePatch> {
1557 let mut patches: Vec<ScorePatch> = Vec::new();
1558
1559 if patch_requires_replace(a, b) {
1560 return vec![ScorePatch::ReplaceScore {
1561 score: Box::new(b.clone()),
1562 }];
1563 }
1564
1565 macro_rules! meta {
1566 ($field:ident, $name:literal) => {
1567 if a.metadata.$field != b.metadata.$field {
1568 patches.push(ScorePatch::SetMetadata {
1569 field: $name.to_string(),
1570 value: b.metadata.$field.clone(),
1571 });
1572 }
1573 };
1574 }
1575 meta!(title, "title");
1576 meta!(composer, "composer");
1577 meta!(lyricist, "lyricist");
1578 meta!(copyright, "copyright");
1579 meta!(work_number, "work_number");
1580 meta!(movement_title, "movement_title");
1581
1582 if a.settings.tempo_bpm != b.settings.tempo_bpm {
1583 patches.push(ScorePatch::SetTempo {
1584 value: b.settings.tempo_bpm,
1585 });
1586 }
1587
1588 for pi in 0..a.parts.len().min(b.parts.len()) {
1589 let ap = &a.parts[pi];
1590 let bp = &b.parts[pi];
1591 for si in 0..ap.staves.len().min(bp.staves.len()) {
1592 let a_staff = &ap.staves[si];
1593 let b_staff = &bp.staves[si];
1594 for mi in 0..a_staff.measures.len().min(b_staff.measures.len()) {
1595 let am = &a_staff.measures[mi];
1596 let bm = &b_staff.measures[mi];
1597
1598 if am.key_sig != bm.key_sig {
1599 patches.push(ScorePatch::SetKeySignature {
1600 part: pi,
1601 staff: si,
1602 measure: mi,
1603 value: bm.key_sig.clone(),
1604 });
1605 }
1606 if am.time_sig != bm.time_sig {
1607 patches.push(ScorePatch::SetTimeSignature {
1608 part: pi,
1609 staff: si,
1610 measure: mi,
1611 value: bm.time_sig.clone(),
1612 });
1613 }
1614 if am.barline_left != bm.barline_left || am.barline_right != bm.barline_right {
1615 patches.push(ScorePatch::SetBarlines {
1616 part: pi,
1617 staff: si,
1618 measure: mi,
1619 left: bm.barline_left.clone(),
1620 right: bm.barline_right.clone(),
1621 });
1622 }
1623 if am.rehearsal != bm.rehearsal {
1624 patches.push(ScorePatch::SetRehearsal {
1625 part: pi,
1626 staff: si,
1627 measure: mi,
1628 value: bm.rehearsal.clone(),
1629 });
1630 }
1631 if am.volta != bm.volta {
1632 patches.push(ScorePatch::SetVolta {
1633 part: pi,
1634 staff: si,
1635 measure: mi,
1636 value: bm.volta.clone(),
1637 });
1638 }
1639 if am.tempo != bm.tempo {
1640 patches.push(ScorePatch::SetMeasureTempo {
1641 part: pi,
1642 staff: si,
1643 measure: mi,
1644 value: bm.tempo,
1645 });
1646 }
1647
1648 for vi in 0..4usize {
1649 let av = &am.voices[vi];
1650 let bv = &bm.voices[vi];
1651 for (ni, (a_note, b_note)) in av.iter().zip(bv.iter()).enumerate() {
1652 if !note_content_eq(a_note, b_note) {
1653 patches.push(ScorePatch::ReplaceNote {
1654 part: pi,
1655 staff: si,
1656 measure: mi,
1657 voice: vi,
1658 note_index: ni,
1659 note: Box::new(b_note.clone()),
1660 });
1661 }
1662 }
1663 for ni in (bv.len()..av.len()).rev() {
1665 patches.push(ScorePatch::RemoveNote {
1666 part: pi,
1667 staff: si,
1668 measure: mi,
1669 voice: vi,
1670 note_index: ni,
1671 });
1672 }
1673 for (offset, note) in bv.iter().skip(av.len()).enumerate() {
1675 patches.push(ScorePatch::AddNote {
1676 part: pi,
1677 staff: si,
1678 measure: mi,
1679 voice: vi,
1680 note_index: av.len() + offset,
1681 note: Box::new(note.clone()),
1682 });
1683 }
1684 }
1685 }
1686 }
1687 }
1688
1689 patches
1690}
1691
1692pub fn apply_patch(score: &Score, patches: &[ScorePatch]) -> Result<Score, Error> {
1697 let mut s = score.clone();
1698 for patch in patches {
1699 match patch {
1700 ScorePatch::ReplaceScore { score } => {
1701 s = (**score).clone();
1702 }
1703 ScorePatch::SetMetadata { field, value } => match field.as_str() {
1704 "title" => s.metadata.title = value.clone(),
1705 "composer" => s.metadata.composer = value.clone(),
1706 "lyricist" => s.metadata.lyricist = value.clone(),
1707 "copyright" => s.metadata.copyright = value.clone(),
1708 "work_number" => s.metadata.work_number = value.clone(),
1709 "movement_title" => s.metadata.movement_title = value.clone(),
1710 other => {
1711 return Err(Error::InvalidPatch(format!(
1712 "unknown metadata field: {other}"
1713 )));
1714 }
1715 },
1716 ScorePatch::SetTempo { value } => {
1717 s.settings.tempo_bpm = *value;
1718 }
1719 ScorePatch::SetKeySignature {
1720 part,
1721 staff,
1722 measure,
1723 value,
1724 } => {
1725 s.parts
1726 .get_mut(*part)
1727 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1728 .staves
1729 .get_mut(*staff)
1730 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1731 .measures
1732 .get_mut(*measure)
1733 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1734 .key_sig = value.clone();
1735 }
1736 ScorePatch::SetTimeSignature {
1737 part,
1738 staff,
1739 measure,
1740 value,
1741 } => {
1742 s.parts
1743 .get_mut(*part)
1744 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1745 .staves
1746 .get_mut(*staff)
1747 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1748 .measures
1749 .get_mut(*measure)
1750 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1751 .time_sig = value.clone();
1752 }
1753 ScorePatch::SetBarlines {
1754 part,
1755 staff,
1756 measure,
1757 left,
1758 right,
1759 } => {
1760 let m = s
1761 .parts
1762 .get_mut(*part)
1763 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1764 .staves
1765 .get_mut(*staff)
1766 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1767 .measures
1768 .get_mut(*measure)
1769 .ok_or_else(|| {
1770 Error::InvalidPatch(format!("measure {measure} out of range"))
1771 })?;
1772 m.barline_left = left.clone();
1773 m.barline_right = right.clone();
1774 }
1775 ScorePatch::SetRehearsal {
1776 part,
1777 staff,
1778 measure,
1779 value,
1780 } => {
1781 s.parts
1782 .get_mut(*part)
1783 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1784 .staves
1785 .get_mut(*staff)
1786 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1787 .measures
1788 .get_mut(*measure)
1789 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1790 .rehearsal = value.clone();
1791 }
1792 ScorePatch::SetVolta {
1793 part,
1794 staff,
1795 measure,
1796 value,
1797 } => {
1798 s.parts
1799 .get_mut(*part)
1800 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1801 .staves
1802 .get_mut(*staff)
1803 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1804 .measures
1805 .get_mut(*measure)
1806 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1807 .volta = value.clone();
1808 }
1809 ScorePatch::AddNote {
1810 part,
1811 staff,
1812 measure,
1813 voice,
1814 note_index,
1815 note,
1816 } => {
1817 let v = s
1818 .parts
1819 .get_mut(*part)
1820 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1821 .staves
1822 .get_mut(*staff)
1823 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1824 .measures
1825 .get_mut(*measure)
1826 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1827 .voices
1828 .get_mut(*voice)
1829 .ok_or_else(|| Error::InvalidPatch(format!("voice {voice} out of range")))?;
1830 let insert_at = if *note_index == usize::MAX {
1831 v.len()
1832 } else {
1833 *note_index
1834 };
1835 if insert_at > v.len() {
1836 return Err(Error::InvalidPatch(format!(
1837 "note_index {note_index} out of range"
1838 )));
1839 }
1840 v.insert(insert_at, *note.clone());
1841 }
1842 ScorePatch::RemoveNote {
1843 part,
1844 staff,
1845 measure,
1846 voice,
1847 note_index,
1848 } => {
1849 let v = s
1850 .parts
1851 .get_mut(*part)
1852 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1853 .staves
1854 .get_mut(*staff)
1855 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1856 .measures
1857 .get_mut(*measure)
1858 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1859 .voices
1860 .get_mut(*voice)
1861 .ok_or_else(|| Error::InvalidPatch(format!("voice {voice} out of range")))?;
1862 if *note_index >= v.len() {
1863 return Err(Error::InvalidPatch(format!(
1864 "note_index {note_index} out of range"
1865 )));
1866 }
1867 v.remove(*note_index);
1868 }
1869 ScorePatch::ReplaceNote {
1870 part,
1871 staff,
1872 measure,
1873 voice,
1874 note_index,
1875 note,
1876 } => {
1877 let v = s
1878 .parts
1879 .get_mut(*part)
1880 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1881 .staves
1882 .get_mut(*staff)
1883 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1884 .measures
1885 .get_mut(*measure)
1886 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1887 .voices
1888 .get_mut(*voice)
1889 .ok_or_else(|| Error::InvalidPatch(format!("voice {voice} out of range")))?;
1890 if *note_index >= v.len() {
1891 return Err(Error::InvalidPatch(format!(
1892 "note_index {note_index} out of range"
1893 )));
1894 }
1895 v[*note_index] = *note.clone();
1896 }
1897 ScorePatch::SetMeasureTempo {
1898 part,
1899 staff,
1900 measure,
1901 value,
1902 } => {
1903 s.parts
1904 .get_mut(*part)
1905 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1906 .staves
1907 .get_mut(*staff)
1908 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1909 .measures
1910 .get_mut(*measure)
1911 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1912 .tempo = *value;
1913 }
1914 }
1915 }
1916 Ok(s)
1917}
1918
1919pub fn respell_score(score: &mut Score, prefer_flat: bool) {
1923 for part in &mut score.parts {
1924 for staff in &mut part.staves {
1925 for measure in &mut staff.measures {
1926 for voice in &mut measure.voices {
1927 for note in voice.iter_mut() {
1928 for pitch in &mut note.pitches {
1929 *pitch = pitch.respell(prefer_flat);
1930 }
1931 }
1932 }
1933 }
1934 }
1935 }
1936}
1937
1938pub fn respell_score_to_key(score: &mut Score) {
1942 let prefer_flat = score.settings.key_signature.fifths < 0;
1943 respell_score(score, prefer_flat);
1944}
1945
1946pub fn score_duration_secs(score: &Score) -> f64 {
1951 if score.settings.tempo_bpm == 0 {
1952 return 0.0;
1953 }
1954 let seq = measure_sequence(score);
1955 let mut total_secs = 0.0f64;
1956 let mut current_bpm = score.settings.tempo_bpm as f64;
1957 if let Some(staff) = score.parts.first().and_then(|p| p.staves.first()) {
1958 for &idx in &seq {
1959 if let Some(m) = staff.measures.get(idx) {
1960 if let Some(b) = m.tempo {
1961 current_bpm = b as f64;
1962 }
1963 if current_bpm == 0.0 {
1964 continue;
1965 }
1966 let beats: f64 = m.voices[0].iter().map(|n| n.beats()).sum();
1967 total_secs += beats / current_bpm * 60.0;
1968 }
1969 }
1970 }
1971 total_secs
1972}
1973
1974pub fn score_duration_secs_region(score: &Score, region: (usize, usize)) -> f64 {
1979 if score.settings.tempo_bpm == 0 {
1980 return 0.0;
1981 }
1982 let seq: Vec<usize> = measure_sequence(score)
1983 .into_iter()
1984 .filter(|&idx| idx >= region.0 && idx <= region.1)
1985 .collect();
1986 let mut total_secs = 0.0f64;
1987 let mut current_bpm = score.settings.tempo_bpm as f64;
1988 if let Some(staff) = score.parts.first().and_then(|p| p.staves.first()) {
1989 for &idx in &seq {
1990 if let Some(m) = staff.measures.get(idx) {
1991 if let Some(b) = m.tempo {
1992 current_bpm = b as f64;
1993 }
1994 if current_bpm == 0.0 {
1995 continue;
1996 }
1997 let beats: f64 = m.voices[0].iter().map(|n| n.beats()).sum();
1998 total_secs += beats / current_bpm * 60.0;
1999 }
2000 }
2001 }
2002 total_secs
2003}
2004
2005pub fn measure_beats_remaining(
2010 score: &Score,
2011 part_index: usize,
2012 staff_index: usize,
2013 measure_index: usize,
2014 voice_index: usize,
2015) -> Result<f64, Error> {
2016 let part = score
2017 .parts
2018 .get(part_index)
2019 .ok_or(Error::PartNotFound(part_index))?;
2020 let staff = part
2021 .staves
2022 .get(staff_index)
2023 .ok_or(Error::StaffNotFound(staff_index))?;
2024 let measure = staff
2025 .measures
2026 .get(measure_index)
2027 .ok_or(Error::MeasureNotFound(measure_index))?;
2028 let voice = measure
2029 .voices
2030 .get(voice_index)
2031 .ok_or(Error::VoiceOutOfRange(voice_index))?;
2032 let ts = measure
2033 .time_sig
2034 .as_ref()
2035 .unwrap_or(&score.settings.time_signature);
2036 let used: f64 = voice.iter().map(|n| n.beats()).sum();
2037 Ok((ts.total_beats() - used).max(0.0))
2038}
2039
2040pub fn suggested_stem_up(pitches: &[Pitch], clef: &Clef) -> bool {
2046 if pitches.is_empty() {
2047 return true;
2048 }
2049 let avg = pitches.iter().map(|p| p.to_midi() as f64).sum::<f64>() / pitches.len() as f64;
2050 avg < clef.middle_line_midi() as f64
2051}
2052
2053fn beam_beat_size(ts: &TimeSignature) -> f64 {
2054 if ts.numerator.is_multiple_of(3) && ts.numerator >= 6 && ts.denominator >= 8 {
2055 3.0 * 4.0 / ts.denominator as f64
2056 } else {
2057 4.0 / ts.denominator as f64
2058 }
2059}
2060
2061pub fn compute_beams(notes: &[Note], time_sig: &TimeSignature) -> Vec<BeamState> {
2066 let beat_size = beam_beat_size(time_sig);
2067 let n = notes.len();
2068 let mut result = vec![BeamState::None; n];
2069
2070 let is_beamable = |note: &Note| -> bool {
2071 !note.is_rest
2072 && matches!(
2073 note.duration,
2074 Duration::Eighth
2075 | Duration::Sixteenth
2076 | Duration::ThirtySecond
2077 | Duration::SixtyFourth
2078 )
2079 };
2080
2081 let mut starts = Vec::with_capacity(n);
2083 let mut pos = 0.0f64;
2084 for note in notes {
2085 starts.push(pos);
2086 pos += note.beats();
2087 }
2088
2089 let group_id = |i: usize| -> i64 { (starts[i] / beat_size).floor() as i64 };
2091
2092 let mut i = 0;
2093 while i < n {
2094 if !is_beamable(¬es[i]) {
2095 i += 1;
2096 continue;
2097 }
2098 let g = group_id(i);
2099 let mut j = i;
2101 while j < n && is_beamable(¬es[j]) && group_id(j) == g {
2102 j += 1;
2103 }
2104 let run = j - i;
2105 if run == 1 {
2106 result[i] = BeamState::None;
2107 } else {
2108 result[i] = BeamState::Begin;
2109 result[i + 1..j - 1].fill(BeamState::Continue);
2110 result[j - 1] = BeamState::End;
2111 }
2112 i = j;
2113 }
2114 result
2115}
2116
2117fn note_content_eq(a: &Note, b: &Note) -> bool {
2118 a.is_rest == b.is_rest
2119 && a.pitches == b.pitches
2120 && a.duration == b.duration
2121 && a.dot_count == b.dot_count
2122 && a.tie_start == b.tie_start
2123 && a.tie_end == b.tie_end
2124 && a.beam == b.beam
2125 && a.articulations == b.articulations
2126 && a.dynamic == b.dynamic
2127 && a.stem_up == b.stem_up
2128 && a.hairpin_start == b.hairpin_start
2129 && a.hairpin_end == b.hairpin_end
2130 && a.tuplet == b.tuplet
2131 && a.chord_symbol == b.chord_symbol
2132 && a.is_grace == b.is_grace
2133 && a.grace_slash == b.grace_slash
2134 && a.ottava_start == b.ottava_start
2135 && a.ottava_end == b.ottava_end
2136 && a.lyric == b.lyric
2137 && a.pedal_start == b.pedal_start
2138 && a.pedal_end == b.pedal_end
2139 && a.slur_start == b.slur_start
2140 && a.slur_end == b.slur_end
2141 && a.arpeggiate == b.arpeggiate
2142 && a.tab_position == b.tab_position
2143 && a.tab_positions == b.tab_positions
2144}
2145
2146#[cfg(test)]
2147mod tests {
2148 use super::*;
2149 use crate::model::pitch::Step;
2150
2151 #[test]
2152 fn default_score_has_one_part_four_measures() {
2153 let score = Score::default();
2154 assert_eq!(score.parts.len(), 1);
2155 assert_eq!(score.parts[0].staves.len(), 1);
2156 assert_eq!(score.parts[0].staves[0].measures.len(), 4);
2157 }
2158
2159 #[test]
2160 fn assign_tablature_positions_is_capo_aware_and_preserves_explicit_positions() {
2161 let mut score = Score::new("Guitar", 120, 4, 4, 0, 1);
2162 score.parts[0].staves[0].tablature = Some(TablatureConfig {
2163 lines: 6,
2164 tuning_midi: vec![64, 59, 55, 50, 45, 40],
2165 capo: 2,
2166 });
2167 score.parts[0].staves[0].measures[0].voices[0].push(Note::new(
2168 Pitch::with_alter(Step::F, 4, 1),
2169 Duration::Quarter,
2170 ));
2171 score.parts[0].staves[0].measures[0].voices[0]
2172 .push(Note::new(Pitch::new(Step::G, 3), Duration::Quarter));
2173 score.parts[0].staves[0].measures[0].voices[0][2].tab_position =
2174 Some(TabPosition { string: 6, fret: 7 });
2175
2176 assert_eq!(assign_tablature_positions(&mut score), 1);
2177 let notes = &score.parts[0].staves[0].measures[0].voices[0];
2178 assert_eq!(
2179 notes[1].tab_position,
2180 Some(TabPosition { string: 1, fret: 0 })
2181 );
2182 assert_eq!(notes[1].string_number, Some(1));
2183 assert_eq!(
2184 notes[2].tab_position,
2185 Some(TabPosition { string: 6, fret: 7 })
2186 );
2187 }
2188
2189 #[test]
2190 fn assign_tablature_positions_optimizes_chord_strings_and_fret_span() {
2191 let mut score = Score::new("Guitar", 120, 4, 4, 0, 1);
2192 score.parts[0].staves[0].tablature = Some(TablatureConfig {
2193 lines: 6,
2194 tuning_midi: vec![64, 59, 55, 50, 45, 40],
2195 capo: 0,
2196 });
2197 let mut chord = Note::new(Pitch::new(Step::E, 4), Duration::Quarter);
2198 chord.pitches.push(Pitch::new(Step::G, 4));
2199 score.parts[0].staves[0].measures[0].voices[0].push(chord);
2200
2201 assert_eq!(assign_tablature_positions(&mut score), 1);
2202 let positions = &score.parts[0].staves[0].measures[0].voices[0][1].tab_positions;
2203 assert_eq!(
2204 positions,
2205 &vec![
2206 TabPosition { string: 2, fret: 5 },
2207 TabPosition { string: 1, fret: 3 },
2208 ]
2209 );
2210 }
2211
2212 #[test]
2213 fn new_score_measure_count() {
2214 let score = Score::new("Test", 120, 4, 4, 0, 8);
2215 assert_eq!(score.measure_count(), 8);
2216 }
2217
2218 #[test]
2219 fn note_beats_quarter() {
2220 let note = Note::new(Pitch::new(Step::C, 4), Duration::Quarter);
2221 assert!((note.beats() - 1.0).abs() < 1e-9);
2222 }
2223
2224 #[test]
2225 fn note_beats_dotted_quarter() {
2226 let mut note = Note::new(Pitch::new(Step::C, 4), Duration::Quarter);
2227 note.dot_count = 1;
2228 assert!((note.beats() - 1.5).abs() < 1e-9);
2229 }
2230
2231 #[test]
2232 fn grace_note_beats_zero() {
2233 let mut note = Note::new(Pitch::new(Step::C, 4), Duration::Eighth);
2234 note.is_grace = true;
2235 assert_eq!(note.beats(), 0.0);
2236 }
2237
2238 #[test]
2239 fn measure_empty_4_4_fills_four_beats() {
2240 let m = Measure::empty(4, 4);
2241 let total: f64 = m.voices[0].iter().map(|n| n.beats()).sum();
2242 assert!((total - 4.0).abs() < 1e-9);
2243 }
2244
2245 #[test]
2246 fn measure_empty_3_4_fills_three_beats() {
2247 let m = Measure::empty(3, 4);
2248 let total: f64 = m.voices[0].iter().map(|n| n.beats()).sum();
2249 assert!((total - 3.0).abs() < 1e-9);
2250 }
2251
2252 #[test]
2253 fn whole_filling_beats() {
2254 assert_eq!(Duration::whole_filling_beats(4.0), Duration::Whole);
2255 assert_eq!(Duration::whole_filling_beats(2.0), Duration::Half);
2256 assert_eq!(Duration::whole_filling_beats(1.0), Duration::Quarter);
2257 }
2258
2259 #[test]
2262 fn statistics_default_score_all_rests() {
2263 let score = Score::default();
2264 let s = score.statistics();
2265 assert_eq!(s.part_count, 1);
2266 assert_eq!(s.measure_count, 4);
2267 assert_eq!(s.note_count, 0);
2268 assert!(s.rest_count > 0);
2269 }
2270
2271 #[test]
2272 fn statistics_duration_estimate() {
2273 let score = Score::new("T", 120, 4, 4, 0, 1);
2275 let s = score.statistics();
2276 assert!((s.estimated_duration_secs - 2.0).abs() < 0.01);
2277 }
2278
2279 #[test]
2280 fn score_duration_secs_matches_statistics() {
2281 use super::score_duration_secs;
2282 let score = Score::new("T", 120, 4, 4, 0, 4);
2283 let secs = score_duration_secs(&score);
2284 assert!((secs - 8.0).abs() < 0.01, "expected ~8.0 s, got {secs}");
2286 }
2287
2288 #[test]
2289 fn score_duration_secs_zero_bpm_returns_zero() {
2290 use super::score_duration_secs;
2291 let mut score = Score::new("T", 120, 4, 4, 0, 1);
2292 score.settings.tempo_bpm = 0;
2293 assert_eq!(score_duration_secs(&score), 0.0);
2294 }
2295
2296 #[test]
2297 fn score_duration_secs_per_measure_tempo() {
2298 use super::score_duration_secs;
2299 let mut score = Score::new("T", 120, 4, 4, 0, 2);
2301 score.parts[0].staves[0].measures[1].tempo = Some(60);
2302 let secs = score_duration_secs(&score);
2303 assert!((secs - 6.0).abs() < 0.01, "expected ~6.0 s, got {secs}");
2304 }
2305
2306 #[test]
2309 fn extract_part_returns_single_part_score() {
2310 let mut score = Score::default();
2311 let mut p2 = Part::new("Violin", "Vln.");
2312 p2.staves.push(Staff::new(Clef::Treble));
2313 score.parts.push(p2);
2314 let ex = score.extract_part(0).unwrap();
2315 assert_eq!(ex.parts.len(), 1);
2316 assert_ne!(ex.id, score.id);
2317 assert_eq!(ex.metadata.title, score.metadata.title);
2318 }
2319
2320 #[test]
2321 fn extract_part_out_of_range_is_none() {
2322 let score = Score::default();
2323 assert!(score.extract_part(99).is_none());
2324 }
2325
2326 #[test]
2329 fn transpose_zero_is_clone() {
2330 let score = Score::new("T", 120, 4, 4, 0, 1);
2331 let t = transpose(&score, 0);
2332 assert_eq!(t.settings.key_signature.fifths, 0);
2333 }
2334
2335 #[test]
2336 fn transpose_c_major_up_2_to_d_major() {
2337 let score = Score::new("T", 120, 4, 4, 0, 1);
2338 assert_eq!(transpose(&score, 2).settings.key_signature.fifths, 2);
2339 }
2340
2341 #[test]
2342 fn transpose_d_major_up_5_to_g_major() {
2343 let score = Score::new("T", 120, 4, 4, 2, 1);
2344 assert_eq!(transpose(&score, 5).settings.key_signature.fifths, 1);
2345 }
2346
2347 #[test]
2348 fn transpose_c4_up_1_to_csharp4() {
2349 let p = transpose_pitch(&Pitch::new(Step::C, 4), 1);
2350 assert_eq!(p.to_midi(), 61);
2351 assert_eq!(p.step, Step::C);
2352 assert_eq!(p.alter, 1);
2353 }
2354
2355 #[test]
2356 fn transpose_c4_down_1_to_b3() {
2357 let p = transpose_pitch(&Pitch::new(Step::C, 4), -1);
2358 assert_eq!(p.to_midi(), 59);
2359 assert_eq!(p.step, Step::B);
2360 assert_eq!(p.alter, 0);
2361 }
2362
2363 #[test]
2364 fn transpose_up_octave_keeps_step() {
2365 let p = transpose_pitch(&Pitch::new(Step::A, 4), 12);
2366 assert_eq!(p.to_midi(), 81);
2367 assert_eq!(p.step, Step::A);
2368 assert_eq!(p.octave, 5);
2369 }
2370
2371 #[test]
2372 fn statistics_with_repeat_doubles_duration() {
2373 let mut score = Score::new("T", 120, 4, 4, 0, 2);
2375 score.parts[0].staves[0].measures[0].barline_left =
2376 crate::model::notation::Barline::RepeatStart;
2377 score.parts[0].staves[0].measures[1].barline_right =
2378 crate::model::notation::Barline::RepeatEnd;
2379 let s = score.statistics();
2380 assert!((s.estimated_duration_secs - 8.0).abs() < 0.01);
2382 }
2383
2384 #[test]
2385 fn transpose_octave_boundary_b4_to_c5() {
2386 let p = transpose_pitch(&Pitch::new(Step::B, 4), 1);
2388 assert_eq!(p.to_midi(), 72);
2389 assert_eq!(p.step, Step::C);
2390 assert_eq!(p.octave, 5);
2391 }
2392
2393 #[test]
2394 fn transpose_clamp_at_midi_127() {
2395 let p = transpose_pitch(&Pitch::new(Step::G, 9), 3);
2397 assert_eq!(p.to_midi(), 127);
2398 }
2399
2400 #[test]
2403 fn merge_combines_parts() {
2404 let mut a = Score::new("A", 120, 4, 4, 0, 2);
2405 let b = Score::new("B", 120, 4, 4, 0, 2);
2406 let mut p2 = Part::new("Violin", "Vln.");
2408 p2.staves.push(Staff::new(Clef::Treble));
2409 for i in 0..2usize {
2410 let mut m = Measure::empty(4, 4);
2411 m.number = i as u32 + 1;
2412 p2.staves[0].measures.push(m);
2413 }
2414 a.parts.push(p2);
2415 let merged = a.merge(&b);
2416 assert_eq!(merged.parts.len(), 3);
2418 }
2419
2420 #[test]
2421 fn merge_pads_shorter_score() {
2422 let a = Score::new("A", 120, 4, 4, 0, 4);
2423 let b = Score::new("B", 120, 4, 4, 0, 2);
2424 let merged = a.merge(&b);
2425 assert_eq!(merged.parts[0].staves[0].measures.len(), 4);
2427 assert_eq!(merged.parts[1].staves[0].measures.len(), 4);
2428 }
2429
2430 #[test]
2431 fn merge_uses_self_metadata() {
2432 let mut a = Score::new("Title A", 120, 4, 4, 0, 2);
2433 a.metadata.composer = "Composer A".to_string();
2434 let b = Score::new("Title B", 120, 4, 4, 0, 2);
2435 let merged = a.merge(&b);
2436 assert_eq!(merged.metadata.title, "Title A");
2437 assert_eq!(merged.metadata.composer, "Composer A");
2438 }
2439
2440 #[test]
2441 fn merge_new_id_differs_from_both() {
2442 let a = Score::new("A", 120, 4, 4, 0, 2);
2443 let b = Score::new("B", 120, 4, 4, 0, 2);
2444 let merged = a.merge(&b);
2445 assert_ne!(merged.id, a.id);
2446 assert_ne!(merged.id, b.id);
2447 }
2448
2449 #[test]
2452 fn staff_default_transpose_is_zero() {
2453 let s = Staff::new(Clef::Treble);
2454 assert_eq!(s.transpose_semitones, 0);
2455 }
2456
2457 #[test]
2460 fn score_default_has_schema_version_1() {
2461 let score = Score::default();
2462 assert_eq!(score.schema_version, 1);
2463 }
2464
2465 #[test]
2466 fn score_new_has_schema_version_1() {
2467 let score = Score::new("T", 120, 4, 4, 0, 4);
2468 assert_eq!(score.schema_version, 1);
2469 }
2470
2471 #[test]
2472 fn score_without_schema_version_deserializes_to_zero() {
2473 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":[]}"#;
2474 let score: Score = serde_json::from_str(json).unwrap();
2475 assert_eq!(score.schema_version, 0);
2476 }
2477
2478 #[test]
2481 fn score_template_solo_has_one_part_treble() {
2482 let score = Score::template(ScoreTemplate::Solo);
2483 assert_eq!(score.parts.len(), 1);
2484 assert_eq!(score.parts[0].staves.len(), 1);
2485 assert_eq!(score.parts[0].staves[0].clef, Clef::Treble);
2486 assert_eq!(score.parts[0].midi_program, 0);
2487 }
2488
2489 #[test]
2490 fn score_template_piano_has_two_staves() {
2491 let score = Score::template(ScoreTemplate::Piano);
2492 assert_eq!(score.parts.len(), 1);
2493 assert_eq!(score.parts[0].staves.len(), 2);
2494 assert_eq!(score.parts[0].staves[0].clef, Clef::Treble);
2495 assert_eq!(score.parts[0].staves[1].clef, Clef::Bass);
2496 }
2497
2498 #[test]
2499 fn score_template_string_quartet_has_four_parts() {
2500 let score = Score::template(ScoreTemplate::StringQuartet);
2501 assert_eq!(score.parts.len(), 4);
2502 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);
2505 assert_eq!(score.parts[3].midi_program, 42);
2506 }
2507
2508 #[test]
2509 fn score_template_string_orchestra_has_five_parts() {
2510 let score = Score::template(ScoreTemplate::StringOrchestra);
2511 assert_eq!(score.parts.len(), 5);
2512 assert_eq!(score.parts[4].midi_program, 43); }
2514
2515 #[test]
2516 fn score_template_brass_quintet_has_five_parts() {
2517 let score = Score::template(ScoreTemplate::BrassQuintet);
2518 assert_eq!(score.parts.len(), 5);
2519 assert_eq!(score.parts[2].midi_program, 60); }
2521
2522 #[test]
2523 fn score_template_default_measures_are_four() {
2524 let score = Score::template(ScoreTemplate::StringQuartet);
2525 for part in &score.parts {
2526 for staff in &part.staves {
2527 assert_eq!(staff.measures.len(), 4);
2528 }
2529 }
2530 }
2531
2532 #[test]
2535 fn measure_empty_has_no_breaks() {
2536 let m = Measure::empty(4, 4);
2537 assert!(!m.system_break);
2538 assert!(!m.page_break);
2539 }
2540
2541 #[test]
2542 fn system_break_survives_json_roundtrip() {
2543 let mut m = Measure::empty(4, 4);
2544 m.system_break = true;
2545 let json = serde_json::to_string(&m).unwrap();
2546 let m2: Measure = serde_json::from_str(&json).unwrap();
2547 assert!(m2.system_break);
2548 assert!(!m2.page_break);
2549 }
2550
2551 #[test]
2554 fn diff_identical_scores_is_empty() {
2555 let s = Score::new("T", 120, 4, 4, 0, 2);
2556 assert!(diff(&s, &s).is_empty());
2557 }
2558
2559 #[test]
2560 fn score_patch_covers_measure_semantics_and_note_insert_index() {
2561 let a = Score::new("T", 120, 4, 4, 0, 1);
2562 let mut b = a.clone();
2563 let measure = &mut b.parts[0].staves[0].measures[0];
2564 measure.key_sig = Some(KeySignature {
2565 fifths: -2,
2566 mode: "major".to_string(),
2567 });
2568 measure.time_sig = Some(TimeSignature {
2569 numerator: 3,
2570 denominator: 4,
2571 });
2572 measure.barline_left = Barline::RepeatStart;
2573 measure.barline_right = Barline::RepeatEnd;
2574 measure.rehearsal = Some("A".to_string());
2575 measure.volta = Some(VoltaBracket {
2576 number: 1,
2577 kind: "begin_end".to_string(),
2578 });
2579 measure.voices[0].insert(0, Note::new(Pitch::new(Step::C, 4), Duration::Quarter));
2580 let expected = b.parts[0].staves[0].measures[0].clone();
2581
2582 let patches = score_patch(&a, &b);
2583 assert!(
2584 patches
2585 .iter()
2586 .any(|p| matches!(p, ScorePatch::SetTimeSignature { .. }))
2587 );
2588 assert!(
2589 patches
2590 .iter()
2591 .any(|p| matches!(p, ScorePatch::SetBarlines { .. }))
2592 );
2593 assert!(
2594 patches
2595 .iter()
2596 .any(|p| matches!(p, ScorePatch::SetRehearsal { .. }))
2597 );
2598 assert!(
2599 patches
2600 .iter()
2601 .any(|p| matches!(p, ScorePatch::SetVolta { .. }))
2602 );
2603 let result = apply_patch(&a, &patches).expect("patch application failed");
2604 let result_measure = &result.parts[0].staves[0].measures[0];
2605 assert_eq!(result_measure.key_sig, expected.key_sig);
2606 assert_eq!(result_measure.time_sig, expected.time_sig);
2607 assert_eq!(result_measure.barline_left, expected.barline_left);
2608 assert_eq!(result_measure.barline_right, expected.barline_right);
2609 assert_eq!(result_measure.rehearsal, expected.rehearsal);
2610 assert_eq!(result_measure.volta, expected.volta);
2611 assert_eq!(result_measure.voices[0].len(), expected.voices[0].len());
2612 }
2613
2614 #[test]
2615 fn score_patch_replaces_when_structure_or_uncovered_fields_change() {
2616 let a = Score::new("T", 120, 4, 4, 0, 1);
2617 let mut b = a.clone();
2618 b.parts[0].name = "Piano".to_string();
2619 b.parts[0].staves[0].measures[0].expression_text = Some("dolce".to_string());
2620 let patches = score_patch(&a, &b);
2621 assert!(matches!(
2622 patches.as_slice(),
2623 [ScorePatch::ReplaceScore { .. }]
2624 ));
2625 let result = apply_patch(&a, &patches).expect("replacement failed");
2626 assert_eq!(result.parts[0].name, "Piano");
2627 assert_eq!(
2628 result.parts[0].staves[0].measures[0].expression_text,
2629 Some("dolce".to_string())
2630 );
2631 }
2632
2633 #[test]
2634 fn diff_detects_tempo_change() {
2635 let a = Score::new("T", 120, 4, 4, 0, 1);
2636 let mut b = a.clone();
2637 b.settings.tempo_bpm = 90;
2638 let changes = diff(&a, &b);
2639 assert_eq!(changes.len(), 1);
2640 assert!(matches!(
2641 changes[0],
2642 ScoreChange::TempoChanged { old: 120, new: 90 }
2643 ));
2644 }
2645
2646 #[test]
2647 fn diff_detects_title_change() {
2648 let a = Score::new("Old Title", 120, 4, 4, 0, 1);
2649 let mut b = a.clone();
2650 b.metadata.title = "New Title".to_string();
2651 let changes = diff(&a, &b);
2652 assert!(
2653 changes.iter().any(
2654 |c| matches!(c, ScoreChange::MetadataChanged { field, .. } if field == "title")
2655 )
2656 );
2657 }
2658
2659 #[test]
2660 fn diff_detects_note_modification() {
2661 let mut a = Score::new("T", 120, 4, 4, 0, 1);
2662 a.parts[0].staves[0].measures[0].voices[0] =
2663 vec![Note::new(Pitch::new(Step::C, 4), Duration::Quarter)];
2664 let mut b = a.clone();
2665 b.parts[0].staves[0].measures[0].voices[0][0] =
2666 Note::new(Pitch::new(Step::D, 4), Duration::Quarter);
2667 let changes = diff(&a, &b);
2668 assert!(
2669 changes
2670 .iter()
2671 .any(|c| matches!(c, ScoreChange::NoteModified { .. }))
2672 );
2673 }
2674
2675 #[test]
2676 fn diff_detects_part_added() {
2677 let a = Score::new("T", 120, 4, 4, 0, 1);
2678 let mut b = a.clone();
2679 let mut p = Part::new("Violin", "Vln.");
2680 p.staves.push(Staff::new(Clef::Treble));
2681 b.parts.push(p);
2682 let changes = diff(&a, &b);
2683 assert!(
2684 changes
2685 .iter()
2686 .any(|c| matches!(c, ScoreChange::PartAdded { part_index: 1 }))
2687 );
2688 }
2689
2690 #[test]
2691 fn diff_detects_measure_tempo_change() {
2692 let a = Score::new("T", 120, 4, 4, 0, 2);
2693 let mut b = a.clone();
2694 b.parts[0].staves[0].measures[1].tempo = Some(60);
2695 let changes = diff(&a, &b);
2696 assert!(changes.iter().any(|c| matches!(
2697 c,
2698 ScoreChange::MeasureTempoChanged {
2699 measure: 1,
2700 old: None,
2701 new: Some(60),
2702 ..
2703 }
2704 )));
2705 }
2706
2707 #[test]
2708 fn diff_detects_barline_change() {
2709 use crate::model::notation::Barline;
2710 let a = Score::new("T", 120, 4, 4, 0, 2);
2711 let mut b = a.clone();
2712 b.parts[0].staves[0].measures[0].barline_left = Barline::RepeatStart;
2713 let changes = diff(&a, &b);
2714 assert!(
2715 changes
2716 .iter()
2717 .any(|c| matches!(c, ScoreChange::BarlineChanged { measure: 0, .. }))
2718 );
2719 }
2720
2721 #[test]
2722 fn diff_detects_rehearsal_change() {
2723 let a = Score::new("T", 120, 4, 4, 0, 2);
2724 let mut b = a.clone();
2725 b.parts[0].staves[0].measures[0].rehearsal = Some("A".to_string());
2726 let changes = diff(&a, &b);
2727 assert!(
2728 changes
2729 .iter()
2730 .any(|c| matches!(c, ScoreChange::RehearsalMarkChanged { measure: 0, .. }))
2731 );
2732 }
2733
2734 #[test]
2735 fn diff_detects_volta_change() {
2736 use super::VoltaBracket;
2737 let a = Score::new("T", 120, 4, 4, 0, 2);
2738 let mut b = a.clone();
2739 b.parts[0].staves[0].measures[0].volta = Some(VoltaBracket {
2740 number: 1,
2741 kind: "begin_end".into(),
2742 });
2743 let changes = diff(&a, &b);
2744 assert!(
2745 changes
2746 .iter()
2747 .any(|c| matches!(c, ScoreChange::VoltaChanged { measure: 0, .. }))
2748 );
2749 }
2750
2751 #[test]
2752 fn diff_detects_key_signature_change() {
2753 let a = Score::new("T", 120, 4, 4, 0, 1);
2754 let mut b = a.clone();
2755 b.settings.key_signature.fifths = 2; let changes = diff(&a, &b);
2757 assert!(
2758 changes
2759 .iter()
2760 .any(|c| matches!(c, ScoreChange::KeySignatureChanged { .. }))
2761 );
2762 }
2763
2764 #[test]
2765 fn diff_same_key_signature_no_change() {
2766 let a = Score::new("T", 120, 4, 4, 2, 1);
2767 let changes = diff(&a, &a);
2768 assert!(changes.is_empty());
2769 }
2770
2771 #[test]
2772 fn score_duration_secs_region_partial() {
2773 use super::score_duration_secs_region;
2774 let score = Score::new("T", 120, 4, 4, 0, 4);
2776 let secs = score_duration_secs_region(&score, (1, 2));
2777 assert!((secs - 4.0).abs() < 0.01, "expected ~4.0 s, got {secs}");
2778 }
2779
2780 #[test]
2781 fn score_duration_secs_region_single_measure() {
2782 use super::score_duration_secs_region;
2783 let score = Score::new("T", 120, 4, 4, 0, 4);
2785 let secs = score_duration_secs_region(&score, (0, 0));
2786 assert!((secs - 2.0).abs() < 0.01, "expected ~2.0 s, got {secs}");
2787 }
2788
2789 #[test]
2792 fn measure_beats_remaining_empty_voice_returns_full() {
2793 use super::measure_beats_remaining;
2794 let mut score = Score::new("T", 120, 4, 4, 0, 1);
2795 score.parts[0].staves[0].measures[0].voices[0].clear();
2796 let rem = measure_beats_remaining(&score, 0, 0, 0, 0).unwrap();
2797 assert!(
2798 (rem - 4.0).abs() < 1e-9,
2799 "expected 4.0 remaining, got {rem}"
2800 );
2801 }
2802
2803 #[test]
2804 fn measure_beats_remaining_half_full_returns_half() {
2805 use super::measure_beats_remaining;
2806 use crate::model::pitch::Step;
2807 let mut score = Score::new("T", 120, 4, 4, 0, 1);
2808 score.parts[0].staves[0].measures[0].voices[0] = vec![
2809 Note::new(Pitch::new(Step::C, 4), Duration::Quarter),
2810 Note::new(Pitch::new(Step::D, 4), Duration::Quarter),
2811 ];
2812 let rem = measure_beats_remaining(&score, 0, 0, 0, 0).unwrap();
2813 assert!(
2814 (rem - 2.0).abs() < 1e-9,
2815 "expected 2.0 remaining, got {rem}"
2816 );
2817 }
2818
2819 #[test]
2820 fn measure_beats_remaining_full_voice_returns_zero() {
2821 use super::measure_beats_remaining;
2822 use crate::model::pitch::Step;
2823 let mut score = Score::new("T", 120, 4, 4, 0, 1);
2824 score.parts[0].staves[0].measures[0].voices[0] =
2825 vec![Note::new(Pitch::new(Step::C, 4), Duration::Whole)];
2826 let rem = measure_beats_remaining(&score, 0, 0, 0, 0).unwrap();
2827 assert!((rem).abs() < 1e-9, "expected 0.0 remaining, got {rem}");
2828 }
2829
2830 #[test]
2831 fn measure_beats_remaining_tuplet_accounting() {
2832 use super::measure_beats_remaining;
2833 use crate::model::notation::TupletInfo;
2834 use crate::model::pitch::Step;
2835 let mut score = Score::new("T", 120, 4, 4, 0, 1);
2837 let tuplet = TupletInfo {
2838 actual_notes: 3,
2839 normal_notes: 2,
2840 };
2841 let mk = |step| {
2842 let mut n = Note::new(Pitch::new(step, 4), Duration::Quarter);
2843 n.tuplet = Some(tuplet.clone());
2844 n
2845 };
2846 score.parts[0].staves[0].measures[0].voices[0] =
2847 vec![mk(Step::C), mk(Step::D), mk(Step::E)];
2848 let rem = measure_beats_remaining(&score, 0, 0, 0, 0).unwrap();
2849 assert!(
2850 (rem - 2.0).abs() < 1e-9,
2851 "expected 2.0 remaining (triplets used 2.0), got {rem}"
2852 );
2853 }
2854
2855 #[test]
2856 fn measure_beats_remaining_out_of_range_returns_err() {
2857 use super::measure_beats_remaining;
2858 let score = Score::new("T", 120, 4, 4, 0, 1);
2859 assert!(measure_beats_remaining(&score, 99, 0, 0, 0).is_err());
2860 assert!(measure_beats_remaining(&score, 0, 99, 0, 0).is_err());
2861 assert!(measure_beats_remaining(&score, 0, 0, 99, 0).is_err());
2862 assert!(measure_beats_remaining(&score, 0, 0, 0, 4).is_err());
2863 }
2864
2865 #[test]
2866 fn note_content_eq_ignores_id() {
2867 use crate::model::pitch::Step;
2868 let mut a = Note::new(Pitch::new(Step::C, 4), Duration::Quarter);
2869 let mut b = a.clone();
2870 b.id = "different-id".to_string();
2871 assert!(note_content_eq(&a, &b));
2872 b.pitches[0] = Pitch::new(Step::D, 4);
2874 assert!(!note_content_eq(&a, &b));
2875 let mut c = a.clone();
2877 a.stem_up = Some(true);
2878 c.stem_up = Some(false);
2879 assert!(!note_content_eq(&a, &c));
2880 }
2881
2882 #[test]
2883 fn suggested_stem_up_below_middle() {
2884 use crate::model::notation::Clef;
2885 let pitches = vec![Pitch::new(Step::C, 4)];
2887 assert!(suggested_stem_up(&pitches, &Clef::Treble));
2888 }
2889
2890 #[test]
2891 fn suggested_stem_up_above_middle() {
2892 use crate::model::notation::Clef;
2893 let pitches = vec![Pitch::new(Step::G, 5)];
2895 assert!(!suggested_stem_up(&pitches, &Clef::Treble));
2896 }
2897
2898 #[test]
2899 fn suggested_stem_up_at_middle_line() {
2900 use crate::model::notation::Clef;
2901 let pitches = vec![Pitch::new(Step::B, 4)];
2903 assert!(!suggested_stem_up(&pitches, &Clef::Treble));
2904 }
2905
2906 #[test]
2907 fn suggested_stem_up_chord() {
2908 use crate::model::notation::Clef;
2909 let pitches = vec![Pitch::new(Step::C, 4), Pitch::new(Step::G, 4)];
2911 assert!(suggested_stem_up(&pitches, &Clef::Treble));
2912 }
2913
2914 #[test]
2915 fn suggested_stem_up_bass_clef() {
2916 use crate::model::notation::Clef;
2917 let pitches = vec![Pitch::new(Step::D, 3)];
2919 assert!(!suggested_stem_up(&pitches, &Clef::Bass));
2920 let pitches2 = vec![Pitch::new(Step::C, 3)];
2922 assert!(suggested_stem_up(&pitches2, &Clef::Bass));
2923 }
2924
2925 #[test]
2926 fn suggested_stem_up_empty_pitches() {
2927 use crate::model::notation::Clef;
2928 assert!(suggested_stem_up(&[], &Clef::Treble));
2929 }
2930
2931 fn eighth(pitch: Pitch) -> Note {
2932 Note::new(pitch, Duration::Eighth)
2933 }
2934 fn quarter(pitch: Pitch) -> Note {
2935 Note::new(pitch, Duration::Quarter)
2936 }
2937 fn rest_eighth() -> Note {
2938 Note::rest(Duration::Eighth)
2939 }
2940
2941 #[test]
2942 fn compute_beams_4_4_four_eighths() {
2943 use crate::model::notation::{Clef, TimeSignature};
2944 let _ = Clef::Treble; let ts = TimeSignature {
2946 numerator: 4,
2947 denominator: 4,
2948 };
2949 let c4 = Pitch::new(Step::C, 4);
2950 let notes = vec![
2951 eighth(c4.clone()),
2952 eighth(c4.clone()),
2953 eighth(c4.clone()),
2954 eighth(c4.clone()),
2955 ];
2956 let beams = compute_beams(¬es, &ts);
2957 assert_eq!(beams[0], BeamState::Begin);
2959 assert_eq!(beams[1], BeamState::End);
2960 assert_eq!(beams[2], BeamState::Begin);
2961 assert_eq!(beams[3], BeamState::End);
2962 }
2963
2964 #[test]
2965 fn compute_beams_4_4_all_eighth_one_group() {
2966 use crate::model::notation::TimeSignature;
2967 let ts = TimeSignature {
2968 numerator: 4,
2969 denominator: 4,
2970 };
2971 let c4 = Pitch::new(Step::C, 4);
2972 let notes = vec![eighth(c4.clone()), eighth(c4.clone())];
2974 let beams = compute_beams(¬es, &ts);
2975 assert_eq!(beams[0], BeamState::Begin);
2976 assert_eq!(beams[1], BeamState::End);
2977 }
2978
2979 #[test]
2980 fn compute_beams_quarter_not_beamed() {
2981 use crate::model::notation::TimeSignature;
2982 let ts = TimeSignature {
2983 numerator: 4,
2984 denominator: 4,
2985 };
2986 let c4 = Pitch::new(Step::C, 4);
2987 let notes = vec![quarter(c4.clone()), quarter(c4.clone())];
2988 let beams = compute_beams(¬es, &ts);
2989 assert_eq!(beams[0], BeamState::None);
2990 assert_eq!(beams[1], BeamState::None);
2991 }
2992
2993 #[test]
2994 fn compute_beams_rest_breaks_beam() {
2995 use crate::model::notation::TimeSignature;
2996 let ts = TimeSignature {
2997 numerator: 4,
2998 denominator: 4,
2999 };
3000 let c4 = Pitch::new(Step::C, 4);
3001 let notes = vec![eighth(c4.clone()), rest_eighth(), eighth(c4.clone())];
3002 let beams = compute_beams(¬es, &ts);
3003 assert_eq!(beams[0], BeamState::None);
3005 assert_eq!(beams[1], BeamState::None);
3006 assert_eq!(beams[2], BeamState::None);
3007 }
3008
3009 #[test]
3010 fn compute_beams_6_8_compound() {
3011 use crate::model::notation::TimeSignature;
3012 let ts = TimeSignature {
3013 numerator: 6,
3014 denominator: 8,
3015 };
3016 let c4 = Pitch::new(Step::C, 4);
3017 let notes: Vec<Note> = (0..6).map(|_| eighth(c4.clone())).collect();
3019 let beams = compute_beams(¬es, &ts);
3020 assert_eq!(beams[0], BeamState::Begin);
3021 assert_eq!(beams[1], BeamState::Continue);
3022 assert_eq!(beams[2], BeamState::End);
3023 assert_eq!(beams[3], BeamState::Begin);
3024 assert_eq!(beams[4], BeamState::Continue);
3025 assert_eq!(beams[5], BeamState::End);
3026 }
3027
3028 #[test]
3029 fn compute_beams_single_eighth() {
3030 use crate::model::notation::TimeSignature;
3031 let ts = TimeSignature {
3032 numerator: 4,
3033 denominator: 4,
3034 };
3035 let c4 = Pitch::new(Step::C, 4);
3036 let notes = vec![eighth(c4.clone())];
3037 let beams = compute_beams(¬es, &ts);
3038 assert_eq!(beams[0], BeamState::None);
3039 }
3040}