double WaveView::_global_gradient_depth = 0.6;
bool WaveView::_global_logscaled = false;
WaveView::Shape WaveView::_global_shape = WaveView::Normal;
+bool WaveView::_global_show_waveform_clipping = true;
PBD::Signal0<void> WaveView::VisualPropertiesChanged;
struct LineTips {
double top;
double bot;
- bool clipped;
+ bool clip_max;
+ bool clip_min;
- LineTips() : top (0.0), bot (0.0), clipped (false) {}
+ LineTips() : top (0.0), bot (0.0), clip_max (false), clip_min (false) {}
};
void
boost::scoped_array<LineTips> tips (new LineTips[n_peaks]);
+ /* Clip level nominally set to -0.9dBFS to account for inter-sample
+ interpolation possibly clipping (value may be too low).
+
+ We adjust by the region's own gain (but note: not by any gain
+ automation or its gain envelope) so that clip indicators are closer
+ to providing data about on-disk data. This multiplication is
+ needed because the data we get from AudioRegion::read_peaks()
+ has been scaled by scale_amplitude() already.
+ */
+
+ const double clip_level = 0.98853 * _region->scale_amplitude();
+
if (_shape == WaveView::Rectified) {
/* each peak is a line from the bottom of the waveview
for (int i = 0; i < n_peaks; ++i) {
tips[i].bot = height();
tips[i].top = position (alt_log_meter (fast_coefficient_to_dB (max (fabs (_peaks[i].max), fabs (_peaks[i].min)))));
+
+ if (fabs (_peaks[i].max) >= clip_level) {
+ tips[i].clip_max = true;
+ }
+
+ if (fabs (_peaks[i].min) >= clip_level) {
+ tips[i].clip_min = true;
+ }
}
} else {for (int i = 0; i < n_peaks; ++i) {
tips[i].bot = height();
tips[i].top = position (max (fabs (_peaks[i].max), fabs (_peaks[i].min)));
+
+ if (fabs (_peaks[i].max) >= clip_level) {
+ tips[i].clip_max = true;
+ }
+
+ if (fabs (_peaks[i].min) >= clip_level) {
+ tips[i].clip_min = true;
+ }
}
}
Coord top = _peaks[i].min;
Coord bot = _peaks[i].max;
+ if (fabs (top) >= clip_level) {
+ tips[i].clip_max = true;
+ }
+
+ if (fabs (bot) >= clip_level) {
+ tips[i].clip_min = true;
+ }
+
if (top > 0.0) {
top = position (alt_log_meter (fast_coefficient_to_dB (top)));
} else if (top < 0.0) {
tips[i].top = top;
tips[i].bot = bot;
-
}
} else {
for (int i = 0; i < n_peaks; ++i) {
+
+ if (fabs (_peaks[i].max) >= clip_level) {
+ tips[i].clip_max = true;
+ }
+
+ if (fabs (_peaks[i].min) >= clip_level) {
+ tips[i].clip_min = true;
+ }
+
tips[i].top = position (_peaks[i].min);
tips[i].bot = position (_peaks[i].max);
+
+
}
}
}
* modelled on pyramix, except that we add clipping indicators.
*/
- context->set_source_rgba (0, 0, 0, 1.0);
+ if (_global_show_waveform_clipping) {
+
+ context->set_source_rgba (0, 0, 0, 1.0);
+
+ /* the height of the clip-indicator should be at most 7 pixels,
+ or 5% of the height of the waveview item.
+ */
+ const double clip_height = min (7.0, ceil (_height * 0.05));
+
+ for (int i = 0; i < n_peaks; ++i) {
+ context->move_to (i, tips[i].top);
+
+ bool show_top_clip = (_shape == WaveView::Rectified && (tips[i].clip_max || tips[i].clip_min)) ||
+ tips[i].clip_max;
+
+ if (show_top_clip) {
+ context->set_source_rgba (1.0, 0, 0, 1.0);
+ context->rel_line_to (0, clip_height);
+ context->stroke ();
+ context->set_source_rgba (0.0, 0, 0, 1.0);
+ } else {
+ context->rel_line_to (0, 1.0);
+ context->stroke ();
+ }
- for (int i = 0; i < n_peaks; ++i) {
- context->move_to (i, tips[i].top);
- context->rel_line_to (0, 1.0);
- context->stroke ();
- if (_shape != WaveView::Rectified) {
- context->move_to (i, tips[i].bot);
- context->rel_line_to (0, -1.0);
- context->stroke ();
+ if (_shape != WaveView::Rectified) {
+ context->move_to (i, tips[i].bot);
+ if (tips[i].clip_min) {
+ context->set_source_rgba (1.0, 0, 0, 1.0);
+ context->rel_line_to (0, -clip_height);
+ context->stroke ();
+ context->set_source_rgba (0.0, 0, 0, 1.0);
+ } else {
+ context->rel_line_to (0, -1.0);
+ context->stroke ();
+ }
+ }
}
}
can ensure correct single pixel behaviour.
*/
+ Coord pos;
+
switch (_shape) {
case Rectified:
- return floor (_height - (s * _height));
+ pos = floor (_height - (s * _height));
default:
+ pos = floor ((1.0-s) * (_height / 2.0));
break;
}
- return floor ((1.0-s) * (_height / 2.0));
+
+ return min (_height, (max (0.0, pos)));
}
void
VisualPropertiesChanged (); /* EMIT SIGNAL */
}
}
+
+void
+WaveView::set_global_show_waveform_clipping (bool yn)
+{
+ if (_global_show_waveform_clipping != yn) {
+ _global_show_waveform_clipping = yn;
+ VisualPropertiesChanged (); /* EMIT SIGNAL */
+ }
+}