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.
This commit is contained in:
committed by
Peter Nelson
parent
7e8bcf44f7
commit
ce5279a8dc
+28
-100
@@ -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. */
|
||||
|
||||
Reference in New Issue
Block a user