Skip to main content

ot_tools_io/projects/
states.rs

1/*
2SPDX-License-Identifier: GPL-3.0-or-later
3Copyright © 2026 Mike Robeson [dijksterhuis]
4*/
5
6//! Type and parsing of a project state.
7//! e.g. the currently selected pattern/bank; whether a scene is muted; current muted tracks etc.
8//! Used in the [`crate::projects::ProjectFile`] type.
9
10use crate::projects::{
11    parse_hashmap_string_value, parse_hashmap_string_value_bool, string_to_hashmap,
12    ProjectParseError, SectionHeader,
13};
14use ot_tools_io_derive::{AsMutDerive, AsRefDerive};
15use serde::{Deserialize, Serialize};
16
17/*
18[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]
19
20----
21
22[STATES]
23BANK=0
24PATTERN=0
25ARRANGEMENT=0
26ARRANGEMENT_MODE=0
27PART=0
28TRACK=0
29TRACK_OTHERMODE=0
30SCENE_A_MUTE=0
31SCENE_B_MUTE=0
32TRACK_CUE_MASK=0
33TRACK_MUTE_MASK=0
34TRACK_SOLO_MASK=0
35MIDI_TRACK_MUTE_MASK=0
36MIDI_TRACK_SOLO_MASK=0
37MIDI_MODE=0
38[/STATES]
39*/
40
41/// Project state from a parsed Octatrack `project.*` file.
42/// This is the current 'UX focus' state, i.e. what parts, patterns, tracks are currently selected etc.
43#[derive(
44    Copy,
45    Clone,
46    Debug,
47    Default, // todo: surely this isn't correct? all zero values?
48    Eq,
49    Hash,
50    Ord,
51    PartialEq,
52    PartialOrd,
53    Serialize,
54    Deserialize,
55    AsMutDerive,
56    AsRefDerive,
57)]
58pub struct State {
59    /// Current active bank
60    pub bank: u8,
61
62    /// Current active pattern within a bank
63    pub pattern: u8,
64
65    /// Current active arrangement.
66    pub arrangement: u8,
67
68    /// Whether in arrangement mode or not.
69    pub arrangement_mode: u8, // dunno if this is a toggle or an enum
70
71    /// Current active part for the active pattern within a bank
72    pub part: u8,
73
74    /// Current active track
75    pub track: u8,
76
77    /// Unknown
78    pub track_othermode: u8, // WTFF is this?
79
80    /// Whether Scene A is disabled/enabled
81    pub scene_a_mute: bool,
82
83    /// Whether Scene B is disabled/enabled
84    pub scene_b_mute: bool,
85
86    /// Mask detailingg which CUE outputs are muted on Audio Tracks.  
87    pub track_cue_mask: u8,
88
89    /// Mask detailingg which MAIN outputs are muted on Audio Tracks.  
90    pub track_mute_mask: u8,
91
92    /// Mask detailingg which MAIN outputs are soloed on Audio Tracks.  
93    pub track_solo_mask: u8,
94
95    /// Mask detailingg which MIDI outputs are muted on MIDI  Tracks.  
96    pub midi_track_mute_mask: u8,
97
98    /// Mask detailingg which MIDI outputs are soloed on MIDI  Tracks.  
99    pub midi_track_solo_mask: u8,
100
101    // Unknown
102    pub midi_mode: u8,
103}
104
105impl std::str::FromStr for State {
106    type Err = ProjectParseError;
107
108    /// Load project 'state' data from the raw project ASCII file.
109    fn from_str(s: &str) -> Result<Self, Self::Err> {
110        let hmap = string_to_hashmap(s, &SectionHeader::States)?;
111
112        Ok(Self {
113            bank: parse_hashmap_string_value::<u8>(&hmap, "bank", None)?,
114            pattern: parse_hashmap_string_value::<u8>(&hmap, "pattern", None)?,
115            arrangement: parse_hashmap_string_value::<u8>(&hmap, "arrangement", None)?,
116            arrangement_mode: parse_hashmap_string_value::<u8>(&hmap, "arrangement_mode", None)?,
117            part: parse_hashmap_string_value::<u8>(&hmap, "part", None)?,
118            track: parse_hashmap_string_value::<u8>(&hmap, "track", None)?,
119            track_othermode: parse_hashmap_string_value::<u8>(&hmap, "track_othermode", None)?,
120            scene_a_mute: parse_hashmap_string_value_bool(&hmap, "scene_a_mute", None)?,
121            scene_b_mute: parse_hashmap_string_value_bool(&hmap, "scene_b_mute", None)?,
122            track_cue_mask: parse_hashmap_string_value::<u8>(&hmap, "track_cue_mask", None)?,
123            track_mute_mask: parse_hashmap_string_value::<u8>(&hmap, "track_mute_mask", None)?,
124            track_solo_mask: parse_hashmap_string_value::<u8>(&hmap, "track_solo_mask", None)?,
125            midi_track_mute_mask: parse_hashmap_string_value::<u8>(
126                &hmap,
127                "midi_track_mute_mask",
128                None,
129            )?,
130            midi_track_solo_mask: parse_hashmap_string_value::<u8>(
131                &hmap,
132                "midi_track_solo_mask",
133                None,
134            )?,
135            midi_mode: parse_hashmap_string_value::<u8>(&hmap, "midi_mode", None)?,
136        })
137    }
138}
139
140impl std::fmt::Display for State {
141    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
142        let mut s = "".to_string();
143        s.push_str("[STATES]\r\n");
144        s.push_str(format!("BANK={}", self.bank).as_str());
145        s.push_str("\r\n");
146        s.push_str(format!("PATTERN={}", self.pattern).as_str());
147        s.push_str("\r\n");
148        s.push_str(format!("ARRANGEMENT={}", self.arrangement).as_str());
149        s.push_str("\r\n");
150        s.push_str(format!("ARRANGEMENT_MODE={}", self.arrangement_mode).as_str());
151        s.push_str("\r\n");
152        s.push_str(format!("PART={}", self.part).as_str());
153        s.push_str("\r\n");
154        s.push_str(format!("TRACK={}", self.track).as_str());
155        s.push_str("\r\n");
156        s.push_str(format!("TRACK_OTHERMODE={}", self.track_othermode).as_str());
157        s.push_str("\r\n");
158        s.push_str(format!("SCENE_A_MUTE={}", self.scene_a_mute as u8).as_str());
159        s.push_str("\r\n");
160        s.push_str(format!("SCENE_B_MUTE={}", self.scene_b_mute as u8).as_str());
161        s.push_str("\r\n");
162        s.push_str(format!("TRACK_CUE_MASK={}", self.track_cue_mask).as_str());
163        s.push_str("\r\n");
164        s.push_str(format!("TRACK_MUTE_MASK={}", self.track_mute_mask).as_str());
165        s.push_str("\r\n");
166        s.push_str(format!("TRACK_SOLO_MASK={}", self.track_solo_mask).as_str());
167        s.push_str("\r\n");
168        s.push_str(format!("MIDI_TRACK_MUTE_MASK={}", self.midi_track_mute_mask).as_str());
169        s.push_str("\r\n");
170        s.push_str(format!("MIDI_TRACK_SOLO_MASK={}", self.midi_track_solo_mask).as_str());
171        s.push_str("\r\n");
172        s.push_str(format!("MIDI_MODE={}", self.midi_mode).as_str());
173        s.push_str("\r\n[/STATES]");
174        write!(f, "{s:#}")
175    }
176}