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