Blob Blame History Raw
/*
** Copyright (C) 2001-2016 Erik de Castro Lopo <erikd@mega-nerd.com>
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU Lesser General Public License as published by
** the Free Software Foundation; either version 2.1 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Lesser General Public License for more details.
**
** You should have received a copy of the GNU Lesser General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

/*	RANT:
**	The VOC file format is the most brain damaged format I have yet had to deal
**	with. No one programmer could have bee stupid enough to put this together.
**	Instead it looks like a series of manic, dyslexic assembly language programmers
**	hacked it to fit their needs.
**	Utterly woeful.
*/

#include "sfconfig.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "sndfile.h"
#include "sfendian.h"
#include "common.h"


/*------------------------------------------------------------------------------
 * Typedefs for file chunks.
*/

#define	VOC_MAX_SECTIONS	200

enum
{	VOC_TERMINATOR		= 0,
	VOC_SOUND_DATA		= 1,
	VOC_SOUND_CONTINUE	= 2,
	VOC_SILENCE			= 3,
	VOC_MARKER			= 4,
	VOC_ASCII			= 5,
	VOC_REPEAT			= 6,
	VOC_END_REPEAT		= 7,
	VOC_EXTENDED		= 8,
	VOC_EXTENDED_II		= 9
} ;

typedef struct
{	int 	samples ;
	int		offset ;	/* Offset of zero => silence. */
} SND_DATA_BLOCKS ;

typedef struct
{	unsigned int 	sections, section_types ;
	int				samplerate, channels, bitwidth ;
	SND_DATA_BLOCKS	blocks [VOC_MAX_SECTIONS] ;
} VOC_DATA ;

/*------------------------------------------------------------------------------
 * Private static functions.
*/

static	int	voc_close	(SF_PRIVATE *psf) ;
static	int voc_write_header (SF_PRIVATE *psf, int calc_length) ;
static	int voc_read_header	(SF_PRIVATE *psf) ;

static const char* voc_encoding2str (int encoding) ;

#if 0

/*	These functions would be required for files with more than one VOC_SOUND_DATA
**	segment. Not sure whether to bother implementing this.
*/

static int	voc_multi_init (SF_PRIVATE *psf, VOC_DATA *pvoc) ;

static int	voc_multi_read_uc2s		(SF_PRIVATE *psf, short *ptr, int len) ;
static int	voc_multi_read_les2s	(SF_PRIVATE *psf, short *ptr, int len) ;

static int	voc_multi_read_uc2i		(SF_PRIVATE *psf, int *ptr, int len) ;
static int	voc_multi_read_les2i	(SF_PRIVATE *psf, int *ptr, int len) ;

static int	voc_multi_read_uc2f		(SF_PRIVATE *psf, float *ptr, int len) ;
static int	voc_multi_read_les2f	(SF_PRIVATE *psf, float *ptr, int len) ;

static int	voc_multi_read_uc2d		(SF_PRIVATE *psf, double *ptr, int len) ;
static int	voc_multi_read_les2d	(SF_PRIVATE *psf, double *ptr, int len) ;
#endif

/*------------------------------------------------------------------------------
** Public function.
*/

int
voc_open	(SF_PRIVATE *psf)
{	int subformat, error = 0 ;

	if (psf->is_pipe)
		return SFE_VOC_NO_PIPE ;

	if (psf->file.mode == SFM_READ || (psf->file.mode == SFM_RDWR && psf->filelength > 0))
	{	if ((error = voc_read_header (psf)))
			return error ;
		} ;

	subformat = SF_CODEC (psf->sf.format) ;

	if (psf->file.mode == SFM_WRITE || psf->file.mode == SFM_RDWR)
	{	if ((SF_CONTAINER (psf->sf.format)) != SF_FORMAT_VOC)
			return	SFE_BAD_OPEN_FORMAT ;

		psf->endian = SF_ENDIAN_LITTLE ;

		if ((error = voc_write_header (psf, SF_FALSE)))
			return error ;

		psf->write_header = voc_write_header ;
		} ;

	psf->blockwidth = psf->bytewidth * psf->sf.channels ;

	psf->container_close = voc_close ;

	switch (subformat)
	{	case SF_FORMAT_PCM_U8 :
		case SF_FORMAT_PCM_16 :
				error = pcm_init (psf) ;
				break ;

		case SF_FORMAT_ALAW :
				error = alaw_init (psf) ;
				break ;

		case SF_FORMAT_ULAW :
				error = ulaw_init (psf) ;
				break ;

		default : return SFE_UNIMPLEMENTED ;
		} ;

	return error ;
} /* voc_open */

