diff --git a/bench/tunes.bench.mjs b/bench/tunes.bench.mjs
new file mode 100644
index 000000000..cd381873d
--- /dev/null
+++ b/bench/tunes.bench.mjs
@@ -0,0 +1,22 @@
+import { queryCode, testCycles } from '../test/runtime.mjs';
+import * as tunes from '../website/src/repl/tunes.mjs';
+import { describe, bench } from 'vitest';
+import { calculateTactus } from '../packages/core/index.mjs';
+
+const tuneKeys = Object.keys(tunes);
+
+describe('renders tunes', () => {
+ tuneKeys.forEach((key) => {
+ describe(key, () => {
+ calculateTactus(true);
+ bench(`+tactus`, async () => {
+ await queryCode(tunes[key], testCycles[key] || 1);
+ });
+ calculateTactus(false);
+ bench(`-tactus`, async () => {
+ await queryCode(tunes[key], testCycles[key] || 1);
+ });
+ calculateTactus(true);
+ });
+ });
+});
diff --git a/package.json b/package.json
index 979009130..8aefa1023 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
"test": "npm run pretest && vitest run --version",
"test-ui": "npm run pretest && vitest --ui",
"test-coverage": "npm run pretest && vitest --coverage",
+ "bench": "npm run pretest && vitest bench",
"snapshot": "npm run pretest && vitest run -u --silent",
"repl": "npm run prestart && cd website && npm run dev",
"start": "npm run prestart && cd website && npm run dev",
diff --git a/packages/core/bench/pattern.bench.mjs b/packages/core/bench/pattern.bench.mjs
new file mode 100644
index 000000000..12644800b
--- /dev/null
+++ b/packages/core/bench/pattern.bench.mjs
@@ -0,0 +1,46 @@
+import { describe, bench } from 'vitest';
+
+import { calculateTactus, sequence, stack } from '../index.mjs';
+
+const pat64 = sequence(...Array(64).keys());
+
+describe('tactus', () => {
+ calculateTactus(true);
+ bench(
+ '+tactus',
+ () => {
+ pat64.iter(64).fast(64).firstCycle();
+ },
+ { time: 1000 },
+ );
+
+ calculateTactus(false);
+ bench(
+ '-tactus',
+ () => {
+ pat64.iter(64).fast(64).firstCycle();
+ },
+ { time: 1000 },
+ );
+});
+
+describe('stack', () => {
+ calculateTactus(true);
+ bench(
+ '+tactus',
+ () => {
+ stack(pat64, pat64, pat64, pat64, pat64, pat64, pat64, pat64).fast(64).firstCycle();
+ },
+ { time: 1000 },
+ );
+
+ calculateTactus(false);
+ bench(
+ '-tactus',
+ () => {
+ stack(pat64, pat64, pat64, pat64, pat64, pat64, pat64, pat64).fast(64).firstCycle();
+ },
+ { time: 1000 },
+ );
+});
+calculateTactus(true);
diff --git a/packages/core/package.json b/packages/core/package.json
index 7f59ed416..6c95d0492 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -9,6 +9,7 @@
},
"scripts": {
"test": "vitest run",
+ "bench": "vitest bench",
"build": "vite build",
"prepublishOnly": "pnpm build"
},
diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs
index 7291c12fd..03ce2e2d9 100644
--- a/packages/core/pattern.mjs
+++ b/packages/core/pattern.mjs
@@ -27,6 +27,12 @@ import { logger } from './logger.mjs';
let stringParser;
+let __tactus = true;
+
+export const calculateTactus = function (x) {
+ __tactus = x ? true : false;
+};
+
// parser is expected to turn a string into a pattern
// if set, the reify function will parse all strings with it
// intended to use with mini to automatically interpret all strings as mini notation
@@ -60,6 +66,9 @@ export class Pattern {
}
withTactus(f) {
+ if (!__tactus) {
+ return this;
+ }
return new Pattern(this.query, this.tactus === undefined ? undefined : f(this.tactus));
}
@@ -149,7 +158,9 @@ export class Pattern {
return span_a.intersection_e(span_b);
};
const result = pat_func.appWhole(whole_func, pat_val);
- result.tactus = lcm(pat_val.tactus, pat_func.tactus);
+ if (__tactus) {
+ result.tactus = lcm(pat_val.tactus, pat_func.tactus);
+ }
return result;
}
@@ -1254,7 +1265,9 @@ export function stack(...pats) {
pats = pats.map((pat) => (Array.isArray(pat) ? sequence(...pat) : reify(pat)));
const query = (state) => flatten(pats.map((pat) => pat.query(state)));
const result = new Pattern(query);
- result.tactus = lcm(...pats.map((pat) => pat.tactus));
+ if (__tactus) {
+ result.tactus = lcm(...pats.map((pat) => pat.tactus));
+ }
return result;
}
@@ -1267,7 +1280,7 @@ function _stackWith(func, pats) {
return pats[0];
}
const [left, ...right] = pats.map((pat) => pat.tactus);
- const tactus = left.maximum(...right);
+ const tactus = __tactus ? left.maximum(...right) : undefined;
return stack(...func(tactus, pats));
}
@@ -1347,7 +1360,7 @@ export function slowcat(...pats) {
const offset = span.begin.floor().sub(span.begin.div(pats.length).floor());
return pat.withHapTime((t) => t.add(offset)).query(state.setSpan(span.withTime((t) => t.sub(offset))));
};
- const tactus = lcm(...pats.map((x) => x.tactus));
+ const tactus = __tactus ? lcm(...pats.map((x) => x.tactus)) : undefined;
return new Pattern(query).splitQueries().setTactus(tactus);
}
@@ -1766,7 +1779,9 @@ export const { focusSpan, focusspan } = register(['focusSpan', 'focusspan'], fun
*/
export const ply = register('ply', function (factor, pat) {
const result = pat.fmap((x) => pure(x)._fast(factor)).squeezeJoin();
- result.tactus = Fraction(factor).mulmaybe(pat.tactus);
+ if (__tactus) {
+ result.tactus = Fraction(factor).mulmaybe(pat.tactus);
+ }
return result;
});
@@ -1781,16 +1796,19 @@ export const ply = register('ply', function (factor, pat) {
* @example
* s("bd hh sd hh").fast(2) // s("[bd hh sd hh]*2")
*/
-export const { fast, density } = register(['fast', 'density'], function (factor, pat) {
- if (factor === 0) {
- return silence;
- }
- factor = Fraction(factor);
- const fastQuery = pat.withQueryTime((t) => t.mul(factor));
- const result = fastQuery.withHapTime((t) => t.div(factor));
- result.tactus = factor.mulmaybe(pat.tactus);
- return result;
-});
+export const { fast, density } = register(
+ ['fast', 'density'],
+ function (factor, pat) {
+ if (factor === 0) {
+ return silence;
+ }
+ factor = Fraction(factor);
+ const fastQuery = pat.withQueryTime((t) => t.mul(factor));
+ return fastQuery.withHapTime((t) => t.div(factor)).setTactus(pat.tactus);
+ },
+ true,
+ true,
+);
/**
* Both speeds up the pattern (like 'fast') and the sample playback (like 'speed').
@@ -1958,7 +1976,7 @@ export const zoom = register('zoom', function (s, e, pat) {
return nothing;
}
const d = e.sub(s);
- const tactus = pat.tactus.mulmaybe(d);
+ const tactus = __tactus ? pat.tactus.mulmaybe(d) : undefined;
return pat
.withQuerySpan((span) => span.withCycle((t) => t.mul(d).add(s)))
.withHapSpan((span) => span.withCycle((t) => t.sub(s).div(d)))
@@ -2173,7 +2191,7 @@ export const { juxBy, juxby } = register(['juxBy', 'juxby'], function (by, func,
const left = pat.withValue((val) => Object.assign({}, val, { pan: elem_or(val, 'pan', 0.5) - by }));
const right = func(pat.withValue((val) => Object.assign({}, val, { pan: elem_or(val, 'pan', 0.5) + by })));
- return stack(left, right).setTactus(lcm(left.tactus, right.tactus));
+ return stack(left, right).setTactus(__tactus ? lcm(left.tactus, right.tactus) : undefined);
});
/**
@@ -2292,7 +2310,13 @@ export const { iterBack, iterback } = register(
export const { repeatCycles } = register(
'repeatCycles',
function (n, pat) {
- return slowcat(...Array(n).fill(pat));
+ return new Pattern(function (state) {
+ const cycle = state.span.begin.sam();
+ const source_cycle = cycle.div(n).sam();
+ const delta = cycle.sub(source_cycle);
+ state = state.withSpan((span) => span.withTime((spant) => spant.sub(delta)));
+ return pat.query(state).map((hap) => hap.withSpan((span) => span.withTime((spant) => spant.add(delta))));
+ }).splitQueries();
},
true,
true,
@@ -2773,7 +2797,7 @@ export const chop = register('chop', function (n, pat) {
const func = function (o) {
return sequence(slice_objects.map((slice_o) => Object.assign({}, o, slice_o)));
};
- return pat.squeezeBind(func).setTactus(Fraction(n).mulmaybe(pat.tactus));
+ return pat.squeezeBind(func).setTactus(__tactus ? Fraction(n).mulmaybe(pat.tactus) : undefined);
});
/**
@@ -2788,7 +2812,10 @@ export const striate = register('striate', function (n, pat) {
const slices = Array.from({ length: n }, (x, i) => i);
const slice_objects = slices.map((i) => ({ begin: i / n, end: (i + 1) / n }));
const slicePat = slowcat(...slice_objects);
- return pat.set(slicePat)._fast(n);
+ return pat
+ .set(slicePat)
+ ._fast(n)
+ .setTactus(__tactus ? Fraction(n).mulmaybe(pat.tactus) : undefined);
});
/**
diff --git a/packages/core/test/pattern.test.mjs b/packages/core/test/pattern.test.mjs
index 4bed34cb8..845ed00ab 100644
--- a/packages/core/test/pattern.test.mjs
+++ b/packages/core/test/pattern.test.mjs
@@ -51,6 +51,7 @@ import {
stackRight,
stackCentre,
s_cat,
+ calculateTactus,
} from '../index.mjs';
import { steady } from '../signal.mjs';
@@ -1127,8 +1128,8 @@ describe('Pattern', () => {
it('Is correctly preserved/calculated through transformations', () => {
expect(sequence(0, 1, 2, 3).linger(4).tactus).toStrictEqual(Fraction(4));
expect(sequence(0, 1, 2, 3).iter(4).tactus).toStrictEqual(Fraction(4));
- expect(sequence(0, 1, 2, 3).fast(4).tactus).toStrictEqual(Fraction(16));
- expect(sequence(0, 1, 2, 3).hurry(4).tactus).toStrictEqual(Fraction(16));
+ expect(sequence(0, 1, 2, 3).fast(4).tactus).toStrictEqual(Fraction(4));
+ expect(sequence(0, 1, 2, 3).hurry(4).tactus).toStrictEqual(Fraction(4));
expect(sequence(0, 1, 2, 3).rev().tactus).toStrictEqual(Fraction(4));
expect(sequence(1).segment(10).tactus).toStrictEqual(Fraction(10));
expect(sequence(1, 0, 1).invert().tactus).toStrictEqual(Fraction(3));
@@ -1164,7 +1165,7 @@ describe('Pattern', () => {
expect(stackCentre(fastcat(0, 1, 2), fastcat(3, 4)).tactus).toStrictEqual(Fraction(3));
expect(fastcat(0, 1).ply(3).tactus).toStrictEqual(Fraction(6));
expect(fastcat(0, 1).setTactus(undefined).ply(3).tactus).toStrictEqual(undefined);
- expect(fastcat(0, 1).fast(3).tactus).toStrictEqual(Fraction(6));
+ expect(fastcat(0, 1).fast(3).tactus).toStrictEqual(Fraction(2));
expect(fastcat(0, 1).setTactus(undefined).fast(3).tactus).toStrictEqual(undefined);
});
});
diff --git a/packages/csound/index.mjs b/packages/csound/index.mjs
index a00be74fb..1eeaf6fa5 100644
--- a/packages/csound/index.mjs
+++ b/packages/csound/index.mjs
@@ -23,7 +23,7 @@ export const csound = register('csound', (instrument, pat) => {
instrument = instrument || 'triangle';
init(); // not async to support csound inside other patterns + to be able to call pattern methods after it
// TODO: find a alternative way to wait for csound to load (to wait with first time playback)
- return pat.onTrigger((time, hap) => {
+ return pat.onTrigger((time_deprecate, hap, currentTime, _cps, targetTime) => {
if (!_csound) {
logger('[csound] not loaded yet', 'warning');
return;
@@ -38,9 +38,11 @@ export const csound = register('csound', (instrument, pat) => {
.join('/');
// TODO: find out how to send a precise ctx based time
// http://www.csounds.com/manual/html/i.html
+ const timeOffset = targetTime - currentTime; // latency ?
+ //const timeOffset = time_deprecate - getAudioContext().currentTime
const params = [
`"${instrument}"`, // p1: instrument name
- time - getAudioContext().currentTime, //.toFixed(precision), // p2: starting time in arbitrary unit called beats
+ timeOffset, // p2: starting time in arbitrary unit called beats
hap.duration + 0, // p3: duration in beats
// instrument specific params:
freq, //.toFixed(precision), // p4: frequency
diff --git a/packages/mini/bench/mini.bench.mjs b/packages/mini/bench/mini.bench.mjs
new file mode 100644
index 000000000..782ac86ba
--- /dev/null
+++ b/packages/mini/bench/mini.bench.mjs
@@ -0,0 +1,25 @@
+import { describe, bench } from 'vitest';
+
+import { calculateTactus } from '../../core/index.mjs';
+import { mini } from '../index.mjs';
+
+describe('mini', () => {
+ calculateTactus(true);
+ bench(
+ '+tactus',
+ () => {
+ mini('a b c*3 [c d e, f g] ').fast(64).firstCycle();
+ },
+ { time: 1000 },
+ );
+
+ calculateTactus(false);
+ bench(
+ '-tactus',
+ () => {
+ mini('a b c*3 [c d e, f g] ').fast(64).firstCycle();
+ },
+ { time: 1000 },
+ );
+ calculateTactus(true);
+});
diff --git a/packages/mini/krill-parser.js b/packages/mini/krill-parser.js
index 7762242d5..1cdd34867 100644
--- a/packages/mini/krill-parser.js
+++ b/packages/mini/krill-parser.js
@@ -295,7 +295,15 @@ function peg$parse(input, options) {
var peg$f6 = function(a) { return a };
var peg$f7 = function(s) { s.arguments_.alignment = 'polymeter_slowcat'; return s; };
var peg$f8 = function(a) { return x => x.options_['weight'] = (x.options_['weight'] ?? 1) + (a ?? 2) - 1 };
- var peg$f9 = function(a) { return x => x.options_['reps'] = (x.options_['reps'] ?? 1) + (a ?? 2) - 1 };
+ var peg$f9 = function(a) { return x => {const reps = (x.options_['reps'] ?? 1) + (a ?? 2) - 1;
+ x.options_['reps'] = reps;
+ console.log("reps: ", reps)
+ x.options_['ops'] = x.options_['ops'].filter(x => x.type_ !== "replicate");
+ x.options_['ops'].push({ type_: "replicate", arguments_ :{ amount:reps }});
+ x.options_['weight'] = reps;
+ console.log("options: ", x.options_);
+ }
+ };
var peg$f10 = function(p, s, r) { return x => x.options_['ops'].push({ type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r }}) };
var peg$f11 = function(a) { return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'slow' }}) };
var peg$f12 = function(a) { return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'fast' }}) };
diff --git a/packages/mini/krill.pegjs b/packages/mini/krill.pegjs
index 35d7bdc42..c1349ca52 100644
--- a/packages/mini/krill.pegjs
+++ b/packages/mini/krill.pegjs
@@ -135,7 +135,14 @@ op_weight = ws ("@" / "_") a:number?
{ return x => x.options_['weight'] = (x.options_['weight'] ?? 1) + (a ?? 2) - 1 }
op_replicate = ws "!" a:number?
- { return x => x.options_['reps'] = (x.options_['reps'] ?? 1) + (a ?? 2) - 1 }
+ { return x => {// A bit fiddly, to support both x!4 and x!!! as equivalent..
+ const reps = (x.options_['reps'] ?? 1) + (a ?? 2) - 1;
+ x.options_['reps'] = reps;
+ x.options_['ops'] = x.options_['ops'].filter(x => x.type_ !== "replicate");
+ x.options_['ops'].push({ type_: "replicate", arguments_ :{ amount:reps }});
+ x.options_['weight'] = reps;
+ }
+ }
op_bjorklund = "(" ws p:slice_with_ops ws comma ws s:slice_with_ops ws comma? ws r:slice_with_ops? ws ")"
{ return x => x.options_['ops'].push({ type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r }}) }
diff --git a/packages/mini/mini.mjs b/packages/mini/mini.mjs
index 8c8932736..8d2276fb2 100644
--- a/packages/mini/mini.mjs
+++ b/packages/mini/mini.mjs
@@ -27,6 +27,12 @@ const applyOptions = (parent, enter) => (pat, i) => {
pat = strudel.reify(pat)[type](enter(amount));
break;
}
+ case 'replicate': {
+ const { amount } = op.arguments_;
+ pat = strudel.reify(pat);
+ pat = pat._repeatCycles(amount)._fast(amount);
+ break;
+ }
case 'bjorklund': {
if (op.arguments_.rotation) {
pat = pat.euclidRot(enter(op.arguments_.pulse), enter(op.arguments_.step), enter(op.arguments_.rotation));
@@ -67,26 +73,13 @@ const applyOptions = (parent, enter) => (pat, i) => {
return pat;
};
-function resolveReplications(ast) {
- ast.source_ = strudel.flatten(
- ast.source_.map((child) => {
- const { reps } = child.options_ || {};
- if (!reps) {
- return [child];
- }
- delete child.options_.reps;
- return Array(reps).fill(child);
- }),
- );
-}
-
// expects ast from mini2ast + quoted mini string + optional callback when a node is entered
export function patternifyAST(ast, code, onEnter, offset = 0) {
onEnter?.(ast);
const enter = (node) => patternifyAST(node, code, onEnter, offset);
switch (ast.type_) {
case 'pattern': {
- resolveReplications(ast);
+ // resolveReplications(ast);
const children = ast.source_.map((child) => enter(child)).map(applyOptions(ast, enter));
const alignment = ast.arguments_.alignment;
const with_tactus = children.filter((child) => child.__tactus_source);
diff --git a/packages/mini/package.json b/packages/mini/package.json
index cb4f459c6..7bd730111 100644
--- a/packages/mini/package.json
+++ b/packages/mini/package.json
@@ -9,6 +9,7 @@
},
"scripts": {
"test": "vitest run",
+ "bench": "vitest bench",
"build:parser": "peggy -o krill-parser.js --format es ./krill.pegjs",
"build": "vite build",
"prepublishOnly": "npm run build"
diff --git a/packages/superdough/dspworklet.mjs b/packages/superdough/dspworklet.mjs
index deff485a3..ed5c1e7e0 100644
--- a/packages/superdough/dspworklet.mjs
+++ b/packages/superdough/dspworklet.mjs
@@ -74,6 +74,6 @@ export const dough = async (code) => {
worklet.node.connect(ac.destination);
};
-export function doughTrigger(t, hap, currentTime, duration, cps) {
- window.postMessage({ time: t, dough: hap.value, currentTime, duration, cps });
+export function doughTrigger(time_deprecate, hap, currentTime, cps, targetTime) {
+ window.postMessage({ time: targetTime, dough: hap.value, currentTime, duration: hap.duration, cps });
}
diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs
index 3fa89ca63..99eca92b5 100644
--- a/packages/superdough/worklets.mjs
+++ b/packages/superdough/worklets.mjs
@@ -130,6 +130,7 @@ class AMProcessor extends AudioWorkletProcessor {
}
registerProcessor('am-processor', AMProcessor);
+const blockSize = 128;
class CoarseProcessor extends AudioWorkletProcessor {
static get parameterDescriptors() {
return [{ name: 'coarse', defaultValue: 1 }];
diff --git a/website/src/repl/util.mjs b/website/src/repl/util.mjs
index 6dba7dabe..3147ae38c 100644
--- a/website/src/repl/util.mjs
+++ b/website/src/repl/util.mjs
@@ -26,13 +26,13 @@ export async function initCode() {
// load code from url hash (either short hash from database or decode long hash)
try {
const initialUrl = window.location.href;
- const hash = initialUrl.split('?')[1]?.split('#')?.[0];
+ const hash = initialUrl.split('?')[1]?.split('#')?.[0]?.split('&')[0];
const codeParam = window.location.href.split('#')[1] || '';
- // looking like https://strudel.cc/?J01s5i1J0200 (fixed hash length)
if (codeParam) {
// looking like https://strudel.cc/#ImMzIGUzIg%3D%3D (hash length depends on code length)
return hash2code(codeParam);
} else if (hash) {
+ // looking like https://strudel.cc/?J01s5i1J0200 (fixed hash length)
return supabase
.from('code_v1')
.select('code')