music_mad.c
author Ozkan Sezer <sezeroz@gmail.com>
Thu, 11 Oct 2018 11:50:10 +0300
branchSDL-1.2
changeset 902 6c862e733898
parent 890 ae9b46ccd5ab
child 930 1f6c6389391a
permissions -rw-r--r--
remove smpeg support completely and backport libmpg123 support instead.
slouken@357
     1
/*
slouken@518
     2
  SDL_mixer:  An audio mixer library based on the SDL library
slouken@518
     3
  Copyright (C) 1997-2012 Sam Lantinga <slouken@libsdl.org>
slouken@357
     4
slouken@518
     5
  This software is provided 'as-is', without any express or implied
slouken@518
     6
  warranty.  In no event will the authors be held liable for any damages
slouken@518
     7
  arising from the use of this software.
slouken@357
     8
slouken@518
     9
  Permission is granted to anyone to use this software for any purpose,
slouken@518
    10
  including commercial applications, and to alter it and redistribute it
slouken@518
    11
  freely, subject to the following restrictions:
slouken@357
    12
slouken@518
    13
  1. The origin of this software must not be misrepresented; you must not
slouken@518
    14
     claim that you wrote the original software. If you use this software
slouken@518
    15
     in a product, an acknowledgment in the product documentation would be
slouken@518
    16
     appreciated but is not required.
slouken@518
    17
  2. Altered source versions must be plainly marked as such, and must not be
slouken@518
    18
     misrepresented as being the original software.
slouken@518
    19
  3. This notice may not be removed or altered from any source distribution.
slouken@357
    20
*/
slouken@357
    21
slouken@357
    22
#ifdef MP3_MAD_MUSIC
slouken@357
    23
slouken@357
    24
#include <string.h>
slouken@357
    25
slouken@357
    26
#include "music_mad.h"
slouken@357
    27
slouken@357
    28
mad_data *
slouken@521
    29
mad_openFileRW(SDL_RWops *rw, SDL_AudioSpec *mixer, int freerw)
slouken@521
    30
{
slouken@357
    31
  mad_data *mp3_mad;
slouken@357
    32
slouken@561
    33
  mp3_mad = (mad_data *)SDL_malloc(sizeof(mad_data));
slouken@380
    34
  if (mp3_mad) {
slouken@380
    35
	mp3_mad->rw = rw;
slouken@521
    36
	mp3_mad->freerw = freerw;
slouken@380
    37
	mad_stream_init(&mp3_mad->stream);
slouken@380
    38
	mad_frame_init(&mp3_mad->frame);
slouken@380
    39
	mad_synth_init(&mp3_mad->synth);
slouken@380
    40
	mp3_mad->frames_read = 0;
slouken@380
    41
	mad_timer_reset(&mp3_mad->next_frame_start);
slouken@380
    42
	mp3_mad->volume = MIX_MAX_VOLUME;
slouken@380
    43
	mp3_mad->status = 0;
slouken@380
    44
	mp3_mad->output_begin = 0;
slouken@380
    45
	mp3_mad->output_end = 0;
slouken@380
    46
	mp3_mad->mixer = *mixer;
slouken@380
    47
  }
slouken@357
    48
  return mp3_mad;
slouken@357
    49
}
slouken@357
    50
slouken@357
    51
void
slouken@521
    52
mad_closeFile(mad_data *mp3_mad)
slouken@521
    53
{
slouken@357
    54
  mad_stream_finish(&mp3_mad->stream);
slouken@357
    55
  mad_frame_finish(&mp3_mad->frame);
slouken@357
    56
  mad_synth_finish(&mp3_mad->synth);
slouken@357
    57
slouken@380
    58
  if (mp3_mad->freerw) {
slouken@521
    59
	SDL_RWclose(mp3_mad->rw);
slouken@380
    60
  }
slouken@561
    61
  SDL_free(mp3_mad);
slouken@357
    62
}
slouken@357
    63
slouken@357
    64
/* Starts the playback. */
slouken@357
    65
void
slouken@357
    66
mad_start(mad_data *mp3_mad) {
slouken@357
    67
  mp3_mad->status |= MS_playing;
slouken@357
    68
}
slouken@357
    69
slouken@357
    70
/* Stops the playback. */
slouken@357
    71
void 
slouken@357
    72
