Skip to content

Commit 1209224

Browse files
committed
analog: KISS refactor of AM/FM blocks + tests and ProcessOne prioritized
Signed-off-by: KrxGu <[email protected]>
1 parent e111e16 commit 1209224

File tree

14 files changed

+434
-333
lines changed

14 files changed

+434
-333
lines changed
Lines changed: 53 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,79 @@
1-
#ifndef INCLUDED_ANALOG_AGC_HPP
2-
#define INCLUDED_ANALOG_AGC_HPP
1+
#ifndef GR_BLOCKS_ANALOG_AGC_HPP_
2+
#define GR_BLOCKS_ANALOG_AGC_HPP_
33

44
#include <gnuradio-4.0/Block.hpp>
55
#include <gnuradio-4.0/BlockRegistry.hpp>
6+
7+
#include <algorithm>
68
#include <cmath>
79
#include <complex>
10+
#include <type_traits>
811

912
namespace gr::blocks::analog {
1013

11-
template<typename T, bool IsFloat = std::is_floating_point_v<T>>
12-
struct Agc : Block<Agc<T, IsFloat>>
14+
template<typename T>
15+
class Agc : public Block<Agc<T>>
1316
{
17+
public:
18+
Annotated<float, "rate", Visible> rate = 1.0e-4f;
19+
Annotated<float, "reference", Visible> reference = 1.0f;
20+
Annotated<float, "max_gain", Visible> max_gain = 0.0f; // 0 ⇒ unlimited
21+
1422
PortIn<T> in;
1523
PortOut<T> out;
1624

17-
Annotated<float, "rate", Visible> rate = 1.0e-4f;
18-
Annotated<float, "reference", Visible> ref = 1.0f;
19-
Annotated<float, "gain", Visible> gain = 1.0f;
20-
Annotated<float, "max_gain", Visible> gmax = 0.0f; // 0 ⇒ unlimited
25+
GR_MAKE_REFLECTABLE(Agc, in, out, rate, reference, max_gain);
2126

22-
GR_MAKE_REFLECTABLE(Agc, in, out, rate, ref, gain, gmax);
27+
static constexpr bool supports_selftest = true;
2328

24-
template<InputSpanLike InSpan, OutputSpanLike OutSpan>
25-
work::Status processBulk(const InSpan& xs, OutSpan& ys)
29+
void start()
2630
{
27-
const std::size_t n = std::min(xs.size(), ys.size());
28-
float g = gain;
29-
const float r = rate, R = ref, M = gmax;
31+
_gain = 1.0f;
32+
}
33+
34+
work::Status processOne(const T& x, T& y)
35+
{
36+
if (!std::isfinite(_gain))
37+
_gain = 1.0f;
3038

31-
for (std::size_t i = 0; i < n; ++i) {
32-
const auto x = xs[i];
33-
const auto y = static_cast<T>(x * g); // apply current gain
39+
const float g = _gain;
40+
y = static_cast<T>(x * g);
3441

35-
const float amp = IsFloat ? std::fabs(y)
36-
: std::abs(y); // magnitude of *output*
37-
g += (R - amp) * r; // adapt afterwards
38-
if (M > 0.f && g > M) g = M;
42+
const float amp = amplitude(y);
43+
const float r = std::clamp(float(rate), 0.0f, 1.0f);
44+
const float delta = (float(reference) - amp) * r;
45+
_gain = g + delta;
3946

40-
ys[i] = y;
41-
}
42-
gain = g;
47+
if (max_gain > 0.0f && _gain > max_gain) _gain = max_gain;
48+
if (_gain < 1.0e-5f) _gain = 1.0e-5f; // floor
49+
50+
return work::Status::OK;
51+
}
52+
53+
template<InputSpanLike InSpan, OutputSpanLike OutSpan>
54+
work::Status processBulk(const InSpan& xs, OutSpan& ys)
55+
{
56+
const std::size_t n = std::min(xs.size(), ys.size());
57+
for (std::size_t i = 0; i < n; ++i)
58+
processOne(xs[i], ys[i]);
4359
ys.publish(n);
4460
return work::Status::OK;
4561
}
62+
63+
explicit Agc(property_map) {}
64+
65+
private:
66+
static inline float amplitude(float v) { return std::fabs(v); }
67+
static inline float amplitude(const std::complex<float>& v) { return std::abs(v); }
68+
69+
float _gain {1.0f};
4670
};
4771

48-
using AgcCC = Agc<std::complex<float>, false>;
49-
using AgcFF = Agc<float, true>;
72+
using AgcFF = Agc<float>;
73+
using AgcCC = Agc<std::complex<float>>;
5074

51-
GR_REGISTER_BLOCK("gr::blocks::analog::AgcCC", gr::blocks::analog::AgcCC)
52-
GR_REGISTER_BLOCK("gr::blocks::analog::AgcFF", gr::blocks::analog::AgcFF)
75+
GR_REGISTER_BLOCK("gr::blocks::analog::AgcFF", AgcFF)
76+
GR_REGISTER_BLOCK("gr::blocks::analog::AgcCC", AgcCC)
5377

5478
} // namespace gr::blocks::analog
55-
#endif /* INCLUDED_ANALOG_AGC_HPP */
79+
#endif // GR_BLOCKS_ANALOG_AGC_HPP_
Lines changed: 52 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,77 @@
1-
#ifndef INCLUDED_ANALOG_AGC2_HPP
2-
#define INCLUDED_ANALOG_AGC2_HPP
1+
#ifndef GR_BLOCKS_ANALOG_AGC2_HPP_
2+
#define GR_BLOCKS_ANALOG_AGC2_HPP_
33

