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.
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.
All parsing code for C++98/STL generated by Kaitai Struct depends on the C++/STL runtime library. You have to install it before you can parse data.
For C++, the easiest way is to clone the runtime library sources and build them along with your project.
Using Kaitai Struct in C++/STL usually consists of 3 steps.
std::istream
). One can open local file for that, or use existing std::string
or char*
buffer.
#include <fstream>
std::ifstream is("path/to/local/file.voc", std::ifstream::binary);
#include <sstream>
std::istringstream is(str);
#include <sstream>
const char buf[] = { ... };
std::string str(buf, sizeof buf);
std::istringstream is(str);
#include "kaitai/kaitaistream.h"
kaitai::kstream ks(&is);
creative_voice_file_t data(&ks);
After that, one can get various attributes from the structure by invoking getter methods like:
data.header_size() // => Total size of this main header (usually 0x001A)
#ifndef CREATIVE_VOICE_FILE_H_
#define CREATIVE_VOICE_FILE_H_
// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild
#include "kaitai/kaitaistruct.h"
#include <stdint.h>
#include <vector>
#if KAITAI_STRUCT_VERSION < 9000L
#error "Incompatible Kaitai Struct C++/STL API: version 0.9 or later is required"
#endif
/**
* 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.
* \sa https://wiki.multimedia.cx/index.php?title=Creative_Voice Source
*/
class creative_voice_file_t : public kaitai::kstruct {
public:
class block_marker_t;
class block_silence_t;
class block_sound_data_new_t;
class block_t;
class block_repeat_start_t;
class block_sound_data_t;
class block_extra_info_t;
enum block_types_t {
BLOCK_TYPES_TERMINATOR = 0,
BLOCK_TYPES_SOUND_DATA = 1,
BLOCK_TYPES_SOUND_DATA_CONT = 2,
BLOCK_TYPES_SILENCE = 3,
BLOCK_TYPES_MARKER = 4,
BLOCK_TYPES_TEXT = 5,
BLOCK_TYPES_REPEAT_START = 6,
BLOCK_TYPES_REPEAT_END = 7,
BLOCK_TYPES_EXTRA_INFO = 8,
BLOCK_TYPES_SOUND_DATA_NEW = 9
};
enum codecs_t {
CODECS_PCM_8BIT_UNSIGNED = 0,
CODECS_ADPCM_4BIT = 1,
CODECS_ADPCM_2_6BIT = 2,
CODECS_ADPCM_2_BIT = 3,
CODECS_PCM_16BIT_SIGNED = 4,
CODECS_ALAW = 6,
CODECS_ULAW = 7,
CODECS_ADPCM_4_TO_16BIT = 512
};
creative_voice_file_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = 0, creative_voice_file_t* p__root = 0);
private:
void _read();
void _clean_up();
public:
~creative_voice_file_t();
/**
* \sa https://wiki.multimedia.cx/index.php?title=Creative_Voice#Block_type_0x04:_Marker Source
*/
class block_marker_t : public kaitai::kstruct {
public:
block_marker_t(kaitai::kstream* p__io, creative_voice_file_t::block_t* p__parent = 0, creative_voice_file_t* p__root = 0);
private:
void _read();
void _clean_up();
public:
~block_marker_t();
private:
uint16_t m_marker_id;
creative_voice_file_t* m__root;
creative_voice_file_t::block_t* m__parent;
public:
/**
* Marker ID
*/
uint16_t marker_id() const { return m_marker_id; }
creative_voice_file_t* _root() const { return m__root; }
creative_voice_file_t::block_t* _parent() const { return m__parent; }
};
/**
* \sa https://wiki.multimedia.cx/index.php?title=Creative_Voice#Block_type_0x03:_Silence Source
*/
class block_silence_t : public kaitai::kstruct {
public:
block_silence_t(kaitai::kstream* p__io, creative_voice_file_t::block_t* p__parent = 0, creative_voice_file_t* p__root = 0);
private:
void _read();
void _clean_up();
public:
~block_silence_t();
private:
bool f_sample_rate;
double m_sample_rate;
public:
double sample_rate();
private:
bool f_duration_sec;
double m_duration_sec;
public:
/**
* Duration of silence, in seconds
*/
double duration_sec();
private:
uint16_t m_duration_samples;
uint8_t m_freq_div;
creative_voice_file_t* m__root;
creative_voice_file_t::block_t* m__parent;
public:
/**
* Duration of silence, in samples
*/
uint16_t duration_samples() const { return m_duration_samples; }
/**
* Frequency divisor, used to determine sample rate
*/
uint8_t freq_div() const { return m_freq_div; }
creative_voice_file_t* _root() const { return m__root; }
creative_voice_file_t::block_t* _parent() const { return m__parent; }
};
/**
* \sa https://wiki.multimedia.cx/index.php?title=Creative_Voice#Block_type_0x09:_Sound_data_.28New_format.29 Source
*/
class block_sound_data_new_t : public kaitai::kstruct {
public:
block_sound_data_new_t(kaitai::kstream* p__io, creative_voice_file_t::block_t* p__parent = 0, creative_voice_file_t* p__root = 0);
private:
void _read();
void _clean_up();
public:
~block_sound_data_new_t();
private:
uint32_t m_sample_rate;
uint8_t m_bits_per_sample;
uint8_t m_num_channels;
codecs_t m_codec;
std::string m_reserved;
std::string m_wave;
creative_voice_file_t* m__root;
creative_voice_file_t::block_t* m__parent;
public:
uint32_t sample_rate() const { return m_sample_rate; }
uint8_t bits_per_sample() const { return m_bits_per_sample; }
uint8_t num_channels() const { return m_num_channels; }
codecs_t codec() const { return m_codec; }
std::string reserved() const { return m_reserved; }
std::string wave() const { return m_wave; }
creative_voice_file_t* _root() const { return m__root; }
creative_voice_file_t::block_t* _parent() const { return m__parent; }
};
class block_t : public kaitai::kstruct {
public:
block_t(kaitai::kstream* p__io, creative_voice_file_t* p__parent = 0, creative_voice_file_t* p__root = 0);
private:
void _read();
void _clean_up();
public:
~block_t();
private:
bool f_body_size;
int32_t m_body_size;
bool n_body_size;
public:
bool _is_null_body_size() { body_size(); return n_body_size; };
private:
public:
/**
* 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).
*/
int32_t body_size();
private:
block_types_t m_block_type;
uint16_t m_body_size1;
bool n_body_size1;
public:
bool _is_null_body_size1() { body_size1(); return n_body_size1; };
private:
uint8_t m_body_size2;
bool n_body_size2;
public:
bool _is_null_body_size2() { body_size2(); return n_body_size2; };
private:
kaitai::kstruct* m_body;
bool n_body;
public:
bool _is_null_body() { body(); return n_body; };
private:
creative_voice_file_t* m__root;
creative_voice_file_t* m__parent;
std::string m__raw_body;
bool n__raw_body;
public:
bool _is_null__raw_body() { _raw_body(); return n__raw_body; };
private:
kaitai::kstream* m__io__raw_body;
public:
/**
* Byte that determines type of block content
*/
block_types_t block_type() const { return m_block_type; }
uint16_t body_size1() const { return m_body_size1; }
uint8_t body_size2() const { return m_body_size2; }
/**
* Block body, type depends on block type byte
*/
kaitai::kstruct* body() const { return m_body; }
creative_voice_file_t* _root() const { return m__root; }
creative_voice_file_t* _parent() const { return m__parent; }
std::string _raw_body() const { return m__raw_body; }
kaitai::kstream* _io__raw_body() const { return m__io__raw_body; }
};
/**
* \sa https://wiki.multimedia.cx/index.php?title=Creative_Voice#Block_type_0x06:_Repeat_start Source
*/
class block_repeat_start_t : public kaitai::kstruct {
public:
block_repeat_start_t(kaitai::kstream* p__io, creative_voice_file_t::block_t* p__parent = 0, creative_voice_file_t* p__root = 0);
private:
void _read();
void _clean_up();
public:
~block_repeat_start_t();
private:
uint16_t m_repeat_count_1;
creative_voice_file_t* m__root;
creative_voice_file_t::block_t* m__parent;
public:
/**
* Number of repetitions minus 1; 0xffff means infinite repetitions
*/
uint16_t repeat_count_1() const { return m_repeat_count_1; }
creative_voice_file_t* _root() const { return m__root; }
creative_voice_file_t::block_t* _parent() const { return m__parent; }
};
/**
* \sa https://wiki.multimedia.cx/index.php?title=Creative_Voice#Block_type_0x01:_Sound_data Source
*/
class block_sound_data_t : public kaitai::kstruct {
public:
block_sound_data_t(kaitai::kstream* p__io, creative_voice_file_t::block_t* p__parent = 0, creative_voice_file_t* p__root = 0);
private:
void _read();
void _clean_up();
public:
~block_sound_data_t();
private:
bool f_sample_rate;
double m_sample_rate;
public:
double sample_rate();
private:
uint8_t m_freq_div;
codecs_t m_codec;
std::string m_wave;
creative_voice_file_t* m__root;
creative_voice_file_t::block_t* m__parent;
public:
/**
* Frequency divisor, used to determine sample rate
*/
uint8_t freq_div() const { return m_freq_div; }
codecs_t codec() const { return m_codec; }
std::string wave() const { return m_wave; }
creative_voice_file_t* _root() const { return m__root; }
creative_voice_file_t::block_t* _parent() const { return m__parent; }
};
/**
* \sa https://wiki.multimedia.cx/index.php?title=Creative_Voice#Block_type_0x08:_Extra_info Source
*/
class block_extra_info_t : public kaitai::kstruct {
public:
block_extra_info_t(kaitai::kstream* p__io, creative_voice_file_t::block_t* p__parent = 0, creative_voice_file_t* p__root = 0);
private:
void _read();
void _clean_up();
public:
~block_extra_info_t();
private:
bool f_num_channels;
int32_t m_num_channels;
public:
/**
* Number of channels (1 = mono, 2 = stereo)
*/
int32_t num_channels();
private:
bool f_sample_rate;
double m_sample_rate;
public:
double sample_rate();
private:
uint16_t m_freq_div;
codecs_t m_codec;
uint8_t m_num_channels_1;
creative_voice_file_t* m__root;
creative_voice_file_t::block_t* m__parent;
public:
/**
* Frequency divisor
*/
uint16_t freq_div() const { return m_freq_div; }
codecs_t codec() const { return m_codec; }
/**
* Number of channels minus 1 (0 = mono, 1 = stereo)
*/
uint8_t num_channels_1() const { return m_num_channels_1; }
creative_voice_file_t* _root() const { return m__root; }
creative_voice_file_t::block_t* _parent() const { return m__parent; }
};
private:
std::string m_magic;
uint16_t m_header_size;
uint16_t m_version;
uint16_t m_checksum;
std::vector<block_t*>* m_blocks;
creative_voice_file_t* m__root;
kaitai::kstruct* m__parent;
public:
std::string magic() const { return m_magic; }
/**
* Total size of this main header (usually 0x001A)
*/
uint16_t header_size() const { return m_header_size; }
uint16_t version() const { return m_version; }
/**
* Checksum: this must be equal to ~version + 0x1234
*/
uint16_t checksum() const { return m_checksum; }
/**
* Series of blocks that contain the actual audio data
*/
std::vector<block_t*>* blocks() const { return m_blocks; }
creative_voice_file_t* _root() const { return m__root; }
kaitai::kstruct* _parent() const { return m__parent; }
};
#endif // CREATIVE_VOICE_FILE_H_
// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild
#include "creative_voice_file.h"
#include "kaitai/exceptions.h"
creative_voice_file_t::creative_voice_file_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, creative_voice_file_t* p__root) : kaitai::kstruct(p__io) {
m__parent = p__parent;
m__root = this;
m_blocks = 0;
try {
_read();
} catch(...) {
_clean_up();
throw;
}
}
void creative_voice_file_t::_read() {
m_magic = m__io->read_bytes(20);
if (!(magic() == std::string("\x43\x72\x65\x61\x74\x69\x76\x65\x20\x56\x6F\x69\x63\x65\x20\x46\x69\x6C\x65\x1A", 20))) {
throw kaitai::validation_not_equal_error<std::string>(std::string("\x43\x72\x65\x61\x74\x69\x76\x65\x20\x56\x6F\x69\x63\x65\x20\x46\x69\x6C\x65\x1A", 20), magic(), _io(), std::string("/seq/0"));
}
m_header_size = m__io->read_u2le();
m_version = m__io->read_u2le();
m_checksum = m__io->read_u2le();
m_blocks = new std::vector<block_t*>();
{
int i = 0;
while (!m__io->is_eof()) {
m_blocks->push_back(new block_t(m__io, this, m__root));
i++;
}
}
}
creative_voice_file_t::~creative_voice_file_t() {
_clean_up();
}
void creative_voice_file_t::_clean_up() {
if (m_blocks) {
for (std::vector<block_t*>::iterator it = m_blocks->begin(); it != m_blocks->end(); ++it) {
delete *it;
}
delete m_blocks; m_blocks = 0;
}
}
creative_voice_file_t::block_marker_t::block_marker_t(kaitai::kstream* p__io, creative_voice_file_t::block_t* p__parent, creative_voice_file_t* p__root) : kaitai::kstruct(p__io) {
m__parent = p__parent;
m__root = p__root;
try {
_read();
} catch(...) {
_clean_up();
throw;
}
}
void creative_voice_file_t::block_marker_t::_read() {
m_marker_id = m__io->read_u2le();
}
creative_voice_file_t::block_marker_t::~block_marker_t() {
_clean_up();
}
void creative_voice_file_t::block_marker_t::_clean_up() {
}
creative_voice_file_t::block_silence_t::block_silence_t(kaitai::kstream* p__io, creative_voice_file_t::block_t* p__parent, creative_voice_file_t* p__root) : kaitai::kstruct(p__io) {
m__parent = p__parent;
m__root = p__root;
f_sample_rate = false;
f_duration_sec = false;
try {
_read();
} catch(...) {
_clean_up();
throw;
}
}
void creative_voice_file_t::block_silence_t::_read() {
m_duration_samples = m__io->read_u2le();
m_freq_div = m__io->read_u1();
}
creative_voice_file_t::block_silence_t::~block_silence_t() {
_clean_up();
}
void creative_voice_file_t::block_silence_t::_clean_up() {
}
double creative_voice_file_t::block_silence_t::sample_rate() {
if (f_sample_rate)
return m_sample_rate;
m_sample_rate = (1000000.0 / (256 - freq_div()));
f_sample_rate = true;
return m_sample_rate;
}
double creative_voice_file_t::block_silence_t::duration_sec() {
if (f_duration_sec)
return m_duration_sec;
m_duration_sec = (duration_samples() / sample_rate());
f_duration_sec = true;
return m_duration_sec;
}
creative_voice_file_t::block_sound_data_new_t::block_sound_data_new_t(kaitai::kstream* p__io, creative_voice_file_t::block_t* p__parent, creative_voice_file_t* p__root) : kaitai::kstruct(p__io) {
m__parent = p__parent;
m__root = p__root;
try {
_read();
} catch(...) {
_clean_up();
throw;
}
}
void creative_voice_file_t::block_sound_data_new_t::_read() {
m_sample_rate = m__io->read_u4le();
m_bits_per_sample = m__io->read_u1();
m_num_channels = m__io->read_u1();
m_codec = static_cast<creative_voice_file_t::codecs_t>(m__io->read_u2le());
m_reserved = m__io->read_bytes(4);
m_wave = m__io->read_bytes_full();
}
creative_voice_file_t::block_sound_data_new_t::~block_sound_data_new_t() {
_clean_up();
}
void creative_voice_file_t::block_sound_data_new_t::_clean_up() {
}
creative_voice_file_t::block_t::block_t(kaitai::kstream* p__io, creative_voice_file_t* p__parent, creative_voice_file_t* p__root) : kaitai::kstruct(p__io) {
m__parent = p__parent;
m__root = p__root;
m__io__raw_body = 0;
f_body_size = false;
try {
_read();
} catch(...) {
_clean_up();
throw;
}
}
void creative_voice_file_t::block_t::_read() {
m_block_type = static_cast<creative_voice_file_t::block_types_t>(m__io->read_u1());
n_body_size1 = true;
if (block_type() != creative_voice_file_t::BLOCK_TYPES_TERMINATOR) {
n_body_size1 = false;
m_body_size1 = m__io->read_u2le();
}
n_body_size2 = true;
if (block_type() != creative_voice_file_t::BLOCK_TYPES_TERMINATOR) {
n_body_size2 = false;
m_body_size2 = m__io->read_u1();
}
n_body = true;
if (block_type() != creative_voice_file_t::BLOCK_TYPES_TERMINATOR) {
n_body = false;
n_body = true;
switch (block_type()) {
case creative_voice_file_t::BLOCK_TYPES_SOUND_DATA_NEW: {
n_body = false;
m__raw_body = m__io->read_bytes(body_size());
m__io__raw_body = new kaitai::kstream(m__raw_body);
m_body = new block_sound_data_new_t(m__io__raw_body, this, m__root);
break;
}
case creative_voice_file_t::BLOCK_TYPES_REPEAT_START: {
n_body = false;
m__raw_body = m__io->read_bytes(body_size());
m__io__raw_body = new kaitai::kstream(m__raw_body);
m_body = new block_repeat_start_t(m__io__raw_body, this, m__root);
break;
}
case creative_voice_file_t::BLOCK_TYPES_MARKER: {
n_body = false;
m__raw_body = m__io->read_bytes(body_size());
m__io__raw_body = new kaitai::kstream(m__raw_body);
m_body = new block_marker_t(m__io__raw_body, this, m__root);
break;
}
case creative_voice_file_t::BLOCK_TYPES_SOUND_DATA: {
n_body = false;
m__raw_body = m__io->read_bytes(body_size());
m__io__raw_body = new kaitai::kstream(m__raw_body);
m_body = new block_sound_data_t(m__io__raw_body, this, m__root);
break;
}
case creative_voice_file_t::BLOCK_TYPES_EXTRA_INFO: {
n_body = false;
m__raw_body = m__io->read_bytes(body_size());
m__io__raw_body = new kaitai::kstream(m__raw_body);
m_body = new block_extra_info_t(m__io__raw_body, this, m__root);
break;
}
case creative_voice_file_t::BLOCK_TYPES_SILENCE: {
n_body = false;
m__raw_body = m__io->read_bytes(body_size());
m__io__raw_body = new kaitai::kstream(m__raw_body);
m_body = new block_silence_t(m__io__raw_body, this, m__root);
break;
}
default: {
m__raw_body = m__io->read_bytes(body_size());
break;
}
}
}
}
creative_voice_file_t::block_t::~block_t() {
_clean_up();
}
void creative_voice_file_t::block_t::_clean_up() {
if (!n_body_size1) {
}
if (!n_body_size2) {
}
if (!n_body) {
if (m__io__raw_body) {
delete m__io__raw_body; m__io__raw_body = 0;
}
if (m_body) {
delete m_body; m_body = 0;
}
}
}
int32_t creative_voice_file_t::block_t::body_size() {
if (f_body_size)
return m_body_size;
n_body_size = true;
if (block_type() != creative_voice_file_t::BLOCK_TYPES_TERMINATOR) {
n_body_size = false;
m_body_size = (body_size1() + (body_size2() << 16));
}
f_body_size = true;
return m_body_size;
}
creative_voice_file_t::block_repeat_start_t::block_repeat_start_t(kaitai::kstream* p__io, creative_voice_file_t::block_t* p__parent, creative_voice_file_t* p__root) : kaitai::kstruct(p__io) {
m__parent = p__parent;
m__root = p__root;
try {
_read();
} catch(...) {
_clean_up();
throw;
}
}
void creative_voice_file_t::block_repeat_start_t::_read() {
m_repeat_count_1 = m__io->read_u2le();
}
creative_voice_file_t::block_repeat_start_t::~block_repeat_start_t() {
_clean_up();
}
void creative_voice_file_t::block_repeat_start_t::_clean_up() {
}
creative_voice_file_t::block_sound_data_t::block_sound_data_t(kaitai::kstream* p__io, creative_voice_file_t::block_t* p__parent, creative_voice_file_t* p__root) : kaitai::kstruct(p__io) {
m__parent = p__parent;
m__root = p__root;
f_sample_rate = false;
try {
_read();
} catch(...) {
_clean_up();
throw;
}
}
void creative_voice_file_t::block_sound_data_t::_read() {
m_freq_div = m__io->read_u1();
m_codec = static_cast<creative_voice_file_t::codecs_t>(m__io->read_u1());
m_wave = m__io->read_bytes_full();
}
creative_voice_file_t::block_sound_data_t::~block_sound_data_t() {
_clean_up();
}
void creative_voice_file_t::block_sound_data_t::_clean_up() {
}
double creative_voice_file_t::block_sound_data_t::sample_rate() {
if (f_sample_rate)
return m_sample_rate;
m_sample_rate = (1000000.0 / (256 - freq_div()));
f_sample_rate = true;
return m_sample_rate;
}
creative_voice_file_t::block_extra_info_t::block_extra_info_t(kaitai::kstream* p__io, creative_voice_file_t::block_t* p__parent, creative_voice_file_t* p__root) : kaitai::kstruct(p__io) {
m__parent = p__parent;
m__root = p__root;
f_num_channels = false;
f_sample_rate = false;
try {
_read();
} catch(...) {
_clean_up();
throw;
}
}
void creative_voice_file_t::block_extra_info_t::_read() {
m_freq_div = m__io->read_u2le();
m_codec = static_cast<creative_voice_file_t::codecs_t>(m__io->read_u1());
m_num_channels_1 = m__io->read_u1();
}
creative_voice_file_t::block_extra_info_t::~block_extra_info_t() {
_clean_up();
}
void creative_voice_file_t::block_extra_info_t::_clean_up() {
}
int32_t creative_voice_file_t::block_extra_info_t::num_channels() {
if (f_num_channels)
return m_num_channels;
m_num_channels = (num_channels_1() + 1);
f_num_channels = true;
return m_num_channels;
}
double creative_voice_file_t::block_extra_info_t::sample_rate() {
if (f_sample_rate)
return m_sample_rate;
m_sample_rate = (256000000.0 / (num_channels() * (65536 - freq_div())));
f_sample_rate = true;
return m_sample_rate;
}