1use crate::bank::{EffectEntry, SoundBank};
2use crate::config::{AudioConfig, crossfade_step_q8};
3use crate::envelope::AdsrSpec;
4use crate::error::AudioError;
5use crate::fixed::{apply_gain_q8, mix_crossfade};
6use crate::output::{
7 DutyMode, PwmMapper, limit_bus, pcm_to_dac_u8, pcm_to_dac_u12, pcm_to_dac_u16, pcm_to_i16,
8 pcm_to_i32,
9};
10
11use crate::voice::Voice;
12
13#[cfg(feature = "fm")]
14use crate::output::{FmMapper, FmTick};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
18pub enum VoiceStealingPolicy {
19 #[default]
21 LowestPriorityOldest,
22 FreeChannelOnly,
24}
25
26pub struct AudioEngine<'a, const N: usize = 2> {
28 bank: Option<SoundBank<'a>>,
29 config: AudioConfig,
30 voices: [Voice<'a>; N],
31 mapper: PwmMapper,
32 crossfade_t_q8: u8,
33 crossfade_step_q8: u8,
34 crossfade_active: bool,
35 stealing_policy: VoiceStealingPolicy,
36 #[cfg(feature = "fm")]
37 fm_mapper: FmMapper,
38}
39
40impl<'a> AudioEngine<'a, 2> {
41 pub fn new(config: AudioConfig) -> Self {
43 Self::with_voice_count(config)
44 }
45
46 pub fn from_sample_rate(sample_rate_hz: u32, pwm_period: u16, duty_mode: DutyMode) -> Self {
47 Self::new(AudioConfig::new(sample_rate_hz, pwm_period, duty_mode))
48 }
49
50 #[cfg(feature = "fm")]
51 pub fn new_markham() -> Self {
52 use crate::profile::markham;
53 Self::new(AudioConfig::new(
54 markham::CONTROL_TICK_HZ,
55 0,
56 DutyMode::Linear,
57 ))
58 }
59}
60
61impl<'a, const N: usize> AudioEngine<'a, N> {
62 pub fn with_voice_count(config: AudioConfig) -> Self {
64 let rate = config.sample_rate_hz;
65 Self {
66 bank: None,
67 config,
68 voices: [Voice::silent(rate); N],
69 mapper: PwmMapper::new(config.duty_mode),
70 crossfade_t_q8: 0,
71 crossfade_step_q8: 0,
72 crossfade_active: false,
73 stealing_policy: VoiceStealingPolicy::default(),
74 #[cfg(feature = "fm")]
75 fm_mapper: FmMapper::markham(),
76 }
77 }
78
79 pub fn config(&self) -> AudioConfig {
80 self.config
81 }
82
83 pub fn set_stealing_policy(&mut self, policy: VoiceStealingPolicy) {
84 self.stealing_policy = policy;
85 }
86
87 pub fn stealing_policy(&self) -> VoiceStealingPolicy {
88 self.stealing_policy
89 }
90
91 pub fn voice_count(&self) -> usize {
92 N
93 }
94
95 pub fn active_voice_count(&self) -> usize {
96 self.voices.iter().filter(|v| v.is_audible()).count()
97 }
98
99 pub fn voice(&self, idx: usize) -> Option<&Voice<'a>> {
100 self.voices.get(idx)
101 }
102
103 pub fn voice_mut(&mut self, idx: usize) -> Option<&mut Voice<'a>> {
104 self.voices.get_mut(idx)
105 }
106
107 pub fn set_bank(&mut self, bank: SoundBank<'a>) {
108 let rate = bank.sample_rate_hz;
109 self.config.sample_rate_hz = rate;
110 self.voices = [Voice::silent(rate); N];
111 self.bank = Some(bank);
112 }
113
114 pub fn set_master_gain_q8(&mut self, gain: u8) {
115 self.config.master_gain_q8 = gain;
116 }
117
118 pub fn stop_all(&mut self) {
119 for v in &mut self.voices {
120 v.stop_immediate();
121 }
122 self.crossfade_active = false;
123 self.crossfade_step_q8 = 0;
124 self.mapper.reset();
125 }
126
127 pub fn is_playing(&self) -> bool {
128 self.voices.iter().any(|v| v.is_audible())
129 }
130
131 pub fn allocate_voice(&mut self, priority: u8) -> Option<usize> {
133 if N == 0 {
134 return None;
135 }
136 for (i, v) in self.voices.iter().enumerate() {
138 if !v.is_audible() {
139 return Some(i);
140 }
141 }
142 if self.stealing_policy == VoiceStealingPolicy::LowestPriorityOldest {
144 let mut lowest_idx = None;
145 let mut lowest_prio = priority;
146 for (i, v) in self.voices.iter().enumerate() {
147 if v.priority < lowest_prio {
148 lowest_prio = v.priority;
149 lowest_idx = Some(i);
150 }
151 }
152 return lowest_idx;
153 }
154 None
155 }
156
157 pub fn play(&mut self, effect_id: u16, adsr: AdsrSpec) -> Result<usize, AudioError> {
159 self.play_with_priority(effect_id, adsr, 128)
160 }
161
162 pub fn play_with_priority(
164 &mut self,
165 effect_id: u16,
166 adsr: AdsrSpec,
167 priority: u8,
168 ) -> Result<usize, AudioError> {
169 let idx = self.allocate_voice(priority).ok_or(AudioError::VoiceBusy)?;
170 let bank = self.bank.ok_or(AudioError::NoBank)?;
171 let entry = bank.find_by_id(effect_id)?;
172 self.start_on_voice(idx, &bank, entry, adsr, priority)?;
173 Ok(idx)
174 }
175
176 pub fn play_wavetable(
178 &mut self,
179 table: &'a [u8],
180 freq_hz: u32,
181 adsr: AdsrSpec,
182 ) -> Result<usize, AudioError> {
183 self.play_wavetable_with_priority(table, freq_hz, adsr, 128)
184 }
185
186 pub fn play_wavetable_with_priority(
188 &mut self,
189 table: &'a [u8],
190 freq_hz: u32,
191 adsr: AdsrSpec,
192 priority: u8,
193 ) -> Result<usize, AudioError> {
194 if table.len() < 256 {
195 return Err(AudioError::InvalidPayload);
196 }
197 let idx = self.allocate_voice(priority).ok_or(AudioError::VoiceBusy)?;
198 let rate = self.config.sample_rate_hz;
199 let mut voice = Voice::silent(rate);
200 voice.set_gain_q8(255);
201 voice.priority = priority;
202 voice.source.start_wavetable(table, freq_hz, rate);
203 voice.trigger_adsr(adsr);
204 self.voices[idx] = voice;
205 Ok(idx)
206 }
207
208 pub fn crossfade_to(
210 &mut self,
211 effect_id: u16,
212 duration_ms: u16,
213 adsr: AdsrSpec,
214 ) -> Result<(), AudioError> {
215 if N < 2 {
216 return Err(AudioError::VoiceBusy);
217 }
218 let bank = self.bank.ok_or(AudioError::NoBank)?;
219 let entry = bank.find_by_id(effect_id)?;
220 self.voices[0].release();
221 self.start_on_voice(1, &bank, entry, adsr, 128)?;
222 self.crossfade_active = true;
223 self.crossfade_t_q8 = 0;
224 self.crossfade_step_q8 = crossfade_step_q8(duration_ms, self.config.sample_rate_hz);
225 Ok(())
226 }
227
228 fn advance_crossfade(&mut self) {
229 if !self.crossfade_active || N < 2 {
230 return;
231 }
232 let t = self.crossfade_t_q8.saturating_add(self.crossfade_step_q8);
233 self.crossfade_t_q8 = t;
234 if t == 255 {
235 self.voices[0].stop_immediate();
236 self.voices[0].source = self.voices[1].source;
237 self.voices[0].set_gain_q8(self.voices[1].gain_q8());
238 self.voices[0].priority = self.voices[1].priority;
239 self.voices[1].stop_immediate();
240 self.crossfade_active = false;
241 self.crossfade_t_q8 = 0;
242 }
243 }
244
245 pub fn start_on_voice(
246 &mut self,
247 idx: usize,
248 bank: &SoundBank<'a>,
249 entry: EffectEntry,
250 adsr: AdsrSpec,
251 priority: u8,
252 ) -> Result<(), AudioError> {
253 if idx >= N {
254 return Err(AudioError::VoiceBusy);
255 }
256 let payload = bank.payload(&entry)?;
257 let rate = bank.sample_rate_hz;
258 let mut voice = Voice::silent(rate);
259 voice.set_gain_q8(entry.default_gain_q8);
260 voice.priority = priority;
261 if !voice.source.start_from_entry(
262 entry.kind,
263 entry.flags,
264 entry.param0,
265 entry.param1,
266 payload,
267 rate,
268 ) {
269 return Err(AudioError::InvalidEffectKind);
270 }
271 voice.trigger_adsr(adsr);
272 self.voices[idx] = voice;
273 Ok(())
274 }
275
276 fn tick_mixed_pcm(&mut self) -> i8 {
277 self.advance_crossfade();
278 for v in &mut self.voices {
279 v.tick_envelope();
280 }
281
282 if N == 0 {
283 return 0;
284 }
285
286 if self.crossfade_active && N >= 2 {
287 let sa = self.voices[0].next_sample();
288 let sb = self.voices[1].next_sample();
289 let mixed = match (sa, sb) {
290 (None, None) => 0,
291 (Some(s), None) => s as i32,
292 (None, Some(s)) => s as i32,
293 (Some(sa), Some(sb)) => mix_crossfade(sa, sb, self.crossfade_t_q8) as i32,
294 };
295 return apply_gain_q8(limit_bus(mixed), self.config.master_gain_q8);
296 }
297
298 let mut sum: i32 = 0;
299 let mut active_count: i32 = 0;
300 for v in &mut self.voices {
301 if let Some(s) = v.next_sample() {
302 sum += s as i32;
303 active_count += 1;
304 }
305 }
306
307 let mixed = if active_count > 1 {
308 sum / active_count
309 } else {
310 sum
311 };
312
313 apply_gain_q8(limit_bus(mixed), self.config.master_gain_q8)
314 }
315
316 pub fn tick_pcm(&mut self) -> i8 {
318 self.tick_mixed_pcm()
319 }
320
321 #[cfg(feature = "dsp")]
322 pub fn tick_pcm_f32(&mut self) -> f32 {
324 self.tick_mixed_pcm() as f32 / 128.0
325 }
326
327 #[cfg(feature = "dsp")]
328 pub fn fill_pcm_f32_buffer(&mut self, out: &mut [f32]) -> usize {
330 for sample in out.iter_mut() {
331 *sample = self.tick_pcm_f32();
332 }
333 out.len()
334 }
335
336 pub fn tick(&mut self) -> u16 {
338 let pcm = self.tick_mixed_pcm();
339 self.mapper.map(pcm, self.config.pwm_period)
340 }
341
342 pub fn fill_duty_buffer(&mut self, out: &mut [u16]) -> usize {
346 let period = self.config.pwm_period;
347 for duty in out.iter_mut() {
348 let pcm = self.tick_mixed_pcm();
349 *duty = self.mapper.map(pcm, period);
350 }
351 out.len()
352 }
353
354 pub fn fill_pcm_i8_buffer(&mut self, out: &mut [i8]) -> usize {
356 for slot in out.iter_mut() {
357 *slot = self.tick_mixed_pcm();
358 }
359 out.len()
360 }
361
362 pub fn fill_pcm_i16_buffer(&mut self, out: &mut [i16]) -> usize {
364 for slot in out.iter_mut() {
365 let pcm = self.tick_mixed_pcm();
366 *slot = pcm_to_i16(pcm);
367 }
368 out.len()
369 }
370
371 pub fn fill_pcm_i32_buffer(&mut self, out: &mut [i32]) -> usize {
373 for slot in out.iter_mut() {
374 let pcm = self.tick_mixed_pcm();
375 *slot = pcm_to_i32(pcm);
376 }
377 out.len()
378 }
379
380 pub fn fill_dac_u8_buffer(&mut self, out: &mut [u8]) -> usize {
382 for slot in out.iter_mut() {
383 let pcm = self.tick_mixed_pcm();
384 *slot = pcm_to_dac_u8(pcm);
385 }
386 out.len()
387 }
388
389 pub fn fill_dac_u12_buffer(&mut self, out: &mut [u16]) -> usize {
391 for slot in out.iter_mut() {
392 let pcm = self.tick_mixed_pcm();
393 *slot = pcm_to_dac_u12(pcm);
394 }
395 out.len()
396 }
397
398 pub fn fill_dac_u16_buffer(&mut self, out: &mut [u16]) -> usize {
400 for slot in out.iter_mut() {
401 let pcm = self.tick_mixed_pcm();
402 *slot = pcm_to_dac_u16(pcm);
403 }
404 out.len()
405 }
406
407 pub fn fill_stereo_i16_buffer(&mut self, out: &mut [i16]) -> usize {
409 let mono_count = out.len() / 2;
410 for i in 0..mono_count {
411 let pcm = self.tick_mixed_pcm();
412 let sample = pcm_to_i16(pcm);
413 out[i * 2] = sample;
414 out[i * 2 + 1] = sample;
415 }
416 mono_count * 2
417 }
418
419 pub fn fill_stereo_i32_buffer(&mut self, out: &mut [i32]) -> usize {
421 let mono_count = out.len() / 2;
422 for i in 0..mono_count {
423 let pcm = self.tick_mixed_pcm();
424 let sample = pcm_to_i32(pcm);
425 out[i * 2] = sample;
426 out[i * 2 + 1] = sample;
427 }
428 mono_count * 2
429 }
430
431 pub fn fill_custom_buffer<T>(
433 &mut self,
434 out: &mut [T],
435 mut map_fn: impl FnMut(i8) -> T,
436 ) -> usize {
437 for slot in out.iter_mut() {
438 let pcm = self.tick_mixed_pcm();
439 *slot = map_fn(pcm);
440 }
441 out.len()
442 }
443
444 #[inline]
446 pub fn tick_pwm(&mut self) -> u16 {
447 self.tick()
448 }
449
450 pub fn play_tone(&mut self, freq_hz: u32, duration_ms: u16, waveform: crate::synth::Waveform) {
452 let rate = self.config.sample_rate_hz;
453 if N > 0 {
454 self.voices[0] = Voice::silent(rate);
455 self.voices[0]
456 .source
457 .start_tone(freq_hz, duration_ms, waveform, rate);
458 self.voices[0].trigger_adsr(AdsrSpec::click());
459 }
460 }
461
462 #[cfg(feature = "fm")]
463 pub fn set_fm_mapper(&mut self, mapper: FmMapper) {
464 self.fm_mapper = mapper;
465 }
466
467 #[cfg(feature = "fm")]
468 #[allow(clippy::collapsible_if)]
469 pub fn tick_fm(&mut self) -> FmTick {
471 let pcm = self.tick_mixed_pcm();
472 let rate = self.config.sample_rate_hz;
473
474 if !self.crossfade_active && N > 0 {
475 if let Some(hz) = self.voices[0].source.carrier_hz(rate) {
476 return FmMapper::from_carrier(hz, self.voices[0].source.is_active());
477 }
478 }
479
480 self.fm_mapper.map_pcm(pcm)
481 }
482}
483
484impl Default for AudioEngine<'static, 2> {
485 fn default() -> Self {
486 Self::new(AudioConfig::default())
487 }
488}