mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-14 06:43:47 -04:00
Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b312ff63a9 | |||
| 93bc353b6b | |||
| 14a0dea826 | |||
| 009f5445be | |||
| 3d37c73103 | |||
| 0b0f9fe13b | |||
| 1d189763b2 | |||
| a4b55776d2 | |||
| 0287be547a | |||
| 6a83ede33d | |||
| 1e3f546918 | |||
| faa60be211 | |||
| dde2b9c6f8 | |||
| 382dce65df | |||
| 63f4f202f1 | |||
| 366637026b | |||
| 608dfd515e | |||
| 9bd7b66228 | |||
| 5e4eb7fc53 | |||
| 45bccf13ff | |||
| 65f1760fad | |||
| b8d2d02466 | |||
| 058cb36640 | |||
| 5c3501caa4 | |||
| 1341bd9c92 | |||
| 760d1abec0 | |||
| 2b44fc48a4 | |||
| 99779171f5 | |||
| 64ebfb57c3 | |||
| 099d321dbd | |||
| 59bdfd2cb8 | |||
| 56d3436fda | |||
| 440ce0cd3b | |||
| 98346261ed | |||
| 38e4718826 | |||
| 38908baccd | |||
| 8d02d4270a | |||
| 9cf1cd2656 | |||
| 4c079b6ebe | |||
| e9e2e881e5 | |||
| 685b4f172b | |||
| 53e50d81b0 | |||
| 8f58d5788f | |||
| 306a6db8dc | |||
| 757d78252b | |||
| 6f2e026e4a | |||
| 22f114be59 | |||
| f993056fb1 | |||
| fdbbcd2dd2 | |||
| 7bec92ca99 |
@@ -1,62 +0,0 @@
|
||||
name: Tauri Builder
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [macos-latest, ubuntu-latest, windows-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
rust_target: x86_64-unknown-linux-gnu
|
||||
- os: macos-latest
|
||||
rust_target: x86_64-apple-darwin
|
||||
- os: macos-latest
|
||||
rust_target: aarch64-apple-darwin
|
||||
- os: windows-latest
|
||||
rust_target: x86_64-pc-windows-msvc
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8.6.2
|
||||
|
||||
- name: Node.js setup
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: latest
|
||||
# node-version-file: '.nvmrc'
|
||||
|
||||
- name: Install Rust (Stable)
|
||||
run:
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||
|
||||
- name: Install dependencies (ubuntu only)
|
||||
if: matrix.platform == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf libasound2-dev
|
||||
|
||||
- name: Install app dependencies from lockfile and build web
|
||||
run: pnpm install
|
||||
|
||||
- name: Build the app
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
# tauri-action replaces \_\_VERSION\_\_ with the app version
|
||||
tagName: ${{ github.ref_name }}
|
||||
releaseName: "Strudel v__VERSION__"
|
||||
releaseBody: |
|
||||
See the assets to download this version and install.
|
||||
releaseDraft: true
|
||||
prerelease: false
|
||||
+2
-1
@@ -71,6 +71,7 @@
|
||||
"jsdoc-json": "^2.0.2",
|
||||
"lerna": "^8.1.9",
|
||||
"prettier": "^3.4.2",
|
||||
"vitest": "^3.0.4"
|
||||
"vitest": "^3.0.4",
|
||||
"vite-plugin-bundle-audioworklet": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/codemirror",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.2",
|
||||
"description": "Codemirror Extensions for Strudel",
|
||||
"main": "index.mjs",
|
||||
"publishConfig": {
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @name Cutie Pi
|
||||
/*
|
||||
* Cutie Pi
|
||||
* by Switch Angel
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @name androidstudio
|
||||
/*
|
||||
* androidstudio
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @name Atom One
|
||||
/*
|
||||
* Atom One
|
||||
* Atom One dark syntax theme
|
||||
*
|
||||
* https://github.com/atom/one-dark-syntax
|
||||
|
||||
+2
-3
@@ -1,6 +1,5 @@
|
||||
/**
|
||||
* @name darcula
|
||||
* @author darcula
|
||||
/*
|
||||
* darcula
|
||||
* Name: IntelliJ IDEA darcula theme
|
||||
* From IntelliJ IDEA by JetBrains
|
||||
*/
|
||||
|
||||
+1
-2
@@ -1,6 +1,5 @@
|
||||
/**
|
||||
/*
|
||||
* @name dracula
|
||||
* @author dracula
|
||||
* Michael Kaminsky (http://github.com/mkaminsky11)
|
||||
* Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme)
|
||||
*/
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @name duotone
|
||||
* @author Bram de Haan
|
||||
/*
|
||||
* duotone
|
||||
* author Bram de Haan
|
||||
* by Bram de Haan, adapted from DuoTone themes by Simurai (http://simurai.com/projects/2016/01/01/duotone-themes)
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @name duotone
|
||||
* @author Bram de Haan
|
||||
/*
|
||||
* duotone
|
||||
* author Bram de Haan
|
||||
* by Bram de Haan, adapted from DuoTone themes by Simurai (http://simurai.com/projects/2016/01/01/duotone-themes)
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
|
||||
-3
@@ -1,6 +1,3 @@
|
||||
/**
|
||||
* @name github
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
|
||||
-3
@@ -1,6 +1,3 @@
|
||||
/**
|
||||
* @name github
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @name Atom One
|
||||
/*
|
||||
* Atom One
|
||||
* Atom One dark syntax theme
|
||||
*
|
||||
* https://github.com/atom/one-dark-syntax
|
||||
|
||||
+3
-4
@@ -1,7 +1,6 @@
|
||||
/**
|
||||
* @name gruvbox-dark
|
||||
* @author morhetz
|
||||
* Name: Gruvbox
|
||||
/*
|
||||
* gruvbox-dark
|
||||
* author morhetz
|
||||
* From github.com/codemirror/codemirror5/blob/master/theme/gruvbox-dark.css
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
|
||||
+3
-4
@@ -1,7 +1,6 @@
|
||||
/**
|
||||
* @name gruvbox-light
|
||||
* @author morhetz
|
||||
* Name: Gruvbox
|
||||
/*
|
||||
* gruvbox-light
|
||||
* author morhetz
|
||||
* From github.com/codemirror/codemirror5/blob/master/theme/gruvbox-light.css
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @name Atom One
|
||||
/*
|
||||
* Atom One
|
||||
* Atom One dark syntax theme
|
||||
*
|
||||
* https://github.com/atom/one-dark-syntax
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @name Atom One
|
||||
/*
|
||||
* Atom One
|
||||
* Atom One dark syntax theme
|
||||
*
|
||||
* https://github.com/atom/one-dark-syntax
|
||||
|
||||
@@ -445,6 +445,32 @@ export const { coarse } = registerControl('coarse');
|
||||
*/
|
||||
export const { drive } = registerControl('drive');
|
||||
|
||||
/**
|
||||
* Create byte beats with custom expressions
|
||||
*
|
||||
* @name byteBeatExpression
|
||||
* @synonyms bbexpr
|
||||
*
|
||||
* @param {number | Pattern} byteBeatExpression bitwise expression for creating bytebeat
|
||||
* @example
|
||||
* s("bytebeat").bbexpr('t*(t>>15^t>>66)')
|
||||
*
|
||||
*/
|
||||
export const { byteBeatExpression, bbexpr } = registerControl('byteBeatExpression', 'bbexpr');
|
||||
|
||||
/**
|
||||
* Create byte beats with custom expressions
|
||||
*
|
||||
* @name byteBeatStartTime
|
||||
* @synonyms bbst
|
||||
*
|
||||
* @param {number | Pattern} byteBeatStartTime in samples (t)
|
||||
* @example
|
||||
* note("c3!8".add("{0 0 12 0 7 5 3}%8")).s("bytebeat:5").bbst("<3 1>".mul(10000))._scope()
|
||||
*
|
||||
*/
|
||||
export const { byteBeatStartTime, bbst } = registerControl('byteBeatStartTime', 'bbst');
|
||||
|
||||
/**
|
||||
* Allows you to set the output channels on the interface
|
||||
*
|
||||
@@ -458,6 +484,41 @@ export const { drive } = registerControl('drive');
|
||||
*/
|
||||
export const { channels, ch } = registerControl('channels', 'ch');
|
||||
|
||||
/**
|
||||
* controls the pulsewidth of the pulse oscillator
|
||||
*
|
||||
* @name pw
|
||||
* @param {number | Pattern} pulsewidth
|
||||
* @example
|
||||
* note("{f a c e}%16").s("pulse").pw(".8:1:.2")
|
||||
* @example
|
||||
* n(run(8)).scale("D:pentatonic").s("pulse").pw("0 .75 .5 1")
|
||||
*/
|
||||
export const { pw } = registerControl(['pw', 'pwrate', 'pwsweep']);
|
||||
|
||||
/**
|
||||
* controls the lfo rate for the pulsewidth of the pulse oscillator
|
||||
*
|
||||
* @name pwrate
|
||||
* @param {number | Pattern} rate
|
||||
* @example
|
||||
* n(run(8)).scale("D:pentatonic").s("pulse").pw("0.5").pwrate("<5 .1 25>").pwsweep("<0.3 .8>")
|
||||
|
||||
*
|
||||
*/
|
||||
export const { pwrate } = registerControl('pwrate');
|
||||
|
||||
/**
|
||||
* controls the lfo sweep for the pulsewidth of the pulse oscillator
|
||||
*
|
||||
* @name pwsweep
|
||||
* @param {number | Pattern} sweep
|
||||
* @example
|
||||
* n(run(8)).scale("D:pentatonic").s("pulse").pw("0.5").pwrate("<5 .1 25>").pwsweep("<0.3 .8>")
|
||||
*
|
||||
*/
|
||||
export const { pwsweep } = registerControl('pwsweep');
|
||||
|
||||
/**
|
||||
* Phaser audio effect that approximates popular guitar pedals.
|
||||
*
|
||||
@@ -1461,7 +1522,7 @@ export const { vowel } = registerControl('vowel');
|
||||
* @name waveloss
|
||||
*/
|
||||
export const { waveloss } = registerControl('waveloss');
|
||||
/*
|
||||
/**
|
||||
* Noise crackle density
|
||||
*
|
||||
* @name density
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/core",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.2",
|
||||
"description": "Port of Tidal Cycles to JavaScript",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -2441,9 +2441,14 @@ const _chunk = function (n, func, pat, back = false, fast = false) {
|
||||
return pat.when(binary_pat, func);
|
||||
};
|
||||
|
||||
export const { chunk, slowchunk, slowChunk } = register(['chunk', 'slowchunk', 'slowChunk'], function (n, func, pat) {
|
||||
return _chunk(n, func, pat, false, false);
|
||||
});
|
||||
export const { chunk, slowchunk, slowChunk } = register(
|
||||
['chunk', 'slowchunk', 'slowChunk'],
|
||||
function (n, func, pat) {
|
||||
return _chunk(n, func, pat, false, false);
|
||||
},
|
||||
true,
|
||||
true,
|
||||
);
|
||||
|
||||
/**
|
||||
* Like `chunk`, but cycles through the parts in reverse order. Known as chunk' in tidalcycles
|
||||
@@ -2455,9 +2460,14 @@ export const { chunk, slowchunk, slowChunk } = register(['chunk', 'slowchunk', '
|
||||
* "0 1 2 3".chunkBack(4, x=>x.add(7))
|
||||
* .scale("A:minor").note()
|
||||
*/
|
||||
export const { chunkBack, chunkback } = register(['chunkBack', 'chunkback'], function (n, func, pat) {
|
||||
return _chunk(n, func, pat, true);
|
||||
});
|
||||
export const { chunkBack, chunkback } = register(
|
||||
['chunkBack', 'chunkback'],
|
||||
function (n, func, pat) {
|
||||
return _chunk(n, func, pat, true);
|
||||
},
|
||||
true,
|
||||
true,
|
||||
);
|
||||
|
||||
/**
|
||||
* Like `chunk`, but the cycles of the source pattern aren't repeated
|
||||
@@ -2471,9 +2481,14 @@ export const { chunkBack, chunkback } = register(['chunkBack', 'chunkback'], fun
|
||||
* .fastChunk(4, x => x.color('red')).slow(2)
|
||||
* .scale("C2:major").note()
|
||||
*/
|
||||
export const { fastchunk, fastChunk } = register(['fastchunk', 'fastChunk'], function (n, func, pat) {
|
||||
return _chunk(n, func, pat, false, true);
|
||||
});
|
||||
export const { fastchunk, fastChunk } = register(
|
||||
['fastchunk', 'fastChunk'],
|
||||
function (n, func, pat) {
|
||||
return _chunk(n, func, pat, false, true);
|
||||
},
|
||||
true,
|
||||
true,
|
||||
);
|
||||
|
||||
// TODO - redefine elsewhere in terms of mask
|
||||
export const bypass = register(
|
||||
|
||||
@@ -156,8 +156,7 @@ export const pickmodReset = register('pickmodReset', function (lookup, pat) {
|
||||
return _pick(lookup, pat, true).resetJoin();
|
||||
});
|
||||
|
||||
/**
|
||||
/** * Picks patterns (or plain values) either from a list (by index) or a lookup table (by name).
|
||||
/** Picks patterns (or plain values) either from a list (by index) or a lookup table (by name).
|
||||
* Similar to `pick`, but cycles are squeezed into the target ('inhabited') pattern.
|
||||
* @name inhabit
|
||||
* @synonyms pickSqueeze
|
||||
|
||||
@@ -279,9 +279,9 @@ const _rearrangeWith = (ipat, n, pat) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* @name shuffle
|
||||
* Slices a pattern into the given number of parts, then plays those parts in random order.
|
||||
* Each part will be played exactly once per cycle.
|
||||
* @name shuffle
|
||||
* @example
|
||||
* note("c d e f").sound("piano").shuffle(4)
|
||||
* @example
|
||||
@@ -292,9 +292,9 @@ export const shuffle = register('shuffle', (n, pat) => {
|
||||
});
|
||||
|
||||
/**
|
||||
* @name scramble
|
||||
* Slices a pattern into the given number of parts, then plays those parts at random. Similar to `shuffle`,
|
||||
* but parts might be played more than once, or not at all, per cycle.
|
||||
* @name scramble
|
||||
* @example
|
||||
* note("c d e f").sound("piano").scramble(4)
|
||||
* @example
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/csound",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.3",
|
||||
"description": "csound bindings for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/draw",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.2",
|
||||
"description": "Helpers for drawing with Strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/gamepad",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.2",
|
||||
"description": "Gamepad Inputs for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/hydra",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.2",
|
||||
"description": "Hydra integration for strudel",
|
||||
"main": "hydra.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/midi",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.3",
|
||||
"description": "Midi API for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/mini",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.2",
|
||||
"description": "Mini notation for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/motion",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.2",
|
||||
"description": "DeviceMotion API for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/mqtt",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.2",
|
||||
"description": "MQTT API for strudel",
|
||||
"main": "mqtt.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/osc",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.2",
|
||||
"description": "OSC messaging for strudel",
|
||||
"main": "osc.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/repl",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.3",
|
||||
"description": "Strudel REPL as a Web Component",
|
||||
"module": "index.mjs",
|
||||
"publishConfig": {
|
||||
@@ -46,6 +46,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-replace": "^6.0.2",
|
||||
"vite": "^6.0.11"
|
||||
"vite": "^6.0.11",
|
||||
"vite-plugin-bundle-audioworklet": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import { resolve } from 'path';
|
||||
import replace from '@rollup/plugin-replace';
|
||||
import bundleAudioWorkletPlugin from 'vite-plugin-bundle-audioworklet';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
base: './',
|
||||
plugins: [],
|
||||
plugins: [bundleAudioWorkletPlugin()],
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'index.mjs'),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/serial",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.2",
|
||||
"description": "Webserial API for strudel",
|
||||
"main": "serial.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/soundfonts",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.3",
|
||||
"description": "Soundsfont support for strudel",
|
||||
"main": "index.mjs",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "superdough",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.3",
|
||||
"description": "simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
@@ -32,7 +32,8 @@
|
||||
},
|
||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||
"devDependencies": {
|
||||
"vite": "^6.0.11"
|
||||
"vite": "^6.0.11",
|
||||
"vite-plugin-bundle-audioworklet": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"nanostores": "^0.11.3"
|
||||
|
||||
@@ -8,7 +8,7 @@ import './feedbackdelay.mjs';
|
||||
import './reverb.mjs';
|
||||
import './vowel.mjs';
|
||||
import { clamp, nanFallback, _mod } from './util.mjs';
|
||||
import workletsUrl from './worklets.mjs?worker&url';
|
||||
import workletsUrl from './worklets.mjs?audioworklet';
|
||||
import { createFilter, gainNode, getCompressor, getWorklet } from './helpers.mjs';
|
||||
import { map } from 'nanostores';
|
||||
import { logger } from './logger.mjs';
|
||||
@@ -22,6 +22,12 @@ let maxPolyphony = DEFAULT_MAX_POLYPHONY;
|
||||
export function setMaxPolyphony(polyphony) {
|
||||
maxPolyphony = parseInt(polyphony) ?? DEFAULT_MAX_POLYPHONY;
|
||||
}
|
||||
|
||||
let multiChannelOrbits = false;
|
||||
export function setMultiChannelOrbits(bool) {
|
||||
multiChannelOrbits = bool == true;
|
||||
}
|
||||
|
||||
export const soundMap = map();
|
||||
|
||||
export function registerSound(key, onTrigger, data = {}) {
|
||||
@@ -131,6 +137,7 @@ const defaultDefaultValues = {
|
||||
shapevol: 1,
|
||||
distortvol: 1,
|
||||
delay: 0,
|
||||
byteBeatExpression: '0',
|
||||
delayfeedback: 0.5,
|
||||
delaytime: 0.25,
|
||||
orbit: 1,
|
||||
@@ -195,8 +202,15 @@ function loadWorklets() {
|
||||
|
||||
// this function should be called on first user interaction (to avoid console warning)
|
||||
export async function initAudio(options = {}) {
|
||||
const { disableWorklets = false, maxPolyphony, audioDeviceName = DEFAULT_AUDIO_DEVICE_NAME } = options;
|
||||
const {
|
||||
disableWorklets = false,
|
||||
maxPolyphony,
|
||||
audioDeviceName = DEFAULT_AUDIO_DEVICE_NAME,
|
||||
multiChannelOrbits = false,
|
||||
} = options;
|
||||
|
||||
setMaxPolyphony(maxPolyphony);
|
||||
setMultiChannelOrbits(multiChannelOrbits);
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
@@ -277,7 +291,7 @@ export const connectToDestination = (input, channels = [0, 1]) => {
|
||||
});
|
||||
stereoMix.connect(splitter);
|
||||
channels.forEach((ch, i) => {
|
||||
splitter.connect(channelMerger, i % stereoMix.channelCount, clamp(ch, 0, ctx.destination.channelCount - 1));
|
||||
splitter.connect(channelMerger, i % stereoMix.channelCount, ch % ctx.destination.channelCount);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -290,7 +304,7 @@ export const panic = () => {
|
||||
channelMerger == null;
|
||||
};
|
||||
|
||||
function getDelay(orbit, delaytime, delayfeedback, t) {
|
||||
function getDelay(orbit, delaytime, delayfeedback, t, channels) {
|
||||
if (delayfeedback > maxfeedback) {
|
||||
//logger(`delayfeedback was clamped to ${maxfeedback} to save your ears`);
|
||||
}
|
||||
@@ -299,7 +313,7 @@ function getDelay(orbit, delaytime, delayfeedback, t) {
|
||||
const ac = getAudioContext();
|
||||
const dly = ac.createFeedbackDelay(1, delaytime, delayfeedback);
|
||||
dly.start?.(t); // for some reason, this throws when audion extension is installed..
|
||||
connectToDestination(dly, [0, 1]);
|
||||
connectToDestination(dly, channels);
|
||||
delays[orbit] = dly;
|
||||
}
|
||||
delays[orbit].delayTime.value !== delaytime && delays[orbit].delayTime.setValueAtTime(delaytime, t);
|
||||
@@ -307,16 +321,9 @@ function getDelay(orbit, delaytime, delayfeedback, t) {
|
||||
return delays[orbit];
|
||||
}
|
||||
|
||||
function getPhaser(time, end, frequency = 1, depth = 0.5, centerFrequency = 1000, sweep = 2000) {
|
||||
//gain
|
||||
const ac = getAudioContext();
|
||||
const lfoGain = ac.createGain();
|
||||
lfoGain.gain.value = sweep * 2;
|
||||
// centerFrequency = centerFrequency * 2;
|
||||
// sweep = sweep * 1.5;
|
||||
|
||||
const lfo = getWorklet(ac, 'lfo-processor', {
|
||||
frequency,
|
||||
export function getLfo(audioContext, time, end, properties = {}) {
|
||||
return getWorklet(audioContext, 'lfo-processor', {
|
||||
frequency: 1,
|
||||
depth: 1,
|
||||
skew: 0,
|
||||
phaseoffset: 0,
|
||||
@@ -324,8 +331,13 @@ function getPhaser(time, end, frequency = 1, depth = 0.5, centerFrequency = 1000
|
||||
end,
|
||||
shape: 1,
|
||||
dcoffset: -0.5,
|
||||
...properties,
|
||||
});
|
||||
lfo.connect(lfoGain);
|
||||
}
|
||||
|
||||
function getPhaser(time, end, frequency = 1, depth = 0.5, centerFrequency = 1000, sweep = 2000) {
|
||||
const ac = getAudioContext();
|
||||
const lfoGain = getLfo(ac, time, end, { frequency, depth: sweep * 2 });
|
||||
|
||||
//filters
|
||||
const numStages = 2; //num of filters in series
|
||||
@@ -356,12 +368,12 @@ function getFilterType(ftype) {
|
||||
|
||||
let reverbs = {};
|
||||
let hasChanged = (now, before) => now !== undefined && now !== before;
|
||||
function getReverb(orbit, duration, fade, lp, dim, ir) {
|
||||
function getReverb(orbit, duration, fade, lp, dim, ir, channels) {
|
||||
// If no reverb has been created for a given orbit, create one
|
||||
if (!reverbs[orbit]) {
|
||||
const ac = getAudioContext();
|
||||
const reverb = ac.createReverb(duration, fade, lp, dim, ir);
|
||||
connectToDestination(reverb, [0, 1]);
|
||||
connectToDestination(reverb, channels);
|
||||
reverbs[orbit] = reverb;
|
||||
}
|
||||
if (
|
||||
@@ -428,8 +440,13 @@ export function resetGlobalEffects() {
|
||||
}
|
||||
|
||||
let activeSoundSources = new Map();
|
||||
//music programs/audio gear usually increments inputs/outputs from 1, we need to subtract 1 from the input because the webaudio API channels start at 0
|
||||
|
||||
export const superdough = async (value, t, hapDuration) => {
|
||||
function mapChannelNumbers(channels) {
|
||||
return (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1);
|
||||
}
|
||||
|
||||
export const superdough = async (value, t, hapDuration, cps) => {
|
||||
const ac = getAudioContext();
|
||||
t = typeof t === 'string' && t.startsWith('=') ? Number(t.slice(1)) : ac.currentTime + t;
|
||||
let { stretch } = value;
|
||||
@@ -490,7 +507,7 @@ export const superdough = async (value, t, hapDuration) => {
|
||||
bpsustain,
|
||||
bprelease,
|
||||
bandq = getDefaultValue('bandq'),
|
||||
channels = getDefaultValue('channels'),
|
||||
|
||||
//phaser
|
||||
phaserrate: phaser,
|
||||
phaserdepth = getDefaultValue('phaserdepth'),
|
||||
@@ -526,6 +543,11 @@ export const superdough = async (value, t, hapDuration) => {
|
||||
compressorRelease,
|
||||
} = value;
|
||||
|
||||
const orbitChannels = mapChannelNumbers(
|
||||
multiChannelOrbits && orbit > 0 ? [orbit * 2 - 1, orbit * 2] : getDefaultValue('channels'),
|
||||
);
|
||||
const channels = value.channels != null ? mapChannelNumbers(value.channels) : orbitChannels;
|
||||
|
||||
gain = applyGainCurve(nanFallback(gain, 1));
|
||||
postgain = applyGainCurve(postgain);
|
||||
shapevol = applyGainCurve(shapevol);
|
||||
@@ -547,9 +569,6 @@ export const superdough = async (value, t, hapDuration) => {
|
||||
activeSoundSources.delete(chainID);
|
||||
}
|
||||
|
||||
//music programs/audio gear usually increments inputs/outputs from 1, so imitate that behavior
|
||||
channels = (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1);
|
||||
|
||||
let audioNodes = [];
|
||||
|
||||
if (bank && s) {
|
||||
@@ -560,7 +579,7 @@ export const superdough = async (value, t, hapDuration) => {
|
||||
// get source AudioNode
|
||||
let sourceNode;
|
||||
if (source) {
|
||||
sourceNode = source(t, value, hapDuration);
|
||||
sourceNode = source(t, value, hapDuration, cps);
|
||||
} else if (getSound(s)) {
|
||||
const { onTrigger } = getSound(s);
|
||||
const onEnded = () => {
|
||||
@@ -699,7 +718,7 @@ export const superdough = async (value, t, hapDuration) => {
|
||||
// delay
|
||||
let delaySend;
|
||||
if (delay > 0 && delaytime > 0 && delayfeedback > 0) {
|
||||
const delyNode = getDelay(orbit, delaytime, delayfeedback, t);
|
||||
const delyNode = getDelay(orbit, delaytime, delayfeedback, t, orbitChannels);
|
||||
delaySend = effectSend(post, delyNode, delay);
|
||||
audioNodes.push(delaySend);
|
||||
}
|
||||
@@ -717,7 +736,7 @@ export const superdough = async (value, t, hapDuration) => {
|
||||
}
|
||||
roomIR = await loadBuffer(url, ac, ir, 0);
|
||||
}
|
||||
const reverbNode = getReverb(orbit, roomsize, roomfade, roomlp, roomdim, roomIR);
|
||||
const reverbNode = getReverb(orbit, roomsize, roomfade, roomlp, roomdim, roomIR, orbitChannels);
|
||||
reverbSend = effectSend(post, reverbNode, room);
|
||||
audioNodes.push(reverbSend);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { clamp, midiToFreq, noteToMidi } from './util.mjs';
|
||||
import { registerSound, getAudioContext } from './superdough.mjs';
|
||||
import { registerSound, getAudioContext, getLfo } from './superdough.mjs';
|
||||
import {
|
||||
applyFM,
|
||||
gainNode,
|
||||
@@ -26,6 +26,9 @@ const getFrequencyFromValue = (value) => {
|
||||
return Number(freq);
|
||||
};
|
||||
function destroyAudioWorkletNode(node) {
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
node.disconnect();
|
||||
node.parameters.get('end')?.setValueAtTime(0, 0);
|
||||
}
|
||||
@@ -141,11 +144,100 @@ export function registerSynthSounds() {
|
||||
{ prebake: true, type: 'synth' },
|
||||
);
|
||||
|
||||
registerSound(
|
||||
'bytebeat',
|
||||
(begin, value, onended) => {
|
||||
const defaultBeats = [
|
||||
'(t%255 >= t/255%255)*255',
|
||||
'(t*(t*8%60 <= 300)|(-t)*(t*4%512 < 256))+t/400',
|
||||
't',
|
||||
't*(t >> 10^t)',
|
||||
't&128',
|
||||
't&t>>8',
|
||||
'((t%255+t%128+t%64+t%32+t%16+t%127.8+t%64.8+t%32.8+t%16.8)/3)',
|
||||
'((t%64+t%63.8+t%64.15+t%64.35+t%63.5)/1.25)',
|
||||
'(t&(t>>7)-t)',
|
||||
'(sin(t*PI/128)*127+127)',
|
||||
'((t^t/2+t+64*(sin((t*PI/64)+(t*PI/32768))+64))%128*2)',
|
||||
'((t^t/2+t+64*(cos >> 0))%127.85*2)',
|
||||
'((t^t/2+t+64)%128*2)',
|
||||
'(((t * .25)^(t * .25)/100+(t * .25))%128)*2',
|
||||
'((t^t/2+t+64)%7 * 24)',
|
||||
];
|
||||
const { n = 0 } = value;
|
||||
const frequency = getFrequencyFromValue(value);
|
||||
const { byteBeatExpression = defaultBeats[n % defaultBeats.length], byteBeatStartTime } = value;
|
||||
|
||||
const ac = getAudioContext();
|
||||
|
||||
let { duration } = value;
|
||||
const [attack, decay, sustain, release] = getADSRValues(
|
||||
[value.attack, value.decay, value.sustain, value.release],
|
||||
'linear',
|
||||
[0.001, 0.05, 0.6, 0.01],
|
||||
);
|
||||
const holdend = begin + duration;
|
||||
const end = holdend + release + 0.01;
|
||||
|
||||
let o = getWorklet(
|
||||
ac,
|
||||
'byte-beat-processor',
|
||||
{
|
||||
frequency,
|
||||
begin,
|
||||
end,
|
||||
},
|
||||
{
|
||||
outputChannelCount: [2],
|
||||
},
|
||||
);
|
||||
|
||||
o.port.postMessage({ codeText: byteBeatExpression, byteBeatStartTime, frequency });
|
||||
|
||||
let envGain = gainNode(1);
|
||||
envGain = o.connect(envGain);
|
||||
|
||||
getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 1, begin, holdend, 'linear');
|
||||
|
||||
let timeoutNode = webAudioTimeout(
|
||||
ac,
|
||||
() => {
|
||||
destroyAudioWorkletNode(o);
|
||||
envGain.disconnect();
|
||||
onended();
|
||||
},
|
||||
begin,
|
||||
end,
|
||||
);
|
||||
|
||||
return {
|
||||
node: envGain,
|
||||
stop: (time) => {
|
||||
timeoutNode.stop(time);
|
||||
},
|
||||
};
|
||||
},
|
||||
{ prebake: true, type: 'synth' },
|
||||
);
|
||||
|
||||
registerSound(
|
||||
'pulse',
|
||||
(begin, value, onended) => {
|
||||
const ac = getAudioContext();
|
||||
let { duration, n: pulsewidth = 0.5 } = value;
|
||||
let { pwrate, pwsweep } = value;
|
||||
if (pwsweep == null) {
|
||||
if (pwrate != null) {
|
||||
pwsweep = 0.3;
|
||||
} else {
|
||||
pwsweep = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (pwrate == null && pwsweep != null) {
|
||||
pwrate = 1;
|
||||
}
|
||||
|
||||
let { duration, pw: pulsewidth = 0.5 } = value;
|
||||
const frequency = getFrequencyFromValue(value);
|
||||
|
||||
const [attack, decay, sustain, release] = getADSRValues(
|
||||
@@ -153,10 +245,8 @@ export function registerSynthSounds() {
|
||||
'linear',
|
||||
[0.001, 0.05, 0.6, 0.01],
|
||||
);
|
||||
|
||||
const holdend = begin + duration;
|
||||
const end = holdend + release + 0.01;
|
||||
|
||||
let o = getWorklet(
|
||||
ac,
|
||||
'pulse-oscillator',
|
||||
@@ -178,11 +268,16 @@ export function registerSynthSounds() {
|
||||
envGain = o.connect(envGain);
|
||||
|
||||
getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 1, begin, holdend, 'linear');
|
||||
|
||||
let lfo;
|
||||
if (pwsweep != 0) {
|
||||
lfo = getLfo(ac, begin, end, { frequency: pwrate, depth: pwsweep });
|
||||
lfo.connect(o.parameters.get('pulsewidth'));
|
||||
}
|
||||
let timeoutNode = webAudioTimeout(
|
||||
ac,
|
||||
() => {
|
||||
destroyAudioWorkletNode(o);
|
||||
destroyAudioWorkletNode(lfo);
|
||||
envGain.disconnect();
|
||||
onended();
|
||||
fm?.stop();
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import { dependencies } from './package.json';
|
||||
import { resolve } from 'path';
|
||||
import bundleAudioWorkletPlugin from 'vite-plugin-bundle-audioworklet';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [],
|
||||
plugins: [bundleAudioWorkletPlugin()],
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'index.mjs'),
|
||||
|
||||
@@ -295,7 +295,7 @@ class LadderProcessor extends AudioWorkletProcessor {
|
||||
cutoff = (cutoff * 2 * _PI) / sampleRate;
|
||||
cutoff = cutoff > 1 ? 1 : cutoff;
|
||||
|
||||
const k = Math.min(8, resonance * 0.4);
|
||||
const k = Math.min(8, resonance * 0.13);
|
||||
// drive makeup * resonance volume loss makeup
|
||||
let makeupgain = (1 / drive) * Math.min(1.75, 1 + k);
|
||||
|
||||
@@ -761,3 +761,137 @@ class PulseOscillatorProcessor extends AudioWorkletProcessor {
|
||||
}
|
||||
|
||||
registerProcessor('pulse-oscillator', PulseOscillatorProcessor);
|
||||
|
||||
/** BYTE BEATS */
|
||||
const chyx = {
|
||||
/*bit*/ bitC: function (x, y, z) {
|
||||
return x & y ? z : 0;
|
||||
},
|
||||
/*bit reverse*/ br: function (x, size = 8) {
|
||||
if (size > 32) {
|
||||
throw new Error('br() Size cannot be greater than 32');
|
||||
} else {
|
||||
let result = 0;
|
||||
for (let idx = 0; idx < size - 0; idx++) {
|
||||
result += chyx.bitC(x, 2 ** idx, 2 ** (size - (idx + 1)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
},
|
||||
/*sin that loops every 128 "steps", instead of every pi steps*/ sinf: function (x) {
|
||||
return Math.sin(x / (128 / Math.PI));
|
||||
},
|
||||
/*cos that loops every 128 "steps", instead of every pi steps*/ cosf: function (x) {
|
||||
return Math.cos(x / (128 / Math.PI));
|
||||
},
|
||||
/*tan that loops every 128 "steps", instead of every pi steps*/ tanf: function (x) {
|
||||
return Math.tan(x / (128 / Math.PI));
|
||||
},
|
||||
/*converts t into a string composed of it's bits, regex's that*/ regG: function (t, X) {
|
||||
return X.test(t.toString(2));
|
||||
},
|
||||
};
|
||||
|
||||
// Create shortened Math functions
|
||||
let mathParams, byteBeatHelperFuncs;
|
||||
function getByteBeatFunc(codetext) {
|
||||
if ((mathParams || byteBeatHelperFuncs) == null) {
|
||||
mathParams = Object.getOwnPropertyNames(Math);
|
||||
byteBeatHelperFuncs = mathParams.map((k) => Math[k]);
|
||||
const chyxNames = Object.getOwnPropertyNames(chyx);
|
||||
const chyxFuncs = chyxNames.map((k) => chyx[k]);
|
||||
mathParams.push('int', 'window', ...chyxNames);
|
||||
byteBeatHelperFuncs.push(Math.floor, globalThis, ...chyxFuncs);
|
||||
}
|
||||
return new Function(...mathParams, 't', `return 0,\n${codetext || 0};`).bind(globalThis, ...byteBeatHelperFuncs);
|
||||
}
|
||||
|
||||
class ByteBeatProcessor extends AudioWorkletProcessor {
|
||||
constructor() {
|
||||
super();
|
||||
this.port.onmessage = (event) => {
|
||||
let { codeText } = event.data;
|
||||
const { byteBeatStartTime } = event.data;
|
||||
if (byteBeatStartTime != null) {
|
||||
this.t = 0;
|
||||
this.initialOffset = Math.floor(byteBeatStartTime);
|
||||
}
|
||||
|
||||
//Optimization pulled from dollchan.net: https://github.com/Chasyxx/EnBeat_NEW, it seemed important
|
||||
//Optimize code like eval(unescape(escape`XXXX`.replace(/u(..)/g,"$1%")))
|
||||
codeText = codeText
|
||||
.trim()
|
||||
.replace(
|
||||
/^eval\(unescape\(escape(?:`|\('|\("|\(`)(.*?)(?:`|'\)|"\)|`\)).replace\(\/u\(\.\.\)\/g,["'`]\$1%["'`]\)\)\)$/,
|
||||
(match, m1) => unescape(escape(m1).replace(/u(..)/g, '$1%')),
|
||||
);
|
||||
|
||||
this.func = getByteBeatFunc(codeText);
|
||||
};
|
||||
this.initialOffset = null;
|
||||
this.t = null;
|
||||
this.func = null;
|
||||
}
|
||||
|
||||
static get parameterDescriptors() {
|
||||
return [
|
||||
{
|
||||
name: 'begin',
|
||||
defaultValue: 0,
|
||||
max: Number.POSITIVE_INFINITY,
|
||||
min: 0,
|
||||
},
|
||||
{
|
||||
name: 'frequency',
|
||||
defaultValue: 440,
|
||||
min: Number.EPSILON,
|
||||
},
|
||||
{
|
||||
name: 'detune',
|
||||
defaultValue: 0,
|
||||
min: Number.NEGATIVE_INFINITY,
|
||||
max: Number.POSITIVE_INFINITY,
|
||||
},
|
||||
{
|
||||
name: 'end',
|
||||
defaultValue: 0,
|
||||
max: Number.POSITIVE_INFINITY,
|
||||
min: 0,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
process(inputs, outputs, params) {
|
||||
if (this.disconnected) {
|
||||
return false;
|
||||
}
|
||||
if (currentTime <= params.begin[0]) {
|
||||
return true;
|
||||
}
|
||||
if (currentTime >= params.end[0]) {
|
||||
return false;
|
||||
}
|
||||
if (this.t == null) {
|
||||
this.t = params.begin[0] * sampleRate;
|
||||
}
|
||||
const output = outputs[0];
|
||||
for (let i = 0; i < output[0].length; i++) {
|
||||
const detune = getParamValue(i, params.detune);
|
||||
const freq = applySemitoneDetuneToFrequency(getParamValue(i, params.frequency), detune / 100);
|
||||
let local_t = (this.t / (sampleRate / 256)) * freq + this.initialOffset;
|
||||
const funcValue = this.func(local_t);
|
||||
let signal = (funcValue & 255) / 127.5 - 1;
|
||||
const out = signal * 0.2;
|
||||
|
||||
for (let c = 0; c < output.length; c++) {
|
||||
//prevent speaker blowout via clipping if threshold exceeds
|
||||
output[c][i] = clamp(out, -0.4, 0.4);
|
||||
}
|
||||
this.t = this.t + 1;
|
||||
}
|
||||
|
||||
return true; // keep the audio processing going
|
||||
}
|
||||
}
|
||||
|
||||
registerProcessor('byte-beat-processor', ByteBeatProcessor);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/tonal",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.2",
|
||||
"description": "Tonal functions for strudel",
|
||||
"main": "index.mjs",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/transpiler",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.2",
|
||||
"description": "Transpiler for strudel user code. Converts syntactically correct but semantically meaningless JS into evaluatable strudel code.",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "vite-plugin-bundle-audioworklet",
|
||||
"main": "./vite-plugin-bundle-audioworklet.js",
|
||||
"version": "0.1.1",
|
||||
"description": "",
|
||||
"keywords": [
|
||||
"vite",
|
||||
"audioworklet"
|
||||
],
|
||||
"author": "Felix Roos <flix91@gmail.com>",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"vite": "^6.0.11"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { createLogger, build } from 'vite';
|
||||
|
||||
const end = '?audioworklet';
|
||||
|
||||
function bundleAudioWorkletPlugin() /* : PluginOption */ {
|
||||
let viteConfig /* : UserConfig */;
|
||||
|
||||
return {
|
||||
name: 'vite-plugin-bundle-audioworklet',
|
||||
/* apply: 'build', */
|
||||
enforce: 'post',
|
||||
|
||||
config(config) {
|
||||
viteConfig = config;
|
||||
},
|
||||
|
||||
async transform(_code, id) {
|
||||
if (!id.endsWith(end)) {
|
||||
return;
|
||||
}
|
||||
const entry = id.replace(end, '');
|
||||
const quietLogger = createLogger();
|
||||
quietLogger.info = () => undefined;
|
||||
|
||||
const output = await build({
|
||||
configFile: false,
|
||||
clearScreen: false,
|
||||
customLogger: quietLogger,
|
||||
build: {
|
||||
lib: {
|
||||
entry,
|
||||
name: '_',
|
||||
formats: ['iife'],
|
||||
},
|
||||
write: false,
|
||||
},
|
||||
});
|
||||
if (!(output instanceof Array)) {
|
||||
throw new Error('Expected output to be Array');
|
||||
}
|
||||
const iife = output[0].output[0].code;
|
||||
const encoded = Buffer.from(iife, 'utf8').toString('base64');
|
||||
return `export default "data:text/javascript;base64,${encoded}";`;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default bundleAudioWorkletPlugin;
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/web",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.3",
|
||||
"description": "Easy to setup, opiniated bundle of Strudel for the browser.",
|
||||
"module": "web.mjs",
|
||||
"publishConfig": {
|
||||
@@ -41,6 +41,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-replace": "^6.0.2",
|
||||
"vite": "^6.0.11"
|
||||
"vite": "^6.0.11",
|
||||
"vite-plugin-bundle-audioworklet": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@ import { defineConfig } from 'vite';
|
||||
import { dependencies } from './package.json';
|
||||
import { resolve } from 'path';
|
||||
import replace from '@rollup/plugin-replace';
|
||||
import bundleAudioWorkletPlugin from 'vite-plugin-bundle-audioworklet';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
base: './',
|
||||
plugins: [],
|
||||
plugins: [bundleAudioWorkletPlugin()],
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'web.mjs'),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/webaudio",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.3",
|
||||
"description": "Web Audio helpers for Strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/xen",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.2",
|
||||
"description": "Xenharmonic API for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
Generated
+23
-3
@@ -41,7 +41,7 @@ importers:
|
||||
version: 2.2.7
|
||||
'@vitest/coverage-v8':
|
||||
specifier: 3.0.4
|
||||
version: 3.0.4(vitest@3.0.4(@types/debug@4.1.12)(@types/node@22.10.10)(@vitest/ui@3.0.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(yaml@2.7.0))
|
||||
version: 3.0.4(vitest@3.0.4)
|
||||
'@vitest/ui':
|
||||
specifier: ^3.0.4
|
||||
version: 3.0.4(vitest@3.0.4)
|
||||
@@ -75,6 +75,9 @@ importers:
|
||||
prettier:
|
||||
specifier: ^3.4.2
|
||||
version: 3.4.2
|
||||
vite-plugin-bundle-audioworklet:
|
||||
specifier: workspace:*
|
||||
version: link:packages/vite-plugin-bundle-audioworklet
|
||||
vitest:
|
||||
specifier: ^3.0.4
|
||||
version: 3.0.4(@types/debug@4.1.12)(@types/node@22.10.10)(@vitest/ui@3.0.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(yaml@2.7.0)
|
||||
@@ -428,6 +431,9 @@ importers:
|
||||
vite:
|
||||
specifier: ^6.0.11
|
||||
version: 6.0.11(@types/node@22.10.10)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(yaml@2.7.0)
|
||||
vite-plugin-bundle-audioworklet:
|
||||
specifier: workspace:*
|
||||
version: link:../vite-plugin-bundle-audioworklet
|
||||
|
||||
packages/sampler:
|
||||
dependencies:
|
||||
@@ -476,6 +482,9 @@ importers:
|
||||
vite:
|
||||
specifier: ^6.0.11
|
||||
version: 6.0.11(@types/node@22.10.10)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(yaml@2.7.0)
|
||||
vite-plugin-bundle-audioworklet:
|
||||
specifier: workspace:*
|
||||
version: link:../vite-plugin-bundle-audioworklet
|
||||
|
||||
packages/tidal:
|
||||
dependencies:
|
||||
@@ -540,6 +549,12 @@ importers:
|
||||
specifier: ^3.0.4
|
||||
version: 3.0.4(@types/debug@4.1.12)(@types/node@22.10.10)(@vitest/ui@3.0.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(yaml@2.7.0)
|
||||
|
||||
packages/vite-plugin-bundle-audioworklet:
|
||||
devDependencies:
|
||||
vite:
|
||||
specifier: ^6.0.11
|
||||
version: 6.0.11(@types/node@22.10.10)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(yaml@2.7.0)
|
||||
|
||||
packages/web:
|
||||
dependencies:
|
||||
'@strudel/core':
|
||||
@@ -564,6 +579,9 @@ importers:
|
||||
vite:
|
||||
specifier: ^6.0.11
|
||||
version: 6.0.11(@types/node@22.10.10)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(yaml@2.7.0)
|
||||
vite-plugin-bundle-audioworklet:
|
||||
specifier: workspace:*
|
||||
version: link:../vite-plugin-bundle-audioworklet
|
||||
|
||||
packages/webaudio:
|
||||
dependencies:
|
||||
@@ -780,6 +798,9 @@ importers:
|
||||
sharp:
|
||||
specifier: ^0.33.5
|
||||
version: 0.33.5
|
||||
vite-plugin-bundle-audioworklet:
|
||||
specifier: workspace:*
|
||||
version: link:../packages/vite-plugin-bundle-audioworklet
|
||||
workbox-window:
|
||||
specifier: ^7.3.0
|
||||
version: 7.3.0
|
||||
@@ -7561,7 +7582,6 @@ packages:
|
||||
|
||||
workbox-google-analytics@7.0.0:
|
||||
resolution: {integrity: sha512-MEYM1JTn/qiC3DbpvP2BVhyIH+dV/5BjHk756u9VbwuAhu0QHyKscTnisQuz21lfRpOwiS9z4XdqeVAKol0bzg==}
|
||||
deprecated: It is not compatible with newer versions of GA starting with v4, as long as you are using GAv3 it should be ok, but the package is not longer being maintained
|
||||
|
||||
workbox-navigation-preload@7.0.0:
|
||||
resolution: {integrity: sha512-juWCSrxo/fiMz3RsvDspeSLGmbgC0U9tKqcUPZBCf35s64wlaLXyn2KdHHXVQrb2cqF7I0Hc9siQalainmnXJA==}
|
||||
@@ -10248,7 +10268,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@vitest/coverage-v8@3.0.4(vitest@3.0.4(@types/debug@4.1.12)(@types/node@22.10.10)(@vitest/ui@3.0.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(yaml@2.7.0))':
|
||||
'@vitest/coverage-v8@3.0.4(vitest@3.0.4)':
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
'@bcoe/v8-coverage': 1.0.2
|
||||
|
||||
@@ -1516,6 +1516,52 @@ exports[`runs examples > example "brandBy" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "byteBeatExpression" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/1 | s:bytebeat byteBeatExpression:t*(t>>15^t>>66) ]",
|
||||
"[ 1/1 → 2/1 | s:bytebeat byteBeatExpression:t*(t>>15^t>>66) ]",
|
||||
"[ 2/1 → 3/1 | s:bytebeat byteBeatExpression:t*(t>>15^t>>66) ]",
|
||||
"[ 3/1 → 4/1 | s:bytebeat byteBeatExpression:t*(t>>15^t>>66) ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "byteBeatStartTime" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | note:48 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 1/8 → 1/4 | note:48 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 1/4 → 3/8 | note:60 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 3/8 → 1/2 | note:48 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 1/2 → 5/8 | note:55 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 5/8 → 3/4 | note:53 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 3/4 → 7/8 | note:51 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 7/8 → 1/1 | note:48 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 1/1 → 9/8 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
"[ 9/8 → 5/4 | note:60 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
"[ 5/4 → 11/8 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
"[ 11/8 → 3/2 | note:55 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
"[ 3/2 → 13/8 | note:53 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
"[ 13/8 → 7/4 | note:51 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
"[ 7/4 → 15/8 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
"[ 15/8 → 2/1 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
"[ 2/1 → 17/8 | note:60 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 17/8 → 9/4 | note:48 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 9/4 → 19/8 | note:55 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 19/8 → 5/2 | note:53 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 5/2 → 21/8 | note:51 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 21/8 → 11/4 | note:48 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 11/4 → 23/8 | note:48 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 23/8 → 3/1 | note:60 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 3/1 → 25/8 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
"[ 25/8 → 13/4 | note:55 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
"[ 13/4 → 27/8 | note:53 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
"[ 27/8 → 7/2 | note:51 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
"[ 7/2 → 29/8 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
"[ 29/8 → 15/4 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
"[ 15/4 → 31/8 | note:60 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
"[ 31/8 → 4/1 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "cat" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/1 | note:e5 ]",
|
||||
@@ -2403,6 +2449,27 @@ exports[`runs examples > example "delaytime" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "density" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | s:crackle density:0.01 ]",
|
||||
"[ 1/4 → 1/2 | s:crackle density:0.01 ]",
|
||||
"[ 1/2 → 3/4 | s:crackle density:0.01 ]",
|
||||
"[ 3/4 → 1/1 | s:crackle density:0.01 ]",
|
||||
"[ 1/1 → 5/4 | s:crackle density:0.01 ]",
|
||||
"[ 5/4 → 3/2 | s:crackle density:0.01 ]",
|
||||
"[ 3/2 → 7/4 | s:crackle density:0.01 ]",
|
||||
"[ 7/4 → 2/1 | s:crackle density:0.01 ]",
|
||||
"[ 2/1 → 9/4 | s:crackle density:0.01 ]",
|
||||
"[ 9/4 → 5/2 | s:crackle density:0.01 ]",
|
||||
"[ 5/2 → 11/4 | s:crackle density:0.01 ]",
|
||||
"[ 11/4 → 3/1 | s:crackle density:0.01 ]",
|
||||
"[ 3/1 → 13/4 | s:crackle density:0.01 ]",
|
||||
"[ 13/4 → 7/2 | s:crackle density:0.01 ]",
|
||||
"[ 7/2 → 15/4 | s:crackle density:0.01 ]",
|
||||
"[ 15/4 → 4/1 | s:crackle density:0.01 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "detune" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/12 | note:d s:supersaw detune:0.1 ]",
|
||||
@@ -6721,6 +6788,186 @@ exports[`runs examples > example "pure" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "pw" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 1/16 → 1/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 1/8 → 3/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 3/16 → 1/4 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 1/4 → 5/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 5/16 → 3/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 3/8 → 7/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 7/16 → 1/2 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 1/2 → 9/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 9/16 → 5/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 5/8 → 11/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 11/16 → 3/4 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 3/4 → 13/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 13/16 → 7/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 7/8 → 15/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 15/16 → 1/1 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 1/1 → 17/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 17/16 → 9/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 9/8 → 19/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 19/16 → 5/4 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 5/4 → 21/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 21/16 → 11/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 11/8 → 23/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 23/16 → 3/2 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 3/2 → 25/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 25/16 → 13/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 13/8 → 27/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 27/16 → 7/4 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 7/4 → 29/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 29/16 → 15/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 15/8 → 31/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 31/16 → 2/1 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 2/1 → 33/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 33/16 → 17/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 17/8 → 35/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 35/16 → 9/4 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 9/4 → 37/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 37/16 → 19/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 19/8 → 39/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 39/16 → 5/2 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 5/2 → 41/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 41/16 → 21/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 21/8 → 43/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 43/16 → 11/4 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 11/4 → 45/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 45/16 → 23/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 23/8 → 47/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 47/16 → 3/1 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 3/1 → 49/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 49/16 → 25/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 25/8 → 51/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 51/16 → 13/4 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 13/4 → 53/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 53/16 → 27/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 27/8 → 55/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 55/16 → 7/2 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 7/2 → 57/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 57/16 → 29/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 29/8 → 59/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 59/16 → 15/4 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 15/4 → 61/16 | note:f s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 61/16 → 31/8 | note:a s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 31/8 → 63/16 | note:c s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
"[ 63/16 → 4/1 | note:e s:pulse pw:0.8 pwrate:1 pwsweep:0.2 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "pw" example index 1 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | note:D3 s:pulse pw:0 ]",
|
||||
"[ 1/8 → 1/4 | note:E3 s:pulse pw:0 ]",
|
||||
"[ 1/4 → 3/8 | note:F#3 s:pulse pw:0.75 ]",
|
||||
"[ 3/8 → 1/2 | note:A3 s:pulse pw:0.75 ]",
|
||||
"[ 1/2 → 5/8 | note:B3 s:pulse pw:0.5 ]",
|
||||
"[ 5/8 → 3/4 | note:D4 s:pulse pw:0.5 ]",
|
||||
"[ 3/4 → 7/8 | note:E4 s:pulse pw:1 ]",
|
||||
"[ 7/8 → 1/1 | note:F#4 s:pulse pw:1 ]",
|
||||
"[ 1/1 → 9/8 | note:D3 s:pulse pw:0 ]",
|
||||
"[ 9/8 → 5/4 | note:E3 s:pulse pw:0 ]",
|
||||
"[ 5/4 → 11/8 | note:F#3 s:pulse pw:0.75 ]",
|
||||
"[ 11/8 → 3/2 | note:A3 s:pulse pw:0.75 ]",
|
||||
"[ 3/2 → 13/8 | note:B3 s:pulse pw:0.5 ]",
|
||||
"[ 13/8 → 7/4 | note:D4 s:pulse pw:0.5 ]",
|
||||
"[ 7/4 → 15/8 | note:E4 s:pulse pw:1 ]",
|
||||
"[ 15/8 → 2/1 | note:F#4 s:pulse pw:1 ]",
|
||||
"[ 2/1 → 17/8 | note:D3 s:pulse pw:0 ]",
|
||||
"[ 17/8 → 9/4 | note:E3 s:pulse pw:0 ]",
|
||||
"[ 9/4 → 19/8 | note:F#3 s:pulse pw:0.75 ]",
|
||||
"[ 19/8 → 5/2 | note:A3 s:pulse pw:0.75 ]",
|
||||
"[ 5/2 → 21/8 | note:B3 s:pulse pw:0.5 ]",
|
||||
"[ 21/8 → 11/4 | note:D4 s:pulse pw:0.5 ]",
|
||||
"[ 11/4 → 23/8 | note:E4 s:pulse pw:1 ]",
|
||||
"[ 23/8 → 3/1 | note:F#4 s:pulse pw:1 ]",
|
||||
"[ 3/1 → 25/8 | note:D3 s:pulse pw:0 ]",
|
||||
"[ 25/8 → 13/4 | note:E3 s:pulse pw:0 ]",
|
||||
"[ 13/4 → 27/8 | note:F#3 s:pulse pw:0.75 ]",
|
||||
"[ 27/8 → 7/2 | note:A3 s:pulse pw:0.75 ]",
|
||||
"[ 7/2 → 29/8 | note:B3 s:pulse pw:0.5 ]",
|
||||
"[ 29/8 → 15/4 | note:D4 s:pulse pw:0.5 ]",
|
||||
"[ 15/4 → 31/8 | note:E4 s:pulse pw:1 ]",
|
||||
"[ 31/8 → 4/1 | note:F#4 s:pulse pw:1 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "pwrate" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | note:D3 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]",
|
||||
"[ 1/8 → 1/4 | note:E3 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]",
|
||||
"[ 1/4 → 3/8 | note:F#3 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]",
|
||||
"[ 3/8 → 1/2 | note:A3 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]",
|
||||
"[ 1/2 → 5/8 | note:B3 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]",
|
||||
"[ 5/8 → 3/4 | note:D4 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]",
|
||||
"[ 3/4 → 7/8 | note:E4 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]",
|
||||
"[ 7/8 → 1/1 | note:F#4 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]",
|
||||
"[ 1/1 → 9/8 | note:D3 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]",
|
||||
"[ 9/8 → 5/4 | note:E3 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]",
|
||||
"[ 5/4 → 11/8 | note:F#3 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]",
|
||||
"[ 11/8 → 3/2 | note:A3 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]",
|
||||
"[ 3/2 → 13/8 | note:B3 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]",
|
||||
"[ 13/8 → 7/4 | note:D4 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]",
|
||||
"[ 7/4 → 15/8 | note:E4 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]",
|
||||
"[ 15/8 → 2/1 | note:F#4 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]",
|
||||
"[ 2/1 → 17/8 | note:D3 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]",
|
||||
"[ 17/8 → 9/4 | note:E3 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]",
|
||||
"[ 9/4 → 19/8 | note:F#3 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]",
|
||||
"[ 19/8 → 5/2 | note:A3 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]",
|
||||
"[ 5/2 → 21/8 | note:B3 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]",
|
||||
"[ 21/8 → 11/4 | note:D4 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]",
|
||||
"[ 11/4 → 23/8 | note:E4 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]",
|
||||
"[ 23/8 → 3/1 | note:F#4 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]",
|
||||
"[ 3/1 → 25/8 | note:D3 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]",
|
||||
"[ 25/8 → 13/4 | note:E3 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]",
|
||||
"[ 13/4 → 27/8 | note:F#3 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]",
|
||||
"[ 27/8 → 7/2 | note:A3 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]",
|
||||
"[ 7/2 → 29/8 | note:B3 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]",
|
||||
"[ 29/8 → 15/4 | note:D4 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]",
|
||||
"[ 15/4 → 31/8 | note:E4 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]",
|
||||
"[ 31/8 → 4/1 | note:F#4 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "pwsweep" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | note:D3 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]",
|
||||
"[ 1/8 → 1/4 | note:E3 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]",
|
||||
"[ 1/4 → 3/8 | note:F#3 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]",
|
||||
"[ 3/8 → 1/2 | note:A3 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]",
|
||||
"[ 1/2 → 5/8 | note:B3 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]",
|
||||
"[ 5/8 → 3/4 | note:D4 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]",
|
||||
"[ 3/4 → 7/8 | note:E4 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]",
|
||||
"[ 7/8 → 1/1 | note:F#4 s:pulse pw:0.5 pwrate:5 pwsweep:0.3 ]",
|
||||
"[ 1/1 → 9/8 | note:D3 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]",
|
||||
"[ 9/8 → 5/4 | note:E3 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]",
|
||||
"[ 5/4 → 11/8 | note:F#3 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]",
|
||||
"[ 11/8 → 3/2 | note:A3 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]",
|
||||
"[ 3/2 → 13/8 | note:B3 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]",
|
||||
"[ 13/8 → 7/4 | note:D4 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]",
|
||||
"[ 7/4 → 15/8 | note:E4 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]",
|
||||
"[ 15/8 → 2/1 | note:F#4 s:pulse pw:0.5 pwrate:0.1 pwsweep:0.8 ]",
|
||||
"[ 2/1 → 17/8 | note:D3 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]",
|
||||
"[ 17/8 → 9/4 | note:E3 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]",
|
||||
"[ 9/4 → 19/8 | note:F#3 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]",
|
||||
"[ 19/8 → 5/2 | note:A3 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]",
|
||||
"[ 5/2 → 21/8 | note:B3 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]",
|
||||
"[ 21/8 → 11/4 | note:D4 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]",
|
||||
"[ 11/4 → 23/8 | note:E4 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]",
|
||||
"[ 23/8 → 3/1 | note:F#4 s:pulse pw:0.5 pwrate:25 pwsweep:0.3 ]",
|
||||
"[ 3/1 → 25/8 | note:D3 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]",
|
||||
"[ 25/8 → 13/4 | note:E3 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]",
|
||||
"[ 13/4 → 27/8 | note:F#3 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]",
|
||||
"[ 27/8 → 7/2 | note:A3 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]",
|
||||
"[ 7/2 → 29/8 | note:B3 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]",
|
||||
"[ 29/8 → 15/4 | note:D4 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]",
|
||||
"[ 15/4 → 31/8 | note:E4 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]",
|
||||
"[ 31/8 → 4/1 | note:F#4 s:pulse pw:0.5 pwrate:5 pwsweep:0.8 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "queryArc" example index 0 1`] = `[]`;
|
||||
|
||||
exports[`runs examples > example "rand" example index 0 1`] = `
|
||||
@@ -7821,9 +8068,7 @@ exports[`runs examples > example "scope" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "scramble
|
||||
Slices a pattern into the given number of parts, then plays those parts at random. Similar to \`shuffle\`,
|
||||
but parts might be played more than once, or not at all, per cycle." example index 0 1`] = `
|
||||
exports[`runs examples > example "scramble" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | note:c s:piano ]",
|
||||
"[ 1/4 → 1/2 | note:d s:piano ]",
|
||||
@@ -7844,9 +8089,7 @@ but parts might be played more than once, or not at all, per cycle." example ind
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "scramble
|
||||
Slices a pattern into the given number of parts, then plays those parts at random. Similar to \`shuffle\`,
|
||||
but parts might be played more than once, or not at all, per cycle." example index 1 1`] = `
|
||||
exports[`runs examples > example "scramble" example index 1 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | note:c s:piano ]",
|
||||
"[ 1/8 → 1/4 | note:d s:piano ]",
|
||||
@@ -8301,9 +8544,7 @@ exports[`runs examples > example "shrink" example index 3 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "shuffle
|
||||
Slices a pattern into the given number of parts, then plays those parts in random order.
|
||||
Each part will be played exactly once per cycle." example index 0 1`] = `
|
||||
exports[`runs examples > example "shuffle" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | note:c s:piano ]",
|
||||
"[ 1/4 → 1/2 | note:d s:piano ]",
|
||||
@@ -8324,9 +8565,7 @@ Each part will be played exactly once per cycle." example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "shuffle
|
||||
Slices a pattern into the given number of parts, then plays those parts in random order.
|
||||
Each part will be played exactly once per cycle." example index 1 1`] = `
|
||||
exports[`runs examples > example "shuffle" example index 1 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | note:c s:piano ]",
|
||||
"[ 1/8 → 1/4 | note:d s:piano ]",
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { configDefaults, defineConfig } from 'vitest/config';
|
||||
import { defineConfig } from 'vitest/config';
|
||||
import bundleAudioWorkletPlugin from 'vite-plugin-bundle-audioworklet';
|
||||
|
||||
/// <reference types="vitest" />
|
||||
export default defineConfig({
|
||||
plugins: [bundleAudioWorkletPlugin()],
|
||||
test: {
|
||||
reporters: 'verbose',
|
||||
isolate: false,
|
||||
@@ -5,6 +5,7 @@ import remarkToc from 'remark-toc';
|
||||
import rehypeSlug from 'rehype-slug';
|
||||
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
|
||||
import rehypeUrls from 'rehype-urls';
|
||||
import bundleAudioWorkletPlugin from 'vite-plugin-bundle-audioworklet';
|
||||
|
||||
import tailwind from '@astrojs/tailwind';
|
||||
import AstroPWA from '@vite-pwa/astro';
|
||||
@@ -134,6 +135,7 @@ export default defineConfig({
|
||||
site,
|
||||
base,
|
||||
vite: {
|
||||
plugins: [bundleAudioWorkletPlugin()],
|
||||
ssr: {
|
||||
// Example: Force a broken package to skip SSR processing, if needed
|
||||
// external: ['fraction.js'], // https://github.com/infusion/Fraction.js/issues/51
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
"@vite-pwa/astro": "^0.5.0",
|
||||
"html-escaper": "^3.0.3",
|
||||
"sharp": "^0.33.5",
|
||||
"workbox-window": "^7.3.0"
|
||||
"workbox-window": "^7.3.0",
|
||||
"vite-plugin-bundle-audioworklet": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ is equivalent to this Pattern without Mini Notation:
|
||||
Similarly, there is an equivalent function for every aspect of the mini notation.
|
||||
|
||||
Which representation to use is a matter of context. As a rule of thumb, functions
|
||||
are better suited in a larger context, while mini notation is more practical for individiual rhythms.
|
||||
are better suited in a larger context, while mini notation is more practical for individual rhythms.
|
||||
|
||||
## Limits of Mini Notation
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { ButtonGroup } from './Forms.jsx';
|
||||
import { AudioDeviceSelector } from './AudioDeviceSelector.jsx';
|
||||
import { AudioEngineTargetSelector } from './AudioEngineTargetSelector.jsx';
|
||||
import { confirmDialog } from '../../util.mjs';
|
||||
import { DEFAULT_MAX_POLYPHONY, setMaxPolyphony } from '@strudel/webaudio';
|
||||
import { DEFAULT_MAX_POLYPHONY, setMaxPolyphony, setMultiChannelOrbits } from '@strudel/webaudio';
|
||||
|
||||
function Checkbox({ label, value, onChange, disabled = false }) {
|
||||
return (
|
||||
@@ -108,6 +108,7 @@ export function SettingsTab({ started }) {
|
||||
audioEngineTarget,
|
||||
togglePanelTrigger,
|
||||
maxPolyphony,
|
||||
multiChannelOrbits,
|
||||
} = useSettings();
|
||||
const shouldAlwaysSync = isUdels();
|
||||
const canChangeAudioDevice = AudioContext.prototype.setSinkId != null;
|
||||
@@ -162,6 +163,22 @@ export function SettingsTab({ started }) {
|
||||
value={maxPolyphony ?? ''}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem>
|
||||
<Checkbox
|
||||
label="Multi Channel Orbits"
|
||||
onChange={(cbEvent) => {
|
||||
const val = cbEvent.target.checked;
|
||||
confirmDialog(RELOAD_MSG).then((r) => {
|
||||
if (r == true) {
|
||||
settingsMap.setKey('multiChannelOrbits', val);
|
||||
setMultiChannelOrbits(val);
|
||||
return window.location.reload();
|
||||
}
|
||||
});
|
||||
}}
|
||||
value={multiChannelOrbits}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="Theme">
|
||||
<SelectInput options={themeOptions} value={theme} onChange={(theme) => settingsMap.setKey('theme', theme)} />
|
||||
</FormItem>
|
||||
|
||||
@@ -18,7 +18,7 @@ import { setVersionDefaultsFrom } from './util.mjs';
|
||||
import { StrudelMirror, defaultSettings } from '@strudel/codemirror';
|
||||
import { clearHydra } from '@strudel/hydra';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { settingsMap, useSettings } from '../settings.mjs';
|
||||
import { parseBoolean, settingsMap, useSettings } from '../settings.mjs';
|
||||
import {
|
||||
setActivePattern,
|
||||
setLatestCode,
|
||||
@@ -36,11 +36,15 @@ import './Repl.css';
|
||||
import { setInterval, clearInterval } from 'worker-timers';
|
||||
import { getMetadata } from '../metadata_parser';
|
||||
|
||||
const { latestCode, maxPolyphony, audioDeviceName } = settingsMap.get();
|
||||
const { latestCode, maxPolyphony, audioDeviceName, multiChannelOrbits } = settingsMap.get();
|
||||
let modulesLoading, presets, drawContext, clearCanvas, audioReady;
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
audioReady = initAudioOnFirstClick({ maxPolyphony, audioDeviceName });
|
||||
audioReady = initAudioOnFirstClick({
|
||||
maxPolyphony,
|
||||
audioDeviceName,
|
||||
multiChannelOrbits: parseBoolean(multiChannelOrbits),
|
||||
});
|
||||
modulesLoading = loadModules();
|
||||
presets = prebake();
|
||||
drawContext = getDrawContext();
|
||||
|
||||
@@ -38,6 +38,7 @@ export const defaultSettings = {
|
||||
isButtonRowHidden: false,
|
||||
isCSSAnimationDisabled: false,
|
||||
maxPolyphony: 128,
|
||||
multiChannelOrbits: false,
|
||||
};
|
||||
|
||||
let search = null;
|
||||
@@ -50,7 +51,7 @@ const settings_key = `strudel-settings${instance > 0 ? instance : ''}`;
|
||||
|
||||
export const settingsMap = persistentMap(settings_key, defaultSettings);
|
||||
|
||||
const parseBoolean = (booleanlike) => ([true, 'true'].includes(booleanlike) ? true : false);
|
||||
export const parseBoolean = (booleanlike) => ([true, 'true'].includes(booleanlike) ? true : false);
|
||||
|
||||
export function useSettings() {
|
||||
const state = useStore(settingsMap);
|
||||
@@ -81,6 +82,7 @@ export function useSettings() {
|
||||
isPanelPinned: parseBoolean(state.isPanelPinned),
|
||||
isPanelOpen: parseBoolean(state.isPanelOpen),
|
||||
userPatterns: userPatterns,
|
||||
multiChannelOrbits: parseBoolean(state.multiChannelOrbits),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user