/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2009 Sam Lantinga This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This is the source needed to decode a Creative Labs VOC file into a waveform. It's pretty straightforward once you get going. The only externally-callable function is Mix_LoadVOC_RW(), which is meant to act as identically to SDL_LoadWAV_RW() as possible. This file by Ryan C. Gordon (icculus@icculus.org). Heavily borrowed from sox v12.17.1's voc.c. (http://www.freshmeat.net/projects/sox/) */ /* $Id$ */ #include #include #include #include "SDL_mutex.h" #include "SDL_endian.h" #include "SDL_timer.h" #include "SDL_mixer.h" #include "load_voc.h" /* Private data for VOC file */ typedef struct vocstuff { Uint32 rest; /* bytes remaining in current block */ Uint32 rate; /* rate code (byte) of this chunk */ int silent; /* sound or silence? */ Uint32 srate; /* rate code (byte) of silence */ Uint32 blockseek; /* start of current output block */ Uint32 samples; /* number of samples output */ Uint32 size; /* word length of data */ Uint8 channels; /* number of sound channels */ int has_extended; /* Has an extended block been read? */ } vs_t; /* Size field */ /* SJB: note that the 1st 3 are sometimes used as sizeof(type) */ #define ST_SIZE_BYTE 1 #define ST_SIZE_8BIT 1 #define ST_SIZE_WORD 2 #define ST_SIZE_16BIT 2 #define ST_SIZE_DWORD 4 #define ST_SIZE_32BIT 4 #define ST_SIZE_FLOAT 5 #define ST_SIZE_DOUBLE 6 #define ST_SIZE_IEEE 7 /* IEEE 80-bit floats. */ /* Style field */ #define ST_ENCODING_UNSIGNED 1 /* unsigned linear: Sound Blaster */ #define ST_ENCODING_SIGN2 2 /* signed linear 2's comp: Mac */ #define ST_ENCODING_ULAW 3 /* U-law signed logs: US telephony, SPARC */ #define ST_ENCODING_ALAW 4 /* A-law signed logs: non-US telephony */ #define ST_ENCODING_ADPCM 5 /* Compressed PCM */ #define ST_ENCODING_IMA_ADPCM 6 /* Compressed PCM */ #define ST_ENCODING_GSM 7 /* GSM 6.10 33-byte frame lossy compression */ #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 static int voc_check_header(SDL_RWops *src) { /* VOC magic header */ Uint8 signature[20]; /* "Creative Voice File\032" */ Uint16 datablockofs; SDL_RWseek(src, 0, RW_SEEK_SET); if (SDL_RWread(src, signature, sizeof (signature), 1) != 1) return(0); if (memcmp(signature, "Creative Voice File\032", sizeof (signature)) != 0) { SDL_SetError("Unrecognized file type (not VOC)"); return(0); } /* get the offset where the first datablock is located */ if (SDL_RWread(src, &datablockofs, sizeof (Uint16), 1) != 1) return(0); datablockofs = SDL_SwapLE16(datablockofs); if (SDL_RWseek(src, datablockofs, RW_SEEK_SET) != datablockofs) return(0); return(1); /* success! */ } /* voc_check_header */ /* Read next block header, save info, leave position at start of data */ static int voc_get_block(SDL_RWops *src, vs_t *v, SDL_AudioSpec *spec) { Uint8 bits24[3]; Uint8 uc, block; Uint32 sblen; Uint16 new_rate_short; Uint32 new_rate_long; Uint8 trash[6]; Uint16 period; unsigned int i; v->silent = 0; while (v->rest == 0) { if (SDL_RWread(src, &block, sizeof (block), 1) != 1) return 1; /* assume that's the end of the file. */ if (block == VOC_TERM) return 1; if (SDL_RWread(src, bits24, sizeof (bits24), 1) != 1) return 1; /* assume that's the end of the file. */ /* Size is an 24-bit value. Ugh. */ sblen = ( (bits24[0]) | (bits24[1] << 8) | (bits24[2] << 16) ); switch(block) { case VOC_DATA: if (SDL_RWread(src, &uc, sizeof (uc), 1) != 1) return 0; /* When DATA block preceeded by an EXTENDED */ /* block, the DATA blocks rate value is invalid */ if (!v->has_extended) { if (uc == 0) { SDL_SetError("VOC Sample rate is zero?"); return 0; } if ((v->rate != -1) && (uc != v->rate)) { SDL_SetError("VOC sample rate codes differ"); return 0; } v->rate = uc; spec->freq = (Uint16)(1000000.0/(256 - v->rate)); v->channels = 1; } if (SDL_RWread(src, &uc, sizeof (uc), 1) != 1) return 0; if (uc != 0) { SDL_SetError("VOC decoder only interprets 8-bit data"); return 0; } v->has_extended = 0; v->rest = sblen - 2; v->size = ST_SIZE_BYTE; return 1; case VOC_DATA_16: if (SDL_RWread(src, &new_rate_long, sizeof (new_rate_long), 1) != 1) return 0; new_rate_long = SDL_SwapLE32(new_rate_long); if (new_rate_long == 0) { SDL_SetError("VOC Sample rate is zero?"); return 0; } if ((v->rate != -1) && (new_rate_long != v->rate)) { SDL_SetError("VOC sample rate codes differ"); return 0; } v->rate = new_rate_long; spec->freq = new_rate_long; if (SDL_RWread(src, &uc, sizeof (uc), 1) != 1) return 0; switch (uc) { case 8: v->size = ST_SIZE_BYTE; break; case 16: v->size = ST_SIZE_WORD; break; default: SDL_SetError("VOC with unknown data size"); return 0; } if (SDL_RWread(src, &v->channels, sizeof (Uint8), 1) != 1) return 0; if (SDL_RWread(src, trash, sizeof (Uint8), 6) != 6) return 0; v->rest = sblen - 12; return 1; case VOC_CONT: v->rest = sblen; return 1; case VOC_SILENCE: if (SDL_RWread(src, &period, sizeof (period), 1) != 1) return 0; period = SDL_SwapLE16(period); if (SDL_RWread(src, &uc, sizeof (uc), 1) != 1) return 0; if (uc == 0) { SDL_SetError("VOC silence sample rate is zero"); return 0; } /* * Some silence-packed files have gratuitously * different sample rate codes in silence. * Adjust period. */ if ((v->rate != -1) && (uc != v->rate)) period = (Uint16)((period * (256 - uc))/(256 - v->rate)); else v->rate = uc; v->rest = period; v->silent = 1; return 1; case VOC_LOOP: case VOC_LOOPEND: for(i = 0; i < sblen; i++) /* skip repeat loops. */ { if (SDL_RWread(src, trash, sizeof (Uint8), 1) != 1) return 0; } 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->has_extended = 1; if (SDL_RWread(src, &new_rate_short, sizeof (new_rate_short), 1) != 1) return 0; new_rate_short = SDL_SwapLE16(new_rate_short); if (new_rate_short == 0) { SDL_SetError("VOC sample rate is zero"); return 0; } if ((v->rate != -1) && (new_rate_short != v->rate)) { SDL_SetError("VOC sample rate codes differ"); return 0; } v->rate = new_rate_short; if (SDL_RWread(src, &uc, sizeof (uc), 1) != 1) return 0; if (uc != 0) { SDL_SetError("VOC decoder only interprets 8-bit data"); return 0; } if (SDL_RWread(src, &uc, sizeof (uc), 1) != 1) return 0; if (uc) spec->channels = 2; /* Stereo */ /* Needed number of channels before finishing compute for rate */ spec->freq = (256000000L/(65536L - v->rate))/spec->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; case VOC_MARKER: if (SDL_RWread(src, trash, sizeof (Uint8), 2) != 2) return 0; /* Falling! Falling! */ default: /* text block or other krapola. */ for(i = 0; i < sblen; i++) { if (SDL_RWread(src, &trash, sizeof (Uint8), 1) != 1) return 0; } if (block == VOC_TEXT) continue; /* get next block */ } } return 1; } static int voc_read(SDL_RWops *src, vs_t *v, Uint8 *buf, SDL_AudioSpec *spec) { int done = 0; Uint8 silence = 0x80; if (v->rest == 0) { if (!voc_get_block(src, v, spec)) return 0; } if (v->rest == 0) return 0; if (v->silent) { if (v->size == ST_SIZE_WORD) silence = 0x00; /* Fill in silence */ memset(buf, silence, v->rest); done = v->rest; v->rest = 0; } else { done = SDL_RWread(src, buf, 1, v->rest); v->rest -= done; if (v->size == ST_SIZE_WORD) { #if (SDL_BYTEORDER == SDL_BIG_ENDIAN) Uint16 *samples = (Uint16 *)buf; for (; v->rest > 0; v->rest -= 2) { *samples = SDL_SwapLE16(*samples); samples++; } #endif done >>= 1; } } return done; } /* voc_read */ /* don't call this directly; use Mix_LoadWAV_RW() for now. */ SDL_AudioSpec *Mix_LoadVOC_RW (SDL_RWops *src, int freesrc, SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len) { vs_t v; int was_error = 1; int samplesize; Uint8 *fillptr; void *ptr; if ( (!src) || (!audio_buf) || (!audio_len) ) /* sanity checks. */ goto done; if ( !voc_check_header(src) ) goto done; v.rate = -1; v.rest = 0; v.has_extended = 0; *audio_buf = NULL; *audio_len = 0; memset(spec, '\0', sizeof (SDL_AudioSpec)); if (!voc_get_block(src, &v, spec)) goto done; if (v.rate == -1) { SDL_SetError("VOC data had no sound!"); goto done; } spec->format = ((v.size == ST_SIZE_WORD) ? AUDIO_S16 : AUDIO_U8); if (spec->channels == 0) spec->channels = v.channels; *audio_len = v.rest; *audio_buf = malloc(v.rest); if (*audio_buf == NULL) goto done; fillptr = *audio_buf; while (voc_read(src, &v, fillptr, spec) > 0) { if (!voc_get_block(src, &v, spec)) goto done; *audio_len += v.rest; ptr = realloc(*audio_buf, *audio_len); if (ptr == NULL) { free(*audio_buf); *audio_buf = NULL; *audio_len = 0; goto done; } *audio_buf = ptr; fillptr = ((Uint8 *) ptr) + (*audio_len - v.rest); } spec->samples = (Uint16)(*audio_len / v.size); was_error = 0; /* success, baby! */ /* Don't return a buffer that isn't a multiple of samplesize */ samplesize = ((spec->format & 0xFF)/8)*spec->channels; *audio_len &= ~(samplesize-1); done: if (src) { if (freesrc) SDL_RWclose(src); else SDL_RWseek(src, 0, RW_SEEK_SET); } if ( was_error ) spec = NULL; return(spec); } /* Mix_LoadVOC_RW */ /* end of load_voc.c ... */