Merge branch 'main' into exportSuperdoughAudioController

This commit is contained in:
Alex McLean 2025-11-28 22:32:10 +01:00
commit 6a4fd27f5b
12 changed files with 204 additions and 47 deletions

88
CHANGELOG.md Normal file
View file

@ -0,0 +1,88 @@
# changelog
NOTE: you can generate this with `node warm.js`. it might still not be perfectly sorted...
## november 2025
- 2025-11-27T22:03:53+01:00 [perf] in `noise`, let noiseMix do the disconnect when it exists by jeromew in: [#1783](https://codeberg.org/uzu/strudel/pulls/1783)
- 2025-11-27T22:01:01+01:00 Bug Fix: Retries for sounds tab by glossing in: [#1754](https://codeberg.org/uzu/strudel/pulls/1754)
- 2025-11-27T20:36:46+01:00 [hydra] return the hydra object when await initHydra(..) is called by jeromew in: [#1784](https://codeberg.org/uzu/strudel/pulls/1784)
- 2025-11-27T20:36:14+01:00 Use errorLogger for query and tonal errors by glossing in: [#1782](https://codeberg.org/uzu/strudel/pulls/1782)
- 2025-11-27T20:27:10+01:00 [perf] level 5 `connect-leak` on `vowel` by jeromew in: [#1779](https://codeberg.org/uzu/strudel/pulls/1779)
- 2025-11-26T08:23:38+01:00 feat: add prebake script import button for loading .strudel files by daslyfe in: [#1774](https://codeberg.org/uzu/strudel/pulls/1774)
- 2025-11-26T07:39:28+01:00 Fix Sampler port trampling by Dayglo in: [#1717](https://codeberg.org/uzu/strudel/pulls/1717)
- 2025-11-25T20:59:09+01:00 Feat: Hook up octave and fix example by glossing in: [#1773](https://codeberg.org/uzu/strudel/pulls/1773)
- 2025-11-25T20:48:09+01:00 [perf] fix `connect-leak` in `tremolo` param by jeromew in: [#1780](https://codeberg.org/uzu/strudel/pulls/1780)
- 2025-11-24T17:51:20+01:00 filter modulation improvements! by daslyfe in: [#1769](https://codeberg.org/uzu/strudel/pulls/1769)
- 2025-11-24T01:20:53+01:00 Add o as a synonym for orbit by daslyfe in: [#1766](https://codeberg.org/uzu/strudel/pulls/1766)
- 2025-11-23T22:23:26+01:00 [perf] fix `connect-leak` in `fm` modulation by jeromew in: [#1758](https://codeberg.org/uzu/strudel/pulls/1758)
- 2025-11-23T09:47:24+01:00 Fix interoperability issue between `all` and `await initHydra()` by jeromew in: [#1663](https://codeberg.org/uzu/strudel/pulls/1663)
- 2025-11-23T04:23:06+01:00 Bug fix: Swap l/r gains with temp variable by glossing in: [#1768](https://codeberg.org/uzu/strudel/pulls/1768)
- 2025-11-23T00:52:28+01:00 FIX: prevent LFO filter modulation from popping from negative values by daslyfe in: [#1767](https://codeberg.org/uzu/strudel/pulls/1767)
- 2025-11-21T01:56:11+01:00 Bug Fix: Use frac due to negative frequencies from FM by glossing in: [#1759](https://codeberg.org/uzu/strudel/pulls/1759)
- 2025-11-20T19:48:16+01:00 [perf] fix `connect-leak` added by #1742 when noise() is not used by jeromew in: [#1757](https://codeberg.org/uzu/strudel/pulls/1757)
- 2025-11-20T14:51:00+01:00 [perf] fix `connect-leak` in `delay` effect by jeromew in: [#1755](https://codeberg.org/uzu/strudel/pulls/1755)
- 2025-11-20T14:49:41+01:00 [perf] fix `connect leak` when .noise() is in the mix by jeromew in: [#1742](https://codeberg.org/uzu/strudel/pulls/1742)
- 2025-11-18T23:52:45+01:00 wchooseCycles has now notes in an example by scrappy_fiddler in: [#1748](https://codeberg.org/uzu/strudel/pulls/1748)
- 2025-11-17T05:31:54+01:00 Feature: Partials by glossing in: [#1659](https://codeberg.org/uzu/strudel/pulls/1659)
- 2025-11-16T22:06:14+01:00 README: update superdough documentation by TristanMlct in: [#1741](https://codeberg.org/uzu/strudel/pulls/1741)
- 2025-11-16T21:08:54+01:00 Worklet optimizations by glossing in: [#1730](https://codeberg.org/uzu/strudel/pulls/1730)
- 2025-11-15T14:59:30+01:00 [perf] disconnect Offline AudioNode connections in generateReverb by jeromew in: [#1740](https://codeberg.org/uzu/strudel/pulls/1740)
- 2025-11-15T14:57:47+01:00 [perf] disconnect `send` effect AudioNode when `room` is used by jeromew in: [#1736](https://codeberg.org/uzu/strudel/pulls/1736)
- 2025-11-12T21:22:34+01:00 [supradough] fix: unstable filter by froos in: [#1593](https://codeberg.org/uzu/strudel/pulls/1593)
- 2025-11-12T21:12:07+01:00 Fix link syntax in `project-start` by Kissaki in: [#1724](https://codeberg.org/uzu/strudel/pulls/1724)
- 2025-11-12T21:06:13+01:00 Fix web README sample code by Kissaki in: [#1725](https://codeberg.org/uzu/strudel/pulls/1725)
- 2025-11-12T20:59:39+01:00 Fix typo: 'studel' -> 'strudel' by drhayes in: [#1726](https://codeberg.org/uzu/strudel/pulls/1726)
- 2025-11-12T15:23:41+01:00 fix for node 24 support in tests - #1718 by ausav in: [#1719](https://codeberg.org/uzu/strudel/pulls/1719)
- 2025-11-11T09:00:49+01:00 soundAlias example fix by PepsiiMan in: [#1720](https://codeberg.org/uzu/strudel/pulls/1720)
- 2025-11-10T08:18:36+01:00 fix: Make Supradough package build by munshkr in: [#1711](https://codeberg.org/uzu/strudel/pulls/1711)
- 2025-11-10T08:18:00+01:00 added docs for spaces in scale names by ondras in: [#1715](https://codeberg.org/uzu/strudel/pulls/1715)
- 2025-11-10T02:44:01+01:00 Feature: LFOs for Filters by glossing in: [#1636](https://codeberg.org/uzu/strudel/pulls/1636)
- 2025-11-05T17:07:00+01:00 improvement: sync midi messages to audio context clock by daslyfe in: [#1708](https://codeberg.org/uzu/strudel/pulls/1708)
- 2025-11-04T19:36:14+01:00 Add stick button mappings to gamepad implementation and improve docs by kaosuryoko in: [#1696](https://codeberg.org/uzu/strudel/pulls/1696)
- 2025-11-04T19:29:35+01:00 Refactor sound stopping and triggering logic in SoundsTab component by IJOL in: [#1688](https://codeberg.org/uzu/strudel/pulls/1688)
- 2025-11-04T19:13:21+01:00 Add setting to toggle pattern auto-start on pattern change by moumar in: [#1690](https://codeberg.org/uzu/strudel/pulls/1690)
- 2025-11-04T18:58:50+01:00 Voicings JSDoc by sharkeys_lunchbox in: [#1686](https://codeberg.org/uzu/strudel/pulls/1686)
- 2025-11-01T07:50:40+01:00 FIX: Loading local samples uses too much memory by daslyfe in: [#1706](https://codeberg.org/uzu/strudel/pulls/1706)
## october 2025
- 2025-10-28T22:58:24+01:00 Bug Fix: Handle scale-for-notes when n also supplied by glossing in: [#1625](https://codeberg.org/uzu/strudel/pulls/1625)
- 2025-10-28T22:51:16+01:00 Repurpose vim shortcuts for usability by dtricks in: [#1624](https://codeberg.org/uzu/strudel/pulls/1624)
- 2025-10-28T22:35:21+01:00 Add support for euclidian in mondo with `bd&3:8` by TristanCacqueray in: [#1630](https://codeberg.org/uzu/strudel/pulls/1630)
- 2025-10-27T10:42:07+01:00 Use bunny cdn for all samples and json files by yaxu in: [#1701](https://codeberg.org/uzu/strudel/pulls/1701)
- 2025-10-26T22:52:54+01:00 degithub - switch some samples to bunnycdn by yaxu in: [#1697](https://codeberg.org/uzu/strudel/pulls/1697)
- 2025-10-26T17:09:22+01:00 Fix ZZFX example by moumar in: [#1685](https://codeberg.org/uzu/strudel/pulls/1685)
- 2025-10-23T15:56:04+02:00 Make osc port and host configurable. Changes dependencies. by yaxu in: [#1682](https://codeberg.org/uzu/strudel/pulls/1682)
- 2025-10-23T03:47:31+02:00 Adds back shape to superdough by glossing in: [#1672](https://codeberg.org/uzu/strudel/pulls/1672)
- 2025-10-22T22:15:19+02:00 Fix sampler.mjs githubPath by jeromew in: [#1684](https://codeberg.org/uzu/strudel/pulls/1684)
- 2025-10-22T16:20:50+02:00 Fix onPaint for widgets by yaxu in: [#1658](https://codeberg.org/uzu/strudel/pulls/1658)
- 2025-10-22T15:19:12+02:00 Fix a bug introduced by #4e17cfbdd6 by milliganf in: [#1679](https://codeberg.org/uzu/strudel/pulls/1679)
- 2025-10-18T04:29:20+02:00 Bug Fix: Wavetable: phase wrapping at 1 and detune by glossing in: [#1620](https://codeberg.org/uzu/strudel/pulls/1620)
- 2025-10-16T20:26:28+02:00 github samples: default to "samples" if repository is not specified by prezmop in: [#1644](https://codeberg.org/uzu/strudel/pulls/1644)
- 2025-10-16T17:33:39+02:00 Docs: add example of custom chained function by dariusk in: [#1642](https://codeberg.org/uzu/strudel/pulls/1642)
- 2025-10-14T12:39:48+02:00 textbox by yaxu in: [#1650](https://codeberg.org/uzu/strudel/pulls/1650)
- 2025-10-13T07:05:57+02:00 Feature: Wavetable FM by glossing in: [#1623](https://codeberg.org/uzu/strudel/pulls/1623)
- 2025-10-12T20:43:42+02:00 fix: repair REPL sample sources and URL concat (#1640) by erikfox in: [#1646](https://codeberg.org/uzu/strudel/pulls/1646)
- 2025-10-10T11:25:02+02:00 Add control-metadata by yaxu in: [#1634](https://codeberg.org/uzu/strudel/pulls/1634)
- 2025-10-07T22:59:39+02:00 Fix MQTT: change the trigger handler to match new hap by jurrchen in: [#1629](https://codeberg.org/uzu/strudel/pulls/1629)
- 2025-10-05T21:42:19+02:00 Fix case sensitivity for synonym search by vvolhejn in: [#1618](https://codeberg.org/uzu/strudel/pulls/1618)
- 2025-10-01T08:56:35+02:00 Optimize wavetable synth by glossing in: [#1613](https://codeberg.org/uzu/strudel/pulls/1613)
- 2025-10-01T02:02:33+02:00 fix: pattern switching by daslyfe in: [#1616](https://codeberg.org/uzu/strudel/pulls/1616)
## September 2025
- 2025-09-29T02:10:54+02:00 fix wavetable JSON by daslyfe in: [#1611](https://codeberg.org/uzu/strudel/pulls/1611)
- 2025-09-28T21:28:16+02:00 rename wavetable controls to match existing naming conventions by daslyfe in: [#1610](https://codeberg.org/uzu/strudel/pulls/1610)
- 2025-09-27T23:28:13+02:00 Feature: Wavetable synth improvements by glossing in: [#1607](https://codeberg.org/uzu/strudel/pulls/1607)
- 2025-09-27T21:43:10+02:00 wavetable improvements by daslyfe in: [#1608](https://codeberg.org/uzu/strudel/pulls/1608)
- 2025-09-26T08:48:46+02:00 create orbit based DJ filter by daslyfe in: [#1603](https://codeberg.org/uzu/strudel/pulls/1603)
- 2025-09-23T13:28:20+02:00 fix: time signal by daslyfe in: [#1583](https://codeberg.org/uzu/strudel/pulls/1583)
- 2025-09-21T16:16:06+02:00 mondo: fix all + setcpm + setcps by froos in: [#1600](https://codeberg.org/uzu/strudel/pulls/1600)
- 2025-09-19T21:59:43+02:00 fix: osc error message by froos in: [#1597](https://codeberg.org/uzu/strudel/pulls/1597)
- 2025-09-18T21:09:56+02:00 osc: --debug flag to see incoming messages by froos in: [#1595](https://codeberg.org/uzu/strudel/pulls/1595)
- 2025-09-17T14:12:10+02:00 simplify osc usage by froos in: [#1588](https://codeberg.org/uzu/strudel/pulls/1588)
- 2025-09-16T16:59:58+02:00 dev: logger errors to console in dev mode by daslyfe in: [#1585](https://codeberg.org/uzu/strudel/pulls/1585)
- 2025-09-15T22:52:14+02:00 add tic80 font by froos in: [#1579](https://codeberg.org/uzu/strudel/pulls/1579)
- 2025-09-15T00:00:19+02:00 supradough: fix delay + add some jsdoc types by froos in: [#1578](https://codeberg.org/uzu/strudel/pulls/1578)

View file

@ -25,7 +25,7 @@ import {
stringifyValues, stringifyValues,
} from './util.mjs'; } from './util.mjs';
import drawLine from './drawLine.mjs'; import drawLine from './drawLine.mjs';
import { logger } from './logger.mjs'; import { errorLogger, logger } from './logger.mjs';
let stringParser; let stringParser;
@ -414,7 +414,7 @@ export class Pattern {
try { try {
return this.query(new State(new TimeSpan(begin, end), controls)); return this.query(new State(new TimeSpan(begin, end), controls));
} catch (err) { } catch (err) {
logger(`[query]: ${err.message}`, 'error'); errorLogger(err, 'query');
return []; return [];
} }
} }

View file

@ -244,8 +244,7 @@ export function repl({
} }
if (!isPattern(pattern)) { if (!isPattern(pattern)) {
const message = `got "${typeof evaluated}" instead of pattern`; pattern = silence;
throw new Error(message + (typeof evaluated === 'function' ? ', did you forget to call a function?' : '.'));
} }
logger(`[eval] code updated`); logger(`[eval] code updated`);
pattern = await setPattern(pattern, autostart); pattern = await setPattern(pattern, autostart);

View file

@ -35,6 +35,7 @@ export async function initHydra(options = {}) {
hydra.synth.s0.init({ src: canvas }); hydra.synth.s0.init({ src: canvas });
} }
} }
return hydra;
} }
export function clearHydra() { export function clearHydra() {

View file

@ -240,6 +240,7 @@ export function createFilter(context, start, end, params, cps, cycle) {
rate = cps * sync; rate = cps * sync;
} }
const hasLFO = [depth, depthfrequency, skew, shape, rate].some((v) => v !== undefined); const hasLFO = [depth, depthfrequency, skew, shape, rate].some((v) => v !== undefined);
let lfo;
if (hasLFO) { if (hasLFO) {
depth = depth ?? 1; depth = depth ?? 1;
const time = cycle / cps; const time = cycle / cps;
@ -255,10 +256,10 @@ export function createFilter(context, start, end, params, cps, cycle) {
time, time,
curve: 1, curve: 1,
}; };
getParamLfo(context, frequencyParam, start, end, lfoValues); lfo = getParamLfo(context, frequencyParam, start, end, lfoValues);
} }
return filter; return { filter, lfo };
} }
// stays 1 until .5, then fades out // stays 1 until .5, then fades out

View file

@ -263,8 +263,8 @@ let audioReady;
export async function initAudioOnFirstClick(options) { export async function initAudioOnFirstClick(options) {
if (!audioReady) { if (!audioReady) {
audioReady = new Promise((resolve) => { audioReady = new Promise((resolve) => {
document.addEventListener('click', async function listener() { document.addEventListener('mousedown', async function listener() {
document.removeEventListener('click', listener); document.removeEventListener('mousedown', listener);
await initAudio(options); await initAudio(options);
resolve(); resolve();
}); });
@ -287,7 +287,7 @@ export function connectToDestination(input, channels) {
function getPhaser(time, end, frequency = 1, depth = 0.5, centerFrequency = 1000, sweep = 2000) { function getPhaser(time, end, frequency = 1, depth = 0.5, centerFrequency = 1000, sweep = 2000) {
const ac = getAudioContext(); const ac = getAudioContext();
const lfoGain = getLfo(ac, time, end, { frequency, depth: sweep * 2 }); const lfo = getLfo(ac, time, end, { frequency, depth: sweep * 2 });
//filters //filters
const numStages = 2; //num of filters in series const numStages = 2; //num of filters in series
@ -300,14 +300,14 @@ function getPhaser(time, end, frequency = 1, depth = 0.5, centerFrequency = 1000
filter.frequency.value = centerFrequency + fOffset; filter.frequency.value = centerFrequency + fOffset;
filter.Q.value = 2 - Math.min(Math.max(depth * 2, 0), 1.9); filter.Q.value = 2 - Math.min(Math.max(depth * 2, 0), 1.9);
lfoGain.connect(filter.detune); lfo.connect(filter.detune);
fOffset += 282; fOffset += 282;
if (i > 0) { if (i > 0) {
filterChain[i - 1].connect(filter); filterChain[i - 1].connect(filter);
} }
filterChain.push(filter); filterChain.push(filter);
} }
return filterChain[filterChain.length - 1]; return { phaser: filterChain[filterChain.length - 1], lfo };
} }
function getFilterType(ftype) { function getFilterType(ftype) {
@ -413,7 +413,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
release = 0, release = 0,
//phaser //phaser
phaserrate: phaser, phaserrate,
phaserdepth = getDefaultValue('phaserdepth'), phaserdepth = getDefaultValue('phaserdepth'),
phasersweep, phasersweep,
phasercenter, phasercenter,
@ -560,10 +560,14 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
}; };
const lpParams = pickAndRename(value, lpMap); const lpParams = pickAndRename(value, lpMap);
lpParams.type = 'lowpass'; lpParams.type = 'lowpass';
let lp = () => createFilter(ac, t, end, lpParams, cps, cycle); const lp = () => createFilter(ac, t, end, lpParams, cps, cycle);
chain.push(lp()); const { filter: lpf1, lfo: lfo1 } = lp();
chain.push(lpf1);
lfo1 && audioNodes.push(lfo1);
if (ftype === '24db') { if (ftype === '24db') {
chain.push(lp()); const { filter: lpf2, lfo: lfo2 } = lp();
chain.push(lpf2);
lfo2 && audioNodes.push(lfo2);
} }
} }
@ -589,10 +593,14 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
}; };
const hpParams = pickAndRename(value, hpMap); const hpParams = pickAndRename(value, hpMap);
hpParams.type = 'highpass'; hpParams.type = 'highpass';
let hp = () => createFilter(ac, t, end, hpParams, cps, cycle); const hp = () => createFilter(ac, t, end, hpParams, cps, cycle);
chain.push(hp()); const { filter: hpf1, lfo: lfo1 } = hp();
chain.push(hpf1);
lfo1 && audioNodes.push(lfo1);
if (ftype === '24db') { if (ftype === '24db') {
chain.push(hp()); const { filter: hpf2, lfo: lfo2 } = hp();
chain.push(hpf2);
lfo2 && audioNodes.push(lfo2);
} }
} }
@ -618,10 +626,14 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
}; };
const bpParams = pickAndRename(value, bpMap); const bpParams = pickAndRename(value, bpMap);
bpParams.type = 'bandpass'; bpParams.type = 'bandpass';
let bp = () => createFilter(ac, t, end, bpParams, cps, cycle); const bp = () => createFilter(ac, t, end, bpParams, cps, cycle);
chain.push(bp()); const { filter: bpf1, lfo: lfo1 } = bp();
chain.push(bpf1);
lfo1 && audioNodes.push(lfo1);
if (ftype === '24db') { if (ftype === '24db') {
chain.push(bp()); const { filter: bpf2, lfo: lfo2 } = bp();
chain.push(bpf2);
lfo2 && audioNodes.push(lfo2);
} }
} }
@ -684,9 +696,10 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
chain.push(panner); chain.push(panner);
} }
// phaser // phaser
if (phaser !== undefined && phaserdepth > 0) { if (phaserrate !== undefined && phaserdepth > 0) {
const phaserFX = getPhaser(t, endWithRelease, phaser, phaserdepth, phasercenter, phasersweep); const { phaser, lfo } = getPhaser(t, endWithRelease, phaserrate, phaserdepth, phasercenter, phasersweep);
chain.push(phaserFX); audioNodes.push(lfo);
chain.push(phaser);
} }
// last gain // last gain

View file

@ -493,7 +493,7 @@ export function getOscillator(s, t, value, onended) {
} }
o.onended = () => { o.onended = () => {
o.disconnect(); noiseMix || o.disconnect();
noiseMix?.node.disconnect(); noiseMix?.node.disconnect();
onended(); onended();
}; };

View file

@ -45,7 +45,8 @@ if (typeof GainNode !== 'undefined') {
throw new Error('vowel: unknown vowel ' + letter); throw new Error('vowel: unknown vowel ' + letter);
} }
const { gains, qs, freqs } = vowelFormant[letter]; const { gains, qs, freqs } = vowelFormant[letter];
const makeupGain = ac.createGain(); this.makeupGain = ac.createGain();
this.audioNodes = [];
for (let i = 0; i < 5; i++) { for (let i = 0; i < 5; i++) {
const gain = ac.createGain(); const gain = ac.createGain();
gain.gain.value = gains[i]; gain.gain.value = gains[i];
@ -53,14 +54,25 @@ if (typeof GainNode !== 'undefined') {
filter.type = 'bandpass'; filter.type = 'bandpass';
filter.Q.value = qs[i]; filter.Q.value = qs[i];
filter.frequency.value = freqs[i]; filter.frequency.value = freqs[i];
this.connect(filter); super.connect(filter);
filter.connect(gain); filter.connect(gain);
gain.connect(makeupGain); this.audioNodes.push(filter);
gain.connect(this.makeupGain);
this.audioNodes.push(gain);
} }
makeupGain.gain.value = 8; // how much makeup gain to add? this.makeupGain.gain.value = 8; // how much makeup gain to add?
this.connect = (target) => makeupGain.connect(target);
return this; return this;
} }
connect(target) {
this.makeupGain.connect(target);
}
disconnect() {
this.makeupGain.disconnect();
this.audioNodes.forEach((n) => n.disconnect());
super.disconnect();
this.makeupGain = null;
this.audioNodes = null;
}
} }
AudioContext.prototype.createVowelFilter = function (letter) { AudioContext.prototype.createVowelFilter = function (letter) {

View file

@ -5,7 +5,16 @@ This program is free software: you can redistribute it and/or modify it under th
*/ */
import { Note, Interval, Scale } from '@tonaljs/tonal'; import { Note, Interval, Scale } from '@tonaljs/tonal';
import { register, _mod, logger, isNote, noteToMidi, removeUndefineds, getAccidentalsOffset } from '@strudel/core'; import {
_mod,
errorLogger,
getAccidentalsOffset,
isNote,
logger,
noteToMidi,
register,
removeUndefineds,
} from '@strudel/core';
import { stepInNamedScale, nearestNumberIndex } from './tonleiter.mjs'; import { stepInNamedScale, nearestNumberIndex } from './tonleiter.mjs';
const octavesInterval = (octaves) => (octaves <= 0 ? -1 : 1) + octaves * 7 + 'P'; const octavesInterval = (octaves) => (octaves <= 0 ? -1 : 1) + octaves * 7 + 'P';
@ -294,7 +303,7 @@ export const scale = register(
} }
if (offset != 0) scaleNote = Note.transpose(scaleNote, Interval.fromSemitones(offset)); if (offset != 0) scaleNote = Note.transpose(scaleNote, Interval.fromSemitones(offset));
} catch (err) { } catch (err) {
logger(`[tonal] ${err.message}`, 'error'); errorLogger(err, 'tonal');
return; // will be removed return; // will be removed
} }
} }

View file

@ -1,4 +1,5 @@
{ {
"name": "dbpatch",
"dependencies": { "dependencies": {
"csv": "^6.3.11" "csv": "^6.3.11"
}, },

14
warm.js Normal file
View file

@ -0,0 +1,14 @@
fetch('https://codeberg.org/api/v1/repos/uzu/strudel/pulls?state=closed&page=1')
.then((res) => res.json())
.then((pulls) => {
const r = pulls
.filter((pull) => pull.merged)
.sort((a, b) => new Date(b.closed_at) - new Date(a.closed_at))
.map((pull) => `${pull.closed_at} ${pull.title} by ${pull.user.login || '?'} in: [#${pull.number}](${pull.url}) `)
.join('\n');
console.log(r);
});
/*
*/

View file

@ -14,6 +14,10 @@ import { prebake } from '@src/repl/prebake.mjs';
const getSamples = (samples) => const getSamples = (samples) =>
Array.isArray(samples) ? samples.length : typeof samples === 'object' ? Object.values(samples).length : 1; Array.isArray(samples) ? samples.length : typeof samples === 'object' ? Object.values(samples).length : 1;
function wait(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export function SoundsTab() { export function SoundsTab() {
const sounds = useStore(soundMap); const sounds = useStore(soundMap);
@ -56,9 +60,7 @@ export function SoundsTab() {
// holds mutable ref to current triggered sound // holds mutable ref to current triggered sound
const trigRef = useRef(); const trigRef = useRef();
const numRef = useRef(0);
// Used to cycle through sound previews on banks with multiple sounds
let soundPreviewIdx = 0;
// stop current sound on mouseup // stop current sound on mouseup
useEvent('mouseup', () => { useEvent('mouseup', () => {
@ -66,6 +68,14 @@ export function SoundsTab() {
trigRef.current = undefined; trigRef.current = undefined;
ref?.stop?.(getAudioContext().currentTime + 0.01); ref?.stop?.(getAudioContext().currentTime + 0.01);
}); });
useEvent('keydown', (e) => {
if (!isNaN(Number(e.key))) {
numRef.current = Number(e.key);
}
});
useEvent('keyup', (e) => {
numRef.current = 0;
});
return ( return (
<div id="sounds-tab" className="px-4 flex gap-2 flex-col w-full h-full text-foreground"> <div id="sounds-tab" className="px-4 flex gap-2 flex-col w-full h-full text-foreground">
<Textbox placeholder="Search" value={search} onChange={(v) => setSearch(v)} /> <Textbox placeholder="Search" value={search} onChange={(v) => setSearch(v)} />
@ -115,25 +125,34 @@ export function SoundsTab() {
const params = { const params = {
note: ['synth', 'soundfont'].includes(data.type) ? 'a3' : undefined, note: ['synth', 'soundfont'].includes(data.type) ? 'a3' : undefined,
s: name, s: name,
n: soundPreviewIdx, n: numRef.current,
clip: 1, clip: 1,
release: 0.5, release: 0.5,
sustain: 1, sustain: 1,
duration: 0.5, duration: 0.5,
}; };
soundPreviewIdx++;
const onended = () => trigRef.current?.node?.disconnect(); const onended = () => trigRef.current?.node?.disconnect();
try { // Attempt to play the sample and retry every 200ms until 10 attempts have been reached
// Pre-load the sample by calling onTrigger with a future time let errMsg;
// This triggers the loading but schedules playback for later for (let attempt = 0; attempt < 10; attempt++) {
const time = ctx.currentTime + 0.05; try {
const ref = await onTrigger(time, params, onended); // Pre-load the sample by calling onTrigger with a future time
trigRef.current = ref; // This triggers the loading but schedules playback for later
if (ref?.node) { const time = ctx.currentTime + 0.05; // Give 50ms for loading
connectToDestination(ref.node); const ref = await onTrigger(time, params, onended);
trigRef.current = ref;
if (ref?.node) {
connectToDestination(ref.node);
break;
}
} catch (err) {
errMsg = err;
}
if (attempt == 9) {
console.warn('Failed to trigger sound after 10 attempts' + (errMsg ? `: ${errMsg}` : ''));
} else {
await wait(200);
} }
} catch (err) {
console.warn('Failed to trigger sound:', err);
} }
}} }}
> >