diff --git a/src/content/strudel/index.html b/src/content/strudel/index.html new file mode 100644 index 00000000..91f05bcc --- /dev/null +++ b/src/content/strudel/index.html @@ -0,0 +1,78 @@ + + + + + + Strudel + + + + + + + + + + + + + +
+ +
+
+
+ STRUDEL.MD +
+
+ +
+
+
+
+ Loading + strudel.md... +
+
+
+ + +
+
+
+ + Strudel CODE +
+
+ +
+
+ +
+
+
+ + Strudel Editor + +
+
Ctrl+Enter Run
+
Ctrl+. Stop
+
+ + + +
+
+
+ + + + diff --git a/src/content/strudel/index.js b/src/content/strudel/index.js new file mode 100644 index 00000000..9116d313 --- /dev/null +++ b/src/content/strudel/index.js @@ -0,0 +1,48 @@ +document.addEventListener("DOMContentLoaded", () => { + const markdownContent = document.getElementById("markdown-content"); + const runAllBtn = document.getElementById("run-all-btn"); + + // Fetch markdown file + fetch("strudel.md?" + new Date().getTime()) + .then((response) => { + if (!response.ok) throw new Error(`HTTP Status ${response.status}`); + return response.text(); + }) + .then((markdown) => { + console.log("Fetched markdown:", markdown); + markdownContent.innerHTML = `
${marked.parse(markdown)}
`; + }) + .catch((error) => { + console.error("Error loading strudel.md:", error); + markdownContent.innerHTML = ` +
+ +

Failed to load strudel.md

+

${error.message}

+
+ `; + }); + + // Run all button + runAllBtn?.addEventListener("click", () => { + document + .querySelectorAll("strudel-editor") + .forEach((editor) => editor.play?.()); + }); + + // Keyboard shortcuts + document.addEventListener("keydown", (e) => { + if (e.ctrlKey && e.key === "Enter") { + e.preventDefault(); + document + .querySelectorAll("strudel-editor") + .forEach((editor) => editor.play?.()); + } + if (e.ctrlKey && e.key === ".") { + e.preventDefault(); + console.log("Stop shortcut pressed"); + } + }); + + console.log("Strudel editors initialized"); +}); diff --git a/src/content/strudel/strudel-logo.png b/src/content/strudel/strudel-logo.png new file mode 100644 index 00000000..ec9ad8e1 Binary files /dev/null and b/src/content/strudel/strudel-logo.png differ diff --git a/src/content/strudel/strudel-wac.webp b/src/content/strudel/strudel-wac.webp new file mode 100644 index 00000000..cd08a28c Binary files /dev/null and b/src/content/strudel/strudel-wac.webp differ diff --git a/src/content/strudel/strudel.md b/src/content/strudel/strudel.md new file mode 100644 index 00000000..5f0205dd --- /dev/null +++ b/src/content/strudel/strudel.md @@ -0,0 +1,239 @@ +
+ image: strudel-logo.png +
+ +# Strudel (2021) + +
+ +

+
+

+ Felix Roos & Team +

+ +

+Strudel, entwickelt von einem Team um Felix Roos und erstmals 2021 veröffentlicht, ist eine domänenspezifische Programmiersprache für Live-Coding und algorithmische Musik. Sie überträgt das Konzept von TidalCycles in die JavaScript-Welt und ermöglicht es, Musik in Echtzeit zu programmieren, zu manipulieren und direkt im Browser auszuführen. +

+ +Bekannte Anwendungen von Strudel sind Live-Coding Performances, algorithmische Musik und Musikpädagogik. Besonders relevant ist Strudel, da keine Installation erforderlich ist und die Sprache kontinuierlich um neue Synthesizer, Effekte und Pattern-Funktionen erweitert wird. + +**Was ist Live-Coding?** Live-Coding bedeutet, dass du Musik schreibst, während sie gleichzeitig läuft. Du tippst Code und hörst sofort das Ergebnis – wie ein Instrument, das man mit der Tastatur spielt. Strudel läuft komplett im Browser, du musst also nichts installieren oder einrichten. + +--- + +## Hello, beats! + +Strudel-Programme werden direkt im Browser geschrieben und ausgeführt. Du brauchst keine Entwicklungsumgebung zu installieren – öffne einfach das Editor-Fenster und starte sofort. + + +### Deinen ersten Beat programmieren + +Schreibe im Editor-Fenster: + +

+s("bd sd")
+
+ +`s(...)` steht für „sound" – du sagst Strudel, welche Klänge es abspielen soll. `"bd sd"` sind zwei Drum-Klänge: **bd** steht für Bass Drum (der tiefe Grundschlag) und **sd** für Snare Drum (der hellere Gegenschlag). Strudel wiederholt diese Sequenz automatisch in einer Endlosschleife. + +Klicke auf den Play-Button oder drücke Strg + Enter, um den Code auszuführen. +Um den Beat zu stoppen, drücke Strg + . + +### Erste Sounds ausprobieren + +

+sound("casio")
+
+ +Strudel hat viele eingebaute Klangvorlagen, sogenannte Samples. `casio` klingt wie ein altes Casio-Keyboard aus den 80ern. Du kannst einfach andere Wörter ausprobieren – wenn Strudel den Sound kennt, spielt er ihn ab. Probiere zum Beispiel `metal`, `piano` oder `jazz`. + +Ändere `casio` in `metal` und drücke wieder Strg + Enter. + +Samples wechseln: + +

+sound("casio:1")
+
+ +Viele Sounds gibt es in mehreren Varianten. Mit `:0`, `:1`, `:2` usw. wählst du eine bestimmte Version aus. Probiere verschiedene Zahlen, um andere Varianten desselben Klangs zu hören. + +### Drum Sounds + +

+sound("bd hh sd oh")
+
+ +Strudel verwendet kurze Kürzel für typische Schlagzeug-Elemente: + +- bd = Bass Drum – der tiefe Grundschlag +- sd = Snare Drum – der hellere Gegenschlag +- hh = HiHat – das kurze, metallische Zischen +- oh = Open HiHat – wie hh, aber offen und länger ausklingend +- rim = Rimshot – Schlag auf den Rand der Snare +- lt = Low Tom – tiefer Trommelklang +- mt = Middle Tom – mittlerer Trommelklang +- ht = High Tom – hoher Trommelklang +- rd = Ride Cymbal – metallisches Becken für gleichmäßige Rhythmen +- cr = Crash Cymbal – das laute Akzent-Becken + +### Sequenzen / Patterns + +

+sound("bd hh sd hh")
+sound("")
+sound("*8")
+setcpm(90/4)
+sound("*8")
+
+ +Leerzeichen zwischen Sounds teilen einen Takt gleichmäßig auf – 4 Sounds ergeben 4 gleichlange Schritte. Spitze Klammern `< >` bedeuten: Strudel wählt bei jedem Takt abwechselnd einen anderen Klang aus der Liste aus. Der Stern `*8` bedeutet, das Pattern 8× schneller abzuspielen, also mehr Schläge pro Takt. `setcpm(90/4)` setzt das Tempo – CPM steht für Cycles per Minute, und `90/4` entspricht einem typischen 90-BPM-Groove. + +### Pausen, Untersequenzen und Parallel-Sequenzen + +

+sound("bd hh - rim")
+sound("bd [hh hh] rim [hh hh]")
+sound("bd hh*2 sd hh*3")
+sound("bd hh*16 sd hh*8")
+sound("hh hh hh, bd casio")
+sound("hh hh hh, bd bd, - casio")
+sound("hh hh hh, bd [bd,casio]")
+
+ +Ein Bindestrich `-` steht für eine Pause – an dieser Stelle im Takt ist Stille. Eckige Klammern `[ ]` fassen mehrere Sounds zu einer Untersequenz zusammen, die in denselben Zeitslot gequetscht wird. Ein Komma `,` spielt zwei oder mehr Sequenzen gleichzeitig ab – so baust du mehrere Spuren übereinander. + +--- + +# Erste Effekte + +## Ein paar grundlegende Effekte + +**low-pass filter** + +

+note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>/2")
+.sound("sawtooth").lpf(800)
+
+ +`lpf` steht für Low-Pass-Filter. Er lässt nur tiefe Frequenzen durch und schneidet hohe ab – niedrige Werte (z.B. 200) klingen dumpf und dunkel, hohe Werte (z.B. 5000) klingen hell und offen. Probiere verschiedene Zahlen aus, um den Unterschied zu hören. + +**Filter automatisieren** + +

+note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>/2")
+.sound("sawtooth").lpf("200 1000")
+
+ +Statt eines festen Werts kannst du dem Filter ein Pattern übergeben. Hier wechselt der Filter zwischen 200 und 1000 – der Klang öffnet und schließt sich rhythmisch. + +**Vokal-Sound** + +

+note("<[c3,g3,e4] [bb2,f3,d4] [a2,f3,c4] [bb2,g3,eb4]>/2")
+.sound("sawtooth").vowel("/2")
+
+ +Der `vowel`-Effekt formt den Klang so, als würde eine Stimme einen Vokal singen. Mit einem Pattern wie `` wechselt der Klang rhythmisch zwischen verschiedenen Vokalformanten. + +**Gain (Lautstärke)** + +

+stack(
+  sound("hh*8").gain("[.25 1]*2"),
+  sound("bd*2,~ sd:1")
+)
+
+ +`gain` steuert die Lautstärke – 0 ist Stille, 1 ist volle Lautstärke. Mit einem Pattern wie `[.25 1]*2` erzeugst du einen rhythmischen Akzent, bei dem jede zweite Note lauter ist. + +**Stack kombinieren** + +

+stack(
+  stack(
+    sound("hh*8").gain("[.25 1]*2"),
+    sound("bd*2,~ sd:1")
+  ),
+  note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>/2")
+  .sound("sawtooth").lpf("200 1000"),
+  note("<[c3,g3,e4] [bb2,f3,d4] [a2,f3,c4] [bb2,g3,eb4]>/2")
+  .sound("sawtooth").vowel("/2")
+)
+
+ +`stack(...)` spielt mehrere Patterns gleichzeitig ab – es ist das Herzstück, um vollständige Songs zusammenzubauen. Du kannst so viele Ebenen schichten wie du möchtest. + +**ADSR-Hüllkurve** + +

+note("")
+.sound("sawtooth").lpf(600)
+.attack(.1)
+.decay(.1)
+.sustain(.25)
+.release(.2)
+
+ +Die ADSR-Hüllkurve bestimmt, wie sich die Lautstärke eines Klangs über die Zeit verhält. **Attack** ist die Zeit, bis der Klang seine volle Lautstärke erreicht. **Decay** ist, wie schnell er danach abfällt. **Sustain** ist die Lautstärke, auf der er sich hält, solange der Ton gehalten wird. **Release** ist, wie lange er nach dem Loslassen noch ausklingt. Alle Werte sind in Sekunden angegeben. + +**Delay** + +

+stack(
+  note("~ [<[d3,a3,f4]!2 [d3,bb3,g4]!2> ~]")
+  .sound("gm_electric_guitar_muted"),
+  sound("").bank("RolandTR707")
+).delay(".5")
+
+ +`delay` erzeugt ein Echo – der Klang wird verzögert ein oder mehrere Male wiederholt. Der Wert bestimmt die Lautstärke des Echos: 0 = kein Echo, 1 = sehr starkes Echo. Werte um 0.3–0.5 klingen oft musikalisch interessant. + +**Room / Reverb** + +

+n("<4 [3@3 4] [<2 0> ~@16] ~>/2")
+.scale("D4:minor").sound("gm_accordion:2")
+.room(2)
+
+ +`room` simuliert einen Raumklang – als würde der Sound in einem Raum oder einer Halle gespielt. Kleine Werte (0.1–0.5) klingen wie ein kleines Zimmer, große Werte (1–4) wie eine große Halle oder Kathedrale. + +**Panorama und Geschwindigkeit** + +

+sound("numbers:1 numbers:2 numbers:3 numbers:4")
+.pan("0 0.3 .6 1")
+.slow(2)
+
+sound("bd rim").speed("<1 2 -1 -2>")
+sound("bd*2,~ rim").slow(2)
+sound("[bd*2,~ rim]*<1 [2 4]>")
+
+ +`pan` verteilt Klänge im Stereo-Panorama: 0 = ganz links, 0.5 = Mitte, 1 = ganz rechts. `.slow(2)` verlangsamt das Pattern auf die halbe Geschwindigkeit. `speed` ändert die Abspielgeschwindigkeit eines Samples – negative Werte spielen das Sample rückwärts ab. + +**Automation mit Signalen** + +

+sound("hh*16").gain(sine)
+sound("hh*8").lpf(saw.range(500, 2000))
+note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>/2")
+.sound("sawtooth")
+.lpf(sine.range(100, 2000).slow(8))
+
+ +Statt fester Werte kannst du Signale wie `sine` oder `saw` verwenden, die sich kontinuierlich über die Zeit verändern. `sine` schwingt weich auf und ab wie eine Sinuswelle, `saw` steigt linear an und springt dann zurück. Mit `.range(min, max)` legst du fest, zwischen welchen Werten das Signal schwingen soll – so entstehen lebendige, sich bewegende Klänge ganz ohne manuelle Automation. + +## Rückblick + +| Effekt | Beispiel | +| ------- | ---------------------------------------------------------------------------------------- | +| lpf |
note("c2 c3").sound("sawtooth").lpf("<400 2000>")
| +| vowel |
note("c3 eb3 g3").sound("sawtooth").vowel("")
| +| gain |
sound("hh*8").gain("[.25 1]*2")
| +| delay |
sound("bd rim").delay(.5)
| +| room |
sound("bd rim").room(.5)
| +| pan |
sound("bd rim").pan("0 1")
| +| speed |
sound("bd rim").speed("<1 2 -1 -2>")
| +| range |
sound("hh*16").lpf(saw.range(200,4000))
| diff --git a/src/content/strudel/style.css b/src/content/strudel/style.css new file mode 100644 index 00000000..3b6727a7 --- /dev/null +++ b/src/content/strudel/style.css @@ -0,0 +1,297 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + background: #151514; + color: #e7e6e1; + font-family: + -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, + Cantarell, sans-serif; + height: 100vh; + overflow: hidden; +} + +/* Split Layout */ +.split-layout { + display: flex; + height: 100vh; + width: 100vw; +} + +/* Left Panel - Markdown Viewer */ +.markdown-panel { + width: 45%; + background: #121212; + border-right: 1px solid #333; + overflow-y: auto; + padding: 2rem 2.5rem; + scrollbar-width: thin; + scrollbar-color: #3a4048 #151514; +} + +.markdown-panel::-webkit-scrollbar { + width: 8px; +} + +.markdown-panel::-webkit-scrollbar-track { + background: #151514; +} + +.markdown-panel::-webkit-scrollbar-thumb { + background: #3a4048; + border-radius: 4px; +} + +/* Markdown Content Styling */ +.markdown-content { + font-family: Georgia, "Times New Roman", serif; + line-height: 1.8; + color: #ccccc9; +} + +.markdown-content h1 { + font-family: Georgia, serif; + font-size: 2.4rem; + font-weight: bold; + color: #e7e6e1; + line-height: 1.2; + margin-bottom: 1rem; + border-bottom: 2px solid #0d95fd; + padding-bottom: 0.4rem; +} + +.markdown-content h2 { + font-family: Georgia, serif; + font-size: 1.5rem; + font-weight: bold; + color: #e7e6e1; + margin: 2.5rem 0 0.6rem; + padding-bottom: 0.3rem; + border-bottom: 1px solid #232626; +} + +.markdown-content h3 { + font-family: -apple-system, sans-serif; + font-size: 0.9rem; + font-weight: 600; + color: #ccccc9; + margin: 1.5rem 0 0.4rem; +} + +.markdown-content p { + font-size: 0.97rem; + color: #ccccc9; + margin: 0.6rem 0; + font-family: Georgia, serif; +} + +.markdown-content ul, +.markdown-content ol { + margin: 0.5rem 0 0.75rem 1.5rem; + font-family: Georgia, serif; +} + +.markdown-content li { + font-size: 0.95rem; + color: #ccccc9; + margin: 0.25rem 0; +} + +.markdown-content code { + font-family: "Courier New", monospace; + font-size: 0.85em; + background: rgba(243, 132, 175, 0.08); + border: 1px solid #555753; + padding: 0.1em 0.4em; + border-radius: 3px; + color: #e5185d; +} + +.markdown-content pre { + background: #0a0a0a; + border: 1px solid #555753; + border-radius: 4px; + padding: 1rem; + overflow-x: auto; + margin: 1rem 0; +} + +.markdown-content pre code { + background: none; + border: none; + padding: 0; + color: #e5185d; +} + +.markdown-content blockquote { + border-left: 3px solid #dc3545; + padding: 0.8rem 1.1rem; + background: #0c0d0d; + margin: 1.2rem 0 1.75rem; + color: #ccccc9; + font-size: 0.94rem; + font-style: italic; + border-radius: 0 4px 4px 0; +} + +.markdown-content hr { + border: none; + border-top: 2px solid #232626; + margin: 2.5rem 0; +} + +.markdown-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.5rem; + padding-bottom: 0.75rem; + border-bottom: 1px solid #232626; + font-family: "Courier New", monospace; + font-size: 0.85rem; +} + +.markdown-header .file-info { + color: #dc3545; +} + +.markdown-header .file-info i { + margin-right: 0.5rem; +} + +.markdown-header .file-badge { + background: #0a0a0a; + border: 1px solid #555753; + border-radius: 4px; + padding: 0.25rem 0.75rem; + color: #e5185d; +} + +.loading-indicator { + text-align: center; + padding: 3rem; + color: #6c757d; + font-style: italic; +} + +.error-indicator { + text-align: center; + padding: 2rem; + color: #dc3545; + border: 1px solid #dc3545; + border-radius: 4px; + background: #0a0a0a; +} + +/* Right Panel - Editor Only */ +.editor-panel { + width: 55%; + background: #0e0e0d; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.editor-header { + padding: 1rem 1.5rem; + background: #151514; + border-bottom: 1px solid #333; + font-family: "Courier New", monospace; + font-size: 0.85rem; + color: #e7e6e1; + display: flex; + justify-content: space-between; + align-items: center; +} + +.editor-header span i { + margin-right: 0.5rem; +} + +.brand { + color: #dc3545; +} + +.audio-status { + margin-right: 1rem; +} + +.audio-status i { + color: #dc3545; +} + +.play-button { + background: #dc3545; + color: #000; + border: none; + padding: 0.4rem 1rem; + border-radius: 4px; + font-weight: bold; + font-size: 0.85rem; + cursor: pointer; + transition: background 0.2s; +} + +.play-button:hover { + background: #dc3545; +} + +.play-button i { + margin-right: 0.3rem; +} + +.editor-container { + flex: 1; + overflow-y: auto; + padding: 1.5rem; + background: #151514; +} + +.editor-tabs { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.5rem; +} + +.editor-tab { + display: inline-block; + padding: 0.4rem 1rem; + background: #181818; + border: 1px solid #555753; + border-bottom: none; + border-radius: 4px 4px 0 0; + margin-right: 0.25rem; + font-size: 0.8rem; + color: #ccccc9; +} + +.editor-tab.active { + background: #1a1e22; + color: #e7e6e1; + border-bottom: 2px solid #dc3545; +} + +strudel-editor { + display: block; + margin-bottom: 1.5rem; + border-radius: 6px; + overflow: hidden; + border: 1px solid #555753; + background: #181818; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} + +kbd { + background: #0a0a0a; + border: 1px solid #555753; + color: #e5185d; + padding: 0.2rem 0.4rem; + border-radius: 3px; + font-size: 0.8rem; + font-family: "Courier New", monospace; +} +