]> git.0x19.co - doomgeneric-snom360/commitdiff
Added SDL2-based sound playback
authorGeorgi Gerganov <ggerganov@gmail.com>
Sat, 26 Feb 2022 13:34:06 +0000 (15:34 +0200)
committerozkl <ozkl@users.noreply.github.com>
Sun, 4 Dec 2022 11:47:24 +0000 (14:47 +0300)
doomgeneric/Makefile.sdl
doomgeneric/doomfeatures.h
doomgeneric/dummy.c
doomgeneric/i_cdmus.c
doomgeneric/i_sdlmusic.c [new file with mode: 0644]
doomgeneric/i_sdlsound.c [new file with mode: 0644]
doomgeneric/i_sound.c
doomgeneric/i_swap.h
doomgeneric/i_timer.c
doomgeneric/mus2mid.c [new file with mode: 0644]
doomgeneric/mus2mid.h [new file with mode: 0644]

index a2605a419fb25e2b8ed3719decc639cc925c3d5b..a7c24a5739fe4df95aa0d7a5f4b17ecfec232218 100644 (file)
@@ -19,14 +19,14 @@ SDL_LIBS =
 CC=clang  # gcc or g++
 CFLAGS+=-ggdb3 -Os $(INCLUDES) $(SDL_CFLAGS)
 LDFLAGS+=-Wl,-dead_strip
-CFLAGS+=-ggdb3 -Wall -DNORMALUNIX -DLINUX -DSNDSERV # -DUSEASM
-LIBS+=-lm -lc -lSDL2
+CFLAGS+=-ggdb3 -Wall -DNORMALUNIX -DLINUX -DSNDSERV -DFEATURE_SOUND # -DUSEASM
+LIBS+=-lm -lc -lSDL2 -lSDL2_mixer `sdl2-config --cflags --libs`
 
 # subdirectory for objects
 OBJDIR=build
 OUTPUT=doomgeneric
 
-SRC_DOOM = i_main.o dummy.o am_map.o doomdef.o doomstat.o dstrings.o d_event.o d_items.o d_iwad.o d_loop.o d_main.o d_mode.o d_net.o f_finale.o f_wipe.o g_game.o hu_lib.o hu_stuff.o info.o i_cdmus.o i_endoom.o i_joystick.o i_scale.o i_sound.o i_system.o i_timer.o memio.o m_argv.o m_bbox.o m_cheat.o m_config.o m_controls.o m_fixed.o m_menu.o m_misc.o m_random.o p_ceilng.o p_doors.o p_enemy.o p_floor.o p_inter.o p_lights.o p_map.o p_maputl.o p_mobj.o p_plats.o p_pspr.o p_saveg.o p_setup.o p_sight.o p_spec.o p_switch.o p_telept.o p_tick.o p_user.o r_bsp.o r_data.o r_draw.o r_main.o r_plane.o r_segs.o r_sky.o r_things.o sha1.o sounds.o statdump.o st_lib.o st_stuff.o s_sound.o tables.o v_video.o wi_stuff.o w_checksum.o w_file.o w_main.o w_wad.o z_zone.o w_file_stdc.o i_input.o i_video.o doomgeneric.o doomgeneric_sdl.o
+SRC_DOOM = i_main.o dummy.o am_map.o doomdef.o doomstat.o dstrings.o d_event.o d_items.o d_iwad.o d_loop.o d_main.o d_mode.o d_net.o f_finale.o f_wipe.o g_game.o hu_lib.o hu_stuff.o info.o i_cdmus.o i_endoom.o i_joystick.o i_scale.o i_sound.o i_system.o i_timer.o memio.o m_argv.o m_bbox.o m_cheat.o m_config.o m_controls.o m_fixed.o m_menu.o m_misc.o m_random.o p_ceilng.o p_doors.o p_enemy.o p_floor.o p_inter.o p_lights.o p_map.o p_maputl.o p_mobj.o p_plats.o p_pspr.o p_saveg.o p_setup.o p_sight.o p_spec.o p_switch.o p_telept.o p_tick.o p_user.o r_bsp.o r_data.o r_draw.o r_main.o r_plane.o r_segs.o r_sky.o r_things.o sha1.o sounds.o statdump.o st_lib.o st_stuff.o s_sound.o tables.o v_video.o wi_stuff.o w_checksum.o w_file.o w_main.o w_wad.o z_zone.o w_file_stdc.o i_input.o i_video.o doomgeneric.o doomgeneric_sdl.o mus2mid.o i_sdlmusic.o i_sdlsound.o
 OBJS += $(addprefix $(OBJDIR)/, $(SRC_DOOM))
 
 all:    $(OUTPUT)
index f7b15f7599c7840f32b072fa8189f66d1a1cc739..dff693611d4faf9c22ed51aedef2600e5b5e0e59 100644 (file)
@@ -33,7 +33,7 @@
 
 // Enables sound output
 
-#undef FEATURE_SOUND
+//#undef FEATURE_SOUND
 
 #endif /* #ifndef DOOM_FEATURES_H */
 
index 0068d034b26645a1b1d4df43b165541dcb464ec6..693901ca696ec7caed6493a2601ede035cd9da35 100644 (file)
@@ -40,10 +40,6 @@ boolean drone = false;
  *  public functions                                                   *
  *---------------------------------------------------------------------*/
 
-void I_InitTimidityConfig(void)
-{
-}
-
 /*---------------------------------------------------------------------*
  *  eof                                                                *
  *---------------------------------------------------------------------*/
index 6a9a9a6ee714ce50c3406f2942d5ab0fe8677189..12a815cfa7f2c1de777ee8d7732709391a68172c 100644 (file)
@@ -19,8 +19,8 @@
 #include <stdio.h>
 
 #ifdef ORIGCODE
-#include "SDL.h"
-#include "SDL_cdrom.h"
+#include "SDL2/SDL.h"
+#include "SDL2/SDL_cdrom.h"
 #endif
 
 #include "doomtype.h"
