make it closer to the js implementation

This commit is contained in:
Vasilii Milovidov
2023-09-20 16:36:03 +04:00
parent ebbbcfb071
commit c456186f0f
2 changed files with 170 additions and 147 deletions
+143 -113
View File
@@ -1,6 +1,6 @@
use std::fs::File;
use web_audio_api::context::{AudioContext, BaseAudioContext};
use web_audio_api::node::{AudioNode, AudioScheduledSourceNode, BiquadFilterNode, BiquadFilterType, GainNode, OscillatorType};
use web_audio_api::node::{AudioBufferSourceNode, AudioNode, AudioScheduledSourceNode, BiquadFilterNode, BiquadFilterType, GainNode, OscillatorNode, OscillatorType};
use web_audio_api::node::BiquadFilterType::{Bandpass, Highpass, Lowpass};
use crate::webaudiobridge::WebAudioMessage;
@@ -56,88 +56,47 @@ pub struct FilterADSR {
pub fn superdough(message: &WebAudioMessage, context: &mut AudioContext) {
let now = context.current_time();
let env = context.create_gain();
env.gain().set_value(0.);
env.connect(&context.destination());
let filters = create_filters(context, message);
let connect_filter_nodes = |node: &dyn AudioNode, filters: &Vec<BiquadFilterNode>, env: &GainNode| {
if let Some(first_filter) = filters.first() {
if let Some(last_filter) = filters.last() {
node.connect(first_filter);
last_filter.connect(env);
}
} else {
node.connect(env);
}
};
let mut chain: Vec<&dyn AudioNode> = Vec::new();
// Play synth or sample
match message.waveform.as_str() {
"sine" | "square" | "triangle" | "saw" => {
// Create nodes for synth playback
let osc = context.create_oscillator();
osc.set_type(create_osc_type(&message));
osc.frequency().set_value(message.note);
connect_filter_nodes(&osc, &filters, &env);
osc.start_at(now);
if message.adsr.adsr_on == 1 {
apply_synth_adsr(&env, message, now);
} else {
apply_default_synth_adsr(&env, message, now);
chain.push(&osc);
let filters = create_filters(context, message);
for f in &filters {
chain.push(f);
}
// apply_synth_adsr(&env, message, now);
for f in filters {
apply_filter_adsr(&f, message, &f.type_(), now);
}
osc.stop_at(now + message.duration + message.adsr.release);
let env = context.create_gain();
chain.push(&env);
// Connect nodes and play synth
connect_nodes(chain, context);
play_synth(&message, now, osc, filters, &env);
}
_ => {
match File::open(format!("samples/{}/{}.wav", message.bank, message.waveform)) {
Ok(file) => {
// Create nodes for sample playback
let audio_buffer = context.decode_audio_data_sync(file).unwrap();
let audio_buffer_duration = audio_buffer.duration();
let src = context.create_buffer_source();
src.set_buffer(audio_buffer);
connect_filter_nodes(&src, &filters, &env);
src.playback_rate().set_value(message.speed);
let (start_at, stop_at) = if message.speed < 0.0 {
(audio_buffer_duration, now + message.duration + 0.2)
} else {
(message.begin * audio_buffer_duration, now + message.duration + message.adsr.release)
};
if message.looper.is_loop > 0 {
src.set_loop(true);
src.set_loop_start(message.looper.loop_start);
src.set_loop_end(message.looper.loop_end);
src.start_at_with_offset(
now,
src.loop_start(),
);
if message.adsr.adsr_on == 1 {
apply_drum_adsr(&env, message, now);
} else {
apply_default_drum_adsr(&env, message, now);
}
for f in filters {
apply_filter_adsr(&f, message, &f.type_(), now);
}
src.stop_at(now + message.duration + message.adsr.release);
} else {
src.start_at_with_offset(
now,
start_at,
);
if message.adsr.adsr_on == 1 {
apply_drum_adsr(&env, message, now);
} else {
apply_default_drum_adsr(&env, message, now);
}
for f in filters {
apply_filter_adsr(&f, message, &f.type_(), now);
};
src.stop_at(stop_at);
chain.push(&src);
let filters = create_filters(context, message);
for f in &filters {
chain.push(f);
}
let env = context.create_gain();
chain.push(&env);
// Connect nodes and play sample
connect_nodes(chain, context);
src.playback_rate().set_value(message.speed);
play_sample(message, now, audio_buffer_duration, src, filters, &env);
}
Err(e) => eprintln!("Failed to open file: {:?}", e),
}
@@ -145,6 +104,20 @@ pub fn superdough(message: &WebAudioMessage, context: &mut AudioContext) {
}
}
fn play_synth(message: &&WebAudioMessage, now: f64, osc: OscillatorNode, filters: Vec<BiquadFilterNode> ,env: &GainNode) {
osc.set_type(create_osc_type(&message));
osc.frequency().set_value(message.note);
osc.start();
match message.adsr.adsr_on {
1 => apply_synth_adsr(&env, message, now),
_ => apply_default_synth_adsr(&env, message, now),
};
for f in filters {
apply_filter_adsr(&f, message, &f.type_(), now);
}
osc.stop_at(now + message.duration + message.adsr.release);
}
fn create_osc_type(message: &WebAudioMessage) -> OscillatorType {
match message.waveform.as_str() {
"sine" => OscillatorType::Sine,
@@ -155,6 +128,44 @@ fn create_osc_type(message: &WebAudioMessage) -> OscillatorType {
}
}
fn play_sample(message: &WebAudioMessage, now: f64, audio_buffer_duration: f64, src: AudioBufferSourceNode, filters: Vec<BiquadFilterNode>, env: &GainNode) {
let (start_at, stop_at) = if message.speed < 0.0 {
(audio_buffer_duration, now + message.duration + 0.2)
} else {
(message.begin * audio_buffer_duration, now + message.duration + message.adsr.release)
};
if message.looper.is_loop > 0 {
src.set_loop(true);
src.set_loop_start(message.looper.loop_start);
src.set_loop_end(message.looper.loop_end);
src.start_at_with_offset(
now,
src.loop_start(),
);
match message.adsr.adsr_on {
1 => apply_drum_adsr(&env, message, now),
_ => apply_default_drum_adsr(&env, message, now),
}
for f in filters {
apply_filter_adsr(&f, message, &f.type_(), now);
}
src.stop_at(now + message.duration + message.adsr.release);
} else {
src.start_at_with_offset(
now,
start_at,
);
match message.adsr.adsr_on {
1 => apply_drum_adsr(&env, message, now),
_ => apply_default_drum_adsr(&env, message, now),
}
for f in filters {
apply_filter_adsr(&f, message, &f.type_(), now);
};
src.stop_at(stop_at);
}
}
fn create_filters(context: &mut AudioContext, message: &WebAudioMessage) -> Vec<BiquadFilterNode> {
let mut filters = Vec::new();
@@ -193,50 +204,6 @@ fn create_filters(context: &mut AudioContext, message: &WebAudioMessage) -> Vec<
filters
}
fn apply_synth_adsr(envelope: &GainNode, message: &WebAudioMessage, now: f64) {
envelope.gain()
.set_value_at_time(0., now)
.linear_ramp_to_value_at_time(message.velocity, now + message.adsr.attack)
.linear_ramp_to_value_at_time(
message.adsr.sustain * message.velocity,
now + message.adsr.attack + message.adsr.decay,
)
.linear_ramp_to_value_at_time(0.0, now + message.duration + message.adsr.release);
}
fn apply_default_synth_adsr(envelope: &GainNode, message: &WebAudioMessage, now: f64) {
envelope.gain()
.set_value_at_time(0., now)
.linear_ramp_to_value_at_time(message.velocity, now + 0.001)
.linear_ramp_to_value_at_time(
message.velocity,
now + 0.001 + 0.05,
)
.linear_ramp_to_value_at_time(0.0, now + message.duration + 0.001);
}
fn apply_drum_adsr(envelope: &GainNode, message: &WebAudioMessage, now: f64) {
envelope.gain()
.set_value_at_time(0., now)
.linear_ramp_to_value_at_time(message.velocity, now + message.adsr.attack)
.linear_ramp_to_value_at_time(
message.adsr.sustain * message.velocity,
now + message.adsr.attack + message.adsr.decay,
)
.linear_ramp_to_value_at_time(0.0, now + message.duration + message.adsr.release);
}
fn apply_default_drum_adsr(envelope: &GainNode, message: &WebAudioMessage, now: f64) {
envelope.gain()
.set_value_at_time(0., now)
.linear_ramp_to_value_at_time(message.velocity, now + 0.001)
.linear_ramp_to_value_at_time(
1.0 * message.velocity,
now + 0.001 + 0.001,
)
.linear_ramp_to_value_at_time(0.0, now + message.duration + 0.001);
}
fn apply_filter_adsr(filter_node: &BiquadFilterNode, message: &WebAudioMessage, filter: &BiquadFilterType, now: f64) {
let env = match filter {
Lowpass => message.lpenv,
@@ -266,3 +233,66 @@ fn apply_filter_adsr(filter_node: &BiquadFilterNode, message: &WebAudioMessage,
filter_node.frequency().linear_ramp_to_value_at_time(min, now + message.duration + env.release.max(0.1));
}
//TODO fix envelopes
fn apply_adsr(
envelope: &GainNode,
message: &WebAudioMessage,
now: f64,
attack: f64,
sustain: f32,
decay: f64,
release: f64,
) {
envelope
.gain()
.set_value_at_time(0., now)
.linear_ramp_to_value_at_time(message.velocity, now + attack)
.linear_ramp_to_value_at_time(
sustain * message.velocity,
now + attack + decay,
)
.set_value_at_time(sustain * message.velocity, now + message.duration)
.linear_ramp_to_value_at_time(0.0, now + message.duration + release);
}
fn apply_synth_adsr(envelope: &GainNode, message: &WebAudioMessage, now: f64) {
apply_adsr(
envelope,
message,
now,
message.adsr.attack,
message.adsr.sustain,
message.adsr.decay,
message.adsr.release,
);
}
fn apply_default_synth_adsr(envelope: &GainNode, message: &WebAudioMessage, now: f64) {
apply_adsr(envelope, message, now, 0.001, message.velocity, 0.05, 0.001);
}
fn apply_drum_adsr(envelope: &GainNode, message: &WebAudioMessage, now: f64) {
apply_adsr(
envelope,
message,
now,
message.adsr.attack,
message.adsr.sustain,
message.adsr.decay,
message.adsr.release,
);
}
fn apply_default_drum_adsr(envelope: &GainNode, message: &WebAudioMessage, now: f64) {
apply_adsr(envelope, message, now, 0.001, message.velocity, 0.05, 0.001);
}
fn connect_nodes<'a>(nodes: Vec<&'a (dyn AudioNode + 'a)>, context: &'a mut AudioContext) -> Vec<&'a (dyn AudioNode)> {
for i in 0..nodes.len() - 1 {
nodes[i].connect(nodes[i + 1]);
}
if let Some(last_node) = nodes.last() {
last_node.connect(&context.destination());
}
nodes
}