| 
									
										
										
										
											2020-10-08 14:13:06 +00:00
										 |  |  | ///////////////////////////////////////////////////////////////////////////////////
 | 
					
						
							|  |  |  | // Copyright (C) 2019 Edouard Griffiths, F4EXB                                   //
 | 
					
						
							|  |  |  | // Copyright (C) 2020 Jon Beniston, M7RCE                                        //
 | 
					
						
							|  |  |  | //                                                                               //
 | 
					
						
							|  |  |  | // 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 as version 3 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. See the                  //
 | 
					
						
							|  |  |  | // GNU General Public License V3 for more details.                               //
 | 
					
						
							|  |  |  | //                                                                               //
 | 
					
						
							|  |  |  | // You should have received a copy of the GNU General Public License             //
 | 
					
						
							|  |  |  | // along with this program. If not, see <http://www.gnu.org/licenses/>.          //
 | 
					
						
							|  |  |  | ///////////////////////////////////////////////////////////////////////////////////
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <cctype>
 | 
					
						
							|  |  |  | #include <QDebug>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "dsp/basebandsamplesink.h"
 | 
					
						
							|  |  |  | #include "ieee_802_15_4_modsource.h"
 | 
					
						
							|  |  |  | #include "util/crc.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | IEEE_802_15_4_ModSource::IEEE_802_15_4_ModSource() : | 
					
						
							|  |  |  |     m_channelSampleRate(3000000), | 
					
						
							|  |  |  |     m_channelFrequencyOffset(0), | 
					
						
							| 
									
										
										
										
											2020-11-14 04:51:19 +00:00
										 |  |  |     m_spectrumRate(0), | 
					
						
							|  |  |  |     m_sinLUT(nullptr), | 
					
						
							| 
									
										
										
										
											2021-03-18 17:17:58 +00:00
										 |  |  |     m_scrambler(0x108, 0x1fe, 1), | 
					
						
							| 
									
										
										
										
											2020-11-14 04:51:19 +00:00
										 |  |  |     m_spectrumSink(nullptr), | 
					
						
							|  |  |  |     m_scopeSink(nullptr), | 
					
						
							| 
									
										
										
										
											2020-10-08 14:13:06 +00:00
										 |  |  |     m_magsq(0.0), | 
					
						
							|  |  |  |     m_levelCalcCount(0), | 
					
						
							|  |  |  |     m_peakLevel(0.0f), | 
					
						
							|  |  |  |     m_levelSum(0.0f), | 
					
						
							|  |  |  |     m_sampleIdx(0), | 
					
						
							|  |  |  |     m_chipsPerSymbol(15), | 
					
						
							|  |  |  |     m_bitsPerSymbol(1), | 
					
						
							|  |  |  |     m_chipRate(300000), | 
					
						
							| 
									
										
										
										
											2020-11-14 04:51:19 +00:00
										 |  |  |     m_state(idle), | 
					
						
							|  |  |  |     m_byteIdx(0), | 
					
						
							|  |  |  |     m_bitIdx(0), | 
					
						
							|  |  |  |     m_bitCount(0) | 
					
						
							| 
									
										
										
										
											2020-10-08 14:13:06 +00:00
										 |  |  | { | 
					
						
							|  |  |  |     m_lowpass.create(301, m_channelSampleRate, 22000.0 / 2.0); | 
					
						
							|  |  |  |     m_pulseShapeI.create(1, 6, m_channelSampleRate/300000, true); | 
					
						
							|  |  |  |     m_pulseShapeQ.create(1, 6, m_channelSampleRate/300000, true); | 
					
						
							|  |  |  |     applySettings(m_settings, true); | 
					
						
							|  |  |  |     applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | IEEE_802_15_4_ModSource::~IEEE_802_15_4_ModSource() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     delete m_sinLUT; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void IEEE_802_15_4_ModSource::pull(SampleVector::iterator begin, unsigned int nbSamples) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     std::for_each( | 
					
						
							|  |  |  |         begin, | 
					
						
							|  |  |  |         begin + nbSamples, | 
					
						
							|  |  |  |         [this](Sample& s) { | 
					
						
							|  |  |  |             pullOne(s); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void IEEE_802_15_4_ModSource::pullOne(Sample& sample) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (m_settings.m_channelMute) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         sample.m_real = 0.0f; | 
					
						
							|  |  |  |         sample.m_imag = 0.0f; | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Calculate next sample
 | 
					
						
							|  |  |  |     modulateSample(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Shift to carrier frequency
 | 
					
						
							|  |  |  |     Complex ci = m_modSample; | 
					
						
							|  |  |  |     ci *= m_carrierNco.nextIQ(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Calculate power
 | 
					
						
							|  |  |  |     double magsq = ci.real() * ci.real() + ci.imag() * ci.imag(); | 
					
						
							|  |  |  |     m_movingAverage(magsq); | 
					
						
							|  |  |  |     m_magsq = m_movingAverage.asDouble(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Convert from float to fixed point
 | 
					
						
							|  |  |  |     sample.m_real = (FixReal) (ci.real() * SDR_TX_SCALEF); | 
					
						
							|  |  |  |     sample.m_imag = (FixReal) (ci.imag() * SDR_TX_SCALEF); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void IEEE_802_15_4_ModSource::sampleToSpectrum(Complex sample) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (m_spectrumSink && (m_settings.m_spectrumRate > 0)) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         Complex out; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Could use a simpler filter here, as currently m_spectrumRate is
 | 
					
						
							|  |  |  |         // always an integer multiple of m_channelSampleRate
 | 
					
						
							|  |  |  |         if (m_interpolator.decimate(&m_interpolatorDistanceRemain, sample, &out)) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             Real r = std::real(out) * SDR_TX_SCALEF; | 
					
						
							|  |  |  |             Real i = std::imag(out) * SDR_TX_SCALEF; | 
					
						
							|  |  |  |             m_sampleBuffer.push_back(Sample(r, i)); | 
					
						
							|  |  |  |             m_spectrumSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), false); | 
					
						
							|  |  |  |             m_sampleBuffer.clear(); | 
					
						
							|  |  |  |             m_interpolatorDistanceRemain += m_interpolatorDistance; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void IEEE_802_15_4_ModSource::sampleToScope(Complex sample) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (m_scopeSink) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         Real r = std::real(sample) * SDR_RX_SCALEF; | 
					
						
							|  |  |  |         Real i = std::imag(sample) * SDR_RX_SCALEF; | 
					
						
							|  |  |  |         m_sampleBuffer.push_back(Sample(r, i)); | 
					
						
							|  |  |  |         m_scopeSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), true); | 
					
						
							|  |  |  |         m_sampleBuffer.clear(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void IEEE_802_15_4_ModSource::modulateSample() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     Real linearRampGain; | 
					
						
							|  |  |  |     Real i, q; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if ((m_state == idle) || (m_state == wait)) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         Real audioMod = 0.0f; | 
					
						
							|  |  |  |         m_modSample.real(audioMod); | 
					
						
							|  |  |  |         m_modSample.imag(0); | 
					
						
							|  |  |  |         calculateLevel(audioMod); | 
					
						
							|  |  |  |         sampleToSpectrum(m_modSample); | 
					
						
							|  |  |  |         sampleToScope(m_modSample); | 
					
						
							|  |  |  |         if (m_state == wait) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             m_waitCounter--; | 
					
						
							|  |  |  |             if (m_waitCounter == 0) | 
					
						
							|  |  |  |                 initTX(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (m_sampleIdx == 0) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             if (chipsValid()) | 
					
						
							|  |  |  |                 m_chips[m_chipOdd] = getChip(); | 
					
						
							|  |  |  |             // Should we start ramping down power?
 | 
					
						
							|  |  |  |             if ((m_bitCount < m_settings.m_rampDownBits) || ((m_bitCount == 0) && !m_settings.m_rampDownBits)) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 m_state = ramp_down; | 
					
						
							|  |  |  |                 if (m_settings.m_rampDownBits > 0) | 
					
						
							|  |  |  |                     m_powRamp = -m_settings.m_rampRange/(m_settings.m_rampDownBits * (Real)m_samplesPerChip); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!m_settings.m_bbNoise) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             if (m_settings.m_modulation == IEEE_802_15_4_ModSettings::BPSK) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 // BPSK - Raised cosine pulse shaping
 | 
					
						
							|  |  |  |                 if ((m_sampleIdx == 1) && (m_state != ramp_down)) | 
					
						
							|  |  |  |                     i = m_pulseShapeI.filter(m_chips[0] ? 1.0f : -1.0f); | 
					
						
							|  |  |  |                 else | 
					
						
							|  |  |  |                     i = m_pulseShapeI.filter(0.0f); | 
					
						
							|  |  |  |                 q = 0.0f; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             else | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 if (m_settings.m_pulseShaping == IEEE_802_15_4_ModSettings::SINE) | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     // O-QPSK - Half-sine pulse shaping over 2 chips. Even chips on I, odd on Q. 1-chip out of phase.
 | 
					
						
							|  |  |  |                     i = (m_chips[0] ? 1.0f : -1.0f) * m_sinLUT[m_sampleIdx+(m_chipOdd ? m_samplesPerChip : 0)]; | 
					
						
							|  |  |  |                     q = (m_chips[1] ? 1.0f : -1.0f) * m_sinLUT[m_sampleIdx+(m_chipOdd ? 0 : m_samplesPerChip)]; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 else | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     // O-QPSK - Raised cosine pulse shaping. Even chips on I, odd on Q. 1-chip out of phase.
 | 
					
						
							|  |  |  |                     if ((m_sampleIdx == 1) && (m_state != ramp_down) && !m_chipOdd) | 
					
						
							|  |  |  |                         i = m_pulseShapeI.filter(m_chips[0] ? 1.0f : -1.0f); | 
					
						
							|  |  |  |                     else | 
					
						
							|  |  |  |                         i = m_pulseShapeI.filter(0.0f); | 
					
						
							|  |  |  |                     if ((m_sampleIdx == 1) && (m_state != ramp_down) && m_chipOdd) | 
					
						
							|  |  |  |                         q = m_pulseShapeQ.filter(m_chips[1] ? 1.0f : -1.0f); | 
					
						
							|  |  |  |                     else | 
					
						
							|  |  |  |                         q = m_pulseShapeQ.filter(0.0f); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         else | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             i = (Real)rand()/((Real)RAND_MAX)-0.5; // Noise to test filter frequency response
 | 
					
						
							|  |  |  |             q = (Real)rand()/((Real)RAND_MAX)-0.5; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (m_basebandFile.is_open()) | 
					
						
							|  |  |  |             m_basebandFile << m_chips[0] << "," << m_chips[1] << "," << m_chipOdd << "," << i << "," << q << "," << (m_sampleIdx+(m_chipOdd ? m_samplesPerChip : 0)) << "," << (m_sampleIdx+(m_chipOdd ? 0 : m_samplesPerChip)) << "\n"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         m_sampleIdx++; | 
					
						
							|  |  |  |         if (m_sampleIdx >= m_samplesPerChip) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             m_sampleIdx = 0; | 
					
						
							|  |  |  |             if (m_settings.m_modulation == IEEE_802_15_4_ModSettings::OQPSK) | 
					
						
							|  |  |  |                 m_chipOdd = !m_chipOdd; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         linearRampGain = powf(10.0f, m_pow/20.0f); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         m_modSample.real(m_linearGain * linearRampGain * i); | 
					
						
							|  |  |  |         m_modSample.imag(m_linearGain * linearRampGain * q); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Display baseband audio in spectrum analyser
 | 
					
						
							|  |  |  |         sampleToSpectrum(m_modSample); | 
					
						
							|  |  |  |         sampleToScope(m_modSample); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Apply low pass filter to limit RF BW
 | 
					
						
							|  |  |  |         m_modSample = m_lowpass.filter(m_modSample); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Ramp up/down power at start/end of frame
 | 
					
						
							|  |  |  |         if ((m_state == ramp_up) || (m_state == ramp_down)) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             m_pow += m_powRamp; | 
					
						
							|  |  |  |             if ((m_state == ramp_up) && (m_pow >= 0.0f)) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 // Finished ramp up, transmit at full gain
 | 
					
						
							|  |  |  |                 m_state = tx; | 
					
						
							|  |  |  |                 m_pow = 0.0f; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             else if ((m_state == ramp_down) && (   (m_settings.m_rampRange == 0) | 
					
						
							|  |  |  |                                                 || (m_settings.m_rampDownBits == 0) | 
					
						
							|  |  |  |                                                 || (m_pow <= -(Real)m_settings.m_rampRange) | 
					
						
							|  |  |  |                                                )) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 m_state = idle; | 
					
						
							|  |  |  |                 // Do we need to retransmit the frame?
 | 
					
						
							|  |  |  |                 if (m_settings.m_repeat) | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     if (m_frameRepeatCount > 0) | 
					
						
							|  |  |  |                         m_frameRepeatCount--; | 
					
						
							|  |  |  |                     if ((m_frameRepeatCount == IEEE_802_15_4_ModSettings::infinitePackets) || (m_frameRepeatCount > 0)) | 
					
						
							|  |  |  |                     { | 
					
						
							|  |  |  |                         if (m_settings.m_repeatDelay > 0.0f) | 
					
						
							|  |  |  |                         { | 
					
						
							|  |  |  |                             // Wait before retransmitting
 | 
					
						
							|  |  |  |                             m_state = wait; | 
					
						
							|  |  |  |                             m_waitCounter = m_settings.m_repeatDelay * m_channelSampleRate; | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                         else | 
					
						
							|  |  |  |                         { | 
					
						
							|  |  |  |                             // Retransmit immediately
 | 
					
						
							|  |  |  |                             initTX(); | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         Real s = std::real(m_modSample); | 
					
						
							|  |  |  |         calculateLevel(s); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void IEEE_802_15_4_ModSource::calculateLevel(Real& sample) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (m_levelCalcCount < m_levelNbSamples) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         m_peakLevel = std::max(std::fabs(m_peakLevel), sample); | 
					
						
							|  |  |  |         m_levelSum += sample * sample; | 
					
						
							|  |  |  |         m_levelCalcCount++; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         m_rmsLevel = sqrt(m_levelSum / m_levelNbSamples); | 
					
						
							|  |  |  |         m_peakLevelOut = m_peakLevel; | 
					
						
							|  |  |  |         m_peakLevel = 0.0f; | 
					
						
							|  |  |  |         m_levelSum = 0.0f; | 
					
						
							|  |  |  |         m_levelCalcCount = 0; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void IEEE_802_15_4_ModSource::applySettings(const IEEE_802_15_4_ModSettings& settings, bool force) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     // Only recreate filters if settings have changed
 | 
					
						
							|  |  |  |     if ((settings.m_lpfTaps != m_settings.m_lpfTaps) || (settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         qDebug() << "IEEE_802_15_4_ModSource::applySettings: Creating new lpf with taps " << settings.m_lpfTaps << " rfBW " << settings.m_rfBandwidth; | 
					
						
							|  |  |  |         m_lowpass.create(settings.m_lpfTaps, m_channelSampleRate, settings.m_rfBandwidth / 2.0); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if ((settings.m_spectrumRate != m_settings.m_spectrumRate) || force) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         m_interpolatorDistanceRemain = 0; | 
					
						
							|  |  |  |         m_interpolatorConsumed = false; | 
					
						
							|  |  |  |         m_interpolatorDistance = (Real) m_channelSampleRate / (Real) settings.m_spectrumRate; | 
					
						
							|  |  |  |         m_interpolator.create(48, settings.m_spectrumRate, settings.m_spectrumRate / 2.2, 3.0); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (settings.m_modulation == IEEE_802_15_4_ModSettings::BPSK) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         m_chipsPerSymbol = 15; | 
					
						
							|  |  |  |         m_bitsPerSymbol = 1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         m_bitsPerSymbol = 4; | 
					
						
							|  |  |  |         m_chipsPerSymbol = settings.m_subGHzBand ? 16 : 32; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     m_chipRate = settings.m_bitRate * m_chipsPerSymbol / m_bitsPerSymbol; | 
					
						
							|  |  |  |     m_samplesPerChip = m_channelSampleRate / m_chipRate; | 
					
						
							|  |  |  |     qDebug() << "m_samplesPerChip: " << m_samplesPerChip; | 
					
						
							|  |  |  |     if (m_channelSampleRate % m_chipRate != 0) | 
					
						
							|  |  |  |         qCritical("Sample rate is not an integer multiple of the chip rate"); | 
					
						
							|  |  |  |     if (m_samplesPerChip <= 2) | 
					
						
							|  |  |  |         qCritical("Sample rate is not a high enough multiple of the chip rate"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if ((settings.m_pulseShaping != m_settings.m_pulseShaping) || (settings.m_beta != m_settings.m_beta) || (settings.m_symbolSpan != m_settings.m_symbolSpan) | 
					
						
							|  |  |  |         || (settings.m_bitRate != m_settings.m_bitRate) || (settings.m_modulation != m_settings.m_modulation) | 
					
						
							|  |  |  |         || (settings.m_subGHzBand != m_settings.m_subGHzBand) | 
					
						
							|  |  |  |         || force) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         qDebug() << "IEEE_802_15_4_ModSource::applySettings: Recreating pulse shaping filter: " | 
					
						
							|  |  |  |                 << " pulseShaping: " << m_settings.m_pulseShaping | 
					
						
							|  |  |  |                 << " beta: " << settings.m_beta | 
					
						
							|  |  |  |                 << " symbolSpan: " << settings.m_symbolSpan | 
					
						
							|  |  |  |                 << " channelSampleRate:" << m_channelSampleRate | 
					
						
							|  |  |  |                 << " subGHzBand: " << settings.m_subGHzBand | 
					
						
							|  |  |  |                 << " bitRate:" << settings.m_bitRate | 
					
						
							|  |  |  |                 << " chipRate:" << m_chipRate; | 
					
						
							|  |  |  |         if (settings.m_pulseShaping == IEEE_802_15_4_ModSettings::RC) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             m_pulseShapeI.create(settings.m_beta, m_settings.m_symbolSpan, m_channelSampleRate/m_chipRate, true); | 
					
						
							|  |  |  |             m_pulseShapeQ.create(settings.m_beta, m_settings.m_symbolSpan, m_channelSampleRate/m_chipRate, true); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         else | 
					
						
							|  |  |  |             createHalfSine(m_channelSampleRate, m_chipRate); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if ((settings.m_polynomial != m_settings.m_polynomial) || force) | 
					
						
							|  |  |  |         m_scrambler.setPolynomial(settings.m_polynomial); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_settings = settings; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Precalculate linear gain to save doing it in the loop
 | 
					
						
							|  |  |  |     m_linearGain = powf(10.0f,  m_settings.m_gain/20.0f); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void IEEE_802_15_4_ModSource::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     qDebug() << "IEEE_802_15_4_ModSource::applyChannelSettings:" | 
					
						
							|  |  |  |             << " channelSampleRate: " << channelSampleRate | 
					
						
							|  |  |  |             << " channelFrequencyOffset: " << channelFrequencyOffset | 
					
						
							|  |  |  |             << " rfBandwidth: " << m_settings.m_rfBandwidth | 
					
						
							|  |  |  |             << " spectrumRate: " << m_settings.m_spectrumRate; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if ((channelFrequencyOffset != m_channelFrequencyOffset) | 
					
						
							|  |  |  |      || (channelSampleRate != m_channelSampleRate) || force) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         m_carrierNco.setFreq(channelFrequencyOffset, channelSampleRate); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if ((m_channelSampleRate != channelSampleRate) || force) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         qDebug() << "IEEE_802_15_4_ModSource::applyChannelSettings: Recreating filters"; | 
					
						
							|  |  |  |         m_lowpass.create(m_settings.m_lpfTaps, channelSampleRate, m_settings.m_rfBandwidth / 2.0); | 
					
						
							|  |  |  |         qDebug() << "IEEE_802_15_4_ModSource::applyChannelSettings: Recreating pulse shaping filter: " | 
					
						
							|  |  |  |                 << " pulseShaping: " << m_settings.m_pulseShaping | 
					
						
							|  |  |  |                 << " beta: " << m_settings.m_beta | 
					
						
							|  |  |  |                 << " symbolSpan: " << m_settings.m_symbolSpan | 
					
						
							|  |  |  |                 << " channelSampleRate:" << channelSampleRate | 
					
						
							|  |  |  |                 << " subGHzBand: " << m_settings.m_subGHzBand | 
					
						
							|  |  |  |                 << " bitRate:" << m_settings.m_bitRate | 
					
						
							|  |  |  |                 << " chipRate:" << m_chipRate; | 
					
						
							|  |  |  |         if (m_settings.m_pulseShaping == IEEE_802_15_4_ModSettings::RC) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             m_pulseShapeI.create(m_settings.m_beta, m_settings.m_symbolSpan, channelSampleRate/m_chipRate, true); | 
					
						
							|  |  |  |             m_pulseShapeQ.create(m_settings.m_beta, m_settings.m_symbolSpan, channelSampleRate/m_chipRate, true); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         else | 
					
						
							|  |  |  |             createHalfSine(channelSampleRate, m_chipRate); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if ((m_channelSampleRate != channelSampleRate) || (m_spectrumRate != m_settings.m_spectrumRate) || force) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         m_interpolatorDistanceRemain = 0; | 
					
						
							|  |  |  |         m_interpolatorConsumed = false; | 
					
						
							|  |  |  |         m_interpolatorDistance = (Real) channelSampleRate / (Real) m_settings.m_spectrumRate; | 
					
						
							|  |  |  |         m_interpolator.create(48, m_settings.m_spectrumRate, m_settings.m_spectrumRate / 2.2, 3.0); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_channelSampleRate = channelSampleRate; | 
					
						
							|  |  |  |     m_channelFrequencyOffset = channelFrequencyOffset; | 
					
						
							|  |  |  |     m_spectrumRate = m_settings.m_spectrumRate; | 
					
						
							|  |  |  |     m_samplesPerChip = m_channelSampleRate / m_chipRate; | 
					
						
							|  |  |  |     qDebug() << "m_samplesPerChip: " << m_samplesPerChip; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Half-sine pulse shaping for O-QPSK
 | 
					
						
							|  |  |  | void IEEE_802_15_4_ModSource::createHalfSine(int sampleRate, int chipRate) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     int samplesPerChip = sampleRate / chipRate; | 
					
						
							|  |  |  |     double tc = 1.0 / chipRate; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (m_sinLUT) | 
					
						
							|  |  |  |         delete m_sinLUT; | 
					
						
							|  |  |  |     m_sinLUT = new double[2*samplesPerChip]; | 
					
						
							|  |  |  |     for (int i = 0; i < 2*samplesPerChip; i++) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         double t=i/(double)sampleRate; | 
					
						
							|  |  |  |         m_sinLUT[i] = sin(M_PI*t/(2.0*tc)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool IEEE_802_15_4_ModSource::chipsValid() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return (m_bitCount > 0) || (m_chipIdx < m_chipsPerSymbol); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Symbol-to-chip mapping
 | 
					
						
							|  |  |  | int IEEE_802_15_4_ModSource::getChip() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     int chip = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (m_chipIdx == 0) | 
					
						
							|  |  |  |         m_symbol = getSymbol(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (m_settings.m_bitRate <= 40000) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         static const int chipsBpsk[2][15] = { | 
					
						
							|  |  |  |             {1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0}, | 
					
						
							|  |  |  |             {0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1} | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |         chip = chipsBpsk[m_symbol][m_chipIdx]; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else if (m_settings.m_subGHzBand) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         static const int chipsSubGHzOqpsk[16][16] = { | 
					
						
							|  |  |  |             {0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1}, | 
					
						
							|  |  |  |             {0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1}, | 
					
						
							|  |  |  |             {0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0}, | 
					
						
							|  |  |  |             {1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0}, | 
					
						
							|  |  |  |             {0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0}, | 
					
						
							|  |  |  |             {1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1}, | 
					
						
							|  |  |  |             {1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1}, | 
					
						
							|  |  |  |             {1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0}, | 
					
						
							|  |  |  |             {0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0}, | 
					
						
							|  |  |  |             {0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0}, | 
					
						
							|  |  |  |             {0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1}, | 
					
						
							|  |  |  |             {1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1}, | 
					
						
							|  |  |  |             {0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1}, | 
					
						
							|  |  |  |             {1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0}, | 
					
						
							|  |  |  |             {1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0}, | 
					
						
							|  |  |  |             {1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1}, | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |         chip = chipsSubGHzOqpsk[m_symbol][m_chipIdx]; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         static const int chipsOqpsk[16][32] = { | 
					
						
							|  |  |  |             {1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0}, | 
					
						
							|  |  |  |             {1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0}, | 
					
						
							|  |  |  |             {0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0}, | 
					
						
							|  |  |  |             {0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1}, | 
					
						
							|  |  |  |             {0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1}, | 
					
						
							|  |  |  |             {0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0}, | 
					
						
							|  |  |  |             {1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1}, | 
					
						
							|  |  |  |             {1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1}, | 
					
						
							|  |  |  |             {1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1}, | 
					
						
							|  |  |  |             {1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1}, | 
					
						
							|  |  |  |             {0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1}, | 
					
						
							|  |  |  |             {0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0}, | 
					
						
							|  |  |  |             {0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0}, | 
					
						
							|  |  |  |             {0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1}, | 
					
						
							|  |  |  |             {1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0}, | 
					
						
							|  |  |  |             {1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0} | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |         chip = chipsOqpsk[m_symbol][m_chipIdx]; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_chipIdx++; | 
					
						
							|  |  |  |     if (m_chipIdx >= m_chipsPerSymbol) | 
					
						
							|  |  |  |         m_chipIdx = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return chip; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int IEEE_802_15_4_ModSource::getSymbol() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     int symbol; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (m_bitCount > 0) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         int mask = m_bitsPerSymbol == 1 ? 0x1 : 0xf; | 
					
						
							|  |  |  |         symbol = (m_bits[m_byteIdx] >> m_bitIdx) & mask; | 
					
						
							|  |  |  |         m_bitIdx += m_bitsPerSymbol; | 
					
						
							|  |  |  |         m_bitCount -= m_bitsPerSymbol; | 
					
						
							|  |  |  |         if (m_bitIdx == 8) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             m_byteIdx++; | 
					
						
							|  |  |  |             m_bitIdx = 0; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (m_settings.m_modulation == IEEE_802_15_4_ModSettings::BPSK) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             // Differential encoding
 | 
					
						
							|  |  |  |             symbol = symbol ^ m_diffBit; | 
					
						
							|  |  |  |             m_diffBit = symbol; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |         symbol = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return symbol; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void IEEE_802_15_4_ModSource::initTX() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     m_sampleIdx = 0; | 
					
						
							|  |  |  |     m_chipOdd = false; | 
					
						
							|  |  |  |     m_chips[0] = 0; | 
					
						
							|  |  |  |     m_chips[1] = 0; | 
					
						
							|  |  |  |     m_chipIdx = 0; | 
					
						
							|  |  |  |     m_diffBit = 0; | 
					
						
							|  |  |  |     m_byteIdx = 0; | 
					
						
							|  |  |  |     m_bitIdx = 0; | 
					
						
							|  |  |  |     m_bitCount = m_bitCountTotal; // Reset to allow retransmission
 | 
					
						
							|  |  |  |     m_symbol = 0; | 
					
						
							|  |  |  |     if (m_settings.m_rampUpBits == 0) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         m_state = tx; | 
					
						
							|  |  |  |         m_pow = 0.0f; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         m_state = ramp_up; | 
					
						
							|  |  |  |         m_pow = -(Real)m_settings.m_rampRange; | 
					
						
							|  |  |  |         m_powRamp = m_settings.m_rampRange/(m_settings.m_rampUpBits * (Real)m_samplesPerChip); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     m_scrambler.init(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | uint8_t *IEEE_802_15_4_ModSource::hexToBin(uint8_t *p, QString data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     // Convert string containing space separated list of hex values to binary
 | 
					
						
							|  |  |  |     QStringList list = data.split(" "); | 
					
						
							|  |  |  |     for (int i = 0; i < list.size(); i++) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         *p++ = list[i].toInt(nullptr, 16); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return p; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void IEEE_802_15_4_ModSource::addTXFrame(QString data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     uint8_t *crcStart; | 
					
						
							|  |  |  |     uint8_t *p; | 
					
						
							|  |  |  |     uint8_t *pLength; | 
					
						
							|  |  |  |     crc16itut crc; | 
					
						
							|  |  |  |     uint16_t crcValue; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Create PHY frame
 | 
					
						
							|  |  |  |     p = m_bits; | 
					
						
							|  |  |  |     // Preamble
 | 
					
						
							|  |  |  |     *p++ = 0x00; | 
					
						
							|  |  |  |     *p++ = 0x00; | 
					
						
							|  |  |  |     *p++ = 0x00; | 
					
						
							|  |  |  |     *p++ = 0x00; | 
					
						
							|  |  |  |     // SFD - start of frame delimiter
 | 
					
						
							|  |  |  |     *p++ = 0xa7; | 
					
						
							|  |  |  |     // PHR - length
 | 
					
						
							|  |  |  |     pLength = p; | 
					
						
							|  |  |  |     *p++ = 0; | 
					
						
							|  |  |  |     // PHY payload
 | 
					
						
							|  |  |  |     crcStart = p; | 
					
						
							|  |  |  |     // Data
 | 
					
						
							| 
									
										
										
										
											2021-04-07 20:09:03 +00:00
										 |  |  |     p = hexToBin(p, data.trimmed()); | 
					
						
							| 
									
										
										
										
											2020-10-08 14:13:06 +00:00
										 |  |  |     // MAC FCS
 | 
					
						
							|  |  |  |     crc.calculate(crcStart, p-crcStart); | 
					
						
							|  |  |  |     crcValue = crc.get(); | 
					
						
							|  |  |  |     *p++ = crcValue & 0xff; | 
					
						
							|  |  |  |     *p++ = (crcValue >> 8); | 
					
						
							|  |  |  |     // Update length
 | 
					
						
							|  |  |  |     *pLength = p - pLength - 1; | 
					
						
							|  |  |  |     // Extra 0 to account for pulse shaping filter delay.
 | 
					
						
							|  |  |  |     // Should probably just be a few chips
 | 
					
						
							|  |  |  |     *p++ = 0x00; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Dump frame
 | 
					
						
							|  |  |  |     QByteArray qb((char *)m_bits, p-m_bits); | 
					
						
							|  |  |  |     qDebug() << "TX: " << qb.toHex(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Save number of bits in frame
 | 
					
						
							|  |  |  |     m_bitCount = m_bitCountTotal = (p-&m_bits[0]) * 8; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_frameRepeatCount = m_settings.m_repeatCount; | 
					
						
							|  |  |  |     initTX(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (m_settings.m_writeToFile) | 
					
						
							|  |  |  |         m_basebandFile.open("IEEE_802_15_4_Mod.csv", std::ofstream::out); | 
					
						
							|  |  |  |     else if (m_basebandFile.is_open()) | 
					
						
							|  |  |  |         m_basebandFile.close(); | 
					
						
							|  |  |  | } |