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-workspace.yaml
**/dev-dist
website/.astro
+1 -1
View File
@@ -114,7 +114,7 @@ You can run the same check with `pnpm check`
## Package Workflow
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.
## Package Publishing
+1 -1
View File
@@ -7,7 +7,7 @@
/>
<div id="output"></div>
<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
const input = document.getElementById('text');
const getEvents = () => {
+1 -1
View File
@@ -8,7 +8,7 @@
/>
<canvas id="canvas"></canvas>
<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
Object.assign(window, strudel);
// setup elements
+9 -9
View File
@@ -1,11 +1,11 @@
import { StrudelMirror } from '@strudel/codemirror';
import { funk42 } from './tunes';
import { drawPianoroll, evalScope, controls } from '@strudel.cycles/core';
import { drawPianoroll, evalScope, controls } from '@strudel/core';
import './style.css';
import { initAudioOnFirstClick } from '@strudel.cycles/webaudio';
import { transpiler } from '@strudel.cycles/transpiler';
import { getAudioContext, webaudioOutput, registerSynthSounds } from '@strudel.cycles/webaudio';
import { registerSoundfonts } from '@strudel.cycles/soundfonts';
import { initAudioOnFirstClick } from '@strudel/webaudio';
import { transpiler } from '@strudel/transpiler';
import { getAudioContext, webaudioOutput, registerSynthSounds } from '@strudel/webaudio';
import { registerSoundfonts } from '@strudel/soundfonts';
// init canvas
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..)
const loadModules = evalScope(
controls,
import('@strudel.cycles/core'),
import('@strudel.cycles/mini'),
import('@strudel.cycles/tonal'),
import('@strudel.cycles/webaudio'),
import('@strudel/core'),
import('@strudel/mini'),
import('@strudel/tonal'),
import('@strudel/webaudio'),
);
await Promise.all([loadModules, registerSynthSounds(), registerSoundfonts()]);
},
+6 -6
View File
@@ -13,11 +13,11 @@
},
"dependencies": {
"@strudel/codemirror": "workspace:*",
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/mini": "workspace:*",
"@strudel.cycles/soundfonts": "workspace:*",
"@strudel.cycles/tonal": "workspace:*",
"@strudel.cycles/transpiler": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*"
"@strudel/core": "workspace:*",
"@strudel/mini": "workspace:*",
"@strudel/soundfonts": "workspace:*",
"@strudel/tonal": "workspace:*",
"@strudel/transpiler": "workspace:*",
"@strudel/webaudio": "workspace:*"
}
}
+7 -7
View File
@@ -1,6 +1,6 @@
import { controls, repl, evalScope } from '@strudel.cycles/core';
import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel.cycles/webaudio';
import { transpiler } from '@strudel.cycles/transpiler';
import { controls, repl, evalScope } from '@strudel/core';
import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel/webaudio';
import { transpiler } from '@strudel/transpiler';
import tune from './tune.mjs';
const ctx = getAudioContext();
@@ -10,10 +10,10 @@ initAudioOnFirstClick();
evalScope(
controls,
import('@strudel.cycles/core'),
import('@strudel.cycles/mini'),
import('@strudel.cycles/webaudio'),
import('@strudel.cycles/tonal'),
import('@strudel/core'),
import('@strudel/mini'),
import('@strudel/webaudio'),
import('@strudel/tonal'),
);
const { evaluate } = repl({
+5 -5
View File
@@ -13,10 +13,10 @@
"vite": "^5.0.10"
},
"dependencies": {
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/mini": "workspace:*",
"@strudel.cycles/transpiler": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*",
"@strudel.cycles/tonal": "workspace:*"
"@strudel/core": "workspace:*",
"@strudel/mini": "workspace:*",
"@strudel/transpiler": "workspace:*",
"@strudel/webaudio": "workspace:*",
"@strudel/tonal": "workspace:*"
}
}
+7 -7
View File
@@ -1,5 +1,5 @@
{
"name": "@strudel.cycles/monorepo",
"name": "@strudel/monorepo",
"version": "0.5.0",
"private": true,
"description": "Port of tidalcycles to javascript",
@@ -45,12 +45,12 @@
},
"homepage": "https://strudel.cc",
"dependencies": {
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/mini": "workspace:*",
"@strudel.cycles/tonal": "workspace:*",
"@strudel.cycles/transpiler": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*",
"@strudel.cycles/xen": "workspace:*"
"@strudel/core": "workspace:*",
"@strudel/mini": "workspace:*",
"@strudel/tonal": "workspace:*",
"@strudel/transpiler": "workspace:*",
"@strudel/webaudio": "workspace:*",
"@strudel/xen": "workspace:*"
},
"devDependencies": {
"dependency-tree": "^10.0.9",
+1 -1
View File
@@ -1,5 +1,5 @@
# 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.
+7 -1
View File
@@ -3,6 +3,12 @@ import jsdoc from '../../doc.json';
import { autocompletion } from '@codemirror/autocomplete';
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 getInnerText = (html) => {
var div = document.createElement('div');
@@ -21,7 +27,7 @@ ${doc.description}
)}
</ul>
<div>
${doc.examples?.map((example) => `<div><pre>${example}</pre></div>`)}
${doc.examples?.map((example) => `<div><pre>${plaintext(example)}</pre></div>`)}
</div>
</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,
drawSelection,
} 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 { isTooltipEnabled } from './tooltip.mjs';
import { flash, isFlashEnabled } from './flash.mjs';
@@ -21,7 +21,6 @@ import { keybindings } from './keybindings.mjs';
import { initTheme, activateTheme, theme } from './themes.mjs';
import { updateWidgets, sliderPlugin } from './slider.mjs';
import { persistentAtom } from '@nanostores/persistent';
import { backlayer } from './backlayer.mjs';
const extensions = {
isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []),
@@ -77,7 +76,6 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
history(),
EditorView.updateListener.of((v) => onChange(v)),
drawSelection({ cursorBlinkRate: 0 }),
backlayer,
Prec.highest(
keymap.of([
{
-1
View File
@@ -3,4 +3,3 @@ export * from './highlight.mjs';
export * from './flash.mjs';
export * from './slider.mjs';
export * from './themes.mjs';
export * from './backlayer.mjs';
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/codemirror",
"version": "0.9.0",
"version": "0.11.0",
"description": "Codemirror Extensions for Strudel",
"main": "index.mjs",
"publishConfig": {
@@ -41,14 +41,14 @@
"@codemirror/state": "^6.4.0",
"@codemirror/view": "^6.23.0",
"@lezer/highlight": "^1.2.0",
"@nanostores/persistent": "^0.9.1",
"@replit/codemirror-emacs": "^6.0.1",
"@replit/codemirror-vim": "^6.1.0",
"@replit/codemirror-vscode-keymap": "^6.0.2",
"@strudel.cycles/core": "workspace:*",
"@strudel/core": "workspace:*",
"@uiw/codemirror-themes": "^4.21.21",
"@uiw/codemirror-themes-all": "^4.21.21",
"nanostores": "^0.9.5",
"@nanostores/persistent": "^0.9.1"
"nanostores": "^0.9.5"
},
"devDependencies": {
"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 { 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.
## Install
```sh
npm i @strudel.cycles/core --save
npm i @strudel/core --save
```
## Example
```js
import { sequence } from '@strudel.cycles/core';
import { sequence } from '@strudel/core';
const pattern = sequence('a', ['b', 'c']);
@@ -33,7 +33,7 @@ b: 3/2 - 7/4
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 minimal repl example](https://raw.githack.com/tidalcycles/strudel/main/packages/core/examples/vanilla.html)
- [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)
*
*/
['decay'],
['decay', 'dec'],
/**
* 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>")
*
*/
[['bandf', 'bandq'], 'bpf', 'bp'],
[['bandf', 'bandq', 'bpenv'], 'bpf', 'bp'],
// TODO: in tidal, it seems to be normalized
/**
* 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")
*
*/
[['cutoff', 'resonance'], 'ctf', 'lpf', 'lp'],
[['cutoff', 'resonance', 'lpenv'], 'ctf', 'lpf', 'lp'],
/**
* Sets the lowpass filter envelope modulation depth.
@@ -758,7 +758,7 @@ const generic_params = [
* .vibmod("<.25 .5 1 2 12>:8")
*/
[['vibmod', 'vib'], 'vmod'],
[['hcutoff', 'hresonance'], 'hpf', 'hp'],
[['hcutoff', 'hresonance', 'hpenv'], 'hpf', 'hp'],
/**
* Controls the **h**igh-**p**ass **q**-value.
*
@@ -891,6 +891,82 @@ const generic_params = [
*
*/
['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
['gate', 'gat'],
// ['hatgrain'],
@@ -1209,7 +1285,7 @@ const generic_params = [
* Formant filter to make things sound like vowels.
*
* @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
* note("c2 <eb2 <g2 g1>>").s('sawtooth')
* .vowel("<a e i <o u>>")
@@ -1394,10 +1470,20 @@ controls.adsr = register('adsr', (adsr, pat) => {
const [attack, decay, sustain, release] = adsr;
return pat.set({ attack, decay, sustain, release });
});
controls.ds = register('ds', (ds, pat) => {
ds = !Array.isArray(ds) ? [ds] : ds;
const [decay, sustain] = ds;
controls.ad = register('ad', (t, pat) => {
t = !Array.isArray(t) ? [t] : t;
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 });
});
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;
+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)
/* import * as p from './package.json';
export const version = p.version; */
logger('🌀 @strudel.cycles/core loaded 🌀');
logger('🌀 @strudel/core loaded 🌀');
if (globalThis._strudelLoaded) {
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.
Please check with "npm ls @strudel.cycles/core".`,
Please check with "npm ls @strudel/core".`,
);
}
globalThis._strudelLoaded = true;
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/core",
"version": "0.9.0",
"name": "@strudel/core",
"version": "0.11.0",
"description": "Port of Tidal Cycles to JavaScript",
"main": "index.mjs",
"type": "module",
+2 -2
View File
@@ -1154,8 +1154,8 @@ export function isPattern(thing) {
/* if (!thing instanceof Pattern) {
console.warn(
`Found Pattern that fails "instanceof Pattern" check.
This may happen if you are using multiple versions of @strudel.cycles/core.
Please check by running "npm ls @strudel.cycles/core".`,
This may happen if you are using multiple versions of @strudel/core.
Please check by running "npm ls @strudel/core".`,
);
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)
export const _mod = (n, m) => ((n % m) + m) % m;
export function nanFallback(value, fallback) {
export function nanFallback(value, fallback = 0) {
if (isNaN(Number(value))) {
logger(`"${value}" is not a number, falling back to ${fallback}`, 'warning');
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 { getAudioContext } from '@strudel.cycles/webaudio';
import { getFrequency, logger, register } from '@strudel/core';
import { getAudioContext } from '@strudel/webaudio';
import csd from './project.csd?raw';
// import livecodeOrc from './livecode.orc?raw';
import presetsOrc from './presets.orc?raw';
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/csound",
"version": "0.9.0",
"name": "@strudel/csound",
"version": "0.11.0",
"description": "csound bindings for strudel",
"main": "index.mjs",
"publishConfig": {
@@ -33,8 +33,8 @@
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@csound/browser": "6.18.7",
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*"
"@strudel/core": "workspace:*",
"@strudel/webaudio": "workspace:*"
},
"devDependencies": {
"vite": "^5.0.10"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Invoke } from './utils.mjs';
import { Pattern, noteToMidi } from '@strudel.cycles/core';
import { Pattern, noteToMidi } from '@strudel/core';
const ON_MESSAGE = 0x90;
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';
Pattern.prototype.osc = function () {
+1 -1
View File
@@ -22,7 +22,7 @@
"url": "https://github.com/tidalcycles/strudel/issues"
},
"dependencies": {
"@strudel.cycles/core": "workspace:*",
"@strudel/core": "workspace:*",
"@tauri-apps/api": "^1.5.3"
},
"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.
## 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
<script src="https://unpkg.com/@strudel.cycles/embed@latest"></script>
<script src="https://unpkg.com/@strudel/embed@latest"></script>
<strudel-repl>
<!--
note(`[[e5 [b4 c5] d5 [c5 b4]]
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/embed",
"version": "0.2.0",
"name": "@strudel/embed",
"version": "0.11.0",
"description": "Embeddable Web Component to load a Strudel REPL into an iframe",
"main": "embed.js",
"type": "module",
+1 -1
View File
@@ -27,7 +27,7 @@ npm i @strudel/hydra
Then add the import to your evalScope:
```js
import { evalScope } from '@strudel.cycles/core';
import { evalScope } from '@strudel/core';
evalScope(
import('@strudel/hydra')
+1 -1
View File
@@ -1,4 +1,4 @@
import { getDrawContext } from '@strudel.cycles/core';
import { getDrawContext } from '@strudel/core';
let latestOptions;
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/hydra",
"version": "0.9.0",
"version": "0.11.0",
"description": "Hydra integration for strudel",
"main": "hydra.mjs",
"publishConfig": {
@@ -33,7 +33,7 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "workspace:*",
"@strudel/core": "workspace:*",
"hydra-synth": "^1.3.29"
},
"devDependencies": {
+2 -2
View File
@@ -1,9 +1,9 @@
# @strudel.cycles/midi
# @strudel/midi
This package adds midi functionality to strudel Patterns.
## Install
```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 { Pattern, isPattern, logger, ref } from '@strudel.cycles/core';
import { noteToMidi } from '@strudel.cycles/core';
import { Pattern, isPattern, logger, ref } from '@strudel/core';
import { noteToMidi } from '@strudel/core';
import { Note } from 'webmidi';
// if you use WebMidi from outside of this package, make sure to import that instance:
export const { WebMidi } = _WebMidi;
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/midi",
"version": "0.9.0",
"name": "@strudel/midi",
"version": "0.11.0",
"description": "Midi API for strudel",
"main": "index.mjs",
"publishConfig": {
@@ -29,8 +29,8 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*",
"@strudel/core": "workspace:*",
"@strudel/webaudio": "workspace:*",
"webmidi": "^3.1.8"
},
"devDependencies": {
+4 -4
View File
@@ -1,17 +1,17 @@
# @strudel.cycles/mini
# @strudel/mini
This package contains the mini notation parser and pattern generator.
## Install
```sh
npm i @strudel.cycles/mini --save
npm i @strudel/mini --save
```
## Example
```js
import { mini } from '@strudel.cycles/mini';
import { mini } from '@strudel/mini';
const pattern = mini('a [b c*2]');
@@ -28,7 +28,7 @@ yields:
(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
+2 -2
View File
@@ -292,7 +292,7 @@ function peg$parse(input, options) {
var peg$f3 = function(s) { return s };
var peg$f4 = function(s, stepsPerCycle) { s.arguments_.stepsPerCycle = stepsPerCycle ; return s; };
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$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 }}) };
@@ -1073,7 +1073,7 @@ function peg$parse(input, options) {
}
if (s2 !== peg$FAILED) {
s3 = peg$parsews();
s4 = peg$parsesequence();
s4 = peg$parsepolymeter_stack();
if (s4 !== peg$FAILED) {
s5 = peg$parsews();
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
// change the alignment to slowcat
slow_sequence = ws "<" ws s:sequence ws ">" ws
{ s.arguments_.alignment = 'slowcat'; return s; }
slow_sequence = ws "<" ws s:polymeter_stack ws ">" ws
{ s.arguments_.alignment = 'polymeter_slowcat'; return s; }
// a slice is either a single step or a sub cycle
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 strudel from '@strudel.cycles/core';
import * as strudel from '@strudel/core';
const randOffset = 0.0003;
@@ -91,6 +91,10 @@ export function patternifyAST(ast, code, onEnter, offset = 0) {
if (alignment === 'stack') {
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') {
// polymeter
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);
}
const weightedChildren = ast.source_.some((child) => !!child.options_?.weight);
if (!weightedChildren && alignment === 'slowcat') {
return strudel.slowcat(...children);
}
if (weightedChildren) {
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]]));
if (alignment === 'slowcat') {
return pat._slow(weightSum); // timecat + slow
}
pat.__weight = weightSum;
return pat;
}
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/mini",
"version": "0.9.0",
"name": "@strudel/mini",
"version": "0.11.0",
"description": "Mini notation for strudel",
"main": "index.mjs",
"type": "module",
@@ -32,7 +32,7 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "workspace:*"
"@strudel/core": "workspace:*"
},
"devDependencies": {
"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 '@strudel.cycles/core/euclid.mjs';
import '@strudel/core/euclid.mjs';
import { describe, expect, it } from 'vitest';
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.
+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 { logger, parseNumeral, Pattern } from '@strudel.cycles/core';
import { logger, parseNumeral, Pattern } from '@strudel/core';
let connection; // Promise<OSC>
function connect() {
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/osc",
"version": "0.9.0",
"name": "@strudel/osc",
"version": "0.11.0",
"description": "OSC messaging for strudel",
"main": "osc.mjs",
"publishConfig": {
@@ -36,7 +36,7 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "workspace:*",
"@strudel/core": "workspace:*",
"osc-js": "^2.4.0"
},
"devDependencies": {
+9 -9
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/repl",
"version": "0.9.4",
"version": "0.11.0",
"description": "Strudel REPL as a Web Component",
"main": "index.mjs",
"publishConfig": {
@@ -33,15 +33,15 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"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/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": {
"@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 { registerSynthSounds, registerZZFXSounds, samples } from '@strudel.cycles/webaudio';
import * as core from '@strudel.cycles/core';
import { controls, noteToMidi, valueToMidi, Pattern, evalScope } from '@strudel/core';
import { registerSynthSounds, registerZZFXSounds, samples } from '@strudel/webaudio';
import * as core from '@strudel/core';
export async function prebake() {
const modulesLoading = evalScope(
// import('@strudel.cycles/core'),
// import('@strudel/core'),
core,
import('@strudel.cycles/mini'),
import('@strudel.cycles/tonal'),
import('@strudel.cycles/webaudio'),
import('@strudel/mini'),
import('@strudel/tonal'),
import('@strudel/webaudio'),
import('@strudel/codemirror'),
import('@strudel/hydra'),
import('@strudel.cycles/soundfonts'),
import('@strudel.cycles/midi'),
// import('@strudel.cycles/xen'),
// import('@strudel.cycles/serial'),
// import('@strudel.cycles/csound'),
// import('@strudel.cycles/osc'),
import('@strudel/soundfonts'),
import('@strudel/midi'),
// import('@strudel/xen'),
// import('@strudel/serial'),
// import('@strudel/csound'),
// import('@strudel/osc'),
controls, // sadly, this cannot be exported from core directly (yet)
);
// load samples
@@ -26,10 +26,10 @@ export async function prebake() {
registerSynthSounds(),
registerZZFXSounds(),
//registerSoundfonts(),
// need dynamic import here, because importing @strudel.cycles/soundfonts fails on server:
// => getting "window is not defined", as soon as "@strudel.cycles/soundfonts" is imported statically
// need dynamic import here, because importing @strudel/soundfonts fails on server:
// => getting "window is not defined", as soon as "@strudel/soundfonts" is imported statically
// 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}/piano.json`),
samples(`${ds}/Dirt-Samples.json`),
+3 -3
View File
@@ -1,6 +1,6 @@
import { getDrawContext, silence } from '@strudel.cycles/core';
import { transpiler } from '@strudel.cycles/transpiler';
import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio';
import { getDrawContext, silence } from '@strudel/core';
import { transpiler } from '@strudel/transpiler';
import { getAudioContext, webaudioOutput } from '@strudel/webaudio';
import { StrudelMirror, codemirrorSettings } from '@strudel/codemirror';
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.
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/serial",
"version": "0.9.0",
"name": "@strudel/serial",
"version": "0.11.0",
"description": "Webserial API for strudel",
"main": "serial.mjs",
"publishConfig": {
@@ -29,7 +29,7 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "workspace:*"
"@strudel/core": "workspace:*"
},
"devDependencies": {
"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/>.
*/
import { Pattern, isPattern } from '@strudel.cycles/core';
import { Pattern, isPattern } from '@strudel/core';
var writeMessagers = {};
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 { getAudioContext, registerSound, getEnvelope } from '@strudel.cycles/webaudio';
import { noteToMidi, freqToMidi, getSoundIndex } from '@strudel/core';
import {
getAudioContext,
registerSound,
getParamADSR,
getADSRValues,
getPitchEnvelope,
getVibratoOscillator,
} from '@strudel/webaudio';
import gm from './gm.mjs';
let loadCache = {};
@@ -130,24 +137,39 @@ export function registerSoundfonts() {
registerSound(
name,
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 { attack = 0.001, decay = 0.001, sustain = 1, release = 0.001 } = value;
const font = fonts[n];
const ctx = getAudioContext();
const bufferSource = await getFontBufferSource(font, value, ctx);
bufferSource.start(time);
const { node: envelope, stop: releaseEnvelope } = getEnvelope(attack, decay, sustain, release, 0.3, time);
bufferSource.connect(envelope);
const stop = (releaseTime) => {
const silentAt = releaseEnvelope(releaseTime);
bufferSource.stop(silentAt);
};
const envGain = ctx.createGain();
const node = bufferSource.connect(envGain);
const holdEnd = time + duration;
getParamADSR(node.gain, attack, decay, sustain, release, 0, 0.3, time, holdEnd, 'linear');
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.disconnect();
envelope.disconnect();
vibratoOscillator?.stop();
node.disconnect();
onended();
};
return { node: envelope, stop };
return { node, stop };
},
{ type: 'soundfont', prebake: true, fonts },
);
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/soundfonts",
"version": "0.9.0",
"name": "@strudel/soundfonts",
"version": "0.11.0",
"description": "Soundsfont support for strudel",
"main": "index.mjs",
"publishConfig": {
@@ -30,8 +30,8 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*",
"@strudel/core": "workspace:*",
"@strudel/webaudio": "workspace:*",
"sfumato": "^0.1.2",
"soundfont2": "^0.4.0"
},
+2 -2
View File
@@ -1,5 +1,5 @@
import { Pattern, getPlayableNoteValue, noteToMidi } from '@strudel.cycles/core';
import { getAudioContext, registerSound } from '@strudel.cycles/webaudio';
import { Pattern, getPlayableNoteValue, noteToMidi } from '@strudel/core';
import { getAudioContext, registerSound } from '@strudel/webaudio';
import { loadSoundfont as _loadSoundfont, startPresetNote } from 'sfumato';
Pattern.prototype.soundfont = function (sf, n = 0) {
+132 -94
View File
@@ -1,5 +1,5 @@
import { getAudioContext } from './superdough.mjs';
import { clamp } from './util.mjs';
import { clamp, nanFallback } from './util.mjs';
export function gainNode(value) {
const node = getAudioContext().createGain();
@@ -7,78 +7,73 @@ export function gainNode(value) {
return node;
}
// alternative to getADSR returning the gain node and a stop handle to trigger the release anytime in the future
export const getEnvelope = (attack, decay, sustain, release, velocity, begin) => {
const gainNode = getAudioContext().createGain();
let phase = begin;
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);
const getSlope = (y1, y2, x1, x2) => {
const denom = x2 - x1;
if (denom === 0) {
return 0;
}
gainNode.gain.exponentialRampToValueAtTime(0.001, end + release); // release */
return gainNode;
return (y2 - y1) / (x2 - x1);
};
export const getParamADSR = (param, attack, decay, sustain, release, min, max, begin, end) => {
export const getParamADSR = (
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 peak = min + range;
const sustainLevel = min + sustain * range;
const peak = max;
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.linearRampToValueAtTime(peak, begin + attack);
param.linearRampToValueAtTime(sustainLevel, begin + attack + decay);
param.setValueAtTime(sustainLevel, end);
param.linearRampToValueAtTime(min, end + Math.max(release, 0.1));
if (attack > duration) {
//attack
param[ramp](envValAtTime(duration), end);
} 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) {
@@ -92,38 +87,44 @@ export function getCompressor(ac, threshold, ratio, knee, attack, release) {
return new DynamicsCompressorNode(ac, options);
}
export function createFilter(
context,
type,
frequency,
Q,
attack,
decay,
sustain,
release,
fenv,
start,
end,
fanchor = 0.5,
) {
// changes the default values of the envelope based on what parameters the user has defined
// so it behaves more like you would expect/familiar as other synthesis tools
// 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.
export const getADSRValues = (params, curve = 'linear', defaultValues) => {
const envmin = curve === 'exponential' ? 0.001 : 0.001;
const releaseMin = 0.01;
const envmax = 1;
const [a, d, s, r] = params;
if (a == null && d == null && s == null && r == null) {
return defaultValues ?? [envmin, envmin, envmax, releaseMin];
}
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();
filter.type = type;
filter.Q.value = Q;
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
if (!isNaN(fenv) && fenv !== 0) {
const offset = fenv * fanchor;
const min = clamp(2 ** -offset * frequency, 0, 20000);
const max = clamp(2 ** (fenv - offset) * frequency, 0, 20000);
// console.log('min', min, 'max', max);
getParamADSR(filter.frequency, attack, decay, sustain, release, min, max, start, end);
if (hasEnvelope !== undefined) {
fenv = nanFallback(fenv, 1, true);
fanchor = nanFallback(fanchor, 0, true);
const fenvAbs = Math.abs(fenv);
const offset = fenvAbs * fanchor;
let min = clamp(2 ** -offset * frequency, 0, 20000);
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, curve);
return filter;
}
return filter;
}
@@ -148,3 +149,40 @@ export function drywet(dry, wet, wetAmount = 0) {
wet_gain.connect(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",
"version": "0.9.12",
"version": "0.10.0",
"description": "simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.",
"main": "index.mjs",
"type": "module",
+23 -28
View File
@@ -1,6 +1,6 @@
import { noteToMidi, valueToMidi, getSoundIndex } from './util.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';
const bufferCache = {}; // string: Promise<ArrayBuffer>
@@ -243,8 +243,7 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
begin = 0,
loopEnd = 1,
end = 1,
vib,
vibmod = 0.5,
duration,
} = value;
// load sample
if (speed === 0) {
@@ -254,24 +253,15 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
loop = s.startsWith('wt_') ? 1 : value.loop;
const ac = getAudioContext();
// 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 time = t + nudge;
const bufferSource = await getSampleBufferSource(s, n, note, speed, freq, bank, resolveUrl);
// vibrato
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(bufferSource.detune);
vibratoOscillator.start(0);
}
let vibratoOscillator = getVibratoOscillator(bufferSource.detune, value, t);
// asny stuff above took too long?
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.start(time, offset);
const { node: envelope, stop: releaseEnvelope } = getEnvelope(attack, decay, sustain, release, 1, t);
bufferSource.connect(envelope);
const envGain = ac.createGain();
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...
envelope.connect(out);
node.connect(out);
bufferSource.onended = function () {
bufferSource.disconnect();
vibratoOscillator?.stop();
envelope.disconnect();
node.disconnect();
out.disconnect();
onended();
};
const stop = (endTime, playWholeBuffer = clip === undefined && loop === undefined) => {
let releaseTime = endTime;
if (playWholeBuffer) {
const bufferDuration = bufferSource.buffer.duration / bufferSource.playbackRate.value;
releaseTime = t + (end - begin) * bufferDuration;
}
const silentAt = releaseEnvelope(releaseTime);
bufferSource.stop(silentAt);
};
let envEnd = holdEnd + release + 0.01;
bufferSource.stop(envEnd);
const stop = (endTime, playWholeBuffer) => {};
const handle = { node: out, bufferSource, stop };
// cut groups
+13 -12
View File
@@ -280,26 +280,26 @@ export const superdough = async (value, deadline, hapDuration) => {
// low pass
cutoff,
lpenv,
lpattack = 0.01,
lpdecay = 0.01,
lpsustain = 1,
lprelease = 0.01,
lpattack,
lpdecay,
lpsustain,
lprelease,
resonance = 1,
// high pass
hpenv,
hcutoff,
hpattack = 0.01,
hpdecay = 0.01,
hpsustain = 1,
hprelease = 0.01,
hpattack,
hpdecay,
hpsustain,
hprelease,
hresonance = 1,
// band pass
bpenv,
bandf,
bpattack = 0.01,
bpdecay = 0.01,
bpsustain = 1,
bprelease = 0.01,
bpattack,
bpdecay,
bpsustain,
bprelease,
bandq = 1,
channels = [1, 2],
//phaser
@@ -333,6 +333,7 @@ export const superdough = async (value, deadline, hapDuration) => {
compressorAttack,
compressorRelease,
} = value;
gain = nanFallback(gain, 1);
//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 { 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';
const mod = (freq, range = 1, type = 'sine') => {
@@ -29,8 +29,11 @@ export function registerSynthSounds() {
registerSound(
s,
(t, value, onended) => {
// destructure adsr here, because the default should be different for synths and samples
let { attack = 0.001, decay = 0.05, sustain = 0.6, release = 0.01 } = value;
const [attack, decay, sustain, release] = getADSRValues(
[value.attack, value.decay, value.sustain, value.release],
'linear',
[0.001, 0.05, 0.6, 0.01],
);
let sound;
if (waveforms.includes(s)) {
@@ -45,21 +48,24 @@ export function registerSynthSounds() {
// turn down
const g = gainNode(0.3);
// gain envelope
const { node: envelope, stop: releaseEnvelope } = getEnvelope(attack, decay, sustain, release, 1, t);
const { duration } = value;
o.onended = () => {
o.disconnect();
g.disconnect();
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 {
node: o.connect(g).connect(envelope),
stop: (releaseTime) => {
const silentAt = releaseEnvelope(releaseTime);
triggerRelease?.(releaseTime);
stop(silentAt);
},
node,
stop: (releaseTime) => {},
};
},
{ type: 'synth', prebake: true },
@@ -99,28 +105,24 @@ export function waveformN(partials, type) {
}
// expects one of waveforms as s
export function getOscillator(
s,
t,
{
export function getOscillator(s, t, value) {
let {
n: partials,
note,
freq,
vib = 0,
vibmod = 0.5,
noise = 0,
// fm
fmh: fmHarmonicity = 1,
fmi: fmModulationIndex,
fmenv: fmEnvelopeType = 'lin',
fmenv: fmEnvelopeType = 'exp',
fmattack: fmAttack,
fmdecay: fmDecay,
fmsustain: fmSustain,
fmrelease: fmRelease,
fmvelocity: fmVelocity,
fmwave: fmWaveform = 'sine',
},
) {
duration,
} = value;
let ac = getAudioContext();
let o;
// If no partials are given, use stock waveforms
@@ -148,42 +150,39 @@ export function getOscillator(
o.start(t);
// FM
let stopFm, fmEnvelope;
let stopFm;
let envGain = ac.createGain();
if (fmModulationIndex) {
const { node: modulator, stop } = fm(o, fmHarmonicity, fmModulationIndex, fmWaveform);
if (![fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity].find((v) => v !== undefined)) {
// no envelope by default
modulator.connect(o.frequency);
} else {
fmAttack = fmAttack ?? 0.001;
fmDecay = fmDecay ?? 0.001;
fmSustain = fmSustain ?? 1;
fmRelease = fmRelease ?? 0.001;
fmVelocity = fmVelocity ?? 1;
fmEnvelope = getEnvelope(fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity, t);
if (fmEnvelopeType === 'exp') {
fmEnvelope = getExpEnvelope(fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity, t);
fmEnvelope.node.maxValue = fmModulationIndex * 2;
fmEnvelope.node.minValue = 0.00001;
}
modulator.connect(fmEnvelope.node);
fmEnvelope.node.connect(o.frequency);
const [attack, decay, sustain, release] = getADSRValues([fmAttack, fmDecay, fmSustain, fmRelease]);
const holdEnd = t + duration;
getParamADSR(
envGain.gain,
attack,
decay,
sustain,
release,
0,
1,
t,
holdEnd,
fmEnvelopeType === 'exp' ? 'exponential' : 'linear',
);
modulator.connect(envGain);
envGain.connect(o.frequency);
}
stopFm = stop;
}
// Additional oscillator for vibrato effect
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(o.detune);
vibratoOscillator.start(t);
}
let vibratoOscillator = getVibratoOscillator(o.detune, value, t);
// pitch envelope
getPitchEnvelope(o.detune, value, t, t + duration);
let noiseMix;
if (noise) {
@@ -199,7 +198,7 @@ export function getOscillator(
o.stop(time);
},
triggerRelease: (time) => {
fmEnvelope?.stop(time);
// envGain?.stop(time);
},
};
}
+2 -2
View File
@@ -54,9 +54,9 @@ export const valueToMidi = (value, fallbackValue) => {
return fallbackValue;
};
export function nanFallback(value, fallback) {
export function nanFallback(value, fallback = 0, silent) {
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 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] },
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] },
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') {
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.
## Install
```sh
npm i @strudel.cycles/tonal --save
npm i @strudel/tonal --save
```
## Example
```js
import { sequence } from '@strudel.cycles/core';
import '@strudel.cycles/tonal';
import { sequence } from '@strudel/core';
import '@strudel/tonal';
const pattern = sequence(0, [1, 2]).scale('C major');
@@ -27,7 +27,7 @@ yields:
(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
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/tonal",
"version": "0.9.0",
"name": "@strudel/tonal",
"version": "0.11.0",
"description": "Tonal functions for strudel",
"main": "index.mjs",
"publishConfig": {
@@ -31,7 +31,7 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "workspace:*",
"@strudel/core": "workspace:*",
"@tonaljs/tonal": "^4.7.2",
"chord-voicings": "^0.0.1",
"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 '../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 { mini } from '../../mini/mini.mjs';
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 { 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';
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';
// 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/>.
*/
import { stack, register, silence, logger } from '@strudel.cycles/core';
import { stack, register, silence, logger } from '@strudel/core';
import { renderVoicing } from './tonleiter.mjs';
import _voicings from 'chord-voicings';
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:
@@ -9,14 +9,14 @@ This package contains a JS code transpiler with the following features:
## Install
```sh
npm i @strudel.cycles/transpiler
npm i @strudel/transpiler
```
## Use
```js
import { transpiler } from '@strudel.cycles/core';
import { evaluate } from '@strudel.cycles/core';
import { transpiler } from '@strudel/core';
import { evaluate } from '@strudel/core';
transpiler('note("c3 [e3,g3]")', { wrapAsync: false, addReturn: false, simpleLocs: true });
/* 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';
export * from './transpiler.mjs';
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/transpiler",
"version": "0.9.0",
"name": "@strudel/transpiler",
"version": "0.11.0",
"description": "Transpiler for strudel user code. Converts syntactically correct but semantically meaningless JS into evaluatable strudel code.",
"main": "index.mjs",
"publishConfig": {
@@ -30,8 +30,8 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/mini": "workspace:*",
"@strudel/core": "workspace:*",
"@strudel/mini": "workspace:*",
"acorn": "^8.11.3",
"escodegen": "^2.1.0",
"estree-walker": "^3.0.1"
+2 -2
View File
@@ -1,8 +1,8 @@
import escodegen from 'escodegen';
import { parse } from 'acorn';
import { walk } from 'estree-walker';
import { isNoteWithOctave } from '@strudel.cycles/core';
import { getLeafLocations } from '@strudel.cycles/mini';
import { isNoteWithOctave } from '@strudel/core';
import { getLeafLocations } from '@strudel/mini';
export function transpiler(input, options = {}) {
const { wrapAsync = false, addReturn = true, emitMiniLocations = true, emitWidgets = true } = options;
+6 -6
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/web",
"version": "0.9.0",
"version": "0.11.0",
"description": "Easy to setup, opiniated bundle of Strudel for the browser.",
"main": "web.mjs",
"publishConfig": {
@@ -33,11 +33,11 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/mini": "workspace:*",
"@strudel.cycles/tonal": "workspace:*",
"@strudel.cycles/transpiler": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*"
"@strudel/core": "workspace:*",
"@strudel/mini": "workspace:*",
"@strudel/tonal": "workspace:*",
"@strudel/transpiler": "workspace:*",
"@strudel/webaudio": "workspace:*"
},
"devDependencies": {
"vite": "^5.0.10"
+16 -16
View File
@@ -1,25 +1,25 @@
export * from '@strudel.cycles/core';
export * from '@strudel.cycles/webaudio';
//export * from '@strudel.cycles/soundfonts';
export * from '@strudel.cycles/transpiler';
export * from '@strudel.cycles/mini';
export * from '@strudel.cycles/tonal';
export * from '@strudel.cycles/webaudio';
import { Pattern, evalScope, controls } from '@strudel.cycles/core';
import { initAudioOnFirstClick, registerSynthSounds, webaudioScheduler } from '@strudel.cycles/webaudio';
// import { registerSoundfonts } from '@strudel.cycles/soundfonts';
import { evaluate as _evaluate } from '@strudel.cycles/transpiler';
import { miniAllStrings } from '@strudel.cycles/mini';
export * from '@strudel/core';
export * from '@strudel/webaudio';
//export * from '@strudel/soundfonts';
export * from '@strudel/transpiler';
export * from '@strudel/mini';
export * from '@strudel/tonal';
export * from '@strudel/webaudio';
import { Pattern, evalScope, controls } from '@strudel/core';
import { initAudioOnFirstClick, registerSynthSounds, webaudioScheduler } from '@strudel/webaudio';
// import { registerSoundfonts } from '@strudel/soundfonts';
import { evaluate as _evaluate } from '@strudel/transpiler';
import { miniAllStrings } from '@strudel/mini';
// init logic
export async function defaultPrebake() {
const loadModules = evalScope(
evalScope,
controls,
import('@strudel.cycles/core'),
import('@strudel.cycles/mini'),
import('@strudel.cycles/tonal'),
import('@strudel.cycles/webaudio'),
import('@strudel/core'),
import('@strudel/mini'),
import('@strudel/tonal'),
import('@strudel/webaudio'),
{ hush, evaluate },
);
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.
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
```sh
npm i @strudel.cycles/webaudio --save
npm i @strudel/webaudio --save
```
## Example
```js
import { repl, controls } from "@strudel.cycles/core";
import { initAudioOnFirstClick, getAudioContext, webaudioOutput } from "@strudel.cycles/webaudio";
import { repl, controls } from "@strudel/core";
import { initAudioOnFirstClick, getAudioContext, webaudioOutput } from "@strudel/webaudio";
const { note } = controls;
initAudioOnFirstClick();
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/webaudio",
"version": "0.9.0",
"name": "@strudel/webaudio",
"version": "0.11.0",
"description": "Web Audio helpers for Strudel",
"main": "index.mjs",
"type": "module",
@@ -34,7 +34,7 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "workspace:*",
"@strudel/core": "workspace:*",
"superdough": "workspace:*"
},
"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';
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/>.
*/
import * as strudel from '@strudel.cycles/core';
import * as strudel from '@strudel/core';
import { superdough, getAudioContext, setLogger, doughTrigger } from 'superdough';
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.
## Install
```sh
npm i @strudel.cycles/xen --save
npm i @strudel/xen --save
```
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/xen",
"version": "0.9.0",
"name": "@strudel/xen",
"version": "0.11.0",
"description": "Xenharmonic API for strudel",
"main": "index.mjs",
"publishConfig": {
@@ -30,7 +30,7 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "workspace:*"
"@strudel/core": "workspace:*"
},
"devDependencies": {
"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 { register } from '@strudel.cycles/core';
import { register } from '@strudel/core';
export const tune = register('tune', (scale, pat) => {
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/>.
*/
import { register, _mod, parseNumeral } from '@strudel.cycles/core';
import { register, _mod, parseNumeral } from '@strudel/core';
export function edo(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
+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`] = `
[
"[ 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`] = `
[
"[ 0/1 → 1/2 | s:hh ]",
+1 -1
View File
@@ -1,5 +1,5 @@
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', () => {
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
// import * as tunes from './tunes.mjs';
import { evaluate } from '@strudel.cycles/transpiler';
import { evalScope } from '@strudel.cycles/core';
import * as strudel from '@strudel.cycles/core';
import * as webaudio from '@strudel.cycles/webaudio';
import controls from '@strudel.cycles/core/controls.mjs';
// import gist from '@strudel.cycles/core/gist.js';
import { mini, m } from '@strudel.cycles/mini/mini.mjs';
// import * as voicingHelpers from '@strudel.cycles/tonal/voicings.mjs';
// import euclid from '@strudel.cycles/core/euclid.mjs';
// import '@strudel.cycles/midi/midi.mjs';
import * as tonalHelpers from '@strudel.cycles/tonal';
import '@strudel.cycles/xen/xen.mjs';
// import '@strudel.cycles/xen/tune.mjs';
// import '@strudel.cycles/core/euclid.mjs';
// import '@strudel.cycles/core/speak.mjs'; // window is not defined
// import '@strudel.cycles/osc/osc.mjs';
// import '@strudel.cycles/webaudio/webaudio.mjs';
// import '@strudel.cycles/serial/serial.mjs';
// import controls from '@strudel.cycles/core/controls.mjs';
import { evaluate } from '@strudel/transpiler';
import { evalScope } from '@strudel/core';
import * as strudel from '@strudel/core';
import * as webaudio from '@strudel/webaudio';
import controls from '@strudel/core/controls.mjs';
// import gist from '@strudel/core/gist.js';
import { mini, m } from '@strudel/mini/mini.mjs';
// import * as voicingHelpers from '@strudel/tonal/voicings.mjs';
// import euclid from '@strudel/core/euclid.mjs';
// import '@strudel/midi/midi.mjs';
import * as tonalHelpers from '@strudel/tonal';
import '@strudel/xen/xen.mjs';
// import '@strudel/xen/tune.mjs';
// import '@strudel/core/euclid.mjs';
// import '@strudel/core/speak.mjs'; // window is not defined
// import '@strudel/osc/osc.mjs';
// import '@strudel/webaudio/webaudio.mjs';
// import '@strudel/serial/serial.mjs';
// import controls from '@strudel/core/controls.mjs';
import '../website/src/repl/piano';
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",
"version": "0.6.0",
"private": true,
@@ -23,17 +23,17 @@
"@heroicons/react": "^2.1.1",
"@nanostores/persistent": "^0.9.1",
"@nanostores/react": "^0.7.1",
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/csound": "workspace:*",
"@strudel.cycles/midi": "workspace:*",
"@strudel.cycles/mini": "workspace:*",
"@strudel.cycles/osc": "workspace:*",
"@strudel.cycles/serial": "workspace:*",
"@strudel.cycles/soundfonts": "workspace:*",
"@strudel.cycles/tonal": "workspace:*",
"@strudel.cycles/transpiler": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*",
"@strudel.cycles/xen": "workspace:*",
"@strudel/core": "workspace:*",
"@strudel/csound": "workspace:*",
"@strudel/midi": "workspace:*",
"@strudel/mini": "workspace:*",
"@strudel/osc": "workspace:*",
"@strudel/serial": "workspace:*",
"@strudel/soundfonts": "workspace:*",
"@strudel/tonal": "workspace:*",
"@strudel/transpiler": "workspace:*",
"@strudel/webaudio": "workspace:*",
"@strudel/xen": "workspace:*",
"@strudel/codemirror": "workspace:*",
"@strudel/desktopbridge": "workspace:*",
"@strudel/hydra": "workspace:*",
@@ -48,6 +48,7 @@
"astro": "^4.0.8",
"canvas": "^2.11.2",
"claviature": "^0.1.0",
"date-fns": "^3.2.0",
"nanoid": "^5.0.4",
"nanostores": "^0.9.5",
"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';
type Props = {
currentPage: string;
currentPage?: string;
};
const { currentPage } = Astro.props as Props;
@@ -1,18 +1,11 @@
---
import type { Frontmatter } from '../../config';
import MoreMenu from '../RightSidebar/MoreMenu.astro';
import TableOfContents from '../RightSidebar/TableOfContents';
import type { MarkdownHeading } from 'astro';
type Props = {
frontmatter: Frontmatter;
headings: MarkdownHeading[];
githubEditUrl: string;
githubEditUrl?: string;
};
const { frontmatter, headings, githubEditUrl } = Astro.props as Props;
const title = frontmatter.title;
const currentPage = Astro.url.pathname;
const { githubEditUrl } = Astro.props as Props;
---
<article id="article" class="content">
+2 -2
View File
@@ -1,7 +1,7 @@
import useEvent from '@src/useEvent.mjs';
import useFrame from '@src/useFrame.mjs';
import { getAudioContext } from '@strudel.cycles/webaudio';
import { midi2note } from '@strudel.cycles/core';
import { getAudioContext } from '@strudel/webaudio';
import { midi2note } from '@strudel/core';
import { useState, useRef, useEffect } from 'react';
import Claviature from '@components/Claviature';
@@ -2,7 +2,7 @@
import * as CONFIG from '../../config';
type Props = {
editHref: string;
editHref?: string;
};
const { editHref } = Astro.props as Props;
@@ -6,7 +6,7 @@ import AvatarList from '../Footer/AvatarList.astro';
type Props = {
headings: MarkdownHeading[];
githubEditUrl: string;
githubEditUrl?: string;
};
const { headings, githubEditUrl } = Astro.props as Props;
+7
View File
@@ -36,6 +36,13 @@ export function Showcase() {
}
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
{ title: 'Jade Rose @ solstice stream 2023', id: 'wg0vW5Ac7L0' },
{
+1
View File
@@ -57,6 +57,7 @@ export const SIDEBAR: Sidebar = {
Presentation: [
{ text: 'What is Strudel?', link: 'workshop/getting-started' },
{ text: 'Showcase', link: 'intro/showcase' },
{ text: 'Blog', link: 'blog' },
],
Workshop: [
// { 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