Completed
Branch riff-file (1c8ef5)
by Rafael S.
07:52
created

lib/riff-file.js   A

Complexity

Total Complexity 25
Complexity/F 2.08

Size

Lines of Code 237
Function Count 12

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 94
dl 0
loc 237
rs 10
c 0
b 0
f 0
wmc 25
mnd 13
bc 13
fnc 12
bpm 1.0833
cpm 2.0833
noi 1

12 Functions

Rating   Name   Duplication   Size   Complexity  
A RIFFFile.getSignature_ 0 15 1
A RIFFFile.getChunkSize_ 0 4 1
A RIFFFile.getChunkId_ 0 4 1
A RIFFFile.getSubChunksIndex_ 0 12 3
A RIFFFile.getSubChunkIndex_ 0 22 3
A RIFFFile.readString_ 0 7 1
A RIFFFile.readZSTR_ 0 9 3
A RIFFFile.constructor 0 30 1
A RIFFFile.readRIFFChunk_ 0 14 3
A RIFFFile.read_ 0 8 1
A RIFFFile.loadRIFF 0 5 1
B RIFFFile.findChunk_ 0 19 6
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 {unpackArray, unpackString, unpack} from 'byte-data';
0 ignored issues
show
Unused Code introduced by
The variable unpackArray seems to be never used. Consider removing it.
Loading history...
31
32
/**
33
 * A class to perform low-level reading of RIFF 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
     * Always 'WAVE'.
58
     * @type {string}
59
     */
60
    this.format = '';
61
    /**
62
     * A object defining the start and end of all chunks in a wav buffer.
63
     * @type {!Object}
64
     */
65
    this.signature = {};
66
  }
67
68
  /**
69
   * Load a RIFF file.
70
   * @param {!Uint8Array} buffer The file bytes.
71
   */
72
  loadRIFF(buffer) {
73
    this.head_ = 0;
74
    this.readRIFFChunk_(buffer);
75
    this.getSignature_(buffer);
76
  }
77
78
  /**
79
   * Return the chunks in a RIFF/RIFX file.
80
   * @param {!Uint8Array} buffer The file bytes.
81
   */
82
  getSignature_(buffer) {
83
      this.head_ = 0;
84
      /** @type {string} */
85
      let chunkId = this.getChunkId_(buffer, 0);
86
      this.uInt32_.be = chunkId == 'RIFX';
87
      /** @type {string} */
88
      let format = unpackString(buffer, 8, 12);
89
      this.head_ += 4;
90
      this.signature = {
91
          chunkId: chunkId,
92
          chunkSize: this.getChunkSize_(buffer, 0),
93
          format: format,
94
          subChunks: this.getSubChunksIndex_(buffer)
95
      };
96
  }
97
98
  /**
99
    * Find a chunk by its fourCC_ in a array of RIFF chunks.
100
    * @param {string} chunkId The chunk fourCC_.
101
    * @param {boolean} multiple True if there may be multiple chunks
102
    *    with the same chunkId.
103
    * @return {Object}
104
    */
105
  findChunk_(chunkId, multiple=false) {
106
    /** @type {!Array<!Object>} */
107
    let chunks = this.signature.subChunks;
108
    /** @type {!Array<!Object>} */
109
    let chunk = [];
110
    for (let i=0; i<chunks.length; i++) {
111
      if (chunks[i].chunkId == chunkId) {
112
        if (multiple) {
113
          chunk.push(chunks[i]);
114
        } else {
115
          return chunks[i];
116
        }
117
      }
118
    }
119
    if (chunkId == 'LIST') {
120
      return chunk.length ? chunk : null;
121
    }
122
    return null;
123
  }
124
125
  /**
126
   * Return the sub chunks of a RIFF file.
127
   * @param {!Uint8Array} buffer the RIFF file bytes.
128
   * @return {!Array<Object>} The subchunks of a RIFF/RIFX or LIST chunk.
129
   * @private
130
   */
131
  getSubChunksIndex_(buffer) {
132
      /** @type {!Array<!Object>} */
133
      let chunks = [];
134
      /** @type {number} */
135
      let i = this.head_;
136
      while(i <= buffer.length - 8) {
137
          chunks.push(this.getSubChunkIndex_(buffer, i));
138
          i += 8 + chunks[chunks.length - 1].chunkSize;
139
          i = i % 2 ? i + 1 : i;
140
      }
141
      return chunks;
142
  }
