mercury-engine 1.3.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mercury.js CHANGED
@@ -3592,7 +3592,7 @@
3592
3592
  'use strict';
3593
3593
 
3594
3594
  /*
3595
- * bignumber.js v9.3.0
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 emptyDefault = {
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(emptyDefault.empty));
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, emptyDefault);
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
- return el;
7578
- },
7579
- '@number' : (el) => {
7580
- return el;
7581
- },
7582
- '@division' : (el) => {
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 = Math.abs(r) / len;
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 (r < 0)? arr.reverse() : arr;
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) * Math.abs(r) + lo;
11061
+ arr[i] = Math.pow((i / len), exp) * r + lo;
11045
11062
  }
11046
- return (r < 0)? arr.reverse() : arr;
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
- rng = seedrandom();
12017
- _seed = 0;
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 getSeed();
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,"./transform.js":52,"./utility":54,"seedrandom":36}],51:[function(require,module,exports){
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
- let arr = a.slice().sort((a,b) => { return a-b; });
12693
-
12694
- let amount = 1;
12695
- let streak = 0;
12696
- let modes = [];
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
- amount++;
12703
- if (amount > streak){
12704
- streak = amount;
12705
- modes = [arr[i]];
12706
- } else if (amount == streak){
12707
- modes.push(arr[i]);
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
- return modes;
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
- // Alternate through 2 or multiple lists consecutively
13330
- // Gives a similar result as lace except the output
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 step(...arrs){
13389
+ function stepCombine(...arrs){
13338
13390
  if (!arrs.length){ return [ 0 ] }
13339
13391
  return flat(arrayCombinations(...arrs), 1);
13340
13392
  }
13341
- exports.step = step;
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 multiCall(func, ...a){
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.multiCall = multiCall;
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 denominator of the input lists
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, but remove duplicate lengths
14649
- let sizes = unique(arrs.map(a => a.length));
14650
- // multiply to get total of possible iterations
14651
- let iters = 1;
14652
- sizes.forEach((l) => iters *= l);
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.6
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 June 4th, 2023.
14988
+ * Build generated on October 11th, 2025.
14869
14989
  *
14870
- * © Copyright 2015-2023, Jean-Philippe Côté.
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&&navigator.requestMIDIAccess}get sysexEnabled(){return!(!this.interface||!this.interface.sysexEnabled)}get time(){return performance.now()}get version(){return"3.1.6"}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;
15448
+ */class WebMidi extends EventEmitter{constructor(){super(),this.defaults={note:{attack:Utilities.from7bitToFloat(64),release:Utilities.from7bitToFloat(64),duration:1/0}},this.interface=null,this.validation=!0,this._inputs=[],this._disconnectedInputs=[],this._outputs=[],this._disconnectedOutputs=[],this._stateChangeQueue=[],this._octaveOffset=0}async enable(e={},t=!1){if(this.validation=!1!==e.validation,this.validation&&("function"==typeof e&&(e={callback:e,sysex:t}),t&&(e.sysex=!0)),this.enabled)return"function"==typeof e.callback&&e.callback(),Promise.resolve();const n={timestamp:this.time,target:this,type:"error",error:void 0},r={timestamp:this.time,target:this,type:"midiaccessgranted"},i={timestamp:this.time,target:this,type:"enabled"};try{"function"==typeof e.requestMIDIAccessFunction?this.interface=await e.requestMIDIAccessFunction({sysex:e.sysex,software:e.software}):this.interface=await navigator.requestMIDIAccess({sysex:e.sysex,software:e.software})}catch(t){return n.error=t,this.emit("error",n),"function"==typeof e.callback&&e.callback(t),Promise.reject(t)}this.emit("midiaccessgranted",r),this.interface.onstatechange=this._onInterfaceStateChange.bind(this);try{await this._updateInputsAndOutputs()}catch(t){return n.error=t,this.emit("error",n),"function"==typeof e.callback&&e.callback(t),Promise.reject(t)}return this.emit("enabled",i),"function"==typeof e.callback&&e.callback(),Promise.resolve(this)}async disable(){return this.interface&&(this.interface.onstatechange=void 0),this._destroyInputsAndOutputs().then(()=>{navigator&&"function"==typeof navigator.close&&navigator.close(),this.interface=null;let e={timestamp:this.time,target:this,type:"disabled"};this.emit("disabled",e),this.removeListener()})}getInputById(e,t={disconnected:!1}){if(this.validation){if(!this.enabled)throw new Error("WebMidi is not enabled.");if(!e)return}if(t.disconnected){for(let t=0;t<this._disconnectedInputs.length;t++)if(this._disconnectedInputs[t]._midiInput&&this._disconnectedInputs[t].id===e.toString())return this._disconnectedInputs[t]}else for(let t=0;t<this.inputs.length;t++)if(this.inputs[t]._midiInput&&this.inputs[t].id===e.toString())return this.inputs[t]}getInputByName(e,t={disconnected:!1}){if(this.validation){if(!this.enabled)throw new Error("WebMidi is not enabled.");if(!e)return;e=e.toString()}if(t.disconnected){for(let t=0;t<this._disconnectedInputs.length;t++)if(~this._disconnectedInputs[t].name.indexOf(e))return this._disconnectedInputs[t]}else for(let t=0;t<this.inputs.length;t++)if(~this.inputs[t].name.indexOf(e))return this.inputs[t]}getOutputByName(e,t={disconnected:!1}){if(this.validation){if(!this.enabled)throw new Error("WebMidi is not enabled.");if(!e)return;e=e.toString()}if(t.disconnected){for(let t=0;t<this._disconnectedOutputs.length;t++)if(~this._disconnectedOutputs[t].name.indexOf(e))return this._disconnectedOutputs[t]}else for(let t=0;t<this.outputs.length;t++)if(~this.outputs[t].name.indexOf(e))return this.outputs[t]}getOutputById(e,t={disconnected:!1}){if(this.validation){if(!this.enabled)throw new Error("WebMidi is not enabled.");if(!e)return}if(t.disconnected){for(let t=0;t<this._disconnectedOutputs.length;t++)if(this._disconnectedOutputs[t]._midiOutput&&this._disconnectedOutputs[t].id===e.toString())return this._disconnectedOutputs[t]}else for(let t=0;t<this.outputs.length;t++)if(this.outputs[t]._midiOutput&&this.outputs[t].id===e.toString())return this.outputs[t]}noteNameToNumber(e){return this.validation&&console.warn("The noteNameToNumber() method is deprecated. Use Utilities.toNoteNumber() instead."),Utilities.toNoteNumber(e,this.octaveOffset)}getOctave(e){return this.validation&&(console.warn("The getOctave()is deprecated. Use Utilities.getNoteDetails() instead"),e=parseInt(e)),!isNaN(e)&&e>=0&&e<=127&&Utilities.getNoteDetails(Utilities.offsetNumber(e,this.octaveOffset)).octave}sanitizeChannels(e){return this.validation&&console.warn("The sanitizeChannels() method has been moved to the utilities class."),Utilities.sanitizeChannels(e)}toMIDIChannels(e){return this.validation&&console.warn("The toMIDIChannels() method has been deprecated. Use Utilities.sanitizeChannels() instead."),Utilities.sanitizeChannels(e)}guessNoteNumber(e){return this.validation&&console.warn("The guessNoteNumber() method has been deprecated. Use Utilities.guessNoteNumber() instead."),Utilities.guessNoteNumber(e,this.octaveOffset)}getValidNoteArray(e,t={}){return this.validation&&console.warn("The getValidNoteArray() method has been moved to the Utilities.buildNoteArray()"),Utilities.buildNoteArray(e,t)}convertToTimestamp(e){return this.validation&&console.warn("The convertToTimestamp() method has been moved to Utilities.toTimestamp()."),Utilities.toTimestamp(e)}async _destroyInputsAndOutputs(){let e=[];return this.inputs.forEach(t=>e.push(t.destroy())),this.outputs.forEach(t=>e.push(t.destroy())),Promise.all(e).then(()=>{this._inputs=[],this._outputs=[]})}_onInterfaceStateChange(e){this._updateInputsAndOutputs();let t={timestamp:e.timeStamp,type:e.port.state,target:this};if("connected"===e.port.state&&"open"===e.port.connection){"output"===e.port.type?t.port=this.getOutputById(e.port.id):"input"===e.port.type&&(t.port=this.getInputById(e.port.id)),this.emit(e.port.state,t);const n=Object.assign({},t);n.type="portschanged",this.emit(n.type,n)}else if("disconnected"===e.port.state&&"pending"===e.port.connection){"input"===e.port.type?t.port=this.getInputById(e.port.id,{disconnected:!0}):"output"===e.port.type&&(t.port=this.getOutputById(e.port.id,{disconnected:!0})),this.emit(e.port.state,t);const n=Object.assign({},t);n.type="portschanged",this.emit(n.type,n)}}async _updateInputsAndOutputs(){return Promise.all([this._updateInputs(),this._updateOutputs()])}async _updateInputs(){if(!this.interface)return;for(let e=this._inputs.length-1;e>=0;e--){const t=this._inputs[e];Array.from(this.interface.inputs.values()).find(e=>e===t._midiInput)||(this._disconnectedInputs.push(t),this._inputs.splice(e,1))}let e=[];return this.interface.inputs.forEach(t=>{if(!this._inputs.find(e=>e._midiInput===t)){let n=this._disconnectedInputs.find(e=>e._midiInput===t);n||(n=new Input(t)),this._inputs.push(n),e.push(n.open())}}),Promise.all(e)}async _updateOutputs(){if(!this.interface)return;for(let e=this._outputs.length-1;e>=0;e--){const t=this._outputs[e];Array.from(this.interface.outputs.values()).find(e=>e===t._midiOutput)||(this._disconnectedOutputs.push(t),this._outputs.splice(e,1))}let e=[];return this.interface.outputs.forEach(t=>{if(!this._outputs.find(e=>e._midiOutput===t)){let n=this._disconnectedOutputs.find(e=>e._midiOutput===t);n||(n=new Output(t)),this._outputs.push(n),e.push(n.open())}}),Promise.all(e)}get enabled(){return null!==this.interface}get inputs(){return this._inputs}get isNode(){return this.validation&&console.warn("WebMidi.isNode has been deprecated. Use Utilities.isNode instead."),Utilities.isNode}get isBrowser(){return this.validation&&console.warn("WebMidi.isBrowser has been deprecated. Use Utilities.isBrowser instead."),Utilities.isBrowser}get octaveOffset(){return this._octaveOffset}set octaveOffset(e){if(this.validation&&(e=parseInt(e),isNaN(e)))throw new TypeError("The 'octaveOffset' property must be an integer.");this._octaveOffset=e}get outputs(){return this._outputs}get supported(){return"undefined"!=typeof navigator&&!!navigator.requestMIDIAccess}get sysexEnabled(){return!(!this.interface||!this.interface.sysexEnabled)}get time(){return performance.now()}get version(){return"3.1.14"}get flavour(){return"cjs"}get CHANNEL_EVENTS(){return this.validation&&console.warn("The CHANNEL_EVENTS enum has been moved to Enumerations.CHANNEL_EVENTS."),Enumerations.CHANNEL_EVENTS}get MIDI_SYSTEM_MESSAGES(){return this.validation&&console.warn("The MIDI_SYSTEM_MESSAGES enum has been moved to Enumerations.SYSTEM_MESSAGES."),Enumerations.SYSTEM_MESSAGES}get MIDI_CHANNEL_MODE_MESSAGES(){return this.validation&&console.warn("The MIDI_CHANNEL_MODE_MESSAGES enum has been moved to Enumerations.CHANNEL_MODE_MESSAGES."),Enumerations.CHANNEL_MODE_MESSAGES}get MIDI_CONTROL_CHANGE_MESSAGES(){return this.validation&&console.warn("The MIDI_CONTROL_CHANGE_MESSAGES enum has been replaced by the Enumerations.CONTROL_CHANGE_MESSAGES array."),Enumerations.MIDI_CONTROL_CHANGE_MESSAGES}get MIDI_REGISTERED_PARAMETER(){return this.validation&&console.warn("The MIDI_REGISTERED_PARAMETER enum has been moved to Enumerations.REGISTERED_PARAMETERS."),Enumerations.REGISTERED_PARAMETERS}get NOTES(){return this.validation&&console.warn("The NOTES enum has been deprecated."),["C","C#","D","D#","E","F","F#","G","G#","A","A#","B"]}}const wm=new WebMidi;wm.constructor=null,exports.Enumerations=Enumerations,exports.Forwarder=Forwarder,exports.Input=Input,exports.InputChannel=InputChannel,exports.Message=Message,exports.Note=Note,exports.Output=Output,exports.OutputChannel=OutputChannel,exports.Utilities=Utilities,exports.WebMidi=wm;
15329
15449
 
