Compare commits

..

100 Commits

Author SHA1 Message Date
Felix Roos b04a262433 Publish
- @strudel/codemirror@0.11.0
 - @strudel/core@0.11.0
 - @strudel/csound@0.11.0
 - @strudel/embed@0.11.0
 - @strudel/hydra@0.11.0
 - @strudel/midi@0.11.0
 - @strudel/mini@0.11.0
 - @strudel/osc@0.11.0
 - @strudel/repl@0.11.0
 - @strudel/serial@0.11.0
 - @strudel/soundfonts@0.11.0
 - @strudel/tonal@0.11.0
 - @strudel/transpiler@0.11.0
 - @strudel/web@0.11.0
 - @strudel/webaudio@0.11.0
 - @strudel/xen@0.11.0
2024-01-18 09:59:57 +01:00
Felix Roos 96bafa7f0b the big rename: @strudel.cycles/* -> @strudel/* 2024-01-18 09:54:37 +01:00
Felix Roos b10612da5c Publish
- @strudel/codemirror@0.10.0
 - @strudel.cycles/core@0.10.0
 - @strudel.cycles/csound@0.10.0
 - @strudel.cycles/embed@0.10.0
 - @strudel/hydra@0.10.0
 - @strudel.cycles/midi@0.10.0
 - @strudel.cycles/mini@0.10.0
 - @strudel.cycles/osc@0.10.0
 - @strudel/repl@0.10.0
 - @strudel.cycles/serial@0.10.0
 - @strudel.cycles/soundfonts@0.10.0
 - superdough@0.10.0
 - @strudel.cycles/tonal@0.10.0
 - @strudel.cycles/transpiler@0.10.0
 - @strudel/web@0.10.0
 - @strudel.cycles/webaudio@0.10.0
 - @strudel.cycles/xen@0.10.0
2024-01-18 09:35:20 +01:00
Felix Roos 171f4206a8 Merge branch 'main' into deprecate-old-package-names 2024-01-18 09:33:16 +01:00
Felix Roos bd83d19197 Merge pull request #913 from tidalcycles/pitch-envelopes
pitch envelope
2024-01-18 06:54:33 +01:00
Felix Roos 95cf201c6d fix: heading 2024-01-18 06:49:09 +01:00
Felix Roos 4107711dae snapshot 2024-01-18 06:48:21 +01:00
Felix Roos 67f0758cb5 document pitch envelope 2024-01-18 06:47:54 +01:00
Felix Roos 1c99944a32 pitch envelope now also works when setting one of the controls (penv not needed) 2024-01-18 06:47:44 +01:00
Felix Roos d3ea4959b4 - more flexible pitch envelopes
- fix: exponential envelopes could break due to 0 values
2024-01-18 06:47:12 +01:00
Felix Roos 70b27fbff2 Merge pull request #916 from oscarbyrne/fix-swatch-static-paths
Fix: swatch/[name].png.js static path
2024-01-18 05:09:38 +01:00
Oscar Byrne 96c2144857 Use astro Response class for swatch/[name].png.js endpoint 2024-01-17 17:09:11 -08:00
Felix Roos 0655af4d4f add pcurve 2024-01-17 17:35:35 +01:00
Felix Roos 3fc0fdbe62 remove todo 2024-01-16 17:57:52 +01:00
Felix Roos fd73571744 make sure the sustained pitch is the selected note 2024-01-16 17:56:38 +01:00
Felix Roos 80d861c12c Merge pull request #907 from fnordomat/more-vowels
Add more vowel qualities for the vowels function
2024-01-16 08:22:27 +01:00
Felix Roos 3506de8d1a vibrato for soundfonts
+ add getVibratoOscillator helper
2024-01-16 00:07:56 +01:00
Felix Roos 32456d6966 pitch envelope for sampler and soundfonts
+ added getPitchEnvelope helper
2024-01-15 23:55:49 +01:00
Felix Roos bdc2af9733 basic pitch envelope 2024-01-15 23:25:22 +01:00
Felix Roos a7daf41934 Merge pull request #911 from tidalcycles/blog
adds a blog
2024-01-14 23:56:36 +01:00
Felix Roos fe6ff2528a Merge pull request #894 from rjulian/feat/add-header-workshop
Remove hideHeader for better mobile UI and consistency
2024-01-14 23:52:41 +01:00
Felix Roos 7ceb8708b0 fix: do not format autogenerated file 2024-01-14 23:47:50 +01:00
Felix Roos 4d22d957a1 add more videos to showcase 2024-01-14 23:45:39 +01:00
Felix Roos 79f1a863b6 add date + author to posts + dedupe headings 2024-01-14 23:39:03 +01:00
Felix Roos 98db3d37ef add anchor links 2024-01-14 23:30:26 +01:00
Felix Roos c02976782b add video component 2024-01-14 23:22:10 +01:00
Felix Roos ceb4376eb8 format autogenerated types 2024-01-14 23:16:44 +01:00
Felix Roos 0566b7bcb3 basic blog with release notes 2024-01-14 23:16:09 +01:00
Jade (Rose) Rowland 6d0ecb9f5e Merge pull request #868 from daslyfe/envelope_improvements
Further Envelope improvements
2024-01-14 16:43:06 -05:00
Felix Roos 0901948564 fanchor default to 0.5 for now 2024-01-14 21:32:38 +01:00
Felix Roos e6d028fd6b soundfont mixing 2024-01-14 16:27:11 +01:00
Alex McLean 945e7082cd Update metadata.mdx 2024-01-14 10:15:40 +00:00
Alex McLean 0d6bdfb514 Link to creative commons chooser 2024-01-14 10:14:43 +00:00
fnordomat 70712bdab6 fix codeformat 2024-01-14 10:19:49 +01:00
Felix Roos 53771d7f95 hotfix: avoid crash for anonymous patterns 2024-01-14 01:50:48 +01:00
Felix Roos 6d32d4024d hotfix: fix built version loading at runtime.. 2024-01-14 01:08:01 +01:00
Felix Roos f387589342 hotfix: fix build error 2024-01-14 00:53:43 +01:00
Felix Roos 47a5080352 Merge pull request #910 from tidalcycles/browse
public sharing
2024-01-14 00:48:03 +01:00
Felix Roos 5314c83534 delete browse page for now 2024-01-14 00:02:27 +01:00
Felix Roos 204c964050 integrate public patterns into patterns tab 2024-01-13 23:53:46 +01:00
Felix Roos 1ede99f71e ask for public sharing (very cheap) + order browse list 2024-01-13 23:12:22 +01:00
Felix Roos 3c1844046a basic browse page 2024-01-13 23:01:16 +01:00
Felix Roos eefa90a2bc fix: synth default envelope 2024-01-13 22:24:59 +01:00
fnordomat efa0737660 added some aliases for the vowels (cosmetics) 2024-01-13 15:11:30 +01:00
fnordomat 6232560273 document extra vowels in quick reference 2024-01-13 11:11:31 +01:00
fnordomat e051d383a0 add 10 new vowel qualities to formant table 2024-01-13 11:11:31 +01:00
Felix Roos 4fb5c1828d i have no clue why this works 2024-01-13 01:16:26 +01:00
Felix Roos 6f3b2fa66b negative fenv values now stay in the same range as positives 2024-01-13 00:26:16 +01:00
Felix Roos f17459fdc3 Merge branch 'main' into envelope_improvements 2024-01-12 19:41:05 +01:00
Felix Roos a053f40e59 Merge pull request #902 from tidalcycles/build-warnings
fix some build warnings
2024-01-12 18:31:41 +01:00
Felix Roos 9a0459fdd3 move non pages out of pages dir 2024-01-12 18:16:57 +01:00
Felix Roos 46a859736a Merge pull request #891 from daslyfe/remove_jsx_exports_for_vite
prevent vite from complaining about additional exports in jsx files
2024-01-12 18:01:55 +01:00
Felix Roos e4524a2be9 Merge pull request #892 from daslyfe/update_vite
Update Vite version so hot reload works properly with newest pnpm version
2024-01-12 17:05:46 +01:00
Felix Roos f3ee1cc947 Merge pull request #886 from tidalcycles/polymeter-slowcat
support , in < >
2024-01-12 15:09:40 +01:00
Felix Roos 86bc28dfb3 add deprecation notes 2024-01-11 20:46:22 +01:00
Felix Roos 1bc0934228 Merge branch 'main' into envelope_improvements 2024-01-10 15:15:22 +01:00
Felix Roos f26fd18c7a use 0.001 as linear mintime as well (to prevent cracks) 2024-01-10 15:02:10 +01:00
Felix Roos c68dba8c21 fix: use end 2024-01-10 14:28:38 +01:00
Felix Roos f768e1eece Merge pull request #898 from tidalcycles/fix-autocomplete
fix: autocomplete / tooltip code example bug
2024-01-10 13:55:39 +01:00
Felix Roos f99557cc05 fix: fanchor + default fanchor to 0 (breaking change) 2024-01-10 00:08:49 +01:00
Felix Roos f785823e00 add fenv to filter cutoff controls 2024-01-10 00:08:13 +01:00
Felix Roos 62cd2263c2 add silent flag to nanFallback 2024-01-10 00:03:18 +01:00
Felix Roos 7157634db0 fix: autocomplete / tooltip code example bug 2024-01-09 22:34:16 +01:00
Jade (Rose) Rowland 86442adb20 prettier 2024-01-06 15:09:09 -05:00
Richard Julian 569137eb22 Formatting for passing CI 2024-01-06 12:05:39 -08:00
Jade (Rose) Rowland 5e3a37c9e8 init 2024-01-06 14:55:45 -05:00
Richard Julian 4651066472 Remove hideHeader for better mobile UI and consistency 2024-01-06 11:55:00 -08:00
Jade (Rose) Rowland 8d325b96ea init 2024-01-06 14:49:52 -05:00
Jade (Rose) Rowland 150a1b8eed restore buffer hold behavior 2024-01-05 17:45:09 -05:00
Jade (Rose) Rowland b2f63897f9 change default FM env to exp because it modulates frequency and sounds way better :) 2024-01-05 01:07:54 -05:00
Jade (Rose) Rowland 2ee392be9b fixed all the things 2024-01-05 01:00:22 -05:00
Felix Roos a2974099c1 add dec synonym for decay 2024-01-03 23:06:23 +01:00
Felix Roos 041a809b07 add ar function 2024-01-03 22:41:03 +01:00
Felix Roos 2ecc6b30aa add ad function 2024-01-03 22:37:35 +01:00
Felix Roos d582bbbb60 fix: format 2024-01-03 22:17:11 +01:00
Felix Roos ca3eda8fc7 support , in < > 2024-01-03 21:54:10 +01:00
Jade (Rose) Rowland 83c820a18c prettier again 2024-01-01 18:50:23 -05:00
Jade (Rose) Rowland 3eface706c Merge branch 'tidalcycles:main' into envelope_improvements 2024-01-01 18:47:28 -05:00
Jade (Rose) Rowland 9756d63cf1 prettier 2023-12-31 11:58:06 -05:00
Jade (Rose) Rowland a800b32529 Merge branch 'main' into envelope_improvements 2023-12-31 11:51:49 -05:00
Jade (Rose) Rowland 12451ba5fc Merge branch 'tidalcycles:main' into envelope_improvements 2023-12-29 22:27:52 -05:00
Jade (Rose) Rowland 77fee0b866 fixed popping on font envelope 2023-12-20 17:39:11 -05:00
Jade (Rose) Rowland 5671b40707 account for phase complete 2023-12-20 15:24:14 -05:00
Jade (Rose) Rowland dec039ead3 fixed filter envelope popping... 2023-12-20 13:09:04 -05:00
Jade (Rose) Rowland fb81f6f268 still working on popping issue with firefox 2023-12-20 10:50:06 -05:00
Jade (Rose) Rowland deb973afa5 fixed hold behavior 2023-12-20 10:17:12 -05:00
Jade (Rose) Rowland 2dea3911ba remove unused variable 2023-12-20 09:29:07 -05:00
Jade (Rose) Rowland e0a7fb8290 filter should decay to set frequency 2023-12-20 09:28:40 -05:00
Jade (Rose) Rowland 30e402b9f1 fixed release bug 2023-12-20 01:52:43 -05:00
Jade (Rose) Rowland f6d9ad51c6 trying to fix divergent firefox behavior 2023-12-20 01:44:34 -05:00
Jade (Rose) Rowland 34ba81a841 fixed test complaint 2023-12-20 00:25:56 -05:00
Jade (Rose) Rowland d7fae2620e create release audio param method, make volume envelopes consistant 2023-12-20 00:21:14 -05:00
Jade Rowland fe60f34015 removed accidental file commit 2023-12-17 12:13:05 -05:00
Jade Rowland b5dc65d52a format 2023-12-16 13:36:05 -05:00
Jade Rowland 93a4efcf6d updated fontloader 2023-12-16 13:21:23 -05:00
Jade Rowland 96e0cce2d2 improve response 2023-12-16 00:30:35 -05:00
Jade Rowland 88f677e910 fix envelope behavior 2023-12-15 18:54:04 -05:00
Jade (Rose) Rowland 9f50f6ef0e Merge branch 'tidalcycles:main' into envelope_improvements 2023-12-15 18:27:23 -05:00
Jade Rowland 9663c2ec85 fileter envelopes 2023-12-15 12:16:28 -07:00
Jade Rowland 7da7554493 it works 2023-12-15 11:32:23 -07:00
149 changed files with 3226 additions and 1548 deletions
+1
View File
@@ -10,3 +10,4 @@ paper
pnpm-lock.yaml pnpm-lock.yaml
pnpm-workspace.yaml pnpm-workspace.yaml
**/dev-dist **/dev-dist
website/.astro
+1 -1
View File
@@ -114,7 +114,7 @@ You can run the same check with `pnpm check`
## Package Workflow ## Package Workflow
The project is split into multiple [packages](https://github.com/tidalcycles/strudel/tree/main/packages) with independent versioning. The project is split into multiple [packages](https://github.com/tidalcycles/strudel/tree/main/packages) with independent versioning.
When you run `pnpm i` on the root folder, [pnpm workspaces](https://pnpm.io/workspaces) will install all dependencies of all subpackages. This will allow any js file to import `@strudel.cycles/<package-name>` to get the local version, When you run `pnpm i` on the root folder, [pnpm workspaces](https://pnpm.io/workspaces) will install all dependencies of all subpackages. This will allow any js file to import `@strudel/<package-name>` to get the local version,
allowing to develop multiple packages at the same time. allowing to develop multiple packages at the same time.
## Package Publishing ## Package Publishing
+1 -1
View File
@@ -7,7 +7,7 @@
/> />
<div id="output"></div> <div id="output"></div>
<script type="module"> <script type="module">
const strudel = await import('https://cdn.skypack.dev/@strudel.cycles/core@0.6.8'); const strudel = await import('https://cdn.skypack.dev/@strudel/core@0.6.8');
Object.assign(window, strudel); // assign all strudel functions to global scope to use with eval Object.assign(window, strudel); // assign all strudel functions to global scope to use with eval
const input = document.getElementById('text'); const input = document.getElementById('text');
const getEvents = () => { const getEvents = () => {
+1 -1
View File
@@ -8,7 +8,7 @@
/> />
<canvas id="canvas"></canvas> <canvas id="canvas"></canvas>
<script type="module"> <script type="module">
const strudel = await import('https://cdn.skypack.dev/@strudel.cycles/core@0.6.8'); const strudel = await import('https://cdn.skypack.dev/@strudel/core@0.6.8');
// this adds all strudel functions to the global scope, to be used by eval // this adds all strudel functions to the global scope, to be used by eval
Object.assign(window, strudel); Object.assign(window, strudel);
// setup elements // setup elements
+9 -9
View File
@@ -1,11 +1,11 @@
import { StrudelMirror } from '@strudel/codemirror'; import { StrudelMirror } from '@strudel/codemirror';
import { funk42 } from './tunes'; import { funk42 } from './tunes';
import { drawPianoroll, evalScope, controls } from '@strudel.cycles/core'; import { drawPianoroll, evalScope, controls } from '@strudel/core';
import './style.css'; import './style.css';
import { initAudioOnFirstClick } from '@strudel.cycles/webaudio'; import { initAudioOnFirstClick } from '@strudel/webaudio';
import { transpiler } from '@strudel.cycles/transpiler'; import { transpiler } from '@strudel/transpiler';
import { getAudioContext, webaudioOutput, registerSynthSounds } from '@strudel.cycles/webaudio'; import { getAudioContext, webaudioOutput, registerSynthSounds } from '@strudel/webaudio';
import { registerSoundfonts } from '@strudel.cycles/soundfonts'; import { registerSoundfonts } from '@strudel/soundfonts';
// init canvas // init canvas
const canvas = document.getElementById('roll'); const canvas = document.getElementById('roll');
@@ -26,10 +26,10 @@ const editor = new StrudelMirror({
initAudioOnFirstClick(); // needed to make the browser happy (don't await this here..) initAudioOnFirstClick(); // needed to make the browser happy (don't await this here..)
const loadModules = evalScope( const loadModules = evalScope(
controls, controls,
import('@strudel.cycles/core'), import('@strudel/core'),
import('@strudel.cycles/mini'), import('@strudel/mini'),
import('@strudel.cycles/tonal'), import('@strudel/tonal'),
import('@strudel.cycles/webaudio'), import('@strudel/webaudio'),
); );
await Promise.all([loadModules, registerSynthSounds(), registerSoundfonts()]); await Promise.all([loadModules, registerSynthSounds(), registerSoundfonts()]);
}, },
+6 -6
View File
@@ -13,11 +13,11 @@
}, },
"dependencies": { "dependencies": {
"@strudel/codemirror": "workspace:*", "@strudel/codemirror": "workspace:*",
"@strudel.cycles/core": "workspace:*", "@strudel/core": "workspace:*",
"@strudel.cycles/mini": "workspace:*", "@strudel/mini": "workspace:*",
"@strudel.cycles/soundfonts": "workspace:*", "@strudel/soundfonts": "workspace:*",
"@strudel.cycles/tonal": "workspace:*", "@strudel/tonal": "workspace:*",
"@strudel.cycles/transpiler": "workspace:*", "@strudel/transpiler": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*" "@strudel/webaudio": "workspace:*"
} }
} }
+7 -7
View File
@@ -1,6 +1,6 @@
import { controls, repl, evalScope } from '@strudel.cycles/core'; import { controls, repl, evalScope } from '@strudel/core';
import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel.cycles/webaudio'; import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel/webaudio';
import { transpiler } from '@strudel.cycles/transpiler'; import { transpiler } from '@strudel/transpiler';
import tune from './tune.mjs'; import tune from './tune.mjs';
const ctx = getAudioContext(); const ctx = getAudioContext();
@@ -10,10 +10,10 @@ initAudioOnFirstClick();
evalScope( evalScope(
controls, controls,
import('@strudel.cycles/core'), import('@strudel/core'),
import('@strudel.cycles/mini'), import('@strudel/mini'),
import('@strudel.cycles/webaudio'), import('@strudel/webaudio'),
import('@strudel.cycles/tonal'), import('@strudel/tonal'),
); );
const { evaluate } = repl({ const { evaluate } = repl({
+5 -5
View File
@@ -13,10 +13,10 @@
"vite": "^5.0.10" "vite": "^5.0.10"
}, },
"dependencies": { "dependencies": {
"@strudel.cycles/core": "workspace:*", "@strudel/core": "workspace:*",
"@strudel.cycles/mini": "workspace:*", "@strudel/mini": "workspace:*",
"@strudel.cycles/transpiler": "workspace:*", "@strudel/transpiler": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*", "@strudel/webaudio": "workspace:*",
"@strudel.cycles/tonal": "workspace:*" "@strudel/tonal": "workspace:*"
} }
} }
+7 -7
View File
@@ -1,5 +1,5 @@
{ {
"name": "@strudel.cycles/monorepo", "name": "@strudel/monorepo",
"version": "0.5.0", "version": "0.5.0",
"private": true, "private": true,
"description": "Port of tidalcycles to javascript", "description": "Port of tidalcycles to javascript",
@@ -45,12 +45,12 @@
}, },
"homepage": "https://strudel.cc", "homepage": "https://strudel.cc",
"dependencies": { "dependencies": {
"@strudel.cycles/core": "workspace:*", "@strudel/core": "workspace:*",
"@strudel.cycles/mini": "workspace:*", "@strudel/mini": "workspace:*",
"@strudel.cycles/tonal": "workspace:*", "@strudel/tonal": "workspace:*",
"@strudel.cycles/transpiler": "workspace:*", "@strudel/transpiler": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*", "@strudel/webaudio": "workspace:*",
"@strudel.cycles/xen": "workspace:*" "@strudel/xen": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"dependency-tree": "^10.0.9", "dependency-tree": "^10.0.9",
+1 -1
View File
@@ -1,5 +1,5 @@
# Packages # Packages
Each folder represents one of the @strudel.cycles/* packages [published to npm](https://www.npmjs.com/org/strudel.cycles). Each folder represents one of the @strudel/* packages [published to npm](https://www.npmjs.com/org/strudel).
To understand how those pieces connect, refer to the [Technical Manual](https://github.com/tidalcycles/strudel/wiki/Technical-Manual) or the individual READMEs. To understand how those pieces connect, refer to the [Technical Manual](https://github.com/tidalcycles/strudel/wiki/Technical-Manual) or the individual READMEs.
+7 -1
View File
@@ -3,6 +3,12 @@ import jsdoc from '../../doc.json';
import { autocompletion } from '@codemirror/autocomplete'; import { autocompletion } from '@codemirror/autocomplete';
import { h } from './html'; import { h } from './html';
function plaintext(str) {
const div = document.createElement('div');
div.innerText = str;
return div.innerHTML;
}
const getDocLabel = (doc) => doc.name || doc.longname; const getDocLabel = (doc) => doc.name || doc.longname;
const getInnerText = (html) => { const getInnerText = (html) => {
var div = document.createElement('div'); var div = document.createElement('div');
@@ -21,7 +27,7 @@ ${doc.description}
)} )}
</ul> </ul>
<div> <div>
${doc.examples?.map((example) => `<div><pre>${example}</pre></div>`)} ${doc.examples?.map((example) => `<div><pre>${plaintext(example)}</pre></div>`)}
</div> </div>
</div>`[0]; </div>`[0];
/* /*
-38
View File
@@ -1,38 +0,0 @@
// import { SelectionRange } from '@codemirror/state';
import { RectangleMarker } from '@codemirror/view';
import { layer } from '@codemirror/view';
// currently stuck with: https://discuss.codemirror.net/t/line-background-layer/7666
export const backlayer = layer({
update: (update) => {
return update.docChanged;
},
markers: (view) => {
const offsetTop = 14; // how to know this number ? .view.documentTop is scroll relative..
const offsetLeft = 4; // how to know this number ?
return view.viewportLineBlocks.map((block) => {
const { left } = view.coordsAtPos(block.to);
return new RectangleMarker('cm-backlayer', offsetLeft, block.top + offsetTop, left, block.height);
});
},
});
/* const len = view.state.doc.length;
const markers = RectangleMarker.forRange(
view,
'cm-backlayer',
SelectionRange.fromJSON({
from: 0,
to: len,
anchor: 0,
head: len,
empty: true,
assoc: -1,
bidiLevel: null,
}),
);
console.log('markser', markers);
return markers;
*/
+1 -3
View File
@@ -12,7 +12,7 @@ import {
lineNumbers, lineNumbers,
drawSelection, drawSelection,
} from '@codemirror/view'; } from '@codemirror/view';
import { Pattern, Drawer, repl, cleanupDraw } from '@strudel.cycles/core'; import { Pattern, Drawer, repl, cleanupDraw } from '@strudel/core';
import { isAutoCompletionEnabled } from './autocomplete.mjs'; import { isAutoCompletionEnabled } from './autocomplete.mjs';
import { isTooltipEnabled } from './tooltip.mjs'; import { isTooltipEnabled } from './tooltip.mjs';
import { flash, isFlashEnabled } from './flash.mjs'; import { flash, isFlashEnabled } from './flash.mjs';
@@ -21,7 +21,6 @@ import { keybindings } from './keybindings.mjs';
import { initTheme, activateTheme, theme } from './themes.mjs'; import { initTheme, activateTheme, theme } from './themes.mjs';
import { updateWidgets, sliderPlugin } from './slider.mjs'; import { updateWidgets, sliderPlugin } from './slider.mjs';
import { persistentAtom } from '@nanostores/persistent'; import { persistentAtom } from '@nanostores/persistent';
import { backlayer } from './backlayer.mjs';
const extensions = { const extensions = {
isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []), isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []),
@@ -77,7 +76,6 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
history(), history(),
EditorView.updateListener.of((v) => onChange(v)), EditorView.updateListener.of((v) => onChange(v)),
drawSelection({ cursorBlinkRate: 0 }), drawSelection({ cursorBlinkRate: 0 }),
backlayer,
Prec.highest( Prec.highest(
keymap.of([ keymap.of([
{ {
-1
View File
@@ -3,4 +3,3 @@ export * from './highlight.mjs';
export * from './flash.mjs'; export * from './flash.mjs';
export * from './slider.mjs'; export * from './slider.mjs';
export * from './themes.mjs'; export * from './themes.mjs';
export * from './backlayer.mjs';
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel/codemirror", "name": "@strudel/codemirror",
"version": "0.9.0", "version": "0.11.0",
"description": "Codemirror Extensions for Strudel", "description": "Codemirror Extensions for Strudel",
"main": "index.mjs", "main": "index.mjs",
"publishConfig": { "publishConfig": {
@@ -41,14 +41,14 @@
"@codemirror/state": "^6.4.0", "@codemirror/state": "^6.4.0",
"@codemirror/view": "^6.23.0", "@codemirror/view": "^6.23.0",
"@lezer/highlight": "^1.2.0", "@lezer/highlight": "^1.2.0",
"@nanostores/persistent": "^0.9.1",
"@replit/codemirror-emacs": "^6.0.1", "@replit/codemirror-emacs": "^6.0.1",
"@replit/codemirror-vim": "^6.1.0", "@replit/codemirror-vim": "^6.1.0",
"@replit/codemirror-vscode-keymap": "^6.0.2", "@replit/codemirror-vscode-keymap": "^6.0.2",
"@strudel.cycles/core": "workspace:*", "@strudel/core": "workspace:*",
"@uiw/codemirror-themes": "^4.21.21", "@uiw/codemirror-themes": "^4.21.21",
"@uiw/codemirror-themes-all": "^4.21.21", "@uiw/codemirror-themes-all": "^4.21.21",
"nanostores": "^0.9.5", "nanostores": "^0.9.5"
"@nanostores/persistent": "^0.9.1"
}, },
"devDependencies": { "devDependencies": {
"vite": "^5.0.10" "vite": "^5.0.10"
+1 -1
View File
@@ -1,4 +1,4 @@
import { ref, pure } from '@strudel.cycles/core'; import { ref, pure } from '@strudel/core';
import { WidgetType, ViewPlugin, Decoration } from '@codemirror/view'; import { WidgetType, ViewPlugin, Decoration } from '@codemirror/view';
import { StateEffect, StateField } from '@codemirror/state'; import { StateEffect, StateField } from '@codemirror/state';
+4 -4
View File
@@ -1,17 +1,17 @@
# @strudel.cycles/core # @strudel/core
This package contains the bare essence of strudel. This package contains the bare essence of strudel.
## Install ## Install
```sh ```sh
npm i @strudel.cycles/core --save npm i @strudel/core --save
``` ```
## Example ## Example
```js ```js
import { sequence } from '@strudel.cycles/core'; import { sequence } from '@strudel/core';
const pattern = sequence('a', ['b', 'c']); const pattern = sequence('a', ['b', 'c']);
@@ -33,7 +33,7 @@ b: 3/2 - 7/4
c: 7/4 - 2 c: 7/4 - 2
``` ```
- [play with @strudel.cycles/core on codesandbox](https://codesandbox.io/s/strudel-core-test-forked-9ywhv7?file=/src/index.js). - [play with @strudel/core on codesandbox](https://codesandbox.io/s/strudel-core-test-forked-9ywhv7?file=/src/index.js).
- [open color pattern example](https://raw.githack.com/tidalcycles/strudel/main/packages/core/examples/canvas.html) - [open color pattern example](https://raw.githack.com/tidalcycles/strudel/main/packages/core/examples/canvas.html)
- [open minimal repl example](https://raw.githack.com/tidalcycles/strudel/main/packages/core/examples/vanilla.html) - [open minimal repl example](https://raw.githack.com/tidalcycles/strudel/main/packages/core/examples/vanilla.html)
- [open minimal vite example](./examples/vite-vanilla-repl/) - [open minimal vite example](./examples/vite-vanilla-repl/)
+94 -8
View File
@@ -234,7 +234,7 @@ const generic_params = [
* note("c3 e3").decay("<.1 .2 .3 .4>").sustain(0) * note("c3 e3").decay("<.1 .2 .3 .4>").sustain(0)
* *
*/ */
['decay'], ['decay', 'dec'],
/** /**
* Amplitude envelope sustain level: The level which is reached after attack / decay, being sustained until the offset. * Amplitude envelope sustain level: The level which is reached after attack / decay, being sustained until the offset.
* *
@@ -270,7 +270,7 @@ const generic_params = [
* s("bd sd,hh*3").bpf("<1000 2000 4000 8000>") * s("bd sd,hh*3").bpf("<1000 2000 4000 8000>")
* *
*/ */
[['bandf', 'bandq'], 'bpf', 'bp'], [['bandf', 'bandq', 'bpenv'], 'bpf', 'bp'],
// TODO: in tidal, it seems to be normalized // TODO: in tidal, it seems to be normalized
/** /**
* Sets the **b**and-**p**ass **q**-factor (resonance). * Sets the **b**and-**p**ass **q**-factor (resonance).
@@ -481,7 +481,7 @@ const generic_params = [
* s("bd*8").lpf("1000:0 1000:10 1000:20 1000:30") * s("bd*8").lpf("1000:0 1000:10 1000:20 1000:30")
* *
*/ */
[['cutoff', 'resonance'], 'ctf', 'lpf', 'lp'], [['cutoff', 'resonance', 'lpenv'], 'ctf', 'lpf', 'lp'],
/** /**
* Sets the lowpass filter envelope modulation depth. * Sets the lowpass filter envelope modulation depth.
@@ -758,7 +758,7 @@ const generic_params = [
* .vibmod("<.25 .5 1 2 12>:8") * .vibmod("<.25 .5 1 2 12>:8")
*/ */
[['vibmod', 'vib'], 'vmod'], [['vibmod', 'vib'], 'vmod'],
[['hcutoff', 'hresonance'], 'hpf', 'hp'], [['hcutoff', 'hresonance', 'hpenv'], 'hpf', 'hp'],
/** /**
* Controls the **h**igh-**p**ass **q**-value. * Controls the **h**igh-**p**ass **q**-value.
* *
@@ -891,6 +891,82 @@ const generic_params = [
* *
*/ */
['freq'], ['freq'],
// pitch envelope
/**
* Attack time of pitch envelope.
*
* @name pattack
* @synonyms patt
* @param {number | Pattern} time time in seconds
* @example
* note("<c eb g bb>").pattack("<0 .1 .25 .5>")
*
*/
['pattack', 'patt'],
/**
* Decay time of pitch envelope.
*
* @name pdecay
* @synonyms pdec
* @param {number | Pattern} time time in seconds
* @example
* note("<c eb g bb>").pdecay("<0 .1 .25 .5>")
*
*/
['pdecay', 'pdec'],
// TODO: how to use psustain?!
['psustain', 'psus'],
/**
* Release time of pitch envelope
*
* @name prelease
* @synonyms prel
* @param {number | Pattern} time time in seconds
* @example
* note("<c eb g bb> ~")
* .release(.5) // to hear the pitch release
* .prelease("<0 .1 .25 .5>")
*
*/
['prelease', 'prel'],
/**
* Amount of pitch envelope. Negative values will flip the envelope.
* If you don't set other pitch envelope controls, `pattack:.2` will be the default.
*
* @name penv
* @param {number | Pattern} semitones change in semitones
* @example
* note("c")
* .penv("<12 7 1 .5 0 -1 -7 -12>")
*
*/
['penv'],
/**
* Curve of envelope. Defaults to linear. exponential is good for kicks
*
* @name pcurve
* @param {number | Pattern} type 0 = linear, 1 = exponential
* @example
* note("g1*2")
* .s("sine").pdec(.5)
* .penv(32)
* .pcurve("<0 1>")
*
*/
['pcurve'],
/**
* Sets the range anchor of the envelope:
* - anchor 0: range = [note, note + penv]
* - anchor 1: range = [note - penv, note]
* If you don't set an anchor, the value will default to the psustain value.
*
* @name panchor
* @param {number | Pattern} anchor anchor offset
* @example
* note("c").penv(12).panchor("<0 .5 1 .5>")
*
*/
['panchor'],
// TODO: https://tidalcycles.org/docs/configuration/MIDIOSC/control-voltage/#gate // TODO: https://tidalcycles.org/docs/configuration/MIDIOSC/control-voltage/#gate
['gate', 'gat'], ['gate', 'gat'],
// ['hatgrain'], // ['hatgrain'],
@@ -1209,7 +1285,7 @@ const generic_params = [
* Formant filter to make things sound like vowels. * Formant filter to make things sound like vowels.
* *
* @name vowel * @name vowel
* @param {string | Pattern} vowel You can use a e i o u. * @param {string | Pattern} vowel You can use a e i o u ae aa oe ue y uh un en an on, corresponding to [a] [e] [i] [o] [u] [æ] [ɑ] [ø] [y] [ɯ] [ʌ] [œ̃] [ɛ̃] [ɑ̃] [ɔ̃]. Aliases: aa = å = ɑ, oe = ø = ö, y = ı, ae = æ.
* @example * @example
* note("c2 <eb2 <g2 g1>>").s('sawtooth') * note("c2 <eb2 <g2 g1>>").s('sawtooth')
* .vowel("<a e i <o u>>") * .vowel("<a e i <o u>>")
@@ -1394,10 +1470,20 @@ controls.adsr = register('adsr', (adsr, pat) => {
const [attack, decay, sustain, release] = adsr; const [attack, decay, sustain, release] = adsr;
return pat.set({ attack, decay, sustain, release }); return pat.set({ attack, decay, sustain, release });
}); });
controls.ds = register('ds', (ds, pat) => { controls.ad = register('ad', (t, pat) => {
ds = !Array.isArray(ds) ? [ds] : ds; t = !Array.isArray(t) ? [t] : t;
const [decay, sustain] = ds; const [attack, decay = attack] = t;
return pat.attack(attack).decay(decay);
});
controls.ds = register('ds', (t, pat) => {
t = !Array.isArray(t) ? [t] : t;
const [decay, sustain = 0] = t;
return pat.set({ decay, sustain }); return pat.set({ decay, sustain });
}); });
controls.ds = register('ar', (t, pat) => {
t = !Array.isArray(t) ? [t] : t;
const [attack, release = attack] = t;
return pat.set({ attack, release });
});
export default controls; export default controls;
+3 -3
View File
@@ -30,12 +30,12 @@ export { default as drawLine } from './drawLine.mjs';
// below won't work with runtime.mjs (json import fails) // below won't work with runtime.mjs (json import fails)
/* import * as p from './package.json'; /* import * as p from './package.json';
export const version = p.version; */ export const version = p.version; */
logger('🌀 @strudel.cycles/core loaded 🌀'); logger('🌀 @strudel/core loaded 🌀');
if (globalThis._strudelLoaded) { if (globalThis._strudelLoaded) {
console.warn( console.warn(
`@strudel.cycles/core was loaded more than once... `@strudel/core was loaded more than once...
This might happen when you have multiple versions of strudel installed. This might happen when you have multiple versions of strudel installed.
Please check with "npm ls @strudel.cycles/core".`, Please check with "npm ls @strudel/core".`,
); );
} }
globalThis._strudelLoaded = true; globalThis._strudelLoaded = true;
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/core", "name": "@strudel/core",
"version": "0.9.0", "version": "0.11.0",
"description": "Port of Tidal Cycles to JavaScript", "description": "Port of Tidal Cycles to JavaScript",
"main": "index.mjs", "main": "index.mjs",
"type": "module", "type": "module",
+2 -2
View File
@@ -1154,8 +1154,8 @@ export function isPattern(thing) {
/* if (!thing instanceof Pattern) { /* if (!thing instanceof Pattern) {
console.warn( console.warn(
`Found Pattern that fails "instanceof Pattern" check. `Found Pattern that fails "instanceof Pattern" check.
This may happen if you are using multiple versions of @strudel.cycles/core. This may happen if you are using multiple versions of @strudel/core.
Please check by running "npm ls @strudel.cycles/core".`, Please check by running "npm ls @strudel/core".`,
); );
console.log(thing); console.log(thing);
} */ } */
+1 -1
View File
@@ -86,7 +86,7 @@ export const midi2note = (n) => {
// modulo that works with negative numbers e.g. _mod(-1, 3) = 2. Works on numbers (rather than patterns of numbers, as @mod@ from pattern.mjs does) // modulo that works with negative numbers e.g. _mod(-1, 3) = 2. Works on numbers (rather than patterns of numbers, as @mod@ from pattern.mjs does)
export const _mod = (n, m) => ((n % m) + m) % m; export const _mod = (n, m) => ((n % m) + m) % m;
export function nanFallback(value, fallback) { export function nanFallback(value, fallback = 0) {
if (isNaN(Number(value))) { if (isNaN(Number(value))) {
logger(`"${value}" is not a number, falling back to ${fallback}`, 'warning'); logger(`"${value}" is not a number, falling back to ${fallback}`, 'warning');
return fallback; return fallback;
+1
View File
@@ -0,0 +1 @@
# @strudel/csound
+2 -2
View File
@@ -1,5 +1,5 @@
import { getFrequency, logger, register } from '@strudel.cycles/core'; import { getFrequency, logger, register } from '@strudel/core';
import { getAudioContext } from '@strudel.cycles/webaudio'; import { getAudioContext } from '@strudel/webaudio';
import csd from './project.csd?raw'; import csd from './project.csd?raw';
// import livecodeOrc from './livecode.orc?raw'; // import livecodeOrc from './livecode.orc?raw';
import presetsOrc from './presets.orc?raw'; import presetsOrc from './presets.orc?raw';
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/csound", "name": "@strudel/csound",
"version": "0.9.0", "version": "0.11.0",
"description": "csound bindings for strudel", "description": "csound bindings for strudel",
"main": "index.mjs", "main": "index.mjs",
"publishConfig": { "publishConfig": {
@@ -33,8 +33,8 @@
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@csound/browser": "6.18.7", "@csound/browser": "6.18.7",
"@strudel.cycles/core": "workspace:*", "@strudel/core": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*" "@strudel/webaudio": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"vite": "^5.0.10" "vite": "^5.0.10"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Invoke } from './utils.mjs'; import { Invoke } from './utils.mjs';
import { Pattern, noteToMidi } from '@strudel.cycles/core'; import { Pattern, noteToMidi } from '@strudel/core';
const ON_MESSAGE = 0x90; const ON_MESSAGE = 0x90;
const OFF_MESSAGE = 0x80; const OFF_MESSAGE = 0x80;
+1 -1
View File
@@ -1,4 +1,4 @@
import { parseNumeral, Pattern } from '@strudel.cycles/core'; import { parseNumeral, Pattern } from '@strudel/core';
import { Invoke } from './utils.mjs'; import { Invoke } from './utils.mjs';
Pattern.prototype.osc = function () { Pattern.prototype.osc = function () {
+1 -1
View File
@@ -22,7 +22,7 @@
"url": "https://github.com/tidalcycles/strudel/issues" "url": "https://github.com/tidalcycles/strudel/issues"
}, },
"dependencies": { "dependencies": {
"@strudel.cycles/core": "workspace:*", "@strudel/core": "workspace:*",
"@tauri-apps/api": "^1.5.3" "@tauri-apps/api": "^1.5.3"
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme" "homepage": "https://github.com/tidalcycles/strudel#readme"
+3 -3
View File
@@ -1,13 +1,13 @@
# @strudel.cycles/embed # @strudel/embed
This package contains a embeddable web component for the Strudel REPL. This package contains a embeddable web component for the Strudel REPL.
## Usage ## Usage
Either install with `npm i @strudel.cycles/embed` or just use a cdn to import the script: Either install with `npm i @strudel/embed` or just use a cdn to import the script:
```html ```html
<script src="https://unpkg.com/@strudel.cycles/embed@latest"></script> <script src="https://unpkg.com/@strudel/embed@latest"></script>
<strudel-repl> <strudel-repl>
<!-- <!--
note(`[[e5 [b4 c5] d5 [c5 b4]] note(`[[e5 [b4 c5] d5 [c5 b4]]
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/embed", "name": "@strudel/embed",
"version": "0.2.0", "version": "0.11.0",
"description": "Embeddable Web Component to load a Strudel REPL into an iframe", "description": "Embeddable Web Component to load a Strudel REPL into an iframe",
"main": "embed.js", "main": "embed.js",
"type": "module", "type": "module",
+1 -1
View File
@@ -27,7 +27,7 @@ npm i @strudel/hydra
Then add the import to your evalScope: Then add the import to your evalScope:
```js ```js
import { evalScope } from '@strudel.cycles/core'; import { evalScope } from '@strudel/core';
evalScope( evalScope(
import('@strudel/hydra') import('@strudel/hydra')
+1 -1
View File
@@ -1,4 +1,4 @@
import { getDrawContext } from '@strudel.cycles/core'; import { getDrawContext } from '@strudel/core';
let latestOptions; let latestOptions;
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel/hydra", "name": "@strudel/hydra",
"version": "0.9.0", "version": "0.11.0",
"description": "Hydra integration for strudel", "description": "Hydra integration for strudel",
"main": "hydra.mjs", "main": "hydra.mjs",
"publishConfig": { "publishConfig": {
@@ -33,7 +33,7 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel.cycles/core": "workspace:*", "@strudel/core": "workspace:*",
"hydra-synth": "^1.3.29" "hydra-synth": "^1.3.29"
}, },
"devDependencies": { "devDependencies": {
+2 -2
View File
@@ -1,9 +1,9 @@
# @strudel.cycles/midi # @strudel/midi
This package adds midi functionality to strudel Patterns. This package adds midi functionality to strudel Patterns.
## Install ## Install
```sh ```sh
npm i @strudel.cycles/midi --save npm i @strudel/midi --save
``` ```
+2 -2
View File
@@ -5,8 +5,8 @@ This program is free software: you can redistribute it and/or modify it under th
*/ */
import * as _WebMidi from 'webmidi'; import * as _WebMidi from 'webmidi';
import { Pattern, isPattern, logger, ref } from '@strudel.cycles/core'; import { Pattern, isPattern, logger, ref } from '@strudel/core';
import { noteToMidi } from '@strudel.cycles/core'; import { noteToMidi } from '@strudel/core';
import { Note } from 'webmidi'; import { Note } from 'webmidi';
// if you use WebMidi from outside of this package, make sure to import that instance: // if you use WebMidi from outside of this package, make sure to import that instance:
export const { WebMidi } = _WebMidi; export const { WebMidi } = _WebMidi;
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/midi", "name": "@strudel/midi",
"version": "0.9.0", "version": "0.11.0",
"description": "Midi API for strudel", "description": "Midi API for strudel",
"main": "index.mjs", "main": "index.mjs",
"publishConfig": { "publishConfig": {
@@ -29,8 +29,8 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel.cycles/core": "workspace:*", "@strudel/core": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*", "@strudel/webaudio": "workspace:*",
"webmidi": "^3.1.8" "webmidi": "^3.1.8"
}, },
"devDependencies": { "devDependencies": {
+4 -4
View File
@@ -1,17 +1,17 @@
# @strudel.cycles/mini # @strudel/mini
This package contains the mini notation parser and pattern generator. This package contains the mini notation parser and pattern generator.
## Install ## Install
```sh ```sh
npm i @strudel.cycles/mini --save npm i @strudel/mini --save
``` ```
## Example ## Example
```js ```js
import { mini } from '@strudel.cycles/mini'; import { mini } from '@strudel/mini';
const pattern = mini('a [b c*2]'); const pattern = mini('a [b c*2]');
@@ -28,7 +28,7 @@ yields:
(7/8 -> 1/1, 7/8 -> 1/1, c) (7/8 -> 1/1, 7/8 -> 1/1, c)
``` ```
[Play with @strudel.cycles/mini codesandbox](https://codesandbox.io/s/strudel-mini-example-oe9wcu?file=/src/index.js) [Play with @strudel/mini codesandbox](https://codesandbox.io/s/strudel-mini-example-oe9wcu?file=/src/index.js)
## Mini Notation API ## Mini Notation API
+2 -2
View File
@@ -292,7 +292,7 @@ function peg$parse(input, options) {
var peg$f3 = function(s) { return s }; var peg$f3 = function(s) { return s };
var peg$f4 = function(s, stepsPerCycle) { s.arguments_.stepsPerCycle = stepsPerCycle ; return s; }; var peg$f4 = function(s, stepsPerCycle) { s.arguments_.stepsPerCycle = stepsPerCycle ; return s; };
var peg$f5 = function(a) { return a }; var peg$f5 = function(a) { return a };
var peg$f6 = function(s) { s.arguments_.alignment = 'slowcat'; return s; }; var peg$f6 = function(s) { s.arguments_.alignment = 'polymeter_slowcat'; return s; };
var peg$f7 = function(a) { return x => x.options_['weight'] = a }; var peg$f7 = function(a) { return x => x.options_['weight'] = a };
var peg$f8 = function(a) { return x => x.options_['reps'] = a }; var peg$f8 = function(a) { return x => x.options_['reps'] = a };
var peg$f9 = function(p, s, r) { return x => x.options_['ops'].push({ type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r }}) }; var peg$f9 = function(p, s, r) { return x => x.options_['ops'].push({ type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r }}) };
@@ -1073,7 +1073,7 @@ function peg$parse(input, options) {
} }
if (s2 !== peg$FAILED) { if (s2 !== peg$FAILED) {
s3 = peg$parsews(); s3 = peg$parsews();
s4 = peg$parsesequence(); s4 = peg$parsepolymeter_stack();
if (s4 !== peg$FAILED) { if (s4 !== peg$FAILED) {
s5 = peg$parsews(); s5 = peg$parsews();
if (input.charCodeAt(peg$currPos) === 62) { if (input.charCodeAt(peg$currPos) === 62) {
+2 -2
View File
@@ -119,8 +119,8 @@ polymeter_steps = "%"a:slice
// define a step-per-cycle timeline e.g <1 3 [3 5]>. We simply defer to a sequence and // define a step-per-cycle timeline e.g <1 3 [3 5]>. We simply defer to a sequence and
// change the alignment to slowcat // change the alignment to slowcat
slow_sequence = ws "<" ws s:sequence ws ">" ws slow_sequence = ws "<" ws s:polymeter_stack ws ">" ws
{ s.arguments_.alignment = 'slowcat'; return s; } { s.arguments_.alignment = 'polymeter_slowcat'; return s; }
// a slice is either a single step or a sub cycle // a slice is either a single step or a sub cycle
slice = step / sub_cycle / polymeter / slow_sequence slice = step / sub_cycle / polymeter / slow_sequence
+5 -7
View File
@@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
*/ */
import * as krill from './krill-parser.js'; import * as krill from './krill-parser.js';
import * as strudel from '@strudel.cycles/core'; import * as strudel from '@strudel/core';
const randOffset = 0.0003; const randOffset = 0.0003;
@@ -91,6 +91,10 @@ export function patternifyAST(ast, code, onEnter, offset = 0) {
if (alignment === 'stack') { if (alignment === 'stack') {
return strudel.stack(...children); return strudel.stack(...children);
} }
if (alignment === 'polymeter_slowcat') {
const aligned = children.map((child) => child._slow(strudel.Fraction(child.__weight ?? 1)));
return strudel.stack(...aligned);
}
if (alignment === 'polymeter') { if (alignment === 'polymeter') {
// polymeter // polymeter
const stepsPerCycle = ast.arguments_.stepsPerCycle const stepsPerCycle = ast.arguments_.stepsPerCycle
@@ -104,15 +108,9 @@ export function patternifyAST(ast, code, onEnter, offset = 0) {
return strudel.chooseInWith(strudel.rand.early(randOffset * ast.arguments_.seed).segment(1), children); return strudel.chooseInWith(strudel.rand.early(randOffset * ast.arguments_.seed).segment(1), children);
} }
const weightedChildren = ast.source_.some((child) => !!child.options_?.weight); const weightedChildren = ast.source_.some((child) => !!child.options_?.weight);
if (!weightedChildren && alignment === 'slowcat') {
return strudel.slowcat(...children);
}
if (weightedChildren) { if (weightedChildren) {
const weightSum = ast.source_.reduce((sum, child) => sum + (child.options_?.weight || 1), 0); const weightSum = ast.source_.reduce((sum, child) => sum + (child.options_?.weight || 1), 0);
const pat = strudel.timeCat(...ast.source_.map((child, i) => [child.options_?.weight || 1, children[i]])); const pat = strudel.timeCat(...ast.source_.map((child, i) => [child.options_?.weight || 1, children[i]]));
if (alignment === 'slowcat') {
return pat._slow(weightSum); // timecat + slow
}
pat.__weight = weightSum; pat.__weight = weightSum;
return pat; return pat;
} }
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/mini", "name": "@strudel/mini",
"version": "0.9.0", "version": "0.11.0",
"description": "Mini notation for strudel", "description": "Mini notation for strudel",
"main": "index.mjs", "main": "index.mjs",
"type": "module", "type": "module",
@@ -32,7 +32,7 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel.cycles/core": "workspace:*" "@strudel/core": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"peggy": "^3.0.2", "peggy": "^3.0.2",
+1 -1
View File
@@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
*/ */
import { getLeafLocation, getLeafLocations, mini, mini2ast } from '../mini.mjs'; import { getLeafLocation, getLeafLocations, mini, mini2ast } from '../mini.mjs';
import '@strudel.cycles/core/euclid.mjs'; import '@strudel/core/euclid.mjs';
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
describe('mini', () => { describe('mini', () => {
+1 -1
View File
@@ -1,4 +1,4 @@
# @strudel.cycles/osc # @strudel/osc
OSC output for strudel patterns! Currently only tested with super collider / super dirt. OSC output for strudel patterns! Currently only tested with super collider / super dirt.
+1 -1
View File
@@ -6,7 +6,7 @@ This program is free software: you can redistribute it and/or modify it under th
import OSC from 'osc-js'; import OSC from 'osc-js';
import { logger, parseNumeral, Pattern } from '@strudel.cycles/core'; import { logger, parseNumeral, Pattern } from '@strudel/core';
let connection; // Promise<OSC> let connection; // Promise<OSC>
function connect() { function connect() {
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/osc", "name": "@strudel/osc",
"version": "0.9.0", "version": "0.11.0",
"description": "OSC messaging for strudel", "description": "OSC messaging for strudel",
"main": "osc.mjs", "main": "osc.mjs",
"publishConfig": { "publishConfig": {
@@ -36,7 +36,7 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel.cycles/core": "workspace:*", "@strudel/core": "workspace:*",
"osc-js": "^2.4.0" "osc-js": "^2.4.0"
}, },
"devDependencies": { "devDependencies": {
+9 -9
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel/repl", "name": "@strudel/repl",
"version": "0.9.4", "version": "0.11.0",
"description": "Strudel REPL as a Web Component", "description": "Strudel REPL as a Web Component",
"main": "index.mjs", "main": "index.mjs",
"publishConfig": { "publishConfig": {
@@ -33,15 +33,15 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/midi": "workspace:*",
"@strudel.cycles/mini": "workspace:*",
"@strudel.cycles/soundfonts": "workspace:*",
"@strudel.cycles/tonal": "workspace:*",
"@strudel.cycles/transpiler": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*",
"@strudel/codemirror": "workspace:*", "@strudel/codemirror": "workspace:*",
"@strudel/hydra": "workspace:*" "@strudel/core": "workspace:*",
"@strudel/hydra": "workspace:*",
"@strudel/midi": "workspace:*",
"@strudel/mini": "workspace:*",
"@strudel/soundfonts": "workspace:*",
"@strudel/tonal": "workspace:*",
"@strudel/transpiler": "workspace:*",
"@strudel/webaudio": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-replace": "^5.0.5", "@rollup/plugin-replace": "^5.0.5",
+16 -16
View File
@@ -1,22 +1,22 @@
import { controls, noteToMidi, valueToMidi, Pattern, evalScope } from '@strudel.cycles/core'; import { controls, noteToMidi, valueToMidi, Pattern, evalScope } from '@strudel/core';
import { registerSynthSounds, registerZZFXSounds, samples } from '@strudel.cycles/webaudio'; import { registerSynthSounds, registerZZFXSounds, samples } from '@strudel/webaudio';
import * as core from '@strudel.cycles/core'; import * as core from '@strudel/core';
export async function prebake() { export async function prebake() {
const modulesLoading = evalScope( const modulesLoading = evalScope(
// import('@strudel.cycles/core'), // import('@strudel/core'),
core, core,
import('@strudel.cycles/mini'), import('@strudel/mini'),
import('@strudel.cycles/tonal'), import('@strudel/tonal'),
import('@strudel.cycles/webaudio'), import('@strudel/webaudio'),
import('@strudel/codemirror'), import('@strudel/codemirror'),
import('@strudel/hydra'), import('@strudel/hydra'),
import('@strudel.cycles/soundfonts'), import('@strudel/soundfonts'),
import('@strudel.cycles/midi'), import('@strudel/midi'),
// import('@strudel.cycles/xen'), // import('@strudel/xen'),
// import('@strudel.cycles/serial'), // import('@strudel/serial'),
// import('@strudel.cycles/csound'), // import('@strudel/csound'),
// import('@strudel.cycles/osc'), // import('@strudel/osc'),
controls, // sadly, this cannot be exported from core directly (yet) controls, // sadly, this cannot be exported from core directly (yet)
); );
// load samples // load samples
@@ -26,10 +26,10 @@ export async function prebake() {
registerSynthSounds(), registerSynthSounds(),
registerZZFXSounds(), registerZZFXSounds(),
//registerSoundfonts(), //registerSoundfonts(),
// need dynamic import here, because importing @strudel.cycles/soundfonts fails on server: // need dynamic import here, because importing @strudel/soundfonts fails on server:
// => getting "window is not defined", as soon as "@strudel.cycles/soundfonts" is imported statically // => getting "window is not defined", as soon as "@strudel/soundfonts" is imported statically
// seems to be a problem with soundfont2 // seems to be a problem with soundfont2
import('@strudel.cycles/soundfonts').then(({ registerSoundfonts }) => registerSoundfonts()), import('@strudel/soundfonts').then(({ registerSoundfonts }) => registerSoundfonts()),
samples(`${ds}/tidal-drum-machines.json`), samples(`${ds}/tidal-drum-machines.json`),
samples(`${ds}/piano.json`), samples(`${ds}/piano.json`),
samples(`${ds}/Dirt-Samples.json`), samples(`${ds}/Dirt-Samples.json`),
+3 -3
View File
@@ -1,6 +1,6 @@
import { getDrawContext, silence } from '@strudel.cycles/core'; import { getDrawContext, silence } from '@strudel/core';
import { transpiler } from '@strudel.cycles/transpiler'; import { transpiler } from '@strudel/transpiler';
import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio'; import { getAudioContext, webaudioOutput } from '@strudel/webaudio';
import { StrudelMirror, codemirrorSettings } from '@strudel/codemirror'; import { StrudelMirror, codemirrorSettings } from '@strudel/codemirror';
import { prebake } from './prebake.mjs'; import { prebake } from './prebake.mjs';
+1 -1
View File
@@ -1,3 +1,3 @@
# @strudel.cycles/serial # @strudel/serial
This package adds webserial functionality to strudel Patterns, for e.g. sending messages to arduino microcontrollers. This package adds webserial functionality to strudel Patterns, for e.g. sending messages to arduino microcontrollers.
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/serial", "name": "@strudel/serial",
"version": "0.9.0", "version": "0.11.0",
"description": "Webserial API for strudel", "description": "Webserial API for strudel",
"main": "serial.mjs", "main": "serial.mjs",
"publishConfig": { "publishConfig": {
@@ -29,7 +29,7 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel.cycles/core": "workspace:*" "@strudel/core": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"vite": "^5.0.10" "vite": "^5.0.10"
+1 -1
View File
@@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Pattern, isPattern } from '@strudel.cycles/core'; import { Pattern, isPattern } from '@strudel/core';
var writeMessagers = {}; var writeMessagers = {};
var choosing = false; var choosing = false;
+1
View File
@@ -0,0 +1 @@
# @strudel/soundfonts
+33 -11
View File
@@ -1,5 +1,12 @@
import { noteToMidi, freqToMidi, getSoundIndex } from '@strudel.cycles/core'; import { noteToMidi, freqToMidi, getSoundIndex } from '@strudel/core';
import { getAudioContext, registerSound, getEnvelope } from '@strudel.cycles/webaudio'; import {
getAudioContext,
registerSound,
getParamADSR,
getADSRValues,
getPitchEnvelope,
getVibratoOscillator,
} from '@strudel/webaudio';
import gm from './gm.mjs'; import gm from './gm.mjs';
let loadCache = {}; let loadCache = {};
@@ -130,24 +137,39 @@ export function registerSoundfonts() {
registerSound( registerSound(
name, name,
async (time, value, onended) => { async (time, value, onended) => {
const [attack, decay, sustain, release] = getADSRValues([
value.attack,
value.decay,
value.sustain,
value.release,
]);
const { duration } = value;
const n = getSoundIndex(value.n, fonts.length); const n = getSoundIndex(value.n, fonts.length);
const { attack = 0.001, decay = 0.001, sustain = 1, release = 0.001 } = value;
const font = fonts[n]; const font = fonts[n];
const ctx = getAudioContext(); const ctx = getAudioContext();
const bufferSource = await getFontBufferSource(font, value, ctx); const bufferSource = await getFontBufferSource(font, value, ctx);
bufferSource.start(time); bufferSource.start(time);
const { node: envelope, stop: releaseEnvelope } = getEnvelope(attack, decay, sustain, release, 0.3, time); const envGain = ctx.createGain();
bufferSource.connect(envelope); const node = bufferSource.connect(envGain);
const stop = (releaseTime) => { const holdEnd = time + duration;
const silentAt = releaseEnvelope(releaseTime); getParamADSR(node.gain, attack, decay, sustain, release, 0, 0.3, time, holdEnd, 'linear');
bufferSource.stop(silentAt); let envEnd = holdEnd + release + 0.01;
};
// vibrato
let vibratoOscillator = getVibratoOscillator(bufferSource.detune, value, time);
// pitch envelope
getPitchEnvelope(bufferSource.detune, value, time, holdEnd);
bufferSource.stop(envEnd);
const stop = (releaseTime) => {};
bufferSource.onended = () => { bufferSource.onended = () => {
bufferSource.disconnect(); bufferSource.disconnect();
envelope.disconnect(); vibratoOscillator?.stop();
node.disconnect();
onended(); onended();
}; };
return { node: envelope, stop }; return { node, stop };
}, },
{ type: 'soundfont', prebake: true, fonts }, { type: 'soundfont', prebake: true, fonts },
); );
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/soundfonts", "name": "@strudel/soundfonts",
"version": "0.9.0", "version": "0.11.0",
"description": "Soundsfont support for strudel", "description": "Soundsfont support for strudel",
"main": "index.mjs", "main": "index.mjs",
"publishConfig": { "publishConfig": {
@@ -30,8 +30,8 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel.cycles/core": "workspace:*", "@strudel/core": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*", "@strudel/webaudio": "workspace:*",
"sfumato": "^0.1.2", "sfumato": "^0.1.2",
"soundfont2": "^0.4.0" "soundfont2": "^0.4.0"
}, },
+2 -2
View File
@@ -1,5 +1,5 @@
import { Pattern, getPlayableNoteValue, noteToMidi } from '@strudel.cycles/core'; import { Pattern, getPlayableNoteValue, noteToMidi } from '@strudel/core';
import { getAudioContext, registerSound } from '@strudel.cycles/webaudio'; import { getAudioContext, registerSound } from '@strudel/webaudio';
import { loadSoundfont as _loadSoundfont, startPresetNote } from 'sfumato'; import { loadSoundfont as _loadSoundfont, startPresetNote } from 'sfumato';
Pattern.prototype.soundfont = function (sf, n = 0) { Pattern.prototype.soundfont = function (sf, n = 0) {
+132 -94
View File
@@ -1,5 +1,5 @@
import { getAudioContext } from './superdough.mjs'; import { getAudioContext } from './superdough.mjs';
import { clamp } from './util.mjs'; import { clamp, nanFallback } from './util.mjs';
export function gainNode(value) { export function gainNode(value) {
const node = getAudioContext().createGain(); const node = getAudioContext().createGain();
@@ -7,78 +7,73 @@ export function gainNode(value) {
return node; return node;
} }
// alternative to getADSR returning the gain node and a stop handle to trigger the release anytime in the future const getSlope = (y1, y2, x1, x2) => {
export const getEnvelope = (attack, decay, sustain, release, velocity, begin) => { const denom = x2 - x1;
const gainNode = getAudioContext().createGain(); if (denom === 0) {
let phase = begin; return 0;
gainNode.gain.setValueAtTime(0, begin);
phase += attack;
gainNode.gain.linearRampToValueAtTime(velocity, phase); // attack
phase += decay;
let sustainLevel = sustain * velocity;
gainNode.gain.linearRampToValueAtTime(sustainLevel, phase); // decay / sustain
// sustain end
return {
node: gainNode,
stop: (t) => {
// to make sure the release won't begin before sustain is reached
phase = Math.max(t, phase);
// see https://github.com/tidalcycles/strudel/issues/522
gainNode.gain.setValueAtTime(sustainLevel, phase);
phase += release;
gainNode.gain.linearRampToValueAtTime(0, phase); // release
return phase;
},
};
};
export const getExpEnvelope = (attack, decay, sustain, release, velocity, begin) => {
sustain = Math.max(0.001, sustain);
velocity = Math.max(0.001, velocity);
const gainNode = getAudioContext().createGain();
gainNode.gain.setValueAtTime(0.0001, begin);
gainNode.gain.exponentialRampToValueAtTime(velocity, begin + attack);
gainNode.gain.exponentialRampToValueAtTime(sustain * velocity, begin + attack + decay);
return {
node: gainNode,
stop: (t) => {
// similar to getEnvelope, this will glitch if sustain level has not been reached
gainNode.gain.exponentialRampToValueAtTime(0.0001, t + release);
},
};
};
export const getADSR = (attack, decay, sustain, release, velocity, begin, end) => {
const gainNode = getAudioContext().createGain();
gainNode.gain.setValueAtTime(0, begin);
gainNode.gain.linearRampToValueAtTime(velocity, begin + attack); // attack
gainNode.gain.linearRampToValueAtTime(sustain * velocity, begin + attack + decay); // sustain start
gainNode.gain.setValueAtTime(sustain * velocity, end); // sustain end
gainNode.gain.linearRampToValueAtTime(0, end + release); // release
// for some reason, using exponential ramping creates little cracklings
/* let t = begin;
gainNode.gain.setValueAtTime(0, t);
gainNode.gain.exponentialRampToValueAtTime(velocity, (t += attack));
const sustainGain = Math.max(sustain * velocity, 0.001);
gainNode.gain.exponentialRampToValueAtTime(sustainGain, (t += decay));
if (end - begin < attack + decay) {
gainNode.gain.cancelAndHoldAtTime(end);
} else {
gainNode.gain.setValueAtTime(sustainGain, end);
} }
gainNode.gain.exponentialRampToValueAtTime(0.001, end + release); // release */ return (y2 - y1) / (x2 - x1);
return gainNode;
}; };
export const getParamADSR = (
export const getParamADSR = (param, attack, decay, sustain, release, min, max, begin, end) => { param,
attack,
decay,
sustain,
release,
min,
max,
begin,
end,
//exponential works better for frequency modulations (such as filter cutoff) due to human ear perception
curve = 'exponential',
) => {
attack = nanFallback(attack);
decay = nanFallback(decay);
sustain = nanFallback(sustain);
release = nanFallback(release);
const ramp = curve === 'exponential' ? 'exponentialRampToValueAtTime' : 'linearRampToValueAtTime';
if (curve === 'exponential') {
min = min === 0 ? 0.001 : min;
max = max === 0 ? 0.001 : max;
}
const range = max - min; const range = max - min;
const peak = min + range; const peak = max;
const sustainLevel = min + sustain * range; const sustainVal = min + sustain * range;
const duration = end - begin;
const envValAtTime = (time) => {
let val;
if (attack > time) {
let slope = getSlope(min, peak, 0, attack);
val = time * slope + (min > peak ? min : 0);
} else {
val = (time - attack) * getSlope(peak, sustainVal, 0, decay) + peak;
}
if (curve === 'exponential') {
val = val || 0.001;
}
return val;
};
param.setValueAtTime(min, begin); param.setValueAtTime(min, begin);
param.linearRampToValueAtTime(peak, begin + attack); if (attack > duration) {
param.linearRampToValueAtTime(sustainLevel, begin + attack + decay); //attack
param.setValueAtTime(sustainLevel, end); param[ramp](envValAtTime(duration), end);
param.linearRampToValueAtTime(min, end + Math.max(release, 0.1)); } else if (attack + decay > duration) {
//attack
param[ramp](envValAtTime(attack), begin + attack);
//decay
param[ramp](envValAtTime(duration), end);
} else {
//attack
param[ramp](envValAtTime(attack), begin + attack);
//decay
param[ramp](envValAtTime(attack + decay), begin + attack + decay);
//sustain
param.setValueAtTime(sustainVal, end);
}
//release
param[ramp](min, end + release);
}; };
export function getCompressor(ac, threshold, ratio, knee, attack, release) { export function getCompressor(ac, threshold, ratio, knee, attack, release) {
@@ -92,38 +87,44 @@ export function getCompressor(ac, threshold, ratio, knee, attack, release) {
return new DynamicsCompressorNode(ac, options); return new DynamicsCompressorNode(ac, options);
} }
export function createFilter( // changes the default values of the envelope based on what parameters the user has defined
context, // so it behaves more like you would expect/familiar as other synthesis tools
type, // ex: sound(val).decay(val) will behave as a decay only envelope. sound(val).attack(val).decay(val) will behave like an "ad" env, etc.
frequency,
Q, export const getADSRValues = (params, curve = 'linear', defaultValues) => {
attack, const envmin = curve === 'exponential' ? 0.001 : 0.001;
decay, const releaseMin = 0.01;
sustain, const envmax = 1;
release, const [a, d, s, r] = params;
fenv, if (a == null && d == null && s == null && r == null) {
start, return defaultValues ?? [envmin, envmin, envmax, releaseMin];
end, }
fanchor = 0.5, const sustain = s != null ? s : (a != null && d == null) || (a == null && d == null) ? envmax : envmin;
) { return [Math.max(a ?? 0, envmin), Math.max(d ?? 0, envmin), Math.min(sustain, envmax), Math.max(r ?? 0, releaseMin)];
};
export function createFilter(context, type, frequency, Q, att, dec, sus, rel, fenv, start, end, fanchor) {
const curve = 'exponential';
const [attack, decay, sustain, release] = getADSRValues([att, dec, sus, rel], curve, [0.005, 0.14, 0, 0.1]);
const filter = context.createBiquadFilter(); const filter = context.createBiquadFilter();
filter.type = type; filter.type = type;
filter.Q.value = Q; filter.Q.value = Q;
filter.frequency.value = frequency; filter.frequency.value = frequency;
// envelope is active when any of these values is set
const hasEnvelope = att ?? dec ?? sus ?? rel ?? fenv;
// Apply ADSR to filter frequency // Apply ADSR to filter frequency
if (!isNaN(fenv) && fenv !== 0) { if (hasEnvelope !== undefined) {
const offset = fenv * fanchor; fenv = nanFallback(fenv, 1, true);
fanchor = nanFallback(fanchor, 0, true);
const min = clamp(2 ** -offset * frequency, 0, 20000); const fenvAbs = Math.abs(fenv);
const max = clamp(2 ** (fenv - offset) * frequency, 0, 20000); const offset = fenvAbs * fanchor;
let min = clamp(2 ** -offset * frequency, 0, 20000);
// console.log('min', min, 'max', max); let max = clamp(2 ** (fenvAbs - offset) * frequency, 0, 20000);
if (fenv < 0) [min, max] = [max, min];
getParamADSR(filter.frequency, attack, decay, sustain, release, min, max, start, end); getParamADSR(filter.frequency, attack, decay, sustain, release, min, max, start, end, curve);
return filter; return filter;
} }
return filter; return filter;
} }
@@ -148,3 +149,40 @@ export function drywet(dry, wet, wetAmount = 0) {
wet_gain.connect(mix); wet_gain.connect(mix);
return mix; return mix;
} }
let curves = ['linear', 'exponential'];
export function getPitchEnvelope(param, value, t, holdEnd) {
// envelope is active when any of these values is set
const hasEnvelope = value.pattack ?? value.pdecay ?? value.psustain ?? value.prelease ?? value.penv;
if (!hasEnvelope) {
return;
}
const penv = nanFallback(value.penv, 1, true);
const curve = curves[value.pcurve ?? 0];
let [pattack, pdecay, psustain, prelease] = getADSRValues(
[value.pattack, value.pdecay, value.psustain, value.prelease],
curve,
[0.2, 0.001, 1, 0.001],
);
let panchor = value.panchor ?? psustain;
const cents = penv * 100; // penv is in semitones
const min = 0 - cents * panchor;
const max = cents - cents * panchor;
getParamADSR(param, pattack, pdecay, psustain, prelease, min, max, t, holdEnd, curve);
}
export function getVibratoOscillator(param, value, t) {
const { vibmod = 0.5, vib } = value;
let vibratoOscillator;
if (vib > 0) {
vibratoOscillator = getAudioContext().createOscillator();
vibratoOscillator.frequency.value = vib;
const gain = getAudioContext().createGain();
// Vibmod is the amount of vibrato, in semitones
gain.gain.value = vibmod * 100;
vibratoOscillator.connect(gain);
gain.connect(param);
vibratoOscillator.start(t);
return vibratoOscillator;
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "superdough", "name": "superdough",
"version": "0.9.12", "version": "0.10.0",
"description": "simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.", "description": "simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.",
"main": "index.mjs", "main": "index.mjs",
"type": "module", "type": "module",
+23 -28
View File
@@ -1,6 +1,6 @@
import { noteToMidi, valueToMidi, getSoundIndex } from './util.mjs'; import { noteToMidi, valueToMidi, getSoundIndex } from './util.mjs';
import { getAudioContext, registerSound } from './index.mjs'; import { getAudioContext, registerSound } from './index.mjs';
import { getEnvelope } from './helpers.mjs'; import { getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs';
import { logger } from './logger.mjs'; import { logger } from './logger.mjs';
const bufferCache = {}; // string: Promise<ArrayBuffer> const bufferCache = {}; // string: Promise<ArrayBuffer>
@@ -243,8 +243,7 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
begin = 0, begin = 0,
loopEnd = 1, loopEnd = 1,
end = 1, end = 1,
vib, duration,
vibmod = 0.5,
} = value; } = value;
// load sample // load sample
if (speed === 0) { if (speed === 0) {
@@ -254,24 +253,15 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
loop = s.startsWith('wt_') ? 1 : value.loop; loop = s.startsWith('wt_') ? 1 : value.loop;
const ac = getAudioContext(); const ac = getAudioContext();
// destructure adsr here, because the default should be different for synths and samples // destructure adsr here, because the default should be different for synths and samples
const { attack = 0.001, decay = 0.001, sustain = 1, release = 0.001 } = value;
let [attack, decay, sustain, release] = getADSRValues([value.attack, value.decay, value.sustain, value.release]);
//const soundfont = getSoundfontKey(s); //const soundfont = getSoundfontKey(s);
const time = t + nudge; const time = t + nudge;
const bufferSource = await getSampleBufferSource(s, n, note, speed, freq, bank, resolveUrl); const bufferSource = await getSampleBufferSource(s, n, note, speed, freq, bank, resolveUrl);
// vibrato // vibrato
let vibratoOscillator; let vibratoOscillator = getVibratoOscillator(bufferSource.detune, value, t);
if (vib > 0) {
vibratoOscillator = getAudioContext().createOscillator();
vibratoOscillator.frequency.value = vib;
const gain = getAudioContext().createGain();
// Vibmod is the amount of vibrato, in semitones
gain.gain.value = vibmod * 100;
vibratoOscillator.connect(gain);
gain.connect(bufferSource.detune);
vibratoOscillator.start(0);
}
// asny stuff above took too long? // asny stuff above took too long?
if (ac.currentTime > t) { if (ac.currentTime > t) {
@@ -298,26 +288,31 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
bufferSource.loopEnd = loopEnd * bufferSource.buffer.duration - offset; bufferSource.loopEnd = loopEnd * bufferSource.buffer.duration - offset;
} }
bufferSource.start(time, offset); bufferSource.start(time, offset);
const { node: envelope, stop: releaseEnvelope } = getEnvelope(attack, decay, sustain, release, 1, t); const envGain = ac.createGain();
bufferSource.connect(envelope); const node = bufferSource.connect(envGain);
if (clip == null && loop == null && value.release == null) {
const bufferDuration = bufferSource.buffer.duration / bufferSource.playbackRate.value;
duration = (end - begin) * bufferDuration;
}
let holdEnd = t + duration;
getParamADSR(node.gain, attack, decay, sustain, release, 0, 1, t, holdEnd, 'linear');
// pitch envelope
getPitchEnvelope(bufferSource.detune, value, t, holdEnd);
const out = ac.createGain(); // we need a separate gain for the cutgroups because firefox... const out = ac.createGain(); // we need a separate gain for the cutgroups because firefox...
envelope.connect(out); node.connect(out);
bufferSource.onended = function () { bufferSource.onended = function () {
bufferSource.disconnect(); bufferSource.disconnect();
vibratoOscillator?.stop(); vibratoOscillator?.stop();
envelope.disconnect(); node.disconnect();
out.disconnect(); out.disconnect();
onended(); onended();
}; };
const stop = (endTime, playWholeBuffer = clip === undefined && loop === undefined) => { let envEnd = holdEnd + release + 0.01;
let releaseTime = endTime; bufferSource.stop(envEnd);
if (playWholeBuffer) { const stop = (endTime, playWholeBuffer) => {};
const bufferDuration = bufferSource.buffer.duration / bufferSource.playbackRate.value;
releaseTime = t + (end - begin) * bufferDuration;
}
const silentAt = releaseEnvelope(releaseTime);
bufferSource.stop(silentAt);
};
const handle = { node: out, bufferSource, stop }; const handle = { node: out, bufferSource, stop };
// cut groups // cut groups
+13 -12
View File
@@ -280,26 +280,26 @@ export const superdough = async (value, deadline, hapDuration) => {
// low pass // low pass
cutoff, cutoff,
lpenv, lpenv,
lpattack = 0.01, lpattack,
lpdecay = 0.01, lpdecay,
lpsustain = 1, lpsustain,
lprelease = 0.01, lprelease,
resonance = 1, resonance = 1,
// high pass // high pass
hpenv, hpenv,
hcutoff, hcutoff,
hpattack = 0.01, hpattack,
hpdecay = 0.01, hpdecay,
hpsustain = 1, hpsustain,
hprelease = 0.01, hprelease,
hresonance = 1, hresonance = 1,
// band pass // band pass
bpenv, bpenv,
bandf, bandf,
bpattack = 0.01, bpattack,
bpdecay = 0.01, bpdecay,
bpsustain = 1, bpsustain,
bprelease = 0.01, bprelease,
bandq = 1, bandq = 1,
channels = [1, 2], channels = [1, 2],
//phaser //phaser
@@ -333,6 +333,7 @@ export const superdough = async (value, deadline, hapDuration) => {
compressorAttack, compressorAttack,
compressorRelease, compressorRelease,
} = value; } = value;
gain = nanFallback(gain, 1); gain = nanFallback(gain, 1);
//music programs/audio gear usually increments inputs/outputs from 1, so imitate that behavior //music programs/audio gear usually increments inputs/outputs from 1, so imitate that behavior
+45 -46
View File
@@ -1,6 +1,6 @@
import { midiToFreq, noteToMidi } from './util.mjs'; import { midiToFreq, noteToMidi } from './util.mjs';
import { registerSound, getAudioContext } from './superdough.mjs'; import { registerSound, getAudioContext } from './superdough.mjs';
import { gainNode, getEnvelope, getExpEnvelope } from './helpers.mjs'; import { gainNode, getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs';
import { getNoiseMix, getNoiseOscillator } from './noise.mjs'; import { getNoiseMix, getNoiseOscillator } from './noise.mjs';
const mod = (freq, range = 1, type = 'sine') => { const mod = (freq, range = 1, type = 'sine') => {
@@ -29,8 +29,11 @@ export function registerSynthSounds() {
registerSound( registerSound(
s, s,
(t, value, onended) => { (t, value, onended) => {
// destructure adsr here, because the default should be different for synths and samples const [attack, decay, sustain, release] = getADSRValues(
let { attack = 0.001, decay = 0.05, sustain = 0.6, release = 0.01 } = value; [value.attack, value.decay, value.sustain, value.release],
'linear',
[0.001, 0.05, 0.6, 0.01],
);
let sound; let sound;
if (waveforms.includes(s)) { if (waveforms.includes(s)) {
@@ -45,21 +48,24 @@ export function registerSynthSounds() {
// turn down // turn down
const g = gainNode(0.3); const g = gainNode(0.3);
// gain envelope const { duration } = value;
const { node: envelope, stop: releaseEnvelope } = getEnvelope(attack, decay, sustain, release, 1, t);
o.onended = () => { o.onended = () => {
o.disconnect(); o.disconnect();
g.disconnect(); g.disconnect();
onended(); onended();
}; };
const envGain = gainNode(1);
let node = o.connect(g).connect(envGain);
const holdEnd = t + duration;
getParamADSR(node.gain, attack, decay, sustain, release, 0, 1, t, holdEnd, 'linear');
const envEnd = holdEnd + release + 0.01;
triggerRelease?.(envEnd);
stop(envEnd);
return { return {
node: o.connect(g).connect(envelope), node,
stop: (releaseTime) => { stop: (releaseTime) => {},
const silentAt = releaseEnvelope(releaseTime);
triggerRelease?.(releaseTime);
stop(silentAt);
},
}; };
}, },
{ type: 'synth', prebake: true }, { type: 'synth', prebake: true },
@@ -99,28 +105,24 @@ export function waveformN(partials, type) {
} }
// expects one of waveforms as s // expects one of waveforms as s
export function getOscillator( export function getOscillator(s, t, value) {
s, let {
t,
{
n: partials, n: partials,
note, note,
freq, freq,
vib = 0,
vibmod = 0.5,
noise = 0, noise = 0,
// fm // fm
fmh: fmHarmonicity = 1, fmh: fmHarmonicity = 1,
fmi: fmModulationIndex, fmi: fmModulationIndex,
fmenv: fmEnvelopeType = 'lin', fmenv: fmEnvelopeType = 'exp',
fmattack: fmAttack, fmattack: fmAttack,
fmdecay: fmDecay, fmdecay: fmDecay,
fmsustain: fmSustain, fmsustain: fmSustain,
fmrelease: fmRelease, fmrelease: fmRelease,
fmvelocity: fmVelocity, fmvelocity: fmVelocity,
fmwave: fmWaveform = 'sine', fmwave: fmWaveform = 'sine',
}, duration,
) { } = value;
let ac = getAudioContext(); let ac = getAudioContext();
let o; let o;
// If no partials are given, use stock waveforms // If no partials are given, use stock waveforms
@@ -148,42 +150,39 @@ export function getOscillator(
o.start(t); o.start(t);
// FM // FM
let stopFm, fmEnvelope; let stopFm;
let envGain = ac.createGain();
if (fmModulationIndex) { if (fmModulationIndex) {
const { node: modulator, stop } = fm(o, fmHarmonicity, fmModulationIndex, fmWaveform); const { node: modulator, stop } = fm(o, fmHarmonicity, fmModulationIndex, fmWaveform);
if (![fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity].find((v) => v !== undefined)) { if (![fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity].find((v) => v !== undefined)) {
// no envelope by default // no envelope by default
modulator.connect(o.frequency); modulator.connect(o.frequency);
} else { } else {
fmAttack = fmAttack ?? 0.001; const [attack, decay, sustain, release] = getADSRValues([fmAttack, fmDecay, fmSustain, fmRelease]);
fmDecay = fmDecay ?? 0.001; const holdEnd = t + duration;
fmSustain = fmSustain ?? 1; getParamADSR(
fmRelease = fmRelease ?? 0.001; envGain.gain,
fmVelocity = fmVelocity ?? 1; attack,
fmEnvelope = getEnvelope(fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity, t); decay,
if (fmEnvelopeType === 'exp') { sustain,
fmEnvelope = getExpEnvelope(fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity, t); release,
fmEnvelope.node.maxValue = fmModulationIndex * 2; 0,
fmEnvelope.node.minValue = 0.00001; 1,
} t,
modulator.connect(fmEnvelope.node); holdEnd,
fmEnvelope.node.connect(o.frequency); fmEnvelopeType === 'exp' ? 'exponential' : 'linear',
);
modulator.connect(envGain);
envGain.connect(o.frequency);
} }
stopFm = stop; stopFm = stop;
} }
// Additional oscillator for vibrato effect // Additional oscillator for vibrato effect
let vibratoOscillator; let vibratoOscillator = getVibratoOscillator(o.detune, value, t);
if (vib > 0) {
vibratoOscillator = getAudioContext().createOscillator(); // pitch envelope
vibratoOscillator.frequency.value = vib; getPitchEnvelope(o.detune, value, t, t + duration);
const gain = getAudioContext().createGain();
// Vibmod is the amount of vibrato, in semitones
gain.gain.value = vibmod * 100;
vibratoOscillator.connect(gain);
gain.connect(o.detune);
vibratoOscillator.start(t);
}
let noiseMix; let noiseMix;
if (noise) { if (noise) {
@@ -199,7 +198,7 @@ export function getOscillator(
o.stop(time); o.stop(time);
}, },
triggerRelease: (time) => { triggerRelease: (time) => {
fmEnvelope?.stop(time); // envGain?.stop(time);
}, },
}; };
} }
+2 -2
View File
@@ -54,9 +54,9 @@ export const valueToMidi = (value, fallbackValue) => {
return fallbackValue; return fallbackValue;
}; };
export function nanFallback(value, fallback) { export function nanFallback(value, fallback = 0, silent) {
if (isNaN(Number(value))) { if (isNaN(Number(value))) {
logger(`"${value}" is not a number, falling back to ${fallback}`, 'warning'); !silent && logger(`"${value}" is not a number, falling back to ${fallback}`, 'warning');
return fallback; return fallback;
} }
return value; return value;
+31
View File
@@ -5,6 +5,37 @@ export var vowelFormant = {
i: { freqs: [270, 1850, 2900, 3350, 3590], gains: [1, 0.0631, 0.0631, 0.0158, 0.0158], qs: [40, 90, 100, 120, 120] }, i: { freqs: [270, 1850, 2900, 3350, 3590], gains: [1, 0.0631, 0.0631, 0.0158, 0.0158], qs: [40, 90, 100, 120, 120] },
o: { freqs: [430, 820, 2700, 3000, 3300], gains: [1, 0.3162, 0.0501, 0.0794, 0.01995], qs: [40, 80, 100, 120, 120] }, o: { freqs: [430, 820, 2700, 3000, 3300], gains: [1, 0.3162, 0.0501, 0.0794, 0.01995], qs: [40, 80, 100, 120, 120] },
u: { freqs: [370, 630, 2750, 3000, 3400], gains: [1, 0.1, 0.0708, 0.0316, 0.01995], qs: [40, 60, 100, 120, 120] }, u: { freqs: [370, 630, 2750, 3000, 3400], gains: [1, 0.1, 0.0708, 0.0316, 0.01995], qs: [40, 60, 100, 120, 120] },
ae: { freqs: [650, 1515, 2400, 3000, 3350], gains: [1, 0.5, 0.1008, 0.0631, 0.0126], qs: [80, 90, 120, 130, 140] },
aa: { freqs: [560, 900, 2570, 3000, 3300], gains: [1, 0.5, 0.0708, 0.0631, 0.0126], qs: [80, 90, 120, 130, 140] },
oe: { freqs: [500, 1430, 2300, 3000, 3300], gains: [1, 0.2, 0.0708, 0.0316, 0.01995], qs: [40, 60, 100, 120, 120] },
ue: { freqs: [250, 1750, 2150, 3200, 3300], gains: [1, 0.1, 0.0708, 0.0316, 0.01995], qs: [40, 60, 100, 120, 120] },
y: { freqs: [400, 1460, 2400, 3000, 3300], gains: [1, 0.2, 0.0708, 0.0316, 0.02995], qs: [40, 60, 100, 120, 120] },
uh: { freqs: [600, 1250, 2100, 3100, 3500], gains: [1, 0.3, 0.0608, 0.0316, 0.01995], qs: [40, 70, 100, 120, 130] },
un: { freqs: [500, 1240, 2280, 3000, 3500], gains: [1, 0.1, 0.1708, 0.0216, 0.02995], qs: [40, 60, 100, 120, 120] },
en: { freqs: [600, 1480, 2450, 3200, 3300], gains: [1, 0.15, 0.0708, 0.0316, 0.02995], qs: [40, 60, 100, 120, 120] },
an: { freqs: [700, 1050, 2500, 3000, 3300], gains: [1, 0.1, 0.0708, 0.0316, 0.02995], qs: [40, 60, 100, 120, 120] },
on: { freqs: [500, 1080, 2350, 3000, 3300], gains: [1, 0.1, 0.0708, 0.0316, 0.02995], qs: [40, 60, 100, 120, 120] },
get æ() {
return this.ae;
},
get ø() {
return this.oe;
},
get ɑ() {
return this.aa;
},
get å() {
return this.aa;
},
get ö() {
return this.oe;
},
get ü() {
return this.ue;
},
get ı() {
return this.y;
},
}; };
if (typeof GainNode !== 'undefined') { if (typeof GainNode !== 'undefined') {
class VowelNode extends GainNode { class VowelNode extends GainNode {
+5 -5
View File
@@ -1,18 +1,18 @@
# @strudel.cycles/tonal # @strudel/tonal
This package adds tonal / harmonic functions to strudel Patterns. This package adds tonal / harmonic functions to strudel Patterns.
## Install ## Install
```sh ```sh
npm i @strudel.cycles/tonal --save npm i @strudel/tonal --save
``` ```
## Example ## Example
```js ```js
import { sequence } from '@strudel.cycles/core'; import { sequence } from '@strudel/core';
import '@strudel.cycles/tonal'; import '@strudel/tonal';
const pattern = sequence(0, [1, 2]).scale('C major'); const pattern = sequence(0, [1, 2]).scale('C major');
@@ -27,7 +27,7 @@ yields:
(3/4 -> 1/1, 3/4 -> 1/1, E3) (3/4 -> 1/1, 3/4 -> 1/1, E3)
``` ```
[play with @strudel.cycles/tonal codesandbox](https://codesandbox.io/s/strudel-tonal-example-rgc5if?file=/src/index.js) [play with @strudel/tonal codesandbox](https://codesandbox.io/s/strudel-tonal-example-rgc5if?file=/src/index.js)
## Tonal API ## Tonal API
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/tonal", "name": "@strudel/tonal",
"version": "0.9.0", "version": "0.11.0",
"description": "Tonal functions for strudel", "description": "Tonal functions for strudel",
"main": "index.mjs", "main": "index.mjs",
"publishConfig": { "publishConfig": {
@@ -31,7 +31,7 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel.cycles/core": "workspace:*", "@strudel/core": "workspace:*",
"@tonaljs/tonal": "^4.7.2", "@tonaljs/tonal": "^4.7.2",
"chord-voicings": "^0.0.1", "chord-voicings": "^0.0.1",
"webmidi": "^3.1.8" "webmidi": "^3.1.8"
+1 -1
View File
@@ -7,7 +7,7 @@ This program is free software: you can redistribute it and/or modify it under th
// import { strict as assert } from 'assert'; // import { strict as assert } from 'assert';
import '../tonal.mjs'; // need to import this to add prototypes import '../tonal.mjs'; // need to import this to add prototypes
import { pure, controls, seq } from '@strudel.cycles/core'; import { pure, controls, seq } from '@strudel/core';
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from 'vitest';
import { mini } from '../../mini/mini.mjs'; import { mini } from '../../mini/mini.mjs';
const { n } = controls; const { n } = controls;
+1 -1
View File
@@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
*/ */
import { Note, Interval, Scale } from '@tonaljs/tonal'; import { Note, Interval, Scale } from '@tonaljs/tonal';
import { register, _mod, silence, logger, pure, isNote } from '@strudel.cycles/core'; import { register, _mod, silence, logger, pure, isNote } from '@strudel/core';
import { stepInNamedScale } from './tonleiter.mjs'; import { stepInNamedScale } from './tonleiter.mjs';
const octavesInterval = (octaves) => (octaves <= 0 ? -1 : 1) + octaves * 7 + 'P'; const octavesInterval = (octaves) => (octaves <= 0 ? -1 : 1) + octaves * 7 + 'P';
+1 -1
View File
@@ -1,4 +1,4 @@
import { isNote, isNoteWithOctave, _mod, noteToMidi, tokenizeNote } from '@strudel.cycles/core'; import { isNote, isNoteWithOctave, _mod, noteToMidi, tokenizeNote } from '@strudel/core';
import { Interval, Scale } from '@tonaljs/tonal'; import { Interval, Scale } from '@tonaljs/tonal';
// https://codesandbox.io/s/stateless-voicings-g2tmz0?file=/src/lib.js:0-2515 // https://codesandbox.io/s/stateless-voicings-g2tmz0?file=/src/lib.js:0-2515
+1 -1
View File
@@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { stack, register, silence, logger } from '@strudel.cycles/core'; import { stack, register, silence, logger } from '@strudel/core';
import { renderVoicing } from './tonleiter.mjs'; import { renderVoicing } from './tonleiter.mjs';
import _voicings from 'chord-voicings'; import _voicings from 'chord-voicings';
import { complex, simple } from './ireal.mjs'; import { complex, simple } from './ireal.mjs';
+4 -4
View File
@@ -1,4 +1,4 @@
# @strudel.cycles/transpiler # @strudel/transpiler
This package contains a JS code transpiler with the following features: This package contains a JS code transpiler with the following features:
@@ -9,14 +9,14 @@ This package contains a JS code transpiler with the following features:
## Install ## Install
```sh ```sh
npm i @strudel.cycles/transpiler npm i @strudel/transpiler
``` ```
## Use ## Use
```js ```js
import { transpiler } from '@strudel.cycles/core'; import { transpiler } from '@strudel/core';
import { evaluate } from '@strudel.cycles/core'; import { evaluate } from '@strudel/core';
transpiler('note("c3 [e3,g3]")', { wrapAsync: false, addReturn: false, simpleLocs: true }); transpiler('note("c3 [e3,g3]")', { wrapAsync: false, addReturn: false, simpleLocs: true });
/* mini('c3 [e3,g3]').withMiniLocation(7,17) */ /* mini('c3 [e3,g3]').withMiniLocation(7,17) */
+1 -1
View File
@@ -1,4 +1,4 @@
import { evaluate as _evaluate } from '@strudel.cycles/core'; import { evaluate as _evaluate } from '@strudel/core';
import { transpiler } from './transpiler.mjs'; import { transpiler } from './transpiler.mjs';
export * from './transpiler.mjs'; export * from './transpiler.mjs';
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/transpiler", "name": "@strudel/transpiler",
"version": "0.9.0", "version": "0.11.0",
"description": "Transpiler for strudel user code. Converts syntactically correct but semantically meaningless JS into evaluatable strudel code.", "description": "Transpiler for strudel user code. Converts syntactically correct but semantically meaningless JS into evaluatable strudel code.",
"main": "index.mjs", "main": "index.mjs",
"publishConfig": { "publishConfig": {
@@ -30,8 +30,8 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel.cycles/core": "workspace:*", "@strudel/core": "workspace:*",
"@strudel.cycles/mini": "workspace:*", "@strudel/mini": "workspace:*",
"acorn": "^8.11.3", "acorn": "^8.11.3",
"escodegen": "^2.1.0", "escodegen": "^2.1.0",
"estree-walker": "^3.0.1" "estree-walker": "^3.0.1"
+2 -2
View File
@@ -1,8 +1,8 @@
import escodegen from 'escodegen'; import escodegen from 'escodegen';
import { parse } from 'acorn'; import { parse } from 'acorn';
import { walk } from 'estree-walker'; import { walk } from 'estree-walker';
import { isNoteWithOctave } from '@strudel.cycles/core'; import { isNoteWithOctave } from '@strudel/core';
import { getLeafLocations } from '@strudel.cycles/mini'; import { getLeafLocations } from '@strudel/mini';
export function transpiler(input, options = {}) { export function transpiler(input, options = {}) {
const { wrapAsync = false, addReturn = true, emitMiniLocations = true, emitWidgets = true } = options; const { wrapAsync = false, addReturn = true, emitMiniLocations = true, emitWidgets = true } = options;
+6 -6
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel/web", "name": "@strudel/web",
"version": "0.9.0", "version": "0.11.0",
"description": "Easy to setup, opiniated bundle of Strudel for the browser.", "description": "Easy to setup, opiniated bundle of Strudel for the browser.",
"main": "web.mjs", "main": "web.mjs",
"publishConfig": { "publishConfig": {
@@ -33,11 +33,11 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel.cycles/core": "workspace:*", "@strudel/core": "workspace:*",
"@strudel.cycles/mini": "workspace:*", "@strudel/mini": "workspace:*",
"@strudel.cycles/tonal": "workspace:*", "@strudel/tonal": "workspace:*",
"@strudel.cycles/transpiler": "workspace:*", "@strudel/transpiler": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*" "@strudel/webaudio": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"vite": "^5.0.10" "vite": "^5.0.10"
+16 -16
View File
@@ -1,25 +1,25 @@
export * from '@strudel.cycles/core'; export * from '@strudel/core';
export * from '@strudel.cycles/webaudio'; export * from '@strudel/webaudio';
//export * from '@strudel.cycles/soundfonts'; //export * from '@strudel/soundfonts';
export * from '@strudel.cycles/transpiler'; export * from '@strudel/transpiler';
export * from '@strudel.cycles/mini'; export * from '@strudel/mini';
export * from '@strudel.cycles/tonal'; export * from '@strudel/tonal';
export * from '@strudel.cycles/webaudio'; export * from '@strudel/webaudio';
import { Pattern, evalScope, controls } from '@strudel.cycles/core'; import { Pattern, evalScope, controls } from '@strudel/core';
import { initAudioOnFirstClick, registerSynthSounds, webaudioScheduler } from '@strudel.cycles/webaudio'; import { initAudioOnFirstClick, registerSynthSounds, webaudioScheduler } from '@strudel/webaudio';
// import { registerSoundfonts } from '@strudel.cycles/soundfonts'; // import { registerSoundfonts } from '@strudel/soundfonts';
import { evaluate as _evaluate } from '@strudel.cycles/transpiler'; import { evaluate as _evaluate } from '@strudel/transpiler';
import { miniAllStrings } from '@strudel.cycles/mini'; import { miniAllStrings } from '@strudel/mini';
// init logic // init logic
export async function defaultPrebake() { export async function defaultPrebake() {
const loadModules = evalScope( const loadModules = evalScope(
evalScope, evalScope,
controls, controls,
import('@strudel.cycles/core'), import('@strudel/core'),
import('@strudel.cycles/mini'), import('@strudel/mini'),
import('@strudel.cycles/tonal'), import('@strudel/tonal'),
import('@strudel.cycles/webaudio'), import('@strudel/webaudio'),
{ hush, evaluate }, { hush, evaluate },
); );
await Promise.all([loadModules, registerSynthSounds() /* , registerSoundfonts() */]); await Promise.all([loadModules, registerSynthSounds() /* , registerSoundfonts() */]);
+4 -4
View File
@@ -1,4 +1,4 @@
# @strudel.cycles/webaudio # @strudel/webaudio
This package contains helpers to make music with strudel and the Web Audio API. This package contains helpers to make music with strudel and the Web Audio API.
It is a thin binding to [superdough](https://www.npmjs.com/package/superdough). It is a thin binding to [superdough](https://www.npmjs.com/package/superdough).
@@ -6,14 +6,14 @@ It is a thin binding to [superdough](https://www.npmjs.com/package/superdough).
## Install ## Install
```sh ```sh
npm i @strudel.cycles/webaudio --save npm i @strudel/webaudio --save
``` ```
## Example ## Example
```js ```js
import { repl, controls } from "@strudel.cycles/core"; import { repl, controls } from "@strudel/core";
import { initAudioOnFirstClick, getAudioContext, webaudioOutput } from "@strudel.cycles/webaudio"; import { initAudioOnFirstClick, getAudioContext, webaudioOutput } from "@strudel/webaudio";
const { note } = controls; const { note } = controls;
initAudioOnFirstClick(); initAudioOnFirstClick();
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/webaudio", "name": "@strudel/webaudio",
"version": "0.9.0", "version": "0.11.0",
"description": "Web Audio helpers for Strudel", "description": "Web Audio helpers for Strudel",
"main": "index.mjs", "main": "index.mjs",
"type": "module", "type": "module",
@@ -34,7 +34,7 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel.cycles/core": "workspace:*", "@strudel/core": "workspace:*",
"superdough": "workspace:*" "superdough": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
+1 -1
View File
@@ -1,4 +1,4 @@
import { Pattern, getDrawContext, clamp } from '@strudel.cycles/core'; import { Pattern, getDrawContext, clamp } from '@strudel/core';
import { analyser, getAnalyzerData } from 'superdough'; import { analyser, getAnalyzerData } from 'superdough';
export function drawTimeScope( export function drawTimeScope(
+1 -1
View File
@@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import * as strudel from '@strudel.cycles/core'; import * as strudel from '@strudel/core';
import { superdough, getAudioContext, setLogger, doughTrigger } from 'superdough'; import { superdough, getAudioContext, setLogger, doughTrigger } from 'superdough';
const { Pattern, logger } = strudel; const { Pattern, logger } = strudel;
+2 -2
View File
@@ -1,9 +1,9 @@
# @strudel.cycles/xen # @strudel/xen
This package adds xenharmonic / microtonal functions to strudel Patterns. Further documentation + examples will follow. This package adds xenharmonic / microtonal functions to strudel Patterns. Further documentation + examples will follow.
## Install ## Install
```sh ```sh
npm i @strudel.cycles/xen --save npm i @strudel/xen --save
``` ```
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel.cycles/xen", "name": "@strudel/xen",
"version": "0.9.0", "version": "0.11.0",
"description": "Xenharmonic API for strudel", "description": "Xenharmonic API for strudel",
"main": "index.mjs", "main": "index.mjs",
"publishConfig": { "publishConfig": {
@@ -30,7 +30,7 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel.cycles/core": "workspace:*" "@strudel/core": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"vite": "^5.0.10", "vite": "^5.0.10",
+1 -1
View File
@@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
*/ */
import Tune from './tunejs.js'; import Tune from './tunejs.js';
import { register } from '@strudel.cycles/core'; import { register } from '@strudel/core';
export const tune = register('tune', (scale, pat) => { export const tune = register('tune', (scale, pat) => {
const tune = new Tune(); const tune = new Tune();
+1 -1
View File
@@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { register, _mod, parseNumeral } from '@strudel.cycles/core'; import { register, _mod, parseNumeral } from '@strudel/core';
export function edo(name) { export function edo(name) {
if (!/^[1-9]+[0-9]*edo$/.test(name)) { if (!/^[1-9]+[0-9]*edo$/.test(name)) {
+715 -561
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,4 +1,4 @@
# @strudel.cycles/tauri # @strudel/tauri
Rust source files for building native desktop apps using Tauri Rust source files for building native desktop apps using Tauri
+58
View File
@@ -3332,6 +3332,55 @@ exports[`runs examples > example "pan" example index 0 1`] = `
] ]
`; `;
exports[`runs examples > example "panchor" example index 0 1`] = `
[
"[ 0/1 → 1/1 | note:c penv:12 panchor:0 ]",
"[ 1/1 → 2/1 | note:c penv:12 panchor:0.5 ]",
"[ 2/1 → 3/1 | note:c penv:12 panchor:1 ]",
"[ 3/1 → 4/1 | note:c penv:12 panchor:0.5 ]",
]
`;
exports[`runs examples > example "pattack" example index 0 1`] = `
[
"[ 0/1 → 1/1 | note:c pattack:0 ]",
"[ 1/1 → 2/1 | note:eb pattack:0.1 ]",
"[ 2/1 → 3/1 | note:g pattack:0.25 ]",
"[ 3/1 → 4/1 | note:bb pattack:0.5 ]",
]
`;
exports[`runs examples > example "pcurve" example index 0 1`] = `
[
"[ 0/1 → 1/2 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:0 ]",
"[ 1/2 → 1/1 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:0 ]",
"[ 1/1 → 3/2 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:1 ]",
"[ 3/2 → 2/1 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:1 ]",
"[ 2/1 → 5/2 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:0 ]",
"[ 5/2 → 3/1 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:0 ]",
"[ 3/1 → 7/2 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:1 ]",
"[ 7/2 → 4/1 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:1 ]",
]
`;
exports[`runs examples > example "pdecay" example index 0 1`] = `
[
"[ 0/1 → 1/1 | note:c pdecay:0 ]",
"[ 1/1 → 2/1 | note:eb pdecay:0.1 ]",
"[ 2/1 → 3/1 | note:g pdecay:0.25 ]",
"[ 3/1 → 4/1 | note:bb pdecay:0.5 ]",
]
`;
exports[`runs examples > example "penv" example index 0 1`] = `
[
"[ 0/1 → 1/1 | note:c penv:12 ]",
"[ 1/1 → 2/1 | note:c penv:7 ]",
"[ 2/1 → 3/1 | note:c penv:1 ]",
"[ 3/1 → 4/1 | note:c penv:0.5 ]",
]
`;
exports[`runs examples > example "perlin" example index 0 1`] = ` exports[`runs examples > example "perlin" example index 0 1`] = `
[ [
"[ 0/1 → 1/4 | s:hh cutoff:512.5097280354112 ]", "[ 0/1 → 1/4 | s:hh cutoff:512.5097280354112 ]",
@@ -3660,6 +3709,15 @@ exports[`runs examples > example "postgain" example index 0 1`] = `
] ]
`; `;
exports[`runs examples > example "prelease" example index 0 1`] = `
[
"[ 0/1 → 1/2 | note:c release:0.5 prelease:0 ]",
"[ 1/1 → 3/2 | note:eb release:0.5 prelease:0.1 ]",
"[ 2/1 → 5/2 | note:g release:0.5 prelease:0.25 ]",
"[ 3/1 → 7/2 | note:bb release:0.5 prelease:0.5 ]",
]
`;
exports[`runs examples > example "press" example index 0 1`] = ` exports[`runs examples > example "press" example index 0 1`] = `
[ [
"[ 0/1 → 1/2 | s:hh ]", "[ 0/1 → 1/2 | s:hh ]",
+1 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
import { getMetadata } from '../website/src/pages/metadata_parser'; import { getMetadata } from '../website/src/metadata_parser';
describe.concurrent('Metadata parser', () => { describe.concurrent('Metadata parser', () => {
it('loads a tag from inline comment', async () => { it('loads a tag from inline comment', async () => {
+19 -19
View File
@@ -3,25 +3,25 @@
// it might require mocking more stuff when tunes added that use other functions // it might require mocking more stuff when tunes added that use other functions
// import * as tunes from './tunes.mjs'; // import * as tunes from './tunes.mjs';
import { evaluate } from '@strudel.cycles/transpiler'; import { evaluate } from '@strudel/transpiler';
import { evalScope } from '@strudel.cycles/core'; import { evalScope } from '@strudel/core';
import * as strudel from '@strudel.cycles/core'; import * as strudel from '@strudel/core';
import * as webaudio from '@strudel.cycles/webaudio'; import * as webaudio from '@strudel/webaudio';
import controls from '@strudel.cycles/core/controls.mjs'; import controls from '@strudel/core/controls.mjs';
// import gist from '@strudel.cycles/core/gist.js'; // import gist from '@strudel/core/gist.js';
import { mini, m } from '@strudel.cycles/mini/mini.mjs'; import { mini, m } from '@strudel/mini/mini.mjs';
// import * as voicingHelpers from '@strudel.cycles/tonal/voicings.mjs'; // import * as voicingHelpers from '@strudel/tonal/voicings.mjs';
// import euclid from '@strudel.cycles/core/euclid.mjs'; // import euclid from '@strudel/core/euclid.mjs';
// import '@strudel.cycles/midi/midi.mjs'; // import '@strudel/midi/midi.mjs';
import * as tonalHelpers from '@strudel.cycles/tonal'; import * as tonalHelpers from '@strudel/tonal';
import '@strudel.cycles/xen/xen.mjs'; import '@strudel/xen/xen.mjs';
// import '@strudel.cycles/xen/tune.mjs'; // import '@strudel/xen/tune.mjs';
// import '@strudel.cycles/core/euclid.mjs'; // import '@strudel/core/euclid.mjs';
// import '@strudel.cycles/core/speak.mjs'; // window is not defined // import '@strudel/core/speak.mjs'; // window is not defined
// import '@strudel.cycles/osc/osc.mjs'; // import '@strudel/osc/osc.mjs';
// import '@strudel.cycles/webaudio/webaudio.mjs'; // import '@strudel/webaudio/webaudio.mjs';
// import '@strudel.cycles/serial/serial.mjs'; // import '@strudel/serial/serial.mjs';
// import controls from '@strudel.cycles/core/controls.mjs'; // import controls from '@strudel/core/controls.mjs';
import '../website/src/repl/piano'; import '../website/src/repl/piano';
class MockedNode { class MockedNode {
+276
View File
@@ -0,0 +1,276 @@
declare module 'astro:content' {
interface Render {
'.mdx': Promise<{
Content: import('astro').MarkdownInstance<{}>['Content'];
headings: import('astro').MarkdownHeading[];
remarkPluginFrontmatter: Record<string, any>;
}>;
}
}
declare module 'astro:content' {
interface Render {
'.md': Promise<{
Content: import('astro').MarkdownInstance<{}>['Content'];
headings: import('astro').MarkdownHeading[];
remarkPluginFrontmatter: Record<string, any>;
}>;
}
}
declare module 'astro:content' {
export { z } from 'astro/zod';
type Flatten<T> = T extends { [K: string]: infer U } ? U : never;
export type CollectionKey = keyof AnyEntryMap;
export type CollectionEntry<C extends CollectionKey> = Flatten<AnyEntryMap[C]>;
export type ContentCollectionKey = keyof ContentEntryMap;
export type DataCollectionKey = keyof DataEntryMap;
// This needs to be in sync with ImageMetadata
export type ImageFunction = () => import('astro/zod').ZodObject<{
src: import('astro/zod').ZodString;
width: import('astro/zod').ZodNumber;
height: import('astro/zod').ZodNumber;
format: import('astro/zod').ZodUnion<
[
import('astro/zod').ZodLiteral<'png'>,
import('astro/zod').ZodLiteral<'jpg'>,
import('astro/zod').ZodLiteral<'jpeg'>,
import('astro/zod').ZodLiteral<'tiff'>,
import('astro/zod').ZodLiteral<'webp'>,
import('astro/zod').ZodLiteral<'gif'>,
import('astro/zod').ZodLiteral<'svg'>,
import('astro/zod').ZodLiteral<'avif'>,
]
>;
}>;
type BaseSchemaWithoutEffects =
| import('astro/zod').AnyZodObject
| import('astro/zod').ZodUnion<[BaseSchemaWithoutEffects, ...BaseSchemaWithoutEffects[]]>
| import('astro/zod').ZodDiscriminatedUnion<string, import('astro/zod').AnyZodObject[]>
| import('astro/zod').ZodIntersection<BaseSchemaWithoutEffects, BaseSchemaWithoutEffects>;
type BaseSchema =
| BaseSchemaWithoutEffects
| import('astro/zod').ZodEffects<BaseSchemaWithoutEffects>;
export type SchemaContext = { image: ImageFunction };
type DataCollectionConfig<S extends BaseSchema> = {
type: 'data';
schema?: S | ((context: SchemaContext) => S);
};
type ContentCollectionConfig<S extends BaseSchema> = {
type?: 'content';
schema?: S | ((context: SchemaContext) => S);
};
type CollectionConfig<S> = ContentCollectionConfig<S> | DataCollectionConfig<S>;
export function defineCollection<S extends BaseSchema>(
input: CollectionConfig<S>
): CollectionConfig<S>;
type AllValuesOf<T> = T extends any ? T[keyof T] : never;
type ValidContentEntrySlug<C extends keyof ContentEntryMap> = AllValuesOf<
ContentEntryMap[C]
>['slug'];
export function getEntryBySlug<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(
collection: C,
// Note that this has to accept a regular string too, for SSR
entrySlug: E
): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
export function getDataEntryById<C extends keyof DataEntryMap, E extends keyof DataEntryMap[C]>(
collection: C,
entryId: E
): Promise<CollectionEntry<C>>;
export function getCollection<C extends keyof AnyEntryMap, E extends CollectionEntry<C>>(
collection: C,
filter?: (entry: CollectionEntry<C>) => entry is E
): Promise<E[]>;
export function getCollection<C extends keyof AnyEntryMap>(
collection: C,
filter?: (entry: CollectionEntry<C>) => unknown
): Promise<CollectionEntry<C>[]>;
export function getEntry<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(entry: {
collection: C;
slug: E;
}): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof DataEntryMap,
E extends keyof DataEntryMap[C] | (string & {}),
>(entry: {
collection: C;
id: E;
}): E extends keyof DataEntryMap[C]
? Promise<DataEntryMap[C][E]>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(
collection: C,
slug: E
): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof DataEntryMap,
E extends keyof DataEntryMap[C] | (string & {}),
>(
collection: C,
id: E
): E extends keyof DataEntryMap[C]
? Promise<DataEntryMap[C][E]>
: Promise<CollectionEntry<C> | undefined>;
/** Resolve an array of entry references from the same collection */
export function getEntries<C extends keyof ContentEntryMap>(
entries: {
collection: C;
slug: ValidContentEntrySlug<C>;
}[]
): Promise<CollectionEntry<C>[]>;
export function getEntries<C extends keyof DataEntryMap>(
entries: {
collection: C;
id: keyof DataEntryMap[C];
}[]
): Promise<CollectionEntry<C>[]>;
export function reference<C extends keyof AnyEntryMap>(
collection: C
): import('astro/zod').ZodEffects<
import('astro/zod').ZodString,
C extends keyof ContentEntryMap
? {
collection: C;
slug: ValidContentEntrySlug<C>;
}
: {
collection: C;
id: keyof DataEntryMap[C];
}
>;
// Allow generic `string` to avoid excessive type errors in the config
// if `dev` is not running to update as you edit.
// Invalid collection names will be caught at build time.
export function reference<C extends string>(
collection: C
): import('astro/zod').ZodEffects<import('astro/zod').ZodString, never>;
type ReturnTypeOrOriginal<T> = T extends (...args: any[]) => infer R ? R : T;
type InferEntrySchema<C extends keyof AnyEntryMap> = import('astro/zod').infer<
ReturnTypeOrOriginal<Required<ContentConfig['collections'][C]>['schema']>
>;
type ContentEntryMap = {
"blog": {
"release-0.0.2-schwindlig.mdx": {
id: "release-0.0.2-schwindlig.mdx";
slug: "release-002-schwindlig";
body: string;
collection: "blog";
data: InferEntrySchema<"blog">
} & { render(): Render[".mdx"] };
"release-0.0.2.1-stuermisch.mdx": {
id: "release-0.0.2.1-stuermisch.mdx";
slug: "release-0021-stuermisch";
body: string;
collection: "blog";
data: InferEntrySchema<"blog">
} & { render(): Render[".mdx"] };
"release-0.0.3-maelstrom.mdx": {
id: "release-0.0.3-maelstrom.mdx";
slug: "release-003-maelstrom";
body: string;
collection: "blog";
data: InferEntrySchema<"blog">
} & { render(): Render[".mdx"] };
"release-0.0.4-gischt.mdx": {
id: "release-0.0.4-gischt.mdx";
slug: "release-004-gischt";
body: string;
collection: "blog";
data: InferEntrySchema<"blog">
} & { render(): Render[".mdx"] };
"release-0.3.0-donauwelle.mdx": {
id: "release-0.3.0-donauwelle.mdx";
slug: "release-030-donauwelle";
body: string;
collection: "blog";
data: InferEntrySchema<"blog">
} & { render(): Render[".mdx"] };
"release-0.4.0-brandung.mdx": {
id: "release-0.4.0-brandung.mdx";
slug: "release-040-brandung";
body: string;
collection: "blog";
data: InferEntrySchema<"blog">
} & { render(): Render[".mdx"] };
"release-0.5.0-wirbel.mdx": {
id: "release-0.5.0-wirbel.mdx";
slug: "release-050-wirbel";
body: string;
collection: "blog";
data: InferEntrySchema<"blog">
} & { render(): Render[".mdx"] };
"release-0.6.0-zimtschnecke.mdx": {
id: "release-0.6.0-zimtschnecke.mdx";
slug: "release-060-zimtschnecke";
body: string;
collection: "blog";
data: InferEntrySchema<"blog">
} & { render(): Render[".mdx"] };
"release-0.7.0-zuckerguss.mdx": {
id: "release-0.7.0-zuckerguss.mdx";
slug: "release-070-zuckerguss";
body: string;
collection: "blog";
data: InferEntrySchema<"blog">
} & { render(): Render[".mdx"] };
"release-0.8.0-himbeermuffin.mdx": {
id: "release-0.8.0-himbeermuffin.mdx";
slug: "release-080-himbeermuffin";
body: string;
collection: "blog";
data: InferEntrySchema<"blog">
} & { render(): Render[".mdx"] };
"release-0.9.0-bananenbrot.mdx": {
id: "release-0.9.0-bananenbrot.mdx";
slug: "release-090-bananenbrot";
body: string;
collection: "blog";
data: InferEntrySchema<"blog">
} & { render(): Render[".mdx"] };
};
};
type DataEntryMap = {
};
type AnyEntryMap = ContentEntryMap & DataEntryMap;
type ContentConfig = typeof import("../src/content/config");
}
+120
View File
@@ -0,0 +1,120 @@
// generated with https://supabase.com/docs/reference/javascript/typescript-support#generating-typescript-types
export type Json = string | number | boolean | null | { [key: string]: Json | undefined } | Json[];
export interface Database {
public: {
Tables: {
code: {
Row: {
code: string | null;
created_at: string | null;
featured: boolean | null;
hash: string | null;
id: number;
public: boolean | null;
};
Insert: {
code?: string | null;
created_at?: string | null;
featured?: boolean | null;
hash?: string | null;
id?: number;
public?: boolean | null;
};
Update: {
code?: string | null;
created_at?: string | null;
featured?: boolean | null;
hash?: string | null;
id?: number;
public?: boolean | null;
};
Relationships: [];
};
};
Views: {
[_ in never]: never;
};
Functions: {
[_ in never]: never;
};
Enums: {
[_ in never]: never;
};
CompositeTypes: {
[_ in never]: never;
};
};
}
export type Tables<
PublicTableNameOrOptions extends
| keyof (Database['public']['Tables'] & Database['public']['Views'])
| { schema: keyof Database },
TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
? keyof (Database[PublicTableNameOrOptions['schema']]['Tables'] &
Database[PublicTableNameOrOptions['schema']]['Views'])
: never = never,
> = PublicTableNameOrOptions extends { schema: keyof Database }
? (Database[PublicTableNameOrOptions['schema']]['Tables'] &
Database[PublicTableNameOrOptions['schema']]['Views'])[TableName] extends {
Row: infer R;
}
? R
: never
: PublicTableNameOrOptions extends keyof (Database['public']['Tables'] & Database['public']['Views'])
? (Database['public']['Tables'] & Database['public']['Views'])[PublicTableNameOrOptions] extends {
Row: infer R;
}
? R
: never
: never;
export type TablesInsert<
PublicTableNameOrOptions extends keyof Database['public']['Tables'] | { schema: keyof Database },
TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
? keyof Database[PublicTableNameOrOptions['schema']]['Tables']
: never = never,
> = PublicTableNameOrOptions extends { schema: keyof Database }
? Database[PublicTableNameOrOptions['schema']]['Tables'][TableName] extends {
Insert: infer I;
}
? I
: never
: PublicTableNameOrOptions extends keyof Database['public']['Tables']
? Database['public']['Tables'][PublicTableNameOrOptions] extends {
Insert: infer I;
}
? I
: never
: never;
export type TablesUpdate<
PublicTableNameOrOptions extends keyof Database['public']['Tables'] | { schema: keyof Database },
TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
? keyof Database[PublicTableNameOrOptions['schema']]['Tables']
: never = never,
> = PublicTableNameOrOptions extends { schema: keyof Database }
? Database[PublicTableNameOrOptions['schema']]['Tables'][TableName] extends {
Update: infer U;
}
? U
: never
: PublicTableNameOrOptions extends keyof Database['public']['Tables']
? Database['public']['Tables'][PublicTableNameOrOptions] extends {
Update: infer U;
}
? U
: never
: never;
export type Enums<
PublicEnumNameOrOptions extends keyof Database['public']['Enums'] | { schema: keyof Database },
EnumName extends PublicEnumNameOrOptions extends { schema: keyof Database }
? keyof Database[PublicEnumNameOrOptions['schema']]['Enums']
: never = never,
> = PublicEnumNameOrOptions extends { schema: keyof Database }
? Database[PublicEnumNameOrOptions['schema']]['Enums'][EnumName]
: PublicEnumNameOrOptions extends keyof Database['public']['Enums']
? Database['public']['Enums'][PublicEnumNameOrOptions]
: never;
+13 -12
View File
@@ -1,5 +1,5 @@
{ {
"name": "@strudel.cycles/website", "name": "@strudel/website",
"type": "module", "type": "module",
"version": "0.6.0", "version": "0.6.0",
"private": true, "private": true,
@@ -23,17 +23,17 @@
"@heroicons/react": "^2.1.1", "@heroicons/react": "^2.1.1",
"@nanostores/persistent": "^0.9.1", "@nanostores/persistent": "^0.9.1",
"@nanostores/react": "^0.7.1", "@nanostores/react": "^0.7.1",
"@strudel.cycles/core": "workspace:*", "@strudel/core": "workspace:*",
"@strudel.cycles/csound": "workspace:*", "@strudel/csound": "workspace:*",
"@strudel.cycles/midi": "workspace:*", "@strudel/midi": "workspace:*",
"@strudel.cycles/mini": "workspace:*", "@strudel/mini": "workspace:*",
"@strudel.cycles/osc": "workspace:*", "@strudel/osc": "workspace:*",
"@strudel.cycles/serial": "workspace:*", "@strudel/serial": "workspace:*",
"@strudel.cycles/soundfonts": "workspace:*", "@strudel/soundfonts": "workspace:*",
"@strudel.cycles/tonal": "workspace:*", "@strudel/tonal": "workspace:*",
"@strudel.cycles/transpiler": "workspace:*", "@strudel/transpiler": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*", "@strudel/webaudio": "workspace:*",
"@strudel.cycles/xen": "workspace:*", "@strudel/xen": "workspace:*",
"@strudel/codemirror": "workspace:*", "@strudel/codemirror": "workspace:*",
"@strudel/desktopbridge": "workspace:*", "@strudel/desktopbridge": "workspace:*",
"@strudel/hydra": "workspace:*", "@strudel/hydra": "workspace:*",
@@ -48,6 +48,7 @@
"astro": "^4.0.8", "astro": "^4.0.8",
"canvas": "^2.11.2", "canvas": "^2.11.2",
"claviature": "^0.1.0", "claviature": "^0.1.0",
"date-fns": "^3.2.0",
"nanoid": "^5.0.4", "nanoid": "^5.0.4",
"nanostores": "^0.9.5", "nanostores": "^0.9.5",
"react": "^18.2.0", "react": "^18.2.0",
+27
View File
@@ -0,0 +1,27 @@
---
import type { CollectionEntry } from 'astro:content';
type Props = CollectionEntry<'blog'>['data'];
const { post } = Astro.props;
const { Content } = await post.render();
import { format } from 'date-fns';
---
<article
class="prose max-w-none prose-headings:font-sans prose-headings:font-black prose-headings:text-slate-900 dark:prose-headings:text-gray-200 dark:text-gray-400 dark:prose-strong:text-gray-400 dark:prose-code:text-slate-400 dark:prose-a:text-gray-300 prose-a:text-slate-900 prose-blockquote:text-slate-800 dark:prose-blockquote:text-slate-400"
>
<div class="pb-2">
<div class="md:flex justify-between">
<h1 class="mb-4" id={post.slug}>{post.data.title}</h1>
</div>
<p class="italic p-0 m-0">
<time datetime={post.data.date.toISOString()}>
{format(post.data.date, 'MMMM yyyy')} by {post.data.author}
</time>
</p>
</div>
<div>
<Content />
</div>
</article>
+5
View File
@@ -0,0 +1,5 @@
---
const { src } = Astro.props;
---
<video controls width="600" src={src}></video>
+1 -1
View File
@@ -9,7 +9,7 @@ import MobileNav from '../../docs/MobileNav';
import { SIDEBAR } from '../../config'; import { SIDEBAR } from '../../config';
type Props = { type Props = {
currentPage: string; currentPage?: string;
}; };
const { currentPage } = Astro.props as Props; const { currentPage } = Astro.props as Props;
@@ -1,18 +1,11 @@
--- ---
import type { Frontmatter } from '../../config';
import MoreMenu from '../RightSidebar/MoreMenu.astro'; import MoreMenu from '../RightSidebar/MoreMenu.astro';
import TableOfContents from '../RightSidebar/TableOfContents';
import type { MarkdownHeading } from 'astro';
type Props = { type Props = {
frontmatter: Frontmatter; githubEditUrl?: string;
headings: MarkdownHeading[];
githubEditUrl: string;
}; };
const { frontmatter, headings, githubEditUrl } = Astro.props as Props; const { githubEditUrl } = Astro.props as Props;
const title = frontmatter.title;
const currentPage = Astro.url.pathname;
--- ---
<article id="article" class="content"> <article id="article" class="content">
+2 -2
View File
@@ -1,7 +1,7 @@
import useEvent from '@src/useEvent.mjs'; import useEvent from '@src/useEvent.mjs';
import useFrame from '@src/useFrame.mjs'; import useFrame from '@src/useFrame.mjs';
import { getAudioContext } from '@strudel.cycles/webaudio'; import { getAudioContext } from '@strudel/webaudio';
import { midi2note } from '@strudel.cycles/core'; import { midi2note } from '@strudel/core';
import { useState, useRef, useEffect } from 'react'; import { useState, useRef, useEffect } from 'react';
import Claviature from '@components/Claviature'; import Claviature from '@components/Claviature';
@@ -2,7 +2,7 @@
import * as CONFIG from '../../config'; import * as CONFIG from '../../config';
type Props = { type Props = {
editHref: string; editHref?: string;
}; };
const { editHref } = Astro.props as Props; const { editHref } = Astro.props as Props;
@@ -6,7 +6,7 @@ import AvatarList from '../Footer/AvatarList.astro';
type Props = { type Props = {
headings: MarkdownHeading[]; headings: MarkdownHeading[];
githubEditUrl: string; githubEditUrl?: string;
}; };
const { headings, githubEditUrl } = Astro.props as Props; const { headings, githubEditUrl } = Astro.props as Props;
+7
View File
@@ -36,6 +36,13 @@ export function Showcase() {
} }
let _videos = [ let _videos = [
{ title: 'Coding Music With Strudel Workhop by Dan Gorelick and Viola He', id: 'oqyAJ4WeKoU' },
{ title: 'Hexe - playing w strudel live coding music', id: '03m3F5xVOMg' },
{ title: 'DJ_Dave - Array [Lil Data Edit]', id: 'KUujFuTcuKc' },
{ title: 'DJ_Dave - Bitrot [v10101a Edit]', id: 'z_cJMdBp67Q' },
{ title: 'you will not steve reich your way out of it', id: 'xpILnXcWyuo' },
{ title: 'dough dream #1 - strudel jam 12/03/23', id: 'p0J7XrT9JEs' },
{ title: 'eddyflux & superdirtspatz at the dough cathedral', id: 'GrkwKMQ7_Ys' },
// solstice 2023 // solstice 2023
{ title: 'Jade Rose @ solstice stream 2023', id: 'wg0vW5Ac7L0' }, { title: 'Jade Rose @ solstice stream 2023', id: 'wg0vW5Ac7L0' },
{ {
+1
View File
@@ -57,6 +57,7 @@ export const SIDEBAR: Sidebar = {
Presentation: [ Presentation: [
{ text: 'What is Strudel?', link: 'workshop/getting-started' }, { text: 'What is Strudel?', link: 'workshop/getting-started' },
{ text: 'Showcase', link: 'intro/showcase' }, { text: 'Showcase', link: 'intro/showcase' },
{ text: 'Blog', link: 'blog' },
], ],
Workshop: [ Workshop: [
// { text: 'Getting Started', link: 'workshop/getting-started' }, // { text: 'Getting Started', link: 'workshop/getting-started' },
@@ -0,0 +1,40 @@
---
title: 'Release Notes v0.0.2 Schwindlig'
description: ''
date: '2022-03-28'
tags: ['meta']
author: froos
---
## What's Changed
- Most work done as [commits to main](https://github.com/tidalcycles/strudel/commits/2a0d8c3f77ff7b34e82602e2d02400707f367316)
- repl + reify functions by @felixroos in https://github.com/tidalcycles/strudel/pull/2
- Fix path by @yaxu in https://github.com/tidalcycles/strudel/pull/3
- update readme for local dev by @kindohm in https://github.com/tidalcycles/strudel/pull/4
- Patternify all the things by @yaxu in https://github.com/tidalcycles/strudel/pull/5
- krill parser + improved repl by @felixroos in https://github.com/tidalcycles/strudel/pull/6
- fixed editor crash by @felixroos in https://github.com/tidalcycles/strudel/pull/7
- timeCat by @yaxu in https://github.com/tidalcycles/strudel/pull/8
- Bugfix every, and create more top level functions by @yaxu in https://github.com/tidalcycles/strudel/pull/9
- Failing test for `when` WIP by @yaxu in https://github.com/tidalcycles/strudel/pull/10
- Added mask() and struct() by @yaxu in https://github.com/tidalcycles/strudel/pull/11
- Add continuous signals (sine, cosine, saw, etc) by @yaxu in https://github.com/tidalcycles/strudel/pull/13
- add apply and layer, and missing div/mul methods by @yaxu in https://github.com/tidalcycles/strudel/pull/15
- higher latencyHint by @felixroos in https://github.com/tidalcycles/strudel/pull/16
- test: 📦 Add missing dependency and a CI check, to prevent oversights ;p by @puria in https://github.com/tidalcycles/strudel/pull/17
- fix: 💄 Enhance visualisation of the Tutorial on mobile by @puria in https://github.com/tidalcycles/strudel/pull/19
- Stateful queries and events (WIP) by @yaxu in https://github.com/tidalcycles/strudel/pull/14
- Fix resolveState by @yaxu in https://github.com/tidalcycles/strudel/pull/22
- added \_asNumber + interprete numbers as midi by @felixroos in https://github.com/tidalcycles/strudel/pull/21
- Update package.json by @ChiakiUehira in https://github.com/tidalcycles/strudel/pull/23
- packaging by @felixroos in https://github.com/tidalcycles/strudel/pull/24
## New Contributors
- @felixroos made their first contribution in https://github.com/tidalcycles/strudel/pull/2
- @kindohm made their first contribution in https://github.com/tidalcycles/strudel/pull/4
- @puria made their first contribution in https://github.com/tidalcycles/strudel/pull/17
- @ChiakiUehira made their first contribution in https://github.com/tidalcycles/strudel/pull/23
**Full Changelog**: https://github.com/tidalcycles/strudel/commits/2a0d8c3f77ff7b34e82602e2d02400707f367316

Some files were not shown because too many files have changed in this diff Show More