windows-nt/Source/XPSP1/NT/drivers/wdm/rt/clients/rtmidi/sequence.c
2020-09-26 16:20:57 +08:00

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);
}
}