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/>.
*/
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;

View File

@ -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);};

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 } } } }
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 } } } }

View File

@ -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;
},
],
},
};
});
}

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 ]",
"[ 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 ]",

View File

@ -31,6 +31,7 @@ export default defineConfig({
tailwind(),
],
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 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>

View File

@ -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>
);

View File

@ -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