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