44
#include <gnuradio-4.0/Block.hpp>
55
#include <gnuradio-4.0/BlockRegistry.hpp>
6+
67
#include <algorithm>
78
#include <cmath>
89
#include <complex>
10+
#include <type_traits>
911

1012
namespace gr::blocks::analog {
1113

12-
template<typename T, bool IsFloat = std::is_floating_point_v<T>>
13-
struct Agc2 : Block<Agc2<T, IsFloat>>
14+
template<typename T>
15+
class Agc2 : public Block<Agc2<T>>
1416
{
17+
public:
18+
Annotated<float, "attack_rate", Visible> attack_rate = 1.0e-1f;
19+
Annotated<float, "decay_rate", Visible> decay_rate = 1.0e-2f;
20+
Annotated<float, "reference", Visible> reference = 1.0f;
21+
Annotated<float, "max_gain", Visible> max_gain = 0.0f; // 0 ⇒ unlimited
22+
1523
PortIn<T> in;
1624
PortOut<T> out;
1725

18-
Annotated<float, "attack_rate", Visible> attack_rate = 1.0e-1f;
19-
Annotated<float, "decay_rate", Visible> decay_rate = 1.0e-2f;
20-
Annotated<float, "reference", Visible> ref = 1.0f;
21-
Annotated<float, "gain", Visible> gain = 1.0f;
22-
Annotated<float, "max_gain", Visible> gmax = 0.0f; // 0 ⇒ unlimited
26+
GR_MAKE_REFLECTABLE(Agc2, in, out, attack_rate, decay_rate, reference, max_gain);
27+
28+
static constexpr bool supports_selftest = true;
29+
30+
void start() { _gain = 1.0f; }
31+
32+
work::Status processOne(const T& x, T& y)
33+
{
34+
if (!std::isfinite(_gain)) _gain = 1.0f;
2335

24-
GR_MAKE_REFLECTABLE(Agc2, in, out,
25-
attack_rate, decay_rate,
26-
ref, gain, gmax);
36+
y = static_cast<T>(x * _gain);
37+
const float amp = amplitude(y);
38+
39+
const float err = reference - amp; // positive if too quiet
40+
const float rate = (err < 0.0f) ? float(attack_rate) // too loud → attack
41+
: float(decay_rate); // too quiet → decay
42+
43+
_gain += rate * err;
44+
45+
/* clamp gain */
46+
if (max_gain > 0.0f && _gain > max_gain) _gain = max_gain;
47+
if (_gain < 1.0e-5f) _gain = 1.0e-5f;
48+
49+
return work::Status::OK;
50+
}
2751

2852
template<InputSpanLike InSpan, OutputSpanLike OutSpan>
2953
work::Status processBulk(const InSpan& xs, OutSpan& ys)
3054
{
31-
const std::size_t N = std::min(xs.size(), ys.size());
32-
float g = gain;
33-
const float R = ref,
34-
A = attack_rate,
35-
D = decay_rate,
36-
M = gmax;
37-
38-
for (std::size_t i = 0; i < N; ++i) {
39-
const auto x = xs[i];
40-
const auto y = static_cast<T>(x * g); // apply current gain
41-
42-
const float amp = IsFloat ? std::fabs(y) : std::abs(y);
43-
const float rate = (std::fabs(amp - R) > g) ? A : D; // attack vs decay
44-
g -= (amp - R) * rate;
45-
46-
if (g < 1.0e-5f) g = 1.0e-5f; // avoid blow‑ups
47-
if (M > 0.f && g > M) g = M;
48-
49-
ys[i] = y;
50-
}
51-
gain = g;
52-
ys.publish(N);
55+
const std::size_t n = std::min(xs.size(), ys.size());
56+
for (std::size_t i = 0; i < n; ++i) processOne(xs[i], ys[i]);
57+
ys.publish(n);
5358
return work::Status::OK;
5459
}
60+
61+
explicit Agc2(property_map) {}
62+
63+
private:
64+
static inline float amplitude(float v) { return std::fabs(v); }
65+
static inline float amplitude(const std::complex<float>& v) { return std::abs(v); }
66+
67+
float _gain {1.0f};
5568
};
5669

57-
using Agc2CC = Agc2<std::complex<float>, false>;
58-
using Agc2FF = Agc2<float, true>;
70+
using Agc2FF = Agc2<float>;
71+
using Agc2CC = Agc2<std::complex<float>>;
5972

60-
GR_REGISTER_BLOCK("gr::blocks::analog::Agc2CC", gr::blocks::analog::Agc2CC)
61-
GR_REGISTER_BLOCK("gr::blocks::analog::Agc2FF", gr::blocks::analog::Agc2FF)
73+
GR_REGISTER_BLOCK("gr::blocks::analog::Agc2FF", Agc2FF)
74+
GR_REGISTER_BLOCK("gr::blocks::analog::Agc2CC", Agc2CC)
6275

6376
} // namespace gr::blocks::analog
64-
#endif /* INCLUDED_ANALOG_AGC2_HPP */
77+
#endif // GR_BLOCKS_ANALOG_AGC2_HPP_
Lines changed: 32 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
#ifndef INCLUDED_ANALOG_AM_DEMOD_HPP
2-
#define INCLUDED_ANALOG_AM_DEMOD_HPP
1+
#ifndef GR_BLOCKS_ANALOG_AM_DEMOD_HPP_
2+
#define GR_BLOCKS_ANALOG_AM_DEMOD_HPP_
33