diff --git a/doomgeneric/i_sdlmusic.c b/doomgeneric/i_sdlmusic.c
new file mode 100644 (file)
index 0000000..c2101b7
--- /dev/null
@@ -0,0 +1,1316 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 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.
+//
+// DESCRIPTION:
+//     System interface for music.
+//
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include "SDL2/SDL.h"
+#include "SDL2/SDL_mixer.h"
+
+#include "config.h"
+#include "doomtype.h"
+#include "memio.h"
+#include "mus2mid.h"
+
+#include "deh_str.h"
+#include "gusconf.h"
+#include "i_sound.h"
+#include "i_system.h"
+#include "i_swap.h"
+#include "m_argv.h"
+#include "m_config.h"
+#include "m_misc.h"
+#include "sha1.h"
+#include "w_wad.h"
+#include "z_zone.h"
+
+#define MAXMIDLENGTH (96 * 1024)
+#define MID_HEADER_MAGIC "MThd"
+#define MUS_HEADER_MAGIC "MUS\x1a"
+
+#define FLAC_HEADER "fLaC"
+#define OGG_HEADER "OggS"
+
+// Looping Vorbis metadata tag names. These have been defined by ZDoom
+// for specifying the start and end positions for looping music tracks
+// in .ogg and .flac files.
+// More information is here: http://zdoom.org/wiki/Audio_loop
+#define LOOP_START_TAG "LOOP_START"
+#define LOOP_END_TAG   "LOOP_END"
+
+// FLAC metadata headers that we care about.
+#define FLAC_STREAMINFO      0
+#define FLAC_VORBIS_COMMENT  4
+
+// Ogg metadata headers that we care about.
+#define OGG_ID_HEADER        1
+#define OGG_COMMENT_HEADER   3
+
+// Structure for music substitution.
+// We store a mapping based on SHA1 checksum -> filename of substitute music
+// file to play, so that substitution occurs based on content rather than
+// lump name. This has some inherent advantages:
+//  * Music for Plutonia (reused from Doom 1) works automatically.
+//  * If a PWAD replaces music, the replacement music is used rather than
+//    the substitute music for the IWAD.
+//  * If a PWAD reuses music from an IWAD (even from a different game), we get
+//    the high quality version of the music automatically (neat!)
+
+typedef struct
+{
+    sha1_digest_t hash;
+    char *filename;
+} subst_music_t;
+
+// Structure containing parsed metadata read from a digital music track:
+typedef struct
+{
+    boolean valid;
+    unsigned int samplerate_hz;
+    int start_time, end_time;
+} file_metadata_t;
+
+static subst_music_t *subst_music = NULL;
+static unsigned int subst_music_len = 0;
+
+static const char *subst_config_filenames[] =
+{
+    "doom1-music.cfg",
+    "doom2-music.cfg",
+    "tnt-music.cfg",
+    "heretic-music.cfg",
+    "hexen-music.cfg",
+    "strife-music.cfg",
+};
+
+static boolean music_initialized = false;
+
+// If this is true, this module initialized SDL sound and has the 
+// responsibility to shut it down
+
+static boolean sdl_was_initialized = false;
+
+static boolean musicpaused = false;
+static int current_music_volume;
+
+char *timidity_cfg_path = "";
+
+static char *temp_timidity_cfg = NULL;
+
+// If true, we are playing a substitute digital track rather than in-WAD
+// MIDI/MUS track, and file_metadata contains loop metadata.
+static boolean playing_substitute = false;
+static file_metadata_t file_metadata;
+
+// Position (in samples) that we have reached in the current track.
+// This is updated by the TrackPositionCallback function.
+static unsigned int current_track_pos;
+
+// Currently playing music track.
+static Mix_Music *current_track_music = NULL;
+
+// If true, the currently playing track is being played on loop.
+static boolean current_track_loop;
+
+// Given a time string (for LOOP_START/LOOP_END), parse it and return
+// the time (in # samples since start of track) it represents.
+static unsigned int ParseVorbisTime(unsigned int samplerate_hz, char *value)
+{
+    char *num_start, *p;
+    unsigned int result = 0;
+    char c;
+
+    if (strchr(value, ':') == NULL)
+    {
+       return atoi(value);
+    }
+
+    result = 0;
+    num_start = value;
+
+    for (p = value; *p != '\0'; ++p)
+    {
+        if (*p == '.' || *p == ':')
+        {
+            c = *p; *p = '\0';
+            result = result * 60 + atoi(num_start);
+            num_start = p + 1;
+            *p = c;
+        }
+
+        if (*p == '.')
+        {
+            return result * samplerate_hz
+                + (unsigned int) (atof(p) * samplerate_hz);
+        }
+    }
+
+    return (result * 60 + atoi(num_start)) * samplerate_hz;
+}
+
+// Given a vorbis comment string (eg. "LOOP_START=12345"), set fields
+// in the metadata structure as appropriate.
+static void ParseVorbisComment(file_metadata_t *metadata, char *comment)
+{
+    char *eq, *key, *value;
+
+    eq = strchr(comment, '=');
+
+    if (eq == NULL)
+    {
+        return;
+    }
+
+    key = comment;
+    *eq = '\0';
+    value = eq + 1;
+
+    if (!strcmp(key, LOOP_START_TAG))
+    {
+        metadata->start_time = ParseVorbisTime(metadata->samplerate_hz, value);
+    }
+    else if (!strcmp(key, LOOP_END_TAG))
+    {
+        metadata->end_time = ParseVorbisTime(metadata->samplerate_hz, value);
+    }
+}
+
+// Parse a vorbis comments structure, reading from the given file.
+static void ParseVorbisComments(file_metadata_t *metadata, FILE *fs)
+{
+    uint32_t buf;
+    unsigned int num_comments, i, comment_len;
+    char *comment;
+
+    // We must have read the sample rate already from an earlier header.
+    if (metadata->samplerate_hz == 0)
+    {
+       return;
+    }
+
+    // Skip the starting part we don't care about.
+    if (fread(&buf, 4, 1, fs) < 1)
+    {
+        return;
+    }
+    if (fseek(fs, LONG(buf), SEEK_CUR) != 0)
+    {
+       return;
+    }
+
+    // Read count field for number of comments.
+    if (fread(&buf, 4, 1, fs) < 1)
+    {
+        return;
+    }
+    num_comments = LONG(buf);
+
+    // Read each individual comment.
+    for (i = 0; i < num_comments; ++i)
+    {
+        // Read length of comment.
+        if (fread(&buf, 4, 1, fs) < 1)
+       {
+            return;
+       }
+
+        comment_len = LONG(buf);
+
+        // Read actual comment data into string buffer.
+        comment = calloc(1, comment_len + 1);
+        if (comment == NULL
+         || fread(comment, 1, comment_len, fs) < comment_len)
+        {
+            free(comment);
+            break;
+        }
+
+        // Parse comment string.
+        ParseVorbisComment(metadata, comment);
+        free(comment);
+    }
+}
+
+static void ParseFlacStreaminfo(file_metadata_t *metadata, FILE *fs)
+{
+    byte buf[34];
+
+    // Read block data.
+    if (fread(buf, sizeof(buf), 1, fs) < 1)
+    {
+        return;
+    }
+
+    // We only care about sample rate and song length.
+    metadata->samplerate_hz = (buf[10] << 12) | (buf[11] << 4)
+                            | (buf[12] >> 4);
+    // Song length is actually a 36 bit field, but 32 bits should be
+    // enough for everybody.
+    //metadata->song_length = (buf[14] << 24) | (buf[15] << 16)
+    //                      | (buf[16] << 8) | buf[17];
+}
+
+static void ParseFlacFile(file_metadata_t *metadata, FILE *fs)
+{
+    byte header[4];
+    unsigned int block_type;
+    size_t block_len;
+    boolean last_block;
+
+    for (;;)
+    {
+        long pos = -1;
+
+        // Read METADATA_BLOCK_HEADER:
+        if (fread(header, 4, 1, fs) < 1)
+        {
+            return;
+        }
+
+        block_type = header[0] & ~0x80;
+        last_block = (header[0] & 0x80) != 0;
+        block_len = (header[1] << 16) | (header[2] << 8) | header[3];
+
+        pos = ftell(fs);
+        if (pos < 0)
+        {
+            return;
+        }
+
+        if (block_type == FLAC_STREAMINFO)
+        {
+            ParseFlacStreaminfo(metadata, fs);
+        }
+        else if (block_type == FLAC_VORBIS_COMMENT)
+        {
+            ParseVorbisComments(metadata, fs);
+        }
+
+        if (last_block)
+        {
+            break;
+        }
+
+        // Seek to start of next block.
+        if (fseek(fs, pos + block_len, SEEK_SET) != 0)
+        {
+            return;
+        }
+    }
+}
+
+static void ParseOggIdHeader(file_metadata_t *metadata, FILE *fs)
+{
+    byte buf[21];
+
+    if (fread(buf, sizeof(buf), 1, fs) < 1)
+    {
+        return;
+    }
+
+    metadata->samplerate_hz = (buf[8] << 24) | (buf[7] << 16)
+                            | (buf[6] << 8) | buf[5];
+}
+
+static void ParseOggFile(file_metadata_t *metadata, FILE *fs)
+{
+    byte buf[7];
+    unsigned int offset;
+
+    // Scan through the start of the file looking for headers. They
+    // begin '[byte]vorbis' where the byte value indicates header type.
+    memset(buf, 0, sizeof(buf));
+
+    for (offset = 0; offset < 100 * 1024; ++offset)
+    {
+       // buf[] is used as a sliding window. Each iteration, we
+       // move the buffer one byte to the left and read an extra
+       // byte onto the end.
+        memmove(buf, buf + 1, sizeof(buf) - 1);
+
+        if (fread(&buf[6], 1, 1, fs) < 1)
+        {
+            return;
+        }
+
+        if (!memcmp(buf + 1, "vorbis", 6))
+        {
+            switch (buf[0])
+            {
+                case OGG_ID_HEADER:
+                    ParseOggIdHeader(metadata, fs);
+                    break;
+                case OGG_COMMENT_HEADER:
+                   ParseVorbisComments(metadata, fs);
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+}
+
+static void ReadLoopPoints(char *filename, file_metadata_t *metadata)
+{
+    FILE *fs;
+    char header[4];
+
+    metadata->valid = false;
+    metadata->samplerate_hz = 0;
+    metadata->start_time = 0;
+    metadata->end_time = -1;
+
+    fs = fopen(filename, "r");
+
+    if (fs == NULL)
+    {
+        return;
+    }
+
+    // Check for a recognized file format; use the first four bytes
+    // of the file.
+
+    if (fread(header, 4, 1, fs) < 1)
+    {
+        fclose(fs);
+        return;
+    }
+
+    if (memcmp(header, FLAC_HEADER, 4) == 0)
+    {
+        ParseFlacFile(metadata, fs);
+    }
+    else if (memcmp(header, OGG_HEADER, 4) == 0)
+    {
+        ParseOggFile(metadata, fs);
+    }
+
+    fclose(fs);
+
+    // Only valid if at the very least we read the sample rate.
+    metadata->valid = metadata->samplerate_hz > 0;
+}
+
+// Given a MUS lump, look up a substitute MUS file to play instead
+// (or NULL to just use normal MIDI playback).
+
+static char *GetSubstituteMusicFile(void *data, size_t data_len)
+{
+    sha1_context_t context;
+    sha1_digest_t hash;
+    char *filename;
+    int i;
+
+    // Don't bother doing a hash if we're never going to find anything.
+    if (subst_music_len == 0)
+    {
+        return NULL;
+    }
+
+    SHA1_Init(&context);
+    SHA1_Update(&context, data, data_len);
+    SHA1_Final(hash, &context);
+
+    // Look for a hash that matches.
+    // The substitute mapping list can (intentionally) contain multiple
+    // filename mappings for the same hash. This allows us to try
+    // different files and fall back if our first choice isn't found.
+
+    filename = NULL;
+
+    for (i = 0; i < subst_music_len; ++i)
+    {
+        if (memcmp(hash, subst_music[i].hash, sizeof(hash)) == 0)
+        {
+            filename = subst_music[i].filename;
+
+            // If the file exists, then use this file in preference to
+            // any fallbacks. But we always return a filename if it's
+            // in the list, even if it's just so we can print an error
+            // message to the user saying it doesn't exist.
+            if (M_FileExists(filename))
+            {
+                break;
+            }
+        }
+    }
+
+    return filename;
+}
+
+// Add a substitute music file to the lookup list.
+
+static void AddSubstituteMusic(subst_music_t *subst)
+{
+    ++subst_music_len;
+    subst_music =
+        realloc(subst_music, sizeof(subst_music_t) * subst_music_len);
+    memcpy(&subst_music[subst_music_len - 1], subst, sizeof(subst_music_t));
+}
+
+static int ParseHexDigit(char c)
+{
+    c = tolower(c);
+
+    if (c >= '0' && c <= '9')
+    {
+        return c - '0';
+    }
+    else if (c >= 'a' && c <= 'f')
+    {
+        return 10 + (c - 'a');
+    }
+    else
+    {
+        return -1;
+    }
+}
+
+static char *GetFullPath(char *base_filename, char *path)
+{
+    char *basedir, *result;
+    char *p;
+
+    // Starting with directory separator means we have an absolute path,
+    // so just return it.
+    if (path[0] == DIR_SEPARATOR)
+    {
+        return strdup(path);
+    }
+
+#ifdef _WIN32
+    // d:\path\...
+    if (isalpha(path[0]) && path[1] == ':' && path[2] == DIR_SEPARATOR)
+    {
+        return strdup(path);
+    }
+#endif
+
+    // Paths in the substitute filenames can contain Unix-style /
+    // path separators, but we should convert this to the separator
+    // for the native platform.
+    path = M_StringReplace(path, "/", DIR_SEPARATOR_S);
+
+    // Copy config filename and cut off the filename to just get the
+    // parent dir.
+    basedir = strdup(base_filename);
+    p = strrchr(basedir, DIR_SEPARATOR);
+    if (p != NULL)
+    {
+        p[1] = '\0';
+        result = M_StringJoin(basedir, path, NULL);
+    }
+    else
+    {
+        result = strdup(path);
+    }
+    free(basedir);
+    free(path);
+
+    return result;
+}
+
+// Parse a line from substitute music configuration file; returns error
+// message or NULL for no error.
+
+static char *ParseSubstituteLine(char *filename, char *line)
+{
+    subst_music_t subst;
+    char *p;
+    int hash_index;
+
+    // Strip out comments if present.
+    p = strchr(line, '#');
+    if (p != NULL)
+    {
+        while (p > line && isspace(*(p - 1)))
+        {
+            --p;
+        }
+        *p = '\0';
+    }
+
+    // Skip leading spaces.
+    for (p = line; *p != '\0' && isspace(*p); ++p);
+
+    // Empty line? This includes comment lines now that comments have
+    // been stripped.
+    if (*p == '\0')
+    {
+        return NULL;
+    }
+
+    // Read hash.
+    hash_index = 0;
+    while (*p != '\0' && *p != '=' && !isspace(*p))
+    {
+        int d1, d2;
+
+        d1 = ParseHexDigit(p[0]);
+        d2 = ParseHexDigit(p[1]);
+
+        if (d1 < 0 || d2 < 0)
+        {
+            return "Invalid hex digit in SHA1 hash";
+        }
+        else if (hash_index >= sizeof(sha1_digest_t))
+        {
+            return "SHA1 hash too long";
+        }
+
+        subst.hash[hash_index] = (d1 << 4) | d2;
+        ++hash_index;
+
+        p += 2;
+    }
+
+    if (hash_index != sizeof(sha1_digest_t))
+    {
+        return "SHA1 hash too short";
+    }
+
+    // Skip spaces.
+    for (; *p != '\0' && isspace(*p); ++p);
+
+    if (*p != '=')
+    {
+        return "Expected '='";
+    }
+
+    ++p;
+
+    // Skip spaces.
+    for (; *p != '\0' && isspace(*p); ++p);
+
+    // We're now at the filename. Cut off trailing space characters.
+    while (strlen(p) > 0 && isspace(p[strlen(p) - 1]))
+    {
+        p[strlen(p) - 1] = '\0';
+    }
+
+    if (strlen(p) == 0)
+    {
+        return "No filename specified for music substitution";
+    }
+
+    // Expand full path and add to our database of substitutes.
+    subst.filename = GetFullPath(filename, p);
+    AddSubstituteMusic(&subst);
+
+    return NULL;
+}
+
+// Read a substitute music configuration file.
+
+static boolean ReadSubstituteConfig(char *filename)
+{
+    char line[128];
+    FILE *fs;
+    char *error;
+    int linenum = 1;
+    int old_subst_music_len;
+
+    fs = fopen(filename, "r");
+
+    if (fs == NULL)
+    {
+        return false;
+    }
+
+    old_subst_music_len = subst_music_len;
+
+    while (!feof(fs))
+    {
+        M_StringCopy(line, "", sizeof(line));
+        fgets(line, sizeof(line), fs);
+
+        error = ParseSubstituteLine(filename, line);
+
+        if (error != NULL)
+        {
+            fprintf(stderr, "%s:%i: Error: %s\n", filename, linenum, error);
+        }
+
+        ++linenum;
+    }
+
+    fclose(fs);
+
+    return true;
+}
+
+// Find substitute configs and try to load them.
+
+static void LoadSubstituteConfigs(void)
+{
+    char *musicdir;
+    char *path;
+    unsigned int i;
+
+    if (!strcmp(configdir, ""))
+    {
+        musicdir = strdup("");
+    }
+    else
+    {
+        musicdir = M_StringJoin(configdir, "music", DIR_SEPARATOR_S, NULL);
+    }
+
+    // Load all music packs. We always load all music substitution packs for
+    // all games. Why? Suppose we have a Doom PWAD that reuses some music from
+    // Heretic. If we have the Heretic music pack loaded, then we get an
+    // automatic substitution.
+    for (i = 0; i < arrlen(subst_config_filenames); ++i)
+    {
+        path = M_StringJoin(musicdir, subst_config_filenames[i], NULL);
+        ReadSubstituteConfig(path);
+        free(path);
+    }
+
+    free(musicdir);
+
+    if (subst_music_len > 0)
+    {
+        printf("Loaded %i music substitutions from config files.\n",
+               subst_music_len);
+    }
+}
+
+// Returns true if the given lump number is a music lump that should
+// be included in substitute configs.
+// Identifying music lumps by name is not feasible; some games (eg.
+// Heretic, Hexen) don't have a common naming pattern for music lumps.
+
+static boolean IsMusicLump(int lumpnum)
+{
+    byte *data;
+    boolean result;
+
+    if (W_LumpLength(lumpnum) < 4)
+    {
+        return false;
+    }
+
+    data = W_CacheLumpNum(lumpnum, PU_STATIC);
+
+    result = memcmp(data, MUS_HEADER_MAGIC, 4) == 0
+          || memcmp(data, MID_HEADER_MAGIC, 4) == 0;
+
+    W_ReleaseLumpNum(lumpnum);
+
+    return result;
+}
+
+// Dump an example config file containing checksums for all MIDI music
+// found in the WAD directory.
+
+static void DumpSubstituteConfig(char *filename)
+{
+    sha1_context_t context;
+    sha1_digest_t digest;
+    char name[9];
+    byte *data;
+    FILE *fs;
+    int lumpnum, h;
+
+    fs = fopen(filename, "w");
+
+    if (fs == NULL)
+    {
+        I_Error("Failed to open %s for writing", filename);
+        return;
+    }
+
+    fprintf(fs, "# Example %s substitute MIDI file.\n\n", PACKAGE_NAME);
+    fprintf(fs, "# SHA1 hash                              = filename\n");
+
+    for (lumpnum = 0; lumpnum < numlumps; ++lumpnum)
+    {
+        strncpy(name, lumpinfo[lumpnum].name, 8);
+        name[8] = '\0';
+
+        if (!IsMusicLump(lumpnum))
+        {
+            continue;
+        }
+
+        // Calculate hash.
+        data = W_CacheLumpNum(lumpnum, PU_STATIC);
+        SHA1_Init(&context);
+        SHA1_Update(&context, data, W_LumpLength(lumpnum));
+        SHA1_Final(digest, &context);
+        W_ReleaseLumpNum(lumpnum);
+
+        // Print line.
+        for (h = 0; h < sizeof(sha1_digest_t); ++h)
+        {
+            fprintf(fs, "%02x", digest[h]);
+        }
+
+        fprintf(fs, " = %s.ogg\n", name);
+    }
+
+    fprintf(fs, "\n");
+    fclose(fs);
+
+    printf("Substitute MIDI config file written to %s.\n", filename);
+    I_Quit();
+}
+
+// If the temp_timidity_cfg config variable is set, generate a "wrapper"
+// config file for Timidity to point to the actual config file. This
+// is needed to inject a "dir" command so that the patches are read
+// relative to the actual config file.
+
+static boolean WriteWrapperTimidityConfig(char *write_path)
+{
+    char *p, *path;
+    FILE *fstream;
+
+    if (!strcmp(timidity_cfg_path, ""))
+    {
+        return false;
+    }
+
+    fstream = fopen(write_path, "w");
+
+    if (fstream == NULL)
+    {
+        return false;
+    }
+
+    p = strrchr(timidity_cfg_path, DIR_SEPARATOR);
+    if (p != NULL)
+    {
+        path = strdup(timidity_cfg_path);
+        path[p - timidity_cfg_path] = '\0';
+        fprintf(fstream, "dir %s\n", path);
+        free(path);
+    }
+
+    fprintf(fstream, "source %s\n", timidity_cfg_path);
+    fclose(fstream);
+
+    return true;
+}
+
+void I_InitTimidityConfig(void)
+{
+    char *env_string;
+    boolean success;
+
+    temp_timidity_cfg = M_TempFile("timidity.cfg");
+
+    success = WriteWrapperTimidityConfig(temp_timidity_cfg);
+
+    // Set the TIMIDITY_CFG environment variable to point to the temporary
+    // config file.
+
+    if (success)
+    {
+        env_string = M_StringJoin("TIMIDITY_CFG=", temp_timidity_cfg, NULL);
+        putenv(env_string);
+    }
+    else
+    {
+        free(temp_timidity_cfg);
+        temp_timidity_cfg = NULL;
+    }
+}
+
+// Remove the temporary config file generated by I_InitTimidityConfig().
+
+static void RemoveTimidityConfig(void)
+{
+    if (temp_timidity_cfg != NULL)
+    {
+        remove(temp_timidity_cfg);
+        free(temp_timidity_cfg);
+    }
+}
+
+// Shutdown music
+
+static void I_SDL_ShutdownMusic(void)
+{
+    if (music_initialized)
+    {
+        Mix_HaltMusic();
+        music_initialized = false;
+
+        if (sdl_was_initialized)
+        {
+            Mix_CloseAudio();
+            SDL_QuitSubSystem(SDL_INIT_AUDIO);
+            sdl_was_initialized = false;
+        }
+    }
+}
+
+static boolean SDLIsInitialized(void)
+{
+    int freq, channels;
+    Uint16 format;
+
+    return Mix_QuerySpec(&freq, &format, &channels) != 0;
+}
+
+// Callback function that is invoked to track current track position.
+void TrackPositionCallback(int chan, void *stream, int len, void *udata)
+{
+    // Position is doubled up twice: for 16-bit samples and for stereo.
+    current_track_pos += len / 4;
+}
+
+// Initialize music subsystem
+static boolean I_SDL_InitMusic(void)
+{
+    int i;
+
+    // SDL_mixer prior to v1.2.11 has a bug that causes crashes
+    // with MIDI playback.  Print a warning message if we are
+    // using an old version.
+
+#ifdef __MACOSX__
+    {
+        const SDL_version *v = Mix_Linked_Version();
+
+        if (SDL_VERSIONNUM(v->major, v->minor, v->patch)
+          < SDL_VERSIONNUM(1, 2, 11))
+        {
+            printf("\n"
+               "                   *** WARNING ***\n"
+               "      You are using an old version of SDL_mixer.\n"
+               "      Music playback on this version may cause crashes\n"
+               "      under OS X and is disabled by default.\n"
+               "\n");
+        }
+    }
+#endif
+
+    //!
+    // @arg <output filename>
+    //
+    // Read all MIDI files from loaded WAD files, dump an example substitution
+    // music config file to the specified filename and quit.
+    //
+
+    i = M_CheckParmWithArgs("-dumpsubstconfig", 1);
+
+    if (i > 0)
+    {
+        DumpSubstituteConfig(myargv[i + 1]);
+    }
+
+    // If SDL_mixer is not initialized, we have to initialize it
+    // and have the responsibility to shut it down later on.
+
+    if (SDLIsInitialized())
+    {
+        music_initialized = true;
+    }
+    else
+    {
+        if (SDL_Init(SDL_INIT_AUDIO) < 0)
+        {
+            fprintf(stderr, "Unable to set up sound.\n");
+        }
+        else if (Mix_OpenAudio(snd_samplerate, AUDIO_S16SYS, 2, 1024) < 0)
+        {
+            fprintf(stderr, "Error initializing SDL_mixer: %s\n",
+                    Mix_GetError());
+            SDL_QuitSubSystem(SDL_INIT_AUDIO);
+        }
+        else
+        {
+            SDL_PauseAudio(0);
+
+            sdl_was_initialized = true;
+            music_initialized = true;
+        }
+    }
+
+    // Once initialization is complete, the temporary Timidity config
+    // file can be removed.
+
+    RemoveTimidityConfig();
+
+    // If snd_musiccmd is set, we need to call Mix_SetMusicCMD to
+    // configure an external music playback program.
+
+    if (strlen(snd_musiccmd) > 0)
+    {
+        Mix_SetMusicCMD(snd_musiccmd);
+    }
+
+    // Register an effect function to track the music position.
+    Mix_RegisterEffect(MIX_CHANNEL_POST, TrackPositionCallback, NULL, NULL);
+
+    // If we're in GENMIDI mode, try to load sound packs.
+    if (snd_musicdevice == SNDDEVICE_GENMIDI)
+    {
+        LoadSubstituteConfigs();
+    }
+
+    return music_initialized;
+}
+
+//
+// SDL_mixer's native MIDI music playing does not pause properly.
+// As a workaround, set the volume to 0 when paused.
+//
+
+static void UpdateMusicVolume(void)
+{
+    int vol;
+
+    if (musicpaused)
+    {
+        vol = 0;
+    }
+    else
+    {
+        vol = (current_music_volume * MIX_MAX_VOLUME) / 127;
+    }
+
+    Mix_VolumeMusic(vol);
+}
+
+// Set music volume (0 - 127)
+
+static void I_SDL_SetMusicVolume(int volume)
+{
+    // Internal state variable.
+    current_music_volume = volume;
+
+    UpdateMusicVolume();
+}
+
+// Start playing a mid
+
+static void I_SDL_PlaySong(void *handle, boolean looping)
+{
+    int loops;
+
+    if (!music_initialized)
+    {
+        return;
+    }
+
+    if (handle == NULL)
+    {
+        return;
+    }
+
+    current_track_music = (Mix_Music *) handle;
+    current_track_loop = looping;
+
+    if (looping)
+    {
+        loops = -1;
+    }
+    else
+    {
+        loops = 1;
+    }
+
+    // Don't loop when playing substitute music, as we do it
+    // ourselves instead.
+    if (playing_substitute && file_metadata.valid)
+    {
+        loops = 1;
+        SDL_LockAudio();
+        current_track_pos = 0;  // start of track
+        SDL_UnlockAudio();
+    }
+
+    Mix_PlayMusic(current_track_music, loops);
+}
+
+static void I_SDL_PauseSong(void)
+{
+    if (!music_initialized)
+    {
+        return;
+    }
+
+    musicpaused = true;
+
+    UpdateMusicVolume();
+}
+
+static void I_SDL_ResumeSong(void)
+{
+    if (!music_initialized)
+    {
+        return;
+    }
+
+    musicpaused = false;
+
+    UpdateMusicVolume();
+}
+
+static void I_SDL_StopSong(void)
+{
+    if (!music_initialized)
+    {
+        return;
+    }
+
+    Mix_HaltMusic();
+    playing_substitute = false;
+    current_track_music = NULL;
+}
+
+static void I_SDL_UnRegisterSong(void *handle)
+{
+    Mix_Music *music = (Mix_Music *) handle;
+
+    if (!music_initialized)
+    {
+        return;
+    }
+
+    if (handle == NULL)
+    {
+        return;
+    }
+
+    Mix_FreeMusic(music);
+}
+
+// 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_SDL_RegisterSong(void *data, int len)
+{
+    char *filename;
+    Mix_Music *music;
+
+    if (!music_initialized)
+    {
+        return NULL;
+    }
+
+    playing_substitute = false;
+
+    // See if we're substituting this MUS for a high-quality replacement.
+    filename = GetSubstituteMusicFile(data, len);
+
+    if (filename != NULL)
+    {
+        music = Mix_LoadMUS(filename);
+
+        if (music == NULL)
+        {
+            // Fall through and play MIDI normally, but print an error
+            // message.
+            fprintf(stderr, "Failed to load substitute music file: %s: %s\n",
+                    filename, Mix_GetError());
+        }
+        else
+        {
+            // Read loop point metadata from the file so that we know where
+            // to loop the music.
+            playing_substitute = true;
+            ReadLoopPoints(filename, &file_metadata);
+            return music;
+        }
+    }
+
+    // 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);
+    }
+
+    // Load the MIDI. In an ideal world we'd be using Mix_LoadMUS_RW()
+    // by now, but Mix_SetMusicCMD() only works with Mix_LoadMUS(), so
+    // we have to generate a temporary file.
+
+    music = Mix_LoadMUS(filename);
+
+    if (music == NULL)
+    {
+        // Failed to load
+
+        fprintf(stderr, "Error loading midi: %s\n", Mix_GetError());
+    }
+
+    // Remove the temporary MIDI file; however, when using an external
+    // MIDI program we can't delete the file. Otherwise, the program
+    // won't find the file to play. This means we leave a mess on
+    // disk :(
+
+    if (strlen(snd_musiccmd) == 0)
+    {
+        remove(filename);
+    }
+
+    free(filename);
+
+    return music;
+}
+
+// Is the song playing?
+static boolean I_SDL_MusicIsPlaying(void)
+{
+    if (!music_initialized)
+    {
+        return false;
+    }
+
+    return Mix_PlayingMusic();
+}
+
+// Get position in substitute music track, in seconds since start of track.
+static double GetMusicPosition(void)
+{
+    unsigned int music_pos;
+    int freq;
+
+    Mix_QuerySpec(&freq, NULL, NULL);
+
+    SDL_LockAudio();
+    music_pos = current_track_pos;
+    SDL_UnlockAudio();
+
+    return (double) music_pos / freq;
+}
+
+static void RestartCurrentTrack(void)
+{
+    double start = (double) file_metadata.start_time
+                 / file_metadata.samplerate_hz;
+
+    // If the track is playing on loop then reset to the start point.
+    // Otherwise we need to stop the track.
+    if (current_track_loop)
+    {
+        // If the track finished we need to restart it.
+        if (current_track_music != NULL)
+        {
+            Mix_PlayMusic(current_track_music, 1);
+        }
+
+        Mix_SetMusicPosition(start);
+        SDL_LockAudio();
+        current_track_pos = file_metadata.start_time;
+        SDL_UnlockAudio();
+    }
+    else
+    {
+        Mix_HaltMusic();
+        current_track_music = NULL;
+        playing_substitute = false;
+    }
+}
+
+// Poll music position; if we have passed the loop point end position
+// then we need to go back.
+static void I_SDL_PollMusic(void)
+{
+    if (playing_substitute && file_metadata.valid)
+    {
+        double end = (double) file_metadata.end_time
+                   / file_metadata.samplerate_hz;
+
+        // If we have reached the loop end point then we have to take action.
+        if (file_metadata.end_time >= 0 && GetMusicPosition() >= end)
+        {
+            RestartCurrentTrack();
+        }
+
+        // Have we reached the actual end of track (not loop end)?
+        if (!Mix_PlayingMusic() && current_track_loop)
+        {
+            RestartCurrentTrack();
+        }
+    }
+}
+
+static snddevice_t music_sdl_devices[] =
+{
+    SNDDEVICE_PAS,
+    SNDDEVICE_GUS,
+    SNDDEVICE_WAVEBLASTER,
+    SNDDEVICE_SOUNDCANVAS,
+    SNDDEVICE_GENMIDI,
+    SNDDEVICE_AWE32,
+};
+
+music_module_t music_sdl_module =
+{
+    music_sdl_devices,
+    arrlen(music_sdl_devices),
+    I_SDL_InitMusic,
+    I_SDL_ShutdownMusic,
+    I_SDL_SetMusicVolume,
+    I_SDL_PauseSong,
+    I_SDL_ResumeSong,
+    I_SDL_RegisterSong,
+    I_SDL_UnRegisterSong,
+    I_SDL_PlaySong,
+    I_SDL_StopSong,
+    I_SDL_MusicIsPlaying,
+    I_SDL_PollMusic,
+};
+
diff --git a/doomgeneric/i_sdlsound.c b/doomgeneric/i_sdlsound.c
new file mode 100644 (file)
index 0000000..e85e2fc
--- /dev/null
@@ -0,0 +1,1076 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+// Copyright(C) 2008 David Flater
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+//     System interface for sound.
+//
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include "SDL2/SDL.h"
+#include "SDL2/SDL_mixer.h"
+
+#ifdef HAVE_LIBSAMPLERATE
+#include <samplerate.h>
+#endif
+
+#include "deh_str.h"
+#include "i_sound.h"
+#include "i_system.h"
+#include "i_swap.h"
+#include "m_argv.h"
+#include "m_misc.h"
+#include "w_wad.h"
+#include "z_zone.h"
+
+#include "doomtype.h"
+
+#define LOW_PASS_FILTER 1
+//#define DEBUG_DUMP_WAVS
+#define NUM_CHANNELS 16
+
+typedef struct allocated_sound_s allocated_sound_t;
+
+struct allocated_sound_s
+{
+    sfxinfo_t *sfxinfo;
+    Mix_Chunk chunk;
+    int use_count;
+    allocated_sound_t *prev, *next;
+};
+
+static boolean setpanning_workaround = false;
+
+static boolean sound_initialized = false;
+
+static sfxinfo_t *channels_playing[NUM_CHANNELS];
+
+static int mixer_freq;
+static Uint16 mixer_format;
+static int mixer_channels;
+static boolean use_sfx_prefix;
+static boolean (*ExpandSoundData)(sfxinfo_t *sfxinfo,
+                                  byte *data,
+                                  int samplerate,
+                                  int length) = NULL;
+
+// Doubly-linked list of allocated sounds.
+// When a sound is played, it is moved to the head, so that the oldest
+// sounds not used recently are at the tail.
+
+static allocated_sound_t *allocated_sounds_head = NULL;
+static allocated_sound_t *allocated_sounds_tail = NULL;
+static int allocated_sounds_size = 0;
+
+int use_libsamplerate = 0;
+
+// Scale factor used when converting libsamplerate floating point numbers
+// to integers. Too high means the sounds can clip; too low means they
+// will be too quiet. This is an amount that should avoid clipping most
+// of the time: with all the Doom IWAD sound effects, at least. If a PWAD
+// is used, clipping might occur.
+
+float libsamplerate_scale = 0.65f;
+
+// Hook a sound into the linked list at the head.
+
+static void AllocatedSoundLink(allocated_sound_t *snd)
+{
+    snd->prev = NULL;
+
+    snd->next = allocated_sounds_head;
+    allocated_sounds_head = snd;
+
+    if (allocated_sounds_tail == NULL)
+    {
+        allocated_sounds_tail = snd;
+    }
+    else
+    {
+        snd->next->prev = snd;
+    }
+}
+
+// Unlink a sound from the linked list.
+
+static void AllocatedSoundUnlink(allocated_sound_t *snd)
+{
+    if (snd->prev == NULL)
+    {
+        allocated_sounds_head = snd->next;
+    }
+    else
+    {
+        snd->prev->next = snd->next;
+    }
+
+    if (snd->next == NULL)
+    {
+        allocated_sounds_tail = snd->prev;
+    }
+    else
+    {
+        snd->next->prev = snd->prev;
+    }
+}
+
+static void FreeAllocatedSound(allocated_sound_t *snd)
+{
+    // Unlink from linked list.
+
+    AllocatedSoundUnlink(snd);
+
+    // Unlink from higher-level code.
+
+    snd->sfxinfo->driver_data = NULL;
+
+    // Keep track of the amount of allocated sound data:
+
+    allocated_sounds_size -= snd->chunk.alen;
+
+    free(snd);
+}
+
+// Search from the tail backwards along the allocated sounds list, find
+// and free a sound that is not in use, to free up memory.  Return true
+// for success.
+
+static boolean FindAndFreeSound(void)
+{
+    allocated_sound_t *snd;
+
+    snd = allocated_sounds_tail;
+
+    while (snd != NULL)
+    {
+        if (snd->use_count == 0)
+        {
+            FreeAllocatedSound(snd);
+            return true;
+        }
+
+        snd = snd->prev;
+    }
+
+    // No available sounds to free...
+
+    return false;
+}
+
+// Enforce SFX cache size limit.  We are just about to allocate "len"
+// bytes on the heap for a new sound effect, so free up some space
+// so that we keep allocated_sounds_size < snd_cachesize
+
+static void ReserveCacheSpace(size_t len)
+{
+    if (snd_cachesize <= 0)
+    {
+        return;
+    }
+
+    // Keep freeing sound effects that aren't currently being played,
+    // until there is enough space for the new sound.
+
+    while (allocated_sounds_size + len > snd_cachesize)
+    {
+        // Free a sound.  If there is nothing more to free, stop.
+
+        if (!FindAndFreeSound())
+        {
+            break;
+        }
+    }
+}
+
+// Allocate a block for a new sound effect.
+
+static Mix_Chunk *AllocateSound(sfxinfo_t *sfxinfo, size_t len)
+{
+    allocated_sound_t *snd;
+
+    // Keep allocated sounds within the cache size.
+
+    ReserveCacheSpace(len);
+
+    // Allocate the sound structure and data.  The data will immediately
+    // follow the structure, which acts as a header.
+
+    do
+    {
+        snd = malloc(sizeof(allocated_sound_t) + len);
+
+        // Out of memory?  Try to free an old sound, then loop round
+        // and try again.
+
+        if (snd == NULL && !FindAndFreeSound())
+        {
+            return NULL;
+        }
+
+    } while (snd == NULL);
+
+    // Skip past the chunk structure for the audio buffer
+
+    snd->chunk.abuf = (byte *) (snd + 1);
+    snd->chunk.alen = len;
+    snd->chunk.allocated = 1;
+    snd->chunk.volume = MIX_MAX_VOLUME;
+
+    snd->sfxinfo = sfxinfo;
+    snd->use_count = 0;
+
+    // driver_data pointer points to the allocated_sound structure.
+
+    sfxinfo->driver_data = snd;
+
+    // Keep track of how much memory all these cached sounds are using...
+
+    allocated_sounds_size += len;
+
+    AllocatedSoundLink(snd);
+
+    return &snd->chunk;
+}
+
+// Lock a sound, to indicate that it may not be freed.
+
+static void LockAllocatedSound(allocated_sound_t *snd)
+{
+    // Increase use count, to stop the sound being freed.
+
+    ++snd->use_count;
+
+    //printf("++ %s: Use count=%i\n", snd->sfxinfo->name, snd->use_count);
+
+    // When we use a sound, re-link it into the list at the head, so
+    // that the oldest sounds fall to the end of the list for freeing.
+
+    AllocatedSoundUnlink(snd);
+    AllocatedSoundLink(snd);
+}
+
+// Unlock a sound to indicate that it may now be freed.
+
+static void UnlockAllocatedSound(allocated_sound_t *snd)
+{
+    if (snd->use_count <= 0)
+    {
+        I_Error("Sound effect released more times than it was locked...");
+    }
+
+    --snd->use_count;
+
+    //printf("-- %s: Use count=%i\n", snd->sfxinfo->name, snd->use_count);
+}
+
+// When a sound stops, check if it is still playing.  If it is not, 
+// we can mark the sound data as CACHE to be freed back for other
+// means.
+
+static void ReleaseSoundOnChannel(int channel)
+{
+    sfxinfo_t *sfxinfo = channels_playing[channel];
+
+    if (sfxinfo == NULL)
+    {
+        return;
+    }
+
+    channels_playing[channel] = NULL;
+
+    UnlockAllocatedSound(sfxinfo->driver_data);
+}
+
+#ifdef HAVE_LIBSAMPLERATE
+
+// Returns the conversion mode for libsamplerate to use.
+
+static int SRC_ConversionMode(void)
+{
+    switch (use_libsamplerate)
+    {
+        // 0 = disabled
+
+        default:
+        case 0:
+            return -1;
+
+        // Ascending numbers give higher quality
+
+        case 1:
+            return SRC_LINEAR;
+        case 2:
+            return SRC_ZERO_ORDER_HOLD;
+        case 3:
+            return SRC_SINC_FASTEST;
+        case 4:
+            return SRC_SINC_MEDIUM_QUALITY;
+        case 5:
+            return SRC_SINC_BEST_QUALITY;
+    }
+}
+
+// libsamplerate-based generic sound expansion function for any sample rate
+//   unsigned 8 bits --> signed 16 bits
+//   mono --> stereo
+//   samplerate --> mixer_freq
+// Returns number of clipped samples.
+// DWF 2008-02-10 with cleanups by Simon Howard.
+
+static boolean ExpandSoundData_SRC(sfxinfo_t *sfxinfo,
+                                   byte *data,
+                                   int samplerate,
+                                   int length)
+{
+    SRC_DATA src_data;
+    uint32_t i, abuf_index=0, clipped=0;
+    uint32_t alen;
+    int retn;
+    int16_t *expanded;
+    Mix_Chunk *chunk;
+
+    src_data.input_frames = length;
+    src_data.data_in = malloc(length * sizeof(float));
+    src_data.src_ratio = (double)mixer_freq / samplerate;
+
+    // We include some extra space here in case of rounding-up.
+    src_data.output_frames = src_data.src_ratio * length + (mixer_freq / 4);
+    src_data.data_out = malloc(src_data.output_frames * sizeof(float));
+
+    assert(src_data.data_in != NULL && src_data.data_out != NULL);
+
+    // Convert input data to floats
+
+    for (i=0; i<length; ++i)
+    {
+        // Unclear whether 128 should be interpreted as "zero" or whether a
+        // symmetrical range should be assumed.  The following assumes a
+        // symmetrical range.
+        src_data.data_in[i] = data[i] / 127.5 - 1;
+    }
+
+    // Do the sound conversion
+
+    retn = src_simple(&src_data, SRC_ConversionMode(), 1);
+    assert(retn == 0);
+
+    // Allocate the new chunk.
+
+    alen = src_data.output_frames_gen * 4;
+
+    chunk = AllocateSound(sfxinfo, src_data.output_frames_gen * 4);
+
+    if (chunk == NULL)
+    {
+        return false;
+    }
+
+    expanded = (int16_t *) chunk->abuf;
+
+    // Convert the result back into 16-bit integers.
+
+    for (i=0; i<src_data.output_frames_gen; ++i)
+    {
+        // libsamplerate does not limit itself to the -1.0 .. 1.0 range on
+        // output, so a multiplier less than INT16_MAX (32767) is required
+        // to avoid overflows or clipping.  However, the smaller the
+        // multiplier, the quieter the sound effects get, and the more you
+        // have to turn down the music to keep it in balance.
+
+        // 22265 is the largest multiplier that can be used to resample all
+        // of the Vanilla DOOM sound effects to 48 kHz without clipping
+        // using SRC_SINC_BEST_QUALITY.  It is close enough (only slightly
+        // too conservative) for SRC_SINC_MEDIUM_QUALITY and
+        // SRC_SINC_FASTEST.  PWADs with interestingly different sound
+        // effects or target rates other than 48 kHz might still result in
+        // clipping--I don't know if there's a limit to it.
+
+        // As the number of clipped samples increases, the signal is
+        // gradually overtaken by noise, with the loudest parts going first.
+        // However, a moderate amount of clipping is often tolerated in the
+        // quest for the loudest possible sound overall.  The results of
+        // using INT16_MAX as the multiplier are not all that bad, but
+        // artifacts are noticeable during the loudest parts.
+
+        float cvtval_f =
+            src_data.data_out[i] * libsamplerate_scale * INT16_MAX;
+        int32_t cvtval_i = cvtval_f + (cvtval_f < 0 ? -0.5 : 0.5);
+
+        // Asymmetrical sound worries me, so we won't use -32768.
+        if (cvtval_i < -INT16_MAX)
+        {
+            cvtval_i = -INT16_MAX;
+            ++clipped;
+        }
+        else if (cvtval_i > INT16_MAX)
+        {
+            cvtval_i = INT16_MAX;
+            ++clipped;
+        }
+
+        // Left and right channels
+
+        expanded[abuf_index++] = cvtval_i;
+        expanded[abuf_index++] = cvtval_i;
+    }
+
+    free(src_data.data_in);
+    free(src_data.data_out);
+
+    if (clipped > 0)
+    {
+        fprintf(stderr, "Sound '%s': clipped %u samples (%0.2f %%)\n", 
+                        sfxinfo->name, clipped,
+                        400.0 * clipped / chunk->alen);
+    }
+
+    return true;
+}
+
+#endif
+
+static boolean ConvertibleRatio(int freq1, int freq2)
+{
+    int ratio;
+
+    if (freq1 > freq2)
+    {
+        return ConvertibleRatio(freq2, freq1);
+    }
+    else if ((freq2 % freq1) != 0)
+    {
+        // Not in a direct ratio
+
+        return false;
+    }
+    else
+    {
+        // Check the ratio is a power of 2
+
+        ratio = freq2 / freq1;
+
+        while ((ratio & 1) == 0)
+        {
+            ratio = ratio >> 1;
+        }
+
+        return ratio == 1;
+    }
+}
+
+#ifdef DEBUG_DUMP_WAVS
+
+// Debug code to dump resampled sound effects to WAV files for analysis.
+
+static void WriteWAV(char *filename, byte *data,
+                     uint32_t length, int samplerate)
+{
+    FILE *wav;
+    unsigned int i;
+    unsigned short s;
+
+    wav = fopen(filename, "wb");
+
+    // Header
+
+    fwrite("RIFF", 1, 4, wav);
+    i = LONG(36 + samplerate);
+    fwrite(&i, 4, 1, wav);
+    fwrite("WAVE", 1, 4, wav);
+
+    // Subchunk 1
+
+    fwrite("fmt ", 1, 4, wav);
+    i = LONG(16);
+    fwrite(&i, 4, 1, wav);           // Length
+    s = SHORT(1);
+    fwrite(&s, 2, 1, wav);           // Format (PCM)
+    s = SHORT(2);
+    fwrite(&s, 2, 1, wav);           // Channels (2=stereo)
+    i = LONG(samplerate);
+    fwrite(&i, 4, 1, wav);           // Sample rate
+    i = LONG(samplerate * 2 * 2);
+    fwrite(&i, 4, 1, wav);           // Byte rate (samplerate * stereo * 16 bit)
+    s = SHORT(2 * 2);
+    fwrite(&s, 2, 1, wav);           // Block align (stereo * 16 bit)
+    s = SHORT(16);
+    fwrite(&s, 2, 1, wav);           // Bits per sample (16 bit)
+
+    // Data subchunk
+
+    fwrite("data", 1, 4, wav);
+    i = LONG(length);
+    fwrite(&i, 4, 1, wav);           // Data length
+    fwrite(data, 1, length, wav);    // Data
+
+    fclose(wav);
+}
+
+#endif
+
+// Generic sound expansion function for any sample rate.
+// Returns number of clipped samples (always 0).
+
+static boolean ExpandSoundData_SDL(sfxinfo_t *sfxinfo,
+                                   byte *data,
+                                   int samplerate,
+                                   int length)
+{
+    SDL_AudioCVT convertor;
+    Mix_Chunk *chunk;
+    uint32_t expanded_length;
+    // Calculate the length of the expanded version of the sample.    
+
+    expanded_length = (uint32_t) ((((uint64_t) length) * mixer_freq) / samplerate);
+
+    // Double up twice: 8 -> 16 bit and mono -> stereo
+
+    expanded_length *= 4;
+
+    // Allocate a chunk in which to expand the sound
+
+    chunk = AllocateSound(sfxinfo, expanded_length);
+
+    if (chunk == NULL)
+    {
+        return false;
+    }
+
+    // If we can, use the standard / optimized SDL conversion routines.
+        Sint16 *expanded = (Sint16 *) chunk->abuf;
+        int expand_ratio;
+        int i;
+
+        // Generic expansion if conversion does not work:
+        //
+        // SDL's audio conversion only works for rate conversions that are
+        // powers of 2; if the two formats are not in a direct power of 2
+        // ratio, do this naive conversion instead.
+
+        // number of samples in the converted sound
+
+        expanded_length = ((uint64_t) length * mixer_freq) / samplerate;
+        expand_ratio = (length << 8) / expanded_length;
+
+        for (i=0; i<expanded_length; ++i)
+        {
+            Sint16 sample;
+            int src;
+
+            src = (i * expand_ratio) >> 8;
+
+            sample = data[src] | (data[src] << 8);
+            sample -= 32768;
+
+            // expand 8->16 bits, mono->stereo
+
+            expanded[i * 2] = expanded[i * 2 + 1] = sample;
+        }
+
+#ifdef LOW_PASS_FILTER
+        // Perform a low-pass filter on the upscaled sound to filter
+        // out high-frequency noise from the conversion process.
+
+        {
+            float rc, dt, alpha;
+
+            // Low-pass filter for cutoff frequency f:
+            //
+            // For sampling rate r, dt = 1 / r
+            // rc = 1 / 2*pi*f
+            // alpha = dt / (rc + dt)
+
+            // Filter to the half sample rate of the original sound effect
+            // (maximum frequency, by nyquist)
+
+            dt = 1.0f / mixer_freq;
+            rc = 1.0f / (3.14f * samplerate);
+            alpha = dt / (rc + dt);
+
+            // Both channels are processed in parallel, hence [i-2]:
+
+            for (i=2; i<expanded_length * 2; ++i)
+            {
+                expanded[i] = (Sint16) (alpha * expanded[i]
+                                      + (1 - alpha) * expanded[i-2]);
+            }
+        }
+#endif /* #ifdef LOW_PASS_FILTER */
+
+    return true;
+}
+
+// Load and convert a sound effect
+// Returns true if successful
+
+static boolean CacheSFX(sfxinfo_t *sfxinfo)
+{
+    int lumpnum;
+    unsigned int lumplen;
+    int samplerate;
+    unsigned int length;
+    byte *data;
+
+    // need to load the sound
+
+    lumpnum = sfxinfo->lumpnum;
+    data = W_CacheLumpNum(lumpnum, PU_STATIC);
+    lumplen = W_LumpLength(lumpnum);
+
+    // Check the header, and ensure this is a valid sound
+
+    if (lumplen < 8
+     || data[0] != 0x03 || data[1] != 0x00)
+    {
+        // Invalid sound
+
+        return false;
+    }
+
+    // 16 bit sample rate field, 32 bit length field
+
+    samplerate = (data[3] << 8) | data[2];
+    length = (data[7] << 24) | (data[6] << 16) | (data[5] << 8) | data[4];
+
+    // If the header specifies that the length of the sound is greater than
+    // the length of the lump itself, this is an invalid sound lump
+
+    // We also discard sound lumps that are less than 49 samples long,
+    // as this is how DMX behaves - although the actual cut-off length
+    // seems to vary slightly depending on the sample rate.  This needs
+    // further investigation to better understand the correct
+    // behavior.
+
+    if (length > lumplen - 8 || length <= 48)
+    {
+        return false;
+    }
+
+    // The DMX sound library seems to skip the first 16 and last 16
+    // bytes of the lump - reason unknown.
+
+    data += 16;
+    length -= 32;
+
+    // Sample rate conversion
+
+    if (!ExpandSoundData(sfxinfo, data + 8, samplerate, length))
+    {
+        return false;
+    }
+
+#ifdef DEBUG_DUMP_WAVS
+    {
+        char filename[16];
+
+        M_snprintf(filename, sizeof(filename), "%s.wav",
+                   DEH_String(S_sfx[sound].name));
+        WriteWAV(filename, sound_chunks[sound].abuf,
+                 sound_chunks[sound].alen, mixer_freq);
+    }
+#endif
+
+    // don't need the original lump any more
+  
+    W_ReleaseLumpNum(lumpnum);
+
+    return true;
+}
+
+static void GetSfxLumpName(sfxinfo_t *sfx, char *buf, size_t buf_len)
+{
+    // Linked sfx lumps? Get the lump number for the sound linked to.
+
+    if (sfx->link != NULL)
+    {
+        sfx = sfx->link;
+    }
+
+    // Doom adds a DS* prefix to sound lumps; Heretic and Hexen don't
+    // do this.
+
+    if (use_sfx_prefix)
+    {
+        M_snprintf(buf, buf_len, "ds%s", DEH_String(sfx->name));
+    }
+    else
+    {
+        M_StringCopy(buf, DEH_String(sfx->name), buf_len);
+    }
+}
+
+#ifdef HAVE_LIBSAMPLERATE
+
+// Preload all the sound effects - stops nasty ingame freezes
+
+static void I_SDL_PrecacheSounds(sfxinfo_t *sounds, int num_sounds)
+{
+    char namebuf[9];
+    int i;
+
+    // Don't need to precache the sounds unless we are using libsamplerate.
+
+    if (use_libsamplerate == 0)
+    {
+       return;
+    }
+
+    printf("I_SDL_PrecacheSounds: Precaching all sound effects..");
+
+    for (i=0; i<num_sounds; ++i)
+    {
+        if ((i % 6) == 0)
+        {
+            printf(".");
+            fflush(stdout);
+        }
+
+        GetSfxLumpName(&sounds[i], namebuf, sizeof(namebuf));
+
+        sounds[i].lumpnum = W_CheckNumForName(namebuf);
+
+        if (sounds[i].lumpnum != -1)
+        {
+            CacheSFX(&sounds[i]);
+        }
+    }
+
+    printf("\n");
+}
+
+#else
+
+static void I_SDL_PrecacheSounds(sfxinfo_t *sounds, int num_sounds)
+{
+    // no-op
+}
+
+#endif
+
+// Load a SFX chunk into memory and ensure that it is locked.
+
+static boolean LockSound(sfxinfo_t *sfxinfo)
+{
+    // If the sound isn't loaded, load it now
+
+    if (sfxinfo->driver_data == NULL)
+    {
+        if (!CacheSFX(sfxinfo))
+        {
+            return false;
+        }
+    }
+
+    LockAllocatedSound(sfxinfo->driver_data);
+
+    return true;
+}
+
+//
+// Retrieve the raw data lump index
+//  for a given SFX name.
+//
+
+static int I_SDL_GetSfxLumpNum(sfxinfo_t *sfx)
+{
+    char namebuf[9];
+
+    GetSfxLumpName(sfx, namebuf, sizeof(namebuf));
+
+    return W_GetNumForName(namebuf);
+}
+
+static void I_SDL_UpdateSoundParams(int handle, int vol, int sep)
+{
+    int left, right;
+
+    if (!sound_initialized || handle < 0 || handle >= NUM_CHANNELS)
+    {
+        return;
+    }
+
+    left = ((254 - sep) * vol) / 127;
+    right = ((sep) * vol) / 127;
+
+    if (left < 0) left = 0;
+    else if ( left > 255) left = 255;
+    if (right < 0) right = 0;
+    else if (right > 255) right = 255;
+
+    // SDL_mixer version 1.2.8 and earlier has a bug in the Mix_SetPanning
+    // function.  A workaround is to call Mix_UnregisterAllEffects for
+    // the channel before calling it.  This is undesirable as it may lead
+    // to the channel volumes resetting briefly.
+
+    if (setpanning_workaround)
+    {
+        Mix_UnregisterAllEffects(handle);
+    }
+
+    Mix_SetPanning(handle, left, right);
+}
+
+//
+// Starting a sound means adding it
+//  to the current list of active sounds
+//  in the internal channels.
+// As the SFX info struct contains
+//  e.g. a pointer to the raw data,
+//  it is ignored.
+// As our sound handling does not handle
+//  priority, it is ignored.
+// Pitching (that is, increased speed of playback)
+//  is set, but currently not used by mixing.
+//
+
+static int I_SDL_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep)
+{
+    allocated_sound_t *snd;
+
+    if (!sound_initialized || channel < 0 || channel >= NUM_CHANNELS)
+    {
+        return -1;
+    }
+
+    // Release a sound effect if there is already one playing
+    // on this channel
+
+    ReleaseSoundOnChannel(channel);
+
+    // Get the sound data
+
+    if (!LockSound(sfxinfo))
+    {
+       return -1;
+    }
+
+    snd = sfxinfo->driver_data;
+
+    // play sound
+
+    Mix_PlayChannelTimed(channel, &snd->chunk, 0, -1);
+
+    channels_playing[channel] = sfxinfo;
+
+    // set separation, etc.
+    I_SDL_UpdateSoundParams(channel, vol, sep);
+
+    return channel;
+}
+
+static void I_SDL_StopSound(int handle)
+{
+    if (!sound_initialized || handle < 0 || handle >= NUM_CHANNELS)
+    {
+        return;
+    }
+
+    Mix_HaltChannel(handle);
+
+    // Sound data is no longer needed; release the
+    // sound data being used for this channel
+
+    ReleaseSoundOnChannel(handle);
+}
+
+
+static boolean I_SDL_SoundIsPlaying(int handle)
+{
+    if (!sound_initialized || handle < 0 || handle >= NUM_CHANNELS)
+    {
+        return false;
+    }
+
+    return Mix_Playing(handle);
+}
+
+// 
+// Periodically called to update the sound system
+//
+
+static void I_SDL_UpdateSound(void)
+{
+    int i;
+
+    // Check all channels to see if a sound has finished
+
+    for (i=0; i<NUM_CHANNELS; ++i)
+    {
+        if (channels_playing[i] && !I_SDL_SoundIsPlaying(i))
+        {
+            // Sound has finished playing on this channel,
+            // but sound data has not been released to cache
+            
+            ReleaseSoundOnChannel(i);
+        }
+    }
+}
+
+static void I_SDL_ShutdownSound(void)
+{    
+    if (!sound_initialized)
+    {
+        return;
+    }
+
+    Mix_CloseAudio();
+    SDL_QuitSubSystem(SDL_INIT_AUDIO);
+
+    sound_initialized = false;
+}
+
+// Calculate slice size, based on snd_maxslicetime_ms.
+// The result must be a power of two.
+
+static int GetSliceSize(void)
+{
+    int limit;
+    int n;
+
+    limit = (snd_samplerate * snd_maxslicetime_ms) / 1000;
+
+    // Try all powers of two, not exceeding the limit.
+
+    for (n=0;; ++n)
+    {
+        // 2^n <= limit < 2^n+1 ?
+
+        if ((1 << (n + 1)) > limit)
+        {
+            return (1 << n);
+        }
+    }
+
+    // Should never happen?
+
+    return 1024;
+}
+
+static boolean I_SDL_InitSound(boolean _use_sfx_prefix)
+{
+    int i;
+
+    use_sfx_prefix = _use_sfx_prefix;
+
+    // No sounds yet
+
+    for (i=0; i<NUM_CHANNELS; ++i)
+    {
+        channels_playing[i] = NULL;
+    }
+
+    if (SDL_Init(SDL_INIT_AUDIO) < 0)
+    {
+        fprintf(stderr, "Unable to set up sound.\n");
+        return false;
+    }
+
+    if (Mix_OpenAudio(snd_samplerate, AUDIO_S16SYS, 2, GetSliceSize()) < 0)
+    {
+        fprintf(stderr, "Error initialising SDL_mixer: %s\n", Mix_GetError());
+        return false;
+    }
+
+    ExpandSoundData = ExpandSoundData_SDL;
+
+    Mix_QuerySpec(&mixer_freq, &mixer_format, &mixer_channels);
+
+#ifdef HAVE_LIBSAMPLERATE
+    if (use_libsamplerate != 0)
+    {
+        if (SRC_ConversionMode() < 0)
+        {
+            I_Error("I_SDL_InitSound: Invalid value for use_libsamplerate: %i",
+                    use_libsamplerate);
+        }
+
+        ExpandSoundData = ExpandSoundData_SRC;
+    }
+#else
+    if (use_libsamplerate != 0)
+    {
+        fprintf(stderr, "I_SDL_InitSound: use_libsamplerate=%i, but "
+                        "libsamplerate support not compiled in.\n",
+                        use_libsamplerate);
+    }
+#endif
+
+    // SDL_mixer version 1.2.8 and earlier has a bug in the Mix_SetPanning
+    // function that can cause the game to lock up.  If we're using an old
+    // version, we need to apply a workaround.  But the workaround has its
+    // own drawbacks ...
+
+    {
+        const SDL_version *mixer_version;
+        int v;
+
+        mixer_version = Mix_Linked_Version();
+        v = SDL_VERSIONNUM(mixer_version->major,
+                           mixer_version->minor,
+                           mixer_version->patch);
+
+        if (v <= SDL_VERSIONNUM(1, 2, 8))
+        {
+            setpanning_workaround = true;
+            fprintf(stderr, "\n"
+              "ATTENTION: You are using an old version of SDL_mixer!\n"
+              "           This version has a bug that may cause "
+                          "your sound to stutter.\n"
+              "           Please upgrade to a newer version!\n"
+              "\n");
+        }
+    }
+
+    Mix_AllocateChannels(NUM_CHANNELS);
+
+    SDL_PauseAudio(0);
+
+    sound_initialized = true;
+
+    return true;
+}
+
+static snddevice_t sound_sdl_devices[] = 
+{
+    SNDDEVICE_SB,
+    SNDDEVICE_PAS,
+    SNDDEVICE_GUS,
+    SNDDEVICE_WAVEBLASTER,
+    SNDDEVICE_SOUNDCANVAS,
+    SNDDEVICE_AWE32,
+};
+
+sound_module_t sound_sdl_module = 
+{
+    sound_sdl_devices,
+    arrlen(sound_sdl_devices),
+    I_SDL_InitSound,
+    I_SDL_ShutdownSound,
+    I_SDL_GetSfxLumpNum,
+    I_SDL_UpdateSound,
+    I_SDL_UpdateSoundParams,
+    I_SDL_StartSound,
+    I_SDL_StopSound,
+    I_SDL_SoundIsPlaying,
+    I_SDL_PrecacheSounds,
+};
+
index 71947d21101626f3863a9a0671a7a4c1f3ce4b2f..ade161dea56c70015be07582f35047af98dc76f8 100644 (file)
@@ -18,8 +18,8 @@
 #include <stdio.h>
 #include <stdlib.h>
 
-#ifdef ORIGCODE
-#include "SDL_mixer.h"
+#ifdef FEATURE_SOUND
+#include "SDL2/SDL_mixer.h"
 #endif
 
 #include "config.h"
@@ -80,21 +80,18 @@ extern char *timidity_cfg_path;
 // so that the config file can be shared between chocolate
 // doom and doom.exe
 
-#if ORIGCODE
 static int snd_sbport = 0;
 static int snd_sbirq = 0;
 static int snd_sbdma = 0;
 static int snd_mport = 0;
-#endif
 
 // Compiled-in sound modules:
 
 static sound_module_t *sound_modules[] = 
 {
-#ifdef FEATURE_SOUND
+    #ifdef FEATURE_SOUND
     &sound_sdl_module,
-    &sound_pcsound_module,
-#endif
+    #endif
     NULL,
 };
 
@@ -102,10 +99,9 @@ static sound_module_t *sound_modules[] =
 
 static music_module_t *music_modules[] =
 {
-#ifdef FEATURE_SOUND
+    #ifdef FEATURE_SOUND
     &music_sdl_module,
-    &music_opl_module,
-#endif
+    #endif
     NULL,
 };
 
@@ -162,9 +158,9 @@ static void InitMusicModule(void)
 {
     int i;
 
-    music_module = NULL;
+    music_module = &music_sdl_module; return;
 
-    for (i=0; music_modules[i] != NULL; ++i)
+    /*for (i=0; music_modules[i] != NULL; ++i)
     {
         // Is the music device in the list of devices supported
         // by this module?
@@ -181,7 +177,7 @@ static void InitMusicModule(void)
                 return;
             }
         }
-    }
+    }*/
 }
 
 //
@@ -243,6 +239,7 @@ void I_InitSound(boolean use_sfx_prefix)
             InitMusicModule();
         }
     }
