mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-31 13:21:52 -04:00
more content + style
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
function ImgTile({ src, width, height }) {
|
||||
return (
|
||||
<div style={{ background: `url(${src})`, backgroundSize: 'cover', backgroundPosition: 'center', width, height }} />
|
||||
);
|
||||
}
|
||||
export default ImgTile;
|
||||
+10
-8
@@ -32,7 +32,7 @@ loadWebDirt({
|
||||
sampleFolder: './EmuSP12',
|
||||
});
|
||||
|
||||
export function MaxiRepl({ code, canvasHeight = 400 }) {
|
||||
export function MaxiRepl({ code, canvasHeight = 500 }) {
|
||||
const [exampleIndex, setExampleIndex] = useState(0);
|
||||
const examples = Array.isArray(code) ? code : [code];
|
||||
return (
|
||||
@@ -48,13 +48,15 @@ export function MaxiRepl({ code, canvasHeight = 400 }) {
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<_MiniRepl
|
||||
key={exampleIndex}
|
||||
tune={examples[exampleIndex]}
|
||||
defaultSynth={defaultSynth}
|
||||
hideOutsideView={true}
|
||||
theme={materialPalenightLarge}
|
||||
/>
|
||||
<div className="max-w-screen">
|
||||
<_MiniRepl
|
||||
key={exampleIndex}
|
||||
tune={examples[exampleIndex]}
|
||||
defaultSynth={defaultSynth}
|
||||
hideOutsideView={true}
|
||||
theme={materialPalenightLarge}
|
||||
/>
|
||||
</div>
|
||||
<canvas
|
||||
id="test-canvas"
|
||||
className="w-full pointer-events-none"
|
||||
|
||||
+43
-11
@@ -1,8 +1,31 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useLayoutEffect, useEffect, useCallback } from 'react';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
|
||||
const getUrlIndex = () => parseInt(window.location.hash.split('#')[1] || 0, 10);
|
||||
|
||||
export function Slides({ children }) {
|
||||
const [slide, setSlide] = useState(0);
|
||||
const [slide, setSlide] = useState(getUrlIndex() % children.length);
|
||||
useEffect(() => {
|
||||
window.location.hash = '#' + slide;
|
||||
}, [slide]);
|
||||
|
||||
const next = useCallback(() => setSlide((s) => (s + 1) % children.length), [children]);
|
||||
const prev = useCallback(() => setSlide((s) => (s + children.length - 1) % children.length), [children]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const handleKeyPress = async (e) => {
|
||||
if (e.ctrlKey && e.altKey) {
|
||||
if (e.code === 'ArrowRight') {
|
||||
next();
|
||||
} else if (e.code === 'ArrowLeft') {
|
||||
prev();
|
||||
}
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
window.addEventListener('keydown', handleKeyPress, true);
|
||||
return () => window.removeEventListener('keydown', handleKeyPress, true);
|
||||
}, [slide]);
|
||||
return (
|
||||
<div className="bg-slate-900">
|
||||
<AnimatePresence exitBeforeEnter>
|
||||
@@ -13,14 +36,26 @@ export function Slides({ children }) {
|
||||
exit={{ opacity: 0 }}
|
||||
className="w-screen h-screen"
|
||||
>
|
||||
<div className="flex justify-center items-center p-6">
|
||||
<div className="px-8 prose grow max-w-[1280px] text-gray-200 font-serif prose-headings:text-gray-200 prose-blockquote:text-gray-200 prose-headings:font-sans prose-2xl prose-slate">
|
||||
<div className="flex justify-center items-center px-6">
|
||||
<div
|
||||
className={`px-8 prose grow max-w-[1280px]
|
||||
text-gray-200 font-serif
|
||||
prose-headings:text-gray-200
|
||||
prose-a:text-indigo-300
|
||||
prose-blockquote:text-gray-200
|
||||
prose-em:text-[1.3em]
|
||||
prose-li:text-[1.4em]
|
||||
prose-headings:font-sans
|
||||
prose-headings:mt-12
|
||||
prose-2xl
|
||||
prose-slate`}
|
||||
>
|
||||
<center>{children[slide]}</center>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
<SlideNav slide={slide} setSlide={setSlide} slides={children} />
|
||||
<SlideNav onNext={next} onPrev={prev} slides={children} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -29,13 +64,10 @@ export function Slide({ children }) {
|
||||
return children;
|
||||
}
|
||||
|
||||
function SlideNav({ slide, setSlide, slides }) {
|
||||
function SlideNav({ onNext, onPrev }) {
|
||||
return (
|
||||
<div className="flex absolute top-1/2 space-x-2 w-full justify-between items-center z-1">
|
||||
<button
|
||||
onClick={() => setSlide((s) => (s + slides.length - 1) % slides.length)}
|
||||
className="absolute left-0 my-auto"
|
||||
>
|
||||
<button onClick={() => onPrev()} className="absolute left-0 my-auto">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-12 w-12 text-gray-200"
|
||||
@@ -47,7 +79,7 @@ function SlideNav({ slide, setSlide, slides }) {
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
<button onClick={() => setSlide((s) => (s + 1) % slides.length)} className="absolute right-0 my-auto">
|
||||
<button onClick={() => onNext()} className="absolute right-0 my-auto">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-12 w-12 text-gray-200"
|
||||
|
||||
+58
-13
@@ -1,23 +1,25 @@
|
||||
import { Slides, Slide } from './Slides.jsx';
|
||||
import { MaxiRepl } from './MaxiRepl';
|
||||
import ImgTile from './ImgTile';
|
||||
|
||||
<Slides>
|
||||
|
||||
<Slide>
|
||||
|
||||
## Strudel: Algorithmic Patterns for the Web
|
||||
<h1 className="mb-0">Strudel</h1>
|
||||
|
||||
<img src="./ada.jpg" className="h-[500px] rounded-md" />
|
||||
<h2 className="mt-0">Algorithmic Patterns for the Web</h2>
|
||||
|
||||
<div className="max-w-2xl">
|
||||
<i>
|
||||
<div className="flex space-x-2 items-center">
|
||||
<em>
|
||||
"The Analytical Engine weaves algebraic patterns, just as the Jacquard loom weaves flowers and leaves."
|
||||
<br />
|
||||
“Supposing, for instance, that the fundamental relations of pitched sounds in the science of harmony and of musical
|
||||
composition were susceptible of such expression and adaptations, the [Analytical] Engine might compose elaborate and
|
||||
scientific pieces of music of any degree of complexity or extent." - Ada Lovelace
|
||||
</i>
|
||||
</em>
|
||||
</div>
|
||||
<img src="./ada.jpg" className="h-[400px] rounded-md" />
|
||||
|
||||
</Slide>
|
||||
<Slide>
|
||||
@@ -39,13 +41,13 @@ import { MaxiRepl } from './MaxiRepl';
|
||||
|
||||
<div className="text-left">
|
||||
|
||||
- A very popular software for live coding music (and more)
|
||||
- A very popular software for live coding music
|
||||
- a powerful pattern language for time-based art
|
||||
- find out more at <a href="https://tidalcycles.org" target="_blank" className="text-indigo-400">tidalcycles.org</a>
|
||||
|
||||
</div>
|
||||
|
||||
<img src="./tidal-screenshot2.png" className="h-[600px] rounded-md" />
|
||||
<img src="./tidal-screenshot2.png" className="h-[500px] rounded-md" />
|
||||
<p>Screenshot: "Yaxu & hellocatfood - LIVE code for Noise Quest"</p>
|
||||
|
||||
</Slide>
|
||||
@@ -56,14 +58,19 @@ import { MaxiRepl } from './MaxiRepl';
|
||||
<div className="text-left">
|
||||
|
||||
- a port of Tidal Cycles to JavaScript
|
||||
- a browser based editor for zero-install live coding
|
||||
- many ways to make sound: Tone.js, Webdirt, OSC Superdirt, MIDI, speech
|
||||
- visuals: highlighting of active events + pianoroll visualization
|
||||
- find out more at <a href="https://strudel.tidalcycles.org/" target="_blank" className="text-indigo-400">strudel.tidalcycles.org</a>
|
||||
- a browser based editor for zero-install live coding: <a href="https://strudel.tidalcycles.org/" target="_blank" className="text-indigo-400">strudel.tidalcycles.org</a>
|
||||
- many ways to make sound: Tone.js, Webdirt, OSC, MIDI ...
|
||||
|
||||
<div className="hidden">
|
||||
|
||||
- "Strudel" = internationally known as delicious pastry, but also "swirl" / "whirlpool" in german
|
||||
- (a tidal can cause a whirlpool!)
|
||||
|
||||
</div>
|
||||
|
||||
<img src="./strudel-screenshot.png" className="h-[600px] rounded-md mb-0" />
|
||||
</div>
|
||||
|
||||
<img src="./strudel-screenshot.png" className="h-[500px] rounded-md mb-0" />
|
||||
<p>Screenshot: "Algorave 10th Birthday March 2022 - froos"</p>
|
||||
|
||||
</Slide>
|
||||
@@ -111,7 +118,45 @@ stack(
|
||||
</Slide>
|
||||
<Slide>
|
||||
|
||||
## Contributors
|
||||
## About Me
|
||||
|
||||
<div className="text-left">
|
||||
|
||||
- 💻 Studied Computer Science and Media in Stuttgart, working as a software developer by day
|
||||
- 🎺 Trumpet Player, studied Jazz and Popular Music in Mannheim
|
||||
- 🎤 Dilettante Music Producer of all kinds of genres
|
||||
- 💿 ocassionally publishes music with Lui Mafuta & Puste
|
||||
- 🔌 Modular Synth & Electronics Lover, Dilettante DJ ..
|
||||
- 💌 Coding Blog: [Loophole Letters](https://loophole-letters.vercel.app/)
|
||||
- ✨ Started live coding music not too long ago
|
||||
|
||||
<div className="grid grid-cols-5 gap-4 mt-12">
|
||||
<ImgTile height={200} src="https://loophole-letters.vercel.app/_next/image?url=%2Fimg%2Flambdoma.png&w=2048&q=75" />
|
||||
{/* <ImgTile
|
||||
height={300}
|
||||
src="https://loophole-letters.vercel.app/_next/image?url=%2Fimg%2Fharmonic-spiral.png&w=2048&q=75"
|
||||
/> */}
|
||||
<ImgTile height={200} src="https://loophole-letters.vercel.app/_next/image?url=%2Fimg%2Fpiano-roll.png&w=2048&q=75" />
|
||||
<ImgTile height={200} src="https://loophole-letters.vercel.app/_next/image?url=%2Fimg%2Fcof.png&w=2048&q=75" />
|
||||
<ImgTile height={200} src="https://loophole-letters.vercel.app/_next/image?url=%2Fimg%2F251graph.png&w=2048&q=75" />
|
||||
<ImgTile
|
||||
height={200}
|
||||
src="https://loophole-letters.vercel.app/_next/image?url=%2Fimg%2Fdiy-modular-synth%2Fmidi2cv-styleshot.png&w=2048&q=75"
|
||||
/>
|
||||
{/* <ImgTile height={200} src="https://loophole-letters.vercel.app/_next/image?url=%2Fimg%2Fcolorpiano.png&w=2048&q=75" /> */}
|
||||
{/*
|
||||
<ImgTile
|
||||
height={300}
|
||||
src="https://loophole-letters.vercel.app/_next/image?url=%2Fimg%2Fvoicing-permutation.png&w=2048&q=75"
|
||||
/> */}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</Slide>
|
||||
<Slide>
|
||||
|
||||
## How it started
|
||||
|
||||
</Slide>
|
||||
<Slide>
|
||||
|
||||
Reference in New Issue
Block a user