Files
strudel/packages/desktopbridge/webaudiobridge.mjs
T
Vasilii Milovidov c2fc2272c5 tests: reverb
2023-10-02 22:11:25 +04:00

239 lines
6.3 KiB
JavaScript

import { Invoke } from './utils.mjs';
import { duration, isNote, midiToFreq, noteToMidi, Pattern, valueToMidi } from '@strudel.cycles/core';
import { getAudioContext, getEnvelope, getSound, logger } from '@strudel.cycles/webaudio';
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',
);
}
let t = ac.currentTime + deadline;
let {
note = 'C2',
s = 'triangle',
bank,
source,
gain = 0.8,
// low pass
cutoff = 8000,
resonance = 1,
// high pass
hcutoff = 0,
hresonance = 1,
// band pass
bandf = 0,
bandq = 1,
//
coarse,
crush,
shape,
pan,
vowel,
delay,
delayfeedback,
delaytime,
orbit = 1,
room,
size,
ir,
i = 0,
velocity = 1,
analyze, // analyser wet
fft = 8, // fftSize 0 - 10
speed = 1, // sample playback speed
begin = 0,
end = 1,
loop = 0,
loopBegin = 0,
loopEnd = 1,
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);
let transpose = midi - 36;
let sampleUrl;
let baseUrl;
let path;
const waveForms = new Set(['sine', 'square', 'saw', 'sawtooth', 'triangle']);
if (waveForms.has(s)) {
sampleUrl = 'none';
} else {
if (getSound(s).data.baseUrl !== undefined) {
baseUrl = getSound(s).data.baseUrl;
if (baseUrl === './piano/') {
path = 'https://strudel.tidalcycles.org/';
} else if (baseUrl === './EmuSP12/') {
path = 'https://strudel.tidalcycles.org/';
} else {
path = '';
}
}
let map = getSound(s).data.samples;
if (Array.isArray(map)) {
sampleUrl =
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,
);
transpose = -midiDiff(closest); // semitones to repitch
sampleUrl =
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) {
delay = Math.abs(delay);
delayfeedback = Math.abs(delayfeedback);
delaytime = Math.abs(delaytime);
}
let ir_path;
let url;
if (ir !== undefined) {
if (getSound(ir).data.baseUrl !== undefined) {
baseUrl = getSound(ir).data.baseUrl;
if (baseUrl === './piano/') {
ir_path = 'https://strudel.tidalcycles.org/';
} else if (baseUrl === './EmuSP12/') {
ir_path = 'https://strudel.tidalcycles.org/';
} else {
ir_path = '';
}
}
let ir_samples = getSound(ir);
if (Array.isArray(ir_samples)) {
url =
ir_path !== undefined
? ir_path + ir_samples[n % ir_samples.length].replace('./', '')
: ir_samples[n % ir_samples.length].replace('./', '');
} else if (typeof ir_samples === 'object') {
console.log('obk', Object.values(ir_samples.data.samples)[i & Object.values(ir_samples.data.samples).length].replace('./', ''))
url =
ir_path !== undefined
? ir_path +
Object.values(ir_samples.data.samples)[i & Object.values(ir_samples.data.samples).length].replace('./', '')
: Object.values(ir_samples.data.samples)[i & Object.values(ir_samples.data.samples).length].replace('./', '');
}
}
console.log('url', url);
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 = [];
const packages = {
loop: [loop, loopBegin, loopEnd],
delay: [delay, delaytime, delayfeedback],
reverb: [room, size, ir, url],
lpf: [cutoff, resonance],
hpf: [hcutoff, hresonance],
bpf: [bandf, bandq],
adsr: [attack, decay, sustain, release],
lpenv: [lpattack, lpdecay, lpsustain, lprelease, lpenv],
hpenv: [hpattack, hpdecay, hpsustain, hprelease, hpenv],
bpenv: [bpattack, bpdecay, bpsustain, bprelease, bpenv],
};
messagesfromjs.push({
note: midiToFreq(note),
offset: roundedOffset,
waveform: s,
lpf: packages.lpf,
hpf: packages.hpf,
bpf: packages.bpf,
duration: hapDuration,
velocity: velocity,
delay: packages.delay,
reverb: packages.reverb,
orbit: orbit,
speed: speed,
begin: begin,
end: end,
looper: packages.loop,
adsr: packages.adsr,
lpenv: packages.lpenv,
hpenv: packages.hpenv,
bpenv: packages.bpenv,
n: n,
sampleurl: sampleUrl,
dirname: dirname,
unit: unit,
playbackrate: playbackRate,
});
if (messagesfromjs.length) {
setTimeout(() => {
Invoke('sendwebaudio', { messagesfromjs });
});
}
};
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);
export const webaudioDesktopOutput = (hap, deadline, hapDuration) =>
desktopAudio(hap2value(hap), deadline, hapDuration);
Pattern.prototype.webaudio = function () {
return this.onTrigger(webaudioDesktopOutputTrigger);
};