This commit is contained in:
2026-06-08 17:33:16 +08:00
parent b5a45c8fb7
commit c8d8f24bf0
21 changed files with 3408 additions and 14 deletions

View File

@@ -1,2 +1,57 @@
#include "pch.h" #include "pch.h"
#include "XYEegParser64.h" #include "XYEegParser64.h"
#include <vector>
namespace {
constexpr std::uint8_t kFrameHeader = 0xAA;
constexpr std::uint8_t kFrameTail = 0x55;
constexpr std::size_t kReservedByteCount = 6;
} // namespace
std::vector<std::uint8_t> XYEegParser64::SerializeImpedanceCommand(ImpedanceSwitch impedance_switch)
{
std::vector<std::uint8_t> payload(kReservedByteCount, 0);
payload.push_back(static_cast<std::uint8_t>(impedance_switch));
return SerializeCommand(FunctionCode::kImpedance, payload);
}
std::vector<std::uint8_t> XYEegParser64::SerializeGainAndSampleRateCommand(GainSwitch gain,
SampleRateSwitch sample_rate)
{
std::vector<std::uint8_t> payload(kReservedByteCount, 0);
payload.push_back(static_cast<std::uint8_t>(gain));
payload.push_back(static_cast<std::uint8_t>(sample_rate));
return SerializeCommand(FunctionCode::kGainSampleRate, payload);
}
std::vector<std::uint8_t> XYEegParser64::SerializeCommand(FunctionCode function_code,
const std::vector<std::uint8_t>& payload)
{
std::vector<std::uint8_t> command;
command.reserve(1 + 1 + 2 + payload.size() + 1 + 2);
command.push_back(kFrameHeader);
command.push_back(static_cast<std::uint8_t>(function_code));
command.push_back(static_cast<std::uint8_t>((payload.size() >> 8) & 0xFF));
command.push_back(static_cast<std::uint8_t>(payload.size() & 0xFF));
command.insert(command.end(), payload.begin(), payload.end());
command.push_back(Checksum(command, 1, command.size()));
command.push_back(kFrameTail);
command.push_back(kFrameTail);
return command;
}
std::uint8_t XYEegParser64::Checksum(const std::vector<std::uint8_t>& bytes,
std::size_t begin_offset,
std::size_t end_offset_exclusive) noexcept
{
std::uint8_t checksum = 0;
for (std::size_t i = begin_offset; i < end_offset_exclusive && i < bytes.size(); ++i) {
checksum = static_cast<std::uint8_t>(checksum + bytes[i]);
}
return checksum;
}

View File

@@ -7,4 +7,49 @@ using XYEegFrame64 = xyparser::XYEegFrame<64>;
class XYEegParser64 final : public xyparser::XYEegTcpParserCommon<64> { class XYEegParser64 final : public xyparser::XYEegTcpParserCommon<64> {
public: public:
using Frame = XYEegFrame64; using Frame = XYEegFrame64;
enum class FunctionCode : std::uint8_t {
kImpedance = 0x01,
kGainSampleRate = 0x02
};
enum class ImpedanceSwitch : std::uint8_t {
kClose = 0x00,
kOpen = 0x01
};
enum class GainSwitch : std::uint8_t {
k1 = 1,
k2 = 2,
k3 = 3,
k4 = 4,
k6 = 6,
k8 = 8,
k12 = 12,
k24 = 24
};
enum class SampleRateSwitch : std::uint8_t {
k250 = 0,
k500 = 1,
k1000 = 2,
k2000 = 3
};
static constexpr std::size_t kImpedancePayloadSize = 7;
static constexpr std::size_t kGainSampleRatePayloadSize = 8;
static constexpr std::size_t kCommandOverheadSize = 7;
static constexpr std::size_t kImpedanceCommandSize = kImpedancePayloadSize + kCommandOverheadSize;
static constexpr std::size_t kGainSampleRateCommandSize = kGainSampleRatePayloadSize + kCommandOverheadSize;
static std::vector<std::uint8_t> SerializeImpedanceCommand(ImpedanceSwitch impedance_switch);
static std::vector<std::uint8_t> SerializeGainAndSampleRateCommand(GainSwitch gain,
SampleRateSwitch sample_rate);
private:
static std::vector<std::uint8_t> SerializeCommand(FunctionCode function_code,
const std::vector<std::uint8_t>& payload);
static std::uint8_t Checksum(const std::vector<std::uint8_t>& bytes,
std::size_t begin_offset,
std::size_t end_offset_exclusive) noexcept;
}; };

View File

@@ -115,3 +115,65 @@ void MinimalExampleFor64ChParser()
XYParser_DestroyParser(parser); XYParser_DestroyParser(parser);
} }
void MinimalExampleFor64ChControlCommand()
{
std::array<std::uint8_t, 32> impedance_cmd{};
std::array<std::uint8_t, 32> setting_cmd{};
const int impedance_size = XYParser_Serialize64ImpedanceCommand(
1, impedance_cmd.data(), impedance_cmd.size());
const int setting_size = XYParser_Serialize64GainSampleRateCommand(
24, 1000, setting_cmd.data(), setting_cmd.size());
(void)impedance_size;
(void)setting_size;
}
void MinimalExampleFor8ChImpedanceCommand()
{
std::array<std::uint8_t, 7> command{};
const int command_size = XYParser_Serialize8ChImpedanceCommand(
1,
command.data(),
command.size());
if (command_size == static_cast<int>(command.size())) {
const std::uint8_t first_byte = command.front();
const std::uint8_t payload = command[3];
(void)first_byte;
(void)payload;
}
}
void MinimalExampleForImpedanceOutput()
{
XYParserHandle parser = XYParser_CreateParser(8);
if (parser == nullptr) {
return;
}
XYParser_SetBypassChecksum(parser, 1);
XYParser_SetSampleRate(parser, 250);
XYParser_SetImpedanceDetection(parser, 1);
const std::vector<std::uint8_t> raw_bytes = BuildMinimalFrame(8);
std::array<XYParserFrameSummary, 1> frame_summaries{};
std::array<XYParserImpedanceSummary, 1> impedance_summaries{};
XYParser_Feed(parser,
raw_bytes.data(),
raw_bytes.size(),
frame_summaries.data(),
static_cast<int>(frame_summaries.size()));
const int impedance_count = XYParser_ReadImpedance(parser,
impedance_summaries.data(),
static_cast<int>(impedance_summaries.size()));
if (impedance_count > 0) {
const std::uint16_t po5_impedance = impedance_summaries.front().impedance_values[LeadChannel_PO5];
(void)po5_impedance;
}
XYParser_DestroyParser(parser);
}

View File

@@ -0,0 +1,216 @@
#include "pch.h"
#include "XYImpedanceProcessor.h"
#include "third_party/kissfft/kiss_fftr.h"
#include "third_party/kissfft/window_functions.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <vector>
namespace {
constexpr int kDefaultSampleRate = 250;
constexpr int k8ChLeadCount = 8;
constexpr int k64ChLeadCount = 64;
constexpr std::array<XYParserLeadChannelNumber, k8ChLeadCount> k8ChLeadMap = {
LeadChannel_PO5, LeadChannel_POZ, LeadChannel_PO6, LeadChannel_PO7,
LeadChannel_O1, LeadChannel_OZ, LeadChannel_O2, LeadChannel_PO8};
constexpr std::array<XYParserLeadChannelNumber, k64ChLeadCount> k64ChLeadMap = {
LeadChannel_FP1, LeadChannel_FP2, LeadChannel_PO6, LeadChannel_POZ,
LeadChannel_F3, LeadChannel_F4, LeadChannel_FPZ, LeadChannel_AF4,
LeadChannel_FC3, LeadChannel_PO8, LeadChannel_CP2, LeadChannel_CP1,
LeadChannel_FCZ, LeadChannel_PO5, LeadChannel_FC2, LeadChannel_FC1,
LeadChannel_C3, LeadChannel_C4, LeadChannel_FC4, LeadChannel_CP4,
LeadChannel_P3, LeadChannel_P4, LeadChannel_F5, LeadChannel_C5,
LeadChannel_F6, LeadChannel_PO4, LeadChannel_CP6, LeadChannel_CP5,
LeadChannel_PO3, LeadChannel_CP3, LeadChannel_FC6, LeadChannel_FC5,
LeadChannel_CB1, LeadChannel_CB2, LeadChannel_P5, LeadChannel_AF7,
LeadChannel_A1, LeadChannel_T7, LeadChannel_FT7, LeadChannel_TP7,
LeadChannel_FT8, LeadChannel_AF8, LeadChannel_F8, LeadChannel_F7,
LeadChannel_P6, LeadChannel_C6, LeadChannel_O2, LeadChannel_O1,
LeadChannel_T8, LeadChannel_P7, LeadChannel_CZ, LeadChannel_PZ,
LeadChannel_P8, LeadChannel_FZ, LeadChannel_OZ, LeadChannel_PO7,
LeadChannel_TP8, LeadChannel_AF3, LeadChannel_C2, LeadChannel_C1,
LeadChannel_P2, LeadChannel_P1, LeadChannel_F2, LeadChannel_F1};
} // namespace
XYImpedanceProcessor::XYImpedanceProcessor(std::uint8_t channel_count)
: channel_count_(channel_count)
{
}
void XYImpedanceProcessor::SetChannelCount(std::uint8_t channel_count)
{
channel_count_ = channel_count;
Reset();
}
void XYImpedanceProcessor::SetSampleRate(int sample_rate)
{
if (sample_rate <= 0) {
return;
}
if (sample_rate_ != sample_rate) {
sample_rate_ = sample_rate;
Reset();
}
}
void XYImpedanceProcessor::SetEnabled(bool enabled)
{
if (enabled_ == enabled) {
return;
}
enabled_ = enabled;
Reset();
}
void XYImpedanceProcessor::Reset()
{
cached_samples_.clear();
pending_results_.clear();
}
void XYImpedanceProcessor::PushFrame(const XYParserFrameSummary& frame)
{
if (!enabled_ || frame.channel_count != channel_count_) {
return;
}
for (std::size_t sample_index = 0; sample_index < frame.sample_count; ++sample_index) {
Sample sample{};
for (std::size_t channel_index = 0; channel_index < frame.channel_count; ++channel_index) {
sample[channel_index] = frame.channel_values_uv[sample_index][channel_index];
}
cached_samples_.push_back(sample);
}
ProcessPendingWindows();
}
int XYImpedanceProcessor::Read(XYParserImpedanceSummary* out_summaries, int max_summaries)
{
if (out_summaries == nullptr || max_summaries <= 0) {
return 0;
}
const int read_count = std::min<int>(static_cast<int>(pending_results_.size()), max_summaries);
for (int i = 0; i < read_count; ++i) {
out_summaries[i] = pending_results_.front();
pending_results_.pop_front();
}
return read_count;
}
void XYImpedanceProcessor::ProcessPendingWindows()
{
const int effective_sample_rate = sample_rate_ > 0 ? sample_rate_ : 1;
const std::size_t window_sample_count = static_cast<std::size_t>(effective_sample_rate);
while (cached_samples_.size() >= window_sample_count) {
pending_results_.push_back(BuildSummaryFromWindow());
cached_samples_.erase(cached_samples_.begin(), cached_samples_.begin() + static_cast<std::ptrdiff_t>(window_sample_count));
}
}
XYParserImpedanceSummary XYImpedanceProcessor::BuildSummaryFromWindow() const
{
XYParserImpedanceSummary summary{};
summary.channel_count = channel_count_;
summary.sample_rate = static_cast<std::uint32_t>(sample_rate_ > 0 ? sample_rate_ : kDefaultSampleRate);
summary.window_sample_count = summary.sample_rate;
const auto fill_channel = [&](std::size_t channel_index, XYParserLeadChannelNumber lead) {
summary.impedance_values[static_cast<std::size_t>(lead)] = EstimateImpedanceValue(channel_index);
};
if (channel_count_ == 8) {
for (std::size_t i = 0; i < k8ChLeadMap.size(); ++i) {
fill_channel(i, k8ChLeadMap[i]);
}
return summary;
}
for (std::size_t i = 0; i < std::min<std::size_t>(channel_count_, k64ChLeadMap.size()); ++i) {
fill_channel(i, k64ChLeadMap[i]);
}
return summary;
}
std::uint16_t XYImpedanceProcessor::EstimateImpedanceValue(std::size_t channel_index) const
{
const int effective_sample_rate = sample_rate_ > 0 ? sample_rate_ : 1;
const int cached_sample_count = static_cast<int>(cached_samples_.size());
const int raw_sample_count = cached_sample_count < effective_sample_rate ? cached_sample_count : effective_sample_rate;
if (raw_sample_count < 2 || channel_index >= channel_count_) {
return 0;
}
const int fft_size = (raw_sample_count % 2 == 0) ? raw_sample_count : (raw_sample_count - 1);
if (fft_size < 2) {
return 0;
}
kiss_fftr_cfg cfg = kiss_fftr_alloc(fft_size, 0, nullptr, nullptr);
if (cfg == nullptr) {
return 0;
}
std::vector<kiss_fft_scalar> time_data(static_cast<std::size_t>(fft_size), 0.0);
std::vector<double> window(static_cast<std::size_t>(fft_size), 0.0);
hanning_function(fft_size, window.data());
double window_sum = 0.0;
for (int i = 0; i < fft_size; ++i) {
time_data[static_cast<std::size_t>(i)] = cached_samples_[static_cast<std::size_t>(i)][channel_index] * window[static_cast<std::size_t>(i)];
window_sum += window[static_cast<std::size_t>(i)];
}
if (window_sum <= 0.0) {
kiss_fftr_free(cfg);
return 0;
}
std::vector<kiss_fft_cpx> freq_data(static_cast<std::size_t>(fft_size / 2 + 1));
kiss_fftr(cfg, time_data.data(), freq_data.data());
kiss_fftr_free(cfg);
const double sample_rate = sample_rate_ > 0 ? static_cast<double>(sample_rate_) : static_cast<double>(kDefaultSampleRate);
const double freq_resolution = sample_rate / static_cast<double>(fft_size);
constexpr double kLineNoiseFreq = 50.0;
constexpr double kLineNoiseHarmonicFreq = 100.0;
constexpr double kRejectHalfBand = 5.0;
constexpr double kImpedanceScale = 0.0008;
double max_magnitude = 0.0;
for (std::size_t k = 1; k < freq_data.size(); ++k) {
const double frequency = static_cast<double>(k) * freq_resolution;
if (std::abs(frequency - kLineNoiseFreq) <= kRejectHalfBand ||
std::abs(frequency - kLineNoiseHarmonicFreq) <= kRejectHalfBand) {
continue;
}
const double real = freq_data[k].r;
const double imag = freq_data[k].i;
double magnitude = std::sqrt(real * real + imag * imag);
if (k == freq_data.size() - 1 && (fft_size % 2 == 0)) {
magnitude /= window_sum;
} else {
magnitude *= 2.0 / window_sum;
}
if (magnitude > max_magnitude) {
max_magnitude = magnitude;
}
}
const double scaled_value = std::round(max_magnitude) * kImpedanceScale;
const long rounded_value = std::lround(scaled_value);
const long clamped_value = rounded_value < 0 ? 0 : (rounded_value > 65535 ? 65535 : rounded_value);
return static_cast<std::uint16_t>(clamped_value);
}

View File

@@ -0,0 +1,34 @@
#pragma once
#include "XYParserApi.h"
#include <array>
#include <cstdint>
#include <deque>
class XYImpedanceProcessor {
public:
explicit XYImpedanceProcessor(std::uint8_t channel_count = 0);
void SetChannelCount(std::uint8_t channel_count);
void SetSampleRate(int sample_rate);
void SetEnabled(bool enabled);
void Reset();
void PushFrame(const XYParserFrameSummary& frame);
int Read(XYParserImpedanceSummary* out_summaries, int max_summaries);
private:
using Sample = std::array<double, XYPARSER_MAX_CHANNELS>;
void ProcessPendingWindows();
XYParserImpedanceSummary BuildSummaryFromWindow() const;
std::uint16_t EstimateImpedanceValue(std::size_t channel_index) const;
private:
std::uint8_t channel_count_ = 0;
int sample_rate_ = 250;
bool enabled_ = false;
std::deque<Sample> cached_samples_;
std::deque<XYParserImpedanceSummary> pending_results_;
};

View File

@@ -137,7 +137,15 @@
<ItemGroup> <ItemGroup>
<ClInclude Include="framework.h" /> <ClInclude Include="framework.h" />
<ClInclude Include="pch.h" /> <ClInclude Include="pch.h" />
<ClInclude Include="third_party\kissfft\fir_filter.h" />
<ClInclude Include="third_party\kissfft\_kiss_fft_guts.h" />
<ClInclude Include="third_party\kissfft\kiss_fft.h" />
<ClInclude Include="third_party\kissfft\kiss_fft_log.h" />
<ClInclude Include="third_party\kissfft\kiss_fftr.h" />
<ClInclude Include="third_party\kissfft\window_functions.h" />
<ClInclude Include="XYImpedanceProcessor.h" />
<ClInclude Include="XYParserApi.h" /> <ClInclude Include="XYParserApi.h" />
<ClInclude Include="XYWelchProcessor.h" />
<ClInclude Include="XYEegParser8.h" /> <ClInclude Include="XYEegParser8.h" />
<ClInclude Include="XYEegParser64.h" /> <ClInclude Include="XYEegParser64.h" />
<ClInclude Include="XYEegParserCommon.h" /> <ClInclude Include="XYEegParserCommon.h" />
@@ -150,7 +158,27 @@
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile> </ClCompile>
<ClCompile Include="third_party\kissfft\fir_filter.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="third_party\kissfft\kiss_fft.c">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="third_party\kissfft\kiss_fftr.c">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="XYImpedanceProcessor.cpp" />
<ClCompile Include="XYParserApi.cpp" /> <ClCompile Include="XYParserApi.cpp" />
<ClCompile Include="XYWelchProcessor.cpp" />
<ClCompile Include="XYEegParser8.cpp" /> <ClCompile Include="XYEegParser8.cpp" />
<ClCompile Include="XYEegParser64.cpp" /> <ClCompile Include="XYEegParser64.cpp" />
<ClCompile Include="XYEegParserExample.cpp" /> <ClCompile Include="XYEegParserExample.cpp" />

View File

