build
This commit is contained in:
parent
df0650e48d
commit
827c983175
24 changed files with 22150 additions and 20110 deletions
|
|
@ -118,21 +118,33 @@ class TimeSpan {
|
|||
}
|
||||
}
|
||||
class Hap {
|
||||
constructor(whole, part, value) {
|
||||
constructor(whole, part, value, context = {}, stateful = false) {
|
||||
this.whole = whole;
|
||||
this.part = part;
|
||||
this.value = value;
|
||||
this.context = context;
|
||||
this.stateful = stateful;
|
||||
if (stateful) {
|
||||
assert(typeof this.value === "function", "Stateful values must be functions");
|
||||
}
|
||||
}
|
||||
withSpan(func) {
|
||||
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) {
|
||||
return new Hap(this.whole, this.part, func(this.value));
|
||||
return new Hap(this.whole, this.part, func(this.value), this.context);
|
||||
}
|
||||
hasOnset() {
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
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 {
|
||||
|
|
@ -157,42 +187,55 @@ class Pattern {
|
|||
}
|
||||
_splitQueries() {
|
||||
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);
|
||||
}
|
||||
withQuerySpan(func) {
|
||||
return new Pattern((span) => this.query(func(span)));
|
||||
return new Pattern((state) => this.query(state.withSpan(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) {
|
||||
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) {
|
||||
return this.withEventSpan((span) => span.withTime(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) {
|
||||
return this.fmap((value) => {
|
||||
value = typeof value === "object" && !Array.isArray(value) ? value : {value};
|
||||
const locations = (value.locations || []).concat([location]);
|
||||
return {...value, locations};
|
||||
return this._withContext((context) => {
|
||||
const locations = (context.locations || []).concat([location]);
|
||||
return {...context, locations};
|
||||
});
|
||||
}
|
||||
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) {
|
||||
return this.withValue(func);
|
||||
}
|
||||
_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) {
|
||||
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() {
|
||||
return this._filterValues((val) => val != void 0);
|
||||
|
|
@ -202,15 +245,15 @@ class Pattern {
|
|||
}
|
||||
_appWhole(whole_func, pat_val) {
|
||||
const pat_func = this;
|
||||
const query = function(span) {
|
||||
const event_funcs = pat_func.query(span);
|
||||
const event_vals = pat_val.query(span);
|
||||
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);
|
||||
if (s == 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)))));
|
||||
};
|
||||
|
|
@ -227,15 +270,19 @@ class Pattern {
|
|||
}
|
||||
appLeft(pat_val) {
|
||||
const pat_func = this;
|
||||
const query = function(span) {
|
||||
const query = function(state) {
|
||||
const haps = [];
|
||||
for (const hap_func of pat_func.query(span)) {
|
||||
const event_vals = pat_val.query(hap_func.part);
|
||||
for (const hap_func of pat_func.query(state)) {
|
||||
const event_vals = pat_val.query(state.setSpan(hap_func.part));
|
||||
for (const hap_val of event_vals) {
|
||||
const new_whole = hap_func.whole;
|
||||
const new_part = hap_func.part.intersection_e(hap_val.part);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -245,15 +292,19 @@ class Pattern {
|
|||
}
|
||||
appRight(pat_val) {
|
||||
const pat_func = this;
|
||||
const query = function(span) {
|
||||
const query = function(state) {
|
||||
const haps = [];
|
||||
for (const hap_val of pat_val.query(span)) {
|
||||
const hap_funcs = pat_func.query(hap_val.part);
|
||||
for (const hap_val of pat_val.query(state)) {
|
||||
const hap_funcs = pat_func.query(state.setSpan(hap_val.part));
|
||||
for (const hap_func of hap_funcs) {
|
||||
const new_whole = hap_val.whole;
|
||||
const new_part = hap_func.part.intersection_e(hap_val.part);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -261,8 +312,12 @@ class Pattern {
|
|||
};
|
||||
return new Pattern(query);
|
||||
}
|
||||
get firstCycle() {
|
||||
return this.query(new TimeSpan(Fraction(0), Fraction(1)));
|
||||
firstCycle(with_context = false) {
|
||||
var self = this;
|
||||
if (!with_context) {
|
||||
self = self._stripContext();
|
||||
}
|
||||
return self.query(new State(new TimeSpan(Fraction(0), Fraction(1))));
|
||||
}
|
||||
_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)))));
|
||||
|
|
@ -287,14 +342,18 @@ class Pattern {
|
|||
}
|
||||
_bindWhole(choose_whole, func) {
|
||||
const pat_val = this;
|
||||
const query = function(span) {
|
||||
const query = function(state) {
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
|
|
@ -409,7 +468,8 @@ class Pattern {
|
|||
}
|
||||
rev() {
|
||||
const pat = this;
|
||||
const query = function(span) {
|
||||
const query = function(state) {
|
||||
const span = state.span;
|
||||
const cycle = span.begin.sam();
|
||||
const next_cycle = span.begin.nextSam();
|
||||
const reflect = function(to_reflect) {
|
||||
|
|
@ -419,7 +479,7 @@ class Pattern {
|
|||
reflected.end = tmp;
|
||||
return reflected;
|
||||
};
|
||||
const haps = pat.query(reflect(span));
|
||||
const haps = pat.query(state.setSpan(reflect(span)));
|
||||
return haps.map((hap) => hap.withSpan(reflect));
|
||||
};
|
||||
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};
|
||||
const silence = new Pattern((_) => []);
|
||||
function pure(value) {
|
||||
function query(span) {
|
||||
return span.spanCycles.map((subspan) => new Hap(Fraction(subspan.begin).wholeCycle(), subspan, value));
|
||||
function query(state) {
|
||||
return state.span.spanCycles.map((subspan) => new Hap(Fraction(subspan.begin).wholeCycle(), subspan, value));
|
||||
}
|
||||
return new Pattern(query);
|
||||
}
|
||||
|
|
@ -472,7 +532,7 @@ function steady(value) {
|
|||
return new Pattern((span) => Hap(void 0, span, value));
|
||||
}
|
||||
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);
|
||||
};
|
||||
const _toBipolar = (pat) => pat.fmap((x) => x * 2 - 1);
|
||||
|
|
@ -497,28 +557,29 @@ function reify(thing) {
|
|||
}
|
||||
function stack(...pats) {
|
||||
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);
|
||||
}
|
||||
function slowcat(...pats) {
|
||||
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 = pats[pat_n];
|
||||
if (!pat) {
|
||||
return [];
|
||||
}
|
||||
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();
|
||||
}
|
||||
function slowcatPrime(...pats) {
|
||||
pats = pats.map(reify);
|
||||
const query = function(span) {
|
||||
const pat_n = Math.floor(span.begin) % pats.length;
|
||||
const query = function(state) {
|
||||
const pat_n = Math.floor(state.span.begin) % pats.length;
|
||||
const pat = pats[pat_n];
|
||||
return pat.query(span);
|
||||
return pat.query(state);
|
||||
};
|
||||
return new Pattern(query)._splitQueries();
|
||||
}
|
||||
|
|
@ -638,9 +699,8 @@ Pattern.prototype.bootstrap = () => {
|
|||
return bootstrapped;
|
||||
};
|
||||
function withLocationOffset(pat, offset) {
|
||||
return pat.fmap((value) => {
|
||||
value = typeof value === "object" && !Array.isArray(value) ? value : {value};
|
||||
let locations = value.locations || [];
|
||||
return pat._withContext((context) => {
|
||||
let locations = context.locations || [];
|
||||
locations = locations.map(({start, end}) => {
|
||||
const colOffset = start.line === 1 ? offset.start.column : 0;
|
||||
return {
|
||||
|
|
@ -656,7 +716,7 @@ function withLocationOffset(pat, offset) {
|
|||
}
|
||||
};
|
||||
});
|
||||
return {...value, locations};
|
||||
return {...context, locations};
|
||||
});
|
||||
}
|
||||
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": {
|
||||
"@tonaljs/tonal": "./@tonaljs/tonal.js",
|
||||
"@tonejs/piano": "./@tonejs/piano.js",
|
||||
"chord-voicings": "./chord-voicings.js",
|
||||
"codemirror/lib/codemirror.css": "./codemirror/lib/codemirror.css",
|
||||
"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
Loading…
Add table
Add a link
Reference in a new issue