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