1use super::{
2 duration::Duration,
3 notation::{
4 Articulation, Barline, BeamState, ChordDefinition, ChordSymbol, Clef, CrossStaff, Dynamic,
5 FiguredBassFigure, GuitarTechnique, HairpinKind, KeySignature, Lyric, NoteHead, OttavaKind,
6 StyledText, TabPosition, 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, PartialEq, Serialize, Deserialize)]
81pub struct StaffGroup {
82 pub first_staff: usize,
84 pub last_staff: usize,
86 pub symbol: PartGroupSymbol,
87 #[serde(default)]
89 pub barlines_connect: bool,
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct Score {
94 pub id: String,
95 #[serde(default)]
97 pub schema_version: u32,
98 pub metadata: ScoreMetadata,
99 pub settings: ScoreSettings,
100 pub parts: Vec<Part>,
101 #[serde(default)]
102 pub part_groups: Vec<PartGroup>,
103 #[serde(default)]
105 pub texts: Vec<StyledText>,
106 #[serde(default)]
108 pub chord_definitions: Vec<ChordDefinition>,
109}
110
111impl Default for Score {
112 fn default() -> Self {
113 let mut part = Part::new("Piano", "Pno.");
114 part.staves.push(Staff::new(Clef::Treble));
115 for _ in 0..4 {
116 part.staves[0].measures.push(Measure::empty(4, 4));
117 }
118 Self {
119 id: Uuid::new_v4().to_string(),
120 schema_version: 1,
121 metadata: ScoreMetadata::default(),
122 settings: ScoreSettings::default(),
123 parts: vec![part],
124 part_groups: Vec::new(),
125 texts: Vec::new(),
126 chord_definitions: Vec::new(),
127 }
128 }
129}
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
133pub enum ScoreTemplate {
134 Solo,
136 Piano,
138 StringQuartet,
140 StringOrchestra,
142 BrassQuintet,
144}
145
146impl Score {
147 pub fn new(
148 title: &str,
149 tempo_bpm: u16,
150 numerator: u8,
151 denominator: u8,
152 fifths: i8,
153 measure_count: u32,
154 ) -> Self {
155 let mut score = Score::default();
156 score.metadata.title = title.to_string();
157 score.settings.tempo_bpm = tempo_bpm;
158 score.settings.time_signature = TimeSignature {
159 numerator,
160 denominator,
161 };
162 score.settings.key_signature = KeySignature {
163 fifths,
164 mode: "major".to_string(),
165 };
166
167 score.parts[0].staves[0].measures.clear();
168 for i in 0..measure_count {
169 let mut m = Measure::empty(numerator, denominator);
170 m.number = i + 1;
171 score.parts[0].staves[0].measures.push(m);
172 }
173 score
174 }
175
176 pub fn template(kind: ScoreTemplate) -> Self {
181 fn measures(num: u8, den: u8, count: u32) -> Vec<Measure> {
182 (0..count)
183 .map(|i| {
184 let mut m = Measure::empty(num, den);
185 m.number = i + 1;
186 m
187 })
188 .collect()
189 }
190 fn part(name: &str, short: &str, clef: Clef, program: u8) -> Part {
191 let mut p = Part::new(name, short);
192 p.midi_program = program;
193 let mut s = Staff::new(clef);
194 s.measures = measures(4, 4, 4);
195 p.staves.push(s);
196 p
197 }
198
199 let mut score = Score {
200 id: uuid::Uuid::new_v4().to_string(),
201 schema_version: 1,
202 metadata: ScoreMetadata::default(),
203 settings: ScoreSettings::default(),
204 parts: Vec::new(),
205 part_groups: Vec::new(),
206 texts: Vec::new(),
207 chord_definitions: Vec::new(),
208 };
209
210 match kind {
211 ScoreTemplate::Solo => {
212 score.parts.push(part("Piano", "Pno.", Clef::Treble, 0));
213 }
214 ScoreTemplate::Piano => {
215 let mut p = Part::new("Piano", "Pno.");
216 p.midi_program = 0;
217 let mut treble = Staff::new(Clef::Treble);
218 treble.measures = measures(4, 4, 4);
219 let mut bass = Staff::new(Clef::Bass);
220 bass.measures = measures(4, 4, 4);
221 p.staves.push(treble);
222 p.staves.push(bass);
223 score.parts.push(p);
224 }
225 ScoreTemplate::StringQuartet => {
226 score
227 .parts
228 .push(part("Violin I", "Vn. I", Clef::Treble, 40));
229 score
230 .parts
231 .push(part("Violin II", "Vn. II", Clef::Treble, 40));
232 score.parts.push(part("Viola", "Va.", Clef::Alto, 41));
233 score.parts.push(part("Cello", "Vc.", Clef::Bass, 42));
234 }
235 ScoreTemplate::StringOrchestra => {
236 score
237 .parts
238 .push(part("Violin I", "Vn. I", Clef::Treble, 40));
239 score
240 .parts
241 .push(part("Violin II", "Vn. II", Clef::Treble, 40));
242 score.parts.push(part("Viola", "Va.", Clef::Alto, 41));
243 score.parts.push(part("Cello", "Vc.", Clef::Bass, 42));
244 score.parts.push(part("Contrabass", "Cb.", Clef::Bass, 43));
245 }
246 ScoreTemplate::BrassQuintet => {
247 score
248 .parts
249 .push(part("Trumpet I", "Tpt. I", Clef::Treble, 56));
250 score
251 .parts
252 .push(part("Trumpet II", "Tpt. II", Clef::Treble, 56));
253 score
254 .parts
255 .push(part("French Horn", "Hn.", Clef::Treble, 60));
256 score.parts.push(part("Trombone", "Tbn.", Clef::Bass, 57));
257 score.parts.push(part("Tuba", "Tba.", Clef::Bass, 58));
258 }
259 }
260 score
261 }
262
263 pub fn measure_count(&self) -> usize {
264 self.parts
265 .first()
266 .and_then(|p| p.staves.first())
267 .map(|s| s.measures.len())
268 .unwrap_or(0)
269 }
270
271 pub fn statistics(&self) -> ScoreStats {
273 let measure_count = self.measure_count();
274 let part_count = self.parts.len();
275
276 let seq = measure_sequence(self);
278 let total_beats: f64 = self
279 .parts
280 .first()
281 .and_then(|p| p.staves.first())
282 .map(|s| {
283 seq.iter()
284 .filter_map(|&idx| s.measures.get(idx))
285 .flat_map(|m| m.voices.iter().flat_map(|v| v.iter()))
286 .map(|n| n.beats())
287 .sum()
288 })
289 .unwrap_or(0.0);
290
291 let mut note_count = 0usize;
292 let mut rest_count = 0usize;
293 for part in &self.parts {
294 for staff in &part.staves {
295 for measure in &staff.measures {
296 for voice in &measure.voices {
297 for note in voice {
298 if note.is_rest {
299 rest_count += 1;
300 } else {
301 note_count += 1;
302 }
303 }
304 }
305 }
306 }
307 }
308
309 let bpm = self.settings.tempo_bpm as f64;
310 let estimated_duration_secs = if bpm > 0.0 {
311 total_beats / bpm * 60.0
312 } else {
313 0.0
314 };
315
316 ScoreStats {
317 measure_count,
318 note_count,
319 rest_count,
320 part_count,
321 estimated_duration_secs,
322 }
323 }
324
325 pub fn extract_part(&self, part_index: usize) -> Option<Score> {
328 let part = self.parts.get(part_index)?.clone();
329 Some(Score {
330 id: Uuid::new_v4().to_string(),
331 schema_version: 1,
332 metadata: self.metadata.clone(),
333 settings: self.settings.clone(),
334 parts: vec![part],
335 part_groups: Vec::new(),
336 texts: self.texts.clone(),
337 chord_definitions: self.chord_definitions.clone(),
338 })
339 }
340
341 pub fn merge(&self, other: &Score) -> Score {
345 let self_count = self.measure_count();
346 let other_count = other.measure_count();
347 let max_count = self_count.max(other_count);
348 let ts = self.settings.time_signature.clone();
349
350 let pad = |mut part: Part, from: usize| -> Part {
351 for staff in &mut part.staves {
352 for i in from..max_count {
353 let mut m = Measure::empty(ts.numerator, ts.denominator);
354 m.number = i as u32 + 1;
355 staff.measures.push(m);
356 }
357 }
358 part
359 };
360
361 let mut parts: Vec<Part> = self
362 .parts
363 .iter()
364 .cloned()
365 .map(|p| pad(p, self_count))
366 .collect();
367 for p in &other.parts {
368 parts.push(pad(p.clone(), other_count));
369 }
370
371 Score {
372 id: Uuid::new_v4().to_string(),
373 schema_version: 1,
374 metadata: self.metadata.clone(),
375 settings: self.settings.clone(),
376 parts,
377 part_groups: Vec::new(),
378 texts: self.texts.clone(),
379 chord_definitions: self.chord_definitions.clone(),
380 }
381 }
382}
383
384#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
386pub struct ScoreStats {
387 pub measure_count: usize,
388 pub note_count: usize,
390 pub rest_count: usize,
391 pub part_count: usize,
392 pub estimated_duration_secs: f64,
394}
395
396use super::pitch::Step;
399use super::repeat::measure_sequence;
400
401pub fn assign_tablature_positions(score: &mut Score) -> usize {
413 const MAX_FRET: i16 = 24;
414 let mut assigned = 0;
415
416 for part in &mut score.parts {
417 for staff in &mut part.staves {
418 let Some(tab) = &staff.tablature else {
419 continue;
420 };
421 let tuning = tab.tuning_midi.clone();
422 let lines = tab.lines as usize;
423 let capo = i16::from(tab.capo);
424
425 for measure in &mut staff.measures {
426 for voice in &mut measure.voices {
427 for note in voice.iter_mut() {
428 if note.is_rest
429 || note.tab_position.is_some()
430 || !note.tab_positions.is_empty()
431 || note.pitches.is_empty()
432 {
433 continue;
434 }
435 let pitches: Vec<i16> = note.pitches.iter().map(Pitch::to_midi).collect();
436 let Some(positions) =
437 best_tablature_assignment(&pitches, &tuning, lines, capo, MAX_FRET)
438 else {
439 continue;
440 };
441
442 note.tab_position = positions.first().cloned();
443 note.tab_positions = positions;
444 note.string_number = note.tab_position.as_ref().map(|p| p.string);
445 assigned += 1;
446 }
447 }
448 }
449 }
450 }
451
452 assigned
453}
454
455pub fn optimize_tablature_positions(score: &mut Score) -> usize {
462 const MAX_FRET: i16 = 24;
463 let mut assigned = 0;
464
465 for part in &mut score.parts {
466 for staff in &mut part.staves {
467 let Some(tab) = &staff.tablature else {
468 continue;
469 };
470 let tuning = tab.tuning_midi.clone();
471 let lines = tab.lines as usize;
472 let capo = i16::from(tab.capo);
473 for voice in 0..4 {
474 let mut locations = Vec::new();
476 for (measure_index, measure) in staff.measures.iter().enumerate() {
477 for (note_index, note) in measure.voices[voice].iter().enumerate() {
478 if !note.is_rest && !note.pitches.is_empty() {
479 locations.push((measure_index, note_index, note.clone()));
480 }
481 }
482 }
483 let candidates: Vec<Vec<Vec<TabPosition>>> = locations
484 .iter()
485 .map(|(_, _, note)| {
486 if let Some(positions) = if !note.tab_positions.is_empty() {
487 Some(note.tab_positions.clone())
488 } else {
489 note.tab_position.clone().map(|position| vec![position])
490 } {
491 vec![positions]
492 } else {
493 tablature_assignments(
494 ¬e.pitches.iter().map(Pitch::to_midi).collect::<Vec<_>>(),
495 &tuning,
496 lines,
497 capo,
498 MAX_FRET,
499 )
500 }
501 })
502 .collect();
503 if candidates.iter().any(Vec::is_empty) {
504 continue;
505 }
506
507 let mut costs: Vec<Vec<(u32, Option<usize>)>> = candidates
508 .iter()
509 .map(|events| vec![(u32::MAX, None); events.len()])
510 .collect();
511 for (candidate_index, candidate) in candidates[0].iter().enumerate() {
512 costs[0][candidate_index] = (tablature_load(candidate), None);
513 }
514 for event_index in 1..candidates.len() {
515 for (candidate_index, candidate) in candidates[event_index].iter().enumerate() {
516 let load = tablature_load(candidate);
517 for (previous_index, previous) in
518 candidates[event_index - 1].iter().enumerate()
519 {
520 let previous_cost = costs[event_index - 1][previous_index].0;
521 let cost = previous_cost
522 .saturating_add(load)
523 .saturating_add(tablature_movement(previous, candidate));
524 if cost < costs[event_index][candidate_index].0 {
525 costs[event_index][candidate_index] = (cost, Some(previous_index));
526 }
527 }
528 }
529 }
530 let mut selected = vec![0; candidates.len()];
531 if let Some((last, _)) = costs.last().and_then(|row| {
532 row.iter()
533 .enumerate()
534 .min_by_key(|(index, (cost, _))| (*cost, *index))
535 }) {
536 selected[candidates.len() - 1] = last;
537 for event_index in (1..candidates.len()).rev() {
538 selected[event_index - 1] =
539 costs[event_index][selected[event_index]].1.unwrap_or(0);
540 }
541 }
542
543 for (((measure_index, note_index, original), event_candidates), selected_index) in
544 locations.into_iter().zip(candidates).zip(selected)
545 {
546 if original.tab_position.is_none() && original.tab_positions.is_empty() {
547 let note = &mut staff.measures[measure_index].voices[voice][note_index];
548 let positions = event_candidates[selected_index].clone();
549 note.tab_position = positions.first().cloned();
550 note.tab_positions = positions;
551 note.string_number = note.tab_position.as_ref().map(|p| p.string);
552 assigned += 1;
553 }
554 }
555 }
556 }
557 }
558
559 assigned
560}
561
562fn tablature_load(positions: &[TabPosition]) -> u32 {
563 let sum: u32 = positions
564 .iter()
565 .map(|position| u32::from(position.fret))
566 .sum();
567 let min = positions
568 .iter()
569 .map(|position| position.fret)
570 .min()
571 .unwrap_or(0);
572 let max = positions
573 .iter()
574 .map(|position| position.fret)
575 .max()
576 .unwrap_or(0);
577 let span = max - min;
578 let stretch_penalty = span.saturating_sub(4) as u32 * 12;
581 sum + u32::from(span) * 2 + stretch_penalty
582}
583
584fn tablature_movement(previous: &[TabPosition], current: &[TabPosition]) -> u32 {
585 previous
586 .iter()
587 .zip(current)
588 .map(|(a, b)| u32::from(a.fret.abs_diff(b.fret)) + u32::from(a.string.abs_diff(b.string)))
589 .sum()
590}
591
592fn tablature_assignments(
593 pitches: &[i16],
594 tuning: &[i16],
595 lines: usize,
596 capo: i16,
597 max_fret: i16,
598) -> Vec<Vec<TabPosition>> {
599 #[allow(clippy::too_many_arguments)]
600 fn visit(
601 pitches: &[i16],
602 tuning: &[i16],
603 lines: usize,
604 capo: i16,
605 max_fret: i16,
606 index: usize,
607 used: &mut [bool],
608 current: &mut Vec<TabPosition>,
609 output: &mut Vec<Vec<TabPosition>>,
610 ) {
611 if index == pitches.len() {
612 output.push(current.clone());
613 return;
614 }
615 for (string, open) in tuning.iter().enumerate().take(lines) {
616 if used[string] {
617 continue;
618 }
619 let fret = pitches[index] - *open - capo;
620 if !(0..=max_fret).contains(&fret) {
621 continue;
622 }
623 used[string] = true;
624 current.push(TabPosition {
625 string: (string + 1) as u8,
626 fret: fret as u8,
627 });
628 visit(
629 pitches,
630 tuning,
631 lines,
632 capo,
633 max_fret,
634 index + 1,
635 used,
636 current,
637 output,
638 );
639 current.pop();
640 used[string] = false;
641 }
642 }
643
644 if pitches.is_empty() || pitches.len() > lines {
645 return Vec::new();
646 }
647 let mut output = Vec::new();
648 visit(
649 pitches,
650 tuning,
651 lines,
652 capo,
653 max_fret,
654 0,
655 &mut vec![false; lines],
656 &mut Vec::new(),
657 &mut output,
658 );
659 output
660}
661
662fn best_tablature_assignment(
663 pitches: &[i16],
664 tuning: &[i16],
665 lines: usize,
666 capo: i16,
667 max_fret: i16,
668) -> Option<Vec<TabPosition>> {
669 type Assignment = (i16, i16, i16, Vec<u8>, Vec<TabPosition>);
670
671 #[allow(clippy::too_many_arguments)]
672 fn search(
673 pitches: &[i16],
674 tuning: &[i16],
675 lines: usize,
676 capo: i16,
677 max_fret: i16,
678 index: usize,
679 used: &mut [bool],
680 current: &mut Vec<TabPosition>,
681 best: &mut Option<Assignment>,
682 ) {
683 if index == pitches.len() {
684 let sum: i16 = current.iter().map(|p| i16::from(p.fret)).sum();
685 let min = current.iter().map(|p| p.fret).min().unwrap_or(0);
686 let max = current.iter().map(|p| p.fret).max().unwrap_or(0);
687 let strings: Vec<u8> = current.iter().map(|p| p.string).collect();
688 let candidate = (
689 sum,
690 i16::from(max) - i16::from(min),
691 i16::from(max),
692 strings,
693 current.clone(),
694 );
695 if best.as_ref().is_none_or(|existing| {
696 (candidate.0, candidate.1, candidate.2, &candidate.3)
697 < (existing.0, existing.1, existing.2, &existing.3)
698 }) {
699 *best = Some(candidate);
700 }
701 return;
702 }
703
704 for (string, open) in tuning.iter().enumerate().take(lines) {
705 if used[string] {
706 continue;
707 }
708 let fret = pitches[index] - *open - capo;
709 if !(0..=max_fret).contains(&fret) {
710 continue;
711 }
712 used[string] = true;
713 current.push(TabPosition {
714 string: (string + 1) as u8,
715 fret: fret as u8,
716 });
717 search(
718 pitches,
719 tuning,
720 lines,
721 capo,
722 max_fret,
723 index + 1,
724 used,
725 current,
726 best,
727 );
728 current.pop();
729 used[string] = false;
730 }
731 }
732
733 if pitches.len() > lines {
734 return None;
735 }
736 let mut used = vec![false; lines];
737 let mut current = Vec::with_capacity(pitches.len());
738 let mut best = None;
739 search(
740 pitches,
741 tuning,
742 lines,
743 capo,
744 max_fret,
745 0,
746 &mut used,
747 &mut current,
748 &mut best,
749 );
750 best.map(|(_, _, _, _, positions)| positions)
751}
752
753pub fn transpose(score: &Score, semitones: i8) -> Score {
757 if semitones == 0 {
758 return score.clone();
759 }
760 let mut out = score.clone();
761 out.settings.key_signature.fifths = transpose_fifths(
762 score.settings.key_signature.fifths,
763 &score.settings.key_signature.mode,
764 semitones,
765 );
766 for part in &mut out.parts {
767 for staff in &mut part.staves {
768 for measure in &mut staff.measures {
769 if let Some(ref mut ks) = measure.key_sig {
770 ks.fifths = transpose_fifths(ks.fifths, &ks.mode, semitones);
771 }
772 for voice in &mut measure.voices {
773 for note in voice.iter_mut() {
774 for pitch in note.pitches.iter_mut() {
775 *pitch = transpose_pitch(pitch, semitones);
776 }
777 }
778 }
779 }
780 }
781 }
782 out
783}
784
785fn transpose_pitch(pitch: &Pitch, semitones: i8) -> Pitch {
786 let new_midi = (pitch.to_midi() + semitones as i16).clamp(0, 127) as u8;
787 let pc = new_midi % 12;
788 let oct = (new_midi / 12) as i8 - 1;
789 let (step, alter): (Step, i8) = if semitones >= 0 {
790 match pc {
791 0 => (Step::C, 0),
792 1 => (Step::C, 1),
793 2 => (Step::D, 0),
794 3 => (Step::D, 1),
795 4 => (Step::E, 0),
796 5 => (Step::F, 0),
797 6 => (Step::F, 1),
798 7 => (Step::G, 0),
799 8 => (Step::G, 1),
800 9 => (Step::A, 0),
801 10 => (Step::A, 1),
802 11 => (Step::B, 0),
803 _ => (Step::C, 0),
804 }
805 } else {
806 match pc {
807 0 => (Step::C, 0),
808 1 => (Step::D, -1),
809 2 => (Step::D, 0),
810 3 => (Step::E, -1),
811 4 => (Step::E, 0),
812 5 => (Step::F, 0),
813 6 => (Step::G, -1),
814 7 => (Step::G, 0),
815 8 => (Step::A, -1),
816 9 => (Step::A, 0),
817 10 => (Step::B, -1),
818 11 => (Step::B, 0),
819 _ => (Step::C, 0),
820 }
821 };
822 Pitch::with_microtone(step, oct, alter, pitch.microtone_cents)
823}
824
825fn transpose_fifths(fifths: i8, mode: &str, semitones: i8) -> i8 {
831 let tonic_major_pc = ((fifths as i32 * 7).rem_euclid(12)) as u8;
832 let tonic_pc = if mode == "minor" {
833 ((tonic_major_pc as i32 + 9).rem_euclid(12)) as u8
834 } else {
835 tonic_major_pc
836 };
837 let new_tonic = ((tonic_pc as i32 + semitones as i32).rem_euclid(12)) as u8;
838 let major_tonic = if mode == "minor" {
839 ((new_tonic as i32 + 3).rem_euclid(12)) as u8
840 } else {
841 new_tonic
842 };
843 let raw = ((major_tonic as i32 * 7).rem_euclid(12)) as i8;
844 if raw > 6 { raw - 12 } else { raw }
845}
846
847#[derive(Debug, Clone, Serialize, Deserialize)]
848pub struct Part {
849 pub id: String,
850 pub name: String,
851 pub short_name: String,
852 pub staves: Vec<Staff>,
853 #[serde(default)]
855 pub midi_channel: u8,
856 #[serde(default)]
858 pub midi_program: u8,
859 #[serde(default)]
861 pub midi_pitch_bends: Vec<MidiPitchBend>,
862 #[serde(default)]
864 pub midi_control_changes: Vec<MidiControlChange>,
865 #[serde(default)]
867 pub midi_program_changes: Vec<MidiProgramChange>,
868 #[serde(default)]
870 pub midi_aftertouch: Vec<MidiAftertouch>,
871 #[serde(default)]
873 pub percussion_instruments: Vec<PercussionInstrument>,
874 #[serde(default)]
876 pub staff_groups: Vec<StaffGroup>,
877}
878
879#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
880pub struct MidiPitchBend {
881 pub tick: u64,
882 pub channel: u8,
883 pub value: i16,
885}
886
887#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
888pub struct MidiControlChange {
889 pub tick: u64,
890 pub channel: u8,
891 pub controller: u8,
893 pub value: u8,
895}
896
897#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
898pub struct MidiProgramChange {
899 pub tick: u64,
900 pub channel: u8,
901 pub program: u8,
903}
904
905#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
906pub struct MidiAftertouch {
907 pub tick: u64,
908 pub channel: u8,
909 pub key: Option<u8>,
911 pub value: u8,
913}
914
915#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
916pub struct PercussionInstrument {
917 pub id: String,
918 #[serde(default)]
919 pub name: Option<String>,
920 #[serde(default)]
921 pub midi_unpitched: Option<u8>,
922}
923
924impl Part {
925 pub fn new(name: &str, short_name: &str) -> Self {
926 Self {
927 id: Uuid::new_v4().to_string(),
928 name: name.to_string(),
929 short_name: short_name.to_string(),
930 staves: Vec::new(),
931 midi_channel: 0,
932 midi_program: 0,
933 midi_pitch_bends: Vec::new(),
934 midi_control_changes: Vec::new(),
935 midi_program_changes: Vec::new(),
936 midi_aftertouch: Vec::new(),
937 percussion_instruments: Vec::new(),
938 staff_groups: Vec::new(),
939 }
940 }
941
942 pub fn percussion_instrument_for_note(&self, note: &Note) -> Option<&PercussionInstrument> {
950 if !note.is_unpitched {
951 return None;
952 }
953 if let Some(instrument_id) = note.instrument_id.as_deref() {
954 return self
955 .percussion_instruments
956 .iter()
957 .find(|instrument| instrument.id == instrument_id);
958 }
959 let midi_key = u8::try_from(note.pitches.first()?.to_midi()).ok()?;
960 self.percussion_instruments
961 .iter()
962 .find(|instrument| instrument.midi_unpitched == Some(midi_key))
963 }
964}
965
966#[derive(Debug, Clone, Serialize, Deserialize)]
967pub struct Staff {
968 pub clef: Clef,
969 pub measures: Vec<Measure>,
970 #[serde(default)]
973 pub transpose_semitones: i8,
974 #[serde(default)]
975 pub tablature: Option<TablatureConfig>,
976}
977
978impl Staff {
979 pub fn new(clef: Clef) -> Self {
980 Self {
981 clef,
982 measures: Vec::new(),
983 transpose_semitones: 0,
984 tablature: None,
985 }
986 }
987}
988
989#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
990pub struct VoltaBracket {
991 pub number: u8,
993 pub kind: String,
995}
996
997#[derive(Debug, Clone, Serialize, Deserialize)]
998pub struct Measure {
999 pub number: u32,
1000 pub time_sig: Option<TimeSignature>,
1001 pub key_sig: Option<KeySignature>,
1002 pub clef: Option<Clef>,
1003 pub tempo: Option<u16>,
1004 pub barline_left: Barline,
1005 pub barline_right: Barline,
1006 #[serde(default)]
1007 pub volta: Option<VoltaBracket>,
1008 #[serde(default)]
1009 pub tempo_text: Option<String>,
1010 #[serde(default)]
1011 pub rehearsal: Option<String>,
1012 #[serde(default)]
1015 pub navigation: Option<String>,
1016 #[serde(default)]
1018 pub expression_text: Option<String>,
1019 #[serde(default)]
1020 pub texts: Vec<StyledText>,
1021 #[serde(default)]
1023 pub figured_bass: Vec<FiguredBassFigure>,
1024 #[serde(default)]
1026 pub multi_rest_count: Option<u8>,
1027 #[serde(default)]
1029 pub system_break: bool,
1030 #[serde(default)]
1032 pub page_break: bool,
1033 pub voices: [Vec<Note>; 4],
1035}
1036
1037impl Measure {
1038 pub fn empty(numerator: u8, denominator: u8) -> Self {
1039 let total_beats = TimeSignature {
1040 numerator,
1041 denominator,
1042 }
1043 .total_beats();
1044 let mut voice0: Vec<Note> = Vec::new();
1045 let mut remaining = total_beats;
1046 while remaining > 1e-9 {
1047 let dur = Duration::whole_filling_beats(remaining);
1048 remaining -= dur.beats(0);
1049 voice0.push(Note::rest(dur));
1050 }
1051 Self {
1052 number: 0,
1053 time_sig: None,
1054 key_sig: None,
1055 clef: None,
1056 tempo: None,
1057 barline_left: Barline::Normal,
1058 barline_right: Barline::Normal,
1059 volta: None,
1060 tempo_text: None,
1061 rehearsal: None,
1062 navigation: None,
1063 expression_text: None,
1064 texts: Vec::new(),
1065 figured_bass: Vec::new(),
1066 multi_rest_count: None,
1067 system_break: false,
1068 page_break: false,
1069 voices: [voice0, vec![], vec![], vec![]],
1070 }
1071 }
1072
1073 pub fn renumber(&mut self, n: u32) {
1074 self.number = n;
1075 }
1076}
1077
1078#[derive(Debug, Clone, Serialize, Deserialize)]
1079pub struct Note {
1080 pub id: String,
1081 pub is_rest: bool,
1082 #[serde(default)]
1084 pub is_unpitched: bool,
1085 #[serde(default)]
1087 pub instrument_id: Option<String>,
1088 pub pitches: Vec<Pitch>,
1090 #[serde(default)]
1091 pub tab_position: Option<super::notation::TabPosition>,
1092 #[serde(default)]
1095 pub tab_positions: Vec<super::notation::TabPosition>,
1096 pub duration: Duration,
1097 pub dot_count: u8,
1098 pub tie_start: bool,
1099 pub tie_end: bool,
1100 pub beam: BeamState,
1101 pub articulations: Vec<Articulation>,
1102 pub dynamic: Option<Dynamic>,
1103 pub stem_up: Option<bool>,
1104 #[serde(default)]
1105 pub hairpin_start: Option<HairpinKind>,
1106 #[serde(default)]
1107 pub hairpin_end: bool,
1108 #[serde(default)]
1109 pub tuplet: Option<TupletInfo>,
1110 #[serde(default)]
1111 pub chord_symbol: Option<ChordSymbol>,
1112 #[serde(default)]
1113 pub is_grace: bool,
1114 #[serde(default)]
1116 pub grace_slash: bool,
1117 #[serde(default)]
1118 pub ottava_start: Option<OttavaKind>,
1119 #[serde(default)]
1120 pub ottava_end: bool,
1121 #[serde(default)]
1122 pub lyric: Option<Lyric>,
1123 #[serde(default)]
1124 pub pedal_start: bool,
1125 #[serde(default)]
1126 pub pedal_end: bool,
1127 #[serde(default)]
1128 pub slur_start: bool,
1129 #[serde(default)]
1130 pub slur_end: bool,
1131 #[serde(default)]
1133 pub arpeggiate: Option<bool>,
1134 #[serde(default)]
1136 pub technique_text: Option<String>,
1137 #[serde(default)]
1138 pub glissando_start: bool,
1139 #[serde(default)]
1140 pub glissando_end: bool,
1141 #[serde(default)]
1142 pub cross_staff: Option<CrossStaff>,
1143 #[serde(default)]
1145 pub fingering: Option<u8>,
1146 #[serde(default)]
1149 pub fingerings: Vec<u8>,
1150 #[serde(default)]
1152 pub string_number: Option<u8>,
1153 #[serde(default)]
1154 pub note_head: NoteHead,
1155 #[serde(default)]
1157 pub is_cue: bool,
1158 #[serde(default)]
1160 pub trill_line_start: bool,
1161 #[serde(default)]
1163 pub trill_line_end: bool,
1164 #[serde(default)]
1166 pub guitar_technique: Option<GuitarTechnique>,
1167 #[serde(default)]
1169 pub guitar_bend_alter_cents: Option<i16>,
1170}
1171
1172impl Note {
1173 pub fn select_fingering(
1175 &self,
1176 policy: super::notation::FingeringSelectionPolicy,
1177 ) -> Option<u8> {
1178 let candidates = if self.fingerings.is_empty() {
1179 self.fingering.into_iter().collect::<Vec<_>>()
1180 } else {
1181 self.fingerings.clone()
1182 };
1183 match policy {
1184 super::notation::FingeringSelectionPolicy::SourceOrder => candidates.first().copied(),
1185 super::notation::FingeringSelectionPolicy::LowestNumber => {
1186 candidates.iter().copied().min()
1187 }
1188 super::notation::FingeringSelectionPolicy::HighestNumber => {
1189 candidates.iter().copied().max()
1190 }
1191 }
1192 }
1193
1194 pub fn new(pitch: Pitch, duration: Duration) -> Self {
1195 Self {
1196 id: Uuid::new_v4().to_string(),
1197 is_rest: false,
1198 is_unpitched: false,
1199 instrument_id: None,
1200 pitches: vec![pitch],
1201 tab_position: None,
1202 tab_positions: Vec::new(),
1203 duration,
1204 dot_count: 0,
1205 tie_start: false,
1206 tie_end: false,
1207 beam: BeamState::None,
1208 articulations: Vec::new(),
1209 dynamic: None,
1210 stem_up: None,
1211 hairpin_start: None,
1212 hairpin_end: false,
1213 tuplet: None,
1214 chord_symbol: None,
1215 is_grace: false,
1216 grace_slash: false,
1217 ottava_start: None,
1218 ottava_end: false,
1219 lyric: None,
1220 pedal_start: false,
1221 pedal_end: false,
1222 slur_start: false,
1223 slur_end: false,
1224 arpeggiate: None,
1225 technique_text: None,
1226 glissando_start: false,
1227 glissando_end: false,
1228 cross_staff: None,
1229 fingering: None,
1230 fingerings: Vec::new(),
1231 string_number: None,
1232 note_head: NoteHead::Normal,
1233 is_cue: false,
1234 trill_line_start: false,
1235 trill_line_end: false,
1236 guitar_technique: None,
1237 guitar_bend_alter_cents: None,
1238 }
1239 }
1240
1241 pub fn rest(duration: Duration) -> Self {
1242 Self {
1243 id: Uuid::new_v4().to_string(),
1244 is_rest: true,
1245 is_unpitched: false,
1246 instrument_id: None,
1247 pitches: Vec::new(),
1248 tab_position: None,
1249 tab_positions: Vec::new(),
1250 duration,
1251 dot_count: 0,
1252 tie_start: false,
1253 tie_end: false,
1254 beam: BeamState::None,
1255 articulations: Vec::new(),
1256 dynamic: None,
1257 stem_up: None,
1258 hairpin_start: None,
1259 hairpin_end: false,
1260 tuplet: None,
1261 chord_symbol: None,
1262 is_grace: false,
1263 grace_slash: false,
1264 ottava_start: None,
1265 ottava_end: false,
1266 lyric: None,
1267 pedal_start: false,
1268 pedal_end: false,
1269 slur_start: false,
1270 slur_end: false,
1271 arpeggiate: None,
1272 technique_text: None,
1273 glissando_start: false,
1274 glissando_end: false,
1275 cross_staff: None,
1276 fingering: None,
1277 fingerings: Vec::new(),
1278 string_number: None,
1279 note_head: NoteHead::Normal,
1280 is_cue: false,
1281 trill_line_start: false,
1282 trill_line_end: false,
1283 guitar_technique: None,
1284 guitar_bend_alter_cents: None,
1285 }
1286 }
1287
1288 pub fn beats(&self) -> f64 {
1289 if self.is_grace || self.is_cue {
1290 return 0.0;
1291 }
1292 let base = self.duration.beats(self.dot_count);
1293 if let Some(ref t) = self.tuplet {
1294 base * (t.normal_notes as f64) / (t.actual_notes as f64)
1295 } else {
1296 base
1297 }
1298 }
1299}
1300
1301impl Duration {
1302 pub fn whole_filling_beats(beats: f64) -> Duration {
1304 if beats >= 4.0 {
1305 Duration::Whole
1306 } else if beats >= 2.0 {
1307 Duration::Half
1308 } else if beats >= 1.0 {
1309 Duration::Quarter
1310 } else if beats >= 0.5 {
1311 Duration::Eighth
1312 } else if beats >= 0.25 {
1313 Duration::Sixteenth
1314 } else if beats >= 0.125 {
1315 Duration::ThirtySecond
1316 } else {
1317 Duration::SixtyFourth
1318 }
1319 }
1320}
1321
1322#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1326pub struct NoteAddr {
1327 pub part: usize,
1328 pub staff: usize,
1329 pub measure: usize,
1330 pub voice: usize,
1331 pub note: usize,
1332}
1333
1334#[derive(Debug, Clone, Serialize, Deserialize)]
1338pub enum ScoreChange {
1339 MetadataChanged {
1340 field: String,
1341 old: String,
1342 new: String,
1343 },
1344 TempoChanged {
1345 old: u16,
1346 new: u16,
1347 },
1348 KeySignatureChanged {
1349 old: KeySignature,
1350 new: KeySignature,
1351 },
1352 PartAdded {
1353 part_index: usize,
1354 },
1355 PartRemoved {
1356 part_index: usize,
1357 name: String,
1358 },
1359 NoteAdded {
1360 part: usize,
1361 staff: usize,
1362 measure: usize,
1363 voice: usize,
1364 note_index: usize,
1365 },
1366 NoteRemoved {
1367 part: usize,
1368 staff: usize,
1369 measure: usize,
1370 voice: usize,
1371 note: Box<Note>,
1372 },
1373 NoteModified {
1374 part: usize,
1375 staff: usize,
1376 measure: usize,
1377 voice: usize,
1378 note_index: usize,
1379 old: Box<Note>,
1380 new: Box<Note>,
1381 },
1382 TimeSigChanged {
1383 part: usize,
1384 staff: usize,
1385 measure: usize,
1386 old: Option<TimeSignature>,
1387 new: Option<TimeSignature>,
1388 },
1389 MeasureTempoChanged {
1390 part: usize,
1391 staff: usize,
1392 measure: usize,
1393 old: Option<u16>,
1394 new: Option<u16>,
1395 },
1396 BarlineChanged {
1397 part: usize,
1398 staff: usize,
1399 measure: usize,
1400 },
1401 RehearsalMarkChanged {
1402 part: usize,
1403 staff: usize,
1404 measure: usize,
1405 old: Option<String>,
1406 new: Option<String>,
1407 },
1408 VoltaChanged {
1409 part: usize,
1410 staff: usize,
1411 measure: usize,
1412 },
1413}
1414
1415pub fn diff(a: &Score, b: &Score) -> Vec<ScoreChange> {
1421 let mut changes: Vec<ScoreChange> = Vec::new();
1422
1423 macro_rules! meta {
1424 ($field:ident, $name:literal) => {
1425 if a.metadata.$field != b.metadata.$field {
1426 changes.push(ScoreChange::MetadataChanged {
1427 field: $name.to_string(),
1428 old: a.metadata.$field.clone(),
1429 new: b.metadata.$field.clone(),
1430 });
1431 }
1432 };
1433 }
1434 meta!(title, "title");
1435 meta!(composer, "composer");
1436 meta!(lyricist, "lyricist");
1437 meta!(copyright, "copyright");
1438 meta!(work_number, "work_number");
1439 meta!(movement_title, "movement_title");
1440
1441 if a.settings.tempo_bpm != b.settings.tempo_bpm {
1442 changes.push(ScoreChange::TempoChanged {
1443 old: a.settings.tempo_bpm,
1444 new: b.settings.tempo_bpm,
1445 });
1446 }
1447 if a.settings.key_signature != b.settings.key_signature {
1448 changes.push(ScoreChange::KeySignatureChanged {
1449 old: a.settings.key_signature.clone(),
1450 new: b.settings.key_signature.clone(),
1451 });
1452 }
1453
1454 let a_len = a.parts.len();
1455 let b_len = b.parts.len();
1456 for i in b_len..a_len {
1457 changes.push(ScoreChange::PartRemoved {
1458 part_index: i,
1459 name: a.parts[i].name.clone(),
1460 });
1461 }
1462 for i in a_len..b_len {
1463 changes.push(ScoreChange::PartAdded { part_index: i });
1464 }
1465
1466 for pi in 0..a_len.min(b_len) {
1467 let ap = &a.parts[pi];
1468 let bp = &b.parts[pi];
1469 for si in 0..ap.staves.len().min(bp.staves.len()) {
1470 let a_staff = &ap.staves[si];
1471 let b_staff = &bp.staves[si];
1472 for mi in 0..a_staff.measures.len().min(b_staff.measures.len()) {
1473 let am = &a_staff.measures[mi];
1474 let bm = &b_staff.measures[mi];
1475 for vi in 0..4usize {
1476 let av = &am.voices[vi];
1477 let bv = &bm.voices[vi];
1478 for (ni, (a_note, b_note)) in av.iter().zip(bv.iter()).enumerate() {
1479 if !note_content_eq(a_note, b_note) {
1480 changes.push(ScoreChange::NoteModified {
1481 part: pi,
1482 staff: si,
1483 measure: mi,
1484 voice: vi,
1485 note_index: ni,
1486 old: Box::new(a_note.clone()),
1487 new: Box::new(b_note.clone()),
1488 });
1489 }
1490 }
1491 for note in av.iter().skip(bv.len()) {
1492 changes.push(ScoreChange::NoteRemoved {
1493 part: pi,
1494 staff: si,
1495 measure: mi,
1496 voice: vi,
1497 note: Box::new(note.clone()),
1498 });
1499 }
1500 for ni in av.len()..bv.len() {
1501 changes.push(ScoreChange::NoteAdded {
1502 part: pi,
1503 staff: si,
1504 measure: mi,
1505 voice: vi,
1506 note_index: ni,
1507 });
1508 }
1509 }
1510 if am.time_sig != bm.time_sig {
1511 changes.push(ScoreChange::TimeSigChanged {
1512 part: pi,
1513 staff: si,
1514 measure: mi,
1515 old: am.time_sig.clone(),
1516 new: bm.time_sig.clone(),
1517 });
1518 }
1519 if am.tempo != bm.tempo {
1520 changes.push(ScoreChange::MeasureTempoChanged {
1521 part: pi,
1522 staff: si,
1523 measure: mi,
1524 old: am.tempo,
1525 new: bm.tempo,
1526 });
1527 }
1528 if am.barline_left != bm.barline_left || am.barline_right != bm.barline_right {
1529 changes.push(ScoreChange::BarlineChanged {
1530 part: pi,
1531 staff: si,
1532 measure: mi,
1533 });
1534 }
1535 if am.rehearsal != bm.rehearsal {
1536 changes.push(ScoreChange::RehearsalMarkChanged {
1537 part: pi,
1538 staff: si,
1539 measure: mi,
1540 old: am.rehearsal.clone(),
1541 new: bm.rehearsal.clone(),
1542 });
1543 }
1544 if am.volta != bm.volta {
1545 changes.push(ScoreChange::VoltaChanged {
1546 part: pi,
1547 staff: si,
1548 measure: mi,
1549 });
1550 }
1551 }
1552 }
1553 }
1554
1555 changes
1556}
1557
1558#[derive(Debug, Clone, Serialize, Deserialize)]
1565pub enum ScorePatch {
1566 SetMetadata {
1567 field: String,
1568 value: String,
1569 },
1570 SetTempo {
1571 value: u16,
1572 },
1573 SetKeySignature {
1574 part: usize,
1575 staff: usize,
1576 measure: usize,
1577 value: Option<KeySignature>,
1578 },
1579 SetTimeSignature {
1580 part: usize,
1581 staff: usize,
1582 measure: usize,
1583 value: Option<TimeSignature>,
1584 },
1585 SetBarlines {
1586 part: usize,
1587 staff: usize,
1588 measure: usize,
1589 left: Barline,
1590 right: Barline,
1591 },
1592 SetRehearsal {
1593 part: usize,
1594 staff: usize,
1595 measure: usize,
1596 value: Option<String>,
1597 },
1598 SetVolta {
1599 part: usize,
1600 staff: usize,
1601 measure: usize,
1602 value: Option<VoltaBracket>,
1603 },
1604 AddNote {
1606 part: usize,
1607 staff: usize,
1608 measure: usize,
1609 voice: usize,
1610 #[serde(default = "legacy_append_index")]
1612 note_index: usize,
1613 note: Box<Note>,
1614 },
1615 RemoveNote {
1616 part: usize,
1617 staff: usize,
1618 measure: usize,
1619 voice: usize,
1620 note_index: usize,
1621 },
1622 ReplaceNote {
1624 part: usize,
1625 staff: usize,
1626 measure: usize,
1627 voice: usize,
1628 note_index: usize,
1629 note: Box<Note>,
1630 },
1631 SetMeasureTempo {
1632 part: usize,
1633 staff: usize,
1634 measure: usize,
1635 value: Option<u16>,
1636 },
1637 ReplaceScore {
1640 score: Box<Score>,
1641 },
1642}
1643
1644fn legacy_append_index() -> usize {
1645 usize::MAX
1646}
1647
1648fn patch_requires_replace(a: &Score, b: &Score) -> bool {
1652 if a.settings.time_signature != b.settings.time_signature
1653 || a.settings.key_signature != b.settings.key_signature
1654 || a.parts.len() != b.parts.len()
1655 || a.part_groups.len() != b.part_groups.len()
1656 {
1657 return true;
1658 }
1659 if a.part_groups.iter().zip(&b.part_groups).any(|(x, y)| {
1660 x.first_part != y.first_part
1661 || x.last_part != y.last_part
1662 || x.symbol != y.symbol
1663 || x.barlines_connect != y.barlines_connect
1664 }) {
1665 return true;
1666 }
1667 for (ap, bp) in a.parts.iter().zip(&b.parts) {
1668 if ap.name != bp.name
1669 || ap.short_name != bp.short_name
1670 || ap.midi_channel != bp.midi_channel
1671 || ap.midi_program != bp.midi_program
1672 || ap.midi_pitch_bends != bp.midi_pitch_bends
1673 || ap.midi_control_changes != bp.midi_control_changes
1674 || ap.midi_program_changes != bp.midi_program_changes
1675 || ap.midi_aftertouch != bp.midi_aftertouch
1676 || ap.percussion_instruments != bp.percussion_instruments
1677 || ap.staff_groups != bp.staff_groups
1678 || ap.staves.len() != bp.staves.len()
1679 {
1680 return true;
1681 }
1682 for (as_, bs) in ap.staves.iter().zip(&bp.staves) {
1683 if as_.clef != bs.clef
1684 || as_.transpose_semitones != bs.transpose_semitones
1685 || as_.measures.len() != bs.measures.len()
1686 {
1687 return true;
1688 }
1689 for (am, bm) in as_.measures.iter().zip(&bs.measures) {
1690 if am.number != bm.number
1691 || am.clef != bm.clef
1692 || am.tempo_text != bm.tempo_text
1693 || am.navigation != bm.navigation
1694 || am.expression_text != bm.expression_text
1695 || am.multi_rest_count != bm.multi_rest_count
1696 || am.system_break != bm.system_break
1697 || am.page_break != bm.page_break
1698 {
1699 return true;
1700 }
1701 }
1702 }
1703 }
1704 false
1705}
1706
1707pub fn score_patch(a: &Score, b: &Score) -> Vec<ScorePatch> {
1712 let mut patches: Vec<ScorePatch> = Vec::new();
1713
1714 if patch_requires_replace(a, b) {
1715 return vec![ScorePatch::ReplaceScore {
1716 score: Box::new(b.clone()),
1717 }];
1718 }
1719
1720 macro_rules! meta {
1721 ($field:ident, $name:literal) => {
1722 if a.metadata.$field != b.metadata.$field {
1723 patches.push(ScorePatch::SetMetadata {
1724 field: $name.to_string(),
1725 value: b.metadata.$field.clone(),
1726 });
1727 }
1728 };
1729 }
1730 meta!(title, "title");
1731 meta!(composer, "composer");
1732 meta!(lyricist, "lyricist");
1733 meta!(copyright, "copyright");
1734 meta!(work_number, "work_number");
1735 meta!(movement_title, "movement_title");
1736
1737 if a.settings.tempo_bpm != b.settings.tempo_bpm {
1738 patches.push(ScorePatch::SetTempo {
1739 value: b.settings.tempo_bpm,
1740 });
1741 }
1742
1743 for pi in 0..a.parts.len().min(b.parts.len()) {
1744 let ap = &a.parts[pi];
1745 let bp = &b.parts[pi];
1746 for si in 0..ap.staves.len().min(bp.staves.len()) {
1747 let a_staff = &ap.staves[si];
1748 let b_staff = &bp.staves[si];
1749 for mi in 0..a_staff.measures.len().min(b_staff.measures.len()) {
1750 let am = &a_staff.measures[mi];
1751 let bm = &b_staff.measures[mi];
1752
1753 if am.key_sig != bm.key_sig {
1754 patches.push(ScorePatch::SetKeySignature {
1755 part: pi,
1756 staff: si,
1757 measure: mi,
1758 value: bm.key_sig.clone(),
1759 });
1760 }
1761 if am.time_sig != bm.time_sig {
1762 patches.push(ScorePatch::SetTimeSignature {
1763 part: pi,
1764 staff: si,
1765 measure: mi,
1766 value: bm.time_sig.clone(),
1767 });
1768 }
1769 if am.barline_left != bm.barline_left || am.barline_right != bm.barline_right {
1770 patches.push(ScorePatch::SetBarlines {
1771 part: pi,
1772 staff: si,
1773 measure: mi,
1774 left: bm.barline_left.clone(),
1775 right: bm.barline_right.clone(),
1776 });
1777 }
1778 if am.rehearsal != bm.rehearsal {
1779 patches.push(ScorePatch::SetRehearsal {
1780 part: pi,
1781 staff: si,
1782 measure: mi,
1783 value: bm.rehearsal.clone(),
1784 });
1785 }
1786 if am.volta != bm.volta {
1787 patches.push(ScorePatch::SetVolta {
1788 part: pi,
1789 staff: si,
1790 measure: mi,
1791 value: bm.volta.clone(),
1792 });
1793 }
1794 if am.tempo != bm.tempo {
1795 patches.push(ScorePatch::SetMeasureTempo {
1796 part: pi,
1797 staff: si,
1798 measure: mi,
1799 value: bm.tempo,
1800 });
1801 }
1802
1803 for vi in 0..4usize {
1804 let av = &am.voices[vi];
1805 let bv = &bm.voices[vi];
1806 for (ni, (a_note, b_note)) in av.iter().zip(bv.iter()).enumerate() {
1807 if !note_content_eq(a_note, b_note) {
1808 patches.push(ScorePatch::ReplaceNote {
1809 part: pi,
1810 staff: si,
1811 measure: mi,
1812 voice: vi,
1813 note_index: ni,
1814 note: Box::new(b_note.clone()),
1815 });
1816 }
1817 }
1818 for ni in (bv.len()..av.len()).rev() {
1820 patches.push(ScorePatch::RemoveNote {
1821 part: pi,
1822 staff: si,
1823 measure: mi,
1824 voice: vi,
1825 note_index: ni,
1826 });
1827 }
1828 for (offset, note) in bv.iter().skip(av.len()).enumerate() {
1830 patches.push(ScorePatch::AddNote {
1831 part: pi,
1832 staff: si,
1833 measure: mi,
1834 voice: vi,
1835 note_index: av.len() + offset,
1836 note: Box::new(note.clone()),
1837 });
1838 }
1839 }
1840 }
1841 }
1842 }
1843
1844 patches
1845}
1846
1847pub fn apply_patch(score: &Score, patches: &[ScorePatch]) -> Result<Score, Error> {
1852 let mut s = score.clone();
1853 for patch in patches {
1854 match patch {
1855 ScorePatch::ReplaceScore { score } => {
1856 s = (**score).clone();
1857 }
1858 ScorePatch::SetMetadata { field, value } => match field.as_str() {
1859 "title" => s.metadata.title = value.clone(),
1860 "composer" => s.metadata.composer = value.clone(),
1861 "lyricist" => s.metadata.lyricist = value.clone(),
1862 "copyright" => s.metadata.copyright = value.clone(),
1863 "work_number" => s.metadata.work_number = value.clone(),
1864 "movement_title" => s.metadata.movement_title = value.clone(),
1865 other => {
1866 return Err(Error::InvalidPatch(format!(
1867 "unknown metadata field: {other}"
1868 )));
1869 }
1870 },
1871 ScorePatch::SetTempo { value } => {
1872 s.settings.tempo_bpm = *value;
1873 }
1874 ScorePatch::SetKeySignature {
1875 part,
1876 staff,
1877 measure,
1878 value,
1879 } => {
1880 s.parts
1881 .get_mut(*part)
1882 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1883 .staves
1884 .get_mut(*staff)
1885 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1886 .measures
1887 .get_mut(*measure)
1888 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1889 .key_sig = value.clone();
1890 }
1891 ScorePatch::SetTimeSignature {
1892 part,
1893 staff,
1894 measure,
1895 value,
1896 } => {
1897 s.parts
1898 .get_mut(*part)
1899 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1900 .staves
1901 .get_mut(*staff)
1902 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1903 .measures
1904 .get_mut(*measure)
1905 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1906 .time_sig = value.clone();
1907 }
1908 ScorePatch::SetBarlines {
1909 part,
1910 staff,
1911 measure,
1912 left,
1913 right,
1914 } => {
1915 let m = s
1916 .parts
1917 .get_mut(*part)
1918 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1919 .staves
1920 .get_mut(*staff)
1921 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1922 .measures
1923 .get_mut(*measure)
1924 .ok_or_else(|| {
1925 Error::InvalidPatch(format!("measure {measure} out of range"))
1926 })?;
1927 m.barline_left = left.clone();
1928 m.barline_right = right.clone();
1929 }
1930 ScorePatch::SetRehearsal {
1931 part,
1932 staff,
1933 measure,
1934 value,
1935 } => {
1936 s.parts
1937 .get_mut(*part)
1938 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1939 .staves
1940 .get_mut(*staff)
1941 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1942 .measures
1943 .get_mut(*measure)
1944 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1945 .rehearsal = value.clone();
1946 }
1947 ScorePatch::SetVolta {
1948 part,
1949 staff,
1950 measure,
1951 value,
1952 } => {
1953 s.parts
1954 .get_mut(*part)
1955 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1956 .staves
1957 .get_mut(*staff)
1958 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1959 .measures
1960 .get_mut(*measure)
1961 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1962 .volta = value.clone();
1963 }
1964 ScorePatch::AddNote {
1965 part,
1966 staff,
1967 measure,
1968 voice,
1969 note_index,
1970 note,
1971 } => {
1972 let v = s
1973 .parts
1974 .get_mut(*part)
1975 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
1976 .staves
1977 .get_mut(*staff)
1978 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
1979 .measures
1980 .get_mut(*measure)
1981 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
1982 .voices
1983 .get_mut(*voice)
1984 .ok_or_else(|| Error::InvalidPatch(format!("voice {voice} out of range")))?;
1985 let insert_at = if *note_index == usize::MAX {
1986 v.len()
1987 } else {
1988 *note_index
1989 };
1990 if insert_at > v.len() {
1991 return Err(Error::InvalidPatch(format!(
1992 "note_index {note_index} out of range"
1993 )));
1994 }
1995 v.insert(insert_at, *note.clone());
1996 }
1997 ScorePatch::RemoveNote {
1998 part,
1999 staff,
2000 measure,
2001 voice,
2002 note_index,
2003 } => {
2004 let v = s
2005 .parts
2006 .get_mut(*part)
2007 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
2008 .staves
2009 .get_mut(*staff)
2010 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
2011 .measures
2012 .get_mut(*measure)
2013 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
2014 .voices
2015 .get_mut(*voice)
2016 .ok_or_else(|| Error::InvalidPatch(format!("voice {voice} out of range")))?;
2017 if *note_index >= v.len() {
2018 return Err(Error::InvalidPatch(format!(
2019 "note_index {note_index} out of range"
2020 )));
2021 }
2022 v.remove(*note_index);
2023 }
2024 ScorePatch::ReplaceNote {
2025 part,
2026 staff,
2027 measure,
2028 voice,
2029 note_index,
2030 note,
2031 } => {
2032 let v = s
2033 .parts
2034 .get_mut(*part)
2035 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
2036 .staves
2037 .get_mut(*staff)
2038 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
2039 .measures
2040 .get_mut(*measure)
2041 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
2042 .voices
2043 .get_mut(*voice)
2044 .ok_or_else(|| Error::InvalidPatch(format!("voice {voice} out of range")))?;
2045 if *note_index >= v.len() {
2046 return Err(Error::InvalidPatch(format!(
2047 "note_index {note_index} out of range"
2048 )));
2049 }
2050 v[*note_index] = *note.clone();
2051 }
2052 ScorePatch::SetMeasureTempo {
2053 part,
2054 staff,
2055 measure,
2056 value,
2057 } => {
2058 s.parts
2059 .get_mut(*part)
2060 .ok_or_else(|| Error::InvalidPatch(format!("part {part} out of range")))?
2061 .staves
2062 .get_mut(*staff)
2063 .ok_or_else(|| Error::InvalidPatch(format!("staff {staff} out of range")))?
2064 .measures
2065 .get_mut(*measure)
2066 .ok_or_else(|| Error::InvalidPatch(format!("measure {measure} out of range")))?
2067 .tempo = *value;
2068 }
2069 }
2070 }
2071 Ok(s)
2072}
2073
2074pub fn respell_score(score: &mut Score, prefer_flat: bool) {
2078 for part in &mut score.parts {
2079 for staff in &mut part.staves {
2080 for measure in &mut staff.measures {
2081 for voice in &mut measure.voices {
2082 for note in voice.iter_mut() {
2083 for pitch in &mut note.pitches {
2084 *pitch = pitch.respell(prefer_flat);
2085 }
2086 }
2087 }
2088 }
2089 }
2090 }
2091}
2092
2093pub fn respell_score_to_key(score: &mut Score) {
2097 let prefer_flat = score.settings.key_signature.fifths < 0;
2098 respell_score(score, prefer_flat);
2099}
2100
2101pub fn score_duration_secs(score: &Score) -> f64 {
2106 if score.settings.tempo_bpm == 0 {
2107 return 0.0;
2108 }
2109 let seq = measure_sequence(score);
2110 let mut total_secs = 0.0f64;
2111 let mut current_bpm = score.settings.tempo_bpm as f64;
2112 if let Some(staff) = score.parts.first().and_then(|p| p.staves.first()) {
2113 for &idx in &seq {
2114 if let Some(m) = staff.measures.get(idx) {
2115 if let Some(b) = m.tempo {
2116 current_bpm = b as f64;
2117 }
2118 if current_bpm == 0.0 {
2119 continue;
2120 }
2121 let beats: f64 = m.voices[0].iter().map(|n| n.beats()).sum();
2122 total_secs += beats / current_bpm * 60.0;
2123 }
2124 }
2125 }
2126 total_secs
2127}
2128
2129pub fn score_duration_secs_region(score: &Score, region: (usize, usize)) -> f64 {
2134 if score.settings.tempo_bpm == 0 {
2135 return 0.0;
2136 }
2137 let seq: Vec<usize> = measure_sequence(score)
2138 .into_iter()
2139 .filter(|&idx| idx >= region.0 && idx <= region.1)
2140 .collect();
2141 let mut total_secs = 0.0f64;
2142 let mut current_bpm = score.settings.tempo_bpm as f64;
2143 if let Some(staff) = score.parts.first().and_then(|p| p.staves.first()) {
2144 for &idx in &seq {
2145 if let Some(m) = staff.measures.get(idx) {
2146 if let Some(b) = m.tempo {
2147 current_bpm = b as f64;
2148 }
2149 if current_bpm == 0.0 {
2150 continue;
2151 }
2152 let beats: f64 = m.voices[0].iter().map(|n| n.beats()).sum();
2153 total_secs += beats / current_bpm * 60.0;
2154 }
2155 }
2156 }
2157 total_secs
2158}
2159
2160pub fn measure_beats_remaining(
2165 score: &Score,
2166 part_index: usize,
2167 staff_index: usize,
2168 measure_index: usize,
2169 voice_index: usize,
2170) -> Result<f64, Error> {
2171 let part = score
2172 .parts
2173 .get(part_index)
2174 .ok_or(Error::PartNotFound(part_index))?;
2175 let staff = part
2176 .staves
2177 .get(staff_index)
2178 .ok_or(Error::StaffNotFound(staff_index))?;
2179 let measure = staff
2180 .measures
2181 .get(measure_index)
2182 .ok_or(Error::MeasureNotFound(measure_index))?;
2183 let voice = measure
2184 .voices
2185 .get(voice_index)
2186 .ok_or(Error::VoiceOutOfRange(voice_index))?;
2187 let ts = measure
2188 .time_sig
2189 .as_ref()
2190 .unwrap_or(&score.settings.time_signature);
2191 let used: f64 = voice.iter().map(|n| n.beats()).sum();
2192 Ok((ts.total_beats() - used).max(0.0))
2193}
2194
2195pub fn suggested_stem_up(pitches: &[Pitch], clef: &Clef) -> bool {
2201 if pitches.is_empty() {
2202 return true;
2203 }
2204 let avg = pitches.iter().map(|p| p.to_midi() as f64).sum::<f64>() / pitches.len() as f64;
2205 avg < clef.middle_line_midi() as f64
2206}
2207
2208fn beam_beat_size(ts: &TimeSignature) -> f64 {
2209 if ts.numerator.is_multiple_of(3) && ts.numerator >= 6 && ts.denominator >= 8 {
2210 3.0 * 4.0 / ts.denominator as f64
2211 } else {
2212 4.0 / ts.denominator as f64
2213 }
2214}
2215
2216pub fn compute_beams(notes: &[Note], time_sig: &TimeSignature) -> Vec<BeamState> {
2221 let beat_size = beam_beat_size(time_sig);
2222 let n = notes.len();
2223 let mut result = vec![BeamState::None; n];
2224
2225 let is_beamable = |note: &Note| -> bool {
2226 !note.is_rest
2227 && matches!(
2228 note.duration,
2229 Duration::Eighth
2230 | Duration::Sixteenth
2231 | Duration::ThirtySecond
2232 | Duration::SixtyFourth
2233 )
2234 };
2235
2236 let mut starts = Vec::with_capacity(n);
2238 let mut pos = 0.0f64;
2239 for note in notes {
2240 starts.push(pos);
2241 pos += note.beats();
2242 }
2243
2244 let group_id = |i: usize| -> i64 { (starts[i] / beat_size).floor() as i64 };
2246
2247 let mut i = 0;
2248 while i < n {
2249 if !is_beamable(¬es[i]) {
2250 i += 1;
2251 continue;
2252 }
2253 let g = group_id(i);
2254 let mut j = i;
2256 while j < n && is_beamable(¬es[j]) && group_id(j) == g {
2257 j += 1;
2258 }
2259 let run = j - i;
2260 if run == 1 {
2261 result[i] = BeamState::None;
2262 } else {
2263 result[i] = BeamState::Begin;
2264 result[i + 1..j - 1].fill(BeamState::Continue);
2265 result[j - 1] = BeamState::End;
2266 }
2267 i = j;
2268 }
2269 result
2270}
2271
2272fn note_content_eq(a: &Note, b: &Note) -> bool {
2273 a.is_rest == b.is_rest
2274 && a.is_unpitched == b.is_unpitched
2275 && a.instrument_id == b.instrument_id
2276 && a.pitches == b.pitches
2277 && a.duration == b.duration
2278 && a.dot_count == b.dot_count
2279 && a.tie_start == b.tie_start
2280 && a.tie_end == b.tie_end
2281 && a.beam == b.beam
2282 && a.articulations == b.articulations
2283 && a.dynamic == b.dynamic
2284 && a.stem_up == b.stem_up
2285 && a.hairpin_start == b.hairpin_start
2286 && a.hairpin_end == b.hairpin_end
2287 && a.tuplet == b.tuplet
2288 && a.chord_symbol == b.chord_symbol
2289 && a.is_grace == b.is_grace
2290 && a.grace_slash == b.grace_slash
2291 && a.ottava_start == b.ottava_start
2292 && a.ottava_end == b.ottava_end
2293 && a.lyric == b.lyric
2294 && a.pedal_start == b.pedal_start
2295 && a.pedal_end == b.pedal_end
2296 && a.slur_start == b.slur_start
2297 && a.slur_end == b.slur_end
2298 && a.arpeggiate == b.arpeggiate
2299 && a.tab_position == b.tab_position
2300 && a.tab_positions == b.tab_positions
2301 && a.guitar_technique == b.guitar_technique
2302 && a.guitar_bend_alter_cents == b.guitar_bend_alter_cents
2303}
2304
2305#[cfg(test)]
2306mod tests {
2307 use super::*;
2308 use crate::model::{notation::FingeringSelectionPolicy, pitch::Step};
2309
2310 #[test]
2311 fn fingering_selection_policy_is_deterministic_and_non_mutating() {
2312 let mut note = Note::new(Pitch::new(Step::C, 4), Duration::Quarter);
2313 note.fingerings = vec![3, 1, 4];
2314 note.fingering = Some(3);
2315 assert_eq!(
2316 note.select_fingering(FingeringSelectionPolicy::SourceOrder),
2317 Some(3)
2318 );
2319 assert_eq!(
2320 note.select_fingering(FingeringSelectionPolicy::LowestNumber),
2321 Some(1)
2322 );
2323 assert_eq!(
2324 note.select_fingering(FingeringSelectionPolicy::HighestNumber),
2325 Some(4)
2326 );
2327 assert_eq!(note.fingerings, vec![3, 1, 4]);
2328 assert_eq!(note.fingering, Some(3));
2329 }
2330
2331 #[test]
2332 fn default_score_has_one_part_four_measures() {
2333 let score = Score::default();
2334 assert_eq!(score.parts.len(), 1);
2335 assert_eq!(score.parts[0].staves.len(), 1);
2336 assert_eq!(score.parts[0].staves[0].measures.len(), 4);
2337 }
2338
2339 #[test]
2340 fn assign_tablature_positions_is_capo_aware_and_preserves_explicit_positions() {
2341 let mut score = Score::new("Guitar", 120, 4, 4, 0, 1);
2342 score.parts[0].staves[0].tablature = Some(TablatureConfig {
2343 lines: 6,
2344 tuning_midi: vec![64, 59, 55, 50, 45, 40],
2345 capo: 2,
2346 });
2347 score.parts[0].staves[0].measures[0].voices[0].push(Note::new(
2348 Pitch::with_alter(Step::F, 4, 1),
2349 Duration::Quarter,
2350 ));
2351 score.parts[0].staves[0].measures[0].voices[0]
2352 .push(Note::new(Pitch::new(Step::G, 3), Duration::Quarter));
2353 score.parts[0].staves[0].measures[0].voices[0][2].tab_position =
2354 Some(TabPosition { string: 6, fret: 7 });
2355
2356 assert_eq!(assign_tablature_positions(&mut score), 1);
2357 let notes = &score.parts[0].staves[0].measures[0].voices[0];
2358 assert_eq!(
2359 notes[1].tab_position,
2360 Some(TabPosition { string: 1, fret: 0 })
2361 );
2362 assert_eq!(notes[1].string_number, Some(1));
2363 assert_eq!(
2364 notes[2].tab_position,
2365 Some(TabPosition { string: 6, fret: 7 })
2366 );
2367 }
2368
2369 #[test]
2370 fn assign_tablature_positions_optimizes_chord_strings_and_fret_span() {
2371 let mut score = Score::new("Guitar", 120, 4, 4, 0, 1);
2372 score.parts[0].staves[0].tablature = Some(TablatureConfig {
2373 lines: 6,
2374 tuning_midi: vec![64, 59, 55, 50, 45, 40],
2375 capo: 0,
2376 });
2377 let mut chord = Note::new(Pitch::new(Step::E, 4), Duration::Quarter);
2378 chord.pitches.push(Pitch::new(Step::G, 4));
2379 score.parts[0].staves[0].measures[0].voices[0].push(chord);
2380
2381 assert_eq!(assign_tablature_positions(&mut score), 1);
2382 let positions = &score.parts[0].staves[0].measures[0].voices[0][1].tab_positions;
2383 assert_eq!(
2384 positions,
2385 &vec![
2386 TabPosition { string: 2, fret: 5 },
2387 TabPosition { string: 1, fret: 3 },
2388 ]
2389 );
2390 }
2391
2392 #[test]
2393 fn new_score_measure_count() {
2394 let score = Score::new("Test", 120, 4, 4, 0, 8);
2395 assert_eq!(score.measure_count(), 8);
2396 }
2397
2398 #[test]
2399 fn note_beats_quarter() {
2400 let note = Note::new(Pitch::new(Step::C, 4), Duration::Quarter);
2401 assert!((note.beats() - 1.0).abs() < 1e-9);
2402 }
2403
2404 #[test]
2405 fn note_beats_dotted_quarter() {
2406 let mut note = Note::new(Pitch::new(Step::C, 4), Duration::Quarter);
2407 note.dot_count = 1;
2408 assert!((note.beats() - 1.5).abs() < 1e-9);
2409 }
2410
2411 #[test]
2412 fn grace_note_beats_zero() {
2413 let mut note = Note::new(Pitch::new(Step::C, 4), Duration::Eighth);
2414 note.is_grace = true;
2415 assert_eq!(note.beats(), 0.0);
2416 }
2417
2418 #[test]
2419 fn measure_empty_4_4_fills_four_beats() {
2420 let m = Measure::empty(4, 4);
2421 let total: f64 = m.voices[0].iter().map(|n| n.beats()).sum();
2422 assert!((total - 4.0).abs() < 1e-9);
2423 }
2424
2425 #[test]
2426 fn measure_empty_3_4_fills_three_beats() {
2427 let m = Measure::empty(3, 4);
2428 let total: f64 = m.voices[0].iter().map(|n| n.beats()).sum();
2429 assert!((total - 3.0).abs() < 1e-9);
2430 }
2431
2432 #[test]
2433 fn whole_filling_beats() {
2434 assert_eq!(Duration::whole_filling_beats(4.0), Duration::Whole);
2435 assert_eq!(Duration::whole_filling_beats(2.0), Duration::Half);
2436 assert_eq!(Duration::whole_filling_beats(1.0), Duration::Quarter);
2437 }
2438
2439 #[test]
2442 fn statistics_default_score_all_rests() {
2443 let score = Score::default();
2444 let s = score.statistics();
2445 assert_eq!(s.part_count, 1);
2446 assert_eq!(s.measure_count, 4);
2447 assert_eq!(s.note_count, 0);
2448 assert!(s.rest_count > 0);
2449 }
2450
2451 #[test]
2452 fn statistics_duration_estimate() {
2453 let score = Score::new("T", 120, 4, 4, 0, 1);
2455 let s = score.statistics();
2456 assert!((s.estimated_duration_secs - 2.0).abs() < 0.01);
2457 }
2458
2459 #[test]
2460 fn score_duration_secs_matches_statistics() {
2461 use super::score_duration_secs;
2462 let score = Score::new("T", 120, 4, 4, 0, 4);
2463 let secs = score_duration_secs(&score);
2464 assert!((secs - 8.0).abs() < 0.01, "expected ~8.0 s, got {secs}");
2466 }
2467
2468 #[test]
2469 fn score_duration_secs_zero_bpm_returns_zero() {
2470 use super::score_duration_secs;
2471 let mut score = Score::new("T", 120, 4, 4, 0, 1);
2472 score.settings.tempo_bpm = 0;
2473 assert_eq!(score_duration_secs(&score), 0.0);
2474 }
2475
2476 #[test]
2477 fn score_duration_secs_per_measure_tempo() {
2478 use super::score_duration_secs;
2479 let mut score = Score::new("T", 120, 4, 4, 0, 2);
2481 score.parts[0].staves[0].measures[1].tempo = Some(60);
2482 let secs = score_duration_secs(&score);
2483 assert!((secs - 6.0).abs() < 0.01, "expected ~6.0 s, got {secs}");
2484 }
2485
2486 #[test]
2489 fn extract_part_returns_single_part_score() {
2490 let mut score = Score::default();
2491 let mut p2 = Part::new("Violin", "Vln.");
2492 p2.staves.push(Staff::new(Clef::Treble));
2493 score.parts.push(p2);
2494 let ex = score.extract_part(0).unwrap();
2495 assert_eq!(ex.parts.len(), 1);
2496 assert_ne!(ex.id, score.id);
2497 assert_eq!(ex.metadata.title, score.metadata.title);
2498 }
2499
2500 #[test]
2501 fn extract_part_out_of_range_is_none() {
2502 let score = Score::default();
2503 assert!(score.extract_part(99).is_none());
2504 }
2505
2506 #[test]
2509 fn transpose_zero_is_clone() {
2510 let score = Score::new("T", 120, 4, 4, 0, 1);
2511 let t = transpose(&score, 0);
2512 assert_eq!(t.settings.key_signature.fifths, 0);
2513 }
2514
2515 #[test]
2516 fn transpose_c_major_up_2_to_d_major() {
2517 let score = Score::new("T", 120, 4, 4, 0, 1);
2518 assert_eq!(transpose(&score, 2).settings.key_signature.fifths, 2);
2519 }
2520
2521 #[test]
2522 fn transpose_d_major_up_5_to_g_major() {
2523 let score = Score::new("T", 120, 4, 4, 2, 1);
2524 assert_eq!(transpose(&score, 5).settings.key_signature.fifths, 1);
2525 }
2526
2527 #[test]
2528 fn transpose_c4_up_1_to_csharp4() {
2529 let p = transpose_pitch(&Pitch::new(Step::C, 4), 1);
2530 assert_eq!(p.to_midi(), 61);
2531 assert_eq!(p.step, Step::C);
2532 assert_eq!(p.alter, 1);
2533 }
2534
2535 #[test]
2536 fn transpose_c4_down_1_to_b3() {
2537 let p = transpose_pitch(&Pitch::new(Step::C, 4), -1);
2538 assert_eq!(p.to_midi(), 59);
2539 assert_eq!(p.step, Step::B);
2540 assert_eq!(p.alter, 0);
2541 }
2542
2543 #[test]
2544 fn transpose_up_octave_keeps_step() {
2545 let p = transpose_pitch(&Pitch::new(Step::A, 4), 12);
2546 assert_eq!(p.to_midi(), 81);
2547 assert_eq!(p.step, Step::A);
2548 assert_eq!(p.octave, 5);
2549 }
2550
2551 #[test]
2552 fn statistics_with_repeat_doubles_duration() {
2553 let mut score = Score::new("T", 120, 4, 4, 0, 2);
2555 score.parts[0].staves[0].measures[0].barline_left =
2556 crate::model::notation::Barline::RepeatStart;
2557 score.parts[0].staves[0].measures[1].barline_right =
2558 crate::model::notation::Barline::RepeatEnd;
2559 let s = score.statistics();
2560 assert!((s.estimated_duration_secs - 8.0).abs() < 0.01);
2562 }
2563
2564 #[test]
2565 fn transpose_octave_boundary_b4_to_c5() {
2566 let p = transpose_pitch(&Pitch::new(Step::B, 4), 1);
2568 assert_eq!(p.to_midi(), 72);
2569 assert_eq!(p.step, Step::C);
2570 assert_eq!(p.octave, 5);
2571 }
2572
2573 #[test]
2574 fn transpose_clamp_at_midi_127() {
2575 let p = transpose_pitch(&Pitch::new(Step::G, 9), 3);
2577 assert_eq!(p.to_midi(), 127);
2578 }
2579
2580 #[test]
2583 fn merge_combines_parts() {
2584 let mut a = Score::new("A", 120, 4, 4, 0, 2);
2585 let b = Score::new("B", 120, 4, 4, 0, 2);
2586 let mut p2 = Part::new("Violin", "Vln.");
2588 p2.staves.push(Staff::new(Clef::Treble));
2589 for i in 0..2usize {
2590 let mut m = Measure::empty(4, 4);
2591 m.number = i as u32 + 1;
2592 p2.staves[0].measures.push(m);
2593 }
2594 a.parts.push(p2);
2595 let merged = a.merge(&b);
2596 assert_eq!(merged.parts.len(), 3);
2598 }
2599
2600 #[test]
2601 fn merge_pads_shorter_score() {
2602 let a = Score::new("A", 120, 4, 4, 0, 4);
2603 let b = Score::new("B", 120, 4, 4, 0, 2);
2604 let merged = a.merge(&b);
2605 assert_eq!(merged.parts[0].staves[0].measures.len(), 4);
2607 assert_eq!(merged.parts[1].staves[0].measures.len(), 4);
2608 }
2609
2610 #[test]
2611 fn merge_uses_self_metadata() {
2612 let mut a = Score::new("Title A", 120, 4, 4, 0, 2);
2613 a.metadata.composer = "Composer A".to_string();
2614 let b = Score::new("Title B", 120, 4, 4, 0, 2);
2615 let merged = a.merge(&b);
2616 assert_eq!(merged.metadata.title, "Title A");
2617 assert_eq!(merged.metadata.composer, "Composer A");
2618 }
2619
2620 #[test]
2621 fn merge_new_id_differs_from_both() {
2622 let a = Score::new("A", 120, 4, 4, 0, 2);
2623 let b = Score::new("B", 120, 4, 4, 0, 2);
2624 let merged = a.merge(&b);
2625 assert_ne!(merged.id, a.id);
2626 assert_ne!(merged.id, b.id);
2627 }
2628
2629 #[test]
2632 fn staff_default_transpose_is_zero() {
2633 let s = Staff::new(Clef::Treble);
2634 assert_eq!(s.transpose_semitones, 0);
2635 }
2636
2637 #[test]
2640 fn score_default_has_schema_version_1() {
2641 let score = Score::default();
2642 assert_eq!(score.schema_version, 1);
2643 }
2644
2645 #[test]
2646 fn score_new_has_schema_version_1() {
2647 let score = Score::new("T", 120, 4, 4, 0, 4);
2648 assert_eq!(score.schema_version, 1);
2649 }
2650
2651 #[test]
2652 fn score_without_schema_version_deserializes_to_zero() {
2653 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":[]}"#;
2654 let score: Score = serde_json::from_str(json).unwrap();
2655 assert_eq!(score.schema_version, 0);
2656 }
2657
2658 #[test]
2659 fn note_without_new_percussion_fields_uses_serde_defaults() {
2660 let note = Note::new(Pitch::new(Step::C, 4), Duration::Quarter);
2661 let mut value = serde_json::to_value(note).unwrap();
2662 let object = value.as_object_mut().unwrap();
2663 object.remove("is_unpitched");
2664 object.remove("instrument_id");
2665 let restored: Note = serde_json::from_value(value).unwrap();
2666 assert!(!restored.is_unpitched);
2667 assert_eq!(restored.instrument_id, None);
2668 }
2669
2670 #[test]
2671 fn percussion_instrument_resolution_prefers_id_then_display_key() {
2672 let mut part = Part::new("Drums", "Dr.");
2673 part.percussion_instruments = vec![
2674 PercussionInstrument {
2675 id: "snare".to_string(),
2676 name: Some("Acoustic Snare".to_string()),
2677 midi_unpitched: Some(38),
2678 },
2679 PercussionInstrument {
2680 id: "rim".to_string(),
2681 name: Some("Side Stick".to_string()),
2682 midi_unpitched: Some(37),
2683 },
2684 ];
2685 let mut note = Note::new(Pitch::from_midi(38, false), Duration::Quarter);
2686 note.is_unpitched = true;
2687 assert_eq!(
2688 part.percussion_instrument_for_note(¬e)
2689 .map(|instrument| instrument.id.as_str()),
2690 Some("snare")
2691 );
2692 note.instrument_id = Some("rim".to_string());
2693 assert_eq!(
2694 part.percussion_instrument_for_note(¬e)
2695 .map(|instrument| instrument.id.as_str()),
2696 Some("rim")
2697 );
2698 note.instrument_id = Some("missing".to_string());
2699 assert!(part.percussion_instrument_for_note(¬e).is_none());
2700 note.instrument_id = None;
2701 note.is_unpitched = false;
2702 assert!(part.percussion_instrument_for_note(¬e).is_none());
2703 }
2704
2705 #[test]
2708 fn score_template_solo_has_one_part_treble() {
2709 let score = Score::template(ScoreTemplate::Solo);
2710 assert_eq!(score.parts.len(), 1);
2711 assert_eq!(score.parts[0].staves.len(), 1);
2712 assert_eq!(score.parts[0].staves[0].clef, Clef::Treble);
2713 assert_eq!(score.parts[0].midi_program, 0);
2714 }
2715
2716 #[test]
2717 fn score_template_piano_has_two_staves() {
2718 let score = Score::template(ScoreTemplate::Piano);
2719 assert_eq!(score.parts.len(), 1);
2720 assert_eq!(score.parts[0].staves.len(), 2);
2721 assert_eq!(score.parts[0].staves[0].clef, Clef::Treble);
2722 assert_eq!(score.parts[0].staves[1].clef, Clef::Bass);
2723 }
2724
2725 #[test]
2726 fn score_template_string_quartet_has_four_parts() {
2727 let score = Score::template(ScoreTemplate::StringQuartet);
2728 assert_eq!(score.parts.len(), 4);
2729 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);
2732 assert_eq!(score.parts[3].midi_program, 42);
2733 }
2734
2735 #[test]
2736 fn score_template_string_orchestra_has_five_parts() {
2737 let score = Score::template(ScoreTemplate::StringOrchestra);
2738 assert_eq!(score.parts.len(), 5);
2739 assert_eq!(score.parts[4].midi_program, 43); }
2741
2742 #[test]
2743 fn score_template_brass_quintet_has_five_parts() {
2744 let score = Score::template(ScoreTemplate::BrassQuintet);
2745 assert_eq!(score.parts.len(), 5);
2746 assert_eq!(score.parts[2].midi_program, 60); }
2748
2749 #[test]
2750 fn score_template_default_measures_are_four() {
2751 let score = Score::template(ScoreTemplate::StringQuartet);
2752 for part in &score.parts {
2753 for staff in &part.staves {
2754 assert_eq!(staff.measures.len(), 4);
2755 }
2756 }
2757 }
2758
2759 #[test]
2762 fn measure_empty_has_no_breaks() {
2763 let m = Measure::empty(4, 4);
2764 assert!(!m.system_break);
2765 assert!(!m.page_break);
2766 }
2767
2768 #[test]
2769 fn system_break_survives_json_roundtrip() {
2770 let mut m = Measure::empty(4, 4);
2771 m.system_break = true;
2772 let json = serde_json::to_string(&m).unwrap();
2773 let m2: Measure = serde_json::from_str(&json).unwrap();
2774 assert!(m2.system_break);
2775 assert!(!m2.page_break);
2776 }
2777
2778 #[test]
2781 fn diff_identical_scores_is_empty() {
2782 let s = Score::new("T", 120, 4, 4, 0, 2);
2783 assert!(diff(&s, &s).is_empty());
2784 }
2785
2786 #[test]
2787 fn score_patch_covers_measure_semantics_and_note_insert_index() {
2788 let a = Score::new("T", 120, 4, 4, 0, 1);
2789 let mut b = a.clone();
2790 let measure = &mut b.parts[0].staves[0].measures[0];
2791 measure.key_sig = Some(KeySignature {
2792 fifths: -2,
2793 mode: "major".to_string(),
2794 });
2795 measure.time_sig = Some(TimeSignature {
2796 numerator: 3,
2797 denominator: 4,
2798 });
2799 measure.barline_left = Barline::RepeatStart;
2800 measure.barline_right = Barline::RepeatEnd;
2801 measure.rehearsal = Some("A".to_string());
2802 measure.volta = Some(VoltaBracket {
2803 number: 1,
2804 kind: "begin_end".to_string(),
2805 });
2806 measure.voices[0].insert(0, Note::new(Pitch::new(Step::C, 4), Duration::Quarter));
2807 let expected = b.parts[0].staves[0].measures[0].clone();
2808
2809 let patches = score_patch(&a, &b);
2810 assert!(
2811 patches
2812 .iter()
2813 .any(|p| matches!(p, ScorePatch::SetTimeSignature { .. }))
2814 );
2815 assert!(
2816 patches
2817 .iter()
2818 .any(|p| matches!(p, ScorePatch::SetBarlines { .. }))
2819 );
2820 assert!(
2821 patches
2822 .iter()
2823 .any(|p| matches!(p, ScorePatch::SetRehearsal { .. }))
2824 );
2825 assert!(
2826 patches
2827 .iter()
2828 .any(|p| matches!(p, ScorePatch::SetVolta { .. }))
2829 );
2830 let result = apply_patch(&a, &patches).expect("patch application failed");
2831 let result_measure = &result.parts[0].staves[0].measures[0];
2832 assert_eq!(result_measure.key_sig, expected.key_sig);
2833 assert_eq!(result_measure.time_sig, expected.time_sig);
2834 assert_eq!(result_measure.barline_left, expected.barline_left);
2835 assert_eq!(result_measure.barline_right, expected.barline_right);
2836 assert_eq!(result_measure.rehearsal, expected.rehearsal);
2837 assert_eq!(result_measure.volta, expected.volta);
2838 assert_eq!(result_measure.voices[0].len(), expected.voices[0].len());
2839 }
2840
2841 #[test]
2842 fn score_patch_replaces_when_structure_or_uncovered_fields_change() {
2843 let a = Score::new("T", 120, 4, 4, 0, 1);
2844 let mut b = a.clone();
2845 b.parts[0].name = "Piano".to_string();
2846 b.parts[0].staves[0].measures[0].expression_text = Some("dolce".to_string());
2847 let patches = score_patch(&a, &b);
2848 assert!(matches!(
2849 patches.as_slice(),
2850 [ScorePatch::ReplaceScore { .. }]
2851 ));
2852 let result = apply_patch(&a, &patches).expect("replacement failed");
2853 assert_eq!(result.parts[0].name, "Piano");
2854 assert_eq!(
2855 result.parts[0].staves[0].measures[0].expression_text,
2856 Some("dolce".to_string())
2857 );
2858 }
2859
2860 #[test]
2861 fn diff_detects_tempo_change() {
2862 let a = Score::new("T", 120, 4, 4, 0, 1);
2863 let mut b = a.clone();
2864 b.settings.tempo_bpm = 90;
2865 let changes = diff(&a, &b);
2866 assert_eq!(changes.len(), 1);
2867 assert!(matches!(
2868 changes[0],
2869 ScoreChange::TempoChanged { old: 120, new: 90 }
2870 ));
2871 }
2872
2873 #[test]
2874 fn diff_detects_title_change() {
2875 let a = Score::new("Old Title", 120, 4, 4, 0, 1);
2876 let mut b = a.clone();
2877 b.metadata.title = "New Title".to_string();
2878 let changes = diff(&a, &b);
2879 assert!(
2880 changes.iter().any(
2881 |c| matches!(c, ScoreChange::MetadataChanged { field, .. } if field == "title")
2882 )
2883 );
2884 }
2885
2886 #[test]
2887 fn diff_detects_note_modification() {
2888 let mut a = Score::new("T", 120, 4, 4, 0, 1);
2889 a.parts[0].staves[0].measures[0].voices[0] =
2890 vec![Note::new(Pitch::new(Step::C, 4), Duration::Quarter)];
2891 let mut b = a.clone();
2892 b.parts[0].staves[0].measures[0].voices[0][0] =
2893 Note::new(Pitch::new(Step::D, 4), Duration::Quarter);
2894 let changes = diff(&a, &b);
2895 assert!(
2896 changes
2897 .iter()
2898 .any(|c| matches!(c, ScoreChange::NoteModified { .. }))
2899 );
2900 }
2901
2902 #[test]
2903 fn diff_detects_part_added() {
2904 let a = Score::new("T", 120, 4, 4, 0, 1);
2905 let mut b = a.clone();
2906 let mut p = Part::new("Violin", "Vln.");
2907 p.staves.push(Staff::new(Clef::Treble));
2908 b.parts.push(p);
2909 let changes = diff(&a, &b);
2910 assert!(
2911 changes
2912 .iter()
2913 .any(|c| matches!(c, ScoreChange::PartAdded { part_index: 1 }))
2914 );
2915 }
2916
2917 #[test]
2918 fn diff_detects_measure_tempo_change() {
2919 let a = Score::new("T", 120, 4, 4, 0, 2);
2920 let mut b = a.clone();
2921 b.parts[0].staves[0].measures[1].tempo = Some(60);
2922 let changes = diff(&a, &b);
2923 assert!(changes.iter().any(|c| matches!(
2924 c,
2925 ScoreChange::MeasureTempoChanged {
2926 measure: 1,
2927 old: None,
2928 new: Some(60),
2929 ..
2930 }
2931 )));
2932 }
2933
2934 #[test]
2935 fn diff_detects_barline_change() {
2936 use crate::model::notation::Barline;
2937 let a = Score::new("T", 120, 4, 4, 0, 2);
2938 let mut b = a.clone();
2939 b.parts[0].staves[0].measures[0].barline_left = Barline::RepeatStart;
2940 let changes = diff(&a, &b);
2941 assert!(
2942 changes
2943 .iter()
2944 .any(|c| matches!(c, ScoreChange::BarlineChanged { measure: 0, .. }))
2945 );
2946 }
2947
2948 #[test]
2949 fn diff_detects_rehearsal_change() {
2950 let a = Score::new("T", 120, 4, 4, 0, 2);
2951 let mut b = a.clone();
2952 b.parts[0].staves[0].measures[0].rehearsal = Some("A".to_string());
2953 let changes = diff(&a, &b);
2954 assert!(
2955 changes
2956 .iter()
2957 .any(|c| matches!(c, ScoreChange::RehearsalMarkChanged { measure: 0, .. }))
2958 );
2959 }
2960
2961 #[test]
2962 fn diff_detects_volta_change() {
2963 use super::VoltaBracket;
2964 let a = Score::new("T", 120, 4, 4, 0, 2);
2965 let mut b = a.clone();
2966 b.parts[0].staves[0].measures[0].volta = Some(VoltaBracket {
2967 number: 1,
2968 kind: "begin_end".into(),
2969 });
2970 let changes = diff(&a, &b);
2971 assert!(
2972 changes
2973 .iter()
2974 .any(|c| matches!(c, ScoreChange::VoltaChanged { measure: 0, .. }))
2975 );
2976 }
2977
2978 #[test]
2979 fn diff_detects_key_signature_change() {
2980 let a = Score::new("T", 120, 4, 4, 0, 1);
2981 let mut b = a.clone();
2982 b.settings.key_signature.fifths = 2; let changes = diff(&a, &b);
2984 assert!(
2985 changes
2986 .iter()
2987 .any(|c| matches!(c, ScoreChange::KeySignatureChanged { .. }))
2988 );
2989 }
2990
2991 #[test]
2992 fn diff_same_key_signature_no_change() {
2993 let a = Score::new("T", 120, 4, 4, 2, 1);
2994 let changes = diff(&a, &a);
2995 assert!(changes.is_empty());
2996 }
2997
2998 #[test]
2999 fn score_duration_secs_region_partial() {
3000 use super::score_duration_secs_region;
3001 let score = Score::new("T", 120, 4, 4, 0, 4);
3003 let secs = score_duration_secs_region(&score, (1, 2));
3004 assert!((secs - 4.0).abs() < 0.01, "expected ~4.0 s, got {secs}");
3005 }
3006
3007 #[test]
3008 fn score_duration_secs_region_single_measure() {
3009 use super::score_duration_secs_region;
3010 let score = Score::new("T", 120, 4, 4, 0, 4);
3012 let secs = score_duration_secs_region(&score, (0, 0));
3013 assert!((secs - 2.0).abs() < 0.01, "expected ~2.0 s, got {secs}");
3014 }
3015
3016 #[test]
3019 fn measure_beats_remaining_empty_voice_returns_full() {
3020 use super::measure_beats_remaining;
3021 let mut score = Score::new("T", 120, 4, 4, 0, 1);
3022 score.parts[0].staves[0].measures[0].voices[0].clear();
3023 let rem = measure_beats_remaining(&score, 0, 0, 0, 0).unwrap();
3024 assert!(
3025 (rem - 4.0).abs() < 1e-9,
3026 "expected 4.0 remaining, got {rem}"
3027 );
3028 }
3029
3030 #[test]
3031 fn measure_beats_remaining_half_full_returns_half() {
3032 use super::measure_beats_remaining;
3033 use crate::model::pitch::Step;
3034 let mut score = Score::new("T", 120, 4, 4, 0, 1);
3035 score.parts[0].staves[0].measures[0].voices[0] = vec![
3036 Note::new(Pitch::new(Step::C, 4), Duration::Quarter),
3037 Note::new(Pitch::new(Step::D, 4), Duration::Quarter),
3038 ];
3039 let rem = measure_beats_remaining(&score, 0, 0, 0, 0).unwrap();
3040 assert!(
3041 (rem - 2.0).abs() < 1e-9,
3042 "expected 2.0 remaining, got {rem}"
3043 );
3044 }
3045
3046 #[test]
3047 fn measure_beats_remaining_full_voice_returns_zero() {
3048 use super::measure_beats_remaining;
3049 use crate::model::pitch::Step;
3050 let mut score = Score::new("T", 120, 4, 4, 0, 1);
3051 score.parts[0].staves[0].measures[0].voices[0] =
3052 vec![Note::new(Pitch::new(Step::C, 4), Duration::Whole)];
3053 let rem = measure_beats_remaining(&score, 0, 0, 0, 0).unwrap();
3054 assert!((rem).abs() < 1e-9, "expected 0.0 remaining, got {rem}");
3055 }
3056
3057 #[test]
3058 fn measure_beats_remaining_tuplet_accounting() {
3059 use super::measure_beats_remaining;
3060 use crate::model::notation::TupletInfo;
3061 use crate::model::pitch::Step;
3062 let mut score = Score::new("T", 120, 4, 4, 0, 1);
3064 let tuplet = TupletInfo {
3065 actual_notes: 3,
3066 normal_notes: 2,
3067 };
3068 let mk = |step| {
3069 let mut n = Note::new(Pitch::new(step, 4), Duration::Quarter);
3070 n.tuplet = Some(tuplet.clone());
3071 n
3072 };
3073 score.parts[0].staves[0].measures[0].voices[0] =
3074 vec![mk(Step::C), mk(Step::D), mk(Step::E)];
3075 let rem = measure_beats_remaining(&score, 0, 0, 0, 0).unwrap();
3076 assert!(
3077 (rem - 2.0).abs() < 1e-9,
3078 "expected 2.0 remaining (triplets used 2.0), got {rem}"
3079 );
3080 }
3081
3082 #[test]
3083 fn measure_beats_remaining_out_of_range_returns_err() {
3084 use super::measure_beats_remaining;
3085 let score = Score::new("T", 120, 4, 4, 0, 1);
3086 assert!(measure_beats_remaining(&score, 99, 0, 0, 0).is_err());
3087 assert!(measure_beats_remaining(&score, 0, 99, 0, 0).is_err());
3088 assert!(measure_beats_remaining(&score, 0, 0, 99, 0).is_err());
3089 assert!(measure_beats_remaining(&score, 0, 0, 0, 4).is_err());
3090 }
3091
3092 #[test]
3093 fn note_content_eq_ignores_id() {
3094 use crate::model::pitch::Step;
3095 let mut a = Note::new(Pitch::new(Step::C, 4), Duration::Quarter);
3096 let mut b = a.clone();
3097 b.id = "different-id".to_string();
3098 assert!(note_content_eq(&a, &b));
3099 b.pitches[0] = Pitch::new(Step::D, 4);
3101 assert!(!note_content_eq(&a, &b));
3102 let mut c = a.clone();
3104 a.stem_up = Some(true);
3105 c.stem_up = Some(false);
3106 assert!(!note_content_eq(&a, &c));
3107 }
3108
3109 #[test]
3110 fn suggested_stem_up_below_middle() {
3111 use crate::model::notation::Clef;
3112 let pitches = vec![Pitch::new(Step::C, 4)];
3114 assert!(suggested_stem_up(&pitches, &Clef::Treble));
3115 }
3116
3117 #[test]
3118 fn suggested_stem_up_above_middle() {
3119 use crate::model::notation::Clef;
3120 let pitches = vec![Pitch::new(Step::G, 5)];
3122 assert!(!suggested_stem_up(&pitches, &Clef::Treble));
3123 }
3124
3125 #[test]
3126 fn suggested_stem_up_at_middle_line() {
3127 use crate::model::notation::Clef;
3128 let pitches = vec![Pitch::new(Step::B, 4)];
3130 assert!(!suggested_stem_up(&pitches, &Clef::Treble));
3131 }
3132
3133 #[test]
3134 fn suggested_stem_up_chord() {
3135 use crate::model::notation::Clef;
3136 let pitches = vec![Pitch::new(Step::C, 4), Pitch::new(Step::G, 4)];
3138 assert!(suggested_stem_up(&pitches, &Clef::Treble));
3139 }
3140
3141 #[test]
3142 fn suggested_stem_up_bass_clef() {
3143 use crate::model::notation::Clef;
3144 let pitches = vec![Pitch::new(Step::D, 3)];
3146 assert!(!suggested_stem_up(&pitches, &Clef::Bass));
3147 let pitches2 = vec![Pitch::new(Step::C, 3)];
3149 assert!(suggested_stem_up(&pitches2, &Clef::Bass));
3150 }
3151
3152 #[test]
3153 fn suggested_stem_up_empty_pitches() {
3154 use crate::model::notation::Clef;
3155 assert!(suggested_stem_up(&[], &Clef::Treble));
3156 }
3157
3158 fn eighth(pitch: Pitch) -> Note {
3159 Note::new(pitch, Duration::Eighth)
3160 }
3161 fn quarter(pitch: Pitch) -> Note {
3162 Note::new(pitch, Duration::Quarter)
3163 }
3164 fn rest_eighth() -> Note {
3165 Note::rest(Duration::Eighth)
3166 }
3167
3168 #[test]
3169 fn compute_beams_4_4_four_eighths() {
3170 use crate::model::notation::{Clef, TimeSignature};
3171 let _ = Clef::Treble; let ts = TimeSignature {
3173 numerator: 4,
3174 denominator: 4,
3175 };
3176 let c4 = Pitch::new(Step::C, 4);
3177 let notes = vec![
3178 eighth(c4.clone()),
3179 eighth(c4.clone()),
3180 eighth(c4.clone()),
3181 eighth(c4.clone()),
3182 ];
3183 let beams = compute_beams(¬es, &ts);
3184 assert_eq!(beams[0], BeamState::Begin);
3186 assert_eq!(beams[1], BeamState::End);
3187 assert_eq!(beams[2], BeamState::Begin);
3188 assert_eq!(beams[3], BeamState::End);
3189 }
3190
3191 #[test]
3192 fn compute_beams_4_4_all_eighth_one_group() {
3193 use crate::model::notation::TimeSignature;
3194 let ts = TimeSignature {
3195 numerator: 4,
3196 denominator: 4,
3197 };
3198 let c4 = Pitch::new(Step::C, 4);
3199 let notes = vec![eighth(c4.clone()), eighth(c4.clone())];
3201 let beams = compute_beams(¬es, &ts);
3202 assert_eq!(beams[0], BeamState::Begin);
3203 assert_eq!(beams[1], BeamState::End);
3204 }
3205
3206 #[test]
3207 fn compute_beams_quarter_not_beamed() {
3208 use crate::model::notation::TimeSignature;
3209 let ts = TimeSignature {
3210 numerator: 4,
3211 denominator: 4,
3212 };
3213 let c4 = Pitch::new(Step::C, 4);
3214 let notes = vec![quarter(c4.clone()), quarter(c4.clone())];
3215 let beams = compute_beams(¬es, &ts);
3216 assert_eq!(beams[0], BeamState::None);
3217 assert_eq!(beams[1], BeamState::None);
3218 }
3219
3220 #[test]
3221 fn compute_beams_rest_breaks_beam() {
3222 use crate::model::notation::TimeSignature;
3223 let ts = TimeSignature {
3224 numerator: 4,
3225 denominator: 4,
3226 };
3227 let c4 = Pitch::new(Step::C, 4);
3228 let notes = vec![eighth(c4.clone()), rest_eighth(), eighth(c4.clone())];
3229 let beams = compute_beams(¬es, &ts);
3230 assert_eq!(beams[0], BeamState::None);
3232 assert_eq!(beams[1], BeamState::None);
3233 assert_eq!(beams[2], BeamState::None);
3234 }
3235
3236 #[test]
3237 fn compute_beams_6_8_compound() {
3238 use crate::model::notation::TimeSignature;
3239 let ts = TimeSignature {
3240 numerator: 6,
3241 denominator: 8,
3242 };
3243 let c4 = Pitch::new(Step::C, 4);
3244 let notes: Vec<Note> = (0..6).map(|_| eighth(c4.clone())).collect();
3246 let beams = compute_beams(¬es, &ts);
3247 assert_eq!(beams[0], BeamState::Begin);
3248 assert_eq!(beams[1], BeamState::Continue);
3249 assert_eq!(beams[2], BeamState::End);
3250 assert_eq!(beams[3], BeamState::Begin);
3251 assert_eq!(beams[4], BeamState::Continue);
3252 assert_eq!(beams[5], BeamState::End);
3253 }
3254
3255 #[test]
3256 fn compute_beams_single_eighth() {
3257 use crate::model::notation::TimeSignature;
3258 let ts = TimeSignature {
3259 numerator: 4,
3260 denominator: 4,
3261 };
3262 let c4 = Pitch::new(Step::C, 4);
3263 let notes = vec![eighth(c4.clone())];
3264 let beams = compute_beams(¬es, &ts);
3265 assert_eq!(beams[0], BeamState::None);
3266 }
3267}