4
\$\begingroup\$

This is a single isomorphic class in ES6, written with the intention of generating a full WAV file given note names and durations in seconds. In JavaScript it can be exported as a Blob and in Node.js it can be exported as a Buffer, to be written to the filesystem.

I have left thorough comments in the code to help document the usage of the function arguments. There is also an example script at the end utilizing this small library.

I hope to address readability and/or performance, but any other advice is welcome too.

class WAV {
  static frequency(note) {
    const map = {
      'REST': 0,
      'A0': 27.5,
      'A0#': 29.135,
      'B0b': 29.135,
      'B0': 30.868,
      'C1b': 30.868,
      'C1': 32.703,
      'C1#': 34.648,
      'D1b': 34.648,
      'D1': 36.708,
      // ...
      // skipped for brevity
      // ...
      'C8': 4185.984
    };

    return map[note];
  }

  constructor(numChannels = 1, sampleRate = 44100, data = [], bitsPerSample = 16, littleEndian = true) {
    // WAV header is always 44 bytes
    this.header = new ArrayBuffer(44);
    // flexible container for reading / writing raw bytes in header
    this.view = new DataView(this.header);
    // leave sound data as non typed array for more flexibility
    this.data = data;

    // initialize as non-configurable because it
    // causes script to freeze when using parsed
    // chunk sizes with wrong endianess assumed
    Object.defineProperty(this, 'littleEndian', {
      configurable: false,
      enumerable: true,
      value: littleEndian,
      writable: false
    });

    // initial write index in data array
    this.pointer = 0;

    // WAV header properties
    this.ChunkID = littleEndian ? 'RIFF' : 'RIFX';
    this.ChunkSize = this.header.byteLength - 8;
    this.Format = 'WAVE';
    this.SubChunk1ID = 'fmt ';
    this.SubChunk1Size = 16;
    this.AudioFormat = 1;
    this.NumChannels = numChannels;
    this.SampleRate = sampleRate;
    this.ByteRate = numChannels * sampleRate * bitsPerSample >>> 3;
    this.BlockAlign = numChannels * bitsPerSample >>> 3;
    this.BitsPerSample = bitsPerSample
    this.SubChunk2ID = 'data';
    this.SubChunk2Size = data.length * bitsPerSample >>> 3;
  }

  // internal setter for writing strings as raw bytes to header
  setString(str, byteLength = str.length, byteOffset = 0) {
    for (var i = 0; i < byteLength; i++) {
      this.view.setUint8(byteOffset + i, str.charCodeAt(i));
    }
  }

  // internal getter for reading raw bytes as strings from header
  getString(byteLength, byteOffset = 0) {
    for (var i = 0, str = ''; i < byteLength; i++) {
      str += String.fromCharCode(this.view.getUint8(byteOffset + i));
    }

    return str;
  }

  // header property mutators

  set ChunkID(str) {
    this.setString(str, 4, 0);
  }

  get ChunkID() {
    return this.getString(4, 0);
  }

  set ChunkSize(uint) {
    this.view.setUint32(4, uint, this.littleEndian);
  }

  get ChunkSize() {
    return this.view.getUint32(4, this.littleEndian);
  }

  set Format(str) {
    this.setString(str, 4, 8);
  }

  get Format() {
    return this.getString(4, 8);
  }

  set SubChunk1ID(str) {
    this.setString(str, 4, 12);
  }

  get SubChunk1ID() {
    return this.getString(4, 12);
  }

  set SubChunk1Size(uint) {
    this.view.setUint32(16, uint, this.littleEndian);
  }

  get SubChunk1Size() {
    return this.view.getUint32(16, this.littleEndian);
  }

  set AudioFormat(uint) {
    this.view.setUint16(20, uint, this.littleEndian);
  }

  get AudioFormat() {
    return this.view.getUint16(20, this.littleEndian);
  }

  set NumChannels(uint) {
    this.view.setUint16(22, uint, this.littleEndian);
  }

  get NumChannels() {
    return this.view.getUint16(22, this.littleEndian);
  }

  set SampleRate(uint) {
    this.view.setUint32(24, uint, this.littleEndian);
  }

  get SampleRate() {
    return this.view.getUint32(24, this.littleEndian);
  }

  set ByteRate(uint) {
    this.view.setUint32(28, uint, this.littleEndian);
  }

