This commit is contained in:
AlexandreRouma
2026-03-10 11:43:17 -04:00
parent 9bc41dbe2a
commit 5c7b6997df
34 changed files with 1608 additions and 122 deletions

View File

@@ -9,6 +9,16 @@ target_include_directories(${PROJECT_NAME} PRIVATE "/")
target_compile_options(${PROJECT_NAME} PRIVATE /std:c++20)
# Set compiler options
if (MSVC)
target_compile_options(${PROJECT_NAME} PRIVATE /EHsc) # /O2 /Ob2
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
target_compile_options(${PROJECT_NAME} PRIVATE -O3)
else ()
target_compile_options(${PROJECT_NAME} PRIVATE -O3)
endif ()
if (MSVC)
# Lib path
target_link_directories(${PROJECT_NAME} PUBLIC "C:/Program Files/PothosSDR/lib/")

View File

@@ -26,21 +26,21 @@ namespace dsp {
template <class T>
Buffer<T>::Buffer(const Buffer<T>& b) {
// Allocate buffer
realloc(b.capacity);
realloc(b._capacity);
// Copy data over
memcpy(buffer, b.buffer, b.capacity * sizeof(T));
memcpy(buffer, b.buffer, b._capacity * sizeof(T));
}
template <class T>
Buffer<T>::Buffer(Buffer<T>&& b) {
// Copy members
buffer = b.buffer;
capacity = b.capacity;
_capacity = b._capacity;
// Neutralize old instance
b.buffer = NULL;
b.capacity = 0;
b._capacity = 0;
}
template <class T>
@@ -52,10 +52,10 @@ namespace dsp {
template <class T>
Buffer<T>& Buffer<T>::operator=(const Buffer<T>& b) {
// Reallocate the buffer
realloc(b.capacity);
realloc(b._capacity);
// Copy over the data
memcpy(buffer, b.buffer, capacity * sizeof(T));
memcpy(buffer, b.buffer, _capacity * sizeof(T));
// Return self
return *this;
@@ -68,11 +68,11 @@ namespace dsp {
// Copy the state of the original
buffer = b.buffer;
capacity = b.capacity;
_capacity = b._capacity;
// Neutralize the original
b.buffer = NULL;
b.capacity = 0;
b._capacity = 0;
// Return self
return *this;
@@ -96,7 +96,7 @@ namespace dsp {
newbuf = (T*)volk_malloc(size * sizeof(T), volk_get_alignment());
// Copy the existing data
memcpy(newbuf, buffer, std::min<size_t>(capacity, size));
memcpy(newbuf, buffer, std::min<size_t>(_capacity, size));
// Free the current buffer
volk_free(buffer);
@@ -121,13 +121,13 @@ namespace dsp {
newbuf = (T*)volk_malloc(size * sizeof(T), volk_get_alignment());
// Copy the existing data
memcpy(newbuf, buffer, std::min<size_t>(capacity, size));
memcpy(newbuf, buffer, std::min<size_t>(_capacity, size));
// Free the current buffer
volk_free(buffer);
// If the new buffer is larger, zero out the extra data
if (size > capacity) { memset(&newbuf[capacity], 0, size - capacity ); }
if (size > _capacity) { memset(&newbuf[_capacity], 0, size - _capacity ); }
// Replace buffer
buffer = newbuf;
@@ -135,7 +135,7 @@ namespace dsp {
}
// Update the current capacity
capacity = size;
_capacity = size;
}
template <class T>
@@ -145,7 +145,7 @@ namespace dsp {
// Mark the buffer as freed
buffer = NULL;
capacity = 0;
_capacity = 0;
}
// Instantiate the class

View File

@@ -1,6 +1,8 @@
#pragma once
#include <stddef.h>
// TODO: Force inline the data stuff for maximal performance in FIR
namespace dsp {
/**
* Re-allocation behavior of buffers.
@@ -72,7 +74,7 @@ namespace dsp {
* @param behavior Specify the reallocaition behavior to either discard exiting samples, keep existing samples or zero out the buffer.
*/
inline void reserve(size_t size, ReallocBehavior behavior = REALLOC_DISCARD) {
if (size > capacity || size < (capacity >> 1)) { realloc(size, behavior); }
if (size > _capacity || size < (_capacity >> 1)) { realloc(size, behavior); }
}
/**
@@ -81,9 +83,9 @@ namespace dsp {
void free();
/**
* Get the number of samples in the buffer.
* Get the number of samples that can be stored in the buffer.
*/
inline size_t size() const { return capacity; }
inline size_t capacity() const { return _capacity; }
/**
* Get a pointer to the samples.
@@ -99,24 +101,24 @@ namespace dsp {
* Cast to bool.
* @return True if the buffer contains samples, false if it is empty.
*/
inline operator bool() const { return capacity; }
inline operator bool() const { return _capacity; }
/**
* Access a sample by index.
* @param index Index of the tap
* @return Tap at index.
* @param index Index of the sample.
* @return Sample at index.
*/
inline T& operator[](uintptr_t index) { return buffer[index]; }
/**
* Get a sample by index.
* @param index Index of the tap
* @return Tap at index.
* @param index Index of the sample.
* @return Sample at index.
*/
inline const T& operator[](uintptr_t index) const { return buffer[index]; }
private:
size_t capacity = 0;
size_t _capacity = 0;
T* buffer = NULL;
};
}

View File

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

73
dsp/demod/fm.cpp Normal file
View File

@@ -0,0 +1,73 @@
#include "fm.h"
#include "../constants.h"
#include "volk/volk.h"
// TODO: Thread safety
namespace dsp::demod {
FM::FM() {}
FM::FM(double deviation, double samplerate) {
// Save the parameters
this->deviation = deviation;
this->samplerate = samplerate;
// Compute the gain
gain = samplerate / (2.0 * DSP_PI * deviation);
}
double FM::getDeviation() {
// Return the deviation
return deviation;
}
void FM::setDeviation(double deviation) {
// Update the deviation
this->deviation = deviation;
// Recompute the gain
gain = samplerate / (2.0 * DSP_PI * deviation);
}
double FM::getSamplerate() {
// Return the sampling rate
return samplerate;
}
void FM::setSamplerate(double deviation) {
// Update the sampling rate
this->samplerate = samplerate;
// Recompute the gain
gain = samplerate / (2.0 * DSP_PI * deviation);
}
void FM::reset() {
// Reset the last phase
lastPhase = 0.0f;
}
// TODO: Move to a dedicated utils file
constexpr inline float clampPhase(float diff) {
if (diff > DSP_PI_FL) { diff -= 2.0f * DSP_PI_FL; }
else if (diff <= -DSP_PI_FL) { diff += 2.0f * DSP_PI_FL; }
return diff;
}
size_t FM::process(const Complex* in, float* out, size_t count) {
// Process all samples
for (uintptr_t i = 0; i < count; i++) {
// Get the input phase
const float phase = in[i].phase();
// Compute the difference with the last sample and update the last phase
out[i] = clampPhase(phase - lastPhase) * gain;
// Save the last phase
lastPhase = phase;
}
// Return the number of samples in the output
return count;
}
}

65
dsp/demod/fm.h Normal file
View File

@@ -0,0 +1,65 @@
#pragma once
#include "../processor.h"
#include "../worker.h"
#include "../complex.h"
namespace dsp::demod {
class FM : SISOProcessor<Complex, float> {
public:
// Default constructor
FM();
/**
* Create a FM demodulator.
* @param deviation Deviation in Hz.
* @param samplerate Sampling rate in Hz.
*/
FM(double deviation, double samplerate);
/**
* Get the deviation.
* @return Deviation in Hz.
*/
double getDeviation();
/**
* Set the deviation.
* @param deviation Deviation in Hz.
*/
void setDeviation(double deviation);
/**
* Get the sampling rate.
* @return Sampling rate in Hz.
*/
double getSamplerate();
/**
* Set the sampling rate.
* @param deviation Sampling rate in Hz.
*/
void setSamplerate(double deviation);
/**
* Reset the state of the demodulator.
*/
void reset();
/**
* Demodulate an FM signal.
* @param in Modulated sample input.
* @param out Demodulated sample output.
* @param count Number of samples to process.
* @return Number of samples processed. Always equal to `count`.
*/
size_t process(const Complex* in, float* out, size_t count);
private:
double deviation = 0.0;
double samplerate = 0.0;
float gain = 0.0f;
float lastPhase = 0.0f;
};
using FMw = SISOWorker<FM, Complex, float>;
}

89
dsp/filter/fir.cpp Normal file
View File

@@ -0,0 +1,89 @@
#include "fir.h"
#include "../complex.h"
#include <volk/volk.h>
namespace dsp::filter {
template <class SAMPLE, class TAP>
FIR<SAMPLE, TAP>::FIR() {}
template <class SAMPLE, class TAP>
FIR<SAMPLE, TAP>::FIR(const Taps<TAP>& taps) {
// Save the taps
this->taps = taps;
// Pre-allocate the history buffer
hist.reserve(taps.size());
// Initialize the state
reset();
}
template <class SAMPLE, class TAP>
FIR<SAMPLE, TAP>::FIR(Taps<TAP>&& taps) {
// Save the taps
this->taps = taps;
// Pre-allocate the history buffer
hist.reserve(taps.size());
// Initialize the state
reset();
}
template <class SAMPLE, class TAP>
const Taps<TAP>& FIR<SAMPLE, TAP>::getTaps() const {
// Return the taps
return taps;
}
template <class SAMPLE, class TAP>
void FIR<SAMPLE, TAP>::setTaps(const Taps<TAP>& taps) {
// Update the taps
this->taps = taps;
}
template <class SAMPLE, class TAP>
void FIR<SAMPLE, TAP>::setTaps(Taps<TAP>&& taps) {
// Update the taps
this->taps = taps;
}
template <class SAMPLE, class TAP>
void FIR<SAMPLE, TAP>::reset() {
// Zero out the history buffer
memset(hist.data(), 0, (taps.size() - 1) * sizeof(SAMPLE));
}
template <class SAMPLE, class TAP>
size_t FIR<SAMPLE, TAP>::process(const SAMPLE* in, SAMPLE* out, size_t count) {
// Reserve enough space in the history buffer
hist.reserve(taps.size() + count - 1, REALLOC_KEEP);
// Copy over the new input samples
memcpy(&hist[taps.size() - 1], in, count * sizeof(SAMPLE));
// Filter the samples
for (uintptr_t i = 0; i < count; i++) {
// Compute the dot product depending on type
if constexpr (std::is_same_v<SAMPLE, float>) {
volk_32f_x2_dot_prod_32f(&out[i], &hist[i], taps.data(), taps.size());
}
else if constexpr (std::is_same_v<SAMPLE, Complex> && std::is_same_v<TAP, float>) {
volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&out[i], (const lv_32fc_t*)&hist[i], taps.data(), taps.size());
}
else if constexpr (std::is_same_v<SAMPLE, Complex> && std::is_same_v<TAP, Complex>) {
volk_32fc_x2_dot_prod_32fc((lv_32fc_t*)&out[i], (const lv_32fc_t*)&hist[i], (const lv_32fc_t*)taps.data(), taps.size());
}
}
// Move over the unused sample to the history buffer
memmove(hist.data(), &hist[count], (taps.size() - 1) * sizeof(SAMPLE));
// Return the number of samples processed
return count;
}
template class FIR<float, float>;
template class FIR<Complex, float>;
template class FIR<Complex, Complex>;
}

61
dsp/filter/fir.h Normal file
View File

@@ -0,0 +1,61 @@
#pragma once
#include "../processor.h"
#include "../buffer.h"
#include "../taps.h"
namespace dsp::filter {
template <class SAMPLE, class TAP>
class FIR : public SISOProcessor<SAMPLE, SAMPLE> {
public:
// Default constructor
FIR();
/**
* Create a FIR filter.
* @param taps Taps to use for the filter.
*/
FIR(const Taps<TAP>& taps);
/**
* Create a FIR filter.
* @param taps Taps to use for the filter.
*/
FIR(Taps<TAP>&& taps);
/**
* Get the filter taps.
* @return Filter taps.
*/
const Taps<TAP>& getTaps() const;
/**
* Set the filter taps.
* @param taps Filter taps.
*/
void setTaps(const Taps<TAP>& taps);
/**
* Set the filter taps.
* @param taps Filter taps.
*/
void setTaps(Taps<TAP>&& taps);
/**
* Reset the state of the filter.
*/
void reset();
/**
* Filter samples.
* @param in Input samples.
* @param out Output samples.
* @param count Number of samples to filter.
* @return Number of samples fitlered. Always equal to `count`.
*/
size_t process(const SAMPLE* in, SAMPLE* out, size_t count);
private:
Buffer<SAMPLE> hist;
Taps<TAP> taps;
};
}

18
dsp/math/add.cpp Normal file
View File

@@ -0,0 +1,18 @@
#include "add.h"
#include <type_traits>
#include <volk/volk.h>
#include "../complex.h"
namespace dsp::math {
template <class T>
size_t Add<T>::process(const T* a, const T* b, T* out, size_t count) {
if constexpr (std::is_same_v<T, float>) {
// Add using volk
volk_32f_x2_add_32f(out, a, b, count);
}
else if constexpr (std::is_same_v<T, Complex>) {
// Add using volk
volk_32fc_x2_add_32fc(out, a, b, count);
}
}
}

17
dsp/math/add.h Normal file
View File

@@ -0,0 +1,17 @@
#pragma once
namespace dsp::math {
template <class T>
class Add {
public:
/**
* Add the samples together.
* @param a Operand A.
* @param b Operand B.
* @param out Sum of A and B.
*/
size_t process(const T* a, const T* b, T* out, size_t count);
};
const int test = sizeof(Add<float>);
}

View File

@@ -0,0 +1,101 @@
#include "decimating_fir.h"
#include "../complex.h"
#include <volk/volk.h>
namespace dsp::multirate {
template <class SAMPLE, class TAP>
DecimatingFIR<SAMPLE, TAP>::DecimatingFIR() {}
template <class SAMPLE, class TAP>
DecimatingFIR<SAMPLE, TAP>::DecimatingFIR(const Taps<TAP>& taps, int decim) {
// Save the parameters
this->taps = taps;
this->decim = decim;
// Pre-allocate the history buffer
hist.reserve(taps.size());
// Initialize the state
reset();
}
template <class SAMPLE, class TAP>
DecimatingFIR<SAMPLE, TAP>::DecimatingFIR(Taps<TAP>&& taps, int decim) {
// Save the parameters
this->taps = taps;
this->decim = decim;
// Pre-allocate the history buffer
hist.reserve(taps.size());
// Initialize the state
reset();
}
template <class SAMPLE, class TAP>
const Taps<TAP>& DecimatingFIR<SAMPLE, TAP>::getTaps() const {
// Return the taps
return taps;
}
template <class SAMPLE, class TAP>
void DecimatingFIR<SAMPLE, TAP>::setTaps(const Taps<TAP>& taps, int decim) {
// Update the taps
this->taps = taps;
// Update the decimation factor if it was given
if (decim) { this->decim = decim; }
}
template <class SAMPLE, class TAP>
void DecimatingFIR<SAMPLE, TAP>::setTaps(Taps<TAP>&& taps, int decim) {
// Update the taps
this->taps = taps;
// Update the decimation factor if it was given
if (decim) { this->decim = decim; }
}
template <class SAMPLE, class TAP>
void DecimatingFIR<SAMPLE, TAP>::reset() {
// Zero out the history buffer
memset(hist.data(), 0, (taps.size() - 1) * sizeof(SAMPLE));
// Reset the offset
offset = 0;
}
template <class SAMPLE, class TAP>
size_t DecimatingFIR<SAMPLE, TAP>::process(const SAMPLE* in, SAMPLE* out, size_t count) {
// Reserve enough space in the history buffer
hist.reserve(taps.size() + count - 1, REALLOC_KEEP);
// Copy over the new input samples
memcpy(&hist[taps.size() - 1], in, count * sizeof(SAMPLE));
// Filter the samples
uintptr_t i;
uintptr_t io = 0;
for (i = offset; i < count; i += decim) {
// Compute the dot product depending on type
if constexpr (std::is_same_v<SAMPLE, float>) {
volk_32f_x2_dot_prod_32f(&out[io++], &hist[i], taps.data(), taps.size());
}
else if constexpr (std::is_same_v<SAMPLE, Complex> && std::is_same_v<TAP, float>) {
volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&out[io++], (const lv_32fc_t*)&hist[i], taps.data(), taps.size());
}
else if constexpr (std::is_same_v<SAMPLE, Complex> && std::is_same_v<TAP, Complex>) {
volk_32fc_x2_dot_prod_32fc((lv_32fc_t*)&out[io++], (const lv_32fc_t*)&hist[i], (const lv_32fc_t*)taps.data(), taps.size());
}
}
// Update the offset
offset = i - count;
// Move over the unused sample to the history buffer
memmove(hist.data(), &hist[count], (taps.size() - 1) * sizeof(SAMPLE));
// Return the number of samples generated
return io;
}
}

View File

@@ -0,0 +1,79 @@
#pragma once
#include "../processor.h"
#include "../buffer.h"
#include "../taps.h"
namespace dsp::multirate {
template <class SAMPLE, class TAP>
class DecimatingFIR {
public:
// Default constructor
DecimatingFIR();
/**
* Create a decimating FIR filter.
* @param taps Taps to use for the filter.
* @param decim Decimation factor.
*/
DecimatingFIR(const Taps<TAP>& taps, int decim);
/**
* Create a decimating FIR filter.
* @param taps Taps to use for the filter.
* @param decim Decimation factor.
*/
DecimatingFIR(Taps<TAP>&& taps, int decim);
/**
* Get the filter taps.
* @return Filter taps.
*/
const Taps<TAP>& getTaps() const;
/**
* Set the filter taps.
* @param taps Filter taps.
* @param decim Decimation factor. Zero to keep unchanged.
*/
void setTaps(const Taps<TAP>& taps, int decim = 0);
/**
* Set the filter taps.
* @param taps Filter taps.
* @param decim Decimation factor. Zero to keep unchanged.
*/
void setTaps(Taps<TAP>&& taps, int decim = 0);
/**
* Get the decimation factor.
* @return Decimation factor.
*/
int getDecimation();
/**
* Set the decimation factor.
* @param decim Decimation factor.
*/
void setDecimation(int decim);
/**
* Reset the state of the filter.
*/
void reset();
/**
* Filter and decimate samples.
* @param in Input samples.
* @param out Output samples.
* @param count Number of samples to filter.
* @return Number of samples generates.
*/
size_t process(const SAMPLE* in, SAMPLE* out, size_t count);
private:
Buffer<SAMPLE> hist;
uintptr_t offset = 0;
Taps<TAP> taps;
int decim = 0;
};
}

View File

@@ -0,0 +1,113 @@
#include "interpolating_fir.h"
#include "../complex.h"
#include <volk/volk.h>
namespace dsp::multirate {
template <class SAMPLE, class TAP>
InterpolatingFIR<SAMPLE, TAP>::InterpolatingFIR() {}
template <class SAMPLE, class TAP>
InterpolatingFIR<SAMPLE, TAP>::InterpolatingFIR(const Taps<TAP>& taps, int interp) {
// Save the parameters
this->taps = taps;
this->interp = interp;
// Pre-allocate the history buffer
hist.reserve(taps.size());
// Initialize the state
reset();
// Generate the tap phases
generatePhases();
}
template <class SAMPLE, class TAP>
InterpolatingFIR<SAMPLE, TAP>::InterpolatingFIR(Taps<TAP>&& taps, int interp) {
// Save the parameters
this->taps = taps;
this->interp = interp;
// Pre-allocate the history buffer
hist.reserve(taps.size());
// Initialize the state
reset();
// Generate the tap phases
generatePhases();
}
template <class SAMPLE, class TAP>
const Taps<TAP>& InterpolatingFIR<SAMPLE, TAP>::getTaps() const {
// Return the taps
return taps;
}
template <class SAMPLE, class TAP>
void InterpolatingFIR<SAMPLE, TAP>::setTaps(const Taps<TAP>& taps, int interp) {
// Update the taps
this->taps = taps;
// Update the interpolation factor if it was given
if (interp) { this->interp = interp; }
// Regenerate the tap phases
generatePhases();
}
template <class SAMPLE, class TAP>
void InterpolatingFIR<SAMPLE, TAP>::setTaps(Taps<TAP>&& taps, int interp) {
// Update the taps
this->taps = taps;
// Update the interpolation factor if it was given
if (interp) { this->interp = interp; }
// Regenerate the tap phases
generatePhases();
}
template <class SAMPLE, class TAP>
void InterpolatingFIR<SAMPLE, TAP>::reset() {
// Zero out the history buffer
memset(hist.data(), 0, (taps.size() - 1) * sizeof(SAMPLE));
// Reset the offset
offset = 0;
}
template <class SAMPLE, class TAP>
size_t InterpolatingFIR<SAMPLE, TAP>::process(const SAMPLE* in, SAMPLE* out, size_t count) {
// Reserve enough space in the history buffer
hist.reserve(taps.size() + count - 1, REALLOC_KEEP);
// Copy over the new input samples
memcpy(&hist[taps.size() - 1], in, count * sizeof(SAMPLE));
// Filter the samples
uintptr_t i;
uintptr_t io = 0;
for (i = offset; i < count; i += decim) {
// Compute the dot product depending on type
if constexpr (std::is_same_v<SAMPLE, float>) {
volk_32f_x2_dot_prod_32f(&out[io++], &hist[i], taps.data(), taps.size());
}
else if constexpr (std::is_same_v<SAMPLE, Complex> && std::is_same_v<TAP, float>) {
volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&out[io++], (const lv_32fc_t*)&hist[i], taps.data(), taps.size());
}
else if constexpr (std::is_same_v<SAMPLE, Complex> && std::is_same_v<TAP, Complex>) {
volk_32fc_x2_dot_prod_32fc((lv_32fc_t*)&out[io++], (const lv_32fc_t*)&hist[i], (const lv_32fc_t*)taps.data(), taps.size());
}
}
// Update the offset
offset = i - count;
// Move over the unused sample to the history buffer
memmove(hist.data(), &hist[count], (taps.size() - 1) * sizeof(SAMPLE));
// Return the number of samples generated
return io;
}
}

View File

@@ -0,0 +1,83 @@
#pragma once
#include "../processor.h"
#include "../buffer.h"
#include "../taps.h"
#include <vector>
namespace dsp::multirate {
template <class SAMPLE, class TAP>
class InterpolatingFIR {
public:
// Default constructor
InterpolatingFIR();
/**
* Create a decimating FIR filter.
* @param taps Taps to use for the filter.
* @param interp Interpolation factor.
*/
InterpolatingFIR(const Taps<TAP>& taps, int interp);
/**
* Create a decimating FIR filter.
* @param taps Taps to use for the filter.
* @param interp Interpolation factor.
*/
InterpolatingFIR(Taps<TAP>&& taps, int interp);
/**
* Get the filter taps.
* @return Filter taps.
*/
const Taps<TAP>& getTaps() const;
/**
* Set the filter taps.
* @param taps Filter taps.
* @param interp Interpolation factor. Zero to keep unchanged.
*/
void setTaps(const Taps<TAP>& taps, int interp = 0);
/**
* Set the filter taps.
* @param taps Filter taps.
* @param interp Interpolation factor. Zero to keep unchanged.
*/
void setTaps(Taps<TAP>&& taps, int interp = 0);
/**
* Get the interpolation factor.
* @return Interpolation factor.
*/
int getInterpolation();
/**
* Set the interpolation factor.
* @param interp Interpolation factor.
*/
void setInterpolation(int interp);
/**
* Reset the state of the filter.
*/
void reset();
/**
* Filter and decimate samples.
* @param in Input samples.
* @param out Output samples.
* @param count Number of samples to filter.
* @return Number of samples generates.
*/
size_t process(const SAMPLE* in, SAMPLE* out, size_t count);
private:
void generatePhases();
Buffer<SAMPLE> hist;
uintptr_t offset = 0;
Taps<TAP> taps;
std::vector<Buffer<TAP>> phases;
int interp = 0;
};
}

View File

@@ -1,10 +1,31 @@
#pragma once
#include <stdint.h>
#include <mutex>
namespace dsp {
template <class T_IN, class T_OUT>
class SISOProcessor {
public:
size_t process(const T_IN* in, T_OUT* out, size_t count);
virtual size_t process(const T_IN* in, T_OUT* out, size_t count) = 0;
inline size_t processSafe(const T_IN* in, T_OUT* out, size_t count) {
// Acquire the mutex
std::lock_guard<std::mutex> lck(mtx);
// Process the samples
return process(in, out, count);
}
/**
* Give the required output buffer size for a given input buffer size.
* @return Required output buffer size for a given input buffer size.
*/
virtual inline size_t outputBufferSize(size_t inputBufferSize) { return inputBufferSize; }
protected:
/**
* Block mutex. Must be acquired when modifying parameters.
*/
std::mutex mtx;
};
}

58
dsp/sink/file.cpp Normal file
View File

@@ -0,0 +1,58 @@
#include "file.h"
#include "../complex.h"
// TODO: Thread safety
namespace dsp::sink {
template <class T>
File<T>::File() {}
template <class T>
File<T>::File(const std::string& path, bool append) {
// Open the given file
open(path, append);
}
template <class T>
File<T>::~File() {
// Close the file in case it's open
close();
}
template <class T>
void File<T>::open(const std::string& path, bool append) {
// Open the file
file.open(path, std::ios::out | std::ios::binary | (append ? std::ios::app : std::ios::trunc));
}
template <class T>
bool File<T>::isOpen() {
// Return the open status
return file.is_open();
}
template <class T>
void File<T>::close() {
// Close the file
file.close();
}
template <class T>
void File<T>::sink(const T* in, size_t count) {
// Write the samples to the file
file.write((char*)in, count * sizeof(T));
}
// Instantiate the class
template class File<uint8_t>;
template class File<uint16_t>;
template class File<uint32_t>;
template class File<uint64_t>;
template class File<int8_t>;
template class File<int16_t>;
template class File<int32_t>;
template class File<int64_t>;
template class File<float>;
template class File<double>;
template class File<Complex>;
}

50
dsp/sink/file.h Normal file
View File

@@ -0,0 +1,50 @@
#pragma once
#include <string>
#include <fstream>
namespace dsp::sink {
template <class T>
class File {
public:
// Default constructor
File();
/**
* Create a file sink and open a file for writing.
* @param path Path to the file to write to.
* @param append True to append samples to those already in the file if it exists, False otherwise.
*/
File(const std::string& path, bool append = false);
// Destructor
~File();
/**
* Open a file for writing.
* @param path Path to the file to write to.
* @param append True to append samples to those already in the file if it exists, False otherwise.
*/
void open(const std::string& path, bool append = false);
/**
* Check if a file is open.
* @return True if a file is open, False otherwise.
*/
bool isOpen();
/**
* Close the file if one is open. Does nothing if no file is open.
*/
void close();
/**
* Sink samples.
* @param in Buffer of samples to sink.
* @param count Number of samples to sink.
*/
void sink(const T* in, size_t count);
private:
std::ofstream file;
};
}

66
dsp/source/file.cpp Normal file
View File

@@ -0,0 +1,66 @@
#include "file.h"
#include "../complex.h"
// TODO: Thread safety
namespace dsp::source {
template <class T>
File<T>::File() {}
template <class T>
File<T>::File(const std::string& path, bool repeat) {
// Open the file
open(path, repeat);
}
template <class T>
File<T>::~File() {
// Close the file if its open
close();
}
template <class T>
void File<T>::open(const std::string& path, bool repeat) {
// Save the repeat flag
this->repeat = repeat;
// Open the file
file.open(path, std::ios::in | std::ios::binary);
}
template <class T>
bool File<T>::isOpen() {
// Return the open status
return file.is_open();
}
template <class T>
void File<T>::close() {
// Close the file
file.close();
}
template <class T>
size_t File<T>::source(T* out, size_t maxCount) {
// Read samples and return number read
file.read((char*)out, maxCount * sizeof(T));
// TODO: Handle repeat flag
// TODO: Check how much was actually read
return maxCount;
}
// Instantiate the class
template class File<uint8_t>;
template class File<uint16_t>;
template class File<uint32_t>;
template class File<uint64_t>;
template class File<int8_t>;
template class File<int16_t>;
template class File<int32_t>;
template class File<int64_t>;
template class File<float>;
template class File<double>;
template class File<Complex>;
}

52
dsp/source/file.h Normal file
View File

@@ -0,0 +1,52 @@
#pragma once
#include <string>
#include <fstream>
namespace dsp::source {
template <class T>
class File {
public:
// Default constructor
File();
/**
* Create a file sink and open a file for reading.
* @param path Path to the file to read from.
* @param repeat True if the file should seek back to the beginning at the end.
*/
File(const std::string& path, bool repeat = false);
// Destructor
~File();
/**
* Open a file for reading.
* @param path Path to the file to read from.
* @param repeat True if the file should seek back to the beginning at the end.
*/
void open(const std::string& path, bool repeat = false);
/**
* Check if a file is open.
* @return True if a file is open, False otherwise.
*/
bool isOpen();
/**
* Close the file if one is open. Does nothing if no file is open.
*/
void close();
/**
* Source samples.
* @param out Buffer to write the sourced samples to.
* @param maxCount Maximum number of samples to source.
* @return Number of samples sourced.
*/
size_t source(T* out, size_t maxCount);
private:
bool repeat = false;
std::ifstream file;
};
}

103
dsp/source/wgn.cpp Normal file
View File

@@ -0,0 +1,103 @@
#include "wgn.h"
#include <type_traits>
#include <stdlib.h>
#include <volk/volk.h>
#include "../constants.h"
#include "../complex.h"
namespace dsp::source {
template <class T>
WGN<T>::WGN() {}
template <class T>
WGN<T>::WGN(float amplitude, T offset) {
// Save the parameters
this->amplitude = amplitude;
this->offset = offset;
}
template <class T>
float WGN<T>::getAmplitude() {
// Return the amplitude
return amplitude;
}
template <class T>
void WGN<T>::setAmplitude(float amplitude) {
// Update the amplitude
this->amplitude = amplitude;
}
template <class T>
T WGN<T>::getOffset() {
// Return the offset
return offset;
}
template <class T>
void WGN<T>::setOffset(T offset) {
// Update the offset
this->offset = offset;
}
template <class T>
size_t WGN<T>::source(T* out, size_t count) {
// Choose algo depending on sample rate
if constexpr (std::is_same_v<T, float>) {
// Generate samples with a variance of 1
for (uintptr_t i = 0; i < count; i++) {
// Generate two uniforn random numbers
const double u1 = (double)rand() / (double)RAND_MAX;
const double u2 = (double)rand() / (double)RAND_MAX;
// Generate a normally distributed number from them
const double z = sqrt(-2 * log(u1)) * cos(2.0 * DSP_PI * u2);
// Save it
out[i] = (float)z;
}
// Apply the scaling if necessary
if (amplitude != 1.0f) { volk_32f_s32f_multiply_32f(out, out, amplitude, count); }
// Apply the offset if necessary
if (offset != 0.0f) { volk_32f_s32f_add_32f(out, out, offset, count); }
}
else if constexpr (std::is_same_v<T, Complex>) {
// Generate samples with a variance of 1
for (uintptr_t i = 0; i < count; i++) {
// Generate two uniforn random numbers
const double u1 = (double)rand() / (double)RAND_MAX;
const double u2 = (double)rand() / (double)RAND_MAX;
// Generate two normally distributed number from them
const double sqr = sqrt(-2 * log(u1));
const double phi = 2.0 * DSP_PI * u2;
const double z1 = sqr * cos(phi);
const double z2 = sqr * sin(phi);
// Save it
out[i].re = z1;
out[i].im = z2;
}
// Apply the scaling if necessary
if (amplitude != 1.0f) { volk_32fc_s32fc_multiply_32fc((lv_32fc_t*)out, (const lv_32fc_t*)out, amplitude, count); }
// Apply the offset if necessary
if (offset != 0.0f) {
// Apply the offset without volk since that kernel is not available
for (uintptr_t i = 0; i < count; i++) {
out[i] += offset;
}
}
}
// Return the sample count
return count;
}
// Instantiate the class
template class WGN<float>;
template class WGN<Complex>;
}

53
dsp/source/wgn.h Normal file
View File

@@ -0,0 +1,53 @@
#pragma once
namespace dsp::source {
template <class T>
class WGN {
public:
// Default constructor
WGN();
/**
* Create a White Guassian Noise (WGN) source.
* @param amplitude RMS amplitude.
* @param offset DC offset.
*/
WGN(float amplitude, T offset = 0.0f);
/**
* Get the RMS amplitude.
* @return RMS amplitude.
*/
float getAmplitude();
/**
* Set the RMS ampltiude.
* @param amplitude RMS amplitude.
*/
void setAmplitude(float amplitude);
/**
* Get the DC offset.
* @return DC offset.
*/
T getOffset();
/**
* Set the DC offset.
* @param offset DC offset.
*/
void setOffset(T offset);
/**
* Source samples.
* @param out Buffer to write the sourced samples to.
* @param count Number of samples to source.
* @return Number of samples sourced. Always equal to `count` for this class.
*/
size_t source(T* out, size_t count);
private:
float amplitude;
T offset;
};
}

View File

@@ -1,5 +1,6 @@
#include "stream.h"
#include "./complex.h"
#include <volk/volk.h>
namespace dsp {
template <typename T>

View File

@@ -100,7 +100,7 @@ namespace dsp {
/**
* Send a set of sample buffers.
* @param count Number of valid samples in each channel buffer.
* @param count Number of valid samples in each channel buffer. Zero indicates an End-of-Stream.
* @param channels Number of valid channels channels.
* @return False if the sender thread must exit, true otherwise.
*/

View File

@@ -1,12 +1,69 @@
#include "taps.h"
#include "complex.h"
#include <utility>
namespace dsp {
template <class T>
Taps<T>::Taps() {}
template <class T>
Taps<T>::Taps(const T* taps, size_t count) : Buffer<T>(taps, count) {}
Taps<T>::Taps(const T* taps, size_t count) {
// Intialize the buffer
buffer = Buffer<T>(taps, count);
// Set the size
_size = count;
}
template <class T>
Taps<T>::Taps(const Taps<T>& b) {
// Reserve enough space in the buffer
buffer.reserve(b._size);
// Copy over the taps
memcpy(buffer.data(), b.buffer.data(), b._size * sizeof(T));
// Set the size
_size = b._size;
}
template <class T>
Taps<T>::Taps(Taps<T>&& b) {
// Move the buffer
buffer = std::move(b.buffer);
// Move the size
_size = b._size;
b._size = 0;
}
template <class T>
Taps<T>& Taps<T>::operator=(const Taps<T>& b) {
// Reserve enough space in the buffer
buffer.reserve(b._size);
// Copy over the taps
memcpy(buffer.data(), b.buffer.data(), b._size * sizeof(T));
// Set the size
_size = b._size;
// Return self
return *this;
}
template <class T>
Taps<T>& Taps<T>::operator=(Taps<T>&& b) {
// Move the buffer
buffer = std::move(b.buffer);
// Move the size
_size = b._size;
b._size = 0;
// Return self
return *this;
}
template class Taps<float>;
template class Taps<Complex>;

View File

@@ -2,12 +2,14 @@
#include "buffer.h"
namespace dsp {
// TODO: Force inline the data stuff for maximal performance in FIR
/**
* Filter tap container.
* This class is NOT thread-safe.
*/
template <class T>
class Taps : public Buffer<T> {
class Taps {
public:
// Default constructor
Taps();
@@ -19,12 +21,58 @@ namespace dsp {
*/
Taps(const T* taps, size_t count);
// Copy constructor
Taps(const Taps<T>& b);
// Move constructor
Taps(Taps<T>&& b);
// Copy assignment operator
Taps<T>& operator=(const Taps<T>& b);
// Move assignment operator
Taps<T>& operator=(Taps<T>&& b);
/**
* Get the number of taps.
* @return Number of taps.
*/
inline size_t size() { return _size; }
/**
* Get a pointer to the taps.
*/
inline T* data() { return buffer.data(); }
/**
* Get a const pointer to the taps.
*/
inline const T* data() const { return buffer.data(); }
// TODO: Operations to combine, window, etc taps efficiently
protected:
using Buffer<T>::realloc;
/**
* Cast to bool.
* @return True if there are more than zero taps, false otherwise.
*/
inline operator bool() const { return _size; }
private:
using Buffer<T>::reserve;
/**
* Access a sample by index.
* @param index Index of the tap.
* @return Tap at index.
*/
inline T& operator[](uintptr_t index) { return buffer[index]; }
/**
* Get a sample by index.
* @param index Index of the tap.
* @return Tap at index.
*/
inline const T& operator[](uintptr_t index) const { return buffer[index]; }
protected:
Buffer<T> buffer;
size_t _size = 0;
};
}

View File

@@ -3,58 +3,92 @@
namespace dsp::taps {
LowPass::LowPass() {}
LowPass::LowPass(float cutoff, float transWidth, float samplerate) {
LowPass::LowPass(double cutoff, double transWidth, double samplerate, double gain, bool forceOdd) {
// Save parameters
this->cutoff = cutoff;
this->transWidth = transWidth;
this->samplerate = samplerate;
this->gain = gain;
this->forceOdd = forceOdd;
// Generate filter
// Generate the filter
generate();
}
float LowPass::getCutoff() {
double LowPass::getCutoff() {
// Return the cutoff
return cutoff;
}
void LowPass::setCutoff(float cutoff, float transWidth) {
// Update parameter
void LowPass::setCutoff(double cutoff, double transWidth) {
// Update the cutoff
this->cutoff = cutoff;
// If the transition width is given, update is as well
if (transWidth > 0) { this->transWidth = transWidth; }
if (!isnan(transWidth)) { this->transWidth = transWidth; }
// Regenerate filter
// Re-generate the filter
generate();
}
float LowPass::getTransWidth() {
double LowPass::getTransWidth() {
// Return the transition width
return transWidth;
}
void LowPass::setTransWidth(float transWidth) {
// Update parameter
void LowPass::setTransWidth(double transWidth) {
// Update the transition width
this->transWidth = transWidth;
// Regenerate filter
// Re-generate the filter
generate();
}
float LowPass::getSamplerate() {
double LowPass::getSamplerate() {
// Return the sampling rate
return samplerate;
}
void LowPass::setSamplerate(float samplerate) {
// Update parameter
void LowPass::setSamplerate(double samplerate) {
// Update sampling rate
this->samplerate = samplerate;
// Regenerate filter
// Re-generate filter
generate();
}
double LowPass::getGain() {
// Return the gain
return gain;
}
void LowPass::setGain(double gain) {
// Update the gain
this->gain = gain;
// Re-generate the filter
generate();
}
double LowPass::getForceOdd() {
// Return force odd
return forceOdd;
}
void LowPass::setForceOdd(bool forceOdd) {
// Update force odd
this->forceOdd = forceOdd;
// Re-generate the filter
generate();
}
void LowPass::generate() {
// Reallocate the buffer
realloc(0 /*TODO: Tap count estimation*/);
// Estimate the number of required taps
// Reserve enough space in the buffer
// TODO
// Generate taps
// TODO

View File

@@ -1,5 +1,6 @@
#pragma once
#include "../taps.h"
#include <math.h>
namespace dsp::taps {
// TODO: Add option to only use an odd number of taps and an option to query the delay
@@ -18,48 +19,79 @@ namespace dsp::taps {
* @param cutoff 3dB cutoff frequency in Hz.
* @param transWidth Transition width in Hz.
* @param samplerate Sample rate in Hz.
* @param gain Passband amplitude gain.
* @param forceOdd Ensures the filter has an odd number of taps. Useful to guarantee an integer delay.
*/
LowPass(float cutoff, float transWidth, float samplerate);
LowPass(double cutoff, double transWidth, double samplerate, double gain = 1.0, bool forceOdd = false);
/**
* Get cutoff frequency.
* Get the cutoff frequency.
* @return Cutoff frequency in Hz.
*/
float getCutoff();
double getCutoff();
/**
* Set cutoff frequency.
* Set the cutoff frequency.
* @param cutoff Cutoff frequency in Hz.
* @param transWidth Transition width in Hz. Negative if unchanged.
* @param transWidth Transition width in Hz. NAN if unchanged.
*/
void setCutoff(float cutoff, float transWidth = -1);
void setCutoff(double cutoff, double transWidth = NAN);
/**
* Get transition width.
* Get the transition width.
* @return Transition width in Hz.
*/
float getTransWidth();
double getTransWidth();
/**
* Set transition width.
* Set the transition width.
* @param transWidth Transition width in Hz.
*/
void setTransWidth(float transWidth);
void setTransWidth(double transWidth);
/**
* Get sample rate.
* Get the sampling rate.
* @return Sampling rate in Hz.
*/
float getSamplerate();
double getSamplerate();
/**
* Set sample rate.
* Set the sampling rate.
* @param samplerate Sample rate in Hz.
*/
void setSamplerate(float samplerate);
void setSamplerate(double samplerate);
/**
* Get the passband amplitude gain.
* @return Passband amplitude gain in linear scale.
*/
double getGain();
/**
* Set the passband amplitude gain.
* @param gain Passband gain in linear scale.
*/
void setGain(double gain);
/**
* Get whether the filter is forced to an odd length. Useful to guarantee an integer delay.
* @return True if the length if forced to be odd, False otherwise.
*/
double getForceOdd();
/**
* Set whether the filter is forced to an odd length. Useful to guarantee an integer delay.
* @param forceOdd True to force the length to be odd, False otherwise.
*/
void setForceOdd(bool forceOdd);
private:
void generate();
float cutoff = 0.0f;
float transWidth = 0.0f;
float samplerate = 0.0f;
double cutoff = 0.0;
double transWidth = 0.0;
double samplerate = 0.0;
double gain = 0.0;
bool forceOdd = false;
};
}

View File

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

View File

@@ -2,9 +2,9 @@
#include "../window.h"
namespace dsp::window {
class Rectangular : public Window {
class Boxcar : public Window {
public:
Rectangular();
Boxcar();
private:
void define();

View File

@@ -8,7 +8,7 @@ namespace dsp::window {
void Hann::define() {
def = [](float x) {
float y = sinf(DSP_PI*x);
float y = sinf(DSP_PI_FL*x);
return y*y;
};
}

180
dsp/worker.h Normal file
View File

@@ -0,0 +1,180 @@
#pragma once
#include <thread>
#include "stream.h"
#include "processor.h"
#include <type_traits>
namespace dsp {
// TODO: Thread safety
template <class PROC, class T_IN, class T_OUT>
class SISOWorker : public PROC {
static_assert(std::is_base_of<SISOProcessor<T_IN, T_OUT>, PROC>::value);
public:
// Default constructor
inline SISOWorker() {}
/**
* Create a processor worker from the underlining processor block.
* @param proc Processor instance.
*/
inline SISOWorker(PROC&& proc) : PROC(proc) {}
/**
* Create a processor worker from the underlining processor block and set its input stream.
* @param proc Processor instance.
* @param in Input stream.
*/
inline SISOWorker(PROC&& proc, dsp::Stream<T_IN>* in) : PROC(proc) {
// Set the input stream
setInput(in);
}
/**
* Create a processor worker from the underlining processor block and set its input and/or output stream.
* @param proc Processor instance.
* @param in Input stream. Null if unused.
* @param out Output stream. Null if unused.
*/
inline SISOWorker(PROC&& proc, dsp::Stream<T_IN>* in, dsp::Stream<T_OUT>* out) : PROC(proc) {
// Set the input stream if it was given
if (in) { setInput(in); };
// Set the output stream if it was given
if (out) { setOutput(out); };
}
// Destructor
~SISOWorker() {
// Stop the worker
stop();
}
// TODO: Copy and move constructors and assignments
/**
* Get the input stream.
* @return Input stream. If one doesn't exist yet, it is created.
*/
inline dsp::Stream<T_IN>* in() {
// If it doesn't exists, create it
if (!_in) {
// Allocate the stream
setInput(new dsp::Stream<T_IN>());
// Mark ownership
ownIn = true;
}
// Return the input stream
return _in;
}
/**
* Set the input stream. If one had been automatically created, it is freed.
* @param in New input stream.
*/
inline void setInput(dsp::Stream<T_IN>* in) {
// If the input exists and is owned, free it
if (_in && ownIn) { delete _in; }
// Save the input
this->_in = in;
// Mark non-ownership
ownIn = false;
}
/**
* Get the output stream.
* @return Output stream. If one doesn't exist yet, it is created.
*/
inline dsp::Stream<T_OUT>* out() {
// If it doesn't exists, create it
if (!_out) {
// Allocate the stream
setOutput(new dsp::Stream<T_OUT>());
// Mark ownership
ownOut = true;
}
// Return the output stream
return _out;
}
/**
* Set the output stream. If one had been automatically created, it is freed.
* @param in New output stream.
*/
inline void setOutput(dsp::Stream<T_OUT>* out) {
// If the output exists and is owned, free it
if (_out && ownOut) { delete _out; }
// Save the output
this->_out = out;
// Mark non-ownership
ownOut = false;
}
inline void start() {
// If already running, do nothing
if (run) { return; }
// Start the worker thread
workerThread = std::thread(&SISOWorker<PROC, T_IN, T_OUT>::worker, this);
}
inline void stop() {
// If not running, do nothing
if (!run) { return; }
// Set the stop signal
_in->stopReceiver();
_out->stopSender();
// Wait for the thread to exit
if (workerThread.joinable()) { workerThread.join(); }
// Clear the stop signal
_in->clearRecvStop();
_out->clearSendStop();
}
inline bool running() {
return run;
}
private:
void worker() {
while (true) {
// Receive a buffer of samples
auto inBuf = _in->recv();
if (!inBuf.samples) { break; }
// Reserve an output buffer (TODO: Query the proc)
auto outBuf = _out->reserve(inBuf.samples);
// Process the samples
size_t outCount = processSafe(inBuf.buffer[0], outBuf.buffer[0], inBuf.samples);
// Flush the input buffer
_in->flush();
// Send the output samples
if (!_out->send(outCount)) { break; }
}
}
using PROC::process;
bool run = false;
dsp::Stream<T_IN>* _in = NULL;
dsp::Stream<T_OUT>* _out = NULL;
bool ownIn = false;
bool ownOut = false;
std::thread workerThread;
};
}

View File

@@ -1,48 +0,0 @@
#include <stdio.h>
// #include "dsp/stream.h"
// #include "dsp/complex.h"
#include "dsp/window.h"
#include "dsp/window/rectangular.h"
// void sendWorker(dsp::Stream<float>& stream) {
// while (true) {
// // Obtrain a stereo buffer set
// auto set = stream.reserve(240, 2);
// // Fill the buffer
// for (int i = 0; i < set.capacity; i++) {
// set.buffer[0][i] = rand();
// set.buffer[1][i] = rand();
// }
// // Send the buffer
// if (!stream.send(set.capacity)) { break; }
// }
// }
// void recvWorker(dsp::Stream<float>& stream) {
// while (true) {
// // Receive a buffer set
// auto set = stream.recv();
// if (!set.samples) { break; }
// // Process
// // TODO: Do something
// // Flush the buffer set
// stream.flush();
// }
// }
void test(const dsp::Window& win) {
float test[7];
win.generate(test, 7);
for (int i = 0; i < 7; i++) {
printf("window[%d] = %f\n", i, test[i]);
}
}
int main() {
test(dsp::window::Rectangular());
return 0;
}

View File

@@ -1,9 +1,71 @@
#include <stdio.h>
#include <exception>
#include "dsp/buffer.h"
#include "dsp/demod/fm.h"
#include "dsp/sink/file.h"
#include <atomic>
#include <thread>
#include "dsp/source/wgn.h"
#define BUFFER_SIZE 1250
void sendWorker(dsp::Stream<dsp::Complex>* input) {
// Generate a buffer of random data
dsp::source::WGN<dsp::Complex> wgn(1.0);
dsp::Buffer<dsp::Complex> randShit(BUFFER_SIZE);
wgn.source(randShit.data(), BUFFER_SIZE);
while (true) {
// Get a buffer for sending
auto bset = input->reserve(BUFFER_SIZE);
// Copy over the samples
memcpy(bset.buffer[0], randShit.data(), BUFFER_SIZE * sizeof(dsp::Complex));
// Send the samples
if (!input->send(BUFFER_SIZE)) { break; }
}
}
std::atomic_uint64_t counter;
void receiveWorker(dsp::Stream<float>* output) {
while (true) {
// Receive a buffer
auto bset = output->recv();
if (!bset.samples) { break; }
// Add to the counter
counter += bset.samples;
// Flush the buffer
output->flush();
}
}
int main() {
try {
// Define the DSP
dsp::demod::FMw fmw = dsp::demod::FM(6125, 50e3);
// Start the send worker
std::thread sendThread(sendWorker, fmw.in());
std::thread receiveThread(receiveWorker, fmw.out());
// Start the FM demod
fmw.start();
// Start receiving
while (true) {
// Get the number of samples processed
size_t proc = counter.exchange(0);
// Print
printf("%lf MS/s\n", (double)proc / 1e6);
// Wait
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
return 0;
}

View File

@@ -6,4 +6,7 @@ Think a lot about how multichannel streams are to be handled. Doing it all in on
Think about how blocks handle multi-channel streams. Maybe sometimes you want different behavior for each channel, so duplicating the DSP as is could be stupid
Using multiple threads for audio may be slower than interleaved channels. It could also add latency since it has to be re-interleaved at the end of the DSP.
Using multiple threads for audio may be slower than interleaved channels. It could also add latency since it has to be re-interleaved at the end of the DSP.
Need a way to represent End-of-File for batch processing. That way sources could emit the flag, and sinks or processing threads could await it