use new Session API to deal with transport state
[ardour.git] / libs / surfaces / osc / osc_global_observer.cc
1 /*
2  * Copyright (C) 2016-2017 Paul Davis <paul@linuxaudiosystems.com>
3  * Copyright (C) 2016-2018 Len Ovens <len@ovenwerks.net>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19
20 #include "boost/lambda/lambda.hpp"
21
22 #include "pbd/control_math.h"
23
24 #include "ardour/amp.h"
25 #include "ardour/session.h"
26 #include "ardour/dB.h"
27 #include "ardour/meter.h"
28 #include "ardour/monitor_processor.h"
29
30 #include "osc.h"
31 #include "osc_global_observer.h"
32
33 #include "pbd/i18n.h"
34
35 using namespace std;
36 using namespace PBD;
37 using namespace ARDOUR;
38 using namespace ArdourSurface;
39
40 OSCGlobalObserver::OSCGlobalObserver (OSC& o, Session& s, ArdourSurface::OSC::OSCSurface* su)
41         : _osc (o)
42         ,sur (su)
43         ,_init (true)
44         ,_last_master_gain (-1.0)
45         ,_last_master_trim (-1.0)
46         ,_last_monitor_gain (-1.0)
47         ,_jog_mode (1024)
48         ,last_punchin (4)
49         ,last_punchout (4)
50         ,last_click (4)
51 {
52         addr = lo_address_new_from_url  (sur->remote_url.c_str());
53         session = &s;
54         gainmode = sur->gainmode;
55         feedback = sur->feedback;
56         uint32_t jogmode = sur->jogmode;
57         _last_sample = -1;
58         mark_text = "";
59         if (feedback[4]) {
60
61                 // connect to all the things we want to send feed back from
62
63                 /*
64                 *       Master (todo)
65                 *               Pan width
66                 */
67
68                 // Master channel first
69                 _osc.text_message (X_("/master/name"), "Master", addr);
70                 boost::shared_ptr<Stripable> strip = session->master_out();
71
72                 boost::shared_ptr<Controllable> mute_controllable = boost::dynamic_pointer_cast<Controllable>(strip->mute_control());
73                 mute_controllable->Changed.connect (strip_connections, MISSING_INVALIDATOR, boost::bind (&OSCGlobalObserver::send_change_message, this, X_("/master/mute"), strip->mute_control()), OSC::instance());
74                 send_change_message (X_("/master/mute"), mute_controllable);
75
76                 boost::shared_ptr<Controllable> trim_controllable = boost::dynamic_pointer_cast<Controllable>(strip->trim_control());
77                 trim_controllable->Changed.connect (strip_connections, MISSING_INVALIDATOR, boost::bind (&OSCGlobalObserver::send_trim_message, this, X_("/master/trimdB"), strip->trim_control()), OSC::instance());
78                 send_trim_message (X_("/master/trimdB"), trim_controllable);
79
80                 boost::shared_ptr<Controllable> pan_controllable = boost::dynamic_pointer_cast<Controllable>(strip->pan_azimuth_control());
81                 if (pan_controllable) {
82                         pan_controllable->Changed.connect (strip_connections, MISSING_INVALIDATOR, boost::bind (&OSCGlobalObserver::send_change_message, this, X_("/master/pan_stereo_position"), strip->pan_azimuth_control()), OSC::instance());
83                         send_change_message (X_("/master/pan_stereo_position"), pan_controllable);
84                 }
85
86                 boost::shared_ptr<Controllable> gain_controllable = boost::dynamic_pointer_cast<Controllable>(strip->gain_control());
87                 gain_controllable->Changed.connect (strip_connections, MISSING_INVALIDATOR, boost::bind (&OSCGlobalObserver::send_gain_message, this, X_("/master/"), strip->gain_control()), OSC::instance());
88                 send_gain_message (X_("/master/"), gain_controllable);
89
90                 // monitor stuff next
91                 strip = session->monitor_out();
92                 if (strip) {
93                         _osc.text_message (X_("/monitor/name"), "Monitor", addr);
94
95                         boost::shared_ptr<Controllable> mon_mute_cont = strip->monitor_control()->cut_control();
96                         mon_mute_cont->Changed.connect (strip_connections, MISSING_INVALIDATOR, boost::bind (&OSCGlobalObserver::send_change_message, this, X_("/monitor/mute"), mon_mute_cont), OSC::instance());
97                         send_change_message (X_("/monitor/mute"), mon_mute_cont);
98
99                         boost::shared_ptr<Controllable> mon_dim_cont = strip->monitor_control()->dim_control();
100                         mon_dim_cont->Changed.connect (strip_connections, MISSING_INVALIDATOR, boost::bind (&OSCGlobalObserver::send_change_message, this, X_("/monitor/dim"), mon_dim_cont), OSC::instance());
101                         send_change_message (X_("/monitor/dim"), mon_dim_cont);
102
103                         boost::shared_ptr<Controllable> mon_mono_cont = strip->monitor_control()->mono_control();
104                         mon_mono_cont->Changed.connect (strip_connections, MISSING_INVALIDATOR, boost::bind (&OSCGlobalObserver::send_change_message, this, X_("/monitor/mono"), mon_mono_cont), OSC::instance());
105                         send_change_message (X_("/monitor/mono"), mon_mono_cont);
106
107                         gain_controllable = boost::dynamic_pointer_cast<Controllable>(strip->gain_control());
108                         gain_controllable->Changed.connect (strip_connections, MISSING_INVALIDATOR, boost::bind (&OSCGlobalObserver::send_gain_message, this, X_("/monitor/"), strip->gain_control()), OSC::instance());
109                         send_gain_message (X_("/monitor/"), gain_controllable);
110                 }
111
112                 //Transport feedback
113                 session->TransportStateChange.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&OSCGlobalObserver::send_transport_state_changed, this), OSC::instance());
114                 send_transport_state_changed ();
115                 session->TransportLooped.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&OSCGlobalObserver::send_transport_state_changed, this), OSC::instance());
116                 session->RecordStateChanged.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&OSCGlobalObserver::send_record_state_changed, this), OSC::instance());
117                 send_record_state_changed ();
118                 session->locations_modified.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&OSCGlobalObserver::marks_changed, this), OSC::instance());
119                 marks_changed ();
120
121                 // session feedback
122                 session->StateSaved.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&OSCGlobalObserver::session_name, this, X_("/session_name"), _1), OSC::instance());
123                 session_name (X_("/session_name"), session->snap_name());
124                 session->SoloActive.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&OSCGlobalObserver::solo_active, this, _1), OSC::instance());
125                 solo_active (session->soloing() || session->listening());
126
127                 boost::shared_ptr<Controllable> click_controllable = boost::dynamic_pointer_cast<Controllable>(session->click_gain()->gain_control());
128                 click_controllable->Changed.connect (strip_connections, MISSING_INVALIDATOR, boost::bind (&OSCGlobalObserver::send_change_message, this, X_("/click/level"), click_controllable), OSC::instance());
129                 send_change_message (X_("/click/level"), click_controllable);
130
131                 session->route_group_added.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&OSCGlobalObserver::group_changed, this, _1), OSC::instance());
132                 session->route_group_removed.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&OSCGlobalObserver::group_changed, this), OSC::instance());
133                 session->route_groups_reordered.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&OSCGlobalObserver::group_changed, this), OSC::instance());
134                 _osc.send_group_list (addr);
135
136                 extra_check ();
137                 jog_mode (jogmode);
138
139                 /*
140                 *       Maybe (many) more
141                 */
142         }
143         _init = false;
144 }
145
146 OSCGlobalObserver::~OSCGlobalObserver ()
147 {
148         _init = true;
149
150         // need to add general zero everything messages
151         strip_connections.drop_connections ();
152         session_connections.drop_connections ();
153
154         lo_address_free (addr);
155 }
156
157 void
158 OSCGlobalObserver::clear_observer ()
159 {
160         strip_connections.drop_connections ();
161         session_connections.drop_connections ();
162         _osc.text_message (X_("/master/name"), " ", addr);
163         _osc.text_message (X_("/monitor/name"), " ", addr);
164         _osc.text_message (X_("/session_name"), " ", addr);
165         _osc.text_message (X_("/marker"), " ", addr);
166         if (feedback[6]) { // timecode enabled
167                 _osc.text_message (X_("/position/smpte"), " ", addr);
168         }
169         if (feedback[5]) { // Bar beat enabled
170                 _osc.text_message (X_("/position/bbt"), " ", addr);
171         }
172         if (feedback[11]) { // minutes/seconds enabled
173                 _osc.text_message (X_("/position/time"), " ", addr);
174         }
175         if (feedback[10]) { // samples
176                 _osc.text_message (X_("/position/samples"), " ", addr);
177         }
178         if (feedback[3]) { //heart beat enabled
179                 _osc.float_message (X_("/heartbeat"), 0.0, addr);
180         }
181         if (feedback[7] || feedback[8]) { // meters enabled
182                 float meter = 0;
183                 if (feedback[7] && !gainmode) {
184                         meter = -193;
185                 }
186                 _osc.float_message (X_("/master/meter"), meter, addr);
187         }
188         if (feedback[9]) {
189                 _osc.float_message (X_("/master/signal"), 0, addr);
190         }
191         _osc.float_message (X_("/master/fader"), 0, addr);
192         _osc.float_message (X_("/monitor/fader"), 0, addr);
193         _osc.float_message (X_("/master/gain"), -193, addr);
194         _osc.float_message (X_("/monitor/gain"), -193, addr);
195         _osc.float_message (X_("/master/trimdB"), 0, addr);
196         _osc.float_message (X_("/master/mute"), 0, addr);
197         _osc.float_message (X_("/master/pan_stereo_position"), 0.5, addr);
198         _osc.float_message (X_("/monitor/mute"), 0, addr);
199         _osc.float_message (X_("/monitor/dim"), 0, addr);
200         _osc.float_message (X_("/monitor/mono"), 0, addr);
201         _osc.float_message (X_("/loop_toggle"), 0, addr);
202         _osc.float_message (X_("/transport_play"), 0, addr);
203         _osc.float_message (X_("/transport_stop"), 0, addr);
204         _osc.float_message (X_("/toggle_roll"), 0, addr);
205         _osc.float_message (X_("/rewind"), 0, addr);
206         _osc.float_message (X_("/ffwd"), 0, addr);
207         _osc.float_message (X_("/record_tally"), 0, addr);
208         _osc.float_message (X_("/rec_enable_toggle"), 0, addr);
209         _osc.float_message (X_("/cancel_all_solos"), 0, addr);
210         _osc.float_message (X_("/toggle_punch_out"), 0, addr);
211         _osc.float_message (X_("/toggle_punch_in"), 0, addr);
212         _osc.float_message (X_("/toggle_click"), 0, addr);
213         _osc.float_message (X_("/click/level"), 0, addr);
214         _osc.text_message (X_("/group/list"), " ", addr);
215         _osc.text_message (X_("/jog/mode/name"), " ", addr);
216         _osc.int_message (X_("/jog/mode"), 0, addr);
217
218
219 }
220
221 void
222 OSCGlobalObserver::tick ()
223 {
224         if (_init) {
225                 return;
226         }
227         samplepos_t now_sample = session->transport_sample();
228         if (now_sample != _last_sample) {
229                 if (feedback[6]) { // timecode enabled
230                         Timecode::Time timecode;
231                         session->timecode_time (now_sample, timecode);
232
233                         // Timecode mode: Hours/Minutes/Seconds/Samples
234                         ostringstream os;
235                         os << setw(2) << setfill('0') << timecode.hours;
236                         os << ':';
237                         os << setw(2) << setfill('0') << timecode.minutes;
238                         os << ':';
239                         os << setw(2) << setfill('0') << timecode.seconds;
240                         os << ':';
241                         os << setw(2) << setfill('0') << timecode.frames;
242
243                         _osc.text_message (X_("/position/smpte"), os.str(), addr);
244                 }
245                 if (feedback[5]) { // Bar beat enabled
246                         Timecode::BBT_Time bbt_time;
247
248                         session->bbt_time (now_sample, bbt_time);
249
250                         // semantics:  BBB/bb/tttt
251                         ostringstream os;
252
253                         os << setw(3) << setfill('0') << bbt_time.bars;
254                         os << '|';
255                         os << setw(2) << setfill('0') << bbt_time.beats;
256                         os << '|';
257                         os << setw(4) << setfill('0') << bbt_time.ticks;
258
259                         _osc.text_message (X_("/position/bbt"), os.str(), addr);
260                 }
261                 if (feedback[11]) { // minutes/seconds enabled
262                         samplepos_t left = now_sample;
263                         int hrs = (int) floor (left / (session->sample_rate() * 60.0f * 60.0f));
264                         left -= (samplecnt_t) floor (hrs * session->sample_rate() * 60.0f * 60.0f);
265                         int mins = (int) floor (left / (session->sample_rate() * 60.0f));
266                         left -= (samplecnt_t) floor (mins * session->sample_rate() * 60.0f);
267                         int secs = (int) floor (left / (float) session->sample_rate());
268                         left -= (samplecnt_t) floor ((double)(secs * session->sample_rate()));
269                         int millisecs = floor (left * 1000.0 / (float) session->sample_rate());
270
271                         // Min/sec mode: Hours/Minutes/Seconds/msec
272                         ostringstream os;
273                         os << setw(2) << setfill('0') << hrs;
274                         os << ':';
275                         os << setw(2) << setfill('0') << mins;
276                         os << ':';
277                         os << setw(2) << setfill('0') << secs;
278                         os << '.';
279                         os << setw(3) << setfill('0') << millisecs;
280
281                         _osc.text_message (X_("/position/time"), os.str(), addr);
282                 }
283                 if (feedback[10]) { // samples
284                         ostringstream os;
285                         os << now_sample;
286                         _osc.text_message (X_("/position/samples"), os.str(), addr);
287                 }
288                 _last_sample = now_sample;
289                 mark_update ();
290         }
291         if (feedback[3]) { //heart beat enabled
292                 if (_heartbeat == 10) {
293                         _osc.float_message (X_("/heartbeat"), 1.0, addr);
294                 }
295                 if (!_heartbeat) {
296                         _osc.float_message (X_("/heartbeat"), 0.0, addr);
297                 }
298                 _heartbeat++;
299                 if (_heartbeat > 20) _heartbeat = 0;
300         }
301         if (feedback[7] || feedback[8] || feedback[9]) { // meters enabled
302                 // the only meter here is master
303                 float now_meter = session->master_out()->peak_meter()->meter_level(0, MeterMCP);
304                 if (now_meter < -94) now_meter = -193;
305                 if (_last_meter != now_meter) {
306                         if (feedback[7] || feedback[8]) {
307                                 if (gainmode && feedback[7]) {
308                                         // change from db to 0-1
309                                         _osc.float_message (X_("/master/meter"), ((now_meter + 94) / 100), addr);
310                                 } else if ((!gainmode) && feedback[7]) {
311                                         _osc.float_message (X_("/master/meter"), now_meter, addr);
312                                 } else if (feedback[8]) {
313                                         uint32_t ledlvl = (uint32_t)(((now_meter + 54) / 3.75)-1);
314                                         uint32_t ledbits = ~(0xfff<<ledlvl);
315                                         _osc.float_message (X_("/master/meter"), ledbits, addr);
316                                 }
317                         }
318                         if (feedback[9]) {
319                                 float signal;
320                                 if (now_meter < -40) {
321                                         signal = 0;
322                                 } else {
323                                         signal = 1;
324                                 }
325                                 _osc.float_message (X_("/master/signal"), signal, addr);
326                         }
327                 }
328                 _last_meter = now_meter;
329
330         }
331         if (feedback[4]) {
332                 if (master_timeout) {
333                         if (master_timeout == 1) {
334                                 _osc.text_message (X_("/master/name"), "Master", addr);
335                         }
336                         master_timeout--;
337                 }
338                 if (monitor_timeout) {
339                         if (monitor_timeout == 1) {
340                                 _osc.text_message (X_("/monitor/name"), "Monitor", addr);
341                         }
342                         monitor_timeout--;
343                 }
344                 extra_check ();
345         }
346 }
347
348 void
349 OSCGlobalObserver::send_change_message (string path, boost::shared_ptr<Controllable> controllable)
350 {
351         float val = controllable->get_value();
352         _osc.float_message (path, (float) controllable->internal_to_interface (val), addr);
353 }
354
355 void
356 OSCGlobalObserver::session_name (string path, string name)
357 {
358         _osc.text_message (path, name, addr);
359 }
360
361 void
362 OSCGlobalObserver::send_gain_message (string path, boost::shared_ptr<Controllable> controllable)
363 {
364         bool ismaster = false;
365
366         if (path.find(X_("master")) != std::string::npos) {
367                 ismaster = true;
368                 if (_last_master_gain != controllable->get_value()) {
369                         _last_master_gain = controllable->get_value();
370                 } else {
371                         return;
372                 }
373         } else {
374                 if (_last_monitor_gain != controllable->get_value()) {
375                         _last_monitor_gain = controllable->get_value();
376                 } else {
377                         return;
378                 }
379         }
380         if (gainmode) {
381                 _osc.float_message (string_compose (X_("%1fader"), path), controllable->internal_to_interface (controllable->get_value()), addr);
382                 if (gainmode == 1) {
383                         _osc.text_message (string_compose (X_("%1name"), path), string_compose ("%1%2%3", std::fixed, std::setprecision(2), accurate_coefficient_to_dB (controllable->get_value())), addr);
384                         if (ismaster) {
385                                 master_timeout = 8;
386                         } else {
387                                 monitor_timeout = 8;
388                         }
389                 }
390         }
391         if (!gainmode || gainmode == 2) {
392                 if (controllable->get_value() < 1e-15) {
393                         _osc.float_message (string_compose (X_("%1gain"),path), -200, addr);
394                 } else {
395                         _osc.float_message (string_compose (X_("%1gain"),path), accurate_coefficient_to_dB (controllable->get_value()), addr);
396                 }
397         }
398 }
399
400 void
401 OSCGlobalObserver::send_trim_message (string path, boost::shared_ptr<Controllable> controllable)
402 {
403         if (_last_master_trim != controllable->get_value()) {
404                 _last_master_trim = controllable->get_value();
405         } else {
406                 return;
407         }
408         _osc.float_message (X_("/master/trimdB"), (float) accurate_coefficient_to_dB (controllable->get_value()), addr);
409 }
410
411
412 void
413 OSCGlobalObserver::send_transport_state_changed()
414 {
415         _osc.float_message (X_("/loop_toggle"), session->get_play_loop(), addr);
416         _osc.float_message (X_("/transport_play"), session->transport_speed() == 1.0, addr);
417         _osc.float_message (X_("/toggle_roll"), session->transport_speed() == 1.0, addr);
418         _osc.float_message (X_("/transport_stop"), session->transport_stopped_or_stopping(), addr);
419         _osc.float_message (X_("/rewind"), session->transport_speed() < 0.0, addr);
420         _osc.float_message (X_("/ffwd"), (session->transport_speed() != 1.0 && session->transport_speed() > 0.0), addr);
421 }
422
423 void
424 OSCGlobalObserver::marks_changed ()
425 {
426         const Locations::LocationList& ll (session->locations ()->list ());
427         // get Locations that are marks
428         for (Locations::LocationList::const_iterator l = ll.begin(); l != ll.end(); ++l) {
429                 if ((*l)->is_session_range ()) {
430                         lm.push_back (LocationMarker(_("start"), (*l)->start ()));
431                         lm.push_back (LocationMarker(_("end"), (*l)->end ()));
432                         continue;
433                 }
434                 if ((*l)->is_mark ()) {
435                         lm.push_back (LocationMarker((*l)->name(), (*l)->start ()));
436                 }
437         }
438         // sort them by position
439         LocationMarkerSort location_marker_sort;
440         std::sort (lm.begin(), lm.end(), location_marker_sort);
441         mark_update ();
442
443 }
444
445 void
446 OSCGlobalObserver::mark_update ()
447 {
448         string send_str = "No Marks";
449         if (lm.size()) {
450                 uint32_t prev = 0;
451                 uint32_t next = lm.size() - 1;
452                 for (uint32_t i = 0; i < lm.size (); i++) {
453                         if ((lm[i].when <= _last_sample) && (i > prev)) {
454                                 prev = i;
455                         }
456                         if ((lm[i].when >= _last_sample) && (i < next)) {
457                                 next = i;
458                                 break;
459                         }
460                 }
461                 if ((prev_mark != lm[prev].when) || (next_mark != lm[next].when)) {
462                         string send_str = lm[prev].label;
463                         prev_mark = lm[prev].when;
464                         next_mark = lm[next].when;
465                         if (prev != next) {
466                                 send_str = string_compose ("%1 <-> %2", lm[prev].label, lm[next].label);
467                         }
468                         if (_last_sample > lm[lm.size() - 1].when) {
469                                 send_str = string_compose ("%1 <-", lm[lm.size() - 1].label);
470                         }
471                         if (_last_sample < lm[0].when) {
472                                 send_str = string_compose ("-> %1", lm[0].label);
473                         }
474                 }
475         }
476         if (send_str != mark_text) {
477                 mark_text = send_str;
478                 _osc.text_message (X_("/marker"), send_str, addr);
479         }
480
481 }
482
483 void
484 OSCGlobalObserver::send_record_state_changed ()
485 {
486         _osc.float_message (X_("/rec_enable_toggle"), (int)session->get_record_enabled (), addr);
487
488         if (session->have_rec_enabled_track () || session->get_record_enabled ()) {
489                 _osc.float_message (X_("/record_tally"), 1, addr);
490         } else {
491                 _osc.float_message (X_("/record_tally"), 0, addr);
492         }
493 }
494
495 void
496 OSCGlobalObserver::solo_active (bool active)
497 {
498         _osc.float_message (X_("/cancel_all_solos"), (float) active, addr);
499 }
500
501 void
502 OSCGlobalObserver::extra_check ()
503 {
504         if (last_punchin != session->config.get_punch_in()) {
505                 last_punchin = session->config.get_punch_in();
506                 _osc.float_message (X_("/toggle_punch_in"), last_punchin, addr);
507         }
508         if (last_punchout != session->config.get_punch_out()) {
509                 last_punchout = session->config.get_punch_out();
510                 _osc.float_message (X_("/toggle_punch_out"), last_punchout, addr);
511         }
512         if (last_click != Config->get_clicking()) {
513                 last_click = Config->get_clicking();
514                 _osc.float_message (X_("/toggle_click"), last_click, addr);
515         }
516 }
517
518 void
519 OSCGlobalObserver::jog_mode (uint32_t jogmode)
520 {
521         if (jogmode == _jog_mode || !feedback[4]) {
522                 // no change
523                 return;
524         }
525         _jog_mode = jogmode;
526
527         switch(jogmode)
528         {
529                 case 0:
530                         _osc.text_message (X_("/jog/mode/name"), "Jog", addr);
531                         break;
532                 case 1:
533                         _osc.text_message (X_("/jog/mode/name"), "Nudge", addr);
534                         break;
535                 case 2:
536                         _osc.text_message (X_("/jog/mode/name"), "Scrub", addr);
537                         break;
538                 case 3:
539                         _osc.text_message (X_("/jog/mode/name"), "Shuttle", addr);
540                         break;
541                 case 4:
542                         _osc.text_message (X_("/jog/mode/name"), "Marker", addr);
543                         break;
544                 case 5:
545                         _osc.text_message (X_("/jog/mode/name"), "Scroll", addr);
546                         break;
547                 case 6:
548                         _osc.text_message (X_("/jog/mode/name"), "Track", addr);
549                         break;
550                 case 7:
551                         _osc.text_message (X_("/jog/mode/name"), "Bank", addr);
552                         break;
553                 default:
554                         PBD::warning << X_("Jog Mode: ") << jogmode << X_(" is not valid.") << endmsg;
555                         break;
556         }
557         _osc.int_message (X_("/jog/mode"), jogmode, addr);
558 }
559
560 void
561 OSCGlobalObserver::group_changed (ARDOUR::RouteGroup *rg)
562 {
563         _osc.send_group_list (addr);
564 }
565
566 void
567 OSCGlobalObserver::group_changed ()
568 {
569         _osc.send_group_list (addr);
570 }
571