klavier_core/
models.rs

1use std::{rc::Rc, io::Cursor};
2
3use serde_json::Value;
4
5use crate::{note::Note, bar::Bar, tempo::Tempo, ctrl_chg::CtrlChg};
6
7#[derive(Clone, PartialEq, Debug, serde::Deserialize, serde::Serialize)]
8pub struct Models {
9    pub notes: Vec<Note>,
10    pub bars: Vec<Bar>,
11    pub tempos: Vec<Tempo>,
12    pub dumpers: Vec<CtrlChg>,
13    pub softs: Vec<CtrlChg>,
14}
15
16#[derive(Debug, PartialEq)]
17pub enum FromClipboardTextErr {
18    VersionErr { detected_ver: u64 },
19    CannotParse { err_json: String, detail: String },
20    EmptyString,
21    VersionNotU64 { err_json: String },
22}
23
24impl Models {
25    pub const VERSION: u64 = 1;
26
27    #[inline]
28    pub fn unwrap_rc(notes: &[Rc<Note>]) -> Vec<Note> {
29        notes.iter().map(|n| (**n).clone()).collect()
30    }
31
32    #[inline]
33    pub fn with_capacity(note: usize, bar: usize, tempo: usize, dumper: usize, soft: usize) -> Self {
34        Self {
35            notes: Vec::with_capacity(note),
36            bars: Vec::with_capacity(bar),
37            tempos: Vec::with_capacity(tempo),
38            dumpers: Vec::with_capacity(dumper),
39            softs: Vec::with_capacity(soft),
40        }
41    }
42
43    #[inline]
44    pub fn empty() -> Self {
45        Self {
46            notes: vec![],
47            bars: vec![],
48            tempos: vec![],
49            dumpers: vec![],
50            softs: vec![],
51        }
52    }
53
54    pub fn move_to_tick(mut self, tick: u32) -> Self {
55        let mut smallest_tick: u32 = u32::MAX;
56        for n in self.notes.iter() {
57            smallest_tick = std::cmp::min(n.base_start_tick, smallest_tick);
58        }
59        for b in self.bars.iter() {
60            smallest_tick = std::cmp::min(b.start_tick, smallest_tick);
61        }
62        for t in self.tempos.iter() {
63            smallest_tick = std::cmp::min(t.start_tick, smallest_tick);
64        }
65        for d in self.dumpers.iter() {
66            smallest_tick = std::cmp::min(d.start_tick, smallest_tick);
67        }
68        for s in self.softs.iter() {
69            smallest_tick = std::cmp::min(s.start_tick, smallest_tick);
70        }
71
72        let offset: i64 = (tick as i64) - (smallest_tick as i64);
73        if offset == 0 { return self }
74
75        for n in self.notes.iter_mut() {
76            n.base_start_tick = ((n.base_start_tick as i64) + offset) as u32;
77        }
78        for b in self.bars.iter_mut() {
79            b.start_tick = ((b.start_tick as i64) + offset) as u32;
80        }
81        for t in self.tempos.iter_mut() {
82            t.start_tick = ((t.start_tick as i64) + offset) as u32;
83        }
84        for d in self.dumpers.iter_mut() {
85            d.start_tick = ((d.start_tick as i64) + offset) as u32;
86        }
87        for s in self.softs.iter_mut() {
88            s.start_tick = ((s.start_tick as i64) + offset) as u32;
89        }
90
91        self
92    }
93
94    pub fn with_notes(mut self, notes: &[Rc<Note>]) -> Self {
95        self.notes = Self::unwrap_rc(notes);
96        self
97    }
98
99    pub fn with_bars(mut self, bars: Vec<Bar>) -> Self {
100        self.bars = bars;
101        self
102    }
103
104    pub fn with_tempos(mut self, tempos: Vec<Tempo>) -> Self {
105        self.tempos = tempos;
106        self
107    }
108
109    pub fn with_dumpers(mut self, dumpers: Vec<CtrlChg>) -> Self {
110        self.dumpers = dumpers;
111        self
112    }
113
114    pub fn with_softs(mut self, softs: Vec<CtrlChg>) -> Self {
115        self.softs = softs;
116        self
117    }
118
119    pub fn to_clipboard_text(&self) -> String {
120        use std::io::prelude::*;
121
122        let mut c = Cursor::new(Vec::new());
123        c.write_all(b"1").unwrap();
124        serde_json::to_writer(&mut c, self).unwrap();
125        String::from_utf8_lossy(c.get_ref()).into_owned()
126    }
127
128    pub fn from_clipboard_text(json: String) -> Result<Self, FromClipboardTextErr> {
129        let mut stream = serde_json::Deserializer::from_str(&json).into_iter::<Value>();
130        match stream.next() {
131            None => Err(FromClipboardTextErr::EmptyString),
132            Some(Ok(ver)) =>
133                if let Value::Number(ver_no) = ver {
134                    if let Some(v) = ver_no.as_u64() {
135                        if v == Self::VERSION {
136                            serde_json::from_slice::<'_, Models>(&json.as_bytes()[stream.byte_offset()..])
137                                .map_err(|e| FromClipboardTextErr::CannotParse { err_json: json, detail: e.to_string() })
138                        } else {
139                            Err(FromClipboardTextErr::VersionErr { detected_ver: v })
140                        }
141                    } else {
142                        Err(FromClipboardTextErr::VersionNotU64 { err_json: json })
143                    }
144                } else {
145                    Err(FromClipboardTextErr::VersionNotU64 { err_json: json })
146                },
147            Some(Err(e)) => Err(FromClipboardTextErr::CannotParse { err_json: json, detail: e.to_string() })
148        }
149    }
150}
151
152pub struct ModelChanges {
153    pub notes: Vec<(Note, Note)>,
154    pub bars: Vec<(Bar, Bar)>,
155    pub tempos: Vec<(Tempo, Tempo)>,
156    pub dumpers: Vec<(CtrlChg, CtrlChg)>,
157    pub softs: Vec<(CtrlChg, CtrlChg)>,
158}
159
160impl ModelChanges {
161    #[inline]
162    pub fn empty() -> Self {
163        Self {
164            notes: vec![],
165            bars: vec![],
166            tempos: vec![],
167            dumpers: vec![],
168            softs: vec![],
169        }
170    }
171
172    #[inline]
173    pub fn with_capacity(note: usize, bar: usize, tempo: usize, dumper: usize, soft: usize) -> Self {
174        Self {
175            notes: Vec::with_capacity(note),
176            bars: Vec::with_capacity(bar),
177            tempos: Vec::with_capacity(tempo),
178            dumpers: Vec::with_capacity(dumper),
179            softs: Vec::with_capacity(soft),
180        }
181    }
182
183    pub fn with_notes(mut self, notes: Vec<(Note, Note)>) -> Self {
184        self.notes = notes;
185        self
186    }
187
188    pub fn with_bars(mut self, bars: Vec<(Bar, Bar)>) -> Self {
189        self.bars = bars;
190        self
191    }
192
193    pub fn with_tempos(mut self, tempos: Vec<(Tempo, Tempo)>) -> Self {
194        self.tempos = tempos;
195        self
196    }
197
198    pub fn with_dumpers(mut self, dumpers: Vec<(CtrlChg, CtrlChg)>) -> Self {
199        self.dumpers = dumpers;
200        self
201    }
202
203    pub fn with_softs(mut self, softs: Vec<(CtrlChg, CtrlChg)>) -> Self {
204        self.softs = softs;
205        self
206    }
207}
208
209#[cfg(test)]
210mod clipboard_tests {
211    use crate::{models::{Models, FromClipboardTextErr}, note::Note, pitch::Pitch, solfa::Solfa, octave::Octave, sharp_flat::SharpFlat, duration::{self, Duration, Dots}, velocity::Velocity, bar::{Bar, RepeatSet}, tempo::Tempo, ctrl_chg::CtrlChg, channel::Channel};
212
213    #[test]
214    fn parse_empty() {
215        assert_eq!(Models::from_clipboard_text("".to_owned()), Err(FromClipboardTextErr::EmptyString));
216        assert_eq!(Models::from_clipboard_text(" ".to_owned()), Err(FromClipboardTextErr::EmptyString));
217    }
218
219    #[test]
220    fn cannot_parse_version() {
221        assert_eq!(Models::from_clipboard_text("-1".to_owned()), Err(FromClipboardTextErr::VersionNotU64 { err_json: "-1".to_owned()}));
222        assert_eq!(Models::from_clipboard_text("1.1".to_owned()), Err(FromClipboardTextErr::VersionNotU64 { err_json: "1.1".to_owned() }));
223        assert_eq!(Models::from_clipboard_text("[0]".to_owned()), Err(FromClipboardTextErr::VersionNotU64 { err_json: "[0]".to_owned() }));
224
225        if let Err(FromClipboardTextErr::CannotParse { err_json: json, detail: _ }) = Models::from_clipboard_text("a".to_owned()) {
226            assert_eq!(json, "a".to_owned());
227        } else {
228            panic!("Logic error.");
229        }
230    }
231
232    #[test]
233    fn version_error() {
234        let ver_str = (Models::VERSION + 1).to_string();
235
236        assert_eq!(Models::from_clipboard_text(ver_str), Err(FromClipboardTextErr::VersionErr { detected_ver: Models::VERSION + 1 }));
237    }
238
239    #[test]
240    fn normal_case() {
241        let pitch = Pitch::new(Solfa::C, Octave::Oct0, SharpFlat::Null);
242        let note = Note {
243            base_start_tick: 100,
244            pitch,
245            duration: Duration::new(duration::Numerator::Quarter, duration::Denominator::from_value(2).unwrap(), Dots::ONE),
246            tie: false, tied: false,
247            base_velocity: Velocity::new(10),
248            ..Default::default()
249        };
250
251        let bar = Bar::new(
252            100,
253            None,
254            None,
255            RepeatSet::EMPTY,
256        );
257
258        let models = Models {
259            notes: vec![note], bars: vec![bar], tempos: vec![], dumpers: vec![], softs: vec![]
260        };
261
262        let json = models.to_clipboard_text();
263
264        let restored = Models::from_clipboard_text(json).unwrap();
265        assert_eq!(restored, models);
266    }
267
268    #[test]
269    fn move_to_tick() {
270        let pitch = Pitch::new(Solfa::C, Octave::Oct0, SharpFlat::Null);
271        let note = Note {
272            base_start_tick: 100,
273            pitch,
274            duration: Duration::new(duration::Numerator::Quarter, duration::Denominator::from_value(2).unwrap(), Dots::ONE),
275            tie: false, tied: false,
276            base_velocity: Velocity::new(10),
277            ..Default::default()
278        };
279
280        let bar = Bar::new(
281            110,
282            None, None, RepeatSet::EMPTY
283        );
284
285        let tempo0 = Tempo::new(110, 200);
286        let tempo1 = Tempo::new(114, 205);
287            
288        let dumper = CtrlChg::new(120, Velocity::new(64), Channel::default());
289
290        let soft = CtrlChg::new(90, Velocity::new(64), Channel::default());
291
292        let models = Models {
293            notes: vec![note],
294            bars: vec![bar],
295            tempos: vec![tempo0, tempo1],
296            dumpers: vec![dumper],
297            softs: vec![soft]
298        }.move_to_tick(50);
299
300        assert_eq!(models.notes[0].base_start_tick, 60);
301        assert_eq!(models.bars[0].start_tick, 70);
302        assert_eq!(models.tempos[0].start_tick, 70);
303        assert_eq!(models.tempos[1].start_tick, 74);
304        assert_eq!(models.dumpers[0].start_tick, 80);
305        assert_eq!(models.softs[0].start_tick, 50);
306    }
307}