rill_core/lib.rs
1//! # Rill Core
2//!
3//! The core of the Rill ecosystem. Provides fundamental traits, types,
4//! and utilities for building real-time signal processing applications.
5//!
6//! ## Architecture Overview
7//!
8//! ```text
9//! rill-core/
10//! ├── traits/ # Core traits (Node, Source, Processor, Sink, etc.)
11//! ├── math/ # Mathematical abstractions (Scalar, Transcendental, Vector)
12//! │ └── vector/ # Vector types, SIMD abstractions, slice operations
13//! ├── buffer/ # Lock-free signal buffers with AtomicCell safety
14//! ├── queues/ # Real-time safe command queues
15//! ├── time/ # Time and clock abstractions (ClockTick, SystemClock)
16//! ├── io/ # Generic I/O backend trait (IoBackend)
17//! ├── macros/ # Node creation macros (source_node!, processor_node!, etc.)
18//! ├── prelude # Convenience prelude for common imports
19//! ├── interpolate # Fractional-index interpolation trait
20//! └── executor/ # Graph executor for driving signal processing
21//! ```
22//!
23//! ## Key Concepts
24//!
25//! - **Scalar**: Base numeric trait for any type (floats and integers)
26//! - **Transcendental**: Float numeric abstraction with sin/cos/sqrt
27//! - **AtomicCell**: Safe atomic wrapper for lock-free data structures
28//! - **Node**: Base trait for all nodes in the signal graph
29//! - **Source**: Active generators (oscillators, file readers)
30//! - **Processor**: Passive processors (filters, effects)
31//! - **Sink**: Active outputs (I/O devices, file writers)
32//! - **PipeBuffer**: Zero-copy connections between nodes
33//! - **CommandQueue**: Real-time safe parameter automation
34//! - **ClockTick**: Sample-accurate timing for synchronization
35//!
36//! ## Example
37//!
38//! ```rust
39//! use rill_core::prelude::*;
40//! use rill_core::Port;
41//! use rill_core::traits::node;
42//!
43//! // Create a simple sine source
44//! struct MySine<T: Transcendental, const BUF_SIZE: usize> {
45//! frequency: T,
46//! amplitude: T,
47//! phase: T,
48//! sample_rate: T,
49//! }
50//!
51//! impl<T: Transcendental, const BUF_SIZE: usize> Node<T, BUF_SIZE> for MySine<T, BUF_SIZE> {
52//! fn metadata(&self) -> NodeMetadata {
53//! NodeMetadata {
54//! name: "Sine".to_string(),
55//! type_name: None,
56//! category: NodeCategory::Source,
57//! description: "Sine wave oscillator".to_string(),
58//! author: "Rill".to_string(),
59//! version: env!("CARGO_PKG_VERSION").to_string(),
60//! signal_inputs: 0,
61//! signal_outputs: 1,
62//! control_inputs: 0,
63//! control_outputs: 0,
64//! clock_inputs: 1,
65//! clock_outputs: 0,
66//! feedback_ports: 0,
67//! parameters: vec![],
68//! }
69//! }
70//!
71//! fn init(&mut self, sample_rate: f32) {
72//! self.sample_rate = T::from_f32(sample_rate);
73//! }
74//!
75//! fn reset(&mut self) {
76//! self.phase = T::ZERO;
77//! }
78//!
79//! fn get_parameter(&self, _id: &ParameterId) -> Option<ParamValue> {
80//! None
81//! }
82//!
83//! fn set_parameter(&mut self, _id: &ParameterId, _value: ParamValue) -> ProcessResult<()> {
84//! Ok(())
85//! }
86//!
87//! fn id(&self) -> NodeId { NodeId(0) }
88//! fn set_id(&mut self, _id: NodeId) {}
89//!
90//! fn input_port(&self, _index: usize) -> Option<&Port<T, BUF_SIZE>> { None }
91//! fn input_port_mut(&mut self, _index: usize) -> Option<&mut Port<T, BUF_SIZE>> { None }
92//! fn output_port(&self, _index: usize) -> Option<&Port<T, BUF_SIZE>> { None }
93//! fn output_port_mut(&mut self, _index: usize) -> Option<&mut Port<T, BUF_SIZE>> { None }
94//! fn control_port(&self, _index: usize) -> Option<&Port<T, BUF_SIZE>> { None }
95//! fn control_port_mut(&mut self, _index: usize) -> Option<&mut Port<T, BUF_SIZE>> { None }
96//!
97//! fn state(&self) -> &node::NodeState<T,BUF_SIZE> {
98//! unimplemented!()
99//! }
100//!
101//! fn state_mut(&mut self) -> &mut node::NodeState<T,BUF_SIZE> {
102//! unimplemented!()
103//! }
104//! }
105//!
106//! impl<T: Transcendental, const BUF_SIZE: usize> Source<T, BUF_SIZE> for MySine<T, BUF_SIZE> {
107//! fn generate(
108//! &mut self,
109//! ctx: &RenderContext,
110//! _control_inputs: &[T],
111//! _clock_inputs: &[RenderContext],
112//! _tick: &ClockTick,
113//! ) -> ProcessResult<()> {
114//! let two_pi = T::from_f32(2.0 * std::f32::consts::PI);
115//! let phase_inc = self.frequency / T::from_f32(ctx.sample_rate);
116//! let amp = self.amplitude;
117//!
118//! let mut temp = [T::ZERO; BUF_SIZE];
119//! for i in 0..BUF_SIZE {
120//! let phase_rad = self.phase * two_pi;
121//! temp[i] = phase_rad.sin() * amp;
122//! self.phase = self.phase + phase_inc;
123//! if self.phase >= T::from_f32(1.0) {
124//! self.phase = self.phase - T::from_f32(1.0);
125//! }
126//! }
127//! *self.output_port_mut(0).unwrap().buffer.as_mut_array() = temp;
128//! Ok(())
129//! }
130//!
131//! fn num_signal_outputs(&self) -> usize { 1 }
132//! fn num_control_inputs(&self) -> usize { 0 }
133//! fn num_clock_inputs(&self) -> usize { 1 }
134//! }
135//! ```
136
137#![warn(missing_docs)]
138#![allow(clippy::doc_lazy_continuation)]
139#![deny(unsafe_code)]
140#![cfg_attr(not(test), deny(unused))]
141#![cfg_attr(docsrs, feature(doc_cfg))]
142#![allow(deprecated)]
143
144// ============================================================================
145// Core Modules
146// ============================================================================
147
148/// Core traits for the Rill ecosystem
149pub mod traits;
150
151/// Mathematical abstractions for signal processing
152pub mod math;
153
154/// Lock-free, real-time safe signal buffers
155pub mod buffer;
156
157/// Real-time safe command queues for automation
158pub mod queues;
159
160/// Time and clock abstractions for synchronization
161pub mod time;
162
163#[doc(hidden)]
164pub use math::vector;
165
166/// Macros for node creation and boilerplate reduction
167#[macro_use]
168pub mod macros;
169
170/// Convenience prelude for importing common types
171pub mod prelude;
172
173/// Fractional-index interpolation trait for slice-like types
174pub mod interpolate;
175
176/// Generic multi-channel signal I/O abstraction
177pub mod io;
178
179/// Graph executor for driving signal processing
180pub mod executor;
181
182// ============================================================================
183// Error Types
184// ============================================================================
185
186/// Core error types for the Rill ecosystem
187mod error;
188pub use error::*;
189
190// ============================================================================
191// Re-exports for Convenience
192// ============================================================================
193
194// Re-export core traits
195pub use traits::{
196 ClockError, ClockResult, ConnectionError, ConnectionResult, Eurorack, Node, NodeCategory,
197 NodeId, NodeMetadata, NodeState, NodeTypeId, ParamMetadata, ParamRange, ParamType, ParamValue,
198 ParameterError, ParameterId, Params, Port, PortDirection, PortError, PortId, PortResult,
199 PortType, ProcessError, ProcessResult, Processor, Sink, Source,
200};
201
202// Re-export math abstractions
203pub use math::{Scalar, Transcendental};
204
205// Re-export buffer types with AtomicCell safety
206pub use buffer::{
207 AtomicCell, AtomicCellError, AtomicStats, Buffer, BufferError, BufferResult, BufferStats,
208 DelayLine, FanInBuffer, FanOutBuffer, PipeBuffer, RingBuffer,
209};
210
211// Re-export queue types (from rill-patchbay integration)
212pub use queues::{QueueError, QueueResult};
213
214// Re-export time abstractions
215pub use time::{ClockSource, ClockTick, RenderContext, SystemClock};
216
217// ============================================================================
218// Constants
219// ============================================================================
220
221/// Current version of rill-core
222pub const VERSION: &str = env!("CARGO_PKG_VERSION");
223
224/// Maximum supported sample rate
225pub const MAX_SAMPLE_RATE: f32 = 384_000.0;
226
227/// Minimum supported sample rate
228pub const MIN_SAMPLE_RATE: f32 = 8_000.0;
229
230/// Default sample rate (44.1 kHz)
231pub const DEFAULT_SAMPLE_RATE: f32 = 44_100.0;
232
233/// Default block size for signal processing
234pub const DEFAULT_BLOCK_SIZE: usize = 64;
235
236/// Maximum block size
237pub const MAX_BLOCK_SIZE: usize = 8192;
238
239/// Minimum block size
240pub const MIN_BLOCK_SIZE: usize = 16;
241
242/// Default buffer size for most use cases
243pub const DEFAULT_BUFFER_SIZE: usize = 1024;
244
245/// Maximum buffer size (2^16 = 65536 samples)
246pub const MAX_BUFFER_SIZE: usize = 65536;
247
248/// Minimum buffer size
249pub const MIN_BUFFER_SIZE: usize = 16;
250
251/// Cache line size for alignment (64 bytes on x86_64)
252pub const CACHE_LINE_SIZE: usize = 64;
253
254// ============================================================================
255// Utility Functions
256// ============================================================================
257
258/// Utility functions for common operations
259pub mod utils {
260 use crate::math::Transcendental;
261
262 /// Convert seconds to samples
263 #[inline(always)]
264 pub fn seconds_to_samples(seconds: f32, sample_rate: f32) -> usize {
265 (seconds * sample_rate) as usize
266 }
267
268 /// Convert samples to seconds
269 #[inline(always)]
270 pub fn samples_to_seconds(samples: usize, sample_rate: f32) -> f32 {
271 samples as f32 / sample_rate
272 }
273
274 /// Convert dB to linear gain
275 #[inline(always)]
276 pub fn db_to_linear<T: Transcendental>(db: T) -> T {
277 T::from_f32(10.0_f32.powf(db.to_f32() / 20.0))
278 }
279
280 /// Convert linear gain to dB
281 #[inline(always)]
282 pub fn linear_to_db<T: Transcendental>(linear: T) -> T {
283 T::from_f32(20.0 * linear.to_f32().log10())
284 }
285
286 /// Check if a value is a power of two
287 #[inline(always)]
288 pub const fn is_power_of_two(x: usize) -> bool {
289 x != 0 && (x & (x - 1)) == 0
290 }
291
292 /// Round up to the next power of two
293 #[inline(always)]
294 pub const fn next_power_of_two(x: usize) -> usize {
295 let mut n = x - 1;
296 n |= n >> 1;
297 n |= n >> 2;
298 n |= n >> 4;
299 n |= n >> 8;
300 n |= n >> 16;
301 n + 1
302 }
303}
304
305// ============================================================================
306// Version Information
307// ============================================================================
308
309/// Get detailed version information
310pub fn version_info() -> VersionInfo {
311 VersionInfo {
312 version: VERSION,
313 crate_name: env!("CARGO_PKG_NAME"),
314 authors: env!("CARGO_PKG_AUTHORS"),
315 description: env!("CARGO_PKG_DESCRIPTION"),
316 repository: env!("CARGO_PKG_REPOSITORY"),
317 }
318}
319
320/// Detailed version information for the rill-core crate.
321#[derive(Debug, Clone)]
322pub struct VersionInfo {
323 /// Crate version string (from `CARGO_PKG_VERSION`).
324 pub version: &'static str,
325 /// Crate name (from `CARGO_PKG_NAME`).
326 pub crate_name: &'static str,
327 /// Author list (from `CARGO_PKG_AUTHORS`).
328 pub authors: &'static str,
329 /// Crate description (from `CARGO_PKG_DESCRIPTION`).
330 pub description: &'static str,
331 /// Repository URL (from `CARGO_PKG_REPOSITORY`).
332 pub repository: &'static str,
333}
334
335// ============================================================================
336// Tests
337// ============================================================================
338
339#[cfg(test)]
340mod tests {
341 use super::prelude::*;
342 use super::utils;
343
344 #[test]
345 fn test_constants() {
346 assert!(!VERSION.is_empty());
347 const {
348 assert!(MAX_SAMPLE_RATE > MIN_SAMPLE_RATE);
349 assert!(MAX_BLOCK_SIZE > MIN_BLOCK_SIZE);
350 }
351 assert_eq!(DEFAULT_BLOCK_SIZE, 64);
352 assert_eq!(DEFAULT_SAMPLE_RATE, 44100.0);
353 assert_eq!(CACHE_LINE_SIZE, 64);
354 }
355
356 #[test]
357 fn test_utils() {
358 assert_eq!(utils::seconds_to_samples(1.0, 44100.0), 44100);
359 assert!((utils::samples_to_seconds(44100, 44100.0) - 1.0).abs() < 1e-6);
360
361 let linear = utils::db_to_linear(0.0f32);
362 assert!((linear - 1.0).abs() < 1e-6);
363
364 let db = utils::linear_to_db(1.0f32);
365 assert!((db - 0.0).abs() < 1e-6);
366
367 assert!(utils::is_power_of_two(64));
368 assert!(!utils::is_power_of_two(63));
369 assert_eq!(utils::next_power_of_two(63), 64);
370 }
371
372 #[test]
373 fn test_atomic_cell() {
374 let cell = AtomicCell::new(42);
375 assert_eq!(cell.load(), 42);
376 cell.store(100);
377 assert_eq!(cell.load(), 100);
378 }
379}
380
381// ============================================================================
382// Documentation Tests
383// ============================================================================
384
385#[cfg(doctest)]
386mod doctests {
387 //! This module exists only to host documentation tests
388}