Audio modulators audio feedback: AM, NFM, SSB. Removed existing changes for FreeDV and WFM

This commit is contained in:
f4exb 2019-07-31 02:38:50 +02:00
parent 68f6994f49
commit 6d3ab1a730
27 changed files with 630 additions and 91 deletions

View File

@ -57,7 +57,7 @@ AMMod::AMMod(DeviceAPI *deviceAPI) :
m_outputSampleRate(48000),
m_inputFrequencyOffset(0),
m_audioFifo(4800),
m_feedbackAudioFifo(4800),
m_feedbackAudioFifo(48000),
m_settingsMutex(QMutex::Recursive),
m_fileSize(0),
m_recordLength(0),
@ -81,6 +81,7 @@ AMMod::AMMod(DeviceAPI *deviceAPI) :
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_feedbackAudioFifo, getInputMessageQueue());
m_feedbackAudioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate();
applyFeedbackAudioSampleRate(m_feedbackAudioSampleRate);
m_toneNco.setFreq(1000.0, m_audioSampleRate);
m_cwKeyer.setSampleRate(m_audioSampleRate);
@ -174,6 +175,11 @@ void AMMod::modulateSample()
Real t;
pullAF(t);
if (m_settings.m_feedbackAudioEnable) {
pushFeedback(t * m_settings.m_feedbackVolumeFactor * 16384.0f);
}
calculateLevel(t);
m_audioBufferFill++;
@ -248,6 +254,50 @@ void AMMod::pullAF(Real& sample)
}
}
void AMMod::pushFeedback(Real sample)
{
Complex c(sample, sample);
Complex ci;
if (m_feedbackInterpolatorDistance < 1.0f) // interpolate
{
while (!m_feedbackInterpolator.interpolate(&m_feedbackInterpolatorDistanceRemain, c, &ci))
{
processOneSample(ci);
m_feedbackInterpolatorDistanceRemain += m_feedbackInterpolatorDistance;
}
}
else // decimate
{
if (m_feedbackInterpolator.decimate(&m_feedbackInterpolatorDistanceRemain, c, &ci))
{
processOneSample(ci);
m_feedbackInterpolatorDistanceRemain += m_feedbackInterpolatorDistance;
}
}
}
void AMMod::processOneSample(Complex& ci)
{
m_feedbackAudioBuffer[m_feedbackAudioBufferFill].l = ci.real();
m_feedbackAudioBuffer[m_feedbackAudioBufferFill].r = ci.imag();
++m_feedbackAudioBufferFill;
if (m_feedbackAudioBufferFill >= m_feedbackAudioBuffer.size())
{
uint res = m_feedbackAudioFifo.write((const quint8*)&m_feedbackAudioBuffer[0], m_feedbackAudioBufferFill);
if (res != m_feedbackAudioBufferFill)
{
qDebug("AMDemod::pushFeedback: %u/%u audio samples written m_feedbackInterpolatorDistance: %f",
res, m_feedbackAudioBufferFill, m_feedbackInterpolatorDistance);
m_feedbackAudioFifo.clear();
}
m_feedbackAudioBufferFill = 0;
}
}
void AMMod::calculateLevel(Real& sample)
{
if (m_levelCalcCount < m_levelNbSamples)
@ -361,12 +411,23 @@ bool AMMod::handleMessage(const Message& cmd)
{
DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd;
uint32_t sampleRate = cfg.getSampleRate();
DSPConfigureAudio::AudioType audioType = cfg.getAudioType();
qDebug() << "AMMod::handleMessage: DSPConfigureAudio:"
<< " sampleRate: " << sampleRate;
<< " sampleRate: " << sampleRate
<< " audioType: " << audioType;
if (sampleRate != m_audioSampleRate) {
applyAudioSampleRate(sampleRate);
if (audioType == DSPConfigureAudio::AudioInput)
{
if (sampleRate != m_audioSampleRate) {
applyAudioSampleRate(sampleRate);
}
}
else if (audioType == DSPConfigureAudio::AudioOutput)
{
if (sampleRate != m_audioSampleRate) {
applyFeedbackAudioSampleRate(sampleRate);
}
}
return true;
@ -436,6 +497,24 @@ void AMMod::applyAudioSampleRate(int sampleRate)
m_settingsMutex.unlock();
m_audioSampleRate = sampleRate;
applyFeedbackAudioSampleRate(m_feedbackAudioSampleRate);
}
void AMMod::applyFeedbackAudioSampleRate(unsigned int sampleRate)
{
qDebug("AMMod::applyFeedbackAudioSampleRate: %u", sampleRate);
m_settingsMutex.lock();
m_feedbackInterpolatorDistanceRemain = 0;
m_feedbackInterpolatorConsumed = false;
m_feedbackInterpolatorDistance = (Real) sampleRate / (Real) m_audioSampleRate;
Real cutoff = std::min(sampleRate, m_audioSampleRate) / 2.2f;
m_feedbackInterpolator.create(48, sampleRate, cutoff, 3.0);
m_settingsMutex.unlock();
m_feedbackAudioSampleRate = sampleRate;
}
void AMMod::applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force)
@ -546,6 +625,20 @@ void AMMod::applySettings(const AMModSettings& settings, bool force)
}
}
if ((settings.m_feedbackAudioDeviceName != m_settings.m_feedbackAudioDeviceName) || force)
{
reverseAPIKeys.append("feedbackAudioDeviceName");
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_feedbackAudioDeviceName);
audioDeviceManager->addAudioSink(&m_feedbackAudioFifo, getInputMessageQueue(), audioDeviceIndex);
uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex);
if (m_feedbackAudioSampleRate != audioSampleRate) {
reverseAPIKeys.append("feedbackAudioSampleRate");
applyFeedbackAudioSampleRate(audioSampleRate);
}
}
if (settings.m_useReverseAPI)
{
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||

View File

@ -277,11 +277,17 @@ private:
NCO m_carrierNco;
NCOF m_toneNco;
Complex m_modSample;
Interpolator m_interpolator;
Real m_interpolatorDistance;
Real m_interpolatorDistanceRemain;
bool m_interpolatorConsumed;
Interpolator m_feedbackInterpolator;
Real m_feedbackInterpolatorDistance;
Real m_feedbackInterpolatorDistanceRemain;
bool m_feedbackInterpolatorConsumed;
double m_magsq;
MovingAverageUtil<double, double, 16> m_movingAverage;
@ -315,9 +321,12 @@ private:
QNetworkRequest m_networkRequest;
void applyAudioSampleRate(int sampleRate);
void applyFeedbackAudioSampleRate(unsigned int sampleRate);
void processOneSample(Complex& ci);
void applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force = false);
void applySettings(const AMModSettings& settings, bool force = false);
void pullAF(Real& sample);
void pushFeedback(Real sample);
void calculateLevel(Real& sample);
void modulateSample();
void openFileStream();

