audio-decode 2.2.2 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/test.js.yml +11 -20
- package/.work/todo.md +9 -0
- package/audio-decode.d.ts +29 -11
- package/audio-decode.js +136 -69
- package/fixtures/qoa-sample.qoa +0 -0
- package/package.json +20 -17
- package/readme.md +57 -26
- package/test.js +287 -101
- package/.eslintrc.json +0 -43
- package/.travis.yml +0 -13
|
@@ -1,31 +1,22 @@
|
|
|
1
|
-
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
|
2
|
-
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
|
|
3
|
-
|
|
4
1
|
name: test
|
|
5
2
|
|
|
6
3
|
on:
|
|
7
4
|
push:
|
|
8
|
-
branches: [
|
|
5
|
+
branches: [master]
|
|
9
6
|
pull_request:
|
|
10
|
-
branches: [
|
|
7
|
+
branches: [master]
|
|
11
8
|
|
|
12
9
|
jobs:
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
test:
|
|
15
11
|
runs-on: ubuntu-latest
|
|
16
|
-
|
|
17
12
|
strategy:
|
|
18
13
|
matrix:
|
|
19
|
-
node-version: [
|
|
20
|
-
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
|
21
|
-
|
|
14
|
+
node-version: [20.x, 22.x]
|
|
22
15
|
steps:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
- run: npm run build --if-present
|
|
31
|
-
- run: npm test
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: actions/setup-node@v4
|
|
18
|
+
with:
|
|
19
|
+
node-version: ${{ matrix.node-version }}
|
|
20
|
+
cache: npm
|
|
21
|
+
- run: npm ci
|
|
22
|
+
- run: npm test
|
package/.work/todo.md
ADDED
package/audio-decode.d.ts
CHANGED
|
@@ -1,14 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
export interface AudioData {
|
|
2
|
+
channelData: Float32Array[];
|
|
3
|
+
sampleRate: number;
|
|
4
|
+
}
|
|
4
5
|
|
|
5
|
-
export interface
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
export interface StreamDecoder {
|
|
7
|
+
/** Feed a chunk of encoded audio data. */
|
|
8
|
+
decode(chunk: Uint8Array): Promise<AudioData>;
|
|
9
|
+
/** Flush remaining buffered data and free resources. */
|
|
10
|
+
decode(): Promise<AudioData>;
|
|
11
|
+
/** Free resources without flushing. */
|
|
12
|
+
free(): void;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
/** Whole-file decode: auto-detects format. */
|
|
16
|
+
export default function decode(buf: ArrayBuffer | Uint8Array): Promise<AudioData>;
|
|
17
|
+
|
|
18
|
+
/** Decode a ReadableStream of audio chunks. */
|
|
19
|
+
export function decodeStream(
|
|
20
|
+
stream: ReadableStream<Uint8Array> | AsyncIterable<Uint8Array>,
|
|
21
|
+
format: 'mp3' | 'flac' | 'opus' | 'oga' | 'm4a' | 'wav' | 'qoa'
|
|
22
|
+
): AsyncGenerator<AudioData>;
|
|
23
|
+
|
|
24
|
+
export declare const decoders: {
|
|
25
|
+
mp3(): Promise<StreamDecoder>;
|
|
26
|
+
flac(): Promise<StreamDecoder>;
|
|
27
|
+
opus(): Promise<StreamDecoder>;
|
|
28
|
+
oga(): Promise<StreamDecoder>;
|
|
29
|
+
m4a(): Promise<StreamDecoder>;
|
|
30
|
+
wav(): Promise<StreamDecoder>;
|
|
31
|
+
qoa(): Promise<StreamDecoder>;
|
|
32
|
+
};
|
package/audio-decode.js
CHANGED
|
@@ -1,92 +1,159 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* @module
|
|
2
|
+
* Audio decoder: whole-file and streaming
|
|
3
|
+
* @module audio-decode
|
|
4
|
+
*
|
|
5
|
+
* let { channelData, sampleRate } = await decode(mp3buf)
|
|
6
|
+
*
|
|
7
|
+
* let dec = await decoders.mp3()
|
|
8
|
+
* let { channelData, sampleRate } = await dec.decode(chunk)
|
|
9
|
+
* await dec.decode() // flush + free
|
|
4
10
|
*/
|
|
5
11
|
|
|
6
12
|
import getType from 'audio-type';
|
|
7
|
-
import AudioBufferShim from 'audio-buffer';
|
|
8
13
|
|
|
9
|
-
const
|
|
14
|
+
const EMPTY = Object.freeze({ channelData: Object.freeze([]), sampleRate: 0 })
|
|
10
15
|
|
|
11
16
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* @
|
|
15
|
-
* @returns {Promise<AudioBuffer>} A promise that resolves to the decoded audio buffer.
|
|
16
|
-
* @throws {Error} Throws an error if the decode target is invalid or if the audio format is not supported.
|
|
17
|
+
* Whole-file decode: auto-detects format
|
|
18
|
+
* @param {ArrayBuffer|Uint8Array} src - encoded audio data
|
|
19
|
+
* @returns {Promise<{channelData: Float32Array[], sampleRate: number}>}
|
|
17
20
|
*/
|
|
18
|
-
export default async function
|
|
19
|
-
if (!
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
let type = getType(buf);
|
|
21
|
+
export default async function decode(src) {
|
|
22
|
+
if (!src || typeof src === 'string' || !(src.buffer || src.byteLength || src.length))
|
|
23
|
+
throw TypeError('Expected ArrayBuffer or Uint8Array')
|
|
24
|
+
let buf = new Uint8Array(src.buffer || src)
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
let type = getType(buf)
|
|
27
|
+
if (!type) throw Error('Unknown audio format')
|
|
28
|
+
if (!decoders[type]) throw Error('No decoder for ' + type)
|
|
25
29
|
|
|
26
|
-
|
|
30
|
+
let dec = await decoders[type]()
|
|
31
|
+
try {
|
|
32
|
+
let result = await dec.decode(buf)
|
|
33
|
+
let flushed = await dec.decode()
|
|
34
|
+
return merge(result, flushed)
|
|
35
|
+
} catch (e) {
|
|
36
|
+
dec.free()
|
|
37
|
+
throw e
|
|
38
|
+
}
|
|
39
|
+
}
|
|
27
40
|
|
|
28
|
-
|
|
29
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Decode a ReadableStream of audio chunks
|
|
43
|
+
* @param {ReadableStream} stream - stream of Uint8Array chunks
|
|
44
|
+
* @param {string} format - codec name (mp3, flac, opus, oga, m4a, wav, qoa)
|
|
45
|
+
* @returns {AsyncGenerator<{channelData: Float32Array[], sampleRate: number}>}
|
|
46
|
+
*/
|
|
47
|
+
export async function* decodeStream(stream, format) {
|
|
48
|
+
if (!decoders[format]) throw Error('No decoder for ' + format)
|
|
49
|
+
let dec = await decoders[format]()
|
|
50
|
+
try {
|
|
51
|
+
for await (let chunk of stream) {
|
|
52
|
+
let result = await dec.decode(chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk))
|
|
53
|
+
if (result.channelData.length) yield result
|
|
54
|
+
}
|
|
55
|
+
let flushed = await dec.decode()
|
|
56
|
+
if (flushed.channelData.length) yield flushed
|
|
57
|
+
} finally {
|
|
58
|
+
dec.free()
|
|
59
|
+
}
|
|
60
|
+
}
|
|
30
61
|
|
|
62
|
+
// codec registry: each returns an initialized StreamDecoder
|
|
31
63
|
export const decoders = {
|
|
32
|
-
async
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
} else await decoder.reset()
|
|
38
|
-
return buf && createBuffer(await decoder.decodeFile(buf))
|
|
64
|
+
async mp3() {
|
|
65
|
+
const { MPEGDecoder } = await import('mpg123-decoder')
|
|
66
|
+
let dec = new MPEGDecoder()
|
|
67
|
+
await dec.ready
|
|
68
|
+
return streamDecoder(chunk => dec.decode(chunk), null, () => dec.free())
|
|
39
69
|
},
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
else await decoder.reset()
|
|
47
|
-
return buf && createBuffer(await decoder.decode(buf))
|
|
70
|
+
|
|
71
|
+
async flac() {
|
|
72
|
+
const { FLACDecoder } = await import('@wasm-audio-decoders/flac')
|
|
73
|
+
let dec = new FLACDecoder()
|
|
74
|
+
await dec.ready
|
|
75
|
+
return streamDecoder(chunk => dec.decode(chunk), () => dec.flush(), () => dec.free())
|
|
48
76
|
},
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
else await decoder.reset()
|
|
56
|
-
return buf && createBuffer(await decoder.decode(buf))
|
|
77
|
+
|
|
78
|
+
async opus() {
|
|
79
|
+
const { OggOpusDecoder } = await import('ogg-opus-decoder')
|
|
80
|
+
let dec = new OggOpusDecoder()
|
|
81
|
+
await dec.ready
|
|
82
|
+
return streamDecoder(chunk => dec.decode(chunk), () => dec.flush(), () => dec.free())
|
|
57
83
|
},
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
else await decoder.reset()
|
|
65
|
-
return buf && createBuffer(await decoder.decodeFile(buf))
|
|
84
|
+
|
|
85
|
+
async oga() {
|
|
86
|
+
const { OggVorbisDecoder } = await import('@wasm-audio-decoders/ogg-vorbis')
|
|
87
|
+
let dec = new OggVorbisDecoder()
|
|
88
|
+
await dec.ready
|
|
89
|
+
return streamDecoder(chunk => dec.decode(chunk), () => dec.flush(), () => dec.free())
|
|
66
90
|
},
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
91
|
+
|
|
92
|
+
async m4a() {
|
|
93
|
+
const { decoder } = await import('aac-decode')
|
|
94
|
+
let dec = await decoder()
|
|
95
|
+
return streamDecoder(chunk => dec.decode(chunk), () => dec.flush(), () => dec.free())
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
async wav() {
|
|
99
|
+
let { default: { decode: wavDecode } } = await import('node-wav')
|
|
100
|
+
return streamDecoder(chunk => wavDecode(chunk))
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
async qoa() {
|
|
104
|
+
let { decode } = await import('qoa-format')
|
|
105
|
+
return streamDecoder(chunk => decode(chunk))
|
|
74
106
|
},
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* StreamDecoder: unified interface
|
|
111
|
+
* .decode(chunk) — decode data, returns { channelData, sampleRate }
|
|
112
|
+
* .decode() — flush remaining + free resources
|
|
113
|
+
* .free() — free resources without flushing
|
|
114
|
+
*/
|
|
115
|
+
function streamDecoder(onDecode, onFlush, onFree) {
|
|
116
|
+
let done = false
|
|
117
|
+
return {
|
|
118
|
+
async decode(chunk) {
|
|
119
|
+
if (chunk?.length) {
|
|
120
|
+
if (done) throw Error('Decoder already freed')
|
|
121
|
+
try { return norm(await onDecode(chunk)) }
|
|
122
|
+
catch (e) { done = true; onFree?.(); throw e }
|
|
123
|
+
}
|
|
124
|
+
if (done) return EMPTY
|
|
125
|
+
done = true
|
|
126
|
+
try {
|
|
127
|
+
let result = onFlush ? norm(await onFlush()) : EMPTY
|
|
128
|
+
onFree?.()
|
|
129
|
+
return result
|
|
130
|
+
} catch (e) { onFree?.(); throw e }
|
|
131
|
+
},
|
|
132
|
+
free() {
|
|
133
|
+
if (done) return
|
|
134
|
+
done = true
|
|
135
|
+
onFree?.()
|
|
79
136
|
}
|
|
80
|
-
return buf && createBuffer(await decode(buf))
|
|
81
137
|
}
|
|
82
138
|
}
|
|
83
139
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
140
|
+
// extract { channelData, sampleRate } from codec result
|
|
141
|
+
function norm(r) {
|
|
142
|
+
if (!r?.channelData?.length) return EMPTY
|
|
143
|
+
return { channelData: r.channelData, sampleRate: r.sampleRate }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// merge two decode results
|
|
147
|
+
function merge(a, b) {
|
|
148
|
+
if (!b?.channelData?.length) return a
|
|
149
|
+
if (!a?.channelData?.length) return b
|
|
150
|
+
return {
|
|
151
|
+
channelData: a.channelData.map((ch, i) => {
|
|
152
|
+
let merged = new Float32Array(ch.length + b.channelData[i].length)
|
|
153
|
+
merged.set(ch)
|
|
154
|
+
merged.set(b.channelData[i], ch.length)
|
|
155
|
+
return merged
|
|
156
|
+
}),
|
|
157
|
+
sampleRate: a.sampleRate
|
|
158
|
+
}
|
|
92
159
|
}
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "audio-decode",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Decode audio data in node or browser",
|
|
5
|
-
"main": "audio-decode.js",
|
|
6
|
-
"module": "audio-decode.js",
|
|
7
|
-
"browser": "audio-decode.js",
|
|
8
5
|
"type": "module",
|
|
6
|
+
"main": "audio-decode.js",
|
|
9
7
|
"types": "audio-decode.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./audio-decode.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
10
12
|
"dependencies": {
|
|
11
|
-
"@wasm-audio-decoders/flac": "^0.2.
|
|
12
|
-
"@wasm-audio-decoders/ogg-vorbis": "^0.1.
|
|
13
|
-
"
|
|
14
|
-
"audio-type": "^2.
|
|
15
|
-
"mpg123-decoder": "^1.0.
|
|
13
|
+
"@wasm-audio-decoders/flac": "^0.2.10",
|
|
14
|
+
"@wasm-audio-decoders/ogg-vorbis": "^0.1.20",
|
|
15
|
+
"aac-decode": "^1.0.0",
|
|
16
|
+
"audio-type": "^2.4.0",
|
|
17
|
+
"mpg123-decoder": "^1.0.3",
|
|
16
18
|
"node-wav": "^0.0.2",
|
|
17
|
-
"ogg-opus-decoder": "^1.
|
|
19
|
+
"ogg-opus-decoder": "^1.7.3",
|
|
18
20
|
"qoa-format": "^1.0.1"
|
|
19
21
|
},
|
|
20
22
|
"devDependencies": {
|
|
21
23
|
"audio-lena": "^2.3.0",
|
|
22
24
|
"base64-arraybuffer": "^1.0.2",
|
|
23
|
-
"tst": "^
|
|
25
|
+
"tst": "^9.2.2"
|
|
24
26
|
},
|
|
25
27
|
"scripts": {
|
|
26
28
|
"test": "node test.js"
|
|
@@ -30,20 +32,21 @@
|
|
|
30
32
|
"url": "git+https://github.com/audiojs/audio-decode.git"
|
|
31
33
|
},
|
|
32
34
|
"keywords": [
|
|
33
|
-
"audiojs",
|
|
34
35
|
"audio",
|
|
35
|
-
"dsp",
|
|
36
36
|
"decode",
|
|
37
|
-
"
|
|
38
|
-
"audio decoder",
|
|
39
|
-
"web audio decoder",
|
|
37
|
+
"decoder",
|
|
40
38
|
"codec",
|
|
41
39
|
"mp3",
|
|
42
40
|
"wav",
|
|
43
41
|
"ogg",
|
|
44
42
|
"vorbis",
|
|
45
43
|
"opus",
|
|
46
|
-
"
|
|
44
|
+
"flac",
|
|
45
|
+
"aac",
|
|
46
|
+
"m4a",
|
|
47
|
+
"qoa",
|
|
48
|
+
"pcm",
|
|
49
|
+
"streaming"
|
|
47
50
|
],
|
|
48
51
|
"author": "ΔY <dfcreative@gmail.com>",
|
|
49
52
|
"license": "MIT",
|
package/readme.md
CHANGED
|
@@ -1,49 +1,80 @@
|
|
|
1
|
-
# audio-decode [](https://github.com/audiojs/audio-decode/actions/workflows/test.js.yml)
|
|
1
|
+
# audio-decode [](https://github.com/audiojs/audio-decode/actions/workflows/test.js.yml)
|
|
2
2
|
|
|
3
|
-
Decode audio data
|
|
3
|
+
Decode audio data in node or browser. Returns `{ channelData, sampleRate }`, where `channelData` is an array of `Float32Array` PCM channels.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
ESM-only package:
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
* [ ] `alac`
|
|
13
|
-
* [ ] `aac`
|
|
14
|
-
* [ ] `m4a`
|
|
15
|
-
* [x] [`qoa`](https://github.com/phoboslab/qoa)
|
|
7
|
+
```js
|
|
8
|
+
import decode from 'audio-decode';
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Supported formats: `wav`, `mp3`, `ogg vorbis`, `flac`, `opus`, `m4a`/`aac`, [`qoa`](https://github.com/phoboslab/qoa).
|
|
16
12
|
|
|
17
13
|
[](https://npmjs.org/package/audio-decode/)
|
|
18
14
|
|
|
15
|
+
### Whole-file decode
|
|
16
|
+
|
|
17
|
+
Auto-detects format. Input can be _ArrayBuffer_, _Uint8Array_, or _Buffer_.
|
|
18
|
+
|
|
19
19
|
```js
|
|
20
|
-
import
|
|
21
|
-
import buffer from 'audio-lena/mp3';
|
|
20
|
+
import decode from 'audio-decode';
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
const { channelData, sampleRate } = await decode(mp3buf);
|
|
24
23
|
```
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
### Chunked decoding
|
|
26
|
+
|
|
27
|
+
Use `decoders` for chunk-by-chunk decoding when you already know the codec.
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
```js
|
|
30
|
+
import { decoders } from 'audio-decode';
|
|
31
|
+
|
|
32
|
+
const decoder = await decoders.mp3();
|
|
33
|
+
const a = await decoder.decode(chunk1); // { channelData, sampleRate }
|
|
34
|
+
const b = await decoder.decode(chunk2);
|
|
35
|
+
const c = await decoder.decode(); // flush + free
|
|
36
|
+
```
|
|
29
37
|
|
|
30
|
-
|
|
38
|
+
Call `.free()` to release resources without flushing:
|
|
31
39
|
|
|
32
40
|
```js
|
|
33
|
-
|
|
41
|
+
decoder.free();
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Stream decoding
|
|
45
|
+
|
|
46
|
+
Decode a `ReadableStream` or async iterable when you already know the codec:
|
|
34
47
|
|
|
35
|
-
|
|
36
|
-
|
|
48
|
+
```js
|
|
49
|
+
import { decodeStream } from 'audio-decode';
|
|
50
|
+
|
|
51
|
+
for await (const { channelData, sampleRate } of decodeStream(stream, 'mp3')) {
|
|
52
|
+
// process each decoded chunk
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Available stream codec keys: `mp3`, `flac`, `opus`, `oga`, `m4a`, `wav`, `qoa`.
|
|
57
|
+
|
|
58
|
+
AAC-in-M4A/MP4 stream decoding is available through `decoders.m4a()` and `decodeStream(stream, 'm4a')`.
|
|
59
|
+
|
|
60
|
+
There is no separate `decoders.aac()` or `decodeStream(stream, 'aac')` alias.
|
|
61
|
+
|
|
62
|
+
### Custom decoders
|
|
63
|
+
|
|
64
|
+
The `decoders` registry is extensible:
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
import { decoders } from 'audio-decode';
|
|
68
|
+
decoders.myformat = async () => ({ decode: chunk => ..., free() {} });
|
|
37
69
|
```
|
|
38
70
|
|
|
39
71
|
## See also
|
|
40
72
|
|
|
41
|
-
* [wasm-audio-decoders](https://github.com/eshaz/wasm-audio-decoders) –
|
|
42
|
-
* [
|
|
43
|
-
* [decodeAudioData](https://
|
|
44
|
-
* [ffmpeg.wasm](https://github.com/ffmpegwasm/ffmpeg.wasm) –
|
|
73
|
+
* [wasm-audio-decoders](https://github.com/eshaz/wasm-audio-decoders) – compact & fast WASM audio decoders.
|
|
74
|
+
* [AudioDecoder](https://developer.mozilla.org/en-US/docs/Web/API/AudioDecoder) – native WebCodecs decoder API.
|
|
75
|
+
* [decodeAudioData](https://developer.mozilla.org/en-US/docs/Web/API/BaseAudioContext/decodeAudioData) – built-in browser decoding method.
|
|
76
|
+
* [ffmpeg.wasm](https://github.com/ffmpegwasm/ffmpeg.wasm) – full encoding/decoding library.
|
|
45
77
|
|
|
46
78
|
## License
|
|
47
79
|
|
|
48
80
|
[MIT](LICENSE) • <a href="https://github.com/krishnized/license/">🕉</a>
|
|
49
|
-
|
package/test.js
CHANGED
|
@@ -1,107 +1,293 @@
|
|
|
1
|
-
|
|
2
|
-
import decodeAudio, { decoders } from './audio-decode.js';
|
|
1
|
+
import decode, { decoders, decodeStream } from './audio-decode.js';
|
|
3
2
|
import wav from 'audio-lena/wav.js';
|
|
4
3
|
import mp3 from 'audio-lena/mp3.js';
|
|
5
4
|
import ogg from 'audio-lena/ogg.js';
|
|
6
5
|
import flac from 'audio-lena/flac.js';
|
|
7
6
|
import opus from 'audio-lena/opus.js';
|
|
8
|
-
import t, { is
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
is(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
let
|
|
38
|
-
|
|
39
|
-
is(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
is(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
is(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const qoa = b64.decode(`cW9hZgAAmgcBALuAFAAIGAAAAAAAAAAAAAAAAOAAQADAT9u4g6Em4roTLwpSDCDQkU5+FDxcWHyYgVOEaUL47KClxa6XUMonkJwLChleRxC1TvCZWqxcyqALA5XNs7zQ1wtEBw2kJTHO6adFgTcyiKXz7MFkrmXotUOnplWgpVOFuU0dkSljYqkCLRWaS1JqdTKo5d2jH/zyAQCASxfo//2F5TVGIXJc9aVRglIxsuz7GdAqES4eddrYBSbr366B7c5pWFZI8YvgQlrdVGnYwu4gDR5jL9bN6gLVhoqcYpvR1ii0HS6Ju+DiUKwT4BTTzyWssSfug8jigj4miC0K67yUQCy8jySs6LIZJMtw0kjBlUWrTWmf8ccRNl7dVApEvF4ME35HJY68UBvVexK8hLSFAd9Xsba1u6t0PopGZzbiyeUgtjyybMxRAph0IQTAxpUBgg8H0O21KyY+IvUu35khezisTVjcovZCQ8OhAEmiHYKA/S2hQLDoIDpPJDMLmQlbBEYCRGSSUHImUQCwHJA5laPaFoZjg00C8z3J1YiE3okw4uoXs4qjGNcCTa1Gp0x8muMsIo2CKde4PF7oSZDSqFGzOT2tkINMyFSlQ2OuI60Ci0GAeZTsPETYYgqFpJN4jRtIFVyEAmdG8/pfRZEvRCJ4nIvcksijFsuI9xicyZDF3BC2cqaAGRBDCrLShvzXPsy8WhCQWFm6Cly1DJBgUBLhI4WjiLZDhsF2hLWOhrT0rey4GpJSXrIJ7ggCkRVPgwVMZJqkkWyWAyBCjagD5JXZIQTAloH1NVgYOMSQm1xiupMGJKIbCJKLwQqamd1om2BollWFf7mz5ouFcLZYBhIEQLSBitjUg66Vdpif00qXJ02aLZPARpAwrMLXgEY5PaatzXmHlg8IjOTcfYu13eU/XBb7pqAkII8AjpmcZCjMyaor4pgSeTlLabXemTuWG3iktsCVrVEeOdyzDKRQagdRSLeQmOISgcIhgPSOzgb63SuYEpB83faadiURhJNa94IVTbyScaWK8aHEhYYTv6PJFeXXpkq8tCsIBBiUWjWImnMAGJKHIZCTSkNah1/1LnMJTt+UbaBEzsFAQJJIqiwC2NhYgOvFZBRRfmWeHUgzC42/WJfaKgoybDoblMiZqNYOpUCWElESkyPUj4KczQAavF6VjjQQ1Zl0NK6Q9ITAc6UgEZkgwqQLFeVBiQSudi/g1S2JgHS1euc45Ie1rKYfSSROhAAc6t0CTpGMLaPRe4Agypjb4FNBeFQQgb9EFl5KJHSaC2UOmMURGpHpdYNSQQaTgd17uBLarNGW6iUysjAwF4ZtjVEOHRNZusBgApAEkCmXSY3kIgWmR4YQEJ2VhoDtkg5iGJLpNluWk0AkGHQR3IqiR2KXRYa4jRrAZHgUihOYkLhU3yHw2qAABQNB4Lbgh4OACnudosaUSk46WU0xiIzNU7PYDLQidiX6/3d7iTGSGhSazG0gMYICLHSqjS0+c8IXttCxGa2SDweSFURA04rB9jajKUKNlNp0qM4mINiKQ2CcCZC9eIJvXMaTEQ4Hcy9xJYjFT0iF0utAJHE2WIRYiG1GApfCfxuTAY3D8CugWEUCAC06gYYhGBQILpLMc/YoS3Xn0sWEmsWE2R6i0YKUKnMdJFkamBJEgsCuEpCGtUKSXQ7I1HhWfI7XYUGXjRoF2PptJFKCoeBWm3E8rIKbHstABkpRhgdtlqvA4iiVWKwSwDEWi4BvYblaEAXSft2vGxCYUCqCe5VCBI4OgYdVOJEQypAZlUoIIPIolhV6KJBYhle10X9eRzeSdPG5klHFoop1hgmGcRGBFy+iHYjWLITQ7ALVgRIgPKmknWx3pF4ij6YzKoWTReKtVkZAiDDokscIAgqMCGQF2vihYoGbZDLriDTEkKsWGmgCFwCUxSmGyIUSEoXD+ofxWoIRgnlNlsRJhHGaOCVTCo1UmHvJjDsWdlOLi+cTojGFxEuLQgyQsSPCF4C+LDkcdTTrmgBkNQikVGmGdY3iFXQna4cgBcQXTurdjA6iDFmgNtuJAnEc6Kl13Hsnh2ofKTdGhQONah3cDhKMSnERaUiGgpzAcLHIsdRkd62QP2kJtPaWmE0eGFUTEIVKCrraTKGKddB71XJ7raF2WUbS8CisdY8qHHMCVSUIlsAsgwouBTiG42eMcwnE3YJDZqSjEMFHc6r9CYhnI12CCa4YqiMxeJDTIORTABbZgE0I4A1Tr0N8mmsgGyF/QYQaOyTsaFdehpGKJlgm0kSASchKw9WegHQMWFZZF9egdMYYc0zt+GZvNM7/Qv+8looQ1b6ZFKUBdIYP1gt8wYeE4mn5U7AsAYKSbpIBoEhAhwAdpygCYFl0Af2FC0NBqXxVoY1BGiVJcI/sIAE5BeFyvXciWMMXSGV/fyA+ylo6cdt4otLYVkhm/1hE8pub9XroSwsGRPbRdmxKMIYd8ih30tiikjfwy3bQioKzd5IXegxAUIi2gFNwphZUifxUaHK0hYBb7aQqcaIC4I9l4ol5omyQX2UmAX2grTR7ohTMdICso1sqAg5pF0XLfYhCJGwtqZ9p0QqkdGuiU0oxNDJlb2RbzspcZnddVJpUiKMRZ+tKuxTKoZNj6GrQtAAEh3vBBLC0BARLfkoEhSIMCF1qwpSpolUgL2JgIc2VJIL/ZlJgLKPpq+1ikC5pCaYT6GOSR0irJJt6XYNDaX0kv0hokTlC3wH7YGyBCwXbYbrkZApKG2gl1iZkGIk7aQ3SlAEAu4AUAAgY9CEQSwnE8RD0BAs22n4EymQJid9ZTbE0ZFEJ3sEigaBiQg33UmWosWLCFfLALC2Cdgg20ogkJQliUxe3AwZoi1JR/dQzcmhPYhj1hJxyZMtiC7wYmgoU6FIftEyDmxd7Yk8kQIBQL1lSe/NkYZAfxVT7KwjOStvaVvkbYp5K+ipmS5EmAEbaEVNCmQYhlt4TYs5RFFKvughWRtgwSv/9QFqUxDaFv5OYVzDNtg2+p4tUtqKwTfWkAWQiNbTL4KTAUZB2kr1lKEpZgHWKP29AgFyXtLdtAyQSXAPkEdg5JFZFv70H+tsY9FI/dBYY0QcMUW0gllLKBwJH/7m7dpm04Vt7BDJ2ioABXvlNMxbMMRtf2UQ1JEQwrVvRSaiyYZDHW1JIqbAloNtanE0FgrSCe1aSQSkJdBL7UDAISBVllutRIAFIXaGH00mWIyh/7FvMWRBGIHtkOl5NI5BLf226RliBEQfNAoYxXIopN2ggsDRUCAU66gVVFkZBjt9ZjRW2UMlCvkkBwSFJWmP6YiHltVDYbPIGaCwBTgl+upROLahO1R+jkM5hL0PT94y1imCfQUjmnJK7Sc1Gm7UZhnMN6E8tPmOajxr7RVdoaFPArjtUwbDkQRmbQ0K6eyxKervQVihHEpoNdspGWcpHHFzePlDRWMZRB5qLWNBQgNEnhmNSjjFSWKbQAEcG3jK8/lEOTjHHup8eoT5UYkam123HFU2O+/jbpujRYIKtUMmhOABTgV2Gei4gTlalbaqfiEMZXUlno0MRbpNYXfB9QTipQkI5vx7Oe3ViZq1NAhrNAopT1SAqgci7iVnoe6ViEab1W1h4VHIaueNmao4SlC0QGWoJgBMMVZM6Z2EJoDMFgo9VclKs82PDZ2aGZTiBoBTZV56eOTz/VXxXNYRFHywCemghKeYVAc4NXQDxKe0pp2FYjudZ+vl1VmCbTQL5ITJOYgWAjh2MmwRc6ziP00WrNWjKudPCa4AiZmpkVsxg3BBumdzWqlQVAmrBTbcTTCObYVJrlHBgwFViyKHY8Wno22caPREMYARraxs8BSlPArhS9+3ol5IOa2TTYMjVOO/VaCipCEAIk8B0KScUWiFS2mKPiiYajNasZtVRatWCnyNkzBmg0EeKcWFeEVRMqvTSYhzEcoT02IxrE8GfAV2hmGByBYolPceFXHbrvcOr6FVglra8TegdUGeYXLM7pyBaYRU1ix9fJqps5aCjUzBmC2xF6aXIGsDgYKiuEPoYUNJnOTYyOkNyFlvfkY4Rwh6eZc1RqvBGwiVhaDBRZEukZWJsInQWLPyLiggEgooEMAh+UEajEwymG2PiQqC1DMrfZExo3BHlqWtRmmcUHOZ96WenXkCrFTZoYqEtYn9MHohc4qrtb7mP8XhYMDVLabPSdSgGNlqgsFJhG4o+2pSxDmBTIK/L1LGhYdALl9WLpzFi3CX0UWiIsGp8TPeOpzxKZgWMtxIeLKhmYy6qI4zAn2jjrahBCcQXYo7snBh5EUFmmHQJKKsT6lqPlUODhy96bUcJYpFEym14Q3EhQEqWw2AYqw1CGdFhahqHABxAegZuWMADG1U6nmFK2JZxFYaBaNFYsOFvpFVghilSVK+0g1ukXgC6fTmcarJWgJ0sAppg4Aegh6BmiVySa/TX4GnQaRmuUUmlCNhji3YS6kUUjGKFdEbryQMAZMFmosuRCqJlabG00Bkg0mL5IVJWcJDCZj1GNoaMBJhmyyCqlMyxm1vRKsQiU6sHX1YhvLFqktNe3ow9oO0D62YYRJGCfTT4YiNBQJMtpj1VMWjo1Q+qT1Ek4kj9sddIUQY1KHuie8ZgiUACKyU23liFkC5FDIqKXJs6G9onhjFoEGkXWiHYMFQKTDfqQNU0VkCW9htNNSRTyYPekWRBE2TBItLxDEgTYkls0lIgrEVWmb6ijEYMi16BfaWZiwC4TsP/zaUbb19Q26GMIzFn6VA75ggUcAFpUr1jSEyBU1lSBSpimMj6WVNBUWDTgd91VchZCFAjvspS3NMwBK7yylKI0AqMf7KYVqKQLok3swtQdISQwbaSo1AmBdRd5oRSVKSVsJvtCApRkE22K61EmlkzbYpdZUSBXKHmo90JYJBUifS1axlEJFSpdBJoUwKOUi8stgjLIoxRXyAa0pAigVFtqINkyIAjS/ihNCbQ1ANbaUwRFkEwC1/NxBGiRIJrW8NAozAtpi9WwhAooi6DX1YEYSkAbdDtSqTObau8f3hWoIUlD3SeekSykWBP7q/DXJIxBVumgspViiMR62XQQlURRQb7LNaWUJFEnv0glqRMQgO/zy1DNkxYe//KacymRSl1/0IAraBCnR3+2sUts0NT/7qKkymjV8lvtOARRGFHa3fRpBltTVZ4LDEUBiArRq/kCabGCD9XZWTgoFCGz1bJZOiBgAdDSu5iLZgye/pAeFItjuE7+kaHm0cBDtvrVAFLZkAungtQhDIFaAfWUlBWSQTbofIUVxzIslttlmRQAohejXwQgFxSSbtSfKUQZCEgsMkmiIBXBnWzS2AZIVeCNrS7JiSUQvJ/2wfzYqxYC2eiSGJp01Sd5LJBUCzLWK3+AUpSEBVWD6wgvJkWTk/t7GYR215IU3tJpGLAK1BTezkyokm03VFrSgJmDPJFXktKFJSOsBlfVE1CoA3bG1bSwayTHJNLRvaQXbfm0X1UgoAEAfQDS1OgCScpdJTYWLBWQu10NppVIQGlb0yWUliCYDFfcJYiAQC7gBQACBj8A/3pAlYDx/DjDmfdtxiXXIoItuioAmZRGSA+6mAEhlCLTJ7aRKUiVElimsmAhZBC2KfxUhjFsk7o7/aSOiyQSvg/uxYnZJhOb3Wzks5MPVFDbcKiSSKfUs1kjBJQoddHyPcNMVpXbUb7jW2eVx/6VtURIRNBtvlUy4Jg4CWex1AAehhJQ7NBVMYDIMCkehFTCNCEKBaWzlbiig8FbPCJaACJg0E9piFgInBSSyKYgFgGVZPR7BSxW5BG8TssyJ5Q4h2ela8li1iVD7TJDW1RUCP0slU4TAVYm77A2BNA4VaN7ZUpmyBsVV89IgobKlpQb3DAAskDSFDvYyZU0LYFUWsqJMaJ2tFb2REEFFLywVvYyKMUJbpJWtrYE6I3hh1S1FEQMD6yH1DygQhQt5BLWyZNGBut1MNQpNQFLeyKWlEgQJAftRDKXKBpqt3lwsFdEWAH6SWEUlWIrAd7SDSkVQgst3sJMrRU1U26XUInFlVbIt7SKOKQWOth11RAzBNSa6zy1IE9CV4ptYccSjSDX0M2gZJKJhhXy2PDM0Fm3VFpaYiyKcHfVGtrHGQItW9WgydEnEV2aF5TRmCLhL5JU1KARBVMv1FZWpg4UW2Tw1gKWjIQr9RIU4qDFsp90IlXMgnKq12zglD2BYBfZAKQWCRHtEesJOJZAARwx64tQF2DNTpqbikcUZW1B3vHOIJUE+/q65knMmTZYZDZWQbiZPlg0mn+/vt6bEkTNowASXKkyjEUFrItd0BJJCAVsh9vUSmsYwKEIWImykmS8ZdFUh6EHYu+A/tyQwQGqe2W+Vi3JWz1LcKFWaT4YO0BltFokm4o62BayGJJDLD5aVHkXEXCKp1FGx5YDZguwoDLJFyI+TvaZ/RjV2hku94q+HJeqk3WmkSZpFrGypeiRQGBUdND8HGt5IVS0mHScGbIT1KEfAySLkyLVyQtqakmNPhSE3Xlnaom7VIFosBHBE/pZBvlJEEZZvhwojzluKYt15SSZYjXDIIoe4g9OCnwhsWIEWnnqU8QXnOQBwjBeO6cfBUJ6vggs9BwuOgzz4K+mnRdlzZ+cFDwgAgwEkCFMI56WE1X4GuvFYXDQpoIVLjQdl4oUCAqoYt048WDBiYcIXqSqrInVIC4htNckkilxkVw0auEjY0oamEOdhwj+UaddJcFozglAch2GO0XVwQeuHRNlURuCSFxeBB5JIvIMK5wqEYBQmg+ZWDPOZ84lPXGZ25rB3Iom4B0Q8mgHk2ZcWNUeFZ3RvG5diqUsszthiOGYAiTAUwYCnAWlSRwZUMDZTXDlbk2ydtyBOSWF2ETK2WDnAzsomHwcLCoIo1JFDx0KDyB2WAuxXTHqbI9QLVKZHrnB+M6H4R1DYEgbkghcmHLqR7VwPc6cvuUt15hiqF2wxi3EMA8koJcRRByBAKTatPF8J43iVtvzue6pbXVP2qJ1p0UbGhncHJooZUlCClzDHWkSexK7XIlNJS7ZBnZbJJuaBUbRxp5k0poqhEnUmxj80dPinrcdCsNEeppmuJkzVkuutHQlmQ8UzPiSd+iYt2Q/1oX0uB7Wmm+0S6xCGpszdTWNckzcPCAsZIEnItqTg7Uce3HHW4jF7OJtC3HYOS1pLcPZTlhgBWsqqlha3KB4aAfBTL5Zr/8IV1pL0NkB6FiuFEHTmissmVR0hqMWG/aJWzS2/BV7Xo+Y8n7jGJqkhLWBJawV+DaNqbW/BBb7s3a4n/XnV9Bw/Oav7zXZtQhsyGsGI1ilEyYEO3iQ2aibJEbbIj5Ywo8iFulIhtosynFLQokwF0g5WBHcwrAaI1sAWkgB9JoW6UXa0BQymQ7KCrqSDaUUB8ZHtnJ1rJhWSA7UgmbElt6EdNWRqUkV8rBcLZInYFWSsqTtgwpBV4RJfOQbqCvW+A9gKC2DNdWlqOskwxkOlcEPR2Y+ML/UJttpK13N/lbDecc1ygq+mQTBmBKCQNhXIh5aNvIlsxUKQcS+nn+QVTdWCZYRNpWWhpaCmBBsqJZUMiuxB2wIVLaMvNDH/gZVspN2hrl2KdKcMfTIxxJIlY0VDMQpUiDWOIvhEN9JNtQgKXc1wDIKU2zfEMZu0CTVZOkoCtDIlxGA/8laXszwE1/p8HtyStOUC0hMghYkgJIPVMqFsNaoUbdejbWwbAUS9oFSvbANKNK/JKJJsOjAEvAwXWwINSPR1DJkTL8ttteACCQkG2E6UZmTGQhfLDvkggggAkOv3uJNstReDgyG5LBkSRIJFdJis1TIKBCusBm/kovVNw3EHTaUFROgLgLh0gwgCAqsANuCIqKkX2aRX7abLMSDSSJZdI1uBRtGHhrAFPEhWeg63aDNJaNLTLHZIRmKIBPSqpS4OVsnrkF7GhjwGTPTbvbcqkBAptAlkNcyXMavOF+DGBSGy1iAZJWYgCEJsFHlkFQPJGrGd2/nWDACXhiCRkSUHIZwrSoiWFeilSCgC7mKFYaXLc5dDRvWVNrpJNHFKhXEbCkU7nmQUCRvWpbDgfJUjjslOhABipUywEErDgwAlCZOKBgkQIiVpgKFuMOu1XwQQQXyVCIic9EeJVJaKhAlwM8BscJwLOQmDw0rZ8SmqBCqYAKqACslyMCwUVpyXOQk10HDUTGnYie/Ni6dxs8lCHyiMlpWV6Gp9xHZRhm4ZO4ICs5jc4YpFquUFoqUJmYxTGJyiiU1o6XBK6VmqJRkLpVCliWCqOU2rB0Wk1ULJHBKYdicTiii43KqpHPwvEBALuAFAAIGAgR+FgLeQk17eUNmeQxB1+GXB9fik0zh4nsY9DutQEMmuExtCMpnEqCCJ6qcWRCN4OIBNGolMJ7g0T1UAjiVTuAEyOtQmlJQpqjJKKXSaDNoOl0bQJM3gCJC+H8BtA1bqLLZIbQGJKRirtEwATOzhJ2wid7GkHxirTAD7LAKgIBik0JkkBToDCW2gQ+kQ0ylYFOgLweZIYqj3JIhSBxnN2WGoXAlTwgE4MTNVWbDKKpmGSkAEDhMlmDEwGtQQnjynZFPU+dkQabmKksFFhOGliC1bEwyTg0xIRDQWdgSBdhezuGFrzC5pKF2GVWWCRxmnQYW5niBJBijlUEOqMKpCN0blwWZF4nBXbIJPycFJy8a/BnozPwfG97gcXpJUzrVXIwLwWFBgBbfF6kLcivEuxrlzCoPRuV7nUBh2YXTn5DdRsKBNmUDgR14bEXSGnwRIBYQYPABZDgdoPFeylDkIGEykUWEC4TkLIBv7gW0LbakzC2uvGZIEGSuDaAWEJiQIDrpVU5ETC6fF9gpWMzOhSGlySGw9BG0nN47k5CxT6JhPggOW4gtER3V1CQE1rYQoapRKogQK4VbukLdpqdhoiBzKgE5GBUmoYJioTQAoTRiglEC5NFlo9lXBRTDr8PmXdwsrRQoZ1actJO4HEp4AN3mYyBuw3CHXRCrjEYoze6cKGqqUCoQVZzM01CV4ilUX06XQB4RSqBeJuxeDgwdFp1VWgn42pSZHsNQKrBxudwbe43HiyNUYt86En5bim0gnblObqherghepqMKnBcSyFqTg1xLl2ouXHOYphSoDybdoWDuCMy5EN+gXUbpSYmjYCMbARKNiZod2GgjMlpnWp0DQ7gUxii1XGdTRErgSpTeELrAppjdlpo0TBpzmS3znbHQKbFi5VTeDqGAgGMP4NwGTJ0LGTSjHdicJtgodAKfhiCwtAXwuFyBIUziY2VHXzOCLgCrTMJdrMysAOgjVJrJBzud2ViVXEaFYS4FEBYeFKgOF1qWKhpB2Oly/hHcmunri6fiSFieCx2I1pPGmhz2xNI2hWEBnPfpICwWNQVdn+UkqaIZiVvQOpXklVdKmPcDqj2bAiDekUDgZCBqM1m2nQDtQ0fP3aYbLgcpSDAcnBgBAFoGOpjGIzo0yHL12ULJRM/lTbbbMLqCUFrUgRklQglWnGB5luYQm5Xw77DZHmWUupUH4plU3jSQAD0CmbICINycqAxWonNnoiexDVg3i1wgg01qVFAC7xWqSyCZjMKpyMxxElmmBwikQ1Gx2AarTk8ZBD4UsjkHUO5MDpmh0XAQxHHY1RDZyqflT5NXXyiPd5lOkxVZRgYyhL9TlcoiErjSo+SV5jCPzpdnoNhwmBWyGyzGlFSS7nU66jQWpGCmxdGrCVgmhQ2iTYHC1IgpPQqrgzKUWLmyXNwKMhTm52IS5Dl91SrtjOdwxc5YCmtNMInOhqSQ26SRMvxwI00Y7pmgrJJdY7OkChv0tVwMolVK7TzvGqyQZaz5S8ChqIBogAlwUN0Vg0UxTSYW3iJVbQ5rJAYdxGyx40JAqprEDVE82Ju02hjLIRrDKHqcDrsEGiwEBp1DUEkqgFRTKIFbpwWiXsqhCJjttYowkCDUWOWw1GYcZZsHLAABAKDhABA0awlESx2Mqq/c2kki3Y1XqPVTOPnsEBkhgF0YpqdUWhArOd83pcbcqXXgZBKhpeeLh0hmveYGGACSJUGjHRWs13u+9SMfNXLxthIeWKPLQTeD4WLAHDKTx6ONzKjhUJxtVJEqJ6GwYyw0HOUF3IrT7sbnRCTiop2cQBhI5iFcAbCBKYpQIYBZ4DXRsTrgBtdEJloAgd5CeiVKQ8SrnawriTAuQNKhFVN4ltEB8FwuWUw7YmKmXzpazRecnBggAM5INBAskZ+AY4u0EqKE3N6QqponZqAgtoodEAptQhl4dKGtug4cX+AQbqTHqJJhsIVFoh0EI141GuMwE0oGnawAJiVuohDdQkVxo03B1V3GO1QKw4iPG3jtOLeuxTKhIMRxMuJosJhHTc+qzneJ3h4qBbsDjoGb0uyUVQalB5xy8HqVEGAI3aoUNKBj4cbZ9pudpbmdRdq4jOEFmysxn4klQCjIsCddFqOwZkdBl1ikvD8h6CAOXRhZEAQ6RVRc5MERE2MQ0qAADY2+ckE7XRZSct7RgZCZwOpcsdYppRs2CwLRCI24mc4cNPoR4IecslNOtgg1RFkmVOWwcwjEm7SKVaieaUhWU5Pkua9KONuGAz0gEWsqVNBXquQrhmHZ4JquJFERElq4LwSgekEW4IIf6SCTAbKZ4qu1QhTkyxcE42rCOVy9WEQaITFRKpaYGAWElqpktNsEgKiGIYUTFeJT3lNcYMBVVdB7ru1MlJLLPiz9cLvilnTRRDGMJV1VOq6okAO0B52Kz32ztIQb2LSyJ55TyWMYlBp10NwxZRwES6fS2VoY4LbrM2ADbAGaYO8skpplbRkmyeeSIVEkVv9pXNVBYeCWNJIwJ7huHRRCGfy48oommZNVDZBLLZDQU2rjkDWRZ5KTik1wPPpkUjVV4UCRl3HWhht1l1gIxg/R3RvnPdg40TA7pTQx2yuUDsllRQx2PtRhSaSBUoGSUOX7lJNagrCTHAvYEZDBt5IgjCl2ZHGHkhdRDrZkFD3SPtxc8tFNyZGGGpe3qmQpEdZQMpW1DgAStiF0tOPo5tHSEK3AGS1FUoMqvgyqCQASjLfOKLsqVtGjDahGBVEuQEAu4AUAAgYAY/6yv7RA6b3ExQV37sM40KBdbgdKMKvSPVkqJNyANFBguAkS3Bu7EaZfiA6YjPYSF0EIuvONj5AnTECy1G6kkgLeEdhwVbwQloLU8gFsiJD6Uw2XiXSjEoqkroAjZWCRtHANrEOgqdAxCq2IH6Ai05MbNSE5YwLOrbfpZF/TLhWkA2AhSzCqUiyNqSHYWpvTKDorM1pIdhBgK8oWLkDXEGbDmB4gxbeQI0CImvJWhpJXRAjQJA6IEDdORbSUrbwQ2hDOtZNUiJHaoA2Xt7YnHLZN9WyRfqYdDEvsKQoEFBqQ2WWcAHoA2ZoPBCQHAAbUIAW1ZhVLLhUM27QVXsomVMFpqDDaMvQUwn2FlsAIkJcKi0k6gYSDEUFeLgZmPVcQEVCL+dkXnFGvS0W7I6mxkVKBjbIkFGYVFsRoXrRApBP3Vl6yXK4BUbcTZpQfRYHSlABGQZFzBhDZEFlIWIci0OJQcibLeZIQhOtEw1gMltMgmDEymIGwkClaCQJWMTCPOs/ZtsGXlNELSTS+ECWXDPFsR7SGXEkNCnZQXbCqvI+6EN+HEO1kjX8Rt7uvfOKPhCa8KBHiYQ28sWvMqClNzaGTfQSfuRNN9R11IDlnL0yAE+cIzUIGT+1VDGnCGCdNGR1ZKGvRlU4Ee2c6SDB/DABdSRbeTjBOa2KbIDKKlI0JWsLGUlaGDnbYMfMZL9ARttpkslCkMAw/EkmlBBTIzCJkFAKVLYKP9MtXsP32is3WgeDUjagUTaEUBWASakLLCbNEbD/do86ky3S3b2T2y/kvcaV7oR5LZ7D7Kl1YoMxIDA1DQC0yTRSbSZI5QZQMwFklMuFSCY4ii4EEpolAC0/RZhe0QICK0/wvgMZtokjfb/f1E4mIC775daEyKhZI/g9tkQkcF8vf6P0yw6y2StTDSexCQGDJBKIyJZNQMImlCkNIUqIaSQajSjBZBJLK4MIBRsFokgk8kNjECI2GSSAQKjYbqvJJstkNm9jtmkomkBCAAYnpCWiGzgKiSiWLPIpRmIxFKEkGU63akTAIiDBZbqIEKmAHHpDtPZHQaImVWa2AyWVRSdpvr7ZbJrvK2220FkMBEgg0CAEkhklCiEAQCgQKMYBHovnURkxp1skSiUkBCkiiiCQkUUMUKAKGFOCZhcm/08mKWXXXTW6yRvo7nNZ5fRaFlrJ5k4NlsIahYQ3HU+QmxDMQB5JrDuBERR5mAQgbLYUNkYUshgoghUYn4b9vq7/Jstt1lsksmkm262Wy2wWGR4PdtNbRaHAEun9AgsoEi8Q+yWYzIoiliARCSUSUSSSJIRKKDLDBBIXiJNJhMAoyBFcKCYSJPotGv8luMk2190f0SAalTb+fRt7Nodr/49fH1mm9muv0OgUQkBAEaAgkRGw0CUAhCgkGSYrQINhAQgVEkWA6qGmBhaBoZZtcIhIFBk2u1t0Wssa//X7fzW37Rft/v/LZNJTEGtqwgyRaKQQlBEltNoNMhwCik0202GUESSaLbbZbaYcttttNpttsh222k2mgChREEJpBFMkUO0WCQ2QwzSywBaQSiSDQQQKGKSBRJIhBCIZEllIglFIlhm0y0kykim2HbLaaJSTTLYZplIkkIEUkBTYYZdppbdNF19+/vu3110b/77/7bXa+x7vf/77vtddG+223u112v0bXXeWyzWWSxbJJBCICARBFAIkgIBkEksW22GS22237Rddvddd79tvF22uu+9+3/0e/fba/ff/fRvvffv99v/tG2+211891tgW2QUwwAE0UBiSUCQSCKSUEpJJAlBIBJIVBAigEEUlEhASSACKCAQQFFENhgMNFAEWGGK22WGmWRLDbZYJZACYEggAoBCCJJIYoAkpFFJJEhgiikikmkSiGSKSaaSaaSQYpJNJJppFFBkSUUSSQQQCEgAIAkAAskkNzUNowr/eFw/023AW27Q2FFBBBBAAIJAQhFAgkkEklBUUUUUiSkmUGTTSSSZZaTIdJEookhBIkhEEAAAAARBAC5+/oT+x5/8SSWW222zW2xLdbbNdbbddF1112uls1uka7XSzWyyyyw/7q//b1/7RA/9tH+/+/+8WWSW2WGWyyxNNLLbbrrbrGttuutt2ut0XXXXXX3a67RrprrpprrbbFtttsttssNgONBfwBa04JhCKKCCKKSQQGCBBBBBBBJIYkgkkAgkEEBSSCQQQSCCSFBFBAAgBEAoPmU3ySkzWBAv9eBrLEIZdBcWMcHZUPTQCGFshFtlJAQR2SOW2lT20BxbbbabbbSIQEEUUgiiggAyi2UyQKGbrBTIAhYDvtc8DX3SL/3Rb/wcD9pDv//fFA+vi738BU0AJokRJBFFFNgyU22uumwQGDZKKaTaIabYEAttKntklFA0kCEEmhR2xA1gJt+sGu1sLxT/2m/7wzQ/7Xf945o77Btc+sRCmxRoLHosottttJBCCKCSSCCQQFBJJJIIJBBIUkggkggggggm2202m22aGDaBSbTJKZJIEAJIK6mL76Qa59Dt/v9otD91Bj3givgwGimCgkAUppARW0RQmWgRyCAAIKbJPLYQAptI08kggkAiQYce87oAtD0t+++1+//8SSWWSy2WWWxbZZZZZZZbLEtttlltssssP/+7//b//+Qb//Jd/9rr/D+21+/hj30EBVw0gmw0yWQrYTHFTVf/bC3/uv///2/8SwwyyyWWyyQ///v///9t/FhhllkklllkWWWWWWWSSSQ/VH94rfPbaAQC7gBQACBgAAAAFAAQAA+3RDv3eIxoxBiktOAByaBAFoogEtIplpg2UO2yik2yRDTZZaabSYbYNJFttAttssg220k2220m2DIKbbabbbLYQggggkkggghCCCSQSSSSQIAAAAAAAAAAUEkkkkkkkkhSSSCSSSSSSFBBIIIIJBBAUEggEEAAAAAy022yym2ykCbZJbbbLTbQMstNpEttpJg22SGmmUUGWBIYaKFBaZJYNIIEFFCigkATFQCpbBZtvB2/++3v//38WWWWW2yyyyxLLLZZZZbLLIkkkkkkkkkkSyyyyy2222xbbLLZZZZZZFllsssstllkWWGGGGGGWWQ/P//f//9vvD/u3///u//0Db/7W37aaOQrtNdvbFFdoBqnssNjhgJoGAEgiAtIEpAkQQSigWGUiAJLSKSbbRbYNpNNttpNttAi02iU22mWmDTKSTSZTTaIBJtMoppEhhAUUCUkQSi0iBSaQIJRQQJIMEghIoUIsEAik0SUUkWGWDZQabTKLTKIJNNplNtlFJgmkU22EkmWUBRZJNOPOVPAIAQAEQSAgQwDBQYoAAYYKANdVMtmmhisKWW3XwxXSQwVXFTVVRQCIBoQJowYYwRQBDBBIM4MskASkUCGWSUEGBIAIQwopKEEAggAAEo448wzTDjlVTjTDAgJqpgIAMAAAxRU1TSwS2wdFFFBQDVTQBNdEJphoxpoDBAgAEkAUEgBDAADYqZLHBWENtmrrihgK+e7bb627bQtNNtZDNFYoDnuljm192/8KaXXa3b/a2wrrrDLbDTVFANEssMElhogGKGnGGQESGAaADVRRBJBTBVeONMWOAAAEkigAgg04YQRAKCIYQRAAAJIgoIIBopAAQEEkAIlAkgSkSUk0SUUkBRSaZRSaaSYJtpJJJhDhlAUU02mW20y0CbSLTbbbbbYItNtttNtpJgm2UWmmGWmmBSTTRYZaaLQNpEotMscMoAgSSiSEUjjQAQJxwJEBppgGWCiCKnGGWAYbdrdHbrrrD20cd20123sG6+/bbffXaw7vHfv9fvv/Cv3+9+/+930P3z337ae/XwHdttNPdbZrB1sUVmhks0MFV11ZVQwAiAIACJFTQaaIBABoKCJRSSIEpJFFJNMotAmky0mUSyykBKLRRSSRQRYJIpJNMsNJEggiUQg0ygSEARBKRQJBJQQAkokAkkkY8QgABCCIIQQAAOONMAIpqy4GmmiACWQCWAqqYopJFHXFAU10tut1tu0LTT3X333/ew9/trtvtbrtB00cd1t121sHb3W6662zXQdbbddNHddNAU1kgsUMElsGGqGWWmKGAABBDjTJaaYYBgAANXNEgowCiEEEAAkkgASSCKcCRQSSCBBJRIJKLCIFlkklhhAlFAUSSigQUUSSAZaKLLLCJRQIIMssspNMIgkWGEUkiUWWBRKLSRZaTCIJJIopJIlJMgykWkiUkyyiDLLGHDKRRZYIpJplhhlhAgUUUCUUCCSSBBBKCGGCCBAIIgE4YEABAgIaaADTQAIYBpzzyxhhlhgKKGGKKWCWWAqopbLFNLFVAU0MNttlt10GKOKWy2622wrbLLNHlTJYBgMUVMMsUMEGWGmGWWGKKgaYYqaADTjTAFhpiihhltkCmqiAAASAEwVjjTVDRAacBwogBIJxpCAAjBCmmCgAUASCKKAQIIQQAABBAIQJIIQBAgnAkAAhAgASCAIIAIJBBNMMNEEEONMAwwWW2Sy22QJbbLJZbFVDBmhhuut111sGa622W26KXQNbbddddd77Cu222v29110LXTbba6+++QprrrbddZbdBuiimlkUUtkC22WCGGGiiAKCCQQSiQSUBRJKKJJLDKIFJJIspJlFpAyykk0y022mCabTSbbaabYNtNNtptNtpg2222m22m02DbTbbaabbbQNNttNtttttg202222m22mDTbbSbbTSaYNJJMtJFlpIAiiSSSUQRQgCBIJJIooIpoGnGmCU1V1UwDLLDDLYqpbAsstuls01ugO7XXbfe6++wtdr77r79tdC222v3199+8Lbb/7fff/7wv/979/vvv9Dvv3+9+++20L77a/fb37ew7rttrrtvbbC088c910110G6Oa2y222KgpbDDNHDYppBhhlkNVUMkgGGGimmAUQwwRRIaaKaaAQAAgBFNOOFFEIEgAAgAQAEQVTRQAASQAIAoAAgAAggIIAAAgAQUEAAAQgCKCAAAQgAIoJAAIJIAIEkEUgkAAEIAQQSSCEUGeCCKRJRRQQYZQIossopJMspAkkkWWki02kDLLTLLTaTLQJtMtNtNNNtA0022002002DbaabbaaaaYJNNpJlptMtA2kk000mk0kCSSSRRZSKJQIsIYIkghBjAKCCCAQgjjhBAAghFNNFAECGmqKWyKuKAYo66qopoorB001uutt11sKee663z3W7Qtdddfdb77tA2vuuvvuuu0Ha2666y3a2wdbrrbrrrrrCutt11stt0sC3W22222WOArZbrbbJbbZBmttlt0thigKKmGxVQxxVQVDLDTDLVlVANNMMMAhksEAQw00SSQQRQVjAYYIBDBBABqooEEAoqoKmmmGiA1U0QQIKKIYYcaYAgAANNAAgAAEAQEAAAAgAQACAAAQQQCEAIIoJAAIIIAEIEggoIEEIAhQECQQCggSAJRQJKKJJRQFAkolBEopBAEiiSUiiUUiCKDDKKKSSRIIsoklFEklFAiSUiiEEiSSBKKCCKJJJKIBALuAFAAIGAACAAMABQAF8fkSz+MTHu8IIFFEEkokhACiSCCQQCCSAIIJRIJJJBAEkkEEkkEEkgCCEECCEECCAJIIBIQooJAEUUgggEIEAACCKaKABABRAAgAIggAAEEAAGqmmmGCAQBBABDTTRDBAEAhpppqppoGCCAAwQCCSQABDBBBDBBBAMEEEMEEEMMA0ww00ww00wDDTTDIaaqq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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAu4AOBwW4AAEAAQABAAH0shPQ5kAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKaIBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU0QCAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAACigKKKKKKKKKKAopxoggAAAEE0QGmiA00QAIIAAAKaIAAAAAFNEAppooCCAAACAACmgaIAIKKIBBAAAgAAgEAoooCiiiAAACmiAIBTRBAAABBBFFEAAAEAAEE40QAQUUUUQRRTjRAAAKaAgFNEApogEAAAUQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACmiAAIAAIBAKIBTBNNNNNNNNNME000000000wRBBAaaaaIIAggggggAFNEAAQQACCnGiAAAAAKaIAKaAgAAAAApogAAAAAAAAAU0QAABTRAIAAKAgAAAgoopogAQQCmiCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU0QAU0QBBAaaaaIBBAAgggggNNNME000000000wTTTTTRBBAKBpppogAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==`)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
t('
|
|
85
|
-
let
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
let
|
|
103
|
-
is(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
7
|
+
import t, { is } from 'tst';
|
|
8
|
+
import { readFileSync } from 'fs';
|
|
9
|
+
|
|
10
|
+
const qoa = readFileSync(new URL('./fixtures/qoa-sample.qoa', import.meta.url))
|
|
11
|
+
const m4a = readFileSync(new URL('../audio-lena/lena.m4a', import.meta.url))
|
|
12
|
+
|
|
13
|
+
const dur = r => r.channelData[0].length / r.sampleRate
|
|
14
|
+
const rms = f32 => { let s = 0; for (let i = 0; i < f32.length; i++) s += f32[i] * f32[i]; return Math.sqrt(s / f32.length) }
|
|
15
|
+
const near = (a, b, tol = 0.02) => Math.abs(a - b) < tol
|
|
16
|
+
|
|
17
|
+
// -- whole-file decode with content verification --
|
|
18
|
+
|
|
19
|
+
t('wav', async () => {
|
|
20
|
+
let r = await decode(wav)
|
|
21
|
+
is(r.channelData.length, 1)
|
|
22
|
+
is(r.sampleRate, 44100)
|
|
23
|
+
is(near(dur(r), 12.27), true, 'duration')
|
|
24
|
+
is(near(rms(r.channelData[0]), 0.13, 0.01), true, 'rms')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
t('mp3', async () => {
|
|
28
|
+
let r = await decode(mp3)
|
|
29
|
+
is(r.channelData.length, 2)
|
|
30
|
+
is(r.sampleRate, 44100)
|
|
31
|
+
is(near(dur(r), 12.27), true, 'duration')
|
|
32
|
+
is(near(rms(r.channelData[0]), 0.13, 0.01), true, 'rms')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
t('ogg vorbis', async () => {
|
|
36
|
+
let r = await decode(ogg)
|
|
37
|
+
is(r.sampleRate, 44100)
|
|
38
|
+
is(near(dur(r), 12.27), true, 'duration')
|
|
39
|
+
is(near(rms(r.channelData[0]), 0.13, 0.01), true, 'rms')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
t('flac', async () => {
|
|
43
|
+
let r = await decode(flac)
|
|
44
|
+
is(r.sampleRate, 44100)
|
|
45
|
+
is(near(dur(r), 12.27), true, 'duration')
|
|
46
|
+
// flac is lossless, must match wav exactly
|
|
47
|
+
is(near(rms(r.channelData[0]), 0.1298, 0.001), true, 'rms lossless')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
t('opus', async () => {
|
|
51
|
+
let r = await decode(opus)
|
|
52
|
+
is(r.sampleRate, 48000)
|
|
53
|
+
is(near(dur(r), 12.27), true, 'duration')
|
|
54
|
+
is(near(rms(r.channelData[0]), 0.12, 0.02), true, 'rms')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
t('m4a', async () => {
|
|
58
|
+
let r = await decode(m4a)
|
|
59
|
+
is(r.channelData.length, 2)
|
|
60
|
+
is(r.sampleRate, 44100)
|
|
61
|
+
is(near(dur(r), 12.27), true, 'duration')
|
|
62
|
+
is(rms(r.channelData[0]) > 0.05, true, 'has audio content')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
t('qoa', async () => {
|
|
66
|
+
let r = await decode(qoa)
|
|
67
|
+
is(near(dur(r), 0.82, 0.05), true, 'duration')
|
|
68
|
+
is(r.channelData.length >= 1, true, 'channels')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
t('uint8array input', async () => {
|
|
72
|
+
let r = await decode(new Uint8Array(wav))
|
|
73
|
+
is(near(dur(r), 12.27), true)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
t('buffer input', async () => {
|
|
77
|
+
let r = await decode(Buffer.from(wav))
|
|
78
|
+
is(near(dur(r), 12.27), true)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// -- streaming via decoders --
|
|
82
|
+
|
|
83
|
+
t('stream mp3', async () => {
|
|
84
|
+
let dec = await decoders.mp3()
|
|
85
|
+
let r = await dec.decode(new Uint8Array(mp3))
|
|
86
|
+
is(r.channelData.length > 0, true)
|
|
87
|
+
is(r.channelData[0].length > 0, true)
|
|
88
|
+
is(r.sampleRate, 44100)
|
|
89
|
+
await dec.decode()
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
t('stream wav', async () => {
|
|
93
|
+
let dec = await decoders.wav()
|
|
94
|
+
let r = await dec.decode(new Uint8Array(wav))
|
|
95
|
+
is(r.channelData[0].length > 0, true)
|
|
96
|
+
is(r.sampleRate, 44100)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
t('stream flac', async () => {
|
|
100
|
+
let dec = await decoders.flac()
|
|
101
|
+
let r = await dec.decode(new Uint8Array(flac))
|
|
102
|
+
is(r.channelData[0].length > 0, true)
|
|
103
|
+
await dec.decode()
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
t('stream opus', async () => {
|
|
107
|
+
let dec = await decoders.opus()
|
|
108
|
+
let r = await dec.decode(new Uint8Array(opus))
|
|
109
|
+
is(r.channelData[0].length > 0, true)
|
|
110
|
+
await dec.decode()
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
t('stream oga', async () => {
|
|
114
|
+
let dec = await decoders.oga()
|
|
115
|
+
let r = await dec.decode(new Uint8Array(ogg))
|
|
116
|
+
is(r.channelData[0].length > 0, true)
|
|
117
|
+
await dec.decode()
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
t('stream m4a', async () => {
|
|
121
|
+
let dec = await decoders.m4a()
|
|
122
|
+
let r = await dec.decode(new Uint8Array(m4a))
|
|
123
|
+
is(r.channelData.length > 0, true)
|
|
124
|
+
is(r.channelData[0].length > 0, true)
|
|
125
|
+
is(r.sampleRate, 44100)
|
|
126
|
+
await dec.decode()
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
// -- flush / free lifecycle --
|
|
130
|
+
|
|
131
|
+
t('double flush', async () => {
|
|
132
|
+
let dec = await decoders.mp3()
|
|
133
|
+
await dec.decode(new Uint8Array(mp3))
|
|
134
|
+
await dec.decode()
|
|
135
|
+
let r = await dec.decode()
|
|
136
|
+
is(r.channelData.length, 0)
|
|
137
|
+
is(r.sampleRate, 0)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
t('free without flush', async () => {
|
|
141
|
+
let dec = await decoders.mp3()
|
|
142
|
+
await dec.decode(new Uint8Array(mp3))
|
|
143
|
+
dec.free()
|
|
144
|
+
let r = await dec.decode()
|
|
145
|
+
is(r.channelData.length, 0)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
t('decode after free throws', async () => {
|
|
149
|
+
let dec = await decoders.mp3()
|
|
150
|
+
dec.free()
|
|
151
|
+
let threw = false
|
|
152
|
+
try { await dec.decode(new Uint8Array(mp3)) } catch { threw = true }
|
|
153
|
+
is(threw, true)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
t('double free is safe', async () => {
|
|
157
|
+
let dec = await decoders.flac()
|
|
158
|
+
dec.free()
|
|
159
|
+
dec.free()
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
// -- EMPTY immutability --
|
|
163
|
+
|
|
164
|
+
t('empty result is immutable', async () => {
|
|
165
|
+
let dec = await decoders.mp3()
|
|
166
|
+
await dec.decode()
|
|
167
|
+
let r = await dec.decode()
|
|
168
|
+
let threw = false
|
|
169
|
+
try { r.sampleRate = 999 } catch { threw = true }
|
|
170
|
+
is(threw, true)
|
|
171
|
+
try { r.channelData.push(new Float32Array(1)) } catch { threw = true }
|
|
172
|
+
is(threw, true)
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
// -- input validation --
|
|
176
|
+
|
|
177
|
+
t('rejects string', async () => {
|
|
178
|
+
let threw = false
|
|
179
|
+
try { await decode('hello') } catch (e) { threw = e instanceof TypeError }
|
|
180
|
+
is(threw, true)
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
t('rejects null', async () => {
|
|
184
|
+
let threw = false
|
|
185
|
+
try { await decode(null) } catch { threw = true }
|
|
186
|
+
is(threw, true)
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
t('rejects number', async () => {
|
|
190
|
+
let threw = false
|
|
191
|
+
try { await decode(42) } catch { threw = true }
|
|
192
|
+
is(threw, true)
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
t('rejects non-audio buffer', async () => {
|
|
196
|
+
let threw = false
|
|
197
|
+
try { await decode(new Uint8Array(100)) } catch (e) { threw = e.message.includes('Unknown audio') }
|
|
198
|
+
is(threw, true)
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
// -- concurrent decoders --
|
|
202
|
+
|
|
203
|
+
t('concurrent decoding', async () => {
|
|
204
|
+
let [r1, r2, r3] = await Promise.all([
|
|
205
|
+
decode(mp3),
|
|
206
|
+
decode(flac),
|
|
207
|
+
decode(ogg),
|
|
208
|
+
])
|
|
209
|
+
is(near(dur(r1), 12.27), true, 'mp3 concurrent')
|
|
210
|
+
is(near(dur(r2), 12.27), true, 'flac concurrent')
|
|
211
|
+
is(near(dur(r3), 12.27), true, 'ogg concurrent')
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
t('concurrent stream decoders', async () => {
|
|
215
|
+
let [d1, d2] = await Promise.all([decoders.mp3(), decoders.flac()])
|
|
216
|
+
let [r1, r2] = await Promise.all([
|
|
217
|
+
d1.decode(new Uint8Array(mp3)),
|
|
218
|
+
d2.decode(new Uint8Array(flac)),
|
|
219
|
+
])
|
|
220
|
+
is(r1.channelData[0].length > 0, true)
|
|
221
|
+
is(r2.channelData[0].length > 0, true)
|
|
222
|
+
await Promise.all([d1.decode(), d2.decode()])
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
// -- zero-length / edge cases --
|
|
226
|
+
|
|
227
|
+
t('zero-length buffer', async () => {
|
|
228
|
+
let threw = false
|
|
229
|
+
try { await decode(new ArrayBuffer(0)) } catch { threw = true }
|
|
230
|
+
is(threw, true)
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
t('minimal invalid buffer', async () => {
|
|
234
|
+
let threw = false
|
|
235
|
+
try { await decode(new Uint8Array([0])) } catch { threw = true }
|
|
236
|
+
is(threw, true)
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
// -- decodeStream --
|
|
240
|
+
|
|
241
|
+
t('decodeStream mp3', async () => {
|
|
242
|
+
let chunks = [new Uint8Array(mp3)]
|
|
243
|
+
async function* gen() { for (let c of chunks) yield c }
|
|
244
|
+
let total = 0
|
|
245
|
+
for await (let r of decodeStream(gen(), 'mp3')) {
|
|
246
|
+
is(r.sampleRate > 0, true)
|
|
247
|
+
total += r.channelData[0].length
|
|
248
|
+
}
|
|
249
|
+
is(total > 0, true, 'decoded samples')
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
t('decodeStream ReadableStream', async () => {
|
|
253
|
+
let data = new Uint8Array(wav)
|
|
254
|
+
let stream = new ReadableStream({
|
|
255
|
+
start(ctrl) { ctrl.enqueue(data); ctrl.close() }
|
|
256
|
+
})
|
|
257
|
+
let total = 0
|
|
258
|
+
for await (let r of decodeStream(stream, 'wav')) {
|
|
259
|
+
is(r.sampleRate, 44100)
|
|
260
|
+
total += r.channelData[0].length
|
|
261
|
+
}
|
|
262
|
+
is(total > 0, true)
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
t('decodeStream m4a', async () => {
|
|
266
|
+
async function* gen() { yield new Uint8Array(m4a) }
|
|
267
|
+
let total = 0
|
|
268
|
+
for await (let r of decodeStream(gen(), 'm4a')) {
|
|
269
|
+
is(r.sampleRate, 44100)
|
|
270
|
+
total += r.channelData[0].length
|
|
271
|
+
}
|
|
272
|
+
is(total > 0, true)
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
t('decodeStream unknown format', async () => {
|
|
276
|
+
let threw = false
|
|
277
|
+
try { for await (let _ of decodeStream([], 'xyz')) {} } catch { threw = true }
|
|
278
|
+
is(threw, true)
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
// -- decoders extensibility --
|
|
282
|
+
|
|
283
|
+
t('custom decoder registration', async () => {
|
|
284
|
+
decoders.test = async () => ({
|
|
285
|
+
decode: async (chunk) => ({ channelData: [new Float32Array(chunk.length)], sampleRate: 8000 }),
|
|
286
|
+
free() {}
|
|
287
|
+
})
|
|
288
|
+
let dec = await decoders.test()
|
|
289
|
+
let r = await dec.decode(new Uint8Array(10))
|
|
290
|
+
is(r.sampleRate, 8000)
|
|
291
|
+
is(r.channelData[0].length, 10)
|
|
292
|
+
delete decoders.test
|
|
107
293
|
})
|
package/.eslintrc.json
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"env": {
|
|
3
|
-
"browser": true,
|
|
4
|
-
"node": true,
|
|
5
|
-
"commonjs": true,
|
|
6
|
-
"es6": true
|
|
7
|
-
},
|
|
8
|
-
"extends": "eslint:recommended",
|
|
9
|
-
"rules": {
|
|
10
|
-
"strict": 2,
|
|
11
|
-
"indent": 0,
|
|
12
|
-
"linebreak-style": 0,
|
|
13
|
-
"quotes": 0,
|
|
14
|
-
"semi": 0,
|
|
15
|
-
"no-cond-assign": 1,
|
|
16
|
-
"no-constant-condition": 1,
|
|
17
|
-
"no-duplicate-case": 1,
|
|
18
|
-
"no-empty": 1,
|
|
19
|
-
"no-ex-assign": 1,
|
|
20
|
-
"no-extra-boolean-cast": 1,
|
|
21
|
-
"no-extra-semi": 1,
|
|
22
|
-
"no-fallthrough": 1,
|
|
23
|
-
"no-func-assign": 1,
|
|
24
|
-
"no-global-assign": 1,
|
|
25
|
-
"no-implicit-globals": 2,
|
|
26
|
-
"no-inner-declarations": ["error", "functions"],
|
|
27
|
-
"no-irregular-whitespace": 2,
|
|
28
|
-
"no-loop-func": 1,
|
|
29
|
-
"no-multi-str": 1,
|
|
30
|
-
"no-mixed-spaces-and-tabs": 1,
|
|
31
|
-
"no-proto": 1,
|
|
32
|
-
"no-sequences": 1,
|
|
33
|
-
"no-throw-literal": 1,
|
|
34
|
-
"no-unmodified-loop-condition": 1,
|
|
35
|
-
"no-useless-call": 1,
|
|
36
|
-
"no-void": 1,
|
|
37
|
-
"no-with": 2,
|
|
38
|
-
"wrap-iife": 1,
|
|
39
|
-
"no-redeclare": 1,
|
|
40
|
-
"no-unused-vars": ["error", { "vars": "all", "args": "none" }],
|
|
41
|
-
"no-sparse-arrays": 1
|
|
42
|
-
}
|
|
43
|
-
}
|