Completed
Push — master ( a73567...f014e9 )
by Rafael S.
02:49
created

WaveFileReader.readiXMLChunk_   A

Complexity

Conditions 2

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 11
rs 10
c 0
b 0
f 0
cc 2
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 WaveFileReader class.
27
 * @see https://github.com/rochars/wavefile
28
 */
29
30
import { RIFFFile } from './riff-file';
31
import { unpackString, unpack } from 'byte-data';
32
33
/**
34
 * A class to read wav files.
35
 * @extends RIFFFile
36
 */
37
export class WaveFileReader extends RIFFFile {
38
39
  constructor() {
40
    super();
41
    // Include 'RF64' as a supported container format
42
    this.supported_containers.push('RF64');
43
    /**
44
     * The data of the 'fmt' chunk.
45
     * @type {!Object<string, *>}
46
     */
47
    this.fmt = {
48
      /** @type {string} */
49
      chunkId: '',
50
      /** @type {number} */
51
      chunkSize: 0,
52
      /** @type {number} */
53
      audioFormat: 0,
54
      /** @type {number} */
55
      numChannels: 0,
56
      /** @type {number} */
57
      sampleRate: 0,
58
      /** @type {number} */
59
      byteRate: 0,
60
      /** @type {number} */
61
      blockAlign: 0,
62
      /** @type {number} */
63
      bitsPerSample: 0,
64
      /** @type {number} */
65
      cbSize: 0,
66
      /** @type {number} */
67
      validBitsPerSample: 0,
68
      /** @type {number} */
69
      dwChannelMask: 0,
70
      /**
71
       * 4 32-bit values representing a 128-bit ID
72
       * @type {!Array<number>}
73
       */
74
      subformat: []
75
    };
76
    /**
77
     * The data of the 'fact' chunk.
78
     * @type {!Object<string, *>}
79
     */
80
    this.fact = {
81
      /** @type {string} */
82
      chunkId: '',
83
      /** @type {number} */
84
      chunkSize: 0,
85
      /** @type {number} */
86
      dwSampleLength: 0
87
    };
88
    /**
89
     * The data of the 'cue ' chunk.
90
     * @type {!Object<string, *>}
91
     */
92
    this.cue = {
93
      /** @type {string} */
94
      chunkId: '',
95
      /** @type {number} */
96
      chunkSize: 0,
97
      /** @type {number} */
98
      dwCuePoints: 0,
99
      /** @type {!Array<!Object>} */
100
      points: [],
101
    };
102
    /**
103
     * The data of the 'smpl' chunk.
104
     * @type {!Object<string, *>}
105
     */
106
    this.smpl = {
107
      /** @type {string} */
108
      chunkId: '',
109
      /** @type {number} */
110
      chunkSize: 0,
111
      /** @type {number} */
112
      dwManufacturer: 0,
113
      /** @type {number} */
114
      dwProduct: 0,
115
      /** @type {number} */
116
      dwSamplePeriod: 0,
117
      /** @type {number} */
118
      dwMIDIUnityNote: 0,
119
      /** @type {number} */
120
      dwMIDIPitchFraction: 0,
121
      /** @type {number} */
122
      dwSMPTEFormat: 0,
123
      /** @type {number} */
124
      dwSMPTEOffset: 0,
125
      /** @type {number} */
126
      dwNumSampleLoops: 0,
127
      /** @type {number} */
128
      dwSamplerData: 0,
129
      /** @type {!Array<!Object>} */
130
      loops: []
131
    };
132
    /**
133
     * The data of the 'bext' chunk.
134
     * @type {!Object<string, *>}
135
     */
136
    this.bext = {
137
      /** @type {string} */
138
      chunkId: '',
139
      /** @type {number} */
140
      chunkSize: 0,
141
      /** @type {string} */
142
      description: '', //256
143
      /** @type {string} */
144
      originator: '', //32
145
      /** @type {string} */
146
      originatorReference: '', //32
147
      /** @type {string} */
148
      originationDate: '', //10
149
      /** @type {string} */
150
      originationTime: '', //8
151
      /**
152
       * 2 32-bit values, timeReference high and low
153
       * @type {!Array<number>}
154
       */
155
      timeReference: [0, 0],
156
      /** @type {number} */
157
      version: 0, //WORD
158
      /** @type {string} */
159
      UMID: '', // 64 chars
160
      /** @type {number} */
161
      loudnessValue: 0, //WORD
162
      /** @type {number} */
163
      loudnessRange: 0, //WORD
164
      /** @type {number} */
165
      maxTruePeakLevel: 0, //WORD
166
      /** @type {number} */
167
      maxMomentaryLoudness: 0, //WORD
168
      /** @type {number} */
169
      maxShortTermLoudness: 0, //WORD
170
      /** @type {string} */
171
      reserved: '', //180
172
      /** @type {string} */
173
      codingHistory: '' // string, unlimited
174
    };
175
    /**
176
     * The data of the 'iXML' chunk.
177
     * @type {!Object<string, *>}
178
     */
179
    this.iXML = {
180
      /** @type {string} */
181
      chunkId: '',
182
      /** @type {number} */
183
      chunkSize: 0,
184
      /** @type {string} */
185
      value: ''
186
    };
187
    /**
188
     * The data of the 'ds64' chunk.
189
     * Used only with RF64 files.
190
     * @type {!Object<string, *>}
191
     */
192
    this.ds64 = {
193
      /** @type {string} */
194
      chunkId: '',
195
      /** @type {number} */
196
      chunkSize: 0,
197
      /** @type {number} */
198
      riffSizeHigh: 0, // DWORD
199
      /** @type {number} */
200
      riffSizeLow: 0, // DWORD
201
      /** @type {number} */
202
      dataSizeHigh: 0, // DWORD
203
      /** @type {number} */
204
      dataSizeLow: 0, // DWORD
205
      /** @type {number} */
206
      originationTime: 0, // DWORD
207
      /** @type {number} */
208
      sampleCountHigh: 0, // DWORD
209
      /** @type {number} */
210
      sampleCountLow: 0 // DWORD
211
      /** @type {number} */
212
      //'tableLength': 0, // DWORD
213
      /** @type {!Array<number>} */
214
      //'table': []
215
    };
216
    /**
217
     * The data of the 'data' chunk.
218
     * @type {!Object<string, *>}
219
     */
220
    this.data = {
221
      /** @type {string} */
222
      chunkId: '',
223
      /** @type {number} */
224
      chunkSize: 0,
225
      /** @type {!Uint8Array} */
226
      samples: new Uint8Array(0)
227
    };
228
    /**
229
     * The data of the 'LIST' chunks.
230
     * Each item in this list look like this:
231
     *  {
232
     *      chunkId: '',
233
     *      chunkSize: 0,
234
     *      format: '',
235
     *      subChunks: []
236
     *   }
237
     * @type {!Array<!Object>}
238
     */
239
    this.LIST = [];
240
    /**
241
     * The data of the 'junk' chunk.
242
     * @type {!Object<string, *>}
243
     */
244
    this.junk = {
245
      /** @type {string} */
246
      chunkId: '',
247
      /** @type {number} */
248
      chunkSize: 0,
249
      /** @type {!Array<number>} */
250
      chunkData: []
251
    };
252
    /**
253
     * The data of the '_PMX' chunk.
254
     * @type {!Object<string, *>}
255
     */
256
    this._PMX = {
257
      /** @type {string} */
258
      chunkId: '',
259
      /** @type {number} */
260
      chunkSize: 0,
261
      /** @type {string} */
262
      value: ''
263
    };
264
    /**
265
     * @type {{be: boolean, bits: number, fp: boolean, signed: boolean}}
266
     * @protected
267
     */
268
    this.uInt16 = {bits: 16, be: false, signed: false, fp: false};
269
  }
270
271
  /**
272
   * Set up the WaveFileReader object from a byte buffer.
273
   * @param {!Uint8Array} wavBuffer The buffer.
274
   * @param {boolean=} samples True if the samples should be loaded.
275
   * @throws {Error} If container is not RIFF, RIFX or RF64.
276
   * @throws {Error} If format is not WAVE.
277
   * @throws {Error} If no 'fmt ' chunk is found.
278
   * @throws {Error} If no 'data' chunk is found.
279
   */
280
  fromBuffer(wavBuffer, samples=true) {
281
    // Always should reset the chunks when reading from a buffer
282
    this.clearHeaders();
283
    this.setSignature(wavBuffer);
284
    this.uInt16.be = this.uInt32.be;
285
    if (this.format != 'WAVE') {
286
      throw Error('Could not find the "WAVE" format identifier');
287
    }
288
    this.readDs64Chunk_(wavBuffer);
289
    this.readFmtChunk_(wavBuffer);
290
    this.readFactChunk_(wavBuffer);
291
    this.readBextChunk_(wavBuffer);
292
    this.readiXMLChunk_(wavBuffer);
293
    this.readCueChunk_(wavBuffer);
294
    this.readSmplChunk_(wavBuffer);
295
    this.readDataChunk_(wavBuffer, samples);
296
    this.readJunkChunk_(wavBuffer);
297
    this.readLISTChunk_(wavBuffer);
298
    this.read_PMXChunk_(wavBuffer);
299
  }
300
301
  /**
302
   * Reset the chunks of the WaveFileReader instance.
303
   * @protected
304
   * @ignore
305
   */
306
  clearHeaders() {
307
    let tmpWav = new WaveFileReader();
308
    Object.assign(this.fmt, tmpWav.fmt);
309
    Object.assign(this.fact, tmpWav.fact);
310
    Object.assign(this.cue, tmpWav.cue);
311
    Object.assign(this.smpl, tmpWav.smpl);
312
    Object.assign(this.bext, tmpWav.bext);
313
    Object.assign(this.iXML, tmpWav.iXML);
314
    Object.assign(this.ds64, tmpWav.ds64);
315
    Object.assign(this.data, tmpWav.data);
316
    this.LIST = [];
317
    Object.assign(this.junk, tmpWav.junk);
318
    Object.assign(this._PMX, tmpWav._PMX);
319
  }
320
  
321
  /**
322
   * Read the 'fmt ' chunk of a wave file.
323
   * @param {!Uint8Array} buffer The wav file buffer.
324
   * @throws {Error} If no 'fmt ' chunk is found.
325
   * @private
326
   */
327
  readFmtChunk_(buffer) {
328
    /** @type {?Object} */
329
    let chunk = this.findChunk('fmt ');
330
    if (chunk) {
331
      this.head = chunk.chunkData.start;
332
      this.fmt.chunkId = chunk.chunkId;
333
      this.fmt.chunkSize = chunk.chunkSize;
334
      this.fmt.audioFormat = this.readUInt16_(buffer);
335
      this.fmt.numChannels = this.readUInt16_(buffer);
336
      this.fmt.sampleRate = this.readUInt32(buffer);
337
      this.fmt.byteRate = this.readUInt32(buffer);
338
      this.fmt.blockAlign = this.readUInt16_(buffer);
339
      this.fmt.bitsPerSample = this.readUInt16_(buffer);
340
      this.readFmtExtension_(buffer);
341
    } else {
342
      throw Error('Could not find the "fmt " chunk');
343
    }
344
  }
345
346
  /**
347
   * Read the 'fmt ' chunk extension.
348
   * @param {!Uint8Array} buffer The wav file buffer.
349
   * @private
350
   */
351
  readFmtExtension_(buffer) {
352
    if (this.fmt.chunkSize > 16) {
353
      this.fmt.cbSize = this.readUInt16_(buffer);
354
      if (this.fmt.chunkSize > 18) {
355
        this.fmt.validBitsPerSample = this.readUInt16_(buffer);
356
        if (this.fmt.chunkSize > 20) {
357
          this.fmt.dwChannelMask = this.readUInt32(buffer);
358
          this.fmt.subformat = [
359
            this.readUInt32(buffer),
360
            this.readUInt32(buffer),
361
            this.readUInt32(buffer),
362
            this.readUInt32(buffer)];
363
        }
364
      }
365
    }
366
  }
367
368
  /**
369
   * Read the 'fact' chunk of a wav file.
370
   * @param {!Uint8Array} buffer The wav file buffer.
371
   * @private
372
   */
373
  readFactChunk_(buffer) {
374
    /** @type {?Object} */
375
    let chunk = this.findChunk('fact');
376
    if (chunk) {
377
      this.head = chunk.chunkData.start;
378
      this.fact.chunkId = chunk.chunkId;
379
      this.fact.chunkSize = chunk.chunkSize;
380
      this.fact.dwSampleLength = this.readUInt32(buffer);
381
    }
382
  }
383
384
  /**
385
   * Read the 'cue ' chunk of a wave file.
386
   * @param {!Uint8Array} buffer The wav file buffer.
387
   * @private
388
   */
389
  readCueChunk_(buffer) {
390
    /** @type {?Object} */
391
    let chunk = this.findChunk('cue ');
392
    if (chunk) {
393
      this.head = chunk.chunkData.start;
394
      this.cue.chunkId = chunk.chunkId;
395
      this.cue.chunkSize = chunk.chunkSize;
396
      this.cue.dwCuePoints = this.readUInt32(buffer);
397
      for (let i = 0; i < this.cue.dwCuePoints; i++) {
398
        this.cue.points.push({
399
          dwName: this.readUInt32(buffer),
400
          dwPosition: this.readUInt32(buffer),
401
          fccChunk: this.readString(buffer, 4),
402
          dwChunkStart: this.readUInt32(buffer),
403
          dwBlockStart: this.readUInt32(buffer),
404
          dwSampleOffset: this.readUInt32(buffer),
405
        });
406
      }
407
    }
408
  }
409
410
  /**
411
   * Read the 'smpl' chunk of a wave file.
412
   * @param {!Uint8Array} buffer The wav file buffer.
413
   * @private
414
   */
415
  readSmplChunk_(buffer) {
416
    /** @type {?Object} */
417
    let chunk = this.findChunk('smpl');
418
    if (chunk) {
419
      this.head = chunk.chunkData.start;
420
      this.smpl.chunkId = chunk.chunkId;
421
      this.smpl.chunkSize = chunk.chunkSize;
422
      this.smpl.dwManufacturer = this.readUInt32(buffer);
423
      this.smpl.dwProduct = this.readUInt32(buffer);
424
      this.smpl.dwSamplePeriod = this.readUInt32(buffer);
425
      this.smpl.dwMIDIUnityNote = this.readUInt32(buffer);
426
      this.smpl.dwMIDIPitchFraction = this.readUInt32(buffer);
427
      this.smpl.dwSMPTEFormat = this.readUInt32(buffer);
428
      this.smpl.dwSMPTEOffset = this.readUInt32(buffer);
429
      this.smpl.dwNumSampleLoops = this.readUInt32(buffer);
430
      this.smpl.dwSamplerData = this.readUInt32(buffer);
431
      for (let i = 0; i < this.smpl.dwNumSampleLoops; i++) {
432
        this.smpl.loops.push({
433
          dwName: this.readUInt32(buffer),
434
          dwType: this.readUInt32(buffer),
435
          dwStart: this.readUInt32(buffer),
436
          dwEnd: this.readUInt32(buffer),
437
          dwFraction: this.readUInt32(buffer),
438
          dwPlayCount: this.readUInt32(buffer),
439
        });
440
      }
441
    }
442
  }
443
444
  /**
445
   * Read the 'data' chunk of a wave file.
446
   * @param {!Uint8Array} buffer The wav file buffer.
447
   * @param {boolean} samples True if the samples should be loaded.
448
   * @throws {Error} If no 'data' chunk is found.
449
   * @private
450
   */
451
  readDataChunk_(buffer, samples) {
452
    /** @type {?Object} */
453
    let chunk = this.findChunk('data');
454
    if (chunk) {
455
      this.data.chunkId = 'data';
456
      this.data.chunkSize = chunk.chunkSize;
457
      if (samples) {
458
        this.data.samples = buffer.slice(
459
          chunk.chunkData.start,
460
          chunk.chunkData.end);
461
      }
462
    } else {
463
      throw Error('Could not find the "data" chunk');
464
    }
465
  }
466
467
  /**
468
   * Read the 'bext' chunk of a wav file.
469
   * @param {!Uint8Array} buffer The wav file buffer.
470
   * @private
471
   */
472
  readBextChunk_(buffer) {
473
    /** @type {?Object} */
474
    let chunk = this.findChunk('bext');
475
    if (chunk) {
476
      this.head = chunk.chunkData.start;
477
      this.bext.chunkId = chunk.chunkId;
478
      this.bext.chunkSize = chunk.chunkSize;
479
      this.bext.description = this.readString(buffer, 256);
480
      this.bext.originator = this.readString(buffer, 32);
481
      this.bext.originatorReference = this.readString(buffer, 32);
482
      this.bext.originationDate = this.readString(buffer, 10);
483
      this.bext.originationTime = this.readString(buffer, 8);
484
      this.bext.timeReference = [
485
        this.readUInt32(buffer),
486
        this.readUInt32(buffer)];
487
      this.bext.version = this.readUInt16_(buffer);
488
      this.bext.UMID = this.readString(buffer, 64);
489
      this.bext.loudnessValue = this.readUInt16_(buffer);
490
      this.bext.loudnessRange = this.readUInt16_(buffer);
491
      this.bext.maxTruePeakLevel = this.readUInt16_(buffer);
492
      this.bext.maxMomentaryLoudness = this.readUInt16_(buffer);
493
      this.bext.maxShortTermLoudness = this.readUInt16_(buffer);
494
      this.bext.reserved = this.readString(buffer, 180);
495
      this.bext.codingHistory = this.readString(
496
        buffer, this.bext.chunkSize - 602);
497
    }
498
  }
499
500
  /**
501
   * Read the 'iXML' chunk of a wav file.
502
   * @param {!Uint8Array} buffer The wav file buffer.
503
   * @private
504
   */
505
  readiXMLChunk_(buffer) {
506
    /** @type {?Object} */
507
    let chunk = this.findChunk('iXML');
508
    if (chunk) {
509
      this.head = chunk.chunkData.start;
510
      this.iXML.chunkId = chunk.chunkId;
511
      this.iXML.chunkSize = chunk.chunkSize;
512
      this.iXML.value = unpackString(
513
        buffer, this.head, this.head + this.iXML.chunkSize);
514
    }
515
  }
516
517
  /**
518
   * Read the 'ds64' chunk of a wave file.
519
   * @param {!Uint8Array} buffer The wav file buffer.
520
   * @throws {Error} If no 'ds64' chunk is found and the file is RF64.
521
   * @private
522
   */
523
  readDs64Chunk_(buffer) {
524
    /** @type {?Object} */
525
    let chunk = this.findChunk('ds64');
526
    if (chunk) {
527
      this.head = chunk.chunkData.start;
528
      this.ds64.chunkId = chunk.chunkId;
529
      this.ds64.chunkSize = chunk.chunkSize;
530
      this.ds64.riffSizeHigh = this.readUInt32(buffer);
531
      this.ds64.riffSizeLow = this.readUInt32(buffer);
532
      this.ds64.dataSizeHigh = this.readUInt32(buffer);
533
      this.ds64.dataSizeLow = this.readUInt32(buffer);
534
      this.ds64.originationTime = this.readUInt32(buffer);
535
      this.ds64.sampleCountHigh = this.readUInt32(buffer);
536
      this.ds64.sampleCountLow = this.readUInt32(buffer);
537
      //if (wav.ds64.chunkSize > 28) {
538
      //  wav.ds64.tableLength = unpack(
539
      //    chunkData.slice(28, 32), uInt32_);
540
      //  wav.ds64.table = chunkData.slice(
541
      //     32, 32 + wav.ds64.tableLength);
542
      //}
543
    } else {
544
      if (this.container == 'RF64') {
545
        throw Error('Could not find the "ds64" chunk');
546
      }
547
    }
548
  }
549
550
  /**
551
   * Read the 'LIST' chunks of a wave file.
552
   * @param {!Uint8Array} buffer The wav file buffer.
553
   * @private
554
   */
555
  readLISTChunk_(buffer) {
556
    /** @type {?Object} */
557
    let listChunks = this.findChunk('LIST', true);
558
    if (listChunks !== null) {
559
      for (let j=0; j < listChunks.length; j++) {
560
        /** @type {!Object} */
561
        let subChunk = listChunks[j];
562
        this.LIST.push({
563
          chunkId: subChunk.chunkId,
564
          chunkSize: subChunk.chunkSize,
565
          format: subChunk.format,
566
          subChunks: []});
567
        for (let x=0; x<subChunk.subChunks.length; x++) {
568
          this.readLISTSubChunks_(subChunk.subChunks[x],
569
            subChunk.format, buffer);
570
        }
571
      }
572
    }
573
  }
574
575
  /**
576
   * Read the sub chunks of a 'LIST' chunk.
577
   * @param {!Object} subChunk The 'LIST' subchunks.
578
   * @param {string} format The 'LIST' format, 'adtl' or 'INFO'.
579
   * @param {!Uint8Array} buffer The wav file buffer.
580
   * @private
581
   */
582
  readLISTSubChunks_(subChunk, format, buffer) {
583
    if (format == 'adtl') {
584
      if (['labl', 'note','ltxt'].indexOf(subChunk.chunkId) > -1) {
585
        this.head = subChunk.chunkData.start;
586
        /** @type {!Object<string, string|number>} */
587
        let item = {
588
          chunkId: subChunk.chunkId,
589
          chunkSize: subChunk.chunkSize,
590
          dwName: this.readUInt32(buffer)
591
        };
592
        if (subChunk.chunkId == 'ltxt') {
593
          item.dwSampleLength = this.readUInt32(buffer);
594
          item.dwPurposeID = this.readUInt32(buffer);
595
          item.dwCountry = this.readUInt16_(buffer);
596
          item.dwLanguage = this.readUInt16_(buffer);
597
          item.dwDialect = this.readUInt16_(buffer);
598
          item.dwCodePage = this.readUInt16_(buffer);
599
          item.value = ''; // kept for compatibility
600
        } else {
601
          item.value = this.readZSTR_(buffer, this.head);
602
        }
603
        this.LIST[this.LIST.length - 1].subChunks.push(item);
604
      }
605
    // RIFF INFO tags like ICRD, ISFT, ICMT
606
    } else if(format == 'INFO') {
607
      this.head = subChunk.chunkData.start;
608
      this.LIST[this.LIST.length - 1].subChunks.push({
609
        chunkId: subChunk.chunkId,
610
        chunkSize: subChunk.chunkSize,
611
        value: this.readZSTR_(buffer, this.head)
612
      });
613
    }
614
  }
615
616
  /**
617
   * Read the 'junk' chunk of a wave file.
618
   * @param {!Uint8Array} buffer The wav file buffer.
619
   * @private
620
   */
621
  readJunkChunk_(buffer) {
622
    /** @type {?Object} */
623
    let chunk = this.findChunk('junk');
624
    if (chunk) {
625
      this.junk = {
626
        chunkId: chunk.chunkId,
627
        chunkSize: chunk.chunkSize,
628
        chunkData: [].slice.call(buffer.slice(
629
          chunk.chunkData.start,
630
          chunk.chunkData.end))
631
      };
632
    }
633
  }
634
635
  /**
636
   * Read the '_PMX' chunk of a wav file.
637
   * @param {!Uint8Array} buffer The wav file buffer.
638
   * @private
639
   */
640
  read_PMXChunk_(buffer) {
641
    /** @type {?Object} */
642
    let chunk = this.findChunk('_PMX');
643
    if (chunk) {
644
      this.head = chunk.chunkData.start;
645
      this._PMX.chunkId = chunk.chunkId;
646
      this._PMX.chunkSize = chunk.chunkSize;
647
      this._PMX.value = unpackString(
648
        buffer, this.head, this.head + this._PMX.chunkSize);
649
    }
650
  }
651
652
  /**
653
   * Read bytes as a ZSTR string.
654
   * @param {!Uint8Array} bytes The bytes.
655
   * @param {number} index the index to start reading.
656
   * @return {string} The string.
657
   * @private
658
   */
659
  readZSTR_(bytes, index=0) {
660
    for (let i = index; i < bytes.length; i++) {
661
      this.head++;
662
      if (bytes[i] === 0) {
663
        break;
664
      }
665
    }
666
    return unpackString(bytes, index, this.head - 1);
667
  }
668
669
  /**
670
   * Read a number from a chunk.
671
   * @param {!Uint8Array} bytes The chunk bytes.
672
   * @return {number} The number.
673
   * @private
674
   */
675
  readUInt16_(bytes) {
676
    /** @type {number} */
677
    let value = unpack(bytes, this.uInt16, this.head);
678
    this.head += 2;
679
    return value;
680
  }
681
}
682