  get ByteRate() {
    return this.view.getUint32(28, this.littleEndian);
  }

  set BlockAlign(uint) {
    this.view.setUint16(32, uint, this.littleEndian);
  }

  get BlockAlign() {
    return this.view.getUint16(32, this.littleEndian);
  }

  set BitsPerSample(uint) {
    this.view.setUint16(34, uint, this.littleEndian);
  }

  get BitsPerSample() {
    return this.view.getUint16(34, this.littleEndian);
  }

  set SubChunk2ID(str) {
    this.setString(str, 4, 36);
  }

  get SubChunk2ID() {
    return this.getString(4, 36);
  }

  set SubChunk2Size(uint) {
    this.view.setUint32(40, uint, this.littleEndian);
  }

  get SubChunk2Size() {
    return this.view.getUint32(40, this.littleEndian);
  }

  // internal getter for sound data as
  // typed array based on header properties
  get typedData() {
    var bytesPerSample = this.BitsPerSample >>> 3;
    var data = this.data;
    var size = this.SubChunk2Size;
    var bytes = size / bytesPerSample;
    var buffer = new ArrayBuffer(size);
    var dataView = new DataView(buffer);

    // convert signed normalized sound data to typed integer data
    // i.e. [-1, 1] -> [INT_MIN, INT_MAX]
    var amplitude = (1 << ((bytesPerSample << 3) - 1)) - 1;
    var i;

    switch (bytesPerSample) {
    case 1:
      for (i = 0; i < bytes; i++) {
        // convert signed to unsigned by adding 128
        // WAV uses unsigned data for 8-bit encoding
        dataView.setUint8(i, data[i] * amplitude + 128);
      }
      break;
    case 2:
      for (i = 0; i < bytes; i++) {
        dataView.setInt16(i * 2, data[i] * amplitude, this.littleEndian);
      }
      break;
    case 4:
      for (i = 0; i < bytes; i++) {
        dataView.setInt32(i * 4, data[i] * amplitude, this.littleEndian);
      }
    }

    return buffer;
  }

  // binary container outputs

  // browser-specific
  // generates blob from concatenated typed arrays
  toBlob() {
    return new Blob([this.header, this.typedData], {type: 'audio/wav'});
  }

  // Node.js-specific
  // generates buffer from concatenated typed arrays
  toBuffer() {
    return Buffer.concat([Buffer.from(this.header), Buffer.from(this.typedData)]);
  }

  // sound data mutators

  // writes the specified note to the sound data
  // for amount of time in seconds
  // at given normalized amplitude
  // to channels listed (or all by default)
  // adds to existing data by default
  // and does not reset write index after operation by default
  addNote(note, time, amplitude = 1, channels = [], blend = true, reset = false) {
    var i;

    // by default write to all channels
    if (channels.length === 0) {
      channels = new Array(this.NumChannels);

      for (i = 0; i < channels.length; i++) {
        channels[i] = i;
      }
    }

    // creating local references to properties
    var data = this.data;
    var numChannels = this.NumChannels;
    var sampleRate = this.SampleRate;
    var bitsPerSample = this.BitsPerSample;

    // calculating properties of given note
    var frequency = WAV.frequency(note) * Math.PI * 2 / sampleRate;
    var period = Math.PI * 2 / frequency;

    // amount of blocks to be written
    var samplesOut = Math.round(sampleRate * time);
    // reduces sound artifacts by stopping at last block
    // where sine wave is approximately 0
    var nonZero = Math.round(samplesOut / period) * period;

    // index of start and stop samples
    var start = this.pointer;
    var stop = data.length;

    // determines amount of blocks to be updated
    var samplesIn = Math.min(Math.floor((stop - start) / numChannels), samplesOut);

    // i = index of each sample block
    i = 0;
    // j = index of each channel in block
    var j;

    // update existing data
    if (blend && frequency > 0) {
      for (i = 0; i < samplesIn; i++) {
        for (j = 0; j < channels.length; j++) {
          data[start + i * numChannels + channels[j]] += amplitude * (
            i > nonZero ? 0 : Math.sin(frequency * i)
          );
        }
      }
    } else if (blend) {
      i = samplesIn;
    }

    // append or overwrite data
    for (; i < samplesOut; i++) {
      for (j = 0; j < numChannels; j++) {
        data[start + i * numChannels + j] = amplitude * (
          frequency === 0 || i > nonZero || channels.indexOf(j) === -1 ?
          0 : Math.sin(frequency * i)
        );
      }
    }

    // update header properties
    var end = Math.max(start + samplesOut * numChannels, stop) * bitsPerSample >>> 3;

    this.ChunkSize = end + this.header.byteLength - 8;
    this.SubChunk2Size = end;

    if (!reset) {
      // move write index to end of written data
      this.pointer = start + i * numChannels;
    }
  }

