Little MidiEvent prettification additions, ifdef'd non-realtime aspects (for future...
[ardour.git] / gtk2_ardour / midi_streamview.cc
1 /*
2     Copyright (C) 2001-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 #include <cmath>
20 #include <cassert>
21 #include <utility>
22
23 #include <gtkmm.h>
24
25 #include <gtkmm2ext/gtk_ui.h>
26
27 #include <ardour/midi_playlist.h>
28 #include <ardour/midi_region.h>
29 #include <ardour/midi_source.h>
30 #include <ardour/midi_diskstream.h>
31 #include <ardour/midi_track.h>
32 #include <ardour/midi_events.h>
33 #include <ardour/smf_source.h>
34 #include <ardour/region_factory.h>
35
36 #include "midi_streamview.h"
37 #include "region_view.h"
38 #include "midi_region_view.h"
39 #include "midi_time_axis.h"
40 #include "canvas-simplerect.h"
41 #include "region_selection.h"
42 #include "selection.h"
43 #include "public_editor.h"
44 #include "ardour_ui.h"
45 #include "rgb_macros.h"
46 #include "gui_thread.h"
47 #include "utils.h"
48 #include "simplerect.h"
49
50 using namespace std;
51 using namespace ARDOUR;
52 using namespace PBD;
53 using namespace Editing;
54
55 MidiStreamView::MidiStreamView (MidiTimeAxisView& tv)
56         : StreamView (tv)
57         , _range(ContentsRange)
58         , _lowest_note(60)
59         , _highest_note(60)
60 {
61         if (tv.is_track())
62                 stream_base_color = ARDOUR_UI::config()->canvasvar_MidiTrackBase.get();
63         else
64                 stream_base_color = ARDOUR_UI::config()->canvasvar_MidiBusBase.get();
65         
66         canvas_rect->property_fill_color_rgba() = stream_base_color;
67         canvas_rect->property_outline_color_rgba() = RGBA_BLACK;
68
69         use_rec_regions = tv.editor.show_waveforms_recording ();
70 }
71
72 MidiStreamView::~MidiStreamView ()
73 {
74 }
75
76
77 RegionView*
78 MidiStreamView::add_region_view_internal (boost::shared_ptr<Region> r, bool wfd)
79 {
80         boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
81
82         if (region == 0) {
83                 return NULL;
84         }
85
86         MidiRegionView *region_view;
87         list<RegionView *>::iterator i;
88
89         for (i = region_views.begin(); i != region_views.end(); ++i) {
90                 if ((*i)->region() == r) {
91                         
92                         /* great. we already have a MidiRegionView for this Region. use it again. */
93
94                         (*i)->set_valid (true);
95                         (*i)->enable_display(wfd);
96                         display_region(dynamic_cast<MidiRegionView*>(*i), wfd);
97
98                         return NULL;
99                 }
100         }
101         
102         region_view = new MidiRegionView (canvas_group, _trackview, region, 
103                         _samples_per_unit, region_color);
104                 
105         region_view->init (region_color, false);
106         region_views.push_front (region_view);
107         
108         /* follow global waveform setting */
109
110         if (wfd) {
111                 region_view->enable_display(true);
112                 region_view->midi_region()->midi_source(0)->load_model();
113         }
114
115         /* display events and find note range */
116         display_region(region_view, wfd);
117
118         /* always display at least 1 octave range */
119         _highest_note = max(_highest_note, static_cast<uint8_t>(_lowest_note + 11));
120
121         /* catch regionview going away */
122         region->GoingAway.connect (bind (mem_fun (*this, &MidiStreamView::remove_region_view), region));
123         
124         RegionViewAdded (region_view);
125
126         return region_view;
127 }
128
129 void
130 MidiStreamView::display_region(MidiRegionView* region_view, bool load_model)
131 {
132         if ( ! region_view)
133                 return;
134
135         boost::shared_ptr<MidiSource> source(region_view->midi_region()->midi_source(0));
136
137         if (load_model)
138                 source->load_model();
139
140         if (source->model()) {
141                 // Find our note range
142                 for (size_t i=0; i < source->model()->n_notes(); ++i) {
143                         const MidiModel::Note& note = source->model()->note_at(i);
144                         update_bounds(note.note());
145                 }
146         }
147         
148         // Display region contents
149         region_view->display_model(source->model());
150 }
151
152 // FIXME: code duplication with AudioStreamView
153 void
154 MidiStreamView::redisplay_diskstream ()
155 {
156         list<RegionView *>::iterator i, tmp;
157
158         for (i = region_views.begin(); i != region_views.end(); ++i) {
159                 (*i)->enable_display(true); // FIXME: double display
160                 (*i)->set_valid (false);
161                 
162                 /* FIXME: slow.  MidiRegionView needs a find_note_range method
163                  * that finds the range without wasting time drawing the events */
164
165                 // Load model if it isn't already, to get note range
166                 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*i);
167                 mrv->midi_region()->midi_source(0)->load_model();
168         }
169         
170         //_lowest_note = 60; // middle C
171         //_highest_note = _lowest_note + 11;
172
173         if (_trackview.is_midi_track()) {
174                 _trackview.get_diskstream()->playlist()->foreach_region (static_cast<StreamView*>(this), &StreamView::add_region_view);
175         }
176         
177         for (i = region_views.begin(); i != region_views.end(); ) {
178                 tmp = i;
179                 tmp++;
180
181                 if (!(*i)->is_valid()) {
182                         delete *i;
183                         region_views.erase (i);
184                 } else {
185                         (*i)->enable_display(true);
186                         (*i)->set_y_position_and_height(0, height); // apply note range
187                 }
188
189                 i = tmp;
190         }
191
192         /* now fix layering */
193
194         for (RegionViewList::iterator i = region_views.begin(); i != region_views.end(); ++i) {
195                 region_layered (*i);
196         }
197 }
198         
199 void 
200 MidiStreamView::update_bounds(uint8_t note_num)
201 {
202         _lowest_note = min(_lowest_note, note_num);
203         _highest_note = max(_highest_note, note_num);
204 }
205
206
207 void
208 MidiStreamView::setup_rec_box ()
209 {
210         // cerr << _trackview.name() << " streamview SRB\n";
211
212         if (_trackview.session().transport_rolling()) {
213
214                 if (!rec_active && 
215                     _trackview.session().record_status() == Session::Recording && 
216                     _trackview.get_diskstream()->record_enabled()) {
217
218                         if (use_rec_regions && rec_regions.size() == rec_rects.size()) {
219
220                                 /* add a new region, but don't bother if they set use_rec_regions mid-record */
221
222                                 MidiRegion::SourceList sources;
223                                 
224                                 for (list<sigc::connection>::iterator prc = rec_data_ready_connections.begin(); prc != rec_data_ready_connections.end(); ++prc) {
225                                         (*prc).disconnect();
226                                 }
227                                 rec_data_ready_connections.clear();
228
229                                 // FIXME
230                                 boost::shared_ptr<MidiDiskstream> mds = boost::dynamic_pointer_cast<MidiDiskstream>(_trackview.get_diskstream());
231                                 assert(mds);
232
233                                 sources.push_back(mds->write_source());
234                                 
235                                 rec_data_ready_connections.push_back (mds->write_source()->ViewDataRangeReady.connect (bind (mem_fun (*this, &MidiStreamView::rec_data_range_ready), boost::weak_ptr<Source>(mds->write_source())))); 
236
237                                 // handle multi
238                                 
239                                 jack_nframes_t start = 0;
240                                 if (rec_regions.size() > 0) {
241                                         start = rec_regions.back().first->start() + _trackview.get_diskstream()->get_captured_frames(rec_regions.size()-1);
242                                 }
243                                 
244                                 boost::shared_ptr<MidiRegion> region (boost::dynamic_pointer_cast<MidiRegion>
245                                         (RegionFactory::create (sources, start, 1 , "", 0, (Region::Flag)(Region::DefaultFlags | Region::DoNotSaveState), false)));
246                                 assert(region);
247                                 region->set_position (_trackview.session().transport_frame(), this);
248                                 rec_regions.push_back (make_pair(region, (RegionView*)0));
249                                 
250                                 // rec regions are destroyed in setup_rec_box
251
252                                 /* we add the region later */
253                         }
254                         
255                         /* start a new rec box */
256
257                         boost::shared_ptr<MidiTrack> mt = _trackview.midi_track(); /* we know what it is already */
258                         boost::shared_ptr<MidiDiskstream> ds = mt->midi_diskstream();
259                         jack_nframes_t frame_pos = ds->current_capture_start ();
260                         gdouble xstart = _trackview.editor.frame_to_pixel (frame_pos);
261                         gdouble xend;
262                         uint32_t fill_color;
263
264                         assert(_trackview.midi_track()->mode() == Normal);
265                         
266                         xend = xstart;
267                         fill_color = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
268                         
269                         ArdourCanvas::SimpleRect * rec_rect = new Gnome::Canvas::SimpleRect (*canvas_group);
270                         rec_rect->property_x1() = xstart;
271                         rec_rect->property_y1() = 1.0;
272                         rec_rect->property_x2() = xend;
273                         rec_rect->property_y2() = (double) _trackview.height - 1;
274                         rec_rect->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
275                         rec_rect->property_fill_color_rgba() = fill_color;
276                         rec_rect->lower_to_bottom();
277                         
278                         RecBoxInfo recbox;
279                         recbox.rectangle = rec_rect;
280                         recbox.start = _trackview.session().transport_frame();
281                         recbox.length = 0;
282                         
283                         rec_rects.push_back (recbox);
284                         
285                         screen_update_connection.disconnect();
286                         screen_update_connection = ARDOUR_UI::instance()->SuperRapidScreenUpdate.connect (mem_fun (*this, &MidiStreamView::update_rec_box));    
287                         rec_updating = true;
288                         rec_active = true;
289
290                 } else if (rec_active &&
291                            (_trackview.session().record_status() != Session::Recording ||
292                             !_trackview.get_diskstream()->record_enabled())) {
293
294                         screen_update_connection.disconnect();
295                         rec_active = false;
296                         rec_updating = false;
297
298                 }
299                 
300         } else {
301
302                 // cerr << "\tNOT rolling, rec_rects = " << rec_rects.size() << " rec_regions = " << rec_regions.size() << endl;
303
304                 if (!rec_rects.empty() || !rec_regions.empty()) {
305
306                         /* disconnect rapid update */
307                         screen_update_connection.disconnect();
308
309                         for (list<sigc::connection>::iterator prc = rec_data_ready_connections.begin(); prc != rec_data_ready_connections.end(); ++prc) {
310                                 (*prc).disconnect();
311                         }
312                         rec_data_ready_connections.clear();
313
314                         rec_updating = false;
315                         rec_active = false;
316                         
317                         /* remove temp regions */
318                         
319                         for (list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator iter = rec_regions.begin(); iter != rec_regions.end();) {
320                                 list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator tmp;
321                                 
322                                 tmp = iter;
323                                 ++tmp;
324
325                                 (*iter).first->drop_references ();
326
327                                 iter = tmp;
328                         }
329                         
330                         rec_regions.clear();
331
332                         // cerr << "\tclear " << rec_rects.size() << " rec rects\n";
333
334                         /* transport stopped, clear boxes */
335                         for (vector<RecBoxInfo>::iterator iter=rec_rects.begin(); iter != rec_rects.end(); ++iter) {
336                                 RecBoxInfo &rect = (*iter);
337                                 delete rect.rectangle;
338                         }
339                         
340                         rec_rects.clear();
341                         
342                 }
343         }
344 }
345
346 void
347 MidiStreamView::update_rec_regions (boost::shared_ptr<MidiBuffer> data, nframes_t start, nframes_t dur)
348 {
349         ENSURE_GUI_THREAD (bind (mem_fun (*this, &MidiStreamView::update_rec_regions), data, start, dur));
350
351         if (use_rec_regions) {
352
353                 uint32_t n = 0;
354
355                 for (list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator iter = rec_regions.begin(); iter != rec_regions.end(); n++) {
356
357                         list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator tmp;
358
359                         tmp = iter;
360                         ++tmp;
361
362                         if (!canvas_item_visible (rec_rects[n].rectangle)) {
363                                 /* rect already hidden, this region is done */
364                                 iter = tmp;
365                                 continue;
366                         }
367                         
368                         boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion>(iter->first);
369                         if (!region) {
370                                 continue;
371                         }
372
373                         nframes_t origlen = region->length();
374                         
375                         //cerr << "MIDI URR: " << start << " * " << dur
376                         //      << " (origlen " << origlen << ")" << endl;
377
378                         if (region == rec_regions.back().first && rec_active) {
379
380                                 if (start >= region->start()) {
381
382                                         nframes_t nlen = start + dur - region->start();
383
384                                         if (nlen != region->length()) {
385
386                                                 region->freeze ();
387                                                 region->set_position (_trackview.get_diskstream()->get_capture_start_frame(n), this);
388                                                 region->set_length (nlen, this);
389                                                 region->thaw ("updated");
390
391                                                 if (origlen == 1) {
392                                                         /* our special initial length */
393                                                         iter->second = add_region_view_internal (region, false);
394                                                         ((MidiRegionView*)iter->second)->begin_write();
395                                                 }
396
397                                                 /* also update rect */
398                                                 ArdourCanvas::SimpleRect * rect = rec_rects[n].rectangle;
399                                                 gdouble xend = _trackview.editor.frame_to_pixel (region->position() + region->length());
400                                                 rect->property_x2() = xend;
401
402                                                 /* draw events */
403                                                 MidiRegionView* mrv = (MidiRegionView*)iter->second;
404                                                 for (MidiBuffer::iterator i = data->begin(); i != data->end(); ++i) {
405                                                         const MidiEvent& ev = *i;
406                                                         mrv->add_event(ev);
407                                                         mrv->extend_active_notes();
408                                                 }
409
410                                         }
411                                 }
412
413                         } else {
414
415                                 nframes_t nlen = _trackview.get_diskstream()->get_captured_frames(n);
416
417                                 if (nlen != region->length()) {
418
419                                         if (region->source(0)->length() >= region->start() + nlen) {
420
421                                                 region->freeze ();
422                                                 region->set_position (_trackview.get_diskstream()->get_capture_start_frame(n), this);
423                                                 region->set_length (nlen, this);
424                                                 region->thaw ("updated");
425                                                 
426                                                 if (origlen == 1) {
427                                                         /* our special initial length */
428                                                         iter->second = add_region_view_internal (region, false);
429                                                 }
430                                                 
431                                                 /* also hide rect */
432                                                 ArdourCanvas::Item * rect = rec_rects[n].rectangle;
433                                                 rect->hide();
434
435                                         }
436                                 }
437                         }
438
439                         iter = tmp;
440                 }
441         }
442 }
443
444 void
445 MidiStreamView::rec_data_range_ready (boost::shared_ptr<MidiBuffer> data, jack_nframes_t start, jack_nframes_t dur, boost::weak_ptr<Source> weak_src)
446 {
447         // this is called from the butler thread for now
448         
449         ENSURE_GUI_THREAD(bind (mem_fun (*this, &MidiStreamView::rec_data_range_ready), data, start, dur, weak_src));
450         
451         boost::shared_ptr<SMFSource> src (boost::dynamic_pointer_cast<SMFSource>(weak_src.lock()));
452         
453         //cerr << src.get() << " MIDI READY: " << start << " * " << dur
454         //      << " -- " << data->size() << " events!" << endl;
455         
456         this->update_rec_regions (data, start, dur);
457 }
458
459 void
460 MidiStreamView::color_handler ()
461 {
462
463         //case cMidiTrackBase:
464         if (_trackview.is_midi_track()) {
465                 canvas_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiTrackBase.get();
466         } 
467
468         //case cMidiBusBase:
469         if (!_trackview.is_midi_track()) {
470                 canvas_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiBusBase.get();;
471         }
472 }
473