Creative Voice File: PHP 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.

PHP source code to parse Creative Voice File

CreativeVoiceFile.php

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

/**
 * 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.
 */

namespace {
    class CreativeVoiceFile extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \Kaitai\Struct\Struct $_parent = null, \CreativeVoiceFile $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_magic = $this->_io->readBytes(20);
            if (!($this->magic() == "\x43\x72\x65\x61\x74\x69\x76\x65\x20\x56\x6F\x69\x63\x65\x20\x46\x69\x6C\x65\x1A")) {
                throw new \Kaitai\Struct\Error\ValidationNotEqualError("\x43\x72\x65\x61\x74\x69\x76\x65\x20\x56\x6F\x69\x63\x65\x20\x46\x69\x6C\x65\x1A", $this->magic(), $this->_io(), "/seq/0");
            }
            $this->_m_headerSize = $this->_io->readU2le();
            $this->_m_version = $this->_io->readU2le();
            $this->_m_checksum = $this->_io->readU2le();
            $this->_m_blocks = [];
            $i = 0;
            while (!$this->_io->isEof()) {
                $this->_m_blocks[] = new \CreativeVoiceFile\Block($this->_io, $this, $this->_root);
                $i++;
            }
        }
        protected $_m_magic;
        protected $_m_headerSize;
        protected $_m_version;
        protected $_m_checksum;
        protected $_m_blocks;
        public function magic() { return $this->_m_magic; }

        /**
         * Total size of this main header (usually 0x001A)
         */
        public function headerSize() { return $this->_m_headerSize; }
        public function version() { return $this->_m_version; }

        /**
         * Checksum: this must be equal to ~version + 0x1234
         */
        public function checksum() { return $this->_m_checksum; }

        /**
         * Series of blocks that contain the actual audio data
         */
        public function blocks() { return $this->_m_blocks; }
    }
}

namespace CreativeVoiceFile {
    class BlockMarker extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \CreativeVoiceFile\Block $_parent = null, \CreativeVoiceFile $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_markerId = $this->_io->readU2le();
        }
        protected $_m_markerId;

        /**
         * Marker ID
         */
        public function markerId() { return $this->_m_markerId; }
    }
}

namespace CreativeVoiceFile {
    class BlockSilence extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \CreativeVoiceFile\Block $_parent = null, \CreativeVoiceFile $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_durationSamples = $this->_io->readU2le();
            $this->_m_freqDiv = $this->_io->readU1();
        }
        protected $_m_sampleRate;
        public function sampleRate() {
            if ($this->_m_sampleRate !== null)
                return $this->_m_sampleRate;
            $this->_m_sampleRate = (1000000.0 / (256 - $this->freqDiv()));
            return $this->_m_sampleRate;
        }
        protected $_m_durationSec;

        /**
         * Duration of silence, in seconds
         */
        public function durationSec() {
            if ($this->_m_durationSec !== null)
                return $this->_m_durationSec;
            $this->_m_durationSec = ($this->durationSamples() / $this->sampleRate());
            return $this->_m_durationSec;
        }
        protected $_m_durationSamples;
        protected $_m_freqDiv;

        /**
         * Duration of silence, in samples
         */
        public function durationSamples() { return $this->_m_durationSamples; }

        /**
         * Frequency divisor, used to determine sample rate
         */
        public function freqDiv() { return $this->_m_freqDiv; }
    }
}

namespace CreativeVoiceFile {
    class BlockSoundDataNew extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \CreativeVoiceFile\Block $_parent = null, \CreativeVoiceFile $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_sampleRate = $this->_io->readU4le();
            $this->_m_bitsPerSample = $this->_io->readU1();
            $this->_m_numChannels = $this->_io->readU1();
            $this->_m_codec = $this->_io->readU2le();
            $this->_m_reserved = $this->_io->readBytes(4);
            $this->_m_wave = $this->_io->readBytesFull();
        }
        protected $_m_sampleRate;
        protected $_m_bitsPerSample;
        protected $_m_numChannels;
        protected $_m_codec;
        protected $_m_reserved;
        protected $_m_wave;
        public function sampleRate() { return $this->_m_sampleRate; }
        public function bitsPerSample() { return $this->_m_bitsPerSample; }
        public function numChannels() { return $this->_m_numChannels; }
        public function codec() { return $this->_m_codec; }
        public function reserved() { return $this->_m_reserved; }
        public function wave() { return $this->_m_wave; }
    }
}

