shithub: choc

Download patch

ref: f3b53778ca850ca0b01ff445000f4f07545b62fc
parent: b2a59b3eec1dcc040f58bdd5e88bfed55face2fe
author: Simon Howard <fraggle@gmail.com>
date: Sun Mar 3 15:29:15 EST 2013

Add GUS pseudo-emulation.

Subversion-branch: /branches/v2-branch
Subversion-revision: 2566

--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -117,6 +117,7 @@
 # source files needed for FEATURE_SOUND
 
 FEATURE_SOUND_SOURCE_FILES =               \
+gusconf.c            gusconf.h             \
 i_pcsound.c                                \
 i_sdlsound.c                               \
 i_sdlmusic.c                               \
--- /dev/null
+++ b/src/gusconf.c
@@ -1,0 +1,268 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2013 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:
+//     GUS emulation code.
+//
+//     Actually emulating a GUS is far too much work; fortunately
+//     GUS "emulation" already exists in the form of Timidity, which
+//     supports GUS patch files. This code therefore converts Doom's
+//     DMXGUS lump into an equivalent Timidity configuration file.
+//
+//-----------------------------------------------------------------------------
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "w_wad.h"
+#include "z_zone.h"
+
+#define MAX_INSTRUMENTS 256
+
+typedef struct
+{
+    char *patch_names[MAX_INSTRUMENTS];
+    int mapping[MAX_INSTRUMENTS];
+} gus_config_t;
+
+char *gus_patch_path = "";
+unsigned int gus_ram_kb = 1024;
+
+static unsigned int MappingIndex(void)
+{
+    unsigned int result = gus_ram_kb / 256;
+
+    if (result < 1)
+    {
+        return 1;
+    }
+    else if (result > 4)
+    {
+        return 4;
+    }
+    else
+    {
+        return result;
+    }
+}
+
+static int SplitLine(char *line, char **fields, unsigned int max_fields)
+{
+    unsigned int num_fields;
+    char *p;
+
+    fields[0] = line;
+    num_fields = 1;
+
+    for (p = line; *p != '\0'; ++p)
+    {
+        if (*p == ',')
+        {
+            *p = '\0';
+
+            // Skip spaces following the comma.
+            do
+            {
+                ++p;
+            } while (*p != '\0' && isspace(*p));
+
+            fields[num_fields] = p;
+            ++num_fields;
+            --p;
+
+            if (num_fields >= max_fields)
+            {
+                break;
+            }
+        }
+        else if (*p == '#')
+        {
+            *p = '\0';
+            break;
+        }
+    }
+
+    // Strip off trailing whitespace from the end of the line.
+    p = fields[num_fields - 1] + strlen(fields[num_fields - 1]);
+    while (p > fields[num_fields - 1] && isspace(*(p - 1)))
+    {
+        --p;
+        *p = '\0';
+    }
+
+    return num_fields;
+}
+
+static void ParseLine(gus_config_t *config, char *line)
+{
+    char *fields[6];
+    unsigned int num_fields;
+    unsigned int instr_id, mapped_id;
+
+    num_fields = SplitLine(line, fields, 6);
+
+    if (num_fields < 6)
+    {
+        return;
+    }
+
+    instr_id = atoi(fields[0]);
+    mapped_id = atoi(fields[MappingIndex()]);
+
+    free(config->patch_names[instr_id]);
+    config->patch_names[instr_id] = strdup(fields[5]);
+    config->mapping[instr_id] = mapped_id;
+}
+
+static void ParseDMXConfig(char *dmxconf, gus_config_t *config)
+{
+    char *p, *newline;
+    unsigned int i;
+
+    memset(config, 0, sizeof(gus_config_t));
+
+    for (i = 0; i < MAX_INSTRUMENTS; ++i)
+    {
+        config->mapping[i] = -1;
+    }
+
+    p = dmxconf;
+
+    for (;;)
+    {
+        newline = strchr(p, '\n');
+
+        if (newline != NULL)
+        {
+            *newline = '\0';
+        }
+
+        ParseLine(config, p);
+
+        if (newline == NULL)
+        {
+            break;
+        }
+        else
+        {
+            p = newline + 1;
+        }
+    }
+}
+
+static void FreeDMXConfig(gus_config_t *config)
+{
+    unsigned int i;
+
+    for (i = 0; i < MAX_INSTRUMENTS; ++i)
+    {
+        free(config->patch_names[i]);
+    }
+}
+
+static char *ReadDMXConfig(void)
+{
+    int lumpnum;
+    unsigned int len;
+    char *data;
+
+    // TODO: This should be chosen based on gamemode == commercial:
+
+    lumpnum = W_CheckNumForName("DMXGUS");
+
+    if (lumpnum < 0)
+    {
+        lumpnum = W_GetNumForName("DMXGUSC");
+    }
+
+    len = W_LumpLength(lumpnum);
+    data = Z_Malloc(len + 1, PU_STATIC, NULL);
+    W_ReadLump(lumpnum, data);
+
+    return data;
+}
+
+static boolean WriteTimidityConfig(char *path, gus_config_t *config)
+{
+    FILE *fstream;
+    unsigned int i;
+
+    fstream = fopen(path, "w");
+
+    if (fstream == NULL)
+    {
+        return false;
+    }
+
+    fprintf(fstream, "# Autogenerated Timidity config.\n\n");
+
+    fprintf(fstream, "dir %s\n", gus_patch_path);
+
+    fprintf(fstream, "\nbank 0\n\n");
+
+    for (i = 0; i < 128; ++i)
+    {
+        if (config->mapping[i] >= 0 && config->mapping[i] < MAX_INSTRUMENTS
+         && config->patch_names[config->mapping[i]] != NULL)
+        {
+            fprintf(fstream, "%i %s\n",
+                    i, config->patch_names[config->mapping[i]]);
+        }
+    }
+
+    fprintf(fstream, "\ndrumset 0\n\n");
+
+    for (i = 128 + 25; i < MAX_INSTRUMENTS; ++i)
+    {
+        if (config->mapping[i] >= 0 && config->mapping[i] < MAX_INSTRUMENTS
+         && config->patch_names[config->mapping[i]] != NULL)
+        {
+            fprintf(fstream, "%i %s\n",
+                    i - 128, config->patch_names[config->mapping[i]]);
+        }
+    }
+
+    fprintf(fstream, "\n");
+
+    fclose(fstream);
+
+    return true;
+}
+
+boolean GUS_WriteConfig(char *path)
+{
+    boolean result;
+    char *dmxconf;
+    gus_config_t config;
+
+    dmxconf = ReadDMXConfig();
+    ParseDMXConfig(dmxconf, &config);
+
+    result = WriteTimidityConfig(path, &config);
+
+    FreeDMXConfig(&config);
+    Z_Free(dmxconf);
+
+    return result;
+}
+
--- /dev/null
+++ b/src/gusconf.h
@@ -1,0 +1,37 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2013 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:
+//     GUS emulation code.
+//
+//-----------------------------------------------------------------------------
+
+#ifndef __GUSCONF_H__
+#define __GUSCONF_H__
+
+#include "doomtype.h"
+
+extern char *gus_patch_path;
+extern unsigned int gus_ram_kb;
+
+boolean GUS_WriteConfig(char *path);
+
+#endif /* #ifndef __GUSCONF_H__ */
+
--- a/src/i_sdlmusic.c
+++ b/src/i_sdlmusic.c
@@ -36,6 +36,7 @@
 #include "mus2mid.h"
 
 #include "deh_str.h"
