diff --git a/.eslintrc b/.eslintrc index 2216763..2420cfa 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,4 +1,5 @@ { + "extends": "airbnb-base", "parserOptions": { "ecmaFeatures": { "experimentalObjectRestSpread": true @@ -6,8 +7,34 @@ "sourceType": "module" }, "env": { - "es6": true + "es6": true, + "browser": true }, "rules": { + "indent": [ + "error", + "tab" + ], + "linebreak-style": [ + "error", + "unix" + ], + "no-tabs": 0, + "quotes": 1, + "semi": [ + "error", + "always" + ], + "no-console": 0, + "import/no-extraneous-dependencies": [ + "error", + { + "devDependencies": [ + "dist/*.js", + "packing/*.js", + "server/**/*.js" + ] + } + ] } } diff --git a/.gitignore b/.gitignore index 3401686..5cb49ea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .DS_Store node_modules/ build/ +dist/ deploy.sh diff --git a/README.md b/README.md index 17c9822..f654dad 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,43 @@ play @synth 💡 Try making multiple play lines for the same sound to make polyphonic melodies and drum beats. +## Rhythm and Note Values + +Musical notes and rhythm values are a core concept of Slang, so let's look at them in a bit more detail. + +**Note values** may look familiar to you if you've ever learned an instrument. They contain both a note (like `c`, `f#`, or `a`) and a number that represents the _octave_. If you imagine a large piano, `c4` is the C key right smack in the middle of it. `c3` will be the C key one octave down from that, `c5` will be one octave up, etc. If you're unfamiliar with how the notes map to a keyboard, [here is a handy image](https://3.bp.blogspot.com/-X7bigGKA1ww/WdA-5CZ0e3I/AAAAAAAABWE/MlQ5xkgSEmICpi3HGpqnRn2gwKX7bdz1ACLcBGAs/s320/piano1.png). + +Note values can also be expressed as numbers. If you've worked with synthesizers or electronic instruments before you might be familiar with the MIDI protocol, which among other things represents all notes on a keyboard from `0` - `127`. `c4` is equivalent to the MIDI number `64`, and to move up or down an octave you can add or subtract `12`. + +Here are the notes in the fourth octave with their MIDI number equivalents: +``` +Notes: c4 c#4 d4 d#4 e4 f4 f#4 g4 g#4 a4 a#4 b4 c5 +Numbers: 64 65 66 67 68 69 70 71 72 73 74 75 76 +``` + +**Rhythm values** describe the _length_ of the note as a fraction of a _measure_. The longest possible note in Slang is `1n`, which in music notation would be referred to as a _whole note_. + +Here are all of the values and how they line up, from slowest to fastest: +- `1n` - whole note (the longest note) +- `2n` - half note (half of a whole note) +- `2t` - half note triplet (3 of these is equal to `1n`) +- `4n` - quarter note (a quarter of a whole note) +- `4t` - quarter note triplet (3 of these is equal to `2n`) +- `8n` - eighth note (1/8 of a whole note) +- `8t` - eighth note triplet (3 of these is equal to `4n`) +- `16n` - sixteenth note (1/16 of a whole note) +- `16t` - sixteenth triplet (3 of these is equal to `8n`) +- `32n` - thirty-second note (1/32 of a whole note) +- `32t` - thirty-second triplet (3 of these is equal to `16n`) +- `64n` - sixty-fourth note (1/64 of a whole note) +- `64t` - sixty-fourth triplet (3 of these is equal to `32n`) + +When creating a rhythm in Slang you can freely mix and match these values. A good rule of thumb is that you should aim for all of your rhythm values to add up to `1n` or multiples of `1n`; for example `4n 4n 4n 4n`, `4n 8n 8n 2n`, and `4n 16t 16t 16t 8n 4t 4t 4t` all add up to `1n`. + +Sometimes you'll want to pause for a beat without playing a note. This is called a _rest_ in music terminology. Adding `r` in front of any rhythm value will turn it into a rest (and it will appear lighter in color within the Slang editor); for example `4n 4n 4n r4n` will play three quarter notes and then rest for the length of one quarter note. + +In addition to the rhythm notation you can also use **number values**, which correspond to _seconds_. Slang runs at a tempo of 120 beats per minute, which means that a whole note — `1n` — is exactly `2` seconds long. Writing `(rhythm [2])` and `(rhythm [1n])` produce exactly the same rhythm. This is useful in other functions like [`(adsr)`](https://github.com/kylestetz/slang#amp-envelope---adsr-osc-attack-001-decay-0-sustain-1-release-005) where the attack, decay, and release all accept a rhythm value. + ## Syntax Functions are contained within parentheses, much like in Clojure. The first keyword in a function is the **function name**, which is followed by all of its arguments. Any argument can be a primitive value or a list (neat!); if it's a list, Slang will take one value at a time and loop back to the beginning when it reaches the end. Check out the Reference section for lots of usage examples. @@ -142,14 +179,29 @@ _Pro tip_: Any number above 11 will wrap around using modulus, so for example 25 #### Amp Envelope - `(adsr )` -Creates an amp envelope which contains an oscillator followed by ADSR values. The attack, decay, and release arguments can be numbers or rhythm values (e.g. `8n`, `8t`, `4n`, etc.). Sustain is a number from 0 - 1. +Creates an amp envelope which contains an oscillator followed by ADSR values. If you're unfamiliar with the concept of an amp envelope, check out the _Typical Stages_ section of [this tutorial](https://theproaudiofiles.com/synthesis-101-envelope-parameters-uses/). Amp envelopes control the _volume_ of a sound over the course of a single note. Since amp envelopes contain oscillators they can kick off a chain of sound. +`osc`: An oscillator function, e.g. `(osc tri)` + +`attack`: A rhythm value or number in seconds corresponding to how long the sound takes to fade in + +`decay`: A rhythm value or number in seconds corresponding to how long the sound takes to descend to the sustain value + +`sustain`: A value from `0` - `1` describing how loud the note should be while it is sustained. + +`release`: A rhythm value or number in seconds corresponding to how long the sound takes to fade from its sustain value down to `0`. + Usage: ``` -# Creates a sine wave oscillator with an amp envelope. -@synth (adsr (osc sine) 8n 8n 0.5 4n) +# Try each of these envelopes one at a time to get a feel for what ADSR does. +@synth (adsr (osc sine) 8n 8n 1 4n) +# @synth (adsr (osc sine) 0 0 1 0) +# @synth (adsr (osc sine) 4n 0 1 2n) +# @synth (adsr (osc sine) 16n 16n 0.2 8n) + +play @synth (rhythm [4n]) (notes [c4 d4 e4 f4 g4 a4 b4 c5]) ``` #### Filter - `+ (filter )` @@ -351,7 +403,7 @@ Primitive values: - **numbers** - integers and floats (`0`, `0.25`, `10000`, etc.) - **lists** (space-separated) - `[0 1 2 3 4 5 6]` - **notes** - `e3`, `d#4`, `f2`, etc. -- **rhythm** - `32t`, `32n`, `16t`, `16n`, `8t`, `8n`, `4t`, `4n`, `2n`, and `1n` +- **rhythm** - `32t`, `32n`, `16t`, `16n`, `8t`, `8n`, `4t`, `4n`, `2n`, `2t`, and `1n` - **rests** - `r32t`, `r32n`, `r16t`, `r16n`, `r8t`, `r8n`, `r4t`, `r4n`, `r2n`, and `r1n` - **special strings** - some functions take string arguments, such as `filter` and `osc` diff --git a/helpers/BufferLoader.js b/helpers/BufferLoader.js deleted file mode 100644 index a1258db..0000000 --- a/helpers/BufferLoader.js +++ /dev/null @@ -1,50 +0,0 @@ -// an abstraction written by Boris Smus, -// taken from http://www.html5rocks.com/en/tutorials/webaudio/intro/ -// ... thanks Boris! - -export default function BufferLoader(context, urlList, callback) { - this.context = context; - this.urlList = urlList; - this.onload = callback; - this.bufferList = []; - this.loadCount = 0; -} - -BufferLoader.prototype.loadBuffer = function(url, index) { - // Load buffer asynchronously - var request = new XMLHttpRequest(); - request.open("GET", url, true); - request.responseType = "arraybuffer"; - - var loader = this; - - request.onload = function() { - // Asynchronously decode the audio file data in request.response - loader.context.decodeAudioData( - request.response, - function(buffer) { - if (!buffer) { - console.error('BufferLoader: error decoding file data from url: ' + url); - return; - } - loader.bufferList[index] = buffer; - if (++loader.loadCount == loader.urlList.length) - loader.onload(loader.bufferList); - }, - function(error) { - console.error('BufferLoader: decodeAudioData error', error); - } - ); - }; - - request.onerror = function() { - console.log('BufferLoader: error decoding', url); - }; - - request.send(); -}; - -BufferLoader.prototype.load = function() { - for (var i = 0; i < this.urlList.length; ++i) - this.loadBuffer(this.urlList[i], i); -}; diff --git a/helpers/context.js b/helpers/context.js deleted file mode 100644 index 5d47d00..0000000 --- a/helpers/context.js +++ /dev/null @@ -1,9 +0,0 @@ -let context; - -if (window.webkitAudioContext) { - context = new webkitAudioContext(); -} else { - context = new AudioContext(); -} - -export default context; diff --git a/helpers/drumMap.js b/helpers/drumMap.js deleted file mode 100644 index 74f8d7c..0000000 --- a/helpers/drumMap.js +++ /dev/null @@ -1,52 +0,0 @@ -const drumMap = { - 0: { - file: '/audio/acoustic/kick1.mp3', - label: 'acoustic kick 1' - }, - 1: { - file: '/audio/acoustic/kick2.mp3', - label: 'acoustic kick 2' - }, - 2: { - file: '/audio/acoustic/kick3.mp3', - label: 'acoustic kick 3' - }, - 3: { - file: '/audio/acoustic/snare1.mp3', - label: 'acoustic snare 1' - }, - 4: { - file: '/audio/acoustic/snare2.mp3', - label: 'acoustic snare 2' - }, - 5: { - file: '/audio/acoustic/snare3.mp3', - label: 'acoustic snare 3' - }, - 6: { - file: '/audio/acoustic/hihat1.mp3', - label: 'acoustic hat 1' - }, - 7: { - file: '/audio/acoustic/hihat2.mp3', - label: 'acoustic hat 2' - }, - 8: { - file: '/audio/acoustic/hihat_open1.mp3', - label: 'acoustic hat (open) 1' - }, - 9: { - file: '/audio/acoustic/hihat_open2.mp3', - label: 'acoustic hat (open) 2' - }, - 10: { - file: '/audio/acoustic/hihat_open3.mp3', - label: 'acoustic hat (open) 3' - }, - 11: { - file: '/audio/acoustic/rim1.mp3', - label: 'acoustic rim' - }, -}; - -export default drumMap; diff --git a/helpers/mtof.js b/helpers/mtof.js deleted file mode 100644 index 9038170..0000000 --- a/helpers/mtof.js +++ /dev/null @@ -1,3 +0,0 @@ -export default function mtof(note) { - return ( Math.pow(2, ( note-69 ) / 12) ) * 440.0; -} diff --git a/package-lock.json b/package-lock.json index 3d29cb6..cdf99a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1064,6 +1064,16 @@ "long": "^3.2.0" } }, + "accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "dev": true, + "requires": { + "mime-types": "~2.1.18", + "negotiator": "0.6.1" + } + }, "acorn": { "version": "5.5.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.5.3.tgz", @@ -1096,6 +1106,52 @@ } } }, + "adjust-sourcemap-loader": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-1.2.0.tgz", + "integrity": "sha512-958oaHHVEXMvsY7v7cC5gEkNIcoaAVIhZ4mBReYVZJOTP9IgKmzLjIOhTtzpLMu+qriXvLsVjJ155EeInp45IQ==", + "dev": true, + "requires": { + "assert": "^1.3.0", + "camelcase": "^1.2.1", + "loader-utils": "^1.1.0", + "lodash.assign": "^4.0.1", + "lodash.defaults": "^3.1.2", + "object-path": "^0.9.2", + "regex-parser": "^2.2.9" + }, + "dependencies": { + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true + }, + "lodash.defaults": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-3.1.2.tgz", + "integrity": "sha1-xzCLGNv4vJNy1wGnNJPGEZK9Liw=", + "dev": true, + "requires": { + "lodash.assign": "^3.0.0", + "lodash.restparam": "^3.0.0" + }, + "dependencies": { + "lodash.assign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz", + "integrity": "sha1-POnwI0tLIiPilrj6CsH+6OvKZPo=", + "dev": true, + "requires": { + "lodash._baseassign": "^3.0.0", + "lodash._createassigner": "^3.0.0", + "lodash.keys": "^3.0.0" + } + } + } + } + } + }, "adsr-envelope": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/adsr-envelope/-/adsr-envelope-1.0.0.tgz", @@ -1113,6 +1169,12 @@ "json-schema-traverse": "^0.3.0" } }, + "ajv-errors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.0.tgz", + "integrity": "sha1-7PAh+hCP0X37Xms4Py3SM+Mf/Fk=", + "dev": true + }, "ajv-keywords": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", @@ -1125,6 +1187,12 @@ "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", "dev": true }, + "ansi-colors": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.1.0.tgz", + "integrity": "sha512-hTv1qPdi+sVEk3jYsdjox5nQI0C9HTbjKShbCdYLKb1LOfNbb7wsF4d7OEKIZoxIHx02tSp3m94jcPW2EfMjmA==", + "dev": true + }, "ansi-escapes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", @@ -1174,6 +1242,12 @@ "sprintf-js": "~1.0.2" } }, + "arity-n": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arity-n/-/arity-n-1.0.4.tgz", + "integrity": "sha1-2edrEXM+CFacCEeuezmyhgswt0U=", + "dev": true + }, "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", @@ -1198,6 +1272,12 @@ "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", "dev": true }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "dev": true + }, "array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", @@ -2274,6 +2354,44 @@ "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", "dev": true }, + "body-parser": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", + "requires": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + } + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2399,6 +2517,11 @@ "electron-to-chromium": "^1.2.7" } }, + "bson": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.0.tgz", + "integrity": "sha512-9Aeai9TacfNtWXOYarkFJRW2CWo+dRon+fuLZYJmvLV3+MiUp0bEI6IAZfXEIg7/Pl/7IWlLaDnhzTsD81etQA==" + }, "buffer": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", @@ -2434,6 +2557,11 @@ "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", "dev": true }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, "cacache": { "version": "10.0.4", "resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz", @@ -2516,6 +2644,16 @@ "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", "dev": true }, + "camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", + "dev": true, + "requires": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, "camelcase": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", @@ -2687,6 +2825,32 @@ } } }, + "clean-css": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", + "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", + "dev": true, + "requires": { + "source-map": "~0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "clean-webpack-plugin": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-0.1.19.tgz", + "integrity": "sha512-M1Li5yLHECcN2MahoreuODul5LkjohJGFxLPTjl3j1ttKrF5rgjZET1SJduuqxLAuT1gAPOdkhg03qcaaU1KeA==", + "dev": true, + "requires": { + "rimraf": "^2.6.1" + } + }, "cli-cursor": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", @@ -2927,6 +3091,15 @@ "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", "dev": true }, + "compose-function": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/compose-function/-/compose-function-3.0.3.tgz", + "integrity": "sha1-ntZ18TzFRQHTCVCkhv9qe6OrGF8=", + "dev": true, + "requires": { + "arity-n": "^1.0.4" + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2966,12 +3139,35 @@ "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", "dev": true }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", + "dev": true + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, "convert-source-map": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", "dev": true }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, "copy-concurrently": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", @@ -3071,6 +3267,26 @@ "randomfill": "^1.0.3" } }, + "css": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", + "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "source-map": "^0.6.1", + "source-map-resolve": "^0.5.2", + "urix": "^0.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "css-color-names": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", @@ -3099,6 +3315,18 @@ "source-list-map": "^2.0.0" } }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dev": true, + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, "css-selector-tokenizer": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz", @@ -3123,6 +3351,12 @@ } } }, + "css-what": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", + "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=", + "dev": true + }, "cssesc": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", @@ -3330,6 +3564,11 @@ "rimraf": "^2.2.8" } }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, "des.js": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", @@ -3340,6 +3579,12 @@ "minimalistic-assert": "^1.0.0" } }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, "detect-conflict": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/detect-conflict/-/detect-conflict-1.0.1.tgz", @@ -3408,12 +3653,64 @@ "esutils": "^2.0.2" } }, + "dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, + "requires": { + "utila": "~0.4" + } + }, + "dom-serializer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", + "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", + "dev": true, + "requires": { + "domelementtype": "~1.1.1", + "entities": "~1.1.1" + }, + "dependencies": { + "domelementtype": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", + "dev": true + } + } + }, "domain-browser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", "dev": true }, + "domelementtype": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", + "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", + "dev": true + }, + "domhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.1.0.tgz", + "integrity": "sha1-0mRvXlf2w7qxHPbLBdPArPdBJZQ=", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", @@ -3438,11 +3735,15 @@ "integrity": "sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg==", "dev": true }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, "ejs": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.1.tgz", - "integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==", - "dev": true + "integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==" }, "electron-to-chromium": { "version": "1.3.48", @@ -3477,6 +3778,12 @@ "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", "dev": true }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, "end-of-stream": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", @@ -3497,6 +3804,12 @@ "tapable": "^1.0.0" } }, + "entities": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", + "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", + "dev": true + }, "envinfo": { "version": "5.8.1", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-5.8.1.tgz", @@ -3584,6 +3897,12 @@ "es5-ext": "~0.10.14" } }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -3794,6 +4113,12 @@ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", "dev": true }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true + }, "events": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", @@ -3935,6 +4260,123 @@ "homedir-polyfill": "^1.0.1" } }, + "express": { + "version": "4.16.3", + "resolved": "http://registry.npmjs.org/express/-/express-4.16.3.tgz", + "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "array-flatten": "1.1.1", + "body-parser": "1.18.2", + "content-disposition": "0.5.2", + "content-type": "~1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.1.1", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.3", + "qs": "6.5.1", + "range-parser": "~1.2.0", + "safe-buffer": "5.1.1", + "send": "0.16.2", + "serve-static": "1.13.2", + "setprototypeof": "1.1.0", + "statuses": "~1.4.0", + "type-is": "~1.6.16", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "body-parser": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "dev": true, + "requires": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.1", + "http-errors": "~1.6.2", + "iconv-lite": "0.4.19", + "on-finished": "~2.3.0", + "qs": "6.5.1", + "raw-body": "2.3.2", + "type-is": "~1.6.15" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", + "dev": true + }, + "raw-body": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "dev": true, + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.2", + "iconv-lite": "0.4.19", + "unpipe": "1.0.0" + }, + "dependencies": { + "depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=", + "dev": true + }, + "http-errors": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "dev": true, + "requires": { + "depd": "1.1.1", + "inherits": "2.0.3", + "setprototypeof": "1.0.3", + "statuses": ">= 1.3.1 < 2" + } + }, + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=", + "dev": true + } + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "dev": true + } + } + }, "extend-shallow": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", @@ -4089,6 +4531,59 @@ "object-assign": "^4.0.1" } }, + "file-loader": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-2.0.0.tgz", + "integrity": "sha512-YCsBfd1ZGCyonOKLxPiKPdu+8ld9HAaMEvJewzz+b2eTF7uL5Zm/HdBF6FjCrpCMRq25Mi0U1gl4pwn2TlH7hQ==", + "dev": true, + "requires": { + "loader-utils": "^1.0.2", + "schema-utils": "^1.0.0" + }, + "dependencies": { + "ajv": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.4.tgz", + "integrity": "sha512-4Wyjt8+t6YszqaXnLDfMmG/8AlO5Zbcsy3ATHncCzjW/NoPzAId8AK6749Ybjmdt+kUY1gP60fCu46oDxPv/mg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", + "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", + "dev": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, "filename-regex": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", @@ -4118,6 +4613,32 @@ } } }, + "finalhandler": { + "version": "1.1.1", + "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, "find-cache-dir": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", @@ -4217,6 +4738,12 @@ "for-in": "^1.0.1" } }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "dev": true + }, "fragment-cache": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", @@ -4226,6 +4753,12 @@ "map-cache": "^0.2.2" } }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, "from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", @@ -5185,6 +5718,12 @@ "minimalistic-assert": "^1.0.0" } }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -5227,12 +5766,138 @@ "integrity": "sha1-ZouTd26q5V696POtRkswekljYl4=", "dev": true }, + "html-minifier": { + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.20.tgz", + "integrity": "sha512-ZmgNLaTp54+HFKkONyLFEfs5dd/ZOtlquKaTnqIWFmx3Av5zG6ZPcV2d0o9XM2fXOTxxIf6eDcwzFFotke/5zA==", + "dev": true, + "requires": { + "camel-case": "3.0.x", + "clean-css": "4.2.x", + "commander": "2.17.x", + "he": "1.1.x", + "param-case": "2.1.x", + "relateurl": "0.2.x", + "uglify-js": "3.4.x" + }, + "dependencies": { + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "uglify-js": { + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", + "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", + "dev": true, + "requires": { + "commander": "~2.17.1", + "source-map": "~0.6.1" + } + } + } + }, + "html-webpack-plugin": { + "version": "3.2.0", + "resolved": "http://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", + "integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=", + "dev": true, + "requires": { + "html-minifier": "^3.2.3", + "loader-utils": "^0.2.16", + "lodash": "^4.17.3", + "pretty-error": "^2.0.2", + "tapable": "^1.0.0", + "toposort": "^1.0.0", + "util.promisify": "1.0.0" + }, + "dependencies": { + "loader-utils": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "dev": true, + "requires": { + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0", + "object-assign": "^4.0.1" + } + } + } + }, + "htmlparser2": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz", + "integrity": "sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4=", + "dev": true, + "requires": { + "domelementtype": "1", + "domhandler": "2.1", + "domutils": "1.1", + "readable-stream": "1.0" + }, + "dependencies": { + "domutils": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.1.6.tgz", + "integrity": "sha1-vdw94Jm5ou+sxRxiPyj0FuzFdIU=", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, "http-cache-semantics": { "version": "3.8.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", "dev": true }, + "http-errors": { + "version": "1.6.3", + "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, "https-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", @@ -5243,7 +5908,6 @@ "version": "0.4.23", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", - "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" } @@ -5440,6 +6104,12 @@ "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", "dev": true }, + "ipaddr.js": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", + "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=", + "dev": true + }, "is-absolute-url": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", @@ -5780,6 +6450,12 @@ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -6313,18 +6989,104 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" }, + "lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "dev": true, + "requires": { + "lodash._basecopy": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "lodash._bindcallback": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", + "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=", + "dev": true + }, + "lodash._createassigner": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz", + "integrity": "sha1-g4pbri/aymOsIt7o4Z+k5taXCxE=", + "dev": true, + "requires": { + "lodash._bindcallback": "^3.0.0", + "lodash._isiterateecall": "^3.0.0", + "lodash.restparam": "^3.0.0" + } + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=", + "dev": true + }, "lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", "dev": true }, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=", + "dev": true + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", "dev": true }, + "lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", + "dev": true + }, "lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -6398,6 +7160,12 @@ "js-tokens": "^3.0.0" } }, + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", + "dev": true + }, "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", @@ -6474,6 +7242,11 @@ "inherits": "^2.0.1" } }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, "mem": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", @@ -6578,12 +7351,30 @@ "readable-stream": "^2.0.1" } }, + "memory-pager": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.1.0.tgz", + "integrity": "sha512-Mf9OHV/Y7h6YWDxTzX/b4ZZ4oh9NSXblQL8dtPCOomOtZciEHxePR78+uHFLLlsk01A6jVHhHsQZZ/WcIPpnzg==", + "optional": true + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, "merge2": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.2.tgz", "integrity": "sha512-bgM8twH86rWni21thii6WCMQMRMmwqqdW3sGWi9IipnVAszdLXRjwDwAnyrVXo6DuP3AjRMMttZKUB48QWIFGg==", "dev": true }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -6615,6 +7406,25 @@ "brorand": "^1.0.1" } }, + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "dev": true + }, + "mime-db": { + "version": "1.36.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz", + "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==" + }, + "mime-types": { + "version": "2.1.20", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz", + "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==", + "requires": { + "mime-db": "~1.36.0" + } + }, "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", @@ -6627,6 +7437,60 @@ "integrity": "sha1-3z02Uqc/3ta5sLJBRub9BSNTRY4=", "dev": true }, + "mini-css-extract-plugin": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.4.3.tgz", + "integrity": "sha512-Mxs0nxzF1kxPv4TRi2NimewgXlJqh0rGE30vviCU2WHrpbta6wklnUV9dr9FUtoAHmB3p3LeXEC+ZjgHvB0Dzg==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "schema-utils": "^1.0.0", + "webpack-sources": "^1.1.0" + }, + "dependencies": { + "ajv": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.4.tgz", + "integrity": "sha512-4Wyjt8+t6YszqaXnLDfMmG/8AlO5Zbcsy3ATHncCzjW/NoPzAId8AK6749Ybjmdt+kUY1gP60fCu46oDxPv/mg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", + "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", + "dev": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -6702,6 +7566,26 @@ "minimist": "0.0.8" } }, + "mongodb": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.1.6.tgz", + "integrity": "sha512-E5QJuXQoMlT7KyCYqNNMfAkhfQD79AT4F8Xd+6x37OX+8BL17GyXyWvfm6wuyx4wnzCCPoCSLeMeUN2S7dU9yw==", + "requires": { + "mongodb-core": "3.1.5", + "safe-buffer": "^5.1.2" + } + }, + "mongodb-core": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-3.1.5.tgz", + "integrity": "sha512-emT/tM4ZBinqd6RZok+EzDdtN4LjYJIckv71qQVOEFmvXgT5cperZegVmTgox/1cx4XQu6LJ5ZuIwipP/eKdQg==", + "requires": { + "bson": "^1.1.0", + "require_optional": "^1.0.1", + "safe-buffer": "^5.1.2", + "saslprep": "^1.0.0" + } + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -6719,8 +7603,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "multimatch": { "version": "2.1.0", @@ -6773,6 +7656,12 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", + "dev": true + }, "neo-async": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.5.1.tgz", @@ -6790,6 +7679,15 @@ "integrity": "sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==", "dev": true }, + "no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "requires": { + "lower-case": "^1.1.1" + } + }, "node-dir": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.8.tgz", @@ -6926,6 +7824,15 @@ "path-key": "^2.0.0" } }, + "nth-check": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", + "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", + "dev": true, + "requires": { + "boolbase": "~1.0.0" + } + }, "num2fraction": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", @@ -6981,6 +7888,12 @@ "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", "dev": true }, + "object-path": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.9.2.tgz", + "integrity": "sha1-D9mnT8X60a45aLWGvaXGMr1sBaU=", + "dev": true + }, "object-visit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", @@ -7030,6 +7943,14 @@ "util-extend": "^1.0.3" } }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -7048,6 +7969,15 @@ "mimic-fn": "^1.0.0" } }, + "opn": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.4.0.tgz", + "integrity": "sha512-YF9MNdVy/0qvJvDtunAOzFw9iasOQHpVthTCvGzxt61Il64AYSGdK+rYwld7NAfk9qJ7dt+hymBNSc9LNYS+Sw==", + "dev": true, + "requires": { + "is-wsl": "^1.1.0" + } + }, "optionator": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", @@ -7247,6 +8177,15 @@ "readable-stream": "^2.1.5" } }, + "param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", + "dev": true, + "requires": { + "no-case": "^2.2.0" + } + }, "parse-asn1": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", @@ -7304,6 +8243,12 @@ "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", "dev": true }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", + "dev": true + }, "pascalcase": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", @@ -7355,6 +8300,12 @@ "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", "dev": true }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true + }, "path-type": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", @@ -7981,6 +8932,16 @@ "integrity": "sha1-sr+C5zUNZcbDOqlaqlpPYyf2HNk=", "dev": true }, + "pretty-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", + "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=", + "dev": true, + "requires": { + "renderkid": "^2.0.1", + "utila": "~0.4" + } + }, "private": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", @@ -8011,6 +8972,16 @@ "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", "dev": true }, + "proxy-addr": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", + "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", + "dev": true, + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.8.0" + } + }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -8069,6 +9040,12 @@ "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", "dev": true }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", + "dev": true + }, "query-string": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", @@ -8130,6 +9107,29 @@ "safe-buffer": "^5.1.0" } }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", + "dev": true + }, + "raw-body": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "unpipe": "1.0.0" + } + }, + "raw-loader": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-0.5.1.tgz", + "integrity": "sha1-DD0L6u2KAclm2Xh793goElKpeao=", + "dev": true + }, "read-chunk": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/read-chunk/-/read-chunk-2.1.0.tgz", @@ -8323,6 +9323,12 @@ "safe-regex": "^1.1.0" } }, + "regex-parser": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.9.tgz", + "integrity": "sha512-VncXxOF6uFlYog5prG2j+e2UGJeam5MfNiJnB/qEgo4KTnMm2XrELCg4rNZ6IlaEUZnGlb8aB6lXowCRQtTkkA==", + "dev": true + }, "regexpp": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", @@ -8355,12 +9361,42 @@ "jsesc": "~0.5.0" } }, + "relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "dev": true + }, "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", "dev": true }, + "renderkid": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.2.tgz", + "integrity": "sha512-FsygIxevi1jSiPY9h7vZmBFUbAOcbYm9UwyiLNdVsLRs/5We9Ob5NMPbGYUTWiLq5L+ezlVdE0A8bbME5CWTpg==", + "dev": true, + "requires": { + "css-select": "^1.1.0", + "dom-converter": "~0.2", + "htmlparser2": "~3.3.0", + "strip-ansi": "^3.0.0", + "utila": "^0.4.0" + }, + "dependencies": { + "strip-ansi": { + "version": "3.0.1", + "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, "repeat-element": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", @@ -8410,6 +9446,22 @@ "resolve-from": "^1.0.0" } }, + "require_optional": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", + "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", + "requires": { + "resolve-from": "^2.0.0", + "semver": "^5.1.0" + }, + "dependencies": { + "resolve-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", + "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" + } + } + }, "resolve": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", @@ -8458,6 +9510,55 @@ "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", "dev": true }, + "resolve-url-loader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-3.0.0.tgz", + "integrity": "sha512-ZzRUnpu+pLkrN2ZBsEEifOD6W+9ZNtdIu+kY3vs+11PwuZ2WykxbAY9qO+S9SmasSDRllxNdSm9IhN8HU4xGKg==", + "dev": true, + "requires": { + "adjust-sourcemap-loader": "^1.1.0", + "camelcase": "^4.1.0", + "compose-function": "^3.0.3", + "convert-source-map": "^1.5.1", + "es6-iterator": "^2.0.3", + "loader-utils": "^1.1.0", + "lodash.defaults": "^4.0.0", + "postcss": "^7.0.0", + "rework": "^1.0.1", + "rework-visit": "^1.0.0", + "source-map": "^0.5.7" + }, + "dependencies": { + "postcss": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.5.tgz", + "integrity": "sha512-HBNpviAUFCKvEh7NZhw1e8MBPivRszIiUnhrJ+sBFVSYSqubrzwX3KG51mYgcRHX8j/cAgZJedONZcm5jTBdgQ==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "responselike": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", @@ -8483,6 +9584,30 @@ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, + "rework": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rework/-/rework-1.0.1.tgz", + "integrity": "sha1-MIBqhBNCtUUQqkEQhQzUhTQUSqc=", + "dev": true, + "requires": { + "convert-source-map": "^0.3.3", + "css": "^2.0.0" + }, + "dependencies": { + "convert-source-map": { + "version": "0.3.5", + "resolved": "http://registry.npmjs.org/convert-source-map/-/convert-source-map-0.3.5.tgz", + "integrity": "sha1-8dgClQr33SYxof6+BZZVDIarMZA=", + "dev": true + } + } + }, + "rework-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rework-visit/-/rework-visit-1.0.0.tgz", + "integrity": "sha1-mUWygD8hni96ygCtuLyfZA+ELJo=", + "dev": true + }, "rimraf": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", @@ -8547,8 +9672,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -8562,8 +9686,16 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "saslprep": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.2.tgz", + "integrity": "sha512-4cDsYuAjXssUSjxHKRe4DTZC0agDwsCqcMqtJAQPzC74nJ7LfAJflAtC1Zed5hMzEQKj82d3tuzqdGNRsLJ4Gw==", + "optional": true, + "requires": { + "sparse-bitfield": "^3.0.3" + } }, "sax": { "version": "1.2.4", @@ -8616,8 +9748,39 @@ "semver": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", - "dev": true + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" + }, + "send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } }, "serialize-javascript": { "version": "1.5.0", @@ -8625,6 +9788,18 @@ "integrity": "sha512-Ga8c8NjAAp46Br4+0oZ2WxJCwIzwP60Gq1YPgU+39PiTVxyed/iKE/zyZI6+UlVYH5Q4PaQdHhcegIFPZTUfoQ==", "dev": true }, + "serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.2" + } + }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -8666,6 +9841,11 @@ "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", "dev": true }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, "sha.js": { "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", @@ -8894,6 +10074,15 @@ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", "dev": true }, + "sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", + "optional": true, + "requires": { + "memory-pager": "^1.0.2" + } + }, "spdx-correct": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", @@ -8971,6 +10160,11 @@ } } }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + }, "stereo-panner-node": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/stereo-panner-node/-/stereo-panner-node-1.4.0.tgz", @@ -9324,6 +10518,22 @@ "tonal-note": "^1.1.2", "tonal-pcset": "^1.1.2", "tonal-scale": "^1.1.2" + }, + "dependencies": { + "tonal-distance": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tonal-distance/-/tonal-distance-1.1.2.tgz", + "integrity": "sha512-1tigEt+qrCGNHVsNJdBwyN/+nYr3kPNobLvKP/TWMGipArMAh01zNuRb/mGHym0ov2M0AuYo05khRG3C7v6zxA==", + "requires": { + "tonal-interval": "^1.1.2", + "tonal-note": "^1.1.2" + } + }, + "tonal-interval": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tonal-interval/-/tonal-interval-1.1.2.tgz", + "integrity": "sha512-Kj3GPWqRYuQN0SeE86CxLMMgm++fPueWLDHONmx1CZw6NMIyMpml3DQkRpY0PqNpr3y6TbQ0NP2jBAHVLtwEpg==" + } } }, "tonal-array": { @@ -9343,6 +10553,22 @@ "tonal-distance": "^1.1.2", "tonal-note": "^1.1.2", "tonal-pcset": "^1.1.2" + }, + "dependencies": { + "tonal-distance": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tonal-distance/-/tonal-distance-1.1.2.tgz", + "integrity": "sha512-1tigEt+qrCGNHVsNJdBwyN/+nYr3kPNobLvKP/TWMGipArMAh01zNuRb/mGHym0ov2M0AuYo05khRG3C7v6zxA==", + "requires": { + "tonal-interval": "^1.1.2", + "tonal-note": "^1.1.2" + } + }, + "tonal-interval": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tonal-interval/-/tonal-interval-1.1.2.tgz", + "integrity": "sha512-Kj3GPWqRYuQN0SeE86CxLMMgm++fPueWLDHONmx1CZw6NMIyMpml3DQkRpY0PqNpr3y6TbQ0NP2jBAHVLtwEpg==" + } } }, "tonal-dictionary": { @@ -9356,18 +10582,25 @@ } }, "tonal-distance": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/tonal-distance/-/tonal-distance-1.1.2.tgz", - "integrity": "sha512-1tigEt+qrCGNHVsNJdBwyN/+nYr3kPNobLvKP/TWMGipArMAh01zNuRb/mGHym0ov2M0AuYo05khRG3C7v6zxA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tonal-distance/-/tonal-distance-2.0.0.tgz", + "integrity": "sha512-RnY3r4FQwjyXE0eR3qjUG8hfs6vBPXThk9y94eU7wMQ2mLKinP2dW/DdgUF6ORnJ36jnTCyYLEAPp23yxrfXDw==", "requires": { - "tonal-interval": "^1.1.2", - "tonal-note": "^1.1.2" + "tonal-interval": "^2.0.0", + "tonal-note": "^2.0.0" + }, + "dependencies": { + "tonal-note": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tonal-note/-/tonal-note-2.0.0.tgz", + "integrity": "sha512-ZUeAloZQE2L3lhobVn7yCX7tT0x+iim26EIkZt8P7f5i4LCxbw/NifS2MgdcCL0Dh+CnaoGD/YMH9QhRmX7Tig==" + } } }, "tonal-interval": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/tonal-interval/-/tonal-interval-1.1.2.tgz", - "integrity": "sha512-Kj3GPWqRYuQN0SeE86CxLMMgm++fPueWLDHONmx1CZw6NMIyMpml3DQkRpY0PqNpr3y6TbQ0NP2jBAHVLtwEpg==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tonal-interval/-/tonal-interval-2.0.0.tgz", + "integrity": "sha512-pXobehztDC/dlZsQT5KOca/7wszBjrAULXFlvpzzWPgsCv3808fdN6mR3g/dTZOEG47AP6CpzAxZzNBgbJbCXg==" }, "tonal-key": { "version": "1.1.2", @@ -9377,6 +10610,22 @@ "tonal-array": "^1.1.2", "tonal-distance": "^1.1.2", "tonal-note": "^1.1.2" + }, + "dependencies": { + "tonal-distance": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tonal-distance/-/tonal-distance-1.1.2.tgz", + "integrity": "sha512-1tigEt+qrCGNHVsNJdBwyN/+nYr3kPNobLvKP/TWMGipArMAh01zNuRb/mGHym0ov2M0AuYo05khRG3C7v6zxA==", + "requires": { + "tonal-interval": "^1.1.2", + "tonal-note": "^1.1.2" + } + }, + "tonal-interval": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tonal-interval/-/tonal-interval-1.1.2.tgz", + "integrity": "sha512-Kj3GPWqRYuQN0SeE86CxLMMgm++fPueWLDHONmx1CZw6NMIyMpml3DQkRpY0PqNpr3y6TbQ0NP2jBAHVLtwEpg==" + } } }, "tonal-note": { @@ -9392,6 +10641,13 @@ "tonal-array": "^1.1.2", "tonal-interval": "^1.1.2", "tonal-note": "^1.1.2" + }, + "dependencies": { + "tonal-interval": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tonal-interval/-/tonal-interval-1.1.2.tgz", + "integrity": "sha512-Kj3GPWqRYuQN0SeE86CxLMMgm++fPueWLDHONmx1CZw6NMIyMpml3DQkRpY0PqNpr3y6TbQ0NP2jBAHVLtwEpg==" + } } }, "tonal-range": { @@ -9403,6 +10659,22 @@ "tonal-distance": "^1.1.2", "tonal-note": "^1.1.2", "tonal-pcset": "^1.1.2" + }, + "dependencies": { + "tonal-distance": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tonal-distance/-/tonal-distance-1.1.2.tgz", + "integrity": "sha512-1tigEt+qrCGNHVsNJdBwyN/+nYr3kPNobLvKP/TWMGipArMAh01zNuRb/mGHym0ov2M0AuYo05khRG3C7v6zxA==", + "requires": { + "tonal-interval": "^1.1.2", + "tonal-note": "^1.1.2" + } + }, + "tonal-interval": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tonal-interval/-/tonal-interval-1.1.2.tgz", + "integrity": "sha512-Kj3GPWqRYuQN0SeE86CxLMMgm++fPueWLDHONmx1CZw6NMIyMpml3DQkRpY0PqNpr3y6TbQ0NP2jBAHVLtwEpg==" + } } }, "tonal-scale": { @@ -9415,8 +10687,30 @@ "tonal-distance": "^1.1.2", "tonal-note": "^1.1.2", "tonal-pcset": "^1.1.2" + }, + "dependencies": { + "tonal-distance": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tonal-distance/-/tonal-distance-1.1.2.tgz", + "integrity": "sha512-1tigEt+qrCGNHVsNJdBwyN/+nYr3kPNobLvKP/TWMGipArMAh01zNuRb/mGHym0ov2M0AuYo05khRG3C7v6zxA==", + "requires": { + "tonal-interval": "^1.1.2", + "tonal-note": "^1.1.2" + } + }, + "tonal-interval": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tonal-interval/-/tonal-interval-1.1.2.tgz", + "integrity": "sha512-Kj3GPWqRYuQN0SeE86CxLMMgm++fPueWLDHONmx1CZw6NMIyMpml3DQkRpY0PqNpr3y6TbQ0NP2jBAHVLtwEpg==" + } } }, + "toposort": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz", + "integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=", + "dev": true + }, "trim-right": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", @@ -9449,6 +10743,15 @@ "prelude-ls": "~1.1.2" } }, + "type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.18" + } + }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -9596,6 +10899,11 @@ "imurmurhash": "^0.1.4" } }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, "unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -9648,6 +10956,12 @@ "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", "dev": true }, + "upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", + "dev": true + }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", @@ -9743,6 +11057,24 @@ "object.getownpropertydescriptors": "^2.0.3" } }, + "utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, "v8-compile-cache": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.0.tgz", @@ -9759,6 +11091,12 @@ "spdx-expression-parse": "^3.0.0" } }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, "vendors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.2.tgz", @@ -10112,6 +11450,36 @@ } } }, + "webpack-dev-middleware": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.4.0.tgz", + "integrity": "sha512-Q9Iyc0X9dP9bAsYskAVJ/hmIZZQwf/3Sy4xCAZgL5cUkjZmUZLt4l5HpbST/Pdgjn3u6pE7u5OdGd1apgzRujA==", + "dev": true, + "requires": { + "memory-fs": "~0.4.1", + "mime": "^2.3.1", + "range-parser": "^1.0.3", + "webpack-log": "^2.0.0" + }, + "dependencies": { + "mime": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", + "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==", + "dev": true + } + } + }, + "webpack-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", + "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", + "dev": true, + "requires": { + "ansi-colors": "^3.0.0", + "uuid": "^3.3.2" + } + }, "webpack-sources": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.1.0.tgz", diff --git a/package.json b/package.json index 4a55715..88ef280 100644 --- a/package.json +++ b/package.json @@ -4,18 +4,28 @@ "description": "", "main": "index.js", "scripts": { - "watch": "webpack -d --watch", - "build": "webpack --config webpack.prod.js" + "prestart": "npm run lint", + "start": "echo 'STARTING' && npm run dev:webpack && npm run dev:server", + "dev:webpack": "webpack --config packing/webpack.config.js --progress --hide-modules", + "dev:server": "node server/server.js", + "lint": "echo 'LINTING' && eslint --ext .js src packing && echo 'Syntax is ok...'", + "prebuild": "npm run lint", + "build": "echo 'BUILDING' && webpack --config packing/webpack.prod.js" }, "author": "", "license": "ISC", "dependencies": { "adsr-envelope": "^1.0.0", + "body-parser": "^1.18.3", "codemirror": "^5.38.0", + "ejs": "^2.6.1", "lodash": "^4.17.10", + "mongodb": "^3.1.6", "ohm-js": "^0.14.0", "stereo-panner-node": "^1.4.0", "tonal": "^1.1.3", + "tonal-distance": "^2.0.0", + "tonal-interval": "^2.0.0", "tonal-range": "^1.1.2", "tonal-scale": "^1.1.2", "tunajs": "^1.0.1" @@ -24,12 +34,21 @@ "@babel/core": "^7.1.2", "@babel/preset-env": "^7.1.0", "babel-loader": "^8.0.4", + "clean-webpack-plugin": "^0.1.19", "css-loader": "^0.28.11", "eslint": "^4.19.1", "eslint-config-airbnb-base": "^12.1.0", "eslint-plugin-import": "^2.12.0", + "express": "^4.16.3", + "file-loader": "^2.0.0", + "html-webpack-plugin": "^3.2.0", + "mini-css-extract-plugin": "^0.4.3", + "opn": "^5.4.0", + "raw-loader": "^0.5.1", + "resolve-url-loader": "^3.0.0", "style-loader": "^0.21.0", "webpack": "^4.10.1", - "webpack-cli": "^2.1.4" + "webpack-cli": "^2.1.4", + "webpack-dev-middleware": "^3.4.0" } } diff --git a/packing/webpack.config.js b/packing/webpack.config.js new file mode 100644 index 0000000..9327a56 --- /dev/null +++ b/packing/webpack.config.js @@ -0,0 +1,82 @@ +const path = require('path'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const CleanWebpackPlugin = require('clean-webpack-plugin'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); + +module.exports = { + mode: 'development', + devtool: 'source-map', + watch: false, + entry: { + app: './src/editor.js', + }, + resolve: { + symlinks: false, + }, + output: { + filename: 'slang.[name].js', + path: path.resolve(__dirname, '../dist'), + publicPath: '/', + pathinfo: false, + }, + module: { + rules: [ + { + test: /\.m?js$/, + exclude: /(node_modules|bower_components)/, + use: { + loader: 'babel-loader', + options: { + presets: ['@babel/preset-env'], + }, + }, + }, + { + test: /\.ohm$/, + use: ['raw-loader'], + }, + { + test: /\.css$/, + use: [ + { + loader: MiniCssExtractPlugin.loader, + options: { + publicPath: '/css', + name: path.posix.join('/css', '[name].[ext]'), + }, + }, + 'css-loader', + ], + }, + { + test: /\.(png|jpe?g|gif|svg)$/, + loader: 'file-loader', + options: { + limit: 100, + name: path.posix.join('img', '[name].[ext]'), + }, + }, + { + test: /\.(mp3)$/, + loader: 'file-loader', + options: { + limit: 10000, + name: path.posix.join('audio', '[name].[ext]'), + }, + }, + ], + }, + plugins: [ + new CleanWebpackPlugin(['dist']), + new HtmlWebpackPlugin({ + filename: 'index.html', + template: './src/static/editor.html', + title: 'SLANG', + inject: true, + }), + new MiniCssExtractPlugin({ + filename: '[name].css', + chunkFilename: '[id].css', + }), + ], +}; diff --git a/packing/webpack.prod.js b/packing/webpack.prod.js new file mode 100644 index 0000000..02c00c9 --- /dev/null +++ b/packing/webpack.prod.js @@ -0,0 +1,81 @@ +const path = require('path'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const CleanWebpackPlugin = require('clean-webpack-plugin'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); + +module.exports = { + mode: 'production', + watch: false, + entry: { + app: './src/editor.js', + }, + resolve: { + symlinks: false, + }, + output: { + filename: 'site.min.js', + path: path.resolve(__dirname, '../dist'), + publicPath: '/', + pathinfo: false, + }, + module: { + rules: [ + { + test: /\.m?js$/, + exclude: /(node_modules|bower_components)/, + use: { + loader: 'babel-loader', + options: { + presets: ['@babel/preset-env'], + }, + }, + }, + { + test: /\.ohm$/, + use: ['raw-loader'], + }, + { + test: /\.css$/, + use: [ + { + loader: MiniCssExtractPlugin.loader, + options: { + publicPath: '/css', + name: path.posix.join('/css', '[name].[ext]'), + }, + }, + 'css-loader', + ], + }, + { + test: /\.(png|jpe?g|gif|svg)$/, + loader: 'file-loader', + options: { + limit: 100, + name: path.posix.join('img', '[name].[ext]'), + }, + }, + { + test: /\.(mp3)$/, + loader: 'file-loader', + options: { + limit: 10000, + name: path.posix.join('audio', '[name].[ext]'), + }, + }, + ], + }, + plugins: [ + new CleanWebpackPlugin(['dist']), + new HtmlWebpackPlugin({ + filename: 'index.html', + template: './src/static/editor.html', + title: 'SLANG', + inject: true, + }), + new MiniCssExtractPlugin({ + filename: '[name].css', + chunkFilename: '[id].css', + }), + ], +}; diff --git a/public/index.html b/public/index.html deleted file mode 100644 index 1c1fb30..0000000 --- a/public/index.html +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - -
-
-
Run
-
Stop
-
-
-
Stopped
-
-
- Docs -
-
-
- - - - diff --git a/server/helpers.js b/server/helpers.js new file mode 100644 index 0000000..70359e9 --- /dev/null +++ b/server/helpers.js @@ -0,0 +1,34 @@ +// Returns a number between the min and max. +function randomRange(min, max) { + return Math.floor(Math.random() * ((max - min) + 1)) + min; +} + +/** + * Returns an encoded version of the current date using the + * provided key or the default one. + * @param {string} k - Optional encode key + * @returns {array} + */ +function createHash(k) { + // Current numerical date is never duplicated + // Use k parameter as key, of not set, use the default key + const key = typeof k === 'string' + ? k + : '1234567890qwertyuiopasdfghjklzQWERTYUIOPASDFGHJKLZXCVBNM+_-.~'; + const r = key.length; + // Date is the raw hash + let n = new Date().getTime(); + let c = ''; + + // Encode the date acording to provided key + while (n > 0) { + c = key.charAt(n % r) + c; + n = Math.floor(n / r); + } + return c; +} + +module.exports = { + createHash, + randomRange, +}; diff --git a/server/mongo/mongo.config.js b/server/mongo/mongo.config.js new file mode 100644 index 0000000..afd75b0 --- /dev/null +++ b/server/mongo/mongo.config.js @@ -0,0 +1,8 @@ +module.exports = { + HOST: '127.0.0.1', + PORT: 27017, + NAME: 'slang', + USER: 'slang', + PASS: '5l4ng+R0x', + AUTH: null, +}; diff --git a/server/mongodb.js b/server/mongodb.js new file mode 100644 index 0000000..6ac745e --- /dev/null +++ b/server/mongodb.js @@ -0,0 +1,9 @@ +const mongoose = require('mongoose'); +const MongoConfig = require('./mongo/mongo.config.js'); + +mongoose.Promise = global.Promise; + +const uri = `mongodb://${MongoConfig.HOST}:${MongoConfig.PORT}/${MongoConfig.NAME}`; +const db = mongoose.createConnection(uri); + +module.exports = db; diff --git a/server/server.js b/server/server.js new file mode 100644 index 0000000..85e51af --- /dev/null +++ b/server/server.js @@ -0,0 +1,160 @@ +const opn = require('opn'); +const path = require('path'); +const express = require('express'); +const webpack = require('webpack'); +const { MongoClient } = require('mongodb'); +const devMiddlewareFn = require('webpack-dev-middleware'); +const bodyParser = require('body-parser'); + +const webpackConfig = require('../packing/webpack.config.js'); +const helpers = require('./helpers'); +const MongoConfig = require('./mongo/mongo.config.js'); + +// default port where dev server listens for incoming traffic +const port = 5555; +// automatically open browser, if not set will be false +const autoOpenBrowser = true; +const app = express(); +const compiler = webpack(webpackConfig); +const devMiddleware = devMiddlewareFn(compiler, { + publicPath: webpackConfig.output.publicPath, + quiet: true, +}); +const notFoundPatch = `# Whoops! We couldn’t a patch at this URL. +# You get the 404 womp womp patch instead. + +@notFoundLeft (adsr (osc tri)) + (pan -1) +@notFoundRight (adsr (osc tri)) + (pan 1) + +play @notFoundLeft + (rhythm [8n 8n 8n 8n 8n r1n r32n]) + (notes [c5 b4 a#4 a4 g#4]) + +play @notFoundRight + (rhythm [r32n 8n 8n 8n 8n 8n r1n]) + (notes (transpose -5 [c5 b4 a#4 a4 g#4]))'`; + +// We are going to send JSON blobs so let's have +// bodyParser get the data ready for us. +app.use(bodyParser.json()); + +// serve webpack bundle output +app.use(devMiddleware); + +// serve pure static assets +const staticPath = path.posix.join('/'); +app.use(staticPath, express.static('./dist')); + +// Using EJS to build the patch page. +app.set('view engine', 'ejs'); +// Point to the views folder +app.set('views', path.join(__dirname, './views')); + +const uri = `http://localhost:${port}`; + +console.log('> Starting dev server...'); + +devMiddleware.waitUntilValid(() => { + console.log(`> Listening at ${uri}\n`); + // when env is testing, don't need open it + if (autoOpenBrowser) { + opn(uri); + } +}); + +// Connect to the database, add our routes, then start the server. +MongoClient.connect(`mongodb://${MongoConfig.HOST}:${MongoConfig.PORT}`, (err, client) => { + if (err) { + console.log('Oh no! The mongo database failed to start.'); + console.error(err); + return; + } + + const db = client.db(MongoConfig.NAME); + const patches = db.collection('Patch'); + + async function cleanup() { + await patches.deleteMany({ + $or: [ + { exp: { $exists: false } }, + { exp: { $lte: new Date().getTime() } }, + ], + }, (cleanupErr, data) => { + if (cleanupErr === null && data.result.n) console.log(`--- Cleanup: ${data.result.n} Patches removed`); + }); + } + + // Load a saved patch, if one exists. + app.get('/:hash', async (req, res) => { + console.log(`--- Looking for: ${req.params.hash}`); + patches.find({ hash: req.params.hash }).toArray((findErr, items) => { + const message = findErr || !items.length ? 'Not Found' : 'Found!'; + console.log(`--- ${req.params.hash} ${message}`); + // Let's be clever and present a "not found" error + // inside of the text editor itself. + let text = findErr || !items.length ? notFoundPatch : items[0].text; + + // We're using a string literal to dump out the text, so + // to avoid XSS let's remove any backticks present in the + // string itself. + text = text.replace(/`/g, ''); + res.render('patch', { patch: text }); + }); + }); + + // Save a new patch. + + // There are lots of ways we could do this, but here's what we're + // going to do and some rationale behind it: the client will send + // a POST to the /save route with the text they want to save, we'll + // respond by sending the ID as text, and the client will then + // redirect itself to the new URL, hitting the `/:id` route above. + + // We could just as well take the response on the client and display + // it as a URL that the user can copy, but because the URL represents + // the text *at the moment it was saved*, I don't want them to + // generate it, continue typing, and assume that their patch will + // update in some way. Redirecting to the new URL reinforces the fact + // that it's a one-time save. + + app.post('/save', async (req, res) => { + // Cleanup before saving + await cleanup(); + const { text } = req.body; + const lifeSpan = 2; // Delete hashs after 2 days + const exp = new Date().getTime() + (lifeSpan * 60 * 60 * 24 * 1000); + console.log('--- Saving...'); + + // Need to set some limits so you don't blow up my database! + if (!text || text.length > 10000) { + res.statusCode = '400'; + res.send(':('); + return; + } + + // No collitions anymore + const hash = helpers.createHash(); + + await patches.insertOne({ hash, text, exp }, (saveErr) => { + if (saveErr) { + res.statusCode = 500; + res.send('Error while saving your Patch'); + } + console.log(`--- ${hash} Saved!`); + res.send(hash); + }); + }); + + app.get('/list/all', async (req, res) => { + // Insert some documents + await patches.find({}).toArray((testErr, docs) => { + console.log('--- All hashes listed'); + res.send(docs); + }); + }); + + // Start the server. + app.listen(port, () => { + console.log('Slang is running on port', (process.env.PORT || 8000), 'at', Date()); + }); +}); diff --git a/server/views/patch.ejs b/server/views/patch.ejs new file mode 100644 index 0000000..13c4262 --- /dev/null +++ b/server/views/patch.ejs @@ -0,0 +1,36 @@ + + + + + + + + +
+
+
Run
+
Stop
+
+
+
Stopped
+
+
+
Create URL
+ Docs +
+
+
+ + + + + \ No newline at end of file diff --git a/slang-grammar.js b/slang-grammar.js deleted file mode 100644 index b0ab8c4..0000000 --- a/slang-grammar.js +++ /dev/null @@ -1,118 +0,0 @@ -module.exports = ` -Sound { - Line = Graph | Play | Comment - - /* - A comment is any line that begins with # - */ - - Comment = "#" any+ - - /* - SOUND OBJECTS - A sound object is a '@synth' variable. We - can also access part of the sound's graph - using dot notation, e.g. '@synth.osc1'. - We'll only accept soundAccessors in a few - places. - */ - - sound = "@" alnum+ - propertyAccessor = "." alnum+ - soundAccessor = sound propertyAccessor? - - /* - FUNCTIONS - A function is anything in parentheses. These - will power blocks and arbitrary tools that - might spit out lists, numbers, etc. The syntax - is inspired by Clojure. Notice that the first - type of sound argument is... a function! This - enables is to write nested functions. Imagine: - (notes (random (chord major E3))) - */ - - function = "(" listOf ")" - soundArgument = function -- function - | range -- range - | list -- list - | rhythm -- rhythm - | float -- float - | note -- note - - /* - GRAPH LINES - A graph is a sound declaration followed by - one or more pipes that configure it. Graph - declarations will be additive, e.g. two - line with '@synth ~ (osc)' will create two - oscillators. - This definition is slightly longer than it - needs to be so that we can make the first - tilde optional. Either '@synth (osc tri)' - or '@synth ~ (osc tri)' will be valid. - */ - - Graph = soundAccessor "~"? PolySoundBlock Pipe? - - /* - Sound blocks look like functions, e.g. - '(osc sine 1)'. You can string several - together using pipes, which will literally - pipe the sounds together. - */ - - PolySoundBlock = MonoSoundBlock ("+" MonoSoundBlock)* - MonoSoundBlock = "(" listOf ")" name? - name = ":" alnum+ - - Pipe = ("~" PolySoundBlock)+ - - /* - PLAY LINES - A play line is a play keyword (either 'play' - or '>'), followed by the sound we want to play, - followed by a pattern. Each pattern uses a - different enclosing bracket. They could also - use a SoundBlock-like definition I guess. - */ - - Play = PlayKeyword sound Pattern - PlayKeyword = "play" | ">" - - /* - PATTERNS - The play line is expecting one or more function - calls that determine what the sound does. Those - might be things like (rhythm xox), (notes E3 D3), - and (times 0.2 0.3 0.5). Determining what tools - are possible should be a *runtime* concern, not - a grammar-level concern. - */ - - Pattern = listOf - - /* - PRIMITIVES - Here are the primitive types we're working with. - */ - - list = "[" listOf "]" - range = "[" int ".." int "]" -- number - | "[" note ".." note "]" -- note - - delimiter = " " - - float = "-"? digit* "." digit+ -- fullFloat - | "-"? digit "." -- dot - | "-"? digit+ -- int - - int = "-"? digit+ - - note = letter "#" digit+ -- sharp - | letter "b" digit+ -- flat - | alnum+ -- major - - rhythm = "r"? digit+ letter -} -`; diff --git a/classes/ADSR.js b/src/classes/ADSR.js similarity index 98% rename from classes/ADSR.js rename to src/classes/ADSR.js index 5bf3304..f2390f3 100644 --- a/classes/ADSR.js +++ b/src/classes/ADSR.js @@ -44,7 +44,7 @@ class ADSR extends Block { sustainLevel: 1, releaseTime: 0.05, gateTime: 0.25, - releaseCurve: "exp", + releaseCurve: 'exp', }); } @@ -95,6 +95,7 @@ class ADSR extends Block { } // TODO: only create gain for osc, otherwise apply to existing property + return null; } } diff --git a/classes/Block.js b/src/classes/Block.js similarity index 86% rename from classes/Block.js rename to src/classes/Block.js index dbfe9cd..fd523de 100644 --- a/classes/Block.js +++ b/src/classes/Block.js @@ -25,8 +25,8 @@ class Block { // This input allows us to give each // block a consistent interface without // having to name it all particular. - this._input = context.createGain(); - this._output = context.createGain(); + this.input = context.createGain(); + this.output = context.createGain(); // Some blocks will want to implement // polyphony, which really just means @@ -35,7 +35,7 @@ class Block { // to other nodes. Implementers just // need to call this.getPolyMode() to // find out which behavior to follow. - this._polyMode = false; + this.polyMode = false; } instantiate() { @@ -43,6 +43,8 @@ class Block { // its audio graph. The methods getInput() // and getOutput() give us an easy way to // connect everything together. + + return this; } connect(block) { @@ -58,26 +60,33 @@ class Block { schedule(start, stop, note) { // use the timestamp to schedule calls to // oscillators or what have you. + + return { + start, + stop, + note, + me: this, + }; } destroy() { - this._output.disconnect(); + this.output.disconnect(); } getInput() { - return this._input; + return this.input; } getOutput() { - return this._output; + return this.output; } setPolyMode(flag) { - this._polyMode = flag; + this.polyMode = flag; } getPolyMode() { - return this._polyMode; + return this.polyMode; } } diff --git a/classes/Delay.js b/src/classes/Delay.js similarity index 96% rename from classes/Delay.js rename to src/classes/Delay.js index b777390..48fd167 100644 --- a/classes/Delay.js +++ b/src/classes/Delay.js @@ -1,5 +1,4 @@ import Block from './Block'; -import context from '../helpers/context'; import tuna from '../helpers/tuna'; import { parseArgument } from '../helpers/parseArguments'; @@ -36,7 +35,7 @@ class Delay extends Block { schedule(start) { if (!this.getPolyMode()) { // update values here - return; + return null; } const delay = new tuna.Delay({ @@ -51,6 +50,7 @@ class Delay extends Block { return { input: delay, output: delay, + start, }; } } diff --git a/classes/Drums.js b/src/classes/Drums.js similarity index 88% rename from classes/Drums.js rename to src/classes/Drums.js index fe0fec0..51fd6a6 100644 --- a/classes/Drums.js +++ b/src/classes/Drums.js @@ -1,7 +1,5 @@ -import { Note } from 'tonal'; import Block from './Block'; import context from '../helpers/context'; -import mtof from '../helpers/mtof'; import { parseArgument } from '../helpers/parseArguments'; import BufferLoader from '../helpers/BufferLoader'; import drumMap from '../helpers/drumMap'; @@ -28,8 +26,8 @@ class Drums extends Block { } } - schedule(start, stop, note, envelopeMode) { - if (!drumBuffers.length || loadingDrums) return; + schedule(start, stop, note, envelope) { + if (!drumBuffers.length || loadingDrums) return null; // we only have 12 samples available but we shouldn't // burden the user with that knowledge so let's use // mod 12, which allows them to use chords, scales, @@ -55,18 +53,21 @@ class Drums extends Block { // Finally, if we are in mono mode, just connect the osc to // the ouput. sample.connect(this.getOutput()); + + return envelope; // TODO: Quickfix to keep lint from complaining } loadDrumSounds() { loadingDrums = true; // Get a list of files const files = Object.keys(drumMap).map(key => drumMap[key].file); // Load the files! - const loader = new BufferLoader(context, files, list => { + const loader = new BufferLoader(context, files, (list) => { // set our global variable to the list of buffers. Done. drumBuffers = list; loadingDrums = false; }); loader.load(); + return this; } } diff --git a/classes/Filter.js b/src/classes/Filter.js similarity index 92% rename from classes/Filter.js rename to src/classes/Filter.js index 0387019..94fba7f 100644 --- a/classes/Filter.js +++ b/src/classes/Filter.js @@ -26,7 +26,7 @@ class Filter extends Block { // the schedule method will take care of that; otherwise we'll // end up with the second value in a cycle on the first // scheduled note. - this.filter.type = typeMap['lp']; + this.filter.type = typeMap.lp; this.filter.frequency.setValueAtTime(11025, context.currentTime, 0); this.filter.Q.setValueAtTime(1, context.currentTime, 0); @@ -41,7 +41,7 @@ class Filter extends Block { this.filter.type = typeMap[this.type.next()]; this.filter.frequency.setValueAtTime((this.amount.next() / 127) * 11025, start, 10); this.filter.Q.setValueAtTime(this.Q.next(), start, 10); - return; + return null; } const filter = context.createBiquadFilter(); @@ -51,17 +51,15 @@ class Filter extends Block { // TODO: envelope mode to return filter.frequency as property - if (envelopeMode) { - return { + return envelopeMode + ? { node: filter, property: filter.frequency, + } + : { + input: filter, + output: filter, }; - } - - return { - input: filter, - output: filter, - }; } } diff --git a/classes/Gain.js b/src/classes/Gain.js similarity index 98% rename from classes/Gain.js rename to src/classes/Gain.js index 94aacc0..39c0f7e 100644 --- a/classes/Gain.js +++ b/src/classes/Gain.js @@ -22,7 +22,7 @@ class Gain extends Block { schedule(start) { if (!this.getPolyMode()) { this.gain.gain.setValueAtTime(this.level.next(), context.currentTime, 0); - return; + return null; } const gain = context.createGain(); diff --git a/classes/Osc.js b/src/classes/Osc.js similarity index 93% rename from classes/Osc.js rename to src/classes/Osc.js index cef8e9d..82a3165 100644 --- a/classes/Osc.js +++ b/src/classes/Osc.js @@ -1,7 +1,6 @@ import { Note } from 'tonal'; import Block from './Block'; import context from '../helpers/context'; -import mtof from '../helpers/mtof'; import { parseArgument } from '../helpers/parseArguments'; const typeMap = { @@ -16,7 +15,7 @@ const typeMap = { sq: 'square', square: 'square', -} +}; class Osc extends Block { constructor(...args) { @@ -34,11 +33,11 @@ class Osc extends Block { osc.type = typeMap[this.type.next()]; - let noteMidiValue = typeof note === 'string' ? Note.midi(note) : note; + const noteMidiValue = typeof note === 'string' ? Note.midi(note) : note; osc.frequency.setValueAtTime( Note.freq(Note.fromMidi(noteMidiValue + this.detune.next())), context.currentTime, - 0 + 0, ); osc.start(start); @@ -59,7 +58,7 @@ class Osc extends Block { return { node: osc, property: osc, - } + }; } else if (this.getPolyMode()) { // An osc has no input! Not sure // what to do about that. @@ -71,6 +70,8 @@ class Osc extends Block { // Finally, if we are in mono mode, just connect the osc to // the ouput. osc.connect(this.getOutput()); + + return null; } } diff --git a/classes/Pan.js b/src/classes/Pan.js similarity index 97% rename from classes/Pan.js rename to src/classes/Pan.js index 55a940f..839c3cf 100644 --- a/classes/Pan.js +++ b/src/classes/Pan.js @@ -25,7 +25,7 @@ class Pan extends Block { schedule(start) { if (!this.getPolyMode()) { this.pan.pan.setValueAtTime(this.value.next(), context.currentTime, 0); - return; + return null; } const pan = context.createStereoPanner(); @@ -34,6 +34,7 @@ class Pan extends Block { return { input: pan, output: pan, + start, }; } } diff --git a/classes/PolyBlock.js b/src/classes/PolyBlock.js similarity index 60% rename from classes/PolyBlock.js rename to src/classes/PolyBlock.js index f045767..74d7710 100644 --- a/classes/PolyBlock.js +++ b/src/classes/PolyBlock.js @@ -10,20 +10,17 @@ class PolyBlock extends Block { instantiate() { // Turn the block model objects into Block classes. this.blocks = this.blockDefinitions.map((block) => { - if (classMap[block.function]) { - // We're doing the same thing that the Sound - // class is doing with the blocks here, but - // in `schedule` we're going to do some tricks - // to link together all of the sounds in a - // polyphonic way. - const b = new classMap[block.function](...block.arguments); - // Tell this block it's in poly mode. - b.setPolyMode(true); - b.instantiate(); - return b; - } else { - throw new Error(`PolyBlock: Block type "${block.function}" does not exist`); - } + if (!(block.function in classMap)) throw new Error(`PolyBlock: Block type "${block.function}" does not exist`); + // We're doing the same thing that the Sound + // class is doing with the blocks here, but + // in `schedule` we're going to do some tricks + // to link together all of the sounds in a + // polyphonic way. + const b = new classMap[block.function](...block.arguments); + // Tell this block it's in poly mode. + b.setPolyMode(true); + b.instantiate(); + return b; }); } schedule(start, stop, note) { @@ -38,13 +35,11 @@ class PolyBlock extends Block { const connections = this.blocks.map(block => block.schedule(start, stop, note)); // Now loop through them and chain them together. - for (let i = 0; i < connections.length; i++) { + for (let i = 0; i < connections.length; i += 1) { // If there is an adjacent block... if (connections[i] && connections[i + 1]) { // Connect them! - connections[i].output.connect( - connections[i + 1].input - ); + connections[i].output.connect(connections[i + 1].input); } else { // We're at the final block; connect // it to the output. @@ -54,4 +49,4 @@ class PolyBlock extends Block { } } -export default PolyBlock; \ No newline at end of file +export default PolyBlock; diff --git a/classes/Scheduler.js b/src/classes/Scheduler.js similarity index 80% rename from classes/Scheduler.js rename to src/classes/Scheduler.js index 1cfd3b9..a1f90f4 100644 --- a/classes/Scheduler.js +++ b/src/classes/Scheduler.js @@ -1,6 +1,5 @@ import context from '../helpers/context'; import { parseArgument, rhythmMap } from '../helpers/parseArguments'; -import List from '../helpers/List'; export default class Scheduler { constructor(patterns) { @@ -22,7 +21,7 @@ export default class Scheduler { // currentTs will keep track of which // tick we've scheduled up to. this.currentTime = null; - this.lookahead = .04; + this.lookahead = 0.04; this.startTime = null; // Loop through whatever we got and overwrite @@ -31,17 +30,17 @@ export default class Scheduler { // is why we're pulling arguments[0] out. patterns.forEach((pattern) => { switch (pattern.function) { - case 'rhythm': - // We have to special-case rhythm argument parsing - // for now because the xoxoxo-style pattern is not - // recognized by the parser as a List. - this.rhythmPattern = parseArgument(pattern.arguments[0]) - break; - case 'notes': - this.notePattern = parseArgument(pattern.arguments[0]); - break; - default: - break; + case 'rhythm': + // We have to special-case rhythm argument parsing + // for now because the xoxoxo-style pattern is not + // recognized by the parser as a List. + this.rhythmPattern = parseArgument(pattern.arguments[0]); + break; + case 'notes': + this.notePattern = parseArgument(pattern.arguments[0]); + break; + default: + break; } }); } @@ -55,6 +54,7 @@ export default class Scheduler { this.currentTime = timestamp; this.interval = setInterval(() => { + const rhythmMapObj = rhythmMap(); while (this.currentTime < context.currentTime + this.lookahead) { // The tick length could be a number or a string that starts // with 'r', indicating a rest. @@ -65,7 +65,7 @@ export default class Scheduler { if (typeof nextTickLength === 'string' && nextTickLength.charAt(0).toLowerCase() === 'r') { rest = true; // Convert it into the appropriate rhythm. - nextTickLength = rhythmMap[nextTickLength.substr(1)]; + nextTickLength = rhythmMapObj[nextTickLength.substr(1)]; } // We're only ticking on beats that aren't rests. if (!rest) { @@ -78,7 +78,7 @@ export default class Scheduler { // this.currentTime + this.lengthPattern.next(), this.currentTime + nextTickLength, // note - nextNote + nextNote, ); } // go to the next beat in the clock @@ -90,4 +90,4 @@ export default class Scheduler { stop() { this.interval = clearInterval(this.interval); } -} \ No newline at end of file +} diff --git a/classes/Sound.js b/src/classes/Sound.js similarity index 75% rename from classes/Sound.js rename to src/classes/Sound.js index 967c57f..04ca8f3 100644 --- a/classes/Sound.js +++ b/src/classes/Sound.js @@ -24,7 +24,7 @@ class Sound { this.idFactory = 0; // This is for debugging. - this._graphs = []; + this.graphs = []; // ======================================== // INSTANTIATE @@ -41,7 +41,8 @@ class Sound { } nextId() { - return `--${++this.idFactory}`; + this.idFactory += 1; + return `--${this.idFactory}`; } appendToGraph(graph) { @@ -53,14 +54,15 @@ class Sound { // Add to the debug graphs array in case // we need to poke around. - this._graphs.push(graph); + this.graphs.push(graph); } createGraph(pipe, index) { // Create a new set of connections. this.connections[index] = []; - const model = pipe.reduce((model, block, i) => { + const model = pipe.reduce((m, block) => { + const theModel = m || {}; // A block can either be a simple Block function // like `osc` & `filter`, OR it can be a polyblock. // We have to treat those two cases differently. @@ -69,30 +71,29 @@ class Sound { // It seems like PolyBlocks aren't going to // be able to support name variables? Tbd. const thisId = `poly${this.nextId()}`; - model[thisId] = new PolyBlock(block); - model[thisId].instantiate(); + theModel[thisId] = new PolyBlock(block); + theModel[thisId].instantiate(); this.connections[index].push(thisId); - return model; - } else { - const thisId = block.name || `${block.function}${this.nextId()}`; - if (classMap[block.function]) { - // If the block was named, we'll stash - // it by name in the model. Otherwise, - // give it an internal ID that we can - // use to reference it. - model[thisId] = new classMap[block.function](...block.arguments); - model[thisId].instantiate(); - - // Add this ID to the connection list. - this.connections[index].push(thisId); - - return model; - } else { - throw new Error(`${this.name}: Block type "${block.function}" does not exist`); - } + return theModel; } + + const thisId = block.name || `${block.function}${this.nextId()}`; + if (classMap[block.function]) { + // If the block was named, we'll stash + // it by name in the model. Otherwise, + // give it an internal ID that we can + // use to reference it. + theModel[thisId] = new classMap[block.function](...block.arguments); + theModel[thisId].instantiate(); + + // Add this ID to the connection list. + this.connections[index].push(thisId); + + return theModel; + } + throw new Error(`${this.name}: Block type "${block.function}" does not exist`); }, {}); // Append this all to our model @@ -109,15 +110,14 @@ class Sound { const connections = this.connections[index]; - const length = this.connections[index].length; + const { length } = this.connections[index]; - for (let i = 0; i < length; i++) { + for (let i = 0; i < length; i += 1) { // If there is an adjacent block... if (connections[i] && connections[i + 1]) { // Connect them! - this.model[connections[i]].connect( - this.model[connections[i + 1]] - ); + this.model[connections[i]] + .connect(this.model[connections[i + 1]]); } else { // We're at the final block; connect // it to the output. diff --git a/classes/classMap.js b/src/classes/classMap.js similarity index 68% rename from classes/classMap.js rename to src/classes/classMap.js index 10747cf..30acc11 100644 --- a/classes/classMap.js +++ b/src/classes/classMap.js @@ -7,13 +7,13 @@ import Pan from './Pan'; import Delay from './Delay'; const classMap = { - 'osc': Osc, - 'drums': Drums, - 'filter': Filter, - 'adsr': ADSR, - 'gain': Gain, - 'pan': Pan, - 'delay': Delay, + osc: Osc, + drums: Drums, + filter: Filter, + adsr: ADSR, + gain: Gain, + pan: Pan, + delay: Delay, }; export default classMap; diff --git a/editor.js b/src/editor.js similarity index 68% rename from editor.js rename to src/editor.js index eab0397..d78ec93 100644 --- a/editor.js +++ b/src/editor.js @@ -1,61 +1,26 @@ -import { runScene, clearScene } from './slang'; -import context from './helpers/context'; - import CodeMirror from 'codemirror'; -import * as simpleMode from 'codemirror/addon/mode/simple'; -import js from 'codemirror/mode/clojure/clojure'; import 'codemirror/lib/codemirror.css'; import 'codemirror/theme/duotone-light.css'; +import * as simpleMode from 'codemirror/addon/mode/simple'; +import js from 'codemirror/mode/clojure/clojure'; + +import { runScene, clearScene } from './slang'; +import context from './helpers/context'; import classMap from './classes/classMap'; import { functionMap } from './functions'; -// ------------------------------ EDITOR ------------------------------ +import logo from './static/img/logo.svg'; +import './static/css/editor.css'; -const keywords = Object.keys(classMap).concat(Object.keys(functionMap), ['notes', 'rhythm']); -const keywordRegex = new RegExp(`(?:${keywords.join('|')})\\b`); - -CodeMirror.defineSimpleMode("slang", { - start: [ - { - regex: keywordRegex, - token: "keyword" - }, - { - regex: /[a-g](\#|b)?\d+/i, - token: "note" - }, - { - regex: /\d+(n|t)/i, - token: "beat" - }, - { - regex: /r\d+(n|t)/i, - token: "rest" - }, - { - regex: /0x[a-f\d]+|[-+]?(?:\.\d+|\d+\.?\d*)(?:e[-+]?\d+)?/i, - token: "number" - }, - { - regex: /(\+|\~)/, - token: "pipe" - }, - { - regex: /\#.+/, - token: "comment" - }, - { - regex: /\@[a-z$][\w$]*/, - token: "variable" - }, - ], -}); +// ------------------------------ CODE MIRROR ------------------------------ const existingCode = window.localStorage.getItem('code'); const defaultCode = `# Welcome to Slang! Here's an example to get you started. # Click the Run button above to start playing this code. +TEMPO 200 + # Make a sound called @synth with a triangle wave @synth (adsr (osc tri) 64n 8n 0.5 8n) @@ -68,12 +33,49 @@ play @synth `; const editor = CodeMirror(document.querySelector('#editor'), { - value: existingCode || defaultCode, - mode: 'slang', + value: window.slangPatch || existingCode || defaultCode, + mode: 'slang', theme: 'duotone-light', indentWithTabs: true, }); +// ------------------------------ ERROR HANDLING ------------------------------ + +// Stash a few references to elements that we'll use to present +// errors to the user. +const $error = document.querySelector('#error'); +const $errorContent = document.querySelector('#error-content'); +const $dismiss = document.querySelector('#dismiss'); + +$dismiss.addEventListener('click', () => { + $error.classList.remove('show'); +}); + +function displayError(message) { + $error.classList.add('show'); + $errorContent.textContent = String(message).trim(); +} + +function clearError() { + $error.classList.remove('show'); +} + +// ------------------------------ CONTROLS ------------------------------ + +const $run = document.querySelector('[data-run]'); +const $stop = document.querySelector('[data-stop]'); +const $status = document.querySelector('[data-status]'); +const $url = document.querySelector('[data-url]'); + +function status(str) { + $status.textContent = str; +} + +function stop() { + clearScene(); + status('Stopped'); +} + function run() { context.resume(); const value = editor.getValue(); @@ -93,51 +95,84 @@ function run() { // save the scene to localStorage window.localStorage.setItem('code', value); } - -function stop() { - clearScene(); - status('Stopped'); +function createUrl() { + const value = editor.getValue(); + // The /save route of our express server is + // expecting a JSON blob containing a `text` field. + fetch('/save', { + method: 'POST', + mode: 'cors', + cache: 'default', + body: JSON.stringify({ text: value }), + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Request-Method': 'post', + 'Access-Control-Allow-Credentials': 'true', + }, + }) + .then(response => response.text()) + .then((text) => { + // Redirect the browser to the newly created patch. + window.location.pathname = `/${text}`; + }) + .catch((e) => { + console.error(e); + displayError('Oh no! There’s a problem with the server. Try again in a bit.'); + }); } -editor.on('keydown', (c, e) => { - if (e.key === 'Enter' && e.metaKey && e.shiftKey) { - stop(); - } else if (e.key === 'Enter' && e.metaKey) { - run(); - } -}); - -// ------------------------------ CONTROLS ------------------------------ - -const $run = document.querySelector('[data-run]'); -const $stop = document.querySelector('[data-stop]'); -const $status = document.querySelector('[data-status]'); - $run.addEventListener('click', run); $stop.addEventListener('click', stop); +$url.addEventListener('click', createUrl); -function status(str) { - $status.textContent = str; -} - -// ------------------------------ ERROR HANDLING ------------------------------ +// ------------------------------ EDITOR ------------------------------ -// Stash a few references to elements that we'll use to present -// errors to the user. -const $error = document.querySelector('#error'); -const $errorContent = document.querySelector('#error-content'); -const $dismiss = document.querySelector('#dismiss'); +const keywords = Object.keys(classMap).concat(Object.keys(functionMap), ['notes', 'rhythm']); +const keywordRegex = new RegExp(`(?:${keywords.join('|')})\\b`); -$dismiss.addEventListener('click', () => { - $error.classList.remove('show'); +CodeMirror.defineSimpleMode('slang', { + start: [ + { + regex: keywordRegex, + token: 'keyword', + }, + { + regex: /[a-g](#|b)?\d+/i, + token: 'note', + }, + { + regex: /\d+(n|t)/i, + token: 'beat', + }, + { + regex: /r\d+(n|t)/i, + token: 'rest', + }, + { + regex: /0x[a-f\d]+|[-+]?(?:\.\d+|\d+\.?\d*)(?:e[-+]?\d+)?/i, + token: 'number', + }, + { + regex: /(\+|~)/, + token: 'pipe', + }, + { + regex: /#.+/, + token: 'comment', + }, + { + regex: /@[a-z$][\w$]*/, + token: 'variable', + }, + ], }); -function displayError(message) { - $error.classList.add('show'); - $errorContent.textContent = String(message).trim(); -} - -function clearError() { - $error.classList.remove('show'); -} +editor.on('keydown', (c, e) => { + if (e.key === 'Enter' && e.metaKey && e.shiftKey) { + stop(); + } else if (e.key === 'Enter' && e.metaKey) { + run(); + } +}); +export { logo, simpleMode, js }; diff --git a/functions/FunctionCall.js b/src/functions/FunctionCall.js similarity index 92% rename from functions/FunctionCall.js rename to src/functions/FunctionCall.js index e4b8b84..5b495be 100644 --- a/functions/FunctionCall.js +++ b/src/functions/FunctionCall.js @@ -14,4 +14,4 @@ class FunctionCall { } } -export default FunctionCall; \ No newline at end of file +export default FunctionCall; diff --git a/functions/chord.js b/src/functions/chord.js similarity index 87% rename from functions/chord.js rename to src/functions/chord.js index a2d0968..51a6cda 100644 --- a/functions/chord.js +++ b/src/functions/chord.js @@ -6,8 +6,9 @@ import { parseArgument } from '../helpers/parseArguments'; // For now let's strip the spaces out of the chord names // to simplify the arguments to the (chord ...) function. -const scaleNamesMap = Scale.names().reduce((ob, name) => { - ob[name.replace(/( |\#)/g, '')] = name; +const scaleNamesMap = Scale.names().reduce((srcOb, name) => { + const ob = Object.assign({}, srcOb); + ob[name.replace(/( |#)/g, '')] = name; return ob; }, {}); @@ -32,7 +33,7 @@ export default class Chord extends FunctionCall { // figure out how many times it repeats ... const repeat = Math.ceil(length / notes.length); // ... repeat it ... - notes = flatMap(Array(repeat).fill(null), __ => notes); + notes = flatMap(Array(repeat).fill(null), () => notes); // ... now take the exact amount. notes = take(notes, length); } @@ -42,4 +43,4 @@ export default class Chord extends FunctionCall { // the default for a FunctionCall is to return `this.data.next()`. this.data = parseArgument(notes); } -} \ No newline at end of file +} diff --git a/functions/flatten.js b/src/functions/flatten.js similarity index 73% rename from functions/flatten.js rename to src/functions/flatten.js index 2b64075..ec73e00 100644 --- a/functions/flatten.js +++ b/src/functions/flatten.js @@ -1,20 +1,17 @@ import { flatMap } from 'lodash'; import FunctionCall from './FunctionCall'; -import { parseArgument } from '../helpers/parseArguments'; import List from '../helpers/List'; export default class Flatten extends FunctionCall { constructor(functionObject) { super(functionObject); - let data; - // All arguments must be arrays. - data = flatMap(this.arguments[0].toArray(), (arg) => { + const data = flatMap(this.arguments[0].toArray(), (arg) => { if (arg.toArray) return arg.toArray(); return arg; }); this.data = new List(data); } -} \ No newline at end of file +} diff --git a/functions/index.js b/src/functions/index.js similarity index 70% rename from functions/index.js rename to src/functions/index.js index 5b8c236..df016c3 100644 --- a/functions/index.js +++ b/src/functions/index.js @@ -8,17 +8,17 @@ import Transpose from './transpose'; import Interpolate from './interpolate'; export const functionMap = { - 'random': Random, - 'chord': Chord, - 'repeat': Repeat, - 'flatten': Flatten, - 'reverse': Reverse, - 'shuffle': Shuffle, - 'transpose': Transpose, - 'lerp': Interpolate, + random: Random, + chord: Chord, + repeat: Repeat, + flatten: Flatten, + reverse: Reverse, + shuffle: Shuffle, + transpose: Transpose, + lerp: Interpolate, }; -export default function(functionObject) { +export default function (functionObject) { if (functionMap[functionObject.function]) { return new functionMap[functionObject.function](functionObject); } diff --git a/functions/interpolate.js b/src/functions/interpolate.js similarity index 54% rename from functions/interpolate.js rename to src/functions/interpolate.js index e2c87a8..71729c7 100644 --- a/functions/interpolate.js +++ b/src/functions/interpolate.js @@ -1,5 +1,3 @@ -import shuffle from 'lodash/shuffle'; -import { parseArgument } from '../helpers/parseArguments'; import FunctionCall from './FunctionCall'; import List from '../helpers/List'; @@ -10,11 +8,11 @@ export default class Interpolate extends FunctionCall { const toValue = this.arguments[1].next(); const steps = this.arguments[2].next(); - const stepInterval = (toValue - fromValue) / (steps - 2); + // const stepInterval = (toValue - fromValue) / (steps - 2); - let values = [fromValue]; - for (let i = 1; i < steps; i++) { - values.push(fromValue * (1 - (i / (steps - 1))) + toValue * (i / (steps - 1))); + const values = [fromValue]; + for (let i = 1; i < steps; i += 1) { + values.push((fromValue * (1 - (i / (steps - 1)))) + (toValue * (i / (steps - 1)))); } this.data = new List(values); diff --git a/functions/random.js b/src/functions/random.js similarity index 99% rename from functions/random.js rename to src/functions/random.js index e9b0265..c3bbd59 100644 --- a/functions/random.js +++ b/src/functions/random.js @@ -16,4 +16,4 @@ export default class Random extends FunctionCall { Math.floor(Math.random() * this.data.length) ].next(); } -} \ No newline at end of file +} diff --git a/functions/repeat.js b/src/functions/repeat.js similarity index 83% rename from functions/repeat.js rename to src/functions/repeat.js index fcbb9ce..57a6999 100644 --- a/functions/repeat.js +++ b/src/functions/repeat.js @@ -1,5 +1,4 @@ import FunctionCall from './FunctionCall'; -import { parseArgument } from '../helpers/parseArguments'; import List from '../helpers/List'; export default class Random extends FunctionCall { @@ -16,15 +15,15 @@ export default class Random extends FunctionCall { if (this.arguments[1] && this.arguments[1].toArray) { data = this.arguments[1].toArray(); } else { - data = this.arguments[1]; + [, data] = this.arguments; } const repeat = this.arguments[0].next(); - for (let i = 0; i < repeat; i++) { + for (let i = 0; i < repeat; i += 1) { this.data = this.data.concat(data); } this.data = new List(this.data); } -} \ No newline at end of file +} diff --git a/functions/reverse.js b/src/functions/reverse.js similarity index 81% rename from functions/reverse.js rename to src/functions/reverse.js index 0bc184b..2f020be 100644 --- a/functions/reverse.js +++ b/src/functions/reverse.js @@ -1,4 +1,3 @@ -import { parseArgument } from '../helpers/parseArguments'; import FunctionCall from './FunctionCall'; import List from '../helpers/List'; diff --git a/functions/shuffle.js b/src/functions/shuffle.js similarity index 65% rename from functions/shuffle.js rename to src/functions/shuffle.js index b4f2018..08f914b 100644 --- a/functions/shuffle.js +++ b/src/functions/shuffle.js @@ -1,11 +1,10 @@ import shuffle from 'lodash/shuffle'; -import { parseArgument } from '../helpers/parseArguments'; import FunctionCall from './FunctionCall'; import List from '../helpers/List'; export default class Shuffle extends FunctionCall { constructor(functionObject) { super(functionObject); - this.data = new List(_.shuffle(this.arguments[0].toArray())); + this.data = new List(shuffle(this.arguments[0].toArray())); } } diff --git a/functions/transpose.js b/src/functions/transpose.js similarity index 87% rename from functions/transpose.js rename to src/functions/transpose.js index 99bd9fa..198d058 100644 --- a/functions/transpose.js +++ b/src/functions/transpose.js @@ -1,5 +1,5 @@ import { transpose as tonalTranspose } from 'tonal-distance'; -import { fromSemitones } from "tonal-interval" +import { fromSemitones } from 'tonal-interval'; import FunctionCall from './FunctionCall'; import { parseArgument, rhythmMap } from '../helpers/parseArguments'; @@ -15,7 +15,9 @@ export default class Transpose extends FunctionCall { this.hasWarned = false; } next(passedValue) { - let nextValue = passedValue || this.data.next(); + const rhythmMapObj = rhythmMap(); + console.log(rhythmMapObj); + const nextValue = passedValue || this.data.next(); // Unfortunately transposing rhythms won't work // easily the way the rhythm strings are passed around @@ -30,7 +32,7 @@ export default class Transpose extends FunctionCall { typeof nextValue === 'string' && ( nextValue.charAt(0).toLowerCase() === 'r' - || rhythmMap[nextValue] + || rhythmMapObj[nextValue] ) ) { if (!this.hasWarned) { @@ -45,7 +47,7 @@ export default class Transpose extends FunctionCall { if (typeof nextValue === 'string') { return tonalTranspose( nextValue, - fromSemitones(Math.floor(this.amount.next())) + fromSemitones(Math.floor(this.amount.next())), ); } @@ -57,4 +59,4 @@ export default class Transpose extends FunctionCall { // which some functions require (like `shuffle`). return this.data.toArray().map(item => this.next(item.next())); } -} \ No newline at end of file +} diff --git a/src/helpers/BufferLoader.js b/src/helpers/BufferLoader.js new file mode 100644 index 0000000..cc112fd --- /dev/null +++ b/src/helpers/BufferLoader.js @@ -0,0 +1,49 @@ +// an abstraction written by Boris Smus, +// taken from http://www.html5rocks.com/en/tutorials/webaudio/intro/ +// ... thanks Boris! + +export default function BufferLoader(context, urlList, callback) { + this.context = context; + this.urlList = urlList; + this.onload = callback; + this.bufferList = []; + this.loadCount = 0; +} + +BufferLoader.prototype.loadBuffer = (url, index) => { + // Load buffer asynchronously + const request = new XMLHttpRequest(); + request.open('GET', url, true); + request.responseType = 'arraybuffer'; + + const loader = this; + + request.onload = () => { + // Asynchronously decode the audio file data in request.response + loader.context.decodeAudioData( + request.response, + (buffer) => { + if (!buffer) { + console.error(`BufferLoader: error decoding file data from url: ${url}`); + return; + } + loader.bufferList[index] = buffer; + loader.loadCount += 1; + if (loader.loadCount === loader.urlList.length) loader.onload(loader.bufferList); + }, + (error) => { + console.error(`BufferLoader: decodeAudioData error ${error}`); + }, + ); + }; + + request.onerror = () => { + console.log(`BufferLoader: error decoding ${url}`); + }; + + request.send(); +}; + +BufferLoader.prototype.load = () => { + for (let i = 0; i < this.urlList.length; i += 1) this.loadBuffer(this.urlList[i], i); +}; diff --git a/helpers/FunctionCall.js b/src/helpers/FunctionCall.js similarity index 74% rename from helpers/FunctionCall.js rename to src/helpers/FunctionCall.js index 804c7e2..f7d3b07 100644 --- a/helpers/FunctionCall.js +++ b/src/helpers/FunctionCall.js @@ -1,12 +1,12 @@ import * as Scale from 'tonal-scale'; import parseArguments, { parseArgument } from './parseArguments'; -import List from './List'; // For now let's strip the spaces out of the chord names // to simplify the arguments to the (chord ...) function. const scaleNamesMap = Scale.names().reduce((ob, name) => { - ob[name.replace(/ /g, '')] = name; - return ob; + const object = Object.assign({}, ob); + object[name.replace(/ /g, '')] = name; + return object; }, {}); class FunctionCall { @@ -34,15 +34,15 @@ class FunctionCall { } next() { switch (this.type) { - case 'random': return this.random(); - case 'chord': return this.chord(); - default: - throw new Error(`Function ${this.type} does not exist`); + case 'random': return this.random(); + case 'chord': return this.chord(); + default: + throw new Error(`Function ${this.type} does not exist`); } } toArray() { - if (this.type === 'chord') return this.chordList.toArray(); + return this.type === 'chord' ? this.chordList.toArray() : null; } // ============================================================ @@ -51,8 +51,9 @@ class FunctionCall { random() { // Returns a single value - if (this.randomList) return this.randomList[Math.floor(Math.random() * this.randomList.length)].next(); - return this.arguments[Math.floor(Math.random() * this.arguments.length)].next(); + return this.randomList + ? this.randomList[Math.floor(Math.random() * this.randomList.length)].next() + : this.arguments[Math.floor(Math.random() * this.arguments.length)].next(); } chord() { @@ -62,4 +63,4 @@ class FunctionCall { } } -export default FunctionCall; \ No newline at end of file +export default FunctionCall; diff --git a/helpers/List.js b/src/helpers/List.js similarity index 80% rename from helpers/List.js rename to src/helpers/List.js index 6cfdc13..cf85046 100644 --- a/helpers/List.js +++ b/src/helpers/List.js @@ -24,19 +24,19 @@ class List { } else if (typeof listObject === 'object' && listObject.arguments) { this.values = parseArguments(listObject.arguments); } else { - throw new Error(`List got a weird value? ${listObject}`) + throw new Error(`List got a weird value? ${listObject}`); } - this._currentIndex = 0; + this.currentIndex = 0; } toArray() { return this.values; } next() { - const value = this.values[this._currentIndex].next(); - this._currentIndex = (this._currentIndex + 1) % this.values.length; + const value = this.values[this.currentIndex].next(); + this.currentIndex = (this.currentIndex + 1) % this.values.length; return value; } } -export default List; \ No newline at end of file +export default List; diff --git a/src/helpers/context.js b/src/helpers/context.js new file mode 100644 index 0000000..86f6ffe --- /dev/null +++ b/src/helpers/context.js @@ -0,0 +1,4 @@ +const WebkitAudio = 'webkitAudioContext'; +const context = window[WebkitAudio] ? new window[WebkitAudio]() : new AudioContext(); + +export default context; diff --git a/src/helpers/drumMap.js b/src/helpers/drumMap.js new file mode 100644 index 0000000..814c354 --- /dev/null +++ b/src/helpers/drumMap.js @@ -0,0 +1,65 @@ +import acousticHatOpen1 from '../static/audio/acoustic/hihat_open1.mp3'; +import acousticHatOpen2 from '../static/audio/acoustic/hihat_open2.mp3'; +import acousticHatOpen3 from '../static/audio/acoustic/hihat_open3.mp3'; +import acousticHat1 from '../static/audio/acoustic/hihat1.mp3'; +import acousticHat2 from '../static/audio/acoustic/hihat2.mp3'; +import acousticKick1 from '../static/audio/acoustic/kick1.mp3'; +import acousticKick2 from '../static/audio/acoustic/kick2.mp3'; +import acousticKick3 from '../static/audio/acoustic/kick3.mp3'; +import acousticRim1 from '../static/audio/acoustic/rim1.mp3'; +import acousticSnare1 from '../static/audio/acoustic/snare1.mp3'; +import acousticSnare2 from '../static/audio/acoustic/snare2.mp3'; +import acousticSnare3 from '../static/audio/acoustic/snare3.mp3'; + +const drumMap = { + 0: { + file: acousticKick1, + label: 'acoustic kick 1', + }, + 1: { + file: acousticKick2, + label: 'acoustic kick 2', + }, + 2: { + file: acousticKick3, + label: 'acoustic kick 3', + }, + 3: { + file: acousticSnare1, + label: 'acoustic snare 1', + }, + 4: { + file: acousticSnare2, + label: 'acoustic snare 2', + }, + 5: { + file: acousticSnare3, + label: 'acoustic snare 3', + }, + 6: { + file: acousticHat1, + label: 'acoustic hat 1', + }, + 7: { + file: acousticHat2, + label: 'acoustic hat 2', + }, + 8: { + file: acousticHatOpen1, + label: 'acoustic hat (open) 1', + }, + 9: { + file: acousticHatOpen2, + label: 'acoustic hat (open) 2', + }, + 10: { + file: acousticHatOpen3, + label: 'acoustic hat (open) 3', + }, + 11: { + file: acousticRim1, + label: 'acoustic rim', + }, +}; + +export default drumMap; diff --git a/src/helpers/mtof.js b/src/helpers/mtof.js new file mode 100644 index 0000000..6415e9c --- /dev/null +++ b/src/helpers/mtof.js @@ -0,0 +1,3 @@ +export default function mtof(note) { + return (2 ** ((note - 69) / 12)) * 440.0; +} diff --git a/helpers/parseArguments.js b/src/helpers/parseArguments.js similarity index 57% rename from helpers/parseArguments.js rename to src/helpers/parseArguments.js index 3733157..4370584 100644 --- a/helpers/parseArguments.js +++ b/src/helpers/parseArguments.js @@ -1,22 +1,25 @@ import List from './List'; import FunctionCall from '../functions'; -const TEMPO = 120; -const DIVISION = (1 / 24) / (TEMPO / 60); - -export const rhythmMap = { - '64t': DIVISION, - '64n': DIVISION * 1.5, - '32t': DIVISION * 2, - '32n': DIVISION * 3, - '16t': DIVISION * 4, - '16n': DIVISION * 6, - '8t': DIVISION * 8, - '8n': DIVISION * 12, - '4t': DIVISION * 16, - '4n':DIVISION * 24, - '2n': DIVISION * 48, - '1n': DIVISION * 96, +let TEMPO = 120; + +export function rhythmMap() { + const DIVISION = (1 / 24) / (TEMPO / 60); + return { + '64t': DIVISION, + '64n': DIVISION * 1.5, + '32t': DIVISION * 2, + '32n': DIVISION * 3, + '16t': DIVISION * 4, + '16n': DIVISION * 6, + '8t': DIVISION * 8, + '8n': DIVISION * 12, + '4t': DIVISION * 16, + '4n': DIVISION * 24, + '2t': DIVISION * 32, + '2n': DIVISION * 48, + '1n': DIVISION * 96, + }; } /* @@ -29,8 +32,12 @@ export const rhythmMap = { If it's a static value, `next` will return that value. */ -export default function(args) { - return args.map((arg) => parseArgument(arg)); +function createArgumentFromStaticValue(value) { + const rhythmMapObj = rhythmMap(); + // convert rhythms into numbers if we catch one + return { + next: () => rhythmMapObj[value] || value, + }; } export function parseArgument(arg) { @@ -52,9 +59,11 @@ export function parseArgument(arg) { return null; } -function createArgumentFromStaticValue(value) { - // convert rhythms into numbers if we catch one - return { - next: () => rhythmMap[value] || value, - }; -} \ No newline at end of file +export default args => args.map(arg => parseArgument(arg)); + +export function changeTempo(tempo) { + const min = 30; + const max = 500; + const newTempo = Math.max(Math.min(tempo, max), min); + TEMPO = Number.isNaN(newTempo) ? TEMPO : newTempo; +} diff --git a/helpers/tuna.js b/src/helpers/tuna.js similarity index 100% rename from helpers/tuna.js rename to src/helpers/tuna.js diff --git a/runtime.js b/src/runtime.js similarity index 83% rename from runtime.js rename to src/runtime.js index dbc3ef5..e108ec5 100644 --- a/runtime.js +++ b/src/runtime.js @@ -1,33 +1,11 @@ import Sound from './classes/Sound'; import context from './helpers/context'; +import { changeTempo } from './helpers/parseArguments'; const model = { sounds: {}, }; -function runScene(scene) { - // a scene is a collection of lines that go together. - - // Stage 1: build the scene - scene.forEach((operation) => { - switch (operation.type) { - case 'graph': - parseGraph(operation); - break; - case 'play': - parsePlay(operation); - break; - } - }); - - const startTime = context.currentTime + 0.01; - - // Stage 2: Schedule the sound - Object.keys(model.sounds).forEach((id) => { - model.sounds[id].start(startTime); - }); -} - function parseGraph(graph) { const { sound } = graph; @@ -49,12 +27,43 @@ function parseGraph(graph) { } else { throw new Error(`Tried to access ${sound.property} of non-existant sound ${sound.name}`); } -}; +} function parsePlay(operation) { model.sounds[operation.sound.name].schedule(operation.patterns); } +function parseTempo(operation) { + changeTempo(operation.value); +} + +function runScene(scene) { + // a scene is a collection of lines that go together. + + // Stage 1: build the scene + scene.forEach((operation) => { + switch (operation.type) { + case 'graph': + parseGraph(operation); + break; + case 'play': + parsePlay(operation); + break; + case 'tempo': + parseTempo(operation); + break; + default: + } + }); + + const startTime = context.currentTime + 0.01; + + // Stage 2: Schedule the sound + Object.keys(model.sounds).forEach((id) => { + model.sounds[id].start(startTime); + }); +} + function clearScene() { Object.keys(model.sounds).forEach((id) => { model.sounds[id].destroy(); diff --git a/slang.js b/src/slang.js similarity index 87% rename from slang.js rename to src/slang.js index 4e388cd..08d6a06 100644 --- a/slang.js +++ b/src/slang.js @@ -1,8 +1,7 @@ -import util from 'util'; import ohm from 'ohm-js'; import range from 'lodash/range'; import * as Range from 'tonal-range'; -import grammarDefinition from './slang-grammar'; +import grammarDefinition from './slang.ohm'; import runtime from './runtime'; const grammar = ohm.grammar(grammarDefinition); @@ -12,6 +11,8 @@ semantics.addOperation('toAST', { Comment(hash, text) { return { type: 'comment', + hash, + text, }; }, Line: rule => rule.toAST(), @@ -35,11 +36,11 @@ semantics.addOperation('toAST', { type: 'function', function: func, arguments: rest, + rp, }; }, PolySoundBlock(monoSB, plus, rest) { - // Because of the way we wrote the parser, // normal non-polyphonic blocks will still // hit the PolySoundBlock definition. It's @@ -69,7 +70,7 @@ semantics.addOperation('toAST', { // This is will be a list of soundArguments. arguments: rest, name: name.sourceString, - } + }; }, // soundArgument: s => s.sourceString, @@ -88,10 +89,18 @@ semantics.addOperation('toAST', { }; }, + Tempo(kw, value) { + return { + type: 'tempo', + value: value.toAST(), + }; + }, + list(lb, soundArguments, rb) { return { type: 'list', arguments: soundArguments.asIteration().toAST(), + rb, }; }, @@ -99,24 +108,34 @@ semantics.addOperation('toAST', { return { type: 'list', arguments: range( - parseInt(arg1.sourceString), - parseInt(arg2.sourceString) + parseInt(arg1.sourceString, 10), + parseInt(arg2.sourceString, 10), ), + rb, }; }, range_note(lb, arg1, __, arg2, rb) { return { type: 'list', - arguments: Range.chromatic( - [arg1.sourceString, arg2.sourceString] - ), + arguments: Range.chromatic([arg1.sourceString, arg2.sourceString]), + rb, }; }, - int: (neg, i) => neg.sourceString ? parseInt(i.sourceString) * -1 : parseInt(i.sourceString), - float: (f) => parseFloat(f.sourceString), - note: n => isNaN(n.sourceString) ? n.sourceString : +n.sourceString, + int: (neg, i) => { + const int = neg.sourceString + ? parseInt(i.sourceString, 10) * -1 + : parseInt(i.sourceString, 10); + return int; + }, + float: f => parseFloat(f.sourceString), + note: (n) => { + const note = (typeof n.sourceString !== 'number') + ? n.sourceString + : +n.sourceString; + return note; + }, rhythm: (r, num, beat) => r.sourceString + num.sourceString + beat.sourceString, }); @@ -137,11 +156,12 @@ export function runScene(text) { // 3. reduce the current set // by appending tab-prefixed // lines onto their predecessor. - .reduce((lines, thisLine, i) => { + .reduce((srcLines, thisLine) => { // If this line is only whitespace and a comment, // let's return early and ignore it here. This will // allow us to support multi-line calls with comments // interspersed. + const lines = srcLines.slice(); if (thisLine.trim().charAt(0) === '#') { return lines; } @@ -187,11 +207,9 @@ export function runScene(text) { // Those seem complicated so instead // let's be marginally helpful by // referencing which "command" it is. - throw new Error( - String(match.message) - .replace('Line 1', `Command ${i}`) - .replace('> 1 | ', '> ') - ); + throw new Error(String(match.message) + .replace('Line 1', `Command ${i}`) + .replace('> 1 | ', '> ')); } // Next we give that to the semantics tool // that we imbued with the `toAST` operation. diff --git a/slang.ohm b/src/slang.ohm similarity index 93% rename from slang.ohm rename to src/slang.ohm index b73d8f9..9b326d2 100644 --- a/slang.ohm +++ b/src/slang.ohm @@ -1,5 +1,5 @@ Sound { - Line = Graph | Play | Comment + Line = Graph | Play | Comment | Tempo /* A comment is any line that begins with # @@ -67,6 +67,15 @@ Sound { Pipe = ("~" PolySoundBlock)+ + /* + TEMPO LINES + A tempo line is a tempo keyword, followed by + the sound beats per minute. + */ + + Tempo = TempoKeyword int + TempoKeyword = "TEMPO" | "|" + /* PLAY LINES A play line is a play keyword (either 'play' diff --git a/public/audio/acoustic/hihat1.mp3 b/src/static/audio/acoustic/hihat1.mp3 similarity index 100% rename from public/audio/acoustic/hihat1.mp3 rename to src/static/audio/acoustic/hihat1.mp3 diff --git a/public/audio/acoustic/hihat2.mp3 b/src/static/audio/acoustic/hihat2.mp3 similarity index 100% rename from public/audio/acoustic/hihat2.mp3 rename to src/static/audio/acoustic/hihat2.mp3 diff --git a/public/audio/acoustic/hihat_open1.mp3 b/src/static/audio/acoustic/hihat_open1.mp3 similarity index 100% rename from public/audio/acoustic/hihat_open1.mp3 rename to src/static/audio/acoustic/hihat_open1.mp3 diff --git a/public/audio/acoustic/hihat_open2.mp3 b/src/static/audio/acoustic/hihat_open2.mp3 similarity index 100% rename from public/audio/acoustic/hihat_open2.mp3 rename to src/static/audio/acoustic/hihat_open2.mp3 diff --git a/public/audio/acoustic/hihat_open3.mp3 b/src/static/audio/acoustic/hihat_open3.mp3 similarity index 100% rename from public/audio/acoustic/hihat_open3.mp3 rename to src/static/audio/acoustic/hihat_open3.mp3 diff --git a/public/audio/acoustic/kick1.mp3 b/src/static/audio/acoustic/kick1.mp3 similarity index 100% rename from public/audio/acoustic/kick1.mp3 rename to src/static/audio/acoustic/kick1.mp3 diff --git a/public/audio/acoustic/kick2.mp3 b/src/static/audio/acoustic/kick2.mp3 similarity index 100% rename from public/audio/acoustic/kick2.mp3 rename to src/static/audio/acoustic/kick2.mp3 diff --git a/public/audio/acoustic/kick3.mp3 b/src/static/audio/acoustic/kick3.mp3 similarity index 100% rename from public/audio/acoustic/kick3.mp3 rename to src/static/audio/acoustic/kick3.mp3 diff --git a/public/audio/acoustic/rim1.mp3 b/src/static/audio/acoustic/rim1.mp3 similarity index 100% rename from public/audio/acoustic/rim1.mp3 rename to src/static/audio/acoustic/rim1.mp3 diff --git a/public/audio/acoustic/snare1.mp3 b/src/static/audio/acoustic/snare1.mp3 similarity index 100% rename from public/audio/acoustic/snare1.mp3 rename to src/static/audio/acoustic/snare1.mp3 diff --git a/public/audio/acoustic/snare2.mp3 b/src/static/audio/acoustic/snare2.mp3 similarity index 100% rename from public/audio/acoustic/snare2.mp3 rename to src/static/audio/acoustic/snare2.mp3 diff --git a/public/audio/acoustic/snare3.mp3 b/src/static/audio/acoustic/snare3.mp3 similarity index 100% rename from public/audio/acoustic/snare3.mp3 rename to src/static/audio/acoustic/snare3.mp3 diff --git a/public/index.css b/src/static/css/editor.css similarity index 98% rename from public/index.css rename to src/static/css/editor.css index 250111f..980858b 100644 --- a/public/index.css +++ b/src/static/css/editor.css @@ -131,6 +131,10 @@ footer a:hover { background-color: #fb5e39; } +.button.blue { + background-color: #28bed3; +} + .button.gray { background-color: gray; } diff --git a/src/static/editor.html b/src/static/editor.html new file mode 100644 index 0000000..27a60bc --- /dev/null +++ b/src/static/editor.html @@ -0,0 +1,33 @@ + + + + + + + +
+
+
Run
+
Stop
+
+
+
Stopped
+
+
+
Create URL
+ Docs +
+
+
+ + + diff --git a/src/static/img/logo.svg b/src/static/img/logo.svg new file mode 100644 index 0000000..b996054 --- /dev/null +++ b/src/static/img/logo.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js deleted file mode 100644 index 1ebe4ce..0000000 --- a/webpack.config.js +++ /dev/null @@ -1,16 +0,0 @@ -const path = require('path'); - -module.exports = { - mode: 'development', - entry: './editor.js', - output: { - filename: 'site.js', - path: path.resolve(__dirname, 'public/build') - }, - module: { - rules: [{ - test: /\.css$/, - use: [ 'style-loader', 'css-loader' ] - }], - }, -}; diff --git a/webpack.prod.js b/webpack.prod.js deleted file mode 100644 index d18213f..0000000 --- a/webpack.prod.js +++ /dev/null @@ -1,26 +0,0 @@ -const path = require('path'); - -module.exports = { - mode: 'production', - entry: './editor.js', - output: { - filename: 'site.min.js', - path: path.resolve(__dirname, 'public/build') - }, - module: { - rules: [{ - test: /\.css$/, - use: [ 'style-loader', 'css-loader' ] - }, - { - test: /\.m?js$/, - exclude: /(node_modules|bower_components)/, - use: { - loader: 'babel-loader', - options: { - presets: ['@babel/preset-env'] - } - } - }], - }, -};