shithub: choc

ref: 090bc3bb61c59cc9d335d840ecc36e9fe3336fa1
dir: /src/i_midipipe.c/

View raw version
//
// Copyright(C) 2013 James Haley et al.
// Copyright(C) 2017 Alex Mayfield
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// DESCRIPTION:
//     Client Interface to RPC Midi Server
//

#if _WIN32

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <SDL_net.h>

#include "i_midipipe.h"

#include "config.h"
#include "m_misc.h"
#include "net_packet.h"

#if defined(_DEBUG)
#define DEBUGOUT(s) puts(s)
#else
#define DEBUGOUT(s)
#endif

//=============================================================================
//
// 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;

static boolean server_init = false; // if true, server was started
static boolean client_init = false; // if true, client was bound

//=============================================================================
//
// 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(!server_init) \
        return false

#define MIDIRPC_MAXTRIES 50 // This number * 10 is the amount of time you can try to wait for.

static boolean I_MidiPipeWrite(void *data, int len)
{
    DWORD written;
    if (WriteFile(midi_process_in_writer, data, len, &written, NULL))
    {
        return true;
    }
    else
    {
        return false;
    }
}

static boolean I_MidiPipeWaitForServer()
{
    int tries = 0;
    while(false) // TODO: Is there some way to tell if the server is listening?
    {
        I_Sleep(10);
        if (++tries >= MIDIRPC_MAXTRIES)
        {
            return false;
        }
    }
    return true;
}

//
// I_MidiPipeRegisterSong
//
// Prepare the RPC MIDI engine to receive new song data, and transmit the song
// filename to the server process.
//
boolean I_MidiPipeRegisterSong(const char *filename)
{
    BOOL wok;
    net_packet_t *packet;

    CHECK_RPC_STATUS();

    packet = NET_NewPacket(64);
    NET_WriteInt16(packet, NET_MIDIPIPE_PACKET_TYPE_PREPARE_NEW_SONG);
    NET_WriteInt16(packet, NET_MIDIPIPE_PACKET_TYPE_SET_FILENAME);
    NET_WriteString(packet, filename);
    wok = WriteFile(midi_process_in_writer, packet->data, packet->len,
        NULL, NULL);
    NET_FreePacket(packet);

    if (!wok)
    {
        DEBUGOUT("I_MidiPipeRegisterSong failed");
        return false;
    }

    DEBUGOUT("I_MidiPipeRegisterSong succeeded");
    return true;
}

//
// I_MidiPipePlaySong
//
// Tell the RPC server to start playing a song.
//
boolean I_MidiPipePlaySong(boolean looping)
{
    BOOL wok;
    net_packet_t *packet;

    CHECK_RPC_STATUS();

    packet = NET_NewPacket(3);
    NET_WriteInt16(packet, NET_MIDIPIPE_PACKET_TYPE_PLAY_SONG);
    NET_WriteInt8(packet, looping);
    wok = WriteFile(midi_process_in_writer, packet->data, packet->len,
        NULL, NULL);
    NET_FreePacket(packet);

    if (!wok)
    {
        DEBUGOUT("I_MidiPipePlaySong failed");
        return false;
    }

    DEBUGOUT("I_MidiPipePlaySong succeeded");
    return true;
}

// 
// I_MidiPipeStopSong
//
// Tell the RPC server to stop any currently playing song.
//
boolean I_MidiPipeStopSong()
{
    BOOL wok;
    net_packet_t *packet;

    CHECK_RPC_STATUS();

    packet = NET_NewPacket(2);
    NET_WriteInt16(packet, NET_MIDIPIPE_PACKET_TYPE_STOP_SONG);
    wok = WriteFile(midi_process_in_writer, packet->data, packet->len,
        NULL, NULL);
    NET_FreePacket(packet);

    if (!wok)
    {
        DEBUGOUT("I_MidiPipeStopSong failed");
        return false;
    }

    DEBUGOUT("I_MidiPipeStopSong succeeded");
    return true;
}

//
// I_MidiPipeSetVolume
//
// Change the volume level of music played by the RPC midi server.
//
boolean I_MidiPipeSetVolume(int volume)
{
    BOOL wok;
    net_packet_t *packet;

    CHECK_RPC_STATUS();

    packet = NET_NewPacket(6);
    NET_WriteInt16(packet, NET_MIDIPIPE_PACKET_TYPE_CHANGE_VOLUME);
    NET_WriteInt32(packet, volume);
    wok = WriteFile(midi_process_in_writer, packet->data, packet->len,
        NULL, NULL);
    NET_FreePacket(packet);

    if (!wok)
    {
        DEBUGOUT("I_MidiPipeSetVolume failed");
        return false;
    }

    DEBUGOUT("I_MidiPipeSetVolume succeeded");
    return true;
}

//
// I_MidiPipePauseSong
//
// Pause the music being played by the server. In actuality, due to SDL_mixer
// limitations, this just temporarily sets the volume to zero.
//
boolean I_MidiPipePauseSong()
{
    BOOL wok;
    net_packet_t *packet;

    CHECK_RPC_STATUS();

    packet = NET_NewPacket(2);
    NET_WriteInt16(packet, NET_MIDIPIPE_PACKET_TYPE_PAUSE_SONG);
    wok = WriteFile(midi_process_in_writer, packet->data, packet->len,
        NULL, NULL);
    NET_FreePacket(packet);

    if (!wok)
    {
        DEBUGOUT("I_MidiPipePauseSong failed");
        return false;
    }

    DEBUGOUT("I_MidiPipePauseSong succeeded");
    return true;
}

