Skip to content

Commit

Permalink
Plugin: refactored audio filters to be model specific
Browse files Browse the repository at this point in the history
The previously global audio filters are now part of the radio model.
Now we can define different audio filter characteristics for different radio models.
This also made the mumble_onAudioSourceFetched a little less long and a little more generic.

Fix #122
Helps with #58
  • Loading branch information
hbeni committed Aug 30, 2021
1 parent 2d9f25b commit 952f556
Show file tree
Hide file tree
Showing 10 changed files with 175 additions and 94 deletions.
10 changes: 5 additions & 5 deletions client/mumble-plugin/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,13 @@ libs: $(lib_OBJS)

# Compile testing tools
tools: libs
$(CC) -o test/geotest lib/radio_model.o test/geotest.cpp $(CFLAGS)
$(CC) -o test/frqtest lib/radio_model.o test/frqtest.cpp $(CFLAGS)
$(CC) -o test/geotest lib/radio_model.o lib/audio.o test/geotest.cpp $(CFLAGS)
$(CC) -o test/frqtest lib/radio_model.o lib/audio.o test/frqtest.cpp $(CFLAGS)


# catch2 unit tests linking against main
test: libs test/catch2/tests-main.o test/catch2/tests-main.o test/catch2/radioModelTest.o
$(CC) -o test/catch2/radioModelTest.catch2 test/catch2/tests-main.o lib/radio_model.o test/catch2/radioModelTest.o $(CFLAGS) && test/catch2/radioModelTest.catch2
$(CC) -o test/catch2/radioModelTest.catch2 test/catch2/tests-main.o lib/radio_model.o lib/audio.o test/catch2/radioModelTest.o $(CFLAGS) && test/catch2/radioModelTest.catch2
# ^ add more

# clean compile results
Expand All @@ -93,8 +93,8 @@ all-win: plugin-win64 tools-win64 clean
# build win64 test tools
tools-win64: CC=x86_64-w64-mingw32-g++-posix
tools-win64:
$(CC) -o test/geotest.exe lib/radio_model.cpp test/geotest.cpp -static-libgcc -static-libstdc++ $(CFLAGS)
$(CC) -o test/frqtest.exe lib/radio_model.cpp test/frqtest.cpp -static-libgcc -static-libstdc++ $(CFLAGS)
$(CC) -o test/geotest.exe lib/radio_model.cpp lib/audio.cpp test/geotest.cpp -static-libgcc -static-libstdc++ $(CFLAGS)
$(CC) -o test/frqtest.exe lib/radio_model.cpp lib/audio.cpp test/frqtest.cpp -static-libgcc -static-libstdc++ $(CFLAGS)

# build win64 plugin-dll and openssl
plugin-win64: openssl-win plugin-win64-only
Expand Down
47 changes: 12 additions & 35 deletions client/mumble-plugin/fgcom-mumble.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
#include "io_UDPServer.h"
#include "io_UDPClient.h"
#include "radio_model.h"
#include "audio.h"
#include "garbage_collector.h"

