1mod metadata;
9pub mod settings;
10mod slots;
11mod states;
12
13pub use crate::projects::{
14 metadata::OsMetadata, settings::Settings, slots::SlotAttributes, slots::SlotsAttributes,
15 states::State,
16};
17
18use crate::settings::InvalidValueError;
19use crate::{
20 HasChecksumField, HasFileVersionField, HasHeaderField, OctatrackFileIO, OtToolsIoError,
21};
22use ot_tools_io_derive::IsDefaultCheck;
23use serde::{Deserialize, Serialize};
24use std::{
25 cmp::PartialEq, collections::HashMap, fmt::Debug, fmt::Display, num::ParseIntError,
26 str::FromStr, str::ParseBoolError,
27};
28use thiserror::Error;
29
30#[derive(Debug, Error)]
31pub enum ProjectParseError {
32 #[error("failed to parse integer value")]
33 Int(#[from] ParseIntError),
34 #[error("failed to parse boolean value")]
35 Bool(#[from] ParseBoolError),
36 #[error("failed to parse string value")]
37 String,
38 #[error("failed to parse value: {0} (ot_tools_io::common_options::InvalidValueErrors)")]
39 InvalidValue(#[from] InvalidValueError),
40 #[error("failed to load hash map for parsing")]
41 HashMap,
42 #[error("failed to parse footer data")]
43 Footer,
44}
45
46#[derive(Debug, Error)]
48pub enum ProjectError {
49 #[error("type cannot be checksummed")]
51 NotChecksummable,
52 #[error("type does not have a file patch version field")]
54 NoFilePatchVersionField,
55 #[error("type does not have a header field")]
57 NoHeaderField,
58}
59
60fn parse_hashmap_string_value<T: FromStr>(
63 hmap: &HashMap<String, String>,
64 key: &str,
65 default_str: Option<&str>,
66) -> Result<T, <T as FromStr>::Err>
67where
68 <T as FromStr>::Err: Debug,
69{
70 match default_str {
71 Some(x) => hmap.get(key).unwrap_or(&x.to_string()).parse::<T>(),
72 None => hmap.get(key).unwrap().parse::<T>(),
73 }
74}
75
76fn parse_hashmap_string_value_bool(
79 hmap: &HashMap<String, String>,
80 key: &str,
81 default_str: Option<&str>,
82) -> Result<bool, <u8 as FromStr>::Err> {
83 let val = parse_hashmap_string_value::<u8>(hmap, key, default_str)?;
84 Ok(matches!(val, 1))
85}
86
87fn string_to_hashmap(
89 data: &str,
90 section: &SectionHeader,
91) -> Result<HashMap<String, String>, ProjectParseError> {
92 let start = format!("[{section}]");
93 let end = format!("[/{section}]");
94
95 let start_idx = data.find(&start).ok_or(ProjectParseError::HashMap)?;
96 let start_idx_shifted: usize = start_idx + start.len();
97 let end_idx = data.find(&end).ok_or(ProjectParseError::HashMap)?;
98
99 let section: String = data[start_idx_shifted..end_idx].to_string();
100
101 let mut hmap: HashMap<String, String> = HashMap::new();
102 let mut trig_mode_midi_field_idx = 1;
103
104 for split_s in section.split("\r\n") {
105 if !split_s.is_empty() {
108 let key_pair_string = split_s.to_string();
109 let mut key_pair_split: Vec<&str> = key_pair_string.split('=').collect();
110
111 let key_renamed: String = format!("trig_mode_midi_track_{}", &trig_mode_midi_field_idx);
116 if key_pair_split[0] == "TRIG_MODE_MIDI" {
117 key_pair_split[0] = key_renamed.as_str();
118 trig_mode_midi_field_idx += 1;
119 }
120
121 hmap.insert(
122 key_pair_split[0].to_string().to_ascii_lowercase(),
123 key_pair_split[1].to_string(),
124 );
125 }
126 }
127
128 Ok(hmap)
129}
130
131#[derive(Debug, Error)]
132#[error("invalid project section header value")]
133struct InvalidSectionHeaderValue;
134
135#[derive(Debug, PartialEq)]
137enum SectionHeader {
138 Meta,
139 States,
140 Settings,
141 Samples,
142}
143
144impl TryFrom<&str> for SectionHeader {
145 type Error = InvalidSectionHeaderValue;
146 fn try_from(value: &str) -> Result<Self, Self::Error> {
147 match value.to_ascii_uppercase().as_str() {
148 "META" => Ok(Self::Meta),
149 "STATES" => Ok(Self::States),
150 "SETTINGS" => Ok(Self::Settings),
151 "SAMPLES" => Ok(Self::Samples),
152 _ => Err(InvalidSectionHeaderValue),
153 }
154 }
155}
156
157impl TryFrom<String> for SectionHeader {
158 type Error = InvalidSectionHeaderValue;
159 fn try_from(value: String) -> Result<Self, Self::Error> {
160 value.as_str().try_into()
161 }
162}
163
164impl Display for SectionHeader {
165 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166 let str = match self {
167 Self::Meta => "META".to_string(),
168 Self::States => "STATES".to_string(),
169 Self::Settings => "SETTINGS".to_string(),
170 Self::Samples => "SAMPLES".to_string(),
171 };
172 write!(f, "{str}")
173 }
174}
175
176#[cfg(test)]
177mod test_project_section {
178
179 mod try_from_str {
180 use crate::projects::{InvalidSectionHeaderValue, SectionHeader};
181
182 #[test]
183 fn no_match_is_err() {
184 assert_eq!(
185 SectionHeader::try_from("skfsdkfjskdh")
186 .unwrap_err()
187 .to_string(),
188 InvalidSectionHeaderValue.to_string()
189 )
190 }
191
192 #[test]
193 fn uppercase_meta() {
194 assert_eq!(
195 SectionHeader::try_from("META").unwrap(),
196 SectionHeader::Meta,
197 )
198 }
199
200 #[test]
201 fn uppercase_states() {
202 assert_eq!(
203 SectionHeader::try_from("STATES").unwrap(),
204 SectionHeader::States,
205 )
206 }
207
208 #[test]
209 fn uppercase_settings() {
210 assert_eq!(
211 SectionHeader::try_from("SETTINGS").unwrap(),
212 SectionHeader::Settings,
213 )
214 }
215
216 #[test]
217 fn uppercase_samples() {
218 assert_eq!(
219 SectionHeader::try_from("SAMPLES").unwrap(),
220 SectionHeader::Samples,
221 )
222 }
223
224 #[test]
225 fn lowercase_meta() {
226 assert_eq!(
227 SectionHeader::try_from("meta").unwrap(),
228 SectionHeader::Meta,
229 )
230 }
231
232 #[test]
233 fn lowercase_states() {
234 assert_eq!(
235 SectionHeader::try_from("states").unwrap(),
236 SectionHeader::States,
237 )
238 }
239
240 #[test]
241 fn lowercase_settings() {
242 assert_eq!(
243 SectionHeader::try_from("settings").unwrap(),
244 SectionHeader::Settings,
245 )
246 }
247
248 #[test]
249 fn lowercase_samples() {
250 assert_eq!(
251 SectionHeader::try_from("samples").unwrap(),
252 SectionHeader::Samples,
253 )
254 }
255 }
256
257 mod try_from_string {
258 use crate::projects::SectionHeader;
259
260 #[test]
261 fn no_match_is_err() {
262 assert!(SectionHeader::try_from("skfsdkfjskdh".to_string()).is_err())
263 }
264
265 #[test]
266 fn uppercase_meta() {
267 assert_eq!(
268 SectionHeader::try_from("META".to_string()).unwrap(),
269 SectionHeader::Meta,
270 )
271 }
272
273 #[test]
274 fn uppercase_states() {
275 assert_eq!(
276 SectionHeader::try_from("STATES".to_string()).unwrap(),
277 SectionHeader::States,
278 )
279 }
280
281 #[test]
282 fn uppercase_settings() {
283 assert_eq!(
284 SectionHeader::try_from("SETTINGS".to_string()).unwrap(),
285 SectionHeader::Settings,
286 )
287 }
288
289 #[test]
290 fn uppercase_samples() {
291 assert_eq!(
292 SectionHeader::try_from("SAMPLES".to_string()).unwrap(),
293 SectionHeader::Samples,
294 )
295 }
296
297 #[test]
298 fn lowercase_meta() {
299 assert_eq!(
300 SectionHeader::try_from("meta".to_string()).unwrap(),
301 SectionHeader::Meta,
302 )
303 }
304
305 #[test]
306 fn lowercase_states() {
307 assert_eq!(
308 SectionHeader::try_from("states".to_string()).unwrap(),
309 SectionHeader::States,
310 )
311 }
312
313 #[test]
314 fn lowercase_settings() {
315 assert_eq!(
316 SectionHeader::try_from("settings".to_string()).unwrap(),
317 SectionHeader::Settings,
318 )
319 }
320 #[test]
321 fn lowercase_samples() {
322 assert_eq!(
323 SectionHeader::try_from("samples".to_string()).unwrap(),
324 SectionHeader::Samples,
325 )
326 }
327 }
328
329 mod to_string {
330 use crate::projects::SectionHeader;
331 #[test]
332 fn section_heading_value_meta() {
333 assert_eq!(SectionHeader::Meta.to_string(), "META".to_string())
334 }
335
336 #[test]
337 fn section_heading_value_states() {
338 assert_eq!(SectionHeader::States.to_string(), "STATES".to_string())
339 }
340
341 #[test]
342 fn section_heading_value_settings() {
343 assert_eq!(SectionHeader::Settings.to_string(), "SETTINGS".to_string())
344 }
345
346 #[test]
347 fn section_heading_value_samples() {
348 assert_eq!(SectionHeader::Samples.to_string(), "SAMPLES".to_string())
349 }
350 }
351}
352
353#[derive(IsDefaultCheck, Serialize, Deserialize, PartialEq, Debug, Clone)]
367pub struct ProjectFile {
368 pub metadata: OsMetadata,
370
371 pub settings: Settings,
373
374 pub states: State,
376
377 pub slots: SlotsAttributes,
379}
380
381impl OctatrackFileIO for ProjectFile {
382 fn encode(&self) -> Result<Vec<u8>, OtToolsIoError> {
384 let data = self.to_string();
385 let bytes: Vec<u8> = data.bytes().collect::<Vec<u8>>();
386 Ok(bytes)
387 }
388
389 fn decode(bytes: &[u8]) -> Result<Self, OtToolsIoError> {
392 let s = std::str::from_utf8(bytes)?.to_string();
393
394 let metadata = OsMetadata::from_str(&s)?;
395 let states = State::from_str(&s)?;
396 let settings = Settings::from_str(&s)?;
397 let slots = SlotsAttributes::from_str(&s)?;
398
399 Ok(Self {
400 metadata,
401 settings,
402 states,
403 slots,
404 })
405 }
406}
407
408impl Default for ProjectFile {
409 fn default() -> Self {
410 let metadata = OsMetadata::default();
411 let states = State::default();
412 let settings = Settings::default();
413 let slots = SlotsAttributes::default();
414
415 Self {
416 metadata,
417 settings,
418 states,
419 slots,
420 }
421 }
422}
423
424impl HasChecksumField for ProjectFile {
425 fn calculate_checksum(&self) -> Result<u16, OtToolsIoError> {
426 Err(ProjectError::NotChecksummable.into())
427 }
428 fn check_checksum(&self) -> Result<bool, OtToolsIoError> {
429 Err(ProjectError::NotChecksummable.into())
430 }
431}
432
433impl HasFileVersionField for ProjectFile {
434 fn check_file_version(&self) -> Result<bool, OtToolsIoError> {
435 Err(ProjectError::NoFilePatchVersionField.into())
436 }
437}
438
439impl HasHeaderField for ProjectFile {
440 fn check_header(&self) -> Result<bool, OtToolsIoError> {
441 Err(ProjectError::NoHeaderField.into())
442 }
443}
444
445impl std::fmt::Display for ProjectFile {
446 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
447 let states_header =
448 "############################\r\n# Project States\r\n############################"
449 .to_string();
450 let settings_header =
451 "############################\r\n# Project Settings\r\n############################"
452 .to_string();
453 let slots_header =
454 "############################\r\n# Samples\r\n############################".to_string();
455 let footer = "############################".to_string();
456
457 let metadata_string: String = self.metadata.to_string();
458 let states_string: String = self.states.to_string();
459 let settings_string: String = self.settings.to_string();
460
461 let sample_slots_string = self.slots.to_string();
462
463 let v: Vec<String> = vec![
464 settings_header,
467 metadata_string,
468 settings_string,
469 states_header,
470 states_string,
471 slots_header,
472 sample_slots_string,
473 footer,
474 ];
475 let mut project_string = v.join("\r\n\r\n");
476 project_string.push_str("\r\n\r\n");
477
478 write!(f, "{project_string}")
479 }
480}
481
482#[cfg(test)]
483#[allow(unused_imports)]
484mod tests {
485 use super::*;
486
487 const DEFAULT_STR_FILE: &str = "############################\r\n# Project Settings\r\n############################\r\n\r\n[META]\r\nTYPE=OCTATRACK DPS-1 PROJECT\r\nVERSION=19\r\nOS_VERSION=R0177 1.40B\r\n[/META]\r\n\r\n[SETTINGS]\r\nWRITEPROTECTED=0\r\nTEMPOx24=2880\r\nPATTERN_TEMPO_ENABLED=0\r\nMIDI_CLOCK_SEND=0\r\nMIDI_CLOCK_RECEIVE=0\r\nMIDI_TRANSPORT_SEND=0\r\nMIDI_TRANSPORT_RECEIVE=0\r\nMIDI_PROGRAM_CHANGE_SEND=0\r\nMIDI_PROGRAM_CHANGE_SEND_CH=-1\r\nMIDI_PROGRAM_CHANGE_RECEIVE=0\r\nMIDI_PROGRAM_CHANGE_RECEIVE_CH=-1\r\nMIDI_TRIG_CH1=0\r\nMIDI_TRIG_CH2=1\r\nMIDI_TRIG_CH3=2\r\nMIDI_TRIG_CH4=3\r\nMIDI_TRIG_CH5=4\r\nMIDI_TRIG_CH6=5\r\nMIDI_TRIG_CH7=6\r\nMIDI_TRIG_CH8=7\r\nMIDI_AUTO_CHANNEL=10\r\nMIDI_SOFT_THRU=0\r\nMIDI_AUDIO_TRK_CC_IN=1\r\nMIDI_AUDIO_TRK_CC_OUT=3\r\nMIDI_AUDIO_TRK_NOTE_IN=1\r\nMIDI_AUDIO_TRK_NOTE_OUT=3\r\nMIDI_MIDI_TRK_CC_IN=1\r\nPATTERN_CHANGE_CHAIN_BEHAVIOR=0\r\nPATTERN_CHANGE_AUTO_SILENCE_TRACKS=0\r\nPATTERN_CHANGE_AUTO_TRIG_LFOS=0\r\nLOAD_24BIT_FLEX=0\r\nDYNAMIC_RECORDERS=0\r\nRECORD_24BIT=0\r\nRESERVED_RECORDER_COUNT=8\r\nRESERVED_RECORDER_LENGTH=16\r\nINPUT_DELAY_COMPENSATION=0\r\nGATE_AB=127\r\nGATE_CD=127\r\nGAIN_AB=64\r\nGAIN_CD=64\r\nDIR_AB=0\r\nDIR_CD=0\r\nPHONES_MIX=64\r\nMAIN_TO_CUE=0\r\nMASTER_TRACK=0\r\nCUE_STUDIO_MODE=0\r\nMAIN_LEVEL=64\r\nCUE_LEVEL=64\r\nMETRONOME_TIME_SIGNATURE=3\r\nMETRONOME_TIME_SIGNATURE_DENOMINATOR=2\r\nMETRONOME_PREROLL=0\r\nMETRONOME_CUE_VOLUME=32\r\nMETRONOME_MAIN_VOLUME=0\r\nMETRONOME_PITCH=12\r\nMETRONOME_TONAL=1\r\nMETRONOME_ENABLED=0\r\nTRIG_MODE_MIDI=0\r\nTRIG_MODE_MIDI=0\r\nTRIG_MODE_MIDI=0\r\nTRIG_MODE_MIDI=0\r\nTRIG_MODE_MIDI=0\r\nTRIG_MODE_MIDI=0\r\nTRIG_MODE_MIDI=0\r\nTRIG_MODE_MIDI=0\r\n[/SETTINGS]\r\n\r\n############################\r\n# Project States\r\n############################\r\n\r\n[STATES]\r\nBANK=0\r\nPATTERN=0\r\nARRANGEMENT=0\r\nARRANGEMENT_MODE=0\r\nPART=0\r\nTRACK=0\r\nTRACK_OTHERMODE=0\r\nSCENE_A_MUTE=0\r\nSCENE_B_MUTE=0\r\nTRACK_CUE_MASK=0\r\nTRACK_MUTE_MASK=0\r\nTRACK_SOLO_MASK=0\r\nMIDI_TRACK_MUTE_MASK=0\r\nMIDI_TRACK_SOLO_MASK=0\r\nMIDI_MODE=0\r\n[/STATES]\r\n\r\n############################\r\n# Samples\r\n############################\r\n\r\n[SAMPLE]\r\nTYPE=FLEX\r\nSLOT=129\r\nPATH=\r\nBPMx24=2880\r\nTSMODE=2\r\nLOOPMODE=0\r\nGAIN=72\r\nTRIGQUANTIZATION=255\r\n[/SAMPLE]\r\n\r\n[SAMPLE]\r\nTYPE=FLEX\r\nSLOT=130\r\nPATH=\r\nBPMx24=2880\r\nTSMODE=2\r\nLOOPMODE=0\r\nGAIN=72\r\nTRIGQUANTIZATION=255\r\n[/SAMPLE]\r\n\r\n[SAMPLE]\r\nTYPE=FLEX\r\nSLOT=131\r\nPATH=\r\nBPMx24=2880\r\nTSMODE=2\r\nLOOPMODE=0\r\nGAIN=72\r\nTRIGQUANTIZATION=255\r\n[/SAMPLE]\r\n\r\n[SAMPLE]\r\nTYPE=FLEX\r\nSLOT=132\r\nPATH=\r\nBPMx24=2880\r\nTSMODE=2\r\nLOOPMODE=0\r\nGAIN=72\r\nTRIGQUANTIZATION=255\r\n[/SAMPLE]\r\n\r\n[SAMPLE]\r\nTYPE=FLEX\r\nSLOT=133\r\nPATH=\r\nBPMx24=2880\r\nTSMODE=2\r\nLOOPMODE=0\r\nGAIN=72\r\nTRIGQUANTIZATION=255\r\n[/SAMPLE]\r\n\r\n[SAMPLE]\r\nTYPE=FLEX\r\nSLOT=134\r\nPATH=\r\nBPMx24=2880\r\nTSMODE=2\r\nLOOPMODE=0\r\nGAIN=72\r\nTRIGQUANTIZATION=255\r\n[/SAMPLE]\r\n\r\n[SAMPLE]\r\nTYPE=FLEX\r\nSLOT=135\r\nPATH=\r\nBPMx24=2880\r\nTSMODE=2\r\nLOOPMODE=0\r\nGAIN=72\r\nTRIGQUANTIZATION=255\r\n[/SAMPLE]\r\n\r\n[SAMPLE]\r\nTYPE=FLEX\r\nSLOT=136\r\nPATH=\r\nBPMx24=2880\r\nTSMODE=2\r\nLOOPMODE=0\r\nGAIN=72\r\nTRIGQUANTIZATION=255\r\n[/SAMPLE]\r\n\r\n############################\r\n\r\n";
488 const DEFAULT_STR_META: &str = "[META]\r\nTYPE=OCTATRACK DPS-1 PROJECT\r\nVERSION=19\r\nOS_VERSION=R0177 1.40B\r\n[/META]";
489 const DEFAULT_STR_STATE: &str = "[STATES]\r\nBANK=0\r\nPATTERN=0\r\nARRANGEMENT=0\r\nARRANGEMENT_MODE=0\r\nPART=0\r\nTRACK=0\r\nTRACK_OTHERMODE=0\r\nSCENE_A_MUTE=0\r\nSCENE_B_MUTE=0\r\nTRACK_CUE_MASK=0\r\nTRACK_MUTE_MASK=0\r\nTRACK_SOLO_MASK=0\r\nMIDI_TRACK_MUTE_MASK=0\r\nMIDI_TRACK_SOLO_MASK=0\r\nMIDI_MODE=0\r\n[/STATES]";
490 const DEFAULT_STR_SETTINGS: &str = "[SETTINGS]\r\nWRITEPROTECTED=0\r\nTEMPOx24=2880\r\nPATTERN_TEMPO_ENABLED=0\r\nMIDI_CLOCK_SEND=0\r\nMIDI_CLOCK_RECEIVE=0\r\nMIDI_TRANSPORT_SEND=0\r\nMIDI_TRANSPORT_RECEIVE=0\r\nMIDI_PROGRAM_CHANGE_SEND=0\r\nMIDI_PROGRAM_CHANGE_SEND_CH=-1\r\nMIDI_PROGRAM_CHANGE_RECEIVE=0\r\nMIDI_PROGRAM_CHANGE_RECEIVE_CH=-1\r\nMIDI_TRIG_CH1=0\r\nMIDI_TRIG_CH2=1\r\nMIDI_TRIG_CH3=2\r\nMIDI_TRIG_CH4=3\r\nMIDI_TRIG_CH5=4\r\nMIDI_TRIG_CH6=5\r\nMIDI_TRIG_CH7=6\r\nMIDI_TRIG_CH8=7\r\nMIDI_AUTO_CHANNEL=10\r\nMIDI_SOFT_THRU=0\r\nMIDI_AUDIO_TRK_CC_IN=1\r\nMIDI_AUDIO_TRK_CC_OUT=3\r\nMIDI_AUDIO_TRK_NOTE_IN=1\r\nMIDI_AUDIO_TRK_NOTE_OUT=3\r\nMIDI_MIDI_TRK_CC_IN=1\r\nPATTERN_CHANGE_CHAIN_BEHAVIOR=0\r\nPATTERN_CHANGE_AUTO_SILENCE_TRACKS=0\r\nPATTERN_CHANGE_AUTO_TRIG_LFOS=0\r\nLOAD_24BIT_FLEX=0\r\nDYNAMIC_RECORDERS=0\r\nRECORD_24BIT=0\r\nRESERVED_RECORDER_COUNT=8\r\nRESERVED_RECORDER_LENGTH=16\r\nINPUT_DELAY_COMPENSATION=0\r\nGATE_AB=127\r\nGATE_CD=127\r\nGAIN_AB=64\r\nGAIN_CD=64\r\nDIR_AB=0\r\nDIR_CD=0\r\nPHONES_MIX=64\r\nMAIN_TO_CUE=0\r\nMASTER_TRACK=0\r\nCUE_STUDIO_MODE=0\r\nMAIN_LEVEL=64\r\nCUE_LEVEL=64\r\nMETRONOME_TIME_SIGNATURE=3\r\nMETRONOME_TIME_SIGNATURE_DENOMINATOR=2\r\nMETRONOME_PREROLL=0\r\nMETRONOME_CUE_VOLUME=32\r\nMETRONOME_MAIN_VOLUME=0\r\nMETRONOME_PITCH=12\r\nMETRONOME_TONAL=1\r\nMETRONOME_ENABLED=0\r\nTRIG_MODE_MIDI=0\r\nTRIG_MODE_MIDI=0\r\nTRIG_MODE_MIDI=0\r\nTRIG_MODE_MIDI=0\r\nTRIG_MODE_MIDI=0\r\nTRIG_MODE_MIDI=0\r\nTRIG_MODE_MIDI=0\r\nTRIG_MODE_MIDI=0\r\n[/SETTINGS]";
491 const DEFAULT_STR_SLOTS: &str = "[SAMPLE]\r\nTYPE=FLEX\r\nSLOT=129\r\nPATH=\r\nBPMx24=2880\r\nTSMODE=2\r\nLOOPMODE=0\r\nGAIN=72\r\nTRIGQUANTIZATION=255\r\n[/SAMPLE]\r\n\r\n[SAMPLE]\r\nTYPE=FLEX\r\nSLOT=130\r\nPATH=\r\nBPMx24=2880\r\nTSMODE=2\r\nLOOPMODE=0\r\nGAIN=72\r\nTRIGQUANTIZATION=255\r\n[/SAMPLE]\r\n\r\n[SAMPLE]\r\nTYPE=FLEX\r\nSLOT=131\r\nPATH=\r\nBPMx24=2880\r\nTSMODE=2\r\nLOOPMODE=0\r\nGAIN=72\r\nTRIGQUANTIZATION=255\r\n[/SAMPLE]\r\n\r\n[SAMPLE]\r\nTYPE=FLEX\r\nSLOT=132\r\nPATH=\r\nBPMx24=2880\r\nTSMODE=2\r\nLOOPMODE=0\r\nGAIN=72\r\nTRIGQUANTIZATION=255\r\n[/SAMPLE]\r\n\r\n[SAMPLE]\r\nTYPE=FLEX\r\nSLOT=133\r\nPATH=\r\nBPMx24=2880\r\nTSMODE=2\r\nLOOPMODE=0\r\nGAIN=72\r\nTRIGQUANTIZATION=255\r\n[/SAMPLE]\r\n\r\n[SAMPLE]\r\nTYPE=FLEX\r\nSLOT=134\r\nPATH=\r\nBPMx24=2880\r\nTSMODE=2\r\nLOOPMODE=0\r\nGAIN=72\r\nTRIGQUANTIZATION=255\r\n[/SAMPLE]\r\n\r\n[SAMPLE]\r\nTYPE=FLEX\r\nSLOT=135\r\nPATH=\r\nBPMx24=2880\r\nTSMODE=2\r\nLOOPMODE=0\r\nGAIN=72\r\nTRIGQUANTIZATION=255\r\n[/SAMPLE]\r\n\r\n[SAMPLE]\r\nTYPE=FLEX\r\nSLOT=136\r\nPATH=\r\nBPMx24=2880\r\nTSMODE=2\r\nLOOPMODE=0\r\nGAIN=72\r\nTRIGQUANTIZATION=255\r\n[/SAMPLE]";
492
493 mod test_to_string_display {
496 use super::*;
497
498 #[test]
499 fn test_full_to_string() {
500 assert_eq!(ProjectFile::default().to_string(), DEFAULT_STR_FILE);
501 assert_eq!(format!["{:#}", ProjectFile::default()], DEFAULT_STR_FILE);
502 }
503
504 #[test]
505 fn test_metadata_to_string() {
506 assert_eq!(OsMetadata::default().to_string(), DEFAULT_STR_META);
507 assert_eq!(format!["{:#}", OsMetadata::default()], DEFAULT_STR_META);
508 }
509
510 #[test]
511 fn test_states_to_string() {
512 assert_eq!(State::default().to_string(), DEFAULT_STR_STATE);
513 assert_eq!(format!["{:#}", State::default()], DEFAULT_STR_STATE);
514 }
515
516 #[test]
517 fn test_settings_to_string() {
518 assert_eq!(Settings::default().to_string(), DEFAULT_STR_SETTINGS);
519 assert_eq!(format!["{:#}", Settings::default()], DEFAULT_STR_SETTINGS);
520 }
521
522 #[test]
523 fn test_sample_slots_to_string() {
524 assert_eq!(SlotsAttributes::default().to_string(), DEFAULT_STR_SLOTS);
525 assert_eq!(
526 format!["{:#}", SlotsAttributes::default()],
527 DEFAULT_STR_SLOTS
528 );
529 }
530 }
531
532 mod test_to_string_debug {
534 use super::*;
535
536 #[test]
537 fn test_full_to_string() {
538 assert_ne!(
539 format!["{:#?}", ProjectFile::default()],
540 DEFAULT_STR_FILE,
541 "debug formatting should not be 'file' representation",
542 );
543 }
544
545 #[test]
546 fn test_metadata_to_string() {
547 assert_ne!(
548 format!["{:#?}", OsMetadata::default().to_string()],
549 DEFAULT_STR_META,
550 "debug formatting should not be 'file' representation",
551 );
552 }
553
554 #[test]
555 fn test_states_to_string() {
556 assert_ne!(
557 format!["{:#?}", State::default().to_string()],
558 DEFAULT_STR_STATE,
559 "debug formatting should not be 'file' representation",
560 );
561 }
562
563 #[test]
564 fn test_settings_to_string() {
565 assert_ne!(
566 format!["{:#?}", Settings::default().to_string()],
567 DEFAULT_STR_SETTINGS,
568 "debug formatting should not be 'file' representation",
569 );
570 }
571
572 #[test]
573 fn test_sample_slots_to_string() {
574 assert_ne!(
575 format!["{:#?}", SlotsAttributes::default().to_string()],
576 DEFAULT_STR_SLOTS,
577 "debug formatting should not be 'file' representation",
578 );
579 }
580 }
581}