Compare commits

..

23 Commits

Author SHA1 Message Date
Felix Roos 5702914661 Publish
- @strudel/reference@1.2.0
2025-05-01 12:52:12 +02:00
Felix Roos f2a9e2d4e6 hotfix: downgrade reference to be able to get 1.2.0 2025-05-01 12:51:54 +02:00
Felix Roos 6398b5fb38 hotfix: make broken hs2js + tidal packages private to opt out of release
- some issue with the build command
2025-05-01 12:48:43 +02:00
Felix Roos 9ceebd4679 hotfix: reference package vite config 2025-05-01 12:47:56 +02:00
Felix Roos 177f5c1431 Publish
- @strudel/codemirror@1.2.0
 - @strudel/core@1.2.0
 - @strudel/csound@1.2.0
 - @strudel/draw@1.2.0
 - @strudel/gamepad@1.2.0
 - hs2js@0.2.0
 - @strudel/hydra@1.2.0
 - @strudel/midi@1.2.0
 - @strudel/mini@1.2.0
 - @strudel/motion@1.2.0
 - @strudel/mqtt@1.2.0
 - @strudel/osc@1.2.0
 - @strudel/reference@1.2.0
 - @strudel/repl@1.2.0
 - @strudel/sampler@0.2.0
 - @strudel/serial@1.2.0
 - @strudel/soundfonts@1.2.0
 - superdough@1.2.0
 - @strudel/tidal@0.2.0
 - @strudel/tonal@1.2.0
 - @strudel/transpiler@1.2.0
 - @strudel/web@1.2.0
 - @strudel/webaudio@1.2.0
 - @strudel/xen@1.2.0
