ref: 4abf58cf86c28893a3ba30fae38a7825267aa061
dir: /src/i_midipipe.c/
// // Copyright(C) 2013 James Haley et al. // Copyright(C) 2017 Alex Mayfield // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // DESCRIPTION: // Client Interface to RPC Midi Server // #if _WIN32 #include <stdlib.h> #include <sys/stat.h> #define WIN32_LEAN_AND_MEAN #include <windows.h> #include "i_midipipe.h" #include "config.h" #include "i_sound.h" #include "i_timer.h" #include "m_misc.h" #include "net_packet.h" #if defined(_DEBUG) #define DEBUGOUT(s) puts(s) #else #define DEBUGOUT(s) #endif //============================================================================= // // Public Data // // True if the midi proces was initialized at least once and has not been // explicitly shut down. This remains true if the server is momentarily // unreachable. boolean midi_server_initialized; // True if the current track is being handled via the MIDI server. boolean midi_server_registered; //============================================================================= // // Data // #define MIDIPIPE_MAX_WAIT 500 // Max amount of ms to wait for expected data. static HANDLE midi_process_in_reader; // Input stream for midi process. static HANDLE midi_process_in_writer; static HANDLE midi_process_out_reader; // Output stream for midi process. static HANDLE midi_process_out_writer; //============================================================================= // // Private functions // // // FreePipes // // Free all pipes in use by this module. // static void FreePipes() { if (midi_process_in_reader != NULL) { CloseHandle(midi_process_in_reader); midi_process_in_reader = NULL; } if (midi_process_in_writer != NULL) { CloseHandle(midi_process_in_writer); midi_process_in_writer = NULL; } if (midi_process_out_reader != NULL) { CloseHandle(midi_process_out_reader); midi_process_in_reader = NULL; } if (midi_process_out_writer != NULL) { CloseHandle(midi_process_out_writer); midi_process_out_writer = NULL; } } // // UsingNativeMidi // // Enumerate all music decoders and return true if NATIVEMIDI is one of them. // // If this is the case, using the MIDI server is probably necessary. If not, // we're likely using Timidity and thus don't need to start the server. // static boolean UsingNativeMidi() { int decoders = Mix_GetNumMusicDecoders(); for (int i = 0;i < decoders;i++) { if (strcmp(Mix_GetMusicDecoder(i), "NATIVEMIDI") == 0) { return true; } } return false; } // // WritePipe // // Writes packet data to the subprocess' standard in. // static boolean WritePipe(net_packet_t *packet) { BOOL ok = WriteFile(midi_process_in_writer, packet->data, packet->len, NULL, NULL); if (!ok) { return false; } return true; } // // ExpectPipe // // Expect the contents of a packet off of the subprocess' stdout. If the // response is unexpected, or doesn't arrive within a specific amuont of time, // assume the subprocess is in an unknown state. // static boolean ExpectPipe(net_packet_t *packet) { BOOL ok; CHAR pipe_buffer[8192]; DWORD pipe_buffer_read = 0; if (packet->len > sizeof(pipe_buffer)) { // The size of the packet we're expecting is larger than our buffer // size, so bail out now. return false; } int start = I_GetTimeMS(); do { // Wait until we see exactly the amount of data we expect on the pipe. ok = PeekNamedPipe(midi_process_out_reader, NULL, 0, NULL, &pipe_buffer_read, NULL); if (!ok) { goto fail; } else if (pipe_buffer_read < packet->len) { I_Sleep(1); continue; } // Read precisely the number of bytes we're expecting, and no more. ok = ReadFile(midi_process_out_reader, pipe_buffer, packet->len, &pipe_buffer_read, NULL); if (!ok || pipe_buffer_read != packet->len) { goto fail; } // Compare our data buffer to the packet. if (memcmp(packet->data, pipe_buffer, packet->len) != 0) { goto fail; } return true; // Continue looping as long as we don't exceed our maximum wait time. } while (I_GetTimeMS() - start <= MIDIPIPE_MAX_WAIT); fail: // TODO: Deal with the wedged process? return false; } //============================================================================= // // Protocol Commands // // // I_MidiPipe_RegisterSong // // Tells the MIDI subprocess to load a specific filename for playing. This // function blocks until there is an acknowledgement from the server. // boolean I_MidiPipe_RegisterSong(const char *filename) { boolean ok; net_packet_t *packet; packet = NET_NewPacket(64); NET_WriteInt16(packet, NET_MIDIPIPE_PACKET_TYPE_REGISTER_SONG); NET_WriteString(packet, filename); ok = WritePipe(packet); NET_FreePacket(packet); if (!ok) { DEBUGOUT("I_MidiPipe_RegisterSong failed"); return false; } packet = NET_NewPacket(2); NET_WriteInt16(packet, NET_MIDIPIPE_PACKET_TYPE_REGISTER_SONG_ACK); ok = ExpectPipe(packet); NET_FreePacket(packet); if (!ok) { DEBUGOUT("I_MidiPipe_RegisterSong ack failed"); return false; } midi_server_registered = true; DEBUGOUT("I_MidiPipe_RegisterSong succeeded"); return true; } // // I_MidiPipe_SetVolume // // Tells the MIDI subprocess to set a specific volume for the song. // void I_MidiPipe_SetVolume(int vol) { boolean ok; net_packet_t *packet; packet = NET_NewPacket(6); NET_WriteInt16(packet, NET_MIDIPIPE_PACKET_TYPE_SET_VOLUME); NET_WriteInt32(packet, vol); ok = WritePipe(packet); NET_FreePacket(packet); if (!ok) { DEBUGOUT("I_MidiPipe_SetVolume failed"); return; } DEBUGOUT("I_MidiPipe_SetVolume succeeded"); } // // I_MidiPipe_PlaySong // // Tells the MIDI subprocess to play the currently loaded song. // void I_MidiPipe_PlaySong(int loops) { boolean ok; net_packet_t *packet; packet = NET_NewPacket(6); NET_WriteInt16(packet, NET_MIDIPIPE_PACKET_TYPE_PLAY_SONG); NET_WriteInt32(packet, loops); ok = WritePipe(packet); NET_FreePacket(packet); if (!ok) { DEBUGOUT("I_MidiPipe_PlaySong failed"); return; } DEBUGOUT("I_MidiPipe_PlaySong succeeded"); } // // I_MidiPipe_StopSong // // Tells the MIDI subprocess to stop playing the currently loaded song. // void I_MidiPipe_StopSong() { boolean ok; net_packet_t *packet; packet = NET_NewPacket(2); NET_WriteInt16(packet, NET_MIDIPIPE_PACKET_TYPE_STOP_SONG); ok = WritePipe(packet); NET_FreePacket(packet); midi_server_registered = false; if (!ok) { DEBUGOUT("I_MidiPipe_StopSong failed"); return; } DEBUGOUT("I_MidiPipe_StopSong succeeded"); } // // I_MidiPipe_ShutdownServer // // Tells the MIDI subprocess to shutdown. // void I_MidiPipe_ShutdownServer() { boolean ok; net_packet_t *packet; packet = NET_NewPacket(2); NET_WriteInt16(packet, NET_MIDIPIPE_PACKET_TYPE_SHUTDOWN); ok = WritePipe(packet); NET_FreePacket(packet); FreePipes(); midi_server_initialized = false; if (!ok) { DEBUGOUT("I_MidiPipe_ShutdownServer failed"); return; } DEBUGOUT("I_MidiPipe_ShutdownServer succeeded"); } //============================================================================= // // Public Interface // // // I_MidiPipeInitServer // // Start up the MIDI server. // boolean I_MidiPipe_InitServer() { struct stat sbuf; char filename[MAX_PATH + 1]; char *module = NULL; char *cmdline = NULL; if (!UsingNativeMidi() || strlen(snd_musiccmd) > 0) { // If we're not using native MIDI, or if we're playing music through // an exteranl program, we don't need to start the server. return false; } memset(filename, 0, sizeof(filename)); size_t filename_len = GetModuleFileName(NULL, filename, MAX_PATH); // Remove filespec // TODO: Move this to m_misc? char *fp = &filename[filename_len]; while (filename <= fp && *fp != DIR_SEPARATOR) { fp--; } *(fp + 1) = '\0'; module = M_StringJoin(filename, PROGRAM_PREFIX "midiproc.exe", NULL); cmdline = M_StringJoin(module, " \"" PACKAGE_STRING "\"", NULL); // Look for executable file if(stat(module, &sbuf)) { DEBUGOUT("Could not find midiproc"); return false; } // Set up pipes SECURITY_ATTRIBUTES sec_attrs; memset(&sec_attrs, 0, sizeof(SECURITY_ATTRIBUTES)); sec_attrs.nLength = sizeof(SECURITY_ATTRIBUTES); sec_attrs.bInheritHandle = TRUE; sec_attrs.lpSecurityDescriptor = NULL; if (!CreatePipe(&midi_process_in_reader, &midi_process_in_writer, &sec_attrs, 0)) { DEBUGOUT("Could not initialize midiproc stdin"); return false; } if (!SetHandleInformation(midi_process_in_writer, HANDLE_FLAG_INHERIT, 0)) { DEBUGOUT("Could not disinherit midiproc stdin"); return false; } if (!CreatePipe(&midi_process_out_reader, &midi_process_out_writer, &sec_attrs, 0)) { DEBUGOUT("Could not initialize midiproc stdout/stderr"); return false; } if (!SetHandleInformation(midi_process_out_reader, HANDLE_FLAG_INHERIT, 0)) { DEBUGOUT("Could not disinherit midiproc stdin"); return false; } // Launch the subprocess PROCESS_INFORMATION proc_info; memset(&proc_info, 0, sizeof(proc_info)); STARTUPINFO startup_info; memset(&startup_info, 0, sizeof(startup_info)); startup_info.cb = sizeof(startup_info); startup_info.hStdInput = midi_process_in_reader; startup_info.hStdOutput = midi_process_out_writer; startup_info.dwFlags = STARTF_USESTDHANDLES; BOOL ok = CreateProcess(TEXT(module), TEXT(cmdline), NULL, NULL, TRUE, 0, NULL, NULL, &startup_info, &proc_info); if (!ok) { goto fail; } // Since the server has these handles, we don't need them anymore. CloseHandle(midi_process_in_reader); midi_process_in_reader = NULL; CloseHandle(midi_process_out_writer); midi_process_out_writer = NULL; midi_server_initialized = true; return true; fail: FreePipes(); free(cmdline); free(module); return false; } #endif