mercury-engine 1.3.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mercury.js +563 -197
- package/dist/mercury.min.js +1 -1
- package/examples/interface/index.html +11 -2
- package/examples/midi/index.html +1 -0
- package/package.json +3 -3
package/dist/mercury.js
CHANGED
|
@@ -3592,7 +3592,7 @@
|
|
|
3592
3592
|
'use strict';
|
|
3593
3593
|
|
|
3594
3594
|
/*
|
|
3595
|
-
* bignumber.js v9.3.
|
|
3595
|
+
* bignumber.js v9.3.1
|
|
3596
3596
|
* A JavaScript library for arbitrary-precision arithmetic.
|
|
3597
3597
|
* https://github.com/MikeMcl/bignumber.js
|
|
3598
3598
|
* Copyright (c) 2025 Michael Mclaughlin <M8ch88l@gmail.com>
|
|
@@ -4860,7 +4860,7 @@
|
|
|
4860
4860
|
|
|
4861
4861
|
// Fixed-point notation.
|
|
4862
4862
|
} else {
|
|
4863
|
-
i -= ne;
|
|
4863
|
+
i -= ne + (id === 2 && e > ne);
|
|
4864
4864
|
str = toFixedPoint(str, e, '0');
|
|
4865
4865
|
|
|
4866
4866
|
// Append zeros?
|
|
@@ -6703,18 +6703,28 @@ module.exports={
|
|
|
6703
6703
|
// The default instrument objects for Mercury
|
|
6704
6704
|
//
|
|
6705
6705
|
|
|
6706
|
-
const
|
|
6706
|
+
const sequencerDefault = {
|
|
6707
6707
|
'empty' : {
|
|
6708
6708
|
'object' : '',
|
|
6709
6709
|
'type' : '',
|
|
6710
6710
|
'functions' : {
|
|
6711
|
+
// 'name' : '',
|
|
6712
|
+
// 'solo' : [ 0 ],
|
|
6711
6713
|
'group' : [],
|
|
6712
6714
|
'time' : [ '1/1', 0 ],
|
|
6715
|
+
// 'once' : [ 'off' ],
|
|
6713
6716
|
'beat' : [ 1, -1 ],
|
|
6714
|
-
'amp' : [ 1 ],
|
|
6715
6717
|
'env' : [ 1, 250 ],
|
|
6718
|
+
'amp' : [ 1 ],
|
|
6719
|
+
// 'pan' : [ 0 ],
|
|
6720
|
+
'add_fx' : [],
|
|
6721
|
+
// 'out' : [ 1, 2 ],
|
|
6722
|
+
// 'ratchet' : [ 0, 2 ],
|
|
6723
|
+
// 'timediv' : [ ],
|
|
6724
|
+
// 'human' : [ 0 ],
|
|
6725
|
+
// 'warp' : [ 1 ],
|
|
6726
|
+
// 'wait' : [ 0 ],
|
|
6716
6727
|
'note' : [ 0, 0 ],
|
|
6717
|
-
'add_fx' : []
|
|
6718
6728
|
}
|
|
6719
6729
|
},
|
|
6720
6730
|
}
|
|
@@ -6748,6 +6758,12 @@ const instrumentDefaults = {
|
|
|
6748
6758
|
'pan' : [ 0 ]
|
|
6749
6759
|
}
|
|
6750
6760
|
},
|
|
6761
|
+
'polySample' : {
|
|
6762
|
+
'type' : 'kick_909',
|
|
6763
|
+
'functions' : {
|
|
6764
|
+
'note' : [ 0, 2 ]
|
|
6765
|
+
}
|
|
6766
|
+
},
|
|
6751
6767
|
'loop' : {
|
|
6752
6768
|
'type' : 'amen',
|
|
6753
6769
|
'functions' : {
|
|
@@ -6782,11 +6798,11 @@ const instrumentDefaults = {
|
|
|
6782
6798
|
|
|
6783
6799
|
// merge the default empty object and the additional defaults
|
|
6784
6800
|
Object.keys(instrumentDefaults).forEach((o) => {
|
|
6785
|
-
let empty = JSON.parse(JSON.stringify(
|
|
6801
|
+
let empty = JSON.parse(JSON.stringify(sequencerDefault.empty));
|
|
6786
6802
|
instrumentDefaults[o] = deepMerge(empty, instrumentDefaults[o]);
|
|
6787
6803
|
});
|
|
6788
6804
|
// add the empty default
|
|
6789
|
-
Object.assign(instrumentDefaults,
|
|
6805
|
+
Object.assign(instrumentDefaults, sequencerDefault);
|
|
6790
6806
|
// instrumentDefaults = { ...instrumentDefaults, ...emptyDefault };
|
|
6791
6807
|
|
|
6792
6808
|
// Return true if input is object
|
|
@@ -7216,8 +7232,14 @@ function mercuryParser(code=''){
|
|
|
7216
7232
|
}
|
|
7217
7233
|
// only if not undefined
|
|
7218
7234
|
if (parser.results[0] !== undefined){
|
|
7235
|
+
// add line number and code to new object if it is one
|
|
7236
|
+
if (parser.results[0]?.['@object']?.['@new']){
|
|
7237
|
+
parser.results[0]['@object']['@new']['@line'] = l+1;
|
|
7238
|
+
// parser.results[0]['@object']['@new']['@code'] = lines[l];
|
|
7239
|
+
}
|
|
7219
7240
|
// build the tokenized syntax tree
|
|
7220
7241
|
syntaxTree['@main'].push(parser.results[0]);
|
|
7242
|
+
|
|
7221
7243
|
} else {
|
|
7222
7244
|
throw new Error();
|
|
7223
7245
|
}
|
|
@@ -7374,10 +7396,17 @@ function traverseTree(tree, code, level, obj){
|
|
|
7374
7396
|
let inst = map['@inst'](el['@inst'], ccode);
|
|
7375
7397
|
delete el['@inst'];
|
|
7376
7398
|
|
|
7399
|
+
// add the line number and code to the object for later use
|
|
7400
|
+
inst.line = map['@line'](el['@line']);
|
|
7401
|
+
delete el['@line'];
|
|
7402
|
+
|
|
7403
|
+
// inst.code = map['@code'](el['@code']);
|
|
7404
|
+
// delete el['@code'];
|
|
7405
|
+
|
|
7377
7406
|
// generate unique ID name for object before checking the name()
|
|
7378
7407
|
// this ID is used for groups if there are any
|
|
7379
7408
|
inst.functions.name = [ uniqueID(8) ];
|
|
7380
|
-
|
|
7409
|
+
|
|
7381
7410
|
Object.keys(el).forEach((k) => {
|
|
7382
7411
|
inst = map[k](el[k], ccode, '@object', inst);
|
|
7383
7412
|
});
|
|
@@ -7573,21 +7602,13 @@ function traverseTree(tree, code, level, obj){
|
|
|
7573
7602
|
}
|
|
7574
7603
|
return el;
|
|
7575
7604
|
},
|
|
7576
|
-
'@string' : (el) => {
|
|
7577
|
-
|
|
7578
|
-
},
|
|
7579
|
-
'@
|
|
7580
|
-
|
|
7581
|
-
},
|
|
7582
|
-
'@
|
|
7583
|
-
return el;
|
|
7584
|
-
},
|
|
7585
|
-
'@note' : (el) => {
|
|
7586
|
-
return el;
|
|
7587
|
-
},
|
|
7588
|
-
'@signal' : (el) => {
|
|
7589
|
-
return el;
|
|
7590
|
-
}
|
|
7605
|
+
'@string' : (el) => { return el; },
|
|
7606
|
+
'@number' : (el) => { return el; },
|
|
7607
|
+
'@division' : (el) => { return el; },
|
|
7608
|
+
'@note' : (el) => { return el; },
|
|
7609
|
+
'@signal' : (el) => { return el; },
|
|
7610
|
+
'@line' : (el) => { return el; },
|
|
7611
|
+
'@code' : (el) => { return el; }
|
|
7591
7612
|
}
|
|
7592
7613
|
|
|
7593
7614
|
if (Array.isArray(tree)) {
|
|
@@ -10991,19 +11012,17 @@ function spreadFloat(len=1, lo=1, hi){
|
|
|
10991
11012
|
if (hi === undefined){ var t=lo, lo=0, hi=t; }
|
|
10992
11013
|
// calculate the range
|
|
10993
11014
|
let r = hi - lo;
|
|
10994
|
-
// lo is actual lowest value
|
|
10995
|
-
lo = Math.min(lo, hi);
|
|
10996
11015
|
// len is minimum of 1 or length of array
|
|
10997
11016
|
len = size(len);
|
|
10998
11017
|
if (len === 1){ return [lo]; }
|
|
10999
11018
|
// stepsize
|
|
11000
|
-
let s =
|
|
11019
|
+
let s = r / len;
|
|
11001
11020
|
// generate array
|
|
11002
11021
|
let arr = [];
|
|
11003
11022
|
for (let i=0; i<len; i++){
|
|
11004
11023
|
arr[i] = i * s + lo;
|
|
11005
11024
|
}
|
|
11006
|
-
return
|
|
11025
|
+
return arr;
|
|
11007
11026
|
}
|
|
11008
11027
|
exports.spreadFloat = spreadFloat;
|
|
11009
11028
|
exports.spreadF = spreadFloat;
|
|
@@ -11032,8 +11051,6 @@ function spreadExpFloat(len=1, lo=1, hi, exp=1){
|
|
|
11032
11051
|
if (hi === undefined){ var t=lo, lo=0, hi=t; }
|
|
11033
11052
|
// calculate the range
|
|
11034
11053
|
let r = hi - lo;
|
|
11035
|
-
// lo is actual lowest value
|
|
11036
|
-
lo = Math.min(lo, hi);
|
|
11037
11054
|
// len is minimum of 1
|
|
11038
11055
|
len = size(len);
|
|
11039
11056
|
// len = Math.max(1, len);
|
|
@@ -11041,9 +11058,9 @@ function spreadExpFloat(len=1, lo=1, hi, exp=1){
|
|
|
11041
11058
|
// generate array
|
|
11042
11059
|
let arr = [];
|
|
11043
11060
|
for (let i=0; i<len; i++){
|
|
11044
|
-
arr[i] = Math.pow((i / len), exp) *
|
|
11061
|
+
arr[i] = Math.pow((i / len), exp) * r + lo;
|
|
11045
11062
|
}
|
|
11046
|
-
return
|
|
11063
|
+
return arr;
|
|
11047
11064
|
}
|
|
11048
11065
|
exports.spreadFloatExp = spreadExpFloat; // deprecated
|
|
11049
11066
|
exports.spreadExpFloat = spreadExpFloat;
|
|
@@ -11994,7 +12011,6 @@ exports.Automaton = Automaton;
|
|
|
11994
12011
|
|
|
11995
12012
|
// require Generative methods
|
|
11996
12013
|
const { spread } = require('./gen-basic.js');
|
|
11997
|
-
const { lookup } = require('./transform.js');
|
|
11998
12014
|
const { fold, size, toArray } = require('./utility');
|
|
11999
12015
|
const { change } = require('./statistic');
|
|
12000
12016
|
|
|
@@ -12003,7 +12019,8 @@ let seedrandom = require('seedrandom');
|
|
|
12003
12019
|
|
|
12004
12020
|
// local pseudorandom number generator and seed storage
|
|
12005
12021
|
let rng = seedrandom();
|
|
12006
|
-
let _seed = 0;
|
|
12022
|
+
let _seed = 0;
|
|
12023
|
+
seed(_seed);
|
|
12007
12024
|
|
|
12008
12025
|
// Set the seed for all the Random Number Generators.
|
|
12009
12026
|
// 0 sets to unpredictable seeding
|
|
@@ -12013,14 +12030,14 @@ let _seed = 0;
|
|
|
12013
12030
|
//
|
|
12014
12031
|
function seed(v=0){
|
|
12015
12032
|
if (v === 0 || v === null || v === undefined){
|
|
12016
|
-
|
|
12017
|
-
_seed =
|
|
12033
|
+
// generate a random seed, which is retrievable
|
|
12034
|
+
_seed = Math.floor(Math.random() * 9999) + 1;
|
|
12018
12035
|
} else {
|
|
12019
|
-
rng = seedrandom(v);
|
|
12020
12036
|
_seed = v;
|
|
12021
12037
|
}
|
|
12038
|
+
rng = seedrandom(_seed);
|
|
12022
12039
|
// also return the seed that has been set
|
|
12023
|
-
return
|
|
12040
|
+
return _seed;
|
|
12024
12041
|
}
|
|
12025
12042
|
exports.seed = seed;
|
|
12026
12043
|
|
|
@@ -12575,7 +12592,7 @@ function reviver(key, value) {
|
|
|
12575
12592
|
}
|
|
12576
12593
|
return value;
|
|
12577
12594
|
}
|
|
12578
|
-
},{"./gen-basic.js":48,"./statistic":51,"./
|
|
12595
|
+
},{"./gen-basic.js":48,"./statistic":51,"./utility":54,"seedrandom":36}],51:[function(require,module,exports){
|
|
12579
12596
|
//=======================================================================
|
|
12580
12597
|
// statistic.js
|
|
12581
12598
|
// part of 'total-serialism' Package
|
|
@@ -12588,7 +12605,7 @@ function reviver(key, value) {
|
|
|
12588
12605
|
|
|
12589
12606
|
const Mod = require('./transform');
|
|
12590
12607
|
|
|
12591
|
-
const { maximum, minimum, flatten, toArray } = require('./utility');
|
|
12608
|
+
const { maximum, minimum, flatten, toArray, lcm, gcd } = require('./utility');
|
|
12592
12609
|
|
|
12593
12610
|
// sort an array of numbers or strings. sorts ascending
|
|
12594
12611
|
// or descending in numerical and alphabetical order
|
|
@@ -12688,26 +12705,28 @@ exports.center = median;
|
|
|
12688
12705
|
function mode(a=[0], d=true){
|
|
12689
12706
|
if (!Array.isArray(a)) { return a; }
|
|
12690
12707
|
if (d) { a = flatten(a); }
|
|
12691
|
-
|
|
12692
|
-
|
|
12693
|
-
|
|
12694
|
-
|
|
12695
|
-
|
|
12696
|
-
|
|
12697
|
-
|
|
12698
|
-
for (let i=1; i<arr.length; i++){
|
|
12699
|
-
if (arr[i-1] != arr[i]){
|
|
12700
|
-
amount = 0;
|
|
12708
|
+
|
|
12709
|
+
// get all the unique occurances and the amount of times they occur
|
|
12710
|
+
let occurances = {};
|
|
12711
|
+
a.forEach((o) => {
|
|
12712
|
+
if (!occurances[o]){
|
|
12713
|
+
occurances[o] = 0;
|
|
12701
12714
|
}
|
|
12702
|
-
|
|
12703
|
-
|
|
12704
|
-
|
|
12705
|
-
|
|
12706
|
-
|
|
12707
|
-
|
|
12715
|
+
occurances[o]++;
|
|
12716
|
+
});
|
|
12717
|
+
// for all the items save the best streak (or streaks)
|
|
12718
|
+
let modes = [];
|
|
12719
|
+
let streak = 0;
|
|
12720
|
+
Object.keys(occurances).forEach((o) => {
|
|
12721
|
+
if (occurances[o] > streak){
|
|
12722
|
+
streak = occurances[o];
|
|
12723
|
+
modes = [o];
|
|
12724
|
+
} else if (occurances[o] === streak){
|
|
12725
|
+
modes.push(o);
|
|
12708
12726
|
}
|
|
12709
|
-
}
|
|
12710
|
-
|
|
12727
|
+
});
|
|
12728
|
+
// remap strings to numbers if possible
|
|
12729
|
+
return modes.map(m => isNaN(m) ? m : Number(m));
|
|
12711
12730
|
}
|
|
12712
12731
|
exports.mode = mode;
|
|
12713
12732
|
exports.common = mode;
|
|
@@ -12765,6 +12784,24 @@ exports.delta = change;
|
|
|
12765
12784
|
exports.difference = change;
|
|
12766
12785
|
exports.diff = change;
|
|
12767
12786
|
|
|
12787
|
+
// Calculate the Greatest Common Divisor from an array
|
|
12788
|
+
// The function uses the algorithm described in _gcd() above
|
|
12789
|
+
//
|
|
12790
|
+
// @param {Array} -> array to calculate on
|
|
12791
|
+
// @return {Int} -> greatest common divisor
|
|
12792
|
+
//
|
|
12793
|
+
exports.greatestCommonDivisor = gcd;
|
|
12794
|
+
exports.gcd = gcd;
|
|
12795
|
+
|
|
12796
|
+
// Calculate the Least Common Multiple from an array
|
|
12797
|
+
// the function uses the algorithm described in _lcd() above
|
|
12798
|
+
//
|
|
12799
|
+
// @param {Array} -> array to calculate on
|
|
12800
|
+
// @return {Int} -> least common multiple
|
|
12801
|
+
//
|
|
12802
|
+
exports.leastCommonMultiple = lcm;
|
|
12803
|
+
exports.lcm = lcm;
|
|
12804
|
+
|
|
12768
12805
|
},{"./transform":52,"./utility":54}],52:[function(require,module,exports){
|
|
12769
12806
|
//=======================================================================
|
|
12770
12807
|
// transform.js
|
|
@@ -13221,6 +13258,7 @@ function reverse(a=[0]){
|
|
|
13221
13258
|
return a.slice().reverse();
|
|
13222
13259
|
}
|
|
13223
13260
|
exports.reverse = reverse;
|
|
13261
|
+
exports.rev = reverse;
|
|
13224
13262
|
|
|
13225
13263
|
// rotate the position of items in an array
|
|
13226
13264
|
// 1 = direction right, -1 = direction left
|
|
@@ -13240,6 +13278,7 @@ function rotate(a=[0], r=0){
|
|
|
13240
13278
|
return arr;
|
|
13241
13279
|
}
|
|
13242
13280
|
exports.rotate = rotate;
|
|
13281
|
+
exports.rot = rotate;
|
|
13243
13282
|
|
|
13244
13283
|
// placeholder for the sort() method found in
|
|
13245
13284
|
// statistic.js
|
|
@@ -13326,19 +13365,33 @@ function spray(values=[0], beats=[0]){
|
|
|
13326
13365
|
}
|
|
13327
13366
|
exports.spray = spray;
|
|
13328
13367
|
|
|
13329
|
-
//
|
|
13330
|
-
//
|
|
13331
|
-
// length is the lowest common denominator of the input lists
|
|
13368
|
+
// Merge 2 or multiple lists by alternating over them.
|
|
13369
|
+
// The output length is the lowest common multiple of the input lists,
|
|
13332
13370
|
// so that every combination of consecutive values is included
|
|
13371
|
+
// until they all appeared an integer multiple of times.
|
|
13372
|
+
// This function is used to allow arrays as input for Generators
|
|
13373
|
+
// And for the step function for algorithmic composition
|
|
13374
|
+
//
|
|
13375
|
+
// @param {Array0, Array1, ..., Array-n} -> arrays to alternate/interleave
|
|
13376
|
+
// @return {Array} -> outputs a 2D array of the results
|
|
13377
|
+
//
|
|
13378
|
+
exports.stepMerge = arrayCombinations;
|
|
13379
|
+
|
|
13380
|
+
// Combine 2 or multiple lists by alternating over them.
|
|
13381
|
+
// Gives a similar result as lace except the output
|
|
13382
|
+
// length is the lowest common multiple of the input lists
|
|
13383
|
+
// so that every combination of consecutive values is included.
|
|
13384
|
+
// A higher dimension in the array is preserved.
|
|
13333
13385
|
//
|
|
13334
13386
|
// @param {Array0, Array1, ..., Array-n} -> arrays to interleave
|
|
13335
13387
|
// @return {Array} -> array of results 1 dimension less
|
|
13336
13388
|
//
|
|
13337
|
-
function
|
|
13389
|
+
function stepCombine(...arrs){
|
|
13338
13390
|
if (!arrs.length){ return [ 0 ] }
|
|
13339
13391
|
return flat(arrayCombinations(...arrs), 1);
|
|
13340
13392
|
}
|
|
13341
|
-
exports.
|
|
13393
|
+
exports.stepCombine = stepCombine;
|
|
13394
|
+
exports.step = stepCombine;
|
|
13342
13395
|
|
|
13343
13396
|
// stretch (or shrink) an array of numbers to a specified length
|
|
13344
13397
|
// interpolating the values to fill in the gaps.
|
|
@@ -13377,6 +13430,7 @@ exports.stretch = stretch;
|
|
|
13377
13430
|
// filter duplicate items from an array
|
|
13378
13431
|
// does not account for 2-dimensional arrays in the array
|
|
13379
13432
|
exports.unique = unique;
|
|
13433
|
+
exports.thin = unique;
|
|
13380
13434
|
|
|
13381
13435
|
},{"./statistic":51,"./utility":54}],53:[function(require,module,exports){
|
|
13382
13436
|
//==============================================================================
|
|
@@ -14620,9 +14674,13 @@ exports.arrayCalc = arrayCalc;
|
|
|
14620
14674
|
// Call a list function with provided arguments
|
|
14621
14675
|
// The difference is that first all the possible combinations of the arrays
|
|
14622
14676
|
// are calculated allowing arrays as arguments to generate
|
|
14623
|
-
// multiple versions of the function and joining them together
|
|
14677
|
+
// multiple versions of the function and joining them together afterwards
|
|
14624
14678
|
//
|
|
14625
|
-
function
|
|
14679
|
+
// @params {Function} -> The function name to use
|
|
14680
|
+
// @params {Arguments} -> The arguments applied to the function
|
|
14681
|
+
// @return {Anything} -> The result of the multi evaluated function
|
|
14682
|
+
//
|
|
14683
|
+
function multiEval(func, ...a){
|
|
14626
14684
|
// calculate the array combinations
|
|
14627
14685
|
let args = arrayCombinations(...a);
|
|
14628
14686
|
// call the function for all the argument combinations
|
|
@@ -14631,26 +14689,27 @@ function multiCall(func, ...a){
|
|
|
14631
14689
|
let out = flatten(args, 1);
|
|
14632
14690
|
return out;
|
|
14633
14691
|
}
|
|
14634
|
-
exports.
|
|
14692
|
+
exports.multiEval = multiEval;
|
|
14693
|
+
exports.multiCall = multiEval;
|
|
14635
14694
|
|
|
14636
14695
|
// Alternate through 2 or multiple lists consecutively
|
|
14637
|
-
// The output length is the lowest common
|
|
14696
|
+
// The output length is the lowest common multiple of the input lists
|
|
14638
14697
|
// so that every combination of consecutive values is included
|
|
14698
|
+
// until they all appeared an integer multiple of times.
|
|
14639
14699
|
// This function is used to allow arrays as input for Generators
|
|
14640
14700
|
// And for the step function for algorithmic composition
|
|
14641
14701
|
//
|
|
14642
|
-
// @param {Array0, Array1, ..., Array-n} -> arrays to interleave
|
|
14702
|
+
// @param {Array0, Array1, ..., Array-n} -> arrays to alternate/interleave
|
|
14643
14703
|
// @return {Array} -> outputs a 2D array of the results
|
|
14644
14704
|
//
|
|
14645
14705
|
function arrayCombinations(...arrs){
|
|
14646
14706
|
// make sure all items are an array of at least 1 item
|
|
14647
14707
|
arrs = arrs.map(a => toArray(a));
|
|
14648
|
-
// get the lengths,
|
|
14649
|
-
let sizes =
|
|
14650
|
-
//
|
|
14651
|
-
let iters =
|
|
14652
|
-
|
|
14653
|
-
// iterate over the total amount pushing the items to array
|
|
14708
|
+
// get the lengths, minimum of 1
|
|
14709
|
+
let sizes = arrs.map(a => Math.max(1, a.length));
|
|
14710
|
+
// get the least common multiple
|
|
14711
|
+
let iters = lcm(sizes);
|
|
14712
|
+
// iterate over the total length, pushing the items to array
|
|
14654
14713
|
let arr = [];
|
|
14655
14714
|
for (let i=0; i<iters; i++){
|
|
14656
14715
|
arr.push(arrs.map((e) => {
|
|
@@ -14661,6 +14720,67 @@ function arrayCombinations(...arrs){
|
|
|
14661
14720
|
}
|
|
14662
14721
|
exports.arrayCombinations = arrayCombinations;
|
|
14663
14722
|
|
|
14723
|
+
// Calculate the Greatest Common Divisor between 2 numbers
|
|
14724
|
+
// Based on the Euclid Algorithm described in:
|
|
14725
|
+
// https://en.wikipedia.org/wiki/Greatest_common_divisor
|
|
14726
|
+
//
|
|
14727
|
+
function _gcd(a, b){
|
|
14728
|
+
// greatest common divisor found if b equals 0
|
|
14729
|
+
if (b === 0){ return a; }
|
|
14730
|
+
// swap inputs and apply a mod b
|
|
14731
|
+
return _gcd(b, a % b);
|
|
14732
|
+
}
|
|
14733
|
+
|
|
14734
|
+
// Calculate the Least Common Multiple between 2 numbers
|
|
14735
|
+
// Based on the algorithm using the GCD() described in:
|
|
14736
|
+
// https://en.wikipedia.org/wiki/Least_common_multiple
|
|
14737
|
+
//
|
|
14738
|
+
function _lcm(a, b){
|
|
14739
|
+
return Math.abs(a) * ( Math.abs(b) / _gcd(a, b) );
|
|
14740
|
+
}
|
|
14741
|
+
|
|
14742
|
+
// Calculate the Greatest Common Divisor from an array
|
|
14743
|
+
// The function uses the algorithm described in _gcd() above
|
|
14744
|
+
//
|
|
14745
|
+
// @param {Array} -> array to calculate on
|
|
14746
|
+
// @return {Int} -> greatest common divisor
|
|
14747
|
+
//
|
|
14748
|
+
function gcd(a=[1]){
|
|
14749
|
+
a = toArray(a);
|
|
14750
|
+
// not enough values to calculate gcd
|
|
14751
|
+
if (a.length < 2){ return a[0]; }
|
|
14752
|
+
|
|
14753
|
+
let _greatest = a[0];
|
|
14754
|
+
// calculate gcd in pairs from the array
|
|
14755
|
+
for (let i = 1; i < a.length; i++){
|
|
14756
|
+
_greatest = _gcd(_greatest, a[i]);
|
|
14757
|
+
}
|
|
14758
|
+
return _greatest;
|
|
14759
|
+
}
|
|
14760
|
+
exports.greatestCommonDivisor = gcd;
|
|
14761
|
+
exports.gcd = gcd;
|
|
14762
|
+
|
|
14763
|
+
// Calculate the Least Common Multiple from an array
|
|
14764
|
+
// the function uses the algorithm described in _lcd() above
|
|
14765
|
+
//
|
|
14766
|
+
// @param {Array} -> array to calculate on
|
|
14767
|
+
// @return {Int} -> least common multiple
|
|
14768
|
+
//
|
|
14769
|
+
function lcm(a=[1]){
|
|
14770
|
+
a = toArray(a);
|
|
14771
|
+
// not enough values to calculate lcm
|
|
14772
|
+
if (a.length < 2){ return a[0]; }
|
|
14773
|
+
|
|
14774
|
+
let _least = a[0];
|
|
14775
|
+
// calculate lcm in pairs from the array
|
|
14776
|
+
for (let i = 1; i < a.length; i++){
|
|
14777
|
+
_least = _lcm(_least, a[i]);
|
|
14778
|
+
}
|
|
14779
|
+
return _least;
|
|
14780
|
+
}
|
|
14781
|
+
exports.leastCommonMultiple = lcm;
|
|
14782
|
+
exports.lcm = lcm;
|
|
14783
|
+
|
|
14664
14784
|
// flatten a multidimensional array. Optionally set the depth
|
|
14665
14785
|
// for the flattening
|
|
14666
14786
|
//
|
|
@@ -14862,12 +14982,12 @@ exports.draw = draw;
|
|
|
14862
14982
|
},{"asciichart":22}],55:[function(require,module,exports){
|
|
14863
14983
|
(function (process,global){(function (){
|
|
14864
14984
|
/**
|
|
14865
|
-
* WEBMIDI.js v3.1.
|
|
14985
|
+
* WEBMIDI.js v3.1.14
|
|
14866
14986
|
* A JavaScript library to kickstart your MIDI projects
|
|
14867
14987
|
* https://webmidijs.org
|
|
14868
|
-
* Build generated on
|
|
14988
|
+
* Build generated on October 11th, 2025.
|
|
14869
14989
|
*
|
|
14870
|
-
* © Copyright 2015-
|
|
14990
|
+
* © Copyright 2015-2025, Jean-Philippe Côté.
|
|
14871
14991
|
*
|
|
14872
14992
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
|
14873
14993
|
* in compliance with the License. You may obtain a copy of the License at
|
|
@@ -15079,7 +15199,7 @@ static toNoteNumber(e,t=0){if(t=null==t?0:parseInt(t),isNaN(t))throw new RangeEr
|
|
|
15079
15199
|
*
|
|
15080
15200
|
* @license Apache-2.0
|
|
15081
15201
|
* @since 3.0.0
|
|
15082
|
-
*/sendControlChange(e,t,n={}){if("string"==typeof e&&(e=Utilities.getCcNumberByName(e)),Array.isArray(t)||(t=[t]),wm.validation){if(void 0===e)throw new TypeError("Control change must be identified with a valid name or an integer between 0 and 127.");if(!Number.isInteger(e)||!(e>=0&&e<=127))throw new TypeError("Control change number must be an integer between 0 and 127.");if(2===(t=t.map(e=>{const t=Math.min(Math.max(parseInt(e),0),127);if(isNaN(t))throw new TypeError("Values must be integers between 0 and 127");return t})).length&&e>=32)throw new TypeError("To use a value array, the controller must be between 0 and 31")}return t.forEach((r,i)=>{this.send([(Enumerations.CHANNEL_MESSAGES.controlchange<<4)+(this.number-1),e+32*i,t[i]],{time:Utilities.toTimestamp(n.time)})}),this}_selectNonRegisteredParameter(e,t={}){return this.sendControlChange(99,e[0],t),this.sendControlChange(98,e[1],t),this}_deselectRegisteredParameter(e={}){return this.sendControlChange(101,127,e),this.sendControlChange(100,127,e),this}_deselectNonRegisteredParameter(e={}){return this.sendControlChange(101,127,e),this.sendControlChange(100,127,e),this}_selectRegisteredParameter(e,t={}){return this.sendControlChange(101,e[0],t),this.sendControlChange(100,e[1],t),this}_setCurrentParameter(e,t={}){return e=[].concat(e),this.sendControlChange(6,e[0],t),e.length<2||this.sendControlChange(38,e[1],t),this}sendRpnDecrement(e,t={}){if(Array.isArray(e)||(e=Enumerations.REGISTERED_PARAMETERS[e]),wm.validation){if(void 0===e)throw new TypeError("The specified registered parameter is invalid.");let t=!1;if(Object.getOwnPropertyNames(Enumerations.REGISTERED_PARAMETERS).forEach(n=>{Enumerations.REGISTERED_PARAMETERS[n][0]===e[0]&&Enumerations.REGISTERED_PARAMETERS[n][1]===e[1]&&(t=!0)}),!t)throw new TypeError("The specified registered parameter is invalid.")}return this._selectRegisteredParameter(e,t),this.sendControlChange(97,0,t),this._deselectRegisteredParameter(t),this}sendRpnIncrement(e,t={}){if(Array.isArray(e)||(e=Enumerations.REGISTERED_PARAMETERS[e]),wm.validation){if(void 0===e)throw new TypeError("The specified registered parameter is invalid.");let t=!1;if(Object.getOwnPropertyNames(Enumerations.REGISTERED_PARAMETERS).forEach(n=>{Enumerations.REGISTERED_PARAMETERS[n][0]===e[0]&&Enumerations.REGISTERED_PARAMETERS[n][1]===e[1]&&(t=!0)}),!t)throw new TypeError("The specified registered parameter is invalid.")}return this._selectRegisteredParameter(e,t),this.sendControlChange(96,0,t),this._deselectRegisteredParameter(t),this}playNote(e,t={}){this.sendNoteOn(e,t);const n=Array.isArray(e)?e:[e];for(let e of n)if(parseInt(e.duration)>0){const n={time:(Utilities.toTimestamp(t.time)||wm.time)+parseInt(e.duration),release:e.release,rawRelease:e.rawRelease};this.sendNoteOff(e,n)}else if(parseInt(t.duration)>0){const n={time:(Utilities.toTimestamp(t.time)||wm.time)+parseInt(t.duration),release:t.release,rawRelease:t.rawRelease};this.sendNoteOff(e,n)}return this}sendNoteOff(e,t={}){if(wm.validation){if(null!=t.rawRelease&&!(t.rawRelease>=0&&t.rawRelease<=127))throw new RangeError("The 'rawRelease' option must be an integer between 0 and 127");if(null!=t.release&&!(t.release>=0&&t.release<=1))throw new RangeError("The 'release' option must be an number between 0 and 1");t.rawVelocity&&(t.rawRelease=t.velocity,console.warn("The 'rawVelocity' option is deprecated. Use 'rawRelease' instead.")),t.velocity&&(t.release=t.velocity,console.warn("The 'velocity' option is deprecated. Use 'attack' instead."))}let n=64;null!=t.rawRelease?n=t.rawRelease:isNaN(t.release)||(n=Math.round(127*t.release));const r=wm.octaveOffset+this.output.octaveOffset+this.octaveOffset;return Utilities.buildNoteArray(e,{rawRelease:parseInt(n)}).forEach(e=>{this.send([(Enumerations.CHANNEL_MESSAGES.noteoff<<4)+(this.number-1),e.getOffsetNumber(r),e.rawRelease],{time:Utilities.toTimestamp(t.time)})}),this}stopNote(e,t={}){return this.sendNoteOff(e,t)}sendNoteOn(e,t={}){if(wm.validation){if(null!=t.rawAttack&&!(t.rawAttack>=0&&t.rawAttack<=127))throw new RangeError("The 'rawAttack' option must be an integer between 0 and 127");if(null!=t.attack&&!(t.attack>=0&&t.attack<=1))throw new RangeError("The 'attack' option must be an number between 0 and 1");t.rawVelocity&&(t.rawAttack=t.velocity,t.rawRelease=t.release,console.warn("The 'rawVelocity' option is deprecated. Use 'rawAttack' or 'rawRelease'.")),t.velocity&&(t.attack=t.velocity,console.warn("The 'velocity' option is deprecated. Use 'attack' instead."))}let n=64;null!=t.rawAttack?n=t.rawAttack:isNaN(t.attack)||(n=Math.round(127*t.attack));const r=wm.octaveOffset+this.output.octaveOffset+this.octaveOffset;return Utilities.buildNoteArray(e,{rawAttack:n}).forEach(e=>{this.send([(Enumerations.CHANNEL_MESSAGES.noteon<<4)+(this.number-1),e.getOffsetNumber(r),e.rawAttack],{time:Utilities.toTimestamp(t.time)})}),this}sendChannelMode(e,t=0,n={}){if("string"==typeof e&&(e=Enumerations.CHANNEL_MODE_MESSAGES[e]),wm.validation){if(void 0===e)throw new TypeError("Invalid channel mode message name or number.");if(isNaN(e)||!(e>=120&&e<=127))throw new TypeError("Invalid channel mode message number.");if(isNaN(parseInt(t))||t<0||t>127)throw new RangeError("Value must be an integer between 0 and 127.")}return this.send([(Enumerations.CHANNEL_MESSAGES.controlchange<<4)+(this.number-1),e,t],{time:Utilities.toTimestamp(n.time)}),this}sendOmniMode(e,t={}){return void 0===e||e?this.sendChannelMode("omnimodeon",0,t):this.sendChannelMode("omnimodeoff",0,t),this}sendChannelAftertouch(e,t={}){if(wm.validation){if(isNaN(parseFloat(e)))throw new RangeError("Invalid channel aftertouch value.");if(t.rawValue){if(!(e>=0&&e<=127&&Number.isInteger(e)))throw new RangeError("Channel aftertouch raw value must be an integer between 0 and 127.")}else if(!(e>=0&&e<=1))throw new RangeError("Channel aftertouch value must be a float between 0 and 1.")}return this.send([(Enumerations.CHANNEL_MESSAGES.channelaftertouch<<4)+(this.number-1),Math.round(127*e)],{time:Utilities.toTimestamp(t.time)}),this}sendMasterTuning(e,t={}){if(e=parseFloat(e)||0,wm.validation&&!(e>-65&&e<64))throw new RangeError("The value must be a decimal number larger than -65 and smaller than 64.");let n=Math.floor(e)+64,r=e-Math.floor(e);r=Math.round((r+1)/2*16383);let i=r>>7&127,s=127&r;return this.sendRpnValue("channelcoarsetuning",n,t),this.sendRpnValue("channelfinetuning",[i,s],t),this}sendModulationRange(e,t,n={}){if(wm.validation){if(!Number.isInteger(e)||!(e>=0&&e<=127))throw new RangeError("The semitones value must be an integer between 0 and 127.");if(!(null==t||Number.isInteger(t)&&t>=0&&t<=127))throw new RangeError("If specified, the cents value must be an integer between 0 and 127.")}return t>=0&&t<=127||(t=0),this.sendRpnValue("modulationrange",[e,t],n),this}sendNrpnValue(e,t,n={}){if(t=[].concat(t),wm.validation){if(!Array.isArray(e)||!Number.isInteger(e[0])||!Number.isInteger(e[1]))throw new TypeError("The specified NRPN is invalid.");if(!(e[0]>=0&&e[0]<=127))throw new RangeError("The first byte of the NRPN must be between 0 and 127.");if(!(e[1]>=0&&e[1]<=127))throw new RangeError("The second byte of the NRPN must be between 0 and 127.");t.forEach(e=>{if(!(e>=0&&e<=127))throw new RangeError("The data bytes of the NRPN must be between 0 and 127.")})}return this._selectNonRegisteredParameter(e,n),this._setCurrentParameter(t,n),this._deselectNonRegisteredParameter(n),this}sendPitchBend(e,t={}){if(wm.validation)if(t.rawValue&&Array.isArray(e)){if(!(e[0]>=0&&e[0]<=127))throw new RangeError("The pitch bend MSB must be an integer between 0 and 127.");if(!(e[1]>=0&&e[1]<=127))throw new RangeError("The pitch bend LSB must be an integer between 0 and 127.")}else if(t.rawValue&&!Array.isArray(e)){if(!(e>=0&&e<=127))throw new RangeError("The pitch bend MSB must be an integer between 0 and 127.")}else{if(isNaN(e)||null===e)throw new RangeError("Invalid pitch bend value.");if(!(e>=-1&&e<=1))throw new RangeError("The pitch bend value must be a float between -1 and 1.")}let n=0,r=0;if(t.rawValue&&Array.isArray(e))n=e[0],r=e[1];else if(t.rawValue&&!Array.isArray(e))n=e;else{const t=Utilities.fromFloatToMsbLsb((e+1)/2);n=t.msb,r=t.lsb}return this.send([(Enumerations.CHANNEL_MESSAGES.pitchbend<<4)+(this.number-1),r,n],{time:Utilities.toTimestamp(t.time)}),this}sendPitchBendRange(e,t,n={}){if(wm.validation){if(!Number.isInteger(e)||!(e>=0&&e<=127))throw new RangeError("The semitones value must be an integer between 0 and 127.");if(!Number.isInteger(t)||!(t>=0&&t<=127))throw new RangeError("The cents value must be an integer between 0 and 127.")}return this.sendRpnValue("pitchbendrange",[e,t],n),this}sendProgramChange(e,t={}){if(e=parseInt(e)||0,wm.validation&&!(e>=0&&e<=127))throw new RangeError("The program number must be between 0 and 127.");return this.send([(Enumerations.CHANNEL_MESSAGES.programchange<<4)+(this.number-1),e],{time:Utilities.toTimestamp(t.time)}),this}sendRpnValue(e,t,n={}){if(Array.isArray(e)||(e=Enumerations.REGISTERED_PARAMETERS[e]),wm.validation){if(!Number.isInteger(e[0])||!Number.isInteger(e[1]))throw new TypeError("The specified NRPN is invalid.");if(!(e[0]>=0&&e[0]<=127))throw new RangeError("The first byte of the RPN must be between 0 and 127.");if(!(e[1]>=0&&e[1]<=127))throw new RangeError("The second byte of the RPN must be between 0 and 127.");[].concat(t).forEach(e=>{if(!(e>=0&&e<=127))throw new RangeError("The data bytes of the RPN must be between 0 and 127.")})}return this._selectRegisteredParameter(e,n),this._setCurrentParameter(t,n),this._deselectRegisteredParameter(n),this}sendTuningBank(e,t={}){if(wm.validation&&(!Number.isInteger(e)||!(e>=0&&e<=127)))throw new RangeError("The tuning bank number must be between 0 and 127.");return this.sendRpnValue("tuningbank",e,t),this}sendTuningProgram(e,t={}){if(wm.validation&&(!Number.isInteger(e)||!(e>=0&&e<=127)))throw new RangeError("The tuning program number must be between 0 and 127.");return this.sendRpnValue("tuningprogram",e,t),this}sendLocalControl(e,t={}){return e?this.sendChannelMode("localcontrol",127,t):this.sendChannelMode("localcontrol",0,t)}sendAllNotesOff(e={}){return this.sendChannelMode("allnotesoff",0,e)}sendAllSoundOff(e={}){return this.sendChannelMode("allsoundoff",0,e)}sendResetAllControllers(e={}){return this.sendChannelMode("resetallcontrollers",0,e)}sendPolyphonicMode(e,t={}){return"mono"===e?this.sendChannelMode("monomodeon",0,t):this.sendChannelMode("polymodeon",0,t)}get octaveOffset(){return this._octaveOffset}set octaveOffset(e){if(this.validation&&(e=parseInt(e),isNaN(e)))throw new TypeError("The 'octaveOffset' property must be an integer.");this._octaveOffset=e}get output(){return this._output}get number(){return this._number}}
|
|
15202
|
+
*/sendControlChange(e,t,n={}){if("string"==typeof e&&(e=Utilities.getCcNumberByName(e)),Array.isArray(t)||(t=[t]),wm.validation){if(void 0===e)throw new TypeError("Control change must be identified with a valid name or an integer between 0 and 127.");if(!Number.isInteger(e)||!(e>=0&&e<=127))throw new TypeError("Control change number must be an integer between 0 and 127.");if(2===(t=t.map(e=>{const t=Math.min(Math.max(parseInt(e),0),127);if(isNaN(t))throw new TypeError("Values must be integers between 0 and 127");return t})).length&&e>=32)throw new TypeError("To use a value array, the controller must be between 0 and 31")}return t.forEach((r,i)=>{this.send([(Enumerations.CHANNEL_MESSAGES.controlchange<<4)+(this.number-1),e+32*i,t[i]],{time:Utilities.toTimestamp(n.time)})}),this}_selectNonRegisteredParameter(e,t={}){return this.sendControlChange(99,e[0],t),this.sendControlChange(98,e[1],t),this}_deselectRegisteredParameter(e={}){return this.sendControlChange(101,127,e),this.sendControlChange(100,127,e),this}_deselectNonRegisteredParameter(e={}){return this.sendControlChange(101,127,e),this.sendControlChange(100,127,e),this}_selectRegisteredParameter(e,t={}){return this.sendControlChange(101,e[0],t),this.sendControlChange(100,e[1],t),this}_setCurrentParameter(e,t={}){return e=[].concat(e),this.sendControlChange(6,e[0],t),e.length<2||this.sendControlChange(38,e[1],t),this}sendRpnDecrement(e,t={}){if(Array.isArray(e)||(e=Enumerations.REGISTERED_PARAMETERS[e]),wm.validation){if(void 0===e)throw new TypeError("The specified registered parameter is invalid.");let t=!1;if(Object.getOwnPropertyNames(Enumerations.REGISTERED_PARAMETERS).forEach(n=>{Enumerations.REGISTERED_PARAMETERS[n][0]===e[0]&&Enumerations.REGISTERED_PARAMETERS[n][1]===e[1]&&(t=!0)}),!t)throw new TypeError("The specified registered parameter is invalid.")}return this._selectRegisteredParameter(e,t),this.sendControlChange(97,0,t),this._deselectRegisteredParameter(t),this}sendRpnIncrement(e,t={}){if(Array.isArray(e)||(e=Enumerations.REGISTERED_PARAMETERS[e]),wm.validation){if(void 0===e)throw new TypeError("The specified registered parameter is invalid.");let t=!1;if(Object.getOwnPropertyNames(Enumerations.REGISTERED_PARAMETERS).forEach(n=>{Enumerations.REGISTERED_PARAMETERS[n][0]===e[0]&&Enumerations.REGISTERED_PARAMETERS[n][1]===e[1]&&(t=!0)}),!t)throw new TypeError("The specified registered parameter is invalid.")}return this._selectRegisteredParameter(e,t),this.sendControlChange(96,0,t),this._deselectRegisteredParameter(t),this}playNote(e,t={}){this.sendNoteOn(e,t);const n=Array.isArray(e)?e:[e];for(let e of n)if(parseInt(e.duration)>0){const n={time:(Utilities.toTimestamp(t.time)||wm.time)+parseInt(e.duration),release:e.release,rawRelease:e.rawRelease};this.sendNoteOff(e,n)}else if(parseInt(t.duration)>0){const n={time:(Utilities.toTimestamp(t.time)||wm.time)+parseInt(t.duration),release:t.release,rawRelease:t.rawRelease};this.sendNoteOff(e,n)}return this}sendNoteOff(e,t={}){if(wm.validation){if(null!=t.rawRelease&&!(t.rawRelease>=0&&t.rawRelease<=127))throw new RangeError("The 'rawRelease' option must be an integer between 0 and 127");if(null!=t.release&&!(t.release>=0&&t.release<=1))throw new RangeError("The 'release' option must be an number between 0 and 1");t.rawVelocity&&(t.rawRelease=t.velocity,console.warn("The 'rawVelocity' option is deprecated. Use 'rawRelease' instead.")),t.velocity&&(t.release=t.velocity,console.warn("The 'velocity' option is deprecated. Use 'attack' instead."))}let n=64;null!=t.rawRelease?n=t.rawRelease:isNaN(t.release)||(n=Math.round(127*t.release));const r=wm.octaveOffset+this.output.octaveOffset+this.octaveOffset;return Utilities.buildNoteArray(e,{rawRelease:parseInt(n)}).forEach(e=>{this.send([(Enumerations.CHANNEL_MESSAGES.noteoff<<4)+(this.number-1),e.getOffsetNumber(r),e.rawRelease],{time:Utilities.toTimestamp(t.time)})}),this}stopNote(e,t={}){return this.sendNoteOff(e,t)}sendNoteOn(e,t={}){if(wm.validation){if(null!=t.rawAttack&&!(t.rawAttack>=0&&t.rawAttack<=127))throw new RangeError("The 'rawAttack' option must be an integer between 0 and 127");if(null!=t.attack&&!(t.attack>=0&&t.attack<=1))throw new RangeError("The 'attack' option must be an number between 0 and 1");t.rawVelocity&&(t.rawAttack=t.velocity,t.rawRelease=t.release,console.warn("The 'rawVelocity' option is deprecated. Use 'rawAttack' or 'rawRelease'.")),t.velocity&&(t.attack=t.velocity,console.warn("The 'velocity' option is deprecated. Use 'attack' instead."))}let n=64;null!=t.rawAttack?n=t.rawAttack:isNaN(t.attack)||(n=Math.round(127*t.attack));const r=wm.octaveOffset+this.output.octaveOffset+this.octaveOffset;return Utilities.buildNoteArray(e,{rawAttack:n}).forEach(e=>{this.send([(Enumerations.CHANNEL_MESSAGES.noteon<<4)+(this.number-1),e.getOffsetNumber(r),e.rawAttack],{time:Utilities.toTimestamp(t.time)})}),this}sendChannelMode(e,t=0,n={}){if("string"==typeof e&&(e=Enumerations.CHANNEL_MODE_MESSAGES[e]),wm.validation){if(void 0===e)throw new TypeError("Invalid channel mode message name or number.");if(isNaN(e)||!(e>=120&&e<=127))throw new TypeError("Invalid channel mode message number.");if(isNaN(parseInt(t))||t<0||t>127)throw new RangeError("Value must be an integer between 0 and 127.")}return this.send([(Enumerations.CHANNEL_MESSAGES.controlchange<<4)+(this.number-1),e,t],{time:Utilities.toTimestamp(n.time)}),this}sendOmniMode(e,t={}){return void 0===e||e?this.sendChannelMode("omnimodeon",0,t):this.sendChannelMode("omnimodeoff",0,t),this}sendChannelAftertouch(e,t={}){if(wm.validation){if(isNaN(parseFloat(e)))throw new RangeError("Invalid channel aftertouch value.");if(t.rawValue){if(!(e>=0&&e<=127&&Number.isInteger(e)))throw new RangeError("Channel aftertouch raw value must be an integer between 0 and 127.")}else if(!(e>=0&&e<=1))throw new RangeError("Channel aftertouch value must be a float between 0 and 1.")}return t.rawValue||(e=Utilities.fromFloatTo7Bit(e)),this.send([(Enumerations.CHANNEL_MESSAGES.channelaftertouch<<4)+(this.number-1),Math.round(e)],{time:Utilities.toTimestamp(t.time)}),this}sendMasterTuning(e,t={}){if(e=parseFloat(e)||0,wm.validation&&!(e>-65&&e<64))throw new RangeError("The value must be a decimal number larger than -65 and smaller than 64.");let n=Math.floor(e)+64,r=e-Math.floor(e);r=Math.round((r+1)/2*16383);let i=r>>7&127,s=127&r;return this.sendRpnValue("channelcoarsetuning",n,t),this.sendRpnValue("channelfinetuning",[i,s],t),this}sendModulationRange(e,t,n={}){if(wm.validation){if(!Number.isInteger(e)||!(e>=0&&e<=127))throw new RangeError("The semitones value must be an integer between 0 and 127.");if(!(null==t||Number.isInteger(t)&&t>=0&&t<=127))throw new RangeError("If specified, the cents value must be an integer between 0 and 127.")}return t>=0&&t<=127||(t=0),this.sendRpnValue("modulationrange",[e,t],n),this}sendNrpnValue(e,t,n={}){if(t=[].concat(t),wm.validation){if(!Array.isArray(e)||!Number.isInteger(e[0])||!Number.isInteger(e[1]))throw new TypeError("The specified NRPN is invalid.");if(!(e[0]>=0&&e[0]<=127))throw new RangeError("The first byte of the NRPN must be between 0 and 127.");if(!(e[1]>=0&&e[1]<=127))throw new RangeError("The second byte of the NRPN must be between 0 and 127.");t.forEach(e=>{if(!(e>=0&&e<=127))throw new RangeError("The data bytes of the NRPN must be between 0 and 127.")})}return this._selectNonRegisteredParameter(e,n),this._setCurrentParameter(t,n),this._deselectNonRegisteredParameter(n),this}sendPitchBend(e,t={}){if(wm.validation)if(t.rawValue&&Array.isArray(e)){if(!(e[0]>=0&&e[0]<=127))throw new RangeError("The pitch bend MSB must be an integer between 0 and 127.");if(!(e[1]>=0&&e[1]<=127))throw new RangeError("The pitch bend LSB must be an integer between 0 and 127.")}else if(t.rawValue&&!Array.isArray(e)){if(!(e>=0&&e<=127))throw new RangeError("The pitch bend MSB must be an integer between 0 and 127.")}else{if(isNaN(e)||null===e)throw new RangeError("Invalid pitch bend value.");if(!(e>=-1&&e<=1))throw new RangeError("The pitch bend value must be a float between -1 and 1.")}let n=0,r=0;if(t.rawValue&&Array.isArray(e))n=e[0],r=e[1];else if(t.rawValue&&!Array.isArray(e))n=e;else{const t=Utilities.fromFloatToMsbLsb((e+1)/2);n=t.msb,r=t.lsb}return this.send([(Enumerations.CHANNEL_MESSAGES.pitchbend<<4)+(this.number-1),r,n],{time:Utilities.toTimestamp(t.time)}),this}sendPitchBendRange(e,t,n={}){if(wm.validation){if(!Number.isInteger(e)||!(e>=0&&e<=127))throw new RangeError("The semitones value must be an integer between 0 and 127.");if(!Number.isInteger(t)||!(t>=0&&t<=127))throw new RangeError("The cents value must be an integer between 0 and 127.")}return this.sendRpnValue("pitchbendrange",[e,t],n),this}sendProgramChange(e,t={}){if(e=parseInt(e)||0,wm.validation&&!(e>=0&&e<=127))throw new RangeError("The program number must be between 0 and 127.");return this.send([(Enumerations.CHANNEL_MESSAGES.programchange<<4)+(this.number-1),e],{time:Utilities.toTimestamp(t.time)}),this}sendRpnValue(e,t,n={}){if(Array.isArray(e)||(e=Enumerations.REGISTERED_PARAMETERS[e]),wm.validation){if(!Number.isInteger(e[0])||!Number.isInteger(e[1]))throw new TypeError("The specified NRPN is invalid.");if(!(e[0]>=0&&e[0]<=127))throw new RangeError("The first byte of the RPN must be between 0 and 127.");if(!(e[1]>=0&&e[1]<=127))throw new RangeError("The second byte of the RPN must be between 0 and 127.");[].concat(t).forEach(e=>{if(!(e>=0&&e<=127))throw new RangeError("The data bytes of the RPN must be between 0 and 127.")})}return this._selectRegisteredParameter(e,n),this._setCurrentParameter(t,n),this._deselectRegisteredParameter(n),this}sendTuningBank(e,t={}){if(wm.validation&&(!Number.isInteger(e)||!(e>=0&&e<=127)))throw new RangeError("The tuning bank number must be between 0 and 127.");return this.sendRpnValue("tuningbank",e,t),this}sendTuningProgram(e,t={}){if(wm.validation&&(!Number.isInteger(e)||!(e>=0&&e<=127)))throw new RangeError("The tuning program number must be between 0 and 127.");return this.sendRpnValue("tuningprogram",e,t),this}sendLocalControl(e,t={}){return e?this.sendChannelMode("localcontrol",127,t):this.sendChannelMode("localcontrol",0,t)}sendAllNotesOff(e={}){return this.sendChannelMode("allnotesoff",0,e)}sendAllSoundOff(e={}){return this.sendChannelMode("allsoundoff",0,e)}sendResetAllControllers(e={}){return this.sendChannelMode("resetallcontrollers",0,e)}sendPolyphonicMode(e,t={}){return"mono"===e?this.sendChannelMode("monomodeon",0,t):this.sendChannelMode("polymodeon",0,t)}get octaveOffset(){return this._octaveOffset}set octaveOffset(e){if(this.validation&&(e=parseInt(e),isNaN(e)))throw new TypeError("The 'octaveOffset' property must be an integer.");this._octaveOffset=e}get output(){return this._output}get number(){return this._number}}
|
|
15083
15203
|
/**
|
|
15084
15204
|
* The `Output` class represents a single MIDI output port (not to be confused with a MIDI channel).
|
|
15085
15205
|
* A port is made available by a MIDI device. A MIDI device can advertise several input and output
|
|
@@ -15305,7 +15425,7 @@ static toNoteNumber(e,t=0){if(t=null==t?0:parseInt(t),isNaN(t))throw new RangeEr
|
|
|
15305
15425
|
*
|
|
15306
15426
|
* @extends EventEmitter
|
|
15307
15427
|
* @license Apache-2.0
|
|
15308
|
-
*/class Input extends EventEmitter{constructor(e){super(),this._midiInput=e,this._octaveOffset=0,this.channels=[];for(let e=1;e<=16;e++)this.channels[e]=new InputChannel(this,e);this._forwarders=[],this._midiInput.onstatechange=this._onStateChange.bind(this),this._midiInput.onmidimessage=this._onMidiMessage.bind(this)}async destroy(){this.removeListener(),this.channels.forEach(e=>e.destroy()),this.channels=[],this._forwarders=[],this._midiInput&&(this._midiInput.onstatechange=null,this._midiInput.onmidimessage=null),await this.close(),this._midiInput=null}_onStateChange(e){let t={timestamp:wm.time,target:this,port:this};"open"===e.port.connection?(t.type="opened",this.emit("opened",t)):"closed"===e.port.connection&&"connected"===e.port.state?(t.type="closed",this.emit("closed",t)):"closed"===e.port.connection&&"disconnected"===e.port.state?(t.type="disconnected",t.port={connection:e.port.connection,id:e.port.id,manufacturer:e.port.manufacturer,name:e.port.name,state:e.port.state,type:e.port.type},this.emit("disconnected",t)):"pending"===e.port.connection&&"disconnected"===e.port.state||console.warn("This statechange event was not caught: ",e.port.connection,e.port.state)}_onMidiMessage(e){const t=new Message(e.data),n={port:this,target:this,message:t,timestamp:e.timeStamp,type:"midimessage",data:t.data,rawData:t.data,statusByte:t.data[0],dataBytes:t.dataBytes};this.emit("midimessage",n),t.isSystemMessage?this._parseEvent(n):t.isChannelMessage&&this.channels[t.channel]._processMidiMessageEvent(n),this._forwarders.forEach(e=>e.forward(t))}_parseEvent(e){const t=Object.assign({},e);t.type=t.message.type||"unknownmidimessage","songselect"===t.type&&(t.song=e.data[1]+1,t.value=e.data[1],t.rawValue=t.value),this.emit(t.type,t)}async open(){try{await this._midiInput.open()}catch(e){return Promise.reject(e)}return Promise.resolve(this)}async close(){if(!this._midiInput)return Promise.resolve(this);try{await this._midiInput.close()}catch(e){return Promise.reject(e)}return Promise.resolve(this)}getChannelModeByNumber(){wm.validation&&console.warn("The 'getChannelModeByNumber()' method has been moved to the 'Utilities' class.")}addListener(e,t,n={}){if(wm.validation&&"function"==typeof n){let e=null!=t?[].concat(t):void 0;t=n,n={channels:e}}if(Enumerations.CHANNEL_EVENTS.includes(e)){void 0===n.channels&&(n.channels=Enumerations.MIDI_CHANNEL_NUMBERS);let r=[];return Utilities.sanitizeChannels(n.channels).forEach(i=>{r.push(this.channels[i].addListener(e,t,n))}),r}return super.addListener(e,t,n)}addOneTimeListener(e,t,n={}){return n.remaining=1,this.addListener(e,t,n)}on(e,t,n,r){return this.addListener(e,t,n,r)}hasListener(e,t,n={}){if(wm.validation&&"function"==typeof n){let e=[].concat(t);t=n,n={channels:e}}return Enumerations.CHANNEL_EVENTS.includes(e)?(void 0===n.channels&&(n.channels=Enumerations.MIDI_CHANNEL_NUMBERS),Utilities.sanitizeChannels(n.channels).every(n=>this.channels[n].hasListener(e,t))):super.hasListener(e,t)}removeListener(e,t,n={}){if(wm.validation&&"function"==typeof n){let e=[].concat(t);t=n,n={channels:e}}if(void 0===n.channels&&(n.channels=Enumerations.MIDI_CHANNEL_NUMBERS),null==e)return Utilities.sanitizeChannels(n.channels).forEach(e=>{this.channels[e]&&this.channels[e].removeListener()}),super.removeListener();Enumerations.CHANNEL_EVENTS.includes(e)?Utilities.sanitizeChannels(n.channels).forEach(r=>{this.channels[r].removeListener(e,t,n)}):super.removeListener(e,t,n)}addForwarder(e,t={}){let n;return n=e instanceof Forwarder?e:new Forwarder(e,t),this._forwarders.push(n),n}removeForwarder(e){this._forwarders=this._forwarders.filter(t=>t!==e)}hasForwarder(e){return this._forwarders.includes(e)}get name(){return this._midiInput.name}get id(){return this._midiInput.id}get connection(){return this._midiInput.connection}get manufacturer(){return this._midiInput.manufacturer}get octaveOffset(){return this._octaveOffset}set octaveOffset(e){if(this.validation&&(e=parseInt(e),isNaN(e)))throw new TypeError("The 'octaveOffset' property must be an integer.");this._octaveOffset=e}get state(){return this._midiInput.state}get type(){return this._midiInput.type}get nrpnEventsEnabled(){return wm.validation&&console.warn("The 'nrpnEventsEnabled' property has been moved to the 'InputChannel' class."),!1}}if(Utilities.isNode){try{window.navigator}catch(err){let jzz;eval('jzz = require("jzz")'),global.navigator=jzz}try{performance}catch(err){let performance;eval('performance = require("perf_hooks").performance'),global.performance=performance}}
|
|
15428
|
+
*/class Input extends EventEmitter{constructor(e){super(),this._midiInput=e,this._octaveOffset=0,this.channels=[];for(let e=1;e<=16;e++)this.channels[e]=new InputChannel(this,e);this._forwarders=[],this._midiInput.onstatechange=this._onStateChange.bind(this),this._midiInput.onmidimessage=this._onMidiMessage.bind(this)}async destroy(){this.removeListener(),this.channels.forEach(e=>e.destroy()),this.channels=[],this._forwarders=[],this._midiInput&&(this._midiInput.onstatechange=null,this._midiInput.onmidimessage=null),await this.close(),this._midiInput=null}_onStateChange(e){let t={timestamp:wm.time,target:this,port:this};"open"===e.port.connection?(t.type="opened",this.emit("opened",t)):"closed"===e.port.connection&&"connected"===e.port.state?(t.type="closed",this.emit("closed",t)):"closed"===e.port.connection&&"disconnected"===e.port.state?(t.type="disconnected",t.port={connection:e.port.connection,id:e.port.id,manufacturer:e.port.manufacturer,name:e.port.name,state:e.port.state,type:e.port.type},this.emit("disconnected",t)):"pending"===e.port.connection&&"disconnected"===e.port.state||console.warn("This statechange event was not caught: ",e.port.connection,e.port.state)}_onMidiMessage(e){const t=new Message(e.data),n={port:this,target:this,message:t,timestamp:e.timeStamp,type:"midimessage",data:t.data,rawData:t.data,statusByte:t.data[0],dataBytes:t.dataBytes};this.emit("midimessage",n),t.isSystemMessage?this._parseEvent(n):t.isChannelMessage&&this.channels[t.channel]._processMidiMessageEvent(n),this._forwarders.forEach(e=>e.forward(t))}_parseEvent(e){const t=Object.assign({},e);t.type=t.message.type||"unknownmidimessage","songselect"===t.type&&(t.song=e.data[1]+1,t.value=e.data[1],t.rawValue=t.value),this.emit(t.type,t)}async open(){try{await this._midiInput.open()}catch(e){return Promise.reject(e)}return Promise.resolve(this)}async close(){if(!this._midiInput)return Promise.resolve(this);try{await this._midiInput.close()}catch(e){return Promise.reject(e)}return Promise.resolve(this)}getChannelModeByNumber(){wm.validation&&console.warn("The 'getChannelModeByNumber()' method has been moved to the 'Utilities' class.")}addListener(e,t,n={}){if(wm.validation&&"function"==typeof n){let e=null!=t?[].concat(t):void 0;t=n,n={channels:e}}if(Enumerations.CHANNEL_EVENTS.includes(e)){void 0===n.channels&&(n.channels=Enumerations.MIDI_CHANNEL_NUMBERS);let r=[];return Utilities.sanitizeChannels(n.channels).forEach(i=>{r.push(this.channels[i].addListener(e,t,n))}),r}return super.addListener(e,t,n)}addOneTimeListener(e,t,n={}){return n.remaining=1,this.addListener(e,t,n)}on(e,t,n,r){return this.addListener(e,t,n,r)}hasListener(e,t,n={}){if(wm.validation&&"function"==typeof n){let e=[].concat(t);t=n,n={channels:e}}return Enumerations.CHANNEL_EVENTS.includes(e)?(void 0===n.channels&&(n.channels=Enumerations.MIDI_CHANNEL_NUMBERS),Utilities.sanitizeChannels(n.channels).every(n=>this.channels[n].hasListener(e,t))):super.hasListener(e,t)}removeListener(e,t,n={}){if(wm.validation&&"function"==typeof n){let e=[].concat(t);t=n,n={channels:e}}if(void 0===n.channels&&(n.channels=Enumerations.MIDI_CHANNEL_NUMBERS),null==e)return Utilities.sanitizeChannels(n.channels).forEach(e=>{this.channels[e]&&this.channels[e].removeListener()}),super.removeListener();Enumerations.CHANNEL_EVENTS.includes(e)?Utilities.sanitizeChannels(n.channels).forEach(r=>{this.channels[r].removeListener(e,t,n)}):super.removeListener(e,t,n)}addForwarder(e,t={}){let n;return n=e instanceof Forwarder?e:new Forwarder(e,t),this._forwarders.push(n),n}removeForwarder(e){this._forwarders=this._forwarders.filter(t=>t!==e)}hasForwarder(e){return this._forwarders.includes(e)}get name(){return this._midiInput.name}get id(){return this._midiInput.id}get connection(){return this._midiInput.connection}get manufacturer(){return this._midiInput.manufacturer}get octaveOffset(){return this._octaveOffset}set octaveOffset(e){if(this.validation&&(e=parseInt(e),isNaN(e)))throw new TypeError("The 'octaveOffset' property must be an integer.");this._octaveOffset=e}get state(){return this._midiInput.state}get type(){return this._midiInput.type}get nrpnEventsEnabled(){return wm.validation&&console.warn("The 'nrpnEventsEnabled' property has been moved to the 'InputChannel' class."),!1}}if(Utilities.isNode){try{window.navigator}catch(err){let jzz;eval('jzz = require("jzz")'),global.navigator||(global.navigator={}),Object.assign(global.navigator,jzz)}try{performance}catch(err){let performance;eval('performance = require("perf_hooks").performance'),global.performance=performance}}
|
|
15309
15429
|
/**
|
|
15310
15430
|
* The `WebMidi` object makes it easier to work with the low-level Web MIDI API. Basically, it
|
|
15311
15431
|
* simplifies sending outgoing MIDI messages and reacting to incoming MIDI messages.
|
|
@@ -15325,7 +15445,7 @@ static toNoteNumber(e,t=0){if(t=null==t?0:parseInt(t),isNaN(t))throw new RangeEr
|
|
|
15325
15445
|
*
|
|
15326
15446
|
* @extends EventEmitter
|
|
15327
15447
|
* @license Apache-2.0
|
|
15328
|
-
*/class WebMidi extends EventEmitter{constructor(){super(),this.defaults={note:{attack:Utilities.from7bitToFloat(64),release:Utilities.from7bitToFloat(64),duration:1/0}},this.interface=null,this.validation=!0,this._inputs=[],this._disconnectedInputs=[],this._outputs=[],this._disconnectedOutputs=[],this._stateChangeQueue=[],this._octaveOffset=0}async enable(e={},t=!1){if(this.validation=!1!==e.validation,this.validation&&("function"==typeof e&&(e={callback:e,sysex:t}),t&&(e.sysex=!0)),this.enabled)return"function"==typeof e.callback&&e.callback(),Promise.resolve();const n={timestamp:this.time,target:this,type:"error",error:void 0},r={timestamp:this.time,target:this,type:"midiaccessgranted"},i={timestamp:this.time,target:this,type:"enabled"};try{"function"==typeof e.requestMIDIAccessFunction?this.interface=await e.requestMIDIAccessFunction({sysex:e.sysex,software:e.software}):this.interface=await navigator.requestMIDIAccess({sysex:e.sysex,software:e.software})}catch(t){return n.error=t,this.emit("error",n),"function"==typeof e.callback&&e.callback(t),Promise.reject(t)}this.emit("midiaccessgranted",r),this.interface.onstatechange=this._onInterfaceStateChange.bind(this);try{await this._updateInputsAndOutputs()}catch(t){return n.error=t,this.emit("error",n),"function"==typeof e.callback&&e.callback(t),Promise.reject(t)}return this.emit("enabled",i),"function"==typeof e.callback&&e.callback(),Promise.resolve(this)}async disable(){return this.interface&&(this.interface.onstatechange=void 0),this._destroyInputsAndOutputs().then(()=>{navigator&&"function"==typeof navigator.close&&navigator.close(),this.interface=null;let e={timestamp:this.time,target:this,type:"disabled"};this.emit("disabled",e),this.removeListener()})}getInputById(e,t={disconnected:!1}){if(this.validation){if(!this.enabled)throw new Error("WebMidi is not enabled.");if(!e)return}if(t.disconnected){for(let t=0;t<this._disconnectedInputs.length;t++)if(this._disconnectedInputs[t].id===e.toString())return this._disconnectedInputs[t]}else for(let t=0;t<this.inputs.length;t++)if(this.inputs[t].id===e.toString())return this.inputs[t]}getInputByName(e,t={disconnected:!1}){if(this.validation){if(!this.enabled)throw new Error("WebMidi is not enabled.");if(!e)return;e=e.toString()}if(t.disconnected){for(let t=0;t<this._disconnectedInputs.length;t++)if(~this._disconnectedInputs[t].name.indexOf(e))return this._disconnectedInputs[t]}else for(let t=0;t<this.inputs.length;t++)if(~this.inputs[t].name.indexOf(e))return this.inputs[t]}getOutputByName(e,t={disconnected:!1}){if(this.validation){if(!this.enabled)throw new Error("WebMidi is not enabled.");if(!e)return;e=e.toString()}if(t.disconnected){for(let t=0;t<this._disconnectedOutputs.length;t++)if(~this._disconnectedOutputs[t].name.indexOf(e))return this._disconnectedOutputs[t]}else for(let t=0;t<this.outputs.length;t++)if(~this.outputs[t].name.indexOf(e))return this.outputs[t]}getOutputById(e,t={disconnected:!1}){if(this.validation){if(!this.enabled)throw new Error("WebMidi is not enabled.");if(!e)return}if(t.disconnected){for(let t=0;t<this._disconnectedOutputs.length;t++)if(this._disconnectedOutputs[t].id===e.toString())return this._disconnectedOutputs[t]}else for(let t=0;t<this.outputs.length;t++)if(this.outputs[t].id===e.toString())return this.outputs[t]}noteNameToNumber(e){return this.validation&&console.warn("The noteNameToNumber() method is deprecated. Use Utilities.toNoteNumber() instead."),Utilities.toNoteNumber(e,this.octaveOffset)}getOctave(e){return this.validation&&(console.warn("The getOctave()is deprecated. Use Utilities.getNoteDetails() instead"),e=parseInt(e)),!isNaN(e)&&e>=0&&e<=127&&Utilities.getNoteDetails(Utilities.offsetNumber(e,this.octaveOffset)).octave}sanitizeChannels(e){return this.validation&&console.warn("The sanitizeChannels() method has been moved to the utilities class."),Utilities.sanitizeChannels(e)}toMIDIChannels(e){return this.validation&&console.warn("The toMIDIChannels() method has been deprecated. Use Utilities.sanitizeChannels() instead."),Utilities.sanitizeChannels(e)}guessNoteNumber(e){return this.validation&&console.warn("The guessNoteNumber() method has been deprecated. Use Utilities.guessNoteNumber() instead."),Utilities.guessNoteNumber(e,this.octaveOffset)}getValidNoteArray(e,t={}){return this.validation&&console.warn("The getValidNoteArray() method has been moved to the Utilities.buildNoteArray()"),Utilities.buildNoteArray(e,t)}convertToTimestamp(e){return this.validation&&console.warn("The convertToTimestamp() method has been moved to Utilities.toTimestamp()."),Utilities.toTimestamp(e)}async _destroyInputsAndOutputs(){let e=[];return this.inputs.forEach(t=>e.push(t.destroy())),this.outputs.forEach(t=>e.push(t.destroy())),Promise.all(e).then(()=>{this._inputs=[],this._outputs=[]})}_onInterfaceStateChange(e){this._updateInputsAndOutputs();let t={timestamp:e.timeStamp,type:e.port.state,target:this};if("connected"===e.port.state&&"open"===e.port.connection){"output"===e.port.type?t.port=this.getOutputById(e.port.id):"input"===e.port.type&&(t.port=this.getInputById(e.port.id)),this.emit(e.port.state,t);const n=Object.assign({},t);n.type="portschanged",this.emit(n.type,n)}else if("disconnected"===e.port.state&&"pending"===e.port.connection){"input"===e.port.type?t.port=this.getInputById(e.port.id,{disconnected:!0}):"output"===e.port.type&&(t.port=this.getOutputById(e.port.id,{disconnected:!0})),this.emit(e.port.state,t);const n=Object.assign({},t);n.type="portschanged",this.emit(n.type,n)}}async _updateInputsAndOutputs(){return Promise.all([this._updateInputs(),this._updateOutputs()])}async _updateInputs(){if(!this.interface)return;for(let e=this._inputs.length-1;e>=0;e--){const t=this._inputs[e];Array.from(this.interface.inputs.values()).find(e=>e===t._midiInput)||(this._disconnectedInputs.push(t),this._inputs.splice(e,1))}let e=[];return this.interface.inputs.forEach(t=>{if(!this._inputs.find(e=>e._midiInput===t)){let n=this._disconnectedInputs.find(e=>e._midiInput===t);n||(n=new Input(t)),this._inputs.push(n),e.push(n.open())}}),Promise.all(e)}async _updateOutputs(){if(!this.interface)return;for(let e=this._outputs.length-1;e>=0;e--){const t=this._outputs[e];Array.from(this.interface.outputs.values()).find(e=>e===t._midiOutput)||(this._disconnectedOutputs.push(t),this._outputs.splice(e,1))}let e=[];return this.interface.outputs.forEach(t=>{if(!this._outputs.find(e=>e._midiOutput===t)){let n=this._disconnectedOutputs.find(e=>e._midiOutput===t);n||(n=new Output(t)),this._outputs.push(n),e.push(n.open())}}),Promise.all(e)}get enabled(){return null!==this.interface}get inputs(){return this._inputs}get isNode(){return this.validation&&console.warn("WebMidi.isNode has been deprecated. Use Utilities.isNode instead."),Utilities.isNode}get isBrowser(){return this.validation&&console.warn("WebMidi.isBrowser has been deprecated. Use Utilities.isBrowser instead."),Utilities.isBrowser}get octaveOffset(){return this._octaveOffset}set octaveOffset(e){if(this.validation&&(e=parseInt(e),isNaN(e)))throw new TypeError("The 'octaveOffset' property must be an integer.");this._octaveOffset=e}get outputs(){return this._outputs}get supported(){return"undefined"!=typeof navigator
|
|
15448
|
+
*/class WebMidi extends EventEmitter{constructor(){super(),this.defaults={note:{attack:Utilities.from7bitToFloat(64),release:Utilities.from7bitToFloat(64),duration:1/0}},this.interface=null,this.validation=!0,this._inputs=[],this._disconnectedInputs=[],this._outputs=[],this._disconnectedOutputs=[],this._stateChangeQueue=[],this._octaveOffset=0}async enable(e={},t=!1){if(this.validation=!1!==e.validation,this.validation&&("function"==typeof e&&(e={callback:e,sysex:t}),t&&(e.sysex=!0)),this.enabled)return"function"==typeof e.callback&&e.callback(),Promise.resolve();const n={timestamp:this.time,target:this,type:"error",error:void 0},r={timestamp:this.time,target:this,type:"midiaccessgranted"},i={timestamp:this.time,target:this,type:"enabled"};try{"function"==typeof e.requestMIDIAccessFunction?this.interface=await e.requestMIDIAccessFunction({sysex:e.sysex,software:e.software}):this.interface=await navigator.requestMIDIAccess({sysex:e.sysex,software:e.software})}catch(t){return n.error=t,this.emit("error",n),"function"==typeof e.callback&&e.callback(t),Promise.reject(t)}this.emit("midiaccessgranted",r),this.interface.onstatechange=this._onInterfaceStateChange.bind(this);try{await this._updateInputsAndOutputs()}catch(t){return n.error=t,this.emit("error",n),"function"==typeof e.callback&&e.callback(t),Promise.reject(t)}return this.emit("enabled",i),"function"==typeof e.callback&&e.callback(),Promise.resolve(this)}async disable(){return this.interface&&(this.interface.onstatechange=void 0),this._destroyInputsAndOutputs().then(()=>{navigator&&"function"==typeof navigator.close&&navigator.close(),this.interface=null;let e={timestamp:this.time,target:this,type:"disabled"};this.emit("disabled",e),this.removeListener()})}getInputById(e,t={disconnected:!1}){if(this.validation){if(!this.enabled)throw new Error("WebMidi is not enabled.");if(!e)return}if(t.disconnected){for(let t=0;t<this._disconnectedInputs.length;t++)if(this._disconnectedInputs[t]._midiInput&&this._disconnectedInputs[t].id===e.toString())return this._disconnectedInputs[t]}else for(let t=0;t<this.inputs.length;t++)if(this.inputs[t]._midiInput&&this.inputs[t].id===e.toString())return this.inputs[t]}getInputByName(e,t={disconnected:!1}){if(this.validation){if(!this.enabled)throw new Error("WebMidi is not enabled.");if(!e)return;e=e.toString()}if(t.disconnected){for(let t=0;t<this._disconnectedInputs.length;t++)if(~this._disconnectedInputs[t].name.indexOf(e))return this._disconnectedInputs[t]}else for(let t=0;t<this.inputs.length;t++)if(~this.inputs[t].name.indexOf(e))return this.inputs[t]}getOutputByName(e,t={disconnected:!1}){if(this.validation){if(!this.enabled)throw new Error("WebMidi is not enabled.");if(!e)return;e=e.toString()}if(t.disconnected){for(let t=0;t<this._disconnectedOutputs.length;t++)if(~this._disconnectedOutputs[t].name.indexOf(e))return this._disconnectedOutputs[t]}else for(let t=0;t<this.outputs.length;t++)if(~this.outputs[t].name.indexOf(e))return this.outputs[t]}getOutputById(e,t={disconnected:!1}){if(this.validation){if(!this.enabled)throw new Error("WebMidi is not enabled.");if(!e)return}if(t.disconnected){for(let t=0;t<this._disconnectedOutputs.length;t++)if(this._disconnectedOutputs[t]._midiOutput&&this._disconnectedOutputs[t].id===e.toString())return this._disconnectedOutputs[t]}else for(let t=0;t<this.outputs.length;t++)if(this.outputs[t]._midiOutput&&this.outputs[t].id===e.toString())return this.outputs[t]}noteNameToNumber(e){return this.validation&&console.warn("The noteNameToNumber() method is deprecated. Use Utilities.toNoteNumber() instead."),Utilities.toNoteNumber(e,this.octaveOffset)}getOctave(e){return this.validation&&(console.warn("The getOctave()is deprecated. Use Utilities.getNoteDetails() instead"),e=parseInt(e)),!isNaN(e)&&e>=0&&e<=127&&Utilities.getNoteDetails(Utilities.offsetNumber(e,this.octaveOffset)).octave}sanitizeChannels(e){return this.validation&&console.warn("The sanitizeChannels() method has been moved to the utilities class."),Utilities.sanitizeChannels(e)}toMIDIChannels(e){return this.validation&&console.warn("The toMIDIChannels() method has been deprecated. Use Utilities.sanitizeChannels() instead."),Utilities.sanitizeChannels(e)}guessNoteNumber(e){return this.validation&&console.warn("The guessNoteNumber() method has been deprecated. Use Utilities.guessNoteNumber() instead."),Utilities.guessNoteNumber(e,this.octaveOffset)}getValidNoteArray(e,t={}){return this.validation&&console.warn("The getValidNoteArray() method has been moved to the Utilities.buildNoteArray()"),Utilities.buildNoteArray(e,t)}convertToTimestamp(e){return this.validation&&console.warn("The convertToTimestamp() method has been moved to Utilities.toTimestamp()."),Utilities.toTimestamp(e)}async _destroyInputsAndOutputs(){let e=[];return this.inputs.forEach(t=>e.push(t.destroy())),this.outputs.forEach(t=>e.push(t.destroy())),Promise.all(e).then(()=>{this._inputs=[],this._outputs=[]})}_onInterfaceStateChange(e){this._updateInputsAndOutputs();let t={timestamp:e.timeStamp,type:e.port.state,target:this};if("connected"===e.port.state&&"open"===e.port.connection){"output"===e.port.type?t.port=this.getOutputById(e.port.id):"input"===e.port.type&&(t.port=this.getInputById(e.port.id)),this.emit(e.port.state,t);const n=Object.assign({},t);n.type="portschanged",this.emit(n.type,n)}else if("disconnected"===e.port.state&&"pending"===e.port.connection){"input"===e.port.type?t.port=this.getInputById(e.port.id,{disconnected:!0}):"output"===e.port.type&&(t.port=this.getOutputById(e.port.id,{disconnected:!0})),this.emit(e.port.state,t);const n=Object.assign({},t);n.type="portschanged",this.emit(n.type,n)}}async _updateInputsAndOutputs(){return Promise.all([this._updateInputs(),this._updateOutputs()])}async _updateInputs(){if(!this.interface)return;for(let e=this._inputs.length-1;e>=0;e--){const t=this._inputs[e];Array.from(this.interface.inputs.values()).find(e=>e===t._midiInput)||(this._disconnectedInputs.push(t),this._inputs.splice(e,1))}let e=[];return this.interface.inputs.forEach(t=>{if(!this._inputs.find(e=>e._midiInput===t)){let n=this._disconnectedInputs.find(e=>e._midiInput===t);n||(n=new Input(t)),this._inputs.push(n),e.push(n.open())}}),Promise.all(e)}async _updateOutputs(){if(!this.interface)return;for(let e=this._outputs.length-1;e>=0;e--){const t=this._outputs[e];Array.from(this.interface.outputs.values()).find(e=>e===t._midiOutput)||(this._disconnectedOutputs.push(t),this._outputs.splice(e,1))}let e=[];return this.interface.outputs.forEach(t=>{if(!this._outputs.find(e=>e._midiOutput===t)){let n=this._disconnectedOutputs.find(e=>e._midiOutput===t);n||(n=new Output(t)),this._outputs.push(n),e.push(n.open())}}),Promise.all(e)}get enabled(){return null!==this.interface}get inputs(){return this._inputs}get isNode(){return this.validation&&console.warn("WebMidi.isNode has been deprecated. Use Utilities.isNode instead."),Utilities.isNode}get isBrowser(){return this.validation&&console.warn("WebMidi.isBrowser has been deprecated. Use Utilities.isBrowser instead."),Utilities.isBrowser}get octaveOffset(){return this._octaveOffset}set octaveOffset(e){if(this.validation&&(e=parseInt(e),isNaN(e)))throw new TypeError("The 'octaveOffset' property must be an integer.");this._octaveOffset=e}get outputs(){return this._outputs}get supported(){return"undefined"!=typeof navigator&&!!navigator.requestMIDIAccess}get sysexEnabled(){return!(!this.interface||!this.interface.sysexEnabled)}get time(){return performance.now()}get version(){return"3.1.14"}get flavour(){return"cjs"}get CHANNEL_EVENTS(){return this.validation&&console.warn("The CHANNEL_EVENTS enum has been moved to Enumerations.CHANNEL_EVENTS."),Enumerations.CHANNEL_EVENTS}get MIDI_SYSTEM_MESSAGES(){return this.validation&&console.warn("The MIDI_SYSTEM_MESSAGES enum has been moved to Enumerations.SYSTEM_MESSAGES."),Enumerations.SYSTEM_MESSAGES}get MIDI_CHANNEL_MODE_MESSAGES(){return this.validation&&console.warn("The MIDI_CHANNEL_MODE_MESSAGES enum has been moved to Enumerations.CHANNEL_MODE_MESSAGES."),Enumerations.CHANNEL_MODE_MESSAGES}get MIDI_CONTROL_CHANGE_MESSAGES(){return this.validation&&console.warn("The MIDI_CONTROL_CHANGE_MESSAGES enum has been replaced by the Enumerations.CONTROL_CHANGE_MESSAGES array."),Enumerations.MIDI_CONTROL_CHANGE_MESSAGES}get MIDI_REGISTERED_PARAMETER(){return this.validation&&console.warn("The MIDI_REGISTERED_PARAMETER enum has been moved to Enumerations.REGISTERED_PARAMETERS."),Enumerations.REGISTERED_PARAMETERS}get NOTES(){return this.validation&&console.warn("The NOTES enum has been deprecated."),["C","C#","D","D#","E","F","F#","G","G#","A","A#","B"]}}const wm=new WebMidi;wm.constructor=null,exports.Enumerations=Enumerations,exports.Forwarder=Forwarder,exports.Input=Input,exports.InputChannel=InputChannel,exports.Message=Message,exports.Note=Note,exports.Output=Output,exports.OutputChannel=OutputChannel,exports.Utilities=Utilities,exports.WebMidi=wm;
|
|
15329
15449
|
|
|
15330
15450
|
|
|
15331
15451
|
}).call(this)}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
|
@@ -15372,14 +15492,17 @@ const fxMap = {
|
|
|
15372
15492
|
'degrade' : (params) => {
|
|
15373
15493
|
return new DownSampler(params);
|
|
15374
15494
|
},
|
|
15375
|
-
'
|
|
15495
|
+
'converb' : (params) => {
|
|
15376
15496
|
return new Reverb(params);
|
|
15377
15497
|
},
|
|
15498
|
+
'room' : (params) => {
|
|
15499
|
+
return new DattorroReverb(params);
|
|
15500
|
+
},
|
|
15378
15501
|
'hall' : (params) => {
|
|
15379
|
-
return new
|
|
15502
|
+
return new DattorroReverb(params);
|
|
15380
15503
|
},
|
|
15381
15504
|
'reverb' : (params) => {
|
|
15382
|
-
return new
|
|
15505
|
+
return new DattorroReverb(params);
|
|
15383
15506
|
},
|
|
15384
15507
|
'shift' : (params) => {
|
|
15385
15508
|
return new PitchShift(params);
|
|
@@ -15422,10 +15545,121 @@ const fxMap = {
|
|
|
15422
15545
|
},
|
|
15423
15546
|
'double' : (params) => {
|
|
15424
15547
|
return new Chorus(Util.mapDefaults(params, ['8/1', 8, 1]));
|
|
15548
|
+
},
|
|
15549
|
+
'vowel' : (params) => {
|
|
15550
|
+
return new FormantFilter(params);
|
|
15551
|
+
},
|
|
15552
|
+
'formant' : (params) => {
|
|
15553
|
+
return new FormantFilter(params);
|
|
15554
|
+
},
|
|
15555
|
+
'speak' : (params) => {
|
|
15556
|
+
return new FormantFilter(params);
|
|
15425
15557
|
}
|
|
15426
15558
|
}
|
|
15427
15559
|
module.exports = fxMap;
|
|
15428
15560
|
|
|
15561
|
+
|
|
15562
|
+
// A formant/vowel filter. With this filter you can imitate the vowels of human
|
|
15563
|
+
// speech.
|
|
15564
|
+
//
|
|
15565
|
+
const FormantFilter = function(_params){
|
|
15566
|
+
// default values for the effect and separate parameters
|
|
15567
|
+
_params = Util.mapDefaults(_params, [ 'o', 0, 1, 1 ]);
|
|
15568
|
+
this._vowel = _params[0];
|
|
15569
|
+
this._slide = _params[1];
|
|
15570
|
+
this._shift = _params[2];
|
|
15571
|
+
this._wet = _params[3];
|
|
15572
|
+
|
|
15573
|
+
// the input and wetdry output nodes
|
|
15574
|
+
this._fx = new Tone.Gain(1);
|
|
15575
|
+
this._mix = new Tone.Add();
|
|
15576
|
+
this._mixWet = new Tone.Gain(0).connect(this._mix);
|
|
15577
|
+
this._mixDry = new Tone.Gain(1).connect(this._mix.addend);
|
|
15578
|
+
this._fx.connect(this._mixDry);
|
|
15579
|
+
|
|
15580
|
+
// data collected from various sources, please see the research on
|
|
15581
|
+
// https://github.com/tmhglnd/vowel-formants-graph
|
|
15582
|
+
this._formantData = {
|
|
15583
|
+
"oo" : [ 299, 850, 2250, "book" ],
|
|
15584
|
+
"u" : [ 438, 998, 2250, "foot" ],
|
|
15585
|
+
"oh" : [ 569, 856, 2410, "pot" ],
|
|
15586
|
+
"uh" : [ 518, 1189, 2390, "bug" ],
|
|
15587
|
+
"er" : [ 490, 1358, 1690, "bird" ],
|
|
15588
|
+
"a" : [ 730, 1102, 2440, "part" ],
|
|
15589
|
+
"ae" : [ 660, 1702, 2410, "lap" ],
|
|
15590
|
+
"e" : [ 528, 1855, 2480, "let" ],
|
|
15591
|
+
"i" : [ 400, 2002, 2250, "bit" ],
|
|
15592
|
+
"ee" : [ 270, 2296, 3010, "leap" ],
|
|
15593
|
+
"o" : [ 399, 709, 2420, "fold" ],
|
|
15594
|
+
"oe" : [ 360, 1546, 2346, "you" ]
|
|
15595
|
+
}
|
|
15596
|
+
this._vowels = Object.keys(this._formantData);
|
|
15597
|
+
|
|
15598
|
+
// a -12dB/octave lowpass filter for preserving low end
|
|
15599
|
+
this._lopass = new Tone.Filter(85, 'lowpass', -12).connect(this._mixWet);
|
|
15600
|
+
this._fx.connect(this._lopass);
|
|
15601
|
+
|
|
15602
|
+
// 3 bandpass biquadfilters for the formants
|
|
15603
|
+
// mix the filters together to one output
|
|
15604
|
+
this._formants = [];
|
|
15605
|
+
for (let f=0; f<3; f++){
|
|
15606
|
+
this._formants[f] = new Tone.Filter(this._formantData['o'][f], 'bandpass');
|
|
15607
|
+
// parallel processing of the filters from the input
|
|
15608
|
+
this._fx.connect(this._formants[f]);
|
|
15609
|
+
this._formants[f].connect(this._mixWet);
|
|
15610
|
+
}
|
|
15611
|
+
|
|
15612
|
+
this.set = function(c, time, bpm){
|
|
15613
|
+
let v = Util.getParam(this._vowel, c);
|
|
15614
|
+
let r = Util.divToS(Util.getParam(this._slide, c), bpm);
|
|
15615
|
+
let s = Util.clip(Util.getParam(this._shift, c), 0.17, 6);
|
|
15616
|
+
let w = Util.clip(Util.getParam(this._wet, c));
|
|
15617
|
+
|
|
15618
|
+
// get the formantdata from the object
|
|
15619
|
+
let freqs = this._formantData['oo'];
|
|
15620
|
+
v = (!isNaN(v)) ?
|
|
15621
|
+
this._vowels[Util.clip(v, 0, this._vowels.length)] : v;
|
|
15622
|
+
// make sure vowel is a valid option
|
|
15623
|
+
if (this._formantData.hasOwnProperty(v)){
|
|
15624
|
+
freqs = this._formantData[v];
|
|
15625
|
+
} else {
|
|
15626
|
+
log(`fx(vowel): ${v} is not a valid vowel selection, using default "o"`);
|
|
15627
|
+
}
|
|
15628
|
+
|
|
15629
|
+
// apply the frequencies, Q's and gain to the individual formant filters
|
|
15630
|
+
for (let f=0; f<this._formants.length; f++){
|
|
15631
|
+
// the frequency is the formant freq * shift factor
|
|
15632
|
+
if (r > 0) {
|
|
15633
|
+
this._formants[f].frequency.rampTo(freqs[f] * s, r, time);
|
|
15634
|
+
} else {
|
|
15635
|
+
this._formants[f].frequency.setValueAtTime(freqs[f] * s, time);
|
|
15636
|
+
}
|
|
15637
|
+
// Q = (Freq * Shift) / (BandWidthHz / 2)
|
|
15638
|
+
// Default bandwidth set to 50Hz
|
|
15639
|
+
this._formants[f].Q.setValueAtTime(freqs[f] * s * 0.05, time);
|
|
15640
|
+
// Apply gain compensation based on formant number, +18dB, +5, +2
|
|
15641
|
+
this._formants[f].output.gain.setValueAtTime(18 * (0.31 ** f), time);
|
|
15642
|
+
}
|
|
15643
|
+
|
|
15644
|
+
// apply wetdry mix
|
|
15645
|
+
this._mixWet.gain.setValueAtTime(w, time);
|
|
15646
|
+
this._mixDry.gain.setValueAtTime(1 - w, time);
|
|
15647
|
+
}
|
|
15648
|
+
|
|
15649
|
+
this.chain = function(){
|
|
15650
|
+
return { 'send' : this._fx, 'return' : this._mix }
|
|
15651
|
+
}
|
|
15652
|
+
|
|
15653
|
+
this.delete = function(){
|
|
15654
|
+
const nodes = [ this._fx, this._mix, this._mixWet, this._mixDry, ...this._formants, this._lopass ];
|
|
15655
|
+
|
|
15656
|
+
nodes.forEach((n) => {
|
|
15657
|
+
n.disconnect();
|
|
15658
|
+
n.dispose();
|
|
15659
|
+
});
|
|
15660
|
+
}
|
|
15661
|
+
}
|
|
15662
|
+
|
|
15429
15663
|
// A Downsampling Chiptune effect. Downsamples the signal by a specified amount
|
|
15430
15664
|
// Resulting in a lower samplerate, making it sound more like 8bit/chiptune
|
|
15431
15665
|
// Programmed with a custom AudioWorkletProcessor, see effects/Processors.js
|
|
@@ -15471,7 +15705,7 @@ const DownSampler = function(_params){
|
|
|
15471
15705
|
}
|
|
15472
15706
|
|
|
15473
15707
|
this.delete = function(){
|
|
15474
|
-
const nodes = [ this._fx, this._mix, this._mixDry ];
|
|
15708
|
+
const nodes = [ this._fx.input, this._fx.output, this._fx, this._mix, this._mixDry ];
|
|
15475
15709
|
|
|
15476
15710
|
nodes.forEach((n) => {
|
|
15477
15711
|
n.disconnect();
|
|
@@ -15485,13 +15719,11 @@ const DownSampler = function(_params){
|
|
|
15485
15719
|
// distortion is applied on the overdrive parameter
|
|
15486
15720
|
//
|
|
15487
15721
|
const TanhDistortion = function(_params){
|
|
15488
|
-
_params = Util.mapDefaults(_params, [
|
|
15722
|
+
_params = Util.mapDefaults(_params, [ 2, 1 ]);
|
|
15489
15723
|
// apply the default values and convert to arrays where necessary
|
|
15490
15724
|
this._drive = Util.toArray(_params[0]);
|
|
15491
15725
|
this._wet = Util.toArray(_params[1]);
|
|
15492
15726
|
|
|
15493
|
-
console.log('Distortion with:', this._drive, this._wet);
|
|
15494
|
-
|
|
15495
15727
|
// The crossfader for wet-dry (originally implemented with CrossFade)
|
|
15496
15728
|
// this._mix = new Tone.CrossFade();
|
|
15497
15729
|
this._mix = new Tone.Add();
|
|
@@ -15536,7 +15768,7 @@ const TanhDistortion = function(_params){
|
|
|
15536
15768
|
}
|
|
15537
15769
|
|
|
15538
15770
|
this.delete = function(){
|
|
15539
|
-
let nodes = [ this._fx, this._mix, this._mixDry, this._mixWet ];
|
|
15771
|
+
let nodes = [ this._fx.input, this._fx.output, this._fx, this._mix, this._mixDry, this._mixWet ];
|
|
15540
15772
|
|
|
15541
15773
|
nodes.forEach((n) => {
|
|
15542
15774
|
n.disconnect();
|
|
@@ -15664,7 +15896,7 @@ const Squash = function(_params){
|
|
|
15664
15896
|
}
|
|
15665
15897
|
|
|
15666
15898
|
this.delete = function(){
|
|
15667
|
-
let nodes = [ this._fx, this._mix, this._mixDry ];
|
|
15899
|
+
let nodes = [ this._fx.input, this._fx.output, this._fx, this._mix, this._mixDry ];
|
|
15668
15900
|
|
|
15669
15901
|
nodes.forEach((n) => {
|
|
15670
15902
|
n.disconnect();
|
|
@@ -15705,6 +15937,65 @@ const Reverb = function(_params){
|
|
|
15705
15937
|
}
|
|
15706
15938
|
}
|
|
15707
15939
|
|
|
15940
|
+
// Dattorro Reverb FX
|
|
15941
|
+
// Good sound plate reverb emulation
|
|
15942
|
+
// Based on the paper by Jon Dattorro and the code from Khoin
|
|
15943
|
+
// https://ccrma.stanford.edu/~dattorro/EffectDesignPart1.pdf
|
|
15944
|
+
// https://github.com/khoin/DattorroReverbNode
|
|
15945
|
+
//
|
|
15946
|
+
const DattorroReverb = function(_params){
|
|
15947
|
+
_params = Util.mapDefaults(_params, [ 0.5, 10, 0, 0.5 ]);
|
|
15948
|
+
this._gain = Util.toArray(_params[0]);
|
|
15949
|
+
this._size = Util.toArray(_params[1]);
|
|
15950
|
+
// unused currently, but here for compatibility with Mercury4Max code
|
|
15951
|
+
// this._slide = Util.toArray(_params[2]);
|
|
15952
|
+
this._wet = Util.toArray(_params[3]);
|
|
15953
|
+
|
|
15954
|
+
// The crossfader for wet-dry (originally implemented with CrossFade)
|
|
15955
|
+
this._mix = new Tone.Add();
|
|
15956
|
+
this._mixWet = new Tone.Gain(0).connect(this._mix);
|
|
15957
|
+
this._mixDry = new Tone.Gain(1).connect(this._mix.addend);
|
|
15958
|
+
|
|
15959
|
+
// a custom tone audio node with input/output gain and worklet effect
|
|
15960
|
+
this._fx = new Tone.ToneAudioNode();
|
|
15961
|
+
this._fx.input = new Tone.Gain(1).connect(this._mixDry);
|
|
15962
|
+
this._fx.output = new Tone.Gain(1).connect(this._mixWet);
|
|
15963
|
+
this._fx.workletNode = Tone.getContext().createAudioWorkletNode('dattorro-reverb');
|
|
15964
|
+
this._fx.input.chain(this._fx.workletNode, this._fx.output);
|
|
15965
|
+
|
|
15966
|
+
this.set = (c, time) => {
|
|
15967
|
+
const gn = Math.max(Util.getParam(this._gain, c), 0);
|
|
15968
|
+
const meta = Util.clip(Util.getParam(this._size, c), 0, 20);
|
|
15969
|
+
const wet = Util.clip(Util.getParam(this._wet, c));
|
|
15970
|
+
|
|
15971
|
+
const dc = Util.remap(meta, 0, 20, 0.01, 0.99, 0.8);
|
|
15972
|
+
const df = Util.remap(meta, 0, 20, 0.2, 0.75, 0.5);
|
|
15973
|
+
const dp = Util.remap(meta, 0, 20, 0.2, 0.65, 2.5);
|
|
15974
|
+
const pd = Util.remap(meta, 0, 20, 700, 100);
|
|
15975
|
+
|
|
15976
|
+
this._fx.workletNode.parameters.get('decay').setValueAtTime(dc, time);
|
|
15977
|
+
this._fx.workletNode.parameters.get('decayDiffusion1').setValueAtTime(df, time);
|
|
15978
|
+
this._fx.workletNode.parameters.get('damping').setValueAtTime(dp, time);
|
|
15979
|
+
this._fx.workletNode.parameters.get('preDelay').setValueAtTime(pd, time);
|
|
15980
|
+
|
|
15981
|
+
this._fx.workletNode.parameters.get('wet').setValueAtTime(gn * 0.7, time);
|
|
15982
|
+
// this._fx.workletNode.parameters.get('dry').setValueAtTime(0.7, time);
|
|
15983
|
+
|
|
15984
|
+
// apply wetdry mix
|
|
15985
|
+
this._mixWet.gain.setValueAtTime(wet, time);
|
|
15986
|
+
this._mixDry.gain.setValueAtTime(1 - wet, time);
|
|
15987
|
+
}
|
|
15988
|
+
|
|
15989
|
+
this.chain = () => {
|
|
15990
|
+
return { 'send' : this._fx, 'return' : this._mix }
|
|
15991
|
+
}
|
|
15992
|
+
|
|
15993
|
+
this.delete = () => {
|
|
15994
|
+
const nodes = [ this._fx, this._mix, this._mixDry, this._fx.input, this._fx.output ];
|
|
15995
|
+
nodes.forEach(n => { n.disconnect(); n.dispose() });
|
|
15996
|
+
}
|
|
15997
|
+
}
|
|
15998
|
+
|
|
15708
15999
|
// PitchShift FX
|
|
15709
16000
|
// Shift the pitch up or down with semitones
|
|
15710
16001
|
// Utilizes the default PitchShift FX from ToneJS
|
|
@@ -16314,7 +16605,7 @@ class Instrument extends Sequencer {
|
|
|
16314
16605
|
|
|
16315
16606
|
channelStrip(){
|
|
16316
16607
|
// gain => output
|
|
16317
|
-
this.gain = new Tone.Gain(0).toDestination();
|
|
16608
|
+
this.gain = new Tone.Gain(0, "normalRange").toDestination();
|
|
16318
16609
|
// panning => gain
|
|
16319
16610
|
this.panner = new Tone.Panner(0).connect(this.gain);
|
|
16320
16611
|
// adsr => panning
|
|
@@ -16325,15 +16616,16 @@ class Instrument extends Sequencer {
|
|
|
16325
16616
|
|
|
16326
16617
|
envelope(d){
|
|
16327
16618
|
// return an Envelope and connect to next node
|
|
16328
|
-
return new Tone.AmplitudeEnvelope({
|
|
16329
|
-
|
|
16330
|
-
|
|
16331
|
-
|
|
16332
|
-
|
|
16333
|
-
|
|
16334
|
-
|
|
16335
|
-
|
|
16336
|
-
}).connect(d);
|
|
16619
|
+
// return new Tone.AmplitudeEnvelope({
|
|
16620
|
+
// attack: 0,
|
|
16621
|
+
// attackCurve: "linear",
|
|
16622
|
+
// decay: 0,
|
|
16623
|
+
// decayCurve: "linear",
|
|
16624
|
+
// sustain: 1,
|
|
16625
|
+
// release: 0.001,
|
|
16626
|
+
// releaseCurve: "linear"
|
|
16627
|
+
// }).connect(d);
|
|
16628
|
+
return new Tone.Gain(0).connect(d);
|
|
16337
16629
|
}
|
|
16338
16630
|
|
|
16339
16631
|
event(c, time){
|
|
@@ -16343,7 +16635,7 @@ class Instrument extends Sequencer {
|
|
|
16343
16635
|
|
|
16344
16636
|
// set FX parameters
|
|
16345
16637
|
if (this._fx){
|
|
16346
|
-
for (let f=0; f<this._fx.length; f++){
|
|
16638
|
+
for (let f = 0; f < this._fx.length; f++){
|
|
16347
16639
|
this._fx[f].set(c, time, this.bpm());
|
|
16348
16640
|
}
|
|
16349
16641
|
}
|
|
@@ -16372,28 +16664,25 @@ class Instrument extends Sequencer {
|
|
|
16372
16664
|
|
|
16373
16665
|
// set shape for playback (fade-in / out and length)
|
|
16374
16666
|
if (this._att){
|
|
16375
|
-
|
|
16376
|
-
|
|
16377
|
-
|
|
16378
|
-
|
|
16379
|
-
// minimum attaack and release times are 1 millisecond
|
|
16380
|
-
this.adsr.attack = Math.max(0.001, att);
|
|
16381
|
-
this.adsr.decay = dec;
|
|
16382
|
-
this.adsr.release = Math.max(0.001, rel);
|
|
16667
|
+
const att = Math.max(Util.divToS(Util.getParam(this._att, c), this.bpm()), 0.001);
|
|
16668
|
+
const dec = Util.divToS(Util.getParam(this._sus, c), this.bpm());
|
|
16669
|
+
const rel = Math.max(Util.divToS(Util.getParam(this._rel, c), this.bpm()), 0.001);
|
|
16383
16670
|
|
|
16384
|
-
//
|
|
16385
|
-
//
|
|
16386
|
-
|
|
16387
|
-
this.adsr.
|
|
16388
|
-
|
|
16389
|
-
|
|
16390
|
-
|
|
16391
|
-
|
|
16392
|
-
//
|
|
16393
|
-
|
|
16671
|
+
// short ramp for retrigger, fades out the envelope over 2 ms
|
|
16672
|
+
// use the retrigger time to schedule the event a bit later as well
|
|
16673
|
+
let retrigger = 0;
|
|
16674
|
+
if (this.adsr.gain.getValueAtTime(time) > 0.01){
|
|
16675
|
+
retrigger = 0.002;
|
|
16676
|
+
// short ramp for retrigger, fades out the previous ramp
|
|
16677
|
+
this.adsr.gain.linearRampTo(0.0, retrigger, time);
|
|
16678
|
+
}
|
|
16679
|
+
// trigger the envelope and release after specified time
|
|
16680
|
+
this.adsr.gain.linearRampTo(1.0, att, time + retrigger);
|
|
16681
|
+
// exponential rampto * 5 for a good sounding exponential ramp
|
|
16682
|
+
this.adsr.gain.exponentialRampTo(0.0, rel * 5, time + att + dec + retrigger);
|
|
16394
16683
|
} else {
|
|
16395
|
-
// if shape is 'off'
|
|
16396
|
-
this.adsr.
|
|
16684
|
+
// if shape is 'off' turn on the gain of the envelope
|
|
16685
|
+
this.adsr.gain.setValueAtTime(1.0, time);
|
|
16397
16686
|
}
|
|
16398
16687
|
}
|
|
16399
16688
|
|
|
@@ -16403,18 +16692,34 @@ class Instrument extends Sequencer {
|
|
|
16403
16692
|
console.log('Instrument()', this._name, c);
|
|
16404
16693
|
}
|
|
16405
16694
|
|
|
16406
|
-
fadeIn(t){
|
|
16695
|
+
fadeIn(t=0){
|
|
16407
16696
|
// fade in the sound upon evaluation of code
|
|
16408
|
-
this.gain.gain.rampTo(1, t, Tone.now());
|
|
16697
|
+
// this.gain.gain.rampTo(1, t, Tone.now());
|
|
16698
|
+
// fade in the sound directly in 5 ms
|
|
16699
|
+
this.gain.gain.rampTo(1, 0.005, Tone.now());
|
|
16409
16700
|
}
|
|
16410
16701
|
|
|
16411
|
-
fadeOut(t){
|
|
16412
|
-
//
|
|
16413
|
-
|
|
16702
|
+
fadeOut(t, immediately=false){
|
|
16703
|
+
// if immediately is true, fade-out immediately instead of waiting
|
|
16704
|
+
let restTime = 0;
|
|
16705
|
+
|
|
16706
|
+
if (this._loop && !immediately){
|
|
16707
|
+
// get the remaining time till the next trigger in the loop
|
|
16708
|
+
// cancel the loop before that trigger happens and fade-out
|
|
16709
|
+
restTime = (1 - this._loop.progress) * this._loop.interval;
|
|
16710
|
+
}
|
|
16711
|
+
|
|
16414
16712
|
setTimeout(() => {
|
|
16415
|
-
|
|
16416
|
-
|
|
16417
|
-
|
|
16713
|
+
// stop the loop
|
|
16714
|
+
if (this._loop) this._loop.mute = 1;
|
|
16715
|
+
// fade out the sound upon evaluation of new code
|
|
16716
|
+
this.gain.gain.rampTo(0, t, Tone.now());
|
|
16717
|
+
|
|
16718
|
+
setTimeout(() => {
|
|
16719
|
+
this.delete();
|
|
16720
|
+
// wait a little bit extra before deleting to avoid clicks
|
|
16721
|
+
}, t * 1000 + 100);
|
|
16722
|
+
}, restTime * 1000 - 25);
|
|
16418
16723
|
}
|
|
16419
16724
|
|
|
16420
16725
|
delete(){
|
|
@@ -16426,6 +16731,14 @@ class Instrument extends Sequencer {
|
|
|
16426
16731
|
|
|
16427
16732
|
this.panner.disconnect();
|
|
16428
16733
|
this.panner.dispose();
|
|
16734
|
+
|
|
16735
|
+
this.adsr?.disconnect();
|
|
16736
|
+
this.adsr?.dispose();
|
|
16737
|
+
|
|
16738
|
+
// dispose the sound source (depending on inheriting class)
|
|
16739
|
+
this.source?.stop();
|
|
16740
|
+
this.source?.disconnect();
|
|
16741
|
+
this.source?.dispose();
|
|
16429
16742
|
// this.adsr.dispose();
|
|
16430
16743
|
// remove all fx
|
|
16431
16744
|
this._fx.map((f) => f.delete());
|
|
@@ -16639,7 +16952,7 @@ class MonoMidi extends Sequencer {
|
|
|
16639
16952
|
// only play a note if the notes are provided in the function
|
|
16640
16953
|
// if (this._note.length > 0){
|
|
16641
16954
|
|
|
16642
|
-
let noteOptions = { duration: d,
|
|
16955
|
+
let noteOptions = { duration: d, attack: g, time: sync };
|
|
16643
16956
|
|
|
16644
16957
|
// if a midinote is selected instead of note
|
|
16645
16958
|
// play the value without mapping
|
|
@@ -17017,16 +17330,11 @@ class MonoSynth extends Instrument {
|
|
|
17017
17330
|
delete(){
|
|
17018
17331
|
// delete super class
|
|
17019
17332
|
super.delete();
|
|
17333
|
+
|
|
17020
17334
|
// dispose the sound source
|
|
17021
|
-
|
|
17022
|
-
|
|
17023
|
-
this.synth
|
|
17024
|
-
this.synth.disconnect();
|
|
17025
|
-
this.synth.dispose();
|
|
17026
|
-
|
|
17027
|
-
this.adsr.dispose();
|
|
17028
|
-
this.synth.dispose();
|
|
17029
|
-
this.source.dispose();
|
|
17335
|
+
this.synth?.stop();
|
|
17336
|
+
this.synth?.disconnect();
|
|
17337
|
+
this.synth?.dispose();
|
|
17030
17338
|
|
|
17031
17339
|
console.log('disposed MonoSynth()', this._wave);
|
|
17032
17340
|
}
|
|
@@ -17114,7 +17422,9 @@ class PolyInstrument extends Instrument {
|
|
|
17114
17422
|
|
|
17115
17423
|
// set all busymaps based on current amplitude value
|
|
17116
17424
|
for (let i=0; i<this.busymap.length; i++){
|
|
17117
|
-
|
|
17425
|
+
// consider the voice busy if amplitude is > -40dB
|
|
17426
|
+
// because of exponential ramp otherwise voice stays busy too long
|
|
17427
|
+
this.busymap[i] = this.adsrs[i].gain.getValueAtTime(time) > 0.01;
|
|
17118
17428
|
if (!this.busymap[i]){
|
|
17119
17429
|
free.push(i);
|
|
17120
17430
|
}
|
|
@@ -17142,26 +17452,28 @@ class PolyInstrument extends Instrument {
|
|
|
17142
17452
|
|
|
17143
17453
|
// set shape for playback (fade-in / out and length)
|
|
17144
17454
|
if (this._att){
|
|
17145
|
-
|
|
17146
|
-
|
|
17147
|
-
|
|
17455
|
+
const att = Math.max(Util.divToS(Util.lookup(this._att, c), this.bpm()), 0.001);
|
|
17456
|
+
const dec = Util.divToS(Util.lookup(this._sus, c), this.bpm());
|
|
17457
|
+
const rel = Math.max(Util.divToS(Util.lookup(this._rel, c), this.bpm()), 0.001);
|
|
17148
17458
|
|
|
17149
|
-
|
|
17150
|
-
|
|
17151
|
-
|
|
17152
|
-
|
|
17153
|
-
|
|
17154
|
-
|
|
17155
|
-
|
|
17156
|
-
|
|
17157
|
-
|
|
17158
|
-
|
|
17159
|
-
this.adsrs[i].
|
|
17459
|
+
// short ramp for retrigger, fades out the envelope over
|
|
17460
|
+
// 2 ms. use the retrigger time to schedule the event
|
|
17461
|
+
// a bit later as well
|
|
17462
|
+
let retrigger = 0;
|
|
17463
|
+
if (this.adsrs[i].gain.getValueAtTime(time) > 0.01){
|
|
17464
|
+
retrigger = 0.002;
|
|
17465
|
+
// short ramp for retrigger, fades out the previous ramp
|
|
17466
|
+
this.adsrs[i].gain.linearRampTo(0.0, retrigger, time);
|
|
17467
|
+
}
|
|
17468
|
+
// trigger the envelope and release after specified time
|
|
17469
|
+
this.adsrs[i].gain.linearRampTo(1.0, att, time + retrigger);
|
|
17470
|
+
this.adsrs[i].gain.exponentialRampTo(0.0, rel * 5, time + att + dec + retrigger);
|
|
17160
17471
|
} else {
|
|
17161
17472
|
// if shape is off only trigger attack
|
|
17162
17473
|
// when voice stealing is 'off' this will lead to all
|
|
17163
17474
|
// voices set to busy!
|
|
17164
|
-
|
|
17475
|
+
// if shape is 'off' turn on the gain of the envelope
|
|
17476
|
+
this.adsrs[i].gain.setValueAtTime(1.0, time);
|
|
17165
17477
|
}
|
|
17166
17478
|
|
|
17167
17479
|
}
|
|
@@ -17451,9 +17763,9 @@ class PolySynth extends PolyInstrument {
|
|
|
17451
17763
|
this.sources[id].frequency.rampTo(f, s, time);
|
|
17452
17764
|
} else {
|
|
17453
17765
|
this.sources[id].frequency.setValueAtTime(f, time);
|
|
17454
|
-
// first time the synth plays it doesn't slide!
|
|
17455
|
-
this._firstSlide[id] = false;
|
|
17456
17766
|
}
|
|
17767
|
+
// first time the synth plays it doesn't slide!
|
|
17768
|
+
this._firstSlide[id] = false;
|
|
17457
17769
|
}
|
|
17458
17770
|
|
|
17459
17771
|
note(i=0, o=0){
|
|
@@ -17532,12 +17844,26 @@ class Sequencer {
|
|
|
17532
17844
|
return Tone.Transport.bpm.value;
|
|
17533
17845
|
}
|
|
17534
17846
|
|
|
17535
|
-
makeLoop(){
|
|
17847
|
+
makeLoop(stepCount, unnamedCount){
|
|
17536
17848
|
// dispose of previous loop if active
|
|
17537
17849
|
if (this._loop){
|
|
17538
17850
|
this._loop.dispose();
|
|
17539
17851
|
}
|
|
17540
17852
|
|
|
17853
|
+
// transfer the stepcount to count and beatcount if provided
|
|
17854
|
+
if (unnamedCount){
|
|
17855
|
+
this._count = unnamedCount.count;
|
|
17856
|
+
this._beatCount = unnamedCount.beat;
|
|
17857
|
+
}
|
|
17858
|
+
// replace count if a name is given.
|
|
17859
|
+
// this works through giving the instrument the same name
|
|
17860
|
+
if (stepCount){
|
|
17861
|
+
if (stepCount[this._name]){
|
|
17862
|
+
this._count = stepCount[this._name].count;
|
|
17863
|
+
this._beatCount = stepCount[this._name].beat;
|
|
17864
|
+
}
|
|
17865
|
+
}
|
|
17866
|
+
|
|
17541
17867
|
// create the event for a loop or external trigger
|
|
17542
17868
|
this._event = (time) => {
|
|
17543
17869
|
// convert transport time to Ticks and convert reset time to ticks
|
|
@@ -17552,15 +17878,15 @@ class Sequencer {
|
|
|
17552
17878
|
this._beatCount = 0;
|
|
17553
17879
|
}
|
|
17554
17880
|
}
|
|
17555
|
-
|
|
17556
|
-
|
|
17881
|
+
if (this._time !== null){
|
|
17882
|
+
// set subdivision speeds
|
|
17883
|
+
this._loop.playbackRate = Util.getParam(this._subdiv, this._count);
|
|
17884
|
+
// get the subdivision count (always 0, except when subdividing)
|
|
17885
|
+
this._subdivCnt = (this._subdivCnt + 1) % this._loop.playbackRate;
|
|
17886
|
+
// humanize method is interesting to add
|
|
17887
|
+
this._loop.humanize = Util.getParam(this._human, this._count);
|
|
17888
|
+
}
|
|
17557
17889
|
|
|
17558
|
-
// get the subdivision count (always 0, except when subdividing)
|
|
17559
|
-
this._subdivCnt = (this._subdivCnt + 1) % this._loop.playbackRate;
|
|
17560
|
-
|
|
17561
|
-
// humanize method is interesting to add
|
|
17562
|
-
this._loop.humanize = Util.getParam(this._human, this._count);
|
|
17563
|
-
|
|
17564
17890
|
// get beat probability for current count
|
|
17565
17891
|
let b = Util.getParam(this._beat, this._count);
|
|
17566
17892
|
|
|
@@ -17586,8 +17912,10 @@ class Sequencer {
|
|
|
17586
17912
|
}, (time - Tone.context.currentTime) * 1000);
|
|
17587
17913
|
}
|
|
17588
17914
|
// also emit an internal event for other instruments to sync to
|
|
17589
|
-
|
|
17590
|
-
|
|
17915
|
+
const event = new CustomEvent(`/${this._name}`, {
|
|
17916
|
+
detail: { value: 1, time: time }
|
|
17917
|
+
});
|
|
17918
|
+
window.dispatchEvent(event);
|
|
17591
17919
|
|
|
17592
17920
|
// execute a visual event for Hydra
|
|
17593
17921
|
if (this._visual.length > 0){
|
|
@@ -17623,16 +17951,17 @@ class Sequencer {
|
|
|
17623
17951
|
this._event(time)
|
|
17624
17952
|
}, this._time).start(schedule);
|
|
17625
17953
|
}
|
|
17626
|
-
|
|
17627
|
-
|
|
17628
|
-
|
|
17629
|
-
|
|
17630
|
-
|
|
17631
|
-
|
|
17632
|
-
|
|
17633
|
-
|
|
17634
|
-
|
|
17635
|
-
|
|
17954
|
+
else {
|
|
17955
|
+
// generate a listener for the osc-address and store for removal
|
|
17956
|
+
this._oscAddress = `${this._offset}`;
|
|
17957
|
+
this._listener = (event) => {
|
|
17958
|
+
// trigger the event if value greater than 0
|
|
17959
|
+
if (event.detail.value > 0){
|
|
17960
|
+
this._event(event.detail.time);
|
|
17961
|
+
}
|
|
17962
|
+
}
|
|
17963
|
+
window.addEventListener(this._oscAddress, this._listener);
|
|
17964
|
+
}
|
|
17636
17965
|
}
|
|
17637
17966
|
|
|
17638
17967
|
event(c, time){
|
|
@@ -17656,18 +17985,23 @@ class Sequencer {
|
|
|
17656
17985
|
|
|
17657
17986
|
delete(){
|
|
17658
17987
|
// dispose loop
|
|
17659
|
-
this._loop
|
|
17988
|
+
this._loop?.stop();
|
|
17989
|
+
this._loop?.dispose();
|
|
17990
|
+
// remove the listenere if one was created
|
|
17991
|
+
if (this._oscAddress){
|
|
17992
|
+
window.removeEventListener(this._oscAddress, this._listener)
|
|
17993
|
+
}
|
|
17660
17994
|
console.log('=> disposed Sequencer()');
|
|
17661
17995
|
}
|
|
17662
17996
|
|
|
17663
17997
|
start(){
|
|
17664
17998
|
// restart at offset
|
|
17665
|
-
this._loop
|
|
17999
|
+
this._loop?.start(this._offset);
|
|
17666
18000
|
}
|
|
17667
18001
|
|
|
17668
18002
|
stop(){
|
|
17669
18003
|
// stop sequencer
|
|
17670
|
-
this._loop
|
|
18004
|
+
this._loop?.stop();
|
|
17671
18005
|
}
|
|
17672
18006
|
|
|
17673
18007
|
time(t, o=0){
|
|
@@ -17776,6 +18110,12 @@ function clip(v, l=0, h=1){
|
|
|
17776
18110
|
return Math.max(l, Math.min(h, v));
|
|
17777
18111
|
}
|
|
17778
18112
|
|
|
18113
|
+
// scale values between an input and output range with exponent
|
|
18114
|
+
function remap(val=0, inLo=0, inHi=1, outLo=0, outHi=1, exp=1){
|
|
18115
|
+
let temp = ((val - inLo) / (inHi - inLo)) ** exp;
|
|
18116
|
+
return temp * (outHi - outLo) + outLo;
|
|
18117
|
+
}
|
|
18118
|
+
|
|
17779
18119
|
// make sure the output is a number, else output a default value
|
|
17780
18120
|
function assureNum(v, d=1){
|
|
17781
18121
|
return isNaN(v) ? d : v;
|
|
@@ -17969,7 +18309,7 @@ function log(msg){
|
|
|
17969
18309
|
}
|
|
17970
18310
|
}
|
|
17971
18311
|
|
|
17972
|
-
module.exports = { mapDefaults, atTime, atodb, dbtoa, clip, assureNum, lookup, randLookup, isRandom, getParam, toArray, msToS, formatRatio, divToS, divToF, toMidi, mtof, noteToMidi, noteToFreq, assureWave, log }
|
|
18312
|
+
module.exports = { mapDefaults, atTime, atodb, dbtoa, clip, remap,assureNum, lookup, randLookup, isRandom, getParam, toArray, msToS, formatRatio, divToS, divToF, toMidi, mtof, noteToMidi, noteToFreq, assureWave, log }
|
|
17973
18313
|
},{"tone":44,"total-serialism":47}],67:[function(require,module,exports){
|
|
17974
18314
|
module.exports={
|
|
17975
18315
|
"uptempo" : 10,
|
|
@@ -18007,6 +18347,7 @@ const PolySample = require('./core/PolySample.js');
|
|
|
18007
18347
|
const Tempos = require('./data/genre-tempos.json');
|
|
18008
18348
|
const Util = require('./core/Util.js');
|
|
18009
18349
|
const { divToS } = require('./core/Util.js');
|
|
18350
|
+
const { count } = require('total-serialism/src/gen-basic.js');
|
|
18010
18351
|
|
|
18011
18352
|
class MercuryInterpreter {
|
|
18012
18353
|
constructor({ hydra, p5canvas } = {}){
|
|
@@ -18065,10 +18406,11 @@ class MercuryInterpreter {
|
|
|
18065
18406
|
});
|
|
18066
18407
|
}
|
|
18067
18408
|
|
|
18068
|
-
removeSounds(s, f=0) {
|
|
18069
|
-
// fade out and delete after fade
|
|
18409
|
+
removeSounds(s, f=0, im=false) {
|
|
18410
|
+
// fade out and delete after fade. second parameter sets
|
|
18411
|
+
// immediate fade-out, otherwise wait till trigger
|
|
18070
18412
|
s.map((_s) => {
|
|
18071
|
-
if (_s){ _s.fadeOut(f); }
|
|
18413
|
+
if (_s){ _s.fadeOut(f, im); }
|
|
18072
18414
|
});
|
|
18073
18415
|
// empty array to trigger garbage collection
|
|
18074
18416
|
s.length = 0;
|
|
@@ -18082,11 +18424,16 @@ class MercuryInterpreter {
|
|
|
18082
18424
|
}
|
|
18083
18425
|
|
|
18084
18426
|
setCrossFade(f){
|
|
18085
|
-
// set the crossFade in milliseconds
|
|
18086
|
-
// set crossFade time in ms
|
|
18427
|
+
// set the crossFade time in milliseconds
|
|
18087
18428
|
this.crossFade = divToS(f, this.getBPM());
|
|
18088
18429
|
// this.crossFade = Number(f) / 1000;
|
|
18089
|
-
Util.log(`
|
|
18430
|
+
Util.log(`crossFade is deprecated, setting fadeOut time to ${this.crossFade}ms`);
|
|
18431
|
+
}
|
|
18432
|
+
|
|
18433
|
+
setFadeOut(f){
|
|
18434
|
+
// set the fadeOut time in milliseconds
|
|
18435
|
+
this.crossFade = divToS(f, this.getBPM());
|
|
18436
|
+
Util.log(`setting fadeOut time to ${this.crossFade}`);
|
|
18090
18437
|
}
|
|
18091
18438
|
|
|
18092
18439
|
getCode(){
|
|
@@ -18145,6 +18492,10 @@ class MercuryInterpreter {
|
|
|
18145
18492
|
// set crossFade time in ms
|
|
18146
18493
|
this.setCrossFade(args[0]);
|
|
18147
18494
|
},
|
|
18495
|
+
'fadeOut' : (args) => {
|
|
18496
|
+
// set fadeOut time in ms
|
|
18497
|
+
this.setFadeOut(args[0]);
|
|
18498
|
+
},
|
|
18148
18499
|
'tempo' : (args) => {
|
|
18149
18500
|
let t = args[0];
|
|
18150
18501
|
if (isNaN(t)){
|
|
@@ -18276,7 +18627,8 @@ class MercuryInterpreter {
|
|
|
18276
18627
|
}
|
|
18277
18628
|
|
|
18278
18629
|
// copy current sounds over to past
|
|
18279
|
-
this._sounds = this.sounds.slice();
|
|
18630
|
+
// this._sounds = this.sounds.slice();
|
|
18631
|
+
this._sounds = [ ...this.sounds ];
|
|
18280
18632
|
// empty new sounds array
|
|
18281
18633
|
this.sounds = [];
|
|
18282
18634
|
|
|
@@ -18290,16 +18642,30 @@ class MercuryInterpreter {
|
|
|
18290
18642
|
}
|
|
18291
18643
|
}
|
|
18292
18644
|
|
|
18645
|
+
// get all the current counts and store in dict
|
|
18646
|
+
let countTransfer = {};
|
|
18647
|
+
this._sounds.map((s) => {
|
|
18648
|
+
countTransfer[s._name] = {
|
|
18649
|
+
count: s._count,
|
|
18650
|
+
beat: s._beatCount
|
|
18651
|
+
}
|
|
18652
|
+
});
|
|
18653
|
+
|
|
18654
|
+
// create new loops, transfer the counts
|
|
18655
|
+
for (let s = 0; s < this.sounds.length; s++){
|
|
18656
|
+
this.sounds[s].makeLoop(countTransfer, Object.values(countTransfer)[s]);
|
|
18657
|
+
}
|
|
18658
|
+
|
|
18293
18659
|
// start new loops;
|
|
18294
|
-
this.makeLoops(this.sounds);
|
|
18295
|
-
this.transferCounts(this._sounds, this.sounds);
|
|
18660
|
+
// this.makeLoops(this.sounds);
|
|
18661
|
+
// this.transferCounts(this._sounds, this.sounds);
|
|
18296
18662
|
|
|
18297
18663
|
// when all loops started fade in the new sounds and fade out old
|
|
18298
|
-
if (!this.sounds.length){
|
|
18299
|
-
this.startSounds(this.sounds);
|
|
18300
|
-
|
|
18301
|
-
this.startSounds(this.sounds, this.crossFade);
|
|
18664
|
+
// if (!this.sounds.length){
|
|
18665
|
+
// this.startSounds(this.sounds);
|
|
18666
|
+
// }
|
|
18302
18667
|
this.removeSounds(this._sounds, this.crossFade);
|
|
18668
|
+
this.startSounds(this.sounds);
|
|
18303
18669
|
|
|
18304
18670
|
this.resume();
|
|
18305
18671
|
|
|
@@ -18324,10 +18690,10 @@ class MercuryInterpreter {
|
|
|
18324
18690
|
}
|
|
18325
18691
|
}
|
|
18326
18692
|
module.exports = { MercuryInterpreter }
|
|
18327
|
-
},{"./core/MonoInput.js":58,"./core/MonoMidi.js":59,"./core/MonoSample.js":60,"./core/MonoSynth.js":61,"./core/PolySample.js":63,"./core/PolySynth.js":64,"./core/Util.js":66,"./data/genre-tempos.json":67,"mercury-lang":27,"tone":44,"total-serialism":47}],69:[function(require,module,exports){
|
|
18693
|
+
},{"./core/MonoInput.js":58,"./core/MonoMidi.js":59,"./core/MonoSample.js":60,"./core/MonoSynth.js":61,"./core/PolySample.js":63,"./core/PolySynth.js":64,"./core/Util.js":66,"./data/genre-tempos.json":67,"mercury-lang":27,"tone":44,"total-serialism":47,"total-serialism/src/gen-basic.js":48}],69:[function(require,module,exports){
|
|
18328
18694
|
|
|
18329
18695
|
console.log(`
|
|
18330
|
-
Mercury Engine by Timo Hoogland (c)
|
|
18696
|
+
Mercury Engine by Timo Hoogland (c) 2018-2025
|
|
18331
18697
|
more info:
|
|
18332
18698
|
https://www.timohoogland.com
|
|
18333
18699
|
https://github.com/tmhglnd/mercury-playground
|
|
@@ -18343,7 +18709,7 @@ const { WebMidi } = require("webmidi");
|
|
|
18343
18709
|
// load extra AudioWorkletProcessors from file
|
|
18344
18710
|
// transformed to inline with browserify brfs
|
|
18345
18711
|
|
|
18346
|
-
const fxExtensions = "\n// A white noise generator at -6dBFS to test AudioWorkletProcessor\n//\n// class NoiseProcessor extends AudioWorkletProcessor {\n// \tprocess(inputs, outputs, parameters){\n// \t\tconst output = outputs[0];\n\n// \t\toutput.forEach((channel) => {\n// \t\t\tfor (let i=0; i<channel.length; i++) {\n// \t\t\t\tchannel[i] = Math.random() - 0.5;\n// \t\t\t}\n// \t\t});\n// \t\treturn true;\n// \t}\n// }\n// registerProcessor('noise-processor', NoiseProcessor);\n\n// A Downsampling Chiptune effect. Downsamples the signal by a specified amount\n// Resulting in a lower samplerate, making it sound more like 8bit/chiptune\n// Programmed with a custom AudioWorkletProcessor, see effects/Processors.js\n//\nclass DownSampleProcessor extends AudioWorkletProcessor {\n\tstatic get parameterDescriptors() {\n\t\treturn [{\n\t\t\tname: 'down',\n\t\t\tdefaultValue: 8,\n\t\t\tminValue: 1,\n\t\t\tmaxValue: 2048\n\t\t}];\n\t}\n\n\tconstructor(){\n\t\tsuper();\n\t\t// the frame counter\n\t\tthis.count = 0;\n\t\t// sample and hold variable array\n\t\tthis.sah = [];\n\t}\n\n\tprocess(inputs, outputs, parameters){\n\t\tconst input = inputs[0];\n\t\tconst output = outputs[0];\n\n\t\t// if there is anything to process\n\t\tif (input.length > 0){\n\t\t\t// for the length of the sample array (generally 128)\n\t\t\tfor (let i=0; i<input[0].length; i++){\n\t\t\t\tconst d = (parameters.down.length > 1) ? parameters.down[i] : parameters.down[0];\n\t\t\t\t// for every channel\n\t\t\t\tfor (let channel=0; channel<input.length; ++channel){\n\t\t\t\t\t// if counter equals 0, sample and hold\n\t\t\t\t\tif (this.count % d === 0){\n\t\t\t\t\t\tthis.sah[channel] = input[channel][i];\n\t\t\t\t\t}\n\t\t\t\t\t// output the currently held sample\n\t\t\t\t\toutput[channel][i] = this.sah[channel];\n\t\t\t\t}\n\t\t\t\t// increment sample counter\n\t\t\t\tthis.count++;\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n}\nregisterProcessor('downsampler-processor', DownSampleProcessor);\n\n// A distortion algorithm using the tanh (hyperbolic-tangent) as a \n// waveshaping technique. Some mapping to apply a more equal loudness \n// distortion is applied on the overdrive parameter\n//\nclass TanhDistortionProcessor extends AudioWorkletProcessor {\n\tstatic get parameterDescriptors(){\n\t\treturn [{\n\t\t\tname: 'amount',\n\t\t\tdefaultValue: 4,\n\t\t\tminValue: 1\n\t\t}, {\n\t\t\tname: 'makeup',\n\t\t\tdefaultValue: 0.5,\n\t\t\tminValue: 0,\n\t\t\tmaxValue: 2\n\t\t}]\n\t}\n\n\tconstructor(){\n\t\tsuper();\n\t}\n\n\tprocess(inputs, outputs, parameters){\n\t\tconst input = inputs[0];\n\t\tconst output = outputs[0];\n\n\t\tif (input.length > 0){\n\t\t\tfor (let channel=0; channel<input.length; ++channel){\n\t\t\t\tfor (let i=0; i<input[channel].length; i++){\n\t\t\t\t\tconst a = (parameters.amount.length > 1)? parameters.amount[i] : parameters.amount[0];\n\t\t\t\t\tconst m = (parameters.makeup.length > 1)? parameters.makeup[i] : parameters.makeup[0];\n\t\t\t\t\t// simple waveshaping with tanh\n\t\t\t\t\toutput[channel][i] = Math.tanh(input[channel][i] * a) * m;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n}\nregisterProcessor('tanh-distortion-processor', TanhDistortionProcessor);\n\n// A distortion/compression effect of an incoming signal\n// Based on an algorithm by Peter McCulloch\n// \nclass SquashProcessor extends AudioWorkletProcessor {\n\tstatic get parameterDescriptors(){\n\t\treturn [{\n\t\t\tname: 'amount',\n\t\t\tdefaultValue: 4,\n\t\t\tminValue: 1,\n\t\t\tmaxValue: 1024\n\t\t}, {\n\t\t\tname: 'makeup',\n\t\t\tdefaultValue: 0.5,\n\t\t\tminValue: 0,\n\t\t\tmaxValue: 2\n\t\t}];\n\t}\n\n\tconstructor(){\n\t\tsuper();\n\t}\n\n\tprocess(inputs, outputs, parameters){\n\t\tconst input = inputs[0];\n\t\tconst output = outputs[0];\n\t\t\n\t\tif (input.length > 0){\n\t\t\tfor (let channel=0; channel<input.length; ++channel){\n\t\t\t\tfor (let i=0; i<input[channel].length; i++){\n\t\t\t\t\t// (s * a) / ((s * a)^2 * 0.28 + 1) / √a\n\t\t\t\t\t// drive amount, minimum of 1\n\t\t\t\t\tconst a = (parameters.amount.length > 1)? parameters.amount[i] : parameters.amount[0];\n\t\t\t\t\t// makeup gain\n\t\t\t\t\tconst m = (parameters.makeup.length > 1)? parameters.makeup[i] : parameters.makeup[0];\n\t\t\t\t\t// set the waveshaper effect\n\t\t\t\t\tconst s = input[channel][i];\n\t\t\t\t\tconst x = s * a * 1.412;\n\t\t\t\t\toutput[channel][i] = (x / (x * x * 0.28 + 1.0)) * m * 0.708;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n}\nregisterProcessor('squash-processor', SquashProcessor);";
|
|
18712
|
+
const fxExtensions = "\n// A white noise generator at -6dBFS to test AudioWorkletProcessor\n//\n// class NoiseProcessor extends AudioWorkletProcessor {\n// \tprocess(inputs, outputs, parameters){\n// \t\tconst output = outputs[0];\n\n// \t\toutput.forEach((channel) => {\n// \t\t\tfor (let i=0; i<channel.length; i++) {\n// \t\t\t\tchannel[i] = Math.random() - 0.5;\n// \t\t\t}\n// \t\t});\n// \t\treturn true;\n// \t}\n// }\n// registerProcessor('noise-processor', NoiseProcessor);\n\n// A Downsampling Chiptune effect. Downsamples the signal by a specified amount\n// Resulting in a lower samplerate, making it sound more like 8bit/chiptune\n// Programmed with a custom AudioWorkletProcessor, see effects/Processors.js\n//\nclass DownSampleProcessor extends AudioWorkletProcessor {\n\tstatic get parameterDescriptors() {\n\t\treturn [{\n\t\t\tname: 'down',\n\t\t\tdefaultValue: 8,\n\t\t\tminValue: 1,\n\t\t\tmaxValue: 2048\n\t\t}];\n\t}\n\n\tconstructor(){\n\t\tsuper();\n\t\t// the frame counter\n\t\tthis.count = 0;\n\t\t// sample and hold variable array\n\t\tthis.sah = [];\n\t}\n\n\tprocess(inputs, outputs, parameters){\n\t\tconst input = inputs[0];\n\t\tconst output = outputs[0];\n\n\t\t// if there is anything to process\n\t\tif (input.length > 0){\n\t\t\t// for the length of the sample array (generally 128)\n\t\t\tfor (let i=0; i<input[0].length; i++){\n\t\t\t\tconst d = (parameters.down.length > 1) ? parameters.down[i] : parameters.down[0];\n\t\t\t\t// for every channel\n\t\t\t\tfor (let channel=0; channel<input.length; ++channel){\n\t\t\t\t\t// if counter equals 0, sample and hold\n\t\t\t\t\tif (this.count % d === 0){\n\t\t\t\t\t\tthis.sah[channel] = input[channel][i];\n\t\t\t\t\t}\n\t\t\t\t\t// output the currently held sample\n\t\t\t\t\toutput[channel][i] = this.sah[channel];\n\t\t\t\t}\n\t\t\t\t// increment sample counter\n\t\t\t\tthis.count++;\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n}\nregisterProcessor('downsampler-processor', DownSampleProcessor);\n\n// A distortion algorithm using the tanh (hyperbolic-tangent) as a \n// waveshaping technique. Some mapping to apply a more equal loudness \n// distortion is applied on the overdrive parameter\n//\nclass TanhDistortionProcessor extends AudioWorkletProcessor {\n\tstatic get parameterDescriptors(){\n\t\treturn [{\n\t\t\tname: 'amount',\n\t\t\tdefaultValue: 4,\n\t\t\tminValue: 1\n\t\t}, {\n\t\t\tname: 'makeup',\n\t\t\tdefaultValue: 0.5,\n\t\t\tminValue: 0,\n\t\t\tmaxValue: 2\n\t\t}]\n\t}\n\n\tconstructor(){\n\t\tsuper();\n\t}\n\n\tprocess(inputs, outputs, parameters){\n\t\tconst input = inputs[0];\n\t\tconst output = outputs[0];\n\n\t\tif (input.length > 0){\n\t\t\tfor (let channel=0; channel<input.length; ++channel){\n\t\t\t\tfor (let i=0; i<input[channel].length; i++){\n\t\t\t\t\tconst a = (parameters.amount.length > 1)? parameters.amount[i] : parameters.amount[0];\n\t\t\t\t\tconst m = (parameters.makeup.length > 1)? parameters.makeup[i] : parameters.makeup[0];\n\t\t\t\t\t// simple waveshaping with tanh\n\t\t\t\t\toutput[channel][i] = Math.tanh(input[channel][i] * a) * m;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n}\nregisterProcessor('tanh-distortion-processor', TanhDistortionProcessor);\n\n// A distortion/compression effect of an incoming signal\n// Based on an algorithm by Peter McCulloch\n// \nclass SquashProcessor extends AudioWorkletProcessor {\n\tstatic get parameterDescriptors(){\n\t\treturn [{\n\t\t\tname: 'amount',\n\t\t\tdefaultValue: 4,\n\t\t\tminValue: 1,\n\t\t\tmaxValue: 1024\n\t\t}, {\n\t\t\tname: 'makeup',\n\t\t\tdefaultValue: 0.5,\n\t\t\tminValue: 0,\n\t\t\tmaxValue: 2\n\t\t}];\n\t}\n\n\tconstructor(){\n\t\tsuper();\n\t}\n\n\tprocess(inputs, outputs, parameters){\n\t\tconst input = inputs[0];\n\t\tconst output = outputs[0];\n\t\t\n\t\tif (input.length > 0){\n\t\t\tfor (let channel=0; channel<input.length; ++channel){\n\t\t\t\tfor (let i=0; i<input[channel].length; i++){\n\t\t\t\t\t// (s * a) / ((s * a)^2 * 0.28 + 1) / √a\n\t\t\t\t\t// drive amount, minimum of 1\n\t\t\t\t\tconst a = (parameters.amount.length > 1)? parameters.amount[i] : parameters.amount[0];\n\t\t\t\t\t// makeup gain\n\t\t\t\t\tconst m = (parameters.makeup.length > 1)? parameters.makeup[i] : parameters.makeup[0];\n\t\t\t\t\t// set the waveshaper effect\n\t\t\t\t\tconst s = input[channel][i];\n\t\t\t\t\tconst x = s * a * 1.412;\n\t\t\t\t\toutput[channel][i] = (x / (x * x * 0.28 + 1.0)) * m * 0.708;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n}\nregisterProcessor('squash-processor', SquashProcessor);\n\n// Dattorro Reverberator\n// Thanks to port by khoin, taken from:\n// https://github.com/khoin/DattorroReverbNode\n// based on the paper from Jon Dattorro:\n// https://ccrma.stanford.edu/~dattorro/EffectDesignPart1.pdf\n// with small modifications to work in Mercury\n//\n// In jurisdictions that recognize copyright laws, this software is to\n// be released into the public domain.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND.\n// THE AUTHOR(S) SHALL NOT BE LIABLE FOR ANYTHING, ARISING FROM, OR IN\n// CONNECTION WITH THE SOFTWARE OR THE DISTRIBUTION OF THE SOFTWARE.\n// \nclass DattorroReverb extends AudioWorkletProcessor {\n\tstatic get parameterDescriptors() {\n\t\treturn [\n\t\t\t[\"preDelay\", 0, 0, sampleRate - 1, \"k-rate\"],\n\t\t\t// [\"bandwidth\", 0.9999, 0, 1, \"k-rate\"],\t\n\t\t\t[\"inputDiffusion1\", 0.75, 0, 1, \"k-rate\"],\n\t\t\t[\"inputDiffusion2\", 0.625, 0, 1, \"k-rate\"],\n\t\t\t[\"decay\", 0.5, 0, 1, \"k-rate\"],\n\t\t\t[\"decayDiffusion1\", 0.7, 0, 0.999999, \"k-rate\"],\n\t\t\t[\"decayDiffusion2\", 0.5, 0, 0.999999, \"k-rate\"],\n\t\t\t[\"damping\", 0.005, 0, 1, \"k-rate\"],\n\t\t\t[\"excursionRate\", 0.5, 0, 2, \"k-rate\"],\n\t\t\t[\"excursionDepth\", 0.7, 0, 2, \"k-rate\"],\n\t\t\t[\"wet\", 0.7, 0, 2, \"k-rate\"],\n\t\t\t// [\"dry\", 0.7, 0, 2, \"k-rate\"]\n\t\t].map(x => new Object({\n\t\t\tname: x[0],\n\t\t\tdefaultValue: x[1],\n\t\t\tminValue: x[2],\n\t\t\tmaxValue: x[3],\n\t\t\tautomationRate: x[4]\n\t\t}));\n\t}\n\n\tconstructor(options) {\n\t\tsuper(options);\n\n\t\tthis._Delays = [];\n\t\t// Pre-delay is always one-second long, rounded to the nearest 128-chunk\n\t\tthis._pDLength = sampleRate + (128 - sampleRate % 128);\n\t\tthis._preDelay = new Float32Array(this._pDLength);\n\t\tthis._pDWrite = 0;\n\t\tthis._lp1 = 0.0;\n\t\tthis._lp2 = 0.0;\n\t\tthis._lp3 = 0.0;\n\t\tthis._excPhase = 0.0;\n\n\t\t[\n\t\t\t0.004771345, 0.003595309, 0.012734787, 0.009307483, // pre-tank\n\t\t\t0.022579886, 0.149625349, 0.060481839, 0.1249958, // left-loop\n\t\t\t0.030509727, 0.141695508, 0.089244313, 0.106280031 // right-loop\n\t\t].forEach(x => this.makeDelay(x));\n\n\t\tthis._taps = Int16Array.from([\n\t\t\t0.008937872, 0.099929438, 0.064278754, 0.067067639, \n\t\t\t0.066866033, 0.006283391, 0.035818689, // left-output\n\t\t\t0.011861161, 0.121870905, 0.041262054, 0.08981553, \n\t\t\t0.070931756, 0.011256342, 0.004065724 // right-output\n\t\t], x => Math.round(x * sampleRate));\n\t}\n\n\tmakeDelay(length) {\n\t\t// len, array, write, read, mask\n\t\tlet len = Math.round(length * sampleRate);\n\t\tlet nextPow2 = 2 ** Math.ceil(Math.log2((len)));\n\t\tthis._Delays.push([\n\t\t\tnew Float32Array(nextPow2), len - 1, 0 | 0, nextPow2 - 1\n\t\t]);\n\t}\n\n\twriteDelay(index, data) {\n\t\treturn this._Delays[index][0][this._Delays[index][1]] = data;\n\t}\n\n\treadDelay(index) {\n\t\treturn this._Delays[index][0][this._Delays[index][2]];\n\t}\n\n\treadDelayAt(index, i) {\n\t\tlet d = this._Delays[index];\n\t\treturn d[0][(d[2] + i) & d[3]];\n\t}\n\n\t// cubic interpolation\n\t// O. Niemitalo: \n\t// https://www.musicdsp.org/en/latest/Other/49-cubic-interpollation.html\n\treadDelayCAt(index, i) {\n\t\tlet d = this._Delays[index],\n\t\t\tfrac = i - ~~i,\n\t\t\tint = ~~i + d[2] - 1,\n\t\t\tmask = d[3];\n\n\t\tlet x0 = d[0][int++ & mask],\n\t\t\tx1 = d[0][int++ & mask],\n\t\t\tx2 = d[0][int++ & mask],\n\t\t\tx3 = d[0][int & mask];\n\n\t\tlet a = (3 * (x1 - x2) - x0 + x3) / 2,\n\t\t\tb = 2 * x2 + x0 - (5 * x1 + x3) / 2,\n\t\t\tc = (x2 - x0) / 2;\n\n\t\treturn (((a * frac) + b) * frac + c) * frac + x1;\n\t}\n\n\t// First input will be downmixed to mono if number of channels is not 2\n\t// Outputs Stereo.\n\tprocess(inputs, outputs, parameters) {\n\t\tconst pd = ~~parameters.preDelay[0],\n\t\t\t// bw = parameters.bandwidth[0], // replaced by using damping\n\t\t\tfi = parameters.inputDiffusion1[0],\n\t\t\tsi = parameters.inputDiffusion2[0],\n\t\t\tdc = parameters.decay[0],\n\t\t\tft = parameters.decayDiffusion1[0],\n\t\t\tst = parameters.decayDiffusion2[0],\n\t\t\tdp = 1 - parameters.damping[0],\n\t\t\tex = parameters.excursionRate[0] / sampleRate,\n\t\t\ted = parameters.excursionDepth[0] * sampleRate / 1000,\n\t\t\twe = parameters.wet[0]; //* 0.6, // lo & ro both mult. by 0.6 anyways\n\t\t\t// dr = parameters.dry[0];\n\n\t\t// write to predelay and dry output\n\t\tif (inputs[0].length == 2) {\n\t\t\tfor (let i = 127; i >= 0; i--) {\n\t\t\t\tthis._preDelay[this._pDWrite + i] = (inputs[0][0][i] + inputs[0][1][i]) * 0.5;\n\n\t\t\t\t// removed the dry parameter, this is handled in the Tone Node\n\t\t\t\t// outputs[0][0][i] = inputs[0][0][i] * dr;\n\t\t\t\t// outputs[0][1][i] = inputs[0][1][i] * dr;\n\t\t\t}\n\t\t} else if (inputs[0].length > 0) {\n\t\t\tthis._preDelay.set(\n\t\t\t\tinputs[0][0],\n\t\t\t\tthis._pDWrite\n\t\t\t);\n\t\t\t// for (let i = 127; i >= 0; i--)\n\t\t\t// \toutputs[0][0][i] = outputs[0][1][i] = inputs[0][0][i] * dr;\n\t\t} else {\n\t\t\tthis._preDelay.set(\n\t\t\t\tnew Float32Array(128),\n\t\t\t\tthis._pDWrite\n\t\t\t);\n\t\t}\n\n\t\tlet i = 0 | 0;\n\t\twhile (i < 128) {\n\t\t\tlet lo = 0.0,\n\t\t\t\tro = 0.0;\n\n\t\t\t// input damping (formerly known as bandwidth bw, now uses dp)\n\t\t\tthis._lp1 += dp * (this._preDelay[(this._pDLength + this._pDWrite - pd + i) % this._pDLength] - this._lp1);\n\n\t\t\t// pre-tank\n\t\t\tlet pre = this.writeDelay(0, this._lp1 - fi * this.readDelay(0));\n\t\t\tpre = this.writeDelay(1, fi * (pre - this.readDelay(1)) + this.readDelay(0));\n\t\t\tpre = this.writeDelay(2, fi * pre + this.readDelay(1) - si * this.readDelay(2));\n\t\t\tpre = this.writeDelay(3, si * (pre - this.readDelay(3)) + this.readDelay(2));\n\n\t\t\tlet split = si * pre + this.readDelay(3);\n\n\t\t\t// excursions\n\t\t\t// could be optimized?\n\t\t\tlet exc = ed * (1 + Math.cos(this._excPhase * 6.2800));\n\t\t\tlet exc2 = ed * (1 + Math.sin(this._excPhase * 6.2847));\n\n\t\t\t// left loop\n\t\t\t// tank diffuse 1\n\t\t\tlet temp = this.writeDelay(4, split + dc * this.readDelay(11) + ft * this.readDelayCAt(4, exc));\n\t\t\t// long delay 1\n\t\t\tthis.writeDelay(5, this.readDelayCAt(4, exc) - ft * temp);\n\t\t\t// damp 1\n\t\t\tthis._lp2 += dp * (this.readDelay(5) - this._lp2);\n\t\t\ttemp = this.writeDelay(6, dc * this._lp2 - st * this.readDelay(6)); // tank diffuse 2\n\t\t\t// long delay 2\n\t\t\tthis.writeDelay(7, this.readDelay(6) + st * temp);\n\n\t\t\t// right loop \n\t\t\t// tank diffuse 3\n\t\t\ttemp = this.writeDelay(8, split + dc * this.readDelay(7) + ft * this.readDelayCAt(8, exc2));\n\t\t\t// long delay 3\n\t\t\tthis.writeDelay(9, this.readDelayCAt(8, exc2) - ft * temp);\n\t\t\t// damp 2\n\t\t\tthis._lp3 += dp * (this.readDelay(9) - this._lp3);\n\t\t\t// tank diffuse 4\n\t\t\ttemp = this.writeDelay(10, dc * this._lp3 - st * this.readDelay(10));\n\t\t\t// long delay 4\n\t\t\tthis.writeDelay(11, this.readDelay(10) + st * temp);\n\n\t\t\tlo = this.readDelayAt(9, this._taps[0]) +\n\t\t\t\tthis.readDelayAt(9, this._taps[1]) -\n\t\t\t\tthis.readDelayAt(10, this._taps[2]) +\n\t\t\t\tthis.readDelayAt(11, this._taps[3]) -\n\t\t\t\tthis.readDelayAt(5, this._taps[4]) -\n\t\t\t\tthis.readDelayAt(6, this._taps[5]) -\n\t\t\t\tthis.readDelayAt(7, this._taps[6]);\n\n\t\t\tro = this.readDelayAt(5, this._taps[7]) +\n\t\t\t\tthis.readDelayAt(5, this._taps[8]) -\n\t\t\t\tthis.readDelayAt(6, this._taps[9]) +\n\t\t\t\tthis.readDelayAt(7, this._taps[10]) -\n\t\t\t\tthis.readDelayAt(9, this._taps[11]) -\n\t\t\t\tthis.readDelayAt(10, this._taps[12]) -\n\t\t\t\tthis.readDelayAt(11, this._taps[13]);\n\n\t\t\toutputs[0][0][i] += lo * we;\n\t\t\toutputs[0][1][i] += ro * we;\n\n\t\t\tthis._excPhase += ex;\n\n\t\t\ti++;\n\n\t\t\tfor (let j = 0, d = this._Delays[0]; j < this._Delays.length; d = this._Delays[++j]) {\n\t\t\t\td[1] = (d[1] + 1) & d[3];\n\t\t\t\td[2] = (d[2] + 1) & d[3];\n\t\t\t}\n\t\t}\n\n\t\t// Update preDelay index\n\t\tthis._pDWrite = (this._pDWrite + 128) % this._pDLength;\n\n\t\treturn true;\n\t}\n}\nregisterProcessor('dattorro-reverb', DattorroReverb);\n";
|
|
18347
18713
|
Tone.getContext().addAudioWorkletModule(URL.createObjectURL(new Blob([ fxExtensions ], { type: 'text/javascript' })));
|
|
18348
18714
|
|
|
18349
18715
|
// Mercury main class controls Tone and loads samples
|
|
@@ -18608,7 +18974,7 @@ class Mercury extends MercuryInterpreter {
|
|
|
18608
18974
|
|
|
18609
18975
|
// set highpass frequency cutoff and ramptime
|
|
18610
18976
|
setHighPass(f, t=0){
|
|
18611
|
-
this.highPass = (f === 'default')?
|
|
18977
|
+
this.highPass = (f === 'default')? 20 : f;
|
|
18612
18978
|
t = Util.divToS(t, this.bpm);
|
|
18613
18979
|
if (t > 0){
|
|
18614
18980
|
this.highPassF.frequency.rampTo(this.highPass, t, Tone.now());
|