Passed
Branch v8.x (d6a5cf)
by Rafael S.
02:05
created

lib/wav-buffer-writer.js   B

Complexity

Total Complexity 43
Complexity/F 2.87

Size

Lines of Code 383
Function Count 15

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 0
eloc 224
c 1
b 0
f 0
nc 1
dl 0
loc 383
rs 8.96
wmc 43
mnd 5
bc 41
fnc 15
bpm 2.7333
cpm 2.8666
noi 7

15 Functions

Rating   Name   Duplication   Size   Complexity  
A wav-buffer-writer.js ➔ getSmplLoopsBytes_ 0 14 2
A wav-buffer-writer.js ➔ getSmplBytes_ 0 22 2
A wav-buffer-writer.js ➔ writeWavBuffer 0 33 3
A wav-buffer-writer.js ➔ getCueBytes_ 0 14 2
B wav-buffer-writer.js ➔ enforceBext_ 0 13 7
A wav-buffer-writer.js ➔ getDs64Bytes_ 0 22 2
A wav-buffer-writer.js ➔ getLISTBytes_ 0 15 2
A wav-buffer-writer.js ➔ getFmtExtensionBytes_ 0 24 5
A wav-buffer-writer.js ➔ getJunkBytes_ 0 11 2
A wav-buffer-writer.js ➔ getLtxtChunkBytes_ 0 13 1
B wav-buffer-writer.js ➔ getLISTSubChunksBytes_ 0 33 7
A wav-buffer-writer.js ➔ getCuePointsBytes_ 0 14 2
A wav-buffer-writer.js ➔ getFactBytes_ 0 11 2
A wav-buffer-writer.js ➔ getBextBytes_ 0 29 2
A wav-buffer-writer.js ➔ getFmtBytes_ 0 17 2

How to fix   Complexity   

Complexity

Complex classes like lib/wav-buffer-writer.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) 2018 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 Make buffers of structured wav data.
27
 * @see https://github.com/rochars/wavefile
28
 */
29
30
import {pack, packTo, packStringTo, packString} from '../vendor/byte-data.js';
31
import BufferIO from './bufferio.js';
32
33
let io = new BufferIO();
34
35
/**
36
 * Return a .wav file byte buffer with the data from the WaveFile object.
37
 * The return value of this method can be written straight to disk.
38
 * @return {!Uint8Array} The wav file bytes.
39
 * @private
40
 */
41
export default function writeWavBuffer(wav, uInt32_, uInt16_) {
42
  /** @type {!Array<!Array<number>>} */
43
  let fileBody = [
44
    getJunkBytes_(wav, uInt32_, uInt16_),
45
    getDs64Bytes_(wav, uInt32_, uInt16_),
46
    getBextBytes_(wav, uInt32_, uInt16_),
47
    getFmtBytes_(wav, uInt32_, uInt16_),
48
    getFactBytes_(wav, uInt32_, uInt16_),
49
    packString(wav.data.chunkId),
50
    pack(wav.data.samples.length, uInt32_),
51
    wav.data.samples,
52
    getCueBytes_(wav, uInt32_, uInt16_),
53
    getSmplBytes_(wav, uInt32_, uInt16_),
54
    getLISTBytes_(wav, uInt32_, uInt16_)
55
  ];
56
  /** @type {number} */
57
  let fileBodyLength = 0;
58
  for (let i=0; i<fileBody.length; i++) {
59
    fileBodyLength += fileBody[i].length;
60
  }
61
  /** @type {!Uint8Array} */
62
  let file = new Uint8Array(fileBodyLength + 12);
63
  /** @type {number} */
64
  let index = 0;
65
  index = packStringTo(wav.container, file, index);
66
  index = packTo(fileBodyLength + 4, uInt32_, file, index);
67
  index = packStringTo(wav.format, file, index);
68
  for (let i=0; i<fileBody.length; i++) {
69
    file.set(fileBody[i], index);
70
    index += fileBody[i].length;
71
  }
72
  return file;
73
}
74
75
/**
76
 * Return the bytes of the 'bext' chunk.
77
 * @return {!Array<number>} The 'bext' chunk bytes.
78
 * @private
79
 */
