annotate src/app/app.component.ts @ 226:4865567d9e43

Refactor feature extraction service to use piper streaming client/server. Change FeatureExtractionWorker accordingly and calling code.
author Lucas Thompson <dev@lucas.im>
date Fri, 21 Apr 2017 12:59:41 +0100
parents 1db0bb28688b
children ac513d535be9
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
angular-cli@0 13 @Component({
angular-cli@0 14 selector: 'app-root',
angular-cli@0 15 templateUrl: './app.component.html',
angular-cli@0 16 styleUrls: ['./app.component.css']
angular-cli@0 17 })
dev@193 18 export class AppComponent implements OnDestroy {
dev@31 19 audioBuffer: AudioBuffer; // TODO consider revising
dev@49 20 canExtract: boolean;
dev@193 21 private onAudioDataSubscription: Subscription;
dev@226 22 private onProgressUpdated: Subscription;
dev@203 23 private analyses: AnalysisItem[]; // TODO some immutable state container describing entire session
dev@203 24 private nRecordings: number; // TODO user control for naming a recording
dev@206 25 private countingId: number; // TODO improve uniquely identifying items
dev@203 26 private rootAudioUri: string;
dev@1 27
dev@47 28 constructor(private audioService: AudioPlayerService,
dev@89 29 private piperService: FeatureExtractionService,
dev@89 30 private iconRegistry: MdIconRegistry,
dev@89 31 private sanitizer: DomSanitizer) {
dev@203 32 this.analyses = [];
dev@49 33 this.canExtract = false;
dev@203 34 this.nRecordings = 0;
dev@226 35 this.countingId = 0;
dev@206 36
dev@89 37 iconRegistry.addSvgIcon(
dev@89 38 'duck',
dev@89 39 sanitizer.bypassSecurityTrustResourceUrl('assets/duck.svg')
dev@89 40 );
dev@193 41
dev@193 42 this.onAudioDataSubscription = this.audioService.audioLoaded$.subscribe(
dev@193 43 resource => {
dev@193 44 const wasError = (resource as AudioResourceError).message != null;
dev@193 45 if (wasError) {
dev@203 46 this.analyses.shift();
dev@193 47 this.canExtract = false;
dev@193 48 } else {
dev@193 49 this.audioBuffer = (resource as AudioResource).samples;
dev@193 50 if (this.audioBuffer) {
dev@193 51 this.canExtract = true;
dev@193 52 }
dev@193 53 }
dev@193 54 }
dev@193 55 );
dev@226 56 this.onProgressUpdated = this.piperService.progressUpdated$.subscribe(
dev@226 57 progress => {
dev@226 58 const index = this.analyses.findIndex(val => val.id === progress.id);
dev@226 59 if (index === -1) return;
dev@226 60 this.analyses[index].progress = progress.value;
dev@226 61 }
dev@226 62 );
dev@48 63 }
dev@16 64
dev@134 65 onFileOpened(file: File | Blob) {
dev@49 66 this.canExtract = false;
dev@203 67 const url = this.audioService.loadAudio(file);
dev@203 68 this.rootAudioUri = url; // TODO this isn't going to work to id previously loaded files
dev@203 69
dev@203 70 // TODO is it safe to assume it is a recording?
dev@203 71 const title = (file instanceof File) ?
dev@203 72 (file as File).name : `Recording ${this.nRecordings++}`;
dev@203 73
dev@203 74 if (this.analyses.filter(item => item.title === title).length > 0) {
dev@203 75 // TODO this reveals how brittle the current name / uri based id is
dev@203 76 // need something more robust, and also need to notify the user
dev@203 77 // in a suitable way in the actual event of a duplicate file
dev@203 78 console.warn('There is already a notebook based on this audio file.');
dev@203 79 return;
dev@203 80 }
dev@203 81
dev@203 82 // TODO re-ordering of items for display
dev@203 83 // , one alternative is a Angular Pipe / Filter for use in the Template
dev@203 84 this.analyses.unshift({
dev@203 85 rootAudioUri: url,
dev@203 86 hasSharedTimeline: true,
dev@203 87 extractorKey: 'not:real',
dev@203 88 isRoot: true,
dev@203 89 title: title,
dev@206 90 description: new Date().toLocaleString(),
dev@226 91 id: `${++this.countingId}`
dev@203 92 });
dev@16 93 }
dev@47 94
dev@48 95 extractFeatures(outputInfo: ExtractorOutputInfo): void {
dev@50 96 if (!this.canExtract || !outputInfo) return;
dev@49 97 this.canExtract = false;
dev@203 98
dev@203 99 this.analyses.unshift({
dev@203 100 rootAudioUri: this.rootAudioUri,
dev@203 101 hasSharedTimeline: true,
dev@203 102 extractorKey: outputInfo.combinedKey,
dev@203 103 isRoot: false,
dev@203 104 title: outputInfo.name,
dev@206 105 description: outputInfo.outputId,
dev@226 106 id: `${++this.countingId}`
dev@203 107 });
dev@203 108
dev@226 109 this.piperService.extract(`${this.countingId}`, {
dev@47 110 audioData: [...Array(this.audioBuffer.numberOfChannels).keys()]
dev@47 111 .map(i => this.audioBuffer.getChannelData(i)),
dev@47 112 audioFormat: {
dev@47 113 sampleRate: this.audioBuffer.sampleRate,
dev@226 114 channelCount: this.audioBuffer.numberOfChannels,
dev@226 115 length: this.audioBuffer.length
dev@47 116 },
dev@47 117 key: outputInfo.extractorKey,
dev@47 118 outputId: outputInfo.outputId
dev@50 119 }).then(() => {
dev@49 120 this.canExtract = true;
dev@115 121 }).catch(err => {
dev@115 122 this.canExtract = true;
dev@226 123 this.analyses.shift();
dev@226 124 console.error(`Error whilst extracting: ${err}`);
dev@115 125 });
dev@47 126 }
dev@193 127
dev@193 128 ngOnDestroy(): void {
dev@193 129 this.onAudioDataSubscription.unsubscribe();
dev@226 130 this.onProgressUpdated.unsubscribe();
dev@193 131 }
angular-cli@0 132 }