View File

@ -236,6 +236,19 @@ void AMModGUI::on_mic_toggled(bool checked)
applySettings();
}
void AMModGUI::on_feedbackEnable_toggled(bool checked)
{
m_settings.m_feedbackAudioEnable = checked;
applySettings();
}
void AMModGUI::on_feedbackVolume_valueChanged(int value)
{
ui->feedbackVolumeText->setText(QString("%1").arg(value / 100.0, 0, 'f', 2));
m_settings.m_feedbackVolumeFactor = value / 100.0;
applySettings();
}
void AMModGUI::on_navTimeSlider_valueChanged(int value)
{
if (m_enableNavTime && ((value >= 0) && (value <= 100)))
@ -440,6 +453,10 @@ void AMModGUI::displaySettings()
ui->play->setChecked(m_settings.m_modAFInput == AMModSettings::AMModInputAF::AMModInputFile);
ui->morseKeyer->setChecked(m_settings.m_modAFInput == AMModSettings::AMModInputAF::AMModInputCWTone);
ui->feedbackEnable->setChecked(m_settings.m_feedbackAudioEnable);
ui->feedbackVolume->setValue(roundf(m_settings.m_feedbackVolumeFactor * 100.0));
ui->feedbackVolumeText->setText(QString("%1").arg(m_settings.m_feedbackVolumeFactor, 0, 'f', 2));
blockApplySettings(false);
}

View File

@ -107,6 +107,9 @@ private slots:
void on_navTimeSlider_valueChanged(int value);
void on_showFileDialog_clicked(bool checked);
void on_feedbackEnable_toggled(bool checked);
void on_feedbackVolume_valueChanged(int value);
void onWidgetRolled(QWidget* widget, bool rollDown);
void onMenuDialogCalled(const QPoint& p);

View File

@ -56,7 +56,16 @@
<property name="spacing">
<number>3</number>
</property>
<property name="margin">
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
@ -436,17 +445,10 @@
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="mic">
<property name="toolTip">
<string>Left: Source audio input Right: Select audio input device</string>
<string>Left: source audio input - Right: select audio input device</string>
</property>
<property name="text">
<string>...</string>
@ -460,6 +462,75 @@
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="feedbackEnable">
<property name="toolTip">
<string>Left: enable / disable audio feedback - Right: select audio output device</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/sound_off.png</normaloff>
<normalon>:/sound_on.png</normalon>:/sound_off.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="feedbackVolume">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Audio feedback volume</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>50</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="feedbackVolumeText">
<property name="maximumSize">
<size>
<width>30</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Audio feedback volume</string>
</property>
<property name="text">
<string>1.00</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">

