diff --git a/CMakeLists.txt b/CMakeLists.txt index b2dd0c9..10e2a29 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/include/filter/filter/filter.hpp b/include/filter/filter/filter.hpp new file mode 100644 index 0000000..50fa37b --- /dev/null +++ b/include/filter/filter/filter.hpp @@ -0,0 +1,21 @@ +#ifndef FILTER_IFILTER_H +#define FILTER_IFILTER_H + +#include +#include + +namespace Filter { + +class IFilter { + protected: + std::vector impulseResponse; + + public: + IFilter(std::size_t size); + virtual ~IFilter(){}; + + void ConvolveIQ(const std::vector& input, std::vector& output); +}; +} // namespace Filter + +#endif \ No newline at end of file diff --git a/include/filter/filter/low_pass_filter.hpp b/include/filter/filter/low_pass_filter.hpp new file mode 100644 index 0000000..37c588b --- /dev/null +++ b/include/filter/filter/low_pass_filter.hpp @@ -0,0 +1,18 @@ +#ifndef FILTER_LOW_PASS_FILTER_H +#define FILTER_LOW_PASS_FILTER_H + +#include + +#include +#include + +namespace Filter { + +class LowPassFilter : public IFilter { + public: + LowPassFilter(std::size_t size, std::size_t cutoffHz, std::size_t sampleRateHz, const std::vector& window); + ~LowPassFilter() override{}; +}; +} // namespace Filter + +#endif \ No newline at end of file diff --git a/include/filter/window/hamming_window.hpp b/include/filter/window/hamming_window.hpp new file mode 100644 index 0000000..c40767e --- /dev/null +++ b/include/filter/window/hamming_window.hpp @@ -0,0 +1,21 @@ +#ifndef FILTER_HAMMING_WINDOW_H +#define FILTER_HAMMING_WINDOW_H + +#include +#include +#include + +namespace Filter { +class HammingWindow : public IWindow { + private: + std::vector window; + + public: + HammingWindow(std::size_t width); + + ~HammingWindow() override {} + const std::vector& getWindow() override { return window; } +}; +} // namespace Filter + +#endif \ No newline at end of file diff --git a/include/filter/window/window.hpp b/include/filter/window/window.hpp new file mode 100644 index 0000000..e451688 --- /dev/null +++ b/include/filter/window/window.hpp @@ -0,0 +1,14 @@ +#ifndef FILTER_IWINDOW_H +#define FILTER_IWINDOW_H + +#include + +namespace Filter { +class IWindow { + public: + virtual ~IWindow() {} + virtual const std::vector& getWindow() = 0; +}; +} // namespace Filter + +#endif \ No newline at end of file diff --git a/src/filter/CMakeLists.txt b/src/filter/CMakeLists.txt new file mode 100644 index 0000000..b92e6da --- /dev/null +++ b/src/filter/CMakeLists.txt @@ -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 +) \ No newline at end of file diff --git a/src/filter/filter/filter.cpp b/src/filter/filter/filter.cpp new file mode 100644 index 0000000..0c9668d --- /dev/null +++ b/src/filter/filter/filter.cpp @@ -0,0 +1,31 @@ +#include +#include + +#include + +namespace Filter { +IFilter::IFilter(std::size_t size) { + impulseResponse.reserve(size); +} + +void IFilter::ConvolveIQ(const std::vector& input, std::vector& 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 \ No newline at end of file diff --git a/src/filter/filter/low_pass_filter.cpp b/src/filter/filter/low_pass_filter.cpp new file mode 100644 index 0000000..e0f4de4 --- /dev/null +++ b/src/filter/filter/low_pass_filter.cpp @@ -0,0 +1,22 @@ +#include +#include +#include + +#include + +namespace Filter { +LowPassFilter::LowPassFilter(std::size_t size, std::size_t cutoffHz, std::size_t sampleRateHz, const std::vector& 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 \ No newline at end of file diff --git a/src/filter/main.cpp b/src/filter/main.cpp new file mode 100644 index 0000000..bd50f55 --- /dev/null +++ b/src/filter/main.cpp @@ -0,0 +1,54 @@ +#include +#include +#include +#include + +#include +#include + +#include + +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 inputBuffer; + inputBuffer.reserve(BUFFER_SIZE); + + std::vector 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; +} \ No newline at end of file diff --git a/src/filter/window/hamming_window.cpp b/src/filter/window/hamming_window.cpp new file mode 100644 index 0000000..51ce13a --- /dev/null +++ b/src/filter/window/hamming_window.cpp @@ -0,0 +1,13 @@ +#include +#include + +#include + +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 \ No newline at end of file