1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
use crate::{
    error::{ConversionError, ParameterError, RytmError, SysexConversionError},
    util::{from_s_u16_t, to_s_u16_t_union_b},
};
use rytm_rs_macro::parameter_range;
use rytm_sys::ar_sysex_meta_t;
use serde::{Deserialize, Serialize};

mod sysex_id {
    #![allow(clippy::cast_possible_truncation)]
    use rytm_sys::{
        ar_sysex_dump_id_t_AR_SYSEX_DUMPX_ID_GLOBAL, ar_sysex_dump_id_t_AR_SYSEX_DUMPX_ID_KIT,
        ar_sysex_dump_id_t_AR_SYSEX_DUMPX_ID_PATTERN,
        ar_sysex_dump_id_t_AR_SYSEX_DUMPX_ID_SETTINGS, ar_sysex_dump_id_t_AR_SYSEX_DUMPX_ID_SONG,
        ar_sysex_dump_id_t_AR_SYSEX_DUMPX_ID_SOUND, ar_sysex_dump_id_t_AR_SYSEX_DUMP_ID_GLOBAL,
        ar_sysex_dump_id_t_AR_SYSEX_DUMP_ID_KIT, ar_sysex_dump_id_t_AR_SYSEX_DUMP_ID_PATTERN,
        ar_sysex_dump_id_t_AR_SYSEX_DUMP_ID_SETTINGS, ar_sysex_dump_id_t_AR_SYSEX_DUMP_ID_SONG,
        ar_sysex_dump_id_t_AR_SYSEX_DUMP_ID_SOUND, ar_sysex_id_t_AR_TYPE_GLOBAL,
        ar_sysex_id_t_AR_TYPE_KIT, ar_sysex_id_t_AR_TYPE_PATTERN, ar_sysex_id_t_AR_TYPE_SETTINGS,
        ar_sysex_id_t_AR_TYPE_SONG, ar_sysex_id_t_AR_TYPE_SOUND,
    };

    pub const DUMP_ID_PATTERN: u8 = ar_sysex_dump_id_t_AR_SYSEX_DUMP_ID_PATTERN as u8;
    pub const DUMP_ID_KIT: u8 = ar_sysex_dump_id_t_AR_SYSEX_DUMP_ID_KIT as u8;
    pub const DUMP_ID_SOUND: u8 = ar_sysex_dump_id_t_AR_SYSEX_DUMP_ID_SOUND as u8;
    pub const DUMP_ID_SONG: u8 = ar_sysex_dump_id_t_AR_SYSEX_DUMP_ID_SONG as u8;
    pub const DUMP_ID_SETTINGS: u8 = ar_sysex_dump_id_t_AR_SYSEX_DUMP_ID_SETTINGS as u8;
    pub const DUMP_ID_GLOBAL: u8 = ar_sysex_dump_id_t_AR_SYSEX_DUMP_ID_GLOBAL as u8;

    pub const DUMPX_ID_PATTERN: u8 = ar_sysex_dump_id_t_AR_SYSEX_DUMPX_ID_PATTERN as u8;
    pub const DUMPX_ID_KIT: u8 = ar_sysex_dump_id_t_AR_SYSEX_DUMPX_ID_KIT as u8;
    pub const DUMPX_ID_SOUND: u8 = ar_sysex_dump_id_t_AR_SYSEX_DUMPX_ID_SOUND as u8;
    pub const DUMPX_ID_SONG: u8 = ar_sysex_dump_id_t_AR_SYSEX_DUMPX_ID_SONG as u8;
    pub const DUMPX_ID_SETTINGS: u8 = ar_sysex_dump_id_t_AR_SYSEX_DUMPX_ID_SETTINGS as u8;
    pub const DUMPX_ID_GLOBAL: u8 = ar_sysex_dump_id_t_AR_SYSEX_DUMPX_ID_GLOBAL as u8;

    pub const ID_PATTERN: u8 = ar_sysex_id_t_AR_TYPE_PATTERN as u8;
    pub const ID_KIT: u8 = ar_sysex_id_t_AR_TYPE_KIT as u8;
    pub const ID_SOUND: u8 = ar_sysex_id_t_AR_TYPE_SOUND as u8;
    pub const ID_SONG: u8 = ar_sysex_id_t_AR_TYPE_SONG as u8;
    pub const ID_SETTINGS: u8 = ar_sysex_id_t_AR_TYPE_SETTINGS as u8;
    pub const ID_GLOBAL: u8 = ar_sysex_id_t_AR_TYPE_GLOBAL as u8;
}

