From b1d103d4a832f448b2e153b5dbf2729c2d0c5257 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 22 Feb 2019 07:17:27 +0100 Subject: [PATCH] FreeDV modulator: added a clone of SSB modulator --- app/main.cpp | 2 +- appbench/main.cpp | 2 +- appsrv/main.cpp | 2 +- debian/changelog | 6 + plugins/channeltx/CMakeLists.txt | 2 + plugins/channeltx/modfreedv/CMakeLists.txt | 50 + plugins/channeltx/modfreedv/freedvmod.cpp | 1380 +++++++++++++++++ plugins/channeltx/modfreedv/freedvmod.h | 339 ++++ plugins/channeltx/modfreedv/freedvmodgui.cpp | 796 ++++++++++ plugins/channeltx/modfreedv/freedvmodgui.h | 136 ++ plugins/channeltx/modfreedv/freedvmodgui.ui | 1343 ++++++++++++++++ .../channeltx/modfreedv/freedvmodplugin.cpp | 78 + plugins/channeltx/modfreedv/freedvmodplugin.h | 47 + .../channeltx/modfreedv/freedvmodsettings.cpp | 241 +++ .../channeltx/modfreedv/freedvmodsettings.h | 88 ++ sdrbase/resources/res.qrc | 3 +- sdrbase/resources/webapi/doc/html2/index.html | 125 +- .../webapi/doc/swagger/include/FreeDVMod.yaml | 80 + .../resources/webapi/doc/swagger/swagger.yaml | 6 +- .../api/swagger/include/FreeDVMod.yaml | 80 + swagger/sdrangel/api/swagger/swagger.yaml | 6 +- swagger/sdrangel/code/html2/index.html | 125 +- .../code/qt5/client/SWGAMDemodReport.cpp | 2 +- .../code/qt5/client/SWGAMDemodReport.h | 2 +- .../code/qt5/client/SWGAMDemodSettings.cpp | 2 +- .../code/qt5/client/SWGAMDemodSettings.h | 2 +- .../code/qt5/client/SWGAMModReport.cpp | 2 +- .../sdrangel/code/qt5/client/SWGAMModReport.h | 2 +- .../code/qt5/client/SWGAMModSettings.cpp | 2 +- .../code/qt5/client/SWGAMModSettings.h | 2 +- .../code/qt5/client/SWGATVModReport.cpp | 2 +- .../code/qt5/client/SWGATVModReport.h | 2 +- .../code/qt5/client/SWGATVModSettings.cpp | 2 +- .../code/qt5/client/SWGATVModSettings.h | 2 +- .../code/qt5/client/SWGAirspyHFReport.cpp | 2 +- .../code/qt5/client/SWGAirspyHFReport.h | 2 +- .../code/qt5/client/SWGAirspyHFSettings.cpp | 2 +- .../code/qt5/client/SWGAirspyHFSettings.h | 2 +- .../code/qt5/client/SWGAirspyReport.cpp | 2 +- .../code/qt5/client/SWGAirspyReport.h | 2 +- .../code/qt5/client/SWGAirspySettings.cpp | 2 +- .../code/qt5/client/SWGAirspySettings.h | 2 +- .../sdrangel/code/qt5/client/SWGArgInfo.cpp | 2 +- swagger/sdrangel/code/qt5/client/SWGArgInfo.h | 2 +- .../sdrangel/code/qt5/client/SWGArgValue.cpp | 2 +- .../sdrangel/code/qt5/client/SWGArgValue.h | 2 +- .../code/qt5/client/SWGAudioDevices.cpp | 2 +- .../code/qt5/client/SWGAudioDevices.h | 2 +- .../code/qt5/client/SWGAudioInputDevice.cpp | 2 +- .../code/qt5/client/SWGAudioInputDevice.h | 2 +- .../code/qt5/client/SWGAudioOutputDevice.cpp | 2 +- .../code/qt5/client/SWGAudioOutputDevice.h | 2 +- .../code/qt5/client/SWGBFMDemodReport.cpp | 2 +- .../code/qt5/client/SWGBFMDemodReport.h | 2 +- .../code/qt5/client/SWGBFMDemodSettings.cpp | 2 +- .../code/qt5/client/SWGBFMDemodSettings.h | 2 +- .../sdrangel/code/qt5/client/SWGBandwidth.cpp | 2 +- .../sdrangel/code/qt5/client/SWGBandwidth.h | 2 +- .../qt5/client/SWGBladeRF1InputSettings.cpp | 2 +- .../qt5/client/SWGBladeRF1InputSettings.h | 2 +- .../qt5/client/SWGBladeRF1OutputSettings.cpp | 2 +- .../qt5/client/SWGBladeRF1OutputSettings.h | 2 +- .../qt5/client/SWGBladeRF2InputReport.cpp | 2 +- .../code/qt5/client/SWGBladeRF2InputReport.h | 2 +- .../qt5/client/SWGBladeRF2InputSettings.cpp | 2 +- .../qt5/client/SWGBladeRF2InputSettings.h | 2 +- .../qt5/client/SWGBladeRF2OutputReport.cpp | 2 +- .../code/qt5/client/SWGBladeRF2OutputReport.h | 2 +- .../qt5/client/SWGBladeRF2OutputSettings.cpp | 2 +- .../qt5/client/SWGBladeRF2OutputSettings.h | 2 +- .../code/qt5/client/SWGCWKeyerSettings.cpp | 2 +- .../code/qt5/client/SWGCWKeyerSettings.h | 2 +- .../sdrangel/code/qt5/client/SWGChannel.cpp | 2 +- swagger/sdrangel/code/qt5/client/SWGChannel.h | 2 +- .../code/qt5/client/SWGChannelListItem.cpp | 2 +- .../code/qt5/client/SWGChannelListItem.h | 2 +- .../code/qt5/client/SWGChannelReport.cpp | 25 +- .../code/qt5/client/SWGChannelReport.h | 9 +- .../code/qt5/client/SWGChannelSettings.cpp | 25 +- .../code/qt5/client/SWGChannelSettings.h | 9 +- .../code/qt5/client/SWGChannelsDetail.cpp | 2 +- .../code/qt5/client/SWGChannelsDetail.h | 2 +- .../sdrangel/code/qt5/client/SWGComplex.cpp | 2 +- swagger/sdrangel/code/qt5/client/SWGComplex.h | 2 +- .../code/qt5/client/SWGDSDDemodReport.cpp | 2 +- .../code/qt5/client/SWGDSDDemodReport.h | 2 +- .../code/qt5/client/SWGDSDDemodSettings.cpp | 2 +- .../code/qt5/client/SWGDSDDemodSettings.h | 2 +- .../code/qt5/client/SWGDVSeralDevices.cpp | 2 +- .../code/qt5/client/SWGDVSeralDevices.h | 2 +- .../code/qt5/client/SWGDVSerialDevice.cpp | 2 +- .../code/qt5/client/SWGDVSerialDevice.h | 2 +- .../code/qt5/client/SWGDeviceListItem.cpp | 2 +- .../code/qt5/client/SWGDeviceListItem.h | 2 +- .../code/qt5/client/SWGDeviceReport.cpp | 2 +- .../code/qt5/client/SWGDeviceReport.h | 2 +- .../sdrangel/code/qt5/client/SWGDeviceSet.cpp | 2 +- .../sdrangel/code/qt5/client/SWGDeviceSet.h | 2 +- .../code/qt5/client/SWGDeviceSetApi.cpp | 2 +- .../code/qt5/client/SWGDeviceSetApi.h | 2 +- .../code/qt5/client/SWGDeviceSetList.cpp | 2 +- .../code/qt5/client/SWGDeviceSetList.h | 2 +- .../code/qt5/client/SWGDeviceSettings.cpp | 2 +- .../code/qt5/client/SWGDeviceSettings.h | 2 +- .../code/qt5/client/SWGDeviceState.cpp | 2 +- .../sdrangel/code/qt5/client/SWGDeviceState.h | 2 +- .../code/qt5/client/SWGErrorResponse.cpp | 2 +- .../code/qt5/client/SWGErrorResponse.h | 2 +- .../code/qt5/client/SWGFCDProPlusSettings.cpp | 2 +- .../code/qt5/client/SWGFCDProPlusSettings.h | 2 +- .../code/qt5/client/SWGFCDProSettings.cpp | 2 +- .../code/qt5/client/SWGFCDProSettings.h | 2 +- .../code/qt5/client/SWGFileSourceReport.cpp | 2 +- .../code/qt5/client/SWGFileSourceReport.h | 2 +- .../code/qt5/client/SWGFileSourceSettings.cpp | 2 +- .../code/qt5/client/SWGFileSourceSettings.h | 2 +- .../code/qt5/client/SWGFreeDVModReport.cpp | 148 ++ .../code/qt5/client/SWGFreeDVModReport.h | 70 + .../code/qt5/client/SWGFreeDVModSettings.cpp | 702 +++++++++ .../code/qt5/client/SWGFreeDVModSettings.h | 228 +++ .../sdrangel/code/qt5/client/SWGFrequency.cpp | 2 +- .../sdrangel/code/qt5/client/SWGFrequency.h | 2 +- .../code/qt5/client/SWGFrequencyBand.cpp | 2 +- .../code/qt5/client/SWGFrequencyBand.h | 2 +- .../code/qt5/client/SWGFrequencyRange.cpp | 2 +- .../code/qt5/client/SWGFrequencyRange.h | 2 +- swagger/sdrangel/code/qt5/client/SWGGain.cpp | 2 +- swagger/sdrangel/code/qt5/client/SWGGain.h | 2 +- .../qt5/client/SWGHackRFInputSettings.cpp | 2 +- .../code/qt5/client/SWGHackRFInputSettings.h | 2 +- .../qt5/client/SWGHackRFOutputSettings.cpp | 2 +- .../code/qt5/client/SWGHackRFOutputSettings.h | 2 +- .../sdrangel/code/qt5/client/SWGHelpers.cpp | 2 +- swagger/sdrangel/code/qt5/client/SWGHelpers.h | 2 +- .../code/qt5/client/SWGHttpRequest.cpp | 2 +- .../sdrangel/code/qt5/client/SWGHttpRequest.h | 2 +- .../code/qt5/client/SWGInstanceApi.cpp | 2 +- .../sdrangel/code/qt5/client/SWGInstanceApi.h | 2 +- .../client/SWGInstanceChannelsResponse.cpp | 2 +- .../qt5/client/SWGInstanceChannelsResponse.h | 2 +- .../qt5/client/SWGInstanceDevicesResponse.cpp | 2 +- .../qt5/client/SWGInstanceDevicesResponse.h | 2 +- .../qt5/client/SWGInstanceSummaryResponse.cpp | 2 +- .../qt5/client/SWGInstanceSummaryResponse.h | 2 +- .../code/qt5/client/SWGLimeSdrInputReport.cpp | 2 +- .../code/qt5/client/SWGLimeSdrInputReport.h | 2 +- .../qt5/client/SWGLimeSdrInputSettings.cpp | 2 +- .../code/qt5/client/SWGLimeSdrInputSettings.h | 2 +- .../qt5/client/SWGLimeSdrOutputReport.cpp | 2 +- .../code/qt5/client/SWGLimeSdrOutputReport.h | 2 +- .../qt5/client/SWGLimeSdrOutputSettings.cpp | 2 +- .../qt5/client/SWGLimeSdrOutputSettings.h | 2 +- .../qt5/client/SWGLocationInformation.cpp | 2 +- .../code/qt5/client/SWGLocationInformation.h | 2 +- .../code/qt5/client/SWGLoggingInfo.cpp | 2 +- .../sdrangel/code/qt5/client/SWGLoggingInfo.h | 2 +- .../code/qt5/client/SWGModelFactory.h | 10 +- .../code/qt5/client/SWGNFMDemodReport.cpp | 2 +- .../code/qt5/client/SWGNFMDemodReport.h | 2 +- .../code/qt5/client/SWGNFMDemodSettings.cpp | 2 +- .../code/qt5/client/SWGNFMDemodSettings.h | 2 +- .../code/qt5/client/SWGNFMModReport.cpp | 2 +- .../code/qt5/client/SWGNFMModReport.h | 2 +- .../code/qt5/client/SWGNFMModSettings.cpp | 2 +- .../code/qt5/client/SWGNFMModSettings.h | 2 +- .../sdrangel/code/qt5/client/SWGNamedEnum.cpp | 2 +- .../sdrangel/code/qt5/client/SWGNamedEnum.h | 2 +- swagger/sdrangel/code/qt5/client/SWGObject.h | 2 +- .../code/qt5/client/SWGPerseusReport.cpp | 2 +- .../code/qt5/client/SWGPerseusReport.h | 2 +- .../code/qt5/client/SWGPerseusSettings.cpp | 2 +- .../code/qt5/client/SWGPerseusSettings.h | 2 +- .../qt5/client/SWGPlutoSdrInputReport.cpp | 2 +- .../code/qt5/client/SWGPlutoSdrInputReport.h | 2 +- .../qt5/client/SWGPlutoSdrInputSettings.cpp | 2 +- .../qt5/client/SWGPlutoSdrInputSettings.h | 2 +- .../qt5/client/SWGPlutoSdrOutputReport.cpp | 2 +- .../code/qt5/client/SWGPlutoSdrOutputReport.h | 2 +- .../qt5/client/SWGPlutoSdrOutputSettings.cpp | 2 +- .../qt5/client/SWGPlutoSdrOutputSettings.h | 2 +- .../code/qt5/client/SWGPresetExport.cpp | 2 +- .../code/qt5/client/SWGPresetExport.h | 2 +- .../code/qt5/client/SWGPresetGroup.cpp | 2 +- .../sdrangel/code/qt5/client/SWGPresetGroup.h | 2 +- .../code/qt5/client/SWGPresetIdentifier.cpp | 2 +- .../code/qt5/client/SWGPresetIdentifier.h | 2 +- .../code/qt5/client/SWGPresetImport.cpp | 2 +- .../code/qt5/client/SWGPresetImport.h | 2 +- .../code/qt5/client/SWGPresetItem.cpp | 2 +- .../sdrangel/code/qt5/client/SWGPresetItem.h | 2 +- .../code/qt5/client/SWGPresetTransfer.cpp | 2 +- .../code/qt5/client/SWGPresetTransfer.h | 2 +- .../sdrangel/code/qt5/client/SWGPresets.cpp | 2 +- swagger/sdrangel/code/qt5/client/SWGPresets.h | 2 +- .../sdrangel/code/qt5/client/SWGRDSReport.cpp | 2 +- .../sdrangel/code/qt5/client/SWGRDSReport.h | 2 +- .../client/SWGRDSReport_altFrequencies.cpp | 2 +- .../qt5/client/SWGRDSReport_altFrequencies.h | 2 +- swagger/sdrangel/code/qt5/client/SWGRange.cpp | 2 +- swagger/sdrangel/code/qt5/client/SWGRange.h | 2 +- .../code/qt5/client/SWGRangeFloat.cpp | 2 +- .../sdrangel/code/qt5/client/SWGRangeFloat.h | 2 +- .../code/qt5/client/SWGRemoteInputReport.cpp | 2 +- .../code/qt5/client/SWGRemoteInputReport.h | 2 +- .../qt5/client/SWGRemoteInputSettings.cpp | 2 +- .../code/qt5/client/SWGRemoteInputSettings.h | 2 +- .../code/qt5/client/SWGRemoteOutputReport.cpp | 2 +- .../code/qt5/client/SWGRemoteOutputReport.h | 2 +- .../qt5/client/SWGRemoteOutputSettings.cpp | 2 +- .../code/qt5/client/SWGRemoteOutputSettings.h | 2 +- .../code/qt5/client/SWGRemoteSinkSettings.cpp | 2 +- .../code/qt5/client/SWGRemoteSinkSettings.h | 2 +- .../code/qt5/client/SWGRemoteSourceReport.cpp | 2 +- .../code/qt5/client/SWGRemoteSourceReport.h | 2 +- .../qt5/client/SWGRemoteSourceSettings.cpp | 2 +- .../code/qt5/client/SWGRemoteSourceSettings.h | 2 +- .../code/qt5/client/SWGRtlSdrReport.cpp | 2 +- .../code/qt5/client/SWGRtlSdrReport.h | 2 +- .../code/qt5/client/SWGRtlSdrSettings.cpp | 2 +- .../code/qt5/client/SWGRtlSdrSettings.h | 2 +- .../code/qt5/client/SWGSDRPlayReport.cpp | 2 +- .../code/qt5/client/SWGSDRPlayReport.h | 2 +- .../code/qt5/client/SWGSDRPlaySettings.cpp | 2 +- .../code/qt5/client/SWGSDRPlaySettings.h | 2 +- .../code/qt5/client/SWGSSBDemodReport.cpp | 2 +- .../code/qt5/client/SWGSSBDemodReport.h | 2 +- .../code/qt5/client/SWGSSBDemodSettings.cpp | 2 +- .../code/qt5/client/SWGSSBDemodSettings.h | 2 +- .../code/qt5/client/SWGSSBModReport.cpp | 2 +- .../code/qt5/client/SWGSSBModReport.h | 2 +- .../code/qt5/client/SWGSSBModSettings.cpp | 2 +- .../code/qt5/client/SWGSSBModSettings.h | 2 +- .../code/qt5/client/SWGSampleRate.cpp | 2 +- .../sdrangel/code/qt5/client/SWGSampleRate.h | 2 +- .../code/qt5/client/SWGSamplingDevice.cpp | 2 +- .../code/qt5/client/SWGSamplingDevice.h | 2 +- .../client/SWGSoapySDRFrequencySetting.cpp | 2 +- .../qt5/client/SWGSoapySDRFrequencySetting.h | 2 +- .../qt5/client/SWGSoapySDRGainSetting.cpp | 2 +- .../code/qt5/client/SWGSoapySDRGainSetting.h | 2 +- .../qt5/client/SWGSoapySDRInputSettings.cpp | 2 +- .../qt5/client/SWGSoapySDRInputSettings.h | 2 +- .../qt5/client/SWGSoapySDROutputSettings.cpp | 2 +- .../qt5/client/SWGSoapySDROutputSettings.h | 2 +- .../code/qt5/client/SWGSoapySDRReport.cpp | 2 +- .../code/qt5/client/SWGSoapySDRReport.h | 2 +- .../code/qt5/client/SWGSuccessResponse.cpp | 2 +- .../code/qt5/client/SWGSuccessResponse.h | 2 +- .../code/qt5/client/SWGTestSourceSettings.cpp | 2 +- .../code/qt5/client/SWGTestSourceSettings.h | 2 +- .../code/qt5/client/SWGUDPSinkReport.cpp | 2 +- .../code/qt5/client/SWGUDPSinkReport.h | 2 +- .../code/qt5/client/SWGUDPSinkSettings.cpp | 2 +- .../code/qt5/client/SWGUDPSinkSettings.h | 2 +- .../code/qt5/client/SWGUDPSourceReport.cpp | 2 +- .../code/qt5/client/SWGUDPSourceReport.h | 2 +- .../code/qt5/client/SWGUDPSourceSettings.cpp | 2 +- .../code/qt5/client/SWGUDPSourceSettings.h | 2 +- .../code/qt5/client/SWGWFMDemodReport.cpp | 2 +- .../code/qt5/client/SWGWFMDemodReport.h | 2 +- .../code/qt5/client/SWGWFMDemodSettings.cpp | 2 +- .../code/qt5/client/SWGWFMDemodSettings.h | 2 +- .../code/qt5/client/SWGWFMModReport.cpp | 2 +- .../code/qt5/client/SWGWFMModReport.h | 2 +- .../code/qt5/client/SWGWFMModSettings.cpp | 2 +- .../code/qt5/client/SWGWFMModSettings.h | 2 +- .../code/qt5/client/SWGXtrxInputReport.cpp | 2 +- .../code/qt5/client/SWGXtrxInputReport.h | 2 +- .../code/qt5/client/SWGXtrxInputSettings.cpp | 2 +- .../code/qt5/client/SWGXtrxInputSettings.h | 2 +- .../code/qt5/client/SWGXtrxOutputReport.cpp | 2 +- .../code/qt5/client/SWGXtrxOutputReport.h | 2 +- .../code/qt5/client/SWGXtrxOutputSettings.cpp | 2 +- .../code/qt5/client/SWGXtrxOutputSettings.h | 2 +- 274 files changed, 6391 insertions(+), 258 deletions(-) create mode 100644 plugins/channeltx/modfreedv/CMakeLists.txt create mode 100644 plugins/channeltx/modfreedv/freedvmod.cpp create mode 100644 plugins/channeltx/modfreedv/freedvmod.h create mode 100644 plugins/channeltx/modfreedv/freedvmodgui.cpp create mode 100644 plugins/channeltx/modfreedv/freedvmodgui.h create mode 100644 plugins/channeltx/modfreedv/freedvmodgui.ui create mode 100644 plugins/channeltx/modfreedv/freedvmodplugin.cpp create mode 100644 plugins/channeltx/modfreedv/freedvmodplugin.h create mode 100644 plugins/channeltx/modfreedv/freedvmodsettings.cpp create mode 100644 plugins/channeltx/modfreedv/freedvmodsettings.h create mode 100644 sdrbase/resources/webapi/doc/swagger/include/FreeDVMod.yaml create mode 100644 swagger/sdrangel/api/swagger/include/FreeDVMod.yaml create mode 100644 swagger/sdrangel/code/qt5/client/SWGFreeDVModReport.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGFreeDVModReport.h create mode 100644 swagger/sdrangel/code/qt5/client/SWGFreeDVModSettings.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGFreeDVModSettings.h diff --git a/app/main.cpp b/app/main.cpp index 5ab611ae3..eb47b11af 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -35,7 +35,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo */ QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangel"); - QCoreApplication::setApplicationVersion("4.4.5"); + QCoreApplication::setApplicationVersion("4.5.0"); #if QT_VERSION >= 0x050600 QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); // DPI support QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); //HiDPI pixmaps diff --git a/appbench/main.cpp b/appbench/main.cpp index 95cad8873..33e21c25d 100644 --- a/appbench/main.cpp +++ b/appbench/main.cpp @@ -57,7 +57,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelBench"); - QCoreApplication::setApplicationVersion("4.4.5"); + QCoreApplication::setApplicationVersion("4.5.0"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/appsrv/main.cpp b/appsrv/main.cpp index 2fd9059d4..a1b2b444d 100644 --- a/appsrv/main.cpp +++ b/appsrv/main.cpp @@ -56,7 +56,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelSrv"); - QCoreApplication::setApplicationVersion("4.4.5"); + QCoreApplication::setApplicationVersion("4.5.0"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/debian/changelog b/debian/changelog index c58426dfa..d67e1649d 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +sdrangel (4.5.0-1) unstable; urgency=medium + + * Implemented a FreeDV modulator + + -- Edouard Griffiths, F4EXB Sun, 03 Mar 2019 20:14:18 +0100 + sdrangel (4.4.5-1) unstable; urgency=medium * UDP/RTP audio: added G722 and Opus support diff --git a/plugins/channeltx/CMakeLists.txt b/plugins/channeltx/CMakeLists.txt index 7af1b373d..608cec058 100644 --- a/plugins/channeltx/CMakeLists.txt +++ b/plugins/channeltx/CMakeLists.txt @@ -20,3 +20,5 @@ if (BUILD_DEBIAN) add_subdirectory(remotesource) endif (BUILD_DEBIAN) +# TODO: add FreeDV stuff only if codec2 library is present +add_subdirectory(modfreedv) \ No newline at end of file diff --git a/plugins/channeltx/modfreedv/CMakeLists.txt b/plugins/channeltx/modfreedv/CMakeLists.txt new file mode 100644 index 000000000..82d46aacb --- /dev/null +++ b/plugins/channeltx/modfreedv/CMakeLists.txt @@ -0,0 +1,50 @@ +project(modfreedv) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +set(modfreedv_SOURCES + freedvmod.cpp + freedvmodgui.cpp + freedvmodplugin.cpp + freedvmodsettings.cpp +) + +set(modfreedv_HEADERS + freedvmod.h + freedvmodgui.h + freedvmodplugin.h + freedvmodsettings.h +) + +set(modfreedv_FORMS + freedvmodgui.ui +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +qt5_wrap_ui(modfreedv_FORMS_HEADERS ${modfreedv_FORMS}) + +add_library(modfreedv SHARED + ${modfreedv_SOURCES} + ${modfreedv_HEADERS_MOC} + ${modfreedv_FORMS_HEADERS} +) + +target_link_libraries(modfreedv + ${QT_LIBRARIES} + sdrbase + sdrgui + swagger +) + +target_link_libraries(modfreedv Qt5::Core Qt5::Widgets) + +install(TARGETS modfreedv DESTINATION lib/plugins/channeltx) diff --git a/plugins/channeltx/modfreedv/freedvmod.cpp b/plugins/channeltx/modfreedv/freedvmod.cpp new file mode 100644 index 000000000..9465ff05f --- /dev/null +++ b/plugins/channeltx/modfreedv/freedvmod.cpp @@ -0,0 +1,1380 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "freedvmod.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "SWGChannelSettings.h" +#include "SWGChannelReport.h" +#include "SWGFreeDVModReport.h" + +#include "dsp/upchannelizer.h" +#include "dsp/dspengine.h" +#include "dsp/threadedbasebandsamplesource.h" +#include "dsp/dspcommands.h" +#include "device/devicesinkapi.h" +#include "util/db.h" + +MESSAGE_CLASS_DEFINITION(FreeDVMod::MsgConfigureFreeDVMod, Message) +MESSAGE_CLASS_DEFINITION(FreeDVMod::MsgConfigureChannelizer, Message) +MESSAGE_CLASS_DEFINITION(FreeDVMod::MsgConfigureFileSourceName, Message) +MESSAGE_CLASS_DEFINITION(FreeDVMod::MsgConfigureFileSourceSeek, Message) +MESSAGE_CLASS_DEFINITION(FreeDVMod::MsgConfigureFileSourceStreamTiming, Message) +MESSAGE_CLASS_DEFINITION(FreeDVMod::MsgReportFileSourceStreamData, Message) +MESSAGE_CLASS_DEFINITION(FreeDVMod::MsgReportFileSourceStreamTiming, Message) + +const QString FreeDVMod::m_channelIdURI = "sdrangel.channeltx.modfreedv"; +const QString FreeDVMod::m_channelId = "FreeDVMod"; +const int FreeDVMod::m_levelNbSamples = 480; // every 10ms +const int FreeDVMod::m_ssbFftLen = 1024; + +FreeDVMod::FreeDVMod(DeviceSinkAPI *deviceAPI) : + ChannelSourceAPI(m_channelIdURI), + m_deviceAPI(deviceAPI), + m_basebandSampleRate(48000), + m_outputSampleRate(48000), + m_inputFrequencyOffset(0), + m_SSBFilter(0), + m_DSBFilter(0), + m_SSBFilterBuffer(0), + m_DSBFilterBuffer(0), + m_SSBFilterBufferIndex(0), + m_DSBFilterBufferIndex(0), + m_sampleSink(0), + m_audioFifo(4800), + m_settingsMutex(QMutex::Recursive), + m_fileSize(0), + m_recordLength(0), + m_sampleRate(48000), + m_levelCalcCount(0), + m_peakLevel(0.0f), + m_levelSum(0.0f), + m_inAGC(9600, 0.2, 1e-4), + m_agcStepLength(2400) +{ + setObjectName(m_channelId); + + DSPEngine::instance()->getAudioDeviceManager()->addAudioSource(&m_audioFifo, getInputMessageQueue()); + m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getInputSampleRate(); + + m_SSBFilter = new fftfilt(m_settings.m_lowCutoff / m_audioSampleRate, m_settings.m_bandwidth / m_audioSampleRate, m_ssbFftLen); + m_DSBFilter = new fftfilt((2.0f * m_settings.m_bandwidth) / m_audioSampleRate, 2 * m_ssbFftLen); + m_SSBFilterBuffer = new Complex[m_ssbFftLen>>1]; // filter returns data exactly half of its size + m_DSBFilterBuffer = new Complex[m_ssbFftLen]; + std::fill(m_SSBFilterBuffer, m_SSBFilterBuffer+(m_ssbFftLen>>1), Complex{0,0}); + std::fill(m_DSBFilterBuffer, m_DSBFilterBuffer+m_ssbFftLen, Complex{0,0}); + + m_audioBuffer.resize(1<<14); + m_audioBufferFill = 0; + + m_sum.real(0.0f); + m_sum.imag(0.0f); + m_undersampleCount = 0; + m_sumCount = 0; + + m_magsq = 0.0; + + m_toneNco.setFreq(1000.0, m_audioSampleRate); + + // CW keyer + m_cwKeyer.setSampleRate(48000); + m_cwKeyer.setWPM(13); + m_cwKeyer.setMode(CWKeyerSettings::CWNone); + + m_inAGC.setGate(m_settings.m_agcThresholdGate); + m_inAGC.setStepDownDelay(m_settings.m_agcThresholdDelay); + m_inAGC.setClamping(true); + + applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true); + applySettings(m_settings, true); + + m_channelizer = new UpChannelizer(this); + m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this); + m_deviceAPI->addThreadedSource(m_threadedChannelizer); + m_deviceAPI->addChannelAPI(this); + + m_networkManager = new QNetworkAccessManager(); + connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); +} + +FreeDVMod::~FreeDVMod() +{ + disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); + delete m_networkManager; + + DSPEngine::instance()->getAudioDeviceManager()->removeAudioSource(&m_audioFifo); + + m_deviceAPI->removeChannelAPI(this); + m_deviceAPI->removeThreadedSource(m_threadedChannelizer); + delete m_threadedChannelizer; + delete m_channelizer; + + delete m_SSBFilter; + delete m_DSBFilter; + delete[] m_SSBFilterBuffer; + delete[] m_DSBFilterBuffer; +} + +void FreeDVMod::pull(Sample& sample) +{ + Complex ci; + + m_settingsMutex.lock(); + + if (m_interpolatorDistance > 1.0f) // decimate + { + modulateSample(); + + while (!m_interpolator.decimate(&m_interpolatorDistanceRemain, m_modSample, &ci)) + { + modulateSample(); + } + } + else + { + if (m_interpolator.interpolate(&m_interpolatorDistanceRemain, m_modSample, &ci)) + { + modulateSample(); + } + } + + m_interpolatorDistanceRemain += m_interpolatorDistance; + + ci *= m_carrierNco.nextIQ(); // shift to carrier frequency + ci *= 0.891235351562f * SDR_TX_SCALEF; //scaling at -1 dB to account for possible filter overshoot + + m_settingsMutex.unlock(); + + double magsq = ci.real() * ci.real() + ci.imag() * ci.imag(); + magsq /= (SDR_TX_SCALED*SDR_TX_SCALED); + m_movingAverage(magsq); + m_magsq = m_movingAverage.asDouble(); + + sample.m_real = (FixReal) ci.real(); + sample.m_imag = (FixReal) ci.imag(); +} + +void FreeDVMod::pullAudio(int nbSamples) +{ + unsigned int nbSamplesAudio = nbSamples * ((Real) m_audioSampleRate / (Real) m_basebandSampleRate); + + if (nbSamplesAudio > m_audioBuffer.size()) + { + m_audioBuffer.resize(nbSamplesAudio); + } + + m_audioFifo.read(reinterpret_cast(&m_audioBuffer[0]), nbSamplesAudio); + m_audioBufferFill = 0; +} + +void FreeDVMod::modulateSample() +{ + pullAF(m_modSample); + calculateLevel(m_modSample); + m_audioBufferFill++; +} + +void FreeDVMod::pullAF(Complex& sample) +{ + if (m_settings.m_audioMute) + { + sample.real(0.0f); + sample.imag(0.0f); + return; + } + + Complex ci; + fftfilt::cmplx *filtered; + int n_out = 0; + + int decim = 1<<(m_settings.m_spanLog2 - 1); + unsigned char decim_mask = decim - 1; // counter LSB bit mask for decimation by 2^(m_scaleLog2 - 1) + + switch (m_settings.m_modAFInput) + { + case FreeDVModSettings::FreeDVModInputTone: + if (m_settings.m_dsb) + { + Real t = m_toneNco.next()/1.25; + sample.real(t); + sample.imag(t); + } + else + { + if (m_settings.m_usb) { + sample = m_toneNco.nextIQ(); + } else { + sample = m_toneNco.nextQI(); + } + } + break; + case FreeDVModSettings::FreeDVModInputFile: + // Monaural (mono): + // sox f4exb_call.wav --encoding float --endian little f4exb_call.raw + // ffplay -f f32le -ar 48k -ac 1 f4exb_call.raw + // Binaural (stereo): + // sox f4exb_call.wav --encoding float --endian little f4exb_call.raw + // ffplay -f f32le -ar 48k -ac 2 f4exb_call.raw + if (m_ifstream.is_open()) + { + if (m_ifstream.eof()) + { + if (m_settings.m_playLoop) + { + m_ifstream.clear(); + m_ifstream.seekg(0, std::ios::beg); + } + } + + if (m_ifstream.eof()) + { + ci.real(0.0f); + ci.imag(0.0f); + } + else + { + if (m_settings.m_audioBinaural) + { + Complex c; + m_ifstream.read(reinterpret_cast(&c), sizeof(Complex)); + + if (m_settings.m_audioFlipChannels) + { + ci.real(c.imag() * m_settings.m_volumeFactor); + ci.imag(c.real() * m_settings.m_volumeFactor); + } + else + { + ci = c * m_settings.m_volumeFactor; + } + } + else + { + Real real; + m_ifstream.read(reinterpret_cast(&real), sizeof(Real)); + + if (m_settings.m_agc) + { + ci.real(real); + ci.imag(0.0f); + m_inAGC.feed(ci); + ci *= m_settings.m_volumeFactor; + } + else + { + ci.real(real * m_settings.m_volumeFactor); + ci.imag(0.0f); + } + } + } + } + else + { + ci.real(0.0f); + ci.imag(0.0f); + } + break; + case FreeDVModSettings::FreeDVModInputAudio: + if (m_settings.m_audioBinaural) + { + if (m_settings.m_audioFlipChannels) + { + ci.real((m_audioBuffer[m_audioBufferFill].r / SDR_TX_SCALEF) * m_settings.m_volumeFactor); + ci.imag((m_audioBuffer[m_audioBufferFill].l / SDR_TX_SCALEF) * m_settings.m_volumeFactor); + } + else + { + ci.real((m_audioBuffer[m_audioBufferFill].l / SDR_TX_SCALEF) * m_settings.m_volumeFactor); + ci.imag((m_audioBuffer[m_audioBufferFill].r / SDR_TX_SCALEF) * m_settings.m_volumeFactor); + } + } + else + { + if (m_settings.m_agc) + { + ci.real(((m_audioBuffer[m_audioBufferFill].l + m_audioBuffer[m_audioBufferFill].r) / 65536.0f)); + ci.imag(0.0f); + m_inAGC.feed(ci); + ci *= m_settings.m_volumeFactor; + } + else + { + ci.real(((m_audioBuffer[m_audioBufferFill].l + m_audioBuffer[m_audioBufferFill].r) / 65536.0f) * m_settings.m_volumeFactor); + ci.imag(0.0f); + } + } + + break; + case FreeDVModSettings::FreeDVModInputCWTone: + Real fadeFactor; + + if (m_cwKeyer.getSample()) + { + m_cwKeyer.getCWSmoother().getFadeSample(true, fadeFactor); + + if (m_settings.m_dsb) + { + Real t = m_toneNco.next() * fadeFactor; + sample.real(t); + sample.imag(t); + } + else + { + if (m_settings.m_usb) { + sample = m_toneNco.nextIQ() * fadeFactor; + } else { + sample = m_toneNco.nextQI() * fadeFactor; + } + } + } + else + { + if (m_cwKeyer.getCWSmoother().getFadeSample(false, fadeFactor)) + { + if (m_settings.m_dsb) + { + Real t = (m_toneNco.next() * fadeFactor)/1.25; + sample.real(t); + sample.imag(t); + } + else + { + if (m_settings.m_usb) { + sample = m_toneNco.nextIQ() * fadeFactor; + } else { + sample = m_toneNco.nextQI() * fadeFactor; + } + } + } + else + { + sample.real(0.0f); + sample.imag(0.0f); + m_toneNco.setPhase(0); + } + } + + break; + case FreeDVModSettings::FreeDVModInputNone: + default: + sample.real(0.0f); + sample.imag(0.0f); + break; + } + + if ((m_settings.m_modAFInput == FreeDVModSettings::FreeDVModInputFile) + || (m_settings.m_modAFInput == FreeDVModSettings::FreeDVModInputAudio)) // real audio + { + if (m_settings.m_dsb) + { + n_out = m_DSBFilter->runDSB(ci, &filtered); + + if (n_out > 0) + { + memcpy((void *) m_DSBFilterBuffer, (const void *) filtered, n_out*sizeof(Complex)); + m_DSBFilterBufferIndex = 0; + } + + sample = m_DSBFilterBuffer[m_DSBFilterBufferIndex]; + m_DSBFilterBufferIndex++; + } + else + { + n_out = m_SSBFilter->runSSB(ci, &filtered, m_settings.m_usb); + + if (n_out > 0) + { + memcpy((void *) m_SSBFilterBuffer, (const void *) filtered, n_out*sizeof(Complex)); + m_SSBFilterBufferIndex = 0; + } + + sample = m_SSBFilterBuffer[m_SSBFilterBufferIndex]; + m_SSBFilterBufferIndex++; + } + + if (n_out > 0) + { + for (int i = 0; i < n_out; i++) + { + // Downsample by 2^(m_scaleLog2 - 1) for SSB band spectrum display + // smart decimation with bit gain using float arithmetic (23 bits significand) + + m_sum += filtered[i]; + + if (!(m_undersampleCount++ & decim_mask)) + { + Real avgr = (m_sum.real() / decim) * 0.891235351562f * SDR_TX_SCALEF; //scaling at -1 dB to account for possible filter overshoot + Real avgi = (m_sum.imag() / decim) * 0.891235351562f * SDR_TX_SCALEF; + + if (!m_settings.m_dsb & !m_settings.m_usb) + { // invert spectrum for LSB + m_sampleBuffer.push_back(Sample(avgi, avgr)); + } + else + { + m_sampleBuffer.push_back(Sample(avgr, avgi)); + } + + m_sum.real(0.0); + m_sum.imag(0.0); + } + } + } + } // Real audio + else if ((m_settings.m_modAFInput == FreeDVModSettings::FreeDVModInputTone) + || (m_settings.m_modAFInput == FreeDVModSettings::FreeDVModInputCWTone)) // tone + { + m_sum += sample; + + if (!(m_undersampleCount++ & decim_mask)) + { + Real avgr = (m_sum.real() / decim) * 0.891235351562f * SDR_TX_SCALEF; //scaling at -1 dB to account for possible filter overshoot + Real avgi = (m_sum.imag() / decim) * 0.891235351562f * SDR_TX_SCALEF; + + if (!m_settings.m_dsb & !m_settings.m_usb) + { // invert spectrum for LSB + m_sampleBuffer.push_back(Sample(avgi, avgr)); + } + else + { + m_sampleBuffer.push_back(Sample(avgr, avgi)); + } + + m_sum.real(0.0); + m_sum.imag(0.0); + } + + if (m_sumCount < (m_settings.m_dsb ? m_ssbFftLen : m_ssbFftLen>>1)) + { + n_out = 0; + m_sumCount++; + } + else + { + n_out = m_sumCount; + m_sumCount = 0; + } + } + + if (n_out > 0) + { + if (m_sampleSink != 0) + { + m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), !m_settings.m_dsb); + } + + m_sampleBuffer.clear(); + } +} + +void FreeDVMod::calculateLevel(Complex& sample) +{ + Real t = sample.real(); // TODO: possibly adjust depending on sample type + + if (m_levelCalcCount < m_levelNbSamples) + { + m_peakLevel = std::max(std::fabs(m_peakLevel), t); + m_levelSum += t * t; + m_levelCalcCount++; + } + else + { + qreal rmsLevel = sqrt(m_levelSum / m_levelNbSamples); + //qDebug("NFMMod::calculateLevel: %f %f", rmsLevel, m_peakLevel); + emit levelChanged(rmsLevel, m_peakLevel, m_levelNbSamples); + m_peakLevel = 0.0f; + m_levelSum = 0.0f; + m_levelCalcCount = 0; + } +} + +void FreeDVMod::start() +{ + qDebug() << "FreeDVMod::start: m_outputSampleRate: " << m_outputSampleRate + << " m_inputFrequencyOffset: " << m_settings.m_inputFrequencyOffset; + + m_audioFifo.clear(); + applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true); +} + +void FreeDVMod::stop() +{ +} + +bool FreeDVMod::handleMessage(const Message& cmd) +{ + if (UpChannelizer::MsgChannelizerNotification::match(cmd)) + { + UpChannelizer::MsgChannelizerNotification& notif = (UpChannelizer::MsgChannelizerNotification&) cmd; + qDebug() << "FreeDVMod::handleMessage: MsgChannelizerNotification"; + + applyChannelSettings(notif.getBasebandSampleRate(), notif.getSampleRate(), notif.getFrequencyOffset()); + + return true; + } + else if (MsgConfigureChannelizer::match(cmd)) + { + MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; + qDebug() << "FreeDVMod::handleMessage: MsgConfigureChannelizer: sampleRate: " << cfg.getSampleRate() + << " centerFrequency: " << cfg.getCenterFrequency(); + + m_channelizer->configure(m_channelizer->getInputMessageQueue(), + cfg.getSampleRate(), + cfg.getCenterFrequency()); + + return true; + } + else if (MsgConfigureFreeDVMod::match(cmd)) + { + MsgConfigureFreeDVMod& cfg = (MsgConfigureFreeDVMod&) cmd; + qDebug() << "FreeDVMod::handleMessage: MsgConfigureFreeDVMod"; + + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } + else if (MsgConfigureFileSourceName::match(cmd)) + { + MsgConfigureFileSourceName& conf = (MsgConfigureFileSourceName&) cmd; + m_fileName = conf.getFileName(); + openFileStream(); + return true; + } + else if (MsgConfigureFileSourceSeek::match(cmd)) + { + MsgConfigureFileSourceSeek& conf = (MsgConfigureFileSourceSeek&) cmd; + int seekPercentage = conf.getPercentage(); + seekFileStream(seekPercentage); + + return true; + } + else if (MsgConfigureFileSourceStreamTiming::match(cmd)) + { + std::size_t samplesCount; + + if (m_ifstream.eof()) { + samplesCount = m_fileSize / sizeof(Real); + } else { + samplesCount = m_ifstream.tellg() / sizeof(Real); + } + + if (getMessageQueueToGUI()) + { + MsgReportFileSourceStreamTiming *report; + report = MsgReportFileSourceStreamTiming::create(samplesCount); + getMessageQueueToGUI()->push(report); + } + + return true; + } + else if (CWKeyer::MsgConfigureCWKeyer::match(cmd)) + { + const CWKeyer::MsgConfigureCWKeyer& cfg = (CWKeyer::MsgConfigureCWKeyer&) cmd; + + if (m_settings.m_useReverseAPI) { + webapiReverseSendCWSettings(cfg.getSettings()); + } + + return true; + } + else if (DSPConfigureAudio::match(cmd)) + { + DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd; + uint32_t sampleRate = cfg.getSampleRate(); + + qDebug() << "FreeDVMod::handleMessage: DSPConfigureAudio:" + << " sampleRate: " << sampleRate; + + if (sampleRate != m_audioSampleRate) { + applyAudioSampleRate(sampleRate); + } + + return true; + } + else if (DSPSignalNotification::match(cmd)) + { + return true; + } + else + { + return false; + } +} + +void FreeDVMod::openFileStream() +{ + if (m_ifstream.is_open()) { + m_ifstream.close(); + } + + m_ifstream.open(m_fileName.toStdString().c_str(), std::ios::binary | std::ios::ate); + m_fileSize = m_ifstream.tellg(); + m_ifstream.seekg(0,std::ios_base::beg); + + m_sampleRate = 48000; // fixed rate + m_recordLength = m_fileSize / (sizeof(Real) * m_sampleRate); + + qDebug() << "FreeDVMod::openFileStream: " << m_fileName.toStdString().c_str() + << " fileSize: " << m_fileSize << "bytes" + << " length: " << m_recordLength << " seconds"; + + if (getMessageQueueToGUI()) + { + MsgReportFileSourceStreamData *report; + report = MsgReportFileSourceStreamData::create(m_sampleRate, m_recordLength); + getMessageQueueToGUI()->push(report); + } +} + +void FreeDVMod::seekFileStream(int seekPercentage) +{ + QMutexLocker mutexLocker(&m_settingsMutex); + + if (m_ifstream.is_open()) + { + int seekPoint = ((m_recordLength * seekPercentage) / 100) * m_sampleRate; + seekPoint *= sizeof(Real); + m_ifstream.clear(); + m_ifstream.seekg(seekPoint, std::ios::beg); + } +} + +void FreeDVMod::applyAudioSampleRate(int sampleRate) +{ + qDebug("FreeDVMod::applyAudioSampleRate: %d", sampleRate); + + + MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( + sampleRate, m_settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(channelConfigMsg); + + m_settingsMutex.lock(); + + m_interpolatorDistanceRemain = 0; + m_interpolatorConsumed = false; + m_interpolatorDistance = (Real) sampleRate / (Real) m_outputSampleRate; + m_interpolator.create(48, sampleRate, m_settings.m_bandwidth, 3.0); + + float band = m_settings.m_bandwidth; + float lowCutoff = m_settings.m_lowCutoff; + bool usb = m_settings.m_usb; + + if (band < 0) // negative means LSB + { + band = -band; // turn to positive + lowCutoff = -lowCutoff; + usb = false; // and take note of side band + } + else + { + usb = true; + } + + if (band < 100.0f) // at least 100 Hz + { + band = 100.0f; + lowCutoff = 0; + } + + if (band - lowCutoff < 100.0f) { + lowCutoff = band - 100.0f; + } + + m_SSBFilter->create_filter(lowCutoff / sampleRate, band / sampleRate); + m_DSBFilter->create_dsb_filter((2.0f * band) / sampleRate); + + m_settings.m_bandwidth = band; + m_settings.m_lowCutoff = lowCutoff; + m_settings.m_usb = usb; + + m_toneNco.setFreq(m_settings.m_toneFrequency, sampleRate); + m_cwKeyer.setSampleRate(sampleRate); + + m_agcStepLength = std::min(sampleRate/20, m_settings.m_agcTime/2); // 50 ms or half the AGC length whichever is smaller + + m_settingsMutex.unlock(); + + m_audioSampleRate = sampleRate; + + if (getMessageQueueToGUI()) + { + DSPConfigureAudio *cfg = new DSPConfigureAudio(m_audioSampleRate); + getMessageQueueToGUI()->push(cfg); + } +} + +void FreeDVMod::applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force) +{ + qDebug() << "FreeDVMod::applyChannelSettings:" + << " basebandSampleRate: " << basebandSampleRate + << " outputSampleRate: " << outputSampleRate + << " inputFrequencyOffset: " << inputFrequencyOffset; + + if ((inputFrequencyOffset != m_inputFrequencyOffset) || + (outputSampleRate != m_outputSampleRate) || force) + { + m_settingsMutex.lock(); + m_carrierNco.setFreq(inputFrequencyOffset, outputSampleRate); + m_settingsMutex.unlock(); + } + + if ((outputSampleRate != m_outputSampleRate) || force) + { + m_settingsMutex.lock(); + m_interpolatorDistanceRemain = 0; + m_interpolatorConsumed = false; + m_interpolatorDistance = (Real) m_audioSampleRate / (Real) outputSampleRate; + m_interpolator.create(48, m_audioSampleRate, m_settings.m_bandwidth, 3.0); + m_settingsMutex.unlock(); + } + + m_basebandSampleRate = basebandSampleRate; + m_outputSampleRate = outputSampleRate; + m_inputFrequencyOffset = inputFrequencyOffset; +} + +void FreeDVMod::applySettings(const FreeDVModSettings& settings, bool force) +{ + float band = settings.m_bandwidth; + float lowCutoff = settings.m_lowCutoff; + bool usb = settings.m_usb; + QList reverseAPIKeys; + + if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) { + reverseAPIKeys.append("inputFrequencyOffset"); + } + if ((settings.m_bandwidth != m_settings.m_bandwidth) || force) { + reverseAPIKeys.append("bandwidth"); + } + if ((settings.m_lowCutoff != m_settings.m_lowCutoff) || force) { + reverseAPIKeys.append("lowCutoff"); + } + if ((settings.m_usb != m_settings.m_usb) || force) { + reverseAPIKeys.append("usb"); + } + if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || force) { + reverseAPIKeys.append("toneFrequency"); + } + if ((settings.m_volumeFactor != m_settings.m_volumeFactor) || force) { + reverseAPIKeys.append("volumeFactor"); + } + if ((settings.m_spanLog2 != m_settings.m_spanLog2) || force) { + reverseAPIKeys.append("spanLog2"); + } + if ((settings.m_audioBinaural != m_settings.m_audioBinaural) || force) { + reverseAPIKeys.append("audioBinaural"); + } + if ((settings.m_audioFlipChannels != m_settings.m_audioFlipChannels) || force) { + reverseAPIKeys.append("audioFlipChannels"); + } + if ((settings.m_dsb != m_settings.m_dsb) || force) { + reverseAPIKeys.append("dsb"); + } + if ((settings.m_audioMute != m_settings.m_audioMute) || force) { + reverseAPIKeys.append("audioMute"); + } + if ((settings.m_playLoop != m_settings.m_playLoop) || force) { + reverseAPIKeys.append("playLoop"); + } + if ((settings.m_agc != m_settings.m_agc) || force) { + reverseAPIKeys.append("agc"); + } + if ((settings.m_agcOrder != m_settings.m_agcOrder) || force) { + reverseAPIKeys.append("agcOrder"); + } + if ((settings.m_agcTime != m_settings.m_agcTime) || force) { + reverseAPIKeys.append("agcTime"); + } + if ((settings.m_agcThresholdEnable != m_settings.m_agcThresholdEnable) || force) { + reverseAPIKeys.append("agcThresholdEnable"); + } + if ((settings.m_agcThreshold != m_settings.m_agcThreshold) || force) { + reverseAPIKeys.append("agcThreshold"); + } + if ((settings.m_agcThresholdGate != m_settings.m_agcThresholdGate) || force) { + reverseAPIKeys.append("agcThresholdGate"); + } + if ((settings.m_agcThresholdDelay != m_settings.m_agcThresholdDelay) || force) { + reverseAPIKeys.append("agcThresholdDelay"); + } + if ((settings.m_rgbColor != m_settings.m_rgbColor) || force) { + reverseAPIKeys.append("rgbColor"); + } + if ((settings.m_title != m_settings.m_title) || force) { + reverseAPIKeys.append("title"); + } + if ((settings.m_modAFInput != m_settings.m_modAFInput) || force) { + reverseAPIKeys.append("modAFInput"); + } + if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) { + reverseAPIKeys.append("audioDeviceName"); + } + + if ((settings.m_bandwidth != m_settings.m_bandwidth) || + (settings.m_lowCutoff != m_settings.m_lowCutoff) || force) + { + if (band < 0) // negative means LSB + { + band = -band; // turn to positive + lowCutoff = -lowCutoff; + usb = false; // and take note of side band + } + else + { + usb = true; + } + + if (band < 100.0f) // at least 100 Hz + { + band = 100.0f; + lowCutoff = 0; + } + + if (band - lowCutoff < 100.0f) { + lowCutoff = band - 100.0f; + } + + m_settingsMutex.lock(); + m_interpolatorDistanceRemain = 0; + m_interpolatorConsumed = false; + m_interpolatorDistance = (Real) m_audioSampleRate / (Real) m_outputSampleRate; + m_interpolator.create(48, m_audioSampleRate, band, 3.0); + m_SSBFilter->create_filter(lowCutoff / m_audioSampleRate, band / m_audioSampleRate); + m_DSBFilter->create_dsb_filter((2.0f * band) / m_audioSampleRate); + m_settingsMutex.unlock(); + } + + if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || force) + { + m_settingsMutex.lock(); + m_toneNco.setFreq(settings.m_toneFrequency, m_audioSampleRate); + m_settingsMutex.unlock(); + } + + if ((settings.m_dsb != m_settings.m_dsb) || force) + { + if (settings.m_dsb) + { + std::fill(m_DSBFilterBuffer, m_DSBFilterBuffer+m_ssbFftLen, Complex{0,0}); + m_DSBFilterBufferIndex = 0; + } + else + { + std::fill(m_SSBFilterBuffer, m_SSBFilterBuffer+(m_ssbFftLen>>1), Complex{0,0}); + m_SSBFilterBufferIndex = 0; + } + } + + if ((settings.m_agcTime != m_settings.m_agcTime) || + (settings.m_agcOrder != m_settings.m_agcOrder) || force) + { + m_settingsMutex.lock(); + m_inAGC.resize(settings.m_agcTime, m_agcStepLength, settings.m_agcOrder); + m_settingsMutex.unlock(); + } + + if ((settings.m_agcThresholdEnable != m_settings.m_agcThresholdEnable) || force) + { + m_inAGC.setThresholdEnable(settings.m_agcThresholdEnable); + } + + if ((settings.m_agcThreshold != m_settings.m_agcThreshold) || force) + { + m_inAGC.setThreshold(settings.m_agcThreshold); + } + + if ((settings.m_agcThresholdGate != m_settings.m_agcThresholdGate) || force) + { + m_inAGC.setGate(settings.m_agcThresholdGate); + } + + if ((settings.m_agcThresholdDelay != m_settings.m_agcThresholdDelay) || force) + { + m_inAGC.setStepDownDelay(settings.m_agcThresholdDelay); + } + + if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) + { + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + int audioDeviceIndex = audioDeviceManager->getInputDeviceIndex(settings.m_audioDeviceName); + audioDeviceManager->addAudioSource(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex); + uint32_t audioSampleRate = audioDeviceManager->getInputSampleRate(audioDeviceIndex); + + if (m_audioSampleRate != audioSampleRate) { + applyAudioSampleRate(audioSampleRate); + } + } + + if (settings.m_useReverseAPI) + { + bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || + (m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) || + (m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) || + (m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex) || + (m_settings.m_reverseAPIChannelIndex != settings.m_reverseAPIChannelIndex); + webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force); + } + + m_settings = settings; + m_settings.m_bandwidth = band; + m_settings.m_lowCutoff = lowCutoff; + m_settings.m_usb = usb; +} + +QByteArray FreeDVMod::serialize() const +{ + return m_settings.serialize(); +} + +bool FreeDVMod::deserialize(const QByteArray& data) +{ + if (m_settings.deserialize(data)) + { + MsgConfigureFreeDVMod *msg = MsgConfigureFreeDVMod::create(m_settings, true); + m_inputMessageQueue.push(msg); + return true; + } + else + { + m_settings.resetToDefaults(); + MsgConfigureFreeDVMod *msg = MsgConfigureFreeDVMod::create(m_settings, true); + m_inputMessageQueue.push(msg); + return false; + } +} + +int FreeDVMod::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setFreeDvModSettings(new SWGSDRangel::SWGFreeDVModSettings()); + response.getFreeDvModSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int FreeDVMod::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + FreeDVModSettings settings = m_settings; + bool frequencyOffsetChanged = false; + + if (channelSettingsKeys.contains("inputFrequencyOffset")) + { + settings.m_inputFrequencyOffset = response.getFreeDvModSettings()->getInputFrequencyOffset(); + frequencyOffsetChanged = true; + } + if (channelSettingsKeys.contains("bandwidth")) { + settings.m_bandwidth = response.getFreeDvModSettings()->getBandwidth(); + } + if (channelSettingsKeys.contains("lowCutoff")) { + settings.m_lowCutoff = response.getFreeDvModSettings()->getLowCutoff(); + } + if (channelSettingsKeys.contains("usb")) { + settings.m_usb = response.getFreeDvModSettings()->getUsb() != 0; + } + if (channelSettingsKeys.contains("toneFrequency")) { + settings.m_toneFrequency = response.getFreeDvModSettings()->getToneFrequency(); + } + if (channelSettingsKeys.contains("volumeFactor")) { + settings.m_volumeFactor = response.getFreeDvModSettings()->getVolumeFactor(); + } + if (channelSettingsKeys.contains("spanLog2")) { + settings.m_spanLog2 = response.getFreeDvModSettings()->getSpanLog2(); + } + if (channelSettingsKeys.contains("audioBinaural")) { + settings.m_audioBinaural = response.getFreeDvModSettings()->getAudioBinaural() != 0; + } + if (channelSettingsKeys.contains("audioFlipChannels")) { + settings.m_audioFlipChannels = response.getFreeDvModSettings()->getAudioFlipChannels() != 0; + } + if (channelSettingsKeys.contains("dsb")) { + settings.m_dsb = response.getFreeDvModSettings()->getDsb() != 0; + } + if (channelSettingsKeys.contains("audioMute")) { + settings.m_audioMute = response.getFreeDvModSettings()->getAudioMute() != 0; + } + if (channelSettingsKeys.contains("playLoop")) { + settings.m_playLoop = response.getFreeDvModSettings()->getPlayLoop() != 0; + } + if (channelSettingsKeys.contains("agc")) { + settings.m_agc = response.getFreeDvModSettings()->getAgc() != 0; + } + if (channelSettingsKeys.contains("agcOrder")) { + settings.m_agcOrder = response.getFreeDvModSettings()->getAgcOrder(); + } + if (channelSettingsKeys.contains("agcTime")) { + settings.m_agcTime = response.getFreeDvModSettings()->getAgcTime(); + } + if (channelSettingsKeys.contains("agcThresholdEnable")) { + settings.m_agcThresholdEnable = response.getFreeDvModSettings()->getAgcThresholdEnable() != 0; + } + if (channelSettingsKeys.contains("agcThreshold")) { + settings.m_agcThreshold = response.getFreeDvModSettings()->getAgcThreshold(); + } + if (channelSettingsKeys.contains("agcThresholdGate")) { + settings.m_agcThresholdGate = response.getFreeDvModSettings()->getAgcThresholdGate(); + } + if (channelSettingsKeys.contains("agcThresholdDelay")) { + settings.m_agcThresholdDelay = response.getFreeDvModSettings()->getAgcThresholdDelay(); + } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getFreeDvModSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getFreeDvModSettings()->getTitle(); + } + if (channelSettingsKeys.contains("modAFInput")) { + settings.m_modAFInput = (FreeDVModSettings::FreeDVModInputAF) response.getFreeDvModSettings()->getModAfInput(); + } + if (channelSettingsKeys.contains("audioDeviceName")) { + settings.m_audioDeviceName = *response.getFreeDvModSettings()->getAudioDeviceName(); + } + if (channelSettingsKeys.contains("useReverseAPI")) { + settings.m_useReverseAPI = response.getFreeDvModSettings()->getUseReverseApi() != 0; + } + if (channelSettingsKeys.contains("reverseAPIAddress")) { + settings.m_reverseAPIAddress = *response.getFreeDvModSettings()->getReverseApiAddress() != 0; + } + if (channelSettingsKeys.contains("reverseAPIPort")) { + settings.m_reverseAPIPort = response.getFreeDvModSettings()->getReverseApiPort(); + } + if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) { + settings.m_reverseAPIDeviceIndex = response.getFreeDvModSettings()->getReverseApiDeviceIndex(); + } + if (channelSettingsKeys.contains("reverseAPIChannelIndex")) { + settings.m_reverseAPIChannelIndex = response.getFreeDvModSettings()->getReverseApiChannelIndex(); + } + + if (channelSettingsKeys.contains("cwKeyer")) + { + SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = response.getFreeDvModSettings()->getCwKeyer(); + CWKeyerSettings cwKeyerSettings = m_cwKeyer.getSettings(); + + if (channelSettingsKeys.contains("cwKeyer.loop")) { + cwKeyerSettings.m_loop = apiCwKeyerSettings->getLoop() != 0; + } + if (channelSettingsKeys.contains("cwKeyer.mode")) { + cwKeyerSettings.m_mode = (CWKeyerSettings::CWMode) apiCwKeyerSettings->getMode(); + } + if (channelSettingsKeys.contains("cwKeyer.text")) { + cwKeyerSettings.m_text = *apiCwKeyerSettings->getText(); + } + if (channelSettingsKeys.contains("cwKeyer.sampleRate")) { + cwKeyerSettings.m_sampleRate = apiCwKeyerSettings->getSampleRate(); + } + if (channelSettingsKeys.contains("cwKeyer.wpm")) { + cwKeyerSettings.m_wpm = apiCwKeyerSettings->getWpm(); + } + + m_cwKeyer.setLoop(cwKeyerSettings.m_loop); + m_cwKeyer.setMode(cwKeyerSettings.m_mode); + m_cwKeyer.setSampleRate(cwKeyerSettings.m_sampleRate); + m_cwKeyer.setText(cwKeyerSettings.m_text); + m_cwKeyer.setWPM(cwKeyerSettings.m_wpm); + + if (m_guiMessageQueue) // forward to GUI if any + { + CWKeyer::MsgConfigureCWKeyer *msgCwKeyer = CWKeyer::MsgConfigureCWKeyer::create(cwKeyerSettings, force); + m_guiMessageQueue->push(msgCwKeyer); + } + } + + if (frequencyOffsetChanged) + { + FreeDVMod::MsgConfigureChannelizer *msgChan = FreeDVMod::MsgConfigureChannelizer::create( + m_audioSampleRate, settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(msgChan); + } + + MsgConfigureFreeDVMod *msg = MsgConfigureFreeDVMod::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureFreeDVMod *msgToGUI = MsgConfigureFreeDVMod::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int FreeDVMod::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setFreeDvModReport(new SWGSDRangel::SWGFreeDVModReport()); + response.getFreeDvModReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + +void FreeDVMod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const FreeDVModSettings& settings) +{ + response.getFreeDvModSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + response.getFreeDvModSettings()->setBandwidth(settings.m_bandwidth); + response.getFreeDvModSettings()->setLowCutoff(settings.m_lowCutoff); + response.getFreeDvModSettings()->setUsb(settings.m_usb ? 1 : 0); + response.getFreeDvModSettings()->setToneFrequency(settings.m_toneFrequency); + response.getFreeDvModSettings()->setVolumeFactor(settings.m_volumeFactor); + response.getFreeDvModSettings()->setSpanLog2(settings.m_spanLog2); + response.getFreeDvModSettings()->setAudioBinaural(settings.m_audioBinaural ? 1 : 0); + response.getFreeDvModSettings()->setAudioFlipChannels(settings.m_audioFlipChannels ? 1 : 0); + response.getFreeDvModSettings()->setDsb(settings.m_dsb ? 1 : 0); + response.getFreeDvModSettings()->setAudioMute(settings.m_audioMute ? 1 : 0); + response.getFreeDvModSettings()->setPlayLoop(settings.m_playLoop ? 1 : 0); + response.getFreeDvModSettings()->setAgc(settings.m_agc ? 1 : 0); + response.getFreeDvModSettings()->setAgcOrder(settings.m_agcOrder); + response.getFreeDvModSettings()->setAgcTime(settings.m_agcTime); + response.getFreeDvModSettings()->setAgcThresholdEnable(settings.m_agcThresholdEnable ? 1 : 0); + response.getFreeDvModSettings()->setAgcThreshold(settings.m_agcThreshold); + response.getFreeDvModSettings()->setAgcThresholdGate(settings.m_agcThresholdGate); + response.getFreeDvModSettings()->setAgcThresholdDelay(settings.m_agcThresholdDelay); + response.getFreeDvModSettings()->setRgbColor(settings.m_rgbColor); + + if (response.getFreeDvModSettings()->getTitle()) { + *response.getFreeDvModSettings()->getTitle() = settings.m_title; + } else { + response.getFreeDvModSettings()->setTitle(new QString(settings.m_title)); + } + + response.getFreeDvModSettings()->setModAfInput((int) settings.m_modAFInput); + + if (response.getFreeDvModSettings()->getAudioDeviceName()) { + *response.getFreeDvModSettings()->getAudioDeviceName() = settings.m_audioDeviceName; + } else { + response.getFreeDvModSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName)); + } + + if (!response.getFreeDvModSettings()->getCwKeyer()) { + response.getFreeDvModSettings()->setCwKeyer(new SWGSDRangel::SWGCWKeyerSettings); + } + + SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = response.getFreeDvModSettings()->getCwKeyer(); + const CWKeyerSettings& cwKeyerSettings = m_cwKeyer.getSettings(); + apiCwKeyerSettings->setLoop(cwKeyerSettings.m_loop ? 1 : 0); + apiCwKeyerSettings->setMode((int) cwKeyerSettings.m_mode); + apiCwKeyerSettings->setSampleRate(cwKeyerSettings.m_sampleRate); + + if (apiCwKeyerSettings->getText()) { + *apiCwKeyerSettings->getText() = cwKeyerSettings.m_text; + } else { + apiCwKeyerSettings->setText(new QString(cwKeyerSettings.m_text)); + } + + apiCwKeyerSettings->setWpm(cwKeyerSettings.m_wpm); + + response.getAmModSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0); + + if (response.getAmModSettings()->getReverseApiAddress()) { + *response.getAmModSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress; + } else { + response.getAmModSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress)); + } + + response.getAmModSettings()->setReverseApiPort(settings.m_reverseAPIPort); + response.getAmModSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex); + response.getAmModSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex); +} + +void FreeDVMod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + response.getFreeDvModReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq())); + response.getFreeDvModReport()->setAudioSampleRate(m_audioSampleRate); + response.getFreeDvModReport()->setChannelSampleRate(m_outputSampleRate); +} + +void FreeDVMod::webapiReverseSendSettings(QList& channelSettingsKeys, const FreeDVModSettings& settings, bool force) +{ + SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings(); + swgChannelSettings->setTx(1); + swgChannelSettings->setChannelType(new QString("FreeDVMod")); + swgChannelSettings->setFreeDvModSettings(new SWGSDRangel::SWGFreeDVModSettings()); + SWGSDRangel::SWGFreeDVModSettings *swgFreeDVModSettings = swgChannelSettings->getFreeDvModSettings(); + + // transfer data that has been modified. When force is on transfer all data except reverse API data + + if (channelSettingsKeys.contains("inputFrequencyOffset") || force) { + swgFreeDVModSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + } + if (channelSettingsKeys.contains("bandwidth") || force) { + swgFreeDVModSettings->setBandwidth(settings.m_bandwidth); + } + if (channelSettingsKeys.contains("lowCutoff") || force) { + swgFreeDVModSettings->setLowCutoff(settings.m_lowCutoff); + } + if (channelSettingsKeys.contains("usb") || force) { + swgFreeDVModSettings->setUsb(settings.m_usb ? 1 : 0); + } + if (channelSettingsKeys.contains("toneFrequency") || force) { + swgFreeDVModSettings->setToneFrequency(settings.m_toneFrequency); + } + if (channelSettingsKeys.contains("volumeFactor") || force) { + swgFreeDVModSettings->setVolumeFactor(settings.m_volumeFactor); + } + if (channelSettingsKeys.contains("spanLog2") || force) { + swgFreeDVModSettings->setSpanLog2(settings.m_spanLog2); + } + if (channelSettingsKeys.contains("audioBinaural") || force) { + swgFreeDVModSettings->setAudioBinaural(settings.m_audioBinaural ? 1 : 0); + } + if (channelSettingsKeys.contains("audioFlipChannels") || force) { + swgFreeDVModSettings->setAudioFlipChannels(settings.m_audioFlipChannels ? 1 : 0); + } + if (channelSettingsKeys.contains("dsb") || force) { + swgFreeDVModSettings->setDsb(settings.m_dsb ? 1 : 0); + } + if (channelSettingsKeys.contains("audioMute") || force) { + swgFreeDVModSettings->setAudioMute(settings.m_audioMute ? 1 : 0); + } + if (channelSettingsKeys.contains("playLoop") || force) { + swgFreeDVModSettings->setPlayLoop(settings.m_playLoop ? 1 : 0); + } + if (channelSettingsKeys.contains("agc") || force) { + swgFreeDVModSettings->setAgc(settings.m_agc ? 1 : 0); + } + if (channelSettingsKeys.contains("agcOrder") || force) { + swgFreeDVModSettings->setAgcOrder(settings.m_agcOrder); + } + if (channelSettingsKeys.contains("agcTime") || force) { + swgFreeDVModSettings->setAgcTime(settings.m_agcTime); + } + if (channelSettingsKeys.contains("agcThresholdEnable") || force) { + swgFreeDVModSettings->setAgcThresholdEnable(settings.m_agcThresholdEnable ? 1 : 0); + } + if (channelSettingsKeys.contains("agcThreshold") || force) { + swgFreeDVModSettings->setAgcThreshold(settings.m_agcThreshold); + } + if (channelSettingsKeys.contains("agcThresholdGate") || force) { + swgFreeDVModSettings->setAgcThresholdGate(settings.m_agcThresholdGate); + } + if (channelSettingsKeys.contains("agcThresholdDelay") || force) { + swgFreeDVModSettings->setAgcThresholdDelay(settings.m_agcThresholdDelay); + } + if (channelSettingsKeys.contains("rgbColor") || force) { + swgFreeDVModSettings->setRgbColor(settings.m_rgbColor); + } + if (channelSettingsKeys.contains("title") || force) { + swgFreeDVModSettings->setTitle(new QString(settings.m_title)); + } + if (channelSettingsKeys.contains("modAFInput") || force) { + swgFreeDVModSettings->setModAfInput((int) settings.m_modAFInput); + } + if (channelSettingsKeys.contains("audioDeviceName") || force) { + swgFreeDVModSettings->setAudioDeviceName(new QString(settings.m_audioDeviceName)); + } + + if (force) + { + const CWKeyerSettings& cwKeyerSettings = m_cwKeyer.getSettings(); + swgFreeDVModSettings->setCwKeyer(new SWGSDRangel::SWGCWKeyerSettings()); + SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = swgFreeDVModSettings->getCwKeyer(); + apiCwKeyerSettings->setLoop(cwKeyerSettings.m_loop ? 1 : 0); + apiCwKeyerSettings->setMode(cwKeyerSettings.m_mode); + apiCwKeyerSettings->setSampleRate(cwKeyerSettings.m_sampleRate); + apiCwKeyerSettings->setText(new QString(cwKeyerSettings.m_text)); + apiCwKeyerSettings->setWpm(cwKeyerSettings.m_wpm); + } + + QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings") + .arg(settings.m_reverseAPIAddress) + .arg(settings.m_reverseAPIPort) + .arg(settings.m_reverseAPIDeviceIndex) + .arg(settings.m_reverseAPIChannelIndex); + m_networkRequest.setUrl(QUrl(channelSettingsURL)); + m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QBuffer *buffer=new QBuffer(); + buffer->open((QBuffer::ReadWrite)); + buffer->write(swgChannelSettings->asJson().toUtf8()); + buffer->seek(0); + + // Always use PATCH to avoid passing reverse API settings + m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer); + + delete swgChannelSettings; +} + +void FreeDVMod::webapiReverseSendCWSettings(const CWKeyerSettings& cwKeyerSettings) +{ + SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings(); + swgChannelSettings->setTx(1); + swgChannelSettings->setChannelType(new QString("FreeDVMod")); + swgChannelSettings->setFreeDvModSettings(new SWGSDRangel::SWGFreeDVModSettings()); + SWGSDRangel::SWGFreeDVModSettings *swgFreeDVModSettings = swgChannelSettings->getFreeDvModSettings(); + + swgFreeDVModSettings->setCwKeyer(new SWGSDRangel::SWGCWKeyerSettings()); + SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = swgFreeDVModSettings->getCwKeyer(); + apiCwKeyerSettings->setLoop(cwKeyerSettings.m_loop ? 1 : 0); + apiCwKeyerSettings->setMode(cwKeyerSettings.m_mode); + apiCwKeyerSettings->setSampleRate(cwKeyerSettings.m_sampleRate); + apiCwKeyerSettings->setText(new QString(cwKeyerSettings.m_text)); + apiCwKeyerSettings->setWpm(cwKeyerSettings.m_wpm); + + QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings") + .arg(m_settings.m_reverseAPIAddress) + .arg(m_settings.m_reverseAPIPort) + .arg(m_settings.m_reverseAPIDeviceIndex) + .arg(m_settings.m_reverseAPIChannelIndex); + m_networkRequest.setUrl(QUrl(channelSettingsURL)); + m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QBuffer *buffer=new QBuffer(); + buffer->open((QBuffer::ReadWrite)); + buffer->write(swgChannelSettings->asJson().toUtf8()); + buffer->seek(0); + + // Always use PATCH to avoid passing reverse API settings + m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer); + + delete swgChannelSettings; +} + +void FreeDVMod::networkManagerFinished(QNetworkReply *reply) +{ + QNetworkReply::NetworkError replyError = reply->error(); + + if (replyError) + { + qWarning() << "FreeDVMod::networkManagerFinished:" + << " error(" << (int) replyError + << "): " << replyError + << ": " << reply->errorString(); + return; + } + + QString answer = reply->readAll(); + answer.chop(1); // remove last \n + qDebug("FreeDVMod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); +} diff --git a/plugins/channeltx/modfreedv/freedvmod.h b/plugins/channeltx/modfreedv/freedvmod.h new file mode 100644 index 000000000..a26427d4a --- /dev/null +++ b/plugins/channeltx/modfreedv/freedvmod.h @@ -0,0 +1,339 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_CHANNELTX_MODFREEDV_FREEDVMOD_H_ +#define PLUGINS_CHANNELTX_MODFREEDV_FREEDVMOD_H_ + +#include +#include +#include + +#include +#include + +#include "dsp/basebandsamplesource.h" +#include "channel/channelsourceapi.h" +#include "dsp/basebandsamplesink.h" +#include "dsp/ncof.h" +#include "dsp/interpolator.h" +#include "util/movingaverage.h" +#include "dsp/agc.h" +#include "dsp/fftfilt.h" +#include "dsp/cwkeyer.h" +#include "audio/audiofifo.h" +#include "util/message.h" + +#include "freedvmodsettings.h" + +class QNetworkAccessManager; +class QNetworkReply; +class DeviceSinkAPI; +class ThreadedBasebandSampleSource; +class UpChannelizer; + +class FreeDVMod : public BasebandSampleSource, public ChannelSourceAPI { + Q_OBJECT + +public: + class MsgConfigureFreeDVMod : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const FreeDVModSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureFreeDVMod* create(const FreeDVModSettings& settings, bool force) + { + return new MsgConfigureFreeDVMod(settings, force); + } + + private: + FreeDVModSettings m_settings; + bool m_force; + + MsgConfigureFreeDVMod(const FreeDVModSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgConfigureChannelizer : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getSampleRate() const { return m_sampleRate; } + int getCenterFrequency() const { return m_centerFrequency; } + + static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency) + { + return new MsgConfigureChannelizer(sampleRate, centerFrequency); + } + + private: + int m_sampleRate; + int m_centerFrequency; + + MsgConfigureChannelizer(int sampleRate, int centerFrequency) : + Message(), + m_sampleRate(sampleRate), + m_centerFrequency(centerFrequency) + { } + }; + + class MsgConfigureFileSourceName : public Message + { + MESSAGE_CLASS_DECLARATION + + public: + const QString& getFileName() const { return m_fileName; } + + static MsgConfigureFileSourceName* create(const QString& fileName) + { + return new MsgConfigureFileSourceName(fileName); + } + + private: + QString m_fileName; + + MsgConfigureFileSourceName(const QString& fileName) : + Message(), + m_fileName(fileName) + { } + }; + + class MsgConfigureFileSourceSeek : public Message + { + MESSAGE_CLASS_DECLARATION + + public: + int getPercentage() const { return m_seekPercentage; } + + static MsgConfigureFileSourceSeek* create(int seekPercentage) + { + return new MsgConfigureFileSourceSeek(seekPercentage); + } + + protected: + int m_seekPercentage; //!< percentage of seek position from the beginning 0..100 + + MsgConfigureFileSourceSeek(int seekPercentage) : + Message(), + m_seekPercentage(seekPercentage) + { } + }; + + class MsgConfigureFileSourceStreamTiming : public Message { + MESSAGE_CLASS_DECLARATION + + public: + + static MsgConfigureFileSourceStreamTiming* create() + { + return new MsgConfigureFileSourceStreamTiming(); + } + + private: + + MsgConfigureFileSourceStreamTiming() : + Message() + { } + }; + + class MsgReportFileSourceStreamTiming : public Message + { + MESSAGE_CLASS_DECLARATION + + public: + std::size_t getSamplesCount() const { return m_samplesCount; } + + static MsgReportFileSourceStreamTiming* create(std::size_t samplesCount) + { + return new MsgReportFileSourceStreamTiming(samplesCount); + } + + protected: + std::size_t m_samplesCount; + + MsgReportFileSourceStreamTiming(std::size_t samplesCount) : + Message(), + m_samplesCount(samplesCount) + { } + }; + + class MsgReportFileSourceStreamData : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getSampleRate() const { return m_sampleRate; } + quint32 getRecordLength() const { return m_recordLength; } + + static MsgReportFileSourceStreamData* create(int sampleRate, + quint32 recordLength) + { + return new MsgReportFileSourceStreamData(sampleRate, recordLength); + } + + protected: + int m_sampleRate; + quint32 m_recordLength; + + MsgReportFileSourceStreamData(int sampleRate, + quint32 recordLength) : + Message(), + m_sampleRate(sampleRate), + m_recordLength(recordLength) + { } + }; + + //================================================================= + + FreeDVMod(DeviceSinkAPI *deviceAPI); + ~FreeDVMod(); + virtual void destroy() { delete this; } + + void setSpectrumSampleSink(BasebandSampleSink* sampleSink) { m_sampleSink = sampleSink; } + + virtual void pull(Sample& sample); + virtual void pullAudio(int nbSamples); + virtual void start(); + virtual void stop(); + virtual bool handleMessage(const Message& cmd); + + virtual void getIdentifier(QString& id) { id = objectName(); } + virtual void getTitle(QString& title) { title = m_settings.m_title; } + virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; } + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + + uint32_t getAudioSampleRate() const { return m_audioSampleRate; } + double getMagSq() const { return m_magsq; } + + CWKeyer *getCWKeyer() { return &m_cwKeyer; } + + static const QString m_channelIdURI; + static const QString m_channelId; + +signals: + /** + * Level changed + * \param rmsLevel RMS level in range 0.0 - 1.0 + * \param peakLevel Peak level in range 0.0 - 1.0 + * \param numSamples Number of audio samples analyzed + */ + void levelChanged(qreal rmsLevel, qreal peakLevel, int numSamples); + + +private: + enum RateState { + RSInitialFill, + RSRunning + }; + + DeviceSinkAPI* m_deviceAPI; + ThreadedBasebandSampleSource* m_threadedChannelizer; + UpChannelizer* m_channelizer; + + int m_basebandSampleRate; + int m_outputSampleRate; + int m_inputFrequencyOffset; + FreeDVModSettings m_settings; + quint32 m_audioSampleRate; + + NCOF m_carrierNco; + NCOF m_toneNco; + Complex m_modSample; + Interpolator m_interpolator; + Real m_interpolatorDistance; + Real m_interpolatorDistanceRemain; + bool m_interpolatorConsumed; + fftfilt* m_SSBFilter; + fftfilt* m_DSBFilter; + Complex* m_SSBFilterBuffer; + Complex* m_DSBFilterBuffer; + int m_SSBFilterBufferIndex; + int m_DSBFilterBufferIndex; + static const int m_ssbFftLen; + + BasebandSampleSink* m_sampleSink; + SampleVector m_sampleBuffer; + + fftfilt::cmplx m_sum; + int m_undersampleCount; + int m_sumCount; + + double m_magsq; + MovingAverageUtil m_movingAverage; + + AudioVector m_audioBuffer; + uint m_audioBufferFill; + + AudioFifo m_audioFifo; + QMutex m_settingsMutex; + + std::ifstream m_ifstream; + QString m_fileName; + quint64 m_fileSize; //!< raw file size (bytes) + quint32 m_recordLength; //!< record length in seconds computed from file size + int m_sampleRate; + + quint32 m_levelCalcCount; + Real m_peakLevel; + Real m_levelSum; + CWKeyer m_cwKeyer; + + MagAGC m_inAGC; + int m_agcStepLength; + + QNetworkAccessManager *m_networkManager; + QNetworkRequest m_networkRequest; + + static const int m_levelNbSamples; + + void applyAudioSampleRate(int sampleRate); + void applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force = false); + void applySettings(const FreeDVModSettings& settings, bool force = false); + void pullAF(Complex& sample); + void calculateLevel(Complex& sample); + void modulateSample(); + void openFileStream(); + void seekFileStream(int seekPercentage); + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const FreeDVModSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); + void webapiReverseSendSettings(QList& channelSettingsKeys, const FreeDVModSettings& settings, bool force); + void webapiReverseSendCWSettings(const CWKeyerSettings& settings); + +private slots: + void networkManagerFinished(QNetworkReply *reply); +}; + + +#endif /* PLUGINS_CHANNELTX_MODFREEDV_FREEDVMOD_H_ */ diff --git a/plugins/channeltx/modfreedv/freedvmodgui.cpp b/plugins/channeltx/modfreedv/freedvmodgui.cpp new file mode 100644 index 000000000..866401d26 --- /dev/null +++ b/plugins/channeltx/modfreedv/freedvmodgui.cpp @@ -0,0 +1,796 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +#include "freedvmodgui.h" + +#include "device/devicesinkapi.h" +#include "device/deviceuiset.h" +#include "dsp/spectrumvis.h" +#include "ui_freedvmodgui.h" +#include "plugin/pluginapi.h" +#include "util/simpleserializer.h" +#include "util/db.h" +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "gui/crightclickenabler.h" +#include "gui/audioselectdialog.h" +#include "gui/basicchannelsettingsdialog.h" +#include "mainwindow.h" + +FreeDVModGUI* FreeDVModGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx) +{ + FreeDVModGUI* gui = new FreeDVModGUI(pluginAPI, deviceUISet, channelTx); + return gui; +} + +void FreeDVModGUI::destroy() +{ + delete this; +} + +void FreeDVModGUI::setName(const QString& name) +{ + setObjectName(name); +} + +QString FreeDVModGUI::getName() const +{ + return objectName(); +} + +qint64 FreeDVModGUI::getCenterFrequency() const { + return m_channelMarker.getCenterFrequency(); +} + +void FreeDVModGUI::setCenterFrequency(qint64 centerFrequency) +{ + m_channelMarker.setCenterFrequency(centerFrequency); + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); + applySettings(); +} + +void FreeDVModGUI::resetToDefaults() +{ + m_settings.resetToDefaults(); +} + +QByteArray FreeDVModGUI::serialize() const +{ + return m_settings.serialize(); +} + +bool FreeDVModGUI::deserialize(const QByteArray& data) +{ + if(m_settings.deserialize(data)) + { + qDebug("FreeDVModGUI::deserialize"); + displaySettings(); + applyBandwidths(5 - ui->spanLog2->value(), true); // does applySettings(true) + return true; + } + else + { + m_settings.resetToDefaults(); + displaySettings(); + applyBandwidths(5 - ui->spanLog2->value(), true); // does applySettings(true) + return false; + } +} + +bool FreeDVModGUI::handleMessage(const Message& message) +{ + if (FreeDVMod::MsgReportFileSourceStreamData::match(message)) + { + m_recordSampleRate = ((FreeDVMod::MsgReportFileSourceStreamData&)message).getSampleRate(); + m_recordLength = ((FreeDVMod::MsgReportFileSourceStreamData&)message).getRecordLength(); + m_samplesCount = 0; + updateWithStreamData(); + return true; + } + else if (FreeDVMod::MsgReportFileSourceStreamTiming::match(message)) + { + m_samplesCount = ((FreeDVMod::MsgReportFileSourceStreamTiming&)message).getSamplesCount(); + updateWithStreamTime(); + return true; + } + else if (DSPConfigureAudio::match(message)) + { + qDebug("FreeDVModGUI::handleMessage: DSPConfigureAudio: %d", m_freeDVMod->getAudioSampleRate()); + applyBandwidths(5 - ui->spanLog2->value()); // will update spectrum details with new sample rate + return true; + } + else if (FreeDVMod::MsgConfigureFreeDVMod::match(message)) + { + const FreeDVMod::MsgConfigureFreeDVMod& cfg = (FreeDVMod::MsgConfigureFreeDVMod&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else if (CWKeyer::MsgConfigureCWKeyer::match(message)) + { + const CWKeyer::MsgConfigureCWKeyer& cfg = (CWKeyer::MsgConfigureCWKeyer&) message; + ui->cwKeyerGUI->displaySettings(cfg.getSettings()); + return true; + } + else + { + return false; + } +} + +void FreeDVModGUI::channelMarkerChangedByCursor() +{ + ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); + applySettings(); +} + +void FreeDVModGUI::channelMarkerUpdate() +{ + m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); + displaySettings(); + applySettings(); +} + +void FreeDVModGUI::handleSourceMessages() +{ + Message* message; + + while ((message = getInputMessageQueue()->pop()) != 0) + { + if (handleMessage(*message)) + { + delete message; + } + } +} + +void FreeDVModGUI::on_deltaFrequency_changed(qint64 value) +{ + m_channelMarker.setCenterFrequency(value); + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); + applySettings(); +} + +void FreeDVModGUI::on_flipSidebands_clicked(bool checked) +{ + (void) checked; + int bwValue = ui->BW->value(); + int lcValue = ui->lowCut->value(); + ui->BW->setValue(-bwValue); + ui->lowCut->setValue(-lcValue); +} + +void FreeDVModGUI::on_dsb_toggled(bool dsb) +{ + ui->flipSidebands->setEnabled(!dsb); + applyBandwidths(5 - ui->spanLog2->value()); +} + +void FreeDVModGUI::on_audioBinaural_toggled(bool checked) +{ + m_settings.m_audioBinaural = checked; + applySettings(); +} + +void FreeDVModGUI::on_audioFlipChannels_toggled(bool checked) +{ + m_settings.m_audioFlipChannels = checked; + applySettings(); +} + +void FreeDVModGUI::on_spanLog2_valueChanged(int value) +{ + if ((value < 0) || (value > 4)) { + return; + } + + applyBandwidths(5 - value); +} + +void FreeDVModGUI::on_BW_valueChanged(int value) +{ + (void) value; + applyBandwidths(5 - ui->spanLog2->value()); +} + +void FreeDVModGUI::on_lowCut_valueChanged(int value) +{ + (void) value; + applyBandwidths(5 - ui->spanLog2->value()); +} + +void FreeDVModGUI::on_toneFrequency_valueChanged(int value) +{ + ui->toneFrequencyText->setText(QString("%1k").arg(value / 100.0, 0, 'f', 2)); + m_settings.m_toneFrequency = value * 10.0; + applySettings(); +} + +void FreeDVModGUI::on_volume_valueChanged(int value) +{ + ui->volumeText->setText(QString("%1").arg(value / 10.0, 0, 'f', 1)); + m_settings.m_volumeFactor = value / 10.0; + applySettings(); +} + +void FreeDVModGUI::on_audioMute_toggled(bool checked) +{ + m_settings.m_audioMute = checked; + applySettings(); +} + +void FreeDVModGUI::on_playLoop_toggled(bool checked) +{ + m_settings.m_playLoop = checked; + applySettings(); +} + +void FreeDVModGUI::on_play_toggled(bool checked) +{ + ui->tone->setEnabled(!checked); // release other source inputs + ui->morseKeyer->setEnabled(!checked); + ui->mic->setEnabled(!checked); + m_settings.m_modAFInput = checked ? FreeDVModSettings::FreeDVModInputFile : FreeDVModSettings::FreeDVModInputNone; + applySettings(); + ui->navTimeSlider->setEnabled(!checked); + m_enableNavTime = !checked; +} + +void FreeDVModGUI::on_tone_toggled(bool checked) +{ + ui->play->setEnabled(!checked); // release other source inputs + ui->morseKeyer->setEnabled(!checked); + ui->mic->setEnabled(!checked); + m_settings.m_modAFInput = checked ? FreeDVModSettings::FreeDVModInputTone : FreeDVModSettings::FreeDVModInputNone; + applySettings(); +} + +void FreeDVModGUI::on_morseKeyer_toggled(bool checked) +{ + ui->play->setEnabled(!checked); // release other source inputs + ui->tone->setEnabled(!checked); // release other source inputs + ui->mic->setEnabled(!checked); + m_settings.m_modAFInput = checked ? FreeDVModSettings::FreeDVModInputCWTone : FreeDVModSettings::FreeDVModInputNone; + applySettings(); +} + +void FreeDVModGUI::on_mic_toggled(bool checked) +{ + ui->play->setEnabled(!checked); // release other source inputs + ui->morseKeyer->setEnabled(!checked); + ui->tone->setEnabled(!checked); // release other source inputs + m_settings.m_modAFInput = checked ? FreeDVModSettings::FreeDVModInputAudio : FreeDVModSettings::FreeDVModInputNone; + applySettings(); +} + +void FreeDVModGUI::on_agc_toggled(bool checked) +{ + m_settings.m_agc = checked; + applySettings(); +} + +void FreeDVModGUI::on_agcOrder_valueChanged(int value){ + QString s = QString::number(value / 100.0, 'f', 2); + ui->agcOrderText->setText(s); + m_settings.m_agcOrder = value / 100.0; + applySettings(); +} + +void FreeDVModGUI::on_agcTime_valueChanged(int value){ + QString s = QString::number(FreeDVModSettings::getAGCTimeConstant(value), 'f', 0); + ui->agcTimeText->setText(s); + m_settings.m_agcTime = FreeDVModSettings::getAGCTimeConstant(value) * 48; + applySettings(); +} + +void FreeDVModGUI::on_agcThreshold_valueChanged(int value) +{ + m_settings.m_agcThreshold = value; // dB + displayAGCPowerThreshold(); + applySettings(); +} + +void FreeDVModGUI::on_agcThresholdGate_valueChanged(int value) +{ + QString s = QString::number(value, 'f', 0); + ui->agcThresholdGateText->setText(s); + m_settings.m_agcThresholdGate = value * 48; + applySettings(); +} + +void FreeDVModGUI::on_agcThresholdDelay_valueChanged(int value) +{ + QString s = QString::number(value * 10, 'f', 0); + ui->agcThresholdDelayText->setText(s); + m_settings.m_agcThresholdDelay = value * 480; + applySettings(); +} + +void FreeDVModGUI::on_navTimeSlider_valueChanged(int value) +{ + if (m_enableNavTime && ((value >= 0) && (value <= 100))) + { + int t_sec = (m_recordLength * value) / 100; + QTime t(0, 0, 0, 0); + t = t.addSecs(t_sec); + + FreeDVMod::MsgConfigureFileSourceSeek* message = FreeDVMod::MsgConfigureFileSourceSeek::create(value); + m_freeDVMod->getInputMessageQueue()->push(message); + } +} + +void FreeDVModGUI::on_showFileDialog_clicked(bool checked) +{ + (void) checked; + QString fileName = QFileDialog::getOpenFileName(this, + tr("Open raw audio file"), ".", tr("Raw audio Files (*.raw)"), 0, QFileDialog::DontUseNativeDialog); + + if (fileName != "") + { + m_fileName = fileName; + ui->recordFileText->setText(m_fileName); + ui->play->setEnabled(true); + configureFileName(); + } +} + +void FreeDVModGUI::configureFileName() +{ + qDebug() << "FileSourceGui::configureFileName: " << m_fileName.toStdString().c_str(); + FreeDVMod::MsgConfigureFileSourceName* message = FreeDVMod::MsgConfigureFileSourceName::create(m_fileName); + m_freeDVMod->getInputMessageQueue()->push(message); +} + +void FreeDVModGUI::onWidgetRolled(QWidget* widget, bool rollDown) +{ + (void) widget; + (void) rollDown; +} + +void FreeDVModGUI::onMenuDialogCalled(const QPoint &p) +{ + BasicChannelSettingsDialog dialog(&m_channelMarker, this); + dialog.setUseReverseAPI(m_settings.m_useReverseAPI); + dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress); + dialog.setReverseAPIPort(m_settings.m_reverseAPIPort); + dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex); + dialog.setReverseAPIChannelIndex(m_settings.m_reverseAPIChannelIndex); + + dialog.move(p); + dialog.exec(); + + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); + m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); + m_settings.m_title = m_channelMarker.getTitle(); + m_settings.m_useReverseAPI = dialog.useReverseAPI(); + m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress(); + m_settings.m_reverseAPIPort = dialog.getReverseAPIPort(); + m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex(); + m_settings.m_reverseAPIChannelIndex = dialog.getReverseAPIChannelIndex(); + + setWindowTitle(m_settings.m_title); + setTitleColor(m_settings.m_rgbColor); + + applySettings(); +} + +FreeDVModGUI::FreeDVModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent) : + RollupWidget(parent), + ui(new Ui::FreeDVModGUI), + m_pluginAPI(pluginAPI), + m_deviceUISet(deviceUISet), + m_channelMarker(this), + m_doApplySettings(true), + m_spectrumRate(6000), + m_recordLength(0), + m_recordSampleRate(48000), + m_samplesCount(0), + m_tickCount(0), + m_enableNavTime(false) +{ + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose, true); + connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); + + m_spectrumVis = new SpectrumVis(SDR_TX_SCALEF, ui->glSpectrum); + m_freeDVMod = (FreeDVMod*) channelTx; + m_freeDVMod->setSpectrumSampleSink(m_spectrumVis); + m_freeDVMod->setMessageQueueToGUI(getInputMessageQueue()); + + resetToDefaults(); + + ui->glSpectrum->setCenterFrequency(m_spectrumRate/2); + ui->glSpectrum->setSampleRate(m_spectrumRate); + ui->glSpectrum->setDisplayWaterfall(true); + ui->glSpectrum->setDisplayMaxHold(true); + ui->glSpectrum->setSsbSpectrum(true); + ui->glSpectrum->connectTimer(MainWindow::getInstance()->getMasterTimer()); + + connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); + + CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->mic); + connect(audioMuteRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(audioSelect())); + + ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); + ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); + ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999); + + m_channelMarker.blockSignals(true); + m_channelMarker.setColor(Qt::darkCyan); + m_channelMarker.setBandwidth(m_spectrumRate); + m_channelMarker.setSidebands(ChannelMarker::usb); + m_channelMarker.setCenterFrequency(0); + m_channelMarker.setTitle("FreeDV Modulator"); + m_channelMarker.blockSignals(false); + m_channelMarker.setVisible(true); + + setTitleColor(m_channelMarker.getColor()); + + m_deviceUISet->registerTxChannelInstance(FreeDVMod::m_channelIdURI, this); + m_deviceUISet->addChannelMarker(&m_channelMarker); + m_deviceUISet->addRollupWidget(this); + + connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor())); + + ui->cwKeyerGUI->setBuddies(m_freeDVMod->getInputMessageQueue(), m_freeDVMod->getCWKeyer()); + ui->spectrumGUI->setBuddies(m_spectrumVis->getInputMessageQueue(), m_spectrumVis, ui->glSpectrum); + + m_settings.setChannelMarker(&m_channelMarker); + m_settings.setSpectrumGUI(ui->spectrumGUI); + m_settings.setCWKeyerGUI(ui->cwKeyerGUI); + + connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages())); + connect(m_freeDVMod, SIGNAL(levelChanged(qreal, qreal, int)), ui->volumeMeter, SLOT(levelChanged(qreal, qreal, int))); + + m_iconDSBUSB.addPixmap(QPixmap("://dsb.png"), QIcon::Normal, QIcon::On); + m_iconDSBUSB.addPixmap(QPixmap("://usb.png"), QIcon::Normal, QIcon::Off); + m_iconDSBLSB.addPixmap(QPixmap("://dsb.png"), QIcon::Normal, QIcon::On); + m_iconDSBLSB.addPixmap(QPixmap("://lsb.png"), QIcon::Normal, QIcon::Off); + + displaySettings(); + applyBandwidths(5 - ui->spanLog2->value(), true); // does applySettings(true) +} + +FreeDVModGUI::~FreeDVModGUI() +{ + m_deviceUISet->removeTxChannelInstance(this); + delete m_freeDVMod; // TODO: check this: when the GUI closes it has to delete the modulator + delete m_spectrumVis; + delete ui; +} + +bool FreeDVModGUI::blockApplySettings(bool block) +{ + bool ret = !m_doApplySettings; + m_doApplySettings = !block; + return ret; +} + +void FreeDVModGUI::applySettings(bool force) +{ + if (m_doApplySettings) + { + FreeDVMod::MsgConfigureChannelizer *msgChan = FreeDVMod::MsgConfigureChannelizer::create( + 48000, m_settings.m_inputFrequencyOffset); + m_freeDVMod->getInputMessageQueue()->push(msgChan); + + FreeDVMod::MsgConfigureFreeDVMod *msg = FreeDVMod::MsgConfigureFreeDVMod::create(m_settings, force); + m_freeDVMod->getInputMessageQueue()->push(msg); + } +} + +void FreeDVModGUI::applyBandwidths(int spanLog2, bool force) +{ + bool dsb = ui->dsb->isChecked(); + m_spectrumRate = m_freeDVMod->getAudioSampleRate() / (1<BW->value(); + int lw = ui->lowCut->value(); + int bwMax = m_freeDVMod->getAudioSampleRate() / (100*(1<BW->setTickInterval(tickInterval); + ui->lowCut->setTickInterval(tickInterval); + + bw = bw < -bwMax ? -bwMax : bw > bwMax ? bwMax : bw; + + if (bw < 0) { + lw = lw < bw+1 ? bw+1 : lw < 0 ? lw : 0; + } else if (bw > 0) { + lw = lw > bw-1 ? bw-1 : lw < 0 ? 0 : lw; + } else { + lw = 0; + } + + if (dsb) + { + bw = bw < 0 ? -bw : bw; + lw = 0; + } + + QString spanStr = QString::number(bwMax/10.0, 'f', 1); + QString bwStr = QString::number(bw/10.0, 'f', 1); + QString lwStr = QString::number(lw/10.0, 'f', 1); + + if (dsb) + { + ui->BWText->setText(tr("%1%2k").arg(QChar(0xB1, 0x00)).arg(bwStr)); + ui->spanText->setText(tr("%1%2k").arg(QChar(0xB1, 0x00)).arg(spanStr)); + ui->scaleMinus->setText("0"); + ui->scaleCenter->setText(""); + ui->scalePlus->setText(tr("%1").arg(QChar(0xB1, 0x00))); + ui->lsbLabel->setText(""); + ui->usbLabel->setText(""); + ui->glSpectrum->setCenterFrequency(0); + ui->glSpectrum->setSampleRate(2*m_spectrumRate); + ui->glSpectrum->setSsbSpectrum(false); + ui->glSpectrum->setLsbDisplay(false); + } + else + { + ui->BWText->setText(tr("%1k").arg(bwStr)); + ui->spanText->setText(tr("%1k").arg(spanStr)); + ui->scaleMinus->setText("-"); + ui->scaleCenter->setText("0"); + ui->scalePlus->setText("+"); + ui->lsbLabel->setText("LSB"); + ui->usbLabel->setText("USB"); + ui->glSpectrum->setCenterFrequency(m_spectrumRate/2); + ui->glSpectrum->setSampleRate(m_spectrumRate); + ui->glSpectrum->setSsbSpectrum(true); + ui->glSpectrum->setLsbDisplay(bw < 0); + } + + ui->lowCutText->setText(tr("%1k").arg(lwStr)); + + + ui->BW->blockSignals(true); + ui->lowCut->blockSignals(true); + + ui->BW->setMaximum(bwMax); + ui->BW->setMinimum(dsb ? 0 : -bwMax); + ui->BW->setValue(bw); + + ui->lowCut->setMaximum(dsb ? 0 : bwMax); + ui->lowCut->setMinimum(dsb ? 0 : -bwMax); + ui->lowCut->setValue(lw); + + ui->lowCut->blockSignals(false); + ui->BW->blockSignals(false); + + + m_settings.m_dsb = dsb; + m_settings.m_spanLog2 = spanLog2; + m_settings.m_bandwidth = bw * 100; + m_settings.m_lowCutoff = lw * 100; + + applySettings(force); + + bool applySettingsWereBlocked = blockApplySettings(true); + m_channelMarker.setBandwidth(bw * 200); + m_channelMarker.setSidebands(dsb ? ChannelMarker::dsb : bw < 0 ? ChannelMarker::lsb : ChannelMarker::usb); + ui->dsb->setIcon(bw < 0 ? m_iconDSBLSB : m_iconDSBUSB); + if (!dsb) { m_channelMarker.setLowCutoff(lw * 100); } + blockApplySettings(applySettingsWereBlocked); +} + +void FreeDVModGUI::displaySettings() +{ + m_channelMarker.blockSignals(true); + m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset); + m_channelMarker.setTitle(m_settings.m_title); + m_channelMarker.setBandwidth(m_settings.m_bandwidth * 2); + m_channelMarker.setLowCutoff(m_settings.m_lowCutoff); + + ui->flipSidebands->setEnabled(!m_settings.m_dsb); + + if (m_settings.m_dsb) { + m_channelMarker.setSidebands(ChannelMarker::dsb); + } else { + if (m_settings.m_bandwidth < 0) { + m_channelMarker.setSidebands(ChannelMarker::lsb); + ui->dsb->setIcon(m_iconDSBLSB); + } else { + m_channelMarker.setSidebands(ChannelMarker::usb); + ui->dsb->setIcon(m_iconDSBUSB); + } + } + + m_channelMarker.blockSignals(false); + m_channelMarker.setColor(m_settings.m_rgbColor); + + setTitleColor(m_settings.m_rgbColor); + setWindowTitle(m_channelMarker.getTitle()); + + blockApplySettings(true); + + QString s = QString::number(m_settings.m_agcTime / 48, 'f', 0); + ui->agcTimeText->setText(s); + ui->agcTime->setValue(FreeDVModSettings::getAGCTimeConstantIndex(m_settings.m_agcTime / 48)); + displayAGCPowerThreshold(); + s = QString::number(m_settings.m_agcThresholdGate / 48, 'f', 0); + ui->agcThresholdGateText->setText(s); + ui->agcThresholdGate->setValue(m_settings.m_agcThresholdGate / 48); + s = QString::number(m_settings.m_agcThresholdDelay / 48, 'f', 0); + ui->agcThresholdDelayText->setText(s); + ui->agcThresholdDelay->setValue(m_settings.m_agcThresholdDelay / 480); + s = QString::number(m_settings.m_agcOrder, 'f', 2); + ui->agcOrderText->setText(s); + ui->agcOrder->setValue(roundf(m_settings.m_agcOrder * 100.0)); + + ui->agc->setChecked(m_settings.m_agc); + ui->audioBinaural->setChecked(m_settings.m_audioBinaural); + ui->audioFlipChannels->setChecked(m_settings.m_audioFlipChannels); + ui->audioMute->setChecked(m_settings.m_audioMute); + ui->playLoop->setChecked(m_settings.m_playLoop); + + // Prevent uncontrolled triggering of applyBandwidths + ui->spanLog2->blockSignals(true); + ui->dsb->blockSignals(true); + ui->BW->blockSignals(true); + + ui->dsb->setChecked(m_settings.m_dsb); + ui->spanLog2->setValue(5 - m_settings.m_spanLog2); + + ui->BW->setValue(roundf(m_settings.m_bandwidth/100.0)); + s = QString::number(m_settings.m_bandwidth/1000.0, 'f', 1); + + if (m_settings.m_dsb) + { + ui->BWText->setText(tr("%1%2k").arg(QChar(0xB1, 0x00)).arg(s)); + } + else + { + ui->BWText->setText(tr("%1k").arg(s)); + } + + ui->spanLog2->blockSignals(false); + ui->dsb->blockSignals(false); + ui->BW->blockSignals(false); + + // The only one of the four signals triggering applyBandwidths will trigger it once only with all other values + // set correctly and therefore validate the settings and apply them to dependent widgets + ui->lowCut->setValue(m_settings.m_lowCutoff / 100.0); + ui->lowCutText->setText(tr("%1k").arg(m_settings.m_lowCutoff / 1000.0)); + + ui->deltaFrequency->setValue(m_settings.m_inputFrequencyOffset); + + ui->toneFrequency->setValue(roundf(m_settings.m_toneFrequency / 10.0)); + ui->toneFrequencyText->setText(QString("%1k").arg(m_settings.m_toneFrequency / 1000.0, 0, 'f', 2)); + + ui->volume->setValue(m_settings.m_volumeFactor * 10.0); + ui->volumeText->setText(QString("%1").arg(m_settings.m_volumeFactor, 0, 'f', 1)); + + ui->tone->setEnabled((m_settings.m_modAFInput == FreeDVModSettings::FreeDVModInputAF::FreeDVModInputTone) + || (m_settings.m_modAFInput == FreeDVModSettings::FreeDVModInputAF::FreeDVModInputNone)); + ui->mic->setEnabled((m_settings.m_modAFInput == FreeDVModSettings::FreeDVModInputAF::FreeDVModInputAudio) + || (m_settings.m_modAFInput == FreeDVModSettings::FreeDVModInputAF::FreeDVModInputNone)); + ui->play->setEnabled((m_settings.m_modAFInput == FreeDVModSettings::FreeDVModInputAF::FreeDVModInputFile) + || (m_settings.m_modAFInput == FreeDVModSettings::FreeDVModInputAF::FreeDVModInputNone)); + ui->morseKeyer->setEnabled((m_settings.m_modAFInput == FreeDVModSettings::FreeDVModInputAF::FreeDVModInputCWTone) + || (m_settings.m_modAFInput == FreeDVModSettings::FreeDVModInputAF::FreeDVModInputNone)); + + ui->tone->setChecked(m_settings.m_modAFInput == FreeDVModSettings::FreeDVModInputAF::FreeDVModInputTone); + ui->mic->setChecked(m_settings.m_modAFInput == FreeDVModSettings::FreeDVModInputAF::FreeDVModInputAudio); + ui->play->setChecked(m_settings.m_modAFInput == FreeDVModSettings::FreeDVModInputAF::FreeDVModInputFile); + ui->morseKeyer->setChecked(m_settings.m_modAFInput == FreeDVModSettings::FreeDVModInputAF::FreeDVModInputCWTone); + + blockApplySettings(false); +} + +void FreeDVModGUI::displayAGCPowerThreshold() +{ + if (m_settings.m_agcThreshold == -99) + { + ui->agcThresholdText->setText("---"); + } + else + { + QString s = QString::number(m_settings.m_agcThreshold, 'f', 0); + ui->agcThresholdText->setText(s); + } + + ui->agcThreshold->setValue(m_settings.m_agcThreshold); +} + +void FreeDVModGUI::leaveEvent(QEvent*) +{ + m_channelMarker.setHighlighted(false); +} + +void FreeDVModGUI::enterEvent(QEvent*) +{ + m_channelMarker.setHighlighted(true); +} + +void FreeDVModGUI::audioSelect() +{ + qDebug("FreeDVModGUI::audioSelect"); + AudioSelectDialog audioSelect(DSPEngine::instance()->getAudioDeviceManager(), m_settings.m_audioDeviceName, true); // true for input + audioSelect.exec(); + + if (audioSelect.m_selected) + { + m_settings.m_audioDeviceName = audioSelect.m_audioDeviceName; + applySettings(); + } +} + +void FreeDVModGUI::tick() +{ + double powDb = CalcDb::dbPower(m_freeDVMod->getMagSq()); + m_channelPowerDbAvg(powDb); + ui->channelPower->setText(tr("%1 dB").arg(m_channelPowerDbAvg.asDouble(), 0, 'f', 1)); + + if (((++m_tickCount & 0xf) == 0) && (m_settings.m_modAFInput == FreeDVModSettings::FreeDVModInputFile)) + { + FreeDVMod::MsgConfigureFileSourceStreamTiming* message = FreeDVMod::MsgConfigureFileSourceStreamTiming::create(); + m_freeDVMod->getInputMessageQueue()->push(message); + } +} + +void FreeDVModGUI::updateWithStreamData() +{ + QTime recordLength(0, 0, 0, 0); + recordLength = recordLength.addSecs(m_recordLength); + QString s_time = recordLength.toString("HH:mm:ss"); + ui->recordLengthText->setText(s_time); + updateWithStreamTime(); +} + +void FreeDVModGUI::updateWithStreamTime() +{ + int t_sec = 0; + int t_msec = 0; + + if (m_recordSampleRate > 0) + { + t_msec = ((m_samplesCount * 1000) / m_recordSampleRate) % 1000; + t_sec = m_samplesCount / m_recordSampleRate; + } + + QTime t(0, 0, 0, 0); + t = t.addSecs(t_sec); + t = t.addMSecs(t_msec); + QString s_timems = t.toString("HH:mm:ss.zzz"); + QString s_time = t.toString("HH:mm:ss"); + ui->relTimeText->setText(s_timems); + + if (!m_enableNavTime) + { + float posRatio = (float) t_sec / (float) m_recordLength; + ui->navTimeSlider->setValue((int) (posRatio * 100.0)); + } +} diff --git a/plugins/channeltx/modfreedv/freedvmodgui.h b/plugins/channeltx/modfreedv/freedvmodgui.h new file mode 100644 index 000000000..6b3394d62 --- /dev/null +++ b/plugins/channeltx/modfreedv/freedvmodgui.h @@ -0,0 +1,136 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_CHANNELTX_MODFREEDV_FREEDVMODGUI_H_ +#define PLUGINS_CHANNELTX_MODFREEDV_FREEDVMODGUI_H_ + +#include + +#include +#include "gui/rollupwidget.h" +#include "dsp/channelmarker.h" +#include "util/movingaverage.h" +#include "util/messagequeue.h" + +#include "freedvmod.h" +#include "freedvmodsettings.h" + +class PluginAPI; +class DeviceUISet; +class BasebandSampleSource; +class SpectrumVis; + +namespace Ui { + class FreeDVModGUI; +} + +class FreeDVModGUI : public RollupWidget, public PluginInstanceGUI { + Q_OBJECT + +public: + static FreeDVModGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx); + virtual void destroy(); + + void setName(const QString& name); + QString getName() const; + virtual qint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency); + + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + virtual bool handleMessage(const Message& message); + +public slots: + void channelMarkerChangedByCursor(); + +private: + Ui::FreeDVModGUI* ui; + PluginAPI* m_pluginAPI; + DeviceUISet* m_deviceUISet; + ChannelMarker m_channelMarker; + FreeDVModSettings m_settings; + bool m_doApplySettings; + int m_spectrumRate; + + SpectrumVis* m_spectrumVis; + FreeDVMod* m_freeDVMod; + MovingAverageUtil m_channelPowerDbAvg; + + QString m_fileName; + quint32 m_recordLength; + int m_recordSampleRate; + int m_samplesCount; + std::size_t m_tickCount; + bool m_enableNavTime; + MessageQueue m_inputMessageQueue; + + QIcon m_iconDSBUSB; + QIcon m_iconDSBLSB; + + explicit FreeDVModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent = 0); + virtual ~FreeDVModGUI(); + + bool blockApplySettings(bool block); + void applySettings(bool force = false); + void applyBandwidths(int spanLog2, bool force = false); + void displaySettings(); + void displayAGCPowerThreshold(); + void updateWithStreamData(); + void updateWithStreamTime(); + void channelMarkerUpdate(); + + void leaveEvent(QEvent*); + void enterEvent(QEvent*); + +private slots: + void handleSourceMessages(); + void on_deltaFrequency_changed(qint64 value); + void on_flipSidebands_clicked(bool checked); + void on_dsb_toggled(bool checked); + void on_audioBinaural_toggled(bool checked); + void on_audioFlipChannels_toggled(bool checked); + void on_spanLog2_valueChanged(int value); + void on_BW_valueChanged(int value); + void on_lowCut_valueChanged(int value); + void on_volume_valueChanged(int value); + void on_audioMute_toggled(bool checked); + void on_tone_toggled(bool checked); + void on_toneFrequency_valueChanged(int value); + void on_mic_toggled(bool checked); + void on_agc_toggled(bool checked); + void on_agcOrder_valueChanged(int value); + void on_agcTime_valueChanged(int value); + void on_agcThreshold_valueChanged(int value); + void on_agcThresholdGate_valueChanged(int value); + void on_agcThresholdDelay_valueChanged(int value); + void on_play_toggled(bool checked); + void on_playLoop_toggled(bool checked); + void on_morseKeyer_toggled(bool checked); + + void on_navTimeSlider_valueChanged(int value); + void on_showFileDialog_clicked(bool checked); + + void onWidgetRolled(QWidget* widget, bool rollDown); + void onMenuDialogCalled(const QPoint& p); + + void configureFileName(); + void audioSelect(); + void tick(); +}; + +#endif /* PLUGINS_CHANNELTX_MODFREEDV_FREEDVMODGUI_H_ */ diff --git a/plugins/channeltx/modfreedv/freedvmodgui.ui b/plugins/channeltx/modfreedv/freedvmodgui.ui new file mode 100644 index 000000000..7138d587a --- /dev/null +++ b/plugins/channeltx/modfreedv/freedvmodgui.ui @@ -0,0 +1,1343 @@ + + + FreeDVModGUI + + + + 0 + 0 + 390 + 643 + + + + + 0 + 0 + + + + + 390 + 0 + + + + + Liberation Sans + 9 + + + + FreeDV Modulator + + + + + 0 + 0 + 385 + 331 + + + + + 385 + 0 + + + + Settings + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + 2 + + + + + + + + 16 + 0 + + + + Df + + + + + + + + 0 + 0 + + + + + 125 + 16 + + + + + Liberation Mono + 12 + + + + PointingHandCursor + + + Qt::StrongFocus + + + Demod shift frequency from center in Hz + + + + + + + Hz + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 60 + 0 + + + + Channel power + + + Qt::RightToLeft + + + -100.0 dB + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Vertical + + + + + + + + + Toggle btw Mono and Binaural I/Q audio + + + ... + + + + :/mono.png + :/stereo.png:/mono.png + + + true + + + + + + + Flip left/right audio channels + + + ... + + + + :/flip_lr.png + :/flip_rl.png:/flip_lr.png + + + true + + + + + + + Qt::Vertical + + + + + + + Flip sidebands in SSB mode (LSB->USB or USB->LSB) + + + + + + + :/flip_sidebands.png:/flip_sidebands.png + + + + + + + DSB/SSB toggle + + + ... + + + + :/usb.png + :/dsb.png:/usb.png + + + true + + + + + + + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + Span + + + + + + + Spectrum display frequency span + + + 0 + + + 4 + + + 1 + + + 2 + + + 2 + + + Qt::Horizontal + + + false + + + false + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + 6.0k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + Low cut + + + + + + + + 16777215 + 16 + + + + Highpass filter cutoff frequency (SSB) + + + -60 + + + 60 + + + 1 + + + 3 + + + Qt::Horizontal + + + QSlider::NoTicks + + + 5 + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + 0.3k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + Hi cut + + + + + + + + 16777215 + 16 + + + + Lowpass filter cutoff frequency + + + -60 + + + 60 + + + 1 + + + 30 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 5 + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + 3.0k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + 0 + + + + + + 50 + 0 + + + + + 50 + 10 + + + + + 8 + + + + f + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 10 + 10 + + + + + 8 + + + + - + + + + + + + + 8 + + + + LSB + + + Qt::AlignCenter + + + + + + + + 12 + 10 + + + + + 8 + + + + 0 + + + Qt::AlignCenter + + + + + + + + 8 + + + + USB + + + Qt::AlignCenter + + + + + + + + 10 + 10 + + + + + 8 + + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 50 + 0 + + + + + 50 + 10 + + + + + + + + + + + + + + + Vol + + + + + + + + 24 + 24 + + + + Audio input gain + + + 30 + + + 1 + + + 10 + + + + + + + Mute/Unmute audio + + + + + + + :/sound_on.png + :/sound_off.png:/sound_on.png + + + true + + + + + + + + 25 + 0 + + + + Audio input gain value + + + + + + 1.0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + Liberation Mono + 8 + + + + Level (% full range) top trace: average, bottom trace: instantaneous peak, tip: peak hold + + + + + + + + + + + Toggle audio compressor + + + CMP + + + true + + + + + + + + 24 + 24 + + + + AGC volume order in fraction of maximum amplitude + + + 100 + + + 1 + + + 20 + + + + + + + AGC volume order in fraction of maximum amplitude + + + 0.00 + + + + + + + + 24 + 24 + + + + Compressor time constant (attack) in ms + + + 0 + + + 9 + + + 1 + + + 7 + + + + + + + + 25 + 0 + + + + Compressor time constant in ms + + + 000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 24 + 24 + + + + Audio squelch threshold (db power) + + + -99 + + + 0 + + + 1 + + + -40 + + + + + + + + 14 + 0 + + + + Audio squelch threshold (dB power) + + + -00 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 24 + 24 + + + + Audio squelch gate in ms + + + 1 + + + 4 + + + + + + + + 18 + 0 + + + + Audio squelch gate in ms + + + 00 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 24 + 24 + + + + Audio squelch delay (release) in ms + + + 1 + + + 5 + + + + + + + + 25 + 0 + + + + Audio squelch delay in ms + + + 000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Horizontal + + + + + + + + + Tone modulation (1 kHz) + + + ... + + + + :/carrier.png:/carrier.png + + + + + + + Morse keyer at tone frequency + + + ... + + + + :/morsekey.png:/morsekey.png + + + + + + + + 24 + 24 + + + + Tone frequency + + + 10 + + + 250 + + + 1 + + + 100 + + + + + + + + 36 + 0 + + + + Tone frequency (kHz) + + + 1.00k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Vertical + + + + + + + Audio input + + + ... + + + + :/microphone.png:/microphone.png + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + + + ... + + + + + + + + + + + + 24 + 24 + + + + + 24 + 24 + + + + Open record file (48 kHz 32 bit float LE mono) + + + + + + + :/preset-load.png:/preset-load.png + + + + + + + Play file in a loop + + + ... + + + + :/playloop.png:/playloop.png + + + true + + + + + + + Record file play/pause + + + ... + + + + :/play.png + :/pause.png + :/play.png + :/play.png:/play.png + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + + + + false + + + + 90 + 0 + + + + Record time from start + + + 00:00:00.000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Vertical + + + + + + + false + + + + 60 + 0 + + + + Total record time + + + 00:00:00 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Record file time navigator + + + 100 + + + 1 + + + Qt::Horizontal + + + + + + + + + + + 0 + 340 + 351 + 284 + + + + Channel Spectrum + + + + 2 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 200 + 250 + + + + + Liberation Mono + 8 + + + + + + + + + + + + + RollupWidget + QWidget +
gui/rollupwidget.h
+ 1 +
+ + GLSpectrum + QWidget +
gui/glspectrum.h
+ 1 +
+ + GLSpectrumGUI + QWidget +
gui/glspectrumgui.h
+ 1 +
+ + LevelMeterVU + QWidget +
gui/levelmeter.h
+ 1 +
+ + ButtonSwitch + QToolButton +
gui/buttonswitch.h
+
+ + CWKeyerGUI + QWidget +
gui/cwkeyergui.h
+ 1 +
+ + ValueDialZ + QWidget +
gui/valuedialz.h
+ 1 +
+ + TickedSlider + QSlider +
gui/tickedslider.h
+
+
+ + + + +
diff --git a/plugins/channeltx/modfreedv/freedvmodplugin.cpp b/plugins/channeltx/modfreedv/freedvmodplugin.cpp new file mode 100644 index 000000000..6bcf54075 --- /dev/null +++ b/plugins/channeltx/modfreedv/freedvmodplugin.cpp @@ -0,0 +1,78 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include "plugin/pluginapi.h" + +#ifndef SERVER_MODE +#include "freedvmodgui.h" +#endif +#include "freedvmod.h" +#include "freedvmodplugin.h" + +const PluginDescriptor FreeDVModPlugin::m_pluginDescriptor = { + QString("SSB Modulator"), + QString("4.5.0"), + QString("(c) Edouard Griffiths, F4EXB"), + QString("https://github.com/f4exb/sdrangel"), + true, + QString("https://github.com/f4exb/sdrangel") +}; + +FreeDVModPlugin::FreeDVModPlugin(QObject* parent) : + QObject(parent), + m_pluginAPI(0) +{ +} + +const PluginDescriptor& FreeDVModPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void FreeDVModPlugin::initPlugin(PluginAPI* pluginAPI) +{ + m_pluginAPI = pluginAPI; + + // register FreeDV modulator + m_pluginAPI->registerTxChannel(FreeDVMod::m_channelIdURI, FreeDVMod::m_channelId, this); +} + +#ifdef SERVER_MODE +PluginInstanceGUI* FreeDVModPlugin::createTxChannelGUI( + DeviceUISet *deviceUISet __attribute__((unused)), + BasebandSampleSource *txChannel __attribute__((unused))) +{ + return 0; +} +#else +PluginInstanceGUI* FreeDVModPlugin::createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel) +{ + return FreeDVModGUI::create(m_pluginAPI, deviceUISet, txChannel); +} +#endif + +BasebandSampleSource* FreeDVModPlugin::createTxChannelBS(DeviceSinkAPI *deviceAPI) +{ + return new FreeDVMod(deviceAPI); +} + +ChannelSourceAPI* FreeDVModPlugin::createTxChannelCS(DeviceSinkAPI *deviceAPI) +{ + return new FreeDVMod(deviceAPI); +} + + diff --git a/plugins/channeltx/modfreedv/freedvmodplugin.h b/plugins/channeltx/modfreedv/freedvmodplugin.h new file mode 100644 index 000000000..1d41b34c8 --- /dev/null +++ b/plugins/channeltx/modfreedv/freedvmodplugin.h @@ -0,0 +1,47 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_FREEDVMODPLUGIN_H +#define INCLUDE_FREEDVMODPLUGIN_H + +#include +#include "plugin/plugininterface.h" + +class DeviceUISet; +class BasebandSampleSource; + +class FreeDVModPlugin : public QObject, PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID "sdrangel.channeltx.freedvmod") + +public: + explicit FreeDVModPlugin(QObject* parent = 0); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + virtual PluginInstanceGUI* createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel); + virtual BasebandSampleSource* createTxChannelBS(DeviceSinkAPI *deviceAPI); + virtual ChannelSourceAPI* createTxChannelCS(DeviceSinkAPI *deviceAPI); + +private: + static const PluginDescriptor m_pluginDescriptor; + + PluginAPI* m_pluginAPI; +}; + +#endif // INCLUDE_FREEDVMODPLUGIN_H diff --git a/plugins/channeltx/modfreedv/freedvmodsettings.cpp b/plugins/channeltx/modfreedv/freedvmodsettings.cpp new file mode 100644 index 000000000..4ea87ae5f --- /dev/null +++ b/plugins/channeltx/modfreedv/freedvmodsettings.cpp @@ -0,0 +1,241 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "dsp/dspengine.h" +#include "util/simpleserializer.h" +#include "settings/serializable.h" +#include "freedvmodsettings.h" + +const int FreeDVModSettings::m_agcTimeConstant[] = { + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 990}; + +const int FreeDVModSettings::m_nbAGCTimeConstants = 10; + +FreeDVModSettings::FreeDVModSettings() : + m_channelMarker(0), + m_spectrumGUI(0), + m_cwKeyerGUI(0) +{ + resetToDefaults(); +} + +void FreeDVModSettings::resetToDefaults() +{ + m_inputFrequencyOffset = 0; + m_bandwidth = 3000.0; + m_lowCutoff = 300.0; + m_usb = true; + m_toneFrequency = 1000.0; + m_volumeFactor = 1.0; + m_spanLog2 = 3; + m_audioBinaural = false; + m_audioFlipChannels = false; + m_dsb = false; + m_audioMute = false; + m_playLoop = false; + m_agc = false; + m_agcOrder = 0.2; + m_agcTime = 9600; + m_agcThresholdEnable = true; + m_agcThreshold = -40; // dB + m_agcThresholdGate = 192; + m_agcThresholdDelay = 2400; + m_rgbColor = QColor(0, 255, 0).rgb(); + m_title = "FreeDV Modulator"; + m_modAFInput = FreeDVModInputAF::FreeDVModInputNone; + m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; + m_useReverseAPI = false; + m_reverseAPIAddress = "127.0.0.1"; + m_reverseAPIPort = 8888; + m_reverseAPIDeviceIndex = 0; + m_reverseAPIChannelIndex = 0; +} + +QByteArray FreeDVModSettings::serialize() const +{ + SimpleSerializer s(1); + + s.writeS32(1, m_inputFrequencyOffset); + s.writeS32(2, roundf(m_bandwidth / 100.0)); + s.writeS32(3, roundf(m_toneFrequency / 10.0)); + + if (m_spectrumGUI) { + s.writeBlob(4, m_spectrumGUI->serialize()); + } + + s.writeU32(5, m_rgbColor); + + if (m_cwKeyerGUI) { + s.writeBlob(6, m_cwKeyerGUI->serialize()); + } + + s.writeS32(7, roundf(m_lowCutoff / 100.0)); + s.writeS32(8, m_spanLog2); + s.writeBool(9, m_audioBinaural); + s.writeBool(10, m_audioFlipChannels); + s.writeBool(11, m_dsb); + s.writeBool(12, m_agc); + s.writeS32(13, getAGCTimeConstantIndex(m_agcTime/48)); + s.writeS32(14, m_agcThreshold); // dB + s.writeS32(15, m_agcThresholdGate / 48); + s.writeS32(16, m_agcThresholdDelay / 48); + s.writeS32(17, roundf(m_agcOrder * 100.0)); + + if (m_channelMarker) { + s.writeBlob(18, m_channelMarker->serialize()); + } + + s.writeString(19, m_title); + s.writeString(20, m_audioDeviceName); + s.writeS32(21, (int) m_modAFInput); + s.writeBool(22, m_useReverseAPI); + s.writeString(23, m_reverseAPIAddress); + s.writeU32(24, m_reverseAPIPort); + s.writeU32(25, m_reverseAPIDeviceIndex); + s.writeU32(26, m_reverseAPIChannelIndex); + + return s.final(); +} + +bool FreeDVModSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if(!d.isValid()) + { + resetToDefaults(); + return false; + } + + if(d.getVersion() == 1) + { + QByteArray bytetmp; + qint32 tmp; + uint32_t utmp; + + d.readS32(1, &tmp, 0); + m_inputFrequencyOffset = tmp; + + d.readS32(2, &tmp, 30); + m_bandwidth = tmp * 100.0; + + d.readS32(3, &tmp, 100); + m_toneFrequency = tmp * 10.0; + + if (m_spectrumGUI) + { + d.readBlob(4, &bytetmp); + m_spectrumGUI->deserialize(bytetmp); + } + + d.readU32(5, &m_rgbColor); + + if (m_cwKeyerGUI) { + d.readBlob(6, &bytetmp); + m_cwKeyerGUI->deserialize(bytetmp); + } + + d.readS32(7, &tmp, 3); + m_lowCutoff = tmp * 100.0; + + d.readS32(8, &m_spanLog2, 3); + d.readBool(9, &m_audioBinaural, false); + d.readBool(10, &m_audioFlipChannels, false); + d.readBool(11, &m_dsb, false); + d.readBool(12, &m_agc, false); + d.readS32(13, &tmp, 7); + m_agcTime = getAGCTimeConstant(tmp) * 48; + d.readS32(14, &m_agcThreshold, -40); + d.readS32(15, &tmp, 4); + m_agcThresholdGate = tmp * 48; + d.readS32(16, &tmp, 5); + m_agcThresholdDelay = tmp * 48; + d.readS32(17, &tmp, 20); + m_agcOrder = tmp / 100.0; + + if (m_channelMarker) { + d.readBlob(18, &bytetmp); + m_channelMarker->deserialize(bytetmp); + } + + d.readString(19, &m_title, "FreeDV Modulator"); + d.readString(20, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName); + + d.readS32(21, &tmp, 0); + if ((tmp < 0) || (tmp > (int) FreeDVModInputAF::FreeDVModInputTone)) { + m_modAFInput = FreeDVModInputNone; + } else { + m_modAFInput = (FreeDVModInputAF) tmp; + } + + d.readBool(22, &m_useReverseAPI, false); + d.readString(23, &m_reverseAPIAddress, "127.0.0.1"); + d.readU32(24, &utmp, 0); + + if ((utmp > 1023) && (utmp < 65535)) { + m_reverseAPIPort = utmp; + } else { + m_reverseAPIPort = 8888; + } + + d.readU32(25, &utmp, 0); + m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp; + d.readU32(26, &utmp, 0); + m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp; + + return true; + } + else + { + resetToDefaults(); + return false; + } +} + +int FreeDVModSettings::getAGCTimeConstant(int index) +{ + if (index < 0) { + return m_agcTimeConstant[0]; + } else if (index < m_nbAGCTimeConstants) { + return m_agcTimeConstant[index]; + } else { + return m_agcTimeConstant[m_nbAGCTimeConstants-1]; + } +} + +int FreeDVModSettings::getAGCTimeConstantIndex(int agcTimeConstant) +{ + for (int i = 0; i < m_nbAGCTimeConstants; i++) + { + if (agcTimeConstant <= m_agcTimeConstant[i]) + { + return i; + } + } + + return m_nbAGCTimeConstants-1; +} diff --git a/plugins/channeltx/modfreedv/freedvmodsettings.h b/plugins/channeltx/modfreedv/freedvmodsettings.h new file mode 100644 index 000000000..f1ee4623e --- /dev/null +++ b/plugins/channeltx/modfreedv/freedvmodsettings.h @@ -0,0 +1,88 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_CHANNELTX_MODFREEDV_FREEDVMODSETTINGS_H_ +#define PLUGINS_CHANNELTX_MODFREEDV_FREEDVMODSETTINGS_H_ + +#include +#include +#include + +class Serializable; + +struct FreeDVModSettings +{ + typedef enum + { + FreeDVModInputNone, + FreeDVModInputTone, + FreeDVModInputFile, + FreeDVModInputAudio, + FreeDVModInputCWTone + } FreeDVModInputAF; + + static const int m_nbAGCTimeConstants; + static const int m_agcTimeConstant[]; + + qint64 m_inputFrequencyOffset; + Real m_bandwidth; + Real m_lowCutoff; + bool m_usb; + float m_toneFrequency; + float m_volumeFactor; + int m_spanLog2; + bool m_audioBinaural; + bool m_audioFlipChannels; + bool m_dsb; + bool m_audioMute; + bool m_playLoop; + bool m_agc; + float m_agcOrder; + int m_agcTime; + bool m_agcThresholdEnable; + int m_agcThreshold; + int m_agcThresholdGate; + int m_agcThresholdDelay; + quint32 m_rgbColor; + + QString m_title; + FreeDVModInputAF m_modAFInput; + QString m_audioDeviceName; + + bool m_useReverseAPI; + QString m_reverseAPIAddress; + uint16_t m_reverseAPIPort; + uint16_t m_reverseAPIDeviceIndex; + uint16_t m_reverseAPIChannelIndex; + + Serializable *m_channelMarker; + Serializable *m_spectrumGUI; + Serializable *m_cwKeyerGUI; + + FreeDVModSettings(); + void resetToDefaults(); + void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; } + void setSpectrumGUI(Serializable *spectrumGUI) { m_spectrumGUI = spectrumGUI; } + void setCWKeyerGUI(Serializable *cwKeyerGUI) { m_cwKeyerGUI = cwKeyerGUI; } + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + + static int getAGCTimeConstant(int index); + static int getAGCTimeConstantIndex(int agcTimeConstant); +}; + + +#endif /* PLUGINS_CHANNELTX_MODFREEDV_FREEDVMODSETTINGS_H_ */ diff --git a/sdrbase/resources/res.qrc b/sdrbase/resources/res.qrc index 851352384..42e83e995 100644 --- a/sdrbase/resources/res.qrc +++ b/sdrbase/resources/res.qrc @@ -15,6 +15,7 @@ webapi/doc/swagger/include/FCDPro.yaml webapi/doc/swagger/include/FCDProPlus.yaml webapi/doc/swagger/include/FileSource.yaml + webapi/doc/swagger/include/FreeDVMod.yaml webapi/doc/swagger/include/HackRF.yaml webapi/doc/swagger/include/LimeSdr.yaml webapi/doc/swagger/include/NFMDemod.yaml @@ -29,7 +30,7 @@ webapi/doc/swagger/include/SDRPlay.yaml webapi/doc/swagger/include/SoapySDR.yaml webapi/doc/swagger/include/SSBDemod.yaml - webapi/doc/swagger/include/SSBMod.yaml + webapi/doc/swagger/include/SSBMod.yaml webapi/doc/swagger/include/Structs.yaml webapi/doc/swagger/include/TestSource.yaml webapi/doc/swagger/include/UDPSource.yaml diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 4edc454c3..5e476397c 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1758,6 +1758,9 @@ margin-bottom: 20px; "DSDDemodReport" : { "$ref" : "#/definitions/DSDDemodReport" }, + "FreeDVModReport" : { + "$ref" : "#/definitions/FreeDVModReport" + }, "NFMDemodReport" : { "$ref" : "#/definitions/NFMDemodReport" }, @@ -1815,6 +1818,9 @@ margin-bottom: 20px; "DSDDemodSettings" : { "$ref" : "#/definitions/DSDDemodSettings" }, + "FreeDVModSettings" : { + "$ref" : "#/definitions/FreeDVModSettings" + }, "NFMDemodSettings" : { "$ref" : "#/definitions/NFMDemodSettings" }, @@ -2539,6 +2545,121 @@ margin-bottom: 20px; } }, "description" : "FileSource" +}; + defs.FreeDVModReport = { + "properties" : { + "channelPowerDB" : { + "type" : "number", + "format" : "float", + "description" : "power transmitted in channel (dB)" + }, + "audioSampleRate" : { + "type" : "integer" + }, + "channelSampleRate" : { + "type" : "integer" + } + }, + "description" : "FreeDVMod" +}; + defs.FreeDVModSettings = { + "properties" : { + "inputFrequencyOffset" : { + "type" : "integer", + "format" : "int64" + }, + "bandwidth" : { + "type" : "number", + "format" : "float" + }, + "lowCutoff" : { + "type" : "number", + "format" : "float" + }, + "usb" : { + "type" : "integer" + }, + "toneFrequency" : { + "type" : "number", + "format" : "float" + }, + "volumeFactor" : { + "type" : "number", + "format" : "float" + }, + "spanLog2" : { + "type" : "integer" + }, + "audioBinaural" : { + "type" : "integer" + }, + "audioFlipChannels" : { + "type" : "integer" + }, + "dsb" : { + "type" : "integer" + }, + "audioMute" : { + "type" : "integer" + }, + "playLoop" : { + "type" : "integer" + }, + "agc" : { + "type" : "integer" + }, + "agcOrder" : { + "type" : "number", + "format" : "float" + }, + "agcTime" : { + "type" : "integer" + }, + "agcThresholdEnable" : { + "type" : "integer" + }, + "agcThreshold" : { + "type" : "integer" + }, + "agcThresholdGate" : { + "type" : "integer" + }, + "agcThresholdDelay" : { + "type" : "integer" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" + }, + "audioDeviceName" : { + "type" : "string" + }, + "modAFInput" : { + "type" : "integer" + }, + "useReverseAPI" : { + "type" : "integer", + "description" : "Synchronize with reverse API (1 for yes, 0 for no)" + }, + "reverseAPIAddress" : { + "type" : "string" + }, + "reverseAPIPort" : { + "type" : "integer" + }, + "reverseAPIDeviceIndex" : { + "type" : "integer" + }, + "reverseAPIChannelIndex" : { + "type" : "integer" + }, + "cwKeyer" : { + "$ref" : "#/definitions/CWKeyerSettings" + } + }, + "description" : "FreeDVMod" }; defs.Frequency = { "properties" : { @@ -5513,7 +5634,7 @@ margin-bottom: 20px;