mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-27 21:48:27 +00:00
commit
14487ed5cf
1
.gitignore
vendored
1
.gitignore
vendored
@ -27,4 +27,5 @@ node_modules/
|
|||||||
repl-parcel
|
repl-parcel
|
||||||
mytunes.ts
|
mytunes.ts
|
||||||
doc
|
doc
|
||||||
|
out
|
||||||
.parcel-cache
|
.parcel-cache
|
||||||
11
jsdoc.config.json
Normal file
11
jsdoc.config.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"source": {
|
||||||
|
"includePattern": ".+\\.(js(doc|x)?|mjs)$",
|
||||||
|
"excludePattern": "node_modules|shift-parser|shift-reducer|shift-traverser"
|
||||||
|
},
|
||||||
|
"plugins": ["plugins/markdown"],
|
||||||
|
"opts": {
|
||||||
|
"destination": "./out/",
|
||||||
|
"recurse": true
|
||||||
|
}
|
||||||
|
}
|
||||||
1175
package-lock.json
generated
1175
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -9,7 +9,9 @@
|
|||||||
"setup": "npm i && npm run bootstrap && cd repl && npm i",
|
"setup": "npm i && npm run bootstrap && cd repl && npm i",
|
||||||
"repl": "cd repl && npm run start",
|
"repl": "cd repl && npm run start",
|
||||||
"osc": "cd packages/osc && npm run server",
|
"osc": "cd packages/osc && npm run server",
|
||||||
"build": "cd repl && npm run build"
|
"build": "cd repl && npm run build",
|
||||||
|
"jsdoc": "jsdoc packages/ -c jsdoc.config.json",
|
||||||
|
"jsdoc-json": "jsdoc packages/ --template ./node_modules/jsdoc-json --destination doc.json -c jsdoc.config.json"
|
||||||
},
|
},
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
@ -33,6 +35,9 @@
|
|||||||
"homepage": "https://strudel.tidalcycles.org",
|
"homepage": "https://strudel.tidalcycles.org",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"events": "^3.3.0",
|
"events": "^3.3.0",
|
||||||
|
"jsdoc": "^3.6.10",
|
||||||
|
"jsdoc-json": "^2.0.2",
|
||||||
|
"jsdoc-to-markdown": "^7.1.1",
|
||||||
"lerna": "^4.0.0",
|
"lerna": "^4.0.0",
|
||||||
"mocha": "^9.1.4"
|
"mocha": "^9.1.4"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,10 @@ export class Hap {
|
|||||||
then the whole will be returned as None, in which case the given
|
then the whole will be returned as None, in which case the given
|
||||||
value will have been sampled from the point halfway between the
|
value will have been sampled from the point halfway between the
|
||||||
start and end of the 'part' timespan.
|
start and end of the 'part' timespan.
|
||||||
The context is to store a list of source code locations causing the event
|
The context is to store a list of source code locations causing the event.
|
||||||
|
|
||||||
|
The word 'Event' is more or less a reserved word in javascript, hence this
|
||||||
|
class is named called 'Hap'.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
constructor(whole, part, value, context = {}, stateful = false) {
|
constructor(whole, part, value, context = {}, stateful = false) {
|
||||||
@ -37,18 +40,18 @@ export class Hap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
withSpan(func) {
|
withSpan(func) {
|
||||||
// Returns a new event with the function f applies to the event timespan.
|
// Returns a new hap with the function f applies to the hap timespan.
|
||||||
const whole = this.whole ? func(this.whole) : undefined;
|
const whole = this.whole ? func(this.whole) : undefined;
|
||||||
return new Hap(whole, func(this.part), this.value, this.context);
|
return new Hap(whole, func(this.part), this.value, this.context);
|
||||||
}
|
}
|
||||||
|
|
||||||
withValue(func) {
|
withValue(func) {
|
||||||
// Returns a new event with the function f applies to the event value.
|
// Returns a new hap with the function f applies to the hap value.
|
||||||
return new Hap(this.whole, this.part, func(this.value), this.context);
|
return new Hap(this.whole, this.part, func(this.value), this.context);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasOnset() {
|
hasOnset() {
|
||||||
// Test whether the event contains the onset, i.e that
|
// Test whether the hap contains the onset, i.e that
|
||||||
// the beginning of the part is the same as that of the whole timespan."""
|
// 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);
|
return this.whole != undefined && this.whole.begin.equals(this.part.begin);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,20 +13,37 @@ import { unionWithObj } from './value.mjs';
|
|||||||
import { isNote, toMidi, compose, removeUndefineds, flatten, id, listRange, curry, mod } from './util.mjs';
|
import { isNote, toMidi, compose, removeUndefineds, flatten, id, listRange, curry, mod } from './util.mjs';
|
||||||
import drawLine from './drawLine.mjs';
|
import drawLine from './drawLine.mjs';
|
||||||
|
|
||||||
|
/** @class Class representing a pattern. */
|
||||||
export class Pattern {
|
export class Pattern {
|
||||||
// the following functions will get patternFactories as nested functions:
|
/**
|
||||||
|
* Create a pattern.
|
||||||
|
* @param {function} query - The function that maps a State to Haps .
|
||||||
|
*/
|
||||||
constructor(query) {
|
constructor(query) {
|
||||||
this.query = query;
|
this.query = query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* query haps insude the tiven time span
|
||||||
|
*
|
||||||
|
* @param {Fraction | number} begin from time
|
||||||
|
* @param {Fraction | number} end to time
|
||||||
|
* @returns Hap[]
|
||||||
|
* @example
|
||||||
|
* const pattern = sequence('a', ['b', 'c']);
|
||||||
|
* const haps = pattern.queryArc(0, 1);
|
||||||
|
*/
|
||||||
queryArc(begin, end) {
|
queryArc(begin, end) {
|
||||||
return this.query(new State(new TimeSpan(begin, end)));
|
return this.query(new State(new TimeSpan(begin, end)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new pattern, with queries split at cycle boundaries. This makes
|
||||||
|
* some calculations easier to express, as all haps are then constrained to
|
||||||
|
* happen within a cycle.
|
||||||
|
* @returns Pattern
|
||||||
|
*/
|
||||||
_splitQueries() {
|
_splitQueries() {
|
||||||
// Splits queries at cycle boundaries. This makes some calculations
|
|
||||||
// easier to express, as all events are then constrained to happen within
|
|
||||||
// a cycle.
|
|
||||||
const pat = this;
|
const pat = this;
|
||||||
const q = (state) => {
|
const q = (state) => {
|
||||||
return flatten(state.span.spanCycles.map((subspan) => pat.query(state.setSpan(subspan))));
|
return flatten(state.span.spanCycles.map((subspan) => pat.query(state.setSpan(subspan))));
|
||||||
@ -34,48 +51,98 @@ export class Pattern {
|
|||||||
return new Pattern(q);
|
return new Pattern(q);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new pattern, where the given function is applied to the query
|
||||||
|
* timespan before passing it to the original pattern.
|
||||||
|
* @param {Function} func the function to apply
|
||||||
|
* @returns Pattern
|
||||||
|
*/
|
||||||
withQuerySpan(func) {
|
withQuerySpan(func) {
|
||||||
return new Pattern((state) => this.query(state.withSpan(func)));
|
return new Pattern((state) => this.query(state.withSpan(func)));
|
||||||
}
|
}
|
||||||
|
|
||||||
withQueryTime(func) {
|
/**
|
||||||
// Returns a new pattern, with the function applied to both the begin
|
* As with {@link Pattern#withQuerySpan|withQuerySpan}, but the function is applied to both the
|
||||||
// and end of the the query timespan
|
* begin and end time of the query timespan.
|
||||||
|
* @param {Function} func the function to apply
|
||||||
|
* @returns Pattern
|
||||||
|
*/
|
||||||
|
withQueryTime(func) {
|
||||||
return new Pattern((state) => this.query(state.withSpan((span) => span.withTime(func))));
|
return new Pattern((state) => this.query(state.withSpan((span) => span.withTime(func))));
|
||||||
}
|
}
|
||||||
|
|
||||||
withEventSpan(func) {
|
/**
|
||||||
// Returns a new pattern, with the function applied to each event
|
* Similar to {@link Pattern#withQuerySpan|withQuerySpan}, but the function is applied to the timespans
|
||||||
// timespan.
|
* of all haps returned by pattern queries (both `part` timespans, and where
|
||||||
|
* present, `whole` timespans).
|
||||||
|
* @param {Function} func
|
||||||
|
* @returns Pattern
|
||||||
|
*/
|
||||||
|
withHapSpan(func) {
|
||||||
return new Pattern((state) => this.query(state).map((hap) => hap.withSpan(func)));
|
return new Pattern((state) => this.query(state).map((hap) => hap.withSpan(func)));
|
||||||
}
|
}
|
||||||
|
|
||||||
withEventTime(func) {
|
/**
|
||||||
// Returns a new pattern, with the function applied to both the begin
|
* As with {@link Pattern#withHapSpan|withHapSpan}, but the function is applied to both the
|
||||||
// and end of each event timespan.
|
* begin and end time of the hap timespans.
|
||||||
return this.withEventSpan((span) => span.withTime(func));
|
* @param {Function} func the function to apply
|
||||||
|
* @returns Pattern
|
||||||
|
*/
|
||||||
|
withHapTime(func) {
|
||||||
|
return this.withHapSpan((span) => span.withTime(func));
|
||||||
}
|
}
|
||||||
|
|
||||||
_withEvents(func) {
|
/**
|
||||||
|
* Returns a new pattern with the given function applied to the list of haps returned by every query.
|
||||||
|
* @param {Function} func
|
||||||
|
* @returns Pattern
|
||||||
|
*/
|
||||||
|
_withHaps(func) {
|
||||||
return new Pattern((state) => func(this.query(state)));
|
return new Pattern((state) => func(this.query(state)));
|
||||||
}
|
}
|
||||||
|
|
||||||
_withEvent(func) {
|
/**
|
||||||
return this._withEvents((events) => events.map(func));
|
* As with {@link Pattern#_withHaps}, but applies the function to every hap, rather than every list of haps.
|
||||||
|
* @param {Function} func
|
||||||
|
* @returns Pattern
|
||||||
|
*/
|
||||||
|
_withHap(func) {
|
||||||
|
return this._withHaps((haps) => haps.map(func));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new pattern with the context field set to every hap set to the given value.
|
||||||
|
* @param {*} context
|
||||||
|
* @returns Pattern
|
||||||
|
*/
|
||||||
_setContext(context) {
|
_setContext(context) {
|
||||||
return this._withEvent((event) => event.setContext(context));
|
return this._withHap((hap) => hap.setContext(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new pattern with the given function applied to the context field of every hap.
|
||||||
|
* @param {Function} func
|
||||||
|
* @returns Pattern
|
||||||
|
*/
|
||||||
_withContext(func) {
|
_withContext(func) {
|
||||||
return this._withEvent((event) => event.setContext(func(event.context)));
|
return this._withHap((hap) => hap.setContext(func(hap.context)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new pattern with the context field of every hap set to an empty object.
|
||||||
|
* @returns Pattern
|
||||||
|
*/
|
||||||
_stripContext() {
|
_stripContext() {
|
||||||
return this._withEvent((event) => event.setContext({}));
|
return this._withHap((hap) => hap.setContext({}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new pattern with the given location information added to the
|
||||||
|
* context of every hap.
|
||||||
|
* @param {Number} start
|
||||||
|
* @param {Number} end
|
||||||
|
* @returns Pattern
|
||||||
|
*/
|
||||||
withLocation(start, end) {
|
withLocation(start, end) {
|
||||||
const location = {
|
const location = {
|
||||||
start: { line: start[0], column: start[1], offset: start[2] },
|
start: { line: start[0], column: start[1], offset: start[2] },
|
||||||
@ -113,39 +180,72 @@ export class Pattern {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new pattern, with the function applied to the value of
|
||||||
|
* each hap. It has the alias {@link Pattern#fmap|fmap}.
|
||||||
|
* @param {Function} func
|
||||||
|
* @returns Pattern
|
||||||
|
*/
|
||||||
withValue(func) {
|
withValue(func) {
|
||||||
// Returns a new pattern, with the function applied to the value of
|
|
||||||
// each event. It has the alias 'fmap'.
|
|
||||||
return new Pattern((state) => this.query(state).map((hap) => hap.withValue(func)));
|
return new Pattern((state) => this.query(state).map((hap) => hap.withValue(func)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// alias
|
/**
|
||||||
fmap(func) {
|
* see {@link Pattern#withValue|withValue}
|
||||||
|
*/
|
||||||
|
fmap(func) {
|
||||||
return this.withValue(func);
|
return this.withValue(func);
|
||||||
}
|
}
|
||||||
|
|
||||||
_filterEvents(event_test) {
|
/**
|
||||||
return new Pattern((state) => this.query(state).filter(event_test));
|
* Returns a new Pattern, which only returns haps that meet the given test.
|
||||||
|
* @param {Function} hap_test - a function which returns false for haps to be removed from the pattern
|
||||||
|
* @returns Pattern
|
||||||
|
*/
|
||||||
|
_filterHaps(hap_test) {
|
||||||
|
return new Pattern((state) => this.query(state).filter(hap_test));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* As with {@link Pattern#_filterHaps}, but the function is applied to values
|
||||||
|
* inside haps.
|
||||||
|
* @param {Function} value_test
|
||||||
|
* @returns Pattern
|
||||||
|
*/
|
||||||
_filterValues(value_test) {
|
_filterValues(value_test) {
|
||||||
return new Pattern((state) => this.query(state).filter((hap) => value_test(hap.value)));
|
return new Pattern((state) => this.query(state).filter((hap) => value_test(hap.value)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new pattern, with haps containing undefined values removed from
|
||||||
|
* query results.
|
||||||
|
* @returns Pattern
|
||||||
|
*/
|
||||||
_removeUndefineds() {
|
_removeUndefineds() {
|
||||||
return this._filterValues((val) => val != undefined);
|
return this._filterValues((val) => val != undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
onsetsOnly() {
|
/**
|
||||||
// Returns a new pattern that will only return events where the start
|
* Returns a new pattern, with all haps without onsets filtered out. A hap
|
||||||
|
* with an onset is one with a `whole` timespan that begins at the same time
|
||||||
|
* as its `part` timespan.
|
||||||
|
* @returns Pattern
|
||||||
|
*/
|
||||||
|
onsetsOnly() {
|
||||||
|
// Returns a new pattern that will only return haps where the start
|
||||||
// of the 'whole' timespan matches the start of the 'part'
|
// of the 'whole' timespan matches the start of the 'part'
|
||||||
// timespan, i.e. the events that include their 'onset'.
|
// timespan, i.e. the haps that include their 'onset'.
|
||||||
return this._filterEvents((hap) => hap.hasOnset());
|
return this._filterHaps((hap) => hap.hasOnset());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new pattern, with 'continuous' haps (those without 'whole'
|
||||||
|
* timespans) removed from query results.
|
||||||
|
* @returns Pattern
|
||||||
|
*/
|
||||||
discreteOnly() {
|
discreteOnly() {
|
||||||
// removes continuous events that don't have a 'whole' timespan
|
// removes continuous haps that don't have a 'whole' timespan
|
||||||
return this._filterEvents((hap) => hap.whole);
|
return this._filterHaps((hap) => hap.whole);
|
||||||
}
|
}
|
||||||
|
|
||||||
_appWhole(whole_func, pat_val) {
|
_appWhole(whole_func, pat_val) {
|
||||||
@ -154,27 +254,38 @@ export class Pattern {
|
|||||||
// pattern of functions.
|
// pattern of functions.
|
||||||
const pat_func = this;
|
const pat_func = this;
|
||||||
const query = function (state) {
|
const query = function (state) {
|
||||||
const event_funcs = pat_func.query(state);
|
const hap_funcs = pat_func.query(state);
|
||||||
const event_vals = pat_val.query(state);
|
const hap_vals = pat_val.query(state);
|
||||||
const apply = function (event_func, event_val) {
|
const apply = function (hap_func, hap_val) {
|
||||||
const s = event_func.part.intersection(event_val.part);
|
const s = hap_func.part.intersection(hap_val.part);
|
||||||
if (s == undefined) {
|
if (s == undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return new Hap(
|
return new Hap(
|
||||||
whole_func(event_func.whole, event_val.whole),
|
whole_func(hap_func.whole, hap_val.whole),
|
||||||
s,
|
s,
|
||||||
event_func.value(event_val.value),
|
hap_func.value(hap_val.value),
|
||||||
event_val.combineContext(event_func),
|
hap_val.combineContext(hap_func),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
return flatten(
|
return flatten(
|
||||||
event_funcs.map((event_func) => removeUndefineds(event_vals.map((event_val) => apply(event_func, event_val)))),
|
hap_funcs.map((hap_func) => removeUndefineds(hap_vals.map((hap_val) => apply(hap_func, hap_val)))),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
return new Pattern(query);
|
return new Pattern(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When this method is called on a pattern of functions, it matches its haps
|
||||||
|
* with those in the given pattern of values. A new pattern is returned, with
|
||||||
|
* each matching value applied to the corresponding function.
|
||||||
|
*
|
||||||
|
* In this `appBoth` variant, where timespans of the function and value haps
|
||||||
|
* are not the same but do intersect, the resulting hap has a timespan of the
|
||||||
|
* intersection. This applies to both the part and the whole timespan.
|
||||||
|
* @param {Pattern} pat_val
|
||||||
|
* @returns Pattern
|
||||||
|
*/
|
||||||
appBoth(pat_val) {
|
appBoth(pat_val) {
|
||||||
// Tidal's <*>
|
// Tidal's <*>
|
||||||
const whole_func = function (span_a, span_b) {
|
const whole_func = function (span_a, span_b) {
|
||||||
@ -186,14 +297,23 @@ export class Pattern {
|
|||||||
return this._appWhole(whole_func, pat_val);
|
return this._appWhole(whole_func, pat_val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* As with {@link Pattern#appBoth|appBoth}, but the `whole` timespan is not the intersection,
|
||||||
|
* but the timespan from the function of patterns that this method is called
|
||||||
|
* on. In practice, this means that the pattern structure, including onsets,
|
||||||
|
* are preserved from the pattern of functions (often referred to as the left
|
||||||
|
* hand or inner pattern).
|
||||||
|
* @param {Pattern} pat_val
|
||||||
|
* @returns Pattern
|
||||||
|
*/
|
||||||
appLeft(pat_val) {
|
appLeft(pat_val) {
|
||||||
const pat_func = this;
|
const pat_func = this;
|
||||||
|
|
||||||
const query = function (state) {
|
const query = function (state) {
|
||||||
const haps = [];
|
const haps = [];
|
||||||
for (const hap_func of pat_func.query(state)) {
|
for (const hap_func of pat_func.query(state)) {
|
||||||
const event_vals = pat_val.query(state.setSpan(hap_func.wholeOrPart()));
|
const hap_vals = pat_val.query(state.setSpan(hap_func.wholeOrPart()));
|
||||||
for (const hap_val of event_vals) {
|
for (const hap_val of hap_vals) {
|
||||||
const new_whole = hap_func.whole;
|
const new_whole = hap_func.whole;
|
||||||
const new_part = hap_func.part.intersection(hap_val.part);
|
const new_part = hap_func.part.intersection(hap_val.part);
|
||||||
if (new_part) {
|
if (new_part) {
|
||||||
@ -209,6 +329,13 @@ export class Pattern {
|
|||||||
return new Pattern(query);
|
return new Pattern(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* As with {@link Pattern#appLeft|appLeft}, but `whole` timespans are instead taken from the
|
||||||
|
* pattern of values, i.e. structure is preserved from the right hand/outer
|
||||||
|
* pattern.
|
||||||
|
* @param {Pattern} pat_val
|
||||||
|
* @returns Pattern
|
||||||
|
*/
|
||||||
appRight(pat_val) {
|
appRight(pat_val) {
|
||||||
const pat_func = this;
|
const pat_func = this;
|
||||||
|
|
||||||
@ -232,6 +359,13 @@ export class Pattern {
|
|||||||
return new Pattern(query);
|
return new Pattern(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queries the pattern for the first cycle, returning Haps. Mainly of use when
|
||||||
|
* debugging a pattern.
|
||||||
|
* @param {Boolean} with_context - set to true, otherwise the context field
|
||||||
|
* will be stripped from the resulting haps.
|
||||||
|
* @returns [Hap]
|
||||||
|
*/
|
||||||
firstCycle(with_context = false) {
|
firstCycle(with_context = false) {
|
||||||
var self = this;
|
var self = this;
|
||||||
if (!with_context) {
|
if (!with_context) {
|
||||||
@ -240,18 +374,30 @@ export class Pattern {
|
|||||||
return self.query(new State(new TimeSpan(Fraction(0), Fraction(1))));
|
return self.query(new State(new TimeSpan(Fraction(0), Fraction(1))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accessor for a list of values returned by querying the first cycle.
|
||||||
|
*/
|
||||||
get _firstCycleValues() {
|
get _firstCycleValues() {
|
||||||
return this.firstCycle().map((hap) => hap.value);
|
return this.firstCycle().map((hap) => hap.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* More human-readable version of the {@link Pattern#_firstCycleValues} accessor.
|
||||||
|
*/
|
||||||
get _showFirstCycle() {
|
get _showFirstCycle() {
|
||||||
return this.firstCycle().map(
|
return this.firstCycle().map(
|
||||||
(hap) => `${hap.value}: ${hap.whole.begin.toFraction()} - ${hap.whole.end.toFraction()}`,
|
(hap) => `${hap.value}: ${hap.whole.begin.toFraction()} - ${hap.whole.end.toFraction()}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_sortEventsByPart() {
|
/**
|
||||||
return this._withEvents((events) =>
|
* Returns a new pattern, which returns haps sorted in temporal order. Mainly
|
||||||
events.sort((a, b) =>
|
* of use when comparing two patterns for equality, in tests.
|
||||||
|
* @returns Pattern
|
||||||
|
*/
|
||||||
|
_sortHapsByPart() {
|
||||||
|
return this._withHaps((haps) =>
|
||||||
|
haps.sort((a, b) =>
|
||||||
a.part.begin
|
a.part.begin
|
||||||
.sub(b.part.begin)
|
.sub(b.part.begin)
|
||||||
.or(a.part.end.sub(b.part.end))
|
.or(a.part.end.sub(b.part.end))
|
||||||
@ -288,21 +434,21 @@ export class Pattern {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_asNumber(dropfails = false, softfail = false) {
|
_asNumber(dropfails = false, softfail = false) {
|
||||||
return this._withEvent((event) => {
|
return this._withHap((hap) => {
|
||||||
const asNumber = Number(event.value);
|
const asNumber = Number(hap.value);
|
||||||
if (!isNaN(asNumber)) {
|
if (!isNaN(asNumber)) {
|
||||||
return event.withValue(() => asNumber);
|
return hap.withValue(() => asNumber);
|
||||||
}
|
}
|
||||||
const specialValue = {
|
const specialValue = {
|
||||||
e: Math.E,
|
e: Math.E,
|
||||||
pi: Math.PI,
|
pi: Math.PI,
|
||||||
}[event.value];
|
}[hap.value];
|
||||||
if (typeof specialValue !== 'undefined') {
|
if (typeof specialValue !== 'undefined') {
|
||||||
return event.withValue(() => specialValue);
|
return hap.withValue(() => specialValue);
|
||||||
}
|
}
|
||||||
if (isNote(event.value)) {
|
if (isNote(hap.value)) {
|
||||||
// set context type to midi to let the player know its meant as midi number and not as frequency
|
// set context type to midi to let the player know its meant as midi number and not as frequency
|
||||||
return new Hap(event.whole, event.part, toMidi(event.value), { ...event.context, type: 'midi' });
|
return new Hap(hap.whole, hap.part, toMidi(hap.value), { ...hap.context, type: 'midi' });
|
||||||
}
|
}
|
||||||
if (dropfail) {
|
if (dropfail) {
|
||||||
// return 'nothing'
|
// return 'nothing'
|
||||||
@ -310,10 +456,10 @@ export class Pattern {
|
|||||||
}
|
}
|
||||||
if (softfail) {
|
if (softfail) {
|
||||||
// return original hap
|
// return original hap
|
||||||
return event;
|
return hap;
|
||||||
}
|
}
|
||||||
throw new Error('cannot parse as number: "' + event.value + '"');
|
throw new Error('cannot parse as number: "' + hap.value + '"');
|
||||||
return event;
|
return hap;
|
||||||
});
|
});
|
||||||
if (dropfail) {
|
if (dropfail) {
|
||||||
return result._removeUndefineds();
|
return result._removeUndefineds();
|
||||||
@ -389,7 +535,7 @@ export class Pattern {
|
|||||||
|
|
||||||
join() {
|
join() {
|
||||||
// Flattens a pattern of patterns into a pattern, where wholes are
|
// Flattens a pattern of patterns into a pattern, where wholes are
|
||||||
// the intersection of matched inner and outer events.
|
// the intersection of matched inner and outer haps.
|
||||||
return this.bind(id);
|
return this.bind(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -399,7 +545,7 @@ export class Pattern {
|
|||||||
|
|
||||||
outerJoin() {
|
outerJoin() {
|
||||||
// Flattens a pattern of patterns into a pattern, where wholes are
|
// Flattens a pattern of patterns into a pattern, where wholes are
|
||||||
// taken from inner events.
|
// taken from inner haps.
|
||||||
return this.outerBind(id);
|
return this.outerBind(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -409,35 +555,35 @@ export class Pattern {
|
|||||||
|
|
||||||
innerJoin() {
|
innerJoin() {
|
||||||
// Flattens a pattern of patterns into a pattern, where wholes are
|
// Flattens a pattern of patterns into a pattern, where wholes are
|
||||||
// taken from inner events.
|
// taken from inner haps.
|
||||||
return this.innerBind(id);
|
return this.innerBind(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flatterns patterns of patterns, by retriggering/resetting inner patterns at onsets of outer pattern events
|
// Flatterns patterns of patterns, by retriggering/resetting inner patterns at onsets of outer pattern haps
|
||||||
_trigJoin(cycleZero = false) {
|
_trigJoin(cycleZero = false) {
|
||||||
const pat_of_pats = this;
|
const pat_of_pats = this;
|
||||||
return new Pattern((state) => {
|
return new Pattern((state) => {
|
||||||
return (
|
return (
|
||||||
pat_of_pats
|
pat_of_pats
|
||||||
// drop continuous events from the outer pattern.
|
// drop continuous haps from the outer pattern.
|
||||||
.discreteOnly()
|
.discreteOnly()
|
||||||
.query(state)
|
.query(state)
|
||||||
.map((outer_hap) => {
|
.map((outer_hap) => {
|
||||||
return (
|
return (
|
||||||
outer_hap.value
|
outer_hap.value
|
||||||
// trig = align the inner pattern cycle start to outer pattern events
|
// trig = align the inner pattern cycle start to outer pattern haps
|
||||||
// Trigzero = align the inner pattern cycle zero to outer pattern events
|
// Trigzero = align the inner pattern cycle zero to outer pattern haps
|
||||||
.late(cycleZero ? outer_hap.whole.begin : outer_hap.whole.begin.cyclePos())
|
.late(cycleZero ? outer_hap.whole.begin : outer_hap.whole.begin.cyclePos())
|
||||||
.query(state)
|
.query(state)
|
||||||
.map((inner_hap) =>
|
.map((inner_hap) =>
|
||||||
new Hap(
|
new Hap(
|
||||||
// Supports continuous events in the inner pattern
|
// Supports continuous haps in the inner pattern
|
||||||
inner_hap.whole ? inner_hap.whole.intersection(outer_hap.whole) : undefined,
|
inner_hap.whole ? inner_hap.whole.intersection(outer_hap.whole) : undefined,
|
||||||
inner_hap.part.intersection(outer_hap.part),
|
inner_hap.part.intersection(outer_hap.part),
|
||||||
inner_hap.value,
|
inner_hap.value,
|
||||||
).setContext(outer_hap.combineContext(inner_hap)),
|
).setContext(outer_hap.combineContext(inner_hap)),
|
||||||
)
|
)
|
||||||
// Drop events that didn't intersect
|
// Drop haps that didn't intersect
|
||||||
.filter((hap) => hap.part)
|
.filter((hap) => hap.part)
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
@ -527,7 +673,7 @@ export class Pattern {
|
|||||||
const end = cycle.add(span.end.sub(cycle).div(factor).min(1));
|
const end = cycle.add(span.end.sub(cycle).div(factor).min(1));
|
||||||
return new TimeSpan(begin, end);
|
return new TimeSpan(begin, end);
|
||||||
};
|
};
|
||||||
return this.withQuerySpan(qf).withEventSpan(ef)._splitQueries();
|
return this.withQuerySpan(qf).withHapSpan(ef)._splitQueries();
|
||||||
}
|
}
|
||||||
|
|
||||||
_compress(b, e) {
|
_compress(b, e) {
|
||||||
@ -543,7 +689,7 @@ export class Pattern {
|
|||||||
|
|
||||||
_fast(factor) {
|
_fast(factor) {
|
||||||
const fastQuery = this.withQueryTime((t) => t.mul(factor));
|
const fastQuery = this.withQueryTime((t) => t.mul(factor));
|
||||||
return fastQuery.withEventTime((t) => t.div(factor));
|
return fastQuery.withHapTime((t) => t.div(factor));
|
||||||
}
|
}
|
||||||
|
|
||||||
_slow(factor) {
|
_slow(factor) {
|
||||||
@ -578,7 +724,7 @@ export class Pattern {
|
|||||||
_early(offset) {
|
_early(offset) {
|
||||||
// Equivalent of Tidal's <~ operator
|
// Equivalent of Tidal's <~ operator
|
||||||
offset = Fraction(offset);
|
offset = Fraction(offset);
|
||||||
return this.withQueryTime((t) => t.add(offset)).withEventTime((t) => t.sub(offset));
|
return this.withQueryTime((t) => t.add(offset)).withHapTime((t) => t.sub(offset));
|
||||||
}
|
}
|
||||||
|
|
||||||
_late(offset) {
|
_late(offset) {
|
||||||
@ -592,7 +738,7 @@ export class Pattern {
|
|||||||
s = Fraction(s);
|
s = Fraction(s);
|
||||||
const d = e.sub(s);
|
const d = e.sub(s);
|
||||||
return this.withQuerySpan((span) => span.withCycle((t) => t.mul(d).add(s)))
|
return this.withQuerySpan((span) => span.withCycle((t) => t.mul(d).add(s)))
|
||||||
.withEventSpan((span) => span.withCycle((t) => t.sub(s).div(d)))
|
.withHapSpan((span) => span.withCycle((t) => t.sub(s).div(d)))
|
||||||
._splitQueries();
|
._splitQueries();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -632,7 +778,7 @@ export class Pattern {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log() {
|
log() {
|
||||||
return this._withEvent((e) => {
|
return this._withHap((e) => {
|
||||||
return e.setContext({ ...e.context, logs: (e.context?.logs || []).concat([e.show()]) });
|
return e.setContext({ ...e.context, logs: (e.context?.logs || []).concat([e.show()]) });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -800,14 +946,14 @@ export class Pattern {
|
|||||||
return silence;
|
return silence;
|
||||||
}
|
}
|
||||||
|
|
||||||
// sets absolute duration of events
|
// sets absolute duration of haps
|
||||||
_duration(value) {
|
_duration(value) {
|
||||||
return this.withEventSpan((span) => new TimeSpan(span.begin, span.begin.add(value)));
|
return this.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(value)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// sets event relative duration of events
|
// sets hap relative duration of haps
|
||||||
_legato(value) {
|
_legato(value) {
|
||||||
return this.withEventSpan((span) => new TimeSpan(span.begin, span.begin.add(span.end.sub(span.begin).mul(value))));
|
return this.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(span.end.sub(span.begin).mul(value))));
|
||||||
}
|
}
|
||||||
|
|
||||||
_velocity(velocity) {
|
_velocity(velocity) {
|
||||||
@ -957,8 +1103,14 @@ Pattern.prototype.factories = {
|
|||||||
// Nothing
|
// Nothing
|
||||||
export const silence = new Pattern((_) => []);
|
export const silence = new Pattern((_) => []);
|
||||||
|
|
||||||
|
/** A discrete value that repeats once per cycle:
|
||||||
|
*
|
||||||
|
* @param {any} value - The value to repeat
|
||||||
|
* @returns {Pattern}
|
||||||
|
* @example
|
||||||
|
* pure('e4')
|
||||||
|
*/
|
||||||
export function pure(value) {
|
export function pure(value) {
|
||||||
// A discrete value that repeats once per cycle
|
|
||||||
function query(state) {
|
function query(state) {
|
||||||
return state.span.spanCycles.map((subspan) => new Hap(Fraction(subspan.begin).wholeCycle(), subspan, value));
|
return state.span.spanCycles.map((subspan) => new Hap(Fraction(subspan.begin).wholeCycle(), subspan, value));
|
||||||
}
|
}
|
||||||
@ -978,8 +1130,13 @@ export function reify(thing) {
|
|||||||
return pure(thing);
|
return pure(thing);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Basic functions for combining patterns
|
/** The given items are played at the same time at the same length:
|
||||||
|
*
|
||||||
|
* @param {...any} items - The items to stack
|
||||||
|
* @return {Pattern}
|
||||||
|
* @example
|
||||||
|
* stack(g3, b3, [e4, d4])
|
||||||
|
*/
|
||||||
export function stack(...pats) {
|
export function stack(...pats) {
|
||||||
// Array test here is to avoid infinite recursions..
|
// Array test here is to avoid infinite recursions..
|
||||||
pats = pats.map((pat) => (Array.isArray(pat) ? sequence(...pat) : reify(pat)));
|
pats = pats.map((pat) => (Array.isArray(pat) ? sequence(...pat) : reify(pat)));
|
||||||
@ -987,10 +1144,17 @@ export function stack(...pats) {
|
|||||||
return new Pattern(query);
|
return new Pattern(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Concatenation: combines a list of patterns, switching between them successively, one per cycle:
|
||||||
|
*
|
||||||
|
* synonyms: {@link cat}
|
||||||
|
*
|
||||||
|
* @param {...any} items - The items to concatenate
|
||||||
|
* @return {Pattern}
|
||||||
|
* @example
|
||||||
|
* slowcat(e5, b4, [d5, c5])
|
||||||
|
*
|
||||||
|
*/
|
||||||
export function slowcat(...pats) {
|
export function slowcat(...pats) {
|
||||||
// Concatenation: combines a list of patterns, switching between them
|
|
||||||
// successively, one per cycle.
|
|
||||||
|
|
||||||
// Array test here is to avoid infinite recursions..
|
// Array test here is to avoid infinite recursions..
|
||||||
pats = pats.map((pat) => (Array.isArray(pat) ? sequence(...pat) : reify(pat)));
|
pats = pats.map((pat) => (Array.isArray(pat) ? sequence(...pat) : reify(pat)));
|
||||||
|
|
||||||
@ -1006,14 +1170,16 @@ export function slowcat(...pats) {
|
|||||||
// For example if three patterns are slowcat-ed, the fourth cycle of the result should
|
// For example if three patterns are slowcat-ed, the fourth cycle of the result should
|
||||||
// be the second (rather than fourth) cycle from the first pattern.
|
// be the second (rather than fourth) cycle from the first pattern.
|
||||||
const offset = span.begin.floor().sub(span.begin.div(pats.length).floor());
|
const offset = span.begin.floor().sub(span.begin.div(pats.length).floor());
|
||||||
return pat.withEventTime((t) => t.add(offset)).query(state.setSpan(span.withTime((t) => t.sub(offset))));
|
return pat.withHapTime((t) => t.add(offset)).query(state.setSpan(span.withTime((t) => t.sub(offset))));
|
||||||
};
|
};
|
||||||
return new Pattern(query)._splitQueries();
|
return new Pattern(query)._splitQueries();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Concatenation: combines a list of patterns, switching between them successively, one per cycle. Unlike slowcat, this version will skip cycles.
|
||||||
|
* @param {...any} items - The items to concatenate
|
||||||
|
* @return {Pattern}
|
||||||
|
*/
|
||||||
export 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);
|
pats = pats.map(reify);
|
||||||
const query = function (state) {
|
const query = function (state) {
|
||||||
const pat_n = Math.floor(state.span.begin) % pats.length;
|
const pat_n = Math.floor(state.span.begin) % pats.length;
|
||||||
@ -1023,18 +1189,33 @@ export function slowcatPrime(...pats) {
|
|||||||
return new Pattern(query)._splitQueries();
|
return new Pattern(query)._splitQueries();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Concatenation: as with {@link slowcat}, but squashes a cycle from each pattern into one cycle
|
||||||
|
*
|
||||||
|
* Synonyms: {@link seq}, {@link sequence}
|
||||||
|
*
|
||||||
|
* @param {...any} items - The items to concatenate
|
||||||
|
* @return {Pattern}
|
||||||
|
* @example
|
||||||
|
* fastcat(e5, b4, [d5, c5])
|
||||||
|
* sequence(e5, b4, [d5, c5])
|
||||||
|
* seq(e5, b4, [d5, c5])
|
||||||
|
*/
|
||||||
export 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);
|
return slowcat(...pats)._fast(pats.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** See {@link slowcat} */
|
||||||
export function cat(...pats) {
|
export function cat(...pats) {
|
||||||
return slowcat(...pats);
|
return slowcat(...pats);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Like {@link fastcat}, but where each step has a temporal weight:
|
||||||
|
* @param {...Array} items - The items to concatenate
|
||||||
|
* @return {Pattern}
|
||||||
|
* @example
|
||||||
|
* timeCat([3,e3],[1, g3])
|
||||||
|
*/
|
||||||
export 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));
|
const total = timepats.map((a) => a[0]).reduce((a, b) => a.add(b), Fraction(0));
|
||||||
let begin = Fraction(0);
|
let begin = Fraction(0);
|
||||||
const pats = [];
|
const pats = [];
|
||||||
@ -1046,11 +1227,12 @@ export function timeCat(...timepats) {
|
|||||||
return stack(...pats);
|
return stack(...pats);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** See {@link fastcat} */
|
||||||
export function sequence(...pats) {
|
export function sequence(...pats) {
|
||||||
return fastcat(...pats);
|
return fastcat(...pats);
|
||||||
}
|
}
|
||||||
|
|
||||||
// shorthand for sequence
|
/** See {@link fastcat} */
|
||||||
export function seq(...pats) {
|
export function seq(...pats) {
|
||||||
return fastcat(...pats);
|
return fastcat(...pats);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,11 +26,11 @@ function speak(words, lang, voice) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Pattern.prototype._speak = function (lang, voice) {
|
Pattern.prototype._speak = function (lang, voice) {
|
||||||
return this._withEvent((event) => {
|
return this._withHap((hap) => {
|
||||||
const onTrigger = (time, event) => {
|
const onTrigger = (time, hap) => {
|
||||||
speak(event.value, lang, voice);
|
speak(hap.value, lang, voice);
|
||||||
};
|
};
|
||||||
return event.setContext({ ...event.context, onTrigger });
|
return hap.setContext({ ...hap.context, onTrigger });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -57,7 +57,7 @@ const third = Fraction(1, 3);
|
|||||||
const twothirds = Fraction(2, 3);
|
const twothirds = Fraction(2, 3);
|
||||||
|
|
||||||
const sameFirst = (a, b) => {
|
const sameFirst = (a, b) => {
|
||||||
return assert.deepStrictEqual(a._sortEventsByPart().firstCycle(), b._sortEventsByPart().firstCycle());
|
return assert.deepStrictEqual(a._sortHapsByPart().firstCycle(), b._sortHapsByPart().firstCycle());
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('TimeSpan', function () {
|
describe('TimeSpan', function () {
|
||||||
@ -320,7 +320,7 @@ describe('Pattern', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('setSqueeze()', () => {
|
describe('setSqueeze()', () => {
|
||||||
it('Can squeeze one pattern inside the events of another', () => {
|
it('Can squeeze one pattern inside the haps of another', () => {
|
||||||
sameFirst(
|
sameFirst(
|
||||||
sequence(1, [2, 3]).setSqueeze(sequence('a', 'b', 'c')),
|
sequence(1, [2, 3]).setSqueeze(sequence('a', 'b', 'c')),
|
||||||
sequence(
|
sequence(
|
||||||
@ -394,7 +394,7 @@ describe('Pattern', function () {
|
|||||||
});
|
});
|
||||||
it('Makes things faster, with a pattern of factors', function () {
|
it('Makes things faster, with a pattern of factors', function () {
|
||||||
assert.equal(pure('a').fast(sequence(1, 4)).firstCycle().length, 3);
|
assert.equal(pure('a').fast(sequence(1, 4)).firstCycle().length, 3);
|
||||||
// .fast(sequence(1,silence) is a quick hack to cut an event in two..
|
// .fast(sequence(1,silence) is a quick hack to cut a hap in two..
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
pure('a').fast(sequence(1, 4)).firstCycle(),
|
pure('a').fast(sequence(1, 4)).firstCycle(),
|
||||||
stack(pure('a').fast(sequence(1, silence)), sequence(silence, ['a', 'a'])).firstCycle(),
|
stack(pure('a').fast(sequence(1, silence)), sequence(silence, ['a', 'a'])).firstCycle(),
|
||||||
@ -468,7 +468,7 @@ describe('Pattern', function () {
|
|||||||
});
|
});
|
||||||
it('Can alternate', function () {
|
it('Can alternate', function () {
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
pure(10).when(slowcat(true, false), add(3)).fast(4)._sortEventsByPart().firstCycle(),
|
pure(10).when(slowcat(true, false), add(3)).fast(4)._sortHapsByPart().firstCycle(),
|
||||||
fastcat(13, 10, 13, 10).firstCycle(),
|
fastcat(13, 10, 13, 10).firstCycle(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -679,7 +679,7 @@ describe('Pattern', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('_setContext()', () => {
|
describe('_setContext()', () => {
|
||||||
it('Can set the event context', () => {
|
it('Can set the hap context', () => {
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
pure('a')
|
pure('a')
|
||||||
._setContext([
|
._setContext([
|
||||||
@ -701,7 +701,7 @@ describe('Pattern', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('_withContext()', () => {
|
describe('_withContext()', () => {
|
||||||
it('Can update the event context', () => {
|
it('Can update the hap context', () => {
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
pure('a')
|
pure('a')
|
||||||
._setContext([
|
._setContext([
|
||||||
@ -756,13 +756,13 @@ describe('Pattern', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('early', () => {
|
describe('early', () => {
|
||||||
it('Can shift an event earlier', () => {
|
it('Can shift a hap earlier', () => {
|
||||||
assert.deepStrictEqual(pure(30)._late(0.25).query(st(1, 2)), [
|
assert.deepStrictEqual(pure(30)._late(0.25).query(st(1, 2)), [
|
||||||
hap(ts(1 / 4, 5 / 4), ts(1, 5 / 4), 30),
|
hap(ts(1 / 4, 5 / 4), ts(1, 5 / 4), 30),
|
||||||
hap(ts(5 / 4, 9 / 4), ts(5 / 4, 2), 30),
|
hap(ts(5 / 4, 9 / 4), ts(5 / 4, 2), 30),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
it('Can shift an event earlier, into negative time', () => {
|
it('Can shift a hap earlier, into negative time', () => {
|
||||||
assert.deepStrictEqual(pure(30)._late(0.25).query(st(0, 1)), [
|
assert.deepStrictEqual(pure(30)._late(0.25).query(st(0, 1)), [
|
||||||
hap(ts(-3 / 4, 1 / 4), ts(0, 1 / 4), 30),
|
hap(ts(-3 / 4, 1 / 4), ts(0, 1 / 4), 30),
|
||||||
hap(ts(1 / 4, 5 / 4), ts(1 / 4, 1), 30),
|
hap(ts(1 / 4, 5 / 4), ts(1 / 4, 1), 30),
|
||||||
@ -780,9 +780,9 @@ describe('Pattern', function () {
|
|||||||
describe('jux', () => {
|
describe('jux', () => {
|
||||||
it('Can juxtapose', () => {
|
it('Can juxtapose', () => {
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
pure({ a: 1 }).jux(fast(2))._sortEventsByPart().firstCycle(),
|
pure({ a: 1 }).jux(fast(2))._sortHapsByPart().firstCycle(),
|
||||||
stack(pure({ a: 1, pan: 0 }), pure({ a: 1, pan: 1 }).fast(2))
|
stack(pure({ a: 1, pan: 0 }), pure({ a: 1, pan: 1 }).fast(2))
|
||||||
._sortEventsByPart()
|
._sortHapsByPart()
|
||||||
.firstCycle(),
|
.firstCycle(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -790,9 +790,9 @@ describe('Pattern', function () {
|
|||||||
describe('juxBy', () => {
|
describe('juxBy', () => {
|
||||||
it('Can juxtapose by half', () => {
|
it('Can juxtapose by half', () => {
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
pure({ a: 1 }).juxBy(0.5, fast(2))._sortEventsByPart().firstCycle(),
|
pure({ a: 1 }).juxBy(0.5, fast(2))._sortHapsByPart().firstCycle(),
|
||||||
stack(pure({ a: 1, pan: 0.25 }), pure({ a: 1, pan: 0.75 }).fast(2))
|
stack(pure({ a: 1, pan: 0.25 }), pure({ a: 1, pan: 0.75 }).fast(2))
|
||||||
._sortEventsByPart()
|
._sortHapsByPart()
|
||||||
.firstCycle(),
|
.firstCycle(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -821,7 +821,7 @@ describe('Pattern', function () {
|
|||||||
sequence(pure('a').fast(3), [pure('b').fast(3), pure('c').fast(3)]).firstCycle(),
|
sequence(pure('a').fast(3), [pure('b').fast(3), pure('c').fast(3)]).firstCycle(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('Doesnt drop events in the 9th cycle', () => {
|
it('Doesnt drop haps in the 9th cycle', () => {
|
||||||
// fixed with https://github.com/tidalcycles/strudel/commit/72eeaf446e3d5e186d63cc0d2276f0723cde017a
|
// fixed with https://github.com/tidalcycles/strudel/commit/72eeaf446e3d5e186d63cc0d2276f0723cde017a
|
||||||
assert.equal(sequence(1, 2, 3).ply(2).early(8).firstCycle().length, 6);
|
assert.equal(sequence(1, 2, 3).ply(2).early(8).firstCycle().length, 6);
|
||||||
});
|
});
|
||||||
@ -848,7 +848,7 @@ describe('Pattern', function () {
|
|||||||
});
|
});
|
||||||
it('Can chop(2,3)', () => {
|
it('Can chop(2,3)', () => {
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
pure({ sound: 'a' }).fast(2).chop(2, 3)._sortEventsByPart().firstCycle(),
|
pure({ sound: 'a' }).fast(2).chop(2, 3)._sortHapsByPart().firstCycle(),
|
||||||
sequence(
|
sequence(
|
||||||
[
|
[
|
||||||
{ sound: 'a', begin: 0, end: 0.5 },
|
{ sound: 'a', begin: 0, end: 0.5 },
|
||||||
@ -860,7 +860,7 @@ describe('Pattern', function () {
|
|||||||
{ sound: 'a', begin: 2 / 3, end: 1 },
|
{ sound: 'a', begin: 2 / 3, end: 1 },
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
._sortEventsByPart()
|
._sortHapsByPart()
|
||||||
.firstCycle(),
|
.firstCycle(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -35,27 +35,27 @@ export const fromMidi = (n) => {
|
|||||||
// const mod = (n: number, m: number): number => (n < 0 ? mod(n + m, m) : n % m);
|
// const mod = (n: number, m: number): number => (n < 0 ? mod(n + m, m) : n % m);
|
||||||
export const mod = (n, m) => ((n % m) + m) % m;
|
export const mod = (n, m) => ((n % m) + m) % m;
|
||||||
|
|
||||||
export const getPlayableNoteValue = (event) => {
|
export const getPlayableNoteValue = (hap) => {
|
||||||
let { value: note, context } = event;
|
let { value: note, context } = hap;
|
||||||
// if value is number => interpret as midi number as long as its not marked as frequency
|
// if value is number => interpret as midi number as long as its not marked as frequency
|
||||||
if (typeof note === 'number' && context.type !== 'frequency') {
|
if (typeof note === 'number' && context.type !== 'frequency') {
|
||||||
note = fromMidi(event.value);
|
note = fromMidi(hap.value);
|
||||||
} else if (typeof note === 'string' && !isNote(note)) {
|
} else if (typeof note === 'string' && !isNote(note)) {
|
||||||
throw new Error('not a note: ' + note);
|
throw new Error('not a note: ' + note);
|
||||||
}
|
}
|
||||||
return note;
|
return note;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getFrequency = (event) => {
|
export const getFrequency = (hap) => {
|
||||||
let { value, context } = event;
|
let { value, context } = hap;
|
||||||
// if value is number => interpret as midi number as long as its not marked as frequency
|
// if value is number => interpret as midi number as long as its not marked as frequency
|
||||||
if (typeof value === 'object' && value.freq) {
|
if (typeof value === 'object' && value.freq) {
|
||||||
return value.freq;
|
return value.freq;
|
||||||
}
|
}
|
||||||
if (typeof value === 'number' && context.type !== 'frequency') {
|
if (typeof value === 'number' && context.type !== 'frequency') {
|
||||||
value = fromMidi(event.value);
|
value = fromMidi(hap.value);
|
||||||
} else if (typeof value === 'string' && isNote(value)) {
|
} else if (typeof value === 'string' && isNote(value)) {
|
||||||
value = fromMidi(toMidi(event.value));
|
value = fromMidi(toMidi(hap.value));
|
||||||
} else if (typeof value !== 'number') {
|
} else if (typeof value !== 'number') {
|
||||||
throw new Error('not a note or frequency:' + value);
|
throw new Error('not a note or frequency:' + value);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,11 +39,11 @@ Pattern.prototype.midi = function (output, channel = 1) {
|
|||||||
}')`,
|
}')`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return this._withEvent((event) => {
|
return this._withHap((hap) => {
|
||||||
// const onTrigger = (time: number, event: any) => {
|
// const onTrigger = (time: number, hap: any) => {
|
||||||
const onTrigger = (time, event) => {
|
const onTrigger = (time, hap) => {
|
||||||
let note = event.value;
|
let note = hap.value;
|
||||||
const velocity = event.context?.velocity ?? 0.9;
|
const velocity = hap.context?.velocity ?? 0.9;
|
||||||
if (!isNote(note)) {
|
if (!isNote(note)) {
|
||||||
throw new Error('not a note: ' + note);
|
throw new Error('not a note: ' + note);
|
||||||
}
|
}
|
||||||
@ -75,10 +75,10 @@ Pattern.prototype.midi = function (output, channel = 1) {
|
|||||||
// await enableWebMidi()
|
// await enableWebMidi()
|
||||||
device.playNote(note, channel, {
|
device.playNote(note, channel, {
|
||||||
time,
|
time,
|
||||||
duration: event.duration.valueOf() * 1000 - 5,
|
duration: hap.duration.valueOf() * 1000 - 5,
|
||||||
velocity,
|
velocity,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
return event.setContext({ ...event.context, onTrigger });
|
return hap.setContext({ ...hap.context, onTrigger });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -12,11 +12,11 @@ comm.open();
|
|||||||
const latency = 0.1;
|
const latency = 0.1;
|
||||||
|
|
||||||
Pattern.prototype.osc = function () {
|
Pattern.prototype.osc = function () {
|
||||||
return this._withEvent((event) => {
|
return this._withHap((hap) => {
|
||||||
const onTrigger = (time, event, currentTime) => {
|
const onTrigger = (time, hap, currentTime) => {
|
||||||
// time should be audio time of onset
|
// time should be audio time of onset
|
||||||
// currentTime should be current time of audio context (slightly before time)
|
// currentTime should be current time of audio context (slightly before time)
|
||||||
const keyvals = Object.entries(event.value).flat();
|
const keyvals = Object.entries(hap.value).flat();
|
||||||
const offset = (time - currentTime + latency) * 1000;
|
const offset = (time - currentTime + latency) * 1000;
|
||||||
const ts = Math.floor(Date.now() + offset);
|
const ts = Math.floor(Date.now() + offset);
|
||||||
const message = new OSC.Message('/dirt/play', ...keyvals);
|
const message = new OSC.Message('/dirt/play', ...keyvals);
|
||||||
@ -24,6 +24,6 @@ Pattern.prototype.osc = function () {
|
|||||||
bundle.timestamp(ts); // workaround for https://github.com/adzialocha/osc-js/issues/60
|
bundle.timestamp(ts); // workaround for https://github.com/adzialocha/osc-js/issues/60
|
||||||
comm.send(bundle);
|
comm.send(bundle);
|
||||||
};
|
};
|
||||||
return event.setContext({ ...event.context, onTrigger });
|
return hap.setContext({ ...hap.context, onTrigger });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -36,23 +36,23 @@ const latency = 0.1;
|
|||||||
|
|
||||||
// Pattern.prototype.midi = function (output: string | number, channel = 1) {
|
// Pattern.prototype.midi = function (output: string | number, channel = 1) {
|
||||||
Pattern.prototype.serial = async function (...args) {
|
Pattern.prototype.serial = async function (...args) {
|
||||||
return this._withEvent((event) => {
|
return this._withHap((hap) => {
|
||||||
if (!serialWriter) {
|
if (!serialWriter) {
|
||||||
getWriter(...args);
|
getWriter(...args);
|
||||||
}
|
}
|
||||||
const onTrigger = (time, event, currentTime) => {
|
const onTrigger = (time, hap, currentTime) => {
|
||||||
var message = "";
|
var message = "";
|
||||||
if (typeof event.value === 'object') {
|
if (typeof hap.value === 'object') {
|
||||||
for (const [key, val] of Object.entries(event.value).flat()) {
|
for (const [key, val] of Object.entries(hap.value).flat()) {
|
||||||
message += `${key}:${val};`
|
message += `${key}:${val};`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
message = event.value;
|
message = hap.value;
|
||||||
}
|
}
|
||||||
const offset = (time - currentTime + latency) * 1000;
|
const offset = (time - currentTime + latency) * 1000;
|
||||||
window.setTimeout(serialWriter, offset, message);
|
window.setTimeout(serialWriter, offset, message);
|
||||||
};
|
};
|
||||||
return event.setContext({ ...event.context, onTrigger });
|
return hap.setContext({ ...hap.context, onTrigger });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -43,17 +43,17 @@ export function scaleTranspose(scale, offset, note) {
|
|||||||
|
|
||||||
// Pattern.prototype._transpose = function (intervalOrSemitones: string | number) {
|
// Pattern.prototype._transpose = function (intervalOrSemitones: string | number) {
|
||||||
Pattern.prototype._transpose = function (intervalOrSemitones) {
|
Pattern.prototype._transpose = function (intervalOrSemitones) {
|
||||||
return this._withEvent((event) => {
|
return this._withHap((hap) => {
|
||||||
const interval = !isNaN(Number(intervalOrSemitones))
|
const interval = !isNaN(Number(intervalOrSemitones))
|
||||||
? Interval.fromSemitones(intervalOrSemitones /* as number */)
|
? Interval.fromSemitones(intervalOrSemitones /* as number */)
|
||||||
: String(intervalOrSemitones);
|
: String(intervalOrSemitones);
|
||||||
if (typeof event.value === 'number') {
|
if (typeof hap.value === 'number') {
|
||||||
const semitones = typeof interval === 'string' ? Interval.semitones(interval) || 0 : interval;
|
const semitones = typeof interval === 'string' ? Interval.semitones(interval) || 0 : interval;
|
||||||
return event.withValue(() => event.value + semitones);
|
return hap.withValue(() => hap.value + semitones);
|
||||||
}
|
}
|
||||||
// TODO: move simplify to player to preserve enharmonics
|
// TODO: move simplify to player to preserve enharmonics
|
||||||
// tone.js doesn't understand multiple sharps flats e.g. F##3 has to be turned into G3
|
// tone.js doesn't understand multiple sharps flats e.g. F##3 has to be turned into G3
|
||||||
return event.withValue(() => Note.simplify(Note.transpose(event.value, interval)));
|
return hap.withValue(() => Note.simplify(Note.transpose(hap.value, interval)));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -64,26 +64,26 @@ Pattern.prototype._transpose = function (intervalOrSemitones) {
|
|||||||
// or even `stack(c3).superimpose(transpose.slowcat(7, 5))` or
|
// or even `stack(c3).superimpose(transpose.slowcat(7, 5))` or
|
||||||
|
|
||||||
Pattern.prototype._scaleTranspose = function (offset /* : number | string */) {
|
Pattern.prototype._scaleTranspose = function (offset /* : number | string */) {
|
||||||
return this._withEvent((event) => {
|
return this._withHap((hap) => {
|
||||||
if (!event.context.scale) {
|
if (!hap.context.scale) {
|
||||||
throw new Error('can only use scaleTranspose after .scale');
|
throw new Error('can only use scaleTranspose after .scale');
|
||||||
}
|
}
|
||||||
if (typeof event.value !== 'string') {
|
if (typeof hap.value !== 'string') {
|
||||||
throw new Error('can only use scaleTranspose with notes');
|
throw new Error('can only use scaleTranspose with notes');
|
||||||
}
|
}
|
||||||
return event.withValue(() => scaleTranspose(event.context.scale, Number(offset), event.value));
|
return hap.withValue(() => scaleTranspose(hap.context.scale, Number(offset), hap.value));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
Pattern.prototype._scale = function (scale /* : string */) {
|
Pattern.prototype._scale = function (scale /* : string */) {
|
||||||
return this._withEvent((event) => {
|
return this._withHap((hap) => {
|
||||||
let note = event.value;
|
let note = hap.value;
|
||||||
const asNumber = Number(note);
|
const asNumber = Number(note);
|
||||||
if (!isNaN(asNumber)) {
|
if (!isNaN(asNumber)) {
|
||||||
let [tonic, scaleName] = Scale.tokenize(scale);
|
let [tonic, scaleName] = Scale.tokenize(scale);
|
||||||
const { pc, oct = 3 } = Note.get(tonic);
|
const { pc, oct = 3 } = Note.get(tonic);
|
||||||
note = scaleTranspose(pc + ' ' + scaleName, asNumber, pc + oct);
|
note = scaleTranspose(pc + ' ' + scaleName, asNumber, pc + oct);
|
||||||
}
|
}
|
||||||
return event.withValue(() => note).setContext({ ...event.context, scale });
|
return hap.withValue(() => note).setContext({ ...hap.context, scale });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -52,36 +52,36 @@ export const getDefaultSynth = () => {
|
|||||||
|
|
||||||
// with this function, you can play the pattern with any tone synth
|
// with this function, you can play the pattern with any tone synth
|
||||||
Pattern.prototype.tone = function (instrument) {
|
Pattern.prototype.tone = function (instrument) {
|
||||||
return this._withEvent((event) => {
|
return this._withHap((hap) => {
|
||||||
const onTrigger = (time, event) => {
|
const onTrigger = (time, hap) => {
|
||||||
let note;
|
let note;
|
||||||
let velocity = event.context?.velocity ?? 0.75;
|
let velocity = hap.context?.velocity ?? 0.75;
|
||||||
if (instrument instanceof PluckSynth) {
|
if (instrument instanceof PluckSynth) {
|
||||||
note = getPlayableNoteValue(event);
|
note = getPlayableNoteValue(hap);
|
||||||
instrument.triggerAttack(note, time);
|
instrument.triggerAttack(note, time);
|
||||||
} else if (instrument instanceof NoiseSynth) {
|
} else if (instrument instanceof NoiseSynth) {
|
||||||
instrument.triggerAttackRelease(event.duration.valueOf(), time); // noise has no value
|
instrument.triggerAttackRelease(hap.duration.valueOf(), time); // noise has no value
|
||||||
} else if (instrument instanceof Piano) {
|
} else if (instrument instanceof Piano) {
|
||||||
note = getPlayableNoteValue(event);
|
note = getPlayableNoteValue(hap);
|
||||||
instrument.keyDown({ note, time, velocity });
|
instrument.keyDown({ note, time, velocity });
|
||||||
instrument.keyUp({ note, time: time + event.duration.valueOf(), velocity });
|
instrument.keyUp({ note, time: time + hap.duration.valueOf(), velocity });
|
||||||
} else if (instrument instanceof Sampler) {
|
} else if (instrument instanceof Sampler) {
|
||||||
note = getPlayableNoteValue(event);
|
note = getPlayableNoteValue(hap);
|
||||||
instrument.triggerAttackRelease(note, event.duration.valueOf(), time, velocity);
|
instrument.triggerAttackRelease(note, hap.duration.valueOf(), time, velocity);
|
||||||
} else if (instrument instanceof Players) {
|
} else if (instrument instanceof Players) {
|
||||||
if (!instrument.has(event.value)) {
|
if (!instrument.has(hap.value)) {
|
||||||
throw new Error(`name "${event.value}" not defined for players`);
|
throw new Error(`name "${hap.value}" not defined for players`);
|
||||||
}
|
}
|
||||||
const player = instrument.player(event.value);
|
const player = instrument.player(hap.value);
|
||||||
// velocity ?
|
// velocity ?
|
||||||
player.start(time);
|
player.start(time);
|
||||||
player.stop(time + event.duration.valueOf());
|
player.stop(time + hap.duration.valueOf());
|
||||||
} else {
|
} else {
|
||||||
note = getPlayableNoteValue(event);
|
note = getPlayableNoteValue(hap);
|
||||||
instrument.triggerAttackRelease(note, event.duration.valueOf(), time, velocity);
|
instrument.triggerAttackRelease(note, hap.duration.valueOf(), time, velocity);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return event.setContext({ ...event.context, instrument, onTrigger });
|
return hap.setContext({ ...hap.context, instrument, onTrigger });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -30,10 +30,10 @@ const adsr = (attack, decay, sustain, release, velocity, begin, end) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Pattern.prototype.withAudioNode = function (createAudioNode) {
|
Pattern.prototype.withAudioNode = function (createAudioNode) {
|
||||||
return this._withEvent((event) => {
|
return this._withHap((hap) => {
|
||||||
return event.setContext({
|
return hap.setContext({
|
||||||
...event.context,
|
...hap.context,
|
||||||
createAudioNode: (t, e) => createAudioNode(t, e, event.context.createAudioNode?.(t, event)),
|
createAudioNode: (t, e) => createAudioNode(t, e, hap.context.createAudioNode?.(t, hap)),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -84,9 +84,9 @@ Pattern.prototype.out = function () {
|
|||||||
console.warn('out: no source! call .osc() first');
|
console.warn('out: no source! call .osc() first');
|
||||||
}
|
}
|
||||||
node?.connect(master);
|
node?.connect(master);
|
||||||
})._withEvent((event) => {
|
})._withHap((hap) => {
|
||||||
const onTrigger = (time, e) => e.context?.createAudioNode?.(time, e);
|
const onTrigger = (time, e) => e.context?.createAudioNode?.(time, e);
|
||||||
return event.setContext({ ...event.context, onTrigger });
|
return hap.setContext({ ...hap.context, onTrigger });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -14,8 +14,8 @@ Pattern.prototype._tune = function (scale, tonic = 220) {
|
|||||||
}
|
}
|
||||||
tune.loadScale(scale);
|
tune.loadScale(scale);
|
||||||
tune.tonicize(tonic);
|
tune.tonicize(tonic);
|
||||||
return this._asNumber()._withEvent((event) => {
|
return this._asNumber()._withHap((hap) => {
|
||||||
return event.withValue(() => tune.note(event.value)).setContext({ ...event.context, type: 'frequency' });
|
return hap.withValue(() => tune.note(hap.value)).setContext({ ...hap.context, type: 'frequency' });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -49,18 +49,18 @@ function xenOffset(xenScale, offset, index = 0) {
|
|||||||
|
|
||||||
// scaleNameOrRatios: string || number[], steps?: number
|
// scaleNameOrRatios: string || number[], steps?: number
|
||||||
Pattern.prototype._xen = function (scaleNameOrRatios, steps) {
|
Pattern.prototype._xen = function (scaleNameOrRatios, steps) {
|
||||||
return this._asNumber()._withEvent((event) => {
|
return this._asNumber()._withHap((hap) => {
|
||||||
const scale = getXenScale(scaleNameOrRatios);
|
const scale = getXenScale(scaleNameOrRatios);
|
||||||
steps = steps || scale.length;
|
steps = steps || scale.length;
|
||||||
const frequency = xenOffset(scale, event.value);
|
const frequency = xenOffset(scale, hap.value);
|
||||||
return event.withValue(() => frequency).setContext({ ...event.context, type: 'frequency' });
|
return hap.withValue(() => frequency).setContext({ ...hap.context, type: 'frequency' });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Pattern.prototype.tuning = function (steps) {
|
Pattern.prototype.tuning = function (steps) {
|
||||||
return this._asNumber()._withEvent((event) => {
|
return this._asNumber()._withHap((hap) => {
|
||||||
const frequency = xenOffset(steps, event.value);
|
const frequency = xenOffset(steps, hap.value);
|
||||||
return event.withValue(() => frequency).setContext({ ...event.context, type: 'frequency' });
|
return hap.withValue(() => frequency).setContext({ ...hap.context, type: 'frequency' });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
Pattern.prototype.define('xen', (scale, pat) => pat.xen(scale), { composable: true, patternified: true });
|
Pattern.prototype.define('xen', (scale, pat) => pat.xen(scale), { composable: true, patternified: true });
|
||||||
|
|||||||
23
repl/etc/agpl-header.txt
Normal file
23
repl/etc/agpl-header.txt
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
Strudel - javascript-based environment for live coding algorithmic (musical) patterns
|
||||||
|
https://strudel.tidalcycles.org / https://github.com/tidalcycles/strudel/
|
||||||
|
|
||||||
|
Copyright (C) Strudel contributors
|
||||||
|
https://github.com/tidalcycles/strudel/graphs/contributors
|
||||||
|
|
||||||
|
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/>.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
@ -22,12 +22,13 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
"build": "BUILD_PATH='../docs' react-scripts build && npm run build-tutorial",
|
"build": "BUILD_PATH='../docs' react-scripts build && npm run build-tutorial && npm add-license",
|
||||||
"test": "mocha ./src/test --colors",
|
"test": "mocha ./src/test --colors",
|
||||||
"snapshot": "cd ./src/ && rm -f ./tunes.snapshot.mjs && node ./shoot.mjs > ./tunes.snapshot.mjs",
|
"snapshot": "cd ./src/ && rm -f ./tunes.snapshot.mjs && node ./shoot.mjs > ./tunes.snapshot.mjs",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
"tutorial": "parcel src/tutorial/index.html --no-cache",
|
"tutorial": "parcel src/tutorial/index.html --no-cache",
|
||||||
"build-tutorial": "rm -rf ../docs/tutorial && parcel build src/tutorial/index.html --dist-dir ../docs/tutorial --public-url /tutorial --no-scope-hoist --no-cache",
|
"build-tutorial": "rm -rf ../docs/tutorial && parcel build src/tutorial/index.html --dist-dir ../docs/tutorial --public-url /tutorial --no-scope-hoist --no-cache",
|
||||||
|
"add-license": "cat etc/agpl-header.txt ../docs/static/js/*LICENSE.txt > /tmp/strudel-license.txt && cp /tmp/strudel-license.txt ../docs/static/js/*LICENSE.txt",
|
||||||
"predeploy": "npm run build",
|
"predeploy": "npm run build",
|
||||||
"deploy": "gh-pages -d ../docs",
|
"deploy": "gh-pages -d ../docs",
|
||||||
"static": "npx serve ../docs"
|
"static": "npx serve ../docs"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user