diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index add6ebbe4d..d99cb7df59 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -417,6 +417,11 @@ add_files( sound.cpp sound_func.h sound_type.h + soundloader.cpp + soundloader_func.h + soundloader_type.h + soundloader_raw.cpp + soundloader_wav.cpp sprite.cpp sprite.h spritecache.cpp diff --git a/src/mixer.cpp b/src/mixer.cpp index bba0c4d2af..81205cf398 100644 --- a/src/mixer.cpp +++ b/src/mixer.cpp @@ -19,7 +19,7 @@ struct MixerChannel { /* pointer to allocated buffer memory */ - int8_t *memory; + std::shared_ptr> memory; /* current position in memory */ uint32_t pos; @@ -76,7 +76,7 @@ static void mix_int16(MixerChannel *sc, int16_t *buffer, uint samples, uint8_t e sc->samples_left -= samples; assert(samples > 0); - const T *b = (const T *)sc->memory + sc->pos; + const T *b = reinterpret_cast(sc->memory->data()) + sc->pos; uint32_t frac_pos = sc->frac_pos; uint32_t frac_speed = sc->frac_speed; int volume_left = sc->volume_left * effect_vol / 255; @@ -103,7 +103,7 @@ static void mix_int16(MixerChannel *sc, int16_t *buffer, uint samples, uint8_t e } sc->frac_pos = frac_pos; - sc->pos = b - (const T *)sc->memory; + sc->pos = b - reinterpret_cast(sc->memory->data()); } static void MxCloseChannel(uint8_t channel_index) @@ -176,20 +176,22 @@ MixerChannel *MxAllocateChannel() uint8_t channel_index = FindFirstBit(available); MixerChannel *mc = &_channels[channel_index]; - free(mc->memory); mc->memory = nullptr; return mc; } -void MxSetChannelRawSrc(MixerChannel *mc, int8_t *mem, size_t size, uint rate, bool is16bit) +void MxSetChannelRawSrc(MixerChannel *mc, const std::shared_ptr> &mem, uint rate, bool is16bit) { mc->memory = mem; mc->frac_pos = 0; mc->pos = 0; - mc->frac_speed = (rate << 16) / _play_rate; + mc->frac_speed = (rate << 16U) / _play_rate; + size_t size = mc->memory->size(); if (is16bit) size /= 2; + /* Less 1 to allow for padding sample for the resampler. */ + size -= 1; /* adjust the magnitude to prevent overflow */ while (size >= _max_size) { @@ -197,6 +199,7 @@ void MxSetChannelRawSrc(MixerChannel *mc, int8_t *mem, size_t size, uint rate, b rate = (rate >> 1) + 1; } + /* Scale number of samples by play rate. */ mc->samples_left = (uint)size * _play_rate / rate; mc->is16bit = is16bit; } diff --git a/src/mixer.h b/src/mixer.h index 3fbcbc4bc8..265725696e 100644 --- a/src/mixer.h +++ b/src/mixer.h @@ -24,7 +24,7 @@ bool MxInitialize(uint rate); void MxMixSamples(void *buffer, uint samples); MixerChannel *MxAllocateChannel(); -void MxSetChannelRawSrc(MixerChannel *mc, int8_t *mem, size_t size, uint rate, bool is16bit); +void MxSetChannelRawSrc(MixerChannel *mc, const std::shared_ptr> &mem, uint rate, bool is16bit); void MxSetChannelVolume(MixerChannel *mc, uint volume, float pan); void MxActivateChannel(MixerChannel*); void MxCloseAllChannels(); diff --git a/src/newgrf.cpp b/src/newgrf.cpp index d217c4da1e..0021a989bb 100644 --- a/src/newgrf.cpp +++ b/src/newgrf.cpp @@ -7914,6 +7914,7 @@ static void LoadGRFSound(size_t offs, SoundEntry *sound) /* Sound is present in the NewGRF. */ sound->file = _cur.file; sound->file_offset = offs; + sound->source = SoundSource::NewGRF; sound->grf_container_ver = _cur.file->GetContainerVersion(); } } diff --git a/src/newgrf_sound.cpp b/src/newgrf_sound.cpp index c79ca59ffb..9febfdc4bd 100644 --- a/src/newgrf_sound.cpp +++ b/src/newgrf_sound.cpp @@ -14,6 +14,8 @@ #include "newgrf_sound.h" #include "vehicle_base.h" #include "sound_func.h" +#include "soundloader_func.h" +#include "string_func.h" #include "random_access_file_type.h" #include "debug.h" #include "settings_type.h" @@ -63,20 +65,20 @@ uint GetNumSounds() * @param sound Sound to load. * @return True if a valid sound was loaded. */ -bool LoadNewGRFSound(SoundEntry *sound) +bool LoadNewGRFSound(SoundEntry &sound, SoundID sound_id) { - if (sound->file_offset == SIZE_MAX || sound->file == nullptr) return false; + if (sound.file_offset == SIZE_MAX || sound.file == nullptr) return false; - RandomAccessFile &file = *sound->file; - file.SeekTo(sound->file_offset, SEEK_SET); + RandomAccessFile &file = *sound.file; + file.SeekTo(sound.file_offset, SEEK_SET); /* Skip ID for container version >= 2 as we only look at the first * entry and ignore any further entries with the same ID. */ - if (sound->grf_container_ver >= 2) file.ReadDword(); + if (sound.grf_container_ver >= 2) file.ReadDword(); /* Format: '\0' */ - uint32_t num = sound->grf_container_ver >= 2 ? file.ReadDword() : file.ReadWord(); + sound.file_size = sound.grf_container_ver >= 2 ? file.ReadDword() : file.ReadWord(); if (file.ReadByte() != 0xFF) return false; if (file.ReadByte() != 0xFF) return false; @@ -85,78 +87,17 @@ bool LoadNewGRFSound(SoundEntry *sound) file.ReadBlock(name.data(), name_len + 1); /* Test string termination */ - if (name[name_len] != 0) { + if (name[name_len] != '\0') { Debug(grf, 2, "LoadNewGRFSound [{}]: Name not properly terminated", file.GetSimplifiedFilename()); return false; } - Debug(grf, 2, "LoadNewGRFSound [{}]: Sound name '{}'...", file.GetSimplifiedFilename(), name); + if (LoadSoundData(sound, true, sound_id, StrMakeValid(name))) return true; - if (file.ReadDword() != BSWAP32('RIFF')) { - Debug(grf, 1, "LoadNewGRFSound [{}]: Missing RIFF header", file.GetSimplifiedFilename()); - return false; - } - - uint32_t total_size = file.ReadDword(); - uint header_size = 11; - if (sound->grf_container_ver >= 2) header_size++; // The first FF in the sprite is only counted for container version >= 2. - if (total_size + name_len + header_size > num) { - Debug(grf, 1, "LoadNewGRFSound [{}]: RIFF was truncated", file.GetSimplifiedFilename()); - return false; - } - - if (file.ReadDword() != BSWAP32('WAVE')) { - Debug(grf, 1, "LoadNewGRFSound [{}]: Invalid RIFF type", file.GetSimplifiedFilename()); - return false; - } - - while (total_size >= 8) { - uint32_t tag = file.ReadDword(); - uint32_t size = file.ReadDword(); - total_size -= 8; - if (total_size < size) { - Debug(grf, 1, "LoadNewGRFSound [{}]: Invalid RIFF", file.GetSimplifiedFilename()); - return false; - } - total_size -= size; - - switch (tag) { - case ' tmf': // 'fmt ' - /* Audio format, must be 1 (PCM) */ - if (size < 16 || file.ReadWord() != 1) { - Debug(grf, 1, "LoadGRFSound [{}]: Invalid audio format", file.GetSimplifiedFilename()); - return false; - } - sound->channels = file.ReadWord(); - sound->rate = file.ReadDword(); - file.ReadDword(); - file.ReadWord(); - sound->bits_per_sample = file.ReadWord(); - - /* The rest will be skipped */ - size -= 16; - break; - - case 'atad': // 'data' - sound->file_size = size; - sound->file_offset = file.GetPos(); - - Debug(grf, 2, "LoadNewGRFSound [{}]: channels {}, sample rate {}, bits per sample {}, length {}", file.GetSimplifiedFilename(), sound->channels, sound->rate, sound->bits_per_sample, size); - return true; // the fmt chunk has to appear before data, so we are finished - - default: - /* Skip unknown chunks */ - break; - } - - /* Skip rest of chunk */ - if (size > 0) file.SkipBytes(size); - } - - Debug(grf, 1, "LoadNewGRFSound [{}]: RIFF does not contain any sound data", file.GetSimplifiedFilename()); + Debug(grf, 1, "LoadNewGRFSound [{}]: does not contain any sound data", file.GetSimplifiedFilename()); /* Clear everything that was read */ - MemSetT(sound, 0); + sound = {}; return false; } diff --git a/src/newgrf_sound.h b/src/newgrf_sound.h index 5b1d1cbc57..ae6b7d94a7 100644 --- a/src/newgrf_sound.h +++ b/src/newgrf_sound.h @@ -30,7 +30,7 @@ enum VehicleSoundEvent { SoundEntry *AllocateSound(uint num); void InitializeSoundPool(); -bool LoadNewGRFSound(SoundEntry *sound); +bool LoadNewGRFSound(SoundEntry &sound, SoundID sound_id); SoundID GetNewGRFSoundID(const struct GRFFile *file, SoundID sound_id); SoundEntry *GetSound(SoundID sound_id); uint GetNumSounds(); diff --git a/src/sound.cpp b/src/sound.cpp index 95d11b1474..5960ee02d5 100644 --- a/src/sound.cpp +++ b/src/sound.cpp @@ -9,6 +9,8 @@ #include "stdafx.h" #include "landscape.h" +#include "sound_type.h" +#include "soundloader_func.h" #include "mixer.h" #include "newgrf_sound.h" #include "random_access_file_type.h" @@ -22,7 +24,7 @@ #include "safeguards.h" -static SoundEntry _original_sounds[ORIGINAL_SAMPLE_COUNT]; +static std::array _original_sounds; static void OpenBankFile(const std::string &filename) { @@ -32,7 +34,7 @@ static void OpenBankFile(const std::string &filename) */ static std::unique_ptr original_sound_file; - memset(_original_sounds, 0, sizeof(_original_sounds)); + _original_sounds.fill({}); /* If there is no sound file (nosound set), don't load anything */ if (filename.empty()) return; @@ -42,7 +44,7 @@ static void OpenBankFile(const std::string &filename) uint count = original_sound_file->ReadDword(); /* The new format has the highest bit always set */ - bool new_format = HasBit(count, 31); + auto source = HasBit(count, 31) ? SoundSource::BasesetNewFormat : SoundSource::BasesetOldFormat; ClrBit(count, 31); count /= 8; @@ -57,101 +59,32 @@ static void OpenBankFile(const std::string &filename) original_sound_file->SeekTo(pos, SEEK_SET); - for (uint i = 0; i != ORIGINAL_SAMPLE_COUNT; i++) { - _original_sounds[i].file = original_sound_file.get(); - _original_sounds[i].file_offset = GB(original_sound_file->ReadDword(), 0, 31) + pos; - _original_sounds[i].file_size = original_sound_file->ReadDword(); - } - - for (uint i = 0; i != ORIGINAL_SAMPLE_COUNT; i++) { - SoundEntry *sound = &_original_sounds[i]; - char name[255]; - - original_sound_file->SeekTo(sound->file_offset, SEEK_SET); - - /* Check for special case, see else case */ - original_sound_file->ReadBlock(name, original_sound_file->ReadByte()); // Read the name of the sound - if (new_format || strcmp(name, "Corrupt sound") != 0) { - original_sound_file->SeekTo(12, SEEK_CUR); // Skip past RIFF header - - /* Read riff tags */ - for (;;) { - uint32_t tag = original_sound_file->ReadDword(); - uint32_t size = original_sound_file->ReadDword(); - - if (tag == ' tmf') { - original_sound_file->ReadWord(); // wFormatTag - sound->channels = original_sound_file->ReadWord(); // wChannels - sound->rate = original_sound_file->ReadDword(); // samples per second - if (!new_format) sound->rate = 11025; // seems like all old samples should be played at this rate. - original_sound_file->ReadDword(); // avg bytes per second - original_sound_file->ReadWord(); // alignment - sound->bits_per_sample = original_sound_file->ReadByte(); // bits per sample - original_sound_file->SeekTo(size - (2 + 2 + 4 + 4 + 2 + 1), SEEK_CUR); - } else if (tag == 'atad') { - sound->file_size = size; - sound->file = original_sound_file.get(); - sound->file_offset = original_sound_file->GetPos(); - break; - } else { - sound->file_size = 0; - break; - } - } - } else { - /* - * Special case for the jackhammer sound - * (name in sample.cat is "Corrupt sound") - * It's no RIFF file, but raw PCM data - */ - sound->channels = 1; - sound->rate = 11025; - sound->bits_per_sample = 8; - sound->file = original_sound_file.get(); - sound->file_offset = original_sound_file->GetPos(); - } + /* Read sound file positions. */ + for (auto &sound : _original_sounds) { + sound.file = original_sound_file.get(); + sound.file_offset = GB(original_sound_file->ReadDword(), 0, 31) + pos; + sound.file_size = original_sound_file->ReadDword(); + sound.source = source; } } -static bool SetBankSource(MixerChannel *mc, const SoundEntry *sound) +static bool SetBankSource(MixerChannel *mc, SoundEntry *sound, SoundID sound_id) { assert(sound != nullptr); - /* Check for valid sound size. */ - if (sound->file_size == 0 || sound->file_size > SIZE_MAX - 2) return false; - - int8_t *mem = MallocT(sound->file_size + 2); - /* Add two extra bytes so rate conversion can read these - * without reading out of its input buffer. */ - mem[sound->file_size ] = 0; - mem[sound->file_size + 1] = 0; - - RandomAccessFile *file = sound->file; - file->SeekTo(sound->file_offset, SEEK_SET); - file->ReadBlock(mem, sound->file_size); - - /* 16-bit PCM WAV files should be signed by default */ - if (sound->bits_per_sample == 8) { - for (uint i = 0; i != sound->file_size; i++) { - mem[i] += -128; // Convert unsigned sound data to signed + if (sound->file != nullptr) { + if (!LoadSound(*sound, sound_id)) { + /* Mark as invalid. */ + sound->file = nullptr; + return false; } + sound->file = nullptr; } - if constexpr (std::endian::native == std::endian::big) { - if (sound->bits_per_sample == 16) { - size_t num_samples = sound->file_size / 2; - int16_t *samples = reinterpret_cast(mem); - for (size_t i = 0; i < num_samples; i++) { - samples[i] = BSWAP16(samples[i]); - } - } - } + /* Check for valid sound. */ + if (sound->data->empty()) return false; - assert(sound->bits_per_sample == 8 || sound->bits_per_sample == 16); - assert(sound->channels == 1); - assert(sound->file_size != 0 && sound->rate != 0); - - MxSetChannelRawSrc(mc, mem, sound->file_size, sound->rate, sound->bits_per_sample == 16); + MxSetChannelRawSrc(mc, sound->data, sound->rate, sound->bits_per_sample == 16); return true; } @@ -162,6 +95,7 @@ void InitializeSound() OpenBankFile(BaseSounds::GetUsedSet()->files->filename); } + /* Low level sound player */ static void StartSound(SoundID sound_id, float pan, uint volume) { @@ -170,22 +104,16 @@ static void StartSound(SoundID sound_id, float pan, uint volume) SoundEntry *sound = GetSound(sound_id); if (sound == nullptr) return; - /* NewGRF sound that wasn't loaded yet? */ - if (sound->rate == 0 && sound->file != nullptr) { - if (!LoadNewGRFSound(sound)) { - /* Mark as invalid. */ - sound->file = nullptr; - return; - } + if (sound->rate == 0) { + /* If the sound's sample rate is not set then the sound needs to be loaded, but if the sound's file pointer + * is empty then an attempt was already made to load the sound but it failed. We don't want to try again. */ + if (sound->file == nullptr) return; } - /* Empty sound? */ - if (sound->rate == 0) return; - MixerChannel *mc = MxAllocateChannel(); if (mc == nullptr) return; - if (!SetBankSource(mc, sound)) return; + if (!SetBankSource(mc, sound, sound_id)) return; /* Apply the sound effect's own volume. */ volume = sound->volume * volume; @@ -250,7 +178,7 @@ void ChangeSoundSet(int index) InitializeSound(); /* Replace baseset sounds in the pool with the updated original sounds. This is safe to do as - * any sound still playing owns its sample data. */ + * any sound still playing holds its own shared_ptr to the sample data. */ for (uint i = 0; i < ORIGINAL_SAMPLE_COUNT; i++) { SoundEntry *sound = GetSound(i); /* GRF Container 0 means the sound comes from the baseset, and isn't overridden by NewGRF. */ diff --git a/src/sound_type.h b/src/sound_type.h index aa841059a9..7fb2faf150 100644 --- a/src/sound_type.h +++ b/src/sound_type.h @@ -10,7 +10,14 @@ #ifndef SOUND_TYPE_H #define SOUND_TYPE_H +enum class SoundSource : uint8_t { + BasesetOldFormat, + BasesetNewFormat, + NewGRF, +}; + struct SoundEntry { + std::shared_ptr> data; class RandomAccessFile *file; size_t file_offset; size_t file_size; @@ -19,6 +26,7 @@ struct SoundEntry { uint8_t channels; uint8_t volume; uint8_t priority; + SoundSource source; uint8_t grf_container_ver; ///< NewGRF container version if the sound is from a NewGRF. }; diff --git a/src/soundloader.cpp b/src/soundloader.cpp new file mode 100644 index 0000000000..4503392cbf --- /dev/null +++ b/src/soundloader.cpp @@ -0,0 +1,73 @@ +/* + * This file is part of OpenTTD. + * OpenTTD 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, version 2. + * OpenTTD 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. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file soundloader.cpp Handling of loading sounds. */ + +#include "stdafx.h" +#include "debug.h" +#include "sound_type.h" +#include "soundloader_type.h" +#include "soundloader_func.h" +#include "string_func.h" +#include "newgrf_sound.h" +#include "random_access_file_type.h" + +#include "safeguards.h" + +template class ProviderManager; + +bool LoadSoundData(SoundEntry &sound, bool new_format, SoundID sound_id, const std::string &name) +{ + /* Check for valid sound size. */ + if (sound.file_size == 0 || sound.file_size > SIZE_MAX - 2) return false; + + size_t pos = sound.file->GetPos(); + sound.data = std::make_shared>(); + for (auto &loader : ProviderManager::GetProviders()) { + sound.file->SeekTo(pos, SEEK_SET); + if (loader->Load(sound, new_format, *sound.data)) break; + } + + if (sound.data->empty()) { + Debug(grf, 0, "LoadSound [{}]: Failed to load sound '{}' for slot {}", sound.file->GetSimplifiedFilename(), name, sound_id); + return false; + } + + assert(sound.bits_per_sample == 8 || sound.bits_per_sample == 16); + assert(sound.channels == 1); + assert(sound.rate != 0); + + Debug(grf, 2, "LoadSound [{}]: channels {}, sample rate {}, bits per sample {}, length {}", sound.file->GetSimplifiedFilename(), sound.channels, sound.rate, sound.bits_per_sample, sound.file_size); + + /* Mixer always requires an extra sample at the end for the built-in linear resampler. */ + sound.data->resize(sound.data->size() + sound.channels * sound.bits_per_sample / 8); + sound.data->shrink_to_fit(); + + return true; +} + +static bool LoadBasesetSound(SoundEntry &sound, bool new_format, SoundID sound_id) +{ + sound.file->SeekTo(sound.file_offset, SEEK_SET); + + /* Read name of sound for diagnostics. */ + size_t name_len = sound.file->ReadByte(); + std::string name(name_len, '\0'); + sound.file->ReadBlock(name.data(), name_len); + + return LoadSoundData(sound, new_format, sound_id, StrMakeValid(name)); +} + +bool LoadSound(SoundEntry &sound, SoundID sound_id) +{ + switch (sound.source) { + case SoundSource::BasesetOldFormat: return LoadBasesetSound(sound, false, sound_id); + case SoundSource::BasesetNewFormat: return LoadBasesetSound(sound, true, sound_id); + case SoundSource::NewGRF: return LoadNewGRFSound(sound, sound_id); + default: NOT_REACHED(); + } +} diff --git a/src/soundloader_func.h b/src/soundloader_func.h new file mode 100644 index 0000000000..1b8837e832 --- /dev/null +++ b/src/soundloader_func.h @@ -0,0 +1,16 @@ +/* + * This file is part of OpenTTD. + * OpenTTD 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, version 2. + * OpenTTD 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. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file soundloader_func.h Functions related to sound loaders. */ + +#ifndef SOUNDLOADER_FUNC_H +#define SOUNDLOADER_FUNC_H + +bool LoadSound(SoundEntry &sound, SoundID sound_id); +bool LoadSoundData(SoundEntry &sound, bool new_format, SoundID sound_id, const std::string &name); + +#endif /* SOUNDLOADER_FUNC_H */ diff --git a/src/soundloader_raw.cpp b/src/soundloader_raw.cpp new file mode 100644 index 0000000000..7a5cea2edc --- /dev/null +++ b/src/soundloader_raw.cpp @@ -0,0 +1,52 @@ +/* + * This file is part of OpenTTD. + * OpenTTD 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, version 2. + * OpenTTD 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. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file soundloader_raw.cpp Loading of raw sounds. */ + +#include "stdafx.h" +#include "random_access_file_type.h" +#include "sound_type.h" +#include "soundloader_type.h" + +#include "safeguards.h" + +/** Raw PCM sound loader, used as a fallback if other sound loaders fail. */ +class SoundLoader_Raw : public SoundLoader { +public: + SoundLoader_Raw() : SoundLoader("raw", "Raw PCM loader", INT_MAX) {} + + static constexpr uint16_t RAW_SAMPLE_RATE = 11025; ///< Sample rate of raw pcm samples. + static constexpr uint8_t RAW_SAMPLE_BITS = 8; ///< Bit depths of raw pcm samples. + + bool Load(SoundEntry &sound, bool new_format, std::vector &data) override + { + /* Raw sounds are apecial case for the jackhammer sound (name in Windows sample.cat is "Corrupt sound") + * It's not a RIFF file, but raw PCM data. + * We no longer compare by name as the same file in the DOS sample.cat does not have a unique name. */ + + /* Raw sounds are not permitted in a new format file. */ + if (new_format) return false; + + sound.channels = 1; + sound.rate = RAW_SAMPLE_RATE; + sound.bits_per_sample = RAW_SAMPLE_BITS; + + /* Allocate an extra sample to ensure the runtime resampler doesn't go out of bounds.*/ + data.reserve(sound.file_size + 1); + data.resize(sound.file_size); + sound.file->ReadBlock(std::data(data), std::size(data)); + + /* Convert 8-bit samples from unsigned to signed. */ + for (auto &sample : data) { + sample = sample - 128; + } + + return true; + } +}; + +static SoundLoader_Raw s_sound_loader_raw; diff --git a/src/soundloader_type.h b/src/soundloader_type.h new file mode 100644 index 0000000000..978a1b94b6 --- /dev/null +++ b/src/soundloader_type.h @@ -0,0 +1,32 @@ +/* + * This file is part of OpenTTD. + * OpenTTD 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, version 2. + * OpenTTD 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. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file soundloader_type.h Types related to sound loaders. */ + +#ifndef SOUNDLOADER_TYPE_H +#define SOUNDLOADER_TYPE_H + +#include "provider_manager.h" +#include "sound_type.h" + +/** Base interface for a SoundLoader implementation. */ +class SoundLoader : public PriorityBaseProvider { +public: + SoundLoader(std::string_view name, std::string_view description, int priority) : PriorityBaseProvider(name, description, priority) + { + ProviderManager::Register(*this); + } + + virtual ~SoundLoader() + { + ProviderManager::Unregister(*this); + } + + virtual bool Load(SoundEntry &sound, bool new_format, std::vector &data) = 0; +}; + +#endif /* SOUNDLOADER_TYPE_H */ diff --git a/src/soundloader_wav.cpp b/src/soundloader_wav.cpp new file mode 100644 index 0000000000..fc93249f31 --- /dev/null +++ b/src/soundloader_wav.cpp @@ -0,0 +1,101 @@ +/* + * This file is part of OpenTTD. + * OpenTTD 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, version 2. + * OpenTTD 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. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file soundloader_wav.cpp Loading of wav sounds. */ + +#include "stdafx.h" +#include "core/bitmath_func.hpp" +#include "core/math_func.hpp" +#include "random_access_file_type.h" +#include "sound_type.h" +#include "soundloader_type.h" + +#include "safeguards.h" + +/** Wav file (RIFF/WAVE) sound loader. */ +class SoundLoader_Wav : public SoundLoader { +public: + SoundLoader_Wav() : SoundLoader("wav", "Wav sound loader", 0) {} + + static constexpr uint16_t DEFAULT_SAMPLE_RATE = 11025; + + bool Load(SoundEntry &sound, bool new_format, std::vector &data) override + { + RandomAccessFile &file = *sound.file; + + /* Check RIFF/WAVE header. */ + if (file.ReadDword() != BSWAP32('RIFF')) return false; + file.ReadDword(); // Skip data size + if (file.ReadDword() != BSWAP32('WAVE')) return false; + + /* Read riff tags */ + for (;;) { + uint32_t tag = file.ReadDword(); + uint32_t size = file.ReadDword(); + + if (tag == BSWAP32('fmt ')) { + uint16_t format = file.ReadWord(); + if (format != 1) return false; // File must be uncompressed PCM + + sound.channels = file.ReadWord(); + if (sound.channels != 1) return false; // File must be mono. + + sound.rate = file.ReadDword(); + if (!new_format) sound.rate = DEFAULT_SAMPLE_RATE; // All old samples should be played at 11025 Hz. + + file.ReadDword(); // avg bytes per second + file.ReadWord(); // alignment + + sound.bits_per_sample = file.ReadWord(); + if (sound.bits_per_sample != 8 && sound.bits_per_sample != 16) return false; // File must be 8 or 16 BPS. + + /* We've read 16 bytes of this chunk, we can skip anything extra. */ + size -= 16; + } else if (tag == BSWAP32('data')) { + uint align = sound.channels * sound.bits_per_sample / 8; + if (Align(size, align) != size) return false; // Ensure length is aligned correctly for channels and BPS. + + if (size == 0) return true; // No need to continue. + + /* Allocate an extra sample to ensure the runtime resampler doesn't go out of bounds. */ + data.reserve(size + align); + data.resize(size); + + file.ReadBlock(std::data(data), size); + + switch (sound.bits_per_sample) { + case 8: + /* Convert 8-bit samples from unsigned to signed. */ + for (auto &sample : data) { + sample = sample - 128; + } + break; + + case 16: + /* 16-bit samples in wav files are little endian, and may need to be converted to native endian. */ + if constexpr (std::endian::native != std::endian::little) { + for (auto it = std::begin(data); it != std::end(data); /* nothing */) { + std::swap(*it++, *it++); + } + } + break; + + default: NOT_REACHED(); + } + + return true; + } + + /* Skip rest of chunk. */ + if (size > 0) file.SkipBytes(size); + } + + return false; + } +}; + +static SoundLoader_Wav s_sound_loader_wav;