@@ -21,9 +21,33 @@
<ClInclude Include="pch.h"> <ClInclude Include="pch.h">
<Filter>头文件</Filter> <Filter>头文件</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="third_party\kissfft\fir_filter.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="third_party\kissfft\_kiss_fft_guts.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="third_party\kissfft\kiss_fft.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="third_party\kissfft\kiss_fft_log.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="third_party\kissfft\kiss_fftr.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="third_party\kissfft\window_functions.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="XYImpedanceProcessor.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="XYParserApi.h"> <ClInclude Include="XYParserApi.h">
<Filter>头文件</Filter> <Filter>头文件</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="XYWelchProcessor.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="XYEegParser8.h"> <ClInclude Include="XYEegParser8.h">
<Filter>头文件</Filter> <Filter>头文件</Filter>
</ClInclude> </ClInclude>
@@ -41,9 +65,24 @@
<ClCompile Include="pch.cpp"> <ClCompile Include="pch.cpp">
<Filter>源文件</Filter> <Filter>源文件</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="third_party\kissfft\fir_filter.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="third_party\kissfft\kiss_fft.c">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="third_party\kissfft\kiss_fftr.c">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="XYImpedanceProcessor.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="XYParserApi.cpp"> <ClCompile Include="XYParserApi.cpp">
<Filter>源文件</Filter> <Filter>源文件</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="XYWelchProcessor.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="XYEegParser8.cpp"> <ClCompile Include="XYEegParser8.cpp">
<Filter>源文件</Filter> <Filter>源文件</Filter>
</ClCompile> </ClCompile>

View File

