Initial work
This commit is contained in:
parent
cf09616b1b
commit
40223a5090
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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
35
source/game/StarVoice.cpp
Normal 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
32
source/game/StarVoice.hpp
Normal 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
|
Loading…
Reference in New Issue
Block a user