+#include "gusconf.h"
 #include "i_sound.h"
 #include "m_misc.h"
 #include "w_wad.h"
@@ -54,6 +55,7 @@
 static int current_music_volume;
 
 char *timidity_cfg_path = "";
+
 static char *temp_timidity_cfg = NULL;
 
 // If the temp_timidity_cfg config variable is set, generate a "wrapper"
@@ -61,27 +63,21 @@
 // is needed to inject a "dir" command so that the patches are read
 // relative to the actual config file.
 
-void I_InitTimidityConfig(void)
+static boolean WriteWrapperTimidityConfig(char *write_path)
 {
-    char *env_string;
     char *p, *path;
     FILE *fstream;
 
-    temp_timidity_cfg = NULL;
-
     if (!strcmp(timidity_cfg_path, ""))
     {
-        return;
+        return false;
     }
 
-    temp_timidity_cfg = M_TempFile("timidity.cfg");
-    fstream = fopen(temp_timidity_cfg, "w");
+    fstream = fopen(write_path, "w");
 
     if (fstream == NULL)
     {
-        Z_Free(temp_timidity_cfg);
-        temp_timidity_cfg = NULL;
-        return;
+        return false;
     }
 
     p = strrchr(timidity_cfg_path, DIR_SEPARATOR);
@@ -96,12 +92,39 @@
     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");
+
+    if (snd_musicdevice == SNDDEVICE_GUS)
+    {
+        success = GUS_WriteConfig(temp_timidity_cfg);
+    }
+    else
+    {
+        success = WriteWrapperTimidityConfig(temp_timidity_cfg);
+    }
+
     // Set the TIMIDITY_CFG environment variable to point to the temporary
     // config file.
 
-    env_string = malloc(strlen(temp_timidity_cfg) + 15);
-    sprintf(env_string, "TIMIDITY_CFG=%s", temp_timidity_cfg);
-    putenv(env_string);
+    if (success)
+    {
+        env_string = malloc(strlen(temp_timidity_cfg) + 15);
+        sprintf(env_string, "TIMIDITY_CFG=%s", temp_timidity_cfg);
+        putenv(env_string);
+    }
+    else
+    {
+        Z_Free(temp_timidity_cfg);
+        temp_timidity_cfg = NULL;
+    }
 }
 
 // Remove the temporary config file generated by I_InitTimidityConfig().
