2 BeepMidi :: beep.sys MIDI player
4 (c) Andrew Greenwood, 2007.
6 Released as open-source software. You may copy, re-distribute and modify
7 this software, provided this copyright notice remains intact.
9 Please see the included README.TXT for more information
12 16th January 2007 Started
13 17th January 2007 Polyphony support and threading added
14 18th January 2007 Made threading optional, added comments
17 /* The timeslice to allocate for all playing notes (in milliseconds) */
18 #define TIMESLICE_SIZE 60
21 If this is defined, notes are added to the playing list, even if
22 they already exist. As a result, the note will sound twice during
23 each timeslice. Also each note on will require a corresponding note
26 #define ALLOW_DUPLICATE_NOTES
29 The maximum number of notes that may be playing at any one time.
30 Higher values result in a messier sound as all the frequencies get
31 mashed together. Do not set this below 2. Recommended = 4
36 Define CONTINUOUS_NOTES to perform note playback in a separate thread.
37 This was originally the intended behaviour, but after experimentation
38 doesn't sound as good for MIDI files which have a lot going on. If not
39 defined, all playing notes are output in sequence as a new note starts.
41 #define CONTINUOUS_NOTES
43 #define WIN32_NO_STATUS
44 #define NTOS_MODE_USER
46 #include <ndk/ntndk.h>
54 /*#define DPRINT printf*/
55 #define DPRINT FakePrintf
57 /* A few MIDI command categories */
58 #define MIDI_NOTE_OFF 0x80
59 #define MIDI_NOTE_ON 0x90
60 #define MIDI_CONTROL_CHANGE 0xB0
61 #define MIDI_PROGRAM 0xC0
62 #define MIDI_PITCH_BEND 0xE0
63 #define MIDI_SYSTEM 0xFF
65 /* Specific commands */
66 #define MIDI_RESET 0xFF
69 typedef struct _NoteNode
71 struct _NoteNode
* next
;
72 struct _NoteNode
* previous
;
75 UCHAR velocity
; /* 0 is note-off */
78 typedef struct _DeviceInfo
89 DWORD playing_notes_count
;
94 BOOL terminate_thread
;
95 HANDLE thread_termination_complete
;
98 DeviceInfo
* the_device
;
99 CRITICAL_SECTION device_lock
;
102 FakePrintf(char* str
, ...)
104 /* Just to shut the compiler up */
109 This is designed to be treated as a thread, however it behaves as a
110 normal function if CONTINUOUS_NOTES is not defined.
117 DeviceInfo
* device_info
= (DeviceInfo
*) parameter
;
119 IO_STATUS_BLOCK io_status_block
;
122 DPRINT("Note processing started\n");
124 /* We lock the note list only while accessing it */
126 #ifdef CONTINUOUS_NOTES
127 while ( ! device_info
->terminate_thread
)
132 /* Number of notes being arpeggiated */
135 EnterCriticalSection(&device_lock
);
137 /* Calculate how much time to allocate to each playing note */
139 DPRINT("%d notes active\n", (int) device_info
->playing_notes_count
);
141 node
= device_info
->note_list
;
143 while ( ( node
!= NULL
) && ( arp_notes
<= POLYPHONY
) )
145 DPRINT("playing..\n");
146 BEEP_SET_PARAMETERS beep_data
;
147 DWORD actually_playing
= 0;
149 double frequency
= node
->note
;
150 frequency
= frequency
/ 12;
151 frequency
= pow(2, frequency
);
152 frequency
= 8.1758 * frequency
;
154 if (device_info
->playing_notes_count
> POLYPHONY
)
155 actually_playing
= POLYPHONY
;
157 actually_playing
= device_info
->playing_notes_count
;
159 DPRINT("Frequency %f\n", frequency
);
162 beep_data
.Frequency
= (DWORD
) frequency
;
163 beep_data
.Duration
= TIMESLICE_SIZE
/ actually_playing
; /* device_info->playing_notes_count; */
165 status
= NtDeviceIoControlFile(device_info
->kernel_device
,
172 sizeof(BEEP_SET_PARAMETERS
),
176 if ( ! NT_SUCCESS(status
) )
178 DPRINT("ERROR %d\n", (int) GetLastError());
181 SleepEx(beep_data
.Duration
, TRUE
);
183 if ( device_info
->refresh_notes
)
185 device_info
->refresh_notes
= FALSE
;
193 LeaveCriticalSection(&device_lock
);
196 #ifdef CONTINUOUS_NOTES
197 SetEvent(device_info
->thread_termination_complete
);
205 Fills a MIDIOUTCAPS structure with information about our device.
209 GetDeviceCapabilities(
212 /* These are ignored for now */
216 caps
->vDriverVersion
= 0x0100;
218 memset(caps
->szPname
, 0, sizeof(caps
->szPname
));
219 memcpy(caps
->szPname
, L
"PC speaker\0", strlen("PC speaker\0") * 2);
221 caps
->wTechnology
= MOD_SQSYNTH
;
223 caps
->wVoices
= 1; /* We only have one voice */
224 caps
->wNotes
= POLYPHONY
;
225 caps
->wChannelMask
= 0xFFBF; /* Ignore channel 10 */
229 return MMSYSERR_NOERROR
;
234 Helper function that just simplifies calling the application making use
240 DeviceInfo
* device_info
,
245 DPRINT("Calling client - callback 0x%x mmhandle 0x%x\n", (int) device_info
->callback
, (int) device_info
->mme_handle
);
246 return DriverCallback(device_info
->callback
,
247 HIWORD(device_info
->flags
),
248 device_info
->mme_handle
,
250 device_info
->instance
,
258 Open the kernel-mode device and allocate resources. This opens the
259 BEEP.SYS kernel device.
264 DeviceInfo
** private_data
,
265 MIDIOPENDESC
* open_desc
,
270 HANDLE kernel_device
;
271 UNICODE_STRING beep_device_name
;
272 OBJECT_ATTRIBUTES attribs
;
273 IO_STATUS_BLOCK status_block
;
275 /* One at a time.. */
278 DPRINT("Already allocated\n");
279 return MMSYSERR_ALLOCATED
;
282 /* Make the device name into a unicode string and open it */
284 RtlInitUnicodeString(&beep_device_name
,
287 InitializeObjectAttributes(&attribs
,
293 status
= NtCreateFile(&kernel_device
,
294 FILE_READ_DATA
| FILE_WRITE_DATA
,
299 FILE_SHARE_READ
| FILE_SHARE_WRITE
,
305 if ( ! NT_SUCCESS(status
) )
307 DPRINT("Could not connect to BEEP device - %d\n", (int) GetLastError());
308 return MMSYSERR_ERROR
;
313 /* Allocate and initialize the device info */
315 heap
= GetProcessHeap();
317 the_device
= HeapAlloc(heap
, HEAP_ZERO_MEMORY
, sizeof(DeviceInfo
));
321 DPRINT("Out of memory\n");
322 return MMSYSERR_NOMEM
;
326 the_device
->kernel_device
= kernel_device
;
327 the_device
->playing_notes_count
= 0;
328 the_device
->note_list
= NULL
;
329 the_device
->thread_handle
= 0;
330 the_device
->terminate_thread
= FALSE
;
331 the_device
->running_status
= 0;
334 the_device
->mme_handle
= (HDRVR
) open_desc
->hMidi
;
335 the_device
->callback
= open_desc
->dwCallback
;
336 the_device
->instance
= open_desc
->dwInstance
;
337 the_device
->flags
= flags
;
339 /* Store the pointer in the user data */
340 *private_data
= the_device
;
342 /* This is threading-related code */
343 #ifdef CONTINUOUS_NOTES
344 the_device
->thread_termination_complete
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
346 if ( ! the_device
->thread_termination_complete
)
348 DPRINT("CreateEvent failed\n");
349 HeapFree(heap
, 0, the_device
);
350 return MMSYSERR_NOMEM
;
353 the_device
->thread_handle
= CreateThread(NULL
,
360 if ( ! the_device
->thread_handle
)
362 DPRINT("CreateThread failed\n");
363 CloseHandle(the_device
->thread_termination_complete
);
364 HeapFree(heap
, 0, the_device
);
365 return MMSYSERR_NOMEM
;
369 /* Now we call the client application to say the device is open */
370 DPRINT("Sending MOM_OPEN\n");
371 DPRINT("Success? %d\n", (int) CallClient(the_device
, MOM_OPEN
, 0, 0));
373 return MMSYSERR_NOERROR
;
378 Close the kernel-mode device.
382 CloseDevice(DeviceInfo
* device_info
)
384 HANDLE heap
= GetProcessHeap();
386 /* If we're working in threaded mode we need to wait for thread to die */
387 #ifdef CONTINUOUS_NOTES
388 the_device
->terminate_thread
= TRUE
;
390 WaitForSingleObject(the_device
->thread_termination_complete
, INFINITE
);
392 CloseHandle(the_device
->thread_termination_complete
);
395 /* Let the client application know the device is closing */
396 DPRINT("Sending MOM_CLOSE\n");
397 CallClient(device_info
, MOM_CLOSE
, 0, 0);
399 NtClose(device_info
->kernel_device
);
402 HeapFree(heap
, 0, device_info
);
406 return MMSYSERR_NOERROR
;
411 Removes a note from the playing notes list. If the note is not playing,
412 we just pretend nothing happened.
417 DeviceInfo
* device_info
,
420 HANDLE heap
= GetProcessHeap();
422 NoteNode
* prev_node
= NULL
;
424 DPRINT("StopNote\n");
426 EnterCriticalSection(&device_lock
);
428 node
= device_info
->note_list
;
430 while ( node
!= NULL
)
432 if ( node
->note
== note
)
434 /* Found the note - just remove the node from the list */
436 DPRINT("Stopping note %d\n", (int) node
->note
);
438 if ( prev_node
!= NULL
)
439 prev_node
->next
= node
->next
;
441 device_info
->note_list
= node
->next
;
443 HeapFree(heap
, 0, node
);
445 device_info
->playing_notes_count
--;
447 DPRINT("Note stopped - now playing %d notes\n", (int) device_info
->playing_notes_count
);
449 LeaveCriticalSection(&device_lock
);
450 device_info
->refresh_notes
= TRUE
;
452 return MMSYSERR_NOERROR
;
459 LeaveCriticalSection(&device_lock
);
461 /* Hmm, a good idea? */
462 #ifndef CONTINUOUS_NOTES
463 ProcessPlayingNotes((PVOID
) device_info
);
466 return MMSYSERR_NOERROR
;
471 Adds a note to the playing notes list. If the note is already playing,
472 the definition of ALLOW_DUPLICATE_NOTES determines if an existing note
473 may be duplicated. Otherwise, duplicate notes are ignored.
478 DeviceInfo
* device_info
,
482 HANDLE heap
= GetProcessHeap();
484 DPRINT("PlayNote\n");
490 DPRINT("Zero velocity\n");
492 /* Velocity zero is effectively a "note off" */
493 StopNote(device_info
, note
);
497 /* Start playing the note */
499 NoteNode
* tail_node
= NULL
;
501 EnterCriticalSection(&device_lock
);
503 node
= device_info
->note_list
;
505 while ( node
!= NULL
)
507 #ifndef ALLOW_DUPLICATE_NOTES
508 if ( ( node
->note
== note
) && ( velocity
> 0 ) )
510 /* The note is already playing - do nothing */
511 DPRINT("Duplicate note playback request ignored\n");
512 LeaveCriticalSection(&device_lock
);
513 return MMSYSERR_NOERROR
;
521 new_node
= HeapAlloc(heap
, HEAP_ZERO_MEMORY
, sizeof(NoteNode
));
525 LeaveCriticalSection(&device_lock
);
526 return MMSYSERR_NOMEM
;
529 new_node
->note
= note
;
530 new_node
->velocity
= velocity
;
533 Prepend to the playing notes list. If exceeding polyphony,
534 remove the oldest note (which will be at the tail.)
537 if ( device_info
->note_list
)
538 device_info
->note_list
->previous
= new_node
;
540 new_node
->next
= device_info
->note_list
;
541 new_node
->previous
= NULL
;
543 device_info
->note_list
= new_node
;
544 device_info
->playing_notes_count
++;
547 if ( device_info->playing_notes_count > POLYPHONY )
551 DPRINT("Polyphony exceeded\n");
553 tail_node->previous->next = NULL;
555 HeapFree(heap, 0, tail_node);
557 device_info->playing_notes_count --;
561 LeaveCriticalSection(&device_lock
);
563 DPRINT("Note started - now playing %d notes\n", (int) device_info
->playing_notes_count
);
564 device_info
->refresh_notes
= TRUE
;
567 #ifndef CONTINUOUS_NOTES
568 ProcessPlayingNotes((PVOID
) device_info
);
571 return MMSYSERR_NOERROR
;
575 Decipher a short MIDI message (which is a MIDI message packed into a DWORD.)
576 This will set "running status", but does not take this into account when
577 processing messages (is this necessary?)
581 ProcessShortMidiMessage(
582 DeviceInfo
* device_info
,
591 status
= message
& 0x000000FF;
593 /* Deal with running status */
595 if ( status
< MIDI_NOTE_OFF
)
597 status
= device_info
->running_status
;
600 /* Ensure the status is sane! */
602 if ( status
< MIDI_NOTE_OFF
)
604 /* It's garbage, ignore it */
605 return MMSYSERR_NOERROR
;
608 /* Figure out the message category and channel */
610 category
= status
& 0xF0;
611 channel
= status
& 0x0F; /* we don't use this */
613 data1
= (message
& 0x0000FF00) >> 8;
614 data2
= (message
& 0x00FF0000) >> 16;
616 DPRINT("0x%x, %d, %d\n", (int) status
, (int) data1
, (int) data2
);
618 /* Filter drums (which are *usually* on channel 10) */
621 return MMSYSERR_NOERROR
;
624 /* Pass to the appropriate message handler */
630 PlayNote(device_info
, data1
, data2
);
636 StopNote(device_info
, data1
);
641 return MMSYSERR_NOERROR
;
645 #define PACK_MIDI(b1, b2, b3) \
646 ((b3 * 65536) + (b2 * 256) + b1);
650 Processes a "long" MIDI message (ie, a MIDI message contained within a
651 buffer.) This is intended for supporting SysEx data, or blocks of MIDI
652 events. However in our case we're only interested in short MIDI messages,
653 so we scan the buffer, and each time we encounter a valid status byte
654 we start recording it as a new event. Once 3 bytes or a new status is
655 received, the event is passed to the short message handler.
659 ProcessLongMidiMessage(
660 DeviceInfo
* device_info
,
664 UCHAR
* midi_bytes
= (UCHAR
*) header
->lpData
;
669 /* Initialize the buffer */
670 msg
[0] = msg
[1] = msg
[2] = 0;
672 if ( ! ( header
->dwFlags
& MHDR_PREPARED
) )
674 DPRINT("Not prepared!\n");
675 return MIDIERR_UNPREPARED
;
678 DPRINT("Processing %d bytes of MIDI\n", (int) header
->dwBufferLength
);
680 while ( index
< header
->dwBufferLength
)
682 /* New status byte? ( = new event) */
683 if ( midi_bytes
[index
] & 0x80 )
687 /* Deal with the existing event */
691 short_msg
= PACK_MIDI(msg
[0], msg
[1], msg
[2]);
693 DPRINT("Complete msg is 0x%x %d %d\n", (int) msg
[0], (int) msg
[1], (int) msg
[2]);
694 ProcessShortMidiMessage(device_info
, short_msg
);
697 /* Set new running status and start recording the event */
698 DPRINT("Set new running status\n");
699 device_info
->running_status
= midi_bytes
[index
];
700 msg
[0] = midi_bytes
[index
];
704 /* Unexpected data byte? ( = re-use previous status) */
705 else if ( msg_index
== 0 )
707 if ( device_info
->running_status
& 0x80 )
709 DPRINT("Retrieving running status\n");
710 msg
[0] = device_info
->running_status
;
711 msg
[1] = midi_bytes
[index
];
718 /* Expected data ( = append to message until buffer full) */
721 DPRINT("Next byte...\n");
722 msg
[msg_index
] = midi_bytes
[index
];
729 short_msg
= PACK_MIDI(msg
[0], msg
[1], msg
[2]);
731 DPRINT("Complete msg is 0x%x %d %d\n", (int) msg
[0], (int) msg
[1], (int) msg
[2]);
732 ProcessShortMidiMessage(device_info
, short_msg
);
736 msg
[0] = msg
[1] = msg
[2] = 0;
744 We're meant to clear MHDR_DONE and set MHDR_INQUEUE but since we
745 deal with everything here and now we might as well just say so.
747 header
->dwFlags
|= MHDR_DONE
;
748 header
->dwFlags
&= ~ MHDR_INQUEUE
;
750 DPRINT("Success? %d\n", (int) CallClient(the_device
, MOM_DONE
, (DWORD
) header
, 0));
752 return MMSYSERR_NOERROR
;
757 Exported function that receives messages from WINMM (the MME API.)
771 case MODM_GETNUMDEVS
:
773 /* Only one internal PC speaker device (and even that's too much) */
774 DPRINT("MODM_GETNUMDEVS\n");
778 case MODM_GETDEVCAPS
:
780 DPRINT("MODM_GETDEVCAPS\n");
781 return GetDeviceCapabilities((MIDIOUTCAPS
*) parameter1
);
786 DPRINT("MODM_OPEN\n");
788 return OpenDevice((DeviceInfo
**) private_data
,
789 (MIDIOPENDESC
*) parameter1
,
795 DPRINT("MODM_CLOSE\n");
796 return CloseDevice((DeviceInfo
*) private_data
);
801 return ProcessShortMidiMessage((DeviceInfo
*) private_data
, parameter1
);
806 /* We don't bother with this */
807 MIDIHDR
* hdr
= (MIDIHDR
*) parameter1
;
808 hdr
->dwFlags
|= MHDR_PREPARED
;
809 return MMSYSERR_NOERROR
;
812 case MODM_UNPREPARE
:
814 MIDIHDR
* hdr
= (MIDIHDR
*) parameter1
;
815 hdr
->dwFlags
&= ~MHDR_PREPARED
;
816 return MMSYSERR_NOERROR
;
821 DPRINT("LONGDATA\n");
822 return ProcessLongMidiMessage((DeviceInfo
*) private_data
, (MIDIHDR
*) parameter1
);
832 DPRINT("Not supported %d\n", message
);
834 return MMSYSERR_NOTSUPPORTED
;
853 DPRINT("DRV_LOAD\n");
858 DPRINT("DRV_FREE\n");
862 DPRINT("DRV_OPEN\n");
863 InitializeCriticalSection(&device_lock
);
867 DPRINT("DRV_CLOSE\n");
871 DPRINT("DRV_ENABLE\n");
875 DPRINT("DRV_DISABLE\n");
879 We don't provide configuration capabilities. This used to be
880 for things like I/O port, IRQ, DMA settings, etc.
883 case DRV_QUERYCONFIGURE
:
884 DPRINT("DRV_QUERYCONFIGURE\n");
888 DPRINT("DRV_CONFIGURE\n");
892 DPRINT("DRV_INSTALL\n");
893 return DRVCNF_RESTART
;
898 return DefDriverProc(driver_id
,