Pattern .define + .bootstrap
+ seperate parse / evaluate + add Pattern.edit + move string hacks to evaluate + automate eval scoping with globalThis + add random tune button + mini: handle angle brackets + add chordBass + fix old tunes
This commit is contained in:
parent
c225c058bc
commit
c11c217baf
9 changed files with 259 additions and 221 deletions
|
|
@ -4,13 +4,12 @@ import cx from './cx';
|
||||||
import * as Tone from 'tone';
|
import * as Tone from 'tone';
|
||||||
import useCycle from './useCycle';
|
import useCycle from './useCycle';
|
||||||
import type { Pattern } from './types';
|
import type { Pattern } from './types';
|
||||||
import defaultTune from './tunes';
|
import * as tunes from './tunes';
|
||||||
import * as parser from './parse';
|
import { evaluate } from './evaluate';
|
||||||
import CodeMirror from './CodeMirror';
|
import CodeMirror from './CodeMirror';
|
||||||
import hot from '../public/hot';
|
import hot from '../public/hot';
|
||||||
import { isNote } from 'tone';
|
import { isNote } from 'tone';
|
||||||
import { useWebMidi } from './midi';
|
import { useWebMidi } from './midi';
|
||||||
const { parse } = parser;
|
|
||||||
|
|
||||||
const [_, codeParam] = window.location.href.split('#');
|
const [_, codeParam] = window.location.href.split('#');
|
||||||
const decoded = atob(codeParam || '');
|
const decoded = atob(codeParam || '');
|
||||||
|
|
@ -23,7 +22,7 @@ const getHotCode = async () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultSynth = new Tone.PolySynth().toDestination();
|
const defaultSynth = new Tone.PolySynth().chain(new Tone.Gain(0.5), Tone.Destination);
|
||||||
defaultSynth.set({
|
defaultSynth.set({
|
||||||
oscillator: { type: 'triangle' },
|
oscillator: { type: 'triangle' },
|
||||||
envelope: {
|
envelope: {
|
||||||
|
|
@ -31,9 +30,17 @@ defaultSynth.set({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function getRandomTune() {
|
||||||
|
const allTunes = Object.values(tunes);
|
||||||
|
const randomItem = (arr: any[]) => arr[Math.floor(Math.random() * arr.length)];
|
||||||
|
return randomItem(allTunes);
|
||||||
|
}
|
||||||
|
|
||||||
|
const randomTune = getRandomTune();
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [mode, setMode] = useState<string>('javascript');
|
const [mode, setMode] = useState<string>('javascript');
|
||||||
const [code, setCode] = useState<string>(decoded || defaultTune);
|
const [code, setCode] = useState<string>(decoded || randomTune);
|
||||||
const [log, setLog] = useState('');
|
const [log, setLog] = useState('');
|
||||||
const logBox = useRef<any>();
|
const logBox = useRef<any>();
|
||||||
const [error, setError] = useState<Error>();
|
const [error, setError] = useState<Error>();
|
||||||
|
|
@ -125,7 +132,7 @@ function App() {
|
||||||
}
|
}
|
||||||
// normal mode
|
// normal mode
|
||||||
try {
|
try {
|
||||||
const parsed = parse(_code);
|
const parsed = evaluate(_code);
|
||||||
// need arrow function here! otherwise if user returns a function, react will think it's a state reducer
|
// need arrow function here! otherwise if user returns a function, react will think it's a state reducer
|
||||||
// only first time, then need ctrl+enter
|
// only first time, then need ctrl+enter
|
||||||
setPattern(() => parsed.pattern);
|
setPattern(() => parsed.pattern);
|
||||||
|
|
@ -164,17 +171,31 @@ function App() {
|
||||||
<img src={logo} className="Tidal-logo w-16 h-16" alt="logo" />
|
<img src={logo} className="Tidal-logo w-16 h-16" alt="logo" />
|
||||||
<h1 className="text-2xl">Strudel REPL</h1>
|
<h1 className="text-2xl">Strudel REPL</h1>
|
||||||
</div>
|
</div>
|
||||||
{window.location.href.includes('http://localhost:8080') && (
|
<div className="flex space-x-4">
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (isHot || confirm('Really switch? You might loose your current pattern..')) {
|
const _code = getRandomTune();
|
||||||
setIsHot((h) => !h);
|
console.log('tune',_code); // uncomment this to debug when random code fails
|
||||||
}
|
setCode(_code);
|
||||||
|
const parsed = evaluate(_code);
|
||||||
|
// Tone.Transport.cancel(Tone.Transport.seconds);
|
||||||
|
setActivePattern(parsed.pattern);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isHot ? '🔥' : ' '} toggle hot mode
|
🎲 random tune
|
||||||
</button>
|
</button>
|
||||||
)}
|
{window.location.href.includes('http://localhost:8080') && (
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
if (isHot || confirm('Really switch? You might loose your current pattern..')) {
|
||||||
|
setIsHot((h) => !h);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
🔥 toggle hot mode
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<section className="grow flex flex-col text-gray-100">
|
<section className="grow flex flex-col text-gray-100">
|
||||||
<div className="grow relative">
|
<div className="grow relative">
|
||||||
|
|
|
||||||
38
repl/src/evaluate.ts
Normal file
38
repl/src/evaluate.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import * as strudel from '../../strudel.mjs';
|
||||||
|
import './tone';
|
||||||
|
import './midi';
|
||||||
|
import './voicings';
|
||||||
|
import './tonal';
|
||||||
|
import './groove';
|
||||||
|
import shapeshifter from './shapeshifter';
|
||||||
|
import { minify } from './parse';
|
||||||
|
|
||||||
|
// this will add all methods from definedMethod to strudel + connect all the partial application stuff
|
||||||
|
const bootstrapped: any = { ...strudel, ...strudel.Pattern.prototype.bootstrap() };
|
||||||
|
// console.log('bootstrapped',bootstrapped.transpose(2).transpose);
|
||||||
|
|
||||||
|
function hackLiteral(literal, names, func) {
|
||||||
|
names.forEach((name) => {
|
||||||
|
Object.defineProperty(literal.prototype, name, {
|
||||||
|
get: function () {
|
||||||
|
return func(String(this));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// with this, you can do 'c2 [eb2 g2]'.mini.fast(2) or 'c2 [eb2 g2]'.m.fast(2),
|
||||||
|
hackLiteral(String, ['mini', 'm'], bootstrapped.mini); // comment out this line if you panic
|
||||||
|
hackLiteral(String, ['pure', 'p'], bootstrapped.pure); // comment out this line if you panic
|
||||||
|
|
||||||
|
Object.assign(globalThis, bootstrapped); // this will add contents of bootstrapped to scope (used by eval)
|
||||||
|
|
||||||
|
export const evaluate: any = (code: string) => {
|
||||||
|
const shapeshifted = shapeshifter(code); // transform syntactically correct js code to semantically usable code
|
||||||
|
const pattern = minify(eval(shapeshifted)); // eval and minify (if user entered a string)
|
||||||
|
if (pattern?.constructor?.name !== 'Pattern') {
|
||||||
|
const message = `got "${typeof pattern}" instead of pattern`;
|
||||||
|
throw new Error(message + (typeof pattern === 'function' ? ', did you forget to call a function?' : '.'));
|
||||||
|
}
|
||||||
|
return { mode: 'javascript', pattern: pattern };
|
||||||
|
};
|
||||||
|
|
@ -6,3 +6,5 @@ const Pattern = _Pattern as any;
|
||||||
Pattern.prototype.groove = function (groove) {
|
Pattern.prototype.groove = function (groove) {
|
||||||
return groove.fmap(() => (v) => v).appLeft(this);
|
return groove.fmap(() => (v) => v).appLeft(this);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Pattern.prototype.define('groove', (groove, pat) => pat.groove(groove), { composable: true });
|
||||||
|
|
|
||||||
|
|
@ -1,67 +1,10 @@
|
||||||
import * as krill from '../krill-parser';
|
import * as krill from '../krill-parser';
|
||||||
import * as strudel from '../../strudel.mjs';
|
import * as strudel from '../../strudel.mjs';
|
||||||
import { Scale, Note, Interval } from '@tonaljs/tonal';
|
import { Scale, Note, Interval } from '@tonaljs/tonal';
|
||||||
import './tone';
|
|
||||||
import './midi';
|
|
||||||
import './voicings';
|
|
||||||
import './tonal';
|
|
||||||
import * as tonalStuff from './tonal';
|
|
||||||
import './groove';
|
|
||||||
import * as toneStuff from './tone';
|
|
||||||
import shapeshifter from './shapeshifter';
|
|
||||||
|
|
||||||
// even if some functions are not used, we need them to be available in eval
|
const { pure, Pattern, Fraction, stack, slowcat, sequence, timeCat, silence } = strudel;
|
||||||
const {
|
|
||||||
Fraction,
|
|
||||||
TimeSpan,
|
|
||||||
Hap,
|
|
||||||
Pattern,
|
|
||||||
pure,
|
|
||||||
stack,
|
|
||||||
slowcat,
|
|
||||||
fastcat,
|
|
||||||
cat,
|
|
||||||
timeCat,
|
|
||||||
sequence,
|
|
||||||
polymeter,
|
|
||||||
pm,
|
|
||||||
polyrhythm,
|
|
||||||
pr,
|
|
||||||
// reify,
|
|
||||||
silence,
|
|
||||||
fast,
|
|
||||||
slow,
|
|
||||||
early,
|
|
||||||
late,
|
|
||||||
rev,
|
|
||||||
add,
|
|
||||||
sub,
|
|
||||||
mul,
|
|
||||||
div,
|
|
||||||
union,
|
|
||||||
every,
|
|
||||||
when,
|
|
||||||
off,
|
|
||||||
jux,
|
|
||||||
append,
|
|
||||||
} = strudel;
|
|
||||||
const { autofilter, filter, gain } = toneStuff;
|
|
||||||
|
|
||||||
const { transpose } = tonalStuff;
|
|
||||||
|
|
||||||
function reify(thing: any) {
|
|
||||||
if (thing?.constructor?.name === 'Pattern') {
|
|
||||||
return thing;
|
|
||||||
}
|
|
||||||
return pure(thing);
|
|
||||||
}
|
|
||||||
|
|
||||||
function minify(thing: any) {
|
|
||||||
if (typeof thing === 'string') {
|
|
||||||
return mini(thing);
|
|
||||||
}
|
|
||||||
return reify(thing);
|
|
||||||
}
|
|
||||||
|
|
||||||
const applyOptions = (parent: any) => (pat: any, i: number) => {
|
const applyOptions = (parent: any) => (pat: any, i: number) => {
|
||||||
const ast = parent.source_[i];
|
const ast = parent.source_[i];
|
||||||
|
|
@ -94,12 +37,20 @@ export function patternifyAST(ast: any): any {
|
||||||
switch (ast.type_) {
|
switch (ast.type_) {
|
||||||
case 'pattern':
|
case 'pattern':
|
||||||
const children = ast.source_.map(patternifyAST).map(applyOptions(ast));
|
const children = ast.source_.map(patternifyAST).map(applyOptions(ast));
|
||||||
if (ast.arguments_.alignment === 'v') {
|
const alignment = ast.arguments_.alignment;
|
||||||
|
if (alignment === 'v') {
|
||||||
return stack(...children);
|
return stack(...children);
|
||||||
}
|
}
|
||||||
const weightedChildren = ast.source_.some((child) => !!child.options_?.weight);
|
const weightedChildren = ast.source_.some((child) => !!child.options_?.weight);
|
||||||
|
if (!weightedChildren && alignment === 't') {
|
||||||
|
return slowcat(...children);
|
||||||
|
}
|
||||||
if (weightedChildren) {
|
if (weightedChildren) {
|
||||||
return timeCat(...ast.source_.map((child, i) => [child.options_?.weight || 1, children[i]]));
|
const pat = timeCat(...ast.source_.map((child, i) => [child.options_?.weight || 1, children[i]]));
|
||||||
|
if (alignment === 't') {
|
||||||
|
return pat._slow(children.length); // timecat + slow
|
||||||
|
}
|
||||||
|
return pat;
|
||||||
}
|
}
|
||||||
return sequence(...children);
|
return sequence(...children);
|
||||||
case 'element':
|
case 'element':
|
||||||
|
|
@ -142,32 +93,13 @@ export function patternifyAST(ast: any): any {
|
||||||
|
|
||||||
// mini notation only (wraps in "")
|
// mini notation only (wraps in "")
|
||||||
export const mini = (...strings: string[]) => {
|
export const mini = (...strings: string[]) => {
|
||||||
const pattern = sequence(
|
const pats = strings.map((str) => {
|
||||||
...strings.map((str) => {
|
const ast = krill.parse(`"${str}"`);
|
||||||
const ast = krill.parse(`"${str}"`);
|
return patternifyAST(ast);
|
||||||
// console.log('ast', ast);
|
});
|
||||||
return patternifyAST(ast);
|
return sequence(...pats);
|
||||||
})
|
|
||||||
);
|
|
||||||
return pattern;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// shorthand for mini
|
|
||||||
const m = mini;
|
|
||||||
|
|
||||||
const hackStrings = () => {
|
|
||||||
const miniGetter = {
|
|
||||||
get: function () {
|
|
||||||
return mini(String(this));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
// with this, you can do 'c2 [eb2 g2]'.mini.fast(2) or 'c2 [eb2 g2]'.m.fast(2),
|
|
||||||
Object.defineProperty(String.prototype, 'mini', miniGetter);
|
|
||||||
Object.defineProperty(String.prototype, 'm', miniGetter);
|
|
||||||
};
|
|
||||||
|
|
||||||
hackStrings(); // comment out this line if you panic
|
|
||||||
|
|
||||||
// includes haskell style (raw krill parsing)
|
// includes haskell style (raw krill parsing)
|
||||||
export const h = (string: string) => {
|
export const h = (string: string) => {
|
||||||
const ast = krill.parse(string);
|
const ast = krill.parse(string);
|
||||||
|
|
@ -175,21 +107,22 @@ export const h = (string: string) => {
|
||||||
return patternifyAST(ast);
|
return patternifyAST(ast);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const parse: any = (code: string) => {
|
// shorthand for mini
|
||||||
let _pattern;
|
Pattern.prototype.define('mini', mini, { composable: true });
|
||||||
let mode;
|
Pattern.prototype.define('m', mini, { composable: true });
|
||||||
/* try {
|
Pattern.prototype.define('h', h, { composable: true });
|
||||||
_pattern = h(code);
|
|
||||||
mode = 'pegjs';
|
// TODO: move this to strudel?
|
||||||
} catch (err) { */
|
export function reify(thing: any) {
|
||||||
// code is not haskell like
|
if (thing?.constructor?.name === 'Pattern') {
|
||||||
mode = 'javascript';
|
return thing;
|
||||||
code = shapeshifter(code);
|
|
||||||
_pattern = minify(eval(code));
|
|
||||||
if (_pattern?.constructor?.name !== 'Pattern') {
|
|
||||||
const message = `got "${typeof _pattern}" instead of pattern`;
|
|
||||||
throw new Error(message + (typeof _pattern === 'function' ? ', did you forget to call a function?' : '.'));
|
|
||||||
}
|
}
|
||||||
/* } */
|
return pure(thing);
|
||||||
return { mode, pattern: _pattern };
|
}
|
||||||
};
|
|
||||||
|
export function minify(thing: any) {
|
||||||
|
if (typeof thing === 'string') {
|
||||||
|
return mini(thing);
|
||||||
|
}
|
||||||
|
return reify(thing);
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Note, Interval, Scale } from '@tonaljs/tonal';
|
import { Note, Interval, Scale } from '@tonaljs/tonal';
|
||||||
import { Pattern as _Pattern, curry, makeComposable } from '../../strudel.mjs';
|
import { Pattern as _Pattern } from '../../strudel.mjs';
|
||||||
|
|
||||||
const Pattern = _Pattern as any;
|
const Pattern = _Pattern as any;
|
||||||
|
|
||||||
|
|
@ -76,10 +76,6 @@ Pattern.prototype._transpose = function (intervalOrSemitones: string | number) {
|
||||||
};
|
};
|
||||||
|
|
||||||
// example: transpose(3).late(0.2) will be equivalent to compose(transpose(3), late(0.2))
|
// example: transpose(3).late(0.2) will be equivalent to compose(transpose(3), late(0.2))
|
||||||
export const transpose = curry(
|
|
||||||
(a, pat) => pat.transpose(a),
|
|
||||||
(partial) => makeComposable(partial)
|
|
||||||
);
|
|
||||||
// TODO: add Pattern.define(name, function, options) that handles all the meta programming stuff
|
// TODO: add Pattern.define(name, function, options) that handles all the meta programming stuff
|
||||||
// TODO: find out how to patternify this function when it's standalone
|
// TODO: find out how to patternify this function when it's standalone
|
||||||
// e.g. `stack(c3).superimpose(transpose(slowcat(7, 5)))` or
|
// e.g. `stack(c3).superimpose(transpose(slowcat(7, 5)))` or
|
||||||
|
|
@ -97,5 +93,6 @@ Pattern.prototype._scale = function (scale: string) {
|
||||||
return this._mapNotes((value) => ({ ...value, scale }));
|
return this._mapNotes((value) => ({ ...value, scale }));
|
||||||
};
|
};
|
||||||
|
|
||||||
Pattern.prototype.patternified = Pattern.prototype.patternified.concat(['transpose', 'scaleTranspose', 'scale']);
|
Pattern.prototype.define('transpose', (a, pat) => pat.transpose(a), { composable: true, patternified: true });
|
||||||
Object.assign(Pattern.prototype.composable, { transpose });
|
Pattern.prototype.define('scale', (a, pat) => pat.scale(a), { composable: true, patternified: true });
|
||||||
|
Pattern.prototype.define('scaleTranspose', (a, pat) => pat.scaleTranspose(a), { composable: true, patternified: true });
|
||||||
|
|
|
||||||
|
|
@ -89,3 +89,7 @@ Pattern.prototype.autofilter = function (g: number) {
|
||||||
};
|
};
|
||||||
|
|
||||||
Pattern.prototype.patternified = Pattern.prototype.patternified.concat(['synth', 'gain', 'filter']);
|
Pattern.prototype.patternified = Pattern.prototype.patternified.concat(['synth', 'gain', 'filter']);
|
||||||
|
|
||||||
|
Pattern.prototype.define('synth', (type, pat) => pat.synth(type), { composable: true, patternified: true });
|
||||||
|
Pattern.prototype.define('gain', (gain, pat) => pat.synth(gain), { composable: true, patternified: true });
|
||||||
|
Pattern.prototype.define('filter', (cutoff, pat) => pat.filter(cutoff), { composable: true, patternified: true });
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,6 @@ export const shapeShifted = `stack(
|
||||||
).rev()
|
).rev()
|
||||||
).slow(16)`;
|
).slow(16)`;
|
||||||
|
|
||||||
export const tetrisMidi = `${shapeShifted}.midi('IAC-Treiber Bus 1')`;
|
|
||||||
|
|
||||||
export const tetrisWithFunctions = `stack(sequence(
|
export const tetrisWithFunctions = `stack(sequence(
|
||||||
'e5', sequence('b4', 'c5'), 'd5', sequence('c5', 'b4'),
|
'e5', sequence('b4', 'c5'), 'd5', sequence('c5', 'b4'),
|
||||||
'a4', sequence('a4', 'c5'), 'e5', sequence('d5', 'c5'),
|
'a4', sequence('a4', 'c5'), 'e5', sequence('d5', 'c5'),
|
||||||
|
|
@ -57,10 +55,9 @@ export const tetrisWithFunctions = `stack(sequence(
|
||||||
'b1', 'b2', 'b1', 'b2', 'e2', 'e3', 'e2', 'e3',
|
'b1', 'b2', 'b1', 'b2', 'e2', 'e3', 'e2', 'e3',
|
||||||
'a1', 'a2', 'a1', 'a2', 'a1', 'a2', 'a1', 'a2',
|
'a1', 'a2', 'a1', 'a2', 'a1', 'a2', 'a1', 'a2',
|
||||||
)
|
)
|
||||||
)._slow(16)`;
|
).slow(16)`;
|
||||||
|
|
||||||
export const tetris = `stack(
|
export const tetris = `stack(
|
||||||
sequence(
|
|
||||||
mini(
|
mini(
|
||||||
'e5 [b4 c5] d5 [c5 b4]',
|
'e5 [b4 c5] d5 [c5 b4]',
|
||||||
'a4 [a4 c5] e5 [d5 c5]',
|
'a4 [a4 c5] e5 [d5 c5]',
|
||||||
|
|
@ -70,9 +67,7 @@ export const tetris = `stack(
|
||||||
'e5 [~ c5] e5 [d5 c5]',
|
'e5 [~ c5] e5 [d5 c5]',
|
||||||
'b4 [b4 c5] d5 e5',
|
'b4 [b4 c5] d5 e5',
|
||||||
'c5 a4 a4 ~'
|
'c5 a4 a4 ~'
|
||||||
)
|
),
|
||||||
),
|
|
||||||
sequence(
|
|
||||||
mini(
|
mini(
|
||||||
'e2 e3 e2 e3 e2 e3 e2 e3',
|
'e2 e3 e2 e3 e2 e3 e2 e3',
|
||||||
'a2 a3 a2 a3 a2 a3 a2 a3',
|
'a2 a3 a2 a3 a2 a3 a2 a3',
|
||||||
|
|
@ -82,14 +77,10 @@ export const tetris = `stack(
|
||||||
'c2 c3 c2 c3 c2 c3 c2 c3',
|
'c2 c3 c2 c3 c2 c3 c2 c3',
|
||||||
'b1 b2 b1 b2 e2 e3 e2 e3',
|
'b1 b2 b1 b2 e2 e3 e2 e3',
|
||||||
'a1 a2 a1 a2 a1 a2 a1 a2'
|
'a1 a2 a1 a2 a1 a2 a1 a2'
|
||||||
)
|
|
||||||
)
|
)
|
||||||
).slow(16).synth({
|
).slow(16)`;
|
||||||
oscillator: {type: 'sawtooth'}
|
|
||||||
})`;
|
|
||||||
|
|
||||||
export const tetrisRev = `stack(
|
export const tetrisRev = `stack(
|
||||||
sequence(
|
|
||||||
mini(
|
mini(
|
||||||
'e5 [b4 c5] d5 [c5 b4]',
|
'e5 [b4 c5] d5 [c5 b4]',
|
||||||
'a4 [a4 c5] e5 [d5 c5]',
|
'a4 [a4 c5] e5 [d5 c5]',
|
||||||
|
|
@ -99,9 +90,7 @@ export const tetrisRev = `stack(
|
||||||
'e5 [~ c5] e5 [d5 c5]',
|
'e5 [~ c5] e5 [d5 c5]',
|
||||||
'b4 [b4 c5] d5 e5',
|
'b4 [b4 c5] d5 e5',
|
||||||
'c5 a4 a4 ~'
|
'c5 a4 a4 ~'
|
||||||
).rev()
|
).rev(),
|
||||||
),
|
|
||||||
sequence(
|
|
||||||
mini(
|
mini(
|
||||||
'e2 e3 e2 e3 e2 e3 e2 e3',
|
'e2 e3 e2 e3 e2 e3 e2 e3',
|
||||||
'a2 a3 a2 a3 a2 a3 a2 a3',
|
'a2 a3 a2 a3 a2 a3 a2 a3',
|
||||||
|
|
@ -112,8 +101,7 @@ export const tetrisRev = `stack(
|
||||||
'b1 b2 b1 b2 e2 e3 e2 e3',
|
'b1 b2 b1 b2 e2 e3 e2 e3',
|
||||||
'a1 a2 a1 a2 a1 a2 a1 a2'
|
'a1 a2 a1 a2 a1 a2 a1 a2'
|
||||||
).rev()
|
).rev()
|
||||||
)
|
).slow(16)`;
|
||||||
).slow(16).synth('sawtooth').filter(1000).gain(0.6)`;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
.synth({
|
.synth({
|
||||||
|
|
@ -123,8 +111,10 @@ export const tetrisRev = `stack(
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const tetrisMini1 = `m\`[[e5 [b4 c5] d5 [c5 b4]] [a4 [a4 c5] e5 [d5 c5]] [b4 [~ c5] d5 e5] [c5 a4 a4 ~] [[~ d5] [~ f5] a5 [g5 f5]] [e5 [~ c5] e5 [d5 c5]] [b4 [b4 c5] d5 e5] [c5 a4 a4 ~]],[[e2 e3 e2 e3 e2 e3 e2 e3] [a2 a3 a2 a3 a2 a3 a2 a3] [g#2 g#3 g#2 g#3 e2 e3 e2 e3] [a2 a3 a2 a3 a2 a3 b1 c2] [d2 d3 d2 d3 d2 d3 d2 d3] [c2 c3 c2 c3 c2 c3 c2 c3] [b1 b2 b1 b2 e2 e3 e2 e3] [a1 a2 a1 a2 a1 a2 a1 a2]]')._slow(16)\``;
|
/* export const tetrisMini1 =
|
||||||
export const tetrisMini = `m\`[[e5 [b4 c5] d5 [c5 b4]]
|
"'[[e5 [b4 c5] d5 [c5 b4]] [a4 [a4 c5] e5 [d5 c5]] [b4 [~ c5] d5 e5] [c5 a4 a4 ~] [[~ d5] [~ f5] a5 [g5 f5]] [e5 [~ c5] e5 [d5 c5]] [b4 [b4 c5] d5 e5] [c5 a4 a4 ~]],[[e2 e3 e2 e3 e2 e3 e2 e3] [a2 a3 a2 a3 a2 a3 a2 a3] [g#2 g#3 g#2 g#3 e2 e3 e2 e3] [a2 a3 a2 a3 a2 a3 b1 c2] [d2 d3 d2 d3 d2 d3 d2 d3] [c2 c3 c2 c3 c2 c3 c2 c3] [b1 b2 b1 b2 e2 e3 e2 e3] [a1 a2 a1 a2 a1 a2 a1 a2]]'.mini.slow(16)";
|
||||||
|
*/
|
||||||
|
export const tetrisMini = `\`[[e5 [b4 c5] d5 [c5 b4]]
|
||||||
[a4 [a4 c5] e5 [d5 c5]]
|
[a4 [a4 c5] e5 [d5 c5]]
|
||||||
[b4 [~ c5] d5 e5]
|
[b4 [~ c5] d5 e5]
|
||||||
[c5 a4 a4 ~]
|
[c5 a4 a4 ~]
|
||||||
|
|
@ -139,10 +129,10 @@ export const tetrisMini = `m\`[[e5 [b4 c5] d5 [c5 b4]]
|
||||||
[[d2 d3]*4]
|
[[d2 d3]*4]
|
||||||
[[c2 c3]*4]
|
[[c2 c3]*4]
|
||||||
[[b1 b2]*2 [e2 e3]*2]
|
[[b1 b2]*2 [e2 e3]*2]
|
||||||
[[a1 a2]*4]\`._slow(16);
|
[[a1 a2]*4]\`.mini.slow(16)
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const tetrisHaskellH = `h(\`slow 16 $ "[[e5 [b4 c5] d5 [c5 b4]]
|
/* export const tetrisHaskellH = `h(\`slow 16 $ "[[e5 [b4 c5] d5 [c5 b4]]
|
||||||
[a4 [a4 c5] e5 [d5 c5]]
|
[a4 [a4 c5] e5 [d5 c5]]
|
||||||
[b4 [~ c5] d5 e5]
|
[b4 [~ c5] d5 e5]
|
||||||
[c5 a4 a4 ~]
|
[c5 a4 a4 ~]
|
||||||
|
|
@ -158,8 +148,9 @@ export const tetrisHaskellH = `h(\`slow 16 $ "[[e5 [b4 c5] d5 [c5 b4]]
|
||||||
[[c2 c3]*4]
|
[[c2 c3]*4]
|
||||||
[[b1 b2]*2 [e2 e3]*2]
|
[[b1 b2]*2 [e2 e3]*2]
|
||||||
[[a1 a2]*4]"\`)
|
[[a1 a2]*4]"\`)
|
||||||
`;
|
`; */
|
||||||
export const tetrisHaskell = `slow 16 $ "[[e5 [b4 c5] d5 [c5 b4]]
|
// following syntax is not supported anymore
|
||||||
|
/* export const tetrisHaskell = `slow 16 $ "[[e5 [b4 c5] d5 [c5 b4]]
|
||||||
[a4 [a4 c5] e5 [d5 c5]]
|
[a4 [a4 c5] e5 [d5 c5]]
|
||||||
[b4 [~ c5] d5 e5]
|
[b4 [~ c5] d5 e5]
|
||||||
[c5 a4 a4 ~]
|
[c5 a4 a4 ~]
|
||||||
|
|
@ -175,23 +166,23 @@ export const tetrisHaskell = `slow 16 $ "[[e5 [b4 c5] d5 [c5 b4]]
|
||||||
[[c2 c3]*4]
|
[[c2 c3]*4]
|
||||||
[[b1 b2]*2 [e2 e3]*2]
|
[[b1 b2]*2 [e2 e3]*2]
|
||||||
[[a1 a2]*4]"
|
[[a1 a2]*4]"
|
||||||
`;
|
`; */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
export const tetrisHaskell = `h(\`slow 16 $ "[[e5 [b4 c5] d5 [c5 b4]] [a4 [a4 c5] e5 [d5 c5]] [b4 [~ c5] d5 e5] [c5 a4 a4 ~] [[~ d5] [~ f5] a5 [g5 f5]] [e5 [~ c5] e5 [d5 c5]] [b4 [b4 c5] d5 e5] [c5 a4 a4 ~]], [[e2 e3]*4] [[a2 a3]*4] [[g#2 g#3]*2 [e2 e3]*2] [a2 a3 a2 a3 a2 a3 b1 c2] [[d2 d3]*4] [[c2 c3]*4] [[b1 b2]*2 [e2 e3]*2] [[a1 a2]*4]"\`)`;
|
export const tetrisHaskell = `h(\`slow 16 $ "[[e5 [b4 c5] d5 [c5 b4]] [a4 [a4 c5] e5 [d5 c5]] [b4 [~ c5] d5 e5] [c5 a4 a4 ~] [[~ d5] [~ f5] a5 [g5 f5]] [e5 [~ c5] e5 [d5 c5]] [b4 [b4 c5] d5 e5] [c5 a4 a4 ~]], [[e2 e3]*4] [[a2 a3]*4] [[g#2 g#3]*2 [e2 e3]*2] [a2 a3 a2 a3 a2 a3 b1 c2] [[d2 d3]*4] [[c2 c3]*4] [[b1 b2]*2 [e2 e3]*2] [[a1 a2]*4]"\`)`;
|
||||||
*/
|
*/
|
||||||
export const spanish = `slowcat(
|
export const spanish = `slowcat(
|
||||||
stack('c4','eb4','g4'),
|
stack(c4,eb4,g4),
|
||||||
stack('bb3','d4','f4'),
|
stack(bb3,d4,f4),
|
||||||
stack('ab3','c4','eb4'),
|
stack(ab3,c4,eb4),
|
||||||
stack('g3','b3','d4')
|
stack(g3,b3,d4)
|
||||||
)`;
|
)`;
|
||||||
|
|
||||||
export const whirlyStrudel = `mini("[e4 [b2 b3] c4]")
|
export const whirlyStrudel = `mini("[e4 [b2 b3] c4]")
|
||||||
.every(4, x => x.fast(2))
|
.every(4, fast(2))
|
||||||
.every(3, x => x.slow(1.5))
|
.every(3, slow(1.5))
|
||||||
.fast(slowcat(1.25,1,1.5))
|
.fast(slowcat(1.25, 1, 1.5))
|
||||||
.every(2, _ => mini("e4 ~ e3 d4 ~"))`;
|
.every(2, _ => mini("e4 ~ e3 d4 ~"))`;
|
||||||
|
|
||||||
export const swimming = `stack(
|
export const swimming = `stack(
|
||||||
mini(
|
mini(
|
||||||
|
|
@ -278,12 +269,39 @@ export const giantSteps = `stack(
|
||||||
)
|
)
|
||||||
).slow(20);`;
|
).slow(20);`;
|
||||||
|
|
||||||
export const transposedChords = `stack(
|
export const giantStepsReggae = `stack(
|
||||||
|
// melody
|
||||||
|
mini(
|
||||||
|
'[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 ~@3]]',
|
||||||
|
),
|
||||||
|
// chords
|
||||||
|
mini(
|
||||||
|
'[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]'
|
||||||
|
)
|
||||||
|
.groove('~ [x ~]'.m.fast(4*8))
|
||||||
|
.voicings(['E3', 'G4']),
|
||||||
|
// bass
|
||||||
|
mini(
|
||||||
|
'[B2 D2] [G2 D2] [Eb2 Bb2] [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 Bb2] [C#2 F#2]'
|
||||||
|
)
|
||||||
|
.groove('x ~'.m.fast(4*8))
|
||||||
|
).slow(25)`;
|
||||||
|
|
||||||
|
/* export const transposedChords = `stack(
|
||||||
m('c2 eb2 g2'),
|
m('c2 eb2 g2'),
|
||||||
m('Cm7').voicings(['g2','c4']).slow(2)
|
m('Cm7').voicings(['g2','c4']).slow(2)
|
||||||
).transpose(
|
).transpose(
|
||||||
slowcat(1, 2, 3, 2).slow(2)
|
slowcat(1, 2, 3, 2).slow(2)
|
||||||
).transpose(5)`;
|
).transpose(5)`; */
|
||||||
|
|
||||||
export const transposedChordsHacked = `stack(
|
export const transposedChordsHacked = `stack(
|
||||||
'c2 eb2 g2'.mini,
|
'c2 eb2 g2'.mini,
|
||||||
|
|
@ -295,55 +313,38 @@ export const transposedChordsHacked = `stack(
|
||||||
export const scaleTranspose = `stack(f2, f3, c4, ab4)
|
export const scaleTranspose = `stack(f2, f3, c4, ab4)
|
||||||
.scale(sequence('F minor', 'F harmonic minor').slow(4))
|
.scale(sequence('F minor', 'F harmonic minor').slow(4))
|
||||||
.scaleTranspose(sequence(0, -1, -2, -3).slow(4))
|
.scaleTranspose(sequence(0, -1, -2, -3).slow(4))
|
||||||
.transpose(sequence(0, 1).slow(16))
|
.transpose(sequence(0, 1).slow(16))`;
|
||||||
.synth('sawtooth')
|
|
||||||
.filter(800)
|
|
||||||
.gain(0.5)`;
|
|
||||||
|
|
||||||
export const groove = `stack(
|
/* export const groove = `stack(
|
||||||
m('c2 g2 a2 [e2@2 eb2] d2 a2 g2 [d2 ~ db2]')
|
m('c2 g2 a2 [e2@2 eb2] d2 a2 g2 [d2 ~ db2]'),
|
||||||
.synth('sawtooth')
|
|
||||||
.filter(500)
|
|
||||||
.gain(.6),
|
|
||||||
m('[C^7 A7] [Dm7 G7]')
|
m('[C^7 A7] [Dm7 G7]')
|
||||||
.groove(m('[x@2 x] [~@2 x] [~ x@2]@2 [x ~@2] ~ [~@2 x@4]@2'))
|
.groove(m('[x@2 x] [~@2 x] [~ x@2]@2 [x ~@2] ~ [~@2 x@4]@2'))
|
||||||
.voicings(['G3','A4'])
|
.voicings(['G3','A4'])
|
||||||
.synth('square')
|
).slow(4.5)`; */
|
||||||
.filter(1000)
|
|
||||||
.adsr(.1,.1,.2)
|
|
||||||
.gain(0.25)
|
|
||||||
).slow(4.5)`;
|
|
||||||
|
|
||||||
export const grooveHacked = `stack(
|
export const groove = `stack(
|
||||||
'c2 g2 a2 [e2@2 eb2] d2 a2 g2 [d2 ~ db2]'.mini
|
'c2 g2 a2 [e2@2 eb2] d2 a2 g2 [d2 ~ db2]'.mini,
|
||||||
.synth('sawtooth')
|
|
||||||
.filter(500)
|
|
||||||
.gain(.6),
|
|
||||||
'[C^7 A7] [Dm7 G7]'.mini.groove('[x@2 x] [~@2 x] [~ x@2]@2 [x ~@2] ~ [~@2 x@4]@2'.mini)
|
'[C^7 A7] [Dm7 G7]'.mini.groove('[x@2 x] [~@2 x] [~ x@2]@2 [x ~@2] ~ [~@2 x@4]@2'.mini)
|
||||||
.voicings(['G3','A4'])
|
.voicings(['G3','A4'])
|
||||||
.synth('square')
|
).slow(4)`;
|
||||||
.filter(1000)
|
|
||||||
.adsr(.1,.1,.2)
|
|
||||||
.gain(0.25)
|
|
||||||
).slow(4.5)`;
|
|
||||||
|
|
||||||
export const magicSofa = `stack(
|
/* export const magicSofa = `stack(
|
||||||
m('[C^7 F^7 ~]/3 [Dm7 G7 A7 ~]/4')
|
m('[C^7 F^7 ~]/3 [Dm7 G7 A7 ~]/4')
|
||||||
.every(2, fast(2))
|
.every(2, fast(2))
|
||||||
.voicings(),
|
.voicings(),
|
||||||
m('[c2 f2 g2]/3 [d2 g2 a2 e2]/4')
|
m('[c2 f2 g2]/3 [d2 g2 a2 e2]/4')
|
||||||
).slow(1)
|
).slow(1)
|
||||||
.transpose.slowcat(0, 2, 3, 4).midi()`;
|
.transpose.slowcat(0, 2, 3, 4)`; */
|
||||||
|
|
||||||
export const magicSofaHacked = `stack(
|
export const magicSofa = `stack(
|
||||||
'[C^7 F^7 ~]/3 [Dm7 G7 A7 ~]/4'.mini
|
'[C^7 F^7 ~]/3 [Dm7 G7 A7 ~]/4'.mini
|
||||||
.every(2, fast(2))
|
.every(2, fast(2))
|
||||||
.voicings(),
|
.voicings(),
|
||||||
'[c2 f2 g2]/3 [d2 g2 a2 e2]/4'.mini
|
'[c2 f2 g2]/3 [d2 g2 a2 e2]/4'.mini
|
||||||
).slow(1)
|
).slow(1)
|
||||||
.transpose.slowcat(0, 2, 3, 4).midi()`;
|
.transpose.slowcat(0, 2, 3, 4)`;
|
||||||
|
|
||||||
export const confusedPhone = `stack('[g2 ~@1.3] [c3 ~@1.3]'.mini.slow(2))
|
/* export const confusedPhone = `stack('[g2 ~@1.3] [c3 ~@1.3]'.mini.slow(2))
|
||||||
.superimpose(
|
.superimpose(
|
||||||
x => transpose(-12,x).late(0),
|
x => transpose(-12,x).late(0),
|
||||||
x => transpose(7,x).late(0.2),
|
x => transpose(7,x).late(0.2),
|
||||||
|
|
@ -353,7 +354,7 @@ export const confusedPhone = `stack('[g2 ~@1.3] [c3 ~@1.3]'.mini.slow(2))
|
||||||
)
|
)
|
||||||
.scale(sequence('C dorian', 'C mixolydian').slow(4))
|
.scale(sequence('C dorian', 'C mixolydian').slow(4))
|
||||||
.scaleTranspose(slowcat(0,1,2,1).slow(2))
|
.scaleTranspose(slowcat(0,1,2,1).slow(2))
|
||||||
.synth('triangle').gain(0.2).filter(1500)`;
|
.synth('triangle').gain(0.5).filter(1500)`; */
|
||||||
|
|
||||||
export const confusedPhoneDynamic = `stack('[g2 ~@1.3] [c3 ~@1.3]'.mini.slow(2))
|
export const confusedPhoneDynamic = `stack('[g2 ~@1.3] [c3 ~@1.3]'.mini.slow(2))
|
||||||
.superimpose(
|
.superimpose(
|
||||||
|
|
@ -361,18 +362,16 @@ export const confusedPhoneDynamic = `stack('[g2 ~@1.3] [c3 ~@1.3]'.mini.slow(2))
|
||||||
)
|
)
|
||||||
.scale(sequence('C dorian', 'C mixolydian').slow(4))
|
.scale(sequence('C dorian', 'C mixolydian').slow(4))
|
||||||
.scaleTranspose(slowcat(0,1,2,1).slow(2))
|
.scaleTranspose(slowcat(0,1,2,1).slow(2))
|
||||||
.synth('triangle').gain(0.2).filter(1500)`;
|
.synth('triangle').gain(0.5).filter(1500)`;
|
||||||
|
|
||||||
export const confusedPhonePartial = `stack('[g2 ~@1.3] [c3 ~@1.3]'.mini.slow(2))
|
export const confusedPhone = `'[g2 ~@1.3] [c3 ~@1.3]'.mini
|
||||||
.superimpose(
|
.superimpose(
|
||||||
transpose(-12).late(0),
|
transpose(-12).late(0),
|
||||||
transpose(7).late(0.2),
|
transpose(7).late(0.1),
|
||||||
transpose(10).late(0.4),
|
transpose(10).late(0.2),
|
||||||
transpose(12).late(0.6),
|
transpose(12).late(0.3),
|
||||||
transpose(24).late(0.8)
|
transpose(24).late(0.4)
|
||||||
)
|
)
|
||||||
.scale(sequence('C dorian', 'C mixolydian').slow(4))
|
.scale(slowcat('C dorian', 'C mixolydian'))
|
||||||
.scaleTranspose(slowcat(0,1,2,1).slow(2))
|
.scaleTranspose(slowcat(0,1,2,1))
|
||||||
.synth('triangle').gain(0.2).filter(1500)`;
|
.slow(2)`;
|
||||||
|
|
||||||
export default swimming;
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Pattern as _Pattern, stack, TimeSpan, Hap, reify } from '../../strudel.mjs';
|
import { Pattern as _Pattern, stack, Hap, reify } from '../../strudel.mjs';
|
||||||
import voicings from 'chord-voicings';
|
import _voicings from 'chord-voicings';
|
||||||
const { dictionaryVoicing, minTopNoteDiff, lefthand } = voicings;
|
const { dictionaryVoicing, minTopNoteDiff, lefthand } = _voicings;
|
||||||
|
|
||||||
const getVoicing = (chord, lastVoicing, range = ['F3', 'A4']) =>
|
const getVoicing = (chord, lastVoicing, range = ['F3', 'A4']) =>
|
||||||
dictionaryVoicing({
|
dictionaryVoicing({
|
||||||
|
|
@ -25,10 +25,29 @@ Pattern.prototype.fmapNested = function (func) {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Pattern.prototype.voicings = function (range = ['F3', 'A4']) {
|
Pattern.prototype.voicings = function (range) {
|
||||||
let lastVoicing;
|
let lastVoicing;
|
||||||
|
if (!range?.length) {
|
||||||
|
// allows to pass empty array, if too lazy to specify range
|
||||||
|
range = ['F3', 'A4'];
|
||||||
|
}
|
||||||
return this.fmapNested((event) => {
|
return this.fmapNested((event) => {
|
||||||
lastVoicing = getVoicing(event.value, lastVoicing, range);
|
lastVoicing = getVoicing(event.value, lastVoicing, range);
|
||||||
return stack(...lastVoicing);
|
return stack(...lastVoicing);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Pattern.prototype.chordBass = function () { // range = ['G1', 'C3']
|
||||||
|
return this._mapNotes((value) => {
|
||||||
|
console.log('value',value);
|
||||||
|
const [_, root] = value.value.match(/^([a-gC-G])[b#]?.*$/);
|
||||||
|
const bassNote = root + '2';
|
||||||
|
return { ...value, value: bassNote };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Pattern.prototype.define('voicings', (range, pat) => pat.voicings(range), { composable: true });
|
||||||
|
Pattern.prototype.define('chordBass', (pat) => {
|
||||||
|
console.log('call chordBass ...', pat);
|
||||||
|
return pat.chordBass()
|
||||||
|
}, { composable: true });
|
||||||
|
|
|
||||||
67
strudel.mjs
67
strudel.mjs
|
|
@ -9,7 +9,7 @@ const flatten = arr => [].concat(...arr)
|
||||||
const id = a => a
|
const id = a => a
|
||||||
|
|
||||||
export function curry(func, overload) {
|
export function curry(func, overload) {
|
||||||
return function curried(...args) {
|
const fn = function curried(...args) {
|
||||||
if (args.length >= func.length) {
|
if (args.length >= func.length) {
|
||||||
return func.apply(this, args)
|
return func.apply(this, args)
|
||||||
}
|
}
|
||||||
|
|
@ -23,6 +23,10 @@ export function curry(func, overload) {
|
||||||
return partial;
|
return partial;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (overload) { // overload function without args... needed for chordBass.transpose(2)
|
||||||
|
overload(fn, []);
|
||||||
|
}
|
||||||
|
return fn;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the start of the cycle.
|
// Returns the start of the cycle.
|
||||||
|
|
@ -567,6 +571,10 @@ class Pattern {
|
||||||
superimpose(...funcs) {
|
superimpose(...funcs) {
|
||||||
return this.stack(...funcs.map((func) => func(this)));
|
return this.stack(...funcs.map((func) => func(this)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
edit(...funcs) {
|
||||||
|
return stack(...funcs.map(func => func(this)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// methods of Pattern that get callable factories
|
// methods of Pattern that get callable factories
|
||||||
|
|
@ -575,20 +583,6 @@ Pattern.prototype.patternified = ['fast', 'slow', 'early', 'late'];
|
||||||
Pattern.prototype.factories = { pure, stack, slowcat, fastcat, cat, timeCat, sequence, polymeter, pm, polyrhythm, pr};
|
Pattern.prototype.factories = { pure, stack, slowcat, fastcat, cat, timeCat, sequence, polymeter, pm, polyrhythm, pr};
|
||||||
// the magic happens in Pattern constructor. Keeping this in prototype enables adding methods from the outside (e.g. see tonal.ts)
|
// the magic happens in Pattern constructor. Keeping this in prototype enables adding methods from the outside (e.g. see tonal.ts)
|
||||||
|
|
||||||
// let's hack built in strings
|
|
||||||
const hackStrings = () => {
|
|
||||||
const pureGetter = {
|
|
||||||
get: function () {
|
|
||||||
return pure(String(this));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
// with this, you can do 'c2'.pure.fast(2) or 'c2'.p.fast(2)
|
|
||||||
Object.defineProperty(String.prototype, 'pure', pureGetter);
|
|
||||||
Object.defineProperty(String.prototype, 'p', pureGetter);
|
|
||||||
};
|
|
||||||
|
|
||||||
hackStrings(); // comment out this line if you panic
|
|
||||||
|
|
||||||
const silence = new Pattern(_ => [])
|
const silence = new Pattern(_ => [])
|
||||||
|
|
||||||
function pure(value) {
|
function pure(value) {
|
||||||
|
|
@ -604,7 +598,7 @@ function steady(value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function reify(thing) {
|
function reify(thing) {
|
||||||
if (thing.constructor.name == "Pattern") {
|
if (thing?.constructor?.name == "Pattern") {
|
||||||
return thing
|
return thing
|
||||||
}
|
}
|
||||||
return pure(thing)
|
return pure(thing)
|
||||||
|
|
@ -744,8 +738,13 @@ const when = curry((binary, f, pat) => pat.when(binary, f))
|
||||||
const off = curry((t, f, pat) => pat.off(t,f))
|
const off = curry((t, f, pat) => pat.off(t,f))
|
||||||
const jux = curry((f, pat) => pat.jux(f))
|
const jux = curry((f, pat) => pat.jux(f))
|
||||||
const append = curry((a, pat) => pat.append(a))
|
const append = curry((a, pat) => pat.append(a))
|
||||||
|
const superimpose = curry((array, pat) => pat.superimpose(...array))
|
||||||
|
|
||||||
Pattern.prototype.composable = { fast, slow, early, late }
|
// problem: curried functions with spread arguments must have pat at the beginning
|
||||||
|
// with this, we cannot keep the pattern open at the end.. solution for now: use array to keep using pat as last arg
|
||||||
|
|
||||||
|
// these are the core composable functions. they are extended with Pattern.prototype.define below
|
||||||
|
Pattern.prototype.composable = { fast, slow, early, late, superimpose }
|
||||||
|
|
||||||
// adds Pattern.prototype.composable to given function as child functions
|
// adds Pattern.prototype.composable to given function as child functions
|
||||||
// then you can do transpose(2).late(0.2) instead of x => x.transpose(2).late(0.2)
|
// then you can do transpose(2).late(0.2) instead of x => x.transpose(2).late(0.2)
|
||||||
|
|
@ -754,16 +753,42 @@ export function makeComposable(func) {
|
||||||
// compose with dot
|
// compose with dot
|
||||||
func[functionName] = (...args) => {
|
func[functionName] = (...args) => {
|
||||||
// console.log(`called ${functionName}(${args.join(',')})`);
|
// console.log(`called ${functionName}(${args.join(',')})`);
|
||||||
return compose(func, composable(...args));
|
const composition = compose(func, composable(...args));
|
||||||
|
// the composition itself must be composable too :)
|
||||||
|
// then you can do endless chaining transpose(2).late(0.2).fast(2) ...
|
||||||
|
return makeComposable(composition);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
return func;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this will add func as name to list of composable / patternified functions.
|
||||||
|
// those lists will be used in bootstrap to curry and compose everything, to support various call patterns
|
||||||
|
Pattern.prototype.define = (name, func, options = {}) => {
|
||||||
|
if (options.composable) {
|
||||||
|
Pattern.prototype.composable[name] = func;
|
||||||
|
}
|
||||||
|
if(options.patternified) {
|
||||||
|
Pattern.prototype.patternified = Pattern.prototype.patternified.concat([name]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// call this after all Patter.prototype.define calls have been executed! (right before evaluate)
|
||||||
|
Pattern.prototype.bootstrap = () => {
|
||||||
|
// makeComposable(Pattern.prototype);
|
||||||
|
const bootstrapped = Object.fromEntries(Object.entries(Pattern.prototype.composable).map(([functionName, composable]) => {
|
||||||
|
if(Pattern.prototype[functionName]) {
|
||||||
|
// without this, 'C^7'.m.chordBass.transpose(2) will throw "C^7".m.chordBass.transpose is not a function
|
||||||
|
Pattern.prototype[functionName] = makeComposable(Pattern.prototype[functionName]); // is this needed?
|
||||||
|
}
|
||||||
|
return [functionName, curry(composable, makeComposable)];
|
||||||
|
}));
|
||||||
|
return bootstrapped;
|
||||||
}
|
}
|
||||||
// TODO: automatically make curried functions curried functions composable
|
|
||||||
// see tonal.ts#transpose for an example
|
|
||||||
|
|
||||||
export {Fraction, TimeSpan, Hap, Pattern,
|
export {Fraction, TimeSpan, Hap, Pattern,
|
||||||
pure, stack, slowcat, fastcat, cat, timeCat, sequence, polymeter, pm, polyrhythm, pr, reify, silence,
|
pure, stack, slowcat, fastcat, cat, timeCat, sequence, polymeter, pm, polyrhythm, pr, reify, silence,
|
||||||
fast, slow, early, late, rev,
|
fast, slow, early, late, rev,
|
||||||
add, sub, mul, div, union, every, when, off, jux, append
|
add, sub, mul, div, union, every, when, off, jux, append, superimpose
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue