diff --git a/README.md b/README.md index 4aa90fd3..baaac82b 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,6 @@ Live coding patterns on the web https://strudel.cc/ -Development is moving to https://codeberg.org/uzu/strudel - - Try it here: - Docs: - Technical Blog Post: @@ -47,3 +45,5 @@ There is a #strudel channel on the TidalCycles discord: The discord and forum is shared with the haskell (tidal) and python (vortex) siblings of this project. + +We also have a mastodon account: social.toplap.org/@strudel diff --git a/packages/codemirror/autocomplete.mjs b/packages/codemirror/autocomplete.mjs index 203ab855..c0845895 100644 --- a/packages/codemirror/autocomplete.mjs +++ b/packages/codemirror/autocomplete.mjs @@ -1,68 +1,91 @@ import jsdoc from '../../doc.json'; -// import { javascriptLanguage } from '@codemirror/lang-javascript'; import { autocompletion } from '@codemirror/autocomplete'; import { h } from './html'; -function plaintext(str) { +const escapeHtml = (str) => { const div = document.createElement('div'); div.innerText = str; return div.innerHTML; -} +}; -const getDocLabel = (doc) => doc.name || doc.longname; -const getInnerText = (html) => { - var div = document.createElement('div'); +const stripHtml = (html) => { + const div = document.createElement('div'); div.innerHTML = html; return div.textContent || div.innerText || ''; }; -export function Autocomplete({ doc, label }) { - return h`
-

${label || getDocLabel(doc)}

-${doc.description} -
    - ${doc.params?.map( - ({ name, type, description }) => - `
  • ${name} : ${type.names?.join(' | ')} ${description ? ` - ${getInnerText(description)}` : ''}
  • `, - )} -
-
- ${doc.examples?.map((example) => `
${plaintext(example)}
`)} -
-
`[0]; - /* -
 {
-  console.log('ola!');
-  navigator.clipboard.writeText(example);
-  e.stopPropagation();
-}}
->
-{example}
-
-*/ -} +const getDocLabel = (doc) => doc.name || doc.longname; + +const buildParamsList = (params) => + params?.length + ? ` +
+

Parameters

+
    + ${params + .map( + ({ name, type, description }) => ` +
  • + ${name} + ${type.names?.join(' | ')} + ${description ? `
    ${stripHtml(description)}
    ` : ''} +
  • + `, + ) + .join('')} +
+
+ ` + : ''; + +const buildExamples = (examples) => + examples?.length + ? ` +
+

Examples

+ ${examples + .map( + (example) => ` +
${escapeHtml(example)}
+ `, + ) + .join('')} +
+ ` + : ''; + +export const Autocomplete = ({ doc, label }) => + h` +
+

${label || getDocLabel(doc)}

