Merge branch 'main' into atfornesmain

This commit is contained in:
Felix Roos 2023-11-24 11:10:15 +01:00
commit 71b4e14dd3
17 changed files with 249 additions and 127 deletions

View File

@ -20,4 +20,5 @@ vite.config.js
**/dist **/dist
/src-tauri/target/**/* /src-tauri/target/**/*
reverbGen.mjs reverbGen.mjs
hydra.mjs hydra.mjs
jsdoc-synonyms.js

17
jsdoc/jsdoc-synonyms.js Normal file
View File

@ -0,0 +1,17 @@
/*
jsdoc-synonyms.js - Add support for @synonym tag
Copyright (C) 2023 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/midi/midi.mjs>
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 <https://www.gnu.org/licenses/>.
*/
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 };

View File

@ -3,7 +3,7 @@
"includePattern": ".+\\.(js(doc|x)?|mjs)$", "includePattern": ".+\\.(js(doc|x)?|mjs)$",
"excludePattern": "node_modules|shift-parser|shift-reducer|shift-traverser|dist" "excludePattern": "node_modules|shift-parser|shift-reducer|shift-traverser|dist"
}, },
"plugins": ["plugins/markdown"], "plugins": ["plugins/markdown", "jsdoc/jsdoc-synonyms"],
"opts": { "opts": {
"destination": "./out/", "destination": "./out/",
"recurse": true "recurse": true

View File

@ -65,7 +65,7 @@ async function getUndocumented(path, docs) {
} }
// read doc.json file // 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({ const paths = dependencyTree.toList({
filename: 'index.mjs', filename: 'index.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 paths = ['../packages/core/pattern.mjs', '../packages/core/hap.mjs'].map((rel) => resolve(__dirname, rel));
const undocumented = Object.fromEntries( 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)); console.log(JSON.stringify(undocumented, null, 2));

View File

@ -18,12 +18,12 @@
"build": "npm run prebuild && cd website && npm run build", "build": "npm run prebuild && cd website && npm run build",
"preview": "cd website && npm run preview", "preview": "cd website && npm run preview",
"osc": "cd packages/osc && npm run server", "osc": "cd packages/osc && npm run server",
"jsdoc": "jsdoc packages/ -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.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", "lint": "eslint . --ext mjs,js --quiet",
"codeformat": "prettier --write .", "codeformat": "prettier --write .",
"format-check": "prettier --check .", "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", "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" "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"
}, },

View File

