├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── codec.js
├── compress.js
├── index.html
├── main.js
├── signal.js
└── ss.png
/.gitattributes:
--------------------------------------------------------------------------------
1 | LICENSE text eol=lf encoding=utf-8
2 | *.md text eol=lf encoding=utf-8
3 | *.html text eol=lf encoding=utf-8
4 | *.js text eol=lf encoding=utf-8
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2019 Shuhei Kuno
2 | https://github.com/redlily
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a
5 | copy of this software and associated documentation files (the
6 | "Software"), to deal in the Software without restriction, including
7 | without limitation the rights to use, copy, modify, merge, publish,
8 | distribute, sublicense, and/or sell copies of the Software, and to
9 | permit persons to whom the Software is furnished to do so, subject to
10 | the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Webで動作する音声圧縮の実証実験
2 |
3 | ## 概要
4 |
5 | 修正離散コサイン変換 (Modified Discrete Cosine Transform : MDCT) を使用したWebブラウザ上でJavaScriptを使用して
6 | 現実的な時間のエンコード、デコード可能な軽量な音声圧縮フォーマットの開発を目的とした実証実験用のプログラムです。
7 |
8 | ## Webアプリケーション
9 |
10 | https://redlily.github.io/training-webaudio-compression
11 |
12 |
13 |
14 | ### 使い方
15 |
16 | 1. 音声ファイル(おすすめは無圧縮)を選択してデータを読み込ませます。
17 | 1. 圧縮オプションを選び圧縮を実行、数秒から数十秒で圧縮が完了します。
18 | 1. 圧縮が完了すると再生ボタンとダウンロードリンクが有効になります。
19 | 1. 再生を押すと圧縮データの再生を開始、ダウンロードを押すと圧縮データのダウンロードを行います。
20 | 1. 圧縮したデータはこのプログラムでデータ読み込み、再生が出来ます。
21 |
22 | ## データフォーマット
23 |
24 | ### ヘッダ
25 |
26 | |変数名|型|説明|
27 | |:---|:---|:---|
28 | |MAGIC_NUMBER|UINT32|マジックナンバー、"WAM0"が固定値|
29 | |DATA_SIZE|UINT32|データのバイトサイズ|
30 | |DATA_TYPE|UINT32|拡張用のデータタイプ、"SMD0"が固定値|
31 | |VERSION|UINT32|データのバージョン|
32 | |SAMPLE_RATE|UINT32|サンプリングレート|
33 | |CHANNEL_SIZE|UINT32|チャネル数、1がモノラル、2がステレオ|
34 | |SAMPLE_COUNT|UINT32|データに含まれるサンプル数|
35 | |FREQUENCY_RANGE|UINT16|周波数ブロックのサイズ|
36 | |FREQUENCY_UPPER_LIMIT|UINT16|周波数ブロックの上限値|
37 | |FREQUENCY_TABLE_SIZE|UINT16|周波数テーブルのサイズ|
38 | |-|UINT16|バイトアライメント調整用の領域、今後の拡張次第では何か数値が入るかも|
39 | |FRAME_COUNT|UINT32|データに含まれるフレーム数|
40 | |FRAME_DATA|FRAME[CHANNEL_SIZE * FRAME_COUNT]|フレーム配列、チャネル数が2の場合、左、右とフーレムが並ぶ|
41 |
42 | #### フレーム
43 |
44 | |変数名|型|説明|
45 | |:---|:---|:---|
46 | |MASTER_SCALE|UINT32|このフーレムの主音量|
47 | |SUB_SCALES|UINT4[8]|8つの周波数帯用の音量を調整するためのスケール値|
48 | |ENABLE_FREQUENCIES|1bit[FREQUENCY_UPPER_LIMIT]
or
ceil(log_2(FREQUENCY_UPPER_LIMIT))bit[FREQUENCY_TABLE_SIZE]|周波数の有効無効を収納した1bitのフラグ配列、もしくは有効な周波数のインデックスを収納した配列
バイト数の小さい方を使用し4バイトアライメントに適合するサイズにする|
49 | |FREQUENCY_VALUES|4bit[FREQUENCY_TABLE_SIZE]|有効な周波数の対数で符号化された数値|
50 |
--------------------------------------------------------------------------------
/codec.js:
--------------------------------------------------------------------------------
1 | // サウンドエンコーダとデコーダの実装
2 |
3 | var wamCodec = wamCodec || {};
4 |
5 | (function () {
6 |
7 | // マジックナンバー ORPH sound data format
8 | const MAGIC_NUMBER =
9 | ("O".charCodeAt(0)) | ("R".charCodeAt(0) << 8) | ("P".charCodeAt(0) << 16) | ("H".charCodeAt(0) << 24);
10 | // ファイルタイプ、 Simple Modified discrete cosine transform Data
11 | const FILE_TYPE_SMD0 =
12 | ("S".charCodeAt(0)) | ("M".charCodeAt(0) << 8) | ("D".charCodeAt(0) << 16) | ("0".charCodeAt(0) << 24);
13 | // SMD0形式のバージョン
14 | const SMD0_VERSION = 0;
15 |
16 | // ヘッダオフセット、マジックナンバー
17 | const HEADER_OFFSET_MAGIC_NUMBER = 0;
18 | // ヘッダオフセット、データサイズ
19 | const HEADER_OFFSET_DATA_SIZE = 4;
20 | // ヘッダオフセット、データタイプ、拡張用
21 | const HEADER_OFFSET_DATA_TYPE = 8;
22 | // ヘッダオフセット、バージョン
23 | const HEADER_OFFSET_VERSION = 12;
24 | // ヘッダオフセット、サンプリングレート
25 | const HEADER_OFFSET_SAMPLE_RATE = 16;
26 | // ヘッダオフセット、サンプル数
27 | const HEADER_OFFSET_SAMPLE_COUNT = 20;
28 | // ヘッダオフセット、フレーム数
29 | const HEADER_OFFSET_FRAME_COUNT = 24;
30 | // ヘッダオフセット、チャネル数、1がモノラル、2がステレオ
31 | const HEADER_OFFSET_CHANNEL_SIZE = 28;
32 | // ヘッダオフセット、周波数レンジ、2のべき乗の値を設定する必要がある
33 | const HEADER_OFFSET_FREQUENCY_RANGE = 30;
34 | // ヘッダオフセット、周波数の上限
35 | const HEADER_OFFSET_FREQUENCY_UPPER_LIMIT = 32;
36 | // ヘッダオフセット、周波数テーブルサイズ、32で割れる数を指定すると効率が良い
37 | const HEADER_OFFSET_FREQUENCY_TABLE_SIZE = 34;
38 | // ヘッダオフセット、データ
39 | const HEADER_OFFSET_DATA = 36;
40 |
41 | // フレームヘッダ、オフセット、振幅のメインスケール
42 | const FRAME_OFFSET_MASTER_SCALE = 0;
43 | // フーレムヘッダ、オフセット、振幅のサブスケール、4bitで8つのメインスケールからのスケール値を対数で保持する
44 | const FRAME_OFFSET_SUB_SCALE = 4;
45 | // フレームヘッダ、オフセット、データ
46 | const FRAME_OFFSET_DATA = 8;
47 |
48 | // 対数による量子化で使用する対数の底
49 | const BASE_OF_LOGARITHM = 2;
50 |
51 | // アサート
52 | function assert(test, message) {
53 | if (!test) throw new Error(message || "Failed to test.");
54 | }
55 |
56 | // Web Audio Media コーダ
57 | class WamCoder {
58 |
59 | constructor() {
60 | this.data = null;
61 | this.frameCount = 0;
62 | this.numChannels = 0;
63 | this.frequencyRange = 0;
64 | this.frequencyUpperLimit = 0;
65 | this.frequencyTableSize = 0;
66 | this.subScales = null;
67 | this.windowFunction = null;
68 | this.samples = null;
69 | this.indexBitSize = 0;
70 | this.indicesSize = 0;
71 | this.isIndexMode = false;
72 | }
73 |
74 | readHalfUbyte(offset, index) {
75 | return 0xf & (this.data.getUint8(offset) >>> (index << 2));
76 | }
77 |
78 | writeHalfUbyte(offset, index, value) {
79 | this.data.setUint8(
80 | offset,
81 | (0xff & (this.data.getUint8(offset) & ~(0xf << (index << 2)))) | ((0xf & value) << (index << 2)));
82 | }
83 |
84 | // 窓関数となる配列を生成、窓の種類はVorbis窓
85 | setupWindowFunction() {
86 | this.windowFunction = new Float32Array(this.frequencyRange << 1);
87 | for (let i = 0; i < this.frequencyRange; ++i) {
88 | let value = Math.sin(Math.PI / 2 * Math.pow(Math.sin(Math.PI * (i / ((this.frequencyRange << 1) - 1))), 2));
89 | this.windowFunction[i] = value;
90 | this.windowFunction[(this.frequencyRange << 1) - 1 - i] = value;
91 | }
92 | }
93 |
94 | // 窓関数をサンプルに適用する
95 | applyWindowFunction() {
96 | for (let i = 0; i < this.frequencyRange << 1; ++i) {
97 | this.samples[i] *= this.windowFunction[i];
98 | }
99 | }
100 |
101 | getDataOffset(frame, channel) {
102 | return HEADER_OFFSET_DATA +
103 | (FRAME_OFFSET_DATA +
104 | (this.isIndexMode ? (this.indicesSize / 8) : (this.frequencyUpperLimit / 8)) +
105 | (this.frequencyTableSize >>> 1)) *
106 | (this.numChannels * frame + channel);
107 | }
108 | }
109 |
110 | // Web Audio Media エンコーダ
111 | class WamEncoder extends WamCoder {
112 |
113 | constructor(sampleRate, numChannels, frequencyRange, frequencyUpperLimit, frequencyTableSize, initSampleCount = 4096) {
114 | super();
115 |
116 | this.sampleRate = sampleRate;
117 | this.numChannels = numChannels;
118 | this.frequencyRange = frequencyRange != null ? frequencyRange : 1024;
119 | this.frequencyUpperLimit = frequencyUpperLimit != null ? frequencyUpperLimit : this.frequencyRange;
120 | this.frequencyTableSize = frequencyTableSize != null ? frequencyTableSize : this.frequencyRange >>> 2;
121 |
122 | assert(this.sampleRate > 0);
123 | assert(this.numChannels > 0);
124 | assert(this.frequencyRange > 0);
125 | assert(this.frequencyRange % 32 == 0); // 効率を重視して32の倍数である必要がある
126 | assert(this.frequencyUpperLimit <= frequencyRange);
127 | assert(this.frequencyTableSize > 0);
128 | assert(this.frequencyTableSize % 8 == 0); // バイト境界を考慮して8の倍数である必要がある
129 |
130 | let initBufferSize = HEADER_OFFSET_DATA +
131 | (FRAME_OFFSET_DATA + (this.frequencyRange / 32) * 4 + this.frequencyTableSize) *
132 | this.numChannels * Math.ceil(initSampleCount / this.frequencyRange);
133 |
134 | this.data = new DataView(new ArrayBuffer(initBufferSize));
135 | this.data.setUint32(HEADER_OFFSET_MAGIC_NUMBER, MAGIC_NUMBER);
136 | this.data.setUint32(HEADER_OFFSET_DATA_SIZE, 0);
137 | this.data.setUint32(HEADER_OFFSET_DATA_TYPE, FILE_TYPE_SMD0);
138 | this.data.setUint32(HEADER_OFFSET_VERSION, SMD0_VERSION);
139 | this.data.setUint32(HEADER_OFFSET_SAMPLE_RATE, this.sampleRate);
140 | this.data.setUint32(HEADER_OFFSET_SAMPLE_COUNT, 0);
141 | this.data.setUint32(HEADER_OFFSET_FRAME_COUNT, 0);
142 | this.data.setUint16(HEADER_OFFSET_CHANNEL_SIZE, this.numChannels);
143 | this.data.setUint16(HEADER_OFFSET_FREQUENCY_RANGE, this.frequencyRange);
144 | this.data.setUint16(HEADER_OFFSET_FREQUENCY_UPPER_LIMIT, this.frequencyUpperLimit);
145 | this.data.setUint16(HEADER_OFFSET_FREQUENCY_TABLE_SIZE, this.frequencyTableSize);
146 |
147 | this.setupWindowFunction();
148 |
149 | this.indexBitSize = Math.ceil(Math.log2(this.frequencyUpperLimit));
150 | this.indicesSize = Math.ceil(this.indexBitSize * this.frequencyTableSize / 32) * 32;
151 | this.isIndexMode = (1 << this.indexBitSize) > this.indicesSize;
152 | this.subScales = new Uint8Array(Math.min(this.indexBitSize, 8));
153 | this.subScaleStart = this.frequencyUpperLimit / (1 << Math.min(Math.ceil(Math.log2(this.frequencyUpperLimit)), 7));
154 | this.frequencyFlags = new Uint32Array(this.frequencyUpperLimit / 32);
155 | this.frequencies = new Float32Array(this.frequencyRange);
156 | this.frequencyPowers = new Float32Array(this.frequencyUpperLimit);
157 | this.samples = new Float32Array(this.frequencyRange << 1);
158 | this.prevInputs = new Array(this.numChannels);
159 | for (let i = 0; i < this.numChannels; ++i) {
160 | this.prevInputs[i] = new Float32Array(this.frequencyRange);
161 | }
162 | this.workBuffers = new Array(this.numChannels);
163 | for (let i = 0; i < this.numChannels; ++i) {
164 | this.workBuffers[i] = new Float32Array(this.frequencyRange);
165 | }
166 | this.workBufferOffset = 0;
167 | }
168 |
169 | writeFrame(inputData, start = 0, length = this.frequencyRange) {
170 | assert(inputData.length >= this.numChannels);
171 | assert(length <= this.frequencyRange && length >= 0);
172 |
173 | this.nextFrame();
174 | for (let i = 0; i < this.numChannels; ++i) {
175 | let input = inputData[i];
176 | let dataOffset = this.getDataOffset(this.frameCount - 1, i);
177 |
178 | // 前回の入力を処理バッファの前半に充填
179 | let prevInput = this.prevInputs[i];
180 | for (let j = 0; j < this.frequencyRange; ++j) {
181 | this.samples[j] = prevInput[j];
182 | }
183 |
184 | // 今回の入力を処理バッファの後半に充填し、次回の処理に備え保存
185 | for (let j = 0; j < length; ++j) {
186 | let value = input[start + j] * ((1 << 16) - 1); // [-1, 1]の数値を16bitの数値にスケール
187 | this.samples[this.frequencyRange + j] = value;
188 | prevInput[j] = value;
189 | }
190 | for (let j = length; j < this.frequencyRange; ++j) {
191 | this.samples[this.frequencyRange + j] = 0;
192 | prevInput[j] = 0;
193 | }
194 |
195 | // 窓関数をかける
196 | this.applyWindowFunction();
197 |
198 | // MDCTをかける
199 | FastMDCT.mdct(this.frequencyRange, this.samples, this.frequencies);
200 |
201 | // 振幅のマスタスケールを書き出し
202 | let masterScale = 1;
203 | for (let j = 0; j < this.frequencyUpperLimit; ++j) {
204 | let power = Math.abs(this.frequencies[j]);
205 | if (power > masterScale) {
206 | masterScale = power;
207 | }
208 | }
209 | this.data.setUint32(dataOffset + FRAME_OFFSET_MASTER_SCALE, masterScale);
210 |
211 | // 振幅のサブスケールを書き出す
212 | for (let j = 0; j < this.subScales.length; ++j) {
213 | let subScale = 1;
214 | for (let k = j == 0 ? 0 : this.subScaleStart << (j - 1); k < this.subScaleStart << j && k < this.frequencyUpperLimit; ++k) {
215 | let power = Math.abs(this.frequencies[k]);
216 | if (power > subScale) {
217 | subScale = power;
218 | }
219 | }
220 | let power = Math.floor(Math.min(-Math.log(subScale / masterScale) / Math.log(BASE_OF_LOGARITHM) * 2, 15));
221 | this.subScales[j] = power;
222 | this.writeHalfUbyte(dataOffset + FRAME_OFFSET_SUB_SCALE + (j >>> 1), 0x1 & j, power);
223 | }
224 |
225 | // 各周波数のパワーを計算しておく
226 | for (let j = 0; j < this.subScales.length; ++j) {
227 | let subScale = this.subScales[j];
228 | for (let k = j == 0 ? 0 : this.subScaleStart << (j - 1); k < this.subScaleStart << j && k < this.frequencyUpperLimit; ++k) {
229 | let power = Math.abs(this.frequencies[k]) / masterScale;
230 | this.frequencyPowers[k] = power > Math.pow(BASE_OF_LOGARITHM, -7 - subScale * 0.5) ? power : 0;
231 | }
232 | }
233 |
234 | // 書き出す周波数を選択
235 | this.frequencyFlags.fill(0);
236 | let writeCount = 0;
237 | while (writeCount < this.frequencyTableSize) {
238 | let sumPower = 0;
239 | for (let j = 0; j < this.frequencyUpperLimit; ++j) {
240 | sumPower += this.frequencyPowers[j];
241 | }
242 | if (sumPower <= 0) {
243 | break;
244 | }
245 |
246 | let sum = 0;
247 | let maxIndex = this.frequencyUpperLimit - 1;
248 | let maxPower = this.frequencyPowers[maxIndex];
249 | for (let j = this.frequencyUpperLimit - 1; j >= 0 && writeCount < this.frequencyTableSize; --j) {
250 | let power = this.frequencyPowers[j];
251 | sum += power;
252 |
253 | if (power > maxPower) {
254 | maxPower = power;
255 | maxIndex = j;
256 | }
257 |
258 | if (sum >= sumPower / this.frequencyTableSize) {
259 | this.frequencyFlags[Math.floor(maxIndex / 32)] |= 1 << (maxIndex % 32);
260 | this.frequencyPowers[maxIndex] = 0;
261 | writeCount++;
262 |
263 | sum = 0;
264 | maxIndex = j - 1;
265 | maxPower = this.frequencyPowers[maxIndex];
266 | }
267 | }
268 | }
269 |
270 | // 周波数フラグを書き出し
271 | dataOffset += FRAME_OFFSET_DATA;
272 | if (this.isIndexMode) {
273 | // 有効な周波数をインデックスで書き出す
274 | let value = 0;
275 | let index = 0;
276 | for (let j = 0; j < this.frequencyRange; ++j) {
277 | if ((this.frequencyFlags[Math.floor(j / 32)] >>> j % 32) & 0x1 != 0) {
278 | value |= j << index;
279 | index += this.indexBitSize;
280 | if (index >= 32) {
281 | this.data.setUint32(dataOffset, value);
282 | dataOffset += 4;
283 | index %= 32;
284 | value = j >> (this.indexBitSize - index);
285 | }
286 | }
287 | }
288 | if (index != 0) {
289 | this.data.setUint32(dataOffset, value);
290 | dataOffset += 4;
291 | }
292 | } else {
293 | // 有効な周波数を1bitのフラグで書き出す
294 | for (let j = 0; j < this.frequencyFlags.length; ++j) {
295 | this.data.setUint32(dataOffset, this.frequencyFlags[j]);
296 | dataOffset += 4;
297 | }
298 | }
299 |
300 | // MDCT用の周波数配列から必要な分を周波数テーブルへ書き出し
301 | let frequencyOffset = 0;
302 | for (let j = 0; j < this.subScales.length; ++j) {
303 | let subScale = this.subScales[j];
304 | for (let k = j == 0 ? 0 : this.subScaleStart << (j - 1); k < this.subScaleStart << j && k < this.frequencyRange; ++k) {
305 | if ((this.frequencyFlags[Math.floor(k / 32)] >>> (k % 32)) & 0x1 != 0) {
306 | let value = this.frequencies[k] / masterScale;
307 | let signed = value >= 0 ? 0x0 : 0x8;
308 | let power = Math.ceil(Math.min(-Math.log(Math.abs(value)) / Math.log(BASE_OF_LOGARITHM) - subScale * 0.5, 7));
309 | this.writeHalfUbyte(
310 | dataOffset + (frequencyOffset >>> 1),
311 | 0x1 & frequencyOffset,
312 | signed | power);
313 | frequencyOffset += 1;
314 | }
315 | }
316 | }
317 | }
318 | this.sampleCount += length;
319 | }
320 |
321 | nextFrame() {
322 | this.frameCount++;
323 | if (this.getDataSize() > this.data.buffer.byteLength) {
324 | let buffer = new ArrayBuffer(this.data.buffer.byteLength << 1);
325 | new Uint8Array(buffer).set(new Uint8Array(this.data.buffer));
326 | this.data = new DataView(buffer);
327 | }
328 | }
329 |
330 | write(inputData, start = 0, length = this.frequencyRange) {
331 | assert(inputData.length >= this.numChannels);
332 |
333 | // 書き込み出来ていないサンプルを書き込む
334 | if (this.workBufferOffset > 0) {
335 | let writeSize = Math.min(this.frequencyRange - this.workBufferOffset, length);
336 | for (let i = 0; i < this.numChannels; ++i) {
337 | let input = inputData[i];
338 | let workBuffer = this.workBuffers[i];
339 | for (let j = 0; j < writeSize; ++j) {
340 | workBuffer[this.workBufferOffset + j] = input[start + j];
341 | }
342 | }
343 | start += writeSize;
344 | length -= writeSize;
345 | this.workBufferOffset += writeSize;
346 | if (this.workBufferOffset >= this.frequencyRange) {
347 | this.writeFrame(this.workBuffers);
348 | this.workBufferOffset = 0;
349 | }
350 | }
351 |
352 | // 入力バッファをフレーム単位で読み込む
353 | while (length >= this.frequencyRange) {
354 | this.writeFrame(inputData, start);
355 | start += this.frequencyRange;
356 | length -= this.frequencyRange;
357 | }
358 |
359 | // まだ入力バッファに書き込むデータが残っている場合
360 | if (length > 0) {
361 | for (let i = 0; i < this.numChannels; ++i) {
362 | let input = inputData[i];
363 | let workBuffer = this.workBuffers[i];
364 | for (let j = 0; j < length; ++j) {
365 | workBuffer[j] = input[start + j];
366 | }
367 | }
368 | this.workBufferOffset = length;
369 | }
370 | }
371 |
372 | flush() {
373 | if (this.workBufferOffset > 0) {
374 | for (let i = 0; i < this.numChannels; ++i) {
375 | this.workBuffers[i].fill(0, this.workBufferOffset, this.frequencyRange);
376 | }
377 | this.writeFrame(this.workBuffers);
378 | this.workBufferOffset = 0;
379 | }
380 | }
381 |
382 | getDataSize() {
383 | return this.getDataOffset(this.frameCount, 0);
384 | }
385 |
386 | getDataBuffer() {
387 | let dataSize = this.getDataSize();
388 | this.data.setUint32(HEADER_OFFSET_DATA_SIZE, dataSize);
389 | this.data.setUint32(HEADER_OFFSET_SAMPLE_COUNT, this.frequencyRange * this.frameCount);
390 | this.data.setUint32(HEADER_OFFSET_FRAME_COUNT, this.frameCount);
391 | return this.data.buffer.slice(0, this.getDataSize());
392 | }
393 | }
394 |
395 | wamCodec.WamEncoder = WamEncoder;
396 |
397 | // Web Audio Media デコーダ
398 | class WamDecoder extends WamCoder {
399 |
400 | static isWamData(data) {
401 | return new DataView(data).getUint32(HEADER_OFFSET_MAGIC_NUMBER) == MAGIC_NUMBER;
402 | }
403 |
404 | constructor(data) {
405 | super();
406 |
407 | this.data = new DataView(data);
408 | this.magicNumber = this.data.getUint32(HEADER_OFFSET_MAGIC_NUMBER);
409 | this.fileSize = this.data.getUint32(HEADER_OFFSET_DATA_SIZE);
410 | this.fileType = this.data.getUint32(HEADER_OFFSET_DATA_TYPE);
411 | this.version = this.data.getUint32(HEADER_OFFSET_VERSION);
412 | this.sampleRate = this.data.getUint32(HEADER_OFFSET_SAMPLE_RATE);
413 | this.sampleCount = this.data.getUint32(HEADER_OFFSET_SAMPLE_COUNT);
414 | this.frameCount = this.data.getUint32(HEADER_OFFSET_FRAME_COUNT);
415 | this.numChannels = this.data.getUint16(HEADER_OFFSET_CHANNEL_SIZE);
416 | this.frequencyRange = this.data.getUint16(HEADER_OFFSET_FREQUENCY_RANGE);
417 | this.frequencyUpperLimit = this.data.getUint16(HEADER_OFFSET_FREQUENCY_UPPER_LIMIT);
418 | this.frequencyTableSize = this.data.getUint16(HEADER_OFFSET_FREQUENCY_TABLE_SIZE);
419 |
420 | assert(this.magicNumber == MAGIC_NUMBER);
421 | assert(this.fileSize <= data.byteLength);
422 | assert(this.fileType == FILE_TYPE_SMD0);
423 | assert(this.version == 0);
424 | assert(this.sampleRate > 0);
425 | assert(this.sampleCount <= this.frequencyRange * this.frameCount);
426 | assert(this.numChannels > 0);
427 | assert(this.frequencyRange > 0);
428 | assert(this.frequencyUpperLimit <= this.frequencyRange);
429 | assert(this.frequencyTableSize > 0);
430 |
431 | this.setupWindowFunction();
432 |
433 | this.indexBitSize = Math.ceil(Math.log2(this.frequencyUpperLimit));
434 | this.indicesSize = Math.ceil(this.indexBitSize * this.frequencyTableSize / 32) * 32;
435 | this.isIndexMode = (1 << this.indexBitSize) > this.indicesSize;
436 | this.subScales = new Uint8Array(Math.min(this.indexBitSize, 8));
437 | this.subScaleStart = this.frequencyUpperLimit / (1 << Math.min(Math.ceil(Math.log2(this.frequencyUpperLimit)), 7));
438 | this.frequencyFlags = new Uint32Array(this.frequencyUpperLimit / 32);
439 | this.frequencies = new Float32Array(this.frequencyRange);
440 | this.samples = new Float32Array(this.frequencyRange << 1);
441 | this.prevOutputs = new Array(this.numChannels);
442 | for (let i = 0; i < this.numChannels; ++i) {
443 | this.prevOutputs[i] = new Float32Array(this.frequencyRange);
444 | }
445 | this.currentFrame = 0;
446 | this.workBuffers = new Array(this.numChannels);
447 | for (let i = 0; i < this.numChannels; ++i) {
448 | this.workBuffers[i] = new Float32Array(this.frequencyRange);
449 | }
450 | this.workBufferOffset = this.frequencyRange;
451 | }
452 |
453 | read(outputData, start = 0, length = this.frequencyRange) {
454 | assert(outputData.length >= this.numChannels);
455 |
456 | // 書き込み出来ていないサンプルを出力バッファ書き込む
457 | if (this.workBufferOffset < this.frequencyRange) {
458 | let writeSize = Math.min(length, this.frequencyRange - this.workBufferOffset);
459 | for (let i = 0; i < this.numChannels; ++i) {
460 | let output = outputData[i];
461 | let workBuffer = this.workBuffers[i];
462 | for (let j = 0; j < writeSize; ++j) {
463 | output[start + j] = workBuffer[this.workBufferOffset + j];
464 | }
465 | }
466 | start += writeSize;
467 | length -= writeSize;
468 | this.workBufferOffset += writeSize;
469 | }
470 |
471 | // 出力バッファにフレーム単位で読み込む
472 | while (length >= this.frequencyRange) {
473 | this.readFrame(outputData, start);
474 | start += this.frequencyRange;
475 | length -= this.frequencyRange;
476 | }
477 |
478 | // まだ出力バッファに書き込みきれていない場合
479 | if (length > 0) {
480 | this.readFrame(this.workBuffers, 0);
481 | for (let i = 0; i < this.numChannels; ++i) {
482 | let output = outputData[i];
483 | let workBuffer = this.workBuffers[i];
484 | for (let j = 0; j < length; ++j) {
485 | output[start + j] = workBuffer[j];
486 | }
487 | }
488 | this.workBufferOffset = length;
489 | }
490 | }
491 |
492 | readFrame(outputData, start = 0, length = this.frequencyRange) {
493 | assert(outputData.length >= this.numChannels);
494 | assert(length <= this.frequencyRange && length >= 0);
495 |
496 | for (let i = 0; i < this.numChannels; ++i) {
497 | let output = outputData[i];
498 | let dataOffset = this.getDataOffset(this.currentFrame, i);
499 |
500 | // 振幅のマスタボリュームを取得
501 | let masterVolume = this.data.getUint32(dataOffset + FRAME_OFFSET_MASTER_SCALE);
502 |
503 | // 振幅のサブスケールを取得
504 | for (let j = 0; j < this.subScales.length; ++j) {
505 | this.subScales[j] = this.readHalfUbyte(dataOffset + FRAME_OFFSET_SUB_SCALE + (j >>> 1), 0x1 & j);
506 | }
507 |
508 | // 周波数フラグを取得
509 | dataOffset += FRAME_OFFSET_DATA;
510 | if (this.isIndexMode) {
511 | // 有効な周波数をインデックスで判別
512 | this.frequencyFlags.fill(0);
513 | let index = 0;
514 | let mask = (1 << this.indexBitSize) - 1;
515 | let value = this.data.getUint32(dataOffset);
516 | dataOffset += 4;
517 | for (let j = 0; j < this.frequencyTableSize; ++j) {
518 | let bitIndex = mask & value;
519 | value >>>= this.indexBitSize;
520 | index += this.indexBitSize;
521 | if (index > 32) {
522 | value = this.data.getUint32(dataOffset);
523 | dataOffset += 4;
524 | index %= 32;
525 | bitIndex |= mask & (value << (this.indexBitSize - index));
526 | value >>>= index;
527 | }
528 | this.frequencyFlags[Math.floor(bitIndex / 32)] |= 1 << (bitIndex % 32);
529 | }
530 | } else {
531 | // 有効な周波数を1bitのフラグで判別
532 | for (let j = 0; j < this.frequencyFlags.length; ++j) {
533 | this.frequencyFlags[j] = this.data.getUint32(dataOffset);
534 | dataOffset += 4;
535 | }
536 | }
537 |
538 | // 周波数テーブルを取得、MDCT用の周波数配列に書き込み
539 | this.frequencies.fill(0);
540 | let frequencyOffset = 0;
541 | for (let j = 0; j < this.subScales.length; ++j) {
542 | let subScale = this.subScales[j];
543 | for (let k = j == 0 ? 0 : this.subScaleStart << (j - 1); k < this.subScaleStart << j && k < this.frequencyUpperLimit; ++k) {
544 | if ((this.frequencyFlags[Math.floor(k / 32)] >>> k % 32) & 0x1 != 0) {
545 | let value = this.readHalfUbyte(dataOffset + (frequencyOffset >>> 1), 0x1 & frequencyOffset);
546 | let signed = 0x8 & value;
547 | let power = Math.pow(BASE_OF_LOGARITHM, -(0x7 & value) - subScale * 0.5) * masterVolume;
548 | this.frequencies[k] = signed == 0 ? power : -power;
549 | frequencyOffset += 1;
550 | }
551 | }
552 | }
553 |
554 | // 逆MDCTをかける
555 | FastMDCT.imdct(this.frequencyRange, this.samples, this.frequencies);
556 |
557 | // 窓関数をかける
558 | this.applyWindowFunction();
559 |
560 | // 前回の後半の計算結果と今回の前半の計算結果をクロスフェードして出力
561 | let prevOutput = this.prevOutputs[i];
562 | for (let j = 0; j < length; ++j) {
563 | output[start + j] = prevOutput[j] + this.samples[j] / ((1 << 16) - 1); // 16bitの数値を[-1, 1]の数値にスケール
564 | prevOutput[j] = this.samples[this.frequencyRange + j] / ((1 << 16) - 1);
565 | }
566 | for (let j = length; j < this.frequencyRange; ++j) {
567 | prevOutput[j] = this.samples[this.frequencyRange + j] / ((1 << 16) - 1);
568 | }
569 | }
570 | this.nextFrame();
571 | }
572 |
573 | nextFrame() {
574 | this.currentFrame = (this.currentFrame + 1) % this.frameCount;
575 | }
576 | }
577 |
578 | wamCodec.WamDcoder = WamDecoder;
579 |
580 | })();
581 |
--------------------------------------------------------------------------------
/compress.js:
--------------------------------------------------------------------------------
1 | // 音声圧縮処理を行うためのWorker
2 |
3 | importScripts("signal.js");
4 | importScripts("codec.js");
5 |
6 | self.addEventListener("message", (message) => {
7 | // パラメータ取得
8 | let sampleRate = message.data["sampleRate"];
9 | let channelSize = message.data["numChannels"];
10 | let frequencyRange = message.data["frequencyRange"];
11 | let frequencyUpperLimit = message.data["frequencyUpperLimit"];
12 | let frequencyTableSize = message.data["frequencyTableSize"];
13 | let originalSampleRate = message.data["originalSampleRate"];
14 | let originalChannelSize = message.data["originalChannelSize"];
15 | let originalSampleData = message.data["originalSampleData"];
16 | let originalSampleCount = originalSampleData[0].length;
17 | let sampleCount = originalSampleCount;
18 |
19 | // サンプリングレートが元データと異なる場合
20 | if (sampleRate < originalSampleRate) {
21 | // 減らす場合
22 | let times = originalSampleRate / sampleRate;
23 | sampleCount = Math.floor(sampleCount / times);
24 | for (let i = 0; i < originalChannelSize; ++i) {
25 | let samples = originalSampleData[i];
26 |
27 | for (let j = 0; j < originalSampleCount; ++j) {
28 | samples[j] = samples[Math.floor(j * times)];
29 | }
30 | }
31 | } else if (sampleRate > originalSampleRate) {
32 | // 増やす場合、増やさない
33 | sampleRate = originalSampleRate;
34 | }
35 |
36 | // チャネル数が元データと異なる場合
37 | if (channelSize == 1 && originalChannelSize == 2) {
38 | // 減らす場合
39 | let left = originalSampleData[0];
40 | let right = originalSampleData[1];
41 | for (let i = 0; i < sampleCount; ++i) {
42 | left[i] += right[i];
43 | }
44 | } else if (channelSize > sampleCount) {
45 | // 増やす場合、増やさない
46 | channelSize = originalChannelSize;
47 | }
48 |
49 | console.log(`encoding`);
50 | console.log(`sample rate ${sampleRate}`);
51 | console.log(`channel size ${channelSize}`);
52 | console.log(`frequency range ${frequencyRange}`);
53 | console.log(`frequency upper limit ${frequencyUpperLimit}`);
54 | console.log(`frequency table size ${frequencyTableSize}`);
55 | console.log(`sample count ${sampleCount}`);
56 |
57 | // エンコード
58 | let encoder = new wamCodec.WamEncoder(
59 | sampleRate, channelSize,
60 | frequencyRange, frequencyUpperLimit, frequencyTableSize,
61 | sampleCount);
62 | for (let k = 0; k < (sampleCount / frequencyRange) - 1; ++k) {
63 | encoder.write(originalSampleData, frequencyRange * k, Math.min(frequencyRange, sampleCount - frequencyRange * (k + 1)));
64 | self.postMessage({
65 | "kind": "update",
66 | "progress": (k * frequencyRange) / sampleCount
67 | });
68 | }
69 | encoder.flush();
70 | self.postMessage({
71 | "kind": "update",
72 | "progress": 1.0
73 | });
74 |
75 | // 結果を返す
76 | let encodedBuffer = encoder.getDataBuffer();
77 | self.postMessage({
78 | "kind": "completed",
79 | "encodedBuffer": encodedBuffer,
80 | }, [encodedBuffer]);
81 | });
82 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
24 | 入力ファイル 25 | | 26 |27 | 28 | | 29 |
32 | | 33 |34 | すでにORPH形式で圧縮済みのデータです。 35 | | 36 |
39 | 40 | |
41 | |
46 | チャネル数 47 | | 48 |49 | 55 | | 56 |
59 | サンプリングレート 60 | | 61 |62 | 72 | | 73 |
76 | 処理ブロックサイズ 77 | | 78 |79 | 89 | | 90 |
93 | 周波数の上限 94 | | 95 |96 | 108 | | 109 |
112 | 周波数テーブルサイズ 113 | | 114 |115 | 132 | | 133 |
136 | ビットレート 137 | | 138 |139 | N/S 140 | | 141 |
144 | 145 | |
146 | |
151 | | 152 |153 | 154 | | 155 |
158 | 圧縮処理の進捗 159 | | 160 |161 | 0% 162 | | 163 |
166 | 167 | |
168 | |
173 | | 174 |175 | 176 | | 177 |
180 | | 181 |182 | ダウンロード 183 | | 184 |