initial commit

This commit is contained in:
AlexandreRouma
2024-08-05 22:28:33 +02:00
commit 2f4eace8ab
15 changed files with 1024 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.vscode/
build/

10
CMakeLists.txt Normal file
View File

@@ -0,0 +1,10 @@
cmake_minimum_required(VERSION 3.13)
project(dsp)
file(GLOB_RECURSE SRC "src/*.cpp" "dsp/*.cpp")
add_executable(${PROJECT_NAME} ${SRC})
target_include_directories(${PROJECT_NAME} PRIVATE "dsp/")
target_compile_options(${PROJECT_NAME} PRIVATE /std:c++20)

98
dsp/block.cpp Normal file
View File

@@ -0,0 +1,98 @@
#include "block.h"
namespace dsp {
Block::Block() {
}
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() {
// Acquire worker variables
std::lock_guard<std::mutex> lck(workerMtx);
// Return run state
return _running;
}
void Block::registerInput(Signaler* input) {
// Acquire worker variables
std::lock_guard<std::mutex> lck(workerMtx);
// Save to the input list
inputs.push_back(input);
}
void Block::unregisterInput(Signaler* input) {
// Acquire worker variables
std::lock_guard<std::mutex> lck(workerMtx);
// TODO
}
void Block::registerOutput(Signaler* output) {
// Acquire worker variables
std::lock_guard<std::mutex> lck(workerMtx);
// Save to the output list
outputs.push_back(output);
}
void Block::unregisterOutput(Signaler* output) {
// Acquire worker variables
std::lock_guard<std::mutex> lck(workerMtx);
// TODO
}
void Block::worker() {
// Call the run function repeatedly
while (!run());
}
}

80
dsp/block.h Normal file
View File

@@ -0,0 +1,80 @@
#pragma once
#include <thread>
#include <vector>
#include "stream.h"
namespace dsp {
/**
* General DSP block class handling the worker thread start/stop operations.
* All input and output streams of the derived blocks must be registered using the appropriate functions.
*/
class Block {
public:
Block();
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();
protected:
/**
* Register an input stream.
* @param input Input stream to register.
*/
void registerInput(Signaler* input);
/**
* Unregister an input stream.
* @param input Input stream to unregister.
*/
void unregisterInput(Signaler* input);
/**
* Register an output stream.
* @param input Output stream to register.
*/
void registerOutput(Signaler* output);
/**
* Unregister an output stream.
* @param input Output stream to unregister.
*/
void unregisterOutput(Signaler* output);
/**
* Run the DSP code.
* @return True if the worker thread should stop.
*/
virtual bool run();
/**
* Mutex to be used for block settings.
*/
std::recursive_mutex settingsMtx;
private:
/**
* Worker thread function.
*/
void worker();
// Worker variables
std::mutex workerMtx;
std::thread workerThread;
std::vector<Signaler*> inputs;
std::vector<Signaler*> outputs;
bool _running = false;
};
}

104
dsp/complex.h Normal file
View File

@@ -0,0 +1,104 @@
#pragma once
#include <math.h>
namespace dsp {
/**
* Complex 32bit floating-point number.
*/
struct Complex {
// TODO: Allow construction from a float
/**
* Compute the conjugate of the Complex number.
*/
constexpr inline Complex conj() const {
return Complex{ re, -im };
}
/**
* Compute the phase of the Complex number normalized between -pi and pi.
*/
inline float phase() const {
return atan2f(im, re);
}
/**
* Compute the amplitude of the Complex number.
*/
inline float amplitude() const {
return sqrtf(re*re + im*im);
}
void operator=(float b) {
re = b;
}
/**
* Real component.
*/
float re;
/**
* Imaginary component.
*/
float im;
};
}
inline constexpr dsp::Complex operator+(const dsp::Complex& a, float b) {
return dsp::Complex{ a.re + b, a.im };
}
inline constexpr dsp::Complex operator+(float a, const dsp::Complex& b) {
return dsp::Complex{ a + b.re, b.im };
}
inline constexpr dsp::Complex operator-(const dsp::Complex& a, float b) {
return dsp::Complex{ a.re - b, a.im };
}
inline constexpr dsp::Complex operator-(float a, const dsp::Complex& b) {
return dsp::Complex{ a - b.re, b.im };
}
inline constexpr dsp::Complex operator*(const dsp::Complex& a, float b) {
return dsp::Complex{ a.re*b, a.im*b };
}
inline constexpr dsp::Complex operator*(float a, const dsp::Complex& b) {
return dsp::Complex{ a*b.re, a*b.im };
}
inline constexpr dsp::Complex operator/(const dsp::Complex& a, float b) {
return dsp::Complex{ a.re/b, a.im/b };
}
inline constexpr dsp::Complex operator/(float a, const dsp::Complex& b) {
float denom = b.re*b.re + b.im*b.im;
return dsp::Complex{ a*b.re / denom, -a*b.im / denom };
}
inline constexpr dsp::Complex operator+(const dsp::Complex& a, const dsp::Complex& b) {
return dsp::Complex{ a.re + b.re, a.im + b.im };
}
inline constexpr dsp::Complex operator-(const dsp::Complex& a, const dsp::Complex& b) {
return dsp::Complex{ a.re - b.re, a.im - b.im };
}
inline constexpr dsp::Complex operator*(const dsp::Complex& a, const dsp::Complex& b) {
return dsp::Complex{ a.re*b.re - a.im*b.im, a.im*b.re + a.re*b.im };
}
inline constexpr dsp::Complex operator/(const dsp::Complex& a, const dsp::Complex& b) {
float denom = b.re*b.re + b.im*b.im;
return dsp::Complex{ (a.re*b.re + a.im*b.im) / denom, (a.im*b.re - a.re*b.im) / denom };
}
inline constexpr dsp::Complex operator""_j(unsigned long long value) {
return dsp::Complex{ 0.0f, (float)value };
}
inline constexpr dsp::Complex operator""_j(long double value) {
return dsp::Complex{ 0.0f, (float)value };
}

