Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions blocks/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ add_subdirectory(http)
add_subdirectory(math)
add_subdirectory(soapy)
add_subdirectory(testing)
add_subdirectory(analog)

# shared library that contains all registered blocks
add_subdirectory(libs)
28 changes: 28 additions & 0 deletions blocks/analog/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
set(GrAnalogBlocks_HDRS
include/gnuradio-4.0/analog/Agc.hpp
include/gnuradio-4.0/analog/Agc2.hpp
include/gnuradio-4.0/analog/AmDemod.hpp
include/gnuradio-4.0/analog/FmDet.hpp
include/gnuradio-4.0/analog/FrequencyMod.hpp
include/gnuradio-4.0/analog/PhaseModulator.hpp
include/gnuradio-4.0/analog/QuadratureDemod.hpp)

set(GrAnalogBlocks_LIBS gr-analog)

add_library(gr-analog INTERFACE ${GrAnalogBlocks_HDRS})
target_link_libraries(gr-analog INTERFACE gnuradio-core gnuradio-algorithm)
target_include_directories(gr-analog INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/>
$<INSTALL_INTERFACE:include/>)

gr_add_block_library(
GrAnalogBlocks
MAKE_SHARED_LIBRARY
MAKE_STATIC_LIBRARY
HEADERS
${GrAnalogBlocks_HDRS}
LINK_LIBRARIES
${GrAnalogBlocks_LIBS})

if(TARGET GrAnalogBlocksShared AND ENABLE_TESTING)
add_subdirectory(test)
endif()
57 changes: 57 additions & 0 deletions blocks/analog/include/gnuradio-4.0/analog/Agc.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#ifndef INCLUDED_ANALOG_AGC_HPP
#define INCLUDED_ANALOG_AGC_HPP

#include <gnuradio-4.0/Block.hpp>
#include <gnuradio-4.0/BlockRegistry.hpp>
#include <gnuradio-4.0/Port.hpp>
#include <gnuradio-4.0/PortTraits.hpp>
#include <cmath>
#include <complex>
#include <type_traits>

namespace gr::blocks::analog {

template<typename T, bool IsFloat = std::is_floating_point_v<T>>
struct Agc : public Block<Agc<T, IsFloat>>
{
PortIn<T> in;
PortOut<T> out;

Annotated<float, "rate", Visible> rate = 1.0e-4f;
Annotated<float, "reference", Visible> ref = 1.0f;
Annotated<float, "gain", Visible> gain = 1.0f;
Annotated<float, "max_gain", Visible> gmax = 0.0f; // 0 ⇒ unlimited

GR_MAKE_REFLECTABLE(Agc, in, out, rate, ref, gain, gmax);

template<InputSpanLike InSpan, OutputSpanLike OutSpan>
work::Status processBulk(const InSpan& xs, OutSpan& ys)
{
const std::size_t n = std::min(xs.size(), ys.size());
float g = gain;
const float r = rate, R = ref, M = gmax;

for (std::size_t i = 0; i < n; ++i) {
const auto x = xs[i];
const auto y = static_cast<T>(x * g); // apply current gain

const float amp = std::abs(y); // magnitude of *output*
g += (R - amp) * r; // adapt afterwards
if (M > 0.f && g > M) g = M;

ys[i] = y;
}
gain = g;
ys.publish(n);
return work::Status::OK;
}
};

using AgcCC = Agc<std::complex<float>, false>;
using AgcFF = Agc<float, true>;

GR_REGISTER_BLOCK("gr::blocks::analog::AgcCC", gr::blocks::analog::AgcCC)
GR_REGISTER_BLOCK("gr::blocks::analog::AgcFF", gr::blocks::analog::AgcFF)

} // namespace gr::blocks::analog
#endif /* INCLUDED_ANALOG_AGC_HPP */
67 changes: 67 additions & 0 deletions blocks/analog/include/gnuradio-4.0/analog/Agc2.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#ifndef INCLUDED_ANALOG_AGC2_HPP
#define INCLUDED_ANALOG_AGC2_HPP

#include <gnuradio-4.0/Block.hpp>
#include <gnuradio-4.0/BlockRegistry.hpp>
#include <gnuradio-4.0/Port.hpp>
#include <gnuradio-4.0/PortTraits.hpp>
#include <algorithm>
#include <cmath>
#include <complex>
#include <type_traits>

