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