Merge pull request #919 from tidalcycles/blog-improvements

Blog improvements
This commit is contained in:
Felix Roos 2024-01-18 23:34:10 +01:00 committed by GitHub
commit 6ef9ca18e9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 208 additions and 54 deletions

21
pnpm-lock.yaml generated
View File

@ -490,6 +490,9 @@ importers:
'@astrojs/react':
specifier: ^3.0.9
version: 3.0.9(@types/react-dom@18.2.18)(@types/react@18.2.46)(react-dom@18.2.0)(react@18.2.0)(vite@5.0.11)
'@astrojs/rss':
specifier: ^4.0.2
version: 4.0.2
'@astrojs/tailwind':
specifier: ^5.1.0
version: 5.1.0(astro@4.0.8)(tailwindcss@3.4.0)
@ -890,6 +893,13 @@ packages:
- vite
dev: false
/@astrojs/rss@4.0.2:
resolution: {integrity: sha512-Hb9GKAyvsn5EUjZtB6SniesBScMQe7SQinEHLY5EFa74QEvgcWaXTmA0Mb0P3vqDSN3d/NTYbGivprrSAawfnA==}
dependencies:
fast-xml-parser: 4.3.3
kleur: 4.1.5
dev: false
/@astrojs/tailwind@5.1.0(astro@4.0.8)(tailwindcss@3.4.0):
resolution: {integrity: sha512-BJoCDKuWhU9FT2qYg+fr6Nfb3qP4ShtyjXGHKA/4mHN94z7BGcmauQK23iy+YH5qWvTnhqkd6mQPQ1yTZTe9Ig==}
peerDependencies:
@ -7182,6 +7192,13 @@ packages:
/fast-levenshtein@2.0.6:
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
/fast-xml-parser@4.3.3:
resolution: {integrity: sha512-coV/D1MhrShMvU6D0I+VAK3umz6hUaxxhL0yp/9RjfiYUfAv14rDhGQL+PLForhMdr0wq3PiV07WtkkNjJjNHg==}
hasBin: true
dependencies:
strnum: 1.0.5
dev: false
/fastq@1.15.0:
resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==}
dependencies:
@ -12637,6 +12654,10 @@ packages:
acorn: 8.11.3
dev: true
/strnum@1.0.5:
resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==}
dev: false
/strong-log-transformer@2.1.0:
resolution: {integrity: sha512-B3Hgul+z0L9a236FAUC9iZsL+nVHgoCJnqCbN588DjYxvGXaXaaFbfmQ/JhvKjZwsOukuR72XbHv71Qkug0HxA==}
engines: {node: '>=4'}

View File

@ -16,6 +16,7 @@
"@astro-community/astro-embed-youtube": "^0.4.3",
"@astrojs/mdx": "^2.0.3",
"@astrojs/react": "^3.0.9",
"@astrojs/rss": "^4.0.2",
"@astrojs/tailwind": "^5.1.0",
"@docsearch/css": "^3.5.2",
"@docsearch/react": "^3.5.2",
@ -23,21 +24,21 @@
"@heroicons/react": "^2.1.1",
"@nanostores/persistent": "^0.9.1",
"@nanostores/react": "^0.7.1",
"@strudel/codemirror": "workspace:*",
"@strudel/core": "workspace:*",
"@strudel/csound": "workspace:*",
"@strudel/desktopbridge": "workspace:*",
"@strudel/hydra": "workspace:*",
"@strudel/midi": "workspace:*",
"@strudel/mini": "workspace:*",
"@strudel/osc": "workspace:*",
"@strudel/repl": "workspace:*",
"@strudel/serial": "workspace:*",
"@strudel/soundfonts": "workspace:*",
"@strudel/tonal": "workspace:*",
"@strudel/transpiler": "workspace:*",
"@strudel/webaudio": "workspace:*",
"@strudel/xen": "workspace:*",
"@strudel/codemirror": "workspace:*",
"@strudel/desktopbridge": "workspace:*",
"@strudel/hydra": "workspace:*",
"@strudel/repl": "workspace:*",
"@supabase/supabase-js": "^2.39.1",
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.10",

View File

@ -1,7 +1,7 @@
---
import type { CollectionEntry } from 'astro:content';
type Props = CollectionEntry<'blog'>['data'];
type Props = { post: CollectionEntry<'blog'> };
const { post } = Astro.props;
const { Content } = await post.render();
@ -9,7 +9,7 @@ import { format } from 'date-fns';
---
<article
class="prose max-w-none prose-headings:font-sans prose-headings:font-black prose-headings:text-slate-900 dark:prose-headings:text-gray-200 dark:text-gray-400 dark:prose-strong:text-gray-400 dark:prose-code:text-slate-400 dark:prose-a:text-gray-300 prose-a:text-slate-900 prose-blockquote:text-slate-800 dark:prose-blockquote:text-slate-400"
class="prose max-w-none prose-headings:font-sans prose-headings:font-black prose-headings:text-slate-900 dark:prose-headings:text-gray-200 dark:text-gray-400 dark:prose-strong:text-gray-400 dark:prose-code:text-slate-400 dark:prose-a:text-gray-300 prose-a:text-slate-900 prose-blockquote:text-slate-800 dark:prose-blockquote:text-slate-400 border-b-4 border-lineHighlight pt-4"
>
<div class="pb-2">
<div class="md:flex justify-between">

View File

