1 // -*- Mode: C++; tab-width:2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 // vi:tw=80:et:ts=2:sts=2
3 //
4 // -----------------------------------------------------------------------
5 //
6 // This file is part of RLVM, a RealLive virtual machine clone.
7 //
8 // -----------------------------------------------------------------------
9 //
10 // Copyright (C) 2008 Elliot Glaysher
11 //
12 // This program is free software; you can redistribute it and/or modify
13 // it under the terms of the GNU General Public License as published by
14 // the Free Software Foundation; either version 3 of the License, or
15 // (at your option) any later version.
16 //
17 // This program is distributed in the hope that it will be useful,
18 // but WITHOUT ANY WARRANTY; without even the implied warranty of
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 // GNU General Public License for more details.
21 //
22 // You should have received a copy of the GNU General Public License
23 // along with this program; if not, write to the Free Software
24 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
25 //
26 // -----------------------------------------------------------------------
27
28 #include "systems/sdl/sdl_music.h"
29
30 #include <SDL/SDL_mixer.h>
31 #include <boost/algorithm/string.hpp>
32 #include <boost/filesystem/operations.hpp>
33 #include <functional>
34 #include <iostream>
35 #include <map>
36 #include <sstream>
37 #include <string>
38 #include <utility>
39 #include <vector>
40
41 #include "systems/base/system.h"
42 #include "systems/sdl/sdl_audio_locker.h"
43 #include "utilities/exception.h"
44
45 namespace fs = boost::filesystem;
46
47 const int STOP_AT_END = -1;
48 const int STOP_NOW = -2;
49
50 const int DEFAULT_FADE_MS = 10;
51
52 std::shared_ptr<SDLMusic> SDLMusic::s_currently_playing;
53 bool SDLMusic::s_bgm_enabled = true;
54 int SDLMusic::s_computed_bgm_vol = 128;
55
56 // -----------------------------------------------------------------------
57 // SDLMusic
58 // -----------------------------------------------------------------------
59
SDLMusic(const SoundSystem::DSTrack & track,WAVFILE * wav)60 SDLMusic::SDLMusic(const SoundSystem::DSTrack& track, WAVFILE* wav)
61 : file_(wav),
62 track_(track),
63 fadetime_total_(0),
64 fade_in_ms_(0),
65 music_paused_(false) {
66 // Advance the audio stream to the starting point
67 if (track.from > 0)
68 wav->Seek(track.from);
69 }
70
~SDLMusic()71 SDLMusic::~SDLMusic() {
72 SDLAudioLocker locker;
73 delete file_;
74
75 if (s_currently_playing.get() == this)
76 s_currently_playing.reset();
77 }
78
IsLooping() const79 bool SDLMusic::IsLooping() const {
80 SDLAudioLocker locker;
81 return loop_point_ != STOP_AT_END;
82 }
83
IsFading() const84 bool SDLMusic::IsFading() const {
85 SDLAudioLocker locker;
86 return fadetime_total_ > 0;
87 }
88
Play(bool loop)89 void SDLMusic::Play(bool loop) { FadeIn(loop, DEFAULT_FADE_MS); }
90
Stop()91 void SDLMusic::Stop() {
92 SDLAudioLocker locker;
93 if (s_currently_playing.get() == this)
94 s_currently_playing.reset();
95 }
96
FadeIn(bool loop,int fade_in_ms)97 void SDLMusic::FadeIn(bool loop, int fade_in_ms) {
98 SDLAudioLocker locker;
99
100 if (loop)
101 loop_point_ = track_.loop;
102 else
103 loop_point_ = STOP_AT_END;
104
105 fade_count_ = 0;
106 fade_in_ms_ = fade_in_ms;
107 s_currently_playing = shared_from_this();
108 }
109
FadeOut(int fade_out_ms)110 void SDLMusic::FadeOut(int fade_out_ms) {
111 SDLAudioLocker locker;
112 fade_count_ = 0;
113 if (fade_out_ms <= 0)
114 fade_out_ms = DEFAULT_FADE_MS;
115 fadetime_total_ = fade_out_ms;
116 }
117
Pause()118 void SDLMusic::Pause() {
119 SDLAudioLocker locker;
120 music_paused_ = true;
121 }
122
Unpause()123 void SDLMusic::Unpause() {
124 SDLAudioLocker locker;
125 music_paused_ = false;
126 }
127
GetName() const128 std::string SDLMusic::GetName() const {
129 SDLAudioLocker locker;
130 return track_.name;
131 }
132
BgmStatus() const133 int SDLMusic::BgmStatus() const {
134 SDLAudioLocker locker;
135
136 if (music_paused_)
137 return 0;
138 else if (IsFading())
139 return 2;
140 else
141 return 1;
142 }
143
144 // static
MixMusic(void * udata,Uint8 * stream,int len)145 void SDLMusic::MixMusic(void* udata, Uint8* stream, int len) {
146 // Inside an SDL_LockAudio() section set up by SDL_Mixer! Don't lock here!
147 SDLMusic* music = s_currently_playing.get();
148
149 int count;
150 if (!s_bgm_enabled || !music || music->music_paused_) {
151 memset(stream, 0, len);
152 return;
153 }
154 count = music->file_->Read((char*)stream, 4, len / 4);
155
156 if (count != len / 4) {
157 memset(stream + count * 4, 0, len - count * 4);
158 if (music->loop_point_ == STOP_AT_END) {
159 music->loop_point_ = STOP_NOW;
160 s_currently_playing.reset();
161 } else {
162 music->file_->Seek(music->loop_point_);
163 music->file_->Read((char*)(stream + count * 4), 4, len / 4 - count);
164 }
165 }
166
167 int cur_vol = s_computed_bgm_vol;
168 // Compute in fadetime results.
169 if (music->fade_in_ms_) {
170 int count_total = music->fade_in_ms_ * (WAVFILE::freq / 1000);
171 if (music->fade_count_ > count_total) {
172 music->fade_in_ms_ = 0;
173 } else {
174 cur_vol = cur_vol * (music->fade_count_) / count_total;
175 music->fade_count_ += len / 4;
176 }
177 } else if (music->fadetime_total_) {
178 int count_total = music->fadetime_total_ * (WAVFILE::freq / 1000);
179 if (music->fade_count_ > count_total) {
180 music->loop_point_ = STOP_NOW;
181 s_currently_playing.reset();
182 memset(stream, 0, len);
183 return;
184 }
185
186 cur_vol = cur_vol * (count_total - music->fade_count_) / count_total;
187 music->fade_count_ += len / 4;
188 }
189
190 if (cur_vol != SDL_MIX_MAXVOLUME) {
191 char stream_dup[len]; // NOLINT
192 memcpy(stream_dup, stream, len);
193 memset(stream, 0, len);
194 SDL_MixAudio(stream, (Uint8*)stream_dup, len, cur_vol);
195 }
196 }
197
198 template <typename TYPE>
BuildMusicImplementation(FILE * file,int size)199 WAVFILE* BuildMusicImplementation(FILE* file, int size) {
200 return WAVFILE::MakeConverter(new TYPE(file, size));
201 }
202
CreateMusic(System & system,const SoundSystem::DSTrack & track)203 std::shared_ptr<SDLMusic> SDLMusic::CreateMusic(
204 System& system,
205 const SoundSystem::DSTrack& track) {
206 typedef std::vector<
207 std::pair<std::string, std::function<WAVFILE*(FILE*, int)>>> FileTypes;
208 static FileTypes types = {{"wav", &BuildMusicImplementation<WAVFILE_Stream>},
209 {"nwa", &BuildMusicImplementation<NWAFILE>},
210 {"ogg", &BuildMusicImplementation<OggFILE>}};
211
212 fs::path file_path = system.FindFile(track.file, SOUND_FILETYPES);
213 if (file_path.empty()) {
214 std::ostringstream oss;
215 oss << "Could not find music file \"" << track.file << "\".";
216 throw rlvm::Exception(oss.str());
217 }
218
219 const std::string& raw_path = file_path.native();
220 for (FileTypes::const_iterator it = types.begin(); it != types.end(); ++it) {
221 if (boost::iends_with(raw_path, it->first)) {
222 FILE* f = fopen(raw_path.c_str(), "r");
223 if (f == 0) {
224 std::ostringstream oss;
225 oss << "Could not open \"" << file_path << "\" for reading.";
226 throw std::runtime_error(oss.str());
227 }
228
229 fseek(f, 0, SEEK_END);
230 int size = ftell(f);
231 rewind(f);
232
233 WAVFILE* w = it->second(f, size);
234 if (w)
235 return std::shared_ptr<SDLMusic>(new SDLMusic(track, w));
236 }
237 }
238
239 std::ostringstream oss;
240 oss << "Unsupported music file: \"" << file_path << "\"";
241 throw std::runtime_error(oss.str());
242 }
243