mercury-engine 1.2.2 → 1.3.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
@@ -6656,6 +6656,12 @@ module.exports={
6656
6656
  "polyphonic"
6657
6657
  ],
6658
6658
 
6659
+ "program" : [
6660
+ "programChange",
6661
+ "pgm",
6662
+ "pc"
6663
+ ],
6664
+
6659
6665
  "tempo" : [
6660
6666
  "bpm"
6661
6667
  ],
@@ -7367,13 +7373,21 @@ function traverseTree(tree, code, level, obj){
7367
7373
  let inst = map['@inst'](el['@inst'], ccode);
7368
7374
  delete el['@inst'];
7369
7375
 
7376
+ // generate unique ID name for object before checking the name()
7377
+ // this ID is used for groups if there are any
7378
+ inst.functions.name = [ uniqueID(8) ];
7379
+
7370
7380
  Object.keys(el).forEach((k) => {
7371
7381
  inst = map[k](el[k], ccode, '@object', inst);
7372
7382
  });
7383
+ // add the name to the all group
7384
+ ccode.groups.all.push(inst.functions.name[0]);
7385
+
7373
7386
  // generate unique ID name for object if no name()
7374
- if (!inst.functions.name){
7375
- inst.functions.name = [ uniqueID(8) ];
7376
- }
7387
+ // if (!inst.functions.name){
7388
+ // inst.functions.name = [ uniqueID(8) ];
7389
+ // }
7390
+ // console.log('code', ccode);
7377
7391
  // add object to complete code
7378
7392
  ccode.objects[inst.functions.name] = inst;
7379
7393
  return ccode;
@@ -7391,15 +7405,27 @@ function traverseTree(tree, code, level, obj){
7391
7405
  inst = map[k](el[k], ccode, '@object', inst);
7392
7406
  });
7393
7407
  ccode.objects[inst.functions.name] = inst;