mad_stop(mad_data *mp3_mad) {
slouken@357
    73
  mp3_mad->status &= ~MS_playing;
slouken@357
    74
}
slouken@357
    75
slouken@357
    76
/* Returns true if the playing is engaged, false otherwise. */
slouken@357
    77
int
slouken@357
    78
mad_isPlaying(mad_data *mp3_mad) {
slouken@357
    79
  return ((mp3_mad->status & MS_playing) != 0);
slouken@357
    80
}
slouken@357
    81
sezeroz@890
    82
sezeroz@890
    83
/*************************** TAG HANDLING: ******************************/
sezeroz@890
    84
sezeroz@890
    85
static __inline__ SDL_bool is_id3v1(const Uint8 *data, size_t length)
sezeroz@890
    86
{
sezeroz@890
    87
    /* http://id3.org/ID3v1 :  3 bytes "TAG" identifier and 125 bytes tag data */
sezeroz@890
    88
    if (length < 3 || SDL_memcmp(data,"TAG",3) != 0) {
sezeroz@890
    89
        return SDL_FALSE;
sezeroz@890
    90
    }
sezeroz@890
    91
    return SDL_TRUE;
sezeroz@890
    92
}
sezeroz@890
    93
static __inline__ SDL_bool is_id3v2(const Uint8 *data, size_t length)
sezeroz@890
    94
{
sezeroz@890
    95
    /* ID3v2 header is 10 bytes:  http://id3.org/id3v2.4.0-structure */
sezeroz@890
    96
    /* bytes 0-2: "ID3" identifier */
sezeroz@890
    97
    if (length < 10 || SDL_memcmp(data,"ID3",3) != 0) {
sezeroz@890
    98
        return SDL_FALSE;
sezeroz@890
    99
    }
sezeroz@890
   100
    /* bytes 3-4: version num (major,revision), each byte always less than 0xff. */
sezeroz@890
   101
    if (data[3] == 0xff || data[4] == 0xff) {
sezeroz@890
   102
        return SDL_FALSE;
sezeroz@890
   103
    }
sezeroz@890
   104
    /* bytes 6-9 are the ID3v2 tag size: a 32 bit 'synchsafe' integer, i.e. the
sezeroz@890
   105
     * highest bit 7 in each byte zeroed.  i.e.: 7 bit information in each byte ->
sezeroz@890
   106
     * effectively a 28 bit value.  */
sezeroz@890
   107
    if (data[6] >= 0x80 || data[7] >= 0x80 || data[8] >= 0x80 || data[9] >= 0x80) {
sezeroz@890
   108
        return SDL_FALSE;
sezeroz@890
   109
    }
sezeroz@890
   110
    return SDL_TRUE;
sezeroz@890
   111
}
sezeroz@890
   112
static __inline__ SDL_bool is_apetag(const Uint8 *data, size_t length)
sezeroz@890
   113
{
sezeroz@890
   114
   /* http://wiki.hydrogenaud.io/index.php?title=APEv2_specification
sezeroz@890
   115
    * APEv2 header is 32 bytes: bytes 0-7 ident, bytes 8-11 version,
sezeroz@890
   116
    * bytes 12-17 size.  bytes 24-31 are reserved: must be all zeroes.
sezeroz@890
   117
    * APEv1 has no header, so no luck.  */
sezeroz@890
   118
    Uint32 v;
sezeroz@890
   119
sezeroz@890
   120
    if (length < 32 || SDL_memcmp(data,"APETAGEX",8) != 0) {
sezeroz@890
   121
        return SDL_FALSE;
sezeroz@890
   122
    }
sezeroz@890
   123
    v = (data[11]<<24) | (data[10]<<16) | (data[9]<<8) | data[8]; /* version */
sezeroz@890
   124
    if (v != 2000U) {
sezeroz@890
   125
        return SDL_FALSE;
sezeroz@890
   126
    }
sezeroz@890
   127
    v = 0; /* reserved bits : */
sezeroz@890
   128
    if (SDL_memcmp(&data[24],&v,4) != 0 || SDL_memcmp(&data[28],&v,4) != 0) {
sezeroz@890
   129
        return SDL_FALSE;
sezeroz@890
   130
    }
sezeroz@890
   131
    return SDL_TRUE;
sezeroz@890
   132
}
sezeroz@890
   133
sezeroz@890
   134
