From 7c2a8b8285278b8a3077b311d82f05ea0463a96e Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Sat, 1 Aug 2015 15:00:07 +0200 Subject: [PATCH] some experiments with sound --- Makefile | 2 +- assets | 2 +- building | 4 +- running | 7 ++ src/app/Application.hpp | 3 + src/app/Assets.hpp | 3 + src/app/Runtime.cpp | 2 + src/app/app.cpp | 24 ++++++- src/app/init.cpp | 34 +++++++++ src/app/init.hpp | 23 ++++++ src/audio/ALError.hpp | 22 ++++++ src/audio/Audio.hpp | 43 +++++++++++ src/audio/Sound.hpp | 32 +++++++++ src/audio/audio.cpp | 154 ++++++++++++++++++++++++++++++++++++++++ src/ui/Interface.hpp | 12 +++- src/ui/ui.cpp | 45 +++++++++++- src/world/Chunk.hpp | 4 ++ 17 files changed, 408 insertions(+), 8 deletions(-) create mode 100644 src/audio/ALError.hpp create mode 100644 src/audio/Audio.hpp create mode 100644 src/audio/Sound.hpp create mode 100644 src/audio/audio.cpp diff --git a/Makefile b/Makefile index 2a355cd..d2e76ca 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ CXX = g++ --std=c++11 LDXX = g++ -LIBS = sdl2 SDL2_image SDL2_ttf glew +LIBS = sdl2 SDL2_image SDL2_ttf glew openal freealut PKGFLAGS := $(shell pkg-config --cflags $(LIBS)) PKGLIBS := $(shell pkg-config --libs $(LIBS)) diff --git a/assets b/assets index 7ac546c..96db33a 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 7ac546c3310a3f147a889077dffa6919fad94be7 +Subproject commit 96db33a3047bf3f20f5b6d4464cf4a4ee238146d diff --git a/building b/building index 3ba7846..98e21ec 100644 --- a/building +++ b/building @@ -1,11 +1,11 @@ Dependencies ============ - GLEW, GLM, SDL2, SDL2_image, SDL2_ttf + GLEW, GLM, SDL2, SDL2_image, SDL2_ttf, OpenAL, freealut CppUnit for tests -archlinux: pacman -S glew glm sdl2 sdl2_image sdl2_ttf cppunit +archlinux: pacman -S glew glm sdl2 sdl2_image sdl2_ttf openal freealut cppunit manual: CppUnit http://sourceforge.net/projects/cppunit/ diff --git a/running b/running index efc1062..1684765 100644 --- a/running +++ b/running @@ -37,6 +37,11 @@ Interface --no-hud disable HUD drawing (includes the selected block outline) +--no-audio + disable audio + the audio device and sounds will still be allocated + it just stops the interface from queueing buffers + World ----- @@ -64,4 +69,6 @@ level there. C dumps info about the chunk of the pointed at block. Press N to toggle player/world collision. +F1 toggles UI rendering. F3 toggles a display telling how long on average it takes to compute a frame. +F4 toggles audio. diff --git a/src/app/Application.hpp b/src/app/Application.hpp index a8e0f2d..636b82e 100644 --- a/src/app/Application.hpp +++ b/src/app/Application.hpp @@ -5,6 +5,7 @@ #include "FrameCounter.hpp" #include "init.hpp" #include "RandomWalk.hpp" +#include "../audio/Audio.hpp" #include "../graphics/Viewport.hpp" #include "../ui/Interface.hpp" #include "../world/World.hpp" @@ -27,6 +28,7 @@ public: }; explicit Application(const Config &); + ~Application(); Application(const Application &) = delete; Application &operator =(const Application &) = delete; @@ -57,6 +59,7 @@ private: Init init; Viewport viewport; Assets assets; + Audio audio; FrameCounter counter; World world; diff --git a/src/app/Assets.hpp b/src/app/Assets.hpp index d9ab3d4..4953ddb 100644 --- a/src/app/Assets.hpp +++ b/src/app/Assets.hpp @@ -7,6 +7,7 @@ namespace blank { class Font; +class Sound; class Assets { @@ -14,9 +15,11 @@ public: explicit Assets(const std::string &base); Font LoadFont(const std::string &name, int size) const; + Sound LoadSound(const std::string &name) const; private: std::string fonts; + std::string sounds; }; diff --git a/src/app/Runtime.cpp b/src/app/Runtime.cpp index 8ddab26..1dcf722 100644 --- a/src/app/Runtime.cpp +++ b/src/app/Runtime.cpp @@ -49,6 +49,8 @@ void Runtime::ReadArgs(int argc, const char *const *argv) { config.interface.mouse_disabled = true; } else if (strcmp(arg + 2, "no-hud") == 0) { config.interface.visual_disabled = true; + } else if (strcmp(arg + 2, "no-audio") == 0) { + config.interface.audio_disabled = true; } else { cerr << "unknown option " << arg << endl; error = true; diff --git a/src/app/app.cpp b/src/app/app.cpp index d13dfc6..59070a5 100644 --- a/src/app/app.cpp +++ b/src/app/app.cpp @@ -2,6 +2,7 @@ #include "Assets.hpp" #include "FrameCounter.hpp" +#include "../audio/Sound.hpp" #include "../graphics/Font.hpp" #include "../world/BlockType.hpp" #include "../world/Entity.hpp" @@ -30,14 +31,19 @@ Application::Application(const Config &config) : init(config.doublebuf, config.multisampling) , viewport() , assets(get_asset_path()) +, audio() , counter() , world(config.world) -, interface(config.interface, assets, counter, world) +, interface(config.interface, assets, audio, counter, world) , test_controller(MakeTestEntity(world)) , running(false) { viewport.VSync(config.vsync); } +Application::~Application() { + audio.StopAll(); +} + Entity &Application::MakeTestEntity(World &world) { Entity &e = world.AddEntity(); e.Name("test"); @@ -156,6 +162,14 @@ void Application::Update(int dt) { interface.Update(dt); test_controller.Update(dt); world.Update(dt); + + glm::mat4 trans = world.Player().Transform(Chunk::Pos(0, 0, 0)); + glm::vec3 dir(trans * glm::vec4(0.0f, 0.0f, -1.0f, 0.0f)); + glm::vec3 up(trans * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f)); + audio.Position(world.Player().Position()); + audio.Velocity(world.Player().Velocity()); + audio.Orientation(dir, up); + counter.ExitUpdate(); } @@ -174,7 +188,8 @@ void Application::Render() { Assets::Assets(const string &base) -: fonts(base + "fonts/") { +: fonts(base + "fonts/") +, sounds(base + "sounds/") { } @@ -183,6 +198,11 @@ Font Assets::LoadFont(const string &name, int size) const { return Font(full.c_str(), size); } +Sound Assets::LoadSound(const string &name) const { + string full = sounds + name + ".wav"; + return Sound(full.c_str()); +} + void FrameCounter::EnterFrame() noexcept { last_enter = SDL_GetTicks(); diff --git a/src/app/init.cpp b/src/app/init.cpp index 1cc0c24..18816a4 100644 --- a/src/app/init.cpp +++ b/src/app/init.cpp @@ -1,6 +1,7 @@ #include "init.hpp" #include +#include #include #include #include @@ -19,10 +20,30 @@ std::string sdl_error_append(std::string msg) { return msg; } +std::string alut_error_append(ALenum num, std::string msg) { + const char *error = alutGetErrorString(num); + if (*error != '\0') { + msg += ": "; + msg += error; + } + return msg; +} + } namespace blank { +AlutError::AlutError(ALenum num) +: std::runtime_error(alutGetErrorString(num)) { + +} + +AlutError::AlutError(ALenum num, const std::string &msg) +: std::runtime_error(alut_error_append(num, msg)) { + +} + + SDLError::SDLError() : std::runtime_error(SDL_GetError()) { @@ -67,6 +88,19 @@ InitTTF::~InitTTF() { } +InitAL::InitAL() { + if (!alutInit(nullptr, nullptr)) { + throw AlutError(alutGetError(), "alutInit"); + } +} + +InitAL::~InitAL() { + if (!alutExit()) { + throw AlutError(alutGetError(), "alutExit"); + } +} + + InitGL::InitGL(bool double_buffer, int sample_size) { if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3) != 0) { throw SDLError("SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3)"); diff --git a/src/app/init.hpp b/src/app/init.hpp index 122210d..13bffa1 100644 --- a/src/app/init.hpp +++ b/src/app/init.hpp @@ -1,6 +1,7 @@ #ifndef BLANK_APP_INIT_HPP_ #define BLANK_APP_INIT_HPP_ +#include #include #include #include @@ -8,6 +9,15 @@ namespace blank { +class AlutError +: public std::runtime_error { + +public: + explicit AlutError(ALenum); + AlutError(ALenum, const std::string &); + +}; + class SDLError : public std::runtime_error { @@ -54,6 +64,18 @@ public: }; +class InitAL { + +public: + InitAL(); + ~InitAL(); + + InitAL(const InitAL &) = delete; + InitAL &operator =(const InitAL &) = delete; + +}; + + class InitGL { public: @@ -123,6 +145,7 @@ struct Init { InitSDL init_sdl; InitIMG init_img; InitTTF init_ttf; + InitAL init_al; InitGL init_gl; Window window; GLContext ctx; diff --git a/src/audio/ALError.hpp b/src/audio/ALError.hpp new file mode 100644 index 0000000..973bcef --- /dev/null +++ b/src/audio/ALError.hpp @@ -0,0 +1,22 @@ +#ifndef BLANK_AUDIO_ALERROR_HPP_ +#define BLANK_AUDIO_ALERROR_HPP_ + +#include +#include +#include + + +namespace blank { + +class ALError +: public std::runtime_error { + +public: + explicit ALError(ALenum); + ALError(ALenum, const std::string &); + +}; + +} + +#endif diff --git a/src/audio/Audio.hpp b/src/audio/Audio.hpp new file mode 100644 index 0000000..fe5364d --- /dev/null +++ b/src/audio/Audio.hpp @@ -0,0 +1,43 @@ +#ifndef BLANK_AUDIO_AUDIO_HPP_ +#define BLANK_AUDIO_AUDIO_HPP_ + +#include +#include + + +namespace blank { + +class Sound; + +class Audio { + +public: + Audio(); + ~Audio(); + + Audio(const Audio &) = delete; + Audio &operator =(const Audio &) = delete; + +public: + void Position(const glm::vec3 &) noexcept; + void Velocity(const glm::vec3 &) noexcept; + void Orientation(const glm::vec3 &dir, const glm::vec3 &up) noexcept; + + void Play( + const Sound &, + const glm::vec3 &pos = glm::vec3(0.0f), + const glm::vec3 &vel = glm::vec3(0.0f), + const glm::vec3 &dir = glm::vec3(0.0f) + ) noexcept; + + void StopAll() noexcept; + +private: + static constexpr std::size_t NUM_SRC = 1; + ALuint source[NUM_SRC]; + +}; + +} + +#endif diff --git a/src/audio/Sound.hpp b/src/audio/Sound.hpp new file mode 100644 index 0000000..abbb27b --- /dev/null +++ b/src/audio/Sound.hpp @@ -0,0 +1,32 @@ +#ifndef BLANK_AUDIO_SOUND_HPP_ +#define BLANK_AUDIO_SOUND_HPP_ + +#include + + +namespace blank { + +class Sound { + +public: + Sound(); + explicit Sound(const char *); + ~Sound(); + + Sound(Sound &&); + Sound &operator =(Sound &&); + + Sound(const Sound &) = delete; + Sound &operator =(const Sound &) = delete; + +public: + void Bind(ALuint src) const; + +private: + ALuint handle; + +}; + +} + +#endif diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp new file mode 100644 index 0000000..eed5dac --- /dev/null +++ b/src/audio/audio.cpp @@ -0,0 +1,154 @@ +#include "ALError.hpp" +#include "Audio.hpp" +#include "Sound.hpp" + +#include +#include +#include +#include +#include + + +namespace { + +const char *al_error_string(ALenum num) { + switch (num) { + case AL_NO_ERROR: + return "no error"; + case AL_INVALID_NAME: + return "invalid name"; + case AL_INVALID_ENUM: + return "invalid enum"; + case AL_INVALID_VALUE: + return "invalid value"; + case AL_INVALID_OPERATION: + return "invalid operation"; + case AL_OUT_OF_MEMORY: + return "out of memory"; + } + return "unknown AL error"; +} + +std::string al_error_append(ALenum num, std::string msg) { + return msg + ": " + al_error_string(num); +} + +} + +namespace blank { + +ALError::ALError(ALenum num) +: std::runtime_error(al_error_string(num)) { + +} + +ALError::ALError(ALenum num, const std::string &msg) +: std::runtime_error(al_error_append(num, msg)) { + +} + + +Audio::Audio() { + alGenSources(NUM_SRC, source); + ALenum err = alGetError(); + if (err != AL_NO_ERROR) { + throw ALError(err, "alGenSources"); + } + for (std::size_t i = 0; i < NUM_SRC; ++i) { + alSourcef(source[i], AL_REFERENCE_DISTANCE, 2.0f); + alSourcef(source[i], AL_ROLLOFF_FACTOR, 1.0f); + } +} + +Audio::~Audio() { + alDeleteSources(NUM_SRC, source); + ALenum err = alGetError(); + if (err != AL_NO_ERROR) { + std::cerr << "warning: alDeleteSources failed with " << al_error_string(err) << std::endl; + //throw ALError(err, "alDeleteSources"); + } +} + +void Audio::Position(const glm::vec3 &pos) noexcept { + alListenerfv(AL_POSITION, glm::value_ptr(pos)); + //std::cout << "listener at " << pos << std::endl; +} + +void Audio::Velocity(const glm::vec3 &vel) noexcept { + alListenerfv(AL_VELOCITY, glm::value_ptr(vel)); +} + +void Audio::Orientation(const glm::vec3 &dir, const glm::vec3 &up) noexcept { + ALfloat orient[6] = { + dir.x, dir.y, dir.z, + up.x, up.y, up.z, + }; + alListenerfv(AL_ORIENTATION, orient); +} + +void Audio::Play( + const Sound &sound, + const glm::vec3 &pos, + const glm::vec3 &vel, + const glm::vec3 &dir +) noexcept { + // TODO: find next free source + ALuint src = source[0]; + + sound.Bind(src); + alSourcefv(src, AL_POSITION, glm::value_ptr(pos)); + alSourcefv(src, AL_VELOCITY, glm::value_ptr(vel)); + alSourcefv(src, AL_DIRECTION, glm::value_ptr(dir)); + alSourcePlay(src); +} + +void Audio::StopAll() noexcept { + alSourceStopv(NUM_SRC, source); + for (std::size_t i = 0; i < NUM_SRC; ++i) { + alSourcei(source[i], AL_BUFFER, AL_NONE); + } +} + + +Sound::Sound() +: handle(AL_NONE) { + alGenBuffers(1, &handle); + ALenum err = alGetError(); + if (err != AL_NO_ERROR) { + throw ALError(err, "alGenBuffers"); + } +} + +Sound::Sound(const char *file) +: handle(alutCreateBufferFromFile(file)) { + if (handle == AL_NONE) { + throw ALError(alGetError(), "alutCreateBufferFromFile"); + } +} + +Sound::~Sound() { + if (handle != AL_NONE) { + alDeleteBuffers(1, &handle); + ALenum err = alGetError(); + if (err != AL_NO_ERROR) { + std::cerr << "warning: alDeleteBuffers failed with " << al_error_string(err) << std::endl; + //throw ALError(err, "alDeleteBuffers"); + } + } +} + +Sound::Sound(Sound &&other) +: handle(other.handle) { + other.handle = AL_NONE; +} + +Sound &Sound::operator =(Sound &&other) { + std::swap(handle, other.handle); + return *this; +} + +void Sound::Bind(ALuint src) const { + alSourcei(src, AL_BUFFER, handle); +} + +} diff --git a/src/ui/Interface.hpp b/src/ui/Interface.hpp index 00b76e6..eaed0fc 100644 --- a/src/ui/Interface.hpp +++ b/src/ui/Interface.hpp @@ -4,6 +4,7 @@ #include "HUD.hpp" #include "../app/FPSController.hpp" #include "../app/IntervalTimer.hpp" +#include "../audio/Sound.hpp" #include "../graphics/FixedText.hpp" #include "../graphics/Font.hpp" #include "../graphics/MessageBox.hpp" @@ -18,6 +19,7 @@ namespace blank { class Assets; +class Audio; class Chunk; class FrameCounter; class Viewport; @@ -33,10 +35,11 @@ public: bool keyboard_disabled = false; bool mouse_disabled = false; + bool audio_disabled = false; bool visual_disabled = false; }; - Interface(const Config &, const Assets &, const FrameCounter &, World &); + Interface(const Config &, const Assets &, Audio &, const FrameCounter &, World &); void HandlePress(const SDL_KeyboardEvent &); void HandleRelease(const SDL_KeyboardEvent &); @@ -63,6 +66,9 @@ public: void SelectNext(); void SelectPrevious(); + void ToggleAudio(); + void ToggleVisual(); + void ToggleCounter(); void UpdateCounter(); @@ -79,6 +85,7 @@ private: void CheckAim(); private: + Audio &audio; const FrameCounter &counter; World &world; FPSController ctrl; @@ -105,6 +112,9 @@ private: Block remove; Block selection; + Sound place_sound; + Sound remove_sound; + glm::tvec3 fwd, rev; }; diff --git a/src/ui/ui.cpp b/src/ui/ui.cpp index 43a5f5d..8a17be2 100644 --- a/src/ui/ui.cpp +++ b/src/ui/ui.cpp @@ -4,6 +4,7 @@ #include "../app/Assets.hpp" #include "../app/FrameCounter.hpp" #include "../app/init.hpp" +#include "../audio/Audio.hpp" #include "../graphics/Font.hpp" #include "../graphics/Viewport.hpp" #include "../model/shapes.hpp" @@ -91,9 +92,11 @@ void HUD::Render(Viewport &viewport) noexcept { Interface::Interface( const Config &config, const Assets &assets, + Audio &audio, const FrameCounter &counter, World &world) -: counter(counter) +: audio(audio) +, counter(counter) , world(world) , ctrl(world.Player()) , font(assets.LoadFont("DejaVuSans", 16)) @@ -112,6 +115,8 @@ Interface::Interface( , remove_timer(256) , remove(0) , selection(1) +, place_sound(assets.LoadSound("thump")) +, remove_sound(assets.LoadSound("plop")) , fwd(0) , rev(0) { counter_text.Hide(); @@ -172,9 +177,15 @@ void Interface::HandlePress(const SDL_KeyboardEvent &event) { PrintSelectionInfo(); break; + case SDLK_F1: + ToggleVisual(); + break; case SDLK_F3: ToggleCounter(); break; + case SDLK_F4: + ToggleAudio(); + break; } } @@ -308,6 +319,24 @@ void Interface::Print(const Block &block) { PostMessage(s.str()); } +void Interface::ToggleAudio() { + config.audio_disabled = !config.audio_disabled; + if (config.audio_disabled) { + PostMessage("audio off"); + } else { + PostMessage("audio on"); + } +} + +void Interface::ToggleVisual() { + config.visual_disabled = !config.visual_disabled; + if (config.visual_disabled) { + PostMessage("visual off"); + } else { + PostMessage("visual on"); + } +} + void Interface::ToggleCounter() { counter_text.Toggle(); if (counter_text.Visible()) { @@ -371,12 +400,26 @@ void Interface::PlaceBlock() { } mod_chunk->SetBlock(next_pos, selection); mod_chunk->Invalidate(); + + if (config.audio_disabled) return; + const Entity &player = ctrl.Controlled(); + audio.Play( + place_sound, + mod_chunk->ToSceneCoords(player.ChunkCoords(), next_pos) + ); } void Interface::RemoveBlock() noexcept { if (!aim_chunk) return; aim_chunk->SetBlock(aim_block, remove); aim_chunk->Invalidate(); + + if (config.audio_disabled) return; + const Entity &player = ctrl.Controlled(); + audio.Play( + remove_sound, + aim_chunk->ToSceneCoords(player.ChunkCoords(), Chunk::ToCoords(aim_block)) + ); } diff --git a/src/world/Chunk.hpp b/src/world/Chunk.hpp index a75d8e2..535f7d7 100644 --- a/src/world/Chunk.hpp +++ b/src/world/Chunk.hpp @@ -73,6 +73,10 @@ public: } glm::mat4 ToTransform(const Pos &pos, int idx) const noexcept; + Block::Pos ToSceneCoords(const Pos &base, const Block::Pos &pos) const noexcept { + return Block::Pos((position - base) * Extent()) + pos; + } + static bool IsBorder(const Pos &pos) noexcept { return pos.x == 0 || -- 2.39.2