#include <stdio.h>
Expand Down Expand Up @@ -947,7 +946,6 @@ bool mumble_onAudioSourceFetched(float *outputPCM, uint32_t sampleCount, uint16_

float bestSignalStrength = -1.0; // we want to get the connections signal strength.
fgcom_radio matchedLocalRadio;
bool isLandline = false;
bool useRawData = false;

// Fetch the remote clients data
Expand Down Expand Up @@ -994,7 +992,9 @@ bool mumble_onAudioSourceFetched(float *outputPCM, uint32_t sampleCount, uint16_
// skip check for "empty radios"
if (lcl.radios[lri].frequency == "") continue;

// calculate frequency match
// Calculate radio type compatibility and basic frequency match.
// (this is expected to return signalMatchFilter==0 when the model is compatible,
// but no connection can be made based on tuned frequency; including half-duplex checks etc)
float signalMatchFilter;
if (radio_model_lcl->isCompatible(radio_model_rmt.get())) {
signalMatchFilter = radio_model_lcl->getFrqMatch(lcl.radios[lri], rmt.radios[ri]);
Expand All @@ -1004,24 +1004,9 @@ bool mumble_onAudioSourceFetched(float *outputPCM, uint32_t sampleCount, uint16_
continue;
}


/* detect landline/intercom */
if (lcl.radios[lri].frequency.substr(0, 5) == "PHONE"
&& lcl.radios[lri].frequency == rmt.radios[ri].frequency && lcl.radios[lri].operable) {
pluginDbg("mumble_onAudioSourceFetched(): local_radio="+std::to_string(lri)+" PHONE mode detected");
// Best quality, full-duplex mode
matchedLocalRadio = lcl.radios[lri];
bestSignalStrength = 1.0;
isLandline = true;
break; // no point in searching more


/* normal radio operation */
// (prefixed special frequencies never should be recieved!)
} else if (signalMatchFilter > 0.0
&& lcl.radios[lri].operable
&& !lcl.radios[lri].ptt // halfduplex!
&& rmt_frq_p.prefix.length() == 0) {
// See if a signal can be received for this radio pair; and if yes, how good it is.
// (only consider non-prefixed remote radio frequencies, as they are special)
if (signalMatchFilter > 0.0 && rmt_frq_p.prefix.length() == 0) {
pluginDbg("mumble_onAudioSourceFetched(): local_radio="+std::to_string(lri)+" frequency "+lcl.radios[lri].frequency+" matches!");
// we are listening on that frequency!
// determine signal strenght for this connection
Expand Down Expand Up @@ -1118,21 +1103,13 @@ bool mumble_onAudioSourceFetched(float *outputPCM, uint32_t sampleCount, uint16_
// we should use the raw audio packets unaffected
pluginDbg("mumble_onAudioSourceFetched(): connected (use raw data)");
rv = false;

} else if (isLandline) {
// we got a landline connection!
pluginDbg("mumble_onAudioSourceFetched(): connected (phone)");
fgcom_audio_makeMono(outputPCM, sampleCount, channelCount);
if (fgcom_cfg.radioAudioEffects) fgcom_audio_filter(bestSignalStrength, outputPCM, sampleCount, channelCount, sampleRate);
fgcom_audio_applyVolume(matchedLocalRadio.volume, outputPCM, sampleCount, channelCount);

} else if (bestSignalStrength > 0.0) {

} else if (bestSignalStrength > 0.0) {
// we got a connection!
pluginDbg("mumble_onAudioSourceFetched(): connected, bestSignalStrength="+std::to_string(bestSignalStrength));
fgcom_audio_makeMono(outputPCM, sampleCount, channelCount);
if (fgcom_cfg.radioAudioEffects) fgcom_audio_filter(bestSignalStrength, outputPCM, sampleCount, channelCount, sampleRate);
if (fgcom_cfg.radioAudioEffects) fgcom_audio_addNoise(bestSignalStrength, outputPCM, sampleCount, channelCount);
fgcom_audio_applyVolume(matchedLocalRadio.volume, outputPCM, sampleCount, channelCount);
std::unique_ptr<FGCom_radiowaveModel> radio_model_lcl(FGCom_radiowaveModel::selectModel(matchedLocalRadio.frequency));
pluginDbg("mumble_onAudioSourceFetched(): connected (lcl_type="+radio_model_lcl->getType()+"), bestSignalStrength="+std::to_string(bestSignalStrength));
if (fgcom_cfg.radioAudioEffects)
radio_model_lcl->processAudioSamples(matchedLocalRadio, bestSignalStrength, outputPCM, sampleCount, channelCount, sampleRate);

} else {
pluginDbg("mumble_onAudioSourceFetched(): no connection, bestSignalStrength="+std::to_string(bestSignalStrength));
Expand Down
66 changes: 21 additions & 45 deletions client/mumble-plugin/lib/audio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,25 +36,7 @@
/**
* Following functions are called from plugin code
*/
void fgcom_audio_addNoise(float signalQuality, float *outputPCM, uint32_t sampleCount, uint16_t channelCount) {

// Calculate volume levels:
// We want the noise to get louder at bad signal quality and the signal to get weaker.
// basicly the signal quality already tells us how the ratio is between signal and noise.
float signalVolume;
float noiseVolume;
float minimumNoiseVolume = 0.05;
float maximumNoiseVolume = 0.45;
signalVolume = signalQuality;
noiseVolume = pow(0.9 - 0.9*signalQuality, 2) + minimumNoiseVolume;
if (noiseVolume > maximumNoiseVolume) noiseVolume = maximumNoiseVolume;

// Now tune down the signal according to calculated volume level
fgcom_audio_applyVolume(signalVolume, outputPCM, sampleCount, channelCount);

// TODO: we may clip some random samples from the signal on low quality

// Apply noise to the signal
void fgcom_audio_addNoise(float noiseVolume, float *outputPCM, uint32_t sampleCount, uint16_t channelCount) {
PinkNoise fgcom_PinkSource;
InitializePinkNoise(&fgcom_PinkSource, 12); // Init new PinkNoise source with num of rows
for (uint32_t s=0; s<channelCount*sampleCount; s++) {
Expand Down Expand Up @@ -94,7 +76,7 @@ void fgcom_audio_makeMono(float *outputPCM, uint32_t sampleCount, uint16_t chann
}


void fgcom_audio_filter(float signalQuality, float *outputPCM, uint32_t sampleCount, uint16_t channelCount, uint32_t sampleRateHz) {
void fgcom_audio_filter(int highpass_cutoff, int lowpass_cutoff, float *outputPCM, uint32_t sampleCount, uint16_t channelCount, uint32_t sampleRateHz) {

// Ok, first some assupmtions to save runtime:
// - we are assuming a mono stream, ie. all channels contain the same float values already!
Expand Down Expand Up @@ -124,32 +106,26 @@ void fgcom_audio_filter(float signalQuality, float *outputPCM, uint32_t sampleCo
const int fadeOverNumSamples = 1024; // fade changes in parameters over that much samples

// HighPass filter cuts away lower frequency ranges and let higher ones pass
// Lower cutoff limit depends on signal quality: the less quality, the more to cut away
// worst is 1000@30% signal; best is [email protected]
int highpass_cutoff = (1-signalQuality) * 1000 + 300;
if (highpass_cutoff < 300) highpass_cutoff = 300; // lower ceiling
if (highpass_cutoff > 1000) highpass_cutoff = 1000; // upside ceiling
std::unique_ptr<Dsp::Filter> f_highpass(new Dsp::SmoothedFilterDesign <Dsp::RBJ::Design::HighPass, 1> (fadeOverNumSamples));
Dsp::Params f_highpass_p;
f_highpass_p[0] = sampleRateHz; // sample rate
f_highpass_p[1] = highpass_cutoff; // cutoff frequency
f_highpass_p[2] = 2.0; // Q
f_highpass->setParams (f_highpass_p);
f_highpass->process (sampleCount, audioData);

if (highpass_cutoff > 0 ) {
std::unique_ptr<Dsp::Filter> f_highpass(new Dsp::SmoothedFilterDesign <Dsp::RBJ::Design::HighPass, 1> (fadeOverNumSamples));
Dsp::Params f_highpass_p;
f_highpass_p[0] = sampleRateHz; // sample rate
f_highpass_p[1] = highpass_cutoff; // cutoff frequency
f_highpass_p[2] = 2.0; // Q
f_highpass->setParams (f_highpass_p);
f_highpass->process (sampleCount, audioData);
}

// LowPass filter cuts away higher frequency ranges and lets lower ones pass
// worst is 2000@035; best is [email protected]
int lowpass_cutoff = signalQuality * 4000 + 500;
if (lowpass_cutoff < 2000) lowpass_cutoff = 2000; // lower ceiling
if (lowpass_cutoff > 4000) lowpass_cutoff = 4000; // upper ceiling
std::unique_ptr<Dsp::Filter> f_lowpass(new Dsp::SmoothedFilterDesign <Dsp::RBJ::Design::LowPass, 1> (fadeOverNumSamples));
Dsp::Params f_lowpass_p;
f_lowpass_p[0] = sampleRateHz; // sample rate
f_lowpass_p[1] = lowpass_cutoff; // cutoff frequency
f_lowpass_p[2] = 0.97; // Q
f_lowpass->setParams (f_lowpass_p);
f_lowpass->process (sampleCount, audioData);

if (lowpass_cutoff > 0 ) {
std::unique_ptr<Dsp::Filter> f_lowpass(new Dsp::SmoothedFilterDesign <Dsp::RBJ::Design::LowPass, 1> (fadeOverNumSamples));
Dsp::Params f_lowpass_p;
f_lowpass_p[0] = sampleRateHz; // sample rate
f_lowpass_p[1] = lowpass_cutoff; // cutoff frequency
f_lowpass_p[2] = 0.97; // Q
f_lowpass->setParams (f_lowpass_p);
f_lowpass->process (sampleCount, audioData);
}

/*
* Apply filtered result to all channels (treats audio as mono!)
Expand Down
8 changes: 5 additions & 3 deletions client/mumble-plugin/lib/audio.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
/*
* Add static noise to the signal
*
* @param float signalQuality 0.0 to 1.0 for signal quality
* @param float noiseVolume 0.0 to 1.0 for normalized volume
*/
void fgcom_audio_addNoise(float signalQuality, float *outputPCM, uint32_t sampleCount, uint16_t channelCount);
void fgcom_audio_addNoise(float noiseVolume, float *outputPCM, uint32_t sampleCount, uint16_t channelCount);


/*
Expand All @@ -42,7 +42,9 @@ void fgcom_audio_makeMono(float *outputPCM, uint32_t sampleCount, uint16_t chann

/*
* Apply audio filter
*
* If highpass_cutoff / lowpass_cutoff == 0, then the respective filter is skipped
*/
void fgcom_audio_filter(float signalQuality, float *outputPCM, uint32_t sampleCount, uint16_t channelCount, uint32_t sampleRateHz);
void fgcom_audio_filter(int highpass_cutoff, int lowpass_cutoff, float *outputPCM, uint32_t sampleCount, uint16_t channelCount, uint32_t sampleRateHz);

#endif
4 changes: 2 additions & 2 deletions client/mumble-plugin/lib/radio_model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@
#include "radio_model.h"

// include concrete implementations
#include "radio_model_string.cpp"
#include "radio_model_hf.cpp"
#include "radio_model_vhf.cpp"
#include "radio_model_hf.cpp"
#include "radio_model_uhf.cpp"
#include "radio_model_string.cpp"


/*
Expand Down
15 changes: 13 additions & 2 deletions client/mumble-plugin/lib/radio_model.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,13 +157,24 @@ class FGCom_radiowaveModel {
* See how good frequencies of two radios align.
*
* This method may call getChannelAlignment() for convinience.
* The method should also consider if the r1 radio is operable and also
* possible half-duplex modes.
*
* @param r1 first radio
* @param r2 second radio
* @param r1 first (local) radio
* @param r2 second (remote) radio
* @return float alignment factor: 0.0=outside band, 1.0=in core region
*/
virtual float getFrqMatch(fgcom_radio r1, fgcom_radio r2) = 0;


/*
* Apply audio processing to provided samples
*
* This method processes the samples based on the radio model in use
* and considers signal quality etc.
*/
virtual void processAudioSamples(fgcom_radio lclRadio, float signalQuality, float *outputPCM, uint32_t sampleCount, uint16_t channelCount, uint32_t sampleRateHz) = 0;


/********************************************************************/
/* Abstract methods; they are usually not needed to be overloaded, */
Expand Down
13 changes: 13 additions & 0 deletions client/mumble-plugin/lib/radio_model_hf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <cmath>
#include <regex>
#include "radio_model.h"
#include "audio.h"

/**
* A HF based radio model for the FGCom-mumble plugin
Expand Down Expand Up @@ -98,6 +99,9 @@ class FGCom_radiowaveModel_HF : public FGCom_radiowaveModel {

// Frequency match is done with a band method, ie. a match is there if the bands overlap
float getFrqMatch(fgcom_radio r1, fgcom_radio r2) {
if (r1.ptt) return 0.0; // Half-duplex!
if (!r1.operable) return 0.0; // stop if radio is inoperable

// channel definition
// TODO: Note, i completely made up those numbers. I have no idea of tuning HF radios.
// TODO: I have read somewhere about 3kHz width: https://onlinelibrary.wiley.com/doi/abs/10.1002/0471208051.fre015
Expand Down Expand Up @@ -126,5 +130,14 @@ class FGCom_radiowaveModel_HF : public FGCom_radiowaveModel {
return filter;
}
}


/*
* Process audio samples
*/
void processAudioSamples(fgcom_radio lclRadio, float signalQuality, float *outputPCM, uint32_t sampleCount, uint16_t channelCount, uint32_t sampleRateHz) {
// Audio processing is like VHF characteristics for now
std::unique_ptr<FGCom_radiowaveModel_VHF> vhf_radio = std::make_unique<FGCom_radiowaveModel_VHF>();
vhf_radio->processAudioSamples(lclRadio, signalQuality, outputPCM, sampleCount, channelCount, sampleRateHz);
}
};
33 changes: 31 additions & 2 deletions client/mumble-plugin/lib/radio_model_string.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <cmath>
#include <regex>
#include "radio_model.h"
#include "audio.h"

/**
* A string based radio model for the FGCom-mumble plugin
Expand Down Expand Up @@ -52,7 +53,35 @@ class FGCom_radiowaveModel_String : public FGCom_radiowaveModel {

// frequencies match if the string is case-sensitively the same
float getFrqMatch(fgcom_radio r1, fgcom_radio r2) {
return (r1.frequency == r2.frequency)? 1.0 : 0.0 ;
if (!r1.operable) return 0.0; // stop if radio is inoperable

bool isLandline = r1.frequency.substr(0, 5) == "PHONE";

// If landline: full duplex
if (isLandline) return (r1.frequency == r2.frequency)? 1.0 : 0.0 ;

// if not: treat as half-duplex:
return (!r1.ptt && r1.frequency == r2.frequency)? 1.0 : 0.0 ;
}


/*
* Process audio samples
*/
void processAudioSamples(fgcom_radio lclRadio, float signalQuality, float *outputPCM, uint32_t sampleCount, uint16_t channelCount, uint32_t sampleRateHz) {
/*
* Check for landline, otherwise use VHF characteristics (for example to simulate private handheld PMR radio channel names)
*/
if (lclRadio.frequency.substr(0, 5) == "PHONE") {
// Telephone characteristics
fgcom_audio_makeMono(outputPCM, sampleCount, channelCount);
fgcom_audio_filter(300, 4000, outputPCM, sampleCount, channelCount, sampleRateHz);
fgcom_audio_applyVolume(lclRadio.volume, outputPCM, sampleCount, channelCount);

} else {
// VHF characteristics
std::unique_ptr<FGCom_radiowaveModel_VHF> vhf_radio = std::make_unique<FGCom_radiowaveModel_VHF>();
vhf_radio->processAudioSamples(lclRadio, signalQuality, outputPCM, sampleCount, channelCount, sampleRateHz);
}
}

};
4 changes: 4 additions & 0 deletions client/mumble-plugin/lib/radio_model_uhf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <cmath>
#include <regex>
#include "radio_model.h"
#include "audio.h"

/**
* A UHF based radio model for the FGCom-mumble plugin.
Expand Down Expand Up @@ -55,6 +56,9 @@ class FGCom_radiowaveModel_UHF : public FGCom_radiowaveModel_VHF {

// Frequency match is done with a band method, ie. a match is there if the bands overlap
float getFrqMatch(fgcom_radio r1, fgcom_radio r2) {
if (r1.ptt) return 0.0; // Half-duplex!
if (!r1.operable) return 0.0; // stop if radio is inoperable

// channel definition
// TODO: Note, i completely made up those numbers. I have no idea of tuning UHF radios.
float width_kHz = r1.channelWidth;
Expand Down
Loading

0 comments on commit 952f556

Please sign in to comment.