//
// I_MidiPipeResumeSong
//
// Resume a song after having paused it.
//
boolean I_MidiPipeResumeSong()
{
    BOOL wok;
    net_packet_t *packet;

    CHECK_RPC_STATUS();

    packet = NET_NewPacket(2);
    NET_WriteInt16(packet, NET_MIDIPIPE_PACKET_TYPE_RESUME_SONG);
    wok = WriteFile(midi_process_in_writer, packet->data, packet->len,
        NULL, NULL);
    NET_FreePacket(packet);

    if (!wok)
    {
        DEBUGOUT("I_MidiPipeResumeSong failed");
        return false;
    }

    DEBUGOUT("I_MidiPipeResumeSong succeeded");
   return true;
}

//=============================================================================
//
// Public Interface
//

//
// I_MidiPipeInitServer
//
// Start up the MIDI server.
//
boolean I_MidiPipeInitServer()
{
    struct stat sbuf;
    char filename[MAX_PATH+1];

    memset(filename, 0, sizeof(filename));
    size_t filename_len = GetModuleFileName(NULL, filename, MAX_PATH);

    // Remove filespec
    // TODO: Move this to m_misc
    char *fp = &filename[filename_len];
    while (filename <= fp && *fp != DIR_SEPARATOR)
    {
        fp--;
    }
    *(fp + 1) = '\0';
    char* module = M_StringJoin(filename, PROGRAM_PREFIX "midiproc.exe", NULL);
    char* cmdline = M_StringJoin(module, " \"" PACKAGE_STRING "\"", NULL);
    DEBUGOUT(module);
    DEBUGOUT(cmdline);

    // Look for executable file
    if(stat(module, &sbuf))
    {
        DEBUGOUT("Could not find midiproc");
        return false;
    }

    // Set up pipes
    SECURITY_ATTRIBUTES sec_attrs;
    memset(&sec_attrs, 0, sizeof(SECURITY_ATTRIBUTES));
    sec_attrs.nLength = sizeof(SECURITY_ATTRIBUTES);
    sec_attrs.bInheritHandle = TRUE;
    sec_attrs.lpSecurityDescriptor = NULL;

    if (!CreatePipe(&midi_process_in_reader, &midi_process_in_writer, &sec_attrs, 0))
    {
        DEBUGOUT("Could not initialize midiproc stdin");
        return false;
    }

    if (!SetHandleInformation(midi_process_in_writer, HANDLE_FLAG_INHERIT, 0))
    {
        DEBUGOUT("Could not disinherit midiproc stdin");
        return false;
    }

    if (!CreatePipe(&midi_process_out_reader, &midi_process_out_writer, &sec_attrs, 0))
    {
        DEBUGOUT("Could not initialize midiproc stdout/stderr");
        return false;
    }

    if (!SetHandleInformation(midi_process_out_reader, HANDLE_FLAG_INHERIT, 0))
    {
        DEBUGOUT("Could not disinherit midiproc stdin");
        return false;
    }

    // Launch the subprocess
    PROCESS_INFORMATION proc_info;
    memset(&proc_info, 0, sizeof(proc_info));

    STARTUPINFO startup_info;
    memset(&startup_info, 0, sizeof(startup_info));
    startup_info.cb = sizeof(startup_info);
    startup_info.hStdInput = midi_process_in_reader;
    startup_info.hStdOutput = midi_process_out_writer;
    startup_info.dwFlags = STARTF_USESTDHANDLES;

    boolean ok = CreateProcess(TEXT(module), TEXT(cmdline), NULL, NULL, TRUE,
        CREATE_NEW_CONSOLE, NULL, NULL, &startup_info, &proc_info);

    if (ok)
    {
        DEBUGOUT("midiproc started");
        server_init = true;
    }
    else
    {
        DEBUGOUT("failed to start midiproc");
    }

    return ok;
}

//
// I_MidiPipeInitClient
//
// Ensure that we can actually communicate with the subprocess.
//
boolean I_MidiPipeInitClient()
{
    client_init = true;
    return true;
}

//
// I_MidiPipeClientShutDown
//
// Shutdown the RPC Client
//
/* void I_MidiPipeClientShutDown()
{
    // stop the server
    if(server_init)
    {
        net_packet_t *packet;
        packet = NET_NewPacket(2);
        NET_WriteInt16(packet, NET_MIDIPIPE_PACKET_TYPE_STOP_SERVER);
        int len = SDLNet_TCP_Send(midi_socket, packet->data, packet->len);
        NET_FreePacket(packet);
        if (len < packet->len)
        {
            DEBUGOUT("Problem encountered when stopping RPC server");
        }

        server_init = false;
    }

    if (midi_socket)
    {
        SDLNet_TCP_Close(midi_socket);
        midi_socket = NULL;
    }

    client_init = false;
} */

//
// I_MidiPipeReady
//
// Returns true if both server and client initialized successfully.
//
boolean I_MidiPipeReady()
{
    CHECK_RPC_STATUS();

    return true;
}

#endif