Skip to content

Commit

Permalink
Structural changes, cpu usage improvements, some other fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
vasilymilovidov committed Sep 29, 2023
1 parent 38af4af commit 6b219ee
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 110 deletions.
97 changes: 54 additions & 43 deletions packages/desktopbridge/webaudiobridge.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ export const desktopAudio = async (value, deadline, hapDuration) => {
const ac = getAudioContext();
if (typeof value !== 'object') {
throw new Error(
`expected hap.value to be an object, but got "${value}". Hint: append .note() or .s() to the end`,
'error',
`expected hap.value to be an object, but got "${value}". Hint: append .note() or .s() to the end`,
'error',
);
}

Expand Down Expand Up @@ -49,45 +49,47 @@ export const desktopAudio = async (value, deadline, hapDuration) => {
loop = 0,
loopBegin = 0,
loopEnd = 1,
attack = 0.001,
decay = 0.005,
sustain = 1,
release = 0.001,
lpattack = 0.0001,
lpdecay = 0.2,
lpsustain = 0.6,
lprelease = 0.2,
lpenv = 0,
hpattack = 0.0001,
hpdecay = 0.2,
hpsustain = 0.6,
hprelease = 0.2,
hpenv = 0,
bpattack = 0.0001,
bpdecay = 0.2,
bpsustain = 0.6,
bprelease = 0.2,
bpenv = 0,
attack,
decay,
sustain,
release,
lpattack,
lpdecay,
lpsustain,
lprelease,
lpenv,
hpattack,
hpdecay,
hpsustain,
hprelease,
hpenv,
bpattack,
bpdecay,
bpsustain,
bprelease,
bpenv,
n = 0,
freq,
unit,
} = value;

value.duration = hapDuration;
if (bank && s) {
s = `${bank}_${s}`;
}
if (freq !== undefined && note !== undefined) {
logger('[sampler] hap has note and freq. ignoring note', 'warning');
}
let midi = valueToMidi({ freq, note }, 36);
value.duration = hapDuration;
let transpose;
transpose = midi - 36;
let transpose = midi - 36;

let sampleUrl;
let baseUrl;
if (s === 'sine' || s === 'square' || s === 'saw' || s === 'sawtooth' || s === 'triangle') {
let path;
const waveForms = new Set(['sine', 'square', 'saw', 'sawtooth', 'triangle']);
if (waveForms.has(s)) {
sampleUrl = 'none';
} else {
let path;
if (getSound(s).data.baseUrl !== undefined) {
baseUrl = getSound(s).data.baseUrl;
if (baseUrl === './piano/') {
Expand All @@ -101,26 +103,28 @@ export const desktopAudio = async (value, deadline, hapDuration) => {
let map = getSound(s).data.samples;
if (Array.isArray(map)) {
sampleUrl =
path !== undefined ? path + map[n % map.length].replace('./', '') : map[n % map.length].replace('./', '');
path !== undefined ? path + map[n % map.length].replace('./', '') : map[n % map.length].replace('./', '');
} else {
const midiDiff = (noteA) => noteToMidi(noteA) - midi;
// object format will expect keys as notes
const closest = Object.keys(map)
.filter((k) => !k.startsWith('_'))
.reduce(
(closest, key, j) => (!closest || Math.abs(midiDiff(key)) < Math.abs(midiDiff(closest)) ? key : closest),
null,
);
.filter((k) => !k.startsWith('_'))
.reduce(
(closest, key, j) => (!closest || Math.abs(midiDiff(key)) < Math.abs(midiDiff(closest)) ? key : closest),
null,
);
transpose = -midiDiff(closest); // semitones to repitch
sampleUrl =
path !== undefined
? path + map[closest][n % map[closest].length].replace('./', '')
: map[closest][n % map[closest].length].replace('./', '');
path !== undefined
? path + map[closest][n % map[closest].length].replace('./', '')
: map[closest][n % map[closest].length].replace('./', '');
}
}

if (isNote(note)) {
note = noteToMidi(note);
}

const playbackRate = 1.0 * Math.pow(2, transpose / 12);

if (delay !== 0) {
Expand All @@ -129,22 +133,26 @@ export const desktopAudio = async (value, deadline, hapDuration) => {
delaytime = Math.abs(delaytime);
}

let adsr_on = attack !== 0.001 || decay !== 0.05 || sustain !== 1 || release !== 0.01 ? 1 : 0;

const packages = {
loop: [loop, loopBegin, loopEnd],
delay: [delay, delaytime, delayfeedback],
lpf: [cutoff, resonance],
hpf: [hcutoff, hresonance],
bpf: [bandf, bandq],
adsr: [attack, decay, sustain, release, adsr_on],
adsr: [attack, decay, sustain, release],
lpenv: [lpattack, lpdecay, lpsustain, lprelease, lpenv],
hpenv: [hpattack, hpdecay, hpsustain, hprelease, hpenv],
bpenv: [bpattack, bpdecay, bpsustain, bprelease, bpenv],
};

const dirname = bank + '/' + s + '/';

console.log('unit', unit);
let folder;
if (baseUrl !== undefined) {
folder = baseUrl.replace('./', '') + '/' + s + '/';
} else {
folder = 'misc';
}
const dirname = folder + '/' + s + '/';
const offset = (t - getAudioContext().currentTime) * 1000;
const roundedOffset = Math.round(offset);
const messagesfromjs = [];
Expand All @@ -159,7 +167,7 @@ export const desktopAudio = async (value, deadline, hapDuration) => {
velocity: velocity,
delay: packages.delay,
orbit: orbit,
speed: speed * playbackRate,
speed: speed,
begin: begin,
end: end,
looper: packages.loop,
Expand All @@ -170,6 +178,8 @@ export const desktopAudio = async (value, deadline, hapDuration) => {
n: n,
sampleurl: sampleUrl,
dirname: dirname,
unit: unit,
playbackrate: playbackRate,
});

if (messagesfromjs.length) {
Expand All @@ -178,14 +188,15 @@ export const desktopAudio = async (value, deadline, hapDuration) => {
});
}
};

const hap2value = (hap) => {
hap.ensureObjectValue();
return { ...hap.value, velocity: hap.context.velocity };
};
export const webaudioDesktopOutputTrigger = (t, hap, ct, cps) =>
desktopAudio(hap2value(hap), t - ct, hap.duration / cps, cps);
desktopAudio(hap2value(hap), t - ct, hap.duration / cps, cps);
export const webaudioDesktopOutput = (hap, deadline, hapDuration) =>
desktopAudio(hap2value(hap), deadline, hapDuration);
desktopAudio(hap2value(hap), deadline, hapDuration);

Pattern.prototype.webaudio = function () {
return this.onTrigger(webaudioDesktopOutputTrigger);
Expand Down
86 changes: 48 additions & 38 deletions src-tauri/src/superdough.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use web_audio_api::AudioBuffer;
use web_audio_api::context::{AudioContext, BaseAudioContext};
use web_audio_api::node::{AudioBufferSourceNode, AudioNode, AudioScheduledSourceNode, BiquadFilterNode, BiquadFilterType, DelayNode, DynamicsCompressorNode, GainNode, OscillatorNode, OscillatorType};
use web_audio_api::node::BiquadFilterType::{Bandpass, Highpass, Lowpass};
use web_audio_api::{
context::{AudioContext, BaseAudioContext},
AudioBuffer,
node::{AudioBufferSourceNode, AudioNode, AudioScheduledSourceNode, BiquadFilterNode, BiquadFilterType, GainNode, OscillatorNode, OscillatorType},
node::BiquadFilterType::{Bandpass, Highpass, Lowpass}
};
use crate::webaudiobridge::WebAudioMessage;

#[derive(Clone, Copy, Debug)]
Expand Down Expand Up @@ -38,26 +40,25 @@ pub struct BPF {

#[derive(Debug, Copy, Clone)]
pub struct ADSR {
pub attack: f64,
pub decay: f64,
pub sustain: f32,
pub release: f64,
pub adsr_on: u8,
pub attack: Option<f64>,
pub decay: Option<f64>,
pub sustain: Option<f32>,
pub release: Option<f64>,
}

#[derive(Debug, Copy, Clone)]
pub struct FilterADSR {
pub attack: f64,
pub decay: f64,
pub sustain: f64,
pub release: f64,
pub env: f64,
pub attack: Option<f64>,
pub decay: Option<f64>,
pub sustain: Option<f64>,
pub release: Option<f64>,
pub env: Option<f64>,
}


pub trait WebAudioPlayer {
pub trait WebAudioInstrument {
fn set_adsr(&mut self, t: f64, adsr: &ADSR, velocity: f32, duration: f64);
fn play(&mut self, t: f64, message: &WebAudioMessage, duration: f64,);
fn play(&mut self, t: f64, message: &WebAudioMessage, duration: f64);
fn set_filters(&mut self, context: &mut AudioContext, message: &WebAudioMessage) -> Vec<BiquadFilterNode>;
}

Expand All @@ -67,7 +68,7 @@ pub struct Synth {
}

impl Synth {
pub fn new(context: &mut AudioContext, message: &WebAudioMessage) -> Self {
pub fn new(context: &mut AudioContext) -> Self {
let oscillator = context.create_oscillator();
let envelope = context.create_gain();
Self { oscillator, envelope }
Expand All @@ -88,16 +89,21 @@ impl Synth {
}
}

impl WebAudioPlayer for Synth {
impl WebAudioInstrument for Synth {
fn set_adsr(&mut self, t: f64, adsr: &ADSR, velocity: f32, duration: f64) {
let attack = adsr.attack.unwrap_or(0.001);
let decay = adsr.decay.unwrap_or(0.05);
let sustain = adsr.sustain.unwrap_or(0.6);
let release = adsr.release.unwrap_or(0.01);
self.envelope.gain()
.set_value_at_time(0.0, t)
.linear_ramp_to_value_at_time(velocity, t + adsr.attack)
.linear_ramp_to_value_at_time((adsr.sustain + 0.001) * velocity, t + adsr.attack + adsr.decay)
.set_value_at_time(adsr.sustain * velocity, t + duration)
.linear_ramp_to_value_at_time(0.0, t + duration + adsr.release);
.linear_ramp_to_value_at_time(velocity, t + attack)
.exponential_ramp_to_value_at_time((sustain + 0.0001) * velocity, t + attack + decay)
// .set_value_at_time((sustain + 0.00001) * velocity, t + duration)
.exponential_ramp_to_value_at_time(0.000001, t + duration + release);
}


fn play(&mut self, t: f64, message: &WebAudioMessage, release: f64) {
self.oscillator.start();
self.oscillator.stop_at(t + message.duration + release);
Expand Down Expand Up @@ -142,30 +148,34 @@ pub struct Sampler {
}

impl Sampler {
pub fn new(context: &mut AudioContext, message: &WebAudioMessage, audio_buffer: AudioBuffer) -> Self {
pub fn new(context: &mut AudioContext, audio_buffer: AudioBuffer) -> Self {
let mut sample = context.create_buffer_source();
sample.set_buffer(audio_buffer);
let envelope = context.create_gain();
Self { sample, envelope }
}
}

impl WebAudioPlayer for Sampler {
impl WebAudioInstrument for Sampler {
fn set_adsr(&mut self, t: f64, adsr: &ADSR, velocity: f32, duration: f64) {
let attack = adsr.attack.unwrap_or(0.001);
let decay = adsr.decay.unwrap_or(0.001);
let sustain = adsr.sustain.unwrap_or(1.0);
let release = adsr.release.unwrap_or(0.01);
self.envelope.gain()
.set_value_at_time(0.0, t)
.linear_ramp_to_value_at_time(velocity, t + adsr.attack)
.linear_ramp_to_value_at_time((adsr.sustain + 0.00001) * velocity, t + adsr.attack + adsr.decay)
.set_value_at_time(adsr.sustain * velocity, t + duration)
.linear_ramp_to_value_at_time(0.0, t + duration + adsr.release);
.linear_ramp_to_value_at_time(velocity, t + attack)
.linear_ramp_to_value_at_time((sustain + 0.00001) * velocity, t + attack + decay)
.set_value_at_time((sustain + 0.00001) * velocity, t + duration)
.linear_ramp_to_value_at_time(0.0, t + duration + release);
}

fn play(&mut self, t: f64, message: &WebAudioMessage, release: f64) {
let buffer_duration = release;
let (start_at, stop_at) = if message.speed < 0.0 {
(buffer_duration, t + message.duration + 0.2)
} else {
(message.begin * buffer_duration, t + message.duration + message.adsr.release)
(message.begin * buffer_duration, t + message.duration + message.adsr.release.unwrap_or(0.01))
};
if message.looper.is_loop > 0 {
self.sample.set_loop(true);
Expand All @@ -175,7 +185,7 @@ impl WebAudioPlayer for Sampler {
t,
self.sample.loop_start(),
);
self.sample.stop_at(t + message.duration + message.adsr.release);
self.sample.stop_at(t + message.duration + message.adsr.release.unwrap_or(0.01));
} else {
self.sample.start_at_with_offset(
t,
Expand Down Expand Up @@ -232,16 +242,16 @@ pub fn apply_filter_adsr(filter_node: &BiquadFilterNode, message: &WebAudioMessa
_ => 8000.0,
};

let offset = env.env * 0.5;
let offset = env.env.unwrap_or(1.0) * 0.5;
let min = (2f32.powf(-offset as f32) * freq).clamp(0.0, 20000.0);
let max = (2f32.powf((env.env - offset) as f32) * freq).clamp(0.0, 20000.0);
let max = (2f32.powf((env.env.unwrap_or(1.0) - offset) as f32) * freq).clamp(0.0, 20000.0);
let range = max - min;
let peak = min + range;
let sustain_level = min + env.sustain as f32 * range;
let sustain_level = min + env.sustain.unwrap_or(1.0) as f32 * range;

filter_node.frequency().set_value_at_time(min, now);
filter_node.frequency().linear_ramp_to_value_at_time(peak, now + env.attack);
filter_node.frequency().linear_ramp_to_value_at_time(sustain_level, now + env.attack + env.decay);
filter_node.frequency().set_value_at_time(sustain_level, now + message.duration);
filter_node.frequency().linear_ramp_to_value_at_time(min, now + message.duration + env.release.max(0.1));
filter_node.frequency().set_value_at_time(min, now)
.linear_ramp_to_value_at_time(peak, now + env.attack.unwrap_or(0.01))
.linear_ramp_to_value_at_time(sustain_level, now + env.attack.unwrap_or(0.01) + env.decay.unwrap_or(0.01))
// .set_value_at_time(sustain_level, now + message.duration)
.linear_ramp_to_value_at_time(min, now + message.duration + env.release.unwrap_or(0.01));
}
Loading

0 comments on commit 6b219ee

Please sign in to comment.