@@ -1,23 +1,106 @@
#include "pch.h" #include "pch.h"
#include "XYParserApi.h" #include "XYParserApi.h"
#include "XYImpedanceProcessor.h"
#include "XYWelchProcessor.h"
#include "XYEegParser8.h" #include "XYEegParser8.h"
#include "XYEegParser64.h" #include "XYEegParser64.h"
#include <algorithm> #include <algorithm>
#include <array>
#include <cstring>
#include <new> #include <new>
#include <string> #include <string>
#include <vector> #include <vector>
namespace { namespace {
constexpr std::uint8_t kCommandFrameHeader = 0xAA;
constexpr std::uint8_t kCommandFrameTail = 0x55;
constexpr std::size_t k8ChImpedanceCommandSize = 7;
constexpr int k8ChLeadCount = 8;
constexpr int k64ChLeadCount = 64;
constexpr std::uint8_t kAlgorithmChannelCount = 64;
constexpr std::size_t kAlgorithmSampleByteCount =
static_cast<std::size_t>(XYPARSER_FRAME_DATA_COLUMN_COUNT) * sizeof(double);
struct AlgorithmSample {
std::array<double, XYPARSER_MAX_CHANNELS> channel_values_uv{};
std::uint8_t trigger_type = 0;
std::uint8_t trigger_index = 0;
};
std::uint8_t CalculateChecksum(const std::uint8_t* data, std::size_t size)
{
std::uint8_t checksum = 0;
for (std::size_t i = 0; i < size; ++i) {
checksum = static_cast<std::uint8_t>(checksum + data[i]);
}
return checksum;
}
std::array<std::uint8_t, k8ChImpedanceCommandSize> Build8ChImpedanceCommand(bool open)
{
const std::uint8_t payload = open ? XYPARSER_8CH_IMPEDANCE_OPEN : XYPARSER_8CH_IMPEDANCE_CLOSE;
return {kCommandFrameHeader,
0x00,
0x01,
payload,
CalculateChecksum(&payload, 1),
kCommandFrameTail,
kCommandFrameTail};
}
constexpr std::array<XYParserLeadChannelNumber, k8ChLeadCount> k8ChLeadMap = {
LeadChannel_PO5, LeadChannel_POZ, LeadChannel_PO6, LeadChannel_PO7,
LeadChannel_O1, LeadChannel_OZ, LeadChannel_O2, LeadChannel_PO8};
constexpr std::array<XYParserLeadChannelNumber, k64ChLeadCount> k64ChLeadMap = {
LeadChannel_FP1, LeadChannel_FP2, LeadChannel_PO6, LeadChannel_POZ,
LeadChannel_F3, LeadChannel_F4, LeadChannel_FPZ, LeadChannel_AF4,
LeadChannel_FC3, LeadChannel_PO8, LeadChannel_CP2, LeadChannel_CP1,
LeadChannel_FCZ, LeadChannel_PO5, LeadChannel_FC2, LeadChannel_FC1,
LeadChannel_C3, LeadChannel_C4, LeadChannel_FC4, LeadChannel_CP4,
LeadChannel_P3, LeadChannel_P4, LeadChannel_F5, LeadChannel_C5,
LeadChannel_F6, LeadChannel_PO4, LeadChannel_CP6, LeadChannel_CP5,
LeadChannel_PO3, LeadChannel_CP3, LeadChannel_FC6, LeadChannel_FC5,
LeadChannel_CB1, LeadChannel_CB2, LeadChannel_P5, LeadChannel_AF7,
LeadChannel_A1, LeadChannel_T7, LeadChannel_FT7, LeadChannel_TP7,
LeadChannel_FT8, LeadChannel_AF8, LeadChannel_F8, LeadChannel_F7,
LeadChannel_P6, LeadChannel_C6, LeadChannel_O2, LeadChannel_O1,
LeadChannel_T8, LeadChannel_P7, LeadChannel_CZ, LeadChannel_PZ,
LeadChannel_P8, LeadChannel_FZ, LeadChannel_OZ, LeadChannel_PO7,
LeadChannel_TP8, LeadChannel_AF3, LeadChannel_C2, LeadChannel_C1,
LeadChannel_P2, LeadChannel_P1, LeadChannel_F2, LeadChannel_F1};
struct ParserContext { struct ParserContext {
std::uint8_t channel_count = 0; std::uint8_t channel_count = 0;
XYEegParser8 parser8; XYEegParser8 parser8;
XYEegParser64 parser64; XYEegParser64 parser64;
XYImpedanceProcessor impedance_processor;
XYWelchProcessor welch_processor;
double adc_vref = 4.5;
double adc_gain = 6.0;
std::vector<std::uint8_t> algorithm_byte_cache;
std::vector<AlgorithmSample> algorithm_sample_cache;
std::uint32_t next_algorithm_frame_index = 1;
std::string last_error; std::string last_error;
}; };
struct WelchBandInfo {
const char* name;
double low_hz;
double high_hz;
};
constexpr std::array<WelchBandInfo, XYPARSER_WELCH_BAND_COUNT> kWelchBandInfos = {{
{"delta", 0.5, 4.0},
{"theta", 4.0, 8.0},
{"alpha", 8.0, 13.0},
{"beta", 13.0, 30.0},
{"gamma", 30.0, 50.0},
}};
void FillSummary(const XYEegFrame8& frame, XYParserFrameSummary& summary) void FillSummary(const XYEegFrame8& frame, XYParserFrameSummary& summary)
{ {
summary.frame_index = frame.index; summary.frame_index = frame.index;
@@ -25,8 +108,8 @@ void FillSummary(const XYEegFrame8& frame, XYParserFrameSummary& summary)
summary.battery = frame.battery; summary.battery = frame.battery;
summary.sample_count = static_cast<std::uint8_t>(frame.samples.size()); summary.sample_count = static_cast<std::uint8_t>(frame.samples.size());
for (std::size_t sample_index = 0; sample_index < XYPARSER_SAMPLES_PER_FRAME; ++sample_index) { for (std::size_t sample_index = 0; sample_index < XYPARSER_SAMPLES_PER_FRAME; ++sample_index) {
summary.trigger_types[sample_index] = frame.samples[sample_index].trigger_type; summary.sample_trigger_types[sample_index] = frame.samples[sample_index].trigger_type;
summary.trigger_indices[sample_index] = frame.samples[sample_index].trigger_index; summary.sample_trigger_indices[sample_index] = frame.samples[sample_index].trigger_index;
for (std::size_t channel_index = 0; channel_index < XYPARSER_MAX_CHANNELS; ++channel_index) { for (std::size_t channel_index = 0; channel_index < XYPARSER_MAX_CHANNELS; ++channel_index) {
summary.channel_values_uv[sample_index][channel_index] = summary.channel_values_uv[sample_index][channel_index] =
channel_index < frame.channel_count channel_index < frame.channel_count
@@ -43,8 +126,8 @@ void FillSummary(const XYEegFrame64& frame, XYParserFrameSummary& summary)
summary.battery = frame.battery; summary.battery = frame.battery;
summary.sample_count = static_cast<std::uint8_t>(frame.samples.size()); summary.sample_count = static_cast<std::uint8_t>(frame.samples.size());
for (std::size_t sample_index = 0; sample_index < XYPARSER_SAMPLES_PER_FRAME; ++sample_index) { for (std::size_t sample_index = 0; sample_index < XYPARSER_SAMPLES_PER_FRAME; ++sample_index) {
summary.trigger_types[sample_index] = frame.samples[sample_index].trigger_type; summary.sample_trigger_types[sample_index] = frame.samples[sample_index].trigger_type;
summary.trigger_indices[sample_index] = frame.samples[sample_index].trigger_index; summary.sample_trigger_indices[sample_index] = frame.samples[sample_index].trigger_index;
for (std::size_t channel_index = 0; channel_index < XYPARSER_MAX_CHANNELS; ++channel_index) { for (std::size_t channel_index = 0; channel_index < XYPARSER_MAX_CHANNELS; ++channel_index) {
summary.channel_values_uv[sample_index][channel_index] = summary.channel_values_uv[sample_index][channel_index] =
channel_index < frame.channel_count channel_index < frame.channel_count
@@ -54,6 +137,143 @@ void FillSummary(const XYEegFrame64& frame, XYParserFrameSummary& summary)
} }
} }
void Convert8ChSummaryTo64ChSummary(const XYParserFrameSummary& input_summary,
XYParserFrameSummary& output_summary)
{
std::memset(&output_summary, 0, sizeof(output_summary));
output_summary.frame_index = input_summary.frame_index;
output_summary.channel_count = 64;
output_summary.battery = input_summary.battery;
output_summary.sample_count = input_summary.sample_count;
for (std::size_t sample_index = 0; sample_index < XYPARSER_SAMPLES_PER_FRAME; ++sample_index) {
output_summary.sample_trigger_types[sample_index] = input_summary.sample_trigger_types[sample_index];
output_summary.sample_trigger_indices[sample_index] = input_summary.sample_trigger_indices[sample_index];
for (std::size_t eight_channel_index = 0; eight_channel_index < k8ChLeadMap.size(); ++eight_channel_index) {
const XYParserLeadChannelNumber lead = k8ChLeadMap[eight_channel_index];
output_summary.channel_values_uv[sample_index][static_cast<std::size_t>(lead)] =
input_summary.channel_values_uv[sample_index][eight_channel_index];
}
}
}
void Convert64ChSummaryToAlgorithmData(const XYParserFrameSummary& input_summary,
double* output_data)
{
std::fill(output_data,
output_data + XYPARSER_FRAME_ALGORITHM_VALUE_COUNT,
0.0);
for (std::size_t sample_index = 0; sample_index < XYPARSER_SAMPLES_PER_FRAME; ++sample_index) {
const std::size_t sample_offset = sample_index * XYPARSER_FRAME_DATA_COLUMN_COUNT;
for (std::size_t channel_index = 0; channel_index < XYPARSER_MAX_CHANNELS; ++channel_index) {
output_data[sample_offset + channel_index] =
input_summary.channel_values_uv[sample_index][channel_index];
}
output_data[sample_offset + XYPARSER_FRAME_DATA_TRIGGER_TYPE_INDEX] =
static_cast<double>(input_summary.sample_trigger_types[sample_index]);
output_data[sample_offset + XYPARSER_FRAME_DATA_TRIGGER_INDEX_INDEX] =
static_cast<double>(input_summary.sample_trigger_indices[sample_index]);
}
}
void FillSummaryFromAlgorithmSamples(const std::vector<AlgorithmSample>& samples,
std::uint32_t frame_index,
XYParserFrameSummary& summary)
{
std::memset(&summary, 0, sizeof(summary));
summary.frame_index = frame_index;
summary.channel_count = kAlgorithmChannelCount;
summary.sample_count = static_cast<std::uint8_t>(
std::min<std::size_t>(samples.size(), static_cast<std::size_t>(XYPARSER_SAMPLES_PER_FRAME)));
for (std::size_t sample_index = 0; sample_index < samples.size() &&
sample_index < static_cast<std::size_t>(XYPARSER_SAMPLES_PER_FRAME);
++sample_index) {
summary.sample_trigger_types[sample_index] = samples[sample_index].trigger_type;
summary.sample_trigger_indices[sample_index] = samples[sample_index].trigger_index;
for (std::size_t channel_index = 0; channel_index < XYPARSER_MAX_CHANNELS; ++channel_index) {
summary.channel_values_uv[sample_index][channel_index] = samples[sample_index].channel_values_uv[channel_index];
}
}
}
bool CopyCommandBytes(const std::vector<std::uint8_t>& command,
std::uint8_t* out_buffer,
std::size_t buffer_size)
{
if (out_buffer == nullptr || buffer_size < command.size()) {
return false;
}
std::memcpy(out_buffer, command.data(), command.size());
return true;
}
bool TryMapSampleRate(std::uint16_t sample_rate, XYEegParser64::SampleRateSwitch& mapped_sample_rate)
{
switch (sample_rate) {
case 250:
mapped_sample_rate = XYEegParser64::SampleRateSwitch::k250;
return true;
case 500:
mapped_sample_rate = XYEegParser64::SampleRateSwitch::k500;
return true;
case 1000:
mapped_sample_rate = XYEegParser64::SampleRateSwitch::k1000;
return true;
case 2000:
mapped_sample_rate = XYEegParser64::SampleRateSwitch::k2000;
return true;
default:
return false;
}
}
bool TryMapGain(std::uint8_t gain, XYEegParser64::GainSwitch& mapped_gain)
{
switch (gain) {
case 1:
mapped_gain = XYEegParser64::GainSwitch::k1;
return true;
case 2:
mapped_gain = XYEegParser64::GainSwitch::k2;
return true;
case 3:
mapped_gain = XYEegParser64::GainSwitch::k3;
return true;
case 4:
mapped_gain = XYEegParser64::GainSwitch::k4;
return true;
case 6:
mapped_gain = XYEegParser64::GainSwitch::k6;
return true;
case 8:
mapped_gain = XYEegParser64::GainSwitch::k8;
return true;
case 12:
mapped_gain = XYEegParser64::GainSwitch::k12;
return true;
case 24:
mapped_gain = XYEegParser64::GainSwitch::k24;
return true;
default:
return false;
}
}
int GetLeadMapCount(std::uint8_t channel_count)
{
switch (channel_count) {
case 8:
return k8ChLeadCount;
case 64:
return k64ChLeadCount;
default:
return 0;
}
}
} // namespace } // namespace
extern "C" { extern "C" {
@@ -70,6 +290,8 @@ XYParserHandle XYParser_CreateParser(std::uint8_t channel_count)
} }
context->channel_count = channel_count; context->channel_count = channel_count;
context->impedance_processor.SetChannelCount(channel_count);
context->welch_processor.SetChannelCount(channel_count);
return context; return context;
} }
@@ -86,6 +308,13 @@ void XYParser_SetAdcParams(XYParserHandle handle, double vref, double gain)
return; return;
} }
if (vref > 0.0) {
context->adc_vref = vref;
}
if (gain > 0.0) {
context->adc_gain = gain;
}
if (context->channel_count == 8) { if (context->channel_count == 8) {
context->parser8.SetAdcParams(vref, gain); context->parser8.SetAdcParams(vref, gain);
} else { } else {
@@ -108,6 +337,87 @@ void XYParser_SetBypassChecksum(XYParserHandle handle, int bypass)
} }
} }
void XYParser_SetSampleRate(XYParserHandle handle, int sample_rate)
{
ParserContext* context = static_cast<ParserContext*>(handle);
if (context == nullptr) {
return;
}
context->impedance_processor.SetSampleRate(sample_rate);
context->welch_processor.SetSampleRate(sample_rate);
}
void XYParser_SetImpedanceDetection(XYParserHandle handle, int enabled)
{
ParserContext* context = static_cast<ParserContext*>(handle);
if (context == nullptr) {
return;
}
const bool impedance_enabled = enabled != 0;
const double impedance_gain = impedance_enabled ? 24.0 : 6.0;
context->adc_gain = impedance_gain;
if (context->channel_count == 8) {
context->parser8.SetAdcParams(context->adc_vref, impedance_gain);
} else {
context->parser64.SetAdcParams(context->adc_vref, impedance_gain);
}
context->impedance_processor.SetEnabled(impedance_enabled);
}
void XYParser_SetWelchDetection(XYParserHandle handle, int enabled)
{
ParserContext* context = static_cast<ParserContext*>(handle);
if (context == nullptr) {
return;
}
context->welch_processor.SetEnabled(enabled != 0);
}
std::size_t XYParser_Get64ImpedanceCommandSize(void)
{
return XYEegParser64::kImpedanceCommandSize;
}
std::size_t XYParser_Get64GainSampleRateCommandSize(void)
{
return XYEegParser64::kGainSampleRateCommandSize;
}
int XYParser_Serialize64ImpedanceCommand(int open,
std::uint8_t* out_buffer,
std::size_t buffer_size)
{
const auto command = XYEegParser64::SerializeImpedanceCommand(
open != 0 ? XYEegParser64::ImpedanceSwitch::kOpen : XYEegParser64::ImpedanceSwitch::kClose);
if (!CopyCommandBytes(command, out_buffer, buffer_size)) {
return 0;
}
return static_cast<int>(command.size());
}
int XYParser_Serialize64GainSampleRateCommand(std::uint8_t gain,
std::uint16_t sample_rate,
std::uint8_t* out_buffer,
std::size_t buffer_size)
{
XYEegParser64::GainSwitch mapped_gain{};
XYEegParser64::SampleRateSwitch mapped_sample_rate{};
if (!TryMapGain(gain, mapped_gain) || !TryMapSampleRate(sample_rate, mapped_sample_rate)) {
return 0;
}
const auto command = XYEegParser64::SerializeGainAndSampleRateCommand(mapped_gain, mapped_sample_rate);
if (!CopyCommandBytes(command, out_buffer, buffer_size)) {
return 0;
}
return static_cast<int>(command.size());
}
int XYParser_Feed(XYParserHandle handle, int XYParser_Feed(XYParserHandle handle,
const std::uint8_t* data, const std::uint8_t* data,
std::size_t size, std::size_t size,
@@ -127,8 +437,13 @@ int XYParser_Feed(XYParserHandle handle,
const std::vector<XYEegFrame8> frames = context->parser8.Feed(data, size); const std::vector<XYEegFrame8> frames = context->parser8.Feed(data, size);
context->last_error = context->parser8.LastError(); context->last_error = context->parser8.LastError();
const int write_count = std::min<int>(static_cast<int>(frames.size()), max_summaries); const int write_count = std::min<int>(static_cast<int>(frames.size()), max_summaries);
for (int i = 0; i < write_count; ++i) { for (std::size_t i = 0; i < frames.size(); ++i) {
FillSummary(frames[static_cast<std::size_t>(i)], out_summaries[i]); XYParserFrameSummary summary{};
FillSummary(frames[i], summary);
context->impedance_processor.PushFrame(summary);
if (static_cast<int>(i) < write_count) {
out_summaries[static_cast<int>(i)] = summary;
}
} }
return write_count; return write_count;
} }
@@ -136,12 +451,299 @@ int XYParser_Feed(XYParserHandle handle,
const std::vector<XYEegFrame64> frames = context->parser64.Feed(data, size); const std::vector<XYEegFrame64> frames = context->parser64.Feed(data, size);
context->last_error = context->parser64.LastError(); context->last_error = context->parser64.LastError();
const int write_count = std::min<int>(static_cast<int>(frames.size()), max_summaries); const int write_count = std::min<int>(static_cast<int>(frames.size()), max_summaries);
for (int i = 0; i < write_count; ++i) { for (std::size_t i = 0; i < frames.size(); ++i) {
FillSummary(frames[static_cast<std::size_t>(i)], out_summaries[i]); XYParserFrameSummary summary{};
FillSummary(frames[i], summary);
context->impedance_processor.PushFrame(summary);
if (static_cast<int>(i) < write_count) {
out_summaries[static_cast<int>(i)] = summary;
}
} }
return write_count; return write_count;
} }
int XYParser_Convert8ChFramesTo64Ch(const XYParserFrameSummary* input_summaries,
int input_count,
XYParserFrameSummary* output_summaries,
int max_summaries)
{
if (input_summaries == nullptr || output_summaries == nullptr || input_count <= 0 || max_summaries <= 0) {
return 0;
}
const int convert_count = input_count < max_summaries ? input_count : max_summaries;
for (int i = 0; i < convert_count; ++i) {
if (input_summaries[i].channel_count != 8) {
return i;
}
Convert8ChSummaryTo64ChSummary(input_summaries[i], output_summaries[i]);
}
return convert_count;
}
std::size_t XYParser_GetAlgorithmDataValueCount()
{
return XYPARSER_FRAME_ALGORITHM_VALUE_COUNT;
}
int XYParser_ConvertSampleFramesToAlgorithmData(const XYParserFrameSummary* input_summary,
double* output_data)
{
if (input_summary == nullptr || output_data == nullptr) {
return 0;
}
if (input_summary->channel_count != 64) {
return 0;
}
Convert64ChSummaryToAlgorithmData(*input_summary, output_data);
return 1;
}
int XYParser_FeedAlgorithmData(XYParserHandle handle,
const std::uint8_t* data,
std::size_t size,
XYParserFrameSummary* out_summaries,
int max_summaries)
{
ParserContext* context = static_cast<ParserContext*>(handle);
if (context == nullptr || context->channel_count != kAlgorithmChannelCount ||
data == nullptr || size == 0) {
return 0;
}
if (max_summaries < 0) {
max_summaries = 0;
}
if (max_summaries > 0 && out_summaries == nullptr) {
return 0;
}
context->algorithm_byte_cache.insert(context->algorithm_byte_cache.end(), data, data + size);
while (context->algorithm_byte_cache.size() >= kAlgorithmSampleByteCount) {
AlgorithmSample sample{};
std::array<double, XYPARSER_FRAME_DATA_COLUMN_COUNT> row{};
std::memcpy(row.data(), context->algorithm_byte_cache.data(), kAlgorithmSampleByteCount);
for (std::size_t channel_index = 0; channel_index < XYPARSER_MAX_CHANNELS; ++channel_index) {
sample.channel_values_uv[channel_index] = row[channel_index];
}
sample.trigger_type = static_cast<std::uint8_t>(row[XYPARSER_FRAME_DATA_TRIGGER_TYPE_INDEX]);
sample.trigger_index = static_cast<std::uint8_t>(row[XYPARSER_FRAME_DATA_TRIGGER_INDEX_INDEX]);
context->welch_processor.PushSample(sample.channel_values_uv);
context->algorithm_sample_cache.push_back(sample);
context->algorithm_byte_cache.erase(
context->algorithm_byte_cache.begin(),
context->algorithm_byte_cache.begin() + static_cast<std::ptrdiff_t>(kAlgorithmSampleByteCount));
}
int output_count = 0;
while (context->algorithm_sample_cache.size() >= static_cast<std::size_t>(XYPARSER_SAMPLES_PER_FRAME)) {
const std::vector<AlgorithmSample> frame_samples(
context->algorithm_sample_cache.begin(),
context->algorithm_sample_cache.begin() + XYPARSER_SAMPLES_PER_FRAME);
XYParserFrameSummary summary{};
FillSummaryFromAlgorithmSamples(frame_samples, context->next_algorithm_frame_index++, summary);
if (output_count < max_summaries) {
out_summaries[output_count] = summary;
++output_count;
}
context->algorithm_sample_cache.erase(
context->algorithm_sample_cache.begin(),
context->algorithm_sample_cache.begin() + XYPARSER_SAMPLES_PER_FRAME);
}
return output_count;
}
void XYParser_ResetAlgorithmDataCache(XYParserHandle handle)
{
ParserContext* context = static_cast<ParserContext*>(handle);
if (context == nullptr || context->channel_count != kAlgorithmChannelCount) {
return;
}
context->algorithm_byte_cache.clear();
context->algorithm_sample_cache.clear();
context->next_algorithm_frame_index = 1;
}
int XYParser_FlushAlgorithmData(XYParserHandle handle,
XYParserFrameSummary* out_summary)
{
ParserContext* context = static_cast<ParserContext*>(handle);
if (context == nullptr || context->channel_count != kAlgorithmChannelCount ||
out_summary == nullptr || context->algorithm_sample_cache.empty()) {
return 0;
}
FillSummaryFromAlgorithmSamples(
context->algorithm_sample_cache,
context->next_algorithm_frame_index++,
*out_summary);
context->algorithm_sample_cache.clear();
return 1;
}
int XYParser_GetLeadMapCount(std::uint8_t channel_count)
{
return GetLeadMapCount(channel_count);
}
int XYParser_GetLeadMap(std::uint8_t channel_count,
XYParserLeadChannelNumber* out_leads,
int max_leads)
{
const int required_count = GetLeadMapCount(channel_count);
if (required_count <= 0 || out_leads == nullptr || max_leads < required_count) {
return 0;
}
if (channel_count == 8) {
std::copy(k8ChLeadMap.begin(), k8ChLeadMap.end(), out_leads);
return required_count;
}
std::copy(k64ChLeadMap.begin(), k64ChLeadMap.end(), out_leads);
return required_count;
}
const char* XYParser_GetLeadName(XYParserLeadChannelNumber lead)
{
switch (lead) {
case LeadChannel_FP1: return "FP1";
case LeadChannel_FP2: return "FP2";
case LeadChannel_PO6: return "PO6";
case LeadChannel_POZ: return "POZ";
case LeadChannel_F3: return "F3";
case LeadChannel_F4: return "F4";
case LeadChannel_FPZ: return "FPZ";
case LeadChannel_AF4: return "AF4";
case LeadChannel_FC3: return "FC3";
case LeadChannel_PO8: return "PO8";
case LeadChannel_CP2: return "CP2";
case LeadChannel_CP1: return "CP1";
case LeadChannel_FCZ: return "FCZ";
case LeadChannel_PO5: return "PO5";
case LeadChannel_FC2: return "FC2";
case LeadChannel_FC1: return "FC1";
case LeadChannel_C3: return "C3";
case LeadChannel_C4: return "C4";
case LeadChannel_FC4: return "FC4";
case LeadChannel_CP4: return "CP4";
case LeadChannel_P3: return "P3";
case LeadChannel_P4: return "P4";
case LeadChannel_F5: return "F5";
case LeadChannel_C5: return "C5";
case LeadChannel_F6: return "F6";
case LeadChannel_PO4: return "PO4";
case LeadChannel_CP6: return "CP6";
case LeadChannel_CP5: return "CP5";
case LeadChannel_PO3: return "PO3";
case LeadChannel_CP3: return "CP3";
case LeadChannel_FC6: return "FC6";
case LeadChannel_FC5: return "FC5";
case LeadChannel_CB1: return "CB1";
case LeadChannel_CB2: return "CB2";
case LeadChannel_P5: return "P5";
case LeadChannel_AF7: return "AF7";
case LeadChannel_A1: return "A1";
case LeadChannel_T7: return "T7";
case LeadChannel_FT7: return "FT7";
case LeadChannel_TP7: return "TP7";
case LeadChannel_FT8: return "FT8";
case LeadChannel_AF8: return "AF8";
case LeadChannel_F8: return "F8";
case LeadChannel_F7: return "F7";
case LeadChannel_P6: return "P6";
case LeadChannel_C6: return "C6";
case LeadChannel_O2: return "O2";
case LeadChannel_O1: return "O1";
case LeadChannel_T8: return "T8";
case LeadChannel_P7: return "P7";
case LeadChannel_CZ: return "CZ";
case LeadChannel_PZ: return "PZ";
case LeadChannel_P8: return "P8";
case LeadChannel_FZ: return "FZ";
case LeadChannel_OZ: return "OZ";
case LeadChannel_PO7: return "PO7";
case LeadChannel_TP8: return "TP8";
case LeadChannel_AF3: return "AF3";
case LeadChannel_C2: return "C2";
case LeadChannel_C1: return "C1";
case LeadChannel_P2: return "P2";
case LeadChannel_P1: return "P1";
case LeadChannel_F2: return "F2";
case LeadChannel_F1: return "F1";
case LeadChannel_GND: return "GND";
default:
return "";
}
}
const char* XYParser_GetWelchBandName(int band_index)
{
if (band_index < 0 || band_index >= static_cast<int>(kWelchBandInfos.size())) {
return "";
}
return kWelchBandInfos[static_cast<std::size_t>(band_index)].name;
}
int XYParser_GetWelchBandRange(int band_index, double* out_low_hz, double* out_high_hz)
{
if (band_index < 0 || band_index >= static_cast<int>(kWelchBandInfos.size()) ||
out_low_hz == nullptr || out_high_hz == nullptr) {
return 0;
}
const WelchBandInfo& band_info = kWelchBandInfos[static_cast<std::size_t>(band_index)];
*out_low_hz = band_info.low_hz;
*out_high_hz = band_info.high_hz;
return 1;
}
int XYParser_ReadImpedance(XYParserHandle handle,
XYParserImpedanceSummary* out_summaries,
int max_summaries)
{
ParserContext* context = static_cast<ParserContext*>(handle);
if (context == nullptr) {
return 0;
}
return context->impedance_processor.Read(out_summaries, max_summaries);
}
int XYParser_ReadWelch(XYParserHandle handle,
XYParserWelchSummary* out_summaries,
int max_summaries)
{
ParserContext* context = static_cast<ParserContext*>(handle);
if (context == nullptr) {
return 0;
}
return context->welch_processor.Read(out_summaries, max_summaries);
}
std::size_t XYParser_Get8ChImpedanceCommandSize()
{
return k8ChImpedanceCommandSize;
}
int XYParser_Serialize8ChImpedanceCommand(int open,
std::uint8_t* out_command,
std::size_t command_size)
{
if (out_command == nullptr || command_size < k8ChImpedanceCommandSize) {
return 0;
}
const auto command = Build8ChImpedanceCommand(open != 0);
std::copy(command.begin(), command.end(), out_command);
return static_cast<int>(command.size());
}
const char* XYParser_GetLastError(XYParserHandle handle) const char* XYParser_GetLastError(XYParserHandle handle)
{ {
ParserContext* context = static_cast<ParserContext*>(handle); ParserContext* context = static_cast<ParserContext*>(handle);

View File

@@ -1,4 +1,4 @@
#pragma once #pragma once
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
@@ -15,7 +15,87 @@ typedef void* XYParserHandle;
enum { enum {
XYPARSER_MAX_CHANNELS = 64, XYPARSER_MAX_CHANNELS = 64,
XYPARSER_SAMPLES_PER_FRAME = 5 XYPARSER_FRAME_DATA_TRIGGER_TYPE_INDEX = XYPARSER_MAX_CHANNELS,
XYPARSER_FRAME_DATA_TRIGGER_INDEX_INDEX = XYPARSER_MAX_CHANNELS + 1,
XYPARSER_FRAME_DATA_COLUMN_COUNT = XYPARSER_MAX_CHANNELS + 2,
XYPARSER_SAMPLES_PER_FRAME = 5,
XYPARSER_FRAME_ALGORITHM_VALUE_COUNT =
XYPARSER_SAMPLES_PER_FRAME * XYPARSER_FRAME_DATA_COLUMN_COUNT,
XYPARSER_WELCH_MAX_FREQUENCY_COUNT = 50,
XYPARSER_WELCH_BAND_COUNT = 5
};
enum XYParser8ChImpedanceSwitch : std::uint8_t {
XYPARSER_8CH_IMPEDANCE_CLOSE = 0xA0,
XYPARSER_8CH_IMPEDANCE_OPEN = 0xA1
};
enum XYParserLeadChannelNumber {
LeadChannel_FP1 = 0,
LeadChannel_FP2,
LeadChannel_PO6,
LeadChannel_POZ,
LeadChannel_F3,
LeadChannel_F4,
LeadChannel_FPZ,
LeadChannel_AF4,
LeadChannel_FC3,
LeadChannel_PO8,
LeadChannel_CP2,
LeadChannel_CP1,
LeadChannel_FCZ,
LeadChannel_PO5,
LeadChannel_FC2,
LeadChannel_FC1,
LeadChannel_C3,
LeadChannel_C4,
LeadChannel_FC4,
LeadChannel_CP4,
LeadChannel_P3,
LeadChannel_P4,
LeadChannel_F5,
LeadChannel_C5,
LeadChannel_F6,
LeadChannel_PO4,
LeadChannel_CP6,
LeadChannel_CP5,
LeadChannel_PO3,
LeadChannel_CP3,
LeadChannel_FC6,
LeadChannel_FC5,
LeadChannel_CB1,
LeadChannel_CB2,
LeadChannel_P5,
LeadChannel_AF7,
LeadChannel_A1,
LeadChannel_T7,
LeadChannel_FT7,
LeadChannel_TP7,
LeadChannel_FT8,
LeadChannel_AF8,
LeadChannel_F8,
LeadChannel_F7,
LeadChannel_P6,
LeadChannel_C6,
LeadChannel_O2,
LeadChannel_O1,
LeadChannel_T8,
LeadChannel_P7,
LeadChannel_CZ,
LeadChannel_PZ,
LeadChannel_P8,
LeadChannel_FZ,
LeadChannel_OZ,
LeadChannel_PO7,
LeadChannel_TP8,
LeadChannel_AF3,
LeadChannel_C2,
LeadChannel_C1,
LeadChannel_P2,
LeadChannel_P1,
LeadChannel_F2,
LeadChannel_F1,
LeadChannel_GND
}; };
struct XYParserFrameSummary { struct XYParserFrameSummary {
@@ -24,19 +104,228 @@ struct XYParserFrameSummary {
std::uint8_t battery; std::uint8_t battery;
std::uint8_t sample_count; std::uint8_t sample_count;
double channel_values_uv[XYPARSER_SAMPLES_PER_FRAME][XYPARSER_MAX_CHANNELS]; double channel_values_uv[XYPARSER_SAMPLES_PER_FRAME][XYPARSER_MAX_CHANNELS];
std::uint8_t trigger_types[XYPARSER_SAMPLES_PER_FRAME]; std::uint8_t sample_trigger_types[XYPARSER_SAMPLES_PER_FRAME];
std::uint8_t trigger_indices[XYPARSER_SAMPLES_PER_FRAME]; std::uint8_t sample_trigger_indices[XYPARSER_SAMPLES_PER_FRAME];
}; };
struct XYParserImpedanceSummary {
std::uint8_t channel_count;
std::uint32_t sample_rate;
std::uint32_t window_sample_count;
std::uint16_t impedance_values[XYPARSER_MAX_CHANNELS];
};
struct XYParserWelchSummary {
std::uint8_t ok;
std::uint8_t channel_count;
std::uint32_t sample_rate;
std::uint32_t window_sample_count;
std::uint32_t frequency_count;
double frequencies[XYPARSER_WELCH_MAX_FREQUENCY_COUNT];
double psd_values[XYPARSER_MAX_CHANNELS][XYPARSER_WELCH_MAX_FREQUENCY_COUNT];
double band_values[XYPARSER_WELCH_BAND_COUNT][XYPARSER_MAX_CHANNELS];
};
// 创建解析器实例。
// @param channel_count 解析器支持的导联数。
// @return 创建成功时返回解析器句柄,失败时返回 nullptr。
XYPARSER_API XYParserHandle XYParser_CreateParser(std::uint8_t channel_count); XYPARSER_API XYParserHandle XYParser_CreateParser(std::uint8_t channel_count);
// 销毁解析器实例并释放相关资源。
// @param handle 要销毁的解析器句柄。
// @return 无返回值。
XYPARSER_API void XYParser_DestroyParser(XYParserHandle handle); XYPARSER_API void XYParser_DestroyParser(XYParserHandle handle);
// 设置 ADC 参考电压和增益参数。
// @param handle 目标解析器句柄。
// @param vref ADC 的参考电压。
// @param gain ADC 的增益值。
// @return 无返回值。
XYPARSER_API void XYParser_SetAdcParams(XYParserHandle handle, double vref, double gain); XYPARSER_API void XYParser_SetAdcParams(XYParserHandle handle, double vref, double gain);
// 设置是否跳过校验和检查。
// @param handle 目标解析器句柄。
// @param bypass 非 0 表示跳过校验和检查0 表示执行校验和检查。
// @return 无返回值。
XYPARSER_API void XYParser_SetBypassChecksum(XYParserHandle handle, int bypass); XYPARSER_API void XYParser_SetBypassChecksum(XYParserHandle handle, int bypass);
// 设置解析器使用的采样率。
// @param handle 目标解析器句柄。
// @param sample_rate 采样率,单位为 Hz。
// @return 无返回值。
XYPARSER_API void XYParser_SetSampleRate(XYParserHandle handle, int sample_rate);
// Frame parsing and algorithm result reading.
// 向解析器输入原始数据并读取解析得到的帧。
// @param handle 目标解析器句柄。
// @param data 输入的原始字节数据。
// @param size 输入数据的字节数。
// @param out_summaries 输出帧数组。
// @param max_summaries 输出数组可写入的最大帧数,用于避免越界写入。
// @return 本次实际输出的帧数;如果参数无效则返回 0。
XYPARSER_API int XYParser_Feed(XYParserHandle handle, XYPARSER_API int XYParser_Feed(XYParserHandle handle,
const std::uint8_t* data, const std::uint8_t* data,
std::size_t size, std::size_t size,
XYParserFrameSummary* out_summaries, XYParserFrameSummary* out_summaries,
int max_summaries); int max_summaries);
// 设置是否启用阻抗检测。
// @param handle 目标解析器句柄。
// @param enabled 非 0 表示启用阻抗检测0 表示禁用阻抗检测。
// @return 无返回值。
XYPARSER_API void XYParser_SetImpedanceDetection(XYParserHandle handle, int enabled);
// 读取当前可用的阻抗结果。
// @param handle 目标解析器句柄。
// @param out_summaries 输出的阻抗结果数组。
// @param max_summaries 输出数组可写入的最大结果数,用于避免越界写入。
// @return 本次实际读取的结果数;如果参数无效则返回 0。
XYPARSER_API int XYParser_ReadImpedance(XYParserHandle handle,
XYParserImpedanceSummary* out_summaries,
int max_summaries);
// 设置是否启用基于算法数据的 Welch 检测。
// @param handle 目标解析器句柄。
// @param enabled 非 0 表示启用 Welch 检测0 表示禁用 Welch 检测。
// @return 无返回值。
XYPARSER_API void XYParser_SetWelchDetection(XYParserHandle handle, int enabled);
// 读取当前基于算法数据计算得到的 Welch 结果。
// @param handle 目标解析器句柄。
// @param out_summaries 输出的 Welch 结果数组。
// @param max_summaries 输出数组可写入的最大结果数,用于避免越界写入。
// @return 本次实际读取的结果数;如果参数无效则返回 0。
XYPARSER_API int XYParser_ReadWelch(XYParserHandle handle,
XYParserWelchSummary* out_summaries,
int max_summaries);
// Command serialization.
// 获取 8 导阻抗命令的字节长度。
// @return 8 导阻抗命令的字节长度。
XYPARSER_API std::size_t XYParser_Get8ChImpedanceCommandSize();
// 生成 8 导阻抗开关命令。
// @param open 非 0 表示打开阻抗检测0 表示关闭阻抗检测。
// @param out_command 输出的命令缓冲区。
// @param command_size 输出缓冲区大小,单位为字节。
// @return 生成成功时返回写入的字节数,失败时返回 0。
XYPARSER_API int XYParser_Serialize8ChImpedanceCommand(int open,
std::uint8_t* out_command,
std::size_t command_size);
// 获取 64 导阻抗命令的字节长度。
// @return 64 导阻抗命令的字节长度。
XYPARSER_API std::size_t XYParser_Get64ImpedanceCommandSize(void);
// 生成 64 导阻抗开关命令。
// @param open 非 0 表示打开阻抗检测0 表示关闭阻抗检测。
// @param out_buffer 输出的命令缓冲区。
// @param buffer_size 输出缓冲区大小,单位为字节。
// @return 生成成功时返回写入的字节数,失败时返回 0。
XYPARSER_API int XYParser_Serialize64ImpedanceCommand(int open,
std::uint8_t* out_buffer,
std::size_t buffer_size);
// 获取 64 导增益和采样率命令的字节长度。
// @return 64 导增益和采样率命令的字节长度。
XYPARSER_API std::size_t XYParser_Get64GainSampleRateCommandSize(void);
// 生成 64 导增益和采样率设置命令。
// @param gain 要设置的增益值。
// @param sample_rate 要设置的采样率。
// @param out_buffer 输出的命令缓冲区。
// @param buffer_size 输出缓冲区大小,单位为字节。
// @return 生成成功时返回写入的字节数,失败时返回 0。
XYPARSER_API int XYParser_Serialize64GainSampleRateCommand(std::uint8_t gain,
std::uint16_t sample_rate,
std::uint8_t* out_buffer,
std::size_t buffer_size);
// Frame conversion and algorithm data output.
// 将 8 导帧批量转换为 64 导帧,未映射的导联补 0。
// @param input_summaries 输入的 8 导帧数组。
// @param input_count 输入数组中的帧数。
// @param output_summaries 输出的 64 导帧数组。
// @param max_summaries 输出数组可写入的最大帧数,用于避免越界写入。
// @return 实际成功转换的帧数;如果参数无效则返回 0如果中途遇到非 8 导帧则返回已完成转换的数量。
XYPARSER_API int XYParser_Convert8ChFramesTo64Ch(const XYParserFrameSummary* input_summaries,
int input_count,
XYParserFrameSummary* output_summaries,
int max_summaries);
// 获取算法数据数组所需的元素数量。
// @return 算法数据数组所需的元素数量。
XYPARSER_API std::size_t XYParser_GetAlgorithmDataValueCount();
// 将单帧数据转换为算法接口使用的连续数组。
// @param input_summary 输入的单帧数据。
// @param output_data 输出的算法数据数组。
// @return 转换成功时返回非 0失败时返回 0。
XYPARSER_API int XYParser_ConvertSampleFramesToAlgorithmData(const XYParserFrameSummary* input_summary,
double* output_data);
// 向算法数据缓存输入字节流,并按每 5 个采样组装为帧,同时驱动 Welch 计算。
// @param handle 目标解析器句柄。
// @param data 输入的算法数据字节流。
// @param size 输入数据的字节数。
// @param out_summaries 输出的帧数组。
// @param max_summaries 输出数组可写入的最大帧数,用于避免越界写入。
// @return 实际输出的帧数;如果参数无效则返回 0。
XYPARSER_API int XYParser_FeedAlgorithmData(XYParserHandle handle,
const std::uint8_t* data,
std::size_t size,
XYParserFrameSummary* out_summaries,
int max_summaries);
// 清空算法数据缓存。
// @param handle 目标解析器句柄。
// @return 无返回值。
XYPARSER_API void XYParser_ResetAlgorithmDataCache(XYParserHandle handle);
// 将缓存中不足 5 个采样的尾数据补齐并输出为 1 帧。
// @param handle 目标解析器句柄。
// @param out_summary 输出的帧。
// @return 成功输出 1 帧时返回 1没有可输出的数据或参数无效时返回 0。
XYPARSER_API int XYParser_FlushAlgorithmData(XYParserHandle handle,
XYParserFrameSummary* out_summary);
// Lead and Welch metadata.
// 获取指定导联数对应的导联映射数量。
// @param channel_count 导联数。
// @return 导联映射数量;如果导联数不支持则返回 0。
XYPARSER_API int XYParser_GetLeadMapCount(std::uint8_t channel_count);
// 获取指定导联数对应的导联映射表。
// @param channel_count 导联数。
// @param out_leads 输出的导联映射数组。
// @param max_leads 输出数组可写入的最大导联数,用于避免越界写入。
// @return 实际写入的导联数;如果参数无效则返回 0。
XYPARSER_API int XYParser_GetLeadMap(std::uint8_t channel_count,
XYParserLeadChannelNumber* out_leads,
int max_leads);
// 获取导联编号对应的名称字符串。
// @param lead 导联编号。
// @return 导联名称字符串;如果导联编号无效则返回 nullptr。
XYPARSER_API const char* XYParser_GetLeadName(XYParserLeadChannelNumber lead);
// 获取 Welch 频段索引对应的名称字符串。
// @param band_index Welch 频段索引。
// @return 频段名称字符串;如果索引无效则返回 nullptr。
XYPARSER_API const char* XYParser_GetWelchBandName(int band_index);
// 获取 Welch 频段索引对应的频率范围。
// @param band_index Welch 频段索引。
// @param out_low_hz 输出的频段下限频率,单位为 Hz。
// @param out_high_hz 输出的频段上限频率,单位为 Hz。
// @return 获取成功时返回非 0失败时返回 0。
XYPARSER_API int XYParser_GetWelchBandRange(int band_index,
double* out_low_hz,
double* out_high_hz);
// 获取解析器最近一次错误的描述信息。
// @param handle 目标解析器句柄。
// @return 错误描述字符串;如果当前没有错误信息则返回 nullptr。
XYPARSER_API const char* XYParser_GetLastError(XYParserHandle handle); XYPARSER_API const char* XYParser_GetLastError(XYParserHandle handle);
} }

View File

@@ -4,9 +4,11 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include "../XYParserApi.h" #include "../XYParserApi.h"
#include <algorithm>
#include <array> #include <array>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <cmath>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -106,6 +108,70 @@ std::vector<std::uint8_t> BuildMinimalFrame(std::uint8_t channel_count)
return BuildMinimalFrame(channel_count, 1U); return BuildMinimalFrame(channel_count, 1U);
} }
void WriteSigned24(std::vector<std::uint8_t>& frame, std::size_t& offset, int raw_value)
{
constexpr int kMinSigned24 = -8388608;
constexpr int kMaxSigned24 = 8388607;
raw_value = std::max(kMinSigned24, std::min(kMaxSigned24, raw_value));
std::uint32_t encoded = 0;
if (raw_value < 0) {
encoded = static_cast<std::uint32_t>((1 << 24) + raw_value);
} else {
encoded = static_cast<std::uint32_t>(raw_value);
}
frame[offset++] = static_cast<std::uint8_t>((encoded >> 16) & 0xFF);
frame[offset++] = static_cast<std::uint8_t>((encoded >> 8) & 0xFF);
frame[offset++] = static_cast<std::uint8_t>(encoded & 0xFF);
}
std::vector<std::uint8_t> BuildFrameWithRawSamples(
std::uint8_t channel_count,
std::uint32_t frame_index,
const std::array<std::array<int, XYPARSER_MAX_CHANNELS>, XYPARSER_SAMPLES_PER_FRAME>& raw_samples)
{
constexpr std::uint8_t kHeader = 0xAA;
constexpr std::uint8_t kTail = 0x55;
constexpr std::size_t kTagLen = 25;
const std::size_t sample_bytes = static_cast<std::size_t>(channel_count) * 3 + 2;
const std::uint16_t payload_length = static_cast<std::uint16_t>(sample_bytes * XYPARSER_SAMPLES_PER_FRAME);
const std::size_t frame_size = 1 + kTagLen + payload_length + 2;
std::vector<std::uint8_t> frame(frame_size, 0);
std::size_t offset = 0;
frame[offset++] = kHeader;
frame[offset++] = static_cast<std::uint8_t>(frame_index & 0xFF);
frame[offset++] = static_cast<std::uint8_t>((frame_index >> 8) & 0xFF);
frame[offset++] = static_cast<std::uint8_t>((frame_index >> 16) & 0xFF);
frame[offset++] = static_cast<std::uint8_t>((frame_index >> 24) & 0xFF);
frame[offset++] = static_cast<std::uint8_t>((payload_length >> 8) & 0xFF);
frame[offset++] = static_cast<std::uint8_t>(payload_length & 0xFF);
frame[offset++] = 95;
frame[offset++] = channel_count;
offset += 2 + 2 + 2 + 2 + 2 + 6;
for (std::size_t sample = 0; sample < XYPARSER_SAMPLES_PER_FRAME; ++sample) {
for (std::size_t channel = 0; channel < channel_count; ++channel) {
WriteSigned24(frame, offset, raw_samples[sample][channel]);
}
frame[offset++] = 0x00;
frame[offset++] = 0x00;
}
frame[offset++] = 0x00;
frame[offset++] = kTail;
frame[offset++] = kTail;
return frame;
}
int BuildSineRawValue(int sample_index, int sample_rate, double frequency_hz, double amplitude)
{
const double angle = 2.0 * 3.14159265358979323846 * frequency_hz *
static_cast<double>(sample_index) / static_cast<double>(sample_rate);
return static_cast<int>(std::lround(std::sin(angle) * amplitude));
}
} // namespace } // namespace
/// 测试:创建解析器时拒绝不支持的通道数 /// 测试:创建解析器时拒绝不支持的通道数
@@ -122,6 +188,507 @@ TEST(XYParserApiTests, GetLastErrorReturnsMessageForNullParser)
EXPECT_EQ(std::string(XYParser_GetLastError(nullptr)), std::string("invalid parser handle")); EXPECT_EQ(std::string(XYParser_GetLastError(nullptr)), std::string("invalid parser handle"));
} }
TEST(XYParserApiTests, Serialize8ChImpedanceOpenCommandMatchesWirelessEegPacket)
{
constexpr std::size_t kCommandSize = 7;
std::array<std::uint8_t, kCommandSize> command{};
const int command_size = XYParser_Serialize8ChImpedanceCommand(1, command.data(), command.size());
ASSERT_EQ(command_size, static_cast<int>(kCommandSize));
const std::array<std::uint8_t, kCommandSize> expected = {
0xAA, 0x00, 0x01, 0xA1, 0xA1, 0x55, 0x55};
EXPECT_EQ(command, expected);
}
TEST(XYParserApiTests, Serialize8ChImpedanceCloseCommandMatchesWirelessEegPacket)
{
constexpr std::size_t kCommandSize = 7;
std::array<std::uint8_t, kCommandSize> command{};
const int command_size = XYParser_Serialize8ChImpedanceCommand(0, command.data(), command.size());
ASSERT_EQ(command_size, static_cast<int>(kCommandSize));
const std::array<std::uint8_t, kCommandSize> expected = {
0xAA, 0x00, 0x01, 0xA0, 0xA0, 0x55, 0x55};
EXPECT_EQ(command, expected);
}
TEST(XYParserApiTests, Serialize8ChImpedanceCommandRejectsSmallBuffer)
{
std::array<std::uint8_t, 6> command{};
EXPECT_EQ(XYParser_Serialize8ChImpedanceCommand(1, command.data(), command.size()), 0);
}
TEST(XYParserApiTests, Get8ChImpedanceCommandSizeMatchesSerializedLength)
{
EXPECT_EQ(XYParser_Get8ChImpedanceCommandSize(), static_cast<std::size_t>(7));
}
TEST(XYParserApiTests, GetAlgorithmDataValueCountMatchesFrameLayout)
{
EXPECT_EQ(XYParser_GetAlgorithmDataValueCount(),
static_cast<std::size_t>(XYPARSER_FRAME_ALGORITHM_VALUE_COUNT));
}
TEST(XYParserApiTests, GetLeadMapCountReturnsExpectedCounts)
{
EXPECT_EQ(XYParser_GetLeadMapCount(8), 8);
EXPECT_EQ(XYParser_GetLeadMapCount(64), 64);
EXPECT_EQ(XYParser_GetLeadMapCount(7), 0);
}
TEST(XYParserApiTests, GetLeadMapReturnsExpected8ChannelSubset)
{
std::array<XYParserLeadChannelNumber, 8> leads{};
const int count = XYParser_GetLeadMap(8, leads.data(), static_cast<int>(leads.size()));
ASSERT_EQ(count, 8);
const std::array<XYParserLeadChannelNumber, 8> expected = {
LeadChannel_PO5, LeadChannel_POZ, LeadChannel_PO6, LeadChannel_PO7,
LeadChannel_O1, LeadChannel_OZ, LeadChannel_O2, LeadChannel_PO8};
EXPECT_EQ(leads, expected);
}
TEST(XYParserApiTests, GetLeadMapReturnsExpected64ChannelOrder)
{
std::array<XYParserLeadChannelNumber, 64> leads{};
const int count = XYParser_GetLeadMap(64, leads.data(), static_cast<int>(leads.size()));
ASSERT_EQ(count, 64);
EXPECT_EQ(leads.front(), LeadChannel_FP1);
EXPECT_EQ(leads[1], LeadChannel_FP2);
EXPECT_EQ(leads[13], LeadChannel_PO5);
EXPECT_EQ(leads[55], LeadChannel_PO7);
EXPECT_EQ(leads.back(), LeadChannel_F1);
}
TEST(XYParserApiTests, GetLeadMapRejectsUnsupportedChannelCountOrSmallBuffer)
{
std::array<XYParserLeadChannelNumber, 7> small_buffer{};
EXPECT_EQ(XYParser_GetLeadMap(8, small_buffer.data(), static_cast<int>(small_buffer.size())), 0);
EXPECT_EQ(XYParser_GetLeadMap(7, nullptr, 0), 0);
}
TEST(XYParserApiTests, Convert8ChFramesTo64ChMapsKnownLeadsAndPadsOthersWithZero)
{
std::array<XYParserFrameSummary, 2> input{};
input[0].frame_index = 7U;
input[0].channel_count = 8U;
input[0].battery = 88U;
input[0].sample_count = 5U;
input[0].sample_trigger_types[0] = 0xB2;
input[0].sample_trigger_indices[0] = 3U;
input[0].channel_values_uv[0][0] = 11.0;
input[0].channel_values_uv[0][1] = 12.0;
input[0].channel_values_uv[0][2] = 13.0;
input[0].channel_values_uv[0][3] = 14.0;
input[0].channel_values_uv[0][4] = 15.0;
input[0].channel_values_uv[0][5] = 16.0;
input[0].channel_values_uv[0][6] = 17.0;
input[0].channel_values_uv[0][7] = 18.0;
input[1].frame_index = 8U;
input[1].channel_count = 8U;
input[1].battery = 77U;
input[1].sample_count = 5U;
input[1].channel_values_uv[1][0] = 21.0;
std::array<XYParserFrameSummary, 2> output{};
ASSERT_EQ(XYParser_Convert8ChFramesTo64Ch(input.data(), static_cast<int>(input.size()), output.data(), static_cast<int>(output.size())), 2);
EXPECT_EQ(output[0].frame_index, 7U);
EXPECT_EQ(output[0].channel_count, 64U);
EXPECT_EQ(output[0].battery, 88U);
EXPECT_EQ(output[0].sample_count, 5U);
EXPECT_EQ(output[0].sample_trigger_types[0], 0xB2);
EXPECT_EQ(output[0].sample_trigger_indices[0], 3U);
EXPECT_DOUBLE_EQ(output[0].channel_values_uv[0][LeadChannel_PO5], 11.0);
EXPECT_DOUBLE_EQ(output[0].channel_values_uv[0][LeadChannel_POZ], 12.0);
EXPECT_DOUBLE_EQ(output[0].channel_values_uv[0][LeadChannel_PO6], 13.0);
EXPECT_DOUBLE_EQ(output[0].channel_values_uv[0][LeadChannel_PO7], 14.0);
EXPECT_DOUBLE_EQ(output[0].channel_values_uv[0][LeadChannel_O1], 15.0);
EXPECT_DOUBLE_EQ(output[0].channel_values_uv[0][LeadChannel_OZ], 16.0);
EXPECT_DOUBLE_EQ(output[0].channel_values_uv[0][LeadChannel_O2], 17.0);
EXPECT_DOUBLE_EQ(output[0].channel_values_uv[0][LeadChannel_PO8], 18.0);
EXPECT_DOUBLE_EQ(output[0].channel_values_uv[0][LeadChannel_FP1], 0.0);
EXPECT_DOUBLE_EQ(output[0].channel_values_uv[0][LeadChannel_CZ], 0.0);
EXPECT_EQ(output[1].frame_index, 8U);
EXPECT_EQ(output[1].channel_count, 64U);
EXPECT_DOUBLE_EQ(output[1].channel_values_uv[1][LeadChannel_PO5], 21.0);
}
TEST(XYParserApiTests, Convert8ChFramesTo64ChRejectsInvalidArgumentsAndStopsAtNon8ChannelInput)
{
std::array<XYParserFrameSummary, 2> input{};
input[0].channel_count = 8U;
input[1].channel_count = 64U;
std::array<XYParserFrameSummary, 2> output{};
EXPECT_EQ(XYParser_Convert8ChFramesTo64Ch(nullptr, 1, output.data(), static_cast<int>(output.size())), 0);
EXPECT_EQ(XYParser_Convert8ChFramesTo64Ch(input.data(), 1, nullptr, static_cast<int>(output.size())), 0);
EXPECT_EQ(XYParser_Convert8ChFramesTo64Ch(input.data(), 0, output.data(), static_cast<int>(output.size())), 0);
EXPECT_EQ(XYParser_Convert8ChFramesTo64Ch(input.data(), static_cast<int>(input.size()), output.data(), static_cast<int>(output.size())), 1);
EXPECT_EQ(output[0].channel_count, 64U);
}
TEST(XYParserApiTests, ConvertSampleFramesToAlgorithmDataAppendsTriggerColumnsPerSample)
{
constexpr std::size_t kFrameValueCount = XYPARSER_FRAME_ALGORITHM_VALUE_COUNT;
XYParserFrameSummary input{};
input.channel_count = 64U;
input.sample_count = 5U;
input.channel_values_uv[0][0] = 101.0;
input.channel_values_uv[0][63] = 164.0;
input.sample_trigger_types[0] = 0xA5;
input.sample_trigger_indices[0] = 9U;
input.channel_values_uv[1][10] = 210.0;
input.sample_trigger_types[1] = 0x7F;
input.sample_trigger_indices[1] = 5U;
input.channel_values_uv[4][3] = 403.0;
input.sample_trigger_types[4] = 0xB2;
input.sample_trigger_indices[4] = 11U;
std::array<double, kFrameValueCount> output{};
ASSERT_EQ(XYParser_ConvertSampleFramesToAlgorithmData(&input, output.data()), 1);
const std::size_t sample0 = 0;
const std::size_t sample1 = XYPARSER_FRAME_DATA_COLUMN_COUNT;
const std::size_t sample4 = 4 * XYPARSER_FRAME_DATA_COLUMN_COUNT;
EXPECT_DOUBLE_EQ(output[sample0 + 0], 101.0);
EXPECT_DOUBLE_EQ(output[sample0 + 63], 164.0);
EXPECT_DOUBLE_EQ(output[sample0 + XYPARSER_FRAME_DATA_TRIGGER_TYPE_INDEX], 165.0);
EXPECT_DOUBLE_EQ(output[sample0 + XYPARSER_FRAME_DATA_TRIGGER_INDEX_INDEX], 9.0);
EXPECT_DOUBLE_EQ(output[sample4 + 3], 403.0);
EXPECT_DOUBLE_EQ(output[sample4 + XYPARSER_FRAME_DATA_TRIGGER_TYPE_INDEX], 178.0);
EXPECT_DOUBLE_EQ(output[sample4 + XYPARSER_FRAME_DATA_TRIGGER_INDEX_INDEX], 11.0);
EXPECT_DOUBLE_EQ(output[sample1 + 10], 210.0);
EXPECT_DOUBLE_EQ(output[sample1 + XYPARSER_FRAME_DATA_TRIGGER_TYPE_INDEX], 127.0);
EXPECT_DOUBLE_EQ(output[sample1 + XYPARSER_FRAME_DATA_TRIGGER_INDEX_INDEX], 5.0);
}
TEST(XYParserApiTests, ConvertSampleFramesToAlgorithmDataRejectsInvalidArgumentsAndNon64ChannelInput)
{
constexpr std::size_t kFrameValueCount = XYPARSER_FRAME_ALGORITHM_VALUE_COUNT;
XYParserFrameSummary input{};
input.channel_count = 8U;
std::array<double, kFrameValueCount> output{};
EXPECT_EQ(XYParser_ConvertSampleFramesToAlgorithmData(nullptr, output.data()), 0);
EXPECT_EQ(XYParser_ConvertSampleFramesToAlgorithmData(&input, nullptr), 0);
EXPECT_EQ(XYParser_ConvertSampleFramesToAlgorithmData(&input, output.data()), 0);
EXPECT_DOUBLE_EQ(output[XYPARSER_FRAME_DATA_TRIGGER_TYPE_INDEX], 0.0);
}
TEST(XYParserApiTests, FeedAlgorithmDataCachesSamplesBuildsFramesAndFlushesTailSamples)
{
ParserGuard parser(XYParser_CreateParser(64));
ASSERT_NE(parser.get(), nullptr);
constexpr int kTotalSamples = 7;
constexpr int kColumnCount = XYPARSER_FRAME_DATA_COLUMN_COUNT;
std::array<double, static_cast<std::size_t>(kTotalSamples) * kColumnCount> input_data{};
for (int sample_index = 0; sample_index < kTotalSamples; ++sample_index) {
const std::size_t row_offset = static_cast<std::size_t>(sample_index) * kColumnCount;
input_data[row_offset + 0] = 1000.0 + sample_index;
input_data[row_offset + 10] = 2000.0 + sample_index;
input_data[row_offset + 63] = 3000.0 + sample_index;
input_data[row_offset + XYPARSER_FRAME_DATA_TRIGGER_TYPE_INDEX] = 10.0 + sample_index;
input_data[row_offset + XYPARSER_FRAME_DATA_TRIGGER_INDEX_INDEX] = 20.0 + sample_index;
}
const std::uint8_t* input_bytes = reinterpret_cast<const std::uint8_t*>(input_data.data());
const std::size_t first_chunk_size = 3 * static_cast<std::size_t>(kColumnCount) * sizeof(double) + sizeof(double);
const std::size_t second_chunk_size = sizeof(input_data) - first_chunk_size;
std::array<XYParserFrameSummary, 1> summaries{};
EXPECT_EQ(XYParser_FeedAlgorithmData(
parser.get(),
input_bytes,
first_chunk_size,
summaries.data(),
static_cast<int>(summaries.size())),
0);
ASSERT_EQ(XYParser_FeedAlgorithmData(
parser.get(),
input_bytes + first_chunk_size,
second_chunk_size,
summaries.data(),
static_cast<int>(summaries.size())),
1);
EXPECT_EQ(summaries[0].frame_index, 1U);
EXPECT_EQ(summaries[0].channel_count, 64U);
EXPECT_EQ(summaries[0].sample_count, 5U);
EXPECT_DOUBLE_EQ(summaries[0].channel_values_uv[0][0], 1000.0);
EXPECT_DOUBLE_EQ(summaries[0].channel_values_uv[2][10], 2002.0);
EXPECT_DOUBLE_EQ(summaries[0].channel_values_uv[4][63], 3004.0);
EXPECT_EQ(summaries[0].sample_trigger_types[0], 10U);
EXPECT_EQ(summaries[0].sample_trigger_indices[4], 24U);
XYParserFrameSummary tail_summary{};
ASSERT_EQ(XYParser_FlushAlgorithmData(parser.get(), &tail_summary), 1);
EXPECT_EQ(tail_summary.frame_index, 2U);
EXPECT_EQ(tail_summary.channel_count, 64U);
EXPECT_EQ(tail_summary.sample_count, 2U);
EXPECT_DOUBLE_EQ(tail_summary.channel_values_uv[0][0], 1005.0);
EXPECT_DOUBLE_EQ(tail_summary.channel_values_uv[1][63], 3006.0);
EXPECT_EQ(tail_summary.sample_trigger_types[0], 15U);
EXPECT_EQ(tail_summary.sample_trigger_indices[1], 26U);
EXPECT_DOUBLE_EQ(tail_summary.channel_values_uv[2][0], 0.0);
EXPECT_EQ(tail_summary.sample_trigger_types[2], 0U);
XYParser_ResetAlgorithmDataCache(parser.get());
EXPECT_EQ(XYParser_FlushAlgorithmData(parser.get(), &tail_summary), 0);
}
TEST(XYParserApiTests, GetLeadNameReturnsExpectedNames)
{
EXPECT_EQ(std::string(XYParser_GetLeadName(LeadChannel_FP1)), "FP1");
EXPECT_EQ(std::string(XYParser_GetLeadName(LeadChannel_PO5)), "PO5");
EXPECT_EQ(std::string(XYParser_GetLeadName(LeadChannel_OZ)), "OZ");
EXPECT_EQ(std::string(XYParser_GetLeadName(LeadChannel_GND)), "GND");
}
TEST(XYParserApiTests, GetLeadNameCovers8ChannelSubsetNames)
{
EXPECT_EQ(std::string(XYParser_GetLeadName(LeadChannel_POZ)), "POZ");
EXPECT_EQ(std::string(XYParser_GetLeadName(LeadChannel_PO6)), "PO6");
EXPECT_EQ(std::string(XYParser_GetLeadName(LeadChannel_PO7)), "PO7");
EXPECT_EQ(std::string(XYParser_GetLeadName(LeadChannel_PO8)), "PO8");
EXPECT_EQ(std::string(XYParser_GetLeadName(LeadChannel_O1)), "O1");
EXPECT_EQ(std::string(XYParser_GetLeadName(LeadChannel_O2)), "O2");
}
TEST(XYParserApiTests, GetLeadNameReturnsEmptyStringForInvalidLead)
{
EXPECT_EQ(std::string(XYParser_GetLeadName(static_cast<XYParserLeadChannelNumber>(255))), "");
}
TEST(XYParserApiTests, GetWelchBandNameReturnsExpectedNames)
{
EXPECT_EQ(std::string(XYParser_GetWelchBandName(0)), "delta");
EXPECT_EQ(std::string(XYParser_GetWelchBandName(1)), "theta");
EXPECT_EQ(std::string(XYParser_GetWelchBandName(2)), "alpha");
EXPECT_EQ(std::string(XYParser_GetWelchBandName(3)), "beta");
EXPECT_EQ(std::string(XYParser_GetWelchBandName(4)), "gamma");
}
TEST(XYParserApiTests, GetWelchBandNameReturnsEmptyStringForInvalidIndex)
{
EXPECT_EQ(std::string(XYParser_GetWelchBandName(-1)), "");
EXPECT_EQ(std::string(XYParser_GetWelchBandName(XYPARSER_WELCH_BAND_COUNT)), "");
}
TEST(XYParserApiTests, GetWelchBandRangeReturnsExpectedRanges)
{
double low_hz = 0.0;
double high_hz = 0.0;
ASSERT_EQ(XYParser_GetWelchBandRange(0, &low_hz, &high_hz), 1);
EXPECT_DOUBLE_EQ(low_hz, 0.5);
EXPECT_DOUBLE_EQ(high_hz, 4.0);
ASSERT_EQ(XYParser_GetWelchBandRange(4, &low_hz, &high_hz), 1);
EXPECT_DOUBLE_EQ(low_hz, 30.0);
EXPECT_DOUBLE_EQ(high_hz, 50.0);
}
TEST(XYParserApiTests, GetWelchBandRangeRejectsInvalidArguments)
{
double low_hz = 0.0;
double high_hz = 0.0;
EXPECT_EQ(XYParser_GetWelchBandRange(-1, &low_hz, &high_hz), 0);
EXPECT_EQ(XYParser_GetWelchBandRange(XYPARSER_WELCH_BAND_COUNT, &low_hz, &high_hz), 0);
EXPECT_EQ(XYParser_GetWelchBandRange(0, nullptr, &high_hz), 0);
EXPECT_EQ(XYParser_GetWelchBandRange(0, &low_hz, nullptr), 0);
}
TEST(XYParserApiTests, SetImpedanceDetectionSwitchesParserGainBetween24And6)
{
ParserGuard parser(XYParser_CreateParser(8));
ASSERT_NE(parser.get(), nullptr);
XYParser_SetAdcParams(parser.get(), 4.5, 6.0);
XYParser_SetBypassChecksum(parser.get(), 1);
const std::vector<std::uint8_t> frame1 = BuildMinimalFrame(8, 1U);
const std::vector<std::uint8_t> frame2 = BuildMinimalFrame(8, 2U);
const std::vector<std::uint8_t> frame3 = BuildMinimalFrame(8, 3U);
std::array<XYParserFrameSummary, 1> summaries{};
ASSERT_EQ(XYParser_Feed(parser.get(), frame1.data(), frame1.size(), summaries.data(), static_cast<int>(summaries.size())), 1);
const double gain6_value_before = summaries[0].channel_values_uv[0][0];
XYParser_SetImpedanceDetection(parser.get(), 1);
ASSERT_EQ(XYParser_Feed(parser.get(), frame2.data(), frame2.size(), summaries.data(), static_cast<int>(summaries.size())), 1);
const double gain24_value = summaries[0].channel_values_uv[0][0];
XYParser_SetImpedanceDetection(parser.get(), 0);
ASSERT_EQ(XYParser_Feed(parser.get(), frame3.data(), frame3.size(), summaries.data(), static_cast<int>(summaries.size())), 1);
const double gain6_value_after = summaries[0].channel_values_uv[0][0];
EXPECT_NEAR(gain24_value * 4.0, gain6_value_before, 1e-9);
EXPECT_NEAR(gain6_value_after, gain6_value_before, 1e-9);
}
TEST(XYParserApiTests, ImpedanceReturnsOneResultAfterOneSecondFor8Channels)
{
ParserGuard parser(XYParser_CreateParser(8));
ASSERT_NE(parser.get(), nullptr);
XYParser_SetBypassChecksum(parser.get(), 1);
XYParser_SetSampleRate(parser.get(), 10);
XYParser_SetImpedanceDetection(parser.get(), 1);
std::array<std::array<int, XYPARSER_MAX_CHANNELS>, XYPARSER_SAMPLES_PER_FRAME> raw_samples1{};
std::array<std::array<int, XYPARSER_MAX_CHANNELS>, XYPARSER_SAMPLES_PER_FRAME> raw_samples2{};
for (int sample = 0; sample < 10; ++sample) {
const int raw_value = BuildSineRawValue(sample, 10, 2.0, 1000000.0);
if (sample < static_cast<int>(XYPARSER_SAMPLES_PER_FRAME)) {
raw_samples1[static_cast<std::size_t>(sample)][0] = raw_value;
} else {
raw_samples2[static_cast<std::size_t>(sample - XYPARSER_SAMPLES_PER_FRAME)][0] = raw_value;
}
}
const std::vector<std::uint8_t> frame1 = BuildFrameWithRawSamples(8, 1U, raw_samples1);
const std::vector<std::uint8_t> frame2 = BuildFrameWithRawSamples(8, 2U, raw_samples2);
std::array<XYParserFrameSummary, 2> summaries{};
std::array<XYParserImpedanceSummary, 2> impedance{};
EXPECT_EQ(XYParser_Feed(parser.get(), frame1.data(), frame1.size(), summaries.data(), static_cast<int>(summaries.size())), 1);
EXPECT_EQ(XYParser_ReadImpedance(parser.get(), impedance.data(), static_cast<int>(impedance.size())), 0);
EXPECT_EQ(XYParser_Feed(parser.get(), frame2.data(), frame2.size(), summaries.data(), static_cast<int>(summaries.size())), 1);
ASSERT_EQ(XYParser_ReadImpedance(parser.get(), impedance.data(), static_cast<int>(impedance.size())), 1);
EXPECT_EQ(impedance[0].channel_count, 8);
EXPECT_EQ(impedance[0].sample_rate, 10U);
EXPECT_EQ(impedance[0].window_sample_count, 10U);
EXPECT_GT(impedance[0].impedance_values[LeadChannel_PO5], 0);
EXPECT_EQ(impedance[0].impedance_values[LeadChannel_FP1], 0);
}
TEST(XYParserApiTests, ImpedanceUsesUnifiedLeadIndexesFor64Channels)
{
ParserGuard parser(XYParser_CreateParser(64));
ASSERT_NE(parser.get(), nullptr);
XYParser_SetBypassChecksum(parser.get(), 1);
XYParser_SetSampleRate(parser.get(), 10);
XYParser_SetImpedanceDetection(parser.get(), 1);
std::array<std::array<int, XYPARSER_MAX_CHANNELS>, XYPARSER_SAMPLES_PER_FRAME> raw_samples1{};
std::array<std::array<int, XYPARSER_MAX_CHANNELS>, XYPARSER_SAMPLES_PER_FRAME> raw_samples2{};
for (int sample = 0; sample < 10; ++sample) {
const int raw_value = BuildSineRawValue(sample, 10, 3.0, 1000000.0);
if (sample < static_cast<int>(XYPARSER_SAMPLES_PER_FRAME)) {
raw_samples1[static_cast<std::size_t>(sample)][0] = raw_value;
} else {
raw_samples2[static_cast<std::size_t>(sample - XYPARSER_SAMPLES_PER_FRAME)][0] = raw_value;
}
}
const std::vector<std::uint8_t> frame1 = BuildFrameWithRawSamples(64, 1U, raw_samples1);
const std::vector<std::uint8_t> frame2 = BuildFrameWithRawSamples(64, 2U, raw_samples2);
std::array<XYParserFrameSummary, 2> summaries{};
std::array<XYParserImpedanceSummary, 2> impedance{};
EXPECT_EQ(XYParser_Feed(parser.get(), frame1.data(), frame1.size(), summaries.data(), static_cast<int>(summaries.size())), 1);
EXPECT_EQ(XYParser_Feed(parser.get(), frame2.data(), frame2.size(), summaries.data(), static_cast<int>(summaries.size())), 1);
ASSERT_EQ(XYParser_ReadImpedance(parser.get(), impedance.data(), static_cast<int>(impedance.size())), 1);
EXPECT_EQ(impedance[0].channel_count, 64);
EXPECT_GT(impedance[0].impedance_values[LeadChannel_FP1], 0);
EXPECT_EQ(impedance[0].impedance_values[LeadChannel_FP2], 0);
}
TEST(XYParserApiTests, WelchReturnsOneResultAfterOneSecondFromAlgorithmData)
{
ParserGuard parser(XYParser_CreateParser(64));
ASSERT_NE(parser.get(), nullptr);
XYParser_SetSampleRate(parser.get(), 10);
XYParser_SetWelchDetection(parser.get(), 1);
std::vector<double> algorithm_data(static_cast<std::size_t>(10 * XYPARSER_FRAME_DATA_COLUMN_COUNT), 0.0);
for (int sample = 0; sample < 10; ++sample) {
const std::size_t sample_offset = static_cast<std::size_t>(sample) * XYPARSER_FRAME_DATA_COLUMN_COUNT;
algorithm_data[sample_offset] = static_cast<double>(BuildSineRawValue(sample, 10, 2.0, 1000000.0));
}
std::array<XYParserWelchSummary, 2> welch{};
EXPECT_EQ(XYParser_FeedAlgorithmData(
parser.get(),
reinterpret_cast<const std::uint8_t*>(algorithm_data.data()),
algorithm_data.size() * sizeof(double),
nullptr,
0),
0);
ASSERT_EQ(XYParser_ReadWelch(parser.get(), welch.data(), static_cast<int>(welch.size())), 1);
EXPECT_EQ(welch[0].ok, 1);
EXPECT_EQ(welch[0].channel_count, 64);
EXPECT_EQ(welch[0].sample_rate, 10U);
EXPECT_EQ(welch[0].window_sample_count, 10U);
EXPECT_GT(welch[0].frequency_count, 0U);
EXPECT_DOUBLE_EQ(welch[0].frequencies[0], 1.0);
EXPECT_GT(welch[0].psd_values[LeadChannel_FP1][0], 0.0);
EXPECT_EQ(welch[0].psd_values[LeadChannel_FP2][0], 0.0);
EXPECT_GT(welch[0].band_values[0][LeadChannel_FP1], 0.0);
}
TEST(XYParserApiTests, WelchFrameFeedDoesNotProduceResults)
{
ParserGuard parser(XYParser_CreateParser(64));
ASSERT_NE(parser.get(), nullptr);
XYParser_SetBypassChecksum(parser.get(), 1);
XYParser_SetSampleRate(parser.get(), 10);
XYParser_SetWelchDetection(parser.get(), 1);
std::array<std::array<int, XYPARSER_MAX_CHANNELS>, XYPARSER_SAMPLES_PER_FRAME> raw_samples1{};
std::array<std::array<int, XYPARSER_MAX_CHANNELS>, XYPARSER_SAMPLES_PER_FRAME> raw_samples2{};
for (int sample = 0; sample < 10; ++sample) {
const int raw_value = BuildSineRawValue(sample, 10, 2.0, 1000000.0);
if (sample < static_cast<int>(XYPARSER_SAMPLES_PER_FRAME)) {
raw_samples1[static_cast<std::size_t>(sample)][0] = raw_value;
} else {
raw_samples2[static_cast<std::size_t>(sample - XYPARSER_SAMPLES_PER_FRAME)][0] = raw_value;
}
}
const std::vector<std::uint8_t> frame1 = BuildFrameWithRawSamples(64, 1U, raw_samples1);
const std::vector<std::uint8_t> frame2 = BuildFrameWithRawSamples(64, 2U, raw_samples2);
std::array<XYParserFrameSummary, 2> summaries{};
std::array<XYParserWelchSummary, 1> welch{};
EXPECT_EQ(XYParser_Feed(parser.get(), frame1.data(), frame1.size(), summaries.data(), static_cast<int>(summaries.size())), 1);
EXPECT_EQ(XYParser_Feed(parser.get(), frame2.data(), frame2.size(), summaries.data(), static_cast<int>(summaries.size())), 1);
EXPECT_EQ(XYParser_ReadWelch(parser.get(), welch.data(), static_cast<int>(welch.size())), 0);
}
TEST(XYParserApiTests, WelchDisabledDoesNotProduceResultsFromAlgorithmData)
{
ParserGuard parser(XYParser_CreateParser(64));
ASSERT_NE(parser.get(), nullptr);
XYParser_SetSampleRate(parser.get(), 10);
std::vector<double> algorithm_data(static_cast<std::size_t>(10 * XYPARSER_FRAME_DATA_COLUMN_COUNT), 0.0);
for (int sample = 0; sample < 10; ++sample) {
const std::size_t sample_offset = static_cast<std::size_t>(sample) * XYPARSER_FRAME_DATA_COLUMN_COUNT;
algorithm_data[sample_offset] = static_cast<double>(BuildSineRawValue(sample, 10, 2.0, 1000000.0));
}
std::array<XYParserWelchSummary, 1> welch{};
EXPECT_EQ(XYParser_FeedAlgorithmData(
parser.get(),
reinterpret_cast<const std::uint8_t*>(algorithm_data.data()),
algorithm_data.size() * sizeof(double),
nullptr,
0),
0);
EXPECT_EQ(XYParser_ReadWelch(parser.get(), welch.data(), static_cast<int>(welch.size())), 0);
}
/// 测试Feed 函数能正确解析完整的 8 通道帧 /// 测试Feed 函数能正确解析完整的 8 通道帧
TEST(XYParserApiTests, FeedParsesAComplete8ChannelFrame) TEST(XYParserApiTests, FeedParsesAComplete8ChannelFrame)
{ {
@@ -151,8 +718,8 @@ TEST(XYParserApiTests, FeedParsesAComplete8ChannelFrame)
EXPECT_EQ(summaries[0].battery, 95U); // 电池电量应为 95 EXPECT_EQ(summaries[0].battery, 95U); // 电池电量应为 95
EXPECT_EQ(summaries[0].sample_count, 5U); // 采样数应为 5 EXPECT_EQ(summaries[0].sample_count, 5U); // 采样数应为 5
EXPECT_GT(summaries[0].channel_values_uv[0][0], 0.0); // 通道值应大于 0 EXPECT_GT(summaries[0].channel_values_uv[0][0], 0.0); // 通道值应大于 0
EXPECT_EQ(summaries[0].trigger_types[0], 0U); // 触发类型应为 0 EXPECT_EQ(summaries[0].sample_trigger_types[0], 0U); // 触发类型应为 0
EXPECT_EQ(summaries[0].trigger_indices[0], 0U); // 触发索引应为 0 EXPECT_EQ(summaries[0].sample_trigger_indices[0], 0U); // 触发索引应为 0
} }
/// 测试Feed 函数能缓冲部分数据直到完整帧可用 /// 测试Feed 函数能缓冲部分数据直到完整帧可用
@@ -268,6 +835,56 @@ TEST(XYParserApiTests, SetBypassChecksumOnNullHandle)
EXPECT_NO_THROW(XYParser_SetBypassChecksum(nullptr, 1)); EXPECT_NO_THROW(XYParser_SetBypassChecksum(nullptr, 1));
} }
// ============================================================================
// 64 导控制协议序列化测试
// ============================================================================
/// 测试64 导开启阻抗命令序列化与 WirelessEEG 协议保持一致
TEST(XYParserApiTests, Serialize64ImpedanceOpenCommandMatchesWirelessEegProtocol)
{
std::array<std::uint8_t, 32> buffer{};
const int size = XYParser_Serialize64ImpedanceCommand(1, buffer.data(), buffer.size());
ASSERT_EQ(size, static_cast<int>(XYParser_Get64ImpedanceCommandSize()));
const std::vector<std::uint8_t> expected = {
0xAA, 0x01, 0x00, 0x07,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x09, 0x55, 0x55
};
EXPECT_TRUE(std::equal(expected.begin(), expected.end(), buffer.begin()));
}
/// 测试64 导增益和采样率命令序列化与 WirelessEEG 协议保持一致
TEST(XYParserApiTests, Serialize64GainAndSampleRateCommandMatchesWirelessEegProtocol)
{
std::array<std::uint8_t, 32> buffer{};
const int size = XYParser_Serialize64GainSampleRateCommand(24, 1000, buffer.data(), buffer.size());
ASSERT_EQ(size, static_cast<int>(XYParser_Get64GainSampleRateCommandSize()));
const std::vector<std::uint8_t> expected = {
0xAA, 0x02, 0x00, 0x08,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x18, 0x02, 0x24, 0x55, 0x55
};
EXPECT_TRUE(std::equal(expected.begin(), expected.end(), buffer.begin()));
}
/// 测试64 导增益和采样率命令拒绝非法采样率
TEST(XYParserApiTests, Serialize64GainAndSampleRateCommandRejectsUnsupportedSampleRate)
{
std::array<std::uint8_t, 32> buffer{};
EXPECT_EQ(XYParser_Serialize64GainSampleRateCommand(24, 256, buffer.data(), buffer.size()), 0);
}
/// 测试64 导阻抗命令在输出缓冲区不足时返回失败
TEST(XYParserApiTests, Serialize64ImpedanceCommandRejectsSmallBuffer)
{
std::array<std::uint8_t, 4> buffer{};
EXPECT_EQ(XYParser_Serialize64ImpedanceCommand(1, buffer.data(), buffer.size()), 0);
}
// ============================================================================ // ============================================================================
// Feed 函数帧解析测试 // Feed 函数帧解析测试
// ============================================================================ // ============================================================================

