1use super::common::{EdgeDetector, GATE_HIGH_V, GATE_THRESHOLD_V};
14use crate::port::{
15 GraphModule, ModulatedParam, ParamRange, PortDef, PortSpec, PortValues, SignalKind,
16};
17use alloc::vec;
18use alloc::vec::Vec;
19use libm::Libm;
20
21pub struct SamplePlayer {
34 buffer: Vec<f64>,
36 buffer_sample_rate: f64,
38 sample_rate: f64,
40 phase: f64,
42 playing: bool,
44 started_by_gate: bool,
47 trig_edge: EdgeDetector,
49 gate_edge: EdgeDetector,
51 pitch: ModulatedParam,
53 start: ModulatedParam,
55 spec: PortSpec,
56}
57
58impl SamplePlayer {
59 pub fn new(buffer: Vec<f64>, buffer_sample_rate: f64, engine_sample_rate: f64) -> Self {
62 Self {
63 buffer,
64 buffer_sample_rate: if buffer_sample_rate > 0.0 {
65 buffer_sample_rate
66 } else {
67 44100.0
68 },
69 sample_rate: if engine_sample_rate > 0.0 {
70 engine_sample_rate
71 } else {
72 44100.0
73 },
74 phase: 0.0,
75 playing: false,
76 started_by_gate: false,
77 trig_edge: EdgeDetector::new(),
78 gate_edge: EdgeDetector::new(),
79 pitch: ModulatedParam::new(ParamRange::VoltPerOctave { base_freq: 1.0 }),
80 start: ModulatedParam::new(ParamRange::Linear { min: 0.0, max: 1.0 }).with_base(0.0),
81 spec: PortSpec {
82 inputs: vec![
83 PortDef::new(0, "trig", SignalKind::Trigger),
84 PortDef::new(1, "gate", SignalKind::Gate),
85 PortDef::new(2, "voct", SignalKind::VoltPerOctave),
86 PortDef::new(3, "start", SignalKind::CvUnipolar)
87 .with_default(0.0)
88 .with_attenuverter(),
89 PortDef::new(4, "loop", SignalKind::Gate).with_default(0.0),
90 ],
91 outputs: vec![
92 PortDef::new(10, "out", SignalKind::Audio),
93 PortDef::new(11, "eos", SignalKind::Trigger),
94 ],
95 },
96 }
97 }
98
99 pub fn empty(engine_sample_rate: f64) -> Self {
101 Self::new(Vec::new(), engine_sample_rate, engine_sample_rate)
102 }
103
104 pub fn set_buffer(&mut self, buffer: Vec<f64>, buffer_sample_rate: f64) {
109 self.buffer = buffer;
110 if buffer_sample_rate > 0.0 {
111 self.buffer_sample_rate = buffer_sample_rate;
112 }
113 self.phase = 0.0;
114 self.playing = false;
115 self.started_by_gate = false;
116 }
117
118 pub fn len(&self) -> usize {
120 self.buffer.len()
121 }
122
123 pub fn is_empty(&self) -> bool {
125 self.buffer.is_empty()
126 }
127
128 pub fn set_start(&mut self, start: f64) {
131 self.start.base = start.clamp(0.0, 1.0);
132 }
133
134 pub fn start_position(&self) -> f64 {
136 self.start.base
137 }
138
139 fn read_cubic(&self, pos: f64) -> f64 {
142 let len = self.buffer.len();
143 if len == 0 {
144 return 0.0;
145 }
146 if len == 1 {
147 return self.buffer[0];
148 }
149 let i = Libm::<f64>::floor(pos) as isize;
150 let frac = pos - i as f64;
151 let last = (len - 1) as isize;
152 let sample = |k: isize| -> f64 {
153 let idx = (i + k).clamp(0, last) as usize;
154 self.buffer[idx]
155 };
156 let y0 = sample(-1);
157 let y1 = sample(0);
158 let y2 = sample(1);
159 let y3 = sample(2);
160 let a = -0.5 * y0 + 1.5 * y1 - 1.5 * y2 + 0.5 * y3;
161 let b = y0 - 2.5 * y1 + 2.0 * y2 - 0.5 * y3;
162 let c = -0.5 * y0 + 0.5 * y2;
163 let d = y1;
164 ((a * frac + b) * frac + c) * frac + d
165 }
166
167 fn start_sample(&self) -> f64 {
169 let len = self.buffer.len();
170 if len == 0 {
171 0.0
172 } else {
173 self.start.value().clamp(0.0, 1.0) * (len - 1) as f64
174 }
175 }
176}
177
178impl Default for SamplePlayer {
179 fn default() -> Self {
180 Self::empty(44100.0)
181 }
182}
183
184impl GraphModule for SamplePlayer {
185 fn port_spec(&self) -> &PortSpec {
186 &self.spec
187 }
188
189 fn tick(&mut self, inputs: &PortValues, outputs: &mut PortValues) {
190 let trig = inputs.get_or(0, 0.0);
191 let gate = inputs.get_or(1, 0.0);
192 let voct = inputs.get_or(2, 0.0);
193 let start_cv = inputs.get_or(3, 0.0);
194 let looping = inputs.get_or(4, 0.0) > GATE_THRESHOLD_V;
195
196 self.start.set_cv(start_cv);
199
200 self.pitch.base = voct;
203 let rate_mult = self.pitch.value();
204
205 let len = self.buffer.len();
206 let mut eos = 0.0;
207
208 let trig_edge = self.trig_edge.rising(trig);
212 let gate_edge = self.gate_edge.rising(gate);
213 if trig_edge {
214 self.phase = self.start_sample();
215 self.playing = len > 0;
216 self.started_by_gate = false;
217 } else if gate_edge {
218 self.phase = self.start_sample();
219 self.playing = len > 0;
220 self.started_by_gate = true;
221 }
222
223 if self.started_by_gate && gate <= GATE_THRESHOLD_V {
225 self.playing = false;
226 }
227
228 if len == 0 || !self.playing {
229 outputs.set(10, 0.0);
230 outputs.set(11, eos);
231 return;
232 }
233
234 let out = self.read_cubic(self.phase);
236
237 let rate = rate_mult * (self.buffer_sample_rate / self.sample_rate);
239 self.phase += rate;
240
241 let end = len as f64;
242 if self.phase >= end {
243 eos = GATE_HIGH_V;
244 if looping {
245 let start = self.start_sample();
253 let span = (end - start).max(1.0);
254 self.phase = start + Libm::<f64>::fmod(self.phase - start, span);
255 } else {
256 self.playing = false;
257 self.phase = end;
258 }
259 }
260
261 outputs.set(10, out);
262 outputs.set(11, eos);
263 }
264
265 fn reset(&mut self) {
266 self.phase = 0.0;
267 self.playing = false;
268 self.started_by_gate = false;
269 self.trig_edge.reset();
270 self.gate_edge.reset();
271 }
272
273 fn set_sample_rate(&mut self, sample_rate: f64) {
274 if sample_rate > 0.0 {
275 self.sample_rate = sample_rate;
276 }
277 }
278
279 fn type_id(&self) -> &'static str {
280 "sample_player"
281 }
282}
283
284#[cfg(test)]
285mod tests {
286 use super::*;
287
288 fn impulse_buffer(spacing: usize, count: usize) -> Vec<f64> {
290 let mut buf = vec![0.0; spacing * count];
291 for k in 0..count {
292 buf[k * spacing] = 1.0;
293 }
294 buf
295 }
296
297 fn trigger_once(player: &mut SamplePlayer, inputs: &mut PortValues, outputs: &mut PortValues) {
298 inputs.set(0, 0.0);
300 player.tick(inputs, outputs);
301 inputs.set(0, 5.0);
302 player.tick(inputs, outputs);
303 }
304
305 #[test]
306 fn test_unity_rate_impulse_spacing() {
307 let sr = 48000.0;
309 let mut player = SamplePlayer::new(impulse_buffer(4, 6), sr, sr);
310 let mut inputs = PortValues::new();
311 let mut outputs = PortValues::new();
312 inputs.set(2, 0.0); trigger_once(&mut player, &mut inputs, &mut outputs);
315 let mut impulse_positions = Vec::new();
317 let first = outputs.get(10).unwrap();
319 if first > 0.5 {
320 impulse_positions.push(0);
321 }
322 for i in 1..20 {
323 player.tick(&inputs, &mut outputs);
324 if outputs.get(10).unwrap() > 0.5 {
325 impulse_positions.push(i);
326 }
327 }
328 assert!(impulse_positions.len() >= 3);
330 assert_eq!(impulse_positions[0], 0);
331 assert_eq!(impulse_positions[1], 4);
332 assert_eq!(impulse_positions[2], 8);
333 }
334
335 #[test]
336 fn test_plus_one_volt_doubles_speed() {
337 let sr = 48000.0;
338 let mut player = SamplePlayer::new(impulse_buffer(4, 6), sr, sr);
339 let mut inputs = PortValues::new();
340 let mut outputs = PortValues::new();
341 inputs.set(2, 1.0); trigger_once(&mut player, &mut inputs, &mut outputs);
344 let mut impulse_positions = Vec::new();
345 if outputs.get(10).unwrap() > 0.5 {
346 impulse_positions.push(0);
347 }
348 for i in 1..20 {
349 player.tick(&inputs, &mut outputs);
350 if outputs.get(10).unwrap() > 0.5 {
351 impulse_positions.push(i);
352 }
353 }
354 assert!(impulse_positions.len() >= 3);
356 assert_eq!(impulse_positions[0], 0);
357 assert_eq!(impulse_positions[1], 2);
358 assert_eq!(impulse_positions[2], 4);
359 }
360
361 #[test]
362 fn test_loop_wraps() {
363 let sr = 48000.0;
364 let mut player = SamplePlayer::new(impulse_buffer(2, 3), sr, sr); let mut inputs = PortValues::new();
367 let mut outputs = PortValues::new();
368 inputs.set(2, 0.0);
369 inputs.set(4, 5.0); trigger_once(&mut player, &mut inputs, &mut outputs);
372 let mut impulses = 0;
373 for _ in 0..60 {
374 player.tick(&inputs, &mut outputs);
375 if outputs.get(10).unwrap() > 0.5 {
376 impulses += 1;
377 }
378 }
379 assert!(impulses > 6, "loop did not wrap: {impulses} impulses");
381 }
382
383 #[test]
384 fn test_loop_wrap_bounded_at_pathological_rate() {
385 let sr = 48000.0;
391 let mut player = SamplePlayer::new(impulse_buffer(1, 8), sr, sr); let mut inputs = PortValues::new();
393 let mut outputs = PortValues::new();
394 player.set_start(1.0);
396 inputs.set(4, 5.0); inputs.set(2, 60.0); trigger_once(&mut player, &mut inputs, &mut outputs);
400 for _ in 0..100 {
402 player.tick(&inputs, &mut outputs);
403 assert!(
404 player.phase.is_finite()
405 && player.phase >= 0.0
406 && player.phase < player.len() as f64,
407 "phase escaped the loop region: {}",
408 player.phase
409 );
410 assert!(outputs.get(10).unwrap().is_finite());
411 }
412 }
413
414 #[test]
415 fn test_eos_fires_once_at_end() {
416 let sr = 48000.0;
417 let mut player = SamplePlayer::new(impulse_buffer(1, 8), sr, sr); let mut inputs = PortValues::new();
419 let mut outputs = PortValues::new();
420 inputs.set(2, 0.0);
421
422 trigger_once(&mut player, &mut inputs, &mut outputs);
423 let mut eos_count = 0;
424 for _ in 0..40 {
425 player.tick(&inputs, &mut outputs);
426 if outputs.get(11).unwrap() > GATE_THRESHOLD_V {
427 eos_count += 1;
428 }
429 }
430 assert_eq!(eos_count, 1, "eos should fire exactly once at end");
431 }
432
433 #[test]
434 fn test_empty_buffer_silent() {
435 let mut player = SamplePlayer::empty(48000.0);
436 let mut inputs = PortValues::new();
437 let mut outputs = PortValues::new();
438 assert!(player.is_empty());
439 assert_eq!(player.len(), 0);
440
441 trigger_once(&mut player, &mut inputs, &mut outputs);
442 for _ in 0..50 {
443 player.tick(&inputs, &mut outputs);
444 assert_eq!(outputs.get(10).unwrap(), 0.0);
445 }
446 }
447
448 #[test]
449 fn test_gated_playback_stops_on_release() {
450 let sr = 48000.0;
451 let mut player = SamplePlayer::new(impulse_buffer(1, 64), sr, sr);
452 let mut inputs = PortValues::new();
453 let mut outputs = PortValues::new();
454 inputs.set(2, 0.0);
455
456 inputs.set(1, 0.0);
458 player.tick(&inputs, &mut outputs);
459 inputs.set(1, 5.0);
460 player.tick(&inputs, &mut outputs);
461 assert!(player.playing);
462
463 inputs.set(1, 0.0);
465 player.tick(&inputs, &mut outputs);
466 assert!(!player.playing);
467 assert_eq!(outputs.get(10).unwrap(), 0.0);
468 }
469
470 #[test]
471 fn test_type_id_and_default() {
472 let player = SamplePlayer::default();
473 assert_eq!(player.type_id(), "sample_player");
474 assert!(player.is_empty());
475 }
476
477 #[test]
478 fn test_set_buffer_swaps() {
479 let mut player = SamplePlayer::empty(48000.0);
480 assert!(player.is_empty());
481 player.set_buffer(vec![0.5; 100], 48000.0);
482 assert_eq!(player.len(), 100);
483 }
484}