@ -10,7 +10,7 @@ const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<link rel="alternate" type="application/rss+xml" title={`RSS Feed for strudel.cc`} href="/rss.xml" />
<link rel="icon" type="image/svg+xml" href={`${baseNoTrailing}/favicon.ico`} />
<meta
@ -24,7 +24,7 @@ const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL
<base href={BASE_URL} />
<!-- Scrollable a11y code helper -->
<script src{`${baseNoTrailing}/make-scrollable-code-focusable.js`} is:inline></script>
<script {`${baseNoTrailing}/make-scrollable-code-focusable.js`} is:inline></script>
<script src="/src/pwa.ts"></script>
<!-- this does not work for some reason: -->

View File

@ -1,5 +1,5 @@
---
import { SITE, OPEN_GRAPH, Frontmatter } from '../config';
import { SITE, OPEN_GRAPH, type Frontmatter } from '../config';
export interface Props {
frontmatter: Frontmatter;
@ -25,11 +25,3 @@ const imageAlt = frontmatter.image?.alt ?? OPEN_GRAPH.image.alt;
<meta property="og:image:alt" content={imageAlt} />
<meta name="description" property="og:description" content={frontmatter.description ?? SITE.description} />
<meta property="og:site_name" content={SITE.title} />
<!-- Twitter Tags -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content={OPEN_GRAPH.twitter} />
<meta name="twitter:title" content={formattedContentTitle} />
<meta name="twitter:description" content={frontmatter.description ?? SITE.description} />
<meta name="twitter:image" content={canonicalImageSrc} />
<meta name="twitter:image:alt" content={imageAlt} />

View File

@ -1,4 +1,19 @@
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 (
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
{
@ -31,6 +46,13 @@ export function Icon({ type }) {
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]
}
</svg>

View File

@ -1,8 +1,8 @@
import { useState, useRef, useCallback, useMemo, useEffect } from 'react';
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 { getAudioContext, webaudioOutput } from '@strudel/webaudio';
import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel/webaudio';
import { StrudelMirror } from '@strudel/codemirror';
// import { prebake } from '@strudel/repl';
import { prebake } from '../repl/prebake.mjs';
@ -10,14 +10,16 @@ import { loadModules } from '../repl/util.mjs';
import Claviature from '@components/Claviature';
import useClient from '@src/useClient.mjs';
let prebaked, modulesLoading;
let prebaked, modulesLoading, audioLoading;
if (typeof window !== 'undefined') {
prebaked = prebake();
modulesLoading = loadModules();
audioLoading = initAudioOnFirstClick();
}
export function MiniRepl({
tune: code,
tune,
tunes,
hideHeader = false,
canvasHeight = 100,
onTrigger,
@ -26,6 +28,7 @@ export function MiniRepl({
claviature,
claviatureLabels,
}) {
const code = tunes ? tunes[0] : tune;
const id = useMemo(() => s4(), []);
const canvasId = useMemo(() => `canvas-${id}`, [id]);
const shouldDraw = !!punchcard || !!claviature;
@ -75,7 +78,7 @@ export function MiniRepl({
}
return pat;
},
prebake: async () => Promise.all([modulesLoading, prebaked]),
prebake: async () => Promise.all([modulesLoading, prebaked, audioLoading]),
onUpdateState: (state) => {
setReplState({ ...state });
},
@ -91,6 +94,14 @@ export function MiniRepl({
const containerRef = useRef();
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) {
return <pre>{code}</pre>;
}
@ -119,6 +130,28 @@ export function MiniRepl({
<Icon type="refresh" />
</button>
</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 className="overflow-auto relative p-1">

81
website/src/examples.mjs Normal file
View 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')
)`,
];

View File

@ -32,7 +32,18 @@ const posts = (await getCollection('blog')).sort((a, b) => compareDesc(a.data.da
<LeftSidebar currentPage={currentPage} />
</aside>
<PageContent>
{posts.map((post) => <BlogPost post={post} />)}
<div class="border-b-4 border-lineHighlight py-4">
<h1>Strudel Blog</h1>
<p>
Welcome to the Strudel Blog, where we will keep you updated with the latest changes and things happening
in the strudelsphere. You can subscribe to this blog using <a target="_blank" href="/rss.xml"
>this rss link</a
>
</p>
</div>
<div class="space-y-8">
{posts.map((post) => <BlogPost post={post} />)}
</div>
</PageContent>
<aside class="fixed right-0 h-full overflow-auto pr-4 pl-0 pb-16 hidden xl:block" title="Table of Contents">
<RightSidebar

View File

@ -0,0 +1,19 @@
import rss from '@astrojs/rss';
import { getCollection } from 'astro:content';
export async function GET(context) {
const posts = (await getCollection('blog')).filter((p) => !p.data.draft);
const options = {
title: 'Strudel Blog',
description:
'The Strudel Blog will keep you updated with the latest changes and things happening in the strudelsphere.',
site: context.site,
items: posts.map((post) => ({
link: `/${post.slug}/`,
title: post.data.title,
pubDate: post.data.date,
description: post.data.description,
})),
};
return rss(options);
}

View File

@ -4,6 +4,7 @@ layout: ../../layouts/MainLayout.astro
---
import { MiniRepl } from '../../docs/MiniRepl';
import { examples } from '../../examples.mjs';
# 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.
- 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
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)`}
/>
<MiniRepl client:idle tunes={examples} />
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