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