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