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 all 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_getChannelData.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@ private _return = HASH_CREATE;
HASH_SET(_return, "mode", "singleChannelPRR");
HASH_SET(_return, "frequencyTX", HASH_GET(_channel, "frequencyTX"));
HASH_SET(_return, "frequencyRX", HASH_GET(_channel, "frequencyRX"));
HASH_SET(_return, "modulation", "CVSD");
HASH_SET(_return, "power", 100);
_return
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
13 changes: 0 additions & 13 deletions addons/sys_sem52sl/radio/fnc_getChannelData.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,6 @@ private _channelNumber = _eventData;
private _channels = HASH_GET(_radioData, "channels");
private _channel = HASHLIST_SELECT(_channels, _channelNumber);

/*
* All needed data from the channel hash can be extracted and
* consequently written to the _return hash.
*
* Optional:
* Here we have the opportunity to add static data to the
* channel data hash. This can be useful if the radio has
* only one power setting. While this data can be stored in
* the _radioData -- channels hash it would only add unneccessary
* data as the value can't be changed ingame.
* For our example, we also got the "mode" parameter set to
* "singleChannel" for all channels.
*/
private _return = HASH_CREATE;
HASH_SET(_return, "mode", GVAR(channelMode));
HASH_SET(_return, "frequencyTX", HASH_GET(_channel, "frequencyTX"));
Expand Down
13 changes: 0 additions & 13 deletions addons/sys_sem52sl/radio/fnc_getCurrentChannelData.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,6 @@ ISNILS(_channelNumber,0);
private _channels = HASH_GET(_radioData, "channels");
private _channel = HASHLIST_SELECT(_channels, _channelNumber);

/*
* All needed data from the channel hash can be extracted and
* consequently written to the _return hash.
*
* Optional:
* Here we have the opportunity to add static data to the
* channel data hash. This can be useful if the radio has
* only one power setting. While this data can be stored in
* the _radioData -- channels hash it would only add unneccessary
* data as the value can't be changed ingame.
* For our example, we also got the "mode" parameter set to
* "singleChannel" for all channels.
*/
private _return = HASH_CREATE;
HASH_SET(_return, "mode", GVAR(channelMode));
HASH_SET(_return, "frequencyTX", HASH_GET(_channel, "frequencyTX"));
Expand Down
18 changes: 5 additions & 13 deletions addons/sys_sem70/radio/fnc_getChannelData.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,6 @@ private _channelNumber = _eventData;
private _channels = HASH_GET(_radioData, "channels");
private _channel = HASHLIST_SELECT(_channels, _channelNumber);
private _manualChannel = HASH_GET(_radioData, "manualChannelSelection");
/*
* All needed data from the channel hash can be extracted and
* consequently written to the _return hash.
*
* Optional:
* Here we have the opportunity to add static data to the
* channel data hash. This can be useful if the radio has
* only one power setting. While this data can be stored in
* the _radioData -- channels hash it would only add unneccessary
* data as the value can't be changed ingame.
* For our example, we also got the "mode" parameter set to
* "singleChannel" for all channels.
*/

private _return = HASH_CREATE;

Expand All @@ -85,6 +72,11 @@ if (_manualChannel isEqualTo 1) then {
HASH_SET(_return, "frequencies", HASH_GET(_channel, "frequencies"));
HASH_SET(_return, "frequencyTX", HASH_GET(_channel, "frequencyTX"));
HASH_SET(_return, "frequencyRX", HASH_GET(_channel, "frequencyRX"));
HASH_SET(_return, "CTCSSTx", HASH_GET(_radioData, "CTCSS"));
HASH_SET(_return, "CTCSSRx", HASH_GET(_radioData, "CTCSS"));
HASH_SET(_return, "modulation", HASH_GET(_radioData, "modulation"));
HASH_SET(_return, "encryption", HASH_GET(_radioData, "encryption"));
HASH_SET(_return, "squelch", HASH_GET(_radioData, "squelch"));
if (HASH_GET(_radioData, "powerSource") == "VAU") then {
HASH_SET(_return, "power", (HASH_GET(_radioData, "power") * SEM90_RACK_POWER_MULTIPLIER));
} else {
Expand Down
23 changes: 5 additions & 18 deletions addons/sys_sem70/radio/fnc_getCurrentChannelData.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,6 @@ ISNILS(_channelNumber,0);
private _channels = HASH_GET(_radioData, "channels");
private _channel = HASHLIST_SELECT(_channels, _channelNumber);
private _manualChannel = HASH_GET(_radioData, "manualChannelSelection");
/*
* All needed data from the channel hash can be extracted and
* consequently written to the _return hash.
*
* Optional:
* Here we have the opportunity to add static data to the
* channel data hash. This can be useful if the radio has
* only one power setting. While this data can be stored in
* the _radioData -- channels hash it would only add unneccessary
* data as the value can't be changed ingame.
* For our example, we also got the "mode" parameter set to
* "singleChannel" for all channels.
*/

private _return = HASH_CREATE;

Expand All @@ -82,11 +69,11 @@ if (_manualChannel isEqualTo 1) then {
HASH_SET(_return, "frequencies", HASH_GET(_channel, "frequencies"));
HASH_SET(_return, "frequencyTX", HASH_GET(_channel, "frequencyTX"));
HASH_SET(_return, "frequencyRX", HASH_GET(_channel, "frequencyRX"));
//HASH_SET(_return, "CTCSSTx", HASH_GET(_radioData, "CTCSS"));
//HASH_SET(_return, "CTCSSRx", HASH_GET(_radioData, "CTCSS"));
//HASH_SET(_return, "modulation", HASH_GET(_radioData, "modulation"));
//HASH_SET(_return, "encryption", HASH_GET(_radioData, "encryption"));
HASH_SET(_return, "CTCSSTx", HASH_GET(_radioData, "CTCSS"));
HASH_SET(_return, "CTCSSRx", HASH_GET(_radioData, "CTCSS"));
HASH_SET(_return, "modulation", HASH_GET(_radioData, "modulation"));
HASH_SET(_return, "encryption", HASH_GET(_radioData, "encryption"));
HASH_SET(_return, "power", HASH_GET(_radioData, "power"));
//HASH_SET(_return, "squelch", HASH_GET(_radioData, "squelch"));
HASH_SET(_return, "squelch", HASH_GET(_radioData, "squelch"));
};
_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);
}
Loading