|
6 | 6 | #include <misc/freetype/imgui_freetype.h> |
7 | 7 | #include <imgui_internal.h> |
8 | 8 |
|
| 9 | +#include <algorithm> |
| 10 | + |
9 | 11 | #include "../fonts/dejavu_sans.h" |
10 | 12 | #include "../fonts/eb_garamond.h" |
11 | 13 | #include "../fonts/icons.h" |
@@ -647,6 +649,84 @@ void render::loader(const gfx::Rect& rect, const gfx::Color& color) { |
647 | 649 | text(rect.center(), color, "loading...", fonts::dejavu, FONT_CENTERED_X | FONT_CENTERED_Y); |
648 | 650 | } |
649 | 651 |
|
| 652 | +void render::waveform( |
| 653 | + const gfx::Rect& rect, |
| 654 | + const gfx::Rect& active_rect, |
| 655 | + const gfx::Color& color, |
| 656 | + const std::vector<int16_t>& samples, |
| 657 | + bool filled |
| 658 | +) { |
| 659 | + if (samples.empty()) |
| 660 | + return; |
| 661 | + |
| 662 | + const size_t width = rect.w; |
| 663 | + const size_t height = rect.h; |
| 664 | + |
| 665 | + // Find max amplitude to normalize waveform |
| 666 | + int16_t max_sample = 1; |
| 667 | + for (auto s : samples) { |
| 668 | + max_sample = std::max<int>(std::abs(s), max_sample); |
| 669 | + } |
| 670 | + |
| 671 | + for (size_t x = 0; x < width; ++x) { |
| 672 | + // Map pixel x to corresponding sample index |
| 673 | + float sample_pos = static_cast<float>(x) / width * samples.size(); |
| 674 | + auto idx_start = static_cast<size_t>(sample_pos); |
| 675 | + size_t idx_end = |
| 676 | + std::min(static_cast<size_t>(sample_pos + (samples.size() / static_cast<float>(width))), samples.size()); |
| 677 | + |
| 678 | + // Take max amplitude in this range |
| 679 | + int16_t max_amp = 0; |
| 680 | + for (size_t i = idx_start; i < idx_end; ++i) { |
| 681 | + max_amp = std::max(max_amp, static_cast<int16_t>(std::abs(samples[i]))); |
| 682 | + } |
| 683 | + |
| 684 | + // Normalize to [0,1] |
| 685 | + float norm = static_cast<float>(max_amp) / max_sample; |
| 686 | + int y_center = rect.y + (height / 2); |
| 687 | + int y_half = static_cast<int>(norm * (height / 2.f)); |
| 688 | + |
| 689 | + auto x_color = color; |
| 690 | + if (!active_rect.contains(gfx::Point(rect.x + x, y_center))) |
| 691 | + x_color = color.adjust_alpha(0.5f); |
| 692 | + |
| 693 | + if (filled) { |
| 694 | + gfx::Rect r{ static_cast<int>(rect.x + x), y_center - y_half, 1, y_half * 2 }; |
| 695 | + rect_filled(r, x_color); |
| 696 | + } |
| 697 | + else { |
| 698 | + gfx::Point p1{ static_cast<int>(rect.x + x), y_center - y_half }; |
| 699 | + gfx::Point p2{ static_cast<int>(rect.x + x), y_center + y_half }; |
| 700 | + line(p1, p2, x_color, true); |
| 701 | + } |
| 702 | + } |
| 703 | +} |
| 704 | + |
| 705 | +void render::rect_side(const gfx::Rect& rect, const gfx::Color& color, RectSide side, int thickness) { |
| 706 | + switch (side) { |
| 707 | + case RectSide::LEFT: { |
| 708 | + // Top horizontal |
| 709 | + rect_filled(gfx::Rect{ rect.x, rect.y - thickness, rect.w, thickness }, color); |
| 710 | + // Vertical |
| 711 | + rect_filled( |
| 712 | + gfx::Rect{ rect.x - thickness, rect.y - thickness, thickness, rect.h + (thickness * 2) }, color |
| 713 | + ); |
| 714 | + // Bottom horizontal |
| 715 | + rect_filled(gfx::Rect{ rect.x, rect.y + rect.h, rect.w, thickness }, color); |
| 716 | + break; |
| 717 | + } |
| 718 | + case RectSide::RIGHT: { |
| 719 | + // Top horizontal |
| 720 | + rect_filled(gfx::Rect{ rect.x, rect.y - thickness, rect.w, thickness }, color); |
| 721 | + // Vertical |
| 722 | + rect_filled(gfx::Rect{ rect.x + rect.w, rect.y - thickness, thickness, rect.h + (thickness * 2) }, color); |
| 723 | + // Bottom horizontal |
| 724 | + rect_filled(gfx::Rect{ rect.x, rect.y + rect.h, rect.w, thickness }, color); |
| 725 | + break; |
| 726 | + } |
| 727 | + } |
| 728 | +} |
| 729 | + |
650 | 730 | void render::push_clip_rect(const gfx::Rect& rect, bool intersect_clip_rect) { |
651 | 731 | imgui.drawlist->PushClipRect(rect.origin(), rect.max(), intersect_clip_rect); |
652 | 732 | } |
|
0 commit comments