72
dsp/mailbox.h Normal file
View File

@@ -0,0 +1,72 @@
#pragma once
#include "stream.h"
namespace gui {
/**
* Mailboxes allow to exchange objects between two threads.
*/
template <typename T>
class Mailbox : public Signaler {
public:
/**
* Create a mailbox object.
*/
Mailbox();
~Mailbox();
/**
* Notify the sending thread that it should stop.
*/
void stopSender();
/**
* Notify the receiving thread that it should stop.
*/
void stopReceiver();
/**
* Send an object.
* @return True if the sender thread must exist, false otherwise.
*/
bool send();
/**
* Wait for an object. May also return in case of a signal to exit. ack() must be called as soon as the received object has been processed.
* @return True if the receiver thread must exist, false otherwise.
*/
bool recv();
/**
* Acknowledge reception and processing of the samples. Allows sender thread to send a new buffer.
*/
void ack();
/**
* Get the sending object.
* @return Sending object.
*/
T* getSendObject() { return sendObj; }
/**
* Get the receiving object.
* @return Sending object.
*/
const T* getRecvObject() { return recvObj; }
private:
// Sender variables
std::condition_variable sendCV;
std::mutex sendMtx;
bool canSend = true;
bool stopSend = false;
T* sendObj;
// Receiver variables
std::condition_variable recvCV;
std::mutex recvMtx;
bool available = false;
bool stopRecv = false;
T* recvObj;
};
}

12
dsp/processor.h Normal file
View File

@@ -0,0 +1,12 @@
#pragma once
#include "block.h"
namespace dsp {
template <typename I, typename O>
class Processor : public Block {
public:
private:
};
}

90
dsp/stereo.h Normal file
View File

