Skip to content

Commit e103e88

Browse files
authored
Add terminal visualization of sinc and tests
Using ftxui and a gtest parameterized test based on enum value (declaring a template container that depends on the enum value leveraging variant and friends)
1 parent 31e1502 commit e103e88

1 file changed

Lines changed: 177 additions & 0 deletions

File tree

custom/sinc.cpp

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
// g++ -std=c++17 -I${ftxui}/include -L${ftxui}/build minimal.cpp -lftxui-component -lftxui-dom -lftxui-screen -o minimal -I{googletest}/include -L{googletest}/lib -lgtest
2+
// https://stackoverflow.com/questions/78284124/configuring-a-project-with-ftxui-in-visual-studio-code
3+
4+
#include <cmath>
5+
#include <thread>
6+
#include <variant>
7+
#include <vector>
8+
9+
#include <ftxui/component/component.hpp>
10+
#include <ftxui/component/loop.hpp>
11+
#include <ftxui/component/screen_interactive.hpp>
12+
#include <ftxui/dom/elements.hpp>
13+
#include <ftxui/screen/color.hpp>
14+
15+
#include <gtest/gtest.h>
16+
17+
using namespace ftxui;
18+
19+
enum class WaveSampleFormatEnum { F32, F64, }; // ¯\_(ツ)_/¯
20+
using WaveSampleFormat = WaveSampleFormatEnum;
21+
using variant_vector = std::variant<std::vector<float>, std::vector<double>>;
22+
23+
// C++23's std::unreachable backport for C++17:
24+
#ifdef __GNUC__
25+
[[noreturn]] inline __attribute__((always_inline)) void unreachable()
26+
{ __builtin_unreachable(); }
27+
#elif defined(_MSC_VER)
28+
[[noreturn]] __forceinline void unreachable() { __assume(false); }
29+
#else
30+
[[noreturn]] inline void unreachable() {}
31+
#endif
32+
33+
auto make_samples_vector(WaveSampleFormat wsf) -> variant_vector {
34+
switch (wsf) {
35+
case WaveSampleFormat::F32:
36+
return std::vector<float>{};
37+
case WaveSampleFormat::F64:
38+
return std::vector<double>{};
39+
default:
40+
throw std::runtime_error("Unexpected sample format!");
41+
}
42+
unreachable();
43+
}
44+
45+
struct Wave {
46+
std::vector<std::byte> data;
47+
int wave_sample_size = 0;
48+
49+
Wave() {}
50+
51+
Wave(WaveSampleFormat wsf) {
52+
wave_sample_size = [wsf]() {
53+
switch (wsf) {
54+
case WaveSampleFormat::F32:
55+
return 4;
56+
case WaveSampleFormat::F64:
57+
return 8;
58+
}
59+
}();
60+
}
61+
62+
Wave(WaveSampleFormat wsf, const std::byte* bytes, int N) : Wave(wsf) {
63+
data = std::vector(bytes, bytes + N);
64+
}
65+
66+
auto data_size() const {
67+
return data.size() * wave_sample_size;
68+
}
69+
};
70+
71+
class WaveSamplesFormatTest :
72+
public ::testing::TestWithParam<WaveSampleFormat> {};
73+
74+
TEST_P(WaveSamplesFormatTest, DataSize) {
75+
auto samples = make_samples_vector(GetParam());
76+
const int N = 512;
77+
std::visit([](auto&& arg) {
78+
using T = typename std::decay_t<decltype(arg)>::value_type;
79+
arg.resize(N);
80+
for (int i = 0; i < N; i++) {
81+
arg[i] = std::sin(10 * 1.61803 * i / static_cast<T>(N));
82+
}
83+
Wave wave(GetParam(), reinterpret_cast<const std::byte*>(arg.data()), N);
84+
ASSERT_EQ(wave.data_size(), arg.size()*sizeof(T));
85+
}, samples);
86+
}
87+
88+
INSTANTIATE_TEST_SUITE_P(
89+
WaveSamplesFormatTestInstantiation,
90+
WaveSamplesFormatTest,
91+
::testing::Values(WaveSampleFormat::F32, WaveSampleFormat::F64)
92+
);
93+
94+
int main(int argc, char** argv) {
95+
for (int i = 1; i < argc; i++) {
96+
if (std::string(argv[i]) == "--run_tests") {
97+
::testing::InitGoogleTest(&argc, argv);
98+
return RUN_ALL_TESTS();
99+
}
100+
}
101+
102+
auto screen = ScreenInteractive::Fullscreen();
103+
104+
int iteration = 0;
105+
106+
Wave wave(WaveSampleFormat::F64);
107+
108+
// Periodic sinc pattern
109+
auto sinc = [&iteration, &wave](int width, int height) {
110+
std::vector<int> output(width);
111+
for (int i = 0; i < width; i++) {
112+
/*
113+
Scale in (i + iteration) instead of mapping to [-π, π] and work in a
114+
natural coordinate space where the sinc's side lobes are visible.
115+
116+
After wrapping with modulo, shift values in the second half of each
117+
period to the negative side, so each sinc is centered.
118+
119+
Parameters:
120+
- scale: lower values → wider sinc (fewer side lobes visible)
121+
higher values → narrower sinc (more side lobes)
122+
- period
123+
- amplitude
124+
*/
125+
126+
const float scale = 0.35f; // Adjust this to see more/fewer side lobes
127+
const float period = 40.0f; // Distance between peaks
128+
const float amplitude = 0.69f; // Height of the peaks
129+
130+
float t = ((i + iteration) * scale);
131+
132+
// Periodicity is created with the modulo operation
133+
t = std::fmod(t, period);
134+
135+
// Shift to center each period (fftshift?)
136+
if (t > period / 2.0f) t -= period;
137+
138+
const float sinc_value = std::abs(t) < 0.01f ? 1.0f : std::sin(t) / t;
139+
140+
float x = 0.1;
141+
x += amplitude * sinc_value;
142+
x *= height;
143+
144+
std::vector<std::byte>& bytes = wave.data;
145+
const auto current_size = bytes.size();
146+
bytes.resize(current_size + sizeof(x));
147+
std::memcpy(bytes.data() + current_size, &x, sizeof(x));
148+
output[i] = static_cast<int>(x);
149+
}
150+
return output;
151+
};
152+
153+
auto graph_renderer = Renderer([&sinc] {
154+
auto wave = hbox({graph(std::ref(sinc)) | color(Color::BlueLight)});
155+
156+
return hbox({
157+
wave | flex,
158+
}) |
159+
flex;
160+
});
161+
162+
Loop loop(&screen,
163+
Renderer(graph_renderer, [&] {
164+
return vbox({
165+
text("El Seno Cardinal") | bold | hcenter,
166+
graph_renderer->Render(),
167+
});
168+
}));
169+
170+
const int FPS = 60;
171+
while (!loop.HasQuitted()) {
172+
iteration++;
173+
screen.RequestAnimationFrame();
174+
loop.RunOnce();
175+
std::this_thread::sleep_for(std::chrono::milliseconds(1000 / FPS));
176+
}
177+
}

0 commit comments

Comments
 (0)