From 975a198ee95f4f2e3f6424730c646ba6c4b55bd2 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 28 Dec 2023 20:36:23 +0100 Subject: [PATCH] whole docs now run new repl - move MicroRepl code to MiniRepl - fix a ssr bug --- website/src/docs/MicroRepl.jsx | 172 ---------- website/src/docs/MiniRepl.jsx | 224 +++++++++---- website/src/pages/recipes/recipes-next.mdx | 312 ------------------ .../src/pages/technical-manual/patterns.mdx | 2 +- website/src/repl/idbutils.mjs | 3 + 5 files changed, 158 insertions(+), 555 deletions(-) delete mode 100644 website/src/docs/MicroRepl.jsx delete mode 100644 website/src/pages/recipes/recipes-next.mdx diff --git a/website/src/docs/MicroRepl.jsx b/website/src/docs/MicroRepl.jsx deleted file mode 100644 index 7d2e260b..00000000 --- a/website/src/docs/MicroRepl.jsx +++ /dev/null @@ -1,172 +0,0 @@ -import { useState, useRef, useCallback, useMemo, useEffect } from 'react'; -import { Icon } from './Icon'; -import { silence, getPunchcardPainter, noteToMidi } from '@strudel.cycles/core'; -import { transpiler } from '@strudel.cycles/transpiler'; -import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio'; -import { StrudelMirror } from '@strudel/codemirror'; -import { prebake } from '@strudel/repl'; -import Claviature from '@components/Claviature'; - -export function MicroRepl({ - code, - hideHeader = false, - canvasHeight = 100, - onTrigger, - punchcard, - punchcardLabels = true, - claviature, - claviatureLabels, -}) { - const id = useMemo(() => s4(), []); - const canvasId = useMemo(() => `canvas-${id}`, [id]); - const shouldDraw = !!punchcard || !!claviature; - const shouldShowCanvas = !!punchcard; - const drawTime = punchcard ? [0, 4] : [0, 0]; - const [activeNotes, setActiveNotes] = useState([]); - - const init = useCallback(({ code, shouldDraw }) => { - const drawContext = shouldDraw ? document.querySelector('#' + canvasId)?.getContext('2d') : null; - let onDraw; - if (shouldDraw) { - onDraw = (haps, time, frame, painters) => { - painters.length && drawContext?.clearRect(0, 0, drawContext.canvas.width * 2, drawContext.canvas.height * 2); - painters?.forEach((painter) => { - // ctx time haps drawTime paintOptions - painter(drawContext, time, haps, drawTime, { clear: false }); - }); - }; - } - - const editor = new StrudelMirror({ - id, - defaultOutput: webaudioOutput, - getTime: () => getAudioContext().currentTime, - transpiler, - autodraw: !!shouldDraw, - root: containerRef.current, - initialCode: '// LOADING', - pattern: silence, - drawTime, - onDraw, - editPattern: (pat, id) => { - if (onTrigger) { - pat = pat.onTrigger(onTrigger, false); - } - if (claviature) { - editor?.painters.push((ctx, time, haps, drawTime) => { - const active = haps - .map((hap) => hap.value.note) - .filter(Boolean) - .map((n) => (typeof n === 'string' ? noteToMidi(n) : n)); - setActiveNotes(active); - }); - } - if (punchcard) { - editor?.painters.push(getPunchcardPainter({ labels: !!punchcardLabels })); - } - return pat; - }, - prebake, - onUpdateState: (state) => { - setReplState({ ...state }); - }, - }); - // init settings - editor.setCode(code); - editorRef.current = editor; - }, []); - - const [replState, setReplState] = useState({}); - const { started, isDirty, error } = replState; - const editorRef = useRef(); - const containerRef = useRef(); - const [client, setClient] = useState(false); - useEffect(() => { - setClient(true); - if (!editorRef.current) { - setTimeout(() => { - init({ code, shouldDraw }); - }); - } - return () => { - editorRef.current?.clear(); - }; - }, []); - - if (!client) { - return
{code}
; - } - - return ( -
- {!hideHeader && ( -
-
- - -
-
- )} -
-
- {error &&
{error.message}
} -
- {shouldShowCanvas && ( - { - if (el && el.width !== el.clientWidth) { - el.width = el.clientWidth; - } - }} - > - )} - {/* !!log.length && ( -
- {log.map(({ message }, i) => ( -
{message}
- ))} -
- ) */} - {claviature && ( - - )} -
- ); -} - -function cx(...classes) { - // : Array - return classes.filter(Boolean).join(' '); -} - -function s4() { - return Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1); -} diff --git a/website/src/docs/MiniRepl.jsx b/website/src/docs/MiniRepl.jsx index 4b37ebad..3a16f3dd 100644 --- a/website/src/docs/MiniRepl.jsx +++ b/website/src/docs/MiniRepl.jsx @@ -1,84 +1,159 @@ -import { evalScope, controls, noteToMidi } from '@strudel.cycles/core'; -import { initAudioOnFirstClick } from '@strudel.cycles/webaudio'; -import { useEffect, useState } from 'react'; -import { prebake } from '../repl/prebake'; -import { themes, settings } from '../repl/themes.mjs'; -import './MiniRepl.css'; -import { useSettings } from '../settings.mjs'; +import { useState, useRef, useCallback, useMemo, useEffect } from 'react'; +import { Icon } from './Icon'; +import { silence, getPunchcardPainter, noteToMidi } from '@strudel.cycles/core'; +import { transpiler } from '@strudel.cycles/transpiler'; +import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio'; +import { StrudelMirror } from '@strudel/codemirror'; +// import { prebake } from '@strudel/repl'; +import { prebake } from '../repl/prebake.mjs'; +import { loadModules } from '../repl/util.mjs'; import Claviature from '@components/Claviature'; -let modules; +let prebaked, modulesLoading; if (typeof window !== 'undefined') { - modules = evalScope( - controls, - import('@strudel.cycles/core'), - import('@strudel.cycles/tonal'), - import('@strudel.cycles/mini'), - import('@strudel.cycles/midi'), - import('@strudel.cycles/xen'), - import('@strudel.cycles/webaudio'), - import('@strudel.cycles/osc'), - import('@strudel.cycles/csound'), - import('@strudel.cycles/soundfonts'), - import('@strudel/hydra'), - ); -} - -if (typeof window !== 'undefined') { - initAudioOnFirstClick(); - prebake(); + prebaked = prebake(); + modulesLoading = loadModules(); } export function MiniRepl({ - tune, - drawTime, + tune: code, + hideHeader = false, + canvasHeight = 100, + onTrigger, punchcard, punchcardLabels = true, - span = [0, 4], - canvasHeight = 100, - hideHeader, claviature, claviatureLabels, }) { - const [Repl, setRepl] = useState(); - const { theme, keybindings, fontSize, fontFamily, isLineNumbersDisplayed, isActiveLineHighlighted } = useSettings(); + const id = useMemo(() => s4(), []); + const canvasId = useMemo(() => `canvas-${id}`, [id]); + const shouldDraw = !!punchcard || !!claviature; + const shouldShowCanvas = !!punchcard; + const drawTime = punchcard ? [0, 4] : [0, 0]; const [activeNotes, setActiveNotes] = useState([]); - useEffect(() => { - // we have to load this package on the client - // because codemirror throws an error on the server - Promise.all([import('@strudel.cycles/react'), modules]) - .then(([res]) => setRepl(() => res.MiniRepl)) - .catch((err) => console.error(err)); - }, []); - return Repl ? ( -
- { - const active = haps - .map((hap) => hap.value.note) - .filter(Boolean) - .map((n) => (typeof n === 'string' ? noteToMidi(n) : n)); - setActiveNotes(active); - } - : undefined + + const init = useCallback(({ code, shouldDraw }) => { + const drawContext = shouldDraw ? document.querySelector('#' + canvasId)?.getContext('2d') : null; + let onDraw; + if (shouldDraw) { + onDraw = (haps, time, frame, painters) => { + painters.length && drawContext?.clearRect(0, 0, drawContext.canvas.width * 2, drawContext.canvas.height * 2); + painters?.forEach((painter) => { + // ctx time haps drawTime paintOptions + painter(drawContext, time, haps, drawTime, { clear: false }); + }); + }; + } + + const editor = new StrudelMirror({ + id, + defaultOutput: webaudioOutput, + getTime: () => getAudioContext().currentTime, + transpiler, + autodraw: !!shouldDraw, + root: containerRef.current, + initialCode: '// LOADING', + pattern: silence, + drawTime, + onDraw, + editPattern: (pat, id) => { + if (onTrigger) { + pat = pat.onTrigger(onTrigger, false); } - /> + if (claviature) { + editor?.painters.push((ctx, time, haps, drawTime) => { + const active = haps + .map((hap) => hap.value.note) + .filter(Boolean) + .map((n) => (typeof n === 'string' ? noteToMidi(n) : n)); + setActiveNotes(active); + }); + } + if (punchcard) { + editor?.painters.push(getPunchcardPainter({ labels: !!punchcardLabels })); + } + return pat; + }, + prebake: async () => Promise.all([modulesLoading, prebaked]), + onUpdateState: (state) => { + setReplState({ ...state }); + }, + }); + // init settings + editor.setCode(code); + editorRef.current = editor; + }, []); + + const [replState, setReplState] = useState({}); + const { started, isDirty, error } = replState; + const editorRef = useRef(); + const containerRef = useRef(); + const [client, setClient] = useState(false); + useEffect(() => { + setClient(true); + if (!editorRef.current) { + setTimeout(() => { + init({ code, shouldDraw }); + }); + } + return () => { + editorRef.current?.clear(); + }; + }, []); + + if (!client) { + return
{code}
; + } + + return ( +
+ {!hideHeader && ( +
+
+ + +
+
+ )} +
+
+ {error &&
{error.message}
} +
+ {shouldShowCanvas && ( + { + if (el && el.width !== el.clientWidth) { + el.width = el.clientWidth; + } + }} + > + )} + {/* !!log.length && ( +
+ {log.map(({ message }, i) => ( +
{message}
+ ))} +
+ ) */} {claviature && ( )}
- ) : ( -
{tune}
); } + +function cx(...classes) { + // : Array + return classes.filter(Boolean).join(' '); +} + +function s4() { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); +} diff --git a/website/src/pages/recipes/recipes-next.mdx b/website/src/pages/recipes/recipes-next.mdx deleted file mode 100644 index 4230063f..00000000 --- a/website/src/pages/recipes/recipes-next.mdx +++ /dev/null @@ -1,312 +0,0 @@ ---- -title: Recipes -layout: ../../layouts/MainLayout.astro ---- - -import { MicroRepl } from '../../docs/MicroRepl'; - -# Recipes - -This page shows possible ways to achieve common (or not so common) musical goals. -There are often many ways to do a thing and there is no right or wrong. -The fun part is that each representation will give you different impulses when improvising. - -## Arpeggios - -An arpeggio is when the notes of a chord are played in sequence. -We can either write the notes by hand: - - - -...or use scales: - - - -...or chord symbols: - - - -...using off: - - - -## Chopping Breaks - -A sample can be looped and chopped like this: - - - -This fits the break into 8 cycles + chops it in 16 pieces. -The chops are not audible yet, because we're not doing any manipulation. -Let's add randmized doubling + reversing: - - - -If we want to specify the order of samples, we can replace `chop` with `slice`: - -") - .cut(1).rarely(ply(2))`} - punchcard -/> - -If we use `splice` instead of `slice`, the speed adjusts to the duration of the event: - -") - .cut(1).rarely(ply(2))`} - punchcard -/> - -Note that we don't need `fit`, because `splice` will do that by itself. - -## Filter Envelopes - -A minimal filter envelope looks like this: - - d2") - .s("sawtooth") - .lpf(400).lpa(.2).lpenv(4) - .scope()`} -/> - -We can flip the envelope by setting `lpenv` negative + add some resonance `lpq`: - - d2") - .s("sawtooth").lpq(8) - .lpf(400).lpa(.2).lpenv(-4) - .scope()`} -/> - -## Layering Sounds - -We can layer sounds by separating them with ",": - -") -.s("sawtooth, square") // <------ -.scope()`} -/> - -We can control the gain of individual sounds like this: - -") -.s("sawtooth, square:0:.5") // <--- "name:number:gain" -.scope()`} -/> - -For more control over each voice, we can use `layer`: - -").layer( - x=>x.s("sawtooth").vib(4), - x=>x.s("square").add(note(12)) -).scope()`} -/> - -Here, we give the sawtooth a vibrato and the square is moved an octave up. -With `layer`, you can use any pattern method available on each voice, so sky is the limit.. - -## Oscillator Detune - -We can fatten a sound by adding a detuned version to itself: - -") -.add(note("0,.1")) // <------ chorus -.s("sawtooth").scope()`} - punchcard -/> - -Try out different values, or add another voice! - -## Polyrhythms - -Here is a simple example of a polyrhythm: - - - -A polyrhythm is when 2 different tempos happen at the same time. - -## Polymeter - -This is a polymeter: - -,").fast(2)`} punchcard /> - -A polymeter is when 2 different bar lengths play at the same tempo. - -## Phasing - -This is a phasing: - -*[6,6.1]").piano()`} punchcard /> - -Phasing happens when the same sequence plays at slightly different tempos. - -## Running through samples - -Using `run` with `n`, we can rush through a sample bank: - - - -This works great with sample banks that contain similar sounds, like in this case different recordings of a tabla. -Often times, you'll hear the beginning of the phrase not where the pattern begins. -In this case, I hear the beginning at the third sample, which can be accounted for with `early`. - - - -Let's add some randomness: - - - -## Tape Warble - -We can emulate a pitch warbling effect like this: - - - -## Sound Duration - -There are a number of ways to change the sound duration. Using clip: - -/2")`} -/> - -The value of clip is relative to the duration of each event. -We can also create overlaps using release: - -/2")`} -/> - -This will smoothly fade out each sound for the given number of seconds. -We could also make the notes shorter with decay / sustain: - -/2").sustain(0)`} -/> - -For now, there is a limitation where decay values that exceed the event duration may cause little cracks, so use higher numbers with caution.. - -When using samples, we also have `.end` to cut relative to the sample length: - -")`} /> - -Compare that to clip: - -")`} /> - -or decay / sustain - -").sustain(0)`} /> - -## Wavetable Synthesis - -You can loop a sample with `loop` / `loopEnd`: - -").s("bd").loop(1).loopEnd(.05).gain(.2)`} /> - -This allows us to play the first 5% of the bass drum as a synth! -To simplify loading wavetables, any sample that starts with `wt_` will be looped automatically: - - - -Running through different wavetables can also give interesting variations: - - - -...adding a filter envelope + reverb: - - diff --git a/website/src/pages/technical-manual/patterns.mdx b/website/src/pages/technical-manual/patterns.mdx index 45664ad7..49ede526 100644 --- a/website/src/pages/technical-manual/patterns.mdx +++ b/website/src/pages/technical-manual/patterns.mdx @@ -14,7 +14,7 @@ Example: e.show())) silence`} diff --git a/website/src/repl/idbutils.mjs b/website/src/repl/idbutils.mjs index 18e15d20..7613b767 100644 --- a/website/src/repl/idbutils.mjs +++ b/website/src/repl/idbutils.mjs @@ -77,6 +77,9 @@ async function bufferToDataUrl(buf) { //open db and initialize it if necessary const openDB = (config, onOpened) => { const { dbName, version, table, columns } = config; + if (typeof window === 'undefined') { + return; + } if (!('indexedDB' in window)) { console.log('IndexedDB is not supported.'); return;