Skip to main content

boomie/
engine.rs

1use std::error::Error;
2use std::sync::{Arc, Mutex};
3use std::collections::HashMap;
4use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
5use cpal::{StreamConfig, Stream};
6
7use crate::error::SynthError;
8use crate::instrument::{Instrument, InstrumentSource, SampleData, SequenceElement};
9use crate::track::{MelodyTrack, LoopPoint};
10use crate::arrangement::{Arrangement, TrackOverrides};
11use crate::effects::EffectsProcessor;
12
13#[derive(Debug, Clone, Copy, PartialEq)]
14pub enum PlaybackState {
15    Stopped,
16    Playing,
17    Paused,
18}
19
20pub struct DynamicParameters {
21    pub master_volume: f32,
22    pub master_pitch: f32,
23    pub track_volumes: HashMap<String, f32>,
24    pub track_enabled: HashMap<String, bool>,
25    pub crossfade_duration: f32,
26}
27
28impl Default for DynamicParameters {
29    fn default() -> Self {
30        DynamicParameters {
31            master_volume: 1.0,
32            master_pitch: 1.0,
33            track_volumes: HashMap::new(),
34            track_enabled: HashMap::new(),
35            crossfade_duration: 1.0,
36        }
37    }
38}
39
40struct PlaybackContext {
41    arrangement: Arrangement,
42    current_sample: usize,
43    state: PlaybackState,
44    loop_enabled: bool,
45    dynamic_params: DynamicParameters,
46    param_interpolators: HashMap<String, f32>,
47    crossfade_state: Option<CrossfadeState>,
48}
49
50struct CrossfadeState {
51    target_arrangement: Arrangement,
52    progress: f32,
53    duration_samples: usize,
54}
55
56pub struct SynthEngine {
57    mel_cache: HashMap<String, MelodyTrack>, // Cached melodies
58    sample_cache: HashMap<String, SampleData>,
59    stream_config: StreamConfig,
60    sample_rate: f32,
61    playback_context: Arc<Mutex<Option<PlaybackContext>>>,
62    stream: Option<Stream>,
63}
64
65impl SynthEngine {
66    pub fn new() -> Result<Self, SynthError> {
67        let host = cpal::default_host();
68        let device = host.default_output_device()
69            .ok_or_else(|| SynthError::AudioError("No output device found".to_string()));
70        let config = device?.default_output_config()
71            .map_err(|e| SynthError::AudioError(e.to_string()))?;
72        let stream_config = config.config();
73        
74        Ok(SynthEngine {
75            mel_cache: HashMap::new(),
76            sample_cache: HashMap::new(),
77            stream_config: stream_config.clone(),
78            sample_rate: stream_config.sample_rate.0 as f32,
79            playback_context: Arc::new(Mutex::new(None)),
80            stream: None,
81        })
82    }
83
84    pub fn get_sample_cache(&self) -> &HashMap<String, SampleData> {
85        &self.sample_cache
86    }
87
88    /// Load a .wav file into the sample cache
89    pub fn load_sample(&mut self, name: &str, path: &str) -> Result<(), Box<dyn Error>> {
90        let data = std::fs::read(path)?;
91        let cursor = std::io::Cursor::new(data);
92        let mut reader = hound::WavReader::new(cursor)?;
93        let spec = reader.spec();
94        
95        println!("Loading sample \'{}\': {} Hz, {} channels", name, spec.sample_rate, spec.channels);
96        println!("Output sample rate: {} Hz", self.sample_rate);
97        
98        let samples: Result<Vec<f32>, _> = reader.samples::<i16>()
99            .map(|r| r.map(|s| s as f32 / 32768.0)) // i16 audio samples range from −32768 to 32767
100            .collect();
101        
102        let sample_data = SampleData {
103            samples: Arc::new(samples?),
104            sample_rate: spec.sample_rate,
105        };
106        
107        self.sample_cache.insert(name.to_string(), sample_data);
108        Ok(())
109    }
110
111    pub fn load_melody(&mut self, name: &str, path: &str) -> Result<(), Box<dyn Error>> {
112        let content = std::fs::read_to_string(path)?;
113        let track = MelodyTrack::from_mel(&content, &self.sample_cache)?;
114        self.mel_cache.insert(name.to_string(), track);
115        Ok(())
116    }
117
118    pub fn load_arrangement(&self, path: &str) -> Result<Arrangement, SynthError> {
119        let content = std::fs::read_to_string(path)
120            .map_err(|e| SynthError::FileError(e.to_string()))?;
121        Arrangement::from_bmi(&content, &self.mel_cache)
122    }
123
124    pub fn play_arrangement(&mut self, arrangement: Arrangement) -> Result<(), SynthError> {
125        self.stop();
126        
127        let mut context = PlaybackContext {
128            arrangement,
129            current_sample: 0,
130            state: PlaybackState::Playing,
131            loop_enabled: false,
132            dynamic_params: DynamicParameters::default(),
133            param_interpolators: HashMap::new(),
134            crossfade_state: None,
135        };
136        
137        for (track, _, _) in &context.arrangement.tracks {
138            context.dynamic_params.track_enabled.insert(track.name.clone(), true);
139            context.dynamic_params.track_volumes.insert(track.name.clone(), 1.0);
140        }
141        
142        *self.playback_context.lock().unwrap() = Some(context);
143        self.start_stream()?;
144        
145        Ok(())
146    }
147
148    pub fn crossfade_to(&mut self, new_arrangement: Arrangement, duration: f32) -> Result<(), SynthError> {
149        {
150            let mut ctx_lock = self.playback_context.lock().unwrap();
151
152            if let Some(ctx) = ctx_lock.as_mut() {
153                ctx.crossfade_state = Some(CrossfadeState {
154                    target_arrangement: new_arrangement,
155                    progress: 0.0,
156                    duration_samples: (duration * self.sample_rate) as usize,
157                });
158                return Ok(());
159            }
160        }
161
162        self.play_arrangement(new_arrangement)?;
163        Ok(())
164    }
165
166    pub fn set_loop_enabled(&self, enabled: bool) {
167        if let Some(ctx) = self.playback_context.lock().unwrap().as_mut() {
168            ctx.loop_enabled = enabled;
169        }
170    }
171
172    pub fn pause(&self) {
173        if let Some(ctx) = self.playback_context.lock().unwrap().as_mut() {
174            if ctx.state == PlaybackState::Playing {
175                ctx.state = PlaybackState::Paused;
176            }
177        }
178    }
179
180    pub fn resume(&self) {
181        if let Some(ctx) = self.playback_context.lock().unwrap().as_mut() {
182            if ctx.state == PlaybackState::Paused {
183                ctx.state = PlaybackState::Playing;
184            }
185        }
186    }
187
188    pub fn stop(&mut self) {
189        if let Some(stream) = self.stream.take() {
190            drop(stream);
191        }
192        *self.playback_context.lock().unwrap() = None;
193    }
194
195    pub fn set_master_volume(&self, volume: f32) {
196        if let Some(ctx) = self.playback_context.lock().unwrap().as_mut() {
197            ctx.dynamic_params.master_volume = volume.max(0.0).min(2.0);
198        }
199    }
200
201    pub fn set_master_pitch(&self, pitch: f32) {
202        if let Some(ctx) = self.playback_context.lock().unwrap().as_mut() {
203            ctx.dynamic_params.master_pitch = pitch.max(0.5).min(2.0);
204        }
205    }
206
207    pub fn set_track_enabled(&self, track_name: &str, enabled: bool) {
208        if let Some(ctx) = self.playback_context.lock().unwrap().as_mut() {
209            ctx.dynamic_params.track_enabled.insert(track_name.to_string(), enabled);
210        }
211    }
212
213    pub fn set_track_volume(&self, track_name: &str, volume: f32) {
214        if let Some(ctx) = self.playback_context.lock().unwrap().as_mut() {
215            ctx.dynamic_params.track_volumes.insert(track_name.to_string(), volume.max(0.0).min(2.0));
216        }
217    }
218
219    pub fn interpolate_track_volume(&self, track_name: &str, target: f32, duration: f32) {
220        if let Some(ctx) = self.playback_context.lock().unwrap().as_mut() {
221            let key = format!("vol_{}", track_name);
222            ctx.param_interpolators.insert(key, duration);
223            ctx.dynamic_params.track_volumes.insert(track_name.to_string(), target);
224        }
225    }
226
227    pub fn get_playback_position(&self) -> f32 {
228        if let Some(ctx) = self.playback_context.lock().unwrap().as_ref() {
229            ctx.current_sample as f32 / self.sample_rate
230        } else {
231            0.0
232        }
233    }
234
235    pub fn get_playback_state(&self) -> PlaybackState {
236        if let Some(ctx) = self.playback_context.lock().unwrap().as_ref() {
237            ctx.state
238        } else {
239            PlaybackState::Stopped
240        }
241    }
242
243    fn start_stream(&mut self) -> Result<(), SynthError> {
244        let host = cpal::default_host();
245        let device = host.default_output_device()
246            .ok_or_else(|| SynthError::AudioError("No output device".to_string()))?;
247                    
248        let config = self.stream_config.clone();
249        let sample_rate = self.sample_rate;
250        let ctx = Arc::clone(&self.playback_context);
251                    
252        let stream = device.build_output_stream(
253            &config,
254            move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
255                let mut context_lock = ctx.lock().unwrap();
256                
257                if let Some(context) = context_lock.as_mut() {
258                    if context.state != PlaybackState::Playing {
259                        for sample in data.iter_mut() {
260                            *sample = 0.0;
261                        }
262                        return;
263                    }
264                    
265                    for frame in data.chunks_mut(config.channels as usize) {
266                        let mut output = 0.0;
267                        
268                        // Current arrangement
269                        output = Self::synthesize_single_sample(
270                            &context.arrangement,
271                            context.current_sample,
272                            sample_rate,
273                            &context.dynamic_params
274                        );
275                        
276                        // Crossfade target
277                        if let Some(ref mut crossfade) = context.crossfade_state {
278                            let t = (crossfade.progress as f32) / (crossfade.duration_samples as f32);
279                            
280                            let target_sample = Self::synthesize_single_sample(
281                                &crossfade.target_arrangement,
282                                context.current_sample,
283                                sample_rate,
284                                &context.dynamic_params
285                            );
286                            
287                            output = output * (1.0 - t) + target_sample * t;
288                            crossfade.progress += 1.0;
289                            
290                            // Crossfade complete
291                            if crossfade.progress >= crossfade.duration_samples as f32 {
292                                context.arrangement = crossfade.target_arrangement.clone();
293                                context.crossfade_state = None;
294                            }
295                        }
296                        
297                        context.current_sample += 1;
298                        
299                        // Loop/stop logic
300                        if context.loop_enabled {
301                            if let Some(ref loop_point) = context.arrangement.loop_point {
302                                let pos = context.current_sample as f32 / sample_rate;
303                                if pos >= loop_point.end {
304                                    context.current_sample = (loop_point.start * sample_rate) as usize;
305                                }
306                            } else {
307                                let total_samples = (context.arrangement.total_length * sample_rate) as usize;
308                                if context.current_sample >= total_samples {
309                                    context.current_sample = 0;
310                                }
311                            }
312                        } else {
313                            let total_samples = (context.arrangement.total_length * sample_rate) as usize;
314                            if context.current_sample >= total_samples {
315                                context.state = PlaybackState::Stopped;
316                            }
317                        }
318                        
319                        // Apply fade in/out envelopes
320                        let current_time = context.current_sample as f32 / sample_rate;
321                        let total_length = context.arrangement.total_length;
322                        let mut fade_mult = 1.0;
323                        
324                        if let Some(fade_in_dur) = context.arrangement.fade_in {
325                            if current_time < fade_in_dur {
326                                fade_mult *= current_time / fade_in_dur;
327                            }
328                        }
329                        
330                        if let Some(fade_out_dur) = context.arrangement.fade_out {
331                            let fade_out_start = total_length - fade_out_dur;
332                            if current_time > fade_out_start {
333                                fade_mult *= (total_length - current_time) / fade_out_dur;
334                            }
335                        }
336                        
337                        let final_output = output * context.dynamic_params.master_volume * fade_mult;
338                        for sample in frame.iter_mut() {
339                            *sample = final_output;
340                        }
341                    }
342                } else {
343                    for sample in data.iter_mut() {
344                        *sample = 0.0;
345                    }
346                }
347            },
348            |err| eprintln!("Stream error: {}", err),
349            None
350        ).map_err(|e| SynthError::AudioError(e.to_string()))?;
351
352        stream.play().map_err(|e| SynthError::AudioError(e.to_string()))?;
353        self.stream = Some(stream);
354        
355        Ok(())
356    }
357
358    fn synthesize_single_sample(
359        arrangement: &Arrangement,
360        sample_idx: usize,
361        sample_rate: f32,
362        params: &DynamicParameters
363    ) -> f32 {
364        let mut output = 0.0;
365        let current_time = sample_idx as f32 / sample_rate;
366        
367        for (track, start_time, overrides) in &arrangement.tracks {
368            let enabled = params.track_enabled.get(&track.name).copied().unwrap_or(true);
369            if !enabled {
370                continue;
371            }
372            
373            let track_vol = params.track_volumes.get(&track.name).copied().unwrap_or(1.0);
374            
375            if current_time < *start_time {
376                continue;
377            }
378            
379            let track_time = current_time - start_time;
380            
381            let mut cumulative_time = 0.0;
382            let beat_duration = 60.0 / track.tempo;
383            
384            // Go through all sequence elements (notes, chords, rests)
385            for element in &track.sequence {
386                match element {
387                    SequenceElement::Note(note) => {
388                        let note_duration = note.duration * beat_duration;
389                        let next_time = cumulative_time + note_duration;
390                        
391                        if track_time >= cumulative_time && track_time < next_time {
392                            let time_in_note = track_time - cumulative_time;
393                            let envelope = Self::calculate_envelope_static(time_in_note, note_duration, &track.instrument);
394                            
395                            // Apply pitch slide when specified
396                            let mut pitch = note.pitch;
397                            if let Some(slide_target) = note.slide_to {
398                                let slide_progress = time_in_note / note_duration;
399                                pitch = note.pitch * (1.0 - slide_progress) + slide_target * slide_progress;
400                            }
401                            
402                            let sample = match &track.instrument.source {
403                                InstrumentSource::Synthesized(waveform) => {
404                                    let phase = (track_time * pitch * params.master_pitch) % 1.0;
405                                    waveform.generate_sample(phase)
406                                }
407                                InstrumentSource::Sample(sample_data) => {
408                                    Self::interpolate_sample(
409                                        sample_data,
410                                        time_in_note,
411                                        track.instrument.pitch * params.master_pitch
412                                    )
413                                }
414                            };
415                            
416                            let volume = track.instrument.volume * overrides.volume.unwrap_or(1.0) * track_vol;
417                            output += sample * envelope * note.velocity * volume;
418                            break;
419                        }
420                        
421                        cumulative_time = next_time;
422                    }
423                    SequenceElement::Chord(chord) => { // Handle chord playback
424                        let chord_duration = chord.duration * beat_duration;
425                        let next_time = cumulative_time + chord_duration;
426                        
427                        if track_time >= cumulative_time && track_time < next_time {
428                            let time_in_note = track_time - cumulative_time;
429                            let envelope = Self::calculate_envelope_static(time_in_note, chord_duration, &track.instrument);
430                            
431                            // Play all pitches in the chord simultaneously
432                            for pitch in &chord.pitches {
433                                let sample = match &track.instrument.source {
434                                    InstrumentSource::Synthesized(waveform) => {
435                                        let phase = (track_time * pitch * params.master_pitch) % 1.0;
436                                        waveform.generate_sample(phase)
437                                    }
438                                    InstrumentSource::Sample(sample_data) => {
439                                        Self::interpolate_sample(
440                                            sample_data,
441                                            time_in_note,
442                                            track.instrument.pitch * params.master_pitch
443                                        )
444                                    }
445                                };
446                                
447                                let volume = track.instrument.volume * overrides.volume.unwrap_or(1.0) * track_vol;
448                                output += sample * envelope * chord.velocity * volume / chord.pitches.len() as f32;
449                            }
450                            break;
451                        }
452                        
453                        cumulative_time = next_time;
454                    }
455                    SequenceElement::Rest(duration) => { // handle rests
456                        let rest_duration = duration * beat_duration;
457                        cumulative_time += rest_duration;
458                    }
459                }
460            }
461        }
462        
463        output
464    }
465    
466    pub fn synthesize_arrangement(&self, arrangement: &Arrangement) -> Result<Vec<f32>, SynthError> {
467        self.synthesize_arrangement_private(arrangement, &DynamicParameters::default())
468    }
469
470    fn synthesize_arrangement_private(
471        &self,
472        arrangement: &Arrangement,
473        params: &DynamicParameters,
474    ) -> Result<Vec<f32>, SynthError> {
475        let total_samples = (arrangement.total_length * self.sample_rate) as usize;
476        let mut buffer = vec![0.0; total_samples];
477        let chunk_size = 1024;
478
479        for (track, start_time, overrides) in &arrangement.tracks {
480            let enabled = params.track_enabled.get(&track.name).copied().unwrap_or(true);
481            if !enabled {
482                continue;
483            }
484
485            let track_vol = params.track_volumes.get(&track.name).copied().unwrap_or(1.0);
486            let start_sample = (start_time * self.sample_rate) as usize;
487
488            let mut t = track.clone();
489
490            // Apply overrides
491            if let Some(v) = overrides.volume { t.instrument.volume = v; }
492            if let Some(p) = overrides.pitch { t.instrument.pitch = p * params.master_pitch; }
493            if let Some(tm) = overrides.tempo { t.tempo = tm; }
494            if let Some(r) = &overrides.reverb { t.instrument.effects.reverb = Some(r.clone()); }
495            if let Some(d) = &overrides.delay { t.instrument.effects.delay = Some(d.clone()); }
496            if let Some(x) = &overrides.distortion { t.instrument.effects.distortion = Some(x.clone()); }
497            if let Some(f) = &overrides.filter { t.instrument.effects.filter = Some(f.clone()); }
498
499            t.instrument.volume *= track_vol;
500
501            let track_total_samples = (t.length * self.sample_rate) as usize;
502            let mut fx = if t.instrument.effects.has_any() {
503                Some(EffectsProcessor::new(self.sample_rate))
504            } else {
505                None
506            };
507
508            let mut sample_offset = 0;
509            while sample_offset < track_total_samples {
510                let current_chunk_size = (chunk_size).min(track_total_samples - sample_offset);
511                let mut chunk_buf = vec![0.0; current_chunk_size];
512
513                self.synthesize_track_into(&mut chunk_buf, &t, sample_offset);
514
515                if let Some(fx_processor) = &mut fx {
516                    for s in chunk_buf.iter_mut() {
517                        *s = fx_processor.process(*s, &t.instrument.effects);
518                    }
519                }
520
521                // Mix chunk into main buffer
522                for (i, &s) in chunk_buf.iter().enumerate() {
523                    if let Some(dst) = buffer.get_mut(start_sample + sample_offset + i) {
524                        *dst += s * params.master_volume;
525                    }
526                }
527
528                sample_offset += current_chunk_size;
529            }
530        }
531
532        // Apply fade in to beginning of buffer
533        if let Some(fade_in_dur) = arrangement.fade_in {
534            let fade_in_samples = (fade_in_dur * self.sample_rate) as usize;
535            for i in 0..fade_in_samples.min(buffer.len()) {
536                let fade_mult = i as f32 / fade_in_samples as f32;
537                buffer[i] *= fade_mult;
538            }
539        }
540        
541        // Apply fade out to end of buffer
542        if let Some(fade_out_dur) = arrangement.fade_out {
543            let fade_out_samples = (fade_out_dur * self.sample_rate) as usize;
544            let fade_start = buffer.len().saturating_sub(fade_out_samples);
545            for i in fade_start..buffer.len() {
546                let fade_mult = (buffer.len() - i) as f32 / fade_out_samples as f32;
547                buffer[i] *= fade_mult;
548            }
549        }
550
551        // Normalize
552        if let Some(max) = buffer.iter().map(|v| v.abs()).max_by(|a,b| a.partial_cmp(b).unwrap()) {
553            if max > 1.0 { buffer.iter_mut().for_each(|s| *s /= max); }
554        }
555
556        Ok(buffer)
557    }
558
559
560    fn synthesize_track_into(&self, buffer: &mut [f32], track: &MelodyTrack, start_sample: usize) {
561        let mut current_sample = 0usize;
562        let beat_duration = 60.0 / track.tempo;
563
564        // Process all sequence elements (notes, chords, rests)
565        for element in &track.sequence {
566            match element {
567                SequenceElement::Note(note) => {
568                    let note_duration_seconds = note.duration * beat_duration;
569                    
570                    match &track.instrument.source {
571                        InstrumentSource::Synthesized(_) => {
572                            let note_samples = (note_duration_seconds * self.sample_rate) as usize;
573                            let mut phase = 0.0f32;
574                            
575                            for i in 0..note_samples {
576                                let sample_idx = start_sample + current_sample + i;
577                                if sample_idx >= buffer.len() {
578                                    break;
579                                }
580
581                                let time_in_note = i as f32 / self.sample_rate;
582                                let envelope = self.calculate_envelope(time_in_note, note_duration_seconds, &track.instrument);
583                                
584                                let mut pitch = note.pitch;
585                                if let Some(slide_target) = note.slide_to {
586                                    let slide_progress = time_in_note / note_duration_seconds;
587                                    pitch = note.pitch * (1.0 - slide_progress) + slide_target * slide_progress;
588                                }
589                                
590                                if let InstrumentSource::Synthesized(waveform) = &track.instrument.source {
591                                    let output = waveform.generate_sample(phase);
592                                    phase += pitch / self.sample_rate;
593                                    if phase >= 1.0 {
594                                        phase -= 1.0;
595                                    }
596                                    
597                                    buffer[sample_idx] += output * envelope * note.velocity * track.instrument.volume;
598                                }
599                            }
600                            
601                            current_sample += note_samples;
602                        }
603                        
604                        InstrumentSource::Sample(sample_data) => {  // hell
605                            let pitch_adjusted_rate = track.instrument.pitch;
606                            let sample_len = sample_data.samples.len();
607                            
608                            let output_len = (sample_len as f32 / pitch_adjusted_rate) as usize;
609                            let actual_duration = output_len as f32 / self.sample_rate;
610                                                
611                            for i in 0..output_len {
612                                let sample_idx = start_sample + current_sample + i;
613                                if sample_idx >= buffer.len() {
614                                    break;
615                                }
616
617                                let time_in_note = i as f32 / self.sample_rate;
618                                let envelope = self.calculate_envelope(time_in_note, actual_duration, &track.instrument);
619                                
620                                let sample_value = Self::interpolate_sample(
621                                    sample_data,
622                                    time_in_note,
623                                    pitch_adjusted_rate
624                                );
625                                
626                                buffer[sample_idx] += sample_value * envelope * note.velocity * track.instrument.volume;
627                            }
628                            
629                            current_sample += output_len; // Continue by the time the sample took to play
630
631                        }
632                    }
633                }
634                SequenceElement::Chord(chord) => { // Synth chord
635                    let chord_duration_seconds = chord.duration * beat_duration;
636                    let chord_samples = (chord_duration_seconds * self.sample_rate) as usize;
637                    
638                    // Render each pitch in the chord
639                    for pitch in &chord.pitches {
640                        let mut phase = 0.0f32;
641                        
642                        for i in 0..chord_samples {
643                            let sample_idx = start_sample + current_sample + i;
644                            if sample_idx >= buffer.len() {
645                                break;
646                            }
647
648                            let time_in_note = i as f32 / self.sample_rate;
649                            let envelope = self.calculate_envelope(time_in_note, chord_duration_seconds, &track.instrument);
650                            
651                            if let InstrumentSource::Synthesized(waveform) = &track.instrument.source {
652                                let output = waveform.generate_sample(phase);
653                                phase += pitch / self.sample_rate;
654                                if phase >= 1.0 {
655                                    phase -= 1.0;
656                                }
657                                
658                                buffer[sample_idx] += output * envelope * chord.velocity * track.instrument.volume / chord.pitches.len() as f32;
659                            }
660                        }
661                    }
662                    
663                    current_sample += chord_samples;
664                }
665                SequenceElement::Rest(duration) => { // Skip forward for rest
666                    let rest_duration_seconds = duration * beat_duration;
667                    let rest_samples = (rest_duration_seconds * self.sample_rate) as usize;
668                    current_sample += rest_samples;
669                }
670            }
671        }
672    }
673
674    #[inline]
675    fn interpolate_sample(sample_data: &SampleData, time_in_note: f32, pitch_rate: f32) -> f32 {
676        let src_pos = time_in_note * sample_data.sample_rate as f32 * pitch_rate;
677        let src_idx = src_pos as usize;
678        
679        if src_idx >= sample_data.samples.len() {
680            return 0.0;
681        }
682        
683        // Linear interpolation
684        if src_idx < sample_data.samples.len() - 1 {
685            let frac = src_pos - src_idx as f32;
686            let s1 = sample_data.samples[src_idx];
687            let s2 = sample_data.samples[src_idx + 1];
688            s1 * (1.0 - frac) + s2 * frac
689        } else {
690            sample_data.samples[src_idx]
691        }
692    }
693
694    // Generate ADSR envelope value at a set time
695    fn calculate_envelope(&self, time: f32, duration: f32, instr: &Instrument) -> f32 {
696        Self::calculate_envelope_static(time, duration, instr)
697    }
698
699    fn calculate_envelope_static(time: f32, duration: f32, instr: &Instrument) -> f32 {
700        let attack_end = instr.attack;
701        let decay_end = attack_end + instr.decay;
702        let release_start = duration - instr.release;
703
704        // ramp, normalize, fade
705        if time < attack_end {
706            time / attack_end
707        } else if time < decay_end {
708            let decay_progress = (time - attack_end) / instr.decay;
709            1.0 - decay_progress * (1.0 - instr.sustain)
710        } else if time < release_start {
711            instr.sustain
712        } else {
713            let release_progress = (time - release_start) / instr.release;
714            instr.sustain * (1.0 - release_progress)
715        }
716    }
717}