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.
# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild
use strict;
use warnings;
use IO::KaitaiStruct 0.009_000;
########################################################################
package CreativeVoiceFile;
our @ISA = 'IO::KaitaiStruct::Struct';
sub from_file {
my ($class, $filename) = @_;
my $fd;
open($fd, '<', $filename) or return undef;
binmode($fd);
return new($class, IO::KaitaiStruct::Stream->new($fd));
}
our $BLOCK_TYPES_TERMINATOR = 0;
our $BLOCK_TYPES_SOUND_DATA = 1;
our $BLOCK_TYPES_SOUND_DATA_CONT = 2;
our $BLOCK_TYPES_SILENCE = 3;
our $BLOCK_TYPES_MARKER = 4;
our $BLOCK_TYPES_TEXT = 5;
our $BLOCK_TYPES_REPEAT_START = 6;
our $BLOCK_TYPES_REPEAT_END = 7;
our $BLOCK_TYPES_EXTRA_INFO = 8;
our $BLOCK_TYPES_SOUND_DATA_NEW = 9;
our $CODECS_PCM_8BIT_UNSIGNED = 0;
our $CODECS_ADPCM_4BIT = 1;
our $CODECS_ADPCM_2_6BIT = 2;
our $CODECS_ADPCM_2_BIT = 3;
our $CODECS_PCM_16BIT_SIGNED = 4;
our $CODECS_ALAW = 6;
our $CODECS_ULAW = 7;
our $CODECS_ADPCM_4_TO_16BIT = 512;
sub new {
my ($class, $_io, $_parent, $_root) = @_;
my $self = IO::KaitaiStruct::Struct->new($_io);
bless $self, $class;
$self->{_parent} = $_parent;
$self->{_root} = $_root || $self;;
$self->_read();
return $self;
}
sub _read {
my ($self) = @_;
$self->{magic} = $self->{_io}->read_bytes(20);
$self->{header_size} = $self->{_io}->read_u2le();
$self->{version} = $self->{_io}->read_u2le();
$self->{checksum} = $self->{_io}->read_u2le();
$self->{blocks} = ();
while (!$self->{_io}->is_eof()) {
push @{$self->{blocks}}, CreativeVoiceFile::Block->new($self->{_io}, $self, $self->{_root});
}
}
sub magic {
my ($self) = @_;
return $self->{magic};
}
sub header_size {
my ($self) = @_;
return $self->{header_size};
}
sub version {
my ($self) = @_;
return $self->{version};
}
sub checksum {
my ($self) = @_;
return $self->{checksum};
}
sub blocks {
my ($self) = @_;
return $self->{blocks};
}
########################################################################
package CreativeVoiceFile::BlockMarker;
our @ISA = 'IO::KaitaiStruct::Struct';
sub from_file {
my ($class, $filename) = @_;
my $fd;
open($fd, '<', $filename) or return undef;
binmode($fd);
return new($class, IO::KaitaiStruct::Stream->new($fd));
}
sub new {
my ($class, $_io, $_parent, $_root) = @_;
my $self = IO::KaitaiStruct::Struct->new($_io);
bless $self, $class;
$self->{_parent} = $_parent;
$self->{_root} = $_root || $self;;
$self->_read();
return $self;
}
sub _read {
my ($self) = @_;
$self->{marker_id} = $self->{_io}->read_u2le();
}
sub marker_id {
my ($self) = @_;
return $self->{marker_id};
}
########################################################################
package CreativeVoiceFile::BlockSilence;
our @ISA = 'IO::KaitaiStruct::Struct';
sub from_file {
my ($class, $filename) = @_;
my $fd;
open($fd, '<', $filename) or return undef;
binmode($fd);
return new($class, IO::KaitaiStruct::Stream->new($fd));
}
sub new {
my ($class, $_io, $_parent, $_root) = @_;
my $self = IO::KaitaiStruct::Struct->new($_io);
bless $self, $class;
$self->{_parent} = $_parent;
$self->{_root} = $_root || $self;;
$self->_read();
return $self;
}
sub _read {
my ($self) = @_;
$self->{duration_samples} = $self->{_io}->read_u2le();
$self->{freq_div} = $self->{_io}->read_u1();
}
sub sample_rate {
my ($self) = @_;
return $self->{sample_rate} if ($self->{sample_rate});
$self->{sample_rate} = (1000000.0 / (256 - $self->freq_div()));
return $self->{sample_rate};
}
sub duration_sec {
my ($self) = @_;
return $self->{duration_sec} if ($self->{duration_sec});
$self->{duration_sec} = ($self->duration_samples() / $self->sample_rate());
return $self->{duration_sec};
}
sub duration_samples {
my ($self) = @_;
return $self->{duration_samples};
}
sub freq_div {
my ($self) = @_;
return $self->{freq_div};
}
########################################################################
package CreativeVoiceFile::BlockSoundDataNew;
our @ISA = 'IO::KaitaiStruct::Struct';
sub from_file {
my ($class, $filename) = @_;
my $fd;
open($fd, '<', $filename) or return undef;
binmode($fd);
return new($class, IO::KaitaiStruct::Stream->new($fd));
}
sub new {
my ($class, $_io, $_parent, $_root) = @_;
my $self = IO::KaitaiStruct::Struct->new($_io);
bless $self, $class;
$self->{_parent} = $_parent;
$self->{_root} = $_root || $self;;
$self->_read();
return $self;
}
sub _read {
my ($self) = @_;
$self->{sample_rate} = $self->{_io}->read_u4le();
$self->{bits_per_sample} = $self->{_io}->read_u1();
$self->{num_channels} = $self->{_io}->read_u1();
$self->{codec} = $self->{_io}->read_u2le();
$self->{reserved} = $self->{_io}->read_bytes(4);
$self->{wave} = $self->{_io}->read_bytes_full();
}
sub sample_rate {
my ($self) = @_;
return $self->{sample_rate};
}
sub bits_per_sample {
my ($self) = @_;
return $self->{bits_per_sample};
}
sub num_channels {
my ($self) = @_;
return $self->{num_channels};
}
sub codec {
my ($self) = @_;
return $self->{codec};
}
sub reserved {
my ($self) = @_;
return $self->{reserved};
}
sub wave {
my ($self) = @_;
return $self->{wave};
}
########################################################################
package CreativeVoiceFile::Block;
our @ISA = 'IO::KaitaiStruct::Struct';
sub from_file {
my ($class, $filename) = @_;
my $fd;
open($fd, '<', $filename) or return undef;
binmode($fd);
return new($class, IO::KaitaiStruct::Stream->new($fd));
}
sub new {
my ($class, $_io, $_parent, $_root) = @_;
my $self = IO::KaitaiStruct::Struct->new($_io);
bless $self, $class;
$self->{_parent} = $_parent;
$self->{_root} = $_root || $self;;
$self->_read();
return $self;
}
sub _read {
my ($self) = @_;
$self->{block_type} = $self->{_io}->read_u1();
if ($self->block_type() != $CreativeVoiceFile::BLOCK_TYPES_TERMINATOR) {
$self->{body_size1} = $self->{_io}->read_u2le();
}
if ($self->block_type() != $CreativeVoiceFile::BLOCK_TYPES_TERMINATOR) {
$self->{body_size2} = $self->{_io}->read_u1();
}
if ($self->block_type() != $CreativeVoiceFile::BLOCK_TYPES_TERMINATOR) {
my $_on = $self->block_type();
if ($_on == $CreativeVoiceFile::BLOCK_TYPES_SOUND_DATA_NEW) {
$self->{_raw_body} = $self->{_io}->read_bytes($self->body_size());
my $io__raw_body = IO::KaitaiStruct::Stream->new($self->{_raw_body});
$self->{body} = CreativeVoiceFile::BlockSoundDataNew->new($io__raw_body, $self, $self->{_root});
}
elsif ($_on == $CreativeVoiceFile::BLOCK_TYPES_REPEAT_START) {
$self->{_raw_body} = $self->{_io}->read_bytes($self->body_size());
my $io__raw_body = IO::KaitaiStruct::Stream->new($self->{_raw_body});
$self->{body} = CreativeVoiceFile::BlockRepeatStart->new($io__raw_body, $self, $self->{_root});
}
elsif ($_on == $CreativeVoiceFile::BLOCK_TYPES_MARKER) {
$self->{_raw_body} = $self->{_io}->read_bytes($self->body_size());
my $io__raw_body = IO::KaitaiStruct::Stream->new($self->{_raw_body});
$self->{body} = CreativeVoiceFile::BlockMarker->new($io__raw_body, $self, $self->{_root});
}
elsif ($_on == $CreativeVoiceFile::BLOCK_TYPES_SOUND_DATA) {
$self->{_raw_body} = $self->{_io}->read_bytes($self->body_size());
my $io__raw_body = IO::KaitaiStruct::Stream->new($self->{_raw_body});
$self->{body} = CreativeVoiceFile::BlockSoundData->new($io__raw_body, $self, $self->{_root});
}
elsif ($_on == $CreativeVoiceFile::BLOCK_TYPES_EXTRA_INFO) {
$self->{_raw_body} = $self->{_io}->read_bytes($self->body_size());
my $io__raw_body = IO::KaitaiStruct::Stream->new($self->{_raw_body});
$self->{body} = CreativeVoiceFile::BlockExtraInfo->new($io__raw_body, $self, $self->{_root});
}
elsif ($_on == $CreativeVoiceFile::BLOCK_TYPES_SILENCE) {
$self->{_raw_body} = $self->{_io}->read_bytes($self->body_size());
my $io__raw_body = IO::KaitaiStruct::Stream->new($self->{_raw_body});
$self->{body} = CreativeVoiceFile::BlockSilence->new($io__raw_body, $self, $self->{_root});
}
else {
$self->{body} = $self->{_io}->read_bytes($self->body_size());
}
}
}
sub body_size {
my ($self) = @_;
return $self->{body_size} if ($self->{body_size});
if ($self->block_type() != $CreativeVoiceFile::BLOCK_TYPES_TERMINATOR) {
$self->{body_size} = ($self->body_size1() + ($self->body_size2() << 16));
}
return $self->{body_size};
}
sub block_type {
my ($self) = @_;
return $self->{block_type};
}
sub body_size1 {
my ($self) = @_;
return $self->{body_size1};
}
sub body_size2 {
my ($self) = @_;
return $self->{body_size2};
}
sub body {
my ($self) = @_;
return $self->{body};
}
sub _raw_body {
my ($self) = @_;
return $self->{_raw_body};
}
########################################################################
package CreativeVoiceFile::BlockRepeatStart;
our @ISA = 'IO::KaitaiStruct::Struct';
sub from_file {
my ($class, $filename) = @_;
my $fd;
open($fd, '<', $filename) or return undef;
binmode($fd);
return new($class, IO::KaitaiStruct::Stream->new($fd));
}
sub new {
my ($class, $_io, $_parent, $_root) = @_;
my $self = IO::KaitaiStruct::Struct->new($_io);
bless $self, $class;
$self->{_parent} = $_parent;
$self->{_root} = $_root || $self;;
$self->_read();
return $self;
}
sub _read {
my ($self) = @_;
$self->{repeat_count_1} = $self->{_io}->read_u2le();
}
sub repeat_count_1 {
my ($self) = @_;
return $self->{repeat_count_1};
}
########################################################################
package CreativeVoiceFile::BlockSoundData;
our @ISA = 'IO::KaitaiStruct::Struct';
sub from_file {
my ($class, $filename) = @_;
my $fd;
open($fd, '<', $filename) or return undef;
binmode($fd);
return new($class, IO::KaitaiStruct::Stream->new($fd));
}
sub new {
my ($class, $_io, $_parent, $_root) = @_;
my $self = IO::KaitaiStruct::Struct->new($_io);
bless $self, $class;
$self->{_parent} = $_parent;
$self->{_root} = $_root || $self;;
$self->_read();
return $self;
}
sub _read {
my ($self) = @_;
$self->{freq_div} = $self->{_io}->read_u1();
$self->{codec} = $self->{_io}->read_u1();
$self->{wave} = $self->{_io}->read_bytes_full();
}
sub sample_rate {
my ($self) = @_;
return $self->{sample_rate} if ($self->{sample_rate});
$self->{sample_rate} = (1000000.0 / (256 - $self->freq_div()));
return $self->{sample_rate};
}
sub freq_div {
my ($self) = @_;
return $self->{freq_div};
}
sub codec {
my ($self) = @_;
return $self->{codec};
}
sub wave {
my ($self) = @_;
return $self->{wave};
}
########################################################################
package CreativeVoiceFile::BlockExtraInfo;
our @ISA = 'IO::KaitaiStruct::Struct';
sub from_file {
my ($class, $filename) = @_;
my $fd;
open($fd, '<', $filename) or return undef;
binmode($fd);
return new($class, IO::KaitaiStruct::Stream->new($fd));
}
sub new {
my ($class, $_io, $_parent, $_root) = @_;
my $self = IO::KaitaiStruct::Struct->new($_io);
bless $self, $class;
$self->{_parent} = $_parent;
$self->{_root} = $_root || $self;;
$self->_read();
return $self;
}
sub _read {
my ($self) = @_;
$self->{freq_div} = $self->{_io}->read_u2le();
$self->{codec} = $self->{_io}->read_u1();
$self->{num_channels_1} = $self->{_io}->read_u1();
}
sub num_channels {
my ($self) = @_;
return $self->{num_channels} if ($self->{num_channels});
$self->{num_channels} = ($self->num_channels_1() + 1);
return $self->{num_channels};
}
sub sample_rate {
my ($self) = @_;
return $self->{sample_rate} if ($self->{sample_rate});
$self->{sample_rate} = (256000000.0 / ($self->num_channels() * (65536 - $self->freq_div())));
return $self->{sample_rate};
}
sub freq_div {
my ($self) = @_;
return $self->{freq_div};
}
sub codec {
my ($self) = @_;
return $self->{codec};
}
sub num_channels_1 {
my ($self) = @_;
return $self->{num_channels_1};
}
1;