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 Default for ProjectFile {
382 fn default() -> Self {
383 let metadata = OsMetadata::default();
384 let states = State::default();
385 let settings = Settings::default();
386 let slots = SlotsAttributes::default();
387
388 Self {
389 metadata,
390 settings,
391 states,
392 slots,
393 }
394 }
395}
396
397impl std::fmt::Display for ProjectFile {
398 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
399 let states_header =
400 "############################\r\n# Project States\r\n############################"
401 .to_string();
402 let settings_header =
403 "############################\r\n# Project Settings\r\n############################"
404 .to_string();
405 let slots_header =
406 "############################\r\n# Samples\r\n############################".to_string();
407 let footer = "############################".to_string();
408
409 let metadata_string: String = self.metadata.to_string();
410 let states_string: String = self.states.to_string();
411 let settings_string: String = self.settings.to_string();
412
413 let sample_slots_string = self.slots.to_string();
414
415 let v: Vec<String> = vec![
416 settings_header,
419 metadata_string,
420 settings_string,
421 states_header,
422 states_string,
423 slots_header,
424 sample_slots_string,
425 footer,
426 ];
427 let mut project_string = v.join("\r\n\r\n");
428 project_string.push_str("\r\n\r\n");
429
430 write!(f, "{project_string}")
431 }
432}
433
434impl OctatrackFileIO for ProjectFile {
435 fn encode(&self) -> Result<Vec<u8>, OtToolsIoError> {
437 let data = self.to_string();
438 let bytes: Vec<u8> = data.bytes().collect::<Vec<u8>>();
439 Ok(bytes)
440 }
441
442 fn decode(bytes: &[u8]) -> Result<Self, OtToolsIoError> {
445 let s = std::str::from_utf8(bytes)?.to_string();
446
447 let metadata = OsMetadata::from_str(&s)?;
448 let states = State::from_str(&s)?;
449 let settings = Settings::from_str(&s)?;
450 let slots = SlotsAttributes::from_str(&s)?;
451
452 Ok(Self {
453 metadata,
454 settings,
455 states,
456 slots,
457 })
458 }
459}
460
461impl HasChecksumField for ProjectFile {
462 fn calculate_checksum(&self) -> Result<u16, OtToolsIoError> {
463 Err(ProjectError::NotChecksummable.into())
464 }
465 fn check_checksum(&self) -> Result<bool, OtToolsIoError> {
466 Err(ProjectError::NotChecksummable.into())
467 }
468}
469
470#[cfg(test)]
471mod checksum_field {
472 use crate::test_utils::get_blank_proj_dirpath;
473 use crate::{HasChecksumField, OctatrackFileIO, OtToolsIoError, ProjectFile};
474
475 #[test]
476 fn fail_calculate_checksum() -> Result<(), OtToolsIoError> {
477 let infile = get_blank_proj_dirpath().join("project.work");
478 let p = ProjectFile::from_data_file(&infile)?;
479 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)");
480
481 Ok(())
482 }
483 #[test]
484 fn fail_check_checksum() -> Result<(), OtToolsIoError> {
485 let infile = get_blank_proj_dirpath().join("project.work");
486 let p = ProjectFile::from_data_file(&infile)?;
487 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)");
488
489 Ok(())
490 }
491}
492
493impl HasHeaderField for ProjectFile {
494 fn check_header(&self) -> Result<bool, OtToolsIoError> {
495 Err(ProjectError::NoHeaderField.into())
496 }
497}
498
499#[cfg(test)]
500mod header_field {
501 use crate::test_utils::get_blank_proj_dirpath;
502 use crate::{HasHeaderField, OctatrackFileIO, OtToolsIoError, ProjectFile};
503
504 #[test]
505 fn fail_check_header() -> Result<(), OtToolsIoError> {
506 let infile = get_blank_proj_dirpath().join("project.work");
507 let p = ProjectFile::from_data_file(&infile)?;
508 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)");
509
510 Ok(())
511 }
512}
513
514impl HasFileVersionField for ProjectFile {
515 fn check_file_version(&self) -> Result<bool, OtToolsIoError> {
516 Err(ProjectError::NoFilePatchVersionField.into())
517 }
518}
519
520#[cfg(test)]
521mod file_version_field {
522 use crate::test_utils::get_blank_proj_dirpath;
523 use crate::{HasFileVersionField, OctatrackFileIO, OtToolsIoError, ProjectFile};
524
525 #[test]
526 fn fail_check_version() -> Result<(), OtToolsIoError> {
527 let infile = get_blank_proj_dirpath().join("project.work");
528 let p = ProjectFile::from_data_file(&infile)?;
529 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)");
530
531 Ok(())
532 }
533}
534
535#[cfg(test)]
536#[allow(unused_imports)]
537mod tests {
538 use super::*;
539
540 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";
541 const DEFAULT_STR_META: &str = "[META]\r\nTYPE=OCTATRACK DPS-1 PROJECT\r\nVERSION=19\r\nOS_VERSION=R0177 1.40B\r\n[/META]";
542 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]";
543 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]";
544 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]";
545
546 mod test_to_string_display {
549 use super::*;
550
551 #[test]
552 fn test_full_to_string() {
553 assert_eq!(ProjectFile::default().to_string(), DEFAULT_STR_FILE);
554 assert_eq!(format!["{:#}", ProjectFile::default()], DEFAULT_STR_FILE);
555 }
556
557 #[test]
558 fn test_metadata_to_string() {
559 assert_eq!(OsMetadata::default().to_string(), DEFAULT_STR_META);
560 assert_eq!(format!["{:#}", OsMetadata::default()], DEFAULT_STR_META);
561 }
562
563 #[test]
564 fn test_states_to_string() {
565 assert_eq!(State::default().to_string(), DEFAULT_STR_STATE);
566 assert_eq!(format!["{:#}", State::default()], DEFAULT_STR_STATE);
567 }
568
569 #[test]
570 fn test_settings_to_string() {
571 assert_eq!(Settings::default().to_string(), DEFAULT_STR_SETTINGS);
572 assert_eq!(format!["{:#}", Settings::default()], DEFAULT_STR_SETTINGS);
573 }
574
575 #[test]
576 fn test_sample_slots_to_string() {
577 assert_eq!(SlotsAttributes::default().to_string(), DEFAULT_STR_SLOTS);
578 assert_eq!(
579 format!["{:#}", SlotsAttributes::default()],
580 DEFAULT_STR_SLOTS
581 );
582 }
583 }
584
585 mod test_to_string_debug {
587 use super::*;
588
589 #[test]
590 fn test_full_to_string() {
591 assert_ne!(
592 format!["{:#?}", ProjectFile::default()],
593 DEFAULT_STR_FILE,
594 "debug formatting should not be 'file' representation",
595 );
596 }
597
598 #[test]
599 fn test_metadata_to_string() {
600 assert_ne!(
601 format!["{:#?}", OsMetadata::default().to_string()],
602 DEFAULT_STR_META,
603 "debug formatting should not be 'file' representation",
604 );
605 }
606
607 #[test]
608 fn test_states_to_string() {
609 assert_ne!(
610 format!["{:#?}", State::default().to_string()],
611 DEFAULT_STR_STATE,
612 "debug formatting should not be 'file' representation",
613 );
614 }
615
616 #[test]
617 fn test_settings_to_string() {
618 assert_ne!(
619 format!["{:#?}", Settings::default().to_string()],
620 DEFAULT_STR_SETTINGS,
621 "debug formatting should not be 'file' representation",
622 );
623 }
624
625 #[test]
626 fn test_sample_slots_to_string() {
627 assert_ne!(
628 format!["{:#?}", SlotsAttributes::default().to_string()],
629 DEFAULT_STR_SLOTS,
630 "debug formatting should not be 'file' representation",
631 );
632 }
633 }
634}