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