standardise on hap rather than event, especially in function names

This commit is contained in:
alex 2022-05-05 17:20:36 +01:00
parent b0dfb44e96
commit f8f744dd87
13 changed files with 159 additions and 153 deletions

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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 });
});
};

View File

@ -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(),
);
});

View File

@ -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);
}

View File

@ -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 });
});
};

View File

@ -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 });
});
};

View File

@ -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 });
});
};

View File

@ -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 });
});
};

View File

@ -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 });
});
};

View File

@ -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 });
});
};

View File

@ -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' });
});
};

View File

@ -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 });