1use std::rc::Rc;
2use std::slice;
3
4use error_stack::IntoReport;
5use klavier_helper::bag_store::{BagStore, BagStoreEvent};
6use klavier_helper::store::{Store, StoreEvent};
7use serde::{Serialize, Deserialize};
8use serdo::undo_store::{SqliteUndoStore, UndoStore};
9use serdo::cmd::{SerializableCmd, Cmd};
10
11use crate::bar::{Bar, RepeatSet};
12use crate::ctrl_chg::CtrlChg;
13use crate::grid::Grid;
14use crate::key::Key;
15use crate::location::Location;
16use crate::models::{Models, ModelChanges};
17use crate::note::Note;
18use crate::rhythm::Rhythm;
19use crate::tempo::{TempoValue, Tempo};
20use crate::tuple;
21use crate::velocity::{Velocity, self};
22
23const DEFAULT_TEMPO: TempoValue = TempoValue::new(120);
24const DEFAULT_CTRL_CHG: Velocity = velocity::MIN;
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
27pub enum LocationError {
28 Overflow,
29}
30
31impl std::fmt::Display for LocationError {
32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 match self {
34 Self::Overflow => write!(f, "Location overflow"),
35 }
36 }
37}
38
39#[derive(PartialEq, Debug)]
40pub enum ChangeRepoType {
41 MoveSelected,
42 AdHoc,
43}
44
45#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, PartialEq, Debug, Default)]
46pub struct ModelChangeMetadata {
47 pub need_select: Option<bool>,
51 pub dragged: bool,
52}
53
54impl ModelChangeMetadata {
55 pub fn with_need_select(self, need_select: bool) -> Self {
56 Self {
57 need_select: Some(need_select),
58 dragged: self.dragged,
59 }
60 }
61}
62
63
64#[derive(Serialize, Deserialize, Clone)]
65#[serde(from = "ExportedProject", into = "ExportedProject")]
66pub struct ProjectImpl {
67 rhythm: Rhythm,
68 key: Key,
69 grid: Grid,
70 note_repo: BagStore<u32, Rc<Note>, ModelChangeMetadata>, bar_repo: Store<u32, Bar, ModelChangeMetadata>,
72 tempo_repo: Store<u32, Tempo, ModelChangeMetadata>,
73 dumper_repo: Store<u32, CtrlChg, ModelChangeMetadata>,
74 soft_repo: Store<u32, CtrlChg, ModelChangeMetadata>,
75}
76
77#[derive(Serialize, Deserialize, Clone)]
78pub struct ExportedProject {
79 rhythm: Rhythm,
80 key: Key,
81 grid: Grid,
82 models: Models,
83}
84
85impl From<ExportedProject> for ProjectImpl {
86 fn from(exported: ExportedProject) -> Self {
87 let mut note_repo: BagStore<u32, Rc<Note>, ModelChangeMetadata> = BagStore::new(true);
88 note_repo.bulk_add(exported.models.notes.into_iter().map(|n| (n.start_tick(), Rc::new(n))).collect(), ModelChangeMetadata::default());
89
90 let mut bar_repo: Store<u32, Bar, ModelChangeMetadata> = Store::new(true);
91 bar_repo.bulk_add(exported.models.bars.into_iter().map(|b| (b.start_tick, b)).collect(), ModelChangeMetadata::default());
92
93 let mut tempo_repo: Store<u32, Tempo, ModelChangeMetadata> = Store::new(true);
94 tempo_repo.bulk_add(exported.models.tempos.into_iter().map(|t| (t.start_tick, t)).collect(), ModelChangeMetadata::default());
95
96 let mut dumper_repo: Store<u32, CtrlChg, ModelChangeMetadata> = Store::new(true);
97 dumper_repo.bulk_add(exported.models.dumpers.into_iter().map(|d| (d.start_tick, d)).collect(), ModelChangeMetadata::default());
98
99 let mut soft_repo: Store<u32, CtrlChg, ModelChangeMetadata> = Store::new(true);
100 soft_repo.bulk_add(exported.models.softs.into_iter().map(|s| (s.start_tick, s)).collect(), ModelChangeMetadata::default());
101
102 ProjectImpl {
103 rhythm: exported.rhythm,
104 key: exported.key,
105 grid: exported.grid,
106 note_repo, bar_repo, tempo_repo, dumper_repo, soft_repo
107 }
108 }
109}
110
111impl From<ProjectImpl> for ExportedProject {
112 fn from(value: ProjectImpl) -> Self {
113 let mut notes: Vec<Note> = Vec::with_capacity(value.note_repo.len());
114 for (_, n) in value.note_repo.iter() {
115 notes.push((**n).clone());
116 }
117
118 let mut bars: Vec<Bar> = Vec::with_capacity(value.bar_repo.len());
119 for (_, b) in value.bar_repo.iter() {
120 bars.push(*b);
121 }
122
123 let mut tempos: Vec<Tempo> = Vec::with_capacity(value.tempo_repo.len());
124 for (_, t) in value.tempo_repo.iter() {
125 tempos.push(*t);
126 }
127
128 let mut dumpers: Vec<CtrlChg> = Vec::with_capacity(value.dumper_repo.len());
129 for (_, d) in value.dumper_repo.iter() {
130 dumpers.push(*d);
131 }
132
133 let mut softs: Vec<CtrlChg> = Vec::with_capacity(value.soft_repo.len());
134 for (_, s) in value.soft_repo.iter() {
135 softs.push(*s);
136 }
137
138 ExportedProject {
139 rhythm: value.rhythm,
140 key: value.key,
141 grid: value.grid,
142 models: Models { notes, bars, tempos, dumpers, softs }
143 }
144
145 }
146}
147
148impl ProjectImpl {
149 pub fn note_repo(&self) -> &BagStore<u32, Rc<Note>, ModelChangeMetadata> {
150 &self.note_repo
151 }
152
153 pub fn bar_repo(&self) -> &Store<u32, Bar, ModelChangeMetadata> {
154 &self.bar_repo
155 }
156
157 pub fn tempo_repo(&self) -> &Store<u32, Tempo, ModelChangeMetadata> {
158 &self.tempo_repo
159 }
160
161 pub fn dumper_repo(&self) -> &Store<u32, CtrlChg, ModelChangeMetadata> {
162 &self.dumper_repo
163 }
164
165 pub fn soft_repo(&self) -> &Store<u32, CtrlChg, ModelChangeMetadata> {
166 &self.soft_repo
167 }
168
169 pub fn rhythm(&self) -> Rhythm {
170 self.rhythm
171 }
172
173 pub fn key(&self) -> Key {
174 self.key
175 }
176
177 pub fn grid(&self) -> Grid {
178 self.grid
179 }
180
181 fn add_bar_internal(&mut self, bar: Bar, select: bool) -> Vec<Bar> {
183 let mut metadata = ModelChangeMetadata::default();
184 if select { metadata.need_select = Some(true); }
185 self.bar_repo.add(bar.start_tick, bar, metadata).map(|o| vec![o]).unwrap_or_default()
186 }
187
188 pub fn bar_no(&self, bar: &Bar) -> Option<usize> {
189 self.bar_repo.index(bar.start_tick).ok()
190 }
191
192 pub fn tempo_at(&self, tick: u32) -> TempoValue {
193 tempo_at(tick, &self.tempo_repo)
194 }
195
196 pub fn dumper_at(&self, tick: u32) -> Velocity {
197 ctrl_chg_at(tick, &self.dumper_repo)
198 }
199
200 pub fn soft_at(&self, tick: u32) -> Velocity {
201 ctrl_chg_at(tick, &self.soft_repo)
202 }
203
204 pub fn location_to_tick(&self, loc: Location) -> Result<u32, LocationError> {
205 if loc.bar_no() == 0 {
206 if (u32::MAX as usize) < loc.offset() {
207 Err(LocationError::Overflow)
208 } else {
209 Ok(loc.offset() as u32)
210 }
211 } else if (u32::MAX as usize) < loc.bar_no() {
212 Err(LocationError::Overflow)
213 } else if loc.bar_no() <= self.bar_repo.len() {
214 let (tick, _) = self.bar_repo[loc.bar_no() - 1];
215 let sum = (tick as usize) + loc.offset();
216 if (u32::MAX as usize) < sum {
217 Err(LocationError::Overflow)
218 } else {
219 Ok(sum as u32)
220 }
221 } else {
222 let tick = match self.last_bar() {
223 None => {
224 loc.bar_no() * (self.rhythm.tick_len() as usize) + loc.offset()
225 },
226 Some((last_bar_no, last_bar)) => {
227 let last_tick = last_bar.start_tick;
228 let tick_len = self.rhythm_at(last_tick).tick_len() as usize;
229 (loc.bar_no() - last_bar_no - 1) * tick_len + loc.offset() + last_bar.start_tick as usize
230 },
231 };
232
233 if (u32::MAX as usize) < tick {
234 Err(LocationError::Overflow)
235 } else {
236 Ok(tick as u32)
237 }
238 }
239 }
240
241 pub fn tick_to_location(&self, tick: u32) -> Location {
242 if self.bar_repo.is_empty() {
243 Location::new(0, tick as usize)
244 } else {
245 match self.bar_repo.find(&tick) {
246 Ok(idx) => Location::new(idx + 1, 0),
247 Err(idx) =>
248 if idx == 0 {
249 Location::new(0, tick as usize)
250 } else {
251 let (_, bar) = self.bar_repo[idx - 1];
252 Location::new(idx, (tick - bar.start_tick) as usize)
253 },
254 }
255 }
256 }
257
258 pub fn rhythm_at(&self, tick: u32) -> Rhythm {
259 let idx = match self.bar_repo.index(tick) {
260 Ok(t) => t,
261 Err(t) => if t == 0 { return self.rhythm } else { t - 1 },
262 };
263
264 for i in (0..=idx).rev() {
265 let bar = self.bar_repo[i].1;
266 if let Some(rhythm) = bar.rhythm {
267 return rhythm;
268 }
269 }
270
271 self.rhythm
272 }
273
274 pub fn key_at(&self, tick: u32) -> Key {
275 let idx = match self.bar_repo.index(tick) {
276 Ok(t) => t,
277 Err(t) => if t == 0 { return self.key } else { t - 1 },
278 };
279
280 for i in (0..=idx).rev() {
281 let bar = self.bar_repo[i].1;
282 if let Some(key) = bar.key {
283 return key;
284 }
285 }
286
287 self.key
288 }
289
290 #[inline]
292 fn last_bar(&self) -> Option<(usize, Bar)> {
293 self.bar_repo.peek_last().map(|(_, bar)| (self.bar_repo.len() - 1, *bar))
294 }
295
296 #[allow(clippy::borrow_interior_mutable_const)]
297 fn note_max_end_tick(&self) -> Option<u32> {
298 if self.note_repo.is_empty() { return None; }
299 let mut start_tick =
300 self.note_repo.peek_last().map(|t| { *t.0 }).unwrap_or(0) as i64 - *Note::LONGEST_TICK_LEN as i64;
301 if start_tick < 0 { start_tick = 0; }
302 let mut max_tick_loc = 0;
303 for (tick, note) in self.note_repo.range(start_tick as u32..) {
304 let end_tick = tick + note.tick_len();
305 max_tick_loc = max_tick_loc.max(end_tick);
306 }
307
308 Some(max_tick_loc)
309 }
310
311 #[inline]
312 fn tempo_max_tick(&self) -> Option<u32> {
313 self.tempo_repo.peek_last().map(|t| t.0)
314 }
315
316 #[inline]
317 fn dumper_max_tick(&self) -> Option<u32> {
318 self.dumper_repo.peek_last().map(|t| t.0)
319 }
320
321 #[inline]
322 fn soft_max_tick(&self) -> Option<u32> {
323 self.soft_repo.peek_last().map(|t| t.0)
324 }
325
326 fn replenish_bars(&mut self) -> Vec<Bar> {
328 let mut bar_tick = self.last_bar().map(|(_, b)| b.start_tick).unwrap_or(0);
329 let max_end_tick =
330 self.note_max_end_tick().unwrap_or(0)
331 .max(self.tempo_max_tick().unwrap_or(0))
332 .max(self.dumper_max_tick().unwrap_or(0))
333 .max(self.soft_max_tick().unwrap_or(0));
334
335 let mut replenished_bars: Vec<Bar> = vec![];
336 if max_end_tick <= bar_tick { return replenished_bars; }
337 let bar_tick_len = self.rhythm_at(bar_tick).tick_len();
338 while bar_tick < max_end_tick {
339 bar_tick += bar_tick_len;
340 let bar = Bar::new(bar_tick, None, None, RepeatSet::EMPTY);
341 self.add_bar_internal(bar, false);
342 replenished_bars.push(bar);
343 }
344 replenished_bars
345 }
346}
347
348pub fn tempo_at(tick: u32, store: &Store<u32, Tempo, ModelChangeMetadata>) -> TempoValue {
349 if store.is_empty() {
350 DEFAULT_TEMPO
351 } else {
352 match store.find(&tick) {
353 Ok(idx) => {
354 store[idx].1.value
355 },
356 Err(idx) => {
357 if idx == 0 {
358 DEFAULT_TEMPO
359 } else {
360 store[idx - 1].1.value
361 }
362 },
363 }
364 }
365}
366
367pub fn ctrl_chg_at(tick: u32, store: &Store<u32, CtrlChg, ModelChangeMetadata>) -> Velocity {
368 if store.is_empty() {
369 DEFAULT_CTRL_CHG
370 } else {
371 match store.find(&tick) {
372 Ok(idx) => {
373 store[idx].1.velocity
374 },
375 Err(idx) => {
376 if idx == 0 {
377 DEFAULT_CTRL_CHG
378 } else {
379 store[idx - 1].1.velocity
380 }
381 },
382 }
383 }
384}
385
386impl Default for ProjectImpl {
387 fn default() -> Self {
388 ProjectImpl {
389 rhythm: Rhythm::default(),
390 key: Key::NONE,
391 grid: Grid::default(),
392 note_repo: BagStore::new(true),
393 bar_repo: Store::new(true),
394 tempo_repo: Store::new(true),
395 dumper_repo: Store::new(true),
396 soft_repo: Store::new(true),
397 }
398 }
399}
400
401#[derive(Serialize, Deserialize, PartialEq, Debug)]
402#[allow(clippy::large_enum_variant)]
403pub enum ProjectCmd {
404 SetRhythm(Rhythm, Rhythm),
405 SetKey(Key, Key),
406 SetGrid(Grid, Grid),
407 ModelChanged { added: Models, removed: Models, metadata: ModelChangeMetadata },
408}
409
410impl Cmd for ProjectCmd {
411 type Model = ProjectImpl;
412
413 fn undo(&self, proj: &mut Self::Model) {
414 match self {
415 ProjectCmd::SetRhythm(old_rhythm, _) => {
416 proj.rhythm = *old_rhythm;
417 },
418 ProjectCmd::SetKey(old_key, _) => {
419 proj.key = *old_key;
420 },
421 ProjectCmd::SetGrid(old_grid, _) => {
422 proj.grid = *old_grid;
423 },
424 ProjectCmd::ModelChanged { added, removed, metadata } => {
425 for n in added.notes.iter() {
426 proj.note_repo.remove(&n.start_tick(), &Rc::new((*n).clone()));
427 }
428 for b in added.bars.iter() {
429 proj.bar_repo.remove(&b.start_tick);
430 }
431 for t in added.tempos.iter() {
432 proj.tempo_repo.remove(&t.start_tick);
433 }
434 for d in added.dumpers.iter() {
435 proj.dumper_repo.remove(&d.start_tick);
436 }
437 for s in added.softs.iter() {
438 proj.soft_repo.remove(&s.start_tick);
439 }
440
441 for n in removed.notes.iter() {
442 proj.note_repo.add(n.start_tick(), Rc::new((*n).clone()), *metadata);
443 }
444 for b in removed.bars.iter() {
445 proj.bar_repo.add(b.start_tick, *b, *metadata);
446 }
447 for t in removed.tempos.iter() {
448 proj.tempo_repo.add(t.start_tick, *t, *metadata);
449 }
450 for d in removed.dumpers.iter() {
451 proj.dumper_repo.add(d.start_tick, *d, *metadata);
452 }
453 for s in removed.softs.iter() {
454 proj.soft_repo.add(s.start_tick, *s, *metadata);
455 }
456 },
457 }
458 }
459
460 fn redo(&self, proj: &mut Self::Model) {
461 match self {
462 ProjectCmd::SetRhythm(_, new_rhythm) => {
463 proj.rhythm = *new_rhythm;
464 },
465 ProjectCmd::SetKey(_, new_key) => {
466 proj.key = *new_key;
467 },
468 ProjectCmd::SetGrid(_, new_grid) => {
469 proj.grid = *new_grid;
470 }
471 ProjectCmd::ModelChanged { added, removed , metadata } => {
472 for n in removed.notes.iter() {
473 proj.note_repo.remove(&n.start_tick(), &Rc::new(n.clone()));
474 }
475 for b in removed.bars.iter() {
476 proj.bar_repo.remove(&b.start_tick);
477 }
478 for t in removed.tempos.iter() {
479 proj.tempo_repo.remove(&t.start_tick);
480 }
481 for d in removed.dumpers.iter() {
482 proj.dumper_repo.remove(&d.start_tick);
483 }
484 for s in removed.softs.iter() {
485 proj.soft_repo.remove(&s.start_tick);
486 }
487
488 for n in added.notes.iter() {
489 proj.note_repo.add(n.start_tick(), Rc::new(n.clone()), *metadata);
490 }
491 for b in added.bars.iter() {
492 proj.bar_repo.add(b.start_tick, *b, *metadata);
493 }
494 for t in added.tempos.iter() {
495 proj.tempo_repo.add(t.start_tick, *t, *metadata);
496 }
497 for d in added.dumpers.iter() {
498 proj.dumper_repo.add(d.start_tick, *d, *metadata);
499 }
500 for s in added.softs.iter() {
501 proj.soft_repo.add(s.start_tick, *s, *metadata);
502 }
503 },
504 }
505 }
506}
507
508impl SerializableCmd for ProjectCmd {
509}
510
511#[derive(Debug)]
512pub enum ProjectCmdErr {
513 NoOp,
514}
515
516impl core::error::Error for ProjectCmdErr {}
517
518impl std::fmt::Display for ProjectCmdErr {
519 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
520 match self {
521 ProjectCmdErr::NoOp => write!(f, "No Operation"),
522 }
523 }
524}
525
526pub trait Project {
527 fn set_rhythm(&mut self, rhythm: Rhythm);
528 fn rhythm(&self) -> Rhythm;
529 fn set_key(&mut self, key: Key);
530 fn key(&self) -> Key;
531 fn set_grid(&mut self, key: Grid);
532 fn grid(&self) -> Grid;
533 fn add_note(&mut self, note: Note, select: bool);
534 fn add_bar(&mut self, bar: Bar, select: bool);
535 fn add_tempo(&mut self, bar: Tempo, select: bool);
536 fn add_dumper(&mut self, dumper: CtrlChg, select: bool);
537 fn add_soft(&mut self, soft: CtrlChg, select: bool);
538 fn tuplize(&mut self, notes: Vec<Rc<Note>>);
539 fn bulk_remove(&mut self, to_remove: Models, metadata: ModelChangeMetadata);
540 fn bulk_add(&mut self, to_add: Models, metadata: ModelChangeMetadata);
541 fn change(&mut self, from_to: ModelChanges, metadata: ModelChangeMetadata);
542 fn bar_no(&self, bar: &Bar) -> Option<usize>;
543 fn tempo_at(&self, tick: u32) -> TempoValue;
544 fn dumper_at(&self, tick: u32) -> Velocity;
545 fn soft_at(&self, tick: u32) -> Velocity;
546 fn clear_model_events(&mut self);
547 fn bar_events(&self) -> &Vec<StoreEvent<u32, Bar, ModelChangeMetadata>>;
548 fn tempo_events(&self) -> &Vec<StoreEvent<u32, Tempo, ModelChangeMetadata>>;
549 fn dumper_events(&self) -> &Vec<StoreEvent<u32, CtrlChg, ModelChangeMetadata>>;
550 fn soft_events(&self) -> &Vec<StoreEvent<u32, CtrlChg, ModelChangeMetadata>>;
551 fn note_events(&self) -> &Vec<BagStoreEvent<u32, Rc<Note>, ModelChangeMetadata>>;
552 fn location_to_tick(&self, loc: Location) -> Result<u32, LocationError>;
553 fn tick_to_location(&self, tick: u32) -> Location;
554 fn rhythm_at(&self, tick: u32) -> Rhythm;
555 fn key_at(&self, tick: u32) -> Key;
556 fn note_repo(&self) -> &BagStore<u32, Rc<Note>, ModelChangeMetadata>;
557 fn bar_repo(&self) -> &Store<u32, Bar, ModelChangeMetadata>;
558 fn tempo_repo(&self) -> &Store<u32, Tempo, ModelChangeMetadata>;
559 fn soft_repo(&self) -> &Store<u32, CtrlChg, ModelChangeMetadata>;
560 fn dumper_repo(&self) -> &Store<u32, CtrlChg, ModelChangeMetadata>;
561}
562
563impl Project for SqliteUndoStore::<ProjectCmd, ProjectImpl, ProjectCmdErr> {
564 fn set_rhythm(&mut self, rhythm: Rhythm) {
565 self.add_cmd(ProjectCmd::SetRhythm(self.model().rhythm, rhythm));
566 }
567
568 fn rhythm(&self) -> Rhythm {
569 self.model().rhythm
570 }
571
572 fn set_key(&mut self, key: Key) {
573 self.add_cmd(ProjectCmd::SetKey(self.model().key, key));
574 }
575
576 fn key(&self) -> Key {
577 self.model().key
578 }
579
580 fn set_grid(&mut self, grid: Grid) {
581 self.add_cmd(ProjectCmd::SetGrid(self.model().grid, grid));
582 }
583
584 fn grid(&self) -> Grid {
585 self.model().grid
586 }
587
588 fn add_note(&mut self, note: Note, select: bool) {
589 let note = Rc::new(note);
590 let mut metadata = ModelChangeMetadata::default();
591 if select { metadata.need_select = Some(true); }
592
593 let _ = self.mutate(Box::new(move |proj| {
594 proj.note_repo.add(note.start_tick(), note.clone(), metadata);
595 let replenishid_bars = proj.replenish_bars();
596 Ok(
597 ProjectCmd::ModelChanged {
598 added: Models::empty().with_notes(slice::from_ref(¬e)).with_bars(replenishid_bars),
599 removed: Models::empty(),
600 metadata,
601 }
602 )
603 }));
604 }
605
606 fn add_bar(&mut self, bar: Bar, select: bool) {
607 let mut metadata = ModelChangeMetadata::default();
608 if select { metadata.need_select = Some(true); }
609 let _ = self.mutate(Box::new(move |proj| {
610 let origin = proj.bar_repo.add(bar.start_tick, bar, metadata).map(|o| vec![o]).unwrap_or_default();
611
612 Ok(
613 ProjectCmd::ModelChanged {
614 added: Models::empty().with_bars(vec![bar]),
615 removed: Models::empty().with_bars(origin),
616 metadata,
617 }
618 )
619 }));
620 }
621
622 fn add_tempo(&mut self, tempo: Tempo, select: bool) {
623 let mut metadata = ModelChangeMetadata::default();
624 if select { metadata.need_select = Some(true); }
625 let _ = self.mutate(Box::new(move |proj| {
626 let origin = proj.tempo_repo.add(tempo.start_tick, tempo, metadata).map(|o| vec![o]).unwrap_or_default();
627 let replenishid_bars = proj.replenish_bars();
628 Ok(
629 ProjectCmd::ModelChanged {
630 added: Models::empty().with_bars(replenishid_bars).with_tempos(vec![tempo]),
631 removed: Models::empty().with_tempos(origin),
632 metadata,
633 }
634
635 )
636 }));
637 }
638
639 fn add_dumper(&mut self, dumper: CtrlChg, select: bool) {
640 let mut metadata = ModelChangeMetadata::default();
641 if select { metadata.need_select = Some(true); }
642 let _ = self.mutate(Box::new(move |proj| {
643 let origin = proj.dumper_repo.add(dumper.start_tick, dumper, metadata).map(|o| vec![o]).unwrap_or_default();
644 let replenishid_bars = proj.replenish_bars();
645 Ok(
646 ProjectCmd::ModelChanged {
647 added: Models::empty().with_bars(replenishid_bars).with_dumpers(vec![dumper]),
648 removed: Models::empty().with_dumpers(origin),
649 metadata,
650 }
651 )
652 }));
653 }
654
655 fn add_soft(&mut self, soft: CtrlChg, select: bool) {
656 let mut metadata = ModelChangeMetadata::default();
657 if select { metadata.need_select = Some(true); }
658 let _ = self.mutate(Box::new(move |proj| {
659 let origin = proj.soft_repo.add(soft.start_tick, soft, metadata).map(|o| vec![o]).unwrap_or_default();
660 let replenishid_bars = proj.replenish_bars();
661 Ok(
662 ProjectCmd::ModelChanged {
663 added: Models::empty().with_bars(replenishid_bars).with_softs(vec![soft]),
664 removed: Models::empty().with_softs(origin),
665 metadata
666 }
667 )
668 }));
669 }
670
671 fn tuplize(&mut self, notes: Vec<Rc<Note>>) {
672 let metadata = ModelChangeMetadata::default().with_need_select(true);
673 let _ = self.mutate(Box::new(move |proj| {
674 if 1 < notes.len() {
675 let mut to_remove = Vec::with_capacity(notes.len());
676 for n in notes.iter() {
677 to_remove.push((n.start_tick(), n.clone()));
678 }
679 let tupled = tuple::tuplize(notes.clone());
680 proj.note_repo.bulk_remove(&to_remove, ModelChangeMetadata::default());
681 proj.note_repo.bulk_add(
682 tupled.iter().map(|n| (n.start_tick(), n.clone())).collect(),
683 metadata
684 );
685 let replenishid_bars = proj.replenish_bars();
686
687 Ok(
688 ProjectCmd::ModelChanged {
689 added: Models::empty().with_notes(&tupled).with_bars(replenishid_bars),
690 removed: Models::empty().with_notes(¬es),
691 metadata,
692 }
693 )
694 } else {
695 Err(IntoReport::into_report(ProjectCmdErr::NoOp))
696 }
697 }));
698
699 }
700
701 fn bulk_remove(&mut self, to_remove: Models, metadata: ModelChangeMetadata) {
702 self.add_cmd(ProjectCmd::ModelChanged { added: Models::empty(), removed: to_remove, metadata });
703 }
704
705 fn bulk_add(&mut self, mut to_add: Models, metadata: ModelChangeMetadata) {
706 let _ = self.mutate(Box::new(move |proj| {
707 let mut removed = Models::empty();
708
709 let mut buf: Vec<(u32, Rc<Note>)> = Vec::with_capacity(to_add.notes.len());
710 for n in to_add.notes.iter() {
711 buf.push((n.start_tick(), Rc::new(n.clone())));
712 }
713 proj.note_repo.bulk_add(buf, metadata);
714
715 let mut buf = Vec::with_capacity(to_add.bars.len());
716 for b in to_add.bars.iter() {
717 buf.push((b.start_tick, *b));
718 }
719 removed.bars = proj.bar_repo.bulk_add(buf, metadata).iter().map(|(_, bar)| *bar).collect();
720
721 let mut buf = Vec::with_capacity(to_add.tempos.len());
722 for t in to_add.tempos.iter() {
723 buf.push((t.start_tick, *t));
724 }
725 removed.tempos = proj.tempo_repo.bulk_add(buf, metadata).iter().map(|(_, t)| *t).collect();
726
727
728 let mut buf = Vec::with_capacity(to_add.dumpers.len());
729 for d in to_add.dumpers.iter() {
730 buf.push((d.start_tick, *d));
731 }
732 removed.dumpers = proj.dumper_repo.bulk_add(buf, metadata).iter().map(|(_, d)| *d).collect();
733
734 let mut buf = Vec::with_capacity(to_add.softs.len());
735 for s in to_add.softs.iter() {
736 buf.push((s.start_tick, *s));
737 }
738 removed.softs = proj.soft_repo.bulk_add(buf, metadata).iter().map(|(_, s)| *s).collect();
739
740 let replenished_bars = proj.replenish_bars();
741 to_add.bars.extend(replenished_bars);
742
743 Ok(ProjectCmd::ModelChanged { added: to_add, removed, metadata })
744 }));
745 }
746
747 fn change(&mut self, from_to: ModelChanges, metadata: ModelChangeMetadata) {
748 let _ = self.mutate(Box::new(move |proj| {
749 let mut added: Models = Models::with_capacity(
750 from_to.notes.len(),
751 from_to.bars.len(),
752 from_to.tempos.len(),
753 from_to.dumpers.len(),
754 from_to.softs.len(),
755 );
756
757 let mut removed: Models = Models::with_capacity(
758 from_to.notes.len(),
759 from_to.bars.len(),
760 from_to.tempos.len(),
761 from_to.dumpers.len(),
762 from_to.softs.len(),
763 );
764
765 type NoteChange = ((u32, Rc<Note>), (u32, Rc<Note>));
766 let mut note_change: Vec<NoteChange> = Vec::with_capacity(from_to.notes.len());
767 for (from, to) in from_to.notes.iter() {
768 note_change.push((
769 (from.start_tick(), Rc::new(from.clone())), (to.start_tick(), Rc::new(to.clone()))
770 ));
771 added.notes.push(to.clone());
772 removed.notes.push(from.clone());
773 }
774 proj.note_repo.change(¬e_change, metadata);
775
776 let mut bar_change: Vec<(&u32, (u32, Bar))> = Vec::with_capacity(from_to.bars.len());
777 for (from, to) in from_to.bars.iter() {
778 bar_change.push((
779 &from.start_tick, (to.start_tick, *to)
780 ));
781 added.bars.push(*to);
782 removed.bars.push(*from);
783 }
784 removed.bars.extend(proj.bar_repo.change(&bar_change, metadata).iter().map(|(_, b)| *b).collect::<Vec<Bar>>());
785
786 let mut tempo_change: Vec<(&u32, (u32, Tempo))> = Vec::with_capacity(from_to.tempos.len());
787 for (from, to) in from_to.tempos.iter() {
788 tempo_change.push((
789 &from.start_tick, (to.start_tick, *to)
790 ));
791 added.tempos.push(*to);
792 removed.tempos.push(*from);
793 }
794 removed.tempos.extend(proj.tempo_repo.change(&tempo_change,metadata).iter().map(|(_, t)| *t).collect::<Vec<Tempo>>());
795
796 let mut dumper_change: Vec<(&u32, (u32, CtrlChg))> = Vec::with_capacity(from_to.dumpers.len());
797 for (from, to) in from_to.dumpers.iter() {
798 dumper_change.push((
799 &from.start_tick, (to.start_tick, *to)
800 ));
801 added.dumpers.push(*to);
802 removed.dumpers.push(*from);
803 }
804 removed.dumpers.extend(proj.dumper_repo.change(&dumper_change,metadata).iter().map(|(_, t)| *t).collect::<Vec<CtrlChg>>());
805
806 let mut soft_change: Vec<(&u32, (u32, CtrlChg))> = Vec::with_capacity(from_to.softs.len());
807 for (from, to) in from_to.softs.iter() {
808 soft_change.push((
809 &from.start_tick, (to.start_tick, *to)
810 ));
811 added.softs.push(*to);
812 removed.softs.push(*from);
813 }
814 removed.softs.extend(proj.soft_repo.change(&soft_change,metadata).iter().map(|(_, t)| *t).collect::<Vec<CtrlChg>>());
815
816 added.bars.extend(proj.replenish_bars());
817 Ok(ProjectCmd::ModelChanged {
818 added, removed, metadata
819 })
820 }));
821 }
822
823 #[inline]
824 fn bar_no(&self, bar: &Bar) -> Option<usize> {
825 self.model().bar_no(bar)
826 }
827
828 #[inline]
829 fn tempo_at(&self, tick: u32) -> TempoValue {
830 self.model().tempo_at(tick)
831 }
832
833 #[inline]
834 fn dumper_at(&self, tick: u32) -> Velocity {
835 self.model().dumper_at(tick)
836 }
837
838 #[inline]
839 fn soft_at(&self, tick: u32) -> Velocity {
840 self.model().soft_at(tick)
841 }
842
843 fn clear_model_events(&mut self) {
844 self.irreversible_mutate(Box::new(|proj| {
845 proj.note_repo.clear_events();
846 proj.bar_repo.clear_events();
847 proj.tempo_repo.clear_events();
848 proj.dumper_repo.clear_events();
849 proj.soft_repo.clear_events();
850 }));
851 }
852
853 #[inline]
854 fn bar_events(&self) -> &Vec<StoreEvent<u32, Bar, ModelChangeMetadata>> {
855 self.model().bar_repo.events()
856 }
857
858 #[inline]
859 fn tempo_events(&self) -> &Vec<StoreEvent<u32, Tempo, ModelChangeMetadata>> {
860 self.model().tempo_repo.events()
861 }
862
863 #[inline]
864 fn dumper_events(&self) -> &Vec<StoreEvent<u32, CtrlChg, ModelChangeMetadata>> {
865 self.model().dumper_repo.events()
866
867 }
868
869 #[inline]
870 fn soft_events(&self) -> &Vec<StoreEvent<u32, CtrlChg, ModelChangeMetadata>> {
871 self.model().soft_repo.events()
872 }
873
874 #[inline]
875 fn note_events(&self) -> &Vec<BagStoreEvent<u32, Rc<Note>, ModelChangeMetadata>> {
876 self.model().note_repo.events()
877 }
878
879 #[inline]
880 fn location_to_tick(&self, loc: Location) -> Result<u32, LocationError> {
881 self.model().location_to_tick(loc)
882 }
883
884 #[inline]
885 fn tick_to_location(&self, tick: u32) -> Location {
886 self.model().tick_to_location(tick)
887 }
888
889 #[inline]
890 fn rhythm_at(&self, tick: u32) -> Rhythm {
891 self.model().rhythm_at(tick)
892 }
893
894 #[inline]
895 fn key_at(&self, tick: u32) -> Key {
896 self.model().key_at(tick)
897 }
898
899 #[inline]
900 fn note_repo(&self) -> &BagStore<u32, Rc<Note>, ModelChangeMetadata> {
901 self.model().note_repo()
902 }
903
904 #[inline]
905 fn bar_repo(&self) -> &Store<u32, Bar, ModelChangeMetadata> {
906 self.model().bar_repo()
907 }
908
909 #[inline]
910 fn tempo_repo(&self) -> &Store<u32, Tempo, ModelChangeMetadata> {
911 self.model().tempo_repo()
912 }
913
914 #[inline]
915 fn soft_repo(&self) -> &Store<u32, CtrlChg, ModelChangeMetadata> {
916 self.model().soft_repo()
917 }
918
919 #[inline]
920 fn dumper_repo(&self) -> &Store<u32, CtrlChg, ModelChangeMetadata> {
921 self.model().dumper_repo()
922 }
923}
924
925pub type ProjectStore = SqliteUndoStore<ProjectCmd, ProjectImpl, ProjectCmdErr>;
926
927#[cfg(test)]
928mod tests {
929 use std::rc::Rc;
930 use klavier_helper::store::Store;
931 use serdo::undo_store::{SqliteUndoStore, UndoStore, self};
932 use crate::{tempo::{Tempo, TempoValue}, project::{tempo_at, ProjectCmd, ProjectCmdErr, ModelChangeMetadata, ProjectStore, LocationError}, note::Note, solfa::Solfa, octave::Octave, sharp_flat::SharpFlat, pitch::Pitch, duration::{Duration, Numerator, Denominator, Dots}, velocity::Velocity, trimmer::RateTrimmer, bar::{Bar, RepeatSet}, location::Location, rhythm::Rhythm, ctrl_chg::CtrlChg, key::Key, grid::Grid, models::{Models, ModelChanges}, channel::Channel};
933 use super::{DEFAULT_TEMPO, ProjectImpl};
934
935 #[test]
936 fn tempo() {
937 let mut store: Store<u32, Tempo, ModelChangeMetadata> = Store::new(false);
938 assert_eq!(tempo_at(0, &store), DEFAULT_TEMPO);
939 let metadata = ModelChangeMetadata::default();
940
941 store.add(10, Tempo { start_tick: 10, value: TempoValue::new(100) }, metadata);
942 assert_eq!(tempo_at(0, &store), DEFAULT_TEMPO);
943 assert_eq!(tempo_at(10, &store), TempoValue::new(100));
944 assert_eq!(tempo_at(11, &store), TempoValue::new(100));
945
946 store.add(20, Tempo { start_tick: 20, value: TempoValue::new(200) }, metadata);
947 assert_eq!(tempo_at(0, &store), DEFAULT_TEMPO);
948 assert_eq!(tempo_at(10, &store), TempoValue::new(100));
949 assert_eq!(tempo_at(11, &store), TempoValue::new(100));
950 assert_eq!(tempo_at(19, &store), TempoValue::new(100));
951 assert_eq!(tempo_at(20, &store), TempoValue::new(200));
952 assert_eq!(tempo_at(100, &store), TempoValue::new(200));
953 }
954
955 #[test]
956 fn undo_note_addition() {
957 let mut dir = tempdir().unwrap().as_ref().to_path_buf();
958 dir.push("project");
959 let mut store: ProjectStore = ProjectStore::open(dir.clone(), undo_store::Options::new()).unwrap();
960
961 let note = Note {
962 base_start_tick: 100,
963 pitch: Pitch::new(Solfa::C, Octave::Oct4, SharpFlat::Null),
964 duration: Duration::new(Numerator::Quarter, Denominator::from_value(2).unwrap(), Dots::ZERO),
965 ..Default::default()
966 };
967
968 store.add_note(note, false);
969 assert_eq!(store.model().note_repo().len(), 1);
970
971 store.undo();
972 assert_eq!(store.model().note_repo().len(), 0);
973 }
974
975 #[test]
976 fn undo_bar_addition() {
977 let mut dir = tempdir().unwrap().as_ref().to_path_buf();
978 dir.push("project");
979 let mut store: ProjectStore = ProjectStore::open(dir.clone(), undo_store::Options::new()).unwrap();
980
981 let bar = Bar::new(
982 100,
983 None,
984 None,
985 RepeatSet::EMPTY,
986 );
987 store.add_bar(bar, false);
988 assert_eq!(store.model().bar_repo().len(), 1);
989 store.undo();
990 assert_eq!(store.model().bar_repo().len(), 0);
991 }
992
993 #[test]
994 fn location_to_tick() {
995 let mut dir = tempdir().unwrap().as_ref().to_path_buf();
996 dir.push("project");
997 let mut store = SqliteUndoStore::<ProjectCmd, ProjectImpl, ProjectCmdErr>::open(dir.clone(), undo_store::Options::new()).unwrap();
998
999 assert_eq!(store.model().location_to_tick(Location::new(0, 0)).unwrap(), 0);
1000 assert_eq!(store.model().location_to_tick(Location::new(0, 1)).unwrap(), 1);
1001 assert_eq!(store.model().location_to_tick(Location::new(0, u32::MAX as usize)).unwrap(), u32::MAX);
1002 assert_eq!(store.model().location_to_tick(Location::new(0, u32::MAX as usize + 1)), Err(LocationError::Overflow));
1003 assert_eq!(
1004 store.model().location_to_tick(Location::new(1, 1)).unwrap(),
1005 store.model().rhythm().tick_len() + 1
1006 );
1007
1008 let bar0 = Bar::new(
1009 100, None, None, RepeatSet::EMPTY
1010 );
1011 store.add_bar(bar0, false);
1012
1013 assert_eq!(store.model().location_to_tick(Location::new(0, 0)).unwrap(), 0);
1014 assert_eq!(store.model().location_to_tick(Location::new(0, 1)).unwrap(), 1);
1015 assert_eq!(store.model().location_to_tick(Location::new(1, 1)).unwrap(), 101);
1016 assert_eq!(store.model().location_to_tick(Location::new(1, (u32::MAX as usize) - 100)).unwrap(), u32::MAX);
1017 assert_eq!(store.model().location_to_tick(Location::new(1, (u32::MAX as usize) - 99)), Err(LocationError::Overflow));
1018
1019 assert_eq!(store.model().location_to_tick(Location::new(2, 1)).unwrap(), 100 + store.model().rhythm().tick_len() + 1);
1022
1023 let bar1 = Bar::new(
1024 1000, None, None, RepeatSet::EMPTY
1025 );
1026 store.add_bar(bar1, false);
1027
1028 assert_eq!(store.model().location_to_tick(Location::new(0, 0)).unwrap(), 0);
1029 assert_eq!(store.model().location_to_tick(Location::new(0, 1)).unwrap(), 1);
1030 assert_eq!(store.model().location_to_tick(Location::new(1, 1)).unwrap(), 101);
1031 assert_eq!(store.model().location_to_tick(Location::new(2, 0)).unwrap(), 1000);
1032
1033 let bar2 = Bar::new(
1034 2000, Some(Rhythm::new(2, 4)), None, RepeatSet::EMPTY
1035 );
1036 store.add_bar(bar2, false);
1037
1038 let bar3 = Bar::new(
1039 3000, None, None, RepeatSet::EMPTY
1040 );
1041 store.add_bar(bar3, false);
1042
1043 assert_eq!(store.model().location_to_tick(Location::new(4, 1)).unwrap(), 3001);
1046 assert_eq!(store.model().location_to_tick(Location::new(5, 1)).unwrap(), 3000 + 480 + 1);
1047
1048 }
1049
1050 #[test]
1051 fn tick_to_location() {
1052 let mut dir = tempdir().unwrap().as_ref().to_path_buf();
1053 dir.push("project");
1054 let mut store = SqliteUndoStore::<ProjectCmd, ProjectImpl, ProjectCmdErr>::open(dir.clone(), undo_store::Options::new()).unwrap();
1055
1056 assert_eq!(store.model().tick_to_location(0), Location::new(0, 0));
1057 assert_eq!(store.model().tick_to_location(100), Location::new(0, 100));
1058 assert_eq!(store.model().tick_to_location(u32::MAX), Location::new(0, u32::MAX as usize));
1059
1060 let bar = Bar::new(
1061 100, None, None, RepeatSet::EMPTY
1062 );
1063 store.add_bar(bar, false);
1064
1065 assert_eq!(store.model().tick_to_location(0), Location::new(0, 0));
1066 assert_eq!(store.model().tick_to_location(99), Location::new(0, 99));
1067 assert_eq!(store.model().tick_to_location(100), Location::new(1, 0));
1068 assert_eq!(store.model().tick_to_location(u32::MAX), Location::new(1, (u32::MAX - 100) as usize));
1069
1070 let bar = Bar::new(
1071 1000, None, None, RepeatSet::EMPTY,
1072 );
1073 store.add_bar(bar, false);
1074
1075 assert_eq!(store.model().tick_to_location(0), Location::new(0, 0));
1076 assert_eq!(store.model().tick_to_location(99), Location::new(0, 99));
1077 assert_eq!(store.model().tick_to_location(999), Location::new(1, 899));
1078 assert_eq!(store.model().tick_to_location(1000), Location::new(2, 0));
1079 assert_eq!(store.model().tick_to_location(u32::MAX), Location::new(2, (u32::MAX - 1000) as usize));
1080 }
1081
1082 #[test]
1083 fn rhythm_at() {
1084 let mut dir = tempdir().unwrap().as_ref().to_path_buf();
1085 dir.push("project");
1086 let mut store = SqliteUndoStore::<ProjectCmd, ProjectImpl, ProjectCmdErr>::open(dir.clone(), undo_store::Options::new()).unwrap();
1087
1088 store.set_rhythm(Rhythm::new(6, 8));
1089 assert_eq!(store.model().last_bar(), None);
1090
1091 assert_eq!(store.model().rhythm_at(500), Rhythm::new(6, 8));
1092 assert_eq!(store.model().rhythm_at(0), Rhythm::new(6, 8));
1093
1094 let bar0 = Bar::new(100, None, None, RepeatSet::EMPTY);
1095 store.add_bar(bar0, false);
1096 assert_eq!(store.model().last_bar().map(|(_, bar)| bar), Some(bar0));
1097
1098 assert_eq!(store.model().rhythm_at(0), Rhythm::new(6, 8));
1099 assert_eq!(store.model().rhythm_at(99), Rhythm::new(6, 8));
1100 assert_eq!(store.model().rhythm_at(100), Rhythm::new(6, 8));
1101 assert_eq!(store.model().rhythm_at(101), Rhythm::new(6, 8));
1102
1103 let bar1 = Bar::new(
1104 200, Some(Rhythm::new(3, 4)), None, RepeatSet::EMPTY
1105 );
1106 store.add_bar(bar1, false);
1107 assert_eq!(store.model().last_bar().map(|(_, bar)| bar), Some(bar1));
1108
1109 let bar2 = Bar::new(300, None, None, RepeatSet::EMPTY);
1110 store.add_bar(bar2, false);
1111 assert_eq!(store.model().last_bar().map(|(_, bar)| bar), Some(bar2));
1112
1113 let bar3 = Bar::new(
1114 400, Some(Rhythm::new(4, 4)), None, RepeatSet::EMPTY
1115 );
1116 store.add_bar(bar3, false);
1117 assert_eq!(store.model().last_bar().map(|(_, bar)| bar), Some(bar3));
1118
1119 assert_eq!(store.model().rhythm_at(0), Rhythm::new(6, 8));
1120 assert_eq!(store.model().rhythm_at(99), Rhythm::new(6, 8));
1121 assert_eq!(store.model().rhythm_at(100), Rhythm::new(6, 8));
1122 assert_eq!(store.model().rhythm_at(101), Rhythm::new(6, 8));
1123 assert_eq!(store.model().rhythm_at(199), Rhythm::new(6, 8));
1124 assert_eq!(store.model().rhythm_at(200), Rhythm::new(3, 4));
1125 assert_eq!(store.model().rhythm_at(201), Rhythm::new(3, 4));
1126 assert_eq!(store.model().rhythm_at(299), Rhythm::new(3, 4));
1127 assert_eq!(store.model().rhythm_at(300), Rhythm::new(3, 4));
1128 assert_eq!(store.model().rhythm_at(301), Rhythm::new(3, 4));
1129 assert_eq!(store.model().rhythm_at(399), Rhythm::new(3, 4));
1130 assert_eq!(store.model().rhythm_at(400), Rhythm::new(4, 4));
1131 assert_eq!(store.model().rhythm_at(401), Rhythm::new(4, 4));
1132 }
1133
1134 #[test]
1135 fn note_max_tick_loc() {
1136 let mut dir = tempdir().unwrap().as_ref().to_path_buf();
1137 dir.push("project");
1138 let mut store = SqliteUndoStore::<ProjectCmd, ProjectImpl, ProjectCmdErr>::open(dir.clone(), undo_store::Options::new()).unwrap();
1139
1140 let pitch = Pitch::new(Solfa::C, Octave::Oct0, SharpFlat::Null);
1141 assert_eq!(store.model().note_max_end_tick(), None);
1142
1143 let note0 = Note { base_start_tick: 100, pitch,
1145 duration: Duration::new(Numerator::Quarter, Denominator::from_value(2).unwrap(), Dots::ONE),
1146 base_velocity: Velocity::new(10),
1147 ..Default::default()
1148 };
1149
1150 let end_tick0 = note0.base_start_tick + note0.tick_len();
1151 store.add_note(note0, false);
1152 assert_eq!(store.model().note_max_end_tick(), Some(end_tick0));
1153
1154 let note1 = Note { base_start_tick: 100, pitch,
1156 duration: Duration::new(Numerator::Whole, Denominator::from_value(2).unwrap(), Dots::ONE),
1157 base_velocity: Velocity::new(10),
1158 ..Default::default()
1159 };
1160
1161 store.add_note(note1.clone(), false);
1162 assert_eq!(store.model().note_max_end_tick(), Some(note1.base_start_tick + note1.tick_len()));
1163
1164 store.add_note(
1165 Note { base_start_tick: 200, pitch,
1167 duration: Duration::new(Numerator::N8th, Denominator::from_value(2).unwrap(), Dots::ZERO),
1168 base_velocity: Velocity::new(10),
1169 duration_trimmer: RateTrimmer::new(1.0, 1.0, 1.0, 2.0),
1170 ..Default::default()
1171 }, false
1172 );
1173
1174 assert_eq!(store.model().note_max_end_tick(), Some(note1.base_start_tick + note1.tick_len()));
1175 }
1176
1177 #[test]
1178 fn replenish_bars() {
1179 let mut dir = tempdir().unwrap().as_ref().to_path_buf();
1180 dir.push("project");
1181 let mut store = SqliteUndoStore::<ProjectCmd, ProjectImpl, ProjectCmdErr>::open(dir.clone(), undo_store::Options::new()).unwrap();
1182
1183 let pitch = Pitch::new(Solfa::C, Octave::Oct0, SharpFlat::Null);
1184 assert_eq!(store.model().bar_repo().len(), 0);
1185 store.add_note(
1186 Note { base_start_tick: 100, pitch,
1188 duration: Duration::new(Numerator::Quarter, Denominator::from_value(2).unwrap(), Dots::ONE),
1189 base_velocity: Velocity::new(10),
1190 ..Default::default()
1191 }, false
1192 );
1193 assert_eq!(store.model().bar_repo().len(), 1);
1194 assert_eq!(store.model().bar_repo().peek_last().unwrap().0, 960);
1195
1196 store.add_note(
1197 Note {
1198 pitch,
1199 duration: Duration::new(Numerator::Whole, Denominator::from_value(2).unwrap(), Dots::ZERO),
1200 base_velocity: Velocity::new(10),
1201 ..Default::default()
1202 }, false
1203 );
1204
1205 assert_eq!(store.model().bar_repo().len(), 1);
1206 assert_eq!(store.model().bar_repo().peek_last().unwrap().0, 960);
1207
1208 store.add_note(
1209 Note {
1210 base_start_tick: 960, pitch,
1211 duration: Duration::new(Numerator::Whole, Denominator::from_value(2).unwrap(), Dots::ONE),
1212 base_velocity: Velocity::new(10),
1213 ..Default::default()
1214 }, false
1215 );
1216
1217 assert_eq!(store.model().bar_repo().len(), 3);
1218 assert_eq!(store.model().bar_repo().peek_last().unwrap().0, 960 * 3);
1219
1220 store.add_tempo(Tempo::new(960*3, 200), false);
1221
1222 assert_eq!(store.model().bar_repo().len(), 3);
1223 assert_eq!(store.model().bar_repo().peek_last().unwrap().0, 960 * 3);
1224
1225 store.add_tempo(Tempo::new(960 * 3 + 1, 200), false);
1226
1227 assert_eq!(store.model().bar_repo().len(), 4);
1228 assert_eq!(store.model().bar_repo().peek_last().unwrap().0, 960 * 4);
1229
1230 store.add_dumper(CtrlChg::new(960 * 4, Velocity::new(20), Channel::default()), false);
1231
1232 assert_eq!(store.model().bar_repo().len(), 4);
1233 assert_eq!(store.model().bar_repo().peek_last().unwrap().0, 960 * 4);
1234
1235 store.add_dumper(CtrlChg::new(960 * 4 + 1, Velocity::new(20), Channel::default()), false);
1236
1237 assert_eq!(store.model().bar_repo().len(), 5);
1238 assert_eq!(store.model().bar_repo().peek_last().unwrap().0, 960 * 5);
1239
1240 store.add_soft(CtrlChg::new(960 * 5, Velocity::new(20), Channel::default()), false);
1241
1242 assert_eq!(store.model().bar_repo().len(), 5);
1243 assert_eq!(store.model().bar_repo().peek_last().unwrap().0, 960 * 5);
1244
1245 store.add_dumper(CtrlChg::new(960 * 5 + 1, Velocity::new(20), Channel::default()), false);
1246
1247 assert_eq!(store.model().bar_repo().len(), 6);
1248 assert_eq!(store.model().bar_repo().peek_last().unwrap().0, 960 * 6);
1249 }
1250
1251 #[test]
1252 fn tuplize() {
1253 let mut dir = tempdir().unwrap().as_ref().to_path_buf();
1254 dir.push("project");
1255 let mut store = SqliteUndoStore::<ProjectCmd, ProjectImpl, ProjectCmdErr>::open(dir.clone(), undo_store::Options::new()).unwrap();
1256
1257 let note0 = Note {
1258 base_start_tick: 100,
1259 pitch: Pitch::new(Solfa::C, Octave::Oct4, SharpFlat::Null),
1260 duration: Duration::new(Numerator::N8th, Denominator::from_value(2).unwrap(), Dots::ZERO),
1261 ..Default::default()
1262 };
1263 store.add_note(note0.clone(), false);
1264
1265 let note1 = Note {
1266 base_start_tick: 120,
1267 pitch: Pitch::new(Solfa::C, Octave::Oct4, SharpFlat::Null),
1268 duration: Duration::new(Numerator::N8th, Denominator::from_value(2).unwrap(), Dots::ZERO),
1269 ..Default::default()
1270 };
1271 store.add_note(note1.clone(), false);
1272
1273 let note2 = Note {
1274 base_start_tick: 150,
1275 pitch: Pitch::new(Solfa::C, Octave::Oct4, SharpFlat::Null),
1276 duration: Duration::new(Numerator::N8th, Denominator::from_value(2).unwrap(), Dots::ZERO),
1277 ..Default::default()
1278 };
1279 store.add_note(note2.clone(), false);
1280 assert_eq!(store.model().note_repo().len(), 3);
1281
1282 store.tuplize(vec![Rc::new(note0), Rc::new(note1), Rc::new(note2)]);
1283 assert_eq!(store.model().note_repo().len(), 3);
1284
1285 let mut z = store.model().note_repo().iter();
1286 let (tick, note) = z.next().unwrap();
1287 assert_eq!(*tick, 100);
1288 assert_eq!(note.duration, Duration::new(Numerator::N8th, Denominator::from_value(3).unwrap(), Dots::ZERO));
1289
1290 let (tick, note) = z.next().unwrap();
1291 assert_eq!(*tick, 100 + 80);
1292 assert_eq!(note.duration, Duration::new(Numerator::N8th, Denominator::from_value(3).unwrap(), Dots::ZERO));
1293
1294 let (tick, note) = z.next().unwrap();
1295 assert_eq!(*tick, 100 + 80 * 2);
1296 assert_eq!(note.duration, Duration::new(Numerator::N8th, Denominator::from_value(3).unwrap(), Dots::ZERO));
1297
1298 store.undo();
1299 assert_eq!(store.model().note_repo().len(), 3);
1300
1301 let mut z = store.model().note_repo().iter();
1302 let (tick, note) = z.next().unwrap();
1303 assert_eq!(*tick, 100);
1304 assert_eq!(note.duration, Duration::new(Numerator::N8th, Denominator::from_value(2).unwrap(), Dots::ZERO));
1305
1306 let (tick, note) = z.next().unwrap();
1307 assert_eq!(*tick, 120);
1308 assert_eq!(note.duration, Duration::new(Numerator::N8th, Denominator::from_value(2).unwrap(), Dots::ZERO));
1309
1310 let (tick, note) = z.next().unwrap();
1311 assert_eq!(*tick, 150);
1312 assert_eq!(note.duration, Duration::new(Numerator::N8th, Denominator::from_value(2).unwrap(), Dots::ZERO));
1313 }
1314
1315 #[test]
1316 fn can_serialize_project() {
1317 let mut proj = ProjectImpl {
1318 key: Key::FLAT_2,
1319 ..Default::default()
1320 };
1321 proj.rhythm = Rhythm::new(3, 4);
1322
1323 let note0 = Rc::new(Note {
1324 base_start_tick: 100,
1325 pitch: Pitch::new(Solfa::C, Octave::Oct4, SharpFlat::Null),
1326 duration: Duration::new(Numerator::N8th, Denominator::from_value(2).unwrap(), Dots::ZERO),
1327 ..Default::default()
1328 });
1329 proj.note_repo.add(note0.start_tick(), note0.clone(), ModelChangeMetadata::default());
1330
1331 let ser = bincode::serialize(&proj).unwrap();
1332
1333 let des: ProjectImpl = bincode::deserialize(&ser).unwrap();
1334
1335 assert_eq!(proj.key, des.key);
1336 assert_eq!(proj.rhythm, des.rhythm);
1337 assert_eq!(des.note_repo.len(), 1);
1338 assert_eq!(*des.note_repo.iter().map(|(_, n)| n).next().unwrap(), note0);
1339 }
1340
1341 use tempfile::tempdir;
1342 use super::Project;
1343
1344 #[test]
1345 fn can_undo_set_rhythm() {
1346 let mut dir = tempdir().unwrap().as_ref().to_path_buf();
1347 dir.push("project");
1348 let mut store = SqliteUndoStore::<ProjectCmd, ProjectImpl, ProjectCmdErr>::open(dir.clone(), undo_store::Options::new()).unwrap();
1349
1350 store.set_rhythm(Rhythm::new(12, 8));
1351 store.wait_until_saved();
1352 assert_eq!(store.model().rhythm(), Rhythm::new(12, 8));
1353
1354 store.set_rhythm(Rhythm::new(12, 4));
1355 store.wait_until_saved();
1356 assert_eq!(store.model().rhythm(), Rhythm::new(12, 4));
1357
1358 store.undo();
1359 assert_eq!(store.model().rhythm(), Rhythm::new(12, 8));
1360
1361 store.undo();
1362 assert_eq!(store.model().rhythm(), Rhythm::default());
1363
1364 store.redo();
1365 assert_eq!(store.model().rhythm(), Rhythm::new(12, 8));
1366
1367 store.set_rhythm(Rhythm::new(16, 8));
1368 store.wait_until_saved();
1369 assert_eq!(store.model().rhythm(), Rhythm::new(16, 8));
1370
1371 store.undo();
1372 assert_eq!(store.model().rhythm(), Rhythm::new(12, 8));
1373 }
1374
1375 #[test]
1376 fn can_undo_set_key() {
1377 let mut dir = tempdir().unwrap().as_ref().to_path_buf();
1378 dir.push("project");
1379 let mut store = SqliteUndoStore::<ProjectCmd, ProjectImpl, ProjectCmdErr>::open(dir.clone(), undo_store::Options::new()).unwrap();
1380
1381 store.set_key(Key::FLAT_1);
1382 store.set_key(Key::FLAT_2);
1383 store.wait_until_saved();
1384 assert_eq!(store.model().key, Key::FLAT_2);
1385
1386 store.undo();
1387 assert_eq!(store.model().key, Key::FLAT_1);
1388
1389 store.redo();
1390 assert_eq!(store.model().key, Key::FLAT_2);
1391 }
1392
1393 #[test]
1394 fn can_undo_set_grid() {
1395 let mut dir = tempdir().unwrap().as_ref().to_path_buf();
1396 dir.push("project");
1397 let mut store = SqliteUndoStore::<ProjectCmd, ProjectImpl, ProjectCmdErr>::open(dir.clone(), undo_store::Options::new()).unwrap();
1398
1399 store.set_grid(Grid::from_u32(100).unwrap());
1400 store.set_grid(Grid::from_u32(200).unwrap());
1401 store.wait_until_saved();
1402 assert_eq!(store.model().grid.as_u32(), 200);
1403
1404 store.undo();
1405 assert_eq!(store.model().grid.as_u32(), 100);
1406
1407 store.redo();
1408 assert_eq!(store.model().grid.as_u32(), 200);
1409 }
1410
1411 #[test]
1412 fn can_undo_add_note() {
1413 let mut dir = tempdir().unwrap().as_ref().to_path_buf();
1414 dir.push("project");
1415 let mut store = SqliteUndoStore::<ProjectCmd, ProjectImpl, ProjectCmdErr>::open(dir.clone(), undo_store::Options::new()).unwrap();
1416
1417 let note0 = Note {
1418 base_start_tick: 100,
1419 pitch: Pitch::new(Solfa::C, Octave::Oct4, SharpFlat::Null),
1420 duration: Duration::new(Numerator::N8th, Denominator::from_value(2).unwrap(), Dots::ZERO),
1421 ..Default::default()
1422 };
1423
1424 store.add_note(note0.clone(), false);
1425 store.wait_until_saved();
1426 assert_eq!(store.model().note_repo.len(), 1);
1427 assert_eq!(store.model().bar_repo.len(), 1); store.undo();
1430 assert_eq!(store.model().note_repo.len(), 0);
1431 assert_eq!(store.model().bar_repo.len(), 0);
1432
1433 store.redo();
1434 assert_eq!(store.model().note_repo.len(), 1);
1435 assert_eq!(store.model().note_repo.get(100u32), &vec![Rc::new(note0.clone())]);
1436 assert_eq!(store.model().bar_repo.len(), 1);
1437 }
1438
1439 #[test]
1440 fn can_undo_add_bar() {
1441 let mut dir = tempdir().unwrap().as_ref().to_path_buf();
1442 dir.push("project");
1443 let mut store = SqliteUndoStore::<ProjectCmd, ProjectImpl, ProjectCmdErr>::open(dir.clone(), undo_store::Options::new()).unwrap();
1444
1445 let bar0 = Bar::new(
1446 1000, Some(Rhythm::new(3, 4)), None, RepeatSet::EMPTY
1447 );
1448
1449 store.add_bar(bar0, false);
1450 store.wait_until_saved();
1451 assert_eq!(store.model().bar_repo.len(), 1); store.undo();
1454 assert_eq!(store.model().bar_repo.len(), 0);
1455
1456 store.redo();
1457 assert_eq!(store.model().bar_repo.len(), 1);
1458 assert_eq!(store.model().bar_repo[0], (1000u32, bar0))
1459 }
1460
1461 #[test]
1462 fn can_undo_add_tempo() {
1463 let mut dir = tempdir().unwrap().as_ref().to_path_buf();
1464 dir.push("project");
1465 let mut store = SqliteUndoStore::<ProjectCmd, ProjectImpl, ProjectCmdErr>::open(dir.clone(), undo_store::Options::new()).unwrap();
1466
1467 let tempo0 = Tempo::new(200, 200);
1468
1469 store.add_tempo(tempo0, false);
1470 store.wait_until_saved();
1471 assert_eq!(store.model().tempo_repo.len(), 1);
1472 assert_eq!(store.model().bar_repo.len(), 1); store.undo();
1475 assert_eq!(store.model().tempo_repo.len(), 0);
1476 assert_eq!(store.model().bar_repo.len(), 0);
1477
1478 store.redo();
1479 assert_eq!(store.model().tempo_repo.len(), 1);
1480 assert_eq!(store.model().bar_repo.len(), 1);
1481 assert_eq!(store.model().tempo_repo[0], (200u32, tempo0));
1482 }
1483
1484 #[test]
1485 fn can_undo_add_dumper() {
1486 let mut dir = tempdir().unwrap().as_ref().to_path_buf();
1487 dir.push("project");
1488 let mut store = SqliteUndoStore::<ProjectCmd, ProjectImpl, ProjectCmdErr>::open(dir.clone(), undo_store::Options::new()).unwrap();
1489
1490 let dumper = CtrlChg::new(200, Velocity::new(20), Channel::default());
1491 store.add_dumper(dumper, false);
1492 store.wait_until_saved();
1493 assert_eq!(store.model().dumper_repo.len(), 1);
1494 assert_eq!(store.model().bar_repo.len(), 1); store.undo();
1497 assert_eq!(store.model().dumper_repo.len(), 0);
1498 assert_eq!(store.model().bar_repo.len(), 0);
1499
1500 store.redo();
1501 assert_eq!(store.model().dumper_repo.len(), 1);
1502 assert_eq!(store.model().bar_repo.len(), 1);
1503 assert_eq!(store.model().dumper_repo[0], (200u32, dumper));
1504 }
1505
1506 #[test]
1507 fn can_undo_add_soft() {
1508 let mut dir = tempdir().unwrap().as_ref().to_path_buf();
1509 dir.push("project");
1510 let mut store = SqliteUndoStore::<ProjectCmd, ProjectImpl, ProjectCmdErr>::open(dir.clone(), undo_store::Options::new()).unwrap();
1511
1512 let soft = CtrlChg::new(200, Velocity::new(20), Channel::default());
1513 store.add_soft(soft, false);
1514 store.wait_until_saved();
1515 assert_eq!(store.model().soft_repo.len(), 1);
1516 assert_eq!(store.model().bar_repo.len(), 1); store.undo();
1519 assert_eq!(store.model().soft_repo.len(), 0);
1520 assert_eq!(store.model().bar_repo.len(), 0);
1521
1522 store.redo();
1523 assert_eq!(store.model().soft_repo.len(), 1);
1524 assert_eq!(store.model().bar_repo.len(), 1);
1525 assert_eq!(store.model().soft_repo[0], (200u32, soft));
1526 }
1527
1528 #[test]
1529 fn can_undo_tuplize() {
1530 let mut dir = tempdir().unwrap().as_ref().to_path_buf();
1531 dir.push("project");
1532 let mut store = SqliteUndoStore::<ProjectCmd, ProjectImpl, ProjectCmdErr>::open(dir.clone(), undo_store::Options::new()).unwrap();
1533
1534 store.tuplize(vec![]);
1536 assert_eq!(store.model().soft_repo.len(), 0);
1537 assert_eq!(store.model().bar_repo.len(), 0);
1538
1539 let note0 = Note {
1540 base_start_tick: 100,
1541 pitch: Pitch::new(Solfa::C, Octave::Oct4, SharpFlat::Null),
1542 duration: Duration::new(Numerator::N8th, Denominator::from_value(2).unwrap(), Dots::ZERO),
1543 ..Default::default()
1544 };
1545 store.add_note(note0.clone(), false);
1546
1547 store.tuplize(vec![Rc::new(note0.clone())]);
1549 store.wait_until_saved();
1550 assert_eq!(store.model().note_repo.len(), 1);
1551 assert_eq!(store.model().bar_repo.len(), 1); assert_eq!(store.model().note_repo.get(100u32), &vec![Rc::new(note0.clone())]);
1553
1554 store.undo(); assert_eq!(store.model().note_repo.len(), 0);
1556 assert_eq!(store.model().bar_repo.len(), 0);
1557
1558 let note1 = Note {
1559 base_start_tick: 110,
1560 pitch: Pitch::new(Solfa::D, Octave::Oct4, SharpFlat::Null),
1561 duration: Duration::new(Numerator::N8th, Denominator::from_value(2).unwrap(), Dots::ZERO),
1562 ..Default::default()
1563 };
1564
1565 let note2 = Note {
1566 base_start_tick: 120,
1567 pitch: Pitch::new(Solfa::E, Octave::Oct4, SharpFlat::Null),
1568 duration: Duration::new(Numerator::N8th, Denominator::from_value(2).unwrap(), Dots::ZERO),
1569 ..Default::default()
1570 };
1571 store.add_note(note0.clone(), false);
1572 store.add_note(note1.clone(), false);
1573 store.add_note(note2.clone(), false);
1574
1575 store.tuplize(vec![Rc::new(note0.clone()), Rc::new(note1.clone()), Rc::new(note2.clone())]);
1576 store.wait_until_saved();
1577
1578 assert_eq!(store.model().note_repo.len(), 3);
1579 assert_eq!(store.model().bar_repo.len(), 1);
1580
1581 store.undo();
1582 assert_eq!(store.model().note_repo.len(), 3);
1583 assert_eq!(store.model().bar_repo.len(), 1);
1584 assert_eq!(store.model().note_repo.get(100u32)[0].pitch.solfa(), Solfa::C);
1585 assert_eq!(store.model().note_repo.get(110u32)[0].pitch.solfa(), Solfa::D);
1586 assert_eq!(store.model().note_repo.get(120u32)[0].pitch.solfa(), Solfa::E);
1587
1588 store.redo();
1589 assert_eq!(store.model().note_repo.len(), 3);
1590 assert_eq!(store.model().bar_repo.len(), 1);
1591
1592 assert_eq!(store.model().note_repo.get(100u32)[0].pitch.solfa(), Solfa::C);
1593 assert_eq!(store.model().note_repo.get(180u32)[0].pitch.solfa(), Solfa::D);
1594 assert_eq!(store.model().note_repo.get(260u32)[0].pitch.solfa(), Solfa::E);
1595
1596 }
1597
1598 #[test]
1599 fn can_undo_bulk_remove() {
1600 let mut dir = tempdir().unwrap().as_ref().to_path_buf();
1601 dir.push("project");
1602 let mut store = SqliteUndoStore::<ProjectCmd, ProjectImpl, ProjectCmdErr>::open(dir.clone(), undo_store::Options::new()).unwrap();
1603
1604 let note0 = Note {
1605 base_start_tick: 100,
1606 pitch: Pitch::new(Solfa::C, Octave::Oct4, SharpFlat::Null),
1607 duration: Duration::new(Numerator::N8th, Denominator::from_value(2).unwrap(), Dots::ZERO),
1608 ..Default::default()
1609 };
1610 let tempo0 = Tempo::new(200, 200);
1611 store.bulk_add(
1612 Models {
1613 notes: vec![note0.clone()],
1614 bars: vec![], tempos: vec![tempo0], dumpers: vec![], softs: vec![]
1615 },
1616 ModelChangeMetadata::default()
1617 );
1618 store.wait_until_saved();
1619 assert_eq!(store.model().note_repo().len(), 1);
1620 assert_eq!(store.model().bar_repo().len(), 1); assert_eq!(store.model().tempo_repo().len(), 1);
1622
1623 store.bulk_remove(
1624 Models {
1625 notes: vec![note0.clone()],
1626 bars: vec![], tempos: vec![tempo0], dumpers: vec![], softs: vec![]
1627 },
1628 ModelChangeMetadata::default()
1629 );
1630 store.wait_until_saved();
1631
1632 assert_eq!(store.model().note_repo().len(), 0);
1633 assert_eq!(store.model().bar_repo().len(), 1);
1634 assert_eq!(store.model().tempo_repo().len(), 0);
1635
1636 store.undo();
1637
1638 assert_eq!(store.model().note_repo().len(), 1);
1639 assert_eq!(store.model().bar_repo().len(), 1); assert_eq!(store.model().tempo_repo().len(), 1);
1641 }
1642
1643 #[test]
1644 fn can_undo_bulk_add() {
1645 let mut dir = tempdir().unwrap().as_ref().to_path_buf();
1646 dir.push("project");
1647 let mut store = SqliteUndoStore::<ProjectCmd, ProjectImpl, ProjectCmdErr>::open(dir.clone(), undo_store::Options::new()).unwrap();
1648
1649 let note0 = Note {
1650 base_start_tick: 100,
1651 pitch: Pitch::new(Solfa::C, Octave::Oct4, SharpFlat::Null),
1652 duration: Duration::new(Numerator::N8th, Denominator::from_value(2).unwrap(), Dots::ZERO),
1653 ..Default::default()
1654 };
1655
1656 let tempo0 = Tempo::new(200, 200);
1657 store.bulk_add(
1658 Models {
1659 notes: vec![note0.clone()],
1660 bars: vec![], tempos: vec![tempo0], dumpers: vec![], softs: vec![]
1661 },
1662 ModelChangeMetadata::default()
1663 );
1664 store.wait_until_saved();
1665
1666 assert_eq!(store.model().note_repo().len(), 1);
1667 assert_eq!(store.model().bar_repo().len(), 1); assert_eq!(store.model().tempo_repo().len(), 1);
1669
1670 store.undo();
1671
1672 assert_eq!(store.model().note_repo().len(), 0);
1673 assert_eq!(store.model().bar_repo().len(), 0);
1674 assert_eq!(store.model().tempo_repo().len(), 0);
1675 }
1676
1677 #[test]
1678 fn can_undo_change() {
1679 let mut dir = tempdir().unwrap().as_ref().to_path_buf();
1680 dir.push("project");
1681 let mut store = SqliteUndoStore::<ProjectCmd, ProjectImpl, ProjectCmdErr>::open(dir.clone(), undo_store::Options::new()).unwrap();
1682
1683 let note00 = Note {
1684 base_start_tick: 100,
1685 pitch: Pitch::new(Solfa::C, Octave::Oct4, SharpFlat::Null),
1686 duration: Duration::new(Numerator::N8th, Denominator::from_value(2).unwrap(), Dots::ZERO),
1687 ..Default::default()
1688 };
1689 let note01 = Note {
1690 base_start_tick: 101,
1691 pitch: Pitch::new(Solfa::C, Octave::Oct3, SharpFlat::Null),
1692 duration: Duration::new(Numerator::N8th, Denominator::from_value(2).unwrap(), Dots::ZERO),
1693 ..Default::default()
1694 };
1695 let tempo0 = Tempo::new(200, 200);
1696 store.bulk_add(
1697 Models {
1698 notes: vec![note00.clone(), note01.clone()],
1699 bars: vec![], tempos: vec![tempo0], dumpers: vec![], softs: vec![]
1700 },
1701 ModelChangeMetadata::default()
1702 );
1703 store.wait_until_saved();
1704
1705 assert_eq!(store.model().note_repo().len(), 2);
1706 assert_eq!(store.model().bar_repo().len(), 1); assert_eq!(store.model().tempo_repo().len(), 1);
1708
1709 let note10 = note00.with_duration_numerator(Numerator::Half);
1710 let note11 = note01.with_duration_numerator(Numerator::Half);
1711 let change = ModelChanges {
1712 notes: vec![(note00.clone(), note10.clone()), (note01.clone(), note11.clone())],
1713 bars: vec![], tempos: vec![], dumpers: vec![], softs: vec![]
1714 };
1715 store.change(change, ModelChangeMetadata::default());
1716 store.wait_until_saved();
1717
1718 assert_eq!(store.model().note_repo().len(), 2);
1719 let mut z = store.model().note_repo().iter();
1720 assert_eq!(z.next(), Some((¬e10.start_tick(), &Rc::new(note10.clone()))));
1721 assert_eq!(z.next(), Some((¬e11.start_tick(), &Rc::new(note11.clone()))));
1722 assert_eq!(z.next(), None);
1723 let mut z = store.model().tempo_repo().iter();
1724 assert_eq!(z.next(), Some(&(tempo0.start_tick, tempo0)));
1725 assert_eq!(z.next(), None);
1726
1727 store.undo();
1728
1729 assert_eq!(store.model().note_repo().len(), 2);
1730 let mut z = store.model().note_repo().iter();
1731 assert_eq!(z.next(), Some((¬e00.start_tick(), &Rc::new(note00.clone()))));
1732 assert_eq!(z.next(), Some((¬e01.start_tick(), &Rc::new(note01.clone()))));
1733 assert_eq!(z.next(), None);
1734 let mut z = store.model().tempo_repo().iter();
1735 assert_eq!(z.next(), Some(&(tempo0.start_tick, tempo0)));
1736 assert_eq!(z.next(), None);
1737 }
1738
1739 #[test]
1740 fn many_changes() {
1741 let mut dir = tempdir().unwrap().as_ref().to_path_buf();
1742 dir.push("project");
1743 let mut store = SqliteUndoStore::<ProjectCmd, ProjectImpl, ProjectCmdErr>::open(dir.clone(), undo_store::Options::new().with_undo_limit(10)).unwrap();
1744
1745 let note00 = Note {
1746 base_start_tick: 100,
1747 pitch: Pitch::new(Solfa::C, Octave::Oct4, SharpFlat::Null),
1748 duration: Duration::new(Numerator::N8th, Denominator::from_value(2).unwrap(), Dots::ZERO),
1749 ..Default::default()
1750 };
1751 let note01 = Note {
1752 base_start_tick: 101,
1753 pitch: Pitch::new(Solfa::C, Octave::Oct3, SharpFlat::Null),
1754 duration: Duration::new(Numerator::N8th, Denominator::from_value(2).unwrap(), Dots::ZERO),
1755 ..Default::default()
1756 };
1757 store.add_note(note00, false);
1758 store.add_note(note01, false);
1759
1760 for i in 0..20 {
1761 let tempo = Tempo::new(200 + i, 200 + i as u16);
1762 store.add_tempo(tempo, false);
1763 }
1764 assert_eq!(store.model().note_repo().len(), 2);
1765 assert_eq!(store.model().tempo_repo().len(), 20);
1766 drop(store);
1767
1768 let store2 = SqliteUndoStore::<ProjectCmd, ProjectImpl, ProjectCmdErr>::open(dir.clone(), undo_store::Options::new().with_undo_limit(10)).unwrap();
1769 assert_eq!(store2.model().note_repo().len(), 2);
1770 assert_eq!(store2.model().tempo_repo().len(), 20);
1771 }
1772}