1 //
2 // Copyright(C) 2012 James Haley
3 // Copyright(C) 2017 Alex Mayfield
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // DESCRIPTION:
16 //
17 // Win32/SDL_mixer MIDI Server
18 //
19 // Uses pipes to communicate with Doom. This allows this separate process to
20 // have its own independent volume control even under Windows Vista and up's
21 // broken, stupid, completely useless mixer model that can't assign separate
22 // volumes to different devices for the same process.
23 //
24 // Seriously, how did they screw up something so fundamental?
25 //
26
27 #ifdef _WIN32
28
29 #define WIN32_LEAN_AND_MEAN
30 #include <windows.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33
34 #include "SDL.h"
35 #include "SDL_mixer.h"
36
37 #include "buffer.h"
38 #include "proto.h"
39
40 #include "config.h"
41 #include "doomtype.h"
42
43 static HANDLE midi_process_in; // Standard In.
44 static HANDLE midi_process_out; // Standard Out.
45
46 // Sound sample rate to use for digital output (Hz)
47 static int snd_samplerate = 0;
48
49 // Currently playing music track.
50 static Mix_Music *music = NULL;
51
52 //=============================================================================
53 //
54 // Private functions
55 //
56
57 //
58 // Write an unsigned integer into a simple CHAR buffer.
59 //
WriteInt16(CHAR * out,size_t osize,unsigned int in)60 static boolean WriteInt16(CHAR *out, size_t osize, unsigned int in)
61 {
62 if (osize < 2)
63 {
64 return false;
65 }
66
67 out[0] = (in >> 8) & 0xff;
68 out[1] = in & 0xff;
69
70 return true;
71 }
72
73 //
74 // Cleanly close our in-use pipes.
75 //
FreePipes(void)76 static void FreePipes(void)
77 {
78 if (midi_process_in != NULL)
79 {
80 CloseHandle(midi_process_in);
81 midi_process_in = NULL;
82 }
83 if (midi_process_out != NULL)
84 {
85 CloseHandle(midi_process_out);
86 midi_process_out = NULL;
87 }
88 }
89
90 //
91 // Unregisters the currently playing song. This is never called from the
92 // protocol, we simply do this before playing a new song.
93 //
UnregisterSong()94 static void UnregisterSong()
95 {
96 if (music == NULL)
97 {
98 return;
99 }
100
101 Mix_FreeMusic(music);
102 music = NULL;
103 }
104
105 //
106 // Cleanly shut down SDL.
107 //
ShutdownSDL(void)108 static void ShutdownSDL(void)
109 {
110 UnregisterSong();
111 Mix_CloseAudio();
112 SDL_Quit();
113 }
114
115 //=============================================================================
116 //
117 // SDL_mixer Interface
118 //
119
RegisterSong(const char * filename)120 static boolean RegisterSong(const char *filename)
121 {
122 UnregisterSong();
123 music = Mix_LoadMUS(filename);
124
125 // Remove the temporary MIDI file
126 remove(filename);
127
128 if (music == NULL)
129 {
130 return false;
131 }
132
133 return true;
134 }
135
SetVolume(int vol)136 static void SetVolume(int vol)
137 {
138 Mix_VolumeMusic(vol);
139 }
140
PlaySong(int loops)141 static void PlaySong(int loops)
142 {
143 Mix_PlayMusic(music, loops);
144
145 // [AM] BUG: In my testing, setting the volume of a MIDI track while there
146 // is no song playing appears to be a no-op. This can happen when
147 // you're mixing midiproc with vanilla SDL_Mixer, such as when you
148 // are alternating between a digital music pack (in the parent
149 // process) and MIDI (in this process).
150 //
151 // To work around this bug, we set the volume to itself after the MIDI
152 // has started playing.
153 Mix_VolumeMusic(Mix_VolumeMusic(-1));
154 }
155
StopSong()156 static void StopSong()
157 {
158 Mix_HaltMusic();
159 }
160
161 //=============================================================================
162 //
163 // Pipe Server Interface
164 //
165
MidiPipe_RegisterSong(buffer_reader_t * reader)166 static boolean MidiPipe_RegisterSong(buffer_reader_t *reader)
167 {
168 CHAR buffer[2];
169 DWORD bytes_written;
170
171 char *filename = Reader_ReadString(reader);
172 if (filename == NULL)
173 {
174 return false;
175 }
176
177 if (!RegisterSong(filename))
178 {
179 return false;
180 }
181
182 if (!WriteInt16(buffer, sizeof(buffer),
183 MIDIPIPE_PACKET_TYPE_REGISTER_SONG_ACK))
184 {
185 return false;
186 }
187
188 WriteFile(midi_process_out, buffer, sizeof(buffer),
189 &bytes_written, NULL);
190
191 return true;
192 }
193
MidiPipe_SetVolume(buffer_reader_t * reader)194 boolean MidiPipe_SetVolume(buffer_reader_t *reader)
195 {
196 int vol;
197 boolean ok = Reader_ReadInt32(reader, (uint32_t*)&vol);
198 if (!ok)
199 {
200 return false;
201 }
202
203 SetVolume(vol);
204
205 return true;
206 }
207
MidiPipe_PlaySong(buffer_reader_t * reader)208 boolean MidiPipe_PlaySong(buffer_reader_t *reader)
209 {
210 int loops;
211 boolean ok = Reader_ReadInt32(reader, (uint32_t*)&loops);
212 if (!ok)
213 {
214 return false;
215 }
216
217 PlaySong(loops);
218
219 return true;
220 }
221
MidiPipe_StopSong()222 boolean MidiPipe_StopSong()
223 {
224 StopSong();
225 UnregisterSong();
226
227 return true;
228 }
229
MidiPipe_Shutdown()230 boolean MidiPipe_Shutdown()
231 {
232 exit(EXIT_SUCCESS);
233 }
234
235 //=============================================================================
236 //
237 // Server Implementation
238 //
239
240 //
241 // Parses a command and directs to the proper read function.
242 //
ParseCommand(buffer_reader_t * reader,uint16_t command)243 boolean ParseCommand(buffer_reader_t *reader, uint16_t command)
244 {
245 switch (command)
246 {
247 case MIDIPIPE_PACKET_TYPE_REGISTER_SONG:
248 return MidiPipe_RegisterSong(reader);
249 case MIDIPIPE_PACKET_TYPE_SET_VOLUME:
250 return MidiPipe_SetVolume(reader);
251 case MIDIPIPE_PACKET_TYPE_PLAY_SONG:
252 return MidiPipe_PlaySong(reader);
253 case MIDIPIPE_PACKET_TYPE_STOP_SONG:
254 return MidiPipe_StopSong();
255 case MIDIPIPE_PACKET_TYPE_SHUTDOWN:
256 return MidiPipe_Shutdown();
257 default:
258 return false;
259 }
260 }
261
262 //
263 // Server packet parser
264 //
ParseMessage(buffer_t * buf)265 boolean ParseMessage(buffer_t *buf)
266 {
267 int bytes_read;
268 uint16_t command;
269 buffer_reader_t *reader = NewReader(buf);
270
271 // Attempt to read a command out of the buffer.
272 if (!Reader_ReadInt16(reader, &command))
273 {
274 goto fail;
275 }
276
277 // Attempt to parse a complete message.
278 if (!ParseCommand(reader, command))
279 {
280 goto fail;
281 }
282
283 // We parsed a complete message! We can now safely shift
284 // the prior message off the front of the buffer.
285 bytes_read = Reader_BytesRead(reader);
286 DeleteReader(reader);
287 Buffer_Shift(buf, bytes_read);
288
289 return true;
290
291 fail:
292 // We did not read a complete packet. Delete our reader and try again
293 // with more data.
294 DeleteReader(reader);
295 return false;
296 }
297
298 //
299 // The main pipe "listening" loop
300 //
ListenForever()301 boolean ListenForever()
302 {
303 BOOL wok = FALSE;
304 CHAR pipe_buffer[8192];
305 DWORD pipe_buffer_read = 0;
306
307 boolean ok = false;
308 buffer_t *buffer = NewBuffer();
309
310 for (;;)
311 {
312 // Wait until we see some data on the pipe.
313 wok = PeekNamedPipe(midi_process_in, NULL, 0, NULL,
314 &pipe_buffer_read, NULL);
315 if (!wok)
316 {
317 break;
318 }
319 else if (pipe_buffer_read == 0)
320 {
321 SDL_Delay(1);
322 continue;
323 }
324
325 // Read data off the pipe and add it to the buffer.
326 wok = ReadFile(midi_process_in, pipe_buffer, sizeof(pipe_buffer),
327 &pipe_buffer_read, NULL);
328 if (!wok)
329 {
330 break;
331 }
332
333 ok = Buffer_Push(buffer, pipe_buffer, pipe_buffer_read);
334 if (!ok)
335 {
336 break;
337 }
338
339 do
340 {
341 // Read messages off the buffer until we can't anymore.
342 ok = ParseMessage(buffer);
343 } while (ok);
344 }
345
346 return false;
347 }
348
349 //=============================================================================
350 //
351 // Main Program
352 //
353
354 //
355 // InitSDL
356 //
357 // Start up SDL and SDL_mixer.
358 //
InitSDL()359 boolean InitSDL()
360 {
361 if (SDL_Init(SDL_INIT_AUDIO) == -1)
362 {
363 return false;
364 }
365
366 if (Mix_OpenAudio(snd_samplerate, MIX_DEFAULT_FORMAT, 2, 2048) < 0)
367 {
368 return false;
369 }
370
371 atexit(ShutdownSDL);
372
373 return true;
374 }
375
376 //
377 // InitPipes
378 //
379 // Ensure that we can communicate.
380 //
InitPipes()381 boolean InitPipes()
382 {
383 midi_process_in = GetStdHandle(STD_INPUT_HANDLE);
384 if (midi_process_in == INVALID_HANDLE_VALUE)
385 {
386 goto fail;
387 }
388
389 midi_process_out = GetStdHandle(STD_OUTPUT_HANDLE);
390 if (midi_process_out == INVALID_HANDLE_VALUE)
391 {
392 goto fail;
393 }
394
395 atexit(FreePipes);
396
397 return true;
398
399 fail:
400 FreePipes();
401
402 return false;
403 }
404
405 //
406 // main
407 //
408 // Application entry point.
409 //
main(int argc,char * argv[])410 int main(int argc, char *argv[])
411 {
412 // Make sure we're not launching this process by itself.
413 if (argc < 3)
414 {
415 MessageBox(NULL, TEXT("This program is tasked with playing Native ")
416 TEXT("MIDI music, and is intended to be launched by ")
417 TEXT(PACKAGE_NAME) TEXT("."),
418 TEXT(PACKAGE_STRING), MB_OK | MB_ICONASTERISK);
419
420 return EXIT_FAILURE;
421 }
422
423 // Make sure our Choccolate Doom and midiproc version are lined up.
424 if (strcmp(PACKAGE_STRING, argv[1]) != 0)
425 {
426 char message[1024];
427 _snprintf(message, sizeof(message),
428 "It appears that the version of %s and %smidiproc are out "
429 "of sync. Please reinstall %s.\r\n\r\n"
430 "Server Version: %s\r\nClient Version: %s",
431 PACKAGE_NAME, PROGRAM_PREFIX, PACKAGE_NAME,
432 PACKAGE_STRING, argv[1]);
433 message[sizeof(message) - 1] = '\0';
434
435 MessageBox(NULL, TEXT(message),
436 TEXT(PACKAGE_STRING), MB_OK | MB_ICONASTERISK);
437
438 return EXIT_FAILURE;
439 }
440
441 // Parse out the sample rate - if we can't, default to 44100.
442 snd_samplerate = strtol(argv[2], NULL, 10);
443 if (snd_samplerate == LONG_MAX || snd_samplerate == LONG_MIN ||
444 snd_samplerate == 0)
445 {
446 snd_samplerate = 44100;
447 }
448
449 if (!InitPipes())
450 {
451 return EXIT_FAILURE;
452 }
453
454 if (!InitSDL())
455 {
456 return EXIT_FAILURE;
457 }
458
459 if (!ListenForever())
460 {
461 return EXIT_FAILURE;
462 }
463
464 return EXIT_SUCCESS;
465 }
466
467 #endif // #ifdef _WIN32
468