diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2ccbe46
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/node_modules/
diff --git a/README.md b/README.md
index 3012560..944f92c 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,12 @@
-# scale-explorer
+# music-explorer
+## scale explorer
Objective: Make an intuitive music scale explorer, since fifth circle until greek modes.
-# running locally
+## running locally
-$ npm install -g http-server # install dependency
-$ http-server -p 8000
\ No newline at end of file
+install:
+`$ yarn`
+
+start:
+`$ yarn start`
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..1749b3e
--- /dev/null
+++ b/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "music-explorer",
+ "license": "MIT",
+ "version": "0.0.0",
+ "main": "index.js",
+ "scripts": {
+ "start": "yarn http-server -p 8000"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/nickmafra/music-explorer.git"
+ },
+ "dependencies": {
+ "http-server": "^14.1.1"
+ }
+}
diff --git a/piano/core.js b/piano/core.js
new file mode 100644
index 0000000..fe6ec7a
--- /dev/null
+++ b/piano/core.js
@@ -0,0 +1,57 @@
+var core = {
+
+ audioCtx: null,
+
+ start: function() {
+ this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();
+ },
+
+ calcFrequency: function(note, octave) {
+ return 440 * Math.pow(2, octave - 4 + note/12);
+ },
+
+ makeEnvelopeNode: function(adsr, time) {
+ if (!time) time = this.audioCtx.currentTime;
+
+ const env = new GainNode(this.audioCtx);
+ env.gain.cancelScheduledValues(time);
+ env.gain.setValueAtTime(0, time);
+ env.gain.linearRampToValueAtTime(adsr.attackAmplitude, time + adsr.attackDuration);
+ env.gain.linearRampToValueAtTime(
+ adsr.decayAmplitude,
+ time + adsr.attackDuration + adsr.decayDuration
+ );
+ if (adsr.sustainDuration) {
+ env.gain.linearRampToValueAtTime(
+ 0,
+ time + adsr.attackDuration + adsr.decayDuration + adsr.sustainDuration
+ );
+ }
+ return env;
+ },
+
+ releaseEnvelope: function(env, adsr, time) {
+ if (!time) time = this.audioCtx.currentTime;
+
+ env.gain.linearRampToValueAtTime(
+ 0,
+ time + adsr.releaseDuration
+ );
+ },
+
+ makeHarmonicNode: function(frequency, harmonics) {
+
+ const wave = new PeriodicWave(this.audioCtx, {
+ real: new Float32Array(harmonics),
+ imag: new Float32Array(harmonics.length)
+ });
+
+ const oscillator = new OscillatorNode(this.audioCtx, {
+ frequency,
+ type: "custom",
+ periodicWave: wave
+ });
+ return oscillator;
+ }
+
+};
\ No newline at end of file
diff --git a/piano/engine.js b/piano/engine.js
new file mode 100644
index 0000000..0c77558
--- /dev/null
+++ b/piano/engine.js
@@ -0,0 +1,73 @@
+var engine = {
+
+ updateFunction: () => {},
+ objects: [],
+ clickStartObject: null,
+ clickingObject: null,
+ clickStopObject: null,
+
+ start: function() {
+ this.canvas = document.createElement("canvas");
+ document.body.insertBefore(this.canvas, document.body.childNodes[0]);
+ this.ctx = this.canvas.getContext("2d");
+
+ addEventListener("resize", () => this.resize());
+ this.resize();
+
+ document.addEventListener('mousedown', (e) => this.onClickStart(e));
+ document.addEventListener('mouseup', (e) => this.onClickStop(e));
+
+ this.loop();
+ },
+
+ resize: function() {
+ this.canvas.width = document.body.clientWidth;
+ this.canvas.height = document.body.clientHeight;
+ this.unit = Math.min(this.canvas.width, this.canvas.height)/2; // center to nearest border
+ },
+
+ loop: function() {
+ this.updateFunction();
+ this.draw();
+
+ this.clickStartObject = null;
+ this.clickStopObject = null;
+
+ requestAnimationFrame(() => this.loop());
+ },
+
+ draw: function() {
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
+ this.ctx.fillStyle = 'gray';
+ this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
+
+ this.objects.forEach(obj => {
+ this.ctx.fillStyle = obj.c;
+ this.ctx.fillRect(
+ this.canvas.width/2 + this.unit * (obj.x - obj.w/2),
+ this.canvas.height/2 + this.unit * (obj.y - obj.h/2),
+ this.unit * obj.w,
+ this.unit * obj.h
+ );
+ });
+ },
+
+ onClickStart: function(e) {
+ var x = (e.clientX - this.canvas.width/2) / this.unit;
+ var y = (e.clientY - this.canvas.height/2) / this.unit;
+
+ for (var i = this.objects.length - 1; i >= 0; i--) {
+ var obj = this.objects[i];
+ if (obj.clickable && Math.abs(x - obj.x) <= obj.w/2 && Math.abs(y - obj.y) <= obj.h/2) {
+ this.clickStartObject = obj;
+ this.clickingObject = obj;
+ return;
+ }
+ }
+ },
+
+ onClickStop: function(e) {
+ this.clickStopObject = this.clickingObject;
+ this.clickingObject = null;
+ }
+}
\ No newline at end of file
diff --git a/piano/index.html b/piano/index.html
new file mode 100644
index 0000000..6ddf2c6
--- /dev/null
+++ b/piano/index.html
@@ -0,0 +1,23 @@
+
+
+
+ Sounzapp, the real-music game!
+
+
+
+
+
+
+
+
+
+
+
diff --git a/piano/main.js b/piano/main.js
new file mode 100644
index 0000000..d24dd8d
--- /dev/null
+++ b/piano/main.js
@@ -0,0 +1,45 @@
+var main = {
+
+ start: function() {
+ core.start();
+
+ this.objects = piano.makeKeyObjects(0, 1, 37);
+
+ engine.updateFunction = () => this.update();
+ engine.start();
+ },
+
+ update: function() {
+ engine.objects = [...this.objects];
+ if (engine.clickStartObject != null && engine.clickStartObject.audioPlayer) {
+ engine.clickStartObject.audioPlayer.start();
+ }
+ if (engine.clickStopObject != null && engine.clickStopObject.audioPlayer) {
+ engine.clickStopObject.audioPlayer.stop();
+ }
+ },
+
+ randomColorString: function() {
+ var rgb = Array(3).fill(255).map(x => Math.floor(x * Math.random()));
+ return `rgb(${rgb[0]},${rgb[1]},${rgb[2]})`;
+ },
+
+ objects: [
+ {
+ x: 0,
+ y: 0,
+ w: 1,
+ h: 1,
+ c: 'gray',
+ clickable: true
+ },
+ {
+ x: 0.5,
+ y: 0.5,
+ w: 0.5,
+ h: 0.5,
+ c: 'cyan',
+ clickable: true
+ },
+ ]
+}
\ No newline at end of file
diff --git a/piano/piano.js b/piano/piano.js
new file mode 100644
index 0000000..9ffde01
--- /dev/null
+++ b/piano/piano.js
@@ -0,0 +1,101 @@
+var piano = {
+
+ adsr: {
+ attackAmplitude: 1,
+ attackDuration: 0.02,
+ decayAmplitude: 0.5,
+ decayDuration: 0.2,
+ sustainDuration: 2,
+ releaseDuration: 0.1
+ },
+ harmonics: [
+ 1.0,
+ 0.6,
+ 0.2,
+ 0.3,
+ 0.1, // 5
+ 0.5,
+ 0.1,
+ 0.4,
+ 0.1,
+ 0.1, // 10
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ ],
+
+ makeKeyObjects: function(y, width, keyCount = 88) {
+ var whiteKeyCountEstimate = keyCount - Math.floor(keyCount*5/12);
+ var scale = 1 * width * 88 / whiteKeyCountEstimate;
+ const space = scale * 0.035;
+ const whiteKeyWidth = scale * 0.033;
+ const whiteKeyHeight = scale * 0.2;
+ const blackKeyWidth = whiteKeyWidth*2/3;
+ const blackKeyHeight = whiteKeyHeight*3/5;
+ const blackIndexes = [1,4,6,9,11];
+ var note = keyCount == 88 ? 0 : 3;
+ var octave = Math.floor((88 - keyCount) / 24);
+ var keyX = -whiteKeyCountEstimate/2 * space;
+ var keys = [];
+ while (keys.length < keyCount) {
+ var isBlack = blackIndexes.includes(note);
+ var freq = core.calcFrequency(note, octave);
+ var key = {
+ note,
+ octave,
+ isBlack,
+ audioPlayer: this.audioPlayer(freq),
+ clickable: true
+ };
+ if (isBlack) {
+ key.c = '#222';
+ key.w = blackKeyWidth;
+ key.h = blackKeyHeight;
+ key.x = keyX - whiteKeyWidth/2;
+ key.y = y - whiteKeyHeight/2 + blackKeyHeight/2;
+ } else {
+ key.c = '#eee';
+ key.w = whiteKeyWidth;
+ key.h = whiteKeyHeight;
+ key.x = keyX;
+ keyX += space;
+ key.y = y;
+ }
+ keys.push(key);
+
+ note++;
+ if (note == 12) {
+ note = 0;
+ octave++;
+ }
+ }
+ keys.sort((a,b) => a.isBlack ? 1 : b.isBlack ? -1 : 0);
+ return keys;
+ },
+
+ audioPlayer: function(freq) {
+ const audioPlayer = {};
+ audioPlayer.start = () => {
+ if (audioPlayer.oscillator) {
+ audioPlayer.oscillator.stop();
+ }
+
+ audioPlayer.oscillator = core.makeHarmonicNode(freq, this.harmonics);
+ audioPlayer.envelope = core.makeEnvelopeNode(this.adsr);
+
+ audioPlayer.oscillator
+ .connect(audioPlayer.envelope)
+ .connect(core.audioCtx.destination);
+
+ audioPlayer.oscillator.start();
+ };
+ audioPlayer.stop = () => {
+ core.releaseEnvelope(audioPlayer.envelope, this.adsr);
+ audioPlayer.oscillator.stop(core.audioCtx.currentTime + this.adsr.releaseDuration);
+ };
+ return audioPlayer;
+ }
+};
\ No newline at end of file
diff --git a/circle-maker.mjs b/scale/circle-maker.mjs
similarity index 100%
rename from circle-maker.mjs
rename to scale/circle-maker.mjs
diff --git a/index.html b/scale/index.html
similarity index 100%
rename from index.html
rename to scale/index.html
diff --git a/note.mjs b/scale/note.mjs
similarity index 100%
rename from note.mjs
rename to scale/note.mjs
diff --git a/notes.js b/scale/notes.js
similarity index 100%
rename from notes.js
rename to scale/notes.js
diff --git a/script.mjs b/scale/script.mjs
similarity index 100%
rename from script.mjs
rename to scale/script.mjs
diff --git a/style.css b/scale/style.css
similarity index 100%
rename from style.css
rename to scale/style.css
diff --git a/tuning-systems.mjs b/scale/tuning-systems.mjs
similarity index 100%
rename from tuning-systems.mjs
rename to scale/tuning-systems.mjs
diff --git a/yarn.lock b/yarn.lock
new file mode 100644
index 0000000..2ed47ba
--- /dev/null
+++ b/yarn.lock
@@ -0,0 +1,300 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+ansi-styles@^4.1.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
+ integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
+ dependencies:
+ color-convert "^2.0.1"
+
+async@^2.6.4:
+ version "2.6.4"
+ resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221"
+ integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==
+ dependencies:
+ lodash "^4.17.14"
+
+basic-auth@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a"
+ integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==
+ dependencies:
+ safe-buffer "5.1.2"
+
+call-bind@^1.0.0:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.5.tgz#6fa2b7845ce0ea49bf4d8b9ef64727a2c2e2e513"
+ integrity sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==
+ dependencies:
+ function-bind "^1.1.2"
+ get-intrinsic "^1.2.1"
+ set-function-length "^1.1.1"
+
+chalk@^4.1.2:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
+ integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
+ dependencies:
+ ansi-styles "^4.1.0"
+ supports-color "^7.1.0"
+
+color-convert@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
+ integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
+ dependencies:
+ color-name "~1.1.4"
+
+color-name@~1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
+ integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
+
+corser@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/corser/-/corser-2.0.1.tgz#8eda252ecaab5840dcd975ceb90d9370c819ff87"
+ integrity sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==
+
+debug@^3.2.7:
+ version "3.2.7"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
+ integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
+ dependencies:
+ ms "^2.1.1"
+
+define-data-property@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.1.tgz#c35f7cd0ab09883480d12ac5cb213715587800b3"
+ integrity sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==
+ dependencies:
+ get-intrinsic "^1.2.1"
+ gopd "^1.0.1"
+ has-property-descriptors "^1.0.0"
+
+eventemitter3@^4.0.0:
+ version "4.0.7"
+ resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
+ integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
+
+follow-redirects@^1.0.0:
+ version "1.15.4"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf"
+ integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==
+
+function-bind@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
+ integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
+
+get-intrinsic@^1.0.2, get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.2.tgz#281b7622971123e1ef4b3c90fd7539306da93f3b"
+ integrity sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==
+ dependencies:
+ function-bind "^1.1.2"
+ has-proto "^1.0.1"
+ has-symbols "^1.0.3"
+ hasown "^2.0.0"
+
+gopd@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
+ integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==
+ dependencies:
+ get-intrinsic "^1.1.3"
+
+has-flag@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
+ integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
+
+has-property-descriptors@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz#52ba30b6c5ec87fd89fa574bc1c39125c6f65340"
+ integrity sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==
+ dependencies:
+ get-intrinsic "^1.2.2"
+
+has-proto@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0"
+ integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==
+
+has-symbols@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
+ integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
+
+hasown@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c"
+ integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==
+ dependencies:
+ function-bind "^1.1.2"
+
+he@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
+ integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
+
+html-encoding-sniffer@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9"
+ integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==
+ dependencies:
+ whatwg-encoding "^2.0.0"
+
+http-proxy@^1.18.1:
+ version "1.18.1"
+ resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549"
+ integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==
+ dependencies:
+ eventemitter3 "^4.0.0"
+ follow-redirects "^1.0.0"
+ requires-port "^1.0.0"
+
+http-server@^14.1.1:
+ version "14.1.1"
+ resolved "https://registry.yarnpkg.com/http-server/-/http-server-14.1.1.tgz#d60fbb37d7c2fdff0f0fbff0d0ee6670bd285e2e"
+ integrity sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==
+ dependencies:
+ basic-auth "^2.0.1"
+ chalk "^4.1.2"
+ corser "^2.0.1"
+ he "^1.2.0"
+ html-encoding-sniffer "^3.0.0"
+ http-proxy "^1.18.1"
+ mime "^1.6.0"
+ minimist "^1.2.6"
+ opener "^1.5.1"
+ portfinder "^1.0.28"
+ secure-compare "3.0.1"
+ union "~0.5.0"
+ url-join "^4.0.1"
+
+iconv-lite@0.6.3:
+ version "0.6.3"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
+ integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
+ dependencies:
+ safer-buffer ">= 2.1.2 < 3.0.0"
+
+lodash@^4.17.14:
+ version "4.17.21"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
+ integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
+
+mime@^1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
+ integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
+
+minimist@^1.2.6:
+ version "1.2.8"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
+ integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
+
+mkdirp@^0.5.6:
+ version "0.5.6"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6"
+ integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==
+ dependencies:
+ minimist "^1.2.6"
+
+ms@^2.1.1:
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
+ integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+
+object-inspect@^1.9.0:
+ version "1.13.1"
+ resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2"
+ integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==
+
+opener@^1.5.1:
+ version "1.5.2"
+ resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598"
+ integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==
+
+portfinder@^1.0.28:
+ version "1.0.32"
+ resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.32.tgz#2fe1b9e58389712429dc2bea5beb2146146c7f81"
+ integrity sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==
+ dependencies:
+ async "^2.6.4"
+ debug "^3.2.7"
+ mkdirp "^0.5.6"
+
+qs@^6.4.0:
+ version "6.11.2"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9"
+ integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==
+ dependencies:
+ side-channel "^1.0.4"
+
+requires-port@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
+ integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
+
+safe-buffer@5.1.2:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+ integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+
+"safer-buffer@>= 2.1.2 < 3.0.0":
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
+ integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
+
+secure-compare@3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/secure-compare/-/secure-compare-3.0.1.tgz#f1a0329b308b221fae37b9974f3d578d0ca999e3"
+ integrity sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==
+
+set-function-length@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.1.1.tgz#4bc39fafb0307224a33e106a7d35ca1218d659ed"
+ integrity sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==
+ dependencies:
+ define-data-property "^1.1.1"
+ get-intrinsic "^1.2.1"
+ gopd "^1.0.1"
+ has-property-descriptors "^1.0.0"
+
+side-channel@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
+ integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==
+ dependencies:
+ call-bind "^1.0.0"
+ get-intrinsic "^1.0.2"
+ object-inspect "^1.9.0"
+
+supports-color@^7.1.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
+ integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
+ dependencies:
+ has-flag "^4.0.0"
+
+union@~0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/union/-/union-0.5.0.tgz#b2c11be84f60538537b846edb9ba266ba0090075"
+ integrity sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==
+ dependencies:
+ qs "^6.4.0"
+
+url-join@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7"
+ integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==
+
+whatwg-encoding@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53"
+ integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==
+ dependencies:
+ iconv-lite "0.6.3"