timidity/timidity.c
author Sam Lantinga <slouken@libsdl.org>
Sat, 21 Aug 2004 12:27:02 +0000
changeset 245 63b3650714de
parent 24 a25bb2d59ce8
child 263 5cd1ec721824
permissions -rw-r--r--
Here are patches for SDL12 and SDL_mixer for 4 or 6 channel
surround sound on Linux using the Alsa driver. To use them, naturally
you need a sound card that will do 4 or 6 channels and probably also a
recent version of the Alsa drivers and library. Since the only SDL
output driver that knows about surround sound is the Alsa driver,
you���ll want to choose it, using:

export SDL_AUDIODRIVER=alsa

There are no syntactic changes to the programming API. No new
library calls, no differences in arguments.

There are two semantic changes:

(1) For library calls with number of channels as an argument, formerly
you could use only 1 or 2 for the number of channels. Now you
can also use 4 or 6.

(2) The two "left" and "right" arguments to Mix_SetPanning, for the
case of 4 or 6 channels, no longer simply control the volumes of
the left and right channels. Now the "left" argument is converted
to an angle and Mix_SetPosition is called, and the "right" argu-
ment is ignored.

With two exceptions, so far as I know, the modified SDL12 and
SDL_mixer work the same way as the original versions, when opened for
1 or 2 channel output. The two exceptions are bugs which I fixed.
Well, the first, anyway, is a bug for sure. When rate conversions up
or down by a factor of two are applied (in src/audio/SDL_audiocvt.c),
streams with different numbers of channels (that is, mono and stereo)
are treated the same way: either each sample is copied or every other
sample is omitted. This is ok for mono, but for stereo, it is frames
that should be copied or omitted, where by "frame" I mean a portion of
the stream containing one sample for each channel. (In the SDL source,
confusingly, sometimes frames are called "samples".) So for these
rate conversions, stereo streams have to be treated differently, and
they are, in my modified version.

The other problem that might be characterized as a bug arises
when SDL_mixer is passed a multichannel chunk which does not have an
integral number of frames. Due to the way the effect_position code
loops over frames, when the chunk ends with a partial frame, memory
outside the chunk buffer will be accessed. In the case of stereo,
it���s possible that because malloc may give more memory than requested,
this potential problem never actually causes a segment fault. I don���t
know. For 6 channel chunks, I do know, and it does cause segment
faults.


If SDL_mixer is passed defective chunks and this causes a segment
fault, arguably, that���s not a bug in SDL_mixer. Still, whether or not
it counts as a bug, it���s easy to protect against, so why not? I added
code in mixer.c to discard any partial frame at the end of a chunk.

Then what about when SDL or SDL_mixer is opened for 4 or 6 chan-
nel output? What happens with the parts of the current library
designed for stereo? I don���t know whether I���ve covered all the bases,
but I���ve tried:

(1) For playing 2 channel waves, or other cases where SDL knows it has
to match up a 2 channel source with a 4 or 6 channel output, I���ve
added code in SDL_audiocvt.c to make the necessary conversions.

(2) For playing midis using timidity, I���ve converted timidity to do 4
or 6 channel output, upon request.

(3) For playing mods using mikmod, I put ad hoc code in music.c to
convert the stereo output that mikmod produces to 4 or 6 chan-
nels. Obviously it would be better to change the mikmod code to
mix down into 4 or 6 channels, but I have a hard time following
the code in mikmod, so I didn���t do that.

(4) For playing mp3s, I put ad hoc code in smpeg to copy channels in
the case when 4 or 6 channel output is needed.

(5) There seems to be no problem with .ogg files - stereo .oggs can be
up converted as .wavs are.

(6) The effect_position code in SDL_mixer is now generalized to in-
clude the cases of 4 and 6 channel streams.

I���ve done a very limited amount of compatibility testing for some
of the games using SDL I happen to have. For details, see the file
TESTS.

