291 lines
6.4 KiB
C
291 lines
6.4 KiB
C
|
|
// A Somewhat Useful Sample Real Time Client.
|
|
|
|
// Author: Joseph Ballantyne
|
|
// Date: 3/12/99
|
|
|
|
// This is the first real time client that actually does something
|
|
// useful. This is a midi MPU401 sequencer. It is simple, but it
|
|
// works and uses less CPU than our current 98 and NT WDM sequencer.
|
|
|
|
// Of course since this is a sample and I am not in the business of
|
|
// writing MIDI sequencers, it has some limitations.
|
|
|
|
// The data to be sequenced is passed in in one big block.
|
|
// It is passed in already formatted and timestamped appropriately.
|
|
// This code simply processes the buffer it was passed, and when
|
|
// it has sequenced everything that was passed in, it returns -
|
|
// which kills the real time thread.
|
|
|
|
|
|
#include "common.h"
|
|
#include "rt.h"
|
|
#include "sequence.h"
|
|
|
|
|
|
// This code only supports the MPU401 at a fixed IO location of 0x330.
|
|
|
|
// MPU401 defines
|
|
|
|
#define MPU401BASEADDRESS 0x330
|
|
|
|
#define MPU401_REG_DATA 0x00 // Data in/out register offset from base address
|
|
#define MPU401_REG_COMMAND 0x01 // Command register offset from base address
|
|
#define MPU401_REG_STATUS 0x01 // Status register offset from base addess
|
|
|
|
#define MPU401_DRR 0x40 // Output ready (for command or data)
|
|
#define MPU401_DSR 0x80 // Input ready (for data)
|
|
|
|
#define MPU401_CMD_RESET 0xFF // Reset command
|
|
#define MPU401_CMD_UART 0x3F // Switch to UART mod
|
|
#define MPU401_ACK 0xFE // Ack from MPU401 after successful command.
|
|
|
|
|
|
#define MidiWriteOK(status) ((status & MPU401_DRR) == 0)
|
|
#define MidiReadOK(status) ((status & MPU401_DSR) == 0)
|
|
|
|
|
|
// Here is the format of the data we process.
|
|
|
|
#pragma pack(push,1)
|
|
|
|
typedef struct MidiChunk{
|
|
struct MidiChunk *next;
|
|
struct MidiChunk *previous;
|
|
ULONGLONG timestamp;
|
|
ULONG numbytes;
|
|
UCHAR data[3];
|
|
} MidiMessage, *PMidiMessage;
|
|
|
|
#pragma pack(pop)
|
|
|
|
|
|
// Remember, everything we touch HAS to be locked down.
|
|
|
|
#pragma LOCKED_CODE
|
|
#pragma LOCKED_DATA
|
|
|
|
|
|
MidiMessage testmidi[2]={
|
|
{&testmidi[1],&testmidi[1],0,3,0x99,0x25,0x7f},
|
|
{&testmidi[0],&testmidi[0],240,3,0x99,0x25,0}
|
|
};
|
|
|
|
|
|
#pragma warning ( disable : 4035 )
|
|
|
|
#define rdtsc __asm _emit 0x0f __asm _emit 0x31
|
|
|
|
|
|
LONGLONG __inline ReadCycleCounter(VOID)
|
|
{
|
|
|
|
__asm {
|
|
rdtsc
|
|
}
|
|
|
|
}
|
|
|
|
#pragma warning ( default : 4035 )
|
|
|
|
|
|
|
|
VOID OutB(ULONG address, ULONG data)
|
|
{
|
|
|
|
__asm {
|
|
mov edx,address
|
|
mov eax,data
|
|
out dx,al
|
|
}
|
|
|
|
}
|
|
|
|
|
|
#pragma warning( disable : 4035 )
|
|
|
|
ULONG InB(ULONG address)
|
|
{
|
|
|
|
__asm {
|
|
mov edx,address
|
|
xor eax,eax
|
|
in al,dx
|
|
}
|
|
|
|
}
|
|
|
|
#pragma warning( default : 4035 )
|
|
|
|
|
|
// This routine sends a command to the MPU401 and waits for an acknowledge from
|
|
// the MPU that the command succeeded. If no ack is recieved in 200ms then
|
|
// it returns FALSE, otherwise it returns TRUE.
|
|
|
|
BOOL SendMpuCommand(ULONG MidiBaseAddress, UCHAR command)
|
|
{
|
|
LONG count=0;
|
|
|
|
// Wait until OK to write a command.
|
|
|
|
while (!MidiWriteOK(InB(MidiBaseAddress+MPU401_REG_STATUS))) {
|
|
RtYield(0, 0);
|
|
}
|
|
|
|
// Send command to the MPU401.
|
|
|
|
OutB(MidiBaseAddress+MPU401_REG_COMMAND, command);
|
|
|
|
// Wait for the MPU acknowlege. If we don't get the correct response
|
|
// in 200ms or less, then punt.
|
|
|
|
while (!MidiReadOK(InB(MidiBaseAddress+MPU401_REG_STATUS))) {
|
|
count++;
|
|
if (count<=200) {
|
|
RtYield(0, 0);
|
|
}
|
|
else {
|
|
// We did not get any response, perhaps we were in UART mode.
|
|
#if 0
|
|
return FALSE;
|
|
#else
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// At this point we received something from the MPU, check if it is an ack.
|
|
|
|
if (InB(MidiBaseAddress+MPU401_REG_DATA)!=0xfe) {
|
|
// Not a command acknowledge.
|
|
Trap();
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
VOID PlayMidi(PVOID Context, ThreadStats *Statistics)
|
|
{
|
|
|
|
PMidiMessage RealTimeMidiData;
|
|
ULONG MidiBaseAddress;
|
|
ULONG MidiStatus;
|
|
ULONG count;
|
|
|
|
LONGLONG starttime;
|
|
|
|
|
|
//Trap();
|
|
|
|
RealTimeMidiData=(PMidiMessage)Context;
|
|
|
|
MidiBaseAddress=MPU401BASEADDRESS;
|
|
|
|
count=0;
|
|
|
|
// First wait until the MPU401 comes on line. We need to do this because
|
|
// many MPU401s are plug and play devices and do not appear until
|
|
// the hardware is setup. Note that on most machines, when there is
|
|
// no device on the bus at the I/O port address, InB will return
|
|
// 0xff.
|
|
while (InB(MidiBaseAddress+MPU401_REG_STATUS)==0xff) {
|
|
RtYield(0, 0);
|
|
}
|
|
|
|
//Trap();
|
|
|
|
// At this point, there is detected hardware at 331.
|
|
// Now wait an extra 10 seconds.
|
|
starttime=Statistics->ThisPeriodStartTime;
|
|
while((Statistics->ThisPeriodStartTime-starttime)/SEC<10) {
|
|
RtYield(0, 0);
|
|
}
|
|
|
|
//Trap();
|
|
|
|
// Now read the MPU401 data register until it is empty.
|
|
while (MidiReadOK(InB(MidiBaseAddress+MPU401_REG_STATUS))) {
|
|
InB(MidiBaseAddress+MPU401_REG_DATA);
|
|
RtYield(0, 0);
|
|
}
|
|
|
|
//Trap();
|
|
|
|
// Now reset the MPU401. If this succeeds, we know we have a real
|
|
// MPU at our base address. Note that the MPU may be in UART
|
|
// mode. If so, then we will NOT get an acknowlege back when we reset it.
|
|
// To handle this case, if there is no acknowlege on the first
|
|
// reset, we will retry once and see if we get the acknowlege the second
|
|
// time. If not, then we punt.
|
|
|
|
if (!SendMpuCommand(MidiBaseAddress, MPU401_CMD_RESET)) {
|
|
if (!SendMpuCommand(MidiBaseAddress, MPU401_CMD_RESET)) {
|
|
Trap();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Now put the MPU into UART mode.
|
|
|
|
if (!SendMpuCommand(MidiBaseAddress, MPU401_CMD_UART)) {
|
|
// For now we disable the trap and the thread exit.
|
|
// We do this because if we run this code on current WDM driven devices,
|
|
// the acknowlege code will be handled by the read interrupt service
|
|
// routine of the driver and so we will not see it.
|
|
Trap();
|
|
return;
|
|
}
|
|
|
|
|
|
// Now reset the starttime.
|
|
starttime=ReadCycleCounter();
|
|
|
|
// Run until we are out of data to send.
|
|
|
|
while (RealTimeMidiData!=NULL) {
|
|
|
|
// Wait until we need to send the next chunk of data.
|
|
// if (Statistics->totalperiods*MSEC<RealTimeMidiData->timestamp) {
|
|
if ((ULONGLONG)(ReadCycleCounter()-starttime)/200000<RealTimeMidiData->timestamp) {
|
|
RtYield(0, 0);
|
|
continue;
|
|
}
|
|
|
|
// Read the status of the device.
|
|
|
|
MidiStatus=InB(MidiBaseAddress+MPU401_REG_STATUS);
|
|
|
|
// Make sure its OK to write to the device. We should ALWAYS
|
|
// be able to, since we syncronize with the hardware. If not,
|
|
// then update the syncronization, and slip to our next time slice.
|
|
|
|
if (!MidiWriteOK(MidiStatus)) {
|
|
RtYield(0, 0);
|
|
continue;
|
|
}
|
|
|
|
// Now write the next byte out to the MPU.
|
|
OutB(MidiBaseAddress+MPU401_REG_DATA, RealTimeMidiData->data[count]);
|
|
|
|
// Update our state.
|
|
count++;
|
|
if (count>=RealTimeMidiData->numbytes) {
|
|
|
|
RealTimeMidiData->timestamp+=250;
|
|
|
|
RealTimeMidiData=RealTimeMidiData->next;
|
|
count=0;
|
|
}
|
|
|
|
// And yeild - we are done until its time for the next byte.
|
|
RtYield(0, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|