X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=src%2Faudio%2Fsound_file.cpp;h=834aedaf47f8d073268b77e564c4204e14533333;hb=0ebb40343bafd9f8b047c1917c91ab3611ded965;hp=f77601f99ae69aefd0900f91326fa4666837e5d5;hpb=089eb04e173803748ced0f13363e8d55c7c432e2;p=supertux.git diff --git a/src/audio/sound_file.cpp b/src/audio/sound_file.cpp index f77601f99..834aedaf4 100644 --- a/src/audio/sound_file.cpp +++ b/src/audio/sound_file.cpp @@ -1,12 +1,10 @@ -// $Id$ -// // SuperTux // Copyright (C) 2006 Matthias Braun // -// 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 2 -// of the License, or (at your option) any later version. +// 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 @@ -14,355 +12,33 @@ // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// along with this program. If not, see . /** Used SDL_mixer and glest source as reference */ -#include -#include "sound_file.hpp" +#include "audio/sound_file.hpp" + +#include -#include #include -#include -#include #include -#include #include -#include -#include -#include "log.hpp" -#include "lisp/parser.hpp" -#include "lisp/lisp.hpp" -#include "file_system.hpp" - -class WavSoundFile : public SoundFile -{ -public: - WavSoundFile(PHYSFS_file* file); - ~WavSoundFile(); - - size_t read(void* buffer, size_t buffer_size); - void reset(); - -private: - PHYSFS_file* file; - - PHYSFS_sint64 datastart; -}; - -static inline uint32_t read32LE(PHYSFS_file* file) -{ - uint32_t result; - if(PHYSFS_readULE32(file, &result) == 0) - throw std::runtime_error("file too short"); - - return result; -} -static inline uint16_t read16LE(PHYSFS_file* file) -{ - uint16_t result; - if(PHYSFS_readULE16(file, &result) == 0) - throw std::runtime_error("file too short"); - - return result; -} - -WavSoundFile::WavSoundFile(PHYSFS_file* file) -{ - this->file = file; - - char magic[4]; - if(PHYSFS_read(file, magic, sizeof(magic), 1) != 1) - throw std::runtime_error("Couldn't read file magic (not a wave file)"); - if(strncmp(magic, "RIFF", 4) != 0) { - log_debug << "MAGIC: " << magic << std::endl; - throw std::runtime_error("file is not a RIFF wav file"); - } - - uint32_t wavelen = read32LE(file); - (void) wavelen; - - if(PHYSFS_read(file, magic, sizeof(magic), 1) != 1) - throw std::runtime_error("Couldn't read chunk header (not a wav file?)"); - if(strncmp(magic, "WAVE", 4) != 0) - throw std::runtime_error("file is not a valid RIFF/WAVE file"); - - char chunkmagic[4]; - uint32_t chunklen; - - // search audio data format chunk - do { - if(PHYSFS_read(file, chunkmagic, sizeof(chunkmagic), 1) != 1) - throw std::runtime_error("EOF while searching format chunk"); - chunklen = read32LE(file); - - if(strncmp(chunkmagic, "fmt ", 4) == 0) - break; - - if(strncmp(chunkmagic, "fact", 4) == 0 - || strncmp(chunkmagic, "LIST", 4) == 0) { - // skip chunk - if(PHYSFS_seek(file, PHYSFS_tell(file) + chunklen) == 0) - throw std::runtime_error("EOF while searching fmt chunk"); - } else { - throw std::runtime_error("complex WAVE files not supported"); - } - } while(true); - - if(chunklen < 16) - throw std::runtime_error("Format chunk too short"); - - // parse format - uint16_t encoding = read16LE(file); - if(encoding != 1) - throw std::runtime_error("only PCM encoding supported"); - channels = read16LE(file); - rate = read32LE(file); - uint32_t byterate = read32LE(file); - (void) byterate; - uint16_t blockalign = read16LE(file); - (void) blockalign; - bits_per_sample = read16LE(file); - - if(chunklen > 16) { - if(PHYSFS_seek(file, PHYSFS_tell(file) + (chunklen-16)) == 0) - throw std::runtime_error("EOF while reading reast of format chunk"); - } - - // set file offset to DATA chunk data - do { - if(PHYSFS_read(file, chunkmagic, sizeof(chunkmagic), 1) != 1) - throw std::runtime_error("EOF while searching data chunk"); - chunklen = read32LE(file); - - if(strncmp(chunkmagic, "data", 4) == 0) - break; - - // skip chunk - if(PHYSFS_seek(file, PHYSFS_tell(file) + chunklen) == 0) - throw std::runtime_error("EOF while searching fmt chunk"); - } while(true); - - datastart = PHYSFS_tell(file); - size = static_cast (chunklen); -} - -WavSoundFile::~WavSoundFile() -{ - PHYSFS_close(file); -} - -void -WavSoundFile::reset() -{ - if(PHYSFS_seek(file, datastart) == 0) - throw std::runtime_error("Couldn't seek to data start"); -} - -size_t -WavSoundFile::read(void* buffer, size_t buffer_size) -{ - PHYSFS_sint64 end = datastart + size; - PHYSFS_sint64 cur = PHYSFS_tell(file); - if(cur >= end) - return 0; - - size_t readsize = std::min(static_cast (end - cur), buffer_size); - if(PHYSFS_read(file, buffer, readsize, 1) != 1) - throw std::runtime_error("read error while reading samples"); - -#ifdef WORDS_BIGENDIAN - if (bits_per_sample != 16) - return readsize; - char *tmp = (char*)buffer; - - size_t i; - char c; - for (i = 0; i < readsize / 2; i++) - { - c = tmp[2*i]; - tmp[2*i] = tmp[2*i+1]; - tmp[2*i+1] = c; - } - - buffer = tmp; -#endif - - return readsize; -} - -//--------------------------------------------------------------------------- - -class OggSoundFile : public SoundFile -{ -public: - OggSoundFile(PHYSFS_file* file, double loop_begin, double loop_at); - ~OggSoundFile(); - - size_t read(void* buffer, size_t buffer_size); - void reset(); - -private: - static size_t cb_read(void* ptr, size_t size, size_t nmemb, void* source); - static int cb_seek(void* source, ogg_int64_t offset, int whence); - static int cb_close(void* source); - static long cb_tell(void* source); - - PHYSFS_file* file; - OggVorbis_File vorbis_file; - ogg_int64_t loop_begin; - ogg_int64_t loop_at; - size_t normal_buffer_loop; -}; - -OggSoundFile::OggSoundFile(PHYSFS_file* file, double loop_begin, double loop_at) -{ - this->file = file; - - ov_callbacks callbacks = { cb_read, cb_seek, cb_close, cb_tell }; - ov_open_callbacks(file, &vorbis_file, 0, 0, callbacks); - - vorbis_info* vi = ov_info(&vorbis_file, -1); - - channels = vi->channels; - rate = vi->rate; - bits_per_sample = 16; - size = static_cast (ov_pcm_total(&vorbis_file, -1) * 2); - - double sample_len = 1.0f / rate; - double samples_begin = loop_begin / sample_len; - double sample_loop = loop_at / sample_len; - - this->loop_begin = (ogg_int64_t) samples_begin; - if(loop_begin < 0) { - this->loop_at = (ogg_int64_t) -1; - } else { - this->loop_at = (ogg_int64_t) sample_loop; - } -} - -OggSoundFile::~OggSoundFile() -{ - ov_clear(&vorbis_file); -} - -size_t -OggSoundFile::read(void* _buffer, size_t buffer_size) -{ - char* buffer = reinterpret_cast (_buffer); - int section = 0; - size_t totalBytesRead = 0; - - while(buffer_size>0) { -#ifdef WORDS_BIGENDIAN - int bigendian = 1; -#else - int bigendian = 0; -#endif - - size_t bytes_to_read = buffer_size; - if(loop_at > 0) { - size_t bytes_per_sample = 2; - ogg_int64_t time = ov_pcm_tell(&vorbis_file); - ogg_int64_t samples_left_till_loop = loop_at - time; - ogg_int64_t bytes_left_till_loop - = samples_left_till_loop * bytes_per_sample; - if(bytes_left_till_loop <= 4) - break; - - if(bytes_left_till_loop < (ogg_int64_t) bytes_to_read) { - bytes_to_read = (size_t) bytes_left_till_loop; - } - } - - long bytesRead - = ov_read(&vorbis_file, buffer, bytes_to_read, bigendian, - 2, 1, §ion); - if(bytesRead == 0) { - break; - } - buffer_size -= bytesRead; - buffer += bytesRead; - totalBytesRead += bytesRead; - } - - return totalBytesRead; -} - -void -OggSoundFile::reset() -{ - ov_pcm_seek(&vorbis_file, loop_begin); -} - -size_t -OggSoundFile::cb_read(void* ptr, size_t size, size_t nmemb, void* source) -{ - PHYSFS_file* file = reinterpret_cast (source); - - PHYSFS_sint64 res - = PHYSFS_read(file, ptr, static_cast (size), - static_cast (nmemb)); - if(res <= 0) - return 0; - - return static_cast (res); -} - -int -OggSoundFile::cb_seek(void* source, ogg_int64_t offset, int whence) -{ - PHYSFS_file* file = reinterpret_cast (source); - - switch(whence) { - case SEEK_SET: - if(PHYSFS_seek(file, static_cast (offset)) == 0) - return -1; - break; - case SEEK_CUR: - if(PHYSFS_seek(file, PHYSFS_tell(file) + offset) == 0) - return -1; - break; - case SEEK_END: - if(PHYSFS_seek(file, PHYSFS_fileLength(file) + offset) == 0) - return -1; - break; - default: -#ifdef DEBUG - assert(false); -#else - return -1; -#endif - } - return 0; -} - -int -OggSoundFile::cb_close(void* source) -{ - PHYSFS_file* file = reinterpret_cast (source); - PHYSFS_close(file); - return 0; -} - -long -OggSoundFile::cb_tell(void* source) -{ - PHYSFS_file* file = reinterpret_cast (source); - return static_cast (PHYSFS_tell(file)); -} - -//--------------------------------------------------------------------------- +#include "audio/sound_error.hpp" +#include "audio/ogg_sound_file.hpp" +#include "audio/wav_sound_file.hpp" +#include "lisp/parser.hpp" +#include "util/reader.hpp" +#include "util/file_system.hpp" +#include "util/log.hpp" -SoundFile* load_music_file(const std::string& filename) +std::unique_ptr load_music_file(const std::string& filename) { lisp::Parser parser(false); const lisp::Lisp* root = parser.parse(filename); const lisp::Lisp* music = root->get_lisp("supertux-music"); if(music == NULL) - throw std::runtime_error("file is not a supertux-music file."); + throw SoundError("file is not a supertux-music file."); std::string raw_music_file; float loop_begin = 0; @@ -372,6 +48,10 @@ SoundFile* load_music_file(const std::string& filename) music->get("loop-begin", loop_begin); music->get("loop-at", loop_at); + if(loop_begin < 0) { + throw SoundError("can't loop from negative value"); + } + std::string basedir = FileSystem::dirname(filename); raw_music_file = FileSystem::normalize(basedir + raw_music_file); @@ -379,40 +59,47 @@ SoundFile* load_music_file(const std::string& filename) if(!file) { std::stringstream msg; msg << "Couldn't open '" << raw_music_file << "': " << PHYSFS_getLastError(); - throw std::runtime_error(msg.str()); + throw SoundError(msg.str()); } - return new OggSoundFile(file, loop_begin, loop_at); + return std::unique_ptr(new OggSoundFile(file, loop_begin, loop_at)); } -SoundFile* load_sound_file(const std::string& filename) +std::unique_ptr load_sound_file(const std::string& filename) { if(filename.length() > 6 - && filename.compare(filename.length()-6, 6, ".music") == 0) { + && filename.compare(filename.length()-6, 6, ".music") == 0) { return load_music_file(filename); } PHYSFS_file* file = PHYSFS_openRead(filename.c_str()); if(!file) { std::stringstream msg; - msg << "Couldn't open '" << filename << "': " << PHYSFS_getLastError(); - throw std::runtime_error(msg.str()); + msg << "Couldn't open '" << filename << "': " << PHYSFS_getLastError() << ", using dummy sound file."; + throw SoundError(msg.str()); } try { char magic[4]; if(PHYSFS_read(file, magic, sizeof(magic), 1) != 1) - throw std::runtime_error("Couldn't read magic, file too short"); - PHYSFS_seek(file, 0); + throw SoundError("Couldn't read magic, file too short"); + if (PHYSFS_seek(file, 0) == 0) { + std::stringstream msg; + msg << "Couldn't seek through sound file: " << PHYSFS_getLastError(); + throw SoundError(msg.str()); + } + if(strncmp(magic, "RIFF", 4) == 0) - return new WavSoundFile(file); + return std::unique_ptr(new WavSoundFile(file)); else if(strncmp(magic, "OggS", 4) == 0) - return new OggSoundFile(file, -1, 0); + return std::unique_ptr(new OggSoundFile(file, 0, -1)); else - throw std::runtime_error("Unknown file format"); + throw SoundError("Unknown file format"); } catch(std::exception& e) { std::stringstream msg; msg << "Couldn't read '" << filename << "': " << e.what(); - throw std::runtime_error(msg.str()); + throw SoundError(msg.str()); } } + +/* EOF */