basic low pass filter app

This commit is contained in:
BENEDEK László 2025-03-14 03:09:16 +01:00
parent 384c189b82
commit ac5121e0d2
10 changed files with 203 additions and 1 deletions

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.12)
project(pico-radio C)
project(pico-radio)
set(CMAKE_EXPORT_COMPILE_COMMANDS true)
@ -8,3 +8,4 @@ include_directories(include /opt/picoscope/include third-party/c-flags/single-he
link_directories(/opt/picoscope/lib)
add_subdirectory(src/recorder)
add_subdirectory(src/filter)

View File

@ -0,0 +1,21 @@
#ifndef FILTER_IFILTER_H
#define FILTER_IFILTER_H
#include <cstdint>
#include <vector>
namespace Filter {
class IFilter {
protected:
std::vector<float> impulseResponse;
public:
IFilter(std::size_t size);
virtual ~IFilter(){};
void ConvolveIQ(const std::vector<float>& input, std::vector<float>& output);
};
} // namespace Filter
#endif

View File

@ -0,0 +1,18 @@
#ifndef FILTER_LOW_PASS_FILTER_H
#define FILTER_LOW_PASS_FILTER_H
#include <vector>
#include <filter/filter/filter.hpp>
#include <filter/window/window.hpp>
namespace Filter {
class LowPassFilter : public IFilter {
public:
LowPassFilter(std::size_t size, std::size_t cutoffHz, std::size_t sampleRateHz, const std::vector<float>& window);
~LowPassFilter() override{};
};
} // namespace Filter
#endif

View File

@ -0,0 +1,21 @@
#ifndef FILTER_HAMMING_WINDOW_H
#define FILTER_HAMMING_WINDOW_H
#include <cstddef>
#include <vector>
#include <filter/window/window.hpp>
namespace Filter {
class HammingWindow : public IWindow {
private:
std::vector<float> window;
public:
HammingWindow(std::size_t width);
~HammingWindow() override {}
const std::vector<float>& getWindow() override { return window; }
};
} // namespace Filter
#endif

View File

@ -0,0 +1,14 @@
#ifndef FILTER_IWINDOW_H
#define FILTER_IWINDOW_H
#include <vector>
namespace Filter {
class IWindow {
public:
virtual ~IWindow() {}
virtual const std::vector<float>& getWindow() = 0;
};
} // namespace Filter
#endif

View File

@ -0,0 +1,7 @@
file(GLOB_RECURSE SOURCES "./*.cpp")
add_executable(filter ${SOURCES})
target_compile_options(filter PRIVATE -Wall -Wextra)
set_target_properties(filter PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
CXX_STANDARD 20
)

View File

@ -0,0 +1,31 @@
#include <cstddef>
#include <vector>
#include <filter/filter/filter.hpp>
namespace Filter {
IFilter::IFilter(std::size_t size) {
impulseResponse.reserve(size);
}
void IFilter::ConvolveIQ(const std::vector<float>& input, std::vector<float>& output) {
// TODO: SIMD
auto itIn = input.begin();
auto itOut = output.begin();
while (itIn != input.end()) {
for (float h : impulseResponse) {
// I
*itOut += *itIn * h;
itIn++;
itOut++;
// Q
*itOut += *itIn * h;
itIn++;
itOut++;
}
}
}
} // namespace Filter

View File

@ -0,0 +1,22 @@
#include <cmath>
#include <numbers>
#include <vector>
#include <filter/filter/low_pass_filter.hpp>
namespace Filter {
LowPassFilter::LowPassFilter(std::size_t size, std::size_t cutoffHz, std::size_t sampleRateHz, const std::vector<float>& window) : IFilter(size) {
for (std::size_t i = 0; i < size; i++) {
std::size_t shift = std::round(size / 2);
float sampleTime = 1.0f / sampleRateHz;
if (shift != i) {
IFilter::impulseResponse[i] = std::sin(2.0f * std::numbers::pi * sampleTime * (i - shift)) / (std::numbers::pi * sampleTime * (i - shift));
} else {
IFilter::impulseResponse[i] = 2.0f * cutoffHz;
}
IFilter::impulseResponse[i] *= sampleTime * window[i];
}
}
} // namespace Filter

54
src/filter/main.cpp Normal file
View File

@ -0,0 +1,54 @@
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#include <filter/filter/low_pass_filter.hpp>
#include <filter/window/hamming_window.hpp>
#include <c-flags.h>
const std::size_t BUFFER_SIZE = 256;
using namespace Filter;
int main(int argc, char** argv) {
(void)argc;
(void)argv;
// prepare pipes for binary data
std::freopen(nullptr, "rb", stdin);
if (std::ferror(stdin)) {
std::cerr << "Failed to reopen stdin: " << std::strerror(errno) << std::endl;
return errno;
}
std::freopen(nullptr, "wb", stdout);
if (std::ferror(stdout)) {
std::cerr << "Failed to reopen stdout: " << std::strerror(errno) << std::endl;
return errno;
}
// create low pass filter
LowPassFilter lpf(BUFFER_SIZE, 1000, 10000, HammingWindow(BUFFER_SIZE).getWindow());
// read data
std::vector<float> inputBuffer;
inputBuffer.reserve(BUFFER_SIZE);
std::vector<float> outputBuffer;
outputBuffer.reserve(BUFFER_SIZE);
std::size_t len;
while ((len = std::fread(inputBuffer.data(), sizeof(inputBuffer[0]), BUFFER_SIZE, stdin)) > 0) {
if (std::ferror(stdin) && !std::feof(stdin)) {
std::cerr << "Failed to read from stdin: " << std::strerror(errno) << std::endl;
return errno;
}
lpf.ConvolveIQ(inputBuffer, outputBuffer);
}
return 0;
}

View File

@ -0,0 +1,13 @@
#include <cmath>
#include <numbers>
#include <filter/window/hamming_window.hpp>
namespace Filter {
HammingWindow::HammingWindow(std::size_t width) {
window.reserve(width);
for (std::size_t i = 0; i < width; i++) {
window[i] = (25 / 46.0) - (21 / 46.0) * std::cos(2 * std::numbers::pi * i / width);
}
}
} // namespace Filter