From 260b391c5e069cc9e9dbe5b5d8550784aebb7830 Mon Sep 17 00:00:00 2001 From: colbyAtCRI Date: Fri, 15 Jan 2021 07:12:54 -0800 Subject: [PATCH] Really added the files this time --- src/modules/modem/analog/ModemCW.cpp | 221 +++++++++++++++++++++++++++ src/modules/modem/analog/ModemCW.h | 53 +++++++ 2 files changed, 274 insertions(+) create mode 100644 src/modules/modem/analog/ModemCW.cpp create mode 100644 src/modules/modem/analog/ModemCW.h diff --git a/src/modules/modem/analog/ModemCW.cpp b/src/modules/modem/analog/ModemCW.cpp new file mode 100644 index 0000000..6efb321 --- /dev/null +++ b/src/modules/modem/analog/ModemCW.cpp @@ -0,0 +1,221 @@ +// Copyright (c) Charles J. Cliffe +// SPDX-License-Identifier: GPL-2.0+ + +#include "ModemCW.h" + +// We are given a baseband segment BW (default 500Hz) wide which we want to +// offset by mBeepFrequency (default 650Hz). This yields a spectrum. +// +// | |....|....| +// | |....|....| +// | |....|....| +// -----------|---|----|----|-- +// 0 150 650 1150 +// +ModemCW::ModemCW() + : ModemAnalog(), + mBeepFrequency(650.0), + mGain(15.0), + mAutoGain(true), + mLO(nullptr), + mToReal(nullptr) +{ + mLO = nco_crcf_create (LIQUID_NCO); + mToReal = firhilbf_create (5,60.0f); + useSignalOutput(true); +} + +ModemCW::~ModemCW() { + if (mLO) + nco_crcf_destroy (mLO); + if (mToReal) + firhilbf_destroy (mToReal); +} + +ModemArgInfoList ModemCW::getSettings() +{ + ModemArgInfoList args; + + ModemArgInfo offsetArg; + offsetArg.key = "offset"; + offsetArg.name = "Frequency Offset"; + offsetArg.value = std::to_string(mBeepFrequency); + offsetArg.units = "Hz"; + offsetArg.description = "Frequency Offset / Beep frequency"; + offsetArg.type = ModemArgInfo::FLOAT; + offsetArg.range = ModemRange (200.0,1000.0); + args.push_back(offsetArg); + + ModemArgInfo autoGain; + autoGain.key = "auto"; + autoGain.name = "Auto Gain"; + autoGain.value = "on"; + autoGain.type = ModemArgInfo::STRING; + std::vector autoOpts; + autoOpts.push_back("on"); + autoOpts.push_back("off"); + autoGain.optionNames = autoOpts; + autoGain.options = autoOpts; + args.push_back(autoGain); + + ModemArgInfo gain; + gain.key = "gain"; + gain.name = "Audio Gain"; + gain.value = "15"; + gain.units = "dB"; + gain.description = "Gain Setting"; + gain.range = ModemRange(0.0,40.0); + gain.type = ModemArgInfo::FLOAT; + args.push_back(gain); + return args; +} + +void ModemCW::writeSetting(std::string setting, std::string value) +{ + if (setting == "offset") { + mBeepFrequency = std::stof(value); + rebuildKit(); + } else + if (setting == "auto") { + mAutoGain = (value=="on")?true:false; + } else + if (setting == "gain") { + mGain = std::stof(value); + } +} + +std::string ModemCW::readSetting(std::string setting) +{ + if (setting == "offset") { + return std::to_string(mBeepFrequency); + } else + if (setting == "auto") { + return (mAutoGain)?"on":"off"; + } else + if (setting == "gain") { + return std::to_string(mGain); + } + return ""; +} + +ModemBase *ModemCW::factory() { + return new ModemCW; +} + +std::string ModemCW::getName() { + return "CW"; +} + +int ModemCW::checkSampleRate (long long srate, int arate) +{ + if (srate < MIN_BANDWIDTH) + return MIN_BANDWIDTH; + return srate; +} + +int ModemCW::getDefaultSampleRate() { + return MIN_BANDWIDTH; +} + +// The modem object is asked to make a "ModemKit" given the IQ sample rate +// and the audio sample rate. For the CW modem the IQ sample rate is small +// or narrow bandwidth. The demodulated sample rate must be fast enough to +// sample 200-1000Hz tones. If the IQ sample rate is less than 2000Hz then +// one doesn't have the bandwidth for these tones. So we need to interpolate +// the input IQ to audioOut, frequency shift, then pass the real part. +// Simple solution is just interpolate the IQ data to the audio sample rate. +ModemKit *ModemCW::buildKit (long long sampleRate, int audioSampleRate) +{ + ModemKitCW *kit = new ModemKitCW(); + float As = 60.0f; + double ratio = double(audioSampleRate) / double(sampleRate); + kit->sampleRate = sampleRate; + kit->audioSampleRate = audioSampleRate; + kit->audioResampleRatio = ratio; + kit->mInputResampler = msresamp_cccf_create (ratio,As); + return kit; +} + +void ModemCW::disposeKit (ModemKit *kit) +{ + ModemKitCW *cwkit = (ModemKitCW*) kit; + msresamp_cccf_destroy (cwkit->mInputResampler); + delete kit; +} + +void ModemCW::initOutputBuffers(ModemKitAnalog *akit, ModemIQData *input) +{ + bufSize = input->data.size(); + + if (!bufSize) { + return; + } + + double audio_resample_ratio = akit->audioResampleRatio; + + size_t audio_out_size = (size_t)ceil((double) (bufSize) * audio_resample_ratio) + 512; + + // Just make everything the audio out size + if (mInput.size() != audio_out_size) { + if (mInput.capacity() < audio_out_size) { + mInput.reserve(audio_out_size); + } + mInput.resize(audio_out_size); + } +} + +void ModemCW::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) { + unsigned int outSize; + float lsb; + liquid_float_complex sig; + ModemKitCW *cwkit = (ModemKitCW *)kit; + + initOutputBuffers(cwkit, input); + + if (!bufSize) { + return; + } + + // Interpolate IQ samples to full audio band. We need to be able to + // sample at 2 times the desired beep frequency. + msresamp_cccf_execute (cwkit->mInputResampler,&input->data[0],bufSize,&mInput[0],&outSize); + + // Make the shoe fit. + if (demodOutputData.size() != outSize) { + demodOutputData.resize(outSize); + } + + // Set the LO to the desired beep frequency. + nco_crcf_set_frequency(mLO,2.0*M_PI*mBeepFrequency/kit->audioSampleRate); + + // Mix up from base band by beep frequency. Extract real part + for (int i = 0; i < outSize; i++) { + nco_crcf_mix_up (mLO,mInput[i],&sig); + nco_crcf_step (mLO); + firhilbf_c2r_execute (mToReal,sig,&lsb,&demodOutputData[i]); + } + + // Determine gain automagically (if desired) + if (mAutoGain) { + aOutputCeilMA = aOutputCeilMA + (aOutputCeil - aOutputCeilMA) * 0.025f; + aOutputCeilMAA = aOutputCeilMAA + (aOutputCeilMA - aOutputCeilMAA) * 0.025f; + aOutputCeil = 0; + + for (size_t i = 0; i < outSize; i++) { + if (demodOutputData[i] > aOutputCeil) { + aOutputCeil = demodOutputData[i]; + } + } + + mGain = 10.0*std::log10(0.5f / aOutputCeilMAA); + } + + // Apply gain to demodulated output data + for (size_t i = 0; i < outSize; i++) { + demodOutputData[i] *= std::pow(10.0,mGain/10.0); + } + + audioOut->channels = 1; + audioOut->sampleRate = cwkit->audioSampleRate; + audioOut->data.assign(demodOutputData.begin(), demodOutputData.end()); +} diff --git a/src/modules/modem/analog/ModemCW.h b/src/modules/modem/analog/ModemCW.h new file mode 100644 index 0000000..297d7ba --- /dev/null +++ b/src/modules/modem/analog/ModemCW.h @@ -0,0 +1,53 @@ +// Copyright (c) Charles J. Cliffe +// SPDX-License-Identifier: GPL-2.0+ + +#pragma once +#include "Modem.h" +#include "ModemAnalog.h" + +class ModemKitCW : public ModemKitAnalog +{ +public: + ModemKitCW() : ModemKitAnalog() { + }; + msresamp_cccf mInputResampler; +}; + +class ModemCW : public ModemAnalog { +public: + ModemCW(); + ~ModemCW(); + + std::string getName(); + + static ModemBase *factory(); + + int checkSampleRate (long long srate, int arate ); + + ModemKit *buildKit (long long srate, int arate); + + void disposeKit (ModemKit *kit); + + void initOutputBuffers (ModemKitAnalog *akit, ModemIQData *input); + + int getDefaultSampleRate(); + + void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut); + + ModemArgInfoList getSettings(); + + void writeSetting(std::string setting,std::string value); + + std::string readSetting(std::string setting); + + // No resampling required. + std::vector *getResampledOutputData () { return &demodOutputData; } + +private: + bool mAutoGain; + float mGain; + float mBeepFrequency; + nco_crcf mLO; + firhilbf mToReal; + std::vector mInput; +};