View File

@ -43,7 +43,7 @@ void AMModSettings::resetToDefaults()
m_modAFInput = AMModInputAF::AMModInputNone;
m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName;
m_feedbackAudioDeviceName = AudioDeviceManager::m_defaultDeviceName;
m_feedbackVolumeFactor = 1.0f;
m_feedbackVolumeFactor = 0.5f;
m_feedbackAudioEnable = false;
m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1";

View File

@ -68,7 +68,6 @@ FreeDVMod::FreeDVMod(DeviceAPI *deviceAPI) :
m_SSBFilterBufferIndex(0),
m_sampleSink(0),
m_audioFifo(4800),
m_feedbackAudioFifo(4800),
m_settingsMutex(QMutex::Recursive),
m_fileSize(0),
m_recordLength(0),
@ -90,9 +89,6 @@ FreeDVMod::FreeDVMod(DeviceAPI *deviceAPI) :
DSPEngine::instance()->getAudioDeviceManager()->addAudioSource(&m_audioFifo, getInputMessageQueue());
m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getInputSampleRate();
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_feedbackAudioFifo, getInputMessageQueue());
m_feedbackAudioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate();
m_SSBFilter = new fftfilt(m_lowCutoff / m_audioSampleRate, m_hiCutoff / m_audioSampleRate, m_ssbFftLen);
m_SSBFilterBuffer = new Complex[m_ssbFftLen>>1]; // filter returns data exactly half of its size
std::fill(m_SSBFilterBuffer, m_SSBFilterBuffer+(m_ssbFftLen>>1), Complex{0,0});
@ -100,9 +96,6 @@ FreeDVMod::FreeDVMod(DeviceAPI *deviceAPI) :
m_audioBuffer.resize(1<<14);
m_audioBufferFill = 0;
m_feedbackAudioBuffer.resize(1<<14);
m_feedbackAudioBufferFill = 0;
m_sum.real(0.0f);
m_sum.imag(0.0f);
m_undersampleCount = 0;
@ -131,7 +124,6 @@ FreeDVMod::~FreeDVMod()
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
delete m_networkManager;
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_feedbackAudioFifo);
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSource(&m_audioFifo);
m_deviceAPI->removeChannelSourceAPI(this);

View File

@ -314,11 +314,6 @@ private:
uint m_audioBufferFill;
AudioFifo m_audioFifo;
quint32 m_feedbackAudioSampleRate;
AudioVector m_feedbackAudioBuffer;
uint m_feedbackAudioBufferFill;
AudioFifo m_feedbackAudioFifo;
QMutex m_settingsMutex;
std::ifstream m_ifstream;

View File

@ -42,9 +42,6 @@ void FreeDVModSettings::resetToDefaults()
m_title = "FreeDV Modulator";
m_modAFInput = FreeDVModInputAF::FreeDVModInputNone;
m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName;
m_feedbackAudioDeviceName = AudioDeviceManager::m_defaultDeviceName;
m_feedbackVolumeFactor = 1.0f;
m_feedbackAudioEnable = false;
m_freeDVMode = FreeDVMode::FreeDVMode2400A;
m_gaugeInputElseModem = false;
m_useReverseAPI = false;
@ -87,9 +84,6 @@ QByteArray FreeDVModSettings::serialize() const
s.writeU32(24, m_reverseAPIPort);
s.writeU32(25, m_reverseAPIDeviceIndex);
s.writeU32(26, m_reverseAPIChannelIndex);
s.writeString(27, m_feedbackAudioDeviceName);
s.writeReal(28, m_feedbackVolumeFactor);
s.writeBool(29, m_feedbackAudioEnable);
return s.final();
}
@ -168,9 +162,6 @@ bool FreeDVModSettings::deserialize(const QByteArray& data)
m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp;
d.readU32(26, &utmp, 0);
m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp;
d.readString(27, &m_feedbackAudioDeviceName, AudioDeviceManager::m_defaultDeviceName);
d.readReal(28, &m_feedbackVolumeFactor, 1.0);
d.readBool(29, &m_feedbackAudioEnable, false);
return true;
}

View File

@ -55,9 +55,6 @@ struct FreeDVModSettings
QString m_title;
FreeDVModInputAF m_modAFInput;
QString m_audioDeviceName;
QString m_feedbackAudioDeviceName; //!< This is the audio device you send the audio samples to for audio feedback
float m_feedbackVolumeFactor;
bool m_feedbackAudioEnable;
FreeDVMode m_freeDVMode;
bool m_gaugeInputElseModem; //!< Volume gauge shows speech input level else modem level

