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; }); }
