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.
import kaitai_struct_nim_runtime
import options
type
CreativeVoiceFile* = ref object of KaitaiStruct
`magic`*: seq[byte]
`headerSize`*: uint16
`version`*: uint16
`checksum`*: uint16
`blocks`*: seq[CreativeVoiceFile_Block]
`parent`*: KaitaiStruct
CreativeVoiceFile_BlockTypes* = enum
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
CreativeVoiceFile_Codecs* = enum
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
CreativeVoiceFile_BlockMarker* = ref object of KaitaiStruct
`markerId`*: uint16
`parent`*: CreativeVoiceFile_Block
CreativeVoiceFile_BlockSilence* = ref object of KaitaiStruct
`durationSamples`*: uint16
`freqDiv`*: uint8
`parent`*: CreativeVoiceFile_Block
`sampleRateInst`: float64
`sampleRateInstFlag`: bool
`durationSecInst`: float64
`durationSecInstFlag`: bool
CreativeVoiceFile_BlockSoundDataNew* = ref object of KaitaiStruct
`sampleRate`*: uint32
`bitsPerSample`*: uint8
`numChannels`*: uint8
`codec`*: CreativeVoiceFile_Codecs
`reserved`*: seq[byte]
`wave`*: seq[byte]
`parent`*: CreativeVoiceFile_Block
CreativeVoiceFile_Block* = ref object of KaitaiStruct
`blockType`*: CreativeVoiceFile_BlockTypes
`bodySize1`*: uint16
`bodySize2`*: uint8
`body`*: KaitaiStruct
`parent`*: CreativeVoiceFile
`rawBody`*: seq[byte]
`bodySizeInst`: int
`bodySizeInstFlag`: bool
CreativeVoiceFile_BlockRepeatStart* = ref object of KaitaiStruct
`repeatCount1`*: uint16
`parent`*: CreativeVoiceFile_Block
CreativeVoiceFile_BlockSoundData* = ref object of KaitaiStruct
`freqDiv`*: uint8
`codec`*: CreativeVoiceFile_Codecs
`wave`*: seq[byte]
`parent`*: CreativeVoiceFile_Block
`sampleRateInst`: float64
`sampleRateInstFlag`: bool
CreativeVoiceFile_BlockExtraInfo* = ref object of KaitaiStruct
`freqDiv`*: uint16
`codec`*: CreativeVoiceFile_Codecs
`numChannels1`*: uint8
`parent`*: CreativeVoiceFile_Block
`numChannelsInst`: int
`numChannelsInstFlag`: bool
`sampleRateInst`: float64
`sampleRateInstFlag`: bool
proc read*(_: typedesc[CreativeVoiceFile], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): CreativeVoiceFile
proc read*(_: typedesc[CreativeVoiceFile_BlockMarker], io: KaitaiStream, root: KaitaiStruct, parent: CreativeVoiceFile_Block): CreativeVoiceFile_BlockMarker
proc read*(_: typedesc[CreativeVoiceFile_BlockSilence], io: KaitaiStream, root: KaitaiStruct, parent: CreativeVoiceFile_Block): CreativeVoiceFile_BlockSilence
proc read*(_: typedesc[CreativeVoiceFile_BlockSoundDataNew], io: KaitaiStream, root: KaitaiStruct, parent: CreativeVoiceFile_Block): CreativeVoiceFile_BlockSoundDataNew
proc read*(_: typedesc[CreativeVoiceFile_Block], io: KaitaiStream, root: KaitaiStruct, parent: CreativeVoiceFile): CreativeVoiceFile_Block
proc read*(_: typedesc[CreativeVoiceFile_BlockRepeatStart], io: KaitaiStream, root: KaitaiStruct, parent: CreativeVoiceFile_Block): CreativeVoiceFile_BlockRepeatStart
proc read*(_: typedesc[CreativeVoiceFile_BlockSoundData], io: KaitaiStream, root: KaitaiStruct, parent: CreativeVoiceFile_Block): CreativeVoiceFile_BlockSoundData
proc read*(_: typedesc[CreativeVoiceFile_BlockExtraInfo], io: KaitaiStream, root: KaitaiStruct, parent: CreativeVoiceFile_Block): CreativeVoiceFile_BlockExtraInfo
proc sampleRate*(this: CreativeVoiceFile_BlockSilence): float64
proc durationSec*(this: CreativeVoiceFile_BlockSilence): float64
proc bodySize*(this: CreativeVoiceFile_Block): int
proc sampleRate*(this: CreativeVoiceFile_BlockSoundData): float64
proc numChannels*(this: CreativeVoiceFile_BlockExtraInfo): int
proc sampleRate*(this: CreativeVoiceFile_BlockExtraInfo): float64
##[
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 <a href="https://wiki.multimedia.cx/index.php?title=Creative_Voice">Source</a>
]##
proc read*(_: typedesc[CreativeVoiceFile], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): CreativeVoiceFile =
template this: untyped = result
this = new(CreativeVoiceFile)
let root = if root == nil: cast[CreativeVoiceFile](this) else: cast[CreativeVoiceFile](root)
this.io = io
this.root = root
this.parent = parent
let magicExpr = this.io.readBytes(int(20))
this.magic = magicExpr
##[
Total size of this main header (usually 0x001A)
]##
let headerSizeExpr = this.io.readU2le()
this.headerSize = headerSizeExpr
let versionExpr = this.io.readU2le()
this.version = versionExpr
##[
Checksum: this must be equal to ~version + 0x1234
]##
let checksumExpr = this.io.readU2le()
this.checksum = checksumExpr
##[
Series of blocks that contain the actual audio data
]##
block:
var i: int
while not this.io.isEof:
let it = CreativeVoiceFile_Block.read(this.io, this.root, this)
this.blocks.add(it)
inc i
proc fromFile*(_: typedesc[CreativeVoiceFile], filename: string): CreativeVoiceFile =
CreativeVoiceFile.read(newKaitaiFileStream(filename), nil, nil)
##[
@see <a href="https://wiki.multimedia.cx/index.php?title=Creative_Voice#Block_type_0x04:_Marker">Source</a>
]##
proc read*(_: typedesc[CreativeVoiceFile_BlockMarker], io: KaitaiStream, root: KaitaiStruct, parent: CreativeVoiceFile_Block): CreativeVoiceFile_BlockMarker =
template this: untyped = result
this = new(CreativeVoiceFile_BlockMarker)
let root = if root == nil: cast[CreativeVoiceFile](this) else: cast[CreativeVoiceFile](root)
this.io = io
this.root = root
this.parent = parent
##[
Marker ID
]##
let markerIdExpr = this.io.readU2le()
this.markerId = markerIdExpr
proc fromFile*(_: typedesc[CreativeVoiceFile_BlockMarker], filename: string): CreativeVoiceFile_BlockMarker =
CreativeVoiceFile_BlockMarker.read(newKaitaiFileStream(filename), nil, nil)
##[
@see <a href="https://wiki.multimedia.cx/index.php?title=Creative_Voice#Block_type_0x03:_Silence">Source</a>
]##
proc read*(_: typedesc[CreativeVoiceFile_BlockSilence], io: KaitaiStream, root: KaitaiStruct, parent: CreativeVoiceFile_Block): CreativeVoiceFile_BlockSilence =
template this: untyped = result
this = new(CreativeVoiceFile_BlockSilence)
let root = if root == nil: cast[CreativeVoiceFile](this) else: cast[CreativeVoiceFile](root)
this.io = io
this.root = root
this.parent = parent
##[
Duration of silence, in samples
]##
let durationSamplesExpr = this.io.readU2le()
this.durationSamples = durationSamplesExpr
##[
Frequency divisor, used to determine sample rate
]##
let freqDivExpr = this.io.readU1()
this.freqDiv = freqDivExpr
proc sampleRate(this: CreativeVoiceFile_BlockSilence): float64 =
if this.sampleRateInstFlag:
return this.sampleRateInst
let sampleRateInstExpr = float64((1000000.0 div (256 - this.freqDiv)))
this.sampleRateInst = sampleRateInstExpr
this.sampleRateInstFlag = true
return this.sampleRateInst
proc durationSec(this: CreativeVoiceFile_BlockSilence): float64 =
##[
Duration of silence, in seconds
]##
if this.durationSecInstFlag:
return this.durationSecInst
let durationSecInstExpr = float64((this.durationSamples div this.sampleRate))
this.durationSecInst = durationSecInstExpr
this.durationSecInstFlag = true
return this.durationSecInst
proc fromFile*(_: typedesc[CreativeVoiceFile_BlockSilence], filename: string): CreativeVoiceFile_BlockSilence =
CreativeVoiceFile_BlockSilence.read(newKaitaiFileStream(filename), nil, nil)
##[
@see <a href="https://wiki.multimedia.cx/index.php?title=Creative_Voice#Block_type_0x09:_Sound_data_.28New_format.29">Source</a>
]##
proc read*(_: typedesc[CreativeVoiceFile_BlockSoundDataNew], io: KaitaiStream, root: KaitaiStruct, parent: CreativeVoiceFile_Block): CreativeVoiceFile_BlockSoundDataNew =
template this: untyped = result
this = new(CreativeVoiceFile_BlockSoundDataNew)
let root = if root == nil: cast[CreativeVoiceFile](this) else: cast[CreativeVoiceFile](root)
this.io = io
this.root = root
this.parent = parent
let sampleRateExpr = this.io.readU4le()
this.sampleRate = sampleRateExpr
let bitsPerSampleExpr = this.io.readU1()
this.bitsPerSample = bitsPerSampleExpr
let numChannelsExpr = this.io.readU1()
this.numChannels = numChannelsExpr
let codecExpr = CreativeVoiceFile_Codecs(this.io.readU2le())
this.codec = codecExpr
let reservedExpr = this.io.readBytes(int(4))
this.reserved = reservedExpr
let waveExpr = this.io.readBytesFull()
this.wave = waveExpr
proc fromFile*(_: typedesc[CreativeVoiceFile_BlockSoundDataNew], filename: string): CreativeVoiceFile_BlockSoundDataNew =
CreativeVoiceFile_BlockSoundDataNew.read(newKaitaiFileStream(filename), nil, nil)
proc read*(_: typedesc[CreativeVoiceFile_Block], io: KaitaiStream, root: KaitaiStruct, parent: CreativeVoiceFile): CreativeVoiceFile_Block =
template this: untyped = result
this = new(CreativeVoiceFile_Block)
let root = if root == nil: cast[CreativeVoiceFile](this) else: cast[CreativeVoiceFile](root)
this.io = io
this.root = root
this.parent = parent
##[
Byte that determines type of block content
]##
let blockTypeExpr = CreativeVoiceFile_BlockTypes(this.io.readU1())
this.blockType = blockTypeExpr
if this.blockType != creative_voice_file.terminator:
let bodySize1Expr = this.io.readU2le()
this.bodySize1 = bodySize1Expr
if this.blockType != creative_voice_file.terminator:
let bodySize2Expr = this.io.readU1()
this.bodySize2 = bodySize2Expr
##[
Block body, type depends on block type byte
]##
if this.blockType != creative_voice_file.terminator:
block:
let on = this.blockType
if on == creative_voice_file.sound_data_new:
let rawBodyExpr = this.io.readBytes(int(this.bodySize))
this.rawBody = rawBodyExpr
let rawBodyIo = newKaitaiStream(rawBodyExpr)
let bodyExpr = CreativeVoiceFile_BlockSoundDataNew.read(rawBodyIo, this.root, this)
this.body = bodyExpr
elif on == creative_voice_file.repeat_start:
let rawBodyExpr = this.io.readBytes(int(this.bodySize))
this.rawBody = rawBodyExpr
let rawBodyIo = newKaitaiStream(rawBodyExpr)
let bodyExpr = CreativeVoiceFile_BlockRepeatStart.read(rawBodyIo, this.root, this)
this.body = bodyExpr
elif on == creative_voice_file.marker:
let rawBodyExpr = this.io.readBytes(int(this.bodySize))
this.rawBody = rawBodyExpr
let rawBodyIo = newKaitaiStream(rawBodyExpr)
let bodyExpr = CreativeVoiceFile_BlockMarker.read(rawBodyIo, this.root, this)
this.body = bodyExpr
elif on == creative_voice_file.sound_data:
let rawBodyExpr = this.io.readBytes(int(this.bodySize))
this.rawBody = rawBodyExpr
let rawBodyIo = newKaitaiStream(rawBodyExpr)
let bodyExpr = CreativeVoiceFile_BlockSoundData.read(rawBodyIo, this.root, this)
this.body = bodyExpr
elif on == creative_voice_file.extra_info:
let rawBodyExpr = this.io.readBytes(int(this.bodySize))
this.rawBody = rawBodyExpr
let rawBodyIo = newKaitaiStream(rawBodyExpr)
let bodyExpr = CreativeVoiceFile_BlockExtraInfo.read(rawBodyIo, this.root, this)
this.body = bodyExpr
elif on == creative_voice_file.silence:
let rawBodyExpr = this.io.readBytes(int(this.bodySize))
this.rawBody = rawBodyExpr
let rawBodyIo = newKaitaiStream(rawBodyExpr)
let bodyExpr = CreativeVoiceFile_BlockSilence.read(rawBodyIo, this.root, this)
this.body = bodyExpr
else:
let bodyExpr = this.io.readBytes(int(this.bodySize))
this.body = bodyExpr
proc bodySize(this: CreativeVoiceFile_Block): int =
##[
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).
]##
if this.bodySizeInstFlag:
return this.bodySizeInst
if this.blockType != creative_voice_file.terminator:
let bodySizeInstExpr = int((this.bodySize1 + (this.bodySize2 shl 16)))
this.bodySizeInst = bodySizeInstExpr
this.bodySizeInstFlag = true
return this.bodySizeInst
proc fromFile*(_: typedesc[CreativeVoiceFile_Block], filename: string): CreativeVoiceFile_Block =
CreativeVoiceFile_Block.read(newKaitaiFileStream(filename), nil, nil)
##[
@see <a href="https://wiki.multimedia.cx/index.php?title=Creative_Voice#Block_type_0x06:_Repeat_start">Source</a>
]##
proc read*(_: typedesc[CreativeVoiceFile_BlockRepeatStart], io: KaitaiStream, root: KaitaiStruct, parent: CreativeVoiceFile_Block): CreativeVoiceFile_BlockRepeatStart =
template this: untyped = result
this = new(CreativeVoiceFile_BlockRepeatStart)
let root = if root == nil: cast[CreativeVoiceFile](this) else: cast[CreativeVoiceFile](root)
this.io = io
this.root = root
this.parent = parent
##[
Number of repetitions minus 1; 0xffff means infinite repetitions
]##
let repeatCount1Expr = this.io.readU2le()
this.repeatCount1 = repeatCount1Expr
proc fromFile*(_: typedesc[CreativeVoiceFile_BlockRepeatStart], filename: string): CreativeVoiceFile_BlockRepeatStart =
CreativeVoiceFile_BlockRepeatStart.read(newKaitaiFileStream(filename), nil, nil)
##[
@see <a href="https://wiki.multimedia.cx/index.php?title=Creative_Voice#Block_type_0x01:_Sound_data">Source</a>
]##
proc read*(_: typedesc[CreativeVoiceFile_BlockSoundData], io: KaitaiStream, root: KaitaiStruct, parent: CreativeVoiceFile_Block): CreativeVoiceFile_BlockSoundData =
template this: untyped = result
this = new(CreativeVoiceFile_BlockSoundData)
let root = if root == nil: cast[CreativeVoiceFile](this) else: cast[CreativeVoiceFile](root)
this.io = io
this.root = root
this.parent = parent
##[
Frequency divisor, used to determine sample rate
]##
let freqDivExpr = this.io.readU1()
this.freqDiv = freqDivExpr
let codecExpr = CreativeVoiceFile_Codecs(this.io.readU1())
this.codec = codecExpr
let waveExpr = this.io.readBytesFull()
this.wave = waveExpr
proc sampleRate(this: CreativeVoiceFile_BlockSoundData): float64 =
if this.sampleRateInstFlag:
return this.sampleRateInst
let sampleRateInstExpr = float64((1000000.0 div (256 - this.freqDiv)))
this.sampleRateInst = sampleRateInstExpr
this.sampleRateInstFlag = true
return this.sampleRateInst
proc fromFile*(_: typedesc[CreativeVoiceFile_BlockSoundData], filename: string): CreativeVoiceFile_BlockSoundData =
CreativeVoiceFile_BlockSoundData.read(newKaitaiFileStream(filename), nil, nil)
##[
@see <a href="https://wiki.multimedia.cx/index.php?title=Creative_Voice#Block_type_0x08:_Extra_info">Source</a>
]##
proc read*(_: typedesc[CreativeVoiceFile_BlockExtraInfo], io: KaitaiStream, root: KaitaiStruct, parent: CreativeVoiceFile_Block): CreativeVoiceFile_BlockExtraInfo =
template this: untyped = result
this = new(CreativeVoiceFile_BlockExtraInfo)
let root = if root == nil: cast[CreativeVoiceFile](this) else: cast[CreativeVoiceFile](root)
this.io = io
this.root = root
this.parent = parent
##[
Frequency divisor
]##
let freqDivExpr = this.io.readU2le()
this.freqDiv = freqDivExpr
let codecExpr = CreativeVoiceFile_Codecs(this.io.readU1())
this.codec = codecExpr
##[
Number of channels minus 1 (0 = mono, 1 = stereo)
]##
let numChannels1Expr = this.io.readU1()
this.numChannels1 = numChannels1Expr
proc numChannels(this: CreativeVoiceFile_BlockExtraInfo): int =
##[
Number of channels (1 = mono, 2 = stereo)
]##
if this.numChannelsInstFlag:
return this.numChannelsInst
let numChannelsInstExpr = int((this.numChannels1 + 1))
this.numChannelsInst = numChannelsInstExpr
this.numChannelsInstFlag = true
return this.numChannelsInst
proc sampleRate(this: CreativeVoiceFile_BlockExtraInfo): float64 =
if this.sampleRateInstFlag:
return this.sampleRateInst
let sampleRateInstExpr = float64((256000000.0 div (this.numChannels * (65536 - this.freqDiv))))
this.sampleRateInst = sampleRateInstExpr
this.sampleRateInstFlag = true
return this.sampleRateInst
proc fromFile*(_: typedesc[CreativeVoiceFile_BlockExtraInfo], filename: string): CreativeVoiceFile_BlockExtraInfo =
CreativeVoiceFile_BlockExtraInfo.read(newKaitaiFileStream(filename), nil, nil)