Mercurial > hg > ugly-duckling
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 ]; +}