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 | |
36 | namespace { |
37 | |
38 | const std::string& get_fallback_path(const std::string& file_path); |
39 | |
40 | ReaderDocument 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 | |
50 | std::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 | |
99 | std::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 | |
127 | SoundFile::FileFormat |
128 | SoundFile::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 | |
156 | namespace { |
157 | |
158 | // List obtained with the help of sed: |
159 | // find | sort | sed 's:^\.:/music:; /\./ !d; s:\(.*/\)\([^/]*$\):{"\2", "\1\2"},:g' |
160 | std::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 | |
247 | const 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 | |