mirror of
https://github.com/eliasstepanik/strudel.git
synced 2026-01-11 21:58:37 +00:00
Merge branch 'main' into docs
This commit is contained in:
commit
6961388545
16
my-patterns/README.md
Normal file
16
my-patterns/README.md
Normal 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`
|
||||
@ -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/>.
|
||||
*/
|
||||
|
||||
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 getValue = (e) => {
|
||||
@ -12,13 +12,19 @@ const getValue = (e) => {
|
||||
if (typeof e.value !== 'object') {
|
||||
value = { value };
|
||||
}
|
||||
let { note, n, freq } = value;
|
||||
let { note, n, freq, s } = value;
|
||||
if (freq) {
|
||||
note = freqToMidi(freq);
|
||||
return freqToMidi(freq);
|
||||
}
|
||||
value = note ?? n ?? e.value;
|
||||
if (typeof value === 'string') {
|
||||
value = toMidi(value);
|
||||
note = note ?? n;
|
||||
if (typeof note === 'string') {
|
||||
return toMidi(note);
|
||||
}
|
||||
if (typeof note === 'number') {
|
||||
return note;
|
||||
}
|
||||
if (s) {
|
||||
return '_' + s;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
@ -143,7 +149,7 @@ Pattern.prototype.pianoroll = function ({
|
||||
maxMidi = max;
|
||||
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;
|
||||
},
|
||||
},
|
||||
@ -215,7 +221,8 @@ export function pianoroll({
|
||||
maxMidi = max;
|
||||
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;
|
||||
|
||||
ctx.fillStyle = background;
|
||||
|
||||
@ -32,7 +32,7 @@ function peg$padEnd(str, targetLength, padString) {
|
||||
}
|
||||
|
||||
peg$SyntaxError.prototype.format = function(sources) {
|
||||
var str = "peg error: " + this.message;
|
||||
var str = "Error: " + this.message;
|
||||
if (this.location) {
|
||||
var src = null;
|
||||
var k;
|
||||
@ -271,8 +271,8 @@ function peg$parse(input, options) {
|
||||
var peg$f4 = function(a) { return { weight: 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$f7 = function(a) { return { operator : { type_: "stretch", arguments_ :{ amount:a } } } };
|
||||
var peg$f8 = function(a) { return { operator : { type_: "stretch", arguments_ :{ amount:"1/"+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:a, type: 'fast' } } } };
|
||||
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$f11 = function(s, o) { return new ElementStub(s, o);};
|
||||
|
||||
@ -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 } } } }
|
||||
|
||||
slice_slow = "/"a:number
|
||||
{ return { operator : { type_: "stretch", arguments_ :{ amount:a } } } }
|
||||
{ return { operator : { type_: "stretch", arguments_ :{ amount:a, type: 'slow' } } } }
|
||||
|
||||
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
|
||||
{ return { operator : { type_: "fixed-step", arguments_ :{ amount:a } } } }
|
||||
|
||||
@ -23,8 +23,12 @@ const applyOptions = (parent) => (pat, i) => {
|
||||
if (operator) {
|
||||
switch (operator.type_) {
|
||||
case 'stretch': {
|
||||
const speed = Fraction(operator.arguments_.amount).inverse();
|
||||
return reify(pat).fast(speed);
|
||||
const legalTypes = ['fast', 'slow'];
|
||||
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':
|
||||
return pat.euclid(operator.arguments_.pulse, operator.arguments_.step, operator.arguments_.rotation);
|
||||
@ -74,32 +78,32 @@ function resolveReplications(ast) {
|
||||
// could this be made easier?!
|
||||
ast.source_ = ast.source_.map((child) => {
|
||||
const { replicate, ...options } = child.options_ || {};
|
||||
if (replicate) {
|
||||
return {
|
||||
...child,
|
||||
options_: { ...options, weight: replicate },
|
||||
source_: {
|
||||
type_: 'pattern',
|
||||
arguments_: {
|
||||
alignment: 'h',
|
||||
},
|
||||
source_: [
|
||||
{
|
||||
type_: 'element',
|
||||
source_: child.source_,
|
||||
location_: child.location_,
|
||||
options_: {
|
||||
operator: {
|
||||
type_: 'stretch',
|
||||
arguments_: { amount: Fraction(replicate).inverse().toString() },
|
||||
},
|
||||
if (!replicate) {
|
||||
return child;
|
||||
}
|
||||
return {
|
||||
...child,
|
||||
options_: { ...options, weight: replicate },
|
||||
source_: {
|
||||
type_: 'pattern',
|
||||
arguments_: {
|
||||
alignment: 'h',
|
||||
},
|
||||
source_: [
|
||||
{
|
||||
type_: 'element',
|
||||
source_: child.source_,
|
||||
location_: child.location_,
|
||||
options_: {
|
||||
operator: {
|
||||
type_: 'stretch',
|
||||
arguments_: { amount: replicate, type: 'fast' },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
return child;
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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 ]",
|
||||
"[ 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 → 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 → 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 ]",
|
||||
]
|
||||
`;
|
||||
@ -8127,10 +8127,10 @@ exports[`renders tunes > tune: loungeSponge 1`] = `
|
||||
|
||||
exports[`renders tunes > tune: meltingsubmarine 1`] = `
|
||||
[
|
||||
"[ (0/1 → 1/1) ⇝ 3/2 | s:bd speed:0.7519542165100574 ]",
|
||||
"[ (3/4 → 1/1) ⇝ 3/2 | s:sd speed:0.7931522866332671 ]",
|
||||
"[ 3/8 → 3/4 | s:hh speed:0.7285963821098448 ]",
|
||||
"[ (3/4 → 1/1) ⇝ 9/8 | s:hh speed:0.77531205091027 ]",
|
||||
"[ (0/1 → 1/1) ⇝ 3/2 | s:bd:5 speed:0.7519542165100574 ]",
|
||||
"[ (3/4 → 1/1) ⇝ 3/2 | s:sd:1 speed:0.7931522866332671 ]",
|
||||
"[ 3/8 → 3/4 | s:hh27 speed:0.7285963821098448 ]",
|
||||
"[ (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.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 ]",
|
||||
|
||||
@ -31,6 +31,7 @@ export default defineConfig({
|
||||
tailwind(),
|
||||
],
|
||||
site: `https://strudel.tidalcycles.org`,
|
||||
base: '',
|
||||
});
|
||||
|
||||
/*
|
||||
|
||||
14
website/src/pages/embed.astro
Normal file
14
website/src/pages/embed.astro
Normal 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>
|
||||
29
website/src/pages/my-patterns/[name].png.js
Normal file
29
website/src/pages/my-patterns/[name].png.js
Normal 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,
|
||||
},
|
||||
}));
|
||||
}
|
||||
32
website/src/pages/my-patterns/index.astro
Normal file
32
website/src/pages/my-patterns/index.astro
Normal 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>
|
||||
15
website/src/pages/my-patterns/list.json.js
Normal file
15
website/src/pages/my-patterns/list.json.js
Normal 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),
|
||||
};
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
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 PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
|
||||
import SparklesIcon from '@heroicons/react/20/solid/SparklesIcon';
|
||||
@ -9,10 +9,9 @@ import React, { useContext } from 'react';
|
||||
// import { ReplContext } from './Repl';
|
||||
import './Repl.css';
|
||||
|
||||
const isEmbedded = window.location !== window.parent.location;
|
||||
|
||||
export function Header({ context }) {
|
||||
const {
|
||||
embedded,
|
||||
started,
|
||||
pending,
|
||||
isDirty,
|
||||
@ -25,32 +24,37 @@ export function Header({ context }) {
|
||||
isZen,
|
||||
setIsZen,
|
||||
} = context;
|
||||
const isEmbedded = embedded || window.location !== window.parent.location;
|
||||
// useContext(ReplContext)
|
||||
|
||||
return (
|
||||
<header
|
||||
id="header"
|
||||
className={cx(
|
||||
'py-1 flex-none w-full md:flex text-black justify-between z-[100] text-lg select-none sticky top-0',
|
||||
!isZen && 'bg-header',
|
||||
'py-1 flex-none w-full text-black justify-between z-[100] text-lg select-none sticky top-0',
|
||||
!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
|
||||
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
|
||||
onClick={() => {
|
||||
if (isEmbedded) window.open(window.location.href.replace('embed', ''));
|
||||
}}
|
||||
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',
|
||||
'text-white font-bold flex space-x-2 items-center',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cx('mt-[1px]', started && 'animate-spin', 'cursor-pointer')}
|
||||
onClick={() => setIsZen((z) => !z)}
|
||||
onClick={() => !isEmbedded && setIsZen((z) => !z)}
|
||||
>
|
||||
🌀
|
||||
</div>
|
||||
@ -65,12 +69,13 @@ export function Header({ context }) {
|
||||
<div className="flex max-w-full overflow-auto text-white ">
|
||||
<button
|
||||
onClick={handleTogglePlay}
|
||||
title={started ? 'stop' : 'play'}
|
||||
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 className={cx('flex items-center space-x-1', isEmbedded ? '' : 'w-16')}>
|
||||
{started ? <StopCircleIcon className="w-6 h-6" /> : <PlayCircleIcon className="w-6 h-6" />}
|
||||
{!isEmbedded && <span>{started ? 'stop' : 'play'}</span>}
|
||||
</span>
|
||||
) : (
|
||||
<>loading...</>
|
||||
@ -78,46 +83,54 @@ export function Header({ context }) {
|
||||
</button>
|
||||
<button
|
||||
onClick={handleUpdate}
|
||||
title="update"
|
||||
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>
|
||||
{/* <CommandLineIcon className="w-6 h-6" /> */}
|
||||
<ArrowPathIcon className="w-6 h-6" />
|
||||
{!isEmbedded && <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" />
|
||||
<button
|
||||
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>
|
||||
</button>
|
||||
)}
|
||||
{!isEmbedded && (
|
||||
<button
|
||||
title="share"
|
||||
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" />
|
||||
<LinkIcon className="w-6 h-6" />
|
||||
<span>share{lastShared && lastShared === (activeCode || code) ? 'd!' : ''}</span>
|
||||
</button>
|
||||
)}
|
||||
{!isEmbedded && (
|
||||
<a
|
||||
title="learn"
|
||||
href="./learn/getting-started"
|
||||
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>
|
||||
</a>
|
||||
)}
|
||||
{isEmbedded && (
|
||||
{/* {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>
|
||||
)}
|
||||
@ -130,10 +143,10 @@ export function Header({ context }) {
|
||||
}}
|
||||
title="Reset"
|
||||
>
|
||||
💔 reset
|
||||
💔
|
||||
</a>
|
||||
</button>
|
||||
)}
|
||||
)} */}
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
|
||||
@ -22,6 +22,7 @@ import { Footer } from './Footer';
|
||||
import { Header } from './Header';
|
||||
import { prebake } from './prebake.mjs';
|
||||
import * as tunes from './tunes.mjs';
|
||||
import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
|
||||
|
||||
initAudioOnFirstClick();
|
||||
|
||||
@ -101,7 +102,8 @@ const { code: randomTune, name } = getRandomTune();
|
||||
|
||||
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 [lastShared, setLastShared] = useState();
|
||||
const [activeFooter, setActiveFooter] = useState('');
|
||||
@ -232,6 +234,7 @@ export function Repl() {
|
||||
}
|
||||
};
|
||||
const context = {
|
||||
embedded,
|
||||
started,
|
||||
pending,
|
||||
isDirty,
|
||||
@ -269,7 +272,16 @@ export function Repl() {
|
||||
{error && (
|
||||
<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>
|
||||
</ReplContext.Provider>
|
||||
);
|
||||
|
||||
@ -24,7 +24,7 @@ stack(
|
||||
"Ab5 [F5@2 C5] C6@2",
|
||||
"A5 [F5@2 C5] [D5@2 F5] F5",
|
||||
"[C5@2 F5] [Bb5 A5 G5] F5@2"
|
||||
),
|
||||
).color('#FFEBB5'),
|
||||
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]]",
|
||||
@ -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]]",
|
||||
"[~ [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]]"
|
||||
),
|
||||
).color('#54C571'),
|
||||
seq(
|
||||
"[G3 G3 C3 E3]",
|
||||
"[F2 D2 G2 C2]",
|
||||
@ -62,8 +62,9 @@ stack(
|
||||
"[Ab2 Ab2 G2 [C2 D2 E2]]",
|
||||
"[F2 A2 Bb2 B2]",
|
||||
"[G2 C2 F2 F2]"
|
||||
)
|
||||
).note().slow(51);
|
||||
).color('#0077C9')
|
||||
).note().slow(51)
|
||||
//.pianoroll({fold:1})
|
||||
`;
|
||||
|
||||
export const giantSteps = `// John Coltrane - Giant Steps
|
||||
@ -76,22 +77,23 @@ stack(
|
||||
"[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]",
|
||||
),
|
||||
).color('#F8E71C'),
|
||||
// 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'),
|
||||
).voicings('lefthand').color('#7ED321'),
|
||||
// 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()`;
|
||||
).color('#00B8D4')
|
||||
).slow(20).note()
|
||||
//.pianoroll({fold:1})`;
|
||||
|
||||
export const zeldasRescue = `// Koji Kondo - Princess Zelda's Rescue
|
||||
stack(
|
||||
@ -101,7 +103,8 @@ stack(
|
||||
[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]\`,
|
||||
[D5@2 [C5 B4]] [[C5 B4] G4 C5] [G5] [~ ~ B3]\`
|
||||
.color('#9C7C38'),
|
||||
// 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]
|
||||
@ -109,17 +112,19 @@ stack(
|
||||
[[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]]\`
|
||||
.color('#4C4646')
|
||||
).transpose(12).slow(48)
|
||||
.superimpose(x=>x.add(0.06)) // add slightly detuned voice
|
||||
.note()
|
||||
.gain(.1)
|
||||
.s('triangle')
|
||||
.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/
|
||||
// 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(
|
||||
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 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")
|
||||
.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"),
|
||||
).apply(thru).note().apply(keys).mask("<~ x>/16")
|
||||
.color('darkseagreen'),
|
||||
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')
|
||||
.s('sawtooth').attack(0.001).decay(0.2).sustain(1).cutoff(500)
|
||||
.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)
|
||||
.delay(.4).delaytime(.12)
|
||||
.mask("<x@7 ~>/8".early(1/4))
|
||||
)
|
||||
stack(
|
||||
drums.fast(2),
|
||||
drums.fast(2).color('tomato'),
|
||||
synths
|
||||
).slow(2)`;
|
||||
).slow(2)
|
||||
//.pianoroll({fold:1})`;
|
||||
|
||||
export const sampleDrums = `samples({
|
||||
bd: 'bd/BT0A0D0.wav',
|
||||
@ -157,10 +167,11 @@ export const sampleDrums = `samples({
|
||||
}, 'https://loophole-letters.vercel.app/samples/tidal/')
|
||||
|
||||
stack(
|
||||
"<bd!3 bd(3,4,2)>",
|
||||
"hh*4",
|
||||
"~ <sn!3 sn(3,4,1)>"
|
||||
"<bd!3 bd(3,4,2)>".color('#F5A623'),
|
||||
"hh*4".color('#673AB7'),
|
||||
"~ <sn!3 sn(3,4,1)>".color('#4CAF50')
|
||||
).s()
|
||||
.pianoroll({fold:1})
|
||||
`;
|
||||
|
||||
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")
|
||||
.slow(2)
|
||||
.note().piano()
|
||||
.color('#00B8D4')
|
||||
`;
|
||||
|
||||
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')])
|
||||
|
||||
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>]>"
|
||||
.scale(scales)
|
||||
.struct("x*8")
|
||||
@ -205,13 +217,14 @@ stack(
|
||||
.room(.5)
|
||||
.delay(.3)
|
||||
.delayfeedback(.4)
|
||||
.delaytime(1/12).gain(.5),
|
||||
.delaytime(1/12).gain(.5).color('#7ED321'),
|
||||
"<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)`;
|
||||
.s('sawtooth').cutoff(600).color('#F8E71C'),
|
||||
).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/
|
||||
// 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/
|
||||
// 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/');
|
||||
await samples('github:tidalcycles/Dirt-Samples/master/')
|
||||
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
|
||||
//.hush()
|
||||
,"<a1 b1*2 a1(3,8) e2>" // bassline
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user