foxygit / doom Log in
commit 1162b2c65c5bcff51e2d13aa2cfc2051529cfe68
Merge: 63ff4879 f151517b
Author:     Simon Howard <fraggle@gmail.com>
AuthorDate: Fri Apr 30 20:53:31 2010 +0000
Commit:     Simon Howard <fraggle@gmail.com>
CommitDate: Fri Apr 30 20:53:31 2010 +0000

    Merge from trunk.

    Subversion-branch: /branches/raven-branch
    Subversion-revision: 1924

 .lvimrc                  |    3 +-
 BUGS                     |   15 -
 ChangeLog                |  809 +++++++++++++++++++++++++
 Makefile.am              |    5 +-
 NEWS                     |    4 +-
 README.OPL               |  107 ++++
 TODO                     |   20 +-
 codeblocks/config.h      |    6 +-
 codeblocks/game-res.rc   |   10 +-
 codeblocks/setup-res.rc  |    8 +-
 configure.in             |   12 +-
 man/manpage.template     |   11 +
 msvc/config.h            |    6 +-
 msvc/win32.rc            |   10 +-
 opl/.gitignore           |    7 +
 opl/Makefile.am          |   19 +
 opl/examples/.gitignore  |    7 +
 opl/examples/Makefile.am |    8 +
 opl/examples/droplay.c   |  217 +++++++
 opl/fmopl.c              | 1155 ++++++++++++++++++++++++++++++++++++
 opl/fmopl.h              |  167 ++++++
 opl/ioperm_sys.c         |  361 ++++++++++++
 opl/ioperm_sys.h         |   36 ++
 opl/opl.c                |  466 +++++++++++++++
 opl/opl.h                |  137 +++++
 opl/opl_internal.h       |   64 ++
 opl/opl_linux.c          |  110 ++++
 opl/opl_obsd.c           |  125 ++++
 opl/opl_queue.c          |  280 +++++++++
 opl/opl_queue.h          |   45 ++
 opl/opl_sdl.c            |  452 ++++++++++++++
 opl/opl_timer.c          |  251 ++++++++
 opl/opl_timer.h          |   42 ++
 opl/opl_win32.c          |  172 ++++++
 pkg/win32/GNUmakefile    |    2 +
 rpm.spec.in              |    1 +
 src/.gitignore           |    2 +-
 src/Makefile.am          |    9 +-
 src/doom/p_setup.c       |   96 ++-
 src/doom/s_sound.c       |    9 +
 src/i_oplmusic.c         | 1464 ++++++++++++++++++++++++++++++++++++++++++++++
 src/i_sdlmusic.c         |    2 -
 src/i_sound.c            |    4 +-
 src/midifile.c           |  814 ++++++++++++++++++++++++++
 src/midifile.h           |  175 ++++++
 src/setup/sound.c        |   36 +-
 textscreen/Makefile.am   |    6 +-
 47 files changed, 7699 insertions(+), 68 deletions(-)

diff --cc Makefile.am
index e331d204,13e8bddb..97a18507
--- a/Makefile.am
+++ b/Makefile.am
@@@ -57,7 -50,7 +58,9 @@@ EXTRA_DIST
  MAINTAINERCLEANFILES =  $(AUX_DIST_GEN)

  docdir=$(prefix)/share/doc/@PACKAGE@
- SUBDIRS=wince textscreen pcsound src man
 -SUBDIRS=wince textscreen opl pcsound src man setup
++
++SUBDIRS=wince textscreen opl pcsound src man
++
  DIST_SUBDIRS=pkg $(SUBDIRS)

  if HAVE_PYTHON