--- a/src/i_sound.c
+++ b/src/i_sound.c
@@ -32,6 +32,7 @@
 #include "doomfeatures.h"
 #include "doomtype.h"
 
+#include "gusconf.h"
 #include "i_sound.h"
 #include "i_video.h"
 #include "m_argv.h"
@@ -218,7 +219,9 @@
         // the TIMIDITY_CFG environment variable here before SDL_mixer
         // is opened.
 
-        if (!nomusic && snd_musicdevice == SNDDEVICE_GENMIDI)
+        if (!nomusic
+         && (snd_musicdevice == SNDDEVICE_GENMIDI
+          || snd_musicdevice == SNDDEVICE_GUS))
         {
             I_InitTimidityConfig();
         }
@@ -435,6 +438,8 @@
     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);
--- a/src/m_config.c
+++ b/src/m_config.c
@@ -915,6 +915,20 @@
 
     CONFIG_VARIABLE_STRING(timidity_cfg_path),
 
+    //!
+    // Path to GUS patch files to use when operating in GUS emulation
+    // mode.
+    //
+
+    CONFIG_VARIABLE_STRING(gus_patch_path),
+
+    //!
+    // Number of kilobytes of RAM to use in GUS emulation mode. Valid
+    // values are 256, 512, 768 or 1024.
+    //
+
+    CONFIG_VARIABLE_INT(gus_ram_kb),
+
 #endif
 
     //!
--- a/src/setup/sound.c
+++ b/src/setup/sound.c
@@ -79,6 +79,8 @@
 static int use_libsamplerate = 0;
 
 static char *timidity_cfg_path = "";
+static char *gus_patch_path = "";
+static unsigned int gus_ram_kb = 1024;
 
 // DOS specific variables: these are unused but should be maintained
 // so that the config file can be shared between chocolate
@@ -254,6 +256,8 @@
     M_BindVariable("snd_samplerate",      &snd_samplerate);
     M_BindVariable("use_libsamplerate",   &use_libsamplerate);
     M_BindVariable("timidity_cfg_path",   &timidity_cfg_path);
+    M_BindVariable("gus_patch_path",      &gus_patch_path);
+    M_BindVariable("gus_ram_kb",          &gus_ram_kb);
 
     M_BindVariable("snd_sbport",          &snd_sbport);
     M_BindVariable("snd_sbirq",           &snd_sbirq);