View File

@@ -0,0 +1,285 @@
#include "pch.h"
#include "XYWelchProcessor.h"
#include "third_party/kissfft/kiss_fftr.h"
#include "third_party/kissfft/window_functions.h"
#include <cmath>
#include <cstring>
#include <limits>
#include <vector>
namespace {
constexpr int kDefaultSampleRate = 250;
constexpr int k8ChLeadCount = 8;
constexpr int k64ChLeadCount = 64;
struct PsdBandDefinition {
double low_hz;
double high_hz;
};
constexpr std::array<XYParserLeadChannelNumber, k8ChLeadCount> k8ChLeadMap = {
LeadChannel_PO5, LeadChannel_POZ, LeadChannel_PO6, LeadChannel_PO7,
LeadChannel_O1, LeadChannel_OZ, LeadChannel_O2, LeadChannel_PO8};
constexpr std::array<XYParserLeadChannelNumber, k64ChLeadCount> k64ChLeadMap = {
LeadChannel_FP1, LeadChannel_FP2, LeadChannel_PO6, LeadChannel_POZ,
LeadChannel_F3, LeadChannel_F4, LeadChannel_FPZ, LeadChannel_AF4,
LeadChannel_FC3, LeadChannel_PO8, LeadChannel_CP2, LeadChannel_CP1,
LeadChannel_FCZ, LeadChannel_PO5, LeadChannel_FC2, LeadChannel_FC1,
LeadChannel_C3, LeadChannel_C4, LeadChannel_FC4, LeadChannel_CP4,
LeadChannel_P3, LeadChannel_P4, LeadChannel_F5, LeadChannel_C5,
LeadChannel_F6, LeadChannel_PO4, LeadChannel_CP6, LeadChannel_CP5,
LeadChannel_PO3, LeadChannel_CP3, LeadChannel_FC6, LeadChannel_FC5,
LeadChannel_CB1, LeadChannel_CB2, LeadChannel_P5, LeadChannel_AF7,
LeadChannel_A1, LeadChannel_T7, LeadChannel_FT7, LeadChannel_TP7,
LeadChannel_FT8, LeadChannel_AF8, LeadChannel_F8, LeadChannel_F7,
LeadChannel_P6, LeadChannel_C6, LeadChannel_O2, LeadChannel_O1,
LeadChannel_T8, LeadChannel_P7, LeadChannel_CZ, LeadChannel_PZ,
LeadChannel_P8, LeadChannel_FZ, LeadChannel_OZ, LeadChannel_PO7,
LeadChannel_TP8, LeadChannel_AF3, LeadChannel_C2, LeadChannel_C1,
LeadChannel_P2, LeadChannel_P1, LeadChannel_F2, LeadChannel_F1};
constexpr std::array<PsdBandDefinition, XYPARSER_WELCH_BAND_COUNT> kWelchBands = {{
{0.5, 4.0},
{4.0, 8.0},
{8.0, 13.0},
{13.0, 30.0},
{30.0, 50.0},
}};
} // namespace
XYWelchProcessor::XYWelchProcessor(std::uint8_t channel_count)
: channel_count_(channel_count)
{
}
void XYWelchProcessor::SetChannelCount(std::uint8_t channel_count)
{
channel_count_ = channel_count;
Reset();
}
void XYWelchProcessor::SetSampleRate(int sample_rate)
{
if (sample_rate <= 0) {
return;
}
if (sample_rate_ != sample_rate) {
sample_rate_ = sample_rate;
Reset();
}
}
void XYWelchProcessor::SetEnabled(bool enabled)
{
if (enabled_ == enabled) {
return;
}
enabled_ = enabled;
Reset();
}
void XYWelchProcessor::Reset()
{
cached_samples_.clear();
pending_results_.clear();
}
void XYWelchProcessor::PushFrame(const XYParserFrameSummary& frame)
{
if (!enabled_ || frame.channel_count != channel_count_) {
return;
}
for (std::size_t sample_index = 0; sample_index < frame.sample_count; ++sample_index) {
Sample sample{};
for (std::size_t channel_index = 0; channel_index < frame.channel_count; ++channel_index) {
sample[channel_index] = frame.channel_values_uv[sample_index][channel_index];
}
cached_samples_.push_back(sample);
}
ProcessPendingWindows();
}
void XYWelchProcessor::PushSample(const std::array<double, XYPARSER_MAX_CHANNELS>& sample)
{
if (!enabled_ || channel_count_ == 0) {
return;
}
cached_samples_.push_back(sample);
ProcessPendingWindows();
}
int XYWelchProcessor::Read(XYParserWelchSummary* out_summaries, int max_summaries)
{
if (out_summaries == nullptr || max_summaries <= 0) {
return 0;
}
const int available_count = static_cast<int>(pending_results_.size());
const int read_count = available_count < max_summaries ? available_count : max_summaries;
for (int i = 0; i < read_count; ++i) {
out_summaries[i] = pending_results_.front();
pending_results_.pop_front();
}
return read_count;
}
void XYWelchProcessor::ProcessPendingWindows()
{
const int effective_sample_rate = sample_rate_ > 0 ? sample_rate_ : 1;
const std::size_t window_sample_count = static_cast<std::size_t>(effective_sample_rate);
while (cached_samples_.size() >= window_sample_count) {
pending_results_.push_back(BuildSummaryFromWindow());
cached_samples_.erase(cached_samples_.begin(), cached_samples_.begin() + static_cast<std::ptrdiff_t>(window_sample_count));
}
}
XYParserWelchSummary XYWelchProcessor::BuildSummaryFromWindow() const
{
XYParserWelchSummary summary{};
summary.channel_count = channel_count_;
summary.sample_rate = static_cast<std::uint32_t>(sample_rate_ > 0 ? sample_rate_ : kDefaultSampleRate);
summary.window_sample_count = summary.sample_rate;
const int sample_count = static_cast<int>(summary.window_sample_count);
if (channel_count_ == 0 || sample_count < 2) {
return summary;
}
int n_per_seg = sample_count;
if (n_per_seg % 2 != 0) {
--n_per_seg;
}
if (n_per_seg < 2) {
return summary;
}
const int n_overlap = n_per_seg / 2;
const int n_step = (n_per_seg - n_overlap) > 0 ? (n_per_seg - n_overlap) : 1;
const int n_freq_bin_count = n_per_seg / 2 + 1;
std::vector<double> window(static_cast<std::size_t>(n_per_seg), 0.0);
hanning_function(n_per_seg, window.data());
double window_power = 0.0;
for (int i = 0; i < n_per_seg; ++i) {
window_power += window[static_cast<std::size_t>(i)] * window[static_cast<std::size_t>(i)];
}
if (window_power <= std::numeric_limits<double>::epsilon()) {
return summary;
}
std::vector<std::vector<double>> psd_accum(static_cast<std::size_t>(channel_count_),
std::vector<double>(static_cast<std::size_t>(n_freq_bin_count), 0.0));
std::vector<kiss_fft_scalar> segment(static_cast<std::size_t>(n_per_seg), 0.0);
std::vector<kiss_fft_cpx> fft_output(static_cast<std::size_t>(n_freq_bin_count));
kiss_fftr_cfg cfg = kiss_fftr_alloc(n_per_seg, 0, nullptr, nullptr);
if (cfg == nullptr) {
return summary;
}
int segment_count = 0;
for (int start = 0; start + n_per_seg <= sample_count; start += n_step) {
++segment_count;
for (std::size_t channel_index = 0; channel_index < channel_count_; ++channel_index) {
double mean = 0.0;
for (int i = 0; i < n_per_seg; ++i) {
mean += cached_samples_[static_cast<std::size_t>(start + i)][channel_index];
}
mean /= static_cast<double>(n_per_seg);
for (int i = 0; i < n_per_seg; ++i) {
segment[static_cast<std::size_t>(i)] =
(cached_samples_[static_cast<std::size_t>(start + i)][channel_index] - mean) *
window[static_cast<std::size_t>(i)];
}
kiss_fftr(cfg, segment.data(), fft_output.data());
for (int bin = 0; bin < n_freq_bin_count; ++bin) {
const double real = fft_output[static_cast<std::size_t>(bin)].r;
const double imag = fft_output[static_cast<std::size_t>(bin)].i;
double psd = (real * real + imag * imag) /
(static_cast<double>(summary.sample_rate) * window_power);
if (bin != 0 && bin != (n_per_seg / 2)) {
psd *= 2.0;
}
psd_accum[channel_index][static_cast<std::size_t>(bin)] += psd;
}
}
}
kiss_fftr_free(cfg);
if (segment_count <= 0) {
return summary;
}
std::vector<double> all_frequencies(static_cast<std::size_t>(n_freq_bin_count), 0.0);
const double freq_resolution = static_cast<double>(summary.sample_rate) / static_cast<double>(n_per_seg);
for (int bin = 0; bin < n_freq_bin_count; ++bin) {
all_frequencies[static_cast<std::size_t>(bin)] = freq_resolution * static_cast<double>(bin);
}
for (int bin = 0; bin < n_freq_bin_count; ++bin) {
const double frequency = all_frequencies[static_cast<std::size_t>(bin)];
if (frequency >= 0.5 && frequency <= 50.0 &&
summary.frequency_count < XYPARSER_WELCH_MAX_FREQUENCY_COUNT) {
summary.frequencies[summary.frequency_count] = frequency;
++summary.frequency_count;
}
}
const auto fill_channel_result = [&](std::size_t raw_channel_index, XYParserLeadChannelNumber lead) {
const std::size_t lead_index = static_cast<std::size_t>(lead);
std::uint32_t selected_index = 0;
for (int bin = 0; bin < n_freq_bin_count; ++bin) {
const double frequency = all_frequencies[static_cast<std::size_t>(bin)];
if (frequency >= 0.5 && frequency <= 50.0 &&
selected_index < summary.frequency_count) {
summary.psd_values[lead_index][selected_index] =
psd_accum[raw_channel_index][static_cast<std::size_t>(bin)] / static_cast<double>(segment_count);
++selected_index;
}
}
for (std::size_t band_index = 0; band_index < kWelchBands.size(); ++band_index) {
int band_bin_count = 0;
double band_sum = 0.0;
for (int bin = 0; bin < n_freq_bin_count; ++bin) {
const double frequency = all_frequencies[static_cast<std::size_t>(bin)];
if (frequency < kWelchBands[band_index].low_hz || frequency > kWelchBands[band_index].high_hz) {
continue;
}
++band_bin_count;
band_sum += psd_accum[raw_channel_index][static_cast<std::size_t>(bin)] / static_cast<double>(segment_count);
}
if (band_bin_count > 0) {
summary.band_values[band_index][lead_index] = band_sum / static_cast<double>(band_bin_count);
}
}
};
if (channel_count_ == 8) {
for (std::size_t i = 0; i < k8ChLeadMap.size(); ++i) {
fill_channel_result(i, k8ChLeadMap[i]);
}
} else {
const std::size_t fill_count = channel_count_ < k64ChLeadMap.size() ? channel_count_ : k64ChLeadMap.size();
for (std::size_t i = 0; i < fill_count; ++i) {
fill_channel_result(i, k64ChLeadMap[i]);
}
}
summary.ok = 1;
return summary;
}

