mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-11 05:38:34 +00:00
standardise on hap rather than event, especially in function names
This commit is contained in:
parent
b0dfb44e96
commit
f8f744dd87
@ -14,7 +14,10 @@ export class Hap {
|
||||
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
|
||||
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) {
|
||||
@ -37,18 +40,18 @@ export class Hap {
|
||||
}
|
||||
|
||||
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;
|
||||
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.
|
||||
// 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);
|
||||
}
|
||||
|
||||
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."""
|
||||
return this.whole != undefined && this.whole.begin.equals(this.part.begin);
|
||||
}
|
||||
|
||||
@ -24,14 +24,14 @@ export class Pattern {
|
||||
}
|
||||
|
||||
/**
|
||||
* query events insude the tiven time span
|
||||
* 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 events = pattern.queryArc(0, 1);
|
||||
* const haps = pattern.queryArc(0, 1);
|
||||
*/
|
||||
queryArc(begin, end) {
|
||||
return this.query(new State(new TimeSpan(begin, end)));
|
||||
@ -39,7 +39,7 @@ export class Pattern {
|
||||
|
||||
/**
|
||||
* Returns a new pattern, with queries split at cycle boundaries. This makes
|
||||
* some calculations easier to express, as all events are then constrained to
|
||||
* some calculations easier to express, as all haps are then constrained to
|
||||
* happen within a cycle.
|
||||
* @returns Pattern
|
||||
*/
|
||||
@ -78,40 +78,43 @@ export class Pattern {
|
||||
* @param {Function} func
|
||||
* @returns Pattern
|
||||
*/
|
||||
withEventSpan(func) {
|
||||
withHapSpan(func) {
|
||||
return new Pattern((state) => this.query(state).map((hap) => hap.withSpan(func)));
|
||||
}
|
||||
|
||||
/**
|
||||
* As with {@link Pattern#withEventSpan|withEventSpan}, but the function is applied to both the
|
||||
* As with {@link Pattern#withHapSpan|withHapSpan}, but the function is applied to both the
|
||||
* begin and end time of the hap timespans.
|
||||
* @param {Function} func the function to apply
|
||||
* @returns Pattern
|
||||
*/
|
||||
withEventTime(func) {
|
||||
// Returns a new pattern, with the function applied to both the begin
|
||||
// and end of each event timespan.
|
||||
return this.withEventSpan((span) => span.withTime(func));
|
||||
withHapTime(func) {
|
||||
return this.withHapSpan((span) => span.withTime(func));
|
||||
}
|
||||
|
||||
_withEvents(func) {
|
||||
/**
|
||||
* Returns a new pattern with the given function applied to all haps returned by queries.
|
||||
* @param {Function} func
|
||||
* @returns Pattern
|
||||
*/
|
||||
_withHaps(func) {
|
||||
return new Pattern((state) => func(this.query(state)));
|
||||
}
|
||||
|
||||
_withEvent(func) {
|
||||
return this._withEvents((events) => events.map(func));
|
||||
_withHap(func) {
|
||||
return this._withHaps((haps) => haps.map(func));
|
||||
}
|
||||
|
||||
_setContext(context) {
|
||||
return this._withEvent((event) => event.setContext(context));
|
||||
return this._withHap((hap) => hap.setContext(context));
|
||||
}
|
||||
|
||||
_withContext(func) {
|
||||
return this._withEvent((event) => event.setContext(func(event.context)));
|
||||
return this._withHap((hap) => hap.setContext(func(hap.context)));
|
||||
}
|
||||
|
||||
_stripContext() {
|
||||
return this._withEvent((event) => event.setContext({}));
|
||||
return this._withHap((hap) => hap.setContext({}));
|
||||
}
|
||||
|
||||
withLocation(start, end) {
|
||||
@ -153,7 +156,7 @@ export class Pattern {
|
||||
|
||||
/**
|
||||
* Returns a new pattern, with the function applied to the value of
|
||||
* each event. It has the alias {@link Pattern#fmap|fmap}.
|
||||
* each hap. It has the alias {@link Pattern#fmap|fmap}.
|
||||
* @param {Function} func
|
||||
* @returns Pattern
|
||||
*/
|
||||
@ -168,8 +171,8 @@ export class Pattern {
|
||||
return this.withValue(func);
|
||||
}
|
||||
|
||||
_filterEvents(event_test) {
|
||||
return new Pattern((state) => this.query(state).filter(event_test));
|
||||
_filterHaps(hap_test) {
|
||||
return new Pattern((state) => this.query(state).filter(hap_test));
|
||||
}
|
||||
|
||||
_filterValues(value_test) {
|
||||
@ -187,15 +190,15 @@ export class Pattern {
|
||||
* @returns Pattern
|
||||
*/
|
||||
onsetsOnly() {
|
||||
// Returns a new pattern that will only return events where the start
|
||||
// Returns a new pattern that will only return haps where the start
|
||||
// of the 'whole' timespan matches the start of the 'part'
|
||||
// timespan, i.e. the events that include their 'onset'.
|
||||
return this._filterEvents((hap) => hap.hasOnset());
|
||||
// timespan, i.e. the haps that include their 'onset'.
|
||||
return this._filterHaps((hap) => hap.hasOnset());
|
||||
}
|
||||
|
||||
discreteOnly() {
|
||||
// removes continuous events that don't have a 'whole' timespan
|
||||
return this._filterEvents((hap) => hap.whole);
|
||||
// removes continuous haps that don't have a 'whole' timespan
|
||||
return this._filterHaps((hap) => hap.whole);
|
||||
}
|
||||
|
||||
_appWhole(whole_func, pat_val) {
|
||||
@ -204,22 +207,22 @@ export class Pattern {
|
||||
// pattern of functions.
|
||||
const pat_func = this;
|
||||
const query = function (state) {
|
||||
const event_funcs = pat_func.query(state);
|
||||
const event_vals = pat_val.query(state);
|
||||
const apply = function (event_func, event_val) {
|
||||
const s = event_func.part.intersection(event_val.part);
|
||||
const hap_funcs = pat_func.query(state);
|
||||
const hap_vals = pat_val.query(state);
|
||||
const apply = function (hap_func, hap_val) {
|
||||
const s = hap_func.part.intersection(hap_val.part);
|
||||
if (s == undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return new Hap(
|
||||
whole_func(event_func.whole, event_val.whole),
|
||||
whole_func(hap_func.whole, hap_val.whole),
|
||||
s,
|
||||
event_func.value(event_val.value),
|
||||
event_val.combineContext(event_func),
|
||||
hap_func.value(hap_val.value),
|
||||
hap_val.combineContext(hap_func),
|
||||
);
|
||||
};
|
||||
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);
|
||||
@ -262,8 +265,8 @@ export class Pattern {
|
||||
const query = function (state) {
|
||||
const haps = [];
|
||||
for (const hap_func of pat_func.query(state)) {
|
||||
const event_vals = pat_val.query(state.setSpan(hap_func.wholeOrPart()));
|
||||
for (const hap_val of event_vals) {
|
||||
const hap_vals = pat_val.query(state.setSpan(hap_func.wholeOrPart()));
|
||||
for (const hap_val of hap_vals) {
|
||||
const new_whole = hap_func.whole;
|
||||
const new_part = hap_func.part.intersection(hap_val.part);
|
||||
if (new_part) {
|
||||
@ -326,9 +329,9 @@ export class Pattern {
|
||||
);
|
||||
}
|
||||
|
||||
_sortEventsByPart() {
|
||||
return this._withEvents((events) =>
|
||||
events.sort((a, b) =>
|
||||
_sortHapsByPart() {
|
||||
return this._withHaps((haps) =>
|
||||
haps.sort((a, b) =>
|
||||
a.part.begin
|
||||
.sub(b.part.begin)
|
||||
.or(a.part.end.sub(b.part.end))
|
||||
@ -365,21 +368,21 @@ export class Pattern {
|
||||
}
|
||||
|
||||
_asNumber(dropfails = false, softfail = false) {
|
||||
return this._withEvent((event) => {
|
||||
const asNumber = Number(event.value);
|
||||
return this._withHap((hap) => {
|
||||
const asNumber = Number(hap.value);
|
||||
if (!isNaN(asNumber)) {
|
||||
return event.withValue(() => asNumber);
|
||||
return hap.withValue(() => asNumber);
|
||||
}
|
||||
const specialValue = {
|
||||
e: Math.E,
|
||||
pi: Math.PI,
|
||||
}[event.value];
|
||||
}[hap.value];
|
||||
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
|
||||
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) {
|
||||
// return 'nothing'
|
||||
@ -387,10 +390,10 @@ export class Pattern {
|
||||
}
|
||||
if (softfail) {
|
||||
// return original hap
|
||||
return event;
|
||||
return hap;
|
||||
}
|
||||
throw new Error('cannot parse as number: "' + event.value + '"');
|
||||
return event;
|
||||
throw new Error('cannot parse as number: "' + hap.value + '"');
|
||||
return hap;
|
||||
});
|
||||
if (dropfail) {
|
||||
return result._removeUndefineds();
|
||||
@ -466,7 +469,7 @@ export class Pattern {
|
||||
|
||||
join() {
|
||||
// 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);
|
||||
}
|
||||
|
||||
@ -476,7 +479,7 @@ export class Pattern {
|
||||
|
||||
outerJoin() {
|
||||
// Flattens a pattern of patterns into a pattern, where wholes are
|
||||
// taken from inner events.
|
||||
// taken from inner haps.
|
||||
return this.outerBind(id);
|
||||
}
|
||||
|
||||
@ -486,35 +489,35 @@ export class Pattern {
|
||||
|
||||
innerJoin() {
|
||||
// Flattens a pattern of patterns into a pattern, where wholes are
|
||||
// taken from inner events.
|
||||
// taken from inner haps.
|
||||
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) {
|
||||
const pat_of_pats = this;
|
||||
return new Pattern((state) => {
|
||||
return (
|
||||
pat_of_pats
|
||||
// drop continuous events from the outer pattern.
|
||||
// drop continuous haps from the outer pattern.
|
||||
.discreteOnly()
|
||||
.query(state)
|
||||
.map((outer_hap) => {
|
||||
return (
|
||||
outer_hap.value
|
||||
// trig = align the inner pattern cycle start to outer pattern events
|
||||
// Trigzero = align the inner pattern cycle zero 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 haps
|
||||
.late(cycleZero ? outer_hap.whole.begin : outer_hap.whole.begin.cyclePos())
|
||||
.query(state)
|
||||
.map((inner_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.part.intersection(outer_hap.part),
|
||||
inner_hap.value,
|
||||
).setContext(outer_hap.combineContext(inner_hap)),
|
||||
)
|
||||
// Drop events that didn't intersect
|
||||
// Drop haps that didn't intersect
|
||||
.filter((hap) => hap.part)
|
||||
);
|
||||
})
|
||||
@ -604,7 +607,7 @@ export class Pattern {
|
||||
const end = cycle.add(span.end.sub(cycle).div(factor).min(1));
|
||||
return new TimeSpan(begin, end);
|
||||
};
|
||||
return this.withQuerySpan(qf).withEventSpan(ef)._splitQueries();
|
||||
return this.withQuerySpan(qf).withHapSpan(ef)._splitQueries();
|
||||
}
|
||||
|
||||
_compress(b, e) {
|
||||
@ -620,7 +623,7 @@ export class Pattern {
|
||||
|
||||
_fast(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) {
|
||||
@ -655,7 +658,7 @@ export class Pattern {
|
||||
_early(offset) {
|
||||
// Equivalent of Tidal's <~ operator
|
||||
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) {
|
||||
@ -669,7 +672,7 @@ export class Pattern {
|
||||
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)))
|
||||
.withHapSpan((span) => span.withCycle((t) => t.sub(s).div(d)))
|
||||
._splitQueries();
|
||||
}
|
||||
|
||||
@ -709,7 +712,7 @@ export class Pattern {
|
||||
}
|
||||
|
||||
log() {
|
||||
return this._withEvent((e) => {
|
||||
return this._withHap((e) => {
|
||||
return e.setContext({ ...e.context, logs: (e.context?.logs || []).concat([e.show()]) });
|
||||
});
|
||||
}
|
||||
@ -877,14 +880,14 @@ export class Pattern {
|
||||
return silence;
|
||||
}
|
||||
|
||||
// sets absolute duration of events
|
||||
// sets absolute duration of haps
|
||||
_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) {
|
||||
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) {
|
||||
@ -1101,7 +1104,7 @@ export function slowcat(...pats) {
|
||||
// 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.
|
||||
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();
|
||||
}
|
||||
|
||||
@ -26,11 +26,11 @@ function speak(words, lang, voice) {
|
||||
}
|
||||
|
||||
Pattern.prototype._speak = function (lang, voice) {
|
||||
return this._withEvent((event) => {
|
||||
const onTrigger = (time, event) => {
|
||||
speak(event.value, lang, voice);
|
||||
return this._withHap((hap) => {
|
||||
const onTrigger = (time, hap) => {
|
||||
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 sameFirst = (a, b) => {
|
||||
return assert.deepStrictEqual(a._sortEventsByPart().firstCycle(), b._sortEventsByPart().firstCycle());
|
||||
return assert.deepStrictEqual(a._sortHapsByPart().firstCycle(), b._sortHapsByPart().firstCycle());
|
||||
};
|
||||
|
||||
describe('TimeSpan', function () {
|
||||
@ -320,7 +320,7 @@ describe('Pattern', function () {
|
||||
});
|
||||
});
|
||||
describe('setSqueeze()', () => {
|
||||
it('Can squeeze one pattern inside the events of another', () => {
|
||||
it('Can squeeze one pattern inside the haps of another', () => {
|
||||
sameFirst(
|
||||
sequence(1, [2, 3]).setSqueeze(sequence('a', 'b', 'c')),
|
||||
sequence(
|
||||
@ -394,7 +394,7 @@ describe('Pattern', function () {
|
||||
});
|
||||
it('Makes things faster, with a pattern of factors', function () {
|
||||
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(
|
||||
pure('a').fast(sequence(1, 4)).firstCycle(),
|
||||
stack(pure('a').fast(sequence(1, silence)), sequence(silence, ['a', 'a'])).firstCycle(),
|
||||
@ -468,7 +468,7 @@ describe('Pattern', function () {
|
||||
});
|
||||
it('Can alternate', function () {
|
||||
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(),
|
||||
);
|
||||
});
|
||||
@ -679,7 +679,7 @@ describe('Pattern', function () {
|
||||
});
|
||||
});
|
||||
describe('_setContext()', () => {
|
||||
it('Can set the event context', () => {
|
||||
it('Can set the hap context', () => {
|
||||
assert.deepStrictEqual(
|
||||
pure('a')
|
||||
._setContext([
|
||||
@ -701,7 +701,7 @@ describe('Pattern', function () {
|
||||
});
|
||||
});
|
||||
describe('_withContext()', () => {
|
||||
it('Can update the event context', () => {
|
||||
it('Can update the hap context', () => {
|
||||
assert.deepStrictEqual(
|
||||
pure('a')
|
||||
._setContext([
|
||||
@ -756,13 +756,13 @@ describe('Pattern', function () {
|
||||
});
|
||||
});
|
||||
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)), [
|
||||
hap(ts(1 / 4, 5 / 4), ts(1, 5 / 4), 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)), [
|
||||
hap(ts(-3 / 4, 1 / 4), ts(0, 1 / 4), 30),
|
||||
hap(ts(1 / 4, 5 / 4), ts(1 / 4, 1), 30),
|
||||
@ -780,9 +780,9 @@ describe('Pattern', function () {
|
||||
describe('jux', () => {
|
||||
it('Can juxtapose', () => {
|
||||
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))
|
||||
._sortEventsByPart()
|
||||
._sortHapsByPart()
|
||||
.firstCycle(),
|
||||
);
|
||||
});
|
||||
@ -790,9 +790,9 @@ describe('Pattern', function () {
|
||||
describe('juxBy', () => {
|
||||
it('Can juxtapose by half', () => {
|
||||
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))
|
||||
._sortEventsByPart()
|
||||
._sortHapsByPart()
|
||||
.firstCycle(),
|
||||
);
|
||||
});
|
||||
@ -821,7 +821,7 @@ describe('Pattern', function () {
|
||||
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
|
||||
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)', () => {
|
||||
assert.deepStrictEqual(
|
||||
pure({ sound: 'a' }).fast(2).chop(2, 3)._sortEventsByPart().firstCycle(),
|
||||
pure({ sound: 'a' }).fast(2).chop(2, 3)._sortHapsByPart().firstCycle(),
|
||||
sequence(
|
||||
[
|
||||
{ sound: 'a', begin: 0, end: 0.5 },
|
||||
@ -860,7 +860,7 @@ describe('Pattern', function () {
|
||||
{ sound: 'a', begin: 2 / 3, end: 1 },
|
||||
],
|
||||
)
|
||||
._sortEventsByPart()
|
||||
._sortHapsByPart()
|
||||
.firstCycle(),
|
||||
);
|
||||
});
|
||||
|
||||
@ -35,27 +35,27 @@ export const fromMidi = (n) => {
|
||||
// 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 getPlayableNoteValue = (event) => {
|
||||
let { value: note, context } = event;
|
||||
export const getPlayableNoteValue = (hap) => {
|
||||
let { value: note, context } = hap;
|
||||
// if value is number => interpret as midi number as long as its not marked as frequency
|
||||
if (typeof note === 'number' && context.type !== 'frequency') {
|
||||
note = fromMidi(event.value);
|
||||
note = fromMidi(hap.value);
|
||||
} else if (typeof note === 'string' && !isNote(note)) {
|
||||
throw new Error('not a note: ' + note);
|
||||
}
|
||||
return note;
|
||||
};
|
||||
|
||||
export const getFrequency = (event) => {
|
||||
let { value, context } = event;
|
||||
export const getFrequency = (hap) => {
|
||||
let { value, context } = hap;
|
||||
// if value is number => interpret as midi number as long as its not marked as frequency
|
||||
if (typeof value === 'object' && value.freq) {
|
||||
return value.freq;
|
||||
}
|
||||
if (typeof value === 'number' && context.type !== 'frequency') {
|
||||
value = fromMidi(event.value);
|
||||
value = fromMidi(hap.value);
|
||||
} else if (typeof value === 'string' && isNote(value)) {
|
||||
value = fromMidi(toMidi(event.value));
|
||||
value = fromMidi(toMidi(hap.value));
|
||||
} else if (typeof value !== 'number') {
|
||||
throw new Error('not a note or frequency:' + value);
|
||||
}
|
||||
|
||||
@ -39,11 +39,11 @@ Pattern.prototype.midi = function (output, channel = 1) {
|
||||
}')`,
|
||||
);
|
||||
}
|
||||
return this._withEvent((event) => {
|
||||
// const onTrigger = (time: number, event: any) => {
|
||||
const onTrigger = (time, event) => {
|
||||
let note = event.value;
|
||||
const velocity = event.context?.velocity ?? 0.9;
|
||||
return this._withHap((hap) => {
|
||||
// const onTrigger = (time: number, hap: any) => {
|
||||
const onTrigger = (time, hap) => {
|
||||
let note = hap.value;
|
||||
const velocity = hap.context?.velocity ?? 0.9;
|
||||
if (!isNote(note)) {
|
||||
throw new Error('not a note: ' + note);
|
||||
}
|
||||
@ -75,10 +75,10 @@ Pattern.prototype.midi = function (output, channel = 1) {
|
||||
// await enableWebMidi()
|
||||
device.playNote(note, channel, {
|
||||
time,
|
||||
duration: event.duration.valueOf() * 1000 - 5,
|
||||
duration: hap.duration.valueOf() * 1000 - 5,
|
||||
velocity,
|
||||
});
|
||||
};
|
||||
return event.setContext({ ...event.context, onTrigger });
|
||||
return hap.setContext({ ...hap.context, onTrigger });
|
||||
});
|
||||
};
|
||||
|
||||
@ -12,11 +12,11 @@ comm.open();
|
||||
const latency = 0.1;
|
||||
|
||||
Pattern.prototype.osc = function () {
|
||||
return this._withEvent((event) => {
|
||||
const onTrigger = (time, event, currentTime) => {
|
||||
return this._withHap((hap) => {
|
||||
const onTrigger = (time, hap, currentTime) => {
|
||||
// time should be audio time of onset
|
||||
// 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 ts = Math.floor(Date.now() + offset);
|
||||
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
|
||||
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.serial = async function (...args) {
|
||||
return this._withEvent((event) => {
|
||||
return this._withHap((hap) => {
|
||||
if (!serialWriter) {
|
||||
getWriter(...args);
|
||||
}
|
||||
const onTrigger = (time, event, currentTime) => {
|
||||
const onTrigger = (time, hap, currentTime) => {
|
||||
var message = "";
|
||||
if (typeof event.value === 'object') {
|
||||
for (const [key, val] of Object.entries(event.value).flat()) {
|
||||
if (typeof hap.value === 'object') {
|
||||
for (const [key, val] of Object.entries(hap.value).flat()) {
|
||||
message += `${key}:${val};`
|
||||
}
|
||||
}
|
||||
else {
|
||||
message = event.value;
|
||||
message = hap.value;
|
||||
}
|
||||
const offset = (time - currentTime + latency) * 1000;
|
||||
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) {
|
||||
return this._withEvent((event) => {
|
||||
return this._withHap((hap) => {
|
||||
const interval = !isNaN(Number(intervalOrSemitones))
|
||||
? Interval.fromSemitones(intervalOrSemitones /* as number */)
|
||||
: String(intervalOrSemitones);
|
||||
if (typeof event.value === 'number') {
|
||||
if (typeof hap.value === 'number') {
|
||||
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
|
||||
// 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
|
||||
|
||||
Pattern.prototype._scaleTranspose = function (offset /* : number | string */) {
|
||||
return this._withEvent((event) => {
|
||||
if (!event.context.scale) {
|
||||
return this._withHap((hap) => {
|
||||
if (!hap.context.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');
|
||||
}
|
||||
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 */) {
|
||||
return this._withEvent((event) => {
|
||||
let note = event.value;
|
||||
return this._withHap((hap) => {
|
||||
let note = hap.value;
|
||||
const asNumber = Number(note);
|
||||
if (!isNaN(asNumber)) {
|
||||
let [tonic, scaleName] = Scale.tokenize(scale);
|
||||
const { pc, oct = 3 } = Note.get(tonic);
|
||||
note = scaleTranspose(pc + ' ' + scaleName, asNumber, pc + oct);
|
||||
}
|
||||
return 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
|
||||
Pattern.prototype.tone = function (instrument) {
|
||||
return this._withEvent((event) => {
|
||||
const onTrigger = (time, event) => {
|
||||
return this._withHap((hap) => {
|
||||
const onTrigger = (time, hap) => {
|
||||
let note;
|
||||
let velocity = event.context?.velocity ?? 0.75;
|
||||
let velocity = hap.context?.velocity ?? 0.75;
|
||||
if (instrument instanceof PluckSynth) {
|
||||
note = getPlayableNoteValue(event);
|
||||
note = getPlayableNoteValue(hap);
|
||||
instrument.triggerAttack(note, time);
|
||||
} 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) {
|
||||
note = getPlayableNoteValue(event);
|
||||
note = getPlayableNoteValue(hap);
|
||||
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) {
|
||||
note = getPlayableNoteValue(event);
|
||||
instrument.triggerAttackRelease(note, event.duration.valueOf(), time, velocity);
|
||||
note = getPlayableNoteValue(hap);
|
||||
instrument.triggerAttackRelease(note, hap.duration.valueOf(), time, velocity);
|
||||
} else if (instrument instanceof Players) {
|
||||
if (!instrument.has(event.value)) {
|
||||
throw new Error(`name "${event.value}" not defined for players`);
|
||||
if (!instrument.has(hap.value)) {
|
||||
throw new Error(`name "${hap.value}" not defined for players`);
|
||||
}
|
||||
const player = instrument.player(event.value);
|
||||
const player = instrument.player(hap.value);
|
||||
// velocity ?
|
||||
player.start(time);
|
||||
player.stop(time + event.duration.valueOf());
|
||||
player.stop(time + hap.duration.valueOf());
|
||||
} else {
|
||||
note = getPlayableNoteValue(event);
|
||||
instrument.triggerAttackRelease(note, event.duration.valueOf(), time, velocity);
|
||||
note = getPlayableNoteValue(hap);
|
||||
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) {
|
||||
return this._withEvent((event) => {
|
||||
return event.setContext({
|
||||
...event.context,
|
||||
createAudioNode: (t, e) => createAudioNode(t, e, event.context.createAudioNode?.(t, event)),
|
||||
return this._withHap((hap) => {
|
||||
return hap.setContext({
|
||||
...hap.context,
|
||||
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');
|
||||
}
|
||||
node?.connect(master);
|
||||
})._withEvent((event) => {
|
||||
})._withHap((hap) => {
|
||||
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.tonicize(tonic);
|
||||
return this._asNumber()._withEvent((event) => {
|
||||
return event.withValue(() => tune.note(event.value)).setContext({ ...event.context, type: 'frequency' });
|
||||
return this._asNumber()._withHap((hap) => {
|
||||
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
|
||||
Pattern.prototype._xen = function (scaleNameOrRatios, steps) {
|
||||
return this._asNumber()._withEvent((event) => {
|
||||
return this._asNumber()._withHap((hap) => {
|
||||
const scale = getXenScale(scaleNameOrRatios);
|
||||
steps = steps || scale.length;
|
||||
const frequency = xenOffset(scale, event.value);
|
||||
return event.withValue(() => frequency).setContext({ ...event.context, type: 'frequency' });
|
||||
const frequency = xenOffset(scale, hap.value);
|
||||
return hap.withValue(() => frequency).setContext({ ...hap.context, type: 'frequency' });
|
||||
});
|
||||
};
|
||||
|
||||
Pattern.prototype.tuning = function (steps) {
|
||||
return this._asNumber()._withEvent((event) => {
|
||||
const frequency = xenOffset(steps, event.value);
|
||||
return event.withValue(() => frequency).setContext({ ...event.context, type: 'frequency' });
|
||||
return this._asNumber()._withHap((hap) => {
|
||||
const frequency = xenOffset(steps, hap.value);
|
||||
return hap.withValue(() => frequency).setContext({ ...hap.context, type: 'frequency' });
|
||||
});
|
||||
};
|
||||
Pattern.prototype.define('xen', (scale, pat) => pat.xen(scale), { composable: true, patternified: true });
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user