/*------------------------------------------------------------------------------
*/

static int
voc_read_header	(SF_PRIVATE *psf)
{	VOC_DATA	*pvoc ;
	char	creative [20] ;
	unsigned char block_type, rate_byte ;
	short	version, checksum, encoding, dataoffset ;
	int		offset ;

	/* Set position to start of file to begin reading header. */
	offset = psf_binheader_readf (psf, "pb", 0, creative, SIGNED_SIZEOF (creative)) ;

	if (creative [sizeof (creative) - 1] != 0x1A)
		return SFE_VOC_NO_CREATIVE ;

	/* Terminate the string. */
	creative [sizeof (creative) - 1] = 0 ;

	if (strcmp ("Creative Voice File", creative))
		return SFE_VOC_NO_CREATIVE ;

	psf_log_printf (psf, "%s\n", creative) ;

	offset += psf_binheader_readf (psf, "e222", &dataoffset, &version, &checksum) ;

	psf->dataoffset = dataoffset ;

	psf_log_printf (psf, 	"dataoffset : %d\n"
							"version    : 0x%X\n"
							"checksum   : 0x%X\n", psf->dataoffset, version, checksum) ;

	if (version != 0x010A && version != 0x0114)
		return SFE_VOC_BAD_VERSION ;

	if (! (psf->codec_data = malloc (sizeof (VOC_DATA))))
		return SFE_MALLOC_FAILED ;

	pvoc = (VOC_DATA*) psf->codec_data ;

	memset (pvoc, 0, sizeof (VOC_DATA)) ;

	/* Set the default encoding now. */
	psf->sf.format = SF_FORMAT_VOC ; /* Major format */
	encoding = SF_FORMAT_PCM_U8 ; /* Minor format */
	psf->endian = SF_ENDIAN_LITTLE ;

	while (1)
	{	char header [256] ;
		unsigned size ;
		short count ;

		block_type = 0 ;
		offset += psf_binheader_readf (psf, "1", &block_type) ;

		switch (block_type)
		{	case VOC_ASCII :
					offset += psf_binheader_readf (psf, "e3", &size) ;

					psf_log_printf (psf, " ASCII : %d\n", size) ;

					if (size < sizeof (header) - 1)
					{	offset += psf_binheader_readf (psf, "b", header, size) ;
						header [size] = 0 ;
						psf_log_printf (psf, "  text : %s\n", header) ;
						continue ;
						}

					offset += psf_binheader_readf (psf, "j", size) ;
					continue ;

			case VOC_REPEAT :
					offset += psf_binheader_readf (psf, "e32", &size, &count) ;
					psf_log_printf (psf, " Repeat : %d\n", count) ;
					continue ;

			case VOC_SOUND_DATA :
			case VOC_EXTENDED :
			case VOC_EXTENDED_II :
					break ;

			default : psf_log_printf (psf, "*** Weird block marker (%d)\n", block_type) ;
			} ;

		break ;
		} ;

	if (block_type == VOC_SOUND_DATA)
	{	unsigned char compression ;
		int 	size ;

		offset += psf_binheader_readf (psf, "e311", &size, &rate_byte, &compression) ;

		psf->sf.samplerate = 1000000 / (256 - (rate_byte & 0xFF)) ;

		psf_log_printf (psf, " Sound Data : %d\n  sr   : %d => %dHz\n  comp : %d\n",
								size, rate_byte, psf->sf.samplerate, compression) ;

		if (offset + size - 1 > psf->filelength)
		{	psf_log_printf (psf, "Seems to be a truncated file.\n") ;
			psf_log_printf (psf, "offset: %d    size: %d    sum: %d    filelength: %D\n", offset, size, offset + size, psf->filelength) ;
			return SFE_VOC_BAD_SECTIONS ;
			}
		else if (psf->filelength - offset - size > 4)
		{	psf_log_printf (psf, "Seems to be a multi-segment file (#1).\n") ;
			psf_log_printf (psf, "offset: %d    size: %d    sum: %d    filelength: %D\n", offset, size, offset + size, psf->filelength) ;
			return SFE_VOC_BAD_SECTIONS ;
			} ;

		psf->dataoffset = offset ;
		psf->dataend	= psf->filelength - 1 ;

		psf->sf.channels = 1 ;
		psf->bytewidth = 1 ;

		psf->sf.format = SF_FORMAT_VOC | SF_FORMAT_PCM_U8 ;

		return 0 ;
		} ;

	if (block_type == VOC_EXTENDED)
	{	unsigned char pack, stereo, compression ;
		unsigned short rate_short ;
		int		size ;

		offset += psf_binheader_readf (psf, "e3211", &size, &rate_short, &pack, &stereo) ;

		psf_log_printf (psf, " Extended : %d\n", size) ;
		if (size == 4)
			psf_log_printf (psf, "  size   : 4\n") ;
		else
			psf_log_printf (psf, "  size   : %d (should be 4)\n", size) ;

		psf_log_printf (psf,	"  pack   : %d\n"
								"  stereo : %s\n", pack, (stereo ? "yes" : "no")) ;

		if (stereo)
		{	psf->sf.channels = 2 ;
			psf->sf.samplerate = 128000000 / (65536 - rate_short) ;
			}
		else
		{	psf->sf.channels = 1 ;
			psf->sf.samplerate = 256000000 / (65536 - rate_short) ;
			} ;

		psf_log_printf (psf, "  sr     : %d => %dHz\n", (rate_short & 0xFFFF), psf->sf.samplerate) ;

		offset += psf_binheader_readf (psf, "1", &block_type) ;

		if (block_type != VOC_SOUND_DATA)
		{	psf_log_printf (psf, "*** Expecting VOC_SOUND_DATA section.\n") ;
			return SFE_VOC_BAD_FORMAT ;
			} ;

		offset += psf_binheader_readf (psf, "e311", &size, &rate_byte, &compression) ;

		psf_log_printf (psf,	" Sound Data : %d\n"
								"  sr     : %d\n"
								"  comp   : %d\n", size, rate_byte, compression) ;


		if (offset + size - 1 > psf->filelength)
		{	psf_log_printf (psf, "Seems to be a truncated file.\n") ;
			psf_log_printf (psf, "offset: %d    size: %d    sum: %d    filelength: %D\n", offset, size, offset + size, psf->filelength) ;
			return SFE_VOC_BAD_SECTIONS ;
			}
		else if (offset + size - 1 < psf->filelength)
		{	psf_log_printf (psf, "Seems to be a multi-segment file (#2).\n") ;
			psf_log_printf (psf, "offset: %d    size: %d    sum: %d    filelength: %D\n", offset, size, offset + size, psf->filelength) ;
			return SFE_VOC_BAD_SECTIONS ;
			} ;

		psf->dataoffset = offset ;
		psf->dataend = psf->filelength - 1 ;

		psf->bytewidth = 1 ;

		psf->sf.format = SF_FORMAT_VOC | SF_FORMAT_PCM_U8 ;

		return 0 ;
		}

	if (block_type == VOC_EXTENDED_II)
	{	unsigned char bitwidth, channels ;
		int size, fourbytes ;

		offset += psf_binheader_readf (psf, "e341124", &size, &psf->sf.samplerate,
								&bitwidth, &channels, &encoding, &fourbytes) ;

		if (size * 2 == psf->filelength - 39)
		{	int temp_size = psf->filelength - 31 ;

			psf_log_printf (psf, " Extended II : %d (SoX bug: should be %d)\n", size, temp_size) ;
			size = temp_size ;
			}
		else
			psf_log_printf (psf, " Extended II : %d\n", size) ;

		psf_log_printf (psf,	"  sample rate : %d\n"
								"  bit width   : %d\n"
								"  channels    : %d\n", psf->sf.samplerate, bitwidth, channels) ;

		if (bitwidth == 16 && encoding == 0)
		{	encoding = 4 ;
			psf_log_printf (psf, "  encoding    : 0 (SoX bug: should be 4 for 16 bit signed PCM)\n") ;
			}
		else
			psf_log_printf (psf, "  encoding    : %d => %s\n", encoding, voc_encoding2str (encoding)) ;


		psf_log_printf (psf, "  fourbytes   : %X\n", fourbytes) ;

		psf->sf.channels = channels ;

		psf->dataoffset = offset ;
		psf->dataend	= psf->filelength - 1 ;

		if (size + 31 == psf->filelength + 1)
		{	/* Hack for reading files produced using
			** sf_command (SFC_UPDATE_HEADER_NOW).
			*/
			psf_log_printf (psf, "Missing zero byte at end of file.\n") ;
			size = psf->filelength - 30 ;
			psf->dataend = 0 ;
			}
		else if (size + 31 > psf->filelength)
		{	psf_log_printf (psf, "Seems to be a truncated file.\n") ;
			size = psf->filelength - 31 ;
			}
		else if (size + 31 < psf->filelength)
			psf_log_printf (psf, "Seems to be a multi-segment file (#3).\n") ;

		switch (encoding)
		{	case 0 :
					psf->sf.format = SF_FORMAT_VOC | SF_FORMAT_PCM_U8 ;
					psf->bytewidth = 1 ;
					break ;

			case 4 :
					psf->sf.format = SF_FORMAT_VOC | SF_FORMAT_PCM_16 ;
					psf->bytewidth = 2 ;
					break ;

			case 6 :
					psf->sf.format = SF_FORMAT_VOC | SF_FORMAT_ALAW ;
					psf->bytewidth = 1 ;
					break ;

			case 7 :
					psf->sf.format = SF_FORMAT_VOC | SF_FORMAT_ULAW ;
					psf->bytewidth = 1 ;
					break ;

			default : /* Unknown */
					return SFE_UNKNOWN_FORMAT ;
					break ;
			} ;

		} ;

	return 0 ;
} /* voc_read_header */