/// The type of a sysex message.
///
/// Can represent known sysex types.
#[derive(
    Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
)]
pub enum SysexType {
    Pattern,
    Kit,
    Sound,
    Song,
    #[default]
    Settings,
    Global,
}

impl SysexType {
    /// Dump ids are different compared to query ids.
    ///
    /// # Errors
    ///
    /// Returns an error if the dump id is invalid.
    pub fn try_from_dump_id(dump_id: u8) -> Result<Self, RytmError> {
        use sysex_id::*;
        match dump_id {
            DUMP_ID_PATTERN | DUMPX_ID_PATTERN => Ok(Self::Pattern),
            DUMP_ID_KIT | DUMPX_ID_KIT => Ok(Self::Kit),
            DUMP_ID_SOUND | DUMPX_ID_SOUND => Ok(Self::Sound),
            DUMP_ID_SONG | DUMPX_ID_SONG => Ok(Self::Song),
            DUMP_ID_SETTINGS | DUMPX_ID_SETTINGS => Ok(Self::Settings),
            DUMP_ID_GLOBAL | DUMPX_ID_GLOBAL => Ok(Self::Global),
            _ => Err(SysexConversionError::InvalidDumpMsgId.into()),
        }
    }
}

impl From<SysexType> for u8 {
    fn from(sysex_type: SysexType) -> Self {
        use sysex_id::*;
        match sysex_type {
            SysexType::Pattern => ID_PATTERN,
            SysexType::Kit => ID_KIT,
            SysexType::Sound => ID_SOUND,
            SysexType::Song => ID_SONG,
            SysexType::Settings => ID_SETTINGS,
            SysexType::Global => ID_GLOBAL,
        }
    }
}

#[allow(non_upper_case_globals)]
impl TryFrom<u8> for SysexType {
    type Error = ConversionError;
    fn try_from(sysex_type: u8) -> Result<Self, Self::Error> {
        use sysex_id::*;
        match sysex_type {
            ID_PATTERN => Ok(Self::Pattern),
            ID_KIT => Ok(Self::Kit),
            ID_SOUND => Ok(Self::Sound),
            ID_SONG => Ok(Self::Song),
            ID_SETTINGS => Ok(Self::Settings),
            ID_GLOBAL => Ok(Self::Global),
            _ => Err(ConversionError::Range {
                value: sysex_type.to_string(),
                type_name: "SysexType".into(),
            }),
        }
    }
}

/// Contains the metadata of a sysex message.
#[derive(
    Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
)]
pub struct SysexMeta {
    pub container_version: u16,
    pub dev_id: u8,
    pub obj_type: u8,
    pub obj_nr: u16,
    // The rest is not calculated in queries. Only calculated in responses and the calculation is done by [libanalogrytm](https://github.com/bsp2/libanalogrytm).
    pub chksum: u16,
    pub data_size: u16,
}

impl SysexMeta {
    // Found in all sysex messages comes from rytm.
    const SYSEX_META_CONTAINER_VERSION: u16 = 0x0101;

    pub const fn is_targeting_work_buffer(&self) -> bool {
        self.obj_nr >= 128
    }

    pub const fn get_normalized_object_index(&self) -> usize {
        if self.is_targeting_work_buffer() {
            return (self.obj_nr & 0b0111_1111) as usize;
        }
        self.obj_nr as usize
    }

    /// Returns the object type of the sysex message.
    pub fn object_type(&self) -> Result<SysexType, RytmError> {
        Ok(self.obj_type.try_into()?)
    }

    pub fn default_for_settings(dev_id: Option<u8>) -> Self {
        Self {
            container_version: Self::SYSEX_META_CONTAINER_VERSION,
            dev_id: dev_id.unwrap_or(0),
            obj_type: SysexType::Settings.into(),
            obj_nr: 0b0000_0000,
            ..Default::default()
        }
    }

