⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 beepmidi.c

📁 这是一个开放源代码的与WINNT/WIN2K/WIN2003兼容的操作系统
💻 C
📖 第 1 页 / 共 2 页
字号:
/*
    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 + -