diff --git a/include/SDL_mixer.h b/include/SDL_mixer.h index 2e8f1ab5c..0623100e9 100644 --- a/include/SDL_mixer.h +++ b/include/SDL_mixer.h @@ -2471,6 +2471,29 @@ extern DECLSPEC double SDLCALL Mix_GetMusicPosition(Mix_Music *music); */ extern DECLSPEC double SDLCALL Mix_MusicDuration(Mix_Music *music); +/** + * Set the current position in the music stream, in beats. + * + * This function is only implemented for MIDI via Timidity at the moment. + * + * \param beat the new position, in beats (as a int). + * \returns 0 if successful, or -1 if it failed or not implemented. + * + * \since This function is available since SDL_mixer 2.7.0. + */ +extern DECLSPEC int SDLCALL Mix_SetMusicBeat(int beat); + +/** + * Get the current position in the music stream, in beats. + * + * This function is only implemented for MIDI via Timidity at the moment. + * + * \returns current beat position if successful, or -1 if it failed or not implemented. + * + * \since This function is available since SDL_mixer 2.7.0. + */ +extern DECLSPEC int SDLCALL Mix_GetMusicBeat(); + /** * Get the loop start time position of music stream, in seconds. * diff --git a/playmus.c b/playmus.c index c4d70b3c2..494633c71 100644 --- a/playmus.c +++ b/playmus.c @@ -119,9 +119,11 @@ int main(int argc, char *argv[]) int audio_channels; int audio_buffers; int audio_volume = MIX_MAX_VOLUME; + int beat = 0; int looping = 0; int interactive = 0; int rwops = 0; + int supports_beats; int i; const char *typ; const char *tag_title = NULL; @@ -162,6 +164,10 @@ int main(int argc, char *argv[]) if (strcmp(argv[i], "-l") == 0) { looping = -1; } else + if (strcmp(argv[i], "-n") == 0 && argv[i+1]) { + ++i; + beat = atoi(argv[i]); + } else if (strcmp(argv[i], "-i") == 0) { interactive = 1; } else @@ -295,14 +301,22 @@ int main(int argc, char *argv[]) if (loop_start > 0.0 && loop_end > 0.0 && loop_length > 0.0) { SDL_Log("Loop points: start %g s, end %g s, length %g s\n", loop_start, loop_end, loop_length); } - Mix_FadeInMusic(music,looping,2000); + Mix_PlayMusic(music, looping); + + supports_beats = Mix_GetMusicBeat(music) != -1; + if (supports_beats) + Mix_SetMusicBeat(beat); + while (!next_track && (Mix_PlayingMusic() || Mix_PausedMusic())) { if(interactive) Menu(); else { current_position = Mix_GetMusicPosition(music); if (current_position >= 0.0) { - printf("Position: %g seconds \r", current_position); + if (supports_beats) + printf("Position: %0.2f seconds Beat: %d\r", current_position, Mix_GetMusicBeat(music)); + else + printf("Position: %0.2f seconds \r", current_position); fflush(stdout); } SDL_Delay(100); diff --git a/src/codecs/music_timidity.c b/src/codecs/music_timidity.c index 6e6baa04f..50d5a1679 100644 --- a/src/codecs/music_timidity.c +++ b/src/codecs/music_timidity.c @@ -225,12 +225,25 @@ static int TIMIDITY_Seek(void *context, double position) return 0; } +static int TIMIDITY_SeekBeat(void *context, int index) +{ + TIMIDITY_Music *music = (TIMIDITY_Music *)context; + Timidity_SeekBeat(music->song, (Uint32)index); + return 0; +} + static double TIMIDITY_Tell(void *context) { TIMIDITY_Music *music = (TIMIDITY_Music *)context; return Timidity_GetSongTime(music->song) / 1000.0; } +static int TIMIDITY_TellBeat(void *context) +{ + TIMIDITY_Music *music = (TIMIDITY_Music *)context; + return Timidity_GetSongBeat(music->song); +} + static double TIMIDITY_Duration(void *context) { TIMIDITY_Music *music = (TIMIDITY_Music *)context; @@ -291,7 +304,9 @@ Mix_MusicInterface Mix_MusicInterface_TIMIDITY = TIMIDITY_Stop, TIMIDITY_Delete, TIMIDITY_Close, - NULL /* Unload */ + NULL, /* Unload */ + TIMIDITY_SeekBeat, + TIMIDITY_TellBeat }; #endif /* MUSIC_MID_TIMIDITY */ diff --git a/src/codecs/timidity/playmidi.c b/src/codecs/timidity/playmidi.c index 87ed25336..6eb7783a7 100644 --- a/src/codecs/timidity/playmidi.c +++ b/src/codecs/timidity/playmidi.c @@ -560,6 +560,75 @@ static void seek_forward(MidiSong *song, Sint32 until_time) song->current_sample=until_time; } +static void seek_forward_beat(MidiSong *song, Uint32 beat) +{ + reset_voices(song); + while (song->current_event->beat < beat) + { + switch(song->current_event->type) + { + /* All notes stay off. Just handle the parameter changes. */ + + case ME_PITCH_SENS: + song->channel[song->current_event->channel].pitchsens = + song->current_event->a; + song->channel[song->current_event->channel].pitchfactor = 0; + break; + + case ME_PITCHWHEEL: + song->channel[song->current_event->channel].pitchbend = + song->current_event->a + song->current_event->b * 128; + song->channel[song->current_event->channel].pitchfactor = 0; + break; + + case ME_MAINVOLUME: + song->channel[song->current_event->channel].volume = + song->current_event->a; + break; + + case ME_PAN: + song->channel[song->current_event->channel].panning = + song->current_event->a; + break; + + case ME_EXPRESSION: + song->channel[song->current_event->channel].expression = + song->current_event->a; + break; + + case ME_PROGRAM: + if (ISDRUMCHANNEL(song, song->current_event->channel)) + /* Change drum set */ + song->channel[song->current_event->channel].bank = + song->current_event->a; + else + song->channel[song->current_event->channel].program = + song->current_event->a; + break; + + case ME_SUSTAIN: + song->channel[song->current_event->channel].sustain = + song->current_event->a; + break; + + case ME_RESET_CONTROLLERS: + reset_controllers(song, song->current_event->channel); + break; + + case ME_TONE_BANK: + song->channel[song->current_event->channel].bank = + song->current_event->a; + break; + + case ME_EOT: + song->current_sample = song->current_event->time; + return; + } + song->current_event++; + } + song->current_sample=song->current_event->time; +} + static void skip_to(MidiSong *song, Sint32 until_time) { if (song->current_sample > until_time) @@ -645,6 +714,17 @@ void Timidity_Seek(MidiSong *song, Uint32 ms) skip_to(song, (ms * (song->rate / 100)) / 10); } +void Timidity_SeekBeat(MidiSong *song, Uint32 beat) +{ + reset_midi(song); + song->buffered_count = 0; + song->buffer_pointer = song->common_buffer; + song->current_event = song->events; + + if (beat) + seek_forward_beat(song, beat); +} + Uint32 Timidity_GetSongLength(MidiSong *song) { MidiEvent *last_event = &song->events[song->groomed_event_count - 1]; @@ -661,6 +741,11 @@ Uint32 Timidity_GetSongTime(MidiSong *song) return retvalue; } +Uint32 Timidity_GetSongBeat(MidiSong *song) +{ + return song->current_event->beat; +} + int Timidity_PlaySome(MidiSong *song, void *stream, Sint32 len) { Sint32 start_sample, end_sample, samples; diff --git a/src/codecs/timidity/readmidi.c b/src/codecs/timidity/readmidi.c index 00ab3fa72..a3b941715 100644 --- a/src/codecs/timidity/readmidi.c +++ b/src/codecs/timidity/readmidi.c @@ -502,6 +502,7 @@ static MidiEvent *groom_list(MidiSong *song, Sint32 divisions,Sint32 *eventsp, /* Add the event to the list */ *lp=meep->event; lp->time=st; + lp->beat = meep->event.time / divisions; lp++; our_event_count++; } @@ -510,6 +511,7 @@ static MidiEvent *groom_list(MidiSong *song, Sint32 divisions,Sint32 *eventsp, } /* Add an End-of-Track event */ lp->time=st; + lp->beat=UINT8_MAX; lp->type=ME_EOT; our_event_count++; free_midi_list(song); diff --git a/src/codecs/timidity/timidity.h b/src/codecs/timidity/timidity.h index 3764c4f0b..b391cb7f6 100644 --- a/src/codecs/timidity/timidity.h +++ b/src/codecs/timidity/timidity.h @@ -98,6 +98,7 @@ typedef struct { typedef struct { Sint32 time; Uint8 channel, type, a, b; + Uint32 beat; } MidiEvent; typedef struct _MidiEventList { @@ -154,7 +155,9 @@ extern int Timidity_PlaySome(MidiSong *song, void *stream, Sint32 len); extern MidiSong *Timidity_LoadSong(SDL_RWops *rw, SDL_AudioSpec *audio); extern void Timidity_Start(MidiSong *song); extern void Timidity_Seek(MidiSong *song, Uint32 ms); +extern void Timidity_SeekBeat(MidiSong *song, Uint32 beat); extern Uint32 Timidity_GetSongLength(MidiSong *song); /* returns millseconds */ +extern Uint32 Timidity_GetSongBeat(MidiSong *song); /* returns current beat (aka quarter note) */ extern Uint32 Timidity_GetSongTime(MidiSong *song); /* returns millseconds */ extern void Timidity_Stop(MidiSong *song); extern int Timidity_IsActive(MidiSong *song); diff --git a/src/music.c b/src/music.c index 416bdbeba..0b9937f51 100644 --- a/src/music.c +++ b/src/music.c @@ -1032,6 +1032,52 @@ int Mix_SetMusicPosition(double position) return(retval); } +int Mix_SetMusicBeat(int beat) +{ + int retval; + + Mix_LockAudio(); + if (music_playing) { + if (music_playing->interface->SeekBeat) { + retval = music_playing->interface->SeekBeat(music_playing->context, beat); + } else { + retval = -1; + } + if (retval < 0) { + Mix_SetError("Seek beat not implemented for music type"); + } + } else { + Mix_SetError("Music isn't playing"); + retval = -1; + } + Mix_UnlockAudio(); + + return(retval); +} + +int Mix_GetMusicBeat() +{ + int retval; + + Mix_LockAudio(); + if (music_playing) { + if (music_playing->interface->TellBeat) { + retval = music_playing->interface->TellBeat(music_playing->context); + } else { + retval = -1; + } + if (retval < 0) { + Mix_SetError("Get beat not implemented for music type"); + } + } else { + Mix_SetError("Music isn't playing"); + retval = -1; + } + Mix_UnlockAudio(); + + return(retval); +} + /* Set the playing music position */ static double music_internal_position_get(Mix_Music *music) { diff --git a/src/music.h b/src/music.h index 8c3afccc2..495b3f43c 100644 --- a/src/music.h +++ b/src/music.h @@ -157,6 +157,12 @@ typedef struct /* Unload the library */ void (*Unload)(void); + + /* Seek to a play position (in beats) */ + int (*SeekBeat)(void *music, int beats); + + /* Tell current beat */ + int (*TellBeat)(void *music); } Mix_MusicInterface;