1use super::change_hint::{ChangeHint, ChangeScope};
2use super::duration::Duration;
3use super::notation::{
4 Articulation, Barline, ChordSymbol, Clef, CrossStaff, Dynamic, GuitarTechnique, HairpinKind,
5 KeySignature, Lyric, NoteHead, OttavaKind, TimeSignature, TupletInfo,
6};
7use super::pitch::Pitch;
8use super::score::{
9 Measure, Note, NoteAddr, Part, PartGroup, Score, ScoreTemplate, Staff, respell_score,
10 respell_score_to_key,
11};
12use crate::Error;
13use serde::{Deserialize, Serialize};
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16#[serde(tag = "type", rename_all = "snake_case")]
17pub enum Command {
18 AddNote(AddNoteCmd),
19 AddPitch(AddPitchCmd),
20 SetDuration(SetDurationCmd),
21 DeleteNote(DeleteNoteCmd),
22 AddMeasure(AddMeasureCmd),
23 DeleteMeasure(DeleteMeasureCmd),
24 SetTempo(SetTempoCmd),
25 NewScore(NewScoreCmd),
26 AddHairpin(AddHairpinCmd),
27 ToggleTie(ToggleTieCmd),
28 SetDynamic(SetDynamicCmd),
29 ToggleArticulation(ToggleArticulationCmd),
30 SetKeySignature(SetKeySignatureCmd),
31 SetTimeSignature(SetTimeSignatureCmd),
32 SetBarline(SetBarlineCmd),
33 AddPart(AddPartCmd),
34 DeletePart(DeletePartCmd),
35 SetMetadata(SetMetadataCmd),
36 SetRehearsalMark(SetRehearsalMarkCmd),
37 SetNavigationMark(SetNavigationMarkCmd),
38 SetChordSymbol(SetChordSymbolCmd),
39 SetGrace(SetGraceCmd),
40 SetOttava(SetOttavaCmd),
41 SetLyric(SetLyricCmd),
42 SetMultiRest(SetMultiRestCmd),
43 AddPedal(AddPedalCmd),
44 SetVolta(SetVoltaCmd),
45 SetClef(SetClefCmd),
46 SetPartName(SetPartNameCmd),
47 SetMidiInstrument(SetMidiInstrumentCmd),
48 SetTranspose(SetTransposeCmd),
49 SetTempoAtMeasure(SetTempoAtMeasureCmd),
50 PasteVoice(PasteVoiceCmd),
51 PasteRange(PasteRangeCmd),
52 SetSystemBreak(SetSystemBreakCmd),
53 SetPageBreak(SetPageBreakCmd),
54 ToggleSlur(ToggleSlurCmd),
55 AddStaff(AddStaffCmd),
56 DeleteStaff(DeleteStaffCmd),
57 SetTuplet(SetTupletCmd),
58 RespellScore(RespellScoreCmd),
59 RespellScoreToKey(RespellScoreToKeyCmd),
60 SetStem(SetStemCmd),
61 SetArpeggio(SetArpeggioCmd),
62 SetTechniqueText(SetTechniqueTextCmd),
63 SetFingering(SetFingeringCmd),
64 SetStringNumber(SetStringNumberCmd),
65 SetTabPosition(SetTabPositionCmd),
66 SetNoteHead(SetNoteHeadCmd),
67 SetCue(SetCueCmd),
68 SetGuitarTechnique(SetGuitarTechniqueCmd),
69 SetExpressionText(SetExpressionTextCmd),
70 ToggleTrillLine(ToggleTrillLineCmd),
71 SetGlissando(SetGlissandoCmd),
72 SetCrossStaff(SetCrossStaffCmd),
73 SetPartGroup(SetPartGroupCmd),
74 Batch(BatchCmd),
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct AddNoteCmd {
79 pub part_index: usize,
80 pub staff_index: usize,
81 pub measure_index: usize,
82 pub voice: usize,
83 pub position: usize,
84 pub pitch: Option<Pitch>,
85 pub duration: Duration,
86 pub dot_count: u8,
87 pub is_rest: bool,
88 #[serde(default)]
89 pub tuplet: Option<TupletInfo>,
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct AddPitchCmd {
94 pub part_index: usize,
95 pub staff_index: usize,
96 pub measure_index: usize,
97 pub voice: usize,
98 pub note_index: usize,
99 pub pitch: Pitch,
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct SetDurationCmd {
104 pub part_index: usize,
105 pub staff_index: usize,
106 pub measure_index: usize,
107 pub voice: usize,
108 pub note_index: usize,
109 pub duration: Duration,
110 #[serde(default)]
111 pub dot_count: u8,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct DeleteNoteCmd {
116 pub note_id: String,
117 pub part_index: usize,
118 pub staff_index: usize,
119 pub measure_index: usize,
120 pub voice: usize,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct AddMeasureCmd {
125 pub after_index: usize,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct DeleteMeasureCmd {
130 pub measure_index: usize,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct SetTempoCmd {
135 pub bpm: u16,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct NewScoreCmd {
140 pub title: String,
141 pub composer: String,
142 pub tempo_bpm: u16,
143 pub time_numerator: u8,
144 pub time_denominator: u8,
145 pub key_fifths: i8,
146 pub measure_count: u32,
147 #[serde(default)]
149 pub template: Option<ScoreTemplate>,
150}
151
152#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct AddHairpinCmd {
154 pub part_index: usize,
155 pub staff_index: usize,
156 pub measure_index: usize,
157 pub voice: usize,
158 pub start_note_idx: usize,
159 pub end_note_idx: usize,
160 pub kind: HairpinKind,
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct ToggleTieCmd {
165 pub part_index: usize,
166 pub staff_index: usize,
167 pub measure_index: usize,
168 pub voice: usize,
169 pub note_index: usize,
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct SetDynamicCmd {
174 pub part_index: usize,
175 pub staff_index: usize,
176 pub measure_index: usize,
177 pub voice: usize,
178 pub note_index: usize,
179 pub dynamic: Option<Dynamic>,
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct ToggleArticulationCmd {
184 pub part_index: usize,
185 pub staff_index: usize,
186 pub measure_index: usize,
187 pub voice: usize,
188 pub note_index: usize,
189 pub articulation: Articulation,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
193pub struct SetKeySignatureCmd {
194 pub fifths: i8,
195}
196
197#[derive(Debug, Clone, Serialize, Deserialize)]
198pub struct SetTimeSignatureCmd {
199 pub numerator: u8,
200 pub denominator: u8,
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize)]
204pub struct SetBarlineCmd {
205 pub measure_index: usize,
206 pub side: String,
208 pub barline: Barline,
209}
210
211#[derive(Debug, Clone, Serialize, Deserialize)]
212pub struct AddPartCmd {
213 pub name: String,
214 pub short_name: String,
215 pub clefs: Vec<String>,
217 #[serde(default)]
219 pub midi_channel: u8,
220 #[serde(default)]
222 pub midi_program: u8,
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize)]
226pub struct DeletePartCmd {
227 pub part_index: usize,
228}
229
230#[derive(Debug, Clone, Default, Serialize, Deserialize)]
231pub struct SetMetadataCmd {
232 pub title: Option<String>,
233 pub composer: Option<String>,
234 pub lyricist: Option<String>,
235 pub copyright: Option<String>,
236 pub work_number: Option<String>,
237 pub movement_title: Option<String>,
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize)]
241pub struct SetRehearsalMarkCmd {
242 pub measure_index: usize,
243 pub text: Option<String>,
244}
245
246#[derive(Debug, Clone, Serialize, Deserialize)]
247pub struct SetNavigationMarkCmd {
248 pub measure_index: usize,
249 pub mark: Option<String>,
252}
253
254#[derive(Debug, Clone, Serialize, Deserialize)]
255pub struct SetChordSymbolCmd {
256 pub part_index: usize,
257 pub staff_index: usize,
258 pub measure_index: usize,
259 pub voice: usize,
260 pub note_index: usize,
261 pub chord: Option<ChordSymbol>,
262}
263
264#[derive(Debug, Clone, Serialize, Deserialize)]
265pub struct SetGraceCmd {
266 pub part_index: usize,
267 pub staff_index: usize,
268 pub measure_index: usize,
269 pub voice: usize,
270 pub note_index: usize,
271 pub is_grace: bool,
272 pub slash: bool,
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize)]
277pub struct SetOttavaCmd {
278 pub part_index: usize,
279 pub staff_index: usize,
280 pub measure_index: usize,
281 pub voice: usize,
282 pub note_index: usize,
283 pub ottava_start: Option<OttavaKind>,
284 pub ottava_end: bool,
285}
286
287#[derive(Debug, Clone, Serialize, Deserialize)]
288pub struct SetLyricCmd {
289 pub part_index: usize,
290 pub staff_index: usize,
291 pub measure_index: usize,
292 pub voice: usize,
293 pub note_index: usize,
294 pub lyric: Option<Lyric>,
295}
296
297#[derive(Debug, Clone, Serialize, Deserialize)]
298pub struct SetMultiRestCmd {
299 pub measure_index: usize,
300 pub count: Option<u8>,
301}
302
303#[derive(Debug, Clone, Serialize, Deserialize)]
304pub struct SetVoltaCmd {
305 pub measure_index: usize,
306 pub volta: Option<super::score::VoltaBracket>,
307}
308
309#[derive(Debug, Clone, Serialize, Deserialize)]
310pub struct SetClefCmd {
311 pub part_index: usize,
312 pub staff_index: usize,
313 pub clef: Clef,
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize)]
317pub struct SetPartNameCmd {
318 pub part_index: usize,
319 pub name: String,
320 pub short_name: String,
321}
322
323#[derive(Debug, Clone, Serialize, Deserialize)]
324pub struct SetMidiInstrumentCmd {
325 pub part_index: usize,
326 pub midi_channel: u8,
328 pub midi_program: u8,
330}
331
332#[derive(Debug, Clone, Serialize, Deserialize)]
333pub struct SetTransposeCmd {
334 pub part_index: usize,
335 pub staff_index: usize,
336 pub semitones: i8,
338}
339
340#[derive(Debug, Clone, Serialize, Deserialize)]
341pub struct SetTempoAtMeasureCmd {
342 pub measure_index: usize,
343 pub bpm: Option<u16>,
345}
346
347#[derive(Debug, Clone, Serialize, Deserialize)]
348pub struct PasteVoiceCmd {
349 pub part_index: usize,
350 pub staff_index: usize,
351 pub measure_index: usize,
352 pub voice_index: usize,
353 pub notes: Vec<Note>,
355}
356
357#[derive(Debug, Clone, Serialize, Deserialize)]
358pub struct SetSystemBreakCmd {
359 pub measure_index: usize,
360 pub value: bool,
361}
362
363#[derive(Debug, Clone, Serialize, Deserialize)]
364pub struct SetPageBreakCmd {
365 pub measure_index: usize,
366 pub value: bool,
367}
368
369#[derive(Debug, Clone, Serialize, Deserialize)]
371pub struct BatchCmd {
372 pub commands: Vec<Command>,
373 #[serde(default)]
376 pub label: Option<String>,
377}
378
379#[derive(Debug, Clone, Serialize, Deserialize)]
380pub struct AddPedalCmd {
381 pub part_index: usize,
382 pub staff_index: usize,
383 pub measure_index: usize,
384 pub voice: usize,
385 pub start_note_idx: usize,
386 pub end_note_idx: usize,
387}
388
389#[derive(Debug, Clone, Serialize, Deserialize)]
394pub struct PasteRangeCmd {
395 pub part_index: usize,
396 pub staff_index: usize,
397 pub voice_index: usize,
398 pub target_measure: usize,
399 pub measures: Vec<Vec<Note>>,
401}
402
403#[derive(Debug, Clone, Serialize, Deserialize)]
405pub struct ToggleSlurCmd {
406 pub start: NoteAddr,
407 pub end: NoteAddr,
408}
409
410#[derive(Debug, Clone, Serialize, Deserialize)]
412pub struct SetPartGroupCmd {
413 pub group: Option<PartGroup>,
415}
416
417#[derive(Debug, Clone, Serialize, Deserialize)]
419pub struct ToggleTrillLineCmd {
420 pub start: NoteAddr,
421 pub end: NoteAddr,
422}
423
424#[derive(Debug, Clone, Serialize, Deserialize)]
429pub struct AddStaffCmd {
430 pub part_index: usize,
431 pub clef: Clef,
432}
433
434#[derive(Debug, Clone, Serialize, Deserialize)]
436pub struct DeleteStaffCmd {
437 pub part_index: usize,
438 pub staff_index: usize,
439}
440
441#[derive(Debug, Clone, Serialize, Deserialize)]
443pub struct SetTupletCmd {
444 pub part_index: usize,
445 pub staff_index: usize,
446 pub measure_index: usize,
447 pub voice_index: usize,
448 pub note_index: usize,
449 pub tuplet: Option<TupletInfo>,
451}
452
453#[derive(Debug, Clone, Serialize, Deserialize)]
455pub struct SetStemCmd {
456 pub part_index: usize,
457 pub staff_index: usize,
458 pub measure_index: usize,
459 pub voice_index: usize,
460 pub note_index: usize,
461 pub stem_up: Option<bool>,
463}
464
465#[derive(Debug, Clone, Serialize, Deserialize)]
466pub struct SetArpeggioCmd {
467 pub part_index: usize,
468 pub staff_index: usize,
469 pub measure_index: usize,
470 pub voice_index: usize,
471 pub note_index: usize,
472 pub direction: Option<bool>,
474}
475
476#[derive(Debug, Clone, Serialize, Deserialize)]
478pub struct SetTechniqueTextCmd {
479 pub part_index: usize,
480 pub staff_index: usize,
481 pub measure_index: usize,
482 pub voice: usize,
483 pub note_index: usize,
484 pub text: Option<String>,
486}
487
488#[derive(Debug, Clone, Serialize, Deserialize)]
489pub struct SetGlissandoCmd {
490 pub part_index: usize,
491 pub staff_index: usize,
492 pub measure_index: usize,
493 pub voice: usize,
494 pub note_index: usize,
495 pub start: bool,
496 pub end: bool,
497}
498
499#[derive(Debug, Clone, Serialize, Deserialize)]
500pub struct SetCrossStaffCmd {
501 pub part_index: usize,
502 pub staff_index: usize,
503 pub measure_index: usize,
504 pub voice: usize,
505 pub note_index: usize,
506 pub placement: Option<CrossStaff>,
507}
508
509#[derive(Debug, Clone, Serialize, Deserialize)]
511pub struct SetFingeringCmd {
512 pub part_index: usize,
513 pub staff_index: usize,
514 pub measure_index: usize,
515 pub voice: usize,
516 pub note_index: usize,
517 pub fingering: Option<u8>,
519}
520
521#[derive(Debug, Clone, Serialize, Deserialize)]
523pub struct SetStringNumberCmd {
524 pub part_index: usize,
525 pub staff_index: usize,
526 pub measure_index: usize,
527 pub voice: usize,
528 pub note_index: usize,
529 pub string_number: Option<u8>,
531}
532
533#[derive(Debug, Clone, Serialize, Deserialize)]
535pub struct SetTabPositionCmd {
536 pub part_index: usize,
537 pub staff_index: usize,
538 pub measure_index: usize,
539 pub voice: usize,
540 pub note_index: usize,
541 pub position: Option<super::notation::TabPosition>,
543}
544
545#[derive(Debug, Clone, Serialize, Deserialize)]
547pub struct SetGuitarTechniqueCmd {
548 pub part_index: usize,
549 pub staff_index: usize,
550 pub measure_index: usize,
551 pub voice: usize,
552 pub note_index: usize,
553 pub technique: Option<GuitarTechnique>,
555}
556
557#[derive(Debug, Clone, Serialize, Deserialize)]
559pub struct SetExpressionTextCmd {
560 pub measure_index: usize,
561 pub text: Option<String>,
563}
564
565#[derive(Debug, Clone, Serialize, Deserialize)]
567pub struct SetCueCmd {
568 pub part_index: usize,
569 pub staff_index: usize,
570 pub measure_index: usize,
571 pub voice: usize,
572 pub note_index: usize,
573 pub is_cue: bool,
574}
575
576#[derive(Debug, Clone, Serialize, Deserialize)]
578pub struct SetNoteHeadCmd {
579 pub part_index: usize,
580 pub staff_index: usize,
581 pub measure_index: usize,
582 pub voice: usize,
583 pub note_index: usize,
584 pub note_head: NoteHead,
585}
586
587#[derive(Debug, Clone, Serialize, Deserialize)]
589pub struct RespellScoreCmd {
590 pub prefer_flat: bool,
591}
592
593#[derive(Debug, Clone, Serialize, Deserialize)]
595pub struct RespellScoreToKeyCmd {}
596
597struct UndoEntry {
598 command: Command,
599 snapshot: Score,
600}
601
602pub struct CommandStack {
603 history: Vec<UndoEntry>,
604 future: Vec<(Command, Score)>,
605 max_depth: usize,
606}
607
608impl CommandStack {
609 pub fn new(max_depth: usize) -> Self {
610 Self {
611 history: Vec::new(),
612 future: Vec::new(),
613 max_depth,
614 }
615 }
616
617 pub fn can_undo(&self) -> bool {
618 !self.history.is_empty()
619 }
620
621 pub fn can_redo(&self) -> bool {
622 !self.future.is_empty()
623 }
624
625 pub fn execute(&mut self, cmd: Command, score: &mut Score) -> Result<(), Error> {
626 let snapshot = score.clone();
627 apply_command(&cmd, score)?;
628 self.history.push(UndoEntry {
629 command: cmd,
630 snapshot,
631 });
632 self.future.clear();
633 if self.history.len() > self.max_depth {
634 self.history.remove(0);
635 }
636 Ok(())
637 }
638
639 pub fn undo(&mut self, score: &mut Score) -> Result<ChangeHint, Error> {
640 let entry = self.history.pop().ok_or(Error::NothingToUndo)?;
641 let hint = command_hint(&entry.command);
642 let post_snapshot = score.clone();
643 *score = entry.snapshot;
644 self.future.push((entry.command, post_snapshot));
645 if self.future.len() > self.max_depth {
646 self.future.remove(0);
647 }
648 Ok(hint)
649 }
650
651 pub fn redo(&mut self, score: &mut Score) -> Result<ChangeHint, Error> {
652 let (cmd, post) = self.future.pop().ok_or(Error::NothingToRedo)?;
653 let hint = command_hint(&cmd);
654 let snapshot = score.clone();
655 *score = post;
656 self.history.push(UndoEntry {
657 command: cmd,
658 snapshot,
659 });
660 Ok(hint)
661 }
662
663 pub fn history_commands(&self) -> Vec<Command> {
666 self.history.iter().map(|e| e.command.clone()).collect()
667 }
668
669 pub fn undo_label(&self) -> Option<String> {
671 self.history.last().map(|e| command_label(&e.command))
672 }
673
674 pub fn redo_label(&self) -> Option<String> {
676 self.future.last().map(|(cmd, _)| command_label(cmd))
677 }
678
679 pub fn undo_key(&self) -> Option<String> {
681 self.history.last().map(|e| command_key(&e.command))
682 }
683
684 pub fn redo_key(&self) -> Option<String> {
686 self.future.last().map(|(cmd, _)| command_key(cmd))
687 }
688
689 pub fn batch_execute(&mut self, cmds: Vec<Command>, score: &mut Score) -> Result<(), Error> {
691 if cmds.is_empty() {
692 return Ok(());
693 }
694 let snapshot = score.clone();
695 for cmd in &cmds {
696 if let Err(e) = apply_command(cmd, score) {
697 *score = snapshot;
698 return Err(e);
699 }
700 }
701 self.history.push(UndoEntry {
702 command: Command::Batch(BatchCmd {
703 commands: cmds,
704 label: None,
705 }),
706 snapshot,
707 });
708 self.future.clear();
709 if self.history.len() > self.max_depth {
710 self.history.remove(0);
711 }
712 Ok(())
713 }
714
715 pub fn batch_execute_labeled(
719 &mut self,
720 cmds: Vec<Command>,
721 label: String,
722 score: &mut Score,
723 ) -> Result<(), Error> {
724 if cmds.is_empty() {
725 return Ok(());
726 }
727 let snapshot = score.clone();
728 for cmd in &cmds {
729 if let Err(e) = apply_command(cmd, score) {
730 *score = snapshot;
731 return Err(e);
732 }
733 }
734 self.history.push(UndoEntry {
735 command: Command::Batch(BatchCmd {
736 commands: cmds,
737 label: Some(label),
738 }),
739 snapshot,
740 });
741 self.future.clear();
742 if self.history.len() > self.max_depth {
743 self.history.remove(0);
744 }
745 Ok(())
746 }
747}
748
749pub fn command_hint(cmd: &Command) -> ChangeHint {
752 use ChangeScope::*;
753 macro_rules! hint {
754 ($scope:expr, $layout:expr, $playback:expr) => {
755 ChangeHint {
756 scope: $scope,
757 layout_dirty: $layout,
758 playback_dirty: $playback,
759 }
760 };
761 }
762 macro_rules! meas {
763 ($c:expr) => {
764 Measures {
765 part: $c.part_index,
766 staff: $c.staff_index,
767 start: $c.measure_index,
768 end: $c.measure_index + 1,
769 }
770 };
771 }
772 match cmd {
773 Command::NewScore(_)
775 | Command::AddPart(_)
776 | Command::DeletePart(_)
777 | Command::AddMeasure(_)
778 | Command::DeleteMeasure(_) => hint!(Global, true, true),
779
780 Command::SetTempo(_) => hint!(Global, false, true),
781
782 Command::SetMetadata(_) => hint!(Global, false, false),
783
784 Command::SetKeySignature(_) => hint!(Global, true, false),
785
786 Command::SetTimeSignature(_) => hint!(Global, true, true),
787
788 Command::SetBarline(_)
789 | Command::SetVolta(_)
790 | Command::SetRehearsalMark(_)
791 | Command::SetNavigationMark(_)
792 | Command::SetExpressionText(_) => hint!(Global, false, false),
793
794 Command::SetMultiRest(_) => hint!(Global, true, false),
795
796 Command::SetTempoAtMeasure(_) => hint!(Global, false, true),
797
798 Command::SetPartName(c) => hint!(Part(c.part_index), false, false),
800
801 Command::SetMidiInstrument(c) => hint!(Part(c.part_index), false, true),
802
803 Command::SetTranspose(c) => hint!(Part(c.part_index), false, true),
804
805 Command::SetClef(c) => hint!(Part(c.part_index), true, false),
806
807 Command::AddNote(c) => hint!(meas!(c), false, true),
809 Command::AddPitch(c) => hint!(meas!(c), false, true),
810 Command::SetDuration(c) => hint!(meas!(c), false, true),
811 Command::DeleteNote(c) => hint!(meas!(c), false, true),
812 Command::PasteVoice(c) => hint!(meas!(c), false, true),
813 Command::PasteRange(c) => hint!(
814 Measures {
815 part: c.part_index,
816 staff: c.staff_index,
817 start: c.target_measure,
818 end: c.target_measure + c.measures.len()
819 },
820 false,
821 true
822 ),
823 Command::AddHairpin(c) => hint!(meas!(c), false, true),
824 Command::ToggleTie(c) => hint!(meas!(c), false, true),
825 Command::SetDynamic(c) => hint!(meas!(c), false, true),
826 Command::ToggleArticulation(c) => hint!(meas!(c), false, true),
827 Command::SetGrace(c) => hint!(meas!(c), false, true),
828 Command::SetOttava(c) => hint!(meas!(c), false, true),
829 Command::SetLyric(c) => hint!(meas!(c), false, true),
830 Command::AddPedal(c) => hint!(meas!(c), false, true),
831 Command::SetChordSymbol(c) => hint!(meas!(c), false, true),
832
833 Command::SetSystemBreak(_) | Command::SetPageBreak(_) => hint!(Global, true, false),
834
835 Command::ToggleSlur(_) | Command::ToggleTrillLine(_) => hint!(Global, true, false),
836 Command::SetGlissando(c) => hint!(meas!(c), true, true),
837 Command::SetCrossStaff(c) => hint!(meas!(c), true, true),
838
839 Command::SetPartGroup(_) => hint!(Global, false, false),
840
841 Command::AddStaff(_) | Command::DeleteStaff(_) => hint!(Global, true, true),
842
843 Command::SetTuplet(c) => hint!(meas!(c), false, true),
844
845 Command::RespellScore(_) | Command::RespellScoreToKey(_) => hint!(Global, true, true),
846
847 Command::SetStem(c) => hint!(meas!(c), false, false),
848
849 Command::SetArpeggio(c) => hint!(meas!(c), false, false),
850
851 Command::SetTechniqueText(c) => hint!(meas!(c), false, false),
852 Command::SetFingering(c) => hint!(meas!(c), false, false),
853 Command::SetStringNumber(c) => hint!(meas!(c), false, false),
854 Command::SetTabPosition(c) => hint!(meas!(c), false, false),
855 Command::SetGuitarTechnique(c) => hint!(meas!(c), false, false),
856 Command::SetNoteHead(c) => hint!(meas!(c), false, false),
857 Command::SetCue(c) => hint!(meas!(c), false, true),
858
859 Command::Batch(c) => {
860 let Some(first) = c.commands.first() else {
861 return hint!(Global, false, false);
862 };
863 let mut merged = command_hint(first);
864 for cmd in c.commands.iter().skip(1) {
865 merged = merged.merge(command_hint(cmd));
866 }
867 merged
868 }
869 }
870}
871
872pub fn command_label(cmd: &Command) -> String {
874 match cmd {
875 Command::AddNote(_) => "Add Note".to_string(),
876 Command::AddPitch(_) => "Add Pitch".to_string(),
877 Command::SetDuration(_) => "Set Duration".to_string(),
878 Command::DeleteNote(_) => "Delete Note".to_string(),
879 Command::AddMeasure(_) => "Add Measure".to_string(),
880 Command::DeleteMeasure(_) => "Delete Measure".to_string(),
881 Command::SetTempo(_) => "Set Tempo".to_string(),
882 Command::NewScore(_) => "New Score".to_string(),
883 Command::AddHairpin(_) => "Add Hairpin".to_string(),
884 Command::ToggleTie(_) => "Toggle Tie".to_string(),
885 Command::SetDynamic(_) => "Set Dynamic".to_string(),
886 Command::ToggleArticulation(_) => "Toggle Articulation".to_string(),
887 Command::SetKeySignature(_) => "Set Key Signature".to_string(),
888 Command::SetTimeSignature(_) => "Set Time Signature".to_string(),
889 Command::SetBarline(_) => "Set Barline".to_string(),
890 Command::AddPart(_) => "Add Part".to_string(),
891 Command::DeletePart(_) => "Delete Part".to_string(),
892 Command::SetMetadata(_) => "Set Metadata".to_string(),
893 Command::SetRehearsalMark(_) => "Set Rehearsal Mark".to_string(),
894 Command::SetNavigationMark(_) => "Set Navigation Mark".to_string(),
895 Command::SetChordSymbol(_) => "Set Chord Symbol".to_string(),
896 Command::SetGrace(_) => "Set Grace Note".to_string(),
897 Command::SetOttava(_) => "Set Ottava".to_string(),
898 Command::SetLyric(_) => "Set Lyric".to_string(),
899 Command::SetMultiRest(_) => "Set Multi-Rest".to_string(),
900 Command::AddPedal(_) => "Add Pedal".to_string(),
901 Command::SetVolta(_) => "Set Volta".to_string(),
902 Command::SetClef(_) => "Set Clef".to_string(),
903 Command::SetPartName(_) => "Set Part Name".to_string(),
904 Command::SetMidiInstrument(_) => "Set MIDI Instrument".to_string(),
905 Command::SetTranspose(_) => "Set Transpose".to_string(),
906 Command::SetTempoAtMeasure(_) => "Set Tempo".to_string(),
907 Command::PasteVoice(_) => "Paste Voice".to_string(),
908 Command::PasteRange(_) => "Paste Range".to_string(),
909 Command::SetSystemBreak(_) => "Set System Break".to_string(),
910 Command::SetPageBreak(_) => "Set Page Break".to_string(),
911 Command::ToggleSlur(_) => "Toggle Slur".to_string(),
912 Command::AddStaff(_) => "Add Staff".to_string(),
913 Command::DeleteStaff(_) => "Delete Staff".to_string(),
914 Command::SetTuplet(c) => if c.tuplet.is_some() {
915 "Set Tuplet"
916 } else {
917 "Clear Tuplet"
918 }
919 .to_string(),
920 Command::RespellScore(c) => if c.prefer_flat {
921 "Respell Score (flat)"
922 } else {
923 "Respell Score (sharp)"
924 }
925 .to_string(),
926 Command::RespellScoreToKey(_) => "Respell Score to Key".to_string(),
927 Command::SetStem(_) => "Set Stem".to_string(),
928 Command::SetArpeggio(_) => "Set Arpeggio".to_string(),
929 Command::SetTechniqueText(_) => "Set Technique Text".to_string(),
930 Command::SetFingering(_) => "Set Fingering".to_string(),
931 Command::SetStringNumber(_) => "Set String Number".to_string(),
932 Command::SetTabPosition(_) => "Set Tablature Position".to_string(),
933 Command::SetGuitarTechnique(_) => "Set Guitar Technique".to_string(),
934 Command::SetNoteHead(_) => "Set Note Head".to_string(),
935 Command::SetCue(c) => if c.is_cue {
936 "Set Cue Note"
937 } else {
938 "Clear Cue Note"
939 }
940 .to_string(),
941 Command::SetExpressionText(_) => "Set Expression Text".to_string(),
942 Command::ToggleTrillLine(_) => "Toggle Trill Line".to_string(),
943 Command::SetGlissando(_) => "Set Glissando".to_string(),
944 Command::SetCrossStaff(_) => "Set Cross-Staff Placement".to_string(),
945 Command::SetPartGroup(_) => "Set Part Group".to_string(),
946 Command::Batch(c) => c.label.clone().unwrap_or_else(|| {
947 c.commands
948 .first()
949 .map(command_label)
950 .unwrap_or_else(|| "Batch".to_string())
951 }),
952 }
953}
954
955pub fn command_key(cmd: &Command) -> String {
959 match cmd {
960 Command::AddNote(_) => "AddNote".to_string(),
961 Command::AddPitch(_) => "AddPitch".to_string(),
962 Command::SetDuration(_) => "SetDuration".to_string(),
963 Command::DeleteNote(_) => "DeleteNote".to_string(),
964 Command::AddMeasure(_) => "AddMeasure".to_string(),
965 Command::DeleteMeasure(_) => "DeleteMeasure".to_string(),
966 Command::SetTempo(_) => "SetTempo".to_string(),
967 Command::NewScore(_) => "NewScore".to_string(),
968 Command::AddHairpin(_) => "AddHairpin".to_string(),
969 Command::ToggleTie(_) => "ToggleTie".to_string(),
970 Command::SetDynamic(_) => "SetDynamic".to_string(),
971 Command::ToggleArticulation(_) => "ToggleArticulation".to_string(),
972 Command::SetKeySignature(_) => "SetKeySignature".to_string(),
973 Command::SetTimeSignature(_) => "SetTimeSignature".to_string(),
974 Command::SetBarline(_) => "SetBarline".to_string(),
975 Command::AddPart(_) => "AddPart".to_string(),
976 Command::DeletePart(_) => "DeletePart".to_string(),
977 Command::SetMetadata(_) => "SetMetadata".to_string(),
978 Command::SetRehearsalMark(_) => "SetRehearsalMark".to_string(),
979 Command::SetNavigationMark(_) => "SetNavigationMark".to_string(),
980 Command::SetChordSymbol(_) => "SetChordSymbol".to_string(),
981 Command::SetGrace(_) => "SetGrace".to_string(),
982 Command::SetOttava(_) => "SetOttava".to_string(),
983 Command::SetLyric(_) => "SetLyric".to_string(),
984 Command::SetMultiRest(_) => "SetMultiRest".to_string(),
985 Command::AddPedal(_) => "AddPedal".to_string(),
986 Command::SetVolta(_) => "SetVolta".to_string(),
987 Command::SetClef(_) => "SetClef".to_string(),
988 Command::SetPartName(_) => "SetPartName".to_string(),
989 Command::SetMidiInstrument(_) => "SetMidiInstrument".to_string(),
990 Command::SetTranspose(_) => "SetTranspose".to_string(),
991 Command::SetTempoAtMeasure(_) => "SetTempoAtMeasure".to_string(),
992 Command::PasteVoice(_) => "PasteVoice".to_string(),
993 Command::PasteRange(_) => "PasteRange".to_string(),
994 Command::SetSystemBreak(_) => "SetSystemBreak".to_string(),
995 Command::SetPageBreak(_) => "SetPageBreak".to_string(),
996 Command::ToggleSlur(_) => "ToggleSlur".to_string(),
997 Command::AddStaff(_) => "AddStaff".to_string(),
998 Command::DeleteStaff(_) => "DeleteStaff".to_string(),
999 Command::SetTuplet(_) => "SetTuplet".to_string(),
1000 Command::RespellScore(_) => "RespellScore".to_string(),
1001 Command::RespellScoreToKey(_) => "RespellScoreToKey".to_string(),
1002 Command::SetStem(_) => "SetStem".to_string(),
1003 Command::SetArpeggio(_) => "SetArpeggio".to_string(),
1004 Command::SetTechniqueText(_) => "SetTechniqueText".to_string(),
1005 Command::SetFingering(_) => "SetFingering".to_string(),
1006 Command::SetStringNumber(_) => "SetStringNumber".to_string(),
1007 Command::SetTabPosition(_) => "SetTabPosition".to_string(),
1008 Command::SetGuitarTechnique(_) => "SetGuitarTechnique".to_string(),
1009 Command::SetNoteHead(_) => "SetNoteHead".to_string(),
1010 Command::SetCue(_) => "SetCue".to_string(),
1011 Command::SetExpressionText(_) => "SetExpressionText".to_string(),
1012 Command::ToggleTrillLine(_) => "ToggleTrillLine".to_string(),
1013 Command::SetGlissando(_) => "SetGlissando".to_string(),
1014 Command::SetCrossStaff(_) => "SetCrossStaff".to_string(),
1015 Command::SetPartGroup(_) => "SetPartGroup".to_string(),
1016 Command::Batch(c) => c.label.clone().unwrap_or_else(|| "Batch".to_string()),
1017 }
1018}
1019
1020pub fn apply_command(cmd: &Command, score: &mut Score) -> Result<(), Error> {
1021 match cmd {
1022 Command::AddNote(c) => apply_add_note(c, score),
1023 Command::AddPitch(c) => apply_add_pitch(c, score),
1024 Command::SetDuration(c) => apply_set_duration(c, score),
1025 Command::DeleteNote(c) => apply_delete_note(c, score),
1026 Command::AddMeasure(c) => apply_add_measure(c, score),
1027 Command::DeleteMeasure(c) => apply_delete_measure(c, score),
1028 Command::SetTempo(c) => {
1029 score.settings.tempo_bpm = c.bpm;
1030 Ok(())
1031 }
1032 Command::NewScore(c) => {
1033 let mut s = match c.template {
1034 Some(kind) => Score::template(kind),
1035 None => Score::new(
1036 &c.title,
1037 c.tempo_bpm,
1038 c.time_numerator,
1039 c.time_denominator,
1040 c.key_fifths,
1041 c.measure_count,
1042 ),
1043 };
1044 if c.template.is_some() {
1045 s.metadata.title = c.title.clone();
1046 s.metadata.composer = c.composer.clone();
1047 s.settings.tempo_bpm = c.tempo_bpm;
1048 s.settings.time_signature = TimeSignature {
1049 numerator: c.time_numerator,
1050 denominator: c.time_denominator,
1051 };
1052 s.settings.key_signature = KeySignature {
1053 fifths: c.key_fifths,
1054 mode: "major".to_string(),
1055 };
1056 for part in &mut s.parts {
1057 for staff in &mut part.staves {
1058 staff.measures.clear();
1059 for i in 0..c.measure_count {
1060 let mut m = Measure::empty(c.time_numerator, c.time_denominator);
1061 m.number = i + 1;
1062 staff.measures.push(m);
1063 }
1064 }
1065 }
1066 }
1067 *score = s;
1068 Ok(())
1069 }
1070 Command::AddHairpin(c) => apply_add_hairpin(c, score),
1071 Command::ToggleTie(c) => apply_toggle_tie(c, score),
1072 Command::SetDynamic(c) => apply_set_dynamic(c, score),
1073 Command::ToggleArticulation(c) => apply_toggle_articulation(c, score),
1074 Command::SetKeySignature(c) => {
1075 score.settings.key_signature = KeySignature {
1076 fifths: c.fifths,
1077 mode: "major".to_string(),
1078 };
1079 Ok(())
1080 }
1081 Command::SetTimeSignature(c) => apply_set_time_signature(c, score),
1082 Command::SetBarline(c) => apply_set_barline(c, score),
1083 Command::AddPart(c) => apply_add_part(c, score),
1084 Command::DeletePart(c) => apply_delete_part(c, score),
1085 Command::SetMetadata(c) => apply_set_metadata(c, score),
1086 Command::SetRehearsalMark(c) => {
1087 for_each_measure_at(score, c.measure_index, |m| {
1088 m.rehearsal = c.text.clone();
1089 });
1090 Ok(())
1091 }
1092 Command::SetNavigationMark(c) => {
1093 for_each_measure_at(score, c.measure_index, |m| {
1094 m.navigation = c.mark.clone();
1095 });
1096 Ok(())
1097 }
1098 Command::SetChordSymbol(c) => {
1099 get_note_mut(
1100 score,
1101 c.part_index,
1102 c.staff_index,
1103 c.measure_index,
1104 c.voice,
1105 c.note_index,
1106 )?
1107 .chord_symbol = c.chord.clone();
1108 Ok(())
1109 }
1110 Command::SetGrace(c) => {
1111 let note = get_note_mut(
1112 score,
1113 c.part_index,
1114 c.staff_index,
1115 c.measure_index,
1116 c.voice,
1117 c.note_index,
1118 )?;
1119 if note.is_rest {
1120 return Err(Error::InvalidCommand(
1121 "cannot make a rest into a grace note".into(),
1122 ));
1123 }
1124 note.is_grace = c.is_grace;
1125 note.grace_slash = c.slash;
1126 Ok(())
1127 }
1128 Command::SetOttava(c) => {
1129 let note = get_note_mut(
1130 score,
1131 c.part_index,
1132 c.staff_index,
1133 c.measure_index,
1134 c.voice,
1135 c.note_index,
1136 )?;
1137 note.ottava_start = c.ottava_start;
1138 note.ottava_end = c.ottava_end;
1139 Ok(())
1140 }
1141 Command::SetLyric(c) => {
1142 get_note_mut(
1143 score,
1144 c.part_index,
1145 c.staff_index,
1146 c.measure_index,
1147 c.voice,
1148 c.note_index,
1149 )?
1150 .lyric = c.lyric.clone();
1151 Ok(())
1152 }
1153 Command::SetMultiRest(c) => {
1154 for_each_measure_at(score, c.measure_index, |m| {
1155 m.multi_rest_count = c.count;
1156 });
1157 Ok(())
1158 }
1159 Command::AddPedal(c) => apply_add_pedal(c, score),
1160 Command::SetVolta(c) => {
1161 for_each_measure_at(score, c.measure_index, |m| {
1162 m.volta = c.volta.clone();
1163 });
1164 Ok(())
1165 }
1166 Command::SetClef(c) => apply_set_clef(c, score),
1167 Command::SetPartName(c) => apply_set_part_name(c, score),
1168 Command::SetMidiInstrument(c) => apply_set_midi_instrument(c, score),
1169 Command::SetTranspose(c) => apply_set_transpose(c, score),
1170 Command::SetTempoAtMeasure(c) => {
1171 for_each_measure_at(score, c.measure_index, |m| {
1172 m.tempo = c.bpm;
1173 });
1174 Ok(())
1175 }
1176 Command::PasteVoice(c) => apply_paste_voice(c, score),
1177 Command::PasteRange(c) => apply_paste_range(c, score),
1178 Command::SetSystemBreak(c) => {
1179 for_each_measure_at(score, c.measure_index, |m| {
1180 m.system_break = c.value;
1181 });
1182 Ok(())
1183 }
1184 Command::SetPageBreak(c) => {
1185 for_each_measure_at(score, c.measure_index, |m| {
1186 m.page_break = c.value;
1187 });
1188 Ok(())
1189 }
1190 Command::ToggleSlur(c) => apply_toggle_slur(c, score),
1191 Command::AddStaff(c) => apply_add_staff(c, score),
1192 Command::DeleteStaff(c) => apply_delete_staff(c, score),
1193 Command::SetTuplet(c) => {
1194 get_note_mut(
1195 score,
1196 c.part_index,
1197 c.staff_index,
1198 c.measure_index,
1199 c.voice_index,
1200 c.note_index,
1201 )?
1202 .tuplet = c.tuplet.clone();
1203 Ok(())
1204 }
1205 Command::RespellScore(c) => {
1206 respell_score(score, c.prefer_flat);
1207 Ok(())
1208 }
1209 Command::RespellScoreToKey(_) => {
1210 respell_score_to_key(score);
1211 Ok(())
1212 }
1213 Command::SetStem(c) => {
1214 get_note_mut(
1215 score,
1216 c.part_index,
1217 c.staff_index,
1218 c.measure_index,
1219 c.voice_index,
1220 c.note_index,
1221 )?
1222 .stem_up = c.stem_up;
1223 Ok(())
1224 }
1225 Command::SetArpeggio(c) => {
1226 get_note_mut(
1227 score,
1228 c.part_index,
1229 c.staff_index,
1230 c.measure_index,
1231 c.voice_index,
1232 c.note_index,
1233 )?
1234 .arpeggiate = c.direction;
1235 Ok(())
1236 }
1237 Command::SetTechniqueText(c) => {
1238 get_note_mut(
1239 score,
1240 c.part_index,
1241 c.staff_index,
1242 c.measure_index,
1243 c.voice,
1244 c.note_index,
1245 )?
1246 .technique_text = c.text.clone();
1247 Ok(())
1248 }
1249 Command::SetGlissando(c) => {
1250 let note = get_note_mut(
1251 score,
1252 c.part_index,
1253 c.staff_index,
1254 c.measure_index,
1255 c.voice,
1256 c.note_index,
1257 )?;
1258 note.glissando_start = c.start;
1259 note.glissando_end = c.end;
1260 Ok(())
1261 }
1262 Command::SetCrossStaff(c) => {
1263 let staff_count = score
1264 .parts
1265 .get(c.part_index)
1266 .ok_or(Error::PartNotFound(c.part_index))?
1267 .staves
1268 .len();
1269 let note = get_note_mut(
1270 score,
1271 c.part_index,
1272 c.staff_index,
1273 c.measure_index,
1274 c.voice,
1275 c.note_index,
1276 )?;
1277 if let Some(ref placement) = c.placement
1278 && placement.target_staff == c.staff_index
1279 {
1280 return Err(Error::InvalidCommand(
1281 "cross-staff target must differ from source staff".into(),
1282 ));
1283 }
1284 if let Some(ref placement) = c.placement
1285 && placement.target_staff >= staff_count
1286 {
1287 return Err(Error::StaffNotFound(placement.target_staff));
1288 }
1289 note.cross_staff = c.placement.clone();
1290 Ok(())
1291 }
1292 Command::SetFingering(c) => {
1293 get_note_mut(
1294 score,
1295 c.part_index,
1296 c.staff_index,
1297 c.measure_index,
1298 c.voice,
1299 c.note_index,
1300 )?
1301 .fingering = c.fingering;
1302 Ok(())
1303 }
1304 Command::SetStringNumber(c) => {
1305 get_note_mut(
1306 score,
1307 c.part_index,
1308 c.staff_index,
1309 c.measure_index,
1310 c.voice,
1311 c.note_index,
1312 )?
1313 .string_number = c.string_number;
1314 Ok(())
1315 }
1316 Command::SetTabPosition(c) => {
1317 let note = get_note_mut(
1318 score,
1319 c.part_index,
1320 c.staff_index,
1321 c.measure_index,
1322 c.voice,
1323 c.note_index,
1324 )?;
1325 note.string_number = c.position.as_ref().map(|position| position.string);
1326 note.tab_position = c.position.clone();
1327 note.tab_positions = c.position.iter().cloned().collect();
1328 Ok(())
1329 }
1330 Command::SetGuitarTechnique(c) => {
1331 get_note_mut(
1332 score,
1333 c.part_index,
1334 c.staff_index,
1335 c.measure_index,
1336 c.voice,
1337 c.note_index,
1338 )?
1339 .guitar_technique = c.technique.clone();
1340 Ok(())
1341 }
1342 Command::SetNoteHead(c) => {
1343 get_note_mut(
1344 score,
1345 c.part_index,
1346 c.staff_index,
1347 c.measure_index,
1348 c.voice,
1349 c.note_index,
1350 )?
1351 .note_head = c.note_head.clone();
1352 Ok(())
1353 }
1354 Command::SetCue(c) => {
1355 get_note_mut(
1356 score,
1357 c.part_index,
1358 c.staff_index,
1359 c.measure_index,
1360 c.voice,
1361 c.note_index,
1362 )?
1363 .is_cue = c.is_cue;
1364 Ok(())
1365 }
1366 Command::SetExpressionText(c) => {
1367 for_each_measure_at(score, c.measure_index, |m| {
1368 m.expression_text = c.text.clone();
1369 });
1370 Ok(())
1371 }
1372 Command::ToggleTrillLine(c) => apply_toggle_trill_line(c, score),
1373 Command::SetPartGroup(c) => {
1374 if let Some(group) = &c.group {
1375 score
1376 .part_groups
1377 .retain(|g| g.first_part != group.first_part || g.last_part != group.last_part);
1378 score.part_groups.push(group.clone());
1379 } else {
1380 score.part_groups.clear();
1382 }
1383 Ok(())
1384 }
1385 Command::Batch(c) => {
1386 for cmd in &c.commands {
1387 apply_command(cmd, score)?;
1388 }
1389 Ok(())
1390 }
1391 }
1392}
1393
1394fn get_note_mut(
1397 score: &mut Score,
1398 part_index: usize,
1399 staff_index: usize,
1400 measure_index: usize,
1401 voice: usize,
1402 note_index: usize,
1403) -> Result<&mut Note, Error> {
1404 score
1405 .parts
1406 .get_mut(part_index)
1407 .ok_or(Error::PartNotFound(part_index))?
1408 .staves
1409 .get_mut(staff_index)
1410 .ok_or(Error::StaffNotFound(staff_index))?
1411 .measures
1412 .get_mut(measure_index)
1413 .ok_or(Error::MeasureNotFound(measure_index))?
1414 .voices
1415 .get_mut(voice)
1416 .ok_or(Error::VoiceOutOfRange(voice))?
1417 .get_mut(note_index)
1418 .ok_or(Error::NoteNotFound(note_index))
1419}
1420
1421fn for_each_measure_at(score: &mut Score, index: usize, mut f: impl FnMut(&mut Measure)) {
1422 for part in &mut score.parts {
1423 for staff in &mut part.staves {
1424 if let Some(m) = staff.measures.get_mut(index) {
1425 f(m);
1426 }
1427 }
1428 }
1429}
1430
1431fn apply_add_note(cmd: &AddNoteCmd, score: &mut Score) -> Result<(), Error> {
1432 let ts_beats = score.settings.time_signature.total_beats();
1433 let voice = score
1434 .parts
1435 .get_mut(cmd.part_index)
1436 .ok_or(Error::PartNotFound(cmd.part_index))?
1437 .staves
1438 .get_mut(cmd.staff_index)
1439 .ok_or(Error::StaffNotFound(cmd.staff_index))?
1440 .measures
1441 .get_mut(cmd.measure_index)
1442 .ok_or(Error::MeasureNotFound(cmd.measure_index))?
1443 .voices
1444 .get_mut(cmd.voice)
1445 .ok_or(Error::VoiceOutOfRange(cmd.voice))?;
1446
1447 let note = if cmd.is_rest {
1448 let mut n = Note::rest(cmd.duration.clone());
1449 n.dot_count = cmd.dot_count;
1450 n.tuplet = cmd.tuplet.clone();
1451 n
1452 } else {
1453 let pitch = cmd
1454 .pitch
1455 .clone()
1456 .ok_or_else(|| Error::InvalidCommand("pitch required for non-rest note".into()))?;
1457 let mut n = Note::new(pitch, cmd.duration.clone());
1458 n.dot_count = cmd.dot_count;
1459 n.tuplet = cmd.tuplet.clone();
1460 n
1461 };
1462
1463 let pos = cmd.position.min(voice.len());
1464 voice.insert(pos, note);
1465 trim_voice_to_measure(voice, ts_beats);
1466 Ok(())
1467}
1468
1469fn apply_add_pitch(cmd: &AddPitchCmd, score: &mut Score) -> Result<(), Error> {
1470 let voice = score
1471 .parts
1472 .get_mut(cmd.part_index)
1473 .ok_or(Error::PartNotFound(cmd.part_index))?
1474 .staves
1475 .get_mut(cmd.staff_index)
1476 .ok_or(Error::StaffNotFound(cmd.staff_index))?
1477 .measures
1478 .get_mut(cmd.measure_index)
1479 .ok_or(Error::MeasureNotFound(cmd.measure_index))?
1480 .voices
1481 .get_mut(cmd.voice)
1482 .ok_or(Error::VoiceOutOfRange(cmd.voice))?;
1483 let note = voice
1484 .get_mut(cmd.note_index)
1485 .ok_or(Error::NoteNotFound(cmd.note_index))?;
1486 if note.is_rest {
1487 return Err(Error::InvalidCommand("cannot add pitch to a rest".into()));
1488 }
1489 if !note
1490 .pitches
1491 .iter()
1492 .any(|p| p.step == cmd.pitch.step && p.octave == cmd.pitch.octave)
1493 {
1494 note.pitches.push(cmd.pitch.clone());
1495 }
1496 Ok(())
1497}
1498
1499fn apply_set_duration(cmd: &SetDurationCmd, score: &mut Score) -> Result<(), Error> {
1500 let ts_beats = score.settings.time_signature.total_beats();
1501 let voice = score
1502 .parts
1503 .get_mut(cmd.part_index)
1504 .ok_or(Error::PartNotFound(cmd.part_index))?
1505 .staves
1506 .get_mut(cmd.staff_index)
1507 .ok_or(Error::StaffNotFound(cmd.staff_index))?
1508 .measures
1509 .get_mut(cmd.measure_index)
1510 .ok_or(Error::MeasureNotFound(cmd.measure_index))?
1511 .voices
1512 .get_mut(cmd.voice)
1513 .ok_or(Error::VoiceOutOfRange(cmd.voice))?;
1514 let note = voice
1515 .get_mut(cmd.note_index)
1516 .ok_or(Error::NoteNotFound(cmd.note_index))?;
1517 note.duration = cmd.duration.clone();
1518 note.dot_count = cmd.dot_count;
1519 trim_voice_to_measure(voice, ts_beats);
1520 Ok(())
1521}
1522
1523fn apply_delete_note(cmd: &DeleteNoteCmd, score: &mut Score) -> Result<(), Error> {
1524 let ts_beats = score.settings.time_signature.total_beats();
1525 let voice = score
1526 .parts
1527 .get_mut(cmd.part_index)
1528 .ok_or(Error::PartNotFound(cmd.part_index))?
1529 .staves
1530 .get_mut(cmd.staff_index)
1531 .ok_or(Error::StaffNotFound(cmd.staff_index))?
1532 .measures
1533 .get_mut(cmd.measure_index)
1534 .ok_or(Error::MeasureNotFound(cmd.measure_index))?
1535 .voices
1536 .get_mut(cmd.voice)
1537 .ok_or(Error::VoiceOutOfRange(cmd.voice))?;
1538 voice.retain(|n| n.id != cmd.note_id);
1539 pad_voice_to_measure(voice, ts_beats);
1540 Ok(())
1541}
1542
1543fn apply_add_measure(cmd: &AddMeasureCmd, score: &mut Score) -> Result<(), Error> {
1544 let ts = score.settings.time_signature.clone();
1545 for part in &mut score.parts {
1546 for staff in &mut part.staves {
1547 let insert_at = (cmd.after_index + 1).min(staff.measures.len());
1548 let mut m = Measure::empty(ts.numerator, ts.denominator);
1549 m.number = insert_at as u32 + 1;
1550 staff.measures.insert(insert_at, m);
1551 for (i, measure) in staff.measures.iter_mut().enumerate() {
1552 measure.number = i as u32 + 1;
1553 }
1554 }
1555 }
1556 Ok(())
1557}
1558
1559fn apply_delete_measure(cmd: &DeleteMeasureCmd, score: &mut Score) -> Result<(), Error> {
1560 for part in &mut score.parts {
1561 for staff in &mut part.staves {
1562 if cmd.measure_index < staff.measures.len() {
1563 staff.measures.remove(cmd.measure_index);
1564 for (i, m) in staff.measures.iter_mut().enumerate() {
1565 m.number = i as u32 + 1;
1566 }
1567 }
1568 }
1569 }
1570 Ok(())
1571}
1572
1573fn apply_add_hairpin(cmd: &AddHairpinCmd, score: &mut Score) -> Result<(), Error> {
1574 let voice = score
1575 .parts
1576 .get_mut(cmd.part_index)
1577 .ok_or(Error::PartNotFound(cmd.part_index))?
1578 .staves
1579 .get_mut(cmd.staff_index)
1580 .ok_or(Error::StaffNotFound(cmd.staff_index))?
1581 .measures
1582 .get_mut(cmd.measure_index)
1583 .ok_or(Error::MeasureNotFound(cmd.measure_index))?
1584 .voices
1585 .get_mut(cmd.voice)
1586 .ok_or(Error::VoiceOutOfRange(cmd.voice))?;
1587 if cmd.start_note_idx >= voice.len() {
1588 return Err(Error::NoteNotFound(cmd.start_note_idx));
1589 }
1590 if cmd.end_note_idx >= voice.len() {
1591 return Err(Error::NoteNotFound(cmd.end_note_idx));
1592 }
1593 if cmd.start_note_idx >= cmd.end_note_idx {
1594 return Err(Error::InvalidCommand(
1595 "start_note_idx must be less than end_note_idx".into(),
1596 ));
1597 }
1598 for note in voice
1599 .iter_mut()
1600 .take(cmd.end_note_idx + 1)
1601 .skip(cmd.start_note_idx)
1602 {
1603 note.hairpin_start = None;
1604 note.hairpin_end = false;
1605 }
1606 voice[cmd.start_note_idx].hairpin_start = Some(cmd.kind);
1607 voice[cmd.end_note_idx].hairpin_end = true;
1608 Ok(())
1609}
1610
1611fn apply_add_pedal(cmd: &AddPedalCmd, score: &mut Score) -> Result<(), Error> {
1612 let voice = score
1613 .parts
1614 .get_mut(cmd.part_index)
1615 .ok_or(Error::PartNotFound(cmd.part_index))?
1616 .staves
1617 .get_mut(cmd.staff_index)
1618 .ok_or(Error::StaffNotFound(cmd.staff_index))?
1619 .measures
1620 .get_mut(cmd.measure_index)
1621 .ok_or(Error::MeasureNotFound(cmd.measure_index))?
1622 .voices
1623 .get_mut(cmd.voice)
1624 .ok_or(Error::VoiceOutOfRange(cmd.voice))?;
1625 if cmd.start_note_idx >= voice.len() {
1626 return Err(Error::NoteNotFound(cmd.start_note_idx));
1627 }
1628 if cmd.end_note_idx >= voice.len() {
1629 return Err(Error::NoteNotFound(cmd.end_note_idx));
1630 }
1631 if cmd.start_note_idx >= cmd.end_note_idx {
1632 return Err(Error::InvalidCommand(
1633 "start_note_idx must be less than end_note_idx".into(),
1634 ));
1635 }
1636 for note in voice
1637 .iter_mut()
1638 .take(cmd.end_note_idx + 1)
1639 .skip(cmd.start_note_idx)
1640 {
1641 note.pedal_start = false;
1642 note.pedal_end = false;
1643 }
1644 voice[cmd.start_note_idx].pedal_start = true;
1645 voice[cmd.end_note_idx].pedal_end = true;
1646 Ok(())
1647}
1648
1649fn apply_toggle_tie(cmd: &ToggleTieCmd, score: &mut Score) -> Result<(), Error> {
1650 let current_tie_start = {
1651 let v = score
1652 .parts
1653 .get(cmd.part_index)
1654 .ok_or(Error::PartNotFound(cmd.part_index))?
1655 .staves
1656 .get(cmd.staff_index)
1657 .ok_or(Error::StaffNotFound(cmd.staff_index))?
1658 .measures
1659 .get(cmd.measure_index)
1660 .ok_or(Error::MeasureNotFound(cmd.measure_index))?
1661 .voices
1662 .get(cmd.voice)
1663 .ok_or(Error::VoiceOutOfRange(cmd.voice))?;
1664 v.get(cmd.note_index)
1665 .ok_or(Error::NoteNotFound(cmd.note_index))?
1666 .tie_start
1667 };
1668 let voice_len = score.parts[cmd.part_index].staves[cmd.staff_index].measures[cmd.measure_index]
1669 .voices[cmd.voice]
1670 .len();
1671 let total_measures = score.parts[cmd.part_index].staves[cmd.staff_index]
1672 .measures
1673 .len();
1674
1675 let new_tie = !current_tie_start;
1676 score.parts[cmd.part_index].staves[cmd.staff_index].measures[cmd.measure_index].voices
1677 [cmd.voice][cmd.note_index]
1678 .tie_start = new_tie;
1679
1680 if cmd.note_index + 1 < voice_len {
1681 score.parts[cmd.part_index].staves[cmd.staff_index].measures[cmd.measure_index].voices
1682 [cmd.voice][cmd.note_index + 1]
1683 .tie_end = new_tie;
1684 } else {
1685 let next_mi = cmd.measure_index + 1;
1686 if next_mi < total_measures {
1687 let next_voice = &mut score.parts[cmd.part_index].staves[cmd.staff_index].measures
1688 [next_mi]
1689 .voices[cmd.voice];
1690 if let Some(n) = next_voice.get_mut(0) {
1691 n.tie_end = new_tie;
1692 }
1693 }
1694 }
1695 Ok(())
1696}
1697
1698fn apply_set_dynamic(cmd: &SetDynamicCmd, score: &mut Score) -> Result<(), Error> {
1699 get_note_mut(
1700 score,
1701 cmd.part_index,
1702 cmd.staff_index,
1703 cmd.measure_index,
1704 cmd.voice,
1705 cmd.note_index,
1706 )?
1707 .dynamic = cmd.dynamic.clone();
1708 Ok(())
1709}
1710
1711fn apply_toggle_articulation(cmd: &ToggleArticulationCmd, score: &mut Score) -> Result<(), Error> {
1712 let note = get_note_mut(
1713 score,
1714 cmd.part_index,
1715 cmd.staff_index,
1716 cmd.measure_index,
1717 cmd.voice,
1718 cmd.note_index,
1719 )?;
1720 if let Some(pos) = note
1721 .articulations
1722 .iter()
1723 .position(|a| a == &cmd.articulation)
1724 {
1725 note.articulations.remove(pos);
1726 } else {
1727 note.articulations.push(cmd.articulation.clone());
1728 }
1729 Ok(())
1730}
1731
1732fn apply_set_time_signature(cmd: &SetTimeSignatureCmd, score: &mut Score) -> Result<(), Error> {
1733 if cmd.numerator == 0 || cmd.denominator == 0 {
1734 return Err(Error::InvalidCommand(
1735 "time signature numerator and denominator must be > 0".into(),
1736 ));
1737 }
1738 if ![1u8, 2, 4, 8, 16, 32].contains(&cmd.denominator) {
1739 return Err(Error::InvalidCommand(format!(
1740 "invalid time signature denominator: {}",
1741 cmd.denominator
1742 )));
1743 }
1744 score.settings.time_signature = TimeSignature {
1745 numerator: cmd.numerator,
1746 denominator: cmd.denominator,
1747 };
1748 let max_beats = score.settings.time_signature.total_beats();
1749 for part in &mut score.parts {
1750 for staff in &mut part.staves {
1751 for measure in &mut staff.measures {
1752 for voice in &mut measure.voices {
1753 trim_voice_to_measure(voice, max_beats);
1754 pad_voice_to_measure(voice, max_beats);
1755 }
1756 }
1757 }
1758 }
1759 Ok(())
1760}
1761
1762fn apply_set_barline(cmd: &SetBarlineCmd, score: &mut Score) -> Result<(), Error> {
1763 for part in &mut score.parts {
1764 for staff in &mut part.staves {
1765 let measure = staff
1766 .measures
1767 .get_mut(cmd.measure_index)
1768 .ok_or(Error::MeasureNotFound(cmd.measure_index))?;
1769 match cmd.side.as_str() {
1770 "left" => measure.barline_left = cmd.barline.clone(),
1771 "right" => measure.barline_right = cmd.barline.clone(),
1772 _ => {
1773 return Err(Error::InvalidCommand(format!(
1774 "invalid barline side: '{}'",
1775 cmd.side
1776 )));
1777 }
1778 }
1779 }
1780 }
1781 Ok(())
1782}
1783
1784fn apply_add_part(cmd: &AddPartCmd, score: &mut Score) -> Result<(), Error> {
1785 if cmd.clefs.is_empty() {
1786 return Err(Error::InvalidCommand(
1787 "AddPart requires at least one clef".into(),
1788 ));
1789 }
1790 let measure_count = score.measure_count();
1791 let ts = score.settings.time_signature.clone();
1792 let mut part = Part::new(&cmd.name, &cmd.short_name);
1793 part.midi_channel = cmd.midi_channel.min(15);
1794 part.midi_program = cmd.midi_program;
1795 for clef_str in &cmd.clefs {
1796 let clef = match clef_str.as_str() {
1797 "Bass" => Clef::Bass,
1798 "Alto" => Clef::Alto,
1799 "Tenor" => Clef::Tenor,
1800 "Percussion" => Clef::Percussion,
1801 _ => Clef::Treble,
1802 };
1803 let mut staff = Staff::new(clef);
1804 for i in 0..measure_count {
1805 let mut m = Measure::empty(ts.numerator, ts.denominator);
1806 m.number = i as u32 + 1;
1807 staff.measures.push(m);
1808 }
1809 part.staves.push(staff);
1810 }
1811 score.parts.push(part);
1812 Ok(())
1813}
1814
1815fn apply_set_clef(cmd: &SetClefCmd, score: &mut Score) -> Result<(), Error> {
1816 score
1817 .parts
1818 .get_mut(cmd.part_index)
1819 .ok_or(Error::PartNotFound(cmd.part_index))?
1820 .staves
1821 .get_mut(cmd.staff_index)
1822 .ok_or(Error::StaffNotFound(cmd.staff_index))?
1823 .clef = cmd.clef.clone();
1824 Ok(())
1825}
1826
1827fn apply_set_part_name(cmd: &SetPartNameCmd, score: &mut Score) -> Result<(), Error> {
1828 let part = score
1829 .parts
1830 .get_mut(cmd.part_index)
1831 .ok_or(Error::PartNotFound(cmd.part_index))?;
1832 part.name = cmd.name.clone();
1833 part.short_name = cmd.short_name.clone();
1834 Ok(())
1835}
1836
1837fn apply_delete_part(cmd: &DeletePartCmd, score: &mut Score) -> Result<(), Error> {
1838 if cmd.part_index >= score.parts.len() {
1839 return Err(Error::PartNotFound(cmd.part_index));
1840 }
1841 score.parts.remove(cmd.part_index);
1842 Ok(())
1843}
1844
1845fn apply_set_metadata(cmd: &SetMetadataCmd, score: &mut Score) -> Result<(), Error> {
1846 if let Some(v) = &cmd.title {
1847 score.metadata.title = v.clone();
1848 }
1849 if let Some(v) = &cmd.composer {
1850 score.metadata.composer = v.clone();
1851 }
1852 if let Some(v) = &cmd.lyricist {
1853 score.metadata.lyricist = v.clone();
1854 }
1855 if let Some(v) = &cmd.copyright {
1856 score.metadata.copyright = v.clone();
1857 }
1858 if let Some(v) = &cmd.work_number {
1859 score.metadata.work_number = v.clone();
1860 }
1861 if let Some(v) = &cmd.movement_title {
1862 score.metadata.movement_title = v.clone();
1863 }
1864 Ok(())
1865}
1866
1867fn apply_set_midi_instrument(cmd: &SetMidiInstrumentCmd, score: &mut Score) -> Result<(), Error> {
1868 let part = score
1869 .parts
1870 .get_mut(cmd.part_index)
1871 .ok_or(Error::PartNotFound(cmd.part_index))?;
1872 part.midi_channel = cmd.midi_channel.min(15);
1873 part.midi_program = cmd.midi_program;
1874 Ok(())
1875}
1876
1877fn apply_set_transpose(cmd: &SetTransposeCmd, score: &mut Score) -> Result<(), Error> {
1878 score
1879 .parts
1880 .get_mut(cmd.part_index)
1881 .ok_or(Error::PartNotFound(cmd.part_index))?
1882 .staves
1883 .get_mut(cmd.staff_index)
1884 .ok_or(Error::StaffNotFound(cmd.staff_index))?
1885 .transpose_semitones = cmd.semitones;
1886 Ok(())
1887}
1888
1889fn apply_paste_voice(cmd: &PasteVoiceCmd, score: &mut Score) -> Result<(), Error> {
1890 let voice = score
1891 .parts
1892 .get_mut(cmd.part_index)
1893 .ok_or(Error::PartNotFound(cmd.part_index))?
1894 .staves
1895 .get_mut(cmd.staff_index)
1896 .ok_or(Error::StaffNotFound(cmd.staff_index))?
1897 .measures
1898 .get_mut(cmd.measure_index)
1899 .ok_or(Error::MeasureNotFound(cmd.measure_index))?
1900 .voices
1901 .get_mut(cmd.voice_index)
1902 .ok_or(Error::VoiceOutOfRange(cmd.voice_index))?;
1903 *voice = cmd.notes.clone();
1904 Ok(())
1905}
1906
1907fn apply_paste_range(cmd: &PasteRangeCmd, score: &mut Score) -> Result<(), Error> {
1908 let part = score
1909 .parts
1910 .get_mut(cmd.part_index)
1911 .ok_or(Error::PartNotFound(cmd.part_index))?;
1912 let staff = part
1913 .staves
1914 .get_mut(cmd.staff_index)
1915 .ok_or(Error::StaffNotFound(cmd.staff_index))?;
1916 if cmd.voice_index >= 4 {
1917 return Err(Error::VoiceOutOfRange(cmd.voice_index));
1918 }
1919 for (offset, notes) in cmd.measures.iter().enumerate() {
1920 let mi = cmd.target_measure + offset;
1921 let measure = staff
1922 .measures
1923 .get_mut(mi)
1924 .ok_or(Error::MeasureNotFound(mi))?;
1925 measure.voices[cmd.voice_index] = notes.clone();
1926 }
1927 Ok(())
1928}
1929
1930fn apply_toggle_slur(cmd: &ToggleSlurCmd, score: &mut Score) -> Result<(), Error> {
1931 let new_start = !{
1932 score
1933 .parts
1934 .get(cmd.start.part)
1935 .ok_or(Error::PartNotFound(cmd.start.part))?
1936 .staves
1937 .get(cmd.start.staff)
1938 .ok_or(Error::StaffNotFound(cmd.start.staff))?
1939 .measures
1940 .get(cmd.start.measure)
1941 .ok_or(Error::MeasureNotFound(cmd.start.measure))?
1942 .voices
1943 .get(cmd.start.voice)
1944 .ok_or(Error::VoiceOutOfRange(cmd.start.voice))?
1945 .get(cmd.start.note)
1946 .ok_or(Error::NoteNotFound(cmd.start.note))?
1947 .slur_start
1948 };
1949 let new_end = !{
1950 score
1951 .parts
1952 .get(cmd.end.part)
1953 .ok_or(Error::PartNotFound(cmd.end.part))?
1954 .staves
1955 .get(cmd.end.staff)
1956 .ok_or(Error::StaffNotFound(cmd.end.staff))?
1957 .measures
1958 .get(cmd.end.measure)
1959 .ok_or(Error::MeasureNotFound(cmd.end.measure))?
1960 .voices
1961 .get(cmd.end.voice)
1962 .ok_or(Error::VoiceOutOfRange(cmd.end.voice))?
1963 .get(cmd.end.note)
1964 .ok_or(Error::NoteNotFound(cmd.end.note))?
1965 .slur_end
1966 };
1967 score.parts[cmd.start.part].staves[cmd.start.staff].measures[cmd.start.measure].voices
1968 [cmd.start.voice][cmd.start.note]
1969 .slur_start = new_start;
1970 score.parts[cmd.end.part].staves[cmd.end.staff].measures[cmd.end.measure].voices
1971 [cmd.end.voice][cmd.end.note]
1972 .slur_end = new_end;
1973 Ok(())
1974}
1975
1976fn apply_toggle_trill_line(cmd: &ToggleTrillLineCmd, score: &mut Score) -> Result<(), Error> {
1977 let new_start = !{
1978 score
1979 .parts
1980 .get(cmd.start.part)
1981 .ok_or(Error::PartNotFound(cmd.start.part))?
1982 .staves
1983 .get(cmd.start.staff)
1984 .ok_or(Error::StaffNotFound(cmd.start.staff))?
1985 .measures
1986 .get(cmd.start.measure)
1987 .ok_or(Error::MeasureNotFound(cmd.start.measure))?
1988 .voices
1989 .get(cmd.start.voice)
1990 .ok_or(Error::VoiceOutOfRange(cmd.start.voice))?
1991 .get(cmd.start.note)
1992 .ok_or(Error::NoteNotFound(cmd.start.note))?
1993 .trill_line_start
1994 };
1995 let new_end = !{
1996 score
1997 .parts
1998 .get(cmd.end.part)
1999 .ok_or(Error::PartNotFound(cmd.end.part))?
2000 .staves
2001 .get(cmd.end.staff)
2002 .ok_or(Error::StaffNotFound(cmd.end.staff))?
2003 .measures
2004 .get(cmd.end.measure)
2005 .ok_or(Error::MeasureNotFound(cmd.end.measure))?
2006 .voices
2007 .get(cmd.end.voice)
2008 .ok_or(Error::VoiceOutOfRange(cmd.end.voice))?
2009 .get(cmd.end.note)
2010 .ok_or(Error::NoteNotFound(cmd.end.note))?
2011 .trill_line_end
2012 };
2013 score.parts[cmd.start.part].staves[cmd.start.staff].measures[cmd.start.measure].voices
2014 [cmd.start.voice][cmd.start.note]
2015 .trill_line_start = new_start;
2016 score.parts[cmd.end.part].staves[cmd.end.staff].measures[cmd.end.measure].voices
2017 [cmd.end.voice][cmd.end.note]
2018 .trill_line_end = new_end;
2019 Ok(())
2020}
2021
2022fn apply_add_staff(cmd: &AddStaffCmd, score: &mut Score) -> Result<(), Error> {
2023 let ts = score.settings.time_signature.clone();
2024 let measure_count = score
2025 .parts
2026 .get(cmd.part_index)
2027 .ok_or(Error::PartNotFound(cmd.part_index))?
2028 .staves
2029 .first()
2030 .map_or(0, |s| s.measures.len());
2031 let mut staff = Staff::new(cmd.clef.clone());
2032 for i in 0..measure_count {
2033 let mut m = Measure::empty(ts.numerator, ts.denominator);
2034 m.number = i as u32 + 1;
2035 staff.measures.push(m);
2036 }
2037 score.parts[cmd.part_index].staves.push(staff);
2038 Ok(())
2039}
2040
2041fn apply_delete_staff(cmd: &DeleteStaffCmd, score: &mut Score) -> Result<(), Error> {
2042 let part = score
2043 .parts
2044 .get_mut(cmd.part_index)
2045 .ok_or(Error::PartNotFound(cmd.part_index))?;
2046 if part.staves.len() <= 1 {
2047 return Err(Error::CannotDeleteLastStaff);
2048 }
2049 if cmd.staff_index >= part.staves.len() {
2050 return Err(Error::StaffNotFound(cmd.staff_index));
2051 }
2052 part.staves.remove(cmd.staff_index);
2053 Ok(())
2054}
2055
2056fn trim_voice_to_measure(voice: &mut Vec<Note>, max_beats: f64) {
2057 let mut total = 0.0f64;
2058 let mut cutoff = voice.len();
2059 for (i, n) in voice.iter().enumerate() {
2060 total += n.beats();
2061 if total > max_beats + 1e-9 {
2062 cutoff = i;
2063 break;
2064 }
2065 }
2066 voice.truncate(cutoff);
2067 pad_voice_to_measure(voice, max_beats);
2068}
2069
2070fn pad_voice_to_measure(voice: &mut Vec<Note>, max_beats: f64) {
2071 let mut used: f64 = voice.iter().map(|n| n.beats()).sum();
2072 while max_beats - used > 1e-9 {
2073 let remaining = max_beats - used;
2074 let rest = Note::rest(Duration::whole_filling_beats(remaining));
2075 used += rest.beats();
2076 voice.push(rest);
2077 }
2078}
2079
2080#[cfg(test)]
2081mod tests {
2082 use super::*;
2083 use crate::model::pitch::Step;
2084
2085 fn default_engine_score() -> Score {
2086 let mut s = Score::default();
2087 for part in &mut s.parts {
2088 for staff in &mut part.staves {
2089 for (i, m) in staff.measures.iter_mut().enumerate() {
2090 m.number = i as u32 + 1;
2091 }
2092 }
2093 }
2094 s
2095 }
2096
2097 #[test]
2098 fn add_note_inserts_into_voice() {
2099 let mut score = default_engine_score();
2100 let cmd = Command::AddNote(AddNoteCmd {
2101 part_index: 0,
2102 staff_index: 0,
2103 measure_index: 0,
2104 voice: 0,
2105 position: 0,
2106 pitch: Some(Pitch::new(Step::C, 4)),
2107 duration: Duration::Quarter,
2108 dot_count: 0,
2109 is_rest: false,
2110 tuplet: None,
2111 });
2112 apply_command(&cmd, &mut score).unwrap();
2113 let first = &score.parts[0].staves[0].measures[0].voices[0][0];
2114 assert!(!first.is_rest);
2115 assert_eq!(first.pitches[0].step, Step::C);
2116 }
2117
2118 #[test]
2119 fn set_duration_updates_note_and_preserves_measure_capacity() {
2120 let mut score = default_engine_score();
2121 apply_command(
2122 &Command::AddNote(AddNoteCmd {
2123 part_index: 0,
2124 staff_index: 0,
2125 measure_index: 0,
2126 voice: 0,
2127 position: 0,
2128 pitch: Some(Pitch::new(Step::C, 4)),
2129 duration: Duration::Quarter,
2130 dot_count: 0,
2131 is_rest: false,
2132 tuplet: None,
2133 }),
2134 &mut score,
2135 )
2136 .unwrap();
2137 apply_command(
2138 &Command::SetDuration(SetDurationCmd {
2139 part_index: 0,
2140 staff_index: 0,
2141 measure_index: 0,
2142 voice: 0,
2143 note_index: 0,
2144 duration: Duration::Half,
2145 dot_count: 1,
2146 }),
2147 &mut score,
2148 )
2149 .unwrap();
2150 let voice = &score.parts[0].staves[0].measures[0].voices[0];
2151 assert_eq!(voice[0].duration, Duration::Half);
2152 assert_eq!(voice[0].dot_count, 1);
2153 assert!((voice.iter().map(|note| note.beats()).sum::<f64>() - 4.0).abs() < 1e-9);
2154 }
2155
2156 #[test]
2157 fn set_tempo_updates_score() {
2158 let mut score = default_engine_score();
2159 let cmd = Command::SetTempo(SetTempoCmd { bpm: 160 });
2160 apply_command(&cmd, &mut score).unwrap();
2161 assert_eq!(score.settings.tempo_bpm, 160);
2162 }
2163
2164 #[test]
2165 fn add_measure_increases_count() {
2166 let mut score = default_engine_score();
2167 let before = score.measure_count();
2168 apply_command(
2169 &Command::AddMeasure(AddMeasureCmd { after_index: 0 }),
2170 &mut score,
2171 )
2172 .unwrap();
2173 assert_eq!(score.measure_count(), before + 1);
2174 }
2175
2176 #[test]
2177 fn delete_measure_decreases_count() {
2178 let mut score = default_engine_score();
2179 let before = score.measure_count();
2180 apply_command(
2181 &Command::DeleteMeasure(DeleteMeasureCmd { measure_index: 0 }),
2182 &mut score,
2183 )
2184 .unwrap();
2185 assert_eq!(score.measure_count(), before - 1);
2186 }
2187
2188 #[test]
2189 fn undo_restores_score() {
2190 let mut stack = CommandStack::new(50);
2191 let mut score = default_engine_score();
2192 let before = score.settings.tempo_bpm;
2193 stack
2194 .execute(Command::SetTempo(SetTempoCmd { bpm: 200 }), &mut score)
2195 .unwrap();
2196 assert_eq!(score.settings.tempo_bpm, 200);
2197 stack.undo(&mut score).unwrap();
2198 assert_eq!(score.settings.tempo_bpm, before);
2199 }
2200
2201 #[test]
2202 fn redo_reapplies_command() {
2203 let mut stack = CommandStack::new(50);
2204 let mut score = default_engine_score();
2205 stack
2206 .execute(Command::SetTempo(SetTempoCmd { bpm: 200 }), &mut score)
2207 .unwrap();
2208 stack.undo(&mut score).unwrap();
2209 stack.redo(&mut score).unwrap();
2210 assert_eq!(score.settings.tempo_bpm, 200);
2211 }
2212
2213 #[test]
2214 fn undo_nothing_returns_error() {
2215 let mut stack = CommandStack::new(50);
2216 let mut score = default_engine_score();
2217 assert!(stack.undo(&mut score).is_err());
2218 }
2219
2220 #[test]
2221 fn add_part_appends_part() {
2222 let mut score = default_engine_score();
2223 let before = score.parts.len();
2224 apply_command(
2225 &Command::AddPart(AddPartCmd {
2226 name: "Violin".into(),
2227 short_name: "Vln.".into(),
2228 clefs: vec!["Treble".into()],
2229 midi_channel: 0,
2230 midi_program: 0,
2231 }),
2232 &mut score,
2233 )
2234 .unwrap();
2235 assert_eq!(score.parts.len(), before + 1);
2236 }
2237
2238 #[test]
2239 fn delete_part_removes_part() {
2240 let mut score = default_engine_score();
2241 apply_command(
2242 &Command::AddPart(AddPartCmd {
2243 name: "Violin".into(),
2244 short_name: "V.".into(),
2245 clefs: vec!["Treble".into()],
2246 midi_channel: 0,
2247 midi_program: 0,
2248 }),
2249 &mut score,
2250 )
2251 .unwrap();
2252 let before = score.parts.len();
2253 apply_command(
2254 &Command::DeletePart(DeletePartCmd { part_index: 0 }),
2255 &mut score,
2256 )
2257 .unwrap();
2258 assert_eq!(score.parts.len(), before - 1);
2259 }
2260
2261 #[test]
2262 fn delete_part_out_of_range_returns_err() {
2263 let mut score = default_engine_score();
2264 assert!(
2265 apply_command(
2266 &Command::DeletePart(DeletePartCmd { part_index: 99 }),
2267 &mut score
2268 )
2269 .is_err()
2270 );
2271 }
2272
2273 #[test]
2274 fn delete_part_undo_restores_part() {
2275 let mut stack = CommandStack::new(50);
2276 let mut score = default_engine_score();
2277 apply_command(
2278 &Command::AddPart(AddPartCmd {
2279 name: "Violin".into(),
2280 short_name: "V.".into(),
2281 clefs: vec!["Treble".into()],
2282 midi_channel: 0,
2283 midi_program: 0,
2284 }),
2285 &mut score,
2286 )
2287 .unwrap();
2288 let before = score.parts.len();
2289 stack
2290 .execute(
2291 Command::DeletePart(DeletePartCmd { part_index: 0 }),
2292 &mut score,
2293 )
2294 .unwrap();
2295 assert_eq!(score.parts.len(), before - 1);
2296 stack.undo(&mut score).unwrap();
2297 assert_eq!(score.parts.len(), before);
2298 }
2299
2300 #[test]
2301 fn set_metadata_updates_title() {
2302 let mut score = default_engine_score();
2303 apply_command(
2304 &Command::SetMetadata(SetMetadataCmd {
2305 title: Some("New Title".into()),
2306 ..Default::default()
2307 }),
2308 &mut score,
2309 )
2310 .unwrap();
2311 assert_eq!(score.metadata.title, "New Title");
2312 }
2313
2314 #[test]
2315 fn set_metadata_none_fields_skipped() {
2316 let mut score = default_engine_score();
2317 let original_composer = score.metadata.composer.clone();
2318 apply_command(
2319 &Command::SetMetadata(SetMetadataCmd {
2320 title: Some("X".into()),
2321 ..Default::default()
2322 }),
2323 &mut score,
2324 )
2325 .unwrap();
2326 assert_eq!(score.metadata.composer, original_composer);
2327 }
2328
2329 #[test]
2330 fn set_volta_sets_bracket() {
2331 use crate::model::score::VoltaBracket;
2332 let mut score = default_engine_score();
2333 let volta = VoltaBracket {
2334 number: 1,
2335 kind: "begin_end".into(),
2336 };
2337 apply_command(
2338 &Command::SetVolta(SetVoltaCmd {
2339 measure_index: 0,
2340 volta: Some(volta.clone()),
2341 }),
2342 &mut score,
2343 )
2344 .unwrap();
2345 assert!(score.parts[0].staves[0].measures[0].volta.is_some());
2346 }
2347
2348 #[test]
2349 fn set_volta_none_clears_bracket() {
2350 use crate::model::score::VoltaBracket;
2351 let mut score = default_engine_score();
2352 score.parts[0].staves[0].measures[0].volta = Some(VoltaBracket {
2353 number: 1,
2354 kind: "begin_end".into(),
2355 });
2356 apply_command(
2357 &Command::SetVolta(SetVoltaCmd {
2358 measure_index: 0,
2359 volta: None,
2360 }),
2361 &mut score,
2362 )
2363 .unwrap();
2364 assert!(score.parts[0].staves[0].measures[0].volta.is_none());
2365 }
2366
2367 #[test]
2368 fn set_volta_undo_restores_old() {
2369 use crate::model::score::VoltaBracket;
2370 let mut stack = CommandStack::new(50);
2371 let mut score = default_engine_score();
2372 stack
2373 .execute(
2374 Command::SetVolta(SetVoltaCmd {
2375 measure_index: 0,
2376 volta: Some(VoltaBracket {
2377 number: 1,
2378 kind: "begin_end".into(),
2379 }),
2380 }),
2381 &mut score,
2382 )
2383 .unwrap();
2384 stack.undo(&mut score).unwrap();
2385 assert!(score.parts[0].staves[0].measures[0].volta.is_none());
2386 }
2387
2388 #[test]
2389 fn set_clef_updates_staff_clef() {
2390 use crate::model::notation::Clef;
2391 let mut score = default_engine_score();
2392 apply_command(
2393 &Command::SetClef(SetClefCmd {
2394 part_index: 0,
2395 staff_index: 0,
2396 clef: Clef::Bass,
2397 }),
2398 &mut score,
2399 )
2400 .unwrap();
2401 assert_eq!(score.parts[0].staves[0].clef, Clef::Bass);
2402 }
2403
2404 #[test]
2405 fn set_clef_out_of_range_returns_err() {
2406 use crate::model::notation::Clef;
2407 let mut score = default_engine_score();
2408 assert!(
2409 apply_command(
2410 &Command::SetClef(SetClefCmd {
2411 part_index: 99,
2412 staff_index: 0,
2413 clef: Clef::Bass,
2414 }),
2415 &mut score
2416 )
2417 .is_err()
2418 );
2419 }
2420
2421 #[test]
2422 fn set_part_name_updates_name() {
2423 let mut score = default_engine_score();
2424 apply_command(
2425 &Command::SetPartName(SetPartNameCmd {
2426 part_index: 0,
2427 name: "Violin".into(),
2428 short_name: "Vln.".into(),
2429 }),
2430 &mut score,
2431 )
2432 .unwrap();
2433 assert_eq!(score.parts[0].name, "Violin");
2434 assert_eq!(score.parts[0].short_name, "Vln.");
2435 }
2436
2437 #[test]
2438 fn set_part_name_undo_restores_old() {
2439 let mut stack = CommandStack::new(50);
2440 let mut score = default_engine_score();
2441 let original = score.parts[0].name.clone();
2442 stack
2443 .execute(
2444 Command::SetPartName(SetPartNameCmd {
2445 part_index: 0,
2446 name: "Flute".into(),
2447 short_name: "Fl.".into(),
2448 }),
2449 &mut score,
2450 )
2451 .unwrap();
2452 stack.undo(&mut score).unwrap();
2453 assert_eq!(score.parts[0].name, original);
2454 }
2455
2456 #[test]
2457 fn set_metadata_undo_restores_old_title() {
2458 let mut stack = CommandStack::new(50);
2459 let mut score = default_engine_score();
2460 let original = score.metadata.title.clone();
2461 stack
2462 .execute(
2463 Command::SetMetadata(SetMetadataCmd {
2464 title: Some("Changed".into()),
2465 ..Default::default()
2466 }),
2467 &mut score,
2468 )
2469 .unwrap();
2470 assert_ne!(score.metadata.title, original);
2471 stack.undo(&mut score).unwrap();
2472 assert_eq!(score.metadata.title, original);
2473 }
2474
2475 #[test]
2476 fn set_midi_instrument_updates_channel_and_program() {
2477 let mut score = default_engine_score();
2478 apply_command(
2479 &Command::SetMidiInstrument(SetMidiInstrumentCmd {
2480 part_index: 0,
2481 midi_channel: 2,
2482 midi_program: 40,
2483 }),
2484 &mut score,
2485 )
2486 .unwrap();
2487 assert_eq!(score.parts[0].midi_channel, 2);
2488 assert_eq!(score.parts[0].midi_program, 40);
2489 }
2490
2491 #[test]
2492 fn set_midi_instrument_clamps_channel_to_15() {
2493 let mut score = default_engine_score();
2494 apply_command(
2495 &Command::SetMidiInstrument(SetMidiInstrumentCmd {
2496 part_index: 0,
2497 midi_channel: 20,
2498 midi_program: 0,
2499 }),
2500 &mut score,
2501 )
2502 .unwrap();
2503 assert_eq!(score.parts[0].midi_channel, 15);
2504 }
2505
2506 #[test]
2507 fn set_midi_instrument_undo_restores_old() {
2508 let mut stack = CommandStack::new(50);
2509 let mut score = default_engine_score();
2510 score.parts[0].midi_channel = 3;
2511 score.parts[0].midi_program = 10;
2512 stack
2513 .execute(
2514 Command::SetMidiInstrument(SetMidiInstrumentCmd {
2515 part_index: 0,
2516 midi_channel: 9,
2517 midi_program: 114,
2518 }),
2519 &mut score,
2520 )
2521 .unwrap();
2522 stack.undo(&mut score).unwrap();
2523 assert_eq!(score.parts[0].midi_channel, 3);
2524 assert_eq!(score.parts[0].midi_program, 10);
2525 }
2526
2527 #[test]
2528 fn set_transpose_updates_staff() {
2529 let mut score = default_engine_score();
2530 apply_command(
2531 &Command::SetTranspose(SetTransposeCmd {
2532 part_index: 0,
2533 staff_index: 0,
2534 semitones: -2,
2535 }),
2536 &mut score,
2537 )
2538 .unwrap();
2539 assert_eq!(score.parts[0].staves[0].transpose_semitones, -2);
2540 }
2541
2542 #[test]
2543 fn set_transpose_out_of_range_returns_err() {
2544 let mut score = default_engine_score();
2545 assert!(
2546 apply_command(
2547 &Command::SetTranspose(SetTransposeCmd {
2548 part_index: 99,
2549 staff_index: 0,
2550 semitones: -2,
2551 }),
2552 &mut score
2553 )
2554 .is_err()
2555 );
2556 }
2557
2558 #[test]
2559 fn set_tempo_at_measure_sets_tempo() {
2560 let mut score = default_engine_score();
2561 apply_command(
2562 &Command::SetTempoAtMeasure(SetTempoAtMeasureCmd {
2563 measure_index: 0,
2564 bpm: Some(80),
2565 }),
2566 &mut score,
2567 )
2568 .unwrap();
2569 assert_eq!(score.parts[0].staves[0].measures[0].tempo, Some(80));
2570 }
2571
2572 #[test]
2573 fn set_tempo_at_measure_none_clears_tempo() {
2574 let mut score = default_engine_score();
2575 score.parts[0].staves[0].measures[0].tempo = Some(120);
2576 apply_command(
2577 &Command::SetTempoAtMeasure(SetTempoAtMeasureCmd {
2578 measure_index: 0,
2579 bpm: None,
2580 }),
2581 &mut score,
2582 )
2583 .unwrap();
2584 assert!(score.parts[0].staves[0].measures[0].tempo.is_none());
2585 }
2586
2587 #[test]
2590 fn batch_execute_two_commands_single_undo() {
2591 let mut stack = CommandStack::new(50);
2592 let mut score = default_engine_score();
2593 let original_bpm = score.settings.tempo_bpm;
2594 stack
2595 .batch_execute(
2596 vec![
2597 Command::SetTempo(SetTempoCmd { bpm: 160 }),
2598 Command::SetTempo(SetTempoCmd { bpm: 180 }),
2599 ],
2600 &mut score,
2601 )
2602 .unwrap();
2603 assert_eq!(score.settings.tempo_bpm, 180);
2604 stack.undo(&mut score).unwrap();
2605 assert_eq!(score.settings.tempo_bpm, original_bpm);
2606 }
2607
2608 #[test]
2609 fn batch_execute_partial_failure_rollback() {
2610 let mut stack = CommandStack::new(50);
2611 let mut score = default_engine_score();
2612 let original_bpm = score.settings.tempo_bpm;
2613 let result = stack.batch_execute(
2614 vec![
2615 Command::SetTempo(SetTempoCmd { bpm: 160 }),
2616 Command::DeleteNote(DeleteNoteCmd {
2617 note_id: "nonexistent".into(),
2618 part_index: 99,
2619 staff_index: 0,
2620 measure_index: 0,
2621 voice: 0,
2622 }),
2623 ],
2624 &mut score,
2625 );
2626 assert!(result.is_err());
2627 assert_eq!(score.settings.tempo_bpm, original_bpm);
2628 }
2629
2630 #[test]
2631 fn batch_execute_empty_is_noop() {
2632 let mut stack = CommandStack::new(50);
2633 let mut score = default_engine_score();
2634 stack.batch_execute(vec![], &mut score).unwrap();
2635 assert!(!stack.can_undo());
2636 }
2637
2638 #[test]
2641 fn batch_label_used_as_command_key() {
2642 let cmd = Command::Batch(BatchCmd {
2643 commands: vec![],
2644 label: Some("ApplyAI".to_string()),
2645 });
2646 assert_eq!(command_key(&cmd), "ApplyAI");
2647 }
2648
2649 #[test]
2650 fn batch_no_label_key_is_batch() {
2651 let cmd = Command::Batch(BatchCmd {
2652 commands: vec![],
2653 label: None,
2654 });
2655 assert_eq!(command_key(&cmd), "Batch");
2656 }
2657
2658 #[test]
2659 fn batch_label_survives_json_roundtrip() {
2660 let cmd = Command::Batch(BatchCmd {
2661 commands: vec![Command::SetTempo(SetTempoCmd { bpm: 120 })],
2662 label: Some("PasteSelection".to_string()),
2663 });
2664 let json = serde_json::to_string(&cmd).unwrap();
2665 let cmd2: Command = serde_json::from_str(&json).unwrap();
2666 assert_eq!(command_key(&cmd2), "PasteSelection");
2667 }
2668
2669 #[test]
2670 fn batch_label_in_undo_key() {
2671 let mut stack = CommandStack::new(50);
2672 let mut score = default_engine_score();
2673 let cmd = Command::Batch(BatchCmd {
2674 commands: vec![Command::SetTempo(SetTempoCmd { bpm: 140 })],
2675 label: Some("ApplyAI".to_string()),
2676 });
2677 stack.execute(cmd, &mut score).unwrap();
2678 assert_eq!(stack.undo_key(), Some("ApplyAI".to_string()));
2679 }
2680
2681 #[test]
2682 fn undo_returns_change_hint() {
2683 use crate::model::change_hint::ChangeScope;
2684 let mut stack = CommandStack::new(50);
2685 let mut score = default_engine_score();
2686 stack
2687 .execute(Command::SetTempo(SetTempoCmd { bpm: 200 }), &mut score)
2688 .unwrap();
2689 let hint = stack.undo(&mut score).unwrap();
2690 assert_eq!(hint.scope, ChangeScope::Global);
2691 assert!(hint.playback_dirty);
2692 }
2693
2694 #[test]
2695 fn redo_returns_change_hint() {
2696 use crate::model::change_hint::ChangeScope;
2697 let mut stack = CommandStack::new(50);
2698 let mut score = default_engine_score();
2699 stack
2700 .execute(Command::SetTempo(SetTempoCmd { bpm: 200 }), &mut score)
2701 .unwrap();
2702 stack.undo(&mut score).unwrap();
2703 let hint = stack.redo(&mut score).unwrap();
2704 assert_eq!(hint.scope, ChangeScope::Global);
2705 assert!(hint.playback_dirty);
2706 }
2707
2708 #[test]
2711 fn toggle_slur_sets_start_and_end() {
2712 let mut score = default_engine_score();
2713 let cmd = Command::AddNote(AddNoteCmd {
2714 part_index: 0,
2715 staff_index: 0,
2716 measure_index: 0,
2717 voice: 0,
2718 position: 0,
2719 pitch: Some(Pitch::new(Step::C, 4)),
2720 duration: Duration::Quarter,
2721 dot_count: 0,
2722 is_rest: false,
2723 tuplet: None,
2724 });
2725 apply_command(&cmd, &mut score).unwrap();
2726 apply_command(
2727 &Command::AddNote(AddNoteCmd {
2728 part_index: 0,
2729 staff_index: 0,
2730 measure_index: 0,
2731 voice: 0,
2732 position: 1,
2733 pitch: Some(Pitch::new(Step::D, 4)),
2734 duration: Duration::Quarter,
2735 dot_count: 0,
2736 is_rest: false,
2737 tuplet: None,
2738 }),
2739 &mut score,
2740 )
2741 .unwrap();
2742 let start = NoteAddr {
2743 part: 0,
2744 staff: 0,
2745 measure: 0,
2746 voice: 0,
2747 note: 0,
2748 };
2749 let end = NoteAddr {
2750 part: 0,
2751 staff: 0,
2752 measure: 0,
2753 voice: 0,
2754 note: 1,
2755 };
2756 apply_command(
2757 &Command::ToggleSlur(ToggleSlurCmd {
2758 start: start.clone(),
2759 end: end.clone(),
2760 }),
2761 &mut score,
2762 )
2763 .unwrap();
2764 assert!(score.parts[0].staves[0].measures[0].voices[0][0].slur_start);
2765 assert!(score.parts[0].staves[0].measures[0].voices[0][1].slur_end);
2766 apply_command(
2768 &Command::ToggleSlur(ToggleSlurCmd { start, end }),
2769 &mut score,
2770 )
2771 .unwrap();
2772 assert!(!score.parts[0].staves[0].measures[0].voices[0][0].slur_start);
2773 assert!(!score.parts[0].staves[0].measures[0].voices[0][1].slur_end);
2774 }
2775
2776 #[test]
2779 fn add_staff_appends_staff_with_correct_measure_count() {
2780 let mut score = default_engine_score();
2781 let before = score.parts[0].staves.len();
2782 let measure_count = score.parts[0].staves[0].measures.len();
2783 apply_command(
2784 &Command::AddStaff(AddStaffCmd {
2785 part_index: 0,
2786 clef: Clef::Bass,
2787 }),
2788 &mut score,
2789 )
2790 .unwrap();
2791 assert_eq!(score.parts[0].staves.len(), before + 1);
2792 let new_staff = score.parts[0].staves.last().unwrap();
2793 assert_eq!(new_staff.measures.len(), measure_count);
2794 }
2795
2796 #[test]
2797 fn add_staff_out_of_range_returns_err() {
2798 let mut score = default_engine_score();
2799 let result = apply_command(
2800 &Command::AddStaff(AddStaffCmd {
2801 part_index: 99,
2802 clef: Clef::Treble,
2803 }),
2804 &mut score,
2805 );
2806 assert!(result.is_err());
2807 }
2808
2809 #[test]
2810 fn delete_staff_removes_extra_staff() {
2811 let mut score = default_engine_score();
2812 apply_command(
2813 &Command::AddStaff(AddStaffCmd {
2814 part_index: 0,
2815 clef: Clef::Bass,
2816 }),
2817 &mut score,
2818 )
2819 .unwrap();
2820 assert_eq!(score.parts[0].staves.len(), 2);
2821 apply_command(
2822 &Command::DeleteStaff(DeleteStaffCmd {
2823 part_index: 0,
2824 staff_index: 1,
2825 }),
2826 &mut score,
2827 )
2828 .unwrap();
2829 assert_eq!(score.parts[0].staves.len(), 1);
2830 }
2831
2832 #[test]
2833 fn delete_last_staff_returns_err() {
2834 let mut score = default_engine_score();
2835 assert_eq!(score.parts[0].staves.len(), 1);
2836 let result = apply_command(
2837 &Command::DeleteStaff(DeleteStaffCmd {
2838 part_index: 0,
2839 staff_index: 0,
2840 }),
2841 &mut score,
2842 );
2843 assert!(result.is_err());
2844 }
2845
2846 #[test]
2849 fn set_tuplet_assigns_and_clears() {
2850 use crate::model::notation::TupletInfo;
2851 let mut score = default_engine_score();
2852 apply_command(
2853 &Command::AddNote(AddNoteCmd {
2854 part_index: 0,
2855 staff_index: 0,
2856 measure_index: 0,
2857 voice: 0,
2858 position: 0,
2859 pitch: Some(Pitch::new(Step::C, 4)),
2860 duration: Duration::Quarter,
2861 dot_count: 0,
2862 is_rest: false,
2863 tuplet: None,
2864 }),
2865 &mut score,
2866 )
2867 .unwrap();
2868 let ti = TupletInfo {
2869 actual_notes: 3,
2870 normal_notes: 2,
2871 };
2872 apply_command(
2873 &Command::SetTuplet(SetTupletCmd {
2874 part_index: 0,
2875 staff_index: 0,
2876 measure_index: 0,
2877 voice_index: 0,
2878 note_index: 0,
2879 tuplet: Some(ti.clone()),
2880 }),
2881 &mut score,
2882 )
2883 .unwrap();
2884 assert_eq!(
2885 score.parts[0].staves[0].measures[0].voices[0][0].tuplet,
2886 Some(ti)
2887 );
2888 apply_command(
2889 &Command::SetTuplet(SetTupletCmd {
2890 part_index: 0,
2891 staff_index: 0,
2892 measure_index: 0,
2893 voice_index: 0,
2894 note_index: 0,
2895 tuplet: None,
2896 }),
2897 &mut score,
2898 )
2899 .unwrap();
2900 assert!(
2901 score.parts[0].staves[0].measures[0].voices[0][0]
2902 .tuplet
2903 .is_none()
2904 );
2905 }
2906
2907 #[test]
2910 fn respell_score_cmd_changes_all_pitches() {
2911 use crate::model::pitch::Step;
2912 let mut score = default_engine_score();
2913 apply_command(
2914 &Command::AddNote(AddNoteCmd {
2915 part_index: 0,
2916 staff_index: 0,
2917 measure_index: 0,
2918 voice: 0,
2919 position: 0,
2920 pitch: Some(Pitch::with_alter(Step::C, 4, 1)), duration: Duration::Quarter,
2922 dot_count: 0,
2923 is_rest: false,
2924 tuplet: None,
2925 }),
2926 &mut score,
2927 )
2928 .unwrap();
2929 apply_command(
2930 &Command::RespellScore(RespellScoreCmd { prefer_flat: true }),
2931 &mut score,
2932 )
2933 .unwrap();
2934 let pitch = &score.parts[0].staves[0].measures[0].voices[0][0].pitches[0];
2935 assert_eq!(pitch.step, Step::D);
2936 assert_eq!(pitch.alter, -1); }
2938}