I���ve put into a separate archive, Surround-SDL-testfiles.tgz, a
couple of 6 channel wave files for testing and a 6 channel ogg file.
If you have the right hardware and version of Alsa, you should be able
to play the wave files with the Alsa utility aplay (and hear all
channels, except maybe lfe, for chan-id.wav, since it���s rather faint).
Don���t expect aplay to give good sound, though. There���s something
wrong with the current version of aplay.

The canyon.ogg file is to test loading of 6 channel oggs. After
patching and compiling, you can play it with playmus. (My version of
ogg123 will not play it, and I had to patch mplayer to get it to play
6 channel oggs.)

Greg Lee <greg@ling.lll.hawaii.edu>
Thus, July 1, 2004
slouken@0
     1
/*
slouken@0
     2
slouken@0
     3
    TiMidity -- Experimental MIDI to WAVE converter
slouken@0
     4
    Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
slouken@0
     5
slouken@0
     6
	 This program is free software; you can redistribute it and/or modify
slouken@0
     7
	 it under the terms of the GNU General Public License as published by
slouken@0
     8
    the Free Software Foundation; either version 2 of the License, or
slouken@0
     9
	 (at your option) any later version.
slouken@0
    10
slouken@0
    11
    This program is distributed in the hope that it will be useful,
slouken@0
    12
    but WITHOUT ANY WARRANTY; without even the implied warranty of
slouken@0
    13
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
slouken@0
    14
	 GNU General Public License for more details.
slouken@0
    15
slouken@0
    16
    You should have received a copy of the GNU General Public License
slouken@0
    17
    along with this program; if not, write to the Free Software
slouken@0
    18
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
slouken@0
    19
slouken@0
    20
*/
slouken@0
    21
#include <stdio.h>
slouken@0
    22
#include <stdlib.h>
slouken@0
    23
#include <string.h>
slouken@0
    24
slouken@24
    25
#include "SDL.h"
slouken@0
    26
#include "config.h"
slouken@0
    27
#include "common.h"
slouken@0
    28
#include "instrum.h"
slouken@0
    29
#include "playmidi.h"
slouken@0
    30
#include "readmidi.h"
slouken@0
    31
#include "output.h"
slouken@0
    32
#include "controls.h"
slouken@0
    33
#include "timidity.h"
slouken@0
    34
slouken@0
    35
#include "tables.h"
slouken@0
    36
slouken@0
    37
void (*s32tobuf)(void *dp, int32 *lp, int32 c);
slouken@0
    38
int free_instruments_afterwards=0;
slouken@0
    39
static char def_instr_name[256]="";
slouken@0
    40
slouken@0
    41
int AUDIO_BUFFER_SIZE;
slouken@245
    42
resample_t *resample_buffer;
slouken@0
    43
int32 *common_buffer;
slouken@245
    44
int num_ochannels;
slouken@0
    45
slouken@0
    46
#define MAXWORDS 10
slouken@0
    47
slouken@0
    48
static int read_config_file(char *name)
slouken@0
    49
{
slouken@0
    50
  FILE *fp;
slouken@0
    51
  char tmp[1024], *w[MAXWORDS], *cp;
slouken@0
    52
  ToneBank *bank=0;
slouken@0
    53
  int i, j, k, line=0, words;
slouken@0
    54
  static int rcf_count=0;
slouken@0
    55
slouken@0
    56
  if (rcf_count>50)
slouken@0
    57
   {
slouken@0
    58
    ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
slouken@0
    59
      "Probable source loop in configuration files");
slouken@0
    60
    return (-1);
slouken@0
    61
   }
slouken@0
    62
slouken@0
    63
  if (!(fp=open_file(name, 1, OF_VERBOSE)))
slouken@0
    64
   return -1;
slouken@0
    65
slouken@0
    66
  while (fgets(tmp, sizeof(tmp), fp))
