An actual tempo line cache (not quite perfect when scrolling left, but miles ahead...
[ardour.git] / gtk2_ardour / tempo_lines.cc
1 /*
2     Copyright (C) 2002-2007 Paul Davis 
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <libgnomecanvasmm/canvas.h>
21 #include <libgnomecanvasmm/group.h>
22 #include "tempo_lines.h"
23 #include "ardour_ui.h"
24
25 using namespace std;
26
27 #define MAX_CACHED_LINES 512
28         
29 TempoLines::TempoLines(ArdourCanvas::Canvas& canvas, ArdourCanvas::Group* group)
30         : _canvas(canvas)
31         , _group(group)
32         , _clean_left(DBL_MAX)
33         , _clean_right(0.0)
34 {
35 }
36
37 void
38 TempoLines::tempo_map_changed()
39 {
40         _clean_left = DBL_MAX;
41         _clean_right = 0.0;
42
43         // TODO: Dirty/slow, but 'needed' for zoom :(
44         for (Lines::iterator i = _lines.begin(); i != _lines.end(); ) {
45                 Lines::iterator next = i;
46                 ++next;
47         i->second->property_x1() = DBL_MAX;
48         i->second->property_x2() = DBL_MAX;
49                 _lines.erase(i);
50                 _lines.insert(make_pair(DBL_MAX, i->second));
51                 i = next;
52         }
53 }
54
55 void
56 TempoLines::show ()
57 {
58         for (Lines::iterator i = _lines.begin(); i != _lines.end(); ++i) {
59         i->second->show();
60         }
61 }
62
63 void
64 TempoLines::hide ()
65 {
66         for (Lines::iterator i = _lines.begin(); i != _lines.end(); ++i) {
67         i->second->hide();
68         }
69 }
70
71 void
72 TempoLines::draw (ARDOUR::TempoMap::BBTPointList& points, double frames_per_unit)
73 {
74         ARDOUR::TempoMap::BBTPointList::iterator i;
75         ArdourCanvas::SimpleLine *line;
76         gdouble xpos;
77         double who_cares;
78         double x1, x2, y1, y2, beat_density;
79
80         uint32_t beats = 0;
81         uint32_t bars = 0;
82         uint32_t color;
83         
84         const size_t needed = points.size();
85
86         _canvas.get_scroll_region (x1, y1, x2, who_cares);
87         _canvas.root()->get_bounds(who_cares, who_cares, who_cares, y2);
88
89         /* get the first bar spacing */
90
91         i = points.end();
92         i--;
93         bars = (*i).bar - (*points.begin()).bar;
94         beats = points.size() - bars;
95
96         beat_density = (beats * 10.0f) / _canvas.get_width ();
97
98         if (beat_density > 4.0f) {
99                 /* if the lines are too close together, they become useless */
100                 return;
101         }
102
103         xpos = rint(((nframes64_t)(*i).frame) / (double)frames_per_unit);
104         const double needed_right = xpos;
105
106         i = points.begin();
107         
108         xpos = rint(((nframes64_t)(*i).frame) / (double)frames_per_unit);
109         const double needed_left = xpos;
110
111         Lines::iterator left = _lines.lower_bound(xpos); // first line >= xpos
112
113         bool exhausted = (left == _lines.end());
114         Lines::iterator li = left;
115         
116         // Tempo map hasn't changed and we're entirely within a clean
117         // range, don't need to do anything.  Yay.
118         if (needed_left >= _clean_left && needed_right <= _clean_right) {
119                 //cout << "LINE CACHE PERFECT HIT!" << endl;
120                 return;
121         }
122
123         //cout << "LINE CACHE MISS :/" << endl;
124
125         bool inserted_last_time = false;
126         bool invalidated = false;
127         
128         for (i = points.begin(); i != points.end(); ++i) {
129
130                 switch ((*i).type) {
131                 case ARDOUR::TempoMap::Bar:
132                         break;
133
134                 case ARDOUR::TempoMap::Beat:
135                         
136                         if ((*i).beat == 1) {
137                                 color = ARDOUR_UI::config()->canvasvar_MeasureLineBar.get();
138                         } else {
139                                 color = ARDOUR_UI::config()->canvasvar_MeasureLineBeat.get();
140
141                                 if (beat_density > 2.0) {
142                                         /* only draw beat lines if the gaps between beats are large. */
143                                         break;
144                                 }
145                         }
146                         
147                         xpos = rint(((nframes64_t)(*i).frame) / (double)frames_per_unit);
148
149                         if (inserted_last_time) {
150                                 li = _lines.lower_bound(xpos); // first line >= xpos
151                         }
152                         
153                         if (!exhausted) {
154                                 line = li->second;
155                                 exhausted = ((++li) == _lines.end());
156                                 inserted_last_time = false;
157                         } else if (_lines.size() < needed || _lines.size() < MAX_CACHED_LINES) {
158                                 line = new ArdourCanvas::SimpleLine (*_group);
159                                 line->property_x1() = xpos;
160                                 line->property_x2() = xpos;
161                                 line->property_y2() = y2;
162                                 line->property_color_rgba() = color;
163                                 _lines.insert(make_pair(xpos, line));
164                                 inserted_last_time = true;
165                         } else {
166                                 assert(li != _lines.begin());
167                                 line = _lines.begin()->second; // steal leftmost line
168                                 _lines.erase(_lines.begin());
169                                 _lines.insert(make_pair(xpos, line));
170                                 inserted_last_time = true;
171                                 invalidated = true;
172                         }
173
174                         /* At this point, line's key is correct, but actual pos may not be */
175                         if (line->property_x1() != xpos) {
176                                 // Turn this on to see the case where this isn't quite ideal yet
177                                 // (scrolling left, lots of lines are moved left when there is
178                                 // likely to be a huge chunk with equivalent coords)
179                                 //cout << "MOVE " << line->property_x1() << " -> " << xpos << endl;
180                                 double x1 = line->property_x1();
181                                 bool was_clean = x1 >= _clean_left && x1 <= _clean_right;
182                                 invalidated = invalidated || was_clean;
183                                 line->property_x1() = xpos;
184                                 line->property_x2() = xpos;
185                                 line->property_y2() = y2;
186                                 line->property_color_rgba() = color;
187                         }
188
189                         break;
190                 }
191         }
192         
193         if (invalidated) { // We messed things up, visible range is all we know is valid
194                 _clean_left  = needed_left;
195                 _clean_right = needed_right;
196         } else { // Extend range to what we've 'fixed'
197                 _clean_left  = min(_clean_left, needed_left);
198                 _clean_right = max(_clean_right, needed_right);
199         }
200 }
201