+
 }
 
 void I_ShutdownSound(void)
@@ -356,6 +353,10 @@ void I_PrecacheSounds(sfxinfo_t *sounds, int num_sounds)
 
 void I_InitMusic(void)
 {
+    if(music_module != NULL)
+    {
+        music_module->Init();
+    }
 }
 
 void I_ShutdownMusic(void)
@@ -433,11 +434,11 @@ boolean I_MusicIsPlaying(void)
     {
         return false;
     }
+    
 }
 
 void I_BindSoundVariables(void)
 {
-#ifdef ORIGCODE
     extern int use_libsamplerate;
     extern float libsamplerate_scale;
 
@@ -451,11 +452,6 @@ void I_BindSoundVariables(void)
     M_BindVariable("snd_musiccmd",      &snd_musiccmd);
     M_BindVariable("snd_samplerate",    &snd_samplerate);
     M_BindVariable("snd_cachesize",     &snd_cachesize);
-    M_BindVariable("opl_io_port",       &opl_io_port);
-
-    M_BindVariable("timidity_cfg_path", &timidity_cfg_path);
-    M_BindVariable("gus_patch_path",    &gus_patch_path);
-    M_BindVariable("gus_ram_kb",        &gus_ram_kb);
 
 #ifdef FEATURE_SOUND
     M_BindVariable("use_libsamplerate",   &use_libsamplerate);
@@ -465,18 +461,5 @@ void I_BindSoundVariables(void)
     // 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
-#endif
 }
 
