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