#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