@@ -0,0 +1,90 @@
#pragma once
#include <math.h>
namespace dsp {
/**
* Two 32bit floating-point number representing the left and right channels.
*/
struct Stereo {
// TODO: Allow construction from a float
void operator=(float b) {
l = b;
r = b;
}
/**
* Left channel.
*/
float l;
/**
* Right channel.
*/
float r;
};
inline constexpr dsp::Stereo operator+(const dsp::Stereo& a, float b) {
return dsp::Stereo{ a.l + b, a.r + b};
}
inline constexpr dsp::Stereo operator+(float a, const dsp::Stereo& b) {
return dsp::Stereo{ a + b.l, a + b.r };
}
inline constexpr dsp::Stereo operator-(const dsp::Stereo& a, float b) {
return dsp::Stereo{ a.l - b, a.r - b };
}
inline constexpr dsp::Stereo operator-(float a, const dsp::Stereo& b) {
return dsp::Stereo{ a - b.l, a - b.r };
}
inline constexpr dsp::Stereo operator*(const dsp::Stereo& a, float b) {
return dsp::Stereo{ a.l*b, a.r*b };
}
inline constexpr dsp::Stereo operator*(float a, const dsp::Stereo& b) {
return dsp::Stereo{ a*b.l, a*b.r };
}
inline constexpr dsp::Stereo operator/(const dsp::Stereo& a, float b) {
return dsp::Stereo{ a.l/b, a.r/b };
}
inline constexpr dsp::Stereo operator/(float a, const dsp::Stereo& b) {
return dsp::Stereo{ a/b.l, a/b.r };
}
inline constexpr dsp::Stereo operator+(const dsp::Stereo& a, const dsp::Stereo& b) {
return dsp::Stereo{ a.l + b.l, a.r + b.r };
}
inline constexpr dsp::Stereo operator-(const dsp::Stereo& a, const dsp::Stereo& b) {
return dsp::Stereo{ a.l - b.l, a.r - b.r };
}
inline constexpr dsp::Stereo operator*(const dsp::Stereo& a, const dsp::Stereo& b) {
return dsp::Stereo{ a.l*b.l, a.r*b.r };
}
inline constexpr dsp::Stereo operator/(const dsp::Stereo& a, const dsp::Stereo& b) {
return dsp::Stereo{ a.l/b.l, a.r/b.r };
}
inline constexpr dsp::Stereo operator""_L(unsigned long long value) {
return dsp::Stereo{ (float)value, 0.0f };
}
inline constexpr dsp::Stereo operator""_L(long double value) {
return dsp::Stereo{ (float)value, 0.0f };
}
inline constexpr dsp::Stereo operator""_R(unsigned long long value) {
return dsp::Stereo{ 0.0f, (float)value };
}
inline constexpr dsp::Stereo operator""_R(long double value) {
return dsp::Stereo{ 0.0f, (float)value };
}
}

143
dsp/stream.cpp Normal file
View File

@@ -0,0 +1,143 @@
#include "Stream.h"
#include "./complex.h"
#include "stereo.h"
namespace dsp {
template <typename T>
Stream<T>::Stream(int channels, int bufferSize) {
// Allocate both send and receive buffers aligned by the size of the type
sendBuf = new T[bufferSize, sizeof(T)];
recvBuf = new T[bufferSize, sizeof(T)];
}
template <typename T>
Stream<T>::~Stream() {
// Free both send and receive buffers
delete[] sendBuf;
delete[] recvBuf;
}
template <typename T>
void Stream<T>::stopSender() {
// Acquire the sender variables
std::unique_lock<std::mutex> slck(sendMtx);
// Set the stop flag
stopSend = true;
// Release the sender variables
slck.unlock();
// Notify the sender thread
sendCV.notify_all();
}
template <typename T>
void Stream<T>::clearSendStop() {
// Acquire the sender variables
std::unique_lock<std::mutex> slck(sendMtx);
// Clear the stop flag
stopSend = false;
}
template <typename T>
void Stream<T>::stopReceiver() {
// Acquire the receiver variables
std::unique_lock<std::mutex> rlck(recvMtx);
// Set the stop flag
stopRecv = true;
// Release the receiver variables
rlck.unlock();
// Notify the receiver thread
recvCV.notify_all();
}
template <typename T>
void Stream<T>::clearRecvStop() {
// Acquire the sender variables
std::unique_lock<std::mutex> rlck(recvMtx);
// Clear the stop flag
stopRecv = false;
}
template <typename T>
bool Stream<T>::send(int count) {
// Acquire the sender variables
std::unique_lock<std::mutex> slck(sendMtx);
// Wait until the sender can send or is notified it should stop
sendCV.wait(slck, [=](){ return canSend || stopSend; });
// If asked to stop, return true
if (stopSend) { return true; }
// Mark that data can no longer be sent
canSend = false;
// Acquire the receiver variables
std::unique_lock<std::mutex> rlck(recvMtx);
// Swap buffers
T* tmp = sendBuf;
sendBuf = recvBuf;
recvBuf = tmp;
// Release the sender variables
slck.unlock();
// Set the number of items that are readable
available = count;
// Release the receiver variables
rlck.unlock();
// Notify the receiver thread that there are items available
recvCV.notify_all();
}
template <typename T>
int Stream<T>::recv() {
// Acquire the receiver variables
std::unique_lock<std::mutex> rlck(recvMtx);
// Wait until there are items that are readable or the receiver is notified that it should stop
recvCV.wait(rlck, [=](){ return available || stopRecv; });
// Return the number of readable items or -1 if the receiver should stop
return stopRecv ? -1 : available;
}
template <typename T>
void Stream<T>::ack() {
// Acquire the sender variables
std::unique_lock<std::mutex> slck(sendMtx);
// Mark that data can be sent
canSend = true;
// Release the sender variables
slck.unlock();
// Notify the sender thread
sendCV.notify_all();
}
// Instantiate the class
template class Stream<uint8_t>;
template class Stream<uint16_t>;
template class Stream<uint32_t>;
template class Stream<uint64_t>;
template class Stream<int8_t>;
template class Stream<int16_t>;
template class Stream<int32_t>;
template class Stream<int64_t>;
template class Stream<float>;
template class Stream<double>;
template class Stream<Complex>;
template class Stream<Stereo>;
}

