Skip to content

Commit ce31288

Browse files
committed
Optimize Oscillator rendering for constant audio param values
1 parent 400900f commit ce31288

File tree

1 file changed

+65
-49
lines changed

1 file changed

+65
-49
lines changed

src/node/oscillator.rs

Lines changed: 65 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ use super::{
1414
ChannelConfigOptions, TABLE_LENGTH_USIZE,
1515
};
1616

17+
fn get_fase_incr(freq: f32, detune: f32, sample_rate: f64) -> f64 {
18+
let computed_freq = freq as f64 * (detune as f64 / 1200.).exp2();
19+
let clamped = computed_freq.clamp(-sample_rate / 2., sample_rate / 2.);
20+
clamped / sample_rate
21+
}
22+
1723
/// Options for constructing an [`OscillatorNode`]
1824
// dictionary OscillatorOptions : AudioNodeOptions {
1925
// OscillatorType type = "sine";
@@ -404,55 +410,21 @@ impl AudioProcessor for OscillatorRenderer {
404410
self.start_time = current_time;
405411
}
406412

407-
let nyquist = scope.sample_rate / 2.;
408-
409-
channel_data
410-
.iter_mut()
411-
.zip(frequency_values.iter().cycle())
412-
.zip(detune_values.iter().cycle())
413-
.for_each(|((o, &frequency), &detune)| {
414-
if current_time < self.start_time || current_time >= self.stop_time {
415-
*o = 0.;
416-
current_time += dt;
417-
418-
return;
419-
}
420-
421-
// @todo: we could avoid recompute that if both param lengths are 1
422-
let computed_frequency = frequency * (detune / 1200.).exp2();
423-
let computed_frequency = computed_frequency.clamp(-nyquist, nyquist);
424-
425-
// first sample to render
426-
if !self.started {
427-
// if start time was between last frame and current frame
428-
// we need to adjust the phase first
429-
if current_time > self.start_time {
430-
let phase_incr = computed_frequency as f64 / sample_rate;
431-
let ratio = (current_time - self.start_time) / dt;
432-
self.phase = Self::unroll_phase(phase_incr * ratio);
433-
}
434-
435-
self.started = true;
436-
}
437-
438-
let phase_incr = computed_frequency as f64 / sample_rate;
439-
440-
// @note: per spec all default oscillators should be rendered from a
441-
// wavetable, define if it worth the assle...
442-
// e.g. for now `generate_sine` and `generate_custom` are almost the sames
443-
// cf. https://webaudio.github.io/web-audio-api/#oscillator-coefficients
444-
*o = match self.type_ {
445-
OscillatorType::Sine => self.generate_sine(),
446-
OscillatorType::Sawtooth => self.generate_sawtooth(phase_incr),
447-
OscillatorType::Square => self.generate_square(phase_incr),
448-
OscillatorType::Triangle => self.generate_triangle(),
449-
OscillatorType::Custom => self.generate_custom(),
450-
};
451-
452-
current_time += dt;
453-
454-
self.phase = Self::unroll_phase(self.phase + phase_incr);
455-
});
413+
if frequency_values.len() == 1 && detune_values.len() == 1 {
414+
let phase_incr = get_fase_incr(frequency_values[0], detune_values[0], sample_rate);
415+
channel_data
416+
.iter_mut()
417+
.for_each(|output| self.generate_sample(output, phase_incr, &mut current_time, dt));
418+
} else {
419+
channel_data
420+
.iter_mut()
421+
.zip(frequency_values.iter().cycle())
422+
.zip(detune_values.iter().cycle())
423+
.for_each(|((output, &f), &d)| {
424+
let phase_incr = get_fase_incr(f, d, sample_rate);
425+
self.generate_sample(output, phase_incr, &mut current_time, dt)
426+
});
427+
}
456428

457429
true
458430
}
@@ -488,6 +460,50 @@ impl AudioProcessor for OscillatorRenderer {
488460
}
489461

490462
impl OscillatorRenderer {
463+
#[inline]
464+
fn generate_sample(
465+
&mut self,
466+
output: &mut f32,
467+
phase_incr: f64,
468+
current_time: &mut f64,
469+
dt: f64,
470+
) {
471+
if *current_time < self.start_time || *current_time >= self.stop_time {
472+
*output = 0.;
473+
*current_time += dt;
474+
475+
return;
476+
}
477+
478+
// first sample to render
479+
if !self.started {
480+
// if start time was between last frame and current frame
481+
// we need to adjust the phase first
482+
if *current_time > self.start_time {
483+
let ratio = (*current_time - self.start_time) / dt;
484+
self.phase = Self::unroll_phase(phase_incr * ratio);
485+
}
486+
487+
self.started = true;
488+
}
489+
490+
// @note: per spec all default oscillators should be rendered from a
491+
// wavetable, define if it worth the assle...
492+
// e.g. for now `generate_sine` and `generate_custom` are almost the sames
493+
// cf. https://webaudio.github.io/web-audio-api/#oscillator-coefficients
494+
*output = match self.type_ {
495+
OscillatorType::Sine => self.generate_sine(),
496+
OscillatorType::Sawtooth => self.generate_sawtooth(phase_incr),
497+
OscillatorType::Square => self.generate_square(phase_incr),
498+
OscillatorType::Triangle => self.generate_triangle(),
499+
OscillatorType::Custom => self.generate_custom(),
500+
};
501+
502+
*current_time += dt;
503+
504+
self.phase = Self::unroll_phase(self.phase + phase_incr);
505+
}
506+
491507
#[inline]
492508
fn generate_sine(&mut self) -> f32 {
493509
let position = self.phase * TABLE_LENGTH_USIZE as f64;

0 commit comments

Comments
 (0)