/////////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2017 F4EXB // // written by Edouard Griffiths // // // // 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 // // // // 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 . // /////////////////////////////////////////////////////////////////////////////////// #ifndef SDRBASE_DSP_SCOPEVISNG_H_ #define SDRBASE_DSP_SCOPEVISNG_H_ #include #include #include #include #include #include "dsp/dsptypes.h" #include "dsp/basebandsamplesink.h" #include "util/export.h" #include "util/message.h" #include "util/doublebuffer.h" class GLScopeNG; class SDRANGEL_API ScopeVisNG : public BasebandSampleSink { public: enum ProjectionType { ProjectionReal, //!< Extract real part ProjectionImag, //!< Extract imaginary part ProjectionMagLin, //!< Calculate linear magnitude or modulus ProjectionMagDB, //!< Calculate logarithmic (dB) of squared magnitude ProjectionPhase, //!< Calculate phase ProjectionDPhase //!< Calculate phase derivative i.e. instantaneous frequency scaled to sample rate }; struct TraceData { ProjectionType m_projectionType; //!< Complex to real projection type uint32_t m_inputIndex; //!< Input or feed index this trace is associated with float m_amp; //!< Amplification factor uint32_t m_ampIndex; //!< Index in list of amplification factors float m_ofs; //!< Offset factor int m_ofsCoarse; //!< Coarse offset slider value int m_ofsFine; //!< Fine offset slider value int m_traceDelay; //!< Trace delay in number of samples int m_traceDelayValue; //!< Trace delay slider value float m_triggerDisplayLevel; //!< Displayable trigger display level in -1:+1 scale. Off scale if not displayable. QColor m_traceColor; //!< Trace display color float m_traceColorR; //!< Trace display color - red shortcut float m_traceColorG; //!< Trace display color - green shortcut float m_traceColorB; //!< Trace display color - blue shortcut TraceData() : m_projectionType(ProjectionReal), m_inputIndex(0), m_amp(1.0f), m_ampIndex(0), m_ofs(0.0f), m_ofsCoarse(0), m_ofsFine(0), m_traceDelay(0), m_traceDelayValue(0), m_triggerDisplayLevel(2.0), // OVer scale by default (2.0) m_traceColor(255,255,64) { setColor(m_traceColor); } void setColor(QColor color) { m_traceColor = color; qreal r,g,b,a; m_traceColor.getRgbF(&r, &g, &b, &a); m_traceColorR = r; m_traceColorG = g; m_traceColorB = b; } }; struct TriggerData { ProjectionType m_projectionType; //!< Complex to real projection type uint32_t m_inputIndex; //!< Input or feed index this trigger is associated with Real m_triggerLevel; //!< Level in real units int m_triggerLevelCoarse; int m_triggerLevelFine; bool m_triggerPositiveEdge; //!< Trigger on the positive edge (else negative) bool m_triggerBothEdges; //!< Trigger on both edges (else only one) uint32_t m_triggerDelay; //!< Delay before the trigger is kicked off in number of samples (trigger delay) double m_triggerDelayMult; //!< Trigger delay as a multiplier of trace length int m_triggerDelayCoarse; int m_triggerDelayFine; uint32_t m_triggerRepeat; //!< Number of trigger conditions before the final decisive trigger QColor m_triggerColor; //!< Trigger line display color float m_triggerColorR; //!< Trigger line display color - red shortcut float m_triggerColorG; //!< Trigger line display color - green shortcut float m_triggerColorB; //!< Trigger line display color - blue shortcut TriggerData() : m_projectionType(ProjectionReal), m_inputIndex(0), m_triggerLevel(0.0f), m_triggerLevelCoarse(0), m_triggerLevelFine(0), m_triggerPositiveEdge(true), m_triggerBothEdges(false), m_triggerDelay(0), m_triggerDelayMult(0.0), m_triggerDelayCoarse(0), m_triggerDelayFine(0), m_triggerRepeat(0), m_triggerColor(0,255,0) { setColor(m_triggerColor); } void setColor(QColor color) { m_triggerColor = color; qreal r,g,b,a; m_triggerColor.getRgbF(&r, &g, &b, &a); m_triggerColorR = r; m_triggerColorG = g; m_triggerColorB = b; } }; static const uint m_traceChunkSize; static const uint m_nbTriggers = 10; ScopeVisNG(GLScopeNG* glScope = 0); virtual ~ScopeVisNG(); void setSampleRate(int sampleRate); void configure(uint32_t traceSize, uint32_t timeOfsProMill, uint32_t triggerPre, bool freeRun); void addTrace(const TraceData& traceData); void changeTrace(const TraceData& traceData, uint32_t traceIndex); void removeTrace(uint32_t traceIndex); void focusOnTrace(uint32_t traceIndex); void addTrigger(const TriggerData& triggerData); void changeTrigger(const TriggerData& triggerData, uint32_t triggerIndex); void removeTrigger(uint32_t triggerIndex); void focusOnTrigger(uint32_t triggerIndex); void getTriggerData(TriggerData& triggerData, uint32_t triggerIndex) { if (triggerIndex < m_triggerConditions.size()) { triggerData = m_triggerConditions[triggerIndex].m_triggerData; } } void getTraceData(TraceData& traceData, uint32_t traceIndex) { if (traceIndex < m_traces.m_tracesData.size()) { traceData = m_traces.m_tracesData[traceIndex]; } } virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); virtual void start(); virtual void stop(); virtual bool handleMessage(const Message& message); SampleVector::const_iterator getTriggerPoint() const { return m_triggerPoint; } private: // === messages === // --------------------------------------------- class MsgConfigureScopeVisNG : public Message { MESSAGE_CLASS_DECLARATION public: static MsgConfigureScopeVisNG* create( uint32_t traceSize, uint32_t timeOfsProMill, uint32_t triggerPre, bool freeRun) { return new MsgConfigureScopeVisNG(traceSize, timeOfsProMill, triggerPre, freeRun); } uint32_t getTraceSize() const { return m_traceSize; } uint32_t getTimeOfsProMill() const { return m_timeOfsProMill; } uint32_t getTriggerPre() const { return m_triggerPre; } bool getFreeRun() const { return m_freeRun; } private: uint32_t m_traceSize; uint32_t m_timeOfsProMill; uint32_t m_triggerPre; bool m_freeRun; MsgConfigureScopeVisNG(uint32_t traceSize, uint32_t timeOfsProMill, uint32_t triggerPre, bool freeRun) : m_traceSize(traceSize), m_timeOfsProMill(timeOfsProMill), m_triggerPre(triggerPre), m_freeRun(freeRun) {} }; // --------------------------------------------- class MsgScopeVisNGAddTrigger : public Message { MESSAGE_CLASS_DECLARATION public: static MsgScopeVisNGAddTrigger* create( const TriggerData& triggerData) { return new MsgScopeVisNGAddTrigger(triggerData); } const TriggerData& getTriggerData() const { return m_triggerData; } private: TriggerData m_triggerData; MsgScopeVisNGAddTrigger(const TriggerData& triggerData) : m_triggerData(triggerData) {} }; // --------------------------------------------- class MsgScopeVisNGChangeTrigger : public Message { MESSAGE_CLASS_DECLARATION public: static MsgScopeVisNGChangeTrigger* create( const TriggerData& triggerData, uint32_t triggerIndex) { return new MsgScopeVisNGChangeTrigger(triggerData, triggerIndex); } const TriggerData& getTriggerData() const { return m_triggerData; } uint32_t getTriggerIndex() const { return m_triggerIndex; } private: TriggerData m_triggerData; uint32_t m_triggerIndex; MsgScopeVisNGChangeTrigger(const TriggerData& triggerData, uint32_t triggerIndex) : m_triggerData(triggerData), m_triggerIndex(triggerIndex) {} }; // --------------------------------------------- class MsgScopeVisNGRemoveTrigger : public Message { MESSAGE_CLASS_DECLARATION public: static MsgScopeVisNGRemoveTrigger* create( uint32_t triggerIndex) { return new MsgScopeVisNGRemoveTrigger(triggerIndex); } uint32_t getTriggerIndex() const { return m_triggerIndex; } private: uint32_t m_triggerIndex; MsgScopeVisNGRemoveTrigger(uint32_t triggerIndex) : m_triggerIndex(triggerIndex) {} }; // --------------------------------------------- class MsgScopeVisNGFocusOnTrigger : public Message { MESSAGE_CLASS_DECLARATION public: static MsgScopeVisNGFocusOnTrigger* create( uint32_t triggerIndex) { return new MsgScopeVisNGFocusOnTrigger(triggerIndex); } uint32_t getTriggerIndex() const { return m_triggerIndex; } private: uint32_t m_triggerIndex; MsgScopeVisNGFocusOnTrigger(uint32_t triggerIndex) : m_triggerIndex(triggerIndex) {} }; // --------------------------------------------- class MsgScopeVisNGAddTrace : public Message { MESSAGE_CLASS_DECLARATION public: static MsgScopeVisNGAddTrace* create( const TraceData& traceData) { return new MsgScopeVisNGAddTrace(traceData); } const TraceData& getTraceData() const { return m_traceData; } private: TraceData m_traceData; MsgScopeVisNGAddTrace(const TraceData& traceData) : m_traceData(traceData) {} }; // --------------------------------------------- class MsgScopeVisNGChangeTrace : public Message { MESSAGE_CLASS_DECLARATION public: static MsgScopeVisNGChangeTrace* create( const TraceData& traceData, uint32_t traceIndex) { return new MsgScopeVisNGChangeTrace(traceData, traceIndex); } const TraceData& getTraceData() const { return m_traceData; } uint32_t getTraceIndex() const { return m_traceIndex; } private: TraceData m_traceData; uint32_t m_traceIndex; MsgScopeVisNGChangeTrace(TraceData traceData, uint32_t traceIndex) : m_traceData(traceData), m_traceIndex(traceIndex) {} }; // --------------------------------------------- class MsgScopeVisNGRemoveTrace : public Message { MESSAGE_CLASS_DECLARATION public: static MsgScopeVisNGRemoveTrace* create( uint32_t traceIndex) { return new MsgScopeVisNGRemoveTrace(traceIndex); } uint32_t getTraceIndex() const { return m_traceIndex; } private: uint32_t m_traceIndex; MsgScopeVisNGRemoveTrace(uint32_t traceIndex) : m_traceIndex(traceIndex) {} }; // --------------------------------------------- class MsgScopeVisNGFocusOnTrace : public Message { MESSAGE_CLASS_DECLARATION public: static MsgScopeVisNGFocusOnTrace* create( uint32_t traceIndex) { return new MsgScopeVisNGFocusOnTrace(traceIndex); } uint32_t getTraceIndex() const { return m_traceIndex; } private: uint32_t m_traceIndex; MsgScopeVisNGFocusOnTrace(uint32_t traceIndex) : m_traceIndex(traceIndex) {} }; // --------------------------------------------- /** * Projection stuff */ class Projector { public: Projector(ProjectionType projectionType) : m_projectionType(projectionType), m_prevArg(0.0f) {} ~Projector() {} ProjectionType getProjectionType() const { return m_projectionType; } void settProjectionType(ProjectionType projectionType) { m_projectionType = projectionType; } Real run(const Sample& s) { switch (m_projectionType) { case ProjectionImag: return s.m_imag / 32768.0f; break; case ProjectionMagLin: { uint32_t magsq = s.m_real*s.m_real + s.m_imag*s.m_imag; return std::sqrt(magsq/1073741824.0f); } break; case ProjectionMagDB: { uint32_t magsq = s.m_real*s.m_real + s.m_imag*s.m_imag; return log10f(magsq/1073741824.0f) * 10.0f; } break; case ProjectionPhase: return std::atan2((float) s.m_imag, (float) s.m_real) / M_PI; break; case ProjectionDPhase: { Real curArg = std::atan2((float) s.m_imag, (float) s.m_real); Real dPhi = (curArg - m_prevArg) / M_PI; m_prevArg = curArg; if (dPhi < -1.0f) { dPhi += 2.0f; } else if (dPhi > 1.0f) { dPhi -= 2.0f; } return dPhi; } break; case ProjectionReal: default: return s.m_real / 32768.0f; break; } } private: ProjectionType m_projectionType; Real m_prevArg; }; /** * Trigger stuff */ enum TriggerState { TriggerUntriggered, //!< Trigger is not kicked off yet (or trigger list is empty) TriggerTriggered, //!< Trigger has been kicked off TriggerWait, //!< In one shot mode trigger waits for manual re-enabling TriggerDelay, //!< Trigger conditions have been kicked off but it is waiting for delay before final kick off TriggerNewConfig, //!< Special condition when a new configuration has been received }; struct TriggerCondition { public: Projector m_projector; TriggerData m_triggerData; //!< Trigger data bool m_prevCondition; //!< Condition (above threshold) at previous sample uint32_t m_triggerDelayCount; //!< Counter of samples for delay uint32_t m_triggerCounter; //!< Counter of trigger occurences TriggerCondition(const TriggerData& triggerData) : m_projector(ProjectionReal), m_triggerData(triggerData), m_prevCondition(false), m_triggerDelayCount(0), m_triggerCounter(0) { } ~TriggerCondition() { } void initProjector() { m_projector.settProjectionType(m_triggerData.m_projectionType); } void releaseProjector() { } void setData(const TriggerData& triggerData) { m_triggerData = triggerData; if (m_projector.getProjectionType() != m_triggerData.m_projectionType) { m_projector.settProjectionType(m_triggerData.m_projectionType); } m_prevCondition = false; m_triggerDelayCount = 0; m_triggerCounter = 0; } }; /** * Complex trace stuff */ typedef DoubleBufferSimple TraceBuffer; struct TraceBackBuffer { TraceBuffer m_traceBuffer; SampleVector::iterator m_endPoint; TraceBackBuffer() { m_endPoint = m_traceBuffer.getCurrent(); } void resize(uint32_t size) { m_traceBuffer.resize(size); } void reset() { m_traceBuffer.reset(); } void write(const SampleVector::const_iterator begin, const SampleVector::const_iterator end) { m_traceBuffer.write(begin, end); } unsigned int absoluteFill() const { return m_traceBuffer.absoluteFill(); } SampleVector::iterator current() { return m_traceBuffer.getCurrent(); } }; struct TraceBackDiscreteMemory { std::vector m_traceBackBuffers; uint32_t m_memSize; uint32_t m_currentMemIndex; uint32_t m_traceSize; /** * Give memory size in number of traces */ TraceBackDiscreteMemory(uint32_t size) : m_memSize(size), m_currentMemIndex(0), m_traceSize(0) { m_traceBackBuffers.resize(m_memSize); } /** * Resize all trace buffers in memory */ void resize(uint32_t size) { m_traceSize = size; for (std::vector::iterator it = m_traceBackBuffers.begin(); it != m_traceBackBuffers.end(); ++it) { it->resize(4*m_traceSize); } } /** * Move index forward by one position and return reference to the trace at this position * Copy a trace length of samples into the new memory slot */ TraceBackBuffer &store() { uint32_t nextMemIndex = m_currentMemIndex < (m_memSize-1) ? m_currentMemIndex+1 : 0; m_traceBackBuffers[nextMemIndex].reset(); m_traceBackBuffers[nextMemIndex].write(m_traceBackBuffers[m_currentMemIndex].m_endPoint - m_traceSize, m_traceBackBuffers[m_currentMemIndex].m_endPoint); m_currentMemIndex = nextMemIndex; return m_traceBackBuffers[m_currentMemIndex]; // new trace } /** * Recalls trace at shift positions back. Therefore 0 is current. Wraps around memory size. */ TraceBackBuffer& recall(uint32_t shift) { int index = (m_currentMemIndex + (m_memSize - (shift % m_memSize))) % m_memSize; return m_traceBackBuffers[index]; } /** * Return trace at current memory position */ TraceBackBuffer& current() { return m_traceBackBuffers[m_currentMemIndex]; } /** * Return current memory index */ uint32_t currentIndex() const { return m_currentMemIndex; } }; /** * Displayable trace stuff */ struct TraceControl { Projector m_projector; //!< Projector transform from complex trace to real (displayable) trace int m_traceCount[2]; //!< Count of samples processed (double buffered) TraceControl() : m_projector(ProjectionReal) { reset(); } ~TraceControl() { } void initProjector(ProjectionType projectionType) { m_projector.settProjectionType(projectionType); } void releaseProjector() { } void reset() { m_traceCount[0] = 0; m_traceCount[1] = 0; } }; struct Traces { std::vector m_tracesControl; //!< Corresponding traces control data std::vector m_tracesData; //!< Corresponding traces data std::vector m_traces[2]; //!< Double buffer of traces processed by glScope int m_traceSize; //!< Current size of a trace in buffer int m_maxTraceSize; //!< Maximum Size of a trace in buffer bool evenOddIndex; //!< Even (true) or odd (false) index Traces() : evenOddIndex(true), m_traceSize(0), m_maxTraceSize(0) {} ~Traces() { std::vector::iterator it0 = m_traces[0].begin(); std::vector::iterator it1 = m_traces[1].begin(); for (; it0 != m_traces[0].end(); ++it0, ++it1) { delete[] (*it0); delete[] (*it1); } } bool isVerticalDisplayChange(const TraceData& traceData, uint32_t traceIndex) { return (m_tracesData[traceIndex].m_projectionType != traceData.m_projectionType) || (m_tracesData[traceIndex].m_amp != traceData.m_amp) || (m_tracesData[traceIndex].m_ofs != traceData.m_ofs || (m_tracesData[traceIndex].m_traceColor != traceData.m_traceColor)); } void addTrace(const TraceData& traceData, int traceSize) { resize(traceSize); m_tracesData.push_back(traceData); m_tracesControl.push_back(TraceControl()); m_tracesControl.back().initProjector(traceData.m_projectionType); float *x0 = new float[2*m_traceSize]; float *x1 = new float[2*m_traceSize]; m_traces[0].push_back(x0); m_traces[1].push_back(x1); } void changeTrace(const TraceData& traceData, uint32_t traceIndex) { if (traceIndex < m_tracesControl.size()) { m_tracesControl[traceIndex].releaseProjector(); m_tracesControl[traceIndex].initProjector(traceData.m_projectionType); m_tracesData[traceIndex] = traceData; } } void removeTrace(uint32_t traceIndex) { if (traceIndex < m_tracesControl.size()) { m_tracesControl[traceIndex].releaseProjector(); m_tracesControl.erase(m_tracesControl.begin() + traceIndex); m_tracesData.erase(m_tracesData.begin() + traceIndex); delete[] (m_traces[0])[traceIndex]; delete[] (m_traces[1])[traceIndex]; m_traces[0].erase(m_traces[0].begin() + traceIndex); m_traces[1].erase(m_traces[1].begin() + traceIndex); } } void resize(int traceSize) { m_traceSize = traceSize; if (m_traceSize > m_maxTraceSize) { std::vector::iterator it0 = m_traces[0].begin(); std::vector::iterator it1 = m_traces[1].begin(); for (; it0 != m_traces[0].end(); ++it0, ++it1) { delete[] (*it0); delete[] (*it1); *it0 = new float[2*m_traceSize]; *it1 = new float[2*m_traceSize]; } m_maxTraceSize = m_traceSize; } } uint32_t currentBufferIndex() const { return evenOddIndex? 0 : 1; } uint32_t size() const { return m_tracesControl.size(); } void switchBuffer() { evenOddIndex = !evenOddIndex; for (std::vector::iterator it = m_tracesControl.begin(); it != m_tracesControl.end(); ++it) { it->m_traceCount[currentBufferIndex()] = 0; } } }; class TriggerComparator { public: TriggerComparator() : m_level(0), m_reset(true) { computeLevels(); } bool triggered(const Sample& s, TriggerCondition& triggerCondition) { if (triggerCondition.m_triggerData.m_triggerLevel != m_level) { m_level = triggerCondition.m_triggerData.m_triggerLevel; computeLevels(); } bool condition, trigger; if (triggerCondition.m_projector.getProjectionType() == ProjectionMagDB) { condition = triggerCondition.m_projector.run(s) > m_levelPowerDB; } else if (triggerCondition.m_projector.getProjectionType() == ProjectionMagLin) { condition = triggerCondition.m_projector.run(s) > m_levelPowerLin; } else { condition = triggerCondition.m_projector.run(s) > m_level; } if (m_reset) { triggerCondition.m_prevCondition = condition; m_reset = false; return false; } if (triggerCondition.m_triggerData.m_triggerBothEdges) { trigger = triggerCondition.m_prevCondition ? !condition : condition; // This is a XOR between bools } else if (triggerCondition.m_triggerData.m_triggerPositiveEdge) { trigger = !triggerCondition.m_prevCondition && condition; } else { trigger = triggerCondition.m_prevCondition && !condition; } // if (trigger) { // qDebug("ScopeVisNG::triggered: %s/%s %f/%f", // triggerCondition.m_prevCondition ? "T" : "F", // condition ? "T" : "F", // triggerCondition.m_projector->run(s), // triggerCondition.m_triggerData.m_triggerLevel); // } triggerCondition.m_prevCondition = condition; return trigger; } void reset() { m_reset = true; } private: void computeLevels() { m_levelPowerLin = m_level + 1.0f; m_levelPowerDB = (100.0f * (m_level - 1.0f)); } Real m_level; Real m_levelPowerDB; Real m_levelPowerLin; bool m_reset; }; GLScopeNG* m_glScope; uint32_t m_preTriggerDelay; //!< Pre-trigger delay in number of samples std::vector m_triggerConditions; //!< Chain of triggers int m_currentTriggerIndex; //!< Index of current index in the chain int m_focusedTriggerIndex; //!< Index of the trigger that has focus TriggerState m_triggerState; //!< Current trigger state Traces m_traces; //!< Displayable traces int m_focusedTraceIndex; //!< Index of the trace that has focus int m_traceSize; //!< Size of traces in number of samples int m_nbSamples; //!< Number of samples yet to process in one complex trace int m_timeOfsProMill; //!< Start trace shift in 1/1000 trace size bool m_traceStart; //!< Trace is at start point int m_traceFill; //!< Count of samples accumulated into trace int m_zTraceIndex; //!< Index of the trace used for Z input (luminance or false colors) SampleVector::const_iterator m_triggerPoint; //!< Trigger start location in the samples vector int m_sampleRate; TraceBackDiscreteMemory m_traceDiscreteMemory; //!< Complex trace memory for triggered states TODO: vectorize when more than on input is allowed bool m_freeRun; //!< True if free running (trigger globally disabled) int m_maxTraceDelay; //!< Maximum trace delay TriggerComparator m_triggerComparator; //!< Compares sample level to trigger level QMutex m_mutex; /** * Moves on to the next trigger if any or increments trigger count if in repeat mode * - If not final it returns true * - If final i.e. signal is actually triggerd it returns false */ bool nextTrigger(); //!< Returns true if not final /** * Process a sample trace which length is at most the trace length (m_traceSize) */ void processTrace(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, int& triggerPointToEnd); /** * Process traces from complex trace memory buffer. * - if finished it returns the number of unprocessed samples left in the buffer * - if not finished it returns -1 */ int processTraces(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool traceBack = false); /** * Get maximum trace delay */ void updateMaxTraceDelay(); /** * Initialize trace buffers */ void initTraceBuffers(); /** * Calculate trigger levels on display * - every time a trigger condition focus changes TBD * - every time the focused trigger condition changes its projection type or level * - every time a trace data changes: projection type, amp, offset * - every time a trace data is added or removed */ void computeDisplayTriggerLevels(); }; #endif /* SDRBASE_DSP_SCOPEVISNG_H_ */