shithub: choc

Download patch

ref: 6eed17381c47788931eacfef23d3bea203d09dea
parent: 5ad017e1713e0fccf33706757b57c17ccd700882
parent: ecd6c015cdbf939104fb507d3fbf2e9de44e2f87
author: Simon Howard <fraggle@soulsphere.org>
date: Wed Nov 10 05:00:05 EST 2021

Merge pull request #1399 from rfomin/win_midi

Replace midiproc with native Windows MIDI support

diff: cannot open a/midiproc//null: file does not exist: 'a/midiproc//null'
--- 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"
@@ -53,6 +53,8 @@
 
 static boolean sdl_was_initialized = false;
 
+static boolean win_midi_stream_opened = false;
+
 static boolean musicpaused = false;
 static int current_music_volume;
 
@@ -154,7 +156,11 @@
     if (music_initialized)
     {
 #if defined(_WIN32)
-        I_MidiPipe_ShutdownServer();
+        if (win_midi_stream_opened)
+        {
+            I_WIN_ShutdownMusic();
+            win_midi_stream_opened = false;
+        }
 #endif
         Mix_HaltMusic();
         music_initialized = false;
@@ -232,11 +238,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();
+        win_midi_stream_opened = I_WIN_InitMusic();
     }
 #endif
 
@@ -262,7 +267,7 @@
     }
 
 #if defined(_WIN32)
-    I_MidiPipe_SetVolume(vol);
+    I_WIN_SetMusicVolume(vol);
 #endif
     Mix_VolumeMusic(vol);
 }
@@ -288,7 +293,7 @@
         return;
     }
 
-    if (handle == NULL && !midi_server_registered)
+    if (handle == NULL && !win_midi_stream_opened)
     {
         return;
     }
@@ -303,9 +308,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 +351,9 @@
     }
 
 #if defined(_WIN32)
-    if (midi_server_registered)
+    if (win_midi_stream_opened)
     {
-        I_MidiPipe_StopSong();
+        I_WIN_StopSong();
     }
     else
 #endif
@@ -367,9 +372,9 @@
     }
 
 #if defined(_WIN32)
-    if (midi_server_registered)
+    if (win_midi_stream_opened)
     {
-        I_MidiPipe_UnregisterSong();
+        I_WIN_UnRegisterSong();
     }
     else
 #endif
@@ -445,16 +450,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,503 @@
+//
+// 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"
+
+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);
+
+    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 timediv;
+    MIDIPROPTEMPO tempo;
+    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;
+    }
+
+    timediv.cbStruct = sizeof(MIDIPROPTIMEDIV);
+    timediv.dwTimeDiv = MIDI_GetFileTimeDivision(file);
+    mmr = midiStreamProperty(hMidiStream, (LPBYTE)&timediv,
+                             MIDIPROP_SET | MIDIPROP_TIMEDIV);
+    if (mmr != MMSYSERR_NOERROR)
+    {
+        MidiErrorMessageBox(mmr);
+        return;
+    }
+
+    // Set initial tempo.
+    tempo.cbStruct = sizeof(MIDIPROPTIMEDIV);
+    tempo.dwTempo = 500000; // 120 bmp
+    mmr = midiStreamProperty(hMidiStream, (LPBYTE)&tempo,
+                             MIDIPROP_SET | MIDIPROP_TEMPO);
+    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);
+}
+
+#endif
--- /dev/null
+++ b/src/i_winmusic.h
@@ -1,0 +1,34 @@
+//
+// 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
+
+#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);
+
+#endif
+
+#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);