80
function getBextBytes_(wav, uInt32_, uInt16_) {
81
  /** @type {!Array<number>} */
82
  let bytes = [];
83
  enforceBext_(wav, uInt32_, uInt16_);
84
  if (wav.bext.chunkId) {
85
    wav.bext.chunkSize = 602 + wav.bext.codingHistory.length;
86
    bytes = bytes.concat(
87
      packString(wav.bext.chunkId),
88
      pack(602 + wav.bext.codingHistory.length, uInt32_),
89
      io.writeString_(wav.bext.description, 256),
90
      io.writeString_(wav.bext.originator, 32),
91
      io.writeString_(wav.bext.originatorReference, 32),
92
      io.writeString_(wav.bext.originationDate, 10),
93
      io.writeString_(wav.bext.originationTime, 8),
94
      pack(wav.bext.timeReference[0], uInt32_),
95
      pack(wav.bext.timeReference[1], uInt32_),
96
      pack(wav.bext.version, uInt16_),
97
      io.writeString_(wav.bext.UMID, 64),
98
      pack(wav.bext.loudnessValue, uInt16_),
99
      pack(wav.bext.loudnessRange, uInt16_),
100
      pack(wav.bext.maxTruePeakLevel, uInt16_),
101
      pack(wav.bext.maxMomentaryLoudness, uInt16_),
102
      pack(wav.bext.maxShortTermLoudness, uInt16_),
103
      io.writeString_(wav.bext.reserved, 180),
104
      io.writeString_(
105
        wav.bext.codingHistory, wav.bext.codingHistory.length));
106
  }
107
  return bytes;
108
}
109
110
/**
111
 * Make sure a 'bext' chunk is created if BWF data was created in a file.
112
 * @private
113
 */
114
function enforceBext_(wav, uInt32_, uInt16_) {
0 ignored issues
show
Unused Code introduced by
The parameter uInt32_ is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter uInt16_ is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
115
  for (var prop in wav.bext) {
116
    if (wav.bext.hasOwnProperty(prop)) {
117
      if (wav.bext[prop] && prop != 'timeReference') {
118
        wav.bext.chunkId = 'bext';
119
        break;
120
      }
121
    }
122
  }
123
  if (wav.bext.timeReference[0] || wav.bext.timeReference[1]) {
124
    wav.bext.chunkId = 'bext';
125
  }
126
}
127
128
/**
129
 * Return the bytes of the 'ds64' chunk.
130
 * @return {!Array<number>} The 'ds64' chunk bytes.
131
 * @private
132
 */
133
function getDs64Bytes_(wav, uInt32_, uInt16_) {
0 ignored issues
show
Unused Code introduced by
The parameter uInt16_ is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
134
  /** @type {!Array<number>} */
135
  let bytes = [];
136
  if (wav.ds64.chunkId) {
137
    bytes = bytes.concat(
138
      packString(wav.ds64.chunkId),
139
      pack(wav.ds64.chunkSize, uInt32_),
140
      pack(wav.ds64.riffSizeHigh, uInt32_),
141
      pack(wav.ds64.riffSizeLow, uInt32_),
142
      pack(wav.ds64.dataSizeHigh, uInt32_),
143
      pack(wav.ds64.dataSizeLow, uInt32_),
144
      pack(wav.ds64.originationTime, uInt32_),
145
      pack(wav.ds64.sampleCountHigh, uInt32_),
146
      pack(wav.ds64.sampleCountLow, uInt32_));
147
  }
148
  //if (this.ds64.tableLength) {
149
  //  ds64Bytes = ds64Bytes.concat(
150
  //    pack(this.ds64.tableLength, this.uInt32_),
151
  //    this.ds64.table);
152
  //}
153
  return bytes;
154
}
155
156
/**
157
 * Return the bytes of the 'cue ' chunk.
158
 * @return {!Array<number>} The 'cue ' chunk bytes.
159
 * @private
160
 */
