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}