+ ${doc.description ? `

${doc.description}

` : ''} + ${buildParamsList(doc.params)} + ${buildExamples(doc.examples)} +
+`[0]; + +const isValidDoc = (doc) => { + const label = getDocLabel(doc); + return label && !label.startsWith('_') && !['package'].includes(doc.kind); +}; + +const hasExcludedTags = (doc) => + ['superdirtOnly', 'noAutocomplete'].some((tag) => doc.tags?.find((t) => t.originalTitle === tag)); const jsdocCompletions = jsdoc.docs - .filter( - (doc) => - getDocLabel(doc) && - !getDocLabel(doc).startsWith('_') && - !['package'].includes(doc.kind) && - !['superdirtOnly', 'noAutocomplete'].some((tag) => doc.tags?.find((t) => t.originalTitle === tag)), - ) + .filter((doc) => isValidDoc(doc) && !hasExcludedTags(doc)) // https://codemirror.net/docs/ref/#autocomplete.Completion - .map((doc) /*: Completion */ => ({ + .map((doc) => ({ label: getDocLabel(doc), // detail: 'xxx', // An optional short piece of information to show (with a different style) after the label. info: () => Autocomplete({ doc }), type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type })); -export const strudelAutocomplete = (context /* : CompletionContext */) => { - let word = context.matchBefore(/\w*/); - if (word.from == word.to && !context.explicit) return null; +export const strudelAutocomplete = (context) => { + const word = context.matchBefore(/\w*/); + if (word.from === word.to && !context.explicit) return null; + return { from: word.from, options: jsdocCompletions, @@ -74,11 +97,5 @@ export const strudelAutocomplete = (context /* : CompletionContext */) => { }; }; -export function isAutoCompletionEnabled(on) { - return on - ? [ - autocompletion({ override: [strudelAutocomplete] }), - //javascriptLanguage.data.of({ autocomplete: strudelAutocomplete }), - ] - : []; // autocompletion({ override: [] }) -} +export const isAutoCompletionEnabled = (enabled) => + enabled ? [autocompletion({ override: [strudelAutocomplete] })] : []; diff --git a/packages/codemirror/package.json b/packages/codemirror/package.json index 4f8508c9..797a4312 100644 --- a/packages/codemirror/package.json +++ b/packages/codemirror/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/codemirror", - "version": "1.2.2", + "version": "1.2.3", "description": "Codemirror Extensions for Strudel", "main": "index.mjs", "publishConfig": { diff --git a/packages/core/bench/pattern.bench.mjs b/packages/core/bench/pattern.bench.mjs index 1b5be0b9..56a84980 100644 --- a/packages/core/bench/pattern.bench.mjs +++ b/packages/core/bench/pattern.bench.mjs @@ -1,11 +1,11 @@ import { describe, bench } from 'vitest'; -import { calculateTactus, sequence, stack } from '../index.mjs'; +import { calculateSteps, sequence, stack } from '../index.mjs'; const pat64 = sequence(...Array(64).keys()); describe('steps', () => { - calculateTactus(true); + calculateSteps(true); bench( '+tactus', () => { @@ -14,7 +14,7 @@ describe('steps', () => { { time: 1000 }, ); - calculateTactus(false); + calculateSteps(false); bench( '-tactus', () => { @@ -25,7 +25,7 @@ describe('steps', () => { }); describe('stack', () => { - calculateTactus(true); + calculateSteps(true); bench( '+tactus', () => { @@ -34,7 +34,7 @@ describe('stack', () => { { time: 1000 }, ); - calculateTactus(false); + calculateSteps(false); bench( '-tactus', () => { @@ -43,4 +43,4 @@ describe('stack', () => { { time: 1000 }, ); }); -calculateTactus(true); +calculateSteps(true); diff --git a/packages/core/euclid.mjs b/packages/core/euclid.mjs index 25e12b07..44ab07f1 100644 --- a/packages/core/euclid.mjs +++ b/packages/core/euclid.mjs @@ -10,7 +10,7 @@ https://rohandrape.net/?t=hmt This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -import { timeCat, register, silence } from './pattern.mjs'; +import { timeCat, register, silence, stack, pure, _morph } from './pattern.mjs'; import { rotate, flatten, splitAt, zipWith } from './util.mjs'; import Fraction, { lcm } from './fraction.mjs'; @@ -196,3 +196,26 @@ export const euclidLegato = register(['euclidLegato'], function (pulses, steps, export const euclidLegatoRot = register(['euclidLegatoRot'], function (pulses, steps, rotation, pat) { return _euclidLegato(pulses, steps, rotation, pat); }); + +/** + * A 'euclid' variant with an additional parameter that morphs the resulting + * rhythm from 0 (no morphing) to 1 (completely 'even'). For example + * `sound("bd").euclidish(3,8,0)` would be the same as + * `sound("bd").euclid(3,8)`, and `sound("bd").euclidish(3,8,1)` would be the + * same as `sound("bd bd bd")`. `sound("bd").euclidish(3,8,0.5)` would have a + * groove somewhere between. + * Inspired by the work of Malcom Braff. + * @name euclidish + * @synonyms eish + * @memberof Pattern + * @param {number} pulses the number of onsets + * @param {number} steps the number of steps to fill + * @param {number} groove exists between the extremes of 0 (straight euclidian) and 1 (straight pulse) + * @example + * sound("hh").euclidish(7,12,sine.slow(8)) + * .pan(sine.slow(8)) + */ +export const { euclidish, eish } = register(['euclidish', 'eish'], function (pulses, steps, perc, pat) { + const morphed = _morph(bjork(pulses, steps), new Array(pulses).fill(1), perc); + return pat.struct(morphed).setSteps(steps); +}); diff --git a/packages/core/package.json b/packages/core/package.json index f4170f2b..33540c8b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/core", - "version": "1.2.2", + "version": "1.2.3", "description": "Port of Tidal Cycles to JavaScript", "main": "index.mjs", "type": "module", diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 56f2c16e..addc301d 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -21,6 +21,7 @@ import { numeralArgs, parseNumeral, pairs, + zipWith, stringifyValues, } from './util.mjs'; import drawLine from './drawLine.mjs'; @@ -3416,3 +3417,69 @@ export const { beat } = register( ['beat'], __beat((x) => x.innerJoin()), ); + +export const _morph = (from, to, by) => { + by = Fraction(by); + const dur = Fraction(1).div(from.length); + const positions = (list) => { + const result = []; + for (const [pos, value] of list.entries()) { + if (value) { + result.push([Fraction(pos).div(list.length), value]); + } + } + return result; + }; + const arcs = zipWith( + ([posa, valuea], [posb, valueb]) => { + const b = by.mul(posb - posa).add(posa); + const e = b.add(dur); + return new TimeSpan(b, e); + }, + positions(from), + positions(to), + ); + function query(state) { + const cycle = state.span.begin.sam(); + const cycleArc = state.span.cycleArc(); + const result = []; + for (const whole of arcs) { + const part = whole.intersection(cycleArc); + if (part !== undefined) { + result.push( + new Hap( + whole.withTime((x) => x.add(cycle)), + part.withTime((x) => x.add(cycle)), + true, + ), + ); + } + } + return result; + } + return new Pattern(query).splitQueries(); +}; + +/** + * Takes two binary rhythms represented as lists of 1s and 0s, and a number + * between 0 and 1 that morphs between them. The two lists should contain the same + * number of true values. + * @example + * sound("hh").struct(morph([1,0,1,0,1,0,1,0], // straight rhythm + * [1,1,0,1,0,1,0], // wonky rhythm + * 0.25 // creates a slightly wonky rhythm + * ) + * ) + * @example + * sound("hh").struct(morph("1:0:1:0:1:0:1:0", // straight rhythm + * "1:1:0:1:0:1:0", // wonky rhythm + * sine.slow(8) // slowly morph between the rhythms + * ) + * ) + */ +export const morph = (frompat, topat, bypat) => { + frompat = reify(frompat); + topat = reify(topat); + bypat = reify(bypat); + return frompat.innerBind((from) => topat.innerBind((to) => bypat.innerBind((by) => _morph(from, to, by)))); +}; diff --git a/packages/core/timespan.mjs b/packages/core/timespan.mjs index 0dbc74fc..446156bb 100644 --- a/packages/core/timespan.mjs +++ b/packages/core/timespan.mjs @@ -72,7 +72,7 @@ export class TimeSpan { } intersection(other) { - // Intersection of two timespans, returns None if they don't intersect. + // Intersection of two timespans, returns undefined if they don't intersect. const intersect_begin = this.begin.max(other.begin); const intersect_end = this.end.min(other.end); diff --git a/packages/csound/package.json b/packages/csound/package.json index 04a5ff24..19b1dd6f 100644 --- a/packages/csound/package.json +++ b/packages/csound/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/csound", - "version": "1.2.3", + "version": "1.2.4", "description": "csound bindings for strudel", "main": "index.mjs", "type": "module", diff --git a/packages/draw/package.json b/packages/draw/package.json index ee1b8dd0..f6336740 100644 --- a/packages/draw/package.json +++ b/packages/draw/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/draw", - "version": "1.2.2", + "version": "1.2.3", "description": "Helpers for drawing with Strudel", "main": "index.mjs", "type": "module", diff --git a/packages/embed/package.json b/packages/embed/package.json index a0cc33de..6a00ee90 100644 --- a/packages/embed/package.json +++ b/packages/embed/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/embed", - "version": "1.1.0", + "version": "1.1.1", "description": "Embeddable Web Component to load a Strudel REPL into an iframe", "main": "embed.js", "type": "module", diff --git a/packages/gamepad/package.json b/packages/gamepad/package.json index 3efb2e08..53fad18a 100644 --- a/packages/gamepad/package.json +++ b/packages/gamepad/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/gamepad", - "version": "1.2.2", + "version": "1.2.3", "description": "Gamepad Inputs for strudel", "main": "index.mjs", "type": "module", diff --git a/packages/hydra/package.json b/packages/hydra/package.json index b022de87..1553264d 100644 --- a/packages/hydra/package.json +++ b/packages/hydra/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/hydra", - "version": "1.2.2", + "version": "1.2.3", "description": "Hydra integration for strudel", "main": "hydra.mjs", "type": "module", diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index d0e09293..ae983d20 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -493,6 +493,9 @@ export async function midin(input) { otherInputs?.length ? `Also available: ${getMidiDeviceNamesString(otherInputs)}` : '' }`, ); + } + // ensure refs for this input are initialized + if (!refs[input]) { refs[input] = {}; } const cc = (cc) => ref(() => refs[input][cc] || 0); diff --git a/packages/midi/package.json b/packages/midi/package.json index 4efd329d..8dde3598 100644 --- a/packages/midi/package.json +++ b/packages/midi/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/midi", - "version": "1.2.3", + "version": "1.2.4", "description": "Midi API for strudel", "main": "index.mjs", "type": "module", diff --git a/packages/mini/bench/mini.bench.mjs b/packages/mini/bench/mini.bench.mjs index 782ac86b..e7471bf2 100644 --- a/packages/mini/bench/mini.bench.mjs +++ b/packages/mini/bench/mini.bench.mjs @@ -1,10 +1,10 @@ import { describe, bench } from 'vitest'; -import { calculateTactus } from '../../core/index.mjs'; +import { calculateSteps } from '../../core/index.mjs'; import { mini } from '../index.mjs'; describe('mini', () => { - calculateTactus(true); + calculateSteps(true); bench( '+tactus', () => { @@ -13,7 +13,7 @@ describe('mini', () => { { time: 1000 }, ); - calculateTactus(false); + calculateSteps(false); bench( '-tactus', () => { @@ -21,5 +21,5 @@ describe('mini', () => { }, { time: 1000 }, ); - calculateTactus(true); + calculateSteps(true); }); diff --git a/packages/mini/package.json b/packages/mini/package.json index 5d94301d..e8fca4ac 100644 --- a/packages/mini/package.json +++ b/packages/mini/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/mini", - "version": "1.2.2", + "version": "1.2.3", "description": "Mini notation for strudel", "main": "index.mjs", "type": "module", diff --git a/packages/mondo/package.json b/packages/mondo/package.json index 277bddb1..a59bfc34 100644 --- a/packages/mondo/package.json +++ b/packages/mondo/package.json @@ -1,6 +1,6 @@ { "name": "mondolang", - "version": "1.1.0", + "version": "1.1.1", "description": "a language for functional composition that translates to js", "main": "mondo.mjs", "type": "module", diff --git a/packages/mondough/package.json b/packages/mondough/package.json index a034424c..99a87f09 100644 --- a/packages/mondough/package.json +++ b/packages/mondough/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/mondo", - "version": "1.1.0", + "version": "1.1.1", "description": "mondo notation for strudel", "main": "mondough.mjs", "type": "module", diff --git a/packages/motion/package.json b/packages/motion/package.json index a7db0568..acacc154 100644 --- a/packages/motion/package.json +++ b/packages/motion/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/motion", - "version": "1.2.2", + "version": "1.2.3", "description": "DeviceMotion API for strudel", "main": "index.mjs", "type": "module", diff --git a/packages/mqtt/package.json b/packages/mqtt/package.json index f522e335..d5ac6dd7 100644 --- a/packages/mqtt/package.json +++ b/packages/mqtt/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/mqtt", - "version": "1.2.2", + "version": "1.2.3", "description": "MQTT API for strudel", "main": "mqtt.mjs", "type": "module", diff --git a/packages/osc/package.json b/packages/osc/package.json index 7d19fbbf..6e8b5781 100644 --- a/packages/osc/package.json +++ b/packages/osc/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/osc", - "version": "1.2.2", + "version": "1.2.3", "description": "OSC messaging for strudel", "main": "osc.mjs", "type": "module", diff --git a/packages/reference/package.json b/packages/reference/package.json index 8dc966cc..634057e4 100644 --- a/packages/reference/package.json +++ b/packages/reference/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/reference", - "version": "1.2.0", + "version": "1.2.1", "description": "Headless reference of all strudel functions", "main": "index.mjs", "type": "module", diff --git a/packages/repl/package.json b/packages/repl/package.json index bfa404c7..aabd1b8c 100644 --- a/packages/repl/package.json +++ b/packages/repl/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/repl", - "version": "1.2.3", + "version": "1.2.4", "description": "Strudel REPL as a Web Component", "module": "index.mjs", "publishConfig": { diff --git a/packages/sampler/README.md b/packages/sampler/README.md index c495c2ad..1142176b 100644 --- a/packages/sampler/README.md +++ b/packages/sampler/README.md @@ -20,3 +20,13 @@ samples('http://localhost:5432') LOG=1 npx @strudel/sampler # adds logging PORT=5555 npx @strudel/sampler # changes port ``` + +## static json + +when running with `--json`, you will simply get the json logged back: + +```sh +npx --yes @strudel/sampler --json > strudel.json +``` + +this is useful if you want to create a sample pack from the current folder. \ No newline at end of file diff --git a/packages/sampler/package.json b/packages/sampler/package.json index b0f27f86..dd701bcf 100644 --- a/packages/sampler/package.json +++ b/packages/sampler/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/sampler", - "version": "0.2.0", + "version": "0.2.2", "description": "", "keywords": [ "tidalcycles", diff --git a/packages/sampler/sample-server.mjs b/packages/sampler/sample-server.mjs index d1e56108..08456add 100644 --- a/packages/sampler/sample-server.mjs +++ b/packages/sampler/sample-server.mjs @@ -10,14 +10,6 @@ import os from 'os'; // eslint-disable-next-line const LOG = !!process.env.LOG || false; -console.log( - cowsay.say({ - text: 'welcome to @strudel/sampler', - e: 'oO', - T: 'U ', - }), -); - async function getFilesInDirectory(directory) { let files = []; const dirents = await readdir(directory, { withFileTypes: true }); @@ -60,8 +52,26 @@ async function getBanks(directory) { return { banks, files }; } +const args = process.argv.slice(2); + // eslint-disable-next-line const directory = process.cwd(); + +if (args.includes('--json')) { + const { banks, files } = await getBanks(directory); + const json = JSON.stringify(banks); + console.log(json); + process.exit(0); +} + +console.log( + cowsay.say({ + text: 'welcome to @strudel/sampler', + e: 'oO', + T: 'U ', + }), +); + const server = http.createServer(async (req, res) => { res.setHeader('Access-Control-Allow-Origin', '*'); const { banks, files } = await getBanks(directory); diff --git a/packages/serial/package.json b/packages/serial/package.json index c04a69cd..8abbe769 100644 --- a/packages/serial/package.json +++ b/packages/serial/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/serial", - "version": "1.2.2", + "version": "1.2.3", "description": "Webserial API for strudel", "main": "serial.mjs", "type": "module", diff --git a/packages/soundfonts/package.json b/packages/soundfonts/package.json index 2c87a6e0..e5c1780e 100644 --- a/packages/soundfonts/package.json +++ b/packages/soundfonts/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/soundfonts", - "version": "1.2.3", + "version": "1.2.4", "description": "Soundsfont support for strudel", "main": "index.mjs", "publishConfig": { diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index c09acf1a..81bec939 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -211,11 +211,29 @@ export function getVibratoOscillator(param, value, t) { export function webAudioTimeout(audioContext, onComplete, startTime, stopTime) { const constantNode = new ConstantSourceNode(audioContext); - constantNode.start(startTime); - constantNode.stop(stopTime); + // Certain browsers requires audio nodes to be connected in order for their onended events + // to fire, so we _mute it_ and then connect it to the destination + const zeroGain = gainNode(0); + zeroGain.connect(audioContext.destination); + constantNode.connect(zeroGain); + + // Schedule the `onComplete` callback to occur at `stopTime` constantNode.onended = () => { + // Ensure garbage collection + try { + zeroGain.disconnect(); + } catch { + // pass + } + try { + constantNode.disconnect(); + } catch { + // pass + } onComplete(); }; + constantNode.start(startTime); + constantNode.stop(stopTime); return constantNode; } const mod = (freq, range = 1, type = 'sine') => { diff --git a/packages/superdough/package.json b/packages/superdough/package.json index 439b8371..809da022 100644 --- a/packages/superdough/package.json +++ b/packages/superdough/package.json @@ -1,6 +1,6 @@ { "name": "superdough", - "version": "1.2.3", + "version": "1.2.4", "description": "simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.", "main": "index.mjs", "type": "module", diff --git a/packages/tonal/package.json b/packages/tonal/package.json index 614e86f7..de02e4b1 100644 --- a/packages/tonal/package.json +++ b/packages/tonal/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/tonal", - "version": "1.2.2", + "version": "1.2.3", "description": "Tonal functions for strudel", "main": "index.mjs", "publishConfig": { diff --git a/packages/transpiler/package.json b/packages/transpiler/package.json index 2a5e3977..f9ebdfde 100644 --- a/packages/transpiler/package.json +++ b/packages/transpiler/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/transpiler", - "version": "1.2.2", + "version": "1.2.3", "description": "Transpiler for strudel user code. Converts syntactically correct but semantically meaningless JS into evaluatable strudel code.", "main": "index.mjs", "type": "module", diff --git a/packages/web/package.json b/packages/web/package.json index 0feddc82..3f9cc50d 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/web", - "version": "1.2.3", + "version": "1.2.4", "description": "Easy to setup, opiniated bundle of Strudel for the browser.", "module": "web.mjs", "publishConfig": { diff --git a/packages/webaudio/package.json b/packages/webaudio/package.json index 5cc0a553..78988340 100644 --- a/packages/webaudio/package.json +++ b/packages/webaudio/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/webaudio", - "version": "1.2.3", + "version": "1.2.4", "description": "Web Audio helpers for Strudel", "main": "index.mjs", "type": "module", diff --git a/packages/xen/package.json b/packages/xen/package.json index 88c2bb08..1cb75115 100644 --- a/packages/xen/package.json +++ b/packages/xen/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/xen", - "version": "1.2.2", + "version": "1.2.3", "description": "Xenharmonic API for strudel", "main": "index.mjs", "type": "module", diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 3409ce1b..41207620 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -3410,6 +3410,39 @@ exports[`runs examples > example "euclidRot" example index 0 1`] = ` ] `; +exports[`runs examples > example "euclidish" example index 0 1`] = ` +[ + "[ 0/1 → 1/12 | s:hh pan:0.5 ]", + "[ 13/84 → 5/21 | s:hh pan:0.5606253170575308 ]", + "[ 15/56 → 59/168 | s:hh pan:0.604413082836085 ]", + "[ 71/168 → 85/168 | s:hh pan:0.6629314122869361 ]", + "[ 97/168 → 37/56 | s:hh pan:0.7190455010067492 ]", + "[ 29/42 → 65/84 | s:hh pan:0.7580531369037533 ]", + "[ 71/84 → 13/14 | s:hh pan:0.8080762739548087 ]", + "[ 1/1 → 13/12 | s:hh pan:0.8535533905932737 ]", + "[ 451099417/393511398 → 322594689/262340932 | s:hh pan:0.891768001805729 ]", + "[ 335923379/262340932 → 536677685/393511398 | s:hh pan:0.9222657853371297 ]", + "[ 1122946175/787022796 → 99044284/65585233 | s:hh pan:0.9501869591788796 ]", + "[ 1238122213/787022796 → 651853723/393511398 | s:hh pan:0.9721673436944069 ]", + "[ 335923379/196755699 → 469759583/262340932 | s:hh pan:0.9868472639237561 ]", + "[ 729434777/393511398 → 1524454787/787022796 | s:hh pan:0.9967009321321423 ]", + "[ 2/1 → 25/12 | s:hh pan:1 ]", + "[ 15/7 → 187/84 | s:hh pan:0.9968561049466214 ]", + "[ 16/7 → 199/84 | s:hh pan:0.9874639560909118 ]", + "[ 17/7 → 211/84 | s:hh pan:0.9719416651541839 ]", + "[ 18/7 → 223/84 | s:hh pan:0.9504844339512095 ]", + "[ 19/7 → 235/84 | s:hh pan:0.9233620996141421 ]", + "[ 20/7 → 247/84 | s:hh pan:0.890915741234015 ]", + "[ 3/1 → 37/12 | s:hh pan:0.8535533905932737 ]", + "[ 1238122213/393511398 → 847276553/262340932 | s:hh pan:0.8106731928589048 ]", + "[ 860605243/262340932 → 1323700481/393511398 | s:hh pan:0.7677528833339 ]", + "[ 2696991767/787022796 → 230214750/65585233 | s:hh pan:0.7175585019834292 ]", + "[ 2812167805/787022796 → 1438876519/393511398 | s:hh pan:0.6644931595798675 ]", + "[ 729434777/196755699 → 994441447/262340932 | s:hh pan:0.6139286689554151 ]", + "[ 1516457573/393511398 → 3098500379/787022796 | s:hh pan:0.557342689325327 ]", +] +`; + exports[`runs examples > example "every" example index 0 1`] = ` [ "[ 0/1 → 1/4 | note:g3 ]", @@ -6211,6 +6244,48 @@ exports[`runs examples > example "miditouch" example index 0 1`] = ` ] `; +exports[`runs examples > example "morph" example index 0 1`] = ` +[ + "[ 0/1 → 1/8 | s:hh ]", + "[ 25/112 → 39/112 | s:hh ]", + "[ 27/56 → 17/28 | s:hh ]", + "[ 83/112 → 97/112 | s:hh ]", + "[ 1/1 → 9/8 | s:hh ]", + "[ 137/112 → 151/112 | s:hh ]", + "[ 83/56 → 45/28 | s:hh ]", + "[ 195/112 → 209/112 | s:hh ]", + "[ 2/1 → 17/8 | s:hh ]", + "[ 249/112 → 263/112 | s:hh ]", + "[ 139/56 → 73/28 | s:hh ]", + "[ 307/112 → 321/112 | s:hh ]", + "[ 3/1 → 25/8 | s:hh ]", + "[ 361/112 → 375/112 | s:hh ]", + "[ 195/56 → 101/28 | s:hh ]", + "[ 419/112 → 433/112 | s:hh ]", +] +`; + +exports[`runs examples > example "morph" example index 1 1`] = ` +[ + "[ 0/1 → 1/8 | s:hh ]", + "[ 11/56 → 9/28 | s:hh ]", + "[ 13/28 → 33/56 | s:hh ]", + "[ 41/56 → 6/7 | s:hh ]", + "[ 1/1 → 9/8 | s:hh ]", + "[ 303934523/262340932 → 673454279/524681864 | s:hh ]", + "[ 188758485/131170466 → 820619173/524681864 | s:hh ]", + "[ 451099417/262340932 → 967784067/524681864 | s:hh ]", + "[ 2/1 → 17/8 | s:hh ]", + "[ 15/7 → 127/56 | s:hh ]", + "[ 17/7 → 143/56 | s:hh ]", + "[ 19/7 → 159/56 | s:hh ]", + "[ 3/1 → 25/8 | s:hh ]", + "[ 828616387/262340932 → 1722818007/524681864 | s:hh ]", + "[ 451099417/131170466 → 1869982901/524681864 | s:hh ]", + "[ 975781281/262340932 → 2017147795/524681864 | s:hh ]", +] +`; + exports[`runs examples > example "mousex" example index 0 1`] = ` [ "[ 0/1 → 1/4 | note:C3 ]", diff --git a/website/package.json b/website/package.json index 49975814..87f4025e 100644 --- a/website/package.json +++ b/website/package.json @@ -8,8 +8,7 @@ "start": "astro dev", "build": "astro build", "preview": "astro preview --port 3009 --host 0.0.0.0", - "astro": "astro", - "postinstall": "cp node_modules/hs2js/dist/tree-sitter.wasm public && cp node_modules/hs2js/dist/tree-sitter-haskell.wasm public" + "astro": "astro" }, "dependencies": { "@algolia/client-search": "^5.20.0", diff --git a/website/src/pages/learn/conditional-modifiers.mdx b/website/src/pages/learn/conditional-modifiers.mdx index c2e22595..d31ab0cf 100644 --- a/website/src/pages/learn/conditional-modifiers.mdx +++ b/website/src/pages/learn/conditional-modifiers.mdx @@ -34,11 +34,11 @@ import { JsDoc } from '../../docs/JsDoc'; ## arp - + ## arpWith 🧪 - + ## struct @@ -58,7 +58,7 @@ import { JsDoc } from '../../docs/JsDoc'; ## hush - + ## invert diff --git a/website/src/pages/learn/effects.mdx b/website/src/pages/learn/effects.mdx index 8f6a3b8a..89f36fe9 100644 --- a/website/src/pages/learn/effects.mdx +++ b/website/src/pages/learn/effects.mdx @@ -339,4 +339,18 @@ global effects use the same chain for all events of the same orbit: +## Duck + +### duckorbit + + + +### duckattack + + + +### duckdepth + + + Next, we'll look at input / output via [MIDI, OSC and other methods](/learn/input-output). diff --git a/website/src/pages/learn/samples.mdx b/website/src/pages/learn/samples.mdx index bbe650cf..a87b8f7d 100644 --- a/website/src/pages/learn/samples.mdx +++ b/website/src/pages/learn/samples.mdx @@ -178,6 +178,16 @@ the version number). It is also possible, of course, to just remove it from cache (deleting cache in browser Privacy settings, or from the dev console if you're technically minded, or by using a cache deleting extension). +## Generating strudel.json + +You can use [@strudel/sampler](https://www.npmjs.com/package/@strudel/sampler) to generate a strudel.json file for you, by running: + +```sh +npx --yes @strudel/sampler --json > strudel.json +``` + +See other uses of strudel/sampler further below, under "From Disk via @strudel/sampler". + ## Github Shortcut Because loading samples from github is common, there is a shortcut: @@ -361,6 +371,10 @@ Sampler effects are functions that can be used to change the behaviour of sample +### scrub + +{' '} + ### speed diff --git a/website/src/repl/Repl.css b/website/src/repl/Repl.css index 3e13ff5a..b8443081 100644 --- a/website/src/repl/Repl.css +++ b/website/src/repl/Repl.css @@ -69,3 +69,133 @@ text-decoration: underline 0.18rem; text-underline-offset: 0.22rem; } + +/* Override default styles from the codemirror inline css for autocomplete info tooltip*/ +.cm-tooltip.cm-completionInfo { + padding: 12px !important; + padding-bottom: 12px !important; + border: 1px solid var(--foreground) !important; + border-radius: 4px !important; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3) !important; + max-width: 500px !important; + min-width: 300px !important; + max-height: 400px !important; + white-space: normal !important; + overflow: auto !important; + background-color: var(--lineHighlight) !important; +} + +/* Main tooltip container */ +.autocomplete-info-tooltip { + border-radius: 4px !important; + color: var(--foreground); + font-family: var(--font-family, 'SF Mono', 'Monaco', monospace); + font-size: var(--font-size, 13px); + line-height: 1.4; + max-width: 600px; + max-height: 400px; + min-width: 400px; +} + +.autocomplete-info-function-name { + font-size: 15px; + font-weight: 600; + color: var(--foreground); + margin: 0 0 8px 0; +} + +.autocomplete-info-function-description { + margin: 0 0 12px 0; + color: var(--foreground); + line-height: 1.5; + opacity: 0.8; +} + +.autocomplete-info-section-title { + font-size: 12px; + font-weight: 600; + color: var(--foreground); + margin: 16px 0 6px 0; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.autocomplete-info-section-title:first-child { + margin-top: 0; +} + +.autocomplete-info-params-section { + margin-top: 12px; +} + +.autocomplete-info-params-list { + list-style: none; + margin: 0; + padding: 0; +} + +.autocomplete-info-param-item { + margin-bottom: 8px; + padding: 8px; + background-color: var(--lineBackground); + border-radius: 3px; + border-left: 2px solid var(--foreground, #555); +} + +.autocomplete-info-param-item:last-child { + margin-bottom: 0; +} + +.autocomplete-info-param-name { + font-weight: 600; + color: var(--variable, var(--foreground)); + margin-right: 8px; +} + +.autocomplete-info-param-type { + color: var(--comment); + font-size: 12px; + background-color: var(--gutterForeground); + padding: 1px 4px; + border-radius: 2px; +} + +.autocomplete-info-param-desc { + color: var(--foreground); + font-size: 10px; + margin-top: 4px; + line-height: 1.4; + opacity: 0.7; +} + +.autocomplete-info-examples-section { + margin-top: 12px; +} + +.autocomplete-info-example-code { + background: var(--lineBackground); + color: var(--foreground); + padding: 8px; + border-radius: 3px; + font-family: var(--font-family, 'SF Mono', 'Monaco', monospace); + font-size: 12px; + line-height: 1.5; + margin: 4px 0; + overflow-x: auto; + white-space: pre; + border: 1px solid var(--foreground, #3a3a3a); +} + +.autocomplete-info-tooltip::-webkit-scrollbar { + width: 4px; +} + +.autocomplete-info-tooltip::-webkit-scrollbar-track { +} + +.autocomplete-info-tooltip::-webkit-scrollbar-thumb { + border-radius: 2px; +} + +.autocomplete-info-tooltip::-webkit-scrollbar-thumb:hover { +}