slouken@111: /* slouken@518: SDL_mixer: An audio mixer library based on the SDL library slouken@601: Copyright (C) 1997-2013 Sam Lantinga slouken@111: slouken@518: This software is provided 'as-is', without any express or implied slouken@518: warranty. In no event will the authors be held liable for any damages slouken@518: arising from the use of this software. slouken@111: slouken@518: Permission is granted to anyone to use this software for any purpose, slouken@518: including commercial applications, and to alter it and redistribute it slouken@518: freely, subject to the following restrictions: slouken@111: slouken@518: 1. The origin of this software must not be misrepresented; you must not slouken@518: claim that you wrote the original software. If you use this software slouken@518: in a product, an acknowledgment in the product documentation would be slouken@518: appreciated but is not required. slouken@518: 2. Altered source versions must be plainly marked as such, and must not be slouken@518: misrepresented as being the original software. slouken@518: 3. This notice may not be removed or altered from any source distribution. slouken@111: slouken@518: This is the source needed to decode a Creative Labs VOC file into a slouken@518: waveform. It's pretty straightforward once you get going. The only slouken@518: externally-callable function is Mix_LoadVOC_RW(), which is meant to slouken@518: act as identically to SDL_LoadWAV_RW() as possible. slouken@111: slouken@518: This file by Ryan C. Gordon (icculus@icculus.org). slouken@111: slouken@518: Heavily borrowed from sox v12.17.1's voc.c. slouken@111: (http://www.freshmeat.net/projects/sox/) slouken@111: */ slouken@111: slouken@111: /* $Id$ */ slouken@111: slouken@111: #include slouken@111: #include slouken@111: #include slouken@111: slouken@111: #include "SDL_mutex.h" slouken@111: #include "SDL_endian.h" slouken@111: #include "SDL_timer.h" slouken@111: slouken@111: #include "SDL_mixer.h" slouken@111: #include "load_voc.h" slouken@111: slouken@111: /* Private data for VOC file */ slouken@111: typedef struct vocstuff { slouken@111: Uint32 rest; /* bytes remaining in current block */ slouken@111: Uint32 rate; /* rate code (byte) of this chunk */ slouken@111: int silent; /* sound or silence? */ slouken@111: Uint32 srate; /* rate code (byte) of silence */ slouken@111: Uint32 blockseek; /* start of current output block */ slouken@111: Uint32 samples; /* number of samples output */ slouken@111: Uint32 size; /* word length of data */ slouken@116: Uint8 channels; /* number of sound channels */ slouken@144: int has_extended; /* Has an extended block been read? */ slouken@111: } vs_t; slouken@111: slouken@111: /* Size field */ slouken@111: /* SJB: note that the 1st 3 are sometimes used as sizeof(type) */ slouken@111: #define ST_SIZE_BYTE 1 slouken@111: #define ST_SIZE_8BIT 1 slouken@111: #define ST_SIZE_WORD 2 slouken@111: #define ST_SIZE_16BIT 2 slouken@111: #define ST_SIZE_DWORD 4 slouken@111: #define ST_SIZE_32BIT 4 slouken@111: #define ST_SIZE_FLOAT 5 slouken@111: #define ST_SIZE_DOUBLE 6 slouken@111: #define ST_SIZE_IEEE 7 /* IEEE 80-bit floats. */ slouken@111: slouken@111: /* Style field */ slouken@111: #define ST_ENCODING_UNSIGNED 1 /* unsigned linear: Sound Blaster */ slouken@111: #define ST_ENCODING_SIGN2 2 /* signed linear 2's comp: Mac */ slouken@111: #define ST_ENCODING_ULAW 3 /* U-law signed logs: US telephony, SPARC */ slouken@111: #define ST_ENCODING_ALAW 4 /* A-law signed logs: non-US telephony */ slouken@111: #define ST_ENCODING_ADPCM 5 /* Compressed PCM */ slouken@111: #define ST_ENCODING_IMA_ADPCM 6 /* Compressed PCM */ slouken@111: #define ST_ENCODING_GSM 7 /* GSM 6.10 33-byte frame lossy compression */ slouken@111: slouken@111: #define VOC_TERM 0 slouken@111: #define VOC_DATA 1 slouken@111: #define VOC_CONT 2 slouken@111: #define VOC_SILENCE 3 slouken@111: #define VOC_MARKER 4 slouken@111: #define VOC_TEXT 5 slouken@111: #define VOC_LOOP 6 slouken@111: #define VOC_LOOPEND 7 slouken@111: #define VOC_EXTENDED 8 slouken@111: #define VOC_DATA_16 9 slouken@111: slouken@111: slouken@114: static int voc_check_header(SDL_RWops *src) slouken@111: { slouken@111: /* VOC magic header */ slouken@111: Uint8 signature[20]; /* "Creative Voice File\032" */ slouken@111: Uint16 datablockofs; slouken@111: slouken@473: SDL_RWseek(src, 0, RW_SEEK_SET); slouken@111: slouken@111: if (SDL_RWread(src, signature, sizeof (signature), 1) != 1) slouken@111: return(0); slouken@111: slouken@111: if (memcmp(signature, "Creative Voice File\032", sizeof (signature)) != 0) { slouken@111: SDL_SetError("Unrecognized file type (not VOC)"); slouken@111: return(0); slouken@111: } slouken@111: slouken@111: /* get the offset where the first datablock is located */ slouken@111: if (SDL_RWread(src, &datablockofs, sizeof (Uint16), 1) != 1) slouken@111: return(0); slouken@111: slouken@111: datablockofs = SDL_SwapLE16(datablockofs); slouken@111: slouken@473: if (SDL_RWseek(src, datablockofs, RW_SEEK_SET) != datablockofs) slouken@111: return(0); slouken@111: slouken@111: return(1); /* success! */ slouken@111: } /* voc_check_header */ slouken@111: slouken@111: slouken@111: /* Read next block header, save info, leave position at start of data */ slouken@111: static int voc_get_block(SDL_RWops *src, vs_t *v, SDL_AudioSpec *spec) slouken@111: { slouken@111: Uint8 bits24[3]; slouken@111: Uint8 uc, block; slouken@111: Uint32 sblen; slouken@111: Uint16 new_rate_short; slouken@111: Uint32 new_rate_long; slouken@111: Uint8 trash[6]; slouken@111: Uint16 period; slouken@114: unsigned int i; slouken@111: slouken@111: v->silent = 0; slouken@111: while (v->rest == 0) slouken@111: { slouken@111: if (SDL_RWread(src, &block, sizeof (block), 1) != 1) slouken@111: return 1; /* assume that's the end of the file. */ slouken@111: slouken@111: if (block == VOC_TERM) slouken@111: return 1; slouken@111: slouken@111: if (SDL_RWread(src, bits24, sizeof (bits24), 1) != 1) slouken@111: return 1; /* assume that's the end of the file. */ slouken@111: slouken@111: /* Size is an 24-bit value. Ugh. */ slouken@111: sblen = ( (bits24[0]) | (bits24[1] << 8) | (bits24[2] << 16) ); slouken@111: slouken@111: switch(block) slouken@111: { slouken@111: case VOC_DATA: slouken@111: if (SDL_RWread(src, &uc, sizeof (uc), 1) != 1) slouken@111: return 0; slouken@111: slouken@111: /* When DATA block preceeded by an EXTENDED */ slouken@111: /* block, the DATA blocks rate value is invalid */ slouken@144: if (!v->has_extended) slouken@111: { slouken@111: if (uc == 0) slouken@111: { slouken@111: SDL_SetError("VOC Sample rate is zero?"); slouken@111: return 0; slouken@111: } slouken@111: slouken@111: if ((v->rate != -1) && (uc != v->rate)) slouken@111: { slouken@111: SDL_SetError("VOC sample rate codes differ"); slouken@111: return 0; slouken@111: } slouken@111: slouken@111: v->rate = uc; slouken@114: spec->freq = (Uint16)(1000000.0/(256 - v->rate)); slouken@111: v->channels = 1; slouken@111: } slouken@111: slouken@111: if (SDL_RWread(src, &uc, sizeof (uc), 1) != 1) slouken@111: return 0; slouken@111: slouken@111: if (uc != 0) slouken@111: { slouken@111: SDL_SetError("VOC decoder only interprets 8-bit data"); slouken@111: return 0; slouken@111: } slouken@111: slouken@144: v->has_extended = 0; slouken@111: v->rest = sblen - 2; slouken@111: v->size = ST_SIZE_BYTE; slouken@111: return 1; slouken@111: slouken@111: case VOC_DATA_16: slouken@111: if (SDL_RWread(src, &new_rate_long, sizeof (new_rate_long), 1) != 1) slouken@111: return 0; slouken@111: new_rate_long = SDL_SwapLE32(new_rate_long); slouken@111: if (new_rate_long == 0) slouken@111: { slouken@111: SDL_SetError("VOC Sample rate is zero?"); slouken@111: return 0; slouken@111: } slouken@111: if ((v->rate != -1) && (new_rate_long != v->rate)) slouken@111: { slouken@111: SDL_SetError("VOC sample rate codes differ"); slouken@111: return 0; slouken@111: } slouken@111: v->rate = new_rate_long; slouken@111: spec->freq = new_rate_long; slouken@111: slouken@111: if (SDL_RWread(src, &uc, sizeof (uc), 1) != 1) slouken@111: return 0; slouken@111: slouken@111: switch (uc) slouken@111: { slouken@111: case 8: v->size = ST_SIZE_BYTE; break; slouken@111: case 16: v->size = ST_SIZE_WORD; break; slouken@111: default: slouken@111: SDL_SetError("VOC with unknown data size"); slouken@111: return 0; slouken@111: } slouken@111: slouken@111: if (SDL_RWread(src, &v->channels, sizeof (Uint8), 1) != 1) slouken@111: return 0; slouken@111: slouken@111: if (SDL_RWread(src, trash, sizeof (Uint8), 6) != 6) slouken@111: return 0; slouken@111: slouken@111: v->rest = sblen - 12; slouken@111: return 1; slouken@111: slouken@111: case VOC_CONT: slouken@111: v->rest = sblen; slouken@111: return 1; slouken@111: slouken@111: case VOC_SILENCE: slouken@111: if (SDL_RWread(src, &period, sizeof (period), 1) != 1) slouken@111: return 0; slouken@111: period = SDL_SwapLE16(period); slouken@111: slouken@111: if (SDL_RWread(src, &uc, sizeof (uc), 1) != 1) slouken@111: return 0; slouken@111: if (uc == 0) slouken@111: { slouken@111: SDL_SetError("VOC silence sample rate is zero"); slouken@111: return 0; slouken@111: } slouken@111: slouken@111: /* slouken@111: * Some silence-packed files have gratuitously slouken@111: * different sample rate codes in silence. slouken@111: * Adjust period. slouken@111: */ slouken@111: if ((v->rate != -1) && (uc != v->rate)) slouken@310: period = (Uint16)((period * (256 - uc))/(256 - v->rate)); slouken@111: else slouken@111: v->rate = uc; slouken@111: v->rest = period; slouken@111: v->silent = 1; slouken@111: return 1; slouken@111: slouken@111: case VOC_LOOP: slouken@111: case VOC_LOOPEND: slouken@111: for(i = 0; i < sblen; i++) /* skip repeat loops. */ slouken@111: { slouken@111: if (SDL_RWread(src, trash, sizeof (Uint8), 1) != 1) slouken@111: return 0; slouken@111: } slouken@111: break; slouken@111: slouken@111: case VOC_EXTENDED: slouken@111: /* An Extended block is followed by a data block */ slouken@111: /* Set this byte so we know to use the rate */ slouken@111: /* value from the extended block and not the */ slouken@111: /* data block. */ slouken@144: v->has_extended = 1; slouken@111: if (SDL_RWread(src, &new_rate_short, sizeof (new_rate_short), 1) != 1) slouken@111: return 0; slouken@111: new_rate_short = SDL_SwapLE16(new_rate_short); slouken@111: if (new_rate_short == 0) slouken@111: { slouken@111: SDL_SetError("VOC sample rate is zero"); slouken@111: return 0; slouken@111: } slouken@111: if ((v->rate != -1) && (new_rate_short != v->rate)) slouken@111: { slouken@111: SDL_SetError("VOC sample rate codes differ"); slouken@111: return 0; slouken@111: } slouken@111: v->rate = new_rate_short; slouken@111: slouken@111: if (SDL_RWread(src, &uc, sizeof (uc), 1) != 1) slouken@111: return 0; slouken@111: slouken@111: if (uc != 0) slouken@111: { slouken@111: SDL_SetError("VOC decoder only interprets 8-bit data"); slouken@111: return 0; slouken@111: } slouken@111: slouken@111: if (SDL_RWread(src, &uc, sizeof (uc), 1) != 1) slouken@111: return 0; slouken@111: slouken@111: if (uc) slouken@111: spec->channels = 2; /* Stereo */ slouken@111: /* Needed number of channels before finishing slouken@111: compute for rate */ slouken@111: spec->freq = (256000000L/(65536L - v->rate))/spec->channels; slouken@111: /* An extended block must be followed by a data */ slouken@111: /* block to be valid so loop back to top so it */ slouken@111: /* can be grabed. */ slouken@111: continue; slouken@111: slouken@111: case VOC_MARKER: slouken@111: if (SDL_RWread(src, trash, sizeof (Uint8), 2) != 2) slouken@111: return 0; slouken@111: slouken@111: /* Falling! Falling! */ slouken@111: slouken@111: default: /* text block or other krapola. */ slouken@111: for(i = 0; i < sblen; i++) slouken@111: { slouken@111: if (SDL_RWread(src, &trash, sizeof (Uint8), 1) != 1) slouken@111: return 0; slouken@111: } slouken@111: slouken@111: if (block == VOC_TEXT) slouken@111: continue; /* get next block */ slouken@111: } slouken@111: } slouken@111: slouken@111: return 1; slouken@111: } slouken@111: slouken@111: slouken@111: static int voc_read(SDL_RWops *src, vs_t *v, Uint8 *buf, SDL_AudioSpec *spec) slouken@111: { slouken@111: int done = 0; slouken@111: Uint8 silence = 0x80; slouken@111: slouken@111: if (v->rest == 0) slouken@111: { slouken@111: if (!voc_get_block(src, v, spec)) slouken@111: return 0; slouken@111: } slouken@111: slouken@111: if (v->rest == 0) slouken@111: return 0; slouken@111: slouken@111: if (v->silent) slouken@111: { slouken@111: if (v->size == ST_SIZE_WORD) slouken@111: silence = 0x00; slouken@111: slouken@111: /* Fill in silence */ slouken@111: memset(buf, silence, v->rest); slouken@111: done = v->rest; slouken@111: v->rest = 0; slouken@111: } slouken@111: slouken@111: else slouken@111: { slouken@111: done = SDL_RWread(src, buf, 1, v->rest); slouken@111: v->rest -= done; slouken@111: if (v->size == ST_SIZE_WORD) slouken@111: { slouken@111: #if (SDL_BYTEORDER == SDL_BIG_ENDIAN) slouken@180: Uint16 *samples = (Uint16 *)buf; slouken@111: for (; v->rest > 0; v->rest -= 2) slouken@111: { slouken@180: *samples = SDL_SwapLE16(*samples); slouken@180: samples++; slouken@111: } slouken@111: #endif slouken@180: done >>= 1; slouken@111: } slouken@111: } slouken@111: slouken@111: return done; slouken@111: } /* voc_read */ slouken@111: slouken@111: slouken@111: /* don't call this directly; use Mix_LoadWAV_RW() for now. */ slouken@111: SDL_AudioSpec *Mix_LoadVOC_RW (SDL_RWops *src, int freesrc, slouken@111: SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len) slouken@111: { slouken@111: vs_t v; slouken@111: int was_error = 1; slouken@111: int samplesize; slouken@111: Uint8 *fillptr; slouken@111: void *ptr; slouken@111: slouken@111: if ( (!src) || (!audio_buf) || (!audio_len) ) /* sanity checks. */ slouken@111: goto done; slouken@111: slouken@111: if ( !voc_check_header(src) ) slouken@111: goto done; slouken@111: slouken@111: v.rate = -1; slouken@111: v.rest = 0; slouken@144: v.has_extended = 0; slouken@111: *audio_buf = NULL; slouken@111: *audio_len = 0; slouken@111: memset(spec, '\0', sizeof (SDL_AudioSpec)); slouken@111: slouken@111: if (!voc_get_block(src, &v, spec)) slouken@111: goto done; slouken@111: slouken@111: if (v.rate == -1) slouken@111: { slouken@111: SDL_SetError("VOC data had no sound!"); slouken@111: goto done; slouken@111: } slouken@111: slouken@111: spec->format = ((v.size == ST_SIZE_WORD) ? AUDIO_S16 : AUDIO_U8); slouken@111: if (spec->channels == 0) slouken@111: spec->channels = v.channels; slouken@111: slouken@111: *audio_len = v.rest; slouken@561: *audio_buf = SDL_malloc(v.rest); slouken@111: if (*audio_buf == NULL) slouken@111: goto done; slouken@111: slouken@111: fillptr = *audio_buf; slouken@111: slouken@111: while (voc_read(src, &v, fillptr, spec) > 0) slouken@111: { slouken@111: if (!voc_get_block(src, &v, spec)) slouken@111: goto done; slouken@111: slouken@111: *audio_len += v.rest; slouken@561: ptr = SDL_realloc(*audio_buf, *audio_len); slouken@111: if (ptr == NULL) slouken@111: { slouken@561: SDL_free(*audio_buf); slouken@111: *audio_buf = NULL; slouken@111: *audio_len = 0; slouken@111: goto done; slouken@111: } slouken@111: slouken@111: *audio_buf = ptr; slouken@111: fillptr = ((Uint8 *) ptr) + (*audio_len - v.rest); slouken@111: } slouken@111: slouken@310: spec->samples = (Uint16)(*audio_len / v.size); slouken@111: slouken@111: was_error = 0; /* success, baby! */ slouken@111: slouken@111: /* Don't return a buffer that isn't a multiple of samplesize */ slouken@111: samplesize = ((spec->format & 0xFF)/8)*spec->channels; slouken@111: *audio_len &= ~(samplesize-1); slouken@111: slouken@111: done: slouken@111: if (src) slouken@111: { slouken@111: if (freesrc) slouken@111: SDL_RWclose(src); slouken@111: else slouken@473: SDL_RWseek(src, 0, RW_SEEK_SET); slouken@111: } slouken@111: slouken@111: if ( was_error ) slouken@111: spec = NULL; slouken@111: slouken@111: return(spec); slouken@111: } /* Mix_LoadVOC_RW */ slouken@111: slouken@111: /* end of load_voc.c ... */