shithub: choc

Download patch

ref: a941e8c70a73570b6c9bae3c037e880262bb499d
parent: 753db4d4c076bfd5b71198fc41b4b94e942ce482
author: Alex Mayfield <[email protected]>
date: Fri Feb 10 13:27:07 EST 2017

Initial version of MidiRPC from Eternity

Quasar has graciously offered this functionality under the GPLv2.

--- /dev/null
+++ b/midiproc/main.cpp
@@ -1,0 +1,386 @@
+// Emacs style mode select   -*- C++ -*-
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2012 James Haley
+//
+// 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.
+// 
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+//
+//-----------------------------------------------------------------------------
+//
+// DESCRIPTION:
+//
+// Win32/SDL_mixer MIDI RPC Server
+//
+// Uses RPC to communicate with Eternity. 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?
+//
+//-----------------------------------------------------------------------------
+
+#include <windows.h>
+#include <stdlib.h>
+#include "SDL.h"
+#include "SDL_mixer.h"
+#include "midiproc.h"
+
+// Currently playing music track
+static Mix_Music *music = NULL;
+static SDL_RWops *rw    = NULL;
+
+static void UnregisterSong();
+
+//=============================================================================
+//
+// RPC Memory Management
+//
+
+void __RPC_FAR * __RPC_USER midl_user_allocate(size_t size)
+{
+   return malloc(size);
+}
+
+void __RPC_USER midl_user_free(void __RPC_FAR *p)
+{
+   free(p);
+}
+
+//=============================================================================
+//
+// SDL_mixer Interface
+//
+
+//
+// InitSDL
+//
+// Start up SDL and SDL_mixer.
+//
+static bool InitSDL()
+{
+   if(SDL_Init(SDL_INIT_AUDIO) == -1)
+      return false;
+
+   if(Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) < 0)
+      return false;
+
+   return true;
+}
+
+//
+// RegisterSong
+//
+static void RegisterSong(void *data, size_t size)
+{
+   if(music)
+      UnregisterSong();
+
+   rw    = SDL_RWFromMem(data, size);
+   music = Mix_LoadMUS_RW(rw);
+}
+
+//
+// StartSong
+//
+static void StartSong(bool loop)
+{
+   if(music)
+      Mix_PlayMusic(music, loop ? -1 : 0);
+}
+
+//
+// SetVolume
+//
+static void SetVolume(int volume)
+{
+   Mix_VolumeMusic((volume * 128) / 15);
+}
+
+static int paused_midi_volume;
+
+//
+// PauseSong
+//
+static void PauseSong()
+{
+   paused_midi_volume = Mix_VolumeMusic(-1);
+   Mix_VolumeMusic(0);
+}
+
+//
+// ResumeSong
+//
+static void ResumeSong()
+{
+   Mix_VolumeMusic(paused_midi_volume);
+}
+
+//
+// StopSong
+//
+static void StopSong()
+{
+   if(music)
+      Mix_HaltMusic();
+}
+
+//
+// UnregisterSong
+//
+static void UnregisterSong()
+{
+   if(!music)
+      return;
+
+   StopSong();
+   Mix_FreeMusic(music);
+   rw    = NULL;
+   music = NULL;
+}
+
+//
+// ShutdownSDL
+//
+static void ShutdownSDL()
+{
+   UnregisterSong();
+   Mix_CloseAudio();
+   SDL_Quit();
+}
+
+//=============================================================================
+//
+// Song Buffer
+//
+// The MIDI program will be transmitted by the client across RPC in fixed-size
+// chunks until all data has been transmitted.
+//
+
+typedef unsigned char midibyte;
+
+class SongBuffer
+{
+protected:
+   midibyte *buffer;    // accumulated input
+   size_t    size;      // size of input
+   size_t    allocated; // amount of memory allocated (>= size)
+
+   static const int defaultSize = 128*1024; // 128 KB
+
+public:
+   // Constructor
+   // Start out with an empty 128 KB buffer.
+   SongBuffer()
+   {
+      buffer = static_cast<midibyte *>(calloc(1, defaultSize));
+      size = 0;
+      allocated = defaultSize;
+   }
+
+   // Destructor.
+   // Release the buffer.
+   ~SongBuffer()
+   {
+      if(buffer)
+      {
+         free(buffer);
+         buffer = NULL;
+         size = allocated = 0;
+      }
+   }
+
+   //
+   // addChunk
+   //
+   // Add a chunk of MIDI data to the buffer.
+   //
+   void addChunk(midibyte *data, size_t newsize)
+   {
+      if(size + newsize > allocated)
+      {
+         allocated += newsize * 2;
+         buffer = static_cast<midibyte *>(realloc(buffer, allocated));
+      }
+
+      memcpy(buffer + size, data, newsize);
+      size += newsize;
+   }
+
+   // Accessors
+
+   midibyte *getBuffer() const { return buffer; }
+   size_t    getSize()   const { return size;   }
+};
+
+static SongBuffer *song;
+
+//=============================================================================
+//
+// RPC Server Interface
+//
+
+//
+// MidiRPC_PrepareNewSong
+//
+// Prepare the engine to receive new song data from the RPC client.
+//
+void MidiRPC_PrepareNewSong()
+{
+   // Stop anything currently playing and free it.
+   UnregisterSong();
+
+   // free any previous song buffer
+   delete song;
+
+   // prep new song buffer
+   song = new SongBuffer();
+}
+
+//
+// MidiRPC_AddChunk
+//
+// Add a chunk of data to the song.
+//
+void MidiRPC_AddChunk(unsigned int count, byte *pBuf)
+{
+   song->addChunk(pBuf, static_cast<size_t>(count));
+}
+
+//
+// MidiRPC_PlaySong
+//
+// Start playing the song.
+//
+void MidiRPC_PlaySong(boolean looping)
+{
+   RegisterSong(song->getBuffer(), song->getSize());
+   StartSong(!!looping);
+}
+
+//
+// MidiRPC_StopSong
+//
+// Stop the song.
+//
+void MidiRPC_StopSong()
+{
+   StopSong();
+}
+
+//
+// MidiRPC_ChangeVolume
+//
+// Set playback volume level.
+//
+void MidiRPC_ChangeVolume(int volume)
+{
+   SetVolume(volume);
+}
+
+//
+// MidiRPC_PauseSong
+//
+// Pause the song.
+//
+void MidiRPC_PauseSong()
+{
+   PauseSong();
+}
+
+//
+// MidiRPC_ResumeSong
+//
+// Resume after pausing.
+//
+void MidiRPC_ResumeSong()
+{
+   ResumeSong();
+}
+
+//
+// MidiRPC_StopServer
+//
+// Stops the RPC server so the program can shutdown.
+//
+void MidiRPC_StopServer()
+{
+   // Local shutdown tasks
+   ShutdownSDL();
+   delete song;
+   song = NULL;
+
+   // Stop RPC server
+   RpcMgmtStopServerListening(NULL);
+}
+
+//
+// RPC Server Init
+//
+static bool MidiRPC_InitServer()
+{
+   RPC_STATUS status;
+
+   // Initialize RPC protocol
+   status = 
+      RpcServerUseProtseqEp
+      (
+         (RPC_CSTR)("ncalrpc"),
+         RPC_C_PROTSEQ_MAX_REQS_DEFAULT,
+         (RPC_CSTR)("2d4dc2f9-ce90-4080-8a00-1cb819086970"),
+         NULL
+      );
+
+   if(status)
+      return false;
+
+   // Register server
+   status = RpcServerRegisterIf(MidiRPC_v1_0_s_ifspec, NULL, NULL);
+
+   if(status)
+      return false;
+
+   // Start listening
+   status = RpcServerListen(1, RPC_C_LISTEN_MAX_CALLS_DEFAULT, FALSE);
+
+   return !status;
+}
+
+//=============================================================================
+//
+// Main Program
+//
+
+//
+// WinMain
+//
+// Application entry point.
+//
+int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
+                     LPSTR lpCmdLine, int nCmdShow)
+{
+   // Initialize SDL
+   if(!InitSDL())
+      return -1;
+
+   // Initialize RPC Server
+   if(!MidiRPC_InitServer())
+      return -1;
+
+   return 0;
+}
+
+// EOF
+
--- /dev/null
+++ b/midiproc/midiproc.acf
@@ -1,0 +1,7 @@
+[
+   implicit_handle(handle_t hMidiRPCBinding)
+]
+
+interface MidiRPC
+{
+}
--- /dev/null
+++ b/midiproc/midiproc.idl
@@ -1,0 +1,18 @@
+[
+   uuid(2d4dc2f9-ce90-4080-8a00-1cb819086970),
+   version(1.0),
+   implicit_handle(handle_t hMidiRPCBinding)
+]
+
+interface MidiRPC
+{
+   void MidiRPC_PrepareNewSong(void);
+   void MidiRPC_AddChunk([in] unsigned int count, [in, size_is(count)] byte *pBuf);
+   void MidiRPC_PlaySong([in] boolean looping);
+   void MidiRPC_StopSong(void);
+   void MidiRPC_ChangeVolume([in] int volume);
+   void MidiRPC_PauseSong(void);
+   void MidiRPC_ResumeSong(void);
+   void MidiRPC_StopServer(void);
+}
+
--- /dev/null
+++ b/src/i_midirpc.cpp
@@ -1,0 +1,398 @@
+// Emacs style mode select   -*- C++ -*-
+//-----------------------------------------------------------------------------
+//
+// Copyright (C) 2013 James Haley et al.
+//
+// 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 3 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.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see http://www.gnu.org/licenses/
+//
+//----------------------------------------------------------------------------
+//
+// DESCRIPTION:
+//
+// Client Interface to RPC Midi Server
+//
+//-----------------------------------------------------------------------------
+
+#ifdef EE_FEATURE_MIDIRPC
+
+#include <windows.h>
+#include "midiproc.h"
+
+#include "../hal/i_timer.h"
+#include "../m_qstr.h"
+
+#if defined(_DEBUG) && defined(EE_RPC_DEBUG)
+#define DEBUGOUT(s) puts(s)
+#else
+#define DEBUGOUT(s)
+#endif
+
+//=============================================================================
+//
+// Data
+//
+
+static unsigned char *szStringBinding; // RPC client binding string
+static bool serverInit = false;        // if true, server was started
+static bool clientInit = false;        // if true, client was bound
+
+// server process information
+static STARTUPINFO         si;
+static PROCESS_INFORMATION pi;
+
+//=============================================================================
+//
+// RPC Memory Management
+//
+
+void __RPC_FAR * __RPC_USER midl_user_allocate(size_t size)
+{
+   return malloc(size);
+}
+
+void __RPC_USER midl_user_free(void __RPC_FAR *p)
+{
+   free(p);
+}
+
+//=============================================================================
+//
+// RPC Wrappers
+//
+
+//
+// CHECK_RPC_STATUS
+//
+// If either server or client initialization failed, we don't try to make any
+// RPC calls.
+//
+#define CHECK_RPC_STATUS()        \
+   if(!serverInit || !clientInit) \
+      return false
+
+#define MIDIRPC_MAXTRIES 50 // This number * 10 is the amount of time you can try to wait for.
+
+static bool I_MidiRPCWaitForServer()
+{
+   int tries = 0;
+   while(RpcMgmtIsServerListening(hMidiRPCBinding) != RPC_S_OK)
+   {
+      i_haltimer.Sleep(10);
+      if(++tries >= MIDIRPC_MAXTRIES)
+         return false;
+   }
+   return true;
+}
+
+//
+// I_MidiRPCRegisterSong
+//
+// Prepare the RPC MIDI engine to receive new song data, and transmit the song
+// data to the server process.
+//
+bool I_MidiRPCRegisterSong(void *data, int size)
+{
+   unsigned int rpcSize = static_cast<unsigned int>(size);
+
+   CHECK_RPC_STATUS();
+
+   RpcTryExcept
+   {
+      MidiRPC_PrepareNewSong();
+
+      // TODO: Try passing it as one chunk; if this ends up not working, 
+      // I'll have to stream it in as smaller divisions.
+      MidiRPC_AddChunk(rpcSize, static_cast<byte *>(data));
+   }
+   RpcExcept(1)
+   {
+      DEBUGOUT("I_MidiRPCRegisterSong failed");
+      return false;
+   }
+   RpcEndExcept
+
+   DEBUGOUT("I_MidiRPCRegisterSong succeeded");
+   return true;
+}
+
+//
+// I_MidiRPCPlaySong
+//
+// Tell the RPC server to start playing a song.
+//
+bool I_MidiRPCPlaySong(bool looping)
+{
+   CHECK_RPC_STATUS();
+
+   RpcTryExcept
+   {
+      MidiRPC_PlaySong(looping ? TRUE : FALSE);
+   }
+   RpcExcept(1)
+   {
+      DEBUGOUT("I_MidiRPCPlaySong failed");
+      return false;
+   }
+   RpcEndExcept
+
+   DEBUGOUT("I_MidiRPCPlaySong succeeded");
+   return true;
+}
+
+// 
+// I_MidiRPCStopSong
+//
+// Tell the RPC server to stop any currently playing song.
+//
+bool I_MidiRPCStopSong()
+{
+   CHECK_RPC_STATUS();
+
+   RpcTryExcept
+   {
+      MidiRPC_StopSong();
+   }
+   RpcExcept(1)
+   {
+      DEBUGOUT("I_MidiRPCStopSong failed");
+      return false;
+   }
+   RpcEndExcept
+
+   DEBUGOUT("I_MidiRPCStopSong succeeded");
+   return true;
+}
+
+//
+// I_MidiRPCSetVolume
+//
+// Change the volume level of music played by the RPC midi server.
+//
+bool I_MidiRPCSetVolume(int volume)
+{
+   CHECK_RPC_STATUS();
+   
+   RpcTryExcept
+   {
+      MidiRPC_ChangeVolume(volume);
+   }
+   RpcExcept(1)
+   {
+      DEBUGOUT("I_MidiRPCSetVolume failed");
+      return false;
+   }
+   RpcEndExcept
+
+   DEBUGOUT("I_MidiRPCSetVolume succeeded");
+   return true;
+}
+
+//
+// I_MidiRPCPauseSong
+//
+// Pause the music being played by the server. In actuality, due to SDL_mixer
+// limitations, this just temporarily sets the volume to zero.
+//
+bool I_MidiRPCPauseSong()
+{
+   CHECK_RPC_STATUS();
+
+   RpcTryExcept
+   {
+      MidiRPC_PauseSong();
+   }
+   RpcExcept(1)
+   {
+      DEBUGOUT("I_MidiRPCPauseSong failed");
+      return false;
+   }
+   RpcEndExcept
+
+   DEBUGOUT("I_MidiRPCPauseSong succeeded");
+   return true;
+}
+
+//
+// I_MidiRPCResumeSong
+//
+// Resume a song after having paused it.
+//
+bool I_MidiRPCResumeSong()
+{
+   CHECK_RPC_STATUS();
+
+   RpcTryExcept
+   {
+      MidiRPC_ResumeSong();
+   }
+   RpcExcept(1)
+   {
+      DEBUGOUT("I_MidiRPCResumeSong failed");
+      return false;
+   }
+   RpcEndExcept
+
+   DEBUGOUT("I_MidiRPCResumeSong succeeded");
+   return true;
+}
+
+//=============================================================================
+//
+// Public Interface
+//
+
+//
+// I_MidiRPCInitServer
+//
+// Start up the RPC MIDI server.
+//
+bool I_MidiRPCInitServer()
+{
+   struct stat sbuf;
+   char filename[MAX_PATH+1];
+
+   memset(filename, 0, sizeof(filename));
+   GetModuleFileName(NULL, filename, MAX_PATH);
+
+   qstring module;
+
+   module = filename;
+   module.removeFileSpec();
+   module.pathConcatenate("midiproc.exe");
+   DEBUGOUT(module.constPtr());
+
+   // Look for executable file
+   if(stat(module.constPtr(), &sbuf))
+   {
+      DEBUGOUT("Could not find midiproc");
+      return false;
+   }
+
+   si.cb = sizeof(si);
+
+   BOOL result = CreateProcess(module.constPtr(), NULL, NULL, NULL, FALSE,
+                               0, NULL, NULL, &si, &pi);
+
+   if(result)
+   {
+      DEBUGOUT("RPC server started");
+      serverInit = true;
+   }
+   else
+      DEBUGOUT("CreateProcess failed to start midiproc");
+
+   return !!result;
+}
+
+//
+// I_MidiRPCInitClient
+//
+// Initialize client RPC bindings and bind to the server.
+//
+bool I_MidiRPCInitClient()
+{
+   RPC_STATUS status;
+
+   // If server didn't start, client cannot be bound.
+   if(!serverInit)
+      return false;
+
+   // Compose binding string
+   status =
+      RpcStringBindingCompose
+      (
+         NULL,
+         (RPC_CSTR)("ncalrpc"),
+         NULL,
+         (RPC_CSTR)("2d4dc2f9-ce90-4080-8a00-1cb819086970"),
+         NULL,
+         &szStringBinding
+      );
+
+   if(status)
+   {
+      DEBUGOUT("RPC binding composition failed");
+      return false;
+   }
+
+   // Create binding handle
+   status = RpcBindingFromStringBinding(szStringBinding, &hMidiRPCBinding);
+
+   if(status)
+   {
+      DEBUGOUT("RPC client binding failed");
+      return false;
+   }
+
+   DEBUGOUT("RPC client initialized");
+   clientInit = true;
+
+   return I_MidiRPCWaitForServer();
+}
+
+//
+// I_MidiRPCClientShutDown
+//
+// Shutdown the RPC Client
+//
+void I_MidiRPCClientShutDown()
+{
+   // stop the server
+   if(serverInit)
+   {
+      RpcTryExcept
+      {
+         MidiRPC_StopServer();
+      }
+      RpcExcept(1)
+      {
+         DEBUGOUT("Exception thrown when stopping RPC server");
+      }
+      RpcEndExcept
+
+      serverInit = false;
+   }
+
+   if(szStringBinding)
+   {
+      RpcStringFree(&szStringBinding);
+      szStringBinding = NULL;
+   }
+
+   if(hMidiRPCBinding)
+   {
+      RpcBindingFree(&hMidiRPCBinding);
+      hMidiRPCBinding = NULL;
+   }
+
+   clientInit = false;
+}
+
+//
+// I_MidiRPCReady
+//
+// Returns true if both server and client initialized successfully.
+//
+bool I_MidiRPCReady()
+{
+   CHECK_RPC_STATUS();
+
+   return true;
+}
+
+#endif
+
+// EOF
+
+
--- /dev/null
+++ b/src/i_midirpc.h
@@ -1,0 +1,52 @@
+// Emacs style mode select   -*- C++ -*-
+//-----------------------------------------------------------------------------
+//
+// Copyright (C) 2013 James Haley et al.
+//
+// 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 3 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.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see http://www.gnu.org/licenses/
+//
+// Additional terms and conditions compatible with the GPLv3 apply. See the
+// file COPYING-EE for details.
+//
+//----------------------------------------------------------------------------
+//
+// DESCRIPTION:
+//
+// Client Interface to RPC Midi Server
+//
+//-----------------------------------------------------------------------------
+
+#ifndef I_MIDIRPC_H__
+#define I_MIDIRPC_H__
+
+#ifdef EE_FEATURE_MIDIRPC
+
+bool I_MidiRPCInitServer();
+bool I_MidiRPCInitClient();
+void I_MidiRPCClientShutDown();
+bool I_MidiRPCReady();
+
+bool I_MidiRPCRegisterSong(void *data, int size);
+bool I_MidiRPCPlaySong(bool looping);
+bool I_MidiRPCStopSong();
+bool I_MidiRPCSetVolume(int volume);
+bool I_MidiRPCPauseSong();
+bool I_MidiRPCResumeSong();
+
+#endif
+
+#endif
+
+// EOF
+