Skip to content

Latest commit

 

History

History
469 lines (387 loc) · 14.1 KB

music_mad.c

File metadata and controls

469 lines (387 loc) · 14.1 KB
 
Jul 15, 2007
Jul 15, 2007
1
/*
Dec 31, 2011
Dec 31, 2011
2
SDL_mixer: An audio mixer library based on the SDL library
Jan 5, 2019
Jan 5, 2019
3
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
Dec 31, 2011
Dec 31, 2011
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
Jul 15, 2007
Jul 15, 2007
20
21
*/
Oct 17, 2017
Oct 17, 2017
22
#ifdef MUSIC_MP3_MAD
Jul 15, 2007
Jul 15, 2007
23
24
#include "music_mad.h"
Dec 10, 2019
Dec 10, 2019
25
#include "mp3utils.h"
Jul 15, 2007
Jul 15, 2007
26
Oct 17, 2017
Oct 17, 2017
27
28
#include "mad.h"
Oct 21, 2017
Oct 21, 2017
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/* NOTE: The dithering functions are GPL, which should be fine if your
application is GPL (which would need to be true if you enabled
libmad support in SDL_mixer). If you're using libmad under the
commercial license, you need to disable this code.
*/
/************************ dithering functions ***************************/
#ifdef MUSIC_MP3_MAD_GPL_DITHERING
/* All dithering done here is taken from the GPL'ed xmms-mad plugin. */
/* Copyright (C) 1997 Makoto Matsumoto and Takuji Nishimura. */
/* Any feedback is very welcome. For any question, comments, */
/* see http://www.math.keio.ac.jp/matumoto/emt.html or email */
/* matumoto@math.keio.ac.jp */
/* Period parameters */
#define MP3_DITH_N 624
#define MP3_DITH_M 397
#define MATRIX_A 0x9908b0df /* constant vector a */
#define UPPER_MASK 0x80000000 /* most significant w-r bits */
#define LOWER_MASK 0x7fffffff /* least significant r bits */
/* Tempering parameters */
#define TEMPERING_MASK_B 0x9d2c5680
#define TEMPERING_MASK_C 0xefc60000
#define TEMPERING_SHIFT_U(y) (y >> 11)
#define TEMPERING_SHIFT_S(y) (y << 7)
#define TEMPERING_SHIFT_T(y) (y << 15)
#define TEMPERING_SHIFT_L(y) (y >> 18)
static unsigned long mt[MP3_DITH_N]; /* the array for the state vector */
static int mti=MP3_DITH_N+1; /* mti==MP3_DITH_N+1 means mt[MP3_DITH_N] is not initialized */
/* initializing the array with a NONZERO seed */
static void sgenrand(unsigned long seed)
{
/* setting initial seeds to mt[MP3_DITH_N] using */
/* the generator Line 25 of Table 1 in */
/* [KNUTH 1981, The Art of Computer Programming */
/* Vol. 2 (2nd Ed.), pp102] */
mt[0]= seed & 0xffffffff;
for (mti=1; mti<MP3_DITH_N; mti++)
mt[mti] = (69069 * mt[mti-1]) & 0xffffffff;
}
static unsigned long genrand(void)
{
unsigned long y;
static unsigned long mag01[2]={0x0, MATRIX_A};
/* mag01[x] = x * MATRIX_A for x=0,1 */
if (mti >= MP3_DITH_N) { /* generate MP3_DITH_N words at one time */
int kk;
if (mti == MP3_DITH_N+1) /* if sgenrand() has not been called, */
sgenrand(4357); /* a default initial seed is used */
for (kk=0;kk<MP3_DITH_N-MP3_DITH_M;kk++) {
y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
mt[kk] = mt[kk+MP3_DITH_M] ^ (y >> 1) ^ mag01[y & 0x1];
}
for (;kk<MP3_DITH_N-1;kk++) {
y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
mt[kk] = mt[kk+(MP3_DITH_M-MP3_DITH_N)] ^ (y >> 1) ^ mag01[y & 0x1];
}
y = (mt[MP3_DITH_N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK);
mt[MP3_DITH_N-1] = mt[MP3_DITH_M-1] ^ (y >> 1) ^ mag01[y & 0x1];
mti = 0;
}
y = mt[mti++];
y ^= TEMPERING_SHIFT_U(y);
y ^= TEMPERING_SHIFT_S(y) & TEMPERING_MASK_B;
y ^= TEMPERING_SHIFT_T(y) & TEMPERING_MASK_C;
y ^= TEMPERING_SHIFT_L(y);
return y;
}
static long triangular_dither_noise(int nbits) {
/* parameter nbits : the peak-to-peak amplitude desired (in bits)
* use with nbits set to 2 + nber of bits to be trimmed.
* (because triangular is made from two uniformly distributed processes,
* it starts at 2 bits peak-to-peak amplitude)
* see The Theory of Dithered Quantization by Robert Alexander Wannamaker
* for complete proof of why that's optimal
*/
long v = (genrand()/2 - genrand()/2); /* in ]-2^31, 2^31[ */
long P = 1 << (32 - nbits); /* the power of 2 */
v /= P;
/* now v in ]-2^(nbits-1), 2^(nbits-1) [ */
return v;
}
#endif /* MUSIC_MP3_MAD_GPL_DITHERING */
Oct 17, 2017
Oct 17, 2017
130
131
132
133
134
#define MAD_INPUT_BUFFER_SIZE (5*8192)
enum {
MS_input_eof = 0x0001,
MS_input_error = 0x0001,
Oct 21, 2017
Oct 21, 2017
135
MS_decode_error = 0x0002,
Nov 17, 2019
Nov 17, 2019
136
MS_error_flags = 0x000f
Oct 17, 2017
Oct 17, 2017
137
138
139
};
typedef struct {
Dec 10, 2019
Dec 10, 2019
140
struct mp3file_t mp3file;
Oct 21, 2017
Oct 21, 2017
141
int play_count;
Oct 17, 2017
Oct 17, 2017
142
143
144
145
146
147
148
int freesrc;
struct mad_stream stream;
struct mad_frame frame;
struct mad_synth synth;
mad_timer_t next_frame_start;
int volume;
int status;
Oct 21, 2017
Oct 21, 2017
149
SDL_AudioStream *audiostream;
Oct 17, 2017
Oct 17, 2017
150
151
unsigned char input_buffer[MAD_INPUT_BUFFER_SIZE + MAD_BUFFER_GUARD];
Oct 21, 2017
Oct 21, 2017
152
} MAD_Music;
Oct 17, 2017
Oct 17, 2017
153
154
Oct 21, 2017
Oct 21, 2017
155
156
static int MAD_Seek(void *context, double position);
Oct 17, 2017
Oct 17, 2017
157
static void *MAD_CreateFromRW(SDL_RWops *src, int freesrc)
Dec 31, 2011
Dec 31, 2011
158
{
Oct 21, 2017
Oct 21, 2017
159
160
161
162
163
164
MAD_Music *music;
music = (MAD_Music *)SDL_calloc(1, sizeof(MAD_Music));
if (!music) {
SDL_OutOfMemory();
return NULL;
Oct 17, 2017
Oct 17, 2017
165
}
Dec 10, 2019
Dec 10, 2019
166
music->mp3file.src = src;
Oct 21, 2017
Oct 21, 2017
167
168
music->volume = MIX_MAX_VOLUME;
Dec 10, 2019
Dec 10, 2019
169
170
music->mp3file.length = SDL_RWsize(src);
if (mp3_skiptags(&music->mp3file) < 0) {
Jul 20, 2019
Jul 20, 2019
171
SDL_free(music);
Dec 10, 2019
Dec 10, 2019
172
Mix_SetError("music_mad: corrupt mp3 file (bad tags.)");
Jul 20, 2019
Jul 20, 2019
173
174
175
return NULL;
}
Oct 21, 2017
Oct 21, 2017
176
177
178
179
180
181
182
mad_stream_init(&music->stream);
mad_frame_init(&music->frame);
mad_synth_init(&music->synth);
mad_timer_reset(&music->next_frame_start);
music->freesrc = freesrc;
return music;
Jul 15, 2007
Jul 15, 2007
183
184
}
Oct 17, 2017
Oct 17, 2017
185
static void MAD_SetVolume(void *context, int volume)
Dec 31, 2011
Dec 31, 2011
186
{
Oct 21, 2017
Oct 21, 2017
187
188
MAD_Music *music = (MAD_Music *)context;
music->volume = volume;
Jul 15, 2007
Jul 15, 2007
189
190
191
}
/* Starts the playback. */
Oct 21, 2017
Oct 21, 2017
192
static int MAD_Play(void *context, int play_count)
Oct 17, 2017
Oct 17, 2017
193
{
Oct 21, 2017
Oct 21, 2017
194
195
196
MAD_Music *music = (MAD_Music *)context;
music->play_count = play_count;
return MAD_Seek(music, 0.0);
Jul 15, 2007
Jul 15, 2007
197
198
}
Oct 7, 2018
Oct 7, 2018
199
Oct 21, 2017
Oct 21, 2017
200
201
202
203
/* Reads the next frame from the file.
Returns true on success or false on failure.
*/
static SDL_bool read_next_frame(MAD_Music *music)
Oct 17, 2017
Oct 17, 2017
204
{
Oct 21, 2017
Oct 21, 2017
205
206
if (music->stream.buffer == NULL ||
music->stream.error == MAD_ERROR_BUFLEN) {
Oct 17, 2017
Oct 17, 2017
207
208
209
210
211
212
213
size_t read_size;
size_t remaining;
unsigned char *read_start;
/* There might be some bytes in the buffer left over from last
time. If so, move them down and read more bytes following
them. */
Oct 21, 2017
Oct 21, 2017
214
215
216
217
if (music->stream.next_frame != NULL) {
remaining = music->stream.bufend - music->stream.next_frame;
memmove(music->input_buffer, music->stream.next_frame, remaining);
read_start = music->input_buffer + remaining;
Oct 17, 2017
Oct 17, 2017
218
219
220
221
read_size = MAD_INPUT_BUFFER_SIZE - remaining;
} else {
read_size = MAD_INPUT_BUFFER_SIZE;
Oct 21, 2017
Oct 21, 2017
222
read_start = music->input_buffer;
Oct 17, 2017
Oct 17, 2017
223
224
remaining = 0;
}
May 22, 2013
May 22, 2013
225
Oct 17, 2017
Oct 17, 2017
226
/* Now read additional bytes from the input file. */
Dec 10, 2019
Dec 10, 2019
227
read_size = MP3_RWread(&music->mp3file, read_start, 1, read_size);
May 22, 2013
May 22, 2013
228
Oct 17, 2017
Oct 17, 2017
229
if (read_size == 0) {
Oct 21, 2017
Oct 21, 2017
230
if ((music->status & (MS_input_eof | MS_input_error)) == 0) {
Oct 17, 2017
Oct 17, 2017
231
/* FIXME: how to detect error? */
Oct 21, 2017
Oct 21, 2017
232
music->status |= MS_input_eof;
May 22, 2013
May 22, 2013
233
Oct 17, 2017
Oct 17, 2017
234
235
236
237
238
239
/* At the end of the file, we must stuff MAD_BUFFER_GUARD
number of 0 bytes. */
SDL_memset(read_start + read_size, 0, MAD_BUFFER_GUARD);
read_size += MAD_BUFFER_GUARD;
}
}
May 22, 2013
May 22, 2013
240
Oct 17, 2017
Oct 17, 2017
241
/* Now feed those bytes into the libmad stream. */
Oct 21, 2017
Oct 21, 2017
242
mad_stream_buffer(&music->stream, music->input_buffer,
Oct 17, 2017
Oct 17, 2017
243
read_size + remaining);
Oct 21, 2017
Oct 21, 2017
244
music->stream.error = MAD_ERROR_NONE;
Oct 17, 2017
Oct 17, 2017
245
}
May 22, 2013
May 22, 2013
246
Oct 17, 2017
Oct 17, 2017
247
248
/* Now ask libmad to extract a frame from the data we just put in
its buffer. */
Oct 21, 2017
Oct 21, 2017
249
250
if (mad_frame_decode(&music->frame, &music->stream)) {
if (MAD_RECOVERABLE(music->stream.error)) {
Oct 7, 2018
Oct 7, 2018
251
mad_stream_sync(&music->stream); /* to frame seek mode */
Oct 21, 2017
Oct 21, 2017
252
return SDL_FALSE;
May 22, 2013
May 22, 2013
253
Oct 21, 2017
Oct 21, 2017
254
255
} else if (music->stream.error == MAD_ERROR_BUFLEN) {
return SDL_FALSE;
May 22, 2013
May 22, 2013
256
Oct 17, 2017
Oct 17, 2017
257
} else {
Oct 21, 2017
Oct 21, 2017
258
259
260
Mix_SetError("mad_frame_decode() failed, corrupt stream?");
music->status |= MS_decode_error;
return SDL_FALSE;
Oct 17, 2017
Oct 17, 2017
261
}
May 22, 2013
May 22, 2013
262
263
}
Oct 21, 2017
Oct 21, 2017
264
mad_timer_add(&music->next_frame_start, music->frame.header.duration);
Jul 15, 2007
Jul 15, 2007
265
Oct 21, 2017
Oct 21, 2017
266
return SDL_TRUE;
Jul 15, 2007
Jul 15, 2007
267
268
269
}
/* Scale a MAD sample to 16 bits for output. */
Oct 21, 2017
Oct 21, 2017
270
271
272
273
static Sint16 scale(mad_fixed_t sample)
{
const int n_bits_to_loose = MAD_F_FRACBITS + 1 - 16;
Oct 17, 2017
Oct 17, 2017
274
/* round */
Oct 21, 2017
Oct 21, 2017
275
276
277
278
279
sample += (1L << (n_bits_to_loose - 1));
#ifdef MUSIC_MP3_MAD_GPL_DITHERING
sample += triangular_dither_noise(n_bits_to_loose + 1);
#endif
Jul 15, 2007
Jul 15, 2007
280
Oct 17, 2017
Oct 17, 2017
281
282
283
284
285
/* clip */
if (sample >= MAD_F_ONE)
sample = MAD_F_ONE - 1;
else if (sample < -MAD_F_ONE)
sample = -MAD_F_ONE;
Jul 15, 2007
Jul 15, 2007
286
Oct 17, 2017
Oct 17, 2017
287
/* quantize */
Oct 21, 2017
Oct 21, 2017
288
return (Sint16)(sample >> n_bits_to_loose);
Jul 15, 2007
Jul 15, 2007
289
290
}
Oct 17, 2017
Oct 17, 2017
291
/* Once the frame has been read, copies its samples into the output buffer. */
Oct 21, 2017
Oct 21, 2017
292
293
static SDL_bool decode_frame(MAD_Music *music)
{
Oct 17, 2017
Oct 17, 2017
294
struct mad_pcm *pcm;
Oct 21, 2017
Oct 21, 2017
295
unsigned int i, nchannels, nsamples;
Oct 17, 2017
Oct 17, 2017
296
mad_fixed_t const *left_ch, *right_ch;
Oct 21, 2017
Oct 21, 2017
297
298
Sint16 *buffer, *dst;
int result;
Oct 17, 2017
Oct 17, 2017
299
Oct 21, 2017
Oct 21, 2017
300
301
mad_synth_frame(&music->synth, &music->frame);
pcm = &music->synth.pcm;
Oct 17, 2017
Oct 17, 2017
302
Oct 21, 2017
Oct 21, 2017
303
if (!music->audiostream) {
Nov 17, 2019
Nov 17, 2019
304
music->audiostream = SDL_NewAudioStream(AUDIO_S16, (Uint8)pcm->channels, (int)pcm->samplerate, music_spec.format, music_spec.channels, music_spec.freq);
Oct 21, 2017
Oct 21, 2017
305
306
307
308
if (!music->audiostream) {
return SDL_FALSE;
}
}
Oct 17, 2017
Oct 17, 2017
309
Oct 21, 2017
Oct 21, 2017
310
311
312
313
314
315
316
317
nchannels = pcm->channels;
nsamples = pcm->length;
left_ch = pcm->samples[0];
right_ch = pcm->samples[1];
buffer = SDL_stack_alloc(Sint16, nsamples*nchannels);
if (!buffer) {
SDL_OutOfMemory();
return SDL_FALSE;
Oct 16, 2017
Oct 16, 2017
318
319
}
Oct 21, 2017
Oct 21, 2017
320
321
322
323
324
325
326
327
328
dst = buffer;
if (nchannels == 1) {
for (i = nsamples; i--;) {
*dst++ = scale(*left_ch++);
}
} else {
for (i = nsamples; i--;) {
*dst++ = scale(*left_ch++);
*dst++ = scale(*right_ch++);
Oct 17, 2017
Oct 17, 2017
329
330
}
}
Jul 15, 2007
Jul 15, 2007
331
Nov 17, 2019
Nov 17, 2019
332
result = SDL_AudioStreamPut(music->audiostream, buffer, (int)(nsamples * nchannels * sizeof(Sint16)));
Oct 21, 2017
Oct 21, 2017
333
SDL_stack_free(buffer);
Jul 15, 2007
Jul 15, 2007
334
Oct 21, 2017
Oct 21, 2017
335
336
337
338
339
if (result < 0) {
return SDL_FALSE;
}
return SDL_TRUE;
}
Jul 15, 2007
Jul 15, 2007
340
Oct 21, 2017
Oct 21, 2017
341
342
343
344
static int MAD_GetSome(void *context, void *data, int bytes, SDL_bool *done)
{
MAD_Music *music = (MAD_Music *)context;
int filled;
Jul 15, 2007
Jul 15, 2007
345
Oct 21, 2017
Oct 21, 2017
346
347
348
349
if (music->audiostream) {
filled = SDL_AudioStreamGet(music->audiostream, data, bytes);
if (filled != 0) {
return filled;
Oct 17, 2017
Oct 17, 2017
350
}
Jul 15, 2007
Jul 15, 2007
351
352
}
Oct 21, 2017
Oct 21, 2017
353
354
355
if (!music->play_count) {
/* All done */
*done = SDL_TRUE;
Oct 17, 2017
Oct 17, 2017
356
357
358
return 0;
}
Oct 21, 2017
Oct 21, 2017
359
360
361
if (read_next_frame(music)) {
if (!decode_frame(music)) {
return -1;
Oct 17, 2017
Oct 17, 2017
362
}
Oct 21, 2017
Oct 21, 2017
363
} else if (music->status & MS_input_eof) {
Oct 21, 2017
Oct 21, 2017
364
365
366
367
368
369
int play_count = -1;
if (music->play_count > 0) {
play_count = (music->play_count - 1);
}
if (MAD_Play(music, play_count) < 0) {
return -1;
Oct 17, 2017
Oct 17, 2017
370
}
Oct 21, 2017
Oct 21, 2017
371
372
} else if (music->status & MS_decode_error) {
return -1;
May 22, 2013
May 22, 2013
373
}
Oct 17, 2017
Oct 17, 2017
374
375
return 0;
}
Dec 10, 2019
Dec 10, 2019
376
Oct 21, 2017
Oct 21, 2017
377
378
379
380
381
static int MAD_GetAudio(void *context, void *data, int bytes)
{
MAD_Music *music = (MAD_Music *)context;
return music_pcm_getaudio(context, data, bytes, music->volume, MAD_GetSome);
}
May 22, 2013
May 22, 2013
382
Oct 17, 2017
Oct 17, 2017
383
384
static int MAD_Seek(void *context, double position)
{
Oct 21, 2017
Oct 21, 2017
385
MAD_Music *music = (MAD_Music *)context;
Oct 17, 2017
Oct 17, 2017
386
387
388
389
mad_timer_t target;
int int_part;
int_part = (int)position;
Nov 17, 2019
Nov 17, 2019
390
mad_timer_set(&target, (unsigned long)int_part, (unsigned long)((position - int_part) * 1000000), 1000000);
Oct 17, 2017
Oct 17, 2017
391
Oct 21, 2017
Oct 21, 2017
392
if (mad_timer_compare(music->next_frame_start, target) > 0) {
Oct 17, 2017
Oct 17, 2017
393
394
395
396
397
398
/* In order to seek backwards in a VBR file, we have to rewind and
start again from the beginning. This isn't necessary if the
file happens to be CBR, of course; in that case we could seek
directly to the frame we want. But I leave that little
optimization for the future developer who discovers she really
needs it. */
Oct 21, 2017
Oct 21, 2017
399
400
mad_timer_reset(&music->next_frame_start);
music->status &= ~MS_error_flags;
Oct 17, 2017
Oct 17, 2017
401
Dec 10, 2019
Dec 10, 2019
402
MP3_RWseek(&music->mp3file, 0, RW_SEEK_SET);
May 22, 2013
May 22, 2013
403
}
Jul 15, 2007
Jul 15, 2007
404
Oct 17, 2017
Oct 17, 2017
405
406
/* Now we have to skip frames until we come to the right one.
Again, only truly necessary if the file is VBR. */
Oct 21, 2017
Oct 21, 2017
407
408
409
while (mad_timer_compare(music->next_frame_start, target) < 0) {
if (!read_next_frame(music)) {
if ((music->status & MS_error_flags) != 0) {
Oct 17, 2017
Oct 17, 2017
410
411
412
413
414
/* Couldn't read a frame; either an error condition or
end-of-file. Stop. */
return Mix_SetError("Seek position out of range");
}
}
May 22, 2013
May 22, 2013
415
}
Jul 15, 2007
Jul 15, 2007
416
Oct 17, 2017
Oct 17, 2017
417
418
419
420
421
/* Here we are, at the beginning of the frame that contains the
target time. Ehh, I say that's close enough. If we wanted to,
we could get more precise by decoding the frame now and counting
the appropriate number of samples out of it. */
return 0;
Jul 15, 2007
Jul 15, 2007
422
423
}
Oct 17, 2017
Oct 17, 2017
424
425
static void MAD_Delete(void *context)
{
Oct 21, 2017
Oct 21, 2017
426
MAD_Music *music = (MAD_Music *)context;
Jul 15, 2007
Jul 15, 2007
427
Oct 21, 2017
Oct 21, 2017
428
429
430
431
432
433
434
435
mad_stream_finish(&music->stream);
mad_frame_finish(&music->frame);
mad_synth_finish(&music->synth);
if (music->audiostream) {
SDL_FreeAudioStream(music->audiostream);
}
if (music->freesrc) {
Dec 10, 2019
Dec 10, 2019
436
SDL_RWclose(music->mp3file.src);
Oct 17, 2017
Oct 17, 2017
437
}
Oct 21, 2017
Oct 21, 2017
438
SDL_free(music);
Oct 17, 2017
Oct 17, 2017
439
440
441
442
443
444
445
446
447
448
}
Mix_MusicInterface Mix_MusicInterface_MAD =
{
"MAD",
MIX_MUSIC_MAD,
MUS_MP3,
SDL_FALSE,
SDL_FALSE,
Oct 21, 2017
Oct 21, 2017
449
450
NULL, /* Load */
NULL, /* Open */
Oct 17, 2017
Oct 17, 2017
451
MAD_CreateFromRW,
Oct 21, 2017
Oct 21, 2017
452
NULL, /* CreateFromFile */
Oct 17, 2017
Oct 17, 2017
453
454
MAD_SetVolume,
MAD_Play,
Oct 21, 2017
Oct 21, 2017
455
NULL, /* IsPlaying */
Oct 17, 2017
Oct 17, 2017
456
457
MAD_GetAudio,
MAD_Seek,
Dec 17, 2019
Dec 17, 2019
458
NULL /* Duration */
Oct 21, 2017
Oct 21, 2017
459
460
461
NULL, /* Pause */
NULL, /* Resume */
NULL, /* Stop */
Oct 17, 2017
Oct 17, 2017
462
MAD_Delete,
Oct 21, 2017
Oct 21, 2017
463
NULL, /* Close */
Nov 26, 2019
Nov 26, 2019
464
NULL /* Unload */
Oct 17, 2017
Oct 17, 2017
465
466
467
468
469
};
#endif /* MUSIC_MP3_MAD */
/* vi: set ts=4 sw=4 expandtab: */