slouken@245
    67
  {
slouken@245
    68
    line++;
slouken@0
    69
    w[words=0]=strtok(tmp, " \t\r\n\240");
slouken@0
    70
    if (!w[0] || (*w[0]=='#')) continue;
slouken@245
    71
    while (w[words] && (words < MAXWORDS))
slouken@245
    72
      {
slouken@245
    73
        w[++words]=strtok(0," \t\r\n\240");
slouken@245
    74
        if (w[words] && w[words][0]=='#') break;
slouken@245
    75
      }
slouken@245
    76
    if (!strcmp(w[0], "map")) continue;
slouken@245
    77
    if (!strcmp(w[0], "dir"))
slouken@245
    78
    {
slouken@245
    79
      if (words < 2)
slouken@245
    80
       {
slouken@0
    81
        ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
slouken@0
    82
          "%s: line %d: No directory given\n", name, line);
slouken@0
    83
        return -2;
slouken@245
    84
       }
slouken@245
    85
      for (i=1; i<words; i++)
slouken@245
    86
        add_to_pathlist(w[i]);
slouken@245
    87
    }
slouken@245
    88
  else if (!strcmp(w[0], "source"))
slouken@0
    89
  {
slouken@0
    90
    if (words < 2)
slouken@0
    91
      {
slouken@0
    92
        ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
slouken@0
    93
          "%s: line %d: No file name given\n", name, line);
slouken@0
    94
        return -2;
slouken@0
    95
     }
slouken@0
    96
    for (i=1; i<words; i++)
slouken@0
    97
      {
slouken@0
    98
        rcf_count++;
slouken@0
    99
      read_config_file(w[i]);
slouken@0
   100
        rcf_count--;
slouken@0
   101
      }
slouken@0
   102
  }
slouken@0
   103
      else if (!strcmp(w[0], "default"))
slouken@0
   104
  {
slouken@0
   105
    if (words != 2)
slouken@0
   106
      {
slouken@0
   107
        ctl->cmsg(CMSG_ERROR, VERB_NORMAL, 
slouken@0
   108
        "%s: line %d: Must specify exactly one patch name\n",
slouken@0
   109
          name, line);
slouken@0
   110
        return -2;
slouken@0
   111
      }
slouken@0
   112
    strncpy(def_instr_name, w[1], 255);
slouken@0
   113
    def_instr_name[255]='\0';
slouken@0
   114
  }
slouken@0
   115
    else if (!strcmp(w[0], "drumset"))
slouken@0
   116
  {
slouken@0
   117
    if (words < 2)
slouken@0
   118
      {
slouken@0
   119
        ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
slouken@0
   120
          "%s: line %d: No drum set number given\n", 
slouken@0
   121
          name, line);
slouken@0
   122
      return -2;
slouken@0
   123
      }
slouken@0
   124
    i=atoi(w[1]);
slouken@0
   125
    if (i<0 || i>127)
slouken@0
   126
     {
slouken@0
   127
        ctl->cmsg(CMSG_ERROR, VERB_NORMAL, 
slouken@0
   128
          "%s: line %d: Drum set must be between 0 and 127\n",
slouken@0
   129
        name, line);
slouken@0
   130
        return -2;
slouken@0
   131
     }
slouken@0
   132
    if (!drumset[i])
slouken@0
   133
      {
slouken@0
   134
        drumset[i]=safe_malloc(sizeof(ToneBank));
slouken@0
   135
      memset(drumset[i], 0, sizeof(ToneBank));
slouken@0
   136
     }
slouken@0
   137
    bank=drumset[i];
slouken@0
   138
  }
slouken@0
   139
    else if (!strcmp(w[0], "bank"))
slouken@0
   140
  {
slouken@0
   141
    if (words < 2)
slouken@0
   142
     {
slouken@0
   143
        ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
slouken@0
   144
          "%s: line %d: No bank number given\n", 
slouken@0
   145
        name, line);
slouken@0
   146
        return -2;
slouken@0
   147
     }
