Files
BT411/engine/MUNGA_L4/L4AUDHDW.cpp
arcattackandClaude Opus 4.8 7b7d465e5e Initial commit: bt411 -- standalone Windows BattleTech (Tesla 4.10 port)
Clean, self-contained extraction of the BattleTech-specific work from the
reverse-engineering workspace -- engine + game + content + build, with nothing
from Red Planet or the raw archive dumps. Builds green (Win32) and runs the
single-player drive->animate->target->fire->damage->destroy loop out of the box.

Layout:
  engine/   MUNGA + MUNGA_L4 shared 2007 engine, carrying our BT render/loader
            work (bgfload/L4D3D/L4VIDEO: BSL bit-slice decode, LOD/ground/shadow
            models) + image codec; the minimal rp/ headers the audio HAL needs
  game/     reconstructed BT logic + surviving-original BT source + fwd shims
            + WinMain launcher
  content/  full runtime tree (BTL4.RES, VIDEO/, GAUGE/, AUDIO/, eggs, BTDPL.INI)
  docs/     format specs + reconstruction ledgers
  reference/ raw Ghidra pseudocode (recon source-of-truth) + decomp exporter
  tools/    MP console emulator + map/resource scanners

One top-level CMake builds munga_engine lib + bt410_l4 game lib + btl4.exe.
All paths relativized (186 fwd shims + ~437 CMake abs paths -> repo-relative);
DXSDK is the one external, overridable via -DDXSDK. Verified: builds to a
byte-identical 2.27MB exe and runs combat (TARGET DESTROYED, 0 crashes) against
the bundled content.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 21:03:40 -05:00

991 lines
22 KiB
C++

