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 ]", ] `;