shithub: sox

ref: 03c73e6b0a38243191a3c20a896ca9b68c89a789
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 */
	int		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))

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

void vocstartread(ft) 
ft_t ft;
{
	char header[20];
	vs_t v = (vs_t) ft->priv;
	int sbseek;
	int littlendian = 1;
	char *endptr;

	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)
		fail("VOC input file must be a file, not a pipe");
	if (fread(header, 1, 20, ft->fp) != 20)
		fail("unexpected EOF in VOC header");
	if (strncmp(header, "Creative Voice File\032", 19))
		fail("VOC file header incorrect");

	sbseek = rshort(ft);
	fseek(ft->fp, sbseek, 0);

	v->rate = -1;
	v->rest = 0;
	v->extended = 0;
	getblock(ft);
	if (v->rate == -1)
		fail("Input .voc file had no sound!");

	ft->info.size = v->size;
	ft->info.style = UNSIGNED;
	if (v->size == WORD)
	    ft->info.style = SIGN2;
	if (ft->info.channels == -1)
		ft->info.channels = v->channels;
}

LONG vocread(ft, buf, len) 
ft_t ft;
LONG *buf, len;
{
	vs_t v = (vs_t) ft->priv;
	int done = 0;
	
	if (v->rest == 0)
		getblock(ft);
	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++) {
			LONG datum;
			switch(v->size)
			{
			    case BYTE:
				if ((datum = getc(ft->fp)) == EOF) {
				    warn("VOC input: short file");
				    v->rest = 0;
				    return done;
				}
				datum ^= 0x80;	/* convert to signed */
				*buf++ = LEFT(datum, 24);
				break;
			    case WORD:
				datum = rshort(ft);
				if (feof(ft->fp))
				{
				    warn("VOC input: short file");
				    v->rest = 0;
				    return done;
				}
				*buf++ = LEFT(datum, 16);
				v->rest--; /* Processed 2 bytes so update */
				break;
			}	
		}
	}
	return done;
}

/* nothing to do */
void vocstopread(ft) 
ft_t ft;
{
}

/* 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.)
 */
void 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)
		fail("Output .voc file must be a file, not a pipe");

	v->samples = 0;

	/* File format name and a ^Z (aborts printing under DOS) */
	(void) fwrite("Creative Voice File\032\032", 1, 20, ft->fp);
	wshort(ft, 26);			/* size of header */
	wshort(ft, 0x10a);              /* major/minor version number */
	wshort(ft, 0x1129);		/* checksum of version number */

	if (ft->info.size == BYTE)
	  ft->info.style = UNSIGNED;
	else
	  ft->info.style = SIGN2;
	if (ft->info.channels == -1)
		ft->info.channels = 1;
}

void vocwrite(ft, buf, len) 
ft_t ft;
LONG *buf, len;
{
	vs_t v = (vs_t) ft->priv;
	unsigned char uc;
	int sw;
	
	if (v->samples == 0) {
	  /* No silence packing yet. */
	  v->silent = 0;
	  blockstart(ft);
	}
	v->samples += len;
	while(len--) {
	  if (ft->info.size == BYTE) {
	    uc = RIGHT(*buf++, 24);
	    uc ^= 0x80;
	    putc(uc, ft->fp);
	  } else {
		sw = (int) RIGHT(*buf++, 16);
	    wshort(ft,sw);
          }
	}
}

void vocstopwrite(ft) 
ft_t ft;
{
	blockstop(ft);
}

/* Voc-file handlers */

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

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

			period = rshort(ft);
			uc = getc(ft->fp);
			if (uc == 0)
				fail("File %s: Silence sample rate is zero");
			/* 
			 * 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;
			}
		case VOC_MARKER:
			uc = getc(ft->fp);
			uc = getc(ft->fp);
			/* Falling! Falling! */
		case VOC_TEXT:
			{
			int i;
			/* Could add to comment in SF? */
			for(i = 0; i < sblen; i++)
				getc(ft->fp);
			}
			continue;	/* get next block */
		case VOC_LOOP:
		case VOC_LOOPEND:
			report("File %s: skipping repeat loop");
			for(i = 0; i < sblen; i++)
				getc(ft->fp);
			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;
			new_rate = rshort(ft);
			if (new_rate == 0)
			   fail("File %s: Sample rate is zero?");
			if ((v->rate != -1) && (new_rate != v->rate))
			   fail("File %s: sample rate codes differ: %d != %d",
					ft->filename, v->rate, new_rate);
			v->rate = new_rate;
			uc = getc(ft->fp);
			if (uc != 0)
				fail("File %s: only interpret 8-bit data!",
					ft->filename);
			uc = getc(ft->fp);
			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:
			report("File %s: skipping unknown block code %d",
				ft->filename, block);
			for(i = 0; i < sblen; i++)
				getc(ft->fp);
		}
	}
}

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

	v->blockseek = ftell(ft->fp);
	if (v->silent) {
		putc(VOC_SILENCE, ft->fp);	/* Silence block code */
		putc(0, ft->fp);		/* Period length */
		putc(0, ft->fp);		/* Period length */
		putc((int) v->rate, ft->fp);		/* Rate code */
	} else {
	  if (ft->info.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) {
	      putc(VOC_EXTENDED, ft->fp);	/* Voice Extended block code */
	      putc(4, ft->fp);                /* block length = 4 */
	      putc(0, ft->fp);                /* block length = 4 */
	      putc(0, ft->fp);                /* block length = 4 */
		  v->rate = 65536L - (256000000.0/(2*(float)ft->info.rate));
	      wshort(ft,v->rate);	/* Rate code */
	      putc(0, ft->fp);                /* File is not packed */
	      putc(1, ft->fp);                /* samples are in stereo */
	    }
	    putc(VOC_DATA, ft->fp);		/* Voice Data block code */
	    putc(0, ft->fp);		/* block length (for now) */
	    putc(0, ft->fp);		/* block length (for now) */
	    putc(0, ft->fp);		/* block length (for now) */
	    v->rate = 256 - (1000000.0/(float)ft->info.rate);
	    putc((int) v->rate, ft->fp);/* Rate code */
	    putc(0, ft->fp);		/* 8-bit raw data */
	} else {
	    putc(VOC_DATA_16, ft->fp);		/* Voice Data block code */
	    putc(0, ft->fp);		/* block length (for now) */
	    putc(0, ft->fp);		/* block length (for now) */
	    putc(0, ft->fp);		/* block length (for now) */
	    v->rate = ft->info.rate;
	    wlong(ft, v->rate);	/* Rate code */
	    putc(16, ft->fp);		/* Sample Size */
	    putc(ft->info.channels, ft->fp);	/* Sample Size */
	    putc(0, ft->fp);		/* Unknown */
	    putc(0, ft->fp);		/* Unused */
	    putc(0, ft->fp);		/* Unused */
	    putc(0, ft->fp);		/* Unused */
	    putc(0, ft->fp);		/* Unused */
	    putc(0, ft->fp);		/* Unused */
	  }
	}
}

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

	putc(0, ft->fp);			/* 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) {
		wshort(ft, v->samples);
	} else {
	  if (ft->info.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;
		putc((int)datum, ft->fp);       /* low byte of length */
		datum = (v->samples >> 8) & 0xff;
		putc((int)datum, ft->fp);  /* middle byte of length */
		datum = (v->samples >> 16) & 0xff;
		putc((int)datum, ft->fp); /* high byte of length */
	}
}