integrate repl into astro website

+ update build and setup tasks + workflow
+ move repl test folder to root
+ move docs and repl to website/src
This commit is contained in:
Felix Roos 2022-12-22 17:20:51 +01:00
parent 006ca12b6d
commit 818cd9044b
141 changed files with 25956 additions and 41 deletions

View file

@ -7,7 +7,7 @@ import '../styles/index.css';
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<link rel="sitemap" href="/sitemap.xml" />

View file

@ -1,4 +1,4 @@
import jsdoc from '../../../../doc.json'; // doc.json is built with `npm run jsdoc-json`
import jsdoc from '../../../doc.json'; // doc.json is built with `npm run jsdoc-json`
const docs = jsdoc.docs.reduce((acc, obj) => Object.assign(acc, { [obj.longname]: obj }), {});
import { MiniRepl } from './MiniRepl';

View file

@ -20,8 +20,6 @@ if (typeof window !== 'undefined') {
);
}
// prebake();
export function MiniRepl({ tune }) {
const [Repl, setRepl] = useState();
useEffect(() => {

View file

@ -1,8 +1,14 @@
<script is:inline>
// Redirect your homepage to the first page of documentation.
// If you have a landing page, remove this script and add it here!
// window.location.pathname = `/en/introduction`;
</script>
---
import HeadCommon from '../components/HeadCommon.astro';
import { Repl } from '../repl/Repl.jsx';
---
<h1>Hello</h1>
<a href="/tutorial">Tutorial</a>
<html>
<head>
<HeadCommon />
<title>Strudel REPL</title>
</head>
<body>
<Repl client:only="react" />
</body>
</html>

View file

@ -4,8 +4,8 @@ description: Strudel Tutorial
layout: ../../layouts/MainLayout.astro
---
import { MiniRepl } from '../../components/docs/MiniRepl';
import { JsDoc } from '../../components/docs/JsDoc';
import { MiniRepl } from '../../docs/MiniRepl';
import { JsDoc } from '../../docs/JsDoc';
# Functional JavaScript API

View file

@ -4,8 +4,8 @@ description: Strudel Tutorial
layout: ../../layouts/MainLayout.astro
---
import { MiniRepl } from '../../components/docs/MiniRepl';
import { JsDoc } from '../../components/docs/JsDoc';
import { MiniRepl } from '../../docs/MiniRepl';
import { JsDoc } from '../../docs/JsDoc';
# What is Strudel?

View file

@ -3,8 +3,8 @@ title: Mini Notation
layout: ../../layouts/MainLayout.astro
---
import { MiniRepl } from '../../components/docs/MiniRepl';
import { JsDoc } from '../../components/docs/JsDoc';
import { MiniRepl } from '../../docs/MiniRepl';
import { JsDoc } from '../../docs/JsDoc';
# Mini Notation

View file

@ -4,8 +4,8 @@ description: Strudel Tutorial
layout: ../../layouts/MainLayout.astro
---
import { MiniRepl } from '../../components/docs/MiniRepl';
import { JsDoc } from '../../components/docs/JsDoc';
import { MiniRepl } from '../../docs/MiniRepl';
import { JsDoc } from '../../docs/JsDoc';
# Alternative Outputs

View file

@ -4,8 +4,8 @@ description: Strudel Tutorial
layout: ../../layouts/MainLayout.astro
---
import { MiniRepl } from '../../components/docs/MiniRepl';
import { JsDoc } from '../../components/docs/JsDoc';
import { MiniRepl } from '../../docs/MiniRepl';
import { JsDoc } from '../../docs/JsDoc';
# Continuous Signals

View file

@ -4,8 +4,8 @@ description: Strudel Tutorial
layout: ../../layouts/MainLayout.astro
---
import { MiniRepl } from '../../components/docs/MiniRepl';
import { JsDoc } from '../../components/docs/JsDoc';
import { MiniRepl } from '../../docs/MiniRepl';
import { JsDoc } from '../../docs/JsDoc';
# Synths, Samples & Effects

View file

@ -3,8 +3,8 @@ title: Tonal API
layout: ../../layouts/MainLayout.astro
---
import { MiniRepl } from '../../components/docs/MiniRepl';
import { JsDoc } from '../../components/docs/JsDoc';
import { MiniRepl } from '../../docs/MiniRepl';
import { JsDoc } from '../../docs/JsDoc';
# Tonal API

View file

@ -4,8 +4,8 @@ description: Strudel Tutorial
layout: ../../layouts/MainLayout.astro
---
import { MiniRepl } from '../../components/docs/MiniRepl';
import { JsDoc } from '../../components/docs/JsDoc';
import { MiniRepl } from '../../docs/MiniRepl';
import { JsDoc } from '../../docs/JsDoc';
# What is Strudel?

197
website/src/repl/Footer.jsx Normal file
View file

@ -0,0 +1,197 @@
import XMarkIcon from '@heroicons/react/20/solid/XMarkIcon';
import { logger } from '@strudel.cycles/core';
import { cx } from '@strudel.cycles/react';
import { nanoid } from 'nanoid';
import React, { useContext, useCallback, useLayoutEffect, useRef, useState } from 'react';
import { useEvent, loadedSamples, ReplContext } from './Repl';
import { Reference } from './Reference';
export function Footer({ context }) {
// const [activeFooter, setActiveFooter] = useState('console');
// const { activeFooter, setActiveFooter, isZen } = useContext?.(ReplContext);
const { activeFooter, setActiveFooter, isZen } = context;
const footerContent = useRef();
const [log, setLog] = useState([]);
useLayoutEffect(() => {
if (footerContent.current && activeFooter === 'console') {
// scroll log box to bottom when log changes
footerContent.current.scrollTop = footerContent.current?.scrollHeight;
}
}, [log, activeFooter]);
useLayoutEffect(() => {
if (!footerContent.current) {
} else if (activeFooter === 'console') {
footerContent.current.scrollTop = footerContent.current?.scrollHeight;
} else {
footerContent.current.scrollTop = 0;
}
}, [activeFooter]);
useLogger(
useCallback((e) => {
const { message, type, data } = e.detail;
setLog((l) => {
const lastLog = l.length ? l[l.length - 1] : undefined;
const id = nanoid(12);
// if (type === 'loaded-sample' && lastLog.type === 'load-sample' && lastLog.url === data.url) {
if (type === 'loaded-sample') {
// const loadIndex = l.length - 1;
const loadIndex = l.findIndex(({ data: { url }, type }) => type === 'load-sample' && url === data.url);
l[loadIndex] = { message, type, id, data };
} else if (lastLog && lastLog.message === message) {
l = l.slice(0, -1).concat([{ message, type, count: (lastLog.count ?? 1) + 1, id, data }]);
} else {
l = l.concat([{ message, type, id, data }]);
}
return l.slice(-20);
});
}, []),
);
const FooterTab = ({ children, name, label }) => (
<>
<div
onClick={() => setActiveFooter(name)}
className={cx(
'h-8 px-2 text-white cursor-pointer hover:text-tertiary flex items-center space-x-1 border-b',
activeFooter === name ? 'border-white hover:border-tertiary' : 'border-transparent',
)}
>
{label || name}
</div>
{activeFooter === name && <>{children}</>}
</>
);
if (isZen) {
return null;
}
return (
<footer className="bg-footer z-[20]">
<div className="flex justify-between px-2">
<div className={cx('flex select-none', activeFooter && 'pb-2')}>
<FooterTab name="intro" label="welcome" />
<FooterTab name="samples" />
<FooterTab name="console" />
<FooterTab name="reference" />
</div>
{activeFooter !== '' && (
<button onClick={() => setActiveFooter('')} className="text-white">
<XMarkIcon className="w-5 h-5" />
</button>
)}
</div>
{activeFooter !== '' && (
<div
className="text-white font-mono text-sm h-[360px] flex-none overflow-auto max-w-full relative"
ref={footerContent}
>
{activeFooter === 'intro' && (
<div className="prose prose-invert max-w-[600px] pt-2 font-sans pb-8 px-4">
<h3>
<span className={cx('animate-spin inline-block select-none')}>🌀</span> welcome
</h3>
<p>
You have found <span className="underline">strudel</span>, a new live coding platform to write dynamic
music pieces in the browser! It is free and open-source and made for beginners and experts alike. To get
started:
<br />
<br />
<span className="underline">1. hit play</span> - <span className="underline">2. change something</span>{' '}
- <span className="underline">3. hit update</span>
<br />
If you don't like what you hear, try <span className="underline">shuffle</span>!
</p>
<p>
To learn more about what this all means, check out the{' '}
<a href="https://strudel.tidalcycles.org/tutorial" target="_blank">
interactive tutorial
</a>
. Also feel free to join the{' '}
<a href="https://discord.com/invite/HGEdXmRkzT" target="_blank">
tidalcycles discord channel
</a>{' '}
to ask any questions, give feedback or just say hello.
</p>
<h3>about</h3>
<p>
strudel is a JavaScript version of{' '}
<a href="tidalcycles.org/" target="_blank">
tidalcycles
</a>
, which is a popular live coding language for music, written in Haskell. You can find the source code at{' '}
<a href="https://github.com/tidalcycles/strudel" target="_blank">
github
</a>
. Please consider to{' '}
<a href="https://opencollective.com/tidalcycles" target="_blank">
support this project
</a>{' '}
to ensure ongoing development 💖
</p>
</div>
)}
{activeFooter === 'console' && (
<div className="break-all px-4">
{log.map((l, i) => {
const message = linkify(l.message);
return (
<div
key={l.id}
className={cx(l.type === 'error' && 'text-red-500', l.type === 'highlight' && 'text-highlight')}
>
<span dangerouslySetInnerHTML={{ __html: message }} />
{l.count ? ` (${l.count})` : ''}
</div>
);
})}
</div>
)}
{activeFooter === 'samples' && (
<div className="break-normal w-full px-4">
<span className="text-white">{loadedSamples.length} banks loaded:</span>
{loadedSamples.map(([name, samples]) => (
<span key={name} className="cursor-pointer hover:text-tertiary" onClick={() => {}}>
{' '}
{name}(
{Array.isArray(samples)
? samples.length
: typeof samples === 'object'
? Object.values(samples).length
: 1}
){' '}
</span>
))}
</div>
)}
{activeFooter === 'reference' && <Reference />}
</div>
)}
</footer>
);
}
function useLogger(onTrigger) {
useEvent(logger.key, onTrigger);
}
function linkify(inputText) {
var replacedText, replacePattern1, replacePattern2, replacePattern3;
//URLs starting with http://, https://, or ftp://
replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
replacedText = inputText.replace(replacePattern1, '<a class="underline" href="$1" target="_blank">$1</a>');
//URLs starting with "www." (without // before it, or it'd re-link the ones done above).
replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
replacedText = replacedText.replace(
replacePattern2,
'$1<a class="underline" href="http://$2" target="_blank">$2</a>',
);
//Change email addresses to mailto:: links.
replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim;
replacedText = replacedText.replace(replacePattern3, '<a class="underline" href="mailto:$1">$1</a>');
return replacedText;
}

142
website/src/repl/Header.jsx Normal file
View file

@ -0,0 +1,142 @@
import AcademicCapIcon from '@heroicons/react/20/solid/AcademicCapIcon';
import CommandLineIcon from '@heroicons/react/20/solid/CommandLineIcon';
import LinkIcon from '@heroicons/react/20/solid/LinkIcon';
import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
import SparklesIcon from '@heroicons/react/20/solid/SparklesIcon';
import StopCircleIcon from '@heroicons/react/20/solid/StopCircleIcon';
import { cx } from '@strudel.cycles/react';
import React, { useContext } from 'react';
// import { ReplContext } from './Repl';
import './Repl.css';
const isEmbedded = window.location !== window.parent.location;
export function Header({ context }) {
const {
started,
pending,
isDirty,
lastShared,
activeCode,
handleTogglePlay,
handleUpdate,
handleShuffle,
handleShare,
isZen,
setIsZen,
} = context;
// useContext(ReplContext)
return (
<header
id="header"
className={cx(
'flex-none w-full md:flex text-black justify-between z-[100] text-lg select-none sticky top-0',
isEmbedded ? 'h-12 md:h-8' : 'h-25 md:h-14',
!isZen && 'bg-header',
)}
>
<div className="px-4 flex items-center space-x-2 pt-2 md:pt-0 select-none">
{/* <img
src={logo}
className={cx('Tidal-logo', isEmbedded ? 'w-8 h-8' : 'w-10 h-10', started && 'animate-pulse')} // 'bg-[#ffffff80] rounded-full'
alt="logo"
/> */}
<h1
className={cx(
isEmbedded ? 'text-l' : 'text-xl',
// 'bg-clip-text bg-gradient-to-r from-primary to-secondary text-transparent font-bold',
'text-white font-bold flex space-x-2',
)}
>
<div
className={cx('mt-[1px]', started && 'animate-spin', 'cursor-pointer')}
onClick={() => setIsZen((z) => !z)}
>
🌀
</div>
{!isZen && (
<div className={cx(started && 'animate-pulse')}>
<span className="">strudel</span> <span className="text-sm">REPL</span>
</div>
)}
</h1>
</div>
{!isZen && (
<div className="flex max-w-full overflow-auto text-white ">
<button
onClick={handleTogglePlay}
className={cx(!isEmbedded ? 'p-2' : 'px-2', 'hover:text-tertiary', !started && 'animate-pulse')}
>
{!pending ? (
<span className={cx('flex items-center space-x-1', isEmbedded ? 'w-16' : 'w-16')}>
{started ? <StopCircleIcon className="w-5 h-5" /> : <PlayCircleIcon className="w-5 h-5" />}
<span>{started ? 'stop' : 'play'}</span>
</span>
) : (
<>loading...</>
)}
</button>
<button
onClick={handleUpdate}
className={cx(
'flex items-center space-x-1',
!isEmbedded ? 'p-2' : 'px-2',
!isDirty || !activeCode ? 'opacity-50' : 'hover:text-tertiary',
)}
>
<CommandLineIcon className="w-5 h-5" />
<span>update</span>
</button>
{!isEmbedded && (
<button className="hover:text-tertiary p-2 flex items-center space-x-1" onClick={handleShuffle}>
<SparklesIcon className="w-5 h-5" />
<span> shuffle</span>
</button>
)}
{!isEmbedded && (
<a
href="./tutorial"
className={cx('hover:text-tertiary flex items-center space-x-1', !isEmbedded ? 'p-2' : 'px-2')}
>
<AcademicCapIcon className="w-5 h-5" />
<span>learn</span>
</a>
)}
{!isEmbedded && (
<button
className={cx(
'cursor-pointer hover:text-tertiary flex items-center space-x-1',
!isEmbedded ? 'p-2' : 'px-2',
)}
onClick={handleShare}
>
<LinkIcon className="w-5 h-5" />
<span>share{lastShared && lastShared === (activeCode || code) ? 'd!' : ''}</span>
</button>
)}
{isEmbedded && (
<button className={cx('hover:text-tertiary px-2')}>
<a href={window.location.href} target="_blank" rel="noopener noreferrer" title="Open in REPL">
🚀 open
</a>
</button>
)}
{isEmbedded && (
<button className={cx('hover:text-tertiary px-2')}>
<a
onClick={() => {
window.location.href = initialUrl;
window.location.reload();
}}
title="Reset"
>
💔 reset
</a>
</button>
)}
</div>
)}
</header>
);
}

View file

@ -0,0 +1,38 @@
import jsdocJson from '../../../doc.json';
const visibleFunctions = jsdocJson.docs
.filter(({ name, description }) => name && !name.startsWith('_') && !!description)
.sort((a, b) => a.meta.filename.localeCompare(b.meta.filename) + a.name.localeCompare(b.name));
export function Reference() {
return (
<div className="flex h-full w-full pt-2">
<div className="w-64 flex-none h-full overflow-y-auto overflow-x-hidden pr-4">
{visibleFunctions.map((entry, i) => (
<a key={i} className="cursor-pointer block hover:bg-linegray py-1 px-4" href={`#doc-${i}`}>
{entry.name} {/* <span className="text-gray-600">{entry.meta.filename}</span> */}
</a>
))}
</div>
<div className="break-normal w-full h-full overflow-auto pl-4 flex relative">
<div className="prose prose-invert">
<h2>API Reference</h2>
<p>
This is the long list functions you can use! Remember that you don't need to remember all of those and that
you can already make music with a small set of functions!
</p>
{visibleFunctions.map((entry, i) => (
<section key={i}>
<h3 id={`doc-${i}`}>{entry.name}</h3>
{/* <small>{entry.meta.filename}</small> */}
<p dangerouslySetInnerHTML={{ __html: entry.description }}></p>
{entry.examples?.map((example, j) => (
<pre key={j}>{example}</pre>
))}
</section>
))}
</div>
</div>
</div>
);
}

26
website/src/repl/Repl.css Normal file
View file

@ -0,0 +1,26 @@
@import '@strudel.cycles/react/dist/style.css';
body {
background-color: #222;
}
.darken::before {
content: ' ';
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: block;
background: black;
opacity: 0.5;
}
.cm-gutters {
display: none !important;
}
.cm-content {
padding-top: 12px !important;
padding-left: 8px !important;
}

288
website/src/repl/Repl.jsx Normal file
View file

@ -0,0 +1,288 @@
/*
App.js - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/repl/src/App.js>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { cleanupDraw, cleanupUi, controls, evalScope, logger } from '@strudel.cycles/core';
import { CodeMirror, cx, flash, useHighlighting, useStrudel } from '@strudel.cycles/react';
import {
getAudioContext,
getLoadedSamples,
initAudioOnFirstClick,
resetLoadedSamples,
webaudioOutput,
} from '@strudel.cycles/webaudio';
import { createClient } from '@supabase/supabase-js';
import { nanoid } from 'nanoid';
import React, { createContext, useCallback, useEffect, useState } from 'react';
import * as WebDirt from 'WebDirt';
import './Repl.css';
import { Footer } from './Footer';
import { Header } from './Header';
import { prebake } from './prebake.mjs';
import * as tunes from '../../../tunes.mjs';
initAudioOnFirstClick();
// Create a single supabase client for interacting with your database
const supabase = createClient(
'https://pidxdsxphlhzjnzmifth.supabase.co',
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM',
);
const modules = [
import('@strudel.cycles/core'),
import('@strudel.cycles/tonal'),
import('@strudel.cycles/mini'),
import('@strudel.cycles/midi'),
import('@strudel.cycles/xen'),
import('@strudel.cycles/webaudio'),
import('@strudel.cycles/osc'),
import('@strudel.cycles/serial'),
import('@strudel.cycles/soundfonts'),
import('@strudel.cycles/csound'),
];
evalScope(
controls, // sadly, this cannot be exported from core direclty
{ WebDirt },
...modules,
);
export let loadedSamples = [];
const presets = prebake();
Promise.all([...modules, presets]).then((data) => {
// console.log('modules and sample registry loade', data);
loadedSamples = Object.entries(getLoadedSamples() || {});
});
const getTime = () => getAudioContext().currentTime;
async function initCode() {
// load code from url hash (either short hash from database or decode long hash)
try {
const initialUrl = window.location.href;
const hash = initialUrl.split('?')[1]?.split('#')?.[0];
const codeParam = window.location.href.split('#')[1];
// looking like https://strudel.tidalcycles.org/?J01s5i1J0200 (fixed hash length)
if (codeParam) {
// looking like https://strudel.tidalcycles.org/#ImMzIGUzIg%3D%3D (hash length depends on code length)
return atob(decodeURIComponent(codeParam || ''));
} else if (hash) {
return supabase
.from('code')
.select('code')
.eq('hash', hash)
.then(({ data, error }) => {
if (error) {
console.warn('failed to load hash', err);
}
if (data.length) {
console.log('load hash from database', hash);
return data[0].code;
}
});
}
} catch (err) {
console.warn('failed to decode', err);
}
}
function getRandomTune() {
const allTunes = Object.entries(tunes);
const randomItem = (arr) => arr[Math.floor(Math.random() * arr.length)];
const [name, code] = randomItem(allTunes);
return { name, code };
}
const { code: randomTune, name } = getRandomTune();
export const ReplContext = createContext(null);
export function Repl() {
const [view, setView] = useState(); // codemirror view
const [lastShared, setLastShared] = useState();
const [activeFooter, setActiveFooter] = useState('');
const [isZen, setIsZen] = useState(false);
const [pending, setPending] = useState(false);
const { code, setCode, scheduler, evaluate, activateCode, isDirty, activeCode, pattern, started, stop, error } =
useStrudel({
initialCode: '// LOADING',
defaultOutput: webaudioOutput,
getTime,
autolink: true,
beforeEval: () => {
cleanupUi();
cleanupDraw();
setPending(true);
},
afterEval: () => {
setPending(false);
},
onToggle: (play) => !play && cleanupDraw(false),
});
// init code
useEffect(() => {
initCode().then((decoded) => {
if (!decoded) {
setActiveFooter('intro'); // TODO: get rid
}
logger(
`Welcome to Strudel! ${
decoded ? `I have loaded the code from the URL.` : `A random code snippet named "${name}" has been loaded!`
} Press play or hit ctrl+enter to run it!`,
'highlight',
);
setCode(decoded || randomTune);
});
}, []);
// keyboard shortcuts
useKeydown(
useCallback(
async (e) => {
if (e.ctrlKey || e.altKey) {
if (e.code === 'Enter') {
e.preventDefault();
flash(view);
await activateCode();
} else if (e.code === 'Period') {
stop();
e.preventDefault();
}
}
},
[activateCode, stop, view],
),
);
// highlighting
useHighlighting({
view,
pattern,
active: started && !activeCode?.includes('strudel disable-highlighting'),
getTime: () => scheduler.getPhase(),
});
//
// UI Actions
//
const handleChangeCode = useCallback(
(c) => {
setCode(c);
started && logger('[edit] code changed. hit ctrl+enter to update');
},
[started],
);
const handleSelectionChange = useCallback((selection) => {
// TODO: scroll to selected function in reference
// console.log('selectino change', selection.ranges[0].from);
}, []);
const handleTogglePlay = async () => {
await getAudioContext().resume(); // fixes no sound in ios webkit
if (!started) {
logger('[repl] started. tip: you can also start by pressing ctrl+enter', 'highlight');
activateCode();
} else {
logger('[repl] stopped. tip: you can also stop by pressing ctrl+dot', 'highlight');
stop();
}
};
const handleUpdate = () => {
isDirty && activateCode();
logger('[repl] code updated! tip: you can also update the code by pressing ctrl+enter', 'highlight');
};
const handleShuffle = async () => {
const { code, name } = getRandomTune();
logger(`[repl] ✨ loading random tune "${name}"`);
resetLoadedSamples();
await prebake(); // declare default samples
await evaluate(code, false);
};
const handleShare = async () => {
const codeToShare = activeCode || code;
if (lastShared === codeToShare) {
logger(`Link already generated!`, 'error');
return;
}
// generate uuid in the browser
const hash = nanoid(12);
const { data, error } = await supabase.from('code').insert([{ code: codeToShare, hash }]);
if (!error) {
setLastShared(activeCode || code);
const shareUrl = window.location.origin + '?' + hash;
// copy shareUrl to clipboard
await navigator.clipboard.writeText(shareUrl);
const message = `Link copied to clipboard: ${shareUrl}`;
alert(message);
// alert(message);
logger(message, 'highlight');
} else {
console.log('error', error);
const message = `Error: ${error.message}`;
// alert(message);
logger(message);
}
};
const context = {
started,
pending,
isDirty,
lastShared,
activeCode,
activeFooter,
setActiveFooter,
handleChangeCode,
handleTogglePlay,
handleUpdate,
handleShuffle,
handleShare,
isZen,
setIsZen,
};
return (
// bg-gradient-to-t from-blue-900 to-slate-900
// bg-gradient-to-t from-green-900 to-slate-900
<ReplContext.Provider value={context}>
<div
className={cx(
'h-screen flex flex-col',
// 'bg-gradient-to-t from-green-900 to-slate-900', //
)}
>
<Header context={context} />
<section className="grow flex text-gray-100 relative overflow-auto cursor-text pb-0" id="code">
<CodeMirror
value={code}
onChange={handleChangeCode}
onViewChanged={setView}
onSelectionChange={handleSelectionChange}
/>
</section>
{error && (
<div className="text-red-500 p-4 bg-lineblack animate-pulse">{error.message || 'Unknown Error :-/'}</div>
)}
<Footer context={context} />
</div>
</ReplContext.Provider>
);
}
export function useEvent(name, onTrigger, useCapture = false) {
useEffect(() => {
document.addEventListener(name, onTrigger, useCapture);
return () => {
document.removeEventListener(name, onTrigger, useCapture);
};
}, [onTrigger]);
}
function useKeydown(onTrigger) {
useEvent('keydown', onTrigger, true);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,31 @@
import { Pattern, toMidi, valueToMidi } from '@strudel.cycles/core';
import { samples } from '@strudel.cycles/webaudio';
export async function prebake({ baseDir = '.' } = {}) {
// https://archive.org/details/SalamanderGrandPianoV3
// License: CC-by http://creativecommons.org/licenses/by/3.0/ Author: Alexander Holm
return await Promise.all([
samples('piano.json', `${baseDir}/piano/`),
// https://github.com/sgossner/VCSL/
// https://api.github.com/repositories/126427031/contents/
// LICENSE: CC0 general-purpose
samples('vcsl.json', 'github:sgossner/VCSL/master/'),
samples('tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/'),
samples('EmuSP12.json', `${baseDir}/EmuSP12/`),
]);
}
const maxPan = toMidi('C8');
const panwidth = (pan, width) => pan * width + (1 - width) / 2;
Pattern.prototype.piano = function () {
return this.clip(1)
.s('piano')
.release(0.1)
.fmap((value) => {
const midi = valueToMidi(value);
// pan by pitch
const pan = panwidth(Math.min(Math.round(midi) / maxPan, 1), 0.5);
return { ...value, pan: (value.pan || 1) * pan };
});
};

862
website/src/repl/tunes.mjs Normal file
View file

@ -0,0 +1,862 @@
/*
tunes.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/repl/src/tunes.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export const swimming = `// Koji Kondo - Swimming (Super Mario World)
stack(
seq(
"~",
"~",
"~",
"A5 [F5@2 C5] [D5@2 F5] F5",
"[C5@2 F5] [F5@2 C6] A5 G5",
"A5 [F5@2 C5] [D5@2 F5] F5",
"[C5@2 F5] [Bb5 A5 G5] F5@2",
"A5 [F5@2 C5] [D5@2 F5] F5",
"[C5@2 F5] [F5@2 C6] A5 G5",
"A5 [F5@2 C5] [D5@2 F5] F5",
"[C5@2 F5] [Bb5 A5 G5] F5@2",
"A5 [F5@2 C5] A5 F5",
"Ab5 [F5@2 Ab5] G5@2",
"A5 [F5@2 C5] A5 F5",
"Ab5 [F5@2 C5] C6@2",
"A5 [F5@2 C5] [D5@2 F5] F5",
"[C5@2 F5] [Bb5 A5 G5] F5@2"
),
seq(
"[F4,Bb4,D5] [[D4,G4,Bb4]@2 [Bb3,D4,F4]] [[G3,C4,E4]@2 [[Ab3,F4] [A3,Gb4]]] [Bb3,E4,G4]",
"[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, Bb3, Db3] [F3, Bb3, Db3]]",
"[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]",
"[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]",
"[~ [A3, C4, E4] [A3, C4, E4]] [~ [Ab3, C4, Eb4] [Ab3, C4, Eb4]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [G3, C4, E4] [G3, C4, E4]]",
"[~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]",
"[~ [F3, Bb3, D4] [F3, Bb3, D4]] [~ [F3, Bb3, C4] [F3, Bb3, C4]] [~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, A3, C4] [F3, A3, C4]]",
"[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]",
"[~ [A3, C4, E4] [A3, C4, E4]] [~ [Ab3, C4, Eb4] [Ab3, C4, Eb4]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [G3, C4, E4] [G3, C4, E4]]",
"[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]",
"[~ [F3, Bb3, D4] [F3, Bb3, D4]] [~ [F3, Bb3, C4] [F3, Bb3, C4]] [~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, A3, C4] [F3, A3, C4]]",
"[~ [Bb3, D3, F4] [Bb3, D3, F4]] [~ [Bb3, D3, F4] [Bb3, D3, F4]] [~ [A3, C4, F4] [A3, C4, F4]] [~ [A3, C4, F4] [A3, C4, F4]]",
"[~ [Ab3, B3, F4] [Ab3, B3, F4]] [~ [Ab3, B3, F4] [Ab3, B3, F4]] [~ [G3, Bb3, F4] [G3, Bb3, F4]] [~ [G3, Bb3, E4] [G3, Bb3, E4]]",
"[~ [Bb3, D3, F4] [Bb3, D3, F4]] [~ [Bb3, D3, F4] [Bb3, D3, F4]] [~ [A3, C4, F4] [A3, C4, F4]] [~ [A3, C4, F4] [A3, C4, F4]]",
"[~ [Ab3, B3, F4] [Ab3, B3, F4]] [~ [Ab3, B3, F4] [Ab3, B3, F4]] [~ [G3, Bb3, F4] [G3, Bb3, F4]] [~ [G3, Bb3, E4] [G3, Bb3, E4]]",
"[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]",
"[~ [F3, Bb3, D4] [F3, Bb3, D4]] [~ [F3, Bb3, C4] [F3, Bb3, C4]] [~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, A3, C4] [F3, A3, C4]]"
),
seq(
"[G3 G3 C3 E3]",
"[F2 D2 G2 C2]",
"[F2 D2 G2 C2]",
"[F2 A2 Bb2 B2]",
"[A2 Ab2 G2 C2]",
"[F2 A2 Bb2 B2]",
"[G2 C2 F2 F2]",
"[F2 A2 Bb2 B2]",
"[A2 Ab2 G2 C2]",
"[F2 A2 Bb2 B2]",
"[G2 C2 F2 F2]",
"[Bb2 Bb2 A2 A2]",
"[Ab2 Ab2 G2 [C2 D2 E2]]",
"[Bb2 Bb2 A2 A2]",
"[Ab2 Ab2 G2 [C2 D2 E2]]",
"[F2 A2 Bb2 B2]",
"[G2 C2 F2 F2]"
)
).note().slow(51);
`;
export const giantSteps = `// John Coltrane - Giant Steps
setVoicingRange('lefthand', ['E3', 'G4']);
stack(
// melody
seq(
"[F#5 D5] [B4 G4] Bb4 [B4 A4]",
"[D5 Bb4] [G4 Eb4] F#4 [G4 F4]",
"Bb4 [B4 A4] D5 [D#5 C#5]",
"F#5 [G5 F5] Bb5 [F#5 F#5]",
),
// chords
seq(
"[B^7 D7] [G^7 Bb7] Eb^7 [Am7 D7]",
"[G^7 Bb7] [Eb^7 F#7] B^7 [Fm7 Bb7]",
"Eb^7 [Am7 D7] G^7 [C#m7 F#7]",
"B^7 [Fm7 Bb7] Eb^7 [C#m7 F#7]"
).voicings('lefthand'),
// bass
seq(
"[B2 D2] [G2 Bb2] [Eb2 Bb3] [A2 D2]",
"[G2 Bb2] [Eb2 F#2] [B2 F#2] [F2 Bb2]",
"[Eb2 Bb2] [A2 D2] [G2 D2] [C#2 F#2]",
"[B2 F#2] [F2 Bb2] [Eb2 Bb3] [C#2 F#2]"
)
).slow(20).note()`;
export const zeldasRescue = `// Koji Kondo - Princess Zelda's Rescue
stack(
// melody
\`[B3@2 D4] [A3@2 [G3 A3]] [B3@2 D4] [A3]
[B3@2 D4] [A4@2 G4] [D4@2 [C4 B3]] [A3]
[B3@2 D4] [A3@2 [G3 A3]] [B3@2 D4] [A3]
[B3@2 D4] [A4@2 G4] D5@2
[D5@2 [C5 B4]] [[C5 B4] G4@2] [C5@2 [B4 A4]] [[B4 A4] E4@2]
[D5@2 [C5 B4]] [[C5 B4] G4 C5] [G5] [~ ~ B3]\`,
// bass
\`[[C2 G2] E3@2] [[C2 G2] F#3@2] [[C2 G2] E3@2] [[C2 G2] F#3@2]
[[B1 D3] G3@2] [[Bb1 Db3] G3@2] [[A1 C3] G3@2] [[D2 C3] F#3@2]
[[C2 G2] E3@2] [[C2 G2] F#3@2] [[C2 G2] E3@2] [[C2 G2] F#3@2]
[[B1 D3] G3@2] [[Bb1 Db3] G3@2] [[A1 C3] G3@2] [[D2 C3] F#3@2]
[[F2 C3] E3@2] [[E2 B2] D3@2] [[D2 A2] C3@2] [[C2 G2] B2@2]
[[F2 C3] E3@2] [[E2 B2] D3@2] [[Eb2 Bb2] Db3@2] [[D2 A2] C3 [F3,G2]]\`
).transpose(12).slow(48)
.superimpose(x=>x.add(0.06)) // add slightly detuned voice
.note()
.gain(.1)
.s('triangle')
.room(1)
`;
export const caverave = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
const keys = x => x.s('sawtooth').cutoff(1200).gain(.5).attack(0).decay(.16).sustain(.3).release(.1);
const drums = stack(
s("bd*2").mask("<x@7 ~>/8").gain(.8),
s("~ <sd!7 [sd@3 ~]>").mask("<x@7 ~>/4").gain(.5),
s("[~ hh]*2").delay(.3).delayfeedback(.5).delaytime(.125).gain(.4)
);
const thru = (x) => x.transpose("<0 1>/8").transpose(-1);
const synths = stack(
"<eb4 d4 c4 b3>/2".scale(timeCat([3,'C minor'],[1,'C melodic minor'])
.slow(8)).struct("[~ x]*2")
.layer(
x=>x.scaleTranspose(0).early(0),
x=>x.scaleTranspose(2).early(1/8),
x=>x.scaleTranspose(7).early(1/4),
x=>x.scaleTranspose(8).early(3/8)
).apply(thru).note().apply(keys).mask("<~ x>/16"),
note("<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".apply(thru))
.struct("[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2".fast(2))
.s('sawtooth').attack(0.001).decay(0.2).sustain(1).cutoff(500),
"<Cm7 Bb7 Fm7 G7b13>/2".struct("~ [x@0.2 ~]".fast(2)).voicings('lefthand')
.apply(thru).every(2, early(1/8)).note().apply(keys).sustain(0)
.delay(.4).delaytime(.12)
.mask("<x@7 ~>/8".early(1/4))
)
stack(
drums.fast(2),
synths
).slow(2)`;
export const sampleDrums = `samples({
bd: 'bd/BT0A0D0.wav',
sn: 'sn/ST0T0S3.wav',
hh: 'hh/000_hh3closedhh.wav'
}, 'https://loophole-letters.vercel.app/samples/tidal/')
stack(
"<bd!3 bd(3,4,2)>",
"hh*4",
"~ <sn!3 sn(3,4,1)>"
).s()
`;
export const barryHarris = `// adapted from a Barry Harris excercise
"0,2,[7 6]"
.add("<0 1 2 3 4 5 7 8>")
.scale('C bebop major')
.transpose("<0 1 2 1>/8")
.slow(2)
.note().piano()
`;
export const blippyRhodes = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
samples({
bd: 'samples/tidal/bd/BT0A0D0.wav',
sn: 'samples/tidal/sn/ST0T0S3.wav',
hh: 'samples/tidal/hh/000_hh3closedhh.wav',
rhodes: {
E1: 'samples/rhodes/MK2Md2000.mp3',
E2: 'samples/rhodes/MK2Md2012.mp3',
E3: 'samples/rhodes/MK2Md2024.mp3',
E4: 'samples/rhodes/MK2Md2036.mp3',
E5: 'samples/rhodes/MK2Md2048.mp3',
E6: 'samples/rhodes/MK2Md2060.mp3',
E7: 'samples/rhodes/MK2Md2072.mp3'
}
}, 'https://loophole-letters.vercel.app/')
const scales = cat('C major', 'C mixolydian', 'F lydian', ['F minor', cat('Db major','Db mixolydian')])
stack(
s("<bd sn> <hh hh*2 hh*3>"),
"<g4 c5 a4 [ab4 <eb5 f5>]>"
.scale(scales)
.struct("x*8")
.scaleTranspose("0 [-5,-2] -7 [-9,-2]")
.legato(.3)
.slow(2)
.note()
.s('rhodes')
.clip(1)
.room(.5)
.delay(.3)
.delayfeedback(.4)
.delaytime(1/12).gain(.5),
"<c2 c3 f2 [[F2 C2] db2]>"
.legato("<1@3 [.3 1]>")
.slow(2).superimpose(x=>x.add(.02))
.note().gain(.3)
.s('sawtooth').cutoff(600),
).fast(3/2)`;
export const wavyKalimba = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
samples({
'kalimba': { c5:'https://freesound.org/data/previews/536/536549_11935698-lq.mp3' }
})
const scales = cat('C major', 'C mixolydian', 'F lydian', ['F minor', 'Db major'])
stack(
"[0 2 4 6 9 2 0 -2]*3"
.add("<0 2>/4")
.scale(scales)
.struct("x*8")
.velocity("<.8 .3 .6>*8")
.slow(2),
"<c2 c2 f2 [[F2 C2] db2]>"
.scale(scales)
.scaleTranspose("[0 <2 4>]*2")
.struct("x*4")
.velocity("<.8 .5>*4")
.velocity(0.8)
.slow(2)
)
.legato("<.4 .8 1 1.2 1.4 1.6 1.8 2>/8")
.fast(1)
.note()
.clip(1)
.s('kalimba')
.delay(.2)
`;
export const festivalOfFingers = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
const chords = "<Cm7 Fm7 G7 F#7>";
stack(
chords.voicings('lefthand').struct("x(3,8,-1)").velocity(.5).off(1/7,x=>x.transpose(12).velocity(.2)),
chords.rootNotes(2).struct("x(4,8,-2)"),
chords.rootNotes(4)
.scale(cat('C minor','F dorian','G dorian','F# mixolydian'))
.struct("x(3,8,-2)".fast(2))
.scaleTranspose("0 4 0 6".early(".125 .5")).layer(scaleTranspose("0,<2 [4,6] [5,7]>/4"))
).slow(2)
.velocity(sine.struct("x*8").add(3/5).mul(2/5).fast(8))
.note().piano()`;
// iter, echo, echoWith
export const undergroundPlumber = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos, inspired by Friendship - Let's not talk about it (1979) :)
samples({ bd: 'bd/BT0A0D0.wav', sn: 'sn/ST0T0S3.wav', hh: 'hh/000_hh3closedhh.wav', cp: 'cp/HANDCLP0.wav',
}, 'https://loophole-letters.vercel.app/samples/tidal/')
const h = x=>x.transpose("<0@2 5 0 7 5 0 -5>/2")
stack(
s("<<bd*2 bd> sn> hh").fast(2).gain(.7),
"[c2 a1 bb1 ~] ~"
.echo(2, 1/16, 1)
.legato(.4)
.slow(2)
.layer(h)
.note().s('square')
.cutoff(400).decay(.12).sustain(0)
,
"[g2,[c3 eb3]]".iter(4)
.echoWith(4, 1/8, (x,n)=>x.transpose(n*12).velocity(Math.pow(.4,n)))
.legato(.1)
.layer(h).note()
)
.fast(2/3)
.pianoroll({})`;
export const bridgeIsOver = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos, bassline by BDP - The Bridge Is Over
samples({mad:'https://freesound.org/data/previews/22/22274_109943-lq.mp3'})
stack(
stack(
"c3*2 [[c3@1.4 bb2] ab2] gb2*2 <[[gb2@1.4 ab2] bb2] gb2>".legato(".5 1".fast(2)).velocity(.8),
"0 ~".scale('c4 whole tone')
.euclidLegato(3,8).slow(2).mask("x ~")
.stutWith(8, 1/16, (x,n)=>x.scaleTranspose(n).velocity(Math.pow(.7,n)))
.scaleTranspose("<0 1 2 3 4 3 2 1>")
.fast(2)
.velocity(.7)
.legato(.5)
.stut(3, .5, 1/8)
).transpose(-1).note().piano(),
s("mad").slow(2)
).cpm(78).slow(4)
.pianoroll()
`;
export const goodTimes = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
const scale = cat('C3 dorian','Bb2 major').slow(4);
stack(
"2*4".add(12).scale(scale)
.off(1/8, scaleTranspose("2")).fast(2)
.scaleTranspose("<0 1 2 1>").hush(),
"<0 1 2 3>(3,8,2)"
.scale(scale)
.off(1/4, scaleTranspose("2,4")),
"<0 4>(5,8)".scale(scale).transpose(-12)
)
.velocity(".6 .7".fast(4))
.legato("2")
.scaleTranspose("<0>".slow(4))
.transpose(5)
.note().piano()
.velocity(.8)
.slow(2)
.pianoroll({maxMidi:100,minMidi:20})`;
/* // new style notation:
export const goodTimes = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
const scale = cat('C3 dorian','Bb2 major').slow(4);
stack(
n("2*4").add(12)
.off(1/8, add("2")).fast(2)
.add("<0 1 2 1>").hush(),
n("<0 1 2 3>(3,8,2)")
.off(1/4, add("2,4")),
n("<0 4>(5,8)").sub(7)
)
.scale(scale)
.gain(".6 .7".fast(4))
.legato(2)
.add(note(5))
.piano()
.mul(gain(.8))
.slow(2)
.pianoroll({maxMidi:100,minMidi:20})`;
*/
export const echoPiano = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
"<0 2 [4 6](3,4,1) 3*2>"
.scale('D minor')
.color('salmon')
.off(1/4, x=>x.scaleTranspose(2).color('green'))
.off(1/2, x=>x.scaleTranspose(6).color('steelblue'))
.legato(.5)
.echo(4, 1/8, .5)
.note().piano()
.pianoroll()`;
export const sml1 = `// Hirokazu Tanaka - World 1-1
stack(
// melody
\`<
[e5 ~] [[d5@2 c5] [~@2 e5]] ~ [~ [c5@2 d5]] [e5 e5] [d5 c5] [e5 f5] [g5 a5]
[~ c5] [c5 d5] [e5 [c5@2 c5]] [~ c5] [f5 e5] [c5 d5] [~ g6] [g6 ~]
[e5 ~] [[d5@2 c5] [~@2 e5]] ~ [~ [c5@2 d5]] [e5 e5] [d5 c5] [a5 g5] [c6 [e5@2 d5]]
[~ c5] [c5 d5] [e5 [c5@2 c5]] [~ c5] [f5 e5] [c5 d5] [~ [g6@2 ~] ~@2] [g5 ~]
[~ a5] [b5 c6] [b5@2 ~@2 g5] ~
[f5 ~] [[g5@2 f5] ~] [[e5 ~] [f5 ~]] [[f#5 ~] [g5 ~]]
[~ a5] [b5 c6] [b5@2 ~@2 g5] ~
[eb6 d6] [~ c6] ~!2
>\`
.legato(.95),
// sub melody
\`<
[~ g4]!2 [~ ab4]!2 [~ a4]!2 [~ bb4]!2
[~ a4]!2 [~ g4]!2 [d4 e4] [f4 gb4] ~!2
[~ g4]!2 [~ ab4]!2 [~ a4]!2 [~ bb4]!2
[~ a4]!2 [~ g4]!2 [d4 e4] [f4 gb4] ~!2
[~ c5]!4 [~ a4]!2 [[c4 ~] [d4 ~]] [[eb4 ~] [e4 ~]]
[~ c5]!4 [~ eb5]!2 [g4*2 [f4 ~]] [[e4 ~] [d4 ~]]
>\`,
// bass
\`<
c3!7 a3 f3!2
e3!2 ~!4
c3!7 a3 f3!2
e3!2 ~!4
f3!2 e3!2 d3!2 ~!2
f3!2 e3!2 ab3!2 ~!2
>\`
.legato(.5)
).fast(2).note()`;
export const randomBells = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
samples({
bell: { c6: 'https://freesound.org/data/previews/411/411089_5121236-lq.mp3' },
bass: { d2: 'https://freesound.org/data/previews/608/608286_13074022-lq.mp3' }
})
stack(
// bells
"0".euclidLegato(3,8)
.echo(3, 1/16, .5)
.add(rand.range(0,12))
.velocity(rand.range(.5,1))
.legato(rand.range(.4,3))
.scale(cat('D minor pentatonic')).note()
.s('bell').gain(.6).delay(.2).delaytime(1/3).delayfeedback(.8),
// bass
"<D2 A2 G2 F2>".euclidLegato(6,8,1).note().s('bass').clip(1).gain(.8)
)
.slow(6)
.pianoroll({minMidi:20,maxMidi:120,background:'transparent'})
`;
export const waa2 = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
n(
"a4 [a3 c3] a3 c3"
.sub("<7 12 5 12>".slow(2))
.off(1/4,x=>x.add(7))
.off(1/8,x=>x.add(12))
)
.slow(2)
.legato(sine.range(0.3, 2).slow(28))
.s("sawtooth square".fast(2))
.cutoff(cosine.range(500,4000).slow(16))
.gain(.5)
.room(.5)
`;
export const hyperpop = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
const lfo = cosine.slow(15);
const lfo2 = sine.slow(16);
const filter1 = x=>x.cutoff(lfo2.range(300,3000));
const filter2 = x=>x.hcutoff(lfo.range(1000,6000)).cutoff(4000)
const scales = cat('D3 major', 'G3 major').slow(8)
samples({
bd: '344/344757_1676145-lq.mp3',
sn: '387/387186_7255534-lq.mp3',
hh: '561/561241_12517458-lq.mp3',
hh2:'44/44944_236326-lq.mp3',
hh3: '44/44944_236326-lq.mp3',
}, 'https://freesound.org/data/previews/')
stack(
"-7 0 -7 7".struct("x(5,8,2)").fast(2).sub(7)
.scale(scales)
.n()
.s("sawtooth,square")
.gain(.3).attack(0.01).decay(0.1).sustain(.5)
.apply(filter1),
"~@3 [<2 3>,<4 5>]"
.echo(4,1/16,.7)
.scale(scales)
.n()
.s('square').gain(.7)
.attack(0.01).decay(0.1).sustain(0)
.apply(filter1),
"6 4 2".add(14)
.superimpose(sub("5"))
.fast(1).euclidLegato(3,8)
.mask("<1 0@7>")
.fast(2)
.echo(32, 1/8, .8)
.scale(scales)
.n()
.s("sawtooth")
.gain(sine.range(.1,.4).slow(8))
.attack(.001).decay(.2).sustain(0)
.apply(filter2)
).stack(
stack(
"bd <~@7 [~ bd]>".fast(2),
"~ sn",
"[~ hh3]*2"
).s().fast(2).gain(.7)
).slow(2)
// strudel disable-highlighting`;
export const festivalOfFingers3 = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
"[-7*3],0,2,6,[8 7]"
.echoWith(4,1/4, (x,n)=>x
.add(n*7)
.velocity(1/(n+1))
.legato(1/(n+1)))
.velocity(perlin.range(.5,.9).slow(8))
.stack("[22 25]*3"
.legato(sine.range(.5,2).slow(8))
.velocity(sine.range(.4,.8).slow(5))
.echo(4,1/12,.5))
.scale(cat('D dorian','G mixolydian','C dorian','F mixolydian'))
.legato(1)
.slow(2)
.note().piano()
//.pianoroll({maxMidi:160})`;
export const meltingsubmarine = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
samples({
bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav','bd/BT0A0DA.wav','bd/BT0A0D3.wav','bd/BT0A0D0.wav','bd/BT0A0A7.wav'],
sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'],
hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
}, 'github:tidalcycles/Dirt-Samples/master/');
stack(
s("bd,[~ <sd!3 sd(3,4,2)>],hh(3,4)") // drums
.speed(perlin.range(.7,.9)) // random sample speed variation
//.hush()
,"<a1 b1*2 a1(3,8) e2>" // bassline
.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
.n() // wrap in "n"
.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
//.hush()
,"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings('lefthand') // chords
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice
.add(perlin.range(0,.5)) // random pitch variation
.n() // wrap in "n"
.s('sawtooth') // waveform
.gain(.16) // turn down
.cutoff(500) // fixed cutoff
.attack(1) // slowly fade in
//.hush()
,"a4 c5 <e6 a6>".struct("x(5,8)")
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice
.add(perlin.range(0,.5)) // random pitch variation
.n() // wrap in "n"
.decay(.1).sustain(0) // make notes short
.s('triangle') // waveform
.degradeBy(perlin.range(0,.5)) // randomly controlled random removal :)
.echoWith(4,.125,(x,n)=>x.gain(.15*1/(n+1))) // echo notes
//.hush()
)
.slow(3/2)`;
export const outroMusic = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
samples({
bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav','bd/BT0A0DA.wav','bd/BT0A0D3.wav','bd/BT0A0D0.wav','bd/BT0A0A7.wav'],
sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'],
hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
perc: ['perc/002_perc2.wav'],
}, 'github:tidalcycles/Dirt-Samples/master/');
"C^7 Am7 Dm7 G7".slow(2).voicings('lefthand')
.stack("0@6 [<1 2> <2 0> 1]@2".scale('C5 major'))
.n().slow(4)
.s('0040_FluidR3_GM_sf2_file')
.color('steelblue')
.stack(
"<-7 ~@2 [~@2 -7] -9 ~@2 [~@2 -9] -10!2 ~ [~@2 -10] -5 ~ [-3 -2 -10]@2>*2".scale('C3 major')
.n().s('sawtooth').color('brown')
)
.attack(0.05).decay(.1).sustain(.7)
.cutoff(perlin.range(800,2000))
.gain(.3)
.stack(
s("<bd!3 [bd ~ bd]> sd,hh*3,~@5 <perc perc*3>")
.speed(perlin.range(.9,1.1))
.n(3).color('gray')
).slow(3/2)
//.pianoroll({autorange:1,vertical:1,fold:0})
`;
export const bassFuge = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
samples({ flbass: ['00_c2_finger_long_neck.wav','01_c2_finger_short_neck.wav','02_c2_finger_long_bridge.wav','03_c2_finger_short_bridge.wav','04_c2_pick_long.wav','05_c2_pick_short.wav','06_c2_palm_mute.wav'] },
'github:cleary/samples-flbass/main/')
samples({
bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav','bd/BT0A0DA.wav','bd/BT0A0D3.wav','bd/BT0A0D0.wav','bd/BT0A0A7.wav'],
sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'],
hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
}, 'github:tidalcycles/Dirt-Samples/master/');
note("<8(3,8) <7 7*2> [4 5@3] 8>".sub(1) // sub 1 -> 1-indexed
.layer(
x=>x,
x=>x.add(7).color('steelblue')
.off(1/8,x=>x.add("2,4").off(1/8,x=>x.add(5).echo(4,.125,.5)))
.slow(2),
).scale('A1 minor'))
.s("flbass").n(0)
.gain(.3)
.cutoff(sine.slow(7).range(200,4000))
.resonance(10)
//.hcutoff(400)
.clip(1)
.stack(s("bd:1*2,~ sd:0,[~ hh:0]*2"))
.pianoroll({vertical:1})`;
export const chop = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
samples({ p: 'https://cdn.freesound.org/previews/648/648433_11943129-lq.mp3' })
s("p")
.loopAt(32)
.chop(128)
.jux(rev)
.shape(.4)
.decay(.1)
.sustain(.6)
`;
export const delay = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
stack(
s("bd <sd cp>")
.delay("<0 .5>")
.delaytime(".16 | .33")
.delayfeedback(".6 | .8")
).sometimes(x=>x.speed("-1"))`;
export const orbit = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
stack(
s("bd <sd cp>")
.delay(.5)
.delaytime(.33)
.delayfeedback(.6),
s("hh*2")
.delay(.8)
.delaytime(.08)
.delayfeedback(.7)
.orbit(2)
).sometimes(x=>x.speed("-1"))`;
export const belldub = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
samples({ bell: {b4:'https://cdn.freesound.org/previews/339/339809_5121236-lq.mp3'}})
// "Hand Bells, B, Single.wav" by InspectorJ (www.jshaw.co.uk) of Freesound.org
stack(
// bass
note("[0 ~] [2 [0 2]] [4 4*2] [[4 ~] [2 ~] 0@2]".scale('g1 dorian').superimpose(x=>x.add(.02)))
.s('sawtooth').cutoff(200).resonance(20).gain(.15).shape(.6).release(.05),
// perc
s("[~ hh]*4").room("0 0.5".fast(2)).end(perlin.range(0.02,1)),
s("mt lt ht").struct("x(3,8)").fast(2).gain(.5).room(.5).sometimes(x=>x.speed(".5")),
s("misc:2").speed(1).delay(.5).delaytime(1/3).gain(.4),
// chords
note("[~ Gm7] ~ [~ Dm7] ~".voicings('lefthand').superimpose(x=>x.add(.1)))
.s('sawtooth').gain(.5)
.cutoff(perlin.range(400,3000).slow(8))
.decay(perlin.range(0.05,.2)).sustain(0)
.delay(.9).room(1),
// blips
note(
"0 5 4 2".iter(4)
.off(1/3, add(7))
.scale('g4 dorian')
).s('square').cutoff(2000).decay(.03).sustain(0)
.degradeBy(.2)
.orbit(2).delay(.2).delaytime(".33 | .6 | .166 | .25")
.room(1).gain(.5).mask("<0 1>/8"),
// bell
note(rand.range(0,12).struct("x(5,8)").scale('g2 minor pentatonic')).s('bell').begin(.05)
.delay(.2).degradeBy(.4).gain(.4)
.mask("<1 0>/8")
).slow(5)`;
export const dinofunk = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
samples({bass:'https://cdn.freesound.org/previews/614/614637_2434927-hq.mp3',
dino:{b4:'https://cdn.freesound.org/previews/316/316403_5123851-hq.mp3'}})
setVoicingRange('lefthand', ['c3','a4'])
stack(
s('bass').loopAt(8).clip(1),
s("bd*2, ~ sd,hh*4"),
note("Abm7".voicings('lefthand').struct("x(3,8,1)".slow(2))),
"0 1 2 3".scale('ab4 minor pentatonic')
.superimpose(x=>x.add(.1))
.sometimes(x=>x.add(12))
.note().s('sawtooth')
.cutoff(sine.range(400,2000).slow(16)).gain(.8)
.decay(perlin.range(.05,.2)).sustain(0)
.delay(sine.range(0,.5).slow(32))
.degradeBy(.4).room(1),
note("<b4 eb4>").s('dino').delay(.8).slow(8).room(.5)
)`;
export const sampleDemo = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
stack(
// percussion
s("[woodblock:1 woodblock:2*2] snare_rim:0,gong/8,brakedrum:1(3,8),~@3 cowbell:3")
.sometimes(x=>x.speed(2)),
// melody
note("<0 4 1 3 2>".off(".25 | .125",add(2)).scale('D3 hirajoshi'))
.s("clavisynth").gain(.2).delay(.25).jux(rev)
.degradeBy(sine.range(0,.5).slow(32)),
// bass
note("<0@3 <2(3,8) 3(3,8)>>".scale('D1 hirajoshi'))
.s('psaltery_pluck').gain(.6).clip(1)
.release(.1).room(.5)
)`;
export const holyflute = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
"c3 eb3(3,8) c4/2 g3*2"
.superimpose(
x=>x.slow(2).add(12),
x=>x.slow(4).sub(5)
).add("<0 1>/16")
.note().s('ocarina_vib').clip(1)
.release(.1).room(1).gain(.2)
.color("salmon | orange | darkseagreen")
.pianoroll({fold:0,autorange:0,vertical:0,cycles:12,smear:0,minMidi:40})
`;
export const flatrave = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
stack(
s("bd*2,~ [cp,sd]").bank('RolandTR909'),
s("hh:1*4").sometimes(fast("2"))
.rarely(x=>x.speed(".5").delay(.5))
.end(perlin.range(0.02,.05).slow(8))
.bank('RolandTR909').room(.5)
.gain("0.4,0.4(5,8)"),
note("<0 2 5 3>".scale('G1 minor')).struct("x(5,8)")
.s('sawtooth').decay(.1).sustain(0),
note("<G4 A4 Bb4 A4>,Bb3,D3").struct("~ x*2").s('square').clip(1)
.cutoff(sine.range(500,4000).slow(16)).resonance(10)
.decay(sine.slow(15).range(.05,.2)).sustain(0)
.room(.5).gain(.3).delay(.2).mask("<0 1@3>/8"),
"0 5 3 2".sometimes(slow(2)).off(1/8,add(5)).scale('G4 minor').note()
.decay(.05).sustain(0).delay(.2).degradeBy(.5).mask("<0 1>/16")
)`;
export const amensister = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
await samples('github:tidalcycles/Dirt-Samples/master')
stack(
// amen
n("0 1 2 3 4 5 6 7")
.sometimes(x=>x.ply(2))
.rarely(x=>x.speed("2 | -2"))
.sometimesBy(.4, x=>x.delay(".5"))
.s("amencutup")
.slow(2)
.room(.5)
,
// bass
sine.add(saw.slow(4)).range(0,7).segment(8)
.superimpose(x=>x.add(.1))
.scale('G0 minor').note()
.s("sawtooth").decay(.1).sustain(0)
.gain(.4).cutoff(perlin.range(300,3000).slow(8)).resonance(10)
.degradeBy("0 0.1 .5 .1")
.rarely(add(note("12")))
,
// chord
note("Bb3,D4".superimpose(x=>x.add(.2)))
.s('sawtooth').cutoff(1000).struct("<~@3 [~ x]>")
.decay(.05).sustain(.0).delay(.8).delaytime(.125).room(.8)
,
// alien
s("breath").room(1).shape(.6).chop(16).rev().mask("<x ~@7>")
,
n("0 1").s("east").delay(.5).degradeBy(.8).speed(rand.range(.5,1.5))
).reset("<x@7 x(5,8)>")`;
export const juxUndTollerei = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
note("c3 eb3 g3 bb3").palindrome()
.s('sawtooth')
.jux(x=>x.rev().color('green').s('sawtooth'))
.off(1/4, x=>x.add(note("<7 12>/2")).slow(2).late(.005).s('triangle'))
//.delay(.5)
.fast(1).cutoff(sine.range(200,2000).slow(8))
.decay(.05).sustain(0)
.room(.6)
.delay(.5).delaytime(.1).delayfeedback(.4)
.pianoroll()`;
export const csoundDemo = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
await loadCsound\`
instr CoolSynth
iduration = p3
ifreq = p4
igain = p5
ioct = octcps(ifreq)
kpwm = oscili(.05, 8)
asig = vco2(igain, ifreq, 4, .5 + kpwm)
asig += vco2(igain, ifreq * 2)
idepth = 2
acut = transegr:a(0, .005, 0, idepth, .06, -4.2, 0.001, .01, -4.2, 0) ; filter envelope
asig = zdf_2pole(asig, cpsoct(ioct + acut + 2), 0.5)
iattack = .01
isustain = .5
idecay = .1
irelease = .1
asig *= linsegr:a(0, iattack, 1, idecay, isustain, iduration, isustain, irelease, 0)
out(asig, asig)
endin\`
"<0 2 [4 6](3,4,1) 3*2>"
.off(1/4, add(2))
.off(1/2, add(6))
.scale('D minor')
.note()
//.pianoroll()
.csound('CoolSynth')`;
export const loungeSponge = `
// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
// livecode.orc by Steven Yi
await loadOrc('github:kunstmusik/csound-live-code/master/livecode.orc')
stack(
note("<C^7 A7 Dm7 Fm7>/2".voicings('lefthand'))
.cutoff(sine.range(500,2000).round().slow(16))
.euclidLegato(3,8).csound('FM1')
,
note("<C2 A1 D2 F2>/2").ply(8).csound('Bass').gain("1 4 1 4")
,
note("0 7 [4 3] 2".fast(2/3).off(".25 .125",add("<2 4 -3 -1>"))
.slow(2).scale('A4 minor'))
.legato(.25).csound('SynHarp')
,
s("bd*2,[~ hh]*2,~ cp").bank('RolandTR909')
)`;
export const arpoon = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// "Arpoon" by Felix Roos
await samples('github:tidalcycles/Dirt-Samples/master')
"<<Am7 C^7> C7 F^7 [Fm7 E7b9]>".voicings('lefthand')
.arp("[0,3] 2 [1,3] 2".fast(3)).legato(2)
.add(perlin.range(0,0.2)).sub("<0 -12>/8")
.note().cutoff(perlin.range(500,4000)).resonance(12)
.gain("<.5 .8>*16")
.decay(.16).sustain(0.5)
.delay(.2)
.room(.5).pan(sine.range(.3,.6))
.s('piano').clip(1)
.stack("<<A1 C2>!2 F2 [F2 E2]>".add.out("0 -5".fast(2)).add("0,.12").note().s('sawtooth').clip(1).cutoff(300))
.slow(4)
.stack(s("bd*4, [~ [hh hh? hh?]]*2,~ [sd ~ [sd:2? bd?]]").arp("0|1").bank('RolandTR909').gain(.5).slow(2))
`;