slouken@0
   148
    i=atoi(w[1]);
slouken@0
   149
    if (i<0 || i>127)
slouken@0
   150
      {
slouken@0
   151
        ctl->cmsg(CMSG_ERROR, VERB_NORMAL, 
slouken@0
   152
          "%s: line %d: Tone bank must be between 0 and 127\n",
slouken@0
   153
        name, line);
slouken@0
   154
        return -2;
slouken@0
   155
      }
slouken@0
   156
    if (!tonebank[i])
slouken@0
   157
     {
slouken@0
   158
      tonebank[i]=safe_malloc(sizeof(ToneBank));
slouken@0
   159
        memset(tonebank[i], 0, sizeof(ToneBank));
slouken@0
   160
      }
slouken@0
   161
    bank=tonebank[i];
slouken@0
   162
  }
slouken@0
   163
      else {
slouken@0
   164
  if ((words < 2) || (*w[0] < '0' || *w[0] > '9'))
slouken@0
   165
    {
slouken@0
   166
     ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
slouken@0
   167
        "%s: line %d: syntax error\n", name, line);
slouken@0
   168
     return -2;
slouken@0
   169
    }
slouken@0
   170
  i=atoi(w[0]);
slouken@0
   171
  if (i<0 || i>127)
slouken@0
   172
    {
slouken@0
   173
      ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
slouken@0
   174
        "%s: line %d: Program must be between 0 and 127\n",
slouken@0
   175
        name, line);
slouken@0
   176
      return -2;
slouken@0
   177
    }
slouken@0
   178
  if (!bank)
slouken@0
   179
    {
slouken@0
   180
      ctl->cmsg(CMSG_ERROR, VERB_NORMAL, 
slouken@0
   181
       "%s: line %d: Must specify tone bank or drum set "
slouken@0
   182
        "before assignment\n",
slouken@0
   183
        name, line);
slouken@0
   184
     return -2;
slouken@0
   185
    }
slouken@0
   186
  if (bank->tone[i].name)
slouken@0
   187
    free(bank->tone[i].name);
slouken@0
   188
  strcpy((bank->tone[i].name=safe_malloc(strlen(w[1])+1)),w[1]);
slouken@0
   189
  bank->tone[i].note=bank->tone[i].amp=bank->tone[i].pan=
slouken@0
   190
    bank->tone[i].strip_loop=bank->tone[i].strip_envelope=
slouken@0
   191
      bank->tone[i].strip_tail=-1;
slouken@0
   192
slouken@0
   193
  for (j=2; j<words; j++)
slouken@0
   194
    {
slouken@0
   195
      if (!(cp=strchr(w[j], '=')))
slouken@0
   196
        {
slouken@0
   197
    ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: bad patch option %s\n",
slouken@0
   198
      name, line, w[j]);
slouken@0
   199
    return -2;
slouken@0
   200
        }
slouken@0
   201
      *cp++=0;
slouken@0
   202
      if (!strcmp(w[j], "amp"))
slouken@0
   203
      {
slouken@0
   204
    k=atoi(cp);
slouken@0
   205
    if ((k<0 || k>MAX_AMPLIFICATION) || (*cp < '0' || *cp > '9'))
slouken@0
   206
      {
slouken@0
   207
       ctl->cmsg(CMSG_ERROR, VERB_NORMAL, 
slouken@0
   208
          "%s: line %d: amplification must be between "
slouken@0
   209
         "0 and %d\n", name, line, MAX_AMPLIFICATION);
slouken@0
   210
       return -2;
slouken@0
   211
      }
slouken@0
   212
    bank->tone[i].amp=k;
slouken@0
   213
        }
slouken@0
   214
      else if (!strcmp(w[j], "note"))
