rytm_rs/lib.rs
1#![warn(clippy::all, clippy::pedantic, clippy::nursery)]
2#![allow(
3 clippy::module_name_repetitions,
4 clippy::wildcard_imports,
5 clippy::similar_names
6)]
7// TODO: Re-check later.
8#![allow(clippy::must_use_candidate, clippy::unsafe_derive_deserialize)]
9#![doc(
10 html_logo_url = "https://raw.githubusercontent.com/alisomay/rytm-rs/main/assets/logo.png",
11 html_favicon_url = "https://raw.githubusercontent.com/alisomay/rytm-rs/main/assets/favicon/favicon.ico"
12)]
13
14//! # rytm-rs
15//!
16//! More than safe rust abstractions over [rytm-sys](https://github.com/alisomay/rytm-sys), an unofficial SDK for writing software for Analog Rytm running on firmware 1.70.
17//!
18//! On top of `CC` and `NRPN` messages, Rytm also accepts sysex messages which are undocumented and not officially supported by Elektron.
19//!
20//! The effort of reverse engineering the sysex format started with [libanalogrytm](https://github.com/bsp2/libanalogrytm) which is a `C` library powers parts of `rytm-rs` through `rytm-sys` bindings.
21//!
22//! [libanalogrytm](https://github.com/bsp2/libanalogrytm) though a great foundation, is not accessible to many developers due to its low level nature and also lacks high level abstractions for common tasks. The scope of the [libanalogrytm](https://github.com/bsp2/libanalogrytm) is to provide the necessary types for the encoded and decoded sysex messages and focus on the low level details of the sysex protocol.
23//!
24//! `rytm-rs` builds on top of [libanalogrytm](https://github.com/bsp2/libanalogrytm) and provides high level abstractions for common tasks and designed to provide an SDK like experience for developers with ease of use in mind abstracting the low level details completely.
25//!
26//! ## Features
27//!
28//! - All structures in a Rytm project is completely represented with [`RytmProject`] including all the necessary fields and methods to receive manipulate and send the project to the device.
29//! - All getter and setter methods have range and validity checks including comments about the range and validity of the values.
30//! - The Rytm device project defaults are represented in all the struct `Default` implementations.
31//! - Sysex encoding and decoding is completely abstracted away. Update the project with a single method call.
32//! - Convert parts of the project to sysex with one method call and send it to the device with your choice of transport.
33//! - Separate query types provided for [`Pattern`](crate::object::Pattern), [`Kit`](crate::object::Kit), [`Sound`](crate::object::Sound), [`Settings`](crate::object::Settings) and [`Global`](crate::object::Global) types which covers the entire Rytm project parameters except songs.
34//! - Different methods provided for setting, getting, clearing parameter locks exhaustively and available in [`Trig`](crate::object::pattern::track::trig::Trig) struct.
35//! - All 34 machine types are represented including parameter lock setters getters and clearers.
36//! - All getters and setters use the actual range of values on the device not the internal ranges which are used in the sysex protocol.
37//!
38//! ## Purpose
39//!
40//! The purpose of this crate is to provide a safe and easy to use SDK like experience for developers who would like to write software for Analog Rytm.
41//!
42//! The first priority for this crate is to provide an easy to use api for developers who would like to
43//!
44//! - Develop a software products for Analog Rytm
45//! - Develop custom creative software for artistic purposes
46//! - Discover and experiment with generative and algorithmic music but don't want to deal with the low level details of the sysex protocol communicating with the device.
47//!
48//! The crate is not optimized for the best performance or memory. On the other hand the memory footprint is not that big and the performance is good enough since the performance bottleneck is the device itself when it comes to sysex communication.
49//!
50//! I believe that Rytm uses a low priority thread for sysex communication in their internal RTOS. If you flood Rytm with sysex messages it will queue the responses and get back to you when it can. This is not an issue for most use cases but it is a nice to know.
51//!
52//! ## Layers
53//!
54//! `rytm-rs` is composed of 3 main layers.
55//!
56//! ### [`rytm-sys`](https://docs.rs/rytm-sys/latest/rytm_sys/#)
57//!
58//! - Encoding/decoding sysex messages
59//! - Providing `#[repr(C,packed)]` structs to identically represent the sysex messages in memory keeping the original memory layout of the messages.
60//! - Exposing types from [libanalogrytm](https://github.com/bsp2/libanalogrytm) through `rytm-sys` bindings. Which is the main hub for reverse engineering.
61//!
62//! ### `rytm-rs`
63//!
64//! Internal layer which deals with communicating with `rytm-sys` and deals with conversion from/to raw types (`#[repr(C,packed)]` structs).
65//!
66//! User facing layer which provides high level abstractions for common tasks. Getters, setters etc.
67//!
68//!
69//! ## Usage
70//!
71//! Starting with importing the prelude is a good idea since it brings the necessary traits and types into scope.
72//!
73//! Also the [`midir`](https://github.com/Boddlnagg/midir) library will be used for midi communication with the device in these examples but you can use any midi library you want.
74//!
75//! ```
76//! use std::sync::Arc; use parking_lot::Mutex;
77//! use midir::{Ignore, MidiInputConnection, MidiOutputConnection};
78//! use rytm_rs::prelude::*;
79//!
80//! // We'll be using this connection for sending sysex messages to the device.
81//! //
82//! // Using an Arc<Mutex<MidiOutputConnection>> is a good idea since you can share the connection between threads.
83//! // Which will be common in this context.
84//! fn get_connection_to_rytm() -> Arc<Mutex<MidiOutputConnection>> {
85//! let output = port::MidiOut::new("rytm_test_out").unwrap();
86//! let rytm_out_identifier = "Elektron Analog Rytm MKII";
87//! let rytm_output_port = output.find_output_port(rytm_out_identifier).unwrap();
88//!
89//! Arc::new(Mutex::new(
90//! output.make_output_connection(&rytm_output_port, 0).unwrap(),
91//! ))
92//! }
93//!
94//! // We'll be using this connection for receiving sysex messages from the device and forwarding them to our main thread.
95//! pub fn make_input_message_forwarder() -> (
96//! MidiInputConnection<()>,
97//! std::sync::mpsc::Receiver<(Vec<u8>, u64)>,
98//! ) {
99//! let mut input = crate::port::MidiIn::new("rytm_test_in").unwrap();
100//! input.ignore(Ignore::None);
101//! let rytm_in_identifier = "Elektron Analog Rytm MKII";
102//! let rytm_input_port = input.find_input_port(rytm_in_identifier).unwrap();
103//!
104//! let (tx, rx) = std::sync::mpsc::channel::<(Vec<u8>, u64)>();
105//!
106//! let conn_in: midir::MidiInputConnection<()> = input
107//! .into_inner()
108//! .connect(
109//! &rytm_input_port,
110//! "rytm_test_in",
111//! move |stamp, message, _| {
112//! // Do some filtering here if you like.
113//! tx.send((message.to_vec(), stamp)).unwrap();
114//! },
115//! (),
116//! )
117//! .unwrap();
118//!
119//! (conn_in, rx)
120//! }
121//!
122//! fn main() {
123//! // Make a default rytm project
124//! let mut rytm = RytmProject::default();
125//!
126//! // Get a connection to the device
127//! let conn_out = get_connection_to_rytm();
128//!
129//! // Listen for incoming messages from the device
130//! let (_conn_in, rx) = make_input_message_forwarder();
131//!
132//! // Make a query for the pattern in the work buffer
133//! let query = PatternQuery::new_targeting_work_buffer();
134//!
135//! // Send the query to the device
136//! conn_out
137//! .lock()
138//! .unwrap()
139//! .send(&query.as_sysex().unwrap())
140//! .unwrap();
141//!
142//! // Wait for the response
143//! match rx.recv() {
144//! Ok((message, _stamp)) => {
145//! match rytm.update_from_sysex_response(&message) {
146//! Ok(_) => {
147//! for track in rytm.work_buffer_mut().pattern_mut().tracks_mut() {
148//! // Set the number of steps to 64
149//! track.set_number_of_steps(64).unwrap();
150//! for (i, trig) in track.trigs_mut().iter_mut().enumerate() {
151//! // Enable every 4th trig.
152//! // Set retrig on.
153//! if i % 4 == 0 {
154//! trig.set_trig_enable(true);
155//! trig.set_retrig(true);
156//! }
157//! }
158//! }
159//!
160//! // Send the updated pattern to the device if you like
161//! conn_out
162//! .lock()
163//! .unwrap()
164//! .send(&rytm.work_buffer().pattern().as_sysex().unwrap())
165//! .unwrap();
166//! }
167//! Err(err) => {
168//! println!("Error: {:?}", err);
169//! }
170//! }
171//! }
172//! Err(err) => {
173//! println!("Error: {:?}", err);
174//! }
175//! }
176//! }
177//! ```
178//!
179//! ## Remarks
180//!
181//! The people mentioned here are major contributors to the reverse engineering effort and I would like to thank them for their work.
182//! This crate would not be possible in this form and time frame without their work.
183//!
184//! ### bsp2
185//!
186//! The maintainer of [libanalogrytm](https://github.com/bsp2/libanalogrytm) and the original author of the reverse engineering effort. He is the one who started the reverse engineering effort and provided the initial `C` library which is the foundation of `rytm-rs`.
187//!
188//! - <https://github.com/bsp2>
189//!
190//! ### mekohler
191//!
192//! Author of the [Collider](https://www.elektronauts.com/t/collider-for-the-ipad/27479) app which is available for iPad in the app store.
193//! Another contributor to the reverse engineering effort.
194//!
195//! - <https://marcoskohler.com/>
196//! - <https://github.com/mekohler>
197//! - <https://www.elektronauts.com/u/mekohler/summary>
198//!
199//! ### void
200//!
201//! Author of the [STROM](https://apps.apple.com/us/app/strom/id907044543) app which is available for iPad in the app store.
202//! Another contributor to the reverse engineering effort.
203//!
204//! - <https://www.elektronauts.com/u/void/summary>
205//! - <https://soundcloud.com/jakob-penca>
206//!
207//!
208//! Many thanks to [Başak Ünal](https://basakunal.design) for the logo.
209
210pub(crate) mod defaults;
211pub mod error;
212pub mod object;
213pub mod prelude;
214pub mod query;
215pub(crate) mod sysex;
216pub(crate) mod util;
217
218use self::error::RytmError;
219use crate::error::ParameterError;
220use defaults::*;
221use error::SysexConversionError;
222use object::{
223 global::Global,
224 kit::Kit,
225 pattern::Pattern,
226 settings::Settings,
227 sound::{Sound, SoundType},
228};
229use rytm_sys::{ar_global_t, ar_kit_t, ar_pattern_t, ar_settings_t, ar_sound_t};
230use serde::{Deserialize, Serialize};
231use sysex::decode_sysex_response_to_raw;
232pub use sysex::{AnySysexType, SysexCompatible, SysexType};
233
234/// [`RytmProject`] represents the state of the analog rytm.
235///
236/// It contains all structures scoped to an Analog Rytm MKII FW 1.70 project.
237#[derive(Clone, Debug, Serialize, Deserialize)]
238pub struct RytmProject {
239 work_buffer: RytmProjectWorkBuffer,
240 patterns: Vec<Pattern>,
241 pool_sounds: Vec<Sound>,
242 kits: Vec<Kit>,
243 globals: Vec<Global>,
244 // TODO: Songs (16)
245 settings: Settings,
246
247 pub(crate) last_queried_pattern_index: Option<usize>,
248 pub(crate) last_queried_kit_index: Option<usize>,
249 pub(crate) last_queried_work_buffer_pattern_index: Option<usize>,
250 pub(crate) last_queried_work_buffer_kit_index: Option<usize>,
251}
252
253impl RytmProject {
254 /// Try to construct a project from a JSON string.
255 ///
256 /// # Errors
257 /// - Project or JSON might be corrupted.
258 pub fn try_from_str(project_content: &str) -> Result<Self, RytmError> {
259 let project = serde_json::from_str(project_content)?;
260 Ok(project)
261 }
262
263 /// Try to convert the project to a JSON string.
264 ///
265 /// # Errors
266 /// - Project or JSON might be corrupted.
267 pub fn try_to_string(&self) -> Result<String, RytmError> {
268 let project_content = serde_json::to_string(self)?;
269 Ok(project_content)
270 }
271
272 pub fn set_device_id(&mut self, device_id: u8) {
273 // wb
274 self.work_buffer_mut()
275 .pattern_mut()
276 .set_device_id(device_id);
277 self.work_buffer_mut().kit_mut().set_device_id(device_id);
278 for sound in self.work_buffer_mut().sounds_mut().iter_mut() {
279 sound.set_device_id(device_id);
280 }
281 self.work_buffer_mut().global_mut().set_device_id(device_id);
282
283 // Normal
284 for pattern in self.patterns_mut().iter_mut() {
285 pattern.set_device_id(device_id);
286 }
287
288 for kit in self.kits_mut().iter_mut() {
289 kit.set_device_id(device_id);
290 }
291
292 for sound in self.pool_sounds_mut().iter_mut() {
293 sound.set_device_id(device_id);
294 }
295
296 for global in self.globals_mut().iter_mut() {
297 global.set_device_id(device_id);
298 }
299
300 self.settings_mut().set_device_id(device_id);
301 }
302
303 #[allow(clippy::missing_errors_doc)]
304 pub fn try_default() -> Result<Self, RytmError> {
305 Self::try_default_with_device_id(0)
306 }
307
308 #[allow(clippy::missing_errors_doc)]
309 pub fn try_default_with_device_id(device_id: u8) -> Result<Self, RytmError> {
310 let mut patterns = Vec::with_capacity(PATTERN_MAX_COUNT);
311 patterns.reserve_exact(PATTERN_MAX_COUNT);
312 let mut kits = Vec::with_capacity(KIT_MAX_COUNT);
313 kits.reserve_exact(KIT_MAX_COUNT);
314
315 // PATTERN_MAX_COUNT == KIT_MAX_COUNT is true.
316 for i in 0..PATTERN_MAX_COUNT {
317 let pattern = Pattern::try_default_with_device_id(i, device_id)?;
318 let mut kit = Kit::try_default_with_device_id(i, device_id)?;
319 kit.link_parameter_lock_pool(&pattern.parameter_lock_pool)?;
320 patterns.push(pattern);
321 kits.push(kit);
322 }
323
324 // TODO: ALSO FOR THE TRACK AND TRIG TYPES!
325
326 Ok(Self {
327 work_buffer: RytmProjectWorkBuffer::try_default_with_device_id(device_id)?,
328 patterns,
329 pool_sounds: default_pool_sounds_with_device_id(device_id),
330 kits,
331 globals: default_globals_with_device_id(device_id),
332 settings: Settings::try_default_with_device_id(device_id)?,
333
334 last_queried_pattern_index: None,
335 last_queried_kit_index: None,
336 last_queried_work_buffer_pattern_index: None,
337 last_queried_work_buffer_kit_index: None,
338 })
339 }
340
341 #[allow(clippy::too_many_lines)]
342 /// Updates the Rytm struct from a sysex response.
343 ///
344 /// All encoding/decoding is done in [`RytmProject`], so this is the only method that needs to be called to update the struct when a sysex response is received.
345 ///
346 /// # Important
347 ///
348 /// This method will act as a no-op if the given slice does not contain a valid sysex message.
349 /// The check is performed by checking the first and last byte of the slice.
350 /// This behaviour is preferred to returning an error, as it is expected to be called in a midi callback which receives not just sysex messages.
351 ///
352 /// # Errors
353 ///
354 /// This method will return an error
355 ///
356 /// - If the sysex message is invalid in the context of Rytm
357 /// - If the sysex message is valid, but the object type is not supported or implemented yet. Example: [`crate::error::RytmError::SysexConversionError::Unimplemented`] variant.
358 /// - If the sysex message is incomplete, this sometimes happens in the initial parts of the transmission and is a behaviour of Rytm. You may check for the error [`crate::error::RytmError::SysexConversionError::ShortRead`] and ignore it.
359 /// - If the sysex message is valid, but the size of the expected object does not match the size of the received object. This may happen if the firmware version of Rytm is different than the one this library supports which is currently FW 1.70 only. Never happened to me in practice but a cut transmission may also cause this in theory.
360 /// - All other [`crate::error::RytmError::SysexConversionError`] variants are possible which are inherited from [libanalogrytm](https://github.com/bsp2/libanalogrytm).
361 pub fn update_from_sysex_response(&mut self, response: &[u8]) -> Result<(), RytmError> {
362 if response.len() < 2 {
363 return Err(SysexConversionError::ShortRead.into());
364 }
365
366 // Ignore non-sysex messages.
367 if !(response[0] == 0xF0 && response[response.len() - 1] == 0xF7) {
368 return Ok(());
369 }
370
371 // Raw buffer and metadata about the sysex message.
372 let (mut raw, meta) = decode_sysex_response_to_raw(response)?;
373
374 // `& 0b0111_1111` is to get rid of the bit that indicates if the object is in the work buffer or not.
375 // `- 0x80` is to get the actual index of the object in the work buffer. Example, there could be 12 sounds in the work buffer.
376 match meta.object_type()? {
377 SysexType::Pattern => {
378 let raw_pattern: &ar_pattern_t =
379 unsafe { &*(raw.as_mut_ptr() as *const ar_pattern_t) };
380 let mut pattern = Pattern::try_from_raw(meta, raw_pattern)?;
381
382 // Once a pattern is retrieved it's parameter lock pool will be linked to the kit we assume that it uses.
383 // This doesn't mean the kit itself is updated, it can be as well out of sync from the device but it's the best we can do.
384 // It is the responsibility of the user to update the kit if it is out of sync.
385
386 let kit_number = pattern.kit_number();
387
388 if kit_number == 0xFF {
389 // When the kit is not set, we assume that the pattern uses the kit in the work buffer.
390 let work_buffer_pattern_kit_number = self.work_buffer().pattern().kit_number();
391
392 // We update the kit which has than number with the parameter lock pool of the pattern.
393 self.kits_mut()[work_buffer_pattern_kit_number]
394 .link_parameter_lock_pool(&pattern.parameter_lock_pool)?;
395
396 // We also update the work buffer kit if it is the same kit.
397 let work_buffer_kit_mut = self.work_buffer_mut().kit_mut();
398 if work_buffer_kit_mut.index() == work_buffer_pattern_kit_number {
399 work_buffer_kit_mut
400 .link_parameter_lock_pool(&pattern.parameter_lock_pool)?;
401 }
402 } else {
403 // When the kit is set then we directly update that kit.
404 self.kits_mut()[kit_number]
405 .link_parameter_lock_pool(&pattern.parameter_lock_pool)?;
406
407 // We also update the work buffer kit if it is the same kit.
408 let work_buffer_kit_mut = self.work_buffer_mut().kit_mut();
409 if work_buffer_kit_mut.index() == kit_number {
410 work_buffer_kit_mut
411 .link_parameter_lock_pool(&pattern.parameter_lock_pool)?;
412 }
413 }
414
415 if meta.is_targeting_work_buffer() {
416 let index = meta.get_normalized_object_index();
417 pattern.index = index;
418 self.work_buffer.pattern = pattern;
419
420 return Ok(());
421 }
422
423 let index = meta.get_normalized_object_index();
424 self.patterns[index] = pattern;
425 Ok(())
426 }
427
428 SysexType::Kit => {
429 let raw_kit: &ar_kit_t = unsafe { &*(raw.as_mut_ptr() as *const ar_kit_t) };
430 let mut kit = Kit::try_from_raw(meta, raw_kit)?;
431
432 // When a kit is received, we check all the existing patterns to see if any of them is linked to the kit index queried or not.
433 // Then we link the plock pool of those patterns to the updated kit.
434
435 for pattern in self.patterns() {
436 if pattern.kit_number() == kit.index() {
437 kit.link_parameter_lock_pool(&pattern.parameter_lock_pool)?;
438 }
439 }
440
441 if self.work_buffer().pattern().kit_number() == kit.index() {
442 kit.link_parameter_lock_pool(
443 &self.work_buffer().pattern().parameter_lock_pool,
444 )?;
445 }
446
447 if meta.is_targeting_work_buffer() {
448 let index = meta.get_normalized_object_index();
449 kit.index = index;
450 self.work_buffer.kit = kit;
451 return Ok(());
452 }
453
454 let index = meta.get_normalized_object_index();
455 self.kits[index] = kit;
456 Ok(())
457 }
458
459 SysexType::Sound => {
460 let raw_sound: &ar_sound_t = unsafe { &*(raw.as_mut_ptr() as *const ar_sound_t) };
461 let mut sound = Sound::try_from_raw(meta, raw_sound, None)?;
462
463 let index = meta.get_normalized_object_index();
464 match sound.sound_type() {
465 SoundType::Pool => {
466 // Pool sounds will not have a linked parameter lock pool.
467 // TODO: Would we need linking here?
468 self.pool_sounds[index] = sound;
469 }
470 SoundType::WorkBuffer => {
471 // Work buffer sounds though will be linked to the work buffer pattern's parameter lock pool.
472 sound.link_parameter_lock_pool(
473 &self.work_buffer().pattern().parameter_lock_pool,
474 )?;
475 self.work_buffer.sounds[index] = sound;
476 }
477 SoundType::KitQuery => {
478 unreachable!("A response from a sound query can not contain a sound which is part of a kit.")
479 }
480 };
481
482 Ok(())
483 }
484
485 SysexType::Global => {
486 let raw_global: &ar_global_t =
487 unsafe { &*(raw.as_mut_ptr() as *const ar_global_t) };
488 let global = Global::try_from_raw(meta, raw_global)?;
489
490 if meta.is_targeting_work_buffer() {
491 self.work_buffer.global = global;
492 return Ok(());
493 }
494
495 let index = (meta.obj_nr & 0b0111_1111) as usize;
496 self.globals[index] = global;
497 Ok(())
498 }
499
500 // There is only a single settings object for a project.
501 SysexType::Settings => {
502 let raw_settings: &ar_settings_t =
503 unsafe { &*(raw.as_mut_ptr() as *const ar_settings_t) };
504 let settings = Settings::try_from_raw(meta, raw_settings)?;
505 self.settings = settings;
506 Ok(())
507 }
508
509 // TODO: Implement Song
510 SysexType::Song => Err(SysexConversionError::Unimplemented("Song".to_owned()).into()),
511 }
512 }
513
514 /// Get all patterns.
515 ///
516 /// Total of 128 patterns.
517 pub fn patterns(&self) -> &[Pattern] {
518 &self.patterns
519 }
520
521 /// Get all kits.
522 ///
523 /// Total of 128 kits.
524 pub fn kits(&self) -> &[Kit] {
525 &self.kits
526 }
527
528 /// Get all sounds in the pool.
529 ///
530 /// Total of 128 sounds.
531 pub fn pool_sounds(&self) -> &[Sound] {
532 &self.pool_sounds
533 }
534
535 /// Get all global slots.
536 ///
537 /// Total of 4 global slots.
538 pub fn globals(&self) -> &[Global] {
539 &self.globals
540 }
541
542 /// Get the settings.
543 pub const fn settings(&self) -> &Settings {
544 &self.settings
545 }
546
547 /// Get all patterns mutably.
548 ///
549 /// Total of 128 patterns.
550 pub fn patterns_mut(&mut self) -> &mut [Pattern] {
551 &mut self.patterns
552 }
553
554 /// Get all kits mutably.
555 ///
556 /// Total of 128 kits.
557 pub fn kits_mut(&mut self) -> &mut [Kit] {
558 &mut self.kits
559 }
560
561 /// Get all sounds in the pool mutably.
562 ///
563 /// Total of 128 sounds.
564 pub fn pool_sounds_mut(&mut self) -> &mut [Sound] {
565 &mut self.pool_sounds
566 }
567
568 /// Get all global slots mutably.
569 ///
570 /// Total of 4 global slots.
571 pub fn globals_mut(&mut self) -> &mut [Global] {
572 &mut self.globals
573 }
574
575 /// Get the settings mutably.
576 pub fn settings_mut(&mut self) -> &mut Settings {
577 &mut self.settings
578 }
579
580 /// Get the work buffer structures.
581 pub const fn work_buffer(&self) -> &RytmProjectWorkBuffer {
582 &self.work_buffer
583 }
584
585 /// Get the work buffer structures mutably.
586 pub fn work_buffer_mut(&mut self) -> &mut RytmProjectWorkBuffer {
587 &mut self.work_buffer
588 }
589}
590
591/// [`RytmProjectWorkBuffer`] represents the state of the analog rytm work buffer.
592///
593/// It contains all structures scoped to an Analog Rytm MKII FW 1.70 project work buffer.
594#[derive(Clone, Debug, Serialize, Deserialize)]
595pub struct RytmProjectWorkBuffer {
596 pattern: Pattern,
597 kit: Kit,
598 sounds: Vec<Sound>,
599 global: Global,
600 // TODO: Work buffer song
601}
602
603impl RytmProjectWorkBuffer {
604 #[allow(clippy::missing_errors_doc)]
605 pub fn try_default() -> Result<Self, RytmError> {
606 Self::try_default_with_device_id(0)
607 }
608
609 #[allow(clippy::missing_errors_doc)]
610 pub fn try_default_with_device_id(device_id: u8) -> Result<Self, RytmError> {
611 let pattern = Pattern::work_buffer_default_with_device_id(device_id);
612 let mut kit = Kit::work_buffer_default_with_device_id(device_id);
613 kit.link_parameter_lock_pool(&pattern.parameter_lock_pool)?;
614
615 // TODO: What are work buffer sounds.. hmm..
616 // Should we link their parameter lock pool also?
617
618 Ok(Self {
619 pattern,
620 kit,
621 sounds: default_work_buffer_sounds_with_device_id(device_id),
622 global: Global::work_buffer_default_with_device_id(device_id),
623 })
624 }
625
626 /// Get the pattern in the work buffer.
627 pub const fn pattern(&self) -> &Pattern {
628 &self.pattern
629 }
630
631 /// Get the kit in the work buffer.
632 pub const fn kit(&self) -> &Kit {
633 &self.kit
634 }
635
636 /// Get the sounds in the work buffer.
637 ///
638 /// Total of 12 sounds for 12 tracks.
639 pub fn sounds(&self) -> &[Sound] {
640 &self.sounds
641 }
642
643 /// Get the global in the work buffer.
644 pub const fn global(&self) -> &Global {
645 &self.global
646 }
647
648 /// Get the pattern in the work buffer mutably.
649 pub fn pattern_mut(&mut self) -> &mut Pattern {
650 &mut self.pattern
651 }
652
653 /// Get the kit in the work buffer mutably.
654 pub fn kit_mut(&mut self) -> &mut Kit {
655 &mut self.kit
656 }
657
658 /// Get the sounds in the work buffer mutably.
659 ///
660 /// Total of 12 sounds for 12 tracks.
661 pub fn sounds_mut(&mut self) -> &mut [Sound] {
662 &mut self.sounds
663 }
664
665 /// Get the global in the work buffer mutably.
666 pub fn global_mut(&mut self) -> &mut Global {
667 &mut self.global
668 }
669}