ref: e01bd185a58dabd79ccdb14d725f32a2800db577
parent: 5ad017e1713e0fccf33706757b57c17ccd700882
author: Roman Fomin <[email protected]>
date: Sat Oct 23 21:57:41 EDT 2021
implementation of native Windows MIDI
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -67,6 +67,6 @@
configure_file(src/setup-res.rc.in src/setup-res.rc)
configure_file(src/setup/setup-manifest.xml.in src/setup/setup-manifest.xml)
-foreach(SUBDIR textscreen midiproc opl pcsound src)
+foreach(SUBDIR textscreen opl pcsound src)
add_subdirectory("${SUBDIR}")
endforeach()
--- a/Makefile.am
+++ b/Makefile.am
@@ -46,7 +46,7 @@
MAINTAINERCLEANFILES = $(AUX_DIST_GEN)
-SUBDIRS=textscreen midiproc opl pcsound data src man
+SUBDIRS=textscreen opl pcsound data src man
DIST_SUBDIRS=pkg $(SUBDIRS)
--- a/configure.ac
+++ b/configure.ac
@@ -119,6 +119,13 @@
# Use the specific library CFLAGS/LIBS variables instead of setting them here.
CFLAGS="$CFLAGS $SDL_CFLAGS ${SAMPLERATE_CFLAGS:-} ${PNG_CFLAGS:-}"
LDFLAGS="$LDFLAGS $SDL_LIBS ${SAMPLERATE_LIBS:-} ${PNG_LIBS:-}"
+case "$host" in
+ *-*-mingw* | *-*-cygwin* | *-*-msvc* )
+ LDFLAGS="$LDFLAGS -lwinmm"
+ ;;
+ *)
+esac
+
AC_CHECK_LIB(m, log)
AC_CHECK_HEADERS([dirent.h linux/kd.h dev/isa/spkrio.h dev/speaker/speaker.h])
@@ -204,7 +211,6 @@
man/bash-completion/heretic.template
man/bash-completion/hexen.template
man/bash-completion/strife.template
-midiproc/Makefile
opl/Makefile
opl/examples/Makefile
pcsound/Makefile
--- a/midiproc/.gitignore
+++ /dev/null
@@ -1,6 +1,0 @@
-Makefile.in
-Makefile
-*.exe
-.deps
-tags
-TAGS
--- a/midiproc/CMakeLists.txt
+++ /dev/null
@@ -1,6 +1,0 @@
-if(WIN32)
- add_executable("${PROGRAM_PREFIX}midiproc" WIN32 buffer.c buffer.h main.c proto.h)
- target_include_directories("${PROGRAM_PREFIX}midiproc"
- PRIVATE "../src/" "${CMAKE_CURRENT_BINARY_DIR}/../")
- target_link_libraries("${PROGRAM_PREFIX}midiproc" SDL2::SDL2main SDL2::mixer)
-endif()
--- a/midiproc/Makefile.am
+++ /dev/null
@@ -1,13 +1,0 @@
-
-AM_CFLAGS=-I$(top_srcdir)/src @SDLMIXER_CFLAGS@
-
-EXTRA_DIST=CMakeLists.txt
-
-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
--- a/midiproc/buffer.c
+++ /dev/null
@@ -1,254 +1,0 @@
-//
-// 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>
-#include <stddef.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
--- a/midiproc/buffer.h
+++ /dev/null
@@ -1,54 +1,0 @@
-//
-// 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
-
--- a/midiproc/main.c
+++ /dev/null
@@ -1,463 +1,0 @@
-//
-// 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);
- 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(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;
- }
-
- // 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
--- a/midiproc/proto.h
+++ /dev/null
@@ -1,33 +1,0 @@
-//
-// 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
-
--- a/pkg/win32/GNUmakefile
+++ b/pkg/win32/GNUmakefile
@@ -42,9 +42,6 @@
LC_ALL=C ./cp-with-libs --ldflags="$(LDFLAGS)" \
$(TOPLEVEL)/src/$(PROGRAM_PREFIX)setup.exe \
$@/$(PROGRAM_PREFIX)$*-setup.exe
- LC_ALL=C ./cp-with-libs --ldflags="$(LDFLAGS)" \
- $(TOPLEVEL)/midiproc/$(PROGRAM_PREFIX)midiproc.exe \
- $@/$(PROGRAM_PREFIX)midiproc.exe
$(STRIP) $@/*.exe $@/*.dll
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -53,7 +53,6 @@
i_input.c i_input.h
i_joystick.c i_joystick.h
i_swap.h
- i_midipipe.c i_midipipe.h
i_musicpack.c
i_oplmusic.c
i_pcsound.c
@@ -63,6 +62,7 @@
i_timer.c i_timer.h
i_video.c i_video.h
i_videohr.c i_videohr.h
+ i_winmusic.c i_winmusic.h
midifile.c midifile.h
mus2mid.c mus2mid.h
m_bbox.c m_bbox.h
@@ -127,6 +127,9 @@
endif()
if(PNG_FOUND)
list(APPEND EXTRA_LIBS PNG::PNG)
+endif()
+if(WIN32)
+ list(APPEND EXTRA_LIBS winmm)
endif()
if(WIN32)
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -72,7 +72,6 @@
i_input.c i_input.h \
i_joystick.c i_joystick.h \
i_swap.h \
-i_midipipe.c i_midipipe.h \
i_musicpack.c \
i_oplmusic.c \
i_pcsound.c \
@@ -82,6 +81,7 @@
i_timer.c i_timer.h \
i_video.c i_video.h \
i_videohr.c i_videohr.h \
+i_winmusic.c i_winmusic.h \
midifile.c midifile.h \
mus2mid.c mus2mid.h \
m_bbox.c m_bbox.h \
--- a/src/i_midipipe.c
+++ /dev/null
@@ -1,505 +1,0 @@
-//
-// 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 = 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 params_buf[128];
- 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";
-
- // 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, " \"" PACKAGE_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);
-
- 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
-
--- a/src/i_midipipe.h
+++ /dev/null
@@ -1,49 +1,0 @@
-//
-// 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
-
--- a/src/i_musicpack.c
+++ b/src/i_musicpack.c
@@ -26,7 +26,6 @@
#include "SDL_mixer.h"
#include "i_glob.h"
-#include "i_midipipe.h"
#include "config.h"
#include "doomtype.h"
--- a/src/i_sdlmusic.c
+++ b/src/i_sdlmusic.c
@@ -25,7 +25,7 @@
#include "SDL.h"
#include "SDL_mixer.h"
-#include "i_midipipe.h"
+#include "i_winmusic.h"
#include "config.h"
#include "doomtype.h"
@@ -154,7 +154,10 @@
if (music_initialized)
{
#if defined(_WIN32)
- I_MidiPipe_ShutdownServer();
+ if (win_midi_stream_opened)
+ {
+ I_WIN_ShutdownMusic();
+ }
#endif
Mix_HaltMusic();
music_initialized = false;
@@ -232,11 +235,10 @@
}
#if defined(_WIN32)
- // [AM] Start up midiproc to handle playing MIDI music.
// Don't enable it for GUS, since it handles its own volume just fine.
if (snd_musicdevice != SNDDEVICE_GUS)
{
- I_MidiPipe_InitServer();
+ I_WIN_InitMusic();
}
#endif
@@ -262,7 +264,7 @@
}
#if defined(_WIN32)
- I_MidiPipe_SetVolume(vol);
+ I_WIN_SetMusicVolume(vol);
#endif
Mix_VolumeMusic(vol);
}
@@ -288,7 +290,7 @@
return;
}
- if (handle == NULL && !midi_server_registered)
+ if (handle == NULL && !win_midi_stream_opened)
{
return;
}
@@ -303,9 +305,9 @@
}
#if defined(_WIN32)
- if (midi_server_registered)
+ if (win_midi_stream_opened)
{
- I_MidiPipe_PlaySong(loops);
+ I_WIN_PlaySong(looping);
}
else
#endif
@@ -346,9 +348,9 @@
}
#if defined(_WIN32)
- if (midi_server_registered)
+ if (win_midi_stream_opened)
{
- I_MidiPipe_StopSong();
+ I_WIN_StopSong();
}
else
#endif
@@ -367,9 +369,9 @@
}
#if defined(_WIN32)
- if (midi_server_registered)
+ if (win_midi_stream_opened)
{
- I_MidiPipe_UnregisterSong();
+ I_WIN_UnRegisterSong();
}
else
#endif
@@ -445,16 +447,12 @@
// 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)
+ // If we do not have an external music command defined, play
+ // music with the Windows native MIDI.
+ if (win_midi_stream_opened)
{
music = NULL;
- if (!I_MidiPipe_RegisterSong(filename))
- {
- fprintf(stderr, "Error loading midi: %s\n",
- "Could not communicate with midiproc.");
- }
+ I_WIN_RegisterSong(filename);
}
else
#endif
--- /dev/null
+++ b/src/i_winmusic.c
@@ -1,0 +1,500 @@
+//
+// Copyright(C) 2021 Roman Fomin
+//
+// 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:
+// Windows native MIDI
+
+#ifdef _WIN32
+#define WIN32_LEAN_AND_MEAN
+
+#include <windows.h>
+#include <mmsystem.h>
+#include <stdio.h>
+
+#include "doomtype.h"
+#include "m_misc.h"
+#include "midifile.h"
+
+// Public data
+
+boolean win_midi_stream_opened = false;
+
+// Private data
+
+static HMIDISTRM hMidiStream;
+static HANDLE hBufferReturnEvent;
+static HANDLE hExitEvent;
+static HANDLE hPlayerThread;
+
+// This is a reduced Windows MIDIEVENT structure for MEVT_F_SHORT
+// type of events.
+
+typedef struct
+{
+ DWORD dwDeltaTime;
+ DWORD dwStreamID; // always 0
+ DWORD dwEvent;
+} native_event_t;
+
+typedef struct
+{
+ native_event_t *native_events;
+ int num_events;
+ int position;
+ boolean looping;
+} win_midi_song_t;
+
+static win_midi_song_t song;
+
+typedef struct
+{
+ midi_track_iter_t *iter;
+ int absolute_time;
+} win_midi_track_t;
+
+static float volume_factor = 1.0;
+
+// Save the last volume for each MIDI channel.
+
+static int channel_volume[MIDI_CHANNELS_PER_TRACK];
+
+// Macros for use with the Windows MIDIEVENT dwEvent field.
+
+#define MIDIEVENT_CHANNEL(x) (x & 0x0000000F)
+#define MIDIEVENT_TYPE(x) (x & 0x000000F0)
+#define MIDIEVENT_DATA1(x) ((x & 0x0000FF00) >> 8)
+#define MIDIEVENT_VOLUME(x) ((x & 0x007F0000) >> 16)
+
+// Maximum of 4 events in the buffer for faster volume updates.
+
+#define STREAM_MAX_EVENTS 4
+
+typedef struct
+{
+ native_event_t events[STREAM_MAX_EVENTS];
+ int num_events;
+ MIDIHDR MidiStreamHdr;
+} buffer_t;
+
+static buffer_t buffer;
+
+// Message box for midiStream errors.
+
+static void MidiErrorMessageBox(DWORD dwError)
+{
+ char szErrorBuf[MAXERRORLENGTH];
+ MMRESULT mmr;
+
+ mmr = midiOutGetErrorText(dwError, (LPSTR) szErrorBuf, MAXERRORLENGTH);
+ if (mmr == MMSYSERR_NOERROR)
+ {
+ MessageBox(NULL, szErrorBuf, "midiStream Error", MB_ICONEXCLAMATION);
+ }
+ else
+ {
+ fprintf(stderr, "Unknown midiStream error.\n");
+ }
+}
+
+// Fill the buffer with MIDI events, adjusting the volume as needed.
+
+static void FillBuffer(void)
+{
+ int i;
+
+ for (i = 0; i < STREAM_MAX_EVENTS; ++i)
+ {
+ native_event_t *event = &buffer.events[i];
+
+ if (song.position >= song.num_events)
+ {
+ if (song.looping)
+ {
+ song.position = 0;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ *event = song.native_events[song.position];
+
+ if (MIDIEVENT_TYPE(event->dwEvent) == MIDI_EVENT_CONTROLLER &&
+ MIDIEVENT_DATA1(event->dwEvent) == MIDI_CONTROLLER_MAIN_VOLUME)
+ {
+ int volume = MIDIEVENT_VOLUME(event->dwEvent);
+
+ channel_volume[MIDIEVENT_CHANNEL(event->dwEvent)] = volume;
+
+ volume *= volume_factor;
+
+ event->dwEvent = (event->dwEvent & 0xFF00FFFF) |
+ ((volume & 0x7F) << 16);
+ }
+
+ song.position++;
+ }
+
+ buffer.num_events = i;
+}
+
+// Queue MIDI events.
+
+static void StreamOut(void)
+{
+ MIDIHDR *hdr = &buffer.MidiStreamHdr;
+ MMRESULT mmr;
+
+ int num_events = buffer.num_events;
+
+ if (num_events == 0)
+ {
+ return;
+ }
+
+ hdr->lpData = (LPSTR)buffer.events;
+ hdr->dwBytesRecorded = num_events * sizeof(native_event_t);
+
+ mmr = midiStreamOut(hMidiStream, hdr, sizeof(MIDIHDR));
+ if (mmr != MMSYSERR_NOERROR)
+ {
+ MidiErrorMessageBox(mmr);
+ }
+}
+
+// midiStream callback.
+
+static void CALLBACK MidiStreamProc(HMIDIIN hMidi, UINT uMsg,
+ DWORD_PTR dwInstance, DWORD_PTR dwParam1,
+ DWORD_PTR dwParam2)
+{
+ if (uMsg == MOM_DONE)
+ {
+ SetEvent(hBufferReturnEvent);
+ }
+}
+
+// The Windows API documentation states: "Applications should not call any
+// multimedia functions from inside the callback function, as doing so can
+// cause a deadlock." We use thread to avoid possible deadlocks.
+
+static DWORD WINAPI PlayerProc(void)
+{
+ HANDLE events[2] = { hBufferReturnEvent, hExitEvent };
+
+ while (1)
+ {
+ switch (WaitForMultipleObjects(2, events, FALSE, INFINITE))
+ {
+ case WAIT_OBJECT_0:
+ FillBuffer();
+ StreamOut();
+ break;
+
+ case WAIT_OBJECT_0 + 1:
+ return 0;
+ }
+ }
+ return 0;
+}
+
+// Convert a multi-track MIDI file to an array of Windows MIDIEVENT structures.
+
+static void MIDItoStream(midi_file_t *file)
+{
+ int i;
+
+ int num_tracks = MIDI_NumTracks(file);
+ win_midi_track_t *tracks = malloc(num_tracks * sizeof(win_midi_track_t));
+
+ int current_time = 0;
+
+ for (i = 0; i < num_tracks; ++i)
+ {
+ tracks[i].iter = MIDI_IterateTrack(file, i);
+ tracks[i].absolute_time = 0;
+ }
+
+ song.native_events = calloc(MIDI_NumEvents(file), sizeof(native_event_t));
+
+ while (1)
+ {
+ midi_event_t *event;
+ DWORD data = 0;
+ int min_time = INT_MAX;
+ int idx = -1;
+
+ // Look for an event with a minimal delta time.
+ for (i = 0; i < num_tracks; ++i)
+ {
+ int time = 0;
+
+ if (tracks[i].iter == NULL)
+ {
+ continue;
+ }
+
+ time = tracks[i].absolute_time + MIDI_GetDeltaTime(tracks[i].iter);
+
+ if (time < min_time)
+ {
+ min_time = time;
+ idx = i;
+ }
+ }
+
+ // No more MIDI events left, end the loop.
+ if (idx == -1)
+ {
+ break;
+ }
+
+ tracks[idx].absolute_time = min_time;
+
+ if (!MIDI_GetNextEvent(tracks[idx].iter, &event))
+ {
+ tracks[idx].iter = NULL;
+ continue;
+ }
+
+ switch ((int)event->event_type)
+ {
+ case MIDI_EVENT_META:
+ if (event->data.meta.type == MIDI_META_SET_TEMPO)
+ {
+ data = event->data.meta.data[2] |
+ (event->data.meta.data[1] << 8) |
+ (event->data.meta.data[0] << 16) |
+ (MEVT_TEMPO << 24);
+ }
+ break;
+
+ case MIDI_EVENT_NOTE_OFF:
+ case MIDI_EVENT_NOTE_ON:
+ case MIDI_EVENT_AFTERTOUCH:
+ case MIDI_EVENT_CONTROLLER:
+ case MIDI_EVENT_PITCH_BEND:
+ data = event->event_type |
+ event->data.channel.channel |
+ (event->data.channel.param1 << 8) |
+ (event->data.channel.param2 << 16) |
+ (MEVT_SHORTMSG << 24);
+ break;
+
+ case MIDI_EVENT_PROGRAM_CHANGE:
+ case MIDI_EVENT_CHAN_AFTERTOUCH:
+ data = event->event_type |
+ event->data.channel.channel |
+ (event->data.channel.param1 << 8) |
+ (0 << 16) |
+ (MEVT_SHORTMSG << 24);
+ break;
+ }
+
+ if (data)
+ {
+ native_event_t *native_event = &song.native_events[song.num_events];
+
+ native_event->dwDeltaTime = min_time - current_time;
+ native_event->dwStreamID = 0;
+ native_event->dwEvent = data;
+
+ song.num_events++;
+ current_time = min_time;
+ }
+ }
+
+ if (tracks)
+ {
+ free(tracks);
+ }
+}
+
+boolean I_WIN_InitMusic(void)
+{
+ UINT MidiDevice = MIDI_MAPPER;
+ MIDIHDR *hdr = &buffer.MidiStreamHdr;
+ MMRESULT mmr;
+
+ mmr = midiStreamOpen(&hMidiStream, &MidiDevice, (DWORD)1,
+ (DWORD_PTR)MidiStreamProc, (DWORD_PTR)NULL,
+ CALLBACK_FUNCTION);
+ if (mmr != MMSYSERR_NOERROR)
+ {
+ MidiErrorMessageBox(mmr);
+ return false;
+ }
+
+ hdr->lpData = (LPSTR)buffer.events;
+ hdr->dwBytesRecorded = 0;
+ hdr->dwBufferLength = STREAM_MAX_EVENTS * sizeof(native_event_t);
+ hdr->dwFlags = 0;
+ hdr->dwOffset = 0;
+
+ mmr = midiOutPrepareHeader((HMIDIOUT)hMidiStream, hdr, sizeof(MIDIHDR));
+ if (mmr != MMSYSERR_NOERROR)
+ {
+ MidiErrorMessageBox(mmr);
+ return false;
+ }
+
+ hBufferReturnEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+ hExitEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+
+ win_midi_stream_opened = true;
+ return true;
+}
+
+void I_WIN_SetMusicVolume(int volume)
+{
+ int i;
+
+ volume_factor = (float)volume / 127;
+
+ // Send MIDI controller events to adjust the volume.
+ for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
+ {
+ int value = channel_volume[i] * volume_factor;
+
+ DWORD msg = MIDI_EVENT_CONTROLLER | i |
+ (MIDI_CONTROLLER_MAIN_VOLUME << 8) |
+ (value << 16);
+
+ midiOutShortMsg((HMIDIOUT)hMidiStream, msg);
+ }
+}
+
+void I_WIN_StopSong(void)
+{
+ MMRESULT mmr;
+
+ if (hPlayerThread)
+ {
+ SetEvent(hExitEvent);
+ WaitForSingleObject(hPlayerThread, INFINITE);
+
+ CloseHandle(hPlayerThread);
+ hPlayerThread = NULL;
+ }
+
+ mmr = midiStreamStop(hMidiStream);
+ if (mmr != MMSYSERR_NOERROR)
+ {
+ MidiErrorMessageBox(mmr);
+ }
+ mmr = midiOutReset((HMIDIOUT)hMidiStream);
+ if (mmr != MMSYSERR_NOERROR)
+ {
+ MidiErrorMessageBox(mmr);
+ }
+}
+
+void I_WIN_PlaySong(boolean looping)
+{
+ MMRESULT mmr;
+
+ song.looping = looping;
+
+ hPlayerThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PlayerProc,
+ 0, 0, 0);
+ SetThreadPriority(hPlayerThread, THREAD_PRIORITY_TIME_CRITICAL);
+
+ mmr = midiStreamRestart(hMidiStream);
+ if (mmr != MMSYSERR_NOERROR)
+ {
+ MidiErrorMessageBox(mmr);
+ }
+}
+
+void I_WIN_RegisterSong(char *filename)
+{
+ int i;
+ midi_file_t *file;
+ MIDIPROPTIMEDIV prop;
+ MMRESULT mmr;
+
+ file = MIDI_LoadFile(filename);
+
+ if (file == NULL)
+ {
+ fprintf(stderr, "I_WIN_RegisterSong: Failed to load MID.\n");
+ return;
+ }
+
+ // Initialize channels volume.
+ for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
+ {
+ channel_volume[i] = 100;
+ }
+
+ prop.cbStruct = sizeof(MIDIPROPTIMEDIV);
+ prop.dwTimeDiv = MIDI_GetFileTimeDivision(file);
+ mmr = midiStreamProperty(hMidiStream, (LPBYTE)&prop,
+ MIDIPROP_SET | MIDIPROP_TIMEDIV);
+ if (mmr != MMSYSERR_NOERROR)
+ {
+ MidiErrorMessageBox(mmr);
+ return;
+ }
+
+ MIDItoStream(file);
+
+ MIDI_FreeFile(file);
+
+ ResetEvent(hBufferReturnEvent);
+ ResetEvent(hExitEvent);
+
+ FillBuffer();
+ StreamOut();
+}
+
+void I_WIN_UnRegisterSong(void)
+{
+ if (song.native_events)
+ {
+ free(song.native_events);
+ song.native_events = NULL;
+ }
+ song.num_events = 0;
+ song.position = 0;
+}
+
+void I_WIN_ShutdownMusic(void)
+{
+ MIDIHDR *hdr = &buffer.MidiStreamHdr;
+ MMRESULT mmr;
+
+ I_WIN_StopSong();
+
+ mmr = midiOutUnprepareHeader((HMIDIOUT)hMidiStream, hdr, sizeof(MIDIHDR));
+ if (mmr != MMSYSERR_NOERROR)
+ {
+ MidiErrorMessageBox(mmr);
+ }
+
+ mmr = midiStreamClose(hMidiStream);
+ if (mmr != MMSYSERR_NOERROR)
+ {
+ MidiErrorMessageBox(mmr);
+ }
+
+ hMidiStream = NULL;
+
+ CloseHandle(hBufferReturnEvent);
+ CloseHandle(hExitEvent);
+
+ win_midi_stream_opened = false;
+}
+
+#endif
--- /dev/null
+++ b/src/i_winmusic.h
@@ -1,0 +1,32 @@
+//
+// Copyright(C) 2021 Roman Fomin
+//
+// 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:
+// Windows native MIDI
+
+#ifndef __I_WINMUSIC__
+#define __I_WINMUSIC__
+
+#include "doomtype.h"
+
+boolean I_WIN_InitMusic(void);
+void I_WIN_PlaySong(boolean looping);
+void I_WIN_StopSong(void);
+void I_WIN_SetMusicVolume(int volume);
+void I_WIN_RegisterSong(char* filename);
+void I_WIN_UnRegisterSong(void);
+void I_WIN_ShutdownMusic(void);
+
+extern boolean win_midi_stream_opened;
+
+#endif
--- a/src/midifile.c
+++ b/src/midifile.c
@@ -637,6 +637,21 @@
return file->num_tracks;
}
+// Get the number of events in a MIDI file.
+
+unsigned int MIDI_NumEvents(midi_file_t *file)
+{
+ int i;
+ unsigned int num_events = 0;
+
+ for (i = 0; i < file->num_tracks; ++i)
+ {
+ num_events += file->tracks[i].num_events;
+ }
+
+ return num_events;
+}
+
// Start iterating over the events in a track.
midi_track_iter_t *MIDI_IterateTrack(midi_file_t *file, unsigned int track)
--- a/src/midifile.h
+++ b/src/midifile.h
@@ -145,6 +145,10 @@
unsigned int MIDI_NumTracks(midi_file_t *file);
+// Get the number of events in a MIDI file.
+
+unsigned int MIDI_NumEvents(midi_file_t *file);
+
// Start iterating over the events in a track.
midi_track_iter_t *MIDI_IterateTrack(midi_file_t *file, unsigned int track_num);