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//! ├── 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//! - **Node**: 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> Node<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#![allow(clippy::doc_lazy_continuation)]
137#![deny(unsafe_code)]
138#![cfg_attr(not(test), deny(unused))]
139#![cfg_attr(docsrs, feature(doc_cfg))]
140#![allow(deprecated)]
141
142// ============================================================================
143// Core Modules
144// ============================================================================
145
146/// Core traits for the Rill ecosystem
147pub mod traits;
148
149/// Mathematical abstractions for signal processing
150pub mod math;
151
152/// Lock-free, real-time safe signal buffers
153pub mod buffer;
154
155/// Real-time safe command queues for automation
156pub mod queues;
157
158/// Time and clock abstractions for synchronization
159pub mod time;
160
161#[doc(hidden)]
162pub use math::vector;
163
164/// Macros for node creation and boilerplate reduction
165#[macro_use]
166pub mod macros;
167
168/// Convenience prelude for importing common types
169pub mod prelude;
170
171/// Fractional-index interpolation trait for slice-like types
172pub mod interpolate;
173
174/// Generic multi-channel signal I/O abstraction
175pub mod io;
176
177/// Graph executor for driving signal processing
178pub mod executor;
179
180// ============================================================================
181// Error Types
182// ============================================================================
183
184/// Core error types for the Rill ecosystem
185mod error;
186pub use error::*;
187
188// ============================================================================
189// Re-exports for Convenience
190// ============================================================================
191
192// Re-export core traits
193pub use traits::{
194 ClockError, ClockResult, ConnectionError, ConnectionResult, Node, NodeCategory, NodeId,
195 NodeMetadata, NodeState, NodeTypeId, ParamMetadata, ParamRange, ParamType, ParamValue,
196 ParameterError, ParameterId, Params, Port, PortDirection, PortError, PortId, PortResult,
197 PortType, ProcessError, ProcessResult, Processor, Sink, Source,
198};
199
200// Re-export math abstractions
201pub use math::{Scalar, Transcendental};
202
203// Re-export buffer types with AtomicCell safety
204pub use buffer::{
205 AtomicCell, AtomicCellError, AtomicStats, Buffer, BufferError, BufferResult, BufferStats,
206 DelayLine, FanInBuffer, FanOutBuffer, PipeBuffer, RingBuffer,
207};
208
209// Re-export queue types (from rill-patchbay integration)
210pub use queues::{QueueError, QueueResult};
211
212// Re-export time abstractions
213pub use time::{ClockSource, ClockTick, SystemClock};
214
215// ============================================================================
216// Constants
217// ============================================================================
218
219/// Current version of rill-core
220pub const VERSION: &str = env!("CARGO_PKG_VERSION");
221
222/// Maximum supported sample rate
223pub const MAX_SAMPLE_RATE: f32 = 384_000.0;
224
225/// Minimum supported sample rate
226pub const MIN_SAMPLE_RATE: f32 = 8_000.0;
227
228/// Default sample rate (44.1 kHz)
229pub const DEFAULT_SAMPLE_RATE: f32 = 44_100.0;
230
231/// Default block size for signal processing
232pub const DEFAULT_BLOCK_SIZE: usize = 64;
233
234/// Maximum block size
235pub const MAX_BLOCK_SIZE: usize = 8192;
236
237/// Minimum block size
238pub const MIN_BLOCK_SIZE: usize = 16;
239
240/// Default buffer size for most use cases
241pub const DEFAULT_BUFFER_SIZE: usize = 1024;
242
243/// Maximum buffer size (2^16 = 65536 samples)
244pub const MAX_BUFFER_SIZE: usize = 65536;
245
246/// Minimum buffer size
247pub const MIN_BUFFER_SIZE: usize = 16;
248
249/// Cache line size for alignment (64 bytes on x86_64)
250pub const CACHE_LINE_SIZE: usize = 64;
251
252// ============================================================================
253// Utility Functions
254// ============================================================================
255
256/// Utility functions for common operations
257pub mod utils {
258 use crate::math::Transcendental;
259
260 /// Convert seconds to samples
261 #[inline(always)]
262 pub fn seconds_to_samples(seconds: f32, sample_rate: f32) -> usize {
263 (seconds * sample_rate) as usize
264 }
265
266 /// Convert samples to seconds
267 #[inline(always)]
268 pub fn samples_to_seconds(samples: usize, sample_rate: f32) -> f32 {
269 samples as f32 / sample_rate
270 }
271
272 /// Convert MIDI note to frequency
273 #[inline(always)]
274 pub fn midi_to_freq<T: Transcendental>(note: u8) -> T {
275 let exp = (note as f32 - 69.0) / 12.0;
276 T::from_f32(440.0 * 2.0_f32.powf(exp))
277 }
278
279 /// Convert frequency to MIDI note
280 #[inline(always)]
281 pub fn freq_to_midi<T: Transcendental>(freq: T) -> f32 {
282 69.0 + 12.0 * (freq.to_f32() / 440.0).log2()
283 }
284
285 /// Convert dB to linear gain
286 #[inline(always)]
287 pub fn db_to_linear<T: Transcendental>(db: T) -> T {
288 T::from_f32(10.0_f32.powf(db.to_f32() / 20.0))
289 }
290
291 /// Convert linear gain to dB
292 #[inline(always)]
293 pub fn linear_to_db<T: Transcendental>(linear: T) -> T {
294 T::from_f32(20.0 * linear.to_f32().log10())
295 }
296
297 /// Check if a value is a power of two
298 #[inline(always)]
299 pub const fn is_power_of_two(x: usize) -> bool {
300 x != 0 && (x & (x - 1)) == 0
301 }
302
303 /// Round up to the next power of two
304 #[inline(always)]
305 pub const fn next_power_of_two(x: usize) -> usize {
306 let mut n = x - 1;
307 n |= n >> 1;
308 n |= n >> 2;
309 n |= n >> 4;
310 n |= n >> 8;
311 n |= n >> 16;
312 n + 1
313 }
314}
315
316// ============================================================================
317// Version Information
318// ============================================================================
319
320/// Get detailed version information
321pub fn version_info() -> VersionInfo {
322 VersionInfo {
323 version: VERSION,
324 crate_name: env!("CARGO_PKG_NAME"),
325 authors: env!("CARGO_PKG_AUTHORS"),
326 description: env!("CARGO_PKG_DESCRIPTION"),
327 repository: env!("CARGO_PKG_REPOSITORY"),
328 }
329}
330
331/// Detailed version information for the rill-core crate.
332#[derive(Debug, Clone)]
333pub struct VersionInfo {
334 /// Crate version string (from `CARGO_PKG_VERSION`).
335 pub version: &'static str,
336 /// Crate name (from `CARGO_PKG_NAME`).
337 pub crate_name: &'static str,
338 /// Author list (from `CARGO_PKG_AUTHORS`).
339 pub authors: &'static str,
340 /// Crate description (from `CARGO_PKG_DESCRIPTION`).
341 pub description: &'static str,
342 /// Repository URL (from `CARGO_PKG_REPOSITORY`).
343 pub repository: &'static str,
344}
345
346// ============================================================================
347// Tests
348// ============================================================================
349
350#[cfg(test)]
351mod tests {
352 use super::prelude::*;
353 use super::utils;
354
355 #[test]
356 fn test_constants() {
357 assert!(!VERSION.is_empty());
358 assert!(MAX_SAMPLE_RATE > MIN_SAMPLE_RATE);
359 assert!(MAX_BLOCK_SIZE > MIN_BLOCK_SIZE);
360 assert_eq!(DEFAULT_BLOCK_SIZE, 64);
361 assert_eq!(DEFAULT_SAMPLE_RATE, 44100.0);
362 assert_eq!(CACHE_LINE_SIZE, 64);
363 }
364
365 #[test]
366 fn test_utils() {
367 assert_eq!(utils::seconds_to_samples(1.0, 44100.0), 44100);
368 assert!((utils::samples_to_seconds(44100, 44100.0) - 1.0).abs() < 1e-6);
369
370 let freq: f32 = utils::midi_to_freq(69);
371 assert!((freq - 440.0).abs() < 1e-6);
372
373 let midi = utils::freq_to_midi(440.0f32);
374 assert!((midi - 69.0).abs() < 1e-6);
375
376 let linear = utils::db_to_linear(0.0f32);
377 assert!((linear - 1.0).abs() < 1e-6);
378
379 let db = utils::linear_to_db(1.0f32);
380 assert!((db - 0.0).abs() < 1e-6);
381
382 assert!(utils::is_power_of_two(64));
383 assert!(!utils::is_power_of_two(63));
384 assert_eq!(utils::next_power_of_two(63), 64);
385 }
386
387 #[test]
388 fn test_atomic_cell() {
389 let cell = AtomicCell::new(42);
390 assert_eq!(cell.load(), 42);
391 cell.store(100);
392 assert_eq!(cell.load(), 100);
393 }
394}
395
396// ============================================================================
397// Documentation Tests
398// ============================================================================
399
400#[cfg(doctest)]
401mod doctests {
402 //! This module exists only to host documentation tests
403}