Passed
Pull Request — master (#22)
by
unknown
04:19
created

WaveFileCreator.fromMpeg   B

Complexity

Conditions 2

Size

Total Lines 56
Code Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 44
dl 0
loc 56
rs 8.824
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
/*
2
 * Copyright (c) 2017-2019 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 The WaveFileCreator class.
27
 * @see https://github.com/rochars/wavefile
28
 */
29
30
import { WaveFileParser } from "./wavefile-parser";
31
import { interleave, deInterleave } from "./parsers/interleave";
32
import { validateNumChannels } from "./validators/validate-num-channels";
33
import { validateSampleRate } from "./validators/validate-sample-rate";
34
import { packArrayTo, unpackArrayTo, packTo, unpack } from "./parsers/binary";
35
import { MpegReader } from "./mpeg-reader";
36
/**
37
 * A class to read, write and create wav files.
38
 * @extends WaveFileParser
39
 * @ignore
40
 */
41
export class WaveFileCreator extends WaveFileParser {
42
  constructor() {
43
    super();
44
    /**
45
     * The bit depth code according to the samples.
46
     * @type {string}
47
     */
48
    this.bitDepth = "0";
49
    /**
50
     * @type {!{bits: number, be: boolean}}
51
     * @protected
52
     */
53
    this.dataType = { bits: 0, be: false };
54
    /**
55
     * Audio formats.
56
     * Formats not listed here should be set to 65534,
57
     * the code for WAVE_FORMAT_EXTENSIBLE
58
     * @enum {number}
59
     * @protected
60
     */
61
    this.WAV_AUDIO_FORMATS = {
62
      "4": 17,
63
      "8": 1,
64
      "8a": 6,
65
      "8m": 7,
66
      "16": 1,
67
      "24": 1,
68
      "32": 1,
69
      "32f": 3,
70
      "64": 3,
71
      "65535": 80 // mpeg == 80
72
    };
73
  }
74
75
  /**
76
   * Set up the WaveFileCreator object based on the arguments passed.
77
   * Existing chunks are reset.
78
   * @param {number} numChannels The number of channels.
79
   * @param {number} sampleRate The sample rate.
80
   *    Integers like 8000, 44100, 48000, 96000, 192000.
81
   * @param {string} bitDepthCode The audio bit depth code.
82
   *    One of '4', '8', '8a', '8m', '16', '24', '32', '32f', '64'
83
   *    or any value between '8' and '32' (like '12').
84
   * @param {!(Array|TypedArray)} samples The samples.
85
   * @param {Object=} options Optional. Used to force the container
86
   *    as RIFX with {'container': 'RIFX'}
87
   * @throws {Error} If any argument does not meet the criteria.
88
   */
89
  fromScratch(numChannels, sampleRate, bitDepthCode, samples, options) {
90
    options = options || {};
91
    // reset all chunks
92
    this.clearHeaders();
93
    this.newWavFile_(numChannels, sampleRate, bitDepthCode, samples, options);
94
  }
95
96
  /**
97
   * Set up the WaveFileCreator object from an mpeg buffer and metadata info.
98
   * @param {!Uint8Array} mpegBuffer The buffer.
99
   * @param {Object=} info Mpeg info such as version, layer, bitRate, etc.
100
   * Required mpeg info:
101
   * info.version
102
   * info.layer
103
   * info.errorProtection
104
   * info.bitRate
105
   * info.sampleRate
106
   * info.padding
107
   * info.privateBit
108
   * info.channelMode
109
   * info.modeExtension
110
   * info.copyright
111
   * info.original
112
   * info.emphasis
113
   * info.numChannels
114
   * info.frameSize
115
   * info.sampleLength
116
   * @throws {Error} If any argument does not meet the criteria.
117
   */
118
  fromMpeg(mpegBuffer, info = null) {
119
    this.clearHeaders();
120
121
    if (info == null) {
0 ignored issues
show
Best Practice introduced by
Comparing info to null using the == operator is not safe. Consider using === instead.
Loading history...
122
      info = new MpegReader(mpegBuffer);
123
    }
124
125
    let codingHistory = this.mpegCodingHistory_(info);
126
127
    // riff(4) + fmt(40+8) + mext(12+8) + bxt(602+8+codingHistory.length) + fact(4+8) + buffer.length
128
    // 4 + 48 + 20 + 610 + 12 + codingHistory.length + buffer.length
129
    this.container = "RIFF";
130
    this.chunkSize = 694 + codingHistory.length + mpegBuffer.length;
131
    this.format = "WAVE";
132
    this.bitDepth = "65535";
133
134
    this.fmt.chunkId = "fmt ";
135
    this.fmt.chunkSize = 40;
136
    this.fmt.audioFormat = 80;
137
    this.fmt.numChannels = info.numChannels;
138
    this.fmt.sampleRate = info.sampleRate;
139
    this.fmt.byteRate = (info.bitRate / 8) * 1000;
140
    this.fmt.blockAlign = info.frameSize;
141
    this.fmt.bitsPerSample = 65535;
142
    this.fmt.cbSize = 22;
143
    this.fmt.headLayer = Math.pow(2, info.layer - 1);
144
    this.fmt.headBitRate = info.bitRate * 1000;
145
    this.fmt.headMode = this.mpegHeadMode_(info);
146
    this.fmt.headModeExt = this.mpegHeadModeExt_(info);
147
    this.fmt.headEmphasis = info.emphasis + 1;
148
    this.fmt.headFlags = this.mpegHeadFlags_(info);
149
    this.fmt.ptsLow = 0;
150
    this.fmt.ptsHigh = 0;
151
152
    this.mext.chunkId = "mext";
153
    this.mext.chunkSize = 12;
154
    this.mext.soundInformation = this.mpegSoundInformation_(info);
155
    this.mext.frameSize = info.frameSize;
156
    this.mext.ancillaryDataLength = 0;
157
    this.mext.ancillaryDataDef = 0;
158
    this.mext.reserved = "";
159
160
    this.bext.chunkId = "bext";
161
    this.bext.chunkSize = 602 + codingHistory.length;
162
    this.bext.timeReference = [0, 0];
163
    this.bext.version = 1;
164
    this.bext.codingHistory = codingHistory;
165
166
    this.fact.chunkId = "fact";
167
    this.fact.chunkSize = 4;
168
    this.fact.dwSampleLength = info.sampleLength;
169
170
    this.data.chunkId = "data";
171
    this.data.samples = mpegBuffer;
172
    this.data.chunkSize = this.data.samples.length;
173
  }
174
175
  mpegSoundInformation_(info) {
176
    let soundInformation = 0;
177
    if (info.homogeneous) {
178
      soundInformation += 1;
179
    }
180
    if (!info.padding) {
181
      soundInformation += 2;
182
    }
183
    if (
184
      (info.sampleRate == 44100 || info.sampleRate == 22050) &&
185
      info.padding
186
    ) {
187
      soundInformation += 4;
188
    }
189
    if (info.freeForm) {
190
      soundInformation += 8;
191
    }
192
    return soundInformation;
193
  }
194
195
  /**
196
   * Returns the mode value based on the channel mode of the mpeg file
197
   * @param {Object=} info Mpeg info such as version, layer, bitRate, etc.
198
   * @throws {Error} If any argument does not meet the criteria.
199
   */
200
  // prettier-ignore
201
  mpegHeadMode_(info) {
202
    return {
203
      "stereo": 1,
204
      "joint-stereo": 2,
205
      "dual-mono": 4,
206
      "mono": 8
207
    }[info.channelMode];
208
  }
209
210
  /**
211
   * Contains extra parameters for joint–stereo coding; not used for other modes.
212
   * @param {Object=} info Mpeg info such as version, layer, bitRate, etc.
213
   * @throws {Error} If any argument does not meet the criteria.
214
   */
215
  mpegHeadModeExt_(info) {
216
    if (info.channelMode == "joint-stereo") {
217
      return Math.pow(2, info.modeExtension);
218
    } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
219
      return 0;
220
    }
221
  }
222
223
  /**
224
   * Follows EBU established standards for CodingHistory for MPEG
225
   * EBU Technical Recommendation R98-1999, https://tech.ebu.ch/docs/r/r098.pdf
226
   * @param {Object=} info Mpeg info such as version, layer, bitRate, etc.
227
   * @throws {Error} If any argument does not meet the criteria.
228
   **/
229
  mpegCodingHistory_(info) {
230
    return (
231
      "A=MPEG" +
232
      info.version +
233
      "L" +
234
      info.layer +
235
      ",F=" +
236
      info.sampleRate +
237
      ",B=" +
238
      info.bitRate +
239
      ",M=" +
240
      info.channelMode +
241
      ",T=wavefile\r\n\0\0"
242
    );
243
  }
244
245
  /**
246
   * Follows EBU standards for `fmt` chunk `fwHeadFlags` for MPEG in BWF
247
   * EBU Tech. 3285–E – Supplement 1, 1997
248
   * https://tech.ebu.ch/docs/tech/tech3285s1.pdf
249
   * @param {Object=} info Mpeg info such as version, layer, bitRate, etc.
250
   * @throws {Error} If any argument does not meet the criteria.
251
   **/
252
  mpegHeadFlags_(info) {
253
    let flags = 0;
254
    if (info.privateBit) {
255
      flags += 1;
256
    }
257
    if (info.copyright) {
258
      flags += 2;
259
    }
260
    if (info.original) {
261
      flags += 4;
262
    }
263
    if (info.errorProtection) {
264
      flags += 8;
265
    }
266
    if (info.version > 0) {
267
      flags += 16;
268
    }
269
    return flags;
270
  }
271
272
  /**
273
   * Set up the WaveFileParser object from a byte buffer.
274
   * @param {!Uint8Array} wavBuffer The buffer.
275
   * @param {boolean=} [samples=true] True if the samples should be loaded.
276
   * @throws {Error} If container is not RIFF, RIFX or RF64.
277
   * @throws {Error} If format is not WAVE.
278
   * @throws {Error} If no 'fmt ' chunk is found.
279
   * @throws {Error} If no 'data' chunk is found.
280
   */
281
  fromBuffer(wavBuffer, samples = true) {
282
    super.fromBuffer(wavBuffer, samples);
283
    this.bitDepthFromFmt_();
284
    this.updateDataType_();
285
  }
286
287
  /**
288
   * Return a byte buffer representig the WaveFileParser object as a .wav file.
289
   * The return value of this method can be written straight to disk.
290
   * @return {!Uint8Array} A wav file.
291
   * @throws {Error} If bit depth is invalid.
292
   * @throws {Error} If the number of channels is invalid.
293
   * @throws {Error} If the sample rate is invalid.
294
   */
295
  toBuffer() {
296
    this.validateWavHeader_();
297
    return super.toBuffer();
298
  }
299
300
  /**
301
   * Return the samples packed in a Float64Array.
302
   * @param {boolean=} [interleaved=false] True to return interleaved samples,
303
   *   false to return the samples de-interleaved.
304
   * @param {Function=} [OutputObject=Float64Array] The sample container.
305
   * @return {!(Array|TypedArray)} the samples.
306
   */
307
  getSamples(interleaved = false, OutputObject = Float64Array) {
308
    /**
309
     * A Float64Array created with a size to match the
310
     * the length of the samples.
311
     * @type {!(Array|TypedArray)}
312
     */
313
    let samples = new OutputObject(
314
      this.data.samples.length / (this.dataType.bits / 8)
315
    );
316
    // Unpack all the samples
317
    unpackArrayTo(
318
      this.data.samples,
319
      this.dataType,
320
      samples,
321
      0,
322
      this.data.samples.length
323
    );
324
    if (!interleaved && this.fmt.numChannels > 1) {
325
      return deInterleave(samples, this.fmt.numChannels, OutputObject);
326
    }
327
    return samples;
328
  }
329
330
  /**
331
   * Return the sample at a given index.
332
   * @param {number} index The sample index.
333
   * @return {number} The sample.
334
   * @throws {Error} If the sample index is off range.
335
   */
336
  getSample(index) {
337
    index = index * (this.dataType.bits / 8);
338
    if (index + this.dataType.bits / 8 > this.data.samples.length) {
339
      throw new Error("Range error");
340
    }
341
    return unpack(
342
      this.data.samples.slice(index, index + this.dataType.bits / 8),
343
      this.dataType
344
    );
345
  }
346
347
  /**
348
   * Set the sample at a given index.
349
   * @param {number} index The sample index.
350
   * @param {number} sample The sample.
351
   * @throws {Error} If the sample index is off range.
352
   */
353
  setSample(index, sample) {
354
    index = index * (this.dataType.bits / 8);
355
    if (index + this.dataType.bits / 8 > this.data.samples.length) {
356
      throw new Error("Range error");
357
    }
358
    packTo(sample, this.dataType, this.data.samples, index, true);
359
  }
360
361
  /**
362
   * Return the value of the iXML chunk.
363
   * @return {string} The contents of the iXML chunk.
364
   */
365
  getiXML() {
366
    return this.iXML.value;
367
  }
368
369
  /**
370
   * Set the value of the iXML chunk.
371
   * @param {string} iXMLValue The value for the iXML chunk.
372
   * @throws {TypeError} If the value is not a string.
373
   */
374
  setiXML(iXMLValue) {
375
    if (typeof iXMLValue !== "string") {
376
      throw new TypeError("iXML value must be a string.");
377
    }
378
    this.iXML.value = iXMLValue;
379
    this.iXML.chunkId = "iXML";
380
  }
381
382
  /**
383
   * Get the value of the _PMX chunk.
384
   * @return {string} The contents of the _PMX chunk.
385
   */
386
  get_PMX() {
387
    return this._PMX.value;
388
  }
389
390
  /**
391
   * Set the value of the _PMX chunk.
392
   * @param {string} _PMXValue The value for the _PMX chunk.
393
   * @throws {TypeError} If the value is not a string.
394
   */
395
  set_PMX(_PMXValue) {
396
    if (typeof _PMXValue !== "string") {
397
      throw new TypeError("_PMX value must be a string.");
398
    }
399
    this._PMX.value = _PMXValue;
400
    this._PMX.chunkId = "_PMX";
401
  }
402
403
  /**
404
   * Set up the WaveFileCreator object based on the arguments passed.
405
   * @param {number} numChannels The number of channels.
406
   * @param {number} sampleRate The sample rate.
407
   *   Integers like 8000, 44100, 48000, 96000, 192000.
408
   * @param {string} bitDepthCode The audio bit depth code.
409
   *   One of '4', '8', '8a', '8m', '16', '24', '32', '32f', '64'
410
   *   or any value between '8' and '32' (like '12').
411
   * @param {!(Array|TypedArray)} samples The samples.
412
   * @param {Object} options Used to define the container.
413
   * @throws {Error} If any argument does not meet the criteria.
414
   * @private
415
   */
416
  newWavFile_(numChannels, sampleRate, bitDepthCode, samples, options) {
417
    if (!options.container) {
418
      options.container = "RIFF";
419
    }
420
    this.container = options.container;
421
    this.bitDepth = bitDepthCode;
422
    samples = interleave(samples);
423
    this.updateDataType_();
424
    /** @type {number} */
425
    let numBytes = this.dataType.bits / 8;
426
    this.data.samples = new Uint8Array(samples.length * numBytes);
427
    packArrayTo(samples, this.dataType, this.data.samples, 0, true);
428
    this.makeWavHeader_(
429
      bitDepthCode,
430
      numChannels,
431
      sampleRate,
432
      numBytes,
433
      this.data.samples.length,
434
      options
435
    );
436
    this.data.chunkId = "data";
437
    this.data.chunkSize = this.data.samples.length;
438
    this.validateWavHeader_();
439
  }
440
441
  /**
442
   * Define the header of a wav file.
443
   * @param {string} bitDepthCode The audio bit depth
444
   * @param {number} numChannels The number of channels
445
   * @param {number} sampleRate The sample rate.
446
   * @param {number} numBytes The number of bytes each sample use.
447
   * @param {number} samplesLength The length of the samples in bytes.
448
   * @param {!Object} options The extra options, like container defintion.
449
   * @private
450
   */
451
  makeWavHeader_(
452
    bitDepthCode,
453
    numChannels,
454
    sampleRate,
455
    numBytes,
456
    samplesLength,
457
    options
458
  ) {
459
    if (bitDepthCode == "4") {
460
      this.createADPCMHeader_(
461
        bitDepthCode,
462
        numChannels,
463
        sampleRate,
464
        numBytes,
465
        samplesLength,
466
        options
467
      );
468
    } else if (bitDepthCode == "8a" || bitDepthCode == "8m") {
469
      this.createALawMulawHeader_(
470
        bitDepthCode,
471
        numChannels,
472
        sampleRate,
473
        numBytes,
474
        samplesLength,
475
        options
476
      );
477
    } else if (
478
      Object.keys(this.WAV_AUDIO_FORMATS).indexOf(bitDepthCode) == -1 ||
479
      numChannels > 2
480
    ) {
481
      this.createExtensibleHeader_(
482
        bitDepthCode,
483
        numChannels,
484
        sampleRate,
485
        numBytes,
486
        samplesLength,
487
        options
488
      );
489
    } else {
490
      this.createPCMHeader_(
491
        bitDepthCode,
492
        numChannels,
493
        sampleRate,
494
        numBytes,
495
        samplesLength,
496
        options
497
      );
498
    }
499
  }
500
501
  /**
502
   * Create the header of a linear PCM wave file.
503
   * @param {string} bitDepthCode The audio bit depth
504
   * @param {number} numChannels The number of channels
505
   * @param {number} sampleRate The sample rate.
506
   * @param {number} numBytes The number of bytes each sample use.
507
   * @param {number} samplesLength The length of the samples in bytes.
508
   * @param {!Object} options The extra options, like container defintion.
509
   * @private
510
   */
511
  createPCMHeader_(
512
    bitDepthCode,
513
    numChannels,
514
    sampleRate,
515
    numBytes,
516
    samplesLength,
517
    options
518
  ) {
519
    this.container = options.container;
520
    this.chunkSize = 36 + samplesLength;
521
    this.format = "WAVE";
522
    this.bitDepth = bitDepthCode;
523
    this.fmt = {
524
      chunkId: "fmt ",
525
      chunkSize: 16,
526
      audioFormat: this.WAV_AUDIO_FORMATS[bitDepthCode] || 65534,
527
      numChannels: numChannels,
528
      sampleRate: sampleRate,
529
      byteRate: numChannels * numBytes * sampleRate,
530
      blockAlign: numChannels * numBytes,
531
      bitsPerSample: parseInt(bitDepthCode, 10),
532
      cbSize: 0,
533
      validBitsPerSample: 0,
534
      dwChannelMask: 0,
535
      subformat: []
536
    };
537
  }
538
539
  /**
540
   * Create the header of a ADPCM wave file.
541
   * @param {string} bitDepthCode The audio bit depth
542
   * @param {number} numChannels The number of channels
543
   * @param {number} sampleRate The sample rate.
544
   * @param {number} numBytes The number of bytes each sample use.
545
   * @param {number} samplesLength The length of the samples in bytes.
546
   * @param {!Object} options The extra options, like container defintion.
547
   * @private
548
   */
549
  createADPCMHeader_(
550
    bitDepthCode,
551
    numChannels,
552
    sampleRate,
553
    numBytes,
554
    samplesLength,
555
    options
556
  ) {
557
    this.createPCMHeader_(
558
      bitDepthCode,
559
      numChannels,
560
      sampleRate,
561
      numBytes,
562
      samplesLength,
563
      options
564
    );
565
    this.chunkSize = 40 + samplesLength;
566
    this.fmt.chunkSize = 20;
567
    this.fmt.byteRate = 4055;
568
    this.fmt.blockAlign = 256;
569
    this.fmt.bitsPerSample = 4;
570
    this.fmt.cbSize = 2;
571
    this.fmt.validBitsPerSample = 505;
572
    this.fact = {
573
      chunkId: "fact",
574
      chunkSize: 4,
575
      dwSampleLength: samplesLength * 2
576
    };
577
  }
578
579
  /**
580
   * Create the header of WAVE_FORMAT_EXTENSIBLE file.
581
   * @param {string} bitDepthCode The audio bit depth
582
   * @param {number} numChannels The number of channels
583
   * @param {number} sampleRate The sample rate.
584
   * @param {number} numBytes The number of bytes each sample use.
585
   * @param {number} samplesLength The length of the samples in bytes.
586
   * @param {!Object} options The extra options, like container defintion.
587
   * @private
588
   */
589
  createExtensibleHeader_(
590
    bitDepthCode,
591
    numChannels,
592
    sampleRate,
593
    numBytes,
594
    samplesLength,
595
    options
596
  ) {
597
    this.createPCMHeader_(
598
      bitDepthCode,
599
      numChannels,
600
      sampleRate,
601
      numBytes,
602
      samplesLength,
603
      options
604
    );
605
    this.chunkSize = 36 + 24 + samplesLength;
606
    this.fmt.chunkSize = 40;
607
    this.fmt.bitsPerSample = ((parseInt(bitDepthCode, 10) - 1) | 7) + 1;
608
    this.fmt.cbSize = 22;
609
    this.fmt.validBitsPerSample = parseInt(bitDepthCode, 10);
610
    this.fmt.dwChannelMask = dwChannelMask_(numChannels);
611
    // subformat 128-bit GUID as 4 32-bit values
612
    // only supports uncompressed integer PCM samples
613
    this.fmt.subformat = [1, 1048576, 2852126848, 1905997824];
614
  }
615
616
  /**
617
   * Create the header of mu-Law and A-Law wave files.
618
   * @param {string} bitDepthCode The audio bit depth
619
   * @param {number} numChannels The number of channels
620
   * @param {number} sampleRate The sample rate.
621
   * @param {number} numBytes The number of bytes each sample use.
622
   * @param {number} samplesLength The length of the samples in bytes.
623
   * @param {!Object} options The extra options, like container defintion.
624
   * @private
625
   */
626
  createALawMulawHeader_(
627
    bitDepthCode,
628
    numChannels,
629
    sampleRate,
630
    numBytes,
631
    samplesLength,
632
    options
633
  ) {
634
    this.createPCMHeader_(
635
      bitDepthCode,
636
      numChannels,
637
      sampleRate,
638
      numBytes,
639
      samplesLength,
640
      options
641
    );
642
    this.chunkSize = 40 + samplesLength;
643
    this.fmt.chunkSize = 20;
644
    this.fmt.cbSize = 2;
645
    this.fmt.validBitsPerSample = 8;
646
    this.fact = {
647
      chunkId: "fact",
648
      chunkSize: 4,
649
      dwSampleLength: samplesLength
650
    };
651
  }
652
653
  /**
654
   * Set the string code of the bit depth based on the 'fmt ' chunk.
655
   * @private
656
   */
657
  bitDepthFromFmt_() {
658
    if (this.fmt.audioFormat === 3 && this.fmt.bitsPerSample === 32) {
659
      this.bitDepth = "32f";
660
    } else if (this.fmt.audioFormat === 6) {
661
      this.bitDepth = "8a";
662
    } else if (this.fmt.audioFormat === 7) {
663
      this.bitDepth = "8m";
664
    } else if (this.fmt.audioFormat === 80) {
665
      this.bitDepth = "65535";
666
    } else {
667
      this.bitDepth = this.fmt.bitsPerSample.toString();
668
    }
669
  }
670
671
  /**
672
   * Validate the bit depth.
673
   * @return {boolean} True is the bit depth is valid.
674
   * @throws {Error} If bit depth is invalid.
675
   * @private
676
   */
677
  validateBitDepth_() {
678
    if (!this.WAV_AUDIO_FORMATS[this.bitDepth]) {
679
      if (parseInt(this.bitDepth, 10) > 8 && parseInt(this.bitDepth, 10) < 54) {
680
        return true;
681
      }
682
      throw new Error("Invalid bit depth.");
683
    }
684
    return true;
685
  }
686
687
  /**
688
   * Update the type definition used to read and write the samples.
689
   * @private
690
   */
691
  updateDataType_() {
692
    this.dataType = {
693
      bits: ((parseInt(this.bitDepth, 10) - 1) | 7) + 1,
694
      fp: this.bitDepth == "32f" || this.bitDepth == "64",
695
      signed: this.bitDepth != "8",
696
      be: this.container == "RIFX"
697
    };
698
    if (["4", "8a", "8m"].indexOf(this.bitDepth) > -1) {
699
      this.dataType.bits = 8;
700
      this.dataType.signed = false;
701
    }
702
  }
703
704
  /**
705
   * Validate the header of the file.
706
   * @throws {Error} If bit depth is invalid.
707
   * @throws {Error} If the number of channels is invalid.
708
   * @throws {Error} If the sample rate is invalid.
709
   * @ignore
710
   * @private
711
   */
712
  validateWavHeader_() {
713
    this.validateBitDepth_();
714
    if (!validateNumChannels(this.fmt.numChannels, this.fmt.bitsPerSample)) {
715
      throw new Error("Invalid number of channels.");
716
    }
717
    if (
718
      !validateSampleRate(
719
        this.fmt.numChannels,
720
        this.fmt.bitsPerSample,
721
        this.fmt.sampleRate
722
      )
723
    ) {
724
      throw new Error("Invalid sample rate.");
725
    }
726
  }
727
}
728
729
/**
730
 * Return the value for dwChannelMask according to the number of channels.
731
 * @param {number} numChannels the number of channels.
732
 * @return {number} the dwChannelMask value.
733
 * @private
734
 */
735
function dwChannelMask_(numChannels) {
736
  /** @type {number} */
737
  let mask = 0;
738
  // mono = FC
739
  if (numChannels === 1) {
740
    mask = 0x4;
741
    // stereo = FL, FR
742
  } else if (numChannels === 2) {
743
    mask = 0x3;
744
    // quad = FL, FR, BL, BR
745
  } else if (numChannels === 4) {
746
    mask = 0x33;
747
    // 5.1 = FL, FR, FC, LF, BL, BR
748
  } else if (numChannels === 6) {
749
    mask = 0x3f;
750
    // 7.1 = FL, FR, FC, LF, BL, BR, SL, SR
751
  } else if (numChannels === 8) {
752
    mask = 0x63f;
753
  }
754
  return mask;
755
}
756