changeset 14065:a5fb37f9f59d draft

(svn r18608) -Change: add the concept of music sets
author rubidium <rubidium@openttd.org>
date Tue, 22 Dec 2009 21:40:29 +0000
parents 07b6b76bd9df
children f7ce0e000a0f
files bin/gm/no_music.obm bin/gm/orig_win.obm docs/obm_format.txt os/windows/installer/install.nsi projects/openttd_vs80.vcproj projects/openttd_vs90.vcproj source.list src/base_media_base.h src/base_media_func.h src/console_cmds.cpp src/gfxinit.cpp src/lang/english.txt src/misc.cpp src/music.cpp src/music.h src/music_gui.cpp src/network/core/tcp_content.h src/openttd.cpp src/settings_gui.cpp src/sound.cpp src/table/settings.h
diffstat 21 files changed, 603 insertions(+), 135 deletions(-) [+]
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/bin/gm/no_music.obm
@@ -0,0 +1,48 @@
+; $Id$
+;
+; This represents more or less nothingness
+;
+[metadata]
+name         = NoMusic
+shortname    = NOMU
+version      = 0
+description  = A music pack without actual music
+
+[files]
+theme =
+old_0 =
+old_1 =
+old_2 =
+old_3 =
+old_4 =
+old_5 =
+old_6 =
+old_7 =
+old_8 =
+old_9 =
+new_0 =
+new_1 =
+new_2 =
+new_3 =
+new_4 =
+new_5 =
+new_6 =
+new_7 =
+new_8 =
+new_9 =
+ezy_0 =
+ezy_1 =
+ezy_2 =
+ezy_3 =
+ezy_4 =
+ezy_5 =
+ezy_6 =
+ezy_7 =
+ezy_8 =
+ezy_9 =
+
+[md5s]
+
+[names]
+
+[origin]
new file mode 100644
--- /dev/null
+++ b/bin/gm/orig_win.obm
@@ -0,0 +1,94 @@
+; $Id$
+;
+; This represents the original music as on the Transport
+; Tycoon Deluxe for Windows CD.
+;
+[metadata]
+name         = original_windows
+shortname    = TTDW
+version      = 1
+description  = Original Transport Tycoon Deluxe Windows edition music
+
+[files]
+theme = GM_TT00.GM
+old_0 = GM_TT02.GM
+old_1 = GM_TT06.GM
+old_2 = GM_TT03.GM
+old_3 = GM_TT12.GM
+old_4 = GM_TT08.GM
+old_5 = GM_TT13.GM
+old_6 = GM_TT14.GM
+old_7 = GM_TT19.GM
+old_8 =
+old_9 =
+new_0 = GM_TT04.GM
+new_1 = GM_TT01.GM
+new_2 = GM_TT05.GM
+new_3 = GM_TT15.GM
+new_4 = GM_TT11.GM
+new_5 = GM_TT16.GM
+new_6 = GM_TT09.GM
+new_7 =
+new_8 =
+new_9 =
+ezy_0 = GM_TT18.GM
+ezy_1 = GM_TT19.GM
+ezy_2 = GM_TT21.GM
+ezy_3 = GM_TT17.GM
+ezy_4 = GM_TT20.GM
+ezy_5 = GM_TT07.GM
+ezy_6 =
+ezy_7 =
+ezy_8 =
+ezy_9 =
+
+[md5s]
+GM_TT00.GM = 45cfec1b9d8c7a0ad45e755833cbf221
+GM_TT01.GM = ab14ed3392d848abd2a2e90a9d75d121
+GM_TT02.GM = dd4f696e4be5987ce738257b08b50171
+GM_TT03.GM = a1bfde23343df9e4063419bf29c166b8
+GM_TT04.GM = 4e6943aa0c455203d76c79389054747d
+GM_TT05.GM = cee281cb85a2e2343552d97640545a47
+GM_TT06.GM = 26d1de5efa8675f94065784e9d539e49
+GM_TT07.GM = 6f2691e17558f552ec4c565e4ab7139c
+GM_TT08.GM = a42bf2cb3340a822f1a69646fc7a487d
+GM_TT09.GM = eb35761a58a8df3c59ed8929cce13916
+GM_TT10.GM = 42fecd686720a785d20a78590c466a82
+GM_TT11.GM = 50ef1ef02e49d2112786dd45e69dc3ee
+GM_TT12.GM = 4ce707a0e0e72419f0681dd9bd95271b
+GM_TT13.GM = e765753be29d889ec818f38009103619
+GM_TT14.GM = 270e2d63bd32b95a4d007ce15a6ce45f
+GM_TT15.GM = 89e116a1c0c69f1845cc903a9bfbe460
+GM_TT16.GM = f824e2371b3bedfe61aad4b9c62dd6be
+GM_TT17.GM = 1b23eebb0796c1ab99cd97fa7082cf7b
+GM_TT18.GM = 15650de3bad645d0e88c4f5c7a2df92a
+GM_TT19.GM = 7aec079e15bd09588660b85545ac4dfc
+GM_TT20.GM = 1509097889dee617aa1e9a1738a5a930
+GM_TT21.GM = a8d0aaad02e1a762d8d54cf81da56bab
+
+[names]
+GM_TT00.GM = Tycoon DELUXE Theme
+GM_TT01.GM = Snarl Up
+GM_TT02.GM = Easy Driver
+GM_TT03.GM = Little Red Diesel
+GM_TT04.GM = City Groove
+GM_TT05.GM = Aliens Ate My Railway
+GM_TT06.GM = Stoke It
+GM_TT07.GM = Don't Walk!
+GM_TT08.GM = Sawyer's Tune
+GM_TT09.GM = Fell Apart On Me
+GM_TT10.GM = Can't Get There From Here
+GM_TT11.GM = Hard Drivin'
+GM_TT12.GM = Road Hog
+GM_TT13.GM = Hold That Train!
+GM_TT14.GM = Broomer's Oil Rag
+GM_TT15.GM = Goss Groove
+GM_TT16.GM = Small Town
+GM_TT17.GM = Cruise Control
+GM_TT18.GM = Stroll On
+GM_TT19.GM = Funk Central
+GM_TT20.GM = Jammit
+GM_TT21.GM = Movin' On
+
+[origin]
+default = You can find it on your Transport Tycoon Deluxe CD-ROM.
new file mode 100644
--- /dev/null
+++ b/docs/obm_format.txt
@@ -0,0 +1,106 @@
+;
+; Example file for the OpenTTD Base Music replacement sets.
+; This file consists of basically two different parts:
+;  * metadata
+;  * information about the files/songs
+;
+; Metadata contains information about the name and version
+; of the music set.
+;
+; == Getting started ==
+; - you can't add comments after values
+; - you have to fill the MD5 checksum for each file
+; - you may not miss any of the metadata or files items
+; - `openttd -h` lists all music replacement sets it found to be correct
+; - `openttd -d grf=1` shows warnings/errors when parsing an .obm file
+; - `openttd -M <name>` starts OpenTTD with the given set (case sensitive)
+; - adding `musicset = <name>` to the misc section of openttd.cfg makes
+;   OpenTTD start with that sound set by default
+; - there is a command line tool for all platforms called md5sum that can
+;   create the MD5 checksum you need.
+; - all files specified in this file are search relatively to the path where
+;   this file is found, i.e. if the sound files are in a subdir you have
+;   to add that subdir to the names in this file to! It will NOT search for
+;   a file named like specified in here.
+
+[metadata]
+; the name of the pack, preferably less than 16 characters
+name         = example
+; the short name (4 characters), used to identify this set
+shortname    = XMPL
+; the version of this sound set (read as single integer)
+version      = 0
+; a fairly short description of the set
+; By adding '.<iso code>' you can translate the description.
+; Note that OpenTTD first tries the full ISO code, then the first
+; two characters and then uses the fallback (no '.<iso code>').
+; The ISO code matching is case sensitive!
+; So en_US will be used for en_GB if no en_GB translation is added.
+; As a result the below example has 'howdie' for en_US and en_GB but
+; 'foo' for all other languages.
+description  = foo
+description.en_US = howdie
+
+; The files section lists the files that replace songs.
+; The file names are case sensitive.
+; You can have empty file names; in that case no song will be loaded
+; for that 'entry'.
+[files]
+; The theme song for OpenTTD
+theme = THEME_SONG.GM
+; The songs in the 'old style' category
+old_0 =
+old_1 =
+old_2 =
+old_3 =
+old_4 =
+old_5 =
+old_6 =
+old_7 =
+old_8 =
+old_9 =
+; The songs in the 'new style' category
+new_0 =
+new_1 =
+new_2 =
+new_3 =
+new_4 =
+new_5 =
+new_6 =
+new_7 =
+new_8 =
+new_9 =
+; The songs in the 'ezy street' category
+ezy_0 =
+ezy_1 =
+ezy_2 =
+ezy_3 =
+ezy_4 =
+ezy_5 =
+ezy_6 =
+ezy_7 =
+ezy_8 =
+ezy_9 =
+
+; The names section lists the song names for the given file name.
+; Note that the list of files is case sensitive. Each file listed in the
+; files section must be listed here with it's song name, otherwise you
+; will get a lot of warnings when starting OpenTTD.
+[names]
+THEME_SONG.GM = Tycoon DELUXE Theme
+
+; The md5s section lists the MD5 checksum for the files that replace them.
+; Note that the list of files is case sensitive. Each file listed in the
+; files section must be listed here with it's MD5 checksum, otherwise you
+; will get a lot of warnings when starting OpenTTD.
+[md5s]
+THEME_SONG.GM = 45cfec1b9d8c7a0ad45e755833cbf221
+
+; The origin section provides the possibility to put and extra line into
+; the warning that a file is missing/corrupt. This can be used to tell
+; them where to find it. It works on the filename specified in the
+; files section and if that is not found it will fall back to the default
+; as shown below here.
+[origin]
+default       = You can find it on your Transport Tycoon Deluxe CD-ROM.
+THEME_SONG.GM  = You can find it also on your Transport Tycoon Deluxe CD-ROM.
--- a/os/windows/installer/install.nsi
+++ b/os/windows/installer/install.nsi
@@ -123,6 +123,10 @@
 	File ${PATH_ROOT}bin\data\*.obs
 	File ${PATH_ROOT}bin\data\opntitle.dat
 