diff --cc configure.in
index e3287d00,258822ce..f87ca9e8
--- a/configure.in
+++ b/configure.in
@@@ -137,7 -133,10 +143,9 @@@ AC_DEFUN([AC_DATAROOTDIR_CHECKED]

  AC_OUTPUT([
  Makefile
 -rpm.spec
  man/Makefile
+ opl/Makefile
+ opl/examples/Makefile
  pcsound/Makefile
  pkg/Makefile
  pkg/config.make
diff --cc src/.gitignore
index e560093f,2c4a3c48..aa8a4c05
--- a/src/.gitignore
+++ b/src/.gitignore
@@@ -3,11 -3,8 +3,11 @@@ Makefile.i
  .deps
  *.rc
  chocolate-doom
 +chocolate-heretic
 +chocolate-hexen
  chocolate-server
 +chocolate-setup
- doom-screensaver.desktop
  *.exe
+ *.desktop
  tags
  TAGS
diff --cc src/Makefile.am
index 8019dc82,8b827131..54450e78
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@@ -1,27 -1,9 +1,28 @@@

 +SUBDIRS=doom heretic hexen setup
 +
  gamesdir = $(prefix)/games

 -games_PROGRAMS = chocolate-doom chocolate-server
 +games_PROGRAMS = @PROGRAM_PREFIX@doom     \
 +                 @PROGRAM_PREFIX@heretic  \
 +                 @PROGRAM_PREFIX@hexen    \
 +                 @PROGRAM_PREFIX@server   \
 +                 @PROGRAM_PREFIX@setup
 +
 +AM_CFLAGS = -I$(top_builddir)/textscreen            \
++            -I$(top_builddir)/opl                   \
 +            -I$(top_builddir)/pcsound               \
 +            @SDLMIXER_CFLAGS@ @SDLNET_CFLAGS@

 -AM_CFLAGS = -I../opl -I../textscreen -I../pcsound @SDLMIXER_CFLAGS@ @SDLNET_CFLAGS@
 +# Common source files used by absolutely everything:
 +
 +COMMON_SOURCE_FILES=\
 +i_main.c                                   \
 +i_system.c           i_system.h            \
 +m_argv.c             m_argv.h              \
 +m_misc.c             m_misc.h
 +
 +# Dedicated server (chocolate-server):

  DEDSERV_FILES=\
  d_dedicated.c                              \
@@@ -115,70 -155,30 +116,73 @@@ FEATURE_SOUND_SOURCE_FILES
  i_pcsound.c                                \
  i_sdlsound.c                               \
  i_sdlmusic.c                               \
+ i_oplmusic.c                               \
+ midifile.c           midifile.h            \
  mus2mid.c            mus2mid.h

 -SOURCE_FILES = $(MAIN_SOURCE_FILES)                \
 -               $(FEATURE_DEHACKED_SOURCE_FILES)    \
 -               $(FEATURE_MULTIPLAYER_SOURCE_FILES) \
 +# Some games support dehacked patches, some don't:
 +
 +SOURCE_FILES = $(COMMON_SOURCE_FILES)              \
 +               $(GAME_SOURCE_FILES)                \
                 $(FEATURE_WAD_MERGE_SOURCE_FILES)   \
                 $(FEATURE_SOUND_SOURCE_FILES)

 +SOURCE_FILES_WITH_DEH = $(SOURCE_FILES)                    \
 +                        $(FEATURE_DEHACKED_SOURCE_FILES)
 +
 +EXTRA_LIBS =                                               \
 +               $(top_builddir)/wince/libc_wince.a          \
 +               $(top_builddir)/textscreen/libtextscreen.a  \
 +               $(top_builddir)/pcsound/libpcsound.a        \
++               $(top_builddir)/opl/libopl.a                \
 +               @LDFLAGS@                                   \
 +               @SDL_LIBS@                                  \
 +               @SDLMIXER_LIBS@                             \
-                @SDLNET_LIBS@
++               @SDLNET_LIBS@
 +
  if HAVE_WINDRES
 -chocolate_doom_SOURCES=$(SOURCE_FILES) resource.rc
 +@PROGRAM_PREFIX@doom_SOURCES=$(SOURCE_FILES_WITH_DEH) resource.rc
  else
 -chocolate_doom_SOURCES=$(SOURCE_FILES)
 +@PROGRAM_PREFIX@doom_SOURCES=$(SOURCE_FILES_WITH_DEH)
  endif

 -chocolate_doom_LDADD =                               \
 -                       ../wince/libc_wince.a         \
 -                       ../textscreen/libtextscreen.a \
 -                       ../pcsound/libpcsound.a       \
 -                       ../opl/libopl.a               \
 -                       @LDFLAGS@                     \
 -                       @SDLMIXER_LIBS@               \
 -                       @SDLNET_LIBS@
 +@PROGRAM_PREFIX@doom_LDADD = doom/libdoom.a $(EXTRA_LIBS)
 +
 +if HAVE_WINDRES
 +@PROGRAM_PREFIX@heretic_SOURCES=$(SOURCE_FILES_WITH_DEH) resource.rc
 +else
 +@PROGRAM_PREFIX@heretic_SOURCES=$(SOURCE_FILES_WITH_DEH)
 +endif
 +
 +@PROGRAM_PREFIX@heretic_LDADD = heretic/libheretic.a $(EXTRA_LIBS)
 +
 +if HAVE_WINDRES
 +@PROGRAM_PREFIX@hexen_SOURCES=$(SOURCE_FILES) resource.rc
 +else
 +@PROGRAM_PREFIX@hexen_SOURCES=$(SOURCE_FILES)
 +endif
 +
 +@PROGRAM_PREFIX@hexen_LDADD = hexen/libhexen.a $(EXTRA_LIBS)
 +
 +# Source files needed for chocolate-setup:
 +
 +SETUP_FILES=                               \
 +deh_str.c            deh_str.h             \
 +d_mode.c             d_mode.h              \
 +d_iwad.c             d_iwad.h              \
 +m_config.c           m_config.h            \
 +m_controls.c         m_controls.h          \
 +z_native.c           z_zone.h
 +
 +if HAVE_WINDRES
 +@PROGRAM_PREFIX@setup_SOURCES=$(SETUP_FILES) $(COMMON_SOURCE_FILES) setup-res.rc
 +else
 +@PROGRAM_PREFIX@setup_SOURCES=$(SETUP_FILES) $(COMMON_SOURCE_FILES)
 +endif
 +@PROGRAM_PREFIX@setup_LDADD = setup/libsetup.a                             \
 +                        $(top_builddir)/wince/libc_wince.a           \
 +                        $(top_builddir)/textscreen/libtextscreen.a   \
 +                        @LDFLAGS@ @SDL_LIBS@ @SDLNET_LIBS@

  EXTRA_DIST =                        \
          icon.c                      \
diff --cc src/i_oplmusic.c
index 00000000,e8d65d6d..e7a42e83
mode 000000,100644..100644
--- a/src/i_oplmusic.c
+++ b/src/i_oplmusic.c
@@@ -1,0 -1,1465 +1,1464 @@@
+ // Emacs style mode select   -*- C++ -*-
+ //-----------------------------------------------------------------------------
+ //
+ // Copyright(C) 1993-1996 Id Software, Inc.
+ // Copyright(C) 2005 Simon Howard
+ //
+ // This program is free software; you can redistribute it and/or
+ // modify it under the terms of the GNU General Public License
+ // as published by the Free Software Foundation; either version 2
+ // of the License, or (at your option) any later version.
+ //
+ // This program is distributed in the hope that it will be useful,
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ // GNU General Public License for more details.
+ //
+ // You should have received a copy of the GNU General Public License
+ // along with this program; if not, write to the Free Software
+ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ // 02111-1307, USA.
+ //
+ // DESCRIPTION:
+ //	System interface for music.
+ //
+ //-----------------------------------------------------------------------------
+
+
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <string.h>
+
 -#include "doomdef.h"
+ #include "memio.h"
+ #include "mus2mid.h"
+
+ #include "deh_main.h"
++#include "i_sound.h"
+ #include "i_swap.h"
+ #include "m_misc.h"
 -#include "s_sound.h"
+ #include "w_wad.h"
+ #include "z_zone.h"
+
+ #include "opl.h"
+ #include "midifile.h"
+
+ // #define OPL_MIDI_DEBUG
+
+ #define MAXMIDLENGTH (96 * 1024)
+ #define GENMIDI_NUM_INSTRS  128
+
+ #define GENMIDI_HEADER          "#OPL_II#"
+ #define GENMIDI_FLAG_FIXED      0x0001         /* fixed pitch */
+ #define GENMIDI_FLAG_2VOICE     0x0004         /* double voice (OPL3) */
+
+ typedef struct
+ {
+     byte tremolo;
+     byte attack;
+     byte sustain;
+     byte waveform;
+     byte scale;
+     byte level;
+ } PACKEDATTR genmidi_op_t;
+
+ typedef struct
+ {
+     genmidi_op_t modulator;
+     byte feedback;
+     genmidi_op_t carrier;
+     byte unused;
+     short base_note_offset;
+ } PACKEDATTR genmidi_voice_t;
+
+ typedef struct
+ {
+     unsigned short flags;
+     byte fine_tuning;
+     byte fixed_note;
+
+     genmidi_voice_t voices[2];
+ } PACKEDATTR genmidi_instr_t;
+
+ // Data associated with a channel of a track that is currently playing.
+
+ typedef struct
+ {
+     // The instrument currently used for this track.
+
+     genmidi_instr_t *instrument;
+
+     // Volume level
+
+     int volume;
+
+     // Pitch bend value:
+
+     int bend;
+
+ } opl_channel_data_t;
+
+ // Data associated with a track that is currently playing.
+
+ typedef struct
+ {
+     // Data for each channel.
+
+     opl_channel_data_t channels[MIDI_CHANNELS_PER_TRACK];
+
+     // Track iterator used to read new events.
+
+     midi_track_iter_t *iter;
+
+     // Tempo control variables
+
+     unsigned int ticks_per_beat;
+     unsigned int ms_per_beat;
+ } opl_track_data_t;
+
+ typedef struct opl_voice_s opl_voice_t;
+
+ struct opl_voice_s
+ {
+     // Index of this voice:
+     int index;
+
+     // The operators used by this voice:
+     int op1, op2;
+
+     // Currently-loaded instrument data
+     genmidi_instr_t *current_instr;
+
+     // The voice number in the instrument to use.
+     // This is normally set to zero; if this is a double voice
+     // instrument, it may be one.
+     unsigned int current_instr_voice;
+
+     // The channel currently using this voice.
+     opl_channel_data_t *channel;
+
+     // The midi key that this voice is playing.
+     unsigned int key;
+
+     // The note being played.  This is normally the same as
+     // the key, but if the instrument is a fixed pitch
+     // instrument, it is different.
+     unsigned int note;
+
+     // The frequency value being used.
+     unsigned int freq;
+
+     // The volume of the note being played on this channel.
+     unsigned int note_volume;
+
+     // The current volume (register value) that has been set for this channel.
+     unsigned int reg_volume;
+
+     // Next in linked list; a voice is always either in the
+     // free list or the allocated list.
+     opl_voice_t *next;
+ };
+
+ // Operators used by the different voices.
+
+ static const int voice_operators[2][OPL_NUM_VOICES] = {
+     { 0x00, 0x01, 0x02, 0x08, 0x09, 0x0a, 0x10, 0x11, 0x12 },
+     { 0x03, 0x04, 0x05, 0x0b, 0x0c, 0x0d, 0x13, 0x14, 0x15 }
+ };
+
+ // Frequency values to use for each note.
+
+ static const unsigned short frequency_curve[] = {
+
+     0x133, 0x133, 0x134, 0x134, 0x135, 0x136, 0x136, 0x137,   // -1
+     0x137, 0x138, 0x138, 0x139, 0x139, 0x13a, 0x13b, 0x13b,
+     0x13c, 0x13c, 0x13d, 0x13d, 0x13e, 0x13f, 0x13f, 0x140,
+     0x140, 0x141, 0x142, 0x142, 0x143, 0x143, 0x144, 0x144,
+
+     0x145, 0x146, 0x146, 0x147, 0x147, 0x148, 0x149, 0x149,   // -2
+     0x14a, 0x14a, 0x14b, 0x14c, 0x14c, 0x14d, 0x14d, 0x14e,
+     0x14f, 0x14f, 0x150, 0x150, 0x151, 0x152, 0x152, 0x153,
+     0x153, 0x154, 0x155, 0x155, 0x156, 0x157, 0x157, 0x158,
+
+     // These are used for the first seven MIDI note values:
+
+     0x158, 0x159, 0x15a, 0x15a, 0x15b, 0x15b, 0x15c, 0x15d,   // 0
+     0x15d, 0x15e, 0x15f, 0x15f, 0x160, 0x161, 0x161, 0x162,
+     0x162, 0x163, 0x164, 0x164, 0x165, 0x166, 0x166, 0x167,
+     0x168, 0x168, 0x169, 0x16a, 0x16a, 0x16b, 0x16c, 0x16c,
+
+     0x16d, 0x16e, 0x16e, 0x16f, 0x170, 0x170, 0x171, 0x172,   // 1
+     0x172, 0x173, 0x174, 0x174, 0x175, 0x176, 0x176, 0x177,
+     0x178, 0x178, 0x179, 0x17a, 0x17a, 0x17b, 0x17c, 0x17c,
+     0x17d, 0x17e, 0x17e, 0x17f, 0x180, 0x181, 0x181, 0x182,
+
+     0x183, 0x183, 0x184, 0x185, 0x185, 0x186, 0x187, 0x188,   // 2
+     0x188, 0x189, 0x18a, 0x18a, 0x18b, 0x18c, 0x18d, 0x18d,
+     0x18e, 0x18f, 0x18f, 0x190, 0x191, 0x192, 0x192, 0x193,
+     0x194, 0x194, 0x195, 0x196, 0x197, 0x197, 0x198, 0x199,
+
+     0x19a, 0x19a, 0x19b, 0x19c, 0x19d, 0x19d, 0x19e, 0x19f,   // 3
+     0x1a0, 0x1a0, 0x1a1, 0x1a2, 0x1a3, 0x1a3, 0x1a4, 0x1a5,
+     0x1a6, 0x1a6, 0x1a7, 0x1a8, 0x1a9, 0x1a9, 0x1aa, 0x1ab,
+     0x1ac, 0x1ad, 0x1ad, 0x1ae, 0x1af, 0x1b0, 0x1b0, 0x1b1,
+
+     0x1b2, 0x1b3, 0x1b4, 0x1b4, 0x1b5, 0x1b6, 0x1b7, 0x1b8,   // 4
+     0x1b8, 0x1b9, 0x1ba, 0x1bb, 0x1bc, 0x1bc, 0x1bd, 0x1be,
+     0x1bf, 0x1c0, 0x1c0, 0x1c1, 0x1c2, 0x1c3, 0x1c4, 0x1c4,
+     0x1c5, 0x1c6, 0x1c7, 0x1c8, 0x1c9, 0x1c9, 0x1ca, 0x1cb,
+
+     0x1cc, 0x1cd, 0x1ce, 0x1ce, 0x1cf, 0x1d0, 0x1d1, 0x1d2,   // 5
+     0x1d3, 0x1d3, 0x1d4, 0x1d5, 0x1d6, 0x1d7, 0x1d8, 0x1d8,
+     0x1d9, 0x1da, 0x1db, 0x1dc, 0x1dd, 0x1de, 0x1de, 0x1df,
+     0x1e0, 0x1e1, 0x1e2, 0x1e3, 0x1e4, 0x1e5, 0x1e5, 0x1e6,
+
+     0x1e7, 0x1e8, 0x1e9, 0x1ea, 0x1eb, 0x1ec, 0x1ed, 0x1ed,   // 6
+     0x1ee, 0x1ef, 0x1f0, 0x1f1, 0x1f2, 0x1f3, 0x1f4, 0x1f5,
+     0x1f6, 0x1f6, 0x1f7, 0x1f8, 0x1f9, 0x1fa, 0x1fb, 0x1fc,
+     0x1fd, 0x1fe, 0x1ff, 0x200, 0x201, 0x201, 0x202, 0x203,
+
+     // First note of looped range used for all octaves:
+
+     0x204, 0x205, 0x206, 0x207, 0x208, 0x209, 0x20a, 0x20b,   // 7
+     0x20c, 0x20d, 0x20e, 0x20f, 0x210, 0x210, 0x211, 0x212,
+     0x213, 0x214, 0x215, 0x216, 0x217, 0x218, 0x219, 0x21a,
+     0x21b, 0x21c, 0x21d, 0x21e, 0x21f, 0x220, 0x221, 0x222,
+
+     0x223, 0x224, 0x225, 0x226, 0x227, 0x228, 0x229, 0x22a,   // 8
+     0x22b, 0x22c, 0x22d, 0x22e, 0x22f, 0x230, 0x231, 0x232,
+     0x233, 0x234, 0x235, 0x236, 0x237, 0x238, 0x239, 0x23a,
+     0x23b, 0x23c, 0x23d, 0x23e, 0x23f, 0x240, 0x241, 0x242,
+
+     0x244, 0x245, 0x246, 0x247, 0x248, 0x249, 0x24a, 0x24b,   // 9
+     0x24c, 0x24d, 0x24e, 0x24f, 0x250, 0x251, 0x252, 0x253,
+     0x254, 0x256, 0x257, 0x258, 0x259, 0x25a, 0x25b, 0x25c,
+     0x25d, 0x25e, 0x25f, 0x260, 0x262, 0x263, 0x264, 0x265,
+
+     0x266, 0x267, 0x268, 0x269, 0x26a, 0x26c, 0x26d, 0x26e,   // 10
+     0x26f, 0x270, 0x271, 0x272, 0x273, 0x275, 0x276, 0x277,
+     0x278, 0x279, 0x27a, 0x27b, 0x27d, 0x27e, 0x27f, 0x280,
+     0x281, 0x282, 0x284, 0x285, 0x286, 0x287, 0x288, 0x289,
+
+     0x28b, 0x28c, 0x28d, 0x28e, 0x28f, 0x290, 0x292, 0x293,   // 11
+     0x294, 0x295, 0x296, 0x298, 0x299, 0x29a, 0x29b, 0x29c,
+     0x29e, 0x29f, 0x2a0, 0x2a1, 0x2a2, 0x2a4, 0x2a5, 0x2a6,
+     0x2a7, 0x2a9, 0x2aa, 0x2ab, 0x2ac, 0x2ae, 0x2af, 0x2b0,
+
+     0x2b1, 0x2b2, 0x2b4, 0x2b5, 0x2b6, 0x2b7, 0x2b9, 0x2ba,   // 12
+     0x2bb, 0x2bd, 0x2be, 0x2bf, 0x2c0, 0x2c2, 0x2c3, 0x2c4,
+     0x2c5, 0x2c7, 0x2c8, 0x2c9, 0x2cb, 0x2cc, 0x2cd, 0x2ce,
+     0x2d0, 0x2d1, 0x2d2, 0x2d4, 0x2d5, 0x2d6, 0x2d8, 0x2d9,
+
+     0x2da, 0x2dc, 0x2dd, 0x2de, 0x2e0, 0x2e1, 0x2e2, 0x2e4,   // 13
+     0x2e5, 0x2e6, 0x2e8, 0x2e9, 0x2ea, 0x2ec, 0x2ed, 0x2ee,
+     0x2f0, 0x2f1, 0x2f2, 0x2f4, 0x2f5, 0x2f6, 0x2f8, 0x2f9,
+     0x2fb, 0x2fc, 0x2fd, 0x2ff, 0x300, 0x302, 0x303, 0x304,
+
+     0x306, 0x307, 0x309, 0x30a, 0x30b, 0x30d, 0x30e, 0x310,   // 14
+     0x311, 0x312, 0x314, 0x315, 0x317, 0x318, 0x31a, 0x31b,
+     0x31c, 0x31e, 0x31f, 0x321, 0x322, 0x324, 0x325, 0x327,
+     0x328, 0x329, 0x32b, 0x32c, 0x32e, 0x32f, 0x331, 0x332,
+
+     0x334, 0x335, 0x337, 0x338, 0x33a, 0x33b, 0x33d, 0x33e,   // 15
+     0x340, 0x341, 0x343, 0x344, 0x346, 0x347, 0x349, 0x34a,
+     0x34c, 0x34d, 0x34f, 0x350, 0x352, 0x353, 0x355, 0x357,
+     0x358, 0x35a, 0x35b, 0x35d, 0x35e, 0x360, 0x361, 0x363,
+
+     0x365, 0x366, 0x368, 0x369, 0x36b, 0x36c, 0x36e, 0x370,   // 16
+     0x371, 0x373, 0x374, 0x376, 0x378, 0x379, 0x37b, 0x37c,
+     0x37e, 0x380, 0x381, 0x383, 0x384, 0x386, 0x388, 0x389,
+     0x38b, 0x38d, 0x38e, 0x390, 0x392, 0x393, 0x395, 0x397,
+
+     0x398, 0x39a, 0x39c, 0x39d, 0x39f, 0x3a1, 0x3a2, 0x3a4,   // 17
+     0x3a6, 0x3a7, 0x3a9, 0x3ab, 0x3ac, 0x3ae, 0x3b0, 0x3b1,
+     0x3b3, 0x3b5, 0x3b7, 0x3b8, 0x3ba, 0x3bc, 0x3bd, 0x3bf,
+     0x3c1, 0x3c3, 0x3c4, 0x3c6, 0x3c8, 0x3ca, 0x3cb, 0x3cd,
+
+     // The last note has an incomplete range, and loops round back to
+     // the start.  Note that the last value is actually a buffer overrun
+     // and does not fit with the other values.
+
+     0x3cf, 0x3d1, 0x3d2, 0x3d4, 0x3d6, 0x3d8, 0x3da, 0x3db,   // 18
+     0x3dd, 0x3df, 0x3e1, 0x3e3, 0x3e4, 0x3e6, 0x3e8, 0x3ea,
+     0x3ec, 0x3ed, 0x3ef, 0x3f1, 0x3f3, 0x3f5, 0x3f6, 0x3f8,
+     0x3fa, 0x3fc, 0x3fe, 0x36c,
+ };
+
+ // Mapping from MIDI volume level to OPL level value.
+
+ static const unsigned int volume_mapping_table[] = {
+     0, 1, 3, 5, 6, 8, 10, 11,
+     13, 14, 16, 17, 19, 20, 22, 23,
+     25, 26, 27, 29, 30, 32, 33, 34,
+     36, 37, 39, 41, 43, 45, 47, 49,
+     50, 52, 54, 55, 57, 59, 60, 61,
+     63, 64, 66, 67, 68, 69, 71, 72,
+     73, 74, 75, 76, 77, 79, 80, 81,
+     82, 83, 84, 84, 85, 86, 87, 88,
+     89, 90, 91, 92, 92, 93, 94, 95,
+     96, 96, 97, 98, 99, 99, 100, 101,
+     101, 102, 103, 103, 104, 105, 105, 106,
+     107, 107, 108, 109, 109, 110, 110, 111,
+     112, 112, 113, 113, 114, 114, 115, 115,
+     116, 117, 117, 118, 118, 119, 119, 120,
+     120, 121, 121, 122, 122, 123, 123, 123,
+     124, 124, 125, 125, 126, 126, 127, 127
+ };
+
+ static boolean music_initialized = false;
+
+ //static boolean musicpaused = false;
+ static int current_music_volume;
+
+ // GENMIDI lump instrument data:
+
+ static genmidi_instr_t *main_instrs;
+ static genmidi_instr_t *percussion_instrs;
+
+ // Voices:
+
+ static opl_voice_t voices[OPL_NUM_VOICES];
+ static opl_voice_t *voice_free_list;
+ static opl_voice_t *voice_alloced_list;
+
+ // Track data for playing tracks:
+
+ static opl_track_data_t *tracks;
+ static unsigned int num_tracks = 0;
+ static unsigned int running_tracks = 0;
+ static boolean song_looping;
+
+ // Configuration file variable, containing the port number for the
+ // adlib chip.
+
+ int opl_io_port = 0x388;
+
+ // Load instrument table from GENMIDI lump:
+
+ static boolean LoadInstrumentTable(void)
+ {
+     byte *lump;
+
+     lump = W_CacheLumpName("GENMIDI", PU_STATIC);
+
+     // Check header
+
+     if (strncmp((char *) lump, GENMIDI_HEADER, strlen(GENMIDI_HEADER)) != 0)
+     {
+         W_ReleaseLumpName("GENMIDI");
+
+         return false;
+     }
+
+     main_instrs = (genmidi_instr_t *) (lump + strlen(GENMIDI_HEADER));
+     percussion_instrs = main_instrs + GENMIDI_NUM_INSTRS;
+
+     return true;
+ }
+
+ // Get the next available voice from the freelist.
+
+ static opl_voice_t *GetFreeVoice(void)
+ {
+     opl_voice_t *result;
+
+     // None available?
+
+     if (voice_free_list == NULL)
+     {
+         return NULL;
+     }
+
+     // Remove from free list
+
+     result = voice_free_list;
+     voice_free_list = voice_free_list->next;
+
+     // Add to allocated list
+
+     result->next = voice_alloced_list;
+     voice_alloced_list = result;
+
+     return result;
+ }
+
+ // Remove a voice from the allocated voices list.
+
+ static void RemoveVoiceFromAllocedList(opl_voice_t *voice)
+ {
+     opl_voice_t **rover;
+
+     rover = &voice_alloced_list;
+
+     // Search the list until we find the voice, then remove it.
+
+     while (*rover != NULL)
+     {
+         if (*rover == voice)
+         {
+             *rover = voice->next;
+             voice->next = NULL;
+             break;
+         }
+
+         rover = &(*rover)->next;
+     }
+ }
+
+ // Release a voice back to the freelist.
+
+ static void ReleaseVoice(opl_voice_t *voice)
+ {
+     opl_voice_t **rover;
+
+     voice->channel = NULL;
+     voice->note = 0;
+
+     // Remove from alloced list.
+
+     RemoveVoiceFromAllocedList(voice);
+
+     // Search to the end of the freelist (This is how Doom behaves!)
+
+     rover = &voice_free_list;
+
+     while (*rover != NULL)
+     {
+         rover = &(*rover)->next;
+     }
+
+     *rover = voice;
+     voice->next = NULL;
+ }
+
+ // Load data to the specified operator
+
+ static void LoadOperatorData(int operator, genmidi_op_t *data,
+                              boolean max_level)
+ {
+     int level;
+
+     // The scale and level fields must be combined for the level register.
+     // For the carrier wave we always set the maximum level.
+
+     level = (data->scale & 0xc0) | (data->level & 0x3f);
+
+     if (max_level)
+     {
+         level |= 0x3f;
+     }
+
+     OPL_WriteRegister(OPL_REGS_LEVEL + operator, level);
+     OPL_WriteRegister(OPL_REGS_TREMOLO + operator, data->tremolo);
+     OPL_WriteRegister(OPL_REGS_ATTACK + operator, data->attack);
+     OPL_WriteRegister(OPL_REGS_SUSTAIN + operator, data->sustain);
+     OPL_WriteRegister(OPL_REGS_WAVEFORM + operator, data->waveform);
+ }
+
+ // Set the instrument for a particular voice.
+
+ static void SetVoiceInstrument(opl_voice_t *voice,
+                                genmidi_instr_t *instr,
+                                unsigned int instr_voice)
+ {
+     genmidi_voice_t *data;
+     unsigned int modulating;
+
+     // Instrument already set for this channel?
+
+     if (voice->current_instr == instr
+      && voice->current_instr_voice == instr_voice)
+     {
+         return;
+     }
+
+     voice->current_instr = instr;
+     voice->current_instr_voice = instr_voice;
+
+     data = &instr->voices[instr_voice];
+
+     // Are we usind modulated feedback mode?
+
+     modulating = (data->feedback & 0x01) == 0;
+
+     // Doom loads the second operator first, then the first.
+     // The carrier is set to minimum volume until the voice volume
+     // is set in SetVoiceVolume (below).  If we are not using
+     // modulating mode, we must set both to minimum volume.
+
+     LoadOperatorData(voice->op2, &data->carrier, true);
+     LoadOperatorData(voice->op1, &data->modulator, !modulating);
+
+     // Set feedback register that control the connection between the
+     // two operators.  Turn on bits in the upper nybble; I think this
+     // is for OPL3, where it turns on channel A/B.
+
+     OPL_WriteRegister(OPL_REGS_FEEDBACK + voice->index,
+                       data->feedback | 0x30);
+
+     // Hack to force a volume update.
+
+     voice->reg_volume = 999;
+ }
+
+ static void SetVoiceVolume(opl_voice_t *voice, unsigned int volume)
+ {
+     genmidi_voice_t *opl_voice;
+     unsigned int full_volume;
+     unsigned int op_volume;
+     unsigned int reg_volume;
+
+     voice->note_volume = volume;
+
+     opl_voice = &voice->current_instr->voices[voice->current_instr_voice];
+
+     // Multiply note volume and channel volume to get the actual volume.
+
+     full_volume = (volume_mapping_table[voice->note_volume]
+                    * volume_mapping_table[voice->channel->volume]
+                    * volume_mapping_table[current_music_volume]) / (127 * 127);
+
+     // The volume of each instrument can be controlled via GENMIDI:
+
+     op_volume = 0x3f - opl_voice->carrier.level;
+
+     // The volume value to use in the register:
+
+     reg_volume = (op_volume * full_volume) / 128;
+     reg_volume = (0x3f - reg_volume) | opl_voice->carrier.scale;
+
+     // Update the volume register(s) if necessary.
+
+     if (reg_volume != voice->reg_volume)
+     {
+         voice->reg_volume = reg_volume;
+
+         OPL_WriteRegister(OPL_REGS_LEVEL + voice->op2, reg_volume);
+
+         // If we are using non-modulated feedback mode, we must set the
+         // volume for both voices.
+         // Note that the same register volume value is written for
+         // both voices, always calculated from the carrier's level
+         // value.
+
+         if ((opl_voice->feedback & 0x01) != 0)
+         {
+             OPL_WriteRegister(OPL_REGS_LEVEL + voice->op1, reg_volume);
+         }
+     }
+ }
+
+ // Initialize the voice table and freelist
+
+ static void InitVoices(void)
+ {
+     int i;
+
+     // Start with an empty free list.
+
+     voice_free_list = NULL;
+
+     // Initialize each voice.
+
+     for (i=0; i<OPL_NUM_VOICES; ++i)
+     {
+         voices[i].index = i;
+         voices[i].op1 = voice_operators[0][i];
+         voices[i].op2 = voice_operators[1][i];
+         voices[i].current_instr = NULL;
+
+         // Add this voice to the freelist.
+
+         ReleaseVoice(&voices[i]);
+     }
+ }
+
+ // Set music volume (0 - 127)
+
+ static void I_OPL_SetMusicVolume(int volume)
+ {
+     unsigned int i;
+
+     // Internal state variable.
+
+     current_music_volume = volume;
+
+     // Update the volume of all voices.
+
+     for (i=0; i<OPL_NUM_VOICES; ++i)
+     {
+         if (voices[i].channel != NULL)
+         {
+             SetVoiceVolume(&voices[i], voices[i].note_volume);
+         }
+     }
+ }
+
+ static void VoiceKeyOff(opl_voice_t *voice)
+ {
+     OPL_WriteRegister(OPL_REGS_FREQ_2 + voice->index, voice->freq >> 8);
+ }
+
+ // Get the frequency that we should be using for a voice.
+
+ static void KeyOffEvent(opl_track_data_t *track, midi_event_t *event)
+ {
+     opl_channel_data_t *channel;
+     unsigned int key;
+     unsigned int i;
+
+ /*
+     printf("note off: channel %i, %i, %i\n",
+            event->data.channel.channel,
+            event->data.channel.param1,
+            event->data.channel.param2);
+ */
+
+     channel = &track->channels[event->data.channel.channel];
+     key = event->data.channel.param1;
+
+     // Turn off voices being used to play this key.
+     // If it is a double voice instrument there will be two.
+
+     for (i=0; i<OPL_NUM_VOICES; ++i)
+     {
+         if (voices[i].channel == channel && voices[i].key == key)
+         {
+             VoiceKeyOff(&voices[i]);
+
+             // Finished with this voice now.
+
+             ReleaseVoice(&voices[i]);
+         }
+     }
+ }
+
+ // Compare the priorities of channels, returning either -1, 0 or 1.
+
+ static int CompareChannelPriorities(opl_channel_data_t *chan1,
+                                     opl_channel_data_t *chan2)
+ {
+     // TODO ...
+
+     return 1;
+ }
+
+ // When all voices are in use, we must discard an existing voice to
+ // play a new note.  Find and free an existing voice.  The channel
+ // passed to the function is the channel for the new note to be
+ // played.
+
+ static opl_voice_t *ReplaceExistingVoice(opl_channel_data_t *channel)
+ {
+     opl_voice_t *rover;
+     opl_voice_t *result;
+
+     // Check the allocated voices, if we find an instrument that is
+     // of a lower priority to the new instrument, discard it.
+     // If a voice is being used to play the second voice of an instrument,
+     // use that, as second voices are non-essential.
+     // Lower numbered MIDI channels implicitly have a higher priority
+     // than higher-numbered channels, eg. MIDI channel 1 is never
+     // discarded for MIDI channel 2.
+
+     result = NULL;
+
+     for (rover = voice_alloced_list; rover != NULL; rover = rover->next)
+     {
+         if (rover->current_instr_voice != 0
+          || (rover->channel > channel
+              && CompareChannelPriorities(channel, rover->channel) > 0))
+         {
+             result = rover;
+             break;
+         }
+     }
+
+     // If we didn't find a voice, find an existing voice being used to
+     // play a note on the same channel, and use that.
+
+     if (result == NULL)
+     {
+         for (rover = voice_alloced_list; rover != NULL; rover = rover->next)
+         {
+             if (rover->channel == channel)
+             {
+                 result = rover;
+                 break;
+             }
+         }
+     }
+
+     // Still nothing found?  Give up and just use the first voice in
+     // the list.
+
+     if (result == NULL)
+     {
+         result = voice_alloced_list;
+     }
+
+     // Stop playing this voice playing and release it back to the free
+     // list.
+
+     VoiceKeyOff(result);
+     ReleaseVoice(result);
+
+     // Re-allocate the voice again and return it.
+
+     return GetFreeVoice();
+ }
+
+
+ static unsigned int FrequencyForVoice(opl_voice_t *voice)
+ {
+     genmidi_voice_t *gm_voice;
+     unsigned int freq_index;
+     unsigned int octave;
+     unsigned int sub_index;
+     unsigned int note;
+
+     note = voice->note;
+
+     // Apply note offset.
+     // Don't apply offset if the instrument is a fixed note instrument.
+
+     gm_voice = &voice->current_instr->voices[voice->current_instr_voice];
+
+     if ((voice->current_instr->flags & GENMIDI_FLAG_FIXED) == 0)
+     {
+         note += (signed short) SHORT(gm_voice->base_note_offset);
+     }
+
+     // Avoid possible overflow due to base note offset:
+
+     if (note > 0x7f)
+     {
+         note = voice->note;
+     }
+
+     freq_index = 64 + 32 * note + voice->channel->bend;
+
+     // If this is the second voice of a double voice instrument, the
+     // frequency index can be adjusted by the fine tuning field.
+
+     if (voice->current_instr_voice != 0)
+     {
+         freq_index += (voice->current_instr->fine_tuning / 2) - 64;
+     }
+
+     // The first 7 notes use the start of the table, while
+     // consecutive notes loop around the latter part.
+
+     if (freq_index < 284)
+     {
+         return frequency_curve[freq_index];
+     }
+
+     sub_index = (freq_index - 284) % (12 * 32);
+     octave = (freq_index - 284) / (12 * 32);
+
+     // Once the seventh octave is reached, things break down.
+     // We can only go up to octave 7 as a maximum anyway (the OPL
+     // register only has three bits for octave number), but for the
+     // notes in octave 7, the first five bits have octave=7, the
+     // following notes have octave=6.  This 7/6 pattern repeats in
+     // following octaves (which are technically impossible to
+     // represent anyway).
+
+     if (octave >= 7)
+     {
+         if (sub_index < 5)
+         {
+             octave = 7;
+         }
+         else
+         {
+             octave = 6;
+         }
+     }
+
+     // Calculate the resulting register value to use for the frequency.
+
+     return frequency_curve[sub_index + 284] | (octave << 10);
+ }
+
+ // Update the frequency that a voice is programmed to use.
+
+ static void UpdateVoiceFrequency(opl_voice_t *voice)
+ {
+     unsigned int freq;
+
+     // Calculate the frequency to use for this voice and update it
+     // if neccessary.
+
+     freq = FrequencyForVoice(voice);
+
+     if (voice->freq != freq)
+     {
+         OPL_WriteRegister(OPL_REGS_FREQ_1 + voice->index, freq & 0xff);
+         OPL_WriteRegister(OPL_REGS_FREQ_2 + voice->index, (freq >> 8) | 0x20);
+
+         voice->freq = freq;
+     }
+ }
+
+ // Program a single voice for an instrument.  For a double voice
+ // instrument (GENMIDI_FLAG_2VOICE), this is called twice for each
+ // key on event.
+
+ static void VoiceKeyOn(opl_channel_data_t *channel,
+                        genmidi_instr_t *instrument,
+                        unsigned int instrument_voice,
+                        unsigned int key,
+                        unsigned int volume)
+ {
+     opl_voice_t *voice;
+
+     // Find a voice to use for this new note.
+
+     voice = GetFreeVoice();
+
+     // If there are no more voices left, we must decide what to do.
+     // If this is the first voice of the instrument, free an existing
+     // voice and use that.  Otherwise, if this is the second voice,
+     // it isn't as important; just discard it.
+
+     if (voice == NULL)
+     {
+         if (instrument_voice == 0)
+         {
+             voice = ReplaceExistingVoice(channel);
+         }
+         else
+         {
+             return;
+         }
+     }
+
+     voice->channel = channel;
+     voice->key = key;
+
+     // Work out the note to use.  This is normally the same as
+     // the key, unless it is a fixed pitch instrument.
+
+     if ((instrument->flags & GENMIDI_FLAG_FIXED) != 0)
+     {
+         voice->note = instrument->fixed_note;
+     }
+     else
+     {
+         voice->note = key;
+     }
+
+     // Program the voice with the instrument data:
+
+     SetVoiceInstrument(voice, instrument, instrument_voice);
+
+     // Set the volume level.
+
+     SetVoiceVolume(voice, volume);
+
+     // Write the frequency value to turn the note on.
+
+     voice->freq = 0;
+     UpdateVoiceFrequency(voice);
+ }
+
+ static void KeyOnEvent(opl_track_data_t *track, midi_event_t *event)
+ {
+     genmidi_instr_t *instrument;
+     opl_channel_data_t *channel;
+     unsigned int key;
+     unsigned int volume;
+
+ /*
+     printf("note on: channel %i, %i, %i\n",
+            event->data.channel.channel,
+            event->data.channel.param1,
+            event->data.channel.param2);
+ */
+
+     // The channel.
+
+     channel = &track->channels[event->data.channel.channel];
+     key = event->data.channel.param1;
+     volume = event->data.channel.param2;
+
+     // Percussion channel (10) is treated differently.
+
+     if (event->data.channel.channel == 9)
+     {
+         if (key < 35 || key > 81)
+         {
+             return;
+         }
+
+         instrument = &percussion_instrs[key - 35];
+     }
+     else
+     {
+         instrument = channel->instrument;
+     }
+
+     // Find and program a voice for this instrument.  If this
+     // is a double voice instrument, we must do this twice.
+
+     VoiceKeyOn(channel, instrument, 0, key, volume);
+
+     if ((instrument->flags & GENMIDI_FLAG_2VOICE) != 0)
+     {
+         VoiceKeyOn(channel, instrument, 1, key, volume);
+     }
+ }
+
+ static void ProgramChangeEvent(opl_track_data_t *track, midi_event_t *event)
+ {
+     int channel;
+     int instrument;
+
+     // Set the instrument used on this channel.
+
+     channel = event->data.channel.channel;
+     instrument = event->data.channel.param1;
+     track->channels[channel].instrument = &main_instrs[instrument];
+
+     // TODO: Look through existing voices that are turned on on this
+     // channel, and change the instrument.
+ }
+
+ static void SetChannelVolume(opl_channel_data_t *channel, unsigned int volume)
+ {
+     unsigned int i;
+
+     channel->volume = volume;
+
+     // Update all voices that this channel is using.
+
+     for (i=0; i<OPL_NUM_VOICES; ++i)
+     {
+         if (voices[i].channel == channel)
+         {
+             SetVoiceVolume(&voices[i], voices[i].note_volume);
+         }
+     }
+ }
+
+ static void ControllerEvent(opl_track_data_t *track, midi_event_t *event)
+ {
+     unsigned int controller;
+     unsigned int param;
+     opl_channel_data_t *channel;
+
+ /*
+     printf("change controller: channel %i, %i, %i\n",
+            event->data.channel.channel,
+            event->data.channel.param1,
+            event->data.channel.param2);
+ */
+
+     channel = &track->channels[event->data.channel.channel];
+     controller = event->data.channel.param1;
+     param = event->data.channel.param2;
+
+     switch (controller)
+     {
+         case MIDI_CONTROLLER_MAIN_VOLUME:
+             SetChannelVolume(channel, param);
+             break;
+
+         default:
+ #ifdef OPL_MIDI_DEBUG
+             fprintf(stderr, "Unknown MIDI controller type: %i\n", controller);
+ #endif
+             break;
+     }
+ }
+
+ // Process a pitch bend event.
+
+ static void PitchBendEvent(opl_track_data_t *track, midi_event_t *event)
+ {
+     opl_channel_data_t *channel;
+     unsigned int i;
+
+     // Update the channel bend value.  Only the MSB of the pitch bend
+     // value is considered: this is what Doom does.
+
+     channel = &track->channels[event->data.channel.channel];
+     channel->bend = event->data.channel.param2 - 64;
+
+     // Update all voices for this channel.
+
+     for (i=0; i<OPL_NUM_VOICES; ++i)
+     {
+         if (voices[i].channel == channel)
+         {
+             UpdateVoiceFrequency(&voices[i]);
+         }
+     }
+ }
+
+ // Process a meta event.
+
+ static void MetaEvent(opl_track_data_t *track, midi_event_t *event)
+ {
+     switch (event->data.meta.type)
+     {
+         // Things we can just ignore.
+
+         case MIDI_META_SEQUENCE_NUMBER:
+         case MIDI_META_TEXT:
+         case MIDI_META_COPYRIGHT:
+         case MIDI_META_TRACK_NAME:
+         case MIDI_META_INSTR_NAME:
+         case MIDI_META_LYRICS:
+         case MIDI_META_MARKER:
+         case MIDI_META_CUE_POINT:
+         case MIDI_META_SEQUENCER_SPECIFIC:
+             break;
+
+         // End of track - actually handled when we run out of events
+         // in the track, see below.
+
+         case MIDI_META_END_OF_TRACK:
+             break;
+
+         default:
+ #ifdef OPL_MIDI_DEBUG
+             fprintf(stderr, "Unknown MIDI meta event type: %i\n",
+                             event->data.meta.type);
+ #endif
+             break;
+     }
+ }
+
+ // Process a MIDI event from a track.
+
+ static void ProcessEvent(opl_track_data_t *track, midi_event_t *event)
+ {
+     switch (event->event_type)
+     {
+         case MIDI_EVENT_NOTE_OFF:
+             KeyOffEvent(track, event);
+             break;
+
+         case MIDI_EVENT_NOTE_ON:
+             KeyOnEvent(track, event);
+             break;
+
+         case MIDI_EVENT_CONTROLLER:
+             ControllerEvent(track, event);
+             break;
+
+         case MIDI_EVENT_PROGRAM_CHANGE:
+             ProgramChangeEvent(track, event);
+             break;
+
+         case MIDI_EVENT_PITCH_BEND:
+             PitchBendEvent(track, event);
+             break;
+
+         case MIDI_EVENT_META:
+             MetaEvent(track, event);
+             break;
+
+         // SysEx events can be ignored.
+
+         case MIDI_EVENT_SYSEX:
+         case MIDI_EVENT_SYSEX_SPLIT:
+             break;
+
+         default:
+ #ifdef OPL_MIDI_DEBUG
+             fprintf(stderr, "Unknown MIDI event type %i\n", event->event_type);
+ #endif
+             break;
+     }
+ }
+
+ static void ScheduleTrack(opl_track_data_t *track);
+
+ // Restart a song from the beginning.
+
+ static void RestartSong(void)
+ {
+     unsigned int i;
+
+     running_tracks = num_tracks;
+
+     for (i=0; i<num_tracks; ++i)
+     {
+         MIDI_RestartIterator(tracks[i].iter);
+         ScheduleTrack(&tracks[i]);
+     }
+ }
+
+ // Callback function invoked when another event needs to be read from
+ // a track.
+
+ static void TrackTimerCallback(void *arg)
+ {
+     opl_track_data_t *track = arg;
+     midi_event_t *event;
+
+     // Get the next event and process it.
+
+     if (!MIDI_GetNextEvent(track->iter, &event))
+     {
+         return;
+     }
+
+     ProcessEvent(track, event);
+
+     // End of track?
+
+     if (event->event_type == MIDI_EVENT_META
+      && event->data.meta.type == MIDI_META_END_OF_TRACK)
+     {
+         --running_tracks;
+
+         // When all tracks have finished, restart the song.
+
+         if (running_tracks <= 0 && song_looping)
+         {
+             RestartSong();
+         }
+
+         return;
+     }
+
+     // Reschedule the callback for the next event in the track.
+
+     ScheduleTrack(track);
+ }
+
+ static void ScheduleTrack(opl_track_data_t *track)
+ {
+     unsigned int nticks;
+     unsigned int ms;
+     static int total = 0;
+
+     // Get the number of milliseconds until the next event.
+
+     nticks = MIDI_GetDeltaTime(track->iter);
+     ms = (nticks * track->ms_per_beat) / track->ticks_per_beat;
+     total += ms;
+
+     // Set a timer to be invoked when the next event is
+     // ready to play.
+
+     OPL_SetCallback(ms, TrackTimerCallback, track);
+ }
+
+ // Initialize a channel.
+
+ static void InitChannel(opl_track_data_t *track, opl_channel_data_t *channel)
+ {
+     // TODO: Work out sensible defaults?
+
+     channel->instrument = &main_instrs[0];
+     channel->volume = 127;
+     channel->bend = 0;
+ }
+
+ // Start a MIDI track playing:
+
+ static void StartTrack(midi_file_t *file, unsigned int track_num)
+ {
+     opl_track_data_t *track;
+     unsigned int i;
+
+     track = &tracks[track_num];
+     track->iter = MIDI_IterateTrack(file, track_num);
+     track->ticks_per_beat = MIDI_GetFileTimeDivision(file);
+
+     // Default is 120 bpm.
+     // TODO: this is wrong
+
+     track->ms_per_beat = 500 * 260;
+
+     for (i=0; i<MIDI_CHANNELS_PER_TRACK; ++i)
+     {
+         InitChannel(track, &track->channels[i]);
+     }
+
+     // Schedule the first event.
+
+     ScheduleTrack(track);
+ }
+
+ // Start playing a mid
+
 -static void I_OPL_PlaySong(void *handle, int looping)
++static void I_OPL_PlaySong(void *handle, boolean looping)
+ {
+     midi_file_t *file;
+     unsigned int i;
+
+     if (!music_initialized || handle == NULL)
+     {
+         return;
+     }
+
+     file = handle;
+
+     // Allocate track data.
+
+     tracks = malloc(MIDI_NumTracks(file) * sizeof(opl_track_data_t));
+
+     num_tracks = MIDI_NumTracks(file);
+     running_tracks = num_tracks;
+     song_looping = looping;
+
+     for (i=0; i<num_tracks; ++i)
+     {
+         StartTrack(file, i);
+     }
+ }
+
+ static void I_OPL_PauseSong(void)
+ {
+     unsigned int i;
+
+     if (!music_initialized)
+     {
+         return;
+     }
+
+     // Pause OPL callbacks.
+
+     OPL_SetPaused(1);
+
+     // Turn off all main instrument voices (not percussion).
+     // This is what Vanilla does.
+
+     for (i=0; i<OPL_NUM_VOICES; ++i)
+     {
+         if (voices[i].channel != NULL
+          && voices[i].current_instr < percussion_instrs)
+         {
+             VoiceKeyOff(&voices[i]);
+         }
+     }
+ }
+
+ static void I_OPL_ResumeSong(void)
+ {
+     if (!music_initialized)
+     {
+         return;
+     }
+
+     OPL_SetPaused(0);
+ }
+
+ static void I_OPL_StopSong(void)
+ {
+     unsigned int i;
+
+     if (!music_initialized)
+     {
+         return;
+     }
+
+     OPL_Lock();
+
+     // Stop all playback.
+
+     OPL_ClearCallbacks();
+
+     // Free all voices.
+
+     for (i=0; i<OPL_NUM_VOICES; ++i)
+     {
+         if (voices[i].channel != NULL)
+         {
+             VoiceKeyOff(&voices[i]);
+             ReleaseVoice(&voices[i]);
+         }
+     }
+
+     // Free all track data.
+
+     for (i=0; i<num_tracks; ++i)
+     {
+         MIDI_FreeIterator(tracks[i].iter);
+     }
+
+     free(tracks);
+
+     tracks = NULL;
+     num_tracks = 0;
+
+     OPL_Unlock();
+ }
+
+ static void I_OPL_UnRegisterSong(void *handle)
+ {
+     if (!music_initialized)
+     {
+         return;
+     }
+
+     if (handle != NULL)
+     {
+         MIDI_FreeFile(handle);
+     }
+ }
+
+ // Determine whether memory block is a .mid file
+
+ static boolean IsMid(byte *mem, int len)
+ {
+     return len > 4 && !memcmp(mem, "MThd", 4);
+ }
+
+ static boolean ConvertMus(byte *musdata, int len, char *filename)
+ {
+     MEMFILE *instream;
+     MEMFILE *outstream;
+     void *outbuf;
+     size_t outbuf_len;
+     int result;
+
+     instream = mem_fopen_read(musdata, len);
+     outstream = mem_fopen_write();
+
+     result = mus2mid(instream, outstream);
+
+     if (result == 0)
+     {
+         mem_get_buf(outstream, &outbuf, &outbuf_len);
+
+         M_WriteFile(filename, outbuf, outbuf_len);
+     }
+
+     mem_fclose(instream);
+     mem_fclose(outstream);
+
+     return result;
+ }
+
+ static void *I_OPL_RegisterSong(void *data, int len)
+ {
+     midi_file_t *result;
+     char *filename;
+
+     if (!music_initialized)
+     {
+         return NULL;
+     }
+
+     // MUS files begin with "MUS"
+     // Reject anything which doesnt have this signature
+
+     filename = M_TempFile("doom.mid");
+
+     if (IsMid(data, len) && len < MAXMIDLENGTH)
+     {
+         M_WriteFile(filename, data, len);
+     }
+     else
+     {
+ 	// Assume a MUS file and try to convert
+
+         ConvertMus(data, len, filename);
+     }
+
+     result = MIDI_LoadFile(filename);
+
+     if (result == NULL)
+     {
+         fprintf(stderr, "I_OPL_RegisterSong: Failed to load MID.\n");
+     }
+
+     // remove file now
+
+     remove(filename);
+
+     Z_Free(filename);
+
+     return result;
+ }
+
+ // Is the song playing?
+
+ static boolean I_OPL_MusicIsPlaying(void)
+ {
+     if (!music_initialized)
+     {
+         return false;
+     }
+
+     return num_tracks > 0;
+ }
+
+ // Shutdown music
+
+ static void I_OPL_ShutdownMusic(void)
+ {
+     if (music_initialized)
+     {
+         // Stop currently-playing track, if there is one:
+
+         I_OPL_StopSong();
+
+         OPL_Shutdown();
+
+         // Release GENMIDI lump
+
+         W_ReleaseLumpName("GENMIDI");
+
+         music_initialized = false;
+     }
+ }
+
+ // Initialize music subsystem
+
+ static boolean I_OPL_InitMusic(void)
+ {
+     OPL_SetSampleRate(snd_samplerate);
+
+     if (!OPL_Init(opl_io_port))
+     {
+         printf("Dude.  The Adlib isn't responding.\n");
+         return false;
+     }
+
+     // Load instruments from GENMIDI lump:
+
+     if (!LoadInstrumentTable())
+     {
+         OPL_Shutdown();
+         return false;
+     }
+
+     InitVoices();
+
+     tracks = NULL;
+     num_tracks = 0;
+     music_initialized = true;
+
+     return true;
+ }
+
+ static snddevice_t music_opl_devices[] =
+ {
+     SNDDEVICE_ADLIB,
+     SNDDEVICE_SB,
+ };
+
+ music_module_t music_opl_module =
+ {
+     music_opl_devices,
+     arrlen(music_opl_devices),
+     I_OPL_InitMusic,
+     I_OPL_ShutdownMusic,
+     I_OPL_SetMusicVolume,
+     I_OPL_PauseSong,
+     I_OPL_ResumeSong,
+     I_OPL_RegisterSong,
+     I_OPL_UnRegisterSong,
+     I_OPL_PlaySong,
+     I_OPL_StopSong,
+     I_OPL_MusicIsPlaying,
+ };
+
diff --cc src/i_sound.c
index a9e953ce,00000000..75f82702
mode 100644,000000..100644
--- a/src/i_sound.c
+++ b/src/i_sound.c
@@@ -1,430 -1,0 +1,432 @@@
 +// Emacs style mode select   -*- C++ -*-
 +//-----------------------------------------------------------------------------
 +//
 +// Copyright(C) 1993-1996 Id Software, Inc.
 +// Copyright(C) 2005 Simon Howard
 +//
 +// This program is free software; you can redistribute it and/or
 +// modify it under the terms of the GNU General Public License
 +// as published by the Free Software Foundation; either version 2
 +// of the License, or (at your option) any later version.
 +//
 +// This program is distributed in the hope that it will be useful,
 +// but WITHOUT ANY WARRANTY; without even the implied warranty of
 +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +// GNU General Public License for more details.
 +//
 +// You should have received a copy of the GNU General Public License
 +// along with this program; if not, write to the Free Software
 +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 +// 02111-1307, USA.
 +//
 +// DESCRIPTION:  none
 +//
 +//-----------------------------------------------------------------------------
 +
 +#include <stdio.h>
 +#include <stdlib.h>
 +
 +#include "SDL_mixer.h"
 +
 +#include "config.h"
 +#include "doomfeatures.h"
 +#include "doomtype.h"
 +
 +#include "i_sound.h"
 +#include "i_video.h"
 +#include "m_argv.h"
 +#include "m_config.h"
 +
 +// Sound sample rate to use for digital output (Hz)
 +
 +int snd_samplerate = 44100;
 +
 +// Low-level sound and music modules we are using
 +
 +static sound_module_t *sound_module;
 +static music_module_t *music_module;
 +
- int snd_musicdevice = SNDDEVICE_SB;
++int snd_musicdevice = SNDDEVICE_GENMIDI;
 +int snd_sfxdevice = SNDDEVICE_SB;
 +
 +// Sound modules
 +
 +extern sound_module_t sound_sdl_module;
 +extern sound_module_t sound_pcsound_module;
 +extern music_module_t music_sdl_module;
++extern music_module_t music_opl_module;
 +
 +// DOS-specific options: These are unused but should be maintained
 +// so that the config file can be shared between chocolate
 +// doom and doom.exe
 +
 +static int snd_sbport = 0;
 +static int snd_sbirq = 0;
 +static int snd_sbdma = 0;
 +static int snd_mport = 0;
 +
 +// Compiled-in sound modules:
 +
 +static sound_module_t *sound_modules[] =
 +{
 +#ifdef FEATURE_SOUND
 +    &sound_sdl_module,
 +    &sound_pcsound_module,
 +#endif
 +    NULL,
 +};
 +
 +// Compiled-in music modules:
 +
 +static music_module_t *music_modules[] =
 +{
 +#ifdef FEATURE_SOUND
 +    &music_sdl_module,
++    &music_opl_module,
 +#endif
 +    NULL,
 +};
 +
 +// Check if a sound device is in the given list of devices
 +
 +static boolean SndDeviceInList(snddevice_t device, snddevice_t *list,
 +                               int len)
 +{
 +    int i;
 +
 +    for (i=0; i<len; ++i)
 +    {
 +        if (device == list[i])
 +        {
 +            return true;
 +        }
 +    }
 +
 +    return false;
 +}
 +
 +// Find and initialize a sound_module_t appropriate for the setting
 +// in snd_sfxdevice.
 +
 +static void InitSfxModule(boolean use_sfx_prefix)
 +{
 +    int i;
 +
 +    sound_module = NULL;
 +
 +    for (i=0; sound_modules[i] != NULL; ++i)
 +    {
 +        // Is the sfx device in the list of devices supported by
 +        // this module?
 +
 +        if (SndDeviceInList(snd_sfxdevice,
 +                            sound_modules[i]->sound_devices,
 +                            sound_modules[i]->num_sound_devices))
 +        {
 +            // Initialize the module
 +
 +            if (sound_modules[i]->Init(use_sfx_prefix))
 +            {
 +                sound_module = sound_modules[i];
 +                return;
 +            }
 +        }
 +    }
 +}
 +
 +// Initialize music according to snd_musicdevice.
 +
 +static void InitMusicModule(void)
 +{
 +    int i;
 +
 +    music_module = NULL;
 +
 +    for (i=0; music_modules[i] != NULL; ++i)
 +    {
 +        // Is the music device in the list of devices supported
 +        // by this module?
 +
 +        if (SndDeviceInList(snd_musicdevice,
 +                            music_modules[i]->sound_devices,
 +                            music_modules[i]->num_sound_devices))
 +        {
 +            // Initialize the module
 +
 +            if (music_modules[i]->Init())
 +            {
 +                music_module = music_modules[i];
 +                return;
 +            }
 +        }
 +    }
 +}
 +
 +//
 +// Initializes sound stuff, including volume
 +// Sets channels, SFX and music volume,
 +//  allocates channel buffer, sets S_sfx lookup.
 +//
 +
 +void I_InitSound(boolean use_sfx_prefix)
 +{
 +    boolean nosound, nosfx, nomusic;
 +
 +    //!
 +    // @vanilla
 +    //
 +    // Disable all sound output.
 +    //
 +
 +    nosound = M_CheckParm("-nosound") > 0;
 +
 +    //!
 +    // @vanilla
 +    //
 +    // Disable sound effects.
 +    //
 +
 +    nosfx = M_CheckParm("-nosfx") > 0;
 +
 +    //!
 +    // @vanilla
 +    //
 +    // Disable music.
 +    //
 +
 +    nomusic = M_CheckParm("-nomusic") > 0;
 +
 +    // Initialize the sound and music subsystems.
 +
 +    if (!nosound && !screensaver_mode)
 +    {
 +        if (!nosfx)
 +        {
 +            InitSfxModule(use_sfx_prefix);
 +        }
 +
 +        if (!nomusic)
 +        {
 +            InitMusicModule();
 +        }
 +    }
 +}
 +
 +void I_ShutdownSound(void)
 +{
 +    if (sound_module != NULL)
 +    {
 +        sound_module->Shutdown();
 +    }
 +
 +    if (music_module != NULL)
 +    {
 +        music_module->Shutdown();
 +    }
 +}
 +
 +int I_GetSfxLumpNum(sfxinfo_t *sfxinfo)
 +{
 +    if (sound_module != NULL)
 +    {
 +        return sound_module->GetSfxLumpNum(sfxinfo);
 +    }
 +    else
 +    {
 +        return 0;
 +    }
 +}
 +
 +void I_UpdateSound(void)
 +{
 +    if (sound_module != NULL)
 +    {
 +        sound_module->Update();
 +    }
 +}
 +
 +static void CheckVolumeSeparation(int *sep, int *vol)
 +{
 +return;
 +    if (*sep < 0)
 +    {
 +        *sep = 0;
 +    }
 +    else if (*sep > 254)
 +    {
 +        *sep = 254;
 +    }
 +
 +    if (*vol < 0)
 +    {
 +        *vol = 0;
 +    }
 +    else if (*vol > 127)
 +    {
 +        *vol = 127;
 +    }
 +}
 +
 +void I_UpdateSoundParams(int channel, int vol, int sep)
 +{
 +    if (sound_module != NULL)
 +    {
 +        CheckVolumeSeparation(&vol, &sep);
 +        sound_module->UpdateSoundParams(channel, vol, sep);
 +    }
 +}
 +
 +int I_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep)
 +{
 +    if (sound_module != NULL)
 +    {
 +        CheckVolumeSeparation(&vol, &sep);
 +        return sound_module->StartSound(sfxinfo, channel, vol, sep);
 +    }
 +    else
 +    {
 +        return 0;
 +    }
 +}
 +
 +void I_StopSound(int channel)
 +{
 +    if (sound_module != NULL)
 +    {
 +        sound_module->StopSound(channel);
 +    }
 +}
 +
 +boolean I_SoundIsPlaying(int channel)
 +{
 +    if (sound_module != NULL)
 +    {
 +        return sound_module->SoundIsPlaying(channel);
 +    }
 +    else
 +    {
 +        return false;
 +    }
 +}
 +
 +void I_PrecacheSounds(sfxinfo_t *sounds, int num_sounds)
 +{
 +    if (sound_module != NULL && sound_module->CacheSounds != NULL)
 +    {
 +	sound_module->CacheSounds(sounds, num_sounds);
 +    }
 +}
 +
 +void I_InitMusic(void)
 +{
 +}
 +
 +void I_ShutdownMusic(void)
 +{
 +
 +}
 +
 +void I_SetMusicVolume(int volume)
 +{
 +    if (music_module != NULL)
 +    {
 +        music_module->SetMusicVolume(volume);
 +    }
 +}
 +
 +void I_PauseSong(void)
 +{
 +    if (music_module != NULL)
 +    {
 +        music_module->PauseMusic();
 +    }
 +}
 +
 +void I_ResumeSong(void)
 +{
 +    if (music_module != NULL)
 +    {
 +        music_module->ResumeMusic();
 +    }
 +}
 +
 +void *I_RegisterSong(void *data, int len)
 +{
 +    if (music_module != NULL)
 +    {
 +        return music_module->RegisterSong(data, len);
 +    }
 +    else
 +    {
 +        return NULL;
 +    }
 +}
 +
 +void I_UnRegisterSong(void *handle)
 +{
 +    if (music_module != NULL)
 +    {
 +        music_module->UnRegisterSong(handle);
 +    }
 +}
 +
 +void I_PlaySong(void *handle, boolean looping)
 +{
 +    if (music_module != NULL)
 +    {
 +        music_module->PlaySong(handle, looping);
 +    }
 +}
 +
 +void I_StopSong(void)
 +{
 +    if (music_module != NULL)
 +    {
 +        music_module->StopSong();
 +    }
 +}
 +
 +boolean I_MusicIsPlaying(void)
 +{
 +    if (music_module != NULL)
 +    {
 +        return music_module->MusicIsPlaying();
 +    }
 +    else
 +    {
 +        return false;
 +    }
 +}
 +
 +void I_BindSoundVariables(void)
 +{
 +    extern int use_libsamplerate;
 +
 +    M_BindVariable("snd_musicdevice",   &snd_musicdevice);
 +    M_BindVariable("snd_sfxdevice",     &snd_sfxdevice);
 +    M_BindVariable("snd_sbport",        &snd_sbport);
 +    M_BindVariable("snd_sbirq",         &snd_sbirq);
 +    M_BindVariable("snd_sbdma",         &snd_sbdma);
 +    M_BindVariable("snd_mport",         &snd_mport);
 +    M_BindVariable("snd_samplerate",    &snd_samplerate);
 +#ifdef FEATURE_SOUND
 +    M_BindVariable("use_libsamplerate", &use_libsamplerate);
 +#endif
 +
 +    // Before SDL_mixer version 1.2.11, MIDI music caused the game
 +    // to crash when it looped.  If this is an old SDL_mixer version,
 +    // disable MIDI.
 +
 +#ifdef __MACOSX__
 +    {
 +        const SDL_version *v = Mix_Linked_Version();
 +
 +        if (SDL_VERSIONNUM(v->major, v->minor, v->patch)
 +          < SDL_VERSIONNUM(1, 2, 11))
 +        {
 +            snd_musicdevice = SNDDEVICE_NONE;
 +        }
 +    }
 +#endif
 +}
 +
diff --cc src/midifile.c
index 00000000,bd935ca1..1990dcdc
mode 000000,100644..100644
--- a/src/midifile.c
+++ b/src/midifile.c
@@@ -1,0 -1,815 +1,814 @@@
+ // Emacs style mode select   -*- C++ -*-
+ //-----------------------------------------------------------------------------
+ //
+ // Copyright(C) 2009 Simon Howard
+ //
+ // This program is free software; you can redistribute it and/or
+ // modify it under the terms of the GNU General Public License
+ // as published by the Free Software Foundation; either version 2
+ // of the License, or (at your option) any later version.
+ //
+ // This program is distributed in the hope that it will be useful,
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ // GNU General Public License for more details.
+ //
+ // You should have received a copy of the GNU General Public License
+ // along with this program; if not, write to the Free Software
+ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ // 02111-1307, USA.
+ //
+ // DESCRIPTION:
+ //    Reading of MIDI files.
+ //
+ //-----------------------------------------------------------------------------
+
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <string.h>
+ #include <assert.h>
+
 -#include "doomdef.h"
+ #include "doomtype.h"
+ #include "i_swap.h"
+ #include "midifile.h"
+
+ #define HEADER_CHUNK_ID "MThd"
+ #define TRACK_CHUNK_ID  "MTrk"
+ #define MAX_BUFFER_SIZE 0x10000
+
+ typedef struct
+ {
+     byte chunk_id[4];
+     unsigned int chunk_size;
+ } PACKEDATTR chunk_header_t;
+
+ typedef struct
+ {
+     chunk_header_t chunk_header;
+     unsigned short format_type;
+     unsigned short num_tracks;
+     unsigned short time_division;
+ } PACKEDATTR midi_header_t;
+
+ typedef struct
+ {
+     // Length in bytes:
+
+     unsigned int data_len;
+
+     // Events in this track:
+
+     midi_event_t *events;
+     int num_events;
+ } midi_track_t;
+
+ struct midi_track_iter_s
+ {
+     midi_track_t *track;
+     unsigned int position;
+ };
+
+ struct midi_file_s
+ {
+     midi_header_t header;
+
+     // All tracks in this file:
+     midi_track_t *tracks;
+     unsigned int num_tracks;
+
+     // Data buffer used to store data read for SysEx or meta events:
+     byte *buffer;
+     unsigned int buffer_size;
+ };
+
+ // Check the header of a chunk:
+
+ static boolean CheckChunkHeader(chunk_header_t *chunk,
+                                 char *expected_id)
+ {
+     boolean result;
+
+     result = (memcmp((char *) chunk->chunk_id, expected_id, 4) == 0);
+
+     if (!result)
+     {
+         fprintf(stderr, "CheckChunkHeader: Expected '%s' chunk header, "
+                         "got '%c%c%c%c'\n",
+                         expected_id,
+                         chunk->chunk_id[0], chunk->chunk_id[1],
+                         chunk->chunk_id[2], chunk->chunk_id[3]);
+     }
+
+     return result;
+ }
+
+ // Read a single byte.  Returns false on error.
+
+ static boolean ReadByte(byte *result, FILE *stream)
+ {
+     int c;
+
+     c = fgetc(stream);
+
+     if (c == EOF)
+     {
+         fprintf(stderr, "ReadByte: Unexpected end of file\n");
+         return false;
+     }
+     else
+     {
+         *result = (byte) c;
+
+         return true;
+     }
+ }
+
+ // Read a variable-length value.
+
+ static boolean ReadVariableLength(unsigned int *result, FILE *stream)
+ {
+     int i;
+     byte b;
+
+     *result = 0;
+
+     for (i=0; i<4; ++i)
+     {
+         if (!ReadByte(&b, stream))
+         {
+             fprintf(stderr, "ReadVariableLength: Error while reading "
+                             "variable-length value\n");
+             return false;
+         }
+
+         // Insert the bottom seven bits from this byte.
+
+         *result <<= 7;
+         *result |= b & 0x7f;
+
+         // If the top bit is not set, this is the end.
+
+         if ((b & 0x80) == 0)
+         {
+             return true;
+         }
+     }
+
+     fprintf(stderr, "ReadVariableLength: Variable-length value too "
+                     "long: maximum of four bytes\n");
+     return false;
+ }
+
+ // Read a byte sequence into the data buffer.
+
+ static void *ReadByteSequence(unsigned int num_bytes, FILE *stream)
+ {
+     unsigned int i;
+     byte *result;
+
+     // Allocate a buffer:
+
+     result = malloc(num_bytes);
+
+     if (result == NULL)
+     {
+         fprintf(stderr, "ReadByteSequence: Failed to allocate buffer\n");
+         return NULL;
+     }
+
+     // Read the data:
+
+     for (i=0; i<num_bytes; ++i)
+     {
+         if (!ReadByte(&result[i], stream))
+         {
+             fprintf(stderr, "ReadByteSequence: Error while reading byte %u\n",
+                             i);
+             free(result);
+             return NULL;
+         }
+     }
+
+     return result;
+ }
+
+ // Read a MIDI channel event.
+ // two_param indicates that the event type takes two parameters
+ // (three byte) otherwise it is single parameter (two byte)
+
+ static boolean ReadChannelEvent(midi_event_t *event,
+                                 byte event_type, boolean two_param,
+                                 FILE *stream)
+ {
+     byte b;
+
+     // Set basics:
+
+     event->event_type = event_type & 0xf0;
+     event->data.channel.channel = event_type & 0x0f;
+
+     // Read parameters:
+
+     if (!ReadByte(&b, stream))
+     {
+         fprintf(stderr, "ReadChannelEvent: Error while reading channel "
+                         "event parameters\n");
+         return false;
+     }
+
+     event->data.channel.param1 = b;
+
+     // Second parameter:
+
+     if (two_param)
+     {
+         if (!ReadByte(&b, stream))
+         {
+             fprintf(stderr, "ReadChannelEvent: Error while reading channel "
+                             "event parameters\n");
+             return false;
+         }
+
+         event->data.channel.param2 = b;
+     }
+
+     return true;
+ }
+
+ // Read sysex event:
+
+ static boolean ReadSysExEvent(midi_event_t *event, int event_type,
+                               FILE *stream)
+ {
+     event->event_type = event_type;
+
+     if (!ReadVariableLength(&event->data.sysex.length, stream))
+     {
+         fprintf(stderr, "ReadSysExEvent: Failed to read length of "
+                                         "SysEx block\n");
+         return false;
+     }
+
+     // Read the byte sequence:
+
+     event->data.sysex.data = ReadByteSequence(event->data.sysex.length, stream);
+
+     if (event->data.sysex.data == NULL)
+     {
+         fprintf(stderr, "ReadSysExEvent: Failed while reading SysEx event\n");
+         return false;
+     }
+
+     return true;
+ }
+
+ // Read meta event:
+
+ static boolean ReadMetaEvent(midi_event_t *event, FILE *stream)
+ {
+     byte b;
+
+     event->event_type = MIDI_EVENT_META;
+
+     // Read meta event type:
+
+     if (!ReadByte(&b, stream))
+     {
+         fprintf(stderr, "ReadMetaEvent: Failed to read meta event type\n");
+         return false;
+     }
+
+     event->data.meta.type = b;
+
+     // Read length of meta event data:
+
+     if (!ReadVariableLength(&event->data.meta.length, stream))
+     {
+         fprintf(stderr, "ReadSysExEvent: Failed to read length of "
+                                         "SysEx block\n");
+         return false;
+     }
+
+     // Read the byte sequence:
+
+     event->data.meta.data = ReadByteSequence(event->data.meta.length, stream);
+
+     if (event->data.meta.data == NULL)
+     {
+         fprintf(stderr, "ReadSysExEvent: Failed while reading SysEx event\n");
+         return false;
+     }
+
+     return true;
+ }
+
+ static boolean ReadEvent(midi_event_t *event, unsigned int *last_event_type,
+                          FILE *stream)
+ {
+     byte event_type;
+
+     if (!ReadVariableLength(&event->delta_time, stream))
+     {
+         fprintf(stderr, "ReadEvent: Failed to read event timestamp\n");
+         return false;
+     }
+
+     if (!ReadByte(&event_type, stream))
+     {
+         fprintf(stderr, "ReadEvent: Failed to read event type\n");
+         return false;
+     }
+
+     // All event types have their top bit set.  Therefore, if
+     // the top bit is not set, it is because we are using the "same
+     // as previous event type" shortcut to save a byte.  Skip back
+     // a byte so that we read this byte again.
+
+     if ((event_type & 0x80) == 0)
+     {
+         event_type = *last_event_type;
+
+         if (fseek(stream, -1, SEEK_CUR) < 0)
+         {
+             fprintf(stderr, "ReadEvent: Unable to seek in stream\n");
+             return false;
+         }
+     }
+     else
+     {
+         *last_event_type = event_type;
+     }
+
+     // Check event type:
+
+     switch (event_type & 0xf0)
+     {
+         // Two parameter channel events:
+
+         case MIDI_EVENT_NOTE_OFF:
+         case MIDI_EVENT_NOTE_ON:
+         case MIDI_EVENT_AFTERTOUCH:
+         case MIDI_EVENT_CONTROLLER:
+         case MIDI_EVENT_PITCH_BEND:
+             return ReadChannelEvent(event, event_type, true, stream);
+
+         // Single parameter channel events:
+
+         case MIDI_EVENT_PROGRAM_CHANGE:
+         case MIDI_EVENT_CHAN_AFTERTOUCH:
+             return ReadChannelEvent(event, event_type, false, stream);
+
+         default:
+             break;
+     }
+
+     // Specific value?
+
+     switch (event_type)
+     {
+         case MIDI_EVENT_SYSEX:
+         case MIDI_EVENT_SYSEX_SPLIT:
+             return ReadSysExEvent(event, event_type, stream);
+
+         case MIDI_EVENT_META:
+             return ReadMetaEvent(event, stream);
+
+         default:
+             break;
+     }
+
+     fprintf(stderr, "ReadEvent: Unknown MIDI event type: 0x%x\n", event_type);
+     return false;
+ }
+
+ // Free an event:
+
+ static void FreeEvent(midi_event_t *event)
+ {
+     // Some event types have dynamically allocated buffers assigned
+     // to them that must be freed.
+
+     switch (event->event_type)
+     {
+         case MIDI_EVENT_SYSEX:
+         case MIDI_EVENT_SYSEX_SPLIT:
+             free(event->data.sysex.data);
+             break;
+
+         case MIDI_EVENT_META:
+             free(event->data.meta.data);
+             break;
+
+         default:
+             // Nothing to do.
+             break;
+     }
+ }
+
+ // Read and check the track chunk header
+
+ static boolean ReadTrackHeader(midi_track_t *track, FILE *stream)
+ {
+     size_t records_read;
+     chunk_header_t chunk_header;
+
+     records_read = fread(&chunk_header, sizeof(chunk_header_t), 1, stream);
+
+     if (records_read < 1)
+     {
+         return false;
+     }
+
+     if (!CheckChunkHeader(&chunk_header, TRACK_CHUNK_ID))
+     {
+         return false;
+     }
+
+     track->data_len = SDL_SwapBE32(chunk_header.chunk_size);
+
+     return true;
+ }
+
+ static boolean ReadTrack(midi_track_t *track, FILE *stream)
+ {
+     midi_event_t *new_events;
+     midi_event_t *event;
+     unsigned int last_event_type;
+
+     track->num_events = 0;
+     track->events = NULL;
+
+     // Read the header:
+
+     if (!ReadTrackHeader(track, stream))
+     {
+         return false;
+     }
+
+     // Then the events:
+
+     last_event_type = 0;
+
+     for (;;)
+     {
+         // Resize the track slightly larger to hold another event:
+
+         new_events = realloc(track->events,
+                              sizeof(midi_event_t) * (track->num_events + 1));
+
+         if (new_events == NULL)
+         {
+             return false;
+         }
+
+         track->events = new_events;
+
+         // Read the next event:
+
+         event = &track->events[track->num_events];
+         if (!ReadEvent(event, &last_event_type, stream))
+         {
+             return false;
+         }
+
+         ++track->num_events;
+
+         // End of track?
+
+         if (event->event_type == MIDI_EVENT_META
+          && event->data.meta.type == MIDI_META_END_OF_TRACK)
+         {
+             break;
+         }
+     }
+
+     return true;
+ }
+
+ // Free a track:
+
+ static void FreeTrack(midi_track_t *track)
+ {
+     unsigned int i;
+
+     for (i=0; i<track->num_events; ++i)
+     {
+         FreeEvent(&track->events[i]);
+     }
+
+     free(track->events);
+ }
+
+ static boolean ReadAllTracks(midi_file_t *file, FILE *stream)
+ {
+     unsigned int i;
+
+     // Allocate list of tracks and read each track:
+
+     file->tracks = malloc(sizeof(midi_track_t) * file->num_tracks);
+
+     if (file->tracks == NULL)
+     {
+         return false;
+     }
+
+     memset(file->tracks, 0, sizeof(midi_track_t) * file->num_tracks);
+
+     // Read each track:
+
+     for (i=0; i<file->num_tracks; ++i)
+     {
+         if (!ReadTrack(&file->tracks[i], stream))
+         {
+             return false;
+         }
+     }
+
+     return true;
+ }
+
+ // Read and check the header chunk.
+
+ static boolean ReadFileHeader(midi_file_t *file, FILE *stream)
+ {
+     size_t records_read;
+     unsigned int format_type;
+
+     records_read = fread(&file->header, sizeof(midi_header_t), 1, stream);
+
+     if (records_read < 1)
+     {
+         return false;
+     }
+
+     if (!CheckChunkHeader(&file->header.chunk_header, HEADER_CHUNK_ID)
+      || SDL_SwapBE32(file->header.chunk_header.chunk_size) != 6)
+     {
+         fprintf(stderr, "ReadFileHeader: Invalid MIDI chunk header! "
+                         "chunk_size=%i\n",
+                         SDL_SwapBE32(file->header.chunk_header.chunk_size));
+         return false;
+     }
+
+     format_type = SDL_SwapBE16(file->header.format_type);
+     file->num_tracks = SDL_SwapBE16(file->header.num_tracks);
+
+     if ((format_type != 0 && format_type != 1)
+      || file->num_tracks < 1)
+     {
+         fprintf(stderr, "ReadFileHeader: Only type 0/1 "
+                                          "MIDI files supported!\n");
+         return false;
+     }
+
+     return true;
+ }
+
+ void MIDI_FreeFile(midi_file_t *file)
+ {
+     int i;
+
+     if (file->tracks != NULL)
+     {
+         for (i=0; i<file->num_tracks; ++i)
+         {
+             FreeTrack(&file->tracks[i]);
+         }
+
+         free(file->tracks);
+     }
+
+     free(file);
+ }
+
+ midi_file_t *MIDI_LoadFile(char *filename)
+ {
+     midi_file_t *file;
+     FILE *stream;
+
+     file = malloc(sizeof(midi_file_t));
+
+     if (file == NULL)
+     {
+         return NULL;
+     }
+
+     file->tracks = NULL;
+     file->num_tracks = 0;
+     file->buffer = NULL;
+     file->buffer_size = 0;
+
+     // Open file
+
+     stream = fopen(filename, "rb");
+
+     if (stream == NULL)
+     {
+         fprintf(stderr, "MIDI_LoadFile: Failed to open '%s'\n", filename);
+         MIDI_FreeFile(file);
+         return NULL;
+     }
+
+     // Read MIDI file header
+
+     if (!ReadFileHeader(file, stream))
+     {
+         fclose(stream);
+         MIDI_FreeFile(file);
+         return NULL;
+     }
+
+     // Read all tracks:
+
+     if (!ReadAllTracks(file, stream))
+     {
+         fclose(stream);
+         MIDI_FreeFile(file);
+         return NULL;
+     }
+
+     fclose(stream);
+
+     return file;
+ }
+
+ // Get the number of tracks in a MIDI file.
+
+ unsigned int MIDI_NumTracks(midi_file_t *file)
+ {
+     return file->num_tracks;
+ }
+
+ // Start iterating over the events in a track.
+
+ midi_track_iter_t *MIDI_IterateTrack(midi_file_t *file, unsigned int track)
+ {
+     midi_track_iter_t *iter;
+
+     assert(track < file->num_tracks);
+
+     iter = malloc(sizeof(*iter));
+     iter->track = &file->tracks[track];
+     iter->position = 0;
+
+     return iter;
+ }
+
+ void MIDI_FreeIterator(midi_track_iter_t *iter)
+ {
+     free(iter);
+ }
+
+ // Get the time until the next MIDI event in a track.
+
+ unsigned int MIDI_GetDeltaTime(midi_track_iter_t *iter)
+ {
+     if (iter->position < iter->track->num_events)
+     {
+         midi_event_t *next_event;
+
+         next_event = &iter->track->events[iter->position];
+
+         return next_event->delta_time;
+     }
+     else
+     {
+         return 0;
+     }
+ }
+
+ // Get a pointer to the next MIDI event.
+
+ int MIDI_GetNextEvent(midi_track_iter_t *iter, midi_event_t **event)
+ {
+     if (iter->position < iter->track->num_events)
+     {
+         *event = &iter->track->events[iter->position];
+         ++iter->position;
+
+         return 1;
+     }
+     else
+     {
+         return 0;
+     }
+ }
+
+ unsigned int MIDI_GetFileTimeDivision(midi_file_t *file)
+ {
+     return file->header.time_division;
+ }
+
+ void MIDI_RestartIterator(midi_track_iter_t *iter)
+ {
+     iter->position = 0;
+ }
+
+ #ifdef TEST
+
+ static char *MIDI_EventTypeToString(midi_event_type_t event_type)
+ {
+     switch (event_type)
+     {
+         case MIDI_EVENT_NOTE_OFF:
+             return "MIDI_EVENT_NOTE_OFF";
+         case MIDI_EVENT_NOTE_ON:
+             return "MIDI_EVENT_NOTE_ON";
+         case MIDI_EVENT_AFTERTOUCH:
+             return "MIDI_EVENT_AFTERTOUCH";
+         case MIDI_EVENT_CONTROLLER:
+             return "MIDI_EVENT_CONTROLLER";
+         case MIDI_EVENT_PROGRAM_CHANGE:
+             return "MIDI_EVENT_PROGRAM_CHANGE";
+         case MIDI_EVENT_CHAN_AFTERTOUCH:
+             return "MIDI_EVENT_CHAN_AFTERTOUCH";
+         case MIDI_EVENT_PITCH_BEND:
+             return "MIDI_EVENT_PITCH_BEND";
+         case MIDI_EVENT_SYSEX:
+             return "MIDI_EVENT_SYSEX";
+         case MIDI_EVENT_SYSEX_SPLIT:
+             return "MIDI_EVENT_SYSEX_SPLIT";
+         case MIDI_EVENT_META:
+             return "MIDI_EVENT_META";
+
+         default:
+             return "(unknown)";
+     }
+ }
+
+ void PrintTrack(midi_track_t *track)
+ {
+     midi_event_t *event;
+     unsigned int i;
+
+     for (i=0; i<track->num_events; ++i)
+     {
+         event = &track->events[i];
+
+         if (event->delta_time > 0)
+         {
+             printf("Delay: %i ticks\n", event->delta_time);
+         }
+
+         printf("Event type: %s (%i)\n",
+                MIDI_EventTypeToString(event->event_type),
+                event->event_type);
+
+         switch(event->event_type)
+         {
+             case MIDI_EVENT_NOTE_OFF:
+             case MIDI_EVENT_NOTE_ON:
+             case MIDI_EVENT_AFTERTOUCH:
+             case MIDI_EVENT_CONTROLLER:
+             case MIDI_EVENT_PROGRAM_CHANGE:
+             case MIDI_EVENT_CHAN_AFTERTOUCH:
+             case MIDI_EVENT_PITCH_BEND:
+                 printf("\tChannel: %i\n", event->data.channel.channel);
+                 printf("\tParameter 1: %i\n", event->data.channel.param1);
+                 printf("\tParameter 2: %i\n", event->data.channel.param2);
+                 break;
+
+             case MIDI_EVENT_SYSEX:
+             case MIDI_EVENT_SYSEX_SPLIT:
+                 printf("\tLength: %i\n", event->data.sysex.length);
+                 break;
+
+             case MIDI_EVENT_META:
+                 printf("\tMeta type: %i\n", event->data.meta.type);
+                 printf("\tLength: %i\n", event->data.meta.length);
+                 break;
+         }
+     }
+ }
+
+ int main(int argc, char *argv[])
+ {
+     midi_file_t *file;
+     unsigned int i;
+
+     if (argc < 2)
+     {
+         printf("Usage: %s <filename>\n", argv[0]);
+         exit(1);
+     }
+
+     file = MIDI_LoadFile(argv[1]);
+
+     if (file == NULL)
+     {
+         fprintf(stderr, "Failed to open %s\n", argv[1]);
+         exit(1);
+     }
+
+     for (i=0; i<file->num_tracks; ++i)
+     {
+         printf("\n== Track %i ==\n\n", i);
+
+         PrintTrack(&file->tracks[i]);
+     }
+
+     return 0;
+ }
+
+ #endif
+
diff --cc src/setup/sound.c
index 97037ee9,f7a9c6d4..61182753
--- a/src/setup/sound.c
+++ b/src/setup/sound.c
@@@ -40,52 -35,41 +40,54 @@@ typedef enu
      NUM_SFXMODES
  } sfxmode_t;

 -typedef enum
 -{
 -    MUSMODE_DISABLED,
 -    MUSMODE_OPL,
 -    MUSMODE_NATIVE,
 -    NUM_MUSMODES
 -} musmode_t;
 -
--static char *sfxmode_strings[] =
++static char *sfxmode_strings[] =
  {
      "Disabled",
 -    "PC speaker",
      "Digital",
 +    "PC speaker"
  };

- typedef enum
 -static char *musmode_strings[] =
++typedef enum
 +{
 +    MUSICMODE_DISABLED,
-     MUSICMODE_MIDI,
++    MUSICMODE_OPL,
++    MUSICMODE_NATIVE,
 +    MUSICMODE_CD,
 +    NUM_MUSICMODES
 +} musicmode_t;
 +
 +static char *musicmode_strings[] =
  {
      "Disabled",
-     "MIDI",
+     "OPL (Adlib/SB)",
 -    "Native MIDI"
++    "Native MIDI",
 +    "CD audio"
  };

 -int snd_sfxdevice = SNDDEVICE_SB;
 -int numChannels = 8;
 -int sfxVolume = 15;
 +// Config file variables:

 +int snd_sfxdevice = SNDDEVICE_SB;
- int snd_musicdevice = SNDDEVICE_SB;
+ int snd_musicdevice = SNDDEVICE_GENMIDI;
 -int musicVolume = 15;
 -
  int snd_samplerate = 22050;

 -int use_libsamplerate = 0;
 +static int numChannels = 8;
 +static int sfxVolume = 15;
 +static int musicVolume = 15;
 +static int use_libsamplerate = 0;
 +
 +// DOS specific variables: these are unused but should be maintained
 +// so that the config file can be shared between chocolate
 +// doom and doom.exe
 +
 +static int snd_sbport = 0;
 +static int snd_sbirq = 0;
 +static int snd_sbdma = 0;
 +static int snd_mport = 0;
 +
 +// GUI variables:

  static int snd_sfxmode;
 -static int snd_musmode;
 +static int snd_musicmode;

  static void UpdateSndDevices(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(data))
  {
@@@ -102,15 -86,16 +104,18 @@@
              break;
      }

 -    switch (snd_musmode)
 +    switch (snd_musicmode)
      {
 -        case MUSMODE_DISABLED:
 +        case MUSICMODE_DISABLED:
              snd_musicdevice = SNDDEVICE_NONE;
              break;
-         case MUSICMODE_MIDI:
 -        case MUSMODE_OPL:
++        case MUSICMODE_NATIVE:
++            snd_musicdevice = SNDDEVICE_GENMIDI;
++            break;
++        case MUSICMODE_OPL:
              snd_musicdevice = SNDDEVICE_SB;
              break;
 -        case MUSMODE_NATIVE:
 -            snd_musicdevice = SNDDEVICE_GENMIDI;
 +        case MUSICMODE_CD:
              break;
      }
  }
