Skip to content

Commit f85fb35

Browse files
committed
minirepl multi tune support + add other getting-started examples (more to come)
1 parent 5ada0da commit f85fb35

File tree

4 files changed

+139
-36
lines changed

4 files changed

+139
-36
lines changed

website/src/docs/Icon.jsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
export function Icon({ type }) {
2+
if (type === 'skip') {
3+
// !Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.
4+
return (
5+
<svg fillRule="evenodd" fill="currentColor" xmlns="http://www.w3.org/2000/svg" height="16" width="10" viewBox="0 0 320 512">
6+
<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" />
7+
</svg>
8+
);
9+
}
210
return (
311
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
412
{
@@ -31,6 +39,13 @@ export function Icon({ type }) {
3139
clipRule="evenodd"
3240
/>
3341
),
42+
skip: (
43+
<path
44+
fillRule="evenodd"
45+
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"
46+
clipRule="evenodd"
47+
/>
48+
),
3449
}[type]
3550
}
3651
</svg>

website/src/docs/MiniRepl.jsx

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,25 @@
11
import { useState, useRef, useCallback, useMemo, useEffect } from 'react';
22
import { Icon } from './Icon';
3-
import { silence, getPunchcardPainter, noteToMidi } from '@strudel/core';
3+
import { silence, getPunchcardPainter, noteToMidi, _mod } from '@strudel/core';
44
import { transpiler } from '@strudel/transpiler';
5-
import { getAudioContext, webaudioOutput } from '@strudel/webaudio';
5+
import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel/webaudio';
66
import { StrudelMirror } from '@strudel/codemirror';
77
// import { prebake } from '@strudel/repl';
88
import { prebake } from '../repl/prebake.mjs';
99
import { loadModules } from '../repl/util.mjs';
1010
import Claviature from '@components/Claviature';
1111
import useClient from '@src/useClient.mjs';
1212

13-
let prebaked, modulesLoading;
13+
let prebaked, modulesLoading, audioLoading;
1414
if (typeof window !== 'undefined') {
1515
prebaked = prebake();
1616
modulesLoading = loadModules();
17+
audioLoading = initAudioOnFirstClick();
1718
}
1819

1920
export function MiniRepl({
20-
tune: code,
21+
tune,
22+
tunes,
2123
hideHeader = false,
2224
canvasHeight = 100,
2325
onTrigger,
@@ -26,6 +28,7 @@ export function MiniRepl({
2628
claviature,
2729
claviatureLabels,
2830
}) {
31+
const code = tunes ? tunes[0] : tune;
2932
const id = useMemo(() => s4(), []);
3033
const canvasId = useMemo(() => `canvas-${id}`, [id]);
3134
const shouldDraw = !!punchcard || !!claviature;
@@ -75,7 +78,7 @@ export function MiniRepl({
7578
}
7679
return pat;
7780
},
78-
prebake: async () => Promise.all([modulesLoading, prebaked]),
81+
prebake: async () => Promise.all([modulesLoading, prebaked, audioLoading]),
7982
onUpdateState: (state) => {
8083
setReplState({ ...state });
8184
},
@@ -91,6 +94,14 @@ export function MiniRepl({
9194
const containerRef = useRef();
9295
const client = useClient();
9396

97+
const [tuneIndex, setTuneIndex] = useState(0);
98+
const changeTune = (index) => {
99+
index = _mod(index, tunes.length);
100+
setTuneIndex(index);
101+
editorRef.current?.setCode(tunes[index]);
102+
editorRef.current?.evaluate();
103+
};
104+
94105
if (!client) {
95106
return <pre>{code}</pre>;
96107
}
@@ -119,6 +130,28 @@ export function MiniRepl({
119130
<Icon type="refresh" />
120131
</button>
121132
</div>
133+
{tunes && (
134+
<div className="flex">
135+
<button
136+
className={
137+
'cursor-pointer w-16 flex items-center justify-center p-1 border-r border-lineHighlight text-foreground bg-lineHighlight hover:bg-background'
138+
}
139+
onClick={() => changeTune(tuneIndex - 1)}
140+
>
141+
<div className="rotate-180">
142+
<Icon type="skip" />
143+
</div>
144+
</button>
145+
<button
146+
className={
147+
'cursor-pointer w-16 flex items-center justify-center p-1 border-r border-lineHighlight text-foreground bg-lineHighlight hover:bg-background'
148+
}
149+
onClick={() => changeTune(tuneIndex + 1)}
150+
>
151+
<Icon type="skip" />
152+
</button>
153+
</div>
154+
)}
122155
</div>
123156
)}
124157
<div className="overflow-auto relative p-1">

website/src/examples.mjs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
export const examples = [
2+
`// "coastline" @by eddyflux
3+
await samples('github:eddyflux/crate')
4+
setcps(.75)
5+
let chords = chord("<Bbm9 Fm9>/4").dict('ireal')
6+
stack(
7+
stack( // DRUMS
8+
s("bd").struct("<[x*<1 2> [~@3 x]] x>"),
9+
s("~ [rim, sd:<2 3>]").room("<0 .2>"),
10+
n("[0 <1 3>]*<2!3 4>").s("hh"),
11+
s("rd:<1!3 2>*2").mask("<0 0 1 1>/16").gain(.5)
12+
).bank('crate')
13+
.mask("<[0 1] 1 1 1>/16".early(.5))
14+
, // CHORDS
15+
chords.offset(-1).voicing().s("gm_epiano1:1")
16+
.phaser(4).room(.5)
17+
, // MELODY
18+
n("<0!3 1*2>").set(chords).mode("root:g2")
19+
.voicing().s("gm_acoustic_bass"),
20+
chords.n("[0 <4 3 <2 5>>*2](<3 5>,8)")
21+
.set(x).anchor("D5").voicing()
22+
.segment(4).clip(rand.range(.4,.8))
23+
.room(.75).shape(.3).delay(.25)
24+
.fm(sine.range(3,8).slow(8))
25+
.lpf(sine.range(500,1000).slow(8)).lpq(5)
26+
.rarely(ply("2")).chunk(4, fast(2))
27+
.gain(perlin.range(.6, .9))
28+
.mask("<0 1 1 0>/16")
29+
)
30+
.late("[0 .01]*4").late("[0 .01]*2").size(4)`,
31+
`// "broken cut 1" @by froos
32+
33+
await samples('github:tidalcycles/Dirt-Samples/master')
34+
samples({
35+
'slap': 'https://cdn.freesound.org/previews/495/495416_10350281-lq.mp3',
36+
'whirl': 'https://cdn.freesound.org/previews/495/495313_10350281-lq.mp3',
37+
'attack': 'https://cdn.freesound.org/previews/494/494947_10350281-lq.mp3'
38+
})
39+
40+
setcps(1.25)
41+
42+
note("[c2 ~](3,8)*2,eb,g,bb,d").s("sawtooth")
43+
.noise(0.3)
44+
.lpf(perlin.range(800,2000).mul(0.6))
45+
.lpenv(perlin.range(1,5)).lpa(.25).lpd(.1).lps(0)
46+
.add.mix(note("<0!3 [1 <4!3 12>]>")).late(.5)
47+
.vib("4:.2")
48+
.room(1).roomsize(4).slow(4)
49+
.stack(
50+
s("bd").late("<0.01 .251>"),
51+
s("breaks165:1/2").fit()
52+
.chop(4).sometimesBy(.4, ply("2"))
53+
.sometimesBy(.1, ply("4")).release(.01)
54+
.gain(1.5).sometimes(mul(speed("1.05"))).cut(1)
55+
,
56+
s("<whirl attack>?").delay(".8:.1:.8").room(2).slow(8).cut(2),
57+
).reset("<x@30 [x*[8 [8 [16 32]]]]@2>".late(2))`,
58+
`// "acidic tooth" @by eddyflux
59+
setcps(1)
60+
stack(
61+
note("[<g1 f1>/8](<3 5>,8)")
62+
.clip(perlin.range(.15,1.5))
63+
.release(.1)
64+
.s("sawtooth")
65+
.lpf(sine.range(400,800).slow(16))
66+
.lpq(cosine.range(6,14).slow(3))
67+
.lpenv(sine.mul(4).slow(4))
68+
.lpd(.2).lpa(.02)
69+
.ftype('24db')
70+
.rarely(add(note(12)))
71+
.room(.2).shape(.3).postgain(.5)
72+
.superimpose(x=>x.add(note(12)).delay(.5).bpf(1000))
73+
.gain("[.2 1@3]*2") // fake sidechain
74+
,
75+
stack(
76+
s("bd*2").mask("<0@4 1@16>"),
77+
s("hh*8").gain(saw.mul(saw.fast(2))).clip(sine)
78+
.mask("<0@8 1@16>")
79+
).bank('RolandTR909')
80+
)`,
81+
];

website/src/pages/workshop/getting-started.mdx

Lines changed: 5 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ layout: ../../layouts/MainLayout.astro
44
---
55

66
import { MiniRepl } from '../../docs/MiniRepl';
7+
import { examples } from '../../examples.mjs';
78

89
# Welcome
910

@@ -29,40 +30,13 @@ The best place to actually make music with Strudel is the [Strudel REPL](https:/
2930
- teaching: focussing on a low barrier of entry, Strudel is a good fit for teaching music and code at the same time.
3031
- integrate into your existing music setup: either via MIDI or OSC, you can use Strudel as a really flexible sequencer
3132

32-
## Example
33+
## Examples
3334

34-
Here is an example of how strudel can sound:
35+
Here are some examples of how strudel can sound:
3536

36-
<MiniRepl
37-
client:idle
38-
tune={`stack(
39-
// drums
40-
s("bd,[~ <sd!3 sd(3,4,2)>],hh*8")
41-
.speed(perlin.range(.8,.9)), // random sample speed variation
42-
// bassline
43-
"<a1 b1\*2 a1(3,8) e2>"
44-
.off(1/8,x=>x.add(12).degradeBy(.5)) // random octave jumps
45-
.add(perlin.range(0,.5)) // random pitch variation
46-
.superimpose(add(.05)) // add second, slightly detuned voice
47-
.note() // wrap in "note"
48-
.decay(.15).sustain(0) // make each note of equal length
49-
.s('sawtooth') // waveform
50-
.gain(.4) // turn down
51-
.cutoff(sine.slow(7).range(300,5000)), // automate cutoff
52-
// chords
53-
"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings('lefthand')
54-
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice
55-
.add(perlin.range(0,.5)) // random pitch variation
56-
.note() // wrap in "note"
57-
.s('sawtooth') // waveform
58-
.gain(.16) // turn down
59-
.cutoff(500) // fixed cutoff
60-
.attack(1) // slowly fade in
61-
)
62-
.slow(3/2)`}
63-
/>
37+
<MiniRepl client:idle tunes={examples} />
6438

65-
To hear more, go to the [Strudel REPL](https://strudel.cc/) and press shuffle to hear a random example pattern.
39+
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.
6640

6741
## Getting Started
6842

0 commit comments

Comments
 (0)