Merge branch 'main' into docs

This commit is contained in:
Felix Roos 2023-01-12 02:20:16 +01:00
commit fc3ead4c01
12 changed files with 511 additions and 498 deletions

View File

@ -13,7 +13,7 @@ made into a pattern swatch.
1. in your fork, go to settings -> pages and select "Github Actions" as source 1. in your fork, go to settings -> pages and select "Github Actions" as source
2. edit `website/public/CNAME` to contain `<your-username>.github.io/strudel` 2. edit `website/public/CNAME` to contain `<your-username>.github.io/strudel`
3. edit `website/astro.config.mjs` to use site: `<your-username>.github.io/strudel` and base `/strudel` 3. edit `website/astro.config.mjs` to use site: `https://<your-username>.github.io` and base `/strudel`
4. go to Actions -> `Build and Deploy` and click `Run workflow` 4. go to Actions -> `Build and Deploy` and click `Run workflow`
5. view your patterns at `<your-username>.github.io/strudel/swatch/` 5. view your patterns at `<your-username>.github.io/strudel/swatch/`

View File

@ -15,7 +15,7 @@
"repl": "cd website && npm run dev", "repl": "cd website && npm run dev",
"osc": "cd packages/osc && npm run server", "osc": "cd packages/osc && npm run server",
"build": "cd website && npm run build", "build": "cd website && npm run build",
"preview": "npx serve ./out", "preview": "cd website && npm run preview",
"deploy": "NODE_DEBUG=gh-pages gh-pages -d out", "deploy": "NODE_DEBUG=gh-pages gh-pages -d out",
"jsdoc": "jsdoc packages/ -c jsdoc.config.json", "jsdoc": "jsdoc packages/ -c jsdoc.config.json",
"jsdoc-json": "jsdoc packages/ --template ./node_modules/jsdoc-json --destination doc.json -c jsdoc.config.json", "jsdoc-json": "jsdoc packages/ --template ./node_modules/jsdoc-json --destination doc.json -c jsdoc.config.json",

View File

