separate out strudel.mjs, make index.mjs aggregate module

This commit is contained in:
alex 2022-04-13 17:25:02 +01:00
parent 0396c3f475
commit f75da0e2e3
21 changed files with 296 additions and 329 deletions

View File

@ -1,4 +1,4 @@
import { Pattern, sequence } from '@strudel.cycles/core/strudel.mjs';
import { Pattern, sequence } from './pattern.mjs';
const controls = {};
const generic_params = [

View File

@ -1,4 +1,4 @@
import { Pattern, timeCat } from './strudel.mjs';
import { Pattern, timeCat } from './pattern.mjs';
import bjork from 'bjork';
import { rotate } from './util.mjs';
import Fraction from './fraction.mjs';

View File

@ -1,5 +1,5 @@
import Fraction from 'fraction.js';
import { TimeSpan } from './strudel.mjs';
import { TimeSpan } from './timespan.mjs';
// Returns the start of the cycle.
Fraction.prototype.sam = function () {

90
packages/core/hap.mjs Normal file
View File

@ -0,0 +1,90 @@
export class Hap {
/*
Event class, representing a value active during the timespan
'part'. This might be a fragment of an event, in which case the
timespan will be smaller than the 'whole' timespan, otherwise the
two timespans will be the same. The 'part' must never extend outside of the
'whole'. If the event represents a continuously changing value
then the whole will be returned as None, in which case the given
value will have been sampled from the point halfway between the
start and end of the 'part' timespan.
The context is to store a list of source code locations causing the event
*/
constructor(whole, part, value, context = {}, stateful = false) {
this.whole = whole;
this.part = part;
this.value = value;
this.context = context;
this.stateful = stateful;
if (stateful) {
console.assert(typeof this.value === 'function', 'Stateful values must be functions');
}
}
get duration() {
return this.whole.end.sub(this.whole.begin).valueOf();
}
wholeOrPart() {
return this.whole ? this.whole : this.part;
}
withSpan(func) {
// Returns a new event with the function f applies to the event timespan.
const whole = this.whole ? func(this.whole) : undefined;
return new Hap(whole, func(this.part), this.value, this.context);
}
withValue(func) {
// Returns a new event with the function f applies to the event value.
return new Hap(this.whole, this.part, func(this.value), this.context);
}
hasOnset() {
// Test whether the event contains the onset, i.e that
// the beginning of the part is the same as that of the whole timespan."""
return this.whole != undefined && this.whole.begin.equals(this.part.begin);
}
resolveState(state) {
if (this.stateful && this.hasOnset()) {
console.log('stateful');
const func = this.value;
const [newState, newValue] = func(state);
return [newState, new Hap(this.whole, this.part, newValue, this.context, false)];
}
return [state, this];
}
spanEquals(other) {
return (this.whole == undefined && other.whole == undefined) || this.whole.equals(other.whole);
}
equals(other) {
return (
this.spanEquals(other) &&
this.part.equals(other.part) &&
// TODO would == be better ??
this.value === other.value
);
}
show() {
return (
'(' + (this.whole == undefined ? '~' : this.whole.show()) + ', ' + this.part.show() + ', ' + this.value + ')'
);
}
combineContext(b) {
const a = this;
return { ...a.context, ...b.context, locations: (a.context.locations || []).concat(b.context.locations || []) };
}
setContext(context) {
return new Hap(this.whole, this.part, this.value, context);
}
}
export default Hap;

View File

@ -0,0 +1,10 @@
export * from './controls.mjs';
export * from './euclid.mjs';
import Fraction from './fraction.mjs';
export {Fraction};
export * from './hap.mjs';
export * from './pattern.mjs';
export * from './state.mjs';
export * from './timespan.mjs';
export * from './util.mjs';
// export * from './value.mjs';

View File

@ -2,7 +2,7 @@
"name": "@strudel.cycles/core",
"version": "0.0.3",
"description": "Port of Tidal Cycles to JavaScript",
"main": "strudel.mjs",
"main": "index.mjs",
"type": "module",
"scripts": {
"test": "mocha --colors"

View File

@ -1,216 +1,11 @@
import TimeSpan from './timespan.mjs';
import Fraction from './fraction.mjs';
import Hap from './hap.mjs';
import State from './state.mjs';
import { isNote, toMidi, compose, removeUndefineds, flatten, id, listRange, curry } from './util.mjs';
class TimeSpan {
constructor(begin, end) {
this.begin = Fraction(begin);
this.end = Fraction(end);
}
get spanCycles() {
const spans = [];
var begin = this.begin;
const end = this.end;
const end_sam = end.sam();
while (end.gt(begin)) {
// If begin and end are in the same cycle, we're done.
if (begin.sam().equals(end_sam)) {
spans.push(new TimeSpan(begin, this.end));
break;
}
// add a timespan up to the next sam
const next_begin = begin.nextSam();
spans.push(new TimeSpan(begin, next_begin));
// continue with the next cycle
begin = next_begin;
}
return spans;
}
cycleArc() {
// Shifts a timespan to one of equal duration that starts within cycle zero.
// (Note that the output timespan probably does not start *at* Time 0 --
// that only happens when the input Arc starts at an integral Time.)
const b = this.begin.cyclePos();
const e = b + (this.end - this.begin);
return new TimeSpan(b, e);
}
withTime(func_time) {
// Applies given function to both the begin and end time of the timespan"""
return new TimeSpan(func_time(this.begin), func_time(this.end));
}
withEnd(func_time) {
// Applies given function to the end time of the timespan"""
return new TimeSpan(this.begin, func_time(this.end));
}
withCycle(func_time) {
// Like withTime, but time is relative to relative to the cycle (i.e. the
// sam of the start of the timespan)
const sam = this.begin.sam();
const b = sam.add(func_time(this.begin.sub(sam)));
const e = sam.add(func_time(this.end.sub(sam)));
return new TimeSpan(b, e);
}
intersection(other) {
// Intersection of two timespans, returns None if they don't intersect.
const intersect_begin = this.begin.max(other.begin);
const intersect_end = this.end.min(other.end);
if (intersect_begin.gt(intersect_end)) {
return undefined;
}
if (intersect_begin.equals(intersect_end)) {
// Zero-width (point) intersection - doesn't intersect if it's at the end of a
// non-zero-width timespan.
if (intersect_begin.equals(this.end) && this.begin.lt(this.end)) {
return undefined;
}
if (intersect_begin.equals(other.end) && other.begin.lt(other.end)) {
return undefined;
}
}
return new TimeSpan(intersect_begin, intersect_end);
}
intersection_e(other) {
// Like 'sect', but raises an exception if the timespans don't intersect.
const result = this.intersection(other);
if (result == undefined) {
// TODO - raise exception
// raise ValueError(f'TimeSpan {self} and TimeSpan {other} do not intersect')
}
return result;
}
midpoint() {
return this.begin.add(this.end.sub(this.begin).div(Fraction(2)));
}
equals(other) {
return this.begin.equals(other.begin) && this.end.equals(other.end);
}
show() {
return this.begin.show() + ' -> ' + this.end.show();
}
}
class Hap {
/*
Event class, representing a value active during the timespan
'part'. This might be a fragment of an event, in which case the
timespan will be smaller than the 'whole' timespan, otherwise the
two timespans will be the same. The 'part' must never extend outside of the
'whole'. If the event represents a continuously changing value
then the whole will be returned as None, in which case the given
value will have been sampled from the point halfway between the
start and end of the 'part' timespan.
The context is to store a list of source code locations causing the event
*/
constructor(whole, part, value, context = {}, stateful = false) {
this.whole = whole;
this.part = part;
this.value = value;
this.context = context;
this.stateful = stateful;
if (stateful) {
console.assert(typeof this.value === 'function', 'Stateful values must be functions');
}
}
get duration() {
return this.whole.end.sub(this.whole.begin).valueOf();
}
wholeOrPart() {
return this.whole ? this.whole : this.part;
}
withSpan(func) {
// Returns a new event with the function f applies to the event timespan.
const whole = this.whole ? func(this.whole) : undefined;
return new Hap(whole, func(this.part), this.value, this.context);
}
withValue(func) {
// Returns a new event with the function f applies to the event value.
return new Hap(this.whole, this.part, func(this.value), this.context);
}
hasOnset() {
// Test whether the event contains the onset, i.e that
// the beginning of the part is the same as that of the whole timespan."""
return this.whole != undefined && this.whole.begin.equals(this.part.begin);
}
resolveState(state) {
if (this.stateful && this.hasOnset()) {
console.log('stateful');
const func = this.value;
const [newState, newValue] = func(state);
return [newState, new Hap(this.whole, this.part, newValue, this.context, false)];
}
return [state, this];
}
spanEquals(other) {
return (this.whole == undefined && other.whole == undefined) || this.whole.equals(other.whole);
}
equals(other) {
return (
this.spanEquals(other) &&
this.part.equals(other.part) &&
// TODO would == be better ??
this.value === other.value
);
}
show() {
return (
'(' + (this.whole == undefined ? '~' : this.whole.show()) + ', ' + this.part.show() + ', ' + this.value + ')'
);
}
combineContext(b) {
const a = this;
return { ...a.context, ...b.context, locations: (a.context.locations || []).concat(b.context.locations || []) };
}
setContext(context) {
return new Hap(this.whole, this.part, this.value, context);
}
}
export class State {
constructor(span, controls = {}) {
this.span = span;
this.controls = controls;
}
// Returns new State with different span
setSpan(span) {
return new State(span, this.controls);
}
withSpan(func) {
return this.setSpan(func(this.span));
}
// Returns new State with different controls
setControls(controls) {
return new State(this.span, controls);
}
}
class Pattern {
export class Pattern {
// the following functions will get patternFactories as nested functions:
constructor(query) {
this.query = query;
@ -708,8 +503,8 @@ class Pattern {
}
_zoom(s, e) {
e = Fraction(e)
s = Fraction(s)
e = Fraction(e);
s = Fraction(s);
const d = e.sub(s);
return this.withQuerySpan((span) => span.withCycle((t) => t.mul(d).add(s)))
.withEventSpan((span) => span.withCycle((t) => t.sub(s).div(d)))
@ -932,9 +727,9 @@ Pattern.prototype.factories = { pure, stack, slowcat, fastcat, cat, timeCat, seq
// Elemental patterns
// Nothing
const silence = new Pattern((_) => []);
export const silence = new Pattern((_) => []);
function pure(value) {
export function pure(value) {
// A discrete value that repeats once per cycle
function query(state) {
return state.span.spanCycles.map((subspan) => new Hap(Fraction(subspan.begin).wholeCycle(), subspan, value));
@ -942,7 +737,7 @@ function pure(value) {
return new Pattern(query);
}
function steady(value) {
export function steady(value) {
// A continuous value
return new Pattern((span) => Hap(undefined, span, value));
}
@ -978,7 +773,7 @@ export function isPattern(thing) {
return thing instanceof Pattern;
}
function reify(thing) {
export function reify(thing) {
// Turns something into a pattern, unless it's already a pattern
if (isPattern(thing)) {
return thing;
@ -987,13 +782,13 @@ function reify(thing) {
}
// Basic functions for combining patterns
function stack(...pats) {
export function stack(...pats) {
const reified = pats.map((pat) => reify(pat));
const query = (state) => flatten(reified.map((pat) => pat.query(state)));
return new Pattern(query);
}
function slowcat(...pats) {
export function slowcat(...pats) {
// Concatenation: combines a list of patterns, switching between them
// successively, one per cycle.
pats = pats.map(reify);
@ -1014,7 +809,7 @@ function slowcat(...pats) {
return new Pattern(query)._splitQueries();
}
function slowcatPrime(...pats) {
export function slowcatPrime(...pats) {
// Concatenation: combines a list of patterns, switching between them
// successively, one per cycle. Unlike slowcat, this version will skip cycles.
pats = pats.map(reify);
@ -1026,17 +821,17 @@ function slowcatPrime(...pats) {
return new Pattern(query)._splitQueries();
}
function fastcat(...pats) {
export function fastcat(...pats) {
// Concatenation: as with slowcat, but squashes a cycle from each
// pattern into one cycle
return slowcat(...pats)._fast(pats.length);
}
function cat(...pats) {
export function cat(...pats) {
return fastcat(...pats);
}
function timeCat(...timepats) {
export function timeCat(...timepats) {
// Like cat, but where each step has a temporal 'weight'
const total = timepats.map((a) => a[0]).reduce((a, b) => a.add(b), Fraction(0));
let begin = Fraction(0);
@ -1062,11 +857,11 @@ function _sequenceCount(x) {
return [reify(x), 1];
}
function sequence(...xs) {
export function sequence(...xs) {
return _sequenceCount(xs)[0];
}
function polymeterSteps(steps, ...args) {
export function polymeterSteps(steps, ...args) {
const seqs = args.map((a) => _sequenceCount(a));
if (seqs.length == 0) {
return silence;
@ -1088,16 +883,16 @@ function polymeterSteps(steps, ...args) {
return stack(...pats);
}
function polymeter(...args) {
export function polymeter(...args) {
return polymeterSteps(0, ...args);
}
// alias
function pm(...args) {
export function pm(...args) {
polymeter(...args);
}
function polyrhythm(...xs) {
export function polyrhythm(...xs) {
const seqs = xs.map((a) => sequence(a));
if (seqs.length == 0) {
@ -1107,40 +902,40 @@ function polyrhythm(...xs) {
}
// alias
function pr(args) {
export function pr(args) {
polyrhythm(args);
}
const add = curry((a, pat) => pat.add(a));
const append = curry((a, pat) => pat.append(a));
const chunk = curry((a, pat) => pat.chunk(a));
const chunkBack = curry((a, pat) => pat.chunkBack(a));
const div = curry((a, pat) => pat.div(a));
const early = curry((a, pat) => pat.early(a));
const echo = curry((a, b, c, pat) => pat.echo(a, b, c));
const every = curry((i, f, pat) => pat.every(i, f));
const fast = curry((a, pat) => pat.fast(a));
const inv = (pat) => pat.inv();
const invert = (pat) => pat.invert();
const iter = curry((a, pat) => pat.iter(a));
const iterBack = curry((a, pat) => pat.iter(a));
const jux = curry((f, pat) => pat.jux(f));
const juxBy = curry((by, f, pat) => pat.juxBy(by, f));
const late = curry((a, pat) => pat.late(a));
const linger = curry((a, pat) => pat.linger(a));
const mask = curry((a, pat) => pat.mask(a));
const mul = curry((a, pat) => pat.mul(a));
const off = curry((t, f, pat) => pat.off(t, f));
const ply = curry((a, pat) => pat.ply(a));
const range = curry((a, b, pat) => pat.range(a, b));
const range2 = curry((a, b, pat) => pat.range2(a, b));
const rev = (pat) => pat.rev();
const slow = curry((a, pat) => pat.slow(a));
const struct = curry((a, pat) => pat.struct(a));
const sub = curry((a, pat) => pat.sub(a));
const superimpose = curry((array, pat) => pat.superimpose(...array));
const union = curry((a, pat) => pat.union(a));
const when = curry((binary, f, pat) => pat.when(binary, f));
export const add = curry((a, pat) => pat.add(a));
export const append = curry((a, pat) => pat.append(a));
export const chunk = curry((a, pat) => pat.chunk(a));
export const chunkBack = curry((a, pat) => pat.chunkBack(a));
export const div = curry((a, pat) => pat.div(a));
export const early = curry((a, pat) => pat.early(a));
export const echo = curry((a, b, c, pat) => pat.echo(a, b, c));
export const every = curry((i, f, pat) => pat.every(i, f));
export const fast = curry((a, pat) => pat.fast(a));
export const inv = (pat) => pat.inv();
export const invert = (pat) => pat.invert();
export const iter = curry((a, pat) => pat.iter(a));
export const iterBack = curry((a, pat) => pat.iter(a));
export const jux = curry((f, pat) => pat.jux(f));
export const juxBy = curry((by, f, pat) => pat.juxBy(by, f));
export const late = curry((a, pat) => pat.late(a));
export const linger = curry((a, pat) => pat.linger(a));
export const mask = curry((a, pat) => pat.mask(a));
export const mul = curry((a, pat) => pat.mul(a));
export const off = curry((t, f, pat) => pat.off(t, f));
export const ply = curry((a, pat) => pat.ply(a));
export const range = curry((a, b, pat) => pat.range(a, b));
export const range2 = curry((a, b, pat) => pat.range2(a, b));
export const rev = (pat) => pat.rev();
export const slow = curry((a, pat) => pat.slow(a));
export const struct = curry((a, pat) => pat.struct(a));
export const sub = curry((a, pat) => pat.sub(a));
export const superimpose = curry((array, pat) => pat.superimpose(...array));
export const union = curry((a, pat) => pat.union(a));
export const when = curry((binary, f, pat) => pat.when(binary, f));
// problem: curried functions with spread arguments must have pat at the beginning
// with this, we cannot keep the pattern open at the end.. solution for now: use array to keep using pat as last arg
@ -1202,11 +997,11 @@ Pattern.prototype.chunkBack = function (...args) {
Pattern.prototype.zoom = function (...args) {
args = args.map(reify);
return patternify2(Pattern.prototype._zoom)(...args, this);
}
};
Pattern.prototype.compress = function (...args) {
args = args.map(reify);
return patternify2(Pattern.prototype._compress)(...args, this);
}
};
// call this after all Patter.prototype.define calls have been executed! (right before evaluate)
Pattern.prototype.bootstrap = function () {
@ -1260,54 +1055,3 @@ Pattern.prototype.define = (name, func, options = {}) => {
Pattern.prototype.define('hush', (pat) => pat.hush(), { patternified: false, composable: true });
Pattern.prototype.define('bypass', (pat) => pat.bypass(on), { patternified: true, composable: true });
export {
Fraction,
Hap,
Pattern,
TimeSpan,
add,
append,
cat,
chunk,
chunkBack,
div,
early,
echo,
every,
fast,
fastcat,
id,
inv,
invert,
iter,
iterBack,
jux,
juxBy,
late,
linger,
mask,
mul,
off,
ply,
pm,
polymeter,
polymeterSteps,
polyrhythm,
pr,
pure,
range,
range2,
reify,
rev,
sequence,
silence,
slow,
slowcat,
stack,
struct,
sub,
superimpose,
timeCat,
union,
when,
};

22
packages/core/state.mjs Normal file
View File

@ -0,0 +1,22 @@
export class State {
constructor(span, controls = {}) {
this.span = span;
this.controls = controls;
}
// Returns new State with different span
setSpan(span) {
return new State(span, this.controls);
}
withSpan(func) {
return this.setSpan(func(this.span));
}
// Returns new State with different controls
setControls(controls) {
return new State(this.span, controls);
}
}
export default State;

View File

@ -35,7 +35,7 @@ import {
tri2,
id,
ply,
} from '../strudel.mjs';
} from '../index.mjs';
//import { Time } from 'tone';
import pkg from 'tone';
const { Time } = pkg;

103
packages/core/timespan.mjs Normal file
View File

@ -0,0 +1,103 @@
import Fraction from './fraction.mjs';
export class TimeSpan {
constructor(begin, end) {
this.begin = Fraction(begin);
this.end = Fraction(end);
}
get spanCycles() {
const spans = [];
var begin = this.begin;
const end = this.end;
const end_sam = end.sam();
while (end.gt(begin)) {
// If begin and end are in the same cycle, we're done.
if (begin.sam().equals(end_sam)) {
spans.push(new TimeSpan(begin, this.end));
break;
}
// add a timespan up to the next sam
const next_begin = begin.nextSam();
spans.push(new TimeSpan(begin, next_begin));
// continue with the next cycle
begin = next_begin;
}
return spans;
}
cycleArc() {
// Shifts a timespan to one of equal duration that starts within cycle zero.
// (Note that the output timespan probably does not start *at* Time 0 --
// that only happens when the input Arc starts at an integral Time.)
const b = this.begin.cyclePos();
const e = b + (this.end - this.begin);
return new TimeSpan(b, e);
}
withTime(func_time) {
// Applies given function to both the begin and end time of the timespan"""
return new TimeSpan(func_time(this.begin), func_time(this.end));
}
withEnd(func_time) {
// Applies given function to the end time of the timespan"""
return new TimeSpan(this.begin, func_time(this.end));
}
withCycle(func_time) {
// Like withTime, but time is relative to relative to the cycle (i.e. the
// sam of the start of the timespan)
const sam = this.begin.sam();
const b = sam.add(func_time(this.begin.sub(sam)));
const e = sam.add(func_time(this.end.sub(sam)));
return new TimeSpan(b, e);
}
intersection(other) {
// Intersection of two timespans, returns None if they don't intersect.
const intersect_begin = this.begin.max(other.begin);
const intersect_end = this.end.min(other.end);
if (intersect_begin.gt(intersect_end)) {
return undefined;
}
if (intersect_begin.equals(intersect_end)) {
// Zero-width (point) intersection - doesn't intersect if it's at the end of a
// non-zero-width timespan.
if (intersect_begin.equals(this.end) && this.begin.lt(this.end)) {
return undefined;
}
if (intersect_begin.equals(other.end) && other.begin.lt(other.end)) {
return undefined;
}
}
return new TimeSpan(intersect_begin, intersect_end);
}
intersection_e(other) {
// Like 'sect', but raises an exception if the timespans don't intersect.
const result = this.intersection(other);
if (result == undefined) {
// TODO - raise exception
// raise ValueError(f'TimeSpan {self} and TimeSpan {other} do not intersect')
}
return result;
}
midpoint() {
return this.begin.add(this.end.sub(this.begin).div(Fraction(2)));
}
equals(other) {
return this.begin.equals(other.begin) && this.end.equals(other.end);
}
show() {
return this.begin.show() + ' -> ' + this.end.show();
}
}
export default TimeSpan;

View File

@ -15,7 +15,7 @@ import {
import shiftCodegen from 'shift-codegen';
const codegen = shiftCodegen.default || shiftCodegen; // parcel module resolution fuckup
import * as strudel from '@strudel.cycles/core/strudel.mjs';
import * as strudel from '@strudel.cycles/core';
const { Pattern } = strudel;

View File

@ -1,6 +1,6 @@
import { isNote } from 'tone';
import _WebMidi from 'webmidi';
import { Pattern, isPattern } from '@strudel.cycles/core/strudel.mjs';
import { Pattern, isPattern } from '@strudel.cycles/core';
import { Tone } from '@strudel.cycles/tone';
// if you use WebMidi from outside of this package, make sure to import that instance:

View File

@ -1,5 +1,5 @@
import * as krill from './krill-parser.js';
import * as strudel from '@strudel.cycles/core/strudel.mjs';
import * as strudel from '@strudel.cycles/core';
import { addMiniLocations } from '@strudel.cycles/eval/shapeshifter.mjs';
const { pure, Pattern, Fraction, stack, slowcat, sequence, timeCat, silence, reify } = strudel;

View File

@ -1,5 +1,5 @@
import OSC from './osc.js';
import { Pattern } from '@strudel.cycles/core/strudel.mjs';
import { Pattern } from '@strudel.cycles/core';
const comm = new OSC();
comm.open();

View File

@ -1,6 +1,6 @@
import { strict as assert } from 'assert';
import '../tonal.mjs'; // need to import this to add prototypes
import { pure } from '@strudel.cycles/core/strudel.mjs';
import { pure } from '@strudel.cycles/core';
describe('tonal', () => {
it('Should run tonal functions ', () => {

View File

@ -1,6 +1,5 @@
import { Note, Interval, Scale } from '@tonaljs/tonal';
import { Pattern } from '@strudel.cycles/core';
import { mod } from '@strudel.cycles/core/util.mjs';
import { Pattern, mod } from '@strudel.cycles/core';
// transpose note inside scale by offset steps
// function scaleTranspose(scale: string, offset: number, note: string) {

View File

@ -1,4 +1,4 @@
import { Pattern as _Pattern, stack, Hap, reify } from '@strudel.cycles/core/strudel.mjs';
import { Pattern as _Pattern, stack, Hap, reify } from '@strudel.cycles/core';
import _voicings from 'chord-voicings';
const { dictionaryVoicing, minTopNoteDiff, lefthand } = _voicings.default || _voicings; // parcel module resolution fuckup

View File

@ -1,6 +1,6 @@
import { strict as assert } from 'assert';
import '../tone.mjs';
import { pure } from '@strudel.cycles/core/strudel.mjs';
import { pure } from '@strudel.cycles/core';
import Tone from 'tone';
describe('tone', () => {

View File

@ -1,5 +1,5 @@
import Tune from './tunejs.js';
import { Pattern } from '@strudel.cycles/core/strudel.mjs';
import { Pattern } from '@strudel.cycles/core';
Pattern.prototype._tune = function (scale, tonic = 220) {
const tune = new Tune();

View File

@ -1,5 +1,4 @@
import { Pattern } from '@strudel.cycles/core/strudel.mjs';
import { mod } from '@strudel.cycles/core/util.mjs';
import { Pattern, mod } from '@strudel.cycles/core';
export function edo(name) {
if (!/^[1-9]+[0-9]*edo$/.test(name)) {

View File

@ -10,7 +10,7 @@ import { useWebMidi } from './useWebMidi';
import './App.css';
// eval stuff start
import { evaluate, extend } from '@strudel.cycles/eval';
import * as strudel from '@strudel.cycles/core/strudel.mjs';
import * as strudel from '@strudel.cycles/core';
import gist from '@strudel.cycles/core/gist.js';
import { mini } from '@strudel.cycles/mini/mini.mjs';
import { Tone } from '@strudel.cycles/tone';