Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Extensions - Add CVSD codec effect for AN/PRC-343 #1135

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion addons/sys_core/fnc_processRadioSpeaker.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,20 @@ if (_okRadios isNotEqualTo []) then {
_radioVolume = [_x, _radioVolume] call EFUNC(sys_intercom,modifyRadioVolume);
_radioVolume = _radioVolume * GVAR(globalVolume);
// acre_player sideChat format["rv: %1", _radioVolume];

private _channelData = [_receivingRadioid, "getCurrentChannelData"] call EFUNC(sys_data,dataEvent);
private _modulation = HASH_GET(_channelData, "modulation");

private _isLoudspeaker = [_receivingRadioid, "isExternalAudio"] call EFUNC(sys_data,dataEvent);

private _spatialArray = [0,0,0];
if (!_isLoudspeaker) then {
private _spatial = [_receivingRadioid, "getSpatial"] call EFUNC(sys_data,dataEvent);
_spatialArray = [_spatial, 0, 0];
};
// FULL DUPLEX radios, shouldn't be able to hear themselves.

_params = [_transmittingRadioId, _receivingRadioid, [_signalQuality, _signalDb], [_radioVolume, _signalQuality, _signalModel, _isLoudspeaker, _spatialArray]];
_params = [_transmittingRadioId, _receivingRadioid, [_signalQuality, _signalDb], [_radioVolume, _signalQuality, _signalModel, _isLoudspeaker, _spatialArray, _modulation]];
_unit setVariable ["ACRE_%1CachedSampleParams"+_x, _params];
} else {
_params = _unit getVariable ["ACRE_%1CachedSampleParams"+_x, []];
Expand Down
1 change: 1 addition & 0 deletions addons/sys_prc343/radio/fnc_getCurrentChannelData.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,5 @@ HASH_SET(_return, "mode", "singleChannelPRR");
HASH_SET(_return, "frequencyTX", HASH_GET(_currentChannelData, "frequencyTX"));
HASH_SET(_return, "frequencyRX", HASH_GET(_currentChannelData, "frequencyRX"));
HASH_SET(_return, "power", 100);
HASH_SET(_return, "modulation", "CVSD");
_return
20 changes: 10 additions & 10 deletions extensions/src/ACRE2Core/FilterPosition.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,22 +57,22 @@ acre::Result CFilterPosition::process(short* samples, int sampleCount, int chann
DSPSettings.DstChannelCount = channels;
DSPSettings.pMatrixCoefficients = Matrix;

speaker_position.x = params->getParam("speakerPosX");
speaker_position.y = params->getParam("speakerPosY");
speaker_position.z = params->getParam("speakerPosZ");
speaker_position.x = params->getParam<float>("speakerPosX");
speaker_position.y = params->getParam<float>("speakerPosY");
speaker_position.z = params->getParam<float>("speakerPosZ");

Emitter.Position = speaker_position;

vector_speakerDirection.x = params->getParam("headVectorX");
vector_speakerDirection.y = params->getParam("headVectorY");
vector_speakerDirection.z = params->getParam("headVectorZ");
vector_speakerDirection.x = params->getParam<float>("headVectorX");
vector_speakerDirection.y = params->getParam<float>("headVectorY");
vector_speakerDirection.z = params->getParam<float>("headVectorZ");

Emitter.OrientFront = vector_speakerDirection;
Emitter.OrientTop = this->getUpVector(vector_speakerDirection);
Emitter.Velocity = X3DAUDIO_VECTOR( 0, 0, 0 );
Emitter.ChannelCount = 1;

if (params->getParam("isWorld") == POSITIONAL_EFFECT_ISWORLD) {
if (params->getParam<bool>("isWorld")) {
listener_position.x = CEngine::getInstance()->getSelf()->getWorldPosition().x;
listener_position.y = CEngine::getInstance()->getSelf()->getWorldPosition().y;
listener_position.z = CEngine::getInstance()->getSelf()->getWorldPosition().z;
Expand All @@ -82,16 +82,16 @@ acre::Result CFilterPosition::process(short* samples, int sampleCount, int chann
vector_listenerDirection.y = CEngine::getInstance()->getSelf()->getHeadVector().y;
vector_listenerDirection.z = CEngine::getInstance()->getSelf()->getHeadVector().z;

if (params->getParam("speakingType") == static_cast<float32_t>(acre::Speaking::direct)) {
if (params->getParam<acre::Speaking>("speakingType") == acre::Speaking::direct) {
/*if(CEngine::getInstance()->getSoundEngine()->getCurveModel() == acre::CurveModel::amplitude) {
Emitter.CurveDistanceScaler = (player->getAmplitudeCoef())*(CEngine::getInstance()->getSoundEngine()->getCurveScale());
Emitter.pVolumeCurve = NULL;
} else */
if (CEngine::getInstance()->getSoundEngine()->getCurveModel() == acre::CurveModel::selectableA) {
Emitter.CurveDistanceScaler = 1.0f*(params->getParam("curveScale"));
Emitter.CurveDistanceScaler = 1.0f * params->getParam<float>("curveScale");
Emitter.pVolumeCurve = NULL;
} else if (CEngine::getInstance()->getSoundEngine()->getCurveModel() == acre::CurveModel::selectableB) {
Emitter.CurveDistanceScaler = 1.0f*(params->getParam("curveScale"));
Emitter.CurveDistanceScaler = 1.0f * params->getParam<float>("curveScale");
Emitter.pVolumeCurve = (X3DAUDIO_DISTANCE_CURVE *)&distanceCurve;
} else {
Emitter.CurveDistanceScaler = 1.0f;
Expand Down
2 changes: 1 addition & 1 deletion extensions/src/ACRE2Core/Player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ void CPlayer::clearSoundChannels() {
CEngine::getInstance()->getSoundEngine()->getSoundMixer()->lock();
for (size_t i = 0; i < channels.size(); ++i) {
if (channels[i]) {
CEngine::getInstance()->getSoundEngine()->getSoundMixer()->releaseChannel(channels[i]);
CEngine::getInstance()->getSoundEngine()->getSoundMixer()->releaseChannel(&channels[i]);
}
}
CEngine::getInstance()->getSoundEngine()->getSoundMixer()->unlock();
Expand Down
2 changes: 1 addition & 1 deletion extensions/src/ACRE2Core/Player.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class CPlayer : public CLockable {
DECLARE_MEMBER(acre::volume_t, PreviousVolume);
DECLARE_MEMBER(acre::volume_t, SignalQuality);
DECLARE_MEMBER(char *, SignalModel);
DECLARE_MEMBER(BOOL, IsLoudSpeaker);
DECLARE_MEMBER(bool, IsLoudSpeaker);

DECLARE_MEMBER(std::string, CurrentRadioId);

Expand Down
9 changes: 3 additions & 6 deletions extensions/src/ACRE2Core/PositionalMixdownEffect.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,21 @@
#include "SoundMixdownEffect.h"
#include "FilterPosition.h"

#define POSITIONAL_EFFECT_ISLOCAL 0x00000000
#define POSITIONAL_EFFECT_ISWORLD 0x00000001

class CPositionalMixdownEffect : public CSoundMixdownEffect {
private:
static CFilterPosition positionFilter;
public:
CPositionalMixdownEffect() {
this->setParam("isWorld", POSITIONAL_EFFECT_ISWORLD);
this->setParam("isLoudSpeaker", 0.0f);
this->setParam("isWorld", true);
this->setParam("isLoudSpeaker", false);
this->setParam("speakerPosX", 0.0f);
this->setParam("speakerPosY", 0.0f);
this->setParam("speakerPosZ", 0.0f);
this->setParam("headVectorX", 0.0f);
this->setParam("headVectorY", 1.0f);
this->setParam("headVectorZ", 0.0f);
this->setParam("curveScale", 1.0f);
this->setParam("speakingType", static_cast<float32_t>(acre::Speaking::direct));
this->setParam("speakingType", acre::Speaking::direct);
};
void process(short* samples, int sampleCount, int channels, const unsigned int speakerMask) {
this->positionFilter.process(samples, sampleCount, channels, speakerMask, this);
Expand Down
18 changes: 0 additions & 18 deletions extensions/src/ACRE2Core/RadioEffect.cpp

This file was deleted.

23 changes: 23 additions & 0 deletions extensions/src/ACRE2Core/RadioEffectAnalogue.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#include "RadioEffectAnalogue.h"


CRadioEffectAnalogue::CRadioEffectAnalogue() {
radioFilter = new CFilterRadio();
this->setParam("signalQuality", 0.0f);
};
CRadioEffectAnalogue::~CRadioEffectAnalogue() {
delete radioFilter;
}
void CRadioEffectAnalogue::process(short *samples, int sampleCount) {
bool noise = true;
if (this->getParam<bool>("disableNoise")) {
noise = false;
}

this->radioFilter->process(
samples,
sampleCount,
1,
this->getParam<acre::volume_t>("signalQuality"),
noise);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
#include "SoundMonoEffect.h"
#include "FilterRadio.h"

class CRadioEffect : public CSoundMonoEffect {
class CRadioEffectAnalogue : public CSoundMonoEffect {
private:
CFilterRadio *radioFilter;
public:
CRadioEffect();
~CRadioEffect();
CRadioEffectAnalogue();
~CRadioEffectAnalogue();
void process(short *samples, int sampleCount);
};
};
99 changes: 99 additions & 0 deletions extensions/src/ACRE2Core/RadioEffectCVSD.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#include "RadioEffectCVSD.h"

CRadioEffectCVSD::CVSDDecoder::CVSDDecoder() {
lookback_register.reserve(lookback);
}

short CRadioEffectCVSD::CVSDDecoder::decode(bool sample) {
// shift the new sample into the lookback shift register
if (lookback_register.size() < lookback) {
lookback_register.emplace_back();
}
std::copy(
std::next(lookback_register.rbegin()),
lookback_register.rend(),
lookback_register.rbegin());
if (!lookback_register.empty()) {
lookback_register[0] = sample;
}

if (lookback_register.size() >= lookback) {
// adjust delta
bool positive_run = true;
bool negative_run = true;
for (bool val: lookback_register) {
positive_run &= val;
negative_run &= !val;
}
if (positive_run || negative_run) {
delta = std::min(delta += delta_step, delta_max);
} else {
delta = std::max(delta *= delta_coef, delta_min);
}
}

// adjust reference
const short previous_reference = reference;
if (sample) {
reference += delta;
if (reference < previous_reference) {
reference = SHRT_MAX;
}
} else {
reference -= delta;
if (reference > previous_reference) {
reference = SHRT_MIN;
}
}
reference *= decay;

return reference;
}

bool CRadioEffectCVSD::CVSDEncoder::encode(short sample) {
const bool ret = sample > reference;
reference = decoder.decode(ret);
return ret;
}

CRadioEffectCVSD::CRadioEffectCVSD() {
input_filter.setup(8, TS_SAMPLE_RATE, CVSD_RATE/2, 1);
output_filter.setup(8, TS_SAMPLE_RATE, CVSD_RATE/4, 1);
}

// this is an arbitrary function derived empirically to match the intelligibility
// of the existing signalQuality metric at a variety of distances.
float CRadioEffectCVSD::quality_to_ber(float signalQuality) {
return 0.05 * std::pow((signalQuality - 1), 2);
}

void CRadioEffectCVSD::process(short *samples, int sampleCount) {
// anti-aliasing LPF prior to downsampling
input_filter.process(sampleCount, &samples);

for (std::size_t i = 0; i < sampleCount; i += TS_SAMPLE_RATE / CVSD_RATE) {
bool coded = encoder.encode(samples[i]);

// introduce bit errors
const float bit_error_rate = quality_to_ber(getParam<float>("signalQuality"));
if ((float) std::rand() / RAND_MAX < bit_error_rate) {
coded ^= 1;
}

samples[i] = decoder.decode(coded);

// boost the volume
if (samples[i] < SHRT_MAX / VOL_BOOST) {
samples[i] *= VOL_BOOST;
} else {
samples[i] = SHRT_MAX;
}

// upsample back to 48kHz
for (std::size_t j = 1; j < TS_SAMPLE_RATE / CVSD_RATE; j++) {
samples[i + j] = samples[i];
}
}

output_filter.process(sampleCount, &samples);
}
59 changes: 59 additions & 0 deletions extensions/src/ACRE2Core/RadioEffectCVSD.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#pragma once

#include <vector>
#include <climits>

#include "compat.h"

#include "SoundMonoEffect.h"
#include "AcreDsp.h"

// the CVSD rate must be a factor of the sample rate
#define TS_SAMPLE_RATE 48000
#define CVSD_RATE 16000

// this is chosen to match the volume of other radio effects
#define VOL_BOOST 3

class CRadioEffectCVSD : public CSoundMonoEffect {
private:
class CVSDDecoder {
private:
std::vector<bool> lookback_register;
const std::size_t lookback = 3;
const short delta_min = 256;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My first comment on this. Can you use fixed size integers (for example std::int16_t)?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've done this using shorts as this is what the TS API uses for audio samples (which is a little odd). It's what all the existing effects use also.

const short delta_max = 16 * delta_min;
const short delta_step = delta_min;
const float delta_coef = std::exp(-1.0f / (5e-3 * CVSD_RATE));
const float decay = std::exp(-1.0f / (1e-3 * CVSD_RATE));

short reference = 0;
short delta = delta_min;

public:
CVSDDecoder();

short decode(bool sample);
};

class CVSDEncoder {
private:
CVSDDecoder decoder;

short reference = 0;

public:
bool encode(short sample);
};

CVSDEncoder encoder;
CVSDDecoder decoder;
Dsp::SimpleFilter<Dsp::ChebyshevI::LowPass<8>, 1> input_filter;
Dsp::SimpleFilter<Dsp::ChebyshevI::LowPass<8>, 1> output_filter;

public:
CRadioEffectCVSD();

static float quality_to_ber(float signalQuality);
void process(short* samples, int sampleCount);
};
10 changes: 6 additions & 4 deletions extensions/src/ACRE2Core/SoundMixdownEffect.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@
#include "Lockable.h"
#include <string>
#include <map>
#include <any>
#include <concurrent_unordered_map.h>

class CSoundMixdownEffect : public CLockable {
private:
concurrency::concurrent_unordered_map<std::string, float> paramMap;
concurrency::concurrent_unordered_map<std::string, std::any> paramMap;
public:
CSoundMixdownEffect() { };
~CSoundMixdownEffect() { };
virtual void process(short* samples, int sampleCount, int channels, const unsigned int speakerMask) = 0;
void setParam(std::string paramName, float value) { paramMap[paramName] = value; };
float getParam(std::string paramName) { return paramMap[paramName]; };
};
void setParam(std::string paramName, std::any value) { paramMap[paramName] = value; };
template <typename T>
T getParam(std::string paramName) { return std::any_cast<T>(paramMap[paramName]); };
};
16 changes: 8 additions & 8 deletions extensions/src/ACRE2Core/SoundMixer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,22 +87,22 @@ void CSoundMixer::mixDown(short* samples, int sampleCount, int channels, const u
}
if (cleanUp.size() > 0) {
for (auto it = cleanUp.begin(); it != cleanUp.end(); ++it) {
this->releaseChannel((CSoundChannelMono *)*it);
this->releaseChannel((CSoundChannelMono **)*it);
}
}
delete monoSamples;
delete mixSamples;
this->unlock();
}

bool CSoundMixer::releaseChannel(CSoundChannelMono *releaseChannel) {
bool CSoundMixer::releaseChannel(CSoundChannelMono **releaseChannel) {
this->lock();
if (this->channelList.find(releaseChannel) != this->channelList.end()) {
this->channelList.unsafe_erase(releaseChannel);
if (releaseChannel)
delete releaseChannel;
releaseChannel = NULL;
if (this->channelList.find(*releaseChannel) != this->channelList.end()) {
this->channelList.unsafe_erase(*releaseChannel);
if (*releaseChannel)
delete *releaseChannel;
*releaseChannel = NULL;
}
this->unlock();
return true;
};
};
Loading