mercury-engine 1.3.1 → 1.6.0

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