mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-11 13:48:34 +00:00
Merge branch 'tidalcycles:main' into repl_sync_fixes
This commit is contained in:
commit
af740c3abb
18
README.md
18
README.md
@ -15,25 +15,15 @@ An experiment in making a [Tidal](https://github.com/tidalcycles/tidal/) using w
|
||||
After cloning the project, you can run the REPL locally:
|
||||
|
||||
```bash
|
||||
pnpm run setup
|
||||
pnpm run repl
|
||||
pnpm i
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## Using Strudel In Your Project
|
||||
|
||||
There are multiple npm packages you can use to use strudel, or only parts of it, in your project:
|
||||
This project is organized into many [packages](./packages), which are also available on [npm](https://www.npmjs.com/search?q=%40strudel).
|
||||
|
||||
- [`core`](./packages/core/): tidal pattern engine
|
||||
- [`mini`](./packages/mini): mini notation parser + core binding
|
||||
- [`transpiler`](./packages/transpiler): user code transpiler
|
||||
- [`webaudio`](./packages/webaudio): webaudio output
|
||||
- [`osc`](./packages/osc): bindings to communicate via OSC
|
||||
- [`midi`](./packages/midi): webmidi bindings
|
||||
- [`serial`](./packages/serial): webserial bindings
|
||||
- [`tonal`](./packages/tonal): tonal functions
|
||||
- ... [and there are more](./packages/)
|
||||
|
||||
Click on the package names to find out more about each one.
|
||||
Read more about how to use these in your own project [here](https://strudel.cc/technical-manual/project-start).
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
<!doctype html>
|
||||
<script src="https://unpkg.com/@strudel/web@1.0.3"></script>
|
||||
<button id="play">play</button>
|
||||
<button id="stop">stop</button>
|
||||
<script type="module">
|
||||
import { initStrudel } from 'https://cdn.skypack.dev/@strudel/web@0.8.2';
|
||||
|
||||
initStrudel();
|
||||
<script>
|
||||
strudel.initStrudel();
|
||||
document.getElementById('play').addEventListener('click', () => evaluate('note("c a f e").jux(rev)'));
|
||||
document.getElementById('play').addEventListener('stop', () => hush());
|
||||
</script>
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
<!doctype html>
|
||||
<script src="https://unpkg.com/@strudel/web@1.0.3"></script>
|
||||
<button id="a">A</button>
|
||||
<button id="b">B</button>
|
||||
<button id="c">C</button>
|
||||
<button id="stop">stop</button>
|
||||
<script type="module">
|
||||
import { initStrudel } from 'https://cdn.skypack.dev/@strudel/web@0.8.2';
|
||||
<script>
|
||||
initStrudel({
|
||||
prebake: () => samples('github:tidalcycles/dirt-samples'),
|
||||
});
|
||||
|
||||
@ -1,15 +1,20 @@
|
||||
// this barrel export is currently only used to find undocumented exports
|
||||
export * from './packages/codemirror/index.mjs';
|
||||
export * from './packages/core/index.mjs';
|
||||
export * from './packages/csound/index.mjs';
|
||||
export * from './packages/embed/index.mjs';
|
||||
export * from './packages/desktopbridge/index.mjs';
|
||||
export * from './packages/draw/index.mjs';
|
||||
export * from './packages/embed/index.mjs';
|
||||
export * from './packages/hydra/index.mjs';
|
||||
export * from './packages/midi/index.mjs';
|
||||
export * from './packages/mini/index.mjs';
|
||||
export * from './packages/osc/index.mjs';
|
||||
export * from './packages/react/index.mjs';
|
||||
export * from './packages/repl/index.mjs';
|
||||
export * from './packages/serial/index.mjs';
|
||||
export * from './packages/soundfonts/index.mjs';
|
||||
export * from './packages/superdough/index.mjs';
|
||||
export * from './packages/tonal/index.mjs';
|
||||
export * from './packages/transpiler/index.mjs';
|
||||
export * from './packages/web/index.mjs';
|
||||
export * from './packages/webaudio/index.mjs';
|
||||
export * from './packages/xen/index.mjs';
|
||||
|
||||
@ -53,9 +53,10 @@
|
||||
"@strudel/xen": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"dependency-tree": "^10.0.9",
|
||||
"@tauri-apps/cli": "^1.5.9",
|
||||
"@vitest/ui": "^1.1.0",
|
||||
"acorn": "^8.11.3",
|
||||
"dependency-tree": "^10.0.9",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"events": "^3.3.0",
|
||||
|
||||
@ -141,7 +141,6 @@ export class StrudelMirror {
|
||||
this.painters = [];
|
||||
this.drawTime = drawTime;
|
||||
this.onDraw = onDraw;
|
||||
const self = this;
|
||||
this.id = id || s4();
|
||||
|
||||
this.drawer = new Drawer((haps, time) => {
|
||||
@ -150,13 +149,6 @@ export class StrudelMirror {
|
||||
this.onDraw?.(haps, time, currentFrame, this.painters);
|
||||
}, drawTime);
|
||||
|
||||
// this approach does not work with multiple repls on screen
|
||||
// TODO: refactor onPaint usages + find fix, maybe remove painters here?
|
||||
Pattern.prototype.onPaint = function (onPaint) {
|
||||
self.painters.push(onPaint);
|
||||
return this;
|
||||
};
|
||||
|
||||
this.prebaked = prebake();
|
||||
autodraw && this.drawFirstFrame();
|
||||
|
||||
@ -182,6 +174,14 @@ export class StrudelMirror {
|
||||
beforeEval: async () => {
|
||||
cleanupDraw();
|
||||
this.painters = [];
|
||||
const self = this;
|
||||
// this is similar to repl.mjs > injectPatternMethods
|
||||
// maybe there is a solution without prototype hacking, but hey, it works
|
||||
// we need to do this befor every eval to make sure it works with multiple StrudelMirror's side by side
|
||||
Pattern.prototype.onPaint = function (onPaint) {
|
||||
self.painters.push(onPaint);
|
||||
return this;
|
||||
};
|
||||
await this.prebaked;
|
||||
await replOptions?.beforeEval?.();
|
||||
},
|
||||
|
||||
@ -92,7 +92,7 @@ const miniLocationHighlights = EditorView.decorations.compute([miniLocations, vi
|
||||
|
||||
if (haps.has(id)) {
|
||||
const hap = haps.get(id);
|
||||
const color = hap.context.color ?? 'var(--foreground)';
|
||||
const color = hap.value?.color ?? 'var(--foreground)';
|
||||
// Get explicit channels for color values
|
||||
/*
|
||||
const swatch = document.createElement('div');
|
||||
|
||||
@ -106,17 +106,23 @@ function getCanvasWidget(id, options = {}) {
|
||||
|
||||
registerWidget('_pianoroll', (id, options = {}, pat) => {
|
||||
const ctx = getCanvasWidget(id, options).getContext('2d');
|
||||
return pat.pianoroll({ fold: 1, ...options, ctx, id });
|
||||
return pat.id(id).pianoroll({ fold: 1, ...options, ctx, id });
|
||||
});
|
||||
|
||||
/* registerWidget('_spiral', (id, options = {}, pat) => {
|
||||
options = { width: 200, height: 200, size: 36, ...options };
|
||||
registerWidget('_punchcard', (id, options = {}, pat) => {
|
||||
const ctx = getCanvasWidget(id, options).getContext('2d');
|
||||
return pat.spiral({ ...options, ctx, id });
|
||||
}); */
|
||||
return pat.id(id).punchcard({ fold: 1, ...options, ctx, id });
|
||||
});
|
||||
|
||||
registerWidget('_spiral', (id, options = {}, pat) => {
|
||||
let _size = options.size || 275;
|
||||
options = { width: _size, height: _size, ...options, size: _size / 5 };
|
||||
const ctx = getCanvasWidget(id, options).getContext('2d');
|
||||
return pat.id(id).spiral({ ...options, ctx, id });
|
||||
});
|
||||
|
||||
registerWidget('_scope', (id, options = {}, pat) => {
|
||||
options = { width: 500, height: 60, pos: 0.5, scale: 1, ...options };
|
||||
const ctx = getCanvasWidget(id, options).getContext('2d');
|
||||
return pat.scope({ ...options, ctx, id });
|
||||
return pat.id(id).scope({ ...options, ctx, id });
|
||||
});
|
||||
|
||||
@ -895,17 +895,37 @@ export const { delaytime, delayt, dt } = registerControl('delaytime', 'delayt',
|
||||
*/
|
||||
export const { lock } = registerControl('lock');
|
||||
/**
|
||||
* Set detune of oscillators. Works only with some synths, see <a target="_blank" href="https://tidalcycles.org/docs/patternlib/tutorials/synthesizers">tidal doc</a>
|
||||
* Set detune for stacked voices of supported oscillators
|
||||
*
|
||||
* @name detune
|
||||
* @param {number | Pattern} amount between 0 and 1
|
||||
* @param {number | Pattern} amount
|
||||
* @synonyms det
|
||||
* @superdirtOnly
|
||||
* @example
|
||||
* n("0 3 7").s('superzow').octave(3).detune("<0 .25 .5 1 2>").osc()
|
||||
* note("d f a a# a d3").fast(2).s("supersaw").detune("<.1 .2 .5 24.1>")
|
||||
*
|
||||
*/
|
||||
export const { detune, det } = registerControl('detune', 'det');
|
||||
/**
|
||||
* Set number of stacked voices for supported oscillators
|
||||
*
|
||||
* @name unison
|
||||
* @param {number | Pattern} numvoices
|
||||
* @example
|
||||
* note("d f a a# a d3").fast(2).s("supersaw").unison("<1 2 7>")
|
||||
*
|
||||
*/
|
||||
export const { unison } = registerControl('unison');
|
||||
|
||||
/**
|
||||
* Set the stereo pan spread for supported oscillators
|
||||
*
|
||||
* @name spread
|
||||
* @param {number | Pattern} spread between 0 and 1
|
||||
* @example
|
||||
* note("d f a a# a d3").fast(2).s("supersaw").spread("<0 .3 1>")
|
||||
*
|
||||
*/
|
||||
export const { spread } = registerControl('spread');
|
||||
/**
|
||||
* Set dryness of reverb. See `room` and `size` for more information about reverb.
|
||||
*
|
||||
@ -1512,6 +1532,14 @@ export const { zdelay } = registerControl('zdelay');
|
||||
export const { tremolo } = registerControl('tremolo');
|
||||
export const { zzfx } = registerControl('zzfx');
|
||||
|
||||
/**
|
||||
* Sets the color of the hap in visualizations like pianoroll or highlighting.
|
||||
* @name color
|
||||
* @synonyms colour
|
||||
* @param {string} color Hexadecimal or CSS color name
|
||||
*/
|
||||
export const { color, colour } = registerControl(['color', 'colour']);
|
||||
|
||||
// TODO: slice / splice https://www.youtube.com/watch?v=hKhPdO0RKDQ&list=PL2lW1zNIIwj3bDkh-Y3LUGDuRcoUigoDs&index=13
|
||||
|
||||
export let createParams = (...names) =>
|
||||
|
||||
@ -262,7 +262,7 @@ export class Pattern {
|
||||
}
|
||||
|
||||
// Flatterns patterns of patterns, by retriggering/resetting inner patterns at onsets of outer pattern haps
|
||||
trigJoin(cycleZero = false) {
|
||||
resetJoin(restart = false) {
|
||||
const pat_of_pats = this;
|
||||
return new Pattern((state) => {
|
||||
return (
|
||||
@ -273,9 +273,9 @@ export class Pattern {
|
||||
.map((outer_hap) => {
|
||||
return (
|
||||
outer_hap.value
|
||||
// trig = align the inner pattern cycle start to outer pattern haps
|
||||
// Trigzero = align the inner pattern cycle zero to outer pattern haps
|
||||
.late(cycleZero ? outer_hap.whole.begin : outer_hap.whole.begin.cyclePos())
|
||||
// reset = align the inner pattern cycle start to outer pattern haps
|
||||
// restart = align the inner pattern cycle zero to outer pattern haps
|
||||
.late(restart ? outer_hap.whole.begin : outer_hap.whole.begin.cyclePos())
|
||||
.query(state)
|
||||
.map((inner_hap) =>
|
||||
new Hap(
|
||||
@ -294,8 +294,8 @@ export class Pattern {
|
||||
});
|
||||
}
|
||||
|
||||
trigzeroJoin() {
|
||||
return this.trigJoin(true);
|
||||
restartJoin() {
|
||||
return this.resetJoin(true);
|
||||
}
|
||||
|
||||
// Like the other joins above, joins a pattern of patterns of values, into a flatter
|
||||
@ -708,13 +708,13 @@ export class Pattern {
|
||||
const otherPat = reify(other);
|
||||
return otherPat.fmap((a) => thisPat.fmap((b) => func(b)(a))).squeezeJoin();
|
||||
}
|
||||
_opTrig(other, func) {
|
||||
_opReset(other, func) {
|
||||
const otherPat = reify(other);
|
||||
return otherPat.fmap((b) => this.fmap((a) => func(a)(b))).trigJoin();
|
||||
return otherPat.fmap((b) => this.fmap((a) => func(a)(b))).resetJoin();
|
||||
}
|
||||
_opTrigzero(other, func) {
|
||||
_opRestart(other, func) {
|
||||
const otherPat = reify(other);
|
||||
return otherPat.fmap((b) => this.fmap((a) => func(a)(b))).trigzeroJoin();
|
||||
return otherPat.fmap((b) => this.fmap((a) => func(a)(b))).restartJoin();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
@ -1024,7 +1024,7 @@ function _composeOp(a, b, func) {
|
||||
func: [(a, b) => b(a)],
|
||||
};
|
||||
|
||||
const hows = ['In', 'Out', 'Mix', 'Squeeze', 'SqueezeOut', 'Trig', 'Trigzero'];
|
||||
const hows = ['In', 'Out', 'Mix', 'Squeeze', 'SqueezeOut', 'Reset', 'Restart'];
|
||||
|
||||
// generate methods to do what and how
|
||||
for (const [what, [op, preprocess]] of Object.entries(composers)) {
|
||||
@ -1113,10 +1113,10 @@ function _composeOp(a, b, func) {
|
||||
* s("[<bd lt> sd]*2, hh*8").reset("<x@3 x(5,8)>")
|
||||
*/
|
||||
Pattern.prototype.reset = function (...args) {
|
||||
return this.keepif.trig(...args);
|
||||
return this.keepif.reset(...args);
|
||||
};
|
||||
Pattern.prototype.resetAll = function (...args) {
|
||||
return this.keep.trig(...args);
|
||||
return this.keep.reset(...args);
|
||||
};
|
||||
/**
|
||||
* Restarts the pattern for each onset of the restart pattern.
|
||||
@ -1126,10 +1126,10 @@ function _composeOp(a, b, func) {
|
||||
* s("[<bd lt> sd]*2, hh*8").restart("<x@3 x(5,8)>")
|
||||
*/
|
||||
Pattern.prototype.restart = function (...args) {
|
||||
return this.keepif.trigzero(...args);
|
||||
return this.keepif.restart(...args);
|
||||
};
|
||||
Pattern.prototype.restartAll = function (...args) {
|
||||
return this.keep.trigzero(...args);
|
||||
return this.keep.restart(...args);
|
||||
};
|
||||
})();
|
||||
|
||||
@ -2477,15 +2477,14 @@ export const hsl = register('hsl', (h, s, l, pat) => {
|
||||
});
|
||||
|
||||
/**
|
||||
* Sets the color of the hap in visualizations like pianoroll or highlighting.
|
||||
* @name color
|
||||
* @synonyms colour
|
||||
* @param {string} color Hexadecimal or CSS color name
|
||||
* Sets the id of the hap in, for filtering in the future.
|
||||
* @name id
|
||||
* @noAutocomplete
|
||||
* @param {string} id anything unique
|
||||
*/
|
||||
// TODO: move this to controls https://github.com/tidalcycles/strudel/issues/288
|
||||
export const { color, colour } = register(['color', 'colour'], function (color, pat) {
|
||||
return pat.withContext((context) => ({ ...context, color }));
|
||||
});
|
||||
Pattern.prototype.id = function (id) {
|
||||
return this.withContext((ctx) => ({ ...ctx, id }));
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Control-related functions, i.e. ones that manipulate patterns of
|
||||
|
||||
@ -110,7 +110,7 @@ export function repl({
|
||||
const cpm = register('cpm', function (cpm, pat) {
|
||||
return pat._fast(cpm / 60 / scheduler.cps);
|
||||
});
|
||||
evalScope({
|
||||
return evalScope({
|
||||
all,
|
||||
hush,
|
||||
cpm,
|
||||
@ -127,7 +127,7 @@ export function repl({
|
||||
}
|
||||
try {
|
||||
updateState({ code, pending: true });
|
||||
injectPatternMethods();
|
||||
await injectPatternMethods();
|
||||
await beforeEval?.({ code });
|
||||
shouldHush && hush();
|
||||
let { pattern, meta } = await _evaluate(code, transpiler);
|
||||
|
||||
@ -269,7 +269,7 @@ export const pickmodOut = register('pickmodOut', function (lookup, pat) {
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
export const pickRestart = register('pickRestart', function (lookup, pat) {
|
||||
return _pick(lookup, pat, false).trigzeroJoin();
|
||||
return _pick(lookup, pat, false).restartJoin();
|
||||
});
|
||||
|
||||
/** * The same as `pickRestart`, but if you pick a number greater than the size of the list,
|
||||
@ -279,7 +279,7 @@ export const pickRestart = register('pickRestart', function (lookup, pat) {
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
export const pickmodRestart = register('pickmodRestart', function (lookup, pat) {
|
||||
return _pick(lookup, pat, true).trigzeroJoin();
|
||||
return _pick(lookup, pat, true).restartJoin();
|
||||
});
|
||||
|
||||
/** * Similar to `pick`, but the choosen pattern is reset when its index is triggered.
|
||||
@ -288,7 +288,7 @@ export const pickmodRestart = register('pickmodRestart', function (lookup, pat)
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
export const pickReset = register('pickReset', function (lookup, pat) {
|
||||
return _pick(lookup, pat, false).trigJoin();
|
||||
return _pick(lookup, pat, false).resetJoin();
|
||||
});
|
||||
|
||||
/** * The same as `pickReset`, but if you pick a number greater than the size of the list,
|
||||
@ -298,7 +298,7 @@ export const pickReset = register('pickReset', function (lookup, pat) {
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
export const pickmodReset = register('pickmodReset', function (lookup, pat) {
|
||||
return _pick(lookup, pat, true).trigJoin();
|
||||
return _pick(lookup, pat, true).resetJoin();
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@ -181,18 +181,18 @@ describe('Pattern', () => {
|
||||
new Hap(ts(1 / 2, 2 / 3), ts(1 / 2, 2 / 3), 7),
|
||||
]);
|
||||
});
|
||||
it('can Trig() structure', () => {
|
||||
it('can Reset() structure', () => {
|
||||
sameFirst(
|
||||
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10)
|
||||
.add.trig(20, 30)
|
||||
.add.reset(20, 30)
|
||||
.early(2),
|
||||
sequence(26, 27, 36, 37),
|
||||
);
|
||||
});
|
||||
it('can Trigzero() structure', () => {
|
||||
it('can Restart() structure', () => {
|
||||
sameFirst(
|
||||
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10)
|
||||
.add.trigzero(20, 30)
|
||||
.add.restart(20, 30)
|
||||
.early(2),
|
||||
sequence(21, 22, 31, 32),
|
||||
);
|
||||
@ -233,18 +233,18 @@ describe('Pattern', () => {
|
||||
new Hap(ts(1 / 2, 2 / 3), ts(1 / 2, 2 / 3), 2),
|
||||
]);
|
||||
});
|
||||
it('can Trig() structure', () => {
|
||||
it('can Reset() structure', () => {
|
||||
sameFirst(
|
||||
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10)
|
||||
.keep.trig(20, 30)
|
||||
.keep.reset(20, 30)
|
||||
.early(2),
|
||||
sequence(6, 7, 6, 7),
|
||||
);
|
||||
});
|
||||
it('can Trigzero() structure', () => {
|
||||
it('can Restart() structure', () => {
|
||||
sameFirst(
|
||||
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10)
|
||||
.keep.trigzero(20, 30)
|
||||
.keep.restart(20, 30)
|
||||
.early(2),
|
||||
sequence(1, 2, 1, 2),
|
||||
);
|
||||
@ -279,18 +279,18 @@ describe('Pattern', () => {
|
||||
new Hap(ts(1 / 2, 2 / 3), ts(1 / 2, 2 / 3), 2),
|
||||
]);
|
||||
});
|
||||
it('can Trig() structure', () => {
|
||||
it('can Reset() structure', () => {
|
||||
sameFirst(
|
||||
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10)
|
||||
.keepif.trig(false, true)
|
||||
.keepif.reset(false, true)
|
||||
.early(2),
|
||||
sequence(silence, silence, 6, 7),
|
||||
);
|
||||
});
|
||||
it('can Trigzero() structure', () => {
|
||||
it('can Restart() structure', () => {
|
||||
sameFirst(
|
||||
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10)
|
||||
.keepif.trigzero(false, true)
|
||||
.keepif.restart(false, true)
|
||||
.early(2),
|
||||
sequence(silence, silence, 1, 2),
|
||||
);
|
||||
|
||||
@ -96,9 +96,8 @@ export const cleanupDraw = (clearScreen = true) => {
|
||||
}
|
||||
};
|
||||
|
||||
Pattern.prototype.onPaint = function (onPaint) {
|
||||
// this is evil! TODO: add pattern.context
|
||||
this.context = { onPaint };
|
||||
Pattern.prototype.onPaint = function () {
|
||||
console.warn('[draw] onPaint was not overloaded. Some drawings might not work');
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
@ -129,12 +129,17 @@ export function pianoroll({
|
||||
colorizeInactive = 1,
|
||||
fontFamily,
|
||||
ctx,
|
||||
id,
|
||||
} = {}) {
|
||||
const w = ctx.canvas.width;
|
||||
const h = ctx.canvas.height;
|
||||
let from = -cycles * playhead;
|
||||
let to = cycles * (1 - playhead);
|
||||
|
||||
if (id) {
|
||||
haps = haps.filter((hap) => hap.context.id === id);
|
||||
}
|
||||
|
||||
if (timeframeProp) {
|
||||
console.warn('timeframe is deprecated! use from/to instead');
|
||||
from = 0;
|
||||
@ -189,7 +194,7 @@ export function pianoroll({
|
||||
if (hideInactive && !isActive) {
|
||||
return;
|
||||
}
|
||||
let color = event.value?.color || event.context?.color;
|
||||
let color = event.value?.color;
|
||||
active = color || active;
|
||||
inactive = colorizeInactive ? color || inactive : inactive;
|
||||
color = isActive ? active : inactive;
|
||||
|
||||
@ -19,7 +19,7 @@ function spiralSegment(options) {
|
||||
cy = 100,
|
||||
rotate = 0,
|
||||
thickness = margin / 2,
|
||||
color = '#0000ff30',
|
||||
color = 'steelblue',
|
||||
cap = 'round',
|
||||
stretch = 1,
|
||||
fromOpacity = 1,
|
||||
@ -50,18 +50,18 @@ function spiralSegment(options) {
|
||||
}
|
||||
|
||||
function drawSpiral(options) {
|
||||
const {
|
||||
let {
|
||||
stretch = 1,
|
||||
size = 80,
|
||||
thickness = size / 2,
|
||||
cap = 'butt', // round butt squar,
|
||||
inset = 3, // start angl,
|
||||
playheadColor = '#ffffff90',
|
||||
playheadColor = '#ffffff',
|
||||
playheadLength = 0.02,
|
||||
playheadThickness = thickness,
|
||||
padding = 0,
|
||||
steady = 1,
|
||||
inactiveColor = '#ffffff20',
|
||||
inactiveColor = '#ffffff50',
|
||||
colorizeInactive = 0,
|
||||
fade = true,
|
||||
// logSpiral = true,
|
||||
@ -69,8 +69,13 @@ function drawSpiral(options) {
|
||||
time,
|
||||
haps,
|
||||
drawTime,
|
||||
id,
|
||||
} = options;
|
||||
|
||||
if (id) {
|
||||
haps = haps.filter((hap) => hap.context.id === id);
|
||||
}
|
||||
|
||||
const [w, h] = [ctx.canvas.width, ctx.canvas.height];
|
||||
ctx.clearRect(0, 0, w * 2, h * 2);
|
||||
const [cx, cy] = [w / 2, h / 2];
|
||||
@ -97,7 +102,7 @@ function drawSpiral(options) {
|
||||
const isActive = hap.whole.begin <= time && hap.endClipped > time;
|
||||
const from = hap.whole.begin - time + inset;
|
||||
const to = hap.endClipped - time + inset - padding;
|
||||
const { color } = hap.context;
|
||||
const color = hap.value?.color;
|
||||
const opacity = fade ? 1 - Math.abs((hap.whole.begin - time) / min) : 1;
|
||||
spiralSegment({
|
||||
ctx,
|
||||
|
||||
@ -2,32 +2,63 @@
|
||||
|
||||
This package contains a embeddable web component for the Strudel REPL.
|
||||
|
||||
## Usage
|
||||
## Usage via Script Tag
|
||||
|
||||
Either install with `npm i @strudel/embed` or just use a cdn to import the script:
|
||||
Use this code in any HTML file:
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/@strudel/embed@latest"></script>
|
||||
<strudel-repl>
|
||||
<!--
|
||||
note(`[[e5 [b4 c5] d5 [c5 b4]]
|
||||
[a4 [a4 c5] e5 [d5 c5]]
|
||||
[b4 [~ c5] d5 e5]
|
||||
[c5 a4 a4 ~]
|
||||
[[~ d5] [~ f5] a5 [g5 f5]]
|
||||
[e5 [~ c5] e5 [d5 c5]]
|
||||
[b4 [b4 c5] d5 e5]
|
||||
[c5 a4 a4 ~]],
|
||||
[[e2 e3]*4]
|
||||
[[a2 a3]*4]
|
||||
[[g#2 g#3]*2 [e2 e3]*2]
|
||||
[a2 a3 a2 a3 a2 a3 b1 c2]
|
||||
[[d2 d3]*4]
|
||||
[[c2 c3]*4]
|
||||
[[b1 b2]*2 [e2 e3]*2]
|
||||
[[a1 a2]*4]`).slow(16)
|
||||
-->
|
||||
setcps(1)
|
||||
n("<0 1 2 3 4>*8").scale('G4 minor')
|
||||
.s("gm_lead_6_voice")
|
||||
.clip(sine.range(.2,.8).slow(8))
|
||||
.jux(rev)
|
||||
.room(2)
|
||||
.sometimes(add(note("12")))
|
||||
.lpf(perlin.range(200,20000).slow(4))
|
||||
-->
|
||||
</strudel-repl>
|
||||
```
|
||||
|
||||
Note that the Code is placed inside HTML comments to prevent the browser from treating it as HTML.
|
||||
This will load the strudel website in an iframe, using the code provided within the HTML comments `<!-- -->`.
|
||||
The HTML comments are needed to make sure the browser won't interpret it as HTML.
|
||||
|
||||
Alternatively you can create a REPL from JavaScript like this:
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/@strudel/embed@1.0.2"></script>
|
||||
<div id="strudel"></div>
|
||||
<script>
|
||||
let editor = document.createElement('strudel-repl');
|
||||
editor.setAttribute(
|
||||
'code',
|
||||
`setcps(1)
|
||||
n("<0 1 2 3 4>*8").scale('G4 minor')
|
||||
.s("gm_lead_6_voice")
|
||||
.clip(sine.range(.2,.8).slow(8))
|
||||
.jux(rev)
|
||||
.room(2)
|
||||
.sometimes(add(note("12")))
|
||||
.lpf(perlin.range(200,20000).slow(4))`,
|
||||
);
|
||||
document.getElementById('strudel').append(editor);
|
||||
</script>
|
||||
```
|
||||
|
||||
When you're using JSX, you could also use the `code` attribute in your markup:
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/@strudel/embed@1.0.2"></script>
|
||||
<strudel-repl code={`
|
||||
setcps(1)
|
||||
n("<0 1 2 3 4>*8").scale('G4 minor')
|
||||
.s("gm_lead_6_voice")
|
||||
.clip(sine.range(.2,.8).slow(8))
|
||||
.jux(rev)
|
||||
.room(2)
|
||||
.sometimes(add(note("12")))
|
||||
.lpf(perlin.range(200,20000).slow(4))
|
||||
`}></strudel-repl>
|
||||
```
|
||||
|
||||
@ -4,7 +4,7 @@ class Strudel extends HTMLElement {
|
||||
}
|
||||
connectedCallback() {
|
||||
setTimeout(() => {
|
||||
const code = (this.innerHTML + '').replace('<!--', '').replace('-->', '').trim();
|
||||
const code = this.getAttribute('code') || (this.innerHTML + '').replace('<!--', '').replace('-->', '').trim();
|
||||
const iframe = document.createElement('iframe');
|
||||
const src = `https://strudel.cc/#${encodeURIComponent(btoa(code))}`;
|
||||
// const src = `http://localhost:3000/#${encodeURIComponent(btoa(code))}`;
|
||||
|
||||
@ -2,4 +2,95 @@
|
||||
|
||||
The Strudel REPL as a web component.
|
||||
|
||||
[Usage example](https://github.com/tidalcycles/strudel/blob/main/examples/buildless/web-component-no-iframe.html)
|
||||
## Add Script Tag
|
||||
|
||||
First place this script tag once in your HTML:
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/@strudel/repl@latest"></script>
|
||||
```
|
||||
|
||||
You can also pin the version like this:
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/@strudel/repl@1.0.2"></script>
|
||||
```
|
||||
|
||||
This has the advantage that your code will always work, regardless of potential breaking changes in the strudel codebase.
|
||||
See [releases](https://github.com/tidalcycles/strudel/releases) for the latest versions.
|
||||
|
||||
## Use Web Component
|
||||
|
||||
When you've added the script tag, you can use the `strudel-editor` web component:
|
||||
|
||||
```html
|
||||
<strudel-editor>
|
||||
<!--
|
||||
setcps(1)
|
||||
n("<0 1 2 3 4>*8").scale('G4 minor')
|
||||
.s("gm_lead_6_voice")
|
||||
.clip(sine.range(.2,.8).slow(8))
|
||||
.jux(rev)
|
||||
.room(2)
|
||||
.sometimes(add(note("12")))
|
||||
.lpf(perlin.range(200,20000).slow(4))
|
||||
-->
|
||||
</strudel-editor>
|
||||
```
|
||||
|
||||
This will load the Strudel REPL using the code provided within the HTML comments `<!-- -->`.
|
||||
The HTML comments are needed to make sure the browser won't interpret it as HTML.
|
||||
|
||||
Alternatively you can create a REPL from JavaScript like this:
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/@strudel/repl@latest"></script>
|
||||
<div id="strudel"></div>
|
||||
<script>
|
||||
const repl = document.createElement('strudel-editor');
|
||||
repl.setAttribute(
|
||||
'code',
|
||||
`setcps(1)
|
||||
n("<0 1 2 3 4>*8").scale('G4 minor')
|
||||
.s("gm_lead_6_voice")
|
||||
.clip(sine.range(.2,.8).slow(8))
|
||||
.jux(rev)
|
||||
.room(2)
|
||||
.sometimes(add(note("12")))
|
||||
.lpf(perlin.range(200,20000).slow(4))`,
|
||||
);
|
||||
document.getElementById('strudel').append(repl);
|
||||
</script>
|
||||
```
|
||||
|
||||
## Interacting with the REPL
|
||||
|
||||
If you get a hold of the `strudel-editor` element, you can interact with the strudel REPL from Javascript:
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/@strudel/repl@latest"></script>
|
||||
<strudel-editor id="repl">
|
||||
<!-- ... -->
|
||||
</strudel-editor>
|
||||
<script>
|
||||
const repl = document.getElementById('repl');
|
||||
console.log(repl.editor);
|
||||
</script>
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/@strudel/repl@latest"></script>
|
||||
<div id="strudel"></div>
|
||||
<script>
|
||||
const repl = document.createElement('strudel-editor');
|
||||
repl.setAttribute('code', `...`);
|
||||
document.getElementById('strudel').append(repl);
|
||||
console.log(repl.editor);
|
||||
</script>
|
||||
```
|
||||
|
||||
The `.editor` property on the `strudel-editor` web component gives you the instance of [StrudelMirror](https://github.com/tidalcycles/strudel/blob/a46bd9b36ea7d31c9f1d3fca484297c7da86893f/packages/codemirror/codemirror.mjs#L124) that runs the REPL.
|
||||
|
||||
For example, you could use `setCode` to change the code from the outside, `start` / `stop` to toggle playback or `evaluate` to evaluate the code.
|
||||
|
||||
@ -186,3 +186,76 @@ export function getVibratoOscillator(param, value, t) {
|
||||
return vibratoOscillator;
|
||||
}
|
||||
}
|
||||
// ConstantSource inherits AudioScheduledSourceNode, which has scheduling abilities
|
||||
// a bit of a hack, but it works very well :)
|
||||
export function webAudioTimeout(audioContext, onComplete, startTime, stopTime) {
|
||||
const constantNode = audioContext.createConstantSource();
|
||||
constantNode.start(startTime);
|
||||
constantNode.stop(stopTime);
|
||||
constantNode.onended = () => {
|
||||
onComplete();
|
||||
};
|
||||
}
|
||||
const mod = (freq, range = 1, type = 'sine') => {
|
||||
const ctx = getAudioContext();
|
||||
const osc = ctx.createOscillator();
|
||||
osc.type = type;
|
||||
osc.frequency.value = freq;
|
||||
osc.start();
|
||||
const g = new GainNode(ctx, { gain: range });
|
||||
osc.connect(g); // -range, range
|
||||
return { node: g, stop: (t) => osc.stop(t) };
|
||||
};
|
||||
const fm = (frequencyparam, harmonicityRatio, modulationIndex, wave = 'sine') => {
|
||||
const carrfreq = frequencyparam.value;
|
||||
const modfreq = carrfreq * harmonicityRatio;
|
||||
const modgain = modfreq * modulationIndex;
|
||||
return mod(modfreq, modgain, wave);
|
||||
};
|
||||
export function applyFM(param, value, begin) {
|
||||
const {
|
||||
fmh: fmHarmonicity = 1,
|
||||
fmi: fmModulationIndex,
|
||||
fmenv: fmEnvelopeType = 'exp',
|
||||
fmattack: fmAttack,
|
||||
fmdecay: fmDecay,
|
||||
fmsustain: fmSustain,
|
||||
fmrelease: fmRelease,
|
||||
fmvelocity: fmVelocity,
|
||||
fmwave: fmWaveform = 'sine',
|
||||
duration,
|
||||
} = value;
|
||||
let modulator;
|
||||
let stop = () => {};
|
||||
|
||||
if (fmModulationIndex) {
|
||||
const ac = getAudioContext();
|
||||
const envGain = ac.createGain();
|
||||
const fmmod = fm(param, fmHarmonicity, fmModulationIndex, fmWaveform);
|
||||
|
||||
modulator = fmmod.node;
|
||||
stop = fmmod.stop;
|
||||
if (![fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity].find((v) => v !== undefined)) {
|
||||
// no envelope by default
|
||||
modulator.connect(param);
|
||||
} else {
|
||||
const [attack, decay, sustain, release] = getADSRValues([fmAttack, fmDecay, fmSustain, fmRelease]);
|
||||
const holdEnd = begin + duration;
|
||||
getParamADSR(
|
||||
envGain.gain,
|
||||
attack,
|
||||
decay,
|
||||
sustain,
|
||||
release,
|
||||
0,
|
||||
1,
|
||||
begin,
|
||||
holdEnd,
|
||||
fmEnvelopeType === 'exp' ? 'exponential' : 'linear',
|
||||
);
|
||||
modulator.connect(envGain);
|
||||
envGain.connect(param);
|
||||
}
|
||||
}
|
||||
return { stop };
|
||||
}
|
||||
|
||||
@ -50,8 +50,8 @@ function loadWorklets() {
|
||||
return workletsLoading;
|
||||
}
|
||||
|
||||
function getWorklet(ac, processor, params) {
|
||||
const node = new AudioWorkletNode(ac, processor);
|
||||
export function getWorklet(ac, processor, params, config) {
|
||||
const node = new AudioWorkletNode(ac, processor, config);
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
node.parameters.get(key).value = value;
|
||||
});
|
||||
|
||||
@ -1,31 +1,138 @@
|
||||
import { midiToFreq, noteToMidi } from './util.mjs';
|
||||
import { registerSound, getAudioContext } from './superdough.mjs';
|
||||
import { gainNode, getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs';
|
||||
import { clamp, midiToFreq, noteToMidi } from './util.mjs';
|
||||
import { registerSound, getAudioContext, getWorklet } from './superdough.mjs';
|
||||
import {
|
||||
applyFM,
|
||||
gainNode,
|
||||
getADSRValues,
|
||||
getParamADSR,
|
||||
getPitchEnvelope,
|
||||
getVibratoOscillator,
|
||||
webAudioTimeout,
|
||||
} from './helpers.mjs';
|
||||
import { getNoiseMix, getNoiseOscillator } from './noise.mjs';
|
||||
|
||||
const mod = (freq, range = 1, type = 'sine') => {
|
||||
const ctx = getAudioContext();
|
||||
const osc = ctx.createOscillator();
|
||||
osc.type = type;
|
||||
osc.frequency.value = freq;
|
||||
osc.start();
|
||||
const g = new GainNode(ctx, { gain: range });
|
||||
osc.connect(g); // -range, range
|
||||
return { node: g, stop: (t) => osc.stop(t) };
|
||||
const getFrequencyFromValue = (value) => {
|
||||
let { note, freq } = value;
|
||||
note = note || 36;
|
||||
if (typeof note === 'string') {
|
||||
note = noteToMidi(note); // e.g. c3 => 48
|
||||
}
|
||||
// get frequency
|
||||
if (!freq && typeof note === 'number') {
|
||||
freq = midiToFreq(note); // + 48);
|
||||
}
|
||||
|
||||
return Number(freq);
|
||||
};
|
||||
|
||||
const fm = (osc, harmonicityRatio, modulationIndex, wave = 'sine') => {
|
||||
const carrfreq = osc.frequency.value;
|
||||
const modfreq = carrfreq * harmonicityRatio;
|
||||
const modgain = modfreq * modulationIndex;
|
||||
return mod(modfreq, modgain, wave);
|
||||
};
|
||||
|
||||
const waveforms = ['sine', 'square', 'triangle', 'sawtooth'];
|
||||
const waveforms = ['triangle', 'square', 'sawtooth', 'sine'];
|
||||
const noises = ['pink', 'white', 'brown', 'crackle'];
|
||||
|
||||
export function registerSynthSounds() {
|
||||
[...waveforms, ...noises].forEach((s) => {
|
||||
[...waveforms].forEach((s) => {
|
||||
registerSound(
|
||||
s,
|
||||
(t, value, onended) => {
|
||||
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 = getOscillator(s, t, value);
|
||||
let { node: o, stop, triggerRelease } = sound;
|
||||
|
||||
// turn down
|
||||
const g = gainNode(0.3);
|
||||
|
||||
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,
|
||||
stop: (releaseTime) => {},
|
||||
};
|
||||
},
|
||||
{ type: 'synth', prebake: true },
|
||||
);
|
||||
});
|
||||
registerSound(
|
||||
'supersaw',
|
||||
(begin, value, onended) => {
|
||||
const ac = getAudioContext();
|
||||
let { duration, n, unison = 5, spread = 0.6, detune } = value;
|
||||
detune = detune ?? n ?? 0.18;
|
||||
const frequency = getFrequencyFromValue(value);
|
||||
|
||||
const [attack, decay, sustain, release] = getADSRValues(
|
||||
[value.attack, value.decay, value.sustain, value.release],
|
||||
'linear',
|
||||
[0.001, 0.05, 0.6, 0.01],
|
||||
);
|
||||
|
||||
const holdend = begin + duration;
|
||||
const end = holdend + release + 0.01;
|
||||
const voices = clamp(unison, 1, 100);
|
||||
let panspread = voices > 1 ? clamp(spread, 0, 1) : 0;
|
||||
let o = getWorklet(
|
||||
ac,
|
||||
'supersaw-oscillator',
|
||||
{
|
||||
frequency,
|
||||
begin,
|
||||
end,
|
||||
freqspread: detune,
|
||||
voices,
|
||||
panspread,
|
||||
},
|
||||
{
|
||||
outputChannelCount: [2],
|
||||
},
|
||||
);
|
||||
|
||||
const gainAdjustment = 1 / Math.sqrt(voices);
|
||||
getPitchEnvelope(o.parameters.get('detune'), value, begin, holdend);
|
||||
const vibratoOscillator = getVibratoOscillator(o.parameters.get('detune'), value, begin);
|
||||
const fm = applyFM(o.parameters.get('frequency'), value, begin);
|
||||
let envGain = gainNode(1);
|
||||
envGain = o.connect(envGain);
|
||||
|
||||
webAudioTimeout(
|
||||
ac,
|
||||
() => {
|
||||
o.disconnect();
|
||||
envGain.disconnect();
|
||||
onended();
|
||||
fm?.stop();
|
||||
vibratoOscillator?.stop();
|
||||
},
|
||||
begin,
|
||||
end,
|
||||
);
|
||||
|
||||
getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 0.3 * gainAdjustment, begin, holdend, 'linear');
|
||||
|
||||
return {
|
||||
node: envGain,
|
||||
stop: (time) => {},
|
||||
};
|
||||
},
|
||||
{ prebake: true, type: 'synth' },
|
||||
);
|
||||
|
||||
[...noises].forEach((s) => {
|
||||
registerSound(
|
||||
s,
|
||||
(t, value, onended) => {
|
||||
@ -36,12 +143,9 @@ export function registerSynthSounds() {
|
||||
);
|
||||
|
||||
let sound;
|
||||
if (waveforms.includes(s)) {
|
||||
sound = getOscillator(s, t, value);
|
||||
} else {
|
||||
let { density } = value;
|
||||
sound = getNoiseOscillator(s, t, density);
|
||||
}
|
||||
|
||||
let { density } = value;
|
||||
sound = getNoiseOscillator(s, t, density);
|
||||
|
||||
let { node: o, stop, triggerRelease } = sound;
|
||||
|
||||
@ -106,24 +210,7 @@ export function waveformN(partials, type) {
|
||||
|
||||
// expects one of waveforms as s
|
||||
export function getOscillator(s, t, value) {
|
||||
let {
|
||||
n: partials,
|
||||
note,
|
||||
freq,
|
||||
noise = 0,
|
||||
// fm
|
||||
fmh: fmHarmonicity = 1,
|
||||
fmi: fmModulationIndex,
|
||||
fmenv: fmEnvelopeType = 'exp',
|
||||
fmattack: fmAttack,
|
||||
fmdecay: fmDecay,
|
||||
fmsustain: fmSustain,
|
||||
fmrelease: fmRelease,
|
||||
fmvelocity: fmVelocity,
|
||||
fmwave: fmWaveform = 'sine',
|
||||
duration,
|
||||
} = value;
|
||||
let ac = getAudioContext();
|
||||
let { n: partials, duration, noise = 0 } = value;
|
||||
let o;
|
||||
// If no partials are given, use stock waveforms
|
||||
if (!partials || s === 'sine') {
|
||||
@ -134,55 +221,15 @@ export function getOscillator(s, t, value) {
|
||||
else {
|
||||
o = waveformN(partials, s);
|
||||
}
|
||||
|
||||
// get frequency from note...
|
||||
note = note || 36;
|
||||
if (typeof note === 'string') {
|
||||
note = noteToMidi(note); // e.g. c3 => 48
|
||||
}
|
||||
// get frequency
|
||||
if (!freq && typeof note === 'number') {
|
||||
freq = midiToFreq(note); // + 48);
|
||||
}
|
||||
|
||||
// set frequency
|
||||
o.frequency.value = Number(freq);
|
||||
o.frequency.value = getFrequencyFromValue(value);
|
||||
o.start(t);
|
||||
|
||||
// FM
|
||||
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 {
|
||||
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 = getVibratoOscillator(o.detune, value, t);
|
||||
|
||||
// pitch envelope
|
||||
getPitchEnvelope(o.detune, value, t, t + duration);
|
||||
const fmModulator = applyFM(o.frequency, value, t);
|
||||
|
||||
let noiseMix;
|
||||
if (noise) {
|
||||
@ -192,9 +239,9 @@ export function getOscillator(s, t, value) {
|
||||
return {
|
||||
node: noiseMix?.node || o,
|
||||
stop: (time) => {
|
||||
fmModulator.stop(time);
|
||||
vibratoOscillator?.stop(time);
|
||||
noiseMix?.stop(time);
|
||||
stopFm?.(time);
|
||||
o.stop(time);
|
||||
},
|
||||
triggerRelease: (time) => {
|
||||
|
||||
@ -129,3 +129,148 @@ class DistortProcessor extends AudioWorkletProcessor {
|
||||
}
|
||||
}
|
||||
registerProcessor('distort-processor', DistortProcessor);
|
||||
|
||||
// adjust waveshape to remove frequencies above nyquist to prevent aliasing
|
||||
// referenced from https://www.kvraudio.com/forum/viewtopic.php?t=375517
|
||||
const polyBlep = (phase, dt) => {
|
||||
// 0 <= phase < 1
|
||||
if (phase < dt) {
|
||||
phase /= dt;
|
||||
// 2 * (phase - phase^2/2 - 0.5)
|
||||
return phase + phase - phase * phase - 1;
|
||||
}
|
||||
|
||||
// -1 < phase < 0
|
||||
else if (phase > 1 - dt) {
|
||||
phase = (phase - 1) / dt;
|
||||
// 2 * (phase^2/2 + phase + 0.5)
|
||||
return phase * phase + phase + phase + 1;
|
||||
}
|
||||
|
||||
// 0 otherwise
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
const saw = (phase, dt) => {
|
||||
const v = 2 * phase - 1;
|
||||
return v - polyBlep(phase, dt);
|
||||
};
|
||||
|
||||
function lerp(a, b, n) {
|
||||
return n * (b - a) + a;
|
||||
}
|
||||
|
||||
function getUnisonDetune(unison, detune, voiceIndex) {
|
||||
if (unison < 2) {
|
||||
return 0;
|
||||
}
|
||||
return lerp(-detune * 0.5, detune * 0.5, voiceIndex / (unison - 1));
|
||||
}
|
||||
class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
||||
constructor() {
|
||||
super();
|
||||
this.phase = [];
|
||||
}
|
||||
static get parameterDescriptors() {
|
||||
return [
|
||||
{
|
||||
name: 'begin',
|
||||
defaultValue: 0,
|
||||
max: Number.POSITIVE_INFINITY,
|
||||
min: 0,
|
||||
},
|
||||
|
||||
{
|
||||
name: 'end',
|
||||
defaultValue: 0,
|
||||
max: Number.POSITIVE_INFINITY,
|
||||
min: 0,
|
||||
},
|
||||
|
||||
{
|
||||
name: 'frequency',
|
||||
defaultValue: 440,
|
||||
min: Number.EPSILON,
|
||||
},
|
||||
|
||||
{
|
||||
name: 'panspread',
|
||||
defaultValue: 0.4,
|
||||
min: 0,
|
||||
max: 1,
|
||||
},
|
||||
{
|
||||
name: 'freqspread',
|
||||
defaultValue: 0.2,
|
||||
min: 0,
|
||||
},
|
||||
{
|
||||
name: 'detune',
|
||||
defaultValue: 0,
|
||||
min: 0,
|
||||
},
|
||||
|
||||
{
|
||||
name: 'voices',
|
||||
defaultValue: 5,
|
||||
min: 1,
|
||||
},
|
||||
];
|
||||
}
|
||||
process(input, outputs, params) {
|
||||
// eslint-disable-next-line no-undef
|
||||
if (currentTime <= params.begin[0]) {
|
||||
return true;
|
||||
}
|
||||
// eslint-disable-next-line no-undef
|
||||
if (currentTime >= params.end[0]) {
|
||||
// this.port.postMessage({ type: 'onended' });
|
||||
return false;
|
||||
}
|
||||
let frequency = params.frequency[0];
|
||||
//apply detune in cents
|
||||
frequency = frequency * Math.pow(2, params.detune[0] / 1200);
|
||||
|
||||
const output = outputs[0];
|
||||
const voices = params.voices[0];
|
||||
const freqspread = params.freqspread[0];
|
||||
const panspread = params.panspread[0] * 0.5 + 0.5;
|
||||
const gain1 = Math.sqrt(1 - panspread);
|
||||
const gain2 = Math.sqrt(panspread);
|
||||
|
||||
for (let n = 0; n < voices; n++) {
|
||||
const isOdd = (n & 1) == 1;
|
||||
|
||||
//applies unison "spread" detune in semitones
|
||||
const freq = frequency * Math.pow(2, getUnisonDetune(voices, freqspread, n) / 12);
|
||||
let gainL = gain1;
|
||||
let gainR = gain2;
|
||||
// invert right and left gain
|
||||
if (isOdd) {
|
||||
gainL = gain2;
|
||||
gainR = gain1;
|
||||
}
|
||||
// eslint-disable-next-line no-undef
|
||||
const dt = freq / sampleRate;
|
||||
|
||||
for (let i = 0; i < output[0].length; i++) {
|
||||
this.phase[n] = this.phase[n] ?? Math.random();
|
||||
const v = saw(this.phase[n], dt);
|
||||
|
||||
output[0][i] = output[0][i] + v * gainL;
|
||||
output[1][i] = output[1][i] + v * gainR;
|
||||
|
||||
this.phase[n] += dt;
|
||||
|
||||
if (this.phase[n] > 1.0) {
|
||||
this.phase[n] = this.phase[n] - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
registerProcessor('supersaw-oscillator', SuperSawOscillatorProcessor);
|
||||
|
||||
@ -186,18 +186,35 @@ export const scale = register('scale', function (scale, pat) {
|
||||
// legacy..
|
||||
return pure(step);
|
||||
}
|
||||
const asNumber = Number(step);
|
||||
let asNumber = Number(step);
|
||||
let semitones = 0;
|
||||
if (isNaN(asNumber)) {
|
||||
logger(`[tonal] invalid scale step "${step}", expected number`, 'error');
|
||||
return silence;
|
||||
step = String(step);
|
||||
if (!/^[-+]?\d+(#*|b*){1}$/.test(step)) {
|
||||
logger(
|
||||
`[tonal] invalid scale step "${step}", expected number or integer with optional # b suffixes`,
|
||||
'error',
|
||||
);
|
||||
return silence;
|
||||
}
|
||||
const isharp = step.indexOf('#');
|
||||
if (isharp >= 0) {
|
||||
asNumber = Number(step.substring(0, isharp));
|
||||
semitones = step.length - isharp;
|
||||
} else {
|
||||
const iflat = step.indexOf('b');
|
||||
asNumber = Number(step.substring(0, iflat));
|
||||
semitones = iflat - step.length;
|
||||
}
|
||||
}
|
||||
try {
|
||||
let note;
|
||||
if (value.anchor) {
|
||||
if (isObject && value.anchor) {
|
||||
note = stepInNamedScale(asNumber, scale, value.anchor);
|
||||
} else {
|
||||
note = scaleStep(asNumber, scale);
|
||||
}
|
||||
if (semitones != 0) note = Note.transpose(note, Interval.fromSemitones(semitones));
|
||||
value = pure(isObject ? { ...value, note } : note);
|
||||
} catch (err) {
|
||||
logger(`[tonal] ${err.message}`, 'error');
|
||||
|
||||
@ -7,20 +7,17 @@ This package provides an easy to use bundle of multiple strudel packages for the
|
||||
Save this code as a `.html` file and double click it:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<script src="https://unpkg.com/@strudel/web@1.0.3"></script>
|
||||
<button id="play">play</button>
|
||||
<button id="stop">stop</button>
|
||||
<script type="module">
|
||||
import { initStrudel } from 'https://cdn.skypack.dev/@strudel/web@0.8.2';
|
||||
|
||||
<script>
|
||||
initStrudel();
|
||||
document.getElementById('play').addEventListener('click', () => note('<c a f e>(3,8)').play());
|
||||
document.getElementById('play').addEventListener('click', () => note('<c a f e>(3,8)').jux(rev).play());
|
||||
document.getElementById('stop').addEventListener('click', () => hush());
|
||||
</script>
|
||||
```
|
||||
|
||||
With the help of [skypack](https://www.skypack.dev/), you don't need a bundler nor a server.
|
||||
|
||||
As soon as you call `initStrudel()`, all strudel functions are made available.
|
||||
In this case, we are using the `note` function to create a pattern.
|
||||
To actually play the pattern, you have to append `.play()` to the end.
|
||||
@ -79,4 +76,4 @@ There will probably be an escapte hatch for that in the future.
|
||||
|
||||
## More Examples
|
||||
|
||||
Check out the examples folder for more examples, both using plain html and vite!
|
||||
Check out the examples folder for more examples, both using plain html and vite!
|
||||
|
||||
@ -37,10 +37,10 @@
|
||||
"@strudel/mini": "workspace:*",
|
||||
"@strudel/tonal": "workspace:*",
|
||||
"@strudel/transpiler": "workspace:*",
|
||||
"@strudel/webaudio": "workspace:*",
|
||||
"@rollup/plugin-replace": "^5.0.5"
|
||||
"@strudel/webaudio": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^5.0.10"
|
||||
"vite": "^5.0.10",
|
||||
"@rollup/plugin-replace": "^5.0.5"
|
||||
}
|
||||
}
|
||||
|
||||
15
pnpm-lock.yaml
generated
15
pnpm-lock.yaml
generated
@ -33,6 +33,9 @@ importers:
|
||||
'@vitest/ui':
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0(vitest@1.1.0)
|
||||
acorn:
|
||||
specifier: ^8.11.3
|
||||
version: 8.11.3
|
||||
dependency-tree:
|
||||
specifier: ^10.0.9
|
||||
version: 10.0.9
|
||||
@ -449,9 +452,6 @@ importers:
|
||||
|
||||
packages/web:
|
||||
dependencies:
|
||||
'@rollup/plugin-replace':
|
||||
specifier: ^5.0.5
|
||||
version: 5.0.5
|
||||
'@strudel/core':
|
||||
specifier: workspace:*
|
||||
version: link:../core
|
||||
@ -468,6 +468,9 @@ importers:
|
||||
specifier: workspace:*
|
||||
version: link:../webaudio
|
||||
devDependencies:
|
||||
'@rollup/plugin-replace':
|
||||
specifier: ^5.0.5
|
||||
version: 5.0.5
|
||||
vite:
|
||||
specifier: ^5.0.10
|
||||
version: 5.0.10
|
||||
@ -572,9 +575,6 @@ importers:
|
||||
'@strudel/osc':
|
||||
specifier: workspace:*
|
||||
version: link:../packages/osc
|
||||
'@strudel/repl':
|
||||
specifier: workspace:*
|
||||
version: link:../packages/repl
|
||||
'@strudel/serial':
|
||||
specifier: workspace:*
|
||||
version: link:../packages/serial
|
||||
@ -3930,6 +3930,7 @@ packages:
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.1.0
|
||||
magic-string: 0.30.5
|
||||
dev: true
|
||||
|
||||
/@rollup/pluginutils@3.1.0(rollup@2.79.1):
|
||||
resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==}
|
||||
@ -3955,6 +3956,7 @@ packages:
|
||||
'@types/estree': 1.0.0
|
||||
estree-walker: 2.0.2
|
||||
picomatch: 2.3.1
|
||||
dev: true
|
||||
|
||||
/@rollup/rollup-android-arm-eabi@4.13.0:
|
||||
resolution: {integrity: sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==}
|
||||
@ -7478,6 +7480,7 @@ packages:
|
||||
|
||||
/estree-walker@2.0.2:
|
||||
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
||||
dev: true
|
||||
|
||||
/estree-walker@3.0.3:
|
||||
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
||||
|
||||
@ -1939,18 +1939,54 @@ exports[`runs examples > example "delaytime" example index 0 1`] = `
|
||||
|
||||
exports[`runs examples > example "detune" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/3 | n:0 s:superzow octave:3 detune:0 ]",
|
||||
"[ 1/3 → 2/3 | n:3 s:superzow octave:3 detune:0 ]",
|
||||
"[ 2/3 → 1/1 | n:7 s:superzow octave:3 detune:0 ]",
|
||||
"[ 1/1 → 4/3 | n:0 s:superzow octave:3 detune:0.25 ]",
|
||||
"[ 4/3 → 5/3 | n:3 s:superzow octave:3 detune:0.25 ]",
|
||||
"[ 5/3 → 2/1 | n:7 s:superzow octave:3 detune:0.25 ]",
|
||||
"[ 2/1 → 7/3 | n:0 s:superzow octave:3 detune:0.5 ]",
|
||||
"[ 7/3 → 8/3 | n:3 s:superzow octave:3 detune:0.5 ]",
|
||||
"[ 8/3 → 3/1 | n:7 s:superzow octave:3 detune:0.5 ]",
|
||||
"[ 3/1 → 10/3 | n:0 s:superzow octave:3 detune:1 ]",
|
||||
"[ 10/3 → 11/3 | n:3 s:superzow octave:3 detune:1 ]",
|
||||
"[ 11/3 → 4/1 | n:7 s:superzow octave:3 detune:1 ]",
|
||||
"[ 0/1 → 1/12 | note:d s:supersaw detune:0.1 ]",
|
||||
"[ 1/12 → 1/6 | note:f s:supersaw detune:0.1 ]",
|
||||
"[ 1/6 → 1/4 | note:a s:supersaw detune:0.1 ]",
|
||||
"[ 1/4 → 1/3 | note:a# s:supersaw detune:0.1 ]",
|
||||
"[ 1/3 → 5/12 | note:a s:supersaw detune:0.1 ]",
|
||||
"[ 5/12 → 1/2 | note:d3 s:supersaw detune:0.1 ]",
|
||||
"[ 1/2 → 7/12 | note:d s:supersaw detune:0.1 ]",
|
||||
"[ 7/12 → 2/3 | note:f s:supersaw detune:0.1 ]",
|
||||
"[ 2/3 → 3/4 | note:a s:supersaw detune:0.1 ]",
|
||||
"[ 3/4 → 5/6 | note:a# s:supersaw detune:0.1 ]",
|
||||
"[ 5/6 → 11/12 | note:a s:supersaw detune:0.1 ]",
|
||||
"[ 11/12 → 1/1 | note:d3 s:supersaw detune:0.1 ]",
|
||||
"[ 1/1 → 13/12 | note:d s:supersaw detune:0.2 ]",
|
||||
"[ 13/12 → 7/6 | note:f s:supersaw detune:0.2 ]",
|
||||
"[ 7/6 → 5/4 | note:a s:supersaw detune:0.2 ]",
|
||||
"[ 5/4 → 4/3 | note:a# s:supersaw detune:0.2 ]",
|
||||
"[ 4/3 → 17/12 | note:a s:supersaw detune:0.2 ]",
|
||||
"[ 17/12 → 3/2 | note:d3 s:supersaw detune:0.2 ]",
|
||||
"[ 3/2 → 19/12 | note:d s:supersaw detune:0.2 ]",
|
||||
"[ 19/12 → 5/3 | note:f s:supersaw detune:0.2 ]",
|
||||
"[ 5/3 → 7/4 | note:a s:supersaw detune:0.2 ]",
|
||||
"[ 7/4 → 11/6 | note:a# s:supersaw detune:0.2 ]",
|
||||
"[ 11/6 → 23/12 | note:a s:supersaw detune:0.2 ]",
|
||||
"[ 23/12 → 2/1 | note:d3 s:supersaw detune:0.2 ]",
|
||||
"[ 2/1 → 25/12 | note:d s:supersaw detune:0.5 ]",
|
||||
"[ 25/12 → 13/6 | note:f s:supersaw detune:0.5 ]",
|
||||
"[ 13/6 → 9/4 | note:a s:supersaw detune:0.5 ]",
|
||||
"[ 9/4 → 7/3 | note:a# s:supersaw detune:0.5 ]",
|
||||
"[ 7/3 → 29/12 | note:a s:supersaw detune:0.5 ]",
|
||||
"[ 29/12 → 5/2 | note:d3 s:supersaw detune:0.5 ]",
|
||||
"[ 5/2 → 31/12 | note:d s:supersaw detune:0.5 ]",
|
||||
"[ 31/12 → 8/3 | note:f s:supersaw detune:0.5 ]",
|
||||
"[ 8/3 → 11/4 | note:a s:supersaw detune:0.5 ]",
|
||||
"[ 11/4 → 17/6 | note:a# s:supersaw detune:0.5 ]",
|
||||
"[ 17/6 → 35/12 | note:a s:supersaw detune:0.5 ]",
|
||||
"[ 35/12 → 3/1 | note:d3 s:supersaw detune:0.5 ]",
|
||||
"[ 3/1 → 37/12 | note:d s:supersaw detune:24.1 ]",
|
||||
"[ 37/12 → 19/6 | note:f s:supersaw detune:24.1 ]",
|
||||
"[ 19/6 → 13/4 | note:a s:supersaw detune:24.1 ]",
|
||||
"[ 13/4 → 10/3 | note:a# s:supersaw detune:24.1 ]",
|
||||
"[ 10/3 → 41/12 | note:a s:supersaw detune:24.1 ]",
|
||||
"[ 41/12 → 7/2 | note:d3 s:supersaw detune:24.1 ]",
|
||||
"[ 7/2 → 43/12 | note:d s:supersaw detune:24.1 ]",
|
||||
"[ 43/12 → 11/3 | note:f s:supersaw detune:24.1 ]",
|
||||
"[ 11/3 → 15/4 | note:a s:supersaw detune:24.1 ]",
|
||||
"[ 15/4 → 23/6 | note:a# s:supersaw detune:24.1 ]",
|
||||
"[ 23/6 → 47/12 | note:a s:supersaw detune:24.1 ]",
|
||||
"[ 47/12 → 4/1 | note:d3 s:supersaw detune:24.1 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
@ -2406,8 +2442,6 @@ exports[`runs examples > example "fast" example index 0 1`] = `
|
||||
|
||||
exports[`runs examples > example "fastChunk" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | note:C2 ]",
|
||||
"[ 1/4 → 1/2 | note:D2 ]",
|
||||
"[ 1/2 → 3/4 | note:E2 ]",
|
||||
"[ 3/4 → 1/1 | note:F2 ]",
|
||||
"[ 1/1 → 5/4 | note:G2 ]",
|
||||
@ -2416,8 +2450,6 @@ exports[`runs examples > example "fastChunk" example index 0 1`] = `
|
||||
"[ 7/4 → 2/1 | note:C3 ]",
|
||||
"[ 2/1 → 9/4 | note:D3 ]",
|
||||
"[ 9/4 → 5/2 | note:D2 ]",
|
||||
"[ 5/2 → 11/4 | note:E2 ]",
|
||||
"[ 11/4 → 3/1 | note:F2 ]",
|
||||
"[ 3/1 → 13/4 | note:G2 ]",
|
||||
"[ 13/4 → 7/2 | note:A2 ]",
|
||||
"[ 7/2 → 15/4 | note:B2 ]",
|
||||
@ -4738,34 +4770,34 @@ exports[`runs examples > example "phasersweep" example index 0 1`] = `
|
||||
|
||||
exports[`runs examples > example "pianoroll" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | note:C2 s:piano clip:1 ]",
|
||||
"[ (1/4 → 1/3) ⇝ 3/8 | note:C2 s:piano clip:1 ]",
|
||||
"[ 1/4 ⇜ (1/3 → 3/8) | note:A2 s:piano clip:1 ]",
|
||||
"[ 3/8 → 1/2 | note:A2 s:piano clip:1 ]",
|
||||
"[ (5/8 → 2/3) ⇝ 3/4 | note:A2 s:piano clip:1 ]",
|
||||
"[ 5/8 ⇜ (2/3 → 3/4) | note:G2 s:piano clip:1 ]",
|
||||
"[ 3/4 → 7/8 | note:G2 s:piano clip:1 ]",
|
||||
"[ 1/1 → 9/8 | note:C2 s:piano clip:1 ]",
|
||||
"[ (5/4 → 4/3) ⇝ 11/8 | note:C2 s:piano clip:1 ]",
|
||||
"[ 5/4 ⇜ (4/3 → 11/8) | note:A2 s:piano clip:1 ]",
|
||||
"[ 11/8 → 3/2 | note:A2 s:piano clip:1 ]",
|
||||
"[ (13/8 → 5/3) ⇝ 7/4 | note:A2 s:piano clip:1 ]",
|
||||
"[ 13/8 ⇜ (5/3 → 7/4) | note:G2 s:piano clip:1 ]",
|
||||
"[ 7/4 → 15/8 | note:G2 s:piano clip:1 ]",
|
||||
"[ 2/1 → 17/8 | note:C2 s:piano clip:1 ]",
|
||||
"[ (9/4 → 7/3) ⇝ 19/8 | note:C2 s:piano clip:1 ]",
|
||||
"[ 9/4 ⇜ (7/3 → 19/8) | note:A2 s:piano clip:1 ]",
|
||||
"[ 19/8 → 5/2 | note:A2 s:piano clip:1 ]",
|
||||
"[ (21/8 → 8/3) ⇝ 11/4 | note:A2 s:piano clip:1 ]",
|
||||
"[ 21/8 ⇜ (8/3 → 11/4) | note:G2 s:piano clip:1 ]",
|
||||
"[ 11/4 → 23/8 | note:G2 s:piano clip:1 ]",
|
||||
"[ 3/1 → 25/8 | note:C2 s:piano clip:1 ]",
|
||||
"[ (13/4 → 10/3) ⇝ 27/8 | note:C2 s:piano clip:1 ]",
|
||||
"[ 13/4 ⇜ (10/3 → 27/8) | note:A2 s:piano clip:1 ]",
|
||||
"[ 27/8 → 7/2 | note:A2 s:piano clip:1 ]",
|
||||
"[ (29/8 → 11/3) ⇝ 15/4 | note:A2 s:piano clip:1 ]",
|
||||
"[ 29/8 ⇜ (11/3 → 15/4) | note:G2 s:piano clip:1 ]",
|
||||
"[ 15/4 → 31/8 | note:G2 s:piano clip:1 ]",
|
||||
"[ 0/1 → 1/8 | note:C2 s:piano clip:1 color:salmon ]",
|
||||
"[ (1/4 → 1/3) ⇝ 3/8 | note:C2 s:piano clip:1 color:salmon ]",
|
||||
"[ 1/4 ⇜ (1/3 → 3/8) | note:A2 s:piano clip:1 color:salmon ]",
|
||||
"[ 3/8 → 1/2 | note:A2 s:piano clip:1 color:salmon ]",
|
||||
"[ (5/8 → 2/3) ⇝ 3/4 | note:A2 s:piano clip:1 color:salmon ]",
|
||||
"[ 5/8 ⇜ (2/3 → 3/4) | note:G2 s:piano clip:1 color:salmon ]",
|
||||
"[ 3/4 → 7/8 | note:G2 s:piano clip:1 color:salmon ]",
|
||||
"[ 1/1 → 9/8 | note:C2 s:piano clip:1 color:salmon ]",
|
||||
"[ (5/4 → 4/3) ⇝ 11/8 | note:C2 s:piano clip:1 color:salmon ]",
|
||||
"[ 5/4 ⇜ (4/3 → 11/8) | note:A2 s:piano clip:1 color:salmon ]",
|
||||
"[ 11/8 → 3/2 | note:A2 s:piano clip:1 color:salmon ]",
|
||||
"[ (13/8 → 5/3) ⇝ 7/4 | note:A2 s:piano clip:1 color:salmon ]",
|
||||
"[ 13/8 ⇜ (5/3 → 7/4) | note:G2 s:piano clip:1 color:salmon ]",
|
||||
"[ 7/4 → 15/8 | note:G2 s:piano clip:1 color:salmon ]",
|
||||
"[ 2/1 → 17/8 | note:C2 s:piano clip:1 color:salmon ]",
|
||||
"[ (9/4 → 7/3) ⇝ 19/8 | note:C2 s:piano clip:1 color:salmon ]",
|
||||
"[ 9/4 ⇜ (7/3 → 19/8) | note:A2 s:piano clip:1 color:salmon ]",
|
||||
"[ 19/8 → 5/2 | note:A2 s:piano clip:1 color:salmon ]",
|
||||
"[ (21/8 → 8/3) ⇝ 11/4 | note:A2 s:piano clip:1 color:salmon ]",
|
||||
"[ 21/8 ⇜ (8/3 → 11/4) | note:G2 s:piano clip:1 color:salmon ]",
|
||||
"[ 11/4 → 23/8 | note:G2 s:piano clip:1 color:salmon ]",
|
||||
"[ 3/1 → 25/8 | note:C2 s:piano clip:1 color:salmon ]",
|
||||
"[ (13/4 → 10/3) ⇝ 27/8 | note:C2 s:piano clip:1 color:salmon ]",
|
||||
"[ 13/4 ⇜ (10/3 → 27/8) | note:A2 s:piano clip:1 color:salmon ]",
|
||||
"[ 27/8 → 7/2 | note:A2 s:piano clip:1 color:salmon ]",
|
||||
"[ (29/8 → 11/3) ⇝ 15/4 | note:A2 s:piano clip:1 color:salmon ]",
|
||||
"[ 29/8 ⇜ (11/3 → 15/4) | note:G2 s:piano clip:1 color:salmon ]",
|
||||
"[ 15/4 → 31/8 | note:G2 s:piano clip:1 color:salmon ]",
|
||||
]
|
||||
`;
|
||||
|
||||
@ -6806,6 +6838,59 @@ exports[`runs examples > example "splice" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "spread" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/12 | note:d s:supersaw spread:0 ]",
|
||||
"[ 1/12 → 1/6 | note:f s:supersaw spread:0 ]",
|
||||
"[ 1/6 → 1/4 | note:a s:supersaw spread:0 ]",
|
||||
"[ 1/4 → 1/3 | note:a# s:supersaw spread:0 ]",
|
||||
"[ 1/3 → 5/12 | note:a s:supersaw spread:0 ]",
|
||||
"[ 5/12 → 1/2 | note:d3 s:supersaw spread:0 ]",
|
||||
"[ 1/2 → 7/12 | note:d s:supersaw spread:0 ]",
|
||||
"[ 7/12 → 2/3 | note:f s:supersaw spread:0 ]",
|
||||
"[ 2/3 → 3/4 | note:a s:supersaw spread:0 ]",
|
||||
"[ 3/4 → 5/6 | note:a# s:supersaw spread:0 ]",
|
||||
"[ 5/6 → 11/12 | note:a s:supersaw spread:0 ]",
|
||||
"[ 11/12 → 1/1 | note:d3 s:supersaw spread:0 ]",
|
||||
"[ 1/1 → 13/12 | note:d s:supersaw spread:0.3 ]",
|
||||
"[ 13/12 → 7/6 | note:f s:supersaw spread:0.3 ]",
|
||||
"[ 7/6 → 5/4 | note:a s:supersaw spread:0.3 ]",
|
||||
"[ 5/4 → 4/3 | note:a# s:supersaw spread:0.3 ]",
|
||||
"[ 4/3 → 17/12 | note:a s:supersaw spread:0.3 ]",
|
||||
"[ 17/12 → 3/2 | note:d3 s:supersaw spread:0.3 ]",
|
||||
"[ 3/2 → 19/12 | note:d s:supersaw spread:0.3 ]",
|
||||
"[ 19/12 → 5/3 | note:f s:supersaw spread:0.3 ]",
|
||||
"[ 5/3 → 7/4 | note:a s:supersaw spread:0.3 ]",
|
||||
"[ 7/4 → 11/6 | note:a# s:supersaw spread:0.3 ]",
|
||||
"[ 11/6 → 23/12 | note:a s:supersaw spread:0.3 ]",
|
||||
"[ 23/12 → 2/1 | note:d3 s:supersaw spread:0.3 ]",
|
||||
"[ 2/1 → 25/12 | note:d s:supersaw spread:1 ]",
|
||||
"[ 25/12 → 13/6 | note:f s:supersaw spread:1 ]",
|
||||
"[ 13/6 → 9/4 | note:a s:supersaw spread:1 ]",
|
||||
"[ 9/4 → 7/3 | note:a# s:supersaw spread:1 ]",
|
||||
"[ 7/3 → 29/12 | note:a s:supersaw spread:1 ]",
|
||||
"[ 29/12 → 5/2 | note:d3 s:supersaw spread:1 ]",
|
||||
"[ 5/2 → 31/12 | note:d s:supersaw spread:1 ]",
|
||||
"[ 31/12 → 8/3 | note:f s:supersaw spread:1 ]",
|
||||
"[ 8/3 → 11/4 | note:a s:supersaw spread:1 ]",
|
||||
"[ 11/4 → 17/6 | note:a# s:supersaw spread:1 ]",
|
||||
"[ 17/6 → 35/12 | note:a s:supersaw spread:1 ]",
|
||||
"[ 35/12 → 3/1 | note:d3 s:supersaw spread:1 ]",
|
||||
"[ 3/1 → 37/12 | note:d s:supersaw spread:0 ]",
|
||||
"[ 37/12 → 19/6 | note:f s:supersaw spread:0 ]",
|
||||
"[ 19/6 → 13/4 | note:a s:supersaw spread:0 ]",
|
||||
"[ 13/4 → 10/3 | note:a# s:supersaw spread:0 ]",
|
||||
"[ 10/3 → 41/12 | note:a s:supersaw spread:0 ]",
|
||||
"[ 41/12 → 7/2 | note:d3 s:supersaw spread:0 ]",
|
||||
"[ 7/2 → 43/12 | note:d s:supersaw spread:0 ]",
|
||||
"[ 43/12 → 11/3 | note:f s:supersaw spread:0 ]",
|
||||
"[ 11/3 → 15/4 | note:a s:supersaw spread:0 ]",
|
||||
"[ 15/4 → 23/6 | note:a# s:supersaw spread:0 ]",
|
||||
"[ 23/6 → 47/12 | note:a s:supersaw spread:0 ]",
|
||||
"[ 47/12 → 4/1 | note:d3 s:supersaw spread:0 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "square" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | note:C3 ]",
|
||||
@ -7267,6 +7352,59 @@ exports[`runs examples > example "undegradeBy" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "unison" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/12 | note:d s:supersaw unison:1 ]",
|
||||
"[ 1/12 → 1/6 | note:f s:supersaw unison:1 ]",
|
||||
"[ 1/6 → 1/4 | note:a s:supersaw unison:1 ]",
|
||||
"[ 1/4 → 1/3 | note:a# s:supersaw unison:1 ]",
|
||||
"[ 1/3 → 5/12 | note:a s:supersaw unison:1 ]",
|
||||
"[ 5/12 → 1/2 | note:d3 s:supersaw unison:1 ]",
|
||||
"[ 1/2 → 7/12 | note:d s:supersaw unison:1 ]",
|
||||
"[ 7/12 → 2/3 | note:f s:supersaw unison:1 ]",
|
||||
"[ 2/3 → 3/4 | note:a s:supersaw unison:1 ]",
|
||||
"[ 3/4 → 5/6 | note:a# s:supersaw unison:1 ]",
|
||||
"[ 5/6 → 11/12 | note:a s:supersaw unison:1 ]",
|
||||
"[ 11/12 → 1/1 | note:d3 s:supersaw unison:1 ]",
|
||||
"[ 1/1 → 13/12 | note:d s:supersaw unison:2 ]",
|
||||
"[ 13/12 → 7/6 | note:f s:supersaw unison:2 ]",
|
||||
"[ 7/6 → 5/4 | note:a s:supersaw unison:2 ]",
|
||||
"[ 5/4 → 4/3 | note:a# s:supersaw unison:2 ]",
|
||||
"[ 4/3 → 17/12 | note:a s:supersaw unison:2 ]",
|
||||
"[ 17/12 → 3/2 | note:d3 s:supersaw unison:2 ]",
|
||||
"[ 3/2 → 19/12 | note:d s:supersaw unison:2 ]",
|
||||
"[ 19/12 → 5/3 | note:f s:supersaw unison:2 ]",
|
||||
"[ 5/3 → 7/4 | note:a s:supersaw unison:2 ]",
|
||||
"[ 7/4 → 11/6 | note:a# s:supersaw unison:2 ]",
|
||||
"[ 11/6 → 23/12 | note:a s:supersaw unison:2 ]",
|
||||
"[ 23/12 → 2/1 | note:d3 s:supersaw unison:2 ]",
|
||||
"[ 2/1 → 25/12 | note:d s:supersaw unison:7 ]",
|
||||
"[ 25/12 → 13/6 | note:f s:supersaw unison:7 ]",
|
||||
"[ 13/6 → 9/4 | note:a s:supersaw unison:7 ]",
|
||||
"[ 9/4 → 7/3 | note:a# s:supersaw unison:7 ]",
|
||||
"[ 7/3 → 29/12 | note:a s:supersaw unison:7 ]",
|
||||
"[ 29/12 → 5/2 | note:d3 s:supersaw unison:7 ]",
|
||||
"[ 5/2 → 31/12 | note:d s:supersaw unison:7 ]",
|
||||
"[ 31/12 → 8/3 | note:f s:supersaw unison:7 ]",
|
||||
"[ 8/3 → 11/4 | note:a s:supersaw unison:7 ]",
|
||||
"[ 11/4 → 17/6 | note:a# s:supersaw unison:7 ]",
|
||||
"[ 17/6 → 35/12 | note:a s:supersaw unison:7 ]",
|
||||
"[ 35/12 → 3/1 | note:d3 s:supersaw unison:7 ]",
|
||||
"[ 3/1 → 37/12 | note:d s:supersaw unison:1 ]",
|
||||
"[ 37/12 → 19/6 | note:f s:supersaw unison:1 ]",
|
||||
"[ 19/6 → 13/4 | note:a s:supersaw unison:1 ]",
|
||||
"[ 13/4 → 10/3 | note:a# s:supersaw unison:1 ]",
|
||||
"[ 10/3 → 41/12 | note:a s:supersaw unison:1 ]",
|
||||
"[ 41/12 → 7/2 | note:d3 s:supersaw unison:1 ]",
|
||||
"[ 7/2 → 43/12 | note:d s:supersaw unison:1 ]",
|
||||
"[ 43/12 → 11/3 | note:f s:supersaw unison:1 ]",
|
||||
"[ 11/3 → 15/4 | note:a s:supersaw unison:1 ]",
|
||||
"[ 15/4 → 23/6 | note:a# s:supersaw unison:1 ]",
|
||||
"[ 23/6 → 47/12 | note:a s:supersaw unison:1 ]",
|
||||
"[ 47/12 → 4/1 | note:d3 s:supersaw unison:1 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "unit" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | speed:1 s:bd unit:c ]",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -222,7 +222,6 @@ export const testCycles = {
|
||||
randomBells: 24,
|
||||
waa: 16,
|
||||
waar: 16,
|
||||
hyperpop: 10,
|
||||
festivalOfFingers3: 16,
|
||||
};
|
||||
|
||||
|
||||
@ -1,6 +1,86 @@
|
||||
{
|
||||
"/doc.json": [
|
||||
"acorn parse error: SyntaxError: undefined"
|
||||
],
|
||||
"/packages/codemirror/html.mjs": [
|
||||
"html",
|
||||
"h"
|
||||
],
|
||||
"/packages/codemirror/autocomplete.mjs": [
|
||||
"Autocomplete",
|
||||
"strudelAutocomplete",
|
||||
"isAutoCompletionEnabled"
|
||||
],
|
||||
"/packages/codemirror/tooltip.mjs": [
|
||||
"strudelTooltip",
|
||||
"isTooltipEnabled"
|
||||
],
|
||||
"/packages/codemirror/flash.mjs": [
|
||||
"setFlash",
|
||||
"flashField",
|
||||
"flash",
|
||||
"isFlashEnabled"
|
||||
],
|
||||
"/packages/codemirror/highlight.mjs": [
|
||||
"setMiniLocations",
|
||||
"showMiniLocations",
|
||||
"updateMiniLocations",
|
||||
"highlightMiniLocations",
|
||||
"highlightExtension",
|
||||
"isPatternHighlightingEnabled"
|
||||
],
|
||||
"/packages/codemirror/keybindings.mjs": [
|
||||
"keybindings"
|
||||
],
|
||||
"/packages/codemirror/themes/strudel-theme.mjs": [],
|
||||
"/packages/codemirror/themes/bluescreen.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/blackscreen.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/whitescreen.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/teletext.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/algoboy.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes/terminal.mjs": [
|
||||
"settings"
|
||||
],
|
||||
"/packages/codemirror/themes.mjs": [
|
||||
"themes",
|
||||
"settings",
|
||||
"themeColors",
|
||||
"theme",
|
||||
"injectStyle",
|
||||
"initTheme",
|
||||
"activateTheme"
|
||||
],
|
||||
"/packages/codemirror/slider.mjs": [
|
||||
"acorn parse error: SyntaxError: undefined"
|
||||
],
|
||||
"/packages/codemirror/widget.mjs": [
|
||||
"addWidget",
|
||||
"updateWidgets",
|
||||
"setWidget",
|
||||
"BlockWidget",
|
||||
"widgetPlugin",
|
||||
"registerWidget"
|
||||
],
|
||||
"/packages/codemirror/codemirror.mjs": [
|
||||
"defaultSettings",
|
||||
"codemirrorSettings",
|
||||
"initEditor",
|
||||
"StrudelMirror"
|
||||
],
|
||||
"/packages/codemirror/index.mjs": [],
|
||||
"/packages/core/fraction.mjs": [
|
||||
"gcd"
|
||||
"gcd",
|
||||
"lcm"
|
||||
],
|
||||
"/packages/core/timespan.mjs": [
|
||||
"TimeSpan"
|
||||
@ -11,6 +91,10 @@
|
||||
"/packages/core/state.mjs": [
|
||||
"State"
|
||||
],
|
||||
"/packages/core/logger.mjs": [
|
||||
"logKey",
|
||||
"logger"
|
||||
],
|
||||
"/packages/core/util.mjs": [
|
||||
"isNoteWithOctave",
|
||||
"isNote",
|
||||
@ -20,13 +104,14 @@
|
||||
"freqToMidi",
|
||||
"valueToMidi",
|
||||
"_mod",
|
||||
"nanFallback",
|
||||
"getSoundIndex",
|
||||
"getPlayableNoteValue",
|
||||
"getFrequency",
|
||||
"rotate",
|
||||
"pipe",
|
||||
"compose",
|
||||
"flatten",
|
||||
"id",
|
||||
"constant",
|
||||
"listRange",
|
||||
"curry",
|
||||
@ -42,7 +127,8 @@
|
||||
"unicodeToBase64",
|
||||
"base64ToUnicode",
|
||||
"code2hash",
|
||||
"hash2code"
|
||||
"hash2code",
|
||||
"objectMap"
|
||||
],
|
||||
"/packages/core/value.mjs": [
|
||||
"unionWithObj",
|
||||
@ -51,18 +137,20 @@
|
||||
"map"
|
||||
],
|
||||
"/packages/core/drawLine.mjs": [],
|
||||
"/packages/core/logger.mjs": [
|
||||
"logKey",
|
||||
"logger"
|
||||
],
|
||||
"/packages/core/pattern.mjs": [
|
||||
"setStringParser",
|
||||
"polyrhythm",
|
||||
"pr",
|
||||
"pm",
|
||||
"nothing",
|
||||
"isPattern",
|
||||
"reify",
|
||||
"stackLeft",
|
||||
"stackRight",
|
||||
"stackCentre",
|
||||
"stackBy",
|
||||
"fastcat",
|
||||
"_polymeterListSteps",
|
||||
"set",
|
||||
"keep",
|
||||
"keepif",
|
||||
@ -99,16 +187,195 @@
|
||||
"stutWith",
|
||||
"stutwith",
|
||||
"iterback",
|
||||
"slowchunk",
|
||||
"slowChunk",
|
||||
"chunkback",
|
||||
"fastchunk",
|
||||
"bypass",
|
||||
"duration",
|
||||
"hsla",
|
||||
"hsl",
|
||||
"colour",
|
||||
"loopat",
|
||||
"loopatcps"
|
||||
],
|
||||
"/packages/core/controls.mjs": [],
|
||||
"/packages/core/controls.mjs": [
|
||||
"createParam",
|
||||
"sound",
|
||||
"src",
|
||||
"att",
|
||||
"fmi",
|
||||
"fmrelease",
|
||||
"fmvelocity",
|
||||
"analyze",
|
||||
"fft",
|
||||
"dec",
|
||||
"sus",
|
||||
"rel",
|
||||
"hold",
|
||||
"bandf",
|
||||
"bp",
|
||||
"bandq",
|
||||
"loopb",
|
||||
"loope",
|
||||
"ch",
|
||||
"phaserrate",
|
||||
"phasr",
|
||||
"ph",
|
||||
"phs",
|
||||
"phc",
|
||||
"phd",
|
||||
"phasdp",
|
||||
"cutoff",
|
||||
"ctf",
|
||||
"lp",
|
||||
"lpe",
|
||||
"hpe",
|
||||
"bpe",
|
||||
"lpa",
|
||||
"hpa",
|
||||
"bpa",
|
||||
"lpd",
|
||||
"hpd",
|
||||
"bpd",
|
||||
"lps",
|
||||
"hps",
|
||||
"bps",
|
||||
"lpr",
|
||||
"hpr",
|
||||
"bpr",
|
||||
"fanchor",
|
||||
"vibrato",
|
||||
"v",
|
||||
"vmod",
|
||||
"hcutoff",
|
||||
"hp",
|
||||
"hresonance",
|
||||
"resonance",
|
||||
"delayfb",
|
||||
"dfb",
|
||||
"delayt",
|
||||
"dt",
|
||||
"lock",
|
||||
"det",
|
||||
"fadeTime",
|
||||
"fadeOutTime",
|
||||
"fadeInTime",
|
||||
"patt",
|
||||
"pdec",
|
||||
"psustain",
|
||||
"psus",
|
||||
"prel",
|
||||
"gate",
|
||||
"gat",
|
||||
"activeLabel",
|
||||
"degree",
|
||||
"mtranspose",
|
||||
"ctranspose",
|
||||
"harmonic",
|
||||
"stepsPerOctave",
|
||||
"octaveR",
|
||||
"nudge",
|
||||
"overgain",
|
||||
"overshape",
|
||||
"panspan",
|
||||
"pansplay",
|
||||
"panwidth",
|
||||
"panorient",
|
||||
"rate",
|
||||
"slide",
|
||||
"semitone",
|
||||
"voice",
|
||||
"chord",
|
||||
"dictionary",
|
||||
"dict",
|
||||
"anchor",
|
||||
"offset",
|
||||
"octaves",
|
||||
"mode",
|
||||
"rlp",
|
||||
"rdim",
|
||||
"rfade",
|
||||
"ir",
|
||||
"size",
|
||||
"sz",
|
||||
"rsize",
|
||||
"dist",
|
||||
"compressorKnee",
|
||||
"compressorRatio",
|
||||
"compressorAttack",
|
||||
"compressorRelease",
|
||||
"waveloss",
|
||||
"density",
|
||||
"expression",
|
||||
"sustainpedal",
|
||||
"tremolodepth",
|
||||
"tremdp",
|
||||
"tremolorate",
|
||||
"tremr",
|
||||
"fshift",
|
||||
"fshiftnote",
|
||||
"fshiftphase",
|
||||
"triode",
|
||||
"krush",
|
||||
"kcutoff",
|
||||
"octer",
|
||||
"octersub",
|
||||
"octersubsub",
|
||||
"ring",
|
||||
"ringf",
|
||||
"ringdf",
|
||||
"freeze",
|
||||
"xsdelay",
|
||||
"tsdelay",
|
||||
"real",
|
||||
"imag",
|
||||
"enhance",
|
||||
"partials",
|
||||
"comb",
|
||||
"smear",
|
||||
"scram",
|
||||
"binshift",
|
||||
"hbrick",
|
||||
"lbrick",
|
||||
"midichan",
|
||||
"control",
|
||||
"ccn",
|
||||
"ccv",
|
||||
"polyTouch",
|
||||
"midibend",
|
||||
"miditouch",
|
||||
"ctlNum",
|
||||
"frameRate",
|
||||
"frames",
|
||||
"hours",
|
||||
"midicmd",
|
||||
"minutes",
|
||||
"progNum",
|
||||
"seconds",
|
||||
"songPtr",
|
||||
"uid",
|
||||
"val",
|
||||
"cps",
|
||||
"legato",
|
||||
"dur",
|
||||
"zrand",
|
||||
"curve",
|
||||
"deltaSlide",
|
||||
"pitchJump",
|
||||
"pitchJumpTime",
|
||||
"lfo",
|
||||
"repeatTime",
|
||||
"znoise",
|
||||
"zmod",
|
||||
"zcrush",
|
||||
"zdelay",
|
||||
"tremolo",
|
||||
"zzfx",
|
||||
"colour",
|
||||
"createParams",
|
||||
"ad",
|
||||
"ds",
|
||||
"ar"
|
||||
],
|
||||
"/packages/core/euclid.mjs": [
|
||||
"bjork",
|
||||
"euclidrot"
|
||||
@ -128,6 +395,8 @@
|
||||
"brandBy",
|
||||
"brand",
|
||||
"_irand",
|
||||
"pickSqueeze",
|
||||
"pickmodSqueeze",
|
||||
"__chooseWith",
|
||||
"randcat",
|
||||
"wchoose",
|
||||
@ -143,6 +412,9 @@
|
||||
"evalScope",
|
||||
"evaluate"
|
||||
],
|
||||
"/packages/core/neocyclist.mjs": [
|
||||
"NeoCyclist"
|
||||
],
|
||||
"/packages/core/zyklus.mjs": [],
|
||||
"/packages/core/cyclist.mjs": [
|
||||
"Cyclist"
|
||||
@ -155,31 +427,6 @@
|
||||
"repl",
|
||||
"getTrigger"
|
||||
],
|
||||
"/packages/core/draw.mjs": [
|
||||
"getDrawContext",
|
||||
"cleanupDraw",
|
||||
"Framer",
|
||||
"Drawer"
|
||||
],
|
||||
"/packages/core/animate.mjs": [
|
||||
"x",
|
||||
"y",
|
||||
"w",
|
||||
"h",
|
||||
"angle",
|
||||
"r",
|
||||
"fill",
|
||||
"smear",
|
||||
"rescale",
|
||||
"moveXY",
|
||||
"zoomIn"
|
||||
],
|
||||
"/packages/core/pianoroll.mjs": [
|
||||
"getDrawOptions",
|
||||
"getPunchcardPainter",
|
||||
"drawPianoroll"
|
||||
],
|
||||
"/packages/core/spiral.mjs": [],
|
||||
"/packages/core/ui.mjs": [
|
||||
"backgroundImage",
|
||||
"cleanupUi"
|
||||
@ -199,6 +446,37 @@
|
||||
"/packages/desktopbridge/midibridge.mjs": [],
|
||||
"/packages/desktopbridge/oscbridge.mjs": [],
|
||||
"/packages/desktopbridge/index.mjs": [],
|
||||
"/packages/draw/draw.mjs": [
|
||||
"getDrawContext",
|
||||
"cleanupDraw",
|
||||
"Framer",
|
||||
"Drawer"
|
||||
],
|
||||
"/packages/draw/animate.mjs": [
|
||||
"x",
|
||||
"y",
|
||||
"w",
|
||||
"h",
|
||||
"angle",
|
||||
"r",
|
||||
"fill",
|
||||
"smear",
|
||||
"rescale",
|
||||
"moveXY",
|
||||
"zoomIn"
|
||||
],
|
||||
"/packages/draw/color.mjs": [
|
||||
"colorMap",
|
||||
"convertColorToNumber",
|
||||
"convertHexToNumber"
|
||||
],
|
||||
"/packages/draw/pianoroll.mjs": [
|
||||
"getDrawOptions",
|
||||
"getPunchcardPainter",
|
||||
"drawPianoroll"
|
||||
],
|
||||
"/packages/draw/spiral.mjs": [],
|
||||
"/packages/draw/index.mjs": [],
|
||||
"/packages/midi/midi.mjs": [
|
||||
"WebMidi",
|
||||
"enableWebMidi",
|
||||
@ -219,6 +497,13 @@
|
||||
"miniAllStrings"
|
||||
],
|
||||
"/packages/mini/index.mjs": [],
|
||||
"/packages/repl/prebake.mjs": [
|
||||
"prebake"
|
||||
],
|
||||
"/packages/repl/repl-component.mjs": [
|
||||
"acorn parse error: SyntaxError: undefined"
|
||||
],
|
||||
"/packages/repl/index.mjs": [],
|
||||
"/packages/soundfonts/gm.mjs": [],
|
||||
"/packages/soundfonts/fontloader.mjs": [
|
||||
"getFontBufferSource",
|
||||
@ -234,7 +519,92 @@
|
||||
"loadSoundfont"
|
||||
],
|
||||
"/packages/soundfonts/index.mjs": [],
|
||||
"/packages/tonal/tonal.mjs": [],
|
||||
"/packages/superdough/feedbackdelay.mjs": [],
|
||||
"/packages/superdough/reverbGen.mjs": [],
|
||||
"/packages/superdough/reverb.mjs": [],
|
||||
"/packages/superdough/vowel.mjs": [
|
||||
"vowelFormant"
|
||||
],
|
||||
"/packages/superdough/logger.mjs": [
|
||||
"logger",
|
||||
"setLogger"
|
||||
],
|
||||
"/packages/superdough/util.mjs": [
|
||||
"tokenizeNote",
|
||||
"noteToMidi",
|
||||
"midiToFreq",
|
||||
"clamp",
|
||||
"freqToMidi",
|
||||
"valueToMidi",
|
||||
"nanFallback",
|
||||
"_mod",
|
||||
"getSoundIndex"
|
||||
],
|
||||
"/packages/superdough/helpers.mjs": [
|
||||
"gainNode",
|
||||
"getParamADSR",
|
||||
"getCompressor",
|
||||
"getADSRValues",
|
||||
"createFilter",
|
||||
"drywet",
|
||||
"getPitchEnvelope",
|
||||
"getVibratoOscillator",
|
||||
"webAudioTimeout",
|
||||
"applyFM"
|
||||
],
|
||||
"/packages/superdough/sampler.mjs": [
|
||||
"getCachedBuffer",
|
||||
"getSampleBufferSource",
|
||||
"loadBuffer",
|
||||
"reverseBuffer",
|
||||
"getLoadedBuffer",
|
||||
"processSampleMap",
|
||||
"registerSamplesPrefix",
|
||||
"onTriggerSample"
|
||||
],
|
||||
"/packages/superdough/superdough.mjs": [
|
||||
"soundMap",
|
||||
"registerSound",
|
||||
"getSound",
|
||||
"resetLoadedSounds",
|
||||
"setDefaultAudioContext",
|
||||
"getAudioContext",
|
||||
"getWorklet",
|
||||
"initAudio",
|
||||
"initAudioOnFirstClick",
|
||||
"initializeAudioOutput",
|
||||
"connectToDestination",
|
||||
"panic",
|
||||
"analysers",
|
||||
"analysersData",
|
||||
"getAnalyserById",
|
||||
"getAnalyzerData",
|
||||
"resetGlobalEffects",
|
||||
"superdough",
|
||||
"superdoughTrigger"
|
||||
],
|
||||
"/packages/superdough/noise.mjs": [
|
||||
"getNoiseOscillator",
|
||||
"getNoiseMix"
|
||||
],
|
||||
"/packages/superdough/synth.mjs": [
|
||||
"registerSynthSounds",
|
||||
"waveformN",
|
||||
"getOscillator"
|
||||
],
|
||||
"/packages/superdough/zzfx_fork.mjs": [
|
||||
"acorn parse error: SyntaxError: undefined"
|
||||
],
|
||||
"/packages/superdough/zzfx.mjs": [
|
||||
"getZZFX",
|
||||
"registerZZFXSounds"
|
||||
],
|
||||
"/packages/superdough/dspworklet.mjs": [
|
||||
"dspWorklet",
|
||||
"dough",
|
||||
"doughTrigger"
|
||||
],
|
||||
"/packages/superdough/index.mjs": [],
|
||||
"/packages/tonal/tonleiter.mjs": [
|
||||
"pc2chroma",
|
||||
"rotateChroma",
|
||||
@ -242,30 +612,40 @@
|
||||
"tokenizeChord",
|
||||
"note2pc",
|
||||
"note2oct",
|
||||
"note2midi",
|
||||
"note2chroma",
|
||||
"midi2chroma",
|
||||
"pitch2chroma",
|
||||
"step2semitones",
|
||||
"x2midi",
|
||||
"scaleStep",
|
||||
"nearestNumberIndex",
|
||||
"stepInNamedScale",
|
||||
"renderVoicing",
|
||||
"accidentalOffset",
|
||||
"Step",
|
||||
"Note"
|
||||
],
|
||||
"/packages/tonal/tonal.mjs": [],
|
||||
"/packages/tonal/ireal.mjs": [
|
||||
"simple",
|
||||
"complex"
|
||||
],
|
||||
"/packages/tonal/voicings.mjs": [
|
||||
"voicingRegistry",
|
||||
"setDefaultVoicings",
|
||||
"setVoicingRange",
|
||||
"registerVoicings",
|
||||
"voicingAlias"
|
||||
"voicingAlias",
|
||||
"resetVoicings"
|
||||
],
|
||||
"/packages/tonal/index.mjs": [
|
||||
"packageName"
|
||||
],
|
||||
"/packages/tonal/index.mjs": [],
|
||||
"/packages/transpiler/transpiler.mjs": [
|
||||
"transpiler"
|
||||
"registerWidgetType",
|
||||
"transpiler",
|
||||
"getWidgetID"
|
||||
],
|
||||
"/packages/transpiler/index.mjs": [
|
||||
"evaluate"
|
||||
|
||||
@ -33,7 +33,6 @@
|
||||
"@strudel/midi": "workspace:*",
|
||||
"@strudel/mini": "workspace:*",
|
||||
"@strudel/osc": "workspace:*",
|
||||
"@strudel/repl": "workspace:*",
|
||||
"@strudel/serial": "workspace:*",
|
||||
"@strudel/soundfonts": "workspace:*",
|
||||
"@strudel/tonal": "workspace:*",
|
||||
|
||||
@ -102,9 +102,10 @@ export const SIDEBAR: Sidebar = {
|
||||
{ text: 'Strudel vs Tidal', link: 'learn/strudel-vs-tidal' },
|
||||
],
|
||||
Development: [
|
||||
{ text: 'Strudel in your Project', link: 'technical-manual/project-start' },
|
||||
{ text: 'Packages', link: 'technical-manual/packages' },
|
||||
{ text: 'REPL', link: 'technical-manual/repl' },
|
||||
{ text: 'Sounds', link: 'technical-manual/sounds' },
|
||||
{ text: 'Packages', link: 'technical-manual/packages' },
|
||||
{ text: 'Docs', link: 'technical-manual/docs' },
|
||||
{ text: 'Testing', link: 'technical-manual/testing' },
|
||||
// { text: 'Internals', link: 'technical-manual/internals' },
|
||||
|
||||
@ -5,7 +5,6 @@ import { getPunchcardPainter } from '@strudel/draw';
|
||||
import { transpiler } from '@strudel/transpiler';
|
||||
import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel/webaudio';
|
||||
import { StrudelMirror } from '@strudel/codemirror';
|
||||
// import { prebake } from '@strudel/repl';
|
||||
import { prebake } from '../repl/prebake.mjs';
|
||||
import { loadModules } from '../repl/util.mjs';
|
||||
import Claviature from '@components/Claviature';
|
||||
|
||||
@ -41,8 +41,8 @@ This makes way for other ways to align the pattern, and several are already defi
|
||||
- `mix` - structures from both patterns are combined, so that the new events are not fragments but are created at intersections of events from both sides.
|
||||
- `squeeze` - cycles from the pattern on the right are squeezed into events on the left. So that e.g. `"0 1 2".add.squeeze("10 20")` is equivalent to `"[10 20] [11 21] [12 22]"`.
|
||||
- `squeezeout` - as with `squeeze`, but cycles from the left are squeezed into events on the right. So, `"0 1 2".add.squeezeout("10 20")` is equivalent to `[10 11 12] [20 21 22]`.
|
||||
- `trig` is similar to `squeezeout` in that cycles from the right are aligned with events on the left. However those cycles are not 'squeezed', rather they are truncated to fit the event. So `"0 1 2 3 4 5 6 7".add.trig("10 [20 30]")` would be equivalent to `10 11 12 13 20 21 30 31`. In effect, events on the right 'trigger' cycles on the left.
|
||||
- `trigzero` is similar to `trig`, but the pattern is 'triggered' from its very first cycle, rather than from the current cycle. `trig` and `trigzero` therefore only give different results where the leftmost pattern differs from one cycle to the next.
|
||||
- `reset` is similar to `squeezeout` in that cycles from the right are aligned with events on the left. However those cycles are not 'squeezed', rather they are truncated to fit the event. So `"0 1 2 3 4 5 6 7".add.trig("10 [20 30]")` would be equivalent to `10 11 12 13 20 21 30 31`. In effect, events on the right 'trigger' cycles on the left.
|
||||
- `restart` is similar to `reset`, but the pattern is 'restarted' from its very first cycle, rather than from the current cycle. `reset` and `restart` therefore only give different results where the leftmost pattern differs from one cycle to the next.
|
||||
|
||||
We will save going deeper into the background, design and practicalities of these alignment functions for future publications. However in the next section, we take them as a case study for looking at the different design affordances offered by Haskell to Tidal, and JavaScript to Strudel.
|
||||
|
||||
|
||||
@ -60,13 +60,13 @@ These functions are more low level, probably not needed by the live coder.
|
||||
|
||||
<JsDoc client:idle name="Pattern#innerJoin" h={0} />
|
||||
|
||||
## trigJoin
|
||||
## resetJoin
|
||||
|
||||
<JsDoc client:idle name="Pattern#trigJoin" h={0} />
|
||||
<JsDoc client:idle name="Pattern#resetJoin" h={0} />
|
||||
|
||||
## trigzeroJoin
|
||||
## restartJoin
|
||||
|
||||
<JsDoc client:idle name="Pattern#trigzeroJoin" h={0} />
|
||||
<JsDoc client:idle name="Pattern#restartJoin" h={0} />
|
||||
|
||||
## squeezeJoin
|
||||
|
||||
|
||||
@ -19,6 +19,15 @@ The purpose of the multiple packages is to
|
||||
[See the latest published packages on npm](https://www.npmjs.com/search?q=%40strudel).
|
||||
Here is an overview of all the packages:
|
||||
|
||||
### Umbrella Packages
|
||||
|
||||
These packages give you a batteries-included point of getting started, and most likely the thing you'd want to use in your project:
|
||||
|
||||
- [repl](https://github.com/tidalcycles/strudel/tree/main/packages/repl): The Strudel REPL as a web component.
|
||||
- [web](https://github.com/tidalcycles/strudel/tree/main/packages/web): Strudel library for the browser, without UI.
|
||||
|
||||
To find out more about these two, read [Using Strudel in Your Project](/technical-manual/project-start)
|
||||
|
||||
### Essential Packages
|
||||
|
||||
These package are the most essential. You might want to use all of those if you're using strudel in your project:
|
||||
|
||||
129
website/src/pages/technical-manual/project-start.mdx
Normal file
129
website/src/pages/technical-manual/project-start.mdx
Normal file
@ -0,0 +1,129 @@
|
||||
---
|
||||
title: Using Strudel in your Project
|
||||
layout: ../../layouts/MainLayout.astro
|
||||
---
|
||||
|
||||
# Using Strudel in your Project
|
||||
|
||||
This Guide shows you the different ways to get started with using Strudel in your own project.
|
||||
|
||||
## Embedding the Strudel REPL
|
||||
|
||||
There are 3 quick ways to embed strudel in your website:
|
||||
|
||||
1. Embed the strudel website as an iframe directly
|
||||
2. Embed the strudel website as an iframe using `@strudel/embed`
|
||||
3. Embed the REPL directly using `@strudel/repl`
|
||||
|
||||
### Inside an iframe
|
||||
|
||||
Using an iframe is the most easy way to embed a studel tune.
|
||||
You can embed any pattern of your choice via an iframe and the URL of the pattern of your choice:
|
||||
|
||||
```html
|
||||
<iframe src="https://strudel.cc/?xwWRfuCE8TAR" width="600" height="300"></iframe>
|
||||
```
|
||||
|
||||
The URL can be obtained by pressing `share` in the REPL.
|
||||
Note that these share links depend on a database, which is not guaranteed to live forever.
|
||||
To make sure your code is not lost, you can also use the long url:
|
||||
|
||||
```html
|
||||
<iframe
|
||||
src="https://strudel.cc/#c2V0Y3BzKDEpCm4oIjwwIDEgMiAzIDQ%2BKjgiKS5zY2FsZSgnRzQgbWlub3InKQoucygiZ21fbGVhZF82X3ZvaWNlIikKLmNsaXAoc2luZS5yYW5nZSguMiwuOCkuc2xvdyg4KSkKLmp1eChyZXYpCi5yb29tKDIpCi5zb21ldGltZXMoYWRkKG5vdGUoIjEyIikpKQoubHBmKHBlcmxpbi5yYW5nZSgyMDAsMjAwMDApLnNsb3coNCkp"
|
||||
width="600"
|
||||
height="300"
|
||||
></iframe>
|
||||
```
|
||||
|
||||
That long URL can just be copy pasted from the URL bar when you're on the strudel website. It always reflects the latest evaluation of your code.
|
||||
|
||||
### @strudel/embed
|
||||
|
||||
To simplify the process of emebdding via an iframe, you can use the package `@strudel/embed`:
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/@strudel/embed@latest"></script>
|
||||
<strudel-repl>
|
||||
<!--
|
||||
setcps(1)
|
||||
n("<0 1 2 3 4>*8").scale('G4 minor')
|
||||
.s("gm_lead_6_voice")
|
||||
.clip(sine.range(.2,.8).slow(8))
|
||||
.jux(rev)
|
||||
.room(2)
|
||||
.sometimes(add(note("12")))
|
||||
.lpf(perlin.range(200,20000).slow(4))
|
||||
-->
|
||||
</strudel-repl>
|
||||
```
|
||||
|
||||
This will load the strudel website in an iframe, using the code provided within the HTML comments `<!-- -->`.
|
||||
The HTML comments are needed to make sure the browser won't interpret it as HTML.
|
||||
|
||||
For alternative ways to load this package, see the [@strudel/embed README](https://github.com/tidalcycles/strudel/tree/main/packages/embed#strudelembed).
|
||||
|
||||
### @strudel/repl
|
||||
|
||||
Loading strudel directly in your site, without an iframe, looks similar to the iframe variant:
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/@strudel/repl@latest"></script>
|
||||
<strudel-editor>
|
||||
<!--
|
||||
setcps(1)
|
||||
n("<0 1 2 3 4>*8").scale('G4 minor')
|
||||
.s("gm_lead_6_voice")
|
||||
.clip(sine.range(.2,.8).slow(8))
|
||||
.jux(rev)
|
||||
.room(2)
|
||||
.sometimes(add(note("12")))
|
||||
.lpf(perlin.range(200,20000).slow(4))
|
||||
-->
|
||||
</strudel-editor>
|
||||
```
|
||||
|
||||
Here, we're loading `@strudel/repl` instead of `@strudel/embed`, and the component is called `strudel-editor` instead of `strudel-repl`.
|
||||
Yes the naming is a bit confusing..
|
||||
|
||||
The upside of using the repl without an iframe is that you can pin the strudel version you're using:
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/@strudel/repl@1.0.2"></script>
|
||||
<strudel-editor>
|
||||
<!--
|
||||
...
|
||||
-->
|
||||
</strudel-editor>
|
||||
```
|
||||
|
||||
This will guarantee your pattern wont break due to changes to the strudel project in the future.
|
||||
|
||||
For more info on this package, see the [@strudel/repl README](https://github.com/tidalcycles/strudel/tree/main/packages/repl#strudelrepl).
|
||||
|
||||
## With your own UI
|
||||
|
||||
The above approach assumes you want to use the builtin [codemirror](https://codemirror.net/) editor.
|
||||
If you'd rather use your own UI, you can use the `@strudel/web` package:
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<script src="https://unpkg.com/@strudel/web@1.0.3"></script>
|
||||
<button id="play">play</button>
|
||||
<button id="stop">stop</button>
|
||||
<script>
|
||||
initStrudel();
|
||||
document.getElementById('play').addEventListener('click', () => note('<c a f e>(3,8)').jux(rev).play());
|
||||
document.getElementById('stop').addEventListener('click', () => hush());
|
||||
</script>
|
||||
```
|
||||
|
||||
For more info on this package, see the [@strudel/web README](https://github.com/tidalcycles/strudel/tree/main/packages/web#strudelweb).
|
||||
|
||||
## Via npm
|
||||
|
||||
[All the packages and many more are available on npm under the @strudel namespace](https://www.npmjs.com/search?q=%40strudel).
|
||||
There are actually many more packages you can use to have fine grained control over what you use and what not.
|
||||
To use these packages, you have to use a bundler that supports es modules, like [vite](https://vitejs.dev/).
|
||||
|
||||
To find out more about the purpose of each package, see [Packages](/technical-manual/packages)
|
||||
@ -106,8 +106,7 @@ stack(
|
||||
[B3@2 D4] [A3@2 [G3 A3]] [B3@2 D4] [A3]
|
||||
[B3@2 D4] [A4@2 G4] D5@2
|
||||
[D5@2 [C5 B4]] [[C5 B4] G4@2] [C5@2 [B4 A4]] [[B4 A4] E4@2]
|
||||
[D5@2 [C5 B4]] [[C5 B4] G4 C5] [G5] [~ ~ B3]\`
|
||||
.color('#9C7C38'),
|
||||
[D5@2 [C5 B4]] [[C5 B4] G4 C5] [G5] [~ ~ B3]\`,
|
||||
// bass
|
||||
\`[[C2 G2] E3@2] [[C2 G2] F#3@2] [[C2 G2] E3@2] [[C2 G2] F#3@2]
|
||||
[[B1 D3] G3@2] [[Bb1 Db3] G3@2] [[A1 C3] G3@2] [[D2 C3] F#3@2]
|
||||
@ -115,7 +114,6 @@ stack(
|
||||
[[B1 D3] G3@2] [[Bb1 Db3] G3@2] [[A1 C3] G3@2] [[D2 C3] F#3@2]
|
||||
[[F2 C3] E3@2] [[E2 B2] D3@2] [[D2 A2] C3@2] [[C2 G2] B2@2]
|
||||
[[F2 C3] E3@2] [[E2 B2] D3@2] [[Eb2 Bb2] Db3@2] [[D2 A2] C3 [F3,G2]]\`
|
||||
.color('#4C4646')
|
||||
).transpose(12).slow(48)
|
||||
.superimpose(x=>x.add(0.06)) // add slightly detuned voice
|
||||
.note()
|
||||
@ -447,58 +445,6 @@ note(
|
||||
.room(.5)
|
||||
.lpa(.125).lpenv(-2).v("8:.125").fanchor(.25)`;
|
||||
|
||||
export const hyperpop = `// "Hyperpop"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
|
||||
const lfo = cosine.slow(15);
|
||||
const lfo2 = sine.slow(16);
|
||||
const filter1 = x=>x.cutoff(lfo2.range(300,3000));
|
||||
const filter2 = x=>x.hcutoff(lfo.range(1000,6000)).cutoff(4000)
|
||||
const scales = cat('D3 major', 'G3 major').slow(8)
|
||||
|
||||
samples({
|
||||
bd: '344/344757_1676145-lq.mp3',
|
||||
sn: '387/387186_7255534-lq.mp3',
|
||||
hh: '561/561241_12517458-lq.mp3',
|
||||
hh2:'44/44944_236326-lq.mp3',
|
||||
hh3: '44/44944_236326-lq.mp3',
|
||||
}, 'https://cdn.freesound.org/previews/')
|
||||
|
||||
stack(
|
||||
"-7 0 -7 7".struct("x(5,8,1)").fast(2).sub(7)
|
||||
.scale(scales)
|
||||
.note()
|
||||
.s("sawtooth,square")
|
||||
.gain(.3).attack(0.01).decay(0.1).sustain(.5)
|
||||
.apply(filter1)
|
||||
.lpa(.1).lpenv(2).ftype('24db')
|
||||
,
|
||||
n("~@3 [<2 3>,<4 5>]")
|
||||
.echo(4,1/16,.7)
|
||||
.scale(scales)
|
||||
.s('square').gain(.7)
|
||||
.attack(0.01).decay(0.1).sustain(0)
|
||||
.apply(filter1),
|
||||
"6 4 2".add(14)
|
||||
.superimpose(sub("5"))
|
||||
.fast(1).euclidLegato(3,8)
|
||||
.mask("<1 0@7>")
|
||||
.fast(2).n()
|
||||
.echo(32, 1/8, .8)
|
||||
.scale(scales)
|
||||
.s("sawtooth")
|
||||
.mul(gain(sine.range(.1,.4).slow(8)))
|
||||
.attack(.001).decay(.2).sustain(0)
|
||||
.apply(filter2)
|
||||
).stack(
|
||||
stack(
|
||||
"bd <~@7 [~ bd]>".fast(2),
|
||||
"~ sn",
|
||||
"[~ hh3]*2"
|
||||
).s().fast(2).gain(.7)
|
||||
).slow(2)`;
|
||||
|
||||
export const festivalOfFingers3 = `// "Festival of fingers 3"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
@ -615,7 +561,7 @@ setcps(1)
|
||||
"<8(3,8) <7 7*2> [4 5@3] 8>".sub(1) // sub 1 -> 1-indexed
|
||||
.layer(
|
||||
x=>x,
|
||||
x=>x.add(7).color('steelblue')
|
||||
x=>x.add(7)
|
||||
.off(1/8,x=>x.add("2,4").off(1/8,x=>x.add(5).echo(4,.125,.5)))
|
||||
.slow(2),
|
||||
).n().scale('A1 minor')
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user