diff --git a/CMakeLists.txt b/CMakeLists.txt index 9f5c6b68..042a2e7c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -267,3 +267,4 @@ add_subdirectory(docs) add_subdirectory(examples) add_subdirectory(Source) add_subdirectory(toolsrc) +add_subdirectory(midiproc) diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index a8d56ed2..f812617f 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -28,6 +28,7 @@ set(WOOF_SOURCES hu_lib.c hu_lib.h hu_stuff.c hu_stuff.h i_main.c + i_midipipe.c i_midipipe.h i_net.c i_net.h i_sound.c i_sound.h i_system.c i_system.h @@ -43,6 +44,8 @@ set(WOOF_SOURCES m_random.c m_random.h m_swap.h mmus2mid.c mmus2mid.h + net_defs.h + net_packet.c net_packet.h p_ceilng.c p_doors.c p_enemy.c p_enemy.h diff --git a/Source/i_midipipe.c b/Source/i_midipipe.c new file mode 100644 index 00000000..a97bf09f --- /dev/null +++ b/Source/i_midipipe.c @@ -0,0 +1,513 @@ +// +// 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 +#include + +#define WIN32_LEAN_AND_MEAN +#include + +#include "i_midipipe.h" + +#include "config.h" +#include "i_sound.h" +#include "i_system.h" +#include "i_video.h" // for I_Sleep +#include "m_misc2.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 *fullpath = NULL; + char params_buf[128]; + SECURITY_ATTRIBUTES sec_attrs; + PROCESS_INFORMATION proc_info; + STARTUPINFO startup_info; + BOOL ok; + int snd_samplerate = 44100; + + 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 = "woof-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, " \"" PROJECT_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); + + fullpath = M_StringJoin(dirname, module, NULL); + + ok = CreateProcess(TEXT(fullpath), TEXT(cmdline), NULL, NULL, TRUE, + 0, NULL, dirname, &startup_info, &proc_info); + + if (!ok) + { + FreePipes(); + free(cmdline); + free(fullpath); + + 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 + diff --git a/Source/i_midipipe.h b/Source/i_midipipe.h new file mode 100644 index 00000000..c71b9d4d --- /dev/null +++ b/Source/i_midipipe.h @@ -0,0 +1,49 @@ +// +// 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 +// + +#ifndef __I_MIDIPIPE__ +#define __I_MIDIPIPE__ + +#if _WIN32 + +#include "SDL_mixer.h" + +#include "doomtype.h" + +extern boolean midi_server_initialized; +extern boolean midi_server_registered; + +boolean I_MidiPipe_RegisterSong(char *filename); +void I_MidiPipe_UnregisterSong(void); +void I_MidiPipe_SetVolume(int vol); +void I_MidiPipe_PlaySong(int loops); +void I_MidiPipe_StopSong(); +void I_MidiPipe_ShutdownServer(); + +boolean I_MidiPipe_InitServer(); + +#else + +#include "doomtype.h" + +static const boolean midi_server_registered = false; + +#endif + +#endif + diff --git a/Source/i_sound.c b/Source/i_sound.c index e3cd9e44..ce2acc19 100644 --- a/Source/i_sound.c +++ b/Source/i_sound.c @@ -648,6 +648,8 @@ void I_InitSound(void) #include "mmus2mid.h" #include "m_misc.h" +#include "m_misc2.h" +#include "i_midipipe.h" // Only one track at a time static Mix_Music *music = NULL; @@ -671,7 +673,16 @@ static void *music_block = NULL; // void I_ShutdownMusic(void) { - I_UnRegisterSong(1); +#if defined(_WIN32) + if (midi_server_initialized) + { + I_MidiPipe_ShutdownServer(); + } + else +#endif + { + I_UnRegisterSong(1); + } } // @@ -684,6 +695,13 @@ void I_InitMusic(void) case -1: printf("I_InitMusic: Using SDL_mixer.\n"); mus_init = true; + + // Initialize SDL_Mixer for MIDI music playback + Mix_Init(MIX_INIT_MID | MIX_INIT_FLAC | MIX_INIT_OGG | MIX_INIT_MP3); // [crispy] initialize some more audio formats + #if defined(_WIN32) + // [AM] Start up midiproc to handle playing MIDI music. + I_MidiPipe_InitServer(); + #endif break; default: printf("I_InitMusic: Music is disabled.\n"); @@ -700,6 +718,13 @@ void I_PlaySong(int handle, int looping) if(!mus_init) return; +#if defined(_WIN32) + if (midi_server_registered) + { + I_MidiPipe_PlaySong(looping ? -1 : 1); + } + else +#endif if(CHECK_MUSIC(handle) && Mix_PlayMusic(music, looping ? -1 : 0) == -1) { dprintf("I_PlaySong: Mix_PlayMusic failed\n"); @@ -713,19 +738,38 @@ void I_PlaySong(int handle, int looping) // // I_SetMusicVolume // + +static int current_midi_volume; + void I_SetMusicVolume(int volume) { // haleyjd 09/04/06: adjust to use scale from 0 to 15 - Mix_VolumeMusic((volume * 128) / 15); -} + current_midi_volume = (volume * 128) / 15; -static int paused_midi_volume; +#if defined(_WIN32) + if (midi_server_registered) + { + I_MidiPipe_SetVolume(current_midi_volume); + } + else +#endif + { + Mix_VolumeMusic(current_midi_volume); + } +} // // I_PauseSong // void I_PauseSong(int handle) { +#if defined(_WIN32) + if (midi_server_registered) + { + I_MidiPipe_SetVolume(0); + } + else +#endif if(CHECK_MUSIC(handle)) { // Not for mids @@ -734,7 +778,6 @@ void I_PauseSong(int handle) else { // haleyjd 03/21/06: set MIDI volume to zero on pause - paused_midi_volume = Mix_VolumeMusic(-1); Mix_VolumeMusic(0); } } @@ -745,13 +788,20 @@ void I_PauseSong(int handle) // void I_ResumeSong(int handle) { +#if defined(_WIN32) + if (midi_server_registered) + { + I_MidiPipe_SetVolume(current_midi_volume); + } + else +#endif if(CHECK_MUSIC(handle)) { // Not for mids if(Mix_GetMusicType(music) != MUS_MID) Mix_ResumeMusic(); else - Mix_VolumeMusic(paused_midi_volume); + Mix_VolumeMusic(current_midi_volume); } } @@ -760,6 +810,13 @@ void I_ResumeSong(int handle) // void I_StopSong(int handle) { +#if defined(_WIN32) + if (midi_server_registered) + { + I_MidiPipe_StopSong(); + } + else +#endif if(CHECK_MUSIC(handle)) Mix_HaltMusic(); } @@ -769,6 +826,13 @@ void I_StopSong(int handle) // void I_UnRegisterSong(int handle) { +#if defined(_WIN32) + if (midi_server_registered) + { + I_MidiPipe_UnregisterSong(); + } + else +#endif if(CHECK_MUSIC(handle)) { // Stop and free song @@ -790,6 +854,22 @@ void I_UnRegisterSong(int handle) } } +#if defined(_WIN32) +static void MidiProc_RegisterSong(void *data, int size) +{ + char* filename; + filename = M_TempFile("doom"); // [crispy] generic filename + + M_WriteFile(filename, data, size); + + if (!I_MidiPipe_RegisterSong(filename)) + { + fprintf(stderr, "Error loading midi: %s\n", + "Could not communicate with midiproc."); + } +} +#endif + // // I_RegisterSong // @@ -797,20 +877,29 @@ int I_RegisterSong(void *data, int size) { if(music != NULL) I_UnRegisterSong(1); - - rw = SDL_RWFromMem(data, size); - music = Mix_LoadMUS_RW(rw, false); - - // It's not recognized by SDL_mixer, is it a mus? - if(music == NULL) - { + + if (size < 4 || memcmp(data, "MUS\x1a", 4)) // [crispy] MUS_HEADER_MAGIC + { + #if defined(_WIN32) + if (size >= 4 && memcmp(data, "MThd", 4) == 0 && midi_server_initialized) // MIDI header magic + { + music = NULL; + MidiProc_RegisterSong(data, size); + return 1; + } + else + #endif + { + rw = SDL_RWFromMem(data, size); + music = Mix_LoadMUS_RW(rw, false); + } + } + else // Assume a MUS file and try to convert + { int err; MIDI mididata; UBYTE *mid; int midlen; - - SDL_FreeRW(rw); - rw = NULL; memset(&mididata, 0, sizeof(MIDI)); @@ -823,21 +912,34 @@ int I_RegisterSong(void *data, int size) // Hurrah! Let's make it a mid and give it to SDL_mixer MIDIToMidi(&mididata, &mid, &midlen); - rw = SDL_RWFromMem(mid, midlen); - music = Mix_LoadMUS_RW(rw, false); + + #if defined(_WIN32) + if (midi_server_initialized) + { + music = NULL; + MidiProc_RegisterSong(mid, midlen); + free(mid); + return 1; + } + else + #endif + { + rw = SDL_RWFromMem(mid, midlen); + music = Mix_LoadMUS_RW(rw, false); - if(music == NULL) - { - // Conversion failed, free everything - SDL_FreeRW(rw); - rw = NULL; - free(mid); - } - else - { - // Conversion succeeded - // -> save memory block to free when unregistering - music_block = mid; + if(music == NULL) + { + // Conversion failed, free everything + SDL_FreeRW(rw); + rw = NULL; + free(mid); + } + else + { + // Conversion succeeded + // -> save memory block to free when unregistering + music_block = mid; + } } } diff --git a/Source/m_misc2.c b/Source/m_misc2.c index ac102902..1e2e22d0 100644 --- a/Source/m_misc2.c +++ b/Source/m_misc2.c @@ -74,6 +74,34 @@ boolean M_FileExists(const char *filename) } } +// Returns the path to a temporary file of the given name, stored +// inside the system temporary directory. +// +// The returned value must be freed with Z_Free after use. + +char *M_TempFile(const char *s) +{ + const char *tempdir; + +#ifdef _WIN32 + + // Check the TEMP environment variable to find the location. + + tempdir = getenv("TEMP"); + + if (tempdir == NULL) + { + tempdir = "."; + } +#else + // In Unix, just use /tmp. + + tempdir = "/tmp"; +#endif + + return M_StringJoin(tempdir, DIR_SEPARATOR_S, s, NULL); +} + // Check if a file exists by probing for common case variation of its filename. // Returns a newly allocated string that the caller is responsible for freeing. @@ -382,3 +410,40 @@ char *M_StringJoin(const char *s, ...) return result; } + +// Safe, portable vsnprintf(). +int M_vsnprintf(char *buf, size_t buf_len, const char *s, va_list args) +{ + int result; + + if (buf_len < 1) + { + return 0; + } + + // Windows (and other OSes?) has a vsnprintf() that doesn't always + // append a trailing \0. So we must do it, and write into a buffer + // that is one byte shorter; otherwise this function is unsafe. + result = vsnprintf(buf, buf_len, s, args); + + // If truncated, change the final char in the buffer to a \0. + // A negative result indicates a truncated buffer on Windows. + if (result < 0 || result >= buf_len) + { + buf[buf_len - 1] = '\0'; + result = buf_len - 1; + } + + return result; +} + +// Safe, portable snprintf(). +int M_snprintf(char *buf, size_t buf_len, const char *s, ...) +{ + va_list args; + int result; + va_start(args, s); + result = M_vsnprintf(buf, buf_len, s, args); + va_end(args); + return result; +} diff --git a/Source/m_misc2.h b/Source/m_misc2.h index 538834e9..0f544131 100644 --- a/Source/m_misc2.h +++ b/Source/m_misc2.h @@ -23,6 +23,7 @@ void M_MakeDirectory(const char *dir); boolean M_FileExists(const char *file); +char *M_TempFile(const char *s); char *M_FileCaseExists(const char *file); char *M_DirName(const char *path); const char *M_BaseName(const char *path); @@ -35,5 +36,6 @@ char *M_StringReplace(const char *haystack, const char *needle, const char *replacement); char *M_StringJoin(const char *s, ...); boolean M_StringEndsWith(const char *s, const char *suffix); +int M_snprintf(char *buf, size_t buf_len, const char *s, ...) PRINTF_ATTR(3, 4); #endif diff --git a/Source/net_defs.h b/Source/net_defs.h new file mode 100644 index 00000000..88ea6323 --- /dev/null +++ b/Source/net_defs.h @@ -0,0 +1,31 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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: +// Definitions for use in networking code. +// + +#ifndef NET_DEFS_H +#define NET_DEFS_H + +typedef struct _net_packet_s net_packet_t; + +struct _net_packet_s +{ + byte *data; + size_t len; + size_t alloced; + unsigned int pos; +}; + +#endif /* #ifndef NET_DEFS_H */ diff --git a/Source/net_packet.c b/Source/net_packet.c new file mode 100644 index 00000000..a1651892 --- /dev/null +++ b/Source/net_packet.c @@ -0,0 +1,329 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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: +// Network packet manipulation (net_packet_t) +// + +#include +#include +#include "m_misc.h" +#include "m_misc2.h" +#include "net_packet.h" +#include "z_zone.h" + +static int total_packet_memory = 0; + +net_packet_t *NET_NewPacket(int initial_size) +{ + net_packet_t *packet; + + packet = (net_packet_t *) Z_Malloc(sizeof(net_packet_t), PU_STATIC, 0); + + if (initial_size == 0) + initial_size = 256; + + packet->alloced = initial_size; + packet->data = Z_Malloc(initial_size, PU_STATIC, 0); + packet->len = 0; + packet->pos = 0; + + total_packet_memory += sizeof(net_packet_t) + initial_size; + + //printf("total packet memory: %i bytes\n", total_packet_memory); + //printf("%p: allocated\n", packet); + + return packet; +} + +// duplicates an existing packet + +net_packet_t *NET_PacketDup(net_packet_t *packet) +{ + net_packet_t *newpacket; + + newpacket = NET_NewPacket(packet->len); + memcpy(newpacket->data, packet->data, packet->len); + newpacket->len = packet->len; + + return newpacket; +} + +void NET_FreePacket(net_packet_t *packet) +{ + //printf("%p: destroyed\n", packet); + + total_packet_memory -= sizeof(net_packet_t) + packet->alloced; + Z_Free(packet->data); + Z_Free(packet); +} + +// Read a byte from the packet, returning true if read +// successfully + +boolean NET_ReadInt8(net_packet_t *packet, unsigned int *data) +{ + if (packet->pos + 1 > packet->len) + return false; + + *data = packet->data[packet->pos]; + + packet->pos += 1; + + return true; +} + +// Read a 16-bit integer from the packet, returning true if read +// successfully + +boolean NET_ReadInt16(net_packet_t *packet, unsigned int *data) +{ + byte *p; + + if (packet->pos + 2 > packet->len) + return false; + + p = packet->data + packet->pos; + + *data = (p[0] << 8) | p[1]; + packet->pos += 2; + + return true; +} + +// Read a 32-bit integer from the packet, returning true if read +// successfully + +boolean NET_ReadInt32(net_packet_t *packet, unsigned int *data) +{ + byte *p; + + if (packet->pos + 4 > packet->len) + return false; + + p = packet->data + packet->pos; + + *data = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; + packet->pos += 4; + + return true; +} + +// Signed read functions + +boolean NET_ReadSInt8(net_packet_t *packet, signed int *data) +{ + if (NET_ReadInt8(packet,(unsigned int *) data)) + { + if (*data & (1 << 7)) + { + *data &= ~(1 << 7); + *data -= (1 << 7); + } + return true; + } + else + { + return false; + } +} + +boolean NET_ReadSInt16(net_packet_t *packet, signed int *data) +{ + if (NET_ReadInt16(packet, (unsigned int *) data)) + { + if (*data & (1 << 15)) + { + *data &= ~(1 << 15); + *data -= (1 << 15); + } + return true; + } + else + { + return false; + } +} + +boolean NET_ReadSInt32(net_packet_t *packet, signed int *data) +{ + if (NET_ReadInt32(packet, (unsigned int *) data)) + { + if (*data & (1U << 31)) + { + *data &= ~(1U << 31); + *data -= (1U << 31); + } + return true; + } + else + { + return false; + } +} + +// Read a string from the packet. Returns NULL if a terminating +// NUL character was not found before the end of the packet. + +char *NET_ReadString(net_packet_t *packet) +{ + char *start; + + start = (char *) packet->data + packet->pos; + + // Search forward for a NUL character + + while (packet->pos < packet->len && packet->data[packet->pos] != '\0') + { + ++packet->pos; + } + + if (packet->pos >= packet->len) + { + // Reached the end of the packet + + return NULL; + } + + // packet->data[packet->pos] == '\0': We have reached a terminating + // NULL. Skip past this NULL and continue reading immediately + // after it. + + ++packet->pos; + + return start; +} + +// Read a string from the packet, but (potentially) modify it to strip +// out any unprintable characters which could be malicious control codes. +// Note that this may modify the original packet contents. +char *NET_ReadSafeString(net_packet_t *packet) +{ + char *r, *w, *result; + + result = NET_ReadString(packet); + if (result == NULL) + { + return NULL; + } + + // w is always <= r, so we never produce a longer string than the original. + w = result; + for (r = result; *r != '\0'; ++r) + { + // TODO: This is a very naive way of producing a safe string; only + // ASCII characters are allowed. Probably this should really support + // UTF-8 characters as well. + if (isprint(*r) || *r == '\n') + { + *w = *r; + ++w; + } + } + *w = '\0'; + + return result; +} + +// Dynamically increases the size of a packet + +static void NET_IncreasePacket(net_packet_t *packet) +{ + byte *newdata; + + total_packet_memory -= packet->alloced; + + packet->alloced *= 2; + + newdata = Z_Malloc(packet->alloced, PU_STATIC, 0); + + memcpy(newdata, packet->data, packet->len); + + Z_Free(packet->data); + packet->data = newdata; + + total_packet_memory += packet->alloced; +} + +// Write a single byte to the packet + +void NET_WriteInt8(net_packet_t *packet, unsigned int i) +{ + if (packet->len + 1 > packet->alloced) + NET_IncreasePacket(packet); + + packet->data[packet->len] = i; + packet->len += 1; +} + +// Write a 16-bit integer to the packet + +void NET_WriteInt16(net_packet_t *packet, unsigned int i) +{ + byte *p; + + if (packet->len + 2 > packet->alloced) + NET_IncreasePacket(packet); + + p = packet->data + packet->len; + + p[0] = (i >> 8) & 0xff; + p[1] = i & 0xff; + + packet->len += 2; +} + + +// Write a single byte to the packet + +void NET_WriteInt32(net_packet_t *packet, unsigned int i) +{ + byte *p; + + if (packet->len + 4 > packet->alloced) + NET_IncreasePacket(packet); + + p = packet->data + packet->len; + + p[0] = (i >> 24) & 0xff; + p[1] = (i >> 16) & 0xff; + p[2] = (i >> 8) & 0xff; + p[3] = i & 0xff; + + packet->len += 4; +} + +void NET_WriteString(net_packet_t *packet, const char *string) +{ + byte *p; + size_t string_size; + + string_size = strlen(string) + 1; + + // Increase the packet size until large enough to hold the string + + while (packet->len + string_size > packet->alloced) + { + NET_IncreasePacket(packet); + } + + p = packet->data + packet->len; + + M_StringCopy((char *) p, string, string_size); + + packet->len += string_size; +} + + + + diff --git a/Source/net_packet.h b/Source/net_packet.h new file mode 100644 index 00000000..6beb44b2 --- /dev/null +++ b/Source/net_packet.h @@ -0,0 +1,45 @@ +// +// Copyright(C) 2005-2014 Simon Howard +// +// 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: +// Definitions for use in networking code. +// + +#ifndef NET_PACKET_H +#define NET_PACKET_H + +#include "net_defs.h" + +net_packet_t *NET_NewPacket(int initial_size); +net_packet_t *NET_PacketDup(net_packet_t *packet); +void NET_FreePacket(net_packet_t *packet); + +boolean NET_ReadInt8(net_packet_t *packet, unsigned int *data); +boolean NET_ReadInt16(net_packet_t *packet, unsigned int *data); +boolean NET_ReadInt32(net_packet_t *packet, unsigned int *data); + +boolean NET_ReadSInt8(net_packet_t *packet, signed int *data); +boolean NET_ReadSInt16(net_packet_t *packet, signed int *data); +boolean NET_ReadSInt32(net_packet_t *packet, signed int *data); + +char *NET_ReadString(net_packet_t *packet); +char *NET_ReadSafeString(net_packet_t *packet); + +void NET_WriteInt8(net_packet_t *packet, unsigned int i); +void NET_WriteInt16(net_packet_t *packet, unsigned int i); +void NET_WriteInt32(net_packet_t *packet, unsigned int i); + +void NET_WriteString(net_packet_t *packet, const char *string); + +#endif /* #ifndef NET_PACKET_H */ + diff --git a/midiproc/CMakeLists.txt b/midiproc/CMakeLists.txt new file mode 100644 index 00000000..a1ee9608 --- /dev/null +++ b/midiproc/CMakeLists.txt @@ -0,0 +1,12 @@ +if(WIN32) + include(WoofSettings) + + add_executable(woof-midiproc WIN32 buffer.c buffer.h main.c proto.h) + + target_woof_settings(woof-midiproc) + target_include_directories(woof-midiproc + PRIVATE "../Source/" "${CMAKE_CURRENT_BINARY_DIR}/../") + target_link_libraries(woof-midiproc SDL2::SDL2main SDL2::mixer) + + install(TARGETS woof-midiproc RUNTIME DESTINATION .) +endif() diff --git a/midiproc/buffer.c b/midiproc/buffer.c new file mode 100644 index 00000000..cbeab493 --- /dev/null +++ b/midiproc/buffer.c @@ -0,0 +1,255 @@ +// +// 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: +// A simple buffer and reader implementation. +// + +#ifdef _WIN32 + +#include "buffer.h" + +#include +#include +#include + +// +// Create a new buffer. +// +buffer_t *NewBuffer() +{ + buffer_t *buf = malloc(sizeof(buffer_t)); + + buf->buffer_end = buf->buffer + BUFFER_SIZE; + Buffer_Clear(buf); + + return buf; +} + +// +// Free a buffer. +// +void DeleteBuffer(buffer_t *buf) +{ + free(buf); +} + +// +// Return the data in the buffer. +// +int Buffer_Data(buffer_t *buf, byte **data) +{ + *data = buf->data; + return buf->data_len; +} + +// +// Push data onto the end of the buffer. +// +boolean Buffer_Push(buffer_t *buf, const void *data, int len) +{ + ptrdiff_t space_begin, space_end; + + if (len <= 0) + { + // Do nothing, successfully. + return true; + } + + space_begin = buf->data - buf->buffer; + space_end = buf->buffer_end - buf->data_end; + + if (len > space_end) + { + if (len > space_begin + space_end) + { + // Don't overflow the buffer. + return false; + } + + // Move our data to the front of the buffer. + memmove(buf->buffer, buf->data, buf->data_len); + buf->data = buf->buffer; + buf->data_end = buf->buffer + buf->data_len; + } + + // Append to the buffer. + memcpy(buf->data_end, data, len); + buf->data_len += len; + buf->data_end = buf->data + buf->data_len; + + return true; +} + + +// +// Shift len bytes off of the front of the buffer. +// +void Buffer_Shift(buffer_t *buf, int len) +{ + ptrdiff_t max_shift; + + if (len <= 0) + { + // Do nothing. + return; + } + + max_shift = buf->data_end - buf->data; + if (len >= max_shift) + { + // If the operation would clear the buffer, just zero everything. + Buffer_Clear(buf); + } + else + { + buf->data += len; + buf->data_len -= len; + } +} + +// +// Clear the buffer. +// +void Buffer_Clear(buffer_t *buf) +{ + buf->data = buf->buffer; + buf->data_end = buf->buffer; + buf->data_len = 0; +} + +// +// Create a new buffer reader. +// +// WARNING: This reader will invalidate if the underlying buffer changes. +// Use it, then delete it before you touch the underlying buffer again. +// +buffer_reader_t *NewReader(buffer_t* buffer) +{ + buffer_reader_t *reader = malloc(sizeof(buffer_reader_t)); + + reader->buffer = buffer; + reader->pos = buffer->data; + + return reader; +} + +// +// Delete a buffer reader. +// +void DeleteReader(buffer_reader_t *reader) +{ + free(reader); +} + +// +// Count the number of bytes read thus far. +// +int Reader_BytesRead(buffer_reader_t *reader) +{ + return reader->pos - reader->buffer->data; +} + +// +// Read an unsigned byte from a buffer. +// +boolean Reader_ReadInt8(buffer_reader_t *reader, uint8_t *out) +{ + byte *data, *data_end; + int len = Buffer_Data(reader->buffer, &data); + + data_end = data + len; + + if (data_end - reader->pos < 1) + { + return false; + } + + *out = (uint8_t)*reader->pos; + reader->pos += 1; + + return true; +} + +// +// Read an unsigned short from a buffer. +// +boolean Reader_ReadInt16(buffer_reader_t *reader, uint16_t *out) +{ + byte *data, *data_end, *dp; + int len = Buffer_Data(reader->buffer, &data); + + data_end = data + len; + dp = reader->pos; + + if (data_end - reader->pos < 2) + { + return false; + } + + *out = (uint16_t)((dp[0] << 8) | dp[1]); + reader->pos += 2; + + return true; +} + +// +// Read an unsigned int from a buffer. +// +boolean Reader_ReadInt32(buffer_reader_t *reader, uint32_t *out) +{ + byte *data, *data_end, *dp; + int len = Buffer_Data(reader->buffer, &data); + + data_end = data + len; + dp = reader->pos; + + if (data_end - reader->pos < 4) + { + return false; + } + + *out = (uint32_t)((dp[0] << 24) | (dp[1] << 16) | (dp[2] << 8) | dp[3]); + reader->pos += 4; + + return true; +} + +// +// Read a string from a buffer. +// +char *Reader_ReadString(buffer_reader_t *reader) +{ + byte *data, *data_start, *data_end, *dp; + int len = Buffer_Data(reader->buffer, &data); + + data_start = reader->pos; + data_end = data + len; + dp = reader->pos; + + while (dp < data_end && *dp != '\0') + { + dp++; + } + + if (dp >= data_end) + { + // Didn't see a null terminator, not a complete string. + return NULL; + } + + reader->pos = dp + 1; + return (char*)data_start; +} + +#endif // #ifdef _WIN32 diff --git a/midiproc/buffer.h b/midiproc/buffer.h new file mode 100644 index 00000000..7eafe5b3 --- /dev/null +++ b/midiproc/buffer.h @@ -0,0 +1,54 @@ +// +// 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: +// A simple buffer and reader implementation. +// + +#ifndef __BUFFER__ +#define __BUFFER__ + +#include "../Source/doomtype.h" + +#define BUFFER_SIZE 1024 + +typedef struct { + byte buffer[BUFFER_SIZE]; // Buffer. + byte *buffer_end; // End of Buffer. + byte *data; // Start of actual data. + byte *data_end; // End of actual data. + int data_len; // Length of actual data. +} buffer_t; + +typedef struct { + buffer_t *buffer; + byte *pos; +} buffer_reader_t; + +buffer_t *NewBuffer(); +void DeleteBuffer(buffer_t* buf); +int Buffer_Data(buffer_t *buf, byte **data); +boolean Buffer_Push(buffer_t *buf, const void *data, int len); +void Buffer_Shift(buffer_t *buf, int len); +void Buffer_Clear(buffer_t *buf); + +buffer_reader_t *NewReader(buffer_t* buffer); +void DeleteReader(buffer_reader_t *reader); +int Reader_BytesRead(buffer_reader_t *reader); +boolean Reader_ReadInt8(buffer_reader_t *reader, uint8_t *out); +boolean Reader_ReadInt16(buffer_reader_t *reader, uint16_t *out); +boolean Reader_ReadInt32(buffer_reader_t *reader, uint32_t *out); +char *Reader_ReadString(buffer_reader_t *reader); + +#endif + diff --git a/midiproc/main.c b/midiproc/main.c new file mode 100644 index 00000000..e809d2f0 --- /dev/null +++ b/midiproc/main.c @@ -0,0 +1,463 @@ +// +// Copyright(C) 2012 James Haley +// 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: +// +// Win32/SDL_mixer MIDI Server +// +// Uses pipes to communicate with Doom. This allows this separate process to +// have its own independent volume control even under Windows Vista and up's +// broken, stupid, completely useless mixer model that can't assign separate +// volumes to different devices for the same process. +// +// Seriously, how did they screw up something so fundamental? +// + +#ifdef _WIN32 + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include + +#include "SDL.h" +#include "SDL_mixer.h" + +#include "buffer.h" +#include "proto.h" + +#include "config.h" +#include "doomtype.h" + +static HANDLE midi_process_in; // Standard In. +static HANDLE midi_process_out; // Standard Out. + +// Sound sample rate to use for digital output (Hz) +static int snd_samplerate = 0; + +// Currently playing music track. +static Mix_Music *music = NULL; + +//============================================================================= +// +// Private functions +// + +// +// Write an unsigned integer into a simple CHAR buffer. +// +static boolean WriteInt16(CHAR *out, size_t osize, unsigned int in) +{ + if (osize < 2) + { + return false; + } + + out[0] = (in >> 8) & 0xff; + out[1] = in & 0xff; + + return true; +} + +// +// Cleanly close our in-use pipes. +// +static void FreePipes(void) +{ + if (midi_process_in != NULL) + { + CloseHandle(midi_process_in); + midi_process_in = NULL; + } + if (midi_process_out != NULL) + { + CloseHandle(midi_process_out); + midi_process_out = NULL; + } +} + +// +// Unregisters the currently playing song. This is never called from the +// protocol, we simply do this before playing a new song. +// +static void UnregisterSong() +{ + if (music == NULL) + { + return; + } + + Mix_FreeMusic(music); + music = NULL; +} + +// +// Cleanly shut down SDL. +// +static void ShutdownSDL(void) +{ + UnregisterSong(); + Mix_CloseAudio(); + SDL_Quit(); +} + +//============================================================================= +// +// SDL_mixer Interface +// + +static boolean RegisterSong(const char *filename) +{ + music = Mix_LoadMUS(filename); + + // Remove the temporary MIDI file + remove(filename); + + if (music == NULL) + { + return false; + } + + return true; +} + +static void SetVolume(int vol) +{ + Mix_VolumeMusic(vol); +} + +static void PlaySong(int loops) +{ + Mix_PlayMusic(music, loops); + + // [AM] BUG: In my testing, setting the volume of a MIDI track while there + // is no song playing appears to be a no-op. This can happen when + // you're mixing midiproc with vanilla SDL_Mixer, such as when you + // are alternating between a digital music pack (in the parent + // process) and MIDI (in this process). + // + // To work around this bug, we set the volume to itself after the MIDI + // has started playing. + Mix_VolumeMusic(Mix_VolumeMusic(-1)); +} + +static void StopSong() +{ + Mix_HaltMusic(); +} + +//============================================================================= +// +// Pipe Server Interface +// + +static boolean MidiPipe_RegisterSong(buffer_reader_t *reader) +{ + char *filename = Reader_ReadString(reader); + if (filename == NULL) + { + return false; + } + + return RegisterSong(filename); +} + +static boolean MidiPipe_UnregisterSong(buffer_reader_t *reader) +{ + UnregisterSong(); + return true; +} + +boolean MidiPipe_SetVolume(buffer_reader_t *reader) +{ + int vol; + boolean ok = Reader_ReadInt32(reader, (uint32_t*)&vol); + if (!ok) + { + return false; + } + + SetVolume(vol); + + return true; +} + +boolean MidiPipe_PlaySong(buffer_reader_t *reader) +{ + int loops; + boolean ok = Reader_ReadInt32(reader, (uint32_t*)&loops); + if (!ok) + { + return false; + } + + PlaySong(loops); + + return true; +} + +boolean MidiPipe_StopSong() +{ + StopSong(); + + return true; +} + +boolean MidiPipe_Shutdown() +{ + exit(EXIT_SUCCESS); +} + +//============================================================================= +// +// Server Implementation +// + +// +// Parses a command and directs to the proper read function. +// +boolean ParseCommand(buffer_reader_t *reader, uint16_t command) +{ + switch (command) + { + case MIDIPIPE_PACKET_TYPE_REGISTER_SONG: + return MidiPipe_RegisterSong(reader); + case MIDIPIPE_PACKET_TYPE_UNREGISTER_SONG: + return MidiPipe_UnregisterSong(reader); + case MIDIPIPE_PACKET_TYPE_SET_VOLUME: + return MidiPipe_SetVolume(reader); + case MIDIPIPE_PACKET_TYPE_PLAY_SONG: + return MidiPipe_PlaySong(reader); + case MIDIPIPE_PACKET_TYPE_STOP_SONG: + return MidiPipe_StopSong(); + case MIDIPIPE_PACKET_TYPE_SHUTDOWN: + return MidiPipe_Shutdown(); + default: + return false; + } +} + +// +// Server packet parser +// +boolean ParseMessage(buffer_t *buf) +{ + CHAR buffer[2]; + DWORD bytes_written; + int bytes_read; + uint16_t command; + buffer_reader_t *reader = NewReader(buf); + + // Attempt to read a command out of the buffer. + if (!Reader_ReadInt16(reader, &command)) + { + goto fail; + } + + // Attempt to parse a complete message. + if (!ParseCommand(reader, command)) + { + goto fail; + } + + // We parsed a complete message! We can now safely shift + // the prior message off the front of the buffer. + bytes_read = Reader_BytesRead(reader); + DeleteReader(reader); + Buffer_Shift(buf, bytes_read); + + // Send acknowledgement back that the command has completed. + if (!WriteInt16(buffer, sizeof(buffer), MIDIPIPE_PACKET_TYPE_ACK)) + { + goto fail; + } + + WriteFile(midi_process_out, buffer, sizeof(buffer), + &bytes_written, NULL); + + return true; + +fail: + // We did not read a complete packet. Delete our reader and try again + // with more data. + DeleteReader(reader); + return false; +} + +// +// The main pipe "listening" loop +// +boolean ListenForever() +{ + BOOL wok = FALSE; + CHAR pipe_buffer[8192]; + DWORD pipe_buffer_read = 0; + + boolean ok = false; + buffer_t *buffer = NewBuffer(); + + for (;;) + { + // Wait until we see some data on the pipe. + wok = PeekNamedPipe(midi_process_in, NULL, 0, NULL, + &pipe_buffer_read, NULL); + if (!wok) + { + break; + } + else if (pipe_buffer_read == 0) + { + SDL_Delay(1); + continue; + } + + // Read data off the pipe and add it to the buffer. + wok = ReadFile(midi_process_in, pipe_buffer, sizeof(pipe_buffer), + &pipe_buffer_read, NULL); + if (!wok) + { + break; + } + + ok = Buffer_Push(buffer, pipe_buffer, pipe_buffer_read); + if (!ok) + { + break; + } + + do + { + // Read messages off the buffer until we can't anymore. + ok = ParseMessage(buffer); + } while (ok); + } + + return false; +} + +//============================================================================= +// +// Main Program +// + +// +// InitSDL +// +// Start up SDL and SDL_mixer. +// +boolean InitSDL() +{ + if (SDL_Init(SDL_INIT_AUDIO) == -1) + { + return false; + } + + if (Mix_OpenAudioDevice(snd_samplerate, MIX_DEFAULT_FORMAT, 2, 2048, NULL, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE) < 0) + { + return false; + } + + atexit(ShutdownSDL); + + return true; +} + +// +// InitPipes +// +// Ensure that we can communicate. +// +void InitPipes(HANDLE in, HANDLE out) +{ + midi_process_in = in; + midi_process_out = out; + + atexit(FreePipes); +} + +// +// main +// +// Application entry point. +// +int main(int argc, char *argv[]) +{ + HANDLE in, out; + + // Make sure we're not launching this process by itself. + if (argc < 5) + { + MessageBox(NULL, TEXT("This program is tasked with playing Native ") + TEXT("MIDI music, and is intended to be launched by ") + TEXT(PROJECT_NAME) TEXT("."), + TEXT(PROJECT_STRING), MB_OK | MB_ICONASTERISK); + + return EXIT_FAILURE; + } + + // Make sure our Choccolate Doom and midiproc version are lined up. + if (strcmp(PROJECT_STRING, argv[1]) != 0) + { + char message[1024]; + _snprintf(message, sizeof(message), + "It appears that the version of %s and woof-midiproc are out " + "of sync. Please reinstall %s.\r\n\r\n" + "Server Version: %s\r\nClient Version: %s", + PROJECT_NAME, PROJECT_NAME, + PROJECT_STRING, argv[1]); + message[sizeof(message) - 1] = '\0'; + + MessageBox(NULL, TEXT(message), + TEXT(PROJECT_STRING), MB_OK | MB_ICONASTERISK); + + return EXIT_FAILURE; + } + + // Parse out the sample rate - if we can't, default to 44100. + snd_samplerate = strtol(argv[2], NULL, 10); + if (snd_samplerate == LONG_MAX || snd_samplerate == LONG_MIN || + snd_samplerate == 0) + { + snd_samplerate = 44100; + } + + // Parse out our handle ids. + in = (HANDLE) strtol(argv[3], NULL, 10); + if (in == 0) + { + return EXIT_FAILURE; + } + + out = (HANDLE) strtol(argv[4], NULL, 10); + if (out == 0) + { + return EXIT_FAILURE; + } + + InitPipes(in, out); + + if (!InitSDL()) + { + return EXIT_FAILURE; + } + + if (!ListenForever()) + { + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +#endif // #ifdef _WIN32 diff --git a/midiproc/proto.h b/midiproc/proto.h new file mode 100644 index 00000000..0ded0cfd --- /dev/null +++ b/midiproc/proto.h @@ -0,0 +1,33 @@ +// +// 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: +// Headers for all types of midipipe messages. +// + +#ifndef __PROTO__ +#define __PROTO__ + +typedef enum { + MIDIPIPE_PACKET_TYPE_REGISTER_SONG, + MIDIPIPE_PACKET_TYPE__DEPRECATED_1, + MIDIPIPE_PACKET_TYPE_SET_VOLUME, + MIDIPIPE_PACKET_TYPE_PLAY_SONG, + MIDIPIPE_PACKET_TYPE_STOP_SONG, + MIDIPIPE_PACKET_TYPE_SHUTDOWN, + MIDIPIPE_PACKET_TYPE_UNREGISTER_SONG, + MIDIPIPE_PACKET_TYPE_ACK, +} net_midipipe_packet_type_t; + +#endif +