mercury-engine 1.3.1 → 1.6.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 +765 -239
- package/dist/mercury.min.js +1 -1
- package/examples/interface/index.html +18 -4
- 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 : {})
|
|
@@ -15337,17 +15457,20 @@ const TL = require('total-serialism').Translate;
|
|
|
15337
15457
|
// all the available effects
|
|
15338
15458
|
const fxMap = {
|
|
15339
15459
|
'drive' : (params) => {
|
|
15340
|
-
return new
|
|
15460
|
+
return new Overdrive(params);
|
|
15341
15461
|
},
|
|
15342
15462
|
'distort' : (params) => {
|
|
15343
|
-
return new
|
|
15463
|
+
return new Overdrive(params);
|
|
15344
15464
|
},
|
|
15345
15465
|
'overdrive' : (params) => {
|
|
15346
|
-
return new
|
|
15466
|
+
return new Overdrive(params);
|
|
15347
15467
|
},
|
|
15348
15468
|
'squash' : (params) => {
|
|
15349
15469
|
return new Squash(params);
|
|
15350
15470
|
},
|
|
15471
|
+
'fuzz' : (params) => {
|
|
15472
|
+
return new Fuzz(params);
|
|
15473
|
+
},
|
|
15351
15474
|
'compress' : (params) => {
|
|
15352
15475
|
return new Compressor(params);
|
|
15353
15476
|
},
|
|
@@ -15372,14 +15495,17 @@ const fxMap = {
|
|
|
15372
15495
|
'degrade' : (params) => {
|
|
15373
15496
|
return new DownSampler(params);
|
|
15374
15497
|
},
|
|
15375
|
-
'
|
|
15498
|
+
'converb' : (params) => {
|
|
15376
15499
|
return new Reverb(params);
|
|
15377
15500
|
},
|
|
15501
|
+
'room' : (params) => {
|
|
15502
|
+
return new DattorroReverb(params);
|
|
15503
|
+
},
|
|
15378
15504
|
'hall' : (params) => {
|
|
15379
|
-
return new
|
|
15505
|
+
return new DattorroReverb(params);
|
|
15380
15506
|
},
|
|
15381
15507
|
'reverb' : (params) => {
|
|
15382
|
-
return new
|
|
15508
|
+
return new DattorroReverb(params);
|
|
15383
15509
|
},
|
|
15384
15510
|
'shift' : (params) => {
|
|
15385
15511
|
return new PitchShift(params);
|
|
@@ -15422,10 +15548,129 @@ const fxMap = {
|
|
|
15422
15548
|
},
|
|
15423
15549
|
'double' : (params) => {
|
|
15424
15550
|
return new Chorus(Util.mapDefaults(params, ['8/1', 8, 1]));
|
|
15551
|
+
},
|
|
15552
|
+
'vowel' : (params) => {
|
|
15553
|
+
return new FormantFilter(params);
|
|
15554
|
+
},
|
|
15555
|
+
'formant' : (params) => {
|
|
15556
|
+
return new FormantFilter(params);
|
|
15557
|
+
},
|
|
15558
|
+
'speak' : (params) => {
|
|
15559
|
+
return new FormantFilter(params);
|
|
15425
15560
|
}
|
|
15426
15561
|
}
|
|
15427
15562
|
module.exports = fxMap;
|
|
15428
15563
|
|
|
15564
|
+
// Dispose a array of nodes
|
|
15565
|
+
//
|
|
15566
|
+
function disposeNodes(nodes=[]) {
|
|
15567
|
+
nodes.forEach((n) => {
|
|
15568
|
+
n?.disconnect();
|
|
15569
|
+
n?.dispose();
|
|
15570
|
+
});
|
|
15571
|
+
}
|
|
15572
|
+
|
|
15573
|
+
// A formant/vowel filter. With this filter you can imitate the vowels of human
|
|
15574
|
+
// speech.
|
|
15575
|
+
//
|
|
15576
|
+
const FormantFilter = function(_params){
|
|
15577
|
+
// default values for the effect and separate parameters
|
|
15578
|
+
_params = Util.mapDefaults(_params, [ 'o', 0, 1, 1 ]);
|
|
15579
|
+
this._vowel = _params[0];
|
|
15580
|
+
this._slide = _params[1];
|
|
15581
|
+
this._shift = _params[2];
|
|
15582
|
+
this._wet = _params[3];
|
|
15583
|
+
|
|
15584
|
+
// the input and wetdry output nodes
|
|
15585
|
+
this._fx = new Tone.Gain(1);
|
|
15586
|
+
this._mix = new Tone.Add();
|
|
15587
|
+
this._mixWet = new Tone.Gain(0).connect(this._mix);
|
|
15588
|
+
this._mixDry = new Tone.Gain(1).connect(this._mix.addend);
|
|
15589
|
+
this._fx.connect(this._mixDry);
|
|
15590
|
+
|
|
15591
|
+
// data collected from various sources, please see the research on
|
|
15592
|
+
// https://github.com/tmhglnd/vowel-formants-graph
|
|
15593
|
+
this._formantData = {
|
|
15594
|
+
"oo" : [ 299, 850, 2250, "book" ],
|
|
15595
|
+
"u" : [ 438, 998, 2250, "foot" ],
|
|
15596
|
+
"oh" : [ 569, 856, 2410, "pot" ],
|
|
15597
|
+
"uh" : [ 518, 1189, 2390, "bug" ],
|
|
15598
|
+
"er" : [ 490, 1358, 1690, "bird" ],
|
|
15599
|
+
"a" : [ 730, 1102, 2440, "part" ],
|
|
15600
|
+
"ae" : [ 660, 1702, 2410, "lap" ],
|
|
15601
|
+
"e" : [ 528, 1855, 2480, "let" ],
|
|
15602
|
+
"i" : [ 400, 2002, 2250, "bit" ],
|
|
15603
|
+
"ee" : [ 270, 2296, 3010, "leap" ],
|
|
15604
|
+
"o" : [ 399, 709, 2420, "fold" ],
|
|
15605
|
+
"oe" : [ 360, 1546, 2346, "you" ]
|
|
15606
|
+
}
|
|
15607
|
+
this._vowels = Object.keys(this._formantData);
|
|
15608
|
+
|
|
15609
|
+
// a -12dB/octave lowpass filter for preserving low end
|
|
15610
|
+
this._lopass = new Tone.Filter(85, 'lowpass', -12).connect(this._mixWet);
|
|
15611
|
+
this._fx.connect(this._lopass);
|
|
15612
|
+
|
|
15613
|
+
// 3 bandpass biquadfilters for the formants
|
|
15614
|
+
// mix the filters together to one output
|
|
15615
|
+
this._formants = [];
|
|
15616
|
+
for (let f=0; f<3; f++){
|
|
15617
|
+
this._formants[f] = new Tone.Filter(this._formantData['o'][f], 'bandpass');
|
|
15618
|
+
// parallel processing of the filters from the input
|
|
15619
|
+
this._fx.connect(this._formants[f]);
|
|
15620
|
+
this._formants[f].connect(this._mixWet);
|
|
15621
|
+
}
|
|
15622
|
+
|
|
15623
|
+
this.set = function(c, time, bpm){
|
|
15624
|
+
let v = Util.getParam(this._vowel, c);
|
|
15625
|
+
let r = Util.divToS(Util.getParam(this._slide, c), bpm);
|
|
15626
|
+
let s = Util.clip(Util.getParam(this._shift, c), 0.17, 6);
|
|
15627
|
+
let w = Util.clip(Util.getParam(this._wet, c));
|
|
15628
|
+
|
|
15629
|
+
// get the formantdata from the object
|
|
15630
|
+
let freqs = this._formantData['oo'];
|
|
15631
|
+
v = (!isNaN(v)) ?
|
|
15632
|
+
this._vowels[Util.clip(v, 0, this._vowels.length)] : v;
|
|
15633
|
+
// make sure vowel is a valid option
|
|
15634
|
+
if (this._formantData.hasOwnProperty(v)){
|
|
15635
|
+
freqs = this._formantData[v];
|
|
15636
|
+
} else {
|
|
15637
|
+
log(`fx(vowel): ${v} is not a valid vowel selection, using default "o"`);
|
|
15638
|
+
}
|
|
15639
|
+
|
|
15640
|
+
// apply the frequencies, Q's and gain to the individual formant filters
|
|
15641
|
+
for (let f=0; f<this._formants.length; f++){
|
|
15642
|
+
// the frequency is the formant freq * shift factor
|
|
15643
|
+
if (r > 0) {
|
|
15644
|
+
this._formants[f].frequency.rampTo(freqs[f] * s, r, time);
|
|
15645
|
+
} else {
|
|
15646
|
+
this._formants[f].frequency.setValueAtTime(freqs[f] * s, time);
|
|
15647
|
+
}
|
|
15648
|
+
// Q = (Freq * Shift) / (BandWidthHz / 2)
|
|
15649
|
+
// Default bandwidth set to 50Hz
|
|
15650
|
+
this._formants[f].Q.setValueAtTime(freqs[f] * s * 0.05, time);
|
|
15651
|
+
// Apply gain compensation based on formant number, +18dB, +5, +2
|
|
15652
|
+
this._formants[f].output.gain.setValueAtTime(18 * (0.31 ** f), time);
|
|
15653
|
+
}
|
|
15654
|
+
|
|
15655
|
+
// apply wetdry mix
|
|
15656
|
+
this._mixWet.gain.setValueAtTime(w, time);
|
|
15657
|
+
this._mixDry.gain.setValueAtTime(1 - w, time);
|
|
15658
|
+
}
|
|
15659
|
+
|
|
15660
|
+
this.chain = function(){
|
|
15661
|
+
return { 'send' : this._fx, 'return' : this._mix }
|
|
15662
|
+
}
|
|
15663
|
+
|
|
15664
|
+
this.delete = function(){
|
|
15665
|
+
const nodes = [ this._fx, this._mix, this._mixWet, this._mixDry, ...this._formants, this._lopass ];
|
|
15666
|
+
|
|
15667
|
+
nodes.forEach((n) => {
|
|
15668
|
+
n.disconnect();
|
|
15669
|
+
n.dispose();
|
|
15670
|
+
});
|
|
15671
|
+
}
|
|
15672
|
+
}
|
|
15673
|
+
|
|
15429
15674
|
// A Downsampling Chiptune effect. Downsamples the signal by a specified amount
|
|
15430
15675
|
// Resulting in a lower samplerate, making it sound more like 8bit/chiptune
|
|
15431
15676
|
// Programmed with a custom AudioWorkletProcessor, see effects/Processors.js
|
|
@@ -15471,7 +15716,7 @@ const DownSampler = function(_params){
|
|
|
15471
15716
|
}
|
|
15472
15717
|
|
|
15473
15718
|
this.delete = function(){
|
|
15474
|
-
const nodes = [ this._fx, this._mix, this._mixDry ];
|
|
15719
|
+
const nodes = [ this._fx.input, this._fx.output, this._fx, this._mix, this._mixDry ];
|
|
15475
15720
|
|
|
15476
15721
|
nodes.forEach((n) => {
|
|
15477
15722
|
n.disconnect();
|
|
@@ -15480,18 +15725,16 @@ const DownSampler = function(_params){
|
|
|
15480
15725
|
}
|
|
15481
15726
|
}
|
|
15482
15727
|
|
|
15483
|
-
//
|
|
15728
|
+
// An overdrive/saturation algorithm using the arctan function as a
|
|
15484
15729
|
// waveshaping technique. Some mapping to apply a more equal loudness
|
|
15485
|
-
//
|
|
15730
|
+
// on the overdrive parameter when increasing the amount
|
|
15486
15731
|
//
|
|
15487
|
-
const
|
|
15488
|
-
_params = Util.mapDefaults(_params, [
|
|
15732
|
+
const Overdrive = function(_params){
|
|
15733
|
+
_params = Util.mapDefaults(_params, [ 2, 1 ]);
|
|
15489
15734
|
// apply the default values and convert to arrays where necessary
|
|
15490
15735
|
this._drive = Util.toArray(_params[0]);
|
|
15491
15736
|
this._wet = Util.toArray(_params[1]);
|
|
15492
15737
|
|
|
15493
|
-
console.log('Distortion with:', this._drive, this._wet);
|
|
15494
|
-
|
|
15495
15738
|
// The crossfader for wet-dry (originally implemented with CrossFade)
|
|
15496
15739
|
// this._mix = new Tone.CrossFade();
|
|
15497
15740
|
this._mix = new Tone.Add();
|
|
@@ -15505,7 +15748,7 @@ const TanhDistortion = function(_params){
|
|
|
15505
15748
|
this._fx.output = new Tone.Gain(1).connect(this._mixWet);
|
|
15506
15749
|
|
|
15507
15750
|
// the fx processor
|
|
15508
|
-
this._fx.workletNode = Tone.getContext().createAudioWorkletNode('
|
|
15751
|
+
this._fx.workletNode = Tone.getContext().createAudioWorkletNode('arctan-distortion-processor');
|
|
15509
15752
|
|
|
15510
15753
|
// connect input, fx, output and wetdry
|
|
15511
15754
|
this._fx.input.chain(this._fx.workletNode, this._fx.output);
|
|
@@ -15514,17 +15757,9 @@ const TanhDistortion = function(_params){
|
|
|
15514
15757
|
// drive amount, minimum drive of 1
|
|
15515
15758
|
const d = Util.assureNum(Math.max(0, Util.getParam(this._drive, c)) + 1);
|
|
15516
15759
|
|
|
15517
|
-
// preamp gain reduction for linear at drive = 1
|
|
15518
|
-
const p = 0.8;
|
|
15519
|
-
// makeup gain
|
|
15520
|
-
const m = 1.0 / (p * (d ** 1.1));
|
|
15521
|
-
|
|
15522
15760
|
// set the parameters in the workletNode
|
|
15523
15761
|
const amount = this._fx.workletNode.parameters.get('amount');
|
|
15524
|
-
amount.setValueAtTime(
|
|
15525
|
-
|
|
15526
|
-
const makeup = this._fx.workletNode.parameters.get('makeup');
|
|
15527
|
-
makeup.setValueAtTime(m, time);
|
|
15762
|
+
amount.setValueAtTime(d, time);
|
|
15528
15763
|
|
|
15529
15764
|
const wet = Util.clip(Util.getParam(this._wet, c), 0, 1);
|
|
15530
15765
|
this._mixWet.gain.setValueAtTime(wet);
|
|
@@ -15536,12 +15771,57 @@ const TanhDistortion = function(_params){
|
|
|
15536
15771
|
}
|
|
15537
15772
|
|
|
15538
15773
|
this.delete = function(){
|
|
15539
|
-
|
|
15774
|
+
disposeNodes([ this._fx, this._fx.input, this._fx.output, this._mix, this._mixDry, this._mixWet ]);
|
|
15775
|
+
}
|
|
15776
|
+
}
|
|
15540
15777
|
|
|
15541
|
-
|
|
15542
|
-
|
|
15543
|
-
|
|
15544
|
-
|
|
15778
|
+
// A fuzz distortion effect in modelled after the Big Muff Pi pedal
|
|
15779
|
+
// by Electro Harmonics. Using three stages of distortion:
|
|
15780
|
+
// 1 soft-clipping stage, 2 half-wave rectifier, 3 hard-clipping stage
|
|
15781
|
+
//
|
|
15782
|
+
const Fuzz = function(_params){
|
|
15783
|
+
_params = Util.mapDefaults(_params, [ 10, 1 ]);
|
|
15784
|
+
// apply the default values and convert to arrays where necessary
|
|
15785
|
+
this._drive = Util.toArray(_params[0]);
|
|
15786
|
+
this._wet = Util.toArray(_params[1]);
|
|
15787
|
+
|
|
15788
|
+
// The crossfader for wet-dry (originally implemented with CrossFade)
|
|
15789
|
+
// this._mix = new Tone.CrossFade();
|
|
15790
|
+
this._mix = new Tone.Add();
|
|
15791
|
+
this._mixWet = new Tone.Gain(0).connect(this._mix.input);
|
|
15792
|
+
this._mixDry = new Tone.Gain(1).connect(this._mix.addend);
|
|
15793
|
+
|
|
15794
|
+
// ToneAudioNode has all the tone effect parameters
|
|
15795
|
+
this._fx = new Tone.ToneAudioNode();
|
|
15796
|
+
// A gain node for connecting with input and output
|
|
15797
|
+
this._fx.input = new Tone.Gain(1).connect(this._mixDry);
|
|
15798
|
+
this._fx.output = new Tone.Gain(1).connect(this._mixWet);
|
|
15799
|
+
|
|
15800
|
+
// the fx processor
|
|
15801
|
+
this._fx.workletNode = Tone.getContext().createAudioWorkletNode('fuzz-processor');
|
|
15802
|
+
|
|
15803
|
+
// connect input, fx, output to wetdry
|
|
15804
|
+
this._fx.input.chain(this._fx.workletNode, this._fx.output);
|
|
15805
|
+
|
|
15806
|
+
this.set = function(c, time, bpm){
|
|
15807
|
+
// drive amount, minimum drive of 1
|
|
15808
|
+
const d = Util.assureNum(Math.max(1, Util.getParam(this._drive, c)) + 1);
|
|
15809
|
+
|
|
15810
|
+
// set the parameters in the workletNode
|
|
15811
|
+
const amount = this._fx.workletNode.parameters.get('amount');
|
|
15812
|
+
amount.setValueAtTime(d, time);
|
|
15813
|
+
|
|
15814
|
+
const wet = Util.clip(Util.getParam(this._wet, c), 0, 1);
|
|
15815
|
+
this._mixWet.gain.setValueAtTime(wet);
|
|
15816
|
+
this._mixDry.gain.setValueAtTime(1 - wet);
|
|
15817
|
+
}
|
|
15818
|
+
|
|
15819
|
+
this.chain = function(){
|
|
15820
|
+
return { 'send' : this._fx, 'return' : this._mix }
|
|
15821
|
+
}
|
|
15822
|
+
|
|
15823
|
+
this.delete = function(){
|
|
15824
|
+
disposeNodes([ this._fx, this._fx.input, this._fx.output, this._mix, this._mixDry, this._mixWet ]);
|
|
15545
15825
|
}
|
|
15546
15826
|
}
|
|
15547
15827
|
|
|
@@ -15664,7 +15944,7 @@ const Squash = function(_params){
|
|
|
15664
15944
|
}
|
|
15665
15945
|
|
|
15666
15946
|
this.delete = function(){
|
|
15667
|
-
let nodes = [ this._fx, this._mix, this._mixDry ];
|
|
15947
|
+
let nodes = [ this._fx.input, this._fx.output, this._fx, this._mix, this._mixDry ];
|
|
15668
15948
|
|
|
15669
15949
|
nodes.forEach((n) => {
|
|
15670
15950
|
n.disconnect();
|
|
@@ -15705,6 +15985,65 @@ const Reverb = function(_params){
|
|
|
15705
15985
|
}
|
|
15706
15986
|
}
|
|
15707
15987
|
|
|
15988
|
+
// Dattorro Reverb FX
|
|
15989
|
+
// Good sound plate reverb emulation
|
|
15990
|
+
// Based on the paper by Jon Dattorro and the code from Khoin
|
|
15991
|
+
// https://ccrma.stanford.edu/~dattorro/EffectDesignPart1.pdf
|
|
15992
|
+
// https://github.com/khoin/DattorroReverbNode
|
|
15993
|
+
//
|
|
15994
|
+
const DattorroReverb = function(_params){
|
|
15995
|
+
_params = Util.mapDefaults(_params, [ 0.5, 10, 0, 0.5 ]);
|
|
15996
|
+
this._gain = Util.toArray(_params[0]);
|
|
15997
|
+
this._size = Util.toArray(_params[1]);
|
|
15998
|
+
// unused currently, but here for compatibility with Mercury4Max code
|
|
15999
|
+
// this._slide = Util.toArray(_params[2]);
|
|
16000
|
+
this._wet = Util.toArray(_params[3]);
|
|
16001
|
+
|
|
16002
|
+
// The crossfader for wet-dry (originally implemented with CrossFade)
|
|
16003
|
+
this._mix = new Tone.Add();
|
|
16004
|
+
this._mixWet = new Tone.Gain(0).connect(this._mix);
|
|
16005
|
+
this._mixDry = new Tone.Gain(1).connect(this._mix.addend);
|
|
16006
|
+
|
|
16007
|
+
// a custom tone audio node with input/output gain and worklet effect
|
|
16008
|
+
this._fx = new Tone.ToneAudioNode();
|
|
16009
|
+
this._fx.input = new Tone.Gain(1).connect(this._mixDry);
|
|
16010
|
+
this._fx.output = new Tone.Gain(1).connect(this._mixWet);
|
|
16011
|
+
this._fx.workletNode = Tone.getContext().createAudioWorkletNode('dattorro-reverb');
|
|
16012
|
+
this._fx.input.chain(this._fx.workletNode, this._fx.output);
|
|
16013
|
+
|
|
16014
|
+
this.set = (c, time) => {
|
|
16015
|
+
const gn = Math.max(Util.getParam(this._gain, c), 0);
|
|
16016
|
+
const meta = Util.clip(Util.getParam(this._size, c), 0, 20);
|
|
16017
|
+
const wet = Util.clip(Util.getParam(this._wet, c));
|
|
16018
|
+
|
|
16019
|
+
const dc = Util.remap(meta, 0, 20, 0.01, 0.99, 0.8);
|
|
16020
|
+
const df = Util.remap(meta, 0, 20, 0.2, 0.75, 0.5);
|
|
16021
|
+
const dp = Util.remap(meta, 0, 20, 0.2, 0.65, 2.5);
|
|
16022
|
+
const pd = Util.remap(meta, 0, 20, 700, 100);
|
|
16023
|
+
|
|
16024
|
+
this._fx.workletNode.parameters.get('decay').setValueAtTime(dc, time);
|
|
16025
|
+
this._fx.workletNode.parameters.get('decayDiffusion1').setValueAtTime(df, time);
|
|
16026
|
+
this._fx.workletNode.parameters.get('damping').setValueAtTime(dp, time);
|
|
16027
|
+
this._fx.workletNode.parameters.get('preDelay').setValueAtTime(pd, time);
|
|
16028
|
+
|
|
16029
|
+
this._fx.workletNode.parameters.get('wet').setValueAtTime(gn * 0.7, time);
|
|
16030
|
+
// this._fx.workletNode.parameters.get('dry').setValueAtTime(0.7, time);
|
|
16031
|
+
|
|
16032
|
+
// apply wetdry mix
|
|
16033
|
+
this._mixWet.gain.setValueAtTime(wet, time);
|
|
16034
|
+
this._mixDry.gain.setValueAtTime(1 - wet, time);
|
|
16035
|
+
}
|
|
16036
|
+
|
|
16037
|
+
this.chain = () => {
|
|
16038
|
+
return { 'send' : this._fx, 'return' : this._mix }
|
|
16039
|
+
}
|
|
16040
|
+
|
|
16041
|
+
this.delete = () => {
|
|
16042
|
+
const nodes = [ this._fx, this._mix, this._mixDry, this._fx.input, this._fx.output ];
|
|
16043
|
+
nodes.forEach(n => { n.disconnect(); n.dispose() });
|
|
16044
|
+
}
|
|
16045
|
+
}
|
|
16046
|
+
|
|
15708
16047
|
// PitchShift FX
|
|
15709
16048
|
// Shift the pitch up or down with semitones
|
|
15710
16049
|
// Utilizes the default PitchShift FX from ToneJS
|
|
@@ -16281,7 +16620,7 @@ const Delay = function(_params){
|
|
|
16281
16620
|
// this._fx.dispose();
|
|
16282
16621
|
// }
|
|
16283
16622
|
// }
|
|
16284
|
-
},{"./Util.js":
|
|
16623
|
+
},{"./Util.js":67,"tone":44,"total-serialism":47}],57:[function(require,module,exports){
|
|
16285
16624
|
const Tone = require('tone');
|
|
16286
16625
|
const Util = require('./Util.js');
|
|
16287
16626
|
const fxMap = require('./Effects.js');
|
|
@@ -16304,6 +16643,7 @@ class Instrument extends Sequencer {
|
|
|
16304
16643
|
this.adsr;
|
|
16305
16644
|
this.panner;
|
|
16306
16645
|
this.gain;
|
|
16646
|
+
this.post;
|
|
16307
16647
|
this._fx;
|
|
16308
16648
|
|
|
16309
16649
|
// The source to be defined by inheriting class
|
|
@@ -16314,9 +16654,11 @@ class Instrument extends Sequencer {
|
|
|
16314
16654
|
|
|
16315
16655
|
channelStrip(){
|
|
16316
16656
|
// gain => output
|
|
16317
|
-
this.gain = new Tone.Gain(0).toDestination();
|
|
16657
|
+
this.gain = new Tone.Gain(0, "normalRange").toDestination();
|
|
16658
|
+
// postfx-gain => gain (for gain() function in instrument)
|
|
16659
|
+
this.post = new Tone.Gain(1, "gain").connect(this.gain);
|
|
16318
16660
|
// panning => gain
|
|
16319
|
-
this.panner = new Tone.Panner(0).connect(this.
|
|
16661
|
+
this.panner = new Tone.Panner(0).connect(this.post);
|
|
16320
16662
|
// adsr => panning
|
|
16321
16663
|
this.adsr = this.envelope(this.panner);
|
|
16322
16664
|
// return Node to connect source => adsr
|
|
@@ -16325,15 +16667,16 @@ class Instrument extends Sequencer {
|
|
|
16325
16667
|
|
|
16326
16668
|
envelope(d){
|
|
16327
16669
|
// return an Envelope and connect to next node
|
|
16328
|
-
return new Tone.AmplitudeEnvelope({
|
|
16329
|
-
|
|
16330
|
-
|
|
16331
|
-
|
|
16332
|
-
|
|
16333
|
-
|
|
16334
|
-
|
|
16335
|
-
|
|
16336
|
-
}).connect(d);
|
|
16670
|
+
// return new Tone.AmplitudeEnvelope({
|
|
16671
|
+
// attack: 0,
|
|
16672
|
+
// attackCurve: "linear",
|
|
16673
|
+
// decay: 0,
|
|
16674
|
+
// decayCurve: "linear",
|
|
16675
|
+
// sustain: 1,
|
|
16676
|
+
// release: 0.001,
|
|
16677
|
+
// releaseCurve: "linear"
|
|
16678
|
+
// }).connect(d);
|
|
16679
|
+
return new Tone.Gain(0).connect(d);
|
|
16337
16680
|
}
|
|
16338
16681
|
|
|
16339
16682
|
event(c, time){
|
|
@@ -16343,7 +16686,7 @@ class Instrument extends Sequencer {
|
|
|
16343
16686
|
|
|
16344
16687
|
// set FX parameters
|
|
16345
16688
|
if (this._fx){
|
|
16346
|
-
for (let f=0; f<this._fx.length; f++){
|
|
16689
|
+
for (let f = 0; f < this._fx.length; f++){
|
|
16347
16690
|
this._fx[f].set(c, time, this.bpm());
|
|
16348
16691
|
}
|
|
16349
16692
|
}
|
|
@@ -16354,9 +16697,12 @@ class Instrument extends Sequencer {
|
|
|
16354
16697
|
this.panner.pan.setValueAtTime(p, time);
|
|
16355
16698
|
|
|
16356
16699
|
// ramp volume
|
|
16357
|
-
let g = Util.atodb(Util.getParam(this._gain[0], c) * 0.707);
|
|
16700
|
+
// let g = Util.atodb(Util.getParam(this._gain[0], c) * 0.707);
|
|
16701
|
+
let g = Util.getParam(this._gain[0], c) * 0.707;
|
|
16358
16702
|
let r = Util.msToS(Math.max(0, Util.getParam(this._gain[1], c)));
|
|
16359
|
-
this.source.volume.rampTo(g, r, time);
|
|
16703
|
+
// this.source.volume.rampTo(g, r, time);
|
|
16704
|
+
this.source.volume.setValueAtTime(1, time);
|
|
16705
|
+
this.post.gain.rampTo(g, r, time);
|
|
16360
16706
|
|
|
16361
16707
|
this.sourceEvent(c, e, time);
|
|
16362
16708
|
|
|
@@ -16372,28 +16718,25 @@ class Instrument extends Sequencer {
|
|
|
16372
16718
|
|
|
16373
16719
|
// set shape for playback (fade-in / out and length)
|
|
16374
16720
|
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);
|
|
16721
|
+
const att = Math.max(Util.divToS(Util.getParam(this._att, c), this.bpm()), 0.001);
|
|
16722
|
+
const dec = Util.divToS(Util.getParam(this._sus, c), this.bpm());
|
|
16723
|
+
const rel = Math.max(Util.divToS(Util.getParam(this._rel, c), this.bpm()), 0.001);
|
|
16383
16724
|
|
|
16384
|
-
//
|
|
16385
|
-
//
|
|
16386
|
-
|
|
16387
|
-
this.adsr.
|
|
16388
|
-
|
|
16389
|
-
|
|
16390
|
-
|
|
16391
|
-
|
|
16392
|
-
//
|
|
16393
|
-
|
|
16725
|
+
// short ramp for retrigger, fades out the envelope over 2 ms
|
|
16726
|
+
// use the retrigger time to schedule the event a bit later as well
|
|
16727
|
+
let retrigger = 0;
|
|
16728
|
+
if (this.adsr.gain.getValueAtTime(time) > 0.01){
|
|
16729
|
+
retrigger = 0.002;
|
|
16730
|
+
// short ramp for retrigger, fades out the previous ramp
|
|
16731
|
+
this.adsr.gain.linearRampTo(0.0, retrigger, time);
|
|
16732
|
+
}
|
|
16733
|
+
// trigger the envelope and release after specified time
|
|
16734
|
+
this.adsr.gain.linearRampTo(1.0, att, time + retrigger);
|
|
16735
|
+
// exponential rampto * 5 for a good sounding exponential ramp
|
|
16736
|
+
this.adsr.gain.exponentialRampTo(0.0, rel * 5, time + att + dec + retrigger);
|
|
16394
16737
|
} else {
|
|
16395
|
-
// if shape is 'off'
|
|
16396
|
-
this.adsr.
|
|
16738
|
+
// if shape is 'off' turn on the gain of the envelope
|
|
16739
|
+
this.adsr.gain.setValueAtTime(1.0, time);
|
|
16397
16740
|
}
|
|
16398
16741
|
}
|
|
16399
16742
|
|
|
@@ -16403,18 +16746,34 @@ class Instrument extends Sequencer {
|
|
|
16403
16746
|
console.log('Instrument()', this._name, c);
|
|
16404
16747
|
}
|
|
16405
16748
|
|
|
16406
|
-
fadeIn(t){
|
|
16749
|
+
fadeIn(t=0){
|
|
16407
16750
|
// fade in the sound upon evaluation of code
|
|
16408
|
-
this.gain.gain.rampTo(1, t, Tone.now());
|
|
16751
|
+
// this.gain.gain.rampTo(1, t, Tone.now());
|
|
16752
|
+
// fade in the sound directly in 5 ms
|
|
16753
|
+
this.gain.gain.rampTo(1, 0.005, Tone.now());
|
|
16409
16754
|
}
|
|
16410
16755
|
|
|
16411
|
-
fadeOut(t){
|
|
16412
|
-
//
|
|
16413
|
-
|
|
16756
|
+
fadeOut(t, immediately=false){
|
|
16757
|
+
// if immediately is true, fade-out immediately instead of waiting
|
|
16758
|
+
let restTime = 0;
|
|
16759
|
+
|
|
16760
|
+
if (this._loop && !immediately){
|
|
16761
|
+
// get the remaining time till the next trigger in the loop
|
|
16762
|
+
// cancel the loop before that trigger happens and fade-out
|
|
16763
|
+
restTime = (1 - this._loop.progress) * this._loop.interval;
|
|
16764
|
+
}
|
|
16765
|
+
|
|
16414
16766
|
setTimeout(() => {
|
|
16415
|
-
|
|
16416
|
-
|
|
16417
|
-
|
|
16767
|
+
// stop the loop
|
|
16768
|
+
if (this._loop) this._loop.mute = 1;
|
|
16769
|
+
// fade out the sound upon evaluation of new code
|
|
16770
|
+
this.gain.gain.rampTo(0, t, Tone.now());
|
|
16771
|
+
|
|
16772
|
+
setTimeout(() => {
|
|
16773
|
+
this.delete();
|
|
16774
|
+
// wait a little bit extra before deleting to avoid clicks
|
|
16775
|
+
}, t * 1000 + 100);
|
|
16776
|
+
}, restTime * 1000 - 25);
|
|
16418
16777
|
}
|
|
16419
16778
|
|
|
16420
16779
|
delete(){
|
|
@@ -16424,9 +16783,20 @@ class Instrument extends Sequencer {
|
|
|
16424
16783
|
this.gain.disconnect();
|
|
16425
16784
|
this.gain.dispose();
|
|
16426
16785
|
|
|
16786
|
+
this.post.disconnect();
|
|
16787
|
+
this.post.dispose();
|
|
16788
|
+
|
|
16427
16789
|
this.panner.disconnect();
|
|
16428
16790
|
this.panner.dispose();
|
|
16429
|
-
|
|
16791
|
+
|
|
16792
|
+
this.adsr?.disconnect();
|
|
16793
|
+
this.adsr?.dispose();
|
|
16794
|
+
|
|
16795
|
+
// dispose the sound source (depending on inheriting class)
|
|
16796
|
+
this.source?.stop();
|
|
16797
|
+
this.source?.disconnect();
|
|
16798
|
+
this.source?.dispose();
|
|
16799
|
+
|
|
16430
16800
|
// remove all fx
|
|
16431
16801
|
this._fx.map((f) => f.delete());
|
|
16432
16802
|
console.log('=> disposed Instrument() with FX:', this._fx);
|
|
@@ -16476,7 +16846,7 @@ class Instrument extends Sequencer {
|
|
|
16476
16846
|
add_fx(...fx){
|
|
16477
16847
|
// the effects chain for the sound
|
|
16478
16848
|
this._fx = [];
|
|
16479
|
-
|
|
16849
|
+
|
|
16480
16850
|
fx.forEach((f) => {
|
|
16481
16851
|
if (fxMap[f[0]]){
|
|
16482
16852
|
let tmpF = fxMap[f[0]](f.slice(1));
|
|
@@ -16498,19 +16868,20 @@ class Instrument extends Sequencer {
|
|
|
16498
16868
|
// allowing to chain multiple effects within one process
|
|
16499
16869
|
let pfx = this._ch[0];
|
|
16500
16870
|
this.panner.connect(pfx.send);
|
|
16501
|
-
for (let f=1; f<this._ch.length; f++){
|
|
16871
|
+
for (let f = 1; f < this._ch.length; f++){
|
|
16502
16872
|
if (pfx){
|
|
16503
16873
|
pfx.return.connect(this._ch[f].send);
|
|
16504
16874
|
}
|
|
16505
16875
|
pfx = this._ch[f];
|
|
16506
16876
|
}
|
|
16507
16877
|
// pfx.return.connect(Tone.Destination);
|
|
16508
|
-
pfx.return.connect(this.gain);
|
|
16878
|
+
// pfx.return.connect(this.gain);
|
|
16879
|
+
pfx.return.connect(this.post);
|
|
16509
16880
|
}
|
|
16510
16881
|
}
|
|
16511
16882
|
}
|
|
16512
16883
|
module.exports = Instrument;
|
|
16513
|
-
},{"./Effects.js":56,"./Sequencer.js":
|
|
16884
|
+
},{"./Effects.js":56,"./Sequencer.js":66,"./Util.js":67,"tone":44}],58:[function(require,module,exports){
|
|
16514
16885
|
const Tone = require('tone');
|
|
16515
16886
|
const Instrument = require('./Instrument.js');
|
|
16516
16887
|
const Util = require('./Util.js');
|
|
@@ -16562,7 +16933,7 @@ class MonoInput extends Instrument {
|
|
|
16562
16933
|
}
|
|
16563
16934
|
}
|
|
16564
16935
|
module.exports = MonoInput;
|
|
16565
|
-
},{"./Instrument.js":57,"./Util.js":
|
|
16936
|
+
},{"./Instrument.js":57,"./Util.js":67,"tone":44}],59:[function(require,module,exports){
|
|
16566
16937
|
const Tone = require('tone');
|
|
16567
16938
|
const Util = require('./Util.js');
|
|
16568
16939
|
const Sequencer = require('./Sequencer.js');
|
|
@@ -16639,7 +17010,7 @@ class MonoMidi extends Sequencer {
|
|
|
16639
17010
|
// only play a note if the notes are provided in the function
|
|
16640
17011
|
// if (this._note.length > 0){
|
|
16641
17012
|
|
|
16642
|
-
let noteOptions = { duration: d,
|
|
17013
|
+
let noteOptions = { duration: d, attack: g, time: sync };
|
|
16643
17014
|
|
|
16644
17015
|
// if a midinote is selected instead of note
|
|
16645
17016
|
// play the value without mapping
|
|
@@ -16743,7 +17114,101 @@ class MonoMidi extends Sequencer {
|
|
|
16743
17114
|
}
|
|
16744
17115
|
}
|
|
16745
17116
|
module.exports = MonoMidi;
|
|
16746
|
-
},{"./Sequencer.js":
|
|
17117
|
+
},{"./Sequencer.js":66,"./Util.js":67,"tone":44,"webmidi":55}],60:[function(require,module,exports){
|
|
17118
|
+
const Tone = require('tone');
|
|
17119
|
+
const Instrument = require('./Instrument.js');
|
|
17120
|
+
const { toArray, getParam, clip, log } = require('./Util.js');
|
|
17121
|
+
|
|
17122
|
+
class MonoNoise extends Instrument {
|
|
17123
|
+
constructor(engine, t='white', canvas){
|
|
17124
|
+
// Inherit from Instrument
|
|
17125
|
+
super(engine, canvas);
|
|
17126
|
+
|
|
17127
|
+
// synth specific variables;
|
|
17128
|
+
this._type = toArray(t);
|
|
17129
|
+
this._typeMap = {
|
|
17130
|
+
'white' : 0,
|
|
17131
|
+
'pink' : 1,
|
|
17132
|
+
'brownian' : 2,
|
|
17133
|
+
'brown' : 2,
|
|
17134
|
+
'browny' : 2,
|
|
17135
|
+
'red' : 2,
|
|
17136
|
+
'lofi' : 3,
|
|
17137
|
+
'dust' : 4,
|
|
17138
|
+
'crackle' : 5
|
|
17139
|
+
}
|
|
17140
|
+
this._density = [ 0.25 ];
|
|
17141
|
+
this.started = false;
|
|
17142
|
+
this.createSource();
|
|
17143
|
+
|
|
17144
|
+
console.log('=> MonoNoise()', this);
|
|
17145
|
+
}
|
|
17146
|
+
|
|
17147
|
+
createSource(){
|
|
17148
|
+
// create a noise source from an audioWorkletNode, containing many
|
|
17149
|
+
// types of noises
|
|
17150
|
+
this.source = new Tone.ToneAudioNode();
|
|
17151
|
+
this.source.workletNode = Tone.getContext().createAudioWorkletNode('noise-processor');
|
|
17152
|
+
this.source.input = new Tone.Gain();
|
|
17153
|
+
this.source.output = new Tone.Gain(0, 'decibels');
|
|
17154
|
+
this.source.volume = this.source.output.gain;
|
|
17155
|
+
this.source.input.chain(this.source.workletNode, this.source.output);
|
|
17156
|
+
|
|
17157
|
+
this.source.connect(this.channelStrip());
|
|
17158
|
+
|
|
17159
|
+
// empty method to get rid of stop error
|
|
17160
|
+
this.source.stop = () => {};
|
|
17161
|
+
|
|
17162
|
+
// a pink noise source based on a buffer noise
|
|
17163
|
+
// to reduce complex calculation
|
|
17164
|
+
this.pink = new Tone.Noise('pink').connect(this.source);
|
|
17165
|
+
}
|
|
17166
|
+
|
|
17167
|
+
sourceEvent(c, e, time){
|
|
17168
|
+
// set noise type for the generator
|
|
17169
|
+
let t = getParam(this._type, c);
|
|
17170
|
+
if (Object.hasOwn(this._typeMap, t)){
|
|
17171
|
+
t = this._typeMap[t];
|
|
17172
|
+
} else {
|
|
17173
|
+
log(`${t} is not a valid noise type`);
|
|
17174
|
+
// default wave if wave does not exist
|
|
17175
|
+
t = 0;
|
|
17176
|
+
}
|
|
17177
|
+
let type = this.source.workletNode.parameters.get('type');
|
|
17178
|
+
type.setValueAtTime(t, time);
|
|
17179
|
+
|
|
17180
|
+
// set the density amount (only valid for brownian, lofi, dust, crackle)
|
|
17181
|
+
let d = clip(getParam(this._density, c), 0.01, 1);
|
|
17182
|
+
let density = this.source.workletNode.parameters.get('density');
|
|
17183
|
+
density.setValueAtTime(d, time);
|
|
17184
|
+
|
|
17185
|
+
// start the pink noise source also
|
|
17186
|
+
if (!this.started){
|
|
17187
|
+
this.pink.start(time);
|
|
17188
|
+
this.started = true;
|
|
17189
|
+
}
|
|
17190
|
+
}
|
|
17191
|
+
|
|
17192
|
+
density(d){
|
|
17193
|
+
this._density = toArray(d);
|
|
17194
|
+
}
|
|
17195
|
+
|
|
17196
|
+
delete(){
|
|
17197
|
+
// delete super class
|
|
17198
|
+
super.delete();
|
|
17199
|
+
|
|
17200
|
+
this.source.input.disconnect();
|
|
17201
|
+
this.source.input.dispose();
|
|
17202
|
+
this.source.output.disconnect();
|
|
17203
|
+
this.source.output.dispose();
|
|
17204
|
+
this.pink.disconnect();
|
|
17205
|
+
this.pink.dispose();
|
|
17206
|
+
|
|
17207
|
+
console.log('disposed MonoNoise()');
|
|
17208
|
+
}
|
|
17209
|
+
}
|
|
17210
|
+
module.exports = MonoNoise;
|
|
17211
|
+
},{"./Instrument.js":57,"./Util.js":67,"tone":44}],61:[function(require,module,exports){
|
|
16747
17212
|
const Tone = require('tone');
|
|
16748
17213
|
const Util = require('./Util.js');
|
|
16749
17214
|
// const fxMap = require('./Effects.js');
|
|
@@ -16907,7 +17372,7 @@ class MonoSample extends Instrument {
|
|
|
16907
17372
|
}
|
|
16908
17373
|
}
|
|
16909
17374
|
module.exports = MonoSample;
|
|
16910
|
-
},{"./Instrument.js":57,"./Util.js":
|
|
17375
|
+
},{"./Instrument.js":57,"./Util.js":67,"tone":44}],62:[function(require,module,exports){
|
|
16911
17376
|
const Tone = require('tone');
|
|
16912
17377
|
const Util = require('./Util.js');
|
|
16913
17378
|
// const fxMap = require('./Effects.js');
|
|
@@ -17017,22 +17482,17 @@ class MonoSynth extends Instrument {
|
|
|
17017
17482
|
delete(){
|
|
17018
17483
|
// delete super class
|
|
17019
17484
|
super.delete();
|
|
17485
|
+
|
|
17020
17486
|
// 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();
|
|
17487
|
+
this.synth?.stop();
|
|
17488
|
+
this.synth?.disconnect();
|
|
17489
|
+
this.synth?.dispose();
|
|
17030
17490
|
|
|
17031
17491
|
console.log('disposed MonoSynth()', this._wave);
|
|
17032
17492
|
}
|
|
17033
17493
|
}
|
|
17034
17494
|
module.exports = MonoSynth;
|
|
17035
|
-
},{"./Instrument":57,"./Util.js":
|
|
17495
|
+
},{"./Instrument":57,"./Util.js":67,"tone":44,"total-serialism":47}],63:[function(require,module,exports){
|
|
17036
17496
|
const Tone = require('tone');
|
|
17037
17497
|
const Util = require('./Util.js');
|
|
17038
17498
|
const Instrument = require('./Instrument.js');
|
|
@@ -17065,9 +17525,12 @@ class PolyInstrument extends Instrument {
|
|
|
17065
17525
|
channelStrip(){
|
|
17066
17526
|
// gain => output
|
|
17067
17527
|
this.gain = new Tone.Gain(0).toDestination();
|
|
17528
|
+
// postfx-gain => gain (for gain() function in instrument)
|
|
17529
|
+
this.post = new Tone.Gain(1, "gain").connect(this.gain);
|
|
17068
17530
|
// panning => gain
|
|
17069
|
-
this.panner = new Tone.Panner(0).connect(this.
|
|
17531
|
+
this.panner = new Tone.Panner(0).connect(this.post);
|
|
17070
17532
|
// adsr => panning
|
|
17533
|
+
// done through createVoices
|
|
17071
17534
|
}
|
|
17072
17535
|
|
|
17073
17536
|
createVoices(){
|
|
@@ -17114,7 +17577,9 @@ class PolyInstrument extends Instrument {
|
|
|
17114
17577
|
|
|
17115
17578
|
// set all busymaps based on current amplitude value
|
|
17116
17579
|
for (let i=0; i<this.busymap.length; i++){
|
|
17117
|
-
|
|
17580
|
+
// consider the voice busy if amplitude is > -40dB
|
|
17581
|
+
// because of exponential ramp otherwise voice stays busy too long
|
|
17582
|
+
this.busymap[i] = this.adsrs[i].gain.getValueAtTime(time) > 0.01;
|
|
17118
17583
|
if (!this.busymap[i]){
|
|
17119
17584
|
free.push(i);
|
|
17120
17585
|
}
|
|
@@ -17142,26 +17607,28 @@ class PolyInstrument extends Instrument {
|
|
|
17142
17607
|
|
|
17143
17608
|
// set shape for playback (fade-in / out and length)
|
|
17144
17609
|
if (this._att){
|
|
17145
|
-
|
|
17146
|
-
|
|
17147
|
-
|
|
17610
|
+
const att = Math.max(Util.divToS(Util.lookup(this._att, c), this.bpm()), 0.001);
|
|
17611
|
+
const dec = Util.divToS(Util.lookup(this._sus, c), this.bpm());
|
|
17612
|
+
const rel = Math.max(Util.divToS(Util.lookup(this._rel, c), this.bpm()), 0.001);
|
|
17148
17613
|
|
|
17149
|
-
|
|
17150
|
-
|
|
17151
|
-
|
|
17152
|
-
|
|
17153
|
-
|
|
17154
|
-
|
|
17155
|
-
|
|
17156
|
-
|
|
17157
|
-
|
|
17158
|
-
|
|
17159
|
-
this.adsrs[i].
|
|
17614
|
+
// short ramp for retrigger, fades out the envelope over
|
|
17615
|
+
// 2 ms. use the retrigger time to schedule the event
|
|
17616
|
+
// a bit later as well
|
|
17617
|
+
let retrigger = 0;
|
|
17618
|
+
if (this.adsrs[i].gain.getValueAtTime(time) > 0.01){
|
|
17619
|
+
retrigger = 0.002;
|
|
17620
|
+
// short ramp for retrigger, fades out the previous ramp
|
|
17621
|
+
this.adsrs[i].gain.linearRampTo(0.0, retrigger, time);
|
|
17622
|
+
}
|
|
17623
|
+
// trigger the envelope and release after specified time
|
|
17624
|
+
this.adsrs[i].gain.linearRampTo(1.0, att, time + retrigger);
|
|
17625
|
+
this.adsrs[i].gain.exponentialRampTo(0.0, rel * 5, time + att + dec + retrigger);
|
|
17160
17626
|
} else {
|
|
17161
17627
|
// if shape is off only trigger attack
|
|
17162
17628
|
// when voice stealing is 'off' this will lead to all
|
|
17163
17629
|
// voices set to busy!
|
|
17164
|
-
|
|
17630
|
+
// if shape is 'off' turn on the gain of the envelope
|
|
17631
|
+
this.adsrs[i].gain.setValueAtTime(1.0, time);
|
|
17165
17632
|
}
|
|
17166
17633
|
|
|
17167
17634
|
}
|
|
@@ -17212,7 +17679,7 @@ class PolyInstrument extends Instrument {
|
|
|
17212
17679
|
}
|
|
17213
17680
|
}
|
|
17214
17681
|
module.exports = PolyInstrument;
|
|
17215
|
-
},{"./Instrument.js":57,"./Util.js":
|
|
17682
|
+
},{"./Instrument.js":57,"./Util.js":67,"tone":44}],64:[function(require,module,exports){
|
|
17216
17683
|
const Tone = require('tone');
|
|
17217
17684
|
const Util = require('./Util.js');
|
|
17218
17685
|
const PolyInstrument = require('./PolyInstrument.js');
|
|
@@ -17386,7 +17853,7 @@ class PolySample extends PolyInstrument {
|
|
|
17386
17853
|
}
|
|
17387
17854
|
}
|
|
17388
17855
|
module.exports = PolySample;
|
|
17389
|
-
},{"./PolyInstrument.js":
|
|
17856
|
+
},{"./PolyInstrument.js":63,"./Util.js":67,"tone":44}],65:[function(require,module,exports){
|
|
17390
17857
|
const Tone = require('tone');
|
|
17391
17858
|
const Util = require('./Util.js');
|
|
17392
17859
|
const PolyInstrument = require('./PolyInstrument');
|
|
@@ -17451,9 +17918,9 @@ class PolySynth extends PolyInstrument {
|
|
|
17451
17918
|
this.sources[id].frequency.rampTo(f, s, time);
|
|
17452
17919
|
} else {
|
|
17453
17920
|
this.sources[id].frequency.setValueAtTime(f, time);
|
|
17454
|
-
// first time the synth plays it doesn't slide!
|
|
17455
|
-
this._firstSlide[id] = false;
|
|
17456
17921
|
}
|
|
17922
|
+
// first time the synth plays it doesn't slide!
|
|
17923
|
+
this._firstSlide[id] = false;
|
|
17457
17924
|
}
|
|
17458
17925
|
|
|
17459
17926
|
note(i=0, o=0){
|
|
@@ -17492,7 +17959,7 @@ class PolySynth extends PolyInstrument {
|
|
|
17492
17959
|
}
|
|
17493
17960
|
}
|
|
17494
17961
|
module.exports = PolySynth;
|
|
17495
|
-
},{"./PolyInstrument":
|
|
17962
|
+
},{"./PolyInstrument":63,"./Util.js":67,"tone":44}],66:[function(require,module,exports){
|
|
17496
17963
|
const Tone = require('tone');
|
|
17497
17964
|
const Util = require('./Util.js');
|
|
17498
17965
|
// const WebMidi = require("webmidi");
|
|
@@ -17532,12 +17999,26 @@ class Sequencer {
|
|
|
17532
17999
|
return Tone.Transport.bpm.value;
|
|
17533
18000
|
}
|
|
17534
18001
|
|
|
17535
|
-
makeLoop(){
|
|
18002
|
+
makeLoop(stepCount, unnamedCount){
|
|
17536
18003
|
// dispose of previous loop if active
|
|
17537
18004
|
if (this._loop){
|
|
17538
18005
|
this._loop.dispose();
|
|
17539
18006
|
}
|
|
17540
18007
|
|
|
18008
|
+
// transfer the stepcount to count and beatcount if provided
|
|
18009
|
+
if (unnamedCount){
|
|
18010
|
+
this._count = unnamedCount.count;
|
|
18011
|
+
this._beatCount = unnamedCount.beat;
|
|
18012
|
+
}
|
|
18013
|
+
// replace count if a name is given.
|
|
18014
|
+
// this works through giving the instrument the same name
|
|
18015
|
+
if (stepCount){
|
|
18016
|
+
if (stepCount[this._name]){
|
|
18017
|
+
this._count = stepCount[this._name].count;
|
|
18018
|
+
this._beatCount = stepCount[this._name].beat;
|
|
18019
|
+
}
|
|
18020
|
+
}
|
|
18021
|
+
|
|
17541
18022
|
// create the event for a loop or external trigger
|
|
17542
18023
|
this._event = (time) => {
|
|
17543
18024
|
// convert transport time to Ticks and convert reset time to ticks
|
|
@@ -17552,15 +18033,15 @@ class Sequencer {
|
|
|
17552
18033
|
this._beatCount = 0;
|
|
17553
18034
|
}
|
|
17554
18035
|
}
|
|
17555
|
-
|
|
17556
|
-
|
|
18036
|
+
if (this._time !== null){
|
|
18037
|
+
// set subdivision speeds
|
|
18038
|
+
this._loop.playbackRate = Util.getParam(this._subdiv, this._count);
|
|
18039
|
+
// get the subdivision count (always 0, except when subdividing)
|
|
18040
|
+
this._subdivCnt = (this._subdivCnt + 1) % this._loop.playbackRate;
|
|
18041
|
+
// humanize method is interesting to add
|
|
18042
|
+
this._loop.humanize = Util.getParam(this._human, this._count);
|
|
18043
|
+
}
|
|
17557
18044
|
|
|
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
18045
|
// get beat probability for current count
|
|
17565
18046
|
let b = Util.getParam(this._beat, this._count);
|
|
17566
18047
|
|
|
@@ -17586,8 +18067,10 @@ class Sequencer {
|
|
|
17586
18067
|
}, (time - Tone.context.currentTime) * 1000);
|
|
17587
18068
|
}
|
|
17588
18069
|
// also emit an internal event for other instruments to sync to
|
|
17589
|
-
|
|
17590
|
-
|
|
18070
|
+
const event = new CustomEvent(`/${this._name}`, {
|
|
18071
|
+
detail: { value: 1, time: time }
|
|
18072
|
+
});
|
|
18073
|
+
window.dispatchEvent(event);
|
|
17591
18074
|
|
|
17592
18075
|
// execute a visual event for Hydra
|
|
17593
18076
|
if (this._visual.length > 0){
|
|
@@ -17623,16 +18106,17 @@ class Sequencer {
|
|
|
17623
18106
|
this._event(time)
|
|
17624
18107
|
}, this._time).start(schedule);
|
|
17625
18108
|
}
|
|
17626
|
-
|
|
17627
|
-
|
|
17628
|
-
|
|
17629
|
-
|
|
17630
|
-
|
|
17631
|
-
|
|
17632
|
-
|
|
17633
|
-
|
|
17634
|
-
|
|
17635
|
-
|
|
18109
|
+
else {
|
|
18110
|
+
// generate a listener for the osc-address and store for removal
|
|
18111
|
+
this._oscAddress = `${this._offset}`;
|
|
18112
|
+
this._listener = (event) => {
|
|
18113
|
+
// trigger the event if value greater than 0
|
|
18114
|
+
if (event.detail.value > 0){
|
|
18115
|
+
this._event(event.detail.time);
|
|
18116
|
+
}
|
|
18117
|
+
}
|
|
18118
|
+
window.addEventListener(this._oscAddress, this._listener);
|
|
18119
|
+
}
|
|
17636
18120
|
}
|
|
17637
18121
|
|
|
17638
18122
|
event(c, time){
|
|
@@ -17656,18 +18140,23 @@ class Sequencer {
|
|
|
17656
18140
|
|
|
17657
18141
|
delete(){
|
|
17658
18142
|
// dispose loop
|
|
17659
|
-
this._loop
|
|
18143
|
+
this._loop?.stop();
|
|
18144
|
+
this._loop?.dispose();
|
|
18145
|
+
// remove the listenere if one was created
|
|
18146
|
+
if (this._oscAddress){
|
|
18147
|
+
window.removeEventListener(this._oscAddress, this._listener)
|
|
18148
|
+
}
|
|
17660
18149
|
console.log('=> disposed Sequencer()');
|
|
17661
18150
|
}
|
|
17662
18151
|
|
|
17663
18152
|
start(){
|
|
17664
18153
|
// restart at offset
|
|
17665
|
-
this._loop
|
|
18154
|
+
this._loop?.start(this._offset);
|
|
17666
18155
|
}
|
|
17667
18156
|
|
|
17668
18157
|
stop(){
|
|
17669
18158
|
// stop sequencer
|
|
17670
|
-
this._loop
|
|
18159
|
+
this._loop?.stop();
|
|
17671
18160
|
}
|
|
17672
18161
|
|
|
17673
18162
|
time(t, o=0){
|
|
@@ -17744,7 +18233,7 @@ class Sequencer {
|
|
|
17744
18233
|
}
|
|
17745
18234
|
}
|
|
17746
18235
|
module.exports = Sequencer;
|
|
17747
|
-
},{"./Util.js":
|
|
18236
|
+
},{"./Util.js":67,"tone":44}],67:[function(require,module,exports){
|
|
17748
18237
|
const Tone = require('tone');
|
|
17749
18238
|
const { noteToMidi, toScale, mtof } = require('total-serialism').Translate;
|
|
17750
18239
|
|
|
@@ -17776,6 +18265,12 @@ function clip(v, l=0, h=1){
|
|
|
17776
18265
|
return Math.max(l, Math.min(h, v));
|
|
17777
18266
|
}
|
|
17778
18267
|
|
|
18268
|
+
// scale values between an input and output range with exponent
|
|
18269
|
+
function remap(val=0, inLo=0, inHi=1, outLo=0, outHi=1, exp=1){
|
|
18270
|
+
let temp = ((val - inLo) / (inHi - inLo)) ** exp;
|
|
18271
|
+
return temp * (outHi - outLo) + outLo;
|
|
18272
|
+
}
|
|
18273
|
+
|
|
17779
18274
|
// make sure the output is a number, else output a default value
|
|
17780
18275
|
function assureNum(v, d=1){
|
|
17781
18276
|
return isNaN(v) ? d : v;
|
|
@@ -17969,8 +18464,8 @@ function log(msg){
|
|
|
17969
18464
|
}
|
|
17970
18465
|
}
|
|
17971
18466
|
|
|
17972
|
-
module.exports = { mapDefaults, atTime, atodb, dbtoa, clip, assureNum, lookup, randLookup, isRandom, getParam, toArray, msToS, formatRatio, divToS, divToF, toMidi, mtof, noteToMidi, noteToFreq, assureWave, log }
|
|
17973
|
-
},{"tone":44,"total-serialism":47}],
|
|
18467
|
+
module.exports = { mapDefaults, atTime, atodb, dbtoa, clip, remap,assureNum, lookup, randLookup, isRandom, getParam, toArray, msToS, formatRatio, divToS, divToF, toMidi, mtof, noteToMidi, noteToFreq, assureWave, log }
|
|
18468
|
+
},{"tone":44,"total-serialism":47}],68:[function(require,module,exports){
|
|
17974
18469
|
module.exports={
|
|
17975
18470
|
"uptempo" : 10,
|
|
17976
18471
|
"downtempo" : 10,
|
|
@@ -17991,9 +18486,9 @@ module.exports={
|
|
|
17991
18486
|
"dnb" : 170,
|
|
17992
18487
|
"neurofunk" : 180
|
|
17993
18488
|
}
|
|
17994
|
-
},{}],
|
|
18489
|
+
},{}],69:[function(require,module,exports){
|
|
17995
18490
|
|
|
17996
|
-
const Tone = require('tone');
|
|
18491
|
+
// const Tone = require('tone');
|
|
17997
18492
|
const Mercury = require('mercury-lang');
|
|
17998
18493
|
const TL = require('total-serialism').Translate;
|
|
17999
18494
|
const { normalize, multiply } = require('total-serialism').Utility;
|
|
@@ -18001,6 +18496,7 @@ const { normalize, multiply } = require('total-serialism').Utility;
|
|
|
18001
18496
|
const MonoSample = require('./core/MonoSample.js');
|
|
18002
18497
|
const MonoMidi = require('./core/MonoMidi.js');
|
|
18003
18498
|
const MonoSynth = require('./core/MonoSynth.js');
|
|
18499
|
+
const MonoNoise = require('./core/MonoNoise.js');
|
|
18004
18500
|
const MonoInput = require('./core/MonoInput.js');
|
|
18005
18501
|
const PolySynth = require('./core/PolySynth.js');
|
|
18006
18502
|
const PolySample = require('./core/PolySample.js');
|
|
@@ -18065,10 +18561,11 @@ class MercuryInterpreter {
|
|
|
18065
18561
|
});
|
|
18066
18562
|
}
|
|
18067
18563
|
|
|
18068
|
-
removeSounds(s, f=0) {
|
|
18069
|
-
// fade out and delete after fade
|
|
18564
|
+
removeSounds(s, f=0, im=false) {
|
|
18565
|
+
// fade out and delete after fade. second parameter sets
|
|
18566
|
+
// immediate fade-out, otherwise wait till trigger
|
|
18070
18567
|
s.map((_s) => {
|
|
18071
|
-
if (_s){ _s.fadeOut(f); }
|
|
18568
|
+
if (_s){ _s.fadeOut(f, im); }
|
|
18072
18569
|
});
|
|
18073
18570
|
// empty array to trigger garbage collection
|
|
18074
18571
|
s.length = 0;
|
|
@@ -18082,11 +18579,16 @@ class MercuryInterpreter {
|
|
|
18082
18579
|
}
|
|
18083
18580
|
|
|
18084
18581
|
setCrossFade(f){
|
|
18085
|
-
// set the crossFade in milliseconds
|
|
18086
|
-
// set crossFade time in ms
|
|
18582
|
+
// set the crossFade time in milliseconds
|
|
18087
18583
|
this.crossFade = divToS(f, this.getBPM());
|
|
18088
18584
|
// this.crossFade = Number(f) / 1000;
|
|
18089
|
-
Util.log(`
|
|
18585
|
+
Util.log(`crossFade is deprecated, setting fadeOut time to ${this.crossFade}ms`);
|
|
18586
|
+
}
|
|
18587
|
+
|
|
18588
|
+
setFadeOut(f){
|
|
18589
|
+
// set the fadeOut time in milliseconds
|
|
18590
|
+
this.crossFade = divToS(f, this.getBPM());
|
|
18591
|
+
Util.log(`setting fadeOut time to ${this.crossFade}`);
|
|
18090
18592
|
}
|
|
18091
18593
|
|
|
18092
18594
|
getCode(){
|
|
@@ -18145,6 +18647,10 @@ class MercuryInterpreter {
|
|
|
18145
18647
|
// set crossFade time in ms
|
|
18146
18648
|
this.setCrossFade(args[0]);
|
|
18147
18649
|
},
|
|
18650
|
+
'fadeOut' : (args) => {
|
|
18651
|
+
// set fadeOut time in ms
|
|
18652
|
+
this.setFadeOut(args[0]);
|
|
18653
|
+
},
|
|
18148
18654
|
'tempo' : (args) => {
|
|
18149
18655
|
let t = args[0];
|
|
18150
18656
|
if (isNaN(t)){
|
|
@@ -18226,6 +18732,11 @@ class MercuryInterpreter {
|
|
|
18226
18732
|
objectMap.applyFunctions(obj.functions, inst, obj.type);
|
|
18227
18733
|
return inst;
|
|
18228
18734
|
},
|
|
18735
|
+
'noise' : (obj) => {
|
|
18736
|
+
let inst = new MonoNoise(this, obj.type, this.canvas);
|
|
18737
|
+
objectMap.applyFunctions(obj.functions, inst, obj.type);
|
|
18738
|
+
return inst;
|
|
18739
|
+
},
|
|
18229
18740
|
'polySynth' : (obj) => {
|
|
18230
18741
|
let inst = new PolySynth(this, obj.type, this.canvas);
|
|
18231
18742
|
objectMap.applyFunctions(obj.functions, inst, obj.type);
|
|
@@ -18276,7 +18787,8 @@ class MercuryInterpreter {
|
|
|
18276
18787
|
}
|
|
18277
18788
|
|
|
18278
18789
|
// copy current sounds over to past
|
|
18279
|
-
this._sounds = this.sounds.slice();
|
|
18790
|
+
// this._sounds = this.sounds.slice();
|
|
18791
|
+
this._sounds = [ ...this.sounds ];
|
|
18280
18792
|
// empty new sounds array
|
|
18281
18793
|
this.sounds = [];
|
|
18282
18794
|
|
|
@@ -18290,16 +18802,30 @@ class MercuryInterpreter {
|
|
|
18290
18802
|
}
|
|
18291
18803
|
}
|
|
18292
18804
|
|
|
18805
|
+
// get all the current counts and store in dict
|
|
18806
|
+
let countTransfer = {};
|
|
18807
|
+
this._sounds.map((s) => {
|
|
18808
|
+
countTransfer[s._name] = {
|
|
18809
|
+
count: s._count,
|
|
18810
|
+
beat: s._beatCount
|
|
18811
|
+
}
|
|
18812
|
+
});
|
|
18813
|
+
|
|
18814
|
+
// create new loops, transfer the counts
|
|
18815
|
+
for (let s = 0; s < this.sounds.length; s++){
|
|
18816
|
+
this.sounds[s].makeLoop(countTransfer, Object.values(countTransfer)[s]);
|
|
18817
|
+
}
|
|
18818
|
+
|
|
18293
18819
|
// start new loops;
|
|
18294
|
-
this.makeLoops(this.sounds);
|
|
18295
|
-
this.transferCounts(this._sounds, this.sounds);
|
|
18820
|
+
// this.makeLoops(this.sounds);
|
|
18821
|
+
// this.transferCounts(this._sounds, this.sounds);
|
|
18296
18822
|
|
|
18297
18823
|
// 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);
|
|
18824
|
+
// if (!this.sounds.length){
|
|
18825
|
+
// this.startSounds(this.sounds);
|
|
18826
|
+
// }
|
|
18302
18827
|
this.removeSounds(this._sounds, this.crossFade);
|
|
18828
|
+
this.startSounds(this.sounds);
|
|
18303
18829
|
|
|
18304
18830
|
this.resume();
|
|
18305
18831
|
|
|
@@ -18324,10 +18850,10 @@ class MercuryInterpreter {
|
|
|
18324
18850
|
}
|
|
18325
18851
|
}
|
|
18326
18852
|
module.exports = { MercuryInterpreter }
|
|
18327
|
-
},{"./core/MonoInput.js":58,"./core/MonoMidi.js":59,"./core/
|
|
18853
|
+
},{"./core/MonoInput.js":58,"./core/MonoMidi.js":59,"./core/MonoNoise.js":60,"./core/MonoSample.js":61,"./core/MonoSynth.js":62,"./core/PolySample.js":64,"./core/PolySynth.js":65,"./core/Util.js":67,"./data/genre-tempos.json":68,"mercury-lang":27,"total-serialism":47}],70:[function(require,module,exports){
|
|
18328
18854
|
|
|
18329
18855
|
console.log(`
|
|
18330
|
-
Mercury Engine by Timo Hoogland (c)
|
|
18856
|
+
Mercury Engine by Timo Hoogland (c) 2018-2025
|
|
18331
18857
|
more info:
|
|
18332
18858
|
https://www.timohoogland.com
|
|
18333
18859
|
https://github.com/tmhglnd/mercury-playground
|
|
@@ -18343,7 +18869,7 @@ const { WebMidi } = require("webmidi");
|
|
|
18343
18869
|
// load extra AudioWorkletProcessors from file
|
|
18344
18870
|
// transformed to inline with browserify brfs
|
|
18345
18871
|
|
|
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);";
|
|
18872
|
+
const fxExtensions = "\n// Various noise type processors for the MonoNoise source\n// Type 2 is Pink noise, used from Tone.Noise('pink') instead of calc\n//\nclass NoiseProcessor extends AudioWorkletProcessor {\n\tstatic get parameterDescriptors(){\n\t\treturn [{\n\t\t\tname: 'type',\n\t\t\tdefaultValue: 5,\n\t\t\tminValue: 0,\n\t\t\tmaxValue: 5\n\t\t},{\n\t\t\tname: 'density',\n\t\t\tdefaultValue: 0.125,\n\t\t\tminValue: 0,\n\t\t\tmaxValue: 1\n\t\t}];\n\t}\n\t\n\tconstructor(){\n\t\tsuper();\n\t\t// sample previous value\n\t\tthis.prev = 0;\n\t\t// latch to a sample \n\t\tthis.latch = 0;\n\t\t// phasor ramp\n\t\tthis.phasor = 0;\n\t\tthis.delta = 0;\n\t}\n\n\tprocess(inputs, outputs, parameters){\n\t\t// input is not used because this is a source\n\t\tconst input = inputs[0];\n\t\tconst output = outputs[0];\n\t\tconst HALF_PI = Math.PI/2;\n\n\t\t// for one output channel generate some noise\t\n\t\tif (input.length > 0){\n\t\t\tfor (let i = 0; i < input[0].length; i++){\n\t\t\t\tconst t = (parameters.type.length > 1) ? parameters.type[i] : parameters.type[0];\n\t\t\t\tconst d = (parameters.density.length > 1) ? parameters.density[i] : parameters.density[0];\n\t\t\t\n\t\t\t\t// some bipolar white noise -1 to 1\n\t\t\t\tconst biNoise = Math.random() * 2 - 1;\n\t\t\t\t// empty output\n\t\t\t\tlet out = 0;\n\n\t\t\t\t// White noise, Use for every other choice\n\t\t\t\tif (t < 1){\n\t\t\t\t\tout = biNoise * 0.707;\n\t\t\t\t}\n\t\t\t\t// Pink noise, use Tone.Noise('pink') object for simplicity\n\t\t\t\telse if (t < 2){\n\t\t\t\t\tout = input[0][i] * 1.413;\n\t\t\t\t}\n\t\t\t\t// Brownian noise\n\t\t\t\t// calculate a random next value in \"step size\" and add to \n\t\t\t\t// the previous noise signal value creating a \"drunk walk\" \n\t\t\t\t// or brownian motion\n\t\t\t\telse if (t < 3){\t\t\n\t\t\t\t\tthis.prev += biNoise * d*d;\n\t\t\t\t\tthis.prev = Math.asin(Math.sin(this.prev * HALF_PI)) / HALF_PI;\n\t\t\t\t\tout = this.prev * 0.707;\n\t\t\t\t}\n\t\t\t\t// Lo-Fi (sampled) noise\n\t\t\t\t// creates random values at a specified frequency and slowly \n\t\t\t\t// ramps to that new value\n\t\t\t\telse if (t < 4){\n\t\t\t\t\t// create a ramp from 0-1 at specific frequency/density\n\t\t\t\t\tthis.phasor = (this.phasor + d * d * 0.5) % 1;\n\t\t\t\t\t// calculate the delta\n\t\t\t\t\tlet dlt = this.phasor - this.delta;\n\t\t\t\t\tthis.delta = this.phasor;\n\t\t\t\t\t// when ramp resets, latch a new noise value\n\t\t\t\t\tif (dlt < 0){\n\t\t\t\t\t\tthis.prev = this.latch;\n\t\t\t\t\t\tthis.latch = biNoise;\n\t\t\t\t\t}\n\t\t\t\t\t// linear interpolation from previous to next point\n\t\t\t\t\tout = this.prev + this.phasor * (this.latch - this.prev);\n\t\t\t\t\tout *= 0.707;\n\t\t\t\t}\n\t\t\t\t// Dust noise\n\t\t\t\t// randomly generate an impulse/click of value 1 depending \n\t\t\t\t// on the density, average amount of impulses per second\n\t\t\t\telse if (t < 5){\n\t\t\t\t\tout = Math.random() > (1 - d*d*d * 0.5);\n\t\t\t\t}\n\t\t\t\t// Crackle noise\n\t\t\t\t// Pink generator with \"wave-loss\" leaving gaps\n\t\t\t\telse {\n\t\t\t\t\tlet delta = input[0][i] - this.prev;\n\t\t\t\t\tthis.prev = input[0][i];\n\t\t\t\t\tif (delta > 0){\n\t\t\t\t\t\tthis.latch = Math.random();\n\t\t\t\t\t}\n\t\t\t\t\tout = (this.latch < (1 - d*d*d)) ? 0 : input[0][i] * 1.413;\n\t\t\t\t}\n\t\t\t\t// send to output whichever noise type was chosen\n\t\t\t\toutput[0][i] = out;\n\t\t\t}\n\t\t}\t\t\n\t\treturn true;\n\t}\n}\nregisterProcessor('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 algorithm using the arctan function as a \n// waveshaping technique. Some mapping to apply a more equal loudness \n// distortion is applied on the overdrive parameter\n//\nclass ArctanDistortionProcessor extends AudioWorkletProcessor {\n\tstatic get parameterDescriptors(){\n\t\treturn [{\n\t\t\tname: 'amount',\n\t\t\tdefaultValue: 5,\n\t\t\tminValue: 1\n\t\t}]\n\t}\n\n\tconstructor(){\n\t\tsuper();\n\n\t\t// quarter pi constant and inverse\n\t\tthis.Q_PI = 0.7853981633974483; // 0.25 * Math.PI;\n\t\tthis.INVQ_PI = 1.2732395447351628; //1.0 / this.Q_PI;\n\t}\n\n\tprocess(inputs, outputs, parameters){\n\t\tconst input = inputs[0];\n\t\tconst output = outputs[0];\n\n\t\tconst gain = parameters.amount[0];\n\t\tconst makeup = Math.min(1, Math.max(0, 1 - ((Math.atan(gain) - this.Q_PI) * this.INVQ_PI * 0.823)));\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\toutput[channel][i] = Math.atan(input[channel][i] * gain) * makeup;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n}\nregisterProcessor('arctan-distortion-processor', ArctanDistortionProcessor);\n\n\n// A fuzz distortion effect in modelled after the Big Muff Pi pedal \n// by Electro Harmonics. Using three stages of distortion: \n// 1 soft-clipping stage, 2 half-wave rectifier, 3 hard-clipping stage\n// Based on: https://github.com/hazza-music/EHX-Big-Muff-Pi-Emulation/blob/main/Technical%20Essay.pdf\n// \nclass FuzzProcessor extends AudioWorkletProcessor {\n\tstatic get parameterDescriptors() {\n\t\treturn [{\n\t\t\tname: 'amount',\n\t\t\tdefaultValue: 5,\n\t\t\tminValue: 1\n\t\t}]\n\t}\n\n\tconstructor(){ \n\t\tsuper(); \n\t\t// history for onepole filter for dcblocking\n\t\tthis.history = [0, 0];\n\t}\n\n\tprocess(inputs, outputs, parameters){\n\t\tconst input = inputs[0];\n\t\tconst output = outputs[0];\n\n\t\tconst gain = parameters.amount[0];\n\t\tconst makeup = Math.max((1 - Math.pow((gain-1) / 63, 0.13)) * 0.395 + 0.605, 0.605);\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\t// soft-clipping\n\t\t\t\t\tconst sc = Math.atan(input[channel][i] * gain * 2) * 0.6;\n\t\t\t\t\t// half-wave rectification and add for \n\t\t\t\t\t// asymmetric distortion\n\t\t\t\t\tconst hw = ((sc > 0) ? sc : 0) + input[channel][i];\n\t\t\t\t\t// hard-clipping\n\t\t\t\t\tconst hc = Math.max(-0.707, Math.min(0.707, hw));\n\t\t\t\t\t// onepole lowpass filter for dc-block\n\t\t\t\t\tthis.history[channel] = (hc - this.history[channel]) * 0.0015 + this.history[channel];\n\t\t\t\t\t// dc-block and gain compensation and output\n\t\t\t\t\toutput[channel][i] = (hc - this.history[channel]) * makeup;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n}\nregisterProcessor('fuzz-processor', FuzzProcessor);\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
18873
|
Tone.getContext().addAudioWorkletModule(URL.createObjectURL(new Blob([ fxExtensions ], { type: 'text/javascript' })));
|
|
18348
18874
|
|
|
18349
18875
|
// Mercury main class controls Tone and loads samples
|
|
@@ -18608,7 +19134,7 @@ class Mercury extends MercuryInterpreter {
|
|
|
18608
19134
|
|
|
18609
19135
|
// set highpass frequency cutoff and ramptime
|
|
18610
19136
|
setHighPass(f, t=0){
|
|
18611
|
-
this.highPass = (f === 'default')?
|
|
19137
|
+
this.highPass = (f === 'default')? 20 : f;
|
|
18612
19138
|
t = Util.divToS(t, this.bpm);
|
|
18613
19139
|
if (t > 0){
|
|
18614
19140
|
this.highPassF.frequency.rampTo(this.highPass, t, Tone.now());
|
|
@@ -18684,5 +19210,5 @@ class Mercury extends MercuryInterpreter {
|
|
|
18684
19210
|
// }
|
|
18685
19211
|
}
|
|
18686
19212
|
module.exports = { Mercury };
|
|
18687
|
-
},{"./core/Util.js":
|
|
19213
|
+
},{"./core/Util.js":67,"./interpreter":69,"tone":44,"webmidi":55}]},{},[70])(70)
|
|
18688
19214
|
});
|