+float
+AudioPlot::y_for_linear (float p, Metrics const & metrics) const
+{
+ if (p < 1e-4) {
+ p = 1e-4;
+ }
+
+ return metrics.height - (20 * log10(p) - _minimum) * metrics.y_scale - metrics.y_origin;
+}
+
+void
+AudioPlot::plot_peak (wxGraphicsPath& path, int channel, Metrics const & metrics) const
+{
+ if (_analysis->points (channel) == 0) {
+ return;
+ }
+
+ _peak[channel] = PointList ();
+
+ float peak = 0;
+ int const N = _analysis->points(channel);
+ for (int i = 0; i < N; ++i) {
+ float const p = get_point(channel, i)[AudioPoint::PEAK];
+ peak -= 0.01f * (1 - log10 (_smoothing) / log10 (max_smoothing));
+ if (p > peak) {
+ peak = p;
+ } else if (peak < 0) {
+ peak = 0;
+ }
+
+ _peak[channel].push_back (
+ Point (
+ wxPoint (metrics.db_label_width + i * metrics.x_scale, y_for_linear (peak, metrics)),
+ DCPTime::from_frames (i * _analysis->samples_per_point(), _analysis->sample_rate()),
+ 20 * log10(peak)
+ )
+ );
+ }
+
+ DCPOMATIC_ASSERT (_peak.find(channel) != _peak.end());
+
+ path.MoveToPoint (_peak[channel][0].draw);
+ BOOST_FOREACH (Point const & i, _peak[channel]) {
+ path.AddLineToPoint (i.draw);
+ }
+}
+
+void
+AudioPlot::plot_rms (wxGraphicsPath& path, int channel, Metrics const & metrics) const
+{
+ if (_analysis->points (channel) == 0) {
+ return;
+ }
+
+ _rms[channel] = PointList();
+
+ list<float> smoothing;
+
+ int const N = _analysis->points(channel);
+
+ float const first = get_point(channel, 0)[AudioPoint::RMS];
+ float const last = get_point(channel, N - 1)[AudioPoint::RMS];
+
+ int const before = _smoothing / 2;
+ int const after = _smoothing - before;
+
+ /* Pre-load the smoothing list */
+ for (int i = 0; i < before; ++i) {
+ smoothing.push_back (first);
+ }
+ for (int i = 0; i < after; ++i) {
+ if (i < N) {
+ smoothing.push_back (get_point(channel, i)[AudioPoint::RMS]);
+ } else {
+ smoothing.push_back (last);
+ }
+ }
+
+ for (int i = 0; i < N; ++i) {
+
+ int const next_for_window = i + after;
+
+ if (next_for_window < N) {
+ smoothing.push_back (get_point(channel, i)[AudioPoint::RMS]);
+ } else {
+ smoothing.push_back (last);
+ }
+
+ smoothing.pop_front ();
+
+ float p = 0;
+ for (list<float>::const_iterator j = smoothing.begin(); j != smoothing.end(); ++j) {
+ p += pow (*j, 2);
+ }
+
+ if (!smoothing.empty ()) {
+ p = sqrt (p / smoothing.size ());
+ }
+
+ _rms[channel].push_back (
+ Point (
+ wxPoint (metrics.db_label_width + i * metrics.x_scale, y_for_linear (p, metrics)),
+ DCPTime::from_frames (i * _analysis->samples_per_point(), _analysis->sample_rate()),
+ 20 * log10(p)
+ )
+ );
+ }
+
+ DCPOMATIC_ASSERT (_rms.find(channel) != _rms.end());
+
+ path.MoveToPoint (_rms[channel][0].draw);
+ BOOST_FOREACH (Point const & i, _rms[channel]) {
+ path.AddLineToPoint (i.draw);
+ }
+}
+
+void
+AudioPlot::set_smoothing (int s)
+{
+ _smoothing = s;
+ _rms.clear ();
+ _peak.clear ();
+ Refresh ();
+}
+
+void
+AudioPlot::set_gain_correction (double gain)
+{
+ _gain_correction = gain;
+ Refresh ();
+}
+
+AudioPoint
+AudioPlot::get_point (int channel, int point) const
+{
+ AudioPoint p = _analysis->get_point (channel, point);
+ for (int i = 0; i < AudioPoint::COUNT; ++i) {
+ p[i] *= pow (10, _gain_correction / 20);
+ }
+
+ return p;
+}
+
+/** @param n Channel index.
+ * @return Colour used by that channel in the plot.
+ */
+wxColour
+AudioPlot::colour (int n) const
+{
+ DCPOMATIC_ASSERT (n < int(_colours.size()));
+ return _colours[n];
+}
+
+void
+AudioPlot::search (map<int, PointList> const & search, wxMouseEvent const & ev, double& min_dist, Point& min_point) const
+{
+ for (map<int, PointList>::const_iterator i = search.begin(); i != search.end(); ++i) {
+ BOOST_FOREACH (Point const & j, i->second) {
+ double const dist = pow(ev.GetX() - j.draw.x, 2) + pow(ev.GetY() - j.draw.y, 2);
+ if (dist < min_dist) {
+ min_dist = dist;
+ min_point = j;
+ }
+ }
+ }
+}
+
+void
+AudioPlot::mouse_moved (wxMouseEvent& ev)
+{
+ double min_dist = DBL_MAX;
+ Point min_point;
+
+ search (_rms, ev, min_dist, min_point);
+ search (_peak, ev, min_dist, min_point);
+
+ _cursor = optional<Point> ();
+
+ if (min_dist < DBL_MAX) {
+ wxRect before (min_point.draw.x - _cursor_size / 2, min_point.draw.y - _cursor_size / 2, _cursor_size, _cursor_size);
+ GetParent()->Refresh (true, &before);
+ _cursor = min_point;
+ wxRect after (min_point.draw.x - _cursor_size / 2, min_point.draw.y - _cursor_size / 2, _cursor_size, _cursor_size);
+ GetParent()->Refresh (true, &after);
+ Cursor (min_point.time, min_point.db);
+ }
+}
+