+	; Copy the music base metadata files
+	SetOutPath "$INSTDIR\gm\"
+	File ${PATH_ROOT}bin\gm\*.obm
+
 	; Copy the scripts
 	SetOutPath "$INSTDIR\scripts\"
 	File ${PATH_ROOT}bin\scripts\*.*
@@ -397,6 +401,10 @@
 	; Scripts
 	Delete "$INSTDIR\scripts\*.*"
 
+	; Base sets for music
+	Delete "$INSTDIR\gm\orig_mus.obm"
+	Delete "$INSTDIR\gm\no_sound.obm"
+
 	; Remove remaining directories
 	RMDir "$SMPROGRAMS\$SHORTCUTS\Extras\"
 	RMDir "$SMPROGRAMS\$SHORTCUTS"
--- a/projects/openttd_vs80.vcproj
+++ b/projects/openttd_vs80.vcproj
@@ -1100,10 +1100,6 @@
 				>
 			</File>
 			<File
-				RelativePath=".\..\src\music.h"
-				>
-			</File>
-			<File
 				RelativePath=".\..\src\network\network.h"
 				>
 			</File>
--- a/projects/openttd_vs90.vcproj
+++ b/projects/openttd_vs90.vcproj
@@ -1097,10 +1097,6 @@
 				>
 			</File>
 			<File
-				RelativePath=".\..\src\music.h"
-				>
-			</File>
-			<File
 				RelativePath=".\..\src\network\network.h"
 				>
 			</File>
--- a/source.list
+++ b/source.list
@@ -184,7 +184,6 @@
 map_func.h
 map_type.h
 mixer.h
-music.h
 network/network.h
 network/network_base.h
 network/network_client.h
--- a/src/base_media_base.h
+++ b/src/base_media_base.h
@@ -14,6 +14,7 @@
 
 #include "fileio_func.h"
 #include "core/smallmap_type.hpp"
+#include "gfx_type.h"
 
 /* Forward declare these; can't do 'struct X' in functions as older GCCs barf on that */
 struct IniFile;
@@ -32,21 +33,25 @@
 	uint8 hash[16];              ///< md5 sum of the file
 	const char *missing_warning; ///< warning when this file is missing
 
-	ChecksumResult CheckMD5() const;
+	ChecksumResult CheckMD5(Subdirectory subdir) const;
 };
 
 /**
  * Information about a single base set.
  * @tparam T the real class we're going to be
  * @tparam Tnum_files the number of files in the set
+ * @tparam Tsubdir the subdirectory where to find the files
  */
