annotate src/app/app.component.ts @ 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 82d476b976e0
children 02e7be2daf31
rev   line source
dev@193 1 import {Component, OnDestroy} from '@angular/core';
dev@193 2 import {
dev@193 3 AudioPlayerService,
dev@193 4 AudioResourceError, AudioResource
dev@236 5 } from './services/audio-player/audio-player.service';
dev@236 6 import {FeatureExtractionService} from './services/feature-extraction/feature-extraction.service';
dev@236 7 import {ExtractorOutputInfo} from './feature-extraction-menu/feature-extraction-menu.component';
dev@89 8 import {DomSanitizer} from '@angular/platform-browser';
dev@89 9 import {MdIconRegistry} from '@angular/material';
dev@236 10 import {Subscription} from 'rxjs/Subscription';
dev@350 11 import {
dev@350 12 AnalysisItem, isRootAudioItem,
dev@350 13 Item, PendingAnalysisItem, PendingRootAudioItem, RootAudioItem
dev@350 14 } from './analysis-item/analysis-item.component';
dev@347 15 import {OnSeekHandler} from './playhead/PlayHeadHelpers';
angular-cli@0 16
dev@235 17 class PersistentStack<T> {
dev@235 18 private stack: T[];
dev@235 19 private history: T[][];
dev@235 20
dev@235 21 constructor() {
dev@235 22 this.stack = [];
dev@235 23 this.history = [];
dev@235 24 }
dev@235 25
dev@235 26 shift(): T {
dev@235 27 this.history.push([...this.stack]);
dev@235 28 const item = this.stack[0];
dev@235 29 this.stack = this.stack.slice(1);
dev@235 30 return item;
dev@235 31 }
dev@235 32
dev@236 33 unshift(item: T): number {
dev@235 34 this.history.push([...this.stack]);
dev@235 35 this.stack = [item, ...this.stack];
dev@235 36 return this.stack.length;
dev@235 37 }
dev@235 38
dev@235 39 findIndex(predicate: (value: T,
dev@235 40 index: number,
dev@235 41 array: T[]) => boolean): number {
dev@235 42 return this.stack.findIndex(predicate);
dev@235 43 }
dev@235 44
dev@235 45 filter(predicate: (value: T, index: number, array: T[]) => boolean): T[] {
dev@235 46 return this.stack.filter(predicate);
dev@235 47 }
dev@235 48
dev@235 49 get(index: number): T {
dev@235 50 return this.stack[index];
dev@235 51 }
dev@235 52
dev@235 53 set(index: number, value: T) {
dev@235 54 this.history.push([...this.stack]);
dev@235 55 this.stack = [
dev@235 56 ...this.stack.slice(0, index),
dev@235 57 value,
dev@235 58 ...this.stack.slice(index + 1)
dev@235 59 ];
dev@235 60 }
dev@235 61
dev@235 62 toIterable(): Iterable<T> {
dev@235 63 return this.stack;
dev@235 64 }
dev@235 65 }
dev@235 66
angular-cli@0 67 @Component({
dev@236 68 selector: 'ugly-root',
angular-cli@0 69 templateUrl: './app.component.html',
angular-cli@0 70 styleUrls: ['./app.component.css']
angular-cli@0 71 })
dev@193 72 export class AppComponent implements OnDestroy {
dev@31 73 audioBuffer: AudioBuffer; // TODO consider revising
dev@49 74 canExtract: boolean;
dev@193 75 private onAudioDataSubscription: Subscription;
dev@226 76 private onProgressUpdated: Subscription;
dev@350 77 private analyses: PersistentStack<Item>; // TODO some immutable state container describing entire session
dev@203 78 private nRecordings: number; // TODO user control for naming a recording
dev@206 79 private countingId: number; // TODO improve uniquely identifying items
dev@350 80 private rootAudioItem: RootAudioItem;
dev@347 81 private onSeek: OnSeekHandler;
dev@1 82
dev@47 83 constructor(private audioService: AudioPlayerService,
dev@228 84 private featureService: FeatureExtractionService,
dev@89 85 private iconRegistry: MdIconRegistry,
dev@89 86 private sanitizer: DomSanitizer) {
dev@235 87 this.analyses = new PersistentStack<AnalysisItem>();
dev@49 88 this.canExtract = false;
dev@203 89 this.nRecordings = 0;
dev@226 90 this.countingId = 0;
dev@347 91 this.onSeek = (time) => this.audioService.seekTo(time);
dev@206 92
dev@89 93 iconRegistry.addSvgIcon(
dev@89 94 'duck',
dev@89 95 sanitizer.bypassSecurityTrustResourceUrl('assets/duck.svg')
dev@89 96 );
dev@193 97
dev@193 98 this.onAudioDataSubscription = this.audioService.audioLoaded$.subscribe(
dev@193 99 resource => {
dev@193 100 const wasError = (resource as AudioResourceError).message != null;
dev@193 101 if (wasError) {
dev@203 102 this.analyses.shift();
dev@193 103 this.canExtract = false;
dev@193 104 } else {
dev@193 105 this.audioBuffer = (resource as AudioResource).samples;
dev@350 106 this.rootAudioItem.audioData = this.audioBuffer;
dev@193 107 if (this.audioBuffer) {
dev@193 108 this.canExtract = true;
dev@347 109 const currentRootIndex = this.analyses.findIndex(val => {
dev@350 110 return isRootAudioItem(val) && val.uri === this.rootAudioItem.uri;
dev@347 111 });
dev@347 112 if (currentRootIndex !== -1) {
dev@347 113 this.analyses.set(
dev@347 114 currentRootIndex,
dev@347 115 Object.assign(
dev@347 116 {},
dev@347 117 this.analyses.get(currentRootIndex),
dev@347 118 {audioData: this.audioBuffer}
dev@347 119 )
dev@347 120 );
dev@347 121 }
dev@193 122 }
dev@193 123 }
dev@193 124 }
dev@193 125 );
dev@228 126 this.onProgressUpdated = this.featureService.progressUpdated$.subscribe(
dev@226 127 progress => {
dev@226 128 const index = this.analyses.findIndex(val => val.id === progress.id);
dev@236 129 if (index === -1) {
dev@236 130 return;
dev@236 131 }
dev@235 132
dev@235 133 this.analyses.set(
dev@235 134 index,
dev@235 135 Object.assign(
dev@235 136 {},
dev@235 137 this.analyses.get(index),
dev@235 138 {progress: progress.value}
dev@235 139 )
dev@235 140 );
dev@226 141 }
dev@226 142 );
dev@48 143 }
dev@16 144
dev@134 145 onFileOpened(file: File | Blob) {
dev@49 146 this.canExtract = false;
dev@203 147 const url = this.audioService.loadAudio(file);
dev@203 148 // TODO is it safe to assume it is a recording?
dev@203 149 const title = (file instanceof File) ?
dev@203 150 (file as File).name : `Recording ${this.nRecordings++}`;
dev@203 151
dev@203 152 if (this.analyses.filter(item => item.title === title).length > 0) {
dev@203 153 // TODO this reveals how brittle the current name / uri based id is
dev@203 154 // need something more robust, and also need to notify the user
dev@203 155 // in a suitable way in the actual event of a duplicate file
dev@203 156 console.warn('There is already a notebook based on this audio file.');
dev@203 157 return;
dev@203 158 }
dev@203 159
dev@350 160 const pending = {
dev@350 161 uri: url,
dev@203 162 hasSharedTimeline: true,
dev@203 163 title: title,
dev@206 164 description: new Date().toLocaleString(),
dev@226 165 id: `${++this.countingId}`
dev@350 166 } as PendingRootAudioItem;
dev@350 167 this.rootAudioItem = pending as RootAudioItem; // TODO this is silly
dev@350 168
dev@350 169 // TODO re-ordering of items for display
dev@350 170 // , one alternative is a Angular Pipe / Filter for use in the Template
dev@350 171 this.analyses.unshift(pending);
dev@16 172 }
dev@47 173
dev@48 174 extractFeatures(outputInfo: ExtractorOutputInfo): void {
dev@236 175 if (!this.canExtract || !outputInfo) {
dev@236 176 return;
dev@236 177 }
dev@236 178
dev@49 179 this.canExtract = false;
dev@203 180
dev@350 181 const placeholderCard: PendingAnalysisItem = {
dev@350 182 parent: this.rootAudioItem,
dev@203 183 hasSharedTimeline: true,
dev@203 184 extractorKey: outputInfo.combinedKey,
dev@203 185 title: outputInfo.name,
dev@206 186 description: outputInfo.outputId,
dev@227 187 id: `${++this.countingId}`,
dev@227 188 progress: 0
dev@350 189 };
dev@350 190 this.analyses.unshift(placeholderCard);
dev@203 191
dev@228 192 this.featureService.extract(`${this.countingId}`, {
dev@47 193 audioData: [...Array(this.audioBuffer.numberOfChannels).keys()]
dev@47 194 .map(i => this.audioBuffer.getChannelData(i)),
dev@47 195 audioFormat: {
dev@47 196 sampleRate: this.audioBuffer.sampleRate,
dev@226 197 channelCount: this.audioBuffer.numberOfChannels,
dev@226 198 length: this.audioBuffer.length
dev@47 199 },
dev@47 200 key: outputInfo.extractorKey,
dev@47 201 outputId: outputInfo.outputId
dev@50 202 }).then(() => {
dev@49 203 this.canExtract = true;
dev@115 204 }).catch(err => {
dev@115 205 this.canExtract = true;
dev@226 206 this.analyses.shift();
dev@226 207 console.error(`Error whilst extracting: ${err}`);
dev@115 208 });
dev@47 209 }
dev@193 210
dev@193 211 ngOnDestroy(): void {
dev@193 212 this.onAudioDataSubscription.unsubscribe();
dev@226 213 this.onProgressUpdated.unsubscribe();
dev@193 214 }
angular-cli@0 215 }