#include "pch.h" #include "options.h" #include "Sound.h" #include "maths.h" int Sound::num_channels; bool Sound::enabled_flag = false; std::vector Sound::Channels{}; int Sound::Volume = MIX_MAX_VOLUME; bool Sound::MixOpen = false; void Sound::Init(bool mixOpen, int channels, bool enableFlag, int volume) { MixOpen = mixOpen; Volume = volume; SetChannels(channels); Enable(enableFlag); } void Sound::Enable(bool enableFlag) { enabled_flag = enableFlag; if (MixOpen && !enableFlag) Mix_HaltChannel(-1); } void Sound::Activate() { if (MixOpen) Mix_Resume(-1); } void Sound::Deactivate() { if (MixOpen) Mix_Pause(-1); } void Sound::Close() { Enable(false); Channels.clear(); } void Sound::PlaySound(Mix_Chunk* wavePtr, int time, TPinballComponent* soundSource, const char* info) { if (MixOpen && wavePtr && enabled_flag) { if (Mix_Playing(-1) == num_channels) { auto cmp = [](const ChannelInfo& a, const ChannelInfo& b) { return a.TimeStamp < b.TimeStamp; }; auto min = std::min_element(Channels.begin(), Channels.end(), cmp); auto oldestChannel = static_cast(std::distance(Channels.begin(), min)); Mix_HaltChannel(oldestChannel); } auto channel = Mix_PlayChannel(-1, wavePtr, 0); if (channel != -1) { Channels[channel].TimeStamp = time; if (options::Options.SoundStereo) { // Positional audio uses collision grid 2D coordinates normalized to [0, 1] // Point (0, 0) is bottom left table corner; point (1, 1) is top right table corner. // Z is defined as: 0 at table level, positive axis goes up from table surface. // Get the source sound position. // Sound without position are assumed to be at the center top of the table. vector3 soundPos{}; if (soundSource) { auto soundPos2D = soundSource->get_coordinates(); soundPos = {soundPos2D.X, soundPos2D.Y, 0.0f}; } else { soundPos = {0.5f, 1.0f, 0.0f}; } Channels[channel].Position = soundPos; // Listener is positioned at the bottom center of the table, // at 0.5 height, so roughly a table half - length. vector3 playerPos = {0.5f, 0.0f, 0.5f}; auto soundDir = maths::vector_sub(soundPos, playerPos); // Find sound angle from positive Y axis in clockwise direction with atan2 // Remap atan2 output from (-Pi, Pi] to [0, 2 * Pi) auto angle = fmodf(atan2(soundDir.X, soundDir.Y) + Pi * 2, Pi * 2); auto angleDeg = angle * 180.0f / Pi; auto angleSdl = static_cast(angleDeg); // Distance from listener to the sound position is roughly in the [0, ~1.22] range. // Remap to [0, 122] by multiplying by 100 and cast to an integer. auto distance = static_cast(100.0f * maths::magnitude(soundDir)); // Mix_SetPosition expects an angle in (Sint16)degrees, where // angle 0 is due north, and rotates clockwise as the value increases. // Mix_SetPosition expects a (Uint8)distance from 0 (near) to 255 (far). Mix_SetPosition(channel, angleSdl, distance); // Output position of each sound emitted so we can verify // the sanity of the implementation. /*printf("X: %3.3f Y: %3.3f Angle: %3.3f Distance: %3d, Object: %s\n", soundPos.X, soundPos.Y, angleDeg, distance, info );*/ } } } } Mix_Chunk* Sound::LoadWaveFile(const std::string& lpName) { if (!MixOpen) return nullptr; auto wavFile = fopenu(lpName.c_str(), "r"); if (!wavFile) return nullptr; fclose(wavFile); return Mix_LoadWAV(lpName.c_str()); } void Sound::FreeSound(Mix_Chunk* wave) { if (MixOpen && wave) Mix_FreeChunk(wave); } void Sound::SetChannels(int channels) { if (channels <= 0) channels = 8; num_channels = channels; Channels.resize(num_channels); if (MixOpen) Mix_AllocateChannels(num_channels); SetVolume(Volume); } void Sound::SetVolume(int volume) { Volume = volume; if (MixOpen) Mix_Volume(-1, volume); }