This commit is contained in:
AlexandreRouma
2025-09-09 17:28:30 -04:00
parent b9280ddae1
commit ae39eed0cb
26 changed files with 261 additions and 690 deletions

3
.gitignore vendored
View File

@@ -1,3 +1,4 @@
.vscode/
build/
.old/
.old/
unfinished/

View File

@@ -9,7 +9,6 @@ target_include_directories(${PROJECT_NAME} PRIVATE "/")
target_compile_options(${PROJECT_NAME} PRIVATE /std:c++20)
if (MSVC)
# Lib path
target_link_directories(${PROJECT_NAME} PUBLIC "C:/Program Files/PothosSDR/lib/")

View File

@@ -1,112 +0,0 @@
#include "block.h"
#include <stdexcept>
namespace dsp {
Block::Block() {}
Block::~Block() {
// Stop the worker
stop();
}
void Block::start() {
// Acquire worker variables
std::lock_guard<std::mutex> lck(workerMtx);
// Do nothing if the block is already running
if (_running) { return; }
// Mark as running
_running = true;
// Start the worker thread
workerThread = std::thread(&Block::worker, this);
}
void Block::stop() {
// Acquire worker variables
std::lock_guard<std::mutex> lck(workerMtx);
// Do nothing if the block is not running
if (!_running) { return; }
// Set the receive stop flag on all input streams
for (const auto& input : inputs) {
input->stopReceiver();
}
// Set the send stop flag on all output streams
for (const auto& output : outputs) {
output->stopSender();
}
// Wait for the thread to exist
if (workerThread.joinable()) { workerThread.join(); }
// Clear the receive stop flag on all input streams
for (const auto& input : inputs) {
input->clearRecvStop();
}
// Clear the send stop flag on all output streams
for (const auto& output : outputs) {
output->clearSendStop();
}
// Mark as not running
_running = false;
}
bool Block::running() const {
// Acquire worker variables
std::lock_guard<std::mutex> lck(workerMtx);
// Return run state
return _running;
}
void Block::registerInput(StopNotifier* input) {
// Acquire worker variables
std::lock_guard<std::mutex> lck(workerMtx);
// Save to the input list
inputs.push_back(input);
}
void Block::unregisterInput(StopNotifier* input) {
// Acquire worker variables
std::lock_guard<std::mutex> lck(workerMtx);
// Find the notifier
auto it = std::find(inputs.begin(), inputs.end(), input);
if (it == inputs.end()) { return; }
// Remove it from the list
inputs.erase(it);
}
void Block::registerOutput(StopNotifier* output) {
// Acquire worker variables
std::lock_guard<std::mutex> lck(workerMtx);
// Save to the output list
outputs.push_back(output);
}
void Block::unregisterOutput(StopNotifier* output) {
// Acquire worker variables
std::lock_guard<std::mutex> lck(workerMtx);
// Find the notifier
auto it = std::find(outputs.begin(), outputs.end(), output);
if (it == outputs.end()) { return; }
// Remove it from the list
inputs.erase(it);
}
void Block::worker() {
// Call the run function repeatedly
while (run());
}
}

View File

@@ -1,107 +0,0 @@
#pragma once
#include <thread>
#include <mutex>
#include <vector>
namespace dsp {
/**
* Interface to be used by any blocking class (stream, mailbox, etc...) to allow cancelling an operation.
*/
class StopNotifier {
public:
/**
* Notify the sending thread that it should stop.
*/
virtual void stopSender() = 0;
/**
* Clear the sender stop flag to allow restarting the sender thread.
*/
virtual void clearSendStop() = 0;
/**
* Notify the receiving thread that it should stop.
*/
virtual void stopReceiver() = 0;
/**
* Clear the receiver stop flag to allow restarting the sender thread.
*/
virtual void clearRecvStop() = 0;
};
/**
* General DSP block class handling the worker thread start/stop operations.
* All input and output stop notifiers (usually streams) of the derived blocks must be registered using the appropriate functions.
* This class is thread-safe.
*/
class Block {
public:
// Default constructor
Block();
// Destructor
virtual ~Block();
/**
* Start the block's worker thread.
*/
void start();
/**
* Stop the block's worker thread.
*/
void stop();
/**
* Check wether or not the block's worker thread is running.
*/
bool running() const;
protected:
/**
* Register an input stop notifier.
* @param input Input stop notifier to register.
*/
void registerInput(StopNotifier* input);
/**
* Unregister an input stop notifier.
* @param input Input stop notifier to unregister.
*/
void unregisterInput(StopNotifier* input);
/**
* Register an output stop notifier.
* @param input Output stop notifier to register.
*/
void registerOutput(StopNotifier* output);
/**
* Unregister an output stop notifier.
* @param input Output stop notifier to unregister.
*/
void unregisterOutput(StopNotifier* output);
// TODO: Pause/Resume for when inputs/outputs change to avoid needing to restart a thread
/**
* Run the DSP code.
* @return False if the worker thread should stop. True otherwise.
*/
virtual bool run() = 0;
private:
/**
* Worker thread function.
*/
void worker();
// Worker variables
mutable std::mutex workerMtx;
std::thread workerThread;
std::vector<StopNotifier*> inputs;
std::vector<StopNotifier*> outputs;
bool _running = false;
};
}

