Completed
Push — master ( 4a50f5...b7cf3e )
by Rafael S.
01:57
created

wavio.js ➔ getFmtExtensionBytes_   A

Complexity

Conditions 5
Paths 16

Size

Total Lines 24
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 18
c 1
b 0
f 0
nc 16
dl 0
loc 24
rs 9.0333
nop 0
1
/*
2
 * Copyright (c) 2017-2018 Rafael da Silva Rocha.
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining
5
 * a 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
 *
23
 */
24
25
/**
26
 * @fileoverview A class to read and write wav data.
27
 * @see https://github.com/rochars/wavefile
28
 */
29
30
import riffChunks from '../vendor/riff-chunks.js';
31
import {pack, unpackFrom, unpackString,
32
  packStringTo, packTo, packString,} from '../vendor/byte-data.js';
33
34
// @type {WavStruct}
35
import WavStruct from './wavstruct.js';
36
37
/**
38
 * A class to read and write wav data.
39
 * @extends WavStruct
40
 */
41
export default class WavIO extends WavStruct {
42
43
  constructor() {
44
    super();
45
    /**
46
     * @type {!Object}
47
     * @private
48
     */
49
    this.uInt16_ = {bits: 16, be: false};
50
    /**
51
     * @type {!Object}
52
     * @private
53
     */
54
    this.uInt32_ = {bits: 32, be: false};
55
    /**
56
     * The bit depth code according to the samples.
57
     * @type {string}
58
     */
59
    this.bitDepth = '0';
60
    /**
61
     * Audio formats.
62
     * Formats not listed here will be set to 65534
63
     * and treated as WAVE_FORMAT_EXTENSIBLE
64
     * @enum {number}
65
     * @private
66
     */
67
    this.audioFormats_ = {
68
      '4': 17,
69
      '8': 1,
70
      '8a': 6,
71
      '8m': 7,
72
      '16': 1,
73
      '24': 1,
74
      '32': 1,
75
      '32f': 3,
76
      '64': 3
77
    };
78
    /**
79
     * @type {number}
80
     * @private
81
     */
82
    this.head_ = 0;
83
    /**
84
     * @type {!Object}
85
     * @private
86
     */
87
    this.dataType = {};
88
  }
89
90
  /**
91
   * Write a variable size string as bytes. If the string is smaller
92
   * than the max size the output array is filled with 0s.
93
   * @param {string} str The string to be written as bytes.
94
   * @param {number} maxSize the max size of the string.
95
   * @return {!Array<number>} The bytes.
96
   * @private
97
   */
98
  writeString_(str, maxSize, push=true) {
99
    /** @type {!Array<number>} */   
100
    let bytes = packString(str);
101
    if (push) {
102
      for (let i=bytes.length; i<maxSize; i++) {
103
        bytes.push(0);
104
      }  
105
    }
106
    return bytes;
107
  }
108
109
  /**
110
   * Return the bytes of the 'bext' chunk.
111
   * @return {!Array<number>} The 'bext' chunk bytes.
112
   * @private
113
   */
114
  getBextBytes_() {
115
    /** @type {!Array<number>} */
116
    let bytes = [];
117
    this.enforceBext_();
118
    if (this.bext.chunkId) {
119
      this.bext.chunkSize = 602 + this.bext.codingHistory.length;
120
      bytes = bytes.concat(
121
        packString(this.bext.chunkId),
122
        pack(602 + this.bext.codingHistory.length, this.uInt32_),
123
        this.writeString_(this.bext.description, 256),
124
        this.writeString_(this.bext.originator, 32),
125
        this.writeString_(this.bext.originatorReference, 32),
126
        this.writeString_(this.bext.originationDate, 10),
127
        this.writeString_(this.bext.originationTime, 8),
128
        pack(this.bext.timeReference[0], this.uInt32_),
129
        pack(this.bext.timeReference[1], this.uInt32_),
130
        pack(this.bext.version, this.uInt16_),
131
        this.writeString_(this.bext.UMID, 64),
132
        pack(this.bext.loudnessValue, this.uInt16_),
133
        pack(this.bext.loudnessRange, this.uInt16_),
134
        pack(this.bext.maxTruePeakLevel, this.uInt16_),
135
        pack(this.bext.maxMomentaryLoudness, this.uInt16_),
136
        pack(this.bext.maxShortTermLoudness, this.uInt16_),
137
        this.writeString_(this.bext.reserved, 180),
138
        this.writeString_(
139
          this.bext.codingHistory, this.bext.codingHistory.length));
140
    }
141
    return bytes;
142
  }
143
144
  /**
145
   * Make sure a 'bext' chunk is created if BWF data was created in a file.
146
   * @private
147
   */
148
  enforceBext_() {
149
    for (var prop in this.bext) {
150
      if (this.bext.hasOwnProperty(prop)) {
151
        if (this.bext[prop] && prop != 'timeReference') {
152
          this.bext.chunkId = 'bext';
153
          break;
154
        }
155
      }
156
    }
157
    if (this.bext.timeReference[0] || this.bext.timeReference[1]) {
158
      this.bext.chunkId = 'bext';
159
    }
160
  }
161
162
  /**
163
   * Return the bytes of the 'ds64' chunk.
164
   * @return {!Array<number>} The 'ds64' chunk bytes.
165
   * @private
166
   */
167
  getDs64Bytes_() {
168
    /** @type {!Array<number>} */
169
    let bytes = [];
170
    if (this.ds64.chunkId) {
171
      bytes = bytes.concat(
172
        packString(this.ds64.chunkId),
173
        pack(this.ds64.chunkSize, this.uInt32_),
174
        pack(this.ds64.riffSizeHigh, this.uInt32_),
175
        pack(this.ds64.riffSizeLow, this.uInt32_),
176
        pack(this.ds64.dataSizeHigh, this.uInt32_),
177
        pack(this.ds64.dataSizeLow, this.uInt32_),
178
        pack(this.ds64.originationTime, this.uInt32_),
179
        pack(this.ds64.sampleCountHigh, this.uInt32_),
180
        pack(this.ds64.sampleCountLow, this.uInt32_));
181
    }
182
    //if (this.ds64.tableLength) {
183
    //  ds64Bytes = ds64Bytes.concat(
184
    //    pack(this.ds64.tableLength, this.uInt32_),
185
    //    this.ds64.table);
186
    //}
187
    return bytes;
188
  }
189
190
  /**
191
   * Return the bytes of the 'cue ' chunk.
192
   * @return {!Array<number>} The 'cue ' chunk bytes.
193
   * @private
194
   */
195
  getCueBytes_() {
196
    /** @type {!Array<number>} */
197
    let bytes = [];
198
    if (this.cue.chunkId) {
199
      /** @type {!Array<number>} */
200
      let cuePointsBytes = this.getCuePointsBytes_();
201
      bytes = bytes.concat(
202
        packString(this.cue.chunkId),
203
        pack(cuePointsBytes.length + 4, this.uInt32_),
204
        pack(this.cue.dwCuePoints, this.uInt32_),
205
        cuePointsBytes);
206
    }
207
    return bytes;
208
  }
209
210
  /**
211
   * Return the bytes of the 'cue ' points.
212
   * @return {!Array<number>} The 'cue ' points as an array of bytes.
213
   * @private
214
   */
215
  getCuePointsBytes_() {
216
    /** @type {!Array<number>} */
217
    let points = [];
218
    for (let i=0; i<this.cue.dwCuePoints; i++) {
219
      points = points.concat(
220
        pack(this.cue.points[i].dwName, this.uInt32_),
221
        pack(this.cue.points[i].dwPosition, this.uInt32_),
222
        packString(this.cue.points[i].fccChunk),
223
        pack(this.cue.points[i].dwChunkStart, this.uInt32_),
224
        pack(this.cue.points[i].dwBlockStart, this.uInt32_),
225
        pack(this.cue.points[i].dwSampleOffset, this.uInt32_));
226
    }
227
    return points;
228
  }
229
230
  /**
231
   * Return the bytes of the 'smpl' chunk.
232
   * @return {!Array<number>} The 'smpl' chunk bytes.
233
   * @private
234
   */
235
  getSmplBytes_() {
236
    /** @type {!Array<number>} */
237
    let bytes = [];
238
    if (this.smpl.chunkId) {
239
      /** @type {!Array<number>} */
240
      let smplLoopsBytes = this.getSmplLoopsBytes_();
241
      bytes = bytes.concat(
242
        packString(this.smpl.chunkId),
243
        pack(smplLoopsBytes.length + 36, this.uInt32_),
244
        pack(this.smpl.dwManufacturer, this.uInt32_),
245
        pack(this.smpl.dwProduct, this.uInt32_),
246
        pack(this.smpl.dwSamplePeriod, this.uInt32_),
247
        pack(this.smpl.dwMIDIUnityNote, this.uInt32_),
248
        pack(this.smpl.dwMIDIPitchFraction, this.uInt32_),
249
        pack(this.smpl.dwSMPTEFormat, this.uInt32_),
250
        pack(this.smpl.dwSMPTEOffset, this.uInt32_),
251
        pack(this.smpl.dwNumSampleLoops, this.uInt32_),
252
        pack(this.smpl.dwSamplerData, this.uInt32_),
253
        smplLoopsBytes);
254
    }
255
    return bytes;
256
  }
257
258
  /**
259
   * Return the bytes of the 'smpl' loops.
260
   * @return {!Array<number>} The 'smpl' loops as an array of bytes.
261
   * @private
262
   */
263
  getSmplLoopsBytes_() {
264
    /** @type {!Array<number>} */
265
    let loops = [];
266
    for (let i=0; i<this.smpl.dwNumSampleLoops; i++) {
267
      loops = loops.concat(
268
        pack(this.smpl.loops[i].dwName, this.uInt32_),
269
        pack(this.smpl.loops[i].dwType, this.uInt32_),
270
        pack(this.smpl.loops[i].dwStart, this.uInt32_),
271
        pack(this.smpl.loops[i].dwEnd, this.uInt32_),
272
        pack(this.smpl.loops[i].dwFraction, this.uInt32_),
273
        pack(this.smpl.loops[i].dwPlayCount, this.uInt32_));
274
    }
275
    return loops;
276
  }
277
278
  /**
279
   * Return the bytes of the 'fact' chunk.
280
   * @return {!Array<number>} The 'fact' chunk bytes.
281
   * @private
282
   */
283
  getFactBytes_() {
284
    /** @type {!Array<number>} */
285
    let bytes = [];
286
    if (this.fact.chunkId) {
287
      bytes = bytes.concat(
288
        packString(this.fact.chunkId),
289
        pack(this.fact.chunkSize, this.uInt32_),
290
        pack(this.fact.dwSampleLength, this.uInt32_));
291
    }
292
    return bytes;
293
  }
294
295
  /**
296
   * Return the bytes of the 'fmt ' chunk.
297
   * @return {!Array<number>} The 'fmt' chunk bytes.
298
   * @throws {Error} if no 'fmt ' chunk is present.
299
   * @private
300
   */
301
  getFmtBytes_() {
302
    /** @type {!Array<number>} */
303
    let fmtBytes = [];
304
    if (this.fmt.chunkId) {
305
      return fmtBytes.concat(
306
        packString(this.fmt.chunkId),
307
        pack(this.fmt.chunkSize, this.uInt32_),
308
        pack(this.fmt.audioFormat, this.uInt16_),
309
        pack(this.fmt.numChannels, this.uInt16_),
310
        pack(this.fmt.sampleRate, this.uInt32_),
311
        pack(this.fmt.byteRate, this.uInt32_),
312
        pack(this.fmt.blockAlign, this.uInt16_),
313
        pack(this.fmt.bitsPerSample, this.uInt16_),
314
        this.getFmtExtensionBytes_());
315
    }
316
    throw Error('Could not find the "fmt " chunk');
317
  }
318
319
  /**
320
   * Return the bytes of the fmt extension fields.
321
   * @return {!Array<number>} The fmt extension bytes.
322
   * @private
323
   */
324
  getFmtExtensionBytes_() {
325
    /** @type {!Array<number>} */
326
    let extension = [];
327
    if (this.fmt.chunkSize > 16) {
328
      extension = extension.concat(
329
        pack(this.fmt.cbSize, this.uInt16_));
330
    }
331
    if (this.fmt.chunkSize > 18) {
332
      extension = extension.concat(
333
        pack(this.fmt.validBitsPerSample, this.uInt16_));
334
    }
335
    if (this.fmt.chunkSize > 20) {
336
      extension = extension.concat(
337
        pack(this.fmt.dwChannelMask, this.uInt32_));
338
    }
339
    if (this.fmt.chunkSize > 24) {
340
      extension = extension.concat(
341
        pack(this.fmt.subformat[0], this.uInt32_),
342
        pack(this.fmt.subformat[1], this.uInt32_),
343
        pack(this.fmt.subformat[2], this.uInt32_),
344
        pack(this.fmt.subformat[3], this.uInt32_));
345
    }
346
    return extension;
347
  }
348
349
  /**
350
   * Return the bytes of the 'LIST' chunk.
351
   * @return {!Array<number>} The 'LIST' chunk bytes.
352
   */
353
  getLISTBytes_() {
354
    /** @type {!Array<number>} */
355
    let bytes = [];
356
    for (let i=0; i<this.LIST.length; i++) {
357
      /** @type {!Array<number>} */
358
      let subChunksBytes = this.getLISTSubChunksBytes_(
359
          this.LIST[i].subChunks, this.LIST[i].format);
360
      bytes = bytes.concat(
361
        packString(this.LIST[i].chunkId),
362
        pack(subChunksBytes.length + 4, this.uInt32_),
363
        packString(this.LIST[i].format),
364
        subChunksBytes);
365
    }
366
    return bytes;
367
  }
368
369
  /**
370
   * Return the bytes of the sub chunks of a 'LIST' chunk.
371
   * @param {!Array<!Object>} subChunks The 'LIST' sub chunks.
372
   * @param {string} format The format of the 'LIST' chunk.
373
   *    Currently supported values are 'adtl' or 'INFO'.
374
   * @return {!Array<number>} The sub chunk bytes.
375
   * @private
376
   */
377
  getLISTSubChunksBytes_(subChunks, format) {
378
    /** @type {!Array<number>} */
379
    let bytes = [];
380
    for (let i=0; i<subChunks.length; i++) {
381
      if (format == 'INFO') {
382
        bytes = bytes.concat(
383
          packString(subChunks[i].chunkId),
384
          pack(subChunks[i].value.length + 1, this.uInt32_),
385
          this.writeString_(
386
            subChunks[i].value, subChunks[i].value.length));
387
        bytes.push(0);
388
      } else if (format == 'adtl') {
389
        if (['labl', 'note'].indexOf(subChunks[i].chunkId) > -1) {
390
          bytes = bytes.concat(
391
            packString(subChunks[i].chunkId),
392
            pack(
393
              subChunks[i].value.length + 4 + 1, this.uInt32_),
394
            pack(subChunks[i].dwName, this.uInt32_),
395
            this.writeString_(
396
              subChunks[i].value,
397
              subChunks[i].value.length));
398
          bytes.push(0);
399
        } else if (subChunks[i].chunkId == 'ltxt') {
400
          bytes = bytes.concat(
401
            this.getLtxtChunkBytes_(subChunks[i]));
402
        }
403
      }
404
      if (bytes.length % 2) {
405
        bytes.push(0);
406
      }
407
    }
408
    return bytes;
409
  }
410
411
  /**
412
   * Return the bytes of a 'ltxt' chunk.
413
   * @param {!Object} ltxt the 'ltxt' chunk.
414
   * @return {!Array<number>} The 'ltxt' chunk bytes.
415
   * @private
416
   */
417
  getLtxtChunkBytes_(ltxt) {
418
    return [].concat(
419
      packString(ltxt.chunkId),
420
      pack(ltxt.value.length + 20, this.uInt32_),
421
      pack(ltxt.dwName, this.uInt32_),
422
      pack(ltxt.dwSampleLength, this.uInt32_),
423
      pack(ltxt.dwPurposeID, this.uInt32_),
424
      pack(ltxt.dwCountry, this.uInt16_),
425
      pack(ltxt.dwLanguage, this.uInt16_),
426
      pack(ltxt.dwDialect, this.uInt16_),
427
      pack(ltxt.dwCodePage, this.uInt16_),
428
      this.writeString_(ltxt.value, ltxt.value.length));
429
  }
430
431
  /**
432
   * Return the bytes of the 'junk' chunk.
433
   * @return {!Array<number>} The 'junk' chunk bytes.
434
   * @private
435
   */
436
  getJunkBytes_() {
437
    /** @type {!Array<number>} */
438
    let bytes = [];
439
    if (this.junk.chunkId) {
440
      return bytes.concat(
441
        packString(this.junk.chunkId),
442
        pack(this.junk.chunkData.length, this.uInt32_),
443
        this.junk.chunkData);
444
    }
445
    return bytes;
446
  }
447
448
  /**
449
   * Return 'RIFF' if the container is 'RF64', the current container name
450
   * otherwise. Used to enforce 'RIFF' when RF64 is not allowed.
451
   * @return {string}
452
   * @private
453
   */
454
  correctContainer_() {
455
    return this.container == 'RF64' ? 'RIFF' : this.container;
456
  }
457
458
  /**
459
   * Set the string code of the bit depth based on the 'fmt ' chunk.
460
   * @private
461
   */
462
  bitDepthFromFmt_() {
463
    if (this.fmt.audioFormat === 3 && this.fmt.bitsPerSample === 32) {
464
      this.bitDepth = '32f';
465
    } else if (this.fmt.audioFormat === 6) {
466
      this.bitDepth = '8a';
467
    } else if (this.fmt.audioFormat === 7) {
468
      this.bitDepth = '8m';
469
    } else {
470
      this.bitDepth = this.fmt.bitsPerSample.toString();
471
    }
472
  }
473
474
  /**
475
   * Return a .wav file byte buffer with the data from the WaveFile object.
476
   * The return value of this method can be written straight to disk.
477
   * @return {!Uint8Array} The wav file bytes.
478
   * @private
479
   */
480
  createWaveFile_() {
481
    /** @type {!Array<!Array<number>>} */
482
    let fileBody = [
483
      this.getJunkBytes_(),
484
      this.getDs64Bytes_(),
485
      this.getBextBytes_(),
486
      this.getFmtBytes_(),
487
      this.getFactBytes_(),
488
      packString(this.data.chunkId),
489
      pack(this.data.samples.length, this.uInt32_),
490
      this.data.samples,
491
      this.getCueBytes_(),
492
      this.getSmplBytes_(),
493
      this.getLISTBytes_()
494
    ];
495
    /** @type {number} */
496
    let fileBodyLength = 0;
497
    for (let i=0; i<fileBody.length; i++) {
498
      fileBodyLength += fileBody[i].length;
499
    }
500
    /** @type {!Uint8Array} */
501
    let file = new Uint8Array(fileBodyLength + 12);
502
    /** @type {number} */
503
    let index = 0;
504
    index = packStringTo(this.container, file, index);
505
    index = packTo(fileBodyLength + 4, this.uInt32_, file, index);
506
    index = packStringTo(this.format, file, index);
507
    for (let i=0; i<fileBody.length; i++) {
508
      file.set(fileBody[i], index);
509
      index += fileBody[i].length;
510
    }
511
    return file;
512
  }
513
514
  /**
515
   * Update the type definition used to read and write the samples.
516
   * @private
517
   */
518
  updateDataType_() {
519
    /** @type {!Object} */
520
    this.dataType = {
521
      bits: ((parseInt(this.bitDepth, 10) - 1) | 7) + 1,
522
      float: this.bitDepth == '32f' || this.bitDepth == '64',
523
      signed: this.bitDepth != '8',
524
      be: this.container == 'RIFX'
525
    };
526
    if (['4', '8a', '8m'].indexOf(this.bitDepth) > -1 ) {
527
      this.dataType.bits = 8;
528
      this.dataType.signed = false;
529
    }
530
  }
531
532
  /**
533
   * Set up the WaveFile object from a byte buffer.
534
   * @param {!Uint8Array} buffer The buffer.
535
   * @param {boolean=} samples True if the samples should be loaded.
536
   * @throws {Error} If container is not RIFF, RIFX or RF64.
537
   * @throws {Error} If no 'fmt ' chunk is found.
538
   * @throws {Error} If no 'data' chunk is found.
539
   */
540
  readWavBuffer(buffer, samples=true) {
541
    this.head_ = 0;
542
    this.clearHeader_();
543
    this.readRIFFChunk_(buffer);
544
    /** @type {!Object} */
545
    let chunk = riffChunks(buffer);
546
    this.readDs64Chunk_(buffer, chunk.subChunks);
547
    this.readFmtChunk_(buffer, chunk.subChunks);
548
    this.readFactChunk_(buffer, chunk.subChunks);
549
    this.readBextChunk_(buffer, chunk.subChunks);
550
    this.readCueChunk_(buffer, chunk.subChunks);
551
    this.readSmplChunk_(buffer, chunk.subChunks);
552
    this.readDataChunk_(buffer, chunk.subChunks, samples);
553
    this.readJunkChunk_(buffer, chunk.subChunks);
554
    this.readLISTChunk_(buffer, chunk.subChunks);
555
    this.bitDepthFromFmt_();
556
    this.updateDataType_();
557
  }
558
559
  /**
560
   * Create the header of a ADPCM wave file.
561
   * @param {string} bitDepthCode The audio bit depth
562
   * @param {number} numChannels The number of channels
563
   * @param {number} sampleRate The sample rate.
564
   * @param {number} numBytes The number of bytes each sample use.
565
   * @param {!Object} options The extra options, like container defintion.
566
   * @private
567
   */
568
  createADPCMHeader_(bitDepthCode, numChannels, sampleRate, numBytes, options) {
569
    this.createPCMHeader_(
570
      bitDepthCode, numChannels, sampleRate, numBytes, options);
571
    this.chunkSize = 40 + this.data.samples.length;
572
    this.fmt.chunkSize = 20;
573
    this.fmt.byteRate = 4055;
574
    this.fmt.blockAlign = 256;
575
    this.fmt.bitsPerSample = 4;
576
    this.fmt.cbSize = 2;
577
    this.fmt.validBitsPerSample = 505;
578
    this.fact.chunkId = 'fact';
579
    this.fact.chunkSize = 4;
580
    this.fact.dwSampleLength = this.data.samples.length * 2;
581
    this.data.chunkSize = this.data.samples.length;
582
  }
583
584
  /**
585
   * Create the header of WAVE_FORMAT_EXTENSIBLE file.
586
   * @param {string} bitDepthCode The audio bit depth
587
   * @param {number} numChannels The number of channels
588
   * @param {number} sampleRate The sample rate.
589
   * @param {number} numBytes The number of bytes each sample use.
590
   * @param {!Object} options The extra options, like container defintion.
591
   * @private
592
   */
593
  createExtensibleHeader_(
594
      bitDepthCode, numChannels, sampleRate, numBytes, options) {
595
    this.createPCMHeader_(
596
      bitDepthCode, numChannels, sampleRate, numBytes, options);
597
    this.chunkSize = 36 + 24 + this.data.samples.length;
598
    this.fmt.chunkSize = 40;
599
    this.fmt.bitsPerSample = ((parseInt(bitDepthCode, 10) - 1) | 7) + 1;
600
    this.fmt.cbSize = 22;
601
    this.fmt.validBitsPerSample = parseInt(bitDepthCode, 10);
602
    this.fmt.dwChannelMask = this.getDwChannelMask_();
603
    // subformat 128-bit GUID as 4 32-bit values
604
    // only supports uncompressed integer PCM samples
605
    this.fmt.subformat = [1, 1048576, 2852126848, 1905997824];
606
  }
607
608
  /**
609
   * Get the value for dwChannelMask according to the number of channels.
610
   * @return {number} the dwChannelMask value.
611
   * @private
612
   */
613
  getDwChannelMask_() {
614
    /** @type {number} */
615
    let dwChannelMask = 0;
616
    // mono = FC
617
    if (this.fmt.numChannels === 1) {
618
      dwChannelMask = 0x4;
619
    // stereo = FL, FR
620
    } else if (this.fmt.numChannels === 2) {
621
      dwChannelMask = 0x3;
622
    // quad = FL, FR, BL, BR
623
    } else if (this.fmt.numChannels === 4) {
624
      dwChannelMask = 0x33;
625
    // 5.1 = FL, FR, FC, LF, BL, BR
626
    } else if (this.fmt.numChannels === 6) {
627
      dwChannelMask = 0x3F;
628
    // 7.1 = FL, FR, FC, LF, BL, BR, SL, SR
629
    } else if (this.fmt.numChannels === 8) {
630
      dwChannelMask = 0x63F;
631
    }
632
    return dwChannelMask;
633
  }
634
635
  /**
636
   * Create the header of mu-Law and A-Law wave files.
637
   * @param {string} bitDepthCode The audio bit depth
638
   * @param {number} numChannels The number of channels
639
   * @param {number} sampleRate The sample rate.
640
   * @param {number} numBytes The number of bytes each sample use.
641
   * @param {!Object} options The extra options, like container defintion.
642
   * @private
643
   */
644
  createALawMulawHeader_(
645
      bitDepthCode, numChannels, sampleRate, numBytes, options) {
646
    this.createPCMHeader_(
647
      bitDepthCode, numChannels, sampleRate, numBytes, options);
648
    this.chunkSize = 40 + this.data.samples.length;
649
    this.fmt.chunkSize = 20;
650
    this.fmt.cbSize = 2;
651
    this.fmt.validBitsPerSample = 8;
652
    this.fact.chunkId = 'fact';
653
    this.fact.chunkSize = 4;
654
    this.fact.dwSampleLength = this.data.samples.length;
655
  }
656
657
  /**
658
   * Create the header of a linear PCM wave file.
659
   * @param {string} bitDepthCode The audio bit depth
660
   * @param {number} numChannels The number of channels
661
   * @param {number} sampleRate The sample rate.
662
   * @param {number} numBytes The number of bytes each sample use.
663
   * @param {!Object} options The extra options, like container defintion.
664
   * @private
665
   */
666
  createPCMHeader_(bitDepthCode, numChannels, sampleRate, numBytes, options) {
667
    this.clearHeader_();
668
    this.container = options.container;
669
    this.chunkSize = 36 + this.data.samples.length;
670
    this.format = 'WAVE';
671
    this.fmt.chunkId = 'fmt ';
672
    this.fmt.chunkSize = 16;
673
    this.fmt.byteRate = (numChannels * numBytes) * sampleRate;
674
    this.fmt.blockAlign = numChannels * numBytes;
675
    this.fmt.audioFormat = this.audioFormats_[bitDepthCode] ?
676
      this.audioFormats_[bitDepthCode] : 65534;
677
    this.fmt.numChannels = numChannels;
678
    this.fmt.sampleRate = sampleRate;
679
    this.fmt.bitsPerSample = parseInt(bitDepthCode, 10);
680
    this.fmt.cbSize = 0;
681
    this.fmt.validBitsPerSample = 0;
682
  }
683
684
  /**
685
   * Return the closest greater number of bits for a number of bits that
686
   * do not fill a full sequence of bytes.
687
   * @param {string} bitDepthCode The bit depth.
688
   * @return {string}
689
   * @private
690
   */
691
  realBitDepth_(bitDepthCode) {
692
    if (bitDepthCode != '32f') {
693
      bitDepthCode = (((parseInt(bitDepthCode, 10) - 1) | 7) + 1).toString();
694
    }
695
    return bitDepthCode;
696
  }
697
698
  /**
699
   * Validate the header of the file.
700
   * @throws {Error} If any property of the object appears invalid.
701
   * @private
702
   */
703
  validateHeader_() {
704
    this.validateBitDepth_();
705
    this.validateNumChannels_();
706
    this.validateSampleRate_();
707
  }
708
709
  /**
710
   * Validate the bit depth.
711
   * @return {boolean} True is the bit depth is valid.
712
   * @throws {Error} If bit depth is invalid.
713
   * @private
714
   */
715
  validateBitDepth_() {
716
    if (!this.audioFormats_[this.bitDepth]) {
717
      if (parseInt(this.bitDepth, 10) > 8 &&
718
          parseInt(this.bitDepth, 10) < 54) {
719
        return true;
720
      }
721
      throw new Error('Invalid bit depth.');
722
    }
723
    return true;
724
  }
725
726
  /**
727
   * Validate the number of channels.
728
   * @return {boolean} True is the number of channels is valid.
729
   * @throws {Error} If the number of channels is invalid.
730
   * @private
731
   */
732
  validateNumChannels_() {
733
    /** @type {number} */
734
    let blockAlign = this.fmt.numChannels * this.fmt.bitsPerSample / 8;
735
    if (this.fmt.numChannels < 1 || blockAlign > 65535) {
736
      throw new Error('Invalid number of channels.');
737
    }
738
    return true;
739
  }
740
741
  /**
742
   * Validate the sample rate value.
743
   * @return {boolean} True is the sample rate is valid.
744
   * @throws {Error} If the sample rate is invalid.
745
   * @private
746
   */
747
  validateSampleRate_() {
748
    /** @type {number} */
749
    let byteRate = this.fmt.numChannels *
750
      (this.fmt.bitsPerSample / 8) * this.fmt.sampleRate;
751
    if (this.fmt.sampleRate < 1 || byteRate > 4294967295) {
752
      throw new Error('Invalid sample rate.');
753
    }
754
    return true;
755
  }
756
757
  /**
758
   * Reset attributes that should emptied when a file is
759
   * created with the fromScratch() or fromBuffer() methods.
760
   * @private
761
   */
762
  clearHeader_() {
763
    this.fmt.cbSize = 0;
764
    this.fmt.validBitsPerSample = 0;
765
    this.fact.chunkId = '';
766
    this.ds64.chunkId = '';
767
  }
768
769
  /**
770
   * Set up to work wih big-endian or little-endian files.
771
   * The types used are changed to LE or BE. If the
772
   * the file is big-endian (RIFX), true is returned.
773
   * @return {boolean} True if the file is RIFX.
774
   * @private
775
   */
776
  LEorBE_() {
777
    /** @type {boolean} */
778
    let bigEndian = this.container === 'RIFX';
779
    this.uInt16_.be = bigEndian;
780
    this.uInt32_.be = bigEndian;
781
    return bigEndian;
782
  }
783
784
  /**
785
   * Find a chunk by its fourCC_ in a array of RIFF chunks.
786
   * @param {!Object} chunks The wav file chunks.
787
   * @param {string} chunkId The chunk fourCC_.
788
   * @param {boolean} multiple True if there may be multiple chunks
789
   *    with the same chunkId.
790
   * @return {?Array<!Object>}
791
   * @private
792
   */
793
  findChunk_(chunks, chunkId, multiple=false) {
794
    /** @type {!Array<!Object>} */
795
    let chunk = [];
796
    for (let i=0; i<chunks.length; i++) {
797
      if (chunks[i].chunkId == chunkId) {
798
        if (multiple) {
799
          chunk.push(chunks[i]);
800
        } else {
801
          return chunks[i];
802
        }
803
      }
804
    }
805
    if (chunkId == 'LIST') {
806
      return chunk.length ? chunk : null;
807
    }
808
    return null;
809
  }
810
811
  /**
812
   * Read the RIFF chunk a wave file.
813
   * @param {!Uint8Array} bytes A wav buffer.
814
   * @throws {Error} If no 'RIFF' chunk is found.
815
   * @private
816
   */
817
  readRIFFChunk_(bytes) {
818
    this.head_ = 0;
819
    this.container = this.readString_(bytes, 4);
820
    if (['RIFF', 'RIFX', 'RF64'].indexOf(this.container) === -1) {
821
      throw Error('Not a supported format.');
822
    }
823
    this.LEorBE_();
824
    this.chunkSize = this.read_(bytes, this.uInt32_);
825
    this.format = this.readString_(bytes, 4);
826
    if (this.format != 'WAVE') {
827
      throw Error('Could not find the "WAVE" format identifier');
828
    }
829
  }
830
831
  /**
832
   * Read the 'fmt ' chunk of a wave file.
833
   * @param {!Uint8Array} buffer The wav file buffer.
834
   * @param {!Object} signature The file signature.
835
   * @throws {Error} If no 'fmt ' chunk is found.
836
   * @private
837
   */
838
  readFmtChunk_(buffer, signature) {
839
    /** @type {?Object} */
840
    let chunk = this.findChunk_(signature, 'fmt ');
841
    if (chunk) {
842
      this.head_ = chunk.chunkData.start;
843
      this.fmt.chunkId = chunk.chunkId;
844
      this.fmt.chunkSize = chunk.chunkSize;
845
      this.fmt.audioFormat = this.read_(buffer, this.uInt16_);
846
      this.fmt.numChannels = this.read_(buffer, this.uInt16_);
847
      this.fmt.sampleRate = this.read_(buffer, this.uInt32_);
848
      this.fmt.byteRate = this.read_(buffer, this.uInt32_);
849
      this.fmt.blockAlign = this.read_(buffer, this.uInt16_);
850
      this.fmt.bitsPerSample = this.read_(buffer, this.uInt16_);
851
      this.readFmtExtension_(buffer);
852
    } else {
853
      throw Error('Could not find the "fmt " chunk');
854
    }
855
  }
856
857
  /**
858
   * Read the 'fmt ' chunk extension.
859
   * @param {!Uint8Array} buffer The wav file buffer.
860
   * @private
861
   */
862
  readFmtExtension_(buffer) {
863
    if (this.fmt.chunkSize > 16) {
864
      this.fmt.cbSize = this.read_(buffer, this.uInt16_);
865
      if (this.fmt.chunkSize > 18) {
866
        this.fmt.validBitsPerSample = this.read_(buffer, this.uInt16_);
867
        if (this.fmt.chunkSize > 20) {
868
          this.fmt.dwChannelMask = this.read_(buffer, this.uInt32_);
869
          this.fmt.subformat = [
870
            this.read_(buffer, this.uInt32_),
871
            this.read_(buffer, this.uInt32_),
872
            this.read_(buffer, this.uInt32_),
873
            this.read_(buffer, this.uInt32_)];
874
        }
875
      }
876
    }
877
  }
878
879
  /**
880
   * Read the 'fact' chunk of a wav file.
881
   * @param {!Uint8Array} buffer The wav file buffer.
882
   * @param {!Object} signature The file signature.
883
   * @private
884
   */
885
  readFactChunk_(buffer, signature) {
886
    /** @type {?Object} */
887
    let chunk = this.findChunk_(signature, 'fact');
888
    if (chunk) {
889
      this.head_ = chunk.chunkData.start;
890
      this.fact.chunkId = chunk.chunkId;
891
      this.fact.chunkSize = chunk.chunkSize;
892
      this.fact.dwSampleLength = this.read_(buffer, this.uInt32_);
893
    }
894
  }
895
896
  /**
897
   * Read the 'cue ' chunk of a wave file.
898
   * @param {!Uint8Array} buffer The wav file buffer.
899
   * @param {!Object} signature The file signature.
900
   * @private
901
   */
902
  readCueChunk_(buffer, signature) {
903
    /** @type {?Object} */
904
    let chunk = this.findChunk_(signature, 'cue ');
905
    if (chunk) {
906
      this.head_ = chunk.chunkData.start;
907
      this.cue.chunkId = chunk.chunkId;
908
      this.cue.chunkSize = chunk.chunkSize;
909
      this.cue.dwCuePoints = this.read_(buffer, this.uInt32_);
910
      for (let i=0; i<this.cue.dwCuePoints; i++) {
911
        this.cue.points.push({
912
          dwName: this.read_(buffer, this.uInt32_),
913
          dwPosition: this.read_(buffer, this.uInt32_),
914
          fccChunk: this.readString_(buffer, 4),
915
          dwChunkStart: this.read_(buffer, this.uInt32_),
916
          dwBlockStart: this.read_(buffer, this.uInt32_),
917
          dwSampleOffset: this.read_(buffer, this.uInt32_),
918
        });
919
      }
920
    }
921
  }
922
923
  /**
924
   * Read the 'smpl' chunk of a wave file.
925
   * @param {!Uint8Array} buffer The wav file buffer.
926
   * @param {!Object} signature The file signature.
927
   * @private
928
   */
929
  readSmplChunk_(buffer, signature) {
930
    /** @type {?Object} */
931
    let chunk = this.findChunk_(signature, 'smpl');
932
    if (chunk) {
933
      this.head_ = chunk.chunkData.start;
934
      this.smpl.chunkId = chunk.chunkId;
935
      this.smpl.chunkSize = chunk.chunkSize;
936
      this.smpl.dwManufacturer = this.read_(buffer, this.uInt32_);
937
      this.smpl.dwProduct = this.read_(buffer, this.uInt32_);
938
      this.smpl.dwSamplePeriod = this.read_(buffer, this.uInt32_);
939
      this.smpl.dwMIDIUnityNote = this.read_(buffer, this.uInt32_);
940
      this.smpl.dwMIDIPitchFraction = this.read_(buffer, this.uInt32_);
941
      this.smpl.dwSMPTEFormat = this.read_(buffer, this.uInt32_);
942
      this.smpl.dwSMPTEOffset = this.read_(buffer, this.uInt32_);
943
      this.smpl.dwNumSampleLoops = this.read_(buffer, this.uInt32_);
944
      this.smpl.dwSamplerData = this.read_(buffer, this.uInt32_);
945
      for (let i=0; i<this.smpl.dwNumSampleLoops; i++) {
946
        this.smpl.loops.push({
947
          dwName: this.read_(buffer, this.uInt32_),
948
          dwType: this.read_(buffer, this.uInt32_),
949
          dwStart: this.read_(buffer, this.uInt32_),
950
          dwEnd: this.read_(buffer, this.uInt32_),
951
          dwFraction: this.read_(buffer, this.uInt32_),
952
          dwPlayCount: this.read_(buffer, this.uInt32_),
953
        });
954
      }
955
    }
956
  }
957
958
  /**
959
   * Read the 'data' chunk of a wave file.
960
   * @param {!Uint8Array} buffer The wav file buffer.
961
   * @param {!Object} signature The file signature.
962
   * @param {boolean} samples True if the samples should be loaded.
963
   * @throws {Error} If no 'data' chunk is found.
964
   * @private
965
   */
966
  readDataChunk_(buffer, signature, samples) {
967
    /** @type {?Object} */
968
    let chunk = this.findChunk_(signature, 'data');
969
    if (chunk) {
970
      this.data.chunkId = 'data';
971
      this.data.chunkSize = chunk.chunkSize;
972
      if (samples) {
973
        this.data.samples = buffer.slice(
974
          chunk.chunkData.start,
975
          chunk.chunkData.end);
976
      }
977
    } else {
978
      throw Error('Could not find the "data" chunk');
979
    }
980
  }
981
982
  /**
983
   * Read the 'bext' chunk of a wav file.
984
   * @param {!Uint8Array} buffer The wav file buffer.
985
   * @param {!Object} signature The file signature.
986
   * @private
987
   */
988
  readBextChunk_(buffer, signature) {
989
    /** @type {?Object} */
990
    let chunk = this.findChunk_(signature, 'bext');
991
    if (chunk) {
992
      this.head_ = chunk.chunkData.start;
993
      this.bext.chunkId = chunk.chunkId;
994
      this.bext.chunkSize = chunk.chunkSize;
995
      this.bext.description = this.readString_(buffer, 256);
996
      this.bext.originator = this.readString_(buffer, 32);
997
      this.bext.originatorReference = this.readString_(buffer, 32);
998
      this.bext.originationDate = this.readString_(buffer, 10);
999
      this.bext.originationTime = this.readString_(buffer, 8);
1000
      this.bext.timeReference = [
1001
        this.read_(buffer, this.uInt32_),
1002
        this.read_(buffer, this.uInt32_)];
1003
      this.bext.version = this.read_(buffer, this.uInt16_);
1004
      this.bext.UMID = this.readString_(buffer, 64);
1005
      this.bext.loudnessValue = this.read_(buffer, this.uInt16_);
1006
      this.bext.loudnessRange = this.read_(buffer, this.uInt16_);
1007
      this.bext.maxTruePeakLevel = this.read_(buffer, this.uInt16_);
1008
      this.bext.maxMomentaryLoudness = this.read_(buffer, this.uInt16_);
1009
      this.bext.maxShortTermLoudness = this.read_(buffer, this.uInt16_);
1010
      this.bext.reserved = this.readString_(buffer, 180);
1011
      this.bext.codingHistory = this.readString_(
1012
        buffer, this.bext.chunkSize - 602);
1013
    }
1014
  }
1015
1016
  /**
1017
   * Read the 'ds64' chunk of a wave file.
1018
   * @param {!Uint8Array} buffer The wav file buffer.
1019
   * @param {!Object} signature The file signature.
1020
   * @throws {Error} If no 'ds64' chunk is found and the file is RF64.
1021
   * @private
1022
   */
1023
  readDs64Chunk_(buffer, signature) {
1024
    /** @type {?Object} */
1025
    let chunk = this.findChunk_(signature, 'ds64');
1026
    if (chunk) {
1027
      this.head_ = chunk.chunkData.start;
1028
      this.ds64.chunkId = chunk.chunkId;
1029
      this.ds64.chunkSize = chunk.chunkSize;
1030
      this.ds64.riffSizeHigh = this.read_(buffer, this.uInt32_);
1031
      this.ds64.riffSizeLow = this.read_(buffer, this.uInt32_);
1032
      this.ds64.dataSizeHigh = this.read_(buffer, this.uInt32_);
1033
      this.ds64.dataSizeLow = this.read_(buffer, this.uInt32_);
1034
      this.ds64.originationTime = this.read_(buffer, this.uInt32_);
1035
      this.ds64.sampleCountHigh = this.read_(buffer, this.uInt32_);
1036
      this.ds64.sampleCountLow = this.read_(buffer, this.uInt32_);
1037
      //if (this.ds64.chunkSize > 28) {
1038
      //  this.ds64.tableLength = unpack(
1039
      //    chunkData.slice(28, 32), this.uInt32_);
1040
      //  this.ds64.table = chunkData.slice(
1041
      //     32, 32 + this.ds64.tableLength); 
1042
      //}
1043
    } else {
1044
      if (this.container == 'RF64') {
1045
        throw Error('Could not find the "ds64" chunk');  
1046
      }
1047
    }
1048
  }
1049
1050
  /**
1051
   * Read the 'LIST' chunks of a wave file.
1052
   * @param {!Uint8Array} buffer The wav file buffer.
1053
   * @param {!Object} signature The file signature.
1054
   * @private
1055
   */
1056
  readLISTChunk_(buffer, signature) {
1057
    /** @type {?Object} */
1058
    let listChunks = this.findChunk_(signature, 'LIST', true);
1059
    if (listChunks === null) {
1060
      return;
1061
    }
1062
    for (let j=0; j < listChunks.length; j++) {
1063
      /** @type {!Object} */
1064
      let subChunk = listChunks[j];
1065
      this.LIST.push({
1066
        chunkId: subChunk.chunkId,
1067
        chunkSize: subChunk.chunkSize,
1068
        format: subChunk.format,
1069
        subChunks: []});
1070
      for (let x=0; x<subChunk.subChunks.length; x++) {
1071
        this.readLISTSubChunks_(subChunk.subChunks[x],
1072
          subChunk.format, buffer);
1073
      }
1074
    }
1075
  }
1076
1077
  /**
1078
   * Read the sub chunks of a 'LIST' chunk.
1079
   * @param {!Object} subChunk The 'LIST' subchunks.
1080
   * @param {string} format The 'LIST' format, 'adtl' or 'INFO'.
1081
   * @param {!Uint8Array} buffer The wav file buffer.
1082
   * @private
1083
   */
1084
  readLISTSubChunks_(subChunk, format, buffer) {
1085
    if (format == 'adtl') {
1086
      if (['labl', 'note','ltxt'].indexOf(subChunk.chunkId) > -1) {
1087
        this.head_ = subChunk.chunkData.start;
1088
        /** @type {!Object<string, string|number>} */
1089
        let item = {
1090
          chunkId: subChunk.chunkId,
1091
          chunkSize: subChunk.chunkSize,
1092
          dwName: this.read_(buffer, this.uInt32_)
1093
        };
1094
        if (subChunk.chunkId == 'ltxt') {
1095
          item.dwSampleLength = this.read_(buffer, this.uInt32_);
1096
          item.dwPurposeID = this.read_(buffer, this.uInt32_);
1097
          item.dwCountry = this.read_(buffer, this.uInt16_);
1098
          item.dwLanguage = this.read_(buffer, this.uInt16_);
1099
          item.dwDialect = this.read_(buffer, this.uInt16_);
1100
          item.dwCodePage = this.read_(buffer, this.uInt16_);
1101
        }
1102
        item.value = this.readZSTR_(buffer, this.head_);
1103
        this.LIST[this.LIST.length - 1].subChunks.push(item);
1104
      }
1105
    // RIFF INFO tags like ICRD, ISFT, ICMT
1106
    } else if(format == 'INFO') {
1107
      this.head_ = subChunk.chunkData.start;
1108
      this.LIST[this.LIST.length - 1].subChunks.push({
1109
        chunkId: subChunk.chunkId,
1110
        chunkSize: subChunk.chunkSize,
1111
        value: this.readZSTR_(buffer,  this.head_)
1112
      });
1113
    }
1114
  }
1115
1116
  /**
1117
   * Read the 'junk' chunk of a wave file.
1118
   * @param {!Uint8Array} buffer The wav file buffer.
1119
   * @param {!Object} signature The file signature.
1120
   * @private
1121
   */
1122
  readJunkChunk_(buffer, signature) {
1123
    /** @type {?Object} */
1124
    let chunk = this.findChunk_(signature, 'junk');
1125
    if (chunk) {
1126
      this.junk = {
1127
        chunkId: chunk.chunkId,
1128
        chunkSize: chunk.chunkSize,
1129
        chunkData: [].slice.call(buffer.slice(
1130
          chunk.chunkData.start,
1131
          chunk.chunkData.end))
1132
      };
1133
    }
1134
  }
1135
1136
  /**
1137
   * Read bytes as a ZSTR string.
1138
   * @param {!Uint8Array} bytes The bytes.
1139
   * @return {string} The string.
1140
   * @private
1141
   */
1142
  readZSTR_(bytes, index=0) {
1143
    /** @type {string} */
1144
    let str = '';
1145
    for (let i=index; i<bytes.length; i++) {
1146
      this.head_++;
1147
      if (bytes[i] === 0) {
1148
        break;
1149
      }
1150
      str += unpackString(bytes, i, 1);
1151
    }
1152
    return str;
1153
  }
1154
1155
  /**
1156
   * Read bytes as a string from a RIFF chunk.
1157
   * @param {!Uint8Array} bytes The bytes.
1158
   * @param {number} maxSize the max size of the string.
1159
   * @return {string} The string.
1160
   * @private
1161
   */
1162
  readString_(bytes, maxSize) {
1163
    /** @type {string} */
1164
    let str = '';
1165
    for (let i=0; i<maxSize; i++) {
1166
      str += unpackString(bytes, this.head_, 1);
1167
      this.head_++;
1168
    }
1169
    return str;
1170
  }
1171
1172
  /**
1173
   * Read a number from a chunk.
1174
   * @param {!Uint8Array} bytes The chunk bytes.
1175
   * @param {!Object} bdType The type definition.
1176
   * @return {number} The number.
1177
   * @private
1178
   */
1179
  read_(bytes, bdType) {
1180
    /** @type {number} */
1181
    let size = bdType.bits / 8;
1182
    /** @type {number} */
1183
    let value = unpackFrom(bytes, bdType, this.head_);
1184
    this.head_ += size;
1185
    return value;
1186
  }
1187
1188
1189
  /**
1190
   * Truncate float samples on over and underflow.
1191
   * @private
1192
   */
1193
  truncateSamples(samples) {
1194
    if (this.fmt.audioFormat === 3) {
1195
      /** @type {number} */   
1196
      let len = samples.length;
1197
      for (let i=0; i<len; i++) {
1198
        if (samples[i] > 1) {
1199
          samples[i] = 1;
1200
        } else if (samples[i] < -1) {
1201
          samples[i] = -1;
1202
        }
1203
      }
1204
    }
1205
  }
1206
}
1207