changeset 500:abbc096e0335

Merge branch 'master' of github.com:piper-audio/ugly-duckling into upgrade-piper-js # Conflicts: # package-lock.json # src/app/analysis-item/analysis-item.component.html # src/app/visualisations/FeatureUtilities.ts
author Lucas Thompson <dev@lucas.im>
date Mon, 14 Aug 2017 17:43:09 +0100
parents 43b2f2dae5cc (current diff) 90c7a8bfe519 (diff)
children 62b8e78a38df
files package-lock.json src/app/analysis-item/analysis-item.component.html src/app/app.module.ts src/app/visualisations/FeatureUtilities.ts src/app/visualisations/regions/regions.component.ts
diffstat 4 files changed, 142 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/src/app/analysis-item/analysis-item.component.html	Mon Aug 14 17:23:41 2017 +0100
+++ b/src/app/analysis-item/analysis-item.component.html	Mon Aug 14 17:43:09 2017 +0100
@@ -78,6 +78,20 @@
               [duration]="getDuration()"
             ></ugly-notes>
           </ugly-cross-hair-inspector>
+          <ugly-cross-hair-inspector
+            *ngSwitchCase="'regions'"
+            [unit]="item.unit"
+            [isAnimated]="isActive"
+          >
+            <ugly-regions
+              [colour]="getNextColour()"
+              [timeline]="timeline"
+              [width]="contentWidth"
+              [onSeek]="onSeek"
+              [regions]="item.collected"
+              [duration]="getDuration()"
+            ></ugly-regions>
+          </ugly-cross-hair-inspector>
           <ugly-vertical-scale
             *ngSwitchCase="'matrix'"
           >
--- a/src/app/app.module.ts	Mon Aug 14 17:23:41 2017 +0100
+++ b/src/app/app.module.ts	Mon Aug 14 17:43:09 2017 +0100
@@ -32,6 +32,7 @@
 import {CurveComponent} from './visualisations/curve/curve.component';
 import {TracksComponent} from './visualisations/tracks/tracks.components';
 import {NotesComponent} from './visualisations/notes/notes.component';
+import {RegionsComponent} from './visualisations/regions/regions.component';
 import {InstantsComponent} from './visualisations/instants/instants.component';
 import {GridComponent} from './visualisations/grid/grid.component';
 import {VerticalScaleComponent} from './visualisations/vertical-scale.component';
@@ -133,6 +134,7 @@
     CurveComponent,
     TracksComponent,
     NotesComponent,
+    RegionsComponent,
     InstantsComponent,
     GridComponent,
     VerticalScaleComponent,
--- a/src/app/visualisations/FeatureUtilities.ts	Mon Aug 14 17:23:41 2017 +0100
+++ b/src/app/visualisations/FeatureUtilities.ts	Mon Aug 14 17:43:09 2017 +0100
@@ -19,6 +19,12 @@
   velocity?: number;
 }
 
+export interface Region {
+  time: number;
+  duration: number;
+  value: number;
+}
+
 export function getCanonicalNoteLikeUnit(unit: string): NoteLikeUnit | null {
   const canonicalUnits: NoteLikeUnit[] = ['midi', 'hz'];
   return canonicalUnits.find(canonicalUnit => {
@@ -38,6 +44,16 @@
   }));
 }
 
