#include "AudioManager.h" #include 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