1use super::common::{EdgeDetector, Memo, 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 rate_memo: Memo<3, f64>,
60 spec: PortSpec,
61}
62
63impl SamplePlayer {
64 pub fn new(buffer: Vec<f64>, buffer_sample_rate: f64, engine_sample_rate: f64) -> Self {
67 Self {
68 buffer,
69 buffer_sample_rate: if buffer_sample_rate > 0.0 {
70 buffer_sample_rate
71 } else {
72 44100.0
73 },
74 sample_rate: if engine_sample_rate > 0.0 {
75 engine_sample_rate
76 } else {
77 44100.0
78 },
79 phase: 0.0,
80 playing: false,
81 started_by_gate: false,
82 trig_edge: EdgeDetector::new(),
83 gate_edge: EdgeDetector::new(),
84 pitch: ModulatedParam::new(ParamRange::VoltPerOctave { base_freq: 1.0 }),
85 start: ModulatedParam::new(ParamRange::Linear { min: 0.0, max: 1.0 }).with_base(0.0),
86 rate_memo: Memo::new(0.0),
87 spec: PortSpec {
88 inputs: vec![
89 PortDef::new(0, "trig", SignalKind::Trigger),
90 PortDef::new(1, "gate", SignalKind::Gate),
91 PortDef::new(2, "voct", SignalKind::VoltPerOctave),
92 PortDef::new(3, "start", SignalKind::CvUnipolar)
93 .with_default(0.0)
94 .with_attenuverter(),
95 PortDef::new(4, "loop", SignalKind::Gate).with_default(0.0),
96 ],
97 outputs: vec![
98 PortDef::new(10, "out", SignalKind::Audio),
99 PortDef::new(11, "eos", SignalKind::Trigger),
100 ],
101 },
102 }
103 }
104
105 pub fn empty(engine_sample_rate: f64) -> Self {
107 Self::new(Vec::new(), engine_sample_rate, engine_sample_rate)
108 }
109
110 pub fn set_buffer(&mut self, buffer: Vec<f64>, buffer_sample_rate: f64) {
115 self.buffer = buffer;
116 if buffer_sample_rate > 0.0 {
117 self.buffer_sample_rate = buffer_sample_rate;
118 }
119 self.phase = 0.0;
120 self.playing = false;
121 self.started_by_gate = false;
122 }
123
124 pub fn len(&self) -> usize {
126 self.buffer.len()
127 }
128
129 pub fn is_empty(&self) -> bool {
131 self.buffer.is_empty()
132 }
133
134 pub fn set_start(&mut self, start: f64) {
137 self.start.base = start.clamp(0.0, 1.0);
138 }
139
140 pub fn start_position(&self) -> f64 {
142 self.start.base
143 }
144
145 fn read_cubic(&self, pos: f64) -> f64 {
148 let len = self.buffer.len();
149 if len == 0 {
150 return 0.0;
151 }
152 if len == 1 {
153 return self.buffer[0];
154 }
155 let i = Libm::<f64>::floor(pos) as isize;
156 let frac = pos - i as f64;
157 let last = (len - 1) as isize;
158 let sample = |k: isize| -> f64 {
159 let idx = (i + k).clamp(0, last) as usize;
160 self.buffer[idx]
161 };
162 let y0 = sample(-1);
163 let y1 = sample(0);
164 let y2 = sample(1);
165 let y3 = sample(2);
166 let a = -0.5 * y0 + 1.5 * y1 - 1.5 * y2 + 0.5 * y3;
167 let b = y0 - 2.5 * y1 + 2.0 * y2 - 0.5 * y3;
168 let c = -0.5 * y0 + 0.5 * y2;
169 let d = y1;
170 ((a * frac + b) * frac + c) * frac + d
171 }
172
173 fn start_sample(&self) -> f64 {
175 let len = self.buffer.len();
176 if len == 0 {
177 0.0
178 } else {
179 self.start.value().clamp(0.0, 1.0) * (len - 1) as f64
180 }
181 }
182}
183
184impl Default for SamplePlayer {
185 fn default() -> Self {
186 Self::empty(44100.0)
187 }
188}
189
190impl GraphModule for SamplePlayer {
191 fn port_spec(&self) -> &PortSpec {
192 &self.spec
193 }
194
195 fn tick(&mut self, inputs: &PortValues, outputs: &mut PortValues) {
196 let trig = inputs.get_or(0, 0.0);
197 let gate = inputs.get_or(1, 0.0);
198 let voct = inputs.get_or(2, 0.0);
199 let start_cv = inputs.get_or(3, 0.0);
200 let looping = inputs.get_or(4, 0.0) > GATE_THRESHOLD_V;
201
202 self.start.set_cv(start_cv);
205
206 self.pitch.base = voct;
210 let pitch = &self.pitch;
211 let rate_mult = self
212 .rate_memo
213 .get_or_compute([pitch.base, pitch.cv, pitch.attenuverter], || pitch.value());
214
215 let len = self.buffer.len();
216 let mut eos = 0.0;
217
218 let trig_edge = self.trig_edge.rising(trig);
222 let gate_edge = self.gate_edge.rising(gate);
223 if trig_edge {
224 self.phase = self.start_sample();
225 self.playing = len > 0;
226 self.started_by_gate = false;
227 } else if gate_edge {
228 self.phase = self.start_sample();
229 self.playing = len > 0;
230 self.started_by_gate = true;
231 }
232
233 if self.started_by_gate && gate <= GATE_THRESHOLD_V {
235 self.playing = false;
236 }
237
238 if len == 0 || !self.playing {
239 outputs.set(10, 0.0);
240 outputs.set(11, eos);
241 return;
242 }
243
244 let out = self.read_cubic(self.phase);
246
247 let rate = rate_mult * (self.buffer_sample_rate / self.sample_rate);
249 self.phase += rate;
250
251 let end = len as f64;
252 if self.phase >= end {
253 eos = GATE_HIGH_V;
254 if looping {
255 let start = self.start_sample();
263 let span = (end - start).max(1.0);
264 self.phase = start + Libm::<f64>::fmod(self.phase - start, span);
265 } else {
266 self.playing = false;
267 self.phase = end;
268 }
269 }
270
271 outputs.set(10, out);
272 outputs.set(11, eos);
273 }
274
275 fn reset(&mut self) {
276 self.phase = 0.0;
277 self.playing = false;
278 self.started_by_gate = false;
279 self.trig_edge.reset();
280 self.gate_edge.reset();
281 }
282
283 fn set_sample_rate(&mut self, sample_rate: f64) {
284 if sample_rate > 0.0 {
285 self.sample_rate = sample_rate;
286 }
287 }
288
289 fn type_id(&self) -> &'static str {
290 "sample_player"
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297
298 fn impulse_buffer(spacing: usize, count: usize) -> Vec<f64> {
300 let mut buf = vec![0.0; spacing * count];
301 for k in 0..count {
302 buf[k * spacing] = 1.0;
303 }
304 buf
305 }
306
307 fn trigger_once(player: &mut SamplePlayer, inputs: &mut PortValues, outputs: &mut PortValues) {
308 inputs.set(0, 0.0);
310 player.tick(inputs, outputs);
311 inputs.set(0, 5.0);
312 player.tick(inputs, outputs);
313 }
314
315 #[test]
316 fn test_unity_rate_impulse_spacing() {
317 let sr = 48000.0;
319 let mut player = SamplePlayer::new(impulse_buffer(4, 6), sr, sr);
320 let mut inputs = PortValues::new();
321 let mut outputs = PortValues::new();
322 inputs.set(2, 0.0); trigger_once(&mut player, &mut inputs, &mut outputs);
325 let mut impulse_positions = Vec::new();
327 let first = outputs.get(10).unwrap();
329 if first > 0.5 {
330 impulse_positions.push(0);
331 }
332 for i in 1..20 {
333 player.tick(&inputs, &mut outputs);
334 if outputs.get(10).unwrap() > 0.5 {
335 impulse_positions.push(i);
336 }
337 }
338 assert!(impulse_positions.len() >= 3);
340 assert_eq!(impulse_positions[0], 0);
341 assert_eq!(impulse_positions[1], 4);
342 assert_eq!(impulse_positions[2], 8);
343 }
344
345 #[test]
346 fn test_plus_one_volt_doubles_speed() {
347 let sr = 48000.0;
348 let mut player = SamplePlayer::new(impulse_buffer(4, 6), sr, sr);
349 let mut inputs = PortValues::new();
350 let mut outputs = PortValues::new();
351 inputs.set(2, 1.0); trigger_once(&mut player, &mut inputs, &mut outputs);
354 let mut impulse_positions = Vec::new();
355 if outputs.get(10).unwrap() > 0.5 {
356 impulse_positions.push(0);
357 }
358 for i in 1..20 {
359 player.tick(&inputs, &mut outputs);
360 if outputs.get(10).unwrap() > 0.5 {
361 impulse_positions.push(i);
362 }
363 }
364 assert!(impulse_positions.len() >= 3);
366 assert_eq!(impulse_positions[0], 0);
367 assert_eq!(impulse_positions[1], 2);
368 assert_eq!(impulse_positions[2], 4);
369 }
370
371 #[test]
372 fn test_loop_wraps() {
373 let sr = 48000.0;
374 let mut player = SamplePlayer::new(impulse_buffer(2, 3), sr, sr); let mut inputs = PortValues::new();
377 let mut outputs = PortValues::new();
378 inputs.set(2, 0.0);
379 inputs.set(4, 5.0); trigger_once(&mut player, &mut inputs, &mut outputs);
382 let mut impulses = 0;
383 for _ in 0..60 {
384 player.tick(&inputs, &mut outputs);
385 if outputs.get(10).unwrap() > 0.5 {
386 impulses += 1;
387 }
388 }
389 assert!(impulses > 6, "loop did not wrap: {impulses} impulses");
391 }
392
393 #[test]
394 fn test_loop_wrap_bounded_at_pathological_rate() {
395 let sr = 48000.0;
401 let mut player = SamplePlayer::new(impulse_buffer(1, 8), sr, sr); let mut inputs = PortValues::new();
403 let mut outputs = PortValues::new();
404 player.set_start(1.0);
406 inputs.set(4, 5.0); inputs.set(2, 60.0); trigger_once(&mut player, &mut inputs, &mut outputs);
410 for _ in 0..100 {
412 player.tick(&inputs, &mut outputs);
413 assert!(
414 player.phase.is_finite()
415 && player.phase >= 0.0
416 && player.phase < player.len() as f64,
417 "phase escaped the loop region: {}",
418 player.phase
419 );
420 assert!(outputs.get(10).unwrap().is_finite());
421 }
422 }
423
424 #[test]
425 fn test_eos_fires_once_at_end() {
426 let sr = 48000.0;
427 let mut player = SamplePlayer::new(impulse_buffer(1, 8), sr, sr); let mut inputs = PortValues::new();
429 let mut outputs = PortValues::new();
430 inputs.set(2, 0.0);
431
432 trigger_once(&mut player, &mut inputs, &mut outputs);
433 let mut eos_count = 0;
434 for _ in 0..40 {
435 player.tick(&inputs, &mut outputs);
436 if outputs.get(11).unwrap() > GATE_THRESHOLD_V {
437 eos_count += 1;
438 }
439 }
440 assert_eq!(eos_count, 1, "eos should fire exactly once at end");
441 }
442
443 #[test]
444 fn test_empty_buffer_silent() {
445 let mut player = SamplePlayer::empty(48000.0);
446 let mut inputs = PortValues::new();
447 let mut outputs = PortValues::new();
448 assert!(player.is_empty());
449 assert_eq!(player.len(), 0);
450
451 trigger_once(&mut player, &mut inputs, &mut outputs);
452 for _ in 0..50 {
453 player.tick(&inputs, &mut outputs);
454 assert_eq!(outputs.get(10).unwrap(), 0.0);
455 }
456 }
457
458 #[test]
459 fn test_gated_playback_stops_on_release() {
460 let sr = 48000.0;
461 let mut player = SamplePlayer::new(impulse_buffer(1, 64), sr, sr);
462 let mut inputs = PortValues::new();
463 let mut outputs = PortValues::new();
464 inputs.set(2, 0.0);
465
466 inputs.set(1, 0.0);
468 player.tick(&inputs, &mut outputs);
469 inputs.set(1, 5.0);
470 player.tick(&inputs, &mut outputs);
471 assert!(player.playing);
472
473 inputs.set(1, 0.0);
475 player.tick(&inputs, &mut outputs);
476 assert!(!player.playing);
477 assert_eq!(outputs.get(10).unwrap(), 0.0);
478 }
479
480 #[test]
481 fn test_type_id_and_default() {
482 let player = SamplePlayer::default();
483 assert_eq!(player.type_id(), "sample_player");
484 assert!(player.is_empty());
485 }
486
487 #[test]
488 fn test_set_buffer_swaps() {
489 let mut player = SamplePlayer::empty(48000.0);
490 assert!(player.is_empty());
491 player.set_buffer(vec![0.5; 100], 48000.0);
492 assert_eq!(player.len(), 100);
493 }
494}