ge211
ge211_audio.cpp
1 #include "ge211_audio.h"
2 #include "ge211_resource.h"
3 
4 #include <SDL.h>
5 #include <SDL_mixer.h>
6 
7 #include <algorithm>
8 #include <cassert>
9 
10 namespace ge211 {
11 
12 using namespace detail;
13 
14 namespace audio {
15 
16 static inline int unit_to_volume(double unit_volume)
17 {
18  return int(unit_volume * MIX_MAX_VOLUME);
19 }
20 
21 static inline double volume_to_unit(int int_volume)
22 {
23  return int_volume / double(MIX_MAX_VOLUME);
24 }
25 
26 std::shared_ptr<Mix_Music> Music_track::load_(const std::string& filename)
27 {
28  File_resource file_resource(filename);
29  Mix_Music* raw = Mix_LoadMUS_RW(std::move(file_resource).release(), 1);
30  if (raw) return {raw, &Mix_FreeMusic};
31 
32  throw Mixer_error::could_not_load(filename);
33 }
34 
35 Music_track::Music_track(const std::string& filename, const Mixer&)
36  : ptr_{load_(filename)}
37 { }
38 
39 bool Music_track::empty() const
40 {
41  return ptr_ == nullptr;
42 }
43 
44 Music_track::operator bool() const
45 {
46  return !empty();
47 }
48 
49 std::shared_ptr<Mix_Chunk> Sound_effect::load_(const std::string& filename)
50 {
51  File_resource file_resource(filename);
52  Mix_Chunk* raw = Mix_LoadWAV_RW(std::move(file_resource).release(), 1);
53  if (raw) return {raw, &Mix_FreeChunk};
54 
55  throw Mixer_error::could_not_load(filename);
56 }
57 
58 Sound_effect::Sound_effect(const std::string& filename, const Mixer&)
59  : ptr_{load_(filename)}
60 { }
61 
62 bool Sound_effect::empty() const
63 {
64  return ptr_ == nullptr;
65 }
66 
67 Sound_effect::operator bool() const
68 {
69  return !empty();
70 }
71 
72 std::unique_ptr<Mixer> Mixer::open_mixer()
73 {
74  if (Mix_OpenAudio(MIX_DEFAULT_FREQUENCY,
75  MIX_DEFAULT_FORMAT,
76  2,
77  4096) >= 0)
78  return std::unique_ptr<Mixer>{new Mixer};
79  else
80  return {};
81 }
82 
83 Mixer::Mixer()
84  : channels_(MIX_CHANNELS)
85  , available_effect_channels_(MIX_CHANNELS)
86 {
87  int music_decoders = Mix_GetNumMusicDecoders();
88  info_sdl() << "Number of music decoders is " << music_decoders;
89  for (int i = 0; i < music_decoders; ++i) {
90  info_sdl() << " [" << i << "] " << Mix_GetMusicDecoder(i);
91  }
92 
93  int chunk_decoders = Mix_GetNumChunkDecoders();
94  info_sdl() << "Number of chunk decoders is " << chunk_decoders;
95  for (int i = 0; i < chunk_decoders; ++i) {
96  info_sdl() << " [" << i << "] " << Mix_GetChunkDecoder(i);
97  }
98 }
99 
101 {
102  Mix_CloseAudio();
103 }
104 
106 {
107  attach_music(std::move(music));
108  resume_music();
109 }
110 
112 {
113  switch (music_state_) {
114  case State::paused:
115  case State::detached:
116  break;
117 
118  case State::playing:
119  throw Client_logic_error("Mixer::attach_music: still playing");
120 
121  case State::fading_out:
122  throw Client_logic_error("Mixer::attach_music: fading out");
123  }
124 
125  current_music_ = std::move(music);
126 
127  if (current_music_) {
128  music_state_ = State::paused;
129  } else {
130  music_state_ = State::detached;
131  }
132 }
133 
135 {
136  switch (music_state_) {
137  case State::detached:
138  throw Client_logic_error("Mixer::resume_music: no music attached");
139 
140  case State::paused:
141  Mix_RewindMusic();
142  Mix_FadeInMusicPos(current_music_.ptr_.get(),
143  0,
144  int(fade_in.milliseconds()),
145  music_position_.elapsed_time().seconds());
146  music_position_.resume();
147  music_state_ = State::playing;
148  break;
149 
150  case State::fading_out:
151  throw Client_logic_error("Mixer::resume_music: fading out");
152 
153  case State::playing:
154  // idempotent
155  break;
156  }
157 }
158 
160 {
161  switch (music_state_) {
162  case State::detached:
163  throw Client_logic_error("Mixer::pause_music: no music attached");
164 
165  case State::paused:
166  // Idempotent
167  break;
168 
169  case State::fading_out:
170  throw Client_logic_error("Mixer::pause_music: fading out");
171 
172  case State::playing:
173  if (fade_out == Duration(0)) {
174  Mix_HaltMusic();
175  music_position_.pause();
176  music_state_ = State::paused;
177  } else {
178  Mix_FadeOutMusic(int(fade_out.milliseconds()));
179  music_state_ = State::fading_out;
180  }
181  break;
182  }
183 }
184 
186 {
187  switch (music_state_) {
188  case State::paused:
189  music_position_.reset();
190  break;
191 
192  case State::detached:
193  case State::playing:
194  case State::fading_out:
195  throw Client_logic_error(
196  "Mixer::rewind_music: must be paused");
197  }
198 }
199 
201 {
202  return ptr_->effect;
203 }
204 
206 {
207  return ptr_->state;
208 }
209 
210 int Mixer::find_empty_channel_() const
211 {
212  auto iter = std::find_if(channels_.begin(),
213  channels_.end(),
214  [](const auto& handle) {
215  return handle.empty();
216  });
217  if (iter == channels_.end()) {
218  throw Mixer_error::out_of_channels();
219  }
220 
221  return (int) std::distance(channels_.begin(), iter);
222 }
223 
224 void Mixer::poll_channels_()
225 {
226  if (current_music_) {
227  if (!Mix_PlayingMusic()) {
228  switch (music_state_) {
229  case State::detached:
230  case State::paused:
231  break;
232 
233  case State::playing:
234  music_position_.pause();
235  music_position_.reset();
236  music_state_ = State::paused;
237  break;
238 
239  case State::fading_out:
240  music_position_.pause();
241  music_state_ = State::paused;
242  break;
243  }
244  }
245  }
246 
247  for (int channel = 0; channel < channels_.size(); ++channel) {
248  if (channels_[channel] && !Mix_Playing(channel))
249  {
250  unregister_effect_(channel);
251  }
252  }
253 }
254 
255 Sound_effect_handle
256 Mixer::play_effect(Sound_effect effect, double volume)
257 {
258  int channel = find_empty_channel_();
259  Mix_Volume(channel, unit_to_volume(volume));
260  Mix_PlayChannel(channel, effect.ptr_.get(), 0);
261  return register_effect_(channel, std::move(effect));
262 }
263 
265 {
266  switch (ptr_->state) {
268  throw Client_logic_error("Sound_effect_handle::resume: detached");
269 
271  ptr_->state = Mixer::State::playing;
272  Mix_Resume(ptr_->channel);
273  break;
274 
276  // idempotent
277  break;
278 
280  throw Client_logic_error("Sound_effect_handle::resume: fading out");
281  }
282 }
283 
285 {
286  switch (ptr_->state) {
288  throw Client_logic_error("Sound_effect_handle::pause: detached");
289 
291  // idempotent
292  break;
293 
295  ptr_->state = Mixer::State::paused;
296  Mix_Pause(ptr_->channel);
297  break;
298 
300  throw Client_logic_error("Sound_effect_handle::pause: fading out");
301  }
302 }
303 
305 {
306  switch (ptr_->state) {
308  throw Client_logic_error("Sound_effect_handle::stop: detached");
309 
311  ptr_->mixer.unregister_effect_(ptr_->channel);
312  Mix_HaltChannel(ptr_->channel);
313  break;
314 
316  ptr_->mixer.unregister_effect_(ptr_->channel);
317  Mix_HaltChannel(ptr_->channel);
318  break;
319 
321  throw Client_logic_error("Sound_effect_handle::stop: fading out");
322  }
323 }
324 
326 {
327  Mix_Pause(-1);
328 
329  for (const auto& handle : channels_) {
330  if (handle && handle.ptr_->state == State::playing)
331  handle.ptr_->state = State::paused;
332  }
333 }
334 
336 {
337  Mix_Resume(-1);
338 
339  for (const auto& handle : channels_) {
340  if (handle && handle.ptr_->state == State::paused)
341  handle.ptr_->state = State::playing;
342  }
343 }
344 
346 {
347  return available_effect_channels_;
348 }
349 
351 Mixer::register_effect_(int channel, Sound_effect effect)
352 {
353  assert(!channels_[channel]);
354  channels_[channel] = Sound_effect_handle(*this, std::move(effect), channel);
355  --available_effect_channels_;
356  return channels_[channel];
357 }
358 
359 void Mixer::unregister_effect_(int channel)
360 {
361  assert(channels_[channel]);
362  channels_[channel].ptr_->state = State::detached;
363  channels_[channel] = {};
364  ++available_effect_channels_;
365 }
366 
368 {
369  return volume_to_unit(Mix_VolumeMusic(-1));
370 }
371 
372 void Mixer::set_music_volume(double unit_value)
373 {
374  Mix_VolumeMusic(unit_to_volume(unit_value));
375 }
376 
378 {
379  return ptr_ == nullptr;
380 }
381 
382 Sound_effect_handle::operator bool() const
383 {
384  return !empty();
385 }
386 
388  Sound_effect effect,
389  int channel)
390  : ptr_(std::make_shared<Impl_>(mixer, std::move(effect), channel))
391 { }
392 
394 {
395  if (ptr_->state == Mixer::State::detached)
396  return 0;
397  else
398  return volume_to_unit(Mix_Volume(ptr_->channel, -1));
399 }
400 
401 void Sound_effect_handle::set_volume(double unit_value)
402 {
403  if (ptr_->state != Mixer::State::detached)
404  Mix_Volume(ptr_->channel, unit_to_volume(unit_value));
405 }
406 
407 
408 } // end namespace audio
409 
410 } // end namespace ge211
void rewind_music()
Rewinds the music to the beginning.
void resume_all_effects()
Unpauses all currently-paused effects.
Mixer::State get_state() const
Gets the state of this effect.
Duration reset()
Resets the timer, returning the elapsed time since starting or the most recent reset().
Definition: ge211_time.h:349
void pause_all_effects()
Pauses all currently-playing effects.
In the process of fading out from playing to paused (for music) or to halted and detached (for sound ...
Sound_effect_handle play_effect(Sound_effect effect, double volume=1.0)
Plays the given effect track on this mixer, at the specified volume.
void set_volume(double unit_value)
Sets the playing sound effect&#39;s volume as a number from 0.0 to 1.0.
The game engine namespace.
Definition: ge211.h:17
double get_music_volume() const
Returns the music volume as a number from 0.0 to 1.0.
bool empty() const
Recognizes the empty sound effect track.
Definition: ge211_audio.cpp:62
Used to control a Sound_effect after it is started playing on a Mixer.
Definition: ge211_audio.h:328
double get_volume() const
Returns the playing sound effect&#39;s volume as a number from 0.0 to 1.0.
A music track, which can be attached to the Mixer and played.
Definition: ge211_audio.h:34
A sound effect track, which can be attached to a Mixer channel and played.
Definition: ge211_audio.h:76
double seconds() const
Gets this duration in seconds.
Definition: ge211_time.h:41
Duration elapsed_time() const
The elapsed time since the start or most recent reset, not counting paused times. ...
Definition: ge211_time.h:315
void resume()
Unpauses the timer. If the timer is already running, has no effect.
Definition: ge211_time.h:339
Sound_effect_handle()
Default-constructs the empty sound effect handle.
Definition: ge211_audio.h:337
void pause_music(Duration fade_out=Duration(0))
Pauses the currently attached music, fading out if requested.
void resume_music(Duration fade_in=Duration(0))
Plays the currently attached music from the current saved position, fading in if requested.
int available_effect_channels() const
How many effect channels are currently unused? If this is positive, then we can play an additional so...
void play_music(Music_track)
Attaches the given music track to this mixer and starts it playing.
The entity that coordinates playing all audio tracks.
Definition: ge211_audio.h:142
void set_music_volume(double unit_value)
Sets the music volume, on a scale from 0.0 to 1.0.
bool empty() const
Recognizes the empty music track.
Definition: ge211_audio.cpp:39
Duration pause()
Pauses the timer.
Definition: ge211_time.h:328
Attached but not playing.
const Sound_effect & get_effect() const
Gets the Sound_effect being played by this handle.
long milliseconds() const
Gets this duration, approximately, in milliseconds.
Definition: ge211_time.h:50
An exception that indicates that a logic error was performed by the client.
Definition: ge211_error.h:46
~Mixer()
Destructor, to clean up the mixer&#39;s resources.
State
The state of an audio channel.
Definition: ge211_audio.h:146
A length of time.
Definition: ge211_time.h:30
bool empty() const
Recognizes the empty sound effect handle.
void stop()
Stops the effect from playing and detaches it.
No track is attached to the channel, or no channel is attached to the handle.
Music_track()
Default-constructs the empty music track.
Definition: ge211_audio.h:48
Sound_effect()
Default-constructs the empty sound effect track.
Definition: ge211_audio.h:88
void resume()
Unpauses the effect.
void pause()
Pauses the effect.
void attach_music(Music_track)
Attaches the given music track to this mixer.