Creative Voice File: JavaScript parsing library

Creative Voice File is a container file format for digital audio wave data. Initial revisions were able to support only unsigned 8-bit PCM and ADPCM data, later versions were revised to add support for 16-bit PCM and a-law / u-law formats.

This format was actively used in 1990s, around the advent of Creative's sound cards (Sound Blaster family). It was a popular choice for a digital sound container in lots of games and multimedia software due to simplicity and availability of Creative's recording / editing tools.

File extension

voc

KS implementation details

License: CC0-1.0

References

This page hosts a formal specification of Creative Voice File using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Usage

Runtime library

All parsing code for JavaScript generated by Kaitai Struct depends on the JavaScript runtime library. You have to install it before you can parse data.

The JavaScript runtime library is available at npm:

npm install kaitai-struct

Code

See the usage examples in the JavaScript notes.

Parse structure from an ArrayBuffer:

var arrayBuffer = ...;
var data = new CreativeVoiceFile(new KaitaiStream(arrayBuffer));

After that, one can get various attributes from the structure by accessing fields or properties like:

data.headerSize // => Total size of this main header (usually 0x001A)

JavaScript source code to parse Creative Voice File

CreativeVoiceFile.js

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    define(['kaitai-struct/KaitaiStream'], factory);
  } else if (typeof module === 'object' && module.exports) {
    module.exports = factory(require('kaitai-struct/KaitaiStream'));
  } else {
    root.CreativeVoiceFile = factory(root.KaitaiStream);
  }
}(typeof self !== 'undefined' ? self : this, function (KaitaiStream) {
/**
 * Creative Voice File is a container file format for digital audio
 * wave data. Initial revisions were able to support only unsigned
 * 8-bit PCM and ADPCM data, later versions were revised to add support
 * for 16-bit PCM and a-law / u-law formats.
 * 
 * This format was actively used in 1990s, around the advent of
 * Creative's sound cards (Sound Blaster family). It was a popular
 * choice for a digital sound container in lots of games and multimedia
 * software due to simplicity and availability of Creative's recording
 * / editing tools.
 * @see {@link https://wiki.multimedia.cx/index.php?title=Creative_Voice|Source}
 */

var CreativeVoiceFile = (function() {
  CreativeVoiceFile.BlockTypes = Object.freeze({
    TERMINATOR: 0,
    SOUND_DATA: 1,
    SOUND_DATA_CONT: 2,
    SILENCE: 3,
    MARKER: 4,
    TEXT: 5,
    REPEAT_START: 6,
    REPEAT_END: 7,
    EXTRA_INFO: 8,
    SOUND_DATA_NEW: 9,

    0: "TERMINATOR",
    1: "SOUND_DATA",
    2: "SOUND_DATA_CONT",
    3: "SILENCE",
    4: "MARKER",
    5: "TEXT",
    6: "REPEAT_START",
    7: "REPEAT_END",
    8: "EXTRA_INFO",
    9: "SOUND_DATA_NEW",
  });

  CreativeVoiceFile.Codecs = Object.freeze({
    PCM_8BIT_UNSIGNED: 0,
    ADPCM_4BIT: 1,
    ADPCM_2_6BIT: 2,
    ADPCM_2_BIT: 3,
    PCM_16BIT_SIGNED: 4,
    ALAW: 6,
    ULAW: 7,
    ADPCM_4_TO_16BIT: 512,

    0: "PCM_8BIT_UNSIGNED",
    1: "ADPCM_4BIT",
    2: "ADPCM_2_6BIT",
    3: "ADPCM_2_BIT",
    4: "PCM_16BIT_SIGNED",
    6: "ALAW",
    7: "ULAW",
    512: "ADPCM_4_TO_16BIT",
  });

  function CreativeVoiceFile(_io, _parent, _root) {
    this._io = _io;
    this._parent = _parent;
    this._root = _root || this;

    this._read();
  }
  CreativeVoiceFile.prototype._read = function() {
    this.magic = this._io.readBytes(20);
    if (!((KaitaiStream.byteArrayCompare(this.magic, [67, 114, 101, 97, 116, 105, 118, 101, 32, 86, 111, 105, 99, 101, 32, 70, 105, 108, 101, 26]) == 0))) {
      throw new KaitaiStream.ValidationNotEqualError([67, 114, 101, 97, 116, 105, 118, 101, 32, 86, 111, 105, 99, 101, 32, 70, 105, 108, 101, 26], this.magic, this._io, "/seq/0");
    }
    this.headerSize = this._io.readU2le();
    this.version = this._io.readU2le();
    this.checksum = this._io.readU2le();
    this.blocks = [];
    var i = 0;
    while (!this._io.isEof()) {
      this.blocks.push(new Block(this._io, this, this._root));
      i++;
    }
  }

  /**
   * @see {@link https://wiki.multimedia.cx/index.php?title=Creative_Voice#Block_type_0x04:_Marker|Source}
   */

  var BlockMarker = CreativeVoiceFile.BlockMarker = (function() {
    function BlockMarker(_io, _parent, _root) {
      this._io = _io;
      this._parent = _parent;
      this._root = _root || this;

      this._read();
    }
    BlockMarker.prototype._read = function() {
      this.markerId = this._io.readU2le();
    }

    /**
     * Marker ID
     */

    return BlockMarker;
  })();

  /**
   * @see {@link https://wiki.multimedia.cx/index.php?title=Creative_Voice#Block_type_0x03:_Silence|Source}
   */

  var BlockSilence = CreativeVoiceFile.BlockSilence = (function() {
    function BlockSilence(_io, _parent, _root) {
      this._io = _io;
      this._parent = _parent;
      this._root = _root || this;

      this._read();
    }
    BlockSilence.prototype._read = function() {
      this.durationSamples = this._io.readU2le();
      this.freqDiv = this._io.readU1();
    }
    Object.defineProperty(BlockSilence.prototype, 'sampleRate', {
      get: function() {
        if (this._m_sampleRate !== undefined)
          return this._m_sampleRate;
        this._m_sampleRate = (1000000.0 / (256 - this.freqDiv));
        return this._m_sampleRate;
      }
    });

    /**
     * Duration of silence, in seconds
     */
    Object.defineProperty(BlockSilence.prototype, 'durationSec', {
      get: function() {
        if (this._m_durationSec !== undefined)
          return this._m_durationSec;
        this._m_durationSec = (this.durationSamples / this.sampleRate);
        return this._m_durationSec;
      }
    });

    /**
     * Duration of silence, in samples
     */

    /**
     * Frequency divisor, used to determine sample rate
     */

    return BlockSilence;
  })();

  /**
   * @see {@link https://wiki.multimedia.cx/index.php?title=Creative_Voice#Block_type_0x09:_Sound_data_.28New_format.29|Source}
   */

  var BlockSoundDataNew = CreativeVoiceFile.BlockSoundDataNew = (function() {
    function BlockSoundDataNew(_io, _parent, _root) {
      this._io = _io;
      this._parent = _parent;
      this._root = _root || this;

      this._read();
    }
    BlockSoundDataNew.prototype._read = function() {
      this.sampleRate = this._io.readU4le();
      this.bitsPerSample = this._io.readU1();
      this.numChannels = this._io.readU1();
      this.codec = this._io.readU2le();
      this.reserved = this._io.readBytes(4);
      this.wave = this._io.readBytesFull();
    }

    return BlockSoundDataNew;
  })();

  var Block = CreativeVoiceFile.Block = (function() {
    function Block(_io, _parent, _root) {
      this._io = _io;
      this._parent = _parent;
      this._root = _root || this;

      this._read();
    }
    Block.prototype._read = function() {
      this.blockType = this._io.readU1();
      if (this.blockType != CreativeVoiceFile.BlockTypes.TERMINATOR) {
        this.bodySize1 = this._io.readU2le();
      }
      if (this.blockType != CreativeVoiceFile.BlockTypes.TERMINATOR) {
        this.bodySize2 = this._io.readU1();
      }
      if (this.blockType != CreativeVoiceFile.BlockTypes.TERMINATOR) {
        switch (this.blockType) {
        case CreativeVoiceFile.BlockTypes.SOUND_DATA_NEW:
          this._raw_body = this._io.readBytes(this.bodySize);
          var _io__raw_body = new KaitaiStream(this._raw_body);
          this.body = new BlockSoundDataNew(_io__raw_body, this, this._root);
          break;
        case CreativeVoiceFile.BlockTypes.REPEAT_START:
          this._raw_body = this._io.readBytes(this.bodySize);
          var _io__raw_body = new KaitaiStream(this._raw_body);
          this.body = new BlockRepeatStart(_io__raw_body, this, this._root);
          break;
        case CreativeVoiceFile.BlockTypes.MARKER:
          this._raw_body = this._io.readBytes(this.bodySize);
          var _io__raw_body = new KaitaiStream(this._raw_body);
          this.body = new BlockMarker(_io__raw_body, this, this._root);
          break;
        case CreativeVoiceFile.BlockTypes.SOUND_DATA:
          this._raw_body = this._io.readBytes(this.bodySize);
          var _io__raw_body = new KaitaiStream(this._raw_body);
          this.body = new BlockSoundData(_io__raw_body, this, this._root);
          break;
        case CreativeVoiceFile.BlockTypes.EXTRA_INFO:
          this._raw_body = this._io.readBytes(this.bodySize);
          var _io__raw_body = new KaitaiStream(this._raw_body);
          this.body = new BlockExtraInfo(_io__raw_body, this, this._root);
          break;
        case CreativeVoiceFile.BlockTypes.SILENCE:
          this._raw_body = this._io.readBytes(this.bodySize);
          var _io__raw_body = new KaitaiStream(this._raw_body);
          this.body = new BlockSilence(_io__raw_body, this, this._root);
          break;
        default:
          this.body = this._io.readBytes(this.bodySize);
          break;
        }
      }
    }

    /**
     * body_size is a 24-bit little-endian integer, so we're
     * emulating that by adding two standard-sized integers
     * (body_size1 and body_size2).
     */
    Object.defineProperty(Block.prototype, 'bodySize', {
      get: function() {
        if (this._m_bodySize !== undefined)
          return this._m_bodySize;
        if (this.blockType != CreativeVoiceFile.BlockTypes.TERMINATOR) {
          this._m_bodySize = (this.bodySize1 + (this.bodySize2 << 16));
        }
        return this._m_bodySize;
      }
    });

    /**
     * Byte that determines type of block content
     */

    /**
     * Block body, type depends on block type byte
     */

    return Block;
  })();

  /**
   * @see {@link https://wiki.multimedia.cx/index.php?title=Creative_Voice#Block_type_0x06:_Repeat_start|Source}
   */

  var BlockRepeatStart = CreativeVoiceFile.BlockRepeatStart = (function() {
    function BlockRepeatStart(_io, _parent, _root) {
      this._io = _io;
      this._parent = _parent;
      this._root = _root || this;

      this._read();
    }
    BlockRepeatStart.prototype._read = function() {
      this.repeatCount1 = this._io.readU2le();
    }

    /**
     * Number of repetitions minus 1; 0xffff means infinite repetitions
     */

    return BlockRepeatStart;
  })();

  /**
   * @see {@link https://wiki.multimedia.cx/index.php?title=Creative_Voice#Block_type_0x01:_Sound_data|Source}
   */

  var BlockSoundData = CreativeVoiceFile.BlockSoundData = (function() {
    function BlockSoundData(_io, _parent, _root) {
      this._io = _io;
      this._parent = _parent;
      this._root = _root || this;

      this._read();
    }
    BlockSoundData.prototype._read = function() {
      this.freqDiv = this._io.readU1();
      this.codec = this._io.readU1();
      this.wave = this._io.readBytesFull();
    }
    Object.defineProperty(BlockSoundData.prototype, 'sampleRate', {
      get: function() {
        if (this._m_sampleRate !== undefined)
          return this._m_sampleRate;
        this._m_sampleRate = (1000000.0 / (256 - this.freqDiv));
        return this._m_sampleRate;
      }
    });

    /**
     * Frequency divisor, used to determine sample rate
     */

    return BlockSoundData;
  })();

  /**
   * @see {@link https://wiki.multimedia.cx/index.php?title=Creative_Voice#Block_type_0x08:_Extra_info|Source}
   */

  var BlockExtraInfo = CreativeVoiceFile.BlockExtraInfo = (function() {
    function BlockExtraInfo(_io, _parent, _root) {
      this._io = _io;
      this._parent = _parent;
      this._root = _root || this;

      this._read();
    }
    BlockExtraInfo.prototype._read = function() {
      this.freqDiv = this._io.readU2le();
      this.codec = this._io.readU1();
      this.numChannels1 = this._io.readU1();
    }

    /**
     * Number of channels (1 = mono, 2 = stereo)
     */
    Object.defineProperty(BlockExtraInfo.prototype, 'numChannels', {
      get: function() {
        if (this._m_numChannels !== undefined)
          return this._m_numChannels;
        this._m_numChannels = (this.numChannels1 + 1);
        return this._m_numChannels;
      }
    });
    Object.defineProperty(BlockExtraInfo.prototype, 'sampleRate', {
      get: function() {
        if (this._m_sampleRate !== undefined)
          return this._m_sampleRate;
        this._m_sampleRate = (256000000.0 / (this.numChannels * (65536 - this.freqDiv)));
        return this._m_sampleRate;
      }
    });

    /**
     * Frequency divisor
     */

    /**
     * Number of channels minus 1 (0 = mono, 1 = stereo)
     */

    return BlockExtraInfo;
  })();

  /**
   * Total size of this main header (usually 0x001A)
   */

  /**
   * Checksum: this must be equal to ~version + 0x1234
   */

  /**
   * Series of blocks that contain the actual audio data
   */

  return CreativeVoiceFile;
})();
return CreativeVoiceFile;
}));