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
}