wip: use sample as an impulse response for the reverb
This commit is contained in:
parent
8a741952df
commit
fd18508266
3 changed files with 1232 additions and 1192 deletions
|
|
@ -4,8 +4,8 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
|
|||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Pattern, register, sequence } from './pattern.mjs';
|
||||
import { zipWith } from './util.mjs';
|
||||
import {Pattern, register, sequence} from './pattern.mjs';
|
||||
import {zipWith} from './util.mjs';
|
||||
|
||||
const controls = {};
|
||||
const generic_params = [
|
||||
|
|
@ -988,6 +988,17 @@ const generic_params = [
|
|||
// ['sclap'],
|
||||
// ['sclaves'],
|
||||
// ['scrash'],
|
||||
|
||||
/**
|
||||
* Sets the sample to use as an impulse response for the reverb.
|
||||
*
|
||||
* @name iresponse
|
||||
* @param {string | Pattern} Sets the impulse response
|
||||
* @example
|
||||
* s("bd sd").room(.8).ir("<ir0 ir1>")
|
||||
*
|
||||
*/
|
||||
[['ir', 'i'], 'iresponse'],
|
||||
/**
|
||||
* Wave shaping distortion. CAUTION: it might get loud
|
||||
*
|
||||
|
|
@ -1177,11 +1188,11 @@ controls.createParam = function (names) {
|
|||
});
|
||||
return result;
|
||||
} else {
|
||||
return { [name]: xs };
|
||||
return {[name]: xs};
|
||||
}
|
||||
};
|
||||
} else {
|
||||
withVal = (x) => ({ [name]: x });
|
||||
withVal = (x) => ({[name]: x});
|
||||
}
|
||||
|
||||
const func = (...pats) => sequence(...pats).withValue(withVal);
|
||||
|
|
@ -1207,17 +1218,17 @@ generic_params.forEach(([names, ...aliases]) => {
|
|||
});
|
||||
|
||||
controls.createParams = (...names) =>
|
||||
names.reduce((acc, name) => Object.assign(acc, { [name]: controls.createParam(name) }), {});
|
||||
names.reduce((acc, name) => Object.assign(acc, {[name]: controls.createParam(name)}), {});
|
||||
|
||||
controls.adsr = register('adsr', (adsr, pat) => {
|
||||
adsr = !Array.isArray(adsr) ? [adsr] : adsr;
|
||||
const [attack, decay, sustain, release] = adsr;
|
||||
return pat.set({ attack, decay, sustain, release });
|
||||
return pat.set({attack, decay, sustain, release});
|
||||
});
|
||||
controls.ds = register('ds', (ds, pat) => {
|
||||
ds = !Array.isArray(ds) ? [ds] : ds;
|
||||
const [decay, sustain] = ds;
|
||||
return pat.set({ decay, sustain });
|
||||
return pat.set({decay, sustain});
|
||||
});
|
||||
|
||||
export default controls;
|
||||
|
|
|
|||
|
|
@ -7,14 +7,22 @@ if (typeof AudioContext !== 'undefined') {
|
|||
return impulse;
|
||||
};
|
||||
|
||||
AudioContext.prototype.createReverb = function (duration) {
|
||||
AudioContext.prototype.createReverb = function (duration, buffer) {
|
||||
const convolver = this.createConvolver();
|
||||
convolver.setDuration = (d) => {
|
||||
convolver.buffer = this.impulseResponse(d);
|
||||
convolver.duration = duration;
|
||||
convolver.setDuration = (d, i) => {
|
||||
convolver.buffer = i !== undefined ? buffer : this.impulseResponse(d);
|
||||
convolver.duration = d;
|
||||
return convolver;
|
||||
};
|
||||
convolver.setIR = (i) => {
|
||||
convolver.buffer = i;
|
||||
return convolver;
|
||||
};
|
||||
if (buffer !== undefined) {
|
||||
convolver.setIR(buffer);
|
||||
} else {
|
||||
convolver.setDuration(duration);
|
||||
}
|
||||
return convolver;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,14 +12,18 @@ import workletsUrl from './worklets.mjs?url';
|
|||
import { createFilter, gainNode } from './helpers.mjs';
|
||||
import { map } from 'nanostores';
|
||||
import { logger } from './logger.mjs';
|
||||
import { loadBuffer } from './sampler.mjs';
|
||||
|
||||
export const soundMap = map();
|
||||
|
||||
export function registerSound(key, onTrigger, data = {}) {
|
||||
soundMap.setKey(key, { onTrigger, data });
|
||||
}
|
||||
|
||||
export function getSound(s) {
|
||||
return soundMap.get()[s];
|
||||
}
|
||||
|
||||
export const resetLoadedSounds = () => soundMap.set({});
|
||||
|
||||
let audioContext;
|
||||
|
|
@ -46,6 +50,7 @@ export const panic = () => {
|
|||
};
|
||||
|
||||
let workletsLoading;
|
||||
|
||||
function loadWorklets() {
|
||||
if (workletsLoading) {
|
||||
return workletsLoading;
|
||||
|
|
@ -89,6 +94,7 @@ export async function initAudioOnFirstClick(options) {
|
|||
|
||||
let delays = {};
|
||||
const maxfeedback = 0.98;
|
||||
|
||||
function getDelay(orbit, delaytime, delayfeedback, t) {
|
||||
if (delayfeedback > maxfeedback) {
|
||||
//logger(`delayfeedback was clamped to ${maxfeedback} to save your ears`);
|
||||
|
|
@ -107,10 +113,11 @@ function getDelay(orbit, delaytime, delayfeedback, t) {
|
|||
}
|
||||
|
||||
let reverbs = {};
|
||||
function getReverb(orbit, duration = 2) {
|
||||
|
||||
function getReverb(orbit, duration = 2, ir) {
|
||||
if (!reverbs[orbit]) {
|
||||
const ac = getAudioContext();
|
||||
const reverb = ac.createReverb(duration);
|
||||
const reverb = ac.createReverb(duration, ir);
|
||||
reverb.connect(getDestination());
|
||||
reverbs[orbit] = reverb;
|
||||
}
|
||||
|
|
@ -118,10 +125,15 @@ function getReverb(orbit, duration = 2) {
|
|||
reverbs[orbit] = reverbs[orbit].setDuration(duration);
|
||||
reverbs[orbit].duration = duration;
|
||||
}
|
||||
if (reverbs[orbit].ir !== ir) {
|
||||
reverbs[orbit] = reverbs[orbit].setIR(ir);
|
||||
reverbs[orbit].ir = ir;
|
||||
}
|
||||
return reverbs[orbit];
|
||||
}
|
||||
|
||||
export let analyser, analyserData /* s = {} */;
|
||||
|
||||
export function getAnalyser(/* orbit, */ fftSize = 2048) {
|
||||
if (!analyser /*s [orbit] */) {
|
||||
const analyserNode = getAudioContext().createAnalyser();
|
||||
|
|
@ -151,7 +163,7 @@ export function getAnalyzerData(type = 'time') {
|
|||
return analyserData;
|
||||
}
|
||||
|
||||
function effectSend(input, effect, wet) {
|
||||
function effectSend(input, effect, wet, size) {
|
||||
const send = gainNode(wet);
|
||||
input.connect(send);
|
||||
send.connect(effect);
|
||||
|
|
@ -219,6 +231,8 @@ export const superdough = async (value, deadline, hapDuration) => {
|
|||
velocity = 1,
|
||||
analyze, // analyser wet
|
||||
fft = 8, // fftSize 0 - 10
|
||||
ir,
|
||||
i = 0,
|
||||
} = value;
|
||||
gain *= velocity; // legacy fix for velocity
|
||||
let toDisconnect = []; // audio nodes that will be disconnected when the source has ended
|
||||
|
|
@ -247,6 +261,7 @@ export const superdough = async (value, deadline, hapDuration) => {
|
|||
// this can be used for things like speed(0) in the sampler
|
||||
return;
|
||||
}
|
||||
|
||||
if (ac.currentTime > t) {
|
||||
logger('[webaudio] skip hap: still loading', ac.currentTime - t);
|
||||
return;
|
||||
|
|
@ -352,10 +367,16 @@ export const superdough = async (value, deadline, hapDuration) => {
|
|||
delaySend = effectSend(post, delyNode, delay);
|
||||
}
|
||||
// reverb
|
||||
let buffer;
|
||||
if (ir !== undefined) {
|
||||
let sample = getSound(ir);
|
||||
let url = sample.data.samples[i % sample.data.samples.length];
|
||||
buffer = await loadBuffer(url, ac, ir, 0);
|
||||
}
|
||||
let reverbSend;
|
||||
if (room > 0 && size > 0) {
|
||||
const reverbNode = getReverb(orbit, size);
|
||||
reverbSend = effectSend(post, reverbNode, room);
|
||||
const reverbNode = getReverb(orbit, size, buffer);
|
||||
reverbSend = effectSend(post, reverbNode, room, size);
|
||||
}
|
||||
|
||||
// analyser
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue