Skip to main content

rill_sampler/
player.rs

1use rill_core::time::ClockTick;
2use rill_core::traits::{
3    Algorithm, Node, NodeCategory, NodeId, NodeMetadata, NodeState, ParamValue, ParameterId, Port,
4    Source,
5};
6use rill_core::Transcendental;
7use rill_core::{ProcessError, ProcessResult};
8use rill_core_dsp::generators::{Generator, LoopMode, SamplePlayer};
9use std::marker::PhantomData;
10
11use crate::buffer::SampleBuffer;
12#[cfg(feature = "wav")]
13use crate::wav::load_wav;
14
15/// Sample-playback source node with stereo support.
16///
17/// # Parameters (all automatable via patchbay)
18///
19/// | Name | Type | Range | Description |
20/// |---|---|---|---|
21/// | `"gate"` | Bool | – | Start / stop playback |
22/// | `"rate"` | Float | 0.0–4.0 | Playback speed ratio |
23/// | `"loop_mode"` | Choice | oneshot/forward/pingpong | Loop behaviour |
24/// | `"start"` | Float | 0.0–1.0 | Loop start (normalised) |
25/// | `"end"` | Float | 0.0–1.0 | Loop end (normalised) |
26/// | `"amplitude"` | Float | 0.0–1.0 | Output gain |
27/// | `"interpolation"` | Choice | linear/cubic | Interpolation mode |
28/// | `"position"` | Float | 0.0–1.0 | Current position **(read-only)** |
29///
30/// # Output ports
31/// - Port 0: left channel
32/// - Port 1: right channel (only present when a stereo sample is loaded)
33pub struct SamplePlayerNode<T: Transcendental, const BUF_SIZE: usize> {
34    left: SamplePlayer<T>,
35    right: Option<SamplePlayer<T>>,
36    gate: bool,
37    amplitude: T,
38    rate: f64,
39    loop_mode: LoopMode,
40    loop_start: f64,
41    loop_end: f64,
42    cubic: bool,
43    outputs: Vec<Port<T, BUF_SIZE>>,
44    state: Option<NodeState<T, BUF_SIZE>>,
45    _phantom: PhantomData<[T; BUF_SIZE]>,
46}
47
48impl<T: Transcendental, const BUF_SIZE: usize> SamplePlayerNode<T, BUF_SIZE> {
49    /// Create a new node with an empty sample buffer.
50    pub fn new() -> Self {
51        Self {
52            left: SamplePlayer::new(Vec::new()),
53            right: None,
54            gate: false,
55            amplitude: T::from_f32(1.0),
56            rate: 1.0,
57            loop_mode: LoopMode::OneShot,
58            loop_start: 0.0,
59            loop_end: 0.0,
60            cubic: false,
61            outputs: vec![
62                Port::output(NodeId(0), 0, "left"),
63                Port::output(NodeId(0), 1, "right"),
64            ],
65            state: None,
66            _phantom: PhantomData,
67        }
68    }
69
70    /// Load a sample buffer into the node.
71    pub fn load(&mut self, sample: SampleBuffer<T>) {
72        let len = sample.len() as f64;
73        self.loop_end = len;
74        self.loop_start = 0.0;
75
76        self.left.set_buffer(sample.data);
77        self.left.set_loop_start(self.loop_start);
78        self.left.set_loop_end(self.loop_end);
79        self.left.set_loop_mode(self.loop_mode);
80        self.left.set_playback_rate(self.rate);
81        self.left.set_cubic(self.cubic);
82
83        if let Some(right_data) = sample.right {
84            let mut right_player = SamplePlayer::new(right_data);
85            right_player.set_loop_start(self.loop_start);
86            right_player.set_loop_end(self.loop_end);
87            right_player.set_loop_mode(self.loop_mode);
88            right_player.set_playback_rate(self.rate);
89            right_player.set_cubic(self.cubic);
90            self.right = Some(right_player);
91
92            if self.outputs.len() < 2 {
93                self.outputs.push(Port::output(NodeId(0), 1, "right"));
94            }
95        } else {
96            self.right = None;
97            self.outputs.truncate(1);
98        }
99    }
100
101    /// Start / stop playback.
102    pub fn play(&mut self) {
103        self.gate = true;
104        self.left.set_gate(true);
105        if let Some(ref mut r) = self.right {
106            r.set_gate(true);
107        }
108    }
109
110    /// Stop playback (sets gate to false).
111    pub fn stop(&mut self) {
112        self.gate = false;
113        self.left.set_gate(false);
114        if let Some(ref mut r) = self.right {
115            r.set_gate(false);
116        }
117    }
118
119    fn param_to_t(value: ParamValue) -> Option<T> {
120        match value {
121            ParamValue::Float(f) => Some(T::from_f32(f)),
122            ParamValue::Int(i) => Some(T::from_f32(i as f32)),
123            _ => None,
124        }
125    }
126
127    fn t_to_param(value: T) -> ParamValue {
128        ParamValue::Float(value.to_f32())
129    }
130}
131
132impl<T: Transcendental, const BUF_SIZE: usize> Default for SamplePlayerNode<T, BUF_SIZE> {
133    fn default() -> Self {
134        Self::new()
135    }
136}
137
138impl<T: Transcendental, const BUF_SIZE: usize> Node<T, BUF_SIZE> for SamplePlayerNode<T, BUF_SIZE> {
139    fn metadata(&self) -> NodeMetadata {
140        NodeMetadata {
141            name: "SamplePlayer".to_string(),
142            type_name: None,
143            category: NodeCategory::Source,
144            description: "Sample playback node with loop modes and stereo".to_string(),
145            author: "Rill".to_string(),
146            version: env!("CARGO_PKG_VERSION").to_string(),
147            signal_inputs: 0,
148            signal_outputs: self.outputs.len(),
149            control_inputs: 0,
150            control_outputs: 0,
151            clock_inputs: 0,
152            clock_outputs: 0,
153            feedback_ports: 0,
154            parameters: vec![],
155        }
156    }
157
158    fn init(&mut self, sample_rate: f32) {
159        self.left.init(sample_rate);
160        if let Some(ref mut r) = self.right {
161            r.init(sample_rate);
162        }
163        self.state = Some(NodeState::new(sample_rate));
164    }
165
166    fn reset(&mut self) {
167        self.left.reset();
168        if let Some(ref mut r) = self.right {
169            r.reset();
170        }
171        self.gate = false;
172        if let Some(state) = &mut self.state {
173            state.reset();
174        }
175    }
176
177    fn get_parameter(&self, id: &ParameterId) -> Option<ParamValue> {
178        match id.as_str() {
179            "gate" => Some(ParamValue::Bool(self.gate)),
180            "rate" => Some(ParamValue::Float(self.rate as f32)),
181            "loop_mode" => {
182                let s = match self.loop_mode {
183                    LoopMode::OneShot => "oneshot",
184                    LoopMode::Forward => "forward",
185                    LoopMode::PingPong => "pingpong",
186                };
187                Some(ParamValue::Choice(s.into()))
188            }
189            "start" => {
190                let len = self.left.len().max(1) as f64;
191                Some(ParamValue::Float((self.loop_start / len) as f32))
192            }
193            "end" => {
194                let len = self.left.len().max(1) as f64;
195                Some(ParamValue::Float((self.loop_end / len) as f32))
196            }
197            "amplitude" => Some(Self::t_to_param(self.amplitude)),
198            "interpolation" => Some(ParamValue::Choice(
199                if self.cubic { "cubic" } else { "linear" }.into(),
200            )),
201            "position" => Some(ParamValue::Float(self.left.phase().to_f32())),
202            _ => None,
203        }
204    }
205
206    fn set_parameter(&mut self, id: &ParameterId, value: ParamValue) -> ProcessResult<()> {
207        let len = self.left.len().max(1) as f64;
208        match id.as_str() {
209            "gate" => {
210                if let ParamValue::Bool(b) = value {
211                    self.gate = b;
212                    self.left.set_gate(b);
213                    if let Some(ref mut r) = self.right {
214                        r.set_gate(b);
215                    }
216                    Ok(())
217                } else {
218                    Err(ProcessError::Parameter("Expected bool".into()))
219                }
220            }
221            "rate" => {
222                if let Some(r) = Self::param_to_t(value) {
223                    self.rate = r.to_f64().clamp(0.0, 4.0);
224                    self.left.set_playback_rate(self.rate);
225                    if let Some(ref mut rp) = self.right {
226                        rp.set_playback_rate(self.rate);
227                    }
228                    Ok(())
229                } else {
230                    Err(ProcessError::Parameter("Expected float".into()))
231                }
232            }
233            "loop_mode" => {
234                if let ParamValue::Choice(s) = &value {
235                    self.loop_mode = match s.as_str() {
236                        "forward" => LoopMode::Forward,
237                        "pingpong" => LoopMode::PingPong,
238                        _ => LoopMode::OneShot,
239                    };
240                    self.left.set_loop_mode(self.loop_mode);
241                    if let Some(ref mut r) = self.right {
242                        r.set_loop_mode(self.loop_mode);
243                    }
244                    Ok(())
245                } else {
246                    Err(ProcessError::Parameter("Expected choice".into()))
247                }
248            }
249            "start" => {
250                if let Some(s) = Self::param_to_t(value) {
251                    self.loop_start = (s.to_f64() * len).clamp(0.0, self.loop_end);
252                    self.left.set_loop_start(self.loop_start);
253                    if let Some(ref mut r) = self.right {
254                        r.set_loop_start(self.loop_start);
255                    }
256                    Ok(())
257                } else {
258                    Err(ProcessError::Parameter("Expected float".into()))
259                }
260            }
261            "end" => {
262                if let Some(e) = Self::param_to_t(value) {
263                    self.loop_end = (e.to_f64() * len).clamp(self.loop_start, len);
264                    self.left.set_loop_end(self.loop_end);
265                    if let Some(ref mut r) = self.right {
266                        r.set_loop_end(self.loop_end);
267                    }
268                    Ok(())
269                } else {
270                    Err(ProcessError::Parameter("Expected float".into()))
271                }
272            }
273            "amplitude" => {
274                if let Some(a) = Self::param_to_t(value) {
275                    self.amplitude = a.clamp(T::ZERO, T::from_f32(1.0));
276                    Ok(())
277                } else {
278                    Err(ProcessError::Parameter("Expected float".into()))
279                }
280            }
281            "interpolation" => {
282                if let ParamValue::Choice(s) = &value {
283                    self.cubic = s == "cubic";
284                    self.left.set_cubic(self.cubic);
285                    if let Some(ref mut r) = self.right {
286                        r.set_cubic(self.cubic);
287                    }
288                    Ok(())
289                } else {
290                    Err(ProcessError::Parameter("Expected choice".into()))
291                }
292            }
293            #[cfg(feature = "wav")]
294            "file" => {
295                if let ParamValue::String(path) = &value {
296                    match load_wav(path) {
297                        Ok(sample) => {
298                            let converted = SampleBuffer {
299                                data: sample.data.into_iter().map(|s| T::from_f32(s)).collect(),
300                                right: sample
301                                    .right
302                                    .map(|r| r.into_iter().map(|s| T::from_f32(s)).collect()),
303                                sample_rate: sample.sample_rate,
304                                channels: sample.channels,
305                                name: sample.name,
306                            };
307                            self.load(converted);
308                            self.gate = true;
309                            self.left.set_gate(true);
310                            if let Some(ref mut r) = self.right {
311                                r.set_gate(true);
312                            }
313                            eprintln!("SamplePlayer: loaded {path}");
314                            Ok(())
315                        }
316                        Err(e) => {
317                            eprintln!("SamplePlayer: could not load {path}: {e}");
318                            Err(ProcessError::Parameter(format!(
319                                "Cannot load {}: {}",
320                                path, e
321                            )))
322                        }
323                    }
324                } else {
325                    Err(ProcessError::Parameter("Expected string path".into()))
326                }
327            }
328            _ => Err(ProcessError::Parameter(format!(
329                "Unknown parameter: {}",
330                id
331            ))),
332        }
333    }
334
335    fn id(&self) -> NodeId {
336        NodeId(0)
337    }
338
339    fn set_id(&mut self, _id: NodeId) {}
340
341    fn input_port(&self, _index: usize) -> Option<&Port<T, BUF_SIZE>> {
342        None
343    }
344
345    fn input_port_mut(&mut self, _index: usize) -> Option<&mut Port<T, BUF_SIZE>> {
346        None
347    }
348
349    fn output_port(&self, index: usize) -> Option<&Port<T, BUF_SIZE>> {
350        self.outputs.get(index)
351    }
352
353    fn output_port_mut(&mut self, index: usize) -> Option<&mut Port<T, BUF_SIZE>> {
354        self.outputs.get_mut(index)
355    }
356
357    fn control_port(&self, _index: usize) -> Option<&Port<T, BUF_SIZE>> {
358        None
359    }
360
361    fn control_port_mut(&mut self, _index: usize) -> Option<&mut Port<T, BUF_SIZE>> {
362        None
363    }
364
365    fn state(&self) -> &NodeState<T, BUF_SIZE> {
366        self.state.as_ref().unwrap()
367    }
368
369    fn state_mut(&mut self) -> &mut NodeState<T, BUF_SIZE> {
370        self.state.as_mut().unwrap()
371    }
372
373    fn num_signal_inputs(&self) -> usize {
374        0
375    }
376
377    fn num_signal_outputs(&self) -> usize {
378        self.outputs.len()
379    }
380}
381
382impl<T: Transcendental, const BUF_SIZE: usize> Source<T, BUF_SIZE>
383    for SamplePlayerNode<T, BUF_SIZE>
384{
385    fn generate(
386        &mut self,
387        clock: &ClockTick,
388        _control_inputs: &[T],
389        _clock_inputs: &[ClockTick],
390    ) -> ProcessResult<()> {
391        let amp = self.amplitude;
392
393        let mut temp = [T::ZERO; BUF_SIZE];
394        self.left.process(
395            None,
396            &mut temp[..],
397            &rill_core::traits::ActionContext::new(clock),
398        )?;
399        if amp != T::from_f32(1.0) {
400            for s in temp.iter_mut() {
401                *s *= amp;
402            }
403        }
404        *self.outputs[0].buffer.as_mut_array() = temp;
405
406        if let Some(ref mut right_player) = self.right {
407            let mut right_temp = [T::ZERO; BUF_SIZE];
408            right_player.process(
409                None,
410                &mut right_temp[..],
411                &rill_core::traits::ActionContext::new(clock),
412            )?;
413            if amp != T::from_f32(1.0) {
414                for s in right_temp.iter_mut() {
415                    *s *= amp;
416                }
417            }
418            if self.outputs.len() > 1 {
419                *self.outputs[1].buffer.as_mut_array() = right_temp;
420            }
421        }
422
423        Ok(())
424    }
425}
426
427#[cfg(test)]
428mod tests {
429    use super::*;
430    use rill_core::traits::Node;
431
432    #[test]
433    fn test_set_and_get_parameter() {
434        const B: usize = 64;
435        let mut player = SamplePlayerNode::<f32, B>::new();
436
437        // Set rate → verify via get
438        let pid = ParameterId::new("rate").unwrap();
439        let _ = player.set_parameter(&pid, ParamValue::Float(2.0));
440        let val = player.get_parameter(&pid);
441        assert_eq!(val, Some(ParamValue::Float(2.0)));
442
443        // Set amplitude → verify via get
444        let pid = ParameterId::new("amplitude").unwrap();
445        let _ = player.set_parameter(&pid, ParamValue::Float(0.75));
446        let val = player.get_parameter(&pid);
447        assert_eq!(val, Some(ParamValue::Float(0.75)));
448
449        // Gate on/off → verify via get
450        let pid = ParameterId::new("gate").unwrap();
451        let _ = player.set_parameter(&pid, ParamValue::Bool(true));
452        let val = player.get_parameter(&pid);
453        assert_eq!(val, Some(ParamValue::Bool(true)));
454
455        // Unknown parameter → error on set, None on get
456        let unknown = ParameterId::new("nonexistent").unwrap();
457        let result = player.set_parameter(&unknown, ParamValue::Float(0.0));
458        assert!(result.is_err());
459        assert!(player.get_parameter(&unknown).is_none());
460    }
461}