index d1fd754a18eae4d88d6f4d1f3464d777399971a3..0dbcdb5e7e7420ba7e51d980b1633e6eed6724e3 100644 (file)
@@ -20,8 +20,7 @@
 #ifndef __I_SWAP__
 #define __I_SWAP__
 
-#ifdef ORIGCODE
-#include "SDL_endian.h"
+#include <SDL2/SDL_endian.h>
 
 // Endianess handling.
 // WAD files are stored little endian.
 #define SYS_BIG_ENDIAN
 #endif
 
-#else
-       
-#define SHORT(x)  ((signed short) (x))
-#define LONG(x)   ((signed int) (x))
+// cosmito from lsdldoom
+#define doom_swap_s(x) \
+        ((short int)((((unsigned short int)(x) & 0x00ff) << 8) | \
+                              (((unsigned short int)(x) & 0xff00) >> 8))) 
 
-#define SYS_LITTLE_ENDIAN
 
+#if ( SDL_BYTEORDER == SDL_BIG_ENDIAN )
+#define doom_wtohs(x) doom_swap_s(x)
+#else
+#define doom_wtohs(x) (short int)(x)
 #endif
+
 #endif
 
index 2d6a7cf279cd4701f0fe18a120ec64575ee776ce..27beaaf1dbbce8f184bb61d43d084524f56bab56 100644 (file)
@@ -22,6 +22,8 @@
 #include "doomgeneric.h"
 
 #include <stdarg.h>