  // adds specified notes in series
  // each playing for time * relativeDuration seconds
  // followed by a time * (1 - relativeDuration) second rest
  addProgression(notes, times, amplitude = 1, channels = [], blend = true, reset = false, relativeDuration = 1) {
    var start = this.pointer;

    for (var i = 0, note, time, rest; i < notes.length; i++) {
      note = notes[i];

      if (relativeDuration === 1 || WAV.frequency(note) === 0) {
        this.addNote(note, times[i], amplitude, channels, blend, false);
      } else {
        time = times[i] * relativeDuration;
        rest = times[i] - time;

        this.addNote(note, time, amplitude, channels, blend, false);
        this.addNote('REST', rest, amplitude, channels, blend, false);
      }
    }

    if (reset) {
      this.pointer = start;
    }
  }

  // adds specified notes in parallel
  // playing for time * relativeDuration seconds
  // followed by a time * (1 - relativeDuration) second rest
  addChord(notes, time, amplitude = 1 / notes.length, channels = [], blend = true, reset = false, relativeDuration = 1) {
    var start = this.pointer;

    var secs = time * relativeDuration;
    var rest = time - secs;

    for (var i = 0, note; i < notes.length; i++) {
      // reset pointer manually at the beginning of each iteration
      this.pointer = start;

      note = notes[i];

      if (relativeDuration === 1 || WAV.frequency(note) === 0) {
        this.addNote(note, time, amplitude, channels, blend, false);
      } else {
        this.addNote(note, secs, amplitude, channels, blend, false);
        this.addNote('REST', rest, amplitude, channels, blend, false);
      }

      // after 1st iteration, notes must blend since they are in parallel
      blend = true;
    }

    if (reset) {
      this.pointer = start;
    }
  }
}

Example Usage

/* song data for Portal - Still Alive */
var S = 0.125, // sixteenth note
    E = 0.25,  // eighth note
    Q = 0.5,   // quarter note
    H = 1,     // half note
    W = 2;     // whole note

    // primary melody notes (channel 0)