@ -154,6 +154,11 @@ describe('Pattern', () => {
).toBe(7); ).toBe(7);
}); });
}); });
describe('out()', () => {
it('is an alias for set.out()', () => {
sameFirst(sequence(1, 2).out(5, 6, 7, 8), sequence(1, 2).set.out(5, 6, 7, 8));
});
});
describe('add()', () => { describe('add()', () => {
it('works as toplevel function', () => { it('works as toplevel function', () => {
expect(add(pure(4), pure(5)).query(st(0, 1))[0].value).toBe(9); expect(add(pure(4), pure(5)).query(st(0, 1))[0].value).toBe(9);

View File

@ -274,13 +274,18 @@ function peg$parse(input, options) {
var peg$f3 = function(s, stepsPerCycle) { s.arguments_.stepsPerCycle = stepsPerCycle ; return s; }; var peg$f3 = function(s, stepsPerCycle) { s.arguments_.stepsPerCycle = stepsPerCycle ; return s; };
var peg$f4 = function(a) { return a }; var peg$f4 = function(a) { return a };
var peg$f5 = function(s) { s.arguments_.alignment = 'slowcat'; return s; }; var peg$f5 = function(s) { s.arguments_.alignment = 'slowcat'; return s; };
var peg$f6 = function(a) { return { weight: a} }; var peg$f6 = function(a) { return x => x.options_['weight'] = a };
var peg$f7 = function(a) { return { replicate: a } }; var peg$f7 = function(a) { return x => x.options_['reps'] = a };
var peg$f8 = function(p, s, r) { return { operator : { type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r } } } }; var peg$f8 = function(p, s, r) { return x => x.options_['ops'].push({ type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r }}) };
var peg$f9 = function(a) { return { operator : { type_: "stretch", arguments_ :{ amount:a, type: 'slow' } } } }; var peg$f9 = function(a) { return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'slow' }}) };
var peg$f10 = function(a) { return { operator : { type_: "stretch", arguments_ :{ amount:a, type: 'fast' } } } }; var peg$f10 = function(a) { return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'fast' }}) };
var peg$f11 = function(a) { return { operator : { type_: "degradeBy", arguments_ :{ amount:a } } } }; var peg$f11 = function(a) { return x => x.options_['ops'].push({ type_: "degradeBy", arguments_ :{ amount:a } }) };
var peg$f12 = function(s, o) { return new ElementStub(s, o);}; var peg$f12 = function(s, ops) { const result = new ElementStub(s, {ops: [], weight: 1, reps: 1});
for (const op of ops) {
op(result);
}
return result;
};
var peg$f13 = function(s) { return new PatternStub(s, 'fastcat'); }; var peg$f13 = function(s) { return new PatternStub(s, 'fastcat'); };
var peg$f14 = function(tail) { return { alignment: 'stack', list: tail }; }; var peg$f14 = function(tail) { return { alignment: 'stack', list: tail }; };
var peg$f15 = function(tail) { return { alignment: 'rand', list: tail }; }; var peg$f15 = function(tail) { return { alignment: 'rand', list: tail }; };
@ -1069,20 +1074,20 @@ function peg$parse(input, options) {
return s0; return s0;
} }
function peg$parseslice_modifier() { function peg$parseslice_op() {
var s0; var s0;
s0 = peg$parseslice_weight(); s0 = peg$parseop_weight();
if (s0 === peg$FAILED) { if (s0 === peg$FAILED) {
s0 = peg$parseslice_bjorklund(); s0 = peg$parseop_bjorklund();
if (s0 === peg$FAILED) { if (s0 === peg$FAILED) {
s0 = peg$parseslice_slow(); s0 = peg$parseop_slow();
if (s0 === peg$FAILED) { if (s0 === peg$FAILED) {
s0 = peg$parseslice_fast(); s0 = peg$parseop_fast();
if (s0 === peg$FAILED) { if (s0 === peg$FAILED) {
s0 = peg$parseslice_replicate(); s0 = peg$parseop_replicate();
if (s0 === peg$FAILED) { if (s0 === peg$FAILED) {
s0 = peg$parseslice_degrade(); s0 = peg$parseop_degrade();
} }
} }
} }
@ -1092,7 +1097,7 @@ function peg$parse(input, options) {
return s0; return s0;
} }
function peg$parseslice_weight() { function peg$parseop_weight() {
var s0, s1, s2; var s0, s1, s2;
s0 = peg$currPos; s0 = peg$currPos;
@ -1120,7 +1125,7 @@ function peg$parse(input, options) {
return s0; return s0;
} }
function peg$parseslice_replicate() { function peg$parseop_replicate() {
var s0, s1, s2; var s0, s1, s2;
s0 = peg$currPos; s0 = peg$currPos;
@ -1148,7 +1153,7 @@ function peg$parse(input, options) {
return s0; return s0;
} }
function peg$parseslice_bjorklund() { function peg$parseop_bjorklund() {
var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13; var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13;
s0 = peg$currPos; s0 = peg$currPos;
@ -1161,13 +1166,13 @@ function peg$parse(input, options) {
} }
if (s1 !== peg$FAILED) { if (s1 !== peg$FAILED) {
s2 = peg$parsews(); s2 = peg$parsews();
s3 = peg$parseslice_with_modifier(); s3 = peg$parseslice_with_ops();
if (s3 !== peg$FAILED) { if (s3 !== peg$FAILED) {
s4 = peg$parsews(); s4 = peg$parsews();
s5 = peg$parsecomma(); s5 = peg$parsecomma();
if (s5 !== peg$FAILED) { if (s5 !== peg$FAILED) {
s6 = peg$parsews(); s6 = peg$parsews();
s7 = peg$parseslice_with_modifier(); s7 = peg$parseslice_with_ops();
if (s7 !== peg$FAILED) { if (s7 !== peg$FAILED) {
s8 = peg$parsews(); s8 = peg$parsews();
s9 = peg$parsecomma(); s9 = peg$parsecomma();
@ -1175,7 +1180,7 @@ function peg$parse(input, options) {
s9 = null; s9 = null;
} }
s10 = peg$parsews(); s10 = peg$parsews();
s11 = peg$parseslice_with_modifier(); s11 = peg$parseslice_with_ops();
if (s11 === peg$FAILED) { if (s11 === peg$FAILED) {
s11 = null; s11 = null;
} }
@ -1214,7 +1219,7 @@ function peg$parse(input, options) {
return s0; return s0;
} }
function peg$parseslice_slow() { function peg$parseop_slow() {
var s0, s1, s2; var s0, s1, s2;
s0 = peg$currPos; s0 = peg$currPos;
@ -1242,7 +1247,7 @@ function peg$parse(input, options) {
return s0; return s0;
} }
function peg$parseslice_fast() { function peg$parseop_fast() {
var s0, s1, s2; var s0, s1, s2;
s0 = peg$currPos; s0 = peg$currPos;
@ -1270,7 +1275,7 @@ function peg$parse(input, options) {
return s0; return s0;
} }
function peg$parseslice_degrade() { function peg$parseop_degrade() {
var s0, s1, s2; var s0, s1, s2;
s0 = peg$currPos; s0 = peg$currPos;
@ -1296,15 +1301,17 @@ function peg$parse(input, options) {
return s0; return s0;
} }
function peg$parseslice_with_modifier() { function peg$parseslice_with_ops() {
var s0, s1, s2; var s0, s1, s2, s3;
s0 = peg$currPos; s0 = peg$currPos;
s1 = peg$parseslice(); s1 = peg$parseslice();
if (s1 !== peg$FAILED) { if (s1 !== peg$FAILED) {
s2 = peg$parseslice_modifier(); s2 = [];
if (s2 === peg$FAILED) { s3 = peg$parseslice_op();
s2 = null; while (s3 !== peg$FAILED) {
s2.push(s3);
s3 = peg$parseslice_op();
} }
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f12(s1, s2); s0 = peg$f12(s1, s2);
@ -1321,11 +1328,11 @@ function peg$parse(input, options) {
s0 = peg$currPos; s0 = peg$currPos;
s1 = []; s1 = [];
s2 = peg$parseslice_with_modifier(); s2 = peg$parseslice_with_ops();
if (s2 !== peg$FAILED) { if (s2 !== peg$FAILED) {
while (s2 !== peg$FAILED) { while (s2 !== peg$FAILED) {
s1.push(s2); s1.push(s2);
s2 = peg$parseslice_with_modifier(); s2 = peg$parseslice_with_ops();
} }
} else { } else {
s1 = peg$FAILED; s1 = peg$FAILED;

View File

@ -119,32 +119,37 @@ slice = step / sub_cycle / polymeter / slow_sequence
// slice modifier affects the timing/size of a slice (e.g. [a b c]@3) // slice modifier affects the timing/size of a slice (e.g. [a b c]@3)
// at this point, we assume we can represent them as regular sequence operators // at this point, we assume we can represent them as regular sequence operators
slice_modifier = slice_weight / slice_bjorklund / slice_slow / slice_fast / slice_replicate / slice_degrade slice_op = op_weight / op_bjorklund / op_slow / op_fast / op_replicate / op_degrade
slice_weight = "@" a:number op_weight = "@" a:number
{ return { weight: a} } { return x => x.options_['weight'] = a }
slice_replicate = "!"a:number op_replicate = "!"a:number
{ return { replicate: a } } { return x => x.options_['reps'] = a }
slice_bjorklund = "(" ws p:slice_with_modifier ws comma ws s:slice_with_modifier ws comma? ws r:slice_with_modifier? ws ")" op_bjorklund = "(" ws p:slice_with_ops ws comma ws s:slice_with_ops ws comma? ws r:slice_with_ops? ws ")"
{ return { operator : { type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r } } } } { return x => x.options_['ops'].push({ type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r }}) }
slice_slow = "/"a:slice op_slow = "/"a:slice
{ return { operator : { type_: "stretch", arguments_ :{ amount:a, type: 'slow' } } } } { return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'slow' }}) }
slice_fast = "*"a:slice op_fast = "*"a:slice
{ return { operator : { type_: "stretch", arguments_ :{ amount:a, type: 'fast' } } } } { return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'fast' }}) }
slice_degrade = "?"a:number? op_degrade = "?"a:number?
{ return { operator : { type_: "degradeBy", arguments_ :{ amount:a } } } } { return x => x.options_['ops'].push({ type_: "degradeBy", arguments_ :{ amount:a } }) }
// a slice with an modifier applied i.e [bd@4 sd@3]@2 hh] // a slice with an modifier applied i.e [bd@4 sd@3]@2 hh]
slice_with_modifier = s:slice o:slice_modifier? slice_with_ops = s:slice ops:slice_op*
{ return new ElementStub(s, o);} { const result = new ElementStub(s, {ops: [], weight: 1, reps: 1});
for (const op of ops) {
op(result);
}
return result;
}
// a sequence is a combination of one or more successive slices (as an array) // a sequence is a combination of one or more successive slices (as an array)
sequence = s:(slice_with_modifier)+ sequence = s:(slice_with_ops)+
{ return new PatternStub(s, 'fastcat'); } { return new PatternStub(s, 'fastcat'); }
// a stack is a series of vertically aligned sequence, separated by a comma // a stack is a series of vertically aligned sequence, separated by a comma

View File

@ -17,78 +17,74 @@ function _nextSeed() {
const applyOptions = (parent, code) => (pat, i) => { const applyOptions = (parent, code) => (pat, i) => {
const ast = parent.source_[i]; const ast = parent.source_[i];
const options = ast.options_; const options = ast.options_;
const operator = options?.operator; const ops = options?.ops;
if (operator) { if (ops) {
switch (operator.type_) { for (const op of ops) {
case 'stretch': { switch (op.type_) {
const legalTypes = ['fast', 'slow']; case 'stretch': {
const { type, amount } = operator.arguments_; const legalTypes = ['fast', 'slow'];
if (!legalTypes.includes(type)) { const { type, amount } = op.arguments_;
throw new Error(`mini: stretch: type must be one of ${legalTypes.join('|')} but got ${type}`); if (!legalTypes.includes(type)) {
throw new Error(`mini: stretch: type must be one of ${legalTypes.join('|')} but got ${type}`);
}
pat = strudel.reify(pat)[type](patternifyAST(amount, code));
break;
} }
return strudel.reify(pat)[type](patternifyAST(amount, code)); case 'bjorklund': {
} if (op.arguments_.rotation) {
case 'bjorklund': pat = pat.euclidRot(
if (operator.arguments_.rotation) { patternifyAST(op.arguments_.pulse, code),
return pat.euclidRot( patternifyAST(op.arguments_.step, code),
patternifyAST(operator.arguments_.pulse, code), patternifyAST(op.arguments_.rotation, code),
patternifyAST(operator.arguments_.step, code), );
patternifyAST(operator.arguments_.rotation, code), } else {
); pat = pat.euclid(patternifyAST(op.arguments_.pulse, code), patternifyAST(op.arguments_.step, code));
} else { }
return pat.euclid( break;
patternifyAST(operator.arguments_.pulse, code),
patternifyAST(operator.arguments_.step, code),
);
} }
case 'degradeBy': case 'degradeBy': {
// TODO: find out what is right here // TODO: find out what is right here
// example: // example:
/* /*
stack( stack(
s("hh*8").degrade(), s("hh*8").degrade(),
s("[ht*8]?") s("[ht*8]?")
) )
*/ */
// above example will only be in sync when _degradeBy is used... // above example will only be in sync when _degradeBy is used...
// it also seems that the nextSeed will create undeterministic behaviour // it also seems that the nextSeed will create undeterministic behaviour
// as it uses a global _seedState. This is probably the reason for // as it uses a global _seedState. This is probably the reason for
// https://github.com/tidalcycles/strudel/issues/245 // https://github.com/tidalcycles/strudel/issues/245
// this is how it was: // this is how it was:
/* /*
return strudel.reify(pat)._degradeByWith( return strudel.reify(pat)._degradeByWith(
strudel.rand.early(randOffset * _nextSeed()).segment(1), strudel.rand.early(randOffset * _nextSeed()).segment(1),
operator.arguments_.amount ?? 0.5, op.arguments_.amount ?? 0.5,
); );
*/ */
return strudel.reify(pat).degradeBy(operator.arguments_.amount === null ? 0.5 : operator.arguments_.amount); pat = strudel.reify(pat).degradeBy(op.arguments_.amount === null ? 0.5 : op.arguments_.amount);
break;
}
default: {
console.warn(`operator "${op.type_}" not implemented`);
}
}
} }
console.warn(`operator "${operator.type_}" not implemented`);
}
if (options?.weight) {
// weight is handled by parent
return pat;
}
// TODO: bjorklund e.g. "c3(5,8)"
const unimplemented = Object.keys(options || {}).filter((key) => key !== 'operator');
if (unimplemented.length) {
console.warn(
`option${unimplemented.length > 1 ? 's' : ''} ${unimplemented.map((o) => `"${o}"`).join(', ')} not implemented`,
);
} }
return pat; return pat;
}; };
function resolveReplications(ast) { function resolveReplications(ast) {
ast.source_ = strudel.flatten( ast.source_ = strudel.flatten(
ast.source_.map((child) => { ast.source_.map((child) => {
const { replicate, ...options } = child.options_ || {}; const { reps } = child.options_ || {};
if (!replicate) { if (!reps) {
return [child]; return [child];
} }
delete child.options_.replicate; delete child.options_.reps;
return Array(replicate).fill(child); return Array(reps).fill(child);
}), }),
); );
} }

View File

@ -9,8 +9,8 @@ import '@strudel.cycles/core/euclid.mjs';
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
describe('mini', () => { describe('mini', () => {
const minV = (v) => mini(v).firstCycleValues; const minV = (v) => mini(v).sortHapsByPart().firstCycleValues;
const minS = (v) => mini(v).showFirstCycle; const minS = (v) => mini(v).sortHapsByPart().showFirstCycle;
it('supports single elements', () => { it('supports single elements', () => {
expect(minV('a')).toEqual(['a']); expect(minV('a')).toEqual(['a']);
}); });

View File

@ -396,7 +396,7 @@ export const webaudioOutput = async (hap, deadline, hapDuration) => {
export const webaudioOutputTrigger = (t, hap, ct, cps) => webaudioOutput(hap, t - ct, hap.duration / cps); export const webaudioOutputTrigger = (t, hap, ct, cps) => webaudioOutput(hap, t - ct, hap.duration / cps);
Pattern.prototype.out = function () { Pattern.prototype.webaudio = function () {
// TODO: refactor (t, hap, ct, cps) to (hap, deadline, duration) ? // TODO: refactor (t, hap, ct, cps) to (hap, deadline, duration) ?
return this.onTrigger(webaudioOutputTrigger); return this.onTrigger(webaudioOutputTrigger);
}; };

View File

@ -2483,20 +2483,20 @@ exports[`runs examples > example "outside" example index 0 1`] = `
exports[`runs examples > example "pan" example index 0 1`] = ` exports[`runs examples > example "pan" example index 0 1`] = `
[ [
"[ 0/1 → 1/4 | s:bd pan:0.5 ]", "[ 0/1 → 1/4 | s:bd pan:0.5 ]",
"[ 1/4 → 1/2 | s:hh pan:0.5 ]",
"[ 1/2 → 3/4 | s:bd pan:0.5 ]", "[ 1/2 → 3/4 | s:bd pan:0.5 ]",
"[ 1/4 → 1/2 | s:hh pan:0.5 ]",
"[ 3/4 → 1/1 | s:hh pan:0.5 ]", "[ 3/4 → 1/1 | s:hh pan:0.5 ]",
"[ 1/1 → 5/4 | s:bd pan:1 ]", "[ 1/1 → 5/4 | s:bd pan:1 ]",
"[ 5/4 → 3/2 | s:hh pan:1 ]",
"[ 3/2 → 7/4 | s:bd pan:1 ]", "[ 3/2 → 7/4 | s:bd pan:1 ]",
"[ 5/4 → 3/2 | s:hh pan:1 ]",
"[ 7/4 → 2/1 | s:hh pan:1 ]", "[ 7/4 → 2/1 | s:hh pan:1 ]",
"[ 2/1 → 9/4 | s:bd pan:0.5 ]", "[ 2/1 → 9/4 | s:bd pan:0.5 ]",
"[ 9/4 → 5/2 | s:hh pan:0.5 ]",
"[ 5/2 → 11/4 | s:bd pan:0.5 ]", "[ 5/2 → 11/4 | s:bd pan:0.5 ]",
"[ 9/4 → 5/2 | s:hh pan:0.5 ]",
"[ 11/4 → 3/1 | s:hh pan:0.5 ]", "[ 11/4 → 3/1 | s:hh pan:0.5 ]",
"[ 3/1 → 13/4 | s:bd pan:0 ]", "[ 3/1 → 13/4 | s:bd pan:0 ]",
"[ 13/4 → 7/2 | s:hh pan:0 ]",
"[ 7/2 → 15/4 | s:bd pan:0 ]", "[ 7/2 → 15/4 | s:bd pan:0 ]",
"[ 13/4 → 7/2 | s:hh pan:0 ]",
"[ 15/4 → 4/1 | s:hh pan:0 ]", "[ 15/4 → 4/1 | s:hh pan:0 ]",
] ]
`; `;

File diff suppressed because it is too large Load Diff

View File

@ -116,7 +116,7 @@ strudel.Pattern.prototype.filter = function () {
strudel.Pattern.prototype.adsr = function () { strudel.Pattern.prototype.adsr = function () {
return this; return this;
}; };
strudel.Pattern.prototype.out = function () { strudel.Pattern.prototype.webaudio = function () {
return this; return this;
}; };
strudel.Pattern.prototype.soundfont = function () { strudel.Pattern.prototype.soundfont = function () {

View File

@ -15,7 +15,7 @@ As we have seen, `s` can play back audio samples:
<MiniRepl client:idle client:idle tune={`s("bd sd,hh*8,misc/2")`} /> <MiniRepl client:idle client:idle tune={`s("bd sd,hh*8,misc/2")`} />
These sounds come from Strudels in-built default "sample map". These sounds come from Strudel's in-built default "sample map".
To know which sounds are available, open the [default sample map](https://strudel.tidalcycles.org/EmuSP12.json). To know which sounds are available, open the [default sample map](https://strudel.tidalcycles.org/EmuSP12.json).
# Custom Sample Maps # Custom Sample Maps