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));
}