shithub: sox

ref: 73a82c069d96a57caabc1f5e8fc43ce3c4f4b61d
dir: /src/voc.c/

View raw version
/*
 * Copyright 1991 Lance Norskog And Sundry Contributors
 * This source code is freely redistributable and may be used for
 * any purpose.  This copyright notice must be maintained. 
 * Lance Norskog And Sundry Contributors are not responsible for 
 * the consequences of using this software.
 *
 * September 8, 1993
 * Copyright 1993 T. Allen Grider - for changes to support block type 9
 * and word sized samples.  Same caveats and disclaimer as above.
 *
 * February 22, 1996
 * by Chris Bagwell (cbagwell@sprynet.com)
 * Added support for block type 8 (extended) which allows for 8-bit stereo 
 * files.  Added support for saving stereo files and 16-bit files.
 * Added VOC format info from audio format FAQ so I don't have to keep
 * looking around for it.
 */

/*
 * Sound Tools Sound Blaster VOC handler sources.
 */

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

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

#include "st.h"
#include <string.h>

/* Private data for VOC file */
typedef struct vocstuff {
	LONG	rest;			/* bytes remaining in current block */
	LONG	rate;			/* rate code (byte) of this chunk */
	int		silent;		/* sound or silence? */
	LONG	srate;			/* rate code (byte) of silence */
	LONG	blockseek;		/* start of current output block */
	LONG	samples;		/* number of samples output */
	int		size;		/* word length of data */
	unsigned char	channels;	/* number of sound channels */
	int     extended;       /* Has an extended block been read? */
} *vs_t;

#define	VOC_TERM	0
#define	VOC_DATA	1
#define	VOC_CONT	2
#define	VOC_SILENCE	3
#define	VOC_MARKER	4
#define	VOC_TEXT	5
#define	VOC_LOOP	6
#define	VOC_LOOPEND	7
#define VOC_EXTENDED    8
#define VOC_DATA_16	9

#define	min(a, b)	(((a) < (b)) ? (a) : (b))

static int getblock(P1(ft_t));
static void blockstart(P1(ft_t));
static void blockstop(P1(ft_t));

int st_vocstartread(ft) 
ft_t ft;
{
	char header[20];
	vs_t v = (vs_t) ft->priv;
	unsigned short sbseek;
	int littlendian = 1;
	char *endptr;
	int rc;

	endptr = (char *) &littlendian;
	/* VOC is in Little Endian format.  Swap bytes read in on */
	/* Big Endian mahcines.				          */
	if (!*endptr)
	{
		ft->swap = ft->swap ? 0 : 1;
	}

	if (! ft->seekable)
	{
		st_fail("VOC input file must be a file, not a pipe");
		return(ST_EOF);
	}
	if (fread(header, 1, 20, ft->fp) != 20)
	{
		st_fail("unexpected EOF in VOC header");
		return(ST_EOF);
	}
	if (strncmp(header, "Creative Voice File\032", 19))
	{
		st_fail("VOC file header incorrect");
		return(ST_EOF);
	}

	st_readw(ft, &sbseek);
	fseek(ft->fp, sbseek, 0);

	v->rate = -1;
	v->rest = 0;
	v->extended = 0;
	rc = getblock(ft);
	if (rc)
	    return rc;
	if (v->rate == -1)
	{
		st_fail("Input .voc file had no sound!");
		return(ST_EOF);
	}

	ft->info.size = v->size;
	ft->info.encoding = ST_ENCODING_UNSIGNED;
	if (v->size == ST_SIZE_WORD)
	    ft->info.encoding = ST_ENCODING_SIGN2;
	if (ft->info.channels == -1)
		ft->info.channels = v->channels;

	return(ST_SUCCESS);
}

LONG st_vocread(ft, buf, len) 
ft_t ft;
LONG *buf, len;
{
	vs_t v = (vs_t) ft->priv;
	int done = 0;
	int rc;
	unsigned short us;
	unsigned char uc;
	
	if (v->rest == 0)
	{
		rc = getblock(ft);
		if (rc)
		    return 0;
	}

	if (v->rest == 0)
		return 0;

	if (v->silent) {
		/* Fill in silence */
		for(;v->rest && (done < len); v->rest--, done++)
			*buf++ = 0x80000000L;
	} else {
		for(;v->rest && (done < len); v->rest--, done++) {
			switch(v->size)
			{
			    case ST_SIZE_BYTE:
				if (st_readb(ft, &uc) == ST_EOF) {
				    st_warn("VOC input: short file");
				    v->rest = 0;
				    return done;
				}
				uc ^= 0x80;	/* convert to signed */
				*buf++ = LEFT(uc, 24);
				break;
			    case ST_SIZE_WORD:
				st_readw(ft, &us);
				if (feof(ft->fp))
				{
				    st_warn("VOC input: short file");
				    v->rest = 0;
				    return done;
				}
				*buf++ = LEFT(us, 16);
				v->rest--; /* Processed 2 bytes so update */
				break;
			}	
		}
	}
	return done;
}