161
function getCueBytes_(wav, uInt32_, uInt16_) {
162
  /** @type {!Array<number>} */
163
  let bytes = [];
164
  if (wav.cue.chunkId) {
165
    /** @type {!Array<number>} */
166
    let cuePointsBytes = getCuePointsBytes_(wav, uInt32_, uInt16_);
167
    bytes = bytes.concat(
168
      packString(wav.cue.chunkId),
169
      pack(cuePointsBytes.length + 4, uInt32_),
170
      pack(wav.cue.dwCuePoints, uInt32_),
171
      cuePointsBytes);
172
  }
173
  return bytes;
174
}
175
176
/**
177
 * Return the bytes of the 'cue ' points.
178
 * @return {!Array<number>} The 'cue ' points as an array of bytes.
179
 * @private
180
 */
181
function getCuePointsBytes_(wav, uInt32_, uInt16_) {
0 ignored issues
show
Unused Code introduced by
The parameter uInt16_ is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
182
  /** @type {!Array<number>} */
183
  let points = [];
184
  for (let i=0; i<wav.cue.dwCuePoints; i++) {
185
    points = points.concat(
186
      pack(wav.cue.points[i].dwName, uInt32_),
187
      pack(wav.cue.points[i].dwPosition, uInt32_),
188
      packString(wav.cue.points[i].fccChunk),
189
      pack(wav.cue.points[i].dwChunkStart, uInt32_),
190
      pack(wav.cue.points[i].dwBlockStart, uInt32_),
191
      pack(wav.cue.points[i].dwSampleOffset, uInt32_));
192
  }
193
  return points;
194
}
195
196
/**
197
 * Return the bytes of the 'smpl' chunk.
198
 * @return {!Array<number>} The 'smpl' chunk bytes.
199
 * @private
200
 */
201
function getSmplBytes_(wav, uInt32_, uInt16_) {
202
  /** @type {!Array<number>} */
203
  let bytes = [];
204
  if (wav.smpl.chunkId) {
205
    /** @type {!Array<number>} */
206
    let smplLoopsBytes = getSmplLoopsBytes_(wav, uInt32_, uInt16_);
207
    bytes = bytes.concat(
208
      packString(wav.smpl.chunkId),
209
      pack(smplLoopsBytes.length + 36, uInt32_),
210
      pack(wav.smpl.dwManufacturer, uInt32_),
211
      pack(wav.smpl.dwProduct, uInt32_),
212
      pack(wav.smpl.dwSamplePeriod, uInt32_),
213
      pack(wav.smpl.dwMIDIUnityNote, uInt32_),
214
      pack(wav.smpl.dwMIDIPitchFraction, uInt32_),
215
      pack(wav.smpl.dwSMPTEFormat, uInt32_),
216
      pack(wav.smpl.dwSMPTEOffset, uInt32_),
217
      pack(wav.smpl.dwNumSampleLoops, uInt32_),
218
      pack(wav.smpl.dwSamplerData, uInt32_),
219
      smplLoopsBytes);
220
  }
221
  return bytes;
222
}
223
224
/**
225
 * Return the bytes of the 'smpl' loops.
226
 * @return {!Array<number>} The 'smpl' loops as an array of bytes.
227
 * @private
228
 */
