mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-21 20:55:12 -04:00
make it closer to the js implementation
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import {Invoke} from './utils.mjs';
|
||||
import {duration, isNote, midiToFreq, noteToMidi, Pattern} from '@strudel.cycles/core';
|
||||
import {getAudioContext} from '@strudel.cycles/webaudio';
|
||||
import {getAudioContext, getEnvelope} from '@strudel.cycles/webaudio';
|
||||
|
||||
export const desktopAudio = async (value, deadline, hapDuration) => {
|
||||
const ac = getAudioContext();
|
||||
@@ -49,10 +49,10 @@ export const desktopAudio = async (value, deadline, hapDuration) => {
|
||||
loop = 0,
|
||||
loopBegin = 0,
|
||||
loopEnd = 1,
|
||||
attack,
|
||||
decay,
|
||||
sustain,
|
||||
release,
|
||||
attack = 0.001,
|
||||
decay = 0.05,
|
||||
sustain = 1,
|
||||
release = 0.001,
|
||||
lpattack = 0.0001,
|
||||
lpdecay = 0.2,
|
||||
lpsustain = 0.6,
|
||||
@@ -71,8 +71,6 @@ export const desktopAudio = async (value, deadline, hapDuration) => {
|
||||
} = value;
|
||||
|
||||
value.duration = hapDuration;
|
||||
console.log(value.duration);
|
||||
console.log(t);
|
||||
if (isNote(note)) {
|
||||
note = noteToMidi(note);
|
||||
}
|
||||
@@ -83,49 +81,44 @@ export const desktopAudio = async (value, deadline, hapDuration) => {
|
||||
delaytime = Math.abs(delaytime);
|
||||
}
|
||||
|
||||
let adsr_on = 1;
|
||||
let adsr_on = attack !== 0.001 || decay !== 0.05 || sustain !== 1 || release !== 0.001 ? 1 : 0;
|
||||
|
||||
if (attack === undefined || decay === undefined || sustain === undefined || release === undefined) {
|
||||
adsr_on = 0;
|
||||
attack = 0.001;
|
||||
decay = 0.001;
|
||||
sustain = 1;
|
||||
release = 2;
|
||||
}
|
||||
|
||||
const loop_packaged = [loop, loopBegin, loopEnd];
|
||||
const delay_packaged = [delay, delayfeedback, delaytime];
|
||||
const lpf_packaged = [cutoff, resonance];
|
||||
const hpf_packaged = [hcutoff, hresonance];
|
||||
const band_packaged = [bandf, bandq];
|
||||
const adsr_packaged = [attack, decay, sustain, release, adsr_on];
|
||||
const lpenv_packaged = [lpattack, lpdecay, lpsustain, lprelease, lpenv];
|
||||
const hpenv_packaged = [hpattack, hpdecay, hpsustain, hprelease, hpenv];
|
||||
const bpenv_packaged = [bpattack, bpdecay, bpsustain, bprelease, bpenv];
|
||||
const packages = {
|
||||
loop: [loop, loopBegin, loopEnd],
|
||||
delay: [delay, delayfeedback, delaytime],
|
||||
lpf: [cutoff, resonance],
|
||||
hpf: [hcutoff, hresonance],
|
||||
bpf: [bandf, bandq],
|
||||
adsr: [attack, decay, sustain, release, adsr_on],
|
||||
lpenv: [lpattack, lpdecay, lpsustain, lprelease, lpenv],
|
||||
hpenv: [hpattack, hpdecay, hpsustain, hprelease, hpenv],
|
||||
bpenv: [bpattack, bpdecay, bpsustain, bprelease, bpenv]
|
||||
};
|
||||
|
||||
const offset = (t - getAudioContext().currentTime) * 1000;
|
||||
const roundedOffset = Math.round(offset);
|
||||
const messagesfromjs = [];
|
||||
|
||||
|
||||
messagesfromjs.push({
|
||||
note: midiToFreq(note),
|
||||
offset: roundedOffset,
|
||||
waveform: s,
|
||||
bank: bank,
|
||||
lpf: lpf_packaged,
|
||||
hpf: hpf_packaged,
|
||||
bpf: band_packaged,
|
||||
lpf: packages.lpf,
|
||||
hpf: packages.hpf,
|
||||
bpf: packages.bpf,
|
||||
duration: hapDuration,
|
||||
velocity: velocity,
|
||||
delay: delay_packaged,
|
||||
delay: packages.delay,
|
||||
speed: speed,
|
||||
begin: begin,
|
||||
end: end,
|
||||
looper: loop_packaged,
|
||||
adsr: adsr_packaged,
|
||||
lpenv: lpenv_packaged,
|
||||
hpenv: hpenv_packaged,
|
||||
bpenv: bpenv_packaged,
|
||||
looper: packages.loop,
|
||||
adsr: packages.adsr,
|
||||
lpenv: packages.lpenv,
|
||||
hpenv: packages.hpenv,
|
||||
bpenv: packages.bpenv,
|
||||
});
|
||||
|
||||
if (messagesfromjs.length) {
|
||||
|
||||
+143
-113
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user