mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-24 22:55:38 -04:00
Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ec26392509 | |||
| 30cd486f37 | |||
| cde0520ed9 | |||
| 405338e4f0 | |||
| afbcaa54d0 | |||
| 6a1c6081e0 | |||
| 3c4ba667c8 | |||
| 7fcb5262b8 | |||
| b824c45ba4 | |||
| e47e6c87fc | |||
| 69b5ba89cb | |||
| 33a51761ce | |||
| d217119391 | |||
| d5d6284a90 | |||
| fb790de26c | |||
| ec6f6a1f1c | |||
| a126bc9b86 | |||
| a5c7fcc136 | |||
| d70b758065 | |||
| 9fdba6374c | |||
| af90baea33 | |||
| 743e239e52 | |||
| f44a96a5ce | |||
| b94a686394 | |||
| 728552b669 | |||
| 8b427bc1dd | |||
| ae47d767ce | |||
| ab64451d65 | |||
| 89da9bbdb0 | |||
| 2bc208d8f5 | |||
| cfeff26d42 | |||
| e60ecba393 | |||
| f0d49e0e22 | |||
| 7ee6b933cc | |||
| a3947d1ccf | |||
| e86850b6d8 | |||
| 89a6efb69e | |||
| 31c694b596 | |||
| 7a4babf6e1 | |||
| d5e5ab83ac | |||
| 20016fd9aa | |||
| 132a124644 | |||
| 4caacb0de1 | |||
| 8325b4cfc4 | |||
| 3b2df0b725 | |||
| f80fdbc38a | |||
| 3a7b237765 | |||
| f5af55b63b | |||
| d5dfbd7aa4 | |||
| 6221099af8 | |||
| 22f1512697 | |||
| 6c0d550b6b | |||
| cf12e3e1d8 | |||
| 9a05a679aa | |||
| 321e76166a | |||
| bde17594ce | |||
| 7bbb653963 | |||
| 30e672a027 | |||
| f7f3aa7668 | |||
| adc3a9ac6c | |||
| 2efd56e331 | |||
| d453c861d3 | |||
| f637b8b0dd | |||
| 907f03f3bf | |||
| 74488f8480 | |||
| 8144ec46bd | |||
| 47c85f8540 |
@@ -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)
|
||||||
@@ -162,7 +162,6 @@ export class StrudelMirror {
|
|||||||
this.onDraw = onDraw || this.draw;
|
this.onDraw = onDraw || this.draw;
|
||||||
this.id = id || s4();
|
this.id = id || s4();
|
||||||
this.solo = solo;
|
this.solo = solo;
|
||||||
this.hasPainters = false;
|
|
||||||
|
|
||||||
this.drawer = new Drawer((haps, time, _, painters) => {
|
this.drawer = new Drawer((haps, time, _, painters) => {
|
||||||
const currentFrame = haps.filter((hap) => hap.isActive(time));
|
const currentFrame = haps.filter((hap) => hap.isActive(time));
|
||||||
@@ -178,7 +177,7 @@ export class StrudelMirror {
|
|||||||
onToggle: (started) => {
|
onToggle: (started) => {
|
||||||
replOptions?.onToggle?.(started);
|
replOptions?.onToggle?.(started);
|
||||||
if (started) {
|
if (started) {
|
||||||
this.shouldAnimate() && this.drawer.start(this.repl.scheduler);
|
this.drawer.start(this.repl.scheduler);
|
||||||
if (this.solo) {
|
if (this.solo) {
|
||||||
// stop other repls when this one is started
|
// stop other repls when this one is started
|
||||||
document.dispatchEvent(
|
document.dispatchEvent(
|
||||||
@@ -208,10 +207,9 @@ export class StrudelMirror {
|
|||||||
updateWidgets(this.editor, widgets);
|
updateWidgets(this.editor, widgets);
|
||||||
updateMiniLocations(this.editor, this.miniLocations);
|
updateMiniLocations(this.editor, this.miniLocations);
|
||||||
replOptions?.afterEval?.(options);
|
replOptions?.afterEval?.(options);
|
||||||
this.hasPainters = options.pattern.getPainters().length > 0;
|
|
||||||
// if no painters are set (.onPaint was not called), then we only need
|
// if no painters are set (.onPaint was not called), then we only need
|
||||||
// the present moment (for highlighting)
|
// the present moment (for highlighting)
|
||||||
const drawTime = this.hasPainters ? this.drawTime : [0, 0];
|
const drawTime = options.pattern.getPainters().length ? this.drawTime : [0, 0];
|
||||||
this.drawer.setDrawTime(drawTime);
|
this.drawer.setDrawTime(drawTime);
|
||||||
// invalidate drawer after we've set the appropriate drawTime
|
// invalidate drawer after we've set the appropriate drawTime
|
||||||
this.drawer.invalidate(this.repl.scheduler);
|
this.drawer.invalidate(this.repl.scheduler);
|
||||||
@@ -239,7 +237,6 @@ export class StrudelMirror {
|
|||||||
cmEditor.style.backgroundColor = 'transparent';
|
cmEditor.style.backgroundColor = 'transparent';
|
||||||
}
|
}
|
||||||
const settings = codemirrorSettings.get();
|
const settings = codemirrorSettings.get();
|
||||||
this.isPatternHighlightingEnabled = parseBooleans(settings.isPatternHighlightingEnabled);
|
|
||||||
this.setFontSize(settings.fontSize);
|
this.setFontSize(settings.fontSize);
|
||||||
this.setFontFamily(settings.fontFamily);
|
this.setFontFamily(settings.fontFamily);
|
||||||
|
|
||||||
@@ -324,17 +321,8 @@ export class StrudelMirror {
|
|||||||
flash(this.editor, ms);
|
flash(this.editor, ms);
|
||||||
}
|
}
|
||||||
highlight(haps, time) {
|
highlight(haps, time) {
|
||||||
if (!this.isPatternHighlightingEnabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
highlightMiniLocations(this.editor, time, haps);
|
highlightMiniLocations(this.editor, time, haps);
|
||||||
}
|
}
|
||||||
shouldAnimate() {
|
|
||||||
return (
|
|
||||||
this.repl.scheduler.started &&
|
|
||||||
(this.isPatternHighlightingEnabled || this.hasPainters || this.onDraw !== this.draw)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
setFontSize(size) {
|
setFontSize(size) {
|
||||||
this.root.style.fontSize = size + 'px';
|
this.root.style.fontSize = size + 'px';
|
||||||
}
|
}
|
||||||
@@ -355,11 +343,6 @@ export class StrudelMirror {
|
|||||||
this.editor.dispatch({
|
this.editor.dispatch({
|
||||||
effects: compartments[key].reconfigure(newValue),
|
effects: compartments[key].reconfigure(newValue),
|
||||||
});
|
});
|
||||||
if (key === 'isPatternHighlightingEnabled') {
|
|
||||||
this.isPatternHighlightingEnabled = value;
|
|
||||||
this.drawer.stop();
|
|
||||||
this.shouldAnimate() && this.drawer.start(this.repl.scheduler);
|
|
||||||
}
|
|
||||||
if (key === 'theme') {
|
if (key === 'theme') {
|
||||||
activateTheme(value);
|
activateTheme(value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1294,6 +1294,8 @@ export const { fanchor } = registerControl('fanchor');
|
|||||||
*
|
*
|
||||||
* @name lprate
|
* @name lprate
|
||||||
* @param {number | Pattern} rate rate in hertz
|
* @param {number | Pattern} rate rate in hertz
|
||||||
|
* @example
|
||||||
|
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).lprate("<4 8 2 1>")
|
||||||
*/
|
*/
|
||||||
export const { lprate } = registerControl('lprate');
|
export const { lprate } = registerControl('lprate');
|
||||||
|
|
||||||
@@ -1302,6 +1304,8 @@ export const { lprate } = registerControl('lprate');
|
|||||||
*
|
*
|
||||||
* @name lpsync
|
* @name lpsync
|
||||||
* @param {number | Pattern} rate rate in cycles
|
* @param {number | Pattern} rate rate in cycles
|
||||||
|
* @example
|
||||||
|
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).lpsync("<4 8 2 1>")
|
||||||
*/
|
*/
|
||||||
export const { lpsync } = registerControl('lpsync');
|
export const { lpsync } = registerControl('lpsync');
|
||||||
|
|
||||||
@@ -1310,8 +1314,23 @@ export const { lpsync } = registerControl('lpsync');
|
|||||||
*
|
*
|
||||||
* @name lpdepth
|
* @name lpdepth
|
||||||
* @param {number | Pattern} depth depth of modulation
|
* @param {number | Pattern} depth depth of modulation
|
||||||
|
* @example
|
||||||
|
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).lpdepth("<1 .5 1.8 0>")
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const { lpdepth } = registerControl('lpdepth');
|
export const { lpdepth } = registerControl('lpdepth');
|
||||||
|
/**
|
||||||
|
* Depth of the LFO for the lowpass filter, in HZ
|
||||||
|
*
|
||||||
|
* @name lpdepthfrequency
|
||||||
|
* @synonyms
|
||||||
|
* lpdethfreq
|
||||||
|
* @param {number | Pattern} depth depth of modulation
|
||||||
|
* @example
|
||||||
|
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).lpdepthfrequency("<200 500 100 0>")
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const { lpdepthfrequency } = registerControl('lpdepthfrequency', 'lpdepthfreq');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shape of the LFO for the lowpass filter
|
* Shape of the LFO for the lowpass filter
|
||||||
@@ -1361,6 +1380,19 @@ export const { bpsync } = registerControl('bpsync');
|
|||||||
*/
|
*/
|
||||||
export const { bpdepth } = registerControl('bpdepth');
|
export const { bpdepth } = registerControl('bpdepth');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Depth of the LFO for the bandpass filter, in HZ
|
||||||
|
*
|
||||||
|
* @name bpdepthfrequency
|
||||||
|
* @synonyms
|
||||||
|
* bpdethfreq
|
||||||
|
* @param {number | Pattern} depth depth of modulation
|
||||||
|
* @example
|
||||||
|
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).bpdepthfrequency("<200 500 100 0>")
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const { bpdepthfrequency } = registerControl('bpdepthfrequency', 'bpdepthfreq');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shape of the LFO for the bandpass filter
|
* Shape of the LFO for the bandpass filter
|
||||||
*
|
*
|
||||||
@@ -1407,7 +1439,20 @@ export const { hpsync } = registerControl('hpsync');
|
|||||||
* @name hpdepth
|
* @name hpdepth
|
||||||
* @param {number | Pattern} depth depth of modulation
|
* @param {number | Pattern} depth depth of modulation
|
||||||
*/
|
*/
|
||||||
export const { hpdepth } = registerControl('hpdepth');
|
export const { hpdepth, hpdepthfreq } = registerControl('hpdepth');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Depth of the LFO for the hipass filter, in hz
|
||||||
|
*
|
||||||
|
* @name hpdepthfrequency
|
||||||
|
* @synonyms
|
||||||
|
* hpdethfreq
|
||||||
|
* @param {number | Pattern} depth depth of modulation
|
||||||
|
* @example
|
||||||
|
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).hpdepthfrequency("<200 500 100 0>")
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const { hpdepthfrequency } = registerControl('hpdepthfrequency', 'hpdepthfreq');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shape of the LFO for the highpass filter
|
* Shape of the LFO for the highpass filter
|
||||||
@@ -1820,12 +1865,12 @@ export const { nudge } = registerControl('nudge');
|
|||||||
* Sets the default octave of a synth.
|
* Sets the default octave of a synth.
|
||||||
*
|
*
|
||||||
* @name octave
|
* @name octave
|
||||||
|
* @synonyms oct
|
||||||
* @param {number | Pattern} octave octave number
|
* @param {number | Pattern} octave octave number
|
||||||
* @example
|
* @example
|
||||||
* n("0,4,7").s('supersquare').octave("<3 4 5 6>").osc()
|
* n("0,4,7").scale("F:minor").s('supersaw').octave("<0 1 2 3>")
|
||||||
* @superDirtOnly
|
|
||||||
*/
|
*/
|
||||||
export const { octave } = registerControl('octave');
|
export const { octave, oct } = registerControl('octave', 'oct');
|
||||||
|
|
||||||
// ['ophatdecay'],
|
// ['ophatdecay'],
|
||||||
// TODO: example
|
// TODO: example
|
||||||
@@ -1833,6 +1878,7 @@ export const { octave } = registerControl('octave');
|
|||||||
* An `orbit` is a global parameter context for patterns. Patterns with the same orbit will share the same global effects.
|
* An `orbit` is a global parameter context for patterns. Patterns with the same orbit will share the same global effects.
|
||||||
*
|
*
|
||||||
* @name orbit
|
* @name orbit
|
||||||
|
* @synonyms o
|
||||||
* @param {number | Pattern} number
|
* @param {number | Pattern} number
|
||||||
* @example
|
* @example
|
||||||
* stack(
|
* stack(
|
||||||
@@ -1840,7 +1886,7 @@ export const { octave } = registerControl('octave');
|
|||||||
* s("~ sd ~ sd").delay(.5).delaytime(.125).orbit(2)
|
* s("~ sd ~ sd").delay(.5).delaytime(.125).orbit(2)
|
||||||
* )
|
* )
|
||||||
*/
|
*/
|
||||||
export const { orbit } = registerControl('orbit');
|
export const { orbit } = registerControl('orbit', 'o');
|
||||||
// TODO: what is this? not found in tidal doc Answer: gain is limited to maximum of 2. This allows you to go over that
|
// TODO: what is this? not found in tidal doc Answer: gain is limited to maximum of 2. This allows you to go over that
|
||||||
export const { overgain } = registerControl('overgain');
|
export const { overgain } = registerControl('overgain');
|
||||||
// TODO: what is this? not found in tidal doc. Similar to above, but limited to 1
|
// TODO: what is this? not found in tidal doc. Similar to above, but limited to 1
|
||||||
|
|||||||
@@ -91,9 +91,6 @@ Fraction.prototype.or = function (other) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const fraction = (n) => {
|
const fraction = (n) => {
|
||||||
if (n instanceof Fraction) {
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
if (typeof n === 'number') {
|
if (typeof n === 'number') {
|
||||||
/*
|
/*
|
||||||
https://github.com/infusion/Fraction.js/#doubles
|
https://github.com/infusion/Fraction.js/#doubles
|
||||||
|
|||||||
@@ -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 [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -238,14 +238,13 @@ export function repl({
|
|||||||
pattern = eachTransform(pattern);
|
pattern = eachTransform(pattern);
|
||||||
}
|
}
|
||||||
if (allTransforms.length) {
|
if (allTransforms.length) {
|
||||||
for (let i in allTransforms) {
|
for (const transform of allTransforms) {
|
||||||
pattern = allTransforms[i](pattern);
|
pattern = transform(pattern);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
|||||||
@@ -253,12 +253,7 @@ export const pairs = function (xs) {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Optimized clamp (~1.7x faster than Math.max/min approach)
|
export const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
|
||||||
export const clamp = (num, min, max) => {
|
|
||||||
if (num < min) return min;
|
|
||||||
if (num > max) return max;
|
|
||||||
return num;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* solmization, not used yet */
|
/* solmization, not used yet */
|
||||||
const solfeggio = ['Do', 'Reb', 'Re', 'Mib', 'Mi', 'Fa', 'Solb', 'Sol', 'Lab', 'La', 'Sib', 'Si']; /*solffegio notes*/
|
const solfeggio = ['Do', 'Reb', 'Re', 'Mib', 'Mi', 'Fa', 'Solb', 'Sol', 'Lab', 'La', 'Sib', 'Si']; /*solffegio notes*/
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import readline from 'readline';
|
|||||||
import os from 'os';
|
import os from 'os';
|
||||||
|
|
||||||
const LOG = !!process.env.LOG || false;
|
const LOG = !!process.env.LOG || false;
|
||||||
|
const PORT = process.env.PORT || 5432;
|
||||||
const VALID_AUDIO_EXTENSIONS = ['wav', 'mp3', 'ogg'];
|
const VALID_AUDIO_EXTENSIONS = ['wav', 'mp3', 'ogg'];
|
||||||
|
|
||||||
const isAudioFile = (f) => {
|
const isAudioFile = (f) => {
|
||||||
@@ -54,7 +55,6 @@ async function getBanks(directory, flat = false) {
|
|||||||
banks[bank].push(subDir);
|
banks[bank].push(subDir);
|
||||||
return subDir;
|
return subDir;
|
||||||
});
|
});
|
||||||
banks._base = `http://localhost:5432`;
|
|
||||||
return { banks, files };
|
return { banks, files };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,8 +134,6 @@ const server = http.createServer(async (req, res) => {
|
|||||||
readStream.pipe(res);
|
readStream.pipe(res);
|
||||||
});
|
});
|
||||||
|
|
||||||
// eslint-disable-next-line
|
|
||||||
const PORT = process.env.PORT || 5432;
|
|
||||||
const IP_ADDRESS = '0.0.0.0';
|
const IP_ADDRESS = '0.0.0.0';
|
||||||
let IP;
|
let IP;
|
||||||
const networkInterfaces = os.networkInterfaces();
|
const networkInterfaces = os.networkInterfaces();
|
||||||
|
|||||||
@@ -36,27 +36,6 @@ export function getWorklet(ac, processor, params, config) {
|
|||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pool of inactive (weakly referenced) voices to be reused if possible
|
|
||||||
const voicePools = {};
|
|
||||||
|
|
||||||
// Cycles through the pool attempting to find an inactive voice that hasn't been GC'd
|
|
||||||
export const claimVoice = (key) => {
|
|
||||||
voicePools[key] ??= [];
|
|
||||||
const pool = voicePools[key];
|
|
||||||
let node;
|
|
||||||
while (pool.length && !node) {
|
|
||||||
const ref = pool.pop();
|
|
||||||
node = ref?.deref?.();
|
|
||||||
}
|
|
||||||
return node;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Releases a voice from use and adds to the inactive pool
|
|
||||||
export const releaseVoice = (key, node) => {
|
|
||||||
voicePools[key] ??= [];
|
|
||||||
voicePools[key].push(new WeakRef(node));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getParamADSR = (
|
export const getParamADSR = (
|
||||||
param,
|
param,
|
||||||
attack,
|
attack,
|
||||||
@@ -212,8 +191,7 @@ export function applyParameterModulators(audioContext, param, start, end, envelo
|
|||||||
const lfo = getParamLfo(audioContext, param, start, end, lfoValues);
|
const lfo = getParamLfo(audioContext, param, start, end, lfoValues);
|
||||||
return { lfo, disconnect: () => lfo?.disconnect() };
|
return { lfo, disconnect: () => lfo?.disconnect() };
|
||||||
}
|
}
|
||||||
|
export function createFilter(context, start, end, params, cps, cycle) {
|
||||||
export function createFilter(context, start, end, params, cps) {
|
|
||||||
let {
|
let {
|
||||||
frequency,
|
frequency,
|
||||||
anchor,
|
anchor,
|
||||||
@@ -223,12 +201,14 @@ export function createFilter(context, start, end, params, cps) {
|
|||||||
q = 1,
|
q = 1,
|
||||||
drive = 0.69,
|
drive = 0.69,
|
||||||
depth,
|
depth,
|
||||||
|
depthfrequency,
|
||||||
dcoffset = -0.5,
|
dcoffset = -0.5,
|
||||||
skew,
|
skew,
|
||||||
shape,
|
shape,
|
||||||
rate,
|
rate,
|
||||||
sync,
|
sync,
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
let frequencyParam, filter;
|
let frequencyParam, filter;
|
||||||
if (model === 'ladder') {
|
if (model === 'ladder') {
|
||||||
filter = getWorklet(context, 'ladder-processor', { frequency, q, drive });
|
filter = getWorklet(context, 'ladder-processor', { frequency, q, drive });
|
||||||
@@ -259,8 +239,25 @@ export function createFilter(context, start, end, params, cps) {
|
|||||||
if (sync != null) {
|
if (sync != null) {
|
||||||
rate = cps * sync;
|
rate = cps * sync;
|
||||||
}
|
}
|
||||||
const lfoValues = { depth, dcoffset, skew, shape, frequency: rate, min: 10, max: 20000 };
|
const hasLFO = [depth, depthfrequency, skew, shape, rate].some((v) => v !== undefined);
|
||||||
|
if (hasLFO) {
|
||||||
|
depth = depth ?? 1;
|
||||||
|
const time = cycle / cps;
|
||||||
|
const modDepth = depthfrequency ?? (depth ?? 1) * frequency;
|
||||||
|
const lfoValues = {
|
||||||
|
depth: modDepth,
|
||||||
|
dcoffset,
|
||||||
|
skew,
|
||||||
|
shape,
|
||||||
|
frequency: rate ?? cps,
|
||||||
|
min: -frequency + 30,
|
||||||
|
max: 20000 - frequency,
|
||||||
|
time,
|
||||||
|
curve: 1,
|
||||||
|
};
|
||||||
getParamLfo(context, frequencyParam, start, end, lfoValues);
|
getParamLfo(context, frequencyParam, start, end, lfoValues);
|
||||||
|
}
|
||||||
|
|
||||||
return filter;
|
return filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,17 +336,6 @@ export function scheduleAtTime(callback, targetTime, audioContext = getAudioCont
|
|||||||
const currentTime = audioContext.currentTime;
|
const currentTime = audioContext.currentTime;
|
||||||
webAudioTimeout(audioContext, callback, currentTime, targetTime);
|
webAudioTimeout(audioContext, callback, currentTime, targetTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
let silencer;
|
|
||||||
function getSilencer() {
|
|
||||||
const ac = getAudioContext();
|
|
||||||
if (!silencer) {
|
|
||||||
silencer = gainNode(0);
|
|
||||||
silencer.connect(ac.destination);
|
|
||||||
}
|
|
||||||
return silencer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConstantSource inherits AudioScheduledSourceNode, which has scheduling abilities
|
// ConstantSource inherits AudioScheduledSourceNode, which has scheduling abilities
|
||||||
// a bit of a hack, but it works very well :)
|
// a bit of a hack, but it works very well :)
|
||||||
export function webAudioTimeout(audioContext, onComplete, startTime, stopTime) {
|
export function webAudioTimeout(audioContext, onComplete, startTime, stopTime) {
|
||||||
@@ -357,11 +343,18 @@ export function webAudioTimeout(audioContext, onComplete, startTime, stopTime) {
|
|||||||
|
|
||||||
// Certain browsers requires audio nodes to be connected in order for their onended events
|
// Certain browsers requires audio nodes to be connected in order for their onended events
|
||||||
// to fire, so we _mute it_ and then connect it to the destination
|
// to fire, so we _mute it_ and then connect it to the destination
|
||||||
const zeroGain = getSilencer();
|
const zeroGain = gainNode(0);
|
||||||
|
zeroGain.connect(audioContext.destination);
|
||||||
constantNode.connect(zeroGain);
|
constantNode.connect(zeroGain);
|
||||||
|
|
||||||
// Schedule the `onComplete` callback to occur at `stopTime`
|
// Schedule the `onComplete` callback to occur at `stopTime`
|
||||||
constantNode.onended = () => {
|
constantNode.onended = () => {
|
||||||
|
// Ensure garbage collection
|
||||||
|
try {
|
||||||
|
zeroGain.disconnect();
|
||||||
|
} catch {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
constantNode.disconnect();
|
constantNode.disconnect();
|
||||||
} catch {
|
} catch {
|
||||||
@@ -387,9 +380,9 @@ const mod = (freq, range = 1, type = 'sine') => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
osc.start();
|
osc.start();
|
||||||
const g = new GainNode(ctx, { gain: range });
|
const g = gainNode(range);
|
||||||
osc.connect(g); // -range, range
|
osc.connect(g); // -range, range
|
||||||
return { node: g, stop: (t) => osc.stop(t) };
|
return { node: g, stop: (t) => osc.stop(t), osc: osc };
|
||||||
};
|
};
|
||||||
const fm = (frequencyparam, harmonicityRatio, modulationIndex, wave = 'sine') => {
|
const fm = (frequencyparam, harmonicityRatio, modulationIndex, wave = 'sine') => {
|
||||||
const carrfreq = frequencyparam.value;
|
const carrfreq = frequencyparam.value;
|
||||||
@@ -441,22 +434,22 @@ export function applyFM(param, value, begin) {
|
|||||||
modulator.connect(envGain);
|
modulator.connect(envGain);
|
||||||
envGain.connect(param);
|
envGain.connect(param);
|
||||||
}
|
}
|
||||||
|
fmmod.osc.onended = () => {
|
||||||
|
envGain.disconnect();
|
||||||
|
modulator.disconnect();
|
||||||
|
fmmod.osc.disconnect();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return { stop };
|
return { stop };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Saturation curves
|
// Saturation curves
|
||||||
|
|
||||||
const fastTanh = (x) => {
|
|
||||||
const x2 = x * x;
|
|
||||||
const y = (x * (27 + x2)) / (27 + 9 * x2);
|
|
||||||
return y < -1 ? -1 : y > 1 ? 1 : y;
|
|
||||||
};
|
|
||||||
const __squash = (x) => x / (1 + x); // [0, inf) to [0, 1)
|
const __squash = (x) => x / (1 + x); // [0, inf) to [0, 1)
|
||||||
const _mod = (n, m) => ((n % m) + m) % m;
|
const _mod = (n, m) => ((n % m) + m) % m;
|
||||||
|
|
||||||
const _scurve = (x, k) => ((1 + k) * x) / (1 + k * Math.abs(x));
|
const _scurve = (x, k) => ((1 + k) * x) / (1 + k * Math.abs(x));
|
||||||
const _soft = (x, k) => fastTanh(x * (1 + k));
|
const _soft = (x, k) => Math.tanh(x * (1 + k));
|
||||||
const _hard = (x, k) => clamp((1 + k) * x, -1, 1);
|
const _hard = (x, k) => clamp((1 + k) * x, -1, 1);
|
||||||
|
|
||||||
const _fold = (x, k) => {
|
const _fold = (x, k) => {
|
||||||
@@ -496,8 +489,13 @@ const _chebyshev = (x, k) => {
|
|||||||
let tnm1 = 1;
|
let tnm1 = 1;
|
||||||
let tnm2 = x;
|
let tnm2 = x;
|
||||||
let tn;
|
let tn;
|
||||||
let y = x;
|
let y = 0;
|
||||||
for (let i = 2; i < 64; i++) {
|
for (let i = 1; i < 64; i++) {
|
||||||
|
if (i < 2) {
|
||||||
|
// Already set inital conditions
|
||||||
|
y += i == 0 ? tnm1 : tnm2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
tn = 2 * x * tnm1 - tnm2; // https://en.wikipedia.org/wiki/Chebyshev_polynomials#Recurrence_definition
|
tn = 2 * x * tnm1 - tnm2; // https://en.wikipedia.org/wiki/Chebyshev_polynomials#Recurrence_definition
|
||||||
tnm2 = tnm1;
|
tnm2 = tnm1;
|
||||||
tnm1 = tn;
|
tnm1 = tn;
|
||||||
@@ -542,7 +540,7 @@ export const getDistortion = (distort, postgain, algorithm) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getFrequencyFromValue = (value, defaultNote = 36) => {
|
export const getFrequencyFromValue = (value, defaultNote = 36) => {
|
||||||
let { note, freq } = value;
|
let { note, freq, octave = 0 } = value;
|
||||||
note = note || defaultNote;
|
note = note || defaultNote;
|
||||||
if (typeof note === 'string') {
|
if (typeof note === 'string') {
|
||||||
note = noteToMidi(note); // e.g. c3 => 48
|
note = noteToMidi(note); // e.g. c3 => 48
|
||||||
@@ -551,7 +549,7 @@ export const getFrequencyFromValue = (value, defaultNote = 36) => {
|
|||||||
if (!freq && typeof note === 'number') {
|
if (!freq && typeof note === 'number') {
|
||||||
freq = midiToFreq(note); // + 48);
|
freq = midiToFreq(note); // + 48);
|
||||||
}
|
}
|
||||||
|
freq *= Math.pow(2, octave);
|
||||||
return Number(freq);
|
return Number(freq);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -562,25 +560,3 @@ export const destroyAudioWorkletNode = (node) => {
|
|||||||
node.disconnect();
|
node.disconnect();
|
||||||
node.parameters.get('end')?.setValueAtTime(0, 0);
|
node.parameters.get('end')?.setValueAtTime(0, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setParams = (node, params, t) => {
|
|
||||||
const ac = getAudioContext();
|
|
||||||
t ??= ac.currentTime;
|
|
||||||
const workletParams = node && node.parameters && typeof node.parameters.get === 'function';
|
|
||||||
for (const [key, value] of Object.entries(params)) {
|
|
||||||
if (value == null) continue;
|
|
||||||
let param;
|
|
||||||
if (workletParams) {
|
|
||||||
param = node.parameters.get(key);
|
|
||||||
}
|
|
||||||
if (param == null && node[key] instanceof AudioParam) {
|
|
||||||
param = node[key];
|
|
||||||
}
|
|
||||||
if (param instanceof AudioParam) {
|
|
||||||
param.cancelScheduledValues(t);
|
|
||||||
param.setValueAtTime(value, t);
|
|
||||||
} else {
|
|
||||||
node[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { getCommonSampleInfo } from './util.mjs';
|
import { getBaseURL, getCommonSampleInfo } from './util.mjs';
|
||||||
import { registerSound, registerWaveTable } from './index.mjs';
|
import { registerSound, registerWaveTable } from './index.mjs';
|
||||||
import { getAudioContext } from './audioContext.mjs';
|
import { getAudioContext } from './audioContext.mjs';
|
||||||
import { getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs';
|
import { getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs';
|
||||||
@@ -211,7 +211,7 @@ export async function fetchSampleMap(url) {
|
|||||||
// not a browser
|
// not a browser
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const base = url.split('/').slice(0, -1).join('/');
|
const base = getBaseURL(url);
|
||||||
if (typeof fetch === 'undefined') {
|
if (typeof fetch === 'undefined') {
|
||||||
// skip fetch when in node / testing
|
// skip fetch when in node / testing
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -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();
|
||||||
});
|
});
|
||||||
@@ -478,16 +478,15 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
const endWithRelease = end + release;
|
const endWithRelease = end + release;
|
||||||
const chainID = Math.round(Math.random() * 1000000);
|
const chainID = Math.round(Math.random() * 1000000);
|
||||||
|
|
||||||
// oldest audio nodes will be stopped if maximum polyphony is exceeded
|
// oldest audio nodes will be destroyed if maximum polyphony is exceeded
|
||||||
while (activeSoundSources.size >= maxPolyphony) {
|
for (let i = 0; i <= activeSoundSources.size - maxPolyphony; i++) {
|
||||||
const { value: entry } = activeSoundSources.entries().next();
|
const ch = activeSoundSources.entries().next();
|
||||||
if (!entry) break;
|
const source = ch.value[1].deref();
|
||||||
const [oldChainID, ref] = entry;
|
const chainID = ch.value[0];
|
||||||
const handle = ref?.deref();
|
|
||||||
const endTime = t + 0.25;
|
const endTime = t + 0.25;
|
||||||
handle?.node?.gain?.linearRampToValueAtTime?.(0, endTime);
|
source?.node?.gain?.linearRampToValueAtTime(0, endTime);
|
||||||
handle?.stop?.(endTime);
|
source?.stop?.(endTime);
|
||||||
activeSoundSources.delete(oldChainID);
|
activeSoundSources.delete(chainID);
|
||||||
}
|
}
|
||||||
|
|
||||||
let audioNodes = [];
|
let audioNodes = [];
|
||||||
@@ -554,13 +553,14 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
rate: 'lprate',
|
rate: 'lprate',
|
||||||
sync: 'lpsync',
|
sync: 'lpsync',
|
||||||
depth: 'lpdepth',
|
depth: 'lpdepth',
|
||||||
|
depthfrequency: 'lpdepthfrequency',
|
||||||
shape: 'lpshape',
|
shape: 'lpshape',
|
||||||
dcoffset: 'lpdc',
|
dcoffset: 'lpdc',
|
||||||
skew: 'lpskew',
|
skew: 'lpskew',
|
||||||
};
|
};
|
||||||
const lpParams = pickAndRename(value, lpMap);
|
const lpParams = pickAndRename(value, lpMap);
|
||||||
lpParams.type = 'lowpass';
|
lpParams.type = 'lowpass';
|
||||||
let lp = () => createFilter(ac, t, end, lpParams, cps);
|
let lp = () => createFilter(ac, t, end, lpParams, cps, cycle);
|
||||||
chain.push(lp());
|
chain.push(lp());
|
||||||
if (ftype === '24db') {
|
if (ftype === '24db') {
|
||||||
chain.push(lp());
|
chain.push(lp());
|
||||||
@@ -582,13 +582,14 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
rate: 'hprate',
|
rate: 'hprate',
|
||||||
sync: 'hpsync',
|
sync: 'hpsync',
|
||||||
depth: 'hpdepth',
|
depth: 'hpdepth',
|
||||||
|
depthfrequency: 'hpdepthfrequency',
|
||||||
shape: 'hpshape',
|
shape: 'hpshape',
|
||||||
dcoffset: 'hpdc',
|
dcoffset: 'hpdc',
|
||||||
skew: 'hpskew',
|
skew: 'hpskew',
|
||||||
};
|
};
|
||||||
const hpParams = pickAndRename(value, hpMap);
|
const hpParams = pickAndRename(value, hpMap);
|
||||||
hpParams.type = 'highpass';
|
hpParams.type = 'highpass';
|
||||||
let hp = () => createFilter(ac, t, end, hpParams, cps);
|
let hp = () => createFilter(ac, t, end, hpParams, cps, cycle);
|
||||||
chain.push(hp());
|
chain.push(hp());
|
||||||
if (ftype === '24db') {
|
if (ftype === '24db') {
|
||||||
chain.push(hp());
|
chain.push(hp());
|
||||||
@@ -610,13 +611,14 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
rate: 'bprate',
|
rate: 'bprate',
|
||||||
sync: 'bpsync',
|
sync: 'bpsync',
|
||||||
depth: 'bpdepth',
|
depth: 'bpdepth',
|
||||||
|
depthfrequency: 'bpdepthfrequency',
|
||||||
shape: 'bpshape',
|
shape: 'bpshape',
|
||||||
dcoffset: 'bpdc',
|
dcoffset: 'bpdc',
|
||||||
skew: 'bpskew',
|
skew: 'bpskew',
|
||||||
};
|
};
|
||||||
const bpParams = pickAndRename(value, bpMap);
|
const bpParams = pickAndRename(value, bpMap);
|
||||||
bpParams.type = 'bandpass';
|
bpParams.type = 'bandpass';
|
||||||
let bp = () => createFilter(ac, t, end, bpParams, cps);
|
let bp = () => createFilter(ac, t, end, bpParams, cps, cycle);
|
||||||
chain.push(bp());
|
chain.push(bp());
|
||||||
if (ftype === '24db') {
|
if (ftype === '24db') {
|
||||||
chain.push(bp());
|
chain.push(bp());
|
||||||
@@ -666,6 +668,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
curve: 1.5,
|
curve: 1.5,
|
||||||
});
|
});
|
||||||
lfo.connect(amGain.gain);
|
lfo.connect(amGain.gain);
|
||||||
|
audioNodes.push(lfo);
|
||||||
chain.push(amGain);
|
chain.push(amGain);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { registerSound, soundMap } from './superdough.mjs';
|
|||||||
import { getAudioContext } from './audioContext.mjs';
|
import { getAudioContext } from './audioContext.mjs';
|
||||||
import {
|
import {
|
||||||
applyFM,
|
applyFM,
|
||||||
claimVoice,
|
|
||||||
destroyAudioWorkletNode,
|
destroyAudioWorkletNode,
|
||||||
gainNode,
|
gainNode,
|
||||||
getADSRValues,
|
getADSRValues,
|
||||||
@@ -13,8 +12,6 @@ import {
|
|||||||
getPitchEnvelope,
|
getPitchEnvelope,
|
||||||
getVibratoOscillator,
|
getVibratoOscillator,
|
||||||
getWorklet,
|
getWorklet,
|
||||||
setParams,
|
|
||||||
releaseVoice,
|
|
||||||
noises,
|
noises,
|
||||||
webAudioTimeout,
|
webAudioTimeout,
|
||||||
} from './helpers.mjs';
|
} from './helpers.mjs';
|
||||||
@@ -167,38 +164,36 @@ export function registerSynthSounds() {
|
|||||||
const end = holdend + release + 0.01;
|
const end = holdend + release + 0.01;
|
||||||
const voices = clamp(unison, 1, 100);
|
const voices = clamp(unison, 1, 100);
|
||||||
let panspread = voices > 1 ? clamp(spread, 0, 1) : 0;
|
let panspread = voices > 1 ? clamp(spread, 0, 1) : 0;
|
||||||
let o = claimVoice('supersaw');
|
let o = getWorklet(
|
||||||
const params = {
|
ac,
|
||||||
|
'supersaw-oscillator',
|
||||||
|
{
|
||||||
frequency,
|
frequency,
|
||||||
begin,
|
begin,
|
||||||
end,
|
end,
|
||||||
freqspread: detune,
|
freqspread: detune,
|
||||||
panspread,
|
|
||||||
};
|
|
||||||
const processorOptions = {
|
|
||||||
voices,
|
voices,
|
||||||
};
|
panspread,
|
||||||
if (o == null) {
|
},
|
||||||
o = getWorklet(ac, 'supersaw-oscillator', params, {
|
{
|
||||||
outputChannelCount: [2],
|
outputChannelCount: [2],
|
||||||
processorOptions,
|
},
|
||||||
});
|
);
|
||||||
} else {
|
|
||||||
setParams(o, params);
|
|
||||||
o.port.postMessage({ type: 'reset', payload: { processorOptions } });
|
|
||||||
}
|
|
||||||
const gainAdjustment = 1 / Math.sqrt(voices);
|
const gainAdjustment = 1 / Math.sqrt(voices);
|
||||||
getPitchEnvelope(o.parameters.get('detune'), value, begin, holdend);
|
getPitchEnvelope(o.parameters.get('detune'), value, begin, holdend);
|
||||||
const vibratoOscillator = getVibratoOscillator(o.parameters.get('detune'), value, begin);
|
const vibratoOscillator = getVibratoOscillator(o.parameters.get('detune'), value, begin);
|
||||||
const fm = applyFM(o.parameters.get('frequency'), value, begin);
|
const fm = applyFM(o.parameters.get('frequency'), value, begin);
|
||||||
const envGain = o.connect(gainNode(1));
|
let envGain = gainNode(1);
|
||||||
|
envGain = o.connect(envGain);
|
||||||
|
|
||||||
getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 0.3 * gainAdjustment, begin, holdend, 'linear');
|
getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 0.3 * gainAdjustment, begin, holdend, 'linear');
|
||||||
const timeoutNode = webAudioTimeout(
|
|
||||||
|
let timeoutNode = webAudioTimeout(
|
||||||
ac,
|
ac,
|
||||||
() => {
|
() => {
|
||||||
destroyAudioWorkletNode(o);
|
destroyAudioWorkletNode(o);
|
||||||
envGain.disconnect();
|
envGain.disconnect();
|
||||||
releaseVoice('supersaw', o);
|
|
||||||
onended();
|
onended();
|
||||||
fm?.stop();
|
fm?.stop();
|
||||||
vibratoOscillator?.stop();
|
vibratoOscillator?.stop();
|
||||||
@@ -272,7 +267,7 @@ export function registerSynthSounds() {
|
|||||||
|
|
||||||
getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 1, begin, holdend, 'linear');
|
getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 1, begin, holdend, 'linear');
|
||||||
|
|
||||||
const timeoutNode = webAudioTimeout(
|
let timeoutNode = webAudioTimeout(
|
||||||
ac,
|
ac,
|
||||||
() => {
|
() => {
|
||||||
destroyAudioWorkletNode(o);
|
destroyAudioWorkletNode(o);
|
||||||
@@ -346,7 +341,7 @@ export function registerSynthSounds() {
|
|||||||
lfo = getLfo(ac, begin, end, { frequency: pwrate, depth: pwsweep });
|
lfo = getLfo(ac, begin, end, { frequency: pwrate, depth: pwsweep });
|
||||||
lfo.connect(o.parameters.get('pulsewidth'));
|
lfo.connect(o.parameters.get('pulsewidth'));
|
||||||
}
|
}
|
||||||
const timeoutNode = webAudioTimeout(
|
let timeoutNode = webAudioTimeout(
|
||||||
ac,
|
ac,
|
||||||
() => {
|
() => {
|
||||||
destroyAudioWorkletNode(o);
|
destroyAudioWorkletNode(o);
|
||||||
@@ -498,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();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import { logger } from './logger.mjs';
|
|||||||
// currently duplicate with core util.mjs to skip dependency
|
// currently duplicate with core util.mjs to skip dependency
|
||||||
// TODO: add separate util module?
|
// TODO: add separate util module?
|
||||||
|
|
||||||
export const LN2 = Math.LN2;
|
|
||||||
|
|
||||||
export const tokenizeNote = (note) => {
|
export const tokenizeNote = (note) => {
|
||||||
if (typeof note !== 'string') {
|
if (typeof note !== 'string') {
|
||||||
return [];
|
return [];
|
||||||
@@ -34,9 +32,10 @@ export const noteToMidi = (note, defaultOctave = 3) => {
|
|||||||
export const midiToFreq = (n) => {
|
export const midiToFreq = (n) => {
|
||||||
return Math.pow(2, (n - 69) / 12) * 440;
|
return Math.pow(2, (n - 69) / 12) * 440;
|
||||||
};
|
};
|
||||||
|
export const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
|
||||||
|
|
||||||
export const freqToMidi = (freq) => {
|
export const freqToMidi = (freq) => {
|
||||||
return (12 * Math.log(freq / 440)) / LN2 + 69;
|
return (12 * Math.log(freq / 440)) / Math.LN2 + 69;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const valueToMidi = (value, fallbackValue) => {
|
export const valueToMidi = (value, fallbackValue) => {
|
||||||
@@ -116,37 +115,12 @@ export const pickAndRename = (source, map) => {
|
|||||||
return Object.fromEntries(Object.entries(map).map(([newKey, oldKey]) => [newKey, source[oldKey]]));
|
return Object.fromEntries(Object.entries(map).map(([newKey, oldKey]) => [newKey, source[oldKey]]));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Optimized / numerical functions (ideal for hot dsp loops)
|
export const getBaseURL = (url) => {
|
||||||
|
try {
|
||||||
// Optimized clamp (~1.7x faster than Math.max/min approach)
|
// For real URLs
|
||||||
export const clamp = (num, min, max) => {
|
return new URL('.', new URL(url)).href.replace(/\/$/, ''); // removes trailing slash
|
||||||
if (num < min) return min;
|
} catch {
|
||||||
if (num > max) return max;
|
// For pseudo URLS
|
||||||
return num;
|
return url.split('/').slice(0, -1).join('/');
|
||||||
};
|
}
|
||||||
export const mod = (n, m) => ((n % m) + m) % m;
|
|
||||||
export const lerp = (a, b, n) => n * (b - a) + a;
|
|
||||||
export const frac = (x) => x - Math.floor(x);
|
|
||||||
|
|
||||||
// Fast integer ops for non-negative values
|
|
||||||
export const ffloor = (x) => x | 0;
|
|
||||||
export const fround = (x) => ffloor(x + 0.5);
|
|
||||||
export const fceil = (x) => ffloor(x + 1);
|
|
||||||
export const ffrac = (x) => x - ffloor(x);
|
|
||||||
|
|
||||||
export const fastexpm1 = (x) => x * (1 + x + 0.5 * x * x); // Taylor approximation
|
|
||||||
|
|
||||||
export const fastpow2 = (x) => {
|
|
||||||
// Taylor approximation of 2 ^ x
|
|
||||||
const a = x * LN2;
|
|
||||||
return 1 + a * (1 + a * (0.5 + a / 6));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const applySemitoneDetuneToFrequency = (frequency, detune) => {
|
|
||||||
return frequency * Math.pow(2, detune / 12);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const applyFastDetune = (frequency, detune) => {
|
|
||||||
if (detune < 4) return frequency * fastpow2(detune / 12);
|
|
||||||
return applySemitoneDetuneToFrequency(frequency, detune);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -1,20 +1,16 @@
|
|||||||
import { getAudioContext, registerSound } from './index.mjs';
|
import { getAudioContext, registerSound } from './index.mjs';
|
||||||
import { getCommonSampleInfo } from './util.mjs';
|
import { getBaseURL, getCommonSampleInfo } from './util.mjs';
|
||||||
import {
|
import {
|
||||||
applyFM,
|
applyFM,
|
||||||
applyParameterModulators,
|
applyParameterModulators,
|
||||||
claimVoice,
|
destroyAudioWorkletNode,
|
||||||
gainNode,
|
|
||||||
getADSRValues,
|
getADSRValues,
|
||||||
getFrequencyFromValue,
|
getFrequencyFromValue,
|
||||||
getParamADSR,
|
getParamADSR,
|
||||||
getPitchEnvelope,
|
getPitchEnvelope,
|
||||||
getVibratoOscillator,
|
getVibratoOscillator,
|
||||||
getWorklet,
|
getWorklet,
|
||||||
setParams,
|
|
||||||
releaseVoice,
|
|
||||||
webAudioTimeout,
|
webAudioTimeout,
|
||||||
destroyAudioWorkletNode,
|
|
||||||
} from './helpers.mjs';
|
} from './helpers.mjs';
|
||||||
import { logger } from './logger.mjs';
|
import { logger } from './logger.mjs';
|
||||||
|
|
||||||
@@ -194,6 +190,7 @@ export const tables = async (url, frameLen, json, options = {}) => {
|
|||||||
if (url.startsWith('local:')) {
|
if (url.startsWith('local:')) {
|
||||||
url = `http://localhost:5432`;
|
url = `http://localhost:5432`;
|
||||||
}
|
}
|
||||||
|
const base = getBaseURL(url);
|
||||||
if (typeof fetch !== 'function') {
|
if (typeof fetch !== 'function') {
|
||||||
// not a browser
|
// not a browser
|
||||||
return;
|
return;
|
||||||
@@ -204,7 +201,7 @@ export const tables = async (url, frameLen, json, options = {}) => {
|
|||||||
}
|
}
|
||||||
return fetch(url)
|
return fetch(url)
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((json) => _processTables(json, url, frameLen, options))
|
.then((json) => _processTables(json, base, frameLen, options))
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
throw new Error(`error loading "${url}"`);
|
throw new Error(`error loading "${url}"`);
|
||||||
@@ -222,15 +219,16 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
|
|||||||
const frequency = getFrequencyFromValue(value);
|
const frequency = getFrequencyFromValue(value);
|
||||||
const { url, label } = getCommonSampleInfo(value, tables);
|
const { url, label } = getCommonSampleInfo(value, tables);
|
||||||
const payload = await getPayload(url, label, frameLen);
|
const payload = await getPayload(url, label, frameLen);
|
||||||
const voiceKey = `wavetable:${payload.key}`;
|
|
||||||
let holdEnd = t + duration;
|
let holdEnd = t + duration;
|
||||||
if (clip !== undefined) {
|
if (clip !== undefined) {
|
||||||
holdEnd = Math.min(t + clip * duration, holdEnd);
|
holdEnd = Math.min(t + clip * duration, holdEnd);
|
||||||
}
|
}
|
||||||
const endWithRelease = holdEnd + release;
|
const endWithRelease = holdEnd + release;
|
||||||
const envEnd = endWithRelease + 0.01;
|
const envEnd = endWithRelease + 0.01;
|
||||||
let source = claimVoice(voiceKey);
|
const source = getWorklet(
|
||||||
const params = {
|
ac,
|
||||||
|
'wavetable-oscillator-processor',
|
||||||
|
{
|
||||||
begin: t,
|
begin: t,
|
||||||
end: envEnd,
|
end: envEnd,
|
||||||
frequency,
|
frequency,
|
||||||
@@ -238,23 +236,15 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
|
|||||||
position: value.wt,
|
position: value.wt,
|
||||||
warp: value.warp,
|
warp: value.warp,
|
||||||
warpMode: warpmode,
|
warpMode: warpmode,
|
||||||
panspread: value.spread,
|
|
||||||
};
|
|
||||||
const processorOptions = {
|
|
||||||
phaseRand: value.wtphaserand ?? (value.unison > 1 ? 1 : 0),
|
|
||||||
voices: Math.max(value.unison ?? 1, 1),
|
voices: Math.max(value.unison ?? 1, 1),
|
||||||
};
|
panspread: value.spread,
|
||||||
if (source == null) {
|
phaserand: (value.wtphaserand ?? value.unison > 1) ? 1 : 0,
|
||||||
source = getWorklet(ac, 'wavetable-oscillator-processor', params, { outputChannelCount: [2], processorOptions });
|
},
|
||||||
} else {
|
{ outputChannelCount: [2] },
|
||||||
setParams(source, params);
|
);
|
||||||
source.port.postMessage({ type: 'reset', payload: { processorOptions } });
|
|
||||||
}
|
|
||||||
source.port.postMessage({ type: 'table', payload });
|
source.port.postMessage({ type: 'table', payload });
|
||||||
if (ac.currentTime > t) {
|
if (ac.currentTime > t) {
|
||||||
logger(`[wavetable] still loading sound "${s}:${n}"`, 'highlight');
|
logger(`[wavetable] still loading sound "${s}:${n}"`, 'highlight');
|
||||||
destroyAudioWorkletNode(source);
|
|
||||||
releaseVoice(voiceKey, source);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const posADSRParams = [value.wtattack, value.wtdecay, value.wtsustain, value.wtrelease];
|
const posADSRParams = [value.wtattack, value.wtdecay, value.wtsustain, value.wtrelease];
|
||||||
@@ -321,20 +311,20 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
|
|||||||
);
|
);
|
||||||
const vibratoOscillator = getVibratoOscillator(source.parameters.get('detune'), value, t);
|
const vibratoOscillator = getVibratoOscillator(source.parameters.get('detune'), value, t);
|
||||||
const fm = applyFM(source.parameters.get('frequency'), value, t);
|
const fm = applyFM(source.parameters.get('frequency'), value, t);
|
||||||
const envGain = source.connect(gainNode(1));
|
const envGain = ac.createGain();
|
||||||
getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 0.3, t, holdEnd, 'linear');
|
const node = source.connect(envGain);
|
||||||
|
getParamADSR(node.gain, attack, decay, sustain, release, 0, 0.3, t, holdEnd, 'linear');
|
||||||
getPitchEnvelope(source.parameters.get('detune'), value, t, holdEnd);
|
getPitchEnvelope(source.parameters.get('detune'), value, t, holdEnd);
|
||||||
const handle = { node: envGain };
|
const handle = { node, source };
|
||||||
const timeoutNode = webAudioTimeout(
|
const timeoutNode = webAudioTimeout(
|
||||||
ac,
|
ac,
|
||||||
() => {
|
() => {
|
||||||
|
destroyAudioWorkletNode(source);
|
||||||
vibratoOscillator?.stop();
|
vibratoOscillator?.stop();
|
||||||
fm?.stop();
|
fm?.stop();
|
||||||
|
node.disconnect();
|
||||||
wtPosModulators?.disconnect();
|
wtPosModulators?.disconnect();
|
||||||
wtWarpModulators?.disconnect();
|
wtWarpModulators?.disconnect();
|
||||||
envGain.disconnect();
|
|
||||||
destroyAudioWorkletNode(source);
|
|
||||||
releaseVoice(voiceKey, source);
|
|
||||||
onended();
|
onended();
|
||||||
},
|
},
|
||||||
t,
|
t,
|
||||||
|
|||||||
+132
-124
@@ -5,29 +5,27 @@
|
|||||||
import OLAProcessor from './ola-processor';
|
import OLAProcessor from './ola-processor';
|
||||||
import FFT from './fft.js';
|
import FFT from './fft.js';
|
||||||
import { getDistortionAlgorithm } from './helpers.mjs';
|
import { getDistortionAlgorithm } from './helpers.mjs';
|
||||||
import {
|
|
||||||
applyFastDetune,
|
|
||||||
applySemitoneDetuneToFrequency,
|
|
||||||
clamp,
|
|
||||||
fastexpm1,
|
|
||||||
fceil,
|
|
||||||
ffloor,
|
|
||||||
ffrac,
|
|
||||||
frac,
|
|
||||||
fround,
|
|
||||||
lerp,
|
|
||||||
} from './util.mjs';
|
|
||||||
|
|
||||||
const blockSize = 128;
|
const blockSize = 128;
|
||||||
const PI = Math.PI;
|
const PI = Math.PI;
|
||||||
const TWO_PI = 2 * PI;
|
const TWO_PI = 2 * PI;
|
||||||
const INVSR = 1 / sampleRate;
|
const INVSR = 1 / sampleRate;
|
||||||
const MAX_VOICES = 64;
|
|
||||||
|
|
||||||
|
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
|
||||||
|
const mod = (n, m) => ((n % m) + m) % m;
|
||||||
|
const lerp = (a, b, n) => n * (b - a) + a;
|
||||||
const pv = (arr, n) => arr[n] ?? arr[0];
|
const pv = (arr, n) => arr[n] ?? arr[0];
|
||||||
|
const frac = (x) => x - Math.floor(x);
|
||||||
|
|
||||||
|
// Fast integer ops for non-negative values
|
||||||
|
const ffloor = (x) => x | 0;
|
||||||
|
const fround = (x) => ffloor(x + 0.5);
|
||||||
|
const fceil = (x) => ffloor(x + 1);
|
||||||
|
const ffrac = (x) => x - ffloor(x);
|
||||||
|
|
||||||
const fast_tanh = (x) => {
|
const fast_tanh = (x) => {
|
||||||
const x2 = x * x;
|
const x2 = x ** 2;
|
||||||
return (x * (27 + x2)) / (27 + 9 * x2);
|
return (x * (27.0 + x2)) / (27.0 + 9.0 * x2);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Optimized per-voice detuner which precomputes constants
|
// Optimized per-voice detuner which precomputes constants
|
||||||
@@ -40,6 +38,10 @@ const getDetuner = (unison, detune) => {
|
|||||||
return (voiceIdx) => voiceIdx * scale - center;
|
return (voiceIdx) => voiceIdx * scale - center;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const applySemitoneDetuneToFrequency = (frequency, detune) => {
|
||||||
|
return frequency * Math.pow(2, detune / 12);
|
||||||
|
};
|
||||||
|
|
||||||
// Smooth waveshape near discontinuities to remove frequencies above Nyquist and prevent aliasing
|
// Smooth waveshape near discontinuities to remove frequencies above Nyquist and prevent aliasing
|
||||||
// referenced from https://www.kvraudio.com/forum/viewtopic.php?t=375517
|
// referenced from https://www.kvraudio.com/forum/viewtopic.php?t=375517
|
||||||
function polyBlep(phase, dt) {
|
function polyBlep(phase, dt) {
|
||||||
@@ -167,11 +169,10 @@ class LFOProcessor extends AudioWorkletProcessor {
|
|||||||
}
|
}
|
||||||
const dt = frequency * INVSR;
|
const dt = frequency * INVSR;
|
||||||
for (let n = 0; n < blockSize; n++) {
|
for (let n = 0; n < blockSize; n++) {
|
||||||
let modval = (waveshapes[shape](this.phase, skew) + dcoffset) * depth;
|
|
||||||
modval = curve !== 1 ? Math.pow(modval, curve) : modval;
|
|
||||||
const out = clamp(modval, min, max);
|
|
||||||
for (let i = 0; i < output.length; i++) {
|
for (let i = 0; i < output.length; i++) {
|
||||||
output[i][n] = out;
|
let modval = (waveshapes[shape](this.phase, skew) + dcoffset) * depth;
|
||||||
|
modval = Math.pow(modval, curve);
|
||||||
|
output[i][n] = clamp(modval, min, max);
|
||||||
}
|
}
|
||||||
this.incrementPhase(dt);
|
this.incrementPhase(dt);
|
||||||
}
|
}
|
||||||
@@ -442,7 +443,7 @@ class DistortProcessor extends AudioWorkletProcessor {
|
|||||||
this.started = hasInput;
|
this.started = hasInput;
|
||||||
for (let n = 0; n < blockSize; n++) {
|
for (let n = 0; n < blockSize; n++) {
|
||||||
const postgain = clamp(pv(parameters.postgain, n), 0.001, 1);
|
const postgain = clamp(pv(parameters.postgain, n), 0.001, 1);
|
||||||
const shape = fastexpm1(pv(parameters.distort, n));
|
const shape = Math.expm1(pv(parameters.distort, n));
|
||||||
for (let ch = 0; ch < input.length; ch++) {
|
for (let ch = 0; ch < input.length; ch++) {
|
||||||
const x = input[ch][n];
|
const x = input[ch][n];
|
||||||
output[ch][n] = postgain * this.algorithm(x, shape);
|
output[ch][n] = postgain * this.algorithm(x, shape);
|
||||||
@@ -455,17 +456,9 @@ registerProcessor('distort-processor', DistortProcessor);
|
|||||||
|
|
||||||
// SUPERSAW
|
// SUPERSAW
|
||||||
class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
||||||
constructor(options) {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.phases = new Float32Array(MAX_VOICES);
|
this.phase = [];
|
||||||
this.reset(options.processorOptions);
|
|
||||||
}
|
|
||||||
reset(options) {
|
|
||||||
const phases = this.phases;
|
|
||||||
for (let i = 0; i < phases.length; i++) {
|
|
||||||
phases[i] = Math.random();
|
|
||||||
}
|
|
||||||
this.voices = options.voices;
|
|
||||||
}
|
}
|
||||||
static get parameterDescriptors() {
|
static get parameterDescriptors() {
|
||||||
return [
|
return [
|
||||||
@@ -475,17 +468,20 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
|||||||
max: Number.POSITIVE_INFINITY,
|
max: Number.POSITIVE_INFINITY,
|
||||||
min: 0,
|
min: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'end',
|
name: 'end',
|
||||||
defaultValue: 0,
|
defaultValue: 0,
|
||||||
max: Number.POSITIVE_INFINITY,
|
max: Number.POSITIVE_INFINITY,
|
||||||
min: 0,
|
min: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'frequency',
|
name: 'frequency',
|
||||||
defaultValue: 440,
|
defaultValue: 440,
|
||||||
min: Number.EPSILON,
|
min: Number.EPSILON,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'panspread',
|
name: 'panspread',
|
||||||
defaultValue: 0.4,
|
defaultValue: 0.4,
|
||||||
@@ -502,53 +498,50 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
|||||||
defaultValue: 0,
|
defaultValue: 0,
|
||||||
min: 0,
|
min: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'voices',
|
||||||
|
defaultValue: 5,
|
||||||
|
min: 1,
|
||||||
|
automationRate: 'k-rate',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
process(_input, outputs, params) {
|
process(_input, outputs, params) {
|
||||||
const started = currentTime >= params.begin[0];
|
if (currentTime <= params.begin[0]) {
|
||||||
const ended = currentTime >= params.end[0];
|
|
||||||
if (!started || ended) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (currentTime >= params.end[0]) {
|
||||||
|
// this.port.postMessage({ type: 'onended' });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const output = outputs[0];
|
const output = outputs[0];
|
||||||
// fast k-rate paths
|
const voices = params.voices[0]; // k-rate
|
||||||
let freq, detune, freqspread, detuner;
|
|
||||||
if (params.freqspread.length === 1) {
|
|
||||||
freqspread = params.freqspread[0];
|
|
||||||
detuner = getDetuner(this.voices, freqspread);
|
|
||||||
}
|
|
||||||
if (params.frequency.length === 1 && params.detune.length === 1) {
|
|
||||||
freq = params.frequency[0];
|
|
||||||
detune = params.detune[0];
|
|
||||||
freq = applySemitoneDetuneToFrequency(freq, detune / 100);
|
|
||||||
}
|
|
||||||
let panspread, gainL, gainR;
|
|
||||||
if (params.panspread.length === 1) {
|
|
||||||
panspread = this.voices > 1 ? clamp(params.panspread[0], 0, 1) : 0;
|
|
||||||
gainL = Math.sqrt(0.5 - 0.5 * panspread);
|
|
||||||
gainR = Math.sqrt(0.5 + 0.5 * panspread);
|
|
||||||
}
|
|
||||||
for (let i = 0; i < output[0].length; i++) {
|
for (let i = 0; i < output[0].length; i++) {
|
||||||
|
const detune = pv(params.detune, i);
|
||||||
|
const freqspread = pv(params.freqspread, i);
|
||||||
const panspread = pv(params.panspread, i) * 0.5 + 0.5;
|
const panspread = pv(params.panspread, i) * 0.5 + 0.5;
|
||||||
gainL = Math.sqrt(1 - panspread);
|
let gainL = Math.sqrt(1 - panspread);
|
||||||
gainR = Math.sqrt(panspread);
|
let gainR = Math.sqrt(panspread);
|
||||||
// Main detuning (a-rate path)
|
let freq = pv(params.frequency, i);
|
||||||
detune ??= pv(params.detune, i);
|
// Main detuning
|
||||||
freqspread ??= pv(params.freqspread, i);
|
freq = applySemitoneDetuneToFrequency(freq, detune / 100);
|
||||||
freq ??= applySemitoneDetuneToFrequency(pv(params.frequency, i), detune / 100);
|
const detuner = getDetuner(voices, freqspread);
|
||||||
detuner ??= getDetuner(this.voices, freqspread);
|
for (let n = 0; n < voices; n++) {
|
||||||
for (let n = 0; n < this.voices; n++) {
|
|
||||||
// Individual voice detuning
|
// Individual voice detuning
|
||||||
const freqVoice = applyFastDetune(freq, detuner(n));
|
const freqVoice = applySemitoneDetuneToFrequency(freq, detuner(n));
|
||||||
// We must wrap this here because it is passed into sawblep below which
|
// We must wrap this here because it is passed into sawblep below which
|
||||||
// has domain [0, 1]
|
// has domain [0, 1]
|
||||||
const dt = frac(freqVoice * INVSR);
|
const dt = frac(freqVoice * INVSR);
|
||||||
const v = waveshapes.sawblep(this.phases[n], dt);
|
this.phase[n] = this.phase[n] ?? Math.random();
|
||||||
|
const v = waveshapes.sawblep(this.phase[n], dt);
|
||||||
|
|
||||||
output[0][i] += v * gainL;
|
output[0][i] += v * gainL;
|
||||||
output[1][i] += v * gainR;
|
output[1][i] += v * gainR;
|
||||||
let pn = this.phases[n] + dt;
|
|
||||||
|
let pn = this.phase[n] + dt;
|
||||||
if (pn >= 1.0) pn -= 1.0;
|
if (pn >= 1.0) pn -= 1.0;
|
||||||
this.phases[n] = pn;
|
this.phase[n] = pn;
|
||||||
// invert right and left gain
|
// invert right and left gain
|
||||||
const tmp = gainL;
|
const tmp = gainL;
|
||||||
gainL = gainR;
|
gainL = gainR;
|
||||||
@@ -1132,8 +1125,10 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
|||||||
{ name: 'freqspread', defaultValue: 0.18, min: 0 },
|
{ name: 'freqspread', defaultValue: 0.18, min: 0 },
|
||||||
{ name: 'position', defaultValue: 0, min: 0, max: 1 },
|
{ name: 'position', defaultValue: 0, min: 0, max: 1 },
|
||||||
{ name: 'warp', defaultValue: 0, min: 0, max: 1 },
|
{ name: 'warp', defaultValue: 0, min: 0, max: 1 },
|
||||||
{ name: 'warpMode', defaultValue: 0, automationRate: 'k-rate' },
|
{ name: 'warpMode', defaultValue: 0 },
|
||||||
|
{ name: 'voices', defaultValue: 1, min: 1, automationRate: 'k-rate' },
|
||||||
{ name: 'panspread', defaultValue: 0.7, min: 0, max: 1 },
|
{ name: 'panspread', defaultValue: 0.7, min: 0, max: 1 },
|
||||||
|
{ name: 'phaserand', defaultValue: 0, min: 0, max: 1 },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1141,32 +1136,35 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
|||||||
super(options);
|
super(options);
|
||||||
this.frameLen = 0;
|
this.frameLen = 0;
|
||||||
this.numFrames = 0;
|
this.numFrames = 0;
|
||||||
this.phases = new Float32Array(MAX_VOICES);
|
this.phase = [];
|
||||||
|
|
||||||
this.port.onmessage = (e) => {
|
this.port.onmessage = (e) => {
|
||||||
const { type, payload } = e.data || {};
|
const { type, payload } = e.data || {};
|
||||||
if (type === 'table') {
|
if (type === 'table') {
|
||||||
const key = payload.key;
|
const key = payload.key;
|
||||||
this.frameLen = payload.frameLen;
|
this.frameLen = payload.frameLen;
|
||||||
if (!tablesCache[key]) {
|
if (!tablesCache[key]) {
|
||||||
tablesCache[key] = payload.frames;
|
const tables = [payload.frames];
|
||||||
|
let table = tables[0];
|
||||||
|
for (let level = 1; level < 1; level++) {
|
||||||
|
const nextLen = table.length >> 1;
|
||||||
|
const nextTable = table.map((frame) => {
|
||||||
|
const avg = new Float32Array(nextLen);
|
||||||
|
for (let i = 0; i < nextLen; i++) {
|
||||||
|
avg[i] = (frame[2 * i] + frame[2 * i + 1]) / 2;
|
||||||
}
|
}
|
||||||
this.table = tablesCache[key];
|
return avg;
|
||||||
this.numFrames = this.table.length;
|
});
|
||||||
} else if (type === 'reset') {
|
tables.push(nextTable);
|
||||||
this.reset(payload.processorOptions);
|
table = nextTable;
|
||||||
|
if (nextLen <= 32) break;
|
||||||
|
}
|
||||||
|
tablesCache[key] = tables;
|
||||||
|
}
|
||||||
|
this.tables = tablesCache[key];
|
||||||
|
this.numFrames = this.tables[0].length;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.reset(options.processorOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
reset(options) {
|
|
||||||
const phaseRand = options.phaseRand ?? 1;
|
|
||||||
const phases = this.phases;
|
|
||||||
for (let i = 0; i < phases.length; i++) {
|
|
||||||
phases[i] = Math.random() * phaseRand;
|
|
||||||
}
|
|
||||||
this.voices = options.voices;
|
|
||||||
this.normalizer = 1 / Math.sqrt(this.voices);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_mirror(x) {
|
_mirror(x) {
|
||||||
@@ -1311,63 +1309,73 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
|||||||
return a + (b - a) * frac;
|
return a + (b - a) * frac;
|
||||||
}
|
}
|
||||||
|
|
||||||
process(_inputs, outputs, params) {
|
_chooseMip(dphi) {
|
||||||
const started = currentTime >= params.begin[0];
|
const approxHarm = clamp(dphi, 1e-6, 64);
|
||||||
const ended = currentTime >= params.end[0];
|
let level = 0;
|
||||||
if (!started || ended) {
|
while (level + 1 < (this.tables?.length || 1) && approxHarm < this.tables[level][0].length / 8) {
|
||||||
|
level++;
|
||||||
|
}
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
|
process(_inputs, outputs, parameters) {
|
||||||
|
if (currentTime >= parameters.end[0]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (currentTime <= parameters.begin[0]) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const outL = outputs[0][0];
|
const outL = outputs[0][0];
|
||||||
const outR = outputs[0][1] || outputs[0][0];
|
const outR = outputs[0][1] || outputs[0][0];
|
||||||
// fast k-rate paths
|
if (!this.tables) {
|
||||||
let freq, detune, freqspread, detuner;
|
outL.fill(0);
|
||||||
if (params.freqspread.length === 1) {
|
if (outR !== outL) outR.set(outL);
|
||||||
freqspread = params.freqspread[0];
|
return true;
|
||||||
detuner = getDetuner(this.voices, freqspread);
|
|
||||||
}
|
}
|
||||||
if (params.frequency.length === 1 && params.detune.length === 1) {
|
const voices = parameters.voices[0]; // k-rate
|
||||||
freq = params.frequency[0];
|
|
||||||
detune = params.detune[0];
|
|
||||||
freq = applySemitoneDetuneToFrequency(freq, detune / 100);
|
|
||||||
}
|
|
||||||
let panspread, gainL, gainR;
|
|
||||||
if (params.panspread.length === 1) {
|
|
||||||
panspread = this.voices > 1 ? clamp(params.panspread[0], 0, 1) : 0;
|
|
||||||
gainL = Math.sqrt(0.5 - 0.5 * panspread);
|
|
||||||
gainR = Math.sqrt(0.5 + 0.5 * panspread);
|
|
||||||
}
|
|
||||||
const warpMode = params.warpMode[0];
|
|
||||||
for (let i = 0; i < outL.length; i++) {
|
for (let i = 0; i < outL.length; i++) {
|
||||||
const tablePos = clamp(pv(params.position, i), 0, 1);
|
const detune = pv(parameters.detune, i);
|
||||||
|
const freqspread = pv(parameters.freqspread, i);
|
||||||
|
const tablePos = clamp(pv(parameters.position, i), 0, 1);
|
||||||
const idx = tablePos * (this.numFrames - 1);
|
const idx = tablePos * (this.numFrames - 1);
|
||||||
const fIdx = idx | 0;
|
const fIdx = idx | 0;
|
||||||
const interpT = idx - fIdx;
|
const interpT = idx - fIdx;
|
||||||
const warpAmount = clamp(pv(params.warp, i), 0, 1);
|
const warpAmount = clamp(pv(parameters.warp, i), 0, 1);
|
||||||
panspread ??= this.voices > 1 ? clamp(pv(params.panspread, i), 0, 1) : 0;
|
const warpMode = pv(parameters.warpMode, i);
|
||||||
gainL ??= Math.sqrt(0.5 - 0.5 * panspread);
|
const phaseRand = clamp(pv(parameters.phaserand, i), 0, 1);
|
||||||
gainR ??= Math.sqrt(0.5 + 0.5 * panspread);
|
const panspread = voices > 1 ? clamp(pv(parameters.panspread, i), 0, 1) : 0;
|
||||||
detune ??= pv(params.detune, i);
|
const gain1 = Math.sqrt(0.5 - 0.5 * panspread);
|
||||||
freqspread ??= pv(params.freqspread, i);
|
const gain2 = Math.sqrt(0.5 + 0.5 * panspread);
|
||||||
freq ??= applySemitoneDetuneToFrequency(pv(params.frequency, i), detune / 100); // overall detune
|
let f = pv(parameters.frequency, i);
|
||||||
detuner ??= getDetuner(this.voices, freqspread);
|
f = applySemitoneDetuneToFrequency(f, detune / 100); // overall detune
|
||||||
for (let n = 0; n < this.voices; n++) {
|
const normalizer = 1 / Math.sqrt(voices);
|
||||||
const freqVoice = applyFastDetune(freq, detuner(n)); // voice detune
|
const detuner = getDetuner(voices, freqspread);
|
||||||
const dPhase = freqVoice * INVSR;
|
for (let n = 0; n < voices; n++) {
|
||||||
|
const isOdd = (n & 1) == 1;
|
||||||
|
let gainL = gain1;
|
||||||
|
let gainR = gain2;
|
||||||
|
// invert right and left gain
|
||||||
|
if (isOdd) {
|
||||||
|
gainL = gain2;
|
||||||
|
gainR = gain1;
|
||||||
|
}
|
||||||
|
const fVoice = applySemitoneDetuneToFrequency(f, detuner(n)); // voice detune
|
||||||
|
const dPhase = fVoice * INVSR;
|
||||||
|
const level = this._chooseMip(dPhase);
|
||||||
|
const table = this.tables[level];
|
||||||
|
|
||||||
// warp phase then sample
|
// warp phase then sample
|
||||||
const ph = this._warpPhase(this.phases[n], warpAmount, warpMode);
|
this.phase[n] = this.phase[n] ?? Math.random() * phaseRand;
|
||||||
const s0 = this._sampleFrame(this.table[fIdx], ph);
|
const ph = this._warpPhase(this.phase[n], warpAmount, warpMode);
|
||||||
const s1 = this._sampleFrame(this.table[Math.min(this.numFrames - 1, fIdx + 1)], ph);
|
const s0 = this._sampleFrame(table[fIdx], ph);
|
||||||
|
const s1 = this._sampleFrame(table[Math.min(this.numFrames - 1, fIdx + 1)], ph);
|
||||||
let s = lerp(s0, s1, interpT);
|
let s = lerp(s0, s1, interpT);
|
||||||
if (warpMode === WarpMode.FLIP && this.phases[n] < warpAmount) {
|
if (warpMode === WarpMode.FLIP && this.phase[n] < warpAmount) {
|
||||||
s = -s;
|
s = -s;
|
||||||
}
|
}
|
||||||
outL[i] += s * gainL * this.normalizer;
|
outL[i] += s * gainL * normalizer;
|
||||||
outR[i] += s * gainR * this.normalizer;
|
outR[i] += s * gainR * normalizer;
|
||||||
this.phases[n] = frac(this.phases[n] + dPhase);
|
this.phase[n] = frac(this.phase[n] + dPhase);
|
||||||
// invert right and left gain
|
|
||||||
const tmp = gainL;
|
|
||||||
gainR = gainL;
|
|
||||||
gainL = tmp;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import { Dough } from './dough.mjs';
|
import { Dough } from './dough.mjs';
|
||||||
|
|
||||||
const clamp = (num, min, max) => {
|
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
|
||||||
if (num < min) return min;
|
|
||||||
if (num > max) return max;
|
|
||||||
return num;
|
|
||||||
};
|
|
||||||
|
|
||||||
class DoughProcessor extends AudioWorkletProcessor {
|
class DoughProcessor extends AudioWorkletProcessor {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|||||||
@@ -6,11 +6,7 @@ const PI_DIV_SR = Math.PI / SAMPLE_RATE;
|
|||||||
const ISR = 1 / SAMPLE_RATE;
|
const ISR = 1 / SAMPLE_RATE;
|
||||||
|
|
||||||
let gainCurveFunc = (val) => Math.pow(val, 2);
|
let gainCurveFunc = (val) => Math.pow(val, 2);
|
||||||
const clamp = (num, min, max) => {
|
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
|
||||||
if (num < min) return min;
|
|
||||||
if (num > max) return max;
|
|
||||||
return num;
|
|
||||||
};
|
|
||||||
|
|
||||||
function applyGainCurve(val) {
|
function applyGainCurve(val) {
|
||||||
return gainCurveFunc(val);
|
return gainCurveFunc(val);
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1391,6 +1391,75 @@ exports[`runs examples > example "bpdecay" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "bpdepthfrequency" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 1/16 → 1/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 1/8 → 3/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 3/16 → 1/4 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 1/4 → 5/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 5/16 → 3/8 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 3/8 → 7/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 7/16 → 1/2 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 1/2 → 9/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 9/16 → 5/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 5/8 → 11/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 11/16 → 3/4 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 3/4 → 13/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 13/16 → 7/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 7/8 → 15/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 15/16 → 1/1 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 1/1 → 17/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 17/16 → 9/8 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 9/8 → 19/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 19/16 → 5/4 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 5/4 → 21/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 21/16 → 11/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 11/8 → 23/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 23/16 → 3/2 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 3/2 → 25/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 25/16 → 13/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 13/8 → 27/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 27/16 → 7/4 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 7/4 → 29/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 29/16 → 15/8 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 15/8 → 31/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 31/16 → 2/1 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 2/1 → 33/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 33/16 → 17/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 17/8 → 35/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 35/16 → 9/4 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 9/4 → 37/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 37/16 → 19/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 19/8 → 39/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 39/16 → 5/2 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 5/2 → 41/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 41/16 → 21/8 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 21/8 → 43/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 43/16 → 11/4 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 11/4 → 45/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 45/16 → 23/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 23/8 → 47/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 47/16 → 3/1 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 3/1 → 49/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
"[ 49/16 → 25/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
"[ 25/8 → 51/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
"[ 51/16 → 13/4 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
"[ 13/4 → 53/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
"[ 53/16 → 27/8 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
"[ 27/8 → 55/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
"[ 55/16 → 7/2 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
"[ 7/2 → 57/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
"[ 57/16 → 29/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
"[ 29/8 → 59/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
"[ 59/16 → 15/4 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
"[ 15/4 → 61/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
"[ 61/16 → 31/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
"[ 31/8 → 63/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
"[ 63/16 → 4/1 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "bpenv" example index 0 1`] = `
|
exports[`runs examples > example "bpenv" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/4 | note:c2 s:sawtooth bandf:500 bpattack:0.5 bpenv:4 ]",
|
"[ 0/1 → 1/4 | note:c2 s:sawtooth bandf:500 bpattack:0.5 bpenv:4 ]",
|
||||||
@@ -4786,6 +4855,75 @@ exports[`runs examples > example "hpdecay" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "hpdepthfrequency" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 1/16 → 1/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 1/8 → 3/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 3/16 → 1/4 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 1/4 → 5/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 5/16 → 3/8 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 3/8 → 7/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 7/16 → 1/2 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 1/2 → 9/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 9/16 → 5/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 5/8 → 11/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 11/16 → 3/4 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 3/4 → 13/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 13/16 → 7/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 7/8 → 15/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 15/16 → 1/1 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 1/1 → 17/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 17/16 → 9/8 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 9/8 → 19/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 19/16 → 5/4 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 5/4 → 21/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 21/16 → 11/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 11/8 → 23/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 23/16 → 3/2 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 3/2 → 25/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 25/16 → 13/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 13/8 → 27/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 27/16 → 7/4 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 7/4 → 29/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 29/16 → 15/8 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 15/8 → 31/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 31/16 → 2/1 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 2/1 → 33/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 33/16 → 17/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 17/8 → 35/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 35/16 → 9/4 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 9/4 → 37/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 37/16 → 19/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 19/8 → 39/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 39/16 → 5/2 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 5/2 → 41/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 41/16 → 21/8 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 21/8 → 43/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 43/16 → 11/4 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 11/4 → 45/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 45/16 → 23/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 23/8 → 47/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 47/16 → 3/1 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 3/1 → 49/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
"[ 49/16 → 25/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
"[ 25/8 → 51/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
"[ 51/16 → 13/4 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
"[ 13/4 → 53/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
"[ 53/16 → 27/8 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
"[ 27/8 → 55/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
"[ 55/16 → 7/2 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
"[ 7/2 → 57/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
"[ 57/16 → 29/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
"[ 29/8 → 59/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
"[ 59/16 → 15/4 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
"[ 15/4 → 61/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
"[ 61/16 → 31/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
"[ 31/8 → 63/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
"[ 63/16 → 4/1 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "hpenv" example index 0 1`] = `
|
exports[`runs examples > example "hpenv" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/4 | note:c2 s:sawtooth hcutoff:500 hpattack:0.5 hpenv:4 ]",
|
"[ 0/1 → 1/4 | note:c2 s:sawtooth hcutoff:500 hpattack:0.5 hpenv:4 ]",
|
||||||
@@ -5969,6 +6107,144 @@ exports[`runs examples > example "lpdecay" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "lpdepth" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/16 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 1/16 → 1/8 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 1/8 → 3/16 | note:c# s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 3/16 → 1/4 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 1/4 → 5/16 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 5/16 → 3/8 | note:c4 s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 3/8 → 7/16 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 7/16 → 1/2 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 1/2 → 9/16 | note:c# s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 9/16 → 5/8 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 5/8 → 11/16 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 11/16 → 3/4 | note:c4 s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 3/4 → 13/16 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 13/16 → 7/8 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 7/8 → 15/16 | note:c# s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 15/16 → 1/1 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 1/1 → 17/16 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 17/16 → 9/8 | note:c4 s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 9/8 → 19/16 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 19/16 → 5/4 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 5/4 → 21/16 | note:c# s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 21/16 → 11/8 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 11/8 → 23/16 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 23/16 → 3/2 | note:c4 s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 3/2 → 25/16 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 25/16 → 13/8 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 13/8 → 27/16 | note:c# s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 27/16 → 7/4 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 7/4 → 29/16 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 29/16 → 15/8 | note:c4 s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 15/8 → 31/16 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 31/16 → 2/1 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 2/1 → 33/16 | note:c# s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 33/16 → 17/8 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 17/8 → 35/16 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 35/16 → 9/4 | note:c4 s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 9/4 → 37/16 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 37/16 → 19/8 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 19/8 → 39/16 | note:c# s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 39/16 → 5/2 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 5/2 → 41/16 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 41/16 → 21/8 | note:c4 s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 21/8 → 43/16 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 43/16 → 11/4 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 11/4 → 45/16 | note:c# s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 45/16 → 23/8 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 23/8 → 47/16 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 47/16 → 3/1 | note:c4 s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 3/1 → 49/16 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
"[ 49/16 → 25/8 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
"[ 25/8 → 51/16 | note:c# s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
"[ 51/16 → 13/4 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
"[ 13/4 → 53/16 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
"[ 53/16 → 27/8 | note:c4 s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
"[ 27/8 → 55/16 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
"[ 55/16 → 7/2 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
"[ 7/2 → 57/16 | note:c# s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
"[ 57/16 → 29/8 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
"[ 29/8 → 59/16 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
"[ 59/16 → 15/4 | note:c4 s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
"[ 15/4 → 61/16 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
"[ 61/16 → 31/8 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
"[ 31/8 → 63/16 | note:c# s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
"[ 63/16 → 4/1 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "lpdepthfrequency" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 1/16 → 1/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 1/8 → 3/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 3/16 → 1/4 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 1/4 → 5/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 5/16 → 3/8 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 3/8 → 7/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 7/16 → 1/2 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 1/2 → 9/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 9/16 → 5/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 5/8 → 11/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 11/16 → 3/4 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 3/4 → 13/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 13/16 → 7/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 7/8 → 15/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 15/16 → 1/1 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 1/1 → 17/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 17/16 → 9/8 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 9/8 → 19/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 19/16 → 5/4 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 5/4 → 21/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 21/16 → 11/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 11/8 → 23/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 23/16 → 3/2 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 3/2 → 25/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 25/16 → 13/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 13/8 → 27/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 27/16 → 7/4 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 7/4 → 29/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 29/16 → 15/8 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 15/8 → 31/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 31/16 → 2/1 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 2/1 → 33/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 33/16 → 17/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 17/8 → 35/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 35/16 → 9/4 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 9/4 → 37/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 37/16 → 19/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 19/8 → 39/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 39/16 → 5/2 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 5/2 → 41/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 41/16 → 21/8 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 21/8 → 43/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 43/16 → 11/4 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 11/4 → 45/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 45/16 → 23/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 23/8 → 47/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 47/16 → 3/1 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 3/1 → 49/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
"[ 49/16 → 25/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
"[ 25/8 → 51/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
"[ 51/16 → 13/4 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
"[ 13/4 → 53/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
"[ 53/16 → 27/8 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
"[ 27/8 → 55/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
"[ 55/16 → 7/2 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
"[ 7/2 → 57/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
"[ 57/16 → 29/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
"[ 29/8 → 59/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
"[ 59/16 → 15/4 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
"[ 15/4 → 61/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
"[ 61/16 → 31/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
"[ 31/8 → 63/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
"[ 63/16 → 4/1 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "lpenv" example index 0 1`] = `
|
exports[`runs examples > example "lpenv" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/4 | note:c2 s:sawtooth cutoff:300 lpattack:0.5 lpenv:4 ]",
|
"[ 0/1 → 1/4 | note:c2 s:sawtooth cutoff:300 lpattack:0.5 lpenv:4 ]",
|
||||||
@@ -6157,6 +6433,75 @@ exports[`runs examples > example "lpq" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "lprate" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/16 | note:c s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 1/16 → 1/8 | note:c s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 1/8 → 3/16 | note:c# s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 3/16 → 1/4 | note:c s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 1/4 → 5/16 | note:c s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 5/16 → 3/8 | note:c4 s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 3/8 → 7/16 | note:c s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 7/16 → 1/2 | note:c s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 1/2 → 9/16 | note:c# s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 9/16 → 5/8 | note:c s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 5/8 → 11/16 | note:c s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 11/16 → 3/4 | note:c4 s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 3/4 → 13/16 | note:c s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 13/16 → 7/8 | note:c s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 7/8 → 15/16 | note:c# s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 15/16 → 1/1 | note:c s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 1/1 → 17/16 | note:c s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 17/16 → 9/8 | note:c4 s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 9/8 → 19/16 | note:c s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 19/16 → 5/4 | note:c s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 5/4 → 21/16 | note:c# s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 21/16 → 11/8 | note:c s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 11/8 → 23/16 | note:c s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 23/16 → 3/2 | note:c4 s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 3/2 → 25/16 | note:c s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 25/16 → 13/8 | note:c s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 13/8 → 27/16 | note:c# s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 27/16 → 7/4 | note:c s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 7/4 → 29/16 | note:c s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 29/16 → 15/8 | note:c4 s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 15/8 → 31/16 | note:c s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 31/16 → 2/1 | note:c s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 2/1 → 33/16 | note:c# s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 33/16 → 17/8 | note:c s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 17/8 → 35/16 | note:c s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 35/16 → 9/4 | note:c4 s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 9/4 → 37/16 | note:c s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 37/16 → 19/8 | note:c s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 19/8 → 39/16 | note:c# s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 39/16 → 5/2 | note:c s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 5/2 → 41/16 | note:c s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 41/16 → 21/8 | note:c4 s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 21/8 → 43/16 | note:c s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 43/16 → 11/4 | note:c s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 11/4 → 45/16 | note:c# s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 45/16 → 23/8 | note:c s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 23/8 → 47/16 | note:c s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 47/16 → 3/1 | note:c4 s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 3/1 → 49/16 | note:c s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
"[ 49/16 → 25/8 | note:c s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
"[ 25/8 → 51/16 | note:c# s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
"[ 51/16 → 13/4 | note:c s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
"[ 13/4 → 53/16 | note:c s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
"[ 53/16 → 27/8 | note:c4 s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
"[ 27/8 → 55/16 | note:c s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
"[ 55/16 → 7/2 | note:c s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
"[ 7/2 → 57/16 | note:c# s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
"[ 57/16 → 29/8 | note:c s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
"[ 29/8 → 59/16 | note:c s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
"[ 59/16 → 15/4 | note:c4 s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
"[ 15/4 → 61/16 | note:c s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
"[ 61/16 → 31/8 | note:c s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
"[ 31/8 → 63/16 | note:c# s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
"[ 63/16 → 4/1 | note:c s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "lprelease" example index 0 1`] = `
|
exports[`runs examples > example "lprelease" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/4 | note:c2 s:sawtooth clip:0.5 cutoff:300 lpenv:4 lprelease:0.5 release:0.5 ]",
|
"[ 0/1 → 1/4 | note:c2 s:sawtooth clip:0.5 cutoff:300 lpenv:4 lprelease:0.5 release:0.5 ]",
|
||||||
@@ -6199,6 +6544,75 @@ exports[`runs examples > example "lpsustain" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "lpsync" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/16 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 1/16 → 1/8 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 1/8 → 3/16 | note:c# s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 3/16 → 1/4 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 1/4 → 5/16 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 5/16 → 3/8 | note:c4 s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 3/8 → 7/16 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 7/16 → 1/2 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 1/2 → 9/16 | note:c# s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 9/16 → 5/8 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 5/8 → 11/16 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 11/16 → 3/4 | note:c4 s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 3/4 → 13/16 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 13/16 → 7/8 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 7/8 → 15/16 | note:c# s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 15/16 → 1/1 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 1/1 → 17/16 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 17/16 → 9/8 | note:c4 s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 9/8 → 19/16 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 19/16 → 5/4 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 5/4 → 21/16 | note:c# s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 21/16 → 11/8 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 11/8 → 23/16 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 23/16 → 3/2 | note:c4 s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 3/2 → 25/16 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 25/16 → 13/8 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 13/8 → 27/16 | note:c# s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 27/16 → 7/4 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 7/4 → 29/16 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 29/16 → 15/8 | note:c4 s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 15/8 → 31/16 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 31/16 → 2/1 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 2/1 → 33/16 | note:c# s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 33/16 → 17/8 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 17/8 → 35/16 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 35/16 → 9/4 | note:c4 s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 9/4 → 37/16 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 37/16 → 19/8 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 19/8 → 39/16 | note:c# s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 39/16 → 5/2 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 5/2 → 41/16 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 41/16 → 21/8 | note:c4 s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 21/8 → 43/16 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 43/16 → 11/4 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 11/4 → 45/16 | note:c# s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 45/16 → 23/8 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 23/8 → 47/16 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 47/16 → 3/1 | note:c4 s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 3/1 → 49/16 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
"[ 49/16 → 25/8 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
"[ 25/8 → 51/16 | note:c# s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
"[ 51/16 → 13/4 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
"[ 13/4 → 53/16 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
"[ 53/16 → 27/8 | note:c4 s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
"[ 27/8 → 55/16 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
"[ 55/16 → 7/2 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
"[ 7/2 → 57/16 | note:c# s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
"[ 57/16 → 29/8 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
"[ 29/8 → 59/16 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
"[ 59/16 → 15/4 | note:c4 s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
"[ 15/4 → 61/16 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
"[ 61/16 → 31/8 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
"[ 31/8 → 63/16 | note:c# s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
"[ 63/16 → 4/1 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "lrate" example index 0 1`] = `
|
exports[`runs examples > example "lrate" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/1 | n:0 s:supersquare leslie:1 lrate:1 ]",
|
"[ 0/1 → 1/1 | n:0 s:supersquare leslie:1 lrate:1 ]",
|
||||||
@@ -6888,18 +7302,18 @@ exports[`runs examples > example "nrpv" example index 0 1`] = `
|
|||||||
|
|
||||||
exports[`runs examples > example "octave" example index 0 1`] = `
|
exports[`runs examples > example "octave" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/1 | n:0 s:supersquare octave:3 ]",
|
"[ 0/1 → 1/1 | note:F3 s:supersaw octave:0 ]",
|
||||||
"[ 0/1 → 1/1 | n:4 s:supersquare octave:3 ]",
|
"[ 0/1 → 1/1 | note:C4 s:supersaw octave:0 ]",
|
||||||
"[ 0/1 → 1/1 | n:7 s:supersquare octave:3 ]",
|
"[ 0/1 → 1/1 | note:F4 s:supersaw octave:0 ]",
|
||||||
"[ 1/1 → 2/1 | n:0 s:supersquare octave:4 ]",
|
"[ 1/1 → 2/1 | note:F3 s:supersaw octave:1 ]",
|
||||||
"[ 1/1 → 2/1 | n:4 s:supersquare octave:4 ]",
|
"[ 1/1 → 2/1 | note:C4 s:supersaw octave:1 ]",
|
||||||
"[ 1/1 → 2/1 | n:7 s:supersquare octave:4 ]",
|
"[ 1/1 → 2/1 | note:F4 s:supersaw octave:1 ]",
|
||||||
"[ 2/1 → 3/1 | n:0 s:supersquare octave:5 ]",
|
"[ 2/1 → 3/1 | note:F3 s:supersaw octave:2 ]",
|
||||||
"[ 2/1 → 3/1 | n:4 s:supersquare octave:5 ]",
|
"[ 2/1 → 3/1 | note:C4 s:supersaw octave:2 ]",
|
||||||
"[ 2/1 → 3/1 | n:7 s:supersquare octave:5 ]",
|
"[ 2/1 → 3/1 | note:F4 s:supersaw octave:2 ]",
|
||||||
"[ 3/1 → 4/1 | n:0 s:supersquare octave:6 ]",
|
"[ 3/1 → 4/1 | note:F3 s:supersaw octave:3 ]",
|
||||||
"[ 3/1 → 4/1 | n:4 s:supersquare octave:6 ]",
|
"[ 3/1 → 4/1 | note:C4 s:supersaw octave:3 ]",
|
||||||
"[ 3/1 → 4/1 | n:7 s:supersquare octave:6 ]",
|
"[ 3/1 → 4/1 | note:F4 s:supersaw octave:3 ]",
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"name": "dbpatch",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csv": "^6.3.11"
|
"csv": "^6.3.11"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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);
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
*/
|
||||||
@@ -8,3 +8,34 @@ export function ActionButton({ children, label, labelIsHidden, className, ...but
|
|||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function SpecialActionButton(props) {
|
||||||
|
const { className, ...buttonProps } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ActionButton
|
||||||
|
{...buttonProps}
|
||||||
|
className={cx('bg-background p-2 max-w-[300px] rounded-md hover:opacity-50', className)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ActionInput({ label, className, ...props }) {
|
||||||
|
return (
|
||||||
|
<label className={cx('inline-flex items-center cursor-pointer', className)}>
|
||||||
|
<input {...props} className="sr-only peer" />
|
||||||
|
|
||||||
|
<span className="inline-flex items-center peer-hover:opacity-50">{label}</span>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SpecialActionInput({ className, ...props }) {
|
||||||
|
return (
|
||||||
|
<ActionInput
|
||||||
|
{...props}
|
||||||
|
className={className}
|
||||||
|
label={<span className="bg-background p-2 max-w-[300px] rounded-md">{props.label}</span>}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { errorLogger } from '@strudel/core';
|
||||||
|
import { useSettings, storePrebakeScript } from '../../../settings.mjs';
|
||||||
|
import { SpecialActionInput } from '../button/action-button';
|
||||||
|
|
||||||
|
async function importScript(script) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsText(script);
|
||||||
|
|
||||||
|
reader.onload = () => {
|
||||||
|
const text = reader.result;
|
||||||
|
storePrebakeScript(text);
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.onerror = () => {
|
||||||
|
errorLogger(new Error('failed to import prebake script'), 'importScript');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export function ImportPrebakeScriptButton() {
|
||||||
|
const settings = useSettings();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SpecialActionInput
|
||||||
|
type="file"
|
||||||
|
label="import prebake script"
|
||||||
|
accept=".strudel"
|
||||||
|
onChange={(e) => importScript(e.target.files[0])}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -7,6 +7,8 @@ import { AudioDeviceSelector } from './AudioDeviceSelector.jsx';
|
|||||||
import { AudioEngineTargetSelector } from './AudioEngineTargetSelector.jsx';
|
import { AudioEngineTargetSelector } from './AudioEngineTargetSelector.jsx';
|
||||||
import { confirmDialog } from '../../util.mjs';
|
import { confirmDialog } from '../../util.mjs';
|
||||||
import { DEFAULT_MAX_POLYPHONY, setMaxPolyphony, setMultiChannelOrbits } from '@strudel/webaudio';
|
import { DEFAULT_MAX_POLYPHONY, setMaxPolyphony, setMultiChannelOrbits } from '@strudel/webaudio';
|
||||||
|
import { ActionButton, SpecialActionButton } from '../button/action-button.jsx';
|
||||||
|
import { ImportPrebakeScriptButton } from './ImportPrebakeScriptButton.jsx';
|
||||||
|
|
||||||
function Checkbox({ label, value, onChange, disabled = false }) {
|
function Checkbox({ label, value, onChange, disabled = false }) {
|
||||||
return (
|
return (
|
||||||
@@ -113,6 +115,7 @@ export function SettingsTab({ started }) {
|
|||||||
isTabIndentationEnabled,
|
isTabIndentationEnabled,
|
||||||
isMultiCursorEnabled,
|
isMultiCursorEnabled,
|
||||||
patternAutoStart,
|
patternAutoStart,
|
||||||
|
includePrebakeScriptInShare,
|
||||||
} = useSettings();
|
} = useSettings();
|
||||||
const shouldAlwaysSync = isUdels();
|
const shouldAlwaysSync = isUdels();
|
||||||
const canChangeAudioDevice = AudioContext.prototype.setSinkId != null;
|
const canChangeAudioDevice = AudioContext.prototype.setSinkId != null;
|
||||||
@@ -204,6 +207,15 @@ export function SettingsTab({ started }) {
|
|||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</div>
|
</div>
|
||||||
|
<FormItem label="Prebake">
|
||||||
|
<ImportPrebakeScriptButton />
|
||||||
|
<Checkbox
|
||||||
|
label="Include prebake script in share"
|
||||||
|
onChange={(cbEvent) => settingsMap.setKey('includePrebakeScriptInShare', cbEvent.target.checked)}
|
||||||
|
value={includePrebakeScriptInShare}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
<FormItem label="Keybindings">
|
<FormItem label="Keybindings">
|
||||||
<ButtonGroup
|
<ButtonGroup
|
||||||
value={keybindings}
|
value={keybindings}
|
||||||
@@ -313,8 +325,7 @@ export function SettingsTab({ started }) {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem label="Zen Mode">Try clicking the logo in the top left!</FormItem>
|
<FormItem label="Zen Mode">Try clicking the logo in the top left!</FormItem>
|
||||||
<FormItem label="Reset Settings">
|
<FormItem label="Reset Settings">
|
||||||
<button
|
<SpecialActionButton
|
||||||
className="bg-background p-2 max-w-[300px] rounded-md hover:opacity-50"
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
confirmDialog('Sure?').then((r) => {
|
confirmDialog('Sure?').then((r) => {
|
||||||
if (r) {
|
if (r) {
|
||||||
@@ -325,7 +336,7 @@ export function SettingsTab({ started }) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
restore default settings
|
restore default settings
|
||||||
</button>
|
</SpecialActionButton>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
// Attempt to play the sample and retry every 200ms until 10 attempts have been reached
|
||||||
|
let errMsg;
|
||||||
|
for (let attempt = 0; attempt < 10; attempt++) {
|
||||||
try {
|
try {
|
||||||
// Pre-load the sample by calling onTrigger with a future time
|
// Pre-load the sample by calling onTrigger with a future time
|
||||||
// This triggers the loading but schedules playback for later
|
// This triggers the loading but schedules playback for later
|
||||||
const time = ctx.currentTime + 0.05;
|
const time = ctx.currentTime + 0.05; // Give 50ms for loading
|
||||||
const ref = await onTrigger(time, params, onended);
|
const ref = await onTrigger(time, params, onended);
|
||||||
trigRef.current = ref;
|
trigRef.current = ref;
|
||||||
if (ref?.node) {
|
if (ref?.node) {
|
||||||
connectToDestination(ref.node);
|
connectToDestination(ref.node);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('Failed to trigger sound:', err);
|
errMsg = err;
|
||||||
|
}
|
||||||
|
if (attempt == 9) {
|
||||||
|
console.warn('Failed to trigger sound after 10 attempts' + (errMsg ? `: ${errMsg}` : ''));
|
||||||
|
} else {
|
||||||
|
await wait(200);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { aliasBank, registerSynthSounds, registerZZFXSounds, samples } from '@st
|
|||||||
import { registerSamplesFromDB } from './idbutils.mjs';
|
import { registerSamplesFromDB } from './idbutils.mjs';
|
||||||
import './piano.mjs';
|
import './piano.mjs';
|
||||||
import './files.mjs';
|
import './files.mjs';
|
||||||
|
import { settingsMap } from '@src/settings.mjs';
|
||||||
|
import { evaluate } from '@strudel/transpiler';
|
||||||
|
|
||||||
const { BASE_URL } = import.meta.env;
|
const { BASE_URL } = import.meta.env;
|
||||||
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
|
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
|||||||
|
|
||||||
import { code2hash, getPerformanceTimeSeconds, logger, silence } from '@strudel/core';
|
import { code2hash, getPerformanceTimeSeconds, logger, silence } from '@strudel/core';
|
||||||
import { getDrawContext } from '@strudel/draw';
|
import { getDrawContext } from '@strudel/draw';
|
||||||
import { transpiler } from '@strudel/transpiler';
|
import { evaluate, transpiler } from '@strudel/transpiler';
|
||||||
import {
|
import {
|
||||||
getAudioContextCurrentTime,
|
getAudioContextCurrentTime,
|
||||||
webaudioOutput,
|
webaudioOutput,
|
||||||
@@ -63,11 +63,10 @@ async function getModule(name) {
|
|||||||
const initialCode = `// LOADING`;
|
const initialCode = `// LOADING`;
|
||||||
|
|
||||||
export function useReplContext() {
|
export function useReplContext() {
|
||||||
const { isSyncEnabled, audioEngineTarget } = useSettings();
|
const { isSyncEnabled, audioEngineTarget, prebakeScript, includePrebakeScriptInShare } = useSettings();
|
||||||
const shouldUseWebaudio = audioEngineTarget !== audioEngineTargets.osc;
|
const shouldUseWebaudio = audioEngineTarget !== audioEngineTargets.osc;
|
||||||
const defaultOutput = shouldUseWebaudio ? webaudioOutput : superdirtOutput;
|
const defaultOutput = shouldUseWebaudio ? webaudioOutput : superdirtOutput;
|
||||||
const getTime = shouldUseWebaudio ? getAudioContextCurrentTime : getPerformanceTimeSeconds;
|
const getTime = shouldUseWebaudio ? getAudioContextCurrentTime : getPerformanceTimeSeconds;
|
||||||
|
|
||||||
const init = useCallback(() => {
|
const init = useCallback(() => {
|
||||||
const drawTime = [-2, 2];
|
const drawTime = [-2, 2];
|
||||||
const drawContext = getDrawContext();
|
const drawContext = getDrawContext();
|
||||||
@@ -84,7 +83,12 @@ export function useReplContext() {
|
|||||||
pattern: silence,
|
pattern: silence,
|
||||||
drawTime,
|
drawTime,
|
||||||
drawContext,
|
drawContext,
|
||||||
prebake: async () => Promise.all([modulesLoading, presets]),
|
prebake: async () =>
|
||||||
|
Promise.all([modulesLoading, presets]).then(() => {
|
||||||
|
if (prebakeScript?.length) {
|
||||||
|
return evaluate(prebakeScript ?? '');
|
||||||
|
}
|
||||||
|
}),
|
||||||
onUpdateState: (state) => {
|
onUpdateState: (state) => {
|
||||||
setReplState({ ...state });
|
setReplState({ ...state });
|
||||||
},
|
},
|
||||||
@@ -214,7 +218,13 @@ export function useReplContext() {
|
|||||||
editorRef.current.repl.evaluate(code);
|
editorRef.current.repl.evaluate(code);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleShare = async () => shareCode(replState.code);
|
const handleShare = async () => {
|
||||||
|
let code = replState.code;
|
||||||
|
if (includePrebakeScriptInShare) {
|
||||||
|
code = prebakeScript + '\n' + code;
|
||||||
|
}
|
||||||
|
shareCode(code);
|
||||||
|
};
|
||||||
const context = {
|
const context = {
|
||||||
started,
|
started,
|
||||||
pending,
|
pending,
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { evalScope, hash2code, logger } from '@strudel/core';
|
import { code2hash, evalScope, hash2code, logger } from '@strudel/core';
|
||||||
import { settingPatterns } from '../settings.mjs';
|
import { settingPatterns } from '../settings.mjs';
|
||||||
import { setVersionDefaults } from '@strudel/webaudio';
|
import { setVersionDefaults } from '@strudel/webaudio';
|
||||||
import { getMetadata } from '../metadata_parser';
|
import { getMetadata } from '../metadata_parser';
|
||||||
import { isTauri } from '../tauri.mjs';
|
import { isTauri } from '../tauri.mjs';
|
||||||
import './Repl.css';
|
import './Repl.css';
|
||||||
import { createClient } from '@supabase/supabase-js';
|
import { createClient } from '@supabase/supabase-js';
|
||||||
import { nanoid } from 'nanoid';
|
|
||||||
import { writeText } from '@tauri-apps/plugin-clipboard-manager';
|
import { writeText } from '@tauri-apps/plugin-clipboard-manager';
|
||||||
import { $featuredPatterns /* , loadDBPatterns */ } from '@src/user_pattern_utils.mjs';
|
import { $featuredPatterns /* , loadDBPatterns */ } from '@src/user_pattern_utils.mjs';
|
||||||
|
|
||||||
@@ -109,9 +108,8 @@ export function confirmDialog(msg) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let lastShared;
|
|
||||||
|
|
||||||
//RIP due to SPAM
|
//RIP due to SPAM
|
||||||
|
// let lastShared;
|
||||||
// export async function shareCode(codeToShare) {
|
// export async function shareCode(codeToShare) {
|
||||||
// // const codeToShare = activeCode || code;
|
// // const codeToShare = activeCode || code;
|
||||||
// if (lastShared === codeToShare) {
|
// if (lastShared === codeToShare) {
|
||||||
@@ -146,9 +144,10 @@ let lastShared;
|
|||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
|
|
||||||
export async function shareCode() {
|
export async function shareCode(codeToShare) {
|
||||||
try {
|
try {
|
||||||
const shareUrl = window.location.href;
|
const hash = '#' + code2hash(codeToShare);
|
||||||
|
const shareUrl = window.location.origin + window.location.pathname + hash;
|
||||||
if (isTauri()) {
|
if (isTauri()) {
|
||||||
await writeText(shareUrl);
|
await writeText(shareUrl);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -45,11 +45,13 @@ export const defaultSettings = {
|
|||||||
isPanelOpen: true,
|
isPanelOpen: true,
|
||||||
togglePanelTrigger: 'click', //click | hover
|
togglePanelTrigger: 'click', //click | hover
|
||||||
userPatterns: '{}',
|
userPatterns: '{}',
|
||||||
|
prebakeScript: '',
|
||||||
audioEngineTarget: audioEngineTargets.webaudio,
|
audioEngineTarget: audioEngineTargets.webaudio,
|
||||||
isButtonRowHidden: false,
|
isButtonRowHidden: false,
|
||||||
isCSSAnimationDisabled: false,
|
isCSSAnimationDisabled: false,
|
||||||
maxPolyphony: 128,
|
maxPolyphony: 128,
|
||||||
multiChannelOrbits: false,
|
multiChannelOrbits: false,
|
||||||
|
includePrebakeScriptInShare: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
let search = null;
|
let search = null;
|
||||||
@@ -96,6 +98,7 @@ export function useSettings() {
|
|||||||
isPanelOpen: parseBoolean(state.isPanelOpen),
|
isPanelOpen: parseBoolean(state.isPanelOpen),
|
||||||
userPatterns: userPatterns,
|
userPatterns: userPatterns,
|
||||||
multiChannelOrbits: parseBoolean(state.multiChannelOrbits),
|
multiChannelOrbits: parseBoolean(state.multiChannelOrbits),
|
||||||
|
includePrebakeScriptInShare: parseBoolean(state.includePrebakeScriptInShare),
|
||||||
patternAutoStart: isUdels()
|
patternAutoStart: isUdels()
|
||||||
? false
|
? false
|
||||||
: state.patternAutoStart === undefined
|
: state.patternAutoStart === undefined
|
||||||
@@ -108,6 +111,8 @@ export const setActiveFooter = (tab) => settingsMap.setKey('activeFooter', tab);
|
|||||||
export const setPanelPinned = (bool) => settingsMap.setKey('isPanelPinned', bool);
|
export const setPanelPinned = (bool) => settingsMap.setKey('isPanelPinned', bool);
|
||||||
export const setIsPanelOpened = (bool) => settingsMap.setKey('isPanelOpen', bool);
|
export const setIsPanelOpened = (bool) => settingsMap.setKey('isPanelOpen', bool);
|
||||||
|
|
||||||
|
export const storePrebakeScript = (script) => settingsMap.setKey('prebakeScript', script);
|
||||||
|
|
||||||
export const setIsZen = (active) => settingsMap.setKey('isZen', !!active);
|
export const setIsZen = (active) => settingsMap.setKey('isZen', !!active);
|
||||||
|
|
||||||
const patternSetting = (key) =>
|
const patternSetting = (key) =>
|
||||||
|
|||||||
Reference in New Issue
Block a user