Skip to main content

strudel

Write a live-coded track as a Strudel pattern — mini-notation, synths, effects, song structure — and check it with the verdict. Use whenever the workspace holds a .strudel file or the user asks for a beat, a groove, a bassline, a loop, a track, or music in the pane.

설치로 이동

소스 정보

저장소
autonomous-ai/openharness
최근 소스 활동
2026년 9월 17일 03:35
감지된 SKILL.md 언어
영어
스타
78
포크
13

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
strudel
description
Write a live-coded track as a Strudel pattern — mini-notation, synths, effects, song structure — and check it with the verdict. Use whenever the workspace holds a .strudel file or the user asks for a beat, a groove, a bassline, a loop, a track, or music in the pane.
# Strudel Strudel is TidalCycles in JavaScript. A track is **one expression** that describes a cycle; the REPL plays that cycle forever until you change it. You write one file, `track.strudel`, and the pane plays it — a save hot-swaps the pattern on the next cycle without stopping the transport. The whole file is evaluated as ECMAScript 2022 (top-level `await` allowed), so JavaScript syntax rules apply: balanced brackets, balanced quotes, `//` and `/* */` comments. There is no module loader — **never write `import` or `require`**. Everything Strudel offers is already in scope. Check after every save: ```sh python3 "$STRUDEL_TOOLCHAIN/verdict.py" # track.strudel python3 "$STRUDEL_TOOLCHAIN/verdict.py" b.strudel ``` It parses the file with the same parser the REPL uses, says whether the track will play, and writes `.harness/verdict.json`, which is what the pane header shows. It cannot hear anything — no headless check can. **Playing is the pane's job and the user's ears.** ## Offline first: use synths, not samples The REPL preloads sample *maps* from GitHub at start and fetches each `.wav` the first time it is triggered. So `s("bd sd hh")` needs the internet, twice. These sound sources need nothing: | Source | Names | |---|---| | Oscillators | `sine` `sawtooth` `square` `triangle` (aliases `sin` `saw` `sqr` `tri`) | | Richer | `supersaw` (`unison`, `spread`, `detune`), `pulse` (`pw`, `pwrate`, `pwsweep`) | | Noise | `white` `pink` `brown` (hard → soft), `crackle` (with `density`) | | Drum | `sbd`, a synthesised bass drum | | Chip | `zzfx` `z_sine` `z_sawtooth` `z_triangle` `z_square` `z_tan` `z_noise` | Build drums out of those: `s("sbd*4")` is the kick, short noise is the snare and the hats. ```js stack( s("sbd*4").decay(0.24).lpf(280), s("~ white ~ white").decay(0.11).sustain(0).hpf(1200).gain(0.5), s("white*8").decay(0.03).sustain(0).hpf(7000).gain("[0.3 0.14]*4") ) ``` Reach for `bd sd hh oh rim cp` and `.bank("RolandTR909")` only when the user wants real drum machines and has a connection. Say so when you do; the verdict warns about it too. If you set `note()` and no `s()`, the sound defaults to `triangle` — already offline. ## Mini-notation Everything inside a double-quoted string is mini-notation. Single quotes are a plain string, not a pattern. | | | |---|---| | `a b c` | a sequence; the whole thing still takes one cycle, so more events = shorter events | | `~` or `-` | a rest | | `[a b]` | a subdivision — one slot of the outer sequence, nests as deep as you like | | `<a b c>` | one per cycle. `<a b c>` is exactly `[a b c]/3` | | `a*2` | faster / repeat within the slot (decimals allowed: `*2.75`) | | `a/2` | slower — spread over 2 cycles | | `a, b` | in parallel (a stack), at the top level or inside `[ ]` | | `a!3` | replicate: `a a a`, *without* speeding up. Bare `!` means 2 | | `a@3` | elongate: this one gets 3 slots' worth of time. Default weight 1 | | `a?` `a?0.1` | drop it 50% / 10% of the time | | `a\|b` | pick one at random each cycle | | `a(3,8)` `a(3,8,1)` | Euclid: pulses, steps, and an optional rotation | | `{a b c, x y}` `{a b c}%4` | polymeter | | `bd:2` | second value — for `s()` that is the sample index, a third is gain | ```js s("bd [hh hh] sd [hh bd]") // subdivision note("<c2!2 g1!2 as1!2 f1!2>(3,8)").s("sawtooth") // 8 bars of euclidean bass s("[bd sd]*2, white*8") // two voices in one string n("<4 [3@3 4] [<2 0> ~] ~>").scale("d4:minor") // weights and nesting ``` ## Notes, scales, chords - `note("c3 e3 g3")` — letters a–g, `b`/`#`, optional octave 0–9, default octave 3. MIDI numbers work too (`note("48 55")`, 69 = A4). - `n("0 2 4").scale("c4:minor")` — degrees of a scale, zero-indexed, negatives wrap backwards. The scale name takes **no spaces**: `"c:minor"`, `"f#4:mixolydian"`, `"a:harmonic:minor"`. - `[c3,eb3,g3]` inside a pattern is a chord — a stack of notes in one slot. - `n("0 1 2 3").chord("<C Am F G>").voicing()` builds voicings from chord symbols. - `.arp("0 [0,2] 1 [0,2]")` spreads a stacked chord over time. It takes a **pattern of indices**, not mode names like `"up"`. ```js note("<[c3,ds3,g3] [g2,as2,d3] [as2,d3,f3] [f2,gs2,c3]>") .s("supersaw").unison(5).spread(0.45).attack(0.5).release(1.2).room(0.6) ``` ## Shaping the sound - **Envelope** `attack`/`att`, `decay`/`dec`, `sustain`/`sus`, `release`/`rel`, or all four at once as `.adsr(".1:.1:.5:.2")`. `clip` (alias `legato`) multiplies note length. - **Filter** `lpf` (aliases `cutoff`, `lp`), `hpf`, `bpf`; resonance is `lpq` (alias `resonance`, 0–50), `hpq`, `bpq`. `ftype` picks `12db` / `ladder` / `24db`. - **Filter envelope** `lpattack` `lpdecay` `lpsustain` `lprelease` `lpenv` (short: `lpa` `lpd` `lps` `lpr` `lpe`). `lpenv` is the depth and may be negative. - **Space** `room` (0–1) with `roomsize` (alias `rsize`/`size`, 0–10 — change it sparsely); `delay`, `delaytime`/`dt`, `delayfeedback`/`dfb`. `orbit(n)` gives a voice its own reverb and delay bus; patterns on the same orbit share one. - **Colour** `distort` (prefer it over the deprecated `shape`), `crush` (1 crunchy … 16 clean), `coarse`, `vowel("<a e i o u>")`, `phaser`. - **Level and place** `gain`, `postgain`, `pan` (0 left … 1 right). Many of these take several values through `:` — `lpf("1000:10")` is cutoff and resonance, `delay("0.65:0.25:0.9")` is level, time and feedback, `room("0.9:4")` is level and size. ```js note("c3 bb2 f3 eb3").s("sawtooth").lpf(600).adsr(".1:.1:.5:.2") note("c2 e2 f2 g2").s("sawtooth").lpf(300).lpa(0.5).lpenv("<4 2 1 0 -1 -2 -4>/4") ``` ## Moving the pattern `rev` `palindrome` `iter(4)` `chunk(4, f)` `ply(2)` `off(1/16, f)` `superimpose(f)` `layer(f)` `jux(rev)` `juxBy(0.5, f)` `segment(16)` `struct("x ~ x x")` `mask("1 0")` `degradeBy(0.3)` `sometimes(f)` `sometimesBy(0.4, f)` `often` (0.75) `rarely` (0.25) `almostAlways` (0.9) `someCyclesBy(p, f)` `every(4, f)` `slow(2)` `fast(2)` `euclid(3,8)` `euclidRot(3,8,1)` `euclidLegato(3,8)` `add(note("12"))` `echo(3, 1/8, 0.5)` `linger` `swingBy(1/3, 4)` `hurry(2)`. ```js n("0 1 [4 3] 2 0 2 [~ 3] 4").s("triangle").jux(rev) s("white*8").sometimesBy(0.4, x => x.hpf(2000).gain(0.4)) n("0 [4 <3 2>] <2 3> [~ 1]".off(1/16, x => x.add(4))).scale("c5:minor").s("triangle").dec(0.1) ``` ## Signals as modulators `sine` `cosine` `saw` `isaw` `tri` `square` `rand` `perlin` run 0…1 (the `…2` variants, `sine2` `saw2` `rand2`, run −1…1); `irand(n)` is called as a function. Shape them with `.range(lo, hi)` (or `.rangex` for exponential) and slow them with `.slow(n)`. ```js note("<[c2 c3]*4 [bb1 bb2]*4>").s("sawtooth").lpf(sine.range(100, 2000).slow(4)) n(saw.range(0, 8).segment(8)).scale("c:major") n(irand(8)).struct("x x*2 x x*3").scale("c:minor") ``` **The catch worth knowing:** a signal is sampled once per *event*, not continuously. A pad held for a whole cycle gets one filter value per note, not a sweep. Add events to hear the movement: ```js s("supersaw").seg(16).lpf(tri.range(100, 5000).slow(2)) ``` Envelopes *are* continuous in time: `attack`/`decay`/`release`, the pitch envelope (`penv`, `pattack`, `pdecay`), the filter envelopes, `tremolo`, `phaser`, `vib`. ## Putting a track together **Tempo.** `setcpm(120/4)` — cycles per minute, and with one bar per cycle that is 120 bpm in 4/4. `setcps(x)` is the same thing per second; the default is 0.5 cps, a 2-second cycle. Set it on the first line so the file says its own tempo. **Layers.** `stack(a, b, c)` plays voices together and is the shape to reach for. The `$:` label form does the same thing one line at a time, and `_$:` mutes a line — handy when the user asks to drop a part: ```js setcpm(120/4) $: s("sbd*4").decay(0.24) $: note("<c2 g1>(3,8)").s("sawtooth").lpf(800) _$: n("0 3 7").scale("c4:minor").s("triangle") // muted ``` **Name every voice.** The pane draws one lane per `stack` argument (or `$:` line) — its notes scrolling under a playhead, an activity light, Mute and Solo — and titles the lane from the comment directly above the voice, up to the first ` — `, `:` or `.`. So give every voice its own argument and a title line, and keep what it does after the dash: ```js stack( // Kick — four on the floor s("sbd*4").decay(0.24), // Bass — three pulses in eight, filter opening over four bars note("<c2 g1>(3,8)").s("sawtooth").lpf(sine.range(220, 1600).slow(4)) ) ``` A named label (`bass: note(...)`) is its own title. Without a comment the lane is named after the first sound it plays. **Structure.** A cycle is a bar. Longer form comes from `<>` (one per cycle), `!` and `@` to hold a chord for two bars, `/n` to stretch, and `every(n, f)` / `someCyclesBy` to vary. Eight bars of harmony is `<[chord]!2 [chord]!2 [chord]!2 [chord]!2>`. - **Intro** — one or two voices, filter closed, no lead. `.lpf(400)` on everything is a whole intro. - **Groove** — kick, backbeat, hats, bass, then harmony. Give each voice its own register and one clear rhythmic idea; do not let two voices play the same subdivision at the same volume. - **Break** — `.mask("<1 1 1 0>")` drops a voice for the fourth bar of four; `every(4, x => x.fast(2))` doubles it instead. A break is one voice leaving, not everything changing. - **Lift** — open the filter, add `.jux(rev)`, add an octave with `.sometimes(add(note("12")))`.
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기