mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-27 13:38:40 +00:00
Merge branch 'main' of https://github.com/tidalcycles/strudel
This commit is contained in:
commit
4fa0a9064b
@ -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..
|
||||||
|
|||||||
@ -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
|
||||||
```
|
```
|
||||||
|
|||||||
@ -1,28 +1,47 @@
|
|||||||
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) => {
|
||||||
reverbGen.generateReverb(
|
|
||||||
{
|
|
||||||
audioContext: this,
|
|
||||||
numChannels: 2,
|
|
||||||
decayTime: d,
|
|
||||||
fadeInTime: fade,
|
|
||||||
lpFreqStart: lp,
|
|
||||||
lpFreqEnd: dim,
|
|
||||||
},
|
|
||||||
(buffer) => {
|
|
||||||
convolver.buffer = buffer;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
convolver.duration = d;
|
convolver.duration = d;
|
||||||
convolver.fade = fade;
|
convolver.fade = fade;
|
||||||
convolver.lp = lp;
|
convolver.lp = lp;
|
||||||
convolver.dim = dim;
|
convolver.dim = dim;
|
||||||
|
convolver.ir = ir;
|
||||||
|
if (ir) {
|
||||||
|
convolver.buffer = this.adjustLength(d, ir);
|
||||||
|
} else {
|
||||||
|
reverbGen.generateReverb(
|
||||||
|
{
|
||||||
|
audioContext: this,
|
||||||
|
numChannels: 2,
|
||||||
|
decayTime: d,
|
||||||
|
fadeInTime: fade,
|
||||||
|
lpFreqStart: lp,
|
||||||
|
lpFreqEnd: dim,
|
||||||
|
},
|
||||||
|
(buffer) => {
|
||||||
|
convolver.buffer = buffer;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
convolver.generate(duration, fade, lp, dim);
|
convolver.generate(duration, fade, lp, dim, ir);
|
||||||
return convolver;
|
return convolver;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,11 +16,11 @@ var reverbGen = {};
|
|||||||
|
|
||||||
/** Generates a reverb impulse response.
|
/** Generates a reverb impulse response.
|
||||||
|
|
||||||
@param {!Object} params TODO: Document the properties.
|
@param {!Object} params TODO: Document the properties.
|
||||||
@param {!function(!AudioBuffer)} callback Function to call when
|
@param {!function(!AudioBuffer)} callback Function to call when
|
||||||
the impulse response has been generated. The impulse response
|
the impulse response has been generated. The impulse response
|
||||||
is passed to this function as its parameter. May be called
|
is passed to this function as its parameter. May be called
|
||||||
immediately within the current execution context, or later. */
|
immediately within the current execution context, or later. */
|
||||||
reverbGen.generateReverb = function (params, callback) {
|
reverbGen.generateReverb = function (params, callback) {
|
||||||
var audioContext = params.audioContext || new AudioContext();
|
var audioContext = params.audioContext || new AudioContext();
|
||||||
var sampleRate = audioContext.sampleRate;
|
var sampleRate = audioContext.sampleRate;
|
||||||
@ -48,12 +48,13 @@ 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 {number} width Width in pixels of the canvas.
|
@param {!Float32Array} data An array of numbers, or a Float32Array.
|
||||||
@param {number} height Height in pixels of the canvas.
|
@param {number} width Width in pixels of the canvas.
|
||||||
@param {number} min Minimum value of data for the graph (lower edge).
|
@param {number} height Height in pixels of the canvas.
|
||||||
@param {number} max Maximum value of data in the graph (upper edge).
|
@param {number} min Minimum value of data for the graph (lower edge).
|
||||||
@return {!CanvasElement} The generated canvas element. */
|
@param {number} max Maximum value of data in the graph (upper edge).
|
||||||
|
@return {!CanvasElement} The generated canvas element. */
|
||||||
reverbGen.generateGraph = function (data, width, height, min, max) {
|
reverbGen.generateGraph = function (data, width, height, min, max) {
|
||||||
var canvas = document.createElement('canvas');
|
var canvas = document.createElement('canvas');
|
||||||
canvas.width = width;
|
canvas.width = width;
|
||||||
@ -72,13 +73,13 @@ reverbGen.generateGraph = function (data, width, height, min, max) {
|
|||||||
|
|
||||||
/** Applies a constantly changing lowpass filter to the given sound.
|
/** Applies a constantly changing lowpass filter to the given sound.
|
||||||
|
|
||||||
@private
|
@private
|
||||||
@param {!AudioBuffer} input
|
@param {!AudioBuffer} input
|
||||||
@param {number} lpFreqStart
|
@param {number} lpFreqStart
|
||||||
@param {number} lpFreqEnd
|
@param {number} lpFreqEnd
|
||||||
@param {number} lpFreqEndAt
|
@param {number} lpFreqEndAt
|
||||||
@param {!function(!AudioBuffer)} callback May be called
|
@param {!function(!AudioBuffer)} callback May be called
|
||||||
immediately within the current execution context, or later.*/
|
immediately within the current execution context, or later.*/
|
||||||
var applyGradualLowpass = function (input, lpFreqStart, lpFreqEnd, lpFreqEndAt, callback) {
|
var applyGradualLowpass = function (input, lpFreqStart, lpFreqEnd, lpFreqEndAt, callback) {
|
||||||
if (lpFreqStart == 0) {
|
if (lpFreqStart == 0) {
|
||||||
callback(input);
|
callback(input);
|
||||||
@ -110,8 +111,8 @@ var applyGradualLowpass = function (input, lpFreqStart, lpFreqEnd, lpFreqEndAt,
|
|||||||
};
|
};
|
||||||
|
|
||||||
/** @private
|
/** @private
|
||||||
@param {!AudioBuffer} buffer
|
@param {!AudioBuffer} buffer
|
||||||
@return {!Array.<!Float32Array>} An array containing the Float32Array of each channel's samples. */
|
@return {!Array.<!Float32Array>} An array containing the Float32Array of each channel's samples. */
|
||||||
var getAllChannelData = function (buffer) {
|
var getAllChannelData = function (buffer) {
|
||||||
var channels = [];
|
var channels = [];
|
||||||
for (var i = 0; i < buffer.numberOfChannels; i++) {
|
for (var i = 0; i < buffer.numberOfChannels; i++) {
|
||||||
@ -121,7 +122,7 @@ var getAllChannelData = function (buffer) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/** @private
|
/** @private
|
||||||
@return {number} A random number from -1 to 1. */
|
@return {number} A random number from -1 to 1. */
|
||||||
var randomSample = function () {
|
var randomSample = function () {
|
||||||
return Math.random() * 2 - 1;
|
return Math.random() * 2 - 1;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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).
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user