├── .cargo └── config ├── .gitignore ├── Cargo.toml ├── README.md ├── examples ├── auxiliary_task.rs ├── digital.rs ├── hello.rs └── sample.rs ├── rustfmt.toml └── src ├── error.rs └── lib.rs /.cargo/config: -------------------------------------------------------------------------------- 1 | [target.armv7-unknown-linux-gnueabihf] 2 | linker = "arm-none-linux-gnueabihf-gcc" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .vscode 3 | /target 4 | **/*.rs.bk 5 | Cargo.lock 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bela" 3 | version = "0.1.0" 4 | authors = ["Andrew C. Smith "] 5 | 6 | [dependencies] 7 | 8 | [dev-dependencies] 9 | sample = { package = "dasp", version = "0.11.0", features = [ "signal", "slice" ] } 10 | 11 | [dependencies.bela-sys] 12 | git = "https://github.com/andrewcsmith/bela-sys.git" 13 | 14 | [features] 15 | static = [ "bela-sys/static" ] 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bela-rs 2 | 3 | Safe Rust wrapper for the Bela microcontroller API. 4 | 5 | ## Setup 6 | 7 | For this, you will need a [Bela microcontroller](http://bela.io/) with 8 | compiled libbela.so libraries, the [Rust programming 9 | language](https://rustup.rs/), and the relevant ABI files. 10 | 11 | You can download the [arm-linux-gnueabihf for all platforms at the linaro 12 | site](https://releases.linaro.org/components/toolchain/binaries/latest-5/arm-linux-gnueabihf/), 13 | or likely through your package manager of choice. 14 | 15 | ### Dependencies 16 | 17 | It's possible to link this crate against either [padenot's bela-sys crate](https://github.com/padenot/bela-sys) or [andrewcsmith/bela-sys](https://github.com/andrewcsmith/bela-sys). The difference between the two is mainly in that padenot uses a vendored version of the bela.rs and header files, while the andrewcsmith version generates its own headers using `bindgen` and a local copy of all the relevant header files. This is significantly more complicated to set up. 18 | 19 | padenot/bela-sys is tested on OSX and Linux, while andrewcsmith/bela-sys is tested on Windows 10 Professional. 20 | 21 | ## Design 22 | 23 | bela-rs aims to be a safe wrapper around the core Bela functionality, but 24 | there are a few opinionated design choices to take advantage of specific 25 | capabilities of Rust. The first of these is *runtime guarantees* about the 26 | `userData void*` passed to every call of `render`, `setup`, or `cleanup`. The 27 | global function calls to the Bela are managed by a `Bela` struct. 28 | 29 | Second, the lifecycle functions (`render`, `setup`, `cleanup`) are not 30 | globally defined functions, but rather are defined as closures that are 31 | passed to every call in the lifecycle. This allows the programmer to use 32 | features in closures such as capturing outside variables, and mutating state 33 | on each call. 34 | 35 | Third, the auxiliary tasks are also closures, and are separated into callback 36 | functions and arguments to be passed to the first call. 37 | 38 | ## Example 39 | 40 | ```rust 41 | // Short extract of code from examples/hello.rs, where phasor is some arbitrary 42 | // data that is passed to each render, setup, and cleanup call. 43 | let user_data = AppData::new(phasor, &mut render, Some(&mut setup), Some(&mut cleanup)); 44 | let mut settings = InitSettings::default(); 45 | // The .run call blocks until interrupted, returning a Result 46 | Bela::new(user_data).run(&mut settings) 47 | ``` 48 | -------------------------------------------------------------------------------- /examples/auxiliary_task.rs: -------------------------------------------------------------------------------- 1 | //! Produces a sine wave while printing "this is a string" repeatedly, 2 | //! appending "LOL" to every iteration. 3 | //! 4 | //! There's an example here for both the stack-allocated and a Boxed closure. 5 | //! 6 | extern crate bela; 7 | extern crate sample; 8 | 9 | use bela::*; 10 | 11 | struct MyData { 12 | frame_index: usize, 13 | tasks: Vec, 14 | } 15 | 16 | type BelaApp<'a> = Bela>; 17 | 18 | fn main() { 19 | go().unwrap(); 20 | } 21 | 22 | fn go() -> Result<(), error::Error> { 23 | let mut setup = |_context: &mut Context, user_data: &mut MyData| -> Result<(), error::Error> { 24 | println!("Setting up"); 25 | let print_task = Box::new(|| { 26 | println!("this is a string"); 27 | }); 28 | 29 | let another_print_task = Box::new(|| { 30 | println!("this is another string"); 31 | }); 32 | 33 | user_data.tasks.push(BelaApp::create_auxiliary_task( 34 | print_task, 35 | 10, 36 | &std::ffi::CString::new("printing_stuff").unwrap(), 37 | )); 38 | user_data.tasks.push(BelaApp::create_auxiliary_task( 39 | another_print_task, 40 | 10, 41 | &std::ffi::CStr::from_bytes_with_nul(b"printing_more_stuff\0").unwrap(), 42 | )); 43 | Ok(()) 44 | }; 45 | 46 | let mut cleanup = |_context: &mut Context, _user_data: &mut MyData| { 47 | println!("Cleaning up"); 48 | }; 49 | 50 | let mut render = |_context: &mut Context, user_data: &mut MyData| { 51 | if user_data.frame_index % 1024 == 0 { 52 | for task in user_data.tasks.iter() { 53 | BelaApp::schedule_auxiliary_task(task).unwrap(); 54 | } 55 | } 56 | 57 | user_data.frame_index = user_data.frame_index.wrapping_add(1); 58 | }; 59 | 60 | let my_data = MyData { 61 | tasks: Vec::new(), 62 | frame_index: 0, 63 | }; 64 | 65 | let user_data = AppData::new(my_data, &mut render, Some(&mut setup), Some(&mut cleanup)); 66 | 67 | let mut settings = InitSettings::default(); 68 | Bela::new(user_data).run(&mut settings) 69 | } 70 | -------------------------------------------------------------------------------- /examples/digital.rs: -------------------------------------------------------------------------------- 1 | extern crate bela; 2 | 3 | use bela::*; 4 | 5 | struct State { 6 | idx: usize, 7 | } 8 | 9 | fn main() { 10 | go().unwrap(); 11 | } 12 | 13 | fn go() -> Result<(), error::Error> { 14 | let mut setup = |context: &mut Context, _user_data: &mut State| -> Result<(), error::Error> { 15 | println!("Setting up"); 16 | context.pin_mode(0, 0, DigitalDirection::OUTPUT); 17 | Ok(()) 18 | }; 19 | 20 | let mut cleanup = |_context: &mut Context, _user_data: &mut State| { 21 | println!("Cleaning up"); 22 | }; 23 | 24 | // Generates impulses on the first digital port every 100ms for 10ms 25 | let mut render = |context: &mut Context, state: &mut State| { 26 | let tenms_in_frames = (context.digital_sample_rate() / 100.) as usize; 27 | let hundreadms_in_frames = (tenms_in_frames * 10) as usize; 28 | for f in 0..context.digital_frames() { 29 | let v = state.idx < tenms_in_frames; 30 | context.digital_write_once(f, 0, v); 31 | state.idx += 1; 32 | if state.idx > hundreadms_in_frames { 33 | state.idx = 0; 34 | } 35 | } 36 | }; 37 | 38 | let state = State { idx: 0 }; 39 | 40 | let user_data = AppData::new(state, &mut render, Some(&mut setup), Some(&mut cleanup)); 41 | 42 | let mut bela_app = Bela::new(user_data); 43 | let mut settings = InitSettings::default(); 44 | bela_app.run(&mut settings) 45 | } 46 | -------------------------------------------------------------------------------- /examples/hello.rs: -------------------------------------------------------------------------------- 1 | extern crate bela; 2 | 3 | use bela::*; 4 | 5 | struct Phasor { 6 | idx: usize, 7 | } 8 | 9 | fn main() { 10 | go().unwrap(); 11 | } 12 | 13 | fn go() -> Result<(), error::Error> { 14 | let mut setup = |_context: &mut Context, _user_data: &mut Phasor| -> Result<(), error::Error> { 15 | println!("Setting up"); 16 | Ok(()) 17 | }; 18 | 19 | let mut cleanup = |_context: &mut Context, _user_data: &mut Phasor| { 20 | println!("Cleaning up"); 21 | }; 22 | 23 | // Generates a non-bandlimited sawtooth at 110Hz. 24 | let mut render = |context: &mut Context, phasor: &mut Phasor| { 25 | for (_, samp) in context.audio_out().iter_mut().enumerate() { 26 | let gain = 0.5; 27 | *samp = 2. * (phasor.idx as f32 * 110. / 44100.) - 1.; 28 | *samp *= gain; 29 | phasor.idx += 1; 30 | if phasor.idx as f32 > 44100. / 110. { 31 | phasor.idx = 0; 32 | } 33 | } 34 | }; 35 | 36 | let phasor = Phasor { idx: 0 }; 37 | 38 | let user_data = AppData::new(phasor, &mut render, Some(&mut setup), Some(&mut cleanup)); 39 | 40 | let mut bela_app = Bela::new(user_data); 41 | let mut settings = InitSettings::default(); 42 | bela_app.run(&mut settings) 43 | } 44 | -------------------------------------------------------------------------------- /examples/sample.rs: -------------------------------------------------------------------------------- 1 | extern crate bela; 2 | extern crate sample; 3 | 4 | use bela::*; 5 | use sample::Signal; 6 | use std::{thread, time}; 7 | 8 | fn main() { 9 | go().unwrap(); 10 | } 11 | 12 | fn go() -> Result<(), error::Error> { 13 | let mut setup = |_context: &mut Context, 14 | _user_data: &mut Option>>| 15 | -> Result<(), error::Error> { 16 | println!("Setting up"); 17 | Ok(()) 18 | }; 19 | 20 | let mut cleanup = 21 | |_context: &mut Context, _user_data: &mut Option>>| { 22 | println!("Cleaning up"); 23 | }; 24 | 25 | // Generates a sine wave with the period of whatever the audio frame 26 | // size is. 27 | let mut render = |context: &mut Context, synth: &mut Option>>| { 28 | let audio_out_channels = context.audio_out_channels(); 29 | assert_eq!(audio_out_channels, 2); 30 | let audio_out = context.audio_out(); 31 | let audio_out_frames: &mut [[f32; 2]] = 32 | sample::slice::to_frame_slice_mut(audio_out).unwrap(); 33 | 34 | for frame in audio_out_frames.iter_mut() { 35 | for samp in frame.iter_mut() { 36 | let val = synth.as_mut().unwrap().next(); 37 | *samp = val as f32; 38 | } 39 | } 40 | }; 41 | 42 | let sig = sample::signal::rate(44_100.0).const_hz(440.0).sine(); 43 | 44 | let synth: Option>> = Some(Box::new(sig)); 45 | 46 | let user_data = AppData::new(synth, &mut render, Some(&mut setup), Some(&mut cleanup)); 47 | 48 | let mut bela_app = Bela::new(user_data); 49 | let mut settings = InitSettings::default(); 50 | bela_app.init_audio(&mut settings)?; 51 | bela_app.start_audio()?; 52 | 53 | while !bela_app.should_stop() { 54 | thread::sleep(time::Duration::new(1, 0)); 55 | } 56 | 57 | bela_app.stop_audio(); 58 | bela_app.cleanup_audio(); 59 | 60 | Ok(()) 61 | } 62 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewcsmith/bela-rs/4ed8ad8d39d9fa7f8209dcfeba7ba9baf1b2d52f/rustfmt.toml -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::{error, fmt}; 2 | 3 | #[derive(Copy, Clone, Debug)] 4 | pub enum Error { 5 | Init, 6 | Start, 7 | Stop, 8 | Cleanup, 9 | Task, 10 | } 11 | 12 | impl fmt::Display for Error { 13 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 14 | write!(f, "Error: {:?}.", self) 15 | } 16 | } 17 | 18 | impl error::Error for Error { 19 | fn description(&self) -> &str { 20 | match self { 21 | Error::Init => "Bela_initAudio error", 22 | Error::Start => "Bela_startAudio error", 23 | Error::Stop => "Bela_stopAudio error", 24 | Error::Cleanup => "Bela_cleanupAudio error", 25 | Error::Task => "Bela_scheduleAuxiliaryTask error", 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate bela_sys; 2 | 3 | use bela_sys::{BelaContext, BelaInitSettings}; 4 | use std::convert::TryInto; 5 | use std::{mem, slice}; 6 | use std::{thread, time}; 7 | 8 | pub mod error; 9 | 10 | pub enum DigitalDirection { 11 | INPUT, 12 | OUTPUT, 13 | } 14 | 15 | #[repr(C)] 16 | pub enum BelaHw { 17 | NoHw = bela_sys::BelaHw_BelaHw_NoHw as isize, 18 | Bela = bela_sys::BelaHw_BelaHw_Bela as isize, 19 | BelaMini = bela_sys::BelaHw_BelaHw_BelaMini as isize, 20 | Salt = bela_sys::BelaHw_BelaHw_Salt as isize, 21 | CtagFace = bela_sys::BelaHw_BelaHw_CtagFace as isize, 22 | CtagBeast = bela_sys::BelaHw_BelaHw_CtagBeast as isize, 23 | CtagFaceBela = bela_sys::BelaHw_BelaHw_CtagFaceBela as isize, 24 | CtagBeastBela = bela_sys::BelaHw_BelaHw_CtagBeastBela as isize, 25 | } 26 | 27 | impl BelaHw { 28 | fn from_i32(v: i32) -> Option { 29 | match v { 30 | bela_sys::BelaHw_BelaHw_NoHw => Some(BelaHw::NoHw), 31 | bela_sys::BelaHw_BelaHw_Bela => Some(BelaHw::Bela), 32 | bela_sys::BelaHw_BelaHw_BelaMini => Some(BelaHw::BelaMini), 33 | bela_sys::BelaHw_BelaHw_Salt => Some(BelaHw::Salt), 34 | bela_sys::BelaHw_BelaHw_CtagFace => Some(BelaHw::CtagFace), 35 | bela_sys::BelaHw_BelaHw_CtagBeast => Some(BelaHw::CtagBeast), 36 | bela_sys::BelaHw_BelaHw_CtagFaceBela => Some(BelaHw::CtagFaceBela), 37 | bela_sys::BelaHw_BelaHw_CtagBeastBela => Some(BelaHw::CtagBeastBela), 38 | _ => None, 39 | } 40 | } 41 | } 42 | 43 | /// The `Bela` struct is essentially built to ensure that the type parameter 44 | /// `` is consistent across all invocations of the setup, render, and cleanup 45 | /// functions. This is because `` is the `UserData` of the original Bela 46 | /// library -- we want to ensure that the `UserData` we are initializing with 47 | /// is the exact same as the one we are attempting to access with each function. 48 | /// 49 | /// TODO: Bela needs to also wrap the various setup, render, and cleanup 50 | /// functions and keep them in the same struct. 51 | /// 52 | /// Called when audio is initialized. 53 | /// 54 | /// ```rust 55 | /// pub type SetupFn = FnOnce(&mut Context, T) -> bool; 56 | /// ``` 57 | /// 58 | /// Called on every frame. 59 | /// 60 | /// ```rust 61 | /// pub type RenderFn = Fn(&mut Context, T); 62 | /// ``` 63 | /// 64 | /// Called when audio is stopped. 65 | /// 66 | /// ```rust 67 | /// pub type CleanupFn = FnOnce(&mut Context, T) -> bool; 68 | /// ``` 69 | pub struct Bela { 70 | initialized: bool, 71 | user_data: T, 72 | } 73 | 74 | extern "C" fn render_trampoline<'a, T>( 75 | context: *mut BelaContext, 76 | user_data: *mut std::os::raw::c_void, 77 | ) where 78 | T: UserData<'a> + 'a, 79 | { 80 | let mut context = Context::new(context); 81 | let user_data = unsafe { &mut *(user_data as *mut T) }; 82 | user_data.render_fn(&mut context); 83 | } 84 | 85 | extern "C" fn setup_trampoline<'a, T>( 86 | context: *mut BelaContext, 87 | user_data: *mut std::os::raw::c_void, 88 | ) -> bool 89 | where 90 | T: UserData<'a> + 'a, 91 | { 92 | let mut context = Context::new(context); 93 | let user_data = unsafe { &mut *(user_data as *mut T) }; 94 | user_data.setup_fn(&mut context).is_ok() 95 | } 96 | 97 | extern "C" fn cleanup_trampoline<'a, T>( 98 | context: *mut BelaContext, 99 | user_data: *mut std::os::raw::c_void, 100 | ) where 101 | T: UserData<'a> + 'a, 102 | { 103 | let mut context = Context::new(context); 104 | let user_data = unsafe { &mut *(user_data as *mut T) }; 105 | user_data.cleanup_fn(&mut context); 106 | } 107 | 108 | pub struct CreatedTask(bela_sys::AuxiliaryTask); 109 | 110 | impl<'a, T: UserData<'a> + 'a> Bela { 111 | pub fn new(user_data: T) -> Self { 112 | Bela { 113 | initialized: false, 114 | user_data, 115 | } 116 | } 117 | 118 | pub fn run(&mut self, settings: &mut InitSettings) -> Result<(), error::Error> { 119 | self.init_audio(settings)?; 120 | self.start_audio()?; 121 | while !self.should_stop() { 122 | thread::sleep(time::Duration::new(0, 1000)); 123 | } 124 | 125 | self.stop_audio(); 126 | self.cleanup_audio(); 127 | 128 | Ok(()) 129 | } 130 | 131 | pub fn set_render(&mut self, func: &'a mut F) 132 | where 133 | F: FnMut(&mut Context, T::Data), 134 | for<'r, 's> F: FnMut(&'r mut Context, &'s mut T::Data), 135 | { 136 | self.user_data.set_render_fn(func); 137 | } 138 | 139 | pub fn set_setup(&mut self, func: &'a mut F) 140 | where 141 | F: FnMut(&mut Context, T::Data) -> bool, 142 | for<'r, 's> F: FnMut(&'r mut Context, &'s mut T::Data) -> Result<(), error::Error>, 143 | { 144 | self.user_data.set_setup_fn(Some(func)); 145 | } 146 | 147 | pub fn set_cleanup(&mut self, func: &'a mut F) 148 | where 149 | F: FnMut(&mut Context, T::Data), 150 | for<'r, 's> F: FnMut(&'r mut Context, &'s mut T::Data), 151 | { 152 | self.user_data.set_cleanup_fn(Some(func)); 153 | } 154 | 155 | pub fn init_audio(&mut self, settings: &mut InitSettings) -> Result<(), error::Error> { 156 | settings.settings.setup = Some(setup_trampoline::); 157 | settings.settings.render = Some(render_trampoline::); 158 | settings.settings.cleanup = Some(cleanup_trampoline::); 159 | let out = unsafe { 160 | bela_sys::Bela_initAudio( 161 | settings.settings_ptr(), 162 | &mut self.user_data as *mut _ as *mut _, 163 | ) 164 | }; 165 | 166 | match out { 167 | 0 => { 168 | self.initialized = true; 169 | Ok(()) 170 | } 171 | _ => Err(error::Error::Init), 172 | } 173 | } 174 | 175 | pub fn start_audio(&self) -> Result<(), error::Error> { 176 | if !self.initialized { 177 | return Err(error::Error::Start); 178 | } 179 | 180 | let out = unsafe { bela_sys::Bela_startAudio() }; 181 | 182 | match out { 183 | 0 => Ok(()), 184 | _ => Err(error::Error::Start), 185 | } 186 | } 187 | 188 | pub fn should_stop(&self) -> bool { 189 | unsafe { bela_sys::Bela_stopRequested() != 0 } 190 | } 191 | 192 | /// Create an auxiliary task that runs on a lower-priority thread 193 | /// `name` must be globally unique across all Xenomai processes! 194 | pub fn create_auxiliary_task( 195 | task: Box, 196 | priority: i32, 197 | name: &std::ffi::CStr, 198 | ) -> CreatedTask 199 | where 200 | Auxiliary: FnMut() + Send + 'static, 201 | { 202 | // TODO: Bela API does not currently offer an API to stop and unregister a task, 203 | // so we can only leak the task. Otherwise, we could `Box::into_raw` here, store the 204 | // raw pointer in `CreatedTask` and drop it after unregistering & joining the thread 205 | // using `Box::from_raw`. 206 | let task_ptr = Box::leak(task) as *mut _ as *mut _; 207 | 208 | extern "C" fn auxiliary_task_trampoline(aux_ptr: *mut std::os::raw::c_void) 209 | where 210 | Auxiliary: FnMut() + Send + 'static, 211 | { 212 | let task_ptr = unsafe { &mut *(aux_ptr as *mut Auxiliary) }; 213 | task_ptr(); 214 | } 215 | 216 | let aux_task = unsafe { 217 | bela_sys::Bela_createAuxiliaryTask( 218 | Some(auxiliary_task_trampoline::), 219 | priority, 220 | name.as_ptr(), 221 | task_ptr, 222 | ) 223 | }; 224 | 225 | CreatedTask(aux_task) 226 | } 227 | 228 | pub fn schedule_auxiliary_task(task: &CreatedTask) -> Result<(), error::Error> { 229 | let res = unsafe { bela_sys::Bela_scheduleAuxiliaryTask(task.0) }; 230 | 231 | match res { 232 | 0 => Ok(()), 233 | _ => Err(error::Error::Task), 234 | } 235 | } 236 | 237 | pub fn stop_audio(&self) { 238 | unsafe { 239 | bela_sys::Bela_stopAudio(); 240 | } 241 | } 242 | 243 | pub fn cleanup_audio(&self) { 244 | unsafe { 245 | bela_sys::Bela_cleanupAudio(); 246 | } 247 | } 248 | } 249 | 250 | /// Wraps `BelaContext` 251 | pub struct Context { 252 | context: *mut BelaContext, 253 | } 254 | 255 | impl Context { 256 | pub fn new(context: *mut BelaContext) -> Context { 257 | Context { context } 258 | } 259 | 260 | pub fn context_mut_ptr(&mut self) -> *mut BelaContext { 261 | let ptr: *mut BelaContext = self.context; 262 | ptr 263 | } 264 | 265 | pub fn context_ptr(&self) -> *const BelaContext { 266 | let ptr: *mut BelaContext = self.context; 267 | ptr 268 | } 269 | 270 | /// Access the audio output slice 271 | /// 272 | /// Mutably borrows self so that (hopefully) we do not have multiple mutable 273 | /// pointers to the audio buffer available simultaneously. 274 | pub fn audio_out(&mut self) -> &mut [f32] { 275 | unsafe { 276 | let context = self.context_mut_ptr(); 277 | let n_frames = (*context).audioFrames; 278 | let n_channels = (*context).audioOutChannels; 279 | let audio_out_ptr = (*context).audioOut; 280 | slice::from_raw_parts_mut(audio_out_ptr, (n_frames * n_channels) as usize) 281 | } 282 | } 283 | 284 | /// Access the audio input slice 285 | /// 286 | /// Immutably borrows self and returns an immutable buffer of audio in data. 287 | pub fn audio_in(&self) -> &[f32] { 288 | unsafe { 289 | let context = self.context_ptr(); 290 | let n_frames = (*context).audioFrames; 291 | let n_channels = (*context).audioInChannels; 292 | let audio_in_ptr = (*context).audioIn; 293 | slice::from_raw_parts(audio_in_ptr, (n_frames * n_channels) as usize) 294 | } 295 | } 296 | 297 | /// Access the digital input/output slice immutably 298 | pub fn digital(&self) -> &[u32] { 299 | unsafe { 300 | let context = self.context_ptr(); 301 | let n_frames = (*context).digitalFrames; 302 | let n_channels = (*context).digitalChannels; 303 | let digital_ptr = (*context).digital; 304 | slice::from_raw_parts(digital_ptr, (n_frames * n_channels) as usize) 305 | } 306 | } 307 | 308 | /// Access the digital input/output slice mutably 309 | /// 310 | /// Mutably borrows self so that (hopefully) we do not have multiple mutable 311 | /// pointers to the digital buffer available simultaneously. 312 | pub fn digital_mut(&mut self) -> &mut [u32] { 313 | unsafe { 314 | let context = self.context_ptr(); 315 | let n_frames = (*context).digitalFrames; 316 | let n_channels = (*context).digitalChannels; 317 | let digital_ptr = (*context).digital; 318 | slice::from_raw_parts_mut(digital_ptr, (n_frames * n_channels) as usize) 319 | } 320 | } 321 | 322 | /// Access the analog output slice 323 | /// 324 | /// Mutably borrows self so that (hopefully) we do not have multiple mutable 325 | /// pointers to the analog buffer available simultaneously. 326 | pub fn analog_out(&mut self) -> &mut [f32] { 327 | unsafe { 328 | let context = self.context_ptr(); 329 | let n_frames = (*context).analogFrames; 330 | let n_channels = (*context).analogOutChannels; 331 | let analog_out_ptr = (*context).analogOut; 332 | slice::from_raw_parts_mut(analog_out_ptr, (n_frames * n_channels) as usize) 333 | } 334 | } 335 | 336 | /// Access the analog input slice 337 | pub fn analog_in(&self) -> &[f32] { 338 | unsafe { 339 | let n_frames = (*self.context).analogFrames; 340 | let n_channels = (*self.context).analogInChannels; 341 | let analog_in_ptr = (*self.context).analogIn; 342 | slice::from_raw_parts(analog_in_ptr, (n_frames * n_channels) as usize) 343 | } 344 | } 345 | 346 | pub fn audio_frames(&self) -> usize { 347 | unsafe { (*self.context).audioFrames as usize } 348 | } 349 | 350 | pub fn audio_in_channels(&self) -> usize { 351 | unsafe { (*self.context).audioInChannels as usize } 352 | } 353 | 354 | pub fn audio_out_channels(&self) -> usize { 355 | unsafe { (*self.context).audioOutChannels as usize } 356 | } 357 | 358 | pub fn audio_sample_rate(&self) -> f32 { 359 | unsafe { (*self.context).audioSampleRate } 360 | } 361 | 362 | pub fn analog_frames(&self) -> usize { 363 | unsafe { (*self.context).analogFrames as usize } 364 | } 365 | 366 | pub fn analog_in_channels(&self) -> usize { 367 | unsafe { (*self.context).analogInChannels as usize } 368 | } 369 | 370 | pub fn analog_out_channels(&self) -> usize { 371 | unsafe { (*self.context).analogOutChannels as usize } 372 | } 373 | 374 | pub fn analog_sample_rate(&self) -> f32 { 375 | unsafe { (*self.context).analogSampleRate } 376 | } 377 | 378 | pub fn digital_frames(&self) -> usize { 379 | unsafe { (*self.context).digitalFrames as usize } 380 | } 381 | 382 | pub fn digital_channels(&self) -> usize { 383 | unsafe { (*self.context).digitalChannels as usize } 384 | } 385 | 386 | pub fn digital_sample_rate(&self) -> f32 { 387 | unsafe { (*self.context).digitalSampleRate } 388 | } 389 | 390 | pub fn audio_frames_elapsed(&self) -> usize { 391 | unsafe { (*self.context).audioFramesElapsed as usize } 392 | } 393 | 394 | pub fn multiplexer_channels(&self) -> usize { 395 | unsafe { (*self.context).multiplexerChannels as usize } 396 | } 397 | 398 | pub fn multiplexer_starting_channels(&self) -> usize { 399 | unsafe { (*self.context).multiplexerStartingChannel as usize } 400 | } 401 | 402 | pub fn multiplexer_analog_in(&self) -> &[f32] { 403 | unsafe { 404 | let n_frames = (*self.context).analogFrames; 405 | let n_channels = (*self.context).multiplexerChannels; 406 | let analog_in_ptr = (*self.context).multiplexerAnalogIn; 407 | slice::from_raw_parts(analog_in_ptr, (n_frames * n_channels) as usize) 408 | } 409 | } 410 | 411 | pub fn multiplexer_enabled(&self) -> u32 { 412 | unsafe { (*self.context).audioExpanderEnabled } 413 | } 414 | 415 | pub fn flags(&self) -> u32 { 416 | unsafe { (*self.context).flags } 417 | } 418 | 419 | // Returns the value of a given digital input at the given frame number 420 | pub fn digital_read(&self, frame: usize, channel: usize) -> bool { 421 | let digital = self.digital(); 422 | (digital[frame] >> (channel + 16)) & 1 != 0 423 | } 424 | 425 | // Sets a given digital output channel to a value for the current frame and all subsequent frames 426 | pub fn digital_write(&mut self, frame: usize, channel: usize, value: bool) { 427 | let digital = self.digital_mut(); 428 | for out in &mut digital[frame..] { 429 | if value { 430 | *out |= 1 << (channel + 16) 431 | } else { 432 | *out &= !(1 << (channel + 16)); 433 | } 434 | } 435 | } 436 | 437 | // Sets a given digital output channel to a value for the current frame only 438 | pub fn digital_write_once(&mut self, frame: usize, channel: usize, value: bool) { 439 | let digital = self.digital_mut(); 440 | if value { 441 | digital[frame] |= 1 << (channel + 16); 442 | } else { 443 | digital[frame] &= !(1 << (channel + 16)); 444 | } 445 | } 446 | 447 | // Sets the direction of a digital pin for the current frame and all subsequent frames 448 | pub fn pin_mode(&mut self, frame: usize, channel: usize, mode: DigitalDirection) { 449 | let digital = self.digital_mut(); 450 | for out in &mut digital[frame..] { 451 | match mode { 452 | DigitalDirection::INPUT => { 453 | *out |= 1 << channel; 454 | } 455 | DigitalDirection::OUTPUT => { 456 | *out &= !(1 << channel); 457 | } 458 | } 459 | } 460 | } 461 | 462 | // Sets the direction of a digital pin for the current frame only 463 | pub fn pin_mode_once(&mut self, frame: usize, channel: usize, mode: DigitalDirection) { 464 | let digital = self.digital_mut(); 465 | match mode { 466 | DigitalDirection::INPUT => { 467 | digital[frame] |= 1 << channel; 468 | } 469 | DigitalDirection::OUTPUT => { 470 | digital[frame] &= !(1 << channel); 471 | } 472 | } 473 | } 474 | } 475 | 476 | pub trait UserData<'a> { 477 | type Data; 478 | 479 | fn render_fn(&mut self, context: &mut Context); 480 | fn set_render_fn(&mut self, render_fn: &'a mut dyn FnMut(&mut Context, &mut Self::Data)); 481 | fn setup_fn(&mut self, context: &mut Context) -> Result<(), error::Error>; 482 | fn set_setup_fn( 483 | &mut self, 484 | setup_fn: Option< 485 | &'a mut dyn FnMut(&mut Context, &mut Self::Data) -> Result<(), error::Error>, 486 | >, 487 | ); 488 | fn cleanup_fn(&mut self, context: &mut Context); 489 | fn set_cleanup_fn( 490 | &mut self, 491 | cleanup_fn: Option<&'a mut dyn FnMut(&mut Context, &mut Self::Data)>, 492 | ); 493 | } 494 | 495 | pub struct AppData<'a, D: 'a> { 496 | pub data: D, 497 | render: &'a mut dyn FnMut(&mut Context, &mut D), 498 | setup: Option<&'a mut dyn FnMut(&mut Context, &mut D) -> Result<(), error::Error>>, 499 | cleanup: Option<&'a mut dyn FnMut(&mut Context, &mut D)>, 500 | } 501 | 502 | impl<'a, D> AppData<'a, D> { 503 | pub fn new( 504 | data: D, 505 | render: &'a mut dyn FnMut(&mut Context, &mut D), 506 | setup: Option<&'a mut dyn FnMut(&mut Context, &mut D) -> Result<(), error::Error>>, 507 | cleanup: Option<&'a mut dyn FnMut(&mut Context, &mut D)>, 508 | ) -> AppData<'a, D> { 509 | AppData { 510 | data, 511 | render, 512 | setup, 513 | cleanup, 514 | } 515 | } 516 | } 517 | 518 | impl<'a, D> UserData<'a> for AppData<'a, D> { 519 | type Data = D; 520 | 521 | fn render_fn(&mut self, context: &mut Context) { 522 | let AppData { render, data, .. } = self; 523 | 524 | render(context, data) 525 | } 526 | 527 | fn set_render_fn(&mut self, callback: &'a mut (dyn FnMut(&mut Context, &mut D) + 'a)) { 528 | self.render = callback; 529 | } 530 | 531 | fn setup_fn(&mut self, context: &mut Context) -> Result<(), error::Error> { 532 | let AppData { setup, data, .. } = self; 533 | 534 | match setup { 535 | Some(f) => f(context, data), 536 | None => Ok(()), 537 | } 538 | } 539 | 540 | fn set_setup_fn( 541 | &mut self, 542 | callback: Option< 543 | &'a mut (dyn FnMut(&mut Context, &mut D) -> Result<(), error::Error> + 'a), 544 | >, 545 | ) { 546 | self.setup = callback; 547 | } 548 | 549 | fn cleanup_fn(&mut self, context: &mut Context) { 550 | let AppData { cleanup, data, .. } = self; 551 | 552 | match cleanup { 553 | Some(f) => f(context, data), 554 | None => (), 555 | }; 556 | } 557 | 558 | fn set_cleanup_fn(&mut self, callback: Option<&'a mut (dyn FnMut(&mut Context, &mut D) + 'a)>) { 559 | self.cleanup = callback; 560 | } 561 | } 562 | 563 | /// Safe wrapper for `BelaInitSettings`, which sets initial parameters for the 564 | /// Bela system. 565 | pub struct InitSettings { 566 | settings: BelaInitSettings, 567 | } 568 | 569 | impl InitSettings { 570 | pub fn settings_ptr(&mut self) -> *mut BelaInitSettings { 571 | &mut self.settings 572 | } 573 | 574 | /// Get number of analog frames per period (buffer). Number of audio frames 575 | /// depends on relative sample rates of the two. By default, audio is twice 576 | /// the sample rate, so has twice the period size. 577 | pub fn period_size(&self) -> usize { 578 | self.settings.periodSize as usize 579 | } 580 | 581 | /// Set number of analog frames per period (buffer). Number of audio frames 582 | /// depends on relative sample rates of the two. By default, audio is twice 583 | /// the sample rate, so has twice the period size. 584 | pub fn set_period_size(&mut self, size: usize) { 585 | self.settings.periodSize = size.try_into().unwrap(); 586 | } 587 | 588 | /// Get whether to use the analog input and output 589 | pub fn use_analog(&self) -> bool { 590 | self.settings.useAnalog != 0 591 | } 592 | 593 | /// Set whether to use the analog input and output 594 | pub fn set_use_analog(&mut self, use_analog: bool) { 595 | self.settings.useAnalog = use_analog as _; 596 | } 597 | 598 | /// Get whether to use the digital input and output 599 | pub fn use_digital(&self) -> bool { 600 | self.settings.useDigital != 0 601 | } 602 | 603 | /// Set whether to use the digital input and output 604 | pub fn set_use_digital(&mut self, use_digital: bool) { 605 | self.settings.useDigital = use_digital as _; 606 | } 607 | 608 | pub fn num_analog_in_channels(&self) -> usize { 609 | self.settings.numAnalogInChannels as usize 610 | } 611 | 612 | pub fn set_num_analog_in_channels(&mut self, num: usize) { 613 | self.settings.numAnalogInChannels = num.try_into().unwrap(); 614 | } 615 | 616 | pub fn num_analog_out_channels(&self) -> usize { 617 | self.settings.numAnalogOutChannels as usize 618 | } 619 | 620 | pub fn set_num_analog_out_channels(&mut self, num: usize) { 621 | self.settings.numAnalogOutChannels = num.try_into().unwrap(); 622 | } 623 | 624 | pub fn num_digital_channels(&self) -> usize { 625 | self.settings.numDigitalChannels as usize 626 | } 627 | 628 | pub fn set_num_digital_channels(&mut self, num: usize) { 629 | self.settings.numDigitalChannels = num.try_into().unwrap(); 630 | } 631 | 632 | pub fn begin_muted(&self) -> bool { 633 | self.settings.beginMuted != 0 634 | } 635 | 636 | pub fn set_begin_muted(&mut self, val: bool) { 637 | self.settings.beginMuted = val as _; 638 | } 639 | 640 | pub fn dac_level(&self) -> f32 { 641 | self.settings.dacLevel 642 | } 643 | 644 | pub fn set_dac_level(&mut self, val: f32) { 645 | self.settings.dacLevel = val; 646 | } 647 | 648 | pub fn adc_level(&self) -> f32 { 649 | self.settings.adcLevel 650 | } 651 | 652 | pub fn set_adc_level(&mut self, val: f32) { 653 | self.settings.adcLevel = val; 654 | } 655 | 656 | pub fn pga_gain(&self) -> [f32; 2] { 657 | self.settings.pgaGain 658 | } 659 | 660 | pub fn set_pga_gain(&mut self, val: [f32; 2]) { 661 | self.settings.pgaGain = val; 662 | } 663 | 664 | pub fn headphone_level(&self) -> f32 { 665 | self.settings.headphoneLevel 666 | } 667 | 668 | pub fn set_headphone_level(&mut self, val: f32) { 669 | self.settings.headphoneLevel = val; 670 | } 671 | 672 | pub fn num_mux_channels(&self) -> usize { 673 | self.settings.numMuxChannels as usize 674 | } 675 | 676 | pub fn set_num_mux_channels(&mut self, val: usize) { 677 | self.settings.numMuxChannels = val.try_into().unwrap(); 678 | } 679 | 680 | pub fn audio_expander_inputs(&self) -> usize { 681 | self.settings.audioExpanderInputs as usize 682 | } 683 | 684 | pub fn set_audio_expander_inputs(&mut self, val: usize) { 685 | self.settings.audioExpanderInputs = val.try_into().unwrap(); 686 | } 687 | 688 | pub fn audio_expander_outputs(&self) -> usize { 689 | self.settings.audioExpanderOutputs as usize 690 | } 691 | 692 | pub fn set_audio_expander_outputs(&mut self, val: usize) { 693 | self.settings.audioExpanderOutputs = val.try_into().unwrap(); 694 | } 695 | 696 | pub fn pru_number(&self) -> usize { 697 | self.settings.pruNumber as usize 698 | } 699 | 700 | pub fn set_pru_number(&mut self, val: usize) { 701 | self.settings.pruNumber = val.try_into().unwrap(); 702 | } 703 | 704 | pub fn pru_filename(&self) -> [u8; 256] { 705 | self.settings.pruFilename 706 | } 707 | 708 | pub fn set_pru_filename(&mut self, val: [u8; 256]) { 709 | self.settings.pruFilename = val; 710 | } 711 | 712 | pub fn detect_underruns(&self) -> bool { 713 | self.settings.detectUnderruns != 0 714 | } 715 | 716 | pub fn set_detect_underruns(&mut self, val: bool) { 717 | self.settings.detectUnderruns = val as _; 718 | } 719 | 720 | pub fn verbose(&self) -> bool { 721 | self.settings.verbose != 0 722 | } 723 | 724 | pub fn set_verbose(&mut self, val: bool) { 725 | self.settings.verbose = val as _; 726 | } 727 | 728 | pub fn enable_led(&self) -> bool { 729 | self.settings.enableLED != 0 730 | } 731 | 732 | pub fn set_enable_led(&mut self, val: bool) { 733 | self.settings.enableLED = val as _; 734 | } 735 | 736 | pub fn stop_button_pin(&self) -> Option { 737 | match self.settings.stopButtonPin { 738 | 0..=127 => Some(self.settings.stopButtonPin as _), 739 | _ => None, 740 | } 741 | } 742 | 743 | pub fn set_stop_button_pin(&mut self, val: Option) { 744 | self.settings.stopButtonPin = match val { 745 | Some(v) if v >= 0 => v as _, 746 | _ => -1, 747 | }; 748 | } 749 | 750 | pub fn high_performance_mode(&self) -> bool { 751 | self.settings.highPerformanceMode != 0 752 | } 753 | 754 | pub fn set_high_performance_mode(&mut self, val: bool) { 755 | self.settings.highPerformanceMode = val as _; 756 | } 757 | 758 | pub fn interleave(&self) -> bool { 759 | self.settings.interleave != 0 760 | } 761 | 762 | pub fn set_interleave(&mut self, val: bool) { 763 | self.settings.interleave = val as _; 764 | } 765 | 766 | pub fn analog_outputs_persist(&self) -> bool { 767 | self.settings.analogOutputsPersist != 0 768 | } 769 | 770 | pub fn set_analog_outputs_persist(&mut self, val: bool) { 771 | self.settings.analogOutputsPersist = val as _; 772 | } 773 | 774 | pub fn uniform_sample_rate(&self) -> bool { 775 | self.settings.uniformSampleRate != 0 776 | } 777 | 778 | pub fn set_uniform_sample_rate(&mut self, val: bool) { 779 | self.settings.uniformSampleRate = val as _; 780 | } 781 | 782 | pub fn audio_thread_stack_size(&self) -> usize { 783 | self.settings.audioThreadStackSize as usize 784 | } 785 | 786 | pub fn set_audio_thread_stack_size(&mut self, num: usize) { 787 | self.settings.audioThreadStackSize = num.try_into().unwrap(); 788 | } 789 | 790 | pub fn auxiliary_task_stack_size(&self) -> usize { 791 | self.settings.auxiliaryTaskStackSize as usize 792 | } 793 | 794 | pub fn set_auxiliary_task_stack_size(&mut self, num: usize) { 795 | self.settings.auxiliaryTaskStackSize = num.try_into().unwrap(); 796 | } 797 | 798 | pub fn amp_mute_pin(&self) -> Option { 799 | match self.settings.ampMutePin { 800 | 0..=127 => Some(self.settings.ampMutePin as _), 801 | _ => None, 802 | } 803 | } 804 | 805 | pub fn set_amp_mute_pin(&mut self, val: Option) { 806 | self.settings.ampMutePin = match val { 807 | Some(v) if v >= 0 => v as _, 808 | _ => -1, 809 | }; 810 | } 811 | 812 | /// Get user selected board to work with (as opposed to detected hardware). 813 | pub fn board(&self) -> BelaHw { 814 | BelaHw::from_i32(self.settings.board).expect("unexpected board type") 815 | } 816 | 817 | /// Set user selected board to work with (as opposed to detected hardware). 818 | pub fn set_board(&mut self, board: BelaHw) { 819 | self.settings.board = board as _; 820 | } 821 | } 822 | 823 | impl Default for InitSettings { 824 | fn default() -> InitSettings { 825 | let settings = unsafe { 826 | let mut settings = mem::MaybeUninit::::uninit(); 827 | bela_sys::Bela_defaultSettings(settings.as_mut_ptr()); 828 | settings.assume_init() 829 | }; 830 | 831 | InitSettings { settings } 832 | } 833 | } 834 | --------------------------------------------------------------------------------