Merge branch 'main' into patterns-tab
This commit is contained in:
commit
e72c9cbebf
49 changed files with 712 additions and 255 deletions
|
|
@ -3,7 +3,7 @@ import { pwaInfo } from 'virtual:pwa-info';
|
|||
import '../styles/index.css';
|
||||
|
||||
const { BASE_URL } = import.meta.env;
|
||||
const base = BASE_URL;
|
||||
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
|
||||
---
|
||||
|
||||
<!-- Global Metadata -->
|
||||
|
|
@ -11,20 +11,20 @@ const base = BASE_URL;
|
|||
<meta name="viewport" content="width=device-width" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
|
||||
<link rel="icon" type="image/svg+xml" href="favicon.ico" />
|
||||
<link rel="icon" type="image/svg+xml" href={`${baseNoTrailing}/favicon.ico`} />
|
||||
|
||||
<meta
|
||||
name="description"
|
||||
content="Strudel is a music live coding environment for the browser, porting the TidalCycles pattern language to JavaScript."
|
||||
/>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="apple-touch-icon" href="/icons/apple-icon-180.png" sizes="180x180" />
|
||||
<link rel="icon" href={`${baseNoTrailing}/favicon.ico`} />
|
||||
<link rel="apple-touch-icon" href={`${baseNoTrailing}/icons/apple-icon-180.png`} sizes="180x180" />
|
||||
<meta name="theme-color" content="#222222" />
|
||||
|
||||
<base href={base} />
|
||||
<base href={BASE_URL} />
|
||||
|
||||
<!-- Scrollable a11y code helper -->
|
||||
<script src="./make-scrollable-code-focusable.js" is:inline></script>
|
||||
<script src{`${baseNoTrailing}/make-scrollable-code-focusable.js`} is:inline></script>
|
||||
|
||||
<script src="/src/pwa.ts"></script>
|
||||
<!-- this does not work for some reason: -->
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ const { currentPage } = Astro.props as Props;
|
|||
// const lang = getLanguageFromURL(currentPage);
|
||||
const langCode = 'en'; // getLanguageFromURL(currentPage);
|
||||
const sidebar = SIDEBAR[langCode];
|
||||
|
||||
const { BASE_URL } = import.meta.env;
|
||||
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
|
||||
---
|
||||
|
||||
<nav
|
||||
|
|
@ -23,7 +26,7 @@ const sidebar = SIDEBAR[langCode];
|
|||
title="Top Navigation"
|
||||
>
|
||||
<div class="flex overflow-visible items-center grow" style="overflow:visible">
|
||||
<a href="/" class="flex items-center text-2xl space-x-2">
|
||||
<a href={`${baseNoTrailing}/`} class="flex items-center text-2xl space-x-2">
|
||||
<h1 class="font-bold flex space-x-2 items-baseline text-xl">
|
||||
<span>🌀</span>
|
||||
<div class="flex space-x-1 items-baseline">
|
||||
|
|
@ -35,9 +38,9 @@ const sidebar = SIDEBAR[langCode];
|
|||
</div>
|
||||
{/* KNOWN_LANGUAGE_CODES.length > 1 && <LanguageSelect lang={lang} client:idle /> */}
|
||||
<div class="search-item h-10">
|
||||
<!-- <Search client:idle /> -->
|
||||
<Search client:idle />
|
||||
</div>
|
||||
<a href="./" class="hidden md:flex cursor-pointer items-center space-x-1"
|
||||
<a href={`${baseNoTrailing}/`} class="hidden md:flex cursor-pointer items-center space-x-1"
|
||||
><CommandLineIcon className="w-5 h-5" /><span>go to REPL</span>
|
||||
</a>
|
||||
<div class="md:hidden">
|
||||
|
|
@ -48,8 +51,8 @@ const sidebar = SIDEBAR[langCode];
|
|||
<style>
|
||||
/** Style Algolia */
|
||||
:root {
|
||||
--docsearch-primary-color: var(--theme-accent);
|
||||
--docsearch-logo-color: var(--theme-text);
|
||||
--docsearch-primary-color: var(--lineHighlight);
|
||||
--docsearch-logo-color: var(--foreground);
|
||||
}
|
||||
|
||||
.search-item {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,28 @@
|
|||
/** Style Algolia */
|
||||
:root {
|
||||
--docsearch-primary-color: var(--theme-accent);
|
||||
--docsearch-logo-color: var(--theme-text);
|
||||
--docsearch-primary-color: var(--lineHighlight);
|
||||
--docsearch-logo-color: var(--foreground);
|
||||
--docsearch-container-background: rgba(110, 110, 110, 0.8);
|
||||
--docsearch-modal-shadow: none;
|
||||
--docsearch-text-color: var(--foreground);
|
||||
--docsearch-highlight-color: var(--foreground);
|
||||
--docsearch-searchbox-background: var(--lineBackground);
|
||||
--docsearch-searchbox-focus-background: var(--lineBackground);
|
||||
--docsearch-searchbox-shadow: inset 0 0 0 2px var(--lineHighlight);
|
||||
--docsearch-hit-background: var(--background);
|
||||
--docsearch-hit-active-color: var(--background);
|
||||
--docsearch-hit-color: var(--foreground);
|
||||
--docsearch-hit-shadow: 0 1px 3px 0 var(--foreground);
|
||||
--docsearch-footer-shadow: none;
|
||||
--docsearch-footer-background: var(--gutterBackground);
|
||||
--docsearch-modal-background: var(--background);
|
||||
--docsearch-muted-color: color-mix(in srgb, var(--foreground), #fff 30%);
|
||||
--docsearch-key-gradient: var(--foreground);
|
||||
--docsearch-key-shadow: inset 0 -2px 0 0 var(--gutterForeground), inset 0 0 1px 1px var(--foreground),
|
||||
0 1px 2px 1px var(--gutterBackground);
|
||||
}
|
||||
.dark {
|
||||
--docsearch-muted-color: color-mix(in srgb, var(--foreground), #000 30%);
|
||||
}
|
||||
.search-input {
|
||||
flex-grow: 1;
|
||||
|
|
@ -14,9 +35,9 @@
|
|||
font-size: 1rem;
|
||||
font-family: inherit;
|
||||
line-height: inherit;
|
||||
background-color: var(--theme-divider);
|
||||
border-color: var(--theme-divider);
|
||||
color: var(--theme-text-light);
|
||||
background-color: var(--lineBackground);
|
||||
border-color: var(--lineBackground);
|
||||
color: var(--foreground);
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-radius: 0.25rem;
|
||||
|
|
@ -29,15 +50,15 @@
|
|||
}
|
||||
.search-input:hover,
|
||||
.search-input:focus {
|
||||
color: var(--theme-text);
|
||||
border-color: var(--theme-text-light);
|
||||
color: var(--foreground);
|
||||
border-color: var(--foreground);
|
||||
}
|
||||
.search-input:hover::placeholder,
|
||||
.search-input:focus::placeholder {
|
||||
color: var(--theme-text-light);
|
||||
color: var(--foreground);
|
||||
}
|
||||
.search-input::placeholder {
|
||||
color: var(--theme-text-light);
|
||||
color: var(--foreground);
|
||||
}
|
||||
.search-hint {
|
||||
position: absolute;
|
||||
|
|
@ -48,11 +69,13 @@
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
letter-spacing: 0.125em;
|
||||
font-size: 13px;
|
||||
font-size: 12px;
|
||||
font-family: var(--font-mono);
|
||||
pointer-events: none;
|
||||
border-color: var(--theme-text-lighter);
|
||||
color: var(--theme-text-light);
|
||||
border-color: transparent;
|
||||
color: var(--background);
|
||||
background-color: var(--foreground);
|
||||
box-shadow: var(--docsearch-key-shadow);
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-radius: 0.25rem;
|
||||
|
|
@ -64,12 +87,40 @@
|
|||
display: flex;
|
||||
}
|
||||
}
|
||||
.search-button {
|
||||
background-color: var(--lineBackground) !important;
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ *\
|
||||
DocSearch (Algolia)
|
||||
\* ------------------------------------------------------------ */
|
||||
|
||||
.DocSearch-Form {
|
||||
padding: 3px var(--docsearch-spacing);
|
||||
}
|
||||
.DocSearch-Modal .DocSearch-Hit a {
|
||||
box-shadow: none;
|
||||
border: 1px solid var(--theme-accent);
|
||||
border: none;
|
||||
}
|
||||
#docsearch-input.DocSearch-Input {
|
||||
background-color: var(--lineBackground);
|
||||
border-top: 3px solid var(--docsearch-searchbox-shadow);
|
||||
border-bottom: 3px solid var(--docsearch-searchbox-shadow);
|
||||
}
|
||||
.DocSearch-Commands-Key {
|
||||
color: var(--background);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.DocSearch-SearchBar {
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
#docsearch-input.DocSearch-Input:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
.DocSearch-Footer {
|
||||
color: var(--docsearch-muted-color) !important;
|
||||
}
|
||||
.DocSearch-Logo svg .cls-1,
|
||||
.DocSearch-Logo svg .cls-2 {
|
||||
fill: var(--docsearch-logo-color);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import './Search.css';
|
|||
|
||||
import { createPortal } from 'react-dom';
|
||||
import * as docSearchReact from '@docsearch/react';
|
||||
const { BASE_URL } = import.meta.env;
|
||||
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
|
||||
|
||||
/** FIXME: This is still kinda nasty, but DocSearch is not ESM ready. */
|
||||
const DocSearchModal = docSearchReact.DocSearchModal || (docSearchReact as any).default.DocSearchModal;
|
||||
|
|
@ -43,7 +45,12 @@ export default function Search() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<button type="button" ref={searchButtonRef} onClick={onOpen} className="rounded-md bg-slate-900 w-full px-2">
|
||||
<button
|
||||
type="button"
|
||||
ref={searchButtonRef}
|
||||
onClick={onOpen}
|
||||
className="rounded-md bg-slate-900 w-full px-2 search-button"
|
||||
>
|
||||
<svg width="24" height="24" fill="none">
|
||||
<path
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
||||
|
|
@ -74,6 +81,9 @@ export default function Search() {
|
|||
indexName={ALGOLIA.indexName}
|
||||
appId={ALGOLIA.appId}
|
||||
apiKey={ALGOLIA.apiKey}
|
||||
getMissingResultsUrl={({ query }) => {
|
||||
return `https://github.com/tidalcycles/strudel/issues/new?title=Missing doc for ${query}`;
|
||||
}}
|
||||
transformItems={(items) => {
|
||||
return items.map((item) => {
|
||||
// We transform the absolute URL into a relative URL to
|
||||
|
|
@ -81,9 +91,13 @@ export default function Search() {
|
|||
const a = document.createElement('a');
|
||||
a.href = item.url;
|
||||
const hash = a.hash === '#overview' ? '' : a.hash;
|
||||
let pathname = a.pathname;
|
||||
pathname = pathname.startsWith('/') ? pathname.slice(1) : pathname;
|
||||
pathname = pathname.endsWith('/') ? pathname.slice(0, -1) : pathname;
|
||||
const url = `${baseNoTrailing}/${pathname}/${hash}`;
|
||||
return {
|
||||
...item,
|
||||
url: `${a.pathname}${hash}`,
|
||||
url,
|
||||
};
|
||||
});
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ type Props = {
|
|||
const { currentPage } = Astro.props as Props;
|
||||
const { BASE_URL } = import.meta.env;
|
||||
let currentPageMatch = currentPage.slice(BASE_URL.length, currentPage.endsWith('/') ? -1 : undefined);
|
||||
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
|
||||
|
||||
const langCode = getLanguageFromURL(currentPage) || 'en';
|
||||
const sidebar = SIDEBAR[langCode];
|
||||
|
|
@ -23,7 +24,7 @@ const sidebar = SIDEBAR[langCode];
|
|||
<h2>{header}</h2>
|
||||
<ul>
|
||||
{children.map((child) => {
|
||||
const url = Astro.site?.pathname + child.link;
|
||||
const url = `${baseNoTrailing}/${child.link}${child.link.endsWith('/') ? '' : '/'}`;
|
||||
return (
|
||||
<li class="">
|
||||
<a
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ const TableOfContents: FC<{ headings: MarkdownHeading[]; currentPage: string }>
|
|||
.map((heading, i) => (
|
||||
<li className={`w-full`} key={i}>
|
||||
<a
|
||||
href={`${currentPage}#${heading.slug}`}
|
||||
href={`${currentPage}/#${heading.slug}`}
|
||||
onClick={onLinkClick}
|
||||
className={`py-0.5 block cursor-pointer w-full border-l-4 border-lineHighlight hover:bg-lineHighlight ${
|
||||
['pl-4', 'pl-9', 'pl-12'][heading.depth - minDepth]
|
||||
|
|
|
|||
|
|
@ -2,19 +2,15 @@ import jsdoc from '../../../doc.json'; // doc.json is built with `npm run jsdoc-
|
|||
const docs = jsdoc.docs.reduce((acc, obj) => Object.assign(acc, { [obj.longname]: obj }), {});
|
||||
import { MiniRepl } from './MiniRepl';
|
||||
|
||||
const getTag = (title, item) => item.tags?.find((t) => t.title === title)?.text;
|
||||
|
||||
export function JsDoc({ name, h = 3, hideDescription, punchcard, canvasHeight }) {
|
||||
const item = docs[name];
|
||||
if (!item) {
|
||||
console.warn('Not found: ' + name);
|
||||
return <div />;
|
||||
}
|
||||
const synonyms = getTag('synonyms', item)?.split(', ') || [];
|
||||
const CustomHeading = `h${h}`;
|
||||
const description =
|
||||
item.description?.replaceAll(/\{@link ([a-zA-Z\.]+)?#?([a-zA-Z]*)\}/g, (_, a, b) => {
|
||||
// console.log(_, 'a', a, 'b', b);
|
||||
return `<a href="#${a.replaceAll('.', '').toLowerCase()}${b ? `-${b}` : ''}">${a}${b ? `#${b}` : ''}</a>`;
|
||||
}) || '';
|
||||
return (
|
||||
|
|
@ -22,9 +18,9 @@ export function JsDoc({ name, h = 3, hideDescription, punchcard, canvasHeight })
|
|||
{!!h && <CustomHeading>{item.longname}</CustomHeading>}
|
||||
{!hideDescription && (
|
||||
<>
|
||||
{!!synonyms.length && (
|
||||
{!!item.synonyms_text && (
|
||||
<span>
|
||||
Synonyms: <code>{synonyms.join(', ')}</code>
|
||||
Synonyms: <code>{item.synonyms_text}</code>
|
||||
</span>
|
||||
)}
|
||||
<div dangerouslySetInnerHTML={{ __html: description }} />
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { MiniRepl } from '../../../docs/MiniRepl';
|
|||
|
||||
# Willkommen
|
||||
|
||||
<img src="/icons/strudel_icon.png" className="w-32 animate-pulse md:float-right ml-8" />
|
||||
<div className="w-32 animate-pulse md:float-right ml-8"></div>
|
||||
|
||||
Willkommen zum Strudel Workshop!
|
||||
Du hast den richtigen Ort gefunden wenn du lernen möchtest wie man mit Code Musik macht.
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ import * as tunes from '../../../src/repl/tunes.mjs';
|
|||
import HeadCommon from '../../components/HeadCommon.astro';
|
||||
|
||||
import { getMetadata } from '../metadata_parser';
|
||||
|
||||
const { BASE_URL } = import.meta.env;
|
||||
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
|
||||
---
|
||||
|
||||
<head>
|
||||
|
|
@ -12,11 +15,11 @@ import { getMetadata } from '../metadata_parser';
|
|||
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-2 p-2 select-none">
|
||||
{
|
||||
Object.entries(tunes).map(([name, tune]) => (
|
||||
<a class="rounded-md bg-slate-900 hover:bg-slate-700 cursor-pointer relative" href={`./#${btoa(tune)}`}>
|
||||
<a class="rounded-md bg-slate-900 hover:bg-slate-700 cursor-pointer relative" href={`${baseNoTrailing}/#${btoa(tune)}`}>
|
||||
<div class="absolute w-full h-full flex justify-center items-center">
|
||||
<span class="bg-slate-800 p-2 rounded-md text-white">{getMetadata(tune)['title'] || name}</span>
|
||||
</div>
|
||||
<img src={`./img/example-${name}.png`} />
|
||||
<img src={`${baseNoTrailing}/img/example-${name}.png`} />
|
||||
</a>
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { evaluate } from '@strudel.cycles/transpiler';
|
|||
import '../../../../test/runtime.mjs';
|
||||
import * as tunes from '../../repl/tunes.mjs';
|
||||
|
||||
export async function get({ params, request }) {
|
||||
export async function GET({ params, request }) {
|
||||
const { name } = params;
|
||||
const tune = tunes[name];
|
||||
const { pattern } = await evaluate(tune);
|
||||
|
|
@ -13,10 +13,7 @@ export async function get({ params, request }) {
|
|||
const ctx = canvas.getContext('2d');
|
||||
pianoroll({ time: 4, haps, ctx, playhead: 1, fold: 1, background: 'transparent', playheadColor: 'transparent' });
|
||||
const buffer = canvas.toBuffer('image/png');
|
||||
return {
|
||||
body: buffer,
|
||||
encoding: 'binary',
|
||||
};
|
||||
return new Response(buffer);
|
||||
}
|
||||
export function getStaticPaths() {
|
||||
return Object.keys(tunes).map((name) => ({
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ In both cases, p4 is derived from the value of `freq` or `note`.
|
|||
## Limitations / Future Plans
|
||||
|
||||
Apart from the above listed p values, no other parameter can be patterned so far.
|
||||
This also means that [audio effects](./learn/effects) will not work.
|
||||
This also means that [audio effects](/learn/effects/) will not work.
|
||||
In the future, the integration could be improved by passing all patterned control parameters to the csound instrument.
|
||||
This could work by a unique [channel](https://kunstmusik.github.io/icsc2022-csound-web/tutorial2-interacting-with-csound/#step-4---writing-continuous-data-channels)
|
||||
for each value. Channels could be read [like this](https://github.com/csound/csound/blob/master/Android/CsoundForAndroid/CsoundAndroidExamples/src/main/res/raw/multitouch_xy.csd).
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ This interactive tutorial will guide you through the basics of Strudel.
|
|||
To see and hear what Strudel can do, visit the [Strudel REPL](https://strudel.cc/) and click the Shuffle icon in the top menu bar.
|
||||
You can get a feel for Strudel by browsing and editing these examples and clicking the Refresh icon to update.
|
||||
|
||||
You can also browse through the examples [here](./examples).
|
||||
You can also browse through the examples [here](/examples).
|
||||
|
||||
Alternatively, you can get a taste of what Strudel can do by clicking play on this track:
|
||||
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ note("[a,c,e,<a4 ab4 g4 gb4>,b4]/4").s("sawtooth").vib(2)
|
|||
`}
|
||||
/>
|
||||
|
||||
## H patterns
|
||||
|
||||
There is a special function `H` that allows you to use a pattern as an input to hydra:
|
||||
|
||||
<MiniRepl
|
||||
|
|
@ -48,8 +50,50 @@ There is a special function `H` that allows you to use a pattern as an input to
|
|||
tune={`await initHydra()
|
||||
let pattern = "3 4 5 [6 7]*2"
|
||||
shape(H(pattern)).out(o0)
|
||||
n(pattern).scale("A:minor").piano().room(1)
|
||||
n(pattern).scale("A:minor").piano().room(1)
|
||||
`}
|
||||
/>
|
||||
|
||||
## detectAudio
|
||||
|
||||
To use hydra audio capture, call `initHydra` with `{detectAudio:true}` configuration param:
|
||||
|
||||
<MiniRepl
|
||||
client:only="react"
|
||||
tune={`await initHydra({detectAudio:true})
|
||||
let pattern = "<3 4 5 [6 7]*2>"
|
||||
shape(H(pattern)).repeat()
|
||||
.scrollY(
|
||||
()=> a.fft[0]*.25
|
||||
)
|
||||
.add(src(o0).color(.71 ).scrollX(.005),.95)
|
||||
.out(o0)
|
||||
n(pattern).scale("A:minor").piano().room(1)
|
||||
`}
|
||||
/>
|
||||
|
||||
You might now be able to see this properly here: [open in REPL](/#YXdhaXQgaW5pdEh5ZHJhKCkKbGV0IHBhdHRlcm4gPSAiMyA0IDUgWzYgN10qMiIKc2hhcGUoSChwYXR0ZXJuKSkub3V0KG8wKQpuKHBhdHRlcm4pLnNjYWxlKCJBOm1pbm9yIikucGlhbm8oKS5yb29tKDEpIA%3D%3D)
|
||||
|
||||
Similar to `detectAudio`, all the [available hydra options](https://github.com/hydra-synth/hydra-synth#api) can be passed to `initHydra`.
|
||||
|
||||
## feedStrudel
|
||||
|
||||
Using the `feedStrudel` option, you can transform strudel visualizations with hydra:
|
||||
|
||||
<MiniRepl
|
||||
client:only="react"
|
||||
tune={`await initHydra({feedStrudel:1})
|
||||
//
|
||||
src(s0).kaleid(H("<4 5 6>"))
|
||||
.diff(osc(1,0.5,5))
|
||||
.modulateScale(osc(2,-0.25,1))
|
||||
.out()
|
||||
//
|
||||
stack(
|
||||
s("bd*2,[hh:0:<.5 1>]*4,~ rim").bank("RolandTR909").speed(.9),
|
||||
note("[<g1!3 <bb1 <f1 d1>>>]*3").s("sawtooth")
|
||||
.room(.75).sometimes(add(note(12))).clip(.3)
|
||||
.lpa(.05).lpenv(-4).lpf(2000).lpq(8).ftype('24db')
|
||||
).fft(4)
|
||||
.scope({pos:0,smear:.95})`}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ A standalone app has its own desktop / homescreen icon and launches in a separat
|
|||
without the browser ui.
|
||||
|
||||
<figure>
|
||||
<img src="./pwa/strudel-macos.png" alt="Strudel on MacOS" />
|
||||

|
||||
<figcaption>Strudel on MacOS</figcaption>
|
||||
</figure>
|
||||
|
||||
|
|
@ -67,7 +67,7 @@ Without a chromium based browser, you can use [nativefier](https://github.com/na
|
|||
2. run `npx nativefier strudel.cc`
|
||||
|
||||
<figure>
|
||||
<img src="./pwa/strudel-linux.png" alt="Strudel on Linux" />
|
||||

|
||||
<figcaption>Strudel on Linux</figcaption>
|
||||
</figure>
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ You can learn more about both of these approaches in the pages [Synths](/learn/s
|
|||
|
||||
# Combining notes and sounds
|
||||
|
||||
In both of the above cases, we are no longer directly controlling the `note`/`freq` of the sound heard via `s`, as we were in the [Notes](/strudel/notes) page.
|
||||
In both of the above cases, we are no longer directly controlling the `note`/`freq` of the sound heard via `s`, as we were in the [Notes](/workshop/first-notes/) page.
|
||||
|
||||
So how can we both control the sound and the pitch? We can _combine_ `note`/`freq` with `s` to change the sound of our pitches:
|
||||
|
||||
|
|
|
|||
|
|
@ -96,13 +96,13 @@ If you find a tidal control that's not on the list, please tell!
|
|||
## Sound
|
||||
|
||||
Tidal is commonly paired with Superdirt / Supercollider for sound generation.
|
||||
While Strudel also has a way of [communicating with Superdirt](./learn/input-output),
|
||||
While Strudel also has a way of [communicating with Superdirt](/learn/input-output/),
|
||||
it aims to provide a standalone live coding environment that runs entirely in the browser.
|
||||
|
||||
### Audio Effects
|
||||
|
||||
Many of SuperDirt's effects have been reimplemented in Strudel, using the Web Audio API.
|
||||
You can find a [list of available effects here](./learn/effects).
|
||||
You can find a [list of available effects here](/learn/effects/).
|
||||
|
||||
### Sampler
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,10 @@ Some amount of pink noise can also be added to any oscillator by using the `nois
|
|||
|
||||
<MiniRepl client:idle tune={`note("c3").noise("<0.1 0.25 0.5>").scope()`} />
|
||||
|
||||
You can also use the `crackle` type to play some subtle noise crackles. You can control noise amount by using the `density` parameter:
|
||||
|
||||
<MiniRepl client:idle tune={`s("crackle*4").density("<0.01 0.04 0.2 0.5>".slow(4)).scope()`} />
|
||||
|
||||
### Additive Synthesis
|
||||
|
||||
To tame the harsh sound of the basic waveforms, we can set the `n` control to limit the overtones of the waveform:
|
||||
|
|
|
|||
|
|
@ -110,4 +110,4 @@ Some of these have equivalent operators in the Mini Notation:
|
|||
|
||||
<JsDoc client:idle name="ribbon" h={0} />
|
||||
|
||||
Apart from modifying time, there are ways to [Control Parameters](functions/value-modifiers).
|
||||
Apart from modifying time, there are ways to [Control Parameters](/functions/value-modifiers/).
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ import { Content } from '../../../../my-patterns/README.md';
|
|||
import HeadCommon from '../../components/HeadCommon.astro';
|
||||
|
||||
const myPatterns = await getMyPatterns();
|
||||
|
||||
const { BASE_URL } = import.meta.env;
|
||||
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
|
||||
---
|
||||
|
||||
<head>
|
||||
|
|
@ -23,12 +26,12 @@ const myPatterns = await getMyPatterns();
|
|||
Object.entries(myPatterns).map(([name, tune]) => (
|
||||
<a
|
||||
class="rounded-md bg-slate-900 hover:bg-slate-700 cursor-pointer relative"
|
||||
href={`./#${btoa(tune as string)}`}
|
||||
href={`${baseNoTrailing}/#${btoa(tune as string)}`}
|
||||
>
|
||||
<div class="absolute w-full h-full flex justify-center items-center">
|
||||
<span class="bg-slate-800 p-2 rounded-md text-white">{name}</span>
|
||||
</div>
|
||||
<img src={`./swatch/${name}.png`} />
|
||||
<img src={`${baseNoTrailing}/swatch/${name}.png`} />
|
||||
</a>
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ Try adding more sounds inside a bracket!
|
|||
|
||||
</Box>
|
||||
|
||||
Similar to the whole sequence, the content of a sub-sequence will be squished to the its own length.
|
||||
Similar to the whole sequence, the content of a sub-sequence will be squished to its own length.
|
||||
|
||||
**Multiplication: Speed things up**
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { MiniRepl } from '../../docs/MiniRepl';
|
|||
|
||||
# Welcome
|
||||
|
||||
<img src="/icons/strudel_icon.png" className="w-32 animate-pulse md:float-right ml-8" />
|
||||
<div className="w-32 animate-pulse md:float-right ml-8"></div>
|
||||
|
||||
Welcome to the Strudel documentation pages!
|
||||
You've come to the right place if you want to learn how to make music with code.
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import React, { useContext } from 'react';
|
|||
import { useSettings, setIsZen } from '../settings.mjs';
|
||||
// import { ReplContext } from './Repl';
|
||||
import './Repl.css';
|
||||
const { BASE_URL } = import.meta.env;
|
||||
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
|
||||
|
||||
export function Header({ context }) {
|
||||
const {
|
||||
|
|
@ -123,7 +125,7 @@ export function Header({ context }) {
|
|||
{!isEmbedded && (
|
||||
<a
|
||||
title="learn"
|
||||
href="./workshop/getting-started"
|
||||
href={`${baseNoTrailing}/workshop/getting-started/`}
|
||||
className={cx('hover:opacity-50 flex items-center space-x-1', !isEmbedded ? 'p-2' : 'px-2')}
|
||||
>
|
||||
<AcademicCapIcon className="w-6 h-6" />
|
||||
|
|
|
|||
|
|
@ -37,6 +37,11 @@ export function Reference() {
|
|||
{visibleFunctions.map((entry, i) => (
|
||||
<section key={i}>
|
||||
<h3 id={`doc-${i}`}>{entry.name}</h3>
|
||||
{!!entry.synonyms_text && (
|
||||
<p>
|
||||
Synonyms: <code>{entry.synonyms_text}</code>
|
||||
</p>
|
||||
)}
|
||||
{/* <small>{entry.meta.filename}</small> */}
|
||||
<p dangerouslySetInnerHTML={{ __html: entry.description }}></p>
|
||||
<ul>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ import { registerSynthSounds, registerZZFXSounds, samples } from '@strudel.cycle
|
|||
import './piano.mjs';
|
||||
import './files.mjs';
|
||||
|
||||
const { BASE_URL } = import.meta.env;
|
||||
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
|
||||
|
||||
export async function prebake() {
|
||||
// https://archive.org/details/SalamanderGrandPianoV3
|
||||
// License: CC-by http://creativecommons.org/licenses/by/3.0/ Author: Alexander Holm
|
||||
|
|
@ -14,16 +17,16 @@ export async function prebake() {
|
|||
// => getting "window is not defined", as soon as "@strudel.cycles/soundfonts" is imported statically
|
||||
// seems to be a problem with soundfont2
|
||||
import('@strudel.cycles/soundfonts').then(({ registerSoundfonts }) => registerSoundfonts()),
|
||||
samples(`./piano.json`, `./piano/`, { prebake: true }),
|
||||
samples(`${baseNoTrailing}/piano.json`, `${baseNoTrailing}/piano/`, { prebake: true }),
|
||||
// https://github.com/sgossner/VCSL/
|
||||
// https://api.github.com/repositories/126427031/contents/
|
||||
// LICENSE: CC0 general-purpose
|
||||
samples(`./vcsl.json`, 'github:sgossner/VCSL/master/', { prebake: true }),
|
||||
samples(`./tidal-drum-machines.json`, 'github:ritchse/tidal-drum-machines/main/machines/', {
|
||||
samples(`${baseNoTrailing}/vcsl.json`, 'github:sgossner/VCSL/master/', { prebake: true }),
|
||||
samples(`${baseNoTrailing}/tidal-drum-machines.json`, 'github:ritchse/tidal-drum-machines/main/machines/', {
|
||||
prebake: true,
|
||||
tag: 'drum-machines',
|
||||
}),
|
||||
samples(`./EmuSP12.json`, `./EmuSP12/`, { prebake: true, tag: 'drum-machines' }),
|
||||
samples(`${baseNoTrailing}/EmuSP12.json`, `${baseNoTrailing}/EmuSP12/`, { prebake: true, tag: 'drum-machines' }),
|
||||
samples(
|
||||
{
|
||||
casio: ['casio/high.wav', 'casio/low.wav', 'casio/noise.wav'],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue