X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=vu_common.cpp;h=171f50d26a244059abcb1cab0cbd056565f6dbcc;hb=ffd68fbfb90242069af957f2a28908f0559f8348;hp=9fc3553eeb2fc907f21a1f7feea158b3c4236b20;hpb=fdd77c1f8a51f1e87151b876c2c5ab536a2fb02d;p=nageru diff --git a/vu_common.cpp b/vu_common.cpp index 9fc3553..171f50d 100644 --- a/vu_common.cpp +++ b/vu_common.cpp @@ -1,49 +1,73 @@ #include "vu_common.h" -#include + +#include +#include #include +#include using namespace std; -int lufs_to_pos(float level_lu, int height) -{ - const float min_level = 9.0f; // y=0 is top of screen, so “min” is the loudest level. - const float max_level = -18.0f; +double lufs_to_pos(float level_lu, int height, float min_level, float max_level) +{ + // Note: “max” is the loudest level, but y=0 is top of screen. // Handle -inf. - if (level_lu < max_level) { + if (level_lu < min_level) { return height - 1; } - int y = lrintf(height * (level_lu - min_level) / (max_level - min_level)); - y = std::max(y, 0); - y = std::min(y, height - 1); + double y = height * (level_lu - max_level) / (min_level - max_level); + y = max(y, 0); + y = min(y, height - 1); + + // If we are big enough, snap to pixel grid instead of antialiasing + // the edges; the unevenness will be less noticeable than the blurriness. + double height_per_level = height / (max_level - min_level) - 2.0; + if (height_per_level >= 10.0) { + y = round(y); + } + return y; } -void draw_vu_meter(QPainter &painter, float range_low_lu, float range_high_lu, int width, int height, int margin) +void draw_vu_meter(QPainter &painter, int width, int height, int horizontal_margin, double segment_margin, bool is_on, float min_level, float max_level, bool flip, int y_offset) { - painter.fillRect(margin, 0, width - 2 * margin, height, Qt::black); - - // TODO: QLinearGradient is not gamma-correct; we might want to correct for that. - QLinearGradient on(0, 0, 0, height); - on.setColorAt(0.0f, QColor(255, 0, 0)); - on.setColorAt(0.5f, QColor(255, 255, 0)); - on.setColorAt(1.0f, QColor(0, 255, 0)); - QColor off(80, 80, 80); - - int min_on_y = lufs_to_pos(range_high_lu, height); - int max_on_y = lufs_to_pos(range_low_lu, height); - - // Draw bars colored up until the level, then gray from there. - for (int level = -18; level < 9; ++level) { - int min_y = lufs_to_pos(level + 1.0f, height) + 1; - int max_y = lufs_to_pos(level, height) - 1; - - painter.fillRect(margin, min_y, width - 2 * margin, max_y - min_y, off); - int min_draw_y = std::max(min_y, min_on_y); - int max_draw_y = std::min(max_y, max_on_y); - if (min_draw_y < max_draw_y) { - painter.fillRect(margin, min_draw_y, width - 2 * margin, max_draw_y - min_draw_y, on); + painter.fillRect(horizontal_margin, y_offset, width - 2 * horizontal_margin, height, Qt::black); + + for (int y = 0; y < height; ++y) { + // Find coverage of “on” rectangles in this pixel row. + double coverage = 0.0; + for (int level = floor(min_level); level <= ceil(max_level); ++level) { + double min_y = lufs_to_pos(level + 1.0, height, min_level, max_level) + segment_margin * 0.5; + double max_y = lufs_to_pos(level, height, min_level, max_level) - segment_margin * 0.5; + min_y = std::max(min_y, y); + min_y = std::min(min_y, y + 1); + max_y = std::max(max_y, y); + max_y = std::min(max_y, y + 1); + coverage += max_y - min_y; + } + + double on_r, on_g, on_b; + if (is_on) { + double t = double(y) / height; + if (t <= 0.5) { + on_r = 1.0; + on_g = 2.0 * t; + on_b = 0.0; + } else { + on_r = 1.0 - 2.0 * (t - 0.5); + on_g = 1.0; + on_b = 0.0; + } + } else { + on_r = on_g = on_b = 0.05; } + + // Correct for coverage and do a simple gamma correction. + int r = lrintf(255 * pow(on_r * coverage, 1.0 / 2.2)); + int g = lrintf(255 * pow(on_g * coverage, 1.0 / 2.2)); + int b = lrintf(255 * pow(on_b * coverage, 1.0 / 2.2)); + int draw_y = flip ? (height - y - 1) : y; + painter.fillRect(horizontal_margin, draw_y + y_offset, width - 2 * horizontal_margin, 1, QColor(r, g, b)); } }