mirror of
https://github.com/AlexandreRouma/dsp2.git
synced 2026-04-18 06:52:42 +00:00
progress
This commit is contained in:
@@ -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/")
|
||||
|
||||
@@ -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
|
||||
|
||||
20
dsp/buffer.h
20
dsp/buffer.h
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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
73
dsp/demod/fm.cpp
Normal 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
65
dsp/demod/fm.h
Normal 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
89
dsp/filter/fir.cpp
Normal 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
61
dsp/filter/fir.h
Normal 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
18
dsp/math/add.cpp
Normal 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
17
dsp/math/add.h
Normal 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>);
|
||||
}
|
||||
101
dsp/multirate/decimating_fir.cpp
Normal file
101
dsp/multirate/decimating_fir.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
79
dsp/multirate/decimating_fir.h
Normal file
79
dsp/multirate/decimating_fir.h
Normal 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;
|
||||
};
|
||||
}
|
||||
113
dsp/multirate/interpolating_fir.cpp
Normal file
113
dsp/multirate/interpolating_fir.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
83
dsp/multirate/interpolating_fir.h
Normal file
83
dsp/multirate/interpolating_fir.h
Normal 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;
|
||||
};
|
||||
}
|
||||
@@ -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
58
dsp/sink/file.cpp
Normal 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
50
dsp/sink/file.h
Normal 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
66
dsp/source/file.cpp
Normal 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
52
dsp/source/file.h
Normal 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
103
dsp/source/wgn.cpp
Normal 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
53
dsp/source/wgn.h
Normal 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;
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "stream.h"
|
||||
#include "./complex.h"
|
||||
#include <volk/volk.h>
|
||||
|
||||
namespace dsp {
|
||||
template <typename T>
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
59
dsp/taps.cpp
59
dsp/taps.cpp
@@ -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>;
|
||||
|
||||
58
dsp/taps.h
58
dsp/taps.h
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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; };
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@
|
||||
#include "../window.h"
|
||||
|
||||
namespace dsp::window {
|
||||
class Rectangular : public Window {
|
||||
class Boxcar : public Window {
|
||||
public:
|
||||
Rectangular();
|
||||
Boxcar();
|
||||
|
||||
private:
|
||||
void define();
|
||||
@@ -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
180
dsp/worker.h
Normal 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;
|
||||
};
|
||||
}
|
||||
48
main.cpp
48
main.cpp
@@ -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;
|
||||
}
|
||||
64
src/main.cpp
64
src/main.cpp
@@ -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;
|
||||
}
|
||||
|
||||
5
todo.txt
5
todo.txt
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user