View File

@ -60,7 +60,7 @@ NFMMod::NFMMod(DeviceAPI *deviceAPI) :
m_inputFrequencyOffset(0),
m_modPhasor(0.0f),
m_audioFifo(4800),
m_feedbackAudioFifo(4800),
m_feedbackAudioFifo(48000),
m_settingsMutex(QMutex::Recursive),
m_fileSize(0),
m_recordLength(0),
@ -84,6 +84,7 @@ NFMMod::NFMMod(DeviceAPI *deviceAPI) :
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_feedbackAudioFifo, getInputMessageQueue());
m_feedbackAudioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate();
applyFeedbackAudioSampleRate(m_feedbackAudioSampleRate);
m_lowpass.create(301, m_audioSampleRate, 250.0);
m_toneNco.setFreq(1000.0, m_audioSampleRate);
@ -178,6 +179,11 @@ void NFMMod::modulateSample()
Real t;
pullAF(t);
if (m_settings.m_feedbackAudioEnable) {
pushFeedback(t * m_settings.m_feedbackVolumeFactor * 16384.0f);
}
calculateLevel(t);
m_audioBufferFill++;
@ -262,6 +268,50 @@ void NFMMod::pullAF(Real& sample)
}
}
void NFMMod::pushFeedback(Real sample)
{
Complex c(sample, sample);
Complex ci;
if (m_feedbackInterpolatorDistance < 1.0f) // interpolate
{
while (!m_feedbackInterpolator.interpolate(&m_feedbackInterpolatorDistanceRemain, c, &ci))
{
processOneSample(ci);
m_feedbackInterpolatorDistanceRemain += m_feedbackInterpolatorDistance;
}
}
else // decimate
{
if (m_feedbackInterpolator.decimate(&m_feedbackInterpolatorDistanceRemain, c, &ci))
{
processOneSample(ci);
m_feedbackInterpolatorDistanceRemain += m_feedbackInterpolatorDistance;
}
}
}
void NFMMod::processOneSample(Complex& ci)
{
m_feedbackAudioBuffer[m_feedbackAudioBufferFill].l = ci.real();
m_feedbackAudioBuffer[m_feedbackAudioBufferFill].r = ci.imag();
++m_feedbackAudioBufferFill;
if (m_feedbackAudioBufferFill >= m_feedbackAudioBuffer.size())
{
uint res = m_feedbackAudioFifo.write((const quint8*)&m_feedbackAudioBuffer[0], m_feedbackAudioBufferFill);
if (res != m_feedbackAudioBufferFill)
{
qDebug("AMDemod::pushFeedback: %u/%u audio samples written m_feedbackInterpolatorDistance: %f",
res, m_feedbackAudioBufferFill, m_feedbackInterpolatorDistance);
m_feedbackAudioFifo.clear();
}
m_feedbackAudioBufferFill = 0;
}
}
void NFMMod::calculateLevel(Real& sample)
{
if (m_levelCalcCount < m_levelNbSamples)
@ -376,12 +426,23 @@ bool NFMMod::handleMessage(const Message& cmd)
{
DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd;
uint32_t sampleRate = cfg.getSampleRate();
DSPConfigureAudio::AudioType audioType = cfg.getAudioType();
qDebug() << "NFMMod::handleMessage: DSPConfigureAudio:"
<< " sampleRate: " << sampleRate;
<< " sampleRate: " << sampleRate
<< " audioType: " << audioType;
if (sampleRate != m_audioSampleRate) {
applyAudioSampleRate(sampleRate);
if (audioType == DSPConfigureAudio::AudioInput)
{
if (sampleRate != m_audioSampleRate) {
applyAudioSampleRate(sampleRate);
}
}
else if (audioType == DSPConfigureAudio::AudioOutput)
{
if (sampleRate != m_audioSampleRate) {
applyFeedbackAudioSampleRate(sampleRate);
}
}
return true;
@ -454,6 +515,24 @@ void NFMMod::applyAudioSampleRate(int sampleRate)
m_settingsMutex.unlock();
m_audioSampleRate = sampleRate;
applyFeedbackAudioSampleRate(m_feedbackAudioSampleRate);
}
void NFMMod::applyFeedbackAudioSampleRate(unsigned int sampleRate)
{
qDebug("AMMod::applyFeedbackAudioSampleRate: %u", sampleRate);
m_settingsMutex.lock();
m_feedbackInterpolatorDistanceRemain = 0;
m_feedbackInterpolatorConsumed = false;
m_feedbackInterpolatorDistance = (Real) sampleRate / (Real) m_audioSampleRate;
Real cutoff = std::min(sampleRate, m_audioSampleRate) / 2.2f;
m_feedbackInterpolator.create(48, sampleRate, cutoff, 3.0);
m_settingsMutex.unlock();
m_feedbackAudioSampleRate = sampleRate;
}
void NFMMod::applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force)
@ -588,6 +667,20 @@ void NFMMod::applySettings(const NFMModSettings& settings, bool force)
}
}
if ((settings.m_feedbackAudioDeviceName != m_settings.m_feedbackAudioDeviceName) || force)
{
reverseAPIKeys.append("feedbackAudioDeviceName");
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_feedbackAudioDeviceName);
audioDeviceManager->addAudioSink(&m_feedbackAudioFifo, getInputMessageQueue(), audioDeviceIndex);
uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex);
if (m_feedbackAudioSampleRate != audioSampleRate) {
reverseAPIKeys.append("feedbackAudioSampleRate");
applyFeedbackAudioSampleRate(audioSampleRate);
}
}
if (settings.m_useReverseAPI)
{
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||

View File

@ -281,10 +281,17 @@ private:
NCOF m_ctcssNco;
float m_modPhasor; //!< baseband modulator phasor
Complex m_modSample;
Interpolator m_interpolator;
Real m_interpolatorDistance;
Real m_interpolatorDistanceRemain;
bool m_interpolatorConsumed;
Interpolator m_feedbackInterpolator;
Real m_feedbackInterpolatorDistance;
Real m_feedbackInterpolatorDistanceRemain;
bool m_feedbackInterpolatorConsumed;
Lowpass<Real> m_lowpass;
Bandpass<Real> m_bandpass;
@ -322,9 +329,12 @@ private:
static const int m_levelNbSamples;
void applyAudioSampleRate(int sampleRate);
void applyFeedbackAudioSampleRate(unsigned int sampleRate);
void processOneSample(Complex& ci);
void applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force = false);
void applySettings(const NFMModSettings& settings, bool force = false);
void pullAF(Real& sample);
void pushFeedback(Real sample);
void calculateLevel(Real& sample);
void modulateSample();
void openFileStream();

