From e4b00117c311dd309fbe0e969a5053911264b8d7 Mon Sep 17 00:00:00 2001 From: "Alexandre G.-Raymond" Date: Sat, 11 Nov 2023 14:22:52 +0100 Subject: [PATCH 01/29] Parse the synonyms tag in source documentation --- jsdoc-synonyms.js | 17 +++++++++++++++++ jsdoc.config.json | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 jsdoc-synonyms.js diff --git a/jsdoc-synonyms.js b/jsdoc-synonyms.js new file mode 100644 index 00000000..29aed7b2 --- /dev/null +++ b/jsdoc-synonyms.js @@ -0,0 +1,17 @@ +/* +jsdoc-synonyms.js - Add support for @synonym tag +Copyright (C) 2023 Strudel contributors - see +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 . +*/ + +function defineTags(dictionary) { + dictionary.defineTag('synonyms', { + mustHaveValue: true, + onTagged: function(doclet, tag) { + doclet.synonyms_text = tag.value; + doclet.synonyms = doclet.synonyms_text.split(/[ ,]+/); + } +}); +}; + +module.exports = {defineTags: defineTags} \ No newline at end of file diff --git a/jsdoc.config.json b/jsdoc.config.json index ca9c3d81..4098a5c5 100644 --- a/jsdoc.config.json +++ b/jsdoc.config.json @@ -3,7 +3,7 @@ "includePattern": ".+\\.(js(doc|x)?|mjs)$", "excludePattern": "node_modules|shift-parser|shift-reducer|shift-traverser|dist" }, - "plugins": ["plugins/markdown"], + "plugins": ["plugins/markdown", "jsdoc-synonyms"], "opts": { "destination": "./out/", "recurse": true From bbc292561f20b3fc04637c135693a55f1a77d734 Mon Sep 17 00:00:00 2001 From: "Alexandre G.-Raymond" Date: Sat, 11 Nov 2023 14:23:30 +0100 Subject: [PATCH 02/29] Use synonyms in Tooltip --- packages/react/src/components/Tooltip.jsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/react/src/components/Tooltip.jsx b/packages/react/src/components/Tooltip.jsx index a443c123..a508e761 100644 --- a/packages/react/src/components/Tooltip.jsx +++ b/packages/react/src/components/Tooltip.jsx @@ -31,6 +31,9 @@ window.addEventListener( export const strudelTooltip = hoverTooltip( (view, pos, side) => { // Word selection from CodeMirror Hover Tooltip example https://codemirror.net/examples/tooltip/#hover-tooltips + if (!ctrlDown) { + return null; + } let { from, to, text } = view.state.doc.lineAt(pos); let start = pos, end = pos; @@ -47,11 +50,14 @@ export const strudelTooltip = hoverTooltip( // Get entry from Strudel documentation let entry = jsdoc.docs.filter((doc) => getDocLabel(doc) === word)[0]; if (!entry) { - return null; - } - if (!ctrlDown) { - return null; + // Try for synonyms + entry = jsdoc.docs.filter((doc) => doc.synonyms && doc.synonyms.includes(word))[0]; + if (!entry) { + return null; + } + entry.name = word; } + return { pos: start, end, From 6ba2b767b0145e1f5463f4c49bae1d1670735066 Mon Sep 17 00:00:00 2001 From: "Alexandre G.-Raymond" Date: Sat, 11 Nov 2023 14:23:55 +0100 Subject: [PATCH 03/29] Show synonyms in API reference --- website/src/repl/Reference.jsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/website/src/repl/Reference.jsx b/website/src/repl/Reference.jsx index cf6fd5b1..b43f365e 100644 --- a/website/src/repl/Reference.jsx +++ b/website/src/repl/Reference.jsx @@ -37,6 +37,11 @@ export function Reference() { {visibleFunctions.map((entry, i) => (

{entry.name}

+ {!!entry.synonyms_text && ( +

+ Synonyms: {entry.synonyms_text} +

+ )} {/* {entry.meta.filename} */}

    From 2aff31fe06bcaf3bbfce7df12ef91c847466eccc Mon Sep 17 00:00:00 2001 From: "Alexandre G.-Raymond" Date: Sat, 11 Nov 2023 14:24:20 +0100 Subject: [PATCH 04/29] Simplify JSDoc --- website/src/docs/JsDoc.jsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/website/src/docs/JsDoc.jsx b/website/src/docs/JsDoc.jsx index 1ba80e0a..1a7a034f 100644 --- a/website/src/docs/JsDoc.jsx +++ b/website/src/docs/JsDoc.jsx @@ -2,19 +2,15 @@ import jsdoc from '../../../doc.json'; // doc.json is built with `npm run jsdoc- const docs = jsdoc.docs.reduce((acc, obj) => Object.assign(acc, { [obj.longname]: obj }), {}); import { MiniRepl } from './MiniRepl'; -const getTag = (title, item) => item.tags?.find((t) => t.title === title)?.text; - export function JsDoc({ name, h = 3, hideDescription, punchcard, canvasHeight }) { const item = docs[name]; if (!item) { console.warn('Not found: ' + name); return
    ; } - const synonyms = getTag('synonyms', item)?.split(', ') || []; const CustomHeading = `h${h}`; const description = item.description?.replaceAll(/\{@link ([a-zA-Z\.]+)?#?([a-zA-Z]*)\}/g, (_, a, b) => { - // console.log(_, 'a', a, 'b', b); return `${a}${b ? `#${b}` : ''}`; }) || ''; return ( @@ -22,9 +18,9 @@ export function JsDoc({ name, h = 3, hideDescription, punchcard, canvasHeight }) {!!h && {item.longname}} {!hideDescription && ( <> - {!!synonyms.length && ( + {!!item.synonyms_text && ( - Synonyms: {synonyms.join(', ')} + Synonyms: {item.synonyms_text} )}
    From bfff38a262f46daf0b9186fac35672740bdf7f2c Mon Sep 17 00:00:00 2001 From: "Alexandre G.-Raymond" Date: Sat, 11 Nov 2023 15:44:15 +0100 Subject: [PATCH 05/29] Fix code format of JSDoc synonym plugin --- jsdoc-synonyms.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/jsdoc-synonyms.js b/jsdoc-synonyms.js index 29aed7b2..09190846 100644 --- a/jsdoc-synonyms.js +++ b/jsdoc-synonyms.js @@ -7,11 +7,11 @@ This program is free software: you can redistribute it and/or modify it under th function defineTags(dictionary) { dictionary.defineTag('synonyms', { mustHaveValue: true, - onTagged: function(doclet, tag) { + onTagged: function (doclet, tag) { doclet.synonyms_text = tag.value; doclet.synonyms = doclet.synonyms_text.split(/[ ,]+/); - } -}); -}; + }, + }); +} -module.exports = {defineTags: defineTags} \ No newline at end of file +module.exports = { defineTags: defineTags }; From 24e18818768b8109a6978b869fe08b78f14fc0d2 Mon Sep 17 00:00:00 2001 From: "Alexandre G.-Raymond" Date: Sat, 11 Nov 2023 15:53:03 +0100 Subject: [PATCH 06/29] Ignore jsdoc-synonyms by eslint --- .eslintignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.eslintignore b/.eslintignore index 92c95c85..7d807b65 100644 --- a/.eslintignore +++ b/.eslintignore @@ -20,4 +20,5 @@ vite.config.js **/dist /src-tauri/target/**/* reverbGen.mjs -hydra.mjs \ No newline at end of file +hydra.mjs +jsdoc-synonyms.js \ No newline at end of file From 476bda812e93380d633800d0a99ab280e914ed39 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Fri, 17 Nov 2023 12:55:17 +0100 Subject: [PATCH 07/29] Initial work on crackle --- packages/superdough/noise.mjs | 17 +++++++++++++---- packages/superdough/synth.mjs | 4 ++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/packages/superdough/noise.mjs b/packages/superdough/noise.mjs index 2c8c1d4a..6a2d418c 100644 --- a/packages/superdough/noise.mjs +++ b/packages/superdough/noise.mjs @@ -4,7 +4,7 @@ import { getAudioContext } from './superdough.mjs'; let noiseCache = {}; // lazy generates noise buffers and keeps them forever -function getNoiseBuffer(type) { +function getNoiseBuffer(type, density) { const ac = getAudioContext(); if (noiseCache[type]) { return noiseCache[type]; @@ -34,17 +34,26 @@ function getNoiseBuffer(type) { output[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362; output[i] *= 0.11; b6 = white * 0.115926; + } else if (type === 'crackle') { + if (Math.random() < (Math.random() * (density - 0.001) + density).toFixed(4)) { + output[i] = Math.random() * 2 - 1; + } else { + output[i] = 0; + } } } - noiseCache[type] = noiseBuffer; + + // Prevent caching to randomize crackles + if (type !== "crackle") + noiseCache[type] = noiseBuffer; return noiseBuffer; } // expects one of noises as type -export function getNoiseOscillator(type = 'white', t) { +export function getNoiseOscillator(type = 'white', t, density = 0.02) { const ac = getAudioContext(); const o = ac.createBufferSource(); - o.buffer = getNoiseBuffer(type); + o.buffer = getNoiseBuffer(type, density); o.loop = true; o.start(t); return { diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index dafc2e7c..53f462d5 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -22,7 +22,7 @@ const fm = (osc, harmonicityRatio, modulationIndex, wave = 'sine') => { }; const waveforms = ['sine', 'square', 'triangle', 'sawtooth']; -const noises = ['pink', 'white', 'brown']; +const noises = ['pink', 'white', 'brown', 'crackle']; export function registerSynthSounds() { [...waveforms, ...noises].forEach((s) => { @@ -36,7 +36,7 @@ export function registerSynthSounds() { if (waveforms.includes(s)) { sound = getOscillator(s, t, value); } else { - sound = getNoiseOscillator(s, t); + sound = getNoiseOscillator(s, t, 0.002); } let { node: o, stop, triggerRelease } = sound; From fb28083ce616de5b3a14e36a9cc415d6802b5be8 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Fri, 17 Nov 2023 13:18:49 +0100 Subject: [PATCH 08/29] Fixing density and adding documentation --- packages/core/controls.mjs | 15 +++++++++++++-- packages/superdough/noise.mjs | 3 ++- packages/superdough/superdough.mjs | 1 + packages/superdough/synth.mjs | 3 ++- website/src/pages/learn/synths.mdx | 4 ++++ 5 files changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index a03658a8..a14f06df 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -1214,6 +1214,17 @@ const generic_params = [ * @name waveloss */ ['waveloss'], + /* + * + * Noise crackle density + * + * @name density + * @param {number | Pattern} density between 0 and x + * @example + * s("crackle*4").density("<0.01 0.04 0.2 0.5>".slow(4)) + * + */ + ['density'], // TODO: midi effects? ['dur'], // ['modwheel'], @@ -1308,7 +1319,7 @@ const generic_params = [ ]; // TODO: slice / splice https://www.youtube.com/watch?v=hKhPdO0RKDQ&list=PL2lW1zNIIwj3bDkh-Y3LUGDuRcoUigoDs&index=13 -controls.createParam = function (names) { +controls.createParam = function(names) { const name = Array.isArray(names) ? names[0] : names; var withVal; @@ -1332,7 +1343,7 @@ controls.createParam = function (names) { const func = (...pats) => sequence(...pats).withValue(withVal); - const setter = function (...pats) { + const setter = function(...pats) { if (!pats.length) { return this.fmap(withVal); } diff --git a/packages/superdough/noise.mjs b/packages/superdough/noise.mjs index 6a2d418c..8993f470 100644 --- a/packages/superdough/noise.mjs +++ b/packages/superdough/noise.mjs @@ -35,7 +35,8 @@ function getNoiseBuffer(type, density) { output[i] *= 0.11; b6 = white * 0.115926; } else if (type === 'crackle') { - if (Math.random() < (Math.random() * (density - 0.001) + density).toFixed(4)) { + const probability = density * 0.01 + if (Math.random() < probability) { output[i] = Math.random() * 2 - 1; } else { output[i] = 0; diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 00e2f42c..ed133366 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -241,6 +241,7 @@ export const superdough = async (value, deadline, hapDuration) => { source, gain = 0.8, postgain = 1, + density = 0.03, // filters ftype = '12db', fanchor = 0.5, diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 53f462d5..edc3c01e 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -36,7 +36,8 @@ export function registerSynthSounds() { if (waveforms.includes(s)) { sound = getOscillator(s, t, value); } else { - sound = getNoiseOscillator(s, t, 0.002); + let { density } = value; + sound = getNoiseOscillator(s, t, density); } let { node: o, stop, triggerRelease } = sound; diff --git a/website/src/pages/learn/synths.mdx b/website/src/pages/learn/synths.mdx index 432276ef..abb74c2f 100644 --- a/website/src/pages/learn/synths.mdx +++ b/website/src/pages/learn/synths.mdx @@ -42,6 +42,10 @@ Some amount of pink noise can also be added to any oscillator by using the `nois ").scope()`} /> +You can also use the `crackle` type to play some subtle noise crackles. You can control noise amount by using the `density` parameter: + + + ### Additive Synthesis To tame the harsh sound of the basic waveforms, we can set the `n` control to limit the overtones of the waveform: From 2a18df61cda07a2e4328b53fddd27e091293861a Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Fri, 17 Nov 2023 13:23:54 +0100 Subject: [PATCH 09/29] Fix documentation --- packages/core/controls.mjs | 1 - website/src/pages/learn/synths.mdx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index a14f06df..1cd69e81 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -1215,7 +1215,6 @@ const generic_params = [ */ ['waveloss'], /* - * * Noise crackle density * * @name density diff --git a/website/src/pages/learn/synths.mdx b/website/src/pages/learn/synths.mdx index abb74c2f..d6ac5061 100644 --- a/website/src/pages/learn/synths.mdx +++ b/website/src/pages/learn/synths.mdx @@ -44,7 +44,7 @@ Some amount of pink noise can also be added to any oscillator by using the `nois You can also use the `crackle` type to play some subtle noise crackles. You can control noise amount by using the `density` parameter: - +".slow(4)).scope()`} /> ### Additive Synthesis From 6e92c4915aaad88590fbb8d9ff0b3c8e398d28f7 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Fri, 17 Nov 2023 13:37:16 +0100 Subject: [PATCH 10/29] Run prettier --- packages/core/controls.mjs | 4 ++-- packages/superdough/noise.mjs | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 1cd69e81..6449969e 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -1318,7 +1318,7 @@ const generic_params = [ ]; // TODO: slice / splice https://www.youtube.com/watch?v=hKhPdO0RKDQ&list=PL2lW1zNIIwj3bDkh-Y3LUGDuRcoUigoDs&index=13 -controls.createParam = function(names) { +controls.createParam = function (names) { const name = Array.isArray(names) ? names[0] : names; var withVal; @@ -1342,7 +1342,7 @@ controls.createParam = function(names) { const func = (...pats) => sequence(...pats).withValue(withVal); - const setter = function(...pats) { + const setter = function (...pats) { if (!pats.length) { return this.fmap(withVal); } diff --git a/packages/superdough/noise.mjs b/packages/superdough/noise.mjs index 8993f470..24779470 100644 --- a/packages/superdough/noise.mjs +++ b/packages/superdough/noise.mjs @@ -35,7 +35,7 @@ function getNoiseBuffer(type, density) { output[i] *= 0.11; b6 = white * 0.115926; } else if (type === 'crackle') { - const probability = density * 0.01 + const probability = density * 0.01; if (Math.random() < probability) { output[i] = Math.random() * 2 - 1; } else { @@ -45,8 +45,7 @@ function getNoiseBuffer(type, density) { } // Prevent caching to randomize crackles - if (type !== "crackle") - noiseCache[type] = noiseBuffer; + if (type !== 'crackle') noiseCache[type] = noiseBuffer; return noiseBuffer; } From c0f56be2a9d4eb0de18094fbebed77a9200a5cfc Mon Sep 17 00:00:00 2001 From: Kaspars Date: Fri, 17 Nov 2023 20:41:37 +0100 Subject: [PATCH 11/29] add options param to initHydra --- packages/hydra/hydra.mjs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/hydra/hydra.mjs b/packages/hydra/hydra.mjs index 692e4c14..3e86504c 100644 --- a/packages/hydra/hydra.mjs +++ b/packages/hydra/hydra.mjs @@ -1,13 +1,19 @@ import { getDrawContext } from '@strudel.cycles/core'; -export async function initHydra() { +export async function initHydra( + options = { + src: 'https://unpkg.com/hydra-synth', + detectAudio: false, + }, +) { if (!document.getElementById('hydra-canvas')) { const { canvas: testCanvas } = getDrawContext(); - await import('https://unpkg.com/hydra-synth'); + const { src, ...opts } = options; + await import(src); const hydraCanvas = testCanvas.cloneNode(true); hydraCanvas.id = 'hydra-canvas'; testCanvas.after(hydraCanvas); - new Hydra({ canvas: hydraCanvas, detectAudio: false }); + new Hydra(Object.assign({ canvas: hydraCanvas }, opts)); s0.init({ src: testCanvas }); } } From de00212bb6ed586d604bfa7fddf7ce0a99934065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81mbar=20Tenorio=20Forn=C3=A9s?= Date: Wed, 22 Nov 2023 12:27:18 +0100 Subject: [PATCH 12/29] fix hydra performance issues --- packages/hydra/hydra.mjs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/hydra/hydra.mjs b/packages/hydra/hydra.mjs index 692e4c14..b63ca423 100644 --- a/packages/hydra/hydra.mjs +++ b/packages/hydra/hydra.mjs @@ -4,11 +4,11 @@ export async function initHydra() { if (!document.getElementById('hydra-canvas')) { const { canvas: testCanvas } = getDrawContext(); await import('https://unpkg.com/hydra-synth'); - const hydraCanvas = testCanvas.cloneNode(true); - hydraCanvas.id = 'hydra-canvas'; - testCanvas.after(hydraCanvas); - new Hydra({ canvas: hydraCanvas, detectAudio: false }); - s0.init({ src: testCanvas }); + h = new Hydra({detectAudio: false }); + h.canvas.id = 'hydra-canvas'; + h.canvas.style.position = 'absolute'; + h.canvas.style.top = '0px'; + testCanvas.after(h.canvas); } } From 66fd20ace5bd8e54bf76c3013dd73f31df91fde8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81mbar=20Tenorio=20Forn=C3=A9s?= Date: Wed, 22 Nov 2023 12:36:43 +0100 Subject: [PATCH 13/29] add hydra audio capture option --- packages/hydra/README.md | 7 +++++++ packages/hydra/hydra.mjs | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/hydra/README.md b/packages/hydra/README.md index d300bb88..e36a421b 100644 --- a/packages/hydra/README.md +++ b/packages/hydra/README.md @@ -12,6 +12,13 @@ await initHydra(); Then you can use hydra below! +### Enable audio capture +Use `{audio: true}` param to enable Hydra's audio capture: + +```js +await initHydra({audio: true}); +``` + ## Usage via npm ```sh diff --git a/packages/hydra/hydra.mjs b/packages/hydra/hydra.mjs index b63ca423..e105b440 100644 --- a/packages/hydra/hydra.mjs +++ b/packages/hydra/hydra.mjs @@ -1,10 +1,10 @@ import { getDrawContext } from '@strudel.cycles/core'; -export async function initHydra() { +export async function initHydra(config) { if (!document.getElementById('hydra-canvas')) { const { canvas: testCanvas } = getDrawContext(); await import('https://unpkg.com/hydra-synth'); - h = new Hydra({detectAudio: false }); + h = new Hydra({detectAudio: config?.audio }); h.canvas.id = 'hydra-canvas'; h.canvas.style.position = 'absolute'; h.canvas.style.top = '0px'; From ebc6723f55d4879b3bfcb4853363fd14885a6228 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81mbar=20Tenorio=20Forn=C3=A9s?= Date: Wed, 22 Nov 2023 13:51:01 +0100 Subject: [PATCH 14/29] enable hydra options update --- packages/hydra/hydra.mjs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/hydra/hydra.mjs b/packages/hydra/hydra.mjs index e105b440..778f3361 100644 --- a/packages/hydra/hydra.mjs +++ b/packages/hydra/hydra.mjs @@ -1,6 +1,10 @@ import { getDrawContext } from '@strudel.cycles/core'; +let options = "" + export async function initHydra(config) { + + //load and init hydra if (!document.getElementById('hydra-canvas')) { const { canvas: testCanvas } = getDrawContext(); await import('https://unpkg.com/hydra-synth'); @@ -10,6 +14,17 @@ export async function initHydra(config) { h.canvas.style.top = '0px'; testCanvas.after(h.canvas); } + + // update options + if (options != JSON.stringify(config)) { + options = JSON.stringify(config); + + new Hydra( + { + canvas: document.getElementById('hydra-canvas'), + detectAudio: config?.audio + }) + } } export const H = (p) => () => p.queryArc(getTime(), getTime())[0].value; From a58ac967a620246b6432834cc7df4a80b87aa487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81mbar=20Tenorio=20Forn=C3=A9s?= Date: Wed, 22 Nov 2023 13:51:42 +0100 Subject: [PATCH 15/29] add Hydra audio example to documentation --- website/src/pages/learn/hydra.mdx | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/website/src/pages/learn/hydra.mdx b/website/src/pages/learn/hydra.mdx index a506e53c..dd892ff7 100644 --- a/website/src/pages/learn/hydra.mdx +++ b/website/src/pages/learn/hydra.mdx @@ -48,7 +48,23 @@ There is a special function `H` that allows you to use a pattern as an input to tune={`await initHydra() let pattern = "3 4 5 [6 7]*2" shape(H(pattern)).out(o0) -n(pattern).scale("A:minor").piano().room(1) +n(pattern).scale("A:minor").piano().room(1) +`} +/> + +To use hydra audio capture, call `initHydra` with `{audio:true}` configuration param: + + a.fft[0]*.25 + ) + .add(src(o0).color(.71 ).scrollX(.005),.95) +.out(o0) +n(pattern).scale("A:minor").piano().room(1) `} /> From 0a280e939927ad4956ce14b3e42139935cdf83be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81mbar=20Tenorio=20Forn=C3=A9s?= Date: Wed, 22 Nov 2023 15:47:16 +0100 Subject: [PATCH 16/29] prettier code formatting --- packages/hydra/hydra.mjs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/hydra/hydra.mjs b/packages/hydra/hydra.mjs index 778f3361..25d69c93 100644 --- a/packages/hydra/hydra.mjs +++ b/packages/hydra/hydra.mjs @@ -1,14 +1,13 @@ import { getDrawContext } from '@strudel.cycles/core'; -let options = "" +let options = ''; export async function initHydra(config) { - //load and init hydra if (!document.getElementById('hydra-canvas')) { const { canvas: testCanvas } = getDrawContext(); await import('https://unpkg.com/hydra-synth'); - h = new Hydra({detectAudio: config?.audio }); + h = new Hydra({ detectAudio: config?.audio }); h.canvas.id = 'hydra-canvas'; h.canvas.style.position = 'absolute'; h.canvas.style.top = '0px'; @@ -19,11 +18,10 @@ export async function initHydra(config) { if (options != JSON.stringify(config)) { options = JSON.stringify(config); - new Hydra( - { - canvas: document.getElementById('hydra-canvas'), - detectAudio: config?.audio - }) + new Hydra({ + canvas: document.getElementById('hydra-canvas'), + detectAudio: config?.audio, + }); } } From e0e896d4b534627a7539458339868ee03157ca68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81mbar=20Tenorio=20Forn=C3=A9s?= Date: Wed, 22 Nov 2023 17:39:16 +0100 Subject: [PATCH 17/29] improve hydra audio detection initialization --- packages/hydra/hydra.mjs | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/packages/hydra/hydra.mjs b/packages/hydra/hydra.mjs index 25d69c93..3f41e256 100644 --- a/packages/hydra/hydra.mjs +++ b/packages/hydra/hydra.mjs @@ -1,27 +1,33 @@ import { getDrawContext } from '@strudel.cycles/core'; -let options = ''; +let audio = false; +let hydra; + +function appendCanvas(c){ + const { canvas: testCanvas } = getDrawContext(); + c.canvas.id = 'hydra-canvas'; + c.canvas.style.position = 'absolute'; + c.canvas.style.top = '0px'; + testCanvas.after(c.canvas); +} export async function initHydra(config) { + audio = config?.audio || false; //load and init hydra if (!document.getElementById('hydra-canvas')) { - const { canvas: testCanvas } = getDrawContext(); await import('https://unpkg.com/hydra-synth'); - h = new Hydra({ detectAudio: config?.audio }); - h.canvas.id = 'hydra-canvas'; - h.canvas.style.position = 'absolute'; - h.canvas.style.top = '0px'; - testCanvas.after(h.canvas); + hydra = new Hydra({ detectAudio: audio }); + appendCanvas(hydra); } - // update options - if (options != JSON.stringify(config)) { - options = JSON.stringify(config); - - new Hydra({ - canvas: document.getElementById('hydra-canvas'), - detectAudio: config?.audio, - }); + // if config.audio is true + // and current canvas des not detect audio + if (config?.audio && !hydra.detectAudio ){ + //remove previous canvas without audio detection + document.getElementById('hydra-canvas').remove() + // create and append a new audio responsive canvas + hydra = new Hydra({ detectAudio: audio}); + appendCanvas(hydra) } } From d71877f915aa0cd11a1d58999dd60e7a926b1cd2 Mon Sep 17 00:00:00 2001 From: Alexandre Gravel-Raymond Date: Wed, 22 Nov 2023 20:35:19 +0100 Subject: [PATCH 18/29] Move jsdoc files to subfolder --- jsdoc-synonyms.js => jsdoc/jsdoc-synonyms.js | 0 jsdoc.config.json => jsdoc/jsdoc.config.json | 2 +- undocumented.mjs => jsdoc/undocumented.mjs | 4 ++-- package.json | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) rename jsdoc-synonyms.js => jsdoc/jsdoc-synonyms.js (100%) rename jsdoc.config.json => jsdoc/jsdoc.config.json (78%) rename undocumented.mjs => jsdoc/undocumented.mjs (91%) diff --git a/jsdoc-synonyms.js b/jsdoc/jsdoc-synonyms.js similarity index 100% rename from jsdoc-synonyms.js rename to jsdoc/jsdoc-synonyms.js diff --git a/jsdoc.config.json b/jsdoc/jsdoc.config.json similarity index 78% rename from jsdoc.config.json rename to jsdoc/jsdoc.config.json index 4098a5c5..073b16c5 100644 --- a/jsdoc.config.json +++ b/jsdoc/jsdoc.config.json @@ -3,7 +3,7 @@ "includePattern": ".+\\.(js(doc|x)?|mjs)$", "excludePattern": "node_modules|shift-parser|shift-reducer|shift-traverser|dist" }, - "plugins": ["plugins/markdown", "jsdoc-synonyms"], + "plugins": ["plugins/markdown", "jsdoc/jsdoc-synonyms"], "opts": { "destination": "./out/", "recurse": true diff --git a/undocumented.mjs b/jsdoc/undocumented.mjs similarity index 91% rename from undocumented.mjs rename to jsdoc/undocumented.mjs index 281d27d8..657fdea6 100644 --- a/undocumented.mjs +++ b/jsdoc/undocumented.mjs @@ -65,7 +65,7 @@ async function getUndocumented(path, docs) { } // read doc.json file -const { docs } = JSON.parse(await readFile(resolve(__dirname, 'doc.json'), 'utf8')); +const { docs } = JSON.parse(await readFile(resolve(__dirname, '..', 'doc.json'), 'utf8')); const paths = dependencyTree.toList({ filename: 'index.mjs', @@ -76,7 +76,7 @@ const paths = dependencyTree.toList({ // const paths = ['../packages/core/pattern.mjs', '../packages/core/hap.mjs'].map((rel) => resolve(__dirname, rel)); const undocumented = Object.fromEntries( - await Promise.all(paths.map(async (path) => [path, await getUndocumented(path, docs)])), + await Promise.all(paths.map(async (path) => [path.replace(resolve(__dirname, '..'), ''), await getUndocumented(path, docs)])), ); console.log(JSON.stringify(undocumented, null, 2)); diff --git a/package.json b/package.json index 36229611..172832ee 100644 --- a/package.json +++ b/package.json @@ -18,12 +18,12 @@ "build": "npm run prebuild && cd website && npm run build", "preview": "cd website && npm run preview", "osc": "cd packages/osc && npm run server", - "jsdoc": "jsdoc packages/ -c jsdoc.config.json", - "jsdoc-json": "jsdoc packages/ --template ./node_modules/jsdoc-json --destination doc.json -c jsdoc.config.json", + "jsdoc": "jsdoc packages/ -c jsdoc/jsdoc.config.json", + "jsdoc-json": "jsdoc packages/ --template ./node_modules/jsdoc-json --destination doc.json -c jsdoc/jsdoc.config.json", "lint": "eslint . --ext mjs,js --quiet", "codeformat": "prettier --write .", "format-check": "prettier --check .", - "report-undocumented": "npm run jsdoc-json && node undocumented.mjs > undocumented.json", + "report-undocumented": "npm run jsdoc-json && node jsdoc/undocumented.mjs > undocumented.json", "check": "npm run format-check && npm run lint && npm run test", "iclc": "cd paper && pandoc --template=pandoc/iclc.html --citeproc --number-sections iclc2023.md -o iclc2023.html && pandoc --template=pandoc/iclc.latex --citeproc --number-sections iclc2023.md -o iclc2023.pdf" }, From 2868a5ff4fc21f208d83ce8ce9a4934305e284e1 Mon Sep 17 00:00:00 2001 From: Alexandre Gravel-Raymond Date: Wed, 22 Nov 2023 20:36:01 +0100 Subject: [PATCH 19/29] Update the undocumented functions file --- undocumented.json | 210 +++++++++++++++++++++++++++++----------------- 1 file changed, 134 insertions(+), 76 deletions(-) diff --git a/undocumented.json b/undocumented.json index 0d28835d..0a807c90 100644 --- a/undocumented.json +++ b/undocumented.json @@ -1,17 +1,17 @@ { - "/home/felix/projects/strudel/packages/core/fraction.mjs": [ + "/packages/core/fraction.mjs": [ "gcd" ], - "/home/felix/projects/strudel/packages/core/timespan.mjs": [ + "/packages/core/timespan.mjs": [ "TimeSpan" ], - "/home/felix/projects/strudel/packages/core/hap.mjs": [ + "/packages/core/hap.mjs": [ "Hap" ], - "/home/felix/projects/strudel/packages/core/state.mjs": [ + "/packages/core/state.mjs": [ "State" ], - "/home/felix/projects/strudel/packages/core/util.mjs": [ + "/packages/core/util.mjs": [ "isNoteWithOctave", "isNote", "tokenizeNote", @@ -34,23 +34,31 @@ "mapArgs", "numeralArgs", "parseFractional", - "fractionalArgs" + "fractionalArgs", + "splitAt", + "zipWith", + "clamp", + "sol2note" ], - "/home/felix/projects/strudel/packages/core/value.mjs": [ + "/packages/core/value.mjs": [ "unionWithObj", "valued", "Value", "map" ], - "/home/felix/projects/strudel/packages/core/drawLine.mjs": [], - "/home/felix/projects/strudel/packages/core/logger.mjs": [ + "/packages/core/drawLine.mjs": [], + "/packages/core/logger.mjs": [ "logKey", "logger" ], - "/home/felix/projects/strudel/packages/core/pattern.mjs": [ + "/packages/core/pattern.mjs": [ "setStringParser", + "polyrhythm", + "pr", + "pm", "isPattern", "reify", + "fastcat", "set", "keep", "keepif", @@ -74,22 +82,33 @@ "func", "compressSpan", "compressspan", + "fastgap", "focusSpan", "focusspan", + "density", + "sparsity", "zoomArc", "zoomarc", + "inv", + "juxby", + "echowith", + "stutWith", + "stutwith", + "iterback", + "chunkback", "bypass", "duration", - "color", "colour", - "striate" + "loopat", + "loopatcps" ], - "/home/felix/projects/strudel/packages/core/controls.mjs": [], - "/home/felix/projects/strudel/packages/core/euclid.mjs": [ + "/packages/core/controls.mjs": [], + "/packages/core/euclid.mjs": [ + "bjork", "euclidrot", "euclidLegatoRot" ], - "/home/felix/projects/strudel/packages/core/signal.mjs": [ + "/packages/core/signal.mjs": [ "steady", "signal", "isaw", @@ -112,34 +131,37 @@ "degradeByWith", "undegrade" ], - "/home/felix/projects/strudel/packages/core/speak.mjs": [ + "/packages/core/speak.mjs": [ "speak" ], - "/home/felix/projects/strudel/packages/core/evaluate.mjs": [ + "/packages/core/evaluate.mjs": [ "evalScope", "evaluate" ], - "/home/felix/projects/strudel/packages/core/zyklus.mjs": [], - "/home/felix/projects/strudel/packages/core/cyclist.mjs": [ + "/packages/core/zyklus.mjs": [], + "/packages/core/cyclist.mjs": [ "Cyclist" ], - "/home/felix/projects/strudel/packages/core/time.mjs": [ + "/packages/core/time.mjs": [ "getTime", "setTime" ], - "/home/felix/projects/strudel/packages/core/repl.mjs": [ - "repl" + "/packages/core/repl.mjs": [ + "repl", + "getTrigger" ], - "/home/felix/projects/strudel/packages/core/draw.mjs": [ + "/packages/core/draw.mjs": [ "getDrawContext", - "cleanupDraw" + "cleanupDraw", + "Framer", + "Drawer" ], - "/home/felix/projects/strudel/packages/core/animate.mjs": [ + "/packages/core/animate.mjs": [ "x", "y", "w", "h", - "a", + "angle", "r", "fill", "smear", @@ -147,85 +169,121 @@ "moveXY", "zoomIn" ], - "/home/felix/projects/strudel/packages/core/pianoroll.mjs": [ - "pianoroll" + "/packages/core/pianoroll.mjs": [ + "getDrawOptions", + "drawPianoroll" ], - "/home/felix/projects/strudel/packages/core/ui.mjs": [ + "/packages/core/spiral.mjs": [], + "/packages/core/ui.mjs": [ "backgroundImage", "cleanupUi" ], - "/home/felix/projects/strudel/packages/core/gist.js": [], - "/home/felix/projects/strudel/packages/core/index.mjs": [], - "/home/felix/projects/strudel/packages/midi/midi.mjs": [ + "/packages/core/gist.js": [], + "/packages/core/index.mjs": [], + "/packages/csound/index.mjs": [ + "loadCSound", + "loadcsound", + "loadCsound", + "csound", + "loadOrc" + ], + "/packages/desktopbridge/utils.mjs": [ + "Invoke", + "isTauri" + ], + "/packages/desktopbridge/midibridge.mjs": [], + "/packages/desktopbridge/oscbridge.mjs": [], + "/packages/desktopbridge/index.mjs": [], + "/packages/midi/midi.mjs": [ "WebMidi", - "enableWebMidi" + "enableWebMidi", + "midin" ], - "/home/felix/projects/strudel/packages/midi/index.mjs": [], - "/home/felix/projects/strudel/packages/mini/krill-parser.js": [], - "/home/felix/projects/strudel/packages/mini/mini.mjs": [ + "/packages/midi/index.mjs": [], + "/packages/mini/krill-parser.js": [], + "/packages/mini/mini.mjs": [ "patternifyAST", + "getLeafLocation", + "mini2ast", + "getLeaves", + "getLeafLocations", "mini", + "m", "h", - "minify" + "minify", + "miniAllStrings" ], - "/home/felix/projects/strudel/packages/mini/index.mjs": [], - "/home/felix/projects/strudel/packages/soundfonts/fontloader.mjs": [ + "/packages/mini/index.mjs": [], + "/packages/soundfonts/gm.mjs": [], + "/packages/soundfonts/fontloader.mjs": [ "getFontBufferSource", - "getFontPitch" + "getFontPitch", + "registerSoundfonts" ], - "/home/felix/projects/strudel/packages/soundfonts/list.mjs": [ + "/packages/soundfonts/list.mjs": [ "instruments", "drums", "instrumentNames" ], - "/home/felix/projects/strudel/packages/soundfonts/sfumato.mjs": [ + "/packages/soundfonts/sfumato.mjs": [ "loadSoundfont" ], - "/home/felix/projects/strudel/packages/soundfonts/index.mjs": [], - "/home/felix/projects/strudel/packages/tonal/tonal.mjs": [], - "/home/felix/projects/strudel/packages/tonal/voicings.mjs": [ - "voicingRegistry", - "setVoicingRange" + "/packages/soundfonts/index.mjs": [], + "/packages/tonal/tonal.mjs": [], + "/packages/tonal/tonleiter.mjs": [ + "pc2chroma", + "rotateChroma", + "chroma2pc", + "tokenizeChord", + "note2pc", + "note2oct", + "note2chroma", + "midi2chroma", + "pitch2chroma", + "step2semitones", + "x2midi", + "scaleStep", + "renderVoicing", + "accidentalOffset", + "Step", + "Note" ], - "/home/felix/projects/strudel/packages/tonal/index.mjs": [], - "/home/felix/projects/strudel/packages/transpiler/transpiler.mjs": [ + "/packages/tonal/ireal.mjs": [ + "simple", + "complex" + ], + "/packages/tonal/voicings.mjs": [ + "voicingRegistry", + "setVoicingRange", + "registerVoicings", + "voicingAlias" + ], + "/packages/tonal/index.mjs": [], + "/packages/transpiler/transpiler.mjs": [ "transpiler" ], - "/home/felix/projects/strudel/packages/transpiler/index.mjs": [ + "/packages/transpiler/index.mjs": [ "evaluate" ], - "/home/felix/projects/strudel/packages/webaudio/feedbackdelay.mjs": [], - "/home/felix/projects/strudel/packages/webaudio/reverb.mjs": [], - "/home/felix/projects/strudel/packages/webaudio/sampler.mjs": [ - "getCachedBuffer", - "getSampleBufferSource", - "loadBuffer", - "reverseBuffer", - "getLoadedBuffer", - "resetLoadedSamples", - "getLoadedSamples" - ], - "/home/felix/projects/strudel/packages/webaudio/vowel.mjs": [ - "vowelFormant" - ], - "/home/felix/projects/strudel/packages/webaudio/webaudio.mjs": [ - "getAudioContext", - "panic", - "initAudio", - "initAudioOnFirstClick", + "/packages/webaudio/webaudio.mjs": [ + "webaudioOutputTrigger", "webaudioOutput", - "webaudioOutputTrigger" + "webaudioScheduler" ], - "/home/felix/projects/strudel/packages/webaudio/index.mjs": [], - "/home/felix/projects/strudel/packages/xen/xen.mjs": [ + "/packages/webaudio/scope.mjs": [ + "drawTimeScope", + "drawFrequencyScope" + ], + "/packages/webaudio/index.mjs": [], + "/packages/xen/xen.mjs": [ "edo", "xen", "tuning" ], - "/home/felix/projects/strudel/packages/xen/tunejs.js": [], - "/home/felix/projects/strudel/packages/xen/tune.mjs": [ + "/packages/xen/tunejs.js": [], + "/packages/xen/tune.mjs": [ "tune" ], - "/home/felix/projects/strudel/packages/xen/index.mjs": [], - "/home/felix/projects/strudel/index.mjs": [] + "/packages/xen/index.mjs": [], + "/index.mjs": [] } From ecc8e0a108abe03da8138f457596a3ca1585a01c Mon Sep 17 00:00:00 2001 From: Alexandre Gravel-Raymond Date: Wed, 22 Nov 2023 20:38:25 +0100 Subject: [PATCH 20/29] More code beauty please --- jsdoc/undocumented.mjs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jsdoc/undocumented.mjs b/jsdoc/undocumented.mjs index 657fdea6..9376a8b6 100644 --- a/jsdoc/undocumented.mjs +++ b/jsdoc/undocumented.mjs @@ -76,7 +76,9 @@ const paths = dependencyTree.toList({ // const paths = ['../packages/core/pattern.mjs', '../packages/core/hap.mjs'].map((rel) => resolve(__dirname, rel)); const undocumented = Object.fromEntries( - await Promise.all(paths.map(async (path) => [path.replace(resolve(__dirname, '..'), ''), await getUndocumented(path, docs)])), + await Promise.all( + paths.map(async (path) => [path.replace(resolve(__dirname, '..'), ''), await getUndocumented(path, docs)]), + ), ); console.log(JSON.stringify(undocumented, null, 2)); From 4fd3e6e99c45e8e4992a1ec9bfc78df42a8ea714 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 24 Nov 2023 09:37:11 +0100 Subject: [PATCH 21/29] fix: entry.name mutation bug --- packages/react/src/components/Autocomplete.jsx | 4 ++-- packages/react/src/components/Tooltip.jsx | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/react/src/components/Autocomplete.jsx b/packages/react/src/components/Autocomplete.jsx index 677baa6e..fe15ee9d 100644 --- a/packages/react/src/components/Autocomplete.jsx +++ b/packages/react/src/components/Autocomplete.jsx @@ -8,10 +8,10 @@ const getInnerText = (html) => { return div.textContent || div.innerText || ''; }; -export function Autocomplete({ doc }) { +export function Autocomplete({ doc, label }) { return (
    -

    {getDocLabel(doc)}

    +

    {label || getDocLabel(doc)}

      {doc.params?.map(({ name, type, description }, i) => ( diff --git a/packages/react/src/components/Tooltip.jsx b/packages/react/src/components/Tooltip.jsx index a508e761..43a53476 100644 --- a/packages/react/src/components/Tooltip.jsx +++ b/packages/react/src/components/Tooltip.jsx @@ -55,7 +55,6 @@ export const strudelTooltip = hoverTooltip( if (!entry) { return null; } - entry.name = word; } return { @@ -66,7 +65,7 @@ export const strudelTooltip = hoverTooltip( create(view) { let dom = document.createElement('div'); dom.className = 'strudel-tooltip'; - createRoot(dom).render(); + createRoot(dom).render(); return { dom }; }, }; From 458c97b92709254b6f05bfe55865a521c20c8143 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 24 Nov 2023 09:37:26 +0100 Subject: [PATCH 22/29] list synonyms in Autocomplete --- packages/react/src/components/Autocomplete.jsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/react/src/components/Autocomplete.jsx b/packages/react/src/components/Autocomplete.jsx index fe15ee9d..b8abfc0a 100644 --- a/packages/react/src/components/Autocomplete.jsx +++ b/packages/react/src/components/Autocomplete.jsx @@ -11,7 +11,12 @@ const getInnerText = (html) => { export function Autocomplete({ doc, label }) { return (
      -

      {label || getDocLabel(doc)}

      +

      {label || getDocLabel(doc)}

      {' '} + {!!doc.synonyms_text && ( + + Synonyms: {doc.synonyms_text} + + )}
        {doc.params?.map(({ name, type, description }, i) => ( From 18e9ceb5013181ae7b19063c3f856bf52173c7a6 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 24 Nov 2023 09:52:39 +0100 Subject: [PATCH 23/29] populate autocomplete with all synonyms + properly display synonyms based on label --- .../react/src/components/Autocomplete.jsx | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/packages/react/src/components/Autocomplete.jsx b/packages/react/src/components/Autocomplete.jsx index b8abfc0a..9a0b9de4 100644 --- a/packages/react/src/components/Autocomplete.jsx +++ b/packages/react/src/components/Autocomplete.jsx @@ -2,19 +2,21 @@ import { createRoot } from 'react-dom/client'; import jsdoc from '../../../../doc.json'; const getDocLabel = (doc) => doc.name || doc.longname; +const getDocSynonyms = (doc) => [getDocLabel(doc), ...(doc.synonyms || [])]; const getInnerText = (html) => { var div = document.createElement('div'); div.innerHTML = html; return div.textContent || div.innerText || ''; }; -export function Autocomplete({ doc, label }) { +export function Autocomplete({ doc, label = getDocLabel(doc) }) { + const synonyms = getDocSynonyms(doc).filter((a) => a !== label); return (
        -

        {label || getDocLabel(doc)}

        {' '} - {!!doc.synonyms_text && ( +

        {label}

        {' '} + {!!synonyms.length && ( - Synonyms: {doc.synonyms_text} + Synonyms: {synonyms.join(', ')} )}
        @@ -53,18 +55,24 @@ const jsdocCompletions = jsdoc.docs !['superdirtOnly', 'noAutocomplete'].some((tag) => doc.tags?.find((t) => t.originalTitle === tag)), ) // https://codemirror.net/docs/ref/#autocomplete.Completion - .map((doc) /*: Completion */ => ({ - label: getDocLabel(doc), - // detail: 'xxx', // An optional short piece of information to show (with a different style) after the label. - info: () => { - const node = document.createElement('div'); - // if Autocomplete is non-interactive, it could also be rendered at build time.. - // .. using renderToStaticMarkup - createRoot(node).render(); - return node; - }, - type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type - })); + .reduce( + (acc, doc) /*: Completion */ => + acc.concat( + [getDocLabel(doc), ...(doc.synonyms || [])].map((label) => ({ + label, + // detail: 'xxx', // An optional short piece of information to show (with a different style) after the label. + info: () => { + const node = document.createElement('div'); + // if Autocomplete is non-interactive, it could also be rendered at build time.. + // .. using renderToStaticMarkup + createRoot(node).render(); + return node; + }, + type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type + })), + ), + [], + ); export const strudelAutocomplete = (context /* : CompletionContext */) => { let word = context.matchBefore(/\w*/); From fb7a9e2083c10226acfb7f29991076f64ea477c4 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 24 Nov 2023 10:28:40 +0100 Subject: [PATCH 24/29] format --- packages/hydra/hydra.mjs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/hydra/hydra.mjs b/packages/hydra/hydra.mjs index 3f41e256..a75614ec 100644 --- a/packages/hydra/hydra.mjs +++ b/packages/hydra/hydra.mjs @@ -3,7 +3,7 @@ import { getDrawContext } from '@strudel.cycles/core'; let audio = false; let hydra; -function appendCanvas(c){ +function appendCanvas(c) { const { canvas: testCanvas } = getDrawContext(); c.canvas.id = 'hydra-canvas'; c.canvas.style.position = 'absolute'; @@ -22,12 +22,12 @@ export async function initHydra(config) { // if config.audio is true // and current canvas des not detect audio - if (config?.audio && !hydra.detectAudio ){ + if (config?.audio && !hydra.detectAudio) { //remove previous canvas without audio detection - document.getElementById('hydra-canvas').remove() + document.getElementById('hydra-canvas').remove(); // create and append a new audio responsive canvas - hydra = new Hydra({ detectAudio: audio}); - appendCanvas(hydra) + hydra = new Hydra({ detectAudio: audio }); + appendCanvas(hydra); } } From 456be920d345c3d6b6874a2b88a176fcbfe11e1a Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 24 Nov 2023 11:17:30 +0100 Subject: [PATCH 25/29] else clause --- packages/hydra/hydra.mjs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/hydra/hydra.mjs b/packages/hydra/hydra.mjs index b63fc46c..b2f60ddf 100644 --- a/packages/hydra/hydra.mjs +++ b/packages/hydra/hydra.mjs @@ -21,10 +21,9 @@ export async function initHydra(options = {}) { appendCanvas(hydra); // s0.init({ src: hydraCanvas }); // whats that? } - // if options.detectAudio is true // and current canvas des not detect audio - if (options?.detectAudio && !hydra?.detectAudio) { + else if (options?.detectAudio && !hydra?.detectAudio) { //remove previous canvas without audio detection document.getElementById('hydra-canvas').remove(); return initHydra(options); From 9045ce05da102bdcc2b5a76922807ed1c56230b0 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 24 Nov 2023 11:28:20 +0100 Subject: [PATCH 26/29] comment --- packages/hydra/hydra.mjs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/hydra/hydra.mjs b/packages/hydra/hydra.mjs index b2f60ddf..e2614ac0 100644 --- a/packages/hydra/hydra.mjs +++ b/packages/hydra/hydra.mjs @@ -16,14 +16,12 @@ export async function initHydra(options = {}) { if (!document.getElementById('hydra-canvas')) { const { src = 'https://unpkg.com/hydra-synth', ...opts } = options; await import(src); - hydra = new Hydra(opts); appendCanvas(hydra); - // s0.init({ src: hydraCanvas }); // whats that? - } - // if options.detectAudio is true - // and current canvas des not detect audio - else if (options?.detectAudio && !hydra?.detectAudio) { + // hydra.synth.s0.init({ src: testCanvas }); // is this needed? + } else if (options?.detectAudio && !hydra?.detectAudio) { + // if options.detectAudio is true + // and current canvas des not detect audio //remove previous canvas without audio detection document.getElementById('hydra-canvas').remove(); return initHydra(options); From 508a6c92b28695d50e24e8c4619c525393780c58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81mbar=20Tenorio=20Forn=C3=A9s?= Date: Fri, 24 Nov 2023 12:21:04 +0100 Subject: [PATCH 27/29] default hydra config without audio detection --- packages/hydra/hydra.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hydra/hydra.mjs b/packages/hydra/hydra.mjs index e2614ac0..c97f3a48 100644 --- a/packages/hydra/hydra.mjs +++ b/packages/hydra/hydra.mjs @@ -11,7 +11,7 @@ function appendCanvas(c) { return testCanvas; } -export async function initHydra(options = {}) { +export async function initHydra(options = {detectAudio: false}) { //load and init hydra if (!document.getElementById('hydra-canvas')) { const { src = 'https://unpkg.com/hydra-synth', ...opts } = options; From f7a283f87dd4597fdbaf527acf828f93cfa58887 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 24 Nov 2023 12:49:05 +0100 Subject: [PATCH 28/29] add feedStrudel option + smarter reinit logic --- packages/hydra/hydra.mjs | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/packages/hydra/hydra.mjs b/packages/hydra/hydra.mjs index c97f3a48..97ca5ac4 100644 --- a/packages/hydra/hydra.mjs +++ b/packages/hydra/hydra.mjs @@ -1,6 +1,6 @@ import { getDrawContext } from '@strudel.cycles/core'; -let hydra; +let latestOptions; function appendCanvas(c) { const { canvas: testCanvas } = getDrawContext(); @@ -11,20 +11,28 @@ function appendCanvas(c) { return testCanvas; } -export async function initHydra(options = {detectAudio: false}) { +export async function initHydra(options = {}) { + // reset if options have changed since last init + if (latestOptions && JSON.stringify(latestOptions) !== JSON.stringify(options)) { + document.getElementById('hydra-canvas').remove(); + } + latestOptions = options; //load and init hydra if (!document.getElementById('hydra-canvas')) { - const { src = 'https://unpkg.com/hydra-synth', ...opts } = options; + console.log('reinit..'); + const { + src = 'https://unpkg.com/hydra-synth', + feedStrudel = false, + ...hydraConfig + } = { detectAudio: false, ...options }; await import(src); - hydra = new Hydra(opts); + const hydra = new Hydra(hydraConfig); + if (feedStrudel) { + const { canvas } = getDrawContext(); + canvas.style.display = 'none'; + hydra.synth.s0.init({ src: canvas }); + } appendCanvas(hydra); - // hydra.synth.s0.init({ src: testCanvas }); // is this needed? - } else if (options?.detectAudio && !hydra?.detectAudio) { - // if options.detectAudio is true - // and current canvas des not detect audio - //remove previous canvas without audio detection - document.getElementById('hydra-canvas').remove(); - return initHydra(options); } } From 407510e265a28a479b45af47efd42622c05ee393 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 25 Nov 2023 15:20:19 +0100 Subject: [PATCH 29/29] document feedStrudel --- packages/hydra/README.md | 9 ++++----- website/src/pages/learn/hydra.mdx | 28 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/packages/hydra/README.md b/packages/hydra/README.md index e36a421b..0b238692 100644 --- a/packages/hydra/README.md +++ b/packages/hydra/README.md @@ -12,12 +12,11 @@ await initHydra(); Then you can use hydra below! -### Enable audio capture -Use `{audio: true}` param to enable Hydra's audio capture: +### options -```js -await initHydra({audio: true}); -``` +You can also pass options to the `initHydra` function. These can be used to set [hydra options](https://github.com/hydra-synth/hydra-synth#api) + these strudel specific options: + +- `feedStrudel`: sends the strudel canvas to `s0`. The strudel canvas is used to draw `pianoroll`, `spiral`, `scope` etc.. ## Usage via npm diff --git a/website/src/pages/learn/hydra.mdx b/website/src/pages/learn/hydra.mdx index d5219e6f..f1ace48b 100644 --- a/website/src/pages/learn/hydra.mdx +++ b/website/src/pages/learn/hydra.mdx @@ -41,6 +41,8 @@ note("[a,c,e,,b4]/4").s("sawtooth").vib(2) `} /> +## H patterns + There is a special function `H` that allows you to use a pattern as an input to hydra: +## detectAudio + To use hydra audio capture, call `initHydra` with `{detectAudio:true}` configuration param: You might now be able to see this properly here: [open in REPL](/#YXdhaXQgaW5pdEh5ZHJhKCkKbGV0IHBhdHRlcm4gPSAiMyA0IDUgWzYgN10qMiIKc2hhcGUoSChwYXR0ZXJuKSkub3V0KG8wKQpuKHBhdHRlcm4pLnNjYWxlKCJBOm1pbm9yIikucGlhbm8oKS5yb29tKDEpIA%3D%3D) + +Similar to `detectAudio`, all the [available hydra options](https://github.com/hydra-synth/hydra-synth#api) can be passed to `initHydra`. + +## feedStrudel + +Using the `feedStrudel` option, you can transform strudel visualizations with hydra: + +")) + .diff(osc(1,0.5,5)) + .modulateScale(osc(2,-0.25,1)) + .out() +// +stack( + s("bd*2,[hh:0:<.5 1>]*4,~ rim").bank("RolandTR909").speed(.9), + note("[>>]*3").s("sawtooth") + .room(.75).sometimes(add(note(12))).clip(.3) + .lpa(.05).lpenv(-4).lpf(2000).lpq(8).ftype('24db') +).fft(4) + .scope({pos:0,smear:.95})`} +/>