Files
BT411/engine/MUNGA/AUDSEQ.cpp
T
Joe DiPrimaandClaude Opus 5 e058b6acb7 #99: trace the coolant-leak alarm end to end -- it IS implemented; found the real defect
Oracle: "alarm is not sounding despite an active leak" ... "was sounding,
resolved a leak, but there was another".

RETRACTION FIRST.  I previously told the user the coolant alarm was "genuinely
unbuilt -- there is no alarm implementation to starve".  That was wrong, and it
was asserted without checking.  The alarm is fully implemented and the entire
chain works.  Traced live, in order:

  Condenser6.ReportLeak (= HeatSink::coolantActive) changes 0 -> 1
    -> [watchpoll] CHANGE on that exact address
    -> [matchfire] val=1 -> ctl 1  (StartAudioControlID)
    -> [seqstart] the alarm AudioControlSequence, events=25 looped=1
    -> [seqsend] ctl 8/16 (select), 6/1 (volume), 1/0 (START), 2/0 (STOP), ...
       a three-part looping alarm: two chirps then an 8s sustained tone.

The authored design, read from BTL4.RES: 19 subsystems per mech each bind TWO
AudioLogicalTriggers to their ReportLeak flag -- match 1 -> Start, match 0 ->
Stop -- and ALL of them drive ONE shared alarm sequence.

SO WHY WAS IT SILENT IN .674?  Almost certainly the audio source pool, fixed
after that build in ad9dfad.  Every player log from .674 is saturated with
ACQUIRE FAILED (3k-6.5k lines each, starting ~10% in and never recovering); an
alarm that cannot acquire an OpenAL source is silent.  The bench here shows 0
acquire failures on the current build.  ⚠ NOT PROVEN: this bench has no audio
device (pool census reads live=0 pooled=0), so the control chain is verified but
final playback is not.  Field confirmation needed.

THE SECOND SYMPTOM IS A REAL, REPRODUCED DEFECT AND IT IS IN THE AUTHORED DATA.
All 19 subsystems -- Condenser1-6, GeneratorA-D, Myomers, PPC_1/2, ERMLaser_1-3,
SRM6_1/2, Avionics -- share the single alarm sequence, and EACH one's leak-clear
sends an UNCONDITIONAL Stop.  So the moment any one of them stops leaking the
alarm goes silent, even while others are still leaking.  That is exactly what was
reported.  Since both the authoring and the MUNGA watcher code are authentic,
this is 1995 behaviour, and changing it is a deliberate divergence -- flagged for
a fidelity call rather than "fixed" unilaterally.

Diagnostics added (all under the existing BT_ATTRBIND_LOG gate):
  [seqcfg]  extended: the sequence's authored event list
  [seqstart] extended: events / looped / tempo / divisionsPerBeat
  [seqrun]  RunSequence entry state (isRunning / iterator / current event)
  [seqwait] an event exists but is not yet ready, with the time delta
  [seqsend] the sequence actually emitting a control
  [idlewatch] which components receive the idle tick that advances sequences
  [hsparm] extended with &ReportLeak so audio bindings can be correlated
plus scratchpad/night8/leakaudio.sh.

NOTE ON THE PROBES, because it cost real time twice today: [seqrun] first used a
single shared static counter, which the busy sequences consumed so the alarm
sequence never printed -- reading as "RunSequence is never called".  It is now
throttled PER SEQUENCE (runProbeCount).  A capped diagnostic that is silent is
not evidence of absence; the same trap produced a false "seqsend x0" and a false
"the trigger never fires" earlier in this investigation.

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

917 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.
if (getenv("BT_ATTRBIND_LOG") && 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 (getenv("BT_ATTRBIND_LOG")) { 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 (getenv("BT_ATTRBIND_LOG")) { 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
}