Skip to main content

rill_sampler/
player.rs

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