ot_tools_io/projects/settings/
control_menu.rs

1/*
2SPDX-License-Identifier: GPL-3.0-or-later
3Copyright © 2024 Mike Robeson [dijksterhuis]
4*/
5
6//! Data structures for the Octatrack Project Settings 'Control Menu'.
7
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11use crate::projects::{
12    parse_hashmap_string_value, parse_hashmap_string_value_bool, ProjectParseError,
13};
14use crate::settings::MidiChannel;
15
16/// Convenience struct for all data related to the Octatrack Project Settings 'Control' Menu.
17
18#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
19pub struct ControlMenu {
20    /// 'Audio' page
21    pub audio: AudioControlPage,
22
23    /// 'Input' page
24    pub input: InputControlPage,
25
26    /// 'Sequencer' page
27    pub sequencer: SequencerControlPage,
28
29    /// 'MIDI Sequencer' page
30    pub midi_sequencer: MidiSequencerControlPage,
31
32    /// 'Memory' page
33    pub memory: MemoryControlPage,
34
35    /// 'Metronome' page
36    pub metronome: MetronomeControlPage,
37
38    /// 'Midi' sub menu
39    pub midi: MidiSubMenu,
40}
41
42impl TryFrom<&HashMap<String, String>> for ControlMenu {
43    type Error = ProjectParseError;
44    fn try_from(value: &HashMap<String, String>) -> Result<Self, Self::Error> {
45        let audio = AudioControlPage::try_from(value)?;
46        let input = InputControlPage::try_from(value)?;
47        let sequencer = SequencerControlPage::try_from(value)?;
48        let midi_sequencer = MidiSequencerControlPage {};
49        let memory = MemoryControlPage::try_from(value)?;
50        let metronome = MetronomeControlPage::try_from(value)?;
51        let midi = MidiSubMenu::try_from(value)?;
52
53        Ok(Self {
54            audio,
55            input,
56            sequencer,
57            midi_sequencer,
58            memory,
59            metronome,
60            midi,
61        })
62    }
63}
64
65/// Convenience struct for all data related to the 'MIDI' sub-menu
66/// within the Octatrack Project Settings 'Control' Menu.
67
68#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
69pub struct MidiSubMenu {
70    pub control: MidiControlMidiPage,
71    pub sync: MidiSyncMidiPage,
72    pub channels: MidiChannelsMidiPage,
73    // control_midi_turbo: todo!(),
74}
75
76impl TryFrom<&HashMap<String, String>> for MidiSubMenu {
77    type Error = ProjectParseError;
78    fn try_from(value: &HashMap<String, String>) -> Result<Self, Self::Error> {
79        let control = MidiControlMidiPage::try_from(value)?;
80        let sync = MidiSyncMidiPage::try_from(value)?;
81        let channels = MidiChannelsMidiPage::try_from(value)?;
82        Ok(Self {
83            control,
84            sync,
85            channels,
86        })
87    }
88}
89
90/// `PROJECT` -> `CONTROL` -> `AUDIO` UI menu.
91#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
92pub struct AudioControlPage {
93    /// `TRACK 8` setting. Whether Track 8 is a master audio track or not:
94    /// - **NORMAL**: `false`
95    /// - **MASTER**: `true`
96    pub master_track: bool,
97
98    /// `CUE CFG` setting. Behaviour for audio routing to CUE outputs.
99    /// - **NORMAL** -> **CUE+TRACK** button combo sends audio to CUE out.
100    /// - **STUDIO** -> Individual track volume controls for CUE out (unable to **CUE+TRACK**).
101    pub cue_studio_mode: bool,
102}
103
104impl TryFrom<&HashMap<String, String>> for AudioControlPage {
105    type Error = ProjectParseError;
106    fn try_from(value: &HashMap<String, String>) -> Result<Self, Self::Error> {
107        let master_track = parse_hashmap_string_value_bool(value, "master_track", None)?;
108        let cue_studio_mode = parse_hashmap_string_value_bool(value, "cue_studio_mode", None)?;
109
110        Ok(Self {
111            master_track,
112            cue_studio_mode,
113        })
114    }
115}
116
117/// `PROJECT` -> `CONTROL` -> `INPUT` UI menu.
118#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
119pub struct InputControlPage {
120    /// dB level of noise gate for the AB external audio inputs.
121    /// See Manual section 8.8 MIXER MENU
122    pub gate_ab: u8,
123
124    /// dB level of noise gate for the CD external audio inputs.
125    /// See Manual section 8.8 MIXER MENU
126    pub gate_cd: u8,
127
128    /// See Manual section 8.6.2. INPUT.
129    /// Adds a delay to incoming external audio signals. Controlled by the DIR setting on the MIXER page.
130    pub input_delay_compensation: bool,
131}
132
133impl TryFrom<&HashMap<String, String>> for InputControlPage {
134    type Error = ProjectParseError;
135    fn try_from(value: &HashMap<String, String>) -> Result<Self, Self::Error> {
136        let gate_ab = parse_hashmap_string_value::<u8>(value, "gate_ab", None)?;
137        let gate_cd = parse_hashmap_string_value::<u8>(value, "gate_cd", None)?;
138        let input_delay_compensation =
139            parse_hashmap_string_value_bool(value, "input_delay_compensation", None)?;
140
141        Ok(Self {
142            gate_ab,
143            gate_cd,
144            input_delay_compensation,
145        })
146    }
147}
148
149/// `PROJECT` -> `CONTROL` -> `SEQUENCER` UI menu.
150#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
151pub struct SequencerControlPage {
152    /// `CHAIN AFTER` setting.
153    /// When chained patterns start playing once the pattern is chosen.
154    /// This is the global project level setting, but can be overidden for each pattern.
155    /// Default setting is "PATTERN LENGTH".
156    /// See Manual section 8.6.3. SEQUENCER.
157    pub pattern_change_chain_behaviour: u8, // bool?
158
159    /// `SILENCE TRACKS` setting
160    /// Silence tracks when switching to a new pattern.
161    /// See Manual section 8.6.3. SEQUENCER.
162    pub pattern_change_auto_silence_tracks: bool,
163
164    /// `LFO AUTO CHANGE` setting.
165    /// Whether to retrigger LFOs when swtiching to a new pattern
166    /// See Manual section 8.6.3. SEQUENCER.
167    pub pattern_change_auto_trig_lfos: bool,
168}
169
170impl TryFrom<&HashMap<String, String>> for SequencerControlPage {
171    type Error = ProjectParseError;
172    fn try_from(value: &HashMap<String, String>) -> Result<Self, Self::Error> {
173        let pattern_change_chain_behaviour =
174            parse_hashmap_string_value::<u8>(value, "pattern_change_chain_behavior", None)?;
175        let pattern_change_auto_silence_tracks =
176            parse_hashmap_string_value_bool(value, "pattern_change_auto_trig_lfos", None)?;
177        let pattern_change_auto_trig_lfos =
178            parse_hashmap_string_value_bool(value, "pattern_change_auto_trig_lfos", None)?;
179
180        Ok(Self {
181            pattern_change_chain_behaviour,
182            pattern_change_auto_silence_tracks,
183            pattern_change_auto_trig_lfos,
184        })
185    }
186}
187
188/// `PROJECT` -> `CONTROL` -> `MIDI SEQUENCER` UI menu.
189// TODO: ?!?!?!?! Where is the value for this??!?!?!
190#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
191pub struct MidiSequencerControlPage {}
192
193/// `PROJECT` -> `CONTROL` -> `MEMORY` UI menu.
194#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
195pub struct MemoryControlPage {
196    /// Whether samples can be loaded in 24-bit depth (16 bit depth samples are always oaded as 16 bit).
197    /// Setting this to false loads all samples as 16 bit depth.
198    /// See Manual section 8.6.5. MEMORY.
199    pub load_24bit_flex: bool,
200
201    /// Disabled forces all recorders to use track recorder memory (16 seconds per track).
202    /// When enabled, track recorders can use free Flex RAM memory.
203    /// See Manual section 8.6.5. MEMORY.
204    pub dynamic_recorders: bool,
205
206    /// Whether to record in 24 bit depth (`true`) or 16 bit depth (`false`).
207    /// See Manual section 8.6.5. MEMORY.
208    pub record_24bit: bool,
209
210    /// How many active track recorders are available in a project. Controls whether TR1 through to TR8 are enabled / disabled.
211    /// See Manual section 8.6.5. MEMORY.
212    pub reserved_recorder_count: u8,
213
214    /// How many 'sequencer steps' should be reserved for track recorders in RAM.
215    /// See Manual section 8.6.5. MEMORY.
216    pub reserved_recorder_length: u32,
217}
218
219impl TryFrom<&HashMap<String, String>> for MemoryControlPage {
220    type Error = ProjectParseError;
221    fn try_from(value: &HashMap<String, String>) -> Result<Self, Self::Error> {
222        let load_24bit_flex = parse_hashmap_string_value_bool(value, "load_24bit_flex", None)?;
223        let dynamic_recorders = parse_hashmap_string_value_bool(value, "dynamic_recorders", None)?;
224        let record_24bit = parse_hashmap_string_value_bool(value, "record_24bit", None)?;
225        let reserved_recorder_count =
226            parse_hashmap_string_value::<u8>(value, "reserved_recorder_count", None)?;
227        let reserved_recorder_length =
228            parse_hashmap_string_value::<u32>(value, "reserved_recorder_length", None)?;
229
230        Ok(Self {
231            load_24bit_flex,
232            dynamic_recorders,
233            record_24bit,
234            reserved_recorder_count,
235            reserved_recorder_length,
236        })
237    }
238}
239
240/// `PROJECT` -> `CONTROL` -> `METRONOME` UI menu.
241#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
242pub struct MetronomeControlPage {
243    /// `TIME SIG. NUMER` setting in `PROJECT` -> `CONTROL` -> `METRONOME` UI menu.
244    /// Controls the numerator for time signature (the 3 in 3/4).
245    /// See Manual section 8.6.6 METRONOME
246    pub metronome_time_signature: u8,
247
248    /// `TIME SIG. DENOM` setting in `PROJECT` -> `CONTROL` -> `METRONOME` UI menu.
249    /// Controls the numerator for time signature (the 3 in 3/4).
250    /// See Manual section 8.6.6 METRONOME
251    pub metronome_time_signature_denominator: u8,
252
253    /// `PREROLL` setting in `PROJECT` -> `CONTROL` -> `METRONOME` UI menu.
254    /// How many bars to prerolls with the metronome before playing a pattern.
255    /// See Manual section 8.6.6 METRONOME
256    pub metronome_preroll: u8,
257
258    /// How loud to play the metronome on CUE outputs. Default is 32.
259    /// See Manual section 8.6.6 METRONOME
260    pub metronome_cue_volume: u8,
261
262    /// How loud to play the metronome on MAIN outputs. Default is 0.
263    /// See Manual section 8.6.6 METRONOME
264    pub metronome_main_volume: u8,
265
266    /// Pitch of the metronome clicks. Default is 12.
267    /// See Manual section 8.6.6 METRONOME
268    pub metronome_pitch: u8,
269
270    /// Whether the metronome click has tonal characteristics or not. Default is `true` (enabled).
271    /// See Manual section 8.6.6 METRONOME
272    pub metronome_tonal: bool,
273
274    /// Whether the metronome is active. Default is `false`.
275    /// See Manual section 8.6.6 METRONOME
276    pub metronome_enabled: bool,
277}
278
279impl TryFrom<&HashMap<String, String>> for MetronomeControlPage {
280    type Error = ProjectParseError;
281    fn try_from(value: &HashMap<String, String>) -> Result<Self, Self::Error> {
282        let metronome_time_signature =
283            parse_hashmap_string_value::<u8>(value, "metronome_time_signature", None)?;
284        let metronome_time_signature_denominator =
285            parse_hashmap_string_value::<u8>(value, "metronome_time_signature_denominator", None)?;
286        let metronome_preroll = parse_hashmap_string_value::<u8>(value, "metronome_preroll", None)?;
287        let metronome_cue_volume =
288            parse_hashmap_string_value::<u8>(value, "metronome_cue_volume", None)?;
289        let metronome_main_volume =
290            parse_hashmap_string_value::<u8>(value, "metronome_main_volume", None)?;
291        let metronome_pitch = parse_hashmap_string_value::<u8>(value, "metronome_pitch", None)?;
292        let metronome_tonal = parse_hashmap_string_value_bool(value, "metronome_tonal", None)?;
293        let metronome_enabled = parse_hashmap_string_value_bool(value, "metronome_enabled", None)?;
294
295        Ok(Self {
296            metronome_time_signature,
297            metronome_time_signature_denominator,
298            metronome_preroll,
299            metronome_cue_volume,
300            metronome_main_volume,
301            metronome_pitch,
302            metronome_tonal,
303            metronome_enabled,
304        })
305    }
306}
307
308/// `PROJECT` -> `CONTROL` -> `MIDI` -> `CONTROL` UI menu.
309#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
310pub struct MidiControlMidiPage {
311    /// Whether samples can be loaded in 24-bit depth (16 bit depth samples are always odded as 16 bit).
312    /// `AUDIO CC IN` setting in `PROJECT` -> `CONTROL` -> `MIDI` -> `CONTROL` UI menu.
313    /// Whether audio tracks respond to MIDI CC IN messages.
314    ///
315    /// See manual section 8.7.1 CONTROL.
316    pub midi_audio_track_cc_in: bool,
317
318    /// `AUDIO CC OUT` setting in `PROJECT` -> `CONTROL` -> `MIDI` -> `CONTROL` UI menu.
319    /// Whether audio tracks send MIDI CC OUT messages.
320    /// Three options:
321    /// - `INT`: No messages sent, knobs only affect Octatrack settings.
322    /// - `EXT`: Sends CC OUT messages but they don't alter any Octatrack settings.
323    /// - `INT+EXT`: Simultaneously affects Octatrack settings and sends CC OUT messages.
324    ///
325    /// See manual section 8.7.1 CONTROL.
326    pub midi_audio_track_cc_out: u8,
327
328    /// `AUDIO NOTE IN` setting in `PROJECT` -> `CONTROL` -> `MIDI` -> `CONTROL` UI menu.
329    /// Whether to receive MIDI NOTE IN messages on Audio tracks and how the audio tracks
330    /// respond to those MIDI NOTE IN messages.
331    /// - **OFF**: midi note has no effect.
332    /// - **STANDARD**: standard note mapping (default).
333    /// - **FOLLOW TM**: Track's current trig mode affects audio tracks (track/chromatic/slots).
334    /// - **MAP/TRACK**: Uses MIDI MAP configuration on a per-track basis (track/chromatic/slots
335    ///   disconnected from user trig mode of track).
336    pub midi_audio_track_note_in: u8,
337
338    /// `AUDIO NOTE OUT` setting in `PROJECT` -> `CONTROL` -> `MIDI` -> `CONTROL` UI menu.
339    /// Whether audio tracks send MIDI NOTE OUT messages. Three options:
340    /// - `INT`: No messages sent, knobs only affect Octatrack settings.
341    /// - `EXT`: Sends NOTE OUT messages but they don't alter any Octatrack settings.
342    /// - `INT+EXT`: Simultaneously affects Octatrack settings and sends NOTE OUT messages.
343    ///
344    /// See manual section 8.7.1 CONTROL.
345    pub midi_audio_track_note_out: u8,
346
347    /// Unknown. MIDI channel to MIDI Track CC In messages n (1 - 16) ?
348    pub midi_midi_track_cc_in: u8,
349}
350
351impl TryFrom<&HashMap<String, String>> for MidiControlMidiPage {
352    type Error = ProjectParseError;
353    fn try_from(value: &HashMap<String, String>) -> Result<Self, Self::Error> {
354        let midi_audio_track_cc_in =
355            parse_hashmap_string_value_bool(value, "midi_audio_trk_cc_in", None)?;
356
357        let midi_audio_track_cc_out =
358            parse_hashmap_string_value::<u8>(value, "midi_audio_trk_cc_out", None)?;
359
360        let midi_audio_track_note_in =
361            parse_hashmap_string_value::<u8>(value, "midi_audio_trk_note_in", None)?;
362
363        let midi_audio_track_note_out =
364            parse_hashmap_string_value::<u8>(value, "midi_audio_trk_note_out", None)?;
365
366        let midi_midi_track_cc_in =
367            parse_hashmap_string_value::<u8>(value, "midi_midi_trk_cc_in", None)?;
368
369        Ok(Self {
370            midi_audio_track_cc_in,
371            midi_audio_track_cc_out,
372            midi_audio_track_note_in,
373            midi_audio_track_note_out,
374            midi_midi_track_cc_in,
375        })
376    }
377}
378
379/// `PROJECT` -> `CONTROL` -> `MIDI` -> `SYNC` UI menu.
380#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
381pub struct MidiSyncMidiPage {
382    /// `CLOCK SEND` setting.
383    /// Whether MIDI clock sending is enabled/disabled
384    /// See manual section 8.7.2 SYNC.
385    pub midi_clock_send: bool,
386
387    /// `CLOCK RECV` setting.
388    /// Whether MIDI clock receiving is enabled/disabled
389    /// See manual section 8.7.2 SYNC.
390    pub midi_clock_receive: bool,
391
392    /// `TRANS SEND` setting.
393    /// Whether MIDI transport sending is enabled/disabled
394    /// See manual section 8.7.2 SYNC.
395    pub midi_transport_send: bool,
396
397    /// `TRANS RECV` setting.
398    /// Whether MIDI transport receiving is enabled/disabled
399    /// See manual section 8.7.2 SYNC.
400    pub midi_transport_receive: bool,
401
402    /// `PROG CH SEND` setting.
403    /// Whether MIDI Program Change sending is enabled/disabled
404    /// See manual section 8.7.2 SYNC.
405    pub midi_progchange_send: bool,
406
407    /// `CHANNEL` setting.
408    /// Channel to send MIDI Program Change messages on. (-1, or between 1 - 16).
409    /// **NOTE**: should be set to `-1` when `midi_progchange_send` is disabled.
410    /// See manual section 8.7.2 SYNC.
411    pub midi_progchange_send_channel: MidiChannel,
412
413    /// `PROG CH RECEIVE` setting.
414    /// Whether MIDI Program Change receiveing is enabled/disabled
415    /// See manual section 8.7.2 SYNC.
416    pub midi_progchange_receive: bool,
417
418    /// `CHANNEL` setting.
419    /// Channel to receive MIDI Program Change messages on (-1 or between 1 - 16).
420    /// **NOTE**: should be set to `-1` when `midi_progchange_receive` is disabled.
421    /// See manual section 8.7.2 SYNC.
422    pub midi_progchange_receive_channel: MidiChannel,
423}
424
425impl TryFrom<&HashMap<String, String>> for MidiSyncMidiPage {
426    type Error = ProjectParseError;
427    fn try_from(value: &HashMap<String, String>) -> Result<Self, Self::Error> {
428        /*
429        WONTFIX: older OS versions seem to use non-boolean values for boolean settings.
430
431        Some older projects seem to use different settings values for this.
432        I had an OS 1.25E project (`ROMANT_DELETING`) where `MIDI_CLOCK_SEND=2`.
433        But this is definitely boolean so `2` doesn't make sense here
434        (see manual p. 40 and/or settings menu page).
435
436        This probably applies to the other boolean values here. I probably won't fix
437        this as it's an OS patching issue which can easily be fixed by users.
438        */
439        let midi_clock_send = parse_hashmap_string_value_bool(value, "midi_clock_send", None)?;
440
441        // See WONTFIX note above. Observed in an old 1.25E project.
442        let midi_clock_receive =
443            parse_hashmap_string_value_bool(value, "midi_clock_receive", None)?;
444
445        // See WONTFIX note above. Observed in an old 1.25E project.
446        let midi_transport_send =
447            parse_hashmap_string_value_bool(value, "midi_transport_send", None)?;
448
449        // See WONTFIX note above. Observed in an old 1.25E project.
450        let midi_transport_receive =
451            parse_hashmap_string_value_bool(value, "midi_transport_receive", None)?;
452
453        let midi_progchange_send =
454            parse_hashmap_string_value_bool(value, "midi_program_change_send", None)?;
455
456        let midi_progchange_send_channel = MidiChannel::try_from(
457            &parse_hashmap_string_value::<i8>(value, "midi_program_change_send_ch", None)?,
458        )?;
459
460        let midi_progchange_receive =
461            parse_hashmap_string_value_bool(value, "midi_program_change_receive", None)?;
462
463        let midi_progchange_receive_channel = MidiChannel::try_from(
464            &parse_hashmap_string_value::<i8>(value, "midi_program_change_receive_ch", None)?,
465        )?;
466
467        Ok(Self {
468            midi_clock_send,
469            midi_clock_receive,
470            midi_transport_send,
471            midi_transport_receive,
472            midi_progchange_send,
473            midi_progchange_send_channel,
474            midi_progchange_receive,
475            midi_progchange_receive_channel,
476        })
477    }
478}
479
480/// `PROJECT` -> `CONTROL` -> `MIDI` -> `CHANNELS` UI menu.
481#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
482pub struct MidiChannelsMidiPage {
483    /// `TRIG CH 1` setting in `PROJECT` -> `CONTROL` -> `MIDI` -> `CHANNELS` UI menu.
484    /// MIDI Channel to send MIDI Trig 1 messages to (1 - 16)
485    /// See manual section 8.7.3 CHANNELS.
486    ///
487    /// NOTE: Can also be -1 for DISABLED
488    pub midi_trig_ch1: i8,
489
490    /// `TRIG CH 2` setting in `PROJECT` -> `CONTROL` -> `MIDI` -> `CHANNELS` UI menu.
491    /// MIDI Channel to send MIDI Trig 2 messages to (1 - 16)
492    /// See manual section 8.7.3 CHANNELS.
493    ///
494    /// NOTE: Can also be -1 for DISABLED
495    pub midi_trig_ch2: i8,
496
497    /// `TRIG CH 3` setting in `PROJECT` -> `CONTROL` -> `MIDI` -> `CHANNELS` UI menu.
498    /// MIDI Channel to send MIDI Trig 3 messages to (1 - 16)
499    /// See manual section 8.7.3 CHANNELS.
500    ///
501    /// NOTE: Can also be -1 for DISABLED
502    pub midi_trig_ch3: i8,
503
504    /// `TRIG CH 4` setting in `PROJECT` -> `CONTROL` -> `MIDI` -> `CHANNELS` UI menu.
505    /// MIDI Channel to send MIDI Trig 4 messages to (1 - 16)
506    /// See manual section 8.7.3 CHANNELS.
507    ///
508    /// NOTE: Can also be -1 for DISABLED
509    pub midi_trig_ch4: i8,
510
511    /// `TRIG CH 5` setting in `PROJECT` -> `CONTROL` -> `MIDI` -> `CHANNELS` UI menu.
512    /// MIDI Channel to send MIDI Trig 5 messages to (1 - 16)
513    /// See manual section 8.7.3 CHANNELS.
514    ///
515    /// NOTE: Can also be -1 for DISABLED
516    pub midi_trig_ch5: i8,
517
518    /// `TRIG CH 6` setting in `PROJECT` -> `CONTROL` -> `MIDI` -> `CHANNELS` UI menu.
519    /// MIDI Channel to send MIDI Trig 6 messages to (1 - 16)
520    /// See manual section 8.7.3 CHANNELS.
521    ///
522    /// NOTE: Can also be -1 for DISABLED
523    pub midi_trig_ch6: i8,
524
525    /// `TRIG CH 7` setting in `PROJECT` -> `CONTROL` -> `MIDI` -> `CHANNELS` UI menu.
526    /// MIDI Channel to send MIDI Trig 7 messages to (1 - 16)
527    /// See manual section 8.7.3 CHANNELS.
528    ///
529    /// NOTE: Can also be -1 for DISABLED
530    pub midi_trig_ch7: i8,
531
532    /// `TRIG CH 8` setting in `PROJECT` -> `CONTROL` -> `MIDI` -> `CHANNELS` UI menu.
533    /// MIDI Channel to send MIDI Trig 8 messages to (1 - 16)
534    /// See manual section 8.7.3 CHANNELS.
535    ///
536    /// NOTE: Can also be -1 for DISABLED
537    pub midi_trig_ch8: i8,
538
539    /// `AUTO CH` setting in `PROJECT` -> `CONTROL` -> `MIDI` -> `CHANNELS` UI menu.
540    /// Auto MIDI Channel (1 - 16)
541    /// See manual section 8.7.3 CHANNELS.
542    ///
543    /// NOTE: Can also be -1 for DISABLED
544    pub midi_auto_channel: i8,
545}
546
547impl TryFrom<&HashMap<String, String>> for MidiChannelsMidiPage {
548    type Error = ProjectParseError;
549    fn try_from(value: &HashMap<String, String>) -> Result<Self, Self::Error> {
550        let midi_trig_ch1 = parse_hashmap_string_value::<i8>(value, "midi_trig_ch1", None)?;
551        let midi_trig_ch2 = parse_hashmap_string_value::<i8>(value, "midi_trig_ch2", None)?;
552        let midi_trig_ch3 = parse_hashmap_string_value::<i8>(value, "midi_trig_ch3", None)?;
553        let midi_trig_ch4 = parse_hashmap_string_value::<i8>(value, "midi_trig_ch4", None)?;
554        let midi_trig_ch5 = parse_hashmap_string_value::<i8>(value, "midi_trig_ch5", None)?;
555        let midi_trig_ch6 = parse_hashmap_string_value::<i8>(value, "midi_trig_ch6", None)?;
556        let midi_trig_ch7 = parse_hashmap_string_value::<i8>(value, "midi_trig_ch7", None)?;
557        let midi_trig_ch8 = parse_hashmap_string_value::<i8>(value, "midi_trig_ch8", None)?;
558        let midi_auto_channel = parse_hashmap_string_value::<i8>(value, "midi_auto_channel", None)?;
559        Ok(Self {
560            midi_trig_ch1,
561            midi_trig_ch2,
562            midi_trig_ch3,
563            midi_trig_ch4,
564            midi_trig_ch5,
565            midi_trig_ch6,
566            midi_trig_ch7,
567            midi_trig_ch8,
568            midi_auto_channel,
569        })
570    }
571}