diff --git a/CMakeLists.txt b/CMakeLists.txt index 30ac9bc..324c264 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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/") diff --git a/dsp/buffer.cpp b/dsp/buffer.cpp index 137278b..cfb7fc0 100644 --- a/dsp/buffer.cpp +++ b/dsp/buffer.cpp @@ -26,21 +26,21 @@ namespace dsp { template Buffer::Buffer(const Buffer& 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 Buffer::Buffer(Buffer&& 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 @@ -52,10 +52,10 @@ namespace dsp { template Buffer& Buffer::operator=(const Buffer& 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(capacity, size)); + memcpy(newbuf, buffer, std::min(_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(capacity, size)); + memcpy(newbuf, buffer, std::min(_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 @@ -145,7 +145,7 @@ namespace dsp { // Mark the buffer as freed buffer = NULL; - capacity = 0; + _capacity = 0; } // Instantiate the class diff --git a/dsp/buffer.h b/dsp/buffer.h index 9733b48..de530e0 100644 --- a/dsp/buffer.h +++ b/dsp/buffer.h @@ -1,6 +1,8 @@ #pragma once #include +// 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; }; } \ No newline at end of file diff --git a/dsp/constants.h b/dsp/constants.h index 813220e..e924576 100644 --- a/dsp/constants.h +++ b/dsp/constants.h @@ -1,5 +1,8 @@ #pragma once #include -#define DSP_PI ((float)3.141592653589793) -#define DSP_SQRT2 ((float)1.414213562373095) \ No newline at end of file +#define DSP_PI 3.141592653589793 +#define DSP_SQRT2 1.414213562373095 + +#define DSP_PI_FL ((float)DSP_PI) +#define DSP_SQRT2_FL ((float)DSP_SQRT2) \ No newline at end of file diff --git a/dsp/demod/fm.cpp b/dsp/demod/fm.cpp new file mode 100644 index 0000000..4e6334c --- /dev/null +++ b/dsp/demod/fm.cpp @@ -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; + } +} \ No newline at end of file diff --git a/dsp/demod/fm.h b/dsp/demod/fm.h new file mode 100644 index 0000000..9e8108b --- /dev/null +++ b/dsp/demod/fm.h @@ -0,0 +1,65 @@ +#pragma once +#include "../processor.h" +#include "../worker.h" +#include "../complex.h" + +namespace dsp::demod { + class FM : SISOProcessor { + 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; +} \ No newline at end of file diff --git a/dsp/filter/fir.cpp b/dsp/filter/fir.cpp new file mode 100644 index 0000000..665bad2 --- /dev/null +++ b/dsp/filter/fir.cpp @@ -0,0 +1,89 @@ +#include "fir.h" +#include "../complex.h" +#include + +namespace dsp::filter { + template + FIR::FIR() {} + + template + FIR::FIR(const Taps& taps) { + // Save the taps + this->taps = taps; + + // Pre-allocate the history buffer + hist.reserve(taps.size()); + + // Initialize the state + reset(); + } + + template + FIR::FIR(Taps&& taps) { + // Save the taps + this->taps = taps; + + // Pre-allocate the history buffer + hist.reserve(taps.size()); + + // Initialize the state + reset(); + } + + template + const Taps& FIR::getTaps() const { + // Return the taps + return taps; + } + + template + void FIR::setTaps(const Taps& taps) { + // Update the taps + this->taps = taps; + } + + template + void FIR::setTaps(Taps&& taps) { + // Update the taps + this->taps = taps; + } + + template + void FIR::reset() { + // Zero out the history buffer + memset(hist.data(), 0, (taps.size() - 1) * sizeof(SAMPLE)); + } + + template + size_t FIR::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) { + volk_32f_x2_dot_prod_32f(&out[i], &hist[i], taps.data(), taps.size()); + } + else if constexpr (std::is_same_v && std::is_same_v) { + 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 && std::is_same_v) { + 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; + template class FIR; + template class FIR; +} \ No newline at end of file diff --git a/dsp/filter/fir.h b/dsp/filter/fir.h new file mode 100644 index 0000000..1b4c70e --- /dev/null +++ b/dsp/filter/fir.h @@ -0,0 +1,61 @@ +#pragma once +#include "../processor.h" +#include "../buffer.h" +#include "../taps.h" + +namespace dsp::filter { + template + class FIR : public SISOProcessor { + public: + // Default constructor + FIR(); + + /** + * Create a FIR filter. + * @param taps Taps to use for the filter. + */ + FIR(const Taps& taps); + + /** + * Create a FIR filter. + * @param taps Taps to use for the filter. + */ + FIR(Taps&& taps); + + /** + * Get the filter taps. + * @return Filter taps. + */ + const Taps& getTaps() const; + + /** + * Set the filter taps. + * @param taps Filter taps. + */ + void setTaps(const Taps& taps); + + /** + * Set the filter taps. + * @param taps Filter taps. + */ + void setTaps(Taps&& 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 hist; + Taps taps; + }; +} \ No newline at end of file diff --git a/dsp/math/add.cpp b/dsp/math/add.cpp new file mode 100644 index 0000000..bdda5c0 --- /dev/null +++ b/dsp/math/add.cpp @@ -0,0 +1,18 @@ +#include "add.h" +#include +#include +#include "../complex.h" + +namespace dsp::math { + template + size_t Add::process(const T* a, const T* b, T* out, size_t count) { + if constexpr (std::is_same_v) { + // Add using volk + volk_32f_x2_add_32f(out, a, b, count); + } + else if constexpr (std::is_same_v) { + // Add using volk + volk_32fc_x2_add_32fc(out, a, b, count); + } + } +} \ No newline at end of file diff --git a/dsp/math/add.h b/dsp/math/add.h new file mode 100644 index 0000000..5d7903d --- /dev/null +++ b/dsp/math/add.h @@ -0,0 +1,17 @@ +#pragma once + +namespace dsp::math { + template + 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); +} \ No newline at end of file diff --git a/dsp/multirate/decimating_fir.cpp b/dsp/multirate/decimating_fir.cpp new file mode 100644 index 0000000..596250a --- /dev/null +++ b/dsp/multirate/decimating_fir.cpp @@ -0,0 +1,101 @@ +#include "decimating_fir.h" +#include "../complex.h" +#include + +namespace dsp::multirate { + template + DecimatingFIR::DecimatingFIR() {} + + template + DecimatingFIR::DecimatingFIR(const Taps& 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 + DecimatingFIR::DecimatingFIR(Taps&& 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 + const Taps& DecimatingFIR::getTaps() const { + // Return the taps + return taps; + } + + template + void DecimatingFIR::setTaps(const Taps& taps, int decim) { + // Update the taps + this->taps = taps; + + // Update the decimation factor if it was given + if (decim) { this->decim = decim; } + } + + template + void DecimatingFIR::setTaps(Taps&& taps, int decim) { + // Update the taps + this->taps = taps; + + // Update the decimation factor if it was given + if (decim) { this->decim = decim; } + } + + template + void DecimatingFIR::reset() { + // Zero out the history buffer + memset(hist.data(), 0, (taps.size() - 1) * sizeof(SAMPLE)); + + // Reset the offset + offset = 0; + } + + template + size_t DecimatingFIR::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) { + volk_32f_x2_dot_prod_32f(&out[io++], &hist[i], taps.data(), taps.size()); + } + else if constexpr (std::is_same_v && std::is_same_v) { + 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 && std::is_same_v) { + 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; + } +} \ No newline at end of file diff --git a/dsp/multirate/decimating_fir.h b/dsp/multirate/decimating_fir.h new file mode 100644 index 0000000..b780579 --- /dev/null +++ b/dsp/multirate/decimating_fir.h @@ -0,0 +1,79 @@ +#pragma once +#include "../processor.h" +#include "../buffer.h" +#include "../taps.h" + +namespace dsp::multirate { + template + 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& taps, int decim); + + /** + * Create a decimating FIR filter. + * @param taps Taps to use for the filter. + * @param decim Decimation factor. + */ + DecimatingFIR(Taps&& taps, int decim); + + /** + * Get the filter taps. + * @return Filter taps. + */ + const Taps& getTaps() const; + + /** + * Set the filter taps. + * @param taps Filter taps. + * @param decim Decimation factor. Zero to keep unchanged. + */ + void setTaps(const Taps& taps, int decim = 0); + + /** + * Set the filter taps. + * @param taps Filter taps. + * @param decim Decimation factor. Zero to keep unchanged. + */ + void setTaps(Taps&& 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 hist; + uintptr_t offset = 0; + Taps taps; + int decim = 0; + }; +} \ No newline at end of file diff --git a/dsp/multirate/interpolating_fir.cpp b/dsp/multirate/interpolating_fir.cpp new file mode 100644 index 0000000..0a75138 --- /dev/null +++ b/dsp/multirate/interpolating_fir.cpp @@ -0,0 +1,113 @@ +#include "interpolating_fir.h" +#include "../complex.h" +#include + +namespace dsp::multirate { + template + InterpolatingFIR::InterpolatingFIR() {} + + template + InterpolatingFIR::InterpolatingFIR(const Taps& 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 + InterpolatingFIR::InterpolatingFIR(Taps&& 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 + const Taps& InterpolatingFIR::getTaps() const { + // Return the taps + return taps; + } + + template + void InterpolatingFIR::setTaps(const Taps& 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 + void InterpolatingFIR::setTaps(Taps&& 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 + void InterpolatingFIR::reset() { + // Zero out the history buffer + memset(hist.data(), 0, (taps.size() - 1) * sizeof(SAMPLE)); + + // Reset the offset + offset = 0; + } + + template + size_t InterpolatingFIR::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) { + volk_32f_x2_dot_prod_32f(&out[io++], &hist[i], taps.data(), taps.size()); + } + else if constexpr (std::is_same_v && std::is_same_v) { + 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 && std::is_same_v) { + 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; + } +} \ No newline at end of file diff --git a/dsp/multirate/interpolating_fir.h b/dsp/multirate/interpolating_fir.h new file mode 100644 index 0000000..e2ac2bb --- /dev/null +++ b/dsp/multirate/interpolating_fir.h @@ -0,0 +1,83 @@ +#pragma once +#include "../processor.h" +#include "../buffer.h" +#include "../taps.h" +#include + +namespace dsp::multirate { + template + 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& taps, int interp); + + /** + * Create a decimating FIR filter. + * @param taps Taps to use for the filter. + * @param interp Interpolation factor. + */ + InterpolatingFIR(Taps&& taps, int interp); + + /** + * Get the filter taps. + * @return Filter taps. + */ + const Taps& getTaps() const; + + /** + * Set the filter taps. + * @param taps Filter taps. + * @param interp Interpolation factor. Zero to keep unchanged. + */ + void setTaps(const Taps& taps, int interp = 0); + + /** + * Set the filter taps. + * @param taps Filter taps. + * @param interp Interpolation factor. Zero to keep unchanged. + */ + void setTaps(Taps&& 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 hist; + uintptr_t offset = 0; + Taps taps; + std::vector> phases; + int interp = 0; + }; +} \ No newline at end of file diff --git a/dsp/processor.h b/dsp/processor.h index eadcf6e..c8383ef 100644 --- a/dsp/processor.h +++ b/dsp/processor.h @@ -1,10 +1,31 @@ #pragma once #include +#include namespace dsp { template 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 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; }; } \ No newline at end of file diff --git a/dsp/sink/file.cpp b/dsp/sink/file.cpp new file mode 100644 index 0000000..d37a719 --- /dev/null +++ b/dsp/sink/file.cpp @@ -0,0 +1,58 @@ +#include "file.h" +#include "../complex.h" + +// TODO: Thread safety + +namespace dsp::sink { + template + File::File() {} + + template + File::File(const std::string& path, bool append) { + // Open the given file + open(path, append); + } + + template + File::~File() { + // Close the file in case it's open + close(); + } + + template + void File::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 + bool File::isOpen() { + // Return the open status + return file.is_open(); + } + + template + void File::close() { + // Close the file + file.close(); + } + + template + void File::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; + template class File; + template class File; + template class File; + template class File; + template class File; + template class File; + template class File; + template class File; + template class File; + template class File; +} \ No newline at end of file diff --git a/dsp/sink/file.h b/dsp/sink/file.h new file mode 100644 index 0000000..01f4a75 --- /dev/null +++ b/dsp/sink/file.h @@ -0,0 +1,50 @@ +#pragma once +#include +#include + +namespace dsp::sink { + template + 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; + }; +} \ No newline at end of file diff --git a/dsp/source/file.cpp b/dsp/source/file.cpp new file mode 100644 index 0000000..8fe5a2f --- /dev/null +++ b/dsp/source/file.cpp @@ -0,0 +1,66 @@ +#include "file.h" +#include "../complex.h" + +// TODO: Thread safety + +namespace dsp::source { + template + File::File() {} + + template + File::File(const std::string& path, bool repeat) { + // Open the file + open(path, repeat); + } + + template + File::~File() { + // Close the file if its open + close(); + } + + template + void File::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 + bool File::isOpen() { + // Return the open status + return file.is_open(); + } + + template + void File::close() { + // Close the file + file.close(); + } + + template + size_t File::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; + template class File; + template class File; + template class File; + template class File; + template class File; + template class File; + template class File; + template class File; + template class File; + template class File; +} \ No newline at end of file diff --git a/dsp/source/file.h b/dsp/source/file.h new file mode 100644 index 0000000..91fb7f1 --- /dev/null +++ b/dsp/source/file.h @@ -0,0 +1,52 @@ +#pragma once +#include +#include + +namespace dsp::source { + template + 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; + }; +} \ No newline at end of file diff --git a/dsp/source/wgn.cpp b/dsp/source/wgn.cpp new file mode 100644 index 0000000..ff689be --- /dev/null +++ b/dsp/source/wgn.cpp @@ -0,0 +1,103 @@ +#include "wgn.h" +#include +#include +#include +#include "../constants.h" +#include "../complex.h" + +namespace dsp::source { + template + WGN::WGN() {} + + template + WGN::WGN(float amplitude, T offset) { + // Save the parameters + this->amplitude = amplitude; + this->offset = offset; + } + + template + float WGN::getAmplitude() { + // Return the amplitude + return amplitude; + } + + template + void WGN::setAmplitude(float amplitude) { + // Update the amplitude + this->amplitude = amplitude; + } + + template + T WGN::getOffset() { + // Return the offset + return offset; + } + + template + void WGN::setOffset(T offset) { + // Update the offset + this->offset = offset; + } + + template + size_t WGN::source(T* out, size_t count) { + // Choose algo depending on sample rate + if constexpr (std::is_same_v) { + // 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) { + // 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; + template class WGN; +} \ No newline at end of file diff --git a/dsp/source/wgn.h b/dsp/source/wgn.h new file mode 100644 index 0000000..805fa4a --- /dev/null +++ b/dsp/source/wgn.h @@ -0,0 +1,53 @@ +#pragma once + +namespace dsp::source { + template + 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; + }; +} \ No newline at end of file diff --git a/dsp/stream.cpp b/dsp/stream.cpp index 89cfa48..3341f06 100644 --- a/dsp/stream.cpp +++ b/dsp/stream.cpp @@ -1,5 +1,6 @@ #include "stream.h" #include "./complex.h" +#include namespace dsp { template diff --git a/dsp/stream.h b/dsp/stream.h index 30e8687..13f264b 100644 --- a/dsp/stream.h +++ b/dsp/stream.h @@ -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. */ diff --git a/dsp/taps.cpp b/dsp/taps.cpp index 8f3918c..3f056ef 100644 --- a/dsp/taps.cpp +++ b/dsp/taps.cpp @@ -1,12 +1,69 @@ #include "taps.h" #include "complex.h" +#include namespace dsp { template Taps::Taps() {} template - Taps::Taps(const T* taps, size_t count) : Buffer(taps, count) {} + Taps::Taps(const T* taps, size_t count) { + // Intialize the buffer + buffer = Buffer(taps, count); + + // Set the size + _size = count; + } + + template + Taps::Taps(const Taps& 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 + Taps::Taps(Taps&& b) { + // Move the buffer + buffer = std::move(b.buffer); + + // Move the size + _size = b._size; + b._size = 0; + } + + template + Taps& Taps::operator=(const Taps& 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 + Taps& Taps::operator=(Taps&& 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; template class Taps; diff --git a/dsp/taps.h b/dsp/taps.h index 3908791..c44d116 100644 --- a/dsp/taps.h +++ b/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 Taps : public Buffer { + class Taps { public: // Default constructor Taps(); @@ -19,12 +21,58 @@ namespace dsp { */ Taps(const T* taps, size_t count); + // Copy constructor + Taps(const Taps& b); + + // Move constructor + Taps(Taps&& b); + + // Copy assignment operator + Taps& operator=(const Taps& b); + + // Move assignment operator + Taps& operator=(Taps&& 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::realloc; + /** + * Cast to bool. + * @return True if there are more than zero taps, false otherwise. + */ + inline operator bool() const { return _size; } - private: - using Buffer::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 buffer; + size_t _size = 0; }; } \ No newline at end of file diff --git a/dsp/taps/low_pass.cpp b/dsp/taps/low_pass.cpp index be6cb8c..e9216cb 100644 --- a/dsp/taps/low_pass.cpp +++ b/dsp/taps/low_pass.cpp @@ -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 diff --git a/dsp/taps/low_pass.h b/dsp/taps/low_pass.h index 453b18b..c434425 100644 --- a/dsp/taps/low_pass.h +++ b/dsp/taps/low_pass.h @@ -1,5 +1,6 @@ #pragma once #include "../taps.h" +#include 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; }; } \ No newline at end of file diff --git a/dsp/window/rectangular.cpp b/dsp/window/boxcar.cpp similarity index 52% rename from dsp/window/rectangular.cpp rename to dsp/window/boxcar.cpp index 0fe0b49..03b3536 100644 --- a/dsp/window/rectangular.cpp +++ b/dsp/window/boxcar.cpp @@ -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; }; } } \ No newline at end of file diff --git a/dsp/window/rectangular.h b/dsp/window/boxcar.h similarity index 66% rename from dsp/window/rectangular.h rename to dsp/window/boxcar.h index ff8c210..ccefacc 100644 --- a/dsp/window/rectangular.h +++ b/dsp/window/boxcar.h @@ -2,9 +2,9 @@ #include "../window.h" namespace dsp::window { - class Rectangular : public Window { + class Boxcar : public Window { public: - Rectangular(); + Boxcar(); private: void define(); diff --git a/dsp/window/hann.cpp b/dsp/window/hann.cpp index dfc8b3c..55fb958 100644 --- a/dsp/window/hann.cpp +++ b/dsp/window/hann.cpp @@ -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; }; } diff --git a/dsp/worker.h b/dsp/worker.h new file mode 100644 index 0000000..ee217b7 --- /dev/null +++ b/dsp/worker.h @@ -0,0 +1,180 @@ +#pragma once +#include +#include "stream.h" +#include "processor.h" +#include + +namespace dsp { + + // TODO: Thread safety + + template + class SISOWorker : public PROC { + static_assert(std::is_base_of, 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* 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* in, dsp::Stream* 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* in() { + // If it doesn't exists, create it + if (!_in) { + // Allocate the stream + setInput(new dsp::Stream()); + + // 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* 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* out() { + // If it doesn't exists, create it + if (!_out) { + // Allocate the stream + setOutput(new dsp::Stream()); + + // 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* 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::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* _in = NULL; + dsp::Stream* _out = NULL; + bool ownIn = false; + bool ownOut = false; + std::thread workerThread; + }; +} \ No newline at end of file diff --git a/main.cpp b/main.cpp deleted file mode 100644 index eb5aad2..0000000 --- a/main.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include -// #include "dsp/stream.h" -// #include "dsp/complex.h" -#include "dsp/window.h" -#include "dsp/window/rectangular.h" - -// void sendWorker(dsp::Stream& 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& 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; -} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 27d6f8f..8627acc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,9 +1,71 @@ #include #include +#include "dsp/buffer.h" +#include "dsp/demod/fm.h" +#include "dsp/sink/file.h" +#include +#include + +#include "dsp/source/wgn.h" + +#define BUFFER_SIZE 1250 + +void sendWorker(dsp::Stream* input) { + // Generate a buffer of random data + dsp::source::WGN wgn(1.0); + dsp::Buffer 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* 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; } diff --git a/todo.txt b/todo.txt index 58f6d02..29f2158 100644 --- a/todo.txt +++ b/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. \ No newline at end of file +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 +