7394
- } else if (name === 'all'){
7408
+ } else if (code.groups[name]){ //name === 'all'
7395
7409
  // if set all, set all instrument objects
7396
- Object.keys(ccode.objects).forEach((o) => {
7410
+ code.groups[name].forEach((o) => {
7397
7411
  let inst = ccode.objects[o];
7398
- Object.keys(el).forEach((k) => {
7399
- inst = map[k](el[k], ccode, '@object', inst);
7400
- });
7401
- ccode.objects[inst.functions.name] = inst;
7412
+ if (inst){
7413
+ Object.keys(el).forEach((k) => {
7414
+ inst = map[k](el[k], ccode, '@object', inst);
7415
+ });
7416
+ ccode.objects[inst.functions.name] = inst;
7417
+ }
7402
7418
  });
7419
+
7420
+ // console.log(ccode.objects);
7421
+
7422
+ // Object.keys(ccode.objects).forEach((o) => {
7423
+ // let inst = ccode.objects[o];
7424
+ // Object.keys(el).forEach((k) => {
7425
+ // inst = map[k](el[k], ccode, '@object', inst);
7426
+ // });
7427
+ // ccode.objects[inst.functions.name] = inst;
7428
+ // });
7403
7429
  } else {
7404
7430
  // if name is part of global settings
7405
7431
  let args;
@@ -7507,12 +7533,16 @@ function traverseTree(tree, code, level, obj){
7507
7533
  if (func === 'add_fx'){
7508
7534
  funcs[func].push(args);
7509
7535
  } else {
7510
- if (func === 'name'){
7511
- ccode.groups.all.push(...args);
7512
- }
7513
- else if (func === 'group'){
7514
- // TO-DO:
7515
- // code for group functions
7536
+ // if (func === 'name'){
7537
+ // ccode.groups.all.push(...args);
7538
+ // } else
7539
+ if (func === 'group'){
7540
+ args.forEach((a) => {
7541
+ // add empty array if the group doesn't exist yet
7542
+ if (!ccode.groups[a]) { ccode.groups[a] = []; }
7543
+ // add the name of the inst to the group array
7544
+ ccode.groups[a].push(funcs.name[0]);
7545
+ });
7516
7546
  }
7517
7547
  funcs[func] = args;
7518
7548
  }
@@ -7710,6 +7740,18 @@ const functionMap = {
7710
7740
  'rectF' : (...v) => {
7711
7741
  return Gen.squareFloat(...v);
7712
7742
  },
7743
+ 'binaryBeat' : (...v) => {
7744
+ return Gen.binaryBeat(...v);
7745
+ },
7746
+ 'binary' : (...v) => {
7747
+ return Gen.binaryBeat(...v);
7748
+ },
7749
+ 'spacingBeat' : (...v) => {
7750
+ return Gen.spacingBeat(...v);
7751
+ },
7752
+ 'spacing' : (...v) => {
7753
+ return Gen.spacingBeat(...v);
7754
+ },
7713
7755
  //
7714
7756
  // Algorithmic Methods
7715
7757
  //
@@ -7850,6 +7892,40 @@ const functionMap = {
7850
7892
  v[1] = Math.max(2, (Array.isArray(v[1])) ? v[1][0] : v[1]);
7851
7893
  return Rand.expand(v[0], v[1]);
7852
7894
  },
7895
+ // markov chain methods
7896
+ // combine the markovTrain with markovChain
7897
+ // first train the model based on a list
7898
+ // then generate a list output from the chain
7899
+ 'markovTrain' : (...v) => {
7900
+ // generate markovchain from the incoming list
7901
+ let markov = new Rand.DeepMarkovChain(...v);
7902
+ // create string of data
7903
+ let data = markov.stringify();
7904
+ // clear data and delete
7905
+ markov.clear();
7906
+ markov = null;
7907
+ // output the table as a string array to use for generating
7908
+ return [ data ];
7909
+ },
7910
+ 'markov' : (...v) => {
7911
+ return functionMap['markovTrain'](...v);
7912
+ },
7913
+ 'markovChain' : (...v) => {
7914
+ // train from a markov table and generate a chain
7915
+ let markov = new Rand.DeepMarkovChain();
7916
+ markov.parse(v[1]);
7917
+ // set the seed based on the global seed
7918
+ markov.seed(Rand.getSeed());
7919
+ let gen = markov.chain(v[0]);
7920
+ // clear the data and remove markov
7921
+ markov.clear();
7922
+ markov = null;
7923
+ // return generated array
7924
+ return gen;
7925
+ },
7926
+ 'chain' : (...v) => {
7927
+ return functionMap['markovChain'](...v);
7928
+ },
7853
7929
  //
7854
7930
  // Transformational Methods
7855
7931
  //
@@ -8161,15 +8237,19 @@ const functionMap = {
8161
8237
  'chordsNamed' : (...v) => {
8162
8238
  return functionMap.chordsFromNames(v);
8163
8239
  },
8240
+ // translate text to ASCII
8241
+ 'textCode' : (...v) => {
8242
+ return TL.textCode(...v);
8243
+ },
8164
8244
  //
8165
8245
  // Statistic Methods
8166
8246
  //
8167
8247
  // IMPLEMENTATION NEEDED
8168
8248
  // maximum
8169
8249
  // minimum
8170
- // mean
8171
- // median
8172
- // mode
8250
+ // mean / average
8251
+ // median / center
8252
+ // mode / common
8173
8253
 
8174
8254
  //
8175
8255
  // Utility Methods
@@ -8271,7 +8351,49 @@ const functionMap = {
8271
8351
  },
8272
8352
  'ceil' : (v) => {
8273
8353
  return Util.arrayCalc(v, 0, (a) => Math.ceil(a));
8274
- }
8354
+ },
8355
+ // compare two lists for equals
8356
+ 'equals' : (...v) => {
8357
+ return Util.arrayCalc(v[0], v[1], (a,b) => Number(a === b));
8358
+ },
8359
+ 'eq' : (...v) => {
8360
+ return functionMap.equals(...v);
8361
+ },
8362
+ // compare two lists for not equal
8363
+ 'notEquals' : (...v) => {
8364
+ return Util.arrayCalc(v[0], v[1], (a,b) => Number(a !== b));
8365
+ },
8366
+ 'neq' : (...v) => {
8367
+ return functionMap.notEquals(...v);
8368
+ },
8369
+ // compare left for greater than right list
8370
+ 'greater' : (...v) => {
8371
+ return Util.arrayCalc(v[0], v[1], (a,b) => Number(a > b));
8372
+ },
8373
+ 'gt' : (...v) => {
8374
+ return functionMap.greater(...v);
8375
+ },
8376
+ // compare left for greater than or equal to right list
8377
+ 'greaterEquals' : (...v) => {
8378
+ return Util.arrayCalc(v[0], v[1], (a,b) => Number(a >= b));
8379
+ },
8380
+ 'gte' : (...v) => {
8381
+ return functionMap.greaterEquals(...v);
8382
+ },
8383
+ // compare left for less than right list
8384
+ 'less' : (...v) => {
8385
+ return Util.arrayCalc(v[0], v[1], (a,b) => Number(a < b));
8386
+ },
8387
+ 'lt' : (...v) => {
8388
+ return functionMap.less(...v);
8389
+ },
8390
+ // compare left for less than or equal to right list
8391
+ 'lessEquals' : (...v) => {
8392
+ return Util.arrayCalc(v[0], v[1], (a,b) => Number(a <= b));
8393
+ },
8394
+ 'lte' : (...v) => {
8395
+ return functionMap.lessEquals(...v);
8396
+ },
8275
8397
  }
8276
8398
  exports.functionMap = functionMap;
8277
8399
  },{"total-serialism":47}],33:[function(require,module,exports){
@@ -12190,9 +12312,11 @@ class MarkovChain {
12190
12312
  read(t){
12191
12313
  // read a markov chain table from a json file
12192
12314
  if (Array.isArray(t) || typeof t !== 'object'){
12193
- return console.error(`Error: input is not a valid json formatted table. If your input is an array use train() instead.`);
12315
+ console.error(`Error: input is not a valid json formatted table. If your input is an array use train() instead.`);
12316
+ return false;
12194
12317
  }
12195
12318
  this._table = t;
12319
+ return true;
12196
12320
  }
12197
12321
  clear(){
12198
12322
  // empty the transition probabilities
@@ -12263,18 +12387,46 @@ exports.MarkovChain = MarkovChain;
12263
12387
  // @method chain() -> generate an array of values (default length=2)
12264
12388
  //
12265
12389
  class DeepMarkov {
12266
- constructor(data){
12390
+ constructor(data, order){
12267
12391
  // transition probabilities table
12268
12392
  this._table = new Map();
12269
12393
  // train if dataset is provided
12270
- if (data) { this.train(data) };
12394
+ if (data) { this.train(data, order) };
12271
12395
  // current state of markov chain
12272
12396
  this._state = '';
12273
12397
  }
12274
12398
  get table(){
12275
- // return copy of object
12399
+ // return copy of Map object
12276
12400
  return new Map(JSON.parse(JSON.stringify(Array.from(this._table))));
12277
12401
  }
12402
+ read(t){
12403
+ // read a markov chain table from a Map() generated with DeepMarkov
12404
+ if (Array.isArray(t) || t instanceof Map === false){
12405
+ console.error(`Error: input is not a valid Map() formatted table. If your input is an array use train() instead.`);
12406
+ return false;
12407
+ }
12408
+ this._table = t;
12409
+ return true;
12410
+ }
12411
+ stringify(){
12412
+ // return stringified version of the DeepMarkov table
12413
+ return JSON.stringify(this._table, replacer);
12414
+ }
12415
+ parse(p){
12416
+ // parse an incoming string to a Map() for transition table
12417
+ try {
12418
+ let parsed = JSON.parse(p, reviver);
12419
+ if (parsed instanceof Map === false){
12420
+ console.error(`Error: input is not a valid string that can be parsed to a Map().`)
12421
+ return false;
12422
+ }
12423
+ this._table = parsed;
12424
+ return true;
12425
+ } catch (e) {
12426
+ console.error(`Error: input is not a valid string that can be parsed to a Map().`);
12427
+ return false;
12428
+ }
12429
+ }
12278
12430
  clear(){
12279
12431
  // empty the transition probabilities
12280
12432
  this._table = new Map();
@@ -12348,6 +12500,30 @@ class DeepMarkov {
12348
12500
  }
12349
12501
  exports.DeepMarkov = DeepMarkov;
12350
12502
  exports.DeepMarkovChain = DeepMarkov;
12503
+
12504
+ // functions thanks to:
12505
+ // https://stackoverflow.com/questions/29085197/how-do-you-json-stringify-an-es6-map
12506
+ // helper function for Stringifying a Map() in DeepMarkov
12507
+ function replacer(key, value) {
12508
+ if (value instanceof Map) {
12509
+ return {
12510
+ dataType: 'Map',
12511
+ value: [...value]
12512
+ // value: [Array.from(value.entries())],
12513
+ };
12514
+ }
12515
+ return value;
12516
+ }
12517
+
12518
+ // helper function for parsing a Map() in DeepMarkov
12519
+ function reviver(key, value) {
12520
+ if (typeof value === 'object' && value !== null) {
12521
+ if (value.dataType === 'Map') {
12522
+ return new Map(value.value);
12523
+ }
12524
+ }
12525
+ return value;
12526
+ }
12351
12527
  },{"./gen-basic.js":48,"./statistic":51,"./transform.js":52,"./utility":54,"seedrandom":36}],51:[function(require,module,exports){
12352
12528
  //=======================================================================
12353
12529
  // statistic.js
@@ -12545,7 +12721,11 @@ exports.diff = change;
12545
12721
  // by Timo Hoogland (@t.mo / @tmhglnd), www.timohoogland.com
12546
12722
  // MIT License
12547
12723
  //
12548
- // Basic methods that can transform number sequences
12724
+ // Methods that transform number sequences
12725
+ // These are called the "transformers"
12726
+ // A transformer always takes an input list as the first argument
12727
+ // A transformer never destructively changes the input list
12728
+ // The output of the transformer is the modified input list(s)
12549
12729
  //
12550
12730
  // TODO:
12551
12731
  // - make invert() work with note-values 'c' etc.
@@ -12561,7 +12741,7 @@ exports.diff = change;
12561
12741
  // require the Utility methods
12562
12742
  // const Rand = require('./gen-stochastic');
12563
12743
  const { sort } = require('./statistic');
12564
- const { flatten, add, max, min, lerp, toArray, size } = require('./utility');
12744
+ const { flat, add, max, min, lerp, toArray, size, unique, arrayCombinations } = require('./utility');
12565
12745
 
12566
12746
  // Duplicate an array multiple times,
12567
12747
  // optionaly add an offset to every value when duplicating
@@ -12574,11 +12754,12 @@ const { flatten, add, max, min, lerp, toArray, size } = require('./utility');
12574
12754
  //
12575
12755
  function clone(a=[0], ...c){
12576
12756
  a = toArray(a);
12577
- // flatten clone array if multi-dimensional
12578
12757
  if (!c.length) {
12758
+ // return input if no clone arguments
12579
12759
  return a;
12580
12760
  } else {
12581
- c = flatten(c);
12761
+ // flatten clone array if multi-dimensional
12762
+ c = flat(c);
12582
12763
  }
12583
12764
  let arr = [];
12584
12765
  for (let i=0; i<c.length; i++){
@@ -12594,11 +12775,11 @@ exports.clone = clone;
12594
12775
  // @params {Array0, Array1, ..., Array-n} -> Arrays to join
12595
12776
  // @return {Array}
12596
12777
  //
12597
- function combine(...args){
12598
- if (!args.length){ return [0]; }
12778
+ function combine(...arrs){
12779
+ if (!arrs.length){ return [0]; }
12599
12780
  let arr = [];
12600
- for (let i=0; i<args.length; i++){
12601
- arr = arr.concat(args[i]);
12781
+ for (let i=0; i<arrs.length; i++){
12782
+ arr = arr.concat(arrs[i]);
12602
12783
  }
12603
12784
  return arr;
12604
12785
  }
@@ -12644,8 +12825,8 @@ exports.every = every;
12644
12825
  // flatten a multidimensional array. Optionally set the depth
12645
12826
  // for the flattening
12646
12827
  //
12647
- exports.flatten = flatten;
12648
- exports.flat = flatten;
12828
+ exports.flatten = flat;
12829
+ exports.flat = flat;
12649
12830
 
12650
12831
  // similar to every(), but instead of specifying bars/divisions
12651
12832
  // this method allows you to specify the exact length of the array
@@ -12734,12 +12915,15 @@ function invert(a=[0], lo, hi){
12734
12915
  a = toArray(a);
12735
12916
 
12736
12917
  if (lo === undefined){
12918
+ // if no center value set lo/hi based on min/max
12737
12919
  hi = max(a);
12738
12920
  lo = min(a);
12739
12921
  } else if (hi === undefined){
12922
+ // if no hi defined set hi to be same as lo
12740
12923
  hi = lo;
12741
12924
  }
12742
12925
  return a.slice().map(v => {
12926
+ // apply the algorithm recursively for all items
12743
12927
  if (Array.isArray(v)){
12744
12928
  return invert(v, lo, hi);
12745
12929
  }
@@ -12753,17 +12937,19 @@ exports.invert = invert;
12753
12937
  // @param {Array0, Array1, ..., Array-n} -> arrays to interleave
12754
12938
  // @return {Array}
12755
12939
  //
12756
- function lace(...args){
12757
- if (!args.length){ return [0]; }
12940
+ function lace(...arrs){
12941
+ if (!arrs.length){ return [0]; }
12942
+ // get the length of longest list
12758
12943
  var l = 0;
12759
- for (let i=0; i<args.length; i++){
12760
- args[i] = toArray(args[i]);
12761
- l = Math.max(args[i].length, l);
12944
+ for (let i=0; i<arrs.length; i++){
12945
+ arrs[i] = toArray(arrs[i]);
12946
+ l = Math.max(arrs[i].length, l);
12762
12947
  }
12948
+ // for the max length push all values of the various lists
12763
12949
  var arr = [];
12764
12950
  for (var i=0; i<l; i++){
12765
- for (var k=0; k<args.length; k++){
12766
- let v = args[k][i];
12951
+ for (var k=0; k<arrs.length; k++){
12952
+ let v = arrs[k][i];
12767
12953
  if (v !== undefined){ arr.push(v); }
12768
12954
  }
12769
12955
  }
@@ -12785,7 +12971,8 @@ function lookup(idx=[0], arr=[0]){
12785
12971
  arr = toArray(arr);
12786
12972
  let a = [];
12787
12973
  let len = arr.length;
12788
- for (let i in idx){
12974
+ for (let i=0; i<idx.length; i++){
12975
+ // recursively lookup values for multidimensional arrays
12789
12976
  if (Array.isArray(idx[i])){
12790
12977
  a.push(lookup(idx[i], arr));
12791
12978
  } else {
@@ -12806,18 +12993,18 @@ exports.lookup = lookup;
12806
12993
  // @params {Array0, Array1, ..., Array-n} -> Arrays to merge
12807
12994
  // @return {Array}
12808
12995
  //
12809
- function merge(...args){
12810
- if (!args.length){ return [0]; }
12996
+ function merge(...arrs){
12997
+ if (!arrs.length){ return [0]; }
12811
12998
  let l = 0;
12812
- for (let i=0; i<args.length; i++){
12813
- args[i] = toArray(args[i]);
12814
- l = Math.max(args[i].length, l);
12999
+ for (let i=0; i<arrs.length; i++){
13000
+ arrs[i] = toArray(arrs[i]);
13001
+ l = Math.max(arrs[i].length, l);
12815
13002
  }
12816
13003
  let arr = [];
12817
13004
  for (let i=0; i<l; i++){
12818
13005
  let a = [];
12819
- for (let k=0; k<args.length; k++){
12820
- let v = args[k][i];
13006
+ for (let k=0; k<arrs.length; k++){
13007
+ let v = arrs[k][i];
12821
13008
  if (v !== undefined){
12822
13009
  if (Array.isArray(v)) a.push(...v);
12823
13010
  else a.push(v);
@@ -12862,7 +13049,7 @@ function repeat(arr=[0], rep=1){
12862
13049
  rep = toArray(rep);
12863
13050
 
12864
13051
  let a = [];
12865
- for (let i in arr){
13052
+ for (let i=0; i<arr.length; i++){
12866
13053
  let r = rep[i % rep.length];
12867
13054
  r = (isNaN(r) || r < 0)? 0 : r;
12868
13055
  for (let k=0; k<r; k++){
@@ -12916,7 +13103,7 @@ exports.sort = sort;
12916
13103
  // @params {Number|Array} -> slice points
12917
13104
  // @return {Array}
12918
13105
  //
12919
- function slice(a=[0], s=[1], r=true){
13106
+ function slice(a=[0], s=[0], r=true){
12920
13107
  a = toArray(a);
12921
13108
  s = toArray(s);
12922
13109
 
@@ -12930,7 +13117,9 @@ function slice(a=[0], s=[1], r=true){
12930
13117
  }
12931
13118
  }
12932
13119
  if (r){
12933
- arr.push(a.slice(_s, a.length));
13120
+ let rest = a.slice(_s, a.length);
13121
+ // attach the rest if not an empty array and r=true
13122
+ if (rest.length > 0){ arr.push(rest); }
12934
13123
  }
12935
13124
  return arr;
12936
13125
  }
@@ -12986,6 +13175,20 @@ function spray(values=[0], beats=[0]){
12986
13175
  }
12987
13176
  exports.spray = spray;
12988
13177
 
13178
+ // Alternate through 2 or multiple lists consecutively
13179
+ // Gives a similar result as lace except the output
13180
+ // length is the lowest common denominator of the input lists
13181
+ // so that every combination of consecutive values is included
13182
+ //
13183
+ // @param {Array0, Array1, ..., Array-n} -> arrays to interleave
13184
+ // @return {Array} -> array of results 1 dimension less
13185
+ //
13186
+ function step(...arrs){
13187
+ if (!arrs.length){ return [ 0 ] }
13188
+ return flat(arrayCombinations(...arrs), 1);
13189
+ }
13190
+ exports.step = step;
13191
+
12989
13192
  // stretch (or shrink) an array of numbers to a specified length
12990
13193
  // interpolating the values to fill in the gaps.
12991
13194
  // TO-DO: Interpolations options are: none, linear, cosine, cubic
@@ -13019,15 +13222,9 @@ function stretch(a=[0], len=1, mode='linear'){
13019
13222
  }
13020
13223
  exports.stretch = stretch;
13021
13224
 
13225
+ // placeholder for unique from Utils.js
13022
13226
  // filter duplicate items from an array
13023
13227
  // does not account for 2-dimensional arrays in the array
13024
- //
13025
- // @param {Array} -> array to filter
13026
- // @return {Array}
13027
- //
13028
- function unique(a=[0]){
13029
- return [...new Set(toArray(a))];
13030
- }
13031
13228
  exports.unique = unique;
13032
13229
 
13033
13230
  },{"./statistic":51,"./utility":54}],53:[function(require,module,exports){
@@ -13406,22 +13603,43 @@ exports.rtof = relativeToFreq;
13406
13603
  // Also offsets the values with the root note selected
13407
13604
  //
13408
13605
  // @params {Array/Number} -> Array of relative semitones
13606
+ // @params {String} -> Scale name (optional)
13607
+ // @params {String/Int} -> Root offset
13409
13608
  // @return {Array/Number} -> mapped to scale
13410
13609
  //
13411
- function mapToScale(a=[0]){
13412
- if (!Array.isArray(a)) {
13610
+ function mapToScale(a=[0], s, r){
13611
+ // get the global settings
13612
+ let scale = getSettings().map;
13613
+ let root = getSettings().rootInt;
13614
+ // if a scale is provided and is not undefined
13615
+ if (s && Scales[s]){
13616
+ scale = Scales[s];
13617
+ }
13618
+ // if a root is provided
13619
+ if (r) {
13620
+ root = isNaN(Number(r)) ? chromaToRelative(r) : Math.floor(r);
13621
+ }
13622
+ // apply recursively through the entire array
13623
+ return _mapToScale(a, scale, root);
13624
+ }
13625
+ exports.mapToScale = mapToScale;
13626
+ exports.toScale = mapToScale;
13627
+
13628
+ // private function for mapToScale()
13629
+ //
13630
+ function _mapToScale(arr, scale, root){
13631
+ if (!Array.isArray(arr)) {
13413
13632
  // detuning float
13414
- let d = a - Math.floor(a);
13633
+ let d = arr - Math.floor(arr);
13415
13634
  // selected semitone
13416
- let s = Math.floor(((a % 12) + 12) % 12);
13635
+ let s = Math.floor(((arr % 12) + 12) % 12);
13417
13636
  // octave offset
13418
- let o = Math.floor(a / 12) * 12;
13419
- return notation.map[s] + o + d + notation.rootInt;
13637
+ let o = Math.floor(arr / 12) * 12;
13638
+ // return notation.map[s] + o + d + notation.rootInt;
13639
+ return scale[s] + o + d + root;
13420
13640
  }
13421
- return a.map(x => mapToScale(x));
13641
+ return arr.map(x => _mapToScale(x, scale, root));
13422
13642
  }
13423
- exports.mapToScale = mapToScale;
13424
- exports.toScale = mapToScale;
13425
13643
 
13426
13644
  // Map an array of relative semitone intervals to scale and
13427
13645
  // output in specified octave as midi value
@@ -13671,6 +13889,23 @@ function timevalueRatio(x){
13671
13889
  // function tonetimeRatio(x){
13672
13890
  // }
13673
13891
 
13892
+ // Convert a string or array of strings to the
13893
+ // ASCII code values that belong to those characters
13894
+ // ASCII is the American Standard Code for Information Interchange
13895
+ //
13896
+ // @param {String/Array} -> string to convert
13897
+ // @return {Array} -> array of integers
13898
+ //
13899
+ function textToCode(a=[0]){
13900
+ if (!Array.isArray(a)){
13901
+ return String(a).split('').map(c => c.charCodeAt(0));
13902
+ }
13903
+ return a.map(x => textToCode(x));
13904
+ }
13905
+ exports.textToCode = textToCode;
13906
+ exports.textCode = textToCode;
13907
+ exports.ttoc = textToCode;
13908
+
13674
13909
  //=======================================================================
13675
13910
  // Scala class
13676
13911
  //
@@ -14231,6 +14466,31 @@ function arrayCalc(a=0, v=0, func=()=>{return a;}){
14231
14466
  }
14232
14467
  exports.arrayCalc = arrayCalc;
14233
14468
 
14469
+ // Alternate through 2 or multiple lists consecutively
14470
+ // The output length is the lowest common denominator of the input lists
14471
+ // so that every combination of consecutive values is included
14472
+ // This function is used to allow arrays as input for Generators
14473
+ // And for the step function for algorithmic composition
14474
+ //
14475
+ // @param {Array0, Array1, ..., Array-n} -> arrays to interleave
14476
+ // @return {Array} -> outputs a 2D array of the results
14477
+ //
14478
+ function arrayCombinations(...arrs){
14479
+ // make sure all values are array
14480
+ arrs = arrs.map(a => toArray(a));
14481
+ // the output is the unique list sizes multiplied
14482
+ let sizes = unique(arrs.map(a => a.length));
14483
+ let iters = 1;
14484
+ sizes.forEach((l) => iters *= l);
14485
+ // iterate over the total amount pushing the items to array
14486
+ let arr = [];
14487
+ for (let i=0; i<iters; i++){
14488
+ arr.push(arrs.map((e) => e[i % e.length] ));
14489
+ }
14490
+ return arr;
14491
+ }
14492
+ exports.arrayCombinations = arrayCombinations;
14493
+
14234
14494
  // flatten a multidimensional array. Optionally set the depth
14235
14495
  // for the flattening
14236
14496
  //
@@ -14329,6 +14589,17 @@ function signedNormalize(a=[0]){
14329
14589
  exports.signedNormalize = signedNormalize;
14330
14590
  exports.snorm = signedNormalize;
14331
14591
 
14592
+ // filter duplicate items from an array
14593
+ // does not account for 2-dimensional arrays in the array
14594
+ //
14595
+ // @param {Array} -> array to filter
14596
+ // @return {Array}
14597
+ //
14598
+ function unique(a=[0]){
14599
+ return [...new Set(toArray(a))];
14600
+ }
14601
+ exports.unique = unique;
14602
+
14332
14603
  // Plot an array of values to the console in the form of an
14333
14604
  // ascii chart and return chart from function. If you just want the
14334
14605
  // chart returned as text and not log to console set { log: false }.
@@ -14934,7 +15205,7 @@ const fxMap = {
14934
15205
  'room' : (params) => {
14935
15206
  return new Reverb(params);
14936
15207
  },
14937
- 'verb' : (params) => {
15208
+ 'hall' : (params) => {
14938
15209
  return new Reverb(params);
14939
15210
  },
14940
15211
  'reverb' : (params) => {
@@ -14970,9 +15241,9 @@ const fxMap = {
14970
15241
  'echo' : (params) => {
14971
15242
  return new Delay(params);
14972
15243
  },
14973
- 'ppDelay' : (params) => {
14974
- return new PingPongDelay(params);
14975
- },
15244
+ // 'ppDelay' : (params) => {
15245
+ // return new PingPongDelay(params);
15246
+ // },
14976
15247
  // 'freeverb' : (params) => {
14977
15248
  // return new FreeVerb(params);
14978
15249
  // },
@@ -14990,32 +15261,52 @@ module.exports = fxMap;
14990
15261
  // Programmed with a custom AudioWorkletProcessor, see effects/Processors.js
14991
15262
  //
14992
15263
  const DownSampler = function(_params){
14993
- this._down = (_params[0])? Util.toArray(_params[0]) : [0.5];
14994
-
15264
+ // apply the default values and convert to arrays where necessary
15265
+ _params = Util.mapDefaults(_params, [ 0.5, 1 ]);
15266
+ this._down = Util.toArray(_params[0]);
15267
+ this._wet = Util.toArray(_params[1]);
15268
+
14995
15269
  // ToneAudioNode has all the tone effect parameters
14996
15270
  this._fx = new Tone.ToneAudioNode();
15271
+
15272
+ // The crossfader mix
15273
+ this._mix = new Tone.Add();
15274
+ this._mixDry = new Tone.Gain(0).connect(this._mix.input);
15275
+ // this._mixWet = new Tone.Gain(0.5).connect(this._mix.addend);
15276
+
14997
15277
  // A gain node for connecting with input and output
14998
- this._fx.input = new Tone.Gain(1);
14999
- this._fx.output = new Tone.Gain(1);
15278
+ this._fx.input = new Tone.Gain(1).connect(this._mixDry);
15279
+ this._fx.output = new Tone.Gain(1).connect(this._mix.addend);
15280
+
15000
15281
  // the fx processor
15001
15282
  this._fx.workletNode = Tone.getContext().createAudioWorkletNode('downsampler-processor');
15283
+
15002
15284
  // connect input, fx and output
15003
15285
  this._fx.input.chain(this._fx.workletNode, this._fx.output);
15004
15286
 
15005
15287
  this.set = function(c, time, bpm){
15006
15288
  // some parameter mapping changing input range 0-1 to 1-inf
15007
- let p = this._fx.workletNode.parameters.get('down');
15008
- let d = Math.floor(1 / (1 - Util.clip(Util.getParam(this._down, c) ** 0.25, 0, 0.999)));
15289
+ const p = this._fx.workletNode.parameters.get('down');
15290
+ const d = Math.floor(1 / (1 - Util.clip(Util.getParam(this._down, c) ** 0.25, 0, 0.999)));
15291
+
15009
15292
  p.setValueAtTime(Util.assureNum(d), time);
15293
+
15294
+ const w = Util.clip(Util.getParam(this._wet, c), 0, 1);
15295
+ this._fx.output.gain.setValueAtTime(w, time);
15296
+ this._mixDry.gain.setValueAtTime(1 - w, time);
15010
15297
  }
15011
15298
 
15012
15299
  this.chain = function(){
15013
- return { 'send' : this._fx, 'return' : this._fx }
15300
+ return { 'send' : this._fx, 'return' : this._mix }
15014
15301
  }
15015
15302
 
15016
15303
  this.delete = function(){
15017
- this._fx.disconnect();
15018
- this._fx.dispose();
15304
+ const nodes = [ this._fx, this._mix, this._mixDry ];
15305
+
15306
+ nodes.forEach((n) => {
15307
+ n.disconnect();
15308
+ n.dispose();
15309
+ });
15019
15310
  }
15020
15311
  }
15021
15312
 
@@ -15024,37 +15315,63 @@ const DownSampler = function(_params){
15024
15315
  // distortion is applied on the overdrive parameter
15025
15316
  //
15026
15317
  const TanhDistortion = function(_params){
15027
- this._drive = (_params[0])? Util.toArray(_params[0]) : [4];
15318
+ _params = Util.mapDefaults(_params, [ 4, 1 ]);
15319
+ // apply the default values and convert to arrays where necessary
15320
+ this._drive = Util.toArray(_params[0]);
15321
+ this._wet = Util.toArray(_params[1]);
15322
+
15323
+ console.log('Distortion with:', this._drive, this._wet);
15324
+
15325
+ // The crossfader for wet-dry (originally implemented with CrossFade)
15326
+ // this._mix = new Tone.CrossFade();
15327
+ this._mix = new Tone.Add();
15328
+ this._mixWet = new Tone.Gain(0).connect(this._mix.input);
15329
+ this._mixDry = new Tone.Gain(1).connect(this._mix.addend);
15028
15330
 
15029
15331
  // ToneAudioNode has all the tone effect parameters
15030
15332
  this._fx = new Tone.ToneAudioNode();
15031
15333
  // A gain node for connecting with input and output
15032
- this._fx.input = new Tone.Gain(1);
15033
- this._fx.output = new Tone.Gain(1);
15334
+ this._fx.input = new Tone.Gain(1).connect(this._mixDry);
15335
+ this._fx.output = new Tone.Gain(1).connect(this._mixWet);
15336
+
15034
15337
  // the fx processor
15035
15338
  this._fx.workletNode = Tone.getContext().createAudioWorkletNode('tanh-distortion-processor');
15036
- // connect input, fx and output
15339
+
15340
+ // connect input, fx, output and wetdry
15037
15341
  this._fx.input.chain(this._fx.workletNode, this._fx.output);
15038
15342
 
15039
15343
  this.set = function(c, time, bpm){
15040
15344
  // drive amount, minimum drive of 1
15041
- const d = Util.assureNum(Math.max(1, Math.pow(Util.getParam(this._drive, c), 2) + 1));
15345
+ const d = Util.assureNum(Math.max(0, Util.getParam(this._drive, c)) + 1);
15346
+
15042
15347
  // preamp gain reduction for linear at drive = 1
15043
- const p = 0.4;
15348
+ const p = 0.8;
15044
15349
  // makeup gain
15045
- const m = 1.0 / p / (d ** 0.6);
15046
- // set the input gain and output gain reduction
15047
- this._fx.input.gain.setValueAtTime(p * d, time);
15048
- this._fx.output.gain.setValueAtTime(m, time);
15350
+ const m = 1.0 / (p * (d ** 1.1));
15351
+
15352
+ // set the parameters in the workletNode
15353
+ const amount = this._fx.workletNode.parameters.get('amount');
15354
+ amount.setValueAtTime(p * d * d, time);
15355
+
15356
+ const makeup = this._fx.workletNode.parameters.get('makeup');
15357
+ makeup.setValueAtTime(m, time);
15358
+
15359
+ const wet = Util.clip(Util.getParam(this._wet, c), 0, 1);
15360
+ this._mixWet.gain.setValueAtTime(wet);
15361
+ this._mixDry.gain.setValueAtTime(1 - wet);
15049
15362
  }
15050
15363
 
15051
15364
  this.chain = function(){
15052
- return { 'send' : this._fx, 'return' : this._fx }
15365
+ return { 'send' : this._fx, 'return' : this._mix }
15053
15366
  }
15054
15367
 
15055
15368
  this.delete = function(){
15056
- this._fx.disconnect();
15057
- this._fx.dispose();
15369
+ let nodes = [ this._fx, this._mix, this._mixDry, this._mixWet ];
15370
+
15371
+ nodes.forEach((n) => {
15372
+ n.disconnect();
15373
+ n.dispose();
15374
+ });
15058
15375
  }
15059
15376
  }
15060
15377
 
@@ -15064,9 +15381,7 @@ const TanhDistortion = function(_params){
15064
15381
  //
15065
15382
  const Compressor = function(_params){
15066
15383
  // replace defaults with provided params
15067
- this.defaults = [-30, 6, 10, 80];
15068
- this.defaults.splice(0, _params.length, ..._params);
15069
- _params = this.defaults.map(p => Util.toArray(p));
15384
+ _params = Util.mapDefaults(_params, [-30, 6, 10, 80]);
15070
15385
 
15071
15386
  this._fx = new Tone.Compressor({
15072
15387
  threshold: -24,
@@ -15111,7 +15426,9 @@ const Chorus = function(_params){
15111
15426
  this._fx.frequency.setValueAtTime(f, time);
15112
15427
  // delaytime/2 because of up and down through center
15113
15428
  // eg. 25 goes from 0 to 50, 40 goes from 0 to 80, etc.
15114
- this._fx.delayTime = Util.getParam(_params[1], c) / 2;
15429
+ Util.atTime(() => {
15430
+ this._fx.delayTime = Util.getParam(_params[1], c) / 2;
15431
+ }, time);
15115
15432
 
15116
15433
  // waveform for chorus is not supported in browser instead change wetdry
15117
15434
  let w = Util.getParam(_params[2], c);
@@ -15137,49 +15454,71 @@ const Chorus = function(_params){
15137
15454
  // Based on an algorithm by Peter McCulloch
15138
15455
  //
15139
15456
  const Squash = function(_params){
15140
- this._squash = (_params[0])? Util.toArray(_params[0]) : [1];
15457
+ _params = Util.mapDefaults(_params, [ 4, 1, 0.28 ]);
15458
+ // apply the default values and convert to arrays where necessary
15459
+ this._squash = Util.toArray(_params[0]);
15460
+ this._wet = Util.toArray(_params[1]);
15461
+
15462
+ // The crossfader for wet-dry (originally implemented with CrossFade)
15463
+ this._mix = new Tone.Add();
15464
+ this._mixDry = new Tone.Gain(0).connect(this._mix.input);
15141
15465
 
15142
15466
  // ToneAudioNode has all the tone effect parameters
15143
15467
  this._fx = new Tone.ToneAudioNode();
15144
15468
  // A gain node for connecting with input and output
15145
- this._fx.input = new Tone.Gain(1);
15146
- this._fx.output = new Tone.Gain(1);
15469
+ this._fx.input = new Tone.Gain(1).connect(this._mixDry);
15470
+ this._fx.output = new Tone.Gain(1).connect(this._mix.addend);
15471
+
15147
15472
  // the fx processor
15148
15473
  this._fx.workletNode = Tone.getContext().createAudioWorkletNode('squash-processor');
15149
15474
  // connect input, fx and output
15150
15475
  this._fx.input.chain(this._fx.workletNode, this._fx.output);
15151
15476
 
15152
15477
  this.set = function(c, time, bpm){
15153
- let d = Util.assureNum(Math.max(1, Util.getParam(this._squash, c)));
15154
- let p = this._fx.workletNode.parameters.get('amount');
15155
- let m = 1.0 / Math.sqrt(d);
15156
- p.setValueAtTime(d, time);
15478
+ const d = Util.assureNum(Math.max(1, Util.getParam(this._squash, c)));
15479
+ const m = 1.0 / Math.sqrt(d);
15480
+
15481
+ const amount = this._fx.workletNode.parameters.get('amount');
15482
+ amount.setValueAtTime(d, time);
15483
+
15484
+ const makeup = this._fx.workletNode.parameters.get('makeup');
15485
+ makeup.setValueAtTime(m, time);
15486
+
15487
+ const wet = Util.clip(Util.getParam(this._wet, c));
15157
15488
  this._fx.output.gain.setValueAtTime(m, time);
15489
+ this._mixDry.gain.setValueAtTime(1 - wet, time);
15158
15490
  }
15159
15491
 
15160
15492
  this.chain = function(){
15161
- return { 'send' : this._fx, 'return' : this._fx }
15493
+ return { 'send' : this._fx, 'return' : this._mix }
15162
15494
  }
15163
15495
 
15164
15496
  this.delete = function(){
15165
- this._fx.disconnect();
15166
- this._fx.dispose();
15497
+ let nodes = [ this._fx, this._mix, this._mixDry ];
15498
+
15499
+ nodes.forEach((n) => {
15500
+ n.disconnect();
15501
+ n.dispose();
15502
+ });
15167
15503
  }
15168
15504
  }
15169
15505
 
15170
15506
  // Reverb FX
15171
15507
  // Add a reverb to the sound to give it a feel of space
15508
+ // Using a decaying noise reverb algorithm, that does seem to
15509
+ // increase the memory usage over time slowly
15172
15510
  //
15173
15511
  const Reverb = function(_params){
15174
- this._fx = new Tone.Reverb();
15512
+ _params = Util.mapDefaults(_params, [ 0.5, 1.5 ]);
15513
+ this._wet = _params[0];
15514
+ this._size = _params[1];
15175
15515
 
15176
- this._wet = (_params[0] !== undefined)? Util.toArray(_params[0]) : [ 0.5 ];
15177
- this._size = (_params[1] !== undefined)? Util.toArray(_params[1]) : [ 1.5 ];
15516
+ this._fx = new Tone.Reverb();
15178
15517
 
15179
15518
  this.set = function(c, time){
15180
- let tmp = Math.min(10, Math.max(0.1, Util.getParam(this._size, c)));
15519
+ let tmp = Math.min(15, Math.max(0.1, Util.getParam(this._size, c)));
15181
15520
  if (this._fx.decay != tmp){
15182
- this._fx.decay = tmp;
15521
+ Util.atTime(() => this._fx.decay = tmp, time);
15183
15522
  }
15184
15523
 
15185
15524
  let wet = Math.min(1, Math.max(0, Util.getParam(this._wet, c)));
@@ -15198,19 +15537,21 @@ const Reverb = function(_params){
15198
15537
 
15199
15538
  // PitchShift FX
15200
15539
  // Shift the pitch up or down with semitones
15540
+ // Utilizes the default PitchShift FX from ToneJS
15201
15541
  //
15202
15542
  const PitchShift = function(_params){
15203
- this._fx = new Tone.PitchShift();
15543
+ _params = Util.mapDefaults(_params, [ -12, 1 ]);
15544
+ // apply the default values and convert to arrays where necessary
15545
+ this._pitch = _params[0];
15546
+ this._wet = _params[1];
15204
15547
 
15205
- this._pitch = (_params[0] !== undefined)? Util.toArray(_params[0]) : [-12];
15206
- this._wet = (_params[1] !== undefined)? Util.toArray(_params[1]) : [1];
15548
+ this._fx = new Tone.PitchShift();
15207
15549
 
15208
15550
  this.set = function(c, time){
15209
15551
  let p = Util.getParam(this._pitch, c);
15210
15552
  let w = Util.getParam(this._wet, c);
15211
15553
 
15212
- this._fx.pitch = TL.toScale(p);
15213
- // this._fx.pitch = p;
15554
+ Util.atTime(() => this._fx.pitch = TL.toScale(p), time);
15214
15555
  this._fx.wet.setValueAtTime(w, time);
15215
15556
  }
15216
15557
 
@@ -15228,47 +15569,74 @@ const PitchShift = function(_params){
15228
15569
  // a Low Frequency Oscillator effect, control tempo, type and depth
15229
15570
  //
15230
15571
  const LFO = function(_params){
15572
+ _params = Util.mapDefaults(_params, [ '1/8', 'sine', 1 ]);
15573
+ _params = _params.map(x => Util.toArray(x));
15574
+ this._speed = _params[0];
15575
+ this._type = _params[1];
15576
+ this._depth = _params[2];
15577
+
15231
15578
  this._waveMap = {
15232
15579
  sine : 'sine',
15233
- saw : 'sawtooth',
15580
+ sineUp : 'sine',
15581
+ sineDown : 'sine',
15582
+ sawUp: 'sawtooth',
15583
+ sawDown: 'sawtooth',
15584
+ up: 'sawtooth',
15585
+ down: 'sawtooth',
15234
15586
  square : 'square',
15587
+ squareUp : 'square',
15588
+ squareDown : 'square',
15235
15589
  rect : 'square',
15236
15590
  triangle : 'triangle',
15237
15591
  tri : 'triangle',
15238
- up: 'sawtooth',
15239
- sawUp: 'sawtooth'
15240
15592
  }
15241
15593
 
15242
15594
  this._lfo = new Tone.LFO();
15243
15595
  this._fx = new Tone.Gain();
15244
15596
  this._lfo.connect(this._fx.gain);
15245
- // this._fx = new Tone.Tremolo('8n').start();
15246
-
15247
- this._speed = (_params[0]) ? Util.toArray(_params[0]) : ['1/8'];
15248
- this._type = (_params[1]) ? Util.toArray(_params[1]) : ['sine'];
15249
- this._depth = (_params[2] !== undefined) ? Util.toArray(_params[2]) : [ 1 ];
15250
15597
 
15251
15598
  this.set = function(c, time, bpm){
15252
15599
  let w = Util.getParam(this._type, c);
15253
- if (this._waveMap[w]){
15254
- w = this._waveMap[w];
15255
- } else {
15600
+ if (!this._waveMap[w]){
15256
15601
  Util.log(`'${w} is not a valid waveshape`);
15257
15602
  // default wave if wave does not exist
15258
15603
  w = 'sine';
15259
15604
  }
15260
- this._lfo.set({ type: w });
15605
+ this._lfo.set({ type: this._waveMap[w] });
15261
15606
 
15262
15607
  let s = Util.getParam(this._speed, c);
15263
- let f = Math.max(0.0001, Util.divToS(s, bpm));
15608
+ let t = Util.divToS(s, bpm);
15609
+ let f = Math.max(0.0001, t);
15264
15610
  this._lfo.frequency.setValueAtTime(1/f, time);
15265
15611
 
15266
15612
  let a = Util.getParam(this._depth, c);
15267
- this._lfo.min = Math.min(1, Math.max(0, 1 - a));
15268
- if (this._lfo.state !== 'started'){
15269
- if (w === 'sawtooth') {
15270
- this._lfo.phase = 180;
15613
+ Util.atTime(() => {
15614
+ this._lfo.min = Math.min(1, Math.max(0, 1 - a));
15615
+ this._lfo.max = 1;
15616
+ // fix for squarewave not going to 0 fully
15617
+ if (this._waveMap[w] === 'square'){
15618
+ this._lfo.min += -0.1;
15271
15619
  }
15620
+
15621
+ // swap high and low point to create a saw down
15622
+ if (w === 'down' || w === 'sawDown' || w === 'squareUp' || w === 'sineDown' ){
15623
+ let tmp = this._lfo.min;
15624
+ this._lfo.min = this._lfo.max;
15625
+ this._lfo.max = tmp;
15626
+ }
15627
+ }, time);
15628
+
15629
+ if (this._lfo.state !== 'started'){
15630
+ // fix incorrect phases for sawtooth sine and triangle
15631
+ // simply by starting them a bit later.
15632
+ switch (this._waveMap[w]) {
15633
+ case 'sine' :
15634
+ time += t * 0.25; break;
15635
+ case 'triangle' :
15636
+ time += t * 0.25; break;
15637
+ case 'sawtooth' :
15638
+ time += t * 0.5; break;
15639
+ }
15272
15640
  this._lfo.start(time);
15273
15641
  }
15274
15642
  }
@@ -15278,9 +15646,9 @@ const LFO = function(_params){
15278
15646
  }
15279
15647
 
15280
15648
  this.delete = function(){
15281
- let blocks = [ this._fx, this._lfo ];
15649
+ let nodes = [ this._fx, this._lfo ];
15282
15650
 
15283
- blocks.forEach((b) => {
15651
+ nodes.forEach((b) => {
15284
15652
  b.disconnect();
15285
15653
  b.dispose();
15286
15654
  });
@@ -15289,10 +15657,35 @@ const LFO = function(_params){
15289
15657
 
15290
15658
  // A filter FX, choose between highpass, lowpass and bandpass
15291
15659
  // Set the cutoff frequency and Q factor
15660
+ // Optionally with extra arguments you can apply a modulation
15292
15661
  //
15293
15662
  const Filter = function(_params){
15663
+ // parameter mapping changes based on amount of arguments
15664
+ this._static = true;
15665
+ if (_params.length < 4){
15666
+ if (typeof _params[0] === 'string'){
15667
+ _params = Util.mapDefaults(_params, ['low', 1200, 0.45]);
15668
+ } else {
15669
+ _params = [['low']].concat(Util.mapDefaults(_params, [1200, 0.45]));
15670
+ }
15671
+ }
15672
+ else {
15673
+ _params = Util.mapDefaults(_params, ['low', '1/1', 200, 3000, 0.45, 'sine', 0.5]);
15674
+ this._static = false;
15675
+ }
15676
+
15294
15677
  this._fx = new Tone.Filter();
15295
15678
 
15679
+ // the following is only used if the parameters for modulation
15680
+ // are added as arguments to the fx(filter) function
15681
+ if (!this._static){
15682
+ this._lfo = new Tone.LFO();
15683
+ this._scale = new Tone.ScaleExp();
15684
+ this._lfo.connect(this._scale);
15685
+ this._scale.connect(this._fx.frequency);
15686
+ }
15687
+
15688
+ // available filter types for the filter
15296
15689
  this._types = {
15297
15690
  'lo' : 'lowpass',
15298
15691
  'low' : 'lowpass',
@@ -15301,32 +15694,107 @@ const Filter = function(_params){
15301
15694
  'high' : 'highpass',
15302
15695
  'highpass' : 'highpass',
15303
15696
  'band' : 'bandpass',
15304
- 'bandpass': 'bandpass'
15697
+ 'bandpass': 'bandpass',
15305
15698
  }
15306
15699
  if (this._types[_params[0]]){
15307
15700
  this._fx.set({ type: this._types[_params[0]] });
15308
15701
  } else {
15309
- Util.log(`'${_params[0]}' is not a valid filter type`);
15702
+ console.log(`'${_params[0]}' is not a valid filter type. Defaults to lowpass`);
15310
15703
  this._fx.set({ type: 'lowpass' });
15311
15704
  }
15312
15705
  this._fx.set({ rolloff: -24 });
15313
15706
 
15314
- this._cutoff = (_params[1]) ? Util.toArray(_params[1]) : [ 1000 ];
15315
- this._q = (_params[2]) ? Util.toArray(_params[2]) : [ 0.5 ];
15316
- this._rt = (_params[3]) ? Util.toArray(_params[3]) : [ 0 ];
15707
+ // available waveforms for the LFO
15708
+ this._waveMap = {
15709
+ sine : 'sine',
15710
+ // sineUp : 'sine',
15711
+ // sineDown : 'sine',
15712
+ saw : 'sawtooth',
15713
+ sawUp: 'sawtooth',
15714
+ sawDown: 'sawtooth',
15715
+ up: 'sawtooth',
15716
+ down: 'sawtooth',
15717
+ // square : 'square',
15718
+ // squareUp : 'square',
15719
+ // squareDown : 'square',
15720
+ // rect : 'square',
15721
+ triangle : 'triangle',
15722
+ tri : 'triangle',
15723
+ }
15317
15724
 
15318
15725
  this.set = function(c, time, bpm){
15319
- let f = Util.getParam(this._cutoff, c);
15320
- let r = 1 / (1 - Math.min(0.95, Math.max(0, Util.getParam(this._q, c))));
15321
- let rt = Util.divToS(Util.getParam(this._rt, c), bpm);
15726
+ let _q;
15727
+ // if the filter is static use the settings of frequency and resonance
15728
+ if (this._static){
15729
+ let f = Util.getParam(_params[1], c);
15730
+ _q = _params[2];
15322
15731
 
15323
- if (rt > 0){
15324
- this._fx.frequency.rampTo(f, rt, time);
15325
- } else {
15326
15732
  this._fx.frequency.setValueAtTime(f, time);
15733
+ // let rt = Util.divToS(Util.getParam(this._rt, c), bpm);
15734
+ } else {
15735
+ _q = _params[4];
15736
+ let t = Util.divToS(Util.getParam(_params[1], c), bpm);
15737
+ let f = 1 / t;
15738
+ let lo = Util.clip(Util.getParam(_params[2], c), 5, 19000);
15739
+ let hi = Util.clip(Util.getParam(_params[3], c), 5, 19000);
15740
+
15741
+ let w = Util.getParam(_params[5], c);
15742
+ if (this._waveMap[w]){
15743
+ w = this._waveMap[w];
15744
+ } else {
15745
+ if (isNaN(w)){
15746
+ log(`${w} is not a valid waveshape. Defaults to sine`);
15747
+ // default wave if wave does not exist
15748
+ w = 'sine';
15749
+ } else {
15750
+ // w = value between 0 and 1, map to up, down, triangle
15751
+ // 0=down, 0.5=triangle, 1=up
15752
+ switch(Math.floor(Util.clip(w, 0, 1)*2.99)){
15753
+ case 0:
15754
+ // regular saw up
15755
+ w = 'sawtooth'; break;
15756
+ case 1:
15757
+ w = 'sine'; break;
15758
+ case 2:
15759
+ w = 'sawtooth';
15760
+ // swap hi/lo range for saw down effect
15761
+ let tmp = lo; lo = hi; hi = tmp; break;
15762
+ }
15763
+ }
15764
+ }
15765
+ this._lfo.set({ type: w });
15766
+
15767
+ let exp = Util.clip(Util.getParam(_params[6], c), 0.01, 100);
15768
+
15769
+ this._scale.min = lo;
15770
+ this._scale.max = hi;
15771
+ this._scale.exponent = exp;
15772
+ this._lfo.frequency.setValueAtTime(f, time);
15773
+
15774
+ this._lfo.max = 1;
15775
+
15776
+ if (this._lfo.state !== 'started'){
15777
+ switch (w) {
15778
+ case 'sine' :
15779
+ time += t * 0.25; break;
15780
+ case 'triangle' :
15781
+ time += t * 0.25; break;
15782
+ case 'sawtooth' :
15783
+ time += t * 0.5; break;
15784
+ }
15785
+ this._lfo.start(time);
15786
+ }
15327
15787
  }
15328
15788
 
15789
+ let r = 1 / (1 - Math.min(0.95, Math.max(0, Util.getParam(_q, c))));
15329
15790
  this._fx.Q.setValueAtTime(r, time);
15791
+
15792
+ // ramptime removed now that modulation is possible
15793
+ // if (rt > 0){
15794
+ // this._fx.frequency.rampTo(f, rt, time);
15795
+ // } else {
15796
+ // this._fx.frequency.setValueAtTime(f, time);
15797
+ // }
15330
15798
  }
15331
15799
 
15332
15800
  this.chain = function(){
@@ -15334,8 +15802,12 @@ const Filter = function(_params){
15334
15802
  }
15335
15803
 
15336
15804
  this.delete = function(){
15337
- this._fx.disconnect();
15338
- this._fx.dispose();
15805
+ let nodes = [ this._fx, this._lfo, this._scale ];
15806
+
15807
+ nodes.forEach((n) => {
15808
+ n?.disconnect();
15809
+ n?.dispose();
15810
+ });
15339
15811
  }
15340
15812
  }
15341
15813
 
@@ -15372,10 +15844,10 @@ const TriggerFilter = function(_params){
15372
15844
  'bandpass': 'bandpass'
15373
15845
  }
15374
15846
 
15375
- this.defaults = ['low', 1, '1/16', 4000, 30];
15376
15847
  // replace defaults with provided arguments
15377
- this.defaults.splice(0, _params.length, ..._params);
15378
- _params = this.defaults.map(p => Util.toArray(p));
15848
+ _params = Util.mapDefaults(_params, ['low', 1, '1/16', 4000, 100, 1]);
15849
+ // this.defaults.splice(0, _params.length, ..._params);
15850
+ _params = _params.map(p => Util.toArray(p));
15379
15851
 
15380
15852
  if (this._types[_params[0][0]]){
15381
15853
  this._fx.set({ type: this._types[_params[0][0]] });
@@ -15388,6 +15860,7 @@ const TriggerFilter = function(_params){
15388
15860
  this._rel = _params[2];
15389
15861
  this._high = _params[3];
15390
15862
  this._low = _params[4];
15863
+ this._exp = _params[5];
15391
15864
 
15392
15865
  this.set = function(c, time, bpm){
15393
15866
  this._adsr.attack = Util.divToS(Util.getParam(this._att, c), bpm);
@@ -15397,9 +15870,11 @@ const TriggerFilter = function(_params){
15397
15870
  let max = Util.getParam(this._high, c);
15398
15871
  let range = Math.abs(max - min);
15399
15872
  let lower = Math.min(max, min);
15873
+ let exp = 1 / Util.getParam(this._exp, c);
15400
15874
 
15401
15875
  this._mul.setValueAtTime(range, time);
15402
15876
  this._add.setValueAtTime(lower, time);
15877
+ Util.atTime(() => { this._pow.value = exp }, time);
15403
15878
 
15404
15879
  // fade-out running envelope over 5 ms
15405
15880
  if (this._adsr.value > 0){
@@ -15414,9 +15889,9 @@ const TriggerFilter = function(_params){
15414
15889
  }
15415
15890
 
15416
15891
  this.delete = function(){
15417
- let blocks = [ this._fx, this._adsr, this._mul, this._add, this._pow ];
15892
+ let nodes = [ this._fx, this._adsr, this._mul, this._add, this._pow ];
15418
15893
 
15419
- blocks.forEach((b) => {
15894
+ nodes.forEach((b) => {
15420
15895
  b.disconnect();
15421
15896
  b.dispose();
15422
15897
  });
@@ -15444,27 +15919,31 @@ const TriggerFilter = function(_params){
15444
15919
 
15445
15920
  // Custom stereo delay implementation with lowpass filter in feedback loop
15446
15921
  const Delay = function(_params){
15922
+ // apply the default values and convert to arrays where necessary
15923
+ if (_params.length === 1){ _params[1] = _params[0] }
15924
+ else if (_params.length === 2){
15925
+ _params[2] = _params[1];
15926
+ _params[1] = _params[0];
15927
+ }
15928
+
15929
+ _params = Util.mapDefaults(_params, [ '3/16', '2/8', 0.7, 0.6, 0.5 ]);
15930
+ this._timeL = Util.toArray(_params[0]);
15931
+ this._timeR = Util.toArray(_params[1]);
15932
+ this._feedBack = Util.toArray(_params[2]);
15933
+ this._fbDamp = Util.toArray(_params[3]);
15934
+ this._wet = Util.toArray(_params[4]);
15935
+
15447
15936
  this._fx = new Tone.Gain(1);
15448
15937
  this._fb = new Tone.Gain(0.5);
15449
15938
  this._mix = new Tone.CrossFade(0.5);
15450
15939
  this._split = new Tone.Split(2);
15451
15940
  this._merge = new Tone.Merge(2);
15452
- this._maxDelay = 5;
15941
+ this._maxDelay = 3;
15453
15942
 
15454
15943
  this._delayL = new Tone.Delay({ maxDelay: this._maxDelay });
15455
15944
  this._delayR = new Tone.Delay({ maxDelay: this._maxDelay });
15456
15945
  this._flt = new Tone.Filter(1000, 'lowpass', '-12');
15457
15946
 
15458
- if (_params.length === 2){
15459
- _params[2] = _params[1];
15460
- _params[1] = _params[0];
15461
- }
15462
- // All params and defaults
15463
- this._timeL = (_params[0] !== undefined)? Util.toArray(_params[0]) : [ '2/16' ];
15464
- this._timeR = (_params[1] !== undefined)? Util.toArray(_params[1]) : [ '3/16' ];
15465
- this._feedBack = (_params[2] !== undefined)? Util.toArray(_params[2]) : [ 0.7 ];
15466
- this._fbDamp = (_params[3] !== undefined)? Util.toArray(_params[3]) : [ 0.6 ];
15467
-
15468
15947
  // split the signal
15469
15948
  this._fx.connect(this._mix.a);
15470
15949
  this._fx.connect(this._fb);
@@ -15487,13 +15966,16 @@ const Delay = function(_params){
15487
15966
  this.set = function(c, time, bpm){
15488
15967
  let dL = Math.min(this._maxDelay, Math.max(0, Util.formatRatio(Util.getParam(this._timeL, c), bpm)));
15489
15968
  let dR = Math.min(this._maxDelay, Math.max(0, Util.formatRatio(Util.getParam(this._timeR, c), bpm)));
15490
- let ct = Math.max(10, Util.getParam(this._fbDamp, c) * 5000);
15491
15969
  let fb = Math.max(0, Math.min(0.99, Util.getParam(this._feedBack, c) * 0.707));
15970
+ let cf = Math.max(10, Util.getParam(this._fbDamp, c) * 8000);
15971
+
15972
+ this._delayL.delayTime.setValueAtTime(dL + Math.random() * 0.001, time);
15973
+ this._delayR.delayTime.setValueAtTime(dR + Math.random() * 0.001, time);
15974
+ this._fb.gain.setValueAtTime(Util.assureNum(fb, 0.7), time);
15975
+ this._flt.frequency.setValueAtTime(cf, time);
15492
15976
 
15493
- this._delayL.delayTime.setValueAtTime(dL, time);
15494
- this._delayR.delayTime.setValueAtTime(dR, time);
15495
- this._flt.frequency.setValueAtTime(ct, time);
15496
- this._fb.gain.setValueAtTime(fb, time);
15977
+ const wet = Util.clip(Util.getParam(this._wet, c));
15978
+ this._mix.fade.setValueAtTime(wet, time);
15497
15979
  }
15498
15980
 
15499
15981
  this.chain = function(){
@@ -15501,9 +15983,9 @@ const Delay = function(_params){
15501
15983
  }
15502
15984
 
15503
15985
  this.delete = function(){
15504
- let blocks = [ this._fx, this._fb, this._mix, this._split, this._merge, this._delayL, this._delayR, this._flt ];
15986
+ let nodes = [ this._fx, this._fb, this._mix, this._split, this._merge, this._delayL, this._delayR, this._flt ];
15505
15987
 
15506
- blocks.forEach((b) => {
15988
+ nodes.forEach((b) => {
15507
15989
  b.disconnect();
15508
15990
  b.dispose();
15509
15991
  });
@@ -15511,124 +15993,124 @@ const Delay = function(_params){
15511
15993
  }
15512
15994
 
15513
15995
  // Old pingpong delay implementation, just using the Tone.PingPongDelay()
15514
- const PingPongDelay = function(_params){
15515
- this._fx = new Tone.PingPongDelay();
15516
- this._fx.set({ wet: 0.4 });
15517
-
15518
- // console.log('delay', param);
15519
- this._dTime = (_params[0] !== undefined)? Util.toArray(_params[0]) : [ '3/16' ];
15520
- this._fb = (_params[1] !== undefined)? Util.toArray(_params[1]) : [ 0.3 ];
15521
- // let del = new Tone.PingPongDelay(formatRatio(t), fb);
15522
-
15523
- this.set = function(c, time, bpm){
15524
- let t = Math.max(0, Util.formatRatio(Util.getParam(this._dTime, c), bpm));
15525
- let fb = Math.max(0, Math.min(0.99, Util.getParam(this._fb, c)));
15526
-
15527
- this._fx.delayTime.setValueAtTime(t, time);
15528
- this._fx.feedback.setValueAtTime(fb, time);
15529
- }
15530
-
15531
- this.chain = function(){
15532
- return { 'send' : this._fx, 'return' : this._fx };
15533
- }
15534
-
15535
- this.delete = function(){
15536
- this._fx.disconnect();
15537
- this._fx.dispose();
15538
- }
15539
- }
15996
+ // const PingPongDelay = function(_params){
15997
+ // this._fx = new Tone.PingPongDelay();
15998
+ // this._fx.set({ wet: 0.4 });
15999
+
16000
+ // // console.log('delay', param);
16001
+ // this._dTime = (_params[0] !== undefined)? Util.toArray(_params[0]) : [ '3/16' ];
16002
+ // this._fb = (_params[1] !== undefined)? Util.toArray(_params[1]) : [ 0.3 ];
16003
+ // // let del = new Tone.PingPongDelay(formatRatio(t), fb);
16004
+
16005
+ // this.set = function(c, time, bpm){
16006
+ // let t = Math.max(0, Util.formatRatio(Util.getParam(this._dTime, c), bpm));
16007
+ // let fb = Math.max(0, Math.min(0.99, Util.getParam(this._fb, c)));
16008
+
16009
+ // this._fx.delayTime.setValueAtTime(t, time);
16010
+ // this._fx.feedback.setValueAtTime(fb, time);
16011
+ // }
16012
+
16013
+ // this.chain = function(){
16014
+ // return { 'send' : this._fx, 'return' : this._fx };
16015
+ // }
16016
+
16017
+ // this.delete = function(){
16018
+ // this._fx.disconnect();
16019
+ // this._fx.dispose();
16020
+ // }
16021
+ // }
15540
16022
 
15541
- const FreeVerb = function(_params){
15542
- this._fx = new Tone.Freeverb(_params[0], _params[1]);
16023
+ // const FreeVerb = function(_params){
16024
+ // this._fx = new Tone.Freeverb(_params[0], _params[1]);
15543
16025
 
15544
- this.set = function(c, time, bpm){
16026
+ // this.set = function(c, time, bpm){
15545
16027
 
15546
- }
16028
+ // }
15547
16029
 
15548
- this.chain = function(){
15549
- return { 'send' : this._fx, 'return' : this._fx };
15550
- }
16030
+ // this.chain = function(){
16031
+ // return { 'send' : this._fx, 'return' : this._fx };
16032
+ // }
15551
16033
 
15552
- this.delete = function(){
15553
- let blocks = [ this._fx ];
16034
+ // this.delete = function(){
16035
+ // let nodes = [ this._fx ];
15554
16036
 
15555
- blocks.forEach((b) => {
15556
- b.disconnect();
15557
- b.dispose();
15558
- });
15559
- }
15560
- }
16037
+ // nodes.forEach((b) => {
16038
+ // b.disconnect();
16039
+ // b.dispose();
16040
+ // });
16041
+ // }
16042
+ // }
15561
16043
 
15562
16044
  // squash/compress an incoming signal
15563
16045
  // based on algorithm by Peter McCulloch
15564
- const SquashDeprecated = function(_params){
15565
- this._compress = (_params[0] !== undefined)? Util.toArray(_params[0]) : [1];
15566
-
15567
- this._fx = new Tone.WaveShaper();
15568
-
15569
- this.shaper = function(amount){
15570
- // (a * c) / ((a * c)^2 * 0.28 + 1) / √c
15571
- // drive amount, minimum of 1
15572
- const c = amount;
15573
- // makeup gain
15574
- const m = 1.0 / Math.sqrt(c);
15575
- // set the waveshaper effect
15576
- this._fx.setMap((x) => {
15577
- return (x * c) / ((x * c) * (x * c) * 0.28 + 1) * m;
15578
- });
15579
- }
16046
+ // const SquashDeprecated = function(_params){
16047
+ // this._compress = (_params[0] !== undefined)? Util.toArray(_params[0]) : [1];
16048
+
16049
+ // this._fx = new Tone.WaveShaper();
16050
+
16051
+ // this.shaper = function(amount){
16052
+ // // (a * c) / ((a * c)^2 * 0.28 + 1) / √c
16053
+ // // drive amount, minimum of 1
16054
+ // const c = amount;
16055
+ // // makeup gain
16056
+ // const m = 1.0 / Math.sqrt(c);
16057
+ // // set the waveshaper effect
16058
+ // this._fx.setMap((x) => {
16059
+ // return (x * c) / ((x * c) * (x * c) * 0.28 + 1) * m;
16060
+ // });
16061
+ // }
15580
16062
 
15581
- this.set = function(c){
15582
- let d = Util.getParam(this._compress, c);
15583
- this.shaper(isNaN(d)? 1 : Math.max(1, d));
15584
- }
15585
-
15586
- this.chain = function(){
15587
- return { 'send' : this._fx, 'return' : this._fx };
15588
- }
15589
-
15590
- this.delete = function(){
15591
- this._fx.disconnect();
15592
- this._fx.dispose();
15593
- }
15594
- }
16063
+ // this.set = function(c){
16064
+ // let d = Util.getParam(this._compress, c);
16065
+ // this.shaper(isNaN(d)? 1 : Math.max(1, d));
16066
+ // }
16067
+
16068
+ // this.chain = function(){
16069
+ // return { 'send' : this._fx, 'return' : this._fx };
16070
+ // }
16071
+
16072
+ // this.delete = function(){
16073
+ // this._fx.disconnect();
16074
+ // this._fx.dispose();
16075
+ // }
16076
+ // }
15595
16077
 
15596
16078
  // A distortion algorithm using the tanh (hyperbolic-tangent) as a
15597
16079
  // waveshaping technique. Some mapping to apply a more equal loudness
15598
16080
  // distortion is applied on the overdrive parameter
15599
16081
  //
15600
- const DriveDeprecated = function(_params){
15601
- this._drive = (_params[0] !== undefined)? Util.toArray(_params[0]) : [1.5];
15602
-
15603
- this._fx = new Tone.WaveShaper();
15604
-
15605
- this.shaper = function(amount){
15606
- // drive curve, minimum drive of 1
15607
- const d = Math.pow(amount, 2);
15608
- // makeup gain
15609
- const m = Math.pow(d, 0.6);
15610
- // preamp gain reduction for linear at drive = 1
15611
- const p = 0.4;
15612
- // set the waveshaping effect
15613
- this._fx.setMap((x) => {
15614
- return Math.tanh(x * p * d) / p / m;
15615
- });
15616
- }
16082
+ // const DriveDeprecated = function(_params){
16083
+ // this._drive = (_params[0] !== undefined)? Util.toArray(_params[0]) : [1.5];
16084
+
16085
+ // this._fx = new Tone.WaveShaper();
16086
+
16087
+ // this.shaper = function(amount){
16088
+ // // drive curve, minimum drive of 1
16089
+ // const d = Math.pow(amount, 2);
16090
+ // // makeup gain
16091
+ // const m = Math.pow(d, 0.6);
16092
+ // // preamp gain reduction for linear at drive = 1
16093
+ // const p = 0.4;
16094
+ // // set the waveshaping effect
16095
+ // this._fx.setMap((x) => {
16096
+ // return Math.tanh(x * p * d) / p / m;
16097
+ // });
16098
+ // }
15617
16099
 
15618
- this.set = function(c){
15619
- let d = Util.getParam(this._drive, c);
15620
- this.shaper(isNaN(d)? 1 : Math.max(1, d));
15621
- }
15622
-
15623
- this.chain = function(){
15624
- return { 'send' : this._fx, 'return' : this._fx };
15625
- }
15626
-
15627
- this.delete = function(){
15628
- this._fx.disconnect();
15629
- this._fx.dispose();
15630
- }
15631
- }
16100
+ // this.set = function(c){
16101
+ // let d = Util.getParam(this._drive, c);
16102
+ // this.shaper(isNaN(d)? 1 : Math.max(1, d));
16103
+ // }
16104
+
16105
+ // this.chain = function(){
16106
+ // return { 'send' : this._fx, 'return' : this._fx };
16107
+ // }
16108
+
16109
+ // this.delete = function(){
16110
+ // this._fx.disconnect();
16111
+ // this._fx.dispose();
16112
+ // }
16113
+ // }
15632
16114
  },{"./Util.js":66,"tone":44,"total-serialism":47}],57:[function(require,module,exports){
15633
16115
  const Tone = require('tone');
15634
16116
  const Util = require('./Util.js');
@@ -15709,13 +16191,14 @@ class Instrument extends Sequencer {
15709
16191
  this.sourceEvent(c, e, time);
15710
16192
 
15711
16193
  // fade-out running envelope over 5 ms
15712
- if (this.adsr.value > 0){
15713
- let tmp = this.adsr.release;
15714
- this.adsr.release = 0.005;
15715
- this.adsr.triggerRelease(time);
15716
- this.adsr.release = tmp;
15717
- time += 0.005;
15718
- }
16194
+ // retrigger temporarily disabled to reduce distortion
16195
+ // if (this.adsr.value > 0){
16196
+ // let tmp = this.adsr.release;
16197
+ // this.adsr.release = 0.004;
16198
+ // this.adsr.triggerRelease(time-0.004);
16199
+ // this.adsr.release = tmp;
16200
+ // time += 0.010;
16201
+ // }
15719
16202
 
15720
16203
  // set shape for playback (fade-in / out and length)
15721
16204
  if (this._att){
@@ -15723,15 +16206,21 @@ class Instrument extends Sequencer {
15723
16206
  let dec = Util.divToS(Util.getParam(this._sus, c), this.bpm());
15724
16207
  let rel = Util.divToS(Util.getParam(this._rel, c), this.bpm());
15725
16208
 
15726
- this.adsr.attack = att;
16209
+ // minimum attaack and release times are 1 millisecond
16210
+ this.adsr.attack = Math.max(0.001, att);
15727
16211
  this.adsr.decay = dec;
15728
- this.adsr.release = rel;
16212
+ this.adsr.release = Math.max(0.001, rel);
15729
16213
 
15730
- e = Math.min(this._time, att + dec + rel);
16214
+ // trigger the envelope and release after a short while
16215
+ // a better working alternative for the code below
16216
+ this.adsr.triggerAttack(time);
16217
+ this.adsr.triggerRelease(time + att + dec);
16218
+
16219
+ // e = Math.min(this._time, att + dec + rel);
15731
16220
  // e = Math.min(t, att + dec + rel);
15732
16221
 
15733
- let rt = Math.max(0.001, e - this.adsr.release);
15734
- this.adsr.triggerAttackRelease(rt, time);
16222
+ // let rt = Math.max(0.001, e - this.adsr.release);
16223
+ // this.adsr.triggerAttackRelease(rt, time);
15735
16224
  } else {
15736
16225
  // if shape is 'off' only trigger attack
15737
16226
  this.adsr.triggerAttack(time);
@@ -15762,7 +16251,10 @@ class Instrument extends Sequencer {
15762
16251
  // delete super class
15763
16252
  super.delete();
15764
16253
  // disconnect the sound dispose the player
16254
+ this.gain.disconnect();
15765
16255
  this.gain.dispose();
16256
+
16257
+ this.panner.disconnect();
15766
16258
  this.panner.dispose();
15767
16259
  // this.adsr.dispose();
15768
16260
  // remove all fx
@@ -15920,11 +16412,14 @@ class MonoMidi extends Sequencer {
15920
16412
  }
15921
16413
 
15922
16414
  // Midi specific parameters
15923
- this._note = [];
16415
+ this._note = [];
16416
+ this._midinote = [];
15924
16417
  this._velocity = [ 127, 0 ];
15925
16418
  this._dur = [ 100 ];
15926
16419
  this._cc = [];
15927
16420
  this._channel = [ 1 ];
16421
+ this._program = false;
16422
+ this._pgm = null;
15928
16423
  this._chord = false;
15929
16424
  this._bend = [];
15930
16425
 
@@ -15935,8 +16430,8 @@ class MonoMidi extends Sequencer {
15935
16430
  // normalized velocity (0 - 1)
15936
16431
  let g = Util.getParam(this._velocity[0], c);
15937
16432
 
15938
- // get the duration
15939
- let d = Util.divToS(Util.getParam(this._dur, c), this.bpm()) * 1000;
16433
+ // get the duration (minus 5ms to ensure note-off send before note-on)
16434
+ let d = Util.divToS(Util.getParam(this._dur, c), this.bpm()) * 1000 - 5;
15940
16435
 
15941
16436
  // get the channel
15942
16437
  let ch = Util.getParam(this._channel, c);
@@ -15945,6 +16440,15 @@ class MonoMidi extends Sequencer {
15945
16440
  let offset = WebMidi.time - Tone.context.currentTime * 1000;
15946
16441
  let sync = time * 1000 + offset;
15947
16442
 
16443
+ // send program change messages on specified channel
16444
+ // only if the value is an integer
16445
+ let pc = Util.getParam(this._program, c);
16446
+ if (!isNaN(pc) && this._pgm !== pc){
16447
+ this._device.sendProgramChange(pc, ch, { time: sync - 1 });
16448
+ // only send value if different from previous one
16449
+ this._pgm = pc;
16450
+ }
16451
+
15948
16452
  // send pitchbend message in hires -1 1 at specified channel
15949
16453
  if (this._bend.length > 0){
15950
16454
  let b = Util.lookup(this._bend, c);
@@ -15965,6 +16469,20 @@ class MonoMidi extends Sequencer {
15965
16469
  // only play a note if the notes are provided in the function
15966
16470
  // if (this._note.length > 0){
15967
16471
 
16472
+ let noteOptions = { duration: d, velocity: g, time: sync };
16473
+
16474
+ // if a midinote is selected instead of note
16475
+ // play the value without mapping
16476
+ let m = Util.getParam(this._midinote, c);
16477
+ if (m){
16478
+ if (this._chord){
16479
+ m = Util.lookup(this._midinote, c);
16480
+ m = Util.toArray(m);
16481
+ }
16482
+ this._device.playNote(m, ch, noteOptions);
16483
+ return;
16484
+ }
16485
+
15968
16486
  // note as interval / octave coordinate
15969
16487
  let o = Util.getParam(this._note[1], c);
15970
16488
  let n = [];
@@ -15975,17 +16493,18 @@ class MonoMidi extends Sequencer {
15975
16493
  } else {
15976
16494
  i = [ Util.getParam(this._note[0], c) ];
15977
16495
  }
16496
+
16497
+ // if the note is 'off' don't play the note
16498
+ // useful when only CC or Programchange is needed
16499
+ if (i[0] === 'off'){ return; }
15978
16500
 
15979
16501
  for (let x=0; x<i.length; x++){
15980
16502
  // reconstruct midi note value, (0, 0) = 36
15981
16503
  // convert to scale and include the octave
15982
16504
  n[x] = Util.toMidi(i[x], o);
15983
16505
  }
15984
-
15985
16506
  // play the note(s)! updated for webmidi 3.x
15986
- this._device.playNote(n, ch, { duration: d, attack: g, time: sync });
15987
-
15988
- // }
16507
+ this._device.playNote(n, ch, noteOptions);
15989
16508
  }
15990
16509
 
15991
16510
  amp(g, r){
@@ -16010,6 +16529,10 @@ class MonoMidi extends Sequencer {
16010
16529
  this._bend = Util.toArray(b);
16011
16530
  }
16012
16531
 
16532
+ midinote(n=[60]){
16533
+ this._midinote = Util.toArray(n);
16534
+ }
16535
+
16013
16536
  chord(c){
16014
16537
  this._chord = false;
16015
16538
  if (c === 'on' || c === 1){
@@ -16017,6 +16540,10 @@ class MonoMidi extends Sequencer {
16017
16540
  }
16018
16541
  }
16019
16542
 
16543
+ program(p){
16544
+ this._program = Util.toArray(p);
16545
+ }
16546
+
16020
16547
  add_fx(...cc){
16021
16548
  // control parameters via control change midi messages
16022
16549
  this._cc = [];
@@ -16036,7 +16563,14 @@ class MonoMidi extends Sequencer {
16036
16563
  // send out midiclock messages to sync external devices
16037
16564
  // on this specific midi output and channel
16038
16565
  // this._sync;
16039
- }
16566
+ }
16567
+
16568
+ delete(){
16569
+ // delete super class
16570
+ super.delete();
16571
+
16572
+ console.log('=> disposed MonoMidi()', this._device);
16573
+ }
16040
16574
  }
16041
16575
  module.exports = MonoMidi;
16042
16576
  },{"./Sequencer.js":65,"./Util.js":66,"tone":44,"webmidi":55}],60:[function(require,module,exports){
@@ -16083,16 +16617,19 @@ class MonoSample extends Instrument {
16083
16617
  // clean-up previous buffer
16084
16618
  this.sample.buffer.dispose();
16085
16619
  }
16620
+
16621
+ if (!this._bufs.has(f)){
16622
+ Util.log(`${w} is not a valid sample name`);
16623
+ // defaul sample if file doesn not exist
16624
+ f = 'kick_909';
16625
+ }
16626
+
16086
16627
  if (this._bufs.has(f)){
16087
16628
  this.sample.buffer = this._bufs.get(f);
16088
- // this.sample.buffer = this._bufs.get(f).slice(0);
16089
16629
  } else {
16090
16630
  // default sample if file does not exist
16091
- this.sample.buffer = this._bufs.get('kick_min');
16092
- // this.sample.buffer = this._bufs.get('kick_min').slice(0);
16631
+ this.sample.buffer = this._bufs.get('kick_909');
16093
16632
  }
16094
- // the duration of the buffer in seconds
16095
- let dur = this.sample.buffer.duration;
16096
16633
 
16097
16634
  // get speed and if 2d array pick randomly
16098
16635
  let s = Util.getParam(this._speed, c);
@@ -16115,6 +16652,8 @@ class MonoSample extends Instrument {
16115
16652
  // it becomes normal playback again) no fix yet
16116
16653
  // this.sample.reverse = s < 0.0;
16117
16654
 
16655
+ // the duration of the buffer in seconds
16656
+ let dur = this.sample.buffer.duration;
16118
16657
  let l = Util.lookup(this._stretch, c);
16119
16658
  let n = 1;
16120
16659
  if (l){
@@ -16186,7 +16725,12 @@ class MonoSample extends Instrument {
16186
16725
  // delete super class
16187
16726
  super.delete();
16188
16727
  // disconnect the sound dispose the player
16728
+ this.source.stop();
16729
+ this.source.disconnect();
16189
16730
  this.source.dispose();
16731
+
16732
+ this.sample.stop();
16733
+ this.sample.disconnect();
16190
16734
  this.sample.dispose();
16191
16735
 
16192
16736
  console.log('=> disposed MonoSample()', this._sound);
@@ -16213,10 +16757,11 @@ class MonoSynth extends Instrument {
16213
16757
  triangle : 'triangle',
16214
16758
  tri : 'triangle',
16215
16759
  rect : 'square',
16216
- fm: 'fmsine',
16217
- am: 'amsine',
16218
- pwm: 'pwm',
16219
- organ: 'sine4',
16760
+ organ : 'sine4',
16761
+ // fm: 'fmsine',
16762
+ // am: 'amsine',
16763
+ // pwm: 'pwm',
16764
+ // organ: 'sine4',
16220
16765
  }
16221
16766
  // // synth specific variables;
16222
16767
  this._note = [ 0, 0 ];
@@ -16277,8 +16822,9 @@ class MonoSynth extends Instrument {
16277
16822
  this.synth.frequency.rampTo(f, s, time);
16278
16823
  } else {
16279
16824
  this.synth.frequency.setValueAtTime(f, time);
16280
- this._firstSlide = false;
16281
16825
  }
16826
+ // first time the synth plays don't slide!
16827
+ this._firstSlide = false;
16282
16828
  }
16283
16829
 
16284
16830
  super(v=[3], d=[0.111]){
@@ -16303,6 +16849,11 @@ class MonoSynth extends Instrument {
16303
16849
  super.delete();
16304
16850
  // dispose the sound source
16305
16851
  // this.source.delete();
16852
+ // this.adsr.dispose();
16853
+ this.synth.stop();
16854
+ this.synth.disconnect();
16855
+ this.synth.dispose();
16856
+
16306
16857
  this.adsr.dispose();
16307
16858
  this.synth.dispose();
16308
16859
  this.source.dispose();
@@ -16314,10 +16865,10 @@ module.exports = MonoSynth;
16314
16865
  },{"./Instrument":57,"./Util.js":66,"tone":44,"total-serialism":47}],62:[function(require,module,exports){
16315
16866
  const Tone = require('tone');
16316
16867
  const Util = require('./Util.js');
16317
- const fxMap = require('./Effects.js');
16318
- const TL = require('total-serialism').Translate;
16319
- // const Sequencer = require('./Sequencer.js');
16320
16868
  const Instrument = require('./Instrument.js');
16869
+ // const fxMap = require('./Effects.js');
16870
+ // const TL = require('total-serialism').Translate;
16871
+ // const Sequencer = require('./Sequencer.js');
16321
16872
 
16322
16873
  // Basic class for a poly-instrument
16323
16874
  class PolyInstrument extends Instrument {
@@ -16425,15 +16976,17 @@ class PolyInstrument extends Instrument {
16425
16976
  let dec = Util.divToS(Util.lookup(this._sus, c), this.bpm());
16426
16977
  let rel = Util.divToS(Util.lookup(this._rel, c), this.bpm());
16427
16978
 
16428
- this.adsrs[i].attack = att;
16979
+ this.adsrs[i].attack = Math.max(0.001, att);
16429
16980
  this.adsrs[i].decay = dec;
16430
- this.adsrs[i].release = rel;
16981
+ this.adsrs[i].release = Math.max(0.001, rel);
16431
16982
 
16432
- e = Math.min(this._time, att + dec + rel);
16433
-
16983
+ // e = Math.min(this._time, att + dec + rel);
16984
+ // let rt = Math.max(0.001, e - this.adsrs[i].release);
16985
+ // this.adsrs[i].triggerAttackRelease(rt, time);
16986
+
16434
16987
  // trigger the envelope
16435
- let rt = Math.max(0.001, e - this.adsrs[i].release);
16436
- this.adsrs[i].triggerAttackRelease(rt, time);
16988
+ this.adsrs[i].triggerAttack(time);
16989
+ this.adsrs[i].triggerRelease(time + att + dec);
16437
16990
  } else {
16438
16991
  // if shape is off only trigger attack
16439
16992
  // when voice stealing is 'off' this will lead to all
@@ -16469,18 +17022,27 @@ class PolyInstrument extends Instrument {
16469
17022
  // delete super class
16470
17023
  super.delete();
16471
17024
  // disconnect the sound dispose the player
16472
- // this.gain.dispose();
16473
- // this.panner.dispose();
17025
+ this.gain.disconnect();
17026
+ this.gain.dispose();
17027
+ this.panner.disconnect();
17028
+ this.panner.dispose();
16474
17029
 
16475
- this.adsrs.map((a) => a.dispose());
16476
- this.sources.map((s) => s.dispose());
17030
+ this.adsrs.map((a) => {
17031
+ a.disconnect();
17032
+ a.dispose();
17033
+ });
17034
+ this.sources.map((s) => {
17035
+ s.stop();
17036
+ s.disconnect();
17037
+ s.dispose();
17038
+ });
16477
17039
  // remove all fx
16478
17040
  // this._fx.map((f) => f.delete());
16479
17041
  console.log('=> disposed PolyInstrument() with FX:', this._fx);
16480
17042
  }
16481
17043
  }
16482
17044
  module.exports = PolyInstrument;
16483
- },{"./Effects.js":56,"./Instrument.js":57,"./Util.js":66,"tone":44,"total-serialism":47}],63:[function(require,module,exports){
17045
+ },{"./Instrument.js":57,"./Util.js":66,"tone":44}],63:[function(require,module,exports){
16484
17046
  const Tone = require('tone');
16485
17047
  const Util = require('./Util.js');
16486
17048
  const PolyInstrument = require('./PolyInstrument.js');
@@ -16545,7 +17107,7 @@ class PolySample extends PolyInstrument {
16545
17107
  this.sources[id].buffer = this._bufs.get(b);
16546
17108
  } else {
16547
17109
  // default sample if file does not exist
16548
- this.sources[id].buffer = this._bufs.get('kick_min');
17110
+ this.sources[id].buffer = this._bufs.get('kick_909');
16549
17111
  }
16550
17112
  // the duration of the buffer in seconds
16551
17113
  let dur = this.sources[id].buffer.duration;
@@ -16738,6 +17300,11 @@ class PolySynth extends PolyInstrument {
16738
17300
  this._detune = Util.toArray(d);
16739
17301
  }
16740
17302
 
17303
+ fat(...a){
17304
+ // alias for super/unison synth
17305
+ this.super(...a);
17306
+ }
17307
+
16741
17308
  slide(s){
16742
17309
  // portamento from one note to another
16743
17310
  this._slide = Util.toArray(s);
@@ -16751,14 +17318,14 @@ class PolySynth extends PolyInstrument {
16751
17318
  // delete super class
16752
17319
  super.delete();
16753
17320
 
16754
- console.log('disposed MonoSynth()', this._wave);
17321
+ console.log('disposed PolySynth()', this._wave);
16755
17322
  }
16756
17323
  }
16757
17324
  module.exports = PolySynth;
16758
17325
  },{"./PolyInstrument":62,"./Util.js":66,"tone":44}],65:[function(require,module,exports){
16759
17326
  const Tone = require('tone');
16760
17327
  const Util = require('./Util.js');
16761
- const WebMidi = require("webmidi");
17328
+ // const WebMidi = require("webmidi");
16762
17329
 
16763
17330
  // Basic Sequencer class for triggering events
16764
17331
  class Sequencer {
@@ -16770,10 +17337,12 @@ class Sequencer {
16770
17337
  // Sequencer specific parameters
16771
17338
  this._count = 0;
16772
17339
  this._beatCount = 0;
17340
+ this._subdivCnt = 0;
16773
17341
  this._time = 1;
16774
17342
  this._subdiv = [ 1 ];
16775
17343
  this._offset = 0;
16776
17344
  this._beat = [ 1 ];
17345
+ this._wait = null;
16777
17346
  this._human = 0;
16778
17347
 
16779
17348
  // visual code
@@ -16815,6 +17384,9 @@ class Sequencer {
16815
17384
  }
16816
17385
  // set subdivision speeds
16817
17386
  this._loop.playbackRate = Util.getParam(this._subdiv, this._count);
17387
+
17388
+ // get the subdivision count (always 0, except when subdividing)
17389
+ this._subdivCnt = (this._subdivCnt + 1) % this._loop.playbackRate;
16818
17390
 
16819
17391
  // humanize method is interesting to add
16820
17392
  this._loop.humanize = Util.getParam(this._human, this._count);
@@ -16826,6 +17398,12 @@ class Sequencer {
16826
17398
  if (Math.random() < b){
16827
17399
  // get the count value
16828
17400
  let c = this._beatCount;
17401
+
17402
+ // get the wait time (delay) convert to seconds
17403
+ if (this._wait !== null){
17404
+ let w = Util.getParam(this._wait, c);
17405
+ time += Util.divToS(w, this.bpm());
17406
+ }
16829
17407
 
16830
17408
  // trigger some events for this instrument based
16831
17409
  // on the current count and time
@@ -16848,9 +17426,12 @@ class Sequencer {
16848
17426
  this._canvas.eval(Util.getParam(this._visual, c));
16849
17427
  }
16850
17428
  }
16851
-
17429
+
16852
17430
  // increment internal beat counter
16853
- this._beatCount++;
17431
+ // only increment if ratchetcounter is 0
17432
+ if (this._subdivCnt < 1) {
17433
+ this._beatCount++;
17434
+ }
16854
17435
  }
16855
17436
  // increment count for sequencing
16856
17437
  this._count++;
@@ -16868,7 +17449,9 @@ class Sequencer {
16868
17449
  // calculate the scheduling
16869
17450
  let schedule = Tone.Time(this._offset).toSeconds();
16870
17451
  // create new loop for synth
16871
- this._loop = new Tone.Loop((time) => { this._event(time) }, this._time).start(schedule);
17452
+ this._loop = new Tone.Loop((time) => {
17453
+ this._event(time)
17454
+ }, this._time).start(schedule);
16872
17455
  }
16873
17456
  // else {
16874
17457
  // // generate a listener for the osc-address
@@ -16962,6 +17545,11 @@ class Sequencer {
16962
17545
  }
16963
17546
  }
16964
17547
 
17548
+ wait(w=[0]){
17549
+ // wait a specified amount of milliseconds before triggering
17550
+ this._wait = Util.toArray(w);
17551
+ }
17552
+
16965
17553
  human(h){
16966
17554
  // set the humanizing factor for the instrument in seconds
16967
17555
  this._human = Util.toArray(h).map(x => Util.divToS(x));
@@ -16986,7 +17574,8 @@ class Sequencer {
16986
17574
  }
16987
17575
  }
16988
17576
  module.exports = Sequencer;
16989
- },{"./Util.js":66,"tone":44,"webmidi":55}],66:[function(require,module,exports){
17577
+ },{"./Util.js":66,"tone":44}],66:[function(require,module,exports){
17578
+ const Tone = require('tone');
16990
17579
  const { noteToMidi, toScale, mtof } = require('total-serialism').Translate;
16991
17580
 
16992
17581
  // replace defaults with incoming parameters
@@ -16995,6 +17584,13 @@ function mapDefaults(params, defaults){
16995
17584
  return defaults.map(p => toArray(p));
16996
17585
  }
16997
17586
 
17587
+ // Function that is evaluated at a specific time from Tone Transpor
17588
+ // More precise than Tone.Transport.ScheduleOnce()
17589
+ // Workaround for Tone objects that don't have setValueAtTime
17590
+ function atTime(callback, time){
17591
+ setTimeout(callback, (time - Tone.context.currentTime) * 1000);
17592
+ }
17593
+
16998
17594
  // convert amplitude to dBFS scale
16999
17595
  function atodb(a=0){
17000
17596
  return 20 * Math.log(a);
@@ -17005,8 +17601,8 @@ function dbtoa(db=0){
17005
17601
  return 10 ** (db/20);
17006
17602
  }
17007
17603
 
17008
- // clip a value between a specified range
17009
- function clip(v, l, h){
17604
+ // clip a value between a specified range, defaults to 0 and 1 clip
17605
+ function clip(v, l=0, h=1){
17010
17606
  return Math.max(l, Math.min(h, v));
17011
17607
  }
17012
17608
 
@@ -17203,8 +17799,8 @@ function log(msg){
17203
17799
  }
17204
17800
  }
17205
17801
 
17206
- module.exports = { mapDefaults, atodb, dbtoa, clip, assureNum, lookup, randLookup, isRandom, getParam, toArray, msToS, formatRatio, divToS, divToF, toMidi, mtof, noteToMidi, noteToFreq, assureWave, log }
17207
- },{"total-serialism":47}],67:[function(require,module,exports){
17802
+ module.exports = { mapDefaults, atTime, atodb, dbtoa, clip, assureNum, lookup, randLookup, isRandom, getParam, toArray, msToS, formatRatio, divToS, divToF, toMidi, mtof, noteToMidi, noteToFreq, assureWave, log }
17803
+ },{"tone":44,"total-serialism":47}],67:[function(require,module,exports){
17208
17804
  module.exports={
17209
17805
  "uptempo" : 10,
17210
17806
  "downtempo" : 10,
@@ -17565,7 +18161,7 @@ const { WebMidi } = require("webmidi");
17565
18161
  // load extra AudioWorkletProcessors from file
17566
18162
  // transformed to inline with browserify brfs
17567
18163
 
17568
- const fxExtensions = "\n// A white noise generator at -6dBFS to test AudioWorkletProcessor\n//\nclass 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}\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\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\t// simple waveshaping with tanh\n\t\t\t\t\toutput[channel][i] = Math.tanh(input[channel][i]);\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}\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// set the waveshaper effect\n\t\t\t\t\tconst s = input[channel][i];\n\t\t\t\t\tconst p = (s * a) / ((s * a) * (s * a) * 0.28 + 1.0);\n\t\t\t\t\toutput[channel][i] = p;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n}\nregisterProcessor('squash-processor', SquashProcessor);";
18164
+ 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);";
17569
18165
  Tone.getContext().addAudioWorkletModule(URL.createObjectURL(new Blob([ fxExtensions ], { type: 'text/javascript' })));
17570
18166
 
17571
18167
  // Mercury main class controls Tone and loads samples