2018-02-08 21:28:33 -05:00
|
|
|
#include "Detector.hpp"
|
2024-11-29 12:56:43 -08:00
|
|
|
#include <algorithm>
|
2024-11-29 08:55:51 -08:00
|
|
|
#include <cmath>
|
2018-02-08 21:28:33 -05:00
|
|
|
#include <QDateTime>
|
|
|
|
|
#include <QDebug>
|
2024-11-29 12:56:43 -08:00
|
|
|
#include <QMutexLocker>
|
|
|
|
|
#include <QtAlgorithms>
|
2018-02-08 21:28:33 -05:00
|
|
|
#include "commons.h"
|
2018-09-18 17:24:07 -04:00
|
|
|
#include "DriftingDateTime.h"
|
|
|
|
|
|
2024-11-29 08:55:51 -08:00
|
|
|
/******************************************************************************/
|
2024-11-29 12:56:43 -08:00
|
|
|
// FIR Filter Coefficients
|
2024-11-29 08:55:51 -08:00
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
namespace
|
|
|
|
|
{
|
2024-11-29 12:56:43 -08:00
|
|
|
// Filter coefficients for an FIR lowpass filter designed using ScopeFIR.
|
2024-11-29 08:55:51 -08:00
|
|
|
//
|
|
|
|
|
// fsample = 48000 Hz
|
|
|
|
|
// Ntaps = 49
|
|
|
|
|
// fc = 4500 Hz
|
|
|
|
|
// fstop = 6000 Hz
|
|
|
|
|
// Ripple = 1 dB
|
|
|
|
|
// Stop Atten = 40 dB
|
|
|
|
|
// fout = 12000 Hz
|
|
|
|
|
|
2024-11-29 20:53:53 -08:00
|
|
|
constexpr std::array LOWPASS
|
2024-11-29 08:55:51 -08:00
|
|
|
{
|
|
|
|
|
0.000861074040f, 0.010051920210f, 0.010161983649f, 0.011363155076f,
|
|
|
|
|
0.008706594219f, 0.002613872664f, -0.005202883094f, -0.011720748164f,
|
|
|
|
|
-0.013752163325f, -0.009431602741f, 0.000539063909f, 0.012636767098f,
|
|
|
|
|
0.021494659597f, 0.021951235065f, 0.011564169382f, -0.007656470131f,
|
|
|
|
|
-0.028965787341f, -0.042637874109f, -0.039203309748f, -0.013153301537f,
|
|
|
|
|
0.034320769178f, 0.094717832646f, 0.154224604789f, 0.197758325022f,
|
|
|
|
|
0.213715139513f, 0.197758325022f, 0.154224604789f, 0.094717832646f,
|
|
|
|
|
0.034320769178f, -0.013153301537f, -0.039203309748f, -0.042637874109f,
|
|
|
|
|
-0.028965787341f, -0.007656470131f, 0.011564169382f, 0.021951235065f,
|
|
|
|
|
0.021494659597f, 0.012636767098f, 0.000539063909f, -0.009431602741f,
|
|
|
|
|
-0.013752163325f, -0.011720748164f, -0.005202883094f, 0.002613872664f,
|
|
|
|
|
0.008706594219f, 0.011363155076f, 0.010161983649f, 0.010051920210f,
|
|
|
|
|
0.000861074040f
|
|
|
|
|
};
|
2018-02-08 21:28:33 -05:00
|
|
|
}
|
|
|
|
|
|
2024-11-29 08:55:51 -08:00
|
|
|
/******************************************************************************/
|
|
|
|
|
// Implementation
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
#include "moc_Detector.cpp"
|
|
|
|
|
|
2024-11-29 11:45:00 -08:00
|
|
|
Detector::Detector(unsigned frameRate,
|
|
|
|
|
unsigned periodLengthInSeconds,
|
|
|
|
|
QObject * parent)
|
2018-02-08 21:28:33 -05:00
|
|
|
: AudioDevice (parent)
|
|
|
|
|
, m_frameRate (frameRate)
|
2024-11-29 12:56:43 -08:00
|
|
|
, m_period (periodLengthInSeconds)
|
2024-11-30 08:49:39 -08:00
|
|
|
, m_filter (LOWPASS)
|
2018-02-08 21:28:33 -05:00
|
|
|
{
|
2024-11-29 12:56:43 -08:00
|
|
|
clear();
|
2018-02-08 21:28:33 -05:00
|
|
|
}
|
|
|
|
|
|
2024-11-30 06:34:10 -08:00
|
|
|
void
|
|
|
|
|
Detector::setBlockSize (unsigned n)
|
2018-02-08 21:28:33 -05:00
|
|
|
{
|
|
|
|
|
m_samplesPerFFT = n;
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-30 06:16:06 -08:00
|
|
|
bool
|
|
|
|
|
Detector::reset()
|
2018-02-08 21:28:33 -05:00
|
|
|
{
|
|
|
|
|
clear ();
|
|
|
|
|
// don't call base call reset because it calls seek(0) which causes
|
|
|
|
|
// a warning
|
|
|
|
|
return isOpen ();
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-30 06:16:06 -08:00
|
|
|
void
|
|
|
|
|
Detector::clear()
|
2018-02-08 21:28:33 -05:00
|
|
|
{
|
2019-10-30 11:49:10 -04:00
|
|
|
#if JS8_RING_BUFFER
|
2020-05-13 21:14:15 -04:00
|
|
|
resetBufferPosition();
|
|
|
|
|
resetBufferContent();
|
2019-10-28 16:06:06 -04:00
|
|
|
#else
|
2018-02-08 21:28:33 -05:00
|
|
|
dec_data.params.kin = 0;
|
|
|
|
|
m_bufferPos = 0;
|
2019-10-28 16:06:06 -04:00
|
|
|
#endif
|
2018-02-08 21:28:33 -05:00
|
|
|
|
|
|
|
|
// fill buffer with zeros (G4WJS commented out because it might cause decoder hangs)
|
|
|
|
|
// qFill (dec_data.d2, dec_data.d2 + sizeof (dec_data.d2) / sizeof (dec_data.d2[0]), 0);
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-30 06:16:06 -08:00
|
|
|
void
|
|
|
|
|
Detector::resetBufferPosition()
|
Use std::rotate in detector
Since we have C++17 available to us, std::rotate is available, and on every implementation tested, auto-vectorizes into, for example, use of the XMM registers on Intel machines (note, not in debug mode, the optimizer is required). The result runs at least as fast, typically faster, than the custom versions here, and eliminates use of the static temporary array requirement.
2024-09-24 17:14:18 -07:00
|
|
|
{
|
2024-11-29 12:56:43 -08:00
|
|
|
QMutexLocker mutex(&m_lock);
|
2020-05-13 21:14:15 -04:00
|
|
|
|
2024-11-29 12:56:43 -08:00
|
|
|
// set index to roughly where we are in time (1ms resolution)
|
|
|
|
|
qint64 const now = DriftingDateTime::currentMSecsSinceEpoch ();
|
|
|
|
|
unsigned const msInPeriod = (now % 86400000LL) % (m_period * 1000);
|
|
|
|
|
int const prevKin = dec_data.params.kin;
|
Use std::rotate in detector
Since we have C++17 available to us, std::rotate is available, and on every implementation tested, auto-vectorizes into, for example, use of the XMM registers on Intel machines (note, not in debug mode, the optimizer is required). The result runs at least as fast, typically faster, than the custom versions here, and eliminates use of the static temporary array requirement.
2024-09-24 17:14:18 -07:00
|
|
|
|
2024-11-29 12:56:43 -08:00
|
|
|
dec_data.params.kin = qMin ((msInPeriod * m_frameRate) / 1000, static_cast<unsigned> (sizeof (dec_data.d2) / sizeof (dec_data.d2[0])));
|
|
|
|
|
m_bufferPos = 0;
|
|
|
|
|
m_ns = secondInPeriod();
|
Use std::rotate in detector
Since we have C++17 available to us, std::rotate is available, and on every implementation tested, auto-vectorizes into, for example, use of the XMM registers on Intel machines (note, not in debug mode, the optimizer is required). The result runs at least as fast, typically faster, than the custom versions here, and eliminates use of the static temporary array requirement.
2024-09-24 17:14:18 -07:00
|
|
|
|
2024-11-29 12:56:43 -08:00
|
|
|
int const delta = dec_data.params.kin - prevKin;
|
Use std::rotate in detector
Since we have C++17 available to us, std::rotate is available, and on every implementation tested, auto-vectorizes into, for example, use of the XMM registers on Intel machines (note, not in debug mode, the optimizer is required). The result runs at least as fast, typically faster, than the custom versions here, and eliminates use of the static temporary array requirement.
2024-09-24 17:14:18 -07:00
|
|
|
|
2024-11-29 12:56:43 -08:00
|
|
|
qDebug() << "advancing detector buffer from" << prevKin << "to" << dec_data.params.kin << "delta" << delta;
|
2020-05-13 21:14:15 -04:00
|
|
|
|
2024-11-29 12:56:43 -08:00
|
|
|
// rotate buffer moving the contents that were at prevKin to the new kin position
|
|
|
|
|
if (delta < 0)
|
|
|
|
|
{
|
|
|
|
|
std::rotate(std::begin(dec_data.d2),
|
|
|
|
|
std::begin(dec_data.d2) - delta,
|
|
|
|
|
std::end (dec_data.d2));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
std::rotate(std::rbegin(dec_data.d2),
|
|
|
|
|
std::rbegin(dec_data.d2) + delta,
|
|
|
|
|
std::rend (dec_data.d2));
|
|
|
|
|
}
|
2020-05-13 21:14:15 -04:00
|
|
|
}
|
|
|
|
|
|
2024-11-30 06:16:06 -08:00
|
|
|
void
|
|
|
|
|
Detector::resetBufferContent()
|
Use std::rotate in detector
Since we have C++17 available to us, std::rotate is available, and on every implementation tested, auto-vectorizes into, for example, use of the XMM registers on Intel machines (note, not in debug mode, the optimizer is required). The result runs at least as fast, typically faster, than the custom versions here, and eliminates use of the static temporary array requirement.
2024-09-24 17:14:18 -07:00
|
|
|
{
|
2024-11-29 12:56:43 -08:00
|
|
|
QMutexLocker mutex(&m_lock);
|
2020-05-13 21:14:15 -04:00
|
|
|
|
2024-11-29 12:56:43 -08:00
|
|
|
std::fill(std::begin(dec_data.d2), std::end(dec_data.d2), 0);
|
|
|
|
|
qDebug() << "clearing detector buffer content";
|
2020-05-13 21:14:15 -04:00
|
|
|
}
|
|
|
|
|
|
2024-11-30 06:16:06 -08:00
|
|
|
qint64
|
|
|
|
|
Detector::writeData(char const * const data,
|
|
|
|
|
qint64 const maxSize)
|
2018-02-08 21:28:33 -05:00
|
|
|
{
|
2019-11-17 01:21:07 -05:00
|
|
|
QMutexLocker mutex(&m_lock);
|
|
|
|
|
|
2024-11-30 06:34:10 -08:00
|
|
|
// When ns has wrapped around to zero, restart the buffers.
|
|
|
|
|
|
|
|
|
|
int const ns = secondInPeriod();
|
|
|
|
|
if(ns < m_ns) {
|
2018-02-08 21:28:33 -05:00
|
|
|
dec_data.params.kin = 0;
|
2024-11-30 06:34:10 -08:00
|
|
|
m_bufferPos = 0;
|
2018-02-08 21:28:33 -05:00
|
|
|
}
|
2024-11-30 06:34:10 -08:00
|
|
|
m_ns = ns;
|
|
|
|
|
|
|
|
|
|
// No torn frames.
|
|
|
|
|
|
|
|
|
|
Q_ASSERT (!(maxSize % static_cast<qint64>(bytesPerFrame())));
|
2018-02-08 21:28:33 -05:00
|
|
|
|
2024-11-30 06:34:10 -08:00
|
|
|
// These are in terms of input frames (not down sampled).
|
2018-02-08 21:28:33 -05:00
|
|
|
|
2024-11-30 08:49:39 -08:00
|
|
|
size_t const framesAcceptable = (sizeof(dec_data.d2) / sizeof(dec_data.d2[0]) - dec_data.params.kin) * Filter::NDOWN;
|
2024-11-30 06:34:10 -08:00
|
|
|
size_t const framesAccepted = qMin(static_cast<size_t>(maxSize /bytesPerFrame()), framesAcceptable);
|
|
|
|
|
|
|
|
|
|
if (framesAccepted < static_cast<size_t>(maxSize / bytesPerFrame()))
|
2024-11-29 12:56:43 -08:00
|
|
|
{
|
2024-11-30 06:34:10 -08:00
|
|
|
qDebug() << "dropped " << maxSize / bytesPerFrame () - framesAccepted
|
|
|
|
|
<< " frames of data on the floor!"
|
|
|
|
|
<< dec_data.params.kin
|
|
|
|
|
<< ns;
|
2024-11-29 12:56:43 -08:00
|
|
|
}
|
2018-02-08 21:28:33 -05:00
|
|
|
|
2024-11-30 06:34:10 -08:00
|
|
|
for (unsigned remaining = framesAccepted;
|
|
|
|
|
remaining;)
|
2024-11-29 12:56:43 -08:00
|
|
|
{
|
2024-11-30 08:49:39 -08:00
|
|
|
size_t const numFramesProcessed = qMin(m_samplesPerFFT * Filter::NDOWN - m_bufferPos, remaining);
|
2024-11-30 06:34:10 -08:00
|
|
|
|
|
|
|
|
store (&data[(framesAccepted - remaining) * bytesPerFrame()],
|
|
|
|
|
numFramesProcessed,
|
|
|
|
|
&m_buffer[m_bufferPos]);
|
2018-02-08 21:28:33 -05:00
|
|
|
|
2024-11-29 12:56:43 -08:00
|
|
|
m_bufferPos += numFramesProcessed;
|
2018-02-08 21:28:33 -05:00
|
|
|
|
2024-11-30 08:49:39 -08:00
|
|
|
if (m_bufferPos == m_samplesPerFFT * Filter::NDOWN)
|
2024-11-29 12:56:43 -08:00
|
|
|
{
|
|
|
|
|
if (dec_data.params.kin >= 0 &&
|
|
|
|
|
dec_data.params.kin < static_cast<int>(NTMAX * 12000 - m_samplesPerFFT))
|
2024-11-29 11:45:00 -08:00
|
|
|
{
|
2024-11-29 12:56:43 -08:00
|
|
|
for (std::size_t i = 0; i < m_samplesPerFFT; ++i)
|
2024-11-29 08:55:51 -08:00
|
|
|
{
|
2024-11-30 08:49:39 -08:00
|
|
|
dec_data.d2[dec_data.params.kin++] = m_filter.downSample(&m_buffer[i * Filter::NDOWN]);
|
2018-02-08 21:28:33 -05:00
|
|
|
}
|
|
|
|
|
}
|
2024-11-29 12:56:43 -08:00
|
|
|
Q_EMIT framesWritten (dec_data.params.kin);
|
|
|
|
|
m_bufferPos = 0;
|
2018-02-08 21:28:33 -05:00
|
|
|
}
|
2024-11-29 12:56:43 -08:00
|
|
|
remaining -= numFramesProcessed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We drop any data past the end of the buffer on the floor
|
|
|
|
|
// until the next period starts
|
2018-02-08 21:28:33 -05:00
|
|
|
|
2024-11-29 12:56:43 -08:00
|
|
|
return maxSize;
|
2018-02-08 21:28:33 -05:00
|
|
|
}
|
|
|
|
|
|
2024-11-29 12:56:43 -08:00
|
|
|
unsigned
|
|
|
|
|
Detector::secondInPeriod() const
|
2018-02-08 21:28:33 -05:00
|
|
|
{
|
|
|
|
|
// we take the time of the data as the following assuming no latency
|
|
|
|
|
// delivering it to us (not true but close enough for us)
|
2018-09-18 17:24:07 -04:00
|
|
|
qint64 now (DriftingDateTime::currentMSecsSinceEpoch ());
|
2018-02-08 21:28:33 -05:00
|
|
|
unsigned secondInToday ((now % 86400000LL) / 1000);
|
|
|
|
|
return secondInToday % m_period;
|
|
|
|
|
}
|
2024-11-29 08:55:51 -08:00
|
|
|
|
|
|
|
|
/******************************************************************************/
|