mirror of
https://github.com/reactos/reactos.git
synced 2024-08-28 14:18:41 +00:00
PC speaker MIDI driver (see README.TXT for info)
Tested on Windows XP but should be compatible with ReactOS svn path=/trunk/; revision=25520
This commit is contained in:
parent
f8edfc8d99
commit
8a64d7ef61
903
reactos/dll/win32/beepmidi/beepmidi.c
Normal file
903
reactos/dll/win32/beepmidi/beepmidi.c
Normal file
|
@ -0,0 +1,903 @@
|
|||
/*
|
||||
BeepMidi :: beep.sys MIDI player
|
||||
|
||||
(c) Andrew Greenwood, 2007.
|
||||
|
||||
Released as open-source software. You may copy, re-distribute and modify
|
||||
this software, provided this copyright notice remains intact.
|
||||
|
||||
Please see the included README.TXT for more information
|
||||
|
||||
HISTORY :
|
||||
16th January 2007 Started
|
||||
17th January 2007 Polyphony support and threading added
|
||||
18th January 2007 Made threading optional, added comments
|
||||
*/
|
||||
|
||||
/* The timeslice to allocate for all playing notes (in milliseconds) */
|
||||
#define TIMESLICE_SIZE 60
|
||||
|
||||
/*
|
||||
If this is defined, notes are added to the playing list, even if
|
||||
they already exist. As a result, the note will sound twice during
|
||||
each timeslice. Also each note on will require a corresponding note
|
||||
off event.
|
||||
*/
|
||||
#define ALLOW_DUPLICATE_NOTES
|
||||
|
||||
/*
|
||||
The maximum number of notes that may be playing at any one time.
|
||||
Higher values result in a messier sound as all the frequencies get
|
||||
mashed together. Do not set this below 2. Recommended = 4
|
||||
*/
|
||||
#define POLYPHONY 3
|
||||
|
||||
/*
|
||||
Define CONTINUOUS_NOTES to perform note playback in a separate thread.
|
||||
This was originally the intended behaviour, but after experimentation
|
||||
doesn't sound as good for MIDI files which have a lot going on. If not
|
||||
defined, all playing notes are output in sequence as a new note starts.
|
||||
*/
|
||||
#define CONTINUOUS_NOTES
|
||||
|
||||
#define WIN32_NO_STATUS
|
||||
#define NTOS_MODE_USER
|
||||
#include <windows.h>
|
||||
#include <ndk/ntndk.h>
|
||||
#include <stdio.h>
|
||||
#include <ntddbeep.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <mmddk.h>
|
||||
#include <mmsystem.h>
|
||||
|
||||
#define DPRINT printf
|
||||
//#define DPRINT //
|
||||
|
||||
/* A few MIDI command categories */
|
||||
#define MIDI_NOTE_OFF 0x80
|
||||
#define MIDI_NOTE_ON 0x90
|
||||
#define MIDI_CONTROL_CHANGE 0xB0
|
||||
#define MIDI_PROGRAM 0xC0
|
||||
#define MIDI_PITCH_BEND 0xE0
|
||||
#define MIDI_SYSTEM 0xFF
|
||||
|
||||
/* Specific commands */
|
||||
#define MIDI_RESET 0xFF
|
||||
|
||||
|
||||
typedef struct _NoteNode
|
||||
{
|
||||
struct _NoteNode* next;
|
||||
struct _NoteNode* previous;
|
||||
|
||||
UCHAR note;
|
||||
UCHAR velocity; /* 0 is note-off */
|
||||
} NoteNode;
|
||||
|
||||
typedef struct _DeviceInfo
|
||||
{
|
||||
HDRVR mme_handle;
|
||||
HANDLE kernel_device;
|
||||
|
||||
DWORD callback;
|
||||
DWORD instance;
|
||||
DWORD flags;
|
||||
|
||||
UCHAR running_status;
|
||||
|
||||
DWORD playing_notes_count;
|
||||
NoteNode* note_list;
|
||||
BOOL refresh_notes;
|
||||
|
||||
HANDLE thread_handle;
|
||||
BOOL terminate_thread;
|
||||
HANDLE thread_termination_complete;
|
||||
} DeviceInfo;
|
||||
|
||||
DeviceInfo* the_device;
|
||||
CRITICAL_SECTION device_lock;
|
||||
|
||||
void
|
||||
FakePrintf(char* str, ...)
|
||||
{
|
||||
/* Just to shut the compiler up */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
This is designed to be treated as a thread, however it behaves as a
|
||||
normal function if CONTINUOUS_NOTES is not defined.
|
||||
*/
|
||||
|
||||
DWORD WINAPI
|
||||
ProcessPlayingNotes(
|
||||
LPVOID parameter)
|
||||
{
|
||||
DeviceInfo* device_info = (DeviceInfo*) parameter;
|
||||
NTSTATUS status;
|
||||
IO_STATUS_BLOCK io_status_block;
|
||||
DWORD arp_notes;
|
||||
|
||||
DPRINT("Note processing started\n");
|
||||
|
||||
/* We lock the note list only while accessing it */
|
||||
|
||||
#ifdef CONTINUOUS_NOTES
|
||||
while ( ! device_info->terminate_thread )
|
||||
#endif
|
||||
{
|
||||
NoteNode* node;
|
||||
|
||||
/* Number of notes being arpeggiated */
|
||||
arp_notes = 1;
|
||||
|
||||
EnterCriticalSection(&device_lock);
|
||||
|
||||
/* Calculate how much time to allocate to each playing note */
|
||||
|
||||
DPRINT("%d notes active\n", (int) device_info->playing_notes_count);
|
||||
|
||||
node = device_info->note_list;
|
||||
|
||||
while ( ( node != NULL ) && ( arp_notes <= POLYPHONY ) )
|
||||
{
|
||||
DPRINT("playing..\n");
|
||||
BEEP_SET_PARAMETERS beep_data;
|
||||
DWORD actually_playing = 0;
|
||||
|
||||
double frequency = node->note;
|
||||
frequency = frequency / 12;
|
||||
frequency = pow(2, frequency);
|
||||
frequency = 8.1758 * frequency;
|
||||
|
||||
if (device_info->playing_notes_count > POLYPHONY)
|
||||
actually_playing = POLYPHONY;
|
||||
else
|
||||
actually_playing = device_info->playing_notes_count;
|
||||
|
||||
DPRINT("Frequency %f\n", frequency);
|
||||
|
||||
// TODO
|
||||
beep_data.Frequency = (DWORD) frequency;
|
||||
beep_data.Duration = TIMESLICE_SIZE / actually_playing; /* device_info->playing_notes_count; */
|
||||
|
||||
status = NtDeviceIoControlFile(device_info->kernel_device,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
&io_status_block,
|
||||
IOCTL_BEEP_SET,
|
||||
&beep_data,
|
||||
sizeof(BEEP_SET_PARAMETERS),
|
||||
NULL,
|
||||
0);
|
||||
|
||||
if ( ! NT_SUCCESS(status) )
|
||||
{
|
||||
DPRINT("ERROR %d\n", (int) GetLastError());
|
||||
}
|
||||
|
||||
SleepEx(beep_data.Duration, TRUE);
|
||||
|
||||
if ( device_info->refresh_notes )
|
||||
{
|
||||
device_info->refresh_notes = FALSE;
|
||||
break;
|
||||
}
|
||||
|
||||
arp_notes ++;
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
LeaveCriticalSection(&device_lock);
|
||||
}
|
||||
|
||||
#ifdef CONTINUOUS_NOTES
|
||||
SetEvent(device_info->thread_termination_complete);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Fills a MIDIOUTCAPS structure with information about our device.
|
||||
*/
|
||||
|
||||
MMRESULT
|
||||
GetDeviceCapabilities(
|
||||
MIDIOUTCAPS* caps)
|
||||
{
|
||||
/* These are ignored for now */
|
||||
caps->wMid = 0;
|
||||
caps->wPid = 0;
|
||||
|
||||
caps->vDriverVersion = 0x0100;
|
||||
|
||||
memset(caps->szPname, 0, sizeof(caps->szPname));
|
||||
memcpy(caps->szPname, L"PC speaker\0", strlen("PC speaker\0") * 2);
|
||||
|
||||
caps->wTechnology = MOD_SQSYNTH;
|
||||
|
||||
caps->wVoices = 1; /* We only have one voice */
|
||||
caps->wNotes = POLYPHONY;
|
||||
caps->wChannelMask = 0xFFBF; /* Ignore channel 10 */
|
||||
|
||||
caps->dwSupport = 0;
|
||||
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Helper function that just simplifies calling the application making use
|
||||
of us.
|
||||
*/
|
||||
|
||||
BOOL
|
||||
CallClient(
|
||||
DeviceInfo* device_info,
|
||||
DWORD message,
|
||||
DWORD parameter1,
|
||||
DWORD parameter2)
|
||||
{
|
||||
DPRINT("Calling client - callback 0x%x mmhandle 0x%x\n", (int) device_info->callback, (int) device_info->mme_handle);
|
||||
return DriverCallback(device_info->callback,
|
||||
HIWORD(device_info->flags),
|
||||
device_info->mme_handle,
|
||||
message,
|
||||
device_info->instance,
|
||||
parameter1,
|
||||
parameter2);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Open the kernel-mode device and allocate resources. This opens the
|
||||
BEEP.SYS kernel device.
|
||||
*/
|
||||
|
||||
MMRESULT
|
||||
OpenDevice(
|
||||
DeviceInfo** private_data,
|
||||
MIDIOPENDESC* open_desc,
|
||||
DWORD flags)
|
||||
{
|
||||
NTSTATUS status;
|
||||
HANDLE heap;
|
||||
HANDLE kernel_device;
|
||||
UNICODE_STRING beep_device_name;
|
||||
OBJECT_ATTRIBUTES attribs;
|
||||
IO_STATUS_BLOCK status_block;
|
||||
|
||||
/* One at a time.. */
|
||||
if ( the_device )
|
||||
{
|
||||
DPRINT("Already allocated\n");
|
||||
return MMSYSERR_ALLOCATED;
|
||||
}
|
||||
|
||||
/* Make the device name into a unicode string and open it */
|
||||
|
||||
RtlInitUnicodeString(&beep_device_name,
|
||||
L"\\Device\\Beep");
|
||||
|
||||
InitializeObjectAttributes(&attribs,
|
||||
&beep_device_name,
|
||||
0,
|
||||
NULL,
|
||||
NULL);
|
||||
|
||||
status = NtCreateFile(&kernel_device,
|
||||
FILE_READ_DATA | FILE_WRITE_DATA,
|
||||
&attribs,
|
||||
&status_block,
|
||||
NULL,
|
||||
0,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
FILE_OPEN_IF,
|
||||
0,
|
||||
NULL,
|
||||
0);
|
||||
|
||||
if ( ! NT_SUCCESS(status) )
|
||||
{
|
||||
DPRINT("Could not connect to BEEP device - %d\n", (int) GetLastError());
|
||||
return MMSYSERR_ERROR;
|
||||
}
|
||||
|
||||
DPRINT("Opened!\n");
|
||||
|
||||
/* Allocate and initialize the device info */
|
||||
|
||||
heap = GetProcessHeap();
|
||||
|
||||
the_device = HeapAlloc(heap, HEAP_ZERO_MEMORY, sizeof(DeviceInfo));
|
||||
|
||||
if ( ! the_device )
|
||||
{
|
||||
DPRINT("Out of memory\n");
|
||||
return MMSYSERR_NOMEM;
|
||||
}
|
||||
|
||||
/* Initialize */
|
||||
the_device->kernel_device = kernel_device;
|
||||
the_device->playing_notes_count = 0;
|
||||
the_device->note_list = NULL;
|
||||
the_device->thread_handle = 0;
|
||||
the_device->terminate_thread = FALSE;
|
||||
the_device->running_status = 0;
|
||||
|
||||
// TODO
|
||||
the_device->mme_handle = (HDRVR) open_desc->hMidi;
|
||||
the_device->callback = open_desc->dwCallback;
|
||||
the_device->instance = open_desc->dwInstance;
|
||||
the_device->flags = flags;
|
||||
|
||||
/* Store the pointer in the user data */
|
||||
*private_data = the_device;
|
||||
|
||||
/* This is threading-related code */
|
||||
#ifdef CONTINUOUS_NOTES
|
||||
the_device->thread_termination_complete = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
|
||||
if ( ! the_device->thread_termination_complete )
|
||||
{
|
||||
DPRINT("CreateEvent failed\n");
|
||||
HeapFree(heap, 0, the_device);
|
||||
return MMSYSERR_NOMEM;
|
||||
}
|
||||
|
||||
the_device->thread_handle = CreateThread(NULL,
|
||||
0,
|
||||
ProcessPlayingNotes,
|
||||
(PVOID) the_device,
|
||||
0,
|
||||
NULL);
|
||||
|
||||
if ( ! the_device->thread_handle )
|
||||
{
|
||||
DPRINT("CreateThread failed\n");
|
||||
CloseHandle(the_device->thread_termination_complete);
|
||||
HeapFree(heap, 0, the_device);
|
||||
return MMSYSERR_NOMEM;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Now we call the client application to say the device is open */
|
||||
DPRINT("Sending MOM_OPEN\n");
|
||||
DPRINT("Success? %d\n", (int) CallClient(the_device, MOM_OPEN, 0, 0));
|
||||
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Close the kernel-mode device.
|
||||
*/
|
||||
|
||||
MMRESULT
|
||||
CloseDevice(DeviceInfo* device_info)
|
||||
{
|
||||
HANDLE heap = GetProcessHeap();
|
||||
|
||||
/* If we're working in threaded mode we need to wait for thread to die */
|
||||
#ifdef CONTINUOUS_NOTES
|
||||
the_device->terminate_thread = TRUE;
|
||||
|
||||
WaitForSingleObject(the_device->thread_termination_complete, INFINITE);
|
||||
|
||||
CloseHandle(the_device->thread_termination_complete);
|
||||
#endif
|
||||
|
||||
/* Let the client application know the device is closing */
|
||||
DPRINT("Sending MOM_CLOSE\n");
|
||||
CallClient(device_info, MOM_CLOSE, 0, 0);
|
||||
|
||||
NtClose(device_info->kernel_device);
|
||||
|
||||
/* Free resources */
|
||||
HeapFree(heap, 0, device_info);
|
||||
|
||||
the_device = NULL;
|
||||
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Removes a note from the playing notes list. If the note is not playing,
|
||||
we just pretend nothing happened.
|
||||
*/
|
||||
|
||||
MMRESULT
|
||||
StopNote(
|
||||
DeviceInfo* device_info,
|
||||
UCHAR note)
|
||||
{
|
||||
HANDLE heap = GetProcessHeap();
|
||||
NoteNode* node;
|
||||
NoteNode* prev_node = NULL;
|
||||
|
||||
DPRINT("StopNote\n");
|
||||
|
||||
EnterCriticalSection(&device_lock);
|
||||
|
||||
node = device_info->note_list;
|
||||
|
||||
while ( node != NULL )
|
||||
{
|
||||
if ( node->note == note )
|
||||
{
|
||||
/* Found the note - just remove the node from the list */
|
||||
|
||||
DPRINT("Stopping note %d\n", (int) node->note);
|
||||
|
||||
if ( prev_node != NULL )
|
||||
prev_node->next = node->next;
|
||||
else
|
||||
device_info->note_list = node->next;
|
||||
|
||||
HeapFree(heap, 0, node);
|
||||
|
||||
device_info->playing_notes_count --;
|
||||
|
||||
DPRINT("Note stopped - now playing %d notes\n", (int) device_info->playing_notes_count);
|
||||
|
||||
LeaveCriticalSection(&device_lock);
|
||||
device_info->refresh_notes = TRUE;
|
||||
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
prev_node = node;
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
LeaveCriticalSection(&device_lock);
|
||||
|
||||
/* Hmm, a good idea? */
|
||||
#ifndef CONTINUOUS_NOTES
|
||||
ProcessPlayingNotes((PVOID) device_info);
|
||||
#endif
|
||||
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Adds a note to the playing notes list. If the note is already playing,
|
||||
the definition of ALLOW_DUPLICATE_NOTES determines if an existing note
|
||||
may be duplicated. Otherwise, duplicate notes are ignored.
|
||||
*/
|
||||
|
||||
MMRESULT
|
||||
PlayNote(
|
||||
DeviceInfo* device_info,
|
||||
UCHAR note,
|
||||
UCHAR velocity)
|
||||
{
|
||||
HANDLE heap = GetProcessHeap();
|
||||
|
||||
DPRINT("PlayNote\n");
|
||||
|
||||
NoteNode* node;
|
||||
|
||||
if ( velocity == 0 )
|
||||
{
|
||||
DPRINT("Zero velocity\n");
|
||||
|
||||
/* Velocity zero is effectively a "note off" */
|
||||
StopNote(device_info, note);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Start playing the note */
|
||||
NoteNode* new_node;
|
||||
NoteNode* tail_node = NULL;
|
||||
|
||||
EnterCriticalSection(&device_lock);
|
||||
|
||||
node = device_info->note_list;
|
||||
|
||||
while ( node != NULL )
|
||||
{
|
||||
#ifndef ALLOW_DUPLICATE_NOTES
|
||||
if ( ( node->note == note ) && ( velocity > 0 ) )
|
||||
{
|
||||
/* The note is already playing - do nothing */
|
||||
DPRINT("Duplicate note playback request ignored\n");
|
||||
LeaveCriticalSection(&device_lock);
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
#endif
|
||||
|
||||
tail_node = node;
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
new_node = HeapAlloc(heap, HEAP_ZERO_MEMORY, sizeof(NoteNode));
|
||||
|
||||
if ( ! new_node )
|
||||
{
|
||||
LeaveCriticalSection(&device_lock);
|
||||
return MMSYSERR_NOMEM;
|
||||
}
|
||||
|
||||
new_node->note = note;
|
||||
new_node->velocity = velocity;
|
||||
|
||||
/*
|
||||
Prepend to the playing notes list. If exceeding polyphony,
|
||||
remove the oldest note (which will be at the tail.)
|
||||
*/
|
||||
|
||||
if ( device_info->note_list )
|
||||
device_info->note_list->previous = new_node;
|
||||
|
||||
new_node->next = device_info->note_list;
|
||||
new_node->previous = NULL;
|
||||
|
||||
device_info->note_list = new_node;
|
||||
device_info->playing_notes_count ++;
|
||||
|
||||
/*
|
||||
if ( device_info->playing_notes_count > POLYPHONY )
|
||||
{
|
||||
ASSERT(tail_node);
|
||||
|
||||
DPRINT("Polyphony exceeded\n");
|
||||
|
||||
tail_node->previous->next = NULL;
|
||||
|
||||
HeapFree(heap, 0, tail_node);
|
||||
|
||||
device_info->playing_notes_count --;
|
||||
}
|
||||
*/
|
||||
|
||||
LeaveCriticalSection(&device_lock);
|
||||
|
||||
DPRINT("Note started - now playing %d notes\n", (int) device_info->playing_notes_count);
|
||||
device_info->refresh_notes = TRUE;
|
||||
}
|
||||
|
||||
#ifndef CONTINUOUS_NOTES
|
||||
ProcessPlayingNotes((PVOID) device_info);
|
||||
#endif
|
||||
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
Decipher a short MIDI message (which is a MIDI message packed into a DWORD.)
|
||||
This will set "running status", but does not take this into account when
|
||||
processing messages (is this necessary?)
|
||||
*/
|
||||
|
||||
MMRESULT
|
||||
ProcessShortMidiMessage(
|
||||
DeviceInfo* device_info,
|
||||
DWORD message)
|
||||
{
|
||||
DWORD status;
|
||||
|
||||
DWORD category;
|
||||
DWORD channel;
|
||||
DWORD data1, data2;
|
||||
|
||||
status = message & 0x000000FF;
|
||||
|
||||
/* Deal with running status */
|
||||
|
||||
if ( status < MIDI_NOTE_OFF )
|
||||
{
|
||||
status = device_info->running_status;
|
||||
}
|
||||
|
||||
/* Ensure the status is sane! */
|
||||
|
||||
if ( status < MIDI_NOTE_OFF )
|
||||
{
|
||||
/* It's garbage, ignore it */
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
/* Figure out the message category and channel */
|
||||
|
||||
category = status & 0xF0;
|
||||
channel = status & 0x0F; /* we don't use this */
|
||||
|
||||
data1 = (message & 0x0000FF00) >> 8;
|
||||
data2 = (message & 0x00FF0000) >> 16;
|
||||
|
||||
DPRINT("0x%x, %d, %d\n", (int) status, (int) data1, (int) data2);
|
||||
|
||||
/* Filter drums (which are *usually* on channel 10) */
|
||||
if ( channel == 10 )
|
||||
{
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
/* Pass to the appropriate message handler */
|
||||
|
||||
switch ( category )
|
||||
{
|
||||
case MIDI_NOTE_ON :
|
||||
{
|
||||
PlayNote(device_info, data1, data2);
|
||||
break;
|
||||
}
|
||||
|
||||
case MIDI_NOTE_OFF :
|
||||
{
|
||||
StopNote(device_info, data1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
|
||||
#define PACK_MIDI(b1, b2, b3) \
|
||||
((b3 * 65536) + (b2 * 256) + b1);
|
||||
|
||||
|
||||
/*
|
||||
Processes a "long" MIDI message (ie, a MIDI message contained within a
|
||||
buffer.) This is intended for supporting SysEx data, or blocks of MIDI
|
||||
events. However in our case we're only interested in short MIDI messages,
|
||||
so we scan the buffer, and each time we encounter a valid status byte
|
||||
we start recording it as a new event. Once 3 bytes or a new status is
|
||||
received, the event is passed to the short message handler.
|
||||
*/
|
||||
|
||||
MMRESULT
|
||||
ProcessLongMidiMessage(
|
||||
DeviceInfo* device_info,
|
||||
MIDIHDR* header)
|
||||
{
|
||||
int index = 0;
|
||||
UCHAR* midi_bytes = (UCHAR*) header->lpData;
|
||||
|
||||
int msg_index = 0;
|
||||
UCHAR msg[3];
|
||||
|
||||
/* Initialize the buffer */
|
||||
msg[0] = msg[1] = msg[2] = 0;
|
||||
|
||||
if ( ! ( header->dwFlags & MHDR_PREPARED ) )
|
||||
{
|
||||
DPRINT("Not prepared!\n");
|
||||
return MIDIERR_UNPREPARED;
|
||||
}
|
||||
|
||||
DPRINT("Processing %d bytes of MIDI\n", (int) header->dwBufferLength);
|
||||
|
||||
while ( index < header->dwBufferLength )
|
||||
{
|
||||
/* New status byte? ( = new event) */
|
||||
if ( midi_bytes[index] & 0x80 )
|
||||
{
|
||||
DWORD short_msg;
|
||||
|
||||
/* Deal with the existing event */
|
||||
|
||||
if ( msg[0] & 0x80 )
|
||||
{
|
||||
short_msg = PACK_MIDI(msg[0], msg[1], msg[2]);
|
||||
|
||||
DPRINT("Complete msg is 0x%x %d %d\n", (int) msg[0], (int) msg[1], (int) msg[2]);
|
||||
ProcessShortMidiMessage(device_info, short_msg);
|
||||
}
|
||||
|
||||
/* Set new running status and start recording the event */
|
||||
DPRINT("Set new running status\n");
|
||||
device_info->running_status = midi_bytes[index];
|
||||
msg[0] = midi_bytes[index];
|
||||
msg_index = 1;
|
||||
}
|
||||
|
||||
/* Unexpected data byte? ( = re-use previous status) */
|
||||
else if ( msg_index == 0 )
|
||||
{
|
||||
if ( device_info->running_status & 0x80 )
|
||||
{
|
||||
DPRINT("Retrieving running status\n");
|
||||
msg[0] = device_info->running_status;
|
||||
msg[1] = midi_bytes[index];
|
||||
msg_index = 2;
|
||||
}
|
||||
else
|
||||
DPRINT("garbage\n");
|
||||
}
|
||||
|
||||
/* Expected data ( = append to message until buffer full) */
|
||||
else
|
||||
{
|
||||
DPRINT("Next byte...\n");
|
||||
msg[msg_index] = midi_bytes[index];
|
||||
msg_index ++;
|
||||
|
||||
if ( msg_index > 2 )
|
||||
{
|
||||
DWORD short_msg;
|
||||
|
||||
short_msg = PACK_MIDI(msg[0], msg[1], msg[2]);
|
||||
|
||||
DPRINT("Complete msg is 0x%x %d %d\n", (int) msg[0], (int) msg[1], (int) msg[2]);
|
||||
ProcessShortMidiMessage(device_info, short_msg);
|
||||
|
||||
/* Reinit */
|
||||
msg_index = 0;
|
||||
msg[0] = msg[1] = msg[2] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
index ++;
|
||||
}
|
||||
|
||||
/*
|
||||
We're meant to clear MHDR_DONE and set MHDR_INQUEUE but since we
|
||||
deal with everything here and now we might as well just say so.
|
||||
*/
|
||||
header->dwFlags |= MHDR_DONE;
|
||||
header->dwFlags &= ~ MHDR_INQUEUE;
|
||||
|
||||
DPRINT("Success? %d\n", (int) CallClient(the_device, MOM_DONE, (DWORD) header, 0));
|
||||
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Exported function that receives messages from WINMM (the MME API.)
|
||||
*/
|
||||
|
||||
FAR PASCAL
|
||||
MMRESULT
|
||||
modMessage(
|
||||
UINT device_id,
|
||||
UINT message,
|
||||
DWORD private_data,
|
||||
DWORD parameter1,
|
||||
DWORD parameter2)
|
||||
{
|
||||
switch ( message )
|
||||
{
|
||||
case MODM_GETNUMDEVS :
|
||||
{
|
||||
/* Only one internal PC speaker device (and even that's too much) */
|
||||
DPRINT("MODM_GETNUMDEVS\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
case MODM_GETDEVCAPS :
|
||||
{
|
||||
DPRINT("MODM_GETDEVCAPS\n");
|
||||
return GetDeviceCapabilities((MIDIOUTCAPS*) parameter1);
|
||||
}
|
||||
|
||||
case MODM_OPEN :
|
||||
{
|
||||
DPRINT("MODM_OPEN\n");
|
||||
|
||||
return OpenDevice((DeviceInfo**) private_data,
|
||||
(MIDIOPENDESC*) parameter1,
|
||||
parameter2);
|
||||
}
|
||||
|
||||
case MODM_CLOSE :
|
||||
{
|
||||
DPRINT("MODM_CLOSE\n");
|
||||
return CloseDevice((DeviceInfo*) private_data);
|
||||
}
|
||||
|
||||
case MODM_DATA :
|
||||
{
|
||||
return ProcessShortMidiMessage((DeviceInfo*) private_data, parameter1);
|
||||
}
|
||||
|
||||
case MODM_PREPARE :
|
||||
{
|
||||
/* We don't bother with this */
|
||||
MIDIHDR* hdr = (MIDIHDR*) parameter1;
|
||||
hdr->dwFlags |= MHDR_PREPARED;
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
case MODM_UNPREPARE :
|
||||
{
|
||||
MIDIHDR* hdr = (MIDIHDR*) parameter1;
|
||||
hdr->dwFlags &= ~MHDR_PREPARED;
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
case MODM_LONGDATA :
|
||||
{
|
||||
DPRINT("LONGDATA\n");
|
||||
return ProcessLongMidiMessage((DeviceInfo*) private_data, (MIDIHDR*) parameter1);
|
||||
}
|
||||
|
||||
case MODM_RESET :
|
||||
{
|
||||
/* TODO */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DPRINT("Not supported %d\n", message);
|
||||
|
||||
return MMSYSERR_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Driver entrypoint.
|
||||
*/
|
||||
|
||||
FAR PASCAL LONG
|
||||
DriverProc(
|
||||
DWORD driver_id,
|
||||
HDRVR driver_handle,
|
||||
UINT message,
|
||||
LONG parameter1,
|
||||
LONG parameter2)
|
||||
{
|
||||
switch ( message )
|
||||
{
|
||||
case DRV_LOAD :
|
||||
DPRINT("DRV_LOAD\n");
|
||||
the_device = NULL;
|
||||
return 1L;
|
||||
|
||||
case DRV_FREE :
|
||||
DPRINT("DRV_FREE\n");
|
||||
return 1L;
|
||||
|
||||
case DRV_OPEN :
|
||||
DPRINT("DRV_OPEN\n");
|
||||
InitializeCriticalSection(&device_lock);
|
||||
return 1L;
|
||||
|
||||
case DRV_CLOSE :
|
||||
DPRINT("DRV_CLOSE\n");
|
||||
return 1L;
|
||||
|
||||
case DRV_ENABLE :
|
||||
DPRINT("DRV_ENABLE\n");
|
||||
return 1L;
|
||||
|
||||
case DRV_DISABLE :
|
||||
DPRINT("DRV_DISABLE\n");
|
||||
return 1L;
|
||||
|
||||
/*
|
||||
We don't provide configuration capabilities. This used to be
|
||||
for things like I/O port, IRQ, DMA settings, etc.
|
||||
*/
|
||||
|
||||
case DRV_QUERYCONFIGURE :
|
||||
DPRINT("DRV_QUERYCONFIGURE\n");
|
||||
return 0L;
|
||||
|
||||
case DRV_CONFIGURE :
|
||||
DPRINT("DRV_CONFIGURE\n");
|
||||
return 0L;
|
||||
|
||||
case DRV_INSTALL :
|
||||
DPRINT("DRV_INSTALL\n");
|
||||
return DRVCNF_RESTART;
|
||||
};
|
||||
|
||||
DPRINT("???\n");
|
||||
|
||||
return DefDriverProc(driver_id,
|
||||
driver_handle,
|
||||
message,
|
||||
parameter1,
|
||||
parameter2);
|
||||
}
|
12
reactos/dll/win32/beepmidi/beepmidi.def
Normal file
12
reactos/dll/win32/beepmidi/beepmidi.def
Normal file
|
@ -0,0 +1,12 @@
|
|||
; beepmidi.def
|
||||
;
|
||||
; BeepMidi driver by Andrew Greenwood
|
||||
;
|
||||
; For ReactOS Operating System
|
||||
;
|
||||
|
||||
LIBRARY beepmidi.dll
|
||||
|
||||
EXPORTS
|
||||
DriverProc@20
|
||||
modMessage@20
|
12
reactos/dll/win32/beepmidi/beepmidi.rbuild
Normal file
12
reactos/dll/win32/beepmidi/beepmidi.rbuild
Normal file
|
@ -0,0 +1,12 @@
|
|||
<module name="beepmidi" type="win32dll" installbase="system32" installname="beepmidi.dll">
|
||||
<importlibrary definition="beepmidi.def" />
|
||||
<include base="beepmidi">.</include>
|
||||
<define name="__USE_W32API" />
|
||||
<define name="UNICODE" />
|
||||
<define name="_UNICODE" />
|
||||
<library>ntdll</library>
|
||||
<library>kernel32</library>
|
||||
<library>user32</library>
|
||||
<library>winmm</library>
|
||||
<file>beepmidi.c</file>
|
||||
</module>
|
51
reactos/dll/win32/beepmidi/readme.txt
Normal file
51
reactos/dll/win32/beepmidi/readme.txt
Normal file
|
@ -0,0 +1,51 @@
|
|||
BEEPMIDI :: BEEP.SYS MIDI DRIVER
|
||||
(c) Andrew Greenwood, 2007.
|
||||
|
||||
http://www.silverblade.co.uk
|
||||
|
||||
Released as open-source software. You may copy, re-distribute and modify
|
||||
this software, provided this copyright notice remains intact.
|
||||
|
||||
WHAT'S THIS ?
|
||||
BeepMidi is a MME MIDI driver for NT-compatible operating systems,
|
||||
which uses BEEP.SYS (the kernel-mode PC speaker driver) to play
|
||||
MIDI data. It installs as a standard MIDI output device and can even
|
||||
be selected as your default MIDI output device. The fundamental
|
||||
code for interacting with BEEP.SYS was taken from ReactOS' kernel32
|
||||
module.
|
||||
|
||||
WHY WAS THIS WRITTEN ?
|
||||
Primarily for educational reasons - in the process, I've learned more
|
||||
about the driver side of the MME API and how to interact with kernel
|
||||
device drivers. It aids as a good starting point from which to
|
||||
move on to bigger and better things :)
|
||||
|
||||
HOW TO INSTALL :
|
||||
Copy the file to C:\WINDOWS\SYSTEM32\BEEPMIDI.DLL
|
||||
|
||||
Go into HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\
|
||||
Drivers32 in RegEdit and look for the "midi" entries on the right hand
|
||||
side. Find the highest numbered one (eg: midi1) and create a new STRING
|
||||
value. Give it another midi name, but one above the current highest
|
||||
entry present (eg: midi2.)
|
||||
|
||||
You'll now see a "PC Speaker" entry in Sound & Audio Devices.
|
||||
|
||||
TWEAKING:
|
||||
See the comments toward the top of beepmidi.c for tweakable driver
|
||||
parameters. These can only be adjusted in the source code at present.
|
||||
|
||||
FEATURES :
|
||||
* Supports note-on and note-off messages on channels 1-9 and 11-16
|
||||
(channel 10 is rhythm, which is not supported.)
|
||||
* Fake polyphony (actually just arpeggiates playing notes!)
|
||||
* Threaded design for continuous playback (optional.)
|
||||
|
||||
ROOM FOR IMPROVEMENT :
|
||||
* Pitch bend is not supported
|
||||
* Velocity could determine timeslice
|
||||
* Should wait for timeslice to complete before adding/removing notes
|
||||
* Would be nice to allow configuration of polyphony etc. via Control Panel
|
||||
|
||||
BUGS :
|
||||
* Crashes when used with Windows Media Player (mplayer2 is fine though)
|
Loading…
Reference in a new issue