ref: 5383fa67ab4ddcd01c9352b47a3ad34f2ebf9da7
parent: 52ccdb80987010c768d6f65a1b6c87843a1fcdf3
parent: 83a7dfb9af4b8819b6d578044809fc91cc21661a
author: Jonathan Dowland <[email protected]>
date: Mon Jul 17 09:30:37 EDT 2017
Merge pull request #881 from AlexMax/chocolate-midivolume Separately controllable MIDI volume on Windows Vista+
--- a/Makefile.am
+++ b/Makefile.am
@@ -70,7 +70,7 @@
MAINTAINERCLEANFILES = $(AUX_DIST_GEN)
-SUBDIRS=textscreen opl pcsound data src man
+SUBDIRS=textscreen midiproc opl pcsound data src man
DIST_SUBDIRS=pkg $(SUBDIRS)
--- a/configure.ac
+++ b/configure.ac
@@ -156,6 +156,7 @@
man/bash-completion/heretic.template
man/bash-completion/hexen.template
man/bash-completion/strife.template
+midiproc/Makefile
opl/Makefile
opl/examples/Makefile
pcsound/Makefile
--- /dev/null
+++ b/midiproc/.gitignore
@@ -1,0 +1,6 @@
+Makefile.in
+Makefile
+*.exe
+.deps
+tags
+TAGS
--- /dev/null
+++ b/midiproc/Makefile.am
@@ -1,0 +1,11 @@
+
+AM_CFLAGS=-I$(top_srcdir)/src @SDLMIXER_CFLAGS@
+
+if HAVE_WINDRES
+
+noinst_PROGRAMS = @PROGRAM_PREFIX@midiproc
+
+@PROGRAM_PREFIX@midiproc_LDADD = @SDLMIXER_LIBS@
+@PROGRAM_PREFIX@midiproc_SOURCES = buffer.c buffer.h main.c proto.h
+
+endif
--- /dev/null
+++ b/midiproc/buffer.c
@@ -1,0 +1,253 @@
+//
+// 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 <stdlib.h>
+
+//
+// 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
--- /dev/null
+++ b/midiproc/buffer.h
@@ -1,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 "../src/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
+
--- /dev/null
+++ b/midiproc/main.c
@@ -1,0 +1,459 @@
+//
+// 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 <windows.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#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);
+}
+
+//
+// Cleanly shut down SDL.
+//
+static void ShutdownSDL(void)
+{
+ UnregisterSong();
+ Mix_CloseAudio();
+ SDL_Quit();
+}
+
+//=============================================================================
+//
+// SDL_mixer Interface
+//
+
+static boolean RegisterSong(const char *filename)
+{
+ UnregisterSong();
+ music = Mix_LoadMUS(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 buffer[2];
+ DWORD bytes_written;
+
+ char *filename = Reader_ReadString(reader);
+ if (filename == NULL)
+ {
+ return false;
+ }
+
+ RegisterSong(filename);
+
+ if (!WriteInt16(buffer, sizeof(buffer),
+ MIDIPIPE_PACKET_TYPE_REGISTER_SONG_ACK))
+ {
+ return false;
+ }
+
+ WriteFile(midi_process_out, buffer, sizeof(buffer),
+ &bytes_written, NULL);
+
+ 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_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)
+{
+ 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);
+
+ 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_OpenAudio(snd_samplerate, MIX_DEFAULT_FORMAT, 2, 2048) < 0)
+ {
+ return false;
+ }
+
+ atexit(ShutdownSDL);
+
+ return true;
+}
+
+//
+// InitPipes
+//
+// Ensure that we can communicate.
+//
+boolean InitPipes()
+{
+ midi_process_in = GetStdHandle(STD_INPUT_HANDLE);
+ if (midi_process_in == INVALID_HANDLE_VALUE)
+ {
+ goto fail;
+ }
+
+ midi_process_out = GetStdHandle(STD_OUTPUT_HANDLE);
+ if (midi_process_out == INVALID_HANDLE_VALUE)
+ {
+ goto fail;
+ }
+
+ atexit(FreePipes);
+
+ return true;
+
+fail:
+ FreePipes();
+
+ return false;
+}
+
+//
+// main
+//
+// Application entry point.
+//
+int main(int argc, char *argv[])
+{
+ // Make sure we're not launching this process by itself.
+ if (argc < 3)
+ {
+ MessageBox(NULL, TEXT("This program is tasked with playing Native ")
+ TEXT("MIDI music, and is intended to be launched by ")
+ TEXT(PACKAGE_NAME) TEXT("."),
+ TEXT(PACKAGE_STRING), MB_OK | MB_ICONASTERISK);
+
+ return EXIT_FAILURE;
+ }
+
+ // Make sure our Choccolate Doom and midiproc version are lined up.
+ if (strcmp(PACKAGE_STRING, argv[1]) != 0)
+ {
+ char message[1024];
+ _snprintf(message, sizeof(message),
+ "It appears that the version of %s and %smidiproc are out "
+ "of sync. Please reinstall %s.\r\n\r\n"
+ "Server Version: %s\r\nClient Version: %s",
+ PACKAGE_NAME, PROGRAM_PREFIX, PACKAGE_NAME,
+ PACKAGE_STRING, argv[1]);
+ message[sizeof(message) - 1] = '\0';
+
+ MessageBox(NULL, TEXT(message),
+ TEXT(PACKAGE_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;
+ }
+
+ if (!InitPipes())
+ {
+ return EXIT_FAILURE;
+ }
+
+ if (!InitSDL())
+ {
+ return EXIT_FAILURE;
+ }
+
+ if (!ListenForever())
+ {
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
+
+#endif // #ifdef _WIN32
--- /dev/null
+++ b/midiproc/proto.h
@@ -1,0 +1,31 @@
+//
+// 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_REGISTER_SONG_ACK,
+ MIDIPIPE_PACKET_TYPE_SET_VOLUME,
+ MIDIPIPE_PACKET_TYPE_PLAY_SONG,
+ MIDIPIPE_PACKET_TYPE_STOP_SONG,
+ MIDIPIPE_PACKET_TYPE_SHUTDOWN
+} net_midipipe_packet_type_t;
+
+#endif
+
--- a/pkg/win32/GNUmakefile
+++ b/pkg/win32/GNUmakefile
@@ -43,14 +43,17 @@
./cp-with-libs --ldflags="$(LDFLAGS)" \
$(TOPLEVEL)/src/$(PROGRAM_PREFIX)setup.exe \
$@/$(PROGRAM_PREFIX)$*-setup.exe
+ ./cp-with-libs --ldflags="$(LDFLAGS)" \
+ $(TOPLEVEL)/midiproc/$(PROGRAM_PREFIX)midiproc.exe \
+ $@/$(PROGRAM_PREFIX)midiproc.exe
$(STRIP) $@/*.exe $@/*.dll
-
+
for f in $(DOC_FILES); do \
cp $(TOPLEVEL)/$$f $@/$$(basename $$f .md).txt; \
done
cp $(TOPLEVEL)/man/CMDLINE.$* $@/CMDLINE.txt
-
+
$(TOPLEVEL)/man/simplecpp -D_WIN32 -DPRECOMPILED \
-D$(shell echo $* | tr a-z A-Z) \
< $(TOPLEVEL)/man/INSTALL.template \
@@ -59,4 +62,3 @@
clean:
rm -f $(ZIPS)
rm -rf staging-*
-
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -68,6 +68,7 @@
i_input.c i_input.h \
i_joystick.c i_joystick.h \
i_swap.h \
+i_midipipe.c i_midipipe.h \
i_oplmusic.c \
i_pcsound.c \
i_sdlmusic.c \
--- /dev/null
+++ b/src/i_midipipe.c
@@ -1,0 +1,475 @@
+//
+// Copyright(C) 2013 James Haley et al.
+// Copyright(C) 2017 Alex Mayfield
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// Client Interface to Midi Server
+//
+
+#if _WIN32
+
+#include <stdlib.h>
+#include <sys/stat.h>
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+#include "i_midipipe.h"
+
+#include "config.h"
+#include "i_sound.h"
+#include "i_timer.h"
+#include "m_misc.h"
+#include "net_packet.h"
+
+#include "../midiproc/proto.h"
+
+#if defined(_DEBUG)
+#define DEBUGOUT(s) puts(s)
+#else
+#define DEBUGOUT(s)
+#endif
+
+//=============================================================================
+//
+// Public Data
+//
+
+// True if the midi proces was initialized at least once and has not been
+// explicitly shut down. This remains true if the server is momentarily
+// unreachable.
+boolean midi_server_initialized;
+
+// True if the current track is being handled via the MIDI server.
+boolean midi_server_registered;
+
+//=============================================================================
+//
+// Data
+//
+
+#define MIDIPIPE_MAX_WAIT 500 // 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';
+}
+
+//=============================================================================
+//
+// 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);
+
+ if (!ok)
+ {
+ DEBUGOUT("I_MidiPipe_RegisterSong failed");
+ return false;
+ }
+
+ packet = NET_NewPacket(2);
+ NET_WriteInt16(packet, MIDIPIPE_PACKET_TYPE_REGISTER_SONG_ACK);
+ ok = ExpectPipe(packet);
+ NET_FreePacket(packet);
+
+ if (!ok)
+ {
+ DEBUGOUT("I_MidiPipe_RegisterSong ack failed");
+ return false;
+ }
+
+ midi_server_registered = true;
+
+ DEBUGOUT("I_MidiPipe_RegisterSong succeeded");
+ return true;
+}
+
+//
+// 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);
+
+ 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);
+
+ 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);
+
+ midi_server_registered = false;
+
+ 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);
+
+ 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 snd_samplerate_buf[8];
+ SECURITY_ATTRIBUTES sec_attrs;
+ PROCESS_INFORMATION proc_info;
+ STARTUPINFO startup_info;
+ BOOL ok;
+
+ if (!UsingNativeMidi() || strlen(snd_musiccmd) > 0)
+ {
+ // If we're not using native MIDI, or if we're playing music through
+ // an exteranl program, we don't need to start the server.
+ return false;
+ }
+
+ // Get directory name
+ memset(dirname, 0, sizeof(dirname));
+ dirname_len = GetModuleFileName(NULL, dirname, MAX_PATH);
+ if (dirname_len == 0)
+ {
+ return false;
+ }
+ RemoveFileSpec(dirname, dirname_len);
+
+ // Define the module.
+ module = PROGRAM_PREFIX "midiproc.exe";
+
+ // Define the command line. Version and Sample Rate follow the
+ // executable name.
+ M_snprintf(snd_samplerate_buf, sizeof(snd_samplerate_buf),
+ "%d", snd_samplerate);
+ cmdline = M_StringJoin(module, " \"" PACKAGE_STRING "\"", " ",
+ snd_samplerate_buf, NULL);
+
+ // 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;
+ }
+
+ // Launch the subprocess
+ memset(&proc_info, 0, sizeof(proc_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;
+
+ ok = CreateProcess(TEXT(module), TEXT(cmdline), NULL, NULL, TRUE,
+ 0, NULL, dirname, &startup_info, &proc_info);
+
+ if (!ok)
+ {
+ FreePipes();
+ free(cmdline);
+
+ return false;
+ }
+
+ // Since the server has these handles, we don't need them anymore.
+ CloseHandle(midi_process_in_reader);
+ midi_process_in_reader = NULL;
+ CloseHandle(midi_process_out_writer);
+ midi_process_out_writer = NULL;
+
+ midi_server_initialized = true;
+ return true;
+}
+
+#endif
+
--- /dev/null
+++ b/src/i_midipipe.h
@@ -1,0 +1,42 @@
+//
+// 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_SetVolume(int vol);
+void I_MidiPipe_PlaySong(int loops);
+void I_MidiPipe_StopSong();
+void I_MidiPipe_ShutdownServer();
+
+boolean I_MidiPipe_InitServer();
+
+#endif
+
+#endif
+
--- a/src/i_sdlmusic.c
+++ b/src/i_sdlmusic.c
@@ -25,6 +25,8 @@
#include "SDL.h"
#include "SDL_mixer.h"
+#include "i_midipipe.h"
+
#include "config.h"
#include "doomtype.h"
#include "memio.h"
@@ -876,6 +878,9 @@
{
if (music_initialized)
{
+#if defined(_WIN32)
+ I_MidiPipe_ShutdownServer();
+#endif
Mix_HaltMusic();
music_initialized = false;
@@ -972,6 +977,11 @@
LoadSubstituteConfigs();
}
+#if defined(_WIN32)
+ // [AM] Start up midiproc to handle playing MIDI music.
+ I_MidiPipe_InitServer();
+#endif
+
return music_initialized;
}
@@ -993,6 +1003,9 @@
vol = (current_music_volume * MIX_MAX_VOLUME) / 127;
}
+#if defined(_WIN32)
+ I_MidiPipe_SetVolume(vol);
+#endif
Mix_VolumeMusic(vol);
}
@@ -1017,7 +1030,11 @@
return;
}
+#if defined(_WIN32)
+ if (handle == NULL && !midi_server_registered)
+#else
if (handle == NULL)
+#endif
{
return;
}
@@ -1044,7 +1061,18 @@
SDL_UnlockAudio();
}
+#if defined(_WIN32)
+ if (midi_server_registered)
+ {
+ I_MidiPipe_PlaySong(loops);
+ }
+ else
+ {
+ Mix_PlayMusic(current_track_music, loops);
+ }
+#else
Mix_PlayMusic(current_track_music, loops);
+#endif
}
static void I_SDL_PauseSong(void)
@@ -1078,7 +1106,19 @@
return;
}
+#if defined(_WIN32)
+ if (midi_server_registered)
+ {
+ I_MidiPipe_StopSong();
+ }
+ else
+ {
+ Mix_HaltMusic();
+ }
+#else
Mix_HaltMusic();
+#endif
+
playing_substitute = false;
current_track_music = NULL;
}
@@ -1189,14 +1229,35 @@
// by now, but Mix_SetMusicCMD() only works with Mix_LoadMUS(), so
// we have to generate a temporary file.
+#if defined(_WIN32)
+ // [AM] If we do not have an external music command defined, play
+ // music with the MIDI server.
+ if (midi_server_initialized)
+ {
+ music = NULL;
+ if (!I_MidiPipe_RegisterSong(filename))
+ {
+ fprintf(stderr, "Error loading midi: %s\n",
+ "Could not communicate with midiproc.");
+ }
+ }
+ else
+ {
+ music = Mix_LoadMUS(filename);
+ if (music == NULL)
+ {
+ // Failed to load
+ fprintf(stderr, "Error loading midi: %s\n", Mix_GetError());
+ }
+ }
+#else
music = Mix_LoadMUS(filename);
-
if (music == NULL)
{
// Failed to load
-
fprintf(stderr, "Error loading midi: %s\n", Mix_GetError());
}
+#endif
// Remove the temporary MIDI file; however, when using an external
// MIDI program we can't delete the file. Otherwise, the program