annotate src/app/app.component.ts @ 235:76f2cd2c5a68

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