diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs
index 51aac609c..319ab1ccb 100644
--- a/packages/core/signal.mjs
+++ b/packages/core/signal.mjs
@@ -530,7 +530,7 @@ export const undegrade = register('undegrade', (pat) => pat._undegradeBy(0.5), t
export const sometimesBy = register('sometimesBy', function (patx, func, pat) {
return reify(patx)
- .fmap((x) => stack(pat._degradeBy(x), func(pat)._undegradeBy(1 - x)))
+ .fmap((x) => stack(pat._degradeBy(x), func(pat._undegradeBy(1 - x))))
.innerJoin();
});
diff --git a/packages/core/test/pattern.test.mjs b/packages/core/test/pattern.test.mjs
index 2d29ea5db..7e6d8fd82 100644
--- a/packages/core/test/pattern.test.mjs
+++ b/packages/core/test/pattern.test.mjs
@@ -53,7 +53,6 @@ import {
stackCentre,
s_cat,
calculateTactus,
- sometimes,
} from '../index.mjs';
import { steady } from '../signal.mjs';
@@ -1267,13 +1266,4 @@ describe('Pattern', () => {
expect(s('bev').chop(8).loopAt(2).tactus).toStrictEqual(Fraction(4));
});
});
- describe('sometimes', () => {
- it('works with constant functions', () => {
- expect(
- pure('a')
- .sometimes((x) => pure('b'))
- .fast(16).firstCycleValues.length,
- ).toStrictEqual(16);
- });
- });
});
diff --git a/packages/draw/pianoroll.mjs b/packages/draw/pianoroll.mjs
index 2c6742cdf..d874c9686 100644
--- a/packages/draw/pianoroll.mjs
+++ b/packages/draw/pianoroll.mjs
@@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see .
*/
-import { Pattern, noteToMidi, freqToMidi } from '@strudel/core';
+import { Pattern, noteToMidi, freqToMidi, isPattern } from '@strudel/core';
import { getTheme, getDrawContext } from './draw.mjs';
const scale = (normalized, min, max) => normalized * (max - min) + min;
@@ -36,35 +36,9 @@ const getValue = (e) => {
return value;
};
-Pattern.prototype.pianoroll = function (options = {}) {
- let { cycles = 4, playhead = 0.5, overscan = 0, hideNegative = false, ctx = getDrawContext(), id = 1 } = options;
-
- let from = -cycles * playhead;
- let to = cycles * (1 - playhead);
- const inFrame = (hap, t) => (!hideNegative || hap.whole.begin >= 0) && hap.isWithinTime(t + from, t + to);
- this.draw(
- (haps, time) => {
- pianoroll({
- ...options,
- time,
- ctx,
- haps: haps.filter((hap) => inFrame(hap, time)),
- });
- },
- {
- lookbehind: from - overscan,
- lookahead: to + overscan,
- id,
- },
- );
- return this;
-};
-
-// this function allows drawing a pianoroll without ties to Pattern.prototype
-// it will probably replace the above in the future
-
/**
- * Displays a midi-style piano roll
+ * Visualises a pattern as a scrolling 'pianoroll', displayed in the background of the editor. To show a pianoroll for all running patterns, use `all(pianoroll)`. To have a pianoroll appear below
+ * a pattern instead, prefix with `_`, e.g.: `sound("bd sd")._pianoroll()`.
*
* @name pianoroll
* @synonyms punchcard
@@ -93,15 +67,51 @@ Pattern.prototype.pianoroll = function (options = {}) {
* @param {integer} minMidi minimum note value to display on the value axis - defaults to 10
* @param {integer} maxMidi maximum note value to display on the value axis - defaults to 90
* @param {boolean} autorange automatically calculate the minMidi and maxMidi parameters - 0 by default
- *
+ * @see _pianoroll
* @example
* note("c2 a2 eb2")
* .euclid(5,8)
* .s('sawtooth')
* .lpenv(4).lpf(300)
- * ._pianoroll({ labels: 1 })
+ * .pianoroll({ labels: 1 })
*/
-export function pianoroll({
+
+Pattern.prototype.pianoroll = function (options = {}) {
+ let { cycles = 4, playhead = 0.5, overscan = 0, hideNegative = false, ctx = getDrawContext(), id = 1 } = options;
+
+ let from = -cycles * playhead;
+ let to = cycles * (1 - playhead);
+ const inFrame = (hap, t) => (!hideNegative || hap.whole.begin >= 0) && hap.isWithinTime(t + from, t + to);
+ this.draw(
+ (haps, time) => {
+ __pianoroll({
+ ...options,
+ time,
+ ctx,
+ haps: haps.filter((hap) => inFrame(hap, time)),
+ });
+ },
+ {
+ lookbehind: from - overscan,
+ lookahead: to + overscan,
+ id,
+ },
+ );
+ return this;
+};
+
+export function pianoroll(arg) {
+ if (isPattern(arg)) {
+ // Single argument as a pattern
+ // (to support `all(pianoroll)`)
+ return arg.pianoroll();
+ }
+ // Single argument with option - return function to get the pattern
+ // (to support `all(pianoroll(options))`)
+ return (pat) => pat.pianoroll(arg);
+}
+
+export function __pianoroll({
time,
haps,
cycles = 4,
@@ -278,7 +288,7 @@ export function getDrawOptions(drawTime, options = {}) {
export const getPunchcardPainter =
(options = {}) =>
(ctx, time, haps, drawTime) =>
- pianoroll({ ctx, time, haps, ...getDrawOptions(drawTime, options) });
+ __pianoroll({ ctx, time, haps, ...getDrawOptions(drawTime, options) });
Pattern.prototype.punchcard = function (options) {
return this.onPaint(getPunchcardPainter(options));
@@ -302,5 +312,5 @@ Pattern.prototype.wordfall = function (options) {
export function drawPianoroll(options) {
const { drawTime, ...rest } = options;
- pianoroll({ ...getDrawOptions(drawTime), ...rest });
+ __pianoroll({ ...getDrawOptions(drawTime), ...rest });
}
diff --git a/packages/mqtt/mqtt.mjs b/packages/mqtt/mqtt.mjs
index 75f7904e6..0d02a37c0 100644
--- a/packages/mqtt/mqtt.mjs
+++ b/packages/mqtt/mqtt.mjs
@@ -23,6 +23,9 @@ function onMessageArrived(message) {
function onFailure(err) {
console.error('Connection failed: ', err);
+ if (typeof window !== 'undefined') {
+ document.cookie = 'mqtt_pass=';
+ }
}
Pattern.prototype.mqtt = function (
@@ -35,12 +38,17 @@ Pattern.prototype.mqtt = function (
) {
const key = host + '-' + client;
let connected = false;
+ let password_entered = false;
+
if (!client) {
client = 'strudel-' + String(Math.floor(Math.random() * 1000000));
}
function onConnect() {
console.log('Connected to mqtt broker');
connected = true;
+ if (password_entered) {
+ document.cookie = 'mqtt_pass=' + password;
+ }
}
let cx;
@@ -58,6 +66,17 @@ Pattern.prototype.mqtt = function (
if (username) {
props.userName = username;
+ if (typeof password === 'undefined' && typeof window !== 'undefined') {
+ const cookie = /mqtt_pass=(\w+)/.exec(window.document.cookie);
+ if (cookie) {
+ password = cookie[1];
+ }
+ if (typeof password === 'undefined') {
+ password = prompt('Please enter MQTT server password');
+ password_entered = true;
+ }
+ }
+
props.password = password;
}
cx.connect(props);
diff --git a/packages/reference/README.md b/packages/reference/README.md
new file mode 100644
index 000000000..8ff162597
--- /dev/null
+++ b/packages/reference/README.md
@@ -0,0 +1,8 @@
+# @strudel/reference
+
+this package contains metadata for all documented strudel functions, useful to implement a reference.
+
+```js
+import { reference } from '@strudel/reference';
+console.log(reference)
+```
diff --git a/packages/reference/index.mjs b/packages/reference/index.mjs
new file mode 100644
index 000000000..deeb60392
--- /dev/null
+++ b/packages/reference/index.mjs
@@ -0,0 +1,2 @@
+import jsdoc from '../../doc.json';
+export const reference = jsdoc;
diff --git a/packages/reference/package.json b/packages/reference/package.json
new file mode 100644
index 000000000..1782fca97
--- /dev/null
+++ b/packages/reference/package.json
@@ -0,0 +1,39 @@
+{
+ "name": "@strudel/reference",
+ "version": "1.1.0",
+ "description": "Headless reference of all strudel functions",
+ "main": "index.mjs",
+ "type": "module",
+ "publishConfig": {
+ "main": "dist/index.mjs"
+ },
+ "scripts": {
+ "build": "vite build",
+ "prepublishOnly": "npm run build"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/tidalcycles/strudel.git"
+ },
+ "keywords": [
+ "tidalcycles",
+ "strudel",
+ "pattern",
+ "livecoding",
+ "algorave"
+ ],
+ "author": "Felix Roos ",
+ "contributors": [
+ "Alex McLean "
+ ],
+ "license": "AGPL-3.0-or-later",
+ "bugs": {
+ "url": "https://github.com/tidalcycles/strudel/issues"
+ },
+ "homepage": "https://github.com/tidalcycles/strudel#readme",
+ "dependencies": {
+ },
+ "devDependencies": {
+ "vite": "^5.0.10"
+ }
+}
diff --git a/packages/reference/vite.config.js b/packages/reference/vite.config.js
new file mode 100644
index 000000000..5df3edc1b
--- /dev/null
+++ b/packages/reference/vite.config.js
@@ -0,0 +1,19 @@
+import { defineConfig } from 'vite';
+import { dependencies } from './package.json';
+import { resolve } from 'path';
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [],
+ build: {
+ lib: {
+ entry: resolve(__dirname, 'index.mjs'),
+ formats: ['es'],
+ fileName: (ext) => ({ es: 'index.mjs' })[ext],
+ },
+ rollupOptions: {
+ external: [...Object.keys(dependencies)],
+ },
+ target: 'esnext',
+ },
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6cb259a93..245296521 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -360,6 +360,12 @@ importers:
specifier: ^5.0.10
version: 5.4.9(@types/node@22.7.6)(terser@5.36.0)
+ packages/reference:
+ devDependencies:
+ vite:
+ specifier: ^5.0.10
+ version: 5.4.9(@types/node@22.7.6)(terser@5.36.0)
+
packages/repl:
dependencies:
'@strudel/codemirror':
@@ -7759,7 +7765,6 @@ packages:
workbox-google-analytics@7.0.0:
resolution: {integrity: sha512-MEYM1JTn/qiC3DbpvP2BVhyIH+dV/5BjHk756u9VbwuAhu0QHyKscTnisQuz21lfRpOwiS9z4XdqeVAKol0bzg==}
- deprecated: It is not compatible with newer versions of GA starting with v4, as long as you are using GAv3 it should be ok, but the package is not longer being maintained
workbox-navigation-preload@7.0.0:
resolution: {integrity: sha512-juWCSrxo/fiMz3RsvDspeSLGmbgC0U9tKqcUPZBCf35s64wlaLXyn2KdHHXVQrb2cqF7I0Hc9siQalainmnXJA==}
diff --git a/test/__snapshots__/tunes.test.mjs.snap b/test/__snapshots__/tunes.test.mjs.snap
index 840f9f329..f851077a8 100644
--- a/test/__snapshots__/tunes.test.mjs.snap
+++ b/test/__snapshots__/tunes.test.mjs.snap
@@ -8,7 +8,6 @@ exports[`renders tunes > tune: amensister 1`] = `
"[ 0/1 → 1/4 | n:0 s:amencutup room:0.5 ]",
"[ 1/16 → 1/8 | s:breath room:1 shape:0.6 begin:0.875 end:0.9375 ]",
"[ 1/8 → 3/16 | s:breath room:1 shape:0.6 begin:0.8125 end:0.875 ]",
- "[ 1/8 → 1/4 | n:0 s:amencutup room:0.5 ]",
"[ 1/8 → 1/4 | note:45 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:300.174310575404 ]",
"[ 1/8 → 1/4 | note:45 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:300.174310575404 ]",
"[ 3/16 → 1/4 | s:breath room:1 shape:0.6 begin:0.75 end:0.8125 ]",
@@ -18,20 +17,22 @@ exports[`renders tunes > tune: amensister 1`] = `
"[ 1/4 → 3/8 | note:A1 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:300.7878869297153 ]",
"[ 5/16 → 3/8 | s:breath room:1 shape:0.6 begin:0.625 end:0.6875 ]",
"[ 3/8 → 7/16 | s:breath room:1 shape:0.6 begin:0.5625 end:0.625 ]",
+ "[ 3/8 → 1/2 | n:1 s:amencutup room:0.5 ]",
"[ 3/8 → 1/2 | note:F1 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:302.11020572391345 ]",
"[ 3/8 → 1/2 | note:F1 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:302.11020572391345 ]",
"[ 7/16 → 1/2 | s:breath room:1 shape:0.6 begin:0.5 end:0.5625 ]",
"[ 1/2 → 9/16 | s:breath room:1 shape:0.6 begin:0.4375 end:0.5 ]",
- "[ 1/2 → 5/8 | n:2 s:amencutup room:0.5 ]",
"[ 1/2 → 3/4 | n:2 s:amencutup room:0.5 ]",
"[ 9/16 → 5/8 | s:breath room:1 shape:0.6 begin:0.375 end:0.4375 ]",
"[ 5/8 → 11/16 | s:breath room:1 shape:0.6 begin:0.3125 end:0.375 ]",
"[ 11/16 → 3/4 | s:breath room:1 shape:0.6 begin:0.25 end:0.3125 ]",
"[ 3/4 → 13/16 | s:breath room:1 shape:0.6 begin:0.1875 end:0.25 ]",
+ "[ 3/4 → 7/8 | n:3 s:amencutup room:0.5 ]",
"[ 3/4 → 7/8 | note:Bb0 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:312.54769231985796 ]",
"[ 3/4 → 7/8 | note:Bb0 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:312.54769231985796 ]",
"[ 13/16 → 7/8 | s:breath room:1 shape:0.6 begin:0.125 end:0.1875 ]",
"[ 7/8 → 15/16 | s:breath room:1 shape:0.6 begin:0.0625 end:0.125 ]",
+ "[ 7/8 → 1/1 | n:3 s:amencutup room:0.5 ]",
"[ 7/8 → 1/1 | note:D1 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:318.7927796831686 ]",
"[ 7/8 → 1/1 | note:D1 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:318.7927796831686 ]",
"[ 15/16 → 1/1 | s:breath room:1 shape:0.6 begin:0 end:0.0625 ]",
@@ -6905,10 +6906,10 @@ exports[`renders tunes > tune: flatrave 1`] = `
"[ 1/8 → 1/4 | s:hh n:1 speed:0.5 delay:0.5 end:0.020001936784171157 bank:RolandTR909 room:0.5 gain:0.4 ]",
"[ 1/8 → 1/4 | s:hh n:1 speed:0.5 delay:0.5 end:0.020001936784171157 bank:RolandTR909 room:0.5 gain:0.4 ]",
"[ 1/8 → 1/4 | note:G1 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 cutoff:800 resonance:8 ]",
+ "[ 1/4 → 3/8 | s:hh n:1 end:0.02000875429921906 bank:RolandTR909 room:0.5 gain:0.4 ]",
+ "[ 1/4 → 3/8 | s:hh n:1 end:0.02000875429921906 bank:RolandTR909 room:0.5 gain:0.4 ]",
"[ 1/4 → 3/8 | note:G1 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 cutoff:800 resonance:8 ]",
"[ 3/8 → 1/2 | s:hh n:1 end:0.020023446730265706 bank:RolandTR909 room:0.5 gain:0.4 ]",
- "[ 1/2 → 5/8 | s:hh n:1 speed:0.5 delay:0.5 end:0.020048626493108724 bank:RolandTR909 room:0.5 gain:0.4 ]",
- "[ 1/2 → 5/8 | s:hh n:1 speed:0.5 delay:0.5 end:0.020048626493108724 bank:RolandTR909 room:0.5 gain:0.4 ]",
"[ 1/2 → 5/8 | note:G1 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 cutoff:800 resonance:8 ]",
"[ 1/2 → 1/1 | s:bd bank:RolandTR909 ]",
"[ 1/2 → 1/1 | s:cp bank:RolandTR909 ]",
@@ -6916,6 +6917,7 @@ exports[`renders tunes > tune: flatrave 1`] = `
"[ 5/8 → 3/4 | s:hh n:1 end:0.020086608138500644 bank:RolandTR909 room:0.5 gain:0.4 ]",
"[ 5/8 → 3/4 | s:hh n:1 end:0.020086608138500644 bank:RolandTR909 room:0.5 gain:0.4 ]",
"[ 5/8 → 3/4 | note:G1 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 cutoff:800 resonance:8 ]",
+ "[ 3/4 → 7/8 | s:hh n:1 end:0.02013941880355398 bank:RolandTR909 room:0.5 gain:0.4 ]",
"[ 7/8 → 1/1 | note:G1 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 cutoff:800 resonance:8 ]",
]
`;