Merge branch 'main' into docs

This commit is contained in:
Felix Roos 2022-12-30 17:43:54 +01:00
commit 6961388545
14 changed files with 249 additions and 97 deletions

16
my-patterns/README.md Normal file
View File

@ -0,0 +1,16 @@
# my-patterns
This directory can be used to save your own patterns.
0. fork the strudel repo
1. Save one or more .txt files here.
2. and run `npm run repl`
3. open `http://localhost:3000/my-patterns` !
## deploy
1. in your fork, go to settings -> pages and select "Github Actions" as source
2. edit `website/public/CNAME` to contain `<your-username>.github.io/strudel`
3. edit `website/astro.config.mjs` to use site: `<your-username>.github.io/strudel` and base `/strudel`
4. go to Actions -> "Build and Deploy" and click "Run workflow"
5. view your patterns at `<your-username>.github.io/strudel/my-patterns`

View File

@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
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/>. 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 { Pattern, toMidi, getDrawContext, freqToMidi } from './index.mjs'; import { Pattern, toMidi, getDrawContext, freqToMidi, isNote } from './index.mjs';
const scale = (normalized, min, max) => normalized * (max - min) + min; const scale = (normalized, min, max) => normalized * (max - min) + min;
const getValue = (e) => { const getValue = (e) => {
@ -12,13 +12,19 @@ const getValue = (e) => {
if (typeof e.value !== 'object') { if (typeof e.value !== 'object') {
value = { value }; value = { value };
} }
let { note, n, freq } = value; let { note, n, freq, s } = value;
if (freq) { if (freq) {
note = freqToMidi(freq); return freqToMidi(freq);
} }
value = note ?? n ?? e.value; note = note ?? n;
if (typeof value === 'string') { if (typeof note === 'string') {
value = toMidi(value); return toMidi(note);
}
if (typeof note === 'number') {
return note;
}
if (s) {
return '_' + s;
} }
return value; return value;
}; };
@ -143,7 +149,7 @@ Pattern.prototype.pianoroll = function ({
maxMidi = max; maxMidi = max;
valueExtent = maxMidi - minMidi + 1; valueExtent = maxMidi - minMidi + 1;
} }
foldValues = values.sort((a, b) => a - b); foldValues = values.sort((a, b) => String(a).localeCompare(String(b)));
barThickness = fold ? valueAxis / foldValues.length : valueAxis / valueExtent; barThickness = fold ? valueAxis / foldValues.length : valueAxis / valueExtent;
}, },
}, },
@ -215,7 +221,8 @@ export function pianoroll({
maxMidi = max; maxMidi = max;
valueExtent = maxMidi - minMidi + 1; valueExtent = maxMidi - minMidi + 1;
} }
foldValues = values.sort((a, b) => a - b); // foldValues = values.sort((a, b) => a - b);
foldValues = values.sort((a, b) => String(a).localeCompare(String(b)));
barThickness = fold ? valueAxis / foldValues.length : valueAxis / valueExtent; barThickness = fold ? valueAxis / foldValues.length : valueAxis / valueExtent;
ctx.fillStyle = background; ctx.fillStyle = background;

View File

