Mercurial > hg > ugly-duckling
changeset 350:524f5cd75737
Split AnalysisItem out into individual types for root audio items and features. This is messy as is, these need revising and should perhaps be actual concrete types with methods.
author | Lucas Thompson <dev@lucas.im> |
---|---|
date | Fri, 26 May 2017 12:59:41 +0100 |
parents | bf038a51f7e3 |
children | e108249fc2ff |
files | src/app/analysis-item/analysis-item.component.ts src/app/app.component.ts src/app/notebook-feed/notebook-feed.component.html src/app/notebook-feed/notebook-feed.component.ts src/app/services/feature-extraction/feature-extraction.service.ts |
diffstat | 5 files changed, 110 insertions(+), 47 deletions(-) [+] |
line wrap: on
line diff
--- a/src/app/analysis-item/analysis-item.component.ts Thu May 25 17:57:03 2017 +0100 +++ b/src/app/analysis-item/analysis-item.component.ts Fri May 26 12:59:41 2017 +0100 @@ -9,17 +9,61 @@ } from '@angular/core'; import {naivePagingMapper} from '../visualisations/WavesJunk'; import {OnSeekHandler, TimePixelMapper} from '../playhead/PlayHeadHelpers'; +import {HigherLevelFeatureShape} from '../visualisations/FeatureUtilities'; -export interface AnalysisItem { - rootAudioUri: string; +export interface Item { + id: string; hasSharedTimeline: boolean; - isRoot: boolean; - extractorKey: string; title?: string; description?: string; - id?: string; progress?: number; - audioData?: AudioBuffer; +} + +export interface PendingRootAudioItem extends Item { + uri: string; +} +export interface RootAudioItem extends PendingRootAudioItem{ + audioData: AudioBuffer; +} + +export interface PendingAnalysisItem extends Item { + parent: RootAudioItem; + extractorKey: string; +} + +export interface AnalysisItem extends PendingAnalysisItem { + kind: HigherLevelFeatureShape; +} + +export function isPendingRootAudioItem(item: Item): item is PendingRootAudioItem { + return typeof (item as RootAudioItem).uri === 'string'; +} + +export function isRootAudioItem(item: Item): item is RootAudioItem { + return isPendingRootAudioItem(item) && + typeof (item as RootAudioItem).uri === 'string'; +} + +export function isPendingAnalysisItem(item: Item): item is AnalysisItem { + const downcast = (item as AnalysisItem); + return isRootAudioItem(downcast.parent) + && typeof downcast.extractorKey === 'string'; +} + +export function isAnalysisItem(item: Item): item is AnalysisItem { + const downcast = (item as AnalysisItem); + return isPendingAnalysisItem(item) && downcast.kind != null; +} + +// these should probably be actual concrete types with their own getUri methods +export function getRootUri(item: Item): string { + if (isPendingRootAudioItem(item)) { + return item.uri; + } + if (isPendingAnalysisItem(item)) { + return item.parent.uri; + } + throw new Error('Invalid item: No URI property set.'); } @Component({ @@ -30,9 +74,9 @@ }) export class AnalysisItemComponent implements OnInit { - @Input() timeline: Timeline; + @Input() timeline: Timeline; // TODO should be TimelineTimeContext? @Input() isActive: boolean; - @Input() item: AnalysisItem; + @Input() item: Item; @Input() contentWidth: number; @Input() onSeek: OnSeekHandler; private hasProgressOnInit = false; @@ -51,8 +95,7 @@ } isAudioItem(): boolean { - return this.item && - this.item.isRoot && - this.item.audioData instanceof AudioBuffer; + console.warn('is root?', isRootAudioItem(this.item), this.item); + return isRootAudioItem(this.item); } }
--- a/src/app/app.component.ts Thu May 25 17:57:03 2017 +0100 +++ b/src/app/app.component.ts Fri May 26 12:59:41 2017 +0100 @@ -8,7 +8,10 @@ import {DomSanitizer} from '@angular/platform-browser'; import {MdIconRegistry} from '@angular/material'; import {Subscription} from 'rxjs/Subscription'; -import {AnalysisItem} from './analysis-item/analysis-item.component'; +import { + AnalysisItem, isRootAudioItem, + Item, PendingAnalysisItem, PendingRootAudioItem, RootAudioItem +} from './analysis-item/analysis-item.component'; import {OnSeekHandler} from './playhead/PlayHeadHelpers'; class PersistentStack<T> { @@ -71,10 +74,10 @@ canExtract: boolean; private onAudioDataSubscription: Subscription; private onProgressUpdated: Subscription; - private analyses: PersistentStack<AnalysisItem>; // TODO some immutable state container describing entire session + private analyses: PersistentStack<Item>; // TODO some immutable state container describing entire session private nRecordings: number; // TODO user control for naming a recording private countingId: number; // TODO improve uniquely identifying items - private rootAudioUri: string; + private rootAudioItem: RootAudioItem; private onSeek: OnSeekHandler; constructor(private audioService: AudioPlayerService, @@ -100,10 +103,11 @@ this.canExtract = false; } else { this.audioBuffer = (resource as AudioResource).samples; + this.rootAudioItem.audioData = this.audioBuffer; if (this.audioBuffer) { this.canExtract = true; const currentRootIndex = this.analyses.findIndex(val => { - return val.rootAudioUri === this.rootAudioUri && val.isRoot; + return isRootAudioItem(val) && val.uri === this.rootAudioItem.uri; }); if (currentRootIndex !== -1) { this.analyses.set( @@ -141,8 +145,6 @@ onFileOpened(file: File | Blob) { this.canExtract = false; const url = this.audioService.loadAudio(file); - this.rootAudioUri = url; // TODO this isn't going to work to id previously loaded files - // TODO is it safe to assume it is a recording? const title = (file instanceof File) ? (file as File).name : `Recording ${this.nRecordings++}`; @@ -155,17 +157,18 @@ return; } - // TODO re-ordering of items for display - // , one alternative is a Angular Pipe / Filter for use in the Template - this.analyses.unshift({ - rootAudioUri: url, + const pending = { + uri: url, hasSharedTimeline: true, - extractorKey: 'not:real', - isRoot: true, title: title, description: new Date().toLocaleString(), id: `${++this.countingId}` - }); + } as PendingRootAudioItem; + this.rootAudioItem = pending as RootAudioItem; // TODO this is silly + + // TODO re-ordering of items for display + // , one alternative is a Angular Pipe / Filter for use in the Template + this.analyses.unshift(pending); } extractFeatures(outputInfo: ExtractorOutputInfo): void { @@ -175,16 +178,16 @@ this.canExtract = false; - this.analyses.unshift({ - rootAudioUri: this.rootAudioUri, + const placeholderCard: PendingAnalysisItem = { + parent: this.rootAudioItem, hasSharedTimeline: true, extractorKey: outputInfo.combinedKey, - isRoot: false, title: outputInfo.name, description: outputInfo.outputId, id: `${++this.countingId}`, progress: 0 - }); + }; + this.analyses.unshift(placeholderCard); this.featureService.extract(`${this.countingId}`, { audioData: [...Array(this.audioBuffer.numberOfChannels).keys()]
--- a/src/app/notebook-feed/notebook-feed.component.html Thu May 25 17:57:03 2017 +0100 +++ b/src/app/notebook-feed/notebook-feed.component.html Fri May 26 12:59:41 2017 +0100 @@ -1,6 +1,6 @@ <div class="feed"> <ng-template ngFor let-item [ngForOf]="analyses"> - <div [class.break]="item.isRoot"> + <div [class.break]="isAudioItem(item)"> <ugly-analysis-item [timeline]="getOrCreateTimeline(item)" [isActive]="isActiveItem(item)"
--- a/src/app/notebook-feed/notebook-feed.component.ts Thu May 25 17:57:03 2017 +0100 +++ b/src/app/notebook-feed/notebook-feed.component.ts Fri May 26 12:59:41 2017 +0100 @@ -10,7 +10,11 @@ OnDestroy } from '@angular/core'; import Waves from 'waves-ui-piper'; -import {AnalysisItem} from '../analysis-item/analysis-item.component'; +import { + getRootUri, + isRootAudioItem, + Item +} from '../analysis-item/analysis-item.component'; import {Observable} from 'rxjs/Observable'; import {Dimension} from '../app.module'; import {Subscription} from 'rxjs/Subscription'; @@ -23,7 +27,7 @@ changeDetection: ChangeDetectionStrategy.OnPush }) export class NotebookFeedComponent implements OnDestroy { - @Input() analyses: AnalysisItem[]; + @Input() analyses: Item[]; @Input() set rootAudioUri(uri: string) { this._rootAudioUri = uri; } @@ -61,25 +65,29 @@ requestAnimationFrame(triggerChangeDetectionOnResize); } - getOrCreateTimeline(item: AnalysisItem): Timeline | void { + getOrCreateTimeline(item: Item): Timeline | void { if (!item.hasSharedTimeline) { return; } - - if (this.timelines.has(item.rootAudioUri)) { - return this.timelines.get(item.rootAudioUri); + const uri = getRootUri(item); + if (this.timelines.has(uri)) { + return this.timelines.get(uri); } else { const timeline = new Waves.core.Timeline(); - this.timelines.set(item.rootAudioUri, timeline); + this.timelines.set(uri, timeline); return timeline; } } - isActiveItem(item: AnalysisItem): boolean { - return this.rootAudioUri === item.rootAudioUri; + isAudioItem(item: Item): boolean { + return isRootAudioItem(item); } - getOnSeekForItem(item: AnalysisItem): (timeSecounds: number) => any { + isActiveItem(item: Item): boolean { + return this.rootAudioUri === getRootUri(item); + } + + getOnSeekForItem(item: Item): (timeSeconds: number) => any { return this.isActiveItem(item) ? this.onSeek : () => {}; }
--- a/src/app/services/feature-extraction/feature-extraction.service.ts Thu May 25 17:57:03 2017 +0100 +++ b/src/app/services/feature-extraction/feature-extraction.service.ts Fri May 26 12:59:41 2017 +0100 @@ -3,8 +3,7 @@ ListResponse } from 'piper'; import { - SimpleRequest, - SimpleResponse + SimpleRequest } from 'piper/HigherLevelUtilities'; import {Subject} from 'rxjs/Subject'; import {Observable} from 'rxjs/Observable'; @@ -30,12 +29,17 @@ value: number; // between 0 and 100, for material-ui } +export interface ExtractionResult { + id: RequestId; + result: KnownShapedFeature; +} + @Injectable() export class FeatureExtractionService { private worker: Worker; - private featuresExtracted: Subject<KnownShapedFeature>; - featuresExtracted$: Observable<KnownShapedFeature>; + private featuresExtracted: Subject<ExtractionResult>; + featuresExtracted$: Observable<ExtractionResult>; private librariesUpdated: Subject<ListResponse>; librariesUpdated$: Observable<ListResponse>; private progressUpdated: Subject<Progress>; @@ -45,7 +49,7 @@ constructor(private http: Http, @Inject('PiperRepoUri') private repositoryUri: RepoUri) { this.worker = new Worker('bootstrap-feature-extraction-worker.js'); - this.featuresExtracted = new Subject<KnownShapedFeature>(); + this.featuresExtracted = new Subject<ExtractionResult>(); this.featuresExtracted$ = this.featuresExtracted.asObservable(); this.librariesUpdated = new Subject<ListResponse>(); this.librariesUpdated$ = this.librariesUpdated.asObservable(); @@ -70,7 +74,8 @@ return this.client.list({}); } - extract(analysisItemId: string, request: SimpleRequest): Promise<void> { + extract(analysisItemId: string, + request: SimpleRequest): Promise<ExtractionResult> { let config: StreamingConfiguration; return collect(this.client.process(request), val => { if (val.configuration) { @@ -88,8 +93,12 @@ features: features, outputDescriptor: config.outputDescriptor }); - console.warn(shaped.shape); - this.featuresExtracted.next(shaped); + const result = { + id: analysisItemId, + result: shaped + }; + this.featuresExtracted.next(result); + return result; }); }