-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathindex.js
executable file
·155 lines (151 loc) · 5.9 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
const fs = require('fs');
const path = require('path');
const puppeteer = require('puppeteer');
const clc = require('cli-color');
const defaultValue = require('./defaultValue');
const isPowerOfTwo = (value) => {
return ( value & ( value - 1 ) ) === 0 && value !== 0;
};
module.exports = (args) => {
return new Promise(async (resolve) => {
const CONSTANTS = {
'FILE_PATH': args.file,
'OUTPUT_PATH': args.output || defaultValue.OUTPUT_PATH,
'OUTPUT_TYPE': args.outputType || defaultValue.OUTPUT_TYPE,
'FFT_SIZE': args.fftsize < 32 ? 32 : args.fftsize || defaultValue.FFT_SIZE,
'INTERVAL_TIME': args.intervalTime || defaultValue.INTERVAL_TIME,
'DATA_TYPE': args.dataType || defaultValue.DATA_TYPE,
'DATA_LENGTH': args.dataLength || defaultValue.DATA_LENGTH,
};
if (!CONSTANTS.FILE_PATH) {
throw new Error('Please assign the path of audio file.');
}
if (CONSTANTS.FFT_SIZE < 32) {
console.warn(clc.red('Warning: The fftsize must be greater than or equal to 32, It has been set to 32.'));
}
if (!isPowerOfTwo(CONSTANTS.FFT_SIZE)) {
throw new Error('The fftsize is not power of two.');
}
const browser = await puppeteer.launch({
ignoreDefaultArgs: ['--mute-audio'],
});
const page = await browser.newPage();
page.on('console', msg => console.log(msg.text()));
await page.exposeFunction('getConstant', (name) => {
return new Promise((resolve) => {
resolve(CONSTANTS[name]);
});
});
await page.exposeFunction('loadFile', () => {
return new Promise((resolve) => {
fs.readFile(path.resolve(CONSTANTS.FILE_PATH), 'base64', (err, data) => {
if (err) throw err;
resolve(data);
});
});
});
await page.exposeFunction('writeFile', (data) => {
return new Promise((resolve) => {
const filePath = path.resolve(CONSTANTS.FILE_PATH);
const outputPath = path.resolve(CONSTANTS.OUTPUT_PATH);
const ext = CONSTANTS.OUTPUT_TYPE === 'json' ? '.json' : '.txt';
const outFileName = outputPath + '/' + path.basename(filePath, path.extname(filePath)) + ext;
const outputData = CONSTANTS.OUTPUT_TYPE === 'json' ? JSON.stringify(data) : data;
fs.writeFile(path.resolve(outFileName), outputData, 'utf8', (err) => {
if (err) throw err;
resolve();
});
});
});
await page.exposeFunction('close', () => {
resolve();
browser.close();
});
await page.evaluate(() => {
const base64ToBuffer = (base64) => {
const binaryString = atob(base64);
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
};
const decode = (audioFile) => {
return new Promise((resolve) => {
const audioContext = new AudioContext();
audioContext.decodeAudioData(base64ToBuffer(audioFile), (buffer) => {
resolve(buffer)
}, (err) => {
throw err;
});
});
};
const playAudio = async () => {
const audioFile = await loadFile();
const audioBuffer = await decode(audioFile);
const offlineContext = new OfflineAudioContext(audioBuffer.numberOfChannels, audioBuffer.length, audioBuffer.sampleRate);
const analyserNode = offlineContext.createAnalyser();
const scriptProcessorNode = offlineContext.createScriptProcessor();
const bufferSource = offlineContext.createBufferSource();
const outputType = await getConstant('OUTPUT_TYPE');
const fftSize = await getConstant('FFT_SIZE');
const intervalTime = await getConstant('INTERVAL_TIME');
const dataType = await getConstant('DATA_TYPE');
const dataLength = await getConstant('DATA_LENGTH');
let analyseData = outputType === 'json' ? {} : '';
const analyserNodeAction = {
'floatFrequency': () => {
const dataArray = new Float32Array(dataLength);
analyserNode.getFloatFrequencyData(dataArray);
return dataArray;
},
'byteFrequency': () => {
const dataArray = new Uint8Array(dataLength);
analyserNode.getByteFrequencyData(dataArray);
return dataArray;
},
'floatTimeDomain': () => {
const dataArray = new Float32Array(dataLength);
analyserNode.getFloatTimeDomainData(dataArray);
return dataArray;
},
'byteTimeDomain': () => {
const dataArray = new Uint8Array(dataLength);
analyserNode.getByteTimeDomainData(dataArray);
return dataArray;
},
};
analyserNode.fftSize = fftSize;
bufferSource.buffer = audioBuffer;
bufferSource.connect(analyserNode);
analyserNode.connect(scriptProcessorNode);
scriptProcessorNode.connect(offlineContext.destination);
scriptProcessorNode.onaudioprocess = () => {
const currentTime = Math.floor(offlineContext.currentTime * 100);
const analyse = () => {
const dataArray = analyserNodeAction[dataType]();
if (outputType === 'json') {
analyseData[(currentTime / 100).toFixed(2)] = dataArray.toString();
} else {
analyseData += dataArray.join(',') + '\n';
}
};
if (intervalTime === 0) {
analyse();
} else if(currentTime % (intervalTime * 100) === 0) {
analyse();
}
};
offlineContext.oncomplete = async () => {
await writeFile(analyseData);
console.log('\033[42;30m DONE \033[40;32m 👌 Complete the analysis!');
close();
};
bufferSource.start();
offlineContext.startRendering();
};
playAudio();
});
});
};