comparison decoders/mikmod.c @ 474:c66080364dff

Most decoders now report total sample play time, now. Technically, this breaks binary compatibility with the 1.0 branch, since it extends the Sound_Sample struct, but most (all?) programs are just passing pointers allocated by SDL_sound around, and might be okay. Source-level compatibility is not broken...yet! :) --ryan. -------- Original Message -------- Subject: SDL_sound patch: Finding total length of time of sound file. Date: Sun, 26 Jan 2003 09:31:17 -0800 (PST) Hi Ryan, I am working with Eric Wing and helping him modify SDL_sound. AS part of our efforts in improving and enhancing SDL_sound, we like to submit this patch. We modified the codecs to find the total time of a sound file. Below is the explanation of the patch. The patch is appended as an attachment to this email. * MOTIVATION: We needed the ability to get the total play time of a sample (And we noticed that we're not the only ones). Since SDL_sound blocks direct access to the specific decoders, there is no way for a user to know this information short of decoding the whole thing. Because of this, we believe this will be a useful addition, even though the accuracy may not be perfect (subject to each decoder) or the information may not always be available. * CONTRIBUTORS: Wesley Leong (modified the majority of the codecs and verified the results) Eric Wing (showed everyone how to do modify codec, modified mikmod) Wang Lam (modified a handful of codecs, researched into specs and int overflow) Ahilan Anantha (modified a few codecs and helped with integer math) * GENERAL ISSUES: We chose the value to be milliseconds as an Sint32. Milliseconds because that's what Sound_Seek takes as a parameter and -1 to allow for instances/codecs where the value could not be determined. We are not sure if this is the final convention you want, so we are willing to work with you on this. We also expect the total_time field to be set on open and never again modified by SDL_sound. Users may access it directly much like the sample buffer and buffer_size. We thought about recomputing the time on DecodeAll, but since users may seek or decode small chunks first, not all the data may be there. So this is better done by the user. This may be good information to document. Currently, all the main codecs are implemented except for QuickTime.
author Ryan C. Gordon <icculus@icculus.org>
date Sat, 08 May 2004 08:19:50 +0000
parents 1edb89260487
children 3e705c9180e5
comparison
equal deleted inserted replaced
473:1edb89260487 474:c66080364dff
200 } /* _mm_new_rwops_reader */ 200 } /* _mm_new_rwops_reader */
201 201
202 202
203 static void _mm_delete_rwops_reader(MREADER *reader) 203 static void _mm_delete_rwops_reader(MREADER *reader)
204 { 204 {
205 /* SDL_sound will delete the RWops and sample at a higher level... */ 205 /* SDL_sound will delete the RWops and sample at a higher level... */
206 if (reader != NULL) 206 if (reader != NULL)
207 free(reader); 207 free(reader);
208 } /* _mm_delete_rwops_reader */ 208 } /* _mm_delete_rwops_reader */
209 209
210 210
211 211
212 static int MIKMOD_init(void) 212 static int MIKMOD_init(void)
213 { 213 {
214 MikMod_RegisterDriver(&drv_nos); 214 MikMod_RegisterDriver(&drv_nos);
215 215
216 /* Quick and dirty hack to prevent an infinite loop problem 216 /*
217 * found when using SDL_mixer and SDL_sound together and 217 * Quick and dirty hack to prevent an infinite loop problem
218 * both have MikMod compiled in. So, check to see if 218 * found when using SDL_mixer and SDL_sound together and
219 * MikMod has already been registered first before calling 219 * both have MikMod compiled in. So, check to see if
220 * RegisterAllLoaders() 220 * MikMod has already been registered first before calling
221 */ 221 * RegisterAllLoaders()
222 if(MikMod_InfoLoader() == NULL) 222 */
223 if (MikMod_InfoLoader() == NULL)
223 { 224 {
224 MikMod_RegisterAllLoaders(); 225 MikMod_RegisterAllLoaders();
225 } 226 } /* if */
227
226 /* 228 /*
227 * Both DMODE_SOFT_MUSIC and DMODE_16BITS should be set by default, 229 * Both DMODE_SOFT_MUSIC and DMODE_16BITS should be set by default,
228 * so this is just for clarity. I haven't experimented with any of 230 * so this is just for clarity. I haven't experimented with any of
229 * the other flags. There are a few which are said to give better 231 * the other flags. There are a few which are said to give better
230 * sound quality. 232 * sound quality.
249 static int MIKMOD_open(Sound_Sample *sample, const char *ext) 251 static int MIKMOD_open(Sound_Sample *sample, const char *ext)
250 { 252 {
251 Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; 253 Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
252 MREADER *reader; 254 MREADER *reader;
253 MODULE *module; 255 MODULE *module;
254 256 Uint32 i; /* temp counter for time computation */
257 double segment_time = 0.0; /* temp holder for time */
258
255 reader = _mm_new_rwops_reader(sample); 259 reader = _mm_new_rwops_reader(sample);
256 BAIL_IF_MACRO(reader == NULL, ERR_OUT_OF_MEMORY, 0); 260 BAIL_IF_MACRO(reader == NULL, ERR_OUT_OF_MEMORY, 0);
257 module = Player_LoadGeneric(reader, 64, 0); 261 module = Player_LoadGeneric(reader, 64, 0);
258 _mm_delete_rwops_reader(reader); 262 _mm_delete_rwops_reader(reader);
259 BAIL_IF_MACRO(module == NULL, "MIKMOD: Not a module file.", 0); 263 BAIL_IF_MACRO(module == NULL, "MIKMOD: Not a module file.", 0);
274 Player_Start(module); 278 Player_Start(module);
275 Player_SetPosition(0); 279 Player_SetPosition(0);
276 280
277 sample->flags = SOUND_SAMPLEFLAG_NONE; 281 sample->flags = SOUND_SAMPLEFLAG_NONE;
278 282
283 /*
284 * module->sngtime = current song time in 2^-10 seconds
285 * sample->total_time = (module->sngtime * 1000) / (1<<10)
286 */
287 sample->total_time = (module->sngtime * 1000) / (1<<10);
288
279 SNDDBG(("MIKMOD: Name: %s\n", module->songname)); 289 SNDDBG(("MIKMOD: Name: %s\n", module->songname));
280 SNDDBG(("MIKMOD: Type: %s\n", module->modtype)); 290 SNDDBG(("MIKMOD: Type: %s\n", module->modtype));
281 SNDDBG(("MIKMOD: Accepting data stream\n")); 291 SNDDBG(("MIKMOD: Accepting data stream\n"));
292
293
294 /*
295 * This is a quick and dirty way for getting the play time
296 * of a file. This will often be wrong because the tracker format
297 * allows for so much. If you want a better one, use ModPlug,
298 * demand that the Mikmod people write better functionality,
299 * or write a more complicated version of the code below.
300 *
301 * There are two dumb ways to compute the length. The really
302 * dumb way is to look at the header and take the initial
303 * speed/tempo values. However, speed values can change throughout
304 * the file. The slightly smarter way is to iterate through
305 * all the positions and add up each segment's time based
306 * on the idea that each segment will give us its own
307 * speed value. The hope is that this is more accurate.
308 * However, this demands that the file be seekable
309 * and that we can change the position of the sample.
310 * Depending on the assumptions of SDL_sound, this block
311 * of code should be enabled or disabled. If disabled,
312 * you still can make the computations doing the first method.
313 * For now, we will assume it's acceptable to seek a Mod file
314 * since this is essentially how Modplug also does it.
315 *
316 * Keep in mind that this will be incorrect for loops, jumps, short
317 * patterns and other features.
318 */
319 sample->flags |= SOUND_SAMPLEFLAG_CANSEEK;
320
321 /*
322 * For each position (which corresponds to a particular pattern),
323 * get the speed values and compute the time length of the segment
324 */
325 sample->total_time = 0;
326 for (i = 0; i < module->numpos; i++)
327 {
328 Player_SetPosition(i);
329 /* Must call update, or the speed values won't get reset */
330 MikMod_Update();
331 /* Now the magic formula:
332 * Multiply the number of positions by the
333 * Number of rows (usually 64 but can be different) by the
334 * time it takes to read one row (1/50)
335 * by the speed by the
336 * magic reference beats per minute / the beats per minute
337 *
338 * We're using positions instead of patterns because in our
339 * test cases, this seems to be the correct value for the
340 * number of sections you hear during normal playback.
341 * They typically map to a fewer number of patterns
342 * where some patterns get replayed multiple times
343 * in a song (think chorus). Since we're in a for-loop,
344 * the multiplication is implicit while we're adding
345 * all the segments.
346 *
347 * From a tracker format spec, it seems that 64 rows
348 * is the normal (00-3F), but I've seen songs that
349 * either have less or are jumping positions in the
350 * middle of a pattern. It looks like Mikmod might
351 * reveal this number for us.
352 *
353 * According to the spec, it seems that a speed of 1
354 * corresponds to reading 1 row in 50 ticks. However,
355 * I'm not sure if ticks are real seconds or this
356 * notion of second units:
357 * Assuming that it's just normal seconds, we get 1/50 = 0.02.
358 *
359 * The current speed and current tempo (beats per minute)
360 * we can just grab. However, we need a magic number
361 * to figure out what the tempo is based on. Our primitive
362 * stopwatch results and intuition seem to imply 120-130bpm
363 * is the magic number. Looking at the majority of tracker
364 * files I have, 125 seems to be the common value. Furthermore
365 * most (if not all) of my Future Crew .S3M (Scream Tracker 3)
366 * files also use 125. Since they invented that format,
367 * I'll also assume that's the base number.
368 */
369 if(module->bpm == 0)
370 {
371 /*
372 * Should never get here, but I don't want any
373 * divide by zero errors
374 */
375 continue;
376 } /* if */
377 segment_time += (module->numrow * .02 * module->sngspd *
378 125.0 / module->bpm);
379 } /* for */
380 /* Now convert to milliseconds and store the value */
381 sample->total_time = (Sint32)(segment_time * 1000);
382
383 /* Reset the sample to the beginning */
384 Player_SetPosition(0);
385 MikMod_Update();
282 386
283 return(1); /* we'll handle this data. */ 387 return(1); /* we'll handle this data. */
284 } /* MIKMOD_open */ 388 } /* MIKMOD_open */
285 389
286 390
320 } /* MIKMOD_rewind */ 424 } /* MIKMOD_rewind */
321 425
322 426
323 static int MIKMOD_seek(Sound_Sample *sample, Uint32 ms) 427 static int MIKMOD_seek(Sound_Sample *sample, Uint32 ms)
324 { 428 {
325 #if 0
326 Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; 429 Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
327 MODULE *module = (MODULE *) internal->decoder_private; 430 MODULE *module = (MODULE *) internal->decoder_private;
431 double last_time = 0.0;
432 double current_time = 0.0;
433 double target_time;
434 Uint32 i;
328 435
329 /* 436 /*
330 * Heaven may know what the argument to Player_SetPosition() is. 437 * Heaven may know what the argument to Player_SetPosition() is.
331 * I, however, haven't the faintest idea. 438 * I, however, haven't the faintest idea.
332 */ 439 */
333 Player_Start(module); 440 Player_Start(module);
334 Player_SetPosition(ms); 441
442 /*
443 * Mikmod only lets you seek to the beginning of a pattern.
444 * This means we'll get very coarse grain seeks. The
445 * value we pass to SetPosition is a value between 0
446 * and the number of positions in the file. The
447 * dumb approach would be to take our total_time that
448 * we've already calculated and divide it up by the
449 * number of positions and seek to the position that results.
450 * However, because songs can alter their speed/tempo during
451 * playback, different patterns in the song can take
452 * up different amounts of time. So the slightly
453 * smarter approach is to repeat what was done in the
454 * total_time computation and traverse through the file
455 * until we find the closest position.
456 * The follwing is basically cut and paste from the
457 * open function.
458 */
459 if (ms == 0) /* Check end conditions to simplify things */
460 {
461 Player_SetPosition(0);
462 return(1);
463 } /* if */
464
465 if (ms >= sample->total_time)
466 Player_SetPosition(module->numpos);
467
468 /* Convert time to seconds (double) to make comparisons easier */
469 target_time = ms / 1000.0;
470
471 for (i = 0; i < module->numpos; i++)
472 {
473 Player_SetPosition(i);
474 /* Must call update, or the speed values won't get reset */
475 MikMod_Update();
476 /* Divide by zero check */
477 if(module->bpm == 0)
478 continue;
479 last_time = current_time;
480 /* See the notes in the open function about the formula */
481 current_time += (module->numrow * .02
482 * module->sngspd * 125.0 / module->bpm);
483 if(target_time <= current_time)
484 break; /* We now have our interval, so break out */
485 } /* for */
486
487 if( (target_time-last_time) > (current_time-target_time) )
488 {
489 /* The target time is closer to the higher position, so go there */
490 Player_SetPosition(i+1);
491 } /* if */
492 else
493 {
494 /* The target time is closer to the lower position, so go there */
495 Player_SetPosition(i);
496 } /* else */
497
335 return(1); 498 return(1);
336 #else
337 BAIL_MACRO("MIKMOD: Seeking not implemented", 0);
338 #endif
339 } /* MIKMOD_seek */ 499 } /* MIKMOD_seek */
340 500
341 #endif /* SOUND_SUPPORTS_MIKMOD */ 501 #endif /* SOUND_SUPPORTS_MIKMOD */
342 502
343 503