Веб-сайт самохостера Lotigara

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKae <80987908+Novaenia@users.noreply.github.com>2023-07-13 15:01:07 +1000
committerKae <80987908+Novaenia@users.noreply.github.com>2023-07-13 15:01:07 +1000
commitf9e8b5badf76db844629364fe650035daf21b56b (patch)
tree0a1227d09c86a50e268fb68bd16953876cd5213e
parent40223a5090bf8a502094927da39fc96a5cfd5eae (diff)
more work on Voice
-rw-r--r--source/CMakeLists.txt1
-rw-r--r--source/extern/CMakeLists.txt2
-rw-r--r--source/frontend/StarMainMixer.cpp52
-rw-r--r--source/game/StarVoice.cpp102
-rw-r--r--source/game/StarVoice.hpp101
5 files changed, 214 insertions, 44 deletions
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index d3d60a3..4f44bc2 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -437,6 +437,7 @@ SET (STAR_EXT_LIBS ${STAR_EXT_LIBS}
${FREETYPE_LIBRARY}
${PNG_LIBRARY}
${ZLIB_LIBRARY}
+ "extern/opus/opus"
)
IF (STAR_BUILD_GUI)
diff --git a/source/extern/CMakeLists.txt b/source/extern/CMakeLists.txt
index e356c95..5ea7420 100644
--- a/source/extern/CMakeLists.txt
+++ b/source/extern/CMakeLists.txt
@@ -79,4 +79,4 @@ SET (star_extern_SOURCES
)
ADD_LIBRARY (star_extern OBJECT ${star_extern_SOURCES} ${star_extern_HEADERS})
-TARGET_LINK_LIBRARIES(star_extern opus) \ No newline at end of file
+TARGET_LINK_LIBRARIES(star_extern PUBLIC opus) \ No newline at end of file
diff --git a/source/frontend/StarMainMixer.cpp b/source/frontend/StarMainMixer.cpp
index 59b6b52..6d68ea2 100644
--- a/source/frontend/StarMainMixer.cpp
+++ b/source/frontend/StarMainMixer.cpp
@@ -7,6 +7,7 @@
#include "StarAssets.hpp"
#include "StarWorldClient.hpp"
#include "StarWorldPainter.hpp"
+#include "StarVoice.hpp"
namespace Star {
@@ -80,34 +81,39 @@ void MainMixer::update(bool muteSfx, bool muteMusic) {
auto cameraPos = m_worldPainter->camera().centerWorldPosition();
auto worldGeometry = currentWorld->geometry();
- m_mixer->update([&](unsigned channel, Vec2F pos, float rangeMultiplier) {
- Vec2F playerDiff = worldGeometry.diff(pos, playerPos);
- Vec2F cameraDiff = worldGeometry.diff(pos, cameraPos);
- float playerMagSq = playerDiff.magnitudeSquared();
- float cameraMagSq = cameraDiff.magnitudeSquared();
-
- Vec2F diff;
- float diffMagnitude;
- if (playerMagSq < cameraMagSq) {
- diff = playerDiff;
- diffMagnitude = sqrt(playerMagSq);
- }
- else {
- diff = cameraDiff;
- diffMagnitude = sqrt(cameraMagSq);
- }
+ Mixer::PositionalAttenuationFunction attenuationFunction = [&](unsigned channel, Vec2F pos, float rangeMultiplier) {
+ Vec2F playerDiff = worldGeometry.diff(pos, playerPos);
+ Vec2F cameraDiff = worldGeometry.diff(pos, cameraPos);
+ float playerMagSq = playerDiff.magnitudeSquared();
+ float cameraMagSq = cameraDiff.magnitudeSquared();
+
+ Vec2F diff;
+ float diffMagnitude;
+ if (playerMagSq < cameraMagSq) {
+ diff = playerDiff;
+ diffMagnitude = sqrt(playerMagSq);
+ }
+ else {
+ diff = cameraDiff;
+ diffMagnitude = sqrt(cameraMagSq);
+ }
- if (diffMagnitude == 0.0f)
- return 0.0f;
+ if (diffMagnitude == 0.0f)
+ return 0.0f;
- Vec2F diffNorm = diff / diffMagnitude;
+ Vec2F diffNorm = diff / diffMagnitude;
- float stereoIncidence = channel == 0 ? -diffNorm[0] : diffNorm[0];
+ float stereoIncidence = channel == 0 ? -diffNorm[0] : diffNorm[0];
+
+ float maxDistance = baseMaxDistance * rangeMultiplier * lerp((stereoIncidence + 1.0f) / 2.0f, stereoAdjustmentRange[0], stereoAdjustmentRange[1]);
+
+ return pow(clamp(diffMagnitude / maxDistance, 0.0f, 1.0f), 1.0f / attenuationGamma);
+ };
- float maxDistance = baseMaxDistance * rangeMultiplier * lerp((stereoIncidence + 1.0f) / 2.0f, stereoAdjustmentRange[0], stereoAdjustmentRange[1]);
+ if (Voice* voice = Voice::singletonPtr())
+ voice->update(attenuationFunction);
- return pow(clamp(diffMagnitude / maxDistance, 0.0f, 1.0f), 1.0f / attenuationGamma);
- });
+ m_mixer->update(attenuationFunction);
} else {
if (m_mixer->hasEffect("lowpass"))
diff --git a/source/game/StarVoice.cpp b/source/game/StarVoice.cpp
index 1c949c1..081e2a6 100644
--- a/source/game/StarVoice.cpp
+++ b/source/game/StarVoice.cpp
@@ -1,11 +1,35 @@
#include "StarVoice.hpp"
+#include "StarFormat.hpp"
+#include "opus/include/opus.h"
+
+#include "SDL.h"
namespace Star {
-STAR_EXCEPTION(VoiceException, StarException);
+EnumMap<VoiceTriggerMode> const VoiceTriggerModeNames{
+ {VoiceTriggerMode::VoiceActivity, "VoiceActivity"},
+ {VoiceTriggerMode::PushToTalk, "PushToTalk"}
+};
-void Voice::mix(int16_t* buffer, size_t frames, unsigned channels) {
+EnumMap<VoiceChannelMode> const VoiceChannelModeNames{
+ {VoiceChannelMode::Mono, "Mono"},
+ {VoiceChannelMode::Stereo, "Stereo"}
+};
+
+static SDL_AudioDeviceID sdlInputDevice = 0;
+
+constexpr int VOICE_SAMPLE_RATE = 48000;
+constexpr int VOICE_FRAME_SIZE = 960;
+constexpr int VOICE_MAX_FRAME_SIZE = 6 * VOICE_FRAME_SIZE;
+constexpr int VOICE_MAX_PACKET_SIZE = 3 * 1276;
+
+constexpr uint16_t VOICE_VERSION = 1;
+
+Voice::Speaker::Speaker(SpeakerId id)
+ : decoderMono (createDecoder(1), opus_decoder_destroy)
+ , decoderStereo(createDecoder(2), opus_decoder_destroy) {
+ speakerId = id;
}
Voice* Voice::s_singleton;
@@ -21,10 +45,15 @@ Voice& Voice::singleton() {
return *s_singleton;
}
-Voice::Voice() {
+Voice::Voice() : m_encoder(nullptr, opus_encoder_destroy) {
if (s_singleton)
throw VoiceException("Singleton Voice has been constructed twice");
+ m_clientSpeaker = make_shared<Speaker>(m_speakerId);
+ m_triggerMode = VoiceTriggerMode::PushToTalk;
+ m_channelMode = VoiceChannelMode::Mono;
+
+ resetEncoder();
s_singleton = this;
}
@@ -32,4 +61,71 @@ Voice::~Voice() {
s_singleton = nullptr;
}
+void Voice::load(Json const& config) {
+ // do stuff
+}
+Json Voice::save() const {
+ return JsonObject{};
+}
+
+Voice::SpeakerPtr Voice::setLocalSpeaker(SpeakerId speakerId) {
+ if (m_speakers.contains(m_speakerId))
+ m_speakers.remove(m_speakerId);
+
+ m_clientSpeaker->speakerId = m_speakerId = speakerId;
+ return m_speakers.insert(m_speakerId, m_clientSpeaker).first->second;
+}
+
+Voice::SpeakerPtr Voice::speaker(SpeakerId speakerId) {
+ if (m_speakerId == speakerId)
+ return m_clientSpeaker;
+ else {
+ if (SpeakerPtr const* ptr = m_speakers.ptr(speakerId))
+ return *ptr;
+ else
+ return m_speakers.emplace(speakerId, make_shared<Speaker>(speakerId)).first->second;
+ }
+}
+
+void Voice::mix(int16_t* buffer, size_t frames, unsigned channels) {
+
+}
+
+void Voice::update(PositionalAttenuationFunction positionalAttenuationFunction) {
+ if (positionalAttenuationFunction) {
+ for (auto& entry : m_speakers) {
+ if (SpeakerPtr& speaker = entry.second) {
+ speaker->channelVolumes = {
+ positionalAttenuationFunction(0, speaker->position, 1.0f),
+ positionalAttenuationFunction(1, speaker->position, 1.0f)
+ };
+ }
+ }
+ }
+}
+
+OpusDecoder* Voice::createDecoder(int channels) {
+ int error;
+ OpusDecoder* decoder = opus_decoder_create(VOICE_SAMPLE_RATE, channels, &error);
+ if (error != OPUS_OK)
+ throw VoiceException::format("Could not create decoder: {}", opus_strerror(error));
+ else
+ return decoder;
+}
+
+OpusEncoder* Voice::createEncoder(int channels) {
+ int error;
+ OpusEncoder* encoder = opus_encoder_create(VOICE_SAMPLE_RATE, channels, OPUS_APPLICATION_AUDIO, &error);
+ if (error != OPUS_OK)
+ throw VoiceException::format("Could not create encoder: {}", opus_strerror(error));
+ else
+ return encoder;
+}
+
+void Voice::resetEncoder() {
+ int channels = encoderChannels();
+ m_encoder.reset(createEncoder(channels));
+ opus_encoder_ctl(m_encoder.get(), OPUS_SET_BITRATE(channels == 2 ? 50000 : 24000));
+}
+
} \ No newline at end of file
diff --git a/source/game/StarVoice.hpp b/source/game/StarVoice.hpp
index f102889..e870439 100644
--- a/source/game/StarVoice.hpp
+++ b/source/game/StarVoice.hpp
@@ -1,31 +1,98 @@
#ifndef STAR_VOICE_HPP
#define STAR_VOICE_HPP
-#include "StarString.hpp"
+#include "StarJson.hpp"
+#include "StarBiMap.hpp"
+#include "StarGameTypes.hpp"
+#include "StarException.hpp"
+
+struct OpusDecoder;
+typedef std::unique_ptr<OpusDecoder, void(*)(OpusDecoder*)> OpusDecoderPtr;
+struct OpusEncoder;
+typedef std::unique_ptr<OpusEncoder, void(*)(OpusEncoder*)> OpusEncoderPtr;
namespace Star {
- STAR_CLASS(Voice);
+STAR_EXCEPTION(VoiceException, StarException);
+
+enum class VoiceTriggerMode : uint8_t { VoiceActivity, PushToTalk };
+extern EnumMap<VoiceTriggerMode> const VoiceTriggerModeNames;
+
+enum class VoiceChannelMode: uint8_t { Mono = 1, Stereo = 2 };
+extern EnumMap<VoiceChannelMode> const VoiceChannelModeNames;
- class Voice {
- public:
- void mix(int16_t* buffer, size_t frames, unsigned channels);
+STAR_CLASS(Voice);
- // Get pointer to the singleton Voice instance, if it exists. Otherwise,
- // returns nullptr.
- static Voice* singletonPtr();
+class Voice {
+public:
+ // Individual speakers are represented by their connection ID.
+ typedef ConnectionId SpeakerId;
- // Gets reference to Voice singleton, throws VoiceException if root
- // is not initialized.
- static Voice& singleton();
+ struct Speaker {
+ SpeakerId speakerId = 0;
+ EntityId entityId = 0;
+ Vec2F position = Vec2F();
+ String name = "Unnamed";
- Voice();
- ~Voice();
+ OpusDecoderPtr decoderMono;
+ OpusDecoderPtr decoderStereo;
- Voice(Voice const&) = delete;
- Voice& operator=(Voice const&) = delete;
- private:
- static Voice* s_singleton;
+ atomic<bool> active = false;
+ atomic<float> currentLoudness = 0.0f;
+ atomic<Array<float, 2>> channelVolumes = Array<float, 2>::filled(1.0f);
+
+ Speaker(SpeakerId speakerId);
};
+
+ typedef std::shared_ptr<Speaker> SpeakerPtr;
+
+ // Get pointer to the singleton Voice instance, if it exists. Otherwise,
+ // returns nullptr.
+ static Voice* singletonPtr();
+
+ // Gets reference to Voice singleton, throws VoiceException if root
+ // is not initialized.
+ static Voice& singleton();
+
+ Voice();
+ ~Voice();
+
+ Voice(Voice const&) = delete;
+ Voice& operator=(Voice const&) = delete;
+
+ void load(Json const& config);
+ Json save() const;
+
+ // Sets the local speaker ID and returns the local speaker. Must be called upon loading into a world.
+ SpeakerPtr setLocalSpeaker(SpeakerId speakerId);
+ SpeakerPtr speaker(SpeakerId speakerId);
+
+ // Called to mix voice audio with the game.
+ void mix(int16_t* buffer, size_t frames, unsigned channels);
+
+ typedef function<float(unsigned, Vec2F, float)> PositionalAttenuationFunction;
+ void update(PositionalAttenuationFunction positionalAttenuationFunction = {});
+
+ inline int encoderChannels() const {
+ return m_channelMode == VoiceChannelMode::Mono ? 1 : 2;
+ }
+private:
+ static Voice* s_singleton;
+
+ static OpusDecoder* createDecoder(int channels);
+ static OpusEncoder* createEncoder(int channels);
+ void resetEncoder();
+
+ SpeakerId m_speakerId = 0;
+ SpeakerPtr m_clientSpeaker;
+ HashMap<SpeakerId, SpeakerPtr> m_speakers;
+
+ HashSet<SpeakerPtr> m_activeSpeakers;
+
+ OpusEncoderPtr m_encoder;
+
+ VoiceTriggerMode m_triggerMode;
+ VoiceChannelMode m_channelMode;
+};
}