annotate src/app/app.component.ts @ 236:53ea6406d601

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