Symphony Of Empires
audio.cpp
Go to the documentation of this file.
1 // Eng3D - General purpouse game engine
2 // Copyright (C) 2021, Eng3D contributors
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program. If not, see <https://www.gnu.org/licenses/>.
16 //
17 // ----------------------------------------------------------------------------
18 // Name:
19 // sound.cpp
20 //
21 // Abstract:
22 // Does some important stuff.
23 // ----------------------------------------------------------------------------
24 
25 #include <cstring>
26 #include <algorithm>
27 
28 #include <SDL.h>
29 #include <SDL_audio.h>
30 
31 #include "eng3d/audio.hpp"
32 #include "eng3d/utils.hpp"
33 #include "eng3d/log.hpp"
34 #include "eng3d/state.hpp"
35 extern "C" {
36 #include "stb_vorbis.c"
37 }
38 
39 //
40 // Audio
41 //
42 Eng3D::Audio::Audio(const std::string& path) {
43  Eng3D::Log::debug("audio", Eng3D::translate_format("Decoding audio %s", path.c_str()));
44  int err = 0;
45  this->stream = static_cast<void *>(stb_vorbis_open_filename(path.c_str(), &err, nullptr));
46  if(this->stream == nullptr)
47  CXX_THROW(Eng3D::AudioException, path, translate("Error opening audio"));
48 }
49 
51  stb_vorbis_close(static_cast<stb_vorbis*>(this->stream));
52 }
53 
54 //
55 // Audio manager
56 //
58  : s{ _s }
59 {
60  // Initialize sound subsystem (at 11,050 hz)
61  SDL_AudioSpec fmt;
62  fmt.freq = 44100;
63  fmt.format = AUDIO_S16;
64  fmt.channels = 2;
65  fmt.samples = 16;
66  fmt.callback = &Eng3D::AudioManager::mixaudio;
67  fmt.userdata = this;
68  SDL_AudioSpec new_fmt{};
69  this->audio_dev_id = SDL_OpenAudioDevice(NULL, 0, &fmt, &new_fmt, 0);
70  if(this->audio_dev_id < 0)
71  CXX_THROW(std::runtime_error, Eng3D::translate_format("Unable to open audio: %s", SDL_GetError()));
72  SDL_PauseAudioDevice(this->audio_dev_id, 0);
73 }
74 
76  SDL_PauseAudioDevice(this->audio_dev_id, 1);
77  SDL_CloseAudioDevice(this->audio_dev_id);
78  SDL_QuitSubSystem(SDL_INIT_AUDIO);
79 }
80 
81 void Eng3D::AudioManager::mixaudio(void* userdata, uint8_t* stream, int len) {
82  auto& audio_man = *(static_cast<Eng3D::AudioManager*>(userdata));
83  SDL_memset(stream, 0, len);
84 
85  auto audiobuf = std::unique_ptr<uint8_t[]>(new uint8_t[len]);
86 
87  const std::scoped_lock lock(audio_man.sound_lock);
88  if(!audio_man.sound_queue.empty()) {
89  auto& audio = **audio_man.sound_queue.begin();
90  auto* audio_stream = static_cast<stb_vorbis*>(audio.stream);
91  stb_vorbis_info info = stb_vorbis_get_info(audio_stream);
92  if(stb_vorbis_get_samples_short_interleaved(audio_stream, info.channels, (short*)audiobuf.get(), len / sizeof(short)) == 0) {
93  // Take off queue
94  stb_vorbis_seek_start(audio_stream); // Rewind
95  audio_man.sound_queue.erase(audio_man.sound_queue.begin());
96  }
97  for(size_t i = 0; i < len / sizeof(int16_t); i++) {
98  auto sample = static_cast<float>(((int16_t *)audiobuf.get())[i]);
99  ((int16_t *)stream)[i] += (int16_t)(sample * audio_man.sound_volume);
100  }
101  }
102 
103  if(!audio_man.music_queue.empty()) {
104  auto& audio = **audio_man.music_queue.begin();
105  auto* audio_stream = static_cast<stb_vorbis*>(audio.stream);
106  stb_vorbis_info info = stb_vorbis_get_info(audio_stream);
107  if(stb_vorbis_get_samples_short_interleaved(audio_stream, info.channels, (short*)audiobuf.get(), len / sizeof(short)) == 0) {
108  // Take off queue
109  stb_vorbis_seek_start(audio_stream); // Rewind
110  audio_man.music_queue.erase(audio_man.music_queue.begin());
111  }
112  for(size_t i = 0; i < len / sizeof(int16_t); i++) {
113  auto sample = static_cast<float>(((int16_t *)audiobuf.get())[i]);
114  ((int16_t *)stream)[i] += (int16_t)(sample * audio_man.music_volume);
115  }
116  }
117 }
118 
119 const std::shared_ptr<Eng3D::Audio> Eng3D::AudioManager::load(const std::string& path) {
120  // Find Sound when wanting to be loaded
121  auto it = sounds.find(path);
122  if(it != sounds.cend()) return (*it).second;
123  // Otherwise Sound is not in our control, so we create a new one
124  sounds[path] = std::make_shared<Eng3D::Audio>(path);
125  Eng3D::Log::debug("audio", Eng3D::translate_format("Loaded and cached sound %s", path.c_str()));
126  return sounds[path];
127 }
const std::shared_ptr< Audio > load(const std::string &path)
Definition: audio.cpp:119
std::string translate(const std::string_view str)
Definition: string.cpp:76
void debug(const std::string_view category, const std::string_view msg)
Definition: log.cpp:58
std::string translate_format(const std::string_view format, Args &&... args)
String formatter, with translation.
Definition: string.hpp:128
stb_vorbis_info stb_vorbis_get_info(stb_vorbis *f)
Definition: stb_vorbis.c:4305
int stb_vorbis_seek_start(stb_vorbis *f)
Definition: stb_vorbis.c:4936
void stb_vorbis_close(stb_vorbis *f)
Definition: stb_vorbis.c:4271
int stb_vorbis_get_samples_short_interleaved(stb_vorbis *f, int channels, short *buffer, int num_shorts)
Definition: stb_vorbis.c:5309
stb_vorbis * stb_vorbis_open_filename(const char *filename, int *error, const stb_vorbis_alloc *alloc_buffer)
Definition: stb_vorbis.c:5083
void * stream
Definition: audio.hpp:55
Audio()=default
#define CXX_THROW(class,...)
Definition: utils.hpp:98