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