Skip to content

Latest commit

 

History

History
504 lines (421 loc) · 17.2 KB

mikmod.c

File metadata and controls

504 lines (421 loc) · 17.2 KB
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
* SDL_sound -- An abstract sound format decoding API.
* Copyright (C) 2001 Ryan C. Gordon.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Module player for SDL_sound. This driver handles anything MikMod does, and
* is based on SDL_mixer.
*
Sep 9, 2008
Sep 9, 2008
24
* Please see the file LICENSE.txt in the source's root directory.
Apr 17, 2008
Apr 17, 2008
26
* This file written by Torbjörn Andersson (d91tan@Update.UU.SE)
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
*/
#if HAVE_CONFIG_H
# include <config.h>
#endif
#ifdef SOUND_SUPPORTS_MIKMOD
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "SDL_sound.h"
#define __SDL_SOUND_INTERNAL__
#include "SDL_sound_internal.h"
#include "mikmod.h"
static int MIKMOD_init(void);
static void MIKMOD_quit(void);
static int MIKMOD_open(Sound_Sample *sample, const char *ext);
static void MIKMOD_close(Sound_Sample *sample);
static Uint32 MIKMOD_read(Sound_Sample *sample);
Jan 17, 2002
Jan 17, 2002
52
static int MIKMOD_rewind(Sound_Sample *sample);
Apr 21, 2002
Apr 21, 2002
53
static int MIKMOD_seek(Sound_Sample *sample, Uint32 ms);
54
55
56
static const char *extensions_mikmod[] =
{
Jan 11, 2002
Jan 11, 2002
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
"669", /* Composer 669 */
"AMF", /* DMP Advanced Module Format */
"DSM", /* DSIK internal format */
"FAR", /* Farandole module */
"GDM", /* General DigiMusic module */
"IMF", /* Imago Orpheus module */
"IT", /* Impulse tracker */
"M15", /* 15 instrument MOD / Ultimate Sound Tracker (old M15 format) */
"MED", /* Amiga MED module */
"MOD", /* Generic MOD (Protracker, StarTracker, FastTracker, etc) */
"MTM", /* MTM module */
"OKT", /* Oktalyzer module */
"S3M", /* Screamtracker module */
"STM", /* Screamtracker 2 module */
"STX", /* STMIK 0.2 module */
"ULT", /* Ultratracker module */
"UNI", /* UNIMOD - libmikmod's and APlayer's internal module format */
"XM", /* Fasttracker module */
75
76
77
78
79
80
81
82
NULL
};
const Sound_DecoderFunctions __Sound_DecoderFunctions_MIKMOD =
{
{
extensions_mikmod,
"Play modules through MikMod",
Apr 17, 2008
Apr 17, 2008
83
"Torbjörn Andersson <d91tan@Update.UU.SE>",
May 8, 2004
May 8, 2004
84
"http://mikmod.raphnet.net/"
Jan 17, 2002
Jan 17, 2002
87
88
89
90
91
MIKMOD_init, /* init() method */
MIKMOD_quit, /* quit() method */
MIKMOD_open, /* open() method */
MIKMOD_close, /* close() method */
MIKMOD_read, /* read() method */
Apr 21, 2002
Apr 21, 2002
92
93
MIKMOD_rewind, /* rewind() method */
MIKMOD_seek /* seek() method */
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
};
/* Make MikMod read from a RWops... */
typedef struct MRWOPSREADER {
MREADER core;
Sound_Sample *sample;
int end;
} MRWOPSREADER;
static BOOL _mm_RWopsReader_eof(MREADER *reader)
{
MRWOPSREADER *rwops_reader = (MRWOPSREADER *) reader;
Sound_Sample *sample = rwops_reader->sample;
Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
int pos = SDL_RWtell(internal->rw);
if (rwops_reader->end == pos)
return(1);
return(0);
} /* _mm_RWopsReader_eof */
static BOOL _mm_RWopsReader_read(MREADER *reader, void *ptr, size_t size)
{
MRWOPSREADER *rwops_reader = (MRWOPSREADER *) reader;
Sound_Sample *sample = rwops_reader->sample;
Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
return(SDL_RWread(internal->rw, ptr, size, 1));
} /* _mm_RWopsReader_Read */
static int _mm_RWopsReader_get(MREADER *reader)
{
char buf;
MRWOPSREADER *rwops_reader = (MRWOPSREADER *) reader;
Sound_Sample *sample = rwops_reader->sample;
Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
if (SDL_RWread(internal->rw, &buf, 1, 1) != 1)
return(EOF);
return((int) buf);
} /* _mm_RWopsReader_get */
static BOOL _mm_RWopsReader_seek(MREADER *reader, long offset, int whence)
{
MRWOPSREADER *rwops_reader = (MRWOPSREADER *) reader;
Sound_Sample *sample = rwops_reader->sample;
Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
return(SDL_RWseek(internal->rw, offset, whence));
} /* _mm_RWopsReader_seek */
static long _mm_RWopsReader_tell(MREADER *reader)
{
MRWOPSREADER *rwops_reader = (MRWOPSREADER *) reader;
Sound_Sample *sample = rwops_reader->sample;
Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
return(SDL_RWtell(internal->rw));
} /* _mm_RWopsReader_tell */
static MREADER *_mm_new_rwops_reader(Sound_Sample *sample)
{
Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
MRWOPSREADER *reader = (MRWOPSREADER *) malloc(sizeof (MRWOPSREADER));
if (reader != NULL)
{
Jul 11, 2002
Jul 11, 2002
169
int failed_seek = 1;
170
171
172
173
174
175
176
177
178
179
180
int here;
reader->core.Eof = _mm_RWopsReader_eof;
reader->core.Read = _mm_RWopsReader_read;
reader->core.Get = _mm_RWopsReader_get;
reader->core.Seek = _mm_RWopsReader_seek;
reader->core.Tell = _mm_RWopsReader_tell;
reader->sample = sample;
/* RWops does not explicitly support an eof check, so we shall find
the end manually - this requires seek support for the RWop */
here = SDL_RWtell(internal->rw);
Jul 11, 2002
Jul 11, 2002
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
if (here != -1)
{
reader->end = SDL_RWseek(internal->rw, 0, SEEK_END);
if (reader->end != -1)
{
/* Move back */
if (SDL_RWseek(internal->rw, here, SEEK_SET) != -1)
failed_seek = 0;
} /* if */
} /* if */
if (failed_seek)
{
free(reader);
reader = NULL;
} /* if */
197
198
199
200
201
202
203
204
} /* if */
return((MREADER *) reader);
} /* _mm_new_rwops_reader */
static void _mm_delete_rwops_reader(MREADER *reader)
{
May 8, 2004
May 8, 2004
205
/* SDL_sound will delete the RWops and sample at a higher level... */
206
207
208
209
210
211
212
213
214
if (reader != NULL)
free(reader);
} /* _mm_delete_rwops_reader */
static int MIKMOD_init(void)
{
MikMod_RegisterDriver(&drv_nos);
Dec 21, 2002
Dec 21, 2002
215
May 8, 2004
May 8, 2004
216
217
218
219
220
221
222
223
/*
* Quick and dirty hack to prevent an infinite loop problem
* found when using SDL_mixer and SDL_sound together and
* both have MikMod compiled in. So, check to see if
* MikMod has already been registered first before calling
* RegisterAllLoaders()
*/
if (MikMod_InfoLoader() == NULL)
Dec 21, 2002
Dec 21, 2002
224
225
{
MikMod_RegisterAllLoaders();
May 8, 2004
May 8, 2004
226
227
} /* if */
228
229
230
231
232
233
234
235
/*
* Both DMODE_SOFT_MUSIC and DMODE_16BITS should be set by default,
* so this is just for clarity. I haven't experimented with any of
* the other flags. There are a few which are said to give better
* sound quality.
*/
md_mode |= (DMODE_SOFT_MUSIC | DMODE_16BITS);
md_mixfreq = 0;
Jul 2, 2002
Jul 2, 2002
236
md_reverb = 1;
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
BAIL_IF_MACRO(MikMod_Init(""), MikMod_strerror(MikMod_errno), 0);
return(1); /* success. */
} /* MIKMOD_init */
static void MIKMOD_quit(void)
{
MikMod_Exit();
md_mixfreq = 0;
} /* MIKMOD_quit */
static int MIKMOD_open(Sound_Sample *sample, const char *ext)
{
Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
MREADER *reader;
MODULE *module;
May 8, 2004
May 8, 2004
256
257
258
Uint32 i; /* temp counter for time computation */
double segment_time = 0.0; /* temp holder for time */
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
reader = _mm_new_rwops_reader(sample);
BAIL_IF_MACRO(reader == NULL, ERR_OUT_OF_MEMORY, 0);
module = Player_LoadGeneric(reader, 64, 0);
_mm_delete_rwops_reader(reader);
BAIL_IF_MACRO(module == NULL, "MIKMOD: Not a module file.", 0);
module->extspd = 1;
module->panflag = 1;
module->wrap = 0;
module->loop = 0;
if (md_mixfreq == 0)
md_mixfreq = (!sample->desired.rate) ? 44100 : sample->desired.rate;
sample->actual.channels = 2;
sample->actual.rate = md_mixfreq;
sample->actual.format = AUDIO_S16SYS;
internal->decoder_private = (void *) module;
Player_Start(module);
Player_SetPosition(0);
Apr 24, 2002
Apr 24, 2002
281
sample->flags = SOUND_SAMPLEFLAG_NONE;
May 8, 2004
May 8, 2004
283
284
/*
* module->sngtime = current song time in 2^-10 seconds
May 12, 2004
May 12, 2004
285
* internal->total_time = (module->sngtime * 1000) / (1<<10)
May 8, 2004
May 8, 2004
286
*/
May 12, 2004
May 12, 2004
287
internal->total_time = (module->sngtime * 1000) / (1<<10);
May 8, 2004
May 8, 2004
288
289
290
291
292
SNDDBG(("MIKMOD: Name: %s\n", module->songname));
SNDDBG(("MIKMOD: Type: %s\n", module->modtype));
SNDDBG(("MIKMOD: Accepting data stream\n"));
May 8, 2004
May 8, 2004
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
/*
* This is a quick and dirty way for getting the play time
* of a file. This will often be wrong because the tracker format
* allows for so much. If you want a better one, use ModPlug,
* demand that the Mikmod people write better functionality,
* or write a more complicated version of the code below.
*
* There are two dumb ways to compute the length. The really
* dumb way is to look at the header and take the initial
* speed/tempo values. However, speed values can change throughout
* the file. The slightly smarter way is to iterate through
* all the positions and add up each segment's time based
* on the idea that each segment will give us its own
* speed value. The hope is that this is more accurate.
* However, this demands that the file be seekable
* and that we can change the position of the sample.
* Depending on the assumptions of SDL_sound, this block
* of code should be enabled or disabled. If disabled,
* you still can make the computations doing the first method.
* For now, we will assume it's acceptable to seek a Mod file
* since this is essentially how Modplug also does it.
*
* Keep in mind that this will be incorrect for loops, jumps, short
* patterns and other features.
*/
sample->flags |= SOUND_SAMPLEFLAG_CANSEEK;
/*
* For each position (which corresponds to a particular pattern),
* get the speed values and compute the time length of the segment
*/
May 12, 2004
May 12, 2004
325
internal->total_time = 0;
May 8, 2004
May 8, 2004
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
for (i = 0; i < module->numpos; i++)
{
Player_SetPosition(i);
/* Must call update, or the speed values won't get reset */
MikMod_Update();
/* Now the magic formula:
* Multiply the number of positions by the
* Number of rows (usually 64 but can be different) by the
* time it takes to read one row (1/50)
* by the speed by the
* magic reference beats per minute / the beats per minute
*
* We're using positions instead of patterns because in our
* test cases, this seems to be the correct value for the
* number of sections you hear during normal playback.
* They typically map to a fewer number of patterns
* where some patterns get replayed multiple times
* in a song (think chorus). Since we're in a for-loop,
* the multiplication is implicit while we're adding
* all the segments.
*
* From a tracker format spec, it seems that 64 rows
* is the normal (00-3F), but I've seen songs that
* either have less or are jumping positions in the
* middle of a pattern. It looks like Mikmod might
* reveal this number for us.
*
* According to the spec, it seems that a speed of 1
* corresponds to reading 1 row in 50 ticks. However,
* I'm not sure if ticks are real seconds or this
* notion of second units:
* Assuming that it's just normal seconds, we get 1/50 = 0.02.
*
* The current speed and current tempo (beats per minute)
* we can just grab. However, we need a magic number
* to figure out what the tempo is based on. Our primitive
* stopwatch results and intuition seem to imply 120-130bpm
* is the magic number. Looking at the majority of tracker
* files I have, 125 seems to be the common value. Furthermore
* most (if not all) of my Future Crew .S3M (Scream Tracker 3)
* files also use 125. Since they invented that format,
* I'll also assume that's the base number.
*/
if(module->bpm == 0)
{
/*
* Should never get here, but I don't want any
* divide by zero errors
*/
continue;
} /* if */
segment_time += (module->numrow * .02 * module->sngspd *
125.0 / module->bpm);
} /* for */
/* Now convert to milliseconds and store the value */
May 12, 2004
May 12, 2004
381
internal->total_time = (Sint32)(segment_time * 1000);
May 8, 2004
May 8, 2004
382
383
384
385
386
/* Reset the sample to the beginning */
Player_SetPosition(0);
MikMod_Update();
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
return(1); /* we'll handle this data. */
} /* MIKMOD_open */
static void MIKMOD_close(Sound_Sample *sample)
{
Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
MODULE *module = (MODULE *) internal->decoder_private;
Player_Free(module);
} /* MIKMOD_close */
static Uint32 MIKMOD_read(Sound_Sample *sample)
{
Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
MODULE *module = (MODULE *) internal->decoder_private;
/* Switch to the current module, stopping any previous one. */
Player_Start(module);
if (!Player_Active())
{
sample->flags |= SOUND_SAMPLEFLAG_EOF;
return(0);
} /* if */
return((Uint32) VC_WriteBytes(internal->buffer, internal->buffer_size));
} /* MIKMOD_read */
Jan 17, 2002
Jan 17, 2002
415
416
417
static int MIKMOD_rewind(Sound_Sample *sample)
{
Jan 19, 2002
Jan 19, 2002
418
419
420
421
422
423
Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
MODULE *module = (MODULE *) internal->decoder_private;
Player_Start(module);
Player_SetPosition(0);
return(1);
Jan 17, 2002
Jan 17, 2002
424
425
426
} /* MIKMOD_rewind */
Apr 21, 2002
Apr 21, 2002
427
428
static int MIKMOD_seek(Sound_Sample *sample, Uint32 ms)
{
Apr 24, 2002
Apr 24, 2002
429
430
Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
MODULE *module = (MODULE *) internal->decoder_private;
May 8, 2004
May 8, 2004
431
432
433
434
double last_time = 0.0;
double current_time = 0.0;
double target_time;
Uint32 i;
Apr 24, 2002
Apr 24, 2002
435
436
437
438
439
440
/*
* Heaven may know what the argument to Player_SetPosition() is.
* I, however, haven't the faintest idea.
*/
Player_Start(module);
May 8, 2004
May 8, 2004
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
/*
* Mikmod only lets you seek to the beginning of a pattern.
* This means we'll get very coarse grain seeks. The
* value we pass to SetPosition is a value between 0
* and the number of positions in the file. The
* dumb approach would be to take our total_time that
* we've already calculated and divide it up by the
* number of positions and seek to the position that results.
* However, because songs can alter their speed/tempo during
* playback, different patterns in the song can take
* up different amounts of time. So the slightly
* smarter approach is to repeat what was done in the
* total_time computation and traverse through the file
* until we find the closest position.
* The follwing is basically cut and paste from the
* open function.
*/
if (ms == 0) /* Check end conditions to simplify things */
{
Player_SetPosition(0);
return(1);
} /* if */
May 12, 2004
May 12, 2004
465
if (ms >= internal->total_time)
May 8, 2004
May 8, 2004
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
Player_SetPosition(module->numpos);
/* Convert time to seconds (double) to make comparisons easier */
target_time = ms / 1000.0;
for (i = 0; i < module->numpos; i++)
{
Player_SetPosition(i);
/* Must call update, or the speed values won't get reset */
MikMod_Update();
/* Divide by zero check */
if(module->bpm == 0)
continue;
last_time = current_time;
/* See the notes in the open function about the formula */
current_time += (module->numrow * .02
* module->sngspd * 125.0 / module->bpm);
if(target_time <= current_time)
break; /* We now have our interval, so break out */
} /* for */
if( (target_time-last_time) > (current_time-target_time) )
{
/* The target time is closer to the higher position, so go there */
Player_SetPosition(i+1);
} /* if */
else
{
/* The target time is closer to the lower position, so go there */
Player_SetPosition(i);
} /* else */
Apr 24, 2002
Apr 24, 2002
498
return(1);
Apr 21, 2002
Apr 21, 2002
499
} /* MIKMOD_seek */
Jan 17, 2002
Jan 17, 2002
500
501
502
503
504
#endif /* SOUND_SUPPORTS_MIKMOD */
/* end of mikmod.c ... */