44
#include <cmath>
55
#include <complex>
@@ -8,79 +8,56 @@
88
#include <gnuradio-4.0/Block.hpp>
99
#include <gnuradio-4.0/BlockRegistry.hpp>
1010

11-
namespace gr::blocks::analog
12-
{
13-
struct AmDemod : gr::Block<AmDemod> // fixed‑rate block (1→1)
14-
{
15-
using Description = Doc<
16-
R""(@brief Envelope AM demodulator (complex → float))"">;
11+
namespace gr::blocks::analog {
1712

13+
struct AmDemod : Block<AmDemod>
14+
{
1815
PortIn<std::complex<float>> in;
1916
PortOut<float> out;
2017

21-
Annotated<float, "chan_rate", Doc<"complex sample‑rate [Hz]">>
22-
chan_rate{48'000.f};
23-
Annotated<int, "audio_decim",Doc<"legacy decimation factor ≥ 1">>
24-
audio_decim{8};
25-
Annotated<float, "audio_pass", Doc<"audio LPF corner [Hz]">>
26-
audio_pass{4'000.f};
27-
Annotated<float, "audio_stop", Doc<"stop‑band edge (kept for API parity)">>
28-
audio_stop{5'500.f};
18+
Annotated<float, "chan_rate", Doc<"complex sample-rate [Hz]">>
19+
chan_rate { 48'000.f };
2920

30-
GR_MAKE_REFLECTABLE(
31-
AmDemod, in, out, chan_rate, audio_decim, audio_pass, audio_stop);
21+
Annotated<float, "audio_pass", Doc<"audio LPF corner [Hz]">>
22+
audio_pass { 4'000.f };
3223

33-
void set_chan_rate (float fs) { chan_rate = fs; _recalc(); }
34-
void set_audio_decim(int d ) { audio_decim = std::max(1, d); _recalc(); }
35-
void set_audio_pass (float fp){ audio_pass = fp; _recalc(); }
24+
GR_MAKE_REFLECTABLE(AmDemod, in, out, chan_rate, audio_pass);
3625

37-
explicit AmDemod(property_map) { _recalc(); }
38-
AmDemod(float fs, int d, float fp, float fsb = 0.f)
39-
: chan_rate(fs), audio_decim(std::max(1, d)),
40-
audio_pass(fp), audio_stop(fsb)
41-
{ _recalc(); }
26+
explicit AmDemod(property_map) {}
4227

43-
void settingsChanged(const property_map&, const property_map&)
44-
{ _recalc(); }
28+
work::Status processOne(const std::complex<float>& x, float& y)
29+
{
30+
const float env = std::abs(x);
31+
const float alpha =
32+
std::exp(-2.f * std::numbers::pi_v<float> * audio_pass / chan_rate);
33+
_y = env + alpha * (_y - env);
34+
y = _y;
35+
return work::Status::OK;
36+
}
4537

4638
template<InputSpanLike InSpan,
4739
OutputSpanLike OutSpan>
48-
[[nodiscard]] work::Status
49-
processBulk(const InSpan& xs, OutSpan& ys)
40+
work::Status processBulk(const InSpan& xs, OutSpan& ys)
5041
{
51-
std::size_t produced = 0;
52-
53-
for (auto x : xs) {
54-
const float env = std::abs(x);
55-
_y = env + _alpha * (_y - env);
56-
57-
if (produced == ys.size()) {
58-
ys.publish(produced);
59-
return work::Status::INSUFFICIENT_OUTPUT_ITEMS;
60-
}
61-
62-
ys[produced++] = _y; // 1 : 1 output
42+
const std::size_t n = std::min(xs.size(), ys.size());
43+
const float alpha =
44+
std::exp(-2.f * std::numbers::pi_v<float> * audio_pass / chan_rate);
45+
46+
for (std::size_t i = 0; i < n; ++i) {
47+
const float env = std::abs(xs[i]);
48+
_y = env + alpha * (_y - env);
49+
ys[i] = _y;
6350
}
64-
65-
ys.publish(produced);
51+
ys.publish(n);
6652
return work::Status::OK;
6753
}
6854

6955
private:
70-
float _alpha{1.f}; // IIR coefficient
71-
float _y{0.f}; // filter state
72-
73-
void _recalc()
74-
{
75-
/* one‑pole IIR coefficient (designed at the INPUT rate) */
76-
const float dt = 1.0f / chan_rate;
77-
_alpha = std::exp(-2.f * std::numbers::pi_v<float>
78-
* audio_pass * dt);
79-
}
56+
float _y { 0.f }; /* filter state */
8057
};
8158

8259
GR_REGISTER_BLOCK("gr::blocks::analog::AmDemod",
8360
gr::blocks::analog::AmDemod)
8461

8562
} // namespace gr::blocks::analog
86-
#endif /* INCLUDED_ANALOG_AM_DEMOD_HPP */
63+
#endif /* GR_BLOCKS_ANALOG_AM_DEMOD_HPP_ */

0 commit comments

Comments
 (0)