/*====================================================================================
*/

static int
voc_write_header (SF_PRIVATE *psf, int calc_length)
{	sf_count_t	current ;
	int			rate_const, subformat ;

	current = psf_ftell (psf) ;

	if (calc_length)
	{	psf->filelength = psf_get_filelen (psf) ;

		psf->datalength = psf->filelength - psf->dataoffset ;
		if (psf->dataend)
			psf->datalength -= psf->filelength - psf->dataend ;

		psf->sf.frames = psf->datalength / (psf->bytewidth * psf->sf.channels) ;
		} ;

	subformat = SF_CODEC (psf->sf.format) ;
	/* Reset the current header length to zero. */
	psf->header.ptr [0] = 0 ;
	psf->header.indx = 0 ;
	psf_fseek (psf, 0, SEEK_SET) ;

	/* VOC marker and 0x1A byte. */
	psf_binheader_writef (psf, "eb1", "Creative Voice File", make_size_t (19), 0x1A) ;

	/* Data offset, version and other. */
	psf_binheader_writef (psf, "e222", 26, 0x0114, 0x111F) ;

	/*	Use same logic as SOX.
	**	If the file is mono 8 bit data, use VOC_SOUND_DATA.
	**	If the file is mono 16 bit data, use VOC_EXTENED.
	**	Otherwise use VOC_EXTENED_2.
	*/

	if (subformat == SF_FORMAT_PCM_U8 && psf->sf.channels == 1)
	{	/* samplerate = 1000000 / (256 - rate_const) ; */
		rate_const = 256 - 1000000 / psf->sf.samplerate ;

		/* First type marker, length, rate_const and compression */
		psf_binheader_writef (psf, "e1311", VOC_SOUND_DATA, (int) (psf->datalength + 1), rate_const, 0) ;
		}
	else if (subformat == SF_FORMAT_PCM_U8 && psf->sf.channels == 2)
	{	/* sample_rate = 128000000 / (65536 - rate_short) ; */
		rate_const = 65536 - 128000000 / psf->sf.samplerate ;

		/* First write the VOC_EXTENDED section
		** 		marker, length, rate_const and compression
		*/
		psf_binheader_writef (psf, "e13211", VOC_EXTENDED, 4, rate_const, 0, 1) ;

		/* samplerate = 1000000 / (256 - rate_const) ; */
		rate_const = 256 - 1000000 / psf->sf.samplerate ;

		/*	Now write the VOC_SOUND_DATA section
		** 		marker, length, rate_const and compression
		*/
		psf_binheader_writef (psf, "e1311", VOC_SOUND_DATA, (int) (psf->datalength + 1), rate_const, 0) ;
		}
	else
	{	int length ;

		if (psf->sf.channels < 1 || psf->sf.channels > 2)
			return SFE_CHANNEL_COUNT ;

		switch (subformat)
		{	case SF_FORMAT_PCM_U8 :
					psf->bytewidth = 1 ;
					length = psf->sf.frames * psf->sf.channels * psf->bytewidth + 12 ;
					/* Marker, length, sample rate, bitwidth, stereo flag, encoding and fourt zero bytes. */
					psf_binheader_writef (psf, "e1341124", VOC_EXTENDED_II, length, psf->sf.samplerate, 16, psf->sf.channels, 4, 0) ;
					break ;

			case SF_FORMAT_PCM_16 :
					psf->bytewidth = 2 ;
					length = psf->sf.frames * psf->sf.channels * psf->bytewidth + 12 ;
					/* Marker, length, sample rate, bitwidth, stereo flag, encoding and fourt zero bytes. */
					psf_binheader_writef (psf, "e1341124", VOC_EXTENDED_II, length, psf->sf.samplerate, 16, psf->sf.channels, 4, 0) ;
					break ;

			case SF_FORMAT_ALAW :
					psf->bytewidth = 1 ;
					length = psf->sf.frames * psf->sf.channels * psf->bytewidth + 12 ;
					psf_binheader_writef (psf, "e1341124", VOC_EXTENDED_II, length, psf->sf.samplerate, 8, psf->sf.channels, 6, 0) ;
					break ;

			case SF_FORMAT_ULAW :
					psf->bytewidth = 1 ;
					length = psf->sf.frames * psf->sf.channels * psf->bytewidth + 12 ;
					psf_binheader_writef (psf, "e1341124", VOC_EXTENDED_II, length, psf->sf.samplerate, 8, psf->sf.channels, 7, 0) ;
					break ;

			default : return SFE_UNIMPLEMENTED ;
			} ;
		} ;

	psf_fwrite (psf->header.ptr, psf->header.indx, 1, psf) ;

	if (psf->error)
		return psf->error ;

	psf->dataoffset = psf->header.indx ;

	if (current > 0)
		psf_fseek (psf, current, SEEK_SET) ;

	return psf->error ;
} /* voc_write_header */