var melody = ["G5", "F5#", "E5", "E5", "F5#", "REST", "A4", "G5", "F5#", "E5", "E5", "F5#", "D5", "E5", "A4", "A4", "E5", "F5#", "G5", "E5", "C5#", "D5", "E5", "A4", "A4", "F5#", "REST", "G5", "F5#", "E5", "E5", "F5#", "REST", "A4", "G5", "F5#", "E5", "E5", "F5#", "D5", "E5", "A4", "REST", "E5", "F5#", "G5", "E5", "C5#", "D5", "E5", "A4", "D5", "E5", "F5", "E5", "D5", "C5", "REST", "A4", "A4#", "C5", "F5", "E5", "D5", "D5", "C5", "D5", "C5", "C5", "C5", "A4", "A4#", "C5", "F5", "G5", "F5", "E5", "D5", "D5", "E5", "F5", "F5", "G5", "A5", "A5#", "A5#", "A5", "G5", "F5", "G5", "A5", "A5", "G5", "F5", "D5", "C5", "D5", "F5", "F5", "E5", "E5", "F5#", "F5#", "A5", "D6", "F6#", "D6", "B5", "D6", "F6#", "D6", "A5", "D6", "F6#", "D6", "B5", "D6", "F6#", "D6", "A5", "D6", "F6#", "D6", "B5", "D6", "F6#", "D6", "REST", "A4", "G5", "F5#", "E5", "E5", "F5#", "REST", "G5", "F5#", "E5", "E5", "F5#", "D5", "E5", "A4", "REST", "E5", "F5#", "G5", "E5", "C5#", "D5", "E5", "A4", "A4", "F5#", "REST", "A4", "G5", "F5#", "E5", "E5", "F5#", "REST", "A4", "G5", "F5#", "E5", "E5", "F5#", "D5", "E5", "A4", "REST", "E5", "F5#", "G5", "E5", "C5#", "D5", "E5", "A4", "D5", "E5", "F5", "E5", "D5", "C5", "A4", "A4#", "C5", "F5", "E5", "D5", "D5", "C5", "D5", "C5", "C5", "C5", "A4", "A4#", "C5", "F5", "G5", "F5", "E5", "D5", "D5", "E5", "F5", "F5", "G5", "A5", "A5#", "A5#", "A5", "G5", "F5", "G5", "A5", "A5", "G5", "F5", "F5", "D5", "C5", "D5", "F5", "F5", "E5", "E5", "F5#", "F5#", "REST", "A5", "A5", "B5", "A5", "F5#", "D5", "E5", "F5#", "F5#", "REST", "G5", "A5", "A5", "REST", "G5", "F5#", "F5#", "REST"],
    // secondary melody notes (channel 0)
    melody_sec = ["REST", "B5", "A5", "G5", "G5", "A5", "REST", "B5", "A5", "G5", "G5", "A5", "F5#", "G5", "D5", "REST", "G5", "A5", "A5"],
    // primary harmony notes (channel 1)
    harmony = ["REST", "A3", "D4", "F4#", "D4", "B3", "D4", "F4#", "D4", "A3", "D4", "F4#", "D4", "B3", "D4", "F4#", "D4", "A3", "D4", "F4#", "D4", "B3", "D4", "F4#", "D4", "A3", "D4", "F4#", "D4", "B3", "D4", "F4#", "D4", "B3", "E4", "G4", "E4", "B3", "E4", "G4", "E4", "A3", "E4", "G4", "E4", "A3", "E4", "G4", "E4", "A3", "D4", "F4#", "D4", "B3", "D4", "F4#", "D4", "A3", "D4", "F4#", "D4", "B3", "D4", "F4#", "D4", "A3", "D4", "F4#", "D4", "B3", "D4", "F4#", "D4", "A3", "D4", "F4#", "D4", "B3", "D4", "F4#", "D4", "A3", "D4", "F4#", "D4", "B3", "D4", "F4#", "D4", "A3", "D4", "F4#", "D4", "B3", "D4", "F4#", "D4", "B3", "E4", "G4", "E4", "B3", "E4", "G4", "E4", "A3", "E4", "G4", "E4", "A3", "E4", "G4", "E4", "A3#", "D4", "F4", "A4", "REST", "D4", "D4", "B3", "B3", "D4", "D4", "B3", "B3", "D4", "D4", "B3", "B3", "D4", "D4", "B3", "B3", "A3", "D4", "F4#", "D4", "B3", "D4", "F4#", "D4", "A3", "D4", "F4#", "D4", "B3", "D4", "F4#", "D4", "A3", "D4", "F4#", "D4", "B3", "D4", "F4#", "D4", "A3", "D4", "F4#", "D4", "B3", "D4", "F4#", "D4", "B3", "E4", "G4", "E4", "B3", "E4", "G4", "E4", "A3", "E4", "G4", "E4", "A3", "E4", "G4", "E4", "A3", "D4", "F4#", "D4", "B3", "D4", "F4#", "D4", "D4", "D4", "B3", "B3", "D4", "D4", "B3", "B3", "D4", "D4", "B3", "B3", "D4", "D4", "B3", "B3", "D4", "D4", "B3", "B3", "E4", "E4", "E4", "F4#", "G4", "A3", "A3", "A3", "B3", "C4#", "A3", "A3#", "A3#", "A3#", "A3#", "A3#", "A3#", "A3#", "F4", "F4", "REST", "C4", "C4", "REST", "A3#", "A3#", "REST", "F4", "F4", "REST", "F4", "F4", "REST", "C4", "C4", "REST", "A3#", "A3#", "REST", "F4", "F4", "REST", "A3#", "A3#", "A3#", "A3#", "C4", "C4", "C4", "C4", "F4", "F4", "E4", "E4", "D4", "D4", "C4", "C4", "A3#", "F4", "A3", "E4", "D4", "D4", "B3", "B3", "D4", "D4", "B3", "B3", "D4", "D4", "B3", "B3", "D4", "D4", "B3", "B3", "D4", "REST"],
    // secondary harmony notes (channel 1)
    harmony_sec = ["REST", "C4", "C4", "REST", "G3", "G3", "REST", "F3", "F3", "REST", "C4", "C4", "REST", "C4", "C4", "REST", "G3", "G3", "REST", "F3", "F3", "REST", "C4", "C4", "REST", "F3", "F3", "F3", "F3", "G3", "G3", "G3", "G3", "C4", "C4", "B3", "B3", "A3", "A3", "G3", "G3"];
    // primary melody durations (channel 0)
