/* snd_wav.c - wav format load & save Copyright (C) 2010 Uncle Mike Copyright (C) 2023 FTEQW developers This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 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 General Public License for more details. */ #include #include "nanoquake.h" static const byte *iff_data; static const byte *iff_dataPtr; static const byte *iff_end; static const byte *iff_lastChunk; static int iff_chunkLen; /* ================= GetLittleShort ================= */ static short GetLittleShort( void ) { short val = 0; val += (*(iff_dataPtr+0) << 0); val += (*(iff_dataPtr+1) << 8); iff_dataPtr += 2; return val; } /* ================= GetLittleLong ================= */ static int GetLittleLong( void ) { int val = 0; val += (*(iff_dataPtr+0) << 0); val += (*(iff_dataPtr+1) << 8); val += (*(iff_dataPtr+2) <<16); val += (*(iff_dataPtr+3) <<24); iff_dataPtr += 4; return val; } /* ================= FindNextChunk ================= */ static void FindNextChunk( const char *filename, const char *name ) { while( 1 ) { ptrdiff_t remaining = iff_end - iff_lastChunk; if( remaining < 8 ) { iff_dataPtr = NULL; return; } iff_dataPtr = iff_lastChunk + 4; remaining -= 8; iff_chunkLen = GetLittleLong(); if( iff_chunkLen < 0 ) { iff_dataPtr = NULL; return; } if( iff_chunkLen > remaining ) { Con_DPrintf( "%s: '%s' truncated by %i bytes\n", __func__, filename, iff_chunkLen - remaining ); iff_chunkLen = remaining; } remaining -= iff_chunkLen; iff_dataPtr -= 8; iff_lastChunk = iff_dataPtr + 8 + iff_chunkLen; if ((iff_chunkLen&1) && remaining) iff_lastChunk++; if (!Q_strncmp(iff_dataPtr, name, 4)) return; } } /* ================= FindChunk ================= */ static void FindChunk( const char *filename, const char *name ) { iff_lastChunk = iff_data; FindNextChunk( filename, name ); } /* ============= Sound_LoadWAV ============= */ qboolean Sound_LoadWAV( const char *name, const byte *buffer, fs_offset_t filesize ) { int samples, fmt; if( !buffer || filesize <= 0 ) return false; iff_data = buffer; iff_end = buffer + filesize; // find "RIFF" chunk FindChunk( name, "RIFF" ); if( !( iff_dataPtr && !Q_strncmp( (const char *)iff_dataPtr + 8, "WAVE", 4 ))) { Con_DPrintf( S_ERROR "Sound_LoadWAV: %s missing 'RIFF/WAVE' chunks\n", name ); return false; } // get "fmt " chunk iff_data = iff_dataPtr + 12; FindChunk( name, "fmt " ); if( !iff_dataPtr ) { Con_DPrintf( S_ERROR "Sound_LoadWAV: %s missing 'fmt ' chunk\n", name ); return false; } iff_dataPtr += 8; fmt = GetLittleShort(); if( fmt != 1 ) { Con_DPrintf( S_ERROR "Sound_LoadWAV: %s not a microsoft PCM format\n", name ); return false; } sound.channels = GetLittleShort(); if( sound.channels != 1 && sound.channels != 2 ) { Con_DPrintf( S_ERROR "Sound_LoadWAV: only mono and stereo WAV files supported (%s)\n", name ); return false; } sound.rate = GetLittleLong(); iff_dataPtr += 6; sound.width = GetLittleShort() / 8; if( sound.width != 1 && sound.width != 2 ) { Con_DPrintf( S_ERROR "Sound_LoadWAV: only 8 and 16 bit WAV files supported (%s)\n", name ); return false; } // get cue chunk FindChunk( name, "cue " ); if( iff_dataPtr ) { iff_dataPtr += 32; sound.loopstart = GetLittleLong(); FindNextChunk( name, "LIST" ); // if the next chunk is a LIST chunk, look for a cue length marker if( iff_dataPtr ) { if( !Q_strncmp( (const char *)iff_dataPtr + 28, "mark", 4 )) { // this is not a proper parse, but it works with CoolEdit... iff_dataPtr += 24; sound.samples = sound.loopstart + GetLittleLong(); // samples in loop } } } else { sound.loopstart = -1; sound.samples = 0; } // find data chunk FindChunk( name, "data" ); if( !iff_dataPtr ) { Con_DPrintf( S_ERROR "Sound_LoadWAV: %s missing 'data' chunk\n", name ); return false; } iff_dataPtr += 4; samples = GetLittleLong() / sound.width; if( sound.samples ) { if( samples < sound.samples ) { Con_DPrintf( S_ERROR "Sound_LoadWAV: %s has a bad loop length\n", name ); return false; } } else sound.samples = samples; if( sound.samples <= 0 ) { Con_Reportf( S_ERROR "Sound_LoadWAV: file with %i samples (%s)\n", sound.samples, name ); return false; } sound.type = WF_PCMDATA; sound.samples /= sound.channels; // Load the data sound.size = sound.samples * sound.width * sound.channels; sound.wav = Mem_Malloc( host.soundpool, sound.size ); memcpy( sound.wav, buffer + (iff_dataPtr - buffer), sound.size ); // now convert 8-bit sounds to signed if( sound.width == 1 ) { int i, j; signed char *pData = (signed char *)sound.wav; for( i = 0; i < sound.samples; i++ ) { for( j = 0; j < sound.channels; j++ ) { *pData = (byte)((int)((byte)*pData) - 128 ); pData++; } } } return true; }