Merge remote-tracking branch 'origin/main' into objectify-midi

This commit is contained in:
Felix Roos
2022-10-29 17:55:53 +02:00
33 changed files with 3567 additions and 11640 deletions
+156 -44
View File
@@ -468,7 +468,11 @@ export class Pattern {
/**
* Assumes a numerical pattern. Returns a new pattern with all values rounded
* to the nearest integer.
* @name round
* @memberof Pattern
* @returns Pattern
* @example
* "0.5 1.5 2.5".round().scale('C major').note()
*/
round() {
return this._asNumber().fmap((v) => Math.round(v));
@@ -520,9 +524,9 @@ export class Pattern {
* @memberof Pattern
* @returns Pattern
* @example
* s("bd sd,hh*4").cutoff(sine.range(500,2000).slow(4)).out()
* s("bd sd,hh*4").cutoff(sine.range(500,2000).slow(4))
*/
range(min, max) {
_range(min, max) {
return this.mul(max - min).add(min);
}
@@ -534,8 +538,8 @@ export class Pattern {
* @param {Number} max
* @returns Pattern
*/
rangex(min, max) {
return this.range(Math.log(min), Math.log(max)).fmap(Math.exp);
_rangex(min, max) {
return this._range(Math.log(min), Math.log(max)).fmap(Math.exp);
}
/**
@@ -545,8 +549,8 @@ export class Pattern {
* @param {Number} max
* @returns Pattern
*/
range2(min, max) {
return this._fromBipolar().range(min, max);
_range2(min, max) {
return this._fromBipolar()._range(min, max);
}
_bindWhole(choose_whole, func) {
@@ -695,6 +699,13 @@ export class Pattern {
return this.fmap(func)._squeezeJoin();
}
/**
* Like layer, but with a single function:
* @name apply
* @memberof Pattern
* @example
* "<c3 eb3 g3>".scale('C minor').apply(scaleTranspose("0,2,4")).note()
*/
_apply(func) {
return func(this);
}
@@ -707,7 +718,7 @@ export class Pattern {
* @example
* "<0 2 4 6 ~ 4 ~ 2 0!3 ~!5>*4"
* .layer(x=>x.add("0,2"))
* .scale('C minor').note().out()
* .scale('C minor').note()
*/
layer(...funcs) {
return stack(...funcs.map((func) => func(this)));
@@ -739,13 +750,22 @@ export class Pattern {
const end = cycle.add(span.end.sub(cycle).mul(factor).min(1));
return new TimeSpan(begin, end);
};
const ef = function (span) {
const cycle = span.begin.sam();
const begin = cycle.add(span.begin.sub(cycle).div(factor).min(1));
const end = cycle.add(span.end.sub(cycle).div(factor).min(1));
return new TimeSpan(begin, end);
const ef = function (hap) {
const begin = hap.part.begin;
const end = hap.part.end;
const cycle = begin.sam();
const beginPos = begin.sub(cycle).div(factor).min(1);
const endPos = end.sub(cycle).div(factor).min(1);
const newPart = new TimeSpan(cycle.add(beginPos), cycle.add(endPos));
const newWhole = !hap.whole
? undefined
: new TimeSpan(
newPart.begin.sub(begin.sub(hap.whole.begin).div(factor)),
newPart.end.add(hap.whole.end.sub(end).div(factor)),
);
return new Hap(newWhole, newPart, hap.value, hap.context);
};
return this.withQuerySpan(qf).withHapSpan(ef)._splitQueries();
return this.withQuerySpan(qf)._withHap(ef)._splitQueries();
}
// Compress each cycle into the given timespan, leaving a gap
@@ -765,7 +785,7 @@ export class Pattern {
_focus(b, e) {
return this._fast(Fraction(1).div(e.sub(b))).late(b.cyclePos());
}
_focusSpan(span) {
return this._focus(span.begin, span.end);
}
@@ -778,7 +798,7 @@ export class Pattern {
* @param {number | Pattern} factor speed up factor
* @returns Pattern
* @example
* s("<bd sd> hh").fast(2).out() // s("[<bd sd> hh]*2").out()
* s("<bd sd> hh").fast(2) // s("[<bd sd> hh]*2")
*/
_fast(factor) {
const fastQuery = this.withQueryTime((t) => t.mul(factor));
@@ -793,7 +813,7 @@ export class Pattern {
* @param {number | Pattern} factor slow down factor
* @returns Pattern
* @example
* s("<bd sd> hh").slow(2).out() // s("[<bd sd> hh]/2").out()
* s("<bd sd> hh").slow(2) // s("[<bd sd> hh]/2")
*/
_slow(factor) {
return this._fast(Fraction(1).div(factor));
@@ -811,6 +831,20 @@ export class Pattern {
return this.fmap((x) => pure(x)._fast(factor))._squeezeJoin();
}
/**
* Cuts each sample into the given number of parts, allowing you to explore a technique known as 'granular synthesis'.
* It turns a pattern of samples into a pattern of parts of samples.
* @name chop
* @memberof Pattern
* @returns Pattern
* @example
* samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' })
* s("rhodes")
* .chop(4)
* .rev() // reverse order of chops
* .loopAt(4,1) // fit sample into 4 cycles
*
*/
_chop(n) {
const slices = Array.from({ length: n }, (x, i) => i);
const slice_objects = slices.map((i) => ({ begin: i / n, end: (i + 1) / n }));
@@ -840,7 +874,7 @@ export class Pattern {
* @param {number | Pattern} cycles number of cycles to nudge left
* @returns Pattern
* @example
* "bd ~".stack("hh ~".early(.1)).s().out()
* "bd ~".stack("hh ~".early(.1)).s()
*/
_early(offset) {
offset = Fraction(offset);
@@ -855,7 +889,7 @@ export class Pattern {
* @param {number | Pattern} cycles number of cycles to nudge right
* @returns Pattern
* @example
* "bd ~".stack("hh ~".late(.1)).s().out()
* "bd ~".stack("hh ~".late(.1)).s()
*/
_late(offset) {
offset = Fraction(offset);
@@ -891,9 +925,9 @@ export class Pattern {
* @memberof Pattern
* @returns Pattern
* @example
* "c3,eb3,g3"
* note("c3,eb3,g3")
* .struct("x ~ x ~ ~ x ~ x ~ ~ ~ x ~ x ~ ~")
* .slow(4).note().out()
* .slow(4)
*/
// struct(...binary_pats) {
// // Re structure the pattern according to a binary pattern (false values are dropped)
@@ -950,7 +984,7 @@ export class Pattern {
* @param {function} func
* @returns Pattern
* @example
* "c3 eb3 g3".when("<0 1>/2", x=>x.sub(5))
* "c3 eb3 g3".when("<0 1>/2", x=>x.sub(5)).note()
*/
when(binary_pat, func) {
//binary_pat = sequence(binary_pat)
@@ -969,7 +1003,7 @@ export class Pattern {
* @param {function} func function to apply
* @returns Pattern
* @example
* "c3 eb3 g3".off(1/8, x=>x.add(7))
* "c3 eb3 g3".off(1/8, x=>x.add(7)).note()
*/
off(time_pat, func) {
return stack(this, func(this.late(time_pat)));
@@ -983,7 +1017,7 @@ export class Pattern {
* @param {function} func function to apply
* @returns Pattern
* @example
* note("c3 d3 e3 g3").every(4, x=>x.rev()).out()
* note("c3 d3 e3 g3").every(4, x=>x.rev())
*/
every(n, func) {
const pat = this;
@@ -1000,7 +1034,7 @@ export class Pattern {
* @param {function} func function to apply
* @returns Pattern
* @example
* note("c3 d3 e3 g3").every(4, x=>x.rev()).out()
* note("c3 d3 e3 g3").every(4, x=>x.rev())
*/
every(n, func) {
const pat = this;
@@ -1017,7 +1051,7 @@ export class Pattern {
* @param {function} func function to apply
* @returns Pattern
* @example
* note("c3 d3 e3 g3").every(4, x=>x.rev()).out()
* note("c3 d3 e3 g3").every(4, x=>x.rev())
*/
each(n, func) {
const pat = this;
@@ -1043,7 +1077,7 @@ export class Pattern {
* @memberof Pattern
* @returns Pattern
* @example
* "c3 d3 e3 g3".rev()
* note("c3 d3 e3 g3").rev()
*/
rev() {
const pat = this;
@@ -1094,7 +1128,7 @@ export class Pattern {
* @example
* s("hh*2").stack(
* n("c2(3,8)")
* ).out()
* )
*/
stack(...pats) {
return stack(this, ...pats);
@@ -1111,7 +1145,7 @@ export class Pattern {
* @example
* s("hh*2").seq(
* n("c2(3,8)")
* ).out()
* )
*/
seq(...pats) {
return sequence(this, ...pats);
@@ -1124,7 +1158,7 @@ export class Pattern {
* @example
* s("hh*2").cat(
* n("c2(3,8)")
* ).out()
* )
*/
cat(...pats) {
return cat(this, ...pats);
@@ -1146,7 +1180,7 @@ export class Pattern {
* @example
* "<0 2 4 6 ~ 4 ~ 2 0!3 ~!5>*4"
* .superimpose(x=>x.add(2))
* .scale('C minor').note().out()
* .scale('C minor').note()
*/
superimpose(...funcs) {
return this.stack(...funcs.map((func) => func(this)));
@@ -1171,7 +1205,7 @@ export class Pattern {
* @example
* "<0 [2 4]>"
* .echoWith(4, 1/8, (p,n) => p.add(n*2))
* .scale('C minor').note().legato(.2).out()
* .scale('C minor').note().legato(.2)
*/
_echoWith(times, time, func) {
return stack(...listRange(0, times - 1).map((i) => func(this.late(Fraction(time).mul(i)), i)));
@@ -1182,8 +1216,11 @@ export class Pattern {
* @name echo
* @memberof Pattern
* @returns Pattern
* @param {number} times how many times to repeat
* @param {number} time cycle offset between iterations
* @param {number} feedback velocity multiplicator for each iteration
* @example
* s("bd sd").echo(3, 1/6, .8).out()
* s("bd sd").echo(3, 1/6, .8)
*/
_echo(times, time, feedback) {
return this._echoWith(times, time, (pat, i) => pat.velocity(Math.pow(feedback, i)));
@@ -1195,7 +1232,7 @@ export class Pattern {
* @memberof Pattern
* @returns Pattern
* @example
* note("0 1 2 3".scale('A minor')).iter(4).out()
* note("0 1 2 3".scale('A minor')).iter(4)
*/
iter(times, back = false) {
return slowcat(...listRange(0, times - 1).map((i) => (back ? this.late(i / times) : this.early(i / times))));
@@ -1207,7 +1244,7 @@ export class Pattern {
* @memberof Pattern
* @returns Pattern
* @example
* note("0 1 2 3".scale('A minor')).iterBack(4).out()
* note("0 1 2 3".scale('A minor')).iterBack(4)
*/
iterBack(times) {
return this.iter(times, true);
@@ -1219,7 +1256,7 @@ export class Pattern {
* @memberof Pattern
* @returns Pattern
* @example
* "0 1 2 3".chunk(4, x=>x.add(7)).scale('A minor').note().out()
* "0 1 2 3".chunk(4, x=>x.add(7)).scale('A minor').note()
*/
_chunk(n, func, back = false) {
const binary = Array(n - 1).fill(false);
@@ -1234,7 +1271,7 @@ export class Pattern {
* @memberof Pattern
* @returns Pattern
* @example
* "0 1 2 3".chunkBack(4, x=>x.add(7)).scale('A minor').note().out()
* "0 1 2 3".chunkBack(4, x=>x.add(7)).scale('A minor').note()
*/
_chunkBack(n, func) {
return this._chunk(n, func, true);
@@ -1254,17 +1291,41 @@ export class Pattern {
return this.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(value)));
}
// sets hap relative duration of haps
/**
*
* Multiplies the hap duration with the given factor.
* @name legato
* @memberof Pattern
* @example
* note("c3 eb3 g3 c4").legato("<.25 .5 1 2>")
*/
_legato(value) {
return this.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(span.end.sub(span.begin).mul(value))));
}
/**
*
* Sets the velocity from 0 to 1. Is multiplied together with gain.
* @name velocity
* @example
* s("hh*8")
* .gain(".4!2 1 .4!2 1 .4 1")
* .velocity(".4 1")
*/
_velocity(velocity) {
return this._withContext((context) => ({ ...context, velocity: (context.velocity || 1) * velocity }));
}
*/
// move this to controls? (speed and unit are controls)
/**
* Makes the sample fit the given number of cycles by changing the speed.
* @name loopAt
* @memberof Pattern
* @returns Pattern
* @example
* samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' })
* s("rhodes").loopAt(4,1)
*/
_loopAt(factor, cps = 1) {
return this.speed((1 / factor) * cps)
.unit('c')
@@ -1320,9 +1381,48 @@ function _composeOp(a, b, func) {
keepif: [(a, b) => (b ? a : undefined)],
// numerical functions
/**
*
* Assumes a pattern of numbers. Adds the given number to each item in the pattern.
* @name add
* @memberof Pattern
* @example
* // Here, the triad 0, 2, 4 is shifted by different amounts
* "0 2 4".add("<0 3 4 0>").scale('C major').note()
* // Without add, the equivalent would be:
* // "<[0 2 4] [3 5 7] [4 6 8] [0 2 4]>".scale('C major').note()
* @example
* // You can also use add with notes:
* "c3 e3 g3".add("<0 5 7 0>").note()
* // Behind the scenes, the notes are converted to midi numbers:
* // "48 52 55".add("<0 5 7 0>").note()
*/
add: [(a, b) => a + b, numOrString], // support string concatenation
/**
*
* Like add, but the given numbers are subtracted.
* @name sub
* @memberof Pattern
* @example
* "0 2 4".sub("<0 1 2 3>").scale('C4 minor').note()
* // See add for more information.
*/
sub: [(a, b) => a - b, num],
/**
*
* Multiplies each number by the given factor.
* @name mul
* @memberof Pattern
* @example
* "1 1.5 [1.66, <2 2.33>]".mul(150).freq()
*/
mul: [(a, b) => a * b, num],
/**
*
* Divides each number by the given factor.
* @name div
* @memberof Pattern
*/
div: [(a, b) => a / b, num],
mod: [mod, num],
pow: [Math.pow, num],
@@ -1364,8 +1464,7 @@ function _composeOp(a, b, func) {
// avoid union, as we want to throw away the value of 'b' completely
result = pat['_op' + how](other, (a) => (b) => op(a, b));
result = result._removeUndefineds();
}
else {
} else {
result = pat['_op' + how](other, (a) => (b) => _composeOp(a, b, op));
}
return result;
@@ -1478,7 +1577,7 @@ export function reify(thing) {
*
* @return {Pattern}
* @example
* stack(g3, b3, [e4, d4]) // "g3,b3,[e4,d4]"
* stack(g3, b3, [e4, d4]).note() // "g3,b3,[e4,d4]".note()
*/
export function stack(...pats) {
// Array test here is to avoid infinite recursions..
@@ -1551,7 +1650,7 @@ export function fastcat(...pats) {
* @param {...any} items - The items to concatenate
* @return {Pattern}
* @example
* cat(e5, b4, [d5, c5]) // "<e5 b4 [d5 c5]>"
* cat(e5, b4, [d5, c5]).note() // "<e5 b4 [d5 c5]>".note()
*
*/
export function cat(...pats) {
@@ -1561,7 +1660,7 @@ export function cat(...pats) {
/** Like {@link seq}, but each step has a length, relative to the whole.
* @return {Pattern}
* @example
* timeCat([3,e3],[1, g3]) // "e3@3 g3"
* timeCat([3,e3],[1, g3]).note() // "e3@3 g3".note()
*/
export function timeCat(...timepats) {
const total = timepats.map((a) => a[0]).reduce((a, b) => a.add(b), Fraction(0));
@@ -1582,7 +1681,7 @@ export function sequence(...pats) {
/** Like **cat**, but the items are crammed into one cycle. Synonyms: fastcat, sequence
* @example
* seq(e5, b4, [d5, c5]) // "e5 b4 [d5 c5]"
* seq(e5, b4, [d5, c5]).note() // "e5 b4 [d5 c5]".note()
*
*/
export function seq(...pats) {
@@ -1655,6 +1754,7 @@ export const mul = curry((a, pat) => pat.mul(a));
export const off = curry((t, f, pat) => pat.off(t, f));
export const ply = curry((a, pat) => pat.ply(a));
export const range = curry((a, b, pat) => pat.range(a, b));
export const rangex = curry((a, b, pat) => pat.rangex(a, b));
export const range2 = curry((a, b, pat) => pat.range2(a, b));
export const rev = (pat) => pat.rev();
export const slow = curry((a, pat) => pat.slow(a));
@@ -1741,6 +1841,18 @@ Pattern.prototype.inside = function (...args) {
args = args.map(reify);
return patternify2(Pattern.prototype._inside)(...args, this);
};
Pattern.prototype.range = function (...args) {
args = args.map(reify);
return patternify2(Pattern.prototype._range)(...args, this);
};
Pattern.prototype.rangex = function (...args) {
args = args.map(reify);
return patternify2(Pattern.prototype._rangex)(...args, this);
};
Pattern.prototype.range2 = function (...args) {
args = args.map(reify);
return patternify2(Pattern.prototype._range2)(...args, this);
};
// call this after all Patter.prototype.define calls have been executed! (right before evaluate)
Pattern.prototype.bootstrap = function () {