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("missing field: {key} (likely incompatible OS version)")]
43 MissingField { key: String },
44 #[error("failed to parse footer data")]
45 Footer,
46 #[error("infallible")]
47 Infallible(#[from] std::convert::Infallible),
48}
49
50#[derive(Debug, Error)]
52pub enum ProjectError {
53 #[error("type cannot be checksummed")]
55 NotChecksummable,
56 #[error("type does not have a file patch version field")]
58 NoFilePatchVersionField,
59 #[error("type does not have a header field")]
61 NoHeaderField,
62}
63
64pub const ALLOWED_OS_VERSIONS: [&str; 3] = ["1.40A", "1.40B", "1.40C"];
66
67fn parse_hashmap_string_value<T: FromStr>(
71 hmap: &HashMap<String, String>,
72 key: &str,
73 default_str: Option<&str>,
74) -> Result<T, ProjectParseError>
75where
76 <T as FromStr>::Err: Debug,
77 ProjectParseError: From<<T as FromStr>::Err>,
78{
79 let val = match default_str {
80 Some(x) => Ok(hmap.get(key).cloned().unwrap_or(x.to_string())),
81 None => hmap
82 .get(key)
83 .cloned()
84 .ok_or(ProjectParseError::MissingField {
85 key: key.to_string().to_uppercase(),
86 }),
87 }?;
88
89 let parsed = val.parse::<T>()?;
90 Ok(parsed)
91}
92
93fn parse_hashmap_string_value_bool(
96 hmap: &HashMap<String, String>,
97 key: &str,
98 default_str: Option<&str>,
99) -> Result<bool, ProjectParseError> {
100 let val = parse_hashmap_string_value::<u8>(hmap, key, default_str)?;
101 Ok(matches!(val, 1))
102}
103
104fn string_to_hashmap(
106 data: &str,
107 section: &SectionHeader,
108) -> Result<HashMap<String, String>, ProjectParseError> {
109 let start = format!("[{section}]");
110 let end = format!("[/{section}]");
111
112 let start_idx = data.find(&start).ok_or(ProjectParseError::HashMap)?;
113 let start_idx_shifted: usize = start_idx + start.len();
114 let end_idx = data.find(&end).ok_or(ProjectParseError::HashMap)?;
115
116 let section: String = data[start_idx_shifted..end_idx].to_string();
117
118 let mut hmap: HashMap<String, String> = HashMap::new();
119 let mut trig_mode_midi_field_idx = 1;
120
121 for split_s in section.split("\r\n") {
122 if !split_s.is_empty() {
125 let key_pair_string = split_s.to_string();
126 let mut key_pair_split: Vec<&str> = key_pair_string.split('=').collect();
127
128 let key_renamed: String = format!("trig_mode_midi_track_{}", &trig_mode_midi_field_idx);
133 if key_pair_split[0] == "TRIG_MODE_MIDI" {
134 key_pair_split[0] = key_renamed.as_str();
135 trig_mode_midi_field_idx += 1;
136 }
137
138 hmap.insert(
139 key_pair_split[0].to_string().to_ascii_lowercase(),
140 key_pair_split[1].to_string(),
141 );
142 }
143 }
144
145 Ok(hmap)
146}
147
148#[derive(Debug, Error)]
149#[error("invalid project section header value")]
150struct InvalidSectionHeaderValue;
151
152#[derive(Debug, PartialEq)]
154enum SectionHeader {
155 Meta,
156 States,
157 Settings,
158 Samples,
159}
160
161impl TryFrom<&str> for SectionHeader {
162 type Error = InvalidSectionHeaderValue;
163 fn try_from(value: &str) -> Result<Self, Self::Error> {
164 match value.to_ascii_uppercase().as_str() {
165 "META" => Ok(Self::Meta),
166 "STATES" => Ok(Self::States),
167 "SETTINGS" => Ok(Self::Settings),
168 "SAMPLES" => Ok(Self::Samples),
169 _ => Err(InvalidSectionHeaderValue),
170 }
171 }
172}
173
174impl TryFrom<String> for SectionHeader {
175 type Error = InvalidSectionHeaderValue;
176 fn try_from(value: String) -> Result<Self, Self::Error> {
177 value.as_str().try_into()
178 }
179}
180
181impl Display for SectionHeader {
182 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183 let str = match self {
184 Self::Meta => "META".to_string(),
185 Self::States => "STATES".to_string(),
186 Self::Settings => "SETTINGS".to_string(),
187 Self::Samples => "SAMPLES".to_string(),
188 };
189 write!(f, "{str}")
190 }
191}
192
193#[cfg(test)]
194mod test_project_section {
195
196 mod try_from_str {
197 use crate::projects::{InvalidSectionHeaderValue, SectionHeader};
198
199 #[test]
200 fn no_match_is_err() {
201 assert_eq!(
202 SectionHeader::try_from("skfsdkfjskdh")
203 .unwrap_err()
204 .to_string(),
205 InvalidSectionHeaderValue.to_string()
206 )
207 }
208
209 #[test]
210 fn uppercase_meta() {
211 assert_eq!(
212 SectionHeader::try_from("META").unwrap(),
213 SectionHeader::Meta,
214 )
215 }
216
217 #[test]
218 fn uppercase_states() {
219 assert_eq!(
220 SectionHeader::try_from("STATES").unwrap(),
221 SectionHeader::States,
222 )
223 }
224
225 #[test]
226 fn uppercase_settings() {
227 assert_eq!(
228 SectionHeader::try_from("SETTINGS").unwrap(),
229 SectionHeader::Settings,
230 )
231 }
232
233 #[test]
234 fn uppercase_samples() {
235 assert_eq!(
236 SectionHeader::try_from("SAMPLES").unwrap(),
237 SectionHeader::Samples,
238 )
239 }
240
241 #[test]
242 fn lowercase_meta() {
243 assert_eq!(
244 SectionHeader::try_from("meta").unwrap(),
245 SectionHeader::Meta,
246 )
247 }
248
249 #[test]
250 fn lowercase_states() {
251 assert_eq!(
252 SectionHeader::try_from("states").unwrap(),
253 SectionHeader::States,
254 )
255 }
256
257 #[test]
258 fn lowercase_settings() {
259 assert_eq!(
260 SectionHeader::try_from("settings").unwrap(),
261 SectionHeader::Settings,
262 )
263 }
264
265 #[test]
266 fn lowercase_samples() {
267 assert_eq!(
268 SectionHeader::try_from("samples").unwrap(),
269 SectionHeader::Samples,
270 )
271 }
272 }
273
274 mod try_from_string {
275 use crate::projects::SectionHeader;
276
277 #[test]
278 fn no_match_is_err() {
279 assert!(SectionHeader::try_from("skfsdkfjskdh".to_string()).is_err())
280 }
281
282 #[test]
283 fn uppercase_meta() {
284 assert_eq!(
285 SectionHeader::try_from("META".to_string()).unwrap(),
286 SectionHeader::Meta,
287 )
288 }
289
290 #[test]
291 fn uppercase_states() {
292 assert_eq!(
293 SectionHeader::try_from("STATES".to_string()).unwrap(),
294 SectionHeader::States,
295 )
296 }
297
298 #[test]
299 fn uppercase_settings() {
300 assert_eq!(
301 SectionHeader::try_from("SETTINGS".to_string()).unwrap(),
302 SectionHeader::Settings,
303 )
304 }
305
306 #[test]
307 fn uppercase_samples() {
308 assert_eq!(
309 SectionHeader::try_from("SAMPLES".to_string()).unwrap(),
310 SectionHeader::Samples,
311 )
312 }
313
314 #[test]
315 fn lowercase_meta() {
316 assert_eq!(
317 SectionHeader::try_from("meta".to_string()).unwrap(),
318 SectionHeader::Meta,
319 )
320 }
321
322 #[test]
323 fn lowercase_states() {
324 assert_eq!(
325 SectionHeader::try_from("states".to_string()).unwrap(),
326 SectionHeader::States,
327 )
328 }
329
330 #[test]
331 fn lowercase_settings() {
332 assert_eq!(
333 SectionHeader::try_from("settings".to_string()).unwrap(),
334 SectionHeader::Settings,
335 )
336 }
337 #[test]
338 fn lowercase_samples() {
339 assert_eq!(
340 SectionHeader::try_from("samples".to_string()).unwrap(),
341 SectionHeader::Samples,
342 )
343 }
344 }
345
346 mod to_string {
347 use crate::projects::SectionHeader;
348 #[test]
349 fn section_heading_value_meta() {
350 assert_eq!(SectionHeader::Meta.to_string(), "META".to_string())
351 }
352
353 #[test]
354 fn section_heading_value_states() {
355 assert_eq!(SectionHeader::States.to_string(), "STATES".to_string())
356 }
357
358 #[test]
359 fn section_heading_value_settings() {
360 assert_eq!(SectionHeader::Settings.to_string(), "SETTINGS".to_string())
361 }
362
363 #[test]
364 fn section_heading_value_samples() {
365 assert_eq!(SectionHeader::Samples.to_string(), "SAMPLES".to_string())
366 }
367 }
368}
369
370#[derive(IsDefaultCheck, Serialize, Deserialize, PartialEq, Debug, Clone)]
384pub struct ProjectFile {
385 pub metadata: OsMetadata,
387
388 pub settings: Settings,
390
391 pub states: State,
393
394 pub slots: SlotsAttributes,
396}
397
398impl ProjectFile {
399 pub fn check_compatible_os_version(&self) -> Result<bool, OtToolsIoError> {
414 Ok(crate::ALLOWED_OS_VERSIONS
415 .iter()
416 .any(|x| self.metadata.os_version.ends_with(x)))
417 }
418}
419
420#[cfg(test)]
421mod check_os_version {
422
423 use crate::test_utils::get_blank_proj_dirpath;
424 use crate::OctatrackFileIO;
425 use crate::OtToolsIoError;
426 use crate::ProjectFile;
427
428 #[test]
429 fn valid() -> Result<(), OtToolsIoError> {
430 let path = get_blank_proj_dirpath().join("project.work");
431 let proj = ProjectFile::from_data_file(&path)?;
432 assert!(proj.check_compatible_os_version()?);
433 Ok(())
434 }
435
436 #[test]
437 fn invalid_random() -> Result<(), OtToolsIoError> {
438 let path = get_blank_proj_dirpath().join("project.work");
439 let mut proj = ProjectFile::from_data_file(&path)?;
440 proj.metadata.os_version = "sakjhasjfh".to_string();
441 assert!(!proj.check_compatible_os_version()?);
442 Ok(())
443 }
444
445 #[test]
446 fn invalid_old() -> Result<(), OtToolsIoError> {
447 let path = get_blank_proj_dirpath().join("project.work");
448 let mut proj = ProjectFile::from_data_file(&path)?;
449 proj.metadata.os_version = "1.39D".to_string();
450 assert!(!proj.check_compatible_os_version()?);
451 Ok(())
452 }
453
454 #[test]
455 fn invalid_new() -> Result<(), OtToolsIoError> {
456 let path = get_blank_proj_dirpath().join("project.work");
457 let mut proj = ProjectFile::from_data_file(&path)?;
458 proj.metadata.os_version = "1.41A".to_string();
459 assert!(!proj.check_compatible_os_version()?);
460 Ok(())
461 }
462}
463
464impl Default for ProjectFile {
465 fn default() -> Self {
466 let metadata = OsMetadata::default();
467 let states = State::default();
468 let settings = Settings::default();
469 let slots = SlotsAttributes::default();
470
471 Self {
472 metadata,
473 settings,
474 states,
475 slots,
476 }
477 }
478}
479
480impl std::fmt::Display for ProjectFile {
481 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
482 let states_header =
483 "############################\r\n# Project States\r\n############################"
484 .to_string();
485 let settings_header =
486 "############################\r\n# Project Settings\r\n############################"
487 .to_string();
488 let slots_header =
489 "############################\r\n# Samples\r\n############################".to_string();
490 let footer = "############################".to_string();
491
492 let metadata_string: String = self.metadata.to_string();
493 let states_string: String = self.states.to_string();
494 let settings_string: String = self.settings.to_string();
495
496 let sample_slots_string = self.slots.to_string();
497
498 let v: Vec<String> = vec![
499 settings_header,
502 metadata_string,
503 settings_string,
504 states_header,
505 states_string,
506 slots_header,
507 sample_slots_string,
508 footer,
509 ];
510 let mut project_string = v.join("\r\n\r\n");
511 project_string.push_str("\r\n\r\n");
512
513 write!(f, "{project_string}")
514 }
515}
516
517impl OctatrackFileIO for ProjectFile {
518 fn encode(&self) -> Result<Vec<u8>, OtToolsIoError> {
520 let s = self.to_string();
521 let (cow, _, _) = encoding_rs::WINDOWS_1258.encode(&s);
522 Ok(cow.to_vec())
523 }
524
525 fn decode(bytes: &[u8]) -> Result<Self, OtToolsIoError> {
528 let (cow, _, _) = encoding_rs::WINDOWS_1258.decode(bytes);
529 let s = cow.into_owned();
530
531 let metadata = OsMetadata::from_str(&s)?;
532 let states = State::from_str(&s)?;
533 let settings = Settings::from_str(&s)?;
534 let slots = SlotsAttributes::from_str(&s)?;
535
536 Ok(Self {
537 metadata,
538 settings,
539 states,
540 slots,
541 })
542 }
543}
544
545impl HasChecksumField for ProjectFile {
546 fn calculate_checksum(&self) -> Result<u16, OtToolsIoError> {
547 Err(ProjectError::NotChecksummable.into())
548 }
549 fn check_checksum(&self) -> Result<bool, OtToolsIoError> {
550 Err(ProjectError::NotChecksummable.into())
551 }
552}
553
554#[cfg(test)]
555mod checksum_field {
556 use crate::test_utils::get_blank_proj_dirpath;
557 use crate::{HasChecksumField, OctatrackFileIO, OtToolsIoError, ProjectFile};
558
559 #[test]
560 fn fail_calculate_checksum() -> Result<(), OtToolsIoError> {
561 let infile = get_blank_proj_dirpath().join("project.work");
562 let p = ProjectFile::from_data_file(&infile)?;
563 assert_eq!(p.calculate_checksum().unwrap_err().to_string(), "project files cannot be checked for integrity: type cannot be checksummed (ot_tools_io::projects::ProjectError)");
564
565 Ok(())
566 }
567 #[test]
568 fn fail_check_checksum() -> Result<(), OtToolsIoError> {
569 let infile = get_blank_proj_dirpath().join("project.work");
570 let p = ProjectFile::from_data_file(&infile)?;
571 assert_eq!(p.check_checksum().unwrap_err().to_string(), "project files cannot be checked for integrity: type cannot be checksummed (ot_tools_io::projects::ProjectError)");
572
573 Ok(())
574 }
575}
576
577impl HasHeaderField for ProjectFile {
578 fn check_header(&self) -> Result<bool, OtToolsIoError> {
579 Err(ProjectError::NoHeaderField.into())
580 }
581}
582
583#[cfg(test)]
584mod header_field {
585 use crate::test_utils::get_blank_proj_dirpath;
586 use crate::{HasHeaderField, OctatrackFileIO, OtToolsIoError, ProjectFile};
587
588 #[test]
589 fn fail_check_header() -> Result<(), OtToolsIoError> {
590 let infile = get_blank_proj_dirpath().join("project.work");
591 let p = ProjectFile::from_data_file(&infile)?;
592 assert_eq!(p.check_header().unwrap_err().to_string(), "project files cannot be checked for integrity: type does not have a header field (ot_tools_io::projects::ProjectError)");
593
594 Ok(())
595 }
596}
597
598impl HasFileVersionField for ProjectFile {
599 fn check_file_version(&self) -> Result<bool, OtToolsIoError> {
600 Err(ProjectError::NoFilePatchVersionField.into())
601 }
602}
603
604#[cfg(test)]
605mod file_version_field {
606 use crate::test_utils::get_blank_proj_dirpath;
607 use crate::{HasFileVersionField, OctatrackFileIO, OtToolsIoError, ProjectFile};
608
609 #[test]
610 fn fail_check_version() -> Result<(), OtToolsIoError> {
611 let infile = get_blank_proj_dirpath().join("project.work");
612 let p = ProjectFile::from_data_file(&infile)?;
613 assert_eq!(p.check_file_version().unwrap_err().to_string(), "project files cannot be checked for integrity: type does not have a file patch version field (ot_tools_io::projects::ProjectError)");
614
615 Ok(())
616 }
617}
618
619#[cfg(test)]
620#[allow(unused_imports)]
621mod tests {
622 use super::*;
623
624 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";
625 const DEFAULT_STR_META: &str = "[META]\r\nTYPE=OCTATRACK DPS-1 PROJECT\r\nVERSION=19\r\nOS_VERSION=R0177 1.40B\r\n[/META]";
626 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]";
627 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]";
628 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]";
629
630 mod test_to_string_display {
633 use super::*;
634
635 #[test]
636 fn test_full_to_string() {
637 assert_eq!(ProjectFile::default().to_string(), DEFAULT_STR_FILE);
638 assert_eq!(format!["{:#}", ProjectFile::default()], DEFAULT_STR_FILE);
639 }
640
641 #[test]
642 fn test_metadata_to_string() {
643 assert_eq!(OsMetadata::default().to_string(), DEFAULT_STR_META);
644 assert_eq!(format!["{:#}", OsMetadata::default()], DEFAULT_STR_META);
645 }
646
647 #[test]
648 fn test_states_to_string() {
649 assert_eq!(State::default().to_string(), DEFAULT_STR_STATE);
650 assert_eq!(format!["{:#}", State::default()], DEFAULT_STR_STATE);
651 }
652
653 #[test]
654 fn test_settings_to_string() {
655 assert_eq!(Settings::default().to_string(), DEFAULT_STR_SETTINGS);
656 assert_eq!(format!["{:#}", Settings::default()], DEFAULT_STR_SETTINGS);
657 }
658
659 #[test]
660 fn test_sample_slots_to_string() {
661 assert_eq!(SlotsAttributes::default().to_string(), DEFAULT_STR_SLOTS);
662 assert_eq!(
663 format!["{:#}", SlotsAttributes::default()],
664 DEFAULT_STR_SLOTS
665 );
666 }
667 }
668
669 mod test_to_string_debug {
671 use super::*;
672
673 #[test]
674 fn test_full_to_string() {
675 assert_ne!(
676 format!["{:#?}", ProjectFile::default()],
677 DEFAULT_STR_FILE,
678 "debug formatting should not be 'file' representation",
679 );
680 }
681
682 #[test]
683 fn test_metadata_to_string() {
684 assert_ne!(
685 format!["{:#?}", OsMetadata::default().to_string()],
686 DEFAULT_STR_META,
687 "debug formatting should not be 'file' representation",
688 );
689 }
690
691 #[test]
692 fn test_states_to_string() {
693 assert_ne!(
694 format!["{:#?}", State::default().to_string()],
695 DEFAULT_STR_STATE,
696 "debug formatting should not be 'file' representation",
697 );
698 }
699
700 #[test]
701 fn test_settings_to_string() {
702 assert_ne!(
703 format!["{:#?}", Settings::default().to_string()],
704 DEFAULT_STR_SETTINGS,
705 "debug formatting should not be 'file' representation",
706 );
707 }
708
709 #[test]
710 fn test_sample_slots_to_string() {
711 assert_ne!(
712 format!["{:#?}", SlotsAttributes::default().to_string()],
713 DEFAULT_STR_SLOTS,
714 "debug formatting should not be 'file' representation",
715 );
716 }
717 }
718}