build
This commit is contained in:
parent
df0650e48d
commit
827c983175
24 changed files with 22150 additions and 20110 deletions
14
docs/dist/App.js
vendored
14
docs/dist/App.js
vendored
|
|
@ -14,7 +14,6 @@ try {
|
|||
} catch (err) {
|
||||
console.warn("failed to decode", err);
|
||||
}
|
||||
Tone.setContext(new Tone.Context({latencyHint: "playback", lookAhead: 1}));
|
||||
const defaultSynth = new Tone.PolySynth().chain(new Tone.Gain(0.5), Tone.getDestination());
|
||||
defaultSynth.set({
|
||||
oscillator: {type: "triangle"},
|
||||
|
|
@ -30,7 +29,7 @@ function getRandomTune() {
|
|||
const randomTune = getRandomTune();
|
||||
function App() {
|
||||
const [editor, setEditor] = useState();
|
||||
const {setCode, setPattern, error, code, cycle, dirty, log, togglePlay, activateCode, pattern, pushLog} = useRepl({
|
||||
const {setCode, setPattern, error, code, cycle, dirty, log, togglePlay, activateCode, pattern, pushLog, pending} = useRepl({
|
||||
tune: decoded || randomTune,
|
||||
defaultSynth,
|
||||
onDraw: useCallback(markEvent(editor), [editor])
|
||||
|
|
@ -40,12 +39,11 @@ function App() {
|
|||
logBox.current.scrollTop = logBox.current?.scrollHeight;
|
||||
}, [log]);
|
||||
useLayoutEffect(() => {
|
||||
const handleKeyPress = (e) => {
|
||||
const handleKeyPress = async (e) => {
|
||||
if (e.ctrlKey || e.altKey) {
|
||||
switch (e.code) {
|
||||
case "Enter":
|
||||
activateCode();
|
||||
!cycle.started && cycle.start();
|
||||
await activateCode();
|
||||
break;
|
||||
case "Period":
|
||||
cycle.stop();
|
||||
|
|
@ -81,11 +79,11 @@ function App() {
|
|||
}, "Strudel REPL")), /* @__PURE__ */ React.createElement("div", {
|
||||
className: "flex space-x-4"
|
||||
}, /* @__PURE__ */ React.createElement("button", {
|
||||
onClick: () => {
|
||||
onClick: async () => {
|
||||
const _code = getRandomTune();
|
||||
console.log("tune", _code);
|
||||
setCode(_code);
|
||||
const parsed = evaluate(_code);
|
||||
const parsed = await evaluate(_code);
|
||||
setPattern(parsed.pattern);
|
||||
}
|
||||
}, "🎲 random tune"), /* @__PURE__ */ React.createElement("button", null, /* @__PURE__ */ React.createElement("a", {
|
||||
|
|
@ -116,7 +114,7 @@ function App() {
|
|||
}, error?.message || "unknown error")), /* @__PURE__ */ React.createElement("button", {
|
||||
className: "flex-none w-full border border-gray-700 p-2 bg-slate-700 hover:bg-slate-500",
|
||||
onClick: () => togglePlay()
|
||||
}, cycle.started ? "pause" : "play"), /* @__PURE__ */ React.createElement("textarea", {
|
||||
}, !pending ? /* @__PURE__ */ React.createElement(React.Fragment, null, cycle.started ? "pause" : "play") : /* @__PURE__ */ React.createElement(React.Fragment, null, "loading...")), /* @__PURE__ */ React.createElement("textarea", {
|
||||
className: "grow bg-[#283237] border-0 text-xs min-h-[200px]",
|
||||
value: log,
|
||||
readOnly: true,
|
||||
|
|
|
|||
2
docs/dist/CodeMirror.js
vendored
2
docs/dist/CodeMirror.js
vendored
|
|
@ -20,7 +20,7 @@ export default function CodeMirror({value, onChange, options, editorDidMount}) {
|
|||
});
|
||||
}
|
||||
export const markEvent = (editor) => (time, event) => {
|
||||
const locs = event.value.locations;
|
||||
const locs = event.context.locations;
|
||||
if (!locs || !editor) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
4
docs/dist/evaluate.js
vendored
4
docs/dist/evaluate.js
vendored
|
|
@ -20,9 +20,9 @@ function hackLiteral(literal, names, func) {
|
|||
hackLiteral(String, ["mini", "m"], bootstrapped.mini);
|
||||
hackLiteral(String, ["pure", "p"], bootstrapped.pure);
|
||||
Object.assign(globalThis, bootstrapped, Tone, toneHelpers);
|
||||
export const evaluate = (code) => {
|
||||
export const evaluate = async (code) => {
|
||||
const shapeshifted = shapeshifter(code);
|
||||
let evaluated = eval(shapeshifted);
|
||||
let evaluated = await eval(shapeshifted);
|
||||
if (typeof evaluated === "function") {
|
||||
evaluated = evaluated();
|
||||
}
|
||||
|
|
|
|||
3
docs/dist/parse.js
vendored
3
docs/dist/parse.js
vendored
|
|
@ -90,7 +90,8 @@ export function patternifyAST(ast) {
|
|||
return ast.source_;
|
||||
}
|
||||
const {start, end} = ast.location_;
|
||||
return pure(ast.source_).withLocation({start, end});
|
||||
const value = !isNaN(Number(ast.source_)) ? Number(ast.source_) : ast.source_;
|
||||
return pure(value).withLocation({start, end});
|
||||
}
|
||||
return patternifyAST(ast.source_);
|
||||
case "stretch":
|
||||
|
|
|
|||
13
docs/dist/shapeshifter.js
vendored
13
docs/dist/shapeshifter.js
vendored
|
|
@ -92,15 +92,16 @@ export default (code) => {
|
|||
// add to location to pure(x) calls
|
||||
if (node.type === 'CallExpression' && node.callee.name === 'pure') {
|
||||
const literal = node.arguments[0];
|
||||
const value = literal[{ LiteralNumericExpression: 'value', LiteralStringExpression: 'name' }[literal.type]];
|
||||
return reifyWithLocation(value + '', node.arguments[0], ast.locations, artificialNodes);
|
||||
// const value = literal[{ LiteralNumericExpression: 'value', LiteralStringExpression: 'name' }[literal.type]];
|
||||
// console.log('value',value);
|
||||
return reifyWithLocation(literal, node.arguments[0], ast.locations, artificialNodes);
|
||||
}
|
||||
// replace pseudo note variables
|
||||
if (node.type === 'IdentifierExpression') {
|
||||
if (isNote(node.name)) {
|
||||
const value = node.name[1] === 's' ? node.name.replace('s', '#') : node.name;
|
||||
if (addLocations && isMarkable) {
|
||||
return reifyWithLocation(value, node, ast.locations, artificialNodes);
|
||||
return reifyWithLocation(new LiteralStringExpression({ value }), node, ast.locations, artificialNodes);
|
||||
}
|
||||
return new LiteralStringExpression({ value });
|
||||
}
|
||||
|
|
@ -110,7 +111,7 @@ export default (code) => {
|
|||
}
|
||||
if (addLocations && node.type === 'LiteralStringExpression' && isMarkable) {
|
||||
// console.log('add', node);
|
||||
return reifyWithLocation(node.value, node, ast.locations, artificialNodes);
|
||||
return reifyWithLocation(node, node, ast.locations, artificialNodes);
|
||||
}
|
||||
if (!addMiniLocations) {
|
||||
return wrapFunction('reify', node);
|
||||
|
|
@ -219,10 +220,10 @@ function wrapLocationOffset(node, stringNode, locations, artificialNodes) {
|
|||
|
||||
// turns node in reify(value).withLocation(location), where location is the node's location in the source code
|
||||
// with this, the reified pattern can pass its location to the event, to know where to highlight when it's active
|
||||
function reifyWithLocation(value, node, locations, artificialNodes) {
|
||||
function reifyWithLocation(literalNode, node, locations, artificialNodes) {
|
||||
const withLocation = new CallExpression({
|
||||
callee: new StaticMemberExpression({
|
||||
object: wrapFunction('reify', new LiteralStringExpression({ value })),
|
||||
object: wrapFunction('reify', literalNode),
|
||||
property: 'withLocation',
|
||||
}),
|
||||
arguments: [getLocationObject(node, locations)],
|
||||
|
|
|
|||
37
docs/dist/tonal.js
vendored
37
docs/dist/tonal.js
vendored
|
|
@ -1,15 +1,6 @@
|
|||
import {Note, Interval, Scale} from "../_snowpack/pkg/@tonaljs/tonal.js";
|
||||
import {Pattern as _Pattern} from "../_snowpack/link/strudel.js";
|
||||
const Pattern = _Pattern;
|
||||
function toNoteEvent(event) {
|
||||
if (typeof event === "string" || typeof event === "number") {
|
||||
return {value: event};
|
||||
}
|
||||
if (event.value) {
|
||||
return event;
|
||||
}
|
||||
throw new Error("not a valid note event: " + JSON.stringify(event));
|
||||
}
|
||||
const mod = (n, m) => n < 0 ? mod(n + m, m) : n % m;
|
||||
export function intervalDirection(from, to, direction = 1) {
|
||||
const sign = Math.sign(direction);
|
||||
|
|
@ -44,43 +35,37 @@ function scaleTranspose(scale, offset, note) {
|
|||
}
|
||||
return n + o;
|
||||
}
|
||||
Pattern.prototype._mapNotes = function(func) {
|
||||
return this.fmap((event) => {
|
||||
const noteEvent = toNoteEvent(event);
|
||||
return {...noteEvent, ...func(noteEvent)};
|
||||
});
|
||||
};
|
||||
Pattern.prototype._transpose = function(intervalOrSemitones) {
|
||||
return this._mapNotes(({value, scale}) => {
|
||||
return this._withEvent((event) => {
|
||||
const interval = !isNaN(Number(intervalOrSemitones)) ? Interval.fromSemitones(intervalOrSemitones) : String(intervalOrSemitones);
|
||||
if (typeof value === "number") {
|
||||
if (typeof event.value === "number") {
|
||||
const semitones = typeof interval === "string" ? Interval.semitones(interval) || 0 : interval;
|
||||
return {value: value + semitones};
|
||||
return event.withValue(() => event.value + semitones);
|
||||
}
|
||||
return {value: Note.transpose(value, interval), scale};
|
||||
return event.withValue(() => Note.transpose(event.value, interval));
|
||||
});
|
||||
};
|
||||
Pattern.prototype._scaleTranspose = function(offset) {
|
||||
return this._mapNotes(({value, scale}) => {
|
||||
if (!scale) {
|
||||
return this._withEvent((event) => {
|
||||
if (!event.context.scale) {
|
||||
throw new Error("can only use scaleTranspose after .scale");
|
||||
}
|
||||
if (typeof value !== "string") {
|
||||
if (typeof event.value !== "string") {
|
||||
throw new Error("can only use scaleTranspose with notes");
|
||||
}
|
||||
return {value: scaleTranspose(scale, Number(offset), value), scale};
|
||||
return event.withValue(() => scaleTranspose(event.context.scale, Number(offset), event.value));
|
||||
});
|
||||
};
|
||||
Pattern.prototype._scale = function(scale) {
|
||||
return this._mapNotes((value) => {
|
||||
let note = value.value;
|
||||
return this._withEvent((event) => {
|
||||
let note = event.value;
|
||||
const asNumber = Number(note);
|
||||
if (!isNaN(asNumber)) {
|
||||
let [tonic, scaleName] = Scale.tokenize(scale);
|
||||
const {pc, oct = 3} = Note.get(tonic);
|
||||
note = scaleTranspose(pc + " " + scaleName, asNumber, pc + oct);
|
||||
}
|
||||
return {...value, value: note, scale};
|
||||
return event.withValue(() => note).setContext({...event.context, scale});
|
||||
});
|
||||
};
|
||||
Pattern.prototype.define("transpose", (a, pat) => pat.transpose(a), {composable: true, patternified: true});
|
||||
|
|
|
|||
62
docs/dist/tone.js
vendored
62
docs/dist/tone.js
vendored
|
|
@ -17,20 +17,23 @@ import {
|
|||
Sampler,
|
||||
getDestination
|
||||
} from "../_snowpack/pkg/tone.js";
|
||||
import {Piano} from "../_snowpack/pkg/@tonejs/piano.js";
|
||||
const Pattern = _Pattern;
|
||||
Pattern.prototype.tone = function(instrument) {
|
||||
return this.fmap((value) => {
|
||||
value = typeof value !== "object" && !Array.isArray(value) ? {value} : value;
|
||||
const onTrigger = (time, event) => {
|
||||
return this._withEvent((event) => {
|
||||
const onTrigger = (time, event2) => {
|
||||
if (instrument.constructor.name === "PluckSynth") {
|
||||
instrument.triggerAttack(value.value, time);
|
||||
instrument.triggerAttack(event2.value, time);
|
||||
} else if (instrument.constructor.name === "NoiseSynth") {
|
||||
instrument.triggerAttackRelease(event.duration, time);
|
||||
instrument.triggerAttackRelease(event2.duration, time);
|
||||
} else if (instrument.constructor.name === "Piano") {
|
||||
instrument.keyDown({note: event2.value, time, velocity: 0.5});
|
||||
instrument.keyUp({note: event2.value, time: time + event2.duration});
|
||||
} else {
|
||||
instrument.triggerAttackRelease(value.value, event.duration, time);
|
||||
instrument.triggerAttackRelease(event2.value, event2.duration, time);
|
||||
}
|
||||
};
|
||||
return {...value, instrument, onTrigger};
|
||||
return event.setContext({...event.context, instrument, onTrigger});
|
||||
});
|
||||
};
|
||||
Pattern.prototype.define("tone", (type, pat) => pat.tone(type), {composable: true, patternified: false});
|
||||
|
|
@ -45,6 +48,11 @@ export const pluck = (options) => new PluckSynth(options);
|
|||
export const polysynth = (options) => new PolySynth(options);
|
||||
export const sampler = (options) => new Sampler(options);
|
||||
export const synth = (options) => new Synth(options);
|
||||
export const piano = async (options = {velocities: 1}) => {
|
||||
const p = new Piano(options);
|
||||
await p.load();
|
||||
return p;
|
||||
};
|
||||
export const vol = (v) => new Gain(v);
|
||||
export const lowpass = (v) => new Filter(v, "lowpass");
|
||||
export const highpass = (v) => new Filter(v, "highpass");
|
||||
|
|
@ -75,13 +83,12 @@ Pattern.prototype._poly = function(type = "triangle") {
|
|||
if (!this.instrument) {
|
||||
this.instrument = poly(type);
|
||||
}
|
||||
return this.fmap((value) => {
|
||||
value = typeof value !== "object" && !Array.isArray(value) ? {value} : value;
|
||||
const onTrigger = (time, event) => {
|
||||
return this._withEvent((event) => {
|
||||
const onTrigger = (time, event2) => {
|
||||
this.instrument.set(instrumentConfig);
|
||||
this.instrument.triggerAttackRelease(value.value, event.duration, time);
|
||||
this.instrument.triggerAttackRelease(event2.value, event2.duration, time);
|
||||
};
|
||||
return {...value, instrumentConfig, onTrigger};
|
||||
return event.setContext({...event.context, instrumentConfig, onTrigger});
|
||||
});
|
||||
};
|
||||
Pattern.prototype.define("poly", (type, pat) => pat.poly(type), {composable: true, patternified: true});
|
||||
|
|
@ -96,8 +103,7 @@ const getTrigger = (getChain, value) => (time, event) => {
|
|||
}, event.duration * 2e3);
|
||||
};
|
||||
Pattern.prototype._synth = function(type = "triangle") {
|
||||
return this.fmap((value) => {
|
||||
value = typeof value !== "object" && !Array.isArray(value) ? {value} : value;
|
||||
return this._withEvent((event) => {
|
||||
const instrumentConfig = {
|
||||
oscillator: {type},
|
||||
envelope: {attack: 0.01, decay: 0.01, sustain: 0.6, release: 0.01}
|
||||
|
|
@ -107,37 +113,37 @@ Pattern.prototype._synth = function(type = "triangle") {
|
|||
instrument.set(instrumentConfig);
|
||||
return instrument;
|
||||
};
|
||||
const onTrigger = getTrigger(() => getInstrument().toDestination(), value.value);
|
||||
return {...value, getInstrument, instrumentConfig, onTrigger};
|
||||
const onTrigger = getTrigger(() => getInstrument().toDestination(), event.value);
|
||||
return event.setContext({...event.context, getInstrument, instrumentConfig, onTrigger});
|
||||
});
|
||||
};
|
||||
Pattern.prototype.adsr = function(attack = 0.01, decay = 0.01, sustain = 0.6, release = 0.01) {
|
||||
return this.fmap((value) => {
|
||||
if (!value?.getInstrument) {
|
||||
return this._withEvent((event) => {
|
||||
if (!event.context.getInstrument) {
|
||||
throw new Error("cannot chain adsr: need instrument first (like synth)");
|
||||
}
|
||||
const instrumentConfig = {...value.instrumentConfig, envelope: {attack, decay, sustain, release}};
|
||||
const instrumentConfig = {...event.context.instrumentConfig, envelope: {attack, decay, sustain, release}};
|
||||
const getInstrument = () => {
|
||||
const instrument = value.getInstrument();
|
||||
const instrument = event.context.getInstrument();
|
||||
instrument.set(instrumentConfig);
|
||||
return instrument;
|
||||
};
|
||||
const onTrigger = getTrigger(() => getInstrument().toDestination(), value.value);
|
||||
return {...value, getInstrument, instrumentConfig, onTrigger};
|
||||
const onTrigger = getTrigger(() => getInstrument().toDestination(), event.value);
|
||||
return event.setContext({...event.context, getInstrument, instrumentConfig, onTrigger});
|
||||
});
|
||||
};
|
||||
Pattern.prototype.chain = function(...effectGetters) {
|
||||
return this.fmap((value) => {
|
||||
if (!value?.getInstrument) {
|
||||
return this._withEvent((event) => {
|
||||
if (!event.context?.getInstrument) {
|
||||
throw new Error("cannot chain: need instrument first (like synth)");
|
||||
}
|
||||
const chain = (value.chain || []).concat(effectGetters);
|
||||
const chain = (event.context.chain || []).concat(effectGetters);
|
||||
const getChain = () => {
|
||||
const effects = chain.map((getEffect) => getEffect());
|
||||
return value.getInstrument().chain(...effects, getDestination());
|
||||
return event.context.getInstrument().chain(...effects, getDestination());
|
||||
};
|
||||
const onTrigger = getTrigger(getChain, value.value);
|
||||
return {...value, getChain, onTrigger, chain};
|
||||
const onTrigger = getTrigger(getChain, event.value);
|
||||
return event.setContext({...event.context, getChain, onTrigger, chain});
|
||||
});
|
||||
};
|
||||
export const autofilter = (freq = 1) => () => new AutoFilter(freq).start();
|
||||
|
|
|
|||
8
docs/dist/tunes.js
vendored
8
docs/dist/tunes.js
vendored
|
|
@ -426,3 +426,11 @@ export const sowhatelse = `()=> {
|
|||
"[2,4]/4".scale('D dorian').apply(t).tone(instr('pad')).mask("<x x x ~>/8")
|
||||
).fast(6/8)
|
||||
}`;
|
||||
export const barryHarris = `piano()
|
||||
.then(p => "0,2,[7 6]"
|
||||
.add("<0 1 2 3 4 5 7 8>")
|
||||
.scale('C bebop major')
|
||||
.transpose("<0 1 2 1>/8")
|
||||
.slow(2)
|
||||
.tone(p.toDestination()))
|
||||
`;
|
||||
|
|
|
|||
7
docs/dist/useCycle.js
vendored
7
docs/dist/useCycle.js
vendored
|
|
@ -1,6 +1,6 @@
|
|||
import {useEffect, useState} from "../_snowpack/pkg/react.js";
|
||||
import * as Tone from "../_snowpack/pkg/tone.js";
|
||||
import {TimeSpan} from "../_snowpack/link/strudel.js";
|
||||
import {TimeSpan, State} from "../_snowpack/link/strudel.js";
|
||||
function useCycle(props) {
|
||||
const {onEvent, onQuery, onSchedule, ready = true, onDraw} = props;
|
||||
const [started, setStarted] = useState(false);
|
||||
|
|
@ -8,7 +8,7 @@ function useCycle(props) {
|
|||
const activeCycle = () => Math.floor(Tone.getTransport().seconds / cycleDuration);
|
||||
const query = (cycle = activeCycle()) => {
|
||||
const timespan = new TimeSpan(cycle, cycle + 1);
|
||||
const events = onQuery?.(timespan) || [];
|
||||
const events = onQuery?.(new State(timespan)) || [];
|
||||
onSchedule?.(events, cycle);
|
||||
const cancelFrom = timespan.begin.valueOf();
|
||||
Tone.getTransport().cancel(cancelFrom);
|
||||
|
|
@ -22,7 +22,8 @@ function useCycle(props) {
|
|||
const toneEvent = {
|
||||
time: event.part.begin.valueOf(),
|
||||
duration: event.whole.end.sub(event.whole.begin).valueOf(),
|
||||
value: event.value
|
||||
value: event.value,
|
||||
context: event.context
|
||||
};
|
||||
onEvent(time, toneEvent);
|
||||
Tone.Draw.schedule(() => {
|
||||
|
|
|
|||
23
docs/dist/useRepl.js
vendored
23
docs/dist/useRepl.js
vendored
|
|
@ -12,19 +12,22 @@ function useRepl({tune, defaultSynth, autolink = true, onEvent, onDraw}) {
|
|||
const [activeCode, setActiveCode] = useState();
|
||||
const [log, setLog] = useState("");
|
||||
const [error, setError] = useState();
|
||||
const [pending, setPending] = useState(false);
|
||||
const [hash, setHash] = useState("");
|
||||
const [pattern, setPattern] = useState();
|
||||
const dirty = code !== activeCode || error;
|
||||
const generateHash = () => encodeURIComponent(btoa(code));
|
||||
const activateCode = (_code = code) => {
|
||||
!cycle.started && cycle.start();
|
||||
broadcast({type: "start", from: id});
|
||||
const activateCode = async (_code = code) => {
|
||||
if (activeCode && !dirty) {
|
||||
setError(void 0);
|
||||
!cycle.started && cycle.start();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const parsed = evaluate(_code);
|
||||
setPending(true);
|
||||
const parsed = await evaluate(_code);
|
||||
!cycle.started && cycle.start();
|
||||
broadcast({type: "start", from: id});
|
||||
setPattern(() => parsed.pattern);
|
||||
if (autolink) {
|
||||
window.location.hash = "#" + encodeURIComponent(btoa(code));
|
||||
|
|
@ -32,6 +35,7 @@ function useRepl({tune, defaultSynth, autolink = true, onEvent, onDraw}) {
|
|||
setHash(generateHash());
|
||||
setError(void 0);
|
||||
setActiveCode(_code);
|
||||
setPending(false);
|
||||
} catch (err) {
|
||||
err.message = "evaluation error: " + err.message;
|
||||
console.warn(err);
|
||||
|
|
@ -48,8 +52,9 @@ function useRepl({tune, defaultSynth, autolink = true, onEvent, onDraw}) {
|
|||
onEvent: useCallback((time, event) => {
|
||||
try {
|
||||
onEvent?.(event);
|
||||
if (!event.value?.onTrigger) {
|
||||
const note = event.value?.value || event.value;
|
||||
const {onTrigger} = event.context;
|
||||
if (!onTrigger) {
|
||||
const note = event.value;
|
||||
if (!isNote(note)) {
|
||||
throw new Error("not a note: " + note);
|
||||
}
|
||||
|
|
@ -59,7 +64,6 @@ function useRepl({tune, defaultSynth, autolink = true, onEvent, onDraw}) {
|
|||
throw new Error("no defaultSynth passed to useRepl.");
|
||||
}
|
||||
} else {
|
||||
const {onTrigger} = event.value;
|
||||
onTrigger(time, event);
|
||||
}
|
||||
} catch (err) {
|
||||
|
|
@ -68,9 +72,9 @@ function useRepl({tune, defaultSynth, autolink = true, onEvent, onDraw}) {
|
|||
pushLog(err.message);
|
||||
}
|
||||
}, [onEvent]),
|
||||
onQuery: useCallback((span) => {
|
||||
onQuery: useCallback((state) => {
|
||||
try {
|
||||
return pattern?.query(span) || [];
|
||||
return pattern?.query(state) || [];
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
err.message = "query error: " + err.message;
|
||||
|
|
@ -95,6 +99,7 @@ function useRepl({tune, defaultSynth, autolink = true, onEvent, onDraw}) {
|
|||
}
|
||||
};
|
||||
return {
|
||||
pending,
|
||||
code,
|
||||
setCode,
|
||||
pattern,
|
||||
|
|
|
|||
15
docs/dist/voicings.js
vendored
15
docs/dist/voicings.js
vendored
|
|
@ -10,7 +10,7 @@ const getVoicing = (chord, lastVoicing, range = ["F3", "A4"]) => dictionaryVoici
|
|||
});
|
||||
const Pattern = _Pattern;
|
||||
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, hap.context))).flat());
|
||||
};
|
||||
Pattern.prototype.voicings = function(range) {
|
||||
let lastVoicing;
|
||||
|
|
@ -18,15 +18,16 @@ Pattern.prototype.voicings = function(range) {
|
|||
range = ["F3", "A4"];
|
||||
}
|
||||
return this.fmapNested((event) => {
|
||||
lastVoicing = getVoicing(event.value?.value || event.value, lastVoicing, range);
|
||||
return stack(...lastVoicing);
|
||||
lastVoicing = getVoicing(event.value, lastVoicing, range);
|
||||
return stack(...lastVoicing)._withContext(() => ({
|
||||
locations: event.context.locations || []
|
||||
}));
|
||||
});
|
||||
};
|
||||
Pattern.prototype.rootNotes = function(octave = 2) {
|
||||
return this._mapNotes((value) => {
|
||||
const [_, root] = value.value.match(/^([a-gA-G])[b#]?.*$/);
|
||||
const bassNote = root + octave;
|
||||
return {...value, value: bassNote};
|
||||
return this.fmap((value) => {
|
||||
const [_, root] = value.match(/^([a-gA-G])[b#]?.*$/);
|
||||
return root + octave;
|
||||
});
|
||||
};
|
||||
Pattern.prototype.define("voicings", (range, pat) => pat.voicings(range), {composable: true});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue