Fix implicit selection when operating track-header ctrl buttons
[ardour.git] / gtk2_ardour / automation_controller.cc
1 /*
2     Copyright (C) 2007 Paul Davis
3     Author: David Robillard
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
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19 */
20
21 #include <iomanip>
22 #include <cmath>
23
24 #include "pbd/compose.h"
25 #include "pbd/error.h"
26
27 #include "ardour/automatable.h"
28 #include "ardour/automation_control.h"
29 #include "ardour/session.h"
30 #include "ardour/tempo.h"
31
32 #include "ardour_button.h"
33 #include "ardour_knob.h"
34 #include "automation_controller.h"
35 #include "gui_thread.h"
36 #include "note_select_dialog.h"
37 #include "timers.h"
38
39 #include "pbd/i18n.h"
40
41 using namespace ARDOUR;
42 using namespace Gtk;
43
44 using PBD::Controllable;
45
46 AutomationBarController::AutomationBarController (
47                 boost::shared_ptr<Automatable>       printer,
48                 boost::shared_ptr<AutomationControl> ac,
49                 Adjustment*                          adj)
50         : Gtkmm2ext::BarController(*adj, ac)
51         , _printer(printer)
52         , _controllable(ac)
53 {
54 }
55
56 std::string
57 AutomationBarController::get_label (double& xpos)
58 {
59         xpos = 0.5;
60         return _printer->value_as_string (_controllable);
61 }
62
63 AutomationBarController::~AutomationBarController()
64 {
65 }
66
67 AutomationController::AutomationController(boost::shared_ptr<Automatable>       printer,
68                                            boost::shared_ptr<AutomationControl> ac,
69                                            Adjustment*                          adj,
70                                            bool                                 use_knob)
71         : _widget(NULL)
72         , _printer (printer)
73         , _controllable(ac)
74         , _adjustment(adj)
75         , _ignore_change(false)
76 {
77         assert (_printer);
78
79         if (ac->toggled()) {
80                 ArdourButton* but = manage(new ArdourButton());
81
82                 // Apply styles for special types
83                 if (ac->parameter().type() == MuteAutomation) {
84                         but->set_name("mute button");
85                 } else if (ac->parameter().type() == SoloAutomation) {
86                         but->set_name("solo button");
87                 } else {
88                         but->set_name("generic button");
89                 }
90                 but->set_fallthrough_to_parent(true);
91                 but->set_controllable(ac);
92                 but->signal_button_press_event().connect(
93                         sigc::mem_fun(*this, &AutomationController::button_press));
94                 but->signal_button_release_event().connect(
95                         sigc::mem_fun(*this, &AutomationController::button_release));
96                 const bool active = _adjustment->get_value() >= 0.5;
97                 if (but->get_active() != active) {
98                         but->set_active(active);
99                 }
100                 _widget = but;
101         } else if (use_knob) {
102                 ArdourKnob* knob = manage (new ArdourKnob (ArdourKnob::default_elements, ArdourKnob::Detent));
103                 knob->set_controllable (ac);
104                 knob->set_name("processor control knob");
105                 _widget = knob;
106         } else {
107                 AutomationBarController* bar = manage(new AutomationBarController(_printer, ac, adj));
108
109                 bar->set_name(X_("ProcessorControlSlider"));
110                 bar->StartGesture.connect(
111                         sigc::mem_fun(*this, &AutomationController::start_touch));
112                 bar->StopGesture.connect(
113                         sigc::mem_fun(*this, &AutomationController::end_touch));
114                 bar->signal_button_release_event().connect(
115                         sigc::mem_fun(*this, &AutomationController::on_button_release));
116
117                 _widget = bar;
118         }
119
120         _adjustment->signal_value_changed().connect(
121                 sigc::mem_fun(*this, &AutomationController::value_adjusted));
122
123         _screen_update_connection = Timers::rapid_connect (
124                         sigc::mem_fun (*this, &AutomationController::display_effective_value));
125
126         ac->Changed.connect (_changed_connection, invalidator (*this), boost::bind (&AutomationController::display_effective_value, this), gui_context());
127
128         add(*_widget);
129         show_all();
130 }
131
132 AutomationController::~AutomationController()
133 {
134 }
135
136 boost::shared_ptr<AutomationController>
137 AutomationController::create(boost::shared_ptr<Automatable>       printer,
138                              const Evoral::Parameter&             param,
139                              const ParameterDescriptor&           desc,
140                              boost::shared_ptr<AutomationControl> ac,
141                              bool use_knob)
142 {
143         const double lo        = ac->internal_to_interface(desc.lower);
144         const double up        = ac->internal_to_interface(desc.upper);
145         const double normal    = ac->internal_to_interface(desc.normal);
146         const double smallstep = ac->internal_to_interface(desc.lower + desc.smallstep);
147         const double largestep = ac->internal_to_interface(desc.lower + desc.largestep);
148
149         Gtk::Adjustment* adjustment = manage (
150                 new Gtk::Adjustment (normal, lo, up, smallstep, largestep));
151
152         assert (ac);
153         assert(ac->parameter() == param);
154         return boost::shared_ptr<AutomationController>(new AutomationController(printer, ac, adjustment, use_knob));
155 }
156
157 void
158 AutomationController::display_effective_value()
159 {
160         double const interface_value = _controllable->internal_to_interface(_controllable->get_value());
161
162         if (_adjustment->get_value () != interface_value) {
163                 _ignore_change = true;
164                 _adjustment->set_value (interface_value);
165                 _ignore_change = false;
166         }
167
168 }
169
170 void
171 AutomationController::value_adjusted ()
172 {
173         if (!_ignore_change) {
174                 _controllable->set_value (_controllable->interface_to_internal(_adjustment->get_value()), Controllable::NoGroup);
175         }
176
177         /* A bar controller will automatically follow the adjustment, but for a
178            button we have to do it manually. */
179         ArdourButton* but = dynamic_cast<ArdourButton*>(_widget);
180         if (but) {
181                 const bool active = _adjustment->get_value() >= 0.5;
182                 if (but->get_active() != active) {
183                         but->set_active(active);
184                 }
185         }
186 }
187
188 void
189 AutomationController::start_touch()
190 {
191         _controllable->start_touch (_controllable->session().transport_frame());
192 }
193
194 void
195 AutomationController::end_touch ()
196 {
197         if (_controllable->automation_state() == Touch) {
198
199                 bool mark = false;
200                 double when = 0;
201
202                 if (_controllable->session().transport_rolling()) {
203                         mark = true;
204                         when = _controllable->session().transport_frame();
205                 }
206
207                 _controllable->stop_touch (mark, when);
208         } else {
209                 _controllable->stop_touch (false, _controllable->session().transport_frame());
210         }
211 }
212
213 bool
214 AutomationController::button_press (GdkEventButton*)
215 {
216         ArdourButton* but = dynamic_cast<ArdourButton*>(_widget);
217         if (but) {
218                 start_touch ();
219                 _controllable->set_value (but->get_active () ? 0.0 : 1.0, Controllable::UseGroup);
220         }
221         return false;
222 }
223
224 bool
225 AutomationController::button_release (GdkEventButton*)
226 {
227         end_touch ();
228         return true;
229 }
230
231 static double
232 midi_note_to_hz(int note)
233 {
234         const double tuning = 440.0;
235         return tuning * pow(2, (note - 69.0) / 12.0);
236 }
237
238 static double
239 clamp(double val, double min, double max)
240 {
241         if (val < min) {
242                 return min;
243         } else if (val > max) {
244                 return max;
245         }
246         return val;
247 }
248
249 void
250 AutomationController::run_note_select_dialog()
251 {
252         const ARDOUR::ParameterDescriptor& desc   = _controllable->desc();
253         NoteSelectDialog*                  dialog = new NoteSelectDialog();
254         if (dialog->run() == Gtk::RESPONSE_ACCEPT) {
255                 const double value = ((_controllable->desc().unit == ARDOUR::ParameterDescriptor::HZ)
256                                       ? midi_note_to_hz(dialog->note_number())
257                                       : dialog->note_number());
258                 _controllable->set_value(clamp(value, desc.lower, desc.upper), Controllable::NoGroup);
259         }
260         delete dialog;
261 }
262
263 void
264 AutomationController::set_freq_beats(double beats)
265 {
266         const ARDOUR::ParameterDescriptor& desc    = _controllable->desc();
267         const ARDOUR::Session&             session = _controllable->session();
268         const framepos_t                   pos     = session.transport_frame();
269         const ARDOUR::Tempo&               tempo   = session.tempo_map().tempo_at_frame (pos);
270         const double                       bpm     = tempo.note_types_per_minute();
271         const double                       bps     = bpm / 60.0;
272         const double                       freq    = bps / beats;
273         _controllable->set_value(clamp(freq, desc.lower, desc.upper), Controllable::NoGroup);
274 }
275
276 void
277 AutomationController::set_ratio(double ratio)
278 {
279         const ARDOUR::ParameterDescriptor& desc  = _controllable->desc();
280         const double                       value = _controllable->get_value() * ratio;
281         _controllable->set_value(clamp(value, desc.lower, desc.upper), Controllable::NoGroup);
282 }
283
284 bool
285 AutomationController::on_button_release(GdkEventButton* ev)
286 {
287         using namespace Gtk::Menu_Helpers;
288
289         if (ev->button != 3) {
290                 return false;
291         }
292
293         const ARDOUR::ParameterDescriptor& desc = _controllable->desc();
294         if (desc.unit == ARDOUR::ParameterDescriptor::MIDI_NOTE) {
295                 Gtk::Menu* menu  = manage(new Menu());
296                 MenuList&  items = menu->items();
297                 items.push_back(MenuElem(_("Select Note..."),
298                                          sigc::mem_fun(*this, &AutomationController::run_note_select_dialog)));
299                 menu->popup(1, ev->time);
300                 return true;
301         } else if (desc.unit == ARDOUR::ParameterDescriptor::HZ) {
302                 Gtk::Menu* menu  = manage(new Menu());
303                 MenuList&  items = menu->items();
304                 items.push_back(MenuElem(_("Halve"),
305                                          sigc::bind(sigc::mem_fun(*this, &AutomationController::set_ratio),
306                                                     0.5)));
307                 items.push_back(MenuElem(_("Double"),
308                                          sigc::bind(sigc::mem_fun(*this, &AutomationController::set_ratio),
309                                                     2.0)));
310                 const bool is_audible = desc.upper > 40.0;
311                 const bool is_low     = desc.lower < 1.0;
312                 if (is_audible) {
313                         items.push_back(MenuElem(_("Select Note..."),
314                                                  sigc::mem_fun(*this, &AutomationController::run_note_select_dialog)));
315                 }
316                 if (is_low) {
317                         for (int beats = 1; beats <= 16; ++beats) {
318                                 items.push_back(MenuElem (string_compose(P_("Set to %1 beat", "Set to %1 beats", beats), beats),
319                                                          sigc::bind(sigc::mem_fun(*this, &AutomationController::set_freq_beats),
320                                                                     (double)beats)));
321                         }
322                 }
323                 menu->popup(1, ev->time);
324                 return true;
325         }
326
327         return false;
328 }
329
330 /** Stop updating our value from our controllable */
331 void
332 AutomationController::stop_updating ()
333 {
334         _screen_update_connection.disconnect ();
335 }
336
337 void
338 AutomationController::disable_vertical_scroll ()
339 {
340         AutomationBarController* bar = dynamic_cast<AutomationBarController*>(_widget);
341         if (bar) {
342                 bar->set_tweaks (
343                         Gtkmm2ext::PixFader::Tweaks(bar->tweaks() |
344                                                     Gtkmm2ext::PixFader::NoVerticalScroll));
345         }
346 }