diff --git a/client/mumble-plugin/Makefile b/client/mumble-plugin/Makefile index 4e314929..1a352b5b 100644 --- a/client/mumble-plugin/Makefile +++ b/client/mumble-plugin/Makefile @@ -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 @@ -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 diff --git a/client/mumble-plugin/fgcom-mumble.cpp b/client/mumble-plugin/fgcom-mumble.cpp index 942601a8..52477422 100644 --- a/client/mumble-plugin/fgcom-mumble.cpp +++ b/client/mumble-plugin/fgcom-mumble.cpp @@ -31,7 +31,6 @@ #include "io_UDPServer.h" #include "io_UDPClient.h" #include "radio_model.h" -#include "audio.h" #include "garbage_collector.h" #include @@ -142,22 +141,20 @@ void fgcom_handlePTT() { for (const auto &lcl_idty : fgcom_local_client) { //int iid = lcl_idty.first; fgcom_client lcl = lcl_idty.second; - if (lcl.radios.size() > 0) { - for (long unsigned int i=0; i open mic"); - radio_ptt_result = true; - break; // we only have one output stream, so further search makes no sense - } else { - pluginLog(" COM"+std::to_string(i+1)+" PTT_REQ active but radio not operable!"); - } + for (long unsigned int i=0; i open mic"); + radio_ptt_result = true; + break; // we only have one output stream, so further search makes no sense } else { - pluginDbg(" COM"+std::to_string(i+1)+" PTT_REQ off"); + pluginLog(" COM"+std::to_string(i+1)+" PTT_REQ active but radio not operable!"); } + } else { + pluginDbg(" COM"+std::to_string(i+1)+" PTT_REQ off"); } } } @@ -206,12 +203,12 @@ void fgcom_updateClientComment() { // Add Identity and frequency information fgcom_localcfg_mtx.lock(); - if (fgcom_local_client.size() > 0) { + if (!fgcom_local_client.empty()) { for (const auto &idty : fgcom_local_client) { //int iid = idty.first; fgcom_client lcl = idty.second; std::string frqs; - if (lcl.radios.size() > 0) { + if (!lcl.radios.empty()) { for (long unsigned int i=0; i= 1) frqs += ", "; @@ -463,7 +460,7 @@ mumble_error_t fgcom_initPlugin() { } } - if (fgcom_specialChannelID.size() == 0) { + if (fgcom_specialChannelID.empty()) { pluginLog("ERROR: FAILED TO RETRIEVE SPECIAL CHANNEL '"+fgcom_cfg.specialChannel+"'! Please setup such an channel."); mumAPI.log(ownPluginID, std::string("Failed to retrieve special channel '"+fgcom_cfg.specialChannel+"'! Please setup such an channel.").c_str()); } @@ -825,10 +822,8 @@ void mumble_onUserTalkingStateChanged(mumble_connection_t connection, mumble_use for (const auto &lcl_idty : fgcom_local_client) { //int iid = lcl_idty.first; fgcom_client lcl = lcl_idty.second; - if (lcl.radios.size() > 0) { - for (long unsigned int radio_id=0; radio_id 0) { - for (long unsigned int radio_id=0; radio_idsecond : false; - pluginDbg(" IID="+std::to_string(iid)+"; radio_id="+std::to_string(radio_id)+"; operable="+std::to_string(lcl.radios[radio_id].operable)); - pluginDbg(" radio_ptt_req="+std::to_string(radio_ptt_req)); - pluginDbg(" radio_mapmumbleptt="+std::to_string(radio_mapmumbleptt)); - for (const auto& cv : fgcom_cfg.mapMumblePTT) { - pluginDbg(" mapMumblePTT["+std::to_string(cv.first)+"]="+std::to_string(cv.second)); - } - - bool oldValue = fgcom_local_client[iid].radios[radio_id].ptt; - bool newValue = false; - pluginDbg(" old_ptt="+std::to_string(oldValue)); - pluginDbg(" mumble_talk_detected="+std::to_string(mumble_talk_detected)); - if ( radio_ptt_req || (!udp_protocol_ptt_detected && radio_mapmumbleptt) ) { - // We should activate/deactivate PTT on the radio; either it's ptt was pressed in the UDP client, or we are configured for honoring mumbles talk state - newValue = mumble_talk_detected && lcl.radios[radio_id].operable; - } - pluginDbg(" new_ptt="+std::to_string(lcl.radios[radio_id].ptt)); - - // broadcast changed PTT state to clients - fgcom_local_client[iid].radios[radio_id].ptt = newValue; - if (oldValue != newValue) { - pluginDbg(" COM"+std::to_string(radio_id+1)+" PTT changed: notifying remotes"); - notifyRemotes(iid, NTFY_COM, radio_id); - } + for (long unsigned int radio_id=0; radio_idsecond : false; + pluginDbg(" IID="+std::to_string(iid)+"; radio_id="+std::to_string(radio_id)+"; operable="+std::to_string(lcl.radios[radio_id].operable)); + pluginDbg(" radio_ptt_req="+std::to_string(radio_ptt_req)); + pluginDbg(" radio_mapmumbleptt="+std::to_string(radio_mapmumbleptt)); + for (const auto& cv : fgcom_cfg.mapMumblePTT) { + pluginDbg(" mapMumblePTT["+std::to_string(cv.first)+"]="+std::to_string(cv.second)); + } + + bool oldValue = fgcom_local_client[iid].radios[radio_id].ptt; + bool newValue = false; + pluginDbg(" old_ptt="+std::to_string(oldValue)); + pluginDbg(" mumble_talk_detected="+std::to_string(mumble_talk_detected)); + if ( radio_ptt_req || (!udp_protocol_ptt_detected && radio_mapmumbleptt) ) { + // We should activate/deactivate PTT on the radio; either it's ptt was pressed in the UDP client, or we are configured for honoring mumbles talk state + newValue = mumble_talk_detected && lcl.radios[radio_id].operable; + } + pluginDbg(" new_ptt="+std::to_string(lcl.radios[radio_id].ptt)); + + // broadcast changed PTT state to clients + fgcom_local_client[iid].radios[radio_id].ptt = newValue; + if (oldValue != newValue) { + pluginDbg(" COM"+std::to_string(radio_id+1)+" PTT changed: notifying remotes"); + notifyRemotes(iid, NTFY_COM, radio_id); } } } @@ -953,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 @@ -1000,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]); @@ -1010,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 @@ -1124,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 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)); diff --git a/client/mumble-plugin/fgcom-mumble.h b/client/mumble-plugin/fgcom-mumble.h index 068d2531..c96aeb6b 100644 --- a/client/mumble-plugin/fgcom-mumble.h +++ b/client/mumble-plugin/fgcom-mumble.h @@ -23,8 +23,8 @@ // Plugin Version #define FGCOM_VERSION_MAJOR 0 -#define FGCOM_VERSION_MINOR 14 -#define FGCOM_VERSION_PATCH 3 +#define FGCOM_VERSION_MINOR 15 +#define FGCOM_VERSION_PATCH 1 /* * Is the plugin currently active? diff --git a/client/mumble-plugin/lib/audio.cpp b/client/mumble-plugin/lib/audio.cpp index 7bdfb1f5..c5198a51 100644 --- a/client/mumble-plugin/lib/audio.cpp +++ b/client/mumble-plugin/lib/audio.cpp @@ -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 1000) highpass_cutoff = 1000; // upside ceiling - std::unique_ptr f_highpass(new Dsp::SmoothedFilterDesign (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 f_highpass(new Dsp::SmoothedFilterDesign (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 4000@0.95 - 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 f_lowpass(new Dsp::SmoothedFilterDesign (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 f_lowpass(new Dsp::SmoothedFilterDesign (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!) diff --git a/client/mumble-plugin/lib/audio.h b/client/mumble-plugin/lib/audio.h index 57c41147..cedf689b 100644 --- a/client/mumble-plugin/lib/audio.h +++ b/client/mumble-plugin/lib/audio.h @@ -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); /* @@ -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 diff --git a/client/mumble-plugin/lib/debug.cpp b/client/mumble-plugin/lib/debug.cpp index 4ab02da5..e2b88bd8 100644 --- a/client/mumble-plugin/lib/debug.cpp +++ b/client/mumble-plugin/lib/debug.cpp @@ -46,23 +46,21 @@ void debug_out_internal_state() { state_str += lcl_prefix + "lastUpdate="+lastUpdate_str_f+"\n"; state_str += lcl_prefix + std::to_string(lcl.radios.size()) + " radios registered\n"; - if (lcl.radios.size() > 0) { - for (unsigned long int i=0; i 0) { - for (unsigned long int i=0; i 0) fgcom_updateClientComment(); + if (!staleIIDs.empty()) fgcom_updateClientComment(); } @@ -109,7 +109,7 @@ void fgcom_gc_clean_rmt() { } // if the remote has no identities left: clear also the remote as such - if (fgcom_remote_clients.size() == 0) { + if (fgcom_remote_clients.empty()) { staleRemoteClients.push_back(clid); } diff --git a/client/mumble-plugin/lib/io_plugin.cpp b/client/mumble-plugin/lib/io_plugin.cpp index b2bab618..c802fd05 100644 --- a/client/mumble-plugin/lib/io_plugin.cpp +++ b/client/mumble-plugin/lib/io_plugin.cpp @@ -165,7 +165,7 @@ void notifyRemotes(int iid, FGCOM_NOTIFY_T what, int selector, mumble_userid_t t fgcom_client lcl; // resolved local identity if (what != NTFY_ASK) { // skip notification attempts if we don't have any local state yet - if (fgcom_local_client.size() == 0 ) { + if (fgcom_local_client.empty()) { pluginDbg("[mum_pluginIO] notifyRemotes(): no local state yet, skipping notifications."); return; } diff --git a/client/mumble-plugin/lib/radio_model.cpp b/client/mumble-plugin/lib/radio_model.cpp index 6fab1980..31a03160 100644 --- a/client/mumble-plugin/lib/radio_model.cpp +++ b/client/mumble-plugin/lib/radio_model.cpp @@ -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" /* diff --git a/client/mumble-plugin/lib/radio_model.h b/client/mumble-plugin/lib/radio_model.h index 2f6fd21e..165fe0bb 100644 --- a/client/mumble-plugin/lib/radio_model.h +++ b/client/mumble-plugin/lib/radio_model.h @@ -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, */ diff --git a/client/mumble-plugin/lib/radio_model_hf.cpp b/client/mumble-plugin/lib/radio_model_hf.cpp index 9af9559f..eb7b372a 100644 --- a/client/mumble-plugin/lib/radio_model_hf.cpp +++ b/client/mumble-plugin/lib/radio_model_hf.cpp @@ -18,6 +18,7 @@ #include #include #include "radio_model.h" +#include "audio.h" /** * A HF based radio model for the FGCom-mumble plugin @@ -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 @@ -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 vhf_radio = std::make_unique(); + vhf_radio->processAudioSamples(lclRadio, signalQuality, outputPCM, sampleCount, channelCount, sampleRateHz); + } }; diff --git a/client/mumble-plugin/lib/radio_model_string.cpp b/client/mumble-plugin/lib/radio_model_string.cpp index 066833ef..738ed003 100644 --- a/client/mumble-plugin/lib/radio_model_string.cpp +++ b/client/mumble-plugin/lib/radio_model_string.cpp @@ -18,6 +18,7 @@ #include #include #include "radio_model.h" +#include "audio.h" /** * A string based radio model for the FGCom-mumble plugin @@ -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 vhf_radio = std::make_unique(); + vhf_radio->processAudioSamples(lclRadio, signalQuality, outputPCM, sampleCount, channelCount, sampleRateHz); + } } - }; diff --git a/client/mumble-plugin/lib/radio_model_uhf.cpp b/client/mumble-plugin/lib/radio_model_uhf.cpp index 08bb5a63..c2bd3e1a 100644 --- a/client/mumble-plugin/lib/radio_model_uhf.cpp +++ b/client/mumble-plugin/lib/radio_model_uhf.cpp @@ -18,6 +18,7 @@ #include #include #include "radio_model.h" +#include "audio.h" /** * A UHF based radio model for the FGCom-mumble plugin. @@ -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; diff --git a/client/mumble-plugin/lib/radio_model_vhf.cpp b/client/mumble-plugin/lib/radio_model_vhf.cpp index 16d68c3f..1412b406 100644 --- a/client/mumble-plugin/lib/radio_model_vhf.cpp +++ b/client/mumble-plugin/lib/radio_model_vhf.cpp @@ -18,6 +18,7 @@ #include #include #include "radio_model.h" +#include "audio.h" /** * A VHF based radio model for the FGCom-mumble plugin @@ -45,6 +46,49 @@ class FGCom_radiowaveModel_VHF : public FGCom_radiowaveModel { return sq; } + /* + * Process audio samples + * + * This is a somewhat generic implementation for invocation by the VHF, UHF and HF models, + * providing mono stream, hogh/lowpass filtering and noise addition + */ + virtual void processAudioSamples_VHF(int highpass_cutoff, int lowpass_cutoff, float minimumNoiseVolume, float maximumNoiseVolume, fgcom_radio lclRadio, float signalQuality, float *outputPCM, uint32_t sampleCount, uint16_t channelCount, uint32_t sampleRateHz) { + + /* + * Make the audio stream mono + */ + fgcom_audio_makeMono(outputPCM, sampleCount, channelCount); + + + /* + * Apply audio filtering + */ + fgcom_audio_filter(highpass_cutoff, lowpass_cutoff, outputPCM, sampleCount, channelCount, sampleRateHz); + + + /* + * Apply static noise + */ + // 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; + signalVolume = signalQuality; + noiseVolume = pow(0.9 - 0.9*signalQuality, 2) + minimumNoiseVolume; + if (noiseVolume > maximumNoiseVolume) noiseVolume = maximumNoiseVolume; + + // Now tune down the signal according to calculated noise volume level, then add noise + fgcom_audio_applyVolume(signalVolume, outputPCM, sampleCount, channelCount); + fgcom_audio_addNoise(noiseVolume, outputPCM, sampleCount, channelCount); + // TODO: we may clip some random samples from the signal on low quality + + + /* + * Finally apply radio volume setting + */ + fgcom_audio_applyVolume(lclRadio.volume, outputPCM, sampleCount, channelCount); + } public: @@ -245,6 +289,9 @@ class FGCom_radiowaveModel_VHF : 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 float width_kHz = r1.channelWidth; if (width_kHz <= 0) width_kHz = 8.33; @@ -272,4 +319,26 @@ class FGCom_radiowaveModel_VHF : public FGCom_radiowaveModel { } } + + /* + * Process audio samples + */ + void processAudioSamples(fgcom_radio lclRadio, float signalQuality, float *outputPCM, uint32_t sampleCount, uint16_t channelCount, uint32_t sampleRateHz) { + // 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 300@1.0 + 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 + + // LowPass filter cuts away higher frequency ranges and lets lower ones pass + // worst is 2000@035; best is 4000@0.95 + int lowpass_cutoff = signalQuality * 4000 + 500; + if (lowpass_cutoff < 2000) lowpass_cutoff = 2000; // lower ceiling + if (lowpass_cutoff > 4000) lowpass_cutoff = 4000; // upper ceiling + + float minimumNoiseVolume = 0.05; + float maximumNoiseVolume = 0.45; + processAudioSamples_VHF(highpass_cutoff, lowpass_cutoff, minimumNoiseVolume, maximumNoiseVolume, lclRadio, signalQuality, outputPCM, sampleCount, channelCount, sampleRateHz); + } }; diff --git a/client/mumble-plugin/test/geotest.cpp b/client/mumble-plugin/test/geotest.cpp index ff308cf9..cf544c0e 100644 --- a/client/mumble-plugin/test/geotest.cpp +++ b/client/mumble-plugin/test/geotest.cpp @@ -92,9 +92,9 @@ int main (int argc, char **argv) // Radio frequency model range test std::unique_ptr radio_model = FGCom_radiowaveModel::selectModel(std::string(argv[7])); printf(" conducting radio range test for model '%s':\n", radio_model->getType().c_str()); - for (int pwr=0; pwr<=30; true) { + for (int pwr=0; pwr<=30;) { struct fgcom_radiowave_signal sigStrengthAB = radio_model->getSignal(lat1, lon1, h1, lat2, lon2, h2, pwr); - struct fgcom_radiowave_signal sigStrengthBA = radio_model->getSignal(lat2, lon2, h2, lat1, lon1, h1, pwr); + //struct fgcom_radiowave_signal sigStrengthBA = radio_model->getSignal(lat2, lon2, h2, lat1, lon1, h1, pwr); printf(" signal posA->posB @%iw = %.0f%% \n", pwr, sigStrengthAB.quality*100); // its the same (it should at least) printf(" VHF signal posB->posA @%iw = %.0f% \n", pwr, sigStrengthBA.quality*100); if (pwr < 5) { pwr++; }