static int
voc_close	(SF_PRIVATE *psf)
{
	if (psf->file.mode == SFM_WRITE || psf->file.mode == SFM_RDWR)
	{	/*  Now we know for certain the length of the file we can re-write
		**	correct values for the FORM, 8SVX and BODY chunks.
		*/
		unsigned char byte = VOC_TERMINATOR ;


		psf_fseek (psf, 0, SEEK_END) ;

		/* Write terminator */
		psf_fwrite (&byte, 1, 1, psf) ;

		voc_write_header (psf, SF_TRUE) ;
		} ;

	return 0 ;
} /* voc_close */

static const 	char*
voc_encoding2str (int encoding)
{
	switch (encoding)
	{	case 0 :	return "8 bit unsigned PCM" ;
		case 4 :	return "16 bit signed PCM" ;
		case 6 :	return "A-law" ;
		case 7 :	return "u-law" ;
		default :	break ;
		}
	return "*** Unknown ***" ;
} /* voc_encoding2str */

/*====================================================================================
*/

#if 0
static int
voc_multi_init (SF_PRIVATE *psf, VOC_DATA *pvoc)
{
	psf->sf.frames = 0 ;

	if (pvoc->bitwidth == 8)
	{	psf->read_short		= voc_multi_read_uc2s ;
		psf->read_int		= voc_multi_read_uc2i ;
		psf->read_float		= voc_multi_read_uc2f ;
		psf->read_double	= voc_multi_read_uc2d ;
		return 0 ;
		} ;

	if (pvoc->bitwidth == 16)
	{	psf->read_short		= voc_multi_read_les2s ;
		psf->read_int		= voc_multi_read_les2i ;
		psf->read_float		= voc_multi_read_les2f ;
		psf->read_double	= voc_multi_read_les2d ;
		return 0 ;
		} ;

	psf_log_printf (psf, "Error : bitwith != 8 && bitwidth != 16.\n") ;

	return SFE_UNIMPLEMENTED ;
} /* voc_multi_read_int */

