Skip to main content

rill_lofi/
lofi_chip_source.rs

1//! LofiChipSource — Source node wrapping an audio chip emulator + lofi processing.
2//!
3//! Follows the `SineOsc` pattern: owns a DSP engine (`Algorithm<f32>`),
4//! generates audio via `process()`, applies lofi post-processing.
5
6use std::marker::PhantomData;
7
8use rill_core::{
9    time::{ClockTick, RenderContext},
10    traits::{
11        algorithm::Algorithm, parameter_write::ParameterWrite, Node, NodeCategory, NodeId,
12        NodeMetadata, NodeState, ParamValue, ParameterId, Port, ProcessResult, Source,
13    },
14};
15
16use crate::chip_emulator::ChipEmulator;
17use crate::config::LofiConfig;
18use crate::lofi_processor::LofiProcessor;
19
20/// Source node wrapping a chip emulator with lofi processing.
21///
22/// `C` implements both `Algorithm<f32>` (audio generation) and
23/// `ChipEmulator` (register writes).  `LofiProcessor` applies
24/// bitcrushing, noise, and DAC coloring after the chip output.
25pub struct LofiChipSource<C: Algorithm<f32> + ChipEmulator + ParameterWrite, const BUF_SIZE: usize>
26{
27    id: NodeId,
28    metadata: NodeMetadata,
29    chip: C,
30    lofi: LofiProcessor<BUF_SIZE>,
31    outputs: Vec<Port<f32, BUF_SIZE>>,
32    state: NodeState<f32, BUF_SIZE>,
33    _phantom: PhantomData<[f32; BUF_SIZE]>,
34}
35
36impl<C: Algorithm<f32> + ChipEmulator + ParameterWrite, const BUF_SIZE: usize>
37    LofiChipSource<C, BUF_SIZE>
38{
39    /// Create a new chip source with the given emulator and lofi configuration.
40    pub fn new(chip: C, lofi_config: LofiConfig, num_channels: usize) -> Self {
41        let mut metadata = NodeMetadata::new("LofiChip", NodeCategory::Source);
42        metadata.signal_inputs = 0;
43        metadata.signal_outputs = num_channels;
44        let outputs = (0..num_channels)
45            .map(|i| {
46                Port::output(
47                    NodeId(0),
48                    i as u16,
49                    &if num_channels == 1 {
50                        "out".into()
51                    } else {
52                        format!("ch_{i}")
53                    },
54                )
55            })
56            .collect();
57        Self {
58            id: NodeId(0),
59            metadata,
60            chip,
61            lofi: LofiProcessor::new(lofi_config),
62            outputs,
63            state: NodeState::new(44100.0),
64            _phantom: PhantomData,
65        }
66    }
67}
68
69impl<C: Algorithm<f32> + ChipEmulator + ParameterWrite, const BUF_SIZE: usize> Node<f32, BUF_SIZE>
70    for LofiChipSource<C, BUF_SIZE>
71{
72    fn node_type_id(&self) -> rill_core::NodeTypeId
73    where
74        Self: 'static + Sized,
75    {
76        rill_core::NodeTypeId::of::<Self>()
77    }
78
79    fn id(&self) -> NodeId {
80        self.id
81    }
82
83    fn set_id(&mut self, id: NodeId) {
84        self.id = id;
85    }
86
87    fn metadata(&self) -> NodeMetadata {
88        self.metadata.clone()
89    }
90
91    fn init(&mut self, sample_rate: f32) {
92        self.chip.init(sample_rate);
93        self.lofi.init(sample_rate);
94        self.state.sample_rate = sample_rate;
95    }
96
97    fn reset(&mut self) {
98        self.chip.reset();
99        self.lofi.reset();
100        self.state.reset();
101    }
102
103    fn get_parameter(&self, id: &ParameterId) -> Option<ParamValue> {
104        self.lofi.get_parameter(id)
105    }
106
107    fn set_parameter(&mut self, id: &ParameterId, value: ParamValue) -> ProcessResult<()> {
108        // Try chip-specific parameters first (via ParameterWrite)
109        if self
110            .chip
111            .write_parameter(id.as_str(), value.clone())
112            .is_ok()
113        {
114            return Ok(());
115        }
116        // Delegate lofi parameters (bit_depth, dry_wet, etc.) to LofiProcessor
117        Node::<f32, BUF_SIZE>::set_parameter(&mut self.lofi, id, value)
118    }
119
120    fn input_port(&self, _index: usize) -> Option<&Port<f32, BUF_SIZE>> {
121        None
122    }
123
124    fn input_port_mut(&mut self, _index: usize) -> Option<&mut Port<f32, BUF_SIZE>> {
125        None
126    }
127
128    fn output_port(&self, index: usize) -> Option<&Port<f32, BUF_SIZE>> {
129        self.outputs.get(index)
130    }
131
132    fn output_port_mut(&mut self, index: usize) -> Option<&mut Port<f32, BUF_SIZE>> {
133        self.outputs.get_mut(index)
134    }
135
136    fn control_port(&self, _index: usize) -> Option<&Port<f32, BUF_SIZE>> {
137        None
138    }
139
140    fn control_port_mut(&mut self, _index: usize) -> Option<&mut Port<f32, BUF_SIZE>> {
141        None
142    }
143
144    fn num_signal_inputs(&self) -> usize {
145        0
146    }
147
148    fn num_signal_outputs(&self) -> usize {
149        self.outputs.len()
150    }
151
152    fn state(&self) -> &NodeState<f32, BUF_SIZE> {
153        &self.state
154    }
155
156    fn state_mut(&mut self) -> &mut NodeState<f32, BUF_SIZE> {
157        &mut self.state
158    }
159}
160
161impl<C: Algorithm<f32> + ChipEmulator + ParameterWrite, const BUF_SIZE: usize> Source<f32, BUF_SIZE>
162    for LofiChipSource<C, BUF_SIZE>
163{
164    fn generate(
165        &mut self,
166        _ctx: &RenderContext,
167        _control_inputs: &[f32],
168        _clock_inputs: &[RenderContext],
169        _tick: &ClockTick,
170    ) -> ProcessResult<()> {
171        // Generate raw chip audio into a temp buffer
172        let mut raw = [0.0f32; BUF_SIZE];
173        self.chip.process(None, &mut raw)?;
174
175        // Apply lofi processing and write to output ports
176        let out0 = self.outputs[0].write();
177        for (j, s) in out0.iter_mut().enumerate() {
178            *s = self.lofi.process_sample(raw[j]);
179        }
180        // Copy channel 0 to additional output channels
181        let out0_copy = *self.outputs[0].read();
182        for port in self.outputs.iter_mut().skip(1) {
183            port.write().copy_from_slice(&out0_copy);
184        }
185
186        self.state.advance();
187        Ok(())
188    }
189}