index.js   B
last analyzed

Complexity

Total Complexity 45
Complexity/F 2.81

Size

Lines of Code 410
Function Count 16

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 45
eloc 234
mnd 29
bc 29
fnc 16
dl 0
loc 410
rs 8.8
bpm 1.8125
cpm 2.8125
noi 0
c 0
b 0
f 0

16 Functions

Rating   Name   Duplication   Size   Complexity  
A WaveFileParser.toBuffer 0 35 3
A WaveFileParser.getLISTBytes_ 0 15 2
A WaveFileParser.enforceBext_ 0 13 5
A WaveFileParser.getCueBytes_ 0 14 2
A WaveFileParser.getSmplLoopsBytes_ 0 14 2
A WaveFileParser.getBextBytes_ 0 32 3
A WaveFileParser.getCuePointsBytes_ 0 14 2
A WaveFileParser.getSmplBytes_ 0 22 2
B WaveFileParser.getLISTSubChunksBytes_ 0 33 7
A WaveFileParser.getFmtBytes_ 0 17 2
A WaveFileParser.getJunkBytes_ 0 11 2
A WaveFileParser.getDs64Bytes_ 0 22 2
A WaveFileParser.getLtxtChunkBytes_ 0 13 1
A ➔ writeString 0 10 3
A WaveFileParser.getFmtExtensionBytes_ 0 24 5
A WaveFileParser.getFactBytes_ 0 11 2

How to fix   Complexity   

Complexity

Complex classes like index.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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
/** @module wavefile-parser */
31
32
import { WaveFileReader } from 'wavefile-reader';
33
import { packTo, packStringTo, packString, pack } from 'byte-data';
34
35
/**
36
 * A class to read and write wav files.
37
 * @extends WaveFileReader
38
 */
