klavier_core/
project.rs

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    /// None: Do not care.
48    /// Some(true): Need to select.
49    /// Some(false): Need to unselect.
50    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>, // by start tick.
71    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    // Add bars without posting undo info.
182    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    /// Returns bar no(0 offset) and bar.
291    #[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    /// Returns replenished bars.
327    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(&note)).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(&notes),
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(&note_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        // 0   bar0(t=100)
1020        //     |
1021        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        // 0   bar0(t=100)  bar1(t=1000) bar2(t=2000 r=2/4) bar3(t = 3000)
1044        //     |            |            |                  |
1045        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 { // end tick: 100 + 240 * 1.5 = 460
1144            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 { // end tick: 100 + 960 * 1.5 = 1540
1155            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 { // end tick: 200 + 120 * 2.5 = 440
1166                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 { // end tick: 100 + 240 * 1.5 = 460
1187                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); // bar is replenished.
1428        
1429        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); // with replenished bar
1452        
1453        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); // bar is replenished.
1473        
1474        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); // bar is replenished.
1495        
1496        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); // bar is replenished.
1517        
1518        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        // Do nothing for empty.
1535        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        // Tuplize one note do nothing.
1548        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); // bar replenished
1552        assert_eq!(store.model().note_repo.get(100u32), &vec![Rc::new(note0.clone())]);
1553        
1554        store.undo(); // this undo adding note.
1555        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); // bar is replenished.
1621        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); // bar is replenished.
1640        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); // bar is replenished.
1668        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); // bar is replenished.
1707        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((&note10.start_tick(), &Rc::new(note10.clone()))));
1721        assert_eq!(z.next(), Some((&note11.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((&note00.start_tick(), &Rc::new(note00.clone()))));
1732        assert_eq!(z.next(), Some((&note01.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}