diff --git a/plugins/channeltx/modatv/atvmod.cpp b/plugins/channeltx/modatv/atvmod.cpp index 70bd76e8e..cd420c5ee 100644 --- a/plugins/channeltx/modatv/atvmod.cpp +++ b/plugins/channeltx/modatv/atvmod.cpp @@ -57,7 +57,11 @@ ATVMod::ATVMod() : m_cameraIndex(-1), m_showOverlayText(false), m_SSBFilter(0), - m_SSBFilterBuffer(0) + m_DSBFilter(0), + m_SSBFilterBuffer(0), + m_DSBFilterBuffer(0), + m_SSBFilterBufferIndex(0), + m_DSBFilterBufferIndex(0) { setObjectName("ATVMod"); scanCameras(); @@ -72,6 +76,10 @@ ATVMod::ATVMod() : m_SSBFilterBuffer = new Complex[m_ssbFftLen>>1]; // filter returns data exactly half of its size memset(m_SSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen>>1)); + m_DSBFilter = new fftfilt((2.0f * m_config.m_rfBandwidth) / m_config.m_outputSampleRate, 2 * m_ssbFftLen); + m_DSBFilterBuffer = new Complex[m_ssbFftLen]; + memset(m_DSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen)); + applyStandard(); m_interpolatorDistanceRemain = 0.0f; @@ -193,6 +201,11 @@ void ATVMod::modulateSample() m_modSample = modulateSSB(t); m_modSample *= 29204.0f; break; + case ATVModulationVestigialLSB: + case ATVModulationVestigialUSB: + m_modSample = modulateVestigialSSB(t); + m_modSample *= 29204.0f; + break; case ATVModulationAM: // AM 90% default: m_modSample.real((t*1.8f + 0.1f) * 16384.0f); // modulate and scale zero frequency carrier @@ -219,6 +232,25 @@ Complex& ATVMod::modulateSSB(Real& sample) return m_SSBFilterBuffer[m_SSBFilterBufferIndex-1]; } +Complex& ATVMod::modulateVestigialSSB(Real& sample) +{ + int n_out; + Complex ci(sample, 0.0f); + fftfilt::cmplx *filtered; + + n_out = m_DSBFilter->runVestigial(ci, &filtered, m_running.m_atvModulation == ATVModulationVestigialUSB, 0.15f); + + if (n_out > 0) + { + memcpy((void *) m_DSBFilterBuffer, (const void *) filtered, n_out*sizeof(Complex)); + m_DSBFilterBufferIndex = 0; + } + + m_DSBFilterBufferIndex++; + + return m_DSBFilterBuffer[m_DSBFilterBufferIndex-1]; +} + void ATVMod::pullVideo(Real& sample) { int iLine = m_lineCount % m_nbLines2; @@ -618,6 +650,10 @@ void ATVMod::apply(bool force) memset(m_SSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen>>1)); m_SSBFilterBufferIndex = 0; + m_DSBFilter->create_dsb_filter(m_config.m_rfBandwidth / m_config.m_outputSampleRate); + memset(m_DSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen)); + m_DSBFilterBufferIndex = 0; + applyStandard(); // set all timings m_settingsMutex.unlock(); } diff --git a/plugins/channeltx/modatv/atvmod.h b/plugins/channeltx/modatv/atvmod.h index 5a804e23b..058b41416 100644 --- a/plugins/channeltx/modatv/atvmod.h +++ b/plugins/channeltx/modatv/atvmod.h @@ -62,7 +62,9 @@ public: ATVModulationAM, ATVModulationFM, ATVModulationUSB, - ATVModulationLSB + ATVModulationLSB, + ATVModulationVestigialUSB, + ATVModulationVestigialLSB } ATVModulation; class MsgConfigureImageFileName : public Message @@ -532,8 +534,11 @@ private: bool m_showOverlayText; fftfilt* m_SSBFilter; + fftfilt* m_DSBFilter; Complex* m_SSBFilterBuffer; + Complex* m_DSBFilterBuffer; int m_SSBFilterBufferIndex; + int m_DSBFilterBufferIndex; static const int m_ssbFftLen; static const float m_blackLevel; @@ -548,6 +553,7 @@ private: void calculateLevel(Real& sample); void modulateSample(); Complex& modulateSSB(Real& sample); + Complex& modulateVestigialSSB(Real& sample); void applyStandard(); void openImage(const QString& fileName); void openVideo(const QString& fileName); diff --git a/plugins/channeltx/modatv/atvmodgui.cpp b/plugins/channeltx/modatv/atvmodgui.cpp index 0e108da41..83eccf627 100644 --- a/plugins/channeltx/modatv/atvmodgui.cpp +++ b/plugins/channeltx/modatv/atvmodgui.cpp @@ -202,7 +202,9 @@ void ATVModGUI::viewChanged() void ATVModGUI::channelizerOutputSampleRateChanged() { if ((ui->modulation->currentIndex() == (int) ATVMod::ATVModulationLSB) || - (ui->modulation->currentIndex() == (int) ATVMod::ATVModulationUSB)) + (ui->modulation->currentIndex() == (int) ATVMod::ATVModulationUSB) || + (ui->modulation->currentIndex() == (int) ATVMod::ATVModulationVestigialLSB) || + (ui->modulation->currentIndex() == (int) ATVMod::ATVModulationVestigialUSB)) { ui->rfBW->setMaximum(m_channelizer->getOutputSampleRate() / 200000); } @@ -247,13 +249,15 @@ void ATVModGUI::on_deltaFrequency_changed(quint64 value) void ATVModGUI::on_modulation_currentIndexChanged(int index) { - if (index == (int) ATVMod::ATVModulationLSB) + if ((index == (int) ATVMod::ATVModulationLSB) || + (index == (int) ATVMod::ATVModulationVestigialLSB)) { ui->rfBW->setMaximum(m_channelizer->getOutputSampleRate() / 200000); m_channelMarker.setBandwidth(-ui->rfBW->value()*200000); m_channelMarker.setSidebands(ChannelMarker::lsb); } - else if (index == (int) ATVMod::ATVModulationUSB) + else if ((index == (int) ATVMod::ATVModulationUSB) || + (index == (int) ATVMod::ATVModulationVestigialUSB)) { ui->rfBW->setMaximum(m_channelizer->getOutputSampleRate() / 200000); m_channelMarker.setBandwidth(ui->rfBW->value()*200000); @@ -273,11 +277,13 @@ void ATVModGUI::on_rfBW_valueChanged(int value) { ui->rfBWText->setText(QString("%1 MHz").arg(value / 10.0, 0, 'f', 1)); - if (ui->modulation->currentIndex() == (int) ATVMod::ATVModulationLSB) + if ((ui->modulation->currentIndex() == (int) ATVMod::ATVModulationLSB) || + (ui->modulation->currentIndex() == (int) ATVMod::ATVModulationVestigialLSB)) { m_channelMarker.setBandwidth(-ui->rfBW->value()*200000); } - else if (ui->modulation->currentIndex() == (int) ATVMod::ATVModulationUSB) + else if ((ui->modulation->currentIndex() == (int) ATVMod::ATVModulationUSB) || + (ui->modulation->currentIndex() == (int) ATVMod::ATVModulationVestigialUSB)) { m_channelMarker.setBandwidth(ui->rfBW->value()*200000); } diff --git a/plugins/channeltx/modatv/atvmodgui.ui b/plugins/channeltx/modatv/atvmodgui.ui index 65cc6f972..34df2be66 100644 --- a/plugins/channeltx/modatv/atvmodgui.ui +++ b/plugins/channeltx/modatv/atvmodgui.ui @@ -194,7 +194,7 @@ - 50 + 60 16777215 @@ -221,6 +221,16 @@ LSB + + + VUSB + + + + + VLSB + + diff --git a/sdrbase/dsp/fftfilt.cxx b/sdrbase/dsp/fftfilt.cxx index f11776b53..a2f31616f 100644 --- a/sdrbase/dsp/fftfilt.cxx +++ b/sdrbase/dsp/fftfilt.cxx @@ -271,6 +271,64 @@ int fftfilt::runDSB(const cmplx & in, cmplx **out) return flen2; } +// Version for vestigial sideband. You have to double the FFT size used for SSB. +int fftfilt::runVestigial(const cmplx & in, cmplx **out, bool usb, float vf) +{ + if (vf < 0.0f) vf = 0.0f; + if (vf > 1.0f) vf = 1.0f; + if (usb) vf = 1.0f - vf; + + data[inptr++] = in; + if (inptr < flen2) + return 0; + inptr = 0; + + fft->ComplexFFT(data); + + data[0] *= filter[0]; // always keep DC + + if (usb) + { + for (int i = 1; i < flen2; i++) + { + data[i] *= filter[i]; // usb + + if (i > (int) (vf * flen2)) { // vestigial lsb + data[flen2 + i] *= filter[flen2 + i]; + } else { + data[flen2 + i] = 0; + } + } + } + else + { + for (int i = 1; i < flen2; i++) + { + if (i < (int) (vf * flen2)) { // vestigial usb + data[i] *= filter[i]; + } else { + data[i] = 0; + } + + data[flen2 + i] *= filter[flen2 + i]; // lsb + } + } + + // in-place FFT: freqdata overwritten with filtered timedata + fft->InverseComplexFFT(data); + + // overlap and add + for (int i = 0; i < flen2; i++) { + output[i] = ovlbuf[i] + data[i]; + ovlbuf[i] = data[i+flen2]; + } + + memset (data, 0, flen * sizeof(cmplx)); + + *out = output; + return flen2; +} + /* Sliding FFT from Fldigi */ struct sfft::vrot_bins_pair { diff --git a/sdrbase/dsp/fftfilt.h b/sdrbase/dsp/fftfilt.h index 903bb5b46..b1d0ecdf6 100644 --- a/sdrbase/dsp/fftfilt.h +++ b/sdrbase/dsp/fftfilt.h @@ -1,5 +1,5 @@ /* - * Filters from Fldigi. + * Filters from Fldigi. */ #ifndef _FFTFILT_H @@ -28,6 +28,7 @@ public: int runFilt(const cmplx& in, cmplx **out); int runSSB(const cmplx& in, cmplx **out, bool usb, bool getDC = true); int runDSB(const cmplx& in, cmplx **out); + int runVestigial(const cmplx & in, cmplx **out, bool usb, float vf); protected: int flen; @@ -43,13 +44,13 @@ protected: int window; inline float fsinc(float fc, int i, int len) { - return (i == len/2) ? 2.0 * fc: + return (i == len/2) ? 2.0 * fc: sin(2 * M_PI * fc * (i - len/2)) / (M_PI * (i - len/2)); } inline float _blackman(int i, int len) { - return (0.42 - - 0.50 * cos(2.0 * M_PI * i / len) + + return (0.42 - + 0.50 * cos(2.0 * M_PI * i / len) + 0.08 * cos(4.0 * M_PI * i / len)); }