/* nothing to do */
int st_vocstopread(ft) 
ft_t ft;
{
    return(ST_SUCCESS);
}

/* When saving samples in VOC format the following outline is followed:
 * If an 8-bit mono sample then use a VOC_DATA header.
 * If an 8-bit stereo sample then use a VOC_EXTENDED header followed
 * by a VOC_DATA header.
 * If a 16-bit sample (either stereo or mono) then save with a 
 * VOC_DATA_16 header.
 *
 * This approach will cause the output to be an its most basic format
 * which will work with the oldest software (eg. an 8-bit mono sample
 * will be able to be played with a really old SB VOC player.)
 */
int st_vocstartwrite(ft) 
ft_t ft;
{
	vs_t v = (vs_t) ft->priv;
	int littlendian = 1;
	char *endptr;

	endptr = (char *) &littlendian;
	/* VOC is in Little Endian format.  Swap whats read */
	/* in on Big Endian machines.			    */
	if (!*endptr)
	{
		ft->swap = ft->swap ? 0 : 1;
	}

	if (! ft->seekable)
	{
		st_fail("Output .voc file must be a file, not a pipe");
		return(ST_EOF);
	}

	v->samples = 0;

	/* File format name and a ^Z (aborts printing under DOS) */
	st_writes(ft, "Creative Voice File\032");
	st_writew(ft, 26);			/* size of header */
	st_writew(ft, 0x10a);              /* major/minor version number */
	st_writew(ft, 0x1129);		/* checksum of version number */

	if (ft->info.size == ST_SIZE_BYTE)
	  ft->info.encoding = ST_ENCODING_UNSIGNED;
	else
	  ft->info.encoding = ST_ENCODING_SIGN2;
	if (ft->info.channels == -1)
		ft->info.channels = 1;

	return(ST_SUCCESS);
}

LONG st_vocwrite(ft, buf, len) 
ft_t ft;
LONG *buf, len;
{
	vs_t v = (vs_t) ft->priv;
	unsigned char uc;
	int sw;
	LONG done = 0;
	
	if (v->samples == 0) {
	  /* No silence packing yet. */
	  v->silent = 0;
	  blockstart(ft);
	}
	v->samples += len;
	while(done < len) {
	  if (ft->info.size == ST_SIZE_BYTE) {
	    uc = RIGHT(*buf++, 24);
	    uc ^= 0x80;
	    st_writeb(ft, uc);
	  } else {
		sw = (int) RIGHT(*buf++, 16);
	    st_writew(ft,sw);
          }
	  done++;
	}
	return done;
}

int st_vocstopwrite(ft) 
ft_t ft;
{
	blockstop(ft);
	return(ST_SUCCESS);
}

/* Voc-file handlers */

