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
30 changed files with 223 additions and 143 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
@@ -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',