Compare commits

..

1 Commits

Author SHA1 Message Date
Felix Roos 2375a7c1a2 almost working custom background layer behind selection 2024-01-07 00:09:30 +01:00
149 changed files with 1556 additions and 3234 deletions
-1
View File
@@ -10,4 +10,3 @@ paper
pnpm-lock.yaml pnpm-lock.yaml
pnpm-workspace.yaml pnpm-workspace.yaml
**/dev-dist **/dev-dist
website/.astro
+1 -1
View File
@@ -114,7 +114,7 @@ You can run the same check with `pnpm check`
## Package Workflow ## Package Workflow
The project is split into multiple [packages](https://github.com/tidalcycles/strudel/tree/main/packages) with independent versioning. The project is split into multiple [packages](https://github.com/tidalcycles/strudel/tree/main/packages) with independent versioning.
When you run `pnpm i` on the root folder, [pnpm workspaces](https://pnpm.io/workspaces) will install all dependencies of all subpackages. This will allow any js file to import `@strudel/<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.cycles/<package-name>` to get the local version,
allowing to develop multiple packages at the same time. allowing to develop multiple packages at the same time.
## Package Publishing ## Package Publishing
+1 -1
View File
@@ -7,7 +7,7 @@
/> />
<div id="output"></div> <div id="output"></div>
<script type="module"> <script type="module">
const strudel = await import('https://cdn.skypack.dev/@strudel/core@0.6.8'); const strudel = await import('https://cdn.skypack.dev/@strudel.cycles/core@0.6.8');
Object.assign(window, strudel); // assign all strudel functions to global scope to use with eval Object.assign(window, strudel); // assign all strudel functions to global scope to use with eval
const input = document.getElementById('text'); const input = document.getElementById('text');
const getEvents = () => { const getEvents = () => {
+1 -1
View File
@@ -8,7 +8,7 @@
/> />
<canvas id="canvas"></canvas> <canvas id="canvas"></canvas>
<script type="module"> <script type="module">
const strudel = await import('https://cdn.skypack.dev/@strudel/core@0.6.8'); const strudel = await import('https://cdn.skypack.dev/@strudel.cycles/core@0.6.8');
// this adds all strudel functions to the global scope, to be used by eval // this adds all strudel functions to the global scope, to be used by eval
Object.assign(window, strudel); Object.assign(window, strudel);
// setup elements // setup elements
+9 -9
View File
@@ -1,11 +1,11 @@
import { StrudelMirror } from '@strudel/codemirror'; import { StrudelMirror } from '@strudel/codemirror';
import { funk42 } from './tunes'; import { funk42 } from './tunes';
import { drawPianoroll, evalScope, controls } from '@strudel/core'; import { drawPianoroll, evalScope, controls } from '@strudel.cycles/core';
import './style.css'; import './style.css';
import { initAudioOnFirstClick } from '@strudel/webaudio'; import { initAudioOnFirstClick } from '@strudel.cycles/webaudio';
import { transpiler } from '@strudel/transpiler'; import { transpiler } from '@strudel.cycles/transpiler';
import { getAudioContext, webaudioOutput, registerSynthSounds } from '@strudel/webaudio'; import { getAudioContext, webaudioOutput, registerSynthSounds } from '@strudel.cycles/webaudio';
import { registerSoundfonts } from '@strudel/soundfonts'; import { registerSoundfonts } from '@strudel.cycles/soundfonts';
// init canvas // init canvas
const canvas = document.getElementById('roll'); const canvas = document.getElementById('roll');
@@ -26,10 +26,10 @@ const editor = new StrudelMirror({
initAudioOnFirstClick(); // needed to make the browser happy (don't await this here..) initAudioOnFirstClick(); // needed to make the browser happy (don't await this here..)
const loadModules = evalScope( const loadModules = evalScope(
controls, controls,
import('@strudel/core'), import('@strudel.cycles/core'),
import('@strudel/mini'), import('@strudel.cycles/mini'),
import('@strudel/tonal'), import('@strudel.cycles/tonal'),
import('@strudel/webaudio'), import('@strudel.cycles/webaudio'),
); );
await Promise.all([loadModules, registerSynthSounds(), registerSoundfonts()]); await Promise.all([loadModules, registerSynthSounds(), registerSoundfonts()]);
}, },
+6 -6
View File
@@ -13,11 +13,11 @@
}, },
"dependencies": { "dependencies": {
"@strudel/codemirror": "workspace:*", "@strudel/codemirror": "workspace:*",
"@strudel/core": "workspace:*", "@strudel.cycles/core": "workspace:*",
"@strudel/mini": "workspace:*", "@strudel.cycles/mini": "workspace:*",
"@strudel/soundfonts": "workspace:*", "@strudel.cycles/soundfonts": "workspace:*",
"@strudel/tonal": "workspace:*", "@strudel.cycles/tonal": "workspace:*",
"@strudel/transpiler": "workspace:*", "@strudel.cycles/transpiler": "workspace:*",
"@strudel/webaudio": "workspace:*" "@strudel.cycles/webaudio": "workspace:*"
} }
} }
+7 -7
View File
@@ -1,6 +1,6 @@
import { controls, repl, evalScope } from '@strudel/core'; import { controls, repl, evalScope } from '@strudel.cycles/core';
import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel/webaudio'; import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel.cycles/webaudio';
import { transpiler } from '@strudel/transpiler'; import { transpiler } from '@strudel.cycles/transpiler';
import tune from './tune.mjs'; import tune from './tune.mjs';
const ctx = getAudioContext(); const ctx = getAudioContext();
@@ -10,10 +10,10 @@ initAudioOnFirstClick();
evalScope( evalScope(
controls, controls,
import('@strudel/core'), import('@strudel.cycles/core'),
import('@strudel/mini'), import('@strudel.cycles/mini'),
import('@strudel/webaudio'), import('@strudel.cycles/webaudio'),
import('@strudel/tonal'), import('@strudel.cycles/tonal'),
); );
const { evaluate } = repl({ const { evaluate } = repl({
+5 -5
View File
@@ -13,10 +13,10 @@
"vite": "^5.0.10" "vite": "^5.0.10"
}, },
"dependencies": { "dependencies": {
"@strudel/core": "workspace:*", "@strudel.cycles/core": "workspace:*",
"@strudel/mini": "workspace:*", "@strudel.cycles/mini": "workspace:*",
"@strudel/transpiler": "workspace:*", "@strudel.cycles/transpiler": "workspace:*",
"@strudel/webaudio": "workspace:*", "@strudel.cycles/webaudio": "workspace:*",
"@strudel/tonal": "workspace:*" "@strudel.cycles/tonal": "workspace:*"
} }
} }
+7 -7
View File
@@ -1,5 +1,5 @@
{ {
"name": "@strudel/monorepo", "name": "@strudel.cycles/monorepo",
"version": "0.5.0", "version": "0.5.0",
"private": true, "private": true,
"description": "Port of tidalcycles to javascript", "description": "Port of tidalcycles to javascript",
@@ -45,12 +45,12 @@
}, },
"homepage": "https://strudel.cc", "homepage": "https://strudel.cc",
"dependencies": { "dependencies": {
"@strudel/core": "workspace:*", "@strudel.cycles/core": "workspace:*",
"@strudel/mini": "workspace:*", "@strudel.cycles/mini": "workspace:*",
"@strudel/tonal": "workspace:*", "@strudel.cycles/tonal": "workspace:*",
"@strudel/transpiler": "workspace:*", "@strudel.cycles/transpiler": "workspace:*",
"@strudel/webaudio": "workspace:*", "@strudel.cycles/webaudio": "workspace:*",
"@strudel/xen": "workspace:*" "@strudel.cycles/xen": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"dependency-tree": "^10.0.9", "dependency-tree": "^10.0.9",
+1 -1
View File
@@ -1,5 +1,5 @@
# Packages # Packages
Each folder represents one of the @strudel/* packages [published to npm](https://www.npmjs.com/org/strudel). Each folder represents one of the @strudel.cycles/* packages [published to npm](https://www.npmjs.com/org/strudel.cycles).
To understand how those pieces connect, refer to the [Technical Manual](https://github.com/tidalcycles/strudel/wiki/Technical-Manual) or the individual READMEs. To understand how those pieces connect, refer to the [Technical Manual](https://github.com/tidalcycles/strudel/wiki/Technical-Manual) or the individual READMEs.
+1 -7
View File
@@ -3,12 +3,6 @@ import jsdoc from '../../doc.json';
import { autocompletion } from '@codemirror/autocomplete'; import { autocompletion } from '@codemirror/autocomplete';
import { h } from './html'; import { h } from './html';
function plaintext(str) {
const div = document.createElement('div');
div.innerText = str;
return div.innerHTML;
}
const getDocLabel = (doc) => doc.name || doc.longname; const getDocLabel = (doc) => doc.name || doc.longname;
const getInnerText = (html) => { const getInnerText = (html) => {
var div = document.createElement('div'); var div = document.createElement('div');
@@ -27,7 +21,7 @@ ${doc.description}
)} )}
</ul> </ul>
<div> <div>
${doc.examples?.map((example) => `<div><pre>${plaintext(example)}</pre></div>`)} ${doc.examples?.map((example) => `<div><pre>${example}</pre></div>`)}
</div> </div>
</div>`[0]; </div>`[0];
/* /*
+38
View File
@@ -0,0 +1,38 @@
// 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;
*/
+3 -1
View File
@@ -12,7 +12,7 @@ import {
lineNumbers, lineNumbers,
drawSelection, drawSelection,
} from '@codemirror/view'; } from '@codemirror/view';
import { Pattern, Drawer, repl, cleanupDraw } from '@strudel/core'; import { Pattern, Drawer, repl, cleanupDraw } from '@strudel.cycles/core';
import { isAutoCompletionEnabled } from './autocomplete.mjs'; import { isAutoCompletionEnabled } from './autocomplete.mjs';
import { isTooltipEnabled } from './tooltip.mjs'; import { isTooltipEnabled } from './tooltip.mjs';
import { flash, isFlashEnabled } from './flash.mjs'; import { flash, isFlashEnabled } from './flash.mjs';
@@ -21,6 +21,7 @@ import { keybindings } from './keybindings.mjs';
import { initTheme, activateTheme, theme } from './themes.mjs'; import { initTheme, activateTheme, theme } from './themes.mjs';
import { updateWidgets, sliderPlugin } from './slider.mjs'; import { updateWidgets, sliderPlugin } from './slider.mjs';
import { persistentAtom } from '@nanostores/persistent'; import { persistentAtom } from '@nanostores/persistent';
import { backlayer } from './backlayer.mjs';
const extensions = { const extensions = {
isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []), isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []),
@@ -76,6 +77,7 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
history(), history(),
EditorView.updateListener.of((v) => onChange(v)), EditorView.updateListener.of((v) => onChange(v)),
drawSelection({ cursorBlinkRate: 0 }), drawSelection({ cursorBlinkRate: 0 }),
backlayer,
Prec.highest( Prec.highest(
keymap.of([ keymap.of([
{ {
+1
View File
@@ -3,3 +3,4 @@ export * from './highlight.mjs';
export * from './flash.mjs'; export * from './flash.mjs';
export * from './slider.mjs'; export * from './slider.mjs';
export * from './themes.mjs'; export * from './themes.mjs';
export * from './backlayer.mjs';
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel/codemirror", "name": "@strudel/codemirror",
"version": "0.11.0", "version": "0.9.0",
"description": "Codemirror Extensions for Strudel", "description": "Codemirror Extensions for Strudel",
"main": "index.mjs", "main": "index.mjs",
"publishConfig": { "publishConfig": {
@@ -41,14 +41,14 @@
"@codemirror/state": "^6.4.0", "@codemirror/state": "^6.4.0",
"@codemirror/view": "^6.23.0", "@codemirror/view": "^6.23.0",
"@lezer/highlight": "^1.2.0", "@lezer/highlight": "^1.2.0",
"@nanostores/persistent": "^0.9.1",
"@replit/codemirror-emacs": "^6.0.1", "@replit/codemirror-emacs": "^6.0.1",
"@replit/codemirror-vim": "^6.1.0", "@replit/codemirror-vim": "^6.1.0",
"@replit/codemirror-vscode-keymap": "^6.0.2", "@replit/codemirror-vscode-keymap": "^6.0.2",
"@strudel/core": "workspace:*", "@strudel.cycles/core": "workspace:*",
"@uiw/codemirror-themes": "^4.21.21", "@uiw/codemirror-themes": "^4.21.21",
"@uiw/codemirror-themes-all": "^4.21.21", "@uiw/codemirror-themes-all": "^4.21.21",
"nanostores": "^0.9.5" "nanostores": "^0.9.5",
"@nanostores/persistent": "^0.9.1"
}, },
"devDependencies": { "devDependencies": {
"vite": "^5.0.10" "vite": "^5.0.10"
+1 -1
View File
@@ -1,4 +1,4 @@
import { ref, pure } from '@strudel/core'; import { ref, pure } from '@strudel.cycles/core';
import { WidgetType, ViewPlugin, Decoration } from '@codemirror/view'; import { WidgetType, ViewPlugin, Decoration } from '@codemirror/view';
import { StateEffect, StateField } from '@codemirror/state'; import { StateEffect, StateField } from '@codemirror/state';
+4 -4
View File
@@ -1,17 +1,17 @@
# @strudel/core # @strudel.cycles/core
This package contains the bare essence of strudel. This package contains the bare essence of strudel.
## Install ## Install
```sh ```sh
npm i @strudel/core --save npm i @strudel.cycles/core --save
``` ```
## Example ## Example
```js ```js
import { sequence } from '@strudel/core'; import { sequence } from '@strudel.cycles/core';
const pattern = sequence('a', ['b', 'c']); const pattern = sequence('a', ['b', 'c']);
@@ -33,7 +33,7 @@ b: 3/2 - 7/4
c: 7/4 - 2 c: 7/4 - 2
``` ```
- [play with @strudel/core on codesandbox](https://codesandbox.io/s/strudel-core-test-forked-9ywhv7?file=/src/index.js). - [play with @strudel.cycles/core on codesandbox](https://codesandbox.io/s/strudel-core-test-forked-9ywhv7?file=/src/index.js).
- [open color pattern example](https://raw.githack.com/tidalcycles/strudel/main/packages/core/examples/canvas.html) - [open color pattern example](https://raw.githack.com/tidalcycles/strudel/main/packages/core/examples/canvas.html)
- [open minimal repl example](https://raw.githack.com/tidalcycles/strudel/main/packages/core/examples/vanilla.html) - [open minimal repl example](https://raw.githack.com/tidalcycles/strudel/main/packages/core/examples/vanilla.html)
- [open minimal vite example](./examples/vite-vanilla-repl/) - [open minimal vite example](./examples/vite-vanilla-repl/)
+8 -94
View File
@@ -234,7 +234,7 @@ const generic_params = [
* note("c3 e3").decay("<.1 .2 .3 .4>").sustain(0) * note("c3 e3").decay("<.1 .2 .3 .4>").sustain(0)
* *
*/ */
['decay', 'dec'], ['decay'],
/** /**
* Amplitude envelope sustain level: The level which is reached after attack / decay, being sustained until the offset. * Amplitude envelope sustain level: The level which is reached after attack / decay, being sustained until the offset.
* *
@@ -270,7 +270,7 @@ const generic_params = [
* s("bd sd,hh*3").bpf("<1000 2000 4000 8000>") * s("bd sd,hh*3").bpf("<1000 2000 4000 8000>")
* *
*/ */
[['bandf', 'bandq', 'bpenv'], 'bpf', 'bp'], [['bandf', 'bandq'], 'bpf', 'bp'],
// TODO: in tidal, it seems to be normalized // TODO: in tidal, it seems to be normalized
/** /**
* Sets the **b**and-**p**ass **q**-factor (resonance). * Sets the **b**and-**p**ass **q**-factor (resonance).
@@ -481,7 +481,7 @@ const generic_params = [
* s("bd*8").lpf("1000:0 1000:10 1000:20 1000:30") * s("bd*8").lpf("1000:0 1000:10 1000:20 1000:30")
* *
*/ */
[['cutoff', 'resonance', 'lpenv'], 'ctf', 'lpf', 'lp'], [['cutoff', 'resonance'], 'ctf', 'lpf', 'lp'],
/** /**
* Sets the lowpass filter envelope modulation depth. * Sets the lowpass filter envelope modulation depth.
@@ -758,7 +758,7 @@ const generic_params = [
* .vibmod("<.25 .5 1 2 12>:8") * .vibmod("<.25 .5 1 2 12>:8")
*/ */
[['vibmod', 'vib'], 'vmod'], [['vibmod', 'vib'], 'vmod'],
[['hcutoff', 'hresonance', 'hpenv'], 'hpf', 'hp'], [['hcutoff', 'hresonance'], 'hpf', 'hp'],
/** /**
* Controls the **h**igh-**p**ass **q**-value. * Controls the **h**igh-**p**ass **q**-value.
* *
@@ -891,82 +891,6 @@ const generic_params = [
* *
*/ */
['freq'], ['freq'],
// pitch envelope
/**
* Attack time of pitch envelope.
*
* @name pattack
* @synonyms patt
* @param {number | Pattern} time time in seconds
* @example
* note("<c eb g bb>").pattack("<0 .1 .25 .5>")
*
*/
['pattack', 'patt'],
/**
* Decay time of pitch envelope.
*
* @name pdecay
* @synonyms pdec
* @param {number | Pattern} time time in seconds
* @example
* note("<c eb g bb>").pdecay("<0 .1 .25 .5>")
*
*/
['pdecay', 'pdec'],
// TODO: how to use psustain?!
['psustain', 'psus'],
/**
* Release time of pitch envelope
*
* @name prelease
* @synonyms prel
* @param {number | Pattern} time time in seconds
* @example
* note("<c eb g bb> ~")
* .release(.5) // to hear the pitch release
* .prelease("<0 .1 .25 .5>")
*
*/
['prelease', 'prel'],
/**
* Amount of pitch envelope. Negative values will flip the envelope.
* If you don't set other pitch envelope controls, `pattack:.2` will be the default.
*
* @name penv
* @param {number | Pattern} semitones change in semitones
* @example
* note("c")
* .penv("<12 7 1 .5 0 -1 -7 -12>")
*
*/
['penv'],
/**
* Curve of envelope. Defaults to linear. exponential is good for kicks
*
* @name pcurve
* @param {number | Pattern} type 0 = linear, 1 = exponential
* @example
* note("g1*2")
* .s("sine").pdec(.5)
* .penv(32)
* .pcurve("<0 1>")
*
*/
['pcurve'],
/**
* Sets the range anchor of the envelope:
* - anchor 0: range = [note, note + penv]
* - anchor 1: range = [note - penv, note]
* If you don't set an anchor, the value will default to the psustain value.
*
* @name panchor
* @param {number | Pattern} anchor anchor offset
* @example
* note("c").penv(12).panchor("<0 .5 1 .5>")
*
*/
['panchor'],
// TODO: https://tidalcycles.org/docs/configuration/MIDIOSC/control-voltage/#gate // TODO: https://tidalcycles.org/docs/configuration/MIDIOSC/control-voltage/#gate
['gate', 'gat'], ['gate', 'gat'],
// ['hatgrain'], // ['hatgrain'],
@@ -1285,7 +1209,7 @@ const generic_params = [
* Formant filter to make things sound like vowels. * Formant filter to make things sound like vowels.
* *
* @name vowel * @name vowel
* @param {string | Pattern} vowel You can use a e i o u ae aa oe ue y uh un en an on, corresponding to [a] [e] [i] [o] [u] [æ] [ɑ] [ø] [y] [ɯ] [ʌ] [œ̃] [ɛ̃] [ɑ̃] [ɔ̃]. Aliases: aa = å = ɑ, oe = ø = ö, y = ı, ae = æ. * @param {string | Pattern} vowel You can use a e i o u.
* @example * @example
* note("c2 <eb2 <g2 g1>>").s('sawtooth') * note("c2 <eb2 <g2 g1>>").s('sawtooth')
* .vowel("<a e i <o u>>") * .vowel("<a e i <o u>>")
@@ -1470,20 +1394,10 @@ controls.adsr = register('adsr', (adsr, pat) => {
const [attack, decay, sustain, release] = adsr; const [attack, decay, sustain, release] = adsr;
return pat.set({ attack, decay, sustain, release }); return pat.set({ attack, decay, sustain, release });
}); });
controls.ad = register('ad', (t, pat) => { controls.ds = register('ds', (ds, pat) => {
t = !Array.isArray(t) ? [t] : t; ds = !Array.isArray(ds) ? [ds] : ds;
const [attack, decay = attack] = t; const [decay, sustain] = ds;
return pat.attack(attack).decay(decay);
});
controls.ds = register('ds', (t, pat) => {
t = !Array.isArray(t) ? [t] : t;
const [decay, sustain = 0] = t;
return pat.set({ decay, sustain }); return pat.set({ decay, sustain });
}); });
controls.ds = register('ar', (t, pat) => {
t = !Array.isArray(t) ? [t] : t;
const [attack, release = attack] = t;
return pat.set({ attack, release });
});
export default controls; export default controls;
+3 -3
View File
@@ -30,12 +30,12 @@ export { default as drawLine } from './drawLine.mjs';
// below won't work with runtime.mjs (json import fails) // below won't work with runtime.mjs (json import fails)
/* import * as p from './package.json'; /* import * as p from './package.json';
export const version = p.version; */ export const version = p.version; */
logger('🌀 @strudel/core loaded 🌀'); logger('🌀 @strudel.cycles/core loaded 🌀');
if (globalThis._strudelLoaded) { if (globalThis._strudelLoaded) {
console.warn( console.warn(
`@strudel/core was loaded more than once... `@strudel.cycles/core was loaded more than once...
This might happen when you have multiple versions of strudel installed. This might happen when you have multiple versions of strudel installed.
Please check with "npm ls @strudel/core".`, Please check with "npm ls @strudel.cycles/core".`,
); );
} }
globalThis._strudelLoaded = true; globalThis._strudelLoaded = true;
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel/core", "name": "@strudel.cycles/core",
"version": "0.11.0", "version": "0.9.0",
"description": "Port of Tidal Cycles to JavaScript", "description": "Port of Tidal Cycles to JavaScript",
"main": "index.mjs", "main": "index.mjs",
"type": "module", "type": "module",
+2 -2
View File
@@ -1154,8 +1154,8 @@ export function isPattern(thing) {
/* if (!thing instanceof Pattern) { /* if (!thing instanceof Pattern) {
console.warn( console.warn(
`Found Pattern that fails "instanceof Pattern" check. `Found Pattern that fails "instanceof Pattern" check.
This may happen if you are using multiple versions of @strudel/core. This may happen if you are using multiple versions of @strudel.cycles/core.
Please check by running "npm ls @strudel/core".`, Please check by running "npm ls @strudel.cycles/core".`,
); );
console.log(thing); console.log(thing);
} */ } */
+1 -1
View File
@@ -86,7 +86,7 @@ export const midi2note = (n) => {
// modulo that works with negative numbers e.g. _mod(-1, 3) = 2. Works on numbers (rather than patterns of numbers, as @mod@ from pattern.mjs does) // modulo that works with negative numbers e.g. _mod(-1, 3) = 2. Works on numbers (rather than patterns of numbers, as @mod@ from pattern.mjs does)
export const _mod = (n, m) => ((n % m) + m) % m; export const _mod = (n, m) => ((n % m) + m) % m;
export function nanFallback(value, fallback = 0) { export function nanFallback(value, fallback) {
if (isNaN(Number(value))) { if (isNaN(Number(value))) {
logger(`"${value}" is not a number, falling back to ${fallback}`, 'warning'); logger(`"${value}" is not a number, falling back to ${fallback}`, 'warning');
return fallback; return fallback;
-1
View File
@@ -1 +0,0 @@
# @strudel/csound
+2 -2
View File
@@ -1,5 +1,5 @@
import { getFrequency, logger, register } from '@strudel/core'; import { getFrequency, logger, register } from '@strudel.cycles/core';
import { getAudioContext } from '@strudel/webaudio'; import { getAudioContext } from '@strudel.cycles/webaudio';
import csd from './project.csd?raw'; import csd from './project.csd?raw';
// import livecodeOrc from './livecode.orc?raw'; // import livecodeOrc from './livecode.orc?raw';
import presetsOrc from './presets.orc?raw'; import presetsOrc from './presets.orc?raw';
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel/csound", "name": "@strudel.cycles/csound",
"version": "0.11.0", "version": "0.9.0",
"description": "csound bindings for strudel", "description": "csound bindings for strudel",
"main": "index.mjs", "main": "index.mjs",
"publishConfig": { "publishConfig": {
@@ -33,8 +33,8 @@
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@csound/browser": "6.18.7", "@csound/browser": "6.18.7",
"@strudel/core": "workspace:*", "@strudel.cycles/core": "workspace:*",
"@strudel/webaudio": "workspace:*" "@strudel.cycles/webaudio": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"vite": "^5.0.10" "vite": "^5.0.10"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Invoke } from './utils.mjs'; import { Invoke } from './utils.mjs';
import { Pattern, noteToMidi } from '@strudel/core'; import { Pattern, noteToMidi } from '@strudel.cycles/core';
const ON_MESSAGE = 0x90; const ON_MESSAGE = 0x90;
const OFF_MESSAGE = 0x80; const OFF_MESSAGE = 0x80;
+1 -1
View File
@@ -1,4 +1,4 @@
import { parseNumeral, Pattern } from '@strudel/core'; import { parseNumeral, Pattern } from '@strudel.cycles/core';
import { Invoke } from './utils.mjs'; import { Invoke } from './utils.mjs';
Pattern.prototype.osc = function () { Pattern.prototype.osc = function () {
+1 -1
View File
@@ -22,7 +22,7 @@
"url": "https://github.com/tidalcycles/strudel/issues" "url": "https://github.com/tidalcycles/strudel/issues"
}, },
"dependencies": { "dependencies": {
"@strudel/core": "workspace:*", "@strudel.cycles/core": "workspace:*",
"@tauri-apps/api": "^1.5.3" "@tauri-apps/api": "^1.5.3"
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme" "homepage": "https://github.com/tidalcycles/strudel#readme"
+3 -3
View File
@@ -1,13 +1,13 @@
# @strudel/embed # @strudel.cycles/embed
This package contains a embeddable web component for the Strudel REPL. This package contains a embeddable web component for the Strudel REPL.
## Usage ## Usage
Either install with `npm i @strudel/embed` or just use a cdn to import the script: Either install with `npm i @strudel.cycles/embed` or just use a cdn to import the script:
```html ```html
<script src="https://unpkg.com/@strudel/embed@latest"></script> <script src="https://unpkg.com/@strudel.cycles/embed@latest"></script>
<strudel-repl> <strudel-repl>
<!-- <!--
note(`[[e5 [b4 c5] d5 [c5 b4]] note(`[[e5 [b4 c5] d5 [c5 b4]]
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel/embed", "name": "@strudel.cycles/embed",
"version": "0.11.0", "version": "0.2.0",
"description": "Embeddable Web Component to load a Strudel REPL into an iframe", "description": "Embeddable Web Component to load a Strudel REPL into an iframe",
"main": "embed.js", "main": "embed.js",
"type": "module", "type": "module",
+1 -1
View File
@@ -27,7 +27,7 @@ npm i @strudel/hydra
Then add the import to your evalScope: Then add the import to your evalScope:
```js ```js
import { evalScope } from '@strudel/core'; import { evalScope } from '@strudel.cycles/core';
evalScope( evalScope(
import('@strudel/hydra') import('@strudel/hydra')
+1 -1
View File
@@ -1,4 +1,4 @@
import { getDrawContext } from '@strudel/core'; import { getDrawContext } from '@strudel.cycles/core';
let latestOptions; let latestOptions;
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel/hydra", "name": "@strudel/hydra",
"version": "0.11.0", "version": "0.9.0",
"description": "Hydra integration for strudel", "description": "Hydra integration for strudel",
"main": "hydra.mjs", "main": "hydra.mjs",
"publishConfig": { "publishConfig": {
@@ -33,7 +33,7 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel/core": "workspace:*", "@strudel.cycles/core": "workspace:*",
"hydra-synth": "^1.3.29" "hydra-synth": "^1.3.29"
}, },
"devDependencies": { "devDependencies": {
+2 -2
View File
@@ -1,9 +1,9 @@
# @strudel/midi # @strudel.cycles/midi
This package adds midi functionality to strudel Patterns. This package adds midi functionality to strudel Patterns.
## Install ## Install
```sh ```sh
npm i @strudel/midi --save npm i @strudel.cycles/midi --save
``` ```
+2 -2
View File
@@ -5,8 +5,8 @@ This program is free software: you can redistribute it and/or modify it under th
*/ */
import * as _WebMidi from 'webmidi'; import * as _WebMidi from 'webmidi';
import { Pattern, isPattern, logger, ref } from '@strudel/core'; import { Pattern, isPattern, logger, ref } from '@strudel.cycles/core';
import { noteToMidi } from '@strudel/core'; import { noteToMidi } from '@strudel.cycles/core';
import { Note } from 'webmidi'; import { Note } from 'webmidi';
// if you use WebMidi from outside of this package, make sure to import that instance: // if you use WebMidi from outside of this package, make sure to import that instance:
export const { WebMidi } = _WebMidi; export const { WebMidi } = _WebMidi;
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel/midi", "name": "@strudel.cycles/midi",
"version": "0.11.0", "version": "0.9.0",
"description": "Midi API for strudel", "description": "Midi API for strudel",
"main": "index.mjs", "main": "index.mjs",
"publishConfig": { "publishConfig": {
@@ -29,8 +29,8 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel/core": "workspace:*", "@strudel.cycles/core": "workspace:*",
"@strudel/webaudio": "workspace:*", "@strudel.cycles/webaudio": "workspace:*",
"webmidi": "^3.1.8" "webmidi": "^3.1.8"
}, },
"devDependencies": { "devDependencies": {
+4 -4
View File
@@ -1,17 +1,17 @@
# @strudel/mini # @strudel.cycles/mini
This package contains the mini notation parser and pattern generator. This package contains the mini notation parser and pattern generator.
## Install ## Install
```sh ```sh
npm i @strudel/mini --save npm i @strudel.cycles/mini --save
``` ```
## Example ## Example
```js ```js
import { mini } from '@strudel/mini'; import { mini } from '@strudel.cycles/mini';
const pattern = mini('a [b c*2]'); const pattern = mini('a [b c*2]');
@@ -28,7 +28,7 @@ yields:
(7/8 -> 1/1, 7/8 -> 1/1, c) (7/8 -> 1/1, 7/8 -> 1/1, c)
``` ```
[Play with @strudel/mini codesandbox](https://codesandbox.io/s/strudel-mini-example-oe9wcu?file=/src/index.js) [Play with @strudel.cycles/mini codesandbox](https://codesandbox.io/s/strudel-mini-example-oe9wcu?file=/src/index.js)
## Mini Notation API ## Mini Notation API
+2 -2
View File
@@ -292,7 +292,7 @@ function peg$parse(input, options) {
var peg$f3 = function(s) { return s }; var peg$f3 = function(s) { return s };
var peg$f4 = function(s, stepsPerCycle) { s.arguments_.stepsPerCycle = stepsPerCycle ; return s; }; var peg$f4 = function(s, stepsPerCycle) { s.arguments_.stepsPerCycle = stepsPerCycle ; return s; };
var peg$f5 = function(a) { return a }; var peg$f5 = function(a) { return a };
var peg$f6 = function(s) { s.arguments_.alignment = 'polymeter_slowcat'; return s; }; var peg$f6 = function(s) { s.arguments_.alignment = 'slowcat'; return s; };
var peg$f7 = function(a) { return x => x.options_['weight'] = a }; var peg$f7 = function(a) { return x => x.options_['weight'] = a };
var peg$f8 = function(a) { return x => x.options_['reps'] = a }; var peg$f8 = function(a) { return x => x.options_['reps'] = a };
var peg$f9 = function(p, s, r) { return x => x.options_['ops'].push({ type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r }}) }; var peg$f9 = function(p, s, r) { return x => x.options_['ops'].push({ type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r }}) };
@@ -1073,7 +1073,7 @@ function peg$parse(input, options) {
} }
if (s2 !== peg$FAILED) { if (s2 !== peg$FAILED) {
s3 = peg$parsews(); s3 = peg$parsews();
s4 = peg$parsepolymeter_stack(); s4 = peg$parsesequence();
if (s4 !== peg$FAILED) { if (s4 !== peg$FAILED) {
s5 = peg$parsews(); s5 = peg$parsews();
if (input.charCodeAt(peg$currPos) === 62) { if (input.charCodeAt(peg$currPos) === 62) {
+2 -2
View File
@@ -119,8 +119,8 @@ polymeter_steps = "%"a:slice
// define a step-per-cycle timeline e.g <1 3 [3 5]>. We simply defer to a sequence and // define a step-per-cycle timeline e.g <1 3 [3 5]>. We simply defer to a sequence and
// change the alignment to slowcat // change the alignment to slowcat
slow_sequence = ws "<" ws s:polymeter_stack ws ">" ws slow_sequence = ws "<" ws s:sequence ws ">" ws
{ s.arguments_.alignment = 'polymeter_slowcat'; return s; } { s.arguments_.alignment = 'slowcat'; return s; }
// a slice is either a single step or a sub cycle // a slice is either a single step or a sub cycle
slice = step / sub_cycle / polymeter / slow_sequence slice = step / sub_cycle / polymeter / slow_sequence
+7 -5
View File
@@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
*/ */
import * as krill from './krill-parser.js'; import * as krill from './krill-parser.js';
import * as strudel from '@strudel/core'; import * as strudel from '@strudel.cycles/core';
const randOffset = 0.0003; const randOffset = 0.0003;
@@ -91,10 +91,6 @@ export function patternifyAST(ast, code, onEnter, offset = 0) {
if (alignment === 'stack') { if (alignment === 'stack') {
return strudel.stack(...children); return strudel.stack(...children);
} }
if (alignment === 'polymeter_slowcat') {
const aligned = children.map((child) => child._slow(strudel.Fraction(child.__weight ?? 1)));
return strudel.stack(...aligned);
}
if (alignment === 'polymeter') { if (alignment === 'polymeter') {
// polymeter // polymeter
const stepsPerCycle = ast.arguments_.stepsPerCycle const stepsPerCycle = ast.arguments_.stepsPerCycle
@@ -108,9 +104,15 @@ export function patternifyAST(ast, code, onEnter, offset = 0) {
return strudel.chooseInWith(strudel.rand.early(randOffset * ast.arguments_.seed).segment(1), children); return strudel.chooseInWith(strudel.rand.early(randOffset * ast.arguments_.seed).segment(1), children);
} }
const weightedChildren = ast.source_.some((child) => !!child.options_?.weight); const weightedChildren = ast.source_.some((child) => !!child.options_?.weight);
if (!weightedChildren && alignment === 'slowcat') {
return strudel.slowcat(...children);
}
if (weightedChildren) { if (weightedChildren) {
const weightSum = ast.source_.reduce((sum, child) => sum + (child.options_?.weight || 1), 0); const weightSum = ast.source_.reduce((sum, child) => sum + (child.options_?.weight || 1), 0);
const pat = strudel.timeCat(...ast.source_.map((child, i) => [child.options_?.weight || 1, children[i]])); const pat = strudel.timeCat(...ast.source_.map((child, i) => [child.options_?.weight || 1, children[i]]));
if (alignment === 'slowcat') {
return pat._slow(weightSum); // timecat + slow
}
pat.__weight = weightSum; pat.__weight = weightSum;
return pat; return pat;
} }
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel/mini", "name": "@strudel.cycles/mini",
"version": "0.11.0", "version": "0.9.0",
"description": "Mini notation for strudel", "description": "Mini notation for strudel",
"main": "index.mjs", "main": "index.mjs",
"type": "module", "type": "module",
@@ -32,7 +32,7 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel/core": "workspace:*" "@strudel.cycles/core": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"peggy": "^3.0.2", "peggy": "^3.0.2",
+1 -1
View File
@@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
*/ */
import { getLeafLocation, getLeafLocations, mini, mini2ast } from '../mini.mjs'; import { getLeafLocation, getLeafLocations, mini, mini2ast } from '../mini.mjs';
import '@strudel/core/euclid.mjs'; import '@strudel.cycles/core/euclid.mjs';
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
describe('mini', () => { describe('mini', () => {
+1 -1
View File
@@ -1,4 +1,4 @@
# @strudel/osc # @strudel.cycles/osc
OSC output for strudel patterns! Currently only tested with super collider / super dirt. OSC output for strudel patterns! Currently only tested with super collider / super dirt.
+1 -1
View File
@@ -6,7 +6,7 @@ This program is free software: you can redistribute it and/or modify it under th
import OSC from 'osc-js'; import OSC from 'osc-js';
import { logger, parseNumeral, Pattern } from '@strudel/core'; import { logger, parseNumeral, Pattern } from '@strudel.cycles/core';
let connection; // Promise<OSC> let connection; // Promise<OSC>
function connect() { function connect() {
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel/osc", "name": "@strudel.cycles/osc",
"version": "0.11.0", "version": "0.9.0",
"description": "OSC messaging for strudel", "description": "OSC messaging for strudel",
"main": "osc.mjs", "main": "osc.mjs",
"publishConfig": { "publishConfig": {
@@ -36,7 +36,7 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel/core": "workspace:*", "@strudel.cycles/core": "workspace:*",
"osc-js": "^2.4.0" "osc-js": "^2.4.0"
}, },
"devDependencies": { "devDependencies": {
+9 -9
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel/repl", "name": "@strudel/repl",
"version": "0.11.0", "version": "0.9.4",
"description": "Strudel REPL as a Web Component", "description": "Strudel REPL as a Web Component",
"main": "index.mjs", "main": "index.mjs",
"publishConfig": { "publishConfig": {
@@ -33,15 +33,15 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/midi": "workspace:*",
"@strudel.cycles/mini": "workspace:*",
"@strudel.cycles/soundfonts": "workspace:*",
"@strudel.cycles/tonal": "workspace:*",
"@strudel.cycles/transpiler": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*",
"@strudel/codemirror": "workspace:*", "@strudel/codemirror": "workspace:*",
"@strudel/core": "workspace:*", "@strudel/hydra": "workspace:*"
"@strudel/hydra": "workspace:*",
"@strudel/midi": "workspace:*",
"@strudel/mini": "workspace:*",
"@strudel/soundfonts": "workspace:*",
"@strudel/tonal": "workspace:*",
"@strudel/transpiler": "workspace:*",
"@strudel/webaudio": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-replace": "^5.0.5", "@rollup/plugin-replace": "^5.0.5",
+16 -16
View File
@@ -1,22 +1,22 @@
import { controls, noteToMidi, valueToMidi, Pattern, evalScope } from '@strudel/core'; import { controls, noteToMidi, valueToMidi, Pattern, evalScope } from '@strudel.cycles/core';
import { registerSynthSounds, registerZZFXSounds, samples } from '@strudel/webaudio'; import { registerSynthSounds, registerZZFXSounds, samples } from '@strudel.cycles/webaudio';
import * as core from '@strudel/core'; import * as core from '@strudel.cycles/core';
export async function prebake() { export async function prebake() {
const modulesLoading = evalScope( const modulesLoading = evalScope(
// import('@strudel/core'), // import('@strudel.cycles/core'),
core, core,
import('@strudel/mini'), import('@strudel.cycles/mini'),
import('@strudel/tonal'), import('@strudel.cycles/tonal'),
import('@strudel/webaudio'), import('@strudel.cycles/webaudio'),
import('@strudel/codemirror'), import('@strudel/codemirror'),
import('@strudel/hydra'), import('@strudel/hydra'),
import('@strudel/soundfonts'), import('@strudel.cycles/soundfonts'),
import('@strudel/midi'), import('@strudel.cycles/midi'),
// import('@strudel/xen'), // import('@strudel.cycles/xen'),
// import('@strudel/serial'), // import('@strudel.cycles/serial'),
// import('@strudel/csound'), // import('@strudel.cycles/csound'),
// import('@strudel/osc'), // import('@strudel.cycles/osc'),
controls, // sadly, this cannot be exported from core directly (yet) controls, // sadly, this cannot be exported from core directly (yet)
); );
// load samples // load samples
@@ -26,10 +26,10 @@ export async function prebake() {
registerSynthSounds(), registerSynthSounds(),
registerZZFXSounds(), registerZZFXSounds(),
//registerSoundfonts(), //registerSoundfonts(),
// need dynamic import here, because importing @strudel/soundfonts fails on server: // need dynamic import here, because importing @strudel.cycles/soundfonts fails on server:
// => getting "window is not defined", as soon as "@strudel/soundfonts" is imported statically // => getting "window is not defined", as soon as "@strudel.cycles/soundfonts" is imported statically
// seems to be a problem with soundfont2 // seems to be a problem with soundfont2
import('@strudel/soundfonts').then(({ registerSoundfonts }) => registerSoundfonts()), import('@strudel.cycles/soundfonts').then(({ registerSoundfonts }) => registerSoundfonts()),
samples(`${ds}/tidal-drum-machines.json`), samples(`${ds}/tidal-drum-machines.json`),
samples(`${ds}/piano.json`), samples(`${ds}/piano.json`),
samples(`${ds}/Dirt-Samples.json`), samples(`${ds}/Dirt-Samples.json`),
+3 -3
View File
@@ -1,6 +1,6 @@
import { getDrawContext, silence } from '@strudel/core'; import { getDrawContext, silence } from '@strudel.cycles/core';
import { transpiler } from '@strudel/transpiler'; import { transpiler } from '@strudel.cycles/transpiler';
import { getAudioContext, webaudioOutput } from '@strudel/webaudio'; import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio';
import { StrudelMirror, codemirrorSettings } from '@strudel/codemirror'; import { StrudelMirror, codemirrorSettings } from '@strudel/codemirror';
import { prebake } from './prebake.mjs'; import { prebake } from './prebake.mjs';
+1 -1
View File
@@ -1,3 +1,3 @@
# @strudel/serial # @strudel.cycles/serial
This package adds webserial functionality to strudel Patterns, for e.g. sending messages to arduino microcontrollers. This package adds webserial functionality to strudel Patterns, for e.g. sending messages to arduino microcontrollers.
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel/serial", "name": "@strudel.cycles/serial",
"version": "0.11.0", "version": "0.9.0",
"description": "Webserial API for strudel", "description": "Webserial API for strudel",
"main": "serial.mjs", "main": "serial.mjs",
"publishConfig": { "publishConfig": {
@@ -29,7 +29,7 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel/core": "workspace:*" "@strudel.cycles/core": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"vite": "^5.0.10" "vite": "^5.0.10"
+1 -1
View File
@@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Pattern, isPattern } from '@strudel/core'; import { Pattern, isPattern } from '@strudel.cycles/core';
var writeMessagers = {}; var writeMessagers = {};
var choosing = false; var choosing = false;
-1
View File
@@ -1 +0,0 @@
# @strudel/soundfonts
+11 -33
View File
@@ -1,12 +1,5 @@
import { noteToMidi, freqToMidi, getSoundIndex } from '@strudel/core'; import { noteToMidi, freqToMidi, getSoundIndex } from '@strudel.cycles/core';
import { import { getAudioContext, registerSound, getEnvelope } from '@strudel.cycles/webaudio';
getAudioContext,
registerSound,
getParamADSR,
getADSRValues,
getPitchEnvelope,
getVibratoOscillator,
} from '@strudel/webaudio';
import gm from './gm.mjs'; import gm from './gm.mjs';
let loadCache = {}; let loadCache = {};
@@ -137,39 +130,24 @@ export function registerSoundfonts() {
registerSound( registerSound(
name, name,
async (time, value, onended) => { async (time, value, onended) => {
const [attack, decay, sustain, release] = getADSRValues([
value.attack,
value.decay,
value.sustain,
value.release,
]);
const { duration } = value;
const n = getSoundIndex(value.n, fonts.length); const n = getSoundIndex(value.n, fonts.length);
const { attack = 0.001, decay = 0.001, sustain = 1, release = 0.001 } = value;
const font = fonts[n]; const font = fonts[n];
const ctx = getAudioContext(); const ctx = getAudioContext();
const bufferSource = await getFontBufferSource(font, value, ctx); const bufferSource = await getFontBufferSource(font, value, ctx);
bufferSource.start(time); bufferSource.start(time);
const envGain = ctx.createGain(); const { node: envelope, stop: releaseEnvelope } = getEnvelope(attack, decay, sustain, release, 0.3, time);
const node = bufferSource.connect(envGain); bufferSource.connect(envelope);
const holdEnd = time + duration; const stop = (releaseTime) => {
getParamADSR(node.gain, attack, decay, sustain, release, 0, 0.3, time, holdEnd, 'linear'); const silentAt = releaseEnvelope(releaseTime);
let envEnd = holdEnd + release + 0.01; bufferSource.stop(silentAt);
};
// vibrato
let vibratoOscillator = getVibratoOscillator(bufferSource.detune, value, time);
// pitch envelope
getPitchEnvelope(bufferSource.detune, value, time, holdEnd);
bufferSource.stop(envEnd);
const stop = (releaseTime) => {};
bufferSource.onended = () => { bufferSource.onended = () => {
bufferSource.disconnect(); bufferSource.disconnect();
vibratoOscillator?.stop(); envelope.disconnect();
node.disconnect();
onended(); onended();
}; };
return { node, stop }; return { node: envelope, stop };
}, },
{ type: 'soundfont', prebake: true, fonts }, { type: 'soundfont', prebake: true, fonts },
); );
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel/soundfonts", "name": "@strudel.cycles/soundfonts",
"version": "0.11.0", "version": "0.9.0",
"description": "Soundsfont support for strudel", "description": "Soundsfont support for strudel",
"main": "index.mjs", "main": "index.mjs",
"publishConfig": { "publishConfig": {
@@ -30,8 +30,8 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel/core": "workspace:*", "@strudel.cycles/core": "workspace:*",
"@strudel/webaudio": "workspace:*", "@strudel.cycles/webaudio": "workspace:*",
"sfumato": "^0.1.2", "sfumato": "^0.1.2",
"soundfont2": "^0.4.0" "soundfont2": "^0.4.0"
}, },
+2 -2
View File
@@ -1,5 +1,5 @@
import { Pattern, getPlayableNoteValue, noteToMidi } from '@strudel/core'; import { Pattern, getPlayableNoteValue, noteToMidi } from '@strudel.cycles/core';
import { getAudioContext, registerSound } from '@strudel/webaudio'; import { getAudioContext, registerSound } from '@strudel.cycles/webaudio';
import { loadSoundfont as _loadSoundfont, startPresetNote } from 'sfumato'; import { loadSoundfont as _loadSoundfont, startPresetNote } from 'sfumato';
Pattern.prototype.soundfont = function (sf, n = 0) { Pattern.prototype.soundfont = function (sf, n = 0) {
+94 -132
View File
@@ -1,5 +1,5 @@
import { getAudioContext } from './superdough.mjs'; import { getAudioContext } from './superdough.mjs';
import { clamp, nanFallback } from './util.mjs'; import { clamp } from './util.mjs';
export function gainNode(value) { export function gainNode(value) {
const node = getAudioContext().createGain(); const node = getAudioContext().createGain();
@@ -7,73 +7,78 @@ export function gainNode(value) {
return node; return node;
} }
const getSlope = (y1, y2, x1, x2) => { // alternative to getADSR returning the gain node and a stop handle to trigger the release anytime in the future
const denom = x2 - x1; export const getEnvelope = (attack, decay, sustain, release, velocity, begin) => {
if (denom === 0) { const gainNode = getAudioContext().createGain();
return 0; let phase = begin;
} gainNode.gain.setValueAtTime(0, begin);
return (y2 - y1) / (x2 - x1); 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 getParamADSR = ( };
param,
attack, export const getExpEnvelope = (attack, decay, sustain, release, velocity, begin) => {
decay, sustain = Math.max(0.001, sustain);
sustain, velocity = Math.max(0.001, velocity);
release, const gainNode = getAudioContext().createGain();
min, gainNode.gain.setValueAtTime(0.0001, begin);
max, gainNode.gain.exponentialRampToValueAtTime(velocity, begin + attack);
begin, gainNode.gain.exponentialRampToValueAtTime(sustain * velocity, begin + attack + decay);
end, return {
//exponential works better for frequency modulations (such as filter cutoff) due to human ear perception node: gainNode,
curve = 'exponential', stop: (t) => {
) => { // similar to getEnvelope, this will glitch if sustain level has not been reached
attack = nanFallback(attack); gainNode.gain.exponentialRampToValueAtTime(0.0001, t + release);
decay = nanFallback(decay); },
sustain = nanFallback(sustain); };
release = nanFallback(release); };
const ramp = curve === 'exponential' ? 'exponentialRampToValueAtTime' : 'linearRampToValueAtTime';
if (curve === 'exponential') { export const getADSR = (attack, decay, sustain, release, velocity, begin, end) => {
min = min === 0 ? 0.001 : min; const gainNode = getAudioContext().createGain();
max = max === 0 ? 0.001 : max; gainNode.gain.setValueAtTime(0, begin);
gainNode.gain.linearRampToValueAtTime(velocity, begin + attack); // attack
gainNode.gain.linearRampToValueAtTime(sustain * velocity, begin + attack + decay); // sustain start
gainNode.gain.setValueAtTime(sustain * velocity, end); // sustain end
gainNode.gain.linearRampToValueAtTime(0, end + release); // release
// for some reason, using exponential ramping creates little cracklings
/* let t = begin;
gainNode.gain.setValueAtTime(0, t);
gainNode.gain.exponentialRampToValueAtTime(velocity, (t += attack));
const sustainGain = Math.max(sustain * velocity, 0.001);
gainNode.gain.exponentialRampToValueAtTime(sustainGain, (t += decay));
if (end - begin < attack + decay) {
gainNode.gain.cancelAndHoldAtTime(end);
} else {
gainNode.gain.setValueAtTime(sustainGain, end);
} }
gainNode.gain.exponentialRampToValueAtTime(0.001, end + release); // release */
return gainNode;
};
export const getParamADSR = (param, attack, decay, sustain, release, min, max, begin, end) => {
const range = max - min; const range = max - min;
const peak = max; const peak = min + range;
const sustainVal = min + sustain * range; const sustainLevel = min + sustain * range;
const duration = end - begin;
const envValAtTime = (time) => {
let val;
if (attack > time) {
let slope = getSlope(min, peak, 0, attack);
val = time * slope + (min > peak ? min : 0);
} else {
val = (time - attack) * getSlope(peak, sustainVal, 0, decay) + peak;
}
if (curve === 'exponential') {
val = val || 0.001;
}
return val;
};
param.setValueAtTime(min, begin); param.setValueAtTime(min, begin);
if (attack > duration) { param.linearRampToValueAtTime(peak, begin + attack);
//attack param.linearRampToValueAtTime(sustainLevel, begin + attack + decay);
param[ramp](envValAtTime(duration), end); param.setValueAtTime(sustainLevel, end);
} else if (attack + decay > duration) { param.linearRampToValueAtTime(min, end + Math.max(release, 0.1));
//attack
param[ramp](envValAtTime(attack), begin + attack);
//decay
param[ramp](envValAtTime(duration), end);
} else {
//attack
param[ramp](envValAtTime(attack), begin + attack);
//decay
param[ramp](envValAtTime(attack + decay), begin + attack + decay);
//sustain
param.setValueAtTime(sustainVal, end);
}
//release
param[ramp](min, end + release);
}; };
export function getCompressor(ac, threshold, ratio, knee, attack, release) { export function getCompressor(ac, threshold, ratio, knee, attack, release) {
@@ -87,44 +92,38 @@ export function getCompressor(ac, threshold, ratio, knee, attack, release) {
return new DynamicsCompressorNode(ac, options); return new DynamicsCompressorNode(ac, options);
} }
// changes the default values of the envelope based on what parameters the user has defined export function createFilter(
// so it behaves more like you would expect/familiar as other synthesis tools context,
// 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. type,
frequency,
export const getADSRValues = (params, curve = 'linear', defaultValues) => { Q,
const envmin = curve === 'exponential' ? 0.001 : 0.001; attack,
const releaseMin = 0.01; decay,
const envmax = 1; sustain,
const [a, d, s, r] = params; release,
if (a == null && d == null && s == null && r == null) { fenv,
return defaultValues ?? [envmin, envmin, envmax, releaseMin]; start,
} end,
const sustain = s != null ? s : (a != null && d == null) || (a == null && d == null) ? envmax : envmin; fanchor = 0.5,
return [Math.max(a ?? 0, envmin), Math.max(d ?? 0, envmin), Math.min(sustain, envmax), Math.max(r ?? 0, releaseMin)]; ) {
};
export function createFilter(context, type, frequency, Q, att, dec, sus, rel, fenv, start, end, fanchor) {
const curve = 'exponential';
const [attack, decay, sustain, release] = getADSRValues([att, dec, sus, rel], curve, [0.005, 0.14, 0, 0.1]);
const filter = context.createBiquadFilter(); const filter = context.createBiquadFilter();
filter.type = type; filter.type = type;
filter.Q.value = Q; filter.Q.value = Q;
filter.frequency.value = frequency; filter.frequency.value = frequency;
// envelope is active when any of these values is set
const hasEnvelope = att ?? dec ?? sus ?? rel ?? fenv;
// Apply ADSR to filter frequency // Apply ADSR to filter frequency
if (hasEnvelope !== undefined) { if (!isNaN(fenv) && fenv !== 0) {
fenv = nanFallback(fenv, 1, true); const offset = fenv * fanchor;
fanchor = nanFallback(fanchor, 0, true);
const fenvAbs = Math.abs(fenv); const min = clamp(2 ** -offset * frequency, 0, 20000);
const offset = fenvAbs * fanchor; const max = clamp(2 ** (fenv - offset) * frequency, 0, 20000);
let min = clamp(2 ** -offset * frequency, 0, 20000);
let max = clamp(2 ** (fenvAbs - offset) * frequency, 0, 20000); // console.log('min', min, 'max', max);
if (fenv < 0) [min, max] = [max, min];
getParamADSR(filter.frequency, attack, decay, sustain, release, min, max, start, end, curve); getParamADSR(filter.frequency, attack, decay, sustain, release, min, max, start, end);
return filter; return filter;
} }
return filter; return filter;
} }
@@ -149,40 +148,3 @@ export function drywet(dry, wet, wetAmount = 0) {
wet_gain.connect(mix); wet_gain.connect(mix);
return mix; return mix;
} }
let curves = ['linear', 'exponential'];
export function getPitchEnvelope(param, value, t, holdEnd) {
// envelope is active when any of these values is set
const hasEnvelope = value.pattack ?? value.pdecay ?? value.psustain ?? value.prelease ?? value.penv;
if (!hasEnvelope) {
return;
}
const penv = nanFallback(value.penv, 1, true);
const curve = curves[value.pcurve ?? 0];
let [pattack, pdecay, psustain, prelease] = getADSRValues(
[value.pattack, value.pdecay, value.psustain, value.prelease],
curve,
[0.2, 0.001, 1, 0.001],
);
let panchor = value.panchor ?? psustain;
const cents = penv * 100; // penv is in semitones
const min = 0 - cents * panchor;
const max = cents - cents * panchor;
getParamADSR(param, pattack, pdecay, psustain, prelease, min, max, t, holdEnd, curve);
}
export function getVibratoOscillator(param, value, t) {
const { vibmod = 0.5, vib } = value;
let vibratoOscillator;
if (vib > 0) {
vibratoOscillator = getAudioContext().createOscillator();
vibratoOscillator.frequency.value = vib;
const gain = getAudioContext().createGain();
// Vibmod is the amount of vibrato, in semitones
gain.gain.value = vibmod * 100;
vibratoOscillator.connect(gain);
gain.connect(param);
vibratoOscillator.start(t);
return vibratoOscillator;
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "superdough", "name": "superdough",
"version": "0.10.0", "version": "0.9.12",
"description": "simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.", "description": "simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.",
"main": "index.mjs", "main": "index.mjs",
"type": "module", "type": "module",
+28 -23
View File
@@ -1,6 +1,6 @@
import { noteToMidi, valueToMidi, getSoundIndex } from './util.mjs'; import { noteToMidi, valueToMidi, getSoundIndex } from './util.mjs';
import { getAudioContext, registerSound } from './index.mjs'; import { getAudioContext, registerSound } from './index.mjs';
import { getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs'; import { getEnvelope } from './helpers.mjs';
import { logger } from './logger.mjs'; import { logger } from './logger.mjs';
const bufferCache = {}; // string: Promise<ArrayBuffer> const bufferCache = {}; // string: Promise<ArrayBuffer>
@@ -243,7 +243,8 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
begin = 0, begin = 0,
loopEnd = 1, loopEnd = 1,
end = 1, end = 1,
duration, vib,
vibmod = 0.5,
} = value; } = value;
// load sample // load sample
if (speed === 0) { if (speed === 0) {
@@ -253,15 +254,24 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
loop = s.startsWith('wt_') ? 1 : value.loop; loop = s.startsWith('wt_') ? 1 : value.loop;
const ac = getAudioContext(); const ac = getAudioContext();
// destructure adsr here, because the default should be different for synths and samples // destructure adsr here, because the default should be different for synths and samples
const { attack = 0.001, decay = 0.001, sustain = 1, release = 0.001 } = value;
let [attack, decay, sustain, release] = getADSRValues([value.attack, value.decay, value.sustain, value.release]);
//const soundfont = getSoundfontKey(s); //const soundfont = getSoundfontKey(s);
const time = t + nudge; const time = t + nudge;
const bufferSource = await getSampleBufferSource(s, n, note, speed, freq, bank, resolveUrl); const bufferSource = await getSampleBufferSource(s, n, note, speed, freq, bank, resolveUrl);
// vibrato // vibrato
let vibratoOscillator = getVibratoOscillator(bufferSource.detune, value, t); 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);
}
// asny stuff above took too long? // asny stuff above took too long?
if (ac.currentTime > t) { if (ac.currentTime > t) {
@@ -288,31 +298,26 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
bufferSource.loopEnd = loopEnd * bufferSource.buffer.duration - offset; bufferSource.loopEnd = loopEnd * bufferSource.buffer.duration - offset;
} }
bufferSource.start(time, offset); bufferSource.start(time, offset);
const envGain = ac.createGain(); const { node: envelope, stop: releaseEnvelope } = getEnvelope(attack, decay, sustain, release, 1, t);
const node = bufferSource.connect(envGain); bufferSource.connect(envelope);
if (clip == null && loop == null && value.release == null) {
const bufferDuration = bufferSource.buffer.duration / bufferSource.playbackRate.value;
duration = (end - begin) * bufferDuration;
}
let holdEnd = t + duration;
getParamADSR(node.gain, attack, decay, sustain, release, 0, 1, t, holdEnd, 'linear');
// pitch envelope
getPitchEnvelope(bufferSource.detune, value, t, holdEnd);
const out = ac.createGain(); // we need a separate gain for the cutgroups because firefox... const out = ac.createGain(); // we need a separate gain for the cutgroups because firefox...
node.connect(out); envelope.connect(out);
bufferSource.onended = function () { bufferSource.onended = function () {
bufferSource.disconnect(); bufferSource.disconnect();
vibratoOscillator?.stop(); vibratoOscillator?.stop();
node.disconnect(); envelope.disconnect();
out.disconnect(); out.disconnect();
onended(); onended();
}; };
let envEnd = holdEnd + release + 0.01; const stop = (endTime, playWholeBuffer = clip === undefined && loop === undefined) => {
bufferSource.stop(envEnd); let releaseTime = endTime;
const stop = (endTime, playWholeBuffer) => {}; if (playWholeBuffer) {
const bufferDuration = bufferSource.buffer.duration / bufferSource.playbackRate.value;
releaseTime = t + (end - begin) * bufferDuration;
}
const silentAt = releaseEnvelope(releaseTime);
bufferSource.stop(silentAt);
};
const handle = { node: out, bufferSource, stop }; const handle = { node: out, bufferSource, stop };
// cut groups // cut groups
+12 -13
View File
@@ -280,26 +280,26 @@ export const superdough = async (value, deadline, hapDuration) => {
// low pass // low pass
cutoff, cutoff,
lpenv, lpenv,
lpattack, lpattack = 0.01,
lpdecay, lpdecay = 0.01,
lpsustain, lpsustain = 1,
lprelease, lprelease = 0.01,
resonance = 1, resonance = 1,
// high pass // high pass
hpenv, hpenv,
hcutoff, hcutoff,
hpattack, hpattack = 0.01,
hpdecay, hpdecay = 0.01,
hpsustain, hpsustain = 1,
hprelease, hprelease = 0.01,
hresonance = 1, hresonance = 1,
// band pass // band pass
bpenv, bpenv,
bandf, bandf,
bpattack, bpattack = 0.01,
bpdecay, bpdecay = 0.01,
bpsustain, bpsustain = 1,
bprelease, bprelease = 0.01,
bandq = 1, bandq = 1,
channels = [1, 2], channels = [1, 2],
//phaser //phaser
@@ -333,7 +333,6 @@ export const superdough = async (value, deadline, hapDuration) => {
compressorAttack, compressorAttack,
compressorRelease, compressorRelease,
} = value; } = value;
gain = nanFallback(gain, 1); gain = nanFallback(gain, 1);
//music programs/audio gear usually increments inputs/outputs from 1, so imitate that behavior //music programs/audio gear usually increments inputs/outputs from 1, so imitate that behavior
+46 -45
View File
@@ -1,6 +1,6 @@
import { midiToFreq, noteToMidi } from './util.mjs'; import { midiToFreq, noteToMidi } from './util.mjs';
import { registerSound, getAudioContext } from './superdough.mjs'; import { registerSound, getAudioContext } from './superdough.mjs';
import { gainNode, getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs'; import { gainNode, getEnvelope, getExpEnvelope } from './helpers.mjs';
import { getNoiseMix, getNoiseOscillator } from './noise.mjs'; import { getNoiseMix, getNoiseOscillator } from './noise.mjs';
const mod = (freq, range = 1, type = 'sine') => { const mod = (freq, range = 1, type = 'sine') => {
@@ -29,11 +29,8 @@ export function registerSynthSounds() {
registerSound( registerSound(
s, s,
(t, value, onended) => { (t, value, onended) => {
const [attack, decay, sustain, release] = getADSRValues( // destructure adsr here, because the default should be different for synths and samples
[value.attack, value.decay, value.sustain, value.release], let { attack = 0.001, decay = 0.05, sustain = 0.6, release = 0.01 } = value;
'linear',
[0.001, 0.05, 0.6, 0.01],
);
let sound; let sound;
if (waveforms.includes(s)) { if (waveforms.includes(s)) {
@@ -48,24 +45,21 @@ export function registerSynthSounds() {
// turn down // turn down
const g = gainNode(0.3); const g = gainNode(0.3);
const { duration } = value; // gain envelope
const { node: envelope, stop: releaseEnvelope } = getEnvelope(attack, decay, sustain, release, 1, t);
o.onended = () => { o.onended = () => {
o.disconnect(); o.disconnect();
g.disconnect(); g.disconnect();
onended(); onended();
}; };
const envGain = gainNode(1);
let node = o.connect(g).connect(envGain);
const holdEnd = t + duration;
getParamADSR(node.gain, attack, decay, sustain, release, 0, 1, t, holdEnd, 'linear');
const envEnd = holdEnd + release + 0.01;
triggerRelease?.(envEnd);
stop(envEnd);
return { return {
node, node: o.connect(g).connect(envelope),
stop: (releaseTime) => {}, stop: (releaseTime) => {
const silentAt = releaseEnvelope(releaseTime);
triggerRelease?.(releaseTime);
stop(silentAt);
},
}; };
}, },
{ type: 'synth', prebake: true }, { type: 'synth', prebake: true },
@@ -105,24 +99,28 @@ export function waveformN(partials, type) {
} }
// expects one of waveforms as s // expects one of waveforms as s
export function getOscillator(s, t, value) { export function getOscillator(
let { s,
t,
{
n: partials, n: partials,
note, note,
freq, freq,
vib = 0,
vibmod = 0.5,
noise = 0, noise = 0,
// fm // fm
fmh: fmHarmonicity = 1, fmh: fmHarmonicity = 1,
fmi: fmModulationIndex, fmi: fmModulationIndex,
fmenv: fmEnvelopeType = 'exp', fmenv: fmEnvelopeType = 'lin',
fmattack: fmAttack, fmattack: fmAttack,
fmdecay: fmDecay, fmdecay: fmDecay,
fmsustain: fmSustain, fmsustain: fmSustain,
fmrelease: fmRelease, fmrelease: fmRelease,
fmvelocity: fmVelocity, fmvelocity: fmVelocity,
fmwave: fmWaveform = 'sine', fmwave: fmWaveform = 'sine',
duration, },
} = value; ) {
let ac = getAudioContext(); let ac = getAudioContext();
let o; let o;
// If no partials are given, use stock waveforms // If no partials are given, use stock waveforms
@@ -150,39 +148,42 @@ export function getOscillator(s, t, value) {
o.start(t); o.start(t);
// FM // FM
let stopFm; let stopFm, fmEnvelope;
let envGain = ac.createGain();
if (fmModulationIndex) { if (fmModulationIndex) {
const { node: modulator, stop } = fm(o, fmHarmonicity, fmModulationIndex, fmWaveform); const { node: modulator, stop } = fm(o, fmHarmonicity, fmModulationIndex, fmWaveform);
if (![fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity].find((v) => v !== undefined)) { if (![fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity].find((v) => v !== undefined)) {
// no envelope by default // no envelope by default
modulator.connect(o.frequency); modulator.connect(o.frequency);
} else { } else {
const [attack, decay, sustain, release] = getADSRValues([fmAttack, fmDecay, fmSustain, fmRelease]); fmAttack = fmAttack ?? 0.001;
const holdEnd = t + duration; fmDecay = fmDecay ?? 0.001;
getParamADSR( fmSustain = fmSustain ?? 1;
envGain.gain, fmRelease = fmRelease ?? 0.001;
attack, fmVelocity = fmVelocity ?? 1;
decay, fmEnvelope = getEnvelope(fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity, t);
sustain, if (fmEnvelopeType === 'exp') {
release, fmEnvelope = getExpEnvelope(fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity, t);
0, fmEnvelope.node.maxValue = fmModulationIndex * 2;
1, fmEnvelope.node.minValue = 0.00001;
t, }
holdEnd, modulator.connect(fmEnvelope.node);
fmEnvelopeType === 'exp' ? 'exponential' : 'linear', fmEnvelope.node.connect(o.frequency);
);
modulator.connect(envGain);
envGain.connect(o.frequency);
} }
stopFm = stop; stopFm = stop;
} }
// Additional oscillator for vibrato effect // Additional oscillator for vibrato effect
let vibratoOscillator = getVibratoOscillator(o.detune, value, t); let vibratoOscillator;
if (vib > 0) {
// pitch envelope vibratoOscillator = getAudioContext().createOscillator();
getPitchEnvelope(o.detune, value, t, t + duration); 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 noiseMix; let noiseMix;
if (noise) { if (noise) {
@@ -198,7 +199,7 @@ export function getOscillator(s, t, value) {
o.stop(time); o.stop(time);
}, },
triggerRelease: (time) => { triggerRelease: (time) => {
// envGain?.stop(time); fmEnvelope?.stop(time);
}, },
}; };
} }
+2 -2
View File
@@ -54,9 +54,9 @@ export const valueToMidi = (value, fallbackValue) => {
return fallbackValue; return fallbackValue;
}; };
export function nanFallback(value, fallback = 0, silent) { export function nanFallback(value, fallback) {
if (isNaN(Number(value))) { if (isNaN(Number(value))) {
!silent && logger(`"${value}" is not a number, falling back to ${fallback}`, 'warning'); logger(`"${value}" is not a number, falling back to ${fallback}`, 'warning');
return fallback; return fallback;
} }
return value; return value;
-31
View File
@@ -5,37 +5,6 @@ export var vowelFormant = {
i: { freqs: [270, 1850, 2900, 3350, 3590], gains: [1, 0.0631, 0.0631, 0.0158, 0.0158], qs: [40, 90, 100, 120, 120] }, i: { freqs: [270, 1850, 2900, 3350, 3590], gains: [1, 0.0631, 0.0631, 0.0158, 0.0158], qs: [40, 90, 100, 120, 120] },
o: { freqs: [430, 820, 2700, 3000, 3300], gains: [1, 0.3162, 0.0501, 0.0794, 0.01995], qs: [40, 80, 100, 120, 120] }, o: { freqs: [430, 820, 2700, 3000, 3300], gains: [1, 0.3162, 0.0501, 0.0794, 0.01995], qs: [40, 80, 100, 120, 120] },
u: { freqs: [370, 630, 2750, 3000, 3400], gains: [1, 0.1, 0.0708, 0.0316, 0.01995], qs: [40, 60, 100, 120, 120] }, u: { freqs: [370, 630, 2750, 3000, 3400], gains: [1, 0.1, 0.0708, 0.0316, 0.01995], qs: [40, 60, 100, 120, 120] },
ae: { freqs: [650, 1515, 2400, 3000, 3350], gains: [1, 0.5, 0.1008, 0.0631, 0.0126], qs: [80, 90, 120, 130, 140] },
aa: { freqs: [560, 900, 2570, 3000, 3300], gains: [1, 0.5, 0.0708, 0.0631, 0.0126], qs: [80, 90, 120, 130, 140] },
oe: { freqs: [500, 1430, 2300, 3000, 3300], gains: [1, 0.2, 0.0708, 0.0316, 0.01995], qs: [40, 60, 100, 120, 120] },
ue: { freqs: [250, 1750, 2150, 3200, 3300], gains: [1, 0.1, 0.0708, 0.0316, 0.01995], qs: [40, 60, 100, 120, 120] },
y: { freqs: [400, 1460, 2400, 3000, 3300], gains: [1, 0.2, 0.0708, 0.0316, 0.02995], qs: [40, 60, 100, 120, 120] },
uh: { freqs: [600, 1250, 2100, 3100, 3500], gains: [1, 0.3, 0.0608, 0.0316, 0.01995], qs: [40, 70, 100, 120, 130] },
un: { freqs: [500, 1240, 2280, 3000, 3500], gains: [1, 0.1, 0.1708, 0.0216, 0.02995], qs: [40, 60, 100, 120, 120] },
en: { freqs: [600, 1480, 2450, 3200, 3300], gains: [1, 0.15, 0.0708, 0.0316, 0.02995], qs: [40, 60, 100, 120, 120] },
an: { freqs: [700, 1050, 2500, 3000, 3300], gains: [1, 0.1, 0.0708, 0.0316, 0.02995], qs: [40, 60, 100, 120, 120] },
on: { freqs: [500, 1080, 2350, 3000, 3300], gains: [1, 0.1, 0.0708, 0.0316, 0.02995], qs: [40, 60, 100, 120, 120] },
get æ() {
return this.ae;
},
get ø() {
return this.oe;
},
get ɑ() {
return this.aa;
},
get å() {
return this.aa;
},
get ö() {
return this.oe;
},
get ü() {
return this.ue;
},
get ı() {
return this.y;
},
}; };
if (typeof GainNode !== 'undefined') { if (typeof GainNode !== 'undefined') {
class VowelNode extends GainNode { class VowelNode extends GainNode {
+5 -5
View File
@@ -1,18 +1,18 @@
# @strudel/tonal # @strudel.cycles/tonal
This package adds tonal / harmonic functions to strudel Patterns. This package adds tonal / harmonic functions to strudel Patterns.
## Install ## Install
```sh ```sh
npm i @strudel/tonal --save npm i @strudel.cycles/tonal --save
``` ```
## Example ## Example
```js ```js
import { sequence } from '@strudel/core'; import { sequence } from '@strudel.cycles/core';
import '@strudel/tonal'; import '@strudel.cycles/tonal';
const pattern = sequence(0, [1, 2]).scale('C major'); const pattern = sequence(0, [1, 2]).scale('C major');
@@ -27,7 +27,7 @@ yields:
(3/4 -> 1/1, 3/4 -> 1/1, E3) (3/4 -> 1/1, 3/4 -> 1/1, E3)
``` ```
[play with @strudel/tonal codesandbox](https://codesandbox.io/s/strudel-tonal-example-rgc5if?file=/src/index.js) [play with @strudel.cycles/tonal codesandbox](https://codesandbox.io/s/strudel-tonal-example-rgc5if?file=/src/index.js)
## Tonal API ## Tonal API
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel/tonal", "name": "@strudel.cycles/tonal",
"version": "0.11.0", "version": "0.9.0",
"description": "Tonal functions for strudel", "description": "Tonal functions for strudel",
"main": "index.mjs", "main": "index.mjs",
"publishConfig": { "publishConfig": {
@@ -31,7 +31,7 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel/core": "workspace:*", "@strudel.cycles/core": "workspace:*",
"@tonaljs/tonal": "^4.7.2", "@tonaljs/tonal": "^4.7.2",
"chord-voicings": "^0.0.1", "chord-voicings": "^0.0.1",
"webmidi": "^3.1.8" "webmidi": "^3.1.8"
+1 -1
View File
@@ -7,7 +7,7 @@ This program is free software: you can redistribute it and/or modify it under th
// import { strict as assert } from 'assert'; // import { strict as assert } from 'assert';
import '../tonal.mjs'; // need to import this to add prototypes import '../tonal.mjs'; // need to import this to add prototypes
import { pure, controls, seq } from '@strudel/core'; import { pure, controls, seq } from '@strudel.cycles/core';
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from 'vitest';
import { mini } from '../../mini/mini.mjs'; import { mini } from '../../mini/mini.mjs';
const { n } = controls; const { n } = controls;
+1 -1
View File
@@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
*/ */
import { Note, Interval, Scale } from '@tonaljs/tonal'; import { Note, Interval, Scale } from '@tonaljs/tonal';
import { register, _mod, silence, logger, pure, isNote } from '@strudel/core'; import { register, _mod, silence, logger, pure, isNote } from '@strudel.cycles/core';
import { stepInNamedScale } from './tonleiter.mjs'; import { stepInNamedScale } from './tonleiter.mjs';
const octavesInterval = (octaves) => (octaves <= 0 ? -1 : 1) + octaves * 7 + 'P'; const octavesInterval = (octaves) => (octaves <= 0 ? -1 : 1) + octaves * 7 + 'P';
+1 -1
View File
@@ -1,4 +1,4 @@
import { isNote, isNoteWithOctave, _mod, noteToMidi, tokenizeNote } from '@strudel/core'; import { isNote, isNoteWithOctave, _mod, noteToMidi, tokenizeNote } from '@strudel.cycles/core';
import { Interval, Scale } from '@tonaljs/tonal'; import { Interval, Scale } from '@tonaljs/tonal';
// https://codesandbox.io/s/stateless-voicings-g2tmz0?file=/src/lib.js:0-2515 // https://codesandbox.io/s/stateless-voicings-g2tmz0?file=/src/lib.js:0-2515
+1 -1
View File
@@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { stack, register, silence, logger } from '@strudel/core'; import { stack, register, silence, logger } from '@strudel.cycles/core';
import { renderVoicing } from './tonleiter.mjs'; import { renderVoicing } from './tonleiter.mjs';
import _voicings from 'chord-voicings'; import _voicings from 'chord-voicings';
import { complex, simple } from './ireal.mjs'; import { complex, simple } from './ireal.mjs';
+4 -4
View File
@@ -1,4 +1,4 @@
# @strudel/transpiler # @strudel.cycles/transpiler
This package contains a JS code transpiler with the following features: This package contains a JS code transpiler with the following features:
@@ -9,14 +9,14 @@ This package contains a JS code transpiler with the following features:
## Install ## Install
```sh ```sh
npm i @strudel/transpiler npm i @strudel.cycles/transpiler
``` ```
## Use ## Use
```js ```js
import { transpiler } from '@strudel/core'; import { transpiler } from '@strudel.cycles/core';
import { evaluate } from '@strudel/core'; import { evaluate } from '@strudel.cycles/core';
transpiler('note("c3 [e3,g3]")', { wrapAsync: false, addReturn: false, simpleLocs: true }); transpiler('note("c3 [e3,g3]")', { wrapAsync: false, addReturn: false, simpleLocs: true });
/* mini('c3 [e3,g3]').withMiniLocation(7,17) */ /* mini('c3 [e3,g3]').withMiniLocation(7,17) */
+1 -1
View File
@@ -1,4 +1,4 @@
import { evaluate as _evaluate } from '@strudel/core'; import { evaluate as _evaluate } from '@strudel.cycles/core';
import { transpiler } from './transpiler.mjs'; import { transpiler } from './transpiler.mjs';
export * from './transpiler.mjs'; export * from './transpiler.mjs';
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel/transpiler", "name": "@strudel.cycles/transpiler",
"version": "0.11.0", "version": "0.9.0",
"description": "Transpiler for strudel user code. Converts syntactically correct but semantically meaningless JS into evaluatable strudel code.", "description": "Transpiler for strudel user code. Converts syntactically correct but semantically meaningless JS into evaluatable strudel code.",
"main": "index.mjs", "main": "index.mjs",
"publishConfig": { "publishConfig": {
@@ -30,8 +30,8 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel/core": "workspace:*", "@strudel.cycles/core": "workspace:*",
"@strudel/mini": "workspace:*", "@strudel.cycles/mini": "workspace:*",
"acorn": "^8.11.3", "acorn": "^8.11.3",
"escodegen": "^2.1.0", "escodegen": "^2.1.0",
"estree-walker": "^3.0.1" "estree-walker": "^3.0.1"
+2 -2
View File
@@ -1,8 +1,8 @@
import escodegen from 'escodegen'; import escodegen from 'escodegen';
import { parse } from 'acorn'; import { parse } from 'acorn';
import { walk } from 'estree-walker'; import { walk } from 'estree-walker';
import { isNoteWithOctave } from '@strudel/core'; import { isNoteWithOctave } from '@strudel.cycles/core';
import { getLeafLocations } from '@strudel/mini'; import { getLeafLocations } from '@strudel.cycles/mini';
export function transpiler(input, options = {}) { export function transpiler(input, options = {}) {
const { wrapAsync = false, addReturn = true, emitMiniLocations = true, emitWidgets = true } = options; const { wrapAsync = false, addReturn = true, emitMiniLocations = true, emitWidgets = true } = options;
+6 -6
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel/web", "name": "@strudel/web",
"version": "0.11.0", "version": "0.9.0",
"description": "Easy to setup, opiniated bundle of Strudel for the browser.", "description": "Easy to setup, opiniated bundle of Strudel for the browser.",
"main": "web.mjs", "main": "web.mjs",
"publishConfig": { "publishConfig": {
@@ -33,11 +33,11 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel/core": "workspace:*", "@strudel.cycles/core": "workspace:*",
"@strudel/mini": "workspace:*", "@strudel.cycles/mini": "workspace:*",
"@strudel/tonal": "workspace:*", "@strudel.cycles/tonal": "workspace:*",
"@strudel/transpiler": "workspace:*", "@strudel.cycles/transpiler": "workspace:*",
"@strudel/webaudio": "workspace:*" "@strudel.cycles/webaudio": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"vite": "^5.0.10" "vite": "^5.0.10"
+16 -16
View File
@@ -1,25 +1,25 @@
export * from '@strudel/core'; export * from '@strudel.cycles/core';
export * from '@strudel/webaudio'; export * from '@strudel.cycles/webaudio';
//export * from '@strudel/soundfonts'; //export * from '@strudel.cycles/soundfonts';
export * from '@strudel/transpiler'; export * from '@strudel.cycles/transpiler';
export * from '@strudel/mini'; export * from '@strudel.cycles/mini';
export * from '@strudel/tonal'; export * from '@strudel.cycles/tonal';
export * from '@strudel/webaudio'; export * from '@strudel.cycles/webaudio';
import { Pattern, evalScope, controls } from '@strudel/core'; import { Pattern, evalScope, controls } from '@strudel.cycles/core';
import { initAudioOnFirstClick, registerSynthSounds, webaudioScheduler } from '@strudel/webaudio'; import { initAudioOnFirstClick, registerSynthSounds, webaudioScheduler } from '@strudel.cycles/webaudio';
// import { registerSoundfonts } from '@strudel/soundfonts'; // import { registerSoundfonts } from '@strudel.cycles/soundfonts';
import { evaluate as _evaluate } from '@strudel/transpiler'; import { evaluate as _evaluate } from '@strudel.cycles/transpiler';
import { miniAllStrings } from '@strudel/mini'; import { miniAllStrings } from '@strudel.cycles/mini';
// init logic // init logic
export async function defaultPrebake() { export async function defaultPrebake() {
const loadModules = evalScope( const loadModules = evalScope(
evalScope, evalScope,
controls, controls,
import('@strudel/core'), import('@strudel.cycles/core'),
import('@strudel/mini'), import('@strudel.cycles/mini'),
import('@strudel/tonal'), import('@strudel.cycles/tonal'),
import('@strudel/webaudio'), import('@strudel.cycles/webaudio'),
{ hush, evaluate }, { hush, evaluate },
); );
await Promise.all([loadModules, registerSynthSounds() /* , registerSoundfonts() */]); await Promise.all([loadModules, registerSynthSounds() /* , registerSoundfonts() */]);
+4 -4
View File
@@ -1,4 +1,4 @@
# @strudel/webaudio # @strudel.cycles/webaudio
This package contains helpers to make music with strudel and the Web Audio API. This package contains helpers to make music with strudel and the Web Audio API.
It is a thin binding to [superdough](https://www.npmjs.com/package/superdough). It is a thin binding to [superdough](https://www.npmjs.com/package/superdough).
@@ -6,14 +6,14 @@ It is a thin binding to [superdough](https://www.npmjs.com/package/superdough).
## Install ## Install
```sh ```sh
npm i @strudel/webaudio --save npm i @strudel.cycles/webaudio --save
``` ```
## Example ## Example
```js ```js
import { repl, controls } from "@strudel/core"; import { repl, controls } from "@strudel.cycles/core";
import { initAudioOnFirstClick, getAudioContext, webaudioOutput } from "@strudel/webaudio"; import { initAudioOnFirstClick, getAudioContext, webaudioOutput } from "@strudel.cycles/webaudio";
const { note } = controls; const { note } = controls;
initAudioOnFirstClick(); initAudioOnFirstClick();
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel/webaudio", "name": "@strudel.cycles/webaudio",
"version": "0.11.0", "version": "0.9.0",
"description": "Web Audio helpers for Strudel", "description": "Web Audio helpers for Strudel",
"main": "index.mjs", "main": "index.mjs",
"type": "module", "type": "module",
@@ -34,7 +34,7 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel/core": "workspace:*", "@strudel.cycles/core": "workspace:*",
"superdough": "workspace:*" "superdough": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
+1 -1
View File
@@ -1,4 +1,4 @@
import { Pattern, getDrawContext, clamp } from '@strudel/core'; import { Pattern, getDrawContext, clamp } from '@strudel.cycles/core';
import { analyser, getAnalyzerData } from 'superdough'; import { analyser, getAnalyzerData } from 'superdough';
export function drawTimeScope( export function drawTimeScope(
+1 -1
View File
@@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import * as strudel from '@strudel/core'; import * as strudel from '@strudel.cycles/core';
import { superdough, getAudioContext, setLogger, doughTrigger } from 'superdough'; import { superdough, getAudioContext, setLogger, doughTrigger } from 'superdough';
const { Pattern, logger } = strudel; const { Pattern, logger } = strudel;
+2 -2
View File
@@ -1,9 +1,9 @@
# @strudel/xen # @strudel.cycles/xen
This package adds xenharmonic / microtonal functions to strudel Patterns. Further documentation + examples will follow. This package adds xenharmonic / microtonal functions to strudel Patterns. Further documentation + examples will follow.
## Install ## Install
```sh ```sh
npm i @strudel/xen --save npm i @strudel.cycles/xen --save
``` ```
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel/xen", "name": "@strudel.cycles/xen",
"version": "0.11.0", "version": "0.9.0",
"description": "Xenharmonic API for strudel", "description": "Xenharmonic API for strudel",
"main": "index.mjs", "main": "index.mjs",
"publishConfig": { "publishConfig": {
@@ -30,7 +30,7 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel/core": "workspace:*" "@strudel.cycles/core": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"vite": "^5.0.10", "vite": "^5.0.10",
+1 -1
View File
@@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
*/ */
import Tune from './tunejs.js'; import Tune from './tunejs.js';
import { register } from '@strudel/core'; import { register } from '@strudel.cycles/core';
export const tune = register('tune', (scale, pat) => { export const tune = register('tune', (scale, pat) => {
const tune = new Tune(); const tune = new Tune();
+1 -1
View File
@@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { register, _mod, parseNumeral } from '@strudel/core'; import { register, _mod, parseNumeral } from '@strudel.cycles/core';
export function edo(name) { export function edo(name) {
if (!/^[1-9]+[0-9]*edo$/.test(name)) { if (!/^[1-9]+[0-9]*edo$/.test(name)) {
+569 -723
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,4 +1,4 @@
# @strudel/tauri # @strudel.cycles/tauri
Rust source files for building native desktop apps using Tauri Rust source files for building native desktop apps using Tauri
-58
View File
@@ -3332,55 +3332,6 @@ exports[`runs examples > example "pan" example index 0 1`] = `
] ]
`; `;
exports[`runs examples > example "panchor" example index 0 1`] = `
[
"[ 0/1 → 1/1 | note:c penv:12 panchor:0 ]",
"[ 1/1 → 2/1 | note:c penv:12 panchor:0.5 ]",
"[ 2/1 → 3/1 | note:c penv:12 panchor:1 ]",
"[ 3/1 → 4/1 | note:c penv:12 panchor:0.5 ]",
]
`;
exports[`runs examples > example "pattack" example index 0 1`] = `
[
"[ 0/1 → 1/1 | note:c pattack:0 ]",
"[ 1/1 → 2/1 | note:eb pattack:0.1 ]",
"[ 2/1 → 3/1 | note:g pattack:0.25 ]",
"[ 3/1 → 4/1 | note:bb pattack:0.5 ]",
]
`;
exports[`runs examples > example "pcurve" example index 0 1`] = `
[
"[ 0/1 → 1/2 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:0 ]",
"[ 1/2 → 1/1 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:0 ]",
"[ 1/1 → 3/2 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:1 ]",
"[ 3/2 → 2/1 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:1 ]",
"[ 2/1 → 5/2 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:0 ]",
"[ 5/2 → 3/1 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:0 ]",
"[ 3/1 → 7/2 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:1 ]",
"[ 7/2 → 4/1 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:1 ]",
]
`;
exports[`runs examples > example "pdecay" example index 0 1`] = `
[
"[ 0/1 → 1/1 | note:c pdecay:0 ]",
"[ 1/1 → 2/1 | note:eb pdecay:0.1 ]",
"[ 2/1 → 3/1 | note:g pdecay:0.25 ]",
"[ 3/1 → 4/1 | note:bb pdecay:0.5 ]",
]
`;
exports[`runs examples > example "penv" example index 0 1`] = `
[
"[ 0/1 → 1/1 | note:c penv:12 ]",
"[ 1/1 → 2/1 | note:c penv:7 ]",
"[ 2/1 → 3/1 | note:c penv:1 ]",
"[ 3/1 → 4/1 | note:c penv:0.5 ]",
]
`;
exports[`runs examples > example "perlin" example index 0 1`] = ` exports[`runs examples > example "perlin" example index 0 1`] = `
[ [
"[ 0/1 → 1/4 | s:hh cutoff:512.5097280354112 ]", "[ 0/1 → 1/4 | s:hh cutoff:512.5097280354112 ]",
@@ -3709,15 +3660,6 @@ exports[`runs examples > example "postgain" example index 0 1`] = `
] ]
`; `;
exports[`runs examples > example "prelease" example index 0 1`] = `
[
"[ 0/1 → 1/2 | note:c release:0.5 prelease:0 ]",
"[ 1/1 → 3/2 | note:eb release:0.5 prelease:0.1 ]",
"[ 2/1 → 5/2 | note:g release:0.5 prelease:0.25 ]",
"[ 3/1 → 7/2 | note:bb release:0.5 prelease:0.5 ]",
]
`;
exports[`runs examples > example "press" example index 0 1`] = ` exports[`runs examples > example "press" example index 0 1`] = `
[ [
"[ 0/1 → 1/2 | s:hh ]", "[ 0/1 → 1/2 | s:hh ]",
+1 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
import { getMetadata } from '../website/src/metadata_parser'; import { getMetadata } from '../website/src/pages/metadata_parser';
describe.concurrent('Metadata parser', () => { describe.concurrent('Metadata parser', () => {
it('loads a tag from inline comment', async () => { it('loads a tag from inline comment', async () => {
+19 -19
View File
@@ -3,25 +3,25 @@
// it might require mocking more stuff when tunes added that use other functions // it might require mocking more stuff when tunes added that use other functions
// import * as tunes from './tunes.mjs'; // import * as tunes from './tunes.mjs';
import { evaluate } from '@strudel/transpiler'; import { evaluate } from '@strudel.cycles/transpiler';
import { evalScope } from '@strudel/core'; import { evalScope } from '@strudel.cycles/core';
import * as strudel from '@strudel/core'; import * as strudel from '@strudel.cycles/core';
import * as webaudio from '@strudel/webaudio'; import * as webaudio from '@strudel.cycles/webaudio';
import controls from '@strudel/core/controls.mjs'; import controls from '@strudel.cycles/core/controls.mjs';
// import gist from '@strudel/core/gist.js'; // import gist from '@strudel.cycles/core/gist.js';
import { mini, m } from '@strudel/mini/mini.mjs'; import { mini, m } from '@strudel.cycles/mini/mini.mjs';
// import * as voicingHelpers from '@strudel/tonal/voicings.mjs'; // import * as voicingHelpers from '@strudel.cycles/tonal/voicings.mjs';
// import euclid from '@strudel/core/euclid.mjs'; // import euclid from '@strudel.cycles/core/euclid.mjs';
// import '@strudel/midi/midi.mjs'; // import '@strudel.cycles/midi/midi.mjs';
import * as tonalHelpers from '@strudel/tonal'; import * as tonalHelpers from '@strudel.cycles/tonal';
import '@strudel/xen/xen.mjs'; import '@strudel.cycles/xen/xen.mjs';
// import '@strudel/xen/tune.mjs'; // import '@strudel.cycles/xen/tune.mjs';
// import '@strudel/core/euclid.mjs'; // import '@strudel.cycles/core/euclid.mjs';
// import '@strudel/core/speak.mjs'; // window is not defined // import '@strudel.cycles/core/speak.mjs'; // window is not defined
// import '@strudel/osc/osc.mjs'; // import '@strudel.cycles/osc/osc.mjs';
// import '@strudel/webaudio/webaudio.mjs'; // import '@strudel.cycles/webaudio/webaudio.mjs';
// import '@strudel/serial/serial.mjs'; // import '@strudel.cycles/serial/serial.mjs';
// import controls from '@strudel/core/controls.mjs'; // import controls from '@strudel.cycles/core/controls.mjs';
import '../website/src/repl/piano'; import '../website/src/repl/piano';
class MockedNode { class MockedNode {
-276
View File
@@ -1,276 +0,0 @@
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
@@ -1,120 +0,0 @@
// 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;
+12 -13
View File
@@ -1,5 +1,5 @@
{ {
"name": "@strudel/website", "name": "@strudel.cycles/website",
"type": "module", "type": "module",
"version": "0.6.0", "version": "0.6.0",
"private": true, "private": true,
@@ -23,17 +23,17 @@
"@heroicons/react": "^2.1.1", "@heroicons/react": "^2.1.1",
"@nanostores/persistent": "^0.9.1", "@nanostores/persistent": "^0.9.1",
"@nanostores/react": "^0.7.1", "@nanostores/react": "^0.7.1",
"@strudel/core": "workspace:*", "@strudel.cycles/core": "workspace:*",
"@strudel/csound": "workspace:*", "@strudel.cycles/csound": "workspace:*",
"@strudel/midi": "workspace:*", "@strudel.cycles/midi": "workspace:*",
"@strudel/mini": "workspace:*", "@strudel.cycles/mini": "workspace:*",
"@strudel/osc": "workspace:*", "@strudel.cycles/osc": "workspace:*",
"@strudel/serial": "workspace:*", "@strudel.cycles/serial": "workspace:*",
"@strudel/soundfonts": "workspace:*", "@strudel.cycles/soundfonts": "workspace:*",
"@strudel/tonal": "workspace:*", "@strudel.cycles/tonal": "workspace:*",
"@strudel/transpiler": "workspace:*", "@strudel.cycles/transpiler": "workspace:*",
"@strudel/webaudio": "workspace:*", "@strudel.cycles/webaudio": "workspace:*",
"@strudel/xen": "workspace:*", "@strudel.cycles/xen": "workspace:*",
"@strudel/codemirror": "workspace:*", "@strudel/codemirror": "workspace:*",
"@strudel/desktopbridge": "workspace:*", "@strudel/desktopbridge": "workspace:*",
"@strudel/hydra": "workspace:*", "@strudel/hydra": "workspace:*",
@@ -48,7 +48,6 @@
"astro": "^4.0.8", "astro": "^4.0.8",
"canvas": "^2.11.2", "canvas": "^2.11.2",
"claviature": "^0.1.0", "claviature": "^0.1.0",
"date-fns": "^3.2.0",
"nanoid": "^5.0.4", "nanoid": "^5.0.4",
"nanostores": "^0.9.5", "nanostores": "^0.9.5",
"react": "^18.2.0", "react": "^18.2.0",
-27
View File
@@ -1,27 +0,0 @@
---
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
@@ -1,5 +0,0 @@
---
const { src } = Astro.props;
---
<video controls width="600" src={src}></video>
+1 -1
View File
@@ -9,7 +9,7 @@ import MobileNav from '../../docs/MobileNav';
import { SIDEBAR } from '../../config'; import { SIDEBAR } from '../../config';
type Props = { type Props = {
currentPage?: string; currentPage: string;
}; };
const { currentPage } = Astro.props as Props; const { currentPage } = Astro.props as Props;
@@ -1,11 +1,18 @@
--- ---
import type { Frontmatter } from '../../config';
import MoreMenu from '../RightSidebar/MoreMenu.astro'; import MoreMenu from '../RightSidebar/MoreMenu.astro';
import TableOfContents from '../RightSidebar/TableOfContents';
import type { MarkdownHeading } from 'astro';
type Props = { type Props = {
githubEditUrl?: string; frontmatter: Frontmatter;
headings: MarkdownHeading[];
githubEditUrl: string;
}; };
const { githubEditUrl } = Astro.props as Props; const { frontmatter, headings, githubEditUrl } = Astro.props as Props;
const title = frontmatter.title;
const currentPage = Astro.url.pathname;
--- ---
<article id="article" class="content"> <article id="article" class="content">
+2 -2
View File
@@ -1,7 +1,7 @@
import useEvent from '@src/useEvent.mjs'; import useEvent from '@src/useEvent.mjs';
import useFrame from '@src/useFrame.mjs'; import useFrame from '@src/useFrame.mjs';
import { getAudioContext } from '@strudel/webaudio'; import { getAudioContext } from '@strudel.cycles/webaudio';
import { midi2note } from '@strudel/core'; import { midi2note } from '@strudel.cycles/core';
import { useState, useRef, useEffect } from 'react'; import { useState, useRef, useEffect } from 'react';
import Claviature from '@components/Claviature'; import Claviature from '@components/Claviature';
@@ -2,7 +2,7 @@
import * as CONFIG from '../../config'; import * as CONFIG from '../../config';
type Props = { type Props = {
editHref?: string; editHref: string;
}; };
const { editHref } = Astro.props as Props; const { editHref } = Astro.props as Props;
@@ -6,7 +6,7 @@ import AvatarList from '../Footer/AvatarList.astro';
type Props = { type Props = {
headings: MarkdownHeading[]; headings: MarkdownHeading[];
githubEditUrl?: string; githubEditUrl: string;
}; };
const { headings, githubEditUrl } = Astro.props as Props; const { headings, githubEditUrl } = Astro.props as Props;
-7
View File
@@ -36,13 +36,6 @@ export function Showcase() {
} }
let _videos = [ let _videos = [
{ title: 'Coding Music With Strudel Workhop by Dan Gorelick and Viola He', id: 'oqyAJ4WeKoU' },
{ title: 'Hexe - playing w strudel live coding music', id: '03m3F5xVOMg' },
{ title: 'DJ_Dave - Array [Lil Data Edit]', id: 'KUujFuTcuKc' },
{ title: 'DJ_Dave - Bitrot [v10101a Edit]', id: 'z_cJMdBp67Q' },
{ title: 'you will not steve reich your way out of it', id: 'xpILnXcWyuo' },
{ title: 'dough dream #1 - strudel jam 12/03/23', id: 'p0J7XrT9JEs' },
{ title: 'eddyflux & superdirtspatz at the dough cathedral', id: 'GrkwKMQ7_Ys' },
// solstice 2023 // solstice 2023
{ title: 'Jade Rose @ solstice stream 2023', id: 'wg0vW5Ac7L0' }, { title: 'Jade Rose @ solstice stream 2023', id: 'wg0vW5Ac7L0' },
{ {
-1
View File
@@ -57,7 +57,6 @@ export const SIDEBAR: Sidebar = {
Presentation: [ Presentation: [
{ text: 'What is Strudel?', link: 'workshop/getting-started' }, { text: 'What is Strudel?', link: 'workshop/getting-started' },
{ text: 'Showcase', link: 'intro/showcase' }, { text: 'Showcase', link: 'intro/showcase' },
{ text: 'Blog', link: 'blog' },
], ],
Workshop: [ Workshop: [
// { text: 'Getting Started', link: 'workshop/getting-started' }, // { text: 'Getting Started', link: 'workshop/getting-started' },
@@ -1,40 +0,0 @@
---
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