User reported never hearing any audio. Root-caused: the KB "Audio: DONE" was wrong. The OpenAL playback chain IS fully implemented (device/buffers/sources; PlayNote really calls alSourcePlay), but three gaps kept it silent: 1. GATED OFF (the "no sound" root cause): BTL4Application::MakeAudioRenderer returned NULL unless the pod's AWE_FRONT/AWE_REAR AWE32-card env vars were set -- authentic 1995 pod behavior, dead on modern hardware, so the renderer was NEVER created. FIXED: default audio ON (BT_NO_AUDIO=1 restores silence; AWE vars still force-on). Verified: the OpenAL device now opens with no env vars ([audio] device OPENED). 2. Soundbank STUB: allPresets[2][100] (btstubs.cpp) is zero-init -> PRESET_isImplemented false -> 0 buffers load. Sample data exists (AUDIO1/2.RES: EnginePower/LaserAFire/...) but the event->sample map is gone (not in the decomp). STILL OPEN. 3. No triggering: the reconstructed game never creates AudioEntities on events. STILL OPEN. So the device opens but nothing loads/plays yet -- enabling real sound needs the soundbank reconstructed + the game triggers wired (a proper audio wave). Added a BT_AUDIO_LOG trace harness across L4AUDRND/L4AUDRES/L4AUDLVL. KB corrected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
316 lines
8.1 KiB
C++
316 lines
8.1 KiB
C++
#include <cstdlib>
|
|
#include "mungal4.h"
|
|
#pragma hdrstop
|
|
|
|
#include "l4audlvl.h"
|
|
#include "l4audres.h"
|
|
#include "..\munga\audrend.h"
|
|
#include "..\munga\objstrm.h"
|
|
#include "..\munga\namelist.h"
|
|
#include "openal/al.h"
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PatchLevelOfDetail ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
#if DEBUG_LEVEL>0
|
|
TableOf<PatchLevelOfDetail*, unsigned int>
|
|
PatchLevelOfDetail::patchTableSocket(NULL, True);
|
|
#endif
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
PatchLevelOfDetail::PatchLevelOfDetail(PlugStream *stream):
|
|
AudioLevelOfDetail(stream)
|
|
{
|
|
MemoryStream_Read(stream, &bankID);
|
|
MemoryStream_Read(stream, &patchID);
|
|
MemoryStream_Read(stream, &maxMIDIFilterCutoff);
|
|
|
|
Warn(GetVoiceCount() > 4); // HACK - AWE appears to only play 1st 4 voices
|
|
|
|
#ifdef LAB_ONLY
|
|
setupCount = 0;
|
|
#endif
|
|
|
|
//
|
|
// Keep table of created patchs to verify that duplicates
|
|
// are not created, could move this to tool time
|
|
//
|
|
#if DEBUG_LEVEL>0
|
|
unsigned int index_value = (bankID * 1000) + patchID;
|
|
if (patchTableSocket.Find(index_value) != NULL)
|
|
{
|
|
Dump((int)bankID);
|
|
Dump((int)patchID);
|
|
}
|
|
Verify(patchTableSocket.Find(index_value) == NULL);
|
|
patchTableSocket.AddValue(this, index_value);
|
|
#endif
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
PatchLevelOfDetail::BuildFromPage(
|
|
PlugStream *stream,
|
|
NameList *name_list,
|
|
ClassID class_ID,
|
|
ObjectID object_ID
|
|
)
|
|
{
|
|
AudioLevelOfDetail::BuildFromPage(stream, name_list, class_ID, object_ID);
|
|
|
|
//
|
|
// Store fields
|
|
//
|
|
MEM_STRM_WRITE_ENTRY(*stream, name_list, SBKBankID, bank_ID);
|
|
MEM_STRM_WRITE_ENTRY(*stream, name_list, SBKPatchID, patch_ID);
|
|
MEM_STRM_WRITE_ENTRY(*stream, name_list, MIDINRPNValue, max_filter_cutoff);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
PatchLevelOfDetail::~PatchLevelOfDetail()
|
|
{
|
|
#ifdef LAB_ONLY
|
|
cout << "PatchLevelOfDetail::~PatchLevelOfDetail\t";
|
|
cout << (int)bankID << ":" << (int)patchID << "\t";
|
|
cout << "setupCount=\t" << setupCount << "\n";
|
|
#endif
|
|
|
|
/*if (m_numSources > 0 && m_sources != NULL)
|
|
{
|
|
for (int i=0; i < m_numSources; i++)
|
|
{
|
|
//Detach buffer
|
|
alSourcei(m_sources[i],AL_BUFFER,0);
|
|
//Stop source
|
|
ALenum state;
|
|
alGetSourcei(m_sources[i],AL_SOURCE_STATE,&state);
|
|
if (state == AL_PLAYING)
|
|
{
|
|
alSourceStop(m_sources[i]);
|
|
}
|
|
}
|
|
|
|
//Destroy all sources
|
|
alDeleteSources(m_numSources,m_sources);
|
|
}*/
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
PatchLevelOfDetail::TestInstance() const
|
|
{
|
|
AudioLevelOfDetail::TestInstance();
|
|
return True;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
PatchLevelOfDetail::SetupPatch(SourceSet sourceSet)
|
|
{
|
|
// Check(this);
|
|
//// Check(channel);
|
|
//
|
|
// #ifdef LAB_ONLY
|
|
// setupCount++;
|
|
// #endif
|
|
SAMPLEINFO info;
|
|
|
|
//Attach buffers
|
|
for (int i=0; i < sourceSet.count; i++)
|
|
{
|
|
info = PRESET_getSampleInfo(bankID,patchID,i);
|
|
if (info.bufferIndex >= 0)
|
|
{
|
|
ALenum sourceState;
|
|
alGetSourcei(sourceSet.sources[i], AL_SOURCE_STATE, &sourceState);
|
|
|
|
if (sourceState == AL_INITIAL)
|
|
{
|
|
alSourcei(sourceSet.sources[i],AL_BUFFER,AL_getBuffer(info.bufferIndex));
|
|
{ static int s_a=0; if (getenv("BT_AUDIO_LOG") && s_a++<8) DEBUG_STREAM << "[audio] SetupPatch attach bank=" << (int)bankID << " patch=" << (int)patchID << " buf=" << info.bufferIndex << "\n" << std::flush; }
|
|
alSourcei(sourceSet.sources[i],AL_SOURCE_RELATIVE,AL_TRUE);
|
|
|
|
AudioRenderer *render = application->GetAudioRenderer();
|
|
AudioHead *head = render->GetAudioHead();
|
|
|
|
alSource3f(sourceSet.sources[i],AL_POSITION,0,0,0);
|
|
alSource3f(sourceSet.sources[i],AL_VELOCITY,0,0,0);
|
|
|
|
if (info.loop == ForceStatic)
|
|
{
|
|
alSourcei(sourceSet.sources[i],AL_LOOPING,0);
|
|
} else
|
|
{
|
|
alSourcei(sourceSet.sources[i],AL_LOOPING,1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Set the channel to the bank and program, reset patch controls
|
|
//
|
|
/* channel->SelectBank(bankID);
|
|
channel->SendProgramChange(patchID);
|
|
channel->SendController(MIDI_CONTROL_RESET, MIDI_MAX_CONTROL_VALUE);*/
|
|
|
|
//TODO: Sync up OpenAL source info appropriately
|
|
|
|
/*#if 0
|
|
Tell((int)bankID << ":" << (int)patchID << "\n");
|
|
#endif*/
|
|
}
|
|
|
|
void PatchLevelOfDetail::PlayNote(SourceSet sourceSet)
|
|
{
|
|
if (sourceSet.count > 0 && sourceSet.sources != NULL)
|
|
{
|
|
alGetError();
|
|
//DEBUG_STREAM << "Playing bank " << (int)bankID << ", patch " << (int)patchID << std::endl;
|
|
|
|
for (int i = 0; i < sourceSet.count; i++)
|
|
{
|
|
ALenum state;
|
|
alGetSourcei(sourceSet.sources[i], AL_SOURCE_STATE, &state);
|
|
|
|
if (state != AL_PLAYING)
|
|
{
|
|
alSourcePlay(sourceSet.sources[i]);
|
|
{ static int s_p=0; if (getenv("BT_AUDIO_LOG") && s_p++<8) DEBUG_STREAM << "[audio] PlayNote alSourcePlay src=" << sourceSet.sources[i] << "\n" << std::flush; }
|
|
}
|
|
|
|
ALenum error = alGetError();
|
|
if (error != AL_NO_ERROR)
|
|
{
|
|
DEBUG_STREAM << "Playing hit an error: " << error << std::endl;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PatchLevelOfDetail::StopNote(SourceSet sourceSet)
|
|
{
|
|
if (sourceSet.count >0 && sourceSet.sources != NULL)
|
|
{
|
|
alSourceStopv(sourceSet.count, sourceSet.sources);
|
|
alSourceRewindv(sourceSet.count, sourceSet.sources);
|
|
}
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PatchResource ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
PatchResource::PatchResource(PlugStream *stream):
|
|
AudioResource(stream)
|
|
{
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
PatchResource::BuildFromPage(
|
|
PlugStream *stream,
|
|
NameList *name_list,
|
|
ClassID class_ID,
|
|
ObjectID object_ID
|
|
)
|
|
{
|
|
AudioResource::BuildFromPage(stream, name_list, class_ID, object_ID);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
PatchResource::~PatchResource()
|
|
{
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
PatchResource::TestInstance() const
|
|
{
|
|
AudioResource::TestInstance();
|
|
return True;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
PatchResource::SetupPatch(SourceSet sourceSet)
|
|
{
|
|
Check(this);
|
|
// Check(channel);
|
|
|
|
PatchLevelOfDetail *patch_level_of_detail =
|
|
Cast_Object(PatchLevelOfDetail*, GetAudioLevelOfDetail());
|
|
|
|
Check(patch_level_of_detail);
|
|
patch_level_of_detail->SetupPatch(sourceSet);
|
|
}
|
|
|
|
void PatchResource::PlayNote(SourceSet sourceSet)
|
|
{
|
|
Check(this);
|
|
// Check(channel);
|
|
|
|
PatchLevelOfDetail *patch_level_of_detail =
|
|
Cast_Object(PatchLevelOfDetail*, GetAudioLevelOfDetail());
|
|
|
|
Check(patch_level_of_detail);
|
|
patch_level_of_detail->PlayNote(sourceSet);
|
|
}
|
|
|
|
void PatchResource::StopNote(SourceSet sourceSet)
|
|
{
|
|
Check(this);
|
|
// Check(channel);
|
|
|
|
PatchLevelOfDetail *patch_level_of_detail =
|
|
Cast_Object(PatchLevelOfDetail*, GetAudioLevelOfDetail());
|
|
|
|
Check(patch_level_of_detail);
|
|
patch_level_of_detail->StopNote(sourceSet);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
MIDINRPNValue
|
|
PatchResource::GetMaxMIDIFilterCutoff()
|
|
{
|
|
Check(this);
|
|
|
|
PatchLevelOfDetail *patch_level_of_detail =
|
|
Cast_Object(PatchLevelOfDetail*, GetAudioLevelOfDetail());
|
|
|
|
Check(patch_level_of_detail);
|
|
return patch_level_of_detail->GetMaxMIDIFilterCutoff();
|
|
}
|