View File

@ -240,6 +240,19 @@ void NFMModGUI::on_mic_toggled(bool checked)
applySettings();
}
void NFMModGUI::on_feedbackEnable_toggled(bool checked)
{
m_settings.m_feedbackAudioEnable = checked;
applySettings();
}
void NFMModGUI::on_feedbackVolume_valueChanged(int value)
{
ui->feedbackVolumeText->setText(QString("%1").arg(value / 100.0, 0, 'f', 2));
m_settings.m_feedbackVolumeFactor = value / 100.0;
applySettings();
}
void NFMModGUI::on_navTimeSlider_valueChanged(int value)
{
if (m_enableNavTime && ((value >= 0) && (value <= 100)))
@ -474,6 +487,10 @@ void NFMModGUI::displaySettings()
ui->play->setChecked(m_settings.m_modAFInput == NFMModSettings::NFMModInputAF::NFMModInputFile);
ui->morseKeyer->setChecked(m_settings.m_modAFInput == NFMModSettings::NFMModInputAF::NFMModInputCWTone);
ui->feedbackEnable->setChecked(m_settings.m_feedbackAudioEnable);
ui->feedbackVolume->setValue(roundf(m_settings.m_feedbackVolumeFactor * 100.0));
ui->feedbackVolumeText->setText(QString("%1").arg(m_settings.m_feedbackVolumeFactor, 0, 'f', 2));
blockApplySettings(false);
}

View File

@ -110,6 +110,9 @@ private slots:
void on_ctcss_currentIndexChanged(int index);
void on_ctcssOn_toggled(bool checked);
void on_feedbackEnable_toggled(bool checked);
void on_feedbackVolume_valueChanged(int value);
void onWidgetRolled(QWidget* widget, bool rollDown);
void onMenuDialogCalled(const QPoint& p);

View File

@ -56,7 +56,16 @@
<property name="spacing">
<number>3</number>
</property>
<property name="margin">
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
@ -476,13 +485,6 @@
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="mic">
<property name="toolTip">
@ -500,6 +502,75 @@
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="feedbackEnable">
<property name="toolTip">
<string>Left: enable / disable audio feedback - Right: select audio output device</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/sound_off.png</normaloff>
<normalon>:/sound_on.png</normalon>:/sound_off.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="feedbackVolume">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Audio feedback volume</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>50</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="feedbackVolumeText">
<property name="maximumSize">
<size>
<width>30</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Audio feedback volume</string>
</property>
<property name="text">
<string>1.00</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
@ -520,6 +591,10 @@
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="ctcssLayer">
<item>
<widget class="QLabel" name="ctcssLabel">
<property name="text">
@ -550,6 +625,19 @@
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>

