This commit is contained in:
Felix Roos 2022-02-15 23:05:38 +01:00
parent c11c217baf
commit b64ccb4522
10 changed files with 208 additions and 236 deletions

View File

@ -4,7 +4,7 @@ const removeUndefineds = (xs) => xs.filter((x) => x != void 0);
const flatten = (arr) => [].concat(...arr); 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);
} else { } else {
@ -17,6 +17,10 @@ export function curry(func, overload) {
return partial; return partial;
} }
}; };
if (overload) {
overload(fn, []);
}
return fn;
} }
Fraction.prototype.sam = function() { Fraction.prototype.sam = function() {
return Fraction(Math.floor(this)); return Fraction(Math.floor(this));
@ -401,19 +405,12 @@ 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)));
}
} }
Pattern.prototype.patternified = ["fast", "slow", "early", "late"]; 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};
const hackStrings = () => {
const pureGetter = {
get: function() {
return pure(String(this));
}
};
Object.defineProperty(String.prototype, "pure", pureGetter);
Object.defineProperty(String.prototype, "p", pureGetter);
};
hackStrings();
const silence = new Pattern((_) => []); const silence = new Pattern((_) => []);
function pure(value) { function pure(value) {
function query(span) { function query(span) {
@ -425,7 +422,7 @@ function steady(value) {
return new Pattern((span) => Hap(void 0, span, value)); return new Pattern((span) => Hap(void 0, span, 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);
@ -538,14 +535,34 @@ 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));
Pattern.prototype.composable = {fast, slow, early, late}; const superimpose = curry((array, pat) => pat.superimpose(...array));
Pattern.prototype.composable = {fast, slow, early, late, superimpose};
export function makeComposable(func) { export function makeComposable(func) {
Object.entries(Pattern.prototype.composable).forEach(([functionName, composable]) => { Object.entries(Pattern.prototype.composable).forEach(([functionName, composable]) => {
func[functionName] = (...args) => { func[functionName] = (...args) => {
return compose(func, composable(...args)); const composition = compose(func, composable(...args));
return makeComposable(composition);
}; };
}); });
return func;
} }
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]);
}
};
Pattern.prototype.bootstrap = () => {
const bootstrapped = Object.fromEntries(Object.entries(Pattern.prototype.composable).map(([functionName, composable]) => {
if (Pattern.prototype[functionName]) {
Pattern.prototype[functionName] = makeComposable(Pattern.prototype[functionName]);
}
return [functionName, curry(composable, makeComposable)];
}));
return bootstrapped;
};
export { export {
Fraction, Fraction,
TimeSpan, TimeSpan,
@ -578,5 +595,6 @@ export {
when, when,
off, off,
jux, jux,
append append,
superimpose
}; };

31
docs/dist/App.js vendored
View File

@ -3,13 +3,12 @@ import logo from "./logo.svg.proxy.js";
import cx from "./cx.js"; import cx from "./cx.js";
import * as Tone from "../_snowpack/pkg/tone.js"; import * as Tone from "../_snowpack/pkg/tone.js";
import useCycle from "./useCycle.js"; import useCycle from "./useCycle.js";
import defaultTune from "./tunes.js"; import * as tunes from "./tunes.js";
import * as parser from "./parse.js"; import {evaluate} from "./evaluate.js";
import CodeMirror from "./CodeMirror.js"; import CodeMirror from "./CodeMirror.js";
import hot from "../hot.js"; import hot from "../hot.js";
import {isNote} from "../_snowpack/pkg/tone.js"; import {isNote} from "../_snowpack/pkg/tone.js";
import {useWebMidi} from "./midi.js"; import {useWebMidi} from "./midi.js";
const {parse} = parser;
const [_, codeParam] = window.location.href.split("#"); const [_, codeParam] = window.location.href.split("#");
const decoded = atob(codeParam || ""); const decoded = atob(codeParam || "");
const getHotCode = async () => { const getHotCode = async () => {
@ -17,16 +16,22 @@ const getHotCode = async () => {
return src.split("export default").slice(-1)[0].trim(); return src.split("export default").slice(-1)[0].trim();
}); });
}; };
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: {
release: 0.01 release: 0.01
} }
}); });
function getRandomTune() {
const allTunes = Object.values(tunes);
const randomItem = (arr) => arr[Math.floor(Math.random() * arr.length)];
return randomItem(allTunes);
}
const randomTune = getRandomTune();
function App() { function App() {
const [mode, setMode] = useState("javascript"); const [mode, setMode] = useState("javascript");
const [code, setCode] = useState(decoded || defaultTune); const [code, setCode] = useState(decoded || randomTune);
const [log, setLog] = useState(""); const [log, setLog] = useState("");
const logBox = useRef(); const logBox = useRef();
const [error, setError] = useState(); const [error, setError] = useState();
@ -106,7 +111,7 @@ function App() {
} }
} }
try { try {
const parsed = parse(_code); const parsed = evaluate(_code);
setPattern(() => parsed.pattern); setPattern(() => parsed.pattern);
if (isHot) { if (isHot) {
activatePattern(parsed.pattern); activatePattern(parsed.pattern);
@ -144,13 +149,23 @@ function App() {
alt: "logo" alt: "logo"
}), /* @__PURE__ */ React.createElement("h1", { }), /* @__PURE__ */ React.createElement("h1", {
className: "text-2xl" className: "text-2xl"
}, "Strudel REPL")), window.location.href.includes("http://localhost:8080") && /* @__PURE__ */ React.createElement("button", { }, "Strudel REPL")), /* @__PURE__ */ React.createElement("div", {
className: "flex space-x-4"
}, /* @__PURE__ */ React.createElement("button", {
onClick: () => {
const _code = getRandomTune();
console.log("tune", _code);
setCode(_code);
const parsed = evaluate(_code);
setActivePattern(parsed.pattern);
}
}, "🎲 random tune"), window.location.href.includes("http://localhost:8080") && /* @__PURE__ */ React.createElement("button", {
onClick: () => { onClick: () => {
if (isHot || confirm("Really switch? You might loose your current pattern..")) { if (isHot || confirm("Really switch? You might loose your current pattern..")) {
setIsHot((h) => !h); setIsHot((h) => !h);
} }
} }
}, isHot ? "🔥" : " ", " toggle hot mode")), /* @__PURE__ */ React.createElement("section", { }, "🔥 toggle hot mode"))), /* @__PURE__ */ React.createElement("section", {
className: "grow flex flex-col text-gray-100" className: "grow flex flex-col text-gray-100"
}, /* @__PURE__ */ React.createElement("div", { }, /* @__PURE__ */ React.createElement("div", {
className: "grow relative" className: "grow relative"

30
docs/dist/evaluate.js vendored Normal file
View File

@ -0,0 +1,30 @@
import * as strudel from "../_snowpack/link/strudel.js";
import "./tone.js";
import "./midi.js";
import "./voicings.js";
import "./tonal.js";
import "./groove.js";
import shapeshifter from "./shapeshifter.js";
import {minify} from "./parse.js";
const bootstrapped = {...strudel, ...strudel.Pattern.prototype.bootstrap()};
function hackLiteral(literal, names, func) {
names.forEach((name) => {
Object.defineProperty(literal.prototype, name, {
get: function() {
return func(String(this));
}
});
});
}
hackLiteral(String, ["mini", "m"], bootstrapped.mini);
hackLiteral(String, ["pure", "p"], bootstrapped.pure);
Object.assign(globalThis, bootstrapped);
export const evaluate = (code) => {
const shapeshifted = shapeshifter(code);
const pattern = minify(eval(shapeshifted));
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};
};

1
docs/dist/groove.js vendored
View File

@ -3,3 +3,4 @@ const Pattern = _Pattern;
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});

112
docs/dist/parse.js vendored
View File

@ -1,61 +1,7 @@
import * as krill from "../_snowpack/link/repl/krill-parser.js"; import * as krill from "../_snowpack/link/repl/krill-parser.js";
import * as strudel from "../_snowpack/link/strudel.js"; import * as strudel from "../_snowpack/link/strudel.js";
import {Scale, Note, Interval} from "../_snowpack/pkg/@tonaljs/tonal.js"; import {Scale, Note, Interval} from "../_snowpack/pkg/@tonaljs/tonal.js";
import "./tone.js"; const {pure, Pattern, Fraction, stack, slowcat, sequence, timeCat, silence} = strudel;
import "./midi.js";
import "./voicings.js";
import "./tonal.js";
import * as tonalStuff from "./tonal.js";
import "./groove.js";
import * as toneStuff from "./tone.js";
import shapeshifter from "./shapeshifter.js";
const {
Fraction,
TimeSpan,
Hap,
Pattern,
pure,
stack,
slowcat,
fastcat,
cat,
timeCat,
sequence,
polymeter,
pm,
polyrhythm,
pr,
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) {
if (thing?.constructor?.name === "Pattern") {
return thing;
}
return pure(thing);
}
function minify(thing) {
if (typeof thing === "string") {
return mini(thing);
}
return reify(thing);
}
const applyOptions = (parent) => (pat, i) => { const applyOptions = (parent) => (pat, i) => {
const ast = parent.source_[i]; const ast = parent.source_[i];
const options = ast.options_; const options = ast.options_;
@ -81,12 +27,20 @@ export function patternifyAST(ast) {
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);
}
return pat;
} }
return sequence(...children); return sequence(...children);
case "element": case "element":
@ -112,7 +66,7 @@ export function patternifyAST(ast) {
return step; return step;
} }
const octaves = Math.floor(step / intervals.length); const octaves = Math.floor(step / intervals.length);
const mod = (n, m2) => n < 0 ? mod(n + m2, m2) : n % m2; const mod = (n, m) => n < 0 ? mod(n + m, m) : n % m;
const index = mod(step, intervals.length); const index = mod(step, intervals.length);
const interval = Interval.add(intervals[index], Interval.fromSemitones(octaves * 12)); const interval = Interval.add(intervals[index], Interval.fromSemitones(octaves * 12));
return Note.transpose(tonic, interval || "1P"); return Note.transpose(tonic, interval || "1P");
@ -123,36 +77,28 @@ export function patternifyAST(ast) {
} }
} }
export const mini = (...strings) => { export const mini = (...strings) => {
const pattern = sequence(...strings.map((str) => { const pats = strings.map((str) => {
const ast = krill.parse(`"${str}"`); const ast = krill.parse(`"${str}"`);
return patternifyAST(ast); return patternifyAST(ast);
})); });
return pattern; return sequence(...pats);
}; };
const m = mini;
const hackStrings = () => {
const miniGetter = {
get: function() {
return mini(String(this));
}
};
Object.defineProperty(String.prototype, "mini", miniGetter);
Object.defineProperty(String.prototype, "m", miniGetter);
};
hackStrings();
export const h = (string) => { export const h = (string) => {
const ast = krill.parse(string); const ast = krill.parse(string);
return patternifyAST(ast); return patternifyAST(ast);
}; };
export const parse = (code) => { Pattern.prototype.define("mini", mini, {composable: true});
let _pattern; Pattern.prototype.define("m", mini, {composable: true});
let mode; Pattern.prototype.define("h", h, {composable: true});
mode = "javascript"; export function reify(thing) {
code = shapeshifter(code); if (thing?.constructor?.name === "Pattern") {
_pattern = minify(eval(code)); return thing;
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, pattern: _pattern}; return pure(thing);
}; }
export function minify(thing) {
if (typeof thing === "string") {
return mini(thing);
}
return reify(thing);
}