39
export class WaveFileParser extends WaveFileReader {
40
41
  /**
42
   * Return a byte buffer representig the WaveFileParser object as a .wav file.
43
   * The return value of this method can be written straight to disk.
44
   * @return {!Uint8Array} A wav file.
45
   */
46
  toBuffer() {
47
    this.uInt16.be = this.container === 'RIFX';
48
    this.uInt32.be = this.uInt16.be;
49
    /** @type {!Array<!Array<number>>} */
50
    let fileBody = [
51
      this.getJunkBytes_(),
52
      this.getDs64Bytes_(),
53
      this.getBextBytes_(),
54
      this.getFmtBytes_(),
55
      this.getFactBytes_(),
56
      packString(this.data.chunkId),
57
      pack(this.data.samples.length, this.uInt32),
58
      this.data.samples,
59
      this.getCueBytes_(),
60
      this.getSmplBytes_(),
61
      this.getLISTBytes_()
62
    ];
63
    /** @type {number} */
64
    let fileBodyLength = 0;
65
    for (let i=0; i<fileBody.length; i++) {
66
      fileBodyLength += fileBody[i].length;
67
    }
68
    /** @type {!Uint8Array} */
69
    let file = new Uint8Array(fileBodyLength + 12);
70
    /** @type {number} */
71
    let index = 0;
72
    index = packStringTo(this.container, file, index);
73
    index = packTo(fileBodyLength + 4, this.uInt32, file, index);
74
    index = packStringTo(this.format, file, index);
75
    for (let i=0; i<fileBody.length; i++) {
76
      file.set(fileBody[i], index);
77
      index += fileBody[i].length;
78
    }
79
    return file;
80
  }
81
82
  /**
83
   * Return the bytes of the 'bext' chunk.
84
   * @private
85
   */
86
  getBextBytes_() {
87
    /** @type {!Array<number>} */
88
    let bytes = [];
89
    this.enforceBext_();
90
    if (this.bext.chunkId) {
91
      this.bext.chunkSize = 602 + this.bext.codingHistory.length;
92
      bytes = bytes.concat(
93
        packString(this.bext.chunkId),
94
        pack(602 + this.bext.codingHistory.length, this.uInt32),
95
        writeString(this.bext.description, 256),
96
        writeString(this.bext.originator, 32),
97
        writeString(this.bext.originatorReference, 32),
98
        writeString(this.bext.originationDate, 10),
99
        writeString(this.bext.originationTime, 8),
100
        pack(this.bext.timeReference[0], this.uInt32),
101
        pack(this.bext.timeReference[1], this.uInt32),
102
        pack(this.bext.version, this.uInt16),
103
        writeString(this.bext.UMID, 64),
104
        pack(this.bext.loudnessValue, this.uInt16),
105
        pack(this.bext.loudnessRange, this.uInt16),
106
        pack(this.bext.maxTruePeakLevel, this.uInt16),
107
        pack(this.bext.maxMomentaryLoudness, this.uInt16),
108
        pack(this.bext.maxShortTermLoudness, this.uInt16),
109
        writeString(this.bext.reserved, 180),
110
        writeString(
111
          this.bext.codingHistory, this.bext.codingHistory.length));
112
    }
113
    if (bytes.length % 2) {
114
      bytes.push(0);
115
    }
116
    return bytes;
117
  }
118
119
  /**
120
   * Make sure a 'bext' chunk is created if BWF data was created in a file.
121
   * @private
122
   */
123
  enforceBext_() {
124
    for (let prop in this.bext) {
125
      if (this.bext.hasOwnProperty(prop)) {
126
        if (this.bext[prop] && prop != 'timeReference') {
127
          this.bext.chunkId = 'bext';
128
          break;
129
        }
130
      }
131
    }
132
    if (this.bext.timeReference[0] || this.bext.timeReference[1]) {
133
      this.bext.chunkId = 'bext';
134
    }
135
  }
136
137
  /**
138
   * Return the bytes of the 'ds64' chunk.
139
   * @return {!Array<number>} The 'ds64' chunk bytes.
140
   * @private
141
   */
142
  getDs64Bytes_() {
143
    /** @type {!Array<number>} */
144
    let bytes = [];
145
    if (this.ds64.chunkId) {
146
      bytes = bytes.concat(
147
        packString(this.ds64.chunkId),
148
        pack(this.ds64.chunkSize, this.uInt32),
149
        pack(this.ds64.riffSizeHigh, this.uInt32),
150
        pack(this.ds64.riffSizeLow, this.uInt32),
151
        pack(this.ds64.dataSizeHigh, this.uInt32),
152
        pack(this.ds64.dataSizeLow, this.uInt32),
153
        pack(this.ds64.originationTime, this.uInt32),
154
        pack(this.ds64.sampleCountHigh, this.uInt32),
155
        pack(this.ds64.sampleCountLow, this.uInt32));
156
    }
157
    //if (this.ds64.tableLength) {
158
    //  ds64Bytes = ds64Bytes.concat(
159
    //    pack(this.ds64.tableLength, this.uInt32),
160
    //    this.ds64.table);
161
    //}
162
    return bytes;
163
  }
164
165
  /**
166
   * Return the bytes of the 'cue ' chunk.
167
   * @return {!Array<number>} The 'cue ' chunk bytes.
168
   * @private
169
   */
170
  getCueBytes_() {
171
    /** @type {!Array<number>} */
172
    let bytes = [];
173
    if (this.cue.chunkId) {
174
      /** @type {!Array<number>} */
175
      let cuePointsBytes = this.getCuePointsBytes_();
176
      bytes = bytes.concat(
177
        packString(this.cue.chunkId),
178
        pack(cuePointsBytes.length + 4, this.uInt32),
179
        pack(this.cue.dwCuePoints, this.uInt32),
180
        cuePointsBytes);
181
    }
182
    return bytes;
183
  }
184
185
  /**
186
   * Return the bytes of the 'cue ' points.
187
   * @return {!Array<number>} The 'cue ' points as an array of bytes.
188
   * @private
189
   */
190
  getCuePointsBytes_() {
191
    /** @type {!Array<number>} */
192
    let points = [];
193
    for (let i=0; i<this.cue.dwCuePoints; i++) {
194
      points = points.concat(
195
        pack(this.cue.points[i].dwName, this.uInt32),
196
        pack(this.cue.points[i].dwPosition, this.uInt32),
197
        packString(this.cue.points[i].fccChunk),
198
        pack(this.cue.points[i].dwChunkStart, this.uInt32),
199
        pack(this.cue.points[i].dwBlockStart, this.uInt32),
200
        pack(this.cue.points[i].dwSampleOffset, this.uInt32));
201
    }
202
    return points;
203
  }
204
205
  /**
206
   * Return the bytes of the 'smpl' chunk.
207
   * @return {!Array<number>} The 'smpl' chunk bytes.
208
   * @private
209
   */
210
  getSmplBytes_() {
211
    /** @type {!Array<number>} */
212
    let bytes = [];
213
    if (this.smpl.chunkId) {
214
      /** @type {!Array<number>} */
215
      let smplLoopsBytes = this.getSmplLoopsBytes_();
216
      bytes = bytes.concat(
217
        packString(this.smpl.chunkId),
218
        pack(smplLoopsBytes.length + 36, this.uInt32),
219
        pack(this.smpl.dwManufacturer, this.uInt32),
220
        pack(this.smpl.dwProduct, this.uInt32),
221
        pack(this.smpl.dwSamplePeriod, this.uInt32),
222
        pack(this.smpl.dwMIDIUnityNote, this.uInt32),
223
        pack(this.smpl.dwMIDIPitchFraction, this.uInt32),
224
        pack(this.smpl.dwSMPTEFormat, this.uInt32),
225
        pack(this.smpl.dwSMPTEOffset, this.uInt32),
226
        pack(this.smpl.dwNumSampleLoops, this.uInt32),
227
        pack(this.smpl.dwSamplerData, this.uInt32),
228
        smplLoopsBytes);
229
    }
230
    return bytes;
231
  }
232
233
  /**
234
   * Return the bytes of the 'smpl' loops.
235
   * @return {!Array<number>} The 'smpl' loops as an array of bytes.
236
   * @private
237
   */
238
  getSmplLoopsBytes_() {
239
    /** @type {!Array<number>} */
240
    let loops = [];
241
    for (let i=0; i<this.smpl.dwNumSampleLoops; i++) {
242
      loops = loops.concat(
243
        pack(this.smpl.loops[i].dwName, this.uInt32),
244
        pack(this.smpl.loops[i].dwType, this.uInt32),
245
        pack(this.smpl.loops[i].dwStart, this.uInt32),
246
        pack(this.smpl.loops[i].dwEnd, this.uInt32),
247
        pack(this.smpl.loops[i].dwFraction, this.uInt32),
248
        pack(this.smpl.loops[i].dwPlayCount, this.uInt32));
249
    }
250
    return loops;
251
  }
252
253
  /**
254
   * Return the bytes of the 'fact' chunk.
255
   * @return {!Array<number>} The 'fact' chunk bytes.
256
   * @private
257
   */
258
  getFactBytes_() {
259
    /** @type {!Array<number>} */
260
    let bytes = [];
261
    if (this.fact.chunkId) {
262
      bytes = bytes.concat(
263
        packString(this.fact.chunkId),
264
        pack(this.fact.chunkSize, this.uInt32),
265
        pack(this.fact.dwSampleLength, this.uInt32));
266
    }
267
    return bytes;
268
  }
269
270
  /**
271
   * Return the bytes of the 'fmt ' chunk.
272
   * @return {!Array<number>} The 'fmt' chunk bytes.
273
   * @throws {Error} if no 'fmt ' chunk is present.
274
   * @private
275
   */
276
  getFmtBytes_() {
277
    /** @type {!Array<number>} */
278
    let fmtBytes = [];
279
    if (this.fmt.chunkId) {
280
      return fmtBytes.concat(
281
        packString(this.fmt.chunkId),
282
        pack(this.fmt.chunkSize, this.uInt32),
283
        pack(this.fmt.audioFormat, this.uInt16),
284
        pack(this.fmt.numChannels, this.uInt16),
285
        pack(this.fmt.sampleRate, this.uInt32),
286
        pack(this.fmt.byteRate, this.uInt32),
287
        pack(this.fmt.blockAlign, this.uInt16),
288
        pack(this.fmt.bitsPerSample, this.uInt16),
289
        this.getFmtExtensionBytes_());
290
    }
291
    throw Error('Could not find the "fmt " chunk');
292
  }
293
294
  /**
295
   * Return the bytes of the fmt extension fields.
296
   * @return {!Array<number>} The fmt extension bytes.
297
   * @private
298
   */
299
  getFmtExtensionBytes_() {
300
    /** @type {!Array<number>} */
301
    let extension = [];
302
    if (this.fmt.chunkSize > 16) {
303
      extension = extension.concat(
304
        pack(this.fmt.cbSize, this.uInt16));
305
    }
306
    if (this.fmt.chunkSize > 18) {
307
      extension = extension.concat(
308
        pack(this.fmt.validBitsPerSample, this.uInt16));
309
    }
310
    if (this.fmt.chunkSize > 20) {
311
      extension = extension.concat(
312
        pack(this.fmt.dwChannelMask, this.uInt32));
313
    }
314
    if (this.fmt.chunkSize > 24) {
315
      extension = extension.concat(
316
        pack(this.fmt.subformat[0], this.uInt32),
317
        pack(this.fmt.subformat[1], this.uInt32),
318
        pack(this.fmt.subformat[2], this.uInt32),
319
        pack(this.fmt.subformat[3], this.uInt32));
320
    }
321
    return extension;
322
  }
323
324
  /**
325
   * Return the bytes of the 'LIST' chunk.
326
   * @return {!Array<number>} The 'LIST' chunk bytes.
327
   * @private
328
   */
329
  getLISTBytes_() {
330
    /** @type {!Array<number>} */
331
    let bytes = [];
332
    for (let i=0; i<this.LIST.length; i++) {
333
      /** @type {!Array<number>} */
334
      let subChunksBytes = this.getLISTSubChunksBytes_(
335
          this.LIST[i].subChunks, this.LIST[i].format);
336
      bytes = bytes.concat(
337
        packString(this.LIST[i].chunkId),
338
        pack(subChunksBytes.length + 4, this.uInt32),
339
        packString(this.LIST[i].format),
340
        subChunksBytes);
341
    }
342
    return bytes;
343
  }
344
345
  /**
346
   * Return the bytes of the sub chunks of a 'LIST' chunk.
347
   * @param {!Array<!Object>} subChunks The 'LIST' sub chunks.
348
   * @param {string} format The format of the 'LIST' chunk.
349
   *    Currently supported values are 'adtl' or 'INFO'.
350
   * @return {!Array<number>} The sub chunk bytes.
351
   * @private
352
   */
353
  getLISTSubChunksBytes_(subChunks, format) {
354
    /** @type {!Array<number>} */
355
    let bytes = [];
356
    for (let i=0; i<subChunks.length; i++) {
357
      if (format == 'INFO') {
358
        bytes = bytes.concat(
359
          packString(subChunks[i].chunkId),
360
          pack(subChunks[i].value.length + 1, this.uInt32),
361
          writeString(
362
            subChunks[i].value, subChunks[i].value.length));
363
        bytes.push(0);
364
      } else if (format == 'adtl') {
365
        if (['labl', 'note'].indexOf(subChunks[i].chunkId) > -1) {
366
          bytes = bytes.concat(
367
            packString(subChunks[i].chunkId),
368
            pack(
369
              subChunks[i].value.length + 4 + 1, this.uInt32),
370
            pack(subChunks[i].dwName, this.uInt32),
371
            writeString(
372
              subChunks[i].value,
373
              subChunks[i].value.length));
374
          bytes.push(0);
375
        } else if (subChunks[i].chunkId == 'ltxt') {
376
          bytes = bytes.concat(
377
            this.getLtxtChunkBytes_(subChunks[i]));
378
        }
379
      }
380
      if (bytes.length % 2) {
381
        bytes.push(0);
382
      }
383
    }
384
    return bytes;
385
  }
386
387
  /**
388
   * Return the bytes of a 'ltxt' chunk.
389
   * @param {!Object} ltxt the 'ltxt' chunk.
390
   * @private
391
   */
392
  getLtxtChunkBytes_(ltxt) {
393
    return [].concat(
394
      packString(ltxt.chunkId),
395
      pack(ltxt.value.length + 20, this.uInt32),
396
      pack(ltxt.dwName, this.uInt32),
397
      pack(ltxt.dwSampleLength, this.uInt32),
398
      pack(ltxt.dwPurposeID, this.uInt32),
399
      pack(ltxt.dwCountry, this.uInt16),
400
      pack(ltxt.dwLanguage, this.uInt16),
401
      pack(ltxt.dwDialect, this.uInt16),
402
      pack(ltxt.dwCodePage, this.uInt16),
403
      writeString(ltxt.value, ltxt.value.length));
404
  }
405
406
  /**
407
   * Return the bytes of the 'junk' chunk.
408
   * @private
409
   */
410
  getJunkBytes_() {
411
    /** @type {!Array<number>} */
412
    let bytes = [];
413
    if (this.junk.chunkId) {
414
      return bytes.concat(
415
        packString(this.junk.chunkId),
416
        pack(this.junk.chunkData.length, this.uInt32),
417
        this.junk.chunkData);
418
    }
419
    return bytes;
420
  }
421
}
422
423
/**
424
 * Pack a string an array of bytes. If the string is smaller than the max size
425
 * and the string is of fixed size, the output array is filled with 0s.
426
 * @param {string} str The string to be written as bytes.
427
 * @param {number} maxSize the max size of the string.
428
 * @param {boolean} fixedSize If the string is of fixed size or not.
429
 * @return {!Array<number>} The packed string.
430
 * @private
431
 */
432
function writeString(str, maxSize, fixedSize=true) {
433
  /** @type {!Array<number>} */   
434
  let packedString = packString(str);
435
  if (fixedSize) {
436
    for (let i=packedString.length; i<maxSize; i++) {
437
      packedString.push(0);
438
    }  
439
  }
440
  return packedString;
441
}
442