namespace gr::blocks::analog {

template<typename T, bool IsFloat = std::is_floating_point_v<T>>
struct Agc2 : public Block<Agc2<T, IsFloat>>
{
PortIn<T> in;
PortOut<T> out;

Annotated<float, "attack_rate", Visible> attack_rate = 1.0e-1f;
Annotated<float, "decay_rate", Visible> decay_rate = 1.0e-2f;
Annotated<float, "reference", Visible> ref = 1.0f;
Annotated<float, "gain", Visible> gain = 1.0f;
Annotated<float, "max_gain", Visible> gmax = 0.0f; // 0 ⇒ unlimited

GR_MAKE_REFLECTABLE(Agc2, in, out,
attack_rate, decay_rate,
ref, gain, gmax);

template<InputSpanLike InSpan, OutputSpanLike OutSpan>
work::Status processBulk(const InSpan& xs, OutSpan& ys)
{
const std::size_t N = std::min(xs.size(), ys.size());
float g = gain;
const float R = ref,
A = attack_rate,
D = decay_rate,
M = gmax;

for (std::size_t i = 0; i < N; ++i) {
const auto x = xs[i];
const auto y = static_cast<T>(x * g); // apply current gain

const float amp = std::abs(y); // magnitude of output
const float rate = (std::abs(amp - R) > g) ? A : D; // attack vs decay
g -= (amp - R) * rate;

if (g < 1.0e-5f) g = 1.0e-5f; // avoid blow‑ups
if (M > 0.f && g > M) g = M;

ys[i] = y;
}
gain = g;
ys.publish(N);
return work::Status::OK;
}
};

using Agc2CC = Agc2<std::complex<float>, false>;
using Agc2FF = Agc2<float, true>;

GR_REGISTER_BLOCK("gr::blocks::analog::Agc2CC", gr::blocks::analog::Agc2CC)
GR_REGISTER_BLOCK("gr::blocks::analog::Agc2FF", gr::blocks::analog::Agc2FF)

} // namespace gr::blocks::analog
#endif /* INCLUDED_ANALOG_AGC2_HPP */
86 changes: 86 additions & 0 deletions blocks/analog/include/gnuradio-4.0/analog/AmDemod.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#ifndef INCLUDED_ANALOG_AM_DEMOD_HPP
#define INCLUDED_ANALOG_AM_DEMOD_HPP

#include <cmath>
#include <complex>
#include <numbers>

#include <gnuradio-4.0/Block.hpp>
#include <gnuradio-4.0/BlockRegistry.hpp>