/* Read next block header, save info, leave position at start of data */
static int
getblock(ft)
ft_t ft;
{
	vs_t v = (vs_t) ft->priv;
	unsigned char uc, block;
	ULONG sblen;
	unsigned short new_rate_short;
	ULONG new_rate_long;
	int i;
	ULONG trash;

	v->silent = 0;
	while (v->rest == 0) {
		if (feof(ft->fp))
			return ST_SUCCESS;
		st_readb(ft, &block);
		if (block == VOC_TERM)
			return ST_SUCCESS;
		if (feof(ft->fp))
			return ST_SUCCESS;
		/* 
		 * Size is an 24-bit value.  Currently there is no util 
		 * func to read this so do it this cross-platform way
		 *
		 */
		st_readb(ft, &uc);
		sblen = uc;
		st_readb(ft, &uc);
		sblen |= ((LONG) uc) << 8;
		st_readb(ft, &uc);
		sblen |= ((LONG) uc) << 16;
		switch(block) {
		case VOC_DATA: 
			st_readb(ft, &uc);
			/* When DATA block preceeded by an EXTENDED     */
			/* block, the DATA blocks rate value is invalid */
		        if (!v->extended) {
			  if (uc == 0)
			  {
			    st_fail("File %s: Sample rate is zero?");
			    return(ST_EOF);
			  }
			  if ((v->rate != -1) && (uc != v->rate))
			  {
			    st_fail("File %s: sample rate codes differ: %d != %d",
				 ft->filename,v->rate, uc);
			    return(ST_EOF);
			  }
			  v->rate = uc;
			  ft->info.rate = 1000000.0/(256 - v->rate);
			  v->channels = 1;
			}
			st_readb(ft, &uc);
			if (uc != 0)
			{
			  st_fail("File %s: only interpret 8-bit data!",
			       ft->filename);
			  return(ST_EOF);
			}
			v->extended = 0;
			v->rest = sblen - 2;
			v->size = ST_SIZE_BYTE;
			return (ST_SUCCESS);
		case VOC_DATA_16:
			st_readdw(ft, &new_rate_long);
			if (new_rate_long == 0)
			{
			    st_fail("File %s: Sample rate is zero?",ft->filename);
			    return(ST_EOF);
			}
			if ((v->rate != -1) && (new_rate_long != v->rate))
			{
			    st_fail("File %s: sample rate codes differ: %d != %d",
				ft->filename, v->rate, new_rate_long);
			    return(ST_EOF);
			}
			v->rate = new_rate_long;
			ft->info.rate = new_rate_long;
			st_readb(ft, &uc);
			switch (uc)
			{
			    case 8:	v->size = ST_SIZE_BYTE; break;
			    case 16:	v->size = ST_SIZE_WORD; break;
			    default:	
					st_fail("Don't understand size %d", uc);
					return(ST_EOF);
			}
			st_readb(ft, &(v->channels));
			st_readb(ft, (unsigned char *)&trash); /* unknown */
			st_readb(ft, (unsigned char *)&trash); /* notused */
			st_readb(ft, (unsigned char *)&trash); /* notused */
			st_readb(ft, (unsigned char *)&trash); /* notused */
			st_readb(ft, (unsigned char *)&trash); /* notused */
			st_readb(ft, (unsigned char *)&trash); /* notused */
			v->rest = sblen - 12;
			return (ST_SUCCESS);
		case VOC_CONT: 
			v->rest = sblen;
			return (ST_SUCCESS);
		case VOC_SILENCE: 
			{
			unsigned short period;

			st_readw(ft, &period);
			st_readb(ft, &uc);
			if (uc == 0)
			{
				st_fail("File %s: Silence sample rate is zero");
				return(ST_EOF);
			}
			/* 
			 * Some silence-packed files have gratuitously
			 * different sample rate codes in silence.
			 * Adjust period.
			 */
			if ((v->rate != -1) && (uc != v->rate))
				period = (period * (256 - uc))/(256 - v->rate);
			else
				v->rate = uc;
			v->rest = period;
			v->silent = 1;
			return (ST_SUCCESS);
			}
		case VOC_MARKER:
			st_readb(ft, &uc);
			st_readb(ft, &uc);
			/* Falling! Falling! */
		case VOC_TEXT:
			{
			int i;
			/* Could add to comment in SF? */
			for(i = 0; i < sblen; i++)
			    st_readb(ft, (unsigned char *)&trash);
			}
			continue;	/* get next block */
		case VOC_LOOP:
		case VOC_LOOPEND:
			st_report("File %s: skipping repeat loop");
			for(i = 0; i < sblen; i++)
			    st_readb(ft, (unsigned char *)&trash);
			break;
		case VOC_EXTENDED:
			/* An Extended block is followed by a data block */
			/* Set this byte so we know to use the rate      */
			/* value from the extended block and not the     */
			/* data block.					 */
			v->extended = 1;
			st_readw(ft, &new_rate_short);
			if (new_rate_short == 0)
			{
			   st_fail("File %s: Sample rate is zero?");
			   return(ST_EOF);
			}
			if ((v->rate != -1) && (new_rate_short != v->rate))
			{
			   st_fail("File %s: sample rate codes differ: %d != %d",
					ft->filename, v->rate, new_rate_short);
			   return(ST_EOF);
			}
			v->rate = new_rate_short;
			st_readb(ft, &uc);
			if (uc != 0)
			{
				st_fail("File %s: only interpret 8-bit data!",
					ft->filename);
				return(ST_EOF);
			}
			st_readb(ft, &uc);
			if (uc)
				ft->info.channels = 2;  /* Stereo */
			/* Needed number of channels before finishing
			   compute for rate */
			ft->info.rate = (256000000L/(65536L - v->rate))/ft->info.channels;
			/* An extended block must be followed by a data */
			/* block to be valid so loop back to top so it  */
			/* can be grabed.				*/
			continue;
		default:
			st_report("File %s: skipping unknown block code %d",
				ft->filename, block);
			for(i = 0; i < sblen; i++)
			    st_readb(ft, (unsigned char *)&trash);
		}
	}
	return ST_SUCCESS;
}

