rytm_rs/
query.rs

1//! Query structures for the rytm.
2//!
3//! The structures in this module are used to send sysex queries to the rytm.
4//!
5//! The all implement the `ObjectQuery` trait and there is a blanket implementation for all types which implement `ObjectQuery` to be `SysexCompatible`.
6//!
7//! # Example
8//!
9//! ```
10//! use rytm::prelude::*;
11//!
12//! let query = GlobalQuery::new_targeting_work_buffer();
13//! let sysex = query.as_sysex().unwrap();
14//!
15//! ```
16
17// All casts in this file are intended or safe within the context of this library.
18//
19// One can change `allow` to `warn` to review them if necessary.
20#![allow(
21    clippy::cast_lossless,
22    clippy::cast_possible_truncation,
23    clippy::cast_sign_loss
24)]
25
26/// Holds the global query structure.
27mod global;
28/// Holds the kit query structure.
29mod kit;
30/// Holds the pattern query structure.
31mod pattern;
32/// Holds the raw query structure.
33mod raw;
34/// Holds the settings query structure.
35mod settings;
36/// Holds the song query structure.
37mod song;
38/// Holds the sound query structure.
39mod sound;
40
41pub use global::GlobalQuery;
42pub use kit::KitQuery;
43pub use pattern::PatternQuery;
44pub use raw::RawQuery;
45pub use settings::SettingsQuery;
46pub use song::SongQuery;
47pub use sound::SoundQuery;
48
49/// The size of the rytm sysex query in bytes.
50///
51/// `0xF0 0x00 0x20 0x3c 0x07 0x00 <id> 0x01 0x01 <nr> 0x00 0x00 0x00 0x05 0xF7`
52const RYTM_SYSEX_QUERY_SIZE: usize = rytm_sys::AR_SYSEX_REQUEST_MSG_SZ as usize;
53
54use super::error::{RytmError, SysexConversionError};
55use crate::{
56    sysex::{AnySysexType, SysexMeta},
57    SysexCompatible,
58};
59
60/// A trait which is implemented by all structures which can be converted to rytm sysex query messages.
61pub trait ObjectQuery: SysexCompatible + Send + Sync {
62    /// Returns the sysex type of the object.
63    fn sysex_type(&self) -> AnySysexType;
64
65    /// Returns the device id of the object.
66    fn device_id(&self) -> u8;
67
68    /// Returns the object number (index) of the object.
69    fn obj_nr(&self) -> u16;
70
71    /// Returns the sysex meta data for the object creating it.
72    fn as_sysex_meta(&self) -> SysexMeta {
73        SysexMeta {
74            container_version: 0x0101,
75            dev_id: self.device_id(),
76            obj_type: ObjectQuery::sysex_type(self).into(),
77            obj_nr: self.obj_nr(),
78            // Calculated in libanalogrytm, they're dummy values here in this state.
79            chksum: 0,
80            data_size: 0,
81        }
82    }
83
84    /// Returns the information if this query is targeting the work buffer.
85    fn is_targeting_work_buffer(&self) -> bool {
86        self.obj_nr() >= 128
87    }
88}
89
90impl<T: ObjectQuery> SysexCompatible for T {
91    fn sysex_type(&self) -> AnySysexType {
92        ObjectQuery::sysex_type(self)
93    }
94
95    fn as_sysex(&self) -> Result<Vec<u8>, RytmError> {
96        let mut buffer = vec![0; RYTM_SYSEX_QUERY_SIZE];
97        let destination_buffer = buffer.as_mut_slice();
98        let meta: rytm_sys::ar_sysex_meta_t = self.as_sysex_meta().into();
99
100        // The count of return error codes from `rytm-sys` is far below 255.
101        #[allow(clippy::cast_possible_truncation)]
102        unsafe {
103            let return_code = rytm_sys::ar_sysex_request(
104                destination_buffer.as_mut_ptr(),
105                std::ptr::addr_of!(meta),
106            ) as u8;
107
108            if return_code != 0 {
109                return Err(SysexConversionError::from(return_code).into());
110            }
111        }
112
113        Ok(buffer)
114    }
115}