mirror of
				https://github.com/f4exb/sdrangel.git
				synced 2025-10-31 04:50:29 -04:00 
			
		
		
		
	AM Modulator: added file input (basic)
This commit is contained in:
		
							parent
							
								
									4398a7426a
								
							
						
					
					
						commit
						7c1b1032c9
					
				| @ -37,7 +37,7 @@ Transmission or signal generation support for eligible devices (BladeRF and Hack | ||||
|   - Phase 1: version 2.2.0: generation to file (File Sink) with AM modulator with simple sine modulation. Fixed sample rate of 48 kS/s (no effective interpolation) | ||||
|   - Phase 2: version 2.2.x: full baseband interpolation chain: in AM modulator and Up Channelizer. | ||||
|   - 2.3.0: SDRplay came into play ... | ||||
|   - Phase 3a: version 2.3.x: Improve AM modulator with audio file input | ||||
|   - Phase 3a: version 2.3.1: Improve AM modulator with audio file input | ||||
|   - Phase 3b: version 2.3.x: Improve AM modulator with audio input (Mic) support | ||||
|   - Phase 4a: version 2.4.0: FM modulator | ||||
|   - Phase 4b: version 2.4.x: WFM modulator | ||||
|  | ||||
							
								
								
									
										6
									
								
								debian/changelog
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								debian/changelog
									
									
									
									
										vendored
									
									
								
							| @ -1,3 +1,9 @@ | ||||
| sdrangel (2.3.1-1) unstable; urgency=medium | ||||
| 
 | ||||
|   * AM Modulator: support file input | ||||
| 
 | ||||
|  -- Edouard Griffiths, F4EXB <f4exb06@gmail.com>  Sun, 27 Nov 2016 23:14:18 +0100 | ||||
| 
 | ||||
| sdrangel (2.3.0-1) unstable; urgency=medium | ||||
| 
 | ||||
|   * SDRplay support: new input source plugin | ||||
|  | ||||
| @ -26,6 +26,13 @@ | ||||
| #include "dsp/pidcontroller.h" | ||||
| 
 | ||||
| MESSAGE_CLASS_DEFINITION(AMMod::MsgConfigureAMMod, Message) | ||||
| MESSAGE_CLASS_DEFINITION(AMMod::MsgConfigureFileSourceName, Message) | ||||
| MESSAGE_CLASS_DEFINITION(AMMod::MsgConfigureFileSourceSeek, Message) | ||||
| MESSAGE_CLASS_DEFINITION(AMMod::MsgConfigureAFInput, Message) | ||||
| MESSAGE_CLASS_DEFINITION(AMMod::MsgConfigureFileSourceStreamTiming, Message) | ||||
| MESSAGE_CLASS_DEFINITION(AMMod::MsgReportFileSourceStreamData, Message) | ||||
| MESSAGE_CLASS_DEFINITION(AMMod::MsgReportFileSourceStreamTiming, Message) | ||||
| 
 | ||||
| 
 | ||||
| AMMod::AMMod() : | ||||
| 	m_settingsMutex(QMutex::Recursive) | ||||
| @ -49,6 +56,8 @@ AMMod::AMMod() : | ||||
| 	m_magsq = 0.0; | ||||
| 
 | ||||
| 	m_toneNco.setFreq(1000.0, m_config.m_audioSampleRate); | ||||
| 
 | ||||
| 	m_afInput = AMModInputNone; | ||||
| } | ||||
| 
 | ||||