View File

@ -61,7 +61,7 @@ void NFMModSettings::resetToDefaults()
m_modAFInput = NFMModInputAF::NFMModInputNone;
m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName;
m_feedbackAudioDeviceName = AudioDeviceManager::m_defaultDeviceName;
m_feedbackVolumeFactor = 1.0f;
m_feedbackVolumeFactor = 0.5f;
m_feedbackAudioEnable = false;
m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1";

View File

@ -66,7 +66,7 @@ SSBMod::SSBMod(DeviceAPI *deviceAPI) :
m_DSBFilterBufferIndex(0),
m_sampleSink(0),
m_audioFifo(4800),
m_feedbackAudioFifo(4800),
m_feedbackAudioFifo(48000),
m_settingsMutex(QMutex::Recursive),
m_fileSize(0),
m_recordLength(0),
@ -83,6 +83,7 @@ SSBMod::SSBMod(DeviceAPI *deviceAPI) :
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_feedbackAudioFifo, getInputMessageQueue());
m_feedbackAudioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate();
applyFeedbackAudioSampleRate(m_feedbackAudioSampleRate);
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);
@ -206,6 +207,11 @@ void SSBMod::pullAudio(int nbSamples)
void SSBMod::modulateSample()
{
pullAF(m_modSample);
if (m_settings.m_feedbackAudioEnable) {
pushFeedback(m_modSample * m_settings.m_feedbackVolumeFactor * 16384.0f);
}
calculateLevel(m_modSample);
m_audioBufferFill++;
}
@ -503,6 +509,49 @@ void SSBMod::pullAF(Complex& sample)
}
}
void SSBMod::pushFeedback(Complex c)
{
Complex ci;
if (m_feedbackInterpolatorDistance < 1.0f) // interpolate
{
while (!m_feedbackInterpolator.interpolate(&m_feedbackInterpolatorDistanceRemain, c, &ci))
{
processOneSample(ci);
m_feedbackInterpolatorDistanceRemain += m_feedbackInterpolatorDistance;
}
}
else // decimate
{
if (m_feedbackInterpolator.decimate(&m_feedbackInterpolatorDistanceRemain, c, &ci))
{
processOneSample(ci);
m_feedbackInterpolatorDistanceRemain += m_feedbackInterpolatorDistance;
}
}
}
void SSBMod::processOneSample(Complex& ci)
{
m_feedbackAudioBuffer[m_feedbackAudioBufferFill].l = ci.real();
m_feedbackAudioBuffer[m_feedbackAudioBufferFill].r = ci.imag();
++m_feedbackAudioBufferFill;
if (m_feedbackAudioBufferFill >= m_feedbackAudioBuffer.size())
{
uint res = m_feedbackAudioFifo.write((const quint8*)&m_feedbackAudioBuffer[0], m_feedbackAudioBufferFill);
if (res != m_feedbackAudioBufferFill)
{
qDebug("AMDemod::pushFeedback: %u/%u audio samples written m_feedbackInterpolatorDistance: %f",
res, m_feedbackAudioBufferFill, m_feedbackInterpolatorDistance);
m_feedbackAudioFifo.clear();
}
m_feedbackAudioBufferFill = 0;
}
}
void SSBMod::calculateLevel(Complex& sample)
{
Real t = sample.real(); // TODO: possibly adjust depending on sample type
@ -617,12 +666,23 @@ bool SSBMod::handleMessage(const Message& cmd)
{
DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd;
uint32_t sampleRate = cfg.getSampleRate();
DSPConfigureAudio::AudioType audioType = cfg.getAudioType();
qDebug() << "SSBMod::handleMessage: DSPConfigureAudio:"
<< " sampleRate: " << sampleRate;
<< " sampleRate: " << sampleRate
<< " audioType: " << audioType;
if (sampleRate != m_audioSampleRate) {
applyAudioSampleRate(sampleRate);
if (audioType == DSPConfigureAudio::AudioInput)
{
if (sampleRate != m_audioSampleRate) {
applyAudioSampleRate(sampleRate);
}
}
else if (audioType == DSPConfigureAudio::AudioOutput)
{
if (sampleRate != m_audioSampleRate) {
applyFeedbackAudioSampleRate(sampleRate);
}
}
return true;
@ -727,6 +787,25 @@ void SSBMod::applyAudioSampleRate(int sampleRate)
DSPConfigureAudio *cfg = new DSPConfigureAudio(m_audioSampleRate, DSPConfigureAudio::AudioInput);
getMessageQueueToGUI()->push(cfg);
}
applyFeedbackAudioSampleRate(m_feedbackAudioSampleRate);
}
void SSBMod::applyFeedbackAudioSampleRate(unsigned int sampleRate)
{
qDebug("AMMod::applyFeedbackAudioSampleRate: %u", sampleRate);
m_settingsMutex.lock();
m_feedbackInterpolatorDistanceRemain = 0;
m_feedbackInterpolatorConsumed = false;
m_feedbackInterpolatorDistance = (Real) sampleRate / (Real) m_audioSampleRate;
Real cutoff = std::min(sampleRate, m_audioSampleRate) / 2.2f;
m_feedbackInterpolator.create(48, sampleRate, cutoff, 3.0);
m_settingsMutex.unlock();
m_feedbackAudioSampleRate = sampleRate;
}
void SSBMod::applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force)
@ -876,6 +955,20 @@ void SSBMod::applySettings(const SSBModSettings& settings, bool force)
}
}
if ((settings.m_feedbackAudioDeviceName != m_settings.m_feedbackAudioDeviceName) || force)
{
reverseAPIKeys.append("feedbackAudioDeviceName");
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_feedbackAudioDeviceName);
audioDeviceManager->addAudioSink(&m_feedbackAudioFifo, getInputMessageQueue(), audioDeviceIndex);
uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex);
if (m_feedbackAudioSampleRate != audioSampleRate) {
reverseAPIKeys.append("feedbackAudioSampleRate");
applyFeedbackAudioSampleRate(audioSampleRate);
}
}
if (settings.m_useReverseAPI)
{
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||

View File

@ -282,11 +282,18 @@ private:
NCOF m_carrierNco;
NCOF m_toneNco;
Complex m_modSample;
Interpolator m_interpolator;
Real m_interpolatorDistance;
Real m_interpolatorDistanceRemain;
bool m_interpolatorConsumed;
fftfilt* m_SSBFilter;
Interpolator m_feedbackInterpolator;
Real m_feedbackInterpolatorDistance;
Real m_feedbackInterpolatorDistanceRemain;
bool m_feedbackInterpolatorConsumed;
fftfilt* m_SSBFilter;
fftfilt* m_DSBFilter;
Complex* m_SSBFilterBuffer;
Complex* m_DSBFilterBuffer;
@ -336,9 +343,12 @@ private:
static const int m_levelNbSamples;
void applyAudioSampleRate(int sampleRate);
void applyFeedbackAudioSampleRate(unsigned int sampleRate);
void processOneSample(Complex& ci);
void applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force = false);
void applySettings(const SSBModSettings& settings, bool force = false);
void pullAF(Complex& sample);
void pushFeedback(Complex sample);
void calculateLevel(Complex& sample);
void modulateSample();
void openFileStream();

