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@228
|
29 private featureService: 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@228
|
56 this.onProgressUpdated = this.featureService.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@227
|
106 id: `${++this.countingId}`,
|
dev@227
|
107 progress: 0
|
dev@203
|
108 });
|
dev@203
|
109
|
dev@228
|
110 this.featureService.extract(`${this.countingId}`, {
|
dev@47
|
111 audioData: [...Array(this.audioBuffer.numberOfChannels).keys()]
|
dev@47
|
112 .map(i => this.audioBuffer.getChannelData(i)),
|
dev@47
|
113 audioFormat: {
|
dev@47
|
114 sampleRate: this.audioBuffer.sampleRate,
|
dev@226
|
115 channelCount: this.audioBuffer.numberOfChannels,
|
dev@226
|
116 length: this.audioBuffer.length
|
dev@47
|
117 },
|
dev@47
|
118 key: outputInfo.extractorKey,
|
dev@47
|
119 outputId: outputInfo.outputId
|
dev@50
|
120 }).then(() => {
|
dev@49
|
121 this.canExtract = true;
|
dev@115
|
122 }).catch(err => {
|
dev@115
|
123 this.canExtract = true;
|
dev@226
|
124 this.analyses.shift();
|
dev@226
|
125 console.error(`Error whilst extracting: ${err}`);
|
dev@115
|
126 });
|
dev@47
|
127 }
|
dev@193
|
128
|
dev@193
|
129 ngOnDestroy(): void {
|
dev@193
|
130 this.onAudioDataSubscription.unsubscribe();
|
dev@226
|
131 this.onProgressUpdated.unsubscribe();
|
dev@193
|
132 }
|
angular-cli@0
|
133 }
|