From 18527e4056a5198e84b81cd3260750a39db05def Mon Sep 17 00:00:00 2001 From: Mads Kiilerich Date: Thu, 6 Oct 2022 22:57:07 +0200 Subject: [PATCH] Use nice units for labels in the export spectogram Before, the X-axis labels would be placed in nice even positions, but with whatever odd time corresponded to that. That made it harder than necessary to read the graph and approximate when things happened. Instead, round the interval down to nearest power of ten ... and if suitable, scale that up with a factor of 2 or 5. That will (with the necessary handling of how seconds/minutes/hours relate) make sure that the time labels are nice with a minimal amount of non-zero digits. That makes it easy to do math and interpolate when reading the graph. The number of labels will change between something like 4 and 10 - before it was always something like 7. That is fine, as long as it helps the readability. The total length is no longer the right-most label, but the length can be found in the top-right corner. --- gtk2_ardour/export_analysis_graphs.cc | 52 ++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/gtk2_ardour/export_analysis_graphs.cc b/gtk2_ardour/export_analysis_graphs.cc index 8b40cd7219..501e096ab0 100644 --- a/gtk2_ardour/export_analysis_graphs.cc +++ b/gtk2_ardour/export_analysis_graphs.cc @@ -17,6 +17,7 @@ */ #include +#include #include "ardour/dB.h" #include "ardour/logmeter.h" @@ -468,6 +469,47 @@ ArdourGraphs::loudness_histogram (Glib::RefPtr pctx, ARDOUR::Exp return hist; } +static samplecnt_t +optimal_time_stride(samplecnt_t length, samplecnt_t sample_rate, int width, int label_width) { + // Compute the time interval between labels on the X-axis when it has + // to span `length` and each label takes up `label_width` of the total + // `width`. + + // Compute the minimum amount of labels to show, considering rounding + // to a nice stride might give 2.5 time as many labels. In that case, + // we might need 25% more than the label width to avoid overlap between + // the left-aligned 0:00 and the first real label. + // min_labels is usually 4, leaving room for up to 10 labels. + const int min_labels = width / (label_width * 1.25 * 2.5); + const float max_stride_s = (float)length / sample_rate / min_labels; + + // default units: count 1, 2, or 5 times 10**n + float unit = 1; + // if stride is more than an hour, count in hours instead of 2000s and 5000s etc + if (max_stride_s >= 60 * 60) { + unit = 60 * 60; + } else + // stride 20min is ok, but count in half hours if possible - this avoids how stride 50min adds up above an hour + if (max_stride_s >= 30 * 60) { + unit = 30 * 60; + } else + // if stride is more than a minute, count in minutes instead of 100s + if (max_stride_s >= 60) { + unit = 60; + } else + // stride 20s is ok, but count in half minutes if possible - this avoids how stride 50s adds up above a minute + if (max_stride_s >= 30) { + unit = 30; + } + + const samplecnt_t unit_stride = std::pow(10.f, std::floor(std::log10(max_stride_s / unit))) * unit * sample_rate; + const int unit_label_count = length / unit_stride; + return unit_stride * ( + unit_label_count > 5 * min_labels ? 5 : + unit_label_count > 2 * min_labels ? 2 : + 1); +} + Cairo::RefPtr ArdourGraphs::time_axis (Glib::RefPtr pctx, int width, int m_l, samplepos_t start, samplecnt_t length, samplecnt_t sample_rate) { @@ -479,7 +521,6 @@ ArdourGraphs::time_axis (Glib::RefPtr pctx, int width, int m_l, layout->get_pixel_size (w, h); int height = h * 1.75; - int n_labels = width / (w * 1.75); Cairo::RefPtr ytme = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, m_l + width, height); Cairo::RefPtr cr = Cairo::Context::create (ytme); @@ -494,12 +535,13 @@ ArdourGraphs::time_axis (Glib::RefPtr pctx, int width, int m_l, cr->set_line_width (1.0); - for (int i = 0; i <= n_labels; ++i) { - const float fract = (float)i / n_labels; - const int label_pos = width * fract; + const samplecnt_t label_time_stride = optimal_time_stride(length, sample_rate, width, w); + + for (samplecnt_t label_time = 0; label_time <= length; label_time += label_time_stride) { + const int label_pos = width * (float)label_time / length; char buf[16]; - AudioClock::print_minsec (start + length * fract, buf, sizeof (buf), sample_rate, 1); + AudioClock::print_minsec (start + label_time, buf, sizeof (buf), sample_rate, 1); layout->set_text (&buf[1]); layout->get_pixel_size (w, h);