View File

@ -292,6 +292,19 @@ void SSBModGUI::on_mic_toggled(bool checked)
applySettings();
}
void SSBModGUI::on_feedbackEnable_toggled(bool checked)
{
m_settings.m_feedbackAudioEnable = checked;
applySettings();
}
void SSBModGUI::on_feedbackVolume_valueChanged(int value)
{
ui->feedbackVolumeText->setText(QString("%1").arg(value / 100.0, 0, 'f', 2));
m_settings.m_feedbackVolumeFactor = value / 100.0;
applySettings();
}
void SSBModGUI::on_agc_toggled(bool checked)
{
m_settings.m_agc = checked;
@ -679,6 +692,10 @@ void SSBModGUI::displaySettings()
ui->play->setChecked(m_settings.m_modAFInput == SSBModSettings::SSBModInputAF::SSBModInputFile);
ui->morseKeyer->setChecked(m_settings.m_modAFInput == SSBModSettings::SSBModInputAF::SSBModInputCWTone);
ui->feedbackEnable->setChecked(m_settings.m_feedbackAudioEnable);
ui->feedbackVolume->setValue(roundf(m_settings.m_feedbackVolumeFactor * 100.0));
ui->feedbackVolumeText->setText(QString("%1").arg(m_settings.m_feedbackVolumeFactor, 0, 'f', 2));
blockApplySettings(false);
}

View File

@ -120,6 +120,9 @@ private slots:
void on_navTimeSlider_valueChanged(int value);
void on_showFileDialog_clicked(bool checked);
void on_feedbackEnable_toggled(bool checked);
void on_feedbackVolume_valueChanged(int value);
void onWidgetRolled(QWidget* widget, bool rollDown);
void onMenuDialogCalled(const QPoint& p);

View File

