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

WaveFileParser.getPostTimerBytes_   A

Complexity

Conditions 2

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 7
dl 0
loc 11
rs 10
c 0
b 0
f 0
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 WaveFileParser class.
27
 * @see https://github.com/rochars/wavefile
28
 */
29
30
import { WaveFileReader } from "./wavefile-reader";
31
import { writeString } from "./parsers/write-string";
32
import { packTo, packStringTo, packString, pack } from "./parsers/binary";
33
34
/**
35
 * A class to read and write wav files.
36
 * @extends WaveFileReader
37
 */
38
export class WaveFileParser extends WaveFileReader {
39
  /**
40
   * Return a byte buffer representig the WaveFileParser object as a .wav file.
41
   * The return value of this method can be written straight to disk.
42
   * @return {!Uint8Array} A wav file.
43
   */
44
  toBuffer() {
45
    this.uInt16.be = this.container === "RIFX";
46
    this.uInt32.be = this.uInt16.be;
47
    /** @type {!Array<!Array<number>>} */
48
    let fileBody = [
49
      this.getJunkBytes_(),
50
      this.getDs64Bytes_(),
51
      this.getBextBytes_(),
52
      this.getMextBytes_(),
53
      this.getCartBytes_(),
54
      this.getiXMLBytes_(),
55
      this.getFmtBytes_(),
56
      this.getFactBytes_(),
57
      packString(this.data.chunkId),
58
      pack(this.data.samples.length, this.uInt32),
59
      this.data.samples,
60
      this.getCueBytes_(),
61
      this.getSmplBytes_(),
62
      this.getLISTBytes_(),
63
      this.get_PMXBytes_()
64
    ];
65
    /** @type {number} */
66
    let fileBodyLength = 0;
67
    for (let i = 0; i < fileBody.length; i++) {
68
      fileBodyLength += fileBody[i].length;
69
    }
70
    /** @type {!Uint8Array} */
71
    let file = new Uint8Array(fileBodyLength + 12);
72
    /** @type {number} */
73
    let index = 0;
74
    index = packStringTo(this.container, file, index);
75
    index = packTo(fileBodyLength + 4, this.uInt32, file, index);
76
    index = packStringTo(this.format, file, index);
77
    for (let i = 0; i < fileBody.length; i++) {
78
      file.set(fileBody[i], index);
79
      index += fileBody[i].length;
80
    }
81
    return file;
82
  }
83
84
  /**
85
   * Return the bytes of the 'bext' chunk.
86
   * @private
87
   */
88
  getBextBytes_() {
89
    /** @type {!Array<number>} */
90
    let bytes = [];
91
    this.enforceBext_();
92
    if (this.bext.chunkId) {
93
      this.bext.chunkSize = 602 + this.bext.codingHistory.length;
94
      bytes = bytes.concat(
95
        packString(this.bext.chunkId),
96
        pack(602 + this.bext.codingHistory.length, this.uInt32),
97
        writeString(this.bext.description, 256),
98
        writeString(this.bext.originator, 32),
99
        writeString(this.bext.originatorReference, 32),
100
        writeString(this.bext.originationDate, 10),
101
        writeString(this.bext.originationTime, 8),
102
        pack(this.bext.timeReference[0], this.uInt32),
103
        pack(this.bext.timeReference[1], this.uInt32),
104
        pack(this.bext.version, this.uInt16),
105
        writeString(this.bext.UMID, 64),
106
        pack(this.bext.loudnessValue, this.uInt16),
107
        pack(this.bext.loudnessRange, this.uInt16),
108
        pack(this.bext.maxTruePeakLevel, this.uInt16),
109
        pack(this.bext.maxMomentaryLoudness, this.uInt16),
110
        pack(this.bext.maxShortTermLoudness, this.uInt16),
111
        writeString(this.bext.reserved, 180),
112
        writeString(this.bext.codingHistory, this.bext.codingHistory.length)
113
      );
114
    }
115
    this.enforceByteLen_(bytes);
116
    return bytes;
117
  }
118
119
  /**
120
   * Return the bytes of the 'mext' chunk.
121
   * @private
122
   */
123
  getMextBytes_() {
124
    /** @type {!Array<number>} */
125
    let bytes = [];
126
    if (this.mext.chunkId) {
127
      this.mext.chunkSize = 12;
128
      bytes = bytes.concat(
129
        packString(this.mext.chunkId),
130
        pack(this.mext.chunkSize, this.uInt32),
131
        pack(this.mext.soundInformation, this.uInt16),
132
        pack(this.mext.frameSize, this.uInt16),
133
        pack(this.mext.ancillaryDataLength, this.uInt16),
134
        pack(this.mext.ancillaryDataDef, this.uInt16),
135
        writeString(this.mext.reserved, 4)
136
      );
137
    }
138
    return bytes;
139
  }
140
141
  /**
142
   * Make sure a 'bext' chunk is created if BWF data was created in a file.
143
   * @private
144
   */
145
  enforceBext_() {
146
    for (let prop in this.bext) {
147
      if (this.bext.hasOwnProperty(prop)) {
148
        if (this.bext[prop] && prop != "timeReference") {
149
          this.bext.chunkId = "bext";
150
          break;
151
        }
152
      }
153
    }
154
    if (this.bext.timeReference[0] || this.bext.timeReference[1]) {
155
      this.bext.chunkId = "bext";
156
    }
157
  }
158
159
  /**
160
   * Return the bytes of the 'cart' chunk.
161
   * @private
162
   */
163
  getCartBytes_() {
164
    /** @type {!Array<number>} */
165
    let bytes = [];
166
    let postTimerBytes = this.getPostTimerBytes_();
167
    if (this.cart.chunkId) {
168
      this.cart.chunkSize = 2048 + this.cart.tagText.length;
169
      bytes = bytes.concat(
170
        packString(this.cart.chunkId),
171
        pack(this.cart.chunkSize, this.uInt32),
172
        writeString(this.cart.version, 4),
173
        writeString(this.cart.title, 64),
174
        writeString(this.cart.artist, 64),
175
        writeString(this.cart.cutId, 64),
176
        writeString(this.cart.clientId, 64),
177
        writeString(this.cart.category, 64),
178
        writeString(this.cart.classification, 64),
179
        writeString(this.cart.outCue, 64),
180
        writeString(this.cart.startDate, 10),
181
        writeString(this.cart.startTime, 8),
182
        writeString(this.cart.endDate, 10),
183
        writeString(this.cart.endTime, 8),
184
        writeString(this.cart.producerAppId, 64),
185
        writeString(this.cart.producerAppVersion, 64),
186
        writeString(this.cart.userDef, 64),
187
        pack(this.cart.levelReference, this.uInt32),
188
        postTimerBytes,
189
        writeString(this.cart.reserved, 276),
190
        writeString(this.cart.url, 1024),
191
        writeString(this.cart.tagText, this.cart.tagText.length)
192
      );
193
    }
194
    this.enforceByteLen_(bytes);
195
    return bytes;
196
  }
197
198
  /**
199
   * Return the bytes of the 'cart' postTimers
200
   * @return {!Array<number>} The 'cart' postTimers as an array of bytes.
201
   * @private
202
   */
203
  getPostTimerBytes_() {
204
    /** @type {!Array<number>} */
205
    let postTimers = [];
206
    for (let i = 0; i < this.cart.postTimer.length; i++) {
207
      postTimers = postTimers.concat(
208
        writeString(this.cart.postTimer[i].usage, 4),
209
        pack(this.cart.postTimer[i].value, this.uInt32)
210
      );
211
    }
212
    return postTimers;
213
  }
214
215
  /**
216
   * Return the bytes of the 'iXML' chunk.
217
   * @return {!Array<number>} The 'iXML' chunk bytes.
218
   * @private
219
   */
220
  getiXMLBytes_() {
221
    /** @type {!Array<number>} */
222
    let bytes = [];
223
    if (this.iXML.chunkId) {
224
      /** @type {!Array<number>} */
225
      let iXMLPackedValue = packString(this.iXML.value);
226
      this.iXML.chunkSize = iXMLPackedValue.length;
227
      bytes = bytes.concat(
228
        packString(this.iXML.chunkId),
229
        pack(this.iXML.chunkSize, this.uInt32),
230
        iXMLPackedValue
231
      );
232
    }
233
    this.enforceByteLen_(bytes);
234
    return bytes;
235
  }
236
237
  /**
238
   * Return the bytes of the 'ds64' chunk.
239
   * @return {!Array<number>} The 'ds64' chunk bytes.
240
   * @private
241
   */
242
  getDs64Bytes_() {
243
    /** @type {!Array<number>} */
244
    let bytes = [];
245
    if (this.ds64.chunkId) {
246
      bytes = bytes.concat(
247
        packString(this.ds64.chunkId),
248
        pack(this.ds64.chunkSize, this.uInt32),
249
        pack(this.ds64.riffSizeHigh, this.uInt32),
250
        pack(this.ds64.riffSizeLow, this.uInt32),
251
        pack(this.ds64.dataSizeHigh, this.uInt32),
252
        pack(this.ds64.dataSizeLow, this.uInt32),
253
        pack(this.ds64.originationTime, this.uInt32),
254
        pack(this.ds64.sampleCountHigh, this.uInt32),
255
        pack(this.ds64.sampleCountLow, this.uInt32)
256
      );
257
    }
258
    //if (this.ds64.tableLength) {
259
    //  ds64Bytes = ds64Bytes.concat(
260
    //    pack(this.ds64.tableLength, this.uInt32),
261
    //    this.ds64.table);
262
    //}
263
    this.enforceByteLen_(bytes);
264
    return bytes;
265
  }
266
267
  /**
268
   * Return the bytes of the 'cue ' chunk.
269
   * @return {!Array<number>} The 'cue ' chunk bytes.
270
   * @private
271
   */
272
  getCueBytes_() {
273
    /** @type {!Array<number>} */
274
    let bytes = [];
275
    if (this.cue.chunkId) {
276
      /** @type {!Array<number>} */
277
      let cuePointsBytes = this.getCuePointsBytes_();
278
      bytes = bytes.concat(
279
        packString(this.cue.chunkId),
280
        pack(cuePointsBytes.length + 4, this.uInt32), // chunkSize
281
        pack(this.cue.dwCuePoints, this.uInt32),
282
        cuePointsBytes
283
      );
284
    }
285
    this.enforceByteLen_(bytes);
286
    return bytes;
287
  }
288
289
  /**
290
   * Return the bytes of the 'cue ' points.
291
   * @return {!Array<number>} The 'cue ' points as an array of bytes.
292
   * @private
293
   */
294
  getCuePointsBytes_() {
295
    /** @type {!Array<number>} */
296
    let points = [];
297
    for (let i = 0; i < this.cue.dwCuePoints; i++) {
298
      points = points.concat(
299
        pack(this.cue.points[i].dwName, this.uInt32),
300
        pack(this.cue.points[i].dwPosition, this.uInt32),
301
        packString(this.cue.points[i].fccChunk),
302
        pack(this.cue.points[i].dwChunkStart, this.uInt32),
303
        pack(this.cue.points[i].dwBlockStart, this.uInt32),
304
        pack(this.cue.points[i].dwSampleOffset, this.uInt32)
305
      );
306
    }
307
    return points;
308
  }
309
310
  /**
311
   * Return the bytes of the 'smpl' chunk.
312
   * @return {!Array<number>} The 'smpl' chunk bytes.
313
   * @private
314
   */
315
  getSmplBytes_() {
316
    /** @type {!Array<number>} */
317
    let bytes = [];
318
    if (this.smpl.chunkId) {
319
      /** @type {!Array<number>} */
320
      let smplLoopsBytes = this.getSmplLoopsBytes_();
321
      bytes = bytes.concat(
322
        packString(this.smpl.chunkId),
323
        pack(smplLoopsBytes.length + 36, this.uInt32), //chunkSize
324
        pack(this.smpl.dwManufacturer, this.uInt32),
325
        pack(this.smpl.dwProduct, this.uInt32),
326
        pack(this.smpl.dwSamplePeriod, this.uInt32),
327
        pack(this.smpl.dwMIDIUnityNote, this.uInt32),
328
        pack(this.smpl.dwMIDIPitchFraction, this.uInt32),
329
        pack(this.smpl.dwSMPTEFormat, this.uInt32),
330
        pack(this.smpl.dwSMPTEOffset, this.uInt32),
331
        pack(this.smpl.dwNumSampleLoops, this.uInt32),
332
        pack(this.smpl.dwSamplerData, this.uInt32),
333
        smplLoopsBytes
334
      );
335
    }
336
    this.enforceByteLen_(bytes);
337
    return bytes;
338
  }
339
340
  /**
341
   * Return the bytes of the 'smpl' loops.
342
   * @return {!Array<number>} The 'smpl' loops as an array of bytes.
343
   * @private
344
   */
345
  getSmplLoopsBytes_() {
346
    /** @type {!Array<number>} */
347
    let loops = [];
348
    for (let i = 0; i < this.smpl.dwNumSampleLoops; i++) {
349
      loops = loops.concat(
350
        pack(this.smpl.loops[i].dwName, this.uInt32),
351
        pack(this.smpl.loops[i].dwType, this.uInt32),
352
        pack(this.smpl.loops[i].dwStart, this.uInt32),
353
        pack(this.smpl.loops[i].dwEnd, this.uInt32),
354
        pack(this.smpl.loops[i].dwFraction, this.uInt32),
355
        pack(this.smpl.loops[i].dwPlayCount, this.uInt32)
356
      );
357
    }
358
    return loops;
359
  }
360
361
  /**
362
   * Return the bytes of the 'fact' chunk.
363
   * @return {!Array<number>} The 'fact' chunk bytes.
364
   * @private
365
   */
366
  getFactBytes_() {
367
    /** @type {!Array<number>} */
368
    let bytes = [];
369
    if (this.fact.chunkId) {
370
      bytes = bytes.concat(
371
        packString(this.fact.chunkId),
372
        pack(this.fact.chunkSize, this.uInt32),
373
        pack(this.fact.dwSampleLength, this.uInt32)
374
      );
375
    }
376
    this.enforceByteLen_(bytes);
377
    return bytes;
378
  }
379
380
  /**
381
   * Return the bytes of the 'fmt ' chunk.
382
   * @return {!Array<number>} The 'fmt' chunk bytes.
383
   * @throws {Error} if no 'fmt ' chunk is present.
384
   * @private
385
   */
386
  getFmtBytes_() {
387
    /** @type {!Array<number>} */
388
    let fmtBytes = [];
389
    if (this.fmt.chunkId) {
390
      /** @type {!Array<number>} */
391
      let bytes = fmtBytes.concat(
392
        packString(this.fmt.chunkId),
393
        pack(this.fmt.chunkSize, this.uInt32),
394
        pack(this.fmt.audioFormat, this.uInt16),
395
        pack(this.fmt.numChannels, this.uInt16),
396
        pack(this.fmt.sampleRate, this.uInt32),
397
        pack(this.fmt.byteRate, this.uInt32),
398
        pack(this.fmt.blockAlign, this.uInt16),
399
        pack(this.fmt.bitsPerSample, this.uInt16),
400
        this.getFmtExtensionBytes_()
401
      );
402
      this.enforceByteLen_(bytes);
403
      return bytes;
404
    }
405
    throw Error('Could not find the "fmt " chunk');
406
  }
407
408
  /**
409
   * Return the bytes of the fmt extension fields.
410
   * @return {!Array<number>} The fmt extension bytes.
411
   * @private
412
   */
413
  getFmtExtensionBytes_() {
414
    /** @type {!Array<number>} */
415
    let extension = [];
416
    if (this.fmt.chunkSize > 16) {
417
      extension = extension.concat(pack(this.fmt.cbSize, this.uInt16));
418
    }
419
    if (this.fmt.audioFormat == 80 && this.fmt.chunkSize == 40) {
420
      extension = extension.concat(
421
        pack(this.fmt.headLayer, this.uInt16),
422
        pack(this.fmt.headBitRate, this.uInt32),
423
        pack(this.fmt.headMode, this.uInt16),
424
        pack(this.fmt.headModeExt, this.uInt16),
425
        pack(this.fmt.headEmphasis, this.uInt16),
426
        pack(this.fmt.headFlags, this.uInt16),
427
        pack(this.fmt.ptsLow, this.uInt32),
428
        pack(this.fmt.ptsHigh, this.uInt32)
429
      );
430
    } else {
431
      if (this.fmt.chunkSize > 18) {
432
        extension = extension.concat(
433
          pack(this.fmt.validBitsPerSample, this.uInt16)
434
        );
435
      }
436
      if (this.fmt.chunkSize > 20) {
437
        extension = extension.concat(pack(this.fmt.dwChannelMask, this.uInt32));
438
      }
439
      if (this.fmt.chunkSize > 24) {
440
        extension = extension.concat(
441
          pack(this.fmt.subformat[0], this.uInt32),
442
          pack(this.fmt.subformat[1], this.uInt32),
443
          pack(this.fmt.subformat[2], this.uInt32),
444
          pack(this.fmt.subformat[3], this.uInt32)
445
        );
446
      }
447
    }
448
    return extension;
449
  }
450
451
  /**
452
   * Return the bytes of the 'LIST' chunk.
453
   * @return {!Array<number>} The 'LIST' chunk bytes.
454
   * @private
455
   */
456
  getLISTBytes_() {
457
    /** @type {!Array<number>} */
458
    let bytes = [];
459
    for (let i = 0; i < this.LIST.length; i++) {
460
      /** @type {!Array<number>} */
461
      let subChunksBytes = this.getLISTSubChunksBytes_(
462
        this.LIST[i].subChunks,
463
        this.LIST[i].format
464
      );
465
      bytes = bytes.concat(
466
        packString(this.LIST[i].chunkId),
467
        pack(subChunksBytes.length + 4, this.uInt32), //chunkSize
468
        packString(this.LIST[i].format),
469
        subChunksBytes
470
      );
471
    }
472
    this.enforceByteLen_(bytes);
473
    return bytes;
474
  }
475
476
  /**
477
   * Return the bytes of the sub chunks of a 'LIST' chunk.
478
   * @param {!Array<!Object>} subChunks The 'LIST' sub chunks.
479
   * @param {string} format The format of the 'LIST' chunk.
480
   *    Currently supported values are 'adtl' or 'INFO'.
481
   * @return {!Array<number>} The sub chunk bytes.
482
   * @private
483
   */
484
  getLISTSubChunksBytes_(subChunks, format) {
485
    /** @type {!Array<number>} */
486
    let bytes = [];
487
    for (let i = 0, len = subChunks.length; i < len; i++) {
488
      if (format == "INFO") {
489
        bytes = bytes.concat(this.getLISTINFOSubChunksBytes_(subChunks[i]));
490
      } else if (format == "adtl") {
491
        bytes = bytes.concat(this.getLISTadtlSubChunksBytes_(subChunks[i]));
492
      }
493
      this.enforceByteLen_(bytes);
494
    }
495
    return bytes;
496
  }
497
498
  /**
499
   * Return the bytes of the sub chunks of a 'LIST' chunk of type 'INFO'.
500
   * @param {!Object} subChunk The 'LIST' sub chunk.
501
   * @return {!Array<number>}
502
   * @private
503
   */
504
  getLISTINFOSubChunksBytes_(subChunk) {
505
    /** @type {!Array<number>} */
506
    let bytes = [];
507
    /** @type {!Array<number>} */
508
    let LISTsubChunkValue = writeString(subChunk.value, subChunk.value.length);
509
    bytes = bytes.concat(
510
      packString(subChunk.chunkId),
511
      pack(LISTsubChunkValue.length + 1, this.uInt32), //chunkSize
512
      LISTsubChunkValue
513
    );
514
    bytes.push(0);
515
    return bytes;
516
  }
517
518
  /**
519
   * Return the bytes of the sub chunks of a 'LIST' chunk of type 'INFO'.
520
   * @param {!Object} subChunk The 'LIST' sub chunk.
521
   * @return {!Array<number>}
522
   * @private
523
   */
524
  getLISTadtlSubChunksBytes_(subChunk) {
525
    /** @type {!Array<number>} */
526
    let bytes = [];
527
    if (["labl", "note"].indexOf(subChunk.chunkId) > -1) {
528
      /** @type {!Array<number>} */
529
      let LISTsubChunkValue = writeString(
530
        subChunk.value,
531
        subChunk.value.length
532
      );
533
      bytes = bytes.concat(
534
        packString(subChunk.chunkId),
535
        pack(LISTsubChunkValue.length + 4 + 1, this.uInt32), //chunkSize
536
        pack(subChunk.dwName, this.uInt32),
537
        LISTsubChunkValue
538
      );
539
      bytes.push(0);
540
    } else if (subChunk.chunkId == "ltxt") {
541
      bytes = bytes.concat(this.getLtxtChunkBytes_(subChunk));
542
    }
543
    return bytes;
544
  }
545
546
  /**
547
   * Return the bytes of a 'ltxt' chunk.
548
   * @param {!Object} ltxt the 'ltxt' chunk.
549
   * @return {!Array<number>}
550
   * @private
551
   */
552
  getLtxtChunkBytes_(ltxt) {
553
    return [].concat(
554
      packString(ltxt.chunkId),
555
      pack(ltxt.value.length + 20, this.uInt32),
556
      pack(ltxt.dwName, this.uInt32),
557
      pack(ltxt.dwSampleLength, this.uInt32),
558
      pack(ltxt.dwPurposeID, this.uInt32),
559
      pack(ltxt.dwCountry, this.uInt16),
560
      pack(ltxt.dwLanguage, this.uInt16),
561
      pack(ltxt.dwDialect, this.uInt16),
562
      pack(ltxt.dwCodePage, this.uInt16),
563
      // should always be a empty string;
564
      // kept for compatibility
565
      writeString(ltxt.value, ltxt.value.length)
566
    );
567
  }
568
569
  /**
570
   * Return the bytes of the '_PMX' chunk.
571
   * @return {!Array<number>} The '_PMX' chunk bytes.
572
   * @private
573
   */
574
  get_PMXBytes_() {
575
    /** @type {!Array<number>} */
576
    let bytes = [];
577
    if (this._PMX.chunkId) {
578
      /** @type {!Array<number>} */
579
      let _PMXPackedValue = packString(this._PMX.value);
580
      this._PMX.chunkSize = _PMXPackedValue.length;
581
      bytes = bytes.concat(
582
        packString(this._PMX.chunkId),
583
        pack(this._PMX.chunkSize, this.uInt32),
584
        _PMXPackedValue
585
      );
586
    }
587
    this.enforceByteLen_(bytes);
588
    return bytes;
589
  }
590
591
  /**
592
   * Return the bytes of the 'junk' chunk.
593
   * @private
594
   */
595
  getJunkBytes_() {
596
    /** @type {!Array<number>} */
597
    let bytes = [];
598
    if (this.junk.chunkId) {
599
      return bytes.concat(
600
        packString(this.junk.chunkId),
601
        pack(this.junk.chunkData.length, this.uInt32), //chunkSize
602
        this.junk.chunkData
603
      );
604
    }
605
    this.enforceByteLen_(bytes);
606
    return bytes;
607
  }
608
609
  /**
610
   * Push a null byte into a byte array if
611
   * the byte count is odd.
612
   * @param {!Array<number>} bytes The byte array.
613
   * @private
614
   */
615
  enforceByteLen_(bytes) {
616
    if (bytes.length % 2) {
617
      bytes.push(0);
618
    }
619
  }
620
}
621