15330
15450
 
15331
15451
  }).call(this)}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
@@ -15372,14 +15492,17 @@ const fxMap = {
15372
15492
  'degrade' : (params) => {
15373
15493
  return new DownSampler(params);
15374
15494
  },
15375
- 'room' : (params) => {
15495
+ 'converb' : (params) => {
15376
15496
  return new Reverb(params);
15377
15497
  },
15498
+ 'room' : (params) => {
15499
+ return new DattorroReverb(params);
15500
+ },
15378
15501
  'hall' : (params) => {
15379
- return new Reverb(params);
15502
+ return new DattorroReverb(params);
15380
15503
  },
15381
15504
  'reverb' : (params) => {
15382
- return new Reverb(params);
15505
+ return new DattorroReverb(params);
15383
15506
  },
15384
15507
  'shift' : (params) => {
15385
15508
  return new PitchShift(params);
@@ -15422,10 +15545,121 @@ const fxMap = {
15422
15545
  },
15423
15546
  'double' : (params) => {
15424
15547
  return new Chorus(Util.mapDefaults(params, ['8/1', 8, 1]));
15548
+ },
15549
+ 'vowel' : (params) => {
15550
+ return new FormantFilter(params);
15551
+ },
15552
+ 'formant' : (params) => {
15553
+ return new FormantFilter(params);
15554
+ },
15555
+ 'speak' : (params) => {
15556
+ return new FormantFilter(params);
15425
15557
  }
15426
15558
  }
15427
15559
  module.exports = fxMap;
15428
15560
 
15561
+
15562
+ // A formant/vowel filter. With this filter you can imitate the vowels of human
15563
+ // speech.
15564
+ //
15565
+ const FormantFilter = function(_params){
15566
+ // default values for the effect and separate parameters
15567
+ _params = Util.mapDefaults(_params, [ 'o', 0, 1, 1 ]);
15568
+ this._vowel = _params[0];
15569
+ this._slide = _params[1];
15570
+ this._shift = _params[2];
15571
+ this._wet = _params[3];
15572
+
15573
+ // the input and wetdry output nodes
15574
+ this._fx = new Tone.Gain(1);
15575
+ this._mix = new Tone.Add();
15576
+ this._mixWet = new Tone.Gain(0).connect(this._mix);
15577
+ this._mixDry = new Tone.Gain(1).connect(this._mix.addend);
15578
+ this._fx.connect(this._mixDry);
15579
+
15580
+ // data collected from various sources, please see the research on
15581
+ // https://github.com/tmhglnd/vowel-formants-graph
15582
+ this._formantData = {
15583
+ "oo" : [ 299, 850, 2250, "book" ],
15584
+ "u" : [ 438, 998, 2250, "foot" ],
15585
+ "oh" : [ 569, 856, 2410, "pot" ],
15586
+ "uh" : [ 518, 1189, 2390, "bug" ],
15587
+ "er" : [ 490, 1358, 1690, "bird" ],
15588
+ "a" : [ 730, 1102, 2440, "part" ],
15589
+ "ae" : [ 660, 1702, 2410, "lap" ],
15590
+ "e" : [ 528, 1855, 2480, "let" ],
15591
+ "i" : [ 400, 2002, 2250, "bit" ],
15592
+ "ee" : [ 270, 2296, 3010, "leap" ],
15593
+ "o" : [ 399, 709, 2420, "fold" ],
15594
+ "oe" : [ 360, 1546, 2346, "you" ]
15595
+ }
15596
+ this._vowels = Object.keys(this._formantData);
15597
+
15598
+ // a -12dB/octave lowpass filter for preserving low end
15599
+ this._lopass = new Tone.Filter(85, 'lowpass', -12).connect(this._mixWet);
15600
+ this._fx.connect(this._lopass);
15601
+
15602
+ // 3 bandpass biquadfilters for the formants
15603
+ // mix the filters together to one output
15604
+ this._formants = [];
15605
+ for (let f=0; f<3; f++){
15606
+ this._formants[f] = new Tone.Filter(this._formantData['o'][f], 'bandpass');
15607
+ // parallel processing of the filters from the input
15608
+ this._fx.connect(this._formants[f]);
15609
+ this._formants[f].connect(this._mixWet);
15610
+ }
15611
+
15612
+ this.set = function(c, time, bpm){
15613
+ let v = Util.getParam(this._vowel, c);
15614
+ let r = Util.divToS(Util.getParam(this._slide, c), bpm);
15615
+ let s = Util.clip(Util.getParam(this._shift, c), 0.17, 6);
15616
+ let w = Util.clip(Util.getParam(this._wet, c));
15617
+
15618
+ // get the formantdata from the object
15619
+ let freqs = this._formantData['oo'];
15620
+ v = (!isNaN(v)) ?
15621
+ this._vowels[Util.clip(v, 0, this._vowels.length)] : v;
15622
+ // make sure vowel is a valid option
15623
+ if (this._formantData.hasOwnProperty(v)){
15624
+ freqs = this._formantData[v];
15625
+ } else {
15626
+ log(`fx(vowel): ${v} is not a valid vowel selection, using default "o"`);
15627
+ }
15628
+
15629
+ // apply the frequencies, Q's and gain to the individual formant filters
15630
+ for (let f=0; f<this._formants.length; f++){
15631
+ // the frequency is the formant freq * shift factor
15632
+ if (r > 0) {
15633
+ this._formants[f].frequency.rampTo(freqs[f] * s, r, time);
15634
+ } else {
15635
+ this._formants[f].frequency.setValueAtTime(freqs[f] * s, time);
15636
+ }
15637
+ // Q = (Freq * Shift) / (BandWidthHz / 2)
15638
+ // Default bandwidth set to 50Hz
15639
+ this._formants[f].Q.setValueAtTime(freqs[f] * s * 0.05, time);
15640
+ // Apply gain compensation based on formant number, +18dB, +5, +2
15641
+ this._formants[f].output.gain.setValueAtTime(18 * (0.31 ** f), time);
15642
+ }
15643
+
15644
+ // apply wetdry mix
15645
+ this._mixWet.gain.setValueAtTime(w, time);
15646
+ this._mixDry.gain.setValueAtTime(1 - w, time);
15647
+ }
15648
+
15649
+ this.chain = function(){
15650
+ return { 'send' : this._fx, 'return' : this._mix }
15651
+ }
15652
+
15653
+ this.delete = function(){
15654
+ const nodes = [ this._fx, this._mix, this._mixWet, this._mixDry, ...this._formants, this._lopass ];
15655
+
15656
+ nodes.forEach((n) => {
15657
+ n.disconnect();
15658
+ n.dispose();
15659
+ });
15660
+ }
15661
+ }
15662
+
15429
15663
  // A Downsampling Chiptune effect. Downsamples the signal by a specified amount
15430
15664
  // Resulting in a lower samplerate, making it sound more like 8bit/chiptune
15431
15665
  // Programmed with a custom AudioWorkletProcessor, see effects/Processors.js
@@ -15471,7 +15705,7 @@ const DownSampler = function(_params){
15471
15705
  }
15472
15706
 
15473
15707
  this.delete = function(){
15474
- const nodes = [ this._fx, this._mix, this._mixDry ];
15708
+ const nodes = [ this._fx.input, this._fx.output, this._fx, this._mix, this._mixDry ];
15475
15709
 
15476
15710
  nodes.forEach((n) => {
15477
15711
  n.disconnect();
@@ -15485,13 +15719,11 @@ const DownSampler = function(_params){
15485
15719
  // distortion is applied on the overdrive parameter
15486
15720
  //
15487
15721
  const TanhDistortion = function(_params){
15488
- _params = Util.mapDefaults(_params, [ 4, 1 ]);
15722
+ _params = Util.mapDefaults(_params, [ 2, 1 ]);
15489
15723
  // apply the default values and convert to arrays where necessary
15490
15724
  this._drive = Util.toArray(_params[0]);
15491
15725
  this._wet = Util.toArray(_params[1]);
15492
15726
 
15493
- console.log('Distortion with:', this._drive, this._wet);
15494
-
15495
15727
  // The crossfader for wet-dry (originally implemented with CrossFade)
15496
15728
  // this._mix = new Tone.CrossFade();
15497
15729
  this._mix = new Tone.Add();
@@ -15536,7 +15768,7 @@ const TanhDistortion = function(_params){
15536
15768
  }
15537
15769
 
15538
15770
  this.delete = function(){
15539
- let nodes = [ this._fx, this._mix, this._mixDry, this._mixWet ];
15771
+ let nodes = [ this._fx.input, this._fx.output, this._fx, this._mix, this._mixDry, this._mixWet ];
15540
15772
 
15541
15773
  nodes.forEach((n) => {
15542
15774
  n.disconnect();
@@ -15664,7 +15896,7 @@ const Squash = function(_params){
15664
15896
  }
15665
15897
 
15666
15898
  this.delete = function(){
15667
- let nodes = [ this._fx, this._mix, this._mixDry ];
15899
+ let nodes = [ this._fx.input, this._fx.output, this._fx, this._mix, this._mixDry ];
15668
15900
 
15669
15901
  nodes.forEach((n) => {
15670
15902
  n.disconnect();
@@ -15705,6 +15937,65 @@ const Reverb = function(_params){
15705
15937
  }
15706
15938
  }
15707
15939
 
15940
+ // Dattorro Reverb FX
15941
+ // Good sound plate reverb emulation
15942
+ // Based on the paper by Jon Dattorro and the code from Khoin
15943
+ // https://ccrma.stanford.edu/~dattorro/EffectDesignPart1.pdf
15944
+ // https://github.com/khoin/DattorroReverbNode
15945
+ //
15946
+ const DattorroReverb = function(_params){
15947
+ _params = Util.mapDefaults(_params, [ 0.5, 10, 0, 0.5 ]);
15948
+ this._gain = Util.toArray(_params[0]);
15949
+ this._size = Util.toArray(_params[1]);
15950
+ // unused currently, but here for compatibility with Mercury4Max code
15951
+ // this._slide = Util.toArray(_params[2]);
15952
+ this._wet = Util.toArray(_params[3]);
15953
+
15954
+ // The crossfader for wet-dry (originally implemented with CrossFade)
15955
+ this._mix = new Tone.Add();
15956
+ this._mixWet = new Tone.Gain(0).connect(this._mix);
15957
+ this._mixDry = new Tone.Gain(1).connect(this._mix.addend);
15958
+
15959
+ // a custom tone audio node with input/output gain and worklet effect
15960
+ this._fx = new Tone.ToneAudioNode();
15961
+ this._fx.input = new Tone.Gain(1).connect(this._mixDry);
15962
+ this._fx.output = new Tone.Gain(1).connect(this._mixWet);
15963
+ this._fx.workletNode = Tone.getContext().createAudioWorkletNode('dattorro-reverb');
15964
+ this._fx.input.chain(this._fx.workletNode, this._fx.output);
15965
+
15966
+ this.set = (c, time) => {
15967
+ const gn = Math.max(Util.getParam(this._gain, c), 0);
15968
+ const meta = Util.clip(Util.getParam(this._size, c), 0, 20);
15969
+ const wet = Util.clip(Util.getParam(this._wet, c));
15970
+
15971
+ const dc = Util.remap(meta, 0, 20, 0.01, 0.99, 0.8);
15972
+ const df = Util.remap(meta, 0, 20, 0.2, 0.75, 0.5);
15973
+ const dp = Util.remap(meta, 0, 20, 0.2, 0.65, 2.5);
15974
+ const pd = Util.remap(meta, 0, 20, 700, 100);
15975
+
15976
+ this._fx.workletNode.parameters.get('decay').setValueAtTime(dc, time);
15977
+ this._fx.workletNode.parameters.get('decayDiffusion1').setValueAtTime(df, time);
15978
+ this._fx.workletNode.parameters.get('damping').setValueAtTime(dp, time);
15979
+ this._fx.workletNode.parameters.get('preDelay').setValueAtTime(pd, time);
15980
+
15981
+ this._fx.workletNode.parameters.get('wet').setValueAtTime(gn * 0.7, time);
15982
+ // this._fx.workletNode.parameters.get('dry').setValueAtTime(0.7, time);
15983
+
15984
+ // apply wetdry mix
15985
+ this._mixWet.gain.setValueAtTime(wet, time);
15986
+ this._mixDry.gain.setValueAtTime(1 - wet, time);
15987
+ }
15988
+
15989
+ this.chain = () => {
15990
+ return { 'send' : this._fx, 'return' : this._mix }
15991
+ }
15992
+
15993
+ this.delete = () => {
15994
+ const nodes = [ this._fx, this._mix, this._mixDry, this._fx.input, this._fx.output ];
15995
+ nodes.forEach(n => { n.disconnect(); n.dispose() });
15996
+ }
15997
+ }
15998
+
15708
15999
  // PitchShift FX
15709
16000
  // Shift the pitch up or down with semitones
15710
16001
  // Utilizes the default PitchShift FX from ToneJS
@@ -16314,7 +16605,7 @@ class Instrument extends Sequencer {
16314
16605
 
16315
16606
  channelStrip(){
16316
16607
  // gain => output
16317
- this.gain = new Tone.Gain(0).toDestination();
16608
+ this.gain = new Tone.Gain(0, "normalRange").toDestination();
16318
16609
  // panning => gain
16319
16610
  this.panner = new Tone.Panner(0).connect(this.gain);
16320
16611
  // adsr => panning
@@ -16325,15 +16616,16 @@ class Instrument extends Sequencer {
16325
16616
 
16326
16617
  envelope(d){
16327
16618
  // return an Envelope and connect to next node
16328
- return new Tone.AmplitudeEnvelope({
16329
- attack: 0,
16330
- attackCurve: "linear",
16331
- decay: 0,
16332
- decayCurve: "linear",
16333
- sustain: 1,
16334
- release: 0.001,
16335
- releaseCurve: "linear"
16336
- }).connect(d);
16619
+ // return new Tone.AmplitudeEnvelope({
16620
+ // attack: 0,
16621
+ // attackCurve: "linear",
16622
+ // decay: 0,
16623
+ // decayCurve: "linear",
16624
+ // sustain: 1,
16625
+ // release: 0.001,
16626
+ // releaseCurve: "linear"
16627
+ // }).connect(d);
16628
+ return new Tone.Gain(0).connect(d);
16337
16629
  }
16338
16630
 
16339
16631
  event(c, time){
@@ -16343,7 +16635,7 @@ class Instrument extends Sequencer {
16343
16635
 
16344
16636
  // set FX parameters
16345
16637
  if (this._fx){
16346
- for (let f=0; f<this._fx.length; f++){
16638
+ for (let f = 0; f < this._fx.length; f++){
16347
16639
  this._fx[f].set(c, time, this.bpm());
16348
16640
  }
16349
16641
  }
@@ -16372,28 +16664,25 @@ class Instrument extends Sequencer {
16372
16664
 
16373
16665
  // set shape for playback (fade-in / out and length)
16374
16666
  if (this._att){
16375
- let att = Util.divToS(Util.getParam(this._att, c), this.bpm());
16376
- let dec = Util.divToS(Util.getParam(this._sus, c), this.bpm());
16377
- let rel = Util.divToS(Util.getParam(this._rel, c), this.bpm());
16378
-
16379
- // minimum attaack and release times are 1 millisecond
16380
- this.adsr.attack = Math.max(0.001, att);
16381
- this.adsr.decay = dec;
16382
- this.adsr.release = Math.max(0.001, rel);
16667
+ const att = Math.max(Util.divToS(Util.getParam(this._att, c), this.bpm()), 0.001);
16668
+ const dec = Util.divToS(Util.getParam(this._sus, c), this.bpm());
16669
+ const rel = Math.max(Util.divToS(Util.getParam(this._rel, c), this.bpm()), 0.001);
16383
16670
 
16384
- // trigger the envelope and release after a short while
16385
- // a better working alternative for the code below
16386
- this.adsr.triggerAttack(time);
16387
- this.adsr.triggerRelease(time + att + dec);
16388
-
16389
- // e = Math.min(this._time, att + dec + rel);
16390
- // e = Math.min(t, att + dec + rel);
16391
-
16392
- // let rt = Math.max(0.001, e - this.adsr.release);
16393
- // this.adsr.triggerAttackRelease(rt, time);
16671
+ // short ramp for retrigger, fades out the envelope over 2 ms
16672
+ // use the retrigger time to schedule the event a bit later as well
16673
+ let retrigger = 0;
16674
+ if (this.adsr.gain.getValueAtTime(time) > 0.01){
16675
+ retrigger = 0.002;
16676
+ // short ramp for retrigger, fades out the previous ramp
16677
+ this.adsr.gain.linearRampTo(0.0, retrigger, time);
16678
+ }
16679
+ // trigger the envelope and release after specified time
16680
+ this.adsr.gain.linearRampTo(1.0, att, time + retrigger);
16681
+ // exponential rampto * 5 for a good sounding exponential ramp
16682
+ this.adsr.gain.exponentialRampTo(0.0, rel * 5, time + att + dec + retrigger);
16394
16683
  } else {
16395
- // if shape is 'off' only trigger attack
16396
- this.adsr.triggerAttack(time);
16684
+ // if shape is 'off' turn on the gain of the envelope
16685
+ this.adsr.gain.setValueAtTime(1.0, time);
16397
16686
  }
16398
16687
  }
16399
16688
 
@@ -16403,18 +16692,34 @@ class Instrument extends Sequencer {
16403
16692
  console.log('Instrument()', this._name, c);
16404
16693
  }
16405
16694
 
16406
- fadeIn(t){
16695
+ fadeIn(t=0){
16407
16696
  // fade in the sound upon evaluation of code
16408
- this.gain.gain.rampTo(1, t, Tone.now());
16697
+ // this.gain.gain.rampTo(1, t, Tone.now());
16698
+ // fade in the sound directly in 5 ms
16699
+ this.gain.gain.rampTo(1, 0.005, Tone.now());
16409
16700
  }
16410
16701
 
16411
- fadeOut(t){
16412
- // fade out the sound upon evaluation of new code
16413
- this.gain.gain.rampTo(0, t, Tone.now());
16702
+ fadeOut(t, immediately=false){
16703
+ // if immediately is true, fade-out immediately instead of waiting
16704
+ let restTime = 0;
16705
+
16706
+ if (this._loop && !immediately){
16707
+ // get the remaining time till the next trigger in the loop
16708
+ // cancel the loop before that trigger happens and fade-out
16709
+ restTime = (1 - this._loop.progress) * this._loop.interval;
16710
+ }
16711
+
16414
16712
  setTimeout(() => {
16415
- this.delete();
16416
- // wait a little bit extra before deleting to avoid clicks
16417
- }, t * 1000 + 100);
16713
+ // stop the loop
16714
+ if (this._loop) this._loop.mute = 1;
16715
+ // fade out the sound upon evaluation of new code
16716
+ this.gain.gain.rampTo(0, t, Tone.now());
16717
+
16718
+ setTimeout(() => {
16719
+ this.delete();
16720
+ // wait a little bit extra before deleting to avoid clicks
16721
+ }, t * 1000 + 100);
16722
+ }, restTime * 1000 - 25);
16418
16723
  }
16419
16724
 
16420
16725
  delete(){
@@ -16426,6 +16731,14 @@ class Instrument extends Sequencer {
16426
16731
 
16427
16732
  this.panner.disconnect();
16428
16733
  this.panner.dispose();
16734
+
16735
+ this.adsr?.disconnect();
16736
+ this.adsr?.dispose();
16737
+
16738
+ // dispose the sound source (depending on inheriting class)
16739
+ this.source?.stop();
16740
+ this.source?.disconnect();
16741
+ this.source?.dispose();
16429
16742
  // this.adsr.dispose();
16430
16743
  // remove all fx
16431
16744
  this._fx.map((f) => f.delete());
@@ -16639,7 +16952,7 @@ class MonoMidi extends Sequencer {
16639
16952
  // only play a note if the notes are provided in the function
16640
16953
  // if (this._note.length > 0){
16641
16954
 
16642
- let noteOptions = { duration: d, velocity: g, time: sync };
16955
+ let noteOptions = { duration: d, attack: g, time: sync };
16643
16956
 
16644
16957
  // if a midinote is selected instead of note
16645
16958
  // play the value without mapping
@@ -17017,16 +17330,11 @@ class MonoSynth extends Instrument {
17017
17330
  delete(){
17018
17331
  // delete super class
17019
17332
  super.delete();
17333
+
17020
17334
  // dispose the sound source
17021
- // this.source.delete();
17022
- // this.adsr.dispose();
17023
- this.synth.stop();
17024
- this.synth.disconnect();
17025
- this.synth.dispose();
17026
-
17027
- this.adsr.dispose();
17028
- this.synth.dispose();
17029
- this.source.dispose();
17335
+ this.synth?.stop();
17336
+ this.synth?.disconnect();
17337
+ this.synth?.dispose();
17030
17338
 
17031
17339
  console.log('disposed MonoSynth()', this._wave);
17032
17340
  }
@@ -17114,7 +17422,9 @@ class PolyInstrument extends Instrument {
17114
17422
 
17115
17423
  // set all busymaps based on current amplitude value
17116
17424
  for (let i=0; i<this.busymap.length; i++){
17117
- this.busymap[i] = this.adsrs[i].value > 0;
17425
+ // consider the voice busy if amplitude is > -40dB
17426
+ // because of exponential ramp otherwise voice stays busy too long
17427
+ this.busymap[i] = this.adsrs[i].gain.getValueAtTime(time) > 0.01;
17118
17428
  if (!this.busymap[i]){
17119
17429
  free.push(i);
17120
17430
  }
@@ -17142,26 +17452,28 @@ class PolyInstrument extends Instrument {
17142
17452
 
17143
17453
  // set shape for playback (fade-in / out and length)
17144
17454
  if (this._att){
17145
- let att = Util.divToS(Util.lookup(this._att, c), this.bpm());
17146
- let dec = Util.divToS(Util.lookup(this._sus, c), this.bpm());
17147
- let rel = Util.divToS(Util.lookup(this._rel, c), this.bpm());
17455
+ const att = Math.max(Util.divToS(Util.lookup(this._att, c), this.bpm()), 0.001);
17456
+ const dec = Util.divToS(Util.lookup(this._sus, c), this.bpm());
17457
+ const rel = Math.max(Util.divToS(Util.lookup(this._rel, c), this.bpm()), 0.001);
17148
17458
 
17149
- this.adsrs[i].attack = Math.max(0.001, att);
17150
- this.adsrs[i].decay = dec;
17151
- this.adsrs[i].release = Math.max(0.001, rel);
17152
-
17153
- // e = Math.min(this._time, att + dec + rel);
17154
- // let rt = Math.max(0.001, e - this.adsrs[i].release);
17155
- // this.adsrs[i].triggerAttackRelease(rt, time);
17156
-
17157
- // trigger the envelope
17158
- this.adsrs[i].triggerAttack(time);
17159
- this.adsrs[i].triggerRelease(time + att + dec);
17459
+ // short ramp for retrigger, fades out the envelope over
17460
+ // 2 ms. use the retrigger time to schedule the event
17461
+ // a bit later as well
17462
+ let retrigger = 0;
17463
+ if (this.adsrs[i].gain.getValueAtTime(time) > 0.01){
17464
+ retrigger = 0.002;
17465
+ // short ramp for retrigger, fades out the previous ramp
17466
+ this.adsrs[i].gain.linearRampTo(0.0, retrigger, time);
17467
+ }
17468
+ // trigger the envelope and release after specified time
17469
+ this.adsrs[i].gain.linearRampTo(1.0, att, time + retrigger);
17470
+ this.adsrs[i].gain.exponentialRampTo(0.0, rel * 5, time + att + dec + retrigger);
17160
17471
  } else {
17161
17472
  // if shape is off only trigger attack
17162
17473
  // when voice stealing is 'off' this will lead to all
17163
17474
  // voices set to busy!
17164
- this.adsrs[i].triggerAttack(time);
17475
+ // if shape is 'off' turn on the gain of the envelope
17476
+ this.adsrs[i].gain.setValueAtTime(1.0, time);
17165
17477
  }
17166
17478
 
17167
17479
  }
@@ -17451,9 +17763,9 @@ class PolySynth extends PolyInstrument {
17451
17763
  this.sources[id].frequency.rampTo(f, s, time);
17452
17764
  } else {
17453
17765
  this.sources[id].frequency.setValueAtTime(f, time);
17454
- // first time the synth plays it doesn't slide!
17455
- this._firstSlide[id] = false;
17456
17766
  }
17767
+ // first time the synth plays it doesn't slide!
17768
+ this._firstSlide[id] = false;
17457
17769
  }
17458
17770
 
17459
17771
  note(i=0, o=0){
@@ -17532,12 +17844,26 @@ class Sequencer {
17532
17844
  return Tone.Transport.bpm.value;
17533
17845
  }
17534
17846
 
17535
- makeLoop(){
17847
+ makeLoop(stepCount, unnamedCount){
17536
17848
  // dispose of previous loop if active
17537
17849
  if (this._loop){
17538
17850
  this._loop.dispose();
17539
17851
  }
17540
17852
 
17853
+ // transfer the stepcount to count and beatcount if provided
17854
+ if (unnamedCount){
17855
+ this._count = unnamedCount.count;
17856
+ this._beatCount = unnamedCount.beat;
17857
+ }
17858
+ // replace count if a name is given.
17859
+ // this works through giving the instrument the same name
17860
+ if (stepCount){
17861
+ if (stepCount[this._name]){
17862
+ this._count = stepCount[this._name].count;
17863
+ this._beatCount = stepCount[this._name].beat;
17864
+ }
17865
+ }
17866
+
17541
17867
  // create the event for a loop or external trigger
17542
17868
  this._event = (time) => {
17543
17869
  // convert transport time to Ticks and convert reset time to ticks
@@ -17552,15 +17878,15 @@ class Sequencer {
17552
17878
  this._beatCount = 0;
17553
17879
  }
17554
17880
  }
17555
- // set subdivision speeds
17556
- this._loop.playbackRate = Util.getParam(this._subdiv, this._count);
17881
+ if (this._time !== null){
17882
+ // set subdivision speeds
17883
+ this._loop.playbackRate = Util.getParam(this._subdiv, this._count);
17884
+ // get the subdivision count (always 0, except when subdividing)
17885
+ this._subdivCnt = (this._subdivCnt + 1) % this._loop.playbackRate;
17886
+ // humanize method is interesting to add
17887
+ this._loop.humanize = Util.getParam(this._human, this._count);
17888
+ }
17557
17889
 
17558
- // get the subdivision count (always 0, except when subdividing)
17559
- this._subdivCnt = (this._subdivCnt + 1) % this._loop.playbackRate;
17560
-
17561
- // humanize method is interesting to add
17562
- this._loop.humanize = Util.getParam(this._human, this._count);
17563
-
17564
17890
  // get beat probability for current count
17565
17891
  let b = Util.getParam(this._beat, this._count);
17566
17892
 
@@ -17586,8 +17912,10 @@ class Sequencer {
17586
17912
  }, (time - Tone.context.currentTime) * 1000);
17587
17913
  }
17588
17914
  // also emit an internal event for other instruments to sync to
17589
- // let event = new CustomEvent(`/${this._name}`, { detail: 1 });
17590
- // window.dispatchEvent(event);
17915
+ const event = new CustomEvent(`/${this._name}`, {
17916
+ detail: { value: 1, time: time }
17917
+ });
17918
+ window.dispatchEvent(event);
17591
17919
 
17592
17920
  // execute a visual event for Hydra
17593
17921
  if (this._visual.length > 0){
@@ -17623,16 +17951,17 @@ class Sequencer {
17623
17951
  this._event(time)
17624
17952
  }, this._time).start(schedule);
17625
17953
  }
17626
- // else {
17627
- // // generate a listener for the osc-address
17628
- // let oscAddress = `${this._offset}`;
17629
- // window.addEventListener(oscAddress, (event) => {
17630
- // // trigger the event if value greater than 0
17631
- // if (event.detail > 0){
17632
- // Tone.Transport.scheduleOnce((time) => this._event(time), Tone.immediate());
17633
- // }
17634
- // });
17635
- // }
17954
+ else {
17955
+ // generate a listener for the osc-address and store for removal
17956
+ this._oscAddress = `${this._offset}`;
17957
+ this._listener = (event) => {
17958
+ // trigger the event if value greater than 0
17959
+ if (event.detail.value > 0){
17960
+ this._event(event.detail.time);
17961
+ }
17962
+ }
17963
+ window.addEventListener(this._oscAddress, this._listener);
17964
+ }
17636
17965
  }
17637
17966
 
17638
17967
  event(c, time){
@@ -17656,18 +17985,23 @@ class Sequencer {
17656
17985
 
17657
17986
  delete(){
17658
17987
  // dispose loop
17659
- this._loop.dispose();
17988
+ this._loop?.stop();
17989
+ this._loop?.dispose();
17990
+ // remove the listenere if one was created
17991
+ if (this._oscAddress){
17992
+ window.removeEventListener(this._oscAddress, this._listener)
17993
+ }
17660
17994
  console.log('=> disposed Sequencer()');
17661
17995
  }
17662
17996
 
17663
17997
  start(){
17664
17998
  // restart at offset
17665
- this._loop.start(this._offset);
17999
+ this._loop?.start(this._offset);
17666
18000
  }
17667
18001
 
17668
18002
  stop(){
17669
18003
  // stop sequencer
17670
- this._loop.stop();
18004
+ this._loop?.stop();
17671
18005
  }
17672
18006
 
17673
18007
  time(t, o=0){
@@ -17776,6 +18110,12 @@ function clip(v, l=0, h=1){
17776
18110
  return Math.max(l, Math.min(h, v));
17777
18111
  }
17778
18112
 
18113
+ // scale values between an input and output range with exponent
18114
+ function remap(val=0, inLo=0, inHi=1, outLo=0, outHi=1, exp=1){
18115
+ let temp = ((val - inLo) / (inHi - inLo)) ** exp;
18116
+ return temp * (outHi - outLo) + outLo;
18117
+ }
18118
+
17779
18119
  // make sure the output is a number, else output a default value
17780
18120
  function assureNum(v, d=1){
17781
18121
  return isNaN(v) ? d : v;
@@ -17969,7 +18309,7 @@ function log(msg){
17969
18309
  }
17970
18310
  }
17971
18311
 
17972
- module.exports = { mapDefaults, atTime, atodb, dbtoa, clip, assureNum, lookup, randLookup, isRandom, getParam, toArray, msToS, formatRatio, divToS, divToF, toMidi, mtof, noteToMidi, noteToFreq, assureWave, log }
18312
+ module.exports = { mapDefaults, atTime, atodb, dbtoa, clip, remap,assureNum, lookup, randLookup, isRandom, getParam, toArray, msToS, formatRatio, divToS, divToF, toMidi, mtof, noteToMidi, noteToFreq, assureWave, log }
17973
18313
  },{"tone":44,"total-serialism":47}],67:[function(require,module,exports){
17974
18314
  module.exports={
17975
18315
  "uptempo" : 10,
@@ -18007,6 +18347,7 @@ const PolySample = require('./core/PolySample.js');
18007
18347
  const Tempos = require('./data/genre-tempos.json');
18008
18348
  const Util = require('./core/Util.js');
18009
18349
  const { divToS } = require('./core/Util.js');
18350
+ const { count } = require('total-serialism/src/gen-basic.js');
18010
18351
 
18011
18352
  class MercuryInterpreter {
18012
18353
  constructor({ hydra, p5canvas } = {}){
@@ -18065,10 +18406,11 @@ class MercuryInterpreter {
18065
18406
  });
18066
18407
  }
18067
18408
 
18068
- removeSounds(s, f=0) {
18069
- // fade out and delete after fade
18409
+ removeSounds(s, f=0, im=false) {
18410
+ // fade out and delete after fade. second parameter sets
18411
+ // immediate fade-out, otherwise wait till trigger
18070
18412
  s.map((_s) => {
18071
- if (_s){ _s.fadeOut(f); }
18413
+ if (_s){ _s.fadeOut(f, im); }
18072
18414
  });
18073
18415
  // empty array to trigger garbage collection
18074
18416
  s.length = 0;
@@ -18082,11 +18424,16 @@ class MercuryInterpreter {
18082
18424
  }
18083
18425
 
18084
18426
  setCrossFade(f){
18085
- // set the crossFade in milliseconds
18086
- // set crossFade time in ms
18427
+ // set the crossFade time in milliseconds
18087
18428
  this.crossFade = divToS(f, this.getBPM());
18088
18429
  // this.crossFade = Number(f) / 1000;
18089
- Util.log(`Crossfade set to: ${f}ms`);
18430
+ Util.log(`crossFade is deprecated, setting fadeOut time to ${this.crossFade}ms`);
18431
+ }
18432
+
18433
+ setFadeOut(f){
18434
+ // set the fadeOut time in milliseconds
18435
+ this.crossFade = divToS(f, this.getBPM());
18436
+ Util.log(`setting fadeOut time to ${this.crossFade}`);
18090
18437
  }
18091
18438
 
18092
18439
  getCode(){
@@ -18145,6 +18492,10 @@ class MercuryInterpreter {
18145
18492
  // set crossFade time in ms
18146
18493
  this.setCrossFade(args[0]);
18147
18494
  },
18495
+ 'fadeOut' : (args) => {
18496
+ // set fadeOut time in ms
18497
+ this.setFadeOut(args[0]);
18498
+ },
18148
18499
  'tempo' : (args) => {
18149
18500
  let t = args[0];
18150
18501
  if (isNaN(t)){
@@ -18276,7 +18627,8 @@ class MercuryInterpreter {
18276
18627
  }
18277
18628
 
18278
18629
  // copy current sounds over to past
18279
- this._sounds = this.sounds.slice();
18630
+ // this._sounds = this.sounds.slice();
18631
+ this._sounds = [ ...this.sounds ];
18280
18632
  // empty new sounds array
18281
18633
  this.sounds = [];
18282
18634
 
@@ -18290,16 +18642,30 @@ class MercuryInterpreter {
18290
18642
  }
18291
18643
  }
18292
18644
 
18645
+ // get all the current counts and store in dict
18646
+ let countTransfer = {};
18647
+ this._sounds.map((s) => {
18648
+ countTransfer[s._name] = {
18649
+ count: s._count,
18650
+ beat: s._beatCount
18651
+ }
18652
+ });
18653
+
18654
+ // create new loops, transfer the counts
18655
+ for (let s = 0; s < this.sounds.length; s++){
18656
+ this.sounds[s].makeLoop(countTransfer, Object.values(countTransfer)[s]);
18657
+ }
18658
+
18293
18659
  // start new loops;
18294
- this.makeLoops(this.sounds);
18295
- this.transferCounts(this._sounds, this.sounds);
18660
+ // this.makeLoops(this.sounds);
18661
+ // this.transferCounts(this._sounds, this.sounds);
18296
18662
 
18297
18663
  // when all loops started fade in the new sounds and fade out old
18298
- if (!this.sounds.length){
18299
- this.startSounds(this.sounds);
18300
- }
18301
- this.startSounds(this.sounds, this.crossFade);
18664
+ // if (!this.sounds.length){
18665
+ // this.startSounds(this.sounds);
18666
+ // }
18302
18667
  this.removeSounds(this._sounds, this.crossFade);
18668
+ this.startSounds(this.sounds);
18303
18669
 
18304
18670
  this.resume();
18305
18671
 
@@ -18324,10 +18690,10 @@ class MercuryInterpreter {
18324
18690
  }
18325
18691
  }
18326
18692
  module.exports = { MercuryInterpreter }
18327
- },{"./core/MonoInput.js":58,"./core/MonoMidi.js":59,"./core/MonoSample.js":60,"./core/MonoSynth.js":61,"./core/PolySample.js":63,"./core/PolySynth.js":64,"./core/Util.js":66,"./data/genre-tempos.json":67,"mercury-lang":27,"tone":44,"total-serialism":47}],69:[function(require,module,exports){
18693
+ },{"./core/MonoInput.js":58,"./core/MonoMidi.js":59,"./core/MonoSample.js":60,"./core/MonoSynth.js":61,"./core/PolySample.js":63,"./core/PolySynth.js":64,"./core/Util.js":66,"./data/genre-tempos.json":67,"mercury-lang":27,"tone":44,"total-serialism":47,"total-serialism/src/gen-basic.js":48}],69:[function(require,module,exports){
18328
18694
 
18329
18695
  console.log(`
18330
- Mercury Engine by Timo Hoogland (c) 2023
18696
+ Mercury Engine by Timo Hoogland (c) 2018-2025
18331
18697
  more info:
18332
18698
  https://www.timohoogland.com
18333
18699
  https://github.com/tmhglnd/mercury-playground
@@ -18343,7 +18709,7 @@ const { WebMidi } = require("webmidi");
18343
18709
  // load extra AudioWorkletProcessors from file
18344
18710
  // transformed to inline with browserify brfs
18345
18711
 
18346
- const fxExtensions = "\n// A white noise generator at -6dBFS to test AudioWorkletProcessor\n//\n// class NoiseProcessor extends AudioWorkletProcessor {\n// \tprocess(inputs, outputs, parameters){\n// \t\tconst output = outputs[0];\n\n// \t\toutput.forEach((channel) => {\n// \t\t\tfor (let i=0; i<channel.length; i++) {\n// \t\t\t\tchannel[i] = Math.random() - 0.5;\n// \t\t\t}\n// \t\t});\n// \t\treturn true;\n// \t}\n// }\n// registerProcessor('noise-processor', NoiseProcessor);\n\n// A Downsampling Chiptune effect. Downsamples the signal by a specified amount\n// Resulting in a lower samplerate, making it sound more like 8bit/chiptune\n// Programmed with a custom AudioWorkletProcessor, see effects/Processors.js\n//\nclass DownSampleProcessor extends AudioWorkletProcessor {\n\tstatic get parameterDescriptors() {\n\t\treturn [{\n\t\t\tname: 'down',\n\t\t\tdefaultValue: 8,\n\t\t\tminValue: 1,\n\t\t\tmaxValue: 2048\n\t\t}];\n\t}\n\n\tconstructor(){\n\t\tsuper();\n\t\t// the frame counter\n\t\tthis.count = 0;\n\t\t// sample and hold variable array\n\t\tthis.sah = [];\n\t}\n\n\tprocess(inputs, outputs, parameters){\n\t\tconst input = inputs[0];\n\t\tconst output = outputs[0];\n\n\t\t// if there is anything to process\n\t\tif (input.length > 0){\n\t\t\t// for the length of the sample array (generally 128)\n\t\t\tfor (let i=0; i<input[0].length; i++){\n\t\t\t\tconst d = (parameters.down.length > 1) ? parameters.down[i] : parameters.down[0];\n\t\t\t\t// for every channel\n\t\t\t\tfor (let channel=0; channel<input.length; ++channel){\n\t\t\t\t\t// if counter equals 0, sample and hold\n\t\t\t\t\tif (this.count % d === 0){\n\t\t\t\t\t\tthis.sah[channel] = input[channel][i];\n\t\t\t\t\t}\n\t\t\t\t\t// output the currently held sample\n\t\t\t\t\toutput[channel][i] = this.sah[channel];\n\t\t\t\t}\n\t\t\t\t// increment sample counter\n\t\t\t\tthis.count++;\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n}\nregisterProcessor('downsampler-processor', DownSampleProcessor);\n\n// A distortion algorithm using the tanh (hyperbolic-tangent) as a \n// waveshaping technique. Some mapping to apply a more equal loudness \n// distortion is applied on the overdrive parameter\n//\nclass TanhDistortionProcessor extends AudioWorkletProcessor {\n\tstatic get parameterDescriptors(){\n\t\treturn [{\n\t\t\tname: 'amount',\n\t\t\tdefaultValue: 4,\n\t\t\tminValue: 1\n\t\t}, {\n\t\t\tname: 'makeup',\n\t\t\tdefaultValue: 0.5,\n\t\t\tminValue: 0,\n\t\t\tmaxValue: 2\n\t\t}]\n\t}\n\n\tconstructor(){\n\t\tsuper();\n\t}\n\n\tprocess(inputs, outputs, parameters){\n\t\tconst input = inputs[0];\n\t\tconst output = outputs[0];\n\n\t\tif (input.length > 0){\n\t\t\tfor (let channel=0; channel<input.length; ++channel){\n\t\t\t\tfor (let i=0; i<input[channel].length; i++){\n\t\t\t\t\tconst a = (parameters.amount.length > 1)? parameters.amount[i] : parameters.amount[0];\n\t\t\t\t\tconst m = (parameters.makeup.length > 1)? parameters.makeup[i] : parameters.makeup[0];\n\t\t\t\t\t// simple waveshaping with tanh\n\t\t\t\t\toutput[channel][i] = Math.tanh(input[channel][i] * a) * m;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n}\nregisterProcessor('tanh-distortion-processor', TanhDistortionProcessor);\n\n// A distortion/compression effect of an incoming signal\n// Based on an algorithm by Peter McCulloch\n// \nclass SquashProcessor extends AudioWorkletProcessor {\n\tstatic get parameterDescriptors(){\n\t\treturn [{\n\t\t\tname: 'amount',\n\t\t\tdefaultValue: 4,\n\t\t\tminValue: 1,\n\t\t\tmaxValue: 1024\n\t\t}, {\n\t\t\tname: 'makeup',\n\t\t\tdefaultValue: 0.5,\n\t\t\tminValue: 0,\n\t\t\tmaxValue: 2\n\t\t}];\n\t}\n\n\tconstructor(){\n\t\tsuper();\n\t}\n\n\tprocess(inputs, outputs, parameters){\n\t\tconst input = inputs[0];\n\t\tconst output = outputs[0];\n\t\t\n\t\tif (input.length > 0){\n\t\t\tfor (let channel=0; channel<input.length; ++channel){\n\t\t\t\tfor (let i=0; i<input[channel].length; i++){\n\t\t\t\t\t// (s * a) / ((s * a)^2 * 0.28 + 1) / √a\n\t\t\t\t\t// drive amount, minimum of 1\n\t\t\t\t\tconst a = (parameters.amount.length > 1)? parameters.amount[i] : parameters.amount[0];\n\t\t\t\t\t// makeup gain\n\t\t\t\t\tconst m = (parameters.makeup.length > 1)? parameters.makeup[i] : parameters.makeup[0];\n\t\t\t\t\t// set the waveshaper effect\n\t\t\t\t\tconst s = input[channel][i];\n\t\t\t\t\tconst x = s * a * 1.412;\n\t\t\t\t\toutput[channel][i] = (x / (x * x * 0.28 + 1.0)) * m * 0.708;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n}\nregisterProcessor('squash-processor', SquashProcessor);";
18712
+ const fxExtensions = "\n// A white noise generator at -6dBFS to test AudioWorkletProcessor\n//\n// class NoiseProcessor extends AudioWorkletProcessor {\n// \tprocess(inputs, outputs, parameters){\n// \t\tconst output = outputs[0];\n\n// \t\toutput.forEach((channel) => {\n// \t\t\tfor (let i=0; i<channel.length; i++) {\n// \t\t\t\tchannel[i] = Math.random() - 0.5;\n// \t\t\t}\n// \t\t});\n// \t\treturn true;\n// \t}\n// }\n// registerProcessor('noise-processor', NoiseProcessor);\n\n// A Downsampling Chiptune effect. Downsamples the signal by a specified amount\n// Resulting in a lower samplerate, making it sound more like 8bit/chiptune\n// Programmed with a custom AudioWorkletProcessor, see effects/Processors.js\n//\nclass DownSampleProcessor extends AudioWorkletProcessor {\n\tstatic get parameterDescriptors() {\n\t\treturn [{\n\t\t\tname: 'down',\n\t\t\tdefaultValue: 8,\n\t\t\tminValue: 1,\n\t\t\tmaxValue: 2048\n\t\t}];\n\t}\n\n\tconstructor(){\n\t\tsuper();\n\t\t// the frame counter\n\t\tthis.count = 0;\n\t\t// sample and hold variable array\n\t\tthis.sah = [];\n\t}\n\n\tprocess(inputs, outputs, parameters){\n\t\tconst input = inputs[0];\n\t\tconst output = outputs[0];\n\n\t\t// if there is anything to process\n\t\tif (input.length > 0){\n\t\t\t// for the length of the sample array (generally 128)\n\t\t\tfor (let i=0; i<input[0].length; i++){\n\t\t\t\tconst d = (parameters.down.length > 1) ? parameters.down[i] : parameters.down[0];\n\t\t\t\t// for every channel\n\t\t\t\tfor (let channel=0; channel<input.length; ++channel){\n\t\t\t\t\t// if counter equals 0, sample and hold\n\t\t\t\t\tif (this.count % d === 0){\n\t\t\t\t\t\tthis.sah[channel] = input[channel][i];\n\t\t\t\t\t}\n\t\t\t\t\t// output the currently held sample\n\t\t\t\t\toutput[channel][i] = this.sah[channel];\n\t\t\t\t}\n\t\t\t\t// increment sample counter\n\t\t\t\tthis.count++;\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n}\nregisterProcessor('downsampler-processor', DownSampleProcessor);\n\n// A distortion algorithm using the tanh (hyperbolic-tangent) as a \n// waveshaping technique. Some mapping to apply a more equal loudness \n// distortion is applied on the overdrive parameter\n//\nclass TanhDistortionProcessor extends AudioWorkletProcessor {\n\tstatic get parameterDescriptors(){\n\t\treturn [{\n\t\t\tname: 'amount',\n\t\t\tdefaultValue: 4,\n\t\t\tminValue: 1\n\t\t}, {\n\t\t\tname: 'makeup',\n\t\t\tdefaultValue: 0.5,\n\t\t\tminValue: 0,\n\t\t\tmaxValue: 2\n\t\t}]\n\t}\n\n\tconstructor(){\n\t\tsuper();\n\t}\n\n\tprocess(inputs, outputs, parameters){\n\t\tconst input = inputs[0];\n\t\tconst output = outputs[0];\n\n\t\tif (input.length > 0){\n\t\t\tfor (let channel=0; channel<input.length; ++channel){\n\t\t\t\tfor (let i=0; i<input[channel].length; i++){\n\t\t\t\t\tconst a = (parameters.amount.length > 1)? parameters.amount[i] : parameters.amount[0];\n\t\t\t\t\tconst m = (parameters.makeup.length > 1)? parameters.makeup[i] : parameters.makeup[0];\n\t\t\t\t\t// simple waveshaping with tanh\n\t\t\t\t\toutput[channel][i] = Math.tanh(input[channel][i] * a) * m;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n}\nregisterProcessor('tanh-distortion-processor', TanhDistortionProcessor);\n\n// A distortion/compression effect of an incoming signal\n// Based on an algorithm by Peter McCulloch\n// \nclass SquashProcessor extends AudioWorkletProcessor {\n\tstatic get parameterDescriptors(){\n\t\treturn [{\n\t\t\tname: 'amount',\n\t\t\tdefaultValue: 4,\n\t\t\tminValue: 1,\n\t\t\tmaxValue: 1024\n\t\t}, {\n\t\t\tname: 'makeup',\n\t\t\tdefaultValue: 0.5,\n\t\t\tminValue: 0,\n\t\t\tmaxValue: 2\n\t\t}];\n\t}\n\n\tconstructor(){\n\t\tsuper();\n\t}\n\n\tprocess(inputs, outputs, parameters){\n\t\tconst input = inputs[0];\n\t\tconst output = outputs[0];\n\t\t\n\t\tif (input.length > 0){\n\t\t\tfor (let channel=0; channel<input.length; ++channel){\n\t\t\t\tfor (let i=0; i<input[channel].length; i++){\n\t\t\t\t\t// (s * a) / ((s * a)^2 * 0.28 + 1) / √a\n\t\t\t\t\t// drive amount, minimum of 1\n\t\t\t\t\tconst a = (parameters.amount.length > 1)? parameters.amount[i] : parameters.amount[0];\n\t\t\t\t\t// makeup gain\n\t\t\t\t\tconst m = (parameters.makeup.length > 1)? parameters.makeup[i] : parameters.makeup[0];\n\t\t\t\t\t// set the waveshaper effect\n\t\t\t\t\tconst s = input[channel][i];\n\t\t\t\t\tconst x = s * a * 1.412;\n\t\t\t\t\toutput[channel][i] = (x / (x * x * 0.28 + 1.0)) * m * 0.708;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n}\nregisterProcessor('squash-processor', SquashProcessor);\n\n// Dattorro Reverberator\n// Thanks to port by khoin, taken from:\n// https://github.com/khoin/DattorroReverbNode\n// based on the paper from Jon Dattorro:\n// https://ccrma.stanford.edu/~dattorro/EffectDesignPart1.pdf\n// with small modifications to work in Mercury\n//\n// In jurisdictions that recognize copyright laws, this software is to\n// be released into the public domain.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND.\n// THE AUTHOR(S) SHALL NOT BE LIABLE FOR ANYTHING, ARISING FROM, OR IN\n// CONNECTION WITH THE SOFTWARE OR THE DISTRIBUTION OF THE SOFTWARE.\n// \nclass DattorroReverb extends AudioWorkletProcessor {\n\tstatic get parameterDescriptors() {\n\t\treturn [\n\t\t\t[\"preDelay\", 0, 0, sampleRate - 1, \"k-rate\"],\n\t\t\t// [\"bandwidth\", 0.9999, 0, 1, \"k-rate\"],\t\n\t\t\t[\"inputDiffusion1\", 0.75, 0, 1, \"k-rate\"],\n\t\t\t[\"inputDiffusion2\", 0.625, 0, 1, \"k-rate\"],\n\t\t\t[\"decay\", 0.5, 0, 1, \"k-rate\"],\n\t\t\t[\"decayDiffusion1\", 0.7, 0, 0.999999, \"k-rate\"],\n\t\t\t[\"decayDiffusion2\", 0.5, 0, 0.999999, \"k-rate\"],\n\t\t\t[\"damping\", 0.005, 0, 1, \"k-rate\"],\n\t\t\t[\"excursionRate\", 0.5, 0, 2, \"k-rate\"],\n\t\t\t[\"excursionDepth\", 0.7, 0, 2, \"k-rate\"],\n\t\t\t[\"wet\", 0.7, 0, 2, \"k-rate\"],\n\t\t\t// [\"dry\", 0.7, 0, 2, \"k-rate\"]\n\t\t].map(x => new Object({\n\t\t\tname: x[0],\n\t\t\tdefaultValue: x[1],\n\t\t\tminValue: x[2],\n\t\t\tmaxValue: x[3],\n\t\t\tautomationRate: x[4]\n\t\t}));\n\t}\n\n\tconstructor(options) {\n\t\tsuper(options);\n\n\t\tthis._Delays = [];\n\t\t// Pre-delay is always one-second long, rounded to the nearest 128-chunk\n\t\tthis._pDLength = sampleRate + (128 - sampleRate % 128);\n\t\tthis._preDelay = new Float32Array(this._pDLength);\n\t\tthis._pDWrite = 0;\n\t\tthis._lp1 = 0.0;\n\t\tthis._lp2 = 0.0;\n\t\tthis._lp3 = 0.0;\n\t\tthis._excPhase = 0.0;\n\n\t\t[\n\t\t\t0.004771345, 0.003595309, 0.012734787, 0.009307483, // pre-tank\n\t\t\t0.022579886, 0.149625349, 0.060481839, 0.1249958, // left-loop\n\t\t\t0.030509727, 0.141695508, 0.089244313, 0.106280031 // right-loop\n\t\t].forEach(x => this.makeDelay(x));\n\n\t\tthis._taps = Int16Array.from([\n\t\t\t0.008937872, 0.099929438, 0.064278754, 0.067067639, \n\t\t\t0.066866033, 0.006283391, 0.035818689, // left-output\n\t\t\t0.011861161, 0.121870905, 0.041262054, 0.08981553, \n\t\t\t0.070931756, 0.011256342, 0.004065724 // right-output\n\t\t], x => Math.round(x * sampleRate));\n\t}\n\n\tmakeDelay(length) {\n\t\t// len, array, write, read, mask\n\t\tlet len = Math.round(length * sampleRate);\n\t\tlet nextPow2 = 2 ** Math.ceil(Math.log2((len)));\n\t\tthis._Delays.push([\n\t\t\tnew Float32Array(nextPow2), len - 1, 0 | 0, nextPow2 - 1\n\t\t]);\n\t}\n\n\twriteDelay(index, data) {\n\t\treturn this._Delays[index][0][this._Delays[index][1]] = data;\n\t}\n\n\treadDelay(index) {\n\t\treturn this._Delays[index][0][this._Delays[index][2]];\n\t}\n\n\treadDelayAt(index, i) {\n\t\tlet d = this._Delays[index];\n\t\treturn d[0][(d[2] + i) & d[3]];\n\t}\n\n\t// cubic interpolation\n\t// O. Niemitalo: \n\t// https://www.musicdsp.org/en/latest/Other/49-cubic-interpollation.html\n\treadDelayCAt(index, i) {\n\t\tlet d = this._Delays[index],\n\t\t\tfrac = i - ~~i,\n\t\t\tint = ~~i + d[2] - 1,\n\t\t\tmask = d[3];\n\n\t\tlet x0 = d[0][int++ & mask],\n\t\t\tx1 = d[0][int++ & mask],\n\t\t\tx2 = d[0][int++ & mask],\n\t\t\tx3 = d[0][int & mask];\n\n\t\tlet a = (3 * (x1 - x2) - x0 + x3) / 2,\n\t\t\tb = 2 * x2 + x0 - (5 * x1 + x3) / 2,\n\t\t\tc = (x2 - x0) / 2;\n\n\t\treturn (((a * frac) + b) * frac + c) * frac + x1;\n\t}\n\n\t// First input will be downmixed to mono if number of channels is not 2\n\t// Outputs Stereo.\n\tprocess(inputs, outputs, parameters) {\n\t\tconst pd = ~~parameters.preDelay[0],\n\t\t\t// bw = parameters.bandwidth[0], // replaced by using damping\n\t\t\tfi = parameters.inputDiffusion1[0],\n\t\t\tsi = parameters.inputDiffusion2[0],\n\t\t\tdc = parameters.decay[0],\n\t\t\tft = parameters.decayDiffusion1[0],\n\t\t\tst = parameters.decayDiffusion2[0],\n\t\t\tdp = 1 - parameters.damping[0],\n\t\t\tex = parameters.excursionRate[0] / sampleRate,\n\t\t\ted = parameters.excursionDepth[0] * sampleRate / 1000,\n\t\t\twe = parameters.wet[0]; //* 0.6, // lo & ro both mult. by 0.6 anyways\n\t\t\t// dr = parameters.dry[0];\n\n\t\t// write to predelay and dry output\n\t\tif (inputs[0].length == 2) {\n\t\t\tfor (let i = 127; i >= 0; i--) {\n\t\t\t\tthis._preDelay[this._pDWrite + i] = (inputs[0][0][i] + inputs[0][1][i]) * 0.5;\n\n\t\t\t\t// removed the dry parameter, this is handled in the Tone Node\n\t\t\t\t// outputs[0][0][i] = inputs[0][0][i] * dr;\n\t\t\t\t// outputs[0][1][i] = inputs[0][1][i] * dr;\n\t\t\t}\n\t\t} else if (inputs[0].length > 0) {\n\t\t\tthis._preDelay.set(\n\t\t\t\tinputs[0][0],\n\t\t\t\tthis._pDWrite\n\t\t\t);\n\t\t\t// for (let i = 127; i >= 0; i--)\n\t\t\t// \toutputs[0][0][i] = outputs[0][1][i] = inputs[0][0][i] * dr;\n\t\t} else {\n\t\t\tthis._preDelay.set(\n\t\t\t\tnew Float32Array(128),\n\t\t\t\tthis._pDWrite\n\t\t\t);\n\t\t}\n\n\t\tlet i = 0 | 0;\n\t\twhile (i < 128) {\n\t\t\tlet lo = 0.0,\n\t\t\t\tro = 0.0;\n\n\t\t\t// input damping (formerly known as bandwidth bw, now uses dp)\n\t\t\tthis._lp1 += dp * (this._preDelay[(this._pDLength + this._pDWrite - pd + i) % this._pDLength] - this._lp1);\n\n\t\t\t// pre-tank\n\t\t\tlet pre = this.writeDelay(0, this._lp1 - fi * this.readDelay(0));\n\t\t\tpre = this.writeDelay(1, fi * (pre - this.readDelay(1)) + this.readDelay(0));\n\t\t\tpre = this.writeDelay(2, fi * pre + this.readDelay(1) - si * this.readDelay(2));\n\t\t\tpre = this.writeDelay(3, si * (pre - this.readDelay(3)) + this.readDelay(2));\n\n\t\t\tlet split = si * pre + this.readDelay(3);\n\n\t\t\t// excursions\n\t\t\t// could be optimized?\n\t\t\tlet exc = ed * (1 + Math.cos(this._excPhase * 6.2800));\n\t\t\tlet exc2 = ed * (1 + Math.sin(this._excPhase * 6.2847));\n\n\t\t\t// left loop\n\t\t\t// tank diffuse 1\n\t\t\tlet temp = this.writeDelay(4, split + dc * this.readDelay(11) + ft * this.readDelayCAt(4, exc));\n\t\t\t// long delay 1\n\t\t\tthis.writeDelay(5, this.readDelayCAt(4, exc) - ft * temp);\n\t\t\t// damp 1\n\t\t\tthis._lp2 += dp * (this.readDelay(5) - this._lp2);\n\t\t\ttemp = this.writeDelay(6, dc * this._lp2 - st * this.readDelay(6)); // tank diffuse 2\n\t\t\t// long delay 2\n\t\t\tthis.writeDelay(7, this.readDelay(6) + st * temp);\n\n\t\t\t// right loop \n\t\t\t// tank diffuse 3\n\t\t\ttemp = this.writeDelay(8, split + dc * this.readDelay(7) + ft * this.readDelayCAt(8, exc2));\n\t\t\t// long delay 3\n\t\t\tthis.writeDelay(9, this.readDelayCAt(8, exc2) - ft * temp);\n\t\t\t// damp 2\n\t\t\tthis._lp3 += dp * (this.readDelay(9) - this._lp3);\n\t\t\t// tank diffuse 4\n\t\t\ttemp = this.writeDelay(10, dc * this._lp3 - st * this.readDelay(10));\n\t\t\t// long delay 4\n\t\t\tthis.writeDelay(11, this.readDelay(10) + st * temp);\n\n\t\t\tlo = this.readDelayAt(9, this._taps[0]) +\n\t\t\t\tthis.readDelayAt(9, this._taps[1]) -\n\t\t\t\tthis.readDelayAt(10, this._taps[2]) +\n\t\t\t\tthis.readDelayAt(11, this._taps[3]) -\n\t\t\t\tthis.readDelayAt(5, this._taps[4]) -\n\t\t\t\tthis.readDelayAt(6, this._taps[5]) -\n\t\t\t\tthis.readDelayAt(7, this._taps[6]);\n\n\t\t\tro = this.readDelayAt(5, this._taps[7]) +\n\t\t\t\tthis.readDelayAt(5, this._taps[8]) -\n\t\t\t\tthis.readDelayAt(6, this._taps[9]) +\n\t\t\t\tthis.readDelayAt(7, this._taps[10]) -\n\t\t\t\tthis.readDelayAt(9, this._taps[11]) -\n\t\t\t\tthis.readDelayAt(10, this._taps[12]) -\n\t\t\t\tthis.readDelayAt(11, this._taps[13]);\n\n\t\t\toutputs[0][0][i] += lo * we;\n\t\t\toutputs[0][1][i] += ro * we;\n\n\t\t\tthis._excPhase += ex;\n\n\t\t\ti++;\n\n\t\t\tfor (let j = 0, d = this._Delays[0]; j < this._Delays.length; d = this._Delays[++j]) {\n\t\t\t\td[1] = (d[1] + 1) & d[3];\n\t\t\t\td[2] = (d[2] + 1) & d[3];\n\t\t\t}\n\t\t}\n\n\t\t// Update preDelay index\n\t\tthis._pDWrite = (this._pDWrite + 128) % this._pDLength;\n\n\t\treturn true;\n\t}\n}\nregisterProcessor('dattorro-reverb', DattorroReverb);\n";
18347
18713
  Tone.getContext().addAudioWorkletModule(URL.createObjectURL(new Blob([ fxExtensions ], { type: 'text/javascript' })));
18348
18714
 
18349
18715
  // Mercury main class controls Tone and loads samples
@@ -18608,7 +18974,7 @@ class Mercury extends MercuryInterpreter {
18608
18974
 
18609
18975
  // set highpass frequency cutoff and ramptime
18610
18976
  setHighPass(f, t=0){
18611
- this.highPass = (f === 'default')? 5 : f;
18977
+ this.highPass = (f === 'default')? 20 : f;
18612
18978
  t = Util.divToS(t, this.bpm);
18613
18979
  if (t > 0){
18614
18980
  this.highPassF.frequency.rampTo(this.highPass, t, Tone.now());