var melody_durs = [E, E, E, E, H, W - E, E, E, E, E, Q, Q + E, Q, E, W, E, Q, E, Q + E, E, Q, Q + E, Q, E, Q, W - E, H, E, E, E, E, H, W - E, E, E, E, E, Q + E, E, Q + E, E, H + E, H, Q, E, Q + E, E, Q + E, E, Q, E, E, E, E, E, E, E, Q, E, E, Q, Q, E, E, E, E, E, E, Q, Q, E, E, Q, Q, E, E, E, E, E, E, Q, Q, E, E, E, E, Q, Q, E, E, E, E, Q, Q, E, E, E, E, E, Q, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, Q + E, E, E, E, E, E + S, H - S, W, E, E, E, Q + E, E, Q, Q, H + E, H, Q, E, Q + E, Q, Q, E, Q + E, E, Q, H + E, H + E, E, E, E, E, E, Q, W + E, E, E, E, E, Q + E, E, Q + E, E, H + E, H, Q, E, Q + E, Q, Q, E, Q, E, E, E, E, E, E, Q + E, E, E, Q, Q, E, E, E, E, E, E, Q, Q, E, E, Q, Q, E, E, E, E, E, E, Q, Q, E, E, E, E, Q, Q, E, E, E, E, E, E, Q, E, E, E, E, E, Q, E, E, H + Q, E, E, E, E, E, E, Q, E, E, Q + E, Q + E, E, E, Q + E, Q + E, E, E, H + E, H],
    // secondary melody durations (channel 0)
    melody_sec_durs = [34 * W, E, E, E, E, Q, W + Q, E, E, E, Q + E, E, Q + E, E, H + E, 12 * W + E, E, E, Q + E],
    // primary harmony durations (channel 1)
    harmony_durs = [H, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, 7 * W + H, Q + E, E, Q + E, E, Q + E, E, Q + E, E, Q + E, E, Q + E, E, Q + E, E, Q + E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, Q + E, E, Q + E, E, Q + E, E, Q + E, E, Q + E, E, Q + E, E, Q + E, E, Q + E, E, Q + E, E, Q + E, E, Q + E, E, E, E, Q, Q + E, E, E, E, E, E, Q, E, E, E, E, E, E, E, E, Q, E, E, Q, E, E, Q, E, E, Q, E, E, Q, E, E, Q, E, E, Q, E, E, Q, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, Q, Q, Q, Q, Q + E, E, Q + E, E, Q + E, E, Q + E, E, Q + E, E, Q + E, E, Q + E, E, Q + E, E, H, H],
    // secondary harmony durations (channel 1)
    harmony_sec_durs = [41 * W + H, E, E, Q, E, E, Q, E, E, Q, E, E, Q, E, E, Q, E, E, Q, E, E, Q, E, E, Q, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E];
/* end song data */

// construct an instance of WAV with
// 2 channels
// sample rate of 44100 samples/second
// no initial data
// 16 bits per sample
// little endian byte order
var wav = new WAV(2, 44100, [], 16, true);

// add each layer of song as a progression

// add primary melody with
// normalized amplitude of 0.6
// to channel 0
// without blending
// reset internal pointer
// relative duration of 0.75
wav.addProgression(melody, melody_durs, 0.6, [0], false, true, 0.75);

// add secondary melody with
// normalized amplitude of 0.4
// to channel 0
// with blending
wav.addProgression(melody_sec, melody_sec_durs, 0.4, [0], true, true, 0.75);

// add primary harmony with
// normalized amplitude of 0.6
// to channel 1
wav.addProgression(harmony, harmony_durs, 0.6, [1], true, true, 0.75);

// add secondary harmony with
// normalized amplitude of 0.4
wav.addProgression(harmony_sec, harmony_sec_durs, 0.4, [1], true, true, 0.75);

// create chunk of memory containing WAV header and sound data
var blob = wav.toBlob();
// create temporary local URL from generated blob
var url = URL.createObjectURL(blob);
// create audio source from URL
var audio = new Audio(url);
// play the created audio
audio.play();
<script src="https://cdn.rawgit.com/patrickroberts/3b065ab94ce5094baacf45ed23e2a16e/raw/0b2592af2af4d271a4fc659e5f291d1b3ba1a952/wav.babel.js"></script>