118
dsp/stream.h Normal file
View File

@@ -0,0 +1,118 @@
#pragma once
#include <mutex>
#include <condition_variable>
#define DSP_DEFAULT_BUFFER_SIZE 1000000
namespace dsp {
/**
* Represents a class that can signal to its acting threads to stop. (TODO: Better name)
*/
class Signaler {
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;
};
/**
* Streams allow to exchange samples between two threads.
* The samples have to be of type (u)int8_t, (u)int16_t, (u)int32_t, (u)int64_t, float, double or Complex.
*/
template <typename T>
class Stream : public Signaler {
public:
/**
* Create a stream object.
* @param bufferSize Number of items in the buffers.
*/
Stream(int channels = 1, int bufferSize = DSP_DEFAULT_BUFFER_SIZE);
~Stream();
/**
* Notify the sending thread that it should stop. clearSendStop() must be called once the thread is stopped to clear the stop flag.
*/
void stopSender();
// TODO: More consistent naming
/**
* Clear the sender stop flag to allow restarting the sender thread.
*/
void clearSendStop();
/**
* Notify the receiving thread that it should stop. clearRecvStop() must be called once the thread is stopped to clear the stop flag.
*/
void stopReceiver();
/**
* Clear the receiver stop flag to allow restarting the sender thread.
*/
void clearRecvStop();
/**
* Send a buffer of samples.
* @param count Number of samples in the send buffer.
* @return True if the sender thread must exist, false otherwise.
*/
bool send(int count);
/**
* Wait for buffer of samples. May also return in case of a signal to exit. ack() must be called as soon as the receive buffer has been entirely processed.
* @return Number of samples or -1 if the worker thread must exit.
*/
int recv();
/**
* Acknowledge reception and processing of the samples. Allows sender thread to send a new buffer.
*/
void ack();
/**
* Get the sending buffer.
* @param channel ID of the channel to get the buffer for.
* @return Sending buffer.
*/
T* getSendBuffer(int channel = 0);
/**
* Get the receiving buffer.
* @param channel ID of the channel to get the buffer for.
* @return Sending buffer.
*/
const T* getRecvBuffer(int channel = 0);
private:
// Sender variables
std::condition_variable sendCV;
std::mutex sendMtx;
bool canSend = true;
bool stopSend = false;
T* sendBuf;
// Receiver variables
std::condition_variable recvCV;
std::mutex recvMtx;
int available = 0;
bool stopRecv = false;
T* recvBuf;
};
}

97
dsp/taps.cpp Normal file
View File

@@ -0,0 +1,97 @@
#include "taps.h"
#include "complex.h"
#include <string.h>
namespace dsp {
template <class T>
Taps<T>::Taps() {}
template <class T>
Taps<T>::Taps(int count, bool zero) {
// Allocate buffer
reallocate(count);
// Null out if requested
if (zero) { memset(data, 0, count*sizeof(T)); }
}
template <class T>
Taps<T>::Taps(T* taps, int count) {
// Allocate buffer
reallocate(count);
// Copy data over
memcpy(data, taps, count*sizeof(T));
}
template <class T>
Taps<T>::Taps(const Taps<T>& b) {
// Allocate buffer
reallocate(b.count);
// Copy data over
memcpy(data, b.data, b.count*sizeof(T));
}
template <class T>
Taps<T>::Taps(Taps<T>&& b) {
// Copy members
data = b.data;
count = b.count;
// Neutralize old instance
b.data = NULL;
b.count = 0;
}
template <class T>
Taps<T>::~Taps() {
// Free the buffer if it is allocated
if (data) { delete[] data; }
}
template <class T>
Taps<T>& Taps<T>::operator=(const Taps<T>& b) {
// Reallocate buffer
reallocate(b.count);
// Copy data over
memcpy(data, b.data, b.count*sizeof(T));
// Return self
return *this;
}
template <class T>
Taps<T>& Taps<T>::operator=(Taps<T>&& b) {
// Copy members
data = b.data;
count = b.count;
// Neutralize old instance
b.data = NULL;
b.count = 0;
// Return self
return *this;
}
template <class T>
void Taps<T>::reallocate(int count) {
// If the new count is no different and the buffer is allocated, no need to realloc
if (data && this->count == count) { return; }
// Free buffer
if (data) { delete[] data; }
// Allocate buffer
data = new T[count, sizeof(T)];
// Update tap count
this->count = count;
}
// Instantiate the class
template class Taps<float>;
template class Taps<Complex>;
}

