mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-26 21:18:49 +00:00
build
This commit is contained in:
parent
df0650e48d
commit
827c983175
@ -118,21 +118,33 @@ class TimeSpan {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
class Hap {
|
class Hap {
|
||||||
constructor(whole, part, value) {
|
constructor(whole, part, value, context = {}, stateful = false) {
|
||||||
this.whole = whole;
|
this.whole = whole;
|
||||||
this.part = part;
|
this.part = part;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
|
this.context = context;
|
||||||
|
this.stateful = stateful;
|
||||||
|
if (stateful) {
|
||||||
|
assert(typeof this.value === "function", "Stateful values must be functions");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
withSpan(func) {
|
withSpan(func) {
|
||||||
const whole = this.whole ? func(this.whole) : void 0;
|
const whole = this.whole ? func(this.whole) : void 0;
|
||||||
return new Hap(whole, func(this.part), this.value);
|
return new Hap(whole, func(this.part), this.value, this.context);
|
||||||
}
|
}
|
||||||
withValue(func) {
|
withValue(func) {
|
||||||
return new Hap(this.whole, this.part, func(this.value));
|
return new Hap(this.whole, this.part, func(this.value), this.context);
|
||||||
}
|
}
|
||||||
hasOnset() {
|
hasOnset() {
|
||||||
return this.whole != void 0 && this.whole.begin.equals(this.part.begin);
|
return this.whole != void 0 && this.whole.begin.equals(this.part.begin);
|
||||||
}
|
}
|
||||||
|
resolveState(state) {
|
||||||
|
if (this.stateful && this.hasOnset()) {
|
||||||
|
const func = this.value[newState, newValue] = func(state);
|
||||||
|
return [newState, this.withValue(() => newValue)];
|
||||||
|
}
|
||||||
|
return [state, this];
|
||||||
|
}
|
||||||
spanEquals(other) {
|
spanEquals(other) {
|
||||||
return this.whole == void 0 && other.whole == void 0 || this.whole.equals(other.whole);
|
return this.whole == void 0 && other.whole == void 0 || this.whole.equals(other.whole);
|
||||||
}
|
}
|
||||||
@ -140,7 +152,25 @@ class Hap {
|
|||||||
return this.spanEquals(other) && this.part.equals(other.part) && this.value === other.value;
|
return this.spanEquals(other) && this.part.equals(other.part) && this.value === other.value;
|
||||||
}
|
}
|
||||||
show() {
|
show() {
|
||||||
return "(" + (this.whole == void 0 ? "~" : this.whole.show()) + ", " + this.part.show() + ", " + JSON.stringify(this.value?.value ?? this.value) + ")";
|
return "(" + (this.whole == void 0 ? "~" : this.whole.show()) + ", " + this.part.show() + ", " + this.value + ")";
|
||||||
|
}
|
||||||
|
setContext(context) {
|
||||||
|
return new Hap(this.whole, this.part, this.value, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class State {
|
||||||
|
constructor(span, controls = {}) {
|
||||||
|
this.span = span;
|
||||||
|
this.controls = controls;
|
||||||
|
}
|
||||||
|
setSpan(span) {
|
||||||
|
return new State(span, this.controls);
|
||||||
|
}
|
||||||
|
withSpan(func) {
|
||||||
|
return this.setSpan(func(this.span));
|
||||||
|
}
|
||||||
|
setControls(controls) {
|
||||||
|
return new State(this.span, controls);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class Pattern {
|
class Pattern {
|
||||||
@ -157,42 +187,55 @@ class Pattern {
|
|||||||
}
|
}
|
||||||
_splitQueries() {
|
_splitQueries() {
|
||||||
const pat = this;
|
const pat = this;
|
||||||
const q = (span) => flatten(span.spanCycles.map((subspan) => pat.query(subspan)));
|
const q = (state) => {
|
||||||
|
return flatten(state.span.spanCycles.map((subspan) => pat.query(state.setSpan(subspan))));
|
||||||
|
};
|
||||||
return new Pattern(q);
|
return new Pattern(q);
|
||||||
}
|
}
|
||||||
withQuerySpan(func) {
|
withQuerySpan(func) {
|
||||||
return new Pattern((span) => this.query(func(span)));
|
return new Pattern((state) => this.query(state.withSpan(func)));
|
||||||
}
|
}
|
||||||
withQueryTime(func) {
|
withQueryTime(func) {
|
||||||
return new Pattern((span) => this.query(span.withTime(func)));
|
return new Pattern((state) => this.query(state.withSpan((span) => span.withTime(func))));
|
||||||
}
|
}
|
||||||
withEventSpan(func) {
|
withEventSpan(func) {
|
||||||
return new Pattern((span) => this.query(span).map((hap) => hap.withSpan(func)));
|
return new Pattern((state) => this.query(state).map((hap) => hap.withSpan(func)));
|
||||||
}
|
}
|
||||||
withEventTime(func) {
|
withEventTime(func) {
|
||||||
return this.withEventSpan((span) => span.withTime(func));
|
return this.withEventSpan((span) => span.withTime(func));
|
||||||
}
|
}
|
||||||
_withEvents(func) {
|
_withEvents(func) {
|
||||||
return new Pattern((span) => func(this.query(span)));
|
return new Pattern((state) => func(this.query(state)));
|
||||||
|
}
|
||||||
|
_withEvent(func) {
|
||||||
|
return this._withEvents((events) => events.map(func));
|
||||||
|
}
|
||||||
|
_setContext(context) {
|
||||||
|
return this._withEvent((event) => event.setContext(context));
|
||||||
|
}
|
||||||
|
_withContext(func) {
|
||||||
|
return this._withEvent((event) => event.setContext(func(event.context)));
|
||||||
|
}
|
||||||
|
_stripContext() {
|
||||||
|
return this._withEvent((event) => event.setContext({}));
|
||||||
}
|
}
|
||||||
withLocation(location) {
|
withLocation(location) {
|
||||||
return this.fmap((value) => {
|
return this._withContext((context) => {
|
||||||
value = typeof value === "object" && !Array.isArray(value) ? value : {value};
|
const locations = (context.locations || []).concat([location]);
|
||||||
const locations = (value.locations || []).concat([location]);
|
return {...context, locations};
|
||||||
return {...value, locations};
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
withValue(func) {
|
withValue(func) {
|
||||||
return new Pattern((span) => this.query(span).map((hap) => hap.withValue(func)));
|
return new Pattern((state) => this.query(state).map((hap) => hap.withValue(func)));
|
||||||
}
|
}
|
||||||
fmap(func) {
|
fmap(func) {
|
||||||
return this.withValue(func);
|
return this.withValue(func);
|
||||||
}
|
}
|
||||||
_filterEvents(event_test) {
|
_filterEvents(event_test) {
|
||||||
return new Pattern((span) => this.query(span).filter(event_test));
|
return new Pattern((state) => this.query(state).filter(event_test));
|
||||||
}
|
}
|
||||||
_filterValues(value_test) {
|
_filterValues(value_test) {
|
||||||
return new Pattern((span) => this.query(span).filter((hap) => value_test(hap.value)));
|
return new Pattern((state) => this.query(state).filter((hap) => value_test(hap.value)));
|
||||||
}
|
}
|
||||||
_removeUndefineds() {
|
_removeUndefineds() {
|
||||||
return this._filterValues((val) => val != void 0);
|
return this._filterValues((val) => val != void 0);
|
||||||
@ -202,15 +245,15 @@ class Pattern {
|
|||||||
}
|
}
|
||||||
_appWhole(whole_func, pat_val) {
|
_appWhole(whole_func, pat_val) {
|
||||||
const pat_func = this;
|
const pat_func = this;
|
||||||
const query = function(span) {
|
const query = function(state) {
|
||||||
const event_funcs = pat_func.query(span);
|
const event_funcs = pat_func.query(state);
|
||||||
const event_vals = pat_val.query(span);
|
const event_vals = pat_val.query(state);
|
||||||
const apply = function(event_func, event_val) {
|
const apply = function(event_func, event_val) {
|
||||||
const s = event_func.part.intersection(event_val.part);
|
const s = event_func.part.intersection(event_val.part);
|
||||||
if (s == void 0) {
|
if (s == void 0) {
|
||||||
return void 0;
|
return void 0;
|
||||||
}
|
}
|
||||||
return new Hap(whole_func(event_func.whole, event_val.whole), s, event_func.value(event_val.value));
|
return new Hap(whole_func(event_func.whole, event_val.whole), s, event_func.value(event_val.value), event_val.context);
|
||||||
};
|
};
|
||||||
return flatten(event_funcs.map((event_func) => removeUndefineds(event_vals.map((event_val) => apply(event_func, event_val)))));
|
return flatten(event_funcs.map((event_func) => removeUndefineds(event_vals.map((event_val) => apply(event_func, event_val)))));
|
||||||
};
|
};
|
||||||
@ -227,15 +270,19 @@ class Pattern {
|
|||||||
}
|
}
|
||||||
appLeft(pat_val) {
|
appLeft(pat_val) {
|
||||||
const pat_func = this;
|
const pat_func = this;
|
||||||
const query = function(span) {
|
const query = function(state) {
|
||||||
const haps = [];
|
const haps = [];
|
||||||
for (const hap_func of pat_func.query(span)) {
|
for (const hap_func of pat_func.query(state)) {
|
||||||
const event_vals = pat_val.query(hap_func.part);
|
const event_vals = pat_val.query(state.setSpan(hap_func.part));
|
||||||
for (const hap_val of event_vals) {
|
for (const hap_val of event_vals) {
|
||||||
const new_whole = hap_func.whole;
|
const new_whole = hap_func.whole;
|
||||||
const new_part = hap_func.part.intersection_e(hap_val.part);
|
const new_part = hap_func.part.intersection_e(hap_val.part);
|
||||||
const new_value = hap_func.value(hap_val.value);
|
const new_value = hap_func.value(hap_val.value);
|
||||||
const hap = new Hap(new_whole, new_part, new_value);
|
const hap = new Hap(new_whole, new_part, new_value, {
|
||||||
|
...hap_val.context,
|
||||||
|
...hap_func.context,
|
||||||
|
locations: (hap_val.context.locations || []).concat(hap_func.context.locations || [])
|
||||||
|
});
|
||||||
haps.push(hap);
|
haps.push(hap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -245,15 +292,19 @@ class Pattern {
|
|||||||
}
|
}
|
||||||
appRight(pat_val) {
|
appRight(pat_val) {
|
||||||
const pat_func = this;
|
const pat_func = this;
|
||||||
const query = function(span) {
|
const query = function(state) {
|
||||||
const haps = [];
|
const haps = [];
|
||||||
for (const hap_val of pat_val.query(span)) {
|
for (const hap_val of pat_val.query(state)) {
|
||||||
const hap_funcs = pat_func.query(hap_val.part);
|
const hap_funcs = pat_func.query(state.setSpan(hap_val.part));
|
||||||
for (const hap_func of hap_funcs) {
|
for (const hap_func of hap_funcs) {
|
||||||
const new_whole = hap_val.whole;
|
const new_whole = hap_val.whole;
|
||||||
const new_part = hap_func.part.intersection_e(hap_val.part);
|
const new_part = hap_func.part.intersection_e(hap_val.part);
|
||||||
const new_value = hap_func.value(hap_val.value);
|
const new_value = hap_func.value(hap_val.value);
|
||||||
const hap = new Hap(new_whole, new_part, new_value);
|
const hap = new Hap(new_whole, new_part, new_value, {
|
||||||
|
...hap_func.context,
|
||||||
|
...hap_val.context,
|
||||||
|
locations: (hap_val.context.locations || []).concat(hap_func.context.locations || [])
|
||||||
|
});
|
||||||
haps.push(hap);
|
haps.push(hap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -261,8 +312,12 @@ class Pattern {
|
|||||||
};
|
};
|
||||||
return new Pattern(query);
|
return new Pattern(query);
|
||||||
}
|
}
|
||||||
get firstCycle() {
|
firstCycle(with_context = false) {
|
||||||
return this.query(new TimeSpan(Fraction(0), Fraction(1)));
|
var self = this;
|
||||||
|
if (!with_context) {
|
||||||
|
self = self._stripContext();
|
||||||
|
}
|
||||||
|
return self.query(new State(new TimeSpan(Fraction(0), Fraction(1))));
|
||||||
}
|
}
|
||||||
_sortEventsByPart() {
|
_sortEventsByPart() {
|
||||||
return this._withEvents((events) => events.sort((a, b) => a.part.begin.sub(b.part.begin).or(a.part.end.sub(b.part.end)).or(a.whole.begin.sub(b.whole.begin).or(a.whole.end.sub(b.whole.end)))));
|
return this._withEvents((events) => events.sort((a, b) => a.part.begin.sub(b.part.begin).or(a.part.end.sub(b.part.end)).or(a.whole.begin.sub(b.whole.begin).or(a.whole.end.sub(b.whole.end)))));
|
||||||
@ -287,14 +342,18 @@ class Pattern {
|
|||||||
}
|
}
|
||||||
_bindWhole(choose_whole, func) {
|
_bindWhole(choose_whole, func) {
|
||||||
const pat_val = this;
|
const pat_val = this;
|
||||||
const query = function(span) {
|
const query = function(state) {
|
||||||
const withWhole = function(a, b) {
|
const withWhole = function(a, b) {
|
||||||
return new Hap(choose_whole(a.whole, b.whole), b.part, b.value);
|
return new Hap(choose_whole(a.whole, b.whole), b.part, b.value, {
|
||||||
|
...a.context,
|
||||||
|
...b.context,
|
||||||
|
locations: (a.context.locations || []).concat(b.context.locations || [])
|
||||||
|
});
|
||||||
};
|
};
|
||||||
const match = function(a) {
|
const match = function(a) {
|
||||||
return func(a.value).query(a.part).map((b) => withWhole(a, b));
|
return func(a.value).query(state.setSpan(a.part)).map((b) => withWhole(a, b));
|
||||||
};
|
};
|
||||||
return flatten(pat_val.query(span).map((a) => match(a)));
|
return flatten(pat_val.query(state).map((a) => match(a)));
|
||||||
};
|
};
|
||||||
return new Pattern(query);
|
return new Pattern(query);
|
||||||
}
|
}
|
||||||
@ -409,7 +468,8 @@ class Pattern {
|
|||||||
}
|
}
|
||||||
rev() {
|
rev() {
|
||||||
const pat = this;
|
const pat = this;
|
||||||
const query = function(span) {
|
const query = function(state) {
|
||||||
|
const span = state.span;
|
||||||
const cycle = span.begin.sam();
|
const cycle = span.begin.sam();
|
||||||
const next_cycle = span.begin.nextSam();
|
const next_cycle = span.begin.nextSam();
|
||||||
const reflect = function(to_reflect) {
|
const reflect = function(to_reflect) {
|
||||||
@ -419,7 +479,7 @@ class Pattern {
|
|||||||
reflected.end = tmp;
|
reflected.end = tmp;
|
||||||
return reflected;
|
return reflected;
|
||||||
};
|
};
|
||||||
const haps = pat.query(reflect(span));
|
const haps = pat.query(state.setSpan(reflect(span)));
|
||||||
return haps.map((hap) => hap.withSpan(reflect));
|
return haps.map((hap) => hap.withSpan(reflect));
|
||||||
};
|
};
|
||||||
return new Pattern(query)._splitQueries();
|
return new Pattern(query)._splitQueries();
|
||||||
@ -463,8 +523,8 @@ Pattern.prototype.patternified = ["apply", "fast", "slow", "early", "late"];
|
|||||||
Pattern.prototype.factories = {pure, stack, slowcat, fastcat, cat, timeCat, sequence, polymeter, pm, polyrhythm, pr};
|
Pattern.prototype.factories = {pure, stack, slowcat, fastcat, cat, timeCat, sequence, polymeter, pm, polyrhythm, pr};
|
||||||
const silence = new Pattern((_) => []);
|
const silence = new Pattern((_) => []);
|
||||||
function pure(value) {
|
function pure(value) {
|
||||||
function query(span) {
|
function query(state) {
|
||||||
return 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));
|
||||||
}
|
}
|
||||||
return new Pattern(query);
|
return new Pattern(query);
|
||||||
}
|
}
|
||||||
@ -472,7 +532,7 @@ function steady(value) {
|
|||||||
return new Pattern((span) => Hap(void 0, span, value));
|
return new Pattern((span) => Hap(void 0, span, value));
|
||||||
}
|
}
|
||||||
export const signal = (func) => {
|
export const signal = (func) => {
|
||||||
const query = (span) => [new Hap(void 0, span, func(span.midpoint()))];
|
const query = (state) => [new Hap(void 0, state.span, func(state.span.midpoint()))];
|
||||||
return new Pattern(query);
|
return new Pattern(query);
|
||||||
};
|
};
|
||||||
const _toBipolar = (pat) => pat.fmap((x) => x * 2 - 1);
|
const _toBipolar = (pat) => pat.fmap((x) => x * 2 - 1);
|
||||||
@ -497,28 +557,29 @@ function reify(thing) {
|
|||||||
}
|
}
|
||||||
function stack(...pats) {
|
function stack(...pats) {
|
||||||
const reified = pats.map((pat) => reify(pat));
|
const reified = pats.map((pat) => reify(pat));
|
||||||
const query = (span) => flatten(reified.map((pat) => pat.query(span)));
|
const query = (state) => flatten(reified.map((pat) => pat.query(state)));
|
||||||
return new Pattern(query);
|
return new Pattern(query);
|
||||||
}
|
}
|
||||||
function slowcat(...pats) {
|
function slowcat(...pats) {
|
||||||
pats = pats.map(reify);
|
pats = pats.map(reify);
|
||||||
const query = function(span) {
|
const query = function(state) {
|
||||||
|
const span = state.span;
|
||||||
const pat_n = Math.floor(span.begin) % pats.length;
|
const pat_n = Math.floor(span.begin) % pats.length;
|
||||||
const pat = pats[pat_n];
|
const pat = pats[pat_n];
|
||||||
if (!pat) {
|
if (!pat) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
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(span.withTime((t) => t.sub(offset)));
|
return pat.withEventTime((t) => t.add(offset)).query(state.setSpan(span.withTime((t) => t.sub(offset))));
|
||||||
};
|
};
|
||||||
return new Pattern(query)._splitQueries();
|
return new Pattern(query)._splitQueries();
|
||||||
}
|
}
|
||||||
function slowcatPrime(...pats) {
|
function slowcatPrime(...pats) {
|
||||||
pats = pats.map(reify);
|
pats = pats.map(reify);
|
||||||
const query = function(span) {
|
const query = function(state) {
|
||||||
const pat_n = Math.floor(span.begin) % pats.length;
|
const pat_n = Math.floor(state.span.begin) % pats.length;
|
||||||
const pat = pats[pat_n];
|
const pat = pats[pat_n];
|
||||||
return pat.query(span);
|
return pat.query(state);
|
||||||
};
|
};
|
||||||
return new Pattern(query)._splitQueries();
|
return new Pattern(query)._splitQueries();
|
||||||
}
|
}
|
||||||
@ -638,9 +699,8 @@ Pattern.prototype.bootstrap = () => {
|
|||||||
return bootstrapped;
|
return bootstrapped;
|
||||||
};
|
};
|
||||||
function withLocationOffset(pat, offset) {
|
function withLocationOffset(pat, offset) {
|
||||||
return pat.fmap((value) => {
|
return pat._withContext((context) => {
|
||||||
value = typeof value === "object" && !Array.isArray(value) ? value : {value};
|
let locations = context.locations || [];
|
||||||
let locations = value.locations || [];
|
|
||||||
locations = locations.map(({start, end}) => {
|
locations = locations.map(({start, end}) => {
|
||||||
const colOffset = start.line === 1 ? offset.start.column : 0;
|
const colOffset = start.line === 1 ? offset.start.column : 0;
|
||||||
return {
|
return {
|
||||||
@ -656,7 +716,7 @@ function withLocationOffset(pat, offset) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return {...value, locations};
|
return {...context, locations};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
export {
|
export {
|
||||||
|
|||||||
538
docs/_snowpack/pkg/@tonejs/piano.js
Normal file
538
docs/_snowpack/pkg/@tonejs/piano.js
Normal file
@ -0,0 +1,538 @@
|
|||||||
|
import { T as ToneAudioNode, V as Volume, F as Frequency, M as Midi, S as Sampler, a as ToneAudioBuffers, b as ToneBufferSource, o as optionsFromArguments, G as Gain, i as isString } from '../common/index-b6fc655f.js';
|
||||||
|
import '../common/webmidi.min-97732fd4.js';
|
||||||
|
import '../common/_commonjsHelpers-8c19dec8.js';
|
||||||
|
|
||||||
|
var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Base class for the other components
|
||||||
|
*/
|
||||||
|
class PianoComponent extends ToneAudioNode {
|
||||||
|
constructor(options) {
|
||||||
|
super(options);
|
||||||
|
this.name = 'PianoComponent';
|
||||||
|
this.input = undefined;
|
||||||
|
this.output = new Volume({ context: this.context });
|
||||||
|
/**
|
||||||
|
* If the component is enabled or not
|
||||||
|
*/
|
||||||
|
this._enabled = false;
|
||||||
|
/**
|
||||||
|
* The volume output of the component
|
||||||
|
*/
|
||||||
|
this.volume = this.output.volume;
|
||||||
|
/**
|
||||||
|
* Boolean indication of if the component is loaded or not
|
||||||
|
*/
|
||||||
|
this._loaded = false;
|
||||||
|
this.volume.value = options.volume;
|
||||||
|
this._enabled = options.enabled;
|
||||||
|
this.samples = options.samples;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* If the samples are loaded or not
|
||||||
|
*/
|
||||||
|
get loaded() {
|
||||||
|
return this._loaded;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Load the samples
|
||||||
|
*/
|
||||||
|
load() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
if (this._enabled) {
|
||||||
|
yield this._internalLoad();
|
||||||
|
this._loaded = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// import * as Tone from '../node_modules/tone/Tone'
|
||||||
|
function midiToNote(midi) {
|
||||||
|
const frequency = Frequency(midi, 'midi');
|
||||||
|
const ret = frequency.toNote();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
function randomBetween(low, high) {
|
||||||
|
return Math.random() * (high - low) + low;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getReleasesUrl(midi) {
|
||||||
|
return `rel${midi - 20}.[mp3|ogg]`;
|
||||||
|
}
|
||||||
|
function getHarmonicsUrl(midi) {
|
||||||
|
return `harmS${midiToNote(midi).replace('#', 's')}.[mp3|ogg]`;
|
||||||
|
}
|
||||||
|
function getNotesUrl(midi, vel) {
|
||||||
|
return `${midiToNote(midi).replace('#', 's')}v${vel}.[mp3|ogg]`;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Maps velocity depths to Salamander velocities
|
||||||
|
*/
|
||||||
|
const velocitiesMap = {
|
||||||
|
1: [8],
|
||||||
|
2: [6, 12],
|
||||||
|
3: [1, 7, 15],
|
||||||
|
4: [1, 5, 10, 15],
|
||||||
|
5: [1, 4, 8, 12, 16],
|
||||||
|
6: [1, 3, 7, 10, 13, 16],
|
||||||
|
7: [1, 3, 6, 9, 11, 13, 16],
|
||||||
|
8: [1, 3, 5, 7, 9, 11, 13, 16],
|
||||||
|
9: [1, 3, 5, 7, 9, 11, 13, 15, 16],
|
||||||
|
10: [1, 2, 3, 5, 7, 9, 11, 13, 15, 16],
|
||||||
|
11: [1, 2, 3, 5, 7, 9, 11, 13, 14, 15, 16],
|
||||||
|
12: [1, 2, 3, 4, 5, 7, 9, 11, 13, 14, 15, 16],
|
||||||
|
13: [1, 2, 3, 4, 5, 7, 9, 11, 12, 13, 14, 15, 16],
|
||||||
|
14: [1, 2, 3, 4, 5, 6, 7, 9, 11, 12, 13, 14, 15, 16],
|
||||||
|
15: [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 16],
|
||||||
|
16: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* All the notes of audio samples
|
||||||
|
*/
|
||||||
|
const allNotes = [
|
||||||
|
21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54,
|
||||||
|
57, 60, 63, 66, 69, 72, 75, 78, 81, 84,
|
||||||
|
87, 90, 93, 96, 99, 102, 105, 108
|
||||||
|
];
|
||||||
|
function getNotesInRange(min, max) {
|
||||||
|
return allNotes.filter(note => min <= note && note <= max);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* All the notes of audio samples
|
||||||
|
*/
|
||||||
|
const harmonics = [21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87];
|
||||||
|
function getHarmonicsInRange(min, max) {
|
||||||
|
return harmonics.filter(note => min <= note && note <= max);
|
||||||
|
}
|
||||||
|
function inHarmonicsRange(note) {
|
||||||
|
return harmonics[0] <= note && note <= harmonics[harmonics.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
class Harmonics extends PianoComponent {
|
||||||
|
constructor(options) {
|
||||||
|
super(options);
|
||||||
|
this._urls = {};
|
||||||
|
const notes = getHarmonicsInRange(options.minNote, options.maxNote);
|
||||||
|
for (const n of notes) {
|
||||||
|
this._urls[n] = getHarmonicsUrl(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
triggerAttack(note, time, velocity) {
|
||||||
|
if (this._enabled && inHarmonicsRange(note)) {
|
||||||
|
this._sampler.triggerAttack(Midi(note).toNote(), time, velocity * randomBetween(0.5, 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_internalLoad() {
|
||||||
|
return new Promise(onload => {
|
||||||
|
this._sampler = new Sampler({
|
||||||
|
baseUrl: this.samples,
|
||||||
|
onload,
|
||||||
|
urls: this._urls,
|
||||||
|
}).connect(this.output);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Keybed extends PianoComponent {
|
||||||
|
constructor(options) {
|
||||||
|
super(options);
|
||||||
|
/**
|
||||||
|
* The urls to load
|
||||||
|
*/
|
||||||
|
this._urls = {};
|
||||||
|
for (let i = options.minNote; i <= options.maxNote; i++) {
|
||||||
|
this._urls[i] = getReleasesUrl(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_internalLoad() {
|
||||||
|
return new Promise(success => {
|
||||||
|
this._buffers = new ToneAudioBuffers(this._urls, success, this.samples);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
start(note, time, velocity) {
|
||||||
|
if (this._enabled && this._buffers.has(note)) {
|
||||||
|
const source = new ToneBufferSource({
|
||||||
|
url: this._buffers.get(note),
|
||||||
|
context: this.context,
|
||||||
|
}).connect(this.output);
|
||||||
|
// randomize the velocity slightly
|
||||||
|
source.start(time, 0, undefined, 0.015 * velocity * randomBetween(0.5, 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Pedal extends PianoComponent {
|
||||||
|
constructor(options) {
|
||||||
|
super(options);
|
||||||
|
this._downTime = Infinity;
|
||||||
|
this._currentSound = null;
|
||||||
|
this._downTime = Infinity;
|
||||||
|
}
|
||||||
|
_internalLoad() {
|
||||||
|
return new Promise((success) => {
|
||||||
|
this._buffers = new ToneAudioBuffers({
|
||||||
|
down1: 'pedalD1.mp3',
|
||||||
|
down2: 'pedalD2.mp3',
|
||||||
|
up1: 'pedalU1.mp3',
|
||||||
|
up2: 'pedalU2.mp3',
|
||||||
|
}, success, this.samples);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Squash the current playing sound
|
||||||
|
*/
|
||||||
|
_squash(time) {
|
||||||
|
if (this._currentSound && this._currentSound.state !== 'stopped') {
|
||||||
|
this._currentSound.stop(time);
|
||||||
|
}
|
||||||
|
this._currentSound = null;
|
||||||
|
}
|
||||||
|
_playSample(time, dir) {
|
||||||
|
if (this._enabled) {
|
||||||
|
this._currentSound = new ToneBufferSource({
|
||||||
|
url: this._buffers.get(`${dir}${Math.random() > 0.5 ? 1 : 2}`),
|
||||||
|
context: this.context,
|
||||||
|
curve: 'exponential',
|
||||||
|
fadeIn: 0.05,
|
||||||
|
fadeOut: 0.1,
|
||||||
|
}).connect(this.output);
|
||||||
|
this._currentSound.start(time, randomBetween(0, 0.01), undefined, 0.1 * randomBetween(0.5, 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Put the pedal down
|
||||||
|
*/
|
||||||
|
down(time) {
|
||||||
|
this._squash(time);
|
||||||
|
this._downTime = time;
|
||||||
|
this._playSample(time, 'down');
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Put the pedal up
|
||||||
|
*/
|
||||||
|
up(time) {
|
||||||
|
this._squash(time);
|
||||||
|
this._downTime = Infinity;
|
||||||
|
this._playSample(time, 'up');
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Indicates if the pedal is down at the given time
|
||||||
|
*/
|
||||||
|
isDown(time) {
|
||||||
|
return time > this._downTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single velocity of strings
|
||||||
|
*/
|
||||||
|
class PianoString extends ToneAudioNode {
|
||||||
|
constructor(options) {
|
||||||
|
super(options);
|
||||||
|
this.name = 'PianoString';
|
||||||
|
this._urls = {};
|
||||||
|
// create the urls
|
||||||
|
options.notes.forEach(note => this._urls[note] = getNotesUrl(note, options.velocity));
|
||||||
|
this.samples = options.samples;
|
||||||
|
}
|
||||||
|
load() {
|
||||||
|
return new Promise(onload => {
|
||||||
|
this._sampler = this.output = new Sampler({
|
||||||
|
attack: 0,
|
||||||
|
baseUrl: this.samples,
|
||||||
|
curve: 'exponential',
|
||||||
|
onload,
|
||||||
|
release: 0.4,
|
||||||
|
urls: this._urls,
|
||||||
|
volume: 3,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
triggerAttack(note, time, velocity) {
|
||||||
|
this._sampler.triggerAttack(note, time, velocity);
|
||||||
|
}
|
||||||
|
triggerRelease(note, time) {
|
||||||
|
this._sampler.triggerRelease(note, time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var __awaiter$1 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Manages all of the hammered string sounds
|
||||||
|
*/
|
||||||
|
class PianoStrings extends PianoComponent {
|
||||||
|
constructor(options) {
|
||||||
|
super(options);
|
||||||
|
const notes = getNotesInRange(options.minNote, options.maxNote);
|
||||||
|
const velocities = velocitiesMap[options.velocities].slice();
|
||||||
|
this._strings = velocities.map(velocity => {
|
||||||
|
const string = new PianoString(Object.assign(options, {
|
||||||
|
notes, velocity,
|
||||||
|
}));
|
||||||
|
return string;
|
||||||
|
});
|
||||||
|
this._activeNotes = new Map();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Scale a value between a given range
|
||||||
|
*/
|
||||||
|
scale(val, inMin, inMax, outMin, outMax) {
|
||||||
|
return ((val - inMin) / (inMax - inMin)) * (outMax - outMin) + outMin;
|
||||||
|
}
|
||||||
|
triggerAttack(note, time, velocity) {
|
||||||
|
const scaledVel = this.scale(velocity, 0, 1, -0.5, this._strings.length - 0.51);
|
||||||
|
const stringIndex = Math.max(Math.round(scaledVel), 0);
|
||||||
|
let gain = 1 + scaledVel - stringIndex;
|
||||||
|
if (this._strings.length === 1) {
|
||||||
|
gain = velocity;
|
||||||
|
}
|
||||||
|
const sampler = this._strings[stringIndex];
|
||||||
|
if (this._activeNotes.has(note)) {
|
||||||
|
this.triggerRelease(note, time);
|
||||||
|
}
|
||||||
|
this._activeNotes.set(note, sampler);
|
||||||
|
sampler.triggerAttack(Midi(note).toNote(), time, gain);
|
||||||
|
}
|
||||||
|
triggerRelease(note, time) {
|
||||||
|
// trigger the release of all of the notes at that velociy
|
||||||
|
if (this._activeNotes.has(note)) {
|
||||||
|
this._activeNotes.get(note).triggerRelease(Midi(note).toNote(), time);
|
||||||
|
this._activeNotes.delete(note);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_internalLoad() {
|
||||||
|
return __awaiter$1(this, void 0, void 0, function* () {
|
||||||
|
yield Promise.all(this._strings.map((s) => __awaiter$1(this, void 0, void 0, function* () {
|
||||||
|
yield s.load();
|
||||||
|
s.connect(this.output);
|
||||||
|
})));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var __awaiter$2 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* The Piano
|
||||||
|
*/
|
||||||
|
class Piano extends ToneAudioNode {
|
||||||
|
constructor() {
|
||||||
|
super(optionsFromArguments(Piano.getDefaults(), arguments));
|
||||||
|
this.name = 'Piano';
|
||||||
|
this.input = undefined;
|
||||||
|
this.output = new Gain({ context: this.context });
|
||||||
|
/**
|
||||||
|
* The currently held notes
|
||||||
|
*/
|
||||||
|
this._heldNotes = new Map();
|
||||||
|
/**
|
||||||
|
* If it's loaded or not
|
||||||
|
*/
|
||||||
|
this._loaded = false;
|
||||||
|
const options = optionsFromArguments(Piano.getDefaults(), arguments);
|
||||||
|
// make sure it ends with a /
|
||||||
|
if (!options.url.endsWith('/')) {
|
||||||
|
options.url += '/';
|
||||||
|
}
|
||||||
|
this.maxPolyphony = options.maxPolyphony;
|
||||||
|
this._heldNotes = new Map();
|
||||||
|
this._sustainedNotes = new Map();
|
||||||
|
this._strings = new PianoStrings(Object.assign({}, options, {
|
||||||
|
enabled: true,
|
||||||
|
samples: options.url,
|
||||||
|
volume: options.volume.strings,
|
||||||
|
})).connect(this.output);
|
||||||
|
this.strings = this._strings.volume;
|
||||||
|
this._pedal = new Pedal(Object.assign({}, options, {
|
||||||
|
enabled: options.pedal,
|
||||||
|
samples: options.url,
|
||||||
|
volume: options.volume.pedal,
|
||||||
|
})).connect(this.output);
|
||||||
|
this.pedal = this._pedal.volume;
|
||||||
|
this._keybed = new Keybed(Object.assign({}, options, {
|
||||||
|
enabled: options.release,
|
||||||
|
samples: options.url,
|
||||||
|
volume: options.volume.keybed,
|
||||||
|
})).connect(this.output);
|
||||||
|
this.keybed = this._keybed.volume;
|
||||||
|
this._harmonics = new Harmonics(Object.assign({}, options, {
|
||||||
|
enabled: options.release,
|
||||||
|
samples: options.url,
|
||||||
|
volume: options.volume.harmonics,
|
||||||
|
})).connect(this.output);
|
||||||
|
this.harmonics = this._harmonics.volume;
|
||||||
|
}
|
||||||
|
static getDefaults() {
|
||||||
|
return Object.assign(ToneAudioNode.getDefaults(), {
|
||||||
|
maxNote: 108,
|
||||||
|
minNote: 21,
|
||||||
|
pedal: true,
|
||||||
|
release: false,
|
||||||
|
url: 'https://tambien.github.io/Piano/audio/',
|
||||||
|
velocities: 1,
|
||||||
|
maxPolyphony: 32,
|
||||||
|
volume: {
|
||||||
|
harmonics: 0,
|
||||||
|
keybed: 0,
|
||||||
|
pedal: 0,
|
||||||
|
strings: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Load all the samples
|
||||||
|
*/
|
||||||
|
load() {
|
||||||
|
return __awaiter$2(this, void 0, void 0, function* () {
|
||||||
|
yield Promise.all([
|
||||||
|
this._strings.load(),
|
||||||
|
this._pedal.load(),
|
||||||
|
this._keybed.load(),
|
||||||
|
this._harmonics.load(),
|
||||||
|
]);
|
||||||
|
this._loaded = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* If all the samples are loaded or not
|
||||||
|
*/
|
||||||
|
get loaded() {
|
||||||
|
return this._loaded;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Put the pedal down at the given time. Causes subsequent
|
||||||
|
* notes and currently held notes to sustain.
|
||||||
|
*/
|
||||||
|
pedalDown({ time = this.immediate() } = {}) {
|
||||||
|
if (this.loaded) {
|
||||||
|
time = this.toSeconds(time);
|
||||||
|
if (!this._pedal.isDown(time)) {
|
||||||
|
this._pedal.down(time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Put the pedal up. Dampens sustained notes
|
||||||
|
*/
|
||||||
|
pedalUp({ time = this.immediate() } = {}) {
|
||||||
|
if (this.loaded) {
|
||||||
|
const seconds = this.toSeconds(time);
|
||||||
|
if (this._pedal.isDown(seconds)) {
|
||||||
|
this._pedal.up(seconds);
|
||||||
|
// dampen each of the notes
|
||||||
|
this._sustainedNotes.forEach((t, note) => {
|
||||||
|
if (!this._heldNotes.has(note)) {
|
||||||
|
this._strings.triggerRelease(note, seconds);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._sustainedNotes.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Play a note.
|
||||||
|
* @param note The note to play. If it is a number, it is assumed to be MIDI
|
||||||
|
* @param velocity The velocity to play the note
|
||||||
|
* @param time The time of the event
|
||||||
|
*/
|
||||||
|
keyDown({ note, midi, time = this.immediate(), velocity = 0.8 }) {
|
||||||
|
if (this.loaded && this.maxPolyphony > this._heldNotes.size + this._sustainedNotes.size) {
|
||||||
|
time = this.toSeconds(time);
|
||||||
|
if (isString(note)) {
|
||||||
|
midi = Math.round(Midi(note).toMidi());
|
||||||
|
}
|
||||||
|
if (!this._heldNotes.has(midi)) {
|
||||||
|
// record the start time and velocity
|
||||||
|
this._heldNotes.set(midi, { time, velocity });
|
||||||
|
this._strings.triggerAttack(midi, time, velocity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.warn('samples not loaded');
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Release a held note.
|
||||||
|
*/
|
||||||
|
keyUp({ note, midi, time = this.immediate(), velocity = 0.8 }) {
|
||||||
|
if (this.loaded) {
|
||||||
|
time = this.toSeconds(time);
|
||||||
|
if (isString(note)) {
|
||||||
|
midi = Math.round(Midi(note).toMidi());
|
||||||
|
}
|
||||||
|
if (this._heldNotes.has(midi)) {
|
||||||
|
const prevNote = this._heldNotes.get(midi);
|
||||||
|
this._heldNotes.delete(midi);
|
||||||
|
// compute the release velocity
|
||||||
|
const holdTime = Math.pow(Math.max(time - prevNote.time, 0.1), 0.7);
|
||||||
|
const prevVel = prevNote.velocity;
|
||||||
|
let dampenGain = (3 / holdTime) * prevVel * velocity;
|
||||||
|
dampenGain = Math.max(dampenGain, 0.4);
|
||||||
|
dampenGain = Math.min(dampenGain, 4);
|
||||||
|
if (this._pedal.isDown(time)) {
|
||||||
|
if (!this._sustainedNotes.has(midi)) {
|
||||||
|
this._sustainedNotes.set(midi, time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// release the string sound
|
||||||
|
this._strings.triggerRelease(midi, time);
|
||||||
|
// trigger the harmonics sound
|
||||||
|
this._harmonics.triggerAttack(midi, time, dampenGain);
|
||||||
|
}
|
||||||
|
// trigger the keybed release sound
|
||||||
|
this._keybed.start(midi, time, velocity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
stopAll() {
|
||||||
|
this.pedalUp();
|
||||||
|
this._heldNotes.forEach((_, midi) => {
|
||||||
|
this.keyUp({ midi });
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var __awaiter$3 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export { Piano };
|
||||||
19651
docs/_snowpack/pkg/common/index-b6fc655f.js
Normal file
19651
docs/_snowpack/pkg/common/index-b6fc655f.js
Normal file
File diff suppressed because it is too large
Load Diff
37
docs/_snowpack/pkg/common/webmidi.min-97732fd4.js
Normal file
37
docs/_snowpack/pkg/common/webmidi.min-97732fd4.js
Normal file
File diff suppressed because one or more lines are too long
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"imports": {
|
"imports": {
|
||||||
"@tonaljs/tonal": "./@tonaljs/tonal.js",
|
"@tonaljs/tonal": "./@tonaljs/tonal.js",
|
||||||
|
"@tonejs/piano": "./@tonejs/piano.js",
|
||||||
"chord-voicings": "./chord-voicings.js",
|
"chord-voicings": "./chord-voicings.js",
|
||||||
"codemirror/lib/codemirror.css": "./codemirror/lib/codemirror.css",
|
"codemirror/lib/codemirror.css": "./codemirror/lib/codemirror.css",
|
||||||
"codemirror/mode/javascript/javascript.js": "./codemirror/mode/javascript/javascript.js",
|
"codemirror/mode/javascript/javascript.js": "./codemirror/mode/javascript/javascript.js",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
14
docs/dist/App.js
vendored
14
docs/dist/App.js
vendored
@ -14,7 +14,6 @@ try {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn("failed to decode", err);
|
console.warn("failed to decode", err);
|
||||||
}
|
}
|
||||||
Tone.setContext(new Tone.Context({latencyHint: "playback", lookAhead: 1}));
|
|
||||||
const defaultSynth = new Tone.PolySynth().chain(new Tone.Gain(0.5), Tone.getDestination());
|
const defaultSynth = new Tone.PolySynth().chain(new Tone.Gain(0.5), Tone.getDestination());
|
||||||
defaultSynth.set({
|
defaultSynth.set({
|
||||||
oscillator: {type: "triangle"},
|
oscillator: {type: "triangle"},
|
||||||
@ -30,7 +29,7 @@ function getRandomTune() {
|
|||||||
const randomTune = getRandomTune();
|
const randomTune = getRandomTune();
|
||||||
function App() {
|
function App() {
|
||||||
const [editor, setEditor] = useState();
|
const [editor, setEditor] = useState();
|
||||||
const {setCode, setPattern, error, code, cycle, dirty, log, togglePlay, activateCode, pattern, pushLog} = useRepl({
|
const {setCode, setPattern, error, code, cycle, dirty, log, togglePlay, activateCode, pattern, pushLog, pending} = useRepl({
|
||||||
tune: decoded || randomTune,
|
tune: decoded || randomTune,
|
||||||
defaultSynth,
|
defaultSynth,
|
||||||
onDraw: useCallback(markEvent(editor), [editor])
|
onDraw: useCallback(markEvent(editor), [editor])
|
||||||
@ -40,12 +39,11 @@ function App() {
|
|||||||
logBox.current.scrollTop = logBox.current?.scrollHeight;
|
logBox.current.scrollTop = logBox.current?.scrollHeight;
|
||||||
}, [log]);
|
}, [log]);
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const handleKeyPress = (e) => {
|
const handleKeyPress = async (e) => {
|
||||||
if (e.ctrlKey || e.altKey) {
|
if (e.ctrlKey || e.altKey) {
|
||||||
switch (e.code) {
|
switch (e.code) {
|
||||||
case "Enter":
|
case "Enter":
|
||||||
activateCode();
|
await activateCode();
|
||||||
!cycle.started && cycle.start();
|
|
||||||
break;
|
break;
|
||||||
case "Period":
|
case "Period":
|
||||||
cycle.stop();
|
cycle.stop();
|
||||||
@ -81,11 +79,11 @@ function App() {
|
|||||||
}, "Strudel REPL")), /* @__PURE__ */ React.createElement("div", {
|
}, "Strudel REPL")), /* @__PURE__ */ React.createElement("div", {
|
||||||
className: "flex space-x-4"
|
className: "flex space-x-4"
|
||||||
}, /* @__PURE__ */ React.createElement("button", {
|
}, /* @__PURE__ */ React.createElement("button", {
|
||||||
onClick: () => {
|
onClick: async () => {
|
||||||
const _code = getRandomTune();
|
const _code = getRandomTune();
|
||||||
console.log("tune", _code);
|
console.log("tune", _code);
|
||||||
setCode(_code);
|
setCode(_code);
|
||||||
const parsed = evaluate(_code);
|
const parsed = await evaluate(_code);
|
||||||
setPattern(parsed.pattern);
|
setPattern(parsed.pattern);
|
||||||
}
|
}
|
||||||
}, "🎲 random tune"), /* @__PURE__ */ React.createElement("button", null, /* @__PURE__ */ React.createElement("a", {
|
}, "🎲 random tune"), /* @__PURE__ */ React.createElement("button", null, /* @__PURE__ */ React.createElement("a", {
|
||||||
@ -116,7 +114,7 @@ function App() {
|
|||||||
}, error?.message || "unknown error")), /* @__PURE__ */ React.createElement("button", {
|
}, error?.message || "unknown error")), /* @__PURE__ */ React.createElement("button", {
|
||||||
className: "flex-none w-full border border-gray-700 p-2 bg-slate-700 hover:bg-slate-500",
|
className: "flex-none w-full border border-gray-700 p-2 bg-slate-700 hover:bg-slate-500",
|
||||||
onClick: () => togglePlay()
|
onClick: () => togglePlay()
|
||||||
}, cycle.started ? "pause" : "play"), /* @__PURE__ */ React.createElement("textarea", {
|
}, !pending ? /* @__PURE__ */ React.createElement(React.Fragment, null, cycle.started ? "pause" : "play") : /* @__PURE__ */ React.createElement(React.Fragment, null, "loading...")), /* @__PURE__ */ React.createElement("textarea", {
|
||||||
className: "grow bg-[#283237] border-0 text-xs min-h-[200px]",
|
className: "grow bg-[#283237] border-0 text-xs min-h-[200px]",
|
||||||
value: log,
|
value: log,
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
|
|||||||
2
docs/dist/CodeMirror.js
vendored
2
docs/dist/CodeMirror.js
vendored
@ -20,7 +20,7 @@ export default function CodeMirror({value, onChange, options, editorDidMount}) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
export const markEvent = (editor) => (time, event) => {
|
export const markEvent = (editor) => (time, event) => {
|
||||||
const locs = event.value.locations;
|
const locs = event.context.locations;
|
||||||
if (!locs || !editor) {
|
if (!locs || !editor) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
4
docs/dist/evaluate.js
vendored
4
docs/dist/evaluate.js
vendored
@ -20,9 +20,9 @@ function hackLiteral(literal, names, func) {
|
|||||||
hackLiteral(String, ["mini", "m"], bootstrapped.mini);
|
hackLiteral(String, ["mini", "m"], bootstrapped.mini);
|
||||||
hackLiteral(String, ["pure", "p"], bootstrapped.pure);
|
hackLiteral(String, ["pure", "p"], bootstrapped.pure);
|
||||||
Object.assign(globalThis, bootstrapped, Tone, toneHelpers);
|
Object.assign(globalThis, bootstrapped, Tone, toneHelpers);
|
||||||
export const evaluate = (code) => {
|
export const evaluate = async (code) => {
|
||||||
const shapeshifted = shapeshifter(code);
|
const shapeshifted = shapeshifter(code);
|
||||||
let evaluated = eval(shapeshifted);
|
let evaluated = await eval(shapeshifted);
|
||||||
if (typeof evaluated === "function") {
|
if (typeof evaluated === "function") {
|
||||||
evaluated = evaluated();
|
evaluated = evaluated();
|
||||||
}
|
}
|
||||||
|
|||||||
3
docs/dist/parse.js
vendored
3
docs/dist/parse.js
vendored
@ -90,7 +90,8 @@ export function patternifyAST(ast) {
|
|||||||
return ast.source_;
|
return ast.source_;
|
||||||
}
|
}
|
||||||
const {start, end} = ast.location_;
|
const {start, end} = ast.location_;
|
||||||
return pure(ast.source_).withLocation({start, end});
|
const value = !isNaN(Number(ast.source_)) ? Number(ast.source_) : ast.source_;
|
||||||
|
return pure(value).withLocation({start, end});
|
||||||
}
|
}
|
||||||
return patternifyAST(ast.source_);
|
return patternifyAST(ast.source_);
|
||||||
case "stretch":
|
case "stretch":
|
||||||
|
|||||||
13
docs/dist/shapeshifter.js
vendored
13
docs/dist/shapeshifter.js
vendored
@ -92,15 +92,16 @@ export default (code) => {
|
|||||||
// add to location to pure(x) calls
|
// add to location to pure(x) calls
|
||||||
if (node.type === 'CallExpression' && node.callee.name === 'pure') {
|
if (node.type === 'CallExpression' && node.callee.name === 'pure') {
|
||||||
const literal = node.arguments[0];
|
const literal = node.arguments[0];
|
||||||
const value = literal[{ LiteralNumericExpression: 'value', LiteralStringExpression: 'name' }[literal.type]];
|
// const value = literal[{ LiteralNumericExpression: 'value', LiteralStringExpression: 'name' }[literal.type]];
|
||||||
return reifyWithLocation(value + '', node.arguments[0], ast.locations, artificialNodes);
|
// console.log('value',value);
|
||||||
|
return reifyWithLocation(literal, node.arguments[0], ast.locations, artificialNodes);
|
||||||
}
|
}
|
||||||
// replace pseudo note variables
|
// replace pseudo note variables
|
||||||
if (node.type === 'IdentifierExpression') {
|
if (node.type === 'IdentifierExpression') {
|
||||||
if (isNote(node.name)) {
|
if (isNote(node.name)) {
|
||||||
const value = node.name[1] === 's' ? node.name.replace('s', '#') : node.name;
|
const value = node.name[1] === 's' ? node.name.replace('s', '#') : node.name;
|
||||||
if (addLocations && isMarkable) {
|
if (addLocations && isMarkable) {
|
||||||
return reifyWithLocation(value, node, ast.locations, artificialNodes);
|
return reifyWithLocation(new LiteralStringExpression({ value }), node, ast.locations, artificialNodes);
|
||||||
}
|
}
|
||||||
return new LiteralStringExpression({ value });
|
return new LiteralStringExpression({ value });
|
||||||
}
|
}
|
||||||
@ -110,7 +111,7 @@ export default (code) => {
|
|||||||
}
|
}
|
||||||
if (addLocations && node.type === 'LiteralStringExpression' && isMarkable) {
|
if (addLocations && node.type === 'LiteralStringExpression' && isMarkable) {
|
||||||
// console.log('add', node);
|
// console.log('add', node);
|
||||||
return reifyWithLocation(node.value, node, ast.locations, artificialNodes);
|
return reifyWithLocation(node, node, ast.locations, artificialNodes);
|
||||||
}
|
}
|
||||||
if (!addMiniLocations) {
|
if (!addMiniLocations) {
|
||||||
return wrapFunction('reify', node);
|
return wrapFunction('reify', node);
|
||||||
@ -219,10 +220,10 @@ function wrapLocationOffset(node, stringNode, locations, artificialNodes) {
|
|||||||
|
|
||||||
// turns node in reify(value).withLocation(location), where location is the node's location in the source code
|
// turns node in reify(value).withLocation(location), where location is the node's location in the source code
|
||||||
// with this, the reified pattern can pass its location to the event, to know where to highlight when it's active
|
// with this, the reified pattern can pass its location to the event, to know where to highlight when it's active
|
||||||
function reifyWithLocation(value, node, locations, artificialNodes) {
|
function reifyWithLocation(literalNode, node, locations, artificialNodes) {
|
||||||
const withLocation = new CallExpression({
|
const withLocation = new CallExpression({
|
||||||
callee: new StaticMemberExpression({
|
callee: new StaticMemberExpression({
|
||||||
object: wrapFunction('reify', new LiteralStringExpression({ value })),
|
object: wrapFunction('reify', literalNode),
|
||||||
property: 'withLocation',
|
property: 'withLocation',
|
||||||
}),
|
}),
|
||||||
arguments: [getLocationObject(node, locations)],
|
arguments: [getLocationObject(node, locations)],
|
||||||
|
|||||||
37
docs/dist/tonal.js
vendored
37
docs/dist/tonal.js
vendored
@ -1,15 +1,6 @@
|
|||||||
import {Note, Interval, Scale} from "../_snowpack/pkg/@tonaljs/tonal.js";
|
import {Note, Interval, Scale} from "../_snowpack/pkg/@tonaljs/tonal.js";
|
||||||
import {Pattern as _Pattern} from "../_snowpack/link/strudel.js";
|
import {Pattern as _Pattern} from "../_snowpack/link/strudel.js";
|
||||||
const Pattern = _Pattern;
|
const Pattern = _Pattern;
|
||||||
function toNoteEvent(event) {
|
|
||||||
if (typeof event === "string" || typeof event === "number") {
|
|
||||||
return {value: event};
|
|
||||||
}
|
|
||||||
if (event.value) {
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
throw new Error("not a valid note event: " + JSON.stringify(event));
|
|
||||||
}
|
|
||||||
const mod = (n, m) => n < 0 ? mod(n + m, m) : n % m;
|
const mod = (n, m) => n < 0 ? mod(n + m, m) : n % m;
|
||||||
export function intervalDirection(from, to, direction = 1) {
|
export function intervalDirection(from, to, direction = 1) {
|
||||||
const sign = Math.sign(direction);
|
const sign = Math.sign(direction);
|
||||||
@ -44,43 +35,37 @@ function scaleTranspose(scale, offset, note) {
|
|||||||
}
|
}
|
||||||
return n + o;
|
return n + o;
|
||||||
}
|
}
|
||||||
Pattern.prototype._mapNotes = function(func) {
|
|
||||||
return this.fmap((event) => {
|
|
||||||
const noteEvent = toNoteEvent(event);
|
|
||||||
return {...noteEvent, ...func(noteEvent)};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
Pattern.prototype._transpose = function(intervalOrSemitones) {
|
Pattern.prototype._transpose = function(intervalOrSemitones) {
|
||||||
return this._mapNotes(({value, scale}) => {
|
return this._withEvent((event) => {
|
||||||
const interval = !isNaN(Number(intervalOrSemitones)) ? Interval.fromSemitones(intervalOrSemitones) : String(intervalOrSemitones);
|
const interval = !isNaN(Number(intervalOrSemitones)) ? Interval.fromSemitones(intervalOrSemitones) : String(intervalOrSemitones);
|
||||||
if (typeof value === "number") {
|
if (typeof event.value === "number") {
|
||||||
const semitones = typeof interval === "string" ? Interval.semitones(interval) || 0 : interval;
|
const semitones = typeof interval === "string" ? Interval.semitones(interval) || 0 : interval;
|
||||||
return {value: value + semitones};
|
return event.withValue(() => event.value + semitones);
|
||||||
}
|
}
|
||||||
return {value: Note.transpose(value, interval), scale};
|
return event.withValue(() => Note.transpose(event.value, interval));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
Pattern.prototype._scaleTranspose = function(offset) {
|
Pattern.prototype._scaleTranspose = function(offset) {
|
||||||
return this._mapNotes(({value, scale}) => {
|
return this._withEvent((event) => {
|
||||||
if (!scale) {
|
if (!event.context.scale) {
|
||||||
throw new Error("can only use scaleTranspose after .scale");
|
throw new Error("can only use scaleTranspose after .scale");
|
||||||
}
|
}
|
||||||
if (typeof value !== "string") {
|
if (typeof event.value !== "string") {
|
||||||
throw new Error("can only use scaleTranspose with notes");
|
throw new Error("can only use scaleTranspose with notes");
|
||||||
}
|
}
|
||||||
return {value: scaleTranspose(scale, Number(offset), value), scale};
|
return event.withValue(() => scaleTranspose(event.context.scale, Number(offset), event.value));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
Pattern.prototype._scale = function(scale) {
|
Pattern.prototype._scale = function(scale) {
|
||||||
return this._mapNotes((value) => {
|
return this._withEvent((event) => {
|
||||||
let note = value.value;
|
let note = event.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 {...value, value: note, scale};
|
return event.withValue(() => note).setContext({...event.context, scale});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
Pattern.prototype.define("transpose", (a, pat) => pat.transpose(a), {composable: true, patternified: true});
|
Pattern.prototype.define("transpose", (a, pat) => pat.transpose(a), {composable: true, patternified: true});
|
||||||
|
|||||||
62
docs/dist/tone.js
vendored
62
docs/dist/tone.js
vendored
@ -17,20 +17,23 @@ import {
|
|||||||
Sampler,
|
Sampler,
|
||||||
getDestination
|
getDestination
|
||||||
} from "../_snowpack/pkg/tone.js";
|
} from "../_snowpack/pkg/tone.js";
|
||||||
|
import {Piano} from "../_snowpack/pkg/@tonejs/piano.js";
|
||||||
const Pattern = _Pattern;
|
const Pattern = _Pattern;
|
||||||
Pattern.prototype.tone = function(instrument) {
|
Pattern.prototype.tone = function(instrument) {
|
||||||
return this.fmap((value) => {
|
return this._withEvent((event) => {
|
||||||
value = typeof value !== "object" && !Array.isArray(value) ? {value} : value;
|
const onTrigger = (time, event2) => {
|
||||||
const onTrigger = (time, event) => {
|
|
||||||
if (instrument.constructor.name === "PluckSynth") {
|
if (instrument.constructor.name === "PluckSynth") {
|
||||||
instrument.triggerAttack(value.value, time);
|
instrument.triggerAttack(event2.value, time);
|
||||||
} else if (instrument.constructor.name === "NoiseSynth") {
|
} else if (instrument.constructor.name === "NoiseSynth") {
|
||||||
instrument.triggerAttackRelease(event.duration, time);
|
instrument.triggerAttackRelease(event2.duration, time);
|
||||||
|
} else if (instrument.constructor.name === "Piano") {
|
||||||
|
instrument.keyDown({note: event2.value, time, velocity: 0.5});
|
||||||
|
instrument.keyUp({note: event2.value, time: time + event2.duration});
|
||||||
} else {
|
} else {
|
||||||
instrument.triggerAttackRelease(value.value, event.duration, time);
|
instrument.triggerAttackRelease(event2.value, event2.duration, time);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return {...value, instrument, onTrigger};
|
return event.setContext({...event.context, instrument, onTrigger});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
Pattern.prototype.define("tone", (type, pat) => pat.tone(type), {composable: true, patternified: false});
|
Pattern.prototype.define("tone", (type, pat) => pat.tone(type), {composable: true, patternified: false});
|
||||||
@ -45,6 +48,11 @@ export const pluck = (options) => new PluckSynth(options);
|
|||||||
export const polysynth = (options) => new PolySynth(options);
|
export const polysynth = (options) => new PolySynth(options);
|
||||||
export const sampler = (options) => new Sampler(options);
|
export const sampler = (options) => new Sampler(options);
|
||||||
export const synth = (options) => new Synth(options);
|
export const synth = (options) => new Synth(options);
|
||||||
|
export const piano = async (options = {velocities: 1}) => {
|
||||||
|
const p = new Piano(options);
|
||||||
|
await p.load();
|
||||||
|
return p;
|
||||||
|
};
|
||||||
export const vol = (v) => new Gain(v);
|
export const vol = (v) => new Gain(v);
|
||||||
export const lowpass = (v) => new Filter(v, "lowpass");
|
export const lowpass = (v) => new Filter(v, "lowpass");
|
||||||
export const highpass = (v) => new Filter(v, "highpass");
|
export const highpass = (v) => new Filter(v, "highpass");
|
||||||
@ -75,13 +83,12 @@ Pattern.prototype._poly = function(type = "triangle") {
|
|||||||
if (!this.instrument) {
|
if (!this.instrument) {
|
||||||
this.instrument = poly(type);
|
this.instrument = poly(type);
|
||||||
}
|
}
|
||||||
return this.fmap((value) => {
|
return this._withEvent((event) => {
|
||||||
value = typeof value !== "object" && !Array.isArray(value) ? {value} : value;
|
const onTrigger = (time, event2) => {
|
||||||
const onTrigger = (time, event) => {
|
|
||||||
this.instrument.set(instrumentConfig);
|
this.instrument.set(instrumentConfig);
|
||||||
this.instrument.triggerAttackRelease(value.value, event.duration, time);
|
this.instrument.triggerAttackRelease(event2.value, event2.duration, time);
|
||||||
};
|
};
|
||||||
return {...value, instrumentConfig, onTrigger};
|
return event.setContext({...event.context, instrumentConfig, onTrigger});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
Pattern.prototype.define("poly", (type, pat) => pat.poly(type), {composable: true, patternified: true});
|
Pattern.prototype.define("poly", (type, pat) => pat.poly(type), {composable: true, patternified: true});
|
||||||
@ -96,8 +103,7 @@ const getTrigger = (getChain, value) => (time, event) => {
|
|||||||
}, event.duration * 2e3);
|
}, event.duration * 2e3);
|
||||||
};
|
};
|
||||||
Pattern.prototype._synth = function(type = "triangle") {
|
Pattern.prototype._synth = function(type = "triangle") {
|
||||||
return this.fmap((value) => {
|
return this._withEvent((event) => {
|
||||||
value = typeof value !== "object" && !Array.isArray(value) ? {value} : value;
|
|
||||||
const instrumentConfig = {
|
const instrumentConfig = {
|
||||||
oscillator: {type},
|
oscillator: {type},
|
||||||
envelope: {attack: 0.01, decay: 0.01, sustain: 0.6, release: 0.01}
|
envelope: {attack: 0.01, decay: 0.01, sustain: 0.6, release: 0.01}
|
||||||
@ -107,37 +113,37 @@ Pattern.prototype._synth = function(type = "triangle") {
|
|||||||
instrument.set(instrumentConfig);
|
instrument.set(instrumentConfig);
|
||||||
return instrument;
|
return instrument;
|
||||||
};
|
};
|
||||||
const onTrigger = getTrigger(() => getInstrument().toDestination(), value.value);
|
const onTrigger = getTrigger(() => getInstrument().toDestination(), event.value);
|
||||||
return {...value, getInstrument, instrumentConfig, onTrigger};
|
return event.setContext({...event.context, getInstrument, instrumentConfig, onTrigger});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
Pattern.prototype.adsr = function(attack = 0.01, decay = 0.01, sustain = 0.6, release = 0.01) {
|
Pattern.prototype.adsr = function(attack = 0.01, decay = 0.01, sustain = 0.6, release = 0.01) {
|
||||||
return this.fmap((value) => {
|
return this._withEvent((event) => {
|
||||||
if (!value?.getInstrument) {
|
if (!event.context.getInstrument) {
|
||||||
throw new Error("cannot chain adsr: need instrument first (like synth)");
|
throw new Error("cannot chain adsr: need instrument first (like synth)");
|
||||||
}
|
}
|
||||||
const instrumentConfig = {...value.instrumentConfig, envelope: {attack, decay, sustain, release}};
|
const instrumentConfig = {...event.context.instrumentConfig, envelope: {attack, decay, sustain, release}};
|
||||||
const getInstrument = () => {
|
const getInstrument = () => {
|
||||||
const instrument = value.getInstrument();
|
const instrument = event.context.getInstrument();
|
||||||
instrument.set(instrumentConfig);
|
instrument.set(instrumentConfig);
|
||||||
return instrument;
|
return instrument;
|
||||||
};
|
};
|
||||||
const onTrigger = getTrigger(() => getInstrument().toDestination(), value.value);
|
const onTrigger = getTrigger(() => getInstrument().toDestination(), event.value);
|
||||||
return {...value, getInstrument, instrumentConfig, onTrigger};
|
return event.setContext({...event.context, getInstrument, instrumentConfig, onTrigger});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
Pattern.prototype.chain = function(...effectGetters) {
|
Pattern.prototype.chain = function(...effectGetters) {
|
||||||
return this.fmap((value) => {
|
return this._withEvent((event) => {
|
||||||
if (!value?.getInstrument) {
|
if (!event.context?.getInstrument) {
|
||||||
throw new Error("cannot chain: need instrument first (like synth)");
|
throw new Error("cannot chain: need instrument first (like synth)");
|
||||||
}
|
}
|
||||||
const chain = (value.chain || []).concat(effectGetters);
|
const chain = (event.context.chain || []).concat(effectGetters);
|
||||||
const getChain = () => {
|
const getChain = () => {
|
||||||
const effects = chain.map((getEffect) => getEffect());
|
const effects = chain.map((getEffect) => getEffect());
|
||||||
return value.getInstrument().chain(...effects, getDestination());
|
return event.context.getInstrument().chain(...effects, getDestination());
|
||||||
};
|
};
|
||||||
const onTrigger = getTrigger(getChain, value.value);
|
const onTrigger = getTrigger(getChain, event.value);
|
||||||
return {...value, getChain, onTrigger, chain};
|
return event.setContext({...event.context, getChain, onTrigger, chain});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
export const autofilter = (freq = 1) => () => new AutoFilter(freq).start();
|
export const autofilter = (freq = 1) => () => new AutoFilter(freq).start();
|
||||||
|
|||||||
8
docs/dist/tunes.js
vendored
8
docs/dist/tunes.js
vendored
@ -426,3 +426,11 @@ export const sowhatelse = `()=> {
|
|||||||
"[2,4]/4".scale('D dorian').apply(t).tone(instr('pad')).mask("<x x x ~>/8")
|
"[2,4]/4".scale('D dorian').apply(t).tone(instr('pad')).mask("<x x x ~>/8")
|
||||||
).fast(6/8)
|
).fast(6/8)
|
||||||
}`;
|
}`;
|
||||||
|
export const barryHarris = `piano()
|
||||||
|
.then(p => "0,2,[7 6]"
|
||||||
|
.add("<0 1 2 3 4 5 7 8>")
|
||||||
|
.scale('C bebop major')
|
||||||
|
.transpose("<0 1 2 1>/8")
|
||||||
|
.slow(2)
|
||||||
|
.tone(p.toDestination()))
|
||||||
|
`;
|
||||||
|
|||||||
7
docs/dist/useCycle.js
vendored
7
docs/dist/useCycle.js
vendored
@ -1,6 +1,6 @@
|
|||||||
import {useEffect, useState} from "../_snowpack/pkg/react.js";
|
import {useEffect, useState} from "../_snowpack/pkg/react.js";
|
||||||
import * as Tone from "../_snowpack/pkg/tone.js";
|
import * as Tone from "../_snowpack/pkg/tone.js";
|
||||||
import {TimeSpan} from "../_snowpack/link/strudel.js";
|
import {TimeSpan, State} from "../_snowpack/link/strudel.js";
|
||||||
function useCycle(props) {
|
function useCycle(props) {
|
||||||
const {onEvent, onQuery, onSchedule, ready = true, onDraw} = props;
|
const {onEvent, onQuery, onSchedule, ready = true, onDraw} = props;
|
||||||
const [started, setStarted] = useState(false);
|
const [started, setStarted] = useState(false);
|
||||||
@ -8,7 +8,7 @@ function useCycle(props) {
|
|||||||
const activeCycle = () => Math.floor(Tone.getTransport().seconds / cycleDuration);
|
const activeCycle = () => Math.floor(Tone.getTransport().seconds / cycleDuration);
|
||||||
const query = (cycle = activeCycle()) => {
|
const query = (cycle = activeCycle()) => {
|
||||||
const timespan = new TimeSpan(cycle, cycle + 1);
|
const timespan = new TimeSpan(cycle, cycle + 1);
|
||||||
const events = onQuery?.(timespan) || [];
|
const events = onQuery?.(new State(timespan)) || [];
|
||||||
onSchedule?.(events, cycle);
|
onSchedule?.(events, cycle);
|
||||||
const cancelFrom = timespan.begin.valueOf();
|
const cancelFrom = timespan.begin.valueOf();
|
||||||
Tone.getTransport().cancel(cancelFrom);
|
Tone.getTransport().cancel(cancelFrom);
|
||||||
@ -22,7 +22,8 @@ function useCycle(props) {
|
|||||||
const toneEvent = {
|
const toneEvent = {
|
||||||
time: event.part.begin.valueOf(),
|
time: event.part.begin.valueOf(),
|
||||||
duration: event.whole.end.sub(event.whole.begin).valueOf(),
|
duration: event.whole.end.sub(event.whole.begin).valueOf(),
|
||||||
value: event.value
|
value: event.value,
|
||||||
|
context: event.context
|
||||||
};
|
};
|
||||||
onEvent(time, toneEvent);
|
onEvent(time, toneEvent);
|
||||||
Tone.Draw.schedule(() => {
|
Tone.Draw.schedule(() => {
|
||||||
|
|||||||
23
docs/dist/useRepl.js
vendored
23
docs/dist/useRepl.js
vendored
@ -12,19 +12,22 @@ function useRepl({tune, defaultSynth, autolink = true, onEvent, onDraw}) {
|
|||||||
const [activeCode, setActiveCode] = useState();
|
const [activeCode, setActiveCode] = useState();
|
||||||
const [log, setLog] = useState("");
|
const [log, setLog] = useState("");
|
||||||
const [error, setError] = useState();
|
const [error, setError] = useState();
|
||||||
|
const [pending, setPending] = useState(false);
|
||||||
const [hash, setHash] = useState("");
|
const [hash, setHash] = useState("");
|
||||||
const [pattern, setPattern] = useState();
|
const [pattern, setPattern] = useState();
|
||||||
const dirty = code !== activeCode || error;
|
const dirty = code !== activeCode || error;
|
||||||
const generateHash = () => encodeURIComponent(btoa(code));
|
const generateHash = () => encodeURIComponent(btoa(code));
|
||||||
const activateCode = (_code = code) => {
|
const activateCode = async (_code = code) => {
|
||||||
!cycle.started && cycle.start();
|
|
||||||
broadcast({type: "start", from: id});
|
|
||||||
if (activeCode && !dirty) {
|
if (activeCode && !dirty) {
|
||||||
setError(void 0);
|
setError(void 0);
|
||||||
|
!cycle.started && cycle.start();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const parsed = evaluate(_code);
|
setPending(true);
|
||||||
|
const parsed = await evaluate(_code);
|
||||||
|
!cycle.started && cycle.start();
|
||||||
|
broadcast({type: "start", from: id});
|
||||||
setPattern(() => parsed.pattern);
|
setPattern(() => parsed.pattern);
|
||||||
if (autolink) {
|
if (autolink) {
|
||||||
window.location.hash = "#" + encodeURIComponent(btoa(code));
|
window.location.hash = "#" + encodeURIComponent(btoa(code));
|
||||||
@ -32,6 +35,7 @@ function useRepl({tune, defaultSynth, autolink = true, onEvent, onDraw}) {
|
|||||||
setHash(generateHash());
|
setHash(generateHash());
|
||||||
setError(void 0);
|
setError(void 0);
|
||||||
setActiveCode(_code);
|
setActiveCode(_code);
|
||||||
|
setPending(false);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
err.message = "evaluation error: " + err.message;
|
err.message = "evaluation error: " + err.message;
|
||||||
console.warn(err);
|
console.warn(err);
|
||||||
@ -48,8 +52,9 @@ function useRepl({tune, defaultSynth, autolink = true, onEvent, onDraw}) {
|
|||||||
onEvent: useCallback((time, event) => {
|
onEvent: useCallback((time, event) => {
|
||||||
try {
|
try {
|
||||||
onEvent?.(event);
|
onEvent?.(event);
|
||||||
if (!event.value?.onTrigger) {
|
const {onTrigger} = event.context;
|
||||||
const note = event.value?.value || event.value;
|
if (!onTrigger) {
|
||||||
|
const note = event.value;
|
||||||
if (!isNote(note)) {
|
if (!isNote(note)) {
|
||||||
throw new Error("not a note: " + note);
|
throw new Error("not a note: " + note);
|
||||||
}
|
}
|
||||||
@ -59,7 +64,6 @@ function useRepl({tune, defaultSynth, autolink = true, onEvent, onDraw}) {
|
|||||||
throw new Error("no defaultSynth passed to useRepl.");
|
throw new Error("no defaultSynth passed to useRepl.");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const {onTrigger} = event.value;
|
|
||||||
onTrigger(time, event);
|
onTrigger(time, event);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -68,9 +72,9 @@ function useRepl({tune, defaultSynth, autolink = true, onEvent, onDraw}) {
|
|||||||
pushLog(err.message);
|
pushLog(err.message);
|
||||||
}
|
}
|
||||||
}, [onEvent]),
|
}, [onEvent]),
|
||||||
onQuery: useCallback((span) => {
|
onQuery: useCallback((state) => {
|
||||||
try {
|
try {
|
||||||
return pattern?.query(span) || [];
|
return pattern?.query(state) || [];
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(err);
|
console.warn(err);
|
||||||
err.message = "query error: " + err.message;
|
err.message = "query error: " + err.message;
|
||||||
@ -95,6 +99,7 @@ function useRepl({tune, defaultSynth, autolink = true, onEvent, onDraw}) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
|
pending,
|
||||||
code,
|
code,
|
||||||
setCode,
|
setCode,
|
||||||
pattern,
|
pattern,
|
||||||
|
|||||||
15
docs/dist/voicings.js
vendored
15
docs/dist/voicings.js
vendored
@ -10,7 +10,7 @@ const getVoicing = (chord, lastVoicing, range = ["F3", "A4"]) => dictionaryVoici
|
|||||||
});
|
});
|
||||||
const Pattern = _Pattern;
|
const Pattern = _Pattern;
|
||||||
Pattern.prototype.fmapNested = function(func) {
|
Pattern.prototype.fmapNested = function(func) {
|
||||||
return new Pattern((span) => this.query(span).map((event) => reify(func(event)).query(span).map((hap) => new Hap(event.whole, event.part, hap.value))).flat());
|
return new Pattern((span) => this.query(span).map((event) => reify(func(event)).query(span).map((hap) => new Hap(event.whole, event.part, hap.value, hap.context))).flat());
|
||||||
};
|
};
|
||||||
Pattern.prototype.voicings = function(range) {
|
Pattern.prototype.voicings = function(range) {
|
||||||
let lastVoicing;
|
let lastVoicing;
|
||||||
@ -18,15 +18,16 @@ Pattern.prototype.voicings = function(range) {
|
|||||||
range = ["F3", "A4"];
|
range = ["F3", "A4"];
|
||||||
}
|
}
|
||||||
return this.fmapNested((event) => {
|
return this.fmapNested((event) => {
|
||||||
lastVoicing = getVoicing(event.value?.value || event.value, lastVoicing, range);
|
lastVoicing = getVoicing(event.value, lastVoicing, range);
|
||||||
return stack(...lastVoicing);
|
return stack(...lastVoicing)._withContext(() => ({
|
||||||
|
locations: event.context.locations || []
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
Pattern.prototype.rootNotes = function(octave = 2) {
|
Pattern.prototype.rootNotes = function(octave = 2) {
|
||||||
return this._mapNotes((value) => {
|
return this.fmap((value) => {
|
||||||
const [_, root] = value.value.match(/^([a-gA-G])[b#]?.*$/);
|
const [_, root] = value.match(/^([a-gA-G])[b#]?.*$/);
|
||||||
const bassNote = root + octave;
|
return root + octave;
|
||||||
return {...value, value: bassNote};
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
Pattern.prototype.define("voicings", (range, pat) => pat.voicings(range), {composable: true});
|
Pattern.prototype.define("voicings", (range, pat) => pat.voicings(range), {composable: true});
|
||||||
|
|||||||
@ -843,8 +843,8 @@ Ensure the default browser behavior of the `hidden` attribute.
|
|||||||
.CodeMirror {
|
.CodeMirror {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
height: inherit !important;
|
height: inherit !important;
|
||||||
}.justify-center {
|
}main {
|
||||||
justify-content: center;
|
margin: 0 auto;
|
||||||
}.hover\:bg-slate-500:hover {
|
}.hover\:bg-slate-500:hover {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(100 116 139 / var(--tw-bg-opacity));
|
background-color: rgb(100 116 139 / var(--tw-bg-opacity));
|
||||||
@ -1346,4 +1346,4 @@ span.CodeMirror-selectedtext { background: none; }
|
|||||||
color: white !important;
|
color: white !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*# sourceMappingURL=index.fd7d9b66.css.map */
|
/*# sourceMappingURL=index.0ea4d9ed.css.map */
|
||||||
1
docs/tutorial/index.0ea4d9ed.css.map
Normal file
1
docs/tutorial/index.0ea4d9ed.css.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<link rel="icon" href="/tutorial/favicon.e3ab9dd9.ico">
|
<link rel="icon" href="/tutorial/favicon.e3ab9dd9.ico">
|
||||||
<link rel="stylesheet" type="text/css" href="/tutorial/index.fd7d9b66.css">
|
<link rel="stylesheet" type="text/css" href="/tutorial/index.0ea4d9ed.css">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="description" content="Strudel REPL">
|
<meta name="description" content="Strudel REPL">
|
||||||
<title>Strudel Tutorial</title>
|
<title>Strudel Tutorial</title>
|
||||||
@ -11,6 +11,6 @@
|
|||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<script src="/tutorial/index.4ae5d228.js" defer=""></script>
|
<script src="/tutorial/index.26c147ee.js" defer=""></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user