Skip to main content

rill_lofi/emulators/
akai_s900.rs

1use crate::config::LofiConfig;
2use crate::lofi_processor::LofiProcessor;
3use rill_core::prelude::*;
4
5/// Emulates the Akai S900 hardware sampler, including its 12-bit DAC, analog
6/// filters, and sample interpolation with pitch shifting and loop support.
7pub struct AkaiS900Emulator<const BUF_SIZE: usize> {
8    state: NodeState<f32, BUF_SIZE>,
9    id: NodeId,
10    metadata: NodeMetadata,
11    outputs: Vec<Port<f32, BUF_SIZE>>,
12
13    buffer: Vec<f32>,
14    position: f32,
15    pitch: f32,
16    loop_enabled: bool,
17    loop_start: usize,
18    loop_end: usize,
19    lofi: LofiProcessor<BUF_SIZE>,
20}
21
22impl<const BUF_SIZE: usize> AkaiS900Emulator<BUF_SIZE> {
23    /// Creates a new `AkaiS900Emulator` configured with the S900's 12-bit
24    /// nonlinear DAC and default pitch / looping parameters.
25    pub fn new(_sample_rate: f32) -> Self {
26        let lofi_config = LofiConfig::for_system(crate::config::ClassicSystem::AkaiS900);
27        let id = NodeId(0);
28        let state = NodeState::new(_sample_rate);
29
30        let outputs = vec![Port::output(id, 0, "signal_out")];
31
32        Self {
33            state,
34            id,
35            metadata: NodeMetadata {
36                name: "Akai S900".to_string(),
37
38                type_name: None,
39                category: NodeCategory::Source,
40                description: "Akai S900 sampler emulation".to_string(),
41                author: "Rill Lo-Fi".to_string(),
42                version: "1.0".to_string(),
43                signal_inputs: 0,
44                signal_outputs: 1,
45                control_inputs: 0,
46                control_outputs: 0,
47                clock_inputs: 0,
48                clock_outputs: 0,
49                feedback_ports: 0,
50                parameters: vec![
51                    ParamMetadata::new("pitch", ParamType::Float, ParamValue::Float(1.0))
52                        .with_description("Playback pitch")
53                        .with_range(0.1, 4.0, 0.01)
54                        .with_unit("x"),
55                    ParamMetadata::new("loop_enabled", ParamType::Bool, ParamValue::Bool(false))
56                        .with_description("Enable sample looping"),
57                ],
58            },
59            outputs,
60            buffer: Vec::new(),
61            position: 0.0,
62            pitch: 1.0,
63            loop_enabled: false,
64            loop_start: 0,
65            loop_end: 0,
66            lofi: LofiProcessor::new(lofi_config),
67        }
68    }
69
70    /// Loads a sample buffer into the emulator and sets the loop end point to
71    /// the buffer length.
72    pub fn load_sample(&mut self, samples: &[f32]) {
73        self.buffer = samples.to_vec();
74        self.loop_end = samples.len();
75    }
76
77    /// Sets the playback pitch multiplier, clamped to the valid range `[0.1, 4.0]`.
78    pub fn set_pitch(&mut self, pitch: f32) {
79        self.pitch = pitch.clamp(0.1, 4.0);
80    }
81
82    fn generate_sample(&mut self) -> f32 {
83        if self.buffer.is_empty() {
84            return 0.0;
85        }
86
87        if (self.position as usize) >= self.buffer.len() {
88            return 0.0;
89        }
90
91        let sample = if (self.position as usize) < self.buffer.len() - 1 {
92            let idx = self.position.floor() as usize;
93            let frac = self.position.fract();
94            self.buffer[idx] * (1.0 - frac) + self.buffer[idx + 1] * frac
95        } else {
96            self.buffer[self.position as usize]
97        };
98
99        let processed = self.lofi.process_sample(sample);
100
101        self.position += self.pitch;
102
103        if self.loop_enabled && (self.position as usize) >= self.loop_end {
104            self.position = self.loop_start as f32 + (self.position - self.loop_end as f32);
105        }
106
107        processed
108    }
109}
110
111impl<const BUF_SIZE: usize> Node<f32, BUF_SIZE> for AkaiS900Emulator<BUF_SIZE> {
112    fn metadata(&self) -> NodeMetadata {
113        self.metadata.clone()
114    }
115
116    fn node_type_id(&self) -> NodeTypeId {
117        NodeTypeId::of::<Self>()
118    }
119
120    fn init(&mut self, sample_rate: f32) {
121        self.state = NodeState::new(sample_rate);
122        self.lofi.init(sample_rate);
123    }
124
125    fn reset(&mut self) {
126        self.state.reset();
127        self.position = 0.0;
128        self.lofi.reset();
129    }
130
131    fn get_parameter(&self, id: &ParameterId) -> Option<ParamValue> {
132        match id.as_str() {
133            "pitch" => Some(ParamValue::Float(self.pitch)),
134            "loop_enabled" => Some(ParamValue::Bool(self.loop_enabled)),
135            _ => None,
136        }
137    }
138
139    fn set_parameter(&mut self, id: &ParameterId, value: ParamValue) -> ProcessResult<()> {
140        match id.as_str() {
141            "pitch" => {
142                if let ParamValue::Float(v) = value {
143                    self.pitch = v.clamp(0.1, 4.0);
144                    Ok(())
145                } else {
146                    Err(ProcessError::parameter("pitch must be a float"))
147                }
148            }
149            "loop_enabled" => {
150                if let ParamValue::Bool(v) = value {
151                    self.loop_enabled = v;
152                    Ok(())
153                } else {
154                    Err(ProcessError::parameter("loop_enabled must be a bool"))
155                }
156            }
157            _ => Err(ProcessError::parameter(format!(
158                "Unknown parameter: {}",
159                id
160            ))),
161        }
162    }
163
164    fn id(&self) -> NodeId {
165        self.id
166    }
167    fn set_id(&mut self, id: NodeId) {
168        self.id = id;
169    }
170
171    fn input_port(&self, _index: usize) -> Option<&Port<f32, BUF_SIZE>> {
172        None
173    }
174    fn input_port_mut(&mut self, _index: usize) -> Option<&mut Port<f32, BUF_SIZE>> {
175        None
176    }
177
178    fn output_port(&self, index: usize) -> Option<&Port<f32, BUF_SIZE>> {
179        self.outputs.get(index)
180    }
181
182    fn output_port_mut(&mut self, index: usize) -> Option<&mut Port<f32, BUF_SIZE>> {
183        self.outputs.get_mut(index)
184    }
185
186    fn control_port(&self, _index: usize) -> Option<&Port<f32, BUF_SIZE>> {
187        None
188    }
189    fn control_port_mut(&mut self, _index: usize) -> Option<&mut Port<f32, BUF_SIZE>> {
190        None
191    }
192
193    fn state(&self) -> &NodeState<f32, BUF_SIZE> {
194        &self.state
195    }
196    fn state_mut(&mut self) -> &mut NodeState<f32, BUF_SIZE> {
197        &mut self.state
198    }
199
200    fn num_signal_inputs(&self) -> usize {
201        0
202    }
203    fn num_signal_outputs(&self) -> usize {
204        1
205    }
206}
207
208impl<const BUF_SIZE: usize> Source<f32, BUF_SIZE> for AkaiS900Emulator<BUF_SIZE> {
209    fn generate(
210        &mut self,
211        _ctx: &RenderContext,
212        _control_inputs: &[f32],
213        _clock_inputs: &[RenderContext],
214        _tick: &ClockTick,
215    ) -> ProcessResult<()> {
216        for i in 0..BUF_SIZE {
217            self.outputs[0].write()[i] = self.generate_sample();
218        }
219        Ok(())
220    }
221}