microrepl: ssr static code

This commit is contained in:
Felix Roos 2023-12-17 22:42:51 +01:00
parent 9519d286bd
commit e17df79133
2 changed files with 46 additions and 47 deletions

View File

@ -1,11 +1,10 @@
import { useState, useRef, useCallback, useMemo } from 'react'; import { useState, useRef, useCallback, useMemo, useEffect } from 'react';
import { Icon } from './Icon'; import { Icon } from './Icon';
import { silence, getPunchcardPainter } from '@strudel.cycles/core'; import { silence, getPunchcardPainter } from '@strudel.cycles/core';
import { transpiler } from '@strudel.cycles/transpiler'; import { transpiler } from '@strudel.cycles/transpiler';
import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio'; import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio';
import { StrudelMirror } from '@strudel/codemirror'; import { StrudelMirror } from '@strudel/codemirror';
import { prebake } from '@strudel/repl'; import { prebake } from '@strudel/repl';
import { useInView } from 'react-hook-inview';
export function MicroRepl({ export function MicroRepl({
code, code,
@ -65,21 +64,26 @@ export function MicroRepl({
editorRef.current = editor; editorRef.current = editor;
}, []); }, []);
const [ref, isVisible] = useInView({
threshold: 0.01,
onEnter: () => {
if (!editorRef.current) {
init({ code, shouldDraw });
}
},
});
const [replState, setReplState] = useState({}); const [replState, setReplState] = useState({});
const { started, isDirty, error } = replState; const { started, isDirty, error } = replState;
const editorRef = useRef(); const editorRef = useRef();
const containerRef = useRef(); const containerRef = useRef();
const [client, setClient] = useState(false);
useEffect(() => {
setClient(true);
if (!editorRef.current) {
setTimeout(() => {
init({ code, shouldDraw });
});
}
}, []);
if (!client) {
return <pre>{code}</pre>;
}
return ( return (
<div className="overflow-hidden rounded-t-md bg-background border border-lineHighlight" ref={ref}> <div className="overflow-hidden rounded-t-md bg-background border border-lineHighlight">
{!hideHeader && ( {!hideHeader && (
<div className="flex justify-between bg-lineHighlight"> <div className="flex justify-between bg-lineHighlight">
<div className="flex"> <div className="flex">
@ -117,11 +121,6 @@ export function MicroRepl({
if (el && el.width !== el.clientWidth) { if (el && el.width !== el.clientWidth) {
el.width = el.clientWidth; el.width = el.clientWidth;
} }
//const ratio = el.clientWidth / canvasHeight;
//const targetWidth = Math.round(el.width * ratio);
//if (el.width !== targetWidth) {
// el.width = targetWidth;
//}
}} }}
></canvas> ></canvas>
)} )}

View File

@ -17,7 +17,7 @@ An arpeggio is when the notes of a chord are played in sequence.
We can either write the notes by hand: We can either write the notes by hand:
<MicroRepl <MicroRepl
client:only="react" client:visible
code={`note("c eb g c4") code={`note("c eb g c4")
.clip(2).s("gm_electric_guitar_clean")`} .clip(2).s("gm_electric_guitar_clean")`}
punchcard punchcard
@ -26,7 +26,7 @@ We can either write the notes by hand:
...or use scales: ...or use scales:
<MicroRepl <MicroRepl
client:only="react" client:visible
code={`n("0 2 4 7").scale("C:minor") code={`n("0 2 4 7").scale("C:minor")
.clip(2).s("gm_electric_guitar_clean")`} .clip(2).s("gm_electric_guitar_clean")`}
punchcard punchcard
@ -35,7 +35,7 @@ We can either write the notes by hand:
...or chord symbols: ...or chord symbols:
<MicroRepl <MicroRepl
client:only="react" client:visible
code={`n("0 1 2 3").chord("Cm").mode("above:c3").voicing() code={`n("0 1 2 3").chord("Cm").mode("above:c3").voicing()
.clip(2).s("gm_electric_guitar_clean")`} .clip(2).s("gm_electric_guitar_clean")`}
punchcard punchcard
@ -44,7 +44,7 @@ We can either write the notes by hand:
...using off: ...using off:
<MicroRepl <MicroRepl
client:only="react" client:visible
code={`"0" code={`"0"
.off(1/3, add(2)) .off(1/3, add(2))
.off(1/2, add(4)) .off(1/2, add(4))
@ -59,7 +59,7 @@ We can either write the notes by hand:
A sample can be looped and chopped like this: A sample can be looped and chopped like this:
<MicroRepl <MicroRepl
client:only="react" client:visible
code={`await samples('github:yaxu/clean-breaks/main') code={`await samples('github:yaxu/clean-breaks/main')
s("amen/8").fit().chop(16)`} s("amen/8").fit().chop(16)`}
punchcard punchcard
@ -70,7 +70,7 @@ The chops are not audible yet, because we're not doing any manipulation.
Let's add randmized doubling + reversing: Let's add randmized doubling + reversing:
<MicroRepl <MicroRepl
client:only="react" client:visible
code={`await samples('github:yaxu/clean-breaks/main') code={`await samples('github:yaxu/clean-breaks/main')
s("amen/8").fit().chop(16).cut(1) s("amen/8").fit().chop(16).cut(1)
.sometimesBy(.5, ply(2)) .sometimesBy(.5, ply(2))
@ -81,7 +81,7 @@ s("amen/8").fit().chop(16).cut(1)
If we want to specify the order of samples, we can replace `chop` with `slice`: If we want to specify the order of samples, we can replace `chop` with `slice`:
<MicroRepl <MicroRepl
client:only="react" client:visible
code={`await samples('github:yaxu/clean-breaks/main') code={`await samples('github:yaxu/clean-breaks/main')
s("amen/8").fit() s("amen/8").fit()
.slice(8, "<0 1 2 3 4*2 5 6 [6 7]>") .slice(8, "<0 1 2 3 4*2 5 6 [6 7]>")
@ -92,7 +92,7 @@ s("amen/8").fit()
If we use `splice` instead of `slice`, the speed adjusts to the duration of the event: If we use `splice` instead of `slice`, the speed adjusts to the duration of the event:
<MicroRepl <MicroRepl
client:only="react" client:visible
code={`await samples('github:yaxu/clean-breaks/main') code={`await samples('github:yaxu/clean-breaks/main')
s("amen") s("amen")
.splice(8, "<0 1 2 3 4*2 5 6 [6 7]>") .splice(8, "<0 1 2 3 4*2 5 6 [6 7]>")
@ -107,7 +107,7 @@ Note that we don't need `fit`, because `splice` will do that by itself.
A minimal filter envelope looks like this: A minimal filter envelope looks like this:
<MicroRepl <MicroRepl
client:only="react" client:visible
code={`note("g1 bb1 <c2 eb2> d2") code={`note("g1 bb1 <c2 eb2> d2")
.s("sawtooth") .s("sawtooth")
.lpf(400).lpa(.2).lpenv(4) .lpf(400).lpa(.2).lpenv(4)
@ -117,7 +117,7 @@ A minimal filter envelope looks like this:
We can flip the envelope by setting `lpenv` negative + add some resonance `lpq`: We can flip the envelope by setting `lpenv` negative + add some resonance `lpq`:
<MicroRepl <MicroRepl
client:only="react" client:visible
code={`note("g1 bb1 <c2 eb2> d2") code={`note("g1 bb1 <c2 eb2> d2")
.s("sawtooth").lpq(8) .s("sawtooth").lpq(8)
.lpf(400).lpa(.2).lpenv(-4) .lpf(400).lpa(.2).lpenv(-4)
@ -129,7 +129,7 @@ We can flip the envelope by setting `lpenv` negative + add some resonance `lpq`:
We can layer sounds by separating them with ",": We can layer sounds by separating them with ",":
<MicroRepl <MicroRepl
client:only="react" client:visible
code={`note("<g1 bb1 d2 f1>") code={`note("<g1 bb1 d2 f1>")
.s("sawtooth, square") // <------ .s("sawtooth, square") // <------
.scope()`} .scope()`}
@ -138,7 +138,7 @@ We can layer sounds by separating them with ",":
We can control the gain of individual sounds like this: We can control the gain of individual sounds like this:
<MicroRepl <MicroRepl
client:only="react" client:visible
code={`note("<g1 bb1 d2 f1>") code={`note("<g1 bb1 d2 f1>")
.s("sawtooth, square:0:.5") // <--- "name:number:gain" .s("sawtooth, square:0:.5") // <--- "name:number:gain"
.scope()`} .scope()`}
@ -147,7 +147,7 @@ We can control the gain of individual sounds like this:
For more control over each voice, we can use `layer`: For more control over each voice, we can use `layer`:
<MicroRepl <MicroRepl
client:only="react" client:visible
code={`note("<g1 bb1 d2 f1>").layer( code={`note("<g1 bb1 d2 f1>").layer(
x=>x.s("sawtooth").vib(4), x=>x.s("sawtooth").vib(4),
x=>x.s("square").add(note(12)) x=>x.s("square").add(note(12))
@ -162,7 +162,7 @@ With `layer`, you can use any pattern method available on each voice, so sky is
We can fatten a sound by adding a detuned version to itself: We can fatten a sound by adding a detuned version to itself:
<MicroRepl <MicroRepl
client:only="react" client:visible
code={`note("<g1 bb1 d2 f1>") code={`note("<g1 bb1 d2 f1>")
.add(note("0,.1")) // <------ chorus .add(note("0,.1")) // <------ chorus
.s("sawtooth").scope()`} .s("sawtooth").scope()`}
@ -175,7 +175,7 @@ Try out different values, or add another voice!
Here is a simple example of a polyrhythm: Here is a simple example of a polyrhythm:
<MicroRepl client:only="react" code={`s("bd*2,hh*3")`} punchcard /> <MicroRepl client:visible code={`s("bd*2,hh*3")`} punchcard />
A polyrhythm is when 2 different tempos happen at the same time. A polyrhythm is when 2 different tempos happen at the same time.
@ -183,7 +183,7 @@ A polyrhythm is when 2 different tempos happen at the same time.
This is a polymeter: This is a polymeter:
<MicroRepl client:only="react" code={`s("<bd rim>,<hh hh oh>").fast(2)`} punchcard /> <MicroRepl client:visible code={`s("<bd rim>,<hh hh oh>").fast(2)`} punchcard />
A polymeter is when 2 different bar lengths play at the same tempo. A polymeter is when 2 different bar lengths play at the same tempo.
@ -191,7 +191,7 @@ A polymeter is when 2 different bar lengths play at the same tempo.
This is a phasing: This is a phasing:
<MicroRepl client:only="react" code={`note("<C D G A Bb D C A G D Bb A>*[6,6.1]").piano()`} punchcard /> <MicroRepl client:visible code={`note("<C D G A Bb D C A G D Bb A>*[6,6.1]").piano()`} punchcard />
Phasing happens when the same sequence plays at slightly different tempos. Phasing happens when the same sequence plays at slightly different tempos.
@ -200,7 +200,7 @@ Phasing happens when the same sequence plays at slightly different tempos.
Using `run` with `n`, we can rush through a sample bank: Using `run` with `n`, we can rush through a sample bank:
<MicroRepl <MicroRepl
client:only="react" client:visible
code={`await samples('github:Bubobubobubobubo/Dough-Fox/main') code={`await samples('github:Bubobubobubobubo/Dough-Fox/main')
n(run(8)).s("ftabla")`} n(run(8)).s("ftabla")`}
punchcard punchcard
@ -211,7 +211,7 @@ Often times, you'll hear the beginning of the phrase not where the pattern begin
In this case, I hear the beginning at the third sample, which can be accounted for with `early`. In this case, I hear the beginning at the third sample, which can be accounted for with `early`.
<MicroRepl <MicroRepl
client:only="react" client:visible
code={`await samples('github:Bubobubobubobubo/Dough-Fox/main') code={`await samples('github:Bubobubobubobubo/Dough-Fox/main')
n(run(8)).s("ftabla").early(2/8)`} n(run(8)).s("ftabla").early(2/8)`}
/> />
@ -219,7 +219,7 @@ In this case, I hear the beginning at the third sample, which can be accounted f
Let's add some randomness: Let's add some randomness:
<MicroRepl <MicroRepl
client:only="react" client:visible
code={`await samples('github:Bubobubobubobubo/Dough-Fox/main') code={`await samples('github:Bubobubobubobubo/Dough-Fox/main')
n(run(8)).s("ftabla").early(2/8) n(run(8)).s("ftabla").early(2/8)
.sometimes(mul(speed(1.5)))`} .sometimes(mul(speed(1.5)))`}
@ -230,7 +230,7 @@ n(run(8)).s("ftabla").early(2/8)
We can emulate a pitch warbling effect like this: We can emulate a pitch warbling effect like this:
<MicroRepl <MicroRepl
client:only="react" client:visible
code={`note("c4 bb f eb") code={`note("c4 bb f eb")
.add(note(perlin.range(0,.5))) // <------ warble .add(note(perlin.range(0,.5))) // <------ warble
.clip(2).s("gm_electric_guitar_clean")`} .clip(2).s("gm_electric_guitar_clean")`}
@ -241,7 +241,7 @@ We can emulate a pitch warbling effect like this:
There are a number of ways to change the sound duration. Using clip: There are a number of ways to change the sound duration. Using clip:
<MicroRepl <MicroRepl
client:only="react" client:visible
code={`note("f ab bb c") code={`note("f ab bb c")
.clip("<2 1 .5 .25>/2")`} .clip("<2 1 .5 .25>/2")`}
/> />
@ -250,7 +250,7 @@ The value of clip is relative to the duration of each event.
We can also create overlaps using release: We can also create overlaps using release:
<MicroRepl <MicroRepl
client:only="react" client:visible
code={`note("f ab bb c") code={`note("f ab bb c")
.release("<2 1 .5 .002>/2")`} .release("<2 1 .5 .002>/2")`}
/> />
@ -259,7 +259,7 @@ This will smoothly fade out each sound for the given number of seconds.
We could also make the notes shorter with decay / sustain: We could also make the notes shorter with decay / sustain:
<MicroRepl <MicroRepl
client:only="react" client:visible
code={`note("f ab bb c") code={`note("f ab bb c")
.decay("<.2 .1 .02>/2").sustain(0)`} .decay("<.2 .1 .02>/2").sustain(0)`}
/> />
@ -268,27 +268,27 @@ For now, there is a limitation where decay values that exceed the event duration
When using samples, we also have `.end` to cut relative to the sample length: When using samples, we also have `.end` to cut relative to the sample length:
<MicroRepl client:only="react" code={`s("oh*4").end("<1 .5 .25 .1>")`} /> <MicroRepl client:visible code={`s("oh*4").end("<1 .5 .25 .1>")`} />
Compare that to clip: Compare that to clip:
<MicroRepl client:only="react" code={`s("oh*4").clip("<1 .5 .25 .1>")`} /> <MicroRepl client:visible code={`s("oh*4").clip("<1 .5 .25 .1>")`} />
or decay / sustain or decay / sustain
<MicroRepl client:only="react" code={`s("oh*4").decay("<.2 .12 .06 .01>").sustain(0)`} /> <MicroRepl client:visible code={`s("oh*4").decay("<.2 .12 .06 .01>").sustain(0)`} />
## Wavetable Synthesis ## Wavetable Synthesis
You can loop a sample with `loop` / `loopEnd`: You can loop a sample with `loop` / `loopEnd`:
<MicroRepl client:only="react" code={`note("<c eb g f>").s("bd").loop(1).loopEnd(.05).gain(.2)`} /> <MicroRepl client:visible code={`note("<c eb g f>").s("bd").loop(1).loopEnd(.05).gain(.2)`} />
This allows us to play the first 5% of the bass drum as a synth! 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: To simplify loading wavetables, any sample that starts with `wt_` will be looped automatically:
<MicroRepl <MicroRepl
client:only="react" client:visible
code={`await samples('github:bubobubobubobubo/dough-waveforms/main') code={`await samples('github:bubobubobubobubo/dough-waveforms/main')
note("c eb g bb").s("wt_dbass").clip(2)`} note("c eb g bb").s("wt_dbass").clip(2)`}
/> />
@ -296,7 +296,7 @@ note("c eb g bb").s("wt_dbass").clip(2)`}
Running through different wavetables can also give interesting variations: Running through different wavetables can also give interesting variations:
<MicroRepl <MicroRepl
client:only="react" client:visible
code={`await samples('github:bubobubobubobubo/dough-waveforms/main') code={`await samples('github:bubobubobubobubo/dough-waveforms/main')
note("c2*8").s("wt_dbass").n(run(8))`} note("c2*8").s("wt_dbass").n(run(8))`}
/> />
@ -304,7 +304,7 @@ note("c2*8").s("wt_dbass").n(run(8))`}
...adding a filter envelope + reverb: ...adding a filter envelope + reverb:
<MicroRepl <MicroRepl
client:only="react" client:visible
code={`await samples('github:bubobubobubobubo/dough-waveforms/main') code={`await samples('github:bubobubobubobubo/dough-waveforms/main')
note("c2*8").s("wt_dbass").n(run(8)) note("c2*8").s("wt_dbass").n(run(8))
.lpf(perlin.range(200,2000).slow(8)) .lpf(perlin.range(200,2000).slow(8))