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 BEEP_SET_PARAMETERS beep_data
;
146 DWORD actually_playing
= 0;
148 double frequency
= node
->note
;
150 DPRINT("playing..\n");
152 frequency
= frequency
/ 12;
153 frequency
= pow(2, frequency
);
154 frequency
= 8.1758 * frequency
;
156 if (device_info
->playing_notes_count
> POLYPHONY
)
157 actually_playing
= POLYPHONY
;
159 actually_playing
= device_info
->playing_notes_count
;
161 DPRINT("Frequency %f\n", frequency
);
164 beep_data
.Frequency
= (DWORD
) frequency
;
165 beep_data
.Duration
= TIMESLICE_SIZE
/ actually_playing
; /* device_info->playing_notes_count; */
167 status
= NtDeviceIoControlFile(device_info
->kernel_device
,
174 sizeof(BEEP_SET_PARAMETERS
),
178 if ( ! NT_SUCCESS(status
) )
180 DPRINT("ERROR %d\n", (int) GetLastError());
183 SleepEx(beep_data
.Duration
, TRUE
);
185 if ( device_info
->refresh_notes
)
187 device_info
->refresh_notes
= FALSE
;
195 LeaveCriticalSection(&device_lock
);
198 #ifdef CONTINUOUS_NOTES
199 SetEvent(device_info
->thread_termination_complete
);
207 Fills a MIDIOUTCAPS structure with information about our device.
211 GetDeviceCapabilities(
214 /* These are ignored for now */
218 caps
->vDriverVersion
= 0x0100;
220 memset(caps
->szPname
, 0, sizeof(caps
->szPname
));
221 memcpy(caps
->szPname
, L
"PC speaker\0", strlen("PC speaker\0") * 2);
223 caps
->wTechnology
= MOD_SQSYNTH
;
225 caps
->wVoices
= 1; /* We only have one voice */
226 caps
->wNotes
= POLYPHONY
;
227 caps
->wChannelMask
= 0xFFBF; /* Ignore channel 10 */
231 return MMSYSERR_NOERROR
;
236 Helper function that just simplifies calling the application making use
242 DeviceInfo
* device_info
,
244 DWORD_PTR parameter1
,
245 DWORD_PTR parameter2
)
247 DPRINT("Calling client - callback 0x%x mmhandle 0x%x\n", device_info
->callback
, device_info
->mme_handle
);
248 return DriverCallback(device_info
->callback
,
249 HIWORD(device_info
->flags
),
250 device_info
->mme_handle
,
252 device_info
->instance
,
260 Open the kernel-mode device and allocate resources. This opens the
261 BEEP.SYS kernel device.
266 DeviceInfo
** private_data
,
267 MIDIOPENDESC
* open_desc
,
272 HANDLE kernel_device
;
273 UNICODE_STRING beep_device_name
;
274 OBJECT_ATTRIBUTES attribs
;
275 IO_STATUS_BLOCK status_block
;
277 /* One at a time.. */
280 DPRINT("Already allocated\n");
281 return MMSYSERR_ALLOCATED
;
284 /* Make the device name into a unicode string and open it */
286 RtlInitUnicodeString(&beep_device_name
,
289 InitializeObjectAttributes(&attribs
,
295 status
= NtCreateFile(&kernel_device
,
296 FILE_READ_DATA
| FILE_WRITE_DATA
,
301 FILE_SHARE_READ
| FILE_SHARE_WRITE
,
307 if ( ! NT_SUCCESS(status
) )
309 DPRINT("Could not connect to BEEP device - %d\n", (int) GetLastError());
310 return MMSYSERR_ERROR
;
315 /* Allocate and initialize the device info */
317 heap
= GetProcessHeap();
319 the_device
= HeapAlloc(heap
, HEAP_ZERO_MEMORY
, sizeof(DeviceInfo
));
323 DPRINT("Out of memory\n");
324 return MMSYSERR_NOMEM
;
328 the_device
->kernel_device
= kernel_device
;
329 the_device
->playing_notes_count
= 0;
330 the_device
->note_list
= NULL
;
331 the_device
->thread_handle
= 0;
332 the_device
->terminate_thread
= FALSE
;
333 the_device
->running_status
= 0;
336 the_device
->mme_handle
= (HDRVR
) open_desc
->hMidi
;
337 the_device
->callback
= open_desc
->dwCallback
;
338 the_device
->instance
= open_desc
->dwInstance
;
339 the_device
->flags
= flags
;
341 /* Store the pointer in the user data */
342 *private_data
= the_device
;
344 /* This is threading-related code */
345 #ifdef CONTINUOUS_NOTES
346 the_device
->thread_termination_complete
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
348 if ( ! the_device
->thread_termination_complete
)
350 DPRINT("CreateEvent failed\n");
351 HeapFree(heap
, 0, the_device
);
352 return MMSYSERR_NOMEM
;
355 the_device
->thread_handle
= CreateThread(NULL
,
362 if ( ! the_device
->thread_handle
)
364 DPRINT("CreateThread failed\n");
365 CloseHandle(the_device
->thread_termination_complete
);
366 HeapFree(heap
, 0, the_device
);
367 return MMSYSERR_NOMEM
;
371 /* Now we call the client application to say the device is open */
372 DPRINT("Sending MOM_OPEN\n");
373 DPRINT("Success? %d\n", (int) CallClient(the_device
, MOM_OPEN
, 0, 0));
375 return MMSYSERR_NOERROR
;
380 Close the kernel-mode device.
384 CloseDevice(DeviceInfo
* device_info
)
386 HANDLE heap
= GetProcessHeap();
388 /* If we're working in threaded mode we need to wait for thread to die */
389 #ifdef CONTINUOUS_NOTES
390 the_device
->terminate_thread
= TRUE
;
392 WaitForSingleObject(the_device
->thread_termination_complete
, INFINITE
);
394 CloseHandle(the_device
->thread_termination_complete
);
397 /* Let the client application know the device is closing */
398 DPRINT("Sending MOM_CLOSE\n");
399 CallClient(device_info
, MOM_CLOSE
, 0, 0);
401 NtClose(device_info
->kernel_device
);
404 HeapFree(heap
, 0, device_info
);
408 return MMSYSERR_NOERROR
;
413 Removes a note from the playing notes list. If the note is not playing,
414 we just pretend nothing happened.
419 DeviceInfo
* device_info
,
422 HANDLE heap
= GetProcessHeap();
424 NoteNode
* prev_node
= NULL
;
426 DPRINT("StopNote\n");
428 EnterCriticalSection(&device_lock
);
430 node
= device_info
->note_list
;
432 while ( node
!= NULL
)
434 if ( node
->note
== note
)
436 /* Found the note - just remove the node from the list */
438 DPRINT("Stopping note %d\n", (int) node
->note
);
440 if ( prev_node
!= NULL
)
441 prev_node
->next
= node
->next
;
443 device_info
->note_list
= node
->next
;
445 HeapFree(heap
, 0, node
);
447 device_info
->playing_notes_count
--;
449 DPRINT("Note stopped - now playing %d notes\n", (int) device_info
->playing_notes_count
);
451 LeaveCriticalSection(&device_lock
);
452 device_info
->refresh_notes
= TRUE
;
454 return MMSYSERR_NOERROR
;
461 LeaveCriticalSection(&device_lock
);
463 /* Hmm, a good idea? */
464 #ifndef CONTINUOUS_NOTES
465 ProcessPlayingNotes((PVOID
) device_info
);
468 return MMSYSERR_NOERROR
;
473 Adds a note to the playing notes list. If the note is already playing,
474 the definition of ALLOW_DUPLICATE_NOTES determines if an existing note
475 may be duplicated. Otherwise, duplicate notes are ignored.
480 DeviceInfo
* device_info
,
484 HANDLE heap
= GetProcessHeap();
488 DPRINT("PlayNote\n");
492 DPRINT("Zero velocity\n");
494 /* Velocity zero is effectively a "note off" */
495 StopNote(device_info
, note
);
499 /* Start playing the note */
501 NoteNode
* tail_node
= NULL
;
503 EnterCriticalSection(&device_lock
);
505 node
= device_info
->note_list
;
507 while ( node
!= NULL
)
509 #ifndef ALLOW_DUPLICATE_NOTES
510 if ( ( node
->note
== note
) && ( velocity
> 0 ) )
512 /* The note is already playing - do nothing */
513 DPRINT("Duplicate note playback request ignored\n");
514 LeaveCriticalSection(&device_lock
);
515 return MMSYSERR_NOERROR
;
523 new_node
= HeapAlloc(heap
, HEAP_ZERO_MEMORY
, sizeof(NoteNode
));
527 LeaveCriticalSection(&device_lock
);
528 return MMSYSERR_NOMEM
;
531 new_node
->note
= note
;
532 new_node
->velocity
= velocity
;
535 Prepend to the playing notes list. If exceeding polyphony,
536 remove the oldest note (which will be at the tail.)
539 if ( device_info
->note_list
)
540 device_info
->note_list
->previous
= new_node
;
542 new_node
->next
= device_info
->note_list
;
543 new_node
->previous
= NULL
;
545 device_info
->note_list
= new_node
;
546 device_info
->playing_notes_count
++;
549 if ( device_info->playing_notes_count > POLYPHONY )
553 DPRINT("Polyphony exceeded\n");
555 tail_node->previous->next = NULL;
557 HeapFree(heap, 0, tail_node);
559 device_info->playing_notes_count --;
563 LeaveCriticalSection(&device_lock
);
565 DPRINT("Note started - now playing %d notes\n", (int) device_info
->playing_notes_count
);
566 device_info
->refresh_notes
= TRUE
;
569 #ifndef CONTINUOUS_NOTES
570 ProcessPlayingNotes((PVOID
) device_info
);
573 return MMSYSERR_NOERROR
;
577 Decipher a short MIDI message (which is a MIDI message packed into a DWORD.)
578 This will set "running status", but does not take this into account when
579 processing messages (is this necessary?)
583 ProcessShortMidiMessage(
584 DeviceInfo
* device_info
,
593 status
= message
& 0x000000FF;
595 /* Deal with running status */
597 if ( status
< MIDI_NOTE_OFF
)
599 status
= device_info
->running_status
;
602 /* Ensure the status is sane! */
604 if ( status
< MIDI_NOTE_OFF
)
606 /* It's garbage, ignore it */
607 return MMSYSERR_NOERROR
;
610 /* Figure out the message category and channel */
612 category
= status
& 0xF0;
613 channel
= status
& 0x0F; /* we don't use this */
615 data1
= (message
& 0x0000FF00) >> 8;
616 data2
= (message
& 0x00FF0000) >> 16;
618 DPRINT("0x%x, %d, %d\n", (int) status
, (int) data1
, (int) data2
);
620 /* Filter drums (which are *usually* on channel 10) */
623 return MMSYSERR_NOERROR
;
626 /* Pass to the appropriate message handler */
632 PlayNote(device_info
, data1
, data2
);
638 StopNote(device_info
, data1
);
643 return MMSYSERR_NOERROR
;
647 #define PACK_MIDI(b1, b2, b3) \
648 ((b3 * 65536) + (b2 * 256) + b1);
652 Processes a "long" MIDI message (ie, a MIDI message contained within a
653 buffer.) This is intended for supporting SysEx data, or blocks of MIDI
654 events. However in our case we're only interested in short MIDI messages,
655 so we scan the buffer, and each time we encounter a valid status byte
656 we start recording it as a new event. Once 3 bytes or a new status is
657 received, the event is passed to the short message handler.
661 ProcessLongMidiMessage(
662 DeviceInfo
* device_info
,
665 unsigned int index
= 0;
666 UCHAR
* midi_bytes
= (UCHAR
*) header
->lpData
;
668 unsigned int msg_index
= 0;
671 /* Initialize the buffer */
672 msg
[0] = msg
[1] = msg
[2] = 0;
674 if ( ! ( header
->dwFlags
& MHDR_PREPARED
) )
676 DPRINT("Not prepared!\n");
677 return MIDIERR_UNPREPARED
;
680 DPRINT("Processing %d bytes of MIDI\n", (int) header
->dwBufferLength
);
682 while ( index
< header
->dwBufferLength
)
684 /* New status byte? ( = new event) */
685 if ( midi_bytes
[index
] & 0x80 )
689 /* Deal with the existing event */
693 short_msg
= PACK_MIDI(msg
[0], msg
[1], msg
[2]);
695 DPRINT("Complete msg is 0x%x %d %d\n", (int) msg
[0], (int) msg
[1], (int) msg
[2]);
696 ProcessShortMidiMessage(device_info
, short_msg
);
699 /* Set new running status and start recording the event */
700 DPRINT("Set new running status\n");
701 device_info
->running_status
= midi_bytes
[index
];
702 msg
[0] = midi_bytes
[index
];
706 /* Unexpected data byte? ( = re-use previous status) */
707 else if ( msg_index
== 0 )
709 if ( device_info
->running_status
& 0x80 )
711 DPRINT("Retrieving running status\n");
712 msg
[0] = device_info
->running_status
;
713 msg
[1] = midi_bytes
[index
];
720 /* Expected data ( = append to message until buffer full) */
723 DPRINT("Next byte...\n");
724 msg
[msg_index
] = midi_bytes
[index
];
731 short_msg
= PACK_MIDI(msg
[0], msg
[1], msg
[2]);
733 DPRINT("Complete msg is 0x%x %d %d\n", (int) msg
[0], (int) msg
[1], (int) msg
[2]);
734 ProcessShortMidiMessage(device_info
, short_msg
);
738 msg
[0] = msg
[1] = msg
[2] = 0;
746 We're meant to clear MHDR_DONE and set MHDR_INQUEUE but since we
747 deal with everything here and now we might as well just say so.
749 header
->dwFlags
|= MHDR_DONE
;
750 header
->dwFlags
&= ~ MHDR_INQUEUE
;
752 DPRINT("Success? %d\n", CallClient(the_device
, MOM_DONE
, (DWORD_PTR
) header
, 0));
754 return MMSYSERR_NOERROR
;
759 Exported function that receives messages from WINMM (the MME API.)
767 DWORD_PTR private_data
,
768 DWORD_PTR parameter1
,
769 DWORD_PTR parameter2
)
773 case MODM_GETNUMDEVS
:
775 /* Only one internal PC speaker device (and even that's too much) */
776 DPRINT("MODM_GETNUMDEVS\n");
780 case MODM_GETDEVCAPS
:
782 DPRINT("MODM_GETDEVCAPS\n");
783 return GetDeviceCapabilities((MIDIOUTCAPS
*) parameter1
);
788 DPRINT("MODM_OPEN\n");
790 return OpenDevice((DeviceInfo
**) private_data
,
791 (MIDIOPENDESC
*) parameter1
,
797 DPRINT("MODM_CLOSE\n");
798 return CloseDevice((DeviceInfo
*) private_data
);
803 return ProcessShortMidiMessage((DeviceInfo
*) private_data
, parameter1
);
808 /* We don't bother with this */
809 MIDIHDR
* hdr
= (MIDIHDR
*) parameter1
;
810 hdr
->dwFlags
|= MHDR_PREPARED
;
811 return MMSYSERR_NOERROR
;
814 case MODM_UNPREPARE
:
816 MIDIHDR
* hdr
= (MIDIHDR
*) parameter1
;
817 hdr
->dwFlags
&= ~MHDR_PREPARED
;
818 return MMSYSERR_NOERROR
;
823 DPRINT("LONGDATA\n");
824 return ProcessLongMidiMessage((DeviceInfo
*) private_data
, (MIDIHDR
*) parameter1
);
834 DPRINT("Not supported %d\n", message
);
836 return MMSYSERR_NOTSUPPORTED
;
856 DPRINT("DRV_LOAD\n");
861 DPRINT("DRV_FREE\n");
865 DPRINT("DRV_OPEN\n");
866 InitializeCriticalSection(&device_lock
);
870 DPRINT("DRV_CLOSE\n");
874 DPRINT("DRV_ENABLE\n");
878 DPRINT("DRV_DISABLE\n");
882 We don't provide configuration capabilities. This used to be
883 for things like I/O port, IRQ, DMA settings, etc.
886 case DRV_QUERYCONFIGURE
:
887 DPRINT("DRV_QUERYCONFIGURE\n");
891 DPRINT("DRV_CONFIGURE\n");
895 DPRINT("DRV_INSTALL\n");
896 return DRVCNF_RESTART
;
901 return DefDriverProc(driver_id
,