-template <class T, size_t Tnum_files>
+template <class T, size_t Tnum_files, Subdirectory Tsubdir>
 struct BaseSet {
 	typedef SmallMap<const char *, const char *> TranslatedStrings;
 
 	/** Number of files in this set */
 	static const size_t NUM_FILES = Tnum_files;
 
+	/** The sub directory to search for the files */
+	static const Subdirectory SUBDIR = Tsubdir;
+
 	/** Internal names of the files in this set. */
 	static const char * const *file_names;
 
@@ -163,7 +168,7 @@
 	static uint FindSets()
 	{
 		BaseMedia<Tbase_set> fs;
-		return fs.Scan(GetExtension(), DATA_DIR);
+		return fs.Scan(GetExtension(), Tbase_set::SUBDIR);
 	}
 
 	/**
@@ -226,7 +231,7 @@
 };
 
 /** All data of a graphics set. */
-struct GraphicsSet : BaseSet<GraphicsSet, MAX_GFT> {
+struct GraphicsSet : BaseSet<GraphicsSet, MAX_GFT, DATA_DIR> {
 	PaletteType palette;       ///< Palette of this graphics set
 
 	bool FillSetDetails(struct IniFile *ini, const char *path);
@@ -242,7 +247,7 @@
 };
 
 /** All data of a sounds set. */
-struct SoundsSet : BaseSet<SoundsSet, 1> {
+struct SoundsSet : BaseSet<SoundsSet, 1, DATA_DIR> {
 };
 
 /** All data/functions related with replacing the base sounds */
@@ -250,4 +255,29 @@
 public:
 };
 
+/** Maximum number of songs in the 'class' playlists. */
+static const uint NUM_SONGS_CLASS     = 10;
+/** Number of classes for songs */
+static const uint NUM_SONG_CLASSES    = 3;
+/** Maximum number of songs in the full playlist; theme song + the classes */
+static const uint NUM_SONGS_AVAILABLE = 1 + NUM_SONG_CLASSES * NUM_SONGS_CLASS;
+
+/** Maximum number of songs in the (custom) playlist */
+static const uint NUM_SONGS_PLAYLIST  = 32;
+
+/** All data of a music set. */
+struct MusicSet : BaseSet<MusicSet, NUM_SONGS_AVAILABLE, GM_DIR> {
+	/** The name of the different songs. */
+	char song_name[NUM_SONGS_AVAILABLE][32];
+	byte track_nr[NUM_SONGS_AVAILABLE];
+	byte num_available;
+
+	bool FillSetDetails(struct IniFile *ini, const char *path);
+};
+
+/** All data/functions related with replacing the base music */
+class BaseMusic : public BaseMedia<MusicSet> {
+public:
+};
+
 #endif /* BASE_MEDIA_BASE_H */
--- a/src/base_media_func.h
+++ b/src/base_media_func.h
@@ -28,8 +28,8 @@
 		return false; \
 	}
 
-template <class T, size_t Tnum_files>
-bool BaseSet<T, Tnum_files>::FillSetDetails(IniFile *ini, const char *path)
+template <class T, size_t Tnum_files, Subdirectory Tsubdir>
+bool BaseSet<T, Tnum_files, Tsubdir>::FillSetDetails(IniFile *ini, const char *path)
 {
 	memset(this, 0, sizeof(*this));
 
@@ -64,13 +64,21 @@
 	for (uint i = 0; i < Tnum_files; i++) {
 		MD5File *file = &this->files[i];
 		/* Find the filename first. */
-		item = files->GetItem(BaseSet<T, Tnum_files>::file_names[i], false);
+		item = files->GetItem(BaseSet<T, Tnum_files, Tsubdir>::file_names[i], false);
 		if (item == NULL) {
-			DEBUG(grf, 0, "No " SET_TYPE " file for: %s", BaseSet<T, Tnum_files>::file_names[i]);
+			DEBUG(grf, 0, "No " SET_TYPE " file for: %s", BaseSet<T, Tnum_files, Tsubdir>::file_names[i]);
 			return false;
 		}
 
 		const char *filename = item->value;
+		if (filename == NULL) {
+			file->filename = NULL;
+			/* If we list no file, that file must be valid */
+			this->valid_files++;
+			this->found_files++;
+			continue;
+		}
+
 		file->filename = str_fmt("%s%s", path, filename);
 
 		/* Then find the MD5 checksum */
@@ -100,7 +108,7 @@
 		}
 
 		/* Then find the warning message when the file's missing */
-		item = origin->GetItem(filename, false);
+		item = filename == NULL ? NULL : origin->GetItem(filename, false);
 		if (item == NULL) item = origin->GetItem("default", false);
 		if (item == NULL) {
 			DEBUG(grf, 1, "No origin warning message specified for: %s", filename);
@@ -109,7 +117,7 @@
 			file->missing_warning = strdup(item->value);
 		}
 
-		switch (file->CheckMD5()) {
+		switch (file->CheckMD5(Tsubdir)) {
 			case MD5File::CR_MATCH:
 				this->valid_files++;
 				/* FALL THROUGH */
--- a/src/console_cmds.cpp
+++ b/src/console_cmds.cpp
@@ -1669,7 +1669,7 @@
 	if (strcasecmp(argv[1], "state") == 0) {
 		IConsolePrintF(CC_WHITE, "id, type, state, name");
 		for (ConstContentIterator iter = _network_content_client.Begin(); iter != _network_content_client.End(); iter++) {
-			static const char * const types[] = { "Base graphics", "NewGRF", "AI", "AI library", "Scenario", "Heightmap", "Base sound" };
+			static const char * const types[] = { "Base graphics", "NewGRF", "AI", "AI library", "Scenario", "Heightmap", "Base sound", "Base music" };
 			assert_compile(lengthof(types) == CONTENT_TYPE_END - CONTENT_TYPE_BEGIN);
 			static const char * const states[] = { "Not selected", "Selected", "Dep Selected", "Installed", "Unknown" };
 			static const ConsoleColour state_to_colour[] = { CC_COMMAND, CC_INFO, CC_INFO, CC_WHITE, CC_ERROR };
--- a/src/gfxinit.cpp
+++ b/src/gfxinit.cpp
@@ -118,7 +118,7 @@
 		/* Not all files were loaded succesfully, see which ones */
 		add_pos += seprintf(add_pos, last, "Trying to load graphics set '%s', but it is incomplete. The game will probably not run correctly until you properly install this set or select another one. See section 4.1 of readme.txt.\n\nThe following files are corrupted or missing:\n", used_set->name);
 		for (uint i = 0; i < GraphicsSet::NUM_FILES; i++) {
-			MD5File::ChecksumResult res = used_set->files[i].CheckMD5();
+			MD5File::ChecksumResult res = used_set->files[i].CheckMD5(DATA_DIR);
 			if (res != MD5File::CR_MATCH) add_pos += seprintf(add_pos, last, "\t%s is %s (%s)\n", used_set->files[i].filename, res == MD5File::CR_MISMATCH ? "corrupt" : "missing", used_set->files[i].missing_warning);
 		}
 		add_pos += seprintf(add_pos, last, "\n");
@@ -131,7 +131,7 @@
 		assert_compile(SoundsSet::NUM_FILES == 1);
 		/* No need to loop each file, as long as there is only a single
 		 * sound file. */
-		add_pos += seprintf(add_pos, last, "\t%s is %s (%s)\n", sounds_set->files->filename, sounds_set->files->CheckMD5() == MD5File::CR_MISMATCH ? "corrupt" : "missing", sounds_set->files->missing_warning);
+		add_pos += seprintf(add_pos, last, "\t%s is %s (%s)\n", sounds_set->files->filename, sounds_set->files->CheckMD5(DATA_DIR) == MD5File::CR_MISMATCH ? "corrupt" : "missing", sounds_set->files->missing_warning);
 	}
 
 	if (add_pos != error_msg) ShowInfoF("%s", error_msg);
@@ -206,7 +206,7 @@
 
 bool GraphicsSet::FillSetDetails(IniFile *ini, const char *path)
 {
-	bool ret = this->BaseSet<GraphicsSet, MAX_GFT>::FillSetDetails(ini, path);
+	bool ret = this->BaseSet<GraphicsSet, MAX_GFT, DATA_DIR>::FillSetDetails(ini, path);
 	if (ret) {
 		IniGroup *metadata = ini->GetGroup("metadata");
 		IniItem *item;
@@ -220,15 +220,16 @@
 
 /**
  * Calculate and check the MD5 hash of the supplied filename.
+ * @param subdir The sub directory to get the files from
  * @return
  *  CR_MATCH if the MD5 hash matches
  *  CR_MISMATCH if the MD5 does not match
  *  CR_NO_FILE if the file misses
  */
-MD5File::ChecksumResult MD5File::CheckMD5() const
+MD5File::ChecksumResult MD5File::CheckMD5(Subdirectory subdir) const
 {
 	size_t size;
-	FILE *f = FioFOpenFile(this->filename, "rb", DATA_DIR, &size);
+	FILE *f = FioFOpenFile(this->filename, "rb", subdir, &size);
 
 	if (f == NULL) return CR_NO_FILE;
 
@@ -252,8 +253,8 @@
 static const char * const _graphics_file_names[] = { "base", "logos", "arctic", "tropical", "toyland", "extra" };
 
 /** Implementation */
-template <class T, size_t Tnum_files>
-/* static */ const char * const *BaseSet<T, Tnum_files>::file_names = _graphics_file_names;
+template <class T, size_t Tnum_files, Subdirectory Tsubdir>
+/* static */ const char * const *BaseSet<T, Tnum_files, Tsubdir>::file_names = _graphics_file_names;
 
 extern void UpdateNewGRFConfigPalette();
 
--- a/src/lang/english.txt
+++ b/src/lang/english.txt
@@ -607,6 +607,8 @@
 STR_MUSIC_TOOLTIP_TOGGLE_PROGRAM_SHUFFLE                        :{BLACK}Toggle programme shuffle on/off
 STR_MUSIC_TOOLTIP_SHOW_MUSIC_TRACK_SELECTION                    :{BLACK}Show music track selection window
 
+STR_ERROR_NO_SONGS                                              :{WHITE}A music set without songs has been selected. No songs will be played
+
 # Playlist window
 STR_PLAYLIST_MUSIC_PROGRAM_SELECTION                            :{WHITE}Music Programme Selection
 STR_PLAYLIST_TRACK_NAME                                         :{TINYFONT}{LTBLUE}{ZEROFILL_NUM} "{RAW_STRING}"
@@ -942,6 +944,11 @@
 STR_GAME_OPTIONS_BASE_SFX_TOOLTIP                               :{BLACK}Select the base sounds set to use
 STR_GAME_OPTIONS_BASE_SFX_DESCRIPTION_TOOLTIP                   :{BLACK}Additional information about the base sounds set
 
+STR_GAME_OPTIONS_BASE_MUSIC                                     :{BLACK}Base music set
+STR_GAME_OPTIONS_BASE_MUSIC_TOOLTIP                             :{BLACK}Select the base music set to use
+STR_GAME_OPTIONS_BASE_MUSIC_STATUS                              :{RED}{NUM} corrupted file{P "" s}
+STR_GAME_OPTIONS_BASE_MUSIC_DESCRIPTION_TOOLTIP                 :{BLACK}Additional information about the base music set
+
 STR_ERROR_FULLSCREEN_FAILED                                     :{WHITE}Fullscreen mode failed
 
 # Custom currency window
@@ -1812,6 +1819,7 @@
 STR_CONTENT_TYPE_SCENARIO                                       :Scenario
 STR_CONTENT_TYPE_HEIGHTMAP                                      :Heightmap
 STR_CONTENT_TYPE_BASE_SOUNDS                                    :Base sounds
+STR_CONTENT_TYPE_BASE_MUSIC                                     :Base music
 
 # Content downloading progress window
 STR_CONTENT_DOWNLOAD_TITLE                                      :{WHITE}Downloading content...
--- a/src/misc.cpp
+++ b/src/misc.cpp
@@ -31,6 +31,7 @@
 extern void MakeNewgameSettingsLive();
 
 void InitializeSound();
+void InitializeMusic();
 void InitializeVehicles();
 void InitializeDepots();
 void InitializeEngineRenews();
@@ -71,6 +72,7 @@
 	if (reset_settings) MakeNewgameSettingsLive();
 
 	InitializeSound();
+	InitializeMusic();
 
 	if (reset_date) {
 		SetDate(ConvertYMDToDate(_settings_game.game_creation.starting_year, 0, 1));
--- a/src/music.cpp
+++ b/src/music.cpp
@@ -10,31 +10,85 @@
 /** @file music.cpp The songs that OpenTTD knows. */
 
 #include "stdafx.h"
-#include "music.h"
+#include "debug.h"
+
+/* The type of set we're replacing */
+#define SET_TYPE "music"
+#include "base_media_func.h"
+
+INSTANTIATE_BASE_MEDIA_METHODS(BaseMedia<MusicSet>, MusicSet)
+
+/** Names corresponding to the music set's files */
+static const char * const _music_file_names[] = {
+	"theme",
+	"old_0", "old_1", "old_2", "old_3", "old_4", "old_5", "old_6", "old_7", "old_8", "old_9",
+	"new_0", "new_1", "new_2", "new_3", "new_4", "new_5", "new_6", "new_7", "new_8", "new_9",
+	"ezy_0", "ezy_1", "ezy_2", "ezy_3", "ezy_4", "ezy_5", "ezy_6", "ezy_7", "ezy_8", "ezy_9",
+};
+assert_compile(lengthof(_music_file_names) == NUM_SONGS_AVAILABLE);
+
+template <class T, size_t Tnum_files, Subdirectory Tsubdir>
+/* static */ const char * const *BaseSet<T, Tnum_files, Tsubdir>::file_names = _music_file_names;
+
+template <class Tbase_set>
+/* static */ const char *BaseMedia<Tbase_set>::GetExtension()
+{
+	return ".obm"; // OpenTTD Base Music
+}
+
+template <class Tbase_set>
+/* static */ bool BaseMedia<Tbase_set>::DetermineBestSet()
+{
+	if (BaseMedia<Tbase_set>::used_set != NULL) return true;
+
+	const Tbase_set *best = NULL;
+	for (const Tbase_set *c = BaseMedia<Tbase_set>::available_sets; c != NULL; c = c->next) {
+		if (best == NULL ||
+				best->valid_files < c->valid_files ||
+				(best->valid_files == c->valid_files &&
+					(best->shortname == c->shortname && best->version < c->version))) {
+			best = c;
+		}
+	}
 
-const SongSpecs _origin_songs_specs[] = {
-	{"gm_tt00.gm", "Tycoon DELUXE Theme"},
-	{"gm_tt02.gm", "Easy Driver"},
-	{"gm_tt03.gm", "Little Red Diesel"},
-	{"gm_tt17.gm", "Cruise Control"},
-	{"gm_tt07.gm", "Don't Walk!"},
-	{"gm_tt09.gm", "Fell Apart On Me"},
-	{"gm_tt04.gm", "City Groove"},
-	{"gm_tt19.gm", "Funk Central"},
-	{"gm_tt06.gm", "Stoke It"},
-	{"gm_tt12.gm", "Road Hog"},
-	{"gm_tt05.gm", "Aliens Ate My Railway"},
-	{"gm_tt01.gm", "Snarl Up"},
-	{"gm_tt18.gm", "Stroll On"},
-	{"gm_tt10.gm", "Can't Get There From Here"},
-	{"gm_tt08.gm", "Sawyer's Tune"},
-	{"gm_tt13.gm", "Hold That Train!"},
-	{"gm_tt21.gm", "Movin' On"},
-	{"gm_tt15.gm", "Goss Groove"},
-	{"gm_tt16.gm", "Small Town"},
-	{"gm_tt14.gm", "Broomer's Oil Rag"},
-	{"gm_tt20.gm", "Jammit"},
-	{"gm_tt11.gm", "Hard Drivin'"},
-};
+	BaseMedia<Tbase_set>::used_set = best;
+	return BaseMedia<Tbase_set>::used_set != NULL;
+}
+
+/**
+ * Try to read a single piece of metadata and return false if it doesn't exist.
+ * @param name the name of the item to fetch.
+ */
+#define fetch_name(name) \
+	item = metadata->GetItem(name, false); \
+	if (item == NULL || strlen(item->value) == 0) { \
+		DEBUG(grf, 0, "Base " SET_TYPE "set detail loading: %s field missing", name); \
+		return false; \
+	}
 
-assert_compile(NUM_SONGS_AVAILABLE == lengthof(_origin_songs_specs));
+bool MusicSet::FillSetDetails(IniFile *ini, const char *path)
+{
+	bool ret = this->BaseSet<MusicSet, NUM_SONGS_AVAILABLE, GM_DIR>::FillSetDetails(ini, path);
+	if (ret) {
+		this->num_available = 0;
+		IniGroup *names = ini->GetGroup("names");
+		for (uint i = 0, j = 1; i < lengthof(this->song_name); i++) {
+			const char *filename = this->files[i].filename;
+			if (names == NULL || StrEmpty(filename)) {
+				this->song_name[i][0] = '\0';
+				continue;
+			}
+
+			IniItem *item = names->GetItem(filename, false);
+			if (item == NULL || strlen(item->value) == 0) {
+				DEBUG(grf, 0, "Base music set song name missing: %s", filename);
+				return false;
+			}
+
+			strecpy(this->song_name[i], item->value, lastof(this->song_name[i]));
+			this->track_nr[i] = j++;
+			this->num_available++;
+		}
+	}
+	return true;
+}
deleted file mode 100644
--- a/src/music.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/* $Id$ */
-
-/*
- * This file is part of OpenTTD.
- * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
- * OpenTTD 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 General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
- */
-
-/** @file music.h Base for the music handling. */
-
-#ifndef MUSIC_H
-#define MUSIC_H
-
-#define NUM_SONGS_PLAYLIST 33
-#define NUM_SONGS_AVAILABLE 22
-
-struct SongSpecs {
-	char filename[MAX_PATH];
-	char song_name[64];
-};
-
-extern const SongSpecs _origin_songs_specs[];
-
-#endif /* MUSIC_H */
--- a/src/music_gui.cpp
+++ b/src/music_gui.cpp
@@ -12,7 +12,7 @@
 #include "stdafx.h"
 #include "openttd.h"
 #include "fileio_func.h"
-#include "music.h"
+#include "base_media_base.h"
 #include "music/music_driver.hpp"
 #include "window_gui.h"
 #include "strings_func.h"
@@ -20,6 +20,7 @@
 #include "sound_func.h"
 #include "gfx_func.h"
 #include "core/random_func.hpp"
+#include "gui.h"
 
 #include "table/strings.h"
 #include "table/sprites.h"
@@ -31,32 +32,40 @@
  */
 static const char *GetSongName(int index)
 {
-	return _origin_songs_specs[index].song_name;
+	return BaseMusic::GetUsedSet()->song_name[index];
+}
+
+/**
+ * Get the track number of the song.
+ * @param index of the song.
+ * @return the track number of the song.
+ */
+static int GetTrackNumber(int index)
+{
+	return BaseMusic::GetUsedSet()->track_nr[index];
 }
 
-
-static byte _music_wnd_cursong;
-static bool _song_is_active;
-static byte _cur_playlist[NUM_SONGS_PLAYLIST];
+/** The currently played song */
+static byte _music_wnd_cursong = 1;
+/** Whether a song is currently played */
+static bool _song_is_active = false;
 
-
-
-static byte _playlist_all[] = {
-	1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 0
-};
+/** Indices of the songs in the current playlist */
+static byte _cur_playlist[NUM_SONGS_PLAYLIST + 1];
 
-static byte _playlist_old_style[] = {
-	2, 9, 3, 10, 15, 16, 20, 14, 0
-};
+/** 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];
 
-static byte _playlist_new_style[] = {
-	7, 12, 11, 18, 22, 19, 6, 0
-};
+assert_compile(lengthof(msf.custom_1) == NUM_SONGS_PLAYLIST + 1);
+assert_compile(lengthof(msf.custom_2) == NUM_SONGS_PLAYLIST + 1);
 
-static byte _playlist_ezy_street[] = {
-	13, 8, 17, 4, 21, 5, 0
-};
-
+/** The different playlists that can be played. */
 static byte * const _playlists[] = {
 	_playlist_all,
 	_playlist_old_style,
@@ -66,6 +75,57 @@
 	msf.custom_2,
 };
 
+/**
+ * Validate a playlist.
+ * @param playlist the playlist to validate
+ */
+void ValidatePlaylist(byte *playlist)
+{
+	while (*playlist != 0) {
+		if (*playlist <= BaseMusic::GetUsedSet()->num_available) {
+			playlist++;
+			continue;
+		}
+		for (byte *p = playlist; *p != 0; p++) {
+			p[0] = p[1];
+		}
+	}
+}
+
+/** Initialize the playlists */
+void InitializeMusic()
+{
+	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;
+	}
+
+	ValidatePlaylist(msf.custom_1);
+	ValidatePlaylist(msf.custom_2);
+
+	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;
+	}
+}
+
 static void SkipToPrevSong()
 {
 	byte *b = _cur_playlist;
@@ -112,13 +172,15 @@
 {
 	char filename[MAX_PATH];
 	FioFindFullPath(filename, lengthof(filename), GM_DIR,
-			_origin_songs_specs[_music_wnd_cursong - 1].filename);
+			BaseMusic::GetUsedSet()->files[_music_wnd_cursong - 1].filename);
 	_music_driver->PlaySong(filename);
+	SetWindowDirty(WC_MUSIC_WINDOW, 0);
 }
 
 static void DoStopMusic()
 {
 	_music_driver->StopSong();
+	SetWindowDirty(WC_MUSIC_WINDOW, 0);
 }
 
 static void SelectSongToPlay()
@@ -128,9 +190,10 @@
 
 	memset(_cur_playlist, 0, sizeof(_cur_playlist));
 	do {
+		const char *filename = BaseMusic::GetUsedSet()->files[_playlists[msf.playlist][i] - 1].filename;
 		/* We are now checking for the existence of that file prior
 		 * to add it to the list of available songs */
-		if (FioCheckFileExists(_origin_songs_specs[_playlists[msf.playlist][i] - 1].filename, GM_DIR)) {
+		if (!StrEmpty(filename) && FioCheckFileExists(filename, GM_DIR)) {
 			_cur_playlist[j] = _playlists[msf.playlist][i];
 			j++;
 		}
@@ -275,10 +338,13 @@
 			case MTSW_LIST_LEFT: case MTSW_LIST_RIGHT: {
 				Dimension d = {0, 0};
 
-				for (uint i = 1; i <= NUM_SONGS_AVAILABLE; i++) {
-					SetDParam(0, i);
+				for (uint i = 0; i < NUM_SONGS_AVAILABLE; i++) {
+					const char *song_name = GetSongName(i);
+					if (StrEmpty(song_name)) continue;
+
+					SetDParam(0, GetTrackNumber(i));
 					SetDParam(1, 2);
-					SetDParamStr(2, GetSongName(i - 1));
+					SetDParamStr(2, GetSongName(i));
 					Dimension d2 = GetStringBoundingBox(STR_PLAYLIST_TRACK_NAME);
 					d.width = max(d.width, d2.width);
 					d.height += d2.height;
@@ -297,10 +363,13 @@
 				GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, 0);
 
 				int y = r.top + WD_FRAMERECT_TOP;
-				for (uint i = 1; i <= NUM_SONGS_AVAILABLE; i++) {
-					SetDParam(0, i);
+				for (uint i = 0; i < NUM_SONGS_AVAILABLE; i++) {
+					const char *song_name = GetSongName(i);
+					if (StrEmpty(song_name)) continue;
+
+					SetDParam(0, GetTrackNumber(i));
 					SetDParam(1, 2);
-					SetDParamStr(2, GetSongName(i - 1));
+					SetDParamStr(2, song_name);
 					DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_PLAYLIST_TRACK_NAME);
 					y += FONT_HEIGHT_SMALL;
 				}
@@ -311,10 +380,10 @@
 
 				int y = r.top + WD_FRAMERECT_TOP;
 				for (const byte *p = _playlists[msf.playlist]; *p != 0; p++) {
-					uint i = *p;
-					SetDParam(0, i);
+					uint i = *p - 1;
+					SetDParam(0, GetTrackNumber(i));
 					SetDParam(1, 2);
-					SetDParamStr(2, GetSongName(i - 1));
+					SetDParamStr(2, GetSongName(i));
 					DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_PLAYLIST_TRACK_NAME);
 					y += FONT_HEIGHT_SMALL;
 				}
@@ -334,12 +403,18 @@
 				int y = (pt.y - this->GetWidget<NWidgetBase>(widget)->pos_y) / FONT_HEIGHT_SMALL;
 
 				if (msf.playlist < 4) return;
-				if (!IsInsideMM(y, 0, NUM_SONGS_AVAILABLE)) return;
+				if (!IsInsideMM(y, 0, BaseMusic::GetUsedSet()->num_available)) return;
 
 				byte *p = _playlists[msf.playlist];
 				for (uint i = 0; i != NUM_SONGS_PLAYLIST - 1; i++) {
 					if (p[i] == 0) {
-						p[i] = y + 1;
+						/* 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();
 						SelectSongToPlay();
@@ -352,7 +427,7 @@
 				int y = (pt.y - this->GetWidget<NWidgetBase>(widget)->pos_y) / FONT_HEIGHT_SMALL;
 
 				if (msf.playlist < 4) return;
-				if (!IsInsideMM(y, 0, NUM_SONGS_AVAILABLE)) return;
+				if (!IsInsideMM(y, 0, NUM_SONGS_PLAYLIST)) return;
 
 				byte *p = _playlists[msf.playlist];
 				for (uint i = y; i != NUM_SONGS_PLAYLIST - 1; i++) {
@@ -364,7 +439,7 @@
 			} break;
 
 			case MTSW_CLEAR: // clear
-				_playlists[msf.playlist][0] = 0;
+				for (uint i = 0; _playlists[msf.playlist][i] != 0; i++) _playlists[msf.playlist][i] = 0;
 				this->SetDirty();
 				StopMusic();
 				SelectSongToPlay();
@@ -484,7 +559,7 @@
 
 			case MW_TRACK_NAME: {
 				Dimension d = GetStringBoundingBox(STR_MUSIC_TITLE_NONE);
-				for (int i = 0; i < NUM_SONGS_AVAILABLE; i++) {
+				for (uint i = 0; i < NUM_SONGS_AVAILABLE; i++) {
 					SetDParamStr(0, GetSongName(i));
 					d = maxdim(d, GetStringBoundingBox(STR_MUSIC_TITLE_NAME));
 				}
@@ -523,7 +598,7 @@
 				GfxFillRect(r.left + 1, r.top + 1, r.right, r.bottom, 0);
 				StringID str = STR_MUSIC_TRACK_NONE;
 				if (_song_is_active != 0 && _music_wnd_cursong != 0) {
-					SetDParam(0, _music_wnd_cursong);
+					SetDParam(0, GetTrackNumber(_music_wnd_cursong - 1));
 					SetDParam(1, 2);
 					str = STR_MUSIC_TRACK_DIGIT;
 				}
@@ -727,5 +802,6 @@
 
 void ShowMusicWindow()
 {
+	if (BaseMusic::GetUsedSet()->num_available == 0) ShowErrorMessage(STR_ERROR_NO_SONGS, INVALID_STRING_ID, 0, 0);
 	AllocateWindowDescFront<MusicWindow>(&_music_window_desc, 0);
 }
--- a/src/network/core/tcp_content.h
+++ b/src/network/core/tcp_content.h
@@ -31,6 +31,7 @@
 	CONTENT_TYPE_SCENARIO      = 5, ///< The content consists of a scenario
 	CONTENT_TYPE_HEIGHTMAP     = 6, ///< The content consists of a heightmap
 	CONTENT_TYPE_BASE_SOUNDS   = 7, ///< The content consists of base sounds
+	CONTENT_TYPE_BASE_MUSIC    = 8, ///< The content consists of base music
 	CONTENT_TYPE_END,               ///< Helper to mark the end of the types
 };
 
--- a/src/openttd.cpp
+++ b/src/openttd.cpp
@@ -181,6 +181,7 @@
 		"                          specified in graphics set file (see below)\n"
 		"  -I graphics_set     = Force the graphics set (see below)\n"
 		"  -S sounds_set       = Force the sounds set (see below)\n"
+		"  -M music_set        = Force the music set (see below)\n"
 		"  -c config_file      = Use 'config_file' instead of 'openttd.cfg'\n"
 		"  -x                  = Do not automatically save to config file on exit\n"
 		"\n",
@@ -193,6 +194,9 @@
 	/* List the sounds packs */
 	p = BaseSounds::GetSetsList(p, lastof(buf));
 
+	/* List the music packs */
+	p = BaseMusic::GetSetsList(p, lastof(buf));
+
 	/* List the drivers */
 	p = VideoDriverFactoryBase::GetDriversInfo(p, lastof(buf));
 
@@ -409,6 +413,7 @@
 	char *blitter = NULL;
 	char *graphics_set = NULL;
 	char *sounds_set = NULL;
+	char *music_set = NULL;
 	Dimension resolution = {0, 0};
 	Year startyear = INVALID_YEAR;
 	uint generation_seed = GENERATE_NEW_SEED;
@@ -446,6 +451,7 @@
 		switch (i) {
 		case 'I': free(graphics_set); graphics_set = strdup(mgo.opt); break;
 		case 'S': free(sounds_set); sounds_set = strdup(mgo.opt); break;
+		case 'M': free(music_set); music_set = strdup(mgo.opt); break;
 		case 'm': free(musicdriver); musicdriver = strdup(mgo.opt); break;
 		case 's': free(sounddriver); sounddriver = strdup(mgo.opt); break;
 		case 'v': free(videodriver); videodriver = strdup(mgo.opt); break;
@@ -536,6 +542,7 @@
 			DeterminePaths(argv[0]);
 			BaseGraphics::FindSets();
 			BaseSounds::FindSets();
+			BaseMusic::FindSets();
 			ShowHelp();
 			return 0;
 		}
@@ -549,6 +556,7 @@
 	DeterminePaths(argv[0]);
 	BaseGraphics::FindSets();
 	BaseSounds::FindSets();
+	BaseMusic::FindSets();
 
 #if defined(UNIX) && !defined(__MORPHOS__)
 	/* We must fork here, or we'll end up without some resources we need (like sockets) */
@@ -620,6 +628,14 @@
 	}
 	free(graphics_set);
 
+	if (music_set == NULL && BaseMusic::ini_set != NULL) music_set = strdup(BaseMusic::ini_set);
+	if (!BaseMusic::SetSet(music_set)) {
+		StrEmpty(music_set) ?
+			usererror("Failed to find a music set. Please acquire a music set for OpenTTD. See section 4.1 of readme.txt.") :
+			usererror("Failed to select requested music set '%s'", music_set);
+	}
+	free(music_set);
+
 	/* Initialize game palette */
 	GfxInitPalettes();
 
@@ -748,6 +764,7 @@
 
 	free(const_cast<char *>(BaseGraphics::ini_set));
 	free(const_cast<char *>(BaseSounds::ini_set));
+	free(const_cast<char *>(BaseMusic::ini_set));
 	free(_ini_musicdriver);
 	free(_ini_sounddriver);
 	free(_ini_videodriver);
--- a/src/settings_gui.cpp
+++ b/src/settings_gui.cpp
@@ -104,21 +104,24 @@
 
 /** Widgets of the game options menu */
 enum GameOptionsWidgets {
-	GOW_BACKGROUND,          ///< Background of the window
-	GOW_CURRENCY_DROPDOWN,   ///< Currency dropdown
-	GOW_DISTANCE_DROPDOWN,   ///< Measuring unit dropdown
-	GOW_ROADSIDE_DROPDOWN,   ///< Dropdown to select the road side (to set the right side ;))
-	GOW_TOWNNAME_DROPDOWN,   ///< Town name dropdown
-	GOW_AUTOSAVE_DROPDOWN,   ///< Dropdown to say how often to autosave
-	GOW_LANG_DROPDOWN,       ///< Language dropdown
-	GOW_RESOLUTION_DROPDOWN, ///< Dropdown for the resolution
-	GOW_FULLSCREEN_BUTTON,   ///< Toggle fullscreen
-	GOW_SCREENSHOT_DROPDOWN, ///< Select the screenshot type... please use PNG!
-	GOW_BASE_GRF_DROPDOWN,   ///< Use to select a base GRF
-	GOW_BASE_GRF_STATUS,     ///< Info about missing files etc.
-	GOW_BASE_GRF_DESCRIPTION,///< Description of selected base GRF
-	GOW_BASE_SFX_DROPDOWN,   ///< Use to select a base SFX
-	GOW_BASE_SFX_DESCRIPTION,///< Description of selected base SFX
+	GOW_BACKGROUND,             ///< Background of the window
+	GOW_CURRENCY_DROPDOWN,      ///< Currency dropdown
+	GOW_DISTANCE_DROPDOWN,      ///< Measuring unit dropdown
+	GOW_ROADSIDE_DROPDOWN,      ///< Dropdown to select the road side (to set the right side ;))
+	GOW_TOWNNAME_DROPDOWN,      ///< Town name dropdown
+	GOW_AUTOSAVE_DROPDOWN,      ///< Dropdown to say how often to autosave
+	GOW_LANG_DROPDOWN,          ///< Language dropdown
+	GOW_RESOLUTION_DROPDOWN,    ///< Dropdown for the resolution
+	GOW_FULLSCREEN_BUTTON,      ///< Toggle fullscreen
+	GOW_SCREENSHOT_DROPDOWN,    ///< Select the screenshot type... please use PNG!
+	GOW_BASE_GRF_DROPDOWN,      ///< Use to select a base GRF
+	GOW_BASE_GRF_STATUS,        ///< Info about missing files etc.
+	GOW_BASE_GRF_DESCRIPTION,   ///< Description of selected base GRF
+	GOW_BASE_SFX_DROPDOWN,      ///< Use to select a base SFX
+	GOW_BASE_SFX_DESCRIPTION,   ///< Description of selected base SFX
+	GOW_BASE_MUSIC_DROPDOWN,    ///< Use to select a base music set
+	GOW_BASE_MUSIC_STATUS,      ///< Info about corrupted files etc.
+	GOW_BASE_MUSIC_DESCRIPTION, ///< Description of selected base music set
 };
 
 /**
@@ -194,6 +197,8 @@
 			case GOW_BASE_GRF_DROPDOWN:   SetDParamStr(0, BaseGraphics::GetUsedSet()->name); break;
 			case GOW_BASE_GRF_STATUS:     SetDParam(0, BaseGraphics::GetUsedSet()->GetNumInvalid()); break;
 			case GOW_BASE_SFX_DROPDOWN:   SetDParamStr(0, BaseSounds::GetUsedSet()->name); break;
+			case GOW_BASE_MUSIC_DROPDOWN: SetDParamStr(0, BaseMusic::GetUsedSet()->name); break;
+			case GOW_BASE_MUSIC_STATUS:   SetDParam(0, BaseMusic::GetUsedSet()->GetNumInvalid()); break;
 		}
 	}
 
@@ -214,6 +219,11 @@
 				SetDParamStr(0, BaseSounds::GetUsedSet()->GetDescription(GetCurrentLanguageIsoCode()));
 				DrawStringMultiLine(r.left, r.right, r.top, UINT16_MAX, STR_BLACK_RAW_STRING);
 				break;
+
+			case GOW_BASE_MUSIC_DESCRIPTION:
+				SetDParamStr(0, BaseMusic::GetUsedSet()->GetDescription(GetCurrentLanguageIsoCode()));
+				DrawStringMultiLine(r.left, r.right, r.top, UINT16_MAX, STR_BLACK_RAW_STRING);
+				break;
 		}
 	}
 
@@ -246,6 +256,25 @@
 					size->height = max(size->height, (uint)GetStringHeight(STR_BLACK_RAW_STRING, size->width));
 				}
 				break;
+
+			case GOW_BASE_MUSIC_DESCRIPTION:
+				/* Find the biggest description for the default size. */
+				for (int i = 0; i < BaseMusic::GetNumSets(); i++) {
+					SetDParamStr(0, BaseMusic::GetSet(i)->GetDescription(GetCurrentLanguageIsoCode()));
+					size->height = max(size->height, (uint)GetStringHeight(STR_BLACK_RAW_STRING, size->width));
+				}
+				break;
+
+			case GOW_BASE_MUSIC_STATUS:
+				/* Find the biggest description for the default size. */
+				for (int i = 0; i < BaseMusic::GetNumSets(); i++) {
+					uint invalid_files = BaseMusic::GetSet(i)->GetNumInvalid();
+					if (invalid_files == 0) continue;
+
+					SetDParam(0, invalid_files);
+					*size = maxdim(*size, GetStringBoundingBox(STR_GAME_OPTIONS_BASE_MUSIC_STATUS));
+				}
+				break;
 		}
 	}
 
@@ -320,6 +349,10 @@
 			case GOW_BASE_SFX_DROPDOWN:
 				ShowSetMenu<BaseSounds>(this, GOW_BASE_SFX_DROPDOWN);
 				break;
+
+			case GOW_BASE_MUSIC_DROPDOWN:
+				ShowSetMenu<BaseMusic>(this, GOW_BASE_MUSIC_DROPDOWN);
+				break;
 		}
 	}
 
@@ -403,6 +436,10 @@
 			case GOW_BASE_SFX_DROPDOWN:
 				this->SetMediaSet<BaseSounds>(index);
 				break;
+
+			case GOW_BASE_MUSIC_DROPDOWN:
+				this->SetMediaSet<BaseMusic>(index);
+				break;
 		}
 	}
 
@@ -412,6 +449,9 @@
 
 		bool missing_files = BaseGraphics::GetUsedSet()->GetNumMissing() == 0;
 		this->GetWidget<NWidgetCore>(GOW_BASE_GRF_STATUS)->SetDataTip(missing_files ? STR_EMPTY : STR_GAME_OPTIONS_BASE_GRF_STATUS, STR_NULL);
+
+		missing_files = BaseMusic::GetUsedSet()->GetNumInvalid() == 0;
+		this->GetWidget<NWidgetCore>(GOW_BASE_MUSIC_STATUS)->SetDataTip(missing_files ? STR_EMPTY : STR_GAME_OPTIONS_BASE_MUSIC_STATUS, STR_NULL);
 	}
 };
 
@@ -459,7 +499,7 @@
 		EndContainer(),
 
 		NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_BASE_GRF, STR_NULL), SetPadding(0, 10, 0, 10),
-			NWidget(NWID_HORIZONTAL), SetPIP(00, 30, 0),
+			NWidget(NWID_HORIZONTAL), SetPIP(0, 30, 0),
 				NWidget(WWT_DROPDOWN, COLOUR_GREY, GOW_BASE_GRF_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_RAW_STRING, STR_GAME_OPTIONS_BASE_GRF_TOOLTIP),
 				NWidget(WWT_TEXT, COLOUR_GREY, GOW_BASE_GRF_STATUS), SetMinimalSize(150, 12), SetDataTip(STR_EMPTY, STR_NULL), SetFill(1, 0),
 			EndContainer(),
@@ -473,6 +513,14 @@
 			EndContainer(),
 			NWidget(WWT_TEXT, COLOUR_GREY, GOW_BASE_SFX_DESCRIPTION), SetMinimalSize(330, 0), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_BASE_SFX_DESCRIPTION_TOOLTIP), SetFill(1, 0), SetPadding(6, 0, 0, 0),
 		EndContainer(),
+
+		NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_BASE_MUSIC, STR_NULL), SetPadding(0, 10, 0, 10),
+			NWidget(NWID_HORIZONTAL), SetPIP(0, 30, 0),
+				NWidget(WWT_DROPDOWN, COLOUR_GREY, GOW_BASE_MUSIC_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_RAW_STRING, STR_GAME_OPTIONS_BASE_MUSIC_TOOLTIP),
+				NWidget(WWT_TEXT, COLOUR_GREY, GOW_BASE_MUSIC_STATUS), SetMinimalSize(150, 12), SetDataTip(STR_EMPTY, STR_NULL), SetFill(1, 0),
+			EndContainer(),
+			NWidget(WWT_TEXT, COLOUR_GREY, GOW_BASE_MUSIC_DESCRIPTION), SetMinimalSize(330, 0), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_BASE_MUSIC_DESCRIPTION_TOOLTIP), SetFill(1, 0), SetPadding(6, 0, 0, 0),
+		EndContainer(),
 	EndContainer(),
 };
 
