mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-11 05:38:34 +00:00
separate out strudel.mjs, make index.mjs aggregate module
This commit is contained in:
parent
0396c3f475
commit
f75da0e2e3
@ -1,4 +1,4 @@
|
||||
import { Pattern, sequence } from '@strudel.cycles/core/strudel.mjs';
|
||||
import { Pattern, sequence } from './pattern.mjs';
|
||||
|
||||
const controls = {};
|
||||
const generic_params = [
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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
90
packages/core/hap.mjs
Normal 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;
|
||||
@ -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';
|
||||
@ -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"
|
||||
|
||||
@ -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
22
packages/core/state.mjs
Normal 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;
|
||||
@ -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
103
packages/core/timespan.mjs
Normal 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;
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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 ', () => {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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', () => {
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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)) {
|
||||
|
||||
@ -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';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user