View File

@@ -25,10 +25,10 @@ namespace dsp {
template <class T>
Buffer<T>::Buffer(const Buffer<T>& b) {
// Allocate buffer
realloc(b.count);
realloc(b.capacity);
// Copy data over
memcpy(buffer, b.buffer, b.count * sizeof(T));
memcpy(buffer, b.buffer, b.capacity * sizeof(T));
}
template <class T>
@@ -55,6 +55,9 @@ namespace dsp {
// Copy over the data
memcpy(buffer, b.buffer, capacity * sizeof(T));
// Return self
return *this;
}
template <class T>
@@ -69,22 +72,26 @@ namespace dsp {
// Neutralize the original
b.buffer = NULL;
b.capacity = 0;
// Return self
return *this;
}
template <class T>
void Buffer<T>::realloc(size_t size, ReallocBehavior behavior) {
// Select the desired behavior
T* newbuf;
switch (behavior) {
case REALLOC_DISCARD:
// Free the current buffer
volk_free(buffer);
// Allocate a new buffer
buffer = volk_malloc(size * sizeof(T));
buffer = (T*)volk_malloc(size * sizeof(T), volk_get_alignment());
break;
case REALLOC_KEEP:
// Allocate a new buffer
T* newbuf = volk_malloc(size * sizeof(T));
newbuf = (T*)volk_malloc(size * sizeof(T), volk_get_alignment());
// Copy the existing data
memcpy(newbuf, buffer, std::min<size_t>(capacity, size));
@@ -97,13 +104,15 @@ namespace dsp {
volk_free(buffer);
// Allocate a new buffer
buffer = volk_malloc(size * sizeof(T));
buffer = (T*)volk_malloc(size * sizeof(T), volk_get_alignment());
// Zero-out the new buffer
memset(buffer, 0, size);
break;
case REALLOC_KEEP_AND_ZERO:
// TODO
return;
break;
}
// Update the current capacity

View File

@@ -55,6 +55,8 @@ namespace dsp {
re = b;
}
// TODO: Define in-place operators
/**
* Real component.
*/

5
dsp/constants.h Normal file
View File

@@ -0,0 +1,5 @@
#pragma once
#include <math.h>
#define DSP_PI ((float)3.141592653589793)
#define DSP_SQRT2 ((float)1.414213562373095)

View File

@@ -1,94 +0,0 @@
#include "fm.h"
#define _USE_MATH_DEFINES
#include <math.h>
namespace dsp::demod {
FM_s::FM_s() {}
FM_s::FM_s(float deviation, float samplerate) {
// Save the settings
this->deviation = deviation;
this->samplerate = samplerate;
// Update the normalization factor
normFact = samplerate / (2.0f * M_PI * deviation);
}
float FM_s::getDeviation() const {
// Acquire the settings mutex
std::lock_guard<std::recursive_mutex> lck(settingsMtx);
// Return the deviation
return deviation;
}
void FM_s::setDeviation(float deviation) {
// Acquire the settings mutex
std::lock_guard<std::recursive_mutex> lck(settingsMtx);
// Update the deviation
this->deviation = deviation;
// Update the normalization factor
normFact = samplerate / (2.0f * M_PI * deviation);
}
float FM_s::getSamplerate() const {
// Acquire the settings mutex
std::lock_guard<std::recursive_mutex> lck(settingsMtx);
// Return the samplerate
return samplerate;
}
void FM_s::setSamplerate(float samplerate) {
// Acquire the settings mutex
std::lock_guard<std::recursive_mutex> lck(settingsMtx);
// Update the samplerate
this->samplerate = samplerate;
// Update the normalization factor
normFact = samplerate / (2.0f * M_PI * deviation);
}
void FM_s::reset() {
// Acquire the settings mutex
std::lock_guard<std::recursive_mutex> lck(settingsMtx);
// Set the current phase to zero
phase = 0.0f;
}
int FM_s::process(const Complex* in, float* out, int count) {
for (int i = 0; i < count; i++) {
// Get the current phase
float cphase = in[i].phase();
// Compute the difference with the last phase
// TODO
//out[i] = math::normalizePhase(cphase - phase) * normFact;
// Save the current phase for the next iteration
phase = cphase;
}
return count;
}
FM::FM() {}
FM::FM(float deviation, float samplerate) :
FM_s(deviation, samplerate),
ProcessorAsync(this)
{}
FM::FM(Stream<Complex>* in, float deviation, float samplerate) :
FM_s(deviation, samplerate),
ProcessorAsync(this, in)
{}
FM::FM(Stream<Complex>* in, Stream<float>* out, float deviation, float samplerate) :
FM_s(deviation, samplerate),
ProcessorAsync(this, in, out)
{}
}

View File

@@ -1,100 +0,0 @@
#pragma once
#include "../processor.h"
#include "../complex.h"
namespace dsp::demod {
/**
* FM demodulator (Synchronous).
* This class is thread-safe except for `process()`.
*/
class FM_s : public ProcessorSync<Complex, float> {
public:
// Default constructor
FM_s();
/**
* Create an FM demodulator.
* @param deviation Deviation of the FM signal in Hz.
* @param samplerate Samplerate of the signal in Hz.
*/
FM_s(float deviation, float samplerate);
/**
* Get the deviation.
* @return Deviation of the FM signal in Hz.
*/
float getDeviation() const;
/**
* Set the deviation.
* @param deviation Deviation of the FM signal in Hz.
*/
void setDeviation(float deviation);
/**
* Get the samplerate.
* @return Samplerate of the signal in Hz.
*/
float getSamplerate() const;
/**
* Set the samplerate.
* @param deviation Samplerate of the signal in Hz.
*/
void setSamplerate(float samplerate);
/**
* Reset the state of the demodulator.
*/
void reset();
/**
* Demodulate a FM-modulated signal. A lock must be acquired using `getLock()` prior to invoking if settings could be set from another thread.
* @param in Modulated signal buffer.
* @param out Demodulated signal buffer.
* @param count Number of samples in the input buffer.
* @return Number of samples in the output buffer. Will always be equal to the number of samples in the input buffer.
*/
int process(const Complex* in, float* out, int count);
private:
float deviation = 0.0f;
float samplerate = 0.0f;
float normFact = 1.0f;
float phase = 0.0f;
};
/**
* FM demodulator.
* This class is thread-safe.
*/
class FM : public FM_s, public ProcessorAsync<Complex, float> {
public:
// Default constructor
FM();
/**
* Create an FM demodulator.
* @param deviation Deviation of the FM signal in Hz.
* @param samplerate Samplerate of the signal in Hz.
*/
FM(float deviation, float samplerate);
/**
* Create an FM demodulator.
* @param in Modulated signal stream.
* @param deviation Deviation of the FM signal in Hz.
* @param samplerate Samplerate of the signal in Hz.
*/
FM(Stream<Complex>* in, float deviation, float samplerate);
/**
* Create an FM demodulator.
* @param in Modulated signal stream.
* @param out Demodulated signal stream.
* @param deviation Deviation of the FM signal in Hz.
* @param samplerate Samplerate of the signal in Hz.
*/
FM(Stream<Complex>* in, Stream<float>* out, float deviation, float samplerate);
};
}

View File

View File

@@ -1,77 +0,0 @@
#pragma once
#include "../processor.h"
#include "../taps.h"
namespace dsp::filter {
/**
* Finite Inpulse Response filter (Synchronous).
* This class is thread-safe except for `process()`.
*/
template <class SAMP_T, class TAPS_T>
class FIR_s : public ProcessorSync<SAMP_T, SAMP_T> {
public:
/**
* Create a FIR filter.
* @param taps Filter taps.
*/
FIR_s(const Taps<TAPS_T>& taps);
/**
* Get the filter taps.
* @return Filter taps.
*/
Taps<TAPS_T> getTaps();
/**
* Set the filter taps.
* @param taps Filter taps.
*/
void setTaps(const Taps<TAPS_T>& taps);
/**
* Reset the state of the filter.
*/
void reset();
/**
* Filter samples.
* @param in Input sample buffer.
* @param out Filtered sample buffer.
* @param count Number of samples in the input buffer.
* @return Number of samples in the output buffer. Will always be equal to the number of samples in the input buffer.
*/
int process(const SAMP_T* in, SAMP_T* out, int count);
private:
Taps<TAPS_T> taps;
};
/**
* Finite Inpulse Response filter.
* This class is thread-safe except for `process()`.
*/
template <class SAMP_T, class TAPS_T>
class FIR : public FIR_s<SAMP_T, TAPS_T>, public ProcessorAsync<SAMP_T, SAMP_T> {
public:
/**
* Create a FIR filter.
* @param taps Filter taps.
*/
FIR(const Taps<TAPS_T>& taps);
/**
* Create a FIR filter.
* @param in Input samples.
* @param taps Filter taps.
*/
FIR(Stream<SAMP_T>* in, const Taps<TAPS_T>& taps);
/**
* Create a FIR filter.
* @param in Input samples.
* @param in Filtered samples.
* @param taps Filter taps.
*/
FIR(Stream<SAMP_T>* in, Stream<SAMP_T>* out, const Taps<TAPS_T>& taps);
};
}

View File

@@ -1,164 +0,0 @@
#pragma once
#include <type_traits>
#include "block.h"
#include "stream.h"
namespace dsp {
// TODO: Deal with the fact that always locking will be slow in hier blocks...
/**
* Class representing a DSP kernel taking one input and one output.
* Dervied classes must be thread-safe by locking the provided `settingsMtx` mutex in any function changing settings.
*/
template <class IN, class OUT>
class ProcessorKernel {
public:
// Destructor
virtual ~ProcessorKernel() {}
// TODO: Copy/Move Constructor/Operator
/**
* Acquire a lock to the settings of the kernel. Mandatory if settings are changed in a different thread than the processing function.
* @return Lock to the settings of the block.
*/
inline std::lock_guard<std::recursive_mutex> getLock() const {
return std::lock_guard<std::recursive_mutex>(settingsMtx);
}
/**
* Process samples. This function is NOT thread-safe.
* @param in Input buffer.
* @param out Output buffer.
* @param count Number of samples in the input buffer.
* @return Number of samples in the output buffer.
*/
virtual int process(const IN* in, OUT* out, int count) = 0;
protected:
/**
* Mutex to be used for kernel settings.
*/
mutable std::recursive_mutex settingsMtx;
};
template <class IN, class OUT>
class ProcessorBlock : public Block {
public:
// Default constructor
ProcessorBlock() {
// TODO: Maybe something to do to prevent bad shit if started?
}
/**
* TODO
*/
ProcessorBlock(ProcessorSync<IN, OUT>* proc) {
// Save the pointer to the processor
this->proc = proc;
// Set the streams
setInput(NULL);
setOutput(NULL);
}
/**
* TODO
*/
ProcessorBlock(ProcessorSync<IN, OUT>* proc, Stream<IN>* in) {
// Save the pointer to the processor
this->proc = proc;
// Set the streams
setInput(in);
setOutput(NULL);
}
/**
* TODO
*/
ProcessorBlock(ProcessorSync<IN, OUT>* proc, Stream<IN>* in, Stream<OUT>* out) {
// Save the pointer to the processor
this->proc = proc;
// Set the streams
setInput(in);
setOutput(out);
}
// Destructor
virtual ~ProcessorBlock() {}
/**
* Set the input stream.
* @param in Input stream.
*/
void setInput(Stream<IN>* in) {
// TODO: Lock
// If the current input if it already was registered
unregisterInput(_in);
// TODO: Manage allocating and freeing streams
// Update the input
_in = in;
// Register the new input
registerInput(_in);
}
/**
* Set the output stream.
* @param in Output stream.
*/
void setOutput(Stream<OUT>* out) {
// TODO: Lock
// If the current output if it already was registered
unregisterOutput(_out);
// TODO: Manage allocating and freeing streams
// Update the output
_out = out;
// Register the new output
registerOutput(_out);
}
Stream<IN>* in() const {
// TODO: Lock
return _in;
}
Stream<OUT>* out() const {
// TODO: Lock
return _out;
}
private:
bool run() {
// Read samples
auto bufSet = _in->recv();
if (!bufSet.samples) { return true; }
// Process samples
{
auto lck = proc->getLock();
proc->process(NULL/*TODO*/, NULL/*TODO*/, 0/*TODO*/);
}
// TODO: Write samples
// TODO
return false;
}
ProcessorKernel<IN, OUT>* proc;
Stream<IN>* _in = NULL;
Stream<OUT>* _out = NULL;
bool ownInput = false;
bool ownOutput = false;
};
}

View File

@@ -1,7 +1,33 @@
#pragma once
#include <mutex>
#include <condition_variable>
#include "block.h"
//#include "block.h"
/**
* Interface to be used by any blocking class (stream, mailbox, etc...) to allow cancelling an operation.
*/
class StopNotifier {
public:
/**
* Notify the sending thread that it should stop.
*/
virtual void stopSender() = 0;
/**
* Clear the sender stop flag to allow restarting the sender thread.
*/
virtual void clearSendStop() = 0;
/**
* Notify the receiving thread that it should stop.
*/
virtual void stopReceiver() = 0;
/**
* Clear the receiver stop flag to allow restarting the sender thread.
*/
virtual void clearRecvStop() = 0;
};
namespace dsp {
/**

View File

@@ -8,7 +8,6 @@ namespace dsp {
template <class T>
Taps<T>::Taps(const T* taps, size_t count) : Buffer<T>(taps, count) {}
template class Buffer<float>;
template class Buffer<double>;
template class Buffer<Complex>;
template class Taps<float>;
template class Taps<Complex>;
}

View File

@@ -1,16 +1,68 @@
#include "window.h"
#include "complex.h"
#include <stdexcept>
namespace dsp {
Window::Window() {
// Define the window with the default error
define();
}
Window::Window(const Window& b) {
// Copy the definition
def = b.def;
}
Window::Window(Window&& b) {
// Move the definition
def = std::move(b.def);
}
Window::~Window() {}
void Window::generate(float* data, size_t len) const {
for (size_t i = 0; i < len; i++) {
Window& Window::operator=(const Window& b) {
// Copy the definition
def = b.def;
}
Window& Window::operator=(Window&& b) {
// Move the definition
def = std::move(b.def);
}
void Window::generate(float* data, size_t len) const {
// Compute the linear map ratio
float ratio = 1.0f / ((float)(len + 1));
// Iterate over all taps
for (size_t i = 0; i < len; i++) {
// Evaluate the window at the adimensional parameter
data[i] = def(((float)(i + 1)) * ratio);
}
}
template <class T>
void Window::apply(T* data, size_t len) const {
// TODO
// Compute the linear map ratio
float ratio = 1.0f / ((float)(len + 1));
// Iterate over all taps
for (size_t i = 0; i < len; i++) {
// Evaluate the window at the adimensional parameter
data[i] *= def(((float)(i + 1)) * ratio);
}
}
template void Window::apply(float* data, size_t len) const;
//template void Window::apply(Complex* data, size_t len) const;
void Window::define() {
// Ensure an error is thrown if the undefined window is used
def = Window::undefined;
}
float Window::undefined(float x) {
// Called when a window function was not defined
throw std::runtime_error("Undefined window");
return 0;
}
}

View File

@@ -1,20 +1,37 @@
#pragma once
#include <stdint.h>
// TODO: Make something better, this sucks to use
#include <functional>
namespace dsp {
/**
* Window function.
* This class is NOT thread-safe.
*/
class Window {
public:
// Default constructor
Window();
// Copy constructor
Window(const Window& b);
// Move constructor
Window(Window&& b);
// Virtual destructor
virtual ~Window();
// Copy assignement operator
Window& operator=(const Window& b);
// Move assignement operator
Window& operator=(Window&& b);
/**
* Compute the value of the window function.
* @param x Point at which to compute the window at. Must be bounded between 0 and 1.
* @return Value of the window bounded between 0 and 1.
*/
virtual float operator()(float x) const = 0;
inline float operator()(float x) { return def(x); }
/**
* Generate a window of a given length.
@@ -30,5 +47,21 @@ namespace dsp {
*/
template <class T>
void apply(T* data, size_t len) const;
protected:
/**
* Define the window function by setting the `def` member.
* MUST be overriden by all window functions.
*/
virtual void define();
/**
* The window function itself.
* This member MUST be initialized by all window functions.
*/
std::function<float(float)> def;
private:
static float undefined(float x);
};
}

5
dsp/window/all.h Normal file
View File

@@ -0,0 +1,5 @@
#pragma once
#include "hann.h"
#include "rectangular.h"
#include "triangular.h"
#include "welch.h"

15
dsp/window/hann.cpp Normal file
View File

@@ -0,0 +1,15 @@
#include "hann.h"
#include "../constants.h"
namespace dsp::window {
Hann::Hann() {
define();
}
void Hann::define() {
def = [](float x) {
float y = sinf(DSP_PI*x);
return y*y;
};
}
}

12
dsp/window/hann.h Normal file
View File

@@ -0,0 +1,12 @@
#pragma once
#include "../window.h"
namespace dsp::window {
class Hann : public Window {
public:
Hann();
private:
void define();
};
}

View File

@@ -0,0 +1,11 @@
#include "rectangular.h"
namespace dsp::window {
Rectangular::Rectangular() {
define();
}
void Rectangular::define() {
def = [](float x) { return 1.0f; };
}
}

View File

@@ -4,11 +4,9 @@
namespace dsp::window {
class Rectangular : public Window {
public:
/**
* Compute the value of the window function.
* @param x Point at which to compute the window at. Must be bounded between 0 and 1.
* @return Value of the window bounded between 0 and 1.
*/
inline float operator()(float x) const { return 1.0f; }
Rectangular();
private:
void define();
};
}

12
dsp/window/triangular.cpp Normal file
View File

@@ -0,0 +1,12 @@
#include "triangular.h"
#include <math.h>
namespace dsp::window {
Triangular::Triangular() {
define();
}
void Triangular::define() {
def = [](float x) { return 1.0f - fabsf(2.0f*x - 1.0f); };
}
}

12
dsp/window/triangular.h Normal file
View File

@@ -0,0 +1,12 @@
#pragma once
#include "../window.h"
namespace dsp::window {
class Triangular : public Window {
public:
Triangular();
private:
void define();
};
}

14
dsp/window/welch.cpp Normal file
View File

@@ -0,0 +1,14 @@
#include "welch.h"
namespace dsp::window {
Welch::Welch() {
define();
}
void Welch::define() {
def = [](float x) {
float y = 2.0f*x - 1.0f;
return 1.0f - y*y;
};
}
}

12
dsp/window/welch.h Normal file
View File

@@ -0,0 +1,12 @@
#pragma once
#include "../window.h"
namespace dsp::window {
class Welch : public Window {
public:
Welch();
private:
void define();
};
}

View File

@@ -1,18 +1,26 @@
#include <stdio.h>
#include "dsp/buffer.h"
#include "dsp/taps.h"
#include "dsp/complex.h"
#include <volk/volk.h>
#include "dsp/window.h"
#include "dsp/window/all.h"
void testFunc(const float* buf, size_t len) {
};
void testFunc(dsp::Window win) {
printf("win(0.0) = %f\n", win(0.0));
printf("win(0.5) = %f\n", win(0.5));
printf("win(1.0) = %f\n", win(1.0));
}
int main() {
float* test;
dsp::Taps<float> taps;
try {
testFunc(dsp::window::Hann());
testFunc(((const dsp::Taps<float>&)taps).data(), taps.size());
dsp::Window win = dsp::window::Triangular();
dsp::Window win2 = dsp::window::Hann();
return 0;
win = dsp::window::Hann();
return 0;
}
catch (const std::exception& e) {
fprintf(stderr, "ERROR: %s\n", e.what());
return -1;
}
}