/*------------------------------------------------------------------------------------
*/

static int
voc_multi_read_uc2s (SF_PRIVATE *psf, short *ptr, int len)
{

	return 0 ;
} /* voc_multi_read_uc2s */

static int
voc_multi_read_les2s (SF_PRIVATE *psf, short *ptr, int len)
{

	return 0 ;
} /* voc_multi_read_les2s */


static int
voc_multi_read_uc2i (SF_PRIVATE *psf, int *ptr, int len)
{

	return 0 ;
} /* voc_multi_read_uc2i */

static int
voc_multi_read_les2i (SF_PRIVATE *psf, int *ptr, int len)
{

	return 0 ;
} /* voc_multi_read_les2i */


static int
voc_multi_read_uc2f (SF_PRIVATE *psf, float *ptr, int len)
{

	return 0 ;
} /* voc_multi_read_uc2f */

static int
voc_multi_read_les2f (SF_PRIVATE *psf, float *ptr, int len)
{

	return 0 ;
} /* voc_multi_read_les2f */


static int
voc_multi_read_uc2d (SF_PRIVATE *psf, double *ptr, int len)
{

	return 0 ;
} /* voc_multi_read_uc2d */

static int
voc_multi_read_les2d (SF_PRIVATE *psf, double *ptr, int len)
{

	return 0 ;
} /* voc_multi_read_les2d */