229
function getSmplLoopsBytes_(wav, uInt32_, uInt16_) {
0 ignored issues
show
Unused Code introduced by
The parameter uInt16_ is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
230
  /** @type {!Array<number>} */
231
  let loops = [];
232
  for (let i=0; i<wav.smpl.dwNumSampleLoops; i++) {
233
    loops = loops.concat(
234
      pack(wav.smpl.loops[i].dwName, uInt32_),
235
      pack(wav.smpl.loops[i].dwType, uInt32_),
236
      pack(wav.smpl.loops[i].dwStart, uInt32_),
237
      pack(wav.smpl.loops[i].dwEnd, uInt32_),
238
      pack(wav.smpl.loops[i].dwFraction, uInt32_),
239
      pack(wav.smpl.loops[i].dwPlayCount, uInt32_));
240
  }
241
  return loops;
242
}
243
244
/**
245
 * Return the bytes of the 'fact' chunk.
246
 * @return {!Array<number>} The 'fact' chunk bytes.
247
 * @private
248
 */
249
function getFactBytes_(wav, uInt32_, uInt16_) {
0 ignored issues
show
Unused Code introduced by
The parameter uInt16_ is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
250
  /** @type {!Array<number>} */
251
  let bytes = [];
252
  if (wav.fact.chunkId) {
253
    bytes = bytes.concat(
254
      packString(wav.fact.chunkId),
255
      pack(wav.fact.chunkSize, uInt32_),
256
      pack(wav.fact.dwSampleLength, uInt32_));
257
  }
258
  return bytes;
259
}
260
261
/**
262
 * Return the bytes of the 'fmt ' chunk.
263
 * @return {!Array<number>} The 'fmt' chunk bytes.
264
 * @throws {Error} if no 'fmt ' chunk is present.
265
 * @private
266
 */
267
function getFmtBytes_(wav, uInt32_, uInt16_) {
268
  /** @type {!Array<number>} */
269
  let fmtBytes = [];
270
  if (wav.fmt.chunkId) {
271
    return fmtBytes.concat(
272
      packString(wav.fmt.chunkId),
273
      pack(wav.fmt.chunkSize, uInt32_),
274
      pack(wav.fmt.audioFormat, uInt16_),
275
      pack(wav.fmt.numChannels, uInt16_),
276
      pack(wav.fmt.sampleRate, uInt32_),
277
      pack(wav.fmt.byteRate, uInt32_),
278
      pack(wav.fmt.blockAlign, uInt16_),
279
      pack(wav.fmt.bitsPerSample, uInt16_),
280
      getFmtExtensionBytes_(wav, uInt32_, uInt16_));
281
  }
282
  throw Error('Could not find the "fmt " chunk');
283
}
284
285
/**
286
 * Return the bytes of the fmt extension fields.
287
 * @return {!Array<number>} The fmt extension bytes.
288
 * @private
289
 */
290
function getFmtExtensionBytes_(wav, uInt32_, uInt16_) {
291
  /** @type {!Array<number>} */
292
  let extension = [];
293
  if (wav.fmt.chunkSize > 16) {
294
    extension = extension.concat(
295
      pack(wav.fmt.cbSize, uInt16_));
296
  }
297
  if (wav.fmt.chunkSize > 18) {
298
    extension = extension.concat(
299
      pack(wav.fmt.validBitsPerSample, uInt16_));
300
  }
301
  if (wav.fmt.chunkSize > 20) {
302
    extension = extension.concat(
303
      pack(wav.fmt.dwChannelMask, uInt32_));
304
  }
305
  if (wav.fmt.chunkSize > 24) {
306
    extension = extension.concat(
307
      pack(wav.fmt.subformat[0], uInt32_),
308
      pack(wav.fmt.subformat[1], uInt32_),
309
      pack(wav.fmt.subformat[2], uInt32_),
310
      pack(wav.fmt.subformat[3], uInt32_));
311
  }
312
  return extension;
313
}
314
315
/**
316
 * Return the bytes of the 'LIST' chunk.
317
 * @return {!Array<number>} The 'LIST' chunk bytes.
318
 */