    #[parameter_range(range = "global_slot:0..=3")]
    pub fn try_default_for_global(
        global_slot: usize,
        dev_id: Option<u8>,
    ) -> Result<Self, RytmError> {
        Ok(Self {
            container_version: Self::SYSEX_META_CONTAINER_VERSION,
            dev_id: dev_id.unwrap_or(0),
            obj_type: SysexType::Global.into(),
            obj_nr: global_slot as u16,
            ..Default::default()
        })
    }

    pub fn default_for_global_in_work_buffer(dev_id: Option<u8>) -> Self {
        Self {
            container_version: Self::SYSEX_META_CONTAINER_VERSION,
            dev_id: dev_id.unwrap_or(0),
            obj_type: SysexType::Global.into(),
            obj_nr: 0b1000_0000,
            ..Default::default()
        }
    }

    #[parameter_range(range = "pattern_index:0..=127")]
    pub fn try_default_for_pattern(
        pattern_index: usize,
        dev_id: Option<u8>,
    ) -> Result<Self, RytmError> {
        Ok(Self {
            container_version: Self::SYSEX_META_CONTAINER_VERSION,
            dev_id: dev_id.unwrap_or(0),
            obj_type: SysexType::Pattern.into(),
            obj_nr: pattern_index as u16,
            ..Default::default()
        })
    }

    pub fn default_for_pattern_in_work_buffer(dev_id: Option<u8>) -> Self {
        Self {
            container_version: Self::SYSEX_META_CONTAINER_VERSION,
            dev_id: dev_id.unwrap_or(0),
            obj_type: SysexType::Pattern.into(),
            obj_nr: 0b1000_0000,
            ..Default::default()
        }
    }

    #[parameter_range(range = "kit_index:0..=127")]
    pub fn try_default_for_kit(kit_index: usize, dev_id: Option<u8>) -> Result<Self, RytmError> {
        Ok(Self {
            container_version: Self::SYSEX_META_CONTAINER_VERSION,
            dev_id: dev_id.unwrap_or(0),
            obj_type: SysexType::Kit.into(),
            obj_nr: kit_index as u16,
            ..Default::default()
        })
    }

    pub fn default_for_kit_in_work_buffer(dev_id: Option<u8>) -> Self {
        Self {
            container_version: Self::SYSEX_META_CONTAINER_VERSION,
            dev_id: dev_id.unwrap_or(0),
            obj_type: SysexType::Kit.into(),
            obj_nr: 0b1000_0000,
            ..Default::default()
        }
    }

    #[parameter_range(range = "sound_index:0..=127")]
    pub fn try_default_for_sound(
        sound_index: usize,
        dev_id: Option<u8>,
    ) -> Result<Self, RytmError> {
        Ok(Self {
            container_version: Self::SYSEX_META_CONTAINER_VERSION,
            dev_id: dev_id.unwrap_or(0),
            obj_type: SysexType::Sound.into(),
            obj_nr: sound_index as u16,
            ..Default::default()
        })
    }

    pub fn default_for_sound_in_work_buffer(track_index: usize, dev_id: Option<u8>) -> Self {
        Self {
            container_version: Self::SYSEX_META_CONTAINER_VERSION,
            dev_id: dev_id.unwrap_or(0),
            obj_type: SysexType::Sound.into(),
            obj_nr: (0b1000_0000_u8 | track_index as u8) as u16,
            ..Default::default()
        }
    }
}

impl From<SysexMeta> for ar_sysex_meta_t {
    fn from(meta: SysexMeta) -> Self {
        Self {
            container_version: to_s_u16_t_union_b(meta.container_version),
            dev_id: meta.dev_id,
            obj_type: meta.obj_type,
            obj_nr: meta.obj_nr,
            ..Default::default()
        }
    }
}

impl From<&ar_sysex_meta_t> for SysexMeta {
    fn from(meta: &ar_sysex_meta_t) -> Self {
        Self {
            container_version: unsafe { from_s_u16_t(meta.container_version) },
            dev_id: meta.dev_id,
            obj_type: meta.obj_type,
            obj_nr: meta.obj_nr,
            chksum: unsafe { from_s_u16_t(meta.chksum) },
            data_size: unsafe { from_s_u16_t(meta.data_size) },
        }
    }
}