ref: 1a581dbc358c2f2c97efa2615a6b29c54bad007b
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 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" #include "../midiproc/proto.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 = false; // True if the current track is being handled via the MIDI server. boolean midi_server_registered = false; //============================================================================= // // Data // #define MIDIPIPE_MAX_WAIT 1000 // 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 i; int decoders = Mix_GetNumMusicDecoders(); for (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) { DWORD bytes_written; BOOL ok = WriteFile(midi_process_in_writer, packet->data, packet->len, &bytes_written, NULL); return ok; } // // 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) { int start; 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; } 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) { break; } 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) { break; } // Compare our data buffer to the packet. if (memcmp(packet->data, pipe_buffer, packet->len) != 0) { break; } return true; // Continue looping as long as we don't exceed our maximum wait time. } while (I_GetTimeMS() - start <= MIDIPIPE_MAX_WAIT); // TODO: Deal with the wedged process? return false; } // // RemoveFileSpec // // A reimplementation of PathRemoveFileSpec that doesn't bring in Shlwapi // void RemoveFileSpec(TCHAR *path, size_t size) { TCHAR *fp = NULL; fp = &path[size]; while (path <= fp && *fp != DIR_SEPARATOR) { fp--; } *(fp + 1) = '\0'; } static boolean BlockForAck(void) { boolean ok; net_packet_t *packet; packet = NET_NewPacket(2); NET_WriteInt16(packet, MIDIPIPE_PACKET_TYPE_ACK); ok = ExpectPipe(packet); NET_FreePacket(packet); return ok; } //============================================================================= // // 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(char *filename) { boolean ok; net_packet_t *packet; packet = NET_NewPacket(64); NET_WriteInt16(packet, MIDIPIPE_PACKET_TYPE_REGISTER_SONG); NET_WriteString(packet, filename); ok = WritePipe(packet); NET_FreePacket(packet); midi_server_registered = false; ok = ok && BlockForAck(); if (!ok) { DEBUGOUT("I_MidiPipe_RegisterSong failed"); return false; } midi_server_registered = true; DEBUGOUT("I_MidiPipe_RegisterSong succeeded"); return true; } // // I_MidiPipe_UnregisterSong // // Tells the MIDI subprocess to unload the current song. // void I_MidiPipe_UnregisterSong(void) { boolean ok; net_packet_t *packet; packet = NET_NewPacket(64); NET_WriteInt16(packet, MIDIPIPE_PACKET_TYPE_UNREGISTER_SONG); ok = WritePipe(packet); NET_FreePacket(packet); ok = ok && BlockForAck(); if (!ok) { DEBUGOUT("I_MidiPipe_UnregisterSong failed"); return; } midi_server_registered = false; DEBUGOUT("I_MidiPipe_UnregisterSong succeeded"); } // // 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, MIDIPIPE_PACKET_TYPE_SET_VOLUME); NET_WriteInt32(packet, vol); ok = WritePipe(packet); NET_FreePacket(packet); ok = ok && BlockForAck(); 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, MIDIPIPE_PACKET_TYPE_PLAY_SONG); NET_WriteInt32(packet, loops); ok = WritePipe(packet); NET_FreePacket(packet); ok = ok && BlockForAck(); 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, MIDIPIPE_PACKET_TYPE_STOP_SONG); ok = WritePipe(packet); NET_FreePacket(packet); ok = ok && BlockForAck(); 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, MIDIPIPE_PACKET_TYPE_SHUTDOWN); ok = WritePipe(packet); NET_FreePacket(packet); ok = ok && BlockForAck(); 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() { TCHAR dirname[MAX_PATH + 1]; DWORD dirname_len; char *module = NULL; char *cmdline = NULL; char params_buf[128]; SECURITY_ATTRIBUTES sec_attrs; PROCESS_INFORMATION proc_info; STARTUPINFO startup_info; BOOL ok; 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; } // Get directory name memset(dirname, 0, sizeof(dirname)); dirname_len = GetModuleFileName(NULL, dirname, MAX_PATH); if (dirname_len == 0) { return false; } RemoveFileSpec(dirname, dirname_len); // Define the module. module = PROGRAM_PREFIX "midiproc.exe"; // Set up pipes 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; } // Define the command line. Version, Sample Rate, and handles follow // the executable name. M_snprintf(params_buf, sizeof(params_buf), "%d %Iu %Iu", snd_samplerate, (size_t) midi_process_in_reader, (size_t) midi_process_out_writer); cmdline = M_StringJoin(module, " \"" PACKAGE_STRING "\"", " ", params_buf, NULL); // Launch the subprocess memset(&proc_info, 0, sizeof(proc_info)); memset(&startup_info, 0, sizeof(startup_info)); startup_info.cb = sizeof(startup_info); ok = CreateProcess(TEXT(module), TEXT(cmdline), NULL, NULL, TRUE, 0, NULL, dirname, &startup_info, &proc_info); if (!ok) { FreePipes(); free(cmdline); return false; } // 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; } #endif