mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-23 03:28:33 +00:00
minirepl multi tune support + add other getting-started examples (more to come)
This commit is contained in:
parent
5ada0daf40
commit
f85fb35c66
@ -1,4 +1,12 @@
|
|||||||
export function Icon({ type }) {
|
export function Icon({ type }) {
|
||||||
|
if (type === 'skip') {
|
||||||
|
// !Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.
|
||||||
|
return (
|
||||||
|
<svg fillRule="evenodd" fill="currentColor" xmlns="http://www.w3.org/2000/svg" height="16" width="10" viewBox="0 0 320 512">
|
||||||
|
<path d="M52.5 440.6c-9.5 7.9-22.8 9.7-34.1 4.4S0 428.4 0 416V96C0 83.6 7.2 72.3 18.4 67s24.5-3.6 34.1 4.4l192 160L256 241V96c0-17.7 14.3-32 32-32s32 14.3 32 32V416c0 17.7-14.3 32-32 32s-32-14.3-32-32V271l-11.5 9.6-192 160z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||||
{
|
{
|
||||||
@ -31,6 +39,13 @@ export function Icon({ type }) {
|
|||||||
clipRule="evenodd"
|
clipRule="evenodd"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
skip: (
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M52.5 440.6c-9.5 7.9-22.8 9.7-34.1 4.4S0 428.4 0 416V96C0 83.6 7.2 72.3 18.4 67s24.5-3.6 34.1 4.4l192 160L256 241V96c0-17.7 14.3-32 32-32s32 14.3 32 32V416c0 17.7-14.3 32-32 32s-32-14.3-32-32V271l-11.5 9.6-192 160z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
),
|
||||||
}[type]
|
}[type]
|
||||||
}
|
}
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { useState, useRef, useCallback, useMemo, useEffect } from 'react';
|
import { useState, useRef, useCallback, useMemo, useEffect } from 'react';
|
||||||
import { Icon } from './Icon';
|
import { Icon } from './Icon';
|
||||||
import { silence, getPunchcardPainter, noteToMidi } from '@strudel/core';
|
import { silence, getPunchcardPainter, noteToMidi, _mod } from '@strudel/core';
|
||||||
import { transpiler } from '@strudel/transpiler';
|
import { transpiler } from '@strudel/transpiler';
|
||||||
import { getAudioContext, webaudioOutput } from '@strudel/webaudio';
|
import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel/webaudio';
|
||||||
import { StrudelMirror } from '@strudel/codemirror';
|
import { StrudelMirror } from '@strudel/codemirror';
|
||||||
// import { prebake } from '@strudel/repl';
|
// import { prebake } from '@strudel/repl';
|
||||||
import { prebake } from '../repl/prebake.mjs';
|
import { prebake } from '../repl/prebake.mjs';
|
||||||
@ -10,14 +10,16 @@ import { loadModules } from '../repl/util.mjs';
|
|||||||
import Claviature from '@components/Claviature';
|
import Claviature from '@components/Claviature';
|
||||||
import useClient from '@src/useClient.mjs';
|
import useClient from '@src/useClient.mjs';
|
||||||
|
|
||||||
let prebaked, modulesLoading;
|
let prebaked, modulesLoading, audioLoading;
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
prebaked = prebake();
|
prebaked = prebake();
|
||||||
modulesLoading = loadModules();
|
modulesLoading = loadModules();
|
||||||
|
audioLoading = initAudioOnFirstClick();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MiniRepl({
|
export function MiniRepl({
|
||||||
tune: code,
|
tune,
|
||||||
|
tunes,
|
||||||
hideHeader = false,
|
hideHeader = false,
|
||||||
canvasHeight = 100,
|
canvasHeight = 100,
|
||||||
onTrigger,
|
onTrigger,
|
||||||
@ -26,6 +28,7 @@ export function MiniRepl({
|
|||||||
claviature,
|
claviature,
|
||||||
claviatureLabels,
|
claviatureLabels,
|
||||||
}) {
|
}) {
|
||||||
|
const code = tunes ? tunes[0] : tune;
|
||||||
const id = useMemo(() => s4(), []);
|
const id = useMemo(() => s4(), []);
|
||||||
const canvasId = useMemo(() => `canvas-${id}`, [id]);
|
const canvasId = useMemo(() => `canvas-${id}`, [id]);
|
||||||
const shouldDraw = !!punchcard || !!claviature;
|
const shouldDraw = !!punchcard || !!claviature;
|
||||||
@ -75,7 +78,7 @@ export function MiniRepl({
|
|||||||
}
|
}
|
||||||
return pat;
|
return pat;
|
||||||
},
|
},
|
||||||
prebake: async () => Promise.all([modulesLoading, prebaked]),
|
prebake: async () => Promise.all([modulesLoading, prebaked, audioLoading]),
|
||||||
onUpdateState: (state) => {
|
onUpdateState: (state) => {
|
||||||
setReplState({ ...state });
|
setReplState({ ...state });
|
||||||
},
|
},
|
||||||
@ -91,6 +94,14 @@ export function MiniRepl({
|
|||||||
const containerRef = useRef();
|
const containerRef = useRef();
|
||||||
const client = useClient();
|
const client = useClient();
|
||||||
|
|
||||||
|
const [tuneIndex, setTuneIndex] = useState(0);
|
||||||
|
const changeTune = (index) => {
|
||||||
|
index = _mod(index, tunes.length);
|
||||||
|
setTuneIndex(index);
|
||||||
|
editorRef.current?.setCode(tunes[index]);
|
||||||
|
editorRef.current?.evaluate();
|
||||||
|
};
|
||||||
|
|
||||||
if (!client) {
|
if (!client) {
|
||||||
return <pre>{code}</pre>;
|
return <pre>{code}</pre>;
|
||||||
}
|
}
|
||||||
@ -119,6 +130,28 @@ export function MiniRepl({
|
|||||||
<Icon type="refresh" />
|
<Icon type="refresh" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
{tunes && (
|
||||||
|
<div className="flex">
|
||||||
|
<button
|
||||||
|
className={
|
||||||
|
'cursor-pointer w-16 flex items-center justify-center p-1 border-r border-lineHighlight text-foreground bg-lineHighlight hover:bg-background'
|
||||||
|
}
|
||||||
|
onClick={() => changeTune(tuneIndex - 1)}
|
||||||
|
>
|
||||||
|
<div className="rotate-180">
|
||||||
|
<Icon type="skip" />
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={
|
||||||
|
'cursor-pointer w-16 flex items-center justify-center p-1 border-r border-lineHighlight text-foreground bg-lineHighlight hover:bg-background'
|
||||||
|
}
|
||||||
|
onClick={() => changeTune(tuneIndex + 1)}
|
||||||
|
>
|
||||||
|
<Icon type="skip" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="overflow-auto relative p-1">
|
<div className="overflow-auto relative p-1">
|
||||||
|
|||||||
81
website/src/examples.mjs
Normal file
81
website/src/examples.mjs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
export const examples = [
|
||||||
|
`// "coastline" @by eddyflux
|
||||||
|
await samples('github:eddyflux/crate')
|
||||||
|
setcps(.75)
|
||||||
|
let chords = chord("<Bbm9 Fm9>/4").dict('ireal')
|
||||||
|
stack(
|
||||||
|
stack( // DRUMS
|
||||||
|
s("bd").struct("<[x*<1 2> [~@3 x]] x>"),
|
||||||
|
s("~ [rim, sd:<2 3>]").room("<0 .2>"),
|
||||||
|
n("[0 <1 3>]*<2!3 4>").s("hh"),
|
||||||
|
s("rd:<1!3 2>*2").mask("<0 0 1 1>/16").gain(.5)
|
||||||
|
).bank('crate')
|
||||||
|
.mask("<[0 1] 1 1 1>/16".early(.5))
|
||||||
|
, // CHORDS
|
||||||
|
chords.offset(-1).voicing().s("gm_epiano1:1")
|
||||||
|
.phaser(4).room(.5)
|
||||||
|
, // MELODY
|
||||||
|
n("<0!3 1*2>").set(chords).mode("root:g2")
|
||||||
|
.voicing().s("gm_acoustic_bass"),
|
||||||
|
chords.n("[0 <4 3 <2 5>>*2](<3 5>,8)")
|
||||||
|
.set(x).anchor("D5").voicing()
|
||||||
|
.segment(4).clip(rand.range(.4,.8))
|
||||||
|
.room(.75).shape(.3).delay(.25)
|
||||||
|
.fm(sine.range(3,8).slow(8))
|
||||||
|
.lpf(sine.range(500,1000).slow(8)).lpq(5)
|
||||||
|
.rarely(ply("2")).chunk(4, fast(2))
|
||||||
|
.gain(perlin.range(.6, .9))
|
||||||
|
.mask("<0 1 1 0>/16")
|
||||||
|
)
|
||||||
|
.late("[0 .01]*4").late("[0 .01]*2").size(4)`,
|
||||||
|
`// "broken cut 1" @by froos
|
||||||
|
|
||||||
|
await samples('github:tidalcycles/Dirt-Samples/master')
|
||||||
|
samples({
|
||||||
|
'slap': 'https://cdn.freesound.org/previews/495/495416_10350281-lq.mp3',
|
||||||
|
'whirl': 'https://cdn.freesound.org/previews/495/495313_10350281-lq.mp3',
|
||||||
|
'attack': 'https://cdn.freesound.org/previews/494/494947_10350281-lq.mp3'
|
||||||
|
})
|
||||||
|
|
||||||
|
setcps(1.25)
|
||||||
|
|
||||||
|
note("[c2 ~](3,8)*2,eb,g,bb,d").s("sawtooth")
|
||||||
|
.noise(0.3)
|
||||||
|
.lpf(perlin.range(800,2000).mul(0.6))
|
||||||
|
.lpenv(perlin.range(1,5)).lpa(.25).lpd(.1).lps(0)
|
||||||
|
.add.mix(note("<0!3 [1 <4!3 12>]>")).late(.5)
|
||||||
|
.vib("4:.2")
|
||||||
|
.room(1).roomsize(4).slow(4)
|
||||||
|
.stack(
|
||||||
|
s("bd").late("<0.01 .251>"),
|
||||||
|
s("breaks165:1/2").fit()
|
||||||
|
.chop(4).sometimesBy(.4, ply("2"))
|
||||||
|
.sometimesBy(.1, ply("4")).release(.01)
|
||||||
|
.gain(1.5).sometimes(mul(speed("1.05"))).cut(1)
|
||||||
|
,
|
||||||
|
s("<whirl attack>?").delay(".8:.1:.8").room(2).slow(8).cut(2),
|
||||||
|
).reset("<x@30 [x*[8 [8 [16 32]]]]@2>".late(2))`,
|
||||||
|
`// "acidic tooth" @by eddyflux
|
||||||
|
setcps(1)
|
||||||
|
stack(
|
||||||
|
note("[<g1 f1>/8](<3 5>,8)")
|
||||||
|
.clip(perlin.range(.15,1.5))
|
||||||
|
.release(.1)
|
||||||
|
.s("sawtooth")
|
||||||
|
.lpf(sine.range(400,800).slow(16))
|
||||||
|
.lpq(cosine.range(6,14).slow(3))
|
||||||
|
.lpenv(sine.mul(4).slow(4))
|
||||||
|
.lpd(.2).lpa(.02)
|
||||||
|
.ftype('24db')
|
||||||
|
.rarely(add(note(12)))
|
||||||
|
.room(.2).shape(.3).postgain(.5)
|
||||||
|
.superimpose(x=>x.add(note(12)).delay(.5).bpf(1000))
|
||||||
|
.gain("[.2 1@3]*2") // fake sidechain
|
||||||
|
,
|
||||||
|
stack(
|
||||||
|
s("bd*2").mask("<0@4 1@16>"),
|
||||||
|
s("hh*8").gain(saw.mul(saw.fast(2))).clip(sine)
|
||||||
|
.mask("<0@8 1@16>")
|
||||||
|
).bank('RolandTR909')
|
||||||
|
)`,
|
||||||
|
];
|
||||||
@ -4,6 +4,7 @@ layout: ../../layouts/MainLayout.astro
|
|||||||
---
|
---
|
||||||
|
|
||||||
import { MiniRepl } from '../../docs/MiniRepl';
|
import { MiniRepl } from '../../docs/MiniRepl';
|
||||||
|
import { examples } from '../../examples.mjs';
|
||||||
|
|
||||||
# Welcome
|
# Welcome
|
||||||
|
|
||||||
@ -29,40 +30,13 @@ The best place to actually make music with Strudel is the [Strudel REPL](https:/
|
|||||||
- teaching: focussing on a low barrier of entry, Strudel is a good fit for teaching music and code at the same time.
|
- teaching: focussing on a low barrier of entry, Strudel is a good fit for teaching music and code at the same time.
|
||||||
- integrate into your existing music setup: either via MIDI or OSC, you can use Strudel as a really flexible sequencer
|
- integrate into your existing music setup: either via MIDI or OSC, you can use Strudel as a really flexible sequencer
|
||||||
|
|
||||||
## Example
|
## Examples
|
||||||
|
|
||||||
Here is an example of how strudel can sound:
|
Here are some examples of how strudel can sound:
|
||||||
|
|
||||||
<MiniRepl
|
<MiniRepl client:idle tunes={examples} />
|
||||||
client:idle
|
|
||||||
tune={`stack(
|
|
||||||
// drums
|
|
||||||
s("bd,[~ <sd!3 sd(3,4,2)>],hh*8")
|
|
||||||
.speed(perlin.range(.8,.9)), // random sample speed variation
|
|
||||||
// bassline
|
|
||||||
"<a1 b1\*2 a1(3,8) e2>"
|
|
||||||
.off(1/8,x=>x.add(12).degradeBy(.5)) // random octave jumps
|
|
||||||
.add(perlin.range(0,.5)) // random pitch variation
|
|
||||||
.superimpose(add(.05)) // add second, slightly detuned voice
|
|
||||||
.note() // wrap in "note"
|
|
||||||
.decay(.15).sustain(0) // make each note of equal length
|
|
||||||
.s('sawtooth') // waveform
|
|
||||||
.gain(.4) // turn down
|
|
||||||
.cutoff(sine.slow(7).range(300,5000)), // automate cutoff
|
|
||||||
// chords
|
|
||||||
"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings('lefthand')
|
|
||||||
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice
|
|
||||||
.add(perlin.range(0,.5)) // random pitch variation
|
|
||||||
.note() // wrap in "note"
|
|
||||||
.s('sawtooth') // waveform
|
|
||||||
.gain(.16) // turn down
|
|
||||||
.cutoff(500) // fixed cutoff
|
|
||||||
.attack(1) // slowly fade in
|
|
||||||
)
|
|
||||||
.slow(3/2)`}
|
|
||||||
/>
|
|
||||||
|
|
||||||
To hear more, go to the [Strudel REPL](https://strudel.cc/) and press shuffle to hear a random example pattern.
|
These examples cannot fully encompass the variety of things you can do, so [check out the showcase](/intro/showcase/) for some videos of how people use Strudel.
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user