View File

@@ -0,0 +1,34 @@
#pragma once
#include "XYParserApi.h"
#include <array>
#include <cstdint>
#include <deque>
class XYWelchProcessor {
public:
explicit XYWelchProcessor(std::uint8_t channel_count = 0);
void SetChannelCount(std::uint8_t channel_count);
void SetSampleRate(int sample_rate);
void SetEnabled(bool enabled);
void Reset();
void PushFrame(const XYParserFrameSummary& frame);
void PushSample(const std::array<double, XYPARSER_MAX_CHANNELS>& sample);
int Read(XYParserWelchSummary* out_summaries, int max_summaries);
private:
using Sample = std::array<double, XYPARSER_MAX_CHANNELS>;
void ProcessPendingWindows();
XYParserWelchSummary BuildSummaryFromWindow() const;
private:
std::uint8_t channel_count_ = 0;
int sample_rate_ = 250;
bool enabled_ = false;
std::deque<Sample> cached_samples_;
std::deque<XYParserWelchSummary> pending_results_;
};

View File

@@ -0,0 +1,156 @@
/*
* Copyright (c) 2003-2010, Mark Borgerding. All rights reserved.
* This file is part of KISS FFT - https://github.com/mborgerding/kissfft
*
* SPDX-License-Identifier: BSD-3-Clause
* See COPYING file for more information.
*/
#ifndef _kiss_fft_guts_h
#define _kiss_fft_guts_h
#include "kiss_fft.h"
#include "kiss_fft_log.h"
#include <limits.h>
#define MAXFACTORS 32
struct kiss_fft_state
{
int nfft;
int inverse;
int factors[2 * MAXFACTORS];
kiss_fft_cpx twiddles[1];
};
#ifdef FIXED_POINT
#include <stdint.h>
#if (FIXED_POINT == 32)
#define FRACBITS 31
#define SAMPPROD int64_t
#define SAMP_MAX INT32_MAX
#define SAMP_MIN INT32_MIN
#else
#define FRACBITS 15
#define SAMPPROD int32_t
#define SAMP_MAX INT16_MAX
#define SAMP_MIN INT16_MIN
#endif
#if defined(CHECK_OVERFLOW)
#define CHECK_OVERFLOW_OP(a, op, b) \
if((SAMPPROD)(a) op(SAMPPROD)(b) > SAMP_MAX || (SAMPPROD)(a) op(SAMPPROD)(b) < SAMP_MIN) { \
KISS_FFT_WARNING("overflow (%d " #op " %d) = %ld", (a), (b), (SAMPPROD)(a) op(SAMPPROD)(b)); \
}
#endif
#define smul(a, b) ((SAMPPROD)(a) * (b))
#define sround(x) (kiss_fft_scalar)(((x) + (1 << (FRACBITS - 1))) >> FRACBITS)
#define S_MUL(a, b) sround(smul(a, b))
#define C_MUL(m, a, b) \
do { \
(m).r = sround(smul((a).r, (b).r) - smul((a).i, (b).i)); \
(m).i = sround(smul((a).r, (b).i) + smul((a).i, (b).r)); \
} while(0)
#define DIVSCALAR(x, k) ((x) = sround(smul(x, SAMP_MAX / (k))))
#define C_FIXDIV(c, div) \
do { \
DIVSCALAR((c).r, div); \
DIVSCALAR((c).i, div); \
} while(0)
#define C_MULBYSCALAR(c, s) \
do { \
(c).r = sround(smul((c).r, s)); \
(c).i = sround(smul((c).i, s)); \
} while(0)
#else
#define S_MUL(a, b) ((a) * (b))
#define C_MUL(m, a, b) \
do { \
(m).r = (a).r * (b).r - (a).i * (b).i; \
(m).i = (a).r * (b).i + (a).i * (b).r; \
} while(0)
#define C_FIXDIV(c, div)
#define C_MULBYSCALAR(c, s) \
do { \
(c).r *= (s); \
(c).i *= (s); \
} while(0)
#endif
#ifndef CHECK_OVERFLOW_OP
#define CHECK_OVERFLOW_OP(a, op, b)
#endif
#define C_ADD(res, a, b) \
do { \
CHECK_OVERFLOW_OP((a).r, +, (b).r) \
CHECK_OVERFLOW_OP((a).i, +, (b).i) \
(res).r = (a).r + (b).r; \
(res).i = (a).i + (b).i; \
} while(0)
#define C_SUB(res, a, b) \
do { \
CHECK_OVERFLOW_OP((a).r, -, (b).r) \
CHECK_OVERFLOW_OP((a).i, -, (b).i) \
(res).r = (a).r - (b).r; \
(res).i = (a).i - (b).i; \
} while(0)
#define C_ADDTO(res, a) \
do { \
CHECK_OVERFLOW_OP((res).r, +, (a).r) \
CHECK_OVERFLOW_OP((res).i, +, (a).i) \
(res).r += (a).r; \
(res).i += (a).i; \
} while(0)
#define C_SUBFROM(res, a) \
do { \
CHECK_OVERFLOW_OP((res).r, -, (a).r) \
CHECK_OVERFLOW_OP((res).i, -, (a).i) \
(res).r -= (a).r; \
(res).i -= (a).i; \
} while(0)
#ifdef FIXED_POINT
#define KISS_FFT_COS(phase) floor(.5 + SAMP_MAX * cos(phase))
#define KISS_FFT_SIN(phase) floor(.5 + SAMP_MAX * sin(phase))
#define HALF_OF(x) ((x) >> 1)
#elif defined(USE_SIMD)
#define KISS_FFT_COS(phase) _mm_set1_ps(cos(phase))
#define KISS_FFT_SIN(phase) _mm_set1_ps(sin(phase))
#define HALF_OF(x) ((x) * _mm_set1_ps(.5))
#else
#define KISS_FFT_COS(phase) (kiss_fft_scalar) cos(phase)
#define KISS_FFT_SIN(phase) (kiss_fft_scalar) sin(phase)
#define HALF_OF(x) ((x) * ((kiss_fft_scalar).5))
#endif
#define kf_cexp(x, phase) \
do { \
(x)->r = KISS_FFT_COS(phase); \
(x)->i = KISS_FFT_SIN(phase); \
} while(0)
#define pcpx(c) KISS_FFT_DEBUG("%g + %gi\n", (double)((c)->r), (double)((c)->i))
#ifdef KISS_FFT_USE_ALLOCA
#include <alloca.h>
#define KISS_FFT_TMP_ALLOC(nbytes) alloca(nbytes)
#define KISS_FFT_TMP_FREE(ptr)
#else
#define KISS_FFT_TMP_ALLOC(nbytes) KISS_FFT_MALLOC(nbytes)
#define KISS_FFT_TMP_FREE(ptr) KISS_FFT_FREE(ptr)
#endif
#endif /* _kiss_fft_guts_h */

View File

@@ -0,0 +1,186 @@
#include "fir_filter.h"
namespace {
double sinc(const double x)
{
if(x == 0.0) {
return 1.0;
}
return std::sin(M_PI * x) / (M_PI * x);
}
} // namespace
FIR_filter::FIR_filter(int taps, double f1, double f2, char *type, char *window)
: h(taps, 0.0), samples(taps, 0.0)
{
this->idx = 0;
this->taps = taps;
std::vector<double> coeffs;
std::vector<double> weights;
const char *safeType = type != nullptr ? type : const_cast<char *>("");
const char *safeWindow = window != nullptr ? window : const_cast<char *>("");
if(std::strcmp(safeType, "lp") == 0) {
coeffs = lowPass_coefficient(taps, f1);
} else if(std::strcmp(safeType, "hp") == 0) {
coeffs = highPass_coefficient(taps, f1);
} else if(std::strcmp(safeType, "bp") == 0) {
coeffs = bandPass_coefficient(taps, f1, f2);
} else if(std::strcmp(safeType, "sb") == 0) {
coeffs = bandStop_coefficient(taps, f1, f2);
}
if(std::strcmp(safeWindow, "hamming") == 0) {
weights = window_hammig(taps);
} else if(std::strcmp(safeWindow, "triangle") == 0) {
weights = window_triangle(taps);
} else if(std::strcmp(safeWindow, "hanning") == 0) {
weights = window_hanning(taps);
} else if(std::strcmp(safeWindow, "blackman") == 0) {
weights = window_blackman(taps);
}
if(std::strcmp(safeWindow, "") == 0) {
this->h = coeffs;
} else {
for(int n = 0; n < taps; n++) {
this->h[n] = coeffs[n] * weights[n];
}
}
}
FIR_filter::~FIR_filter() = default;
std::vector<double> FIR_filter::getCoefficients()
{
return this->h;
}
std::vector<double> FIR_filter::lowPass_coefficient(int taps, double f)
{
std::vector<int> n(taps, 0);
std::vector<double> coeffs(taps, 0.0);
for(int i = 0; i < taps; i++) {
n[i] = i - taps / 2;
}
for(int i = 0; i < taps; i++) {
coeffs[i] = 2.0 * f * sinc(2.0 * f * n[i]);
}
return coeffs;
}
std::vector<double> FIR_filter::highPass_coefficient(int taps, double f)
{
std::vector<int> n(taps, 0);
std::vector<double> coeffs(taps, 0.0);
for(int i = 0; i < taps; i++) {
n[i] = i - taps / 2;
}
for(int i = 0; i < taps; i++) {
coeffs[i] = sinc(n[i]) - 2.0 * f * sinc(2.0 * f * n[i]);
}
return coeffs;
}
std::vector<double> FIR_filter::bandPass_coefficient(int taps, double f1, double f2)
{
std::vector<int> n(taps, 0);
std::vector<double> coeffs(taps, 0.0);
for(int i = 0; i < taps; i++) {
n[i] = i - taps / 2;
}
for(int i = 0; i < taps; i++) {
coeffs[i] = 2.0 * f1 * sinc(2.0 * f1 * n[i]) - 2.0 * f2 * sinc(2.0 * f2 * n[i]);
}
return coeffs;
}
std::vector<double> FIR_filter::bandStop_coefficient(int taps, double f1, double f2)
{
std::vector<int> n(taps, 0);
std::vector<double> coeffs(taps, 0.0);
for(int i = 0; i < taps; i++) {
n[i] = i - taps / 2;
}
for(int i = 0; i < taps; i++) {
coeffs[i] = 2.0 * f1 * sinc(2.0 * f1 * n[i]) - 2.0 * f2 * sinc(2.0 * f2 * n[i])
+ sinc(n[i]);
}
return coeffs;
}
std::vector<double> FIR_filter::window_hammig(int taps)
{
std::vector<double> weights(taps, 0.0);
constexpr double alpha = 0.54;
constexpr double beta = 0.46;
for(int i = 0; i < taps; i++) {
weights[i] = alpha - beta * std::cos(2.0 * M_PI * i / (taps - 1));
}
return weights;
}
std::vector<double> FIR_filter::window_hanning(int taps)
{
std::vector<double> weights(taps, 0.0);
for(int i = 0; i < taps; i++) {
const double angle = static_cast<double>(M_PI) * i / (taps - 1);
weights[i] = std::sin(angle) * std::sin(angle);
}
return weights;
}
std::vector<double> FIR_filter::window_triangle(int taps)
{
std::vector<double> weights(taps, 0.0);
const double length = static_cast<double>(taps);
for(int i = 0; i < taps; i++) {
weights[i] = 1.0 - std::abs((i - ((taps - 1) / 2.0)) / (length / 2.0));
}
return weights;
}
std::vector<double> FIR_filter::window_blackman(int taps)
{
std::vector<double> weights(taps, 0.0);
constexpr double alpha0 = 0.42;
constexpr double alpha1 = 0.5;
constexpr double alpha2 = 0.08;
for(int i = 0; i < taps; i++) {
weights[i] = alpha0 - alpha1 * std::cos(2.0 * M_PI * i / (taps - 1))
- alpha2 * std::cos(4.0 * M_PI * i / (taps - 1));
}
return weights;
}
double FIR_filter::filter(double new_sample)
{
double result = 0.0;
this->samples[this->idx] = new_sample;
for(int n = 0; n < this->taps; n++) {
result += this->samples[(this->idx + n) % this->taps] * this->h[n];
}
this->idx = (this->idx + 1) % this->taps;
return result;
}

View File

@@ -0,0 +1,42 @@
#ifndef FIR_FILTER_H
#define FIR_FILTER_H
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <vector>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
class FIR_filter
{
public:
FIR_filter(int taps = 0, double f1 = 0, double f2 = 0, char *type = nullptr,
char *window = nullptr);
~FIR_filter();
std::vector<double> getCoefficients();
double filter(double new_sample);
private:
std::vector<double> lowPass_coefficient(int taps, double f);
std::vector<double> highPass_coefficient(int taps, double f);
std::vector<double> bandPass_coefficient(int taps, double f1, double f2);
std::vector<double> bandStop_coefficient(int taps, double f1, double f2);
std::vector<double> window_hammig(int taps);
std::vector<double> window_triangle(int taps);
std::vector<double> window_hanning(int taps);
std::vector<double> window_blackman(int taps);
std::vector<double> h;
std::vector<double> samples;
int idx = 0;
int taps = 0;
};
#endif // FIR_FILTER_H

349
XYParser/third_party/kissfft/kiss_fft.c vendored Normal file
View File

@@ -0,0 +1,349 @@
/*
* Copyright (c) 2003-2010, Mark Borgerding. All rights reserved.
* This file is part of KISS FFT - https://github.com/mborgerding/kissfft
*
* SPDX-License-Identifier: BSD-3-Clause
* See COPYING file for more information.
*/
#include "_kiss_fft_guts.h"
static void kf_bfly2(kiss_fft_cpx *Fout, const size_t fstride, const kiss_fft_cfg st, int m)
{
kiss_fft_cpx *Fout2 = Fout + m;
kiss_fft_cpx *tw1 = st->twiddles;
kiss_fft_cpx t;
do {
C_FIXDIV(*Fout, 2);
C_FIXDIV(*Fout2, 2);
C_MUL(t, *Fout2, *tw1);
tw1 += fstride;
C_SUB(*Fout2, *Fout, t);
C_ADDTO(*Fout, t);
++Fout2;
++Fout;
} while(--m);
}
static void kf_bfly4(kiss_fft_cpx *Fout, const size_t fstride, const kiss_fft_cfg st,
const size_t m)
{
kiss_fft_cpx *tw1;
kiss_fft_cpx *tw2;
kiss_fft_cpx *tw3;
kiss_fft_cpx scratch[6];
size_t k = m;
const size_t m2 = 2 * m;
const size_t m3 = 3 * m;
tw3 = tw2 = tw1 = st->twiddles;
do {
C_FIXDIV(*Fout, 4);
C_FIXDIV(Fout[m], 4);
C_FIXDIV(Fout[m2], 4);
C_FIXDIV(Fout[m3], 4);
C_MUL(scratch[0], Fout[m], *tw1);
C_MUL(scratch[1], Fout[m2], *tw2);
C_MUL(scratch[2], Fout[m3], *tw3);
C_SUB(scratch[5], *Fout, scratch[1]);
C_ADDTO(*Fout, scratch[1]);
C_ADD(scratch[3], scratch[0], scratch[2]);
C_SUB(scratch[4], scratch[0], scratch[2]);
C_SUB(Fout[m2], *Fout, scratch[3]);
tw1 += fstride;
tw2 += fstride * 2;
tw3 += fstride * 3;
C_ADDTO(*Fout, scratch[3]);
if(st->inverse) {
Fout[m].r = scratch[5].r - scratch[4].i;
Fout[m].i = scratch[5].i + scratch[4].r;
Fout[m3].r = scratch[5].r + scratch[4].i;
Fout[m3].i = scratch[5].i - scratch[4].r;
} else {
Fout[m].r = scratch[5].r + scratch[4].i;
Fout[m].i = scratch[5].i - scratch[4].r;
Fout[m3].r = scratch[5].r - scratch[4].i;
Fout[m3].i = scratch[5].i + scratch[4].r;
}
++Fout;
} while(--k);
}
static void kf_bfly3(kiss_fft_cpx *Fout, const size_t fstride, const kiss_fft_cfg st, size_t m)
{
size_t k = m;
const size_t m2 = 2 * m;
kiss_fft_cpx *tw1 = st->twiddles;
kiss_fft_cpx *tw2 = st->twiddles;
kiss_fft_cpx scratch[5];
kiss_fft_cpx epi3 = st->twiddles[fstride * m];
do {
C_FIXDIV(*Fout, 3);
C_FIXDIV(Fout[m], 3);
C_FIXDIV(Fout[m2], 3);
C_MUL(scratch[1], Fout[m], *tw1);
C_MUL(scratch[2], Fout[m2], *tw2);
C_ADD(scratch[3], scratch[1], scratch[2]);
C_SUB(scratch[0], scratch[1], scratch[2]);
tw1 += fstride;
tw2 += fstride * 2;
Fout[m].r = Fout->r - HALF_OF(scratch[3].r);
Fout[m].i = Fout->i - HALF_OF(scratch[3].i);
C_MULBYSCALAR(scratch[0], epi3.i);
C_ADDTO(*Fout, scratch[3]);
Fout[m2].r = Fout[m].r + scratch[0].i;
Fout[m2].i = Fout[m].i - scratch[0].r;
Fout[m].r -= scratch[0].i;
Fout[m].i += scratch[0].r;
++Fout;
} while(--k);
}
static void kf_bfly5(kiss_fft_cpx *Fout, const size_t fstride, const kiss_fft_cfg st, int m)
{
kiss_fft_cpx *Fout0 = Fout;
kiss_fft_cpx *Fout1 = Fout0 + m;
kiss_fft_cpx *Fout2 = Fout0 + 2 * m;
kiss_fft_cpx *Fout3 = Fout0 + 3 * m;
kiss_fft_cpx *Fout4 = Fout0 + 4 * m;
kiss_fft_cpx scratch[13];
kiss_fft_cpx *tw = st->twiddles;
kiss_fft_cpx ya = st->twiddles[fstride * m];
kiss_fft_cpx yb = st->twiddles[fstride * 2 * m];
int u;
for(u = 0; u < m; ++u) {
C_FIXDIV(*Fout0, 5);
C_FIXDIV(*Fout1, 5);
C_FIXDIV(*Fout2, 5);
C_FIXDIV(*Fout3, 5);
C_FIXDIV(*Fout4, 5);
scratch[0] = *Fout0;
C_MUL(scratch[1], *Fout1, tw[u * fstride]);
C_MUL(scratch[2], *Fout2, tw[2 * u * fstride]);
C_MUL(scratch[3], *Fout3, tw[3 * u * fstride]);
C_MUL(scratch[4], *Fout4, tw[4 * u * fstride]);
C_ADD(scratch[7], scratch[1], scratch[4]);
C_SUB(scratch[10], scratch[1], scratch[4]);
C_ADD(scratch[8], scratch[2], scratch[3]);
C_SUB(scratch[9], scratch[2], scratch[3]);
Fout0->r += scratch[7].r + scratch[8].r;
Fout0->i += scratch[7].i + scratch[8].i;
scratch[5].r = scratch[0].r + S_MUL(scratch[7].r, ya.r) + S_MUL(scratch[8].r, yb.r);
scratch[5].i = scratch[0].i + S_MUL(scratch[7].i, ya.r) + S_MUL(scratch[8].i, yb.r);
scratch[6].r = S_MUL(scratch[10].i, ya.i) + S_MUL(scratch[9].i, yb.i);
scratch[6].i = -S_MUL(scratch[10].r, ya.i) - S_MUL(scratch[9].r, yb.i);
C_SUB(*Fout1, scratch[5], scratch[6]);
C_ADD(*Fout4, scratch[5], scratch[6]);
scratch[11].r = scratch[0].r + S_MUL(scratch[7].r, yb.r) + S_MUL(scratch[8].r, ya.r);
scratch[11].i = scratch[0].i + S_MUL(scratch[7].i, yb.r) + S_MUL(scratch[8].i, ya.r);
scratch[12].r = -S_MUL(scratch[10].i, yb.i) + S_MUL(scratch[9].i, ya.i);
scratch[12].i = S_MUL(scratch[10].r, yb.i) - S_MUL(scratch[9].r, ya.i);
C_ADD(*Fout2, scratch[11], scratch[12]);
C_SUB(*Fout3, scratch[11], scratch[12]);
++Fout0;
++Fout1;
++Fout2;
++Fout3;
++Fout4;
}
}
static void kf_bfly_generic(kiss_fft_cpx *Fout, const size_t fstride, const kiss_fft_cfg st,
int m, int p)
{
int u;
kiss_fft_cpx *twiddles = st->twiddles;
kiss_fft_cpx t;
int Norig = st->nfft;
kiss_fft_cpx *scratch = (kiss_fft_cpx *) KISS_FFT_TMP_ALLOC(sizeof(kiss_fft_cpx) * p);
if(scratch == NULL) {
KISS_FFT_ERROR("Memory allocation failed.");
return;
}
for(u = 0; u < m; ++u) {
int k = u;
int q1;
for(q1 = 0; q1 < p; ++q1) {
scratch[q1] = Fout[k];
C_FIXDIV(scratch[q1], p);
k += m;
}
k = u;
for(q1 = 0; q1 < p; ++q1) {
int q;
int twidx = 0;
Fout[k] = scratch[0];
for(q = 1; q < p; ++q) {
twidx += (int) fstride * k;
if(twidx >= Norig) {
twidx -= Norig;
}
C_MUL(t, scratch[q], twiddles[twidx]);
C_ADDTO(Fout[k], t);
}
k += m;
}
}
KISS_FFT_TMP_FREE(scratch);
}
static void kf_work(kiss_fft_cpx *Fout, const kiss_fft_cpx *f, const size_t fstride,
int in_stride, int *factors, const kiss_fft_cfg st)
{
kiss_fft_cpx *Fout_beg = Fout;
const int p = *factors++;
const int m = *factors++;
const kiss_fft_cpx *Fout_end = Fout + p * m;
if(m == 1) {
do {
*Fout = *f;
f += fstride * in_stride;
} while(++Fout != Fout_end);
} else {
do {
kf_work(Fout, f, fstride * p, in_stride, factors, st);
f += fstride * in_stride;
} while((Fout += m) != Fout_end);
}
Fout = Fout_beg;
switch(p) {
case 2: kf_bfly2(Fout, fstride, st, m); break;
case 3: kf_bfly3(Fout, fstride, st, m); break;
case 4: kf_bfly4(Fout, fstride, st, m); break;
case 5: kf_bfly5(Fout, fstride, st, m); break;
default: kf_bfly_generic(Fout, fstride, st, m, p); break;
}
}
static void kf_factor(int n, int *facbuf)
{
int p = 4;
double floor_sqrt = floor(sqrt((double) n));
do {
while(n % p) {
switch(p) {
case 4: p = 2; break;
case 2: p = 3; break;
default: p += 2; break;
}
if(p > floor_sqrt) {
p = n;
}
}
n /= p;
*facbuf++ = p;
*facbuf++ = n;
} while(n > 1);
}
kiss_fft_cfg kiss_fft_alloc(int nfft, int inverse_fft, void *mem, size_t *lenmem)
{
KISS_FFT_ALIGN_CHECK(mem)
kiss_fft_cfg st = NULL;
size_t memneeded = KISS_FFT_ALIGN_SIZE_UP(sizeof(struct kiss_fft_state)
+ sizeof(kiss_fft_cpx) * (nfft - 1));
if(lenmem == NULL) {
st = (kiss_fft_cfg) KISS_FFT_MALLOC(memneeded);
} else {
if(mem != NULL && *lenmem >= memneeded) {
st = (kiss_fft_cfg) mem;
}
*lenmem = memneeded;
}
if(st) {
int i;
st->nfft = nfft;
st->inverse = inverse_fft;
for(i = 0; i < nfft; ++i) {
const double pi = 3.141592653589793238462643383279502884197169399375105820974944;
double phase = -2 * pi * i / nfft;
if(st->inverse) {
phase *= -1;
}
kf_cexp(st->twiddles + i, phase);
}
kf_factor(nfft, st->factors);
}
return st;
}
void kiss_fft_stride(kiss_fft_cfg st, const kiss_fft_cpx *fin, kiss_fft_cpx *fout, int in_stride)
{
if(fin == fout) {
kiss_fft_cpx *tmpbuf;
if(fout == NULL) {
KISS_FFT_ERROR("fout buffer NULL.");
return;
}
tmpbuf = (kiss_fft_cpx *) KISS_FFT_TMP_ALLOC(sizeof(kiss_fft_cpx) * st->nfft);
if(tmpbuf == NULL) {
KISS_FFT_ERROR("Memory allocation error.");
return;
}
kf_work(tmpbuf, fin, 1, in_stride, st->factors, st);
memcpy(fout, tmpbuf, sizeof(kiss_fft_cpx) * st->nfft);
KISS_FFT_TMP_FREE(tmpbuf);
} else {
kf_work(fout, fin, 1, in_stride, st->factors, st);
}
}
void kiss_fft(kiss_fft_cfg cfg, const kiss_fft_cpx *fin, kiss_fft_cpx *fout)
{
kiss_fft_stride(cfg, fin, fout, 1);
}
void kiss_fft_cleanup(void)
{
}
int kiss_fft_next_fast_size(int n)
{
while(1) {
int m = n;
while((m % 2) == 0) {
m /= 2;
}
while((m % 3) == 0) {
m /= 3;
}
while((m % 5) == 0) {
m /= 5;
}
if(m <= 1) {
break;
}
n++;
}
return n;
}

93
XYParser/third_party/kissfft/kiss_fft.h vendored Normal file
View File

@@ -0,0 +1,93 @@
/*
* Copyright (c) 2003-2010, Mark Borgerding. All rights reserved.
* This file is part of KISS FFT - https://github.com/mborgerding/kissfft
*
* SPDX-License-Identifier: BSD-3-Clause
* See COPYING file for more information.
*/
#ifndef KISS_FFT_H
#define KISS_FFT_H
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef KISS_FFT_SHARED
#if defined(_WIN32)
#if defined(KISS_FFT_BUILD)
#define KISS_FFT_API __declspec(dllexport)
#else
#define KISS_FFT_API __declspec(dllimport)
#endif
#else
#define KISS_FFT_API __attribute__((visibility("default")))
#endif
#else
#define KISS_FFT_API
#endif
#ifdef __cplusplus
extern "C" {
#endif
#ifdef USE_SIMD
#include <xmmintrin.h>
#define kiss_fft_scalar __m128
#ifndef KISS_FFT_MALLOC
#define KISS_FFT_MALLOC(nbytes) _mm_malloc(nbytes, 16)
#define KISS_FFT_ALIGN_CHECK(ptr)
#define KISS_FFT_ALIGN_SIZE_UP(size) ((size + 15UL) & ~0xFUL)
#endif
#ifndef KISS_FFT_FREE
#define KISS_FFT_FREE _mm_free
#endif
#else
#define KISS_FFT_ALIGN_CHECK(ptr)
#define KISS_FFT_ALIGN_SIZE_UP(size) (size)
#ifndef KISS_FFT_MALLOC
#define KISS_FFT_MALLOC malloc
#endif
#ifndef KISS_FFT_FREE
#define KISS_FFT_FREE free
#endif
#endif
#ifdef FIXED_POINT
#include <stdint.h>
#if (FIXED_POINT == 32)
#define kiss_fft_scalar int32_t
#else
#define kiss_fft_scalar int16_t
#endif
#else
#ifndef kiss_fft_scalar
#define kiss_fft_scalar double
#endif
#endif
typedef struct {
kiss_fft_scalar r;
kiss_fft_scalar i;
} kiss_fft_cpx;
typedef struct kiss_fft_state *kiss_fft_cfg;
kiss_fft_cfg KISS_FFT_API kiss_fft_alloc(int nfft, int inverse_fft, void *mem, size_t *lenmem);
void KISS_FFT_API kiss_fft(kiss_fft_cfg cfg, const kiss_fft_cpx *fin, kiss_fft_cpx *fout);
void KISS_FFT_API kiss_fft_stride(kiss_fft_cfg cfg, const kiss_fft_cpx *fin, kiss_fft_cpx *fout,
int fin_stride);
#define kiss_fft_free KISS_FFT_FREE
void KISS_FFT_API kiss_fft_cleanup(void);
int KISS_FFT_API kiss_fft_next_fast_size(int n);
#define kiss_fftr_next_fast_size_real(n) (kiss_fft_next_fast_size(((n) + 1) >> 1) << 1)
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) 2003-2010, Mark Borgerding. All rights reserved.
* This file is part of KISS FFT - https://github.com/mborgerding/kissfft
*
* SPDX-License-Identifier: BSD-3-Clause
* See COPYING file for more information.
*/
#ifndef kiss_fft_log_h
#define kiss_fft_log_h
#define ERROR 1
#define WARNING 2
#define INFO 3
#define DEBUG 4
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
#if defined(NDEBUG)
#define KISS_FFT_LOG_MSG(severity, ...) ((void)0)
#else
#define KISS_FFT_LOG_MSG(severity, ...) \
fprintf(stderr, "[" #severity "] " __FILE__ ":" TOSTRING(__LINE__) " "); \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n")
#endif
#define KISS_FFT_ERROR(...) KISS_FFT_LOG_MSG(ERROR, __VA_ARGS__)
#define KISS_FFT_WARNING(...) KISS_FFT_LOG_MSG(WARNING, __VA_ARGS__)
#define KISS_FFT_INFO(...) KISS_FFT_LOG_MSG(INFO, __VA_ARGS__)
#define KISS_FFT_DEBUG(...) KISS_FFT_LOG_MSG(DEBUG, __VA_ARGS__)
#endif /* kiss_fft_log_h */

159
XYParser/third_party/kissfft/kiss_fftr.c vendored Normal file
View File

@@ -0,0 +1,159 @@
/*
* Copyright (c) 2003-2004, Mark Borgerding. All rights reserved.
* This file is part of KISS FFT - https://github.com/mborgerding/kissfft
*
* SPDX-License-Identifier: BSD-3-Clause
* See COPYING file for more information.
*/
#include "kiss_fftr.h"
#include "_kiss_fft_guts.h"
struct kiss_fftr_state
{
kiss_fft_cfg substate;
kiss_fft_cpx *tmpbuf;
kiss_fft_cpx *super_twiddles;
#ifdef USE_SIMD
void *pad;
#endif
};
kiss_fftr_cfg kiss_fftr_alloc(int nfft, int inverse_fft, void *mem, size_t *lenmem)
{
KISS_FFT_ALIGN_CHECK(mem)
int i;
kiss_fftr_cfg st = NULL;
size_t subsize = 0;
size_t memneeded;
if(nfft & 1) {
KISS_FFT_ERROR("Real FFT optimization must be even.");
return NULL;
}
nfft >>= 1;
kiss_fft_alloc(nfft, inverse_fft, NULL, &subsize);
memneeded = sizeof(struct kiss_fftr_state) + subsize + sizeof(kiss_fft_cpx) * (nfft * 3 / 2);
if(lenmem == NULL) {
st = (kiss_fftr_cfg) KISS_FFT_MALLOC(memneeded);
} else {
if(*lenmem >= memneeded) {
st = (kiss_fftr_cfg) mem;
}
*lenmem = memneeded;
}
if(!st) {
return NULL;
}
st->substate = (kiss_fft_cfg) (st + 1);
st->tmpbuf = (kiss_fft_cpx *) (((char *) st->substate) + subsize);
st->super_twiddles = st->tmpbuf + nfft;
kiss_fft_alloc(nfft, inverse_fft, st->substate, &subsize);
for(i = 0; i < nfft / 2; ++i) {
double phase = -3.14159265358979323846264338327 * ((double) (i + 1) / nfft + .5);
if(inverse_fft) {
phase *= -1;
}
kf_cexp(st->super_twiddles + i, phase);
}
return st;
}
void kiss_fftr(kiss_fftr_cfg st, const kiss_fft_scalar *timedata, kiss_fft_cpx *freqdata)
{
int k;
int ncfft;
kiss_fft_cpx fpnk;
kiss_fft_cpx fpk;
kiss_fft_cpx f1k;
kiss_fft_cpx f2k;
kiss_fft_cpx tw;
kiss_fft_cpx tdc;
if(st->substate->inverse) {
KISS_FFT_ERROR("kiss fft usage error: improper alloc");
return;
}
ncfft = st->substate->nfft;
kiss_fft(st->substate, (const kiss_fft_cpx *) timedata, st->tmpbuf);
tdc.r = st->tmpbuf[0].r;
tdc.i = st->tmpbuf[0].i;
C_FIXDIV(tdc, 2);
CHECK_OVERFLOW_OP(tdc.r, +, tdc.i);
CHECK_OVERFLOW_OP(tdc.r, -, tdc.i);
freqdata[0].r = tdc.r + tdc.i;
freqdata[ncfft].r = tdc.r - tdc.i;
#ifdef USE_SIMD
freqdata[ncfft].i = freqdata[0].i = _mm_set1_ps(0);
#else
freqdata[ncfft].i = freqdata[0].i = 0;
#endif
for(k = 1; k <= ncfft / 2; ++k) {
fpk = st->tmpbuf[k];
fpnk.r = st->tmpbuf[ncfft - k].r;
fpnk.i = -st->tmpbuf[ncfft - k].i;
C_FIXDIV(fpk, 2);
C_FIXDIV(fpnk, 2);
C_ADD(f1k, fpk, fpnk);
C_SUB(f2k, fpk, fpnk);
C_MUL(tw, f2k, st->super_twiddles[k - 1]);
freqdata[k].r = HALF_OF(f1k.r + tw.r);
freqdata[k].i = HALF_OF(f1k.i + tw.i);
freqdata[ncfft - k].r = HALF_OF(f1k.r - tw.r);
freqdata[ncfft - k].i = HALF_OF(tw.i - f1k.i);
}
}
void kiss_fftri(kiss_fftr_cfg st, const kiss_fft_cpx *freqdata, kiss_fft_scalar *timedata)
{
int k;
int ncfft;
if(st->substate->inverse == 0) {
KISS_FFT_ERROR("kiss fft usage error: improper alloc");
return;
}
ncfft = st->substate->nfft;
st->tmpbuf[0].r = freqdata[0].r + freqdata[ncfft].r;
st->tmpbuf[0].i = freqdata[0].r - freqdata[ncfft].r;
C_FIXDIV(st->tmpbuf[0], 2);
for(k = 1; k <= ncfft / 2; ++k) {
kiss_fft_cpx fk;
kiss_fft_cpx fnkc;
kiss_fft_cpx fek;
kiss_fft_cpx fok;
kiss_fft_cpx tmp;
fk = freqdata[k];
fnkc.r = freqdata[ncfft - k].r;
fnkc.i = -freqdata[ncfft - k].i;
C_FIXDIV(fk, 2);
C_FIXDIV(fnkc, 2);
C_ADD(fek, fk, fnkc);
C_SUB(tmp, fk, fnkc);
C_MUL(fok, tmp, st->super_twiddles[k - 1]);
C_ADD(st->tmpbuf[k], fek, fok);
C_SUB(st->tmpbuf[ncfft - k], fek, fok);
#ifdef USE_SIMD
st->tmpbuf[ncfft - k].i *= _mm_set1_ps(-1.0);
#else
st->tmpbuf[ncfft - k].i *= -1;
#endif
}
kiss_fft(st->substate, st->tmpbuf, (kiss_fft_cpx *) timedata);
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2003-2004, Mark Borgerding. All rights reserved.
* This file is part of KISS FFT - https://github.com/mborgerding/kissfft
*
* SPDX-License-Identifier: BSD-3-Clause
* See COPYING file for more information.
*/
#ifndef KISS_FTR_H
#define KISS_FTR_H
#include "kiss_fft.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct kiss_fftr_state *kiss_fftr_cfg;
kiss_fftr_cfg KISS_FFT_API kiss_fftr_alloc(int nfft, int inverse_fft, void *mem, size_t *lenmem);
void KISS_FFT_API kiss_fftr(kiss_fftr_cfg cfg, const kiss_fft_scalar *timedata,
kiss_fft_cpx *freqdata);
void KISS_FFT_API kiss_fftri(kiss_fftr_cfg cfg, const kiss_fft_cpx *freqdata,
kiss_fft_scalar *timedata);
#define kiss_fftr_free KISS_FFT_FREE
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,37 @@
#pragma once
#include <cmath>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
inline void no_window_function(int window_len, double *wind)
{
for(int i = 0; i < window_len; i++) {
wind[i] = 1.0;
}
}
inline void hanning_function(int window_len, double *wind)
{
for(int i = 0; i < window_len; i++) {
wind[i] = 0.5 - 0.5 * std::cos(2.0 * M_PI * i / window_len);
}
}
inline void hamming_function(int window_len, double *wind)
{
for(int i = 0; i < window_len; i++) {
wind[i] = 0.54 - 0.46 * std::cos(2.0 * M_PI * i / window_len);
}
}
inline void blackman_harris_function(int window_len, double *wind)
{
for(int i = 0; i < window_len; i++) {
wind[i] = 0.355768 - 0.487396 * std::cos(2.0 * M_PI * i / window_len)
+ 0.144232 * std::cos(4.0 * M_PI * i / window_len)
- 0.012604 * std::cos(6.0 * M_PI * i / window_len);
}
}