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@203
|
22 private analyses: AnalysisItem[]; // TODO some immutable state container describing entire session
|
dev@203
|
23 private nRecordings: number; // TODO user control for naming a recording
|
dev@206
|
24 private countingId: number; // TODO improve uniquely identifying items
|
dev@203
|
25 private rootAudioUri: string;
|
dev@1
|
26
|
dev@47
|
27 constructor(private audioService: AudioPlayerService,
|
dev@89
|
28 private piperService: FeatureExtractionService,
|
dev@89
|
29 private iconRegistry: MdIconRegistry,
|
dev@89
|
30 private sanitizer: DomSanitizer) {
|
dev@203
|
31 this.analyses = [];
|
dev@49
|
32 this.canExtract = false;
|
dev@203
|
33 this.nRecordings = 0;
|
dev@206
|
34 this.countingId = 1;
|
dev@206
|
35
|
dev@89
|
36 iconRegistry.addSvgIcon(
|
dev@89
|
37 'duck',
|
dev@89
|
38 sanitizer.bypassSecurityTrustResourceUrl('assets/duck.svg')
|
dev@89
|
39 );
|
dev@193
|
40
|
dev@193
|
41 this.onAudioDataSubscription = this.audioService.audioLoaded$.subscribe(
|
dev@193
|
42 resource => {
|
dev@193
|
43 const wasError = (resource as AudioResourceError).message != null;
|
dev@193
|
44 if (wasError) {
|
dev@203
|
45 this.analyses.shift();
|
dev@193
|
46 this.canExtract = false;
|
dev@193
|
47 } else {
|
dev@193
|
48 this.audioBuffer = (resource as AudioResource).samples;
|
dev@193
|
49 if (this.audioBuffer) {
|
dev@193
|
50 this.canExtract = true;
|
dev@193
|
51 }
|
dev@193
|
52 }
|
dev@193
|
53 }
|
dev@193
|
54 );
|
dev@48
|
55 }
|
dev@16
|
56
|
dev@134
|
57 onFileOpened(file: File | Blob) {
|
dev@49
|
58 this.canExtract = false;
|
dev@203
|
59 const url = this.audioService.loadAudio(file);
|
dev@203
|
60 this.rootAudioUri = url; // TODO this isn't going to work to id previously loaded files
|
dev@203
|
61
|
dev@203
|
62 // TODO is it safe to assume it is a recording?
|
dev@203
|
63 const title = (file instanceof File) ?
|
dev@203
|
64 (file as File).name : `Recording ${this.nRecordings++}`;
|
dev@203
|
65
|
dev@203
|
66 if (this.analyses.filter(item => item.title === title).length > 0) {
|
dev@203
|
67 // TODO this reveals how brittle the current name / uri based id is
|
dev@203
|
68 // need something more robust, and also need to notify the user
|
dev@203
|
69 // in a suitable way in the actual event of a duplicate file
|
dev@203
|
70 console.warn('There is already a notebook based on this audio file.');
|
dev@203
|
71 return;
|
dev@203
|
72 }
|
dev@203
|
73
|
dev@203
|
74 // TODO re-ordering of items for display
|
dev@203
|
75 // , one alternative is a Angular Pipe / Filter for use in the Template
|
dev@203
|
76 this.analyses.unshift({
|
dev@203
|
77 rootAudioUri: url,
|
dev@203
|
78 hasSharedTimeline: true,
|
dev@203
|
79 extractorKey: 'not:real',
|
dev@203
|
80 isRoot: true,
|
dev@203
|
81 title: title,
|
dev@206
|
82 description: new Date().toLocaleString(),
|
dev@206
|
83 id: `${this.countingId++}`
|
dev@203
|
84 });
|
dev@16
|
85 }
|
dev@47
|
86
|
dev@48
|
87 extractFeatures(outputInfo: ExtractorOutputInfo): void {
|
dev@50
|
88 if (!this.canExtract || !outputInfo) return;
|
dev@49
|
89 this.canExtract = false;
|
dev@203
|
90
|
dev@203
|
91 this.analyses.unshift({
|
dev@203
|
92 rootAudioUri: this.rootAudioUri,
|
dev@203
|
93 hasSharedTimeline: true,
|
dev@203
|
94 extractorKey: outputInfo.combinedKey,
|
dev@203
|
95 isRoot: false,
|
dev@203
|
96 title: outputInfo.name,
|
dev@206
|
97 description: outputInfo.outputId,
|
dev@206
|
98 id: `${this.countingId++}`
|
dev@203
|
99 });
|
dev@203
|
100
|
dev@63
|
101 this.piperService.collect({
|
dev@47
|
102 audioData: [...Array(this.audioBuffer.numberOfChannels).keys()]
|
dev@47
|
103 .map(i => this.audioBuffer.getChannelData(i)),
|
dev@47
|
104 audioFormat: {
|
dev@47
|
105 sampleRate: this.audioBuffer.sampleRate,
|
dev@47
|
106 channelCount: this.audioBuffer.numberOfChannels
|
dev@47
|
107 },
|
dev@47
|
108 key: outputInfo.extractorKey,
|
dev@47
|
109 outputId: outputInfo.outputId
|
dev@50
|
110 }).then(() => {
|
dev@49
|
111 this.canExtract = true;
|
dev@115
|
112 }).catch(err => {
|
dev@115
|
113 this.canExtract = true;
|
dev@115
|
114 console.error(err)
|
dev@115
|
115 });
|
dev@47
|
116 }
|
dev@193
|
117
|
dev@193
|
118 ngOnDestroy(): void {
|
dev@193
|
119 this.onAudioDataSubscription.unsubscribe();
|
dev@193
|
120 }
|
angular-cli@0
|
121 }
|