+#include <stdio.h>
+#include <SDL2/SDL.h>
 //#include <sys/time.h>
 //#include <unistd.h>
 
@@ -50,7 +52,7 @@ int  I_GetTime (void)
 
     ticks -= basetime;
 
-    return (ticks * TICRATE) / 1000;    
+    return (ticks * TICRATE) / 1000;
 }
 
 
@@ -90,6 +92,12 @@ void I_InitTimer(void)
 {
     // initialize timer
 
-    //SDL_Init(SDL_INIT_TIMER);
+    printf("I_InitTimer: Setting up timer.\n");
+    if (SDL_Init(SDL_INIT_TIMER) < 0)
+    {
+        printf("SDL_Init failed: %s\n", SDL_GetError());
+        atexit(SDL_Quit);
+        exit(1);
+    }
 }
 
diff --git a/doomgeneric/mus2mid.c b/doomgeneric/mus2mid.c
new file mode 100644 (file)
index 0000000..bcbeb00
--- /dev/null
@@ -0,0 +1,737 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+// Copyright(C) 2006 Ben Ryves 2006
+//
+// 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.
+//
+// mus2mid.c - Ben Ryves 2006 - http://benryves.com - benryves@benryves.com
+// Use to convert a MUS file into a single track, type 0 MIDI file.
+
+#include <stdio.h>
+
+#include "doomtype.h"
+#include "i_swap.h"
+
+#include "memio.h"
+#include "mus2mid.h"
+
+#define NUM_CHANNELS 16
+
+#define MIDI_PERCUSSION_CHAN 9
+#define MUS_PERCUSSION_CHAN 15
+
+// MUS event codes
+typedef enum
+{
+    mus_releasekey = 0x00,
+    mus_presskey = 0x10,
+    mus_pitchwheel = 0x20,
+    mus_systemevent = 0x30,
+    mus_changecontroller = 0x40,
+    mus_scoreend = 0x60
+} musevent;
+
+// MIDI event codes
+typedef enum
+{
+    midi_releasekey = 0x80,
+    midi_presskey = 0x90,
+    midi_aftertouchkey = 0xA0,
+    midi_changecontroller = 0xB0,
+    midi_changepatch = 0xC0,
+    midi_aftertouchchannel = 0xD0,
+    midi_pitchwheel = 0xE0
+} midievent;
+
+// Structure to hold MUS file header
+typedef struct 
+{
+    byte id[4];
+    unsigned short scorelength;
+    unsigned short scorestart;
+    unsigned short primarychannels;
+    unsigned short secondarychannels;
+    unsigned short instrumentcount;
+} musheader;
+
+// Standard MIDI type 0 header + track header
+static const byte midiheader[] =
+{
+    'M', 'T', 'h', 'd',     // Main header
+    0x00, 0x00, 0x00, 0x06, // Header size
+    0x00, 0x00,             // MIDI type (0)
+    0x00, 0x01,             // Number of tracks
+    0x00, 0x46,             // Resolution
+    'M', 'T', 'r', 'k',        // Start of track
+    0x00, 0x00, 0x00, 0x00  // Placeholder for track length
+};
+
+// Cached channel velocities
+static byte channelvelocities[] =
+{
+    127, 127, 127, 127, 127, 127, 127, 127,
+    127, 127, 127, 127, 127, 127, 127, 127
+};
+
+// Timestamps between sequences of MUS events
+
+static unsigned int queuedtime = 0;
+
+// Counter for the length of the track
+
+static unsigned int tracksize;
+
+static const byte controller_map[] =
+{
+    0x00, 0x20, 0x01, 0x07, 0x0A, 0x0B, 0x5B, 0x5D,
+    0x40, 0x43, 0x78, 0x7B, 0x7E, 0x7F, 0x79
+};
+
+static int channel_map[NUM_CHANNELS];
+
+// Write timestamp to a MIDI file.
+
+static boolean WriteTime(unsigned int time, MEMFILE *midioutput)
+{
+    unsigned int buffer = time & 0x7F;
+    byte writeval;
+
+    while ((time >>= 7) != 0)
+    {
+        buffer <<= 8;
+        buffer |= ((time & 0x7F) | 0x80);
+    }
+
+    for (;;)
+    {
+        writeval = (byte)(buffer & 0xFF);
+
+        if (mem_fwrite(&writeval, 1, 1, midioutput) != 1)
+        {
+            return true;
+        }
+
+        ++tracksize;
+
+        if ((buffer & 0x80) != 0)
+        {
+            buffer >>= 8;
+        }
+        else
+        {
+            queuedtime = 0;
+            return false;
+        }
+    }
+}
+
+
+// Write the end of track marker
+static boolean WriteEndTrack(MEMFILE *midioutput)
+{
+    byte endtrack[] = {0xFF, 0x2F, 0x00};
+
+    if (WriteTime(queuedtime, midioutput))
+    {
+        return true;
+    }
+
+    if (mem_fwrite(endtrack, 1, 3, midioutput) != 3)
+    {
+        return true;
+    }
+
+    tracksize += 3;
+    return false;
+}
+
+// Write a key press event
+static boolean WritePressKey(byte channel, byte key,
+                             byte velocity, MEMFILE *midioutput)
+{
+    byte working = midi_presskey | channel;
+
+    if (WriteTime(queuedtime, midioutput))
+    {
+        return true;
+    }
+
+    if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+    {
+        return true;
+    }
+
+    working = key & 0x7F;
+
+    if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+    {
+        return true;
+    }
+
+    working = velocity & 0x7F;
+
+    if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+    {
+        return true;
+    }
+
+    tracksize += 3;
+
+    return false;
+}
+
+// Write a key release event
+static boolean WriteReleaseKey(byte channel, byte key,
+                               MEMFILE *midioutput)
+{
+    byte working = midi_releasekey | channel;
+
+    if (WriteTime(queuedtime, midioutput))
+    {
+        return true;
+    }
+
+    if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+    {
+        return true;
+    }
+
+    working = key & 0x7F;
+
+    if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+    {
+        return true;
+    }
+
+    working = 0;
+
+    if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+    {
+        return true;
+    }
+
+    tracksize += 3;
+
+    return false;
+}
+
+// Write a pitch wheel/bend event
+static boolean WritePitchWheel(byte channel, short wheel,
+                               MEMFILE *midioutput)
+{
+    byte working = midi_pitchwheel | channel;
+
+    if (WriteTime(queuedtime, midioutput))
+    {
+        return true;
+    }
+
+    if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+    {
+        return true;
+    }
+
+    working = wheel & 0x7F;
+
+    if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+    {
+        return true;
+    }
+
+    working = (wheel >> 7) & 0x7F;
+
+    if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+    {
+        return true;
+    }
+
+    tracksize += 3;
+    return false;
+}
+
+// Write a patch change event
+static boolean WriteChangePatch(byte channel, byte patch,
+                                MEMFILE *midioutput)
+{
+    byte working = midi_changepatch | channel;
+
+    if (WriteTime(queuedtime, midioutput))
+    {
+        return true;
+    }
+
+    if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+    {
+        return true;
+    }
+
+    working = patch & 0x7F;
+
+    if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+    {
+        return true;
+    }
+
+    tracksize += 2;
+
+    return false;
+}
+
+// Write a valued controller change event
+
+static boolean WriteChangeController_Valued(byte channel,
+                                            byte control,
+                                            byte value,
+                                            MEMFILE *midioutput)
+{
+    byte working = midi_changecontroller | channel;
+
+    if (WriteTime(queuedtime, midioutput))
+    {
+        return true;
+    }
+
+    if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+    {
+        return true;
+    }
+
+    working = control & 0x7F;
+
+    if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+    {
+        return true;
+    }
+
+    // Quirk in vanilla DOOM? MUS controller values should be
+    // 7-bit, not 8-bit.
+
+    working = value;// & 0x7F;
+
+    // Fix on said quirk to stop MIDI players from complaining that
+    // the value is out of range:
+
+    if (working & 0x80)
+    {
+        working = 0x7F;
+    }
+
+    if (mem_fwrite(&working, 1, 1, midioutput) != 1)
+    {
+        return true;
+    }
+
+    tracksize += 3;
+
+    return false;
+}
+
+// Write a valueless controller change event
+static boolean WriteChangeController_Valueless(byte channel,
+                                               byte control,
+                                               MEMFILE *midioutput)
+{
+    return WriteChangeController_Valued(channel, control, 0,
+                                             midioutput);
+}
+
+// Allocate a free MIDI channel.
+
+static int AllocateMIDIChannel(void)
+{
+    int result;
+    int max;
+    int i;
+
+    // Find the current highest-allocated channel.
+
+    max = -1;
+
+    for (i=0; i<NUM_CHANNELS; ++i)
+    {
+        if (channel_map[i] > max)
+        {
+            max = channel_map[i];
+        }
+    }
+
+    // max is now equal to the highest-allocated MIDI channel.  We can
+    // now allocate the next available channel.  This also works if
+    // no channels are currently allocated (max=-1)
+
+    result = max + 1;
+
+    // Don't allocate the MIDI percussion channel!
+
+    if (result == MIDI_PERCUSSION_CHAN)
+    {
+        ++result;
+    }
+
+    return result;
+}
+
+// Given a MUS channel number, get the MIDI channel number to use
+// in the outputted file.
+
+static int GetMIDIChannel(int mus_channel, MEMFILE *midioutput)
+{
+    // Find the MIDI channel to use for this MUS channel.
+    // MUS channel 15 is the percusssion channel.
+
+    if (mus_channel == MUS_PERCUSSION_CHAN)
+    {
+        return MIDI_PERCUSSION_CHAN;
+    }
+    else
+    {
+        // If a MIDI channel hasn't been allocated for this MUS channel
+        // yet, allocate the next free MIDI channel.
+
+        if (channel_map[mus_channel] == -1)
+        {
+            channel_map[mus_channel] = AllocateMIDIChannel();
+
+            // First time using the channel, send an "all notes off"
+            // event. This fixes "The D_DDTBLU disease" described here:
+            // https://www.doomworld.com/vb/source-ports/66802-the
+            WriteChangeController_Valueless(channel_map[mus_channel], 0x7b,
+                                            midioutput);
+        }
+
+        return channel_map[mus_channel];
+    }
+}
+
+static boolean ReadMusHeader(MEMFILE *file, musheader *header)
+{
+    boolean result;
+
+    result = mem_fread(&header->id, sizeof(byte), 4, file) == 4
+          && mem_fread(&header->scorelength, sizeof(short), 1, file) == 1
+          && mem_fread(&header->scorestart, sizeof(short), 1, file) == 1
+          && mem_fread(&header->primarychannels, sizeof(short), 1, file) == 1
+          && mem_fread(&header->secondarychannels, sizeof(short), 1, file) == 1
+          && mem_fread(&header->instrumentcount, sizeof(short), 1, file) == 1;
+
+    if (result)
+    {
+        header->scorelength = SHORT(header->scorelength);
+        header->scorestart = SHORT(header->scorestart);
+        header->primarychannels = SHORT(header->primarychannels);
+        header->secondarychannels = SHORT(header->secondarychannels);
+        header->instrumentcount = SHORT(header->instrumentcount);
+    }
+
+    return result;
+}
+
+
+// Read a MUS file from a stream (musinput) and output a MIDI file to
+// a stream (midioutput).
+//
+// Returns 0 on success or 1 on failure.
+
+boolean mus2mid(MEMFILE *musinput, MEMFILE *midioutput)
+{
+    // Header for the MUS file
+    musheader musfileheader;
+
+    // Descriptor for the current MUS event
+    byte eventdescriptor;
+    int channel; // Channel number
+    musevent event;
+
+
+    // Bunch of vars read from MUS lump
+    byte key;
+    byte controllernumber;
+    byte controllervalue;
+
+    // Buffer used for MIDI track size record
+    byte tracksizebuffer[4];
+
+    // Flag for when the score end marker is hit.
+    int hitscoreend = 0;
+
+    // Temp working byte
+    byte working;
+    // Used in building up time delays
+    unsigned int timedelay;
+
+    // Initialise channel map to mark all channels as unused.
+
+    for (channel=0; channel<NUM_CHANNELS; ++channel)
+    {
+        channel_map[channel] = -1;
+    }
+
+    // Grab the header
+
+    if (!ReadMusHeader(musinput, &musfileheader))
+    {
+        return true;
+    }
+
+// [crispy] enable MUS format header check
+#define CHECK_MUS_HEADER
+#ifdef CHECK_MUS_HEADER
+    // Check MUS header
+    if (musfileheader.id[0] != 'M'
+     || musfileheader.id[1] != 'U'
+     || musfileheader.id[2] != 'S'
+     || musfileheader.id[3] != 0x1A)
+    {
+        return true;
+    }
+#endif
+
+    // Seek to where the data is held
+    if (mem_fseek(musinput, (long)musfileheader.scorestart,
+                  MEM_SEEK_SET) != 0)
+    {
+        return true;
+    }
+
+    // So, we can assume the MUS file is faintly legit. Let's start
+    // writing MIDI data...
+
+    mem_fwrite(midiheader, 1, sizeof(midiheader), midioutput);
+    tracksize = 0;
+
+    // Now, process the MUS file:
+    while (!hitscoreend)
+    {
+        // Handle a block of events:
+
+        while (!hitscoreend)
+        {
+            // Fetch channel number and event code:
+
+            if (mem_fread(&eventdescriptor, 1, 1, musinput) != 1)
+            {
+                return true;
+            }
+
+            channel = GetMIDIChannel(eventdescriptor & 0x0F, midioutput);
+            event = eventdescriptor & 0x70;
+
+            switch (event)
+            {
+                case mus_releasekey:
+                    if (mem_fread(&key, 1, 1, musinput) != 1)
+                    {
+                        return true;
+                    }
+
+                    if (WriteReleaseKey(channel, key, midioutput))
+                    {
+                        return true;
+                    }
+
+                    break;
+
+                case mus_presskey:
+                    if (mem_fread(&key, 1, 1, musinput) != 1)
+                    {
+                        return true;
+                    }
+
+                    if (key & 0x80)
+                    {
+                        if (mem_fread(&channelvelocities[channel], 1, 1, musinput) != 1)
+                        {
+                            return true;
+                        }
+
+                        channelvelocities[channel] &= 0x7F;
+                    }
+
+                    if (WritePressKey(channel, key,
+                                      channelvelocities[channel], midioutput))
+                    {
+                        return true;
+                    }
+
+                    break;
+
+                case mus_pitchwheel:
+                    if (mem_fread(&key, 1, 1, musinput) != 1)
+                    {
+                        break;
+                    }
+                    if (WritePitchWheel(channel, (short)(key * 64), midioutput))
+                    {
+                        return true;
+                    }
+
+                    break;
+
+                case mus_systemevent:
+                    if (mem_fread(&controllernumber, 1, 1, musinput) != 1)
+                    {
+                        return true;
+                    }
+                    if (controllernumber < 10 || controllernumber > 14)
+                    {
+                        return true;
+                    }
+
+                    if (WriteChangeController_Valueless(channel,
+                                                        controller_map[controllernumber],
+                                                        midioutput))
+                    {
+                        return true;
+                    }
+
+                    break;
+
+                case mus_changecontroller:
+                    if (mem_fread(&controllernumber, 1, 1, musinput) != 1)
+                    {
+                        return true;
+                    }
+
+                    if (mem_fread(&controllervalue, 1, 1, musinput) != 1)
+                    {
+                        return true;
+                    }
+
+                    if (controllernumber == 0)
+                    {
+                        if (WriteChangePatch(channel, controllervalue,
+                                             midioutput))
+                        {
+                            return true;
+                        }
+                    }
+                    else
+                    {
+                        if (controllernumber < 1 || controllernumber > 9)
+                        {
+                            return true;
+                        }
+
+                        if (WriteChangeController_Valued(channel,
+                                                         controller_map[controllernumber],
+                                                         controllervalue,
+                                                         midioutput))
+                        {
+                            return true;
+                        }
+                    }
+
+                    break;
+
+                case mus_scoreend:
+                    hitscoreend = 1;
+                    break;
+
+                default:
+                    return true;
+                    break;
+            }
+
+            if (eventdescriptor & 0x80)
+            {
+                break;
+            }
+        }
+        // Now we need to read the time code:
+        if (!hitscoreend)
+        {
+            timedelay = 0;
+            for (;;)
+            {
+                if (mem_fread(&working, 1, 1, musinput) != 1)
+                {
+                    return true;
+                }
+
+                timedelay = timedelay * 128 + (working & 0x7F);
+                if ((working & 0x80) == 0)
+                {
+                    break;
+                }
+            }
+            queuedtime += timedelay;
+        }
+    }
+
+    // End of track
+    if (WriteEndTrack(midioutput))
+    {
+        return true;
+    }
+
+    // Write the track size into the stream
+    if (mem_fseek(midioutput, 18, MEM_SEEK_SET))
+    {
+        return true;
+    }
+
+    tracksizebuffer[0] = (tracksize >> 24) & 0xff;
+    tracksizebuffer[1] = (tracksize >> 16) & 0xff;
+    tracksizebuffer[2] = (tracksize >> 8) & 0xff;
+    tracksizebuffer[3] = tracksize & 0xff;
+
+    if (mem_fwrite(tracksizebuffer, 1, 4, midioutput) != 4)
+    {
+        return true;
+    }
+
+    return false;
+}
+
+#ifdef STANDALONE
+
+#include "m_misc.h"
+#include "z_zone.h"
+
+int main(int argc, char *argv[])
+{
+    MEMFILE *src, *dst;
+    byte *infile;
+    long infile_len;
+    void *outfile;
+    size_t outfile_len;
+
+    if (argc != 3)
+    {
+        printf("Usage: %s <musfile> <midfile>\n", argv[0]);
+        exit(-1);
+    }
+
+    Z_Init();
+
+    infile_len = M_ReadFile(argv[1], &infile);
+
+    src = mem_fopen_read(infile, infile_len);
+    dst = mem_fopen_write();
+
+    if (mus2mid(src, dst))
+    {
+        fprintf(stderr, "mus2mid() failed\n");
+        exit(-1);
+    }
+
+    // Write result to output file:
+
+    mem_get_buf(dst, &outfile, &outfile_len);
+
+    M_WriteFile(argv[2], outfile, outfile_len);
+
+    return 0;
+}
+
+#endif
+
diff --git a/doomgeneric/mus2mid.h b/doomgeneric/mus2mid.h
new file mode 100644 (file)
index 0000000..d21516c
--- /dev/null
@@ -0,0 +1,9 @@
+#ifndef MUS2MID_H
+#define MUS2MID_H
+
+#include "doomtype.h"
+#include "memio.h"
+
+boolean mus2mid(MEMFILE *musinput, MEMFILE *midioutput);
+
+#endif /* #ifndef MUS2MID_H */
\ No newline at end of file