1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-10 10:33:29 -05:00

ATV Modulator: implemented vestigial sideband

This commit is contained in:
f4exb 2017-03-16 02:45:51 +01:00
parent 1ae2135c3b
commit df88215798
6 changed files with 129 additions and 12 deletions

View File

@ -57,7 +57,11 @@ ATVMod::ATVMod() :
m_cameraIndex(-1), m_cameraIndex(-1),
m_showOverlayText(false), m_showOverlayText(false),
m_SSBFilter(0), m_SSBFilter(0),
m_SSBFilterBuffer(0) m_DSBFilter(0),
m_SSBFilterBuffer(0),
m_DSBFilterBuffer(0),
m_SSBFilterBufferIndex(0),
m_DSBFilterBufferIndex(0)
{ {
setObjectName("ATVMod"); setObjectName("ATVMod");
scanCameras(); scanCameras();
@ -72,6 +76,10 @@ ATVMod::ATVMod() :
m_SSBFilterBuffer = new Complex[m_ssbFftLen>>1]; // filter returns data exactly half of its size m_SSBFilterBuffer = new Complex[m_ssbFftLen>>1]; // filter returns data exactly half of its size
memset(m_SSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen>>1)); 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(); applyStandard();
m_interpolatorDistanceRemain = 0.0f; m_interpolatorDistanceRemain = 0.0f;
@ -193,6 +201,11 @@ void ATVMod::modulateSample()
m_modSample = modulateSSB(t); m_modSample = modulateSSB(t);
m_modSample *= 29204.0f; m_modSample *= 29204.0f;
break; break;
case ATVModulationVestigialLSB:
case ATVModulationVestigialUSB:
m_modSample = modulateVestigialSSB(t);
m_modSample *= 29204.0f;
break;
case ATVModulationAM: // AM 90% case ATVModulationAM: // AM 90%
default: default:
m_modSample.real((t*1.8f + 0.1f) * 16384.0f); // modulate and scale zero frequency carrier 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]; 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) void ATVMod::pullVideo(Real& sample)
{ {
int iLine = m_lineCount % m_nbLines2; int iLine = m_lineCount % m_nbLines2;
@ -618,6 +650,10 @@ void ATVMod::apply(bool force)
memset(m_SSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen>>1)); memset(m_SSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen>>1));
m_SSBFilterBufferIndex = 0; 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 applyStandard(); // set all timings
m_settingsMutex.unlock(); m_settingsMutex.unlock();
} }

View File

@ -62,7 +62,9 @@ public:
ATVModulationAM, ATVModulationAM,
ATVModulationFM, ATVModulationFM,
ATVModulationUSB, ATVModulationUSB,
ATVModulationLSB ATVModulationLSB,
ATVModulationVestigialUSB,
ATVModulationVestigialLSB
} ATVModulation; } ATVModulation;
class MsgConfigureImageFileName : public Message class MsgConfigureImageFileName : public Message
@ -532,8 +534,11 @@ private:
bool m_showOverlayText; bool m_showOverlayText;
fftfilt* m_SSBFilter; fftfilt* m_SSBFilter;
fftfilt* m_DSBFilter;
Complex* m_SSBFilterBuffer; Complex* m_SSBFilterBuffer;
Complex* m_DSBFilterBuffer;
int m_SSBFilterBufferIndex; int m_SSBFilterBufferIndex;
int m_DSBFilterBufferIndex;
static const int m_ssbFftLen; static const int m_ssbFftLen;
static const float m_blackLevel; static const float m_blackLevel;
@ -548,6 +553,7 @@ private:
void calculateLevel(Real& sample); void calculateLevel(Real& sample);
void modulateSample(); void modulateSample();
Complex& modulateSSB(Real& sample); Complex& modulateSSB(Real& sample);
Complex& modulateVestigialSSB(Real& sample);
void applyStandard(); void applyStandard();
void openImage(const QString& fileName); void openImage(const QString& fileName);
void openVideo(const QString& fileName); void openVideo(const QString& fileName);

View File

@ -202,7 +202,9 @@ void ATVModGUI::viewChanged()
void ATVModGUI::channelizerOutputSampleRateChanged() void ATVModGUI::channelizerOutputSampleRateChanged()
{ {
if ((ui->modulation->currentIndex() == (int) ATVMod::ATVModulationLSB) || 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); 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) 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); ui->rfBW->setMaximum(m_channelizer->getOutputSampleRate() / 200000);
m_channelMarker.setBandwidth(-ui->rfBW->value()*200000); m_channelMarker.setBandwidth(-ui->rfBW->value()*200000);
m_channelMarker.setSidebands(ChannelMarker::lsb); 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); ui->rfBW->setMaximum(m_channelizer->getOutputSampleRate() / 200000);
m_channelMarker.setBandwidth(ui->rfBW->value()*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)); 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); 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); m_channelMarker.setBandwidth(ui->rfBW->value()*200000);
} }

View File

@ -194,7 +194,7 @@
<widget class="QComboBox" name="modulation"> <widget class="QComboBox" name="modulation">
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>50</width> <width>60</width>
<height>16777215</height> <height>16777215</height>
</size> </size>
</property> </property>
@ -221,6 +221,16 @@
<string>LSB</string> <string>LSB</string>
</property> </property>
</item> </item>
<item>
<property name="text">
<string>VUSB</string>
</property>
</item>
<item>
<property name="text">
<string>VLSB</string>
</property>
</item>
</widget> </widget>
</item> </item>
<item> <item>

View File

@ -271,6 +271,64 @@ int fftfilt::runDSB(const cmplx & in, cmplx **out)
return flen2; 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 */ /* Sliding FFT from Fldigi */
struct sfft::vrot_bins_pair { struct sfft::vrot_bins_pair {

View File

@ -1,5 +1,5 @@
/* /*
* Filters from Fldigi. * Filters from Fldigi.
*/ */
#ifndef _FFTFILT_H #ifndef _FFTFILT_H
@ -28,6 +28,7 @@ public:
int runFilt(const cmplx& in, cmplx **out); int runFilt(const cmplx& in, cmplx **out);
int runSSB(const cmplx& in, cmplx **out, bool usb, bool getDC = true); int runSSB(const cmplx& in, cmplx **out, bool usb, bool getDC = true);
int runDSB(const cmplx& in, cmplx **out); int runDSB(const cmplx& in, cmplx **out);
int runVestigial(const cmplx & in, cmplx **out, bool usb, float vf);
protected: protected:
int flen; int flen;
@ -43,13 +44,13 @@ protected:
int window; int window;
inline float fsinc(float fc, int i, int len) { 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)); sin(2 * M_PI * fc * (i - len/2)) / (M_PI * (i - len/2));
} }
inline float _blackman(int i, int len) { inline float _blackman(int i, int len) {
return (0.42 - return (0.42 -
0.50 * cos(2.0 * M_PI * i / len) + 0.50 * cos(2.0 * M_PI * i / len) +
0.08 * cos(4.0 * M_PI * i / len)); 0.08 * cos(4.0 * M_PI * i / len));
} }