1pub(crate) mod de;
2pub mod machine;
4pub mod page;
6pub mod settings;
8pub mod types;
10pub(crate) mod unknown;
11
12use self::{
13 machine::MachineParameters,
14 page::{Amplitude, Filter, Lfo, Sample},
15 settings::SoundSettings,
16 types::MachineType,
17 unknown::SoundUnknown,
18};
19use super::pattern::plock::ParameterLockPool;
20use crate::{
21 error::{RytmError, SysexConversionError},
22 impl_sysex_compatible,
23 object::types::ObjectName,
24 sysex::{SysexCompatible, SysexMeta, SysexType, SOUND_SYSEX_SIZE},
25 util::{arc_mutex_owner, assemble_u32_from_u8_array_be},
26 AnySysexType, ParameterError,
27};
28use derivative::Derivative;
29use parking_lot::Mutex;
30use rytm_rs_macro::parameter_range;
31use rytm_sys::{ar_sound_raw_to_syx, ar_sound_t, ar_sysex_meta_t};
32use serde::{Deserialize, Serialize};
33use std::sync::Arc;
34
35#[derive(
39 Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
40)]
41pub enum SoundType {
42 Pool,
43 #[default]
44 WorkBuffer,
45 KitQuery,
46}
47
48impl_sysex_compatible!(
49 Sound,
50 ar_sound_t,
51 ar_sound_raw_to_syx,
52 SysexType::Sound,
53 SOUND_SYSEX_SIZE
54);
55
56#[derive(Derivative, Clone, Serialize)]
60#[derivative(Debug)]
61pub struct Sound {
62 #[derivative(Debug = "ignore")]
63 sysex_meta: SysexMeta,
64 version: u32,
66
67 index: usize,
74 pool_index: Option<usize>,
76 kit_number: Option<usize>,
78 assigned_track: Option<usize>,
80
81 name: ObjectName,
83
84 machine_parameters: MachineParameters,
85
86 sample: Sample,
87 filter: Filter,
88 amplitude: Amplitude,
89 lfo: Lfo,
90 settings: SoundSettings,
91
92 accent_level: u8,
93 def_note: u8,
96
97 #[derivative(Debug = "ignore")]
98 __unknown: SoundUnknown,
99
100 #[derivative(Debug = "ignore")]
101 #[serde(serialize_with = "arc_mutex_owner::opt_serialize")]
102 pub(crate) parameter_lock_pool: Option<Arc<Mutex<ParameterLockPool>>>,
103}
104
105impl From<&Sound> for ar_sound_t {
106 fn from(sound: &Sound) -> Self {
107 let mut raw_sound = Self {
108 name: sound.name.copy_inner(),
109 accent_level: sound.accent_level,
110 def_note: sound.def_note,
111 ..Default::default()
112 };
113
114 sound.sample.apply_to_raw_sound(&mut raw_sound);
115 sound.filter.apply_to_raw_sound(&mut raw_sound);
116 sound.amplitude.apply_to_raw_sound(&mut raw_sound);
117 sound.lfo.apply_to_raw_sound(&mut raw_sound);
118 sound.settings.apply_to_raw_sound(&mut raw_sound);
119 sound.machine_parameters.apply_to_raw_sound(&mut raw_sound);
120
121 sound.__unknown.apply_to_raw_sound(&mut raw_sound);
122
123 raw_sound
124 }
125}
126
127impl Sound {
128 #[allow(clippy::missing_panics_doc)]
130 pub fn link_parameter_lock_pool(
141 &mut self,
142 parameter_lock_pool: &Arc<Mutex<ParameterLockPool>>,
143 ) -> Result<(), RytmError> {
144 let compatibility_error = ParameterError::Compatibility {
145 value: "ParameterLockPool".into(),
146 parameter_name: "parameter_lock_pool".into(),
147 reason: Some("The sound you're trying to link the parameter lock pool is a pool sound. Pool sounds cannot have parameter locks.".into()),
148 };
149 if self.is_pool_sound() {
150 return Err(compatibility_error.into());
151 }
152 self.parameter_lock_pool = Some(Arc::clone(parameter_lock_pool));
153 let parameter_lock_pool_ref = Arc::clone(
154 self.parameter_lock_pool
155 .as_ref()
156 .ok_or(compatibility_error)?,
157 );
158
159 self.machine_parameters
160 .link_parameter_lock_pool(parameter_lock_pool_ref);
161 Ok(())
162 }
163
164 pub(crate) fn try_from_raw(
166 sysex_meta: SysexMeta,
167 raw_sound: &ar_sound_t,
168 kit_number_and_assigned_track: Option<(usize, usize)>,
169 ) -> Result<Self, RytmError> {
170 let mut index: usize = 0;
171 let mut assigned_track = None;
172 let mut kit_number = None;
173 let mut pool_index = None;
174
175 match sysex_meta.object_type()? {
176 SysexType::Sound => {
177 if sysex_meta.is_targeting_work_buffer() {
178 index = (sysex_meta.obj_nr & 0b0111_1111) as usize;
179 assigned_track = Some(index);
180 }
181
182 if let Some((kit_n, assigned_t)) = kit_number_and_assigned_track {
183 index = assigned_t;
184 assigned_track = Some(assigned_t);
185 kit_number = Some(kit_n);
186 }
187
188 if kit_number_and_assigned_track.is_none() && !sysex_meta.is_targeting_work_buffer()
189 {
190 index = (sysex_meta.obj_nr & 0b0111_1111) as usize;
191 pool_index = Some(index);
192 }
193 }
194 SysexType::Kit => {
195 if let Some((kit_n, assigned_t)) = kit_number_and_assigned_track {
197 index = assigned_t;
198 assigned_track = Some(assigned_t);
199 kit_number = Some(kit_n);
200 } else {
201 panic!("This is not a sound query. Kit queries should provide the kit number and assigned track.")
202 }
203 }
204 _ => panic!(" This is not a sound or kit query."),
205 }
206
207 Ok(Self {
208 index,
209 pool_index,
210 kit_number,
211 assigned_track,
212 sysex_meta,
213 version: assemble_u32_from_u8_array_be(&raw_sound.__unknown_arr1[4..=7]),
214
215 name: ObjectName::from_u8_array(raw_sound.name),
216
217 sample: raw_sound.try_into()?,
218 filter: raw_sound.try_into()?,
219 amplitude: raw_sound.try_into()?,
220 lfo: raw_sound.try_into()?,
221 settings: raw_sound.try_into()?,
222 machine_parameters: MachineParameters::try_from_raw_sound(raw_sound, assigned_track)?,
223
224 accent_level: raw_sound.accent_level,
225 def_note: raw_sound.def_note,
226
227 __unknown: raw_sound.into(),
228
229 parameter_lock_pool: None,
230 })
231 }
232
233 pub(crate) fn as_raw_parts(&self) -> (SysexMeta, ar_sound_t) {
234 (self.sysex_meta, self.into())
235 }
236
237 pub const fn sound_type(&self) -> SoundType {
239 if self.is_pool_sound() {
240 SoundType::Pool
241 } else if self.is_work_buffer_sound() {
242 SoundType::WorkBuffer
243 } else {
244 SoundType::KitQuery
245 }
246 }
247
248 pub const fn is_pool_sound(&self) -> bool {
250 self.pool_index.is_some()
251 }
252
253 pub const fn is_work_buffer_sound(&self) -> bool {
255 self.assigned_track().is_some() && self.kit_number.is_none()
256 }
257
258 pub const fn is_part_of_a_kit_query(&self) -> bool {
260 self.kit_number.is_some()
261 }
262
263 pub fn set_name(&mut self, name: &str) -> Result<(), RytmError> {
269 self.name = name.try_into()?;
270 Ok(())
271 }
272
273 #[parameter_range(range = "accent_level:0..=127")]
277 #[allow(clippy::cast_possible_truncation)]
279 pub fn set_accent_level(&mut self, accent_level: usize) -> Result<(), RytmError> {
280 self.accent_level = accent_level as u8;
281 Ok(())
282 }
283
284 pub const fn assigned_track(&self) -> Option<usize> {
290 self.assigned_track
291 }
292
293 pub const fn kit_number(&self) -> Option<usize> {
299 self.kit_number
300 }
301
302 pub const fn pool_index(&self) -> Option<usize> {
308 self.pool_index
309 }
310
311 pub const fn accent_level(&self) -> usize {
315 self.accent_level as usize
316 }
317
318 pub fn name(&self) -> &str {
320 self.name.as_str()
321 }
322
323 pub const fn sample(&self) -> &Sample {
325 &self.sample
326 }
327
328 pub const fn filter(&self) -> &Filter {
330 &self.filter
331 }
332
333 pub const fn amplitude(&self) -> &Amplitude {
335 &self.amplitude
336 }
337
338 pub const fn lfo(&self) -> &Lfo {
340 &self.lfo
341 }
342
343 pub const fn settings(&self) -> &SoundSettings {
345 &self.settings
346 }
347
348 pub const fn machine_parameters(&self) -> &MachineParameters {
350 &self.machine_parameters
351 }
352
353 pub fn sample_mut(&mut self) -> &mut Sample {
355 &mut self.sample
356 }
357
358 pub fn filter_mut(&mut self) -> &mut Filter {
360 &mut self.filter
361 }
362
363 pub fn amplitude_mut(&mut self) -> &mut Amplitude {
365 &mut self.amplitude
366 }
367
368 pub fn lfo_mut(&mut self) -> &mut Lfo {
370 &mut self.lfo
371 }
372
373 pub fn settings_mut(&mut self) -> &mut SoundSettings {
375 &mut self.settings
376 }
377
378 pub fn machine_parameters_mut(&mut self) -> &mut MachineParameters {
380 &mut self.machine_parameters
381 }
382
383 pub const fn structure_version(&self) -> u32 {
385 self.version
386 }
387
388 #[parameter_range(range = "sound_index:0..=127")]
392 pub fn try_default(sound_index: usize) -> Result<Self, RytmError> {
393 Self::try_default_with_device_id(sound_index, 0)
394 }
395
396 #[parameter_range(range = "sound_index:0..=127", range = "device_id:0..=127")]
401 pub fn try_default_with_device_id(
402 sound_index: usize,
403 device_id: u8,
404 ) -> Result<Self, RytmError> {
405 Ok(Self {
406 sysex_meta: SysexMeta::try_default_for_sound(sound_index, Some(device_id))?,
407 index: sound_index,
408 pool_index: Some(sound_index),
409 kit_number: None,
410 assigned_track: None,
411
412 version: 4,
413 name: ObjectName::try_from(format!("POOL_SOUND {sound_index}"))?,
415 accent_level: 32,
416
417 sample: Sample::default(),
418 filter: Filter::default(),
419 amplitude: Amplitude::default(),
420 lfo: Lfo::default(),
421 settings: SoundSettings::default(),
422 machine_parameters: MachineParameters::default(),
423
424 def_note: 0,
426
427 __unknown: SoundUnknown::default(),
428
429 parameter_lock_pool: None,
430 })
431 }
432
433 #[parameter_range(range = "track_index:0..=11", range = "kit_index:0..=127")]
438 pub fn try_kit_default(
439 track_index: usize,
440 kit_index: usize,
441 sysex_meta: SysexMeta,
442 ) -> Result<Self, RytmError> {
443 let index = track_index;
445 Ok(Self {
446 sysex_meta,
447 index,
448 pool_index: None,
449 kit_number: Some(kit_index),
450 assigned_track: Some(track_index),
451
452 version: 4,
453 name: ObjectName::try_from(format!("KIT_SOUND {track_index}"))?,
454 accent_level: 32,
455
456 sample: Sample::default(),
457 filter: Filter::default(),
458 amplitude: Amplitude::default(),
459 lfo: Lfo::default(),
460 settings: SoundSettings::try_default_for_track(track_index)?,
461 machine_parameters: MachineParameters::try_default_for_track(track_index)?,
462
463 def_note: 0,
465
466 __unknown: SoundUnknown::default(),
467
468 parameter_lock_pool: None,
469 })
470 }
471
472 #[allow(clippy::missing_panics_doc)]
476 pub fn work_buffer_default(track_index: usize) -> Self {
477 Self::try_work_buffer_default_with_device_id(track_index, 0).unwrap()
478 }
479
480 #[parameter_range(range = "track_index:0..=11", range = "device_id:0..=127")]
485 pub fn try_work_buffer_default_with_device_id(
486 track_index: usize,
487 device_id: u8,
488 ) -> Result<Self, RytmError> {
489 let index = track_index | 0b1000_0000;
491 Ok(Self {
492 sysex_meta: SysexMeta::default_for_sound_in_work_buffer(track_index, Some(device_id)),
493 index,
494 pool_index: None,
495 kit_number: None,
496 assigned_track: Some(track_index),
497
498 version: 4,
499 name: ObjectName::try_from(format!("SOUND {track_index}"))?,
500 accent_level: 32,
501
502 sample: Sample::default(),
503 filter: Filter::default(),
504 amplitude: Amplitude::default(),
505 lfo: Lfo::default(),
506 settings: SoundSettings::try_default_for_track(track_index)?,
507 machine_parameters: MachineParameters::try_default_for_track(track_index)?,
508
509 def_note: 0,
511
512 __unknown: SoundUnknown::default(),
513
514 parameter_lock_pool: None,
515 })
516 }
517
518 pub fn set_machine_type(&mut self, machine_type: MachineType) -> Result<(), RytmError> {
528 if let Some(assigned_track) = self.assigned_track() {
529 if !crate::util::is_machine_compatible_for_track(assigned_track, machine_type) {
530 return Err(ParameterError::Compatibility {
531 value: machine_type.to_string(),
532 parameter_name: "Machine".to_string(),
533 reason: Some(format!(
534 "Given machine {} is not compatible for track {}",
535 machine_type, self.index
536 )),
537 }
538 .into());
539 }
540 }
541
542 self.settings_mut().machine_type = machine_type;
543 self.machine_parameters = machine_type.into();
544 Ok(())
545 }
546
547 pub const fn machine_type(&self) -> MachineType {
549 self.settings().machine_type
550 }
551
552 pub const fn index(&self) -> usize {
556 if self.sysex_meta.is_targeting_work_buffer() {
557 return self.sysex_meta.get_normalized_object_index();
558 }
559 self.index
560 }
561
562 pub(crate) fn set_device_id(&mut self, device_id: u8) {
563 self.sysex_meta.set_device_id(device_id);
564 }
565}