namespace CreativeVoiceFile {
    class Block extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \CreativeVoiceFile $_parent = null, \CreativeVoiceFile $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_blockType = $this->_io->readU1();
            if ($this->blockType() != \CreativeVoiceFile\BlockTypes::TERMINATOR) {
                $this->_m_bodySize1 = $this->_io->readU2le();
            }
            if ($this->blockType() != \CreativeVoiceFile\BlockTypes::TERMINATOR) {
                $this->_m_bodySize2 = $this->_io->readU1();
            }
            if ($this->blockType() != \CreativeVoiceFile\BlockTypes::TERMINATOR) {
                switch ($this->blockType()) {
                    case \CreativeVoiceFile\BlockTypes::SOUND_DATA_NEW:
                        $this->_m__raw_body = $this->_io->readBytes($this->bodySize());
                        $_io__raw_body = new \Kaitai\Struct\Stream($this->_m__raw_body);
                        $this->_m_body = new \CreativeVoiceFile\BlockSoundDataNew($_io__raw_body, $this, $this->_root);
                        break;
                    case \CreativeVoiceFile\BlockTypes::REPEAT_START:
                        $this->_m__raw_body = $this->_io->readBytes($this->bodySize());
                        $_io__raw_body = new \Kaitai\Struct\Stream($this->_m__raw_body);
                        $this->_m_body = new \CreativeVoiceFile\BlockRepeatStart($_io__raw_body, $this, $this->_root);
                        break;
                    case \CreativeVoiceFile\BlockTypes::MARKER:
                        $this->_m__raw_body = $this->_io->readBytes($this->bodySize());
                        $_io__raw_body = new \Kaitai\Struct\Stream($this->_m__raw_body);
                        $this->_m_body = new \CreativeVoiceFile\BlockMarker($_io__raw_body, $this, $this->_root);
                        break;
                    case \CreativeVoiceFile\BlockTypes::SOUND_DATA:
                        $this->_m__raw_body = $this->_io->readBytes($this->bodySize());
                        $_io__raw_body = new \Kaitai\Struct\Stream($this->_m__raw_body);
                        $this->_m_body = new \CreativeVoiceFile\BlockSoundData($_io__raw_body, $this, $this->_root);
                        break;
                    case \CreativeVoiceFile\BlockTypes::EXTRA_INFO:
                        $this->_m__raw_body = $this->_io->readBytes($this->bodySize());
                        $_io__raw_body = new \Kaitai\Struct\Stream($this->_m__raw_body);
                        $this->_m_body = new \CreativeVoiceFile\BlockExtraInfo($_io__raw_body, $this, $this->_root);
                        break;
                    case \CreativeVoiceFile\BlockTypes::SILENCE:
                        $this->_m__raw_body = $this->_io->readBytes($this->bodySize());
                        $_io__raw_body = new \Kaitai\Struct\Stream($this->_m__raw_body);
                        $this->_m_body = new \CreativeVoiceFile\BlockSilence($_io__raw_body, $this, $this->_root);
                        break;
                    default:
                        $this->_m_body = $this->_io->readBytes($this->bodySize());
                        break;
                }
            }
        }
        protected $_m_bodySize;

        /**
         * 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).
         */
        public function bodySize() {
            if ($this->_m_bodySize !== null)
                return $this->_m_bodySize;
            if ($this->blockType() != \CreativeVoiceFile\BlockTypes::TERMINATOR) {
                $this->_m_bodySize = ($this->bodySize1() + ($this->bodySize2() << 16));
            }
            return $this->_m_bodySize;
        }
        protected $_m_blockType;
        protected $_m_bodySize1;
        protected $_m_bodySize2;
        protected $_m_body;
        protected $_m__raw_body;

        /**
         * Byte that determines type of block content
         */
        public function blockType() { return $this->_m_blockType; }
        public function bodySize1() { return $this->_m_bodySize1; }
        public function bodySize2() { return $this->_m_bodySize2; }

        /**
         * Block body, type depends on block type byte
         */
        public function body() { return $this->_m_body; }
        public function _raw_body() { return $this->_m__raw_body; }
    }
}

