mirror of https://github.com/OpenTTD/OpenTTD
Codechange: Implement SoundLoader interface and cache loaded sounds in memory.
Sounds are loaded into memory on first use, using the SoundLoader interface to support format conversion. Sounds are retained in memory to avoid reloading every time a sound is played. This deduplicates WAV header parsing between NewGRF and baseset sounds, and will allow different audio formats to be supported.pull/13152/head
parent
7e8bcf44f7
commit
ce5279a8dc
|
@ -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
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
struct MixerChannel {
|
||||
/* pointer to allocated buffer memory */
|
||||
int8_t *memory;
|
||||
std::shared_ptr<std::vector<uint8_t>> 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<const T *>(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<const T *>(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<std::vector<uint8_t>> &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;
|
||||
}
|
||||
|
|
|
@ -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<std::vector<uint8_t>> &mem, uint rate, bool is16bit);
|
||||
void MxSetChannelVolume(MixerChannel *mc, uint volume, float pan);
|
||||
void MxActivateChannel(MixerChannel*);
|
||||
void MxCloseAllChannels();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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: <num> <FF> <FF> <name_len> <name> '\0' <data> */
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
128
src/sound.cpp
128
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<SoundEntry, ORIGINAL_SAMPLE_COUNT> _original_sounds;
|
||||
|
||||
static void OpenBankFile(const std::string &filename)
|
||||
{
|
||||
|
@ -32,7 +34,7 @@ static void OpenBankFile(const std::string &filename)
|
|||
*/
|
||||
static std::unique_ptr<RandomAccessFile> 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<int8_t>(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<int16_t *>(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. */
|
||||
|
|
|
@ -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<std::vector<uint8_t>> 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.
|
||||
};
|
||||
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @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<SoundLoader>;
|
||||
|
||||
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<std::vector<uint8_t>>();
|
||||
for (auto &loader : ProviderManager<SoundLoader>::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();
|
||||
}
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @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 */
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @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<uint8_t> &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;
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @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<SoundLoader> {
|
||||
public:
|
||||
SoundLoader(std::string_view name, std::string_view description, int priority) : PriorityBaseProvider<SoundLoader>(name, description, priority)
|
||||
{
|
||||
ProviderManager<SoundLoader>::Register(*this);
|
||||
}
|
||||
|
||||
virtual ~SoundLoader()
|
||||
{
|
||||
ProviderManager<SoundLoader>::Unregister(*this);
|
||||
}
|
||||
|
||||
virtual bool Load(SoundEntry &sound, bool new_format, std::vector<uint8_t> &data) = 0;
|
||||
};
|
||||
|
||||
#endif /* SOUNDLOADER_TYPE_H */
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @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<uint8_t> &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;
|
Loading…
Reference in New Issue