Skip to content

Commit

Permalink
Added try_* operations to audio APIs.
Browse files Browse the repository at this point in the history
  • Loading branch information
tov committed Mar 7, 2020
1 parent 16f9ee1 commit 46e721e
Show file tree
Hide file tree
Showing 9 changed files with 222 additions and 63 deletions.
125 changes: 101 additions & 24 deletions include/ge211_audio.hxx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#pragma once

#include "ge211_forward.hxx"
#include "ge211_error.hxx"
#include "ge211_session.hxx"
#include "ge211_time.hxx"
#include "ge211_util.hxx"

Expand All @@ -20,6 +22,75 @@ namespace ge211 {
/// for more.
namespace audio {

/// Abstract base class for classes that load audio data, Music_track and
/// Sound_effect.
class Audio_clip
{
public:
/// Returns true if this audio clip is empty.
bool empty() const { return real_empty(); }

/// Recognizes a non-empty audio clip.
/// Equivalent to `!empty()`.
operator bool() const { return !real_empty(); }

/// Loads audio from a resource file into this audio clip instance.
///
/// Throws exceptions::File_error if the file cannot be opened, and
/// exceptions::Mixer_error if the file format cannot be understood.
void load(const std::string& filename, const Mixer& mixer)
{
if (!real_try_load(filename, mixer))
throw Mixer_error::could_not_load(filename);
}

/// Attempts to load audio from a resource file into this Audio_clip
/// instance. Returns `true` if loading succeeds, or `false` if
/// the file format cannot be understood.
///
/// Throws exceptions::File_error if the file cannot be opened.
bool try_load(const std::string& filename, const Mixer& mixer)
{
return real_try_load(filename, mixer);
}

/// Attempts to load audio from a resource file into this Audio_clip
/// instance, but skips loading and returns `false` if `mixer` is null.
/// Otherwise, returns whether loading succeeds.
///
/// Throws exceptions::File_error if the file cannot be opened.
bool try_load(const std::string& filename, const Mixer* mixer)
{
return mixer && real_try_load(filename, *mixer);
}

/// Unloads any audio data, leaving this Audio_clip empty.
void clear()
{
real_clear();
}

virtual ~Audio_clip() = default;

protected:
Audio_clip()
{
detail::Session::check_session("Audio loading");
}

/// Derived classes must override this to provide an implementation
/// for Audio_clip::try_load(const std::string&, const Mixer&).
virtual bool real_try_load(const std::string& filename, const Mixer&) = 0;

/// Derived classes must override this to provide an implementation
/// for Audio_clip::clear().
virtual void real_clear() = 0;

/// Derived classes must override this to provide an implementation
/// for Audio_clip::empty().
virtual bool real_empty() const = 0;
};

/// A music track, which can be attached to the Mixer and played.
/// A music track may be *empty* or *non-empty*; only non-empty tracks can
/// actually be played.
Expand All @@ -31,13 +102,17 @@ namespace audio {
/// - Mixer::attach_music(Music_track)
///
/// Note also that the mixer can only play one music track at a time.
class Music_track
class Music_track : public Audio_clip
{
public:
/// Loads a new music track from a resource file.
///
/// Supported file formats include WAV, MP3, OGG, FLAC, MID, and ABC.
/// However, pausing and resuming does not work correctly with all audio
/// Supported file formats may include WAV, MP3, OGG, FLAC, MID,
/// and ABC. Which you actually get depends on which options were
/// enabled when your copy of the SDL2_mixer library that %ge211 links
/// against was compiled.
///
/// Pausing and resuming does not work correctly with all audio
/// formats.
///
/// Throws exceptions::File_error if the file cannot be opened, and
Expand All @@ -47,20 +122,15 @@ public:
/// Default-constructs the empty music track.
Music_track() { }

/// Recognizes the empty music track.
bool empty() const;

/// Recognizes a non-empty music track.
/// Equivalent to `!empty()`.
operator bool() const;
protected:
bool real_try_load(const std::string&, const Mixer&) override;
void real_clear() override;
bool real_empty() const override;

private:
// Friends
friend Mixer;

// Private helper.
static std::shared_ptr<Mix_Music> load_(const std::string& filename);

std::shared_ptr<Mix_Music> ptr_;
};

Expand All @@ -73,12 +143,15 @@ private:
/// effect track can be passed to the Mixer member function
/// Mixer::play_effect(Sound_effect, double)
/// to play it.
class Sound_effect
class Sound_effect : public Audio_clip
{
public:
/// Loads a new sound effect track from a resource file.
///
/// Supported file formats include WAV, MP3, OGG, FLAC, MID, and ABC.
/// Supported file formats may include WAV, MP3, OGG, FLAC, MID,
/// and ABC. Which you actually get depends on which options were
/// enabled when your copy of the SDL2_mixer library that %ge211 links
/// against was compiled.
///
/// Throws exceptions::File_error if the file cannot be opened, and
/// exceptions::Mixer_error if the file format cannot be understood.
Expand All @@ -87,20 +160,15 @@ public:
/// Default-constructs the empty sound effect track.
Sound_effect() { }

/// Recognizes the empty sound effect track.
bool empty() const;

/// Recognizes a non-empty sound effect track.
/// Equivalent to `!empty()`.
operator bool() const;
protected:
bool real_try_load(const std::string&, const Mixer&) override;
void real_clear() override;
bool real_empty() const override;

private:
// Friends
friend Mixer;

// Private static factory
static std::shared_ptr<Mix_Chunk> load_(const std::string& filename);

std::shared_ptr<Mix_Chunk> ptr_;
};

Expand Down Expand Up @@ -258,6 +326,14 @@ public:
Sound_effect_handle
play_effect(Sound_effect effect, double volume = 1.0);

/// Attempts to play the given effect track on this mixer, returning an
/// empty Sound_effect_handle if no effect channel is available.
///
/// \preconditions
/// - `!effect.empty()`, undefined behavior if violated.
Sound_effect_handle
try_play_effect(Sound_effect effect, double volume = 1.0);

/// Pauses all currently-playing effects.
void pause_all_effects();

Expand Down Expand Up @@ -299,7 +375,8 @@ private:
void poll_channels_();
friend detail::Engine; // calls poll_channels_().

/// Returns the index of an empty channel, or throws if all are full.
/// Returns the index of an empty channel. Returns -1 if all
/// are full.
int find_empty_channel_() const;

/// Registers an effect with a channel.
Expand Down
2 changes: 1 addition & 1 deletion include/ge211_base.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ private:

mutable Random rng_;
detail::Session session_;
Mixer::Ptr mixer_ = Mixer::open_if_(session_.mix.enabled);
Mixer::Ptr mixer_ = Mixer::open_if_(session_.is_mixer_enabled());
detail::Engine* engine_ = nullptr;

bool quit_ = false;
Expand Down
21 changes: 21 additions & 0 deletions include/ge211_error.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,26 @@ public:
explicit Client_logic_error(const std::string& message);
};

/// An exception thrown when the client attempts to perform an
/// action that requires a GE211 session before GE211 starts.
/// For example, GE211 needs to initialize the font subsystem
/// before fonts can be loaded, so the `Font` constructor throws
/// this exception if it’s called too early.
class Session_needed_error : public Client_logic_error
{
public:
/// The action that the client attempted that couldn't be
/// completed without a GE211 session.
const std::string& attempted_action() const { return action_; }

private:
friend detail::Session;

explicit Session_needed_error(const std::string& action);

std::string action_;
};

/// Indicates that an error was encountered by the game engine or
/// in the client's environment.
/// This could indicate a problem with your video driver,
Expand Down Expand Up @@ -142,6 +162,7 @@ class Mixer_error : public Host_error

/// Thrower
friend Mixer;
friend Audio_clip;
friend Music_track;
friend Sound_effect;
};
Expand Down
4 changes: 3 additions & 1 deletion include/ge211_forward.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class Engine;
class File_resource;
struct Placed_sprite;
class Renderer;
struct Session;
class Session;
class Render_sprite;
class Texture;
class Texture_sprite;
Expand All @@ -41,6 +41,7 @@ namespace audio {

enum class Channel_state;
class Mixer;
class Audio_clip;
class Music_track;
class Sound_effect;
class Sound_effect_handle;
Expand All @@ -58,6 +59,7 @@ namespace exceptions {

class Exception_base;
class Client_logic_error;
class Session_needed_error;
class Environment_error;
class Ge211_logic_error;
class Host_error;
Expand Down
23 changes: 17 additions & 6 deletions include/ge211_session.hxx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

#include <atomic>

namespace ge211 {

namespace detail {
Expand Down Expand Up @@ -46,15 +48,24 @@ struct Text_input_session : PINNED
~Text_input_session();
};

struct Session
class Session
{
public:
Session();
~Session();

bool is_mixer_enabled() const { return mix_.enabled; }

static void check_session(const char*);

private:
Sdl_session sdl_;
Mix_session mix_;
Img_session img_;
Ttf_session ttf_;
Text_input_session text_input_;

Sdl_session sdl;
Mix_session mix;
Img_session img;
Ttf_session ttf;
Text_input_session text_input;
static std::atomic<int> session_count_;
};

} // end namespace detail
Expand Down
Loading

0 comments on commit 46e721e

Please sign in to comment.