Fix repeating sounds in MxWavePresenter (#35)
Some checks are pending
Build / Current msys2 mingw32 (push) Waiting to run
Build / Current msys2 mingw64 (push) Waiting to run
Build / Current MSVC (32-bit) (push) Waiting to run
Build / Current MSVC (64-bit) (push) Waiting to run
Format / C++ (push) Waiting to run
Naming / C++ (push) Waiting to run

This commit is contained in:
Christian Semmler 2024-12-15 16:29:41 -07:00 committed by GitHub
parent 3b7e60bdc0
commit 874714a40d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 49 additions and 14 deletions

View file

@ -38,6 +38,7 @@ class MxWavePresenter : public MxSoundPresenter {
void ReadyTickle() override; // vtable+0x18 void ReadyTickle() override; // vtable+0x18
void StartingTickle() override; // vtable+0x1c void StartingTickle() override; // vtable+0x1c
void StreamingTickle() override; // vtable+0x20 void StreamingTickle() override; // vtable+0x20
void RepeatingTickle() override; // added by isle-portable
void DoneTickle() override; // vtable+0x2c void DoneTickle() override; // vtable+0x2c
void ParseExtra() override; // vtable+0x30 void ParseExtra() override; // vtable+0x30
MxResult AddToManager() override; // vtable+0x34 MxResult AddToManager() override; // vtable+0x34

View file

@ -108,18 +108,6 @@ void MxWavePresenter::StartingTickle()
assert(m_waveFormat->m_formatTag == g_supportedFormatTag); assert(m_waveFormat->m_formatTag == g_supportedFormatTag);
assert(m_waveFormat->m_bitsPerSample == 8 || m_waveFormat->m_bitsPerSample == 16); assert(m_waveFormat->m_bitsPerSample == 8 || m_waveFormat->m_bitsPerSample == 16);
// [library:audio]
// The original game supported a looping/repeating action mode which apparently
// went unused in the retail version of the game (at least for audio).
// It is only ever "used" on startup to load two dummy sounds which are
// initially disabled and never play: IsleScript::c_TransitionSound1 and IsleScript::c_TransitionSound2
// If MxDSAction::c_looping was set, MxWavePresenter kept the entire sound track
// in a buffer, presumably to allow random seeking and looping. This functionality
// has most likely been superseded by the looping mechanism implemented in the streaming layer.
// Since this has gone unused and to reduce complexity, we don't allow this anymore;
// except for the two dummy sounds, which must be !IsEnabled()
assert(!(m_action->GetFlags() & MxDSAction::c_looping) || !IsEnabled());
if (m_waveFormat->m_bitsPerSample == 8) { if (m_waveFormat->m_bitsPerSample == 8) {
m_silenceData = 0x7F; m_silenceData = 0x7F;
} }
@ -128,11 +116,17 @@ void MxWavePresenter::StartingTickle()
m_silenceData = 0; m_silenceData = 0;
} }
// [library:audio]
// If we have a repeating action (MxDSAction::c_looping set), we must make sure the ring buffer
// is large enough to contain the entire sound at once. The size must be a multiple of `g_millisecondsPerChunk`
if (ma_pcm_rb_init( if (ma_pcm_rb_init(
m_waveFormat->m_bitsPerSample == 16 ? ma_format_s16 : ma_format_u8, m_waveFormat->m_bitsPerSample == 16 ? ma_format_s16 : ma_format_u8,
m_waveFormat->m_channels, m_waveFormat->m_channels,
ma_calculate_buffer_size_in_frames_from_milliseconds( ma_calculate_buffer_size_in_frames_from_milliseconds(
g_rbSizeInMilliseconds, m_action->GetFlags() & MxDSAction::c_looping
? (m_action->GetDuration() / m_action->GetLoopCount() + g_millisecondsPerChunk - 1) /
g_millisecondsPerChunk * g_millisecondsPerChunk
: g_rbSizeInMilliseconds,
m_waveFormat->m_samplesPerSec m_waveFormat->m_samplesPerSec
), ),
NULL, NULL,
@ -194,6 +188,15 @@ void MxWavePresenter::StreamingTickle()
} }
} }
void MxWavePresenter::RepeatingTickle()
{
if (IsEnabled() && !m_currentChunk) {
if (m_action->GetElapsedTime() >= m_action->GetStartTime() + m_action->GetDuration()) {
ProgressTickleState(e_freezing);
}
}
}
// FUNCTION: LEGO1 0x100b20c0 // FUNCTION: LEGO1 0x100b20c0
void MxWavePresenter::DoneTickle() void MxWavePresenter::DoneTickle()
{ {
@ -206,9 +209,29 @@ void MxWavePresenter::DoneTickle()
// FUNCTION: LEGO1 0x100b2130 // FUNCTION: LEGO1 0x100b2130
void MxWavePresenter::LoopChunk(MxStreamChunk* p_chunk) void MxWavePresenter::LoopChunk(MxStreamChunk* p_chunk)
{ {
WriteToSoundBuffer(p_chunk->GetData(), p_chunk->GetLength()); // [library:audio]
// The original code writes all the chunks directly into the buffer. However, since we are using
// a ring buffer instead, we cannot do that. Instead, we use the original code's `m_loopingChunks`
// to store permanent copies of all the chunks. (`MxSoundPresenter::LoopChunk`)
// These will then be used to write them all at once to the ring buffer when necessary.
MxSoundPresenter::LoopChunk(p_chunk);
assert(m_action->GetFlags() & MxDSAction::c_looping);
// [library:audio]
// We don't currently support a loop count greater than 1 for repeating actions.
// However, there don't seem to be any such actions in the game.
assert(m_action->GetLoopCount() == 1);
// [library:audio]
// So far there are no known cases where the sound is initially enabled if it's set to repeat.
// While we can technically support this (see branch below), this should be tested.
assert(!IsEnabled());
if (IsEnabled()) { if (IsEnabled()) {
WriteToSoundBuffer(p_chunk->GetData(), p_chunk->GetLength());
m_subscriber->FreeDataChunk(p_chunk); m_subscriber->FreeDataChunk(p_chunk);
m_currentChunk = NULL;
} }
} }
@ -236,6 +259,17 @@ MxResult MxWavePresenter::PutData()
break; break;
} }
assert(!ma_sound_is_playing(&m_sound));
// [library:audio]
// We push all the repeating chunks at once into the buffer.
// This should never fail, since the buffer is ensured to be large enough to contain the entire sound.
while (m_loopingChunkCursor->Next(m_currentChunk)) {
assert(WriteToSoundBuffer(m_currentChunk->GetData(), m_currentChunk->GetLength()));
}
m_currentChunk = NULL;
if (ma_sound_start(&m_sound) == MA_SUCCESS) { if (ma_sound_start(&m_sound) == MA_SUCCESS) {
m_started = TRUE; m_started = TRUE;
} }