summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNiels Martin Hansen <nielsm@indvikleren.dk>2018-06-24 20:06:05 +0200
committerMichael Lutz <michi@icosahedron.de>2018-06-24 20:06:05 +0200
commit6298b9657103f5dca0f85c1117e720530cc4b037 (patch)
tree138961409147f12009377eb2655e02672641eeae
parent889175f7adaeae9e23041363fe2d3f685b6df695 (diff)
downloadopenttd-6298b9657103f5dca0f85c1117e720530cc4b037.tar.xz
Change: Modernise music control logic implementation (#6839)
Rewrite of almost the entire music control logic to a more modern style, hopefully also easier to understand. The old playlist handling made it look like arcane magic, which it doesn't have to be. - Playlists are now stored in std::vector of objects instead of arrays of bytes with magic sentinel values, that need to be rotated around all the time. Position in playlist is stored as a simple index. - The theme song is now reserved for the title screen, it doesn't play on any of the standard playlists, but is still available for use on custom playlists. - When the player enters/leaves the game from the main menu, the music always restarts. - Playback state (playing or not) is kept even if music becomes unavailable due to an empty playlist (or an empty music set), so it can restart immediately if music becomes available again. - The shuffle algorithm was changed to a standard Fisher-Yates. - Possibly better behavior when editing a custom playlist while it's playing. - Custom playlists should be compatible. - Framework for supporting custom playlists with songs from multiple music sets.
-rw-r--r--src/music.cpp10
-rw-r--r--src/music_gui.cpp666
-rw-r--r--src/openttd.cpp3
3 files changed, 372 insertions, 307 deletions
diff --git a/src/music.cpp b/src/music.cpp
index ffb1d7590..3d0e40bf9 100644
--- a/src/music.cpp
+++ b/src/music.cpp
@@ -126,7 +126,8 @@ bool MusicSet::FillSetDetails(IniFile *ini, const char *path, const char *full_f
IniGroup *names = ini->GetGroup("names");
IniGroup *catindex = ini->GetGroup("catindex");
IniGroup *timingtrim = ini->GetGroup("timingtrim");
- for (uint i = 0, j = 1; i < lengthof(this->songinfo); i++) {
+ uint tracknr = 1;
+ for (uint i = 0; i < lengthof(this->songinfo); i++) {
const char *filename = this->files[i].filename;
if (names == NULL || StrEmpty(filename) || this->files[i].check_result == MD5File::CR_NO_FILE) {
this->songinfo[i].songname[0] = '\0';
@@ -175,7 +176,12 @@ bool MusicSet::FillSetDetails(IniFile *ini, const char *path, const char *full_f
}
this->num_available++;
- this->songinfo[i].tracknr = j++;
+ /* Number the theme song (if any) track 0, rest are normal */
+ if (i == 0) {
+ this->songinfo[i].tracknr = 0;
+ } else {
+ this->songinfo[i].tracknr = tracknr++;
+ }
item = timingtrim->GetItem(trimmed_filename, false);
if (item != NULL && !StrEmpty(item->value)) {
diff --git a/src/music_gui.cpp b/src/music_gui.cpp
index c5cfb3bae..1edfbab41 100644
--- a/src/music_gui.cpp
+++ b/src/music_gui.cpp
@@ -10,6 +10,7 @@
/** @file music_gui.cpp GUI for the music playback. */
#include "stdafx.h"
+#include <vector>
#include "openttd.h"
#include "base_media_base.h"
#include "music/music_driver.hpp"
@@ -35,278 +36,393 @@
#include "safeguards.h"
-/**
- * Get the name of the song.
- * @param index of the song.
- * @return the name of the song.
- */
-static const char *GetSongName(int index)
+
+struct MusicSystem {
+ struct PlaylistEntry : MusicSongInfo {
+ const MusicSet *set; ///< music set the song comes from
+ uint set_index; ///< index of song in set
+
+ PlaylistEntry(const MusicSet *set, uint set_index) : MusicSongInfo(set->songinfo[set_index]), set(set), set_index(set_index) { }
+ bool IsValid() const { return !StrEmpty(this->songname); }
+ };
+ typedef std::vector<PlaylistEntry> Playlist;
+
+ enum PlaylistChoices {
+ PLCH_ALLMUSIC,
+ PLCH_OLDSTYLE,
+ PLCH_NEWSTYLE,
+ PLCH_EZYSTREET,
+ PLCH_CUSTOM1,
+ PLCH_CUSTOM2,
+ PLCH_THEMEONLY,
+ PLCH_MAX,
+ };
+
+ Playlist active_playlist; ///< current play order of songs, including any shuffle
+ Playlist displayed_playlist; ///< current playlist as displayed in GUI, never in shuffled order
+ Playlist music_set; ///< all songs in current music set, in set order
+
+ PlaylistChoices selected_playlist;
+
+ void BuildPlaylists();
+
+ void ChangePlaylist(PlaylistChoices pl);
+ void ChangeMusicSet(const char *set_name);
+ void Shuffle();
+ void Unshuffle();
+
+ void Play();
+ void Stop();
+ void Next();
+ void Prev();
+ void CheckStatus();
+
+ bool IsPlaying() const;
+ bool IsShuffle() const;
+ PlaylistEntry GetCurrentSong() const;
+
+ bool IsCustomPlaylist() const;
+ void PlaylistAdd(size_t song_index);
+ void PlaylistRemove(size_t song_index);
+ void PlaylistClear();
+
+private:
+ void ChangePlaylistPosition(int ofs);
+ int playlist_position;
+
+ void SaveCustomPlaylist(PlaylistChoices pl);
+
+ Playlist standard_playlists[PLCH_MAX];
+};
+
+MusicSystem _music;
+
+
+/** Rebuild all playlists for the current music set */
+void MusicSystem::BuildPlaylists()
{
- return BaseMusic::GetUsedSet()->songinfo[index].songname;
+ const MusicSet *set = BaseMusic::GetUsedSet();
+
+ /* Clear current playlists */
+ for (size_t i = 0; i < lengthof(this->standard_playlists); ++i) this->standard_playlists[i].clear();
+ this->music_set.clear();
+
+ /* Build standard playlists, and a list of available music */
+ for (uint i = 0; i < NUM_SONGS_AVAILABLE; i++) {
+ PlaylistEntry entry(set, i);
+ if (!entry.IsValid()) continue;
+
+ this->music_set.push_back(entry);
+
+ /* Add theme song to theme-only playlist */
+ if (i == 0) this->standard_playlists[PLCH_THEMEONLY].push_back(entry);
+
+ /* Don't add the theme song to standard playlists */
+ if (i > 0) {
+ this->standard_playlists[PLCH_ALLMUSIC].push_back(entry);
+ uint theme = (i - 1) / NUM_SONGS_CLASS;
+ this->standard_playlists[PLCH_OLDSTYLE + theme].push_back(entry);
+ }
+ }
+
+ /* Load custom playlists
+ * Song index offsets are 1-based, zero indicates invalid/end-of-list value */
+ for (uint i = 0; i < NUM_SONGS_PLAYLIST; i++) {
+ if (_settings_client.music.custom_1[i] > 0) {
+ PlaylistEntry entry(set, _settings_client.music.custom_1[i] - 1);
+ if (entry.IsValid()) this->standard_playlists[PLCH_CUSTOM1].push_back(entry);
+ }
+ if (_settings_client.music.custom_2[i] > 0) {
+ PlaylistEntry entry(set, _settings_client.music.custom_2[i] - 1);
+ if (entry.IsValid()) this->standard_playlists[PLCH_CUSTOM2].push_back(entry);
+ }
+ }
}
/**
- * Get the track number of the song.
- * @param index of the song.
- * @return the track number of the song.
+ * Switch to another playlist, or reload the current one.
+ * @param pl Playlist to select
*/
-static int GetTrackNumber(int index)
+void MusicSystem::ChangePlaylist(PlaylistChoices pl)
{
- return BaseMusic::GetUsedSet()->songinfo[index].tracknr;
-}
+ assert(pl < PLCH_MAX && pl >= PLCH_ALLMUSIC);
-/** The currently played song */
-static byte _music_wnd_cursong = 1;
-/** Whether a song is currently played */
-static bool _song_is_active = false;
-
-/** Indices of the songs in the current playlist */
-static byte _cur_playlist[NUM_SONGS_PLAYLIST + 1];
-
-/** Indices of all songs */
-static byte _playlist_all[NUM_SONGS_AVAILABLE + 1];
-/** Indices of all old style songs */
-static byte _playlist_old_style[NUM_SONGS_CLASS + 1];
-/** Indices of all new style songs */
-static byte _playlist_new_style[NUM_SONGS_CLASS + 1];
-/** Indices of all ezy street songs */
-static byte _playlist_ezy_street[NUM_SONGS_CLASS + 1];
-
-assert_compile(lengthof(_settings_client.music.custom_1) == NUM_SONGS_PLAYLIST + 1);
-assert_compile(lengthof(_settings_client.music.custom_2) == NUM_SONGS_PLAYLIST + 1);
-
-/** The different playlists that can be played. */
-static byte * const _playlists[] = {
- _playlist_all,
- _playlist_old_style,
- _playlist_new_style,
- _playlist_ezy_street,
- _settings_client.music.custom_1,
- _settings_client.music.custom_2,
-};
+ this->displayed_playlist = this->standard_playlists[pl];
+ this->active_playlist = this->displayed_playlist;
+ this->selected_playlist = pl;
+ this->playlist_position = 0;
+
+ if (this->selected_playlist != PLCH_THEMEONLY) _settings_client.music.playlist = this->selected_playlist;
+
+ if (_settings_client.music.shuffle) {
+ this->Shuffle();
+ /* Shuffle() will also Play() if necessary, only start once */
+ } else if (_settings_client.music.playing) {
+ this->Play();
+ }
+
+ InvalidateWindowData(WC_MUSIC_TRACK_SELECTION, 0);
+ InvalidateWindowData(WC_MUSIC_WINDOW, 0);
+}
/**
- * Validate a playlist.
- * @param playlist The playlist to validate.
- * @param last The last location in the list.
+ * Change to named music set, and reset playback.
+ * @param set_name Name of music set to select
*/
-void ValidatePlaylist(byte *playlist, byte *last)
+void MusicSystem::ChangeMusicSet(const char *set_name)
{
- while (*playlist != 0 && playlist <= last) {
- /* Song indices are saved off-by-one so 0 is "nothing". */
- if (*playlist <= NUM_SONGS_AVAILABLE && !StrEmpty(GetSongName(*playlist - 1))) {
- playlist++;
- continue;
- }
- for (byte *p = playlist; *p != 0 && p <= last; p++) {
- p[0] = p[1];
- }
- }
+ BaseMusic::SetSet(set_name);
- /* Make sure the list is null terminated. */
- *last = 0;
+ this->BuildPlaylists();
+ this->ChangePlaylist(this->selected_playlist);
+
+ InvalidateWindowData(WC_GAME_OPTIONS, WN_GAME_OPTIONS_GAME_OPTIONS, 0, true);
}
-/** Prepare the playlists */
-void InitializeMusic()
+/** Enable shuffle mode and restart playback */
+void MusicSystem::Shuffle()
{
- uint j = 0;
- for (uint i = 0; i < NUM_SONGS_AVAILABLE; i++) {
- if (StrEmpty(GetSongName(i))) continue;
- _playlist_all[j++] = i + 1;
- }
- /* Terminate the list */
- _playlist_all[j] = 0;
-
- /* Now make the 'styled' playlists */
- for (uint k = 0; k < NUM_SONG_CLASSES; k++) {
- j = 0;
- for (uint i = 0; i < NUM_SONGS_CLASS; i++) {
- int id = k * NUM_SONGS_CLASS + i + 1;
- if (StrEmpty(GetSongName(id))) continue;
- _playlists[k + 1][j++] = id + 1;
- }
- /* Terminate the list */
- _playlists[k + 1][j] = 0;
+ _settings_client.music.shuffle = true;
+
+ this->active_playlist = this->displayed_playlist;
+ for (size_t i = 0; i < this->active_playlist.size(); i++) {
+ size_t shuffle_index = InteractiveRandom() % (this->active_playlist.size() - i);
+ std::swap(this->active_playlist[i], this->active_playlist[i + shuffle_index]);
}
- ValidatePlaylist(_settings_client.music.custom_1, lastof(_settings_client.music.custom_1));
- ValidatePlaylist(_settings_client.music.custom_2, lastof(_settings_client.music.custom_2));
+ if (_settings_client.music.playing) this->Play();
- if (BaseMusic::GetUsedSet()->num_available < _music_wnd_cursong) {
- /* If there are less songs than the currently played song,
- * just pause and reset to no song. */
- _music_wnd_cursong = 0;
- _song_is_active = false;
- }
+ InvalidateWindowData(WC_MUSIC_WINDOW, 0);
}
-static void SkipToPrevSong()
+/** Disable shuffle and restart playback */
+void MusicSystem::Unshuffle()
{
- byte *b = _cur_playlist;
- byte *p = b;
- byte t;
+ _settings_client.music.shuffle = false;
+ this->active_playlist = this->displayed_playlist;
- if (b[0] == 0) return; // empty playlist
+ if (_settings_client.music.playing) this->Play();
- do p++; while (p[0] != 0); // find the end
+ InvalidateWindowData(WC_MUSIC_WINDOW, 0);
+}
- t = *--p; // and copy the bytes
- while (p != b) {
- p--;
- p[1] = p[0];
- }
- *b = t;
+/** Start/restart playback at current song */
+void MusicSystem::Play()
+{
+ /* Always set the playing flag, even if there is no music */
+ _settings_client.music.playing = true;
+ MusicDriver::GetInstance()->StopSong();
+ /* Make sure playlist_position is a valid index, if playlist has changed etc. */
+ this->ChangePlaylistPosition(0);
+
+ /* If there is no music, don't try to play it */
+ if (this->active_playlist.empty()) return;
- _song_is_active = false;
+ MusicSongInfo song = this->active_playlist[this->playlist_position];
+ if (_game_mode == GM_MENU && this->selected_playlist == PLCH_THEMEONLY) song.loop = true;
+ MusicDriver::GetInstance()->PlaySong(song);
+
+ InvalidateWindowData(WC_MUSIC_WINDOW, 0);
}
-static void SkipToNextSong()
+/** Stop playback and set flag that we don't intend to play music */
+void MusicSystem::Stop()
{
- byte *b = _cur_playlist;
- byte t;
-
- t = b[0];
- if (t != 0) {
- while (b[1] != 0) {
- b[0] = b[1];
- b++;
- }
- b[0] = t;
- }
+ MusicDriver::GetInstance()->StopSong();
+ _settings_client.music.playing = false;
- _song_is_active = false;
+ InvalidateWindowData(WC_MUSIC_WINDOW, 0);
}
-static void MusicVolumeChanged(byte new_vol)
+/** Skip to next track */
+void MusicSystem::Next()
{
- MusicDriver::GetInstance()->SetVolume(new_vol);
+ this->ChangePlaylistPosition(+1);
+ if (_settings_client.music.playing) this->Play();
+
+ InvalidateWindowData(WC_MUSIC_WINDOW, 0);
}
-static void DoPlaySong()
+/** Skip to previous track */
+void MusicSystem::Prev()
{
- char filename[MAX_PATH];
- MusicSongInfo songinfo = BaseMusic::GetUsedSet()->songinfo[_music_wnd_cursong - 1]; // copy
- if (FioFindFullPath(filename, lastof(filename), BASESET_DIR, songinfo.filename) == NULL) {
- FioFindFullPath(filename, lastof(filename), OLD_GM_DIR, songinfo.filename);
+ this->ChangePlaylistPosition(-1);
+ if (_settings_client.music.playing) this->Play();
+
+ InvalidateWindowData(WC_MUSIC_WINDOW, 0);
+}
+
+/** Check that music is playing if it should, and that appropriate playlist is active for game/main menu */
+void MusicSystem::CheckStatus()
+{
+ if ((_game_mode == GM_MENU) != (this->selected_playlist == PLCH_THEMEONLY)) {
+ /* Make sure the theme-only playlist is active when on the title screen, and not during gameplay */
+ this->ChangePlaylist((_game_mode == GM_MENU) ? PLCH_THEMEONLY : (PlaylistChoices)_settings_client.music.playlist);
}
- songinfo.filename = filename; // non-owned pointer
- songinfo.loop = (_game_mode == GM_MENU) && (_music_wnd_cursong == 1);
- MusicDriver::GetInstance()->PlaySong(songinfo);
- SetWindowDirty(WC_MUSIC_WINDOW, 0);
+ if (this->active_playlist.empty()) return;
+ /* If we were supposed to be playing, but music has stopped, move to next song */
+ if (this->IsPlaying() && !MusicDriver::GetInstance()->IsSongPlaying()) this->Next();
}
-static void DoStopMusic()
+/** Is the player getting music right now? */
+bool MusicSystem::IsPlaying() const
{
- MusicDriver::GetInstance()->StopSong();
- SetWindowDirty(WC_MUSIC_WINDOW, 0);
+ return _settings_client.music.playing && !this->active_playlist.empty();
}
-/** Reload the active playlist data from playlist selection and shuffle setting */
-static void ResetPlaylist()
+/** Is shuffle mode enabled? */
+bool MusicSystem::IsShuffle() const
{
- uint i = 0;
- uint j = 0;
-
- memset(_cur_playlist, 0, sizeof(_cur_playlist));
- do {
- /* File is the index into the file table of the music set. The play list uses 0 as 'no entry',
- * so we need to subtract 1. In case of 'no entry' (file = -1), just skip adding it outright. */
- int file = _playlists[_settings_client.music.playlist][i] - 1;
- if (file >= 0) {
- const char *filename = BaseMusic::GetUsedSet()->files[file].filename;
- /* We are now checking for the existence of that file prior
- * to add it to the list of available songs */
- if (!StrEmpty(filename) && FioCheckFileExists(filename, BASESET_DIR)) {
- _cur_playlist[j] = _playlists[_settings_client.music.playlist][i];
- j++;
- }
- }
- } while (_playlists[_settings_client.music.playlist][++i] != 0 && j < lengthof(_cur_playlist) - 1);
-
- /* Do not shuffle when on the intro-start window, as the song to play has to be the original TTD Theme*/
- if (_settings_client.music.shuffle && _game_mode != GM_MENU) {
- i = 500;
- do {
- uint32 r = InteractiveRandom();
- byte *a = &_cur_playlist[GB(r, 0, 5)];
- byte *b = &_cur_playlist[GB(r, 8, 5)];
-
- if (*a != 0 && *b != 0) {
- byte t = *a;
- *a = *b;
- *b = t;
- }
- } while (--i);
- }
+ return _settings_client.music.shuffle;
}
-static void StopMusic()
+/** Return the current song, or a dummy if none */
+MusicSystem::PlaylistEntry MusicSystem::GetCurrentSong() const
{
- _music_wnd_cursong = 0;
- DoStopMusic();
- _song_is_active = false;
- SetWindowWidgetDirty(WC_MUSIC_WINDOW, 0, 9);
+ if (!this->IsPlaying()) return PlaylistEntry(BaseMusic::GetUsedSet(), 0);
+ return this->active_playlist[this->playlist_position];
+}
+
+/** Is one of the custom playlists selected? */
+bool MusicSystem::IsCustomPlaylist() const
+{
+ return (this->selected_playlist == PLCH_CUSTOM1) || (this->selected_playlist == PLCH_CUSTOM2);
+}
+
+/**
+ * Append a song to a custom playlist.
+ * Always adds to the currently active playlist.
+ * @param song_index Index of song in the current music set to add
+ */
+void MusicSystem::PlaylistAdd(size_t song_index)
+{
+ if (!this->IsCustomPlaylist()) return;
+
+ /* Pick out song from the music set */
+ if (song_index >= this->music_set.size()) return;
+ PlaylistEntry entry = this->music_set[song_index];
+
+ /* Check for maximum length */
+ if (this->standard_playlists[this->selected_playlist].size() >= NUM_SONGS_PLAYLIST) return;
+
+ /* Add it to the appropriate playlist, and the display */
+ this->standard_playlists[this->selected_playlist].push_back(entry);
+ this->displayed_playlist.push_back(entry);
+
+ /* Add it to the active playlist, if playback is shuffled select a random position to add at */
+ if (this->active_playlist.empty()) {
+ this->active_playlist.push_back(entry);
+ if (this->IsPlaying()) this->Play();
+ } else if (this->IsShuffle()) {
+ /* Generate a random position between 0 and n (inclusive, new length) to insert at */
+ size_t maxpos = this->displayed_playlist.size();
+ size_t newpos = InteractiveRandom() % maxpos;
+ this->active_playlist.insert(this->active_playlist.begin() + newpos, entry);
+ /* Make sure to shift up the current playback position if the song was inserted before it */
+ if ((int)newpos <= this->playlist_position) this->playlist_position++;
+ } else {
+ this->active_playlist.push_back(entry);
+ }
+
+ this->SaveCustomPlaylist(this->selected_playlist);
+
+ InvalidateWindowData(WC_MUSIC_TRACK_SELECTION, 0);
}
-/** Begin playing the next song on the playlist */
-static void PlayPlaylistSong()
+/**
+ * Remove a song from a custom playlist.
+ * @param song_index Index in the custom playlist to remove.
+ */
+void MusicSystem::PlaylistRemove(size_t song_index)
{
- if (_cur_playlist[0] == 0) {
- ResetPlaylist();
- /* if there is not songs in the playlist, it may indicate
- * no file on the gm folder, or even no gm folder.
- * Stop the playback, then */
- if (_cur_playlist[0] == 0) {
- _song_is_active = false;
- _music_wnd_cursong = 0;
- _settings_client.music.playing = false;
- return;
+ if (!this->IsCustomPlaylist()) return;
+
+ Playlist &pl = this->standard_playlists[this->selected_playlist];
+ if (song_index >= pl.size()) return;
+
+ /* Remove from "simple" playlists */
+ PlaylistEntry song = pl[song_index];
+ pl.erase(pl.begin() + song_index);
+ this->displayed_playlist.erase(this->displayed_playlist.begin() + song_index);
+
+ /* Find in actual active playlist (may be shuffled) and remove,
+ * if it's the current song restart playback */
+ for (size_t i = 0; i < this->active_playlist.size(); i++) {
+ Playlist::iterator s2 = this->active_playlist.begin() + i;
+ if (s2->filename == song.filename && s2->cat_index == song.cat_index) {
+ this->active_playlist.erase(s2);
+ if ((int)i == this->playlist_position && this->IsPlaying()) this->Play();
+ break;
}
}
- _music_wnd_cursong = _cur_playlist[0];
- DoPlaySong();
- _song_is_active = true;
- SetWindowWidgetDirty(WC_MUSIC_WINDOW, 0, 9);
+ this->SaveCustomPlaylist(this->selected_playlist);
+
+ InvalidateWindowData(WC_MUSIC_TRACK_SELECTION, 0);
}
-void ResetMusic()
+/**
+ * Remove all songs from the current custom playlist.
+ * Effectively stops playback too.
+ */
+void MusicSystem::PlaylistClear()
{
- _music_wnd_cursong = 1;
- DoPlaySong();
+ if (!this->IsCustomPlaylist()) return;
+
+ this->standard_playlists[this->selected_playlist].clear();
+ this->ChangePlaylist(this->selected_playlist);
+
+ this->SaveCustomPlaylist(this->selected_playlist);
}
/**
- * Check music playback status and start/stop/song-finished.
- * Called from main loop.
+ * Change playlist position pointer by the given offset, making sure to keep it within valid range.
+ * If the playlist is empty, position is always set to 0.
+ * @param ofs Amount to move playlist position by.
*/
-void MusicLoop()
+void MusicSystem::ChangePlaylistPosition(int ofs)
{
- if (!_settings_client.music.playing && _song_is_active) {
- StopMusic();
- } else if (_settings_client.music.playing && !_song_is_active) {
- PlayPlaylistSong();
+ if (this->active_playlist.empty()) {
+ this->playlist_position = 0;
+ } else {
+ this->playlist_position += ofs;
+ while (this->playlist_position >= (int)this->active_playlist.size()) this->playlist_position -= (int)this->active_playlist.size();
+ while (this->playlist_position < 0) this->playlist_position += (int)this->active_playlist.size();
}
+}
- if (!_song_is_active) return;
+/**
+ * Save a custom playlist to settings after modification.
+ * @param pl Playlist to store back
+ */
+void MusicSystem::SaveCustomPlaylist(PlaylistChoices pl)
+{
+ byte *settings_pl;
+ if (pl == PLCH_CUSTOM1) {
+ settings_pl = _settings_client.music.custom_1;
+ } else if (pl == PLCH_CUSTOM2) {
+ settings_pl = _settings_client.music.custom_2;
+ } else {
+ return;
+ }
- if (!MusicDriver::GetInstance()->IsSongPlaying()) {
- if (_game_mode != GM_MENU) {
- StopMusic();
- SkipToNextSong();
- PlayPlaylistSong();
- } else {
- ResetMusic();
- }
+ size_t num = 0;
+ MemSetT(settings_pl, 0, NUM_SONGS_PLAYLIST);
+
+ for (Playlist::const_iterator song = this->standard_playlists[pl].begin(); song != this->standard_playlists[pl].end(); ++song) {
+ /* Music set indices in the settings playlist are 1-based, 0 means unused slot */
+ settings_pl[num++] = (byte)song->set_index + 1;
}
}
-static void SelectPlaylist(byte list)
+
+/**
+ * Check music playback status and start/stop/song-finished.
+ * Called from main loop.
+ */
+void MusicLoop()
{
- _settings_client.music.playlist = list;
- InvalidateWindowData(WC_MUSIC_TRACK_SELECTION, 0);
- InvalidateWindowData(WC_MUSIC_WINDOW, 0);
+ _music.CheckStatus();
}
/**
@@ -316,29 +432,20 @@ static void SelectPlaylist(byte list)
void ChangeMusicSet(int index)
{
if (BaseMusic::GetIndexOfUsedSet() == index) return;
-
- /* Resume playback after switching?
- * Always if music is already playing, and also if the user is switching
- * away from an empty music set.
- * If the user switches away from an empty set, assume it's because they
- * want to hear music now. */
- bool shouldplay = _song_is_active || (BaseMusic::GetUsedSet()->num_available == 0);
- StopMusic();
-
const char *name = BaseMusic::GetSet(index)->name;
- BaseMusic::SetSet(name);
- free(BaseMusic::ini_set);
- BaseMusic::ini_set = stredup(name);
-
- InitializeMusic();
- ResetPlaylist();
- _settings_client.music.playing = shouldplay;
+ _music.ChangeMusicSet(name);
+}
- InvalidateWindowData(WC_MUSIC_TRACK_SELECTION, 0);
- InvalidateWindowData(WC_MUSIC_WINDOW, 0);
- InvalidateWindowData(WC_GAME_OPTIONS, WN_GAME_OPTIONS_GAME_OPTIONS, 0, true);
+/**
+ * Prepare the music system for use.
+ * Called from \c InitializeGame
+ */
+void InitializeMusic()
+{
+ _music.BuildPlaylists();
}
+
struct MusicTrackSelectionWindow : public Window {
MusicTrackSelectionWindow(WindowDesc *desc, WindowNumber number) : Window(desc)
{
@@ -395,13 +502,10 @@ struct MusicTrackSelectionWindow : public Window {
case WID_MTS_LIST_LEFT: case WID_MTS_LIST_RIGHT: {
Dimension d = {0, 0};
- for (uint i = 0; i < NUM_SONGS_AVAILABLE; i++) {
- const char *song_name = GetSongName(i);
- if (StrEmpty(song_name)) continue;
-
- SetDParam(0, GetTrackNumber(i));
+ for (MusicSystem::Playlist::const_iterator song = _music.music_set.begin(); song != _music.music_set.end(); ++song) {
+ SetDParam(0, song->tracknr);
SetDParam(1, 2);
- SetDParamStr(2, GetSongName(i));
+ SetDParamStr(2, song->songname);
Dimension d2 = GetStringBoundingBox(STR_PLAYLIST_TRACK_NAME);
d.width = max(d.width, d2.width);
d.height += d2.height;
@@ -421,13 +525,10 @@ struct MusicTrackSelectionWindow : public Window {
GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, PC_BLACK);
int y = r.top + WD_FRAMERECT_TOP;
- for (uint i = 0; i < NUM_SONGS_AVAILABLE; i++) {
- const char *song_name = GetSongName(i);
- if (StrEmpty(song_name)) continue;
-
- SetDParam(0, GetTrackNumber(i));
+ for (MusicSystem::Playlist::const_iterator song = _music.music_set.begin(); song != _music.music_set.end(); ++song) {
+ SetDParam(0, song->tracknr);
SetDParam(1, 2);
- SetDParamStr(2, song_name);
+ SetDParamStr(2, song->songname);
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_PLAYLIST_TRACK_NAME);
y += FONT_HEIGHT_SMALL;
}
@@ -438,11 +539,10 @@ struct MusicTrackSelectionWindow : public Window {
GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, PC_BLACK);
int y = r.top + WD_FRAMERECT_TOP;
- for (const byte *p = _playlists[_settings_client.music.playlist]; *p != 0; p++) {
- uint i = *p - 1;
- SetDParam(0, GetTrackNumber(i));
+ for (MusicSystem::Playlist::const_iterator song = _music.music_set.begin(); song != _music.music_set.end(); ++song) {
+ SetDParam(0, song->tracknr);
SetDParam(1, 2);
- SetDParamStr(2, GetSongName(i));
+ SetDParamStr(2, song->songname);
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_PLAYLIST_TRACK_NAME);
y += FONT_HEIGHT_SMALL;
}
@@ -456,42 +556,13 @@ struct MusicTrackSelectionWindow : public Window {
switch (widget) {
case WID_MTS_LIST_LEFT: { // add to playlist
int y = this->GetRowFromWidget(pt.y, widget, 0, FONT_HEIGHT_SMALL);
-
- if (_settings_client.music.playlist < 4) return;
- if (!IsInsideMM(y, 0, BaseMusic::GetUsedSet()->num_available)) return;
-
- byte *p = _playlists[_settings_client.music.playlist];
- for (uint i = 0; i != NUM_SONGS_PLAYLIST - 1; i++) {
- if (p[i] == 0) {
- /* Find the actual song number */
- for (uint j = 0; j < NUM_SONGS_AVAILABLE; j++) {
- if (GetTrackNumber(j) == y + 1) {
- p[i] = j + 1;
- break;
- }
- }
- p[i + 1] = 0;
- this->SetDirty();
- ResetPlaylist();
- break;
- }
- }
+ _music.PlaylistAdd(y);
break;
}
case WID_MTS_LIST_RIGHT: { // remove from playlist
int y = this->GetRowFromWidget(pt.y, widget, 0, FONT_HEIGHT_SMALL);
-
- if (_settings_client.music.playlist < 4) return;
- if (!IsInsideMM(y, 0, NUM_SONGS_PLAYLIST)) return;
-
- byte *p = _playlists[_settings_client.music.playlist];
- for (uint i = y; i != NUM_SONGS_PLAYLIST - 1; i++) {
- p[i] = p[i + 1];
- }
-
- this->SetDirty();
- ResetPlaylist();
+ _music.PlaylistRemove(y);
break;
}
@@ -503,17 +574,12 @@ struct MusicTrackSelectionWindow : public Window {
}
case WID_MTS_CLEAR: // clear
- for (uint i = 0; _playlists[_settings_client.music.playlist][i] != 0; i++) _playlists[_settings_client.music.playlist][i] = 0;
- this->SetDirty();
- StopMusic();
- ResetPlaylist();
+ _music.PlaylistClear();
break;
case WID_MTS_ALL: case WID_MTS_OLD: case WID_MTS_NEW:
case WID_MTS_EZY: case WID_MTS_CUSTOM1: case WID_MTS_CUSTOM2: // set playlist
- SelectPlaylist(widget - WID_MTS_ALL);
- StopMusic();
- ResetPlaylist();
+ _music.ChangePlaylist((MusicSystem::PlaylistChoices)(widget - WID_MTS_ALL));
break;
}
}
@@ -628,8 +694,8 @@ struct MusicWindow : public Window {
case WID_M_TRACK_NAME: {
Dimension d = GetStringBoundingBox(STR_MUSIC_TITLE_NONE);
- for (uint i = 0; i < NUM_SONGS_AVAILABLE; i++) {
- SetDParamStr(0, GetSongName(i));
+ for (MusicSystem::Playlist::const_iterator song = _music.music_set.begin(); song != _music.music_set.end(); ++song) {
+ SetDParamStr(0, song->songname);
d = maxdim(d, GetStringBoundingBox(STR_MUSIC_TITLE_NAME));
}
d.width += WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
@@ -655,8 +721,8 @@ struct MusicWindow : public Window {
break;
}
StringID str = STR_MUSIC_TRACK_NONE;
- if (_song_is_active != 0 && _music_wnd_cursong != 0) {
- SetDParam(0, GetTrackNumber(_music_wnd_cursong - 1));
+ if (_music.IsPlaying()) {
+ SetDParam(0, _music.GetCurrentSong().tracknr);
SetDParam(1, 2);
str = STR_MUSIC_TRACK_DIGIT;
}
@@ -669,9 +735,9 @@ struct MusicWindow : public Window {
StringID str = STR_MUSIC_TITLE_NONE;
if (BaseMusic::GetUsedSet()->num_available == 0) {
str = STR_MUSIC_TITLE_NOMUSIC;
- } else if (_song_is_active != 0 && _music_wnd_cursong != 0) {
+ } else if (_music.IsPlaying()) {
str = STR_MUSIC_TITLE_NAME;
- SetDParamStr(0, GetSongName(_music_wnd_cursong - 1));
+ SetDParamStr(0, _music.GetCurrentSong().songname);
}
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, str, TC_FROMSTRING, SA_HOR_CENTER);
break;
@@ -711,23 +777,19 @@ struct MusicWindow : public Window {
{
switch (widget) {
case WID_M_PREV: // skip to prev
- if (!_song_is_active) return;
- SkipToPrevSong();
- this->SetDirty();
+ _music.Prev();
break;
case WID_M_NEXT: // skip to next
- if (!_song_is_active) return;
- SkipToNextSong();
- this->SetDirty();
+ _music.Next();
break;
case WID_M_STOP: // stop playing
- _settings_client.music.playing = false;
+ _music.Stop();
break;
case WID_M_PLAY: // start playing
- _settings_client.music.playing = true;
+ _music.Play();
break;
case WID_M_MUSIC_VOL: case WID_M_EFFECT_VOL: { // volume sliders
@@ -742,7 +804,7 @@ struct MusicWindow : public Window {
if (new_vol < 3) new_vol = 0;
if (new_vol != *vol) {
*vol = new_vol;
- if (widget == WID_M_MUSIC_VOL) MusicVolumeChanged(new_vol);
+ if (widget == WID_M_MUSIC_VOL) MusicDriver::GetInstance()->SetVolume(new_vol);
this->SetDirty();
}
@@ -751,12 +813,13 @@ struct MusicWindow : public Window {
}
case WID_M_SHUFFLE: // toggle shuffle
- _settings_client.music.shuffle ^= 1;
- this->SetWidgetLoweredState(WID_M_SHUFFLE, _settings_client.music.shuffle);
+ if (_music.IsShuffle()) {
+ _music.Unshuffle();
+ } else {
+ _music.Shuffle();
+ }
+ this->SetWidgetLoweredState(WID_M_SHUFFLE, _music.IsShuffle());
this->SetWidgetDirty(WID_M_SHUFFLE);
- StopMusic();
- ResetPlaylist();
- this->SetDirty();
break;
case WID_M_PROGRAMME: // show track selection
@@ -765,10 +828,7 @@ struct MusicWindow : public Window {
case WID_M_ALL: case WID_M_OLD: case WID_M_NEW:
case WID_M_EZY: case WID_M_CUSTOM1: case WID_M_CUSTOM2: // playlist
- SelectPlaylist(widget - WID_M_ALL);
- StopMusic();
- ResetPlaylist();
- this->SetDirty();
+ _music.ChangePlaylist((MusicSystem::PlaylistChoices)(widget - WID_M_ALL));
break;
}
}
diff --git a/src/openttd.cpp b/src/openttd.cpp
index c97fb6163..43c6b1170 100644
--- a/src/openttd.cpp
+++ b/src/openttd.cpp
@@ -341,8 +341,7 @@ static void LoadIntroGame(bool load_newgrfs = true)
CheckForMissingGlyphs();
- /* Play main theme */
- if (MusicDriver::GetInstance()->IsSongPlaying()) ResetMusic();
+ MusicLoop(); // ensure music is correct
}
void MakeNewgameSettingsLive()