55
dsp/taps.h Normal file
View File

@@ -0,0 +1,55 @@
#pragma once
namespace dsp {
/**
* Filter tap container.
*/
template <class T>
class Taps {
public:
Taps();
/**
* Create a tap bank holding count taps.
* @param count Number of taps.
* @param zero Zero out the taps.
*/
Taps(int count, bool zero = true);
/**
* Create a tap bank from an array.
* @param taps Array contianing taps.
* @param count Number of taps to load.
*/
Taps(T* taps, int count);
Taps(const Taps<T>& b);
Taps(Taps<T>&& b);
~Taps();
Taps<T>& operator=(const Taps<T>& b);
Taps<T>& operator=(Taps<T>&& b);
/**
* Get the number of taps in the filter.
*/
inline int size() const { return count; }
inline operator const T*() const { return data; }
inline operator bool() const { return data && count; }
/**
* Get a tap by index.
* @param index Index of the tap
* @return Tap at index.
*/
inline const T& operator[](int index) const { return data[index]; }
protected:
void reallocate(int count);
int count = 0;
T* data = nullptr;
};
}

62
dsp/taps/low_pass.cpp Normal file
View File

@@ -0,0 +1,62 @@
#include "low_pass.h"
namespace dsp::taps {
LowPass::LowPass() {}
LowPass::LowPass(float cutoff, float transWidth, float samplerate) {
// Save parameters
this->cutoff = cutoff;
this->transWidth = transWidth;
this->samplerate = samplerate;
// Generate filter
generate();
}
float LowPass::getCutoff() {
return cutoff;
}
void LowPass::setCutoff(float cutoff, float transWidth) {
// Update parameter
this->cutoff = cutoff;
// If the transition width is given, update is as well
if (transWidth > 0) { this->transWidth = transWidth; }
// Regenerate filter
generate();
}
float LowPass::getTransWidth() {
return transWidth;
}
void LowPass::setTransWidth(float transWidth) {
// Update parameter
this->transWidth = transWidth;
// Regenerate filter
generate();
}
float LowPass::getSamplerate() {
return samplerate;
}
void LowPass::setSamplerate(float samplerate) {
// Update parameter
this->samplerate = samplerate;
// Regenerate filter
generate();
}
void LowPass::generate() {
// Reallocate the buffer
reallocate(0 /*TODO: Tap count estimation*/);
// Generate taps
// TODO
}
}

61
dsp/taps/low_pass.h Normal file
View File

@@ -0,0 +1,61 @@
#pragma once
#include "../taps.h"
namespace dsp::taps {
/**
* Low-pass filter taps.
*/
class LowPass : public Taps<float> {
public:
LowPass();
/**
* Create low-pass FIR taps.
* @param cutoff 3dB cutoff frequency in Hz.
* @param transWidth Transition width in Hz.
* @param samplerate Sample rate in Hz.
*/
LowPass(float cutoff, float transWidth, float samplerate);
/**
* Get cutoff frequency.
*/
float getCutoff();
/**
* Set cutoff frequency.
* @param cutoff Cutoff frequency in Hz.
* @param transWidth Transition width in Hz. Negative if unchanged.
*/
void setCutoff(float cutoff, float transWidth = -1);
/**
* Get transition width.
*/
float getTransWidth();
/**
* Set transition width.
* @param transWidth Transition width in Hz.
*/
void setTransWidth(float transWidth);
/**
* Get sample rate.
*/
float getSamplerate();
/**
* Set sample rate.
* @param samplerate Sample rate in Hz.
*/
void setSamplerate(float samplerate);
private:
void generate();
float cutoff;
float transWidth;
float samplerate;
};
}

20
src/main.cpp Normal file
View File

@@ -0,0 +1,20 @@
#include "dsp/taps.h"
#include "dsp/taps/low_pass.h"
#include "dsp/complex.h"
#include "dsp/stream.h"
#include <stdio.h>
struct TestStruct {
bool a;
int b;
};
int main() {
dsp::taps::LowPass lp(3000, 100, 15000);
float test = lp[0];
dsp::Stream<float> str;
return 0;
}