namespace gr::blocks::analog
{
struct AmDemod : gr::Block<AmDemod> // fixed‑rate block (1→1)
{
using Description = Doc<
R""(@brief Envelope AM demodulator (complex → float))"">;

PortIn<std::complex<float>> in;
PortOut<float> out;

Annotated<float, "chan_rate", Doc<"complex sample‑rate [Hz]">>
chan_rate{48'000.f};
Annotated<int, "audio_decim",Doc<"legacy decimation factor ≥ 1">>
audio_decim{8};
Annotated<float, "audio_pass", Doc<"audio LPF corner [Hz]">>
audio_pass{4'000.f};
Annotated<float, "audio_stop", Doc<"stop‑band edge (kept for API parity)">>
audio_stop{5'500.f};

GR_MAKE_REFLECTABLE(
AmDemod, in, out, chan_rate, audio_decim, audio_pass, audio_stop);

void set_chan_rate (float fs) { chan_rate = fs; _recalc(); }
void set_audio_decim(int d ) { audio_decim = std::max(1, d); _recalc(); }
void set_audio_pass (float fp){ audio_pass = fp; _recalc(); }

explicit AmDemod(property_map) { _recalc(); }
AmDemod(float fs, int d, float fp, float fsb = 0.f)
: chan_rate(fs), audio_decim(std::max(1, d)),
audio_pass(fp), audio_stop(fsb)
{ _recalc(); }

void settingsChanged(const property_map&, const property_map&)
{ _recalc(); }

template<InputSpanLike InSpan,
OutputSpanLike OutSpan>
[[nodiscard]] work::Status
processBulk(const InSpan& xs, OutSpan& ys)
{
std::size_t produced = 0;

for (auto x : xs) {
const float env = std::abs(x);
_y = env + _alpha * (_y - env);

if (produced == ys.size()) {
ys.publish(produced);
return work::Status::INSUFFICIENT_OUTPUT_ITEMS;
}

ys[produced++] = _y; // 1 : 1 output
}

ys.publish(produced);
return work::Status::OK;
}

private:
float _alpha{1.f}; // IIR coefficient
float _y{0.f}; // filter state

void _recalc()
{
/* one‑pole IIR coefficient (designed at the INPUT rate) */
const float dt = 1.0f / chan_rate;
_alpha = std::exp(-2.f * std::numbers::pi_v<float>
* audio_pass * dt);
}
};

GR_REGISTER_BLOCK("gr::blocks::analog::AmDemod",
gr::blocks::analog::AmDemod)

} // namespace gr::blocks::analog
#endif /* INCLUDED_ANALOG_AM_DEMOD_HPP */
68 changes: 68 additions & 0 deletions blocks/analog/include/gnuradio-4.0/analog/FmDet.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#ifndef GNURADIO_ANALOG_FMDET_HPP
#define GNURADIO_ANALOG_FMDET_HPP

#include <gnuradio-4.0/Block.hpp>
#include <gnuradio-4.0/BlockRegistry.hpp>
#include <complex>
#include <numbers>

namespace gr::blocks::analog {

template<typename T> struct FmDet;

template<>
struct FmDet<std::complex<float>> : Block<FmDet<std::complex<float>>>
{
using Description = Doc<"IQ slope detector (complex → float)">;

PortIn<std::complex<float>> in;
PortOut<float> out;

Annotated<float,"samplerate",Visible> samplerate = 1.0f;
Annotated<float,"freq_low", Visible> f_low = -1.0f;
Annotated<float,"freq_high", Visible> f_high = 1.0f;
Annotated<float,"scale", Visible> scl = 1.0f;
GR_MAKE_REFLECTABLE(FmDet,in,out,samplerate,f_low,f_high,scl);

std::complex<float> _prev = {1.0f, 0.0f}; // initial phase = 0
float _bias = 0.0f;

void recompute_bias()
{
const float hi = f_high, lo = f_low;
_bias = (hi != lo) ? 0.5f * scl * (hi + lo) / (hi - lo) : 0.0f;
}

void set_scale(float s) { scl = s; recompute_bias(); }
float scale() const { return scl; }

void set_freq_range(float lo,float hi) { f_low = lo; f_high = hi; recompute_bias(); }
float freq_low() const { return f_low; }
float freq_high() const { return f_high; }
float freq() const { return 0.0f; } // legacy stub
float bias() const { return _bias; }

void start() { _prev = {1.0f,0.0f}; recompute_bias(); }

work::Status processOne(const std::complex<float>& x, float& y)
{
const std::complex<float> prod = x * std::conj(_prev);
_prev = x;
y = scl * std::arg(prod) - _bias;
return work::Status::OK;
}

template<InputSpanLike InSpan, OutputSpanLike OutSpan>
work::Status processBulk(const InSpan& xs, OutSpan& ys)
{
const std::size_t n = std::min(xs.size(), ys.size());
for(std::size_t i = 0; i < n; ++i) processOne(xs[i], ys[i]);
ys.publish(n);
return work::Status::OK;
}
};

GR_REGISTER_BLOCK("gr::blocks::analog::FmDet",gr::blocks::analog::FmDet,([T]),[ std::complex<float> ])

} // namespace gr::blocks::analog
#endif /* GNURADIO_ANALOG_FMDET_HPP */
70 changes: 70 additions & 0 deletions blocks/analog/include/gnuradio-4.0/analog/FrequencyMod.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#ifndef GNURADIO_ANALOG_FREQUENCYMOD_HPP
#define GNURADIO_ANALOG_FREQUENCYMOD_HPP

#include <gnuradio-4.0/Block.hpp>
#include <gnuradio-4.0/BlockRegistry.hpp>

#include <complex>
#include <numbers>

namespace gr::blocks::analog {

template<typename T>
struct FrequencyMod;

template<>
struct FrequencyMod<float> : Block<FrequencyMod<float>>
{
using Description = Doc<"Frequency‑modulator (float → complex<float>)">;

PortIn<float> in;
PortOut<std::complex<float>> out;

Annotated<float,
"sensitivity",
Visible,
Doc<"Phase increment [rad/sample] per input‑unit">>
sensitivity = std::numbers::pi_v<float> / 4.0f; // π/4 rad/sample

GR_MAKE_REFLECTABLE(FrequencyMod, in, out, sensitivity);

float _phase = 0.0f;
void start() { _phase = 0.0f; }

inline std::complex<float> to_polar(float ph) const
{
return { std::cos(ph), std::sin(ph) };
}

work::Status processOne(float x, std::complex<float>& y)
{
_phase += x * sensitivity;
/* keep phase in [-π, π] to avoid float overflow */
constexpr float two_pi = 2.0f * std::numbers::pi_v<float>;
if (_phase > std::numbers::pi_v<float>) _phase -= two_pi;
if (_phase < -std::numbers::pi_v<float>) _phase += two_pi;

y = to_polar(_phase);
return work::Status::OK;
}

template<InputSpanLike InSpan,
OutputSpanLike OutSpan>
work::Status processBulk(const InSpan& xs, OutSpan& ys)
{
if (xs.empty())
return work::Status::DONE;

const std::size_t n = std::min(xs.size(), ys.size());
for (std::size_t i = 0; i < n; ++i)
processOne(xs[i], ys[i]);

ys.publish(n);
return work::Status::OK;
}
};

GR_REGISTER_BLOCK("gr::blocks::analog::FrequencyMod",gr::blocks::analog::FrequencyMod,([T]),[ float ])

} // namespace gr::blocks::analog
#endif /* GNURADIO_ANALOG_FREQUENCYMOD_HPP */
Loading
Loading