#endif

/*------------------------------------------------------------------------------------

Creative Voice (VOC) file format
--------------------------------

~From: galt@dsd.es.com

(byte numbers are hex!)

    HEADER (bytes 00-19)
    Series of DATA BLOCKS (bytes 1A+) [Must end w/ Terminator Block]

- ---------------------------------------------------------------

HEADER:
=======
     byte #     Description
     ------     ------------------------------------------
     00-12      "Creative Voice File"
     13         1A (eof to abort printing of file)
     14-15      Offset of first datablock in .voc file (std 1A 00
                in Intel Notation)
     16-17      Version number (minor,major) (VOC-HDR puts 0A 01)
     18-19      1's Comp of Ver. # + 1234h (VOC-HDR puts 29 11)

- ---------------------------------------------------------------

DATA BLOCK:
===========

   Data Block:  TYPE(1-byte), SIZE(3-bytes), INFO(0+ bytes)
   NOTE: Terminator Block is an exception -- it has only the TYPE byte.

      TYPE   Description     Size (3-byte int)   Info
      ----   -----------     -----------------   -----------------------
      00     Terminator      (NONE)              (NONE)
      01     Sound data      2+length of data    *
      02     Sound continue  length of data      Voice Data
      03     Silence         3                   **
      04     Marker          2                   Marker# (2 bytes)
      05     ASCII           length of string    null terminated string
      06     Repeat          2                   Count# (2 bytes)
      07     End repeat      0                   (NONE)
      08     Extended        4                   ***

      *Sound Info Format:
       ---------------------
       00   Sample Rate
       01   Compression Type
       02+  Voice Data

      **Silence Info Format:
      ----------------------------
      00-01  Length of silence - 1
      02     Sample Rate


    ***Extended Info Format:
       ---------------------
       00-01  Time Constant: Mono: 65536 - (256000000/sample_rate)
                             Stereo: 65536 - (25600000/(2*sample_rate))
       02     Pack
       03     Mode: 0 = mono
                    1 = stereo


  Marker#           -- Driver keeps the most recent marker in a status byte
  Count#            -- Number of repetitions + 1
                         Count# may be 1 to FFFE for 0 - FFFD repetitions
                         or FFFF for endless repetitions
  Sample Rate       -- SR byte = 256-(1000000/sample_rate)
  Length of silence -- in units of sampling cycle
  Compression Type  -- of voice data
                         8-bits    = 0
                         4-bits    = 1
                         2.6-bits  = 2
                         2-bits    = 3
                         Multi DAC = 3+(# of channels) [interesting--
                                       this isn't in the developer's manual]


---------------------------------------------------------------------------------
Addendum submitted by Votis Kokavessis:

After some experimenting with .VOC files I found out that there is a Data Block
Type 9, which is not covered in the VOC.TXT file. Here is what I was able to discover
about this block type:


TYPE: 09
SIZE: 12 + length of data
INFO: 12 (twelve) bytes

INFO STRUCTURE:

Bytes 0-1: (Word) Sample Rate (e.g. 44100)
Bytes 2-3: zero (could be that bytes 0-3 are a DWord for Sample Rate)
Byte 4: Sample Size in bits (e.g. 16)
Byte 5: Number of channels (e.g. 1 for mono, 2 for stereo)
Byte 6: Unknown (equal to 4 in all files I examined)
Bytes 7-11: zero


-------------------------------------------------------------------------------------*/

/*=====================================================================================
**=====================================================================================
**=====================================================================================
**=====================================================================================
*/

