mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-23 05:33:13 -04:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e3c34f22fd | |||
| f2608e712b | |||
| 34b41fc26f | |||
| f6e171fed8 | |||
| 2511dcc09e | |||
| 2fbdc9fdd7 | |||
| 0a96fa5896 | |||
| 06ebcbff8c | |||
| ff2c59a2a6 | |||
| 9b9176325b | |||
| e84c39837f | |||
| 6527c68cda | |||
| 6a09f54b25 | |||
| dc6b766ab7 | |||
| 9eb3c9410d | |||
| 189e650a73 | |||
| a5886bb9d4 | |||
| 877bc95a58 | |||
| 18b1739b89 | |||
| b54139376e | |||
| d64a0ef0eb | |||
| 659071a4ee | |||
| 64f7bc4442 | |||
| 8c97f2e2d0 | |||
| b8c98736c5 | |||
| a0fc52b1ec | |||
| 0693a32736 | |||
| e490774294 | |||
| 34e8a57472 | |||
| 59c8d70714 | |||
| ce7cff2c3b | |||
| 32fe73aba2 | |||
| cf55d4c8d1 | |||
| e7e636886d | |||
| e9aef9e4d4 |
@@ -3,8 +3,6 @@
|
|||||||
Live coding patterns on the web
|
Live coding patterns on the web
|
||||||
https://strudel.cc/
|
https://strudel.cc/
|
||||||
|
|
||||||
Development is moving to https://codeberg.org/uzu/strudel
|
|
||||||
|
|
||||||
- Try it here: <https://strudel.cc>
|
- Try it here: <https://strudel.cc>
|
||||||
- Docs: <https://strudel.cc/learn>
|
- Docs: <https://strudel.cc/learn>
|
||||||
- Technical Blog Post: <https://loophole-letters.vercel.app/strudel>
|
- Technical Blog Post: <https://loophole-letters.vercel.app/strudel>
|
||||||
@@ -47,3 +45,5 @@ There is a #strudel channel on the TidalCycles discord: <https://discord.com/inv
|
|||||||
You can also ask questions and find related discussions on the tidal club forum: <https://club.tidalcycles.org/>
|
You can also ask questions and find related discussions on the tidal club forum: <https://club.tidalcycles.org/>
|
||||||
|
|
||||||
The discord and forum is shared with the haskell (tidal) and python (vortex) siblings of this project.
|
The discord and forum is shared with the haskell (tidal) and python (vortex) siblings of this project.
|
||||||
|
|
||||||
|
We also have a mastodon account: <a rel="me" href="https://social.toplap.org/@strudel">social.toplap.org/@strudel</a>
|
||||||
|
|||||||
@@ -56,11 +56,13 @@ const buildExamples = (examples) =>
|
|||||||
|
|
||||||
export const Autocomplete = ({ doc, label }) =>
|
export const Autocomplete = ({ doc, label }) =>
|
||||||
h`
|
h`
|
||||||
<div class="autocomplete-info-tooltip">
|
<div class="autocomplete-info-container">
|
||||||
<h3 class="autocomplete-info-function-name">${label || getDocLabel(doc)}</h3>
|
<div class="autocomplete-info-tooltip">
|
||||||
${doc.description ? `<p class="autocomplete-info-function-description">${doc.description}</p>` : ''}
|
<h3 class="autocomplete-info-function-name">${label || getDocLabel(doc)}</h3>
|
||||||
${buildParamsList(doc.params)}
|
${doc.description ? `<div class="autocomplete-info-function-description">${doc.description}</div>` : ''}
|
||||||
${buildExamples(doc.examples)}
|
${buildParamsList(doc.params)}
|
||||||
|
${buildExamples(doc.examples)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`[0];
|
`[0];
|
||||||
|
|
||||||
@@ -98,4 +100,4 @@ export const strudelAutocomplete = (context) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const isAutoCompletionEnabled = (enabled) =>
|
export const isAutoCompletionEnabled = (enabled) =>
|
||||||
enabled ? [autocompletion({ override: [strudelAutocomplete] })] : [];
|
enabled ? [autocompletion({ override: [strudelAutocomplete], closeOnBlur: false })] : [];
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { describe, bench } from 'vitest';
|
import { describe, bench } from 'vitest';
|
||||||
|
|
||||||
import { calculateTactus, sequence, stack } from '../index.mjs';
|
import { calculateSteps, sequence, stack } from '../index.mjs';
|
||||||
|
|
||||||
const pat64 = sequence(...Array(64).keys());
|
const pat64 = sequence(...Array(64).keys());
|
||||||
|
|
||||||
describe('steps', () => {
|
describe('steps', () => {
|
||||||
calculateTactus(true);
|
calculateSteps(true);
|
||||||
bench(
|
bench(
|
||||||
'+tactus',
|
'+tactus',
|
||||||
() => {
|
() => {
|
||||||
@@ -14,7 +14,7 @@ describe('steps', () => {
|
|||||||
{ time: 1000 },
|
{ time: 1000 },
|
||||||
);
|
);
|
||||||
|
|
||||||
calculateTactus(false);
|
calculateSteps(false);
|
||||||
bench(
|
bench(
|
||||||
'-tactus',
|
'-tactus',
|
||||||
() => {
|
() => {
|
||||||
@@ -25,7 +25,7 @@ describe('steps', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('stack', () => {
|
describe('stack', () => {
|
||||||
calculateTactus(true);
|
calculateSteps(true);
|
||||||
bench(
|
bench(
|
||||||
'+tactus',
|
'+tactus',
|
||||||
() => {
|
() => {
|
||||||
@@ -34,7 +34,7 @@ describe('stack', () => {
|
|||||||
{ time: 1000 },
|
{ time: 1000 },
|
||||||
);
|
);
|
||||||
|
|
||||||
calculateTactus(false);
|
calculateSteps(false);
|
||||||
bench(
|
bench(
|
||||||
'-tactus',
|
'-tactus',
|
||||||
() => {
|
() => {
|
||||||
@@ -43,4 +43,4 @@ describe('stack', () => {
|
|||||||
{ time: 1000 },
|
{ time: 1000 },
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
calculateTactus(true);
|
calculateSteps(true);
|
||||||
|
|||||||
@@ -1516,6 +1516,29 @@ export const { roomfade, rfade } = registerControl('roomfade', 'rfade');
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export const { ir, iresponse } = registerControl(['ir', 'i'], 'iresponse');
|
export const { ir, iresponse } = registerControl(['ir', 'i'], 'iresponse');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets speed of the sample for the impulse response.
|
||||||
|
* @name irspeed
|
||||||
|
* @param {string | Pattern} speed
|
||||||
|
* @example
|
||||||
|
* samples('github:switchangel/pad')
|
||||||
|
* $: s("brk/2").fit().scrub(irand(16).div(16).seg(8)).ir("swpad:4").room(.2).irspeed("<2 1 .5>/2").irbegin(.5).roomsize(.5)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const { irspeed } = registerControl('irspeed');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the beginning of the IR response sample
|
||||||
|
* @name irbegin
|
||||||
|
* @param {string | Pattern} begin between 0 and 1
|
||||||
|
* @synonyms ir
|
||||||
|
* @example
|
||||||
|
* samples('github:switchangel/pad')
|
||||||
|
* $: s("brk/2").fit().scrub(irand(16).div(16).seg(8)).ir("swpad:4").room(.65).irspeed("-2").irbegin("<0 .5 .75>/2").roomsize(.6)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const { irbegin } = registerControl('irbegin');
|
||||||
/**
|
/**
|
||||||
* Sets the room size of the reverb, see `room`.
|
* Sets the room size of the reverb, see `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..
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ let debounce = 1000,
|
|||||||
|
|
||||||
export function errorLogger(e, origin = 'cyclist') {
|
export function errorLogger(e, origin = 'cyclist') {
|
||||||
//TODO: add some kind of debug flag that enables this while in dev mode
|
//TODO: add some kind of debug flag that enables this while in dev mode
|
||||||
// console.error(e);
|
console.error(e);
|
||||||
logger(`[${origin}] error: ${e.message}`);
|
logger(`[${origin}] error: ${e.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3277,7 +3277,7 @@ export const slice = register(
|
|||||||
* @memberof Pattern
|
* @memberof Pattern
|
||||||
* @returns Pattern
|
* @returns Pattern
|
||||||
* @example
|
* @example
|
||||||
* s("bd!8").onTriggerTime((hap) => {console.info(hap)})
|
* s("bd!8").onTriggerTime((hap) => {console.log(hap)})
|
||||||
*/
|
*/
|
||||||
Pattern.prototype.onTriggerTime = function (func) {
|
Pattern.prototype.onTriggerTime = function (func) {
|
||||||
return this.onTrigger((hap, currentTime, _cps, targetTime) => {
|
return this.onTrigger((hap, currentTime, _cps, targetTime) => {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { describe, bench } from 'vitest';
|
import { describe, bench } from 'vitest';
|
||||||
|
|
||||||
import { calculateTactus } from '../../core/index.mjs';
|
import { calculateSteps } from '../../core/index.mjs';
|
||||||
import { mini } from '../index.mjs';
|
import { mini } from '../index.mjs';
|
||||||
|
|
||||||
describe('mini', () => {
|
describe('mini', () => {
|
||||||
calculateTactus(true);
|
calculateSteps(true);
|
||||||
bench(
|
bench(
|
||||||
'+tactus',
|
'+tactus',
|
||||||
() => {
|
() => {
|
||||||
@@ -13,7 +13,7 @@ describe('mini', () => {
|
|||||||
{ time: 1000 },
|
{ time: 1000 },
|
||||||
);
|
);
|
||||||
|
|
||||||
calculateTactus(false);
|
calculateSteps(false);
|
||||||
bench(
|
bench(
|
||||||
'-tactus',
|
'-tactus',
|
||||||
() => {
|
() => {
|
||||||
@@ -21,5 +21,5 @@ describe('mini', () => {
|
|||||||
},
|
},
|
||||||
{ time: 1000 },
|
{ time: 1000 },
|
||||||
);
|
);
|
||||||
calculateTactus(true);
|
calculateSteps(true);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { oscTriggerTauri } from '../desktopbridge/oscbridge.mjs';
|
/* import { oscTriggerTauri } from '../desktopbridge/oscbridge.mjs';
|
||||||
import { isTauri } from '../desktopbridge/utils.mjs';
|
import { isTauri } from '../desktopbridge/utils.mjs'; */
|
||||||
import { oscTrigger } from './osc.mjs';
|
import { oscTrigger } from './osc.mjs';
|
||||||
|
|
||||||
const trigger = isTauri() ? oscTriggerTauri : oscTrigger;
|
const trigger = /* isTauri() ? oscTriggerTauri : */ oscTrigger;
|
||||||
|
|
||||||
export const superdirtOutput = (hap, deadline, hapDuration, cps, targetTime) => {
|
export const superdirtOutput = (hap, deadline, hapDuration, cps, targetTime) => {
|
||||||
const currentTime = performance.now() / 1000;
|
const currentTime = performance.now() / 1000;
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
import cowsay from 'cowsay';
|
import cowsay from 'cowsay';
|
||||||
import { createReadStream, existsSync } from 'fs';
|
import { createReadStream, existsSync, writeFileSync } from 'fs';
|
||||||
import { readdir } from 'fs/promises';
|
import { readdir } from 'fs/promises';
|
||||||
import http from 'http';
|
import http from 'http';
|
||||||
import { join, sep } from 'path';
|
import { join, resolve, sep } from 'path';
|
||||||
|
import readline from 'readline';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
|
|
||||||
// eslint-disable-next-line
|
|
||||||
const LOG = !!process.env.LOG || false;
|
const LOG = !!process.env.LOG || false;
|
||||||
|
const VALID_AUDIO_EXTENSIONS = ['wav', 'mp3', 'ogg'];
|
||||||
|
|
||||||
|
const isAudioFile = (f) => {
|
||||||
|
const ext = f.split('.').slice(-1)[0].toLowerCase();
|
||||||
|
return VALID_AUDIO_EXTENSIONS.includes(ext);
|
||||||
|
};
|
||||||
|
|
||||||
async function getFilesInDirectory(directory) {
|
async function getFilesInDirectory(directory) {
|
||||||
let files = [];
|
let files = [];
|
||||||
@@ -21,32 +27,32 @@ async function getFilesInDirectory(directory) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const subFiles = (await getFilesInDirectory(fullPath)).filter((f) =>
|
const subFiles = (await getFilesInDirectory(fullPath)).filter(isAudioFile);
|
||||||
['wav', 'mp3', 'ogg'].includes(f.split('.').slice(-1)[0].toLowerCase()),
|
|
||||||
);
|
|
||||||
files = files.concat(subFiles);
|
files = files.concat(subFiles);
|
||||||
LOG && console.log(`${dirent.name} (${subFiles.length})`);
|
LOG && console.log(`${dirent.name} (${subFiles.length})`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
LOG && console.warn(`skipped due to error: ${fullPath}`);
|
LOG && console.warn(`skipped due to error: ${fullPath}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
files.push(fullPath);
|
isAudioFile(fullPath) && files.push(fullPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getBanks(directory) {
|
async function getBanks(directory, flat = false) {
|
||||||
let files = await getFilesInDirectory(directory);
|
let files = await getFilesInDirectory(directory);
|
||||||
let banks = {};
|
let banks = {};
|
||||||
directory = directory.split(sep).join('/');
|
directory = directory.split(sep).join('/');
|
||||||
files = files.map((path) => {
|
files = files.map((path) => {
|
||||||
path = path.split(sep).join('/');
|
path = path.split(sep).join('/');
|
||||||
const [bank] = path.split('/').slice(-2);
|
const subDir = path.replace(directory, '');
|
||||||
|
const subDirFlat = subDir.replaceAll('/', '_').slice(1); // remove initial underscore
|
||||||
|
const subDirFlatStem = subDirFlat.replace(/\.[^.]+$/, ''); // remove extension
|
||||||
|
let bank = flat ? subDirFlatStem : path.split('/').slice(-2)[0];
|
||||||
banks[bank] = banks[bank] || [];
|
banks[bank] = banks[bank] || [];
|
||||||
const relativeUrl = path.replace(directory, '');
|
banks[bank].push(subDir);
|
||||||
banks[bank].push(relativeUrl);
|
return subDir;
|
||||||
return relativeUrl;
|
|
||||||
});
|
});
|
||||||
banks._base = `http://localhost:5432`;
|
banks._base = `http://localhost:5432`;
|
||||||
return { banks, files };
|
return { banks, files };
|
||||||
@@ -54,14 +60,44 @@ async function getBanks(directory) {
|
|||||||
|
|
||||||
const args = process.argv.slice(2);
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
// eslint-disable-next-line
|
function getArgValue(flag) {
|
||||||
const directory = process.cwd();
|
const i = args.indexOf(flag);
|
||||||
|
if (i !== -1) {
|
||||||
|
const nextIsFlag = args[i + 1]?.startsWith('--') ?? true;
|
||||||
|
if (nextIsFlag) return true;
|
||||||
|
return args[i + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInput(query) {
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout,
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Promise((resolve) =>
|
||||||
|
rl.question(query, (response) => {
|
||||||
|
rl.close();
|
||||||
|
resolve(response);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let directory = getArgValue('--dir') || process.cwd();
|
||||||
|
directory = resolve(directory);
|
||||||
if (args.includes('--json')) {
|
if (args.includes('--json')) {
|
||||||
const { banks, files } = await getBanks(directory);
|
const { banks } = await getBanks(directory, getArgValue('--flat'));
|
||||||
const json = JSON.stringify(banks);
|
const json = JSON.stringify(banks);
|
||||||
console.log(json);
|
const outFile = resolve(directory, 'strudel.json');
|
||||||
process.exit(0);
|
if (existsSync(outFile)) {
|
||||||
|
const answer = await getInput(`Warning: File already exists at ${outFile}. Overwrite? (y/N): `);
|
||||||
|
if (answer.toLowerCase() !== 'y') {
|
||||||
|
console.log('Aborted.');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeFileSync(outFile, json, 'utf8');
|
||||||
|
console.log(`Wrote json to ${outFile}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
@@ -74,7 +110,7 @@ console.log(
|
|||||||
|
|
||||||
const server = http.createServer(async (req, res) => {
|
const server = http.createServer(async (req, res) => {
|
||||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||||
const { banks, files } = await getBanks(directory);
|
const { banks, files } = await getBanks(directory, getArgValue('--flat'));
|
||||||
if (req.url === '/') {
|
if (req.url === '/') {
|
||||||
res.setHeader('Content-Type', 'application/json');
|
res.setHeader('Content-Type', 'application/json');
|
||||||
return res.end(JSON.stringify(banks));
|
return res.end(JSON.stringify(banks));
|
||||||
@@ -82,7 +118,7 @@ const server = http.createServer(async (req, res) => {
|
|||||||
let subpath = decodeURIComponent(req.url);
|
let subpath = decodeURIComponent(req.url);
|
||||||
const filePath = join(directory, subpath.split('/').join(sep));
|
const filePath = join(directory, subpath.split('/').join(sep));
|
||||||
|
|
||||||
//console.log('GET:', filePath);
|
// console.log('GET:', filePath);
|
||||||
const isFound = existsSync(filePath);
|
const isFound = existsSync(filePath);
|
||||||
if (!isFound) {
|
if (!isFound) {
|
||||||
res.statusCode = 404;
|
res.statusCode = 404;
|
||||||
|
|||||||
@@ -211,11 +211,29 @@ export function getVibratoOscillator(param, value, t) {
|
|||||||
export function webAudioTimeout(audioContext, onComplete, startTime, stopTime) {
|
export function webAudioTimeout(audioContext, onComplete, startTime, stopTime) {
|
||||||
const constantNode = new ConstantSourceNode(audioContext);
|
const constantNode = new ConstantSourceNode(audioContext);
|
||||||
|
|
||||||
constantNode.start(startTime);
|
// Certain browsers requires audio nodes to be connected in order for their onended events
|
||||||
constantNode.stop(stopTime);
|
// to fire, so we _mute it_ and then connect it to the destination
|
||||||
|
const zeroGain = gainNode(0);
|
||||||
|
zeroGain.connect(audioContext.destination);
|
||||||
|
constantNode.connect(zeroGain);
|
||||||
|
|
||||||
|
// Schedule the `onComplete` callback to occur at `stopTime`
|
||||||
constantNode.onended = () => {
|
constantNode.onended = () => {
|
||||||
|
// Ensure garbage collection
|
||||||
|
try {
|
||||||
|
zeroGain.disconnect();
|
||||||
|
} catch {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
constantNode.disconnect();
|
||||||
|
} catch {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
onComplete();
|
onComplete();
|
||||||
};
|
};
|
||||||
|
constantNode.start(startTime);
|
||||||
|
constantNode.stop(stopTime);
|
||||||
return constantNode;
|
return constantNode;
|
||||||
}
|
}
|
||||||
const mod = (freq, range = 1, type = 'sine') => {
|
const mod = (freq, range = 1, type = 'sine') => {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import reverbGen from './reverbGen.mjs';
|
import reverbGen from './reverbGen.mjs';
|
||||||
|
import { clamp } from './util.mjs';
|
||||||
|
|
||||||
if (typeof AudioContext !== 'undefined') {
|
if (typeof AudioContext !== 'undefined') {
|
||||||
AudioContext.prototype.adjustLength = function (duration, buffer) {
|
AudioContext.prototype.adjustLength = function (duration, buffer, speed = 1, offsetAmount = 0) {
|
||||||
|
const sampleOffset = Math.floor(clamp(offsetAmount, 0, 1) * buffer.length);
|
||||||
const newLength = buffer.sampleRate * duration;
|
const newLength = buffer.sampleRate * duration;
|
||||||
const newBuffer = this.createBuffer(buffer.numberOfChannels, buffer.length, buffer.sampleRate);
|
const newBuffer = this.createBuffer(buffer.numberOfChannels, buffer.length, buffer.sampleRate);
|
||||||
for (let channel = 0; channel < buffer.numberOfChannels; channel++) {
|
for (let channel = 0; channel < buffer.numberOfChannels; channel++) {
|
||||||
@@ -9,22 +11,30 @@ if (typeof AudioContext !== 'undefined') {
|
|||||||
let newData = newBuffer.getChannelData(channel);
|
let newData = newBuffer.getChannelData(channel);
|
||||||
|
|
||||||
for (let i = 0; i < newLength; i++) {
|
for (let i = 0; i < newLength; i++) {
|
||||||
newData[i] = oldData[i] || 0;
|
// loop the buffer around to prevent
|
||||||
|
let position = (sampleOffset + i * Math.abs(speed)) % oldData.length;
|
||||||
|
if (speed < 1) {
|
||||||
|
position = position * -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
newData[i] = oldData.at(position) || 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return newBuffer;
|
return newBuffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
AudioContext.prototype.createReverb = function (duration, fade, lp, dim, ir) {
|
AudioContext.prototype.createReverb = function (duration, fade, lp, dim, ir, irspeed, irbegin) {
|
||||||
const convolver = this.createConvolver();
|
const convolver = this.createConvolver();
|
||||||
convolver.generate = (d = 2, fade = 0.1, lp = 15000, dim = 1000, ir) => {
|
convolver.generate = (d = 2, fade = 0.1, lp = 15000, dim = 1000, ir, irspeed, irbegin) => {
|
||||||
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;
|
convolver.ir = ir;
|
||||||
|
convolver.irspeed = irspeed;
|
||||||
|
convolver.irbegin = irbegin;
|
||||||
if (ir) {
|
if (ir) {
|
||||||
convolver.buffer = this.adjustLength(d, ir);
|
convolver.buffer = this.adjustLength(d, ir, irspeed, irbegin);
|
||||||
} else {
|
} else {
|
||||||
reverbGen.generateReverb(
|
reverbGen.generateReverb(
|
||||||
{
|
{
|
||||||
@@ -41,7 +51,7 @@ if (typeof AudioContext !== 'undefined') {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
convolver.generate(duration, fade, lp, dim, ir);
|
convolver.generate(duration, fade, lp, dim, ir, irspeed, irbegin);
|
||||||
return convolver;
|
return convolver;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -282,7 +282,6 @@ export async function initAudioOnFirstClick(options) {
|
|||||||
return audioReady;
|
return audioReady;
|
||||||
}
|
}
|
||||||
|
|
||||||
let delays = {};
|
|
||||||
const maxfeedback = 0.98;
|
const maxfeedback = 0.98;
|
||||||
|
|
||||||
let channelMerger, destinationGain;
|
let channelMerger, destinationGain;
|
||||||
@@ -326,7 +325,7 @@ export const panic = () => {
|
|||||||
channelMerger == null;
|
channelMerger == null;
|
||||||
};
|
};
|
||||||
|
|
||||||
function getDelay(orbit, delaytime, delayfeedback, t, channels) {
|
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`);
|
||||||
}
|
}
|
||||||
@@ -414,7 +413,7 @@ function connectToOrbit(node, orbit) {
|
|||||||
function setOrbit(audioContext, orbit, channels) {
|
function setOrbit(audioContext, orbit, channels) {
|
||||||
if (orbits[orbit] == null) {
|
if (orbits[orbit] == null) {
|
||||||
orbits[orbit] = {
|
orbits[orbit] = {
|
||||||
gain: new GainNode(audioContext, { gain: 1 }),
|
gain: new GainNode(audioContext, { gain: 1, channelCount: 2, channelCountMode: 'explicit' }),
|
||||||
};
|
};
|
||||||
connectToDestination(orbits[orbit].gain, channels);
|
connectToDestination(orbits[orbit].gain, channels);
|
||||||
}
|
}
|
||||||
@@ -442,19 +441,22 @@ function duckOrbit(audioContext, targetOrbit, t, attacktime = 0.1, duckdepth = 1
|
|||||||
}
|
}
|
||||||
|
|
||||||
let hasChanged = (now, before) => now !== undefined && now !== before;
|
let hasChanged = (now, before) => now !== undefined && now !== before;
|
||||||
function getReverb(orbit, duration, fade, lp, dim, ir) {
|
function getReverb(orbit, duration, fade, lp, dim, ir, irspeed, irbegin) {
|
||||||
// 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 (!orbits[orbit].reverbNode) {
|
if (!orbits[orbit].reverbNode) {
|
||||||
const ac = getAudioContext();
|
const ac = getAudioContext();
|
||||||
const reverb = ac.createReverb(duration, fade, lp, dim, ir);
|
const reverb = ac.createReverb(duration, fade, lp, dim, ir, irspeed, irbegin);
|
||||||
connectToOrbit(reverb, orbit);
|
connectToOrbit(reverb, orbit);
|
||||||
orbits[orbit].reverbNode = reverb;
|
orbits[orbit].reverbNode = reverb;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
hasChanged(duration, orbits[orbit].reverbNode.duration) ||
|
hasChanged(duration, orbits[orbit].reverbNode.duration) ||
|
||||||
hasChanged(fade, orbits[orbit].reverbNode.fade) ||
|
hasChanged(fade, orbits[orbit].reverbNode.fade) ||
|
||||||
hasChanged(lp, orbits[orbit].reverbNode.lp) ||
|
hasChanged(lp, orbits[orbit].reverbNode.lp) ||
|
||||||
hasChanged(dim, orbits[orbit].reverbNode.dim) ||
|
hasChanged(dim, orbits[orbit].reverbNode.dim) ||
|
||||||
|
hasChanged(irspeed, orbits[orbit].reverbNode.irspeed) ||
|
||||||
|
hasChanged(irbegin, orbits[orbit].reverbNode.irbegin) ||
|
||||||
orbits[orbit].reverbNode.ir !== ir
|
orbits[orbit].reverbNode.ir !== ir
|
||||||
) {
|
) {
|
||||||
// only regenerate when something has changed
|
// only regenerate when something has changed
|
||||||
@@ -462,7 +464,7 @@ function getReverb(orbit, duration, fade, lp, dim, ir) {
|
|||||||
// 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
|
||||||
orbits[orbit].reverbNode.generate(duration, fade, lp, dim, ir);
|
orbits[orbit].reverbNode.generate(duration, fade, lp, dim, ir, irspeed, irbegin);
|
||||||
}
|
}
|
||||||
return orbits[orbit].reverbNode;
|
return orbits[orbit].reverbNode;
|
||||||
}
|
}
|
||||||
@@ -619,6 +621,8 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
roomdim,
|
roomdim,
|
||||||
roomsize,
|
roomsize,
|
||||||
ir,
|
ir,
|
||||||
|
irspeed,
|
||||||
|
irbegin,
|
||||||
i = getDefaultValue('i'),
|
i = getDefaultValue('i'),
|
||||||
velocity = getDefaultValue('velocity'),
|
velocity = getDefaultValue('velocity'),
|
||||||
analyze, // analyser wet
|
analyze, // analyser wet
|
||||||
@@ -832,7 +836,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
// delay
|
// delay
|
||||||
let delaySend;
|
let delaySend;
|
||||||
if (delay > 0 && delaytime > 0 && delayfeedback > 0) {
|
if (delay > 0 && delaytime > 0 && delayfeedback > 0) {
|
||||||
const delayNode = getDelay(orbit, delaytime, delayfeedback, t, orbitChannels);
|
const delayNode = getDelay(orbit, delaytime, delayfeedback, t);
|
||||||
delaySend = effectSend(post, delayNode, delay);
|
delaySend = effectSend(post, delayNode, delay);
|
||||||
audioNodes.push(delaySend);
|
audioNodes.push(delaySend);
|
||||||
}
|
}
|
||||||
@@ -850,7 +854,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
}
|
}
|
||||||
roomIR = await loadBuffer(url, ac, ir, 0);
|
roomIR = await loadBuffer(url, ac, ir, 0);
|
||||||
}
|
}
|
||||||
const reverbNode = getReverb(orbit, roomsize, roomfade, roomlp, roomdim, roomIR, orbitChannels);
|
const reverbNode = getReverb(orbit, roomsize, roomfade, roomlp, roomdim, roomIR, irspeed, irbegin);
|
||||||
reverbSend = effectSend(post, reverbNode, room);
|
reverbSend = effectSend(post, reverbNode, room);
|
||||||
audioNodes.push(reverbSend);
|
audioNodes.push(reverbSend);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4875,6 +4875,43 @@ exports[`runs examples > example "irand" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "irbegin" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/8 | s:brk speed:0.5 unit:c begin:0 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||||
|
"[ 1/8 → 1/4 | s:brk speed:0.5 unit:c begin:0.625 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||||
|
"[ 1/4 → 3/8 | s:brk speed:0.5 unit:c begin:0.3125 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||||
|
"[ 3/8 → 1/2 | s:brk speed:0.5 unit:c begin:0.375 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||||
|
"[ 1/2 → 5/8 | s:brk speed:0.5 unit:c begin:0.25 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||||
|
"[ 5/8 → 3/4 | s:brk speed:0.5 unit:c begin:0.125 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||||
|
"[ 3/4 → 7/8 | s:brk speed:0.5 unit:c begin:0.1875 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||||
|
"[ 7/8 → 1/1 | s:brk speed:0.5 unit:c begin:0.375 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||||
|
"[ 1/1 → 9/8 | s:brk speed:0.5 unit:c begin:0.5 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||||
|
"[ 9/8 → 5/4 | s:brk speed:0.5 unit:c begin:0.625 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||||
|
"[ 5/4 → 11/8 | s:brk speed:0.5 unit:c begin:0.6875 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||||
|
"[ 11/8 → 3/2 | s:brk speed:0.5 unit:c begin:0.125 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||||
|
"[ 3/2 → 13/8 | s:brk speed:0.5 unit:c begin:0.5625 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||||
|
"[ 13/8 → 7/4 | s:brk speed:0.5 unit:c begin:0.4375 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||||
|
"[ 7/4 → 15/8 | s:brk speed:0.5 unit:c begin:0.1875 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||||
|
"[ 15/8 → 2/1 | s:brk speed:0.5 unit:c begin:0.5625 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||||
|
"[ 2/1 → 17/8 | s:brk speed:0.5 unit:c begin:0.9375 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||||
|
"[ 17/8 → 9/4 | s:brk speed:0.5 unit:c begin:0.875 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||||
|
"[ 9/4 → 19/8 | s:brk speed:0.5 unit:c begin:0.3125 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||||
|
"[ 19/8 → 5/2 | s:brk speed:0.5 unit:c begin:0.8125 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||||
|
"[ 5/2 → 21/8 | s:brk speed:0.5 unit:c begin:0.4375 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||||
|
"[ 21/8 → 11/4 | s:brk speed:0.5 unit:c begin:0.125 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||||
|
"[ 11/4 → 23/8 | s:brk speed:0.5 unit:c begin:0.625 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||||
|
"[ 23/8 → 3/1 | s:brk speed:0.5 unit:c begin:0 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||||
|
"[ 3/1 → 25/8 | s:brk speed:0.5 unit:c begin:0.1875 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||||
|
"[ 25/8 → 13/4 | s:brk speed:0.5 unit:c begin:0.3125 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||||
|
"[ 13/4 → 27/8 | s:brk speed:0.5 unit:c begin:0.9375 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||||
|
"[ 27/8 → 7/2 | s:brk speed:0.5 unit:c begin:0.375 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||||
|
"[ 7/2 → 29/8 | s:brk speed:0.5 unit:c begin:0.375 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||||
|
"[ 29/8 → 15/4 | s:brk speed:0.5 unit:c begin:0.9375 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||||
|
"[ 15/4 → 31/8 | s:brk speed:0.5 unit:c begin:0.75 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||||
|
"[ 31/8 → 4/1 | s:brk speed:0.5 unit:c begin:0.6875 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "iresponse" example index 0 1`] = `
|
exports[`runs examples > example "iresponse" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/4 | s:bd room:0.8 ir:shaker_large i:0 ]",
|
"[ 0/1 → 1/4 | s:bd room:0.8 ir:shaker_large i:0 ]",
|
||||||
@@ -4896,6 +4933,43 @@ exports[`runs examples > example "iresponse" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "irspeed" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/8 | s:brk speed:0.5 unit:c begin:0 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 1/8 → 1/4 | s:brk speed:0.5 unit:c begin:0.625 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 1/4 → 3/8 | s:brk speed:0.5 unit:c begin:0.3125 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 3/8 → 1/2 | s:brk speed:0.5 unit:c begin:0.375 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 1/2 → 5/8 | s:brk speed:0.5 unit:c begin:0.25 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 5/8 → 3/4 | s:brk speed:0.5 unit:c begin:0.125 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 3/4 → 7/8 | s:brk speed:0.5 unit:c begin:0.1875 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 7/8 → 1/1 | s:brk speed:0.5 unit:c begin:0.375 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 1/1 → 9/8 | s:brk speed:0.5 unit:c begin:0.5 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 9/8 → 5/4 | s:brk speed:0.5 unit:c begin:0.625 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 5/4 → 11/8 | s:brk speed:0.5 unit:c begin:0.6875 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 11/8 → 3/2 | s:brk speed:0.5 unit:c begin:0.125 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 3/2 → 13/8 | s:brk speed:0.5 unit:c begin:0.5625 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 13/8 → 7/4 | s:brk speed:0.5 unit:c begin:0.4375 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 7/4 → 15/8 | s:brk speed:0.5 unit:c begin:0.1875 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 15/8 → 2/1 | s:brk speed:0.5 unit:c begin:0.5625 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 2/1 → 17/8 | s:brk speed:0.5 unit:c begin:0.9375 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 17/8 → 9/4 | s:brk speed:0.5 unit:c begin:0.875 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 9/4 → 19/8 | s:brk speed:0.5 unit:c begin:0.3125 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 19/8 → 5/2 | s:brk speed:0.5 unit:c begin:0.8125 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 5/2 → 21/8 | s:brk speed:0.5 unit:c begin:0.4375 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 21/8 → 11/4 | s:brk speed:0.5 unit:c begin:0.125 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 11/4 → 23/8 | s:brk speed:0.5 unit:c begin:0.625 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 23/8 → 3/1 | s:brk speed:0.5 unit:c begin:0 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 3/1 → 25/8 | s:brk speed:0.5 unit:c begin:0.1875 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 25/8 → 13/4 | s:brk speed:0.5 unit:c begin:0.3125 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 13/4 → 27/8 | s:brk speed:0.5 unit:c begin:0.9375 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 27/8 → 7/2 | s:brk speed:0.5 unit:c begin:0.375 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 7/2 → 29/8 | s:brk speed:0.5 unit:c begin:0.375 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 29/8 → 15/4 | s:brk speed:0.5 unit:c begin:0.9375 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 15/4 → 31/8 | s:brk speed:0.5 unit:c begin:0.75 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
"[ 31/8 → 4/1 | s:brk speed:0.5 unit:c begin:0.6875 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "isaw" example index 0 1`] = `
|
exports[`runs examples > example "isaw" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/8 | note:c3 clip:1 ]",
|
"[ 0/1 → 1/8 | note:c3 clip:1 ]",
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
@@ -11,6 +11,129 @@ import { JsDoc } from '../../docs/JsDoc';
|
|||||||
Whether you're using a synth or a sample, you can apply any of the following built-in audio effects.
|
Whether you're using a synth or a sample, you can apply any of the following built-in audio effects.
|
||||||
As you might suspect, the effects can be chained together, and they accept a pattern string as their argument.
|
As you might suspect, the effects can be chained together, and they accept a pattern string as their argument.
|
||||||
|
|
||||||
|
# Signal chain
|
||||||
|
|
||||||
|
<img src="/img/strudel-signal-flow.png"></img>
|
||||||
|
|
||||||
|
The signal chain in Strudel is as follows:
|
||||||
|
|
||||||
|
- An sound-generating event is triggered by a pattern
|
||||||
|
- This has a start time and a duration, which is usually
|
||||||
|
controlled by the note length and ADSR parameters
|
||||||
|
- If we exceed the max polyphony, old sounds begin to die off
|
||||||
|
- Muted sounds (one whose `s` value is `-`, `~`, or `_`) are skipped
|
||||||
|
- A sound is produced (through, say, a sample or an oscillator)
|
||||||
|
- This is where detune-based effects (like `detune`, `penv`, etc. occur)
|
||||||
|
- The following will occur _in order_ and only if they've been called in the pattern. Note that all of these are
|
||||||
|
single use effects, meaning that multiple occurrences of them in a pattern will simply override the values
|
||||||
|
(e.g. you can't do `s("bd").lpf(100).distort(2).lpf(800)` to lowpass, distort, and then lowpass
|
||||||
|
again)
|
||||||
|
- Phase vocoder (`stretch`)
|
||||||
|
- Gain is applied (`gain`)
|
||||||
|
- This is where the main (volume) ADSR happens
|
||||||
|
- A lowpass filter (`lpf`)
|
||||||
|
- A highpass filter (`hpf`)
|
||||||
|
- A bandpass filter (`bandpass`)
|
||||||
|
- A vowel filter (`vowel`)
|
||||||
|
- Sample rate reduction (`coarse`)
|
||||||
|
- Bit crushing (`crush`)
|
||||||
|
- Waveshape distortion (`shape`)
|
||||||
|
- Normal distortion (`distort`)
|
||||||
|
- Tremolo (`tremolo`)
|
||||||
|
- Compressor (`compressor`)
|
||||||
|
- Panning (`pan`)
|
||||||
|
- Phaser (`phaser`)
|
||||||
|
- Postgain (`post`)
|
||||||
|
- The sound is then split into multiple destinations
|
||||||
|
- Dry output (amount controlled by `dry` parameter)
|
||||||
|
- The sends
|
||||||
|
- Analyzers
|
||||||
|
- These are used for tooling like `scope` and `spectrum` and their setup usually happens behind the scenes
|
||||||
|
- Delay (amount controlled by `delay` parameter)
|
||||||
|
- Reverb (amount controlled by `room` parameter)
|
||||||
|
- The dry output, delay, and reverb are joined into what is called the "orbit" of the pattern (see more in the section below)
|
||||||
|
- The `duck` effect affects the volume of all signals in the orbit
|
||||||
|
- The orbit is then sent to the mixer
|
||||||
|
|
||||||
|
## Orbits
|
||||||
|
|
||||||
|
Orbits are the way in which outputs are handled in Strudel. They also prescribe which delay and reverb to associate with the dry signal.
|
||||||
|
By default, all orbits are mixed down to channels `1` and `2` in stereo, however with the "Multi Channel Orbits" setting
|
||||||
|
(under Settings at the right) you can use them as individual 2 channel stereo outs (orbit `i` will be mapped to
|
||||||
|
to channels `2i` and `2i + 1`). You can then use routers like Blackhole 16 to retrieve and record all of the channels in a DAW for later processing.
|
||||||
|
|
||||||
|
The default orbit is `1` and it is set with `orbit`. You may send a sound to multiple orbits via mininotation
|
||||||
|
|
||||||
|
<MiniRepl client:visible tune={`s("white").orbit("2,3,4").gain(0.2)`} />
|
||||||
|
|
||||||
|
but please be careful as this will create three copies of the sound behind the scenes, meaning that if they are mixed
|
||||||
|
down to a single output, they will triple the volume. We've reduced the gain here to save your ears.
|
||||||
|
|
||||||
|
⚠️ There is only one delay and reverb per orbit, so please be aware that if you attempt to change the parameters on two
|
||||||
|
patterns pointing to the same orbit, it can lead to unpredictable results. Compare, for example, this pretty pluck
|
||||||
|
with a large reverb:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`
|
||||||
|
$: s("triangle*4").decay(0.5).n(irand(12)).scale('C minor')
|
||||||
|
.room(1).roomsize(10)`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
versus the same pluck with a muted kick drum coming in and overwriting the `roomsize` value:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`
|
||||||
|
$: s("triangle*4").decay(0.5).n(irand(12)).scale('C minor')
|
||||||
|
.room(1).roomsize(10)
|
||||||
|
|
||||||
|
$: s("bd\*4").room(0.01).roomsize(0.01).postgain(0)`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
This is due to them sharing the same orbit: the default of `1`. It can be corrected simply by updating the orbits to be
|
||||||
|
distinct:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`
|
||||||
|
$: s("triangle*4").decay(0.5).n(irand(12)).scale('C minor')
|
||||||
|
.room(1).roomsize(10).orbit(2)
|
||||||
|
|
||||||
|
$: s("bd\*4").room(0.01).roomsize(0.01).postgain(0)`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
## Continuous changes
|
||||||
|
|
||||||
|
As all of the above is triggered by a _sound occurring_, it is often the case that parameters may not be
|
||||||
|
modified continuously in time. For example,
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`
|
||||||
|
s("supersaw").lpf(tri.range(100, 5000).slow(2))`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
Will not produce a continually LFO'd low-pass filter due to the `tri` only being sampled every time the note hits
|
||||||
|
(in this case the default of once per cycle). You can fake it by introducing more sound-generating events, e.g.:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:visible
|
||||||
|
tune={`
|
||||||
|
s("supersaw").seg(16).lpf(tri.range(100, 5000).slow(2))`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
Some parameters _do_ induce continuous variations in time, though:
|
||||||
|
|
||||||
|
- The ADSR curve (governed by `attack`, `sustain`, `decay`, `release`)
|
||||||
|
- The pitch envelope curve (governed by `penv` and its associated ADSR)
|
||||||
|
- The FM curve (`fmenv`)
|
||||||
|
- The filter envelopes (`lpenv`, `hpenv`, `bpenv`)
|
||||||
|
- Tremolo (`tremolo`)
|
||||||
|
- Phaser (`phaser`)
|
||||||
|
- Vibrato (`vib`)
|
||||||
|
- Ducking (`duckorbit`)
|
||||||
|
|
||||||
# Filters
|
# Filters
|
||||||
|
|
||||||
Filters are an essential building block of [subtractive synthesis](https://en.wikipedia.org/wiki/Subtractive_synthesis).
|
Filters are an essential building block of [subtractive synthesis](https://en.wikipedia.org/wiki/Subtractive_synthesis).
|
||||||
|
|||||||
@@ -72,21 +72,19 @@
|
|||||||
|
|
||||||
/* Override default styles from the codemirror inline css for autocomplete info tooltip*/
|
/* Override default styles from the codemirror inline css for autocomplete info tooltip*/
|
||||||
.cm-tooltip.cm-completionInfo {
|
.cm-tooltip.cm-completionInfo {
|
||||||
padding: 12px !important;
|
padding: 0 !important;
|
||||||
padding-bottom: 12px !important;
|
|
||||||
border: 1px solid var(--foreground) !important;
|
border: 1px solid var(--foreground) !important;
|
||||||
border-radius: 4px !important;
|
border-radius: 4px !important;
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3) !important;
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3) !important;
|
||||||
max-width: 500px !important;
|
max-width: 500px !important;
|
||||||
min-width: 300px !important;
|
min-width: 300px !important;
|
||||||
max-height: 400px !important;
|
max-height: 400px !important;
|
||||||
white-space: normal !important;
|
|
||||||
overflow: auto !important;
|
|
||||||
background-color: var(--lineHighlight) !important;
|
background-color: var(--lineHighlight) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Main tooltip container */
|
/* Main tooltip container */
|
||||||
.autocomplete-info-tooltip {
|
.autocomplete-info-container {
|
||||||
|
padding: 12px !important;
|
||||||
border-radius: 4px !important;
|
border-radius: 4px !important;
|
||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
font-family: var(--font-family, 'SF Mono', 'Monaco', monospace);
|
font-family: var(--font-family, 'SF Mono', 'Monaco', monospace);
|
||||||
@@ -95,6 +93,16 @@
|
|||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
max-height: 400px;
|
max-height: 400px;
|
||||||
min-width: 400px;
|
min-width: 400px;
|
||||||
|
white-space: normal !important;
|
||||||
|
overflow-y: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete-info-tooltip {
|
||||||
|
overflow-y: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete-info-function-description {
|
||||||
|
white-space: pre-wrap !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.autocomplete-info-function-name {
|
.autocomplete-info-function-name {
|
||||||
|
|||||||
@@ -311,7 +311,8 @@ export function SettingsTab({ started }) {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
confirmDialog('Sure?').then((r) => {
|
confirmDialog('Sure?').then((r) => {
|
||||||
if (r) {
|
if (r) {
|
||||||
settingsMap.set(defaultSettings);
|
const { userPatterns } = settingsMap.get(); // keep current patterns
|
||||||
|
settingsMap.set({ ...defaultSettings, userPatterns });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { register, signal } from '@strudel/core';
|
||||||
|
import { clamp } from '../../../packages/superdough/util.mjs';
|
||||||
|
|
||||||
|
let sigs = [0, 0, 0, 0, 0, 0, 0, 0];
|
||||||
|
|
||||||
|
export let sig = register('sig', (pat) =>
|
||||||
|
pat
|
||||||
|
.fmap((n) => {
|
||||||
|
return signal(() => clamp(sigs[n], 0.0001, 1));
|
||||||
|
})
|
||||||
|
.outerJoin(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let socket;
|
||||||
|
export function initSocket(ip) {
|
||||||
|
// e.g. 192.168.1.12:4422
|
||||||
|
socket = socket || new WebSocket(`ws://${ip}`);
|
||||||
|
socket.addEventListener('open', () => console.log('connection opened'));
|
||||||
|
socket.addEventListener('error', () => console.log('connection error'));
|
||||||
|
socket.addEventListener('message', (event) => {
|
||||||
|
if (Math.random() < 0.01) {
|
||||||
|
console.log(event.data.toString());
|
||||||
|
}
|
||||||
|
sigs = JSON.parse(event.data.toString());
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user