Skip to main content

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}