mirror of
https://codeberg.org/uzu/strudel
synced 2026-09-16 02:36:55 -04:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 76bdc5fd13 | |||
| 0ea5097a2c | |||
| 3cfb0f4c76 | |||
| 436ba067bc | |||
| f8724bf47e | |||
| 1f7e293204 | |||
| 86d2652258 | |||
| f5c0cc2e2d | |||
| 4463d4a417 | |||
| df35a5c9b1 | |||
| dda93b3c13 | |||
| 5de40b6bf5 | |||
| dcbe799d25 | |||
| a5641aef03 | |||
| 668f97a4c8 | |||
| e9d577496b | |||
| 66fba947ac | |||
| 18c7e02609 | |||
| 7e1351507a | |||
| 032acbd27c | |||
| 8b51f0fbd2 | |||
| ead5942ef0 | |||
| cfe369d9fe | |||
| 2750a3c130 | |||
| a7f6d6ecac | |||
| 3680282450 | |||
| 3b3435c999 | |||
| e2ae6278c2 | |||
| 37d6723e3c | |||
| 961bbf6f55 | |||
| fc71d349ed | |||
| 97c8ab0f71 | |||
| 28534ea547 | |||
| 1a75b79a33 | |||
| 75dfb0af35 | |||
| 073fa09648 | |||
| 57157fde9c | |||
| e616891606 | |||
| 97b01b2891 | |||
| b4fcc359cf | |||
| 1ee6962b85 | |||
| 8d936b93c6 | |||
| 5bd1c0fd7a | |||
| eeaead66b5 |
@@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Pattern, sequence, register } from './pattern.mjs';
|
||||
import { Pattern, sequence } from './pattern.mjs';
|
||||
|
||||
const controls = {};
|
||||
const generic_params = [
|
||||
@@ -828,20 +828,26 @@ const generic_params = [
|
||||
|
||||
// TODO: slice / splice https://www.youtube.com/watch?v=hKhPdO0RKDQ&list=PL2lW1zNIIwj3bDkh-Y3LUGDuRcoUigoDs&index=13
|
||||
|
||||
controls.play = {}
|
||||
const _name = (name, ...pats) => sequence(...pats).withValue((x) => ({ [name]: x }));
|
||||
|
||||
const makeControl = name => {
|
||||
controls.play[name] = pat => pat.fmap(v => ({[name]: v}))
|
||||
return register(name, (v, pat) => pat.fmap(o => ({...o, ...{[name]: v}})))
|
||||
}
|
||||
const _setter = (func, name) =>
|
||||
function (...pats) {
|
||||
if (!pats.length) {
|
||||
return this.fmap((value) => ({ [name]: value }));
|
||||
}
|
||||
return this.set(func(...pats));
|
||||
};
|
||||
|
||||
generic_params.forEach(([type, name, description]) => {
|
||||
controls[name] = makeControl(name);
|
||||
controls[name] = (...pats) => _name(name, ...pats);
|
||||
Pattern.prototype[name] = _setter(controls[name], name);
|
||||
});
|
||||
|
||||
// create custom param
|
||||
controls.createParam = (name) => {
|
||||
return makeControl(name);
|
||||
const func = (...pats) => _name(name, ...pats);
|
||||
Pattern.prototype[name] = _setter(func, name);
|
||||
return (...pats) => _name(name, ...pats);
|
||||
};
|
||||
|
||||
controls.createParams = (...names) =>
|
||||
|
||||
+22
-17
@@ -10,44 +10,49 @@ import { logger } from './logger.mjs';
|
||||
export class Cyclist {
|
||||
constructor({ interval, onTrigger, onToggle, onError, getTime, latency = 0.1 }) {
|
||||
this.started = false;
|
||||
this.cps = 1; // TODO
|
||||
this.phase = 0;
|
||||
this.getTime = getTime;
|
||||
this.cps = 1;
|
||||
this.lastTick = 0; // absolute time when last tick (clock callback) happened
|
||||
this.lastBegin = 0; // query begin of last tick
|
||||
this.lastEnd = 0; // query end of last tick
|
||||
this.getTime = getTime; // get absolute time
|
||||
this.onToggle = onToggle;
|
||||
this.latency = latency;
|
||||
this.latency = latency; // fixed trigger time offset
|
||||
const round = (x) => Math.round(x * 1000) / 1000;
|
||||
this.clock = createClock(
|
||||
getTime,
|
||||
// called slightly before each cycle
|
||||
(phase, duration, tick) => {
|
||||
if (tick === 0) {
|
||||
this.origin = phase;
|
||||
}
|
||||
const begin = round(phase - this.origin);
|
||||
this.phase = begin - latency;
|
||||
const end = round(begin + duration);
|
||||
const time = getTime();
|
||||
try {
|
||||
const haps = this.pattern.queryArc(begin, end); // get Haps
|
||||
const time = getTime();
|
||||
const begin = this.lastEnd;
|
||||
this.lastBegin = begin;
|
||||
const end = round(begin + duration * this.cps);
|
||||
this.lastEnd = end;
|
||||
const haps = this.pattern.queryArc(begin, end);
|
||||
const tickdeadline = phase - time; // time left till phase begins
|
||||
this.lastTick = time + tickdeadline;
|
||||
|
||||
haps.forEach((hap) => {
|
||||
if (hap.part.begin.equals(hap.whole.begin)) {
|
||||
const deadline = hap.whole.begin + this.origin - time + latency;
|
||||
const duration = hap.duration * 1;
|
||||
onTrigger?.(hap, deadline, duration);
|
||||
const deadline = (hap.whole.begin - begin) / this.cps + tickdeadline + latency;
|
||||
const duration = hap.duration / this.cps;
|
||||
onTrigger?.(hap, deadline, duration, this.cps);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
logger(`[cyclist] error: ${e.message}`);
|
||||
onError?.(e);
|
||||
}
|
||||
}, // called slightly before each cycle
|
||||
},
|
||||
interval, // duration of each cycle
|
||||
);
|
||||
}
|
||||
getPhase() {
|
||||
return this.getTime() - this.origin - this.latency;
|
||||
}
|
||||
now() {
|
||||
return this.getTime() - this.origin + this.clock.minLatency;
|
||||
const secondsSinceLastTick = this.getTime() - this.lastTick - this.clock.duration;
|
||||
return this.lastBegin + secondsSinceLastTick * this.cps; // + this.clock.minLatency;
|
||||
}
|
||||
setStarted(v) {
|
||||
this.started = v;
|
||||
|
||||
@@ -6,12 +6,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
|
||||
import { isPattern } from './index.mjs';
|
||||
|
||||
let scoped = false;
|
||||
export const evalScope = async (...args) => {
|
||||
if (scoped) {
|
||||
console.warn('evalScope was called more than once.');
|
||||
}
|
||||
scoped = true;
|
||||
const results = await Promise.allSettled(args);
|
||||
const modules = results.filter((result) => result.status === 'fulfilled').map((r) => r.value);
|
||||
results.forEach((result, i) => {
|
||||
@@ -42,9 +37,6 @@ function safeEval(str, options = {}) {
|
||||
}
|
||||
|
||||
export const evaluate = async (code, transpiler) => {
|
||||
if (!scoped) {
|
||||
await evalScope(); // at least scope Pattern.prototype.boostrap
|
||||
}
|
||||
if (transpiler) {
|
||||
code = transpiler(code); // transform syntactically correct js code to semantically usable code
|
||||
}
|
||||
|
||||
+92
-299
@@ -21,141 +21,6 @@ let stringParser;
|
||||
// intended to use with mini to automatically interpret all strings as mini notation
|
||||
export const setStringParser = (parser) => (stringParser = parser);
|
||||
|
||||
const alignments = ['in', 'out', 'mix', 'squeeze', 'squeezeout', 'trig', 'trigzero'];
|
||||
|
||||
const methodRegistry = [];
|
||||
const getterRegistry = [];
|
||||
const controlRegistry = [];
|
||||
const controlSubscribers = [];
|
||||
const composifiedRegistry = [];
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Magic for supporting higher order composition of method chains
|
||||
|
||||
// Dresses the given (unary) function with methods for composition chaining, so e.g.
|
||||
// `fast(2).iter(4)` composes to pattern functions into a new one.
|
||||
function composify(func) {
|
||||
if (!func.__composified) {
|
||||
for (const [name, method] of methodRegistry) {
|
||||
func[name] = method;
|
||||
}
|
||||
for (const [name, getter] of getterRegistry) {
|
||||
Object.defineProperty(func, name, getter);
|
||||
}
|
||||
func.__composified = true;
|
||||
composifiedRegistry.push(func);
|
||||
} else {
|
||||
console.log('Warning: attempt at composifying a function more than once');
|
||||
}
|
||||
return func;
|
||||
}
|
||||
|
||||
export function registerMethod(name, addAlignments = false, addControls = false) {
|
||||
if (addAlignments || addControls) {
|
||||
// This method needs to make its 'this' object available to chained alignments and/or
|
||||
// control parameters, so it has to be implemented as a getter
|
||||
const getter = {
|
||||
get: function () {
|
||||
const func = this;
|
||||
const wrapped = function (...args) {
|
||||
const composed = (pat) => func(pat)[name](...args);
|
||||
return composify(composed);
|
||||
};
|
||||
|
||||
if (addAlignments) {
|
||||
for (const alignment of alignments) {
|
||||
wrapped[alignment] = function (...args) {
|
||||
const composed = (pat) => func(pat)[name][alignment](...args);
|
||||
return composify(composed);
|
||||
};
|
||||
for (const [controlname, controlfunc] of controlRegistry) {
|
||||
wrapped[alignment][controlname] = function (...args) {
|
||||
const composed = (pat) => func(pat)[name][alignment](controlfunc(...args));
|
||||
return composify(composed);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (addControls) {
|
||||
for (const [controlname, controlfunc] of controlRegistry) {
|
||||
wrapped[controlname] = function (...args) {
|
||||
const composed = (pat) => func(pat)[name](controlfunc(...args));
|
||||
return composify(composed);
|
||||
};
|
||||
}
|
||||
}
|
||||
return wrapped;
|
||||
},
|
||||
};
|
||||
|
||||
getterRegistry.push([name, getter]);
|
||||
|
||||
// Add to functions already 'composified'
|
||||
for (const composified of composifiedRegistry) {
|
||||
Object.defineProperty(composified, name, getter);
|
||||
}
|
||||
} else {
|
||||
// No chained alignments/controls needed, so we can just add as a plain method,
|
||||
// probably more efficient this way?
|
||||
const method = function (...args) {
|
||||
const func = this;
|
||||
const composed = (pat) => func(pat)[name](...args);
|
||||
return composify(composed);
|
||||
};
|
||||
|
||||
methodRegistry.push([name, method]);
|
||||
|
||||
// Add to functions already 'composified'
|
||||
for (const composified of composifiedRegistry) {
|
||||
composified[name] = method;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function registerControl(controlname, controlfunc) {
|
||||
registerMethod(controlname);
|
||||
controlRegistry.push([controlname, controlfunc]);
|
||||
for (const subscriber of controlSubscribers) {
|
||||
subscriber(controlname, controlfunc);
|
||||
}
|
||||
}
|
||||
|
||||
export function withControls(func) {
|
||||
for (const [controlname, controlfunc] of controlRegistry) {
|
||||
func(controlname, controlfunc);
|
||||
}
|
||||
controlSubscribers.push(func);
|
||||
}
|
||||
|
||||
export function addToPrototype(name, func) {
|
||||
Pattern.prototype[name] = func;
|
||||
registerMethod(name);
|
||||
}
|
||||
|
||||
export function curryPattern(func, arity = func.length) {
|
||||
const fn = function curried(...args) {
|
||||
if (args.length >= arity) {
|
||||
return func.apply(this, args);
|
||||
}
|
||||
|
||||
const partial = function (...args2) {
|
||||
return curried.apply(this, args.concat(args2));
|
||||
};
|
||||
if (args.length == arity - 1) {
|
||||
return composify(partial);
|
||||
}
|
||||
|
||||
return partial;
|
||||
};
|
||||
if (arity == 1) {
|
||||
composify(fn);
|
||||
}
|
||||
return fn;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// The core Pattern class
|
||||
|
||||
/** @class Class representing a pattern. */
|
||||
export class Pattern {
|
||||
/**
|
||||
@@ -778,9 +643,7 @@ export class Pattern {
|
||||
* @noAutocomplete
|
||||
*/
|
||||
get firstCycleValues() {
|
||||
return this.sortHapsByPart()
|
||||
.firstCycle()
|
||||
.map((hap) => hap.value);
|
||||
return this.firstCycle().map((hap) => hap.value);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -830,7 +693,7 @@ export class Pattern {
|
||||
const otherPat = reify(other);
|
||||
return this.fmap((a) => otherPat.fmap((b) => func(a)(b))).squeezeJoin();
|
||||
}
|
||||
_opSqueezeout(other, func) {
|
||||
_opSqueezeOut(other, func) {
|
||||
const thisPat = this;
|
||||
const otherPat = reify(other);
|
||||
return otherPat.fmap((a) => thisPat.fmap((b) => func(b)(a))).squeezeJoin();
|
||||
@@ -997,11 +860,11 @@ function groupHapsBy(eq, haps) {
|
||||
const congruent = (a, b) => a.spanEquals(b);
|
||||
// Pattern<Hap<T>> -> Pattern<Hap<T[]>>
|
||||
// returned pattern contains arrays of congruent haps
|
||||
addToPrototype('collect', function () {
|
||||
Pattern.prototype.collect = function () {
|
||||
return this.withHaps((haps) =>
|
||||
groupHapsBy(congruent, haps).map((_haps) => new Hap(_haps[0].whole, _haps[0].part, _haps, {})),
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Selects indices in in stacked notes.
|
||||
@@ -1009,12 +872,12 @@ addToPrototype('collect', function () {
|
||||
* note("<[c,eb,g]!2 [c,f,ab] [d,f,ab]>")
|
||||
* .arpWith(haps => haps[2])
|
||||
* */
|
||||
addToPrototype('arpWith', function (func) {
|
||||
Pattern.prototype.arpWith = function (func) {
|
||||
return this.collect()
|
||||
.fmap((v) => reify(func(v)))
|
||||
.innerJoin()
|
||||
.withHap((h) => new Hap(h.whole, h.part, h.value.value, h.combineContext(h.value)));
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Selects indices in in stacked notes.
|
||||
@@ -1022,28 +885,27 @@ addToPrototype('arpWith', function (func) {
|
||||
* note("<[c,eb,g]!2 [c,f,ab] [d,f,ab]>")
|
||||
* .arp("0 [0,2] 1 [0,2]").slow(2)
|
||||
* */
|
||||
addToPrototype('arp', function (pat) {
|
||||
Pattern.prototype.arp = function (pat) {
|
||||
return this.arpWith((haps) => pat.fmap((i) => haps[i % haps.length]));
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
/*
|
||||
* Takes a time duration followed by one or more patterns, and shifts the given patterns in time, so they are
|
||||
* distributed equally over the given time duration. They are then combined with the pattern 'weave' is called on, after it has been stretched out (i.e. slowed down by) the time duration.
|
||||
* @name weave
|
||||
* @memberof Pattern
|
||||
* @example pan(saw).weave(4, s("bd(3,8)"), s("~ sd"))
|
||||
* @example n("0 1 2 3 4 5 6 7").weave(8, s("bd(3,8)"), s("~ sd"))
|
||||
*/
|
||||
|
||||
addToPrototype('weave', function (t, ...pats) {
|
||||
return this.weaveWith(t, ...pats.map((x) => set.out(x)));
|
||||
});
|
||||
|
||||
/**
|
||||
*/
|
||||
/*
|
||||
* Like 'weave', but accepts functions rather than patterns, which are applied to the pattern.
|
||||
* @name weaveWith
|
||||
* @memberof Pattern
|
||||
*/
|
||||
|
||||
addToPrototype('weaveWith', function (t, ...funcs) {
|
||||
const pat = this;
|
||||
@@ -1054,6 +916,7 @@ addToPrototype('weaveWith', function (t, ...funcs) {
|
||||
}
|
||||
return stack(...funcs.map((func, i) => pat.inside(t, func).early(Fraction(i).div(l))))._slow(t);
|
||||
});
|
||||
*/
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// compose matrix functions
|
||||
@@ -1151,15 +1014,15 @@ function _composeOp(a, b, func) {
|
||||
func: [(a, b) => b(a)],
|
||||
};
|
||||
|
||||
const hows = alignments.map((x) => x.charAt(0).toUpperCase() + x.slice(1));
|
||||
const hows = ['In', 'Out', 'Mix', 'Squeeze', 'SqueezeOut', 'Trig', 'Trigzero'];
|
||||
|
||||
// generate methods to do what and how
|
||||
for (const [what, [op, preprocess]] of Object.entries(composers)) {
|
||||
// make plain version, e.g. pat._add(value) adds that plain value
|
||||
// to all the values in pat
|
||||
addToPrototype('_' + what, function (value) {
|
||||
Pattern.prototype['_' + what] = function (value) {
|
||||
return this.fmap((x) => op(x, value));
|
||||
});
|
||||
};
|
||||
|
||||
// make patternified monster version
|
||||
Object.defineProperty(Pattern.prototype, what, {
|
||||
@@ -1173,7 +1036,7 @@ function _composeOp(a, b, func) {
|
||||
|
||||
// add methods to that function for each behaviour
|
||||
for (const how of hows) {
|
||||
const howfunc = function (...other) {
|
||||
wrapper[how.toLowerCase()] = function (...other) {
|
||||
var howpat = pat;
|
||||
other = sequence(other);
|
||||
if (preprocess) {
|
||||
@@ -1191,41 +1054,19 @@ function _composeOp(a, b, func) {
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
for (const [controlname, controlfunc] of controlRegistry) {
|
||||
howfunc[controlname] = (...args) => howfunc(controlfunc(...args));
|
||||
}
|
||||
wrapper[how.toLowerCase()] = howfunc;
|
||||
}
|
||||
wrapper.squeezein = wrapper.squeeze;
|
||||
|
||||
for (const [controlname, controlfunc] of controlRegistry) {
|
||||
wrapper[controlname] = (...args) => wrapper.in(controlfunc(...args));
|
||||
}
|
||||
|
||||
return wrapper;
|
||||
},
|
||||
});
|
||||
|
||||
registerMethod(what, true, true);
|
||||
}
|
||||
|
||||
// Default op to 'set', e.g. pat.squeeze(pat2) = pat.set.squeeze(pat2)
|
||||
for (const howLower of alignments) {
|
||||
// Using a 'get'ted function so that all the controls are added
|
||||
Object.defineProperty(Pattern.prototype, howLower, {
|
||||
get: function () {
|
||||
const pat = this;
|
||||
const howfunc = function (...args) {
|
||||
return pat.set[howLower](args);
|
||||
};
|
||||
for (const [controlname, controlfunc] of controlRegistry) {
|
||||
howfunc[controlname] = (...args) => howfunc(controlfunc(...args));
|
||||
}
|
||||
return howfunc;
|
||||
},
|
||||
});
|
||||
registerMethod(howLower, false, true);
|
||||
// Default op to 'set', e.g. pat.squeeze(pat2) = pat.set.squeeze(pat2)
|
||||
for (const how of hows) {
|
||||
Pattern.prototype[how.toLowerCase()] = function (...args) {
|
||||
return this.set[how.toLowerCase()](args);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// binary composers
|
||||
@@ -1237,36 +1078,36 @@ function _composeOp(a, b, func) {
|
||||
* .struct("x ~ x ~ ~ x ~ x ~ ~ ~ x ~ x ~ ~")
|
||||
* .slow(4)
|
||||
*/
|
||||
addToPrototype('struct', function (...args) {
|
||||
Pattern.prototype.struct = function (...args) {
|
||||
return this.keepif.out(...args);
|
||||
});
|
||||
addToPrototype('structAll', function (...args) {
|
||||
};
|
||||
Pattern.prototype.structAll = function (...args) {
|
||||
return this.keep.out(...args);
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Returns silence when mask is 0 or "~"
|
||||
*
|
||||
* @example
|
||||
* note("c [eb,g] d [eb,g]").mask("<1 [0 1]>").slow(2)
|
||||
*/
|
||||
addToPrototype('mask', function (...args) {
|
||||
Pattern.prototype.mask = function (...args) {
|
||||
return this.keepif.in(...args);
|
||||
});
|
||||
addToPrototype('maskAll', function (...args) {
|
||||
};
|
||||
Pattern.prototype.maskAll = function (...args) {
|
||||
return this.keep.in(...args);
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Resets the pattern to the start of the cycle for each onset of the reset pattern.
|
||||
*
|
||||
* @example
|
||||
* s("<bd lt> sd, hh*4").reset("<x@3 x(3,8)>")
|
||||
*/
|
||||
addToPrototype('reset', function (...args) {
|
||||
Pattern.prototype.reset = function (...args) {
|
||||
return this.keepif.trig(...args);
|
||||
});
|
||||
addToPrototype('resetAll', function (...args) {
|
||||
};
|
||||
Pattern.prototype.resetAll = function (...args) {
|
||||
return this.keep.trig(...args);
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Restarts the pattern for each onset of the restart pattern.
|
||||
* While reset will only reset the current cycle, restart will start from cycle 0.
|
||||
@@ -1274,12 +1115,12 @@ function _composeOp(a, b, func) {
|
||||
* @example
|
||||
* s("<bd lt> sd, hh*4").restart("<x@3 x(3,8)>")
|
||||
*/
|
||||
addToPrototype('restart', function (...args) {
|
||||
Pattern.prototype.restart = function (...args) {
|
||||
return this.keepif.trigzero(...args);
|
||||
});
|
||||
addToPrototype('restartAll', function (...args) {
|
||||
};
|
||||
Pattern.prototype.restartAll = function (...args) {
|
||||
return this.keep.trigzero(...args);
|
||||
});
|
||||
};
|
||||
})();
|
||||
|
||||
// aliases
|
||||
@@ -1524,68 +1365,36 @@ export function pm(...args) {
|
||||
polymeter(...args);
|
||||
}
|
||||
|
||||
export const mask = curryPattern((a, b) => reify(b).mask(a));
|
||||
export const struct = curryPattern((a, b) => reify(b).struct(a));
|
||||
export const superimpose = curryPattern((a, b) => reify(b).superimpose(...a));
|
||||
|
||||
const methodToFunction = function (name, addAlignments = false) {
|
||||
const func = curryPattern((a, b) => reify(b)[name](a));
|
||||
|
||||
withControls((controlname, controlfunc) => {
|
||||
func[controlname] = function (...pats) {
|
||||
return func(controlfunc(...pats));
|
||||
};
|
||||
});
|
||||
|
||||
if (addAlignments) {
|
||||
for (const alignment of alignments) {
|
||||
func[alignment] = curryPattern((a, b) => reify(b)[name][alignment](a));
|
||||
withControls((controlname, controlfunc) => {
|
||||
func[alignment][controlname] = function (...pats) {
|
||||
return func[alignment](controlfunc(...pats));
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return func;
|
||||
};
|
||||
export const mask = curry((a, b) => reify(b).mask(a));
|
||||
export const struct = curry((a, b) => reify(b).struct(a));
|
||||
export const superimpose = curry((a, b) => reify(b).superimpose(...a));
|
||||
|
||||
// operators
|
||||
export const set = methodToFunction('set', true);
|
||||
export const keep = methodToFunction('keep', true);
|
||||
export const keepif = methodToFunction('keepif', true);
|
||||
export const add = methodToFunction('add', true);
|
||||
export const sub = methodToFunction('sub', true);
|
||||
export const mul = methodToFunction('mul', true);
|
||||
export const div = methodToFunction('div', true);
|
||||
export const mod = methodToFunction('mod', true);
|
||||
export const pow = methodToFunction('pow', true);
|
||||
export const band = methodToFunction('band', true);
|
||||
export const bor = methodToFunction('bor', true);
|
||||
export const bxor = methodToFunction('bxor', true);
|
||||
export const blshift = methodToFunction('blshift', true);
|
||||
export const brshift = methodToFunction('brshift', true);
|
||||
export const lt = methodToFunction('lt', true);
|
||||
export const gt = methodToFunction('gt', true);
|
||||
export const lte = methodToFunction('lte', true);
|
||||
export const gte = methodToFunction('gte', true);
|
||||
export const eq = methodToFunction('eq', true);
|
||||
export const eqt = methodToFunction('eqt', true);
|
||||
export const ne = methodToFunction('ne', true);
|
||||
export const net = methodToFunction('net', true);
|
||||
export const and = methodToFunction('and', true);
|
||||
export const or = methodToFunction('or', true);
|
||||
export const func = methodToFunction('func', true);
|
||||
|
||||
// alignments
|
||||
// export const in = methodToFunction('in'); // reserved word :(
|
||||
export const out = methodToFunction('out');
|
||||
export const mix = methodToFunction('mix');
|
||||
export const squeeze = methodToFunction('squeeze');
|
||||
export const squeezeout = methodToFunction('squeezeout');
|
||||
export const trig = methodToFunction('trig');
|
||||
export const trigzero = methodToFunction('trigzero');
|
||||
export const set = curry((a, b) => reify(b).set(a));
|
||||
export const keep = curry((a, b) => reify(b).keep(a));
|
||||
export const keepif = curry((a, b) => reify(b).keepif(a));
|
||||
export const add = curry((a, b) => reify(b).add(a));
|
||||
export const sub = curry((a, b) => reify(b).sub(a));
|
||||
export const mul = curry((a, b) => reify(b).mul(a));
|
||||
export const div = curry((a, b) => reify(b).div(a));
|
||||
export const mod = curry((a, b) => reify(b).mod(a));
|
||||
export const pow = curry((a, b) => reify(b).pow(a));
|
||||
export const band = curry((a, b) => reify(b).band(a));
|
||||
export const bor = curry((a, b) => reify(b).bor(a));
|
||||
export const bxor = curry((a, b) => reify(b).bxor(a));
|
||||
export const blshift = curry((a, b) => reify(b).blshift(a));
|
||||
export const brshift = curry((a, b) => reify(b).brshift(a));
|
||||
export const lt = curry((a, b) => reify(b).lt(a));
|
||||
export const gt = curry((a, b) => reify(b).gt(a));
|
||||
export const lte = curry((a, b) => reify(b).lte(a));
|
||||
export const gte = curry((a, b) => reify(b).gte(a));
|
||||
export const eq = curry((a, b) => reify(b).eq(a));
|
||||
export const eqt = curry((a, b) => reify(b).eqt(a));
|
||||
export const ne = curry((a, b) => reify(b).ne(a));
|
||||
export const net = curry((a, b) => reify(b).net(a));
|
||||
export const and = curry((a, b) => reify(b).and(a));
|
||||
export const or = curry((a, b) => reify(b).or(a));
|
||||
export const func = curry((a, b) => reify(b).func(a));
|
||||
|
||||
/**
|
||||
* Registers a new pattern method. The method is added to the Pattern class + the standalone function is returned from register.
|
||||
@@ -1604,43 +1413,27 @@ export function register(name, func, patternify = true) {
|
||||
return result;
|
||||
}
|
||||
const arity = func.length;
|
||||
var pfunc; // the patternified function
|
||||
|
||||
registerMethod(name);
|
||||
|
||||
var pfunc;
|
||||
|
||||
if (patternify) {
|
||||
pfunc = function (...args) {
|
||||
args = args.map(reify);
|
||||
const pat = args[args.length - 1];
|
||||
if (arity === 1) {
|
||||
return func(pat);
|
||||
}
|
||||
const [left, ...right] = args.slice(0, -1);
|
||||
let mapFn = (...args) => {
|
||||
// make sure to call func with the correct argument count
|
||||
// args.length is expected to be <= arity-1
|
||||
// so we set undefined args explicitly undefined
|
||||
Array(arity - 1)
|
||||
.fill()
|
||||
.map((_, i) => args[i] ?? undefined);
|
||||
return func(...args, pat);
|
||||
};
|
||||
mapFn = curryPattern(mapFn, arity - 1);
|
||||
|
||||
const app = function (acc, p, i) {
|
||||
return acc.appLeft(p);
|
||||
};
|
||||
const start = left.fmap(mapFn);
|
||||
|
||||
return right.reduce(app, start).innerJoin();
|
||||
pfunc = function (...args) {
|
||||
args = args.map(reify);
|
||||
const pat = args[args.length - 1];
|
||||
if (arity === 1) {
|
||||
return func(pat);
|
||||
}
|
||||
const [left, ...right] = args.slice(0, -1);
|
||||
let mapFn = (...args) => {
|
||||
// make sure to call func with the correct argument count
|
||||
// args.length is expected to be <= arity-1
|
||||
// so we set undefined args explicitly undefined
|
||||
Array(arity - 1)
|
||||
.fill()
|
||||
.map((_, i) => args[i] ?? undefined);
|
||||
return func(...args, pat);
|
||||
};
|
||||
} else {
|
||||
pfunc = function (...args) {
|
||||
args = args.map(reify);
|
||||
return func(...args);
|
||||
};
|
||||
}
|
||||
mapFn = curry(mapFn, null, arity - 1);
|
||||
return right.reduce((acc, p) => acc.appLeft(p), left.fmap(mapFn)).innerJoin();
|
||||
};
|
||||
|
||||
Pattern.prototype[name] = function (...args) {
|
||||
// For methods that take a single argument (plus 'this'), allow
|
||||
@@ -1664,7 +1457,7 @@ export function register(name, func, patternify = true) {
|
||||
|
||||
// toplevel functions get curried as well as patternified
|
||||
// because pfunc uses spread args, we need to state the arity explicitly!
|
||||
return curryPattern(pfunc, arity);
|
||||
return curry(pfunc, null, arity);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
@@ -2430,7 +2223,7 @@ const _loopAt = function (factor, pat, cps = 1) {
|
||||
.slow(factor);
|
||||
};
|
||||
|
||||
/**
|
||||
/*
|
||||
* Chops samples into the given number of slices, triggering those slices with a given pattern of slice numbers.
|
||||
* @name slice
|
||||
* @memberof Pattern
|
||||
@@ -2438,12 +2231,11 @@ const _loopAt = function (factor, pat, cps = 1) {
|
||||
* @example
|
||||
* await samples('github:tidalcycles/Dirt-Samples/master')
|
||||
* s("breaks165").slice(8, "0 1 <2 2*2> 3 [4 0] 5 6 7".every(3, rev)).slow(1.5)
|
||||
*/
|
||||
const slice = register(
|
||||
'slice',
|
||||
function (npat, ipat, opat) {
|
||||
return npat.innerBind((n) =>
|
||||
ipat.outerBind((i) =>
|
||||
ipat.outerBind((i) =>
|
||||
opat.outerBind((o) => {
|
||||
// If it's not an object, assume it's a string and make it a 's' control parameter
|
||||
o = o instanceof Object ? o : { s: o };
|
||||
@@ -2456,8 +2248,9 @@ const slice = register(
|
||||
},
|
||||
false, // turns off auto-patternification
|
||||
);
|
||||
*/
|
||||
|
||||
/**
|
||||
/*
|
||||
* Works the same as slice, but changes the playback speed of each slice to match the duration of its step.
|
||||
* @name splice
|
||||
* @memberof Pattern
|
||||
@@ -2465,7 +2258,6 @@ const slice = register(
|
||||
* @example
|
||||
* await samples('github:tidalcycles/Dirt-Samples/master')
|
||||
* s("breaks165").splice(8, "0 1 [2 3 0]@2 3 0@2 7").hurry(0.65)
|
||||
*/
|
||||
const splice = register(
|
||||
'splice',
|
||||
function (npat, ipat, opat) {
|
||||
@@ -2481,7 +2273,8 @@ const splice = register(
|
||||
});
|
||||
},
|
||||
false, // turns off auto-patternification
|
||||
);
|
||||
);
|
||||
*/
|
||||
|
||||
const { loopAt, loopat } = register(['loopAt', 'loopat'], function (factor, pat) {
|
||||
return _loopAt(factor, pat, 1);
|
||||
|
||||
+19
-3
@@ -2,6 +2,7 @@ import { Cyclist } from './cyclist.mjs';
|
||||
import { evaluate as _evaluate } from './evaluate.mjs';
|
||||
import { logger } from './logger.mjs';
|
||||
import { setTime } from './time.mjs';
|
||||
import { evalScope } from './evaluate.mjs';
|
||||
|
||||
export function repl({
|
||||
interval,
|
||||
@@ -17,13 +18,12 @@ export function repl({
|
||||
}) {
|
||||
const scheduler = new Cyclist({
|
||||
interval,
|
||||
onTrigger: async (hap, deadline, duration) => {
|
||||
onTrigger: async (hap, deadline, duration, cps) => {
|
||||
try {
|
||||
if (!hap.context.onTrigger || !hap.context.dominantTrigger) {
|
||||
await defaultOutput(hap, deadline, duration);
|
||||
}
|
||||
if (hap.context.onTrigger) {
|
||||
const cps = 1;
|
||||
// call signature of output / onTrigger is different...
|
||||
await hap.context.onTrigger(getTime() + deadline, hap, getTime(), cps);
|
||||
}
|
||||
@@ -42,6 +42,17 @@ export function repl({
|
||||
}
|
||||
try {
|
||||
beforeEval?.({ code });
|
||||
scheduler.setCps(1); // reset cps in case the code does not contain a setCps call
|
||||
// problem: when the code does contain a setCps after an awaited promise,
|
||||
// the cps will be 1 until the promise resolves
|
||||
// example:
|
||||
/*
|
||||
await new Promise(resolve => setTimeout(resolve,1000))
|
||||
setCps(.5)
|
||||
note("c a f e")
|
||||
*/
|
||||
// to make sure the setCps inside the code is called immediately,
|
||||
// it has to be placed first
|
||||
let { pattern } = await _evaluate(code, transpiler);
|
||||
|
||||
logger(`[eval] code updated`);
|
||||
@@ -58,5 +69,10 @@ export function repl({
|
||||
const stop = () => scheduler.stop();
|
||||
const start = () => scheduler.start();
|
||||
const pause = () => scheduler.pause();
|
||||
return { scheduler, evaluate, start, stop, pause };
|
||||
const setCps = (cps) => scheduler.setCps(cps);
|
||||
evalScope({
|
||||
setCps,
|
||||
setcps: setCps,
|
||||
});
|
||||
return { scheduler, evaluate, start, stop, pause, setCps };
|
||||
}
|
||||
|
||||
@@ -45,9 +45,6 @@ import {
|
||||
rev,
|
||||
time,
|
||||
run,
|
||||
hitch,
|
||||
set,
|
||||
begin,
|
||||
} from '../index.mjs';
|
||||
|
||||
import { steady } from '../signal.mjs';
|
||||
@@ -207,7 +204,7 @@ describe('Pattern', () => {
|
||||
),
|
||||
);
|
||||
});
|
||||
it('can squeezeout() structure', () => {
|
||||
it('can SqueezeOut() structure', () => {
|
||||
sameFirst(
|
||||
sequence(1, [2, 3]).add.squeezeout(10, 20, 30),
|
||||
sequence([11, [12, 13]], [21, [22, 23]], [31, [32, 33]]),
|
||||
@@ -255,7 +252,7 @@ describe('Pattern', () => {
|
||||
),
|
||||
);
|
||||
});
|
||||
it('can squeezeout() structure', () => {
|
||||
it('can SqueezeOut() structure', () => {
|
||||
sameFirst(sequence(1, [2, 3]).keep.squeezeout(10, 20, 30), sequence([1, [2, 3]], [1, [2, 3]], [1, [2, 3]]));
|
||||
});
|
||||
});
|
||||
@@ -297,7 +294,7 @@ describe('Pattern', () => {
|
||||
),
|
||||
);
|
||||
});
|
||||
it('can squeezeout() structure', () => {
|
||||
it('can SqueezeOut() structure', () => {
|
||||
sameFirst(sequence(1, [2, 3]).keepif.squeezeout(true, true, false), sequence([1, [2, 3]], [1, [2, 3]], silence));
|
||||
});
|
||||
});
|
||||
@@ -932,14 +929,6 @@ describe('Pattern', () => {
|
||||
});
|
||||
});
|
||||
describe('alignments', () => {
|
||||
it('Can combine controls', () => {
|
||||
sameFirst(s('bd').set.in.n(3), s('bd').n(3));
|
||||
sameFirst(s('bd').set.squeeze.n(3, 4), sequence(s('bd').n(3), s('bd').n(4)));
|
||||
});
|
||||
it('Can combine functions with alignmed controls', () => {
|
||||
sameFirst(s('bd').apply(fast(2).set(n(3))), s('bd').fast(2).set.in.n(3));
|
||||
sameFirst(s('bd').apply(fast(2).set.in.n(3)), s('bd').fast(2).set.in.n(3));
|
||||
});
|
||||
it('Can squeeze arguments', () => {
|
||||
expect(sequence(1, 2).add.squeeze(4, 5).firstCycle()).toStrictEqual(sequence(5, 6, 6, 7).firstCycle());
|
||||
});
|
||||
@@ -970,7 +959,7 @@ describe('Pattern', () => {
|
||||
sameFirst(s('a', 'b').hurry(2), s('a', 'b').fast(2).speed(2));
|
||||
});
|
||||
});
|
||||
describe('composable functions', () => {
|
||||
/*describe('composable functions', () => {
|
||||
it('Can compose functions', () => {
|
||||
sameFirst(sequence(3, 4).fast(2).rev().fast(2), fast(2).rev().fast(2)(sequence(3, 4)));
|
||||
});
|
||||
@@ -1012,5 +1001,5 @@ describe('Pattern', () => {
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
});*/
|
||||
});
|
||||
|
||||
@@ -139,7 +139,7 @@ export const removeUndefineds = (xs) => xs.filter((x) => x != undefined);
|
||||
export const flatten = (arr) => [].concat(...arr);
|
||||
|
||||
export const id = (a) => a;
|
||||
export const constant = curry((a, b) => a);
|
||||
export const constant = (a, b) => a;
|
||||
|
||||
export const listRange = (min, max) => Array.from({ length: max - min + 1 }, (_, i) => i + min);
|
||||
|
||||
|
||||
@@ -44,6 +44,6 @@ function createClock(
|
||||
};
|
||||
const getPhase = () => phase;
|
||||
// setCallback
|
||||
return { setDuration, start, stop, pause, duration, getPhase, minLatency };
|
||||
return { setDuration, start, stop, pause, duration, interval, getPhase, minLatency };
|
||||
}
|
||||
export default createClock;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# @strudel.cycles/link
|
||||
|
||||
This is an experiment to find out how to hook up ableton link with strudel.
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
cd packages/link/ && npm i
|
||||
node link-server.mjs # tested with node 18
|
||||
```
|
||||
|
||||
This will play the pattern in `link-server.mjs`, which will be synced automatically via Ableton Link!
|
||||
|
||||
## Notes
|
||||
|
||||
- The sound generation uses [node-web-audio-api](https://github.com/audiojs/web-audio-api), supporting only oscillators with a fixed envelope
|
||||
- All functions from `@strudel.cycles/webaudio` could be supported when the [node-webaudio branch](https://github.com/tidalcycles/strudel/tree/node-webaudio) is further developed. This includes refactoring the webaudio stuff to allow passing an audio context + making sure it can be imported in node
|
||||
- To make this live codable, I see 2 options:
|
||||
- headless: add an express server (or similar) that allows sending `PUT` requests to change the pattern.
|
||||
- browser: find a way combine the REPL with the ableton clock.
|
||||
- the server could pass the callback from ableton link via http or websocket
|
||||
- the client could then use these messages to control the scheduler
|
||||
- could try implementing getTime as a LERP between received beat values
|
||||
- or do not use the scheduler and just query within the received beat values
|
||||
- highlighting is another thing..
|
||||
@@ -0,0 +1 @@
|
||||
export * from './link-client.mjs';
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
link-server.mjs - <short description TODO>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/osc/osc.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { connectOSC } from '@strudel.cycles/osc';
|
||||
import { Pattern, silence } from '@strudel.cycles/core';
|
||||
|
||||
/**
|
||||
*
|
||||
* Connects to Ableton Link
|
||||
*
|
||||
* @name link
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
*/
|
||||
console.log('link-client');
|
||||
Pattern.prototype.link = async function () {
|
||||
const osc = await connectOSC();
|
||||
osc.on('*', (msg) => {
|
||||
const [begin, end, cps] = msg.address.split('/');
|
||||
console.log(begin, end, cps);
|
||||
});
|
||||
return silence;
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
import { evaluate } from '@strudel.cycles/transpiler';
|
||||
import { evalScope, controls, getFrequency } from '@strudel.cycles/core';
|
||||
import { AudioContext, OscillatorNode, GainNode } from 'node-web-audio-api';
|
||||
import abletonlink from 'abletonlink';
|
||||
|
||||
//// prepare pattern
|
||||
|
||||
await evalScope(
|
||||
controls,
|
||||
import('@strudel.cycles/core'),
|
||||
import('@strudel.cycles/tonal'),
|
||||
import('@strudel.cycles/mini'),
|
||||
// import('@strudel.cycles/xen'),
|
||||
// import('@strudel.cycles/osc'),
|
||||
);
|
||||
|
||||
const { pattern } = await evaluate(
|
||||
`note("<Dm7 G7 C^7!2>".voicings('lefthand')).ply(8).gain(.0625).s('sawtooth').early(.06)`,
|
||||
);
|
||||
|
||||
//// web audio logic
|
||||
|
||||
const ac = new AudioContext();
|
||||
|
||||
export function playHaps(haps, beat, cps = 1) {
|
||||
const ct = ac.currentTime;
|
||||
haps
|
||||
.filter((hap) => hap.hasOnset())
|
||||
.forEach((hap) => {
|
||||
const deadline = (hap.whole.begin - beat) / cps;
|
||||
const t = ct + deadline;
|
||||
const freq = getFrequency(hap) ?? 220;
|
||||
const { gain = 1, s = 'triangle' } = hap.value;
|
||||
|
||||
const env = new GainNode(ac);
|
||||
env.connect(ac.destination);
|
||||
env.gain.value = 0;
|
||||
env.gain.setValueAtTime(0, t);
|
||||
env.gain.linearRampToValueAtTime(gain, t + 0.02);
|
||||
env.gain.exponentialRampToValueAtTime(0.0001, t + hap.duration);
|
||||
|
||||
const osc = new OscillatorNode(ac);
|
||||
osc.frequency.value = freq;
|
||||
osc.type = s;
|
||||
osc.connect(env);
|
||||
osc.start(t);
|
||||
osc.stop(t + hap.duration);
|
||||
});
|
||||
}
|
||||
|
||||
//// link scheduling
|
||||
|
||||
const link = new abletonlink();
|
||||
|
||||
let lastBeat;
|
||||
link.startUpdate(60, (beat, phase, bpm) => {
|
||||
if (lastBeat !== undefined) {
|
||||
const cps = bpm / 60;
|
||||
const haps = pattern.slow(2).queryArc(lastBeat, beat);
|
||||
playHaps(haps, lastBeat, cps);
|
||||
}
|
||||
lastBeat = beat;
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "@strudel.cycles/link",
|
||||
"version": "0.6.0",
|
||||
"description": "Ableton Link Support for Strudel",
|
||||
"main": "link-server.mjs",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node link-server.mjs"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/tidalcycles/strudel.git"
|
||||
},
|
||||
"keywords": [
|
||||
"tidalcycles",
|
||||
"strudel",
|
||||
"pattern",
|
||||
"livecoding",
|
||||
"algorave"
|
||||
],
|
||||
"author": "Felix Roos <flix91@gmail.com>",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"bugs": {
|
||||
"url": "https://github.com/tidalcycles/strudel/issues"
|
||||
},
|
||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||
"devDependencies": {
|
||||
"abletonlink": "^0.1.3",
|
||||
"node-web-audio-api": "^0.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@strudel.cycles/core": "workspace:^0.6.8",
|
||||
"@strudel.cycles/mini": "workspace:^0.6.0",
|
||||
"@strudel.cycles/osc": "workspace:^0.6.0",
|
||||
"@strudel.cycles/tonal": "workspace:^0.6.0",
|
||||
"@strudel.cycles/transpiler": "workspace:^0.6.0"
|
||||
}
|
||||
}
|
||||
+33
-14
@@ -13,24 +13,43 @@ npm i @strudel.cycles/react
|
||||
Here is a minimal example of how to set up a MiniRepl:
|
||||
|
||||
```jsx
|
||||
import { evalScope, controls } from '@strudel.cycles/core';
|
||||
import * as React from 'react';
|
||||
import '@strudel.cycles/react/dist/style.css';
|
||||
import { MiniRepl } from '@strudel.cycles/react';
|
||||
import { prebake } from '../repl/src/prebake.mjs';
|
||||
import { evalScope, controls } from '@strudel.cycles/core';
|
||||
import { samples, initAudioOnFirstClick } from '@strudel.cycles/webaudio';
|
||||
|
||||
evalScope(
|
||||
controls,
|
||||
import('@strudel.cycles/core'),
|
||||
import('@strudel.cycles/tonal'),
|
||||
import('@strudel.cycles/mini'),
|
||||
import('@strudel.cycles/webaudio'),
|
||||
/* probably import other strudel packages */
|
||||
);
|
||||
async function prebake() {
|
||||
await samples(
|
||||
'https://strudel.tidalcycles.org/tidal-drum-machines.json',
|
||||
'github:ritchse/tidal-drum-machines/main/machines/'
|
||||
);
|
||||
await samples(
|
||||
'https://strudel.tidalcycles.org/EmuSP12.json',
|
||||
'https://strudel.tidalcycles.org/EmuSP12/'
|
||||
);
|
||||
}
|
||||
|
||||
prebake();
|
||||
async function init() {
|
||||
await evalScope(
|
||||
controls,
|
||||
import('@strudel.cycles/core'),
|
||||
import('@strudel.cycles/mini'),
|
||||
import('@strudel.cycles/webaudio'),
|
||||
import('@strudel.cycles/tonal')
|
||||
);
|
||||
await prebake();
|
||||
initAudioOnFirstClick();
|
||||
}
|
||||
|
||||
export function Repl({ tune }) {
|
||||
return <MiniRepl tune={tune} hideOutsideView={true} />;
|
||||
if (typeof window !== 'undefined') {
|
||||
init();
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
return <MiniRepl tune={`s("bd sd,hh*4")`} />;
|
||||
}
|
||||
```
|
||||
|
||||
For a more sophisticated example, check out the [nano-repl](./examples/nano-repl/)!
|
||||
- Open [example on stackblitz](https://stackblitz.com/edit/react-ts-saaair?file=tune.tsx,App.tsx)
|
||||
- Also check out the [nano-repl](./examples/nano-repl/) for a more sophisticated example
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel.cycles/react",
|
||||
"version": "0.6.0",
|
||||
"version": "0.6.4",
|
||||
"description": "React components for strudel",
|
||||
"main": "src/index.js",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -7,7 +7,7 @@ import strudelTheme from '../themes/strudel-theme';
|
||||
import './style.css';
|
||||
import { useCallback } from 'react';
|
||||
import { autocompletion } from '@codemirror/autocomplete';
|
||||
import { strudelAutocomplete } from './Autocomplete';
|
||||
//import { strudelAutocomplete } from './Autocomplete';
|
||||
import { vim } from '@replit/codemirror-vim';
|
||||
import { emacs } from '@replit/codemirror-emacs';
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import useHighlighting from '../hooks/useHighlighting.mjs';
|
||||
import useStrudel from '../hooks/useStrudel.mjs';
|
||||
import CodeMirror6, { flash } from './CodeMirror6';
|
||||
import { Icon } from './Icon';
|
||||
import styles from './MiniRepl.module.css';
|
||||
import './style.css';
|
||||
import { logger } from '@strudel.cycles/core';
|
||||
import useEvent from '../hooks/useEvent.mjs';
|
||||
@@ -129,19 +128,31 @@ export function MiniRepl({
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.container} ref={ref}>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.buttons}>
|
||||
<button className={cx(styles.button, started ? 'animate-pulse' : '')} onClick={() => togglePlay()}>
|
||||
<div className="overflow-hidden rounded-t-md bg-background border border-lineHighlight" ref={ref}>
|
||||
<div className="flex justify-between bg-lineHighlight">
|
||||
<div className="flex">
|
||||
<button
|
||||
className={cx(
|
||||
'cursor-pointer w-16 flex items-center justify-center p-1 border-r border-lineHighlight text-foreground bg-lineHighlight hover:bg-background',
|
||||
started ? 'animate-pulse' : '',
|
||||
)}
|
||||
onClick={() => togglePlay()}
|
||||
>
|
||||
<Icon type={started ? 'stop' : 'play'} />
|
||||
</button>
|
||||
<button className={cx(isDirty ? styles.button : styles.buttonDisabled)} onClick={() => activateCode()}>
|
||||
<button
|
||||
className={cx(
|
||||
'w-16 flex items-center justify-center p-1 text-foreground border-lineHighlight bg-lineHighlight',
|
||||
isDirty ? 'text-foreground hover:bg-background cursor-pointer' : 'opacity-50 cursor-not-allowed',
|
||||
)}
|
||||
onClick={() => activateCode()}
|
||||
>
|
||||
<Icon type="refresh" />
|
||||
</button>
|
||||
</div>
|
||||
{error && <div className={styles.error}>{error.message}</div>}
|
||||
{error && <div className="text-right p-1 text-sm text-red-200">{error.message}</div>}
|
||||
</div>
|
||||
<div className={styles.body}>
|
||||
<div className="overflow-auto relative">
|
||||
{show && <CodeMirror6 value={code} onChange={setCode} onViewChanged={setView} theme={theme} />}
|
||||
</div>
|
||||
{drawTime && (
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
.container {
|
||||
@apply overflow-hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
@apply flex justify-between bg-lineHighlight border-t border-l border-r border-lineHighlight rounded-t-md overflow-hidden;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
@apply flex;
|
||||
}
|
||||
|
||||
.button {
|
||||
@apply cursor-pointer w-16 flex items-center justify-center p-1 border-r border-lineHighlight text-foreground hover:bg-background;
|
||||
}
|
||||
|
||||
.buttonDisabled {
|
||||
@apply w-16 flex items-center justify-center p-1 opacity-50 cursor-not-allowed border-r border-lineHighlight;
|
||||
}
|
||||
|
||||
.error {
|
||||
@apply text-right p-1 text-sm text-red-200;
|
||||
}
|
||||
|
||||
.body {
|
||||
@apply overflow-auto relative;
|
||||
}
|
||||
@@ -1,3 +1,15 @@
|
||||
:root {
|
||||
--background: #222;
|
||||
--lineBackground: #22222250;
|
||||
--foreground: #fff;
|
||||
--caret: #ffcc00;
|
||||
--selection: rgba(128, 203, 196, 0.5);
|
||||
--selectionMatch: #036dd626;
|
||||
--lineHighlight: #00000050;
|
||||
--gutterBackground: transparent;
|
||||
--gutterForeground: #8a919966;
|
||||
}
|
||||
|
||||
.cm-editor {
|
||||
background-color: transparent !important;
|
||||
height: 100%;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { setHighlights } from '../components/CodeMirror6';
|
||||
const round = (x) => Math.round(x * 1000) / 1000;
|
||||
|
||||
function useHighlighting({ view, pattern, active, getTime }) {
|
||||
const highlights = useRef([]);
|
||||
@@ -14,7 +15,7 @@ function useHighlighting({ view, pattern, active, getTime }) {
|
||||
// force min framerate of 10 fps => fixes crash on tab refocus, where lastEnd could be far away
|
||||
// see https://github.com/tidalcycles/strudel/issues/108
|
||||
const begin = Math.max(lastEnd.current ?? audioTime, audioTime - 1 / 10, -0.01); // negative time seems buggy
|
||||
const span = [begin, audioTime + 1 / 60];
|
||||
const span = [round(begin), round(audioTime + 1 / 60)];
|
||||
lastEnd.current = span[1];
|
||||
highlights.current = highlights.current.filter((hap) => hap.whole.end > audioTime); // keep only highlights that are still active
|
||||
const haps = pattern.queryArc(...span).filter((hap) => hap.hasOnset());
|
||||
|
||||
@@ -32,7 +32,7 @@ function useStrudel({
|
||||
const shouldPaint = useCallback((pat) => !!(pat?.context?.onPaint && drawContext), [drawContext]);
|
||||
|
||||
// TODO: make sure this hook reruns when scheduler.started changes
|
||||
const { scheduler, evaluate, start, stop, pause } = useMemo(
|
||||
const { scheduler, evaluate, start, stop, pause, setCps } = useMemo(
|
||||
() =>
|
||||
repl({
|
||||
interval,
|
||||
@@ -153,6 +153,7 @@ function useStrudel({
|
||||
stop,
|
||||
pause,
|
||||
togglePlay,
|
||||
setCps,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
module.exports = {
|
||||
content: ['./src/**/*.{js,jsx,ts,tsx}'],
|
||||
theme: {
|
||||
extend: {},
|
||||
extend: {
|
||||
colors: {
|
||||
// codemirror-theme settings
|
||||
background: 'var(--background)',
|
||||
lineBackground: 'var(--lineBackground)',
|
||||
foreground: 'var(--foreground)',
|
||||
caret: 'var(--caret)',
|
||||
selection: 'var(--selection)',
|
||||
selectionMatch: 'var(--selectionMatch)',
|
||||
gutterBackground: 'var(--gutterBackground)',
|
||||
gutterForeground: 'var(--gutterForeground)',
|
||||
gutterBorder: 'var(--gutterBorder)',
|
||||
lineHighlight: 'var(--lineHighlight)',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
corePlugins: {
|
||||
|
||||
Generated
+66
@@ -120,6 +120,25 @@ importers:
|
||||
vite: 3.2.5
|
||||
vitest: 0.25.8
|
||||
|
||||
packages/link:
|
||||
specifiers:
|
||||
'@strudel.cycles/core': workspace:^0.6.8
|
||||
'@strudel.cycles/mini': workspace:^0.6.0
|
||||
'@strudel.cycles/osc': workspace:^0.6.0
|
||||
'@strudel.cycles/tonal': workspace:^0.6.0
|
||||
'@strudel.cycles/transpiler': workspace:^0.6.0
|
||||
abletonlink: ^0.1.3
|
||||
node-web-audio-api: ^0.5.0
|
||||
dependencies:
|
||||
'@strudel.cycles/core': link:../core
|
||||
'@strudel.cycles/mini': link:../mini
|
||||
'@strudel.cycles/osc': link:../osc
|
||||
'@strudel.cycles/tonal': link:../tonal
|
||||
'@strudel.cycles/transpiler': link:../transpiler
|
||||
devDependencies:
|
||||
abletonlink: 0.1.3
|
||||
node-web-audio-api: 0.5.0
|
||||
|
||||
packages/midi:
|
||||
specifiers:
|
||||
'@strudel.cycles/core': workspace:*
|
||||
@@ -3199,6 +3218,22 @@ packages:
|
||||
use-sync-external-store: 1.2.0_react@18.2.0
|
||||
dev: false
|
||||
|
||||
/@napi-rs/cli/2.14.8:
|
||||
resolution: {integrity: sha512-IvA3s8BqohMdUbOkFn7+23u1dhIJZCkA8Xps7DD4SLdCMbcbUF6MUuKiqxuqmVHBFTaxU25sU56WdX3efqGgPw==}
|
||||
engines: {node: '>= 10'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/@napi-rs/triples/1.1.0:
|
||||
resolution: {integrity: sha512-XQr74QaLeMiqhStEhLn1im9EOMnkypp7MZOwQhGzqp2Weu5eQJbpPxWxixxlYRKWPOmJjsk6qYfYH9kq43yc2w==}
|
||||
dev: true
|
||||
|
||||
/@node-rs/helper/1.3.3:
|
||||
resolution: {integrity: sha512-p4OdfQObGN9YFy5WZaGwlPYICQSe7xZYyXB0sxREmvj1HzGKp5bPg2PlfgfMZEfnjIA882B9ZrnagYzZihIwjA==}
|
||||
dependencies:
|
||||
'@napi-rs/triples': 1.1.0
|
||||
dev: true
|
||||
|
||||
/@nodelib/fs.scandir/2.1.5:
|
||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -4331,6 +4366,15 @@ packages:
|
||||
/abbrev/1.1.1:
|
||||
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
|
||||
|
||||
/abletonlink/0.1.3:
|
||||
resolution: {integrity: sha512-rPMS0X6+wcW607Lpr3XeqfMJ26oNDc/Yx6+5oEtkS9CYr2rKRh6dfiaHpDxhoEqKZtBrZY0JWOlkZhD49tesjQ==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
bindings: 1.5.0
|
||||
node-addon-api: 2.0.2
|
||||
dev: true
|
||||
|
||||
/acorn-jsx/5.3.2_acorn@8.8.2:
|
||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||
peerDependencies:
|
||||
@@ -4881,6 +4925,12 @@ packages:
|
||||
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
/bindings/1.5.0:
|
||||
resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
|
||||
dependencies:
|
||||
file-uri-to-path: 1.0.0
|
||||
dev: true
|
||||
|
||||
/bl/4.1.0:
|
||||
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
|
||||
dependencies:
|
||||
@@ -6849,6 +6899,10 @@ packages:
|
||||
glob: 7.2.3
|
||||
dev: true
|
||||
|
||||
/file-uri-to-path/1.0.0:
|
||||
resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
|
||||
dev: true
|
||||
|
||||
/filelist/1.0.4:
|
||||
resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==}
|
||||
dependencies:
|
||||
@@ -9713,6 +9767,10 @@ packages:
|
||||
semver: 5.7.1
|
||||
dev: true
|
||||
|
||||
/node-addon-api/2.0.2:
|
||||
resolution: {integrity: sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==}
|
||||
dev: true
|
||||
|
||||
/node-domexception/1.0.0:
|
||||
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
|
||||
engines: {node: '>=10.5.0'}
|
||||
@@ -9805,6 +9863,14 @@ packages:
|
||||
'@babel/parser': 7.20.13
|
||||
dev: false
|
||||
|
||||
/node-web-audio-api/0.5.0:
|
||||
resolution: {integrity: sha512-HMFQHti2RnhgAzIrK6PSpDqgwKHMdeWsVILBwEQcCWzol9nGDK+QQQCJPWpMTfvTuGsfEletzRqz+p6fhcjDuA==}
|
||||
engines: {node: '>= 14'}
|
||||
dependencies:
|
||||
'@napi-rs/cli': 2.14.8
|
||||
'@node-rs/helper': 1.3.3
|
||||
dev: true
|
||||
|
||||
/nopt/4.0.3:
|
||||
resolution: {integrity: sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==}
|
||||
hasBin: true
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
<svg height="100%" viewBox="0.00 0.00 538.13 507.20" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet">
|
||||
<defs><pattern id="warning" width="12" height="12" patternUnits="userSpaceOnUse" patternTransform="rotate(45 50 50)">
|
||||
<line class="line0" stroke-width="6px" x1="3" x2="3" y2="12"></line>
|
||||
<line class="line1" stroke-width="6px" x1="9" x2="9" y2="12"></line>
|
||||
</pattern></defs><g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 503.2)">
|
||||
|
||||
<!-- @strudel.cycles/core@0.6.8 -->
|
||||
<g id="node1" class="node module-_strudel_cycles_core maintainer-yaxupaxo maintainer-felixroos license-agpl_3_0_or_later" data-module="@strudel.cycles/core@0.6.8">
|
||||
<title>@strudel.cycles/core@0.6.8</title>
|
||||
<path fill="none" stroke="black" d="M174.8,-37.3C174.8,-37.3 56.9,-37.3 56.9,-37.3 53.66,-37.3 50.43,-34.07 50.43,-30.83 50.43,-30.83 50.43,-24.37 50.43,-24.37 50.43,-21.13 53.66,-17.9 56.9,-17.9 56.9,-17.9 174.8,-17.9 174.8,-17.9 178.03,-17.9 181.26,-21.13 181.26,-24.37 181.26,-24.37 181.26,-30.83 181.26,-30.83 181.26,-34.07 178.03,-37.3 174.8,-37.3"></path>
|
||||
<text text-anchor="middle" x="115.85" y="-24.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@strudel.cycles/core@0.6.8</text>
|
||||
</g>
|
||||
<!-- fraction.js@4.2.0 -->
|
||||
<g id="node21" class="node module-fraction_js maintainer-infusion license-mit" data-module="fraction.js@4.2.0">
|
||||
<title>fraction.js@4.2.0</title>
|
||||
<path fill="none" stroke="black" d="M343.81,-19.3C343.81,-19.3 273.9,-19.3 273.9,-19.3 270.67,-19.3 267.44,-16.07 267.44,-12.83 267.44,-12.83 267.44,-6.37 267.44,-6.37 267.44,-3.13 270.67,0.1 273.9,0.1 273.9,0.1 343.81,0.1 343.81,0.1 347.05,0.1 350.28,-3.13 350.28,-6.37 350.28,-6.37 350.28,-12.83 350.28,-12.83 350.28,-16.07 347.05,-19.3 343.81,-19.3"></path>
|
||||
<text text-anchor="middle" x="308.86" y="-6.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">fraction.js@4.2.0</text>
|
||||
</g>
|
||||
<!-- @strudel.cycles/core@0.6.8->fraction.js@4.2.0 -->
|
||||
|
||||
<!-- @strudel.cycles/csound@0.6.2 -->
|
||||
<g id="node2" class="node module-_strudel_cycles_csound maintainer-yaxupaxo maintainer-felixroos license-agpl_3_0_or_later" data-module="@strudel.cycles/csound@0.6.2">
|
||||
<title>@strudel.cycles/csound@0.6.2</title>
|
||||
<path fill="none" stroke="black" d="M180.53,-296.3C180.53,-296.3 51.16,-296.3 51.16,-296.3 47.93,-296.3 44.7,-293.07 44.7,-289.83 44.7,-289.83 44.7,-283.37 44.7,-283.37 44.7,-280.13 47.93,-276.9 51.16,-276.9 51.16,-276.9 180.53,-276.9 180.53,-276.9 183.77,-276.9 187,-280.13 187,-283.37 187,-283.37 187,-289.83 187,-289.83 187,-293.07 183.77,-296.3 180.53,-296.3"></path>
|
||||
<text text-anchor="middle" x="115.85" y="-283.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@strudel.cycles/csound@0.6.2</text>
|
||||
</g>
|
||||
<!-- @strudel.cycles/csound@0.6.2->@strudel.cycles/core@0.6.8 -->
|
||||
|
||||
<!-- @strudel.cycles/webaudio@0.6.1 -->
|
||||
<g id="node12" class="node module-_strudel_cycles_webaudio maintainer-yaxupaxo maintainer-felixroos license-agpl_3_0_or_later" data-module="@strudel.cycles/webaudio@0.6.1">
|
||||
<title>@strudel.cycles/webaudio@0.6.1</title>
|
||||
<path fill="none" stroke="black" d="M186.63,-259.3C186.63,-259.3 45.06,-259.3 45.06,-259.3 41.83,-259.3 38.59,-256.07 38.59,-252.83 38.59,-252.83 38.59,-246.37 38.59,-246.37 38.59,-243.13 41.83,-239.9 45.06,-239.9 45.06,-239.9 186.63,-239.9 186.63,-239.9 189.87,-239.9 193.1,-243.13 193.1,-246.37 193.1,-246.37 193.1,-252.83 193.1,-252.83 193.1,-256.07 189.87,-259.3 186.63,-259.3"></path>
|
||||
<text text-anchor="middle" x="115.85" y="-246.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@strudel.cycles/webaudio@0.6.1</text>
|
||||
</g>
|
||||
<!-- @strudel.cycles/csound@0.6.2->@strudel.cycles/webaudio@0.6.1 -->
|
||||
|
||||
<!-- @csound/browser@6.18.5 -->
|
||||
<g id="node14" class="node module-_csound_browser collapsed maintainer-skyne maintainer-kunstmusik maintainer-eddyc maintainer-hlolli license-lgpl_2_1" data-module="@csound/browser@6.18.5">
|
||||
<title>@csound/browser@6.18.5</title>
|
||||
<path fill="none" stroke="black" d="M364.3,-257.3C364.3,-257.3 253.41,-257.3 253.41,-257.3 250.18,-257.3 246.95,-254.07 246.95,-250.83 246.95,-250.83 246.95,-244.37 246.95,-244.37 246.95,-241.13 250.18,-237.9 253.41,-237.9 253.41,-237.9 364.3,-237.9 364.3,-237.9 367.54,-237.9 370.77,-241.13 370.77,-244.37 370.77,-244.37 370.77,-250.83 370.77,-250.83 370.77,-254.07 367.54,-257.3 364.3,-257.3"></path>
|
||||
<text text-anchor="middle" x="308.86" y="-244.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@csound/browser@6.18.5</text>
|
||||
</g>
|
||||
<!-- @strudel.cycles/csound@0.6.2->@csound/browser@6.18.5 -->
|
||||
|
||||
<!-- @strudel.cycles/embed@0.2.0 -->
|
||||
<g id="node3" class="node module-_strudel_cycles_embed maintainer-yaxupaxo maintainer-felixroos license-agpl_3_0_or_later" data-module="@strudel.cycles/embed@0.2.0">
|
||||
<title>@strudel.cycles/embed@0.2.0</title>
|
||||
<path fill="none" stroke="black" d="M179.69,-462.3C179.69,-462.3 52.01,-462.3 52.01,-462.3 48.77,-462.3 45.54,-459.07 45.54,-455.83 45.54,-455.83 45.54,-449.37 45.54,-449.37 45.54,-446.13 48.77,-442.9 52.01,-442.9 52.01,-442.9 179.69,-442.9 179.69,-442.9 182.92,-442.9 186.16,-446.13 186.16,-449.37 186.16,-449.37 186.16,-455.83 186.16,-455.83 186.16,-459.07 182.92,-462.3 179.69,-462.3"></path>
|
||||
<text text-anchor="middle" x="115.85" y="-449.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@strudel.cycles/embed@0.2.0</text>
|
||||
</g>
|
||||
<!-- @strudel.cycles/midi@0.6.0 -->
|
||||
<g id="node4" class="node module-_strudel_cycles_midi maintainer-yaxupaxo maintainer-felixroos license-agpl_3_0_or_later" data-module="@strudel.cycles/midi@0.6.0">
|
||||
<title>@strudel.cycles/midi@0.6.0</title>
|
||||
<path fill="none" stroke="black" d="M175.04,-370.3C175.04,-370.3 56.66,-370.3 56.66,-370.3 53.42,-370.3 50.19,-367.07 50.19,-363.83 50.19,-363.83 50.19,-357.37 50.19,-357.37 50.19,-354.13 53.42,-350.9 56.66,-350.9 56.66,-350.9 175.04,-350.9 175.04,-350.9 178.27,-350.9 181.5,-354.13 181.5,-357.37 181.5,-357.37 181.5,-363.83 181.5,-363.83 181.5,-367.07 178.27,-370.3 175.04,-370.3"></path>
|
||||
<text text-anchor="middle" x="115.85" y="-357.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@strudel.cycles/midi@0.6.0</text>
|
||||
</g>
|
||||
<!-- @strudel.cycles/midi@0.6.0->@strudel.cycles/core@0.6.8 -->
|
||||
|
||||
<!-- @strudel.cycles/webaudio@0.6.0 -->
|
||||
<g id="node15" class="node module-_strudel_cycles_webaudio maintainer-yaxupaxo maintainer-felixroos license-agpl_3_0_or_later" data-module="@strudel.cycles/webaudio@0.6.0">
|
||||
<title>@strudel.cycles/webaudio@0.6.0</title>
|
||||
<path fill="none" stroke="black" d="M379.64,-370.3C379.64,-370.3 238.07,-370.3 238.07,-370.3 234.84,-370.3 231.6,-367.07 231.6,-363.83 231.6,-363.83 231.6,-357.37 231.6,-357.37 231.6,-354.13 234.84,-350.9 238.07,-350.9 238.07,-350.9 379.64,-350.9 379.64,-350.9 382.88,-350.9 386.11,-354.13 386.11,-357.37 386.11,-357.37 386.11,-363.83 386.11,-363.83 386.11,-367.07 382.88,-370.3 379.64,-370.3"></path>
|
||||
<text text-anchor="middle" x="308.86" y="-357.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@strudel.cycles/webaudio@0.6.0</text>
|
||||
</g>
|
||||
<!-- @strudel.cycles/midi@0.6.0->@strudel.cycles/webaudio@0.6.0 -->
|
||||
|
||||
<!-- webmidi@3.1.4 -->
|
||||
<g id="node25" class="node module-webmidi collapsed maintainer-djipco license-apache_2_0" data-module="webmidi@3.1.4">
|
||||
<title>webmidi@3.1.4</title>
|
||||
<path fill="none" stroke="black" d="M340.52,-407.3C340.52,-407.3 277.2,-407.3 277.2,-407.3 273.97,-407.3 270.73,-404.07 270.73,-400.83 270.73,-400.83 270.73,-394.37 270.73,-394.37 270.73,-391.13 273.97,-387.9 277.2,-387.9 277.2,-387.9 340.52,-387.9 340.52,-387.9 343.75,-387.9 346.98,-391.13 346.98,-394.37 346.98,-394.37 346.98,-400.83 346.98,-400.83 346.98,-404.07 343.75,-407.3 340.52,-407.3"></path>
|
||||
<text text-anchor="middle" x="308.86" y="-394.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">webmidi@3.1.4</text>
|
||||
</g>
|
||||
<!-- @strudel.cycles/midi@0.6.0->webmidi@3.1.4 -->
|
||||
|
||||
<!-- @strudel.cycles/mini@0.6.0 -->
|
||||
<g id="node5" class="node module-_strudel_cycles_mini maintainer-yaxupaxo maintainer-felixroos license-agpl_3_0_or_later" data-module="@strudel.cycles/mini@0.6.0">
|
||||
<title>@strudel.cycles/mini@0.6.0</title>
|
||||
<path fill="none" stroke="black" d="M175.04,-111.3C175.04,-111.3 56.66,-111.3 56.66,-111.3 53.42,-111.3 50.19,-108.07 50.19,-104.83 50.19,-104.83 50.19,-98.37 50.19,-98.37 50.19,-95.13 53.42,-91.9 56.66,-91.9 56.66,-91.9 175.04,-91.9 175.04,-91.9 178.27,-91.9 181.5,-95.13 181.5,-98.37 181.5,-98.37 181.5,-104.83 181.5,-104.83 181.5,-108.07 178.27,-111.3 175.04,-111.3"></path>
|
||||
<text text-anchor="middle" x="115.85" y="-98.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@strudel.cycles/mini@0.6.0</text>
|
||||
</g>
|
||||
<!-- @strudel.cycles/mini@0.6.0->@strudel.cycles/core@0.6.8 -->
|
||||
|
||||
<!-- @strudel.cycles/osc@0.6.0 -->
|
||||
<g id="node6" class="node module-_strudel_cycles_osc maintainer-yaxupaxo maintainer-felixroos license-agpl_3_0_or_later" data-module="@strudel.cycles/osc@0.6.0">
|
||||
<title>@strudel.cycles/osc@0.6.0</title>
|
||||
<path fill="none" stroke="black" d="M172.53,-222.3C172.53,-222.3 59.16,-222.3 59.16,-222.3 55.93,-222.3 52.7,-219.07 52.7,-215.83 52.7,-215.83 52.7,-209.37 52.7,-209.37 52.7,-206.13 55.93,-202.9 59.16,-202.9 59.16,-202.9 172.53,-202.9 172.53,-202.9 175.76,-202.9 179,-206.13 179,-209.37 179,-209.37 179,-215.83 179,-215.83 179,-219.07 175.76,-222.3 172.53,-222.3"></path>
|
||||
<text text-anchor="middle" x="115.85" y="-209.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@strudel.cycles/osc@0.6.0</text>
|
||||
</g>
|
||||
<!-- @strudel.cycles/osc@0.6.0->@strudel.cycles/core@0.6.8 -->
|
||||
|
||||
<!-- osc-js@2.4.0 -->
|
||||
<g id="node22" class="node module-osc_js collapsed maintainer-andreasdz license-mit" data-module="osc-js@2.4.0">
|
||||
<title>osc-js@2.4.0</title>
|
||||
<path fill="none" stroke="black" d="M334.18,-220.3C334.18,-220.3 283.53,-220.3 283.53,-220.3 280.3,-220.3 277.06,-217.07 277.06,-213.83 277.06,-213.83 277.06,-207.37 277.06,-207.37 277.06,-204.13 280.3,-200.9 283.53,-200.9 283.53,-200.9 334.18,-200.9 334.18,-200.9 337.42,-200.9 340.65,-204.13 340.65,-207.37 340.65,-207.37 340.65,-213.83 340.65,-213.83 340.65,-217.07 337.42,-220.3 334.18,-220.3"></path>
|
||||
<text text-anchor="middle" x="308.86" y="-207.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">osc-js@2.4.0</text>
|
||||
</g>
|
||||
<!-- @strudel.cycles/osc@0.6.0->osc-js@2.4.0 -->
|
||||
|
||||
<!-- @strudel.cycles/react@0.6.0 -->
|
||||
<g id="node7" class="node module-_strudel_cycles_react collapsed maintainer-yaxupaxo maintainer-felixroos license-agpl_3_0_or_later" data-module="@strudel.cycles/react@0.6.0">
|
||||
<title>@strudel.cycles/react@0.6.0</title>
|
||||
<path fill="none" stroke="black" d="M175.73,-499.3C175.73,-499.3 55.96,-499.3 55.96,-499.3 52.73,-499.3 49.49,-496.07 49.49,-492.83 49.49,-492.83 49.49,-486.37 49.49,-486.37 49.49,-483.13 52.73,-479.9 55.96,-479.9 55.96,-479.9 175.73,-479.9 175.73,-479.9 178.97,-479.9 182.2,-483.13 182.2,-486.37 182.2,-486.37 182.2,-492.83 182.2,-492.83 182.2,-496.07 178.97,-499.3 175.73,-499.3"></path>
|
||||
<text text-anchor="middle" x="115.85" y="-486.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@strudel.cycles/react@0.6.0</text>
|
||||
</g>
|
||||
<!-- @strudel.cycles/serial@0.6.0 -->
|
||||
<g id="node8" class="node module-_strudel_cycles_serial maintainer-yaxupaxo maintainer-felixroos license-agpl_3_0_or_later" data-module="@strudel.cycles/serial@0.6.0">
|
||||
<title>@strudel.cycles/serial@0.6.0</title>
|
||||
<path fill="none" stroke="black" d="M177.19,-148.3C177.19,-148.3 54.5,-148.3 54.5,-148.3 51.27,-148.3 48.04,-145.07 48.04,-141.83 48.04,-141.83 48.04,-135.37 48.04,-135.37 48.04,-132.13 51.27,-128.9 54.5,-128.9 54.5,-128.9 177.19,-128.9 177.19,-128.9 180.42,-128.9 183.66,-132.13 183.66,-135.37 183.66,-135.37 183.66,-141.83 183.66,-141.83 183.66,-145.07 180.42,-148.3 177.19,-148.3"></path>
|
||||
<text text-anchor="middle" x="115.85" y="-135.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@strudel.cycles/serial@0.6.0</text>
|
||||
</g>
|
||||
<!-- @strudel.cycles/serial@0.6.0->@strudel.cycles/core@0.6.8 -->
|
||||
|
||||
<!-- @strudel.cycles/soundfonts@0.6.0 -->
|
||||
<g id="node9" class="node module-_strudel_cycles_soundfonts maintainer-yaxupaxo maintainer-felixroos license-agpl_3_0_or_later" data-module="@strudel.cycles/soundfonts@0.6.0">
|
||||
<title>@strudel.cycles/soundfonts@0.6.0</title>
|
||||
<path fill="none" stroke="black" d="M189.15,-333.3C189.15,-333.3 42.55,-333.3 42.55,-333.3 39.31,-333.3 36.08,-330.07 36.08,-326.83 36.08,-326.83 36.08,-320.37 36.08,-320.37 36.08,-317.13 39.31,-313.9 42.55,-313.9 42.55,-313.9 189.15,-313.9 189.15,-313.9 192.38,-313.9 195.62,-317.13 195.62,-320.37 195.62,-320.37 195.62,-326.83 195.62,-326.83 195.62,-330.07 192.38,-333.3 189.15,-333.3"></path>
|
||||
<text text-anchor="middle" x="115.85" y="-320.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@strudel.cycles/soundfonts@0.6.0</text>
|
||||
</g>
|
||||
<!-- @strudel.cycles/soundfonts@0.6.0->@strudel.cycles/core@0.6.8 -->
|
||||
|
||||
<!-- @strudel.cycles/soundfonts@0.6.0->@strudel.cycles/webaudio@0.6.0 -->
|
||||
|
||||
<!-- sfumato@0.1.2 -->
|
||||
<g id="node23" class="node module-sfumato maintainer-felixroos license-isc" data-module="sfumato@0.1.2">
|
||||
<title>sfumato@0.1.2</title>
|
||||
<path fill="none" stroke="black" d="M338.96,-333.3C338.96,-333.3 278.76,-333.3 278.76,-333.3 275.52,-333.3 272.29,-330.07 272.29,-326.83 272.29,-326.83 272.29,-320.37 272.29,-320.37 272.29,-317.13 275.52,-313.9 278.76,-313.9 278.76,-313.9 338.96,-313.9 338.96,-313.9 342.19,-313.9 345.43,-317.13 345.43,-320.37 345.43,-320.37 345.43,-326.83 345.43,-326.83 345.43,-330.07 342.19,-333.3 338.96,-333.3"></path>
|
||||
<text text-anchor="middle" x="308.86" y="-320.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">sfumato@0.1.2</text>
|
||||
</g>
|
||||
<!-- @strudel.cycles/soundfonts@0.6.0->sfumato@0.1.2 -->
|
||||
|
||||
<!-- soundfont2@0.4.0 -->
|
||||
<g id="node24" class="node module-soundfont2 maintainer-mrten license-mit" data-module="soundfont2@0.4.0">
|
||||
<title>soundfont2@0.4.0</title>
|
||||
<path fill="none" stroke="black" d="M513.22,-314.3C513.22,-314.3 438.89,-314.3 438.89,-314.3 435.66,-314.3 432.42,-311.07 432.42,-307.83 432.42,-307.83 432.42,-301.37 432.42,-301.37 432.42,-298.13 435.66,-294.9 438.89,-294.9 438.89,-294.9 513.22,-294.9 513.22,-294.9 516.45,-294.9 519.69,-298.13 519.69,-301.37 519.69,-301.37 519.69,-307.83 519.69,-307.83 519.69,-311.07 516.45,-314.3 513.22,-314.3"></path>
|
||||
<text text-anchor="middle" x="476.06" y="-301.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">soundfont2@0.4.0</text>
|
||||
</g>
|
||||
<!-- @strudel.cycles/soundfonts@0.6.0->soundfont2@0.4.0 -->
|
||||
|
||||
<!-- @strudel.cycles/tonal@0.6.0 -->
|
||||
<g id="node10" class="node module-_strudel_cycles_tonal maintainer-yaxupaxo maintainer-felixroos license-agpl_3_0_or_later" data-module="@strudel.cycles/tonal@0.6.0">
|
||||
<title>@strudel.cycles/tonal@0.6.0</title>
|
||||
<path fill="none" stroke="black" d="M176.36,-425.3C176.36,-425.3 55.33,-425.3 55.33,-425.3 52.1,-425.3 48.86,-422.07 48.86,-418.83 48.86,-418.83 48.86,-412.37 48.86,-412.37 48.86,-409.13 52.1,-405.9 55.33,-405.9 55.33,-405.9 176.36,-405.9 176.36,-405.9 179.6,-405.9 182.83,-409.13 182.83,-412.37 182.83,-412.37 182.83,-418.83 182.83,-418.83 182.83,-422.07 179.6,-425.3 176.36,-425.3"></path>
|
||||
<text text-anchor="middle" x="115.85" y="-412.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@strudel.cycles/tonal@0.6.0</text>
|
||||
</g>
|
||||
<!-- @strudel.cycles/tonal@0.6.0->@strudel.cycles/core@0.6.8 -->
|
||||
|
||||
<!-- @tonaljs/tonal@4.10.0 -->
|
||||
<g id="node16" class="node module-_tonaljs_tonal collapsed maintainer-danigb license-mit" data-module="@tonaljs/tonal@4.10.0">
|
||||
<title>@tonaljs/tonal@4.10.0</title>
|
||||
<path fill="none" stroke="black" d="M523.73,-463.3C523.73,-463.3 428.38,-463.3 428.38,-463.3 425.15,-463.3 421.91,-460.07 421.91,-456.83 421.91,-456.83 421.91,-450.37 421.91,-450.37 421.91,-447.13 425.15,-443.9 428.38,-443.9 428.38,-443.9 523.73,-443.9 523.73,-443.9 526.96,-443.9 530.2,-447.13 530.2,-450.37 530.2,-450.37 530.2,-456.83 530.2,-456.83 530.2,-460.07 526.96,-463.3 523.73,-463.3"></path>
|
||||
<text text-anchor="middle" x="476.06" y="-450.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@tonaljs/tonal@4.10.0</text>
|
||||
</g>
|
||||
<!-- @strudel.cycles/tonal@0.6.0->@tonaljs/tonal@4.10.0 -->
|
||||
|
||||
<!-- chord-voicings@0.0.1 -->
|
||||
<g id="node18" class="node module-chord_voicings maintainer-felixroos license-isc" data-module="chord-voicings@0.0.1">
|
||||
<title>chord-voicings@0.0.1</title>
|
||||
<path fill="none" stroke="black" d="M354.5,-444.3C354.5,-444.3 263.21,-444.3 263.21,-444.3 259.98,-444.3 256.74,-441.07 256.74,-437.83 256.74,-437.83 256.74,-431.37 256.74,-431.37 256.74,-428.13 259.98,-424.9 263.21,-424.9 263.21,-424.9 354.5,-424.9 354.5,-424.9 357.74,-424.9 360.97,-428.13 360.97,-431.37 360.97,-431.37 360.97,-437.83 360.97,-437.83 360.97,-441.07 357.74,-444.3 354.5,-444.3"></path>
|
||||
<text text-anchor="middle" x="308.86" y="-431.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">chord-voicings@0.0.1</text>
|
||||
</g>
|
||||
<!-- @strudel.cycles/tonal@0.6.0->chord-voicings@0.0.1 -->
|
||||
|
||||
<!-- @strudel.cycles/tonal@0.6.0->webmidi@3.1.4 -->
|
||||
|
||||
<!-- @strudel.cycles/transpiler@0.6.0 -->
|
||||
<g id="node11" class="node module-_strudel_cycles_transpiler maintainer-yaxupaxo maintainer-felixroos license-agpl_3_0_or_later" data-module="@strudel.cycles/transpiler@0.6.0">
|
||||
<title>@strudel.cycles/transpiler@0.6.0</title>
|
||||
<path fill="none" stroke="black" d="M185.91,-74.3C185.91,-74.3 45.79,-74.3 45.79,-74.3 42.55,-74.3 39.32,-71.07 39.32,-67.83 39.32,-67.83 39.32,-61.37 39.32,-61.37 39.32,-58.13 42.55,-54.9 45.79,-54.9 45.79,-54.9 185.91,-54.9 185.91,-54.9 189.14,-54.9 192.38,-58.13 192.38,-61.37 192.38,-61.37 192.38,-67.83 192.38,-67.83 192.38,-71.07 189.14,-74.3 185.91,-74.3"></path>
|
||||
<text text-anchor="middle" x="115.85" y="-61.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@strudel.cycles/transpiler@0.6.0</text>
|
||||
</g>
|
||||
<!-- @strudel.cycles/transpiler@0.6.0->@strudel.cycles/core@0.6.8 -->
|
||||
|
||||
<!-- acorn@8.8.2 -->
|
||||
<g id="node17" class="node module-acorn collapsed maintainer-marijn maintainer-adrianheine maintainer-rreverser license-mit" data-module="acorn@8.8.2">
|
||||
<title>acorn@8.8.2</title>
|
||||
<path fill="none" stroke="black" d="M333.45,-130.3C333.45,-130.3 284.27,-130.3 284.27,-130.3 281.03,-130.3 277.8,-127.07 277.8,-123.83 277.8,-123.83 277.8,-117.37 277.8,-117.37 277.8,-114.13 281.03,-110.9 284.27,-110.9 284.27,-110.9 333.45,-110.9 333.45,-110.9 336.68,-110.9 339.92,-114.13 339.92,-117.37 339.92,-117.37 339.92,-123.83 339.92,-123.83 339.92,-127.07 336.68,-130.3 333.45,-130.3"></path>
|
||||
<text text-anchor="middle" x="308.86" y="-117.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">acorn@8.8.2</text>
|
||||
</g>
|
||||
<!-- @strudel.cycles/transpiler@0.6.0->acorn@8.8.2 -->
|
||||
|
||||
<!-- escodegen@2.0.0 -->
|
||||
<g id="node19" class="node module-escodegen collapsed maintainer-constellation maintainer-michaelficarra license-bsd_2_clause" data-module="escodegen@2.0.0">
|
||||
<title>escodegen@2.0.0</title>
|
||||
<path fill="none" stroke="black" d="M344.33,-93.3C344.33,-93.3 273.39,-93.3 273.39,-93.3 270.15,-93.3 266.92,-90.07 266.92,-86.83 266.92,-86.83 266.92,-80.37 266.92,-80.37 266.92,-77.13 270.15,-73.9 273.39,-73.9 273.39,-73.9 344.33,-73.9 344.33,-73.9 347.56,-73.9 350.8,-77.13 350.8,-80.37 350.8,-80.37 350.8,-86.83 350.8,-86.83 350.8,-90.07 347.56,-93.3 344.33,-93.3"></path>
|
||||
<text text-anchor="middle" x="308.86" y="-80.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">escodegen@2.0.0</text>
|
||||
</g>
|
||||
<!-- @strudel.cycles/transpiler@0.6.0->escodegen@2.0.0 -->
|
||||
|
||||
<!-- estree-walker@3.0.3 -->
|
||||
<g id="node20" class="node module-estree_walker collapsed maintainer-rich_harris license-mit" data-module="estree-walker@3.0.3">
|
||||
<title>estree-walker@3.0.3</title>
|
||||
<path fill="none" stroke="black" d="M351.26,-56.3C351.26,-56.3 266.46,-56.3 266.46,-56.3 263.22,-56.3 259.99,-53.07 259.99,-49.83 259.99,-49.83 259.99,-43.37 259.99,-43.37 259.99,-40.13 263.22,-36.9 266.46,-36.9 266.46,-36.9 351.26,-36.9 351.26,-36.9 354.49,-36.9 357.73,-40.13 357.73,-43.37 357.73,-43.37 357.73,-49.83 357.73,-49.83 357.73,-53.07 354.49,-56.3 351.26,-56.3"></path>
|
||||
<text text-anchor="middle" x="308.86" y="-43.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">estree-walker@3.0.3</text>
|
||||
</g>
|
||||
<!-- @strudel.cycles/transpiler@0.6.0->estree-walker@3.0.3 -->
|
||||
|
||||
<!-- @strudel.cycles/webaudio@0.6.1->@strudel.cycles/core@0.6.8 -->
|
||||
|
||||
<!-- @strudel.cycles/xen@0.6.0 -->
|
||||
<g id="node13" class="node module-_strudel_cycles_xen maintainer-yaxupaxo maintainer-felixroos license-agpl_3_0_or_later" data-module="@strudel.cycles/xen@0.6.0">
|
||||
<title>@strudel.cycles/xen@0.6.0</title>
|
||||
<path fill="none" stroke="black" d="M173.25,-185.3C173.25,-185.3 58.44,-185.3 58.44,-185.3 55.21,-185.3 51.98,-182.07 51.98,-178.83 51.98,-178.83 51.98,-172.37 51.98,-172.37 51.98,-169.13 55.21,-165.9 58.44,-165.9 58.44,-165.9 173.25,-165.9 173.25,-165.9 176.48,-165.9 179.72,-169.13 179.72,-172.37 179.72,-172.37 179.72,-178.83 179.72,-178.83 179.72,-182.07 176.48,-185.3 173.25,-185.3"></path>
|
||||
<text text-anchor="middle" x="115.85" y="-172.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@strudel.cycles/xen@0.6.0</text>
|
||||
</g>
|
||||
<!-- @strudel.cycles/xen@0.6.0->@strudel.cycles/core@0.6.8 -->
|
||||
|
||||
<!-- @strudel.cycles/webaudio@0.6.0->@strudel.cycles/core@0.6.8 -->
|
||||
|
||||
<!-- chord-voicings@0.0.1->@tonaljs/tonal@4.10.0 -->
|
||||
<g id="edge27" class="edge">
|
||||
<title>chord-voicings@0.0.1->@tonaljs/tonal@4.10.0</title>
|
||||
<path fill="none" stroke="black" d="M361.22,-440.51C377.14,-442.34 394.88,-444.38 411.53,-446.29"></path>
|
||||
<polygon fill="black" stroke="black" points="411.39,-449.8 421.73,-447.47 412.19,-442.85 411.39,-449.8"></polygon>
|
||||
</g>
|
||||
<!-- sfumato@0.1.2->soundfont2@0.4.0 -->
|
||||
<g id="edge28" class="edge">
|
||||
<title>sfumato@0.1.2->soundfont2@0.4.0</title>
|
||||
<path fill="none" stroke="black" d="M345.7,-319.48C368.06,-316.91 397.22,-313.55 422.19,-310.68"></path>
|
||||
<polygon fill="black" stroke="black" points="422.72,-314.14 432.25,-309.52 421.92,-307.19 422.72,-314.14"></polygon>
|
||||
</g>
|
||||
<g id="edge1" class="edge">
|
||||
<title>@strudel.cycles/core@0.6.8->fraction.js@4.2.0</title>
|
||||
<path fill="none" stroke="black" d="M181.25,-21.53C206.16,-19.18 234.23,-16.54 257.6,-14.34"></path>
|
||||
<polygon fill="black" stroke="black" points="258.02,-17.81 267.64,-13.39 257.36,-10.84 258.02,-17.81"></polygon>
|
||||
</g><g id="edge3" class="edge">
|
||||
<title>@strudel.cycles/csound@0.6.2->@strudel.cycles/core@0.6.8</title>
|
||||
<path fill="none" stroke="black" d="M44.23,-280.41C33.55,-276.09 24.08,-269.5 17.96,-259.6 -5.99,-220.85 -5.99,-93.35 17.96,-54.6 23.42,-45.77 31.54,-39.58 40.82,-35.27"></path>
|
||||
<polygon fill="black" stroke="black" points="42.35,-38.43 50.42,-31.56 39.83,-31.9 42.35,-38.43"></polygon>
|
||||
</g><g id="edge4" class="edge">
|
||||
<title>@strudel.cycles/csound@0.6.2->@strudel.cycles/webaudio@0.6.1</title>
|
||||
<path fill="none" stroke="black" d="M115.85,-276.84C115.85,-274.39 115.85,-271.94 115.85,-269.49"></path>
|
||||
<polygon fill="black" stroke="black" points="119.35,-269.43 115.85,-259.43 112.35,-269.43 119.35,-269.43"></polygon>
|
||||
</g><g id="edge6" class="edge">
|
||||
<title>@strudel.cycles/midi@0.6.0->@strudel.cycles/core@0.6.8</title>
|
||||
<path fill="none" stroke="black" d="M50.17,-356.56C37.1,-352.37 25.19,-345.3 17.96,-333.6 1.66,-307.23 1.66,-80.97 17.96,-54.6 23.42,-45.77 31.54,-39.58 40.82,-35.27"></path>
|
||||
<polygon fill="black" stroke="black" points="42.35,-38.43 50.42,-31.56 39.83,-31.9 42.35,-38.43"></polygon>
|
||||
</g><g id="edge7" class="edge">
|
||||
<title>@strudel.cycles/midi@0.6.0->@strudel.cycles/webaudio@0.6.0</title>
|
||||
<path fill="none" stroke="black" d="M181.51,-360.6C194.37,-360.6 208.05,-360.6 221.49,-360.6"></path>
|
||||
<polygon fill="black" stroke="black" points="221.71,-364.1 231.7,-360.6 221.7,-357.1 221.71,-364.1"></polygon>
|
||||
</g><g id="edge5" class="edge">
|
||||
<title>@strudel.cycles/midi@0.6.0->webmidi@3.1.4</title>
|
||||
<path fill="none" stroke="black" d="M166.53,-370.22C195.48,-375.83 231.78,-382.86 260.34,-388.4"></path>
|
||||
<polygon fill="black" stroke="black" points="260.01,-391.9 270.49,-390.36 261.34,-385.02 260.01,-391.9"></polygon>
|
||||
</g><g id="edge8" class="edge">
|
||||
<title>@strudel.cycles/mini@0.6.0->@strudel.cycles/core@0.6.8</title>
|
||||
<path fill="none" stroke="black" d="M50.17,-97.56C37.1,-93.37 25.19,-86.3 17.96,-74.6 7.78,-58.12 21.54,-47.3 41.67,-40.25"></path>
|
||||
<polygon fill="black" stroke="black" points="42.78,-43.57 51.3,-37.27 40.71,-36.88 42.78,-43.57"></polygon>
|
||||
</g><g id="edge10" class="edge">
|
||||
<title>@strudel.cycles/osc@0.6.0->@strudel.cycles/core@0.6.8</title>
|
||||
<path fill="none" stroke="black" d="M52.7,-209.33C38.65,-205.26 25.65,-198.05 17.96,-185.6 2.66,-160.84 2.66,-79.36 17.96,-54.6 23.42,-45.77 31.54,-39.58 40.82,-35.27"></path>
|
||||
<polygon fill="black" stroke="black" points="42.35,-38.43 50.42,-31.56 39.83,-31.9 42.35,-38.43"></polygon>
|
||||
</g><g id="edge9" class="edge">
|
||||
<title>@strudel.cycles/osc@0.6.0->osc-js@2.4.0</title>
|
||||
<path fill="none" stroke="black" d="M179.13,-211.95C207.93,-211.65 241.33,-211.3 266.9,-211.03"></path>
|
||||
<polygon fill="black" stroke="black" points="267.02,-214.53 276.99,-210.92 266.95,-207.53 267.02,-214.53"></polygon>
|
||||
</g><g id="edge11" class="edge">
|
||||
<title>@strudel.cycles/serial@0.6.0->@strudel.cycles/core@0.6.8</title>
|
||||
<path fill="none" stroke="black" d="M48.17,-133.89C35.89,-129.63 24.82,-122.7 17.96,-111.6 4.64,-90.05 4.64,-76.15 17.96,-54.6 23.42,-45.77 31.54,-39.58 40.82,-35.27"></path>
|
||||
<polygon fill="black" stroke="black" points="42.35,-38.43 50.42,-31.56 39.83,-31.9 42.35,-38.43"></polygon>
|
||||
</g><g id="edge14" class="edge">
|
||||
<title>@strudel.cycles/soundfonts@0.6.0->@strudel.cycles/core@0.6.8</title>
|
||||
<path fill="none" stroke="black" d="M36.71,-313.85C29.15,-309.7 22.6,-304.1 17.96,-296.6 3.83,-273.73 3.83,-77.47 17.96,-54.6 23.42,-45.77 31.54,-39.58 40.82,-35.27"></path>
|
||||
<polygon fill="black" stroke="black" points="42.35,-38.43 50.42,-31.56 39.83,-31.9 42.35,-38.43"></polygon>
|
||||
</g><g id="edge15" class="edge">
|
||||
<title>@strudel.cycles/soundfonts@0.6.0->@strudel.cycles/webaudio@0.6.0</title>
|
||||
<path fill="none" stroke="black" d="M166.53,-333.22C191.53,-338.07 222.01,-343.97 248.26,-349.05"></path>
|
||||
<polygon fill="black" stroke="black" points="247.6,-352.49 258.09,-350.96 248.94,-345.62 247.6,-352.49"></polygon>
|
||||
</g><g id="edge12" class="edge">
|
||||
<title>@strudel.cycles/soundfonts@0.6.0->sfumato@0.1.2</title>
|
||||
<path fill="none" stroke="black" d="M195.64,-323.6C218.22,-323.6 242.08,-323.6 261.97,-323.6"></path>
|
||||
<polygon fill="black" stroke="black" points="261.98,-327.1 271.98,-323.6 261.98,-320.1 261.98,-327.1"></polygon>
|
||||
</g><g id="edge13" class="edge">
|
||||
<title>@strudel.cycles/soundfonts@0.6.0->soundfont2@0.4.0</title>
|
||||
<path fill="none" stroke="black" d="M164.14,-313.99C184.8,-310.25 209.37,-306.44 231.73,-304.6 297.18,-299.22 372.86,-300.38 422.21,-302.11"></path>
|
||||
<polygon fill="black" stroke="black" points="422.21,-305.61 432.33,-302.49 422.47,-298.62 422.21,-305.61"></polygon>
|
||||
</g><g id="edge19" class="edge">
|
||||
<title>@strudel.cycles/tonal@0.6.0->@strudel.cycles/core@0.6.8</title>
|
||||
<path fill="none" stroke="black" d="M68.74,-405.99C49.45,-399.45 29.07,-388.58 17.96,-370.6 -0.5,-340.73 -0.5,-84.47 17.96,-54.6 23.42,-45.77 31.54,-39.58 40.82,-35.27"></path>
|
||||
<polygon fill="black" stroke="black" points="42.35,-38.43 50.42,-31.56 39.83,-31.9 42.35,-38.43"></polygon>
|
||||
</g><g id="edge16" class="edge">
|
||||
<title>@strudel.cycles/tonal@0.6.0->@tonaljs/tonal@4.10.0</title>
|
||||
<path fill="none" stroke="black" d="M168.82,-425.25C177.92,-427.57 187.2,-430.33 195.73,-433.6 212.82,-440.15 214.01,-449.01 231.73,-453.6 291.41,-469.07 362.13,-466.9 411.9,-462.17"></path>
|
||||
<polygon fill="black" stroke="black" points="412.28,-465.65 421.88,-461.17 411.58,-458.69 412.28,-465.65"></polygon>
|
||||
</g><g id="edge17" class="edge">
|
||||
<title>@strudel.cycles/tonal@0.6.0->chord-voicings@0.0.1</title>
|
||||
<path fill="none" stroke="black" d="M182.85,-422.17C203.51,-424.22 226.22,-426.48 246.55,-428.5"></path>
|
||||
<polygon fill="black" stroke="black" points="246.33,-432 256.62,-429.5 247.02,-425.03 246.33,-432"></polygon>
|
||||
</g><g id="edge18" class="edge">
|
||||
<title>@strudel.cycles/tonal@0.6.0->webmidi@3.1.4</title>
|
||||
<path fill="none" stroke="black" d="M182.85,-409.38C208.4,-406.97 237.07,-404.27 260.51,-402.06"></path>
|
||||
<polygon fill="black" stroke="black" points="260.91,-405.54 270.54,-401.12 260.26,-398.57 260.91,-405.54"></polygon>
|
||||
</g><g id="edge23" class="edge">
|
||||
<title>@strudel.cycles/transpiler@0.6.0->@strudel.cycles/core@0.6.8</title>
|
||||
<path fill="none" stroke="black" d="M115.85,-54.84C115.85,-52.39 115.85,-49.94 115.85,-47.49"></path>
|
||||
<polygon fill="black" stroke="black" points="119.35,-47.43 115.85,-37.43 112.35,-47.43 119.35,-47.43"></polygon>
|
||||
</g><g id="edge20" class="edge">
|
||||
<title>@strudel.cycles/transpiler@0.6.0->acorn@8.8.2</title>
|
||||
<path fill="none" stroke="black" d="M168.82,-74.25C177.92,-76.57 187.2,-79.33 195.73,-82.6 212.82,-89.15 214.66,-95.99 231.73,-102.6 243.09,-107 255.84,-110.53 267.61,-113.25"></path>
|
||||
<polygon fill="black" stroke="black" points="267.12,-116.73 277.64,-115.44 268.61,-109.89 267.12,-116.73"></polygon>
|
||||
</g><g id="edge21" class="edge">
|
||||
<title>@strudel.cycles/transpiler@0.6.0->escodegen@2.0.0</title>
|
||||
<path fill="none" stroke="black" d="M192.61,-72.14C214.17,-74.28 237.12,-76.56 256.83,-78.52"></path>
|
||||
<polygon fill="black" stroke="black" points="256.51,-82.01 266.81,-79.52 257.21,-75.04 256.51,-82.01"></polygon>
|
||||
</g><g id="edge22" class="edge">
|
||||
<title>@strudel.cycles/transpiler@0.6.0->estree-walker@3.0.3</title>
|
||||
<path fill="none" stroke="black" d="M192.61,-57.46C211.68,-55.66 231.85,-53.76 249.89,-52.06"></path>
|
||||
<polygon fill="black" stroke="black" points="250.33,-55.54 259.96,-51.11 249.68,-48.57 250.33,-55.54"></polygon>
|
||||
</g><g id="edge24" class="edge">
|
||||
<title>@strudel.cycles/webaudio@0.6.1->@strudel.cycles/core@0.6.8</title>
|
||||
<path fill="none" stroke="black" d="M38.54,-240.82C30.22,-236.58 22.97,-230.7 17.96,-222.6 -1.66,-190.84 -1.66,-86.36 17.96,-54.6 23.42,-45.77 31.54,-39.58 40.82,-35.27"></path>
|
||||
<polygon fill="black" stroke="black" points="42.35,-38.43 50.42,-31.56 39.83,-31.9 42.35,-38.43"></polygon>
|
||||
</g><g id="edge25" class="edge">
|
||||
<title>@strudel.cycles/xen@0.6.0->@strudel.cycles/core@0.6.8</title>
|
||||
<path fill="none" stroke="black" d="M51.94,-172.1C38.18,-168 25.52,-160.82 17.96,-148.6 -4,-113.06 -4,-90.14 17.96,-54.6 23.42,-45.77 31.54,-39.58 40.82,-35.27"></path>
|
||||
<polygon fill="black" stroke="black" points="42.35,-38.43 50.42,-31.56 39.83,-31.9 42.35,-38.43"></polygon>
|
||||
</g><g id="edge26" class="edge">
|
||||
<title>@strudel.cycles/webaudio@0.6.0->@strudel.cycles/core@0.6.8</title>
|
||||
<path fill="none" stroke="black" d="M241.66,-350.81C237.99,-348.55 234.63,-345.84 231.73,-342.6 143.1,-243.48 284.75,-144.37 195.73,-45.6 193.96,-43.64 192.03,-41.87 189.96,-40.27"></path>
|
||||
<polygon fill="black" stroke="black" points="191.74,-37.25 181.4,-34.93 188.03,-43.19 191.74,-37.25"></polygon>
|
||||
</g><g id="edge2" class="edge">
|
||||
<title>@strudel.cycles/csound@0.6.2->@csound/browser@6.18.5</title>
|
||||
<path fill="none" stroke="black" d="M164.06,-276.96C190.24,-271.62 222.97,-264.93 250.6,-259.29"></path>
|
||||
<polygon fill="black" stroke="black" points="251.43,-262.69 260.52,-257.26 250.02,-255.84 251.43,-262.69"></polygon>
|
||||
</g></g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 31 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 110 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 520 KiB |
@@ -78,7 +78,7 @@ const base = BASE_URL;
|
||||
// https://medium.com/quick-code/100vh-problem-with-ios-safari-92ab23c852a8
|
||||
const appHeight = () => {
|
||||
const doc = document.documentElement;
|
||||
doc.style.setProperty('--app-height', `${window.innerHeight}px`);
|
||||
doc.style.setProperty('--app-height', `${window.innerHeight - 1}px`);
|
||||
};
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('resize', appHeight);
|
||||
|
||||
@@ -19,7 +19,10 @@ const langCode = 'en'; // getLanguageFromURL(currentPage);
|
||||
const sidebar = SIDEBAR[langCode];
|
||||
---
|
||||
|
||||
<nav class="flex justify-between py-2 px-4 items-center h-14 max-h-14 bg-lineHighlight text-foreground" title="Top Navigation">
|
||||
<nav
|
||||
class="flex justify-between py-2 px-4 items-center h-14 max-h-14 bg-lineHighlight text-foreground"
|
||||
title="Top Navigation"
|
||||
>
|
||||
<!-- <div class="menu-toggle">
|
||||
<SidebarToggle client:idle />
|
||||
</div> -->
|
||||
@@ -36,7 +39,7 @@ const sidebar = SIDEBAR[langCode];
|
||||
</div>
|
||||
{/* KNOWN_LANGUAGE_CODES.length > 1 && <LanguageSelect lang={lang} client:idle /> */}
|
||||
<div class="search-item h-10">
|
||||
<!-- <Search client:idle /> -->
|
||||
<!-- <Search client:idle /> -->
|
||||
</div>
|
||||
<a href="./" class="hidden md:flex cursor-pointer items-center space-x-1"
|
||||
><CommandLineIcon className="w-5 h-5" /><span>go to REPL</span>
|
||||
|
||||
@@ -23,7 +23,7 @@ const sidebar = SIDEBAR[langCode];
|
||||
<h2>{header}</h2>
|
||||
<ul>
|
||||
{children.map((child) => {
|
||||
const url = '.' + Astro.site?.pathname + child.link;
|
||||
const url = Astro.site?.pathname + child.link;
|
||||
return (
|
||||
<li class="">
|
||||
<a
|
||||
|
||||
@@ -33,9 +33,9 @@ export const COMMUNITY_INVITE_URL = `https://discord.com/invite/HGEdXmRkzT`;
|
||||
|
||||
// See "Algolia" section of the README for more information.
|
||||
export const ALGOLIA = {
|
||||
indexName: 'XXXXXXXXXX',
|
||||
appId: 'XXXXXXXXXX',
|
||||
apiKey: 'XXXXXXXXXX',
|
||||
indexName: 'strudel-tidalcycles',
|
||||
appId: 'SAZ71S8CLS',
|
||||
apiKey: 'd5044f9d21b80e7721e5b0067a8730b1',
|
||||
};
|
||||
|
||||
export type Sidebar = Record<(typeof KNOWN_LANGUAGE_CODES)[number], Record<string, { text: string; link: string }[]>>;
|
||||
@@ -73,9 +73,9 @@ export const SIDEBAR: Sidebar = {
|
||||
],
|
||||
Development: [
|
||||
{ text: 'REPL', link: 'technical-manual/repl' },
|
||||
{ text: 'Packages', link: 'technical-manual/packages' },
|
||||
{ text: 'Docs', link: 'technical-manual/docs' },
|
||||
{ text: 'Testing', link: 'technical-manual/testing' },
|
||||
// { text: 'Packages', link: 'technical-manual/packages' },
|
||||
// { text: 'Internals', link: 'technical-manual/internals' },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -159,8 +159,6 @@ Using "!" we can repeat without speeding up:
|
||||
|
||||
<MiniRepl client:idle tune={`note("<[g3,b3,e4]!2 [a3,c3,e4] [b3,d3,f#4]>")`} punchcard />
|
||||
|
||||
In essence, the `x!n` is like a shortcut for `[x*n]@n`.
|
||||
|
||||
## Mini-notation review
|
||||
|
||||
To recap what we've learned so far, compare the following patterns:
|
||||
|
||||
@@ -7,5 +7,79 @@ layout: ../../layouts/MainLayout.astro
|
||||
|
||||
You can use Strudel even without a network! When you first visit the [Strudel REPL](strudel.tidalcycles.org/),
|
||||
your browser will download the whole web app including documentation.
|
||||
When the download is finished (<1MB), you can visit the website even when offline,
|
||||
getting the downloaded website instead of the online one.
|
||||
|
||||
When the site gets updated, your browser will download that update on the next online visit.
|
||||
When an update is available, the site will refresh after the download is finished.
|
||||
|
||||
This works because Strudel is implemented as progessive web app (using [Vite PWA](https://vite-pwa-org.netlify.app/)).
|
||||
|
||||
## Samples
|
||||
|
||||
While the browser will download the app itself, samples are only downloaded when you're actively using them.
|
||||
So to make sure a specific set of samples is available when offline, just use them.
|
||||
Also, only samples from these domains will be cached for offline use:
|
||||
|
||||
- `https://raw.githubusercontent.com/*` for samples uploaded to github
|
||||
- `https://freesound.org/*` / `https://cdn.freesound.org/*` for freesound
|
||||
- `https://shabda.ndre.gr/.*` for shabda
|
||||
|
||||
## Inspecting / Clearing Cache
|
||||
|
||||
You can view all cached files in your browser.
|
||||
|
||||
### Firefox
|
||||
|
||||
- Open the Developer Tools (`Tools > Web Developer > Web Developer Tools`)
|
||||
- go to `Storage` tab and expand `Cache Storage > https://strudel.tidalcycles.org`.
|
||||
- or go to the `Application` tab and view the latest updates in `Service Workers`
|
||||
|
||||
### Chromium based Browsers
|
||||
|
||||
- Open Developer Tools (`Right Click > Inspect`)
|
||||
- go to the `Application` tab
|
||||
- view downloaded files under `Cache > Cache Storage`
|
||||
- view the latest updates in `Service Workers`
|
||||
|
||||
## Strudel Standalone App
|
||||
|
||||
You can also install Strudel as a standalone app on most devices.
|
||||
A standalone app has its own desktop / homescreen icon and launches in a separate window,
|
||||
without the browser ui.
|
||||
|
||||
<figure>
|
||||
<img src="./pwa/strudel-macos.png" alt="Strudel on MacOS" />
|
||||
<figcaption>Strudel on MacOS</figcaption>
|
||||
</figure>
|
||||
|
||||
### Desktop
|
||||
|
||||
With a chromium based browser:
|
||||
|
||||
1. go to the [Strudel REPL](strudel.tidalcycles.org/).
|
||||
2. on the right of the adress bar, click `install Strudel REPL`
|
||||
3. the REPL should now run as a standalone chromium app
|
||||
|
||||
Without a chromium based browser, you can use [nativefier](https://github.com/nativefier/nativefier) to generate a desktop app:
|
||||
|
||||
1. make sure you have NodeJS installed
|
||||
2. run `npx nativefier strudel.tidalcycles.org`
|
||||
|
||||
<figure>
|
||||
<img src="./pwa/strudel-linux.png" alt="Strudel on Linux" />
|
||||
<figcaption>Strudel on Linux</figcaption>
|
||||
</figure>
|
||||
|
||||
### iOS
|
||||
|
||||
1. open to the [Strudel REPL](strudel.tidalcycles.org/) in safari
|
||||
2. press the share icon and tab `Add to homescreen`
|
||||
3. You should now have a strudel app icon that opens the repl in full screen
|
||||
|
||||
### Android
|
||||
|
||||
1. open to the [Strudel REPL](strudel.tidalcycles.org/)
|
||||
2. Tab the install button at the bottom
|
||||
|
||||
Ok, what are [Patterns](/technical-manual/patterns) all about?
|
||||
|
||||
@@ -5,75 +5,58 @@ layout: ../../layouts/MainLayout.astro
|
||||
|
||||
import { MiniRepl } from '../../docs/MiniRepl';
|
||||
|
||||
## Strudel Packages
|
||||
# Strudel Packages
|
||||
|
||||
The [strudel repo](github.com/tidalcycles/strudel) is organized into packages, using [npm workspaces](https://docs.npmjs.com/cli/v7/using-npm/workspaces).
|
||||
Publishing packages is done with [lerna](https://lerna.js.org/).
|
||||
The [strudel repo](github.com/tidalcycles/strudel) is organized as a monorepo, containing multiple npm packages.
|
||||
The purpose of the multiple packages is to
|
||||
|
||||
There are different packages for different purposes. They..
|
||||
- organize the codebase into more modular, encapsulated pieces
|
||||
- be able to opt out of certain functionalities
|
||||
- keep the dependencies of the core packages small
|
||||
|
||||
- split up the code into smaller chunks
|
||||
- can be selectively used to implement some sort of time based system
|
||||
## Overview
|
||||
|
||||
[See the latest published packages on npm](https://www.npmjs.com/search?q=%40strudel.cycles).
|
||||
Here is an overview of all the packages:
|
||||
|
||||
### Important bits
|
||||
### Essential Packages
|
||||
|
||||
- The [root package.json](https://github.com/tidalcycles/strudel/blob/main/package.json) specifies `packages/*` as `workspaces`
|
||||
- Each folder in `packages` comes with its own `package.json`, defining a package name of the format `@strudel.cycles/<name>`
|
||||
- Running `npm i` from the root folder will symlink all packages to the `node_modules` folder, e.g. `node_modules/@strudel.cycles/core` symlinks `packages/core`
|
||||
- These symlinks allow importing the packages with their package name, instead of a relative path, e.g. `import { seq } from '@strudel.cycles/core'`, instead of `import { seq } from '../core/`.
|
||||
This works because the [bare module import](https://vitejs.dev/guide/features.html#npm-dependency-resolving-and-pre-bundling) `@strudel.cycles/core` is resolved to `node_modules/@strudel.cycles/core`.
|
||||
In a project that installs the published packages from npm, these imports will still work, whereas relative ones might not.
|
||||
- When a strudel package is importing from another strudel package, the package that is imported from should be listed in the `dependencies` field of the `package.json`.
|
||||
For example, [@strudel.cycles/mini lists `@strudel.cycles/core` as a dependency](https://github.com/tidalcycles/strudel/blob/main/packages/mini/package.json).
|
||||
- In development, files in any package can be changed and saved to instantly update the dev server via [hot module replacement](https://vitejs.dev/guide/features.html#hot-module-replacement)
|
||||
- To publish packages, `npx lerna publish` will check which packages were changed since the last publish and publish only those.
|
||||
The version numbers in the dependencies of each packages will be updated automatically to the latest version.
|
||||
These package are the most essential. You might want to use all of those if you're using strudel in your project:
|
||||
|
||||
### Building & Publishing
|
||||
- [core](https://github.com/tidalcycles/strudel/tree/main/packages/core#strudelcyclescore): tidal pattern engine with core primitives
|
||||
- [mini](https://github.com/tidalcycles/strudel/tree/main/packages/mini#strudelcyclesmini): mini notation parser + core bindings
|
||||
- [transpiler](https://github.com/tidalcycles/strudel/tree/main/packages/transpiler#strudelcyclestranspiler): user code transpiler. syntax sugar + highlighting
|
||||
|
||||
Currently, all packages are only published as ESM with vite flavour.
|
||||
To build standardized ESM and CJS files, a `vite.config.js` like that is needed:
|
||||
### Language Extensions
|
||||
|
||||
```js
|
||||
import { defineConfig } from 'vite';
|
||||
import { dependencies } from './package.json';
|
||||
import { resolve } from 'path';
|
||||
These packages extend the pattern language by specific functions
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [],
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'index.mjs'),
|
||||
formats: ['es', 'cjs'],
|
||||
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' }[ext]),
|
||||
},
|
||||
rollupOptions: {
|
||||
external: [...Object.keys(dependencies)],
|
||||
},
|
||||
target: 'esnext',
|
||||
},
|
||||
});
|
||||
```
|
||||
- [tonal](https://github.com/tidalcycles/strudel/tree/main/packages/tonal): tonal functions for scales and chords
|
||||
- [xen](https://github.com/tidalcycles/strudel/tree/main/packages/xen): microtonal / xenharmonic functions
|
||||
|
||||
This will build `index.mjs` (ESM) and `index.js` (CJS) to the dist folder.
|
||||
### Outputs
|
||||
|
||||
### What's the main file?
|
||||
These packages provide bindings for different ways to output strudel patterns:
|
||||
|
||||
Currently, each package uses the unbundled `index.mjs` as its main file, which must change for the published version.
|
||||
There are 2 ways to handle this:
|
||||
- [webaudio](https://github.com/tidalcycles/strudel/tree/main/packages/webaudio#strudelcycleswebaudio): the default webaudio output
|
||||
- [osc](https://github.com/tidalcycles/strudel/tree/main/packages/osc#strudelcyclesosc): bindings to communicate via OSC
|
||||
- [midi](https://github.com/tidalcycles/strudel/tree/main/packages/midi#strudelcyclesmidi): webmidi bindings
|
||||
- [csound](https://github.com/tidalcycles/strudel/tree/main/packages/csound#strudelcyclescsound): csound bindings
|
||||
- [soundfonts](https://github.com/tidalcycles/strudel/tree/main/packages/serial#strudelcyclessoundfonts): Soundfont support
|
||||
- [serial](https://github.com/tidalcycles/strudel/tree/main/packages/serial#strudelcyclesserial): webserial bindings
|
||||
|
||||
1. `main` = `dist/index.js`, `module` = `dist/index.mjs`. The built files are used. This means that changing a source file won't take effect in the dev server without a rebuild.
|
||||
2. Use different `package.json` files for dev vs publish. So the unbuilt `index.mjs` could be used in dev, while the built files can be used when publishing.
|
||||
### Others
|
||||
|
||||
Option 1 could be done with [workspace watching](https://lerna.js.org/docs/features/workspace-watching), although it might make the dev server less snappy..
|
||||
Option 2 can be done by [publishing just the dist folder and copying over the `package.json` file](https://stackoverflow.com/questions/37862712/how-to-publish-contents-only-of-a-specific-folder).
|
||||
Sadly, [this does not fit into how lerna works](https://github.com/lerna/lerna/issues/91).
|
||||
- [embed](https://github.com/tidalcycles/strudel/tree/main/packages/embed#strudelcyclesembed): embeddable REPL web component
|
||||
- [react](https://github.com/tidalcycles/strudel/tree/main/packages/react#strudelcyclesreact): react hooks and components for strudel
|
||||
|
||||
https://github.com/changesets/changesets
|
||||
### No Longer Maintained
|
||||
|
||||
https://turbo.build/repo/docs/handbook/publishing-packages/versioning-and-publishing
|
||||
- [eval](https://github.com/tidalcycles/strudel/tree/main/packages/eval): old code transpiler
|
||||
- [tone](https://github.com/tidalcycles/strudel/tree/main/packages/tone#strudelcyclestone): bindings for Tone.js instruments and effects
|
||||
- [webdirt](https://github.com/tidalcycles/strudel/tree/main/packages/webdirt): webdirt bindings, replaced by webaudio package
|
||||
|
||||
https://pnpm.io/workspaces
|
||||
## Tools
|
||||
|
||||
- [pnpm workspaces](https://pnpm.io/)
|
||||
- Publishing packages is done with [lerna](https://lerna.js.org/).
|
||||
|
||||
@@ -92,7 +92,7 @@ export function Footer({ context }) {
|
||||
{activeFooter === 'console' && <ConsoleTab log={log} />}
|
||||
{activeFooter === 'samples' && <SamplesTab />}
|
||||
{activeFooter === 'reference' && <Reference />}
|
||||
{activeFooter === 'settings' && <SettingsTab />}
|
||||
{activeFooter === 'settings' && <SettingsTab scheduler={context.scheduler} />}
|
||||
</div>
|
||||
)}
|
||||
</footer>
|
||||
@@ -284,10 +284,28 @@ const fontFamilyOptions = {
|
||||
PressStart: 'PressStart2P',
|
||||
};
|
||||
|
||||
function SettingsTab() {
|
||||
function SettingsTab({ scheduler }) {
|
||||
const { theme, keybindings, fontSize, fontFamily } = useSettings();
|
||||
return (
|
||||
<div className="text-foreground p-4 space-y-4">
|
||||
{/* <FormItem label="Tempo">
|
||||
<div className="space-x-4">
|
||||
<button
|
||||
onClick={() => {
|
||||
scheduler.setCps(scheduler.cps - 0.1);
|
||||
}}
|
||||
>
|
||||
slower
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
scheduler.setCps(scheduler.cps + 0.1);
|
||||
}}
|
||||
>
|
||||
faster
|
||||
</button>
|
||||
</div>
|
||||
</FormItem> */}
|
||||
<FormItem label="Theme">
|
||||
<SelectInput options={themeOptions} value={theme} onChange={(theme) => settingsMap.setKey('theme', theme)} />
|
||||
</FormItem>
|
||||
|
||||
@@ -242,6 +242,7 @@ export function Repl({ embedded = false }) {
|
||||
}
|
||||
};
|
||||
const context = {
|
||||
scheduler,
|
||||
embedded,
|
||||
started,
|
||||
pending,
|
||||
@@ -273,7 +274,10 @@ export function Repl({ embedded = false }) {
|
||||
fontSize={fontSize}
|
||||
fontFamily={fontFamily}
|
||||
onChange={handleChangeCode}
|
||||
onViewChanged={setView}
|
||||
onViewChanged={(v) => {
|
||||
setView(v);
|
||||
// window.editorView = v;
|
||||
}}
|
||||
onSelectionChange={handleSelectionChange}
|
||||
/>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user