Initial work

This commit is contained in:
Kae 2023-07-12 22:16:12 +10:00
parent cf09616b1b
commit 40223a5090
16 changed files with 189 additions and 12 deletions

View File

@ -221,7 +221,7 @@ void Mixer::stopAll(float rampTime) {
p.first->stop(vel);
}
void Mixer::read(int16_t* outBuffer, size_t frameCount) {
void Mixer::read(int16_t* outBuffer, size_t frameCount, ExtraMixFunction extraMixFunction) {
// Make this method as least locky as possible by copying all the needed
// member data before the expensive audio / effect stuff.
unsigned sampleRate;
@ -326,7 +326,7 @@ void Mixer::read(int16_t* outBuffer, size_t frameCount) {
m_mixBuffer[s * channels + c] = 0;
} else {
for (size_t c = 0; c < channels; ++c)
m_mixBuffer[s * channels + c] = m_mixBuffer[s * channels + c] * volume;
m_mixBuffer[s * channels + c] *= volume;
}
}
}
@ -338,7 +338,8 @@ void Mixer::read(int16_t* outBuffer, size_t frameCount) {
float vol = lerp((float)s / frameCount, beginVolume * groupVolume * audioStopVolBegin, endVolume * groupEndVolume * audioStopVolEnd);
for (size_t c = 0; c < channels; ++c) {
float sample = m_mixBuffer[s * channels + c] * vol * audioState.positionalChannelVolumes[c] * audioInstance->m_volume.value;
outBuffer[s * channels + c] = clamp(sample + outBuffer[s * channels + c], -32767.0f, 32767.0f);
int16_t& outSample = outBuffer[s * channels + c];
outSample = clamp(sample + outSample, -32767.0f, 32767.0f);
}
}
@ -347,6 +348,9 @@ void Mixer::read(int16_t* outBuffer, size_t frameCount) {
}
}
if (extraMixFunction)
extraMixFunction(outBuffer, bufferSize, channels);
{
MutexLocker locker(m_effectsMutex);
// Apply all active effects

View File

@ -95,6 +95,7 @@ private:
// Thread safe mixer class with basic effects support.
class Mixer {
public:
typedef function<void(int16_t* buffer, size_t frames, unsigned channels)> ExtraMixFunction;
typedef function<void(int16_t* buffer, size_t frames, unsigned channels)> EffectFunction;
typedef function<float(unsigned, Vec2F, float)> PositionalAttenuationFunction;
@ -126,7 +127,7 @@ public:
// Reads pending audio data. This is thread safe with the other Mixer
// methods, but only one call to read may be active at a time.
void read(int16_t* samples, size_t frameCount);
void read(int16_t* samples, size_t frameCount, ExtraMixFunction extraMixFunction = {});
// Call within the main loop of the program using Mixer, calculates
// positional attenuation of audio and does cleanup.

View File

@ -14,6 +14,9 @@
#include "StarWorldTemplate.hpp"
#include "StarWorldClient.hpp"
#include "StarRootLoader.hpp"
#include "StarInput.hpp"
#include "StarVoice.hpp"
#include "StarInterfaceLuaBindings.hpp"
#include "StarInputLuaBindings.hpp"
@ -171,6 +174,7 @@ void ClientApplication::applicationInit(ApplicationControllerPtr appController)
m_guiContext = make_shared<GuiContext>(m_mainMixer->mixer(), appController);
m_input = make_shared<Input>();
m_voice = make_shared<Voice>();
auto configuration = m_root->configuration();
bool vsync = configuration->get("vsync").toBool();
@ -417,8 +421,12 @@ void ClientApplication::render() {
}
void ClientApplication::getAudioData(int16_t* sampleData, size_t frameCount) {
if (m_mainMixer)
m_mainMixer->read(sampleData, frameCount);
if (m_mainMixer) {
m_mainMixer->read(sampleData, frameCount, [&](int16_t* buffer, size_t frames, unsigned channels) {
if (m_voice)
m_voice->mix(buffer, frames, channels);
});
}
}
void ClientApplication::changeState(MainAppState newState) {

View File

@ -11,11 +11,13 @@
#include "StarErrorScreen.hpp"
#include "StarCinematic.hpp"
#include "StarKeyBindings.hpp"
#include "StarInput.hpp"
#include "StarMainApplication.hpp"
namespace Star {
STAR_CLASS(Input);
STAR_CLASS(Voice);
class ClientApplication : public Application {
protected:
virtual void startup(StringList const& cmdLineArgs) override;
@ -76,6 +78,7 @@ private:
MainMixerPtr m_mainMixer;
GuiContextPtr m_guiContext;
InputPtr m_input;
VoicePtr m_voice;
// Valid after renderInit is called the first time
CinematicPtr m_cinematicOverlay;

View File

@ -205,7 +205,7 @@ size_t DataStream::readVlqU(uint64_t& i) {
size_t bytesRead = Star::readVlqU(i, makeFunctionInputIterator([this]() { return this->read<uint8_t>(); }));
if (bytesRead == NPos)
throw DataStreamException("Error reading VLQ encoded intenger!");
throw DataStreamException("Error reading VLQ encoded integer!");
return bytesRead;
}
@ -214,7 +214,7 @@ size_t DataStream::readVlqI(int64_t& i) {
size_t bytesRead = Star::readVlqI(i, makeFunctionInputIterator([this]() { return this->read<uint8_t>(); }));
if (bytesRead == NPos)
throw DataStreamException("Error reading VLQ encoded intenger!");
throw DataStreamException("Error reading VLQ encoded integer!");
return bytesRead;
}

View File

@ -164,4 +164,11 @@ void DataStreamExternalBuffer::reset(char const* externalData, size_t len) {
m_buffer.reset(externalData, len);
}
void DataStreamExternalBuffer::readData(char* data, size_t len) {
m_buffer.readFull(data, len);
}
void DataStreamExternalBuffer::writeData(char const* data, size_t len) {
m_buffer.writeFull(data, len);
}
}

View File

@ -140,6 +140,9 @@ public:
void reset(char const* externalData, size_t len);
void readData(char* data, size_t len) override;
void writeData(char const* data, size_t len) override;
private:
ExternalBuffer m_buffer;
};

View File

@ -127,8 +127,8 @@ void MainMixer::setVolume(float volume, float rampTime) {
m_mixer->setVolume(volume, rampTime);
}
void MainMixer::read(int16_t* sampleData, size_t frameCount) {
m_mixer->read(sampleData, frameCount);
void MainMixer::read(int16_t* sampleData, size_t frameCount, Mixer::ExtraMixFunction extraMixFunction) {
m_mixer->read(sampleData, frameCount, extraMixFunction);
}
}

View File

@ -22,7 +22,7 @@ public:
MixerPtr mixer() const;
void setVolume(float volume, float rampTime = 0.0f);
void read(int16_t* sampleData, size_t frameCount);
void read(int16_t* sampleData, size_t frameCount, Mixer::ExtraMixFunction = {});
private:
UniverseClientPtr m_universeClient;

View File

@ -156,6 +156,7 @@ SET (star_game_HEADERS
StarVehicle.hpp
StarVehicleDatabase.hpp
StarVersioningDatabase.hpp
StarVoice.hpp
StarWarping.hpp
StarWeather.hpp
StarWeatherTypes.hpp
@ -414,6 +415,7 @@ SET (star_game_SOURCES
StarVehicle.cpp
StarVehicleDatabase.cpp
StarVersioningDatabase.cpp
StarVoice.cpp
StarWarping.cpp
StarWeather.cpp
StarWeatherTypes.cpp

View File

@ -383,6 +383,15 @@ void NetworkedAnimator::setGlobalTag(String tagName, String tagValue) {
m_globalTags.set(move(tagName), move(tagValue));
}
void NetworkedAnimator::removeGlobalTag(String const& tagName) {
m_globalTags.remove(tagName);
}
String const* NetworkedAnimator::globalTagPtr(String const& tagName) const {
return m_globalTags.ptr(tagName);
}
void NetworkedAnimator::setPartTag(String const& partType, String tagName, String tagValue) {
m_partTags[partType].set(move(tagName), move(tagValue));
}

View File

@ -115,6 +115,8 @@ public:
// Drawables can also have a <frame> tag which will be set to whatever the
// current state frame is (1 indexed, so the first frame is 1).
void setGlobalTag(String tagName, String tagValue);
void removeGlobalTag(String const& tagName);
String const* globalTagPtr(String const& tagName) const;
void setPartTag(String const& partType, String tagName, String tagValue);
void setProcessingDirectives(Directives const& directives);

View File

@ -2464,4 +2464,53 @@ Vec2F Player::cameraPosition() {
return position();
}
NetworkedAnimatorPtr Player::effectsAnimator() {
return m_effectsAnimator;
}
const String secretProprefix = strf("{:c}JsonProperty{:c}", 0, 0);
Maybe<StringView> Player::getSecretPropertyView(String const& name) const {
if (auto tag = m_effectsAnimator->globalTagPtr(secretProprefix + name)) {
auto& view = tag->utf8();
DataStreamExternalBuffer buffer(view.data(), view.size());
try {
uint8_t typeIndex = buffer.read<uint8_t>() - 1;
if ((Json::Type)typeIndex == Json::Type::String) {
size_t len = buffer.readVlqU();
size_t pos = buffer.pos();
if (pos + len == buffer.size())
return StringView(buffer.ptr() + pos, len);
}
}
catch (StarException const& e) {}
}
return {};
}
Json Player::getSecretProperty(String const& name, Json defaultValue) const {
if (auto tag = m_effectsAnimator->globalTagPtr(secretProprefix + name)) {
DataStreamExternalBuffer buffer(tag->utf8Ptr(), tag->utf8Size());
try
{ return buffer.read<Json>(); }
catch (StarException const& e)
{ Logger::error("Exception reading secret player property '{}': {}", name, e.what()); }
}
return move(defaultValue);
}
void Player::setSecretProperty(String const& name, Json const& value) {
if (value) {
DataStreamBuffer ds;
ds.write(value);
auto& data = ds.data();
m_effectsAnimator->setGlobalTag(secretProprefix + name, String(data.ptr(), data.size()));
}
else
m_effectsAnimator->removeGlobalTag(secretProprefix + name);
}
}

View File

@ -447,6 +447,28 @@ public:
using Entity::setTeam;
NetworkedAnimatorPtr effectsAnimator();
// We need to store ephemeral/large/always-changing networked properties that other clients can read. Candidates:
// genericProperties:
// Non-starter, is not networked.
// statusProperties:
// Nope! Changes to the status properties aren't networked efficiently - one change resends the whole map.
// We can't fix that because it would break compatibility with vanilla servers.
// effectsAnimator's globalTags:
// Cursed, but viable.
// Efficient networking due to using a NetElementMapWrapper.
// Unfortunately values are Strings, so to work with Json we need to serialize/deserialize. Whatever.
// Additionally, this is compatible with vanilla networking.
// I call this a 'secret property'.
// If the secret property exists as a serialized Json string, returns a view to it without deserializing.
Maybe<StringView> getSecretPropertyView(String const& name) const;
// Gets a secret Json property. It will be de-serialized.
Json getSecretProperty(String const& name, Json defaultValue = Json()) const;
// Sets a secret Json property. It will be serialized.
void setSecretProperty(String const& name, Json const& value);
private:
enum class State {
Idle,

35
source/game/StarVoice.cpp Normal file
View File

@ -0,0 +1,35 @@
#include "StarVoice.hpp"
namespace Star {
STAR_EXCEPTION(VoiceException, StarException);
void Voice::mix(int16_t* buffer, size_t frames, unsigned channels) {
}
Voice* Voice::s_singleton;
Voice* Voice::singletonPtr() {
return s_singleton;
}
Voice& Voice::singleton() {
if (!s_singleton)
throw VoiceException("Voice::singleton() called with no Voice instance available");
else
return *s_singleton;
}
Voice::Voice() {
if (s_singleton)
throw VoiceException("Singleton Voice has been constructed twice");
s_singleton = this;
}
Voice::~Voice() {
s_singleton = nullptr;
}
}

32
source/game/StarVoice.hpp Normal file
View File

@ -0,0 +1,32 @@
#ifndef STAR_VOICE_HPP
#define STAR_VOICE_HPP
#include "StarString.hpp"
namespace Star {
STAR_CLASS(Voice);
class Voice {
public:
void mix(int16_t* buffer, size_t frames, unsigned channels);
// 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;
private:
static Voice* s_singleton;
};
}
#endif