+export function mapFeaturesToRegions(featureData: FeatureList,
+                                     descriptor: OutputDescriptor): Region[] {
+  return featureData.map(feature => ({
+    time: toSeconds(feature.timestamp),
+    duration: toSeconds(feature.duration),
+    value: feature.featureValues.length > 0 ? feature.featureValues[0] : null,
+    label: feature.label
+  }));
+}
+
 export function frequencyToMidiNote(frequency: number,
                                     concertA: number = 440.0): number {
   return 69 + 12 * Math.log2(frequency / concertA);
@@ -69,8 +85,7 @@
 
 type CollectedShape = 'vector' | 'matrix' | 'tracks';
 
-// TODO regions
-type ShapeDeducedFromList = 'instants' | 'notes';
+type ShapeDeducedFromList = 'notes' | 'regions' | 'instants';
 export type HigherLevelFeatureShape = CollectedShape | ShapeDeducedFromList;
 
 export abstract class Grid {
@@ -85,6 +100,7 @@
   | Grid
   | TracksFeature
   | Note[]
+  | Region[]
   | Instant[];
 
 // These needn't be classes (could just be interfaces), just experimenting
@@ -98,11 +114,13 @@
 export class Matrix extends ShapedFeature<'matrix', Grid> {}
 export class Tracks extends ShapedFeature<'tracks', TracksFeature> {}
 export class Notes extends ShapedFeature<'notes', Note[]> {}
+export class Regions extends ShapedFeature<'regions', Region[]> {}
 export class Instants extends ShapedFeature<'instants', Instant[]> {}
 export type KnownShapedFeature = Vector
   | Matrix
   | Tracks
   | Notes
+  | Regions
   | Instants;
 
 function hasKnownShapeOtherThanList(shape: string): shape is CollectedShape {
@@ -113,6 +131,11 @@
   throw new Error('No shape could be deduced');
 }
 
+const rdfTypes = {
+  'http://purl.org/ontology/af/Note': 'notes',
+//  'http://purl.org/ontology/af/StructuralSegment': 'segments' // TODO segments
+};
+
 function deduceHigherLevelFeatureShape(response: SimpleResponse)
 : HigherLevelFeatureShape {
   const collection = response.features;
@@ -133,23 +156,36 @@
   const binCount = descriptor.configured.binCount;
   const isMarker = !hasDuration
     && binCount === 0
-    && featureData[0].featureValues == null;
+    && (featureData.length === 0 || featureData[0].featureValues == null);
+
+  if (isMarker) {
+    return 'instants';
+  }
+
+  if (descriptor.static) {
+    const typeURI = descriptor.static.typeURI;
+    if (typeof typeURI === 'string' && typeof rdfTypes[typeURI] === 'string') {
+      return rdfTypes[typeURI];
+    }
+  }
+
+  const isRegionLike = hasDuration &&
+    (featureData.length > 0 && featureData[0].timestamp != null);
+
   const hasUnit = descriptor.configured && descriptor.configured.unit;
 
   const isMaybeNote = hasUnit
     && getCanonicalNoteLikeUnit(descriptor.configured.unit)
     && [1, 2].find(nBins => nBins === binCount);
 
-  // TODO any need to be directly inspecting features?
-  const isRegionLike = hasDuration && featureData[0].timestamp != null;
+  if (isRegionLike) {
+    if (isMaybeNote) {
+      return 'notes';
+    } else {
+      return 'regions';
+    }
+  }
 
-  const isNote = isMaybeNote && isRegionLike;
-  if (isMarker) {
-    return 'instants';
-  }
-  if (isNote) {
-    return 'notes';
-  }
   throwShapeError();
 }
 
@@ -175,6 +211,14 @@
           response.outputDescriptor
         )
       };
+    case 'regions':
+      return {
+        shape: deducedShape,
+        collected: mapFeaturesToRegions(
+          response.features.collected as FeatureList,
+          response.outputDescriptor
+        )
+      };
     case 'instants':
       const featureData = response.features.collected as FeatureList;
       return {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/app/visualisations/regions/regions.component.ts	Mon Aug 14 17:43:09 2017 +0100
@@ -0,0 +1,70 @@
+/**
+ * Created by lucast on 31/05/2017.
+ */
+import {
+  InspectableVerticallyBoundedComponent,
+  PlayheadRenderer,
+  VerticallyLabelled,
+  VerticalScaleRenderer,
+  VerticalValueInspectorRenderer,
+  WavesComponent
+} from '../waves-base.component';
+import {
+  ChangeDetectionStrategy,
+  Component,
+  Input,
+} from '@angular/core';
+import {Region} from '../FeatureUtilities';
+import Waves from 'waves-ui-piper';
+
+@Component({
+  selector: 'ugly-regions',
+  templateUrl: '../waves-template.html',
+  styleUrls: ['../waves-template.css'],
+  changeDetection: ChangeDetectionStrategy.OnPush,
+  providers: [
+    { provide: VerticallyLabelled, useExisting: RegionsComponent },
+    { provide: VerticalScaleRenderer, useExisting: RegionsComponent },
+    {provide: VerticalValueInspectorRenderer, useExisting: RegionsComponent },
+    {provide: PlayheadRenderer, useExisting: RegionsComponent},
+    {provide: WavesComponent, useExisting: RegionsComponent}
+  ]
+})
+export class RegionsComponent extends InspectableVerticallyBoundedComponent<Region[]> {
+  private currentVerticalRange: [number, number];
+
+  get labels(): [number, number] {
+    return this.currentVerticalRange;
+  }
+
+  @Input() set regions(regions: Region[]) {
+    this.feature = regions;
+  }
+
+  protected get featureLayers(): Layer[] {
+    this.currentVerticalRange = findVerticalRange(this.feature);
+    return [
+      new Waves.helpers.RegionLayer(
+        this.feature,
+        {
+          height: this.height,
+          color: this.colour,
+          yDomain: this.currentVerticalRange
+        }
+      )
+    ];
+  }
+}
+
+// TODO there might be scope to create a generic utility function like this
+function findVerticalRange(regions: Region[]): [number, number] {
+  let [min, max] = regions.reduce((acc, region) => {
+    const [min, max] = acc;
+    return [Math.min (min, region.value), Math.max (max, region.value)];
+  }, [Infinity, -Infinity]);
+  if (min === Infinity) {
+    min = 0;
+    max = 1;
+  }
+  return [ min, max ];
+}