SpaceCadetPinball/SpaceCadetPinball/Sound.cpp

155 lines
3.9 KiB
C++

#include "pch.h"
#include "options.h"
#include "Sound.h"
#include "maths.h"
int Sound::num_channels;
bool Sound::enabled_flag = false;
std::vector<ChannelInfo> 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<int>(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<Sint16>(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<Uint8>(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);
}