@ -811,13 +811,6 @@
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="mic">
<property name="toolTip">
@ -835,6 +828,75 @@
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="feedbackEnable">
<property name="toolTip">
<string>Left: enable / disable audio feedback - Right: select audio output device</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/sound_off.png</normaloff>
<normalon>:/sound_on.png</normalon>:/sound_off.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="feedbackVolume">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Audio feedback volume</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>50</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="feedbackVolumeText">
<property name="maximumSize">
<size>
<width>30</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Audio feedback volume</string>
</property>
<property name="text">
<string>1.00</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">

View File

@ -64,7 +64,7 @@ void SSBModSettings::resetToDefaults()
m_modAFInput = SSBModInputAF::SSBModInputNone;
m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName;
m_feedbackAudioDeviceName = AudioDeviceManager::m_defaultDeviceName;
m_feedbackVolumeFactor = 1.0f;
m_feedbackVolumeFactor = 0.5f;
m_feedbackAudioEnable = false;
m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1";

View File

@ -60,7 +60,6 @@ WFMMod::WFMMod(DeviceAPI *deviceAPI) :
m_inputFrequencyOffset(0),
m_modPhasor(0.0f),
m_audioFifo(4800),
m_feedbackAudioFifo(4800),
m_settingsMutex(QMutex::Recursive),
m_fileSize(0),
m_recordLength(0),
@ -80,17 +79,11 @@ WFMMod::WFMMod(DeviceAPI *deviceAPI) :
m_audioBuffer.resize(1<<14);
m_audioBufferFill = 0;
m_feedbackAudioBuffer.resize(1<<14);
m_feedbackAudioBufferFill = 0;
m_magsq = 0.0;
DSPEngine::instance()->getAudioDeviceManager()->addAudioSource(&m_audioFifo, getInputMessageQueue());
m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getInputSampleRate();
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_feedbackAudioFifo, getInputMessageQueue());
m_feedbackAudioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate();
m_toneNcoRF.setFreq(1000.0, m_outputSampleRate);
m_cwKeyer.setSampleRate(m_outputSampleRate);
m_cwKeyer.reset();
@ -111,7 +104,6 @@ WFMMod::~WFMMod()
{
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
delete m_networkManager;
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_feedbackAudioFifo);
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSource(&m_audioFifo);
m_deviceAPI->removeChannelSourceAPI(this);
m_deviceAPI->removeChannelSource(m_threadedChannelizer);

View File

@ -297,11 +297,6 @@ private:
uint m_audioBufferFill;
AudioFifo m_audioFifo;
quint32 m_feedbackAudioSampleRate;
AudioVector m_feedbackAudioBuffer;
uint m_feedbackAudioBufferFill;
AudioFifo m_feedbackAudioFifo;
SampleVector m_sampleBuffer;
QMutex m_settingsMutex;

View File

@ -50,9 +50,6 @@ void WFMModSettings::resetToDefaults()
m_title = "WFM Modulator";
m_modAFInput = WFMModInputNone;
m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName;
m_feedbackAudioDeviceName = AudioDeviceManager::m_defaultDeviceName;
m_feedbackVolumeFactor = 1.0f;
m_feedbackAudioEnable = false;
m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1";
m_reverseAPIPort = 8888;
@ -88,9 +85,6 @@ QByteArray WFMModSettings::serialize() const
s.writeU32(15, m_reverseAPIPort);
s.writeU32(16, m_reverseAPIDeviceIndex);
s.writeU32(17, m_reverseAPIChannelIndex);
s.writeString(18, m_feedbackAudioDeviceName);
s.writeReal(19, m_feedbackVolumeFactor);
s.writeBool(20, m_feedbackAudioEnable);
return s.final();
}
@ -154,9 +148,6 @@ bool WFMModSettings::deserialize(const QByteArray& data)
m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp;
d.readU32(17, &utmp, 0);
m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp;
d.readString(18, &m_feedbackAudioDeviceName, AudioDeviceManager::m_defaultDeviceName);
d.readReal(19, &m_feedbackVolumeFactor, 1.0);
d.readBool(20, &m_feedbackAudioEnable, false);
return true;
}

View File

@ -48,9 +48,6 @@ struct WFMModSettings
QString m_title;
WFMModInputAF m_modAFInput;
QString m_audioDeviceName; //!< This is the audio device you get the audio samples from
QString m_feedbackAudioDeviceName; //!< This is the audio device you send the audio samples to for audio feedback
float m_feedbackVolumeFactor;
bool m_feedbackAudioEnable;
bool m_useReverseAPI;
QString m_reverseAPIAddress;
uint16_t m_reverseAPIPort;

View File

@ -388,7 +388,7 @@ public:
{ }
int getSampleRate() const { return m_sampleRate; }
bool getAudioType() const { return m_autioType; }
AudioType getAudioType() const { return m_autioType; }
private:
int m_sampleRate;