#![warn(clippy::all, clippy::pedantic, clippy::nursery)]
#![allow(
clippy::module_name_repetitions,
clippy::wildcard_imports,
clippy::similar_names
)]
#![allow(clippy::must_use_candidate, clippy::unsafe_derive_deserialize)]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/alisomay/rytm-rs/main/assets/logo.png",
html_favicon_url = "https://raw.githubusercontent.com/alisomay/rytm-rs/main/assets/favicon/favicon.ico"
)]
pub(crate) mod defaults;
pub mod error;
pub mod object;
pub mod prelude;
pub mod query;
pub(crate) mod sysex;
pub(crate) mod util;
use self::error::RytmError;
use crate::error::ParameterError;
use defaults::*;
use error::SysexConversionError;
use object::{
global::Global,
kit::Kit,
pattern::Pattern,
settings::Settings,
sound::{Sound, SoundType},
};
use rytm_sys::{ar_global_t, ar_kit_t, ar_pattern_t, ar_settings_t, ar_sound_t};
use serde::{Deserialize, Serialize};
use sysex::decode_sysex_response_to_raw;
pub use sysex::{AnySysexType, SysexCompatible, SysexType};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RytmProject {
work_buffer: RytmProjectWorkBuffer,
patterns: Vec<Pattern>,
pool_sounds: Vec<Sound>,
kits: Vec<Kit>,
globals: Vec<Global>,
settings: Settings,
pub(crate) last_queried_pattern_index: Option<usize>,
pub(crate) last_queried_kit_index: Option<usize>,
pub(crate) last_queried_work_buffer_pattern_index: Option<usize>,
pub(crate) last_queried_work_buffer_kit_index: Option<usize>,
}
impl RytmProject {
pub fn try_from_str(project_content: &str) -> Result<Self, RytmError> {
let project = serde_json::from_str(project_content)?;
Ok(project)
}
pub fn try_to_string(&self) -> Result<String, RytmError> {
let project_content = serde_json::to_string(self)?;
Ok(project_content)
}
pub fn set_device_id(&mut self, device_id: u8) {
self.work_buffer_mut()
.pattern_mut()
.set_device_id(device_id);
self.work_buffer_mut().kit_mut().set_device_id(device_id);
for sound in self.work_buffer_mut().sounds_mut().iter_mut() {
sound.set_device_id(device_id);
}
self.work_buffer_mut().global_mut().set_device_id(device_id);
for pattern in self.patterns_mut().iter_mut() {
pattern.set_device_id(device_id);
}
for kit in self.kits_mut().iter_mut() {
kit.set_device_id(device_id);
}
for sound in self.pool_sounds_mut().iter_mut() {
sound.set_device_id(device_id);
}
for global in self.globals_mut().iter_mut() {
global.set_device_id(device_id);
}
self.settings_mut().set_device_id(device_id);
}
#[allow(clippy::missing_errors_doc)]
pub fn try_default() -> Result<Self, RytmError> {
Self::try_default_with_device_id(0)
}
#[allow(clippy::missing_errors_doc)]
pub fn try_default_with_device_id(device_id: u8) -> Result<Self, RytmError> {
let mut patterns = Vec::with_capacity(PATTERN_MAX_COUNT);
patterns.reserve_exact(PATTERN_MAX_COUNT);
let mut kits = Vec::with_capacity(KIT_MAX_COUNT);
kits.reserve_exact(KIT_MAX_COUNT);
for i in 0..PATTERN_MAX_COUNT {
let pattern = Pattern::try_default_with_device_id(i, device_id)?;
let mut kit = Kit::try_default_with_device_id(i, device_id)?;
kit.link_parameter_lock_pool(&pattern.parameter_lock_pool)?;
patterns.push(pattern);
kits.push(kit);
}
Ok(Self {
work_buffer: RytmProjectWorkBuffer::try_default_with_device_id(device_id)?,
patterns,
pool_sounds: default_pool_sounds_with_device_id(device_id),
kits,
globals: default_globals_with_device_id(device_id),
settings: Settings::try_default_with_device_id(device_id)?,
last_queried_pattern_index: None,
last_queried_kit_index: None,
last_queried_work_buffer_pattern_index: None,
last_queried_work_buffer_kit_index: None,
})
}
#[allow(clippy::too_many_lines)]
pub fn update_from_sysex_response(&mut self, response: &[u8]) -> Result<(), RytmError> {
if response.len() < 2 {
return Err(SysexConversionError::ShortRead.into());
}
if !(response[0] == 0xF0 && response[response.len() - 1] == 0xF7) {
return Ok(());
}
let (mut raw, meta) = decode_sysex_response_to_raw(response)?;
match meta.object_type()? {
SysexType::Pattern => {
let raw_pattern: &ar_pattern_t =
unsafe { &*(raw.as_mut_ptr() as *const ar_pattern_t) };
let mut pattern = Pattern::try_from_raw(meta, raw_pattern)?;
let kit_number = pattern.kit_number();
if kit_number == 0xFF {
let work_buffer_pattern_kit_number = self.work_buffer().pattern().kit_number();
self.kits_mut()[work_buffer_pattern_kit_number]
.link_parameter_lock_pool(&pattern.parameter_lock_pool)?;
let work_buffer_kit_mut = self.work_buffer_mut().kit_mut();
if work_buffer_kit_mut.index() == work_buffer_pattern_kit_number {
work_buffer_kit_mut
.link_parameter_lock_pool(&pattern.parameter_lock_pool)?;
}
} else {
self.kits_mut()[kit_number]
.link_parameter_lock_pool(&pattern.parameter_lock_pool)?;
let work_buffer_kit_mut = self.work_buffer_mut().kit_mut();
if work_buffer_kit_mut.index() == kit_number {
work_buffer_kit_mut
.link_parameter_lock_pool(&pattern.parameter_lock_pool)?;
}
}
if meta.is_targeting_work_buffer() {
let index = meta.get_normalized_object_index();
pattern.index = index;
self.work_buffer.pattern = pattern;
return Ok(());
}
let index = meta.get_normalized_object_index();
self.patterns[index] = pattern;
Ok(())
}
SysexType::Kit => {
let raw_kit: &ar_kit_t = unsafe { &*(raw.as_mut_ptr() as *const ar_kit_t) };
let mut kit = Kit::try_from_raw(meta, raw_kit)?;
for pattern in self.patterns() {
if pattern.kit_number() == kit.index() {
kit.link_parameter_lock_pool(&pattern.parameter_lock_pool)?;
}
}
if self.work_buffer().pattern().kit_number() == kit.index() {
kit.link_parameter_lock_pool(
&self.work_buffer().pattern().parameter_lock_pool,
)?;
}
if meta.is_targeting_work_buffer() {
let index = meta.get_normalized_object_index();
kit.index = index;
self.work_buffer.kit = kit;
return Ok(());
}
let index = meta.get_normalized_object_index();
self.kits[index] = kit;
Ok(())
}
SysexType::Sound => {
let raw_sound: &ar_sound_t = unsafe { &*(raw.as_mut_ptr() as *const ar_sound_t) };
let mut sound = Sound::try_from_raw(meta, raw_sound, None)?;
let index = meta.get_normalized_object_index();
match sound.sound_type() {
SoundType::Pool => {
self.pool_sounds[index] = sound;
}
SoundType::WorkBuffer => {
sound.link_parameter_lock_pool(
&self.work_buffer().pattern().parameter_lock_pool,
)?;
self.work_buffer.sounds[index] = sound;
}
SoundType::KitQuery => {
unreachable!("A response from a sound query can not contain a sound which is part of a kit.")
}
};
Ok(())
}
SysexType::Global => {
let raw_global: &ar_global_t =
unsafe { &*(raw.as_mut_ptr() as *const ar_global_t) };
let global = Global::try_from_raw(meta, raw_global)?;
if meta.is_targeting_work_buffer() {
self.work_buffer.global = global;
return Ok(());
}
let index = (meta.obj_nr & 0b0111_1111) as usize;
self.globals[index] = global;
Ok(())
}
SysexType::Settings => {
let raw_settings: &ar_settings_t =
unsafe { &*(raw.as_mut_ptr() as *const ar_settings_t) };
let settings = Settings::try_from_raw(meta, raw_settings)?;
self.settings = settings;
Ok(())
}
SysexType::Song => Err(SysexConversionError::Unimplemented("Song".to_owned()).into()),
}
}
pub fn patterns(&self) -> &[Pattern] {
&self.patterns
}
pub fn kits(&self) -> &[Kit] {
&self.kits
}
pub fn pool_sounds(&self) -> &[Sound] {
&self.pool_sounds
}
pub fn globals(&self) -> &[Global] {
&self.globals
}
pub const fn settings(&self) -> &Settings {
&self.settings
}
pub fn patterns_mut(&mut self) -> &mut [Pattern] {
&mut self.patterns
}
pub fn kits_mut(&mut self) -> &mut [Kit] {
&mut self.kits
}
pub fn pool_sounds_mut(&mut self) -> &mut [Sound] {
&mut self.pool_sounds
}
pub fn globals_mut(&mut self) -> &mut [Global] {
&mut self.globals
}
pub fn settings_mut(&mut self) -> &mut Settings {
&mut self.settings
}
pub const fn work_buffer(&self) -> &RytmProjectWorkBuffer {
&self.work_buffer
}
pub fn work_buffer_mut(&mut self) -> &mut RytmProjectWorkBuffer {
&mut self.work_buffer
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RytmProjectWorkBuffer {
pattern: Pattern,
kit: Kit,
sounds: Vec<Sound>,
global: Global,
}
impl RytmProjectWorkBuffer {
#[allow(clippy::missing_errors_doc)]
pub fn try_default() -> Result<Self, RytmError> {
Self::try_default_with_device_id(0)
}
#[allow(clippy::missing_errors_doc)]
pub fn try_default_with_device_id(device_id: u8) -> Result<Self, RytmError> {
let pattern = Pattern::work_buffer_default_with_device_id(device_id);
let mut kit = Kit::work_buffer_default_with_device_id(device_id);
kit.link_parameter_lock_pool(&pattern.parameter_lock_pool)?;
Ok(Self {
pattern,
kit,
sounds: default_work_buffer_sounds_with_device_id(device_id),
global: Global::work_buffer_default_with_device_id(device_id),
})
}
pub const fn pattern(&self) -> &Pattern {
&self.pattern
}
pub const fn kit(&self) -> &Kit {
&self.kit
}
pub fn sounds(&self) -> &[Sound] {
&self.sounds
}
pub const fn global(&self) -> &Global {
&self.global
}
pub fn pattern_mut(&mut self) -> &mut Pattern {
&mut self.pattern
}
pub fn kit_mut(&mut self) -> &mut Kit {
&mut self.kit
}
pub fn sounds_mut(&mut self) -> &mut [Sound] {
&mut self.sounds
}
pub fn global_mut(&mut self) -> &mut Global {
&mut self.global
}
}