Passed
Push — master ( 6ca9b2...ebd43e )
by Rafael S.
04:26
created

lib/wavefile-parser.js   A

Complexity

Total Complexity 41
Complexity/F 2.73

Size

Lines of Code 388
Function Count 15

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 41
eloc 227
mnd 26
bc 26
fnc 15
dl 0
loc 388
rs 9.1199
bpm 1.7333
cpm 2.7333
noi 0
c 0
b 0
f 0

15 Functions

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

How to fix   Complexity   

Complexity

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