About `Mix_MusicSetPosition`, in the fact it's always used for absolute position and nothing relative, and always in seconds unit. Even on the side of official SDL Mixer this function works for absolute seconds position. Maybe, in the past it had some crap, but on the moment when I have picked it up initially, it had the sense "absolute seconds position".
OK, so it seems that there is no documentation for SDL_Mixer 2 and that the doc to which SDL_Mixer 2 points is in fact the same SDL_Mixer 1.2 documentation : https://www.libsdl.org/projects/SDL_mixer/docs/SDL_mixer.html#SEC65 , but the code is indeed as you say: always absolute.
However, I think the Mikmod version does not use seconds but straight 'positions', which seem to be some kind of ticks (like MIDI). It does not seem to be pattern numbers as SDL_Mixer (1.2) documentation says, but rather a row number inside a pattern.
I did a quick investigation last night, and took the following notes. I was interested in behaviour when seeking out of bounds, which is unspecified (undocumented) by SDL Mixer and by most underlying libraries too. It may not be very important when doing pure seeking which has little reason to seek out of bounds, but it is useful to have a fixed specified behaviour when doing some skipping.
As you can see, it is completely inconsistent
(It is possible I overlooked or misunderstood a few points as I did it quickly, just by reading code and not by testing, but well, the general picture should be rather correct.)
Code: Select all
| seek type | seek < 0 | seek > end | notes
-------------------------------------------------------------------------------------------
flac | absolute | unsigned^1 | no action | libFLAC behaviour undocumented
fluidsynth | n/a | n/a | n/a |
gme | absolute | bug^3 | to end + err? | libGME behaviour undocumented
mad | | unsigned^1 | to end + err | no dedicated lib function
midi_adl | absolute | no action | seek to 0 | never returns errors
midi_opn | absolute | no action | seek to 0 | never returns errors
mikmod | abs tick pos| unsigned^1 | to end | libmikmod behaviour undocumented^4, no error possible
modplug | absolute | unsigned^1 | to end | no doc at all for libmodplug
mpg123 | absolute^2 | seek to 0 | ? | libmpg123 behaviour undocumented
nativemidi | n/a | n/a | n/a |
ogg | absolute | no action+err | no action+err | libvorbis behaviour not clearly documented
opus | absolute | no action+err | no action+err | libopusfile behaviour not clearly documented
timidity | absolute | unsigned^5 | to end | no doc at all
wav | absolute | variable!^6 | no action+err |
^1: music_flac casts negative double to unsigned int without any check! -> UB
music_mad casts negative double to unsigned long without any check! -> UB
music_mikmod casts negative double to unsigned short without any check -> UB
libmodplug casts negative double to uint32_t without any check -> UB
music_timidity casts negative double to Uint32 without any check -> UB
^2: the underlying library also supports relative seeking.
^3: it seems that no check is performed before the argument is used in
various calculations and loop conditions, so it probably does... whatever.
^4: bad comment in music_mikmod.c, refering to time (seconds) instead of
pattern rows.
^5: timidity casts double to unsigned and then back to signed -> UB
^6: if the underlying SDL_RWops is a file (stdio or windows_file) seeking
below zero will result in: no action taken + error emitted. But if it is a
memory buffer, then the result is: seeking to 0!
That's why, I'd still like to add properly specified functions, possibly configurable, like
[Seek/Skip]Seconds(..., double seconds, enum seek_bounds mode)
, which operate on seconds for really all formats, and with enum seek_bounds
being SB_SATURATE
for seeking/skipping to 0 for when seeking/skipping below 0, and to the end (or just before?) when seeking/skipping beyond the end, SB_ERROR
for doing nothing and returning an error when seeking/skipping out of bounds, SB_SATURATE_ERROR
for performing the same action as SB_SATURATE
and signalling an error as SB_ERROR
too.I don't have a definitive API in mind, but something like that.
MAD MP3 hadn't seekability originally, I have implemented my own seeker which does two algorithms in dependence from seek the direction (when position is going before, drop position into begin and seek frames forward; but when it going after, just skip some frames)
Oh, that's your work in the official SDL_mixer too?
Yeah, MP3 is a mess to seek, but it's possible.
In fact, my understanding of my readings on this matter (very fresh readings, I am no expert at all) is that it is not so complicated.
From what I understood:
- MP3 frames have a fixed length: always the same number of samples;
- this number only depend on the MPEG version used;
- in a MP3 stream/file, all frames should have the same MPEG version and the same sample rate (otherwise it's a Frankenstein MP3 as a library called them);
- same sample rate + same number of samples / frame => all frames in a MP3 stream/file should have the same time duration;
- contrarily to what is written in
music_mad.c
comments, the bitrate doesn't matter (it just means a frame will occupy more or less bytes, but the duration it describes remains constant), so VBR MP3 can be handled the same as CBR.
So, for regular MP3 files (including VBR) each frame has a fixed time duration, all frames have the same.
The only small difficulty is if we want to handle irregular Frankenstein files, which have different MPEG version, or different sample rates, in some frames.
But is not very difficult either, since now we already have to parse the full file when we load it in order to calculate its duration (in your
calculate_total_time()
function). We just have to store a table of frames timestamps at the same time, those time points are already calculated anyway. Then we also need to store the offsets to which they refer in the source stream/buffer, which is a bit more annoying (because of the way music_mad operates on a small buffer and not directly on the whole file buffer) but doable.Then we can seek directly to the right frame in regular MP3 files, and almost directly for Frankenstein MP3 files (the difference between Frankenstein and regular files has also been very simply detected at the first parsing time, during file duration calculation). No need to decode again each frame, and the next, and so on, until we find the right one to perform a seek.
I have written code for that, but I have been doing something else since, so I need to review it again, and it probably requires some testing and cleaning before I show it. Just wait a few days well, I hope