319
function getLISTBytes_(wav, uInt32_, uInt16_) {
320
  /** @type {!Array<number>} */
321
  let bytes = [];
322
  for (let i=0; i<wav.LIST.length; i++) {
323
    /** @type {!Array<number>} */
324
    let subChunksBytes = getLISTSubChunksBytes_(
325
        wav.LIST[i].subChunks, wav.LIST[i].format, wav, uInt32_, uInt16_);
326
    bytes = bytes.concat(
327
      packString(wav.LIST[i].chunkId),
328
      pack(subChunksBytes.length + 4, uInt32_),
329
      packString(wav.LIST[i].format),
330
      subChunksBytes);
331
  }
332
  return bytes;
333
}
334
335
/**
336
 * Return the bytes of the sub chunks of a 'LIST' chunk.
337
 * @param {!Array<!Object>} subChunks The 'LIST' sub chunks.
338
 * @param {string} format The format of the 'LIST' chunk.
339
 *    Currently supported values are 'adtl' or 'INFO'.
340
 * @return {!Array<number>} The sub chunk bytes.
341
 * @private
342
 */
343
function getLISTSubChunksBytes_(subChunks, format, wav, uInt32_, uInt16_) {
344
  /** @type {!Array<number>} */
345
  let bytes = [];
346
  for (let i=0; i<subChunks.length; i++) {
347
    if (format == 'INFO') {
348
      bytes = bytes.concat(
349
        packString(subChunks[i].chunkId),
350
        pack(subChunks[i].value.length + 1, uInt32_),
351
        io.writeString_(
352
          subChunks[i].value, subChunks[i].value.length));
353
      bytes.push(0);
354
    } else if (format == 'adtl') {
355
      if (['labl', 'note'].indexOf(subChunks[i].chunkId) > -1) {
356
        bytes = bytes.concat(
357
          packString(subChunks[i].chunkId),
358
          pack(
359
            subChunks[i].value.length + 4 + 1, uInt32_),
360
          pack(subChunks[i].dwName, uInt32_),
361
          io.writeString_(
362
            subChunks[i].value,
363
            subChunks[i].value.length));
364
        bytes.push(0);
365
      } else if (subChunks[i].chunkId == 'ltxt') {
366
        bytes = bytes.concat(
367
          getLtxtChunkBytes_(subChunks[i], wav, uInt32_, uInt16_));
368
      }
369
    }
370
    if (bytes.length % 2) {
371
      bytes.push(0);
372
    }
373
  }
374
  return bytes;
375
}
376
377
/**
378
 * Return the bytes of a 'ltxt' chunk.
379
 * @param {!Object} ltxt the 'ltxt' chunk.
380
 * @return {!Array<number>} The 'ltxt' chunk bytes.
381
 * @private
382
 */
383
function getLtxtChunkBytes_(ltxt, wav, uInt32_, uInt16_) {
384
  return [].concat(
385
    packString(ltxt.chunkId),
386
    pack(ltxt.value.length + 20, uInt32_),
387
    pack(ltxt.dwName, uInt32_),
388
    pack(ltxt.dwSampleLength, uInt32_),
389
    pack(ltxt.dwPurposeID, uInt32_),
390
    pack(ltxt.dwCountry, uInt16_),
391
    pack(ltxt.dwLanguage, uInt16_),
392
    pack(ltxt.dwDialect, uInt16_),
393
    pack(ltxt.dwCodePage, uInt16_),
394
    io.writeString_(ltxt.value, ltxt.value.length));
395
}
396
397
/**
398
 * Return the bytes of the 'junk' chunk.
399
 * @return {!Array<number>} The 'junk' chunk bytes.
400
 * @private
401
 */
402
function getJunkBytes_(wav, uInt32_, uInt16_) {
0 ignored issues
show
Unused Code introduced by
The parameter uInt16_ is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
403
  /** @type {!Array<number>} */
404
  let bytes = [];
405
  if (wav.junk.chunkId) {
406
    return bytes.concat(
407
      packString(wav.junk.chunkId),
408
      pack(wav.junk.chunkData.length, uInt32_),
409
      wav.junk.chunkData);
410
  }
411
  return bytes;
412
}
413