@ -32,7 +32,7 @@ function peg$padEnd(str, targetLength, padString) {
} }
peg$SyntaxError.prototype.format = function(sources) { peg$SyntaxError.prototype.format = function(sources) {
var str = "peg error: " + this.message; var str = "Error: " + this.message;
if (this.location) { if (this.location) {
var src = null; var src = null;
var k; var k;
@ -271,8 +271,8 @@ function peg$parse(input, options) {
var peg$f4 = function(a) { return { weight: a} }; var peg$f4 = function(a) { return { weight: a} };
var peg$f5 = function(a) { return { replicate: a } }; var peg$f5 = function(a) { return { replicate: a } };
var peg$f6 = function(p, s, r) { return { operator : { type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r || 0 } } } }; var peg$f6 = function(p, s, r) { return { operator : { type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r || 0 } } } };
var peg$f7 = function(a) { return { operator : { type_: "stretch", arguments_ :{ amount:a } } } }; var peg$f7 = function(a) { return { operator : { type_: "stretch", arguments_ :{ amount:a, type: 'slow' } } } };
var peg$f8 = function(a) { return { operator : { type_: "stretch", arguments_ :{ amount:"1/"+a } } } }; var peg$f8 = function(a) { return { operator : { type_: "stretch", arguments_ :{ amount:a, type: 'fast' } } } };
var peg$f9 = function(a) { return { operator : { type_: "fixed-step", arguments_ :{ amount:a } } } }; var peg$f9 = function(a) { return { operator : { type_: "fixed-step", arguments_ :{ amount:a } } } };
var peg$f10 = function(a) { return { operator : { type_: "degradeBy", arguments_ :{ amount:(a? a : 0.5) } } } }; var peg$f10 = function(a) { return { operator : { type_: "degradeBy", arguments_ :{ amount:(a? a : 0.5) } } } };
var peg$f11 = function(s, o) { return new ElementStub(s, o);}; var peg$f11 = function(s, o) { return new ElementStub(s, o);};

View File

@ -116,10 +116,10 @@ slice_bjorklund = "(" ws p:number ws comma ws s:number ws comma? ws r:number? ws
{ return { operator : { type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r || 0 } } } } { return { operator : { type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r || 0 } } } }
slice_slow = "/"a:number slice_slow = "/"a:number
{ return { operator : { type_: "stretch", arguments_ :{ amount:a } } } } { return { operator : { type_: "stretch", arguments_ :{ amount:a, type: 'slow' } } } }
slice_fast = "*"a:number slice_fast = "*"a:number
{ return { operator : { type_: "stretch", arguments_ :{ amount:"1/"+a } } } } { return { operator : { type_: "stretch", arguments_ :{ amount:a, type: 'fast' } } } }
slice_fixed_step = "%"a:number slice_fixed_step = "%"a:number
{ return { operator : { type_: "fixed-step", arguments_ :{ amount:a } } } } { return { operator : { type_: "fixed-step", arguments_ :{ amount:a } } } }

View File

@ -23,8 +23,12 @@ const applyOptions = (parent) => (pat, i) => {
if (operator) { if (operator) {
switch (operator.type_) { switch (operator.type_) {
case 'stretch': { case 'stretch': {
const speed = Fraction(operator.arguments_.amount).inverse(); const legalTypes = ['fast', 'slow'];
return reify(pat).fast(speed); const { type, amount } = operator.arguments_;
if (!legalTypes.includes(type)) {
throw new Error(`mini: stretch: type must be one of ${legalTypes.join('|')} but got ${type}`);
}
return reify(pat)[type](amount);
} }
case 'bjorklund': case 'bjorklund':
return pat.euclid(operator.arguments_.pulse, operator.arguments_.step, operator.arguments_.rotation); return pat.euclid(operator.arguments_.pulse, operator.arguments_.step, operator.arguments_.rotation);
@ -74,32 +78,32 @@ function resolveReplications(ast) {
// could this be made easier?! // could this be made easier?!
ast.source_ = ast.source_.map((child) => { ast.source_ = ast.source_.map((child) => {
const { replicate, ...options } = child.options_ || {}; const { replicate, ...options } = child.options_ || {};
if (replicate) { if (!replicate) {
return { return child;
...child, }
options_: { ...options, weight: replicate }, return {
source_: { ...child,
type_: 'pattern', options_: { ...options, weight: replicate },
arguments_: { source_: {
alignment: 'h', type_: 'pattern',
}, arguments_: {
source_: [ alignment: 'h',
{ },
type_: 'element', source_: [
source_: child.source_, {
location_: child.location_, type_: 'element',
options_: { source_: child.source_,
operator: { location_: child.location_,
type_: 'stretch', options_: {
arguments_: { amount: Fraction(replicate).inverse().toString() }, operator: {
}, type_: 'stretch',
arguments_: { amount: replicate, type: 'fast' },
}, },
}, },
], },
}, ],
}; },
} };
return child;
}); });
} }

View File

@ -339,8 +339,8 @@ exports[`renders tunes > tune: blippyRhodes 1`] = `
"[ 2/3 → 43/60 | note:G3 s:rhodes clip:1 room:0.5 delay:0.3 delayfeedback:0.4 delaytime:0.08333333333333333 gain:0.5 ]", "[ 2/3 → 43/60 | note:G3 s:rhodes clip:1 room:0.5 delay:0.3 delayfeedback:0.4 delaytime:0.08333333333333333 gain:0.5 ]",
"[ 5/6 → 53/60 | note:G3 s:rhodes clip:1 room:0.5 delay:0.3 delayfeedback:0.4 delaytime:0.08333333333333333 gain:0.5 ]", "[ 5/6 → 53/60 | note:G3 s:rhodes clip:1 room:0.5 delay:0.3 delayfeedback:0.4 delaytime:0.08333333333333333 gain:0.5 ]",
"[ (0/1 → 2/3) ⇝ 4/3 | note:c2 gain:0.3 s:sawtooth cutoff:600 ]", "[ (0/1 → 2/3) ⇝ 4/3 | note:c2 gain:0.3 s:sawtooth cutoff:600 ]",
"[ 0/1 ⇜ (2/3 → 1/1) ⇝ 4/3 | note:c2 gain:0.3 s:sawtooth cutoff:600 ]",
"[ (0/1 → 2/3) ⇝ 4/3 | note:36.02 gain:0.3 s:sawtooth cutoff:600 ]", "[ (0/1 → 2/3) ⇝ 4/3 | note:36.02 gain:0.3 s:sawtooth cutoff:600 ]",
"[ 0/1 ⇜ (2/3 → 1/1) ⇝ 4/3 | note:c2 gain:0.3 s:sawtooth cutoff:600 ]",
"[ 0/1 ⇜ (2/3 → 1/1) ⇝ 4/3 | note:36.02 gain:0.3 s:sawtooth cutoff:600 ]", "[ 0/1 ⇜ (2/3 → 1/1) ⇝ 4/3 | note:36.02 gain:0.3 s:sawtooth cutoff:600 ]",
] ]
`; `;
@ -8127,10 +8127,10 @@ exports[`renders tunes > tune: loungeSponge 1`] = `
exports[`renders tunes > tune: meltingsubmarine 1`] = ` exports[`renders tunes > tune: meltingsubmarine 1`] = `
[ [
"[ (0/1 → 1/1) ⇝ 3/2 | s:bd speed:0.7519542165100574 ]", "[ (0/1 → 1/1) ⇝ 3/2 | s:bd:5 speed:0.7519542165100574 ]",
"[ (3/4 → 1/1) ⇝ 3/2 | s:sd speed:0.7931522866332671 ]", "[ (3/4 → 1/1) ⇝ 3/2 | s:sd:1 speed:0.7931522866332671 ]",
"[ 3/8 → 3/4 | s:hh speed:0.7285963821098448 ]", "[ 3/8 → 3/4 | s:hh27 speed:0.7285963821098448 ]",
"[ (3/4 → 1/1) ⇝ 9/8 | s:hh speed:0.77531205091027 ]", "[ (3/4 → 1/1) ⇝ 9/8 | s:hh27 speed:0.77531205091027 ]",
"[ (0/1 → 1/1) ⇝ 3/2 | n:33.129885541275144 decay:0.15 sustain:0 s:sawtooth gain:0.4 cutoff:3669.6267869262615 ]", "[ (0/1 → 1/1) ⇝ 3/2 | n:33.129885541275144 decay:0.15 sustain:0 s:sawtooth gain:0.4 cutoff:3669.6267869262615 ]",
"[ (0/1 → 1/1) ⇝ 3/2 | n:33.17988554127514 decay:0.15 sustain:0 s:sawtooth gain:0.4 cutoff:3669.6267869262615 ]", "[ (0/1 → 1/1) ⇝ 3/2 | n:33.17988554127514 decay:0.15 sustain:0 s:sawtooth gain:0.4 cutoff:3669.6267869262615 ]",
"[ (0/1 → 1/1) ⇝ 3/2 | n:55.129885541275144 s:sawtooth gain:0.16 cutoff:500 attack:1 ]", "[ (0/1 → 1/1) ⇝ 3/2 | n:55.129885541275144 s:sawtooth gain:0.16 cutoff:500 attack:1 ]",

View File

@ -31,6 +31,7 @@ export default defineConfig({
tailwind(), tailwind(),
], ],
site: `https://strudel.tidalcycles.org`, site: `https://strudel.tidalcycles.org`,
base: '',
}); });
/* /*

View File

@ -0,0 +1,14 @@
---
import HeadCommon from '../components/HeadCommon.astro';
import { Repl } from '../repl/Repl.jsx';
---
<html>
<head>
<HeadCommon />
<title>Strudel REPL</title>
</head>
<body>
<Repl client:only="react" embedded />
</body>
</html>

View File

@ -0,0 +1,29 @@
import { createCanvas } from 'canvas';
import { pianoroll } from '@strudel.cycles/core';
import { evaluate } from '@strudel.cycles/transpiler';
import '../../../../test/runtime.mjs';
import { getMyPatterns } from './list.json';
export async function get({ params, request }) {
const patterns = await getMyPatterns();
const { name } = params;
const tune = patterns[name];
const { pattern } = await evaluate(tune);
const haps = pattern.queryArc(0, 4);
const canvas = createCanvas(800, 800);
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',
};
}
export async function getStaticPaths() {
const patterns = await getMyPatterns();
return Object.keys(patterns).map((name) => ({
params: {
name,
},
}));
}

View File

@ -0,0 +1,32 @@
---
import { getMyPatterns } from './list.json';
import { Content } from '../../../../my-patterns/README.md';
const myPatterns = await getMyPatterns();
---
<body class="bg-slate-800">
{
Object.keys(myPatterns).length === 0 && (
<div class="prose prose-invert p-2">
<Content />
</div>
)
}
<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(myPatterns).map(([name, tune]) => (
<a
class="rounded-md bg-slate-900 hover:bg-slate-700 cursor-pointer relative"
href={`../#${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={`./${name}.png`} />
</a>
))
}
</div>
</body>

View File

@ -0,0 +1,15 @@
export function getMyPatterns() {
const my = import.meta.glob('../../../../my-patterns/**', { as: 'raw', eager: true });
return Object.fromEntries(
Object.entries(my) //
.filter(([name]) => name.endsWith('.txt')) //
.map(([name, raw]) => [name.split('/').slice(-1), raw]), //
);
}
export async function get() {
const all = await getMyPatterns();
return {
body: JSON.stringify(all),
};
}

View File

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

View File

@ -22,6 +22,7 @@ import { Footer } from './Footer';
import { Header } from './Header'; import { Header } from './Header';
import { prebake } from './prebake.mjs'; import { prebake } from './prebake.mjs';
import * as tunes from './tunes.mjs'; import * as tunes from './tunes.mjs';
import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
initAudioOnFirstClick(); initAudioOnFirstClick();
@ -101,7 +102,8 @@ const { code: randomTune, name } = getRandomTune();
export const ReplContext = createContext(null); export const ReplContext = createContext(null);
export function Repl() { export function Repl({ embedded = false }) {
const isEmbedded = embedded || window.location !== window.parent.location;
const [view, setView] = useState(); // codemirror view const [view, setView] = useState(); // codemirror view
const [lastShared, setLastShared] = useState(); const [lastShared, setLastShared] = useState();
const [activeFooter, setActiveFooter] = useState(''); const [activeFooter, setActiveFooter] = useState('');
@ -232,6 +234,7 @@ export function Repl() {
} }
}; };
const context = { const context = {
embedded,
started, started,
pending, pending,
isDirty, isDirty,
@ -269,7 +272,16 @@ export function Repl() {
{error && ( {error && (
<div className="text-red-500 p-4 bg-lineblack animate-pulse">{error.message || 'Unknown Error :-/'}</div> <div className="text-red-500 p-4 bg-lineblack animate-pulse">{error.message || 'Unknown Error :-/'}</div>
)} )}
<Footer context={context} /> {isEmbedded && !started && (
<button
onClick={() => handleTogglePlay()}
className="text-white text-2xl fixed left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%] z-[1000] m-auto p-4 bg-black rounded-md flex items-center space-x-2"
>
<PlayCircleIcon className="w-6 h-6" />
<span>play</span>
</button>
)}
{!isEmbedded && <Footer context={context} />}
</div> </div>
</ReplContext.Provider> </ReplContext.Provider>
); );

View File

@ -24,7 +24,7 @@ stack(
"Ab5 [F5@2 C5] C6@2", "Ab5 [F5@2 C5] C6@2",
"A5 [F5@2 C5] [D5@2 F5] F5", "A5 [F5@2 C5] [D5@2 F5] F5",
"[C5@2 F5] [Bb5 A5 G5] F5@2" "[C5@2 F5] [Bb5 A5 G5] F5@2"
), ).color('#FFEBB5'),
seq( seq(
"[F4,Bb4,D5] [[D4,G4,Bb4]@2 [Bb3,D4,F4]] [[G3,C4,E4]@2 [[Ab3,F4] [A3,Gb4]]] [Bb3,E4,G4]", "[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, Bb3, Db3] [F3, Bb3, Db3]]",
@ -43,7 +43,7 @@ stack(
"[~ [Ab3, B3, F4] [Ab3, B3, F4]] [~ [Ab3, B3, F4] [Ab3, B3, F4]] [~ [G3, Bb3, F4] [G3, Bb3, F4]] [~ [G3, Bb3, E4] [G3, Bb3, E4]]", "[~ [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, 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]]" "[~ [F3, Bb3, D4] [F3, Bb3, D4]] [~ [F3, Bb3, C4] [F3, Bb3, C4]] [~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, A3, C4] [F3, A3, C4]]"
), ).color('#54C571'),
seq( seq(
"[G3 G3 C3 E3]", "[G3 G3 C3 E3]",
"[F2 D2 G2 C2]", "[F2 D2 G2 C2]",
@ -62,8 +62,9 @@ stack(
"[Ab2 Ab2 G2 [C2 D2 E2]]", "[Ab2 Ab2 G2 [C2 D2 E2]]",
"[F2 A2 Bb2 B2]", "[F2 A2 Bb2 B2]",
"[G2 C2 F2 F2]" "[G2 C2 F2 F2]"
) ).color('#0077C9')
).note().slow(51); ).note().slow(51)
//.pianoroll({fold:1})
`; `;
export const giantSteps = `// John Coltrane - Giant Steps export const giantSteps = `// John Coltrane - Giant Steps
@ -76,22 +77,23 @@ stack(
"[D5 Bb4] [G4 Eb4] F#4 [G4 F4]", "[D5 Bb4] [G4 Eb4] F#4 [G4 F4]",
"Bb4 [B4 A4] D5 [D#5 C#5]", "Bb4 [B4 A4] D5 [D#5 C#5]",
"F#5 [G5 F5] Bb5 [F#5 F#5]", "F#5 [G5 F5] Bb5 [F#5 F#5]",
), ).color('#F8E71C'),
// chords // chords
seq( seq(
"[B^7 D7] [G^7 Bb7] Eb^7 [Am7 D7]", "[B^7 D7] [G^7 Bb7] Eb^7 [Am7 D7]",
"[G^7 Bb7] [Eb^7 F#7] B^7 [Fm7 Bb7]", "[G^7 Bb7] [Eb^7 F#7] B^7 [Fm7 Bb7]",
"Eb^7 [Am7 D7] G^7 [C#m7 F#7]", "Eb^7 [Am7 D7] G^7 [C#m7 F#7]",
"B^7 [Fm7 Bb7] Eb^7 [C#m7 F#7]" "B^7 [Fm7 Bb7] Eb^7 [C#m7 F#7]"
).voicings('lefthand'), ).voicings('lefthand').color('#7ED321'),
// bass // bass
seq( seq(
"[B2 D2] [G2 Bb2] [Eb2 Bb3] [A2 D2]", "[B2 D2] [G2 Bb2] [Eb2 Bb3] [A2 D2]",
"[G2 Bb2] [Eb2 F#2] [B2 F#2] [F2 Bb2]", "[G2 Bb2] [Eb2 F#2] [B2 F#2] [F2 Bb2]",
"[Eb2 Bb2] [A2 D2] [G2 D2] [C#2 F#2]", "[Eb2 Bb2] [A2 D2] [G2 D2] [C#2 F#2]",
"[B2 F#2] [F2 Bb2] [Eb2 Bb3] [C#2 F#2]" "[B2 F#2] [F2 Bb2] [Eb2 Bb3] [C#2 F#2]"
) ).color('#00B8D4')
).slow(20).note()`; ).slow(20).note()
//.pianoroll({fold:1})`;
export const zeldasRescue = `// Koji Kondo - Princess Zelda's Rescue export const zeldasRescue = `// Koji Kondo - Princess Zelda's Rescue
stack( stack(
@ -101,7 +103,8 @@ stack(
[B3@2 D4] [A3@2 [G3 A3]] [B3@2 D4] [A3] [B3@2 D4] [A3@2 [G3 A3]] [B3@2 D4] [A3]
[B3@2 D4] [A4@2 G4] D5@2 [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@2] [C5@2 [B4 A4]] [[B4 A4] E4@2]
[D5@2 [C5 B4]] [[C5 B4] G4 C5] [G5] [~ ~ B3]\`, [D5@2 [C5 B4]] [[C5 B4] G4 C5] [G5] [~ ~ B3]\`
.color('#9C7C38'),
// bass // bass
\`[[C2 G2] E3@2] [[C2 G2] F#3@2] [[C2 G2] E3@2] [[C2 G2] 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] [[B1 D3] G3@2] [[Bb1 Db3] G3@2] [[A1 C3] G3@2] [[D2 C3] F#3@2]
@ -109,17 +112,19 @@ stack(
[[B1 D3] G3@2] [[Bb1 Db3] G3@2] [[A1 C3] G3@2] [[D2 C3] 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] [[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]]\` [[F2 C3] E3@2] [[E2 B2] D3@2] [[Eb2 Bb2] Db3@2] [[D2 A2] C3 [F3,G2]]\`
.color('#4C4646')
).transpose(12).slow(48) ).transpose(12).slow(48)
.superimpose(x=>x.add(0.06)) // add slightly detuned voice .superimpose(x=>x.add(0.06)) // add slightly detuned voice
.note() .note()
.gain(.1) .gain(.1)
.s('triangle') .s('triangle')
.room(1) .room(1)
`; //.pianoroll({fold:1})`;
export const caverave = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ export const caverave = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos // by Felix Roos
const keys = x => x.s('sawtooth').cutoff(1200).gain(.5).attack(0).decay(.16).sustain(.3).release(.1); const keys = x => x.s('sawtooth').cutoff(1200).gain(.5)
.attack(0).decay(.16).sustain(.3).release(.1);
const drums = stack( const drums = stack(
s("bd*2").mask("<x@7 ~>/8").gain(.8), s("bd*2").mask("<x@7 ~>/8").gain(.8),
@ -129,26 +134,31 @@ const drums = stack(
const thru = (x) => x.transpose("<0 1>/8").transpose(-1); const thru = (x) => x.transpose("<0 1>/8").transpose(-1);
const synths = stack( const synths = stack(
"<eb4 d4 c4 b3>/2".scale(timeCat([3,'C minor'],[1,'C melodic minor']) "<eb4 d4 c4 b3>/2"
.scale(timeCat([3,'C minor'],[1,'C melodic minor'])
.slow(8)).struct("[~ x]*2") .slow(8)).struct("[~ x]*2")
.layer( .layer(
x=>x.scaleTranspose(0).early(0), x=>x.scaleTranspose(0).early(0),
x=>x.scaleTranspose(2).early(1/8), x=>x.scaleTranspose(2).early(1/8),
x=>x.scaleTranspose(7).early(1/4), x=>x.scaleTranspose(7).early(1/4),
x=>x.scaleTranspose(8).early(3/8) x=>x.scaleTranspose(8).early(3/8)
).apply(thru).note().apply(keys).mask("<~ x>/16"), ).apply(thru).note().apply(keys).mask("<~ x>/16")
.color('darkseagreen'),
note("<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".apply(thru)) note("<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".apply(thru))
.struct("[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2".fast(2)) .struct("[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2".fast(2))
.s('sawtooth').attack(0.001).decay(0.2).sustain(1).cutoff(500), .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') .color('brown'),
"<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) .apply(thru).every(2, early(1/8)).note().apply(keys).sustain(0)
.delay(.4).delaytime(.12) .delay(.4).delaytime(.12)
.mask("<x@7 ~>/8".early(1/4)) .mask("<x@7 ~>/8".early(1/4))
) )
stack( stack(
drums.fast(2), drums.fast(2).color('tomato'),
synths synths
).slow(2)`; ).slow(2)
//.pianoroll({fold:1})`;
export const sampleDrums = `samples({ export const sampleDrums = `samples({
bd: 'bd/BT0A0D0.wav', bd: 'bd/BT0A0D0.wav',
@ -157,10 +167,11 @@ export const sampleDrums = `samples({
}, 'https://loophole-letters.vercel.app/samples/tidal/') }, 'https://loophole-letters.vercel.app/samples/tidal/')
stack( stack(
"<bd!3 bd(3,4,2)>", "<bd!3 bd(3,4,2)>".color('#F5A623'),
"hh*4", "hh*4".color('#673AB7'),
"~ <sn!3 sn(3,4,1)>" "~ <sn!3 sn(3,4,1)>".color('#4CAF50')
).s() ).s()
.pianoroll({fold:1})
`; `;
export const barryHarris = `// adapted from a Barry Harris excercise export const barryHarris = `// adapted from a Barry Harris excercise
@ -170,6 +181,7 @@ export const barryHarris = `// adapted from a Barry Harris excercise
.transpose("<0 1 2 1>/8") .transpose("<0 1 2 1>/8")
.slow(2) .slow(2)
.note().piano() .note().piano()
.color('#00B8D4')
`; `;
export const blippyRhodes = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ export const blippyRhodes = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
@ -192,7 +204,7 @@ samples({
const scales = cat('C major', 'C mixolydian', 'F lydian', ['F minor', cat('Db major','Db mixolydian')]) const scales = cat('C major', 'C mixolydian', 'F lydian', ['F minor', cat('Db major','Db mixolydian')])
stack( stack(
s("<bd sn> <hh hh*2 hh*3>"), s("<bd sn> <hh hh*2 hh*3>").color('#00B8D4'),
"<g4 c5 a4 [ab4 <eb5 f5>]>" "<g4 c5 a4 [ab4 <eb5 f5>]>"
.scale(scales) .scale(scales)
.struct("x*8") .struct("x*8")
@ -205,13 +217,14 @@ stack(
.room(.5) .room(.5)
.delay(.3) .delay(.3)
.delayfeedback(.4) .delayfeedback(.4)
.delaytime(1/12).gain(.5), .delaytime(1/12).gain(.5).color('#7ED321'),
"<c2 c3 f2 [[F2 C2] db2]>" "<c2 c3 f2 [[F2 C2] db2]>"
.legato("<1@3 [.3 1]>") .legato("<1@3 [.3 1]>")
.slow(2).superimpose(x=>x.add(.02)) .slow(2).superimpose(x=>x.add(.02))
.note().gain(.3) .note().gain(.3)
.s('sawtooth').cutoff(600), .s('sawtooth').cutoff(600).color('#F8E71C'),
).fast(3/2)`; ).fast(3/2)
//.pianoroll({fold:1})`;
export const wavyKalimba = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ export const wavyKalimba = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos // by Felix Roos
@ -505,13 +518,9 @@ export const festivalOfFingers3 = `// licensed with CC BY-NC-SA 4.0 https://crea
export const meltingsubmarine = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ export const meltingsubmarine = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos // by Felix Roos
samples({ await samples('github:tidalcycles/Dirt-Samples/master/')
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( stack(
s("bd,[~ <sd!3 sd(3,4,2)>],hh(3,4)") // drums s("bd:5,[~ <sd:1!3 sd:1(3,4,2)>],hh27(3,4)") // drums
.speed(perlin.range(.7,.9)) // random sample speed variation .speed(perlin.range(.7,.9)) // random sample speed variation
//.hush() //.hush()
,"<a1 b1*2 a1(3,8) e2>" // bassline ,"<a1 b1*2 a1(3,8) e2>" // bassline