8
docs/dist/tonal.js vendored
View File

@ -1,5 +1,5 @@
import {Note, Interval, Scale} from "../_snowpack/pkg/@tonaljs/tonal.js"; import {Note, Interval, Scale} from "../_snowpack/pkg/@tonaljs/tonal.js";
import {Pattern as _Pattern, curry, makeComposable} from "../_snowpack/link/strudel.js"; import {Pattern as _Pattern} from "../_snowpack/link/strudel.js";
const Pattern = _Pattern; const Pattern = _Pattern;
function toNoteEvent(event) { function toNoteEvent(event) {
if (typeof event === "string") { if (typeof event === "string") {
@ -55,7 +55,6 @@ Pattern.prototype._transpose = function(intervalOrSemitones) {
return {value: Note.transpose(value, interval), scale}; return {value: Note.transpose(value, interval), scale};
}); });
}; };
export const transpose = curry((a, pat) => pat.transpose(a), (partial) => makeComposable(partial));
Pattern.prototype._scaleTranspose = function(offset) { Pattern.prototype._scaleTranspose = function(offset) {
return this._mapNotes(({value, scale}) => { return this._mapNotes(({value, scale}) => {
if (!scale) { if (!scale) {
@ -67,5 +66,6 @@ Pattern.prototype._scaleTranspose = function(offset) {
Pattern.prototype._scale = function(scale) { Pattern.prototype._scale = function(scale) {
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});

3
docs/dist/tone.js vendored
View File

@ -69,3 +69,6 @@ Pattern.prototype.autofilter = function(g) {
return this.chain(autofilter(g)); return this.chain(autofilter(g));
}; };
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", (gain2, pat) => pat.synth(gain2), {composable: true, patternified: true});
Pattern.prototype.define("filter", (cutoff, pat) => pat.filter(cutoff), {composable: true, patternified: true});

178
docs/dist/tunes.js vendored
View File

@ -33,7 +33,6 @@ export const shapeShifted = `stack(
a1, a2, a1, a2, a1, a2, a1, a2, a1, a2, a1, a2, a1, a2, a1, a2,
).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'),
@ -53,9 +52,8 @@ 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]',
@ -65,9 +63,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',
@ -77,13 +73,9 @@ 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]',
@ -93,9 +85,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',
@ -106,10 +96,8 @@ 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)`; export const tetrisMini = `\`[[e5 [b4 c5] d5 [c5 b4]]
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 tetrisMini = `m\`[[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 ~]
@ -124,53 +112,19 @@ 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]]
[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 = `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(
'~', '~',
@ -254,12 +208,32 @@ export const giantSteps = `stack(
'[B2 F#2] [F2 Bb2] [Eb2 Bb3] [C#2 F#2]' '[B2 F#2] [F2 Bb2] [Eb2 Bb3] [C#2 F#2]'
) )
).slow(20);`; ).slow(20);`;
export const transposedChords = `stack( export const giantStepsReggae = `stack(
m('c2 eb2 g2'), // melody
m('Cm7').voicings(['g2','c4']).slow(2) mini(
).transpose( '[F#5 D5] [B4 G4] Bb4 [B4 A4]',
slowcat(1, 2, 3, 2).slow(2) '[D5 Bb4] [G4 Eb4] F#4 [G4 F4]',
).transpose(5)`; '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 transposedChordsHacked = `stack( export const transposedChordsHacked = `stack(
'c2 eb2 g2'.mini, 'c2 eb2 g2'.mini,
'Cm7'.pure.voicings(['g2','c4']).slow(2) 'Cm7'.pure.voicings(['g2','c4']).slow(2)
@ -269,76 +243,34 @@ 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]') 'c2 g2 a2 [e2@2 eb2] d2 a2 g2 [d2 ~ db2]'.mini,
.synth('sawtooth')
.filter(500)
.gain(.6),
m('[C^7 A7] [Dm7 G7]')
.groove(m('[x@2 x] [~@2 x] [~ x@2]@2 [x ~@2] ~ [~@2 x@4]@2'))
.voicings(['G3','A4'])
.synth('square')
.filter(1000)
.adsr(.1,.1,.2)
.gain(0.25)
).slow(4.5)`;
export const grooveHacked = `stack(
'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')
.every(2, fast(2))
.voicings(),
m('[c2 f2 g2]/3 [d2 g2 a2 e2]/4')
).slow(1)
.transpose.slowcat(0, 2, 3, 4).midi()`;
export const magicSofaHacked = `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))
.superimpose(
x => transpose(-12,x).late(0),
x => transpose(7,x).late(0.2),
x => transpose(10,x).late(0.4),
x => transpose(12,x).late(0.6),
x => transpose(24,x).late(0.8)
)
.scale(sequence('C dorian', 'C mixolydian').slow(4))
.scaleTranspose(slowcat(0,1,2,1).slow(2))
.synth('triangle').gain(0.2).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(
...[-12,7,10,12,24].slice(0,5).map((t,i,{length}) => x => transpose(t,x).late(i/length)) ...[-12,7,10,12,24].slice(0,5).map((t,i,{length}) => x => transpose(t,x).late(i/length))
) )
.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;

22
docs/dist/voicings.js vendored
View File

@ -1,6 +1,6 @@
import {Pattern as _Pattern, stack, Hap, reify} from "../_snowpack/link/strudel.js"; import {Pattern as _Pattern, stack, Hap, reify} from "../_snowpack/link/strudel.js";
import voicings from "../_snowpack/pkg/chord-voicings.js"; import _voicings from "../_snowpack/pkg/chord-voicings.js";
const {dictionaryVoicing, minTopNoteDiff, lefthand} = voicings; const {dictionaryVoicing, minTopNoteDiff, lefthand} = _voicings;
const getVoicing = (chord, lastVoicing, range = ["F3", "A4"]) => dictionaryVoicing({ const getVoicing = (chord, lastVoicing, range = ["F3", "A4"]) => dictionaryVoicing({
chord, chord,
dictionary: lefthand, dictionary: lefthand,
@ -12,10 +12,26 @@ const Pattern = _Pattern;
Pattern.prototype.fmapNested = function(func) { Pattern.prototype.fmapNested = function(func) {
return new Pattern((span) => this.query(span).map((event) => reify(func(event)).query(span).map((hap) => new Hap(event.whole, event.part, hap.value))).flat()); return new Pattern((span) => this.query(span).map((event) => reify(func(event)).query(span).map((hap) => new Hap(event.whole, event.part, hap.value))).flat());
}; };
Pattern.prototype.voicings = function(range = ["F3", "A4"]) { Pattern.prototype.voicings = function(range) {
let lastVoicing; let lastVoicing;
if (!range?.length) {
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() {
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});

View File

@ -621,6 +621,9 @@ select {
.flex { .flex {
display: flex; display: flex;
} }
.contents {
display: contents;
}
.h-16 { .h-16 {
height: 4rem; height: 4rem;
} }
@ -645,6 +648,9 @@ select {
.grow { .grow {
flex-grow: 1; flex-grow: 1;
} }
.transform {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.flex-col { .flex-col {
flex-direction: column; flex-direction: column;
} }
@ -659,6 +665,11 @@ select {
margin-right: calc(0.5rem * var(--tw-space-x-reverse)); margin-right: calc(0.5rem * var(--tw-space-x-reverse));
margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
} }
.space-x-4 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(1rem * var(--tw-space-x-reverse));
margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
}
.whitespace-pre { .whitespace-pre {
white-space: pre; white-space: pre;
} }