2025-05-01 12:39:24 +02:00
Jade (Rose) Rowland d6c2a88496 Merge pull request #1333 from daslyfe/jade/soundimportorder
FIX: sound import order
2025-04-27 16:25:53 -04:00
Jade (Rose) Rowland 3ee7d0cd4f lint 2025-04-27 16:21:14 -04:00
Jade (Rose) Rowland 53d43dc44c working 2025-04-27 16:19:58 -04:00
Jade (Rose) Rowland 637c619345 Merge pull request #1332 from hpunq/import-sounds-docs
update docs to reflect import sounds tab change
2025-04-27 12:39:49 -04:00
Jade (Rose) Rowland 47fb441feb Merge pull request #1318 from daslyfe/jade/gaincurve
feat: Improve gain curve
2025-04-27 12:38:26 -04:00
hpunq d02b1fc05c update docs to reflect import sounds tab change 2025-04-27 11:05:06 +01:00
Jade (Rose) Rowland 480b5ff58b fix vel 2025-04-27 02:38:10 -04:00
Jade (Rose) Rowland c088c0c9d2 def 2025-04-27 02:21:49 -04:00
Jade (Rose) Rowland f32abac06f reset gaincurve 2025-04-27 02:20:53 -04:00
Jade (Rose) Rowland 697f0a82e6 Merge branch 'main' into jade/gaincurve 2025-04-27 02:10:30 -04:00
Jade (Rose) Rowland 98970417c3 Merge pull request #1331 from tidalcycles/BRANCH_NAME
Add Icon to import sample button
2025-04-27 01:54:49 -04:00
Jade (Rose) Rowland b5d48b2f0b Merge pull request #1329 from hpunq/sample-ui
Add new "import-sounds" tab with explanation on folder import
2025-04-27 01:53:40 -04:00
Jade (Rose) Rowland c48aba50f0 button size and icon 2025-04-27 01:51:41 -04:00
hpunq f539930509 formatted code with prettier 2025-04-26 01:04:11 +01:00
hpunq 759cdb6276 add new import sounds tab with explanation and hyperlinks 2025-04-24 13:53:42 +01:00
hpunq 3dae2a80ab change import sounds label to import sounds folder 2025-04-24 13:51:44 +01:00
Jade (Rose) Rowland 999c0b2349 lint 2025-03-31 00:21:01 -04:00
Jade (Rose) Rowland 5ed28d6168 gain curve func 2025-03-31 00:17:26 -04:00
34 changed files with 273 additions and 210 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/codemirror",
"version": "1.1.0",
"version": "1.2.0",
"description": "Codemirror Extensions for Strudel",
"main": "index.mjs",
"publishConfig": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/core",
"version": "1.1.0",
"version": "1.2.0",
"description": "Port of Tidal Cycles to JavaScript",
"main": "index.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/csound",
"version": "1.1.0",
"version": "1.2.0",
"description": "csound bindings for strudel",
"main": "index.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/draw",
"version": "1.1.0",
"version": "1.2.0",
"description": "Helpers for drawing with Strudel",
"main": "index.mjs",
"type": "module",
+35 -36
View File
@@ -1,38 +1,37 @@
{
"name": "@strudel/gamepad",
"version": "1.1.0",
"description": "Gamepad Inputs for strudel",
"main": "index.mjs",
"type": "module",
"publishConfig": {
"main": "dist/index.mjs"
},
"scripts": {
"build": "vite build",
"prepublishOnly": "npm run build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
},
"keywords": [
"titdalcycles",
"strudel",
"pattern",
"livecoding",
"algorave"
],
"author": "Yuta Nakayama <nkymut@gmail.com>",
"license": "AGPL-3.0-or-later",
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel/core": "workspace:*"
},
"devDependencies": {
"vite": "^6.0.11"
}
"name": "@strudel/gamepad",
"version": "1.2.0",
"description": "Gamepad Inputs for strudel",
"main": "index.mjs",
"type": "module",
"publishConfig": {
"main": "dist/index.mjs"
},
"scripts": {
"build": "vite build",
"prepublishOnly": "npm run build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
},
"keywords": [
"titdalcycles",
"strudel",
"pattern",
"livecoding",
"algorave"
],
"author": "Yuta Nakayama <nkymut@gmail.com>",
"license": "AGPL-3.0-or-later",
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel/core": "workspace:*"
},
"devDependencies": {
"vite": "^6.0.11"
}
}
+2 -1
View File
@@ -1,6 +1,7 @@
{
"name": "hs2js",
"version": "0.1.0",
"version": "0.2.0",
"private": true,
"description": "Experimental Haskell in JavaScript interpreter",
"main": "src/index.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/hydra",
"version": "1.1.0",
"version": "1.2.0",
"description": "Hydra integration for strudel",
"main": "hydra.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/midi",
"version": "1.1.0",
"version": "1.2.0",
"description": "Midi API for strudel",
"main": "index.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/mini",
"version": "1.1.0",
"version": "1.2.0",
"description": "Mini notation for strudel",
"main": "index.mjs",
"type": "module",
+34 -35
View File
@@ -1,38 +1,37 @@
{
"name": "@strudel/motion",
"version": "1.1.0",
"description": "DeviceMotion API for strudel",
"main": "index.mjs",
"type": "module",
"publishConfig": {
"main": "dist/index.mjs"
},
"scripts": {
"build": "vite build",
"prepublishOnly": "npm run build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
},
"keywords": [
"titdalcycles",
"strudel",
"pattern",
"livecoding",
"algorave"
],
"author": "Yuta Nakayama <nkymut@gmail.com>",
"license": "AGPL-3.0-or-later",
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel/core": "workspace:*"
},
"devDependencies": {
"name": "@strudel/motion",
"version": "1.2.0",
"description": "DeviceMotion API for strudel",
"main": "index.mjs",
"type": "module",
"publishConfig": {
"main": "dist/index.mjs"
},
"scripts": {
"build": "vite build",
"prepublishOnly": "npm run build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
},
"keywords": [
"titdalcycles",
"strudel",
"pattern",
"livecoding",
"algorave"
],
"author": "Yuta Nakayama <nkymut@gmail.com>",
"license": "AGPL-3.0-or-later",
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel/core": "workspace:*"
},
"devDependencies": {
"vite": "^6.0.11"
}
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/mqtt",
"version": "1.1.0",
"version": "1.2.0",
"description": "MQTT API for strudel",
"main": "mqtt.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/osc",
"version": "1.1.0",
"version": "1.2.0",
"description": "OSC messaging for strudel",
"main": "osc.mjs",
"type": "module",
+1 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/reference",
"version": "1.1.0",
"version": "1.2.0",
"description": "Headless reference of all strudel functions",
"main": "index.mjs",
"type": "module",
@@ -31,8 +31,6 @@
"url": "https://github.com/tidalcycles/strudel/issues"
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
},
"devDependencies": {
"vite": "^6.0.11"
}
-4
View File
@@ -1,5 +1,4 @@
import { defineConfig } from 'vite';
import { dependencies } from './package.json';
import { resolve } from 'path';
// https://vitejs.dev/config/
@@ -11,9 +10,6 @@ export default defineConfig({
formats: ['es'],
fileName: (ext) => ({ es: 'index.mjs' })[ext],
},
rollupOptions: {
external: [...Object.keys(dependencies)],
},
target: 'esnext',
},
});
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/repl",
"version": "1.1.0",
"version": "1.2.0",
"description": "Strudel REPL as a Web Component",
"module": "index.mjs",
"publishConfig": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/sampler",
"version": "0.1.0",
"version": "0.2.0",
"description": "",
"keywords": [
"tidalcycles",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/serial",
"version": "1.1.0",
"version": "1.2.0",
"description": "Webserial API for strudel",
"main": "serial.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/soundfonts",
"version": "1.1.0",
"version": "1.2.0",
"description": "Soundsfont support for strudel",
"main": "index.mjs",
"publishConfig": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "superdough",
"version": "1.1.0",
"version": "1.2.0",
"description": "simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.",
"main": "index.mjs",
"type": "module",
+19 -2
View File
@@ -18,6 +18,7 @@ export const DEFAULT_MAX_POLYPHONY = 128;
const DEFAULT_AUDIO_DEVICE_NAME = 'System Standard';
let maxPolyphony = DEFAULT_MAX_POLYPHONY;
export function setMaxPolyphony(polyphony) {
maxPolyphony = parseInt(polyphony) ?? DEFAULT_MAX_POLYPHONY;
}
@@ -28,6 +29,16 @@ export function registerSound(key, onTrigger, data = {}) {
soundMap.setKey(key, { onTrigger, data });
}
let gainCurveFunc = (val) => val;
export function applyGainCurve(val) {
return gainCurveFunc(val);
}
export function setGainCurve(newGainCurveFunc) {
gainCurveFunc = newGainCurveFunc;
}
function aliasBankMap(aliasMap) {
// Make all bank keys lower case for case insensitivity
for (const key in aliasMap) {
@@ -515,7 +526,13 @@ export const superdough = async (value, t, hapDuration) => {
compressorRelease,
} = value;
gain = nanFallback(gain, 1);
gain = applyGainCurve(nanFallback(gain, 1));
postgain = applyGainCurve(postgain);
shapevol = applyGainCurve(shapevol);
distortvol = applyGainCurve(distortvol);
delay = applyGainCurve(delay);
velocity = applyGainCurve(velocity);
gain *= velocity; // velocity currently only multiplies with gain. it might do other things in the future
const chainID = Math.round(Math.random() * 1000000);
@@ -532,7 +549,7 @@ export const superdough = async (value, t, hapDuration) => {
//music programs/audio gear usually increments inputs/outputs from 1, so imitate that behavior
channels = (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1);
gain *= velocity; // velocity currently only multiplies with gain. it might do other things in the future
let audioNodes = [];
if (bank && s) {
+2 -1
View File
@@ -1,6 +1,7 @@
{
"name": "@strudel/tidal",
"version": "0.1.0",
"version": "0.2.0",
"private": true,
"description": "Experimental Tidal Code interpreter",
"module": "tidal.mjs",
"repository": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/tonal",
"version": "1.1.0",
"version": "1.2.0",
"description": "Tonal functions for strudel",
"main": "index.mjs",
"publishConfig": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/transpiler",
"version": "1.1.0",
"version": "1.2.0",
"description": "Transpiler for strudel user code. Converts syntactically correct but semantically meaningless JS into evaluatable strudel code.",
"main": "index.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/web",
"version": "1.1.0",
"version": "1.2.0",
"description": "Easy to setup, opiniated bundle of Strudel for the browser.",
"module": "web.mjs",
"publishConfig": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/webaudio",
"version": "1.1.0",
"version": "1.2.0",
"description": "Web Audio helpers for Strudel",
"main": "index.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/xen",
"version": "1.1.0",
"version": "1.2.0",
"description": "Xenharmonic API for strudel",
"main": "index.mjs",
"type": "module",
+8 -6
View File
@@ -66,7 +66,7 @@ While it saves resources, it can also lead to sounds not being audible the first
# Sound Banks
If we open the `sounds` tab and then `drum machines`, we can see that the drum samples are all prefixed with drum machine names: `RolandTR808_bd`, `RolandTR808_sd`, `RolandTR808_hh` etc..
If we open the `sounds` tab and then `drum-machines`, we can see that the drum samples are all prefixed with drum machine names: `RolandTR808_bd`, `RolandTR808_sd`, `RolandTR808_hh` etc..
We _could_ use them like this:
@@ -180,11 +180,12 @@ It assumes a `strudel.json` file to be present at the root of the repository:
https://raw.githubusercontent.com/<user>/<repo>/<branch>/strudel.json
```
## From Disk via "Import Sounds"
## From Disk via "Import Sounds Folder"
If you don't want to upload your samples to the internet, you can also load them from your local disk.
Go to the `sounds` tab in the REPL and press "import sounds".
Then you can select a folder that contains audio files. The folder you select can also contain subfolders with audio files.
Go to the `sounds` tab in the REPL and open the `import-sounds` tab below the search bar.
Press the "import sounds folder" button and select a folder that contains audio files.
The folder you select can also contain subfolders with audio files.
Example:
```
@@ -200,12 +201,13 @@ Example:
```
In the above example the folder `samples` contains 2 subfolders `swoop` and `smash`, which contain audio files.
If you select that `samples` folder, the `user` tab (next to the import sounds button) will then contain 2 new sounds: `swoop(3) smash(3)`
If you select that `samples` folder, the `user` tab (next to the `import-sounds` tab) will then contain 2 new sounds: `swoop(3) smash(3)`
The individual samples can the be played normally like `s("swoop:0 swoop:1 smash:2")`.
The samples within each sound use zero-based indexing in alphabetical order.
## From Disk via @strudel/sampler
Instead of loading your samples into your browser with the "import sounds" button, you can also serve the samples from a local file server.
Instead of loading your samples into your browser with the "import sounds folder" button, you can also serve the samples from a local file server.
The easiest way to do this is using [@strudel/sampler](https://www.npmjs.com/package/@strudel/sampler):
```sh
+11 -18
View File
@@ -296,23 +296,16 @@ The whole modulation will now take 8 cycles to repeat.
## Recap
| Name | Description | Example |
| ------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- |
| lpf | Low-pass filter, cuts high frequencies | <MiniRepl client:visible tune={`note("c2 c3 c2 c3").s("sawtooth").lpf("<400 2000>")`} /> |
| vowel | Vowel-like filter | <MiniRepl client:visible tune={`note("c3 eb3 g3").s("sawtooth").vowel("<a e i o>")`} /> |
| gain | Adjusts volume/amplitude | <MiniRepl client:visible tune={`s("hh*16").gain("[.25 1]*2")`} /> |
| attack | ADSR: Time to reach full volume | <MiniRepl client:visible tune={`note("c3").s("sawtooth").attack(.5)`} /> |
| decay | ADSR: Time to reach sustain level | <MiniRepl client:visible tune={`note("c3").s("sawtooth").decay(.5)`} /> |
| sustain | ADSR: Volume level while note is held | <MiniRepl client:visible tune={`note("c3").s("sawtooth").sustain(.5)`} /> |
| release | ADSR: Time to fade out after note ends | <MiniRepl client:visible tune={`note("c3").s("sawtooth").release(.5)`} /> |
| adsr | ADSR: Short notation (attack:decay:sustain:release) | <MiniRepl client:visible tune={`note("c3").s("sawtooth").adsr(".1:.2:.5:.3")`} /> |
| delay | Creates echoes | <MiniRepl client:visible tune={`s("bd rim bd cp").delay(.5)`} /> |
| room | Simulates room reverb | <MiniRepl client:visible tune={`s("bd rim bd cp").room(.5)`} /> |
| pan | Pans sound left (0) to right (1) | <MiniRepl client:visible tune={`s("bd rim bd cp").pan("0 1")`} /> |
| speed | Changes playback speed (and pitch) | <MiniRepl client:visible tune={`s("bd rim bd cp").speed("<1 2 -1 -2>")`} /> |
| fast | Speeds up pattern playback (outside mini-notation) | <MiniRepl client:visible tune={`s("bd sd").fast(2)`} /> |
| slow | Slows down pattern playback (outside mini-notation)| <MiniRepl client:visible tune={`s("bd sd").slow(2)`} /> |
| signals | Modulate parameters with waveforms (`sine`, etc.) | `sine`, `saw`, `square`, `tri`, `rand`, `perlin`<br/><MiniRepl client:visible tune={`s("hh*16").gain(saw)`} /> |
| range | Set min/max values for signal modulation | <MiniRepl client:visible tune={`s("hh*16").lpf(saw.range(200,4000))`} /> |
| name | example |
| ------- | ---------------------------------------------------------------------------------------------------------------- |
| lpf | <MiniRepl client:visible tune={`note("c2 c3 c2 c3").s("sawtooth").lpf("<400 2000>")`} /> |
| vowel | <MiniRepl client:visible tune={`note("c3 eb3 g3").s("sawtooth").vowel("<a e i o>")`} /> |
| gain | <MiniRepl client:visible tune={`s("hh*16").gain("[.25 1]*2")`} /> |
| delay | <MiniRepl client:visible tune={`s("bd rim bd cp").delay(.5)`} /> |
| room | <MiniRepl client:visible tune={`s("bd rim bd cp").room(.5)`} /> |
| pan | <MiniRepl client:visible tune={`s("bd rim bd cp").pan("0 1")`} /> |
| speed | <MiniRepl client:visible tune={`s("bd rim bd cp").speed("<1 2 -1 -2>")`} /> |
| signals | `sine`, `saw`, `square`, `tri`, `rand`, `perlin`<br/><MiniRepl client:visible tune={`s("hh*16").gain (saw)`} /> |
| range | <MiniRepl client:visible tune={`s("hh*16").lpf(saw.range(200,4000))`} /> |
Let us now take a look at some of Tidal's typical [pattern effects](/workshop/pattern-effects).
+6 -8
View File
@@ -305,14 +305,12 @@ What's the difference?
Let's recap what we've learned in this chapter:
| Concept | Syntax | Description | Example |
| --------------- | ------ | ---------------------------------------------- | -------------------------------------------------------------------------- |
| Slow down | / | Divide a sequence over multiple cycles | <MiniRepl client:visible tune={`note("[c a f e]/2")`} /> |
| Alternate | \<\> | Play one element per cycle from a sequence | <MiniRepl client:visible tune={`note("c a f <e g>")`} /> |
| Elongate | @ | Extend the duration of an element | <MiniRepl client:visible tune={`note("c@3 e")`} /> |
| Replicate | ! | Repeat an element multiple times | <MiniRepl client:visible tune={`note("c!3 e")`} /> |
| Switch sounds | space | Cycle through sounds within the `sound` func | <MiniRepl client:visible tune={`note("c e").sound("piano sawtooth")`} /> |
| Stack sounds | , | Play multiple sounds together in `sound` func | <MiniRepl client:visible tune={`note("c e").sound("piano, sawtooth")`} /> |
| Concept | Syntax | Example |
| --------- | ------ | -------------------------------------------------------- |
| Slow down | \/ | <MiniRepl client:visible tune={`note("[c a f e]/2")`} /> |
| Alternate | \<\> | <MiniRepl client:visible tune={`note("c a f <e g>")`} /> |
| Elongate | @ | <MiniRepl client:visible tune={`note("c@3 e")`} /> |
| Replicate | ! | <MiniRepl client:visible tune={`note("c!3 e")`} /> |
New functions:
+9 -10
View File
@@ -270,17 +270,16 @@ This is shorter and more readable than:
Now we've learned the basics of the so called Mini-Notation, the rhythm language of Tidal.
This is what we've learned so far:
| Concept | Syntax | Description | Example |
| ----------------- | -------- | ----------------------------------------- | ----------------------------------------------------------------------- |
| Sequence | space | Play sounds one after another | <MiniRepl client:visible tune={`sound("bd bd sd hh bd cp sd hh")`} /> |
| Sample Number | :x | Select a specific sample from a sound | <MiniRepl client:visible tune={`sound("hh:0 hh:1 hh:2 hh:3")`} /> |
| Rests | - or ~ | Add silence into the sequence | <MiniRepl client:visible tune={`sound("metal ~ jazz jazz:1")`} /> |
| Concept | Syntax | Example |
| ----------------- | -------- | ----------------------------------------------------------------------- |
| Sequence | space | <MiniRepl client:visible tune={`sound("bd bd sd hh")`} /> |
| Sample Number | :x | <MiniRepl client:visible tune={`sound("hh:0 hh:1 hh:2 hh:3")`} /> |
| Rests | - or ~ | <MiniRepl client:visible tune={`sound("metal - jazz jazz:1")`} /> |
| Alternate | \<\> | <MiniRepl client:visible tune={`sound("<bd hh rim oh bd rim>")`} /> |
| Sub-Sequences | \[\] | Group elements, squished into their space | <MiniRepl client:visible tune={`sound("bd wind [metal jazz] hh")`} /> |
| Sub-Sub-Sequences | \[\[\]\] | Nest groups within groups | <MiniRepl client:visible tune={`sound("bd [metal [jazz sd]]")`} /> |
| Speed up | \* | Make elements play faster | <MiniRepl client:visible tune={`sound("bd sd*2 cp*3")`} /> |
| Parallel | , | Play multiple sequences at the same time | <MiniRepl client:visible tune={`sound("bd*2, hh*2 [hh oh]")`} /> |
| Multiple-Lines | \`\` | Write sequences across multiple lines | <MiniRepl client:visible tune={'sound(`bd*2, \n hh*4`)'} /> |
| Sub-Sequences | \[\] | <MiniRepl client:visible tune={`sound("bd wind [metal jazz] hh")`} /> |
| Sub-Sub-Sequences | \[\[\]\] | <MiniRepl client:visible tune={`sound("bd [metal [jazz [sd cp]]]")`} /> |
| Speed up | \* | <MiniRepl client:visible tune={`sound("bd sd*2 cp*3")`} /> |
| Parallel | , | <MiniRepl client:visible tune={`sound("bd*2, hh*2 [hh oh]")`} /> |
The Mini-Notation is usually used inside some function. These are the functions we've seen so far:
+24 -31
View File
@@ -11,20 +11,19 @@ This page is just a listing of all functions covered in the workshop!
## Mini Notation
| Concept | Syntax | Description | Example |
| ----------------- | -------- | ----------------------------------------- | ----------------------------------------------------------------------- |
| Sequence | space | Play sounds one after another | <MiniRepl client:visible tune={`sound("bd bd sd hh bd cp sd hh")`} /> |
| Sample Number | :x | Select a specific sample from a sound | <MiniRepl client:visible tune={`sound("hh:0 hh:1 hh:2 hh:3")`} /> |
| Rests | ~ | Add silence into the sequence | <MiniRepl client:visible tune={`sound("metal ~ jazz jazz:1")`} /> |
| Sub-Sequences | \[\] | Group elements, squished into their space | <MiniRepl client:visible tune={`sound("bd wind [metal jazz] hh")`} /> |
| Sub-Sub-Sequences | \[\[\]\] | Nest groups within groups | <MiniRepl client:visible tune={`sound("bd [metal [jazz sd]]")`} /> |
| Speed up | \* | Make elements play faster | <MiniRepl client:visible tune={`sound("bd sd*2 cp*3")`} /> |
| Parallel | , | Play multiple sequences at the same time | <MiniRepl client:visible tune={`sound("bd*2, hh*2 [hh oh]")`} /> |
| Slow down | \/ | Divide a sequence over multiple cycles | <MiniRepl client:visible tune={`note("[c a f e]/2")`} /> |
| Alternate | \<\> | Play one element per cycle from a sequence| <MiniRepl client:visible tune={`note("c <e g>")`} /> |
| Elongate | @ | Extend the duration of an element | <MiniRepl client:visible tune={`note("c@3 e")`} /> |
| Replicate | ! | Repeat an element multiple times | <MiniRepl client:visible tune={`note("c!3 e")`} /> |
| Multiple-Lines | \`\` | Write sequences across multiple lines | <MiniRepl client:visible tune={'sound(`bd*2, \n hh*4`)'} /> |
| Concept | Syntax | Example |
| ----------------- | -------- | --------------------------------------------------------------------- |
| Sequence | space | <MiniRepl client:visible tune={`sound("bd bd sd hh bd cp sd hh")`} /> |
| Sample Number | :x | <MiniRepl client:visible tune={`sound("hh:0 hh:1 hh:2 hh:3")`} /> |
| Rests | ~ | <MiniRepl client:visible tune={`sound("metal ~ jazz jazz:1")`} /> |
| Sub-Sequences | \[\] | <MiniRepl client:visible tune={`sound("bd wind [metal jazz] hh")`} /> |
| Sub-Sub-Sequences | \[\[\]\] | <MiniRepl client:visible tune={`sound("bd [metal [jazz sd]]")`} /> |
| Speed up | \* | <MiniRepl client:visible tune={`sound("bd sd*2 cp*3")`} /> |
| Parallel | , | <MiniRepl client:visible tune={`sound("bd*2, hh*2 [hh oh]")`} /> |
| Slow down | \/ | <MiniRepl client:visible tune={`note("[c a f e]/2")`} /> |
| Alternate | \<\> | <MiniRepl client:visible tune={`note("c <e g>")`} /> |
| Elongate | @ | <MiniRepl client:visible tune={`note("c@3 e")`} /> |
| Replicate | ! | <MiniRepl client:visible tune={`note("c!3 e")`} /> |
## Sounds
@@ -44,26 +43,20 @@ This page is just a listing of all functions covered in the workshop!
## Audio Effects
| Name | Description | Example |
| ------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- |
| lpf | Low-pass filter, cuts high frequencies | <MiniRepl client:visible tune={`note("c2 c3 c2 c3").s("sawtooth").lpf("400 2000")`} /> |
| vowel | Vowel-like filter | <MiniRepl client:visible tune={`note("c3 eb3 g3").s("sawtooth").vowel("<a e i o>")`} /> |
| gain | Adjusts volume/amplitude | <MiniRepl client:visible tune={`s("hh*16").gain("[.25 1]*2")`} /> |
| attack | ADSR: Time to reach full volume | <MiniRepl client:visible tune={`note("c3").s("sawtooth").attack(.5)`} /> |
| decay | ADSR: Time to reach sustain level | <MiniRepl client:visible tune={`note("c3").s("sawtooth").decay(.5)`} /> |
| sustain | ADSR: Volume level while note is held | <MiniRepl client:visible tune={`note("c3").s("sawtooth").sustain(.5)`} /> |
| release | ADSR: Time to fade out after note ends | <MiniRepl client:visible tune={`note("c3").s("sawtooth").release(.5)`} /> |
| adsr | ADSR: Short notation (attack:decay:sustain:release) | <MiniRepl client:visible tune={`note("c3").s("sawtooth").adsr(".1:.2:.5:.3")`} /> |
| delay | Creates echoes | <MiniRepl client:visible tune={`s("bd rim bd cp").delay(.5)`} /> |
| room | Simulates room reverb | <MiniRepl client:visible tune={`s("bd rim bd cp").room(.5)`} /> |
| pan | Pans sound left (0) to right (1) | <MiniRepl client:visible tune={`s("bd rim bd cp").pan("0 1")`} /> |
| speed | Changes playback speed (and pitch) | <MiniRepl client:visible tune={`s("bd rim bd cp").speed("<1 2 -1 -2>")`} /> |
| signals | Modulate parameters with waveforms (`sine`, etc.) | `sine`, `saw`, `square`, `tri`, `rand`, `perlin`<br/><MiniRepl client:visible tune={`s("hh*16").gain(saw)`} /> |
| range | Set min/max values for signal modulation | <MiniRepl client:visible tune={`s("hh*16").lpf(saw.range(200,4000))`} /> |
| name | example |
| ----- | --------------------------------------------------------------------------------------- |
| lpf | <MiniRepl client:visible tune={`note("c2 c3 c2 c3").s("sawtooth").lpf("400 2000")`} /> |
| vowel | <MiniRepl client:visible tune={`note("c3 eb3 g3").s("sawtooth").vowel("<a e i o>")`} /> |
| gain | <MiniRepl client:visible tune={`s("hh*16").gain("[.25 1]*4")`} /> |
| delay | <MiniRepl client:visible tune={`s("bd rim bd cp").delay(.5)`} /> |
| room | <MiniRepl client:visible tune={`s("bd rim bd cp").room(.5)`} /> |
| pan | <MiniRepl client:visible tune={`s("bd rim bd cp").pan("0 1")`} /> |
| speed | <MiniRepl client:visible tune={`s("bd rim bd cp").speed("<1 2 -1 -2>")`} /> |
| range | <MiniRepl client:visible tune={`s("hh*32").lpf(saw.range(200,4000))`} /> |
## Pattern Effects
| Name | Description | Example |
| name | description | example |
| ---- | ----------------------------------- | ----------------------------------------------------------------------------------- |
| cpm | sets the tempo in cycles per minute | <MiniRepl client:visible tune={`sound("bd sd [~ bd] sd").cpm(45)`} /> |
| fast | speed up | <MiniRepl client:visible tune={`sound("bd sd [~ bd] sd").fast(2)`} /> |
@@ -20,25 +20,42 @@ export default function ImportSoundsButton({ onComplete }) {
});
return (
<label
style={{ alignItems: 'center' }}
className="flex bg-background ml-2 pl-2 pr-2 max-w-[300px] rounded-md hover:opacity-50 whitespace-nowrap cursor-pointer"
>
<input
disabled={isUploading}
ref={fileUploadRef}
id="audio_file"
style={{ display: 'none' }}
type="file"
directory=""
webkitdirectory=""
multiple
accept="audio/*, .wav, .mp3, .m4a, .flac, .aac, .ogg"
onChange={() => {
onChange();
}}
/>
{isUploading ? 'importing...' : 'import sounds'}
</label>
<div>
<label
style={{ alignItems: 'center', borderColor: 'red', border: 1 }}
className="flex bg-background p-4 w-fit rounded-xl hover:opacity-50 whitespace-nowrap cursor-pointer"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="size-6 mr-2"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M7.5 7.5h-.75A2.25 2.25 0 0 0 4.5 9.75v7.5a2.25 2.25 0 0 0 2.25 2.25h7.5a2.25 2.25 0 0 0 2.25-2.25v-7.5a2.25 2.25 0 0 0-2.25-2.25h-.75m0-3-3-3m0 0-3 3m3-3v11.25m6-2.25h.75a2.25 2.25 0 0 1 2.25 2.25v7.5a2.25 2.25 0 0 1-2.25 2.25h-7.5a2.25 2.25 0 0 1-2.25-2.25v-.75"
/>
</svg>
<input
disabled={isUploading}
ref={fileUploadRef}
id="audio_file"
style={{ display: 'none' }}
type="file"
directory=""
webkitdirectory=""
multiple
accept="audio/*, .wav, .mp3, .m4a, .flac, .aac, .ogg"
onChange={() => {
onChange();
}}
/>
{isUploading ? 'importing...' : 'import sounds folder'}
</label>
</div>
);
}
@@ -14,6 +14,8 @@ export function SoundsTab() {
const sounds = useStore(soundMap);
const { soundsFilter } = useSettings();
const [search, setSearch] = useState('');
const { BASE_URL } = import.meta.env;
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
const soundEntries = useMemo(() => {
if (!sounds) {
@@ -37,6 +39,9 @@ export function SoundsTab() {
if (soundsFilter === 'synths') {
return filtered.filter(([_, { data }]) => ['synth', 'soundfont'].includes(data.type));
}
if (soundsFilter === 'importSounds') {
return [];
}
return filtered;
}, [sounds, soundsFilter, search]);
@@ -51,7 +56,6 @@ export function SoundsTab() {
ref?.stop(getAudioContext().currentTime + 0.01);
});
});
return (
<div id="sounds-tab" className="px-4 flex flex-col w-full h-full text-foreground">
<Textbox placeholder="Search" value={search} onChange={(v) => setSearch(v)} />
@@ -65,9 +69,9 @@ export function SoundsTab() {
drums: 'drum-machines',
synths: 'Synths',
user: 'User',
importSounds: 'import-sounds',
}}
></ButtonGroup>
<ImportSoundsButton onComplete={() => settingsMap.setKey('soundsFilter', 'user')} />
</div>
<div className="min-h-0 max-h-full grow overflow-auto text-sm break-normal pb-2">
@@ -101,7 +105,55 @@ export function SoundsTab() {
</span>
);
})}
{!soundEntries.length ? 'No custom sounds loaded in this pattern (yet).' : ''}
{!soundEntries.length && soundsFilter === 'importSounds' ? (
<div className="prose dark:prose-invert min-w-full pt-2 pb-8 px-4">
<ImportSoundsButton onComplete={() => settingsMap.setKey('soundsFilter', 'user')} />
<p>
To import sounds into strudel, they must be contained{' '}
<a href={`${baseNoTrailing}/learn/samples/#from-disk-via-import-sounds-folder`} target="_blank">
within a folder or subfolder
</a>
. The best way to do this is to upload a samples folder containing subfolders of individual sounds or
soundbanks (see diagram below).{' '}
</p>
<pre className="bg-background" key={'sample-diagram'}>
{`└─ samples <-- import this folder
├─ swoop
│ ├─ swoopshort.wav
│ ├─ swooplong.wav
│ └─ swooptight.wav
└─ smash
├─ smashhigh.wav
├─ smashlow.wav
└─ smashmiddle.wav`}
</pre>
<p>
The name of a subfolder corresponds to the sound name under the user tab. Multiple samples within a
subfolder are all labelled with the same name, but can be accessed using .n( ) - remember sounds are
zero-indexed and in alphabetical order!
</p>
<p>
For more information, and other ways to use your own sounds in strudel,{' '}
<a href={`${baseNoTrailing}/learn/samples/#from-disk-via-import-sounds-folder`} target="_blank">
check out the docs
</a>
!
</p>
<h3>Preview Sounds</h3>
<pre className="bg-background" key={'sample-preview'}>
n("0 1 2 3 4 5").s("sample-name")
</pre>
<p>
Paste the line above into the main editor to hear the uploaded folder. Remember to use the name of your
sample as it appears under the "user" tab.
</p>
</div>
) : (
''
)}
{!soundEntries.length && soundsFilter !== 'importSounds'
? 'No custom sounds loaded in this pattern (yet).'
: ''}
</div>
</div>
);
+11 -13
View File
@@ -54,25 +54,23 @@ export function registerSamplesFromDB(config = userSamplesDBConfig, onComplete =
splitRelativePath[splitRelativePath.length - 2] ?? soundFile.id.split(/\W+/)[0] ?? 'user';
const blob = soundFile.blob;
// Files used to be uploaded as base64 strings, After Jan 1 2025 this check can be safely deleted
if (typeof blob === 'string') {
const soundPaths = sounds.get(parentDirectory) ?? new Set();
soundPaths.add(blob);
sounds.set(parentDirectory, soundPaths);
return;
}
return blobToDataUrl(blob).then((soundPath) => {
const soundPaths = sounds.get(parentDirectory) ?? new Set();
soundPaths.add(soundPath);
sounds.set(parentDirectory, soundPaths);
const titlePathMap = sounds.get(parentDirectory) ?? new Map();
titlePathMap.set(title, soundPath);
sounds.set(parentDirectory, titlePathMap);
return;
});
}),
)
.then(() => {
sounds.forEach((soundPaths, key) => {
const value = Array.from(soundPaths);
sounds.forEach((titlePathMap, key) => {
const value = Array.from(titlePathMap.keys())
.sort((a, b) => {
return a.localeCompare(b);
})
.map((title) => titlePathMap.get(title));
registerSound(key, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, value), {
type: 'sample',