pub mod audio;
pub mod clip;
pub mod cpal_backend;
pub mod engine;
pub mod metronome;
pub mod mixer;
pub mod pattern;
pub mod project;
pub mod transport;
use serde::{Deserialize, Serialize};
#[cfg(test)]
pub(crate) mod alloc_count {
use std::alloc::{GlobalAlloc, Layout, System};
use std::cell::Cell;
thread_local! {
static ALLOCATIONS: Cell<u64> = const { Cell::new(0) };
}
struct Counting;
fn note_allocation() {
let _ = ALLOCATIONS.try_with(|c| c.set(c.get() + 1));
}
unsafe impl GlobalAlloc for Counting {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
note_allocation();
System.alloc(layout)
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
System.dealloc(ptr, layout);
}
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
note_allocation();
System.alloc_zeroed(layout)
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
note_allocation();
System.realloc(ptr, layout, new_size)
}
}
#[global_allocator]
static COUNTING: Counting = Counting;
pub(crate) fn allocations_during(body: impl FnOnce()) -> u64 {
let before = ALLOCATIONS.with(Cell::get);
body();
ALLOCATIONS.with(Cell::get) - before
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct EngineConfig {
pub buffer_size: u32,
pub sample_rate: u32,
}
impl Default for EngineConfig {
fn default() -> Self {
Self {
buffer_size: 64,
sample_rate: 44100,
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct AudioRequest {
pub sample_rate: Option<u32>,
pub buffer_size: Option<u32>,
}
impl AudioRequest {
#[must_use]
pub const fn follow_device() -> Self {
Self { sample_rate: None, buffer_size: None }
}
#[must_use]
pub fn without_device(self) -> EngineConfig {
let fallback = EngineConfig::default();
EngineConfig {
sample_rate: self.sample_rate.unwrap_or(fallback.sample_rate),
buffer_size: self.buffer_size.unwrap_or(fallback.buffer_size),
}
}
}
impl From<EngineConfig> for AudioRequest {
fn from(config: EngineConfig) -> Self {
Self {
sample_rate: Some(config.sample_rate),
buffer_size: Some(config.buffer_size),
}
}
}
impl From<crate::cpal_backend::StreamFormat> for EngineConfig {
fn from(format: crate::cpal_backend::StreamFormat) -> Self {
Self {
buffer_size: format.buffer_size.unwrap_or(format.max_buffer_frames),
sample_rate: format.sample_rate,
}
}
}
impl EngineConfig {
pub fn buffer_duration_secs(&self) -> f64 {
self.buffer_size as f64 / self.sample_rate as f64
}
pub fn buffer_duration_ms(&self) -> f64 {
self.buffer_duration_secs() * 1000.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_config_is_sensible() {
let config = EngineConfig::default();
assert_eq!(config.buffer_size, 64);
assert_eq!(config.sample_rate, 44100);
}
#[test]
fn an_empty_request_asks_for_nothing() {
let request = AudioRequest::follow_device();
assert_eq!(request.sample_rate, None);
assert_eq!(request.buffer_size, None);
assert_eq!(request, AudioRequest::default());
}
#[test]
fn without_a_device_the_gaps_are_filled_from_the_default() {
assert_eq!(
AudioRequest::follow_device().without_device(),
EngineConfig::default()
);
}
#[test]
fn without_a_device_what_was_asked_for_is_still_honoured() {
let request = AudioRequest { sample_rate: Some(96000), buffer_size: None };
let config = request.without_device();
assert_eq!(config.sample_rate, 96000);
assert_eq!(config.buffer_size, EngineConfig::default().buffer_size);
}
#[test]
fn a_concrete_config_converts_to_a_request_for_exactly_it() {
let config = EngineConfig { buffer_size: 256, sample_rate: 96000 };
assert_eq!(
AudioRequest::from(config),
AudioRequest { sample_rate: Some(96000), buffer_size: Some(256) }
);
assert_eq!(AudioRequest::from(config).without_device(), config);
}
#[test]
fn a_device_chosen_block_size_reports_the_worst_case() {
use crate::cpal_backend::{Requested, StreamFormat};
let format = StreamFormat {
sample_rate: 48000,
buffer_size: None,
max_buffer_frames: 4096,
channels: 2,
sample_rate_request: Requested::Unasked,
buffer_size_request: Requested::Unasked,
};
let config = EngineConfig::from(format);
assert_eq!(config.sample_rate, 48000);
assert_eq!(config.buffer_size, 4096);
}
#[test]
fn buffer_duration_calculation() {
let config = EngineConfig {
buffer_size: 64,
sample_rate: 44100,
};
let ms = config.buffer_duration_ms();
assert!((ms - 1.451).abs() < 0.01, "Expected ~1.45ms, got {ms}ms");
}
#[test]
fn buffer_duration_various_sizes() {
for (size, rate, expected_ms) in [
(64, 44100, 1.451),
(128, 44100, 2.902),
(256, 48000, 5.333),
(64, 96000, 0.667),
] {
let config = EngineConfig {
buffer_size: size,
sample_rate: rate,
};
let ms = config.buffer_duration_ms();
assert!(
(ms - expected_ms).abs() < 0.01,
"size={size} rate={rate}: expected {expected_ms}ms, got {ms}ms"
);
}
}
}