This commit is contained in:
Vasilii Milovidov
2023-09-26 15:54:39 +04:00
parent 6633b373bc
commit 592b982990
3 changed files with 158 additions and 66 deletions
+37 -32
View File
@@ -15,7 +15,7 @@ export const desktopAudio = async (value, deadline, hapDuration) => {
let {
note = 'C3',
s = 'triangle',
s,
bank = '',
source,
gain = 0.8,
@@ -70,44 +70,52 @@ export const desktopAudio = async (value, deadline, hapDuration) => {
bpenv = 1,
n = 0,
} = value;
bank = getSound(s).data.samples;
// SAMPLES
value.duration = hapDuration;
if (isNote(note)) {
note = noteToMidi(note);
}
const baseUrl = getSound(s).data.baseUrl;
let path;
if (baseUrl === './piano/') {
path = 'https://strudel.tidalcycles.org/';
} else if (baseUrl === './EmuSP12/') {
path = 'https://strudel.tidalcycles.org/';
}
value.duration = hapDuration;
let transpose = 0;
transpose = note - 36;
let sampleUrl;
let key;
if (Array.isArray(bank)) {
sampleUrl = path !== undefined ? path + bank[n % bank.length].replace('./', '') : bank[n % bank.length].replace('./', '');
if (s === 'sine' || s === 'square' || s === 'saw' || s === 'triangle') {
sampleUrl = 'none';
} else {
const midiDiff = (noteA) => noteToMidi(noteA) - note;
// object format will expect keys as notes
const closest = Object.keys(bank)
.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 + bank[closest][n % bank[closest].length].replace('./', '') : bank[closest][n % bank[closest].length].replace('./', '');
bank = getSound(s).data.samples;
console.log('bank', bank)
// if (bank && s) {
// s = `${bank}_${s}`;
// }
let path = '';
if (getSound(s).data.baseUrl !== undefined) {
const baseUrl = getSound(s).data.baseUrl;
if (baseUrl === './piano/') {
path = 'https://strudel.tidalcycles.org/';
} else if (baseUrl === './EmuSP12/') {
path = 'https://strudel.tidalcycles.org/';
}
}
if (Array.isArray(bank)) {
sampleUrl = path !== undefined ? path + bank[n % bank.length].replace('./', '') : bank[n % bank.length].replace('./', '');
} else {
const midiDiff = (noteA) => noteToMidi(noteA) - note;
// object format will expect keys as notes
const closest = Object.keys(bank)
.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 + bank[closest][n % bank[closest].length].replace('./', '') : bank[closest][n % bank[closest].length].replace('./', '');
}
}
console.log('sampleUrl', sampleUrl)
const playbackRate = 1.0 * Math.pow(2, transpose / 12);
// const sound = getSoundData(s);
// SYNTHS
if (delay !== 0) {
delay = Math.abs(delay);
delayfeedback = Math.abs(delayfeedback);
@@ -127,16 +135,13 @@ export const desktopAudio = async (value, deadline, hapDuration) => {
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: packages.lpf,
hpf: packages.hpf,
bpf: packages.bpf,
@@ -144,7 +149,7 @@ export const desktopAudio = async (value, deadline, hapDuration) => {
velocity: velocity,
delay: packages.delay,
orbit: orbit,
speed: speed,
speed: speed * playbackRate,
begin: begin,
end: end,
looper: packages.loop,
+77 -2
View File
@@ -1,4 +1,5 @@
use std::fs::File;
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};
@@ -54,6 +55,80 @@ pub struct FilterADSR {
pub env: f64,
}
pub fn sample(message: &WebAudioMessage, context: &mut AudioContext, audio_buffer: AudioBuffer) {
let now = context.current_time();
let mut chain: Vec<&dyn AudioNode> = Vec::new();
let compressor = context.create_dynamics_compressor();
compressor.connect(&context.destination());
compressor.threshold().set_value(-50.0);
let env = context.create_gain();
let delay = context.create_delay(1.);
// Play synth or sample
// Create nodes for sample playback
// let audio_buffer = context.decode_audio_data_sync(file).unwrap();
let audio_buffer_duration = audio_buffer.duration();
let mut src = context.create_buffer_source();
src.set_buffer(audio_buffer);
chain.push(&src);
let filters = create_filters(context, message);
for f in &filters {
chain.push(f);
}
let env = context.create_gain();
chain.push(&env);
if message.delay.wet > 0.0 {
create_delay(message, context, &env, &delay, now, &compressor);
}
// Connect nodes and play sample
connect_nodes(chain, context, &compressor);
src.playback_rate().set_value(message.speed);
let src = play_sample(message, now, audio_buffer_duration, src, filters, &env);
src.set_onended(move |_| {
delay.disconnect();
});
}
pub fn superdough_synth(message: &WebAudioMessage, context: &mut AudioContext) {
let now = context.current_time();
let mut chain: Vec<&dyn AudioNode> = Vec::new();
let compressor = context.create_dynamics_compressor();
compressor.connect(&context.destination());
compressor.threshold().set_value(-50.0);
let env = context.create_gain();
let delay = context.create_delay(1.);
// Play synth or sample
let osc = context.create_oscillator();
// Create nodes for synth playback
chain.push(&osc);
let filters = create_filters(context, message);
for f in &filters {
chain.push(f);
}
chain.push(&env);
if message.delay.wet > 0.0 {
create_delay(message, context, &env, &delay, now, &compressor);
};
// Connect nodes and play synth
connect_nodes(chain, context, &compressor);
let osc = play_synth(&message, now, osc, filters, &env);
osc.set_onended(move |_| {
delay.disconnect();
});
}
pub fn superdough(message: &WebAudioMessage, context: &mut AudioContext) {
let now = context.current_time();
let mut chain: Vec<&dyn AudioNode> = Vec::new();
@@ -85,11 +160,11 @@ pub fn superdough(message: &WebAudioMessage, context: &mut AudioContext) {
osc.set_onended(move |_| {
delay.disconnect();
delay.disconnect();
});
}
_ => {
match File::open(format!("samples/{}/{}.wav", message.bank, message.waveform)) {
match File::open(format!("samples/{}.wav", message.waveform)) {
Ok(file) => {
// Create nodes for sample playback
+44 -32
View File
@@ -3,6 +3,7 @@ use std::{
time::Duration,
thread::sleep,
};
use std::fs::File;
use mini_moka::sync::Cache;
use reqwest::Url;
@@ -11,14 +12,12 @@ use tokio::{
time::Instant,
};
use serde::Deserialize;
// use tokio::fs::File;
use tokio::io::AsyncWriteExt;
use web_audio_api::{
context::{AudioContext, AudioContextLatencyCategory, AudioContextOptions},
node::AudioNode,
};
use crate::superdough::{ADSR, BPF, Delay, FilterADSR, HPF, Loop, LPF, superdough};
use web_audio_api::{AudioBuffer, context::{AudioContext, AudioContextLatencyCategory, AudioContextOptions}, node::AudioNode};
use web_audio_api::context::BaseAudioContext;
use crate::superdough::{ADSR, BPF, Delay, FilterADSR, HPF, Loop, LPF, sample, superdough, superdough_synth};
const BLOCK_SIZE: usize = 128;
#[derive(Debug)]
pub struct WebAudioMessage {
@@ -26,7 +25,6 @@ pub struct WebAudioMessage {
pub instant: Instant,
pub offset: f64,
pub waveform: String,
pub bank: String,
pub lpf: LPF,
pub hpf: HPF,
pub bpf: BPF,
@@ -90,8 +88,7 @@ pub fn init(
/* ...........................................................
Prepare audio context
............................................................*/
// let cache = Cache::new(10_000);
// let cache_clone_1 = cache.clone();
let cache:Cache<String, AudioBuffer> = Cache::new(10_000);
/* ...........................................................
Process queued messages
............................................................*/
@@ -104,30 +101,47 @@ pub fn init(
return true;
};
let url = Url::parse(&*message.sampleurl).unwrap();
let fname = url.path_segments().and_then(Iterator::last).and_then(|name| if name.is_empty() { None } else { Some(name) }).unwrap_or("tmp.ben");
let file_path = "/Users/vasiliymilovidov/samples/".to_owned() + fname;
let url_copy = url.clone();
// superdough(message.clone(), &mut audio_context);
tokio::spawn(async move {
match tokio::fs::metadata(&file_path).await {
Ok(_) => {
println!("File exists");
}
Err(_) => {
println!("File does not exist, downloading");
let resp = reqwest::get(url_copy).await.unwrap().text().await.unwrap();
let bytes = resp.bytes().await.unwrap();
let mut out = tokio::fs::File::create(file_path).await.unwrap();
out.write_all(&bytes).await.unwrap();
}
match message.waveform.as_str() {
"sine" | "square" | "triangle" | "saw" => {
// superdough(message.clone(), &mut audio_context);
superdough_synth(message.clone(), &mut audio_context);
}
});
_ => {
let url = Url::parse(&*message.sampleurl).unwrap();
let fname = url.path_segments().and_then(Iterator::last).and_then(|name| if name.is_empty() { None } else { Some(name) }).unwrap_or("tmp.ben");
let file_path = "/Users/vasiliymilovidov/samples/".to_owned() + fname;
let url_copy = url.clone();
let file_path_copy = file_path.clone();
match cache.get(&file_path_copy) {
Some(buff) => {
sample(message.clone(), &mut audio_context, buff.clone());
return false;
}
None => {
match File::open(file_path_copy.clone()) {
Ok(file) => {
let buff = audio_context.decode_audio_data_sync(file).unwrap();
cache.insert(file_path_copy.clone(), buff.clone());
}
Err(_) => {}
}
}
}
tokio::spawn(async move {
match tokio::fs::metadata(&file_path).await {
Ok(_) => {}
Err(_) => {
let resp = reqwest::get(url_copy).await.unwrap();
let bytes = resp.bytes().await.unwrap();
let mut out = tokio::fs::File::create(file_path).await.unwrap();
out.write_all(&bytes).await.unwrap();
}
}
});
}
}
return false;
});
tokio::time::sleep(Duration::from_millis(1)).await;
@@ -151,7 +165,6 @@ pub struct MessageFromJS {
note: f32,
offset: f64,
waveform: String,
bank: String,
lpf: (f32, f32),
hpf: (f32, f32),
bpf: (f32, f32),
@@ -186,7 +199,6 @@ pub async fn sendwebaudio(
instant: Instant::now(),
offset: m.offset,
waveform: m.waveform,
bank: m.bank,
lpf: LPF {
frequency: m.lpf.0,
resonance: m.lpf.1,