slouken@0
   215
        {
slouken@0
   216
    k=atoi(cp);
slouken@0
   217
    if ((k<0 || k>127) || (*cp < '0' || *cp > '9'))
slouken@0
   218
      {
slouken@0
   219
       ctl->cmsg(CMSG_ERROR, VERB_NORMAL, 
slouken@0
   220
         "%s: line %d: note must be between 0 and 127\n",
slouken@0
   221
          name, line);
slouken@0
   222
        return -2;
slouken@0
   223
      }
slouken@0
   224
    bank->tone[i].note=k;
slouken@0
   225
      }
slouken@0
   226
     else if (!strcmp(w[j], "pan"))
slouken@0
   227
      {
slouken@0
   228
    if (!strcmp(cp, "center"))
slouken@0
   229
      k=64;
slouken@0
   230
    else if (!strcmp(cp, "left"))
slouken@0
   231
      k=0;
slouken@0
   232
    else if (!strcmp(cp, "right"))
slouken@0
   233
      k=127;
slouken@0
   234
    else
slouken@0
   235
      k=((atoi(cp)+100) * 100) / 157;
slouken@0
   236
    if ((k<0 || k>127) ||
slouken@0
   237
       (k==0 && *cp!='-' && (*cp < '0' || *cp > '9')))
slouken@0
   238
      {
slouken@0
   239
       ctl->cmsg(CMSG_ERROR, VERB_NORMAL, 
slouken@0
   240
         "%s: line %d: panning must be left, right, "
slouken@0
   241
         "center, or between -100 and 100\n",
slouken@0
   242
         name, line);
slouken@0
   243
       return -2;
slouken@0
   244
      }
slouken@0
   245
    bank->tone[i].pan=k;
slouken@0
   246
      }
slouken@0
   247
     else if (!strcmp(w[j], "keep"))
slouken@0
   248
      {
slouken@0
   249
    if (!strcmp(cp, "env"))
slouken@0
   250
      bank->tone[i].strip_envelope=0;
slouken@0
   251
    else if (!strcmp(cp, "loop"))
slouken@0
   252
      bank->tone[i].strip_loop=0;
slouken@0
   253
    else
slouken@0
   254
      {
slouken@0
   255
        ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
slouken@0
   256
          "%s: line %d: keep must be env or loop\n", name, line);
slouken@0
   257
       return -2;
slouken@0
   258
      }
slouken@0
   259
      }
slouken@0
   260
     else if (!strcmp(w[j], "strip"))
slouken@0
   261
      {
slouken@0
   262
    if (!strcmp(cp, "env"))
slouken@0
   263
      bank->tone[i].strip_envelope=1;
slouken@0
   264
    else if (!strcmp(cp, "loop"))
slouken@0
   265
      bank->tone[i].strip_loop=1;
slouken@0
   266
    else if (!strcmp(cp, "tail"))
slouken@0
   267
      bank->tone[i].strip_tail=1;
slouken@0
   268
    else
slouken@0
   269
      {
slouken@0
   270
       ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
slouken@0
   271
         "%s: line %d: strip must be env, loop, or tail\n",
slouken@0
   272
         name, line);
slouken@0
   273
       return -2;
slouken@0
   274
      }
slouken@0
   275
      }
slouken@0
   276
     else
slouken@0
   277
      {
slouken@0
   278
    ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: bad patch option %s\n",
slouken@0
   279
      name, line, w[j]);
slouken@0
   280
    return -2;
slouken@0
   281
      }
slouken@0
   282
    }
slouken@0
   283
    }
slouken@0
   284
   }
slouken@0
   285
  if (ferror(fp))
slouken@0
   286
   {
slouken@0
   287
    ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "Can't read from %s\n", name);
slouken@0
   288
    close_file(fp);
slouken@0
   289
    return -2;
slouken@0
   290
   }
slouken@0
   291
  close_file(fp);
slouken@0
   292
  return 0;
slouken@0
   293
}
slouken@0
   294
slouken@0
   295