\$\endgroup\$
  • \$\begingroup\$ @wOxxOm I was looking at the documentation for the closure compiler just now. It appears not to be a good solution for projects that are intended to be used as libraries, but rather a solution for complete, self-contained applications, so I don't think compiling my code applies in this case. \$\endgroup\$ – Patrick Roberts Sep 25 '16 at 22:24
  • \$\begingroup\$ I have rolled back the last edit. Please see what you may and may not do after receiving answers. \$\endgroup\$ – Vogel612 Sep 26 '16 at 20:15
  • \$\begingroup\$ @Vogel612 I'm sorry, I didn't realize that was not acceptable. I thought that leaving the original question intact would make the revision legitimate. \$\endgroup\$ – Patrick Roberts Sep 26 '16 at 20:18
2
\$\begingroup\$

Any performance optimization must start with Timeline/JS profiling.

In this case the slowest part is typedData getter, ~850ms on i7 CPU with the OP's example. This is an enormously huge time for such a simple operation.

Let's speed it up 10 times or more by eliminating function calls to DataView's methods in favor of directly accessed unsigned byte view:

var uint8 = new Uint8Array(buffer);

8-bit WAV case is simple:

case 1:
  for (i = 0; i < bytes; i++) {
    uint8[i] = data[i] * amplitude + 128;
  }
  break;

16-bit WAV requires us to implement byte order maintenance manually (see P.S.):

case 2:
  if (this.littleEndian) {
    for (i = 0; i < bytes; i++) {
      var v = data[i] * amplitude;
      if (!v) {
        uint8[i * 2] = 0;
        uint8[i * 2 + 1] = 0;
      } else {
        if (v < 0) {
          v = 65536 + v;
        }
        uint8[i * 2] = v & 255;
        uint8[i * 2 + 1] = v >> 8;
      }
    }
  } else {
    for (i = 0; i < bytes; i++) {
      var v = data[i] * amplitude;
      if (!v) {
        uint8[i * 2] = 0;
        uint8[i * 2 + 1] = 0;
      } else {
        if (v < 0) {
          v = 65536 + v;
        }
        uint8[i * 2] = v >> 8;
        uint8[i * 2 + 1] = v & 255;
      }
    }
  }
  break;

32-bit case is similar.

Eliminate the largest bottlenecks and repeat the profiling tests progressively until satisfied.

P.S. The optimized 16-bit case without inner-loop branching as per the comments:

case 2:
  if (this.littleEndian) {
    for (i = 0; i < bytes; i++) {
      var v = (data[i] * amplitude + 65536) & 65535;
      uint8[i * 2] = v & 255;
      uint8[i * 2 + 1] = v >> 8;
    }
  } else {
    for (i = 0; i < bytes; i++) {
      var v = (data[i] * amplitude + 65536) & 65535;
      uint8[i * 2] = v >> 8;
      uint8[i * 2 + 1] = v & 255;
    }
  }
  break;
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ I've read content from stackoverflow claiming that DataView methods for manually determining endianess were slow, and now I understand why. I will definitely use this advice, thank you. If you don't mind me asking, is there a reason the if (!v) check is necessary? It seems to be unnecessary branching logic within a heavy for loop. \$\endgroup\$ – Patrick Roberts Sep 26 '16 at 12:18
  • \$\begingroup\$ Yeah, it's quite probably an unnecessary comparison. A leftover from my initial experiments. Such preemptive microoptimizations aren't needed usually. \$\endgroup\$ – wOxxOm Sep 26 '16 at 12:23
  • 2
    \$\begingroup\$ By the way, I noticed a semantic issue with the variable name bytes. I should have called it samples. So, thanks for helping me notice that. \$\endgroup\$ – Patrick Roberts Sep 26 '16 at 14:52
  • \$\begingroup\$ Try var v = (data[i] * amplitude + 65536) & 65535; without both inner if checks. \$\endgroup\$ – wOxxOm Sep 26 '16 at 18:44
  • \$\begingroup\$ Already figured that out on my own. I'm gonna update my question with my iterated refactors based on your advice. \$\endgroup\$ – Patrick Roberts Sep 26 '16 at 18:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.