Passed
Push — master ( 1769fa...d9ce30 )
by Rafael S.
03:30
created

RIFFFile.getSubChunkIndex_   A

Complexity

Conditions 3

Size

Total Lines 22
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 16
dl 0
loc 22
rs 9.6
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 RIFFFile class.
27
 * @see https://github.com/rochars/wavefile
28
 */
29
30
import {unpackString, unpack} from 'byte-data';
31
32
/**
33
 * A class to perform low-level reading of RIFF/RIFX files.
34
 */
35
export default class RIFFFile {
36
37
  constructor() {
38
    
39
    /** @type {number} */
40
    this.head_ = 0;
41
    /** @type {!Object} */
42
    this.uInt32_ = {bits: 32, be: false};
43
    /** @type {!Object} */
44
    this.uInt16_ = {bits: 16, be: false};
45
    /**
46
     * The container identifier.
47
     * 'RIFF', 'RIFX' and 'RF64' are supported.
48
     * @type {string}
49
     */
50
    this.container = '';
51
    /**
52
     * @type {number}
53
     */
54
    this.chunkSize = 0;
55
    /**
56
     * The format.
57
     * @type {string}
58
     */
59
    this.format = '';
60
    /**
61
     * A object defining the start and end of all chunks in a wav buffer.
62
     * @type {!Object}
63
     */
64
    this.signature = {};
65
  }
66
67
  /**
68
   * Read the signature of the chunks in a RIFF/RIFX file.
69
   * @param {!Uint8Array} buffer The file bytes.
70
   * @protected
71
   */
72
  setSignature(buffer) {
73
      this.head_ = 0;
74
      /** @type {string} */
75
      let chunkId = this.getChunkId_(buffer, 0);
76
      this.uInt32_.be = chunkId == 'RIFX';
77
      /** @type {string} */
78
      let format = unpackString(buffer, 8, 12);
79
      this.head_ += 4;
80
      this.signature = {
81
          chunkId: chunkId,
82
          chunkSize: this.getChunkSize_(buffer, 0),
83
          format: format,
84
          subChunks: this.getSubChunksIndex_(buffer)
85
      };
86
  }
87
88
  /**
89
    * Find a chunk by its fourCC_ in a array of RIFF chunks.
90
    * @param {string} chunkId The chunk fourCC_.
91
    * @param {boolean} multiple True if there may be multiple chunks
92
    *    with the same chunkId.
93
    * @return {Object}
94
    * @protected
95
    */
96
  findChunk(chunkId, multiple=false) {
97
    /** @type {!Array<!Object>} */
98
    let chunks = this.signature.subChunks;
99
    /** @type {!Array<!Object>} */
100
    let chunk = [];
101
    for (let i=0; i<chunks.length; i++) {
102
      if (chunks[i].chunkId == chunkId) {
103
        if (multiple) {
104
          chunk.push(chunks[i]);
105
        } else {
106
          return chunks[i];
107
        }
108
      }
109
    }
110
    if (chunkId == 'LIST') {
111
      return chunk.length ? chunk : null;
112
    }
113
    return null;
114
  }
115
116
  /**
117
   * Read the main chunk of a RIFF file.
118
   * @param {!Uint8Array} bytes A RIFF file buffer.
119
   * @throws {Error} If container is not RIFF, RIFX or RF64.
120
   * @protected
121
   */
122
  readRIFFChunk(bytes) {
123
    this.head_ = 0;
124
    this.container = this.readString(bytes, 4);
125
    if (['RIFF', 'RIFX', 'RF64'].indexOf(this.container) === -1) {
126
      throw Error('Not a supported format.');
127
    }
128
    this.uInt16_.be = this.container === 'RIFX';
129
    this.uInt32_.be = this.uInt16_.be;
130
    this.chunkSize = this.readNumber(bytes, this.uInt32_);
131
    this.format = this.readString(bytes, 4);
132
  }
133
134
  /**
135
   * Read bytes as a ZSTR string.
136
   * @param {!Uint8Array} bytes The bytes.
137
   * @param {number} index the index to start reading.
138
   * @return {string} The string.
139
   * @protected
140
   */
141
  readZSTR(bytes, index=0) {
142
    for (let i = index; i < bytes.length; i++) {
143
      this.head_++;
144
      if (bytes[i] === 0) {
145
        break;
146
      }
147
    }
148
    return unpackString(bytes, index, this.head_ - 1);
149
  }
150
151
  /**
152
   * Read bytes as a string from a RIFF chunk.
153
   * @param {!Uint8Array} bytes The bytes.
154
   * @param {number} maxSize the max size of the string.
155
   * @return {string} The string.
156
   * @protected
157
   */
158
  readString(bytes, maxSize) {
159
    /** @type {string} */
160
    let str = '';
161
    str = unpackString(bytes, this.head_, this.head_ + maxSize);
162
    this.head_ += maxSize;
163
    return str;
164
  }
165
166
  /**
167
   * Read a number from a chunk.
168
   * @param {!Uint8Array} bytes The chunk bytes.
169
   * @param {!Object} bdType The type definition.
170
   * @return {number} The number.
171
   * @protected
172
   */
173
  readNumber(bytes, bdType) {
174
    /** @type {number} */
175
    let size = bdType.bits / 8;
176
    /** @type {number} */
177
    let value = unpack(bytes, bdType, this.head_);
178
    this.head_ += size;
179
    return value;
180
  }
181
182
  /**
183
   * Return the sub chunks of a RIFF file.
184
   * @param {!Uint8Array} buffer the RIFF file bytes.
185
   * @return {!Array<Object>} The subchunks of a RIFF/RIFX or LIST chunk.
186
   * @private
187
   */
188
  getSubChunksIndex_(buffer) {
189
      /** @type {!Array<!Object>} */
190
      let chunks = [];
191
      /** @type {number} */
192
      let i = this.head_;
193
      while(i <= buffer.length - 8) {
194
          chunks.push(this.getSubChunkIndex_(buffer, i));
195
          i += 8 + chunks[chunks.length - 1].chunkSize;
196
          i = i % 2 ? i + 1 : i;
197
      }
198
      return chunks;
199
  }
200
201
  /**
202
   * Return a sub chunk from a RIFF file.
203
   * @param {!Uint8Array} buffer the RIFF file bytes.
204
   * @param {number} index The start index of the chunk.
205
   * @return {!Object} A subchunk of a RIFF/RIFX or LIST chunk.
206
   * @private
207
   */
208
  getSubChunkIndex_(buffer, index) {
209
      /** @type {!Object} */
210
      let chunk = {
211
          chunkId: this.getChunkId_(buffer, index),
212
          chunkSize: this.getChunkSize_(buffer, index),
213
      };
214
      if (chunk.chunkId == 'LIST') {
215
          chunk.format = unpackString(buffer, index + 8, index + 12);
216
          this.head_ += 4;
217
          chunk.subChunks = this.getSubChunksIndex_(buffer);
218
      } else {
219
          /** @type {number} */
220
          let realChunkSize = chunk.chunkSize % 2 ?
221
              chunk.chunkSize + 1 : chunk.chunkSize;
222
          this.head_ = index + 8 + realChunkSize;
223
          chunk.chunkData = {
224
              start: index + 8,
225
              end: this.head_
226
          };
227
      }
228
      return chunk;
229
  }
230
231
  /**
232
   * Return the fourCC_ of a chunk.
233
   * @param {!Uint8Array} buffer the RIFF file bytes.
234
   * @param {number} index The start index of the chunk.
235
   * @return {string} The id of the chunk.
236
   * @private
237
   */
238
  getChunkId_(buffer, index) {
239
      this.head_ += 4;
240
      return unpackString(buffer, index, index + 4);
241
  }
242
243
  /**
244
   * Return the size of a chunk.
245
   * @param {!Uint8Array} buffer the RIFF file bytes.
246
   * @param {number} index The start index of the chunk.
247
   * @return {number} The size of the chunk without the id and size fields.
248
   * @private
249
   */
250
  getChunkSize_(buffer, index) {
251
      this.head_ += 4;
252
      return unpack(buffer, this.uInt32_, index + 4);
253
  }
254
}
255