mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-21 10:38:37 +00:00
build
This commit is contained in:
parent
ad42e5eb61
commit
a2ab2b9da5
@ -1,5 +1,6 @@
|
|||||||
import Fraction from "../pkg/fractionjs.js";
|
import Fraction from "../pkg/fractionjs.js";
|
||||||
import {compose} from "../pkg/ramda.js";
|
import {compose} from "../pkg/ramda.js";
|
||||||
|
import {isNote, toMidi} from "./util.js";
|
||||||
const removeUndefineds = (xs) => xs.filter((x) => x != void 0);
|
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;
|
||||||
@ -327,17 +328,39 @@ class Pattern {
|
|||||||
_opleft(other, func) {
|
_opleft(other, func) {
|
||||||
return this.fmap(func).appLeft(reify(other));
|
return this.fmap(func).appLeft(reify(other));
|
||||||
}
|
}
|
||||||
|
_asNumber() {
|
||||||
|
return this._withEvent((event) => {
|
||||||
|
const asNumber = Number(event.value);
|
||||||
|
if (!isNaN(asNumber)) {
|
||||||
|
return event.withValue(() => asNumber);
|
||||||
|
}
|
||||||
|
const specialValue = {
|
||||||
|
e: Math.E,
|
||||||
|
pi: Math.PI
|
||||||
|
}[event.value];
|
||||||
|
if (typeof specialValue !== "undefined") {
|
||||||
|
return event.withValue(() => specialValue);
|
||||||
|
}
|
||||||
|
if (isNote(event.value)) {
|
||||||
|
return new Hap(event.whole, event.part, toMidi(event.value), {...event.context, type: "midi"});
|
||||||
|
}
|
||||||
|
throw new Error('cannot parse as number: "' + event.value + '"');
|
||||||
|
});
|
||||||
|
}
|
||||||
add(other) {
|
add(other) {
|
||||||
return this._opleft(other, (a) => (b) => a + b);
|
return this._asNumber()._opleft(other, (a) => (b) => a + b);
|
||||||
}
|
}
|
||||||
sub(other) {
|
sub(other) {
|
||||||
return this._opleft(other, (a) => (b) => a - b);
|
return this._asNumber()._opleft(other, (a) => (b) => a - b);
|
||||||
}
|
}
|
||||||
mul(other) {
|
mul(other) {
|
||||||
return this._opleft(other, (a) => (b) => a * b);
|
return this._asNumber()._opleft(other, (a) => (b) => a * b);
|
||||||
}
|
}
|
||||||
div(other) {
|
div(other) {
|
||||||
return this._opleft(other, (a) => (b) => a / b);
|
return this._asNumber()._opleft(other, (a) => (b) => a / b);
|
||||||
|
}
|
||||||
|
round() {
|
||||||
|
return this._asNumber().fmap((v) => Math.round(v));
|
||||||
}
|
}
|
||||||
union(other) {
|
union(other) {
|
||||||
return this._opleft(other, (a) => (b) => Object.assign({}, a, b));
|
return this._opleft(other, (a) => (b) => Object.assign({}, a, b));
|
||||||
|
|||||||
33
docs/_snowpack/link/util.js
Normal file
33
docs/_snowpack/link/util.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
export const isNote = (name) => /^[a-gA-G][#b]*[0-9]$/.test(name);
|
||||||
|
export const tokenizeNote = (note) => {
|
||||||
|
if (typeof note !== "string") {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const [pc, acc = "", oct] = note.match(/^([a-gA-G])([#b]*)([0-9])?$/)?.slice(1) || [];
|
||||||
|
if (!pc) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return [pc, acc, oct ? Number(oct) : void 0];
|
||||||
|
};
|
||||||
|
export const toMidi = (note) => {
|
||||||
|
const [pc, acc, oct] = tokenizeNote(note);
|
||||||
|
if (!pc) {
|
||||||
|
throw new Error('not a note: "' + note + '"');
|
||||||
|
}
|
||||||
|
const chroma = {c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11}[pc.toLowerCase()];
|
||||||
|
const offset = acc?.split("").reduce((o, char) => o + {"#": 1, b: -1}[char], 0) || 0;
|
||||||
|
return (Number(oct) + 1) * 12 + chroma + offset;
|
||||||
|
};
|
||||||
|
export const fromMidi = (n) => {
|
||||||
|
return Math.pow(2, (n - 69) / 12) * 440;
|
||||||
|
};
|
||||||
|
export const mod = (n, m) => n < 0 ? mod(n + m, m) : n % m;
|
||||||
|
export const getPlayableNoteValue = (event) => {
|
||||||
|
let {value: note, context} = event;
|
||||||
|
if (typeof note === "number" && context.type !== "frequency") {
|
||||||
|
note = fromMidi(event.value);
|
||||||
|
} else if (typeof note === "string" && !isNote(note)) {
|
||||||
|
throw new Error("not a note: " + note);
|
||||||
|
}
|
||||||
|
return note;
|
||||||
|
};
|
||||||
3
docs/dist/evaluate.js
vendored
3
docs/dist/evaluate.js
vendored
@ -3,6 +3,9 @@ import "./tone.js";
|
|||||||
import "./midi.js";
|
import "./midi.js";
|
||||||
import "./voicings.js";
|
import "./voicings.js";
|
||||||
import "./tonal.js";
|
import "./tonal.js";
|
||||||
|
import "./xen.js";
|
||||||
|
import "./tune.js";
|
||||||
|
import "./tonal.js";
|
||||||
import gist from "./gist.js";
|
import gist from "./gist.js";
|
||||||
import shapeshifter from "./shapeshifter.js";
|
import shapeshifter from "./shapeshifter.js";
|
||||||
import {minify} from "./parse.js";
|
import {minify} from "./parse.js";
|
||||||
|
|||||||
9
docs/dist/tonal.js
vendored
9
docs/dist/tonal.js
vendored
@ -1,13 +1,8 @@
|
|||||||
import {Note, Interval, Scale} from "../_snowpack/pkg/@tonaljs/tonal.js";
|
import {Note, Interval, Scale} from "../_snowpack/pkg/@tonaljs/tonal.js";
|
||||||
import {Pattern as _Pattern} from "../_snowpack/link/strudel.js";
|
import {Pattern as _Pattern} from "../_snowpack/link/strudel.js";
|
||||||
|
import {mod, tokenizeNote} from "../_snowpack/link/util.js";
|
||||||
const Pattern = _Pattern;
|
const Pattern = _Pattern;
|
||||||
const mod = (n, m) => n < 0 ? mod(n + m, m) : n % m;
|
export function scaleTranspose(scale, offset, note) {
|
||||||
export function intervalDirection(from, to, direction = 1) {
|
|
||||||
const sign = Math.sign(direction);
|
|
||||||
const interval = sign < 0 ? Interval.distance(to, from) : Interval.distance(from, to);
|
|
||||||
return (sign < 0 ? "-" : "") + interval;
|
|
||||||
}
|
|
||||||
function scaleTranspose(scale, offset, note) {
|
|
||||||
let [tonic, scaleName] = Scale.tokenize(scale);
|
let [tonic, scaleName] = Scale.tokenize(scale);
|
||||||
let {notes} = Scale.get(`${tonic} ${scaleName}`);
|
let {notes} = Scale.get(`${tonic} ${scaleName}`);
|
||||||
notes = notes.map((note2) => Note.get(note2).pc);
|
notes = notes.map((note2) => Note.get(note2).pc);
|
||||||
|
|||||||
7
docs/dist/tone.js
vendored
7
docs/dist/tone.js
vendored
@ -19,24 +19,28 @@ import {
|
|||||||
Players
|
Players
|
||||||
} from "../_snowpack/pkg/tone.js";
|
} from "../_snowpack/pkg/tone.js";
|
||||||
import {Piano} from "../_snowpack/pkg/@tonejs/piano.js";
|
import {Piano} from "../_snowpack/pkg/@tonejs/piano.js";
|
||||||
|
import {getPlayableNoteValue} from "../_snowpack/link/util.js";
|
||||||
const Pattern = _Pattern;
|
const Pattern = _Pattern;
|
||||||
Pattern.prototype.tone = function(instrument) {
|
Pattern.prototype.tone = function(instrument) {
|
||||||
return this._withEvent((event) => {
|
return this._withEvent((event) => {
|
||||||
const onTrigger = (time, event2) => {
|
const onTrigger = (time, event2) => {
|
||||||
let note = event2.value;
|
let note;
|
||||||
let velocity = event2.context?.velocity ?? 0.75;
|
let velocity = event2.context?.velocity ?? 0.75;
|
||||||
switch (instrument.constructor.name) {
|
switch (instrument.constructor.name) {
|
||||||
case "PluckSynth":
|
case "PluckSynth":
|
||||||
|
note = getPlayableNoteValue(event2);
|
||||||
instrument.triggerAttack(note, time);
|
instrument.triggerAttack(note, time);
|
||||||
break;
|
break;
|
||||||
case "NoiseSynth":
|
case "NoiseSynth":
|
||||||
instrument.triggerAttackRelease(event2.duration, time);
|
instrument.triggerAttackRelease(event2.duration, time);
|
||||||
break;
|
break;
|
||||||
case "Piano":
|
case "Piano":
|
||||||
|
note = getPlayableNoteValue(event2);
|
||||||
instrument.keyDown({note, time, velocity: 0.5});
|
instrument.keyDown({note, time, velocity: 0.5});
|
||||||
instrument.keyUp({note, time: time + event2.duration, velocity});
|
instrument.keyUp({note, time: time + event2.duration, velocity});
|
||||||
break;
|
break;
|
||||||
case "Sampler":
|
case "Sampler":
|
||||||
|
note = getPlayableNoteValue(event2);
|
||||||
instrument.triggerAttackRelease(note, event2.duration, time, velocity);
|
instrument.triggerAttackRelease(note, event2.duration, time, velocity);
|
||||||
break;
|
break;
|
||||||
case "Players":
|
case "Players":
|
||||||
@ -48,6 +52,7 @@ Pattern.prototype.tone = function(instrument) {
|
|||||||
player.stop(time + event2.duration);
|
player.stop(time + event2.duration);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
note = getPlayableNoteValue(event2);
|
||||||
instrument.triggerAttackRelease(note, event2.duration, time, velocity);
|
instrument.triggerAttackRelease(note, event2.duration, time, velocity);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
14
docs/dist/tune.js
vendored
Normal file
14
docs/dist/tune.js
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import Tune from "./tunejs.js";
|
||||||
|
import {Pattern} from "../_snowpack/link/strudel.js";
|
||||||
|
Pattern.prototype._tune = function(scale, tonic = 220) {
|
||||||
|
const tune = new Tune();
|
||||||
|
if (!tune.isValidScale(scale)) {
|
||||||
|
throw new Error('not a valid tune.js scale name: "' + scale + '". See http://abbernie.github.io/tune/scales.html');
|
||||||
|
}
|
||||||
|
tune.loadScale(scale);
|
||||||
|
tune.tonicize(tonic);
|
||||||
|
return this._asNumber()._withEvent((event) => {
|
||||||
|
return event.withValue(() => tune.note(event.value)).setContext({...event.context, type: "frequency"});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Pattern.prototype.define("tune", (scale, pat) => pat.tune(scale), {composable: true, patternified: true});
|
||||||
233
docs/dist/tunejs.js
vendored
Normal file
233
docs/dist/tunejs.js
vendored
Normal file
File diff suppressed because one or more lines are too long
28
docs/dist/tunes.js
vendored
28
docs/dist/tunes.js
vendored
@ -489,3 +489,31 @@ export const wavyKalimba = `sampler({
|
|||||||
.legato("<.4 .8 1 1.2 1.4 1.6 1.8 2>/8")
|
.legato("<.4 .8 1 1.2 1.4 1.6 1.8 2>/8")
|
||||||
.fast(1)
|
.fast(1)
|
||||||
})`;
|
})`;
|
||||||
|
export const jemblung = `() => {
|
||||||
|
const delay = new FeedbackDelay(1/8, .6).chain(vol(0.15), out());
|
||||||
|
const snare = noise({type:'white',...adsr(0,0.2,0)}).chain(lowpass(5000),vol(1.8),out());
|
||||||
|
const s = polysynth().set({...osc('sawtooth4'),...adsr(0.01,.2,.6,0.2)}).chain(vol(.23).connect(delay),out());
|
||||||
|
return stack(
|
||||||
|
stack(
|
||||||
|
"0 1 4 [3!2 5]".edit(
|
||||||
|
// chords
|
||||||
|
x=>x.add("0,3").duration("0.05!3 0.02"),
|
||||||
|
// bass
|
||||||
|
x=>x.add("-8").struct("x*8").duration(0.1)
|
||||||
|
),
|
||||||
|
// melody
|
||||||
|
"12 11*3 12 ~".duration(0.005)
|
||||||
|
)
|
||||||
|
.add("<0 1>")
|
||||||
|
.tune("jemblung2")
|
||||||
|
//.mul(22/5).round().xen("22edo")
|
||||||
|
//.mul(12/5).round().xen("12edo")
|
||||||
|
.tone(s),
|
||||||
|
// kick
|
||||||
|
"[c2 ~]*2".duration(0.05).tone(membrane().chain(out())),
|
||||||
|
// snare
|
||||||
|
"[~ c1]*2".early(0.001).tone(snare),
|
||||||
|
// hihat
|
||||||
|
"c2*8".tone(noise().chain(highpass(6000),vol(0.5).connect(delay),out())),
|
||||||
|
).slow(3)
|
||||||
|
}`;
|
||||||
|
|||||||
7
docs/dist/useRepl.js
vendored
7
docs/dist/useRepl.js
vendored
@ -1,5 +1,5 @@
|
|||||||
import {useCallback, useState, useMemo} from "../_snowpack/pkg/react.js";
|
import {useCallback, useState, useMemo} from "../_snowpack/pkg/react.js";
|
||||||
import {isNote} from "../_snowpack/pkg/tone.js";
|
import {getPlayableNoteValue} from "../_snowpack/link/util.js";
|
||||||
import {evaluate} from "./evaluate.js";
|
import {evaluate} from "./evaluate.js";
|
||||||
import useCycle from "./useCycle.js";
|
import useCycle from "./useCycle.js";
|
||||||
import usePostMessage from "./usePostMessage.js";
|
import usePostMessage from "./usePostMessage.js";
|
||||||
@ -54,11 +54,8 @@ function useRepl({tune, defaultSynth, autolink = true, onEvent, onDraw}) {
|
|||||||
onEvent?.(event);
|
onEvent?.(event);
|
||||||
const {onTrigger, velocity} = event.context;
|
const {onTrigger, velocity} = event.context;
|
||||||
if (!onTrigger) {
|
if (!onTrigger) {
|
||||||
const note = event.value;
|
|
||||||
if (!isNote(note)) {
|
|
||||||
throw new Error("not a note: " + note);
|
|
||||||
}
|
|
||||||
if (defaultSynth) {
|
if (defaultSynth) {
|
||||||
|
const note = getPlayableNoteValue(event);
|
||||||
defaultSynth.triggerAttackRelease(note, event.duration, time, velocity);
|
defaultSynth.triggerAttackRelease(note, event.duration, time, velocity);
|
||||||
} else {
|
} else {
|
||||||
throw new Error("no defaultSynth passed to useRepl.");
|
throw new Error("no defaultSynth passed to useRepl.");
|
||||||
|
|||||||
52
docs/dist/xen.js
vendored
Normal file
52
docs/dist/xen.js
vendored
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import {Pattern} from "../_snowpack/link/strudel.js";
|
||||||
|
import {mod} from "../_snowpack/link/util.js";
|
||||||
|
function edo(name) {
|
||||||
|
if (!/^[1-9]+[0-9]*edo$/.test(name)) {
|
||||||
|
throw new Error('not an edo scale: "' + name + '"');
|
||||||
|
}
|
||||||
|
const [_, divisions] = name.match(/^([1-9]+[0-9]*)edo$/);
|
||||||
|
return Array.from({length: divisions}, (_2, i) => Math.pow(2, i / divisions));
|
||||||
|
}
|
||||||
|
const presets = {
|
||||||
|
"12ji": [1 / 1, 16 / 15, 9 / 8, 6 / 5, 5 / 4, 4 / 3, 45 / 32, 3 / 2, 8 / 5, 5 / 3, 16 / 9, 15 / 8]
|
||||||
|
};
|
||||||
|
function withBase(freq, scale) {
|
||||||
|
return scale.map((r) => r * freq);
|
||||||
|
}
|
||||||
|
const defaultBase = 220;
|
||||||
|
function getXenScale(scale, indices) {
|
||||||
|
if (typeof scale === "string") {
|
||||||
|
if (/^[1-9]+[0-9]*edo$/.test(scale)) {
|
||||||
|
scale = edo(scale);
|
||||||
|
} else if (presets[scale]) {
|
||||||
|
scale = presets[scale];
|
||||||
|
} else {
|
||||||
|
throw new Error('unknown scale name: "' + scale + '"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scale = withBase(defaultBase, scale);
|
||||||
|
if (!indices) {
|
||||||
|
return scale;
|
||||||
|
}
|
||||||
|
return scale.filter((_, i) => indices.includes(i));
|
||||||
|
}
|
||||||
|
function xenOffset(xenScale, offset, index = 0) {
|
||||||
|
const i = mod(index + offset, xenScale.length);
|
||||||
|
const oct = Math.floor(offset / xenScale.length);
|
||||||
|
return xenScale[i] * Math.pow(2, oct);
|
||||||
|
}
|
||||||
|
Pattern.prototype._xen = function(scaleNameOrRatios, steps) {
|
||||||
|
return this._asNumber()._withEvent((event) => {
|
||||||
|
const scale = getXenScale(scaleNameOrRatios);
|
||||||
|
steps = steps || scale.length;
|
||||||
|
const frequency = xenOffset(scale, event.value);
|
||||||
|
return event.withValue(() => frequency).setContext({...event.context, type: "frequency"});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Pattern.prototype.tuning = function(steps) {
|
||||||
|
return this._asNumber()._withEvent((event) => {
|
||||||
|
const frequency = xenOffset(steps, event.value);
|
||||||
|
return event.withValue(() => frequency).setContext({...event.context, type: "frequency"});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Pattern.prototype.define("xen", (scale, pat) => pat.xen(scale), {composable: true, patternified: true});
|
||||||
@ -1052,6 +1052,11 @@ select {
|
|||||||
.overflow-hidden {
|
.overflow-hidden {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
.truncate {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
.whitespace-pre {
|
.whitespace-pre {
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
1
docs/tutorial/index.7a60a07f.js.map
Normal file
1
docs/tutorial/index.7a60a07f.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -762,6 +762,10 @@ Ensure the default browser behavior of the `hidden` attribute.
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
}.overflow-hidden {
|
}.overflow-hidden {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
}.truncate {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
}.whitespace-pre {
|
}.whitespace-pre {
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
}.rounded-md {
|
}.rounded-md {
|
||||||
@ -1346,4 +1350,4 @@ span.CodeMirror-selectedtext { background: none; }
|
|||||||
color: white !important;
|
color: white !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*# sourceMappingURL=index.0ea4d9ed.css.map */
|
/*# sourceMappingURL=index.edd7bd0d.css.map */
|
||||||
1
docs/tutorial/index.edd7bd0d.css.map
Normal file
1
docs/tutorial/index.edd7bd0d.css.map
Normal file
File diff suppressed because one or more lines are too long
@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<link rel="icon" href="/tutorial/favicon.e3ab9dd9.ico">
|
<link rel="icon" href="/tutorial/favicon.e3ab9dd9.ico">
|
||||||
<link rel="stylesheet" type="text/css" href="/tutorial/index.0ea4d9ed.css">
|
<link rel="stylesheet" type="text/css" href="/tutorial/index.edd7bd0d.css">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="description" content="Strudel REPL">
|
<meta name="description" content="Strudel REPL">
|
||||||
<title>Strudel Tutorial</title>
|
<title>Strudel Tutorial</title>
|
||||||
@ -11,6 +11,6 @@
|
|||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<script src="/tutorial/index.d9bcaff1.js" defer=""></script>
|
<script src="/tutorial/index.7a60a07f.js" defer=""></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import './voicings';
|
|||||||
import './tonal.mjs';
|
import './tonal.mjs';
|
||||||
import './xen.mjs';
|
import './xen.mjs';
|
||||||
import './tune.mjs';
|
import './tune.mjs';
|
||||||
import './tonal';
|
import './tonal.mjs';
|
||||||
import gist from './gist.js';
|
import gist from './gist.js';
|
||||||
import shapeshifter from './shapeshifter';
|
import shapeshifter from './shapeshifter';
|
||||||
import { minify } from './parse';
|
import { minify } from './parse';
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user