rytm_rs/object/
pattern.rs

1// All casts in this file are intended or safe within the context of this library.
2//
3// One can change `allow` to `warn` to review them if necessary.
4#![allow(
5    clippy::cast_lossless,
6    clippy::cast_possible_truncation,
7    clippy::cast_sign_loss
8)]
9// TODO: Re-check if bpm related casts are accurate.
10// TODO: Correctly represent unset values in pattern, trig, track.
11// TODO: Check if we can get info about if this pattern has a kit assigned and which?
12
13pub(crate) mod de;
14pub(crate) mod plock;
15
16/// Holds the structure to represent a track.
17pub mod track;
18/// Holds the types used in pattern.
19pub mod types;
20
21use self::{
22    plock::ParameterLockPool,
23    types::{Speed, TimeMode},
24};
25use crate::{
26    defaults::default_tracks,
27    error::{ParameterError, RytmError, SysexConversionError},
28    impl_sysex_compatible,
29    object::pattern::track::Track,
30    sysex::{SysexCompatible, SysexMeta, SysexType, PATTERN_SYSEX_SIZE},
31    util::{
32        arc_mutex_owner, assemble_u32_from_u8_array_be, break_u32_into_u8_array_be, from_s_u16_t,
33        to_s_u16_t_union_b,
34    },
35    AnySysexType,
36};
37use derivative::Derivative;
38use parking_lot::Mutex;
39use rytm_rs_macro::parameter_range;
40use rytm_sys::{ar_pattern_raw_to_syx, ar_pattern_t, ar_pattern_track_t, ar_sysex_meta_t};
41use serde::Serialize;
42use std::sync::Arc;
43pub use track::{
44    trig::{types::*, Trig},
45    types::*,
46};
47
48impl_sysex_compatible!(
49    Pattern,
50    ar_pattern_t,
51    ar_pattern_raw_to_syx,
52    SysexType::Pattern,
53    PATTERN_SYSEX_SIZE
54);
55
56/// Represents a pattern in the analog rytm.
57///
58/// It does not map identically to the structure in the firmware.
59#[derive(Derivative, Clone, Serialize)]
60#[derivative(Debug)]
61pub struct Pattern {
62    #[derivative(Debug = "ignore")]
63    sysex_meta: SysexMeta,
64    /// Index of this pattern.
65    ///
66    /// Range `0..=127` or 0 for the pattern at work buffer.
67    pub(crate) index: usize,
68    /// Version of the pattern structure.
69    version: u32,
70    /// Fx Track
71    #[serde(serialize_with = "arc_mutex_owner::serialize")]
72    pub(crate) fx_track: Arc<Mutex<Track>>,
73    /// Tracks
74    ///
75    /// 12 tracks of analog rytm excluding the FX track.
76    tracks: Vec<Track>,
77    /// Master Length
78    ///
79    /// Range `1..=1024`
80    ///
81    /// - `1` = `Infinite`
82    /// - `2` = `2`
83    ///
84    /// and onwards.
85    master_length: u16,
86    /// Master Change
87    ///
88    /// Range `1..=1024`
89    ///
90    /// - `1` = `OFF`
91    /// - `2` = `2`
92    ///
93    /// and onwards.
94    master_change: u16,
95    /// Pattern Kit Number
96    ///
97    /// Range `0..=127`
98    /// Unset 0xFF
99    kit_number: u8,
100    /// Pattern Swing Amount
101    ///
102    /// Range `0..=30`
103    ///
104    /// - `0` = 50%
105    /// - `30` = 80%
106    swing_amount: u8,
107    /// Pattern Time Mode
108    ///
109    /// - Normal = `0`
110    /// - Advanced = `1`
111    time_mode: TimeMode,
112    /// Pattern Speed
113    ///
114    /// - 1x
115    /// - 2x
116    /// - 3/2x
117    /// - 3/4x
118    /// - 1/2x
119    /// - 1/4x
120    /// - 1/8x
121    speed: Speed,
122    /// Pattern Global Quantize
123    ///
124    /// Range `0..=127`
125    global_quantize: u8,
126    /// Pattern BPM
127    ///
128    /// Range `30.0..=300.0`
129    bpm: f32,
130
131    // 1=apply to all tracks, 0=apply only to current track
132    pad_scale_per_pattern: u8,
133
134    /// Parameter Lock Pool
135    #[derivative(Debug = "ignore")]
136    #[serde(serialize_with = "arc_mutex_owner::serialize")]
137    pub(crate) parameter_lock_pool: Arc<Mutex<ParameterLockPool>>,
138}
139
140impl From<&Pattern> for ar_pattern_t {
141    fn from(pattern: &Pattern) -> Self {
142        let mut tracks: [ar_pattern_track_t; 13] = [ar_pattern_track_t::default(); 13];
143
144        for (i, track) in pattern.tracks.iter().enumerate() {
145            if i == 12 {
146                tracks[i] = (&*pattern.fx_track.lock()).into();
147                break;
148            }
149            tracks[i] = track.into();
150        }
151
152        let bpm = (pattern.bpm * 120.0) as u16;
153        Self {
154            magic: break_u32_into_u8_array_be(pattern.version),
155            tracks,
156            plock_seqs: pattern.parameter_lock_pool.lock().as_raw(),
157            master_length: to_s_u16_t_union_b(pattern.master_length),
158            master_chg_msb: (pattern.master_change >> 8) as u8,
159            master_chg_lsb: pattern.master_change as u8,
160            kit_number: pattern.kit_number,
161            swing_amount: pattern.swing_amount,
162            time_mode: pattern.time_mode.into(),
163            master_speed: pattern.speed.into(),
164            global_quantize: pattern.global_quantize,
165            bpm_msb: (bpm >> 8) as u8,
166            bpm_lsb: bpm as u8,
167            pad_scale_per_pattern: pattern.pad_scale_per_pattern,
168        }
169    }
170}
171
172impl Pattern {
173    #[allow(clippy::too_many_arguments)]
174    pub(crate) fn try_from_raw(
175        sysex_meta: SysexMeta,
176        raw_pattern: &ar_pattern_t,
177    ) -> Result<Self, RytmError> {
178        let is_targeting_work_buffer = sysex_meta.is_targeting_work_buffer();
179        let index = sysex_meta.get_normalized_object_index();
180
181        let parameter_lock_pool = Arc::new(Mutex::new(ParameterLockPool::from_raw(
182            &raw_pattern.plock_seqs,
183            index,
184            is_targeting_work_buffer,
185        )));
186
187        let fx_track = Arc::new(Mutex::new(Track::try_from_raw(
188            12,
189            &raw_pattern.tracks[12],
190            &parameter_lock_pool,
191            None,
192        )?));
193
194        // Parameters does not matter here since they're going to be replaced.
195        // We just provide dummy values.
196        let mut tracks: Vec<Track> = default_tracks(0, false, None);
197
198        for (i, track) in raw_pattern.tracks.iter().enumerate() {
199            if i == 12 {
200                break;
201            }
202            tracks[i] =
203                Track::try_from_raw(i, track, &parameter_lock_pool, Some(Arc::clone(&fx_track)))?;
204        }
205
206        let version = assemble_u32_from_u8_array_be(&raw_pattern.magic);
207
208        let bpm = ((raw_pattern.bpm_msb as u16) << 8) | (raw_pattern.bpm_lsb as u16);
209        let bpm = bpm as f32 / 120.0;
210
211        let master_change =
212            ((raw_pattern.master_chg_msb as u16) << 8) | (raw_pattern.master_chg_lsb as u16);
213
214        Ok(Self {
215            index,
216            sysex_meta,
217            version,
218            tracks,
219            fx_track,
220            parameter_lock_pool,
221            master_length: unsafe { from_s_u16_t(raw_pattern.master_length) },
222            master_change,
223            kit_number: raw_pattern.kit_number,
224            swing_amount: raw_pattern.swing_amount,
225            time_mode: raw_pattern.time_mode.try_into()?,
226            speed: raw_pattern.master_speed.try_into()?,
227            global_quantize: raw_pattern.global_quantize,
228            bpm,
229            pad_scale_per_pattern: raw_pattern.pad_scale_per_pattern,
230        })
231    }
232
233    pub(crate) fn as_raw_parts(&self) -> (SysexMeta, ar_pattern_t) {
234        (self.sysex_meta, self.into())
235    }
236
237    /// Makes a new pattern with the project defaults.
238    ///
239    /// Range `0..=127`
240    #[parameter_range(range = "index:0..=127")]
241    pub fn try_default(index: usize) -> Result<Self, RytmError> {
242        Self::try_default_with_device_id(index, 0)
243    }
244
245    /// Makes a new pattern with the project defaults.
246    ///
247    /// Pattern index range: 0..=127`
248    /// Device id range: `0..=127`
249    #[parameter_range(range = "index:0..=127", range = "device_id:0..=127")]
250    pub fn try_default_with_device_id(index: usize, device_id: u8) -> Result<Self, RytmError> {
251        let parameter_lock_pool = Arc::new(Mutex::new(ParameterLockPool::default()));
252
253        let mut fx_track = Track::try_default(12, index, false, None).unwrap();
254        fx_track.parameter_lock_pool = Some(Arc::clone(&parameter_lock_pool));
255        let fx_track = Arc::new(Mutex::new(fx_track));
256
257        let mut tracks = default_tracks(0, true, Some(Arc::clone(&fx_track)));
258        for track in &mut tracks {
259            track.parameter_lock_pool = Some(Arc::clone(&parameter_lock_pool));
260            for trig in track.trigs_mut() {
261                trig.parameter_lock_pool = Some(Arc::clone(&parameter_lock_pool));
262            }
263        }
264
265        Ok(Self {
266            sysex_meta: SysexMeta::try_default_for_pattern(index, Some(device_id))?,
267            index,
268            version: 5,
269            tracks,
270            fx_track,
271            parameter_lock_pool,
272            master_length: 16,
273            master_change: 1,
274            kit_number: 0,
275            swing_amount: 0,
276            time_mode: TimeMode::Normal,
277            speed: Speed::default(),
278            global_quantize: 0,
279            bpm: 120.0,
280            pad_scale_per_pattern: 0x01,
281        })
282    }
283
284    // This function can not panic.
285    #[allow(clippy::missing_panics_doc)]
286    /// Makes a new pattern with the project defaults as if it is in the work buffer..
287    pub fn work_buffer_default() -> Self {
288        Self::work_buffer_default_with_device_id(0)
289    }
290
291    // This function can not panic.
292    #[allow(clippy::missing_panics_doc)]
293    /// Makes a new pattern with the project defaults as if it is in the work buffer..
294    pub fn work_buffer_default_with_device_id(device_id: u8) -> Self {
295        let parameter_lock_pool = Arc::new(Mutex::new(ParameterLockPool::default()));
296
297        let mut fx_track = Track::try_default(12, 0, true, None).unwrap();
298        fx_track.parameter_lock_pool = Some(Arc::clone(&parameter_lock_pool));
299        let fx_track = Arc::new(Mutex::new(fx_track));
300
301        let mut tracks = default_tracks(0, true, Some(Arc::clone(&fx_track)));
302        for track in &mut tracks {
303            track.parameter_lock_pool = Some(Arc::clone(&parameter_lock_pool));
304            for trig in track.trigs_mut() {
305                trig.parameter_lock_pool = Some(Arc::clone(&parameter_lock_pool));
306            }
307        }
308
309        Self {
310            sysex_meta: SysexMeta::default_for_pattern_in_work_buffer(Some(device_id)),
311            index: 0,
312            version: 5,
313            tracks,
314            fx_track,
315            parameter_lock_pool,
316            master_length: 16,
317            master_change: 1,
318            kit_number: 0,
319            swing_amount: 0,
320            time_mode: TimeMode::Normal,
321            speed: Speed::default(),
322            global_quantize: 0,
323            bpm: 120.0,
324            pad_scale_per_pattern: 0x01,
325        }
326    }
327
328    /// Returns a mutable reference to the tracks which this pattern contains.
329    ///
330    /// 13th element is the FX track.
331    pub fn tracks_mut(&mut self) -> &mut [Track] {
332        &mut self.tracks
333    }
334
335    /// Sets master length for the pattern.
336    ///
337    /// Range `1..=1024`
338    ///
339    /// - `1` = `Infinite`
340    /// - `2` = `2`
341    ///
342    /// and onwards.
343    #[parameter_range(range = "master_length:1..=1024")]
344    pub fn set_master_length(&mut self, master_length: usize) -> Result<(), RytmError> {
345        self.master_length = master_length as u16;
346        Ok(())
347    }
348
349    /// Sets swing amount for the pattern.
350    ///
351    /// Range `50..=80`
352    ///
353    /// Range denotes percentage.
354    #[parameter_range(range = "swing_amount:50..=80")]
355    pub fn set_swing_amount(&mut self, swing_amount: usize) -> Result<(), RytmError> {
356        // Internally, swing amount is stored as 0..=30
357        self.swing_amount = (swing_amount - 50) as u8;
358        Ok(())
359    }
360
361    /// Sets the speed for the pattern.
362    ///
363    /// Check [`Speed`] for options.
364    pub fn set_speed(&mut self, speed: Speed) {
365        self.speed = speed;
366    }
367
368    /// Sets the global quantize for the pattern.
369    ///
370    /// Range `0..=127`
371    #[parameter_range(range = "global_quantize:0..=127")]
372    pub fn set_global_quantize(&mut self, global_quantize: usize) -> Result<(), RytmError> {
373        self.global_quantize = global_quantize as u8;
374        Ok(())
375    }
376
377    /// Sets the kit number for the pattern.
378    ///
379    /// Range `0..=127`
380    #[parameter_range(range = "kit_number:0..=127")]
381    pub fn set_kit_number(&mut self, kit_number: usize) -> Result<(), RytmError> {
382        self.kit_number = kit_number as u8;
383        Ok(())
384    }
385
386    /// Sets the time mode for the pattern.
387    ///
388    /// Check [`TimeMode`] for options.
389    pub fn set_time_mode(&mut self, time_mode: TimeMode) {
390        self.time_mode = time_mode;
391    }
392
393    /// Sets the master change for the pattern.
394    ///
395    /// Range `1..=1024`
396    ///
397    /// - `1` = `OFF`
398    /// - `2` = `2`
399    ///
400    /// and onwards.
401    #[parameter_range(range = "master_change:1..=1024")]
402    pub fn set_master_change(&mut self, master_change: usize) -> Result<(), RytmError> {
403        self.master_change = master_change as u16;
404        Ok(())
405    }
406
407    /// Sets the BPM for the pattern.
408    ///
409    /// Range `30.0..=300.0`
410    ///
411    /// This is only effective when pattern level bpm is enabled.
412    #[parameter_range(range = "bpm:30.0..=300.0")]
413    pub fn set_bpm(&mut self, bpm: f32) -> Result<(), RytmError> {
414        self.bpm = bpm;
415        Ok(())
416    }
417
418    /// Returns a reference to the tracks which this pattern contains.
419    ///
420    /// 13th element is the FX track.
421    pub fn tracks(&self) -> &[Track] {
422        &self.tracks
423    }
424
425    /// Returns the master length for the pattern.
426    ///
427    /// Range `1..=1024`
428    ///
429    /// - `1` = `Infinite`
430    /// - `2` = `2`
431    ///
432    /// and onwards.
433    pub const fn master_length(&self) -> usize {
434        self.master_length as usize
435    }
436
437    /// Returns the swing amount for the pattern.
438    ///
439    /// Range `50..=80`
440    ///
441    /// Range denotes percentage.
442    pub const fn swing_amount(&self) -> usize {
443        // Internally, swing amount is stored as 0..=30
444        self.swing_amount as usize + 50
445    }
446
447    /// Returns the speed for the pattern.
448    ///
449    /// Check [`Speed`] for options.
450    pub const fn speed(&self) -> Speed {
451        self.speed
452    }
453
454    /// Returns the global quantize for the pattern.
455    ///
456    /// Range `0..=127`
457    pub const fn global_quantize(&self) -> usize {
458        self.global_quantize as usize
459    }
460
461    /// Returns the kit number for the pattern.
462    ///
463    /// Range `0..=127`
464    pub const fn kit_number(&self) -> usize {
465        self.kit_number as usize
466    }
467
468    /// Returns the time mode for the pattern.
469    ///
470    /// Check [`TimeMode`] for options.
471    pub const fn time_mode(&self) -> TimeMode {
472        self.time_mode
473    }
474
475    /// Returns the master change for the pattern.
476    ///
477    /// Range `1..=1024`
478    ///
479    /// - `1` = `OFF`
480    /// - `2` = `2`
481    ///
482    /// and onwards.
483    pub const fn master_change(&self) -> usize {
484        self.master_change as usize
485    }
486
487    /// Returns the BPM for the pattern.
488    ///
489    /// Range `30.0..=300.0`
490    ///
491    /// This is only effective when pattern level bpm is enabled.
492    pub const fn bpm(&self) -> f32 {
493        self.bpm
494    }
495
496    /// Returns the index of the pattern.
497    pub const fn index(&self) -> usize {
498        self.index
499    }
500
501    /// Checks if this pattern is the pattern at work buffer.
502    pub const fn is_work_buffer_pattern(&self) -> bool {
503        self.sysex_meta.is_targeting_work_buffer()
504    }
505
506    /// Returns the version of the pattern structure.
507    pub const fn structure_version(&self) -> u32 {
508        self.version
509    }
510
511    /// Clears all the parameter locks for this pattern.
512    pub fn clear_all_plocks(&mut self) {
513        self.parameter_lock_pool.lock().clear_all_plocks();
514    }
515
516    /// Clears all the parameter locks for the given track in this pattern.
517    ///
518    /// Range `0..=12`
519    #[parameter_range(range = "track_index:0..=12")]
520    pub fn clear_all_plocks_for_track(&mut self, track_index: u8) -> Result<(), RytmError> {
521        self.parameter_lock_pool
522            .lock()
523            .clear_all_plocks_for_track(track_index);
524
525        Ok(())
526    }
527
528    pub(crate) fn set_device_id(&mut self, device_id: u8) {
529        self.sysex_meta.set_device_id(device_id);
530    }
531}