1pub mod metadata;
9pub mod options;
10pub mod settings;
11pub mod slots;
12pub mod states;
13
14use crate::projects::{
15 metadata::ProjectMetadata, options::ProjectSampleSlotType, settings::ProjectSettings,
16 slots::ProjectSampleSlot, states::ProjectStates,
17};
18use crate::{Decode, Encode, IsDefault, OptionEnumValueConvert, OtToolsIoErrors, RBoxErr};
19use serde::{Deserialize, Serialize};
20use std::cmp::PartialEq;
21use std::{collections::HashMap, fmt::Debug, str::FromStr};
22
23trait FromHashMap {
25 type A;
27
28 type B;
30
31 type T;
33
34 fn from_hashmap(hmap: &HashMap<Self::A, Self::B>) -> RBoxErr<Self::T>;
36}
37
38trait ProjectFromString {
40 type T;
42
43 fn from_string(data: &str) -> RBoxErr<Self::T>;
45}
46
47trait ProjectToString {
50 fn to_string(&self) -> RBoxErr<String>;
52}
53
54fn parse_hashmap_string_value<T: FromStr>(
56 hmap: &HashMap<String, String>,
57 key: &str,
58 default_str: Option<&str>,
59) -> Result<T, <T as FromStr>::Err>
60where
61 <T as FromStr>::Err: Debug,
62{
63 match default_str {
64 Some(x) => hmap.get(key).unwrap_or(&x.to_string()).parse::<T>(),
65 None => hmap.get(key).unwrap().parse::<T>(),
66 }
67}
68
69fn parse_hashmap_string_value_bool(
72 hmap: &HashMap<String, String>,
73 key: &str,
74 default_str: Option<&str>,
75) -> RBoxErr<bool> {
76 Ok(matches!(
78 parse_hashmap_string_value::<u8>(hmap, key, default_str)?,
79 1
80 ))
81}
82
83fn string_to_hashmap(
85 data: &str,
86 section: &ProjectRawFileSection,
87) -> RBoxErr<HashMap<String, String>> {
88 let start_idx: usize = data.find(§ion.start_string()?).unwrap();
89 let start_idx_shifted: usize = start_idx + section.start_string()?.len();
90 let end_idx: usize = data.find(§ion.end_string()?).unwrap();
91
92 let section: String = data[start_idx_shifted..end_idx].to_string();
93
94 let mut hmap: HashMap<String, String> = HashMap::new();
95 let mut trig_mode_midi_field_idx = 1;
96
97 for split_s in section.split("\r\n") {
98 if !split_s.is_empty() {
101 let key_pair_string = split_s.to_string();
102 let mut key_pair_split: Vec<&str> = key_pair_string.split('=').collect();
103
104 let key_renamed: String = format!("trig_mode_midi_track_{}", &trig_mode_midi_field_idx);
109 if key_pair_split[0] == "TRIG_MODE_MIDI" {
110 key_pair_split[0] = key_renamed.as_str();
111 trig_mode_midi_field_idx += 1;
112 }
113
114 hmap.insert(
115 key_pair_split[0].to_string().to_ascii_lowercase(),
116 key_pair_split[1].to_string(),
117 );
118 }
119 }
120
121 Ok(hmap)
122}
123
124fn sslots_vec_to_string(v: &[ProjectSampleSlot]) -> String {
125 let sslots_mapped: Vec<String> = v.iter().map(|x| x.to_string().unwrap()).collect();
126
127 sslots_mapped.join("\r\n\r\n")
128}
129
130#[derive(Debug, PartialEq)]
132enum ProjectRawFileSection {
133 Meta,
134 States,
135 Settings,
136 Samples,
137}
138
139impl OptionEnumValueConvert for ProjectRawFileSection {
140 type T = ProjectRawFileSection;
141 type V = String;
142
143 fn from_value(v: &Self::V) -> RBoxErr<Self::T> {
144 match v.to_ascii_uppercase().as_str() {
145 "META" => Ok(Self::Meta),
146 "STATES" => Ok(Self::States),
147 "SETTINGS" => Ok(Self::Settings),
148 "SAMPLES" => Ok(Self::Samples),
149 _ => Err(OtToolsIoErrors::NoMatchingOptionEnumValue.into()),
150 }
151 }
152
153 fn value(&self) -> RBoxErr<Self::V> {
155 match self {
156 Self::Meta => Ok("META".to_string()),
157 Self::States => Ok("STATES".to_string()),
158 Self::Settings => Ok("SETTINGS".to_string()),
159 Self::Samples => Ok("SAMPLES".to_string()),
160 }
161 }
162}
163
164impl ProjectRawFileSection {
165 fn start_string(&self) -> RBoxErr<String> {
166 Ok(format!("[{}]", self.value()?))
167 }
168 fn end_string(&self) -> RBoxErr<String> {
169 Ok(format!("[/{}]", self.value()?))
170 }
171}
172
173#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
175pub struct ProjectFile {
176 pub metadata: ProjectMetadata,
178
179 pub settings: ProjectSettings,
181
182 pub states: ProjectStates,
184
185 pub slots: Vec<ProjectSampleSlot>,
187}
188
189impl ProjectFile {
190 pub fn update_sample_slot_id(
191 &mut self,
192 old_slot_id: &u8,
193 new_slot_id: &u8,
194 sample_type: Option<ProjectSampleSlotType>,
195 ) -> RBoxErr<()> {
196 use itertools::Itertools;
197 let type_filt = sample_type.unwrap_or(ProjectSampleSlotType::Static);
198
199 let sample_slot_find: Option<(usize, ProjectSampleSlot)> = self
200 .slots
201 .clone()
202 .into_iter()
203 .find_position(|x| x.slot_id == *old_slot_id && x.sample_type == type_filt);
204
205 if sample_slot_find.is_some() {
208 println!("Found matchin slot id");
209 let mut sample_slot = sample_slot_find.clone().unwrap().1;
210 sample_slot.slot_id = *new_slot_id;
211 self.slots[sample_slot_find.unwrap().0] = sample_slot;
212 }
213
214 Ok(())
215 }
216}
217
218impl Default for ProjectFile {
219 fn default() -> Self {
220 let metadata = ProjectMetadata::default();
221 let states = ProjectStates::default();
222 let settings = ProjectSettings::default();
223
224 let slots: Vec<ProjectSampleSlot> = ProjectSampleSlot::defaults();
225
226 ProjectFile {
227 metadata,
228 settings,
229 states,
230 slots,
231 }
232 }
233}
234
235impl IsDefault for ProjectFile {
236 fn is_default(&self) -> bool {
237 &ProjectFile::default() == self
238 }
239}
240
241impl ProjectToString for ProjectFile {
242 fn to_string(&self) -> RBoxErr<String> {
244 let states_header =
245 "############################\r\n# Project States\r\n############################"
246 .to_string();
247 let settings_header =
248 "############################\r\n# Project Settings\r\n############################"
249 .to_string();
250 let slots_header =
251 "############################\r\n# Samples\r\n############################".to_string();
252 let footer = "############################".to_string();
253
254 let metadata_string: String = self.metadata.to_string()?;
255 let states_string: String = self.states.to_string()?;
256 let settings_string: String = self.settings.to_string()?;
257
258 let sslots_string = sslots_vec_to_string(&self.slots);
259
260 let v: Vec<String> = vec![
261 settings_header,
262 metadata_string,
263 settings_string,
264 states_header,
265 states_string,
266 slots_header,
267 sslots_string,
268 footer,
269 ];
270
271 let mut project_string = v.join("\r\n\r\n");
272 project_string.push_str("\r\n\r\n");
273 Ok(project_string)
274 }
275}
276
277impl Decode for ProjectFile {
280 fn decode(bytes: &[u8]) -> RBoxErr<Self> {
281 let s = std::str::from_utf8(bytes)?.to_string();
282
283 let metadata = ProjectMetadata::from_string(&s)?;
284 let states = ProjectStates::from_string(&s)?;
285 let settings = ProjectSettings::from_string(&s)?;
286 let slots = ProjectSampleSlot::from_string(&s)?;
287
288 Ok(Self {
289 metadata,
290 settings,
291 states,
292 slots,
293 })
294 }
295}
296
297impl Encode for ProjectFile {
299 fn encode(&self) -> RBoxErr<Vec<u8>> {
300 let data = self.to_string()?;
301 let bytes: Vec<u8> = data.bytes().collect::<Vec<u8>>();
302 Ok(bytes)
303 }
304}
305
306#[cfg(test)]
307#[allow(unused_imports)]
308mod tests {
309 use super::*;
310
311 mod test_spec {
312 use super::*;
313
314 #[test]
315 fn section_heading_from_value_no_match_is_err() {
316 assert!(ProjectRawFileSection::from_value(&"skfsdkfjskdh".to_string()).is_err())
317 }
318
319 #[test]
320 fn section_heading_from_value_uppercase_meta() {
321 assert_eq!(
322 ProjectRawFileSection::from_value(&"META".to_string()).unwrap(),
323 ProjectRawFileSection::Meta,
324 )
325 }
326
327 #[test]
328 fn section_heading_from_value_uppercase_states() {
329 assert_eq!(
330 ProjectRawFileSection::from_value(&"STATES".to_string()).unwrap(),
331 ProjectRawFileSection::States,
332 )
333 }
334
335 #[test]
336 fn section_heading_from_value_uppercase_settings() {
337 assert_eq!(
338 ProjectRawFileSection::from_value(&"SETTINGS".to_string()).unwrap(),
339 ProjectRawFileSection::Settings,
340 )
341 }
342
343 #[test]
344 fn section_heading_from_value_uppercase_samples() {
345 assert_eq!(
346 ProjectRawFileSection::from_value(&"SAMPLES".to_string()).unwrap(),
347 ProjectRawFileSection::Samples,
348 )
349 }
350
351 #[test]
352 fn section_heading_from_value_lowercase_meta() {
353 assert_eq!(
354 ProjectRawFileSection::from_value(&"meta".to_string()).unwrap(),
355 ProjectRawFileSection::Meta,
356 )
357 }
358
359 #[test]
360 fn section_heading_from_value_lowercase_states() {
361 assert_eq!(
362 ProjectRawFileSection::from_value(&"states".to_string()).unwrap(),
363 ProjectRawFileSection::States,
364 )
365 }
366
367 #[test]
368 fn section_heading_from_value_lowercase_settings() {
369 assert_eq!(
370 ProjectRawFileSection::from_value(&"settings".to_string()).unwrap(),
371 ProjectRawFileSection::Settings,
372 )
373 }
374
375 #[test]
376 fn section_heading_from_value_lowercase_samples() {
377 assert_eq!(
378 ProjectRawFileSection::from_value(&"samples".to_string()).unwrap(),
379 ProjectRawFileSection::Samples,
380 )
381 }
382
383 #[test]
384 fn section_heading_value_meta() {
385 assert_eq!(
386 ProjectRawFileSection::Meta.value().unwrap(),
387 "META".to_string(),
388 )
389 }
390
391 #[test]
392 fn section_heading_value_states() {
393 assert_eq!(
394 ProjectRawFileSection::States.value().unwrap(),
395 "STATES".to_string(),
396 )
397 }
398
399 #[test]
400 fn section_heading_value_settings() {
401 assert_eq!(
402 ProjectRawFileSection::Settings.value().unwrap(),
403 "SETTINGS".to_string(),
404 )
405 }
406
407 #[test]
408 fn section_heading_value_samples() {
409 assert_eq!(
410 ProjectRawFileSection::Samples.value().unwrap(),
411 "SAMPLES".to_string(),
412 )
413 }
414 }
415
416 mod test_read {
417 use super::*;
418
419 #[test]
420 fn test_full_to_string() {
421 let valid = "############################\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\nTRIM_BARSx100=0\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\nTRIM_BARSx100=0\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\nTRIM_BARSx100=0\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\nTRIM_BARSx100=0\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\nTRIM_BARSx100=0\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\nTRIM_BARSx100=0\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\nTRIM_BARSx100=0\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\nTRIM_BARSx100=0\r\nTSMODE=2\r\nLOOPMODE=0\r\nGAIN=72\r\nTRIGQUANTIZATION=255\r\n[/SAMPLE]\r\n\r\n############################\r\n\r\n";
422 assert_eq!(ProjectFile::default().to_string().unwrap(), valid);
423 }
424
425 #[test]
426 fn test_metadata_to_string() {
427 let valid = "[META]\r\nTYPE=OCTATRACK DPS-1 PROJECT\r\nVERSION=19\r\nOS_VERSION=R0177 1.40B\r\n[/META]";
428 assert_eq!(ProjectMetadata::default().to_string().unwrap(), valid);
429 }
430
431 #[test]
432 fn test_states_to_string() {
433 let valid = "[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]";
434 assert_eq!(ProjectStates::default().to_string().unwrap(), valid);
435 }
436
437 #[test]
438 fn test_settings_to_string() {
439 let valid = "[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]";
440 assert_eq!(ProjectSettings::default().to_string().unwrap(), valid);
441 }
442
443 #[test]
444 fn test_sslots_to_string() {
445 let valid = "[SAMPLE]\r\nTYPE=FLEX\r\nSLOT=129\r\nPATH=\r\nTRIM_BARSx100=0\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\nTRIM_BARSx100=0\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\nTRIM_BARSx100=0\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\nTRIM_BARSx100=0\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\nTRIM_BARSx100=0\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\nTRIM_BARSx100=0\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\nTRIM_BARSx100=0\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\nTRIM_BARSx100=0\r\nTSMODE=2\r\nLOOPMODE=0\r\nGAIN=72\r\nTRIGQUANTIZATION=255\r\n[/SAMPLE]";
446 assert_eq!(sslots_vec_to_string(&ProjectSampleSlot::defaults()), valid);
447 }
448 }
449}