Files
BT411/engine/MUNGA/AUDSEQ.cpp
T
Joe DiPrimaandClaude Opus 5 5a1a407ed1 pre-ship: resolve the new audio diagnostic gates ONCE instead of per frame
RunSequence and AudioIdleWatcher::Execute run per sequence per frame, so the
BT_ATTRBIND_LOG probes added while chasing #99 were calling getenv ~2700x/sec on
a 15-sequence mech.  Small, but this is the same path the source-pooling fix
(#32) just bought 10% of frame time in, and an unset diagnostic should cost
nothing.  Cached in function-static flags.

Pre-ship smoke (combat, 2 kills, a respawn, clean teardown):
  frame time 7.18 / 8.27 / 8.71 ms  -- matches the post-pooling 7.79ms baseline
  0 asserts, 0 access violations, 0 audio acquireFails

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 20:11:06 -05:00

918 lines
22 KiB
C++

#include <cstdlib>
#include "munga.h"
#pragma hdrstop
#include "audseq.h"
#include "objstrm.h"
#include "namelist.h"
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~ AudioControlEvent ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//#############################################################################
//#############################################################################
//
AudioControlEvent::AudioControlEvent(
AudioTick start_tick,
AudioControlID audio_control_ID,
AudioControlValue audio_control_value
):
Plug(AudioControlEventClassID)
{
startTick = start_tick;
audioControlID = audio_control_ID;
audioControlValue = audio_control_value;
}
//
//#############################################################################
//#############################################################################
//
AudioControlEvent::AudioControlEvent():
Plug(AudioControlEventClassID)
{
startTick = 0;
audioControlID = NullAudioControlID;
audioControlValue = 0.0f;
}
//
//#############################################################################
//#############################################################################
//
AudioControlEvent::~AudioControlEvent()
{
}
//
//#############################################################################
//#############################################################################
//
Logical
AudioControlEvent::TestInstance() const
{
Plug::TestInstance();
Verify(
(Enumeration)audioControlID >= (Enumeration)0 &&
(Enumeration)audioControlID < (Enumeration)AudioControlIDCount
);
return True;
}
//
//#############################################################################
//#############################################################################
//
void
AudioControlEvent::Send(AudioComponent *audio_component)
{
Check(this);
Check(audio_component);
if (getenv("BT_AUDIO_SPATIAL")) { static int s_ev=0; if (s_ev++<200)
DEBUG_STREAM << "[seqev] tgt=" << (void*)audio_component
<< " ctl=" << (int)audioControlID << "/" << audioControlValue << "\n" << std::flush; }
audio_component->ReceiveControl(audioControlID, audioControlValue);
}
//
//#############################################################################
//#############################################################################
//
Logical
AudioControlEvent::Chase(AudioComponent *audio_component)
{
Check(this);
Check(audio_component);
switch (audioControlID)
{
case StartAudioControlID:
return False;
case StopAudioControlID:
Send(audio_component);
return False;
default:
break;
}
return True;
}
//
//#############################################################################
//#############################################################################
//
MemoryStream&
MemoryStream_Read(
MemoryStream *stream,
AudioControlEvent *control_event
)
{
Check(stream);
Check_Signature(control_event);
MemoryStream_Read(stream, &control_event->startTick);
MemoryStream_Read(stream, &control_event->audioControlID);
MemoryStream_Read(stream, &control_event->audioControlValue);
Check(control_event);
return *stream;
}
//
//#############################################################################
//#############################################################################
//
MemoryStream&
MemoryStream_Write(
MemoryStream *stream,
const AudioControlEvent *control_event
)
{
Check(stream);
Check(control_event);
MemoryStream_Write(stream, &control_event->startTick);
MemoryStream_Write(stream, &control_event->audioControlID);
MemoryStream_Write(stream, &control_event->audioControlValue);
return *stream;
}
//
//#############################################################################
//#############################################################################
//
std::ostream& operator << (std::ostream &strm, const AudioControlEvent &control_event)
{
Check(&control_event);
strm << "[" ;
strm << control_event.startTick << ",";
strm << control_event.audioControlID << ",";
strm << control_event.audioControlValue;
strm << "]";
return strm;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~ AudioControlSequence ~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//#############################################################################
//#############################################################################
//
AudioControlSequence::AudioControlSequence(
PlugStream *stream,
Entity *entity
):
AudioComponent(stream),
audioComponentSocket(NULL),
audioControlEventSocket(NULL)
{
Logical dump_value;
Logical is_looped;
AudioComponent *audio_component;
AudioControlEvent *audio_control_event;
CollectionSize i, number_of_control_events;
AudioDivisionsPerBeat divisions_per_beat;
AudioTempo tempo;
MemoryStream_Read(stream, &dump_value);
MemoryStream_Read(stream, &is_looped);
PlugStream_ReadObjectIDAndFindPlug(stream, &audio_component);
MemoryStream_Read(stream, &divisions_per_beat);
MemoryStream_Read(stream, &tempo);
MemoryStream_Read(stream, &number_of_control_events);
for (i = 0; i < number_of_control_events; i++)
{
audio_control_event = new AudioControlEvent;
Register_Object(audio_control_event);
MemoryStream_Read(stream, audio_control_event);
audioControlEventSocket.Add(audio_control_event);
}
if (getenv("BT_ATTRBIND_LOG")) { static int s_sq=0; if (s_sq++<60) {
DEBUG_STREAM << "[seqcfg] seq=" << (void*)this << " tgt=" << (void*)audio_component
<< " looped=" << (int)is_looped << " div=" << (int)divisions_per_beat
<< " tempo=" << (int)tempo << " events=" << (int)number_of_control_events << " ";
{ SChainIteratorOf<AudioControlEvent*> it(&audioControlEventSocket);
AudioControlEvent *e; int n=0;
while ((e = it.ReadAndNext()) != NULL && n++ < 24)
DEBUG_STREAM << *e; }
DEBUG_STREAM << "\n" << std::flush; } }
AudioControlSequenceX(
audio_component,
entity,
is_looped,
divisions_per_beat,
tempo,
dump_value
);
}
//
//#############################################################################
//#############################################################################
//
#if DEBUG_LEVEL>0
void
AudioControlSequence::AudioControlSequenceX(
AudioComponent *audio_component,
Entity *entity,
Logical is_looped,
AudioDivisionsPerBeat divisions_per_beat,
AudioTempo the_tempo,
Logical dump_value
)
#else
void
AudioControlSequence::AudioControlSequenceX(
AudioComponent *audio_component,
Entity *entity,
Logical is_looped,
AudioDivisionsPerBeat divisions_per_beat,
AudioTempo the_tempo,
Logical
)
#endif
{
Check(audio_component);
audioComponentSocket.Add(audio_component);
isLooped = is_looped;
runProbeCount = 0; // #99 diag
isRunning = False;
startTime = AudioTime::Null;
audioControlEventIterator = NULL;
divisionsPerBeat = divisions_per_beat;
tempo = the_tempo;
#if DEBUG_LEVEL>0
dumpValue = dump_value;
#endif
audio_component->AddWatcher(this);
Check(entity);
entity->AddAudioComponent(this);
}
//
//#############################################################################
//#############################################################################
//
AudioControlSequence::~AudioControlSequence()
{
StopSequence();
Verify(!isRunning);
Verify(audioControlEventIterator == NULL);
AudioControlEventIterator iterator(&audioControlEventSocket);
Check(&iterator);
iterator.DeletePlugs();
}
//
//#############################################################################
//#############################################################################
//
void
AudioControlSequence::BuildFromPage(
PlugStream *stream,
NameList *name_list,
ClassID class_ID,
ObjectID object_ID
)
{
AudioComponent::BuildFromPage(stream, name_list, class_ID, object_ID);
MEM_STRM_WRITE_ENTRY(*stream, name_list, Logical, dump_value);
MEM_STRM_WRITE_ENTRY(*stream, name_list, Logical, is_looped);
//
// Write audio component
//
CString audio_component_name("audio_component");
PlugStream_FindEntryAndWriteObjectID(stream, name_list, audio_component_name);
//
// Read tempo
//
AudioTempo tempo = 100;
if (name_list->FindData("tempo") != NULL)
{
Check_Pointer(name_list->FindData("tempo"));
Convert_From_Ascii((const char *)name_list->FindData("tempo"), &tempo);
}
//
// Read midi file
//
DynamicMemoryStream midi_file_stream;
{
CString midi_file_name;
Check_Pointer(name_list->FindData("midi_file"));
midi_file_name = (const char*)name_list->FindData("midi_file");
std::ifstream input_midi_file(midi_file_name, std::ios::in | std::ios::binary);
#if DEBUG_LEVEL>0
if (!input_midi_file)
{
Dump(midi_file_name);
}
#endif
MemoryStream_Write(&midi_file_stream, &input_midi_file);
midi_file_stream.Rewind();
}
//
// Parse midi stream
//
DynamicMemoryStream control_event_stream;
CreateAudioControlEventStream midi_parser;
Check(&midi_file_stream);
Check(&control_event_stream);
Check(&midi_parser);
midi_parser.Parse(&midi_file_stream, &control_event_stream, tempo);
control_event_stream.Rewind();
//
// Write the divisions per beat and tempo
//
AudioDivisionsPerBeat divisions_per_beat;
divisions_per_beat = midi_parser.GetDivisionsPerBeat();
tempo = midi_parser.GetTempo();
MemoryStream_Write(stream, &divisions_per_beat);
MemoryStream_Write(stream, &tempo);
//
// Write control event stream
//
CollectionSize number_of_control_events;
number_of_control_events = midi_parser.GetNumberOfEvents();
MemoryStream_Write(stream, &number_of_control_events);
MemoryStream_Write(stream, &control_event_stream);
}
//
//#############################################################################
//#############################################################################
//
Logical
AudioControlSequence::TestInstance() const
{
AudioComponent::TestInstance();
Check(&audioComponentSocket);
Check(&audioControlEventSocket);
if (isRunning)
{
Check(audioControlEventIterator);
}
else
{
Verify(audioControlEventIterator == NULL);
}
Verify(tempo >= 1 && tempo <= 600);
return True;
}
//
//#############################################################################
//#############################################################################
//
void
AudioControlSequence::StartSequence()
{
if (getenv("BT_ATTRBIND_LOG")) { static int s_ss=0; if (s_ss++<40)
{ AudioControlEventIterator probe(&audioControlEventSocket);
DEBUG_STREAM << "[seqstart] seq=" << (void*)this
<< " events=" << (int)probe.GetSize()
<< " looped=" << (int)isLooped
<< " tempo=" << (int)tempo
<< " divPerBeat=" << (int)divisionsPerBeat << std::endl; } }
Check(this);
//
// If already running, stop sequence
//
if (isRunning)
{
StopSequence();
}
//
// Make an iterator for the control sequence
//
Verify(audioControlEventIterator == NULL);
audioControlEventIterator =
new AudioControlEventIterator(&audioControlEventSocket);
Register_Object(audioControlEventIterator);
startTime = AudioTime::Now();
isRunning = True;
}
//
//#############################################################################
//#############################################################################
//
void
AudioControlSequence::StopSequence()
{
Check(this);
//
// If not running, return
//
if (!isRunning)
return;
//
// Find next on or off event
//
AudioControlEvent *control_event;
Check(audioControlEventIterator);
while ((control_event = audioControlEventIterator->ReadAndNext()) != NULL)
{
Check(control_event);
if (!control_event->Chase(audioComponentSocket.GetCurrent()))
break;
}
//
// Destroy the iterator for the control sequence
//
Unregister_Object(audioControlEventIterator);
delete audioControlEventIterator;
audioControlEventIterator = NULL;
isRunning = False;
}
//
//#############################################################################
//#############################################################################
//
void
AudioControlSequence::RunSequence()
{
Check(this);
//
// If the sequence is not running, then return
//
// #99 probe: PER-SEQUENCE throttle. A single shared counter here hid the
// alarm sequence entirely -- the busy sequences ate every slot.
static const int s_seqDiag = (getenv("BT_ATTRBIND_LOG") != 0); // hot path: resolve once
if (s_seqDiag && isLooped) { if (runProbeCount < 6)
{ ++runProbeCount;
DEBUG_STREAM << "[seqrun] seq=" << (void *)this
<< " isRunning=" << (int)isRunning
<< " iter=" << (void *)audioControlEventIterator
<< " cur=" << (void *)(audioControlEventIterator ? audioControlEventIterator->GetCurrent() : 0)
<< std::endl;
} }
if (!isRunning)
return;
//
// Play events which have a start time less then the
// current time
//
AudioControlEvent *control_event;
Check(audioControlEventIterator);
control_event = audioControlEventIterator->GetCurrent();
if (s_seqDiag) { static int s_nr=0;
if (control_event != NULL && !IsEventReady(control_event) && (s_nr++ % 500)==0)
{
AudioTime off(startTime);
off += CalculateEventSeconds(control_event);
DEBUG_STREAM << "[seqwait] seq=" << (void *)this
<< " eventSeconds=" << CalculateEventSeconds(control_event)
<< " readyAt-now=" << (Scalar)(off - AudioTime::Now())
<< std::endl;
} }
while (
control_event != NULL &&
IsEventReady(control_event)
)
{
#if DEBUG_LEVEL>0
if (dumpValue)
{
Dump(*control_event);
}
#endif
#if 0
//
// HACK - convert start control to use duration
//
Verify(control_event->audioControlID != StopAudioControlID);
if (control_event->audioControlID == StartAudioControlID)
{
//
// Convert ticks to seconds
// seconds = ticks / (divisionsPerBeat (t/b) * tempo (b/60s))
//
Scalar denominator =
(Scalar)divisionsPerBeat * (Scalar)tempo / 60.0f;
Verify(!Small_Enough(denominator));
control_event->audioControlValue =
control_event->audioControlValue / denominator;
}
#endif
//
// Send the controller to the connected audio component
//
Check(control_event);
if (s_seqDiag) { static int s_se=0; if (s_se++<60)
DEBUG_STREAM << "[seqsend] seq=" << (void *)this
<< " -> comp=" << (void *)audioComponentSocket.GetCurrent()
<< " ctl=" << (int)control_event->audioControlID
<< "/" << control_event->audioControlValue << std::endl; }
control_event->Send(audioComponentSocket.GetCurrent());
//
// Step to next event
//
audioControlEventIterator->Next();
control_event = audioControlEventIterator->GetCurrent();
}
//
// If there are no more events to be played then stop running
//
if (control_event == NULL)
{
StopSequence();
if (isLooped)
{
StartSequence();
}
}
}
//
//#############################################################################
//#############################################################################
//
void
AudioControlSequence::ReceiveControl(
AudioControlID control_ID,
AudioControlValue control_value
)
{
Check(this);
switch(control_ID)
{
case StartAudioControlID:
StartSequence();
break;
case StopAudioControlID:
StopSequence();
break;
case IdleAudioControlID:
RunSequence();
break;
case TempoAudioControlID:
tempo = control_value;
Verify(tempo >= 1 && tempo <= 600);
break;
default:
break;
}
}
//
//#############################################################################
//#############################################################################
//
Logical
AudioControlSequence::IsEventReady(AudioControlEvent *audio_control_event)
{
Check(this);
AudioTime offset_time(startTime);
offset_time += CalculateEventSeconds(audio_control_event);
return (offset_time <= AudioTime::Now());
}
//
//#############################################################################
//#############################################################################
//
Scalar
AudioControlSequence::CalculateEventSeconds(
AudioControlEvent *audio_control_event
)
{
Check(this);
Check(audio_control_event);
//
// Convert ticks to seconds
// seconds = ticks / (divisionsPerBeat (t/b) * tempo (b/60s))
//
Scalar denominator = (Scalar)divisionsPerBeat * (Scalar)tempo / 60.0f;
Verify(!Small_Enough(denominator));
return (Scalar)audio_control_event->GetStartTick() / denominator;
}
//#############################################################################
//#################### CreateAudioControlEventStream ####################
//#############################################################################
CreateAudioControlEventStream::CreateAudioControlEventStream():
audioControlEventSocket(NULL, False)
{
inputStream = NULL;
outputStream = NULL;
divisionsPerBeat = 120;
tempo = 100;
}
//
//#############################################################################
//#############################################################################
//
CreateAudioControlEventStream::~CreateAudioControlEventStream(void)
{
AudioControlEventIterator iterator(&audioControlEventSocket);
Check(&iterator);
iterator.DeletePlugs();
}
//
//#############################################################################
//#############################################################################
//
Logical
CreateAudioControlEventStream::TestInstance() const
{
MidiParse::TestInstance();
Check(&audioControlEventSocket);
return True;
}
//
//#############################################################################
//#############################################################################
//
void
CreateAudioControlEventStream::Parse(
MemoryStream *input_stream,
MemoryStream *output_stream,
SignedLongWord tempo_argument
)
{
Check(this);
Check(input_stream);
Check(output_stream);
inputStream = input_stream;
outputStream = output_stream;
//
// Parse the stream
//
tempo = tempo_argument;
Verify(GetNumberOfEvents() == 0);
MidiParse::Parse();
//
// Write the events
//
AudioControlEventIterator iterator(&audioControlEventSocket);
AudioControlEvent *control_event;
Check(&iterator);
while ((control_event = iterator.ReadAndNext()) != NULL)
{
Check(control_event);
MemoryStream_Write(outputStream, control_event);
}
}
//
//#############################################################################
//#############################################################################
//
CollectionSize
CreateAudioControlEventStream::GetNumberOfEvents()
{
Check(this);
AudioControlEventIterator iterator(&audioControlEventSocket);
Check(&iterator);
return iterator.GetSize();
}
//
//#############################################################################
//#############################################################################
//
MidiParse::SignedWord
CreateAudioControlEventStream::Mf_getc()
{
Check(this);
unsigned char c;
MemoryStream_Read(inputStream, &c);
return c;
}
//
//#############################################################################
//#############################################################################
//
void
CreateAudioControlEventStream::Mf_error(char *str)
{
Check(this);
Check_Pointer(str);
Fail(str);
}
//
//#############################################################################
//#############################################################################
//
void
CreateAudioControlEventStream::Mf_header(
SignedWord,
SignedWord,
SignedWord division
)
{
Check(this);
divisionsPerBeat = division;
}
//
//#############################################################################
//#############################################################################
//
void
CreateAudioControlEventStream::Mf_on(
SignedWord channel,
SignedWord pitch,
SignedWord vol
)
{
Check(this);
//
// Interpret vol 0 as note off
//
if (vol == 0)
{
Mf_off(channel, pitch, vol);
return;
}
//
// Verify that last event was a stop
//
#if DEBUG_LEVEL>0
{
AudioControlEventIterator iterator(&audioControlEventSocket);
AudioControlEvent *last_control_event;
iterator.Last();
if ((last_control_event = iterator.GetCurrent()) != NULL)
{
Check(last_control_event);
Verify(last_control_event->audioControlID == StopAudioControlID);
}
}
#endif
AudioTick current_ticks = CurTime();
AudioControlEvent *audio_control_event;
//
// Add note value event
//
audio_control_event
= new AudioControlEvent(
current_ticks,
NoteAudioControlID,
(Scalar)pitch
);
Register_Object(audio_control_event);
audioControlEventSocket.AddValue(audio_control_event, current_ticks);
//
// Add velocity event
//
audio_control_event
= new AudioControlEvent(
current_ticks,
AttackVolumeAudioControlID,
(Scalar)vol / (Scalar)127
);
Register_Object(audio_control_event);
audioControlEventSocket.AddValue(audio_control_event, current_ticks);
//
// Add start event
//
audio_control_event
= new AudioControlEvent(
current_ticks,
StartAudioControlID,
0.0f
);
Register_Object(audio_control_event);
audioControlEventSocket.AddValue(audio_control_event, current_ticks);
}
//
//#############################################################################
//#############################################################################
//
void
CreateAudioControlEventStream::Mf_off(
SignedWord,
SignedWord pitch,
SignedWord
)
{
Check(this);
AudioTick current_ticks = CurTime();
AudioControlEvent *audio_control_event;
#if 0
//
// HACK - convert start control to use duration
//
AudioControlEventIterator
iterator(&audioControlEventSocket);
iterator.Last();
audio_control_event = iterator.GetCurrent();
Check(audio_control_event);
Verify(audio_control_event->audioControlID == StartAudioControlID);
audio_control_event->audioControlValue =
current_ticks - audio_control_event->startTick;
#else
//
// Add note value event
//
audio_control_event
= new AudioControlEvent(
current_ticks,
NoteAudioControlID,
(Scalar)pitch
);
Register_Object(audio_control_event);
audioControlEventSocket.AddValue(audio_control_event, current_ticks);
//
// Add stop event
//
audio_control_event
= new AudioControlEvent(
current_ticks,
StopAudioControlID,
0.0f
);
Register_Object(audio_control_event);
audioControlEventSocket.AddValue(audio_control_event, current_ticks);
#endif
}
//
//#############################################################################
//#############################################################################
//
void
CreateAudioControlEventStream::Mf_tempo(SignedLongWord)
{
Check(this);
// tempo = i1; // HACK - Cakewalk appears to produce bogus tempo
}