137 lines
3.6 KiB
C++
137 lines
3.6 KiB
C++
#include "AudioManager.h"
|
|||
|
|
#include <cmath>
|
||
|
|
|
||
|
|
namespace NGame {
|
||
|
|
|
||
|
|
TAudioManager::TAudioManager(TFileManager& fileManager)
|
||
|
|
: FileManager_(fileManager) {
|
||
|
|
|
||
|
|
SDL_AudioSpec desired;
|
||
|
|
desired.freq = 44100;
|
||
|
|
desired.format = AUDIO_F32;
|
||
|
|
desired.channels = 2;
|
||
|
|
desired.samples = 4096;
|
||
|
|
desired.callback = NULL;
|
||
|
|
|
||
|
|
Device_ = SDL_OpenAudioDevice(NULL, 0, &desired, NULL, 0);
|
||
|
|
if (!Device_) {
|
||
|
|
throw std::runtime_error("no audio device");
|
||
|
|
}
|
||
|
|
|
||
|
|
Stream_ = SDL_NewAudioStream(AUDIO_U8, 1, 22050, AUDIO_F32, 2, 44100);
|
||
|
|
if (!Stream_) {
|
||
|
|
throw std::runtime_error("can't create audio stream");
|
||
|
|
}
|
||
|
|
|
||
|
|
for (std::size_t i = 0; i < 16; ++i) {
|
||
|
|
Channels_[i].InUse = false;
|
||
|
|
}
|
||
|
|
SDL_PauseAudioDevice(Device_, SDL_FALSE);
|
||
|
|
|
||
|
|
LastTick_ = SDL_GetTicks();
|
||
|
|
}
|
||
|
|
|
||
|
|
void TAudioManager::Run() {
|
||
|
|
std::uint32_t currentTick = SDL_GetTicks();
|
||
|
|
|
||
|
|
Paint(Stream_, currentTick - LastTick_);
|
||
|
|
if (SDL_GetQueuedAudioSize(Device_) < 8192) {
|
||
|
|
const int bytesRemaining = SDL_AudioStreamAvailable(Stream_);
|
||
|
|
if (bytesRemaining > 0) {
|
||
|
|
const int samplesRemaining = SDL_min(bytesRemaining, 32 * 1024);
|
||
|
|
static Uint8 convertedBuffer[32 * 1024];
|
||
|
|
auto bytesRead = SDL_AudioStreamGet(Stream_, convertedBuffer, samplesRemaining);
|
||
|
|
SDL_QueueAudio(Device_, convertedBuffer, bytesRead);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
LastTick_ = currentTick;
|
||
|
|
}
|
||
|
|
|
||
|
|
void TAudioManager::Play(const std::string& path) {
|
||
|
|
TSound* sound = Load(path);
|
||
|
|
|
||
|
|
for (std::size_t i = 0; i < 16; i++) {
|
||
|
|
if (Channels_[i].InUse) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
Channels_[i].InUse = true;
|
||
|
|
Channels_[i].Position = 0;
|
||
|
|
Channels_[i].Sound = sound;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void TAudioManager::Paint(SDL_AudioStream* stream, std::uint32_t ticks) {
|
||
|
|
std::uint8_t temporary[512];
|
||
|
|
std::size_t samples = std::min((ticks * 22050 + 999) / 1000, 512u);
|
||
|
|
|
||
|
|
// Mix
|
||
|
|
for (size_t i = 0; i < samples; ++i) {
|
||
|
|
int16_t accumulator = 0;
|
||
|
|
|
||
|
|
for (size_t j = 0; j < 16; j++) {
|
||
|
|
auto& channel = Channels_[j];
|
||
|
|
if (!channel.InUse) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
accumulator += channel.Sound->Data[channel.Position];
|
||
|
|
accumulator -= 128;
|
||
|
|
channel.Position++;
|
||
|
|
|
||
|
|
if (channel.Position > channel.Sound->Size) {
|
||
|
|
channel.InUse = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
accumulator += 128;
|
||
|
|
if (accumulator < 0) {
|
||
|
|
accumulator = 0;
|
||
|
|
}
|
||
|
|
if (accumulator > 255) {
|
||
|
|
accumulator = 255;
|
||
|
|
}
|
||
|
|
|
||
|
|
temporary[i] = accumulator;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Output
|
||
|
|
if (samples > 0) {
|
||
|
|
SDL_AudioStreamPut(stream, temporary, samples * sizeof (std::uint8_t));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
TAudioManager::TSound* TAudioManager::Load(const std::string& path) {
|
||
|
|
auto it = Sounds_.find(path);
|
||
|
|
if (it != Sounds_.end()) {
|
||
|
|
return it->second;
|
||
|
|
}
|
||
|
|
|
||
|
|
TSound* newSound = new TSound;
|
||
|
|
auto data = FileManager_.Get(path);
|
||
|
|
SDL_RWops* rw = SDL_RWFromConstMem(data.data(), data.size());
|
||
|
|
if (!SDL_LoadWAV_RW(rw, 1, &newSound->Spec, &newSound->Data, &newSound->Size)) {
|
||
|
|
throw std::runtime_error("can't read WAV file " + path);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (newSound->Spec.channels != 1) {
|
||
|
|
throw std::runtime_error("not mono WAV file " + path);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (newSound->Spec.format != AUDIO_U8) {
|
||
|
|
throw std::runtime_error("not 8bit pcm WAV file " + path);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (newSound->Spec.freq != 22050) {
|
||
|
|
throw std::runtime_error("not 22050hz WAV file " + path);
|
||
|
|
}
|
||
|
|
|
||
|
|
Sounds_[path] = newSound;
|
||
|
|
return newSound;
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace NGame
|