1// SuperTux
2// Copyright (C) 2006 Matthias Braun <matze@braunis.de>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17/** Used SDL_mixer and glest source as reference */
18
19#include "audio/sound_file.hpp"
20
21#include <config.h>
22
23#include <string.h>
24#include <physfs.h>
25#include <sstream>
26#include <unordered_map>
27
28#include "audio/ogg_sound_file.hpp"
29#include "audio/sound_error.hpp"
30#include "audio/wav_sound_file.hpp"
31#include "util/file_system.hpp"
32#include "util/reader_document.hpp"
33#include "util/reader_mapping.hpp"
34#include "util/string_util.hpp"
35
36namespace {
37
38const std::string& get_fallback_path(const std::string& file_path);
39
40ReaderDocument doc_from_file_fallback(std::string& filename)
41{
42 try {
43 return ReaderDocument::from_file(filename);
44 } catch(const std::exception&) {
45 filename = get_fallback_path(filename);
46 return ReaderDocument::from_file(filename);
47 }
48}
49
50std::unique_ptr<SoundFile> load_music_file(const std::string& filename_original)
51{
52 // filename may be changed by doc_from_file_fallback
53 std::string filename = filename_original;
54 auto doc = doc_from_file_fallback(filename);
55 auto root = doc.get_root();
56 if (root.get_name() != "supertux-music")
57 {
58 throw SoundError("file is not a supertux-music file.");
59 }
60 else
61 {
62 auto music = root.get_mapping();
63
64 std::string raw_music_file;
65 float loop_begin = 0;
66 float loop_at = -1;
67
68 music.get("file", raw_music_file);
69 music.get("loop-begin", loop_begin);
70 music.get("loop-at", loop_at);
71
72 if (loop_begin < 0) {
73 throw SoundError("can't loop from negative value");
74 }
75
76 std::string basedir = FileSystem::dirname(filename);
77 raw_music_file = FileSystem::normalize(basedir + raw_music_file);
78
79 auto file = PHYSFS_openRead(raw_music_file.c_str());
80 if (!file) {
81 std::stringstream msg;
82 msg << "Couldn't open '" << raw_music_file << "': " << PHYSFS_getLastErrorCode();
83 throw SoundError(msg.str());
84 }
85 auto format = SoundFile::get_file_format(file, raw_music_file);
86 if (format == SoundFile::FORMAT_WAV)
87 {
88 return std::make_unique<WavSoundFile>(file);
89 }
90 else
91 {
92 return std::make_unique<OggSoundFile>(file, loop_begin, loop_at);
93 }
94 }
95}
96
97} // namespace
98
99std::unique_ptr<SoundFile> load_sound_file(const std::string& filename)
100{
101 if (StringUtil::has_suffix(filename, ".music")) {
102 return load_music_file(filename);
103 }
104
105 auto file = PHYSFS_openRead(filename.c_str());
106 if (!file) {
107 file = PHYSFS_openRead(get_fallback_path(filename).c_str());
108 if (!file) {
109 std::stringstream msg;
110 msg << "Couldn't open '" << filename << "': " <<
111 PHYSFS_getLastErrorCode() << ", using dummy sound file.";
112 throw SoundError(msg.str());
113 }
114 }
115
116 auto format = SoundFile::get_file_format(file, filename);
117 if (format == SoundFile::FORMAT_WAV)
118 {
119 return std::make_unique<WavSoundFile>(file);
120 }
121 else
122 {
123 return std::make_unique<OggSoundFile>(file, 0, -1);
124 }
125}
126
127SoundFile::FileFormat
128SoundFile::get_file_format(PHYSFS_File* file, const std::string& filename)
129{
130 try {
131 char magic[4];
132 if (PHYSFS_readBytes(file, magic, sizeof(magic)) < static_cast<std::make_signed<size_t>::type>(sizeof(magic)))
133 throw SoundError("Couldn't read magic, file too short");
134 if (PHYSFS_seek(file, 0) == 0) {
135 std::stringstream msg;
136 msg << "Couldn't seek through sound file: " << PHYSFS_getLastErrorCode();
137 throw SoundError(msg.str());
138 }
139
140 if (strncmp(magic, "RIFF", 4) == 0)
141 return FileFormat::FORMAT_WAV;
142 else if (strncmp(magic, "OggS", 4) == 0)
143 return FileFormat::FORMAT_OGG;
144 else
145 throw SoundError("Unknown file format");
146 } catch(std::exception& e) {
147 std::stringstream msg;
148 msg << "Couldn't read '" << filename << "': " << e.what();
149 throw SoundError(msg.str());
150 }
151}
152
153
154// Legacy
155
156namespace {
157
158// List obtained with the help of sed:
159// find | sort | sed 's:^\.:/music:; /\./ !d; s:\(.*/\)\([^/]*$\):{"\2", "\1\2"},:g'
160std::unordered_map<std::string, std::string> fallback_paths = {
161 {"airship_2.ogg", "/music/antarctic/airship_2.ogg"},
162 {"airship_remix-2.music", "/music/antarctic/airship_remix-2.music"},
163 {"airship_remix.music", "/music/antarctic/airship_remix.music"},
164 {"airship_remix.ogg", "/music/antarctic/airship_remix.ogg"},
165 {"arctic_breeze.music", "/music/antarctic/arctic_breeze.music"},
166 {"arctic_breeze.ogg", "/music/antarctic/arctic_breeze.ogg"},
167 {"arctic_cave.music", "/music/antarctic/arctic_cave.music"},
168 {"arctic_cave.ogg", "/music/antarctic/arctic_cave.ogg"},
169 {"bossattack.music", "/music/antarctic/bossattack.music"},
170 {"bossattack.ogg", "/music/antarctic/bossattack.ogg"},
171 {"cave.music", "/music/antarctic/cave.music"},
172 {"cave.ogg", "/music/antarctic/cave.ogg"},
173 {"chipdisko.music", "/music/antarctic/chipdisko.music"},
174 {"chipdisko.ogg", "/music/antarctic/chipdisko.ogg"},
175 {"jewels.music", "/music/antarctic/jewels.music"},
176 {"jewels.ogg", "/music/antarctic/jewels.ogg"},
177 {"salcon.music", "/music/antarctic/salcon.music"},
178 {"salcon.ogg", "/music/antarctic/salcon.ogg"},
179 {"voc-boss.music", "/music/antarctic/voc-boss.music"},
180 {"voc-boss.ogg", "/music/antarctic/voc-boss.ogg"},
181 {"voc-dark.music", "/music/antarctic/voc-dark.music"},
182 {"voc-dark.ogg", "/music/antarctic/voc-dark.ogg"},
183 {"voc-daytime2.music", "/music/antarctic/voc-daytime2.music"},
184 {"voc-daytime2.ogg", "/music/antarctic/voc-daytime2.ogg"},
185 {"voc-daytime.music", "/music/antarctic/voc-daytime.music"},
186 {"voc-daytime.ogg", "/music/antarctic/voc-daytime.ogg"},
187 {"voc-night.music", "/music/antarctic/voc-night.music"},
188 {"voc-night.ogg", "/music/antarctic/voc-night.ogg"},
189 {"darkforestkeep.music", "/music/castle/darkforestkeep.music"},
190 {"darkforestkeep.ogg", "/music/castle/darkforestkeep.ogg"},
191 {"fortress.music", "/music/castle/fortress.music"},
192 {"fortress.ogg", "/music/castle/fortress.ogg"},
193 {"beneath_the_rabbit_hole.music", "/music/forest/beneath_the_rabbit_hole.music"},
194 {"beneath_the_rabbit_hole.ogg", "/music/forest/beneath_the_rabbit_hole.ogg"},
195 {"bright_thunders.music", "/music/forest/bright_thunders.music"},
196 {"bright_thunders.ogg", "/music/forest/bright_thunders.ogg"},
197 {"clavelian_march.music", "/music/forest/clavelian_march.music"},
198 {"clavelian_march.ogg", "/music/forest/clavelian_march.ogg"},
199 {"forest2.music", "/music/forest/forest2.music"},
200 {"forest2.ogg", "/music/forest/forest2.ogg"},
201 {"forest3.music", "/music/forest/forest3.music"},
202 {"forest3.ogg", "/music/forest/forest3.ogg"},
203 {"forest-cave.music", "/music/forest/forest-cave.music"},
204 {"forest-cave.ogg", "/music/forest/forest-cave.ogg"},
205 {"forest-map.music", "/music/forest/forest-map.music"},
206 {"forestmap.ogg", "/music/forest/forestmap.ogg"},
207 {"forest.music", "/music/forest/forest.music"},
208 {"forest.ogg", "/music/forest/forest.ogg"},
209 {"forest-sprint.music", "/music/forest/forest-sprint.music"},
210 {"forest-sprint.ogg", "/music/forest/forest-sprint.ogg"},
211 {"forest_theme.music", "/music/forest/forest_theme.music"},
212 {"forest_theme.ogg", "/music/forest/forest_theme.ogg"},
213 {"ghostforest2.music", "/music/forest/ghostforest2.music"},
214 {"ghostforest2.ogg", "/music/forest/ghostforest2.ogg"},
215 {"ghostforest_map.music", "/music/forest/ghostforest_map.music"},
216 {"ghostforest_map.ogg", "/music/forest/ghostforest_map.ogg"},
217 {"ghostforest.music", "/music/forest/ghostforest.music"},
218 {"ghostforest.ogg", "/music/forest/ghostforest.ogg"},
219 {"greatgigantic.music", "/music/forest/greatgigantic.music"},
220 {"greatgigantic.ogg", "/music/forest/greatgigantic.ogg"},
221 {"new_forest_map.music", "/music/forest/new_forest_map.music"},
222 {"new_forest_map.ogg", "/music/forest/new_forest_map.ogg"},
223 {"shallow-green.music", "/music/forest/shallow-green.music"},
224 {"shallow-green.ogg", "/music/forest/shallow-green.ogg"},
225 {"treeboss.music", "/music/forest/treeboss.music"},
226 {"treeboss.ogg", "/music/forest/treeboss.ogg"},
227 {"wisphunt.music", "/music/forest/wisphunt.music"},
228 {"wisphunt.ogg", "/music/forest/wisphunt.ogg"},
229 {"battle_theme.music", "/music/misc/battle_theme.music"},
230 {"battle_theme.ogg", "/music/misc/battle_theme.ogg"},
231 {"bonuscave.music", "/music/misc/bonuscave.music"},
232 {"bonuscave.ogg", "/music/misc/bonuscave.ogg"},
233 {"christmas_theme.music", "/music/misc/christmas_theme.music"},
234 {"christmas_theme.ogg", "/music/misc/christmas_theme.ogg"},
235 {"credits.music", "/music/misc/credits.music"},
236 {"credits.ogg", "/music/misc/credits.ogg"},
237 {"halloween_1.music", "/music/misc/halloween_1.music"},
238 {"halloween_1.ogg", "/music/misc/halloween_1.ogg"},
239 {"intro.music", "/music/misc/intro.music"},
240 {"intro.ogg", "/music/misc/intro.ogg"},
241 {"invincible.ogg", "/music/misc/invincible.ogg"},
242 {"leveldone.ogg", "/music/misc/leveldone.ogg"},
243 {"theme.music", "/music/misc/theme.music"},
244 {"theme.ogg", "/music/misc/theme.ogg"},
245};
246
247const std::string& get_fallback_path(const std::string& file_path)
248{
249 std::string file_name = FileSystem::basename(file_path);
250 auto it = fallback_paths.find(file_name);
251 if (it != fallback_paths.end())
252 return it->second;
253 // No fallback path found
254 return file_path;
255}
256
257} // namespace
258
259
260/* EOF */
261