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
+