diff --git a/addons/sys_core/fnc_processRadioSpeaker.sqf b/addons/sys_core/fnc_processRadioSpeaker.sqf index 757377fd3..e8c6e9326 100644 --- a/addons/sys_core/fnc_processRadioSpeaker.sqf +++ b/addons/sys_core/fnc_processRadioSpeaker.sqf @@ -92,7 +92,12 @@ 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); @@ -100,7 +105,7 @@ if (_okRadios isNotEqualTo []) then { }; // 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, []]; diff --git a/addons/sys_prc343/radio/fnc_getChannelData.sqf b/addons/sys_prc343/radio/fnc_getChannelData.sqf index 067403fd9..709fbd935 100644 --- a/addons/sys_prc343/radio/fnc_getChannelData.sqf +++ b/addons/sys_prc343/radio/fnc_getChannelData.sqf @@ -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 diff --git a/addons/sys_prc343/radio/fnc_getCurrentChannelData.sqf b/addons/sys_prc343/radio/fnc_getCurrentChannelData.sqf index d73aaa6b5..e3e0f1311 100644 --- a/addons/sys_prc343/radio/fnc_getCurrentChannelData.sqf +++ b/addons/sys_prc343/radio/fnc_getCurrentChannelData.sqf @@ -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 diff --git a/addons/sys_sem52sl/radio/fnc_getChannelData.sqf b/addons/sys_sem52sl/radio/fnc_getChannelData.sqf index 6f482069c..f30f04af8 100644 --- a/addons/sys_sem52sl/radio/fnc_getChannelData.sqf +++ b/addons/sys_sem52sl/radio/fnc_getChannelData.sqf @@ -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")); diff --git a/addons/sys_sem52sl/radio/fnc_getCurrentChannelData.sqf b/addons/sys_sem52sl/radio/fnc_getCurrentChannelData.sqf index 54dffea6c..5a98fc51f 100644 --- a/addons/sys_sem52sl/radio/fnc_getCurrentChannelData.sqf +++ b/addons/sys_sem52sl/radio/fnc_getCurrentChannelData.sqf @@ -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")); diff --git a/addons/sys_sem70/radio/fnc_getChannelData.sqf b/addons/sys_sem70/radio/fnc_getChannelData.sqf index 997b636df..a2c24a266 100644 --- a/addons/sys_sem70/radio/fnc_getChannelData.sqf +++ b/addons/sys_sem70/radio/fnc_getChannelData.sqf @@ -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; @@ -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 { diff --git a/addons/sys_sem70/radio/fnc_getCurrentChannelData.sqf b/addons/sys_sem70/radio/fnc_getCurrentChannelData.sqf index 18d82a3ad..b1281a2b2 100644 --- a/addons/sys_sem70/radio/fnc_getCurrentChannelData.sqf +++ b/addons/sys_sem70/radio/fnc_getCurrentChannelData.sqf @@ -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; @@ -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 diff --git a/extensions/src/ACRE2Core/FilterPosition.cpp b/extensions/src/ACRE2Core/FilterPosition.cpp index 49db8551b..08a7011f2 100644 --- a/extensions/src/ACRE2Core/FilterPosition.cpp +++ b/extensions/src/ACRE2Core/FilterPosition.cpp @@ -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("speakerPosX"); + speaker_position.y = params->getParam("speakerPosY"); + speaker_position.z = params->getParam("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("headVectorX"); + vector_speakerDirection.y = params->getParam("headVectorY"); + vector_speakerDirection.z = params->getParam("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("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; @@ -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(acre::Speaking::direct)) { + if (params->getParam("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("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("curveScale"); Emitter.pVolumeCurve = (X3DAUDIO_DISTANCE_CURVE *)&distanceCurve; } else { Emitter.CurveDistanceScaler = 1.0f; diff --git a/extensions/src/ACRE2Core/Player.cpp b/extensions/src/ACRE2Core/Player.cpp index 11e14a82e..acccdd2be 100644 --- a/extensions/src/ACRE2Core/Player.cpp +++ b/extensions/src/ACRE2Core/Player.cpp @@ -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(); diff --git a/extensions/src/ACRE2Core/Player.h b/extensions/src/ACRE2Core/Player.h index 5f171b5dd..6d259d087 100644 --- a/extensions/src/ACRE2Core/Player.h +++ b/extensions/src/ACRE2Core/Player.h @@ -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); diff --git a/extensions/src/ACRE2Core/PositionalMixdownEffect.h b/extensions/src/ACRE2Core/PositionalMixdownEffect.h index 37d1abc33..a11c5bae1 100644 --- a/extensions/src/ACRE2Core/PositionalMixdownEffect.h +++ b/extensions/src/ACRE2Core/PositionalMixdownEffect.h @@ -5,16 +5,13 @@ #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); @@ -22,7 +19,7 @@ class CPositionalMixdownEffect : public CSoundMixdownEffect { this->setParam("headVectorY", 1.0f); this->setParam("headVectorZ", 0.0f); this->setParam("curveScale", 1.0f); - this->setParam("speakingType", static_cast(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); diff --git a/extensions/src/ACRE2Core/RadioEffect.cpp b/extensions/src/ACRE2Core/RadioEffect.cpp deleted file mode 100644 index 8da80e341..000000000 --- a/extensions/src/ACRE2Core/RadioEffect.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "RadioEffect.h" - - -CRadioEffect::CRadioEffect() { - radioFilter = new CFilterRadio(); - this->setParam("signalQuality", 0.0f); -}; -CRadioEffect::~CRadioEffect() { - delete radioFilter; -} -void CRadioEffect::process(short *samples, int sampleCount) { - - bool noise = true; - if (this->getParam("disableNoise")) - noise = false; - - this->radioFilter->process(samples, sampleCount, 1, static_cast(this->getParam("signalQuality")), noise); -}; diff --git a/extensions/src/ACRE2Core/RadioEffectAnalogue.cpp b/extensions/src/ACRE2Core/RadioEffectAnalogue.cpp new file mode 100644 index 000000000..1b712332f --- /dev/null +++ b/extensions/src/ACRE2Core/RadioEffectAnalogue.cpp @@ -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("disableNoise")) { + noise = false; + } + + this->radioFilter->process( + samples, + sampleCount, + 1, + this->getParam("signalQuality"), + noise); +}; diff --git a/extensions/src/ACRE2Core/RadioEffect.h b/extensions/src/ACRE2Core/RadioEffectAnalogue.h similarity index 65% rename from extensions/src/ACRE2Core/RadioEffect.h rename to extensions/src/ACRE2Core/RadioEffectAnalogue.h index c4a86c55e..2781d7cc2 100644 --- a/extensions/src/ACRE2Core/RadioEffect.h +++ b/extensions/src/ACRE2Core/RadioEffectAnalogue.h @@ -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); -}; \ No newline at end of file +}; diff --git a/extensions/src/ACRE2Core/RadioEffectCVSD.cpp b/extensions/src/ACRE2Core/RadioEffectCVSD.cpp new file mode 100644 index 000000000..cc5a3abd3 --- /dev/null +++ b/extensions/src/ACRE2Core/RadioEffectCVSD.cpp @@ -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("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); +} diff --git a/extensions/src/ACRE2Core/RadioEffectCVSD.h b/extensions/src/ACRE2Core/RadioEffectCVSD.h new file mode 100644 index 000000000..7c01e659b --- /dev/null +++ b/extensions/src/ACRE2Core/RadioEffectCVSD.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include + +#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 lookback_register; + const std::size_t lookback = 3; + const short delta_min = 256; + 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, 1> input_filter; + Dsp::SimpleFilter, 1> output_filter; + +public: + CRadioEffectCVSD(); + + static float quality_to_ber(float signalQuality); + void process(short* samples, int sampleCount); +}; diff --git a/extensions/src/ACRE2Core/SoundMixdownEffect.h b/extensions/src/ACRE2Core/SoundMixdownEffect.h index b46d67bda..742f82d3b 100644 --- a/extensions/src/ACRE2Core/SoundMixdownEffect.h +++ b/extensions/src/ACRE2Core/SoundMixdownEffect.h @@ -4,15 +4,17 @@ #include "Lockable.h" #include #include +#include #include class CSoundMixdownEffect : public CLockable { private: - concurrency::concurrent_unordered_map paramMap; + concurrency::concurrent_unordered_map 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]; }; -}; \ No newline at end of file + void setParam(std::string paramName, std::any value) { paramMap[paramName] = value; }; + template + T getParam(std::string paramName) { return std::any_cast(paramMap[paramName]); }; +}; diff --git a/extensions/src/ACRE2Core/SoundMixer.cpp b/extensions/src/ACRE2Core/SoundMixer.cpp index 49df083fa..1107bc1b7 100644 --- a/extensions/src/ACRE2Core/SoundMixer.cpp +++ b/extensions/src/ACRE2Core/SoundMixer.cpp @@ -87,7 +87,7 @@ 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; @@ -95,14 +95,14 @@ void CSoundMixer::mixDown(short* samples, int sampleCount, int channels, const u 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; -}; \ No newline at end of file +}; diff --git a/extensions/src/ACRE2Core/SoundMixer.h b/extensions/src/ACRE2Core/SoundMixer.h index 4e5df254c..43113dc0f 100644 --- a/extensions/src/ACRE2Core/SoundMixer.h +++ b/extensions/src/ACRE2Core/SoundMixer.h @@ -18,7 +18,7 @@ class CSoundMixer : public CLockable { bool acquireChannel(CSoundChannelMono **returnChannel, int bufferSize); bool acquireChannel(CSoundChannelMono **returnChannel, int bufferSize, bool singleShot); - bool releaseChannel(CSoundChannelMono *releaseChannel); + bool releaseChannel(CSoundChannelMono **releaseChannel); void mixDown(short* samples, int sampleCount, int channels, const unsigned int speakerMask); -}; \ No newline at end of file +}; diff --git a/extensions/src/ACRE2Core/SoundMonoChannel.cpp b/extensions/src/ACRE2Core/SoundMonoChannel.cpp index 29600d480..00cad1f7f 100644 --- a/extensions/src/ACRE2Core/SoundMonoChannel.cpp +++ b/extensions/src/ACRE2Core/SoundMonoChannel.cpp @@ -1,14 +1,17 @@ #include "SoundMonoChannel.h" -#include "RadioEffect.h" +#include "RadioEffectAnalogue.h" +#include "RadioEffectCVSD.h" #include "VolumeEffect.h" #include "BabbelEffect.h" #include "PositionalMixdownEffect.h" #include "Log.h" void CSoundChannelMono::init( int length, bool singleShot ) { - for (int i = 0; i < 8; ++i) { + for (int i = 0; i < effects.size(); ++i) { this->effects[i] = NULL; + } + for (int i = 0; i < mixdownEffects.size(); ++i) { this->mixdownEffects[i] = NULL; } this->bufferMaxSize = length; @@ -36,7 +39,7 @@ CSoundChannelMono::~CSoundChannelMono() { delete this->buffer; this->buffer = NULL; } - for (int i = 0; i < 8; ++i) { + for (int i = 0; i < effects.size(); ++i) { if (effects[i]) delete effects[i]; if (mixdownEffects[i]) @@ -68,15 +71,18 @@ int CSoundChannelMono::Out(short *samples, int sampleCount) { } CSoundMonoEffect * CSoundChannelMono::setEffectInsert(int index, std::string type) { - if (index > 7) + if (index >= effects.size()) return NULL; if (this->effects[index]) delete this->effects[index]; if (type == "acre_volume") { this->effects[index] = new CVolumeEffect(); return this->effects[index]; - } else if (type == "acre_radio") { - this->effects[index] = new CRadioEffect(); + } else if (type == "acre_radio_analogue") { + this->effects[index] = new CRadioEffectAnalogue(); + return this->effects[index]; + } else if (type == "acre_radio_cvsd") { + this->effects[index] = new CRadioEffectCVSD(); return this->effects[index]; } else if (type == "acre_babbel") { this->effects[index] = new CBabbelEffect(); @@ -85,20 +91,24 @@ CSoundMonoEffect * CSoundChannelMono::setEffectInsert(int index, std::string typ } CSoundMonoEffect * CSoundChannelMono::getEffectInsert(int index) { - if (index > 7) + if (index >= effects.size()) return NULL; return this->effects[index]; } void CSoundChannelMono::clearEffectInsert(int index) { - if (index > 7) + if (index >= effects.size()) return; if (this->effects[index]) delete this->effects[index]; } +std::size_t CSoundChannelMono::maxEffectInserts() { + return this->effects.size(); +} + CSoundMixdownEffect * CSoundChannelMono::setMixdownEffectInsert(int index, std::string type) { - if (index > 7) + if (index >= mixdownEffects.size()) return NULL; if (this->mixdownEffects[index]) delete this->mixdownEffects[index]; @@ -110,7 +120,18 @@ CSoundMixdownEffect * CSoundChannelMono::setMixdownEffectInsert(int index, std:: } CSoundMixdownEffect * CSoundChannelMono::getMixdownEffectInsert(int index) { - if (index > 7) + if (index >= mixdownEffects.size()) return NULL; return this->mixdownEffects[index]; -} \ No newline at end of file +} + +void CSoundChannelMono::clearMixdownEffectInsert(int index) { + if (index >= mixdownEffects.size()) + return; + if (this->mixdownEffects[index]) + delete this->mixdownEffects[index]; +} + +std::size_t CSoundChannelMono::maxMixdownEffectInserts() { + return this->mixdownEffects.size(); +} diff --git a/extensions/src/ACRE2Core/SoundMonoChannel.h b/extensions/src/ACRE2Core/SoundMonoChannel.h index 907197874..33b91564c 100644 --- a/extensions/src/ACRE2Core/SoundMonoChannel.h +++ b/extensions/src/ACRE2Core/SoundMonoChannel.h @@ -31,9 +31,14 @@ class CSoundChannelMono : public CLockable { int Out(short *samples, int sampleCount); int GetCurrentBufferSize() { return this->bufferLength-this->bufferPos; }; bool IsOneShot() { return this->oneShot; }; + CSoundMonoEffect * setEffectInsert(int index, std::string type); CSoundMonoEffect * getEffectInsert(int index); + void clearEffectInsert(int index); + std::size_t maxEffectInserts(); + CSoundMixdownEffect * setMixdownEffectInsert(int index, std::string type); CSoundMixdownEffect * getMixdownEffectInsert(int index); - void clearEffectInsert(int index); + void clearMixdownEffectInsert(int index); + std::size_t maxMixdownEffectInserts(); }; diff --git a/extensions/src/ACRE2Core/SoundMonoEffect.h b/extensions/src/ACRE2Core/SoundMonoEffect.h index 881d066ae..0a4dd000c 100644 --- a/extensions/src/ACRE2Core/SoundMonoEffect.h +++ b/extensions/src/ACRE2Core/SoundMonoEffect.h @@ -4,21 +4,23 @@ #include "Lockable.h" #include #include +#include #include class CSoundMonoEffect : public CLockable { private: - concurrency::concurrent_unordered_map paramMap; + concurrency::concurrent_unordered_map paramMap; public: CSoundMonoEffect() { }; ~CSoundMonoEffect() { }; virtual void process(short *samples, int sampleCount) = 0; - void setParam(std::string paramName, float value) { paramMap[paramName] = value; }; - float getParam(std::string paramName) { + void setParam(std::string paramName, std::any value) { paramMap[paramName] = value; }; + template + T getParam(std::string paramName) { if (paramMap.find(paramName) != paramMap.end()) { - return paramMap[paramName]; + return std::any_cast(paramMap[paramName]); } else { - return 0.0f; + return T(); } }; -}; \ No newline at end of file +}; diff --git a/extensions/src/ACRE2Core/SoundPlayback.cpp b/extensions/src/ACRE2Core/SoundPlayback.cpp index 9ddd09085..8d1ab7660 100644 --- a/extensions/src/ACRE2Core/SoundPlayback.cpp +++ b/extensions/src/ACRE2Core/SoundPlayback.cpp @@ -72,10 +72,10 @@ acre::Result CSoundPlayback::playSound(std::string id, acre::vec3_fp32_t positio tempChannel->getMixdownEffectInsert(0)->setParam("headVectorZ", direction.z); if (isWorld) { - tempChannel->getMixdownEffectInsert(0)->setParam("isWorld", 0x00000001); - tempChannel->getMixdownEffectInsert(0)->setParam("speakingType", static_cast(acre::Speaking::radio)); + tempChannel->getMixdownEffectInsert(0)->setParam("isWorld", true); + tempChannel->getMixdownEffectInsert(0)->setParam("speakingType", acre::Speaking::radio); } else { - tempChannel->getMixdownEffectInsert(0)->setParam("isWorld", 0x00000000); + tempChannel->getMixdownEffectInsert(0)->setParam("isWorld", false); } tempChannel->In((short *)waveFile.GetData(), waveFile.GetSize()/sizeof(short)); diff --git a/extensions/src/ACRE2Core/VolumeEffect.h b/extensions/src/ACRE2Core/VolumeEffect.h index e8633a0e8..bdfc4f6ed 100644 --- a/extensions/src/ACRE2Core/VolumeEffect.h +++ b/extensions/src/ACRE2Core/VolumeEffect.h @@ -15,7 +15,13 @@ class CVolumeEffect : public CSoundMonoEffect { }; void process(short *samples, int sampleCount) { - this->volumeFilter.process(samples, sampleCount, 1, static_cast(this->getParam("volume")), static_cast(this->getParam("previousVolume"))); - this->setParam("previousVolume", this->getParam("volume")); + this->volumeFilter.process( + samples, + sampleCount, + 1, + this->getParam("volume"), + this->getParam("previousVolume")); + + this->setParam("previousVolume", this->getParam("volume")); }; }; diff --git a/extensions/src/ACRE2Core/updateSpeakingData.h b/extensions/src/ACRE2Core/updateSpeakingData.h index c29328f45..52f8f2f30 100644 --- a/extensions/src/ACRE2Core/updateSpeakingData.h +++ b/extensions/src/ACRE2Core/updateSpeakingData.h @@ -34,113 +34,141 @@ RPC_FUNCTION(updateSpeakingData) { if (speaker) { LOCK(speaker); if ((speakingType == "d") || (speakingType == "i") || (speakingType == "z")) { - if (((speaker->getInitType() != "d") && (speaker->getInitType() != "i") && (speaker->getInitType() != "z")) || speakingType != speaker->getInitType()) { + CSoundChannelMono *channel = speaker->channels[0]; + if (speaker->getInitType() != speakingType) { speaker->setInitType(speakingType); - if (speaker->channels[0]) { - CEngine::getInstance()->getSoundEngine()->getSoundMixer()->releaseChannel(speaker->channels[0]); - speaker->channels[0] = NULL; - } - CEngine::getInstance()->getSoundEngine()->getSoundMixer()->acquireChannel(&speaker->channels[0], 4800, false); + reinitChannel(&(speaker->channels[0])); + channel = speaker->channels[0]; + if (speaksBabbel) { - speaker->channels[0]->setEffectInsert(0, "acre_babbel"); + channel->setEffectInsert(0, "acre_babbel"); } - speaker->channels[0]->setMixdownEffectInsert(0, "acre_positional"); - speaker->channels[0]->getMixdownEffectInsert(0)->setParam("speakingType", static_cast(acre::Speaking::direct)); - speaker->setSpeakingType(acre::Speaking::direct); + + channel->setEffectInsert(channel->maxEffectInserts() - 1, "acre_volume"); + channel->setMixdownEffectInsert(0, "acre_positional"); + + acre::Speaking speakingTypeEnum; if (speakingType == "i") { - speaker->channels[0]->setEffectInsert(2, "acre_radio"); - speaker->channels[0]->getEffectInsert(2)->setParam("disableNoise", true); - speaker->channels[0]->getEffectInsert(2)->setParam("signalQuality", 1.0f); - speaker->channels[0]->getMixdownEffectInsert(0)->setParam("speakingType", static_cast(acre::Speaking::intercom)); - speaker->setSpeakingType(acre::Speaking::intercom); + channel->setEffectInsert(2, "acre_radio_analogue"); + channel->getEffectInsert(2)->setParam("disableNoise", true); + channel->getEffectInsert(2)->setParam("signalQuality", 1.0f); + speakingTypeEnum = acre::Speaking::intercom; + } else { + speakingTypeEnum = acre::Speaking::direct; } - speaker->channels[0]->setEffectInsert(7, "acre_volume"); + speaker->setSpeakingType(speakingTypeEnum); + channel->getMixdownEffectInsert(0)->setParam("speakingType", speakingTypeEnum); } - if (speaker->channels[0]) { - speaker->channels[0]->getEffectInsert(7)->setParam("volume", vMessage->getParameterAsFloat(3)); - - speaker->channels[0]->getMixdownEffectInsert(0)->setParam("speakerPosX", vMessage->getParameterAsFloat(4)); - speaker->channels[0]->getMixdownEffectInsert(0)->setParam("speakerPosZ", vMessage->getParameterAsFloat(5)); - speaker->channels[0]->getMixdownEffectInsert(0)->setParam("speakerPosY", vMessage->getParameterAsFloat(6)); - - speaker->channels[0]->getMixdownEffectInsert(0)->setParam("headVectorX", vMessage->getParameterAsFloat(7)); - speaker->channels[0]->getMixdownEffectInsert(0)->setParam("headVectorZ", vMessage->getParameterAsFloat(8)); - speaker->channels[0]->getMixdownEffectInsert(0)->setParam("headVectorY", vMessage->getParameterAsFloat(9)); - - speaker->channels[0]->getMixdownEffectInsert(0)->setParam("curveScale", speaker->getSelectableCurveScale()); + if (channel) { + channel->getEffectInsert(channel->maxEffectInserts() - 1)->setParam("volume", vMessage->getParameterAsFloat(3)); + + CSoundMixdownEffect *positionalEffect = channel->getMixdownEffectInsert(0); + positionalEffect->setParam("speakerPosX", vMessage->getParameterAsFloat(4)); + positionalEffect->setParam("speakerPosZ", vMessage->getParameterAsFloat(5)); + positionalEffect->setParam("speakerPosY", vMessage->getParameterAsFloat(6)); + positionalEffect->setParam("vectorHeadX", vMessage->getParameterAsFloat(7)); + positionalEffect->setParam("vectorHeadZ", vMessage->getParameterAsFloat(8)); + positionalEffect->setParam("vectorHeadY", vMessage->getParameterAsFloat(9)); + positionalEffect->setParam("curveScale", speaker->getSelectableCurveScale()); } } else if (speakingType == "r") { // Unmute them here. Dynamically muting and unmuting them when they transmit on a radio // cuts down on bandwidth and complexity. Now there is no more need to transmit lists of // radios or transfer radio ownerships around. Speakers just use whatever radio ID they // want. - const int32_t count = vMessage->getParameterAsInt(3); speaker->setSpeakingType(acre::Speaking::radio); if (CEngine::getInstance()->getClient()->getMuted(playerId) == acre::Result::ok) { CEngine::getInstance()->getClient()->setMuted(playerId, false); } + + const int32_t count = vMessage->getParameterAsInt(3); + const float32_t volume = vMessage->getParameterAsFloat(4); + const float32_t signalQuality = vMessage->getParameterAsFloat(5); + for (int32_t i = 0; i < count; ++i) { const int32_t channelId = i + 1; - if (!speaker->channels[channelId]) { - CEngine::getInstance()->getSoundEngine()->getSoundMixer()->acquireChannel(&speaker->channels[channelId], 4800, false); + CSoundChannelMono *channel = speaker->channels[channelId]; + + std::string modulation; + if (vMessage->getParameter(11 + i*8)) { + modulation = reinterpret_cast(vMessage->getParameter(11 + i*8)); + } else { + modulation = "FM"; + DEBUG("Warning, no modulation type provided by ArmA plugin. Falling back to FM."); + } + + if (!channel) { + CEngine::getInstance()->getSoundEngine()->getSoundMixer()->acquireChannel(&(speaker->channels[channelId]), 4800, false); + channel = speaker->channels[channelId]; + if (speaksBabbel) - speaker->channels[channelId]->setEffectInsert(0, "acre_babbel"); - speaker->channels[channelId]->setEffectInsert(7, "acre_volume"); - speaker->channels[channelId]->getEffectInsert(7)->setParam("volume", vMessage->getParameterAsFloat(4)); + channel->setEffectInsert(0, "acre_babbel"); - speaker->channels[channelId]->setEffectInsert(2, "acre_radio"); - speaker->channels[channelId]->getEffectInsert(2)->setParam("disableNoise", false); - speaker->channels[channelId]->getEffectInsert(2)->setParam("signalQuality", vMessage->getParameterAsFloat(5)); + channel->setEffectInsert(channel->maxEffectInserts() - 1, "acre_volume"); + channel->getEffectInsert(channel->maxEffectInserts() - 1)->setParam("volume", volume); - speaker->channels[channelId]->setMixdownEffectInsert(0, "acre_positional"); - } + if (modulation == "CVSD") { + channel->setEffectInsert(2, "acre_radio_cvsd"); + } else { + channel->setEffectInsert(2, "acre_radio_analogue"); + channel->getEffectInsert(2)->setParam("disableNoise", false); + channel->getEffectInsert(2)->setParam("signalQuality", signalQuality); - if (speaker->channels[channelId]) { - speaker->channels[channelId]->getMixdownEffectInsert(0)->setParam("speakingType", static_cast(acre::Speaking::radio)); + if (modulation != "FM" && modulation != "AM" && modulation != "NB") { + DEBUG("Warning, invalid modulation type provided. Falling back to analogue effect."); + } + } - speaker->channels[channelId]->getEffectInsert(7)->setParam("volume", vMessage->getParameterAsFloat(4 + (i * 7))); + channel->setMixdownEffectInsert(0, "acre_positional"); + channel->getMixdownEffectInsert(0)->setParam("speakingType", acre::Speaking::radio); + } - speaker->channels[channelId]->getEffectInsert(2)->setParam("disableNoise", FALSE); - speaker->channels[channelId]->getEffectInsert(2)->setParam("signalQuality", vMessage->getParameterAsFloat(5 + (i * 7))); - speaker->channels[channelId]->getEffectInsert(2)->setParam("signalModel", vMessage->getParameterAsFloat(6 + (i * 7))); + if (channel) { + channel->getEffectInsert(channel->maxEffectInserts() - 1)->setParam("volume", vMessage->getParameterAsFloat(4 + (i * 8))); - speaker->channels[channelId]->getMixdownEffectInsert(0)->setParam("isLoudSpeaker", vMessage->getParameterAsFloat(7 + (i * 7))); - if (vMessage->getParameterAsFloat(7 + (i * 7))) { - speaker->channels[channelId]->getMixdownEffectInsert(0)->setParam("isWorld", 0x00000001); + if (modulation == "CVSD") { + channel->getEffectInsert(2)->setParam("signalQuality", vMessage->getParameterAsFloat(5 + (i * 8))); } else { - speaker->channels[channelId]->getMixdownEffectInsert(0)->setParam("isWorld", 0x00000000); + channel->getEffectInsert(2)->setParam("disableNoise", false); + channel->getEffectInsert(2)->setParam("signalQuality", vMessage->getParameterAsFloat(5 + (i * 8))); + channel->getEffectInsert(2)->setParam("signalModel", vMessage->getParameterAsFloat(6 + (i * 8))); } - speaker->channels[channelId]->getMixdownEffectInsert(0)->setParam("speakerPosX", vMessage->getParameterAsFloat(8 + (i * 7))); - speaker->channels[channelId]->getMixdownEffectInsert(0)->setParam("speakerPosZ", vMessage->getParameterAsFloat(9 + (i * 7))); - speaker->channels[channelId]->getMixdownEffectInsert(0)->setParam("speakerPosY", vMessage->getParameterAsFloat(10 + (i * 7))); - - speaker->channels[channelId]->getMixdownEffectInsert(0)->setParam("headVectorX", 0.0f); - speaker->channels[channelId]->getMixdownEffectInsert(0)->setParam("headVectorZ", 1.0f); - speaker->channels[channelId]->getMixdownEffectInsert(0)->setParam("headVectorY", 0.0f); - speaker->channels[channelId]->getMixdownEffectInsert(0)->setParam("curveScale", speaker->getSelectableCurveScale()); + const bool isLoudSpeaker = vMessage->getParameterAsInt(7 + (i * 8)); + channel->getMixdownEffectInsert(0)->setParam("isLoudSpeaker", isLoudSpeaker); + channel->getMixdownEffectInsert(0)->setParam("isWorld", isLoudSpeaker); + + CSoundMixdownEffect *positionalEffect = channel->getMixdownEffectInsert(0); + positionalEffect->setParam("speakerPosX", vMessage->getParameterAsFloat(8 + (i * 8))); + positionalEffect->setParam("speakerPosZ", vMessage->getParameterAsFloat(9 + (i * 8))); + positionalEffect->setParam("speakerPosY", vMessage->getParameterAsFloat(10 + (i * 8))); + positionalEffect->setParam("vectorHeadX", 0.0f); + positionalEffect->setParam("vectorHeadZ", 1.0f); + positionalEffect->setParam("vectorHeadY", 0.0f); + positionalEffect->setParam("curveScale", speaker->getSelectableCurveScale()); } } } else if (speakingType == "s" || speakingType == "g") { - if ((speaker->getInitType() != "s" && speaker->getInitType() != "g") || speakingType != speaker->getInitType()) { + CSoundChannelMono *channel = speaker->channels[0]; + if (speakingType != speaker->getInitType()) { speaker->setInitType(speakingType); - if (speaker->channels[0]) { - CEngine::getInstance()->getSoundEngine()->getSoundMixer()->releaseChannel(speaker->channels[0]); - speaker->channels[0] = NULL; - } - CEngine::getInstance()->getSoundEngine()->getSoundMixer()->acquireChannel(&speaker->channels[0], 4800, false); - speaker->channels[0]->setEffectInsert(7, "acre_volume"); + reinitChannel(&(speaker->channels[0])); + channel = speaker->channels[0]; + channel->setEffectInsert(channel->maxEffectInserts() - 1, "acre_volume"); } + if (speakingType == "s") { speaker->setSpeakingType(acre::Speaking::spectate); } else { speaker->setSpeakingType(acre::Speaking::god); } + if (CEngine::getInstance()->getClient()->getMuted(playerId) == acre::Result::ok) { CEngine::getInstance()->getClient()->setMuted(playerId, false); } - if (speaker->channels[0]) { - speaker->channels[0]->getEffectInsert(7)->setParam("volume", vMessage->getParameterAsFloat(3)); + + if (channel) { + channel->getEffectInsert(channel->maxEffectInserts() - 1)->setParam("volume", vMessage->getParameterAsFloat(3)); } } else { speaker->setSpeakingType(acre::Speaking::unknown); @@ -150,6 +178,14 @@ RPC_FUNCTION(updateSpeakingData) { CEngine::getInstance()->getSoundEngine()->getSoundMixer()->unlock(); return acre::Result::ok; } +private: + void reinitChannel(CSoundChannelMono **channel) { + if (*channel) { + CEngine::getInstance()->getSoundEngine()->getSoundMixer()->releaseChannel(channel); + } + CEngine::getInstance()->getSoundEngine()->getSoundMixer()->acquireChannel(channel, 4800, false); + }; + public: inline void setName(const char *const value) final { m_Name = value; } inline const char* getName() const final { return m_Name; }