nfroidure/midifile

View on GitHub
src/MIDIFileHeader.js

Summary

Maintainability
C
7 hrs
Test Coverage
'use strict';

// MIDIFileHeader : Read and edit a MIDI header chunk in a given ArrayBuffer
function MIDIFileHeader(buffer) {
  let a;
  // No buffer creating him
  if (!buffer) {
    a = new Uint8Array(MIDIFileHeader.HEADER_LENGTH);
    // Adding the header id (MThd)
    a[0] = 0x4d;
    a[1] = 0x54;
    a[2] = 0x68;
    a[3] = 0x64;
    // Adding the header chunk size
    a[4] = 0x00;
    a[5] = 0x00;
    a[6] = 0x00;
    a[7] = 0x06;
    // Adding the file format (1 here cause it's the most commonly used)
    a[8] = 0x00;
    a[9] = 0x01;
    // Adding the track count (1 cause it's a new file)
    a[10] = 0x00;
    a[11] = 0x01;
    // Adding the time division (192 ticks per beat)
    a[12] = 0x00;
    a[13] = 0xc0;
    // saving the buffer
    this.datas = new DataView(a.buffer, 0, MIDIFileHeader.HEADER_LENGTH);
    // Parsing the given buffer
  } else {
    if (!(buffer instanceof ArrayBuffer)) {
      throw Error('Invalid buffer received.');
    }
    this.datas = new DataView(buffer, 0, MIDIFileHeader.HEADER_LENGTH);
    // Reading MIDI header chunk
    if (
      !(
        'M' === String.fromCharCode(this.datas.getUint8(0)) &&
        'T' === String.fromCharCode(this.datas.getUint8(1)) &&
        'h' === String.fromCharCode(this.datas.getUint8(2)) &&
        'd' === String.fromCharCode(this.datas.getUint8(3))
      )
    ) {
      throw new Error('Invalid MIDIFileHeader : MThd prefix not found');
    }
    // Reading chunk length
    if (6 !== this.datas.getUint32(4)) {
      throw new Error('Invalid MIDIFileHeader : Chunk length must be 6');
    }
  }
}

// Static constants
MIDIFileHeader.HEADER_LENGTH = 14;
MIDIFileHeader.FRAMES_PER_SECONDS = 1;
MIDIFileHeader.TICKS_PER_BEAT = 2;

// MIDI file format
MIDIFileHeader.prototype.getFormat = function() {
  const format = this.datas.getUint16(8);
  if (0 !== format && 1 !== format && 2 !== format) {
    throw new Error(
      'Invalid MIDI file : MIDI format (' +
        format +
        '),' +
        ' format can be 0, 1 or 2 only.'
    );
  }
  return format;
};

MIDIFileHeader.prototype.setFormat = function(format) {
  if (0 !== format && 1 !== format && 2 !== format) {
    throw new Error(
      'Invalid MIDI format given (' +
        format +
        '),' +
        ' format can be 0, 1 or 2 only.'
    );
  }
  this.datas.setUint16(8, format);
};

// Number of tracks
MIDIFileHeader.prototype.getTracksCount = function() {
  return this.datas.getUint16(10);
};

MIDIFileHeader.prototype.setTracksCount = function(n) {
  return this.datas.setUint16(10, n);
};

// Tick compute
MIDIFileHeader.prototype.getTickResolution = function(tempo) {
  // Frames per seconds
  if (this.datas.getUint16(12) & 0x8000) {
    return 1000000 / (this.getSMPTEFrames() * this.getTicksPerFrame());
    // Ticks per beat
  }
  // Default MIDI tempo is 120bpm, 500ms per beat
  tempo = tempo || 500000;
  return tempo / this.getTicksPerBeat();
};

// Time division type
MIDIFileHeader.prototype.getTimeDivision = function() {
  if (this.datas.getUint16(12) & 0x8000) {
    return MIDIFileHeader.FRAMES_PER_SECONDS;
  }
  return MIDIFileHeader.TICKS_PER_BEAT;
};

// Ticks per beat
MIDIFileHeader.prototype.getTicksPerBeat = function() {
  var divisionWord = this.datas.getUint16(12);
  if (divisionWord & 0x8000) {
    throw new Error('Time division is not expressed as ticks per beat.');
  }
  return divisionWord;
};

MIDIFileHeader.prototype.setTicksPerBeat = function(ticksPerBeat) {
  this.datas.setUint16(12, ticksPerBeat & 0x7fff);
};

// Frames per seconds
MIDIFileHeader.prototype.getSMPTEFrames = function() {
  const divisionWord = this.datas.getUint16(12);
  let smpteFrames;

  if (!(divisionWord & 0x8000)) {
    throw new Error('Time division is not expressed as frames per seconds.');
  }
  smpteFrames = divisionWord & 0x7f00;
  if (-1 === [24, 25, 29, 30].indexOf(smpteFrames)) {
    throw new Error('Invalid SMPTE frames value (' + smpteFrames + ').');
  }
  return 29 === smpteFrames ? 29.97 : smpteFrames;
};

MIDIFileHeader.prototype.getTicksPerFrame = function() {
  const divisionWord = this.datas.getUint16(12);

  if (!(divisionWord & 0x8000)) {
    throw new Error('Time division is not expressed as frames per seconds.');
  }
  return divisionWord & 0x00ff;
};

MIDIFileHeader.prototype.setSMTPEDivision = function(
  smpteFrames,
  ticksPerFrame
) {
  if (29.97 === smpteFrames) {
    smpteFrames = 29;
  }
  if (-1 === [24, 25, 29, 30].indexOf(smpteFrames)) {
    throw new Error('Invalid SMPTE frames value given (' + smpteFrames + ').');
  }
  if (0 > ticksPerFrame || 0xff < ticksPerFrame) {
    throw new Error(
      'Invalid ticks per frame value given (' + smpteFrames + ').'
    );
  }
  this.datas.setUint8(12, 0x80 | smpteFrames);
  this.datas.setUint8(13, ticksPerFrame);
};

module.exports = MIDIFileHeader;