static size_t get_tagsize(const Uint8 *data, size_t length)
sezeroz@890
   135
{
sezeroz@890
   136
    size_t size;
sezeroz@890
   137
sezeroz@890
   138
    if (is_id3v1(data, length)) {
sezeroz@890
   139
        return 128; /* fixed length */
sezeroz@890
   140
    }
sezeroz@890
   141
    if (is_id3v2(data, length)) {
sezeroz@890
   142
        /* size is a 'synchsafe' integer (see above) */
sezeroz@890
   143
        size = (data[6]<<21) + (data[7]<<14) + (data[8]<<7) + data[9];
sezeroz@890
   144
        size += 10; /* header size */
sezeroz@890
   145
        /* ID3v2 header[5] is flags (bits 4-7 only, 0-3 are zero).
sezeroz@890
   146
         * bit 4 set: footer is present (a copy of the header but
sezeroz@890
   147
         * with "3DI" as ident.)  */
sezeroz@890
   148
        if (data[5] & 0x10) {
sezeroz@890
   149
            size += 10; /* footer size */
sezeroz@890
   150
        }
sezeroz@890
   151
        /* optional padding (always zeroes) */
sezeroz@890
   152
        while (size < length && data[size] == 0) {
sezeroz@890
   153
            ++size;
sezeroz@890
   154
        }
sezeroz@890
   155
        return size;
sezeroz@890
   156
    }
sezeroz@890
   157
    if (is_apetag(data, length)) {
sezeroz@890
   158
        size = (data[15]<<24) | (data[14]<<16) | (data[13]<<8) | data[12];
sezeroz@890
   159
        size += 32; /* header size */
sezeroz@890
   160
        return size;
sezeroz@890
   161
    }
sezeroz@890
   162
    return 0;
sezeroz@890
   163
}
sezeroz@890
   164
sezeroz@890
   165
static int consume_tag(struct mad_stream *stream)
sezeroz@890
   166
{
sezeroz@890
   167
    /* FIXME: what if the buffer doesn't have the full tag ??? */
sezeroz@890
   168
    size_t remaining = stream->bufend - stream->next_frame;
sezeroz@890
   169
    size_t tagsize = get_tagsize(stream->this_frame, remaining);
sezeroz@890
   170
    if (tagsize != 0) {
sezeroz@890
   171
        mad_stream_skip(stream, tagsize);
sezeroz@890
   172
        return 0;
sezeroz@890
   173
    }
sezeroz@890
   174
    return -1;
sezeroz@890
   175
}
sezeroz@890
   176
slouken@357
   177
/* Reads the next frame from the file.  Returns true on success or
slouken@357
   178
   false on failure. */
slouken@357
   179
static int
slouken@357
   180