@ -1214,6 +1214,16 @@ const generic_params = [
* @name waveloss * @name waveloss
*/ */
['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? // TODO: midi effects?
['dur'], ['dur'],
// ['modwheel'], // ['modwheel'],

View File

@ -1,33 +1,33 @@
import { getDrawContext } from '@strudel.cycles/core'; import { getDrawContext } from '@strudel.cycles/core';
let audio = false;
let hydra; let hydra;
function appendCanvas(c) { function appendCanvas(c) {
const { canvas: testCanvas } = getDrawContext(); const { canvas: testCanvas } = getDrawContext();
c.canvas.id = 'hydra-canvas'; c.canvas.id = 'hydra-canvas';
c.canvas.style.position = 'absolute'; c.canvas.style.position = 'fixed';
c.canvas.style.top = '0px'; c.canvas.style.top = '0px';
testCanvas.after(c.canvas); testCanvas.after(c.canvas);
return testCanvas;
} }
export async function initHydra(config) { export async function initHydra(options = {}) {
audio = config?.audio || false;
//load and init hydra //load and init hydra
if (!document.getElementById('hydra-canvas')) { if (!document.getElementById('hydra-canvas')) {
await import('https://unpkg.com/hydra-synth'); const { src = 'https://unpkg.com/hydra-synth', ...opts } = options;
hydra = new Hydra({ detectAudio: audio }); await import(src);
hydra = new Hydra(opts);
appendCanvas(hydra); appendCanvas(hydra);
// s0.init({ src: hydraCanvas }); // whats that?
} }
// if config.audio is true // if options.detectAudio is true
// and current canvas des not detect audio // and current canvas des not detect audio
if (config?.audio && !hydra.detectAudio) { if (options?.detectAudio && !hydra?.detectAudio) {
//remove previous canvas without audio detection //remove previous canvas without audio detection
document.getElementById('hydra-canvas').remove(); document.getElementById('hydra-canvas').remove();
// create and append a new audio responsive canvas return initHydra(options);
hydra = new Hydra({ detectAudio: audio });
appendCanvas(hydra);
} }
} }

View File

@ -2,16 +2,23 @@ import { createRoot } from 'react-dom/client';
import jsdoc from '../../../../doc.json'; import jsdoc from '../../../../doc.json';
const getDocLabel = (doc) => doc.name || doc.longname; const getDocLabel = (doc) => doc.name || doc.longname;
const getDocSynonyms = (doc) => [getDocLabel(doc), ...(doc.synonyms || [])];
const getInnerText = (html) => { const getInnerText = (html) => {
var div = document.createElement('div'); var div = document.createElement('div');
div.innerHTML = html; div.innerHTML = html;
return div.textContent || div.innerText || ''; return div.textContent || div.innerText || '';
}; };
export function Autocomplete({ doc }) { export function Autocomplete({ doc, label = getDocLabel(doc) }) {
const synonyms = getDocSynonyms(doc).filter((a) => a !== label);
return ( return (
<div className="prose dark:prose-invert max-h-[400px] overflow-auto"> <div className="prose dark:prose-invert max-h-[400px] overflow-auto">
<h3 className="pt-0 mt-0">{getDocLabel(doc)}</h3> <h3 className="pt-0 mt-0">{label}</h3>{' '}
{!!synonyms.length && (
<span>
Synonyms: <code>{synonyms.join(', ')}</code>
</span>
)}
<div dangerouslySetInnerHTML={{ __html: doc.description }} /> <div dangerouslySetInnerHTML={{ __html: doc.description }} />
<ul> <ul>
{doc.params?.map(({ name, type, description }, i) => ( {doc.params?.map(({ name, type, description }, i) => (
@ -48,18 +55,24 @@ const jsdocCompletions = jsdoc.docs
!['superdirtOnly', 'noAutocomplete'].some((tag) => doc.tags?.find((t) => t.originalTitle === tag)), !['superdirtOnly', 'noAutocomplete'].some((tag) => doc.tags?.find((t) => t.originalTitle === tag)),
) )
// https://codemirror.net/docs/ref/#autocomplete.Completion // https://codemirror.net/docs/ref/#autocomplete.Completion
.map((doc) /*: Completion */ => ({ .reduce(
label: getDocLabel(doc), (acc, doc) /*: Completion */ =>
// detail: 'xxx', // An optional short piece of information to show (with a different style) after the label. acc.concat(
info: () => { [getDocLabel(doc), ...(doc.synonyms || [])].map((label) => ({
const node = document.createElement('div'); label,
// if Autocomplete is non-interactive, it could also be rendered at build time.. // detail: 'xxx', // An optional short piece of information to show (with a different style) after the label.
// .. using renderToStaticMarkup info: () => {
createRoot(node).render(<Autocomplete doc={doc} />); const node = document.createElement('div');
return node; // if Autocomplete is non-interactive, it could also be rendered at build time..
}, // .. using renderToStaticMarkup
type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type createRoot(node).render(<Autocomplete doc={doc} label={label} />);
})); return node;
},
type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type
})),
),
[],
);
export const strudelAutocomplete = (context /* : CompletionContext */) => { export const strudelAutocomplete = (context /* : CompletionContext */) => {
let word = context.matchBefore(/\w*/); let word = context.matchBefore(/\w*/);

View File

@ -31,6 +31,9 @@ window.addEventListener(
export const strudelTooltip = hoverTooltip( export const strudelTooltip = hoverTooltip(
(view, pos, side) => { (view, pos, side) => {
// Word selection from CodeMirror Hover Tooltip example https://codemirror.net/examples/tooltip/#hover-tooltips // 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 { from, to, text } = view.state.doc.lineAt(pos);
let start = pos, let start = pos,
end = pos; end = pos;
@ -47,11 +50,13 @@ export const strudelTooltip = hoverTooltip(
// Get entry from Strudel documentation // Get entry from Strudel documentation
let entry = jsdoc.docs.filter((doc) => getDocLabel(doc) === word)[0]; let entry = jsdoc.docs.filter((doc) => getDocLabel(doc) === word)[0];
if (!entry) { if (!entry) {
return null; // Try for synonyms
} entry = jsdoc.docs.filter((doc) => doc.synonyms && doc.synonyms.includes(word))[0];
if (!ctrlDown) { if (!entry) {
return null; return null;
}
} }
return { return {
pos: start, pos: start,
end, end,
@ -60,7 +65,7 @@ export const strudelTooltip = hoverTooltip(
create(view) { create(view) {
let dom = document.createElement('div'); let dom = document.createElement('div');
dom.className = 'strudel-tooltip'; dom.className = 'strudel-tooltip';
createRoot(dom).render(<Autocomplete doc={entry} />); createRoot(dom).render(<Autocomplete doc={entry} label={word} />);
return { dom }; return { dom };
}, },
}; };

View File

@ -4,7 +4,7 @@ import { getAudioContext } from './superdough.mjs';
let noiseCache = {}; let noiseCache = {};
// lazy generates noise buffers and keeps them forever // lazy generates noise buffers and keeps them forever
function getNoiseBuffer(type) { function getNoiseBuffer(type, density) {
const ac = getAudioContext(); const ac = getAudioContext();
if (noiseCache[type]) { if (noiseCache[type]) {
return 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] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362;
output[i] *= 0.11; output[i] *= 0.11;
b6 = white * 0.115926; b6 = white * 0.115926;
} else if (type === 'crackle') {
const probability = density * 0.01;
if (Math.random() < probability) {
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; return noiseBuffer;
} }
// expects one of noises as type // 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 ac = getAudioContext();
const o = ac.createBufferSource(); const o = ac.createBufferSource();
o.buffer = getNoiseBuffer(type); o.buffer = getNoiseBuffer(type, density);
o.loop = true; o.loop = true;
o.start(t); o.start(t);
return { return {

View File

@ -241,6 +241,7 @@ export const superdough = async (value, deadline, hapDuration) => {
source, source,
gain = 0.8, gain = 0.8,
postgain = 1, postgain = 1,
density = 0.03,
// filters // filters
ftype = '12db', ftype = '12db',
fanchor = 0.5, fanchor = 0.5,

View File

@ -22,7 +22,7 @@ const fm = (osc, harmonicityRatio, modulationIndex, wave = 'sine') => {
}; };
const waveforms = ['sine', 'square', 'triangle', 'sawtooth']; const waveforms = ['sine', 'square', 'triangle', 'sawtooth'];
const noises = ['pink', 'white', 'brown']; const noises = ['pink', 'white', 'brown', 'crackle'];
export function registerSynthSounds() { export function registerSynthSounds() {
[...waveforms, ...noises].forEach((s) => { [...waveforms, ...noises].forEach((s) => {
@ -36,7 +36,8 @@ export function registerSynthSounds() {
if (waveforms.includes(s)) { if (waveforms.includes(s)) {
sound = getOscillator(s, t, value); sound = getOscillator(s, t, value);
} else { } else {
sound = getNoiseOscillator(s, t); let { density } = value;
sound = getNoiseOscillator(s, t, density);
} }
let { node: o, stop, triggerRelease } = sound; let { node: o, stop, triggerRelease } = sound;

View File

@ -1,17 +1,17 @@
{ {
"/home/felix/projects/strudel/packages/core/fraction.mjs": [ "/packages/core/fraction.mjs": [
"gcd" "gcd"
], ],
"/home/felix/projects/strudel/packages/core/timespan.mjs": [ "/packages/core/timespan.mjs": [
"TimeSpan" "TimeSpan"
], ],
"/home/felix/projects/strudel/packages/core/hap.mjs": [ "/packages/core/hap.mjs": [
"Hap" "Hap"
], ],
"/home/felix/projects/strudel/packages/core/state.mjs": [ "/packages/core/state.mjs": [
"State" "State"
], ],
"/home/felix/projects/strudel/packages/core/util.mjs": [ "/packages/core/util.mjs": [
"isNoteWithOctave", "isNoteWithOctave",
"isNote", "isNote",
"tokenizeNote", "tokenizeNote",
@ -34,23 +34,31 @@
"mapArgs", "mapArgs",
"numeralArgs", "numeralArgs",
"parseFractional", "parseFractional",
"fractionalArgs" "fractionalArgs",
"splitAt",
"zipWith",
"clamp",
"sol2note"
], ],
"/home/felix/projects/strudel/packages/core/value.mjs": [ "/packages/core/value.mjs": [
"unionWithObj", "unionWithObj",
"valued", "valued",
"Value", "Value",
"map" "map"
], ],
"/home/felix/projects/strudel/packages/core/drawLine.mjs": [], "/packages/core/drawLine.mjs": [],
"/home/felix/projects/strudel/packages/core/logger.mjs": [ "/packages/core/logger.mjs": [
"logKey", "logKey",
"logger" "logger"
], ],
"/home/felix/projects/strudel/packages/core/pattern.mjs": [ "/packages/core/pattern.mjs": [
"setStringParser", "setStringParser",
"polyrhythm",
"pr",
"pm",
"isPattern", "isPattern",
"reify", "reify",
"fastcat",
"set", "set",
"keep", "keep",
"keepif", "keepif",
@ -74,22 +82,33 @@
"func", "func",
"compressSpan", "compressSpan",
"compressspan", "compressspan",
"fastgap",
"focusSpan", "focusSpan",
"focusspan", "focusspan",
"density",
"sparsity",
"zoomArc", "zoomArc",
"zoomarc", "zoomarc",
"inv",
"juxby",
"echowith",
"stutWith",
"stutwith",
"iterback",
"chunkback",
"bypass", "bypass",
"duration", "duration",
"color",
"colour", "colour",
"striate" "loopat",
"loopatcps"
], ],
"/home/felix/projects/strudel/packages/core/controls.mjs": [], "/packages/core/controls.mjs": [],
"/home/felix/projects/strudel/packages/core/euclid.mjs": [ "/packages/core/euclid.mjs": [
"bjork",
"euclidrot", "euclidrot",
"euclidLegatoRot" "euclidLegatoRot"
], ],
"/home/felix/projects/strudel/packages/core/signal.mjs": [ "/packages/core/signal.mjs": [
"steady", "steady",
"signal", "signal",
"isaw", "isaw",
@ -112,34 +131,37 @@
"degradeByWith", "degradeByWith",
"undegrade" "undegrade"
], ],
"/home/felix/projects/strudel/packages/core/speak.mjs": [ "/packages/core/speak.mjs": [
"speak" "speak"
], ],
"/home/felix/projects/strudel/packages/core/evaluate.mjs": [ "/packages/core/evaluate.mjs": [
"evalScope", "evalScope",
"evaluate" "evaluate"
], ],
"/home/felix/projects/strudel/packages/core/zyklus.mjs": [], "/packages/core/zyklus.mjs": [],
"/home/felix/projects/strudel/packages/core/cyclist.mjs": [ "/packages/core/cyclist.mjs": [
"Cyclist" "Cyclist"
], ],
"/home/felix/projects/strudel/packages/core/time.mjs": [ "/packages/core/time.mjs": [
"getTime", "getTime",
"setTime" "setTime"
], ],
"/home/felix/projects/strudel/packages/core/repl.mjs": [ "/packages/core/repl.mjs": [
"repl" "repl",
"getTrigger"
], ],
"/home/felix/projects/strudel/packages/core/draw.mjs": [ "/packages/core/draw.mjs": [
"getDrawContext", "getDrawContext",
"cleanupDraw" "cleanupDraw",
"Framer",
"Drawer"
], ],
"/home/felix/projects/strudel/packages/core/animate.mjs": [ "/packages/core/animate.mjs": [
"x", "x",
"y", "y",
"w", "w",
"h", "h",
"a", "angle",
"r", "r",
"fill", "fill",
"smear", "smear",
@ -147,85 +169,121 @@
"moveXY", "moveXY",
"zoomIn" "zoomIn"
], ],
"/home/felix/projects/strudel/packages/core/pianoroll.mjs": [ "/packages/core/pianoroll.mjs": [
"pianoroll" "getDrawOptions",
"drawPianoroll"
], ],
"/home/felix/projects/strudel/packages/core/ui.mjs": [ "/packages/core/spiral.mjs": [],
"/packages/core/ui.mjs": [
"backgroundImage", "backgroundImage",
"cleanupUi" "cleanupUi"
], ],
"/home/felix/projects/strudel/packages/core/gist.js": [], "/packages/core/gist.js": [],
"/home/felix/projects/strudel/packages/core/index.mjs": [], "/packages/core/index.mjs": [],
"/home/felix/projects/strudel/packages/midi/midi.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", "WebMidi",
"enableWebMidi" "enableWebMidi",
"midin"
], ],
"/home/felix/projects/strudel/packages/midi/index.mjs": [], "/packages/midi/index.mjs": [],
"/home/felix/projects/strudel/packages/mini/krill-parser.js": [], "/packages/mini/krill-parser.js": [],
"/home/felix/projects/strudel/packages/mini/mini.mjs": [ "/packages/mini/mini.mjs": [
"patternifyAST", "patternifyAST",
"getLeafLocation",
"mini2ast",
"getLeaves",
"getLeafLocations",
"mini", "mini",
"m",
"h", "h",
"minify" "minify",
"miniAllStrings"
], ],
"/home/felix/projects/strudel/packages/mini/index.mjs": [], "/packages/mini/index.mjs": [],
"/home/felix/projects/strudel/packages/soundfonts/fontloader.mjs": [ "/packages/soundfonts/gm.mjs": [],
"/packages/soundfonts/fontloader.mjs": [
"getFontBufferSource", "getFontBufferSource",
"getFontPitch" "getFontPitch",
"registerSoundfonts"
], ],
"/home/felix/projects/strudel/packages/soundfonts/list.mjs": [ "/packages/soundfonts/list.mjs": [
"instruments", "instruments",
"drums", "drums",
"instrumentNames" "instrumentNames"
], ],
"/home/felix/projects/strudel/packages/soundfonts/sfumato.mjs": [ "/packages/soundfonts/sfumato.mjs": [
"loadSoundfont" "loadSoundfont"
], ],
"/home/felix/projects/strudel/packages/soundfonts/index.mjs": [], "/packages/soundfonts/index.mjs": [],
"/home/felix/projects/strudel/packages/tonal/tonal.mjs": [], "/packages/tonal/tonal.mjs": [],
"/home/felix/projects/strudel/packages/tonal/voicings.mjs": [ "/packages/tonal/tonleiter.mjs": [
"voicingRegistry", "pc2chroma",
"setVoicingRange" "rotateChroma",
"chroma2pc",
"tokenizeChord",
"note2pc",
"note2oct",
"note2chroma",
"midi2chroma",
"pitch2chroma",
"step2semitones",
"x2midi",
"scaleStep",
"renderVoicing",
"accidentalOffset",
"Step",
"Note"
], ],
"/home/felix/projects/strudel/packages/tonal/index.mjs": [], "/packages/tonal/ireal.mjs": [
"/home/felix/projects/strudel/packages/transpiler/transpiler.mjs": [ "simple",
"complex"
],
"/packages/tonal/voicings.mjs": [
"voicingRegistry",
"setVoicingRange",
"registerVoicings",
"voicingAlias"
],
"/packages/tonal/index.mjs": [],
"/packages/transpiler/transpiler.mjs": [
"transpiler" "transpiler"
], ],
"/home/felix/projects/strudel/packages/transpiler/index.mjs": [ "/packages/transpiler/index.mjs": [
"evaluate" "evaluate"
], ],
"/home/felix/projects/strudel/packages/webaudio/feedbackdelay.mjs": [], "/packages/webaudio/webaudio.mjs": [
"/home/felix/projects/strudel/packages/webaudio/reverb.mjs": [], "webaudioOutputTrigger",
"/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",
"webaudioOutput", "webaudioOutput",
"webaudioOutputTrigger" "webaudioScheduler"
], ],
"/home/felix/projects/strudel/packages/webaudio/index.mjs": [], "/packages/webaudio/scope.mjs": [
"/home/felix/projects/strudel/packages/xen/xen.mjs": [ "drawTimeScope",
"drawFrequencyScope"
],
"/packages/webaudio/index.mjs": [],
"/packages/xen/xen.mjs": [
"edo", "edo",
"xen", "xen",
"tuning" "tuning"
], ],
"/home/felix/projects/strudel/packages/xen/tunejs.js": [], "/packages/xen/tunejs.js": [],
"/home/felix/projects/strudel/packages/xen/tune.mjs": [ "/packages/xen/tune.mjs": [
"tune" "tune"
], ],
"/home/felix/projects/strudel/packages/xen/index.mjs": [], "/packages/xen/index.mjs": [],
"/home/felix/projects/strudel/index.mjs": [] "/index.mjs": []
} }

View File

@ -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 }), {}); const docs = jsdoc.docs.reduce((acc, obj) => Object.assign(acc, { [obj.longname]: obj }), {});
import { MiniRepl } from './MiniRepl'; 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 }) { export function JsDoc({ name, h = 3, hideDescription, punchcard, canvasHeight }) {
const item = docs[name]; const item = docs[name];
if (!item) { if (!item) {
console.warn('Not found: ' + name); console.warn('Not found: ' + name);
return <div />; return <div />;
} }
const synonyms = getTag('synonyms', item)?.split(', ') || [];
const CustomHeading = `h${h}`; const CustomHeading = `h${h}`;
const description = const description =
item.description?.replaceAll(/\{@link ([a-zA-Z\.]+)?#?([a-zA-Z]*)\}/g, (_, a, b) => { item.description?.replaceAll(/\{@link ([a-zA-Z\.]+)?#?([a-zA-Z]*)\}/g, (_, a, b) => {
// console.log(_, 'a', a, 'b', b);
return `<a href="#${a.replaceAll('.', '').toLowerCase()}${b ? `-${b}` : ''}">${a}${b ? `#${b}` : ''}</a>`; return `<a href="#${a.replaceAll('.', '').toLowerCase()}${b ? `-${b}` : ''}">${a}${b ? `#${b}` : ''}</a>`;
}) || ''; }) || '';
return ( return (
@ -22,9 +18,9 @@ export function JsDoc({ name, h = 3, hideDescription, punchcard, canvasHeight })
{!!h && <CustomHeading>{item.longname}</CustomHeading>} {!!h && <CustomHeading>{item.longname}</CustomHeading>}
{!hideDescription && ( {!hideDescription && (
<> <>
{!!synonyms.length && ( {!!item.synonyms_text && (
<span> <span>
Synonyms: <code>{synonyms.join(', ')}</code> Synonyms: <code>{item.synonyms_text}</code>
</span> </span>
)} )}
<div dangerouslySetInnerHTML={{ __html: description }} /> <div dangerouslySetInnerHTML={{ __html: description }} />

View File

@ -52,11 +52,11 @@ n(pattern).scale("A:minor").piano().room(1)
`} `}
/> />
To use hydra audio capture, call `initHydra` with `{audio:true}` configuration param: To use hydra audio capture, call `initHydra` with `{detectAudio:true}` configuration param:
<MiniRepl <MiniRepl
client:only="react" client:only="react"
tune={`await initHydra({audio:true}) tune={`await initHydra({detectAudio:true})
let pattern = "<3 4 5 [6 7]*2>" let pattern = "<3 4 5 [6 7]*2>"
shape(H(pattern)).repeat() shape(H(pattern)).repeat()
.scrollY( .scrollY(

View File

@ -42,6 +42,10 @@ Some amount of pink noise can also be added to any oscillator by using the `nois
<MiniRepl client:idle tune={`note("c3").noise("<0.1 0.25 0.5>").scope()`} /> <MiniRepl client:idle tune={`note("c3").noise("<0.1 0.25 0.5>").scope()`} />
You can also use the `crackle` type to play some subtle noise crackles. You can control noise amount by using the `density` parameter:
<MiniRepl client:idle tune={`s("crackle*4").density("<0.01 0.04 0.2 0.5>".slow(4)).scope()`} />
### Additive Synthesis ### Additive Synthesis
To tame the harsh sound of the basic waveforms, we can set the `n` control to limit the overtones of the waveform: To tame the harsh sound of the basic waveforms, we can set the `n` control to limit the overtones of the waveform:

View File

@ -37,6 +37,11 @@ export function Reference() {
{visibleFunctions.map((entry, i) => ( {visibleFunctions.map((entry, i) => (
<section key={i}> <section key={i}>
<h3 id={`doc-${i}`}>{entry.name}</h3> <h3 id={`doc-${i}`}>{entry.name}</h3>
{!!entry.synonyms_text && (
<p>
Synonyms: <code>{entry.synonyms_text}</code>
</p>
)}
{/* <small>{entry.meta.filename}</small> */} {/* <small>{entry.meta.filename}</small> */}
<p dangerouslySetInnerHTML={{ __html: entry.description }}></p> <p dangerouslySetInnerHTML={{ __html: entry.description }}></p>
<ul> <ul>