@@@ -138,46 -120,22 +143,52 @@@ void ConfigSound(void
      {
          snd_sfxmode = SFXMODE_DISABLED;
      }
-
+
 +    // Is music enabled?
 +
-     if (snd_musicdevice == SNDDEVICE_NONE)
+     if (snd_musicdevice == SNDDEVICE_GENMIDI)
      {
-         snd_musicmode = MUSICMODE_DISABLED;
 -        snd_musmode = MUSMODE_NATIVE;
++        snd_musicmode = MUSICMODE_NATIVE;
 +    }
 +    else if (snd_musicmode == SNDDEVICE_CD)
 +    {
 +        snd_musicmode = MUSICMODE_CD;
      }
+     else if (snd_musicdevice == SNDDEVICE_SB
+           || snd_musicdevice == SNDDEVICE_ADLIB
+           || snd_musicdevice == SNDDEVICE_AWE32)
+     {
 -        snd_musmode = MUSMODE_OPL;
++        snd_musicmode = MUSICMODE_OPL;
+     }
      else
      {
-         snd_musicmode = MUSICMODE_MIDI;
 -        snd_musmode = MUSMODE_DISABLED;
++        snd_musicmode = MUSICMODE_DISABLED;
      }

 +    // Doom has PC speaker sound effects, but others do not:
 +
 +    if (gamemission == doom)
 +    {
 +        num_sfx_modes = NUM_SFXMODES;
 +    }
 +    else
 +    {
 +        num_sfx_modes = NUM_SFXMODES - 1;
 +    }
 +
 +    // Hexen has CD audio; others do not.
 +
 +    if (gamemission == hexen)
 +    {
 +        num_music_modes = NUM_MUSICMODES;
 +    }
 +    else
 +    {
 +        num_music_modes = NUM_MUSICMODES - 1;
 +    }
 +
 +    // Build the window
 +
      window = TXT_NewWindow("Sound configuration");

      TXT_AddWidgets(window,
@@@ -200,51 -158,20 +211,50 @@@
                     TXT_NewSpinControl(&sfxVolume, 0, 15),
                     NULL);

-     TXT_SetColumnWidths(music_table, 20, 5);
+     TXT_SetColumnWidths(music_table, 20, 14);

      TXT_AddWidgets(music_table,
 -                   TXT_NewLabel("Music playback"),
 -                   mus_mode_control = TXT_NewDropdownList(&snd_musmode,
 -                                                          musmode_strings,
 -                                                          NUM_MUSMODES),
 +                   TXT_NewLabel("Music"),
 +                   music_mode_control = TXT_NewDropdownList(&snd_musicmode,
 +                                                            musicmode_strings,
 +                                                            num_music_modes),
                     TXT_NewLabel("Music volume"),
                     TXT_NewSpinControl(&musicVolume, 0, 15),
                     NULL);

 -    TXT_SignalConnect(sfx_mode_control, "changed",
 -                      UpdateSndDevices, NULL);
 -    TXT_SignalConnect(mus_mode_control, "changed",
 -                      UpdateSndDevices, NULL);
 +    TXT_SignalConnect(sfx_mode_control, "changed", UpdateSndDevices, NULL);
 +    TXT_SignalConnect(music_mode_control, "changed", UpdateSndDevices, NULL);
-
 +}
 +
 +void BindSoundVariables(void)
 +{
 +    M_BindVariable("snd_sfxdevice",       &snd_sfxdevice);
 +    M_BindVariable("snd_musicdevice",     &snd_musicdevice);
 +    M_BindVariable("snd_channels",        &numChannels);
 +    M_BindVariable("sfx_volume",          &sfxVolume);
 +    M_BindVariable("music_volume",        &musicVolume);
 +    M_BindVariable("snd_samplerate",      &snd_samplerate);
 +    M_BindVariable("use_libsamplerate",   &use_libsamplerate);
 +
 +    M_BindVariable("snd_sbport",          &snd_sbport);
 +    M_BindVariable("snd_sbirq",          &snd_sbirq);
 +    M_BindVariable("snd_sbdma",          &snd_sbdma);
 +    M_BindVariable("snd_mport",          &snd_mport);
 +
 +    // Before SDL_mixer version 1.2.11, MIDI music caused the game
 +    // to crash when it looped.  If this is an old SDL_mixer version,
 +    // disable MIDI.
 +
 +#ifdef __MACOSX__
 +    {
 +        const SDL_version *v = Mix_Linked_Version();
 +
 +        if (SDL_VERSIONNUM(v->major, v->minor, v->patch)
 +          < SDL_VERSIONNUM(1, 2, 11))
 +        {
 +            snd_musicdevice = SNDDEVICE_NONE;
 +        }
 +    }
 +#endif
  }