read_next_frame(mad_data *mp3_mad) {
slouken@357
   181
  if (mp3_mad->stream.buffer == NULL || 
slouken@357
   182
	  mp3_mad->stream.error == MAD_ERROR_BUFLEN) {
slouken@357
   183
	size_t read_size;
slouken@357
   184
	size_t remaining;
slouken@357
   185
	unsigned char *read_start;
slouken@357
   186
	
slouken@357
   187
	/* There might be some bytes in the buffer left over from last
slouken@357
   188
	   time.  If so, move them down and read more bytes following
slouken@357
   189
	   them. */
slouken@357
   190
	if (mp3_mad->stream.next_frame != NULL) {
slouken@357
   191
	  remaining = mp3_mad->stream.bufend - mp3_mad->stream.next_frame;
slouken@357
   192
	  memmove(mp3_mad->input_buffer, mp3_mad->stream.next_frame, remaining);
slouken@357
   193
	  read_start = mp3_mad->input_buffer + remaining;
slouken@357
   194
	  read_size = MAD_INPUT_BUFFER_SIZE - remaining;
slouken@357
   195
	  
slouken@357
   196
	} else {
slouken@357
   197
	  read_size = MAD_INPUT_BUFFER_SIZE;
slouken@357
   198
	  read_start = mp3_mad->input_buffer;
slouken@357
   199
	  remaining = 0;
slouken@357
   200
	}
slouken@357
   201
slouken@357
   202
	/* Now read additional bytes from the input file. */
slouken@357
   203
	read_size = SDL_RWread(mp3_mad->rw, read_start, 1, read_size);
slouken@357
   204
	
slouken@357
   205
	if (read_size <= 0) {
slouken@357
   206
	  if ((mp3_mad->status & (MS_input_eof | MS_input_error)) == 0) {
slouken@357
   207
		if (read_size == 0) {
slouken@357
   208
		  mp3_mad->status |= MS_input_eof;
slouken@357
   209
		} else {
slouken@357
   210
		  mp3_mad->status |= MS_input_error;
slouken@357
   211
		}
slouken@357
   212
		
slouken@357
   213
		/* At the end of the file, we must stuff MAD_BUFFER_GUARD
slouken@357
   214
		   number of 0 bytes. */
slouken@357
   215
		memset(read_start + read_size, 0, MAD_BUFFER_GUARD);
slouken@357
   216
		read_size += MAD_BUFFER_GUARD;
slouken@357
   217
	  }
slouken@357
   218
	}
slouken@357
   219
	
slouken@357
   220
	/* Now feed those bytes into the libmad stream. */
slouken@357
   221
	mad_stream_buffer(&mp3_mad->stream, mp3_mad->input_buffer,
slouken@357
   222
					  read_size + remaining);
slouken@357
   223
	mp3_mad->stream.error = MAD_ERROR_NONE;
slouken@357
   224
  }
slouken@357
   225
  
slouken@357
   226
  /* Now ask libmad to extract a frame from the data we just put in
slouken@357
   227
	 its buffer. */
slouken@357
   228
  if (mad_frame_decode(&mp3_mad->frame, &mp3_mad->stream)) {
slouken@357
   229
	if (MAD_RECOVERABLE(mp3_mad->stream.error)) {
sezeroz@890
   230
	  consume_tag(&mp3_mad->stream); /* consume any ID3 tags */
sezeroz@890
   231
	  mad_stream_sync(&mp3_mad->stream); /* to frame seek mode */
slouken@357
   232
	  return 0;
slouken@357
   233
	  
slouken@357
   234
	} else if (mp3_mad->stream.error == MAD_ERROR_BUFLEN) {
slouken@357
   235
	  return 0;
slouken@357
   236
	  
slouken@357
   237
	} else {
slouken@357
   238
	  mp3_mad->status |= MS_decode_error;
slouken@357
   239
	  return 0;
slouken@357
   240
	}
slouken@357
   241
  }
slouken@357
   242
  
slouken@357
   243
  mp3_mad->frames_read++;
slouken@357
   244
  mad_timer_add(&mp3_mad->next_frame_start, mp3_mad->frame.header.duration);
slouken@357
   245
slouken@357
   246
  return 1;
slouken@357
   247
}
slouken@357
   248
slouken@357
   249
/* Scale a MAD sample to 16 bits for output. */
slouken@357
   250
static signed int
slouken@357
   251
scale(mad_fixed_t sample) {
slouken@357
   252
  /* round */
slouken@357
   253
  sample += (1L << (MAD_F_FRACBITS - 16));
slouken@357
   254
slouken@357
   255
  /* clip */
slouken@357
   256
  if (sample >= MAD_F_ONE)
slouken@357
   257
    sample = MAD_F_ONE - 1;
slouken@357
   258
  else if (sample < -MAD_F_ONE)
slouken@357
   259
    sample = -MAD_F_ONE;
slouken@357
   260
slouken@357
   261
  /* quantize */
slouken@357
   262
  return sample >> (MAD_F_FRACBITS + 1 - 16);
slouken@357
   263
}
slouken@357
   264
slouken@357
   265
/* Once the frame has been read, copies its samples into the output
slouken@357
   266
   buffer. */
slouken@357
   267
static void
slouken@357
   268