| AMMod::~AMMod() | ||||
| @ -64,18 +73,19 @@ void AMMod::configure(MessageQueue* messageQueue, Real rfBandwidth, Real afBandw | ||||
| void AMMod::pull(Sample& sample) | ||||
| { | ||||
| 	Complex ci; | ||||
| 	Real t; | ||||
| 
 | ||||
| 	m_settingsMutex.lock(); | ||||
| 
 | ||||
|     if (m_interpolatorDistance > 1.0f) // decimate
 | ||||
|     { | ||||
|         Real t = m_toneNco.next(); | ||||
|         pullAF(t); | ||||
|         m_modSample.real(((t+1.0f) * m_running.m_modFactor * 16384.0f)); // modulate and scale zero frequency carrier
 | ||||
|         m_modSample.imag(0.0f); | ||||
| 
 | ||||
|         while (!m_interpolator.decimate(&m_interpolatorDistanceRemain, m_modSample, &ci)) | ||||
|         { | ||||
|             Real t = m_toneNco.next(); | ||||
|             pullAF(t); | ||||
|             m_modSample.real(((t+1.0f) * m_running.m_modFactor * 16384.0f)); // modulate and scale zero frequency carrier
 | ||||
|             m_modSample.imag(0.0f); | ||||
|         } | ||||
| @ -84,7 +94,7 @@ void AMMod::pull(Sample& sample) | ||||
|     { | ||||
|         if (m_interpolator.interpolate(&m_interpolatorDistanceRemain, m_modSample, &ci)) | ||||
|         { | ||||
|             Real t = m_toneNco.next(); | ||||
|             pullAF(t); | ||||
|             m_modSample.real(((t+1.0f) * m_running.m_modFactor * 16384.0f)); // modulate and scale zero frequency carrier
 | ||||
|             m_modSample.imag(0.0f); | ||||
|         } | ||||
| @ -105,6 +115,41 @@ void AMMod::pull(Sample& sample) | ||||
| 	sample.m_imag = (FixReal) ci.imag(); | ||||
| } | ||||
| 
 | ||||
| void AMMod::pullAF(Real& sample) | ||||
| { | ||||
|     switch (m_afInput) | ||||
|     { | ||||
|     case AMModInputTone: | ||||
|         sample = m_toneNco.next(); | ||||
|         break; | ||||
|     case AMModInputFile: | ||||
|         // sox f4exb_call.wav --encoding float --endian little f4exb_call.raw
 | ||||
|         // ffplay -f f32le -ar 48k -ac 1 f4exb_call.raw
 | ||||
|         if (m_ifstream.is_open()) | ||||
|         { | ||||
|             if (m_ifstream.eof()) // TODO: handle loop playback situation
 | ||||
|             { | ||||
|                 m_ifstream.clear(); | ||||
|                 m_ifstream.seekg(0, std::ios::beg); | ||||
|             } | ||||
| 
 | ||||
|             m_ifstream.read(reinterpret_cast<char*>(&sample), sizeof(Real)); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             sample = 0.0f; | ||||
|         } | ||||
|         break; | ||||
|     case AMModInputAudio: | ||||
|         sample = 0.0f; // TODO
 | ||||
|         break; | ||||
|     case AMModInputNone: | ||||
|     default: | ||||
|         sample = 0.0f; | ||||
|         break; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void AMMod::start() | ||||
| { | ||||
| 	qDebug() << "AMMod::start: m_outputSampleRate: " << m_config.m_outputSampleRate | ||||
| @ -155,6 +200,37 @@ bool AMMod::handleMessage(const Message& cmd) | ||||
| 
 | ||||
| 		return true; | ||||
| 	} | ||||
| 	else if (MsgConfigureFileSourceName::match(cmd)) | ||||
|     { | ||||
|         MsgConfigureFileSourceName& conf = (MsgConfigureFileSourceName&) cmd; | ||||
|         m_fileName = conf.getFileName(); | ||||
|         openFileStream(); | ||||
|         return true; | ||||
|     } | ||||
|     else if (MsgConfigureFileSourceSeek::match(cmd)) | ||||
|     { | ||||
|         MsgConfigureFileSourceSeek& conf = (MsgConfigureFileSourceSeek&) cmd; | ||||
|         int seekPercentage = conf.getPercentage(); | ||||
|         seekFileStream(seekPercentage); | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
|     else if (MsgConfigureAFInput::match(cmd)) | ||||
|     { | ||||
|         MsgConfigureAFInput& conf = (MsgConfigureAFInput&) cmd; | ||||
|         m_afInput = conf.getAFInput(); | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
|     else if (MsgConfigureFileSourceStreamTiming::match(cmd)) | ||||
|     { | ||||
|         std::size_t samplesCount = m_ifstream.tellg() / sizeof(Real); | ||||
|         MsgReportFileSourceStreamTiming *report; | ||||
|         report = MsgReportFileSourceStreamTiming::create(samplesCount); | ||||
|         getOutputMessageQueue()->push(report); | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| 	else | ||||
| 	{ | ||||
| 		return false; | ||||
| @ -200,3 +276,37 @@ void AMMod::apply() | ||||
| 	m_running.m_audioMute = m_config.m_audioMute; | ||||
| } | ||||
| 
 | ||||
| void AMMod::openFileStream() | ||||
| { | ||||
|     if (m_ifstream.is_open()) { | ||||
|         m_ifstream.close(); | ||||
|     } | ||||
| 
 | ||||
|     m_ifstream.open(m_fileName.toStdString().c_str(), std::ios::binary | std::ios::ate); | ||||
|     quint64 fileSize = m_ifstream.tellg(); | ||||
|     m_ifstream.seekg(0,std::ios_base::beg); | ||||
| 
 | ||||
|     m_sampleRate = 48000; // fixed rate
 | ||||
|     m_recordLength = fileSize / (sizeof(Real) * m_sampleRate); | ||||
| 
 | ||||
|     qDebug() << "AMMod::openFileStream: " << m_fileName.toStdString().c_str() | ||||
|             << " fileSize: " << fileSize << "bytes" | ||||
|             << " length: " << m_recordLength << " seconds"; | ||||
| 
 | ||||
|     MsgReportFileSourceStreamData *report; | ||||
|     report = MsgReportFileSourceStreamData::create(m_sampleRate, m_recordLength); | ||||
|     getOutputMessageQueue()->push(report); | ||||
| } | ||||
| 
 | ||||
| void AMMod::seekFileStream(int seekPercentage) | ||||
| { | ||||
|     QMutexLocker mutexLocker(&m_settingsMutex); | ||||
| 
 | ||||
|     if (m_ifstream.is_open()) | ||||
|     { | ||||
|         int seekPoint = ((m_recordLength * seekPercentage) / 100) * m_sampleRate; | ||||
|         seekPoint *= sizeof(Real); | ||||
|         m_ifstream.clear(); | ||||
|         m_ifstream.seekg(seekPoint, std::ios::beg); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -19,6 +19,9 @@ | ||||
| 
 | ||||
| #include <QMutex> | ||||
| #include <vector> | ||||
| #include <iostream> | ||||
| #include <fstream> | ||||
| 
 | ||||
| #include "dsp/basebandsamplesource.h" | ||||
| #include "dsp/nco.h" | ||||
| #include "dsp/interpolator.h" | ||||
| @ -32,6 +35,142 @@ class AMMod : public BasebandSampleSource { | ||||
|     Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|     typedef enum | ||||
|     { | ||||
|         AMModInputNone, | ||||
|         AMModInputTone, | ||||
|         AMModInputFile, | ||||
|         AMModInputAudio | ||||
|     } AMModInputAF; | ||||
| 
 | ||||
|     class MsgConfigureFileSourceName : public Message | ||||
|     { | ||||
|         MESSAGE_CLASS_DECLARATION | ||||
| 
 | ||||
|     public: | ||||
|         const QString& getFileName() const { return m_fileName; } | ||||
| 
 | ||||
|         static MsgConfigureFileSourceName* create(const QString& fileName) | ||||
|         { | ||||
|             return new MsgConfigureFileSourceName(fileName); | ||||
|         } | ||||
| 
 | ||||
|     private: | ||||
|         QString m_fileName; | ||||
| 
 | ||||
|         MsgConfigureFileSourceName(const QString& fileName) : | ||||
|             Message(), | ||||
|             m_fileName(fileName) | ||||
|         { } | ||||
|     }; | ||||
| 
 | ||||
|     class MsgConfigureFileSourceSeek : public Message | ||||
|     { | ||||
|         MESSAGE_CLASS_DECLARATION | ||||
| 
 | ||||
|     public: | ||||
|         int getPercentage() const { return m_seekPercentage; } | ||||
| 
 | ||||
|         static MsgConfigureFileSourceSeek* create(int seekPercentage) | ||||
|         { | ||||
|             return new MsgConfigureFileSourceSeek(seekPercentage); | ||||
|         } | ||||
| 
 | ||||
|     protected: | ||||
|         int m_seekPercentage; //!< percentage of seek position from the beginning 0..100
 | ||||
| 
 | ||||
|         MsgConfigureFileSourceSeek(int seekPercentage) : | ||||
|             Message(), | ||||
|             m_seekPercentage(seekPercentage) | ||||
|         { } | ||||
|     }; | ||||
| 
 | ||||
|     class MsgConfigureFileSourceStreamTiming : public Message { | ||||
|         MESSAGE_CLASS_DECLARATION | ||||
| 
 | ||||
|     public: | ||||
| 
 | ||||
|         static MsgConfigureFileSourceStreamTiming* create() | ||||
|         { | ||||
|             return new MsgConfigureFileSourceStreamTiming(); | ||||
|         } | ||||
| 
 | ||||
|     private: | ||||
| 
 | ||||
|         MsgConfigureFileSourceStreamTiming() : | ||||
|             Message() | ||||
|         { } | ||||
|     }; | ||||
| 
 | ||||
|     class MsgConfigureAFInput : public Message | ||||
|     { | ||||
|         MESSAGE_CLASS_DECLARATION | ||||
| 
 | ||||
|     public: | ||||
|         AMModInputAF getAFInput() const { return m_afInput; } | ||||
| 
 | ||||
|         static MsgConfigureAFInput* create(AMModInputAF afInput) | ||||
|         { | ||||
|             return new MsgConfigureAFInput(afInput); | ||||
|         } | ||||
| 
 | ||||
|     private: | ||||
|         AMModInputAF m_afInput; | ||||
| 
 | ||||
|         MsgConfigureAFInput(AMModInputAF afInput) : | ||||
|             Message(), | ||||
|             m_afInput(afInput) | ||||
|         { } | ||||
|     }; | ||||
| 
 | ||||
|     class MsgReportFileSourceStreamTiming : public Message | ||||
|     { | ||||
|         MESSAGE_CLASS_DECLARATION | ||||
| 
 | ||||
|     public: | ||||
|         std::size_t getSamplesCount() const { return m_samplesCount; } | ||||
| 
 | ||||
|         static MsgReportFileSourceStreamTiming* create(std::size_t samplesCount) | ||||
|         { | ||||
|             return new MsgReportFileSourceStreamTiming(samplesCount); | ||||
|         } | ||||
| 
 | ||||
|     protected: | ||||
|         std::size_t m_samplesCount; | ||||
| 
 | ||||
|         MsgReportFileSourceStreamTiming(std::size_t samplesCount) : | ||||
|             Message(), | ||||
|             m_samplesCount(samplesCount) | ||||
|         { } | ||||
|     }; | ||||
| 
 | ||||
|     class MsgReportFileSourceStreamData : public Message { | ||||
|         MESSAGE_CLASS_DECLARATION | ||||
| 
 | ||||
|     public: | ||||
|         int getSampleRate() const { return m_sampleRate; } | ||||
|         quint32 getRecordLength() const { return m_recordLength; } | ||||
| 
 | ||||
|         static MsgReportFileSourceStreamData* create(int sampleRate, | ||||
|                 quint32 recordLength) | ||||
|         { | ||||
|             return new MsgReportFileSourceStreamData(sampleRate, recordLength); | ||||
|         } | ||||
| 
 | ||||
|     protected: | ||||
|         int m_sampleRate; | ||||
|         quint32 m_recordLength; | ||||
| 
 | ||||
|         MsgReportFileSourceStreamData(int sampleRate, | ||||
|                 quint32 recordLength) : | ||||
|             Message(), | ||||
|             m_sampleRate(sampleRate), | ||||
|             m_recordLength(recordLength) | ||||
|         { } | ||||
|     }; | ||||
| 
 | ||||
|     //=================================================================
 | ||||
| 
 | ||||
|     AMMod(); | ||||
|     ~AMMod(); | ||||
| 
 | ||||
| @ -45,7 +184,8 @@ public: | ||||
|     Real getMagSq() const { return m_magsq; } | ||||
| 
 | ||||
| private: | ||||
|     class MsgConfigureAMMod : public Message { | ||||
|     class MsgConfigureAMMod : public Message | ||||
|     { | ||||
|         MESSAGE_CLASS_DECLARATION | ||||
| 
 | ||||
|     public: | ||||
| @ -74,6 +214,8 @@ private: | ||||
|         { } | ||||
|     }; | ||||
| 
 | ||||
|     //=================================================================
 | ||||
| 
 | ||||
|     struct AudioSample { | ||||
|         qint16 l; | ||||
|         qint16 r; | ||||
| @ -105,6 +247,8 @@ private: | ||||
|         { } | ||||
|     }; | ||||
| 
 | ||||
|     //=================================================================
 | ||||
| 
 | ||||
|     Config m_config; | ||||
|     Config m_running; | ||||
| 
 | ||||
| @ -128,7 +272,17 @@ private: | ||||
|     SampleVector m_sampleBuffer; | ||||
|     QMutex m_settingsMutex; | ||||
| 
 | ||||
|     std::ifstream m_ifstream; | ||||
|     QString m_fileName; | ||||
|     quint32 m_recordLength; //!< record length in seconds computed from file size
 | ||||
|     int m_sampleRate; | ||||
| 
 | ||||
|     AMModInputAF m_afInput; | ||||
| 
 | ||||
|     void apply(); | ||||
|     void pullAF(Real& sample); | ||||
|     void openFileStream(); | ||||
|     void seekFileStream(int seekPercentage); | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -16,6 +16,9 @@ | ||||
| 
 | ||||
| #include <QDockWidget> | ||||
| #include <QMainWindow> | ||||
| #include <QFileDialog> | ||||
| #include <QTime> | ||||
| #include <QDebug> | ||||
| 
 | ||||
| #include "ammodgui.h" | ||||
| 
 | ||||
| @ -31,8 +34,6 @@ | ||||
| #include "dsp/dspengine.h" | ||||
| #include "mainwindow.h" | ||||
| 
 | ||||
| #include "ammod.h" | ||||
| 
 | ||||
| const QString AMModGUI::m_channelID = "sdrangel.channeltx.modam"; | ||||
| 
 | ||||
| const int AMModGUI::m_rfBW[] = { | ||||
| @ -141,7 +142,24 @@ bool AMModGUI::deserialize(const QByteArray& data) | ||||
| 
 | ||||
| bool AMModGUI::handleMessage(const Message& message) | ||||
| { | ||||
|     if (AMMod::MsgReportFileSourceStreamData::match(message)) | ||||
|     { | ||||
|         m_recordSampleRate = ((AMMod::MsgReportFileSourceStreamData&)message).getSampleRate(); | ||||
|         m_recordLength = ((AMMod::MsgReportFileSourceStreamData&)message).getRecordLength(); | ||||
|         m_samplesCount = 0; | ||||
|         updateWithStreamData(); | ||||
|         return true; | ||||
|     } | ||||
|     else if (AMMod::MsgReportFileSourceStreamTiming::match(message)) | ||||
|     { | ||||
|         m_samplesCount = ((AMMod::MsgReportFileSourceStreamTiming&)message).getSamplesCount(); | ||||
|         updateWithStreamTime(); | ||||
|         return true; | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void AMModGUI::viewChanged() | ||||
| @ -149,6 +167,19 @@ void AMModGUI::viewChanged() | ||||
| 	applySettings(); | ||||
| } | ||||
| 
 | ||||
| void AMModGUI::handleSourceMessages() | ||||
| { | ||||
|     Message* message; | ||||
| 
 | ||||
|     while ((message = m_amMod->getOutputMessageQueue()->pop()) != 0) | ||||
|     { | ||||
|         if (handleMessage(*message)) | ||||
|         { | ||||
|             delete message; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void AMModGUI::on_deltaMinus_toggled(bool minus) | ||||
| { | ||||
| 	int deltaFrequency = m_channelMarker.getCenterFrequency(); | ||||
| @ -193,6 +224,74 @@ void AMModGUI::on_audioMute_toggled(bool checked) | ||||
| 	applySettings(); | ||||
| } | ||||
| 
 | ||||
| void AMModGUI::on_playLoop_toggled(bool checked) | ||||
| { | ||||
|     // TODO: do something about it!
 | ||||
| } | ||||
| 
 | ||||
| void AMModGUI::on_play_toggled(bool checked) | ||||
| { | ||||
|     ui->tone->setEnabled(!checked); // release other source inputs
 | ||||
|     ui->mic->setEnabled(!checked); | ||||
|     m_modAFInput = checked ? AMMod::AMModInputFile : AMMod::AMModInputNone; | ||||
|     AMMod::MsgConfigureAFInput* message = AMMod::MsgConfigureAFInput::create(m_modAFInput); | ||||
|     m_amMod->getInputMessageQueue()->push(message); | ||||
|     ui->navTimeSlider->setEnabled(!checked); | ||||
|     m_enableNavTime = !checked; | ||||
| } | ||||
| 
 | ||||
| void AMModGUI::on_tone_toggled(bool checked) | ||||
| { | ||||
|     ui->play->setEnabled(!checked); // release other source inputs
 | ||||
|     ui->mic->setEnabled(!checked); | ||||
|     m_modAFInput = checked ? AMMod::AMModInputTone : AMMod::AMModInputNone; | ||||
|     AMMod::MsgConfigureAFInput* message = AMMod::MsgConfigureAFInput::create(m_modAFInput); | ||||
|     m_amMod->getInputMessageQueue()->push(message); | ||||
| } | ||||
| 
 | ||||
| void AMModGUI::on_mic_toggled(bool checked) | ||||
| { | ||||
|     ui->play->setEnabled(!checked); // release other source inputs
 | ||||
|     ui->tone->setEnabled(!checked); // release other source inputs
 | ||||
|     m_modAFInput = checked ? AMMod::AMModInputAudio : AMMod::AMModInputNone; | ||||
|     AMMod::MsgConfigureAFInput* message = AMMod::MsgConfigureAFInput::create(m_modAFInput); | ||||
|     m_amMod->getInputMessageQueue()->push(message); | ||||
| } | ||||
| 
 | ||||
| void AMModGUI::on_navTimeSlider_valueChanged(int value) | ||||
| { | ||||
|     if (m_enableNavTime && ((value >= 0) && (value <= 100))) | ||||
|     { | ||||
|         int t_sec = (m_recordLength * value) / 100; | ||||
|         QTime t(0, 0, 0, 0); | ||||
|         t = t.addSecs(t_sec); | ||||
| 
 | ||||
|         AMMod::MsgConfigureFileSourceSeek* message = AMMod::MsgConfigureFileSourceSeek::create(value); | ||||
|         m_amMod->getInputMessageQueue()->push(message); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void AMModGUI::on_showFileDialog_clicked(bool checked) | ||||
| { | ||||
|     QString fileName = QFileDialog::getOpenFileName(this, | ||||
|         tr("Open raw audio file"), ".", tr("Raw audio Files (*.raw)")); | ||||
| 
 | ||||
|     if (fileName != "") | ||||
|     { | ||||
|         m_fileName = fileName; | ||||
|         ui->recordFileText->setText(m_fileName); | ||||
|         ui->play->setEnabled(true); | ||||
|         configureFileName(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void AMModGUI::configureFileName() | ||||
| { | ||||
|     qDebug() << "FileSourceGui::configureFileName: " << m_fileName.toStdString().c_str(); | ||||
|     AMMod::MsgConfigureFileSourceName* message = AMMod::MsgConfigureFileSourceName::create(m_fileName); | ||||
|     m_amMod->getInputMessageQueue()->push(message); | ||||
| } | ||||
| 
 | ||||
| void AMModGUI::onWidgetRolled(QWidget* widget, bool rollDown) | ||||
| { | ||||
| } | ||||
| @ -214,7 +313,13 @@ AMModGUI::AMModGUI(PluginAPI* pluginAPI, DeviceSinkAPI *deviceAPI, QWidget* pare | ||||
| 	m_channelMarker(this), | ||||
| 	m_basicSettingsShown(false), | ||||
| 	m_doApplySettings(true), | ||||
| 	m_channelPowerDbAvg(20,0) | ||||
| 	m_channelPowerDbAvg(20,0), | ||||
|     m_recordLength(0), | ||||
|     m_recordSampleRate(48000), | ||||
|     m_samplesCount(0), | ||||
|     m_tickCount(0), | ||||
|     m_enableNavTime(false), | ||||
|     m_modAFInput(AMMod::AMModInputNone) | ||||
| { | ||||
| 	ui->setupUi(this); | ||||
| 	setAttribute(Qt::WA_DeleteOnClose, true); | ||||
| @ -243,7 +348,14 @@ AMModGUI::AMModGUI(PluginAPI* pluginAPI, DeviceSinkAPI *deviceAPI, QWidget* pare | ||||
|     m_deviceAPI->addChannelMarker(&m_channelMarker); | ||||
|     m_deviceAPI->addRollupWidget(this); | ||||
| 
 | ||||
|     ui->play->setEnabled(false); | ||||
|     ui->play->setChecked(false); | ||||
|     ui->tone->setChecked(false); | ||||
|     ui->mic->setChecked(false); | ||||
| 
 | ||||
| 	applySettings(); | ||||
| 
 | ||||
| 	connect(m_amMod->getOutputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages())); | ||||
| } | ||||
| 
 | ||||
| AMModGUI::~AMModGUI() | ||||
| @ -302,5 +414,44 @@ void AMModGUI::tick() | ||||
| 	Real powDb = CalcDb::dbPower(m_amMod->getMagSq()); | ||||
| 	m_channelPowerDbAvg.feed(powDb); | ||||
| 	ui->channelPower->setText(QString::number(m_channelPowerDbAvg.average(), 'f', 1)); | ||||
| 
 | ||||
|     if (((++m_tickCount & 0xf) == 0) && (m_modAFInput == AMMod::AMModInputFile)) | ||||
|     { | ||||
|         AMMod::MsgConfigureFileSourceStreamTiming* message = AMMod::MsgConfigureFileSourceStreamTiming::create(); | ||||
|         m_amMod->getInputMessageQueue()->push(message); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void AMModGUI::updateWithStreamData() | ||||
| { | ||||
|     QTime recordLength(0, 0, 0, 0); | ||||
|     recordLength = recordLength.addSecs(m_recordLength); | ||||
|     QString s_time = recordLength.toString("hh:mm:ss"); | ||||
|     ui->recordLengthText->setText(s_time); | ||||
|     updateWithStreamTime(); | ||||
| } | ||||
| 
 | ||||
| void AMModGUI::updateWithStreamTime() | ||||
| { | ||||
|     int t_sec = 0; | ||||
|     int t_msec = 0; | ||||
| 
 | ||||
|     if (m_recordSampleRate > 0) | ||||
|     { | ||||
|         t_msec = ((m_samplesCount * 1000) / m_recordSampleRate) % 1000; | ||||
|         t_sec = m_samplesCount / m_recordSampleRate; | ||||
|     } | ||||
| 
 | ||||
|     QTime t(0, 0, 0, 0); | ||||
|     t = t.addSecs(t_sec); | ||||
|     t = t.addMSecs(t_msec); | ||||
|     QString s_timems = t.toString("hh:mm:ss.zzz"); | ||||
|     QString s_time = t.toString("hh:mm:ss"); | ||||
|     ui->relTimeText->setText(s_timems); | ||||
| 
 | ||||
|     if (!m_enableNavTime) | ||||
|     { | ||||
|         float posRatio = (float) t_sec / (float) m_recordLength; | ||||
|         ui->navTimeSlider->setValue((int) (posRatio * 100.0)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -21,6 +21,7 @@ | ||||
| #include "plugin/plugingui.h" | ||||
| #include "dsp/channelmarker.h" | ||||
| #include "dsp/movingaverage.h" | ||||
| #include "ammod.h" | ||||
| 
 | ||||
| class PluginAPI; | ||||
| class DeviceSinkAPI; | ||||
| @ -55,14 +56,26 @@ public: | ||||
| 
 | ||||
| private slots: | ||||
|     void viewChanged(); | ||||
|     void handleSourceMessages(); | ||||
| 
 | ||||
|     void on_deltaFrequency_changed(quint64 value); | ||||
|     void on_deltaMinus_toggled(bool minus); | ||||
|     void on_rfBW_valueChanged(int value); | ||||
|     void on_afBW_valueChanged(int value); | ||||
|     void on_modPercent_valueChanged(int value); | ||||
|     void on_audioMute_toggled(bool checked); | ||||
|     void on_tone_toggled(bool checked); | ||||
|     void on_mic_toggled(bool checked); | ||||
|     void on_play_toggled(bool checked); | ||||
| 
 | ||||
|     void on_playLoop_toggled(bool checked); | ||||
|     void on_navTimeSlider_valueChanged(int value); | ||||
|     void on_showFileDialog_clicked(bool checked); | ||||
| 
 | ||||
|     void onWidgetRolled(QWidget* widget, bool rollDown); | ||||
|     void onMenuDoubleClicked(); | ||||
| 
 | ||||
|     void configureFileName(); | ||||
|     void tick(); | ||||
| 
 | ||||
| private: | ||||
| @ -78,6 +91,14 @@ private: | ||||
|     AMMod* m_amMod; | ||||
|     MovingAverage<Real> m_channelPowerDbAvg; | ||||
| 
 | ||||
|     QString m_fileName; | ||||
|     quint32 m_recordLength; | ||||
|     int m_recordSampleRate; | ||||
|     int m_samplesCount; | ||||
|     std::size_t m_tickCount; | ||||
|     bool m_enableNavTime; | ||||
|     AMMod::AMModInputAF m_modAFInput; | ||||
| 
 | ||||
|     static const int m_rfBW[]; | ||||
| 
 | ||||
|     explicit AMModGUI(PluginAPI* pluginAPI, DeviceSinkAPI *deviceAPI, QWidget* parent = NULL); | ||||
| @ -85,6 +106,8 @@ private: | ||||
| 
 | ||||
|     void blockApplySettings(bool block); | ||||
|     void applySettings(); | ||||
|     void updateWithStreamData(); | ||||
|     void updateWithStreamTime(); | ||||
| 
 | ||||
|     void leaveEvent(QEvent*); | ||||
|     void enterEvent(QEvent*); | ||||
|  | ||||
| @ -6,8 +6,8 @@ | ||||
|    <rect> | ||||
|     <x>0</x> | ||||
|     <y>0</y> | ||||
|     <width>257</width> | ||||
|     <height>143</height> | ||||
|     <width>261</width> | ||||
|     <height>180</height> | ||||
|    </rect> | ||||
|   </property> | ||||
|   <property name="font"> | ||||
| @ -27,8 +27,8 @@ | ||||
|     <rect> | ||||
|      <x>10</x> | ||||
|      <y>10</y> | ||||
|      <width>235</width> | ||||
|      <height>121</height> | ||||
|      <width>241</width> | ||||
|      <height>161</height> | ||||
|     </rect> | ||||
|    </property> | ||||
|    <property name="windowTitle"> | ||||
| @ -302,22 +302,221 @@ | ||||
|       </item> | ||||
|      </layout> | ||||
|     </item> | ||||
|     <item> | ||||
|      <layout class="QHBoxLayout" name="recordFileSelectLayout"> | ||||
|       <item> | ||||
|        <widget class="ButtonSwitch" name="tone"> | ||||
|         <property name="toolTip"> | ||||
|          <string>Tone modulation (1 kHz)</string> | ||||
|         </property> | ||||
|         <property name="text"> | ||||
|          <string>...</string> | ||||
|         </property> | ||||
|         <property name="icon"> | ||||
|          <iconset resource="../../../sdrbase/resources/res.qrc"> | ||||
|           <normaloff>:/carrier.png</normaloff>:/carrier.png</iconset> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item> | ||||
|        <widget class="ButtonSwitch" name="mic"> | ||||
|         <property name="toolTip"> | ||||
|          <string>Audio input</string> | ||||
|         </property> | ||||
|         <property name="text"> | ||||
|          <string>...</string> | ||||
|         </property> | ||||
|         <property name="icon"> | ||||
|          <iconset resource="../../../sdrbase/resources/res.qrc"> | ||||
|           <normaloff>:/microphone.png</normaloff>:/microphone.png</iconset> | ||||
|         </property> | ||||
|         <property name="checkable"> | ||||
|          <bool>true</bool> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item> | ||||
|        <widget class="QPushButton" name="showFileDialog"> | ||||
|         <property name="minimumSize"> | ||||
|          <size> | ||||
|           <width>24</width> | ||||
|           <height>24</height> | ||||
|          </size> | ||||
|         </property> | ||||
|         <property name="maximumSize"> | ||||
|          <size> | ||||
|           <width>24</width> | ||||
|           <height>24</height> | ||||
|          </size> | ||||
|         </property> | ||||
|         <property name="toolTip"> | ||||
|          <string>Open record file (48 kHz 32 bit float LE mono)</string> | ||||
|         </property> | ||||
|         <property name="text"> | ||||
|          <string/> | ||||
|         </property> | ||||
|         <property name="icon"> | ||||
|          <iconset resource="../../../sdrbase/resources/res.qrc"> | ||||
|           <normaloff>:/preset-load.png</normaloff>:/preset-load.png</iconset> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item> | ||||
|        <widget class="QLabel" name="recordFileText"> | ||||
|         <property name="text"> | ||||
|          <string>...</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|      </layout> | ||||
|     </item> | ||||
|     <item> | ||||
|      <layout class="QHBoxLayout" name="playControllLayout"> | ||||
|       <item> | ||||
|        <widget class="ButtonSwitch" name="playLoop"> | ||||
|         <property name="toolTip"> | ||||
|          <string>Play record file in a loop</string> | ||||
|         </property> | ||||
|         <property name="text"> | ||||
|          <string>...</string> | ||||
|         </property> | ||||
|         <property name="icon"> | ||||
|          <iconset resource="../../../sdrbase/resources/res.qrc"> | ||||
|           <normaloff>:/playloop.png</normaloff>:/playloop.png</iconset> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item> | ||||
|        <widget class="ButtonSwitch" name="play"> | ||||
|         <property name="toolTip"> | ||||
|          <string>Record file play/pause</string> | ||||
|         </property> | ||||
|         <property name="text"> | ||||
|          <string>...</string> | ||||
|         </property> | ||||
|         <property name="icon"> | ||||
|          <iconset resource="../../../sdrbase/resources/res.qrc"> | ||||
|           <normaloff>:/play.png</normaloff> | ||||
|           <normalon>:/pause.png</normalon> | ||||
|           <disabledoff>:/play.png</disabledoff> | ||||
|           <disabledon>:/play.png</disabledon>:/play.png</iconset> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item> | ||||
|        <spacer name="horizontalSpacer_2"> | ||||
|         <property name="orientation"> | ||||
|          <enum>Qt::Horizontal</enum> | ||||
|         </property> | ||||
|         <property name="sizeHint" stdset="0"> | ||||
|          <size> | ||||
|           <width>40</width> | ||||
|           <height>20</height> | ||||
|          </size> | ||||
|         </property> | ||||
|        </spacer> | ||||
|       </item> | ||||
|       <item> | ||||
|        <widget class="Line" name="linePlay1"> | ||||
|         <property name="orientation"> | ||||
|          <enum>Qt::Vertical</enum> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item> | ||||
|        <widget class="QLabel" name="relTimeText"> | ||||
|         <property name="enabled"> | ||||
|          <bool>false</bool> | ||||
|         </property> | ||||
|         <property name="minimumSize"> | ||||
|          <size> | ||||
|           <width>90</width> | ||||
|           <height>0</height> | ||||
|          </size> | ||||
|         </property> | ||||
|         <property name="toolTip"> | ||||
|          <string>Record time from start</string> | ||||
|         </property> | ||||
|         <property name="text"> | ||||
|          <string>00:00:00.000</string> | ||||
|         </property> | ||||
|         <property name="alignment"> | ||||
|          <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item> | ||||
|        <widget class="Line" name="linePlay2"> | ||||
|         <property name="orientation"> | ||||
|          <enum>Qt::Vertical</enum> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item> | ||||
|        <widget class="QLabel" name="recordLengthText"> | ||||
|         <property name="enabled"> | ||||
|          <bool>false</bool> | ||||
|         </property> | ||||
|         <property name="minimumSize"> | ||||
|          <size> | ||||
|           <width>60</width> | ||||
|           <height>0</height> | ||||
|          </size> | ||||
|         </property> | ||||
|         <property name="toolTip"> | ||||
|          <string>Total record time</string> | ||||
|         </property> | ||||
|         <property name="text"> | ||||
|          <string>00:00:00</string> | ||||
|         </property> | ||||
|         <property name="alignment"> | ||||
|          <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|      </layout> | ||||
|     </item> | ||||
|     <item> | ||||
|      <layout class="QHBoxLayout" name="horizontalLayout_nav"> | ||||
|       <item> | ||||
|        <widget class="QSlider" name="navTimeSlider"> | ||||
|         <property name="toolTip"> | ||||
|          <string>Record file time navigator</string> | ||||
|         </property> | ||||
|         <property name="maximum"> | ||||
|          <number>100</number> | ||||
|         </property> | ||||
|         <property name="pageStep"> | ||||
|          <number>1</number> | ||||
|         </property> | ||||
|         <property name="orientation"> | ||||
|          <enum>Qt::Horizontal</enum> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|      </layout> | ||||
|     </item> | ||||
|    </layout> | ||||
|   </widget> | ||||
|  </widget> | ||||
|  <customwidgets> | ||||
|   <customwidget> | ||||
|    <class>RollupWidget</class> | ||||
|    <extends>QWidget</extends> | ||||
|    <header>gui/rollupwidget.h</header> | ||||
|    <container>1</container> | ||||
|   </customwidget> | ||||
|   <customwidget> | ||||
|    <class>ValueDial</class> | ||||
|    <extends>QWidget</extends> | ||||
|    <header>gui/valuedial.h</header> | ||||
|    <container>1</container> | ||||
|   </customwidget> | ||||
|   <customwidget> | ||||
|    <class>ButtonSwitch</class> | ||||
|    <extends>QToolButton</extends> | ||||
|    <header>gui/buttonswitch.h</header> | ||||
|   </customwidget> | ||||
|   <customwidget> | ||||
|    <class>RollupWidget</class> | ||||
|    <extends>QWidget</extends> | ||||
|    <header>gui/rollupwidget.h</header> | ||||
|    <container>1</container> | ||||
|   </customwidget> | ||||
|  </customwidgets> | ||||
|  <resources> | ||||
|   <include location="../../../sdrbase/resources/res.qrc"/> | ||||
|  | ||||
| @ -23,7 +23,7 @@ | ||||
| 
 | ||||
| const PluginDescriptor AMModPlugin::m_pluginDescriptor = { | ||||
|     QString("AM Modulator"), | ||||
|     QString("2.2.1"), | ||||
|     QString("2.3.1"), | ||||
|     QString("(c) Edouard Griffiths, F4EXB"), | ||||
|     QString("https://github.com/f4exb/sdrangel"), | ||||
|     true, | ||||
|  | ||||
| @ -31,7 +31,7 @@ else (BUILD_DEBIAN) | ||||
| include_directories( | ||||
|     . | ||||
|     ${CMAKE_CURRENT_BINARY_DIR} | ||||
|     ${LIBRTLSDRSRC}/include | ||||
|     ${LIBRTLSDR_INCLUDE_DIR} | ||||
| ) | ||||
| endif (BUILD_DEBIAN) | ||||
| 
 | ||||
|  | ||||
| @ -84,7 +84,7 @@ | ||||
|    <item> | ||||
|     <widget class="QLabel" name="label_2"> | ||||
|      <property name="text"> | ||||
|       <string><html><head/><body><p>Version 2.3.0 - Copyright (C) 2015-2016 Edouard Griffiths, F4EXB. </p><p>Code at <a href="https://github.com/f4exb/sdrangel"><span style=" text-decoration: underline; color:#0000ff;">https://github.com/f4exb/sdrangel</span></a> This is a complete redesign from RTL-SDRangelove at <a href="https://github.com/hexameron/rtl-sdrangelove"><span style=" text-decoration: underline; color:#0000ff;">https://github.com/hexameron/rtl-sdrangelove</span></a></p><p>Many thanks to the original developers:</p><p>The osmocom developer team - especially horizon, Hoernchen &amp; tnt.</p><p>Christian Daniel from maintech GmbH.</p><p>John Greb (hexameron) for the contributions in RTL-SDRangelove</p><p>The following rules apply to the SDRangel main application and libsdrbase:<br/>This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. You should have received a copy of the GNU General Public License along with this program. If not, see <a href="http://www.gnu.org/licenses/"><span style=" text-decoration: underline; color:#0000ff;">http://www.gnu.org/licenses/</span></a>.</p><p>For the license of installed plugins, look into the plugin list.</p></body></html></string> | ||||
|       <string><html><head/><body><p>Version 2.3.1 - Copyright (C) 2015-2016 Edouard Griffiths, F4EXB. </p><p>Code at <a href="https://github.com/f4exb/sdrangel"><span style=" text-decoration: underline; color:#0000ff;">https://github.com/f4exb/sdrangel</span></a> This is a complete redesign from RTL-SDRangelove at <a href="https://github.com/hexameron/rtl-sdrangelove"><span style=" text-decoration: underline; color:#0000ff;">https://github.com/hexameron/rtl-sdrangelove</span></a></p><p>Many thanks to the original developers:</p><p>The osmocom developer team - especially horizon, Hoernchen &amp; tnt.</p><p>Christian Daniel from maintech GmbH.</p><p>John Greb (hexameron) for the contributions in RTL-SDRangelove</p><p>The following rules apply to the SDRangel main application and libsdrbase:<br/>This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. You should have received a copy of the GNU General Public License along with this program. If not, see <a href="http://www.gnu.org/licenses/"><span style=" text-decoration: underline; color:#0000ff;">http://www.gnu.org/licenses/</span></a>.</p><p>For the license of installed plugins, look into the plugin list.</p></body></html></string> | ||||
|      </property> | ||||
|      <property name="wordWrap"> | ||||
|       <bool>true</bool> | ||||
|  | ||||
| @ -450,7 +450,7 @@ void MainWindow::savePresetSettings(Preset* preset, int tabIndex) | ||||
| void MainWindow::createStatusBar() | ||||
| { | ||||
|     QString qtVersionStr = QString("Qt %1 ").arg(QT_VERSION_STR); | ||||
|     m_showSystemWidget = new QLabel("SDRangel v2.3.0 " + qtVersionStr + QSysInfo::prettyProductName(), this); | ||||
|     m_showSystemWidget = new QLabel("SDRangel v2.3.1 " + qtVersionStr + QSysInfo::prettyProductName(), this); | ||||
|     statusBar()->addPermanentWidget(m_showSystemWidget); | ||||
| 
 | ||||
| 	m_dateTimeWidget = new QLabel(tr("Date"), this); | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								sdrbase/resources/microphone.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								sdrbase/resources/microphone.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 1.1 KiB | 
| @ -1,5 +1,6 @@ | ||||
| <RCC> | ||||
|   <qresource prefix="/"> | ||||
|     <file>microphone.png</file> | ||||
|     <file>checkmark.png</file> | ||||
|     <file>questionmark.png</file> | ||||
|     <file>res.qrc</file> | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user