/* Start an output block. */
static void blockstart(ft)
ft_t ft;
{
	vs_t v = (vs_t) ft->priv;

	v->blockseek = ftell(ft->fp);
	if (v->silent) {
		st_writeb(ft, VOC_SILENCE);	/* Silence block code */
		st_writeb(ft, 0);		/* Period length */
		st_writeb(ft, 0);		/* Period length */
		st_writeb(ft, v->rate);		/* Rate code */
	} else {
	  if (ft->info.size == ST_SIZE_BYTE) {
	    /* 8-bit sample section.  By always setting the correct     */
	    /* rate value in the DATA block (even when its preceeded    */
	    /* by an EXTENDED block) old software can still play stereo */
	    /* files in mono by just skipping over the EXTENDED block.  */
	    /* Prehaps the rate should be doubled though to make up for */
	    /* double amount of samples for a given time????            */
	    if (ft->info.channels > 1) {
	      st_writeb(ft, VOC_EXTENDED);	/* Voice Extended block code */
	      st_writeb(ft, 4);                /* block length = 4 */
	      st_writeb(ft, 0);                /* block length = 4 */
	      st_writeb(ft, 0);                /* block length = 4 */
		  v->rate = 65536L - (256000000.0/(2*(float)ft->info.rate));
	      st_writew(ft,v->rate);	/* Rate code */
	      st_writeb(ft, 0);         /* File is not packed */
	      st_writeb(ft, 1);         /* samples are in stereo */
	    }
	    st_writeb(ft, VOC_DATA);	/* Voice Data block code */
	    st_writeb(ft, 0);		/* block length (for now) */
	    st_writeb(ft, 0);		/* block length (for now) */
	    st_writeb(ft, 0);		/* block length (for now) */
	    v->rate = 256 - (1000000.0/(float)ft->info.rate);
	    st_writeb(ft, (int) v->rate);/* Rate code */
	    st_writeb(ft, 0);		/* 8-bit raw data */
	} else {
	    st_writeb(ft, VOC_DATA_16); /* Voice Data block code */
	    st_writeb(ft, 0);		/* block length (for now) */
	    st_writeb(ft, 0);		/* block length (for now) */
	    st_writeb(ft, 0);		/* block length (for now) */
	    v->rate = ft->info.rate;
	    st_writedw(ft, v->rate);	/* Rate code */
	    st_writeb(ft, 16);		/* Sample Size */
	    st_writeb(ft, ft->info.channels);	/* Sample Size */
	    st_writeb(ft, 0);		/* Unknown */
	    st_writeb(ft, 0);		/* Unused */
	    st_writeb(ft, 0);		/* Unused */
	    st_writeb(ft, 0);		/* Unused */
	    st_writeb(ft, 0);		/* Unused */
	    st_writeb(ft, 0);		/* Unused */
	  }
	}
}

/* End the current data or silence block. */
static void blockstop(ft) 
ft_t ft;
{
	vs_t v = (vs_t) ft->priv;
	LONG datum;

	st_writeb(ft, 0);			/* End of file block code */
	fseek(ft->fp, v->blockseek, 0);		/* seek back to block length */
	fseek(ft->fp, 1, 1);			/* seek forward one */
	if (v->silent) {
		st_writew(ft, v->samples);
	} else {
	  if (ft->info.size == ST_SIZE_BYTE) {
	    if (ft->info.channels > 1) {
	      fseek(ft->fp, 8, 1); /* forward 7 + 1 for new block header */
	    }
	  }
	        v->samples += 2;		/* adjustment: SBDK pp. 3-5 */
		datum = (v->samples) & 0xff;
		st_writeb(ft, (int)datum);       /* low byte of length */
		datum = (v->samples >> 8) & 0xff;
		st_writeb(ft, (int)datum);  /* middle byte of length */
		datum = (v->samples >> 16) & 0xff;
		st_writeb(ft, (int)datum); /* high byte of length */
	}
}