decode_frame(mad_data *mp3_mad) {
slouken@357
   269
  struct mad_pcm *pcm;
slouken@357
   270
  unsigned int nchannels, nsamples;
slouken@357
   271
  mad_fixed_t const *left_ch, *right_ch;
slouken@357
   272
  unsigned char *out;
slouken@357
   273
  int ret;
slouken@357
   274
slouken@357
   275
  mad_synth_frame(&mp3_mad->synth, &mp3_mad->frame);
slouken@357
   276
  pcm = &mp3_mad->synth.pcm;
slouken@357
   277
  out = mp3_mad->output_buffer + mp3_mad->output_end;
slouken@357
   278
slouken@357
   279
  if ((mp3_mad->status & MS_cvt_decoded) == 0) {
slouken@357
   280
	mp3_mad->status |= MS_cvt_decoded;
slouken@357
   281
slouken@357
   282
	/* The first frame determines some key properties of the stream.
slouken@357
   283
	   In particular, it tells us enough to set up the convert
slouken@357
   284
	   structure now. */
slouken@357
   285
	SDL_BuildAudioCVT(&mp3_mad->cvt, AUDIO_S16, pcm->channels, mp3_mad->frame.header.samplerate, mp3_mad->mixer.format, mp3_mad->mixer.channels, mp3_mad->mixer.freq);
slouken@357
   286
  }
slouken@357
   287
slouken@357
   288
  /* pcm->samplerate contains the sampling frequency */
slouken@357
   289
slouken@357
   290
  nchannels = pcm->channels;
slouken@357
   291
  nsamples  = pcm->length;
slouken@357
   292
  left_ch   = pcm->samples[0];
slouken@357
   293
  right_ch  = pcm->samples[1];
slouken@357
   294
slouken@357
   295
  while (nsamples--) {
slouken@357
   296
    signed int sample;
slouken@357
   297
slouken@357
   298
    /* output sample(s) in 16-bit signed little-endian PCM */
slouken@357
   299
slouken@357
   300
    sample = scale(*left_ch++);
slouken@357
   301
    *out++ = ((sample >> 0) & 0xff);
slouken@357
   302
    *out++ = ((sample >> 8) & 0xff);
slouken@357
   303
slouken@357
   304
    if (nchannels == 2) {
slouken@357
   305
      sample = scale(*right_ch++);
slouken@357
   306
      *out++ = ((sample >> 0) & 0xff);
slouken@357
   307
      *out++ = ((sample >> 8) & 0xff);
slouken@357
   308
    }
slouken@357
   309
  }
slouken@357
   310
slouken@357
   311
  mp3_mad->output_end = out - mp3_mad->output_buffer;
slouken@357
   312
  /*assert(mp3_mad->output_end <= MAD_OUTPUT_BUFFER_SIZE);*/
slouken@357
   313
}
slouken@357
   314
slouken@407
   315
int
slouken@357
   316
mad_getSamples(mad_data *mp3_mad, Uint8 *stream, int len) {
slouken@357
   317
  int bytes_remaining;
slouken@357
   318
  int num_bytes;
slouken@357
   319
  Uint8 *out;
slouken@357
   320
slouken@357
   321
  if ((mp3_mad->status & MS_playing) == 0) {
slouken@357
   322
	/* We're not supposed to be playing, so send silence instead. */
slouken@357
   323
	memset(stream, 0, len);
sezeroz@868
   324
	return 0;
slouken@357
   325
  }
slouken@357
   326
slouken@357
   327
  out = stream;
slouken@357
   328
  bytes_remaining = len;
slouken@357
   329
  while (bytes_remaining > 0) {
slouken@357
   330
	if (mp3_mad->output_end == mp3_mad->output_begin) {
slouken@357
   331
	  /* We need to get a new frame. */
slouken@357
   332
	  mp3_mad->output_begin = 0;
slouken@357
   333
	  mp3_mad->output_end = 0;
slouken@357
   334
	  if (!read_next_frame(mp3_mad)) {
slouken@357
   335
		if ((mp3_mad->status & MS_error_flags) != 0) {
slouken@357
   336
		  /* Couldn't read a frame; either an error condition or
slouken@357
   337
			 end-of-file.  Stop. */
slouken@357
   338
		  memset(out, 0, bytes_remaining);
slouken@357
   339
		  mp3_mad->status &= ~MS_playing;
slouken@407
   340
		  return bytes_remaining;
slouken@357
   341
		}
slouken@357
   342
	  } else {
slouken@357
   343
		decode_frame(mp3_mad);
slouken@357
   344
slouken@357
   345
		/* Now convert the frame data to the appropriate format for
slouken@357
   346
		   output. */
slouken@357
   347
		mp3_mad->cvt.buf = mp3_mad->output_buffer;
slouken@357
   348
		mp3_mad->cvt.len = mp3_mad->output_end;
slouken@357
   349
		
slouken@357
   350
		mp3_mad->output_end = (int)(mp3_mad->output_end * mp3_mad->cvt.len_ratio);
slouken@357
   351
		/*assert(mp3_mad->output_end <= MAD_OUTPUT_BUFFER_SIZE);*/
slouken@357
   352
		SDL_ConvertAudio(&mp3_mad->cvt);
slouken@357
   353
	  }
slouken@357
   354
	}
slouken@357
   355
slouken@357
   356
	num_bytes = mp3_mad->output_end - mp3_mad->output_begin;
slouken@357
   357
	if (bytes_remaining < num_bytes) {
slouken@357
   358
	  num_bytes = bytes_remaining;
slouken@357
   359
	}
slouken@357
   360
slouken@357
   361
	if (mp3_mad->volume == MIX_MAX_VOLUME) {
slouken@357
   362
	  memcpy(out, mp3_mad->output_buffer + mp3_mad->output_begin, num_bytes);
slouken@357
   363
	} else {
slouken@357
   364
	  SDL_MixAudio(out, mp3_mad->output_buffer + mp3_mad->output_begin,
slouken@357
   365
				   num_bytes, mp3_mad->volume);
slouken@357
   366
	}
slouken@357
   367
	out += num_bytes;
slouken@357
   368
	mp3_mad->output_begin += num_bytes;
slouken@357
   369
	bytes_remaining -= num_bytes;
slouken@357
   370
  }
slouken@407
   371
  return 0;
slouken@357
   372
}
slouken@357
   373