143
144
  /**
145
   * Return a sub chunk from a RIFF file.
146
   * @param {!Uint8Array} buffer the RIFF file bytes.
147
   * @param {number} index The start index of the chunk.
148
   * @return {!Object} A subchunk of a RIFF/RIFX or LIST chunk.
149
   * @private
150
   */
151
  getSubChunkIndex_(buffer, index) {
152
      /** @type {!Object} */
153
      let chunk = {
154
          chunkId: this.getChunkId_(buffer, index),
155
          chunkSize: this.getChunkSize_(buffer, index),
156
      };
157
      if (chunk.chunkId == 'LIST') {
158
          chunk.format = unpackString(buffer, index + 8, index + 12);
159
          this.head_ += 4;
160
          chunk.subChunks = this.getSubChunksIndex_(buffer);
161
      } else {
162
          /** @type {number} */
163
          let realChunkSize = chunk.chunkSize % 2 ?
164
              chunk.chunkSize + 1 : chunk.chunkSize;
165
          this.head_ = index + 8 + realChunkSize;
166
          chunk.chunkData = {
167
              start: index + 8,
168
              end: this.head_
169
          };
170
      }
171
      return chunk;
172
  }
173
174
  /**
175
   * Return the fourCC_ of a chunk.
176
   * @param {!Uint8Array} buffer the RIFF file bytes.
177
   * @param {number} index The start index of the chunk.
178
   * @return {string} The id of the chunk.
179
   * @private
180
   */
181
  getChunkId_(buffer, index) {
182
      this.head_ += 4;
183
      return unpackString(buffer, index, index + 4);
184
  }
185
186
  /**
187
   * Return the size of a chunk.
188
   * @param {!Uint8Array} buffer the RIFF file bytes.
189
   * @param {number} index The start index of the chunk.
190
   * @return {number} The size of the chunk without the id and size fields.
191
   * @private
192
   */
193
  getChunkSize_(buffer, index) {
194
      this.head_ += 4;
195
      return unpack(buffer, this.uInt32_, index + 4);
196
  }
197
198
  /**
199
   * Read the RIFF chunk a wave file.
200
   * @param {!Uint8Array} bytes A wav buffer.
201
   * @throws {Error} If no 'RIFF' chunk is found.
202
   * @private
203
   */
204
  readRIFFChunk_(bytes) {
205
    this.head_ = 0;
206
    this.container = this.readString_(bytes, 4);
207
    if (['RIFF', 'RIFX', 'RF64'].indexOf(this.container) === -1) {
208
      throw Error('Not a supported format.');
209
    }
210
    this.uInt16_.be = this.container === 'RIFX';
211
    this.uInt32_.be = this.uInt16_.be;
212
    this.chunkSize = this.read_(bytes, this.uInt32_);
213
    this.format = this.readString_(bytes, 4);
214
    if (this.format != 'WAVE') {
215
      throw Error('Could not find the "WAVE" format identifier');
216
    }
217
  }
218
219
  /**
220
   * Read bytes as a ZSTR string.
221
   * @param {!Uint8Array} bytes The bytes.
222
   * @param {number} index the index to start reading.
223
   * @return {string} The string.
224
   * @private
225
   */
226
  readZSTR_(bytes, index=0) {
227
    for (let i = index; i < bytes.length; i++) {
228
      this.head_++;
229
      if (bytes[i] === 0) {
230
        break;
231
      }
232
    }
233
    return unpackString(bytes, index, this.head_ - 1);
234
  }
235
236
  /**
237
   * Read bytes as a string from a RIFF chunk.
238
   * @param {!Uint8Array} bytes The bytes.
239
   * @param {number} maxSize the max size of the string.
240
   * @return {string} The string.
241
   * @private
242
   */
243
  readString_(bytes, maxSize) {
244
    /** @type {string} */
245
    let str = '';
246
    str = unpackString(bytes, this.head_, this.head_ + maxSize);
247
    this.head_ += maxSize;
248
    return str;
249
  }
250
251
  /**
252
   * Read a number from a chunk.
253
   * @param {!Uint8Array} bytes The chunk bytes.
254
   * @param {!Object} bdType The type definition.
255
   * @return {number} The number.
256
   * @private
257
   */
258
  read_(bytes, bdType) {
259
    /** @type {number} */
260
    let size = bdType.bits / 8;
261
    /** @type {number} */
262
    let value = unpack(bytes, bdType, this.head_);
263
    this.head_ += size;
264
    return value;
265
  }
266
}
267