int Timidity_Init(int rate, int format, int channels, int samples)
slouken@0
   296
{
slouken@0
   297
  if (read_config_file(CONFIG_FILE)<0) {
slouken@0
   298
    return(-1);
slouken@0
   299
  }
slouken@0
   300
slouken@245
   301
  if (channels < 1 || channels == 3 || channels == 5 || channels > 6) return(-1);
slouken@245
   302
slouken@245
   303
  num_ochannels = channels;
slouken@245
   304
slouken@0
   305
  /* Set play mode parameters */
slouken@0
   306
  play_mode->rate = rate;
slouken@0
   307
  play_mode->encoding = 0;
slouken@0
   308
  if ( (format&0xFF) == 16 ) {
slouken@0
   309
    play_mode->encoding |= PE_16BIT;
slouken@0
   310
  }
slouken@0
   311
  if ( (format&0x8000) ) {
slouken@0
   312
    play_mode->encoding |= PE_SIGNED;
slouken@0
   313
  }
slouken@0
   314
  if ( channels == 1 ) {
slouken@0
   315
    play_mode->encoding |= PE_MONO;
slouken@0
   316
  } 
slouken@0
   317
  switch (format) {
slouken@0
   318
    case AUDIO_S8:
slouken@0
   319
      s32tobuf = s32tos8;
slouken@0
   320
      break;
slouken@0
   321
    case AUDIO_U8:
slouken@0
   322
      s32tobuf = s32tou8;
slouken@0
   323
      break;
slouken@0
   324
    case AUDIO_S16LSB:
slouken@0
   325
      s32tobuf = s32tos16l;
slouken@0
   326
      break;
slouken@0
   327
    case AUDIO_S16MSB:
slouken@0
   328
      s32tobuf = s32tos16b;
slouken@0
   329
      break;
slouken@0
   330
    case AUDIO_U16LSB:
slouken@0
   331
      s32tobuf = s32tou16l;
slouken@0
   332
      break;
slouken@0
   333
    case AUDIO_U16MSB:
slouken@0
   334
      s32tobuf = s32tou16b;
slouken@0
   335
      break;
slouken@0
   336
    default:
slouken@0
   337
      ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "Unsupported audio format");
slouken@0
   338
      return(-1);
slouken@0
   339
  }
slouken@0
   340
  AUDIO_BUFFER_SIZE = samples;
slouken@0
   341
slouken@0
   342
  /* Allocate memory for mixing (WARNING:  Memory leak!) */
slouken@245
   343
  resample_buffer = safe_malloc(AUDIO_BUFFER_SIZE*sizeof(resample_t)+100);
slouken@245
   344
  common_buffer = safe_malloc(AUDIO_BUFFER_SIZE*num_ochannels*sizeof(int32));
slouken@0
   345
slouken@0
   346
  init_tables();
slouken@0
   347
slouken@0
   348
  if (ctl->open(0, 0)) {
slouken@0
   349
    ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "Couldn't open %s\n", ctl->id_name);
slouken@0
   350
    return(-1);
slouken@0
   351
  }
slouken@0
   352
slouken@0
   353
  if (!control_ratio) {
slouken@0
   354
    control_ratio = play_mode->rate / CONTROLS_PER_SECOND;
slouken@0
   355
    if(control_ratio<1)
slouken@0
   356
      control_ratio=1;
slouken@0
   357
    else if (control_ratio > MAX_CONTROL_RATIO)
slouken@0
   358
      control_ratio=MAX_CONTROL_RATIO;
slouken@0
   359
  }
slouken@0
   360
  if (*def_instr_name)
slouken@0
   361
    set_default_instrument(def_instr_name);
slouken@0
   362
  return(0);
slouken@0
   363
}
slouken@0
   364
slouken@0
   365
char timidity_error[1024] = "";
slouken@0
   366
char *Timidity_Error(void)
slouken@0
   367
{
slouken@0
   368
  return(timidity_error);
slouken@0
   369
}