slouken@357
   374
void
slouken@357
   375
mad_seek(mad_data *mp3_mad, double position) {
slouken@357
   376
  mad_timer_t target;
slouken@357
   377
  int int_part;
slouken@357
   378
slouken@357
   379
  int_part = (int)position;
slouken@357
   380
  mad_timer_set(&target, int_part, 
slouken@357
   381
				(int)((position - int_part) * 1000000), 1000000);
slouken@357
   382
slouken@357
   383
  if (mad_timer_compare(mp3_mad->next_frame_start, target) > 0) {
slouken@357
   384
	/* In order to seek backwards in a VBR file, we have to rewind and
slouken@357
   385
	   start again from the beginning.  This isn't necessary if the
slouken@357
   386
	   file happens to be CBR, of course; in that case we could seek
slouken@357
   387
	   directly to the frame we want.  But I leave that little
slouken@357
   388
	   optimization for the future developer who discovers she really
slouken@357
   389
	   needs it. */
slouken@357
   390
	mp3_mad->frames_read = 0;
slouken@357
   391
	mad_timer_reset(&mp3_mad->next_frame_start);
slouken@357
   392
	mp3_mad->status &= ~MS_error_flags;
slouken@357
   393
	mp3_mad->output_begin = 0;
slouken@357
   394
	mp3_mad->output_end = 0;
slouken@357
   395
slouken@473
   396
	SDL_RWseek(mp3_mad->rw, 0, RW_SEEK_SET);
slouken@357
   397
  }
slouken@357
   398
slouken@357
   399
  /* Now we have to skip frames until we come to the right one.
slouken@357
   400
	 Again, only truly necessary if the file is VBR. */
slouken@357
   401
  while (mad_timer_compare(mp3_mad->next_frame_start, target) < 0) {
slouken@357
   402
	if (!read_next_frame(mp3_mad)) {
slouken@357
   403
	  if ((mp3_mad->status & MS_error_flags) != 0) {
slouken@357
   404
		/* Couldn't read a frame; either an error condition or
slouken@357
   405
		   end-of-file.  Stop. */
slouken@357
   406
		mp3_mad->status &= ~MS_playing;
slouken@357
   407
		return;
slouken@357
   408
	  }
slouken@357
   409
	}
slouken@357
   410
  }
slouken@357
   411
slouken@357
   412
  /* Here we are, at the beginning of the frame that contains the
slouken@357
   413
	 target time.  Ehh, I say that's close enough.  If we wanted to,
slouken@357
   414
	 we could get more precise by decoding the frame now and counting
slouken@357
   415
	 the appropriate number of samples out of it. */
slouken@357
   416
}
slouken@357
   417
slouken@357
   418
void
slouken@357
   419
mad_setVolume(mad_data *mp3_mad, int volume) {
slouken@357
   420
  mp3_mad->volume = volume;
slouken@357
   421
}
slouken@357
   422
slouken@357
   423
slouken@357
   424
#endif  /* MP3_MAD_MUSIC */