changeset 331:dcb015c5a845

Merge pull request #37 from LucasThompson/dev/notes-bodge Dev/notes bodge
author Lucas Thompson <LucasThompson@users.noreply.github.com>
date Wed, 17 May 2017 14:13:12 +0100
parents cddc1cc19d8d (current diff) cc8a1e08dd2d (diff)
children 6ada6ca40baf
files
diffstat 6 files changed, 369 insertions(+), 234 deletions(-) [+]
line wrap: on
line diff
--- a/src/app/feature-extraction-menu/feature-extraction-menu.component.ts	Fri May 12 17:20:31 2017 +0100
+++ b/src/app/feature-extraction-menu/feature-extraction-menu.component.ts	Wed May 17 14:13:12 2017 +0100
@@ -80,9 +80,9 @@
   }
 
   ngOnInit() {
-    this.piperService.list().then(this.populateExtractors);
     this.librariesUpdatedSubscription =
       this.piperService.librariesUpdated$.subscribe(this.populateExtractors);
+    this.piperService.list().then(this.populateExtractors);
   }
 
   extract(combinedKey: string): void {
@@ -94,9 +94,7 @@
   }
 
   load(): void {
-    this.piperService.updateAvailableLibraries().subscribe(res => {
-      Object.keys(res).forEach(key => this.piperService.load(key));
-    });
+    this.piperService.updateAvailableLibraries();
   }
 
   ngOnDestroy(): void {
--- a/src/app/playback-control/playback-control.component.html	Fri May 12 17:20:31 2017 +0100
+++ b/src/app/playback-control/playback-control.component.html	Wed May 17 14:13:12 2017 +0100
@@ -1,3 +1,6 @@
+<button md-icon-button (click)="emitFastRewindStart()">
+  <md-icon>skip_previous</md-icon>
+</button>
 <button md-icon-button (click)="emitPlayPause()">
   <md-icon>
     <ng-template [ngIf]="isPlaying()">pause</ng-template>
--- a/src/app/services/feature-extraction/FeatureExtractionWorker.ts	Fri May 12 17:20:31 2017 +0100
+++ b/src/app/services/feature-extraction/FeatureExtractionWorker.ts	Wed May 17 14:13:12 2017 +0100
@@ -2,7 +2,7 @@
  * Created by lucas on 01/12/2016.
  */
 
-import {PiperVampService, ListRequest, ListResponse} from 'piper';
+import {PiperVampService, ListRequest, ListResponse, Service} from 'piper';
 import {
   SimpleRequest
 } from 'piper/HigherLevelUtilities';
@@ -30,33 +30,51 @@
 type LibraryUri = string;
 type LibraryKey = string;
 
-type RequireJs = (libs: string[], callback: (...libs: any[]) => void) => void;
+type RequireJs = (libs: string[],
+                  callback: (...libs: any[]) => void,
+                  errBack: (...failedLibIds: string[]) => void) => void;
+type Factory<T> = () => T;
+
+function waterfall<T>(tasks: (() => Promise<T>)[]): Promise<T[]> {
+  const reducer = (running: T[], next: Promise<T>): Promise<T[]> => {
+    return next.then(response => {
+      running = running.concat(response);
+      return running;
+    });
+  };
+
+  return tasks.reduce((runningResponses, nextResponse) => {
+    return runningResponses.then(response => {
+      return reducer(response, nextResponse());
+    });
+  }, Promise.resolve([]));
+}
 
 class AggregateStreamingService implements StreamingService {
-  private services: Map<LibraryKey, PiperStreamingService>;
+  private services: Map<LibraryKey, Factory<PiperStreamingService>>;
 
   constructor() {
-    this.services = new Map<LibraryKey, PiperStreamingService>();
+    this.services = new Map<LibraryKey, Factory<PiperStreamingService>>();
     this.services.set(
       'vamp-example-plugins',
-      new PiperStreamingService(new PiperVampService(VampExamplePlugins()))
+      () => new PiperStreamingService(
+        new PiperVampService(VampExamplePlugins())
+      )
     );
   }
 
-  addService(key: LibraryKey, service: PiperStreamingService): void {
+  addService(key: LibraryKey, service: Factory<PiperStreamingService>): void {
     this.services.set(key, service);
   }
 
   list(request: ListRequest): Promise<ListResponse> {
-    return Promise.all(
-      [...this.services.values()].map(client => client.list({}))
-    ).then(allAvailable => ({
-        available: allAvailable.reduce(
-          (all, current) => all.concat(current.available),
-          []
-        )
-      })
-    );
+    const listThunks: (() => Promise<ListResponse>)[] = [
+      ...this.services.values()
+    ].map(createClient => () => createClient().list({}));
+
+    return waterfall(listThunks).then(responses => ({
+      available: responses.reduce((flat, res) => flat.concat(res.available), [])
+    }));
   }
 
   process(request: SimpleRequest): Observable<StreamingResponse> {
@@ -66,8 +84,8 @@
   protected dispatch(method: 'process',
                      request: SimpleRequest): Observable<StreamingResponse> {
     const key = request.key.split(':')[0];
-    return this.services.has(key) ?
-      this.services.get(key)[method](request) : Observable.throw('Invalid key');
+    return this.services.has(key) ? this.services.get(key)()[method](request) :
+      Observable.throw('Invalid key');
   }
 }
 
@@ -104,14 +122,12 @@
 
 export default class FeatureExtractionWorker {
   private workerScope: DedicatedWorkerGlobalScope;
-  private remoteLibraries: Map<LibraryKey, LibraryUri>;
   private server: WebWorkerStreamingServer;
   private service: AggregateStreamingService;
 
   constructor(workerScope: DedicatedWorkerGlobalScope,
               private requireJs: RequireJs) {
     this.workerScope = workerScope;
-    this.remoteLibraries = new Map<LibraryKey, LibraryUri>();
     this.service = new ThrottledReducingAggregateService();
     this.setupImportLibraryListener();
     this.server = new WebWorkerStreamingServer(
@@ -123,36 +139,47 @@
   private setupImportLibraryListener(): void {
 
     this.workerScope.onmessage = (ev: MessageEvent) => {
-      const sendResponse = (result) => {
-        this.workerScope.postMessage({
-          method: ev.data.method,
-          result: result
-        });
-      };
       switch (ev.data.method) {
-        case 'import':
-          const key: LibraryKey = ev.data.params;
-          if (this.remoteLibraries.has(key)) {
-            this.requireJs([this.remoteLibraries.get(key)], (plugin) => {
-              // TODO a factory with more logic probably belongs in piper-js
-              const lib: any | EmscriptenModule = plugin.createLibrary();
-              const isEmscriptenModule = typeof lib.cwrap === 'function';
-              const service = new PiperStreamingService(
-                isEmscriptenModule ? new PiperVampService(lib) : lib // TODO
-              );
-              this.service.addService(key, service);
-              this.service.list({}).then(sendResponse);
-            });
-          } else {
-            console.error('Non registered library key.'); // TODO handle error
-          }
-          break;
         case 'addRemoteLibraries': // TODO rename
           const available: AvailableLibraries = ev.data.params;
-          Object.keys(available).forEach(libraryKey => {
-            this.remoteLibraries.set(libraryKey, available[libraryKey]);
+          const importThunks = Object.keys(available).map(libraryKey => {
+            return () => {
+              return this.downloadRemoteLibrary(
+                libraryKey,
+                available[libraryKey]
+              ).then(createService => {
+                this.service.addService(libraryKey,
+                  () => new PiperStreamingService(
+                    createService()
+                  ));
+              });
+            };
           });
+          waterfall(importThunks)
+            .then(() => this.service.list({}))
+            .then(response => {
+              this.workerScope.postMessage({
+                method: 'import',
+                result: response
+              });
+            });
       }
     };
   }
+
+  private downloadRemoteLibrary(key: LibraryKey,
+                                uri: LibraryUri): Promise<Factory<Service>> {
+    return new Promise((res, rej) => {
+      this.requireJs([uri], (plugin) => {
+        res(() => {
+          // TODO a factory with more logic probably belongs in piper-js
+          const lib: any | EmscriptenModule = plugin.createLibrary();
+          const isEmscriptenModule = typeof lib.cwrap === 'function';
+          return isEmscriptenModule ? new PiperVampService(lib) : lib; // TODO
+        });
+      }, (err) => {
+        rej(`Failed to load ${key} remote module.`);
+      });
+    });
+  }
 }
--- a/src/app/services/feature-extraction/feature-extraction.service.ts	Fri May 12 17:20:31 2017 +0100
+++ b/src/app/services/feature-extraction/feature-extraction.service.ts	Wed May 17 14:13:12 2017 +0100
@@ -8,7 +8,7 @@
 } from 'piper/HigherLevelUtilities';
 import {Subject} from 'rxjs/Subject';
 import {Observable} from 'rxjs/Observable';
-import {Http, Response} from '@angular/http';
+import {Http} from '@angular/http';
 import {
   countingIdProvider,
   WebWorkerStreamingClient
@@ -87,20 +87,16 @@
     });
   }
 
-  updateAvailableLibraries(): Observable<AvailableLibraries> {
-    return this.http.get(this.repositoryUri)
-      .map(res => {
-        const map = res.json();
+  updateAvailableLibraries(): void {
+    this.http.get(this.repositoryUri)
+      .toPromise() // just turn into a promise for now to subscribe / execute
+      .then(res => {
         this.worker.postMessage({
           method: 'addRemoteLibraries',
-          params: map
-        });
-        return map;
+          params: res.json()
+        })
       })
-      .catch((error: Response | any) => {
-        console.error(error);
-        return Observable.throw(error);
-      });
+      .catch(console.error); // TODO Report error to user
   }
 
   load(libraryKey: string): void {
--- a/src/app/waveform/waveform.component.ts	Fri May 12 17:20:31 2017 +0100
+++ b/src/app/waveform/waveform.component.ts	Wed May 17 14:13:12 2017 +0100
@@ -25,7 +25,7 @@
   MatrixFeature,
   TracksFeature
 } from 'piper/HigherLevelUtilities';
-import {toSeconds} from 'piper';
+import {toSeconds, OutputDescriptor} from 'piper';
 import {FeatureList, Feature} from 'piper/Feature';
 import * as Hammer from 'hammerjs';
 import {WavesSpectrogramLayer} from '../spectrogram/Spectrogram';
@@ -53,6 +53,15 @@
   '#034748' // "deep jungle green"
 ]);
 
+type HigherLevelFeatureShape = 'regions' | 'instants' | 'notes';
+type NoteLikeUnit = 'midi' | 'hz' ;
+interface Note {
+  time: number;
+  duration: number;
+  pitch: number;
+  velocity?: number;
+}
+
 @Component({
   selector: 'ugly-waveform',
   templateUrl: './waveform.component.html',
@@ -754,100 +763,55 @@
         if (featureData.length === 0) {
           return;
         }
-        // TODO look at output descriptor instead of directly inspecting features
-        const hasDuration = outputDescriptor.configured.hasDuration;
-        const isMarker = !hasDuration
-          && outputDescriptor.configured.binCount === 0
-          && featureData[0].featureValues == null;
-        const isRegion = hasDuration
-          && featureData[0].timestamp != null;
-        console.log('Have list features: length ' + featureData.length +
-          ', isMarker ' + isMarker + ', isRegion ' + isRegion +
-          ', hasDuration ' + hasDuration);
+
         // TODO refactor, this is incomprehensible
-        if (isMarker) {
-          const plotData = featureData.map(feature => ({
-            time: toSeconds(feature.timestamp),
-            label: feature.label
-          }));
-          const featureLayer = new wavesUI.helpers.TickLayer(plotData, {
-            height: height,
-            color: colour,
-            labelPosition: 'bottom',
-            shadeSegments: true
-          });
-          this.addLayer(
-            featureLayer,
-            waveTrack,
-            this.timeline.timeContext
+        try {
+          const featureShape = deduceHigherLevelFeatureShape(
+            featureData,
+            outputDescriptor
           );
-        } else if (isRegion) {
-          console.log('Output is of region type');
-          const binCount = outputDescriptor.configured.binCount || 0;
-          const isBarRegion = featureData[0].featureValues.length >= 1 || binCount >= 1 ;
-          const getSegmentArgs = () => {
-            if (isBarRegion) {
-
-              // TODO refactor - this is messy
-              interface FoldsToNumber<T> {
-                reduce(fn: (previousValue: number,
-                            currentValue: T,
-                            currentIndex: number,
-                            array: ArrayLike<T>) => number,
-                       initialValue?: number): number;
-              }
-
-              // TODO potentially change impl., i.e avoid reduce
-              const findMin = <T>(arr: FoldsToNumber<T>, getElement: (x: T) => number): number => {
-                return arr.reduce((min, val) => Math.min(min, getElement(val)), Infinity);
-              };
-
-              const findMax = <T>(arr: FoldsToNumber<T>, getElement: (x: T) => number): number => {
-                return arr.reduce((min, val) => Math.max(min, getElement(val)), -Infinity);
-              };
-
-              const min = findMin<Feature>(featureData, (x: Feature) => {
-                return findMin<number>(x.featureValues, y => y);
+          switch (featureShape) {
+            case 'instants':
+              const plotData = featureData.map(feature => ({
+                time: toSeconds(feature.timestamp),
+                label: feature.label
+              }));
+              const featureLayer = new wavesUI.helpers.TickLayer(plotData, {
+                height: height,
+                color: colour,
+                labelPosition: 'bottom',
+                shadeSegments: true
               });
-
-              const max = findMax<Feature>(featureData, (x: Feature) => {
-                return findMax<number>(x.featureValues, y => y);
-              });
-
-              const barHeight = 1.0 / height;
-              return [
-                featureData.reduce((bars, feature) => {
-                  const staticProperties = {
-                    x: toSeconds(feature.timestamp),
-                    width: toSeconds(feature.duration),
-                    height: min + barHeight,
-                    color: colour,
-                    opacity: 0.8
-                  };
-                  // TODO avoid copying Float32Array to an array - map is problematic here
-                  return bars.concat([...feature.featureValues]
-                    .map(val => Object.assign({}, staticProperties, {y: val})));
-                }, []),
-                {yDomain: [min, max + barHeight], height: height} as any
-              ];
-            } else {
-              return [featureData.map(feature => ({
-                x: toSeconds(feature.timestamp),
-                width: toSeconds(feature.duration),
-                color: colour,
-                opacity: 0.8
-              })), {height: height}];
-            }
-          };
-
-          const segmentLayer = new wavesUI.helpers.SegmentLayer(
-            ...getSegmentArgs()
-          );
-          this.addLayer(
-            segmentLayer,
-            waveTrack,
-            this.timeline.timeContext
-          );
+              this.addLayer(
+                featureLayer,
+                waveTrack,
+                this.timeline.timeContext
+              );
+              break;
+            case 'regions':
+              this.renderRegions(
+                featureData,
+                outputDescriptor,
+                waveTrack,
+                height,
+                colour
+              );
+              break;
+            case 'notes':
+              const pianoRollLayer = new wavesUI.helpers.PianoRollLayer(
+                mapFeaturesToNotes(featureData, outputDescriptor),
+                {height: height, color: colour}
+              );
+              this.addLayer(
+                pianoRollLayer,
+                waveTrack,
+                this.timeline.timeContext
+              );
+              break;
+          }
+        } catch (e) {
+          console.warn(e); // TODO display
+          break;
         }
         break;
       }
@@ -947,6 +911,89 @@
     });
   }
 
+  // TODO not sure how much of the logic in here is actually sensible w.r.t
+  // what it functionally produces
+  private renderRegions(featureData: FeatureList,
+                        outputDescriptor: OutputDescriptor,
+                        waveTrack: any,
+                        height: number,
+                        colour: Colour) {
+    console.log('Output is of region type');
+    const binCount = outputDescriptor.configured.binCount || 0;
+    const isBarRegion = featureData[0].featureValues.length >= 1 || binCount >= 1 ;
+    const getSegmentArgs = () => {
+      if (isBarRegion) {
+
+        // TODO refactor - this is messy
+        interface FoldsToNumber<T> {
+          reduce(fn: (previousValue: number,
+                      currentValue: T,
+                      currentIndex: number,
+                      array: ArrayLike<T>) => number,
+                 initialValue?: number): number;
+        }
+
+        // TODO potentially change impl., i.e avoid reduce
+        const findMin = <T>(arr: FoldsToNumber<T>,
+                            getElement: (x: T) => number): number => {
+          return arr.reduce(
+            (min, val) => Math.min(min, getElement(val)),
+            Infinity
+          );
+        };
+
+        const findMax = <T>(arr: FoldsToNumber<T>,
+                            getElement: (x: T) => number): number => {
+          return arr.reduce(
+            (min, val) => Math.max(min, getElement(val)),
+            -Infinity
+          );
+        };
+
+        const min = findMin<Feature>(featureData, (x: Feature) => {
+          return findMin<number>(x.featureValues, y => y);
+        });
+
+        const max = findMax<Feature>(featureData, (x: Feature) => {
+          return findMax<number>(x.featureValues, y => y);
+        });
+
+        const barHeight = 1.0 / height;
+        return [
+          featureData.reduce((bars, feature) => {
+            const staticProperties = {
+              x: toSeconds(feature.timestamp),
+              width: toSeconds(feature.duration),
+              height: min + barHeight,
+              color: colour,
+              opacity: 0.8
+            };
+            // TODO avoid copying Float32Array to an array - map is problematic here
+            return bars.concat([...feature.featureValues]
+              .map(val => Object.assign({}, staticProperties, {y: val})));
+          }, []),
+          {yDomain: [min, max + barHeight], height: height} as any
+        ];
+      } else {
+        return [featureData.map(feature => ({
+          x: toSeconds(feature.timestamp),
+          width: toSeconds(feature.duration),
+          color: colour,
+          opacity: 0.8
+        })), {height: height}];
+      }
+    };
+
+    const segmentLayer = new wavesUI.helpers.SegmentLayer(
+      ...getSegmentArgs()
+    );
+    this.addLayer(
+      segmentLayer,
+      waveTrack,
+      this.timeline.timeContext
+    );
+  }
+
   private addLayer(layer: Layer, track: Track, timeContext: any, isAxis: boolean = false): void {
     timeContext.zoom = 1.0;
     if (!layer.timeContext) {
@@ -1003,3 +1050,56 @@
     }
   }
 }
+
+function deduceHigherLevelFeatureShape(featureData: FeatureList,
+                                       descriptor: OutputDescriptor)
+: HigherLevelFeatureShape {
+  // TODO look at output descriptor instead of directly inspecting features
+  const hasDuration = descriptor.configured.hasDuration;
+  const binCount = descriptor.configured.binCount;
+  const isMarker = !hasDuration
+    && binCount === 0
+    && featureData[0].featureValues == null;
+
+  const isMaybeNote = getCanonicalNoteLikeUnit(descriptor.configured.unit)
+    && [1, 2].find(nBins => nBins === binCount);
+
+  const isRegionLike = hasDuration && featureData[0].timestamp != null;
+
+  const isNote = isMaybeNote && isRegionLike;
+  const isRegion = !isMaybeNote && isRegionLike;
+  if (isMarker) {
+    return 'instants';
+  }
+  if (isNote) {
+    return 'notes';
+  }
+  if (isRegion) {
+    return 'regions';
+  }
+  throw 'No shape could be deduced';
+}
+
+function getCanonicalNoteLikeUnit(unit: string): NoteLikeUnit | null {
+  const canonicalUnits: NoteLikeUnit[] = ['midi', 'hz'];
+  return canonicalUnits.find(canonicalUnit => {
+    return unit.toLowerCase().indexOf(canonicalUnit) >= 0
+  });
+}
+
+function mapFeaturesToNotes(featureData: FeatureList,
+                            descriptor: OutputDescriptor): Note[] {
+  const canonicalUnit = getCanonicalNoteLikeUnit(descriptor.configured.unit);
+  const isHz = canonicalUnit === 'hz';
+  return featureData.map(feature => ({
+    time: toSeconds(feature.timestamp),
+    duration: toSeconds(feature.duration),
+    pitch: isHz ?
+      frequencyToMidiNote(feature.featureValues[0]) : feature.featureValues[0]
+  }));
+}
+
+function frequencyToMidiNote(frequency: number,
+                             concertA: number = 440.0): number {
+  return 69 + 12 * Math.log2(frequency / concertA);
+}
--- a/yarn.lock	Fri May 12 17:20:31 2017 +0100
+++ b/yarn.lock	Wed May 17 14:13:12 2017 +0100
@@ -97,8 +97,8 @@
   resolved "https://registry.yarnpkg.com/@angular/http/-/http-4.1.2.tgz#fc378c3330c0410e1fb8aac2546329a6887776e4"
 
 "@angular/material@^2.0.0-beta.3":
-  version "2.0.0-beta.3"
-  resolved "https://registry.yarnpkg.com/@angular/material/-/material-2.0.0-beta.3.tgz#ec31dee61d7300ece28fee476852db236ded1e13"
+  version "2.0.0-beta.5"
+  resolved "https://registry.yarnpkg.com/@angular/material/-/material-2.0.0-beta.5.tgz#712141ebfa77e3ace3ec8e32b5953b355e773965"
 
 "@angular/platform-browser-dynamic@^4.0.0":
   version "4.1.2"
@@ -1009,8 +1009,8 @@
     lodash.uniq "^4.5.0"
 
 caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
-  version "1.0.30000667"
-  resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000667.tgz#fb6060dbf349c101df26f421442419802fc6dab1"
+  version "1.0.30000670"
+  resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000670.tgz#90d33b79e3090e25829c311113c56d6b1788bf43"
 
 capture-stack-trace@^1.0.0:
   version "1.0.0"
@@ -1064,9 +1064,9 @@
   dependencies:
     chalk "^1.1.3"
 
-clean-css@4.0.x:
-  version "4.0.13"
-  resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.0.13.tgz#feb2a176062d72a6c3e624d9213cac6a0c485e80"
+clean-css@4.1.x:
+  version "4.1.2"
+  resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.1.2.tgz#6029aea30b1d9520a968a3caee0dabb1184e353a"
   dependencies:
     source-map "0.5.x"
 
@@ -1207,7 +1207,7 @@
   dependencies:
     delayed-stream "~1.0.0"
 
-commander@^2.5.0, commander@^2.6.0, commander@2.9.x:
+commander@^2.5.0, commander@^2.6.0, commander@~2.9.0, commander@2.9.x:
   version "2.9.0"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4"
   dependencies:
@@ -1294,11 +1294,11 @@
   resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.3.0.tgz#e51d17f8f0ef0db90a64fdb47de3051556e9f169"
 
 connect@^3.3.5:
-  version "3.6.1"
-  resolved "https://registry.yarnpkg.com/connect/-/connect-3.6.1.tgz#b7760693a74f0454face1d9378edb3f885b43227"
-  dependencies:
-    debug "2.6.3"
-    finalhandler "1.0.1"
+  version "3.6.2"
+  resolved "https://registry.yarnpkg.com/connect/-/connect-3.6.2.tgz#694e8d20681bfe490282c8ab886be98f09f42fe7"
+  dependencies:
+    debug "2.6.7"
+    finalhandler "1.0.3"
     parseurl "~1.3.1"
     utils-merge "1.0.0"
 
@@ -1565,11 +1565,11 @@
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
 
-debug@*, debug@^2.1.1, debug@^2.1.3, debug@^2.2.0, debug@^2.6.3, debug@2:
-  version "2.6.6"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.6.tgz#a9fa6fbe9ca43cf1e79f73b75c0189cbb7d6db5a"
-  dependencies:
-    ms "0.7.3"
+debug@*, debug@^2.1.1, debug@^2.1.3, debug@^2.2.0, debug@^2.6.3, debug@2, debug@2.6.7:
+  version "2.6.7"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.7.tgz#92bad1f6d05bbb6bba22cca88bcd0ec894c2861e"
+  dependencies:
+    ms "2.0.0"
 
 debug@~2.2.0, debug@2.2.0:
   version "2.2.0"
@@ -1589,18 +1589,6 @@
   dependencies:
     ms "0.7.2"
 
-debug@2.6.3:
-  version "2.6.3"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.3.tgz#0f7eb8c30965ec08c72accfa0130c8b79984141d"
-  dependencies:
-    ms "0.7.2"
-
-debug@2.6.4:
-  version "2.6.4"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.4.tgz#7586a9b3c39741c0282ae33445c4e8ac74734fe0"
-  dependencies:
-    ms "0.7.3"
-
 decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
@@ -1813,8 +1801,8 @@
   resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
 
 electron-to-chromium@^1.2.7:
-  version "1.3.10"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.10.tgz#63d62b785471f0d8dda85199d64579de8a449f08"
+  version "1.3.11"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.11.tgz#744761df1d67b492b322ce9aa0aba5393260eb61"
 
 elliptic@^6.0.0:
   version "6.4.0"
@@ -2130,23 +2118,11 @@
     repeat-element "^1.1.2"
     repeat-string "^1.5.2"
 
-finalhandler@~1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.2.tgz#d0e36f9dbc557f2de14423df6261889e9d60c93a"
-  dependencies:
-    debug "2.6.4"
-    encodeurl "~1.0.1"
-    escape-html "~1.0.3"
-    on-finished "~2.3.0"
-    parseurl "~1.3.1"
-    statuses "~1.3.1"
-    unpipe "~1.0.0"
-
-finalhandler@1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.1.tgz#bcd15d1689c0e5ed729b6f7f541a6df984117db8"
-  dependencies:
-    debug "2.6.3"
+finalhandler@~1.0.0, finalhandler@1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.3.tgz#ef47e77950e999780e86022a560e3217e0d0cc89"
+  dependencies:
+    debug "2.6.7"
     encodeurl "~1.0.1"
     escape-html "~1.0.3"
     on-finished "~2.3.0"
@@ -2529,17 +2505,17 @@
   resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f"
 
 html-minifier@^3.2.3:
-  version "3.4.4"
-  resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-3.4.4.tgz#616fe3e3ef16da02b393d9a6099eeff468a35df0"
+  version "3.5.0"
+  resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-3.5.0.tgz#98be1b18f87443592722f654e67a1541f22018cb"
   dependencies:
     camel-case "3.0.x"
-    clean-css "4.0.x"
+    clean-css "4.1.x"
     commander "2.9.x"
     he "1.1.x"
     ncname "1.0.x"
     param-case "2.1.x"
     relateurl "0.2.x"
-    uglify-js "~2.8.22"
+    uglify-js "3.0.x"
 
 html-webpack-plugin@^2.19.0:
   version "2.28.0"
@@ -2653,8 +2629,8 @@
   resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
 
 image-size@~0.5.0:
-  version "0.5.3"
-  resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.3.tgz#5cbe9fafc8436386ceb7e9e3a9d90c5b71b70ad9"
+  version "0.5.4"
+  resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.4.tgz#94e07beec0659386f1aefb84b2222e88405485cd"
 
 img-stats@^0.5.2:
   version "0.5.2"
@@ -3052,7 +3028,7 @@
     glob "^7.0.6"
     jasmine-core "~2.6.0"
 
-jasminewd2@^2.0.0:
+jasminewd2@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/jasminewd2/-/jasminewd2-2.1.0.tgz#da595275d1ae631de736ac0a7c7d85c9f73ef652"
 
@@ -3263,8 +3239,8 @@
     is-buffer "^1.0.2"
 
 kind-of@^3.0.2:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.0.tgz#b58abe4d5c044ad33726a8c1525b48cf891bff07"
+  version "3.2.2"
+  resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
   dependencies:
     is-buffer "^1.1.5"
 
@@ -3546,7 +3522,11 @@
     bn.js "^4.0.0"
     brorand "^1.0.1"
 
-"mime-db@>= 1.27.0 < 2", mime-db@~1.27.0:
+"mime-db@>= 1.27.0 < 2":
+  version "1.28.0"
+  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.28.0.tgz#fedd349be06d2865b7fc57d837c6de4f17d7ac3c"
+
+mime-db@~1.27.0:
   version "1.27.0"
   resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1"
 
@@ -3641,9 +3621,9 @@
   version "0.7.2"
   resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765"
 
-ms@0.7.3:
-  version "0.7.3"
-  resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.3.tgz#708155a5e44e33f5fd0fc53e81d0d40a91be1fff"
+ms@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
 
 mute-stream@0.0.7:
   version "0.0.7"
@@ -3734,8 +3714,8 @@
     tar-pack "^3.4.0"
 
 node-sass@^4.3.0, node-sass@^4.5.2:
-  version "4.5.2"
-  resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.5.2.tgz#4012fa2bd129b1d6365117e88d9da0500d99da64"
+  version "4.5.3"
+  resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.5.3.tgz#d09c9d1179641239d1b97ffc6231fdcec53e1568"
   dependencies:
     async-foreach "^0.1.3"
     chalk "^1.1.1"
@@ -4428,8 +4408,8 @@
     asap "~2.0.3"
 
 protractor@~5.1.0:
-  version "5.1.1"
-  resolved "https://registry.yarnpkg.com/protractor/-/protractor-5.1.1.tgz#10c4e336571b28875b8acc3ae3e4e1e40ef7e986"
+  version "5.1.2"
+  resolved "https://registry.yarnpkg.com/protractor/-/protractor-5.1.2.tgz#9b221741709a4c62d5cd53c6aadd54a71137e95f"
   dependencies:
     "@types/node" "^6.0.46"
     "@types/q" "^0.0.32"
@@ -4438,14 +4418,14 @@
     chalk "^1.1.3"
     glob "^7.0.3"
     jasmine "^2.5.3"
-    jasminewd2 "^2.0.0"
+    jasminewd2 "^2.1.0"
     optimist "~0.6.0"
     q "1.4.1"
     saucelabs "~1.3.0"
     selenium-webdriver "3.0.1"
     source-map-support "~0.4.0"
     webdriver-js-extender "^1.0.0"
-    webdriver-manager "^12.0.1"
+    webdriver-manager "^12.0.6"
 
 proxy-addr@~1.1.3:
   version "1.1.4"
@@ -4899,13 +4879,13 @@
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7"
 
 sass-graph@^2.1.1:
-  version "2.2.3"
-  resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.3.tgz#2ba9f170f6cafed5b51665abe13cf319c9269c31"
+  version "2.2.4"
+  resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49"
   dependencies:
     glob "^7.0.0"
     lodash "^4.0.0"
     scss-tokenizer "^0.2.3"
-    yargs "^6.6.0"
+    yargs "^7.0.0"
 
 sass-loader@^6.0.3:
   version "6.0.5"
@@ -5650,8 +5630,8 @@
     update-notifier "^2.0.0"
 
 tsutils@^1.1.0:
-  version "1.8.0"
-  resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-1.8.0.tgz#bf8118ed8e80cd5c9fc7d75728c7963d44ed2f52"
+  version "1.9.1"
+  resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-1.9.1.tgz#b9f9ab44e55af9681831d5f28d0aeeaf5c750cb0"
 
 tty-browserify@~0.0.0, tty-browserify@0.0.0:
   version "0.0.0"
@@ -5682,9 +5662,9 @@
   version "2.2.2"
   resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.2.2.tgz#606022508479b55ffa368b58fee963a03dfd7b0c"
 
-uglify-js@^2.6, uglify-js@^2.7.5, uglify-js@~2.8.22:
-  version "2.8.23"
-  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.23.tgz#8230dd9783371232d62a7821e2cf9a817270a8a0"
+uglify-js@^2.6, uglify-js@^2.7.5:
+  version "2.8.26"
+  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.26.tgz#3a1db8ae0a0aba7f92e1ddadadbd0293d549f90e"
   dependencies:
     source-map "~0.5.1"
     yargs "~3.10.0"
@@ -5699,6 +5679,13 @@
     optimist "~0.3.5"
     source-map "~0.1.7"
 
+uglify-js@3.0.x:
+  version "3.0.7"
+  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.0.7.tgz#5cca9c14abae2dd60ceccdf7da3c672cc8069cec"
+  dependencies:
+    commander "~2.9.0"
+    source-map "~0.5.1"
+
 uglify-to-browserify@~1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
@@ -5892,8 +5879,8 @@
     graceful-fs "^4.1.2"
 
 waves-ui-piper@piper-audio/waves-ui-piper:
-  version "0.5.0"
-  resolved "https://codeload.github.com/piper-audio/waves-ui-piper/tar.gz/b68523895c5ff538a5a82b2012d0482a925b6c87"
+  version "0.5.2"
+  resolved "https://codeload.github.com/piper-audio/waves-ui-piper/tar.gz/36af0dd064c209b70744dabb27c575880fa595a1"
   dependencies:
     babel-runtime "^5.8.12"
 
@@ -5910,7 +5897,7 @@
     "@types/selenium-webdriver" "^2.53.35"
     selenium-webdriver "^2.53.2"
 
-webdriver-manager@^12.0.1:
+webdriver-manager@^12.0.6:
   version "12.0.6"
   resolved "https://registry.yarnpkg.com/webdriver-manager/-/webdriver-manager-12.0.6.tgz#3df1a481977010b4cbf8c9d85c7a577828c0e70b"
   dependencies:
@@ -6063,8 +6050,8 @@
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
 
 write-file-atomic@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.0.0.tgz#bb99a5440d0d31dd860a68da392bffeef66251a1"
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.1.0.tgz#1769f4b551eedce419f0505deae2e26763542d37"
   dependencies:
     graceful-fs "^4.1.11"
     imurmurhash "^0.1.4"
@@ -6146,7 +6133,13 @@
   dependencies:
     camelcase "^3.0.0"
 
-yargs@^6.0.0, yargs@^6.6.0:
+yargs-parser@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a"
+  dependencies:
+    camelcase "^3.0.0"
+
+yargs@^6.0.0:
   version "6.6.0"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208"
   dependencies:
@@ -6164,6 +6157,24 @@
     y18n "^3.2.1"
     yargs-parser "^4.2.0"
 
+yargs@^7.0.0:
+  version "7.1.0"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8"
+  dependencies:
+    camelcase "^3.0.0"
+    cliui "^3.2.0"
+    decamelize "^1.1.1"
+    get-caller-file "^1.0.1"
+    os-locale "^1.4.0"
+    read-pkg-up "^1.0.1"
+    require-directory "^2.1.1"
+    require-main-filename "^1.0.1"
+    set-blocking "^2.0.0"
+    string-width "^1.0.2"
+    which-module "^1.0.0"
+    y18n "^3.2.1"
+    yargs-parser "^5.0.0"
+
 yargs@~3.10.0:
   version "3.10.0"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1"