namespace CreativeVoiceFile {
    class BlockRepeatStart extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \CreativeVoiceFile\Block $_parent = null, \CreativeVoiceFile $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_repeatCount1 = $this->_io->readU2le();
        }
        protected $_m_repeatCount1;

        /**
         * Number of repetitions minus 1; 0xffff means infinite repetitions
         */
        public function repeatCount1() { return $this->_m_repeatCount1; }
    }
}

namespace CreativeVoiceFile {
    class BlockSoundData extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \CreativeVoiceFile\Block $_parent = null, \CreativeVoiceFile $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_freqDiv = $this->_io->readU1();
            $this->_m_codec = $this->_io->readU1();
            $this->_m_wave = $this->_io->readBytesFull();
        }
        protected $_m_sampleRate;
        public function sampleRate() {
            if ($this->_m_sampleRate !== null)
                return $this->_m_sampleRate;
            $this->_m_sampleRate = (1000000.0 / (256 - $this->freqDiv()));
            return $this->_m_sampleRate;
        }
        protected $_m_freqDiv;
        protected $_m_codec;
        protected $_m_wave;

        /**
         * Frequency divisor, used to determine sample rate
         */
        public function freqDiv() { return $this->_m_freqDiv; }
        public function codec() { return $this->_m_codec; }
        public function wave() { return $this->_m_wave; }
    }
}

namespace CreativeVoiceFile {
    class BlockExtraInfo extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \CreativeVoiceFile\Block $_parent = null, \CreativeVoiceFile $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_freqDiv = $this->_io->readU2le();
            $this->_m_codec = $this->_io->readU1();
            $this->_m_numChannels1 = $this->_io->readU1();
        }
        protected $_m_numChannels;

        /**
         * Number of channels (1 = mono, 2 = stereo)
         */
        public function numChannels() {
            if ($this->_m_numChannels !== null)
                return $this->_m_numChannels;
            $this->_m_numChannels = ($this->numChannels1() + 1);
            return $this->_m_numChannels;
        }
        protected $_m_sampleRate;
        public function sampleRate() {
            if ($this->_m_sampleRate !== null)
                return $this->_m_sampleRate;
            $this->_m_sampleRate = (256000000.0 / ($this->numChannels() * (65536 - $this->freqDiv())));
            return $this->_m_sampleRate;
        }
        protected $_m_freqDiv;
        protected $_m_codec;
        protected $_m_numChannels1;

        /**
         * Frequency divisor
         */
        public function freqDiv() { return $this->_m_freqDiv; }
        public function codec() { return $this->_m_codec; }

        /**
         * Number of channels minus 1 (0 = mono, 1 = stereo)
         */
        public function numChannels1() { return $this->_m_numChannels1; }
    }
}

namespace CreativeVoiceFile {
    class BlockTypes {
        const TERMINATOR = 0;
        const SOUND_DATA = 1;
        const SOUND_DATA_CONT = 2;
        const SILENCE = 3;
        const MARKER = 4;
        const TEXT = 5;
        const REPEAT_START = 6;
        const REPEAT_END = 7;
        const EXTRA_INFO = 8;
        const SOUND_DATA_NEW = 9;
    }
}

namespace CreativeVoiceFile {
    class Codecs {
        const PCM_8BIT_UNSIGNED = 0;
        const ADPCM_4BIT = 1;
        const ADPCM_2_6BIT = 2;
        const ADPCM_2_BIT = 3;
        const PCM_16BIT_SIGNED = 4;
        const ALAW = 6;
        const ULAW = 7;
        const ADPCM_4_TO_16BIT = 512;
    }
}