#include "mungal4.h"
#pragma hdrstop
#include "l4audhdw.h"
//#if defined(AUDIO_HARDWARE)
// #include "sos\bc4\sosmawe.h"
//#endif
#if defined(TRACE_AUDIO_RENDERER_MIDI)
BitTrace Audio_Renderer_MIDI("Audio Renderer MIDI");
#endif
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MPU Emulation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#define _inp inportb
#define _outp outportb
#define MPUPort(x) ((x)+mpuAddx)
#define SBCPort(x) ((x)+srcAddx)
/* DSP defines */
#define MPU_ACK_OK 0xfe
#define MPU_RESET_CMD 0xff
#define MPU_ENTER_UART 0x3f
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MIDIMessage ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//#############################################################################
// TestInstance
//#############################################################################
//
Logical
MIDIMessage::TestInstance() const
{
return True;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
std::ostream&
operator << (
std::ostream &strm,
const MIDIMessage &midi_message
)
{
Check(&midi_message);
strm << "[";
strm << (int)(midi_message.message[0] & 0x0F) << ",";
strm << (int)(midi_message.message[0] >> 4) << ",";
strm << (int)midi_message.message[1] << ",";
strm << (int)midi_message.message[2] << "]";
return strm;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ AudioChannel ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//#############################################################################
//#############################################################################
//
/*AudioChannel::AudioChannel(
AudioChannelType audio_channel_type,
MIDIChannel midi_channel,
AudioCard *audio_card
)
{
audioChannelType = audio_channel_type;
midiChannel = midi_channel;
voicesUsed = 0;
Check(audio_card);
audioCard = audio_card;
}
//
//#############################################################################
//#############################################################################
//
AudioChannel::~AudioChannel()
{
Verify(voicesUsed == 0);
}
//
//#############################################################################
// TestInstance
//#############################################################################
//
Logical
AudioChannel::TestInstance() const
{
Plug::TestInstance();
Verify(voicesUsed >=0 && voicesUsed <= AWE_VOICE_COUNT);
Check(audioCard);
return True;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ AudioCard ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//#############################################################################
// AudioCard
//#############################################################################
//
AudioCard::AudioCard():
idleChannelSocket(NULL),
percussionChannelSocket(NULL)
{
MIDIChannel channel;
idleVoices = AWE_VOICE_COUNT;
for (channel = 0; channel < AWE_CHANNEL_COUNT; channel++)
{
AudioChannel *audio_channel;
if (channel == AWE_PERCUSSIVE_CHANNEL)
{
audio_channel =
new AudioChannel(
PercussionAudioChannelType,
channel,
this
);
Register_Object(audio_channel);
Check(&percussionChannelSocket);
percussionChannelSocket.Add(audio_channel);
}
else
{
audio_channel =
new AudioChannel(
InstrumentAudioChannelType,
channel,
this
);
Register_Object(audio_channel);
Check(&idleChannelSocket);
idleChannelSocket.Add(audio_channel);
}
}
}
//
//#############################################################################
// ~AudioCard
//#############################################################################
//
AudioCard::~AudioCard()
{
//
// Delete the idle channels
//
{
ChainIteratorOf<AudioChannel*> iterator(&idleChannelSocket);
Check(&iterator);
Verify(idleVoices == AWE_VOICE_COUNT);
Verify(iterator.GetSize() == AWE_CHANNEL_COUNT - 1);
iterator.DeletePlugs();
}
//
// Delete the percussive channel
//
{
ChainIteratorOf<AudioChannel*> iterator(&percussionChannelSocket);
Check(&iterator);
Verify(idleVoices == AWE_VOICE_COUNT);
Verify(iterator.GetSize() == 1);
iterator.DeletePlugs();
}
}
//
//#############################################################################
// TestInstance
//#############################################################################
//
Logical
AudioCard::TestInstance() const
{
Node::TestInstance();
Verify(idleVoices >= 0 && idleVoices <= AWE_VOICE_COUNT);
Check(&idleChannelSocket);
return True;
}
//
//#############################################################################
// Initialize
//#############################################################################
//
void
AudioCard::Initialize()
{
#ifdef AUDIO_HARDWARE
Tell("AudioCard::Initialize - Start\n");
WORD wError;
//
// Set the selector of the pointer to the driver memory for the
// MIDI driver to _NULL. this will tell the load driver routine
// to allocate new memory for the MIDI driver
//
#if defined(__BCPLUSPLUS__)
sSOSMIDIInitDriver.lpDriverMemory.sel = _NULL;
#else
sSOSMIDIInitDriver.lpDriverMemory = _NULL;
#endif
//
// Setup the port for the MIDI driver to use
//
// sSOSMIDIHardware.wPort = _MIDI_DRIVER_PORT;
sSOSMIDIHardware.wPort = emuAddx;
//
// Load and initialize the MIDI driver
//
wError = sosMIDIInitDriver(
_MIDI_DRIVER_TYPE,
&sSOSMIDIHardware,
&sSOSMIDIInitDriver,
&wDriverHandle
);
if (wError)
{
std::cout << sosGetErrorString(wError);
Fail("AudioCard::Initialize - sosMIDIInitDriver failed\n");
}
Tell("AudioCard::Initialize - Finish\n");
#endif
}
//
//#############################################################################
// Close
//#############################################################################
//
void
AudioCard::Close()
{
#ifdef AUDIO_HARDWARE
Check(this);
sosMIDIAWE32ReleaseSBKFiles(wDriverHandle);
//
// Reset the midi driver
//
sosMIDIResetDriver(wDriverHandle);
//
// Uninitialize the midi driver and tell it to free the memory allocated
// for the driver
//
sosMIDIUnInitDriver(wDriverHandle, _TRUE);
#endif
}
//
//#############################################################################
// GetEnvironmentSettings
//#############################################################################
//
void
AudioCard::GetEnvironmentSettings(char *env_str)
{
WORD src_addx = 0xffff;
WORD irq_interrupt = 0xffff;
WORD dma_channel = 0xffff;
WORD mpu_addx = 0xffff;
WORD emu_addx = 0xffff;
const char *sz_blaster;
sz_blaster = getenv(env_str);
Check_Pointer(sz_blaster);
while (*sz_blaster) {
if (*sz_blaster == 'a' || *sz_blaster == 'A') {
++sz_blaster;
if (*sz_blaster) {
if (*sz_blaster == ':')
++sz_blaster;
src_addx = GetHexWord(&sz_blaster);
}
}
if (*sz_blaster == 'i' || *sz_blaster == 'I') {
++sz_blaster;
if (*sz_blaster) {
if (*sz_blaster == ':')
++sz_blaster;
irq_interrupt = GetHexWord(&sz_blaster);
}
}
if (*sz_blaster == 'd' || *sz_blaster == 'D') {
++sz_blaster;
if (*sz_blaster) {
if (*sz_blaster == ':')
++sz_blaster;
dma_channel = GetHexWord(&sz_blaster);
}
}
if (*sz_blaster == 'e' || *sz_blaster == 'E') {
++sz_blaster;
if (*sz_blaster) {
if (*sz_blaster == ':')
++sz_blaster;
emu_addx = GetHexWord(&sz_blaster);
}
}
if (*sz_blaster == 'p' || *sz_blaster == 'P') {
++sz_blaster;
if (*sz_blaster) {
if (*sz_blaster == ':')
++sz_blaster;
mpu_addx = GetHexWord(&sz_blaster);
}
}
//
// Skip until blank or end of string
//
while (*sz_blaster && *sz_blaster != ' ')
++sz_blaster;
while (*sz_blaster == ' ')
++sz_blaster;
}
if (emu_addx == 0xffff)
emu_addx = (WORD)(src_addx + 0x400);
srcAddx = src_addx;
irqInterrupt = irq_interrupt;
dmaChannel = dma_channel;
mpuAddx = mpu_addx;
emuAddx = emu_addx;
#if DEBUG_LEVEL>0
Tell("AudioCard::GetEnvironmentSettings: " << env_str << "\n");
#if 0
Dump(srcAddx);
Dump(irqInterrupt);
Dump(dmaChannel);
Dump(mpuAddx);
Dump(emuAddx);
#endif
printf(" srcAddx = %x\n", (unsigned)srcAddx);
printf(" irqInterrupt = %x\n", (unsigned)irqInterrupt);
printf(" dmaChannel = %x\n", (unsigned)dmaChannel);
printf(" mpuAddx = %x\n", (unsigned)mpuAddx);
printf(" emuAddx = %x\n", (unsigned)emuAddx);
#endif
}
//
//#############################################################################
// InitMIDIReceive
// Initializes Sound Blaster to ready to receive data state.
//#############################################################################
//
Logical
AudioCard::InitMIDIReceive()
{
#if 0
volatile DWORD dwCount;
for (dwCount=0; dwCount<0x2000; dwCount++) ;
dwCount = 0x2000;
while (dwCount && _inp(MPUPort(1)) & 0x40) --dwCount;
_outp(MPUPort(1), MPU_RESET_CMD);
for (dwCount=0; dwCount<0x2000; dwCount++) ;
dwCount = 0x2000;
while (dwCount && _inp(MPUPort(1)) & 0x80) --dwCount;
_inp(MPUPort(0));
for (dwCount=0; dwCount<0x2000; dwCount++) ;
dwCount = 0x2000;
while (dwCount && _inp(MPUPort(1)) & 0x40) --dwCount;
_outp(MPUPort(1), MPU_RESET_CMD);
for (dwCount=0; dwCount<0x2000; dwCount++) ;
dwCount = 0x2000;
while (dwCount && _inp(MPUPort(1)) & 0x80) --dwCount;
_inp(MPUPort(0));
for (dwCount=0; dwCount<0x2000; dwCount++) ;
dwCount = 0x2000;
while (dwCount && _inp(MPUPort(1)) & 0x40) --dwCount;
_outp(MPUPort(1), MPU_ENTER_UART);
for (dwCount=0; dwCount<0x2000; dwCount++) ;
dwCount = 0x2000;
while (dwCount && _inp(MPUPort(1)) & 0x80) --dwCount;
if (!dwCount) return True;
if (_inp(MPUPort(0)) != MPU_ACK_OK) return True;
// mask MPU-401 interrupt
_outp(SBCPort(0x4), 0x83);
_outp(SBCPort(0x5), _inp(SBCPort(0x5)) & ~0x04);
#endif
return False;
}
//
//#############################################################################
// CloseMIDIReceive
//#############################################################################
//
void
AudioCard::CloseMIDIReceive()
{
#if 0
volatile DWORD dwCount;
for (dwCount=0; dwCount<0x2000; dwCount++) ;
dwCount = 0x2000;
while (dwCount && _inp(MPUPort(1)) & 0x40) --dwCount;
_outp(MPUPort(1), MPU_RESET_CMD);
for (dwCount=0; dwCount<0x2000; dwCount++) ;
_inp(MPUPort(0));
for (dwCount=0; dwCount<0x2000; dwCount++) ;
dwCount = 0x2000;
while (dwCount && _inp(MPUPort(1)) & 0x40) --dwCount;
_outp(MPUPort(1), MPU_RESET_CMD);
for (dwCount=0; dwCount<0x2000; dwCount++) ;
_inp(MPUPort(0));
#endif
}
//
//#############################################################################
// IsMIDIAvailable
//#############################################################################
//
Logical
AudioCard::IsMIDIAvailable()
{
#if 0
return ((_inp(MPUPort(1)) & 0x80) == 0);
#endif
return False;
}
//
//#############################################################################
// GetMIDIByte
//#############################################################################
//
MIDIValue
AudioCard::GetMIDIByte()
{
#if 0
return (_inp(MPUPort(0)));
#endif
return (MIDIValue)0;
}
//
//#############################################################################
// InitializeChannels
//#############################################################################
//
void
AudioCard::InitializeChannels()
{
Check(this);
//
// Initialize idle channels
//
{
ChainIteratorOf<AudioChannel*> iterator(&idleChannelSocket);
AudioChannel *channel;
Check(&iterator);
Verify(iterator.GetSize() == (AWE_CHANNEL_COUNT-1));
while ((channel = iterator.ReadAndNext()) != NULL)
{
Check(channel);
channel->SendPitchBendRange(MIDI_PITCH_BEND_RANGE);
}
}
//
// Initialize percussive channels
//
{
ChainIteratorOf<AudioChannel*> iterator(&percussionChannelSocket);
AudioChannel *channel;
Check(&iterator);
Verify(iterator.GetSize() == 1);
channel = iterator.GetCurrent();
Check(channel);
channel->SendPitchBendRange(MIDI_PITCH_BEND_RANGE);
}
}
//
//#############################################################################
// LoadSBK
//#############################################################################
//
int
AudioCard::LoadSBK(const char *file_name)
{
#ifdef AUDIO_HARDWARE
Check(this);
Check_Pointer(file_name);
int ret;
Tell("AudioCard::LoadSBK - " << file_name << "\n");
ret = sosMIDIAWE32SetSBKFile(wDriverHandle, file_name);
Verify_And_Dump(ret >= 0, ret);
Tell("AudioCard::LoadSBK - Finished\n");
return ret;
#else
return 1;
#endif
}
//
//#############################################################################
// ReleaseAllBanks
//#############################################################################
//
void
AudioCard::ReleaseAllBanks()
{
#ifdef AUDIO_HARDWARE
Check(this);
sosMIDIAWE32ReleaseSBKFiles(wDriverHandle);
#endif
}
//
//#############################################################################
// MIDI methods
//#############################################################################
//
//
//#############################################################################
//#############################################################################
//
void
AudioCard::SendPitchBend(
MIDIChannel channel,
MIDIPitchBend pitch_bend
)
{
MIDIMessage
message(
MIDI_PITCH_BEND_STATUS,
channel,
(MIDIValue)((pitch_bend+8192)%128),
(MIDIValue)((pitch_bend+8192)/128)
);
SendMIDIMessage(&message, 3);
}
//
//#############################################################################
//#############################################################################
//
void
AudioCard::SendPitchBendRange(
MIDIChannel channel,
MIDIValue sensitivity
)
{
SendController(channel, (MIDIValue)101, (MIDIValue)0);
SendController(channel, (MIDIValue)100, (MIDIValue)0);
SendController(channel, (MIDIValue)6, sensitivity);
}
//
//#############################################################################
//#############################################################################
//
void
AudioCard::SendNRPN(
MIDIChannel channel,
MIDIValue param_number,
MIDINRPNValue value
)
{
//
// Set sound parameter
//
SendController(channel, (MIDIValue)99, (MIDIValue)127);
SendController(channel, (MIDIValue)98, param_number);
//
// Set parameter value
//
SendController(channel, (MIDIValue)6, (MIDIValue)((value+8192)/128) );
SendController(channel, (MIDIValue)38, (MIDIValue)((value+8192)%128) );
}
//
//#############################################################################
//#############################################################################
//
#define SYSEX_MACRO_LENGTH 11
Byte
chorus_sysex[SYSEX_MACRO_LENGTH] =
{
0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x01, 0x38, 0x00, 0x00, 0xF7
};
Byte
reverb_sysex[SYSEX_MACRO_LENGTH] =
{
0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x01, 0x30, 0x00, 0x00, 0xF7
};
void
AudioCard::SelectEffect(
MIDIEffectType type,
MIDIValue effect
)
{
switch (type)
{
case ChorusMIDIEffectType:
chorus_sysex[8] = effect;
SendSysex(chorus_sysex, SYSEX_MACRO_LENGTH);
break;
case ReverbMIDIEffectType:
reverb_sysex[8] = effect;
SendSysex(reverb_sysex, SYSEX_MACRO_LENGTH);
break;
}
}
//
//#############################################################################
//#############################################################################
//
void
AudioCard::SendSysex(
void *data,
size_t length
)
{
#ifdef AUDIO_HARDWARE
WORD wError;
//
// Note - HMI can specify sysex channel via F0, F1, ..., FF
//
#if defined(__BCPLUSPLUS__)
wError = sosMIDISendMIDIData(
wDriverHandle,
(char*)data,
length
);
#else
wError = sosMIDISendMIDIData(
wDriverHandle,
(Byte*)data,
length
);
#endif
if (wError != _ERR_NO_ERROR)
{
Fail("AudioCard::SendSysex - wError != _ERR_NO_ERROR");
}
#endif
}
#if 0
//
//#############################################################################
//#############################################################################
//
void
AudioCard::SendMIDIMessage(MIDIMessage *midi_message)
{
#ifdef AUDIO_HARDWARE
SET_AUDIO_RENDERER_MIDI();
Check(midi_message);
#if 0
long now = Now();
Tell(now << "\t" << (int)srcAddx << "\t" << *midi_message << "\t");
Tell(idleVoices << "\n");
#endif
switch (midi_message->message[0] >> 4) {
case MIDI_NOTE_ON_STATUS:
case MIDI_NOTE_OFF_STATUS:
case MIDI_POLYKEY_STATUS:
case MIDI_CONTROLLER_STATUS:
case MIDI_PITCH_BEND_STATUS:
#if DEBUG_LEVEL>0
{
Word wError = sosMIDISendMIDIData(
wDriverHandle,
(char*)midi_message->message,
3
);
Verify(wError == _ERR_NO_ERROR);
}
#else
sosMIDISendMIDIData(
wDriverHandle,
(char*)midi_message->message,
3
);
#endif
break;
case MIDI_PROGRAM_CHANGE_STATUS:
case MIDI_CHANNEL_PRESSURE_STATUS:
#if DEBUG_LEVEL>0
{
Word wError = sosMIDISendMIDIData(
wDriverHandle,
(char*)midi_message->message,
2
);
Verify(wError == _ERR_NO_ERROR);
}
#else
sosMIDISendMIDIData(
wDriverHandle,
(char*)midi_message->message,
2
);
#endif
break;
default:
//
// Should never reach here
//
Fail("Should never reach here");
break;
}
CLEAR_AUDIO_RENDERER_MIDI();
#endif
}
#endif
//
//#############################################################################
// Channel methods
//#############################################################################
//
//
//#############################################################################
//#############################################################################
//
AudioChannel*
AudioCard::RequestAudioChannel(AudioVoiceCount voice_count)
{
Check(this);
//
// Monitor resource usage
//
#if 0
#if DEBUG_LEVEL>0
{
ChainIteratorOf<AudioChannel*> iterator(&idleChannelSocket);
if (voice_count > idleVoices || iterator.GetSize() == 0)
{
Tell("RequestAudioChannel - voices: " << idleVoices);
Tell(" channels: " << iterator.GetSize());
Tell("\n");
}
}
#endif
#endif
//
// Are there enough voices to allocate
//
if (voice_count > idleVoices)
return NULL;
//
// Allocate a channel
//
ChainIteratorOf<AudioChannel*> iterator(&idleChannelSocket);
AudioChannel *audio_channel;
Check(&iterator);
if ((audio_channel = iterator.GetCurrent()) != NULL)
{
Check(audio_channel);
iterator.Remove();
idleVoices -= voice_count;
audio_channel->SetVoicesUsed(voice_count);
return audio_channel;
}
return NULL;
}
//
//#############################################################################
//#############################################################################
//
void
AudioCard::ReleaseAudioChannel(AudioChannel *audio_channel)
{
Check(this);
Check(audio_channel);
#if 0
Tell("AudioCard::ReleaseAudioChannel - Released channel\n");
Tell(" ");
Dump((int)audio_channel->GetChannelNumber());
#endif
//
// Add to idle list
//
idleVoices += audio_channel->GetVoicesUsed();
Verify(idleVoices > 0 && idleVoices <= AWE_VOICE_COUNT);
audio_channel->SetVoicesUsed(0);
idleChannelSocket.Add(audio_channel);
}
//
//#############################################################################
// GetHexWord
//#############################################################################
//
WORD
AudioCard::GetHexWord(const char ** s)
{
WORD n = 0;
while (**s) {
if (**s >= '0' && **s <= '9')
n = (WORD)(n * 16 + (*(*s)++ - '0'));
else
if (**s >= 'a' && **s <= 'f')
n = (WORD)(n * 16 + (*(*s)++ - 'a') + 10);
else
if (**s >= 'A' && **s <= 'F')
n = (WORD)(n * 16 + (*(*s)++ - 'A') + 10);
else
break;
}
return n;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ AudioHardware ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//#############################################################################
// AudioHardware
//#############################################################################
//
AudioHardware::AudioHardware()
{
}
//
//#############################################################################
// ~AudioHardware
//#############################################################################
//
AudioHardware::~AudioHardware()
{
}
//
//#############################################################################
// TestInstance
//#############################################################################
//
Logical
AudioHardware::TestInstance() const
{
Node::TestInstance();
Check(&frontCard);
Check(&rearCard);
return True;
}
//
//#############################################################################
// Initialize
//#############################################################################
//
void
AudioHardware::Initialize()
{
#ifdef AUDIO_HARDWARE
Tell("AudioHardware::Initialize - Start\n");
#if 0
//
// initialize the TIMER system and tell it to call the DOS timer
// at a rate of 19 times/second. the actual rate is 18.2, but
// 19 is a good approximation.
//
if (getenv(TIMER_ENV) == NULL) // L4Time is not setting up timer
{
sosTIMERInitSystem(_TIMER_DOS_RATE, _SOS_DEBUG_NORMAL);
}
#endif
//
// initialize the MIDI system
//
sosMIDIInitSystem((PSTR)_SOS_DRIVER_PATH, _SOS_DEBUG_NORMAL);
//
// initialize the cards
//
frontCard.GetEnvironmentSettings(FRONT_CARD_ENV_VAR);
rearCard.GetEnvironmentSettings(REAR_CARD_ENV_VAR);
frontCard.Initialize();
rearCard.Initialize();
//
// Setup standard channel attributes
//
frontCard.InitializeChannels();
rearCard.InitializeChannels();
Tell("AudioHardware::Initialize - Finish\n");
#endif
}
//
//#############################################################################
// Close
//#############################################################################
//
void
AudioHardware::Close()
{
#ifdef AUDIO_HARDWARE
Check(this);
//
// Close the cards
//
frontCard.Close();
rearCard.Close();
#if 0
//
// Uninitialize the TIMER system and pass it the value to write to the
// timer chip to reset the timer, 0 is equivalent to 18.2 times/second
// which is the DOS clock rate. the calculation for the value to pass
// is 1193180/RATE.
//
if (getenv(TIMER_ENV) == NULL) // L4Time is not setting up timer
{
sosTIMERUnInitSystem(0);
}
sosTIMERUnInitSystem(0);
#endif
//
// Uninitialize the MIDI system
//
sosMIDIUnInitSystem();
#endif
}*/
#ifdef TEST_CLASS
# include "l4audhdw.tcp"
#endif