/*------------------------------------------------------------------------
The following is taken from the Audio File Formats FAQ dated 2-Jan-1995
and submitted by Guido van Rossum <guido@cwi.nl>.
--------------------------------------------------------------------------
Creative Voice (VOC) file format
--------------------------------

From: galt@dsd.es.com

(byte numbers are hex!)

    HEADER (bytes 00-19)
    Series of DATA BLOCKS (bytes 1A+) [Must end w/ Terminator Block]

- ---------------------------------------------------------------

HEADER:
-------
     byte #     Description
     ------     ------------------------------------------
     00-12      "Creative Voice File"
     13         1A (eof to abort printing of file)
     14-15      Offset of first datablock in .voc file (std 1A 00
                in Intel Notation)
     16-17      Version number (minor,major) (VOC-HDR puts 0A 01)
     18-19      2's Comp of Ver. # + 1234h (VOC-HDR puts 29 11)

- ---------------------------------------------------------------

DATA BLOCK:
-----------

   Data Block:  TYPE(1-byte), SIZE(3-bytes), INFO(0+ bytes)
   NOTE: Terminator Block is an exception -- it has only the TYPE byte.

      TYPE   Description     Size (3-byte int)   Info
      ----   -----------     -----------------   -----------------------
      00     Terminator      (NONE)              (NONE)
      01     Sound data      2+length of data    *
      02     Sound continue  length of data      Voice Data
      03     Silence         3                   **
      04     Marker          2                   Marker# (2 bytes)
      05     ASCII           length of string    null terminated string
      06     Repeat          2                   Count# (2 bytes)
      07     End repeat      0                   (NONE)
      08     Extended        4                   ***

      *Sound Info Format:       **Silence Info Format:
       ---------------------      ----------------------------
       00   Sample Rate           00-01  Length of silence - 1
       01   Compression Type      02     Sample Rate
       02+  Voice Data

    ***Extended Info Format:
       ---------------------
       00-01  Time Constant: Mono: 65536 - (256000000/sample_rate)
                             Stereo: 65536 - (25600000/(2*sample_rate))
       02     Pack
       03     Mode: 0 = mono
                    1 = stereo


  Marker#           -- Driver keeps the most recent marker in a status byte
  Count#            -- Number of repetitions + 1
                         Count# may be 1 to FFFE for 0 - FFFD repetitions
                         or FFFF for endless repetitions
  Sample Rate       -- SR byte = 256-(1000000/sample_rate)
  Length of silence -- in units of sampling cycle
  Compression Type  -- of voice data
                         8-bits    = 0
                         4-bits    = 1
                         2.6-bits  = 2
                         2-bits    = 3
                         Multi DAC = 3+(# of channels) [interesting--
                                       this isn't in the developer's manual]

Detailed description of new data blocks (VOC files version 1.20 and above):

        (Source is fax from Barry Boone at Creative Labs, 405/742-6622)

BLOCK 8 - digitized sound attribute extension, must preceed block 1.
          Used to define stereo, 8 bit audio
        BYTE bBlockID;       // = 8
        BYTE nBlockLen[3];   // 3 byte length
        WORD wTimeConstant;  // time constant = same as block 1
        BYTE bPackMethod;    // same as in block 1
        BYTE bVoiceMode;     // 0-mono, 1-stereo

        Data is stored left, right

BLOCK 9 - data block that supersedes blocks 1 and 8.
          Used for stereo, 16 bit.

        BYTE bBlockID;          // = 9
        BYTE nBlockLen[3];      // length 12 plus length of sound
        DWORD dwSamplesPerSec;  // samples per second, not time const.
        BYTE bBitsPerSample;    // e.g., 8 or 16
        BYTE bChannels;         // 1 for mono, 2 for stereo
        WORD wFormat;           // see below
        BYTE reserved[4];       // pad to make block w/o data
                                // have a size of 16 bytes

        Valid values of wFormat are:

                0x0000  8-bit unsigned PCM
                0x0001  Creative 8-bit to 4-bit ADPCM
                0x0002  Creative 8-bit to 3-bit ADPCM
                0x0003  Creative 8-bit to 2-bit ADPCM
                0x0004  16-bit signed PCM
                0x0006  CCITT a-Law
                0x0007  CCITT u-Law
                0x02000 Creative 16-bit to 4-bit ADPCM

        Data is stored left, right

------------------------------------------------------------------------*/