--- a/src/sound.cpp
+++ b/src/sound.cpp
@@ -282,8 +282,8 @@
 static const char * const _sound_file_names[] = { "samples" };
 
 
-template <class T, size_t Tnum_files>
-/* static */ const char * const *BaseSet<T, Tnum_files>::file_names = _sound_file_names;
+template <class T, size_t Tnum_files, Subdirectory Tsubdir>
+/* static */ const char * const *BaseSet<T, Tnum_files, Tsubdir>::file_names = _sound_file_names;
 
 template <class Tbase_set>
 /* static */ const char *BaseMedia<Tbase_set>::GetExtension()
--- a/src/table/settings.h
+++ b/src/table/settings.h
@@ -249,6 +249,7 @@
 	 SDTG_BOOL("fullscreen",                 S, 0, _fullscreen,           false,    STR_NULL, NULL),
 	  SDTG_STR("graphicsset",      SLE_STRQ, S, 0, BaseGraphics::ini_set,  NULL,    STR_NULL, NULL),
 	  SDTG_STR("soundsset",        SLE_STRQ, S, 0, BaseSounds::ini_set,    NULL,    STR_NULL, NULL),
+	  SDTG_STR("musicset",         SLE_STRQ, S, 0, BaseMusic::ini_set,     NULL,    STR_NULL, NULL),
 	  SDTG_STR("videodriver",      SLE_STRQ, S, 0, _ini_videodriver,       NULL,    STR_NULL, NULL),
 	  SDTG_STR("musicdriver",      SLE_STRQ, S, 0, _ini_musicdriver,       NULL,    STR_NULL, NULL),
 	  SDTG_STR("sounddriver",      SLE_STRQ, S, 0, _ini_sounddriver,       NULL,    STR_NULL, NULL),