This commit is contained in:
Felix Roos 2023-10-14 10:08:53 +02:00
commit 4fa0a9064b
6 changed files with 99 additions and 46 deletions

View file

@ -1030,6 +1030,15 @@ const generic_params = [
* *
*/ */
['roomfade', 'rfade'], ['roomfade', 'rfade'],
/**
* Sets the sample to use as an impulse response for the reverb. * * @name iresponse
* @param {string | Pattern} sample to use as an impulse response
* @synonyms ir
* @example
* s("bd sd").room(.8).ir("<shaker_large:0 shaker_large:2>")
*
*/
[['ir', 'i'], 'iresponse'],
/** /**
* Sets the room size of the reverb, see {@link room}. * Sets the room size of the reverb, see {@link room}.
* When this property is changed, the reverb will be recaculated, so only change this sparsely.. * When this property is changed, the reverb will be recaculated, so only change this sparsely..

View file

@ -3,6 +3,6 @@
This folder demonstrates how to set up a strudel repl using vite and vanilla JS + codemirror. Run it using: This folder demonstrates how to set up a strudel repl using vite and vanilla JS + codemirror. Run it using:
```sh ```sh
npm i pnpm i
npm run dev pnpm dev
``` ```

View file

@ -1,9 +1,31 @@
import reverbGen from './reverbGen.mjs'; import reverbGen from './reverbGen.mjs';
if (typeof AudioContext !== 'undefined') { if (typeof AudioContext !== 'undefined') {
AudioContext.prototype.createReverb = function (duration, fade, lp, dim) { AudioContext.prototype.adjustLength = function (duration, buffer) {
const newLength = buffer.sampleRate * duration;
const newBuffer = this.createBuffer(buffer.numberOfChannels, buffer.length, buffer.sampleRate);
for (let channel = 0; channel < buffer.numberOfChannels; channel++) {
let oldData = buffer.getChannelData(channel);
let newData = newBuffer.getChannelData(channel);
for (let i = 0; i < newLength; i++) {
newData[i] = oldData[i] || 0;
}
}
return newBuffer;
};
AudioContext.prototype.createReverb = function (duration, fade, lp, dim, ir) {
const convolver = this.createConvolver(); const convolver = this.createConvolver();
convolver.generate = (d = 2, fade = 0.1, lp = 15000, dim = 1000) => { convolver.generate = (d = 2, fade = 0.1, lp = 15000, dim = 1000, ir) => {
convolver.duration = d;
convolver.fade = fade;
convolver.lp = lp;
convolver.dim = dim;
convolver.ir = ir;
if (ir) {
convolver.buffer = this.adjustLength(d, ir);
} else {
reverbGen.generateReverb( reverbGen.generateReverb(
{ {
audioContext: this, audioContext: this,
@ -17,12 +39,9 @@ if (typeof AudioContext !== 'undefined') {
convolver.buffer = buffer; convolver.buffer = buffer;
}, },
); );
convolver.duration = d; }
convolver.fade = fade;
convolver.lp = lp;
convolver.dim = dim;
}; };
convolver.generate(duration, fade, lp, dim); convolver.generate(duration, fade, lp, dim, ir);
return convolver; return convolver;
}; };
} }

View file

@ -48,6 +48,7 @@ reverbGen.generateReverb = function (params, callback) {
/** Creates a canvas element showing a graph of the given data. /** Creates a canvas element showing a graph of the given data.
@param {!Float32Array} data An array of numbers, or a Float32Array. @param {!Float32Array} data An array of numbers, or a Float32Array.
@param {number} width Width in pixels of the canvas. @param {number} width Width in pixels of the canvas.
@param {number} height Height in pixels of the canvas. @param {number} height Height in pixels of the canvas.

View file

@ -12,14 +12,18 @@ import workletsUrl from './worklets.mjs?url';
import { createFilter, gainNode, getCompressor } from './helpers.mjs'; import { createFilter, gainNode, getCompressor } from './helpers.mjs';
import { map } from 'nanostores'; import { map } from 'nanostores';
import { logger } from './logger.mjs'; import { logger } from './logger.mjs';
import { loadBuffer } from './sampler.mjs';
export const soundMap = map(); export const soundMap = map();
export function registerSound(key, onTrigger, data = {}) { export function registerSound(key, onTrigger, data = {}) {
soundMap.setKey(key, { onTrigger, data }); soundMap.setKey(key, { onTrigger, data });
} }
export function getSound(s) { export function getSound(s) {
return soundMap.get()[s]; return soundMap.get()[s];
} }
export const resetLoadedSounds = () => soundMap.set({}); export const resetLoadedSounds = () => soundMap.set({});
let audioContext; let audioContext;
@ -46,6 +50,7 @@ export const panic = () => {
}; };
let workletsLoading; let workletsLoading;
function loadWorklets() { function loadWorklets() {
if (workletsLoading) { if (workletsLoading) {
return workletsLoading; return workletsLoading;
@ -89,6 +94,7 @@ export async function initAudioOnFirstClick(options) {
let delays = {}; let delays = {};
const maxfeedback = 0.98; const maxfeedback = 0.98;
function getDelay(orbit, delaytime, delayfeedback, t) { function getDelay(orbit, delaytime, delayfeedback, t) {
if (delayfeedback > maxfeedback) { if (delayfeedback > maxfeedback) {
//logger(`delayfeedback was clamped to ${maxfeedback} to save your ears`); //logger(`delayfeedback was clamped to ${maxfeedback} to save your ears`);
@ -110,33 +116,33 @@ let reverbs = {};
let hasChanged = (now, before) => now !== undefined && now !== before; let hasChanged = (now, before) => now !== undefined && now !== before;
function getReverb(orbit, duration, fade, lp, dim) { function getReverb(orbit, duration, fade, lp, dim, ir) {
// If no reverb has been created for a given orbit, create one // If no reverb has been created for a given orbit, create one
if (!reverbs[orbit]) { if (!reverbs[orbit]) {
const ac = getAudioContext(); const ac = getAudioContext();
const reverb = ac.createReverb(duration, fade, lp, dim); const reverb = ac.createReverb(duration, fade, lp, dim, ir);
reverb.connect(getDestination()); reverb.connect(getDestination());
reverbs[orbit] = reverb; reverbs[orbit] = reverb;
} }
if ( if (
hasChanged(duration, reverbs[orbit].duration) || hasChanged(duration, reverbs[orbit].duration) ||
hasChanged(fade, reverbs[orbit].fade) || hasChanged(fade, reverbs[orbit].fade) ||
hasChanged(lp, reverbs[orbit].lp) || hasChanged(lp, reverbs[orbit].lp) ||
hasChanged(dim, reverbs[orbit].dim) hasChanged(dim, reverbs[orbit].dim) ||
reverbs[orbit].ir !== ir
) { ) {
// only regenerate when something has changed // only regenerate when something has changed
// avoids endless regeneration on things like // avoids endless regeneration on things like
// stack(s("a"), s("b").rsize(8)).room(.5) // stack(s("a"), s("b").rsize(8)).room(.5)
// this only works when args may stay undefined until here // this only works when args may stay undefined until here
// setting default values breaks this // setting default values breaks this
reverbs[orbit].generate(duration, fade, lp, dim); reverbs[orbit].generate(duration, fade, lp, dim, ir);
} }
return reverbs[orbit]; return reverbs[orbit];
} }
export let analyser, analyserData /* s = {} */; export let analyser, analyserData /* s = {} */;
export function getAnalyser(/* orbit, */ fftSize = 2048) { export function getAnalyser(/* orbit, */ fftSize = 2048) {
if (!analyser /*s [orbit] */) { if (!analyser /*s [orbit] */) {
const analyserNode = getAudioContext().createAnalyser(); const analyserNode = getAudioContext().createAnalyser();
@ -235,6 +241,8 @@ export const superdough = async (value, deadline, hapDuration) => {
roomlp, roomlp,
roomdim, roomdim,
roomsize, roomsize,
ir,
i = 0,
velocity = 1, velocity = 1,
analyze, // analyser wet analyze, // analyser wet
fft = 8, // fftSize 0 - 10 fft = 8, // fftSize 0 - 10
@ -271,6 +279,7 @@ export const superdough = async (value, deadline, hapDuration) => {
// this can be used for things like speed(0) in the sampler // this can be used for things like speed(0) in the sampler
return; return;
} }
if (ac.currentTime > t) { if (ac.currentTime > t) {
logger('[webaudio] skip hap: still loading', ac.currentTime - t); logger('[webaudio] skip hap: still loading', ac.currentTime - t);
return; return;
@ -383,7 +392,18 @@ export const superdough = async (value, deadline, hapDuration) => {
// reverb // reverb
let reverbSend; let reverbSend;
if (room > 0) { if (room > 0) {
const reverbNode = getReverb(orbit, roomsize, roomfade, roomlp, roomdim); let roomIR;
if (ir !== undefined) {
let url;
let sample = getSound(ir);
if (Array.isArray(sample)) {
url = sample.data.samples[i % sample.data.samples.length];
} else if (typeof sample === 'object') {
url = Object.values(sample.data.samples).flat()[i % Object.values(sample.data.samples).length];
}
roomIR = await loadBuffer(url, ac, ir, 0);
}
const reverbNode = getReverb(orbit, roomsize, roomfade, roomlp, roomdim, roomIR);
reverbSend = effectSend(post, reverbNode, room); reverbSend = effectSend(post, reverbNode, room);
} }

View file

@ -227,4 +227,8 @@ global effects use the same chain for all events of the same orbit:
<JsDoc client:idle name="roomdim" h={0} /> <JsDoc client:idle name="roomdim" h={0} />
### iresponse
<JsDoc client:idle name="iresponse" h={0} />
Next, we'll look at strudel's support for [Csound](/learn/csound). Next, we'll look at strudel's support for [Csound](/learn/csound).