📄 beepmidi.c
字号:
/*
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;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -