allow AutomatoinContoller to render as Knob instead of Slider.
[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 "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_controllable(ac);
91                 but->signal_clicked.connect(
92                         sigc::mem_fun(*this, &AutomationController::toggled));
93                 _widget = but;
94         } else if (use_knob) {
95                 ArdourKnob* knob = manage (new ArdourKnob (ArdourKnob::default_elements, ArdourKnob::Detent));
96                 knob->set_controllable (ac);
97                 knob->set_name("processor control knob");
98                 _widget = knob;
99         } else {
100                 AutomationBarController* bar = manage(new AutomationBarController(_printer, ac, adj));
101
102                 bar->set_name(X_("ProcessorControlSlider"));
103                 bar->StartGesture.connect(
104                         sigc::mem_fun(*this, &AutomationController::start_touch));
105                 bar->StopGesture.connect(
106                         sigc::mem_fun(*this, &AutomationController::end_touch));
107                 bar->signal_button_release_event().connect(
108                         sigc::mem_fun(*this, &AutomationController::on_button_release));
109
110                 _widget = bar;
111         }
112
113         _adjustment->signal_value_changed().connect(
114                 sigc::mem_fun(*this, &AutomationController::value_adjusted));
115
116         _screen_update_connection = Timers::rapid_connect (
117                         sigc::mem_fun (*this, &AutomationController::display_effective_value));
118
119         ac->Changed.connect (_changed_connection, invalidator (*this), boost::bind (&AutomationController::value_changed, this), gui_context());
120
121         add(*_widget);
122         show_all();
123 }
124
125 AutomationController::~AutomationController()
126 {
127 }
128
129 boost::shared_ptr<AutomationController>
130 AutomationController::create(boost::shared_ptr<Automatable>       printer,
131                              const Evoral::Parameter&             param,
132                              const ParameterDescriptor&           desc,
133                              boost::shared_ptr<AutomationControl> ac,
134                              bool use_knob)
135 {
136         const double lo        = ac->internal_to_interface(desc.lower);
137         const double up        = ac->internal_to_interface(desc.upper);
138         const double normal    = ac->internal_to_interface(desc.normal);
139         const double smallstep = ac->internal_to_interface(desc.lower + desc.smallstep);
140         const double largestep = ac->internal_to_interface(desc.lower + desc.largestep);
141
142         Gtk::Adjustment* adjustment = manage (
143                 new Gtk::Adjustment (normal, lo, up, smallstep, largestep));
144
145         assert (ac);
146         assert(ac->parameter() == param);
147         return boost::shared_ptr<AutomationController>(new AutomationController(printer, ac, adjustment, use_knob));
148 }
149
150 void
151 AutomationController::display_effective_value()
152 {
153         double const interface_value = _controllable->internal_to_interface(_controllable->get_value());
154
155         if (_adjustment->get_value () != interface_value) {
156                 _ignore_change = true;
157                 _adjustment->set_value (interface_value);
158                 _ignore_change = false;
159         }
160 }
161
162 void
163 AutomationController::value_adjusted ()
164 {
165         if (!_ignore_change) {
166                 _controllable->set_value (_controllable->interface_to_internal(_adjustment->get_value()), Controllable::NoGroup);
167         }
168
169         /* A bar controller will automatically follow the adjustment, but for a
170            button we have to do it manually. */
171         ArdourButton* but = dynamic_cast<ArdourButton*>(_widget);
172         if (but) {
173                 const bool active = _adjustment->get_value() >= 0.5;
174                 if (but->get_active() != active) {
175                         but->set_active(active);
176                 }
177         }
178 }
179
180 void
181 AutomationController::start_touch()
182 {
183         _controllable->start_touch (_controllable->session().transport_frame());
184 }
185
186 void
187 AutomationController::end_touch ()
188 {
189         if (_controllable->automation_state() == Touch) {
190
191                 bool mark = false;
192                 double when = 0;
193
194                 if (_controllable->session().transport_rolling()) {
195                         mark = true;
196                         when = _controllable->session().transport_frame();
197                 }
198
199                 _controllable->stop_touch (mark, when);
200         } else {
201                 _controllable->stop_touch (false, _controllable->session().transport_frame());
202         }
203 }
204
205 void
206 AutomationController::toggled ()
207 {
208         ArdourButton* but = dynamic_cast<ArdourButton*>(_widget);
209         const AutoState as = _controllable->automation_state ();
210         const double where = _controllable->session ().audible_frame ();
211         const bool to_list = _controllable->list () && _controllable->session().transport_rolling () && (as == Touch || as == Write);
212
213         if (but) {
214                 if (to_list) {
215                         if (as == Touch && _controllable->list ()->in_new_write_pass ()) {
216                                 _controllable->alist ()->start_write_pass (where);
217                         }
218                         _controllable->list ()->set_in_write_pass (true, false, where);
219                 }
220                 /* set to opposite value.*/
221                 _controllable->set_double (but->get_active () ? 0.0 : 1.0, where, to_list);
222
223                 const bool active = _controllable->get_double (to_list, where) >= 0.5;
224                 if (active && !but->get_active ()) {
225                         _adjustment->set_value (1.0);
226                         but->set_active (true);
227                 } else if (!active && but->get_active ()) {
228                         _adjustment->set_value (0.0);
229                         but->set_active (false);
230                 }
231         }
232 }
233
234 static double
235 midi_note_to_hz(int note)
236 {
237         const double tuning = 440.0;
238         return tuning * pow(2, (note - 69.0) / 12.0);
239 }
240
241 static double
242 clamp(double val, double min, double max)
243 {
244         if (val < min) {
245                 return min;
246         } else if (val > max) {
247                 return max;
248         }
249         return val;
250 }
251
252 void
253 AutomationController::run_note_select_dialog()
254 {
255         const ARDOUR::ParameterDescriptor& desc   = _controllable->desc();
256         NoteSelectDialog*                  dialog = new NoteSelectDialog();
257         if (dialog->run() == Gtk::RESPONSE_ACCEPT) {
258                 const double value = ((_controllable->desc().unit == ARDOUR::ParameterDescriptor::HZ)
259                                       ? midi_note_to_hz(dialog->note_number())
260                                       : dialog->note_number());
261                 _controllable->set_value(clamp(value, desc.lower, desc.upper), Controllable::NoGroup);
262         }
263         delete dialog;
264 }
265
266 void
267 AutomationController::set_freq_beats(double beats)
268 {
269         const ARDOUR::ParameterDescriptor& desc    = _controllable->desc();
270         const ARDOUR::Session&             session = _controllable->session();
271         const framepos_t                   pos     = session.transport_frame();
272         const ARDOUR::Tempo&               tempo   = session.tempo_map().tempo_at_frame (pos);
273         const double                       bpm     = tempo.beats_per_minute();
274         const double                       bps     = bpm / 60.0;
275         const double                       freq    = bps / beats;
276         _controllable->set_value(clamp(freq, desc.lower, desc.upper), Controllable::NoGroup);
277 }
278
279 void
280 AutomationController::set_ratio(double ratio)
281 {
282         const ARDOUR::ParameterDescriptor& desc  = _controllable->desc();
283         const double                       value = _controllable->get_value() * ratio;
284         _controllable->set_value(clamp(value, desc.lower, desc.upper), Controllable::NoGroup);
285 }
286
287 bool
288 AutomationController::on_button_release(GdkEventButton* ev)
289 {
290         using namespace Gtk::Menu_Helpers;
291
292         if (ev->button != 3) {
293                 return false;
294         }
295
296         const ARDOUR::ParameterDescriptor& desc = _controllable->desc();
297         if (desc.unit == ARDOUR::ParameterDescriptor::MIDI_NOTE) {
298                 Gtk::Menu* menu  = manage(new Menu());
299                 MenuList&  items = menu->items();
300                 items.push_back(MenuElem(_("Select Note..."),
301                                          sigc::mem_fun(*this, &AutomationController::run_note_select_dialog)));
302                 menu->popup(1, ev->time);
303                 return true;
304         } else if (desc.unit == ARDOUR::ParameterDescriptor::HZ) {
305                 Gtk::Menu* menu  = manage(new Menu());
306                 MenuList&  items = menu->items();
307                 items.push_back(MenuElem(_("Halve"),
308                                          sigc::bind(sigc::mem_fun(*this, &AutomationController::set_ratio),
309                                                     0.5)));
310                 items.push_back(MenuElem(_("Double"),
311                                          sigc::bind(sigc::mem_fun(*this, &AutomationController::set_ratio),
312                                                     2.0)));
313                 const bool is_audible = desc.upper > 40.0;
314                 const bool is_low     = desc.lower < 1.0;
315                 if (is_audible) {
316                         items.push_back(MenuElem(_("Select Note..."),
317                                                  sigc::mem_fun(*this, &AutomationController::run_note_select_dialog)));
318                 }
319                 if (is_low) {
320                         for (int beats = 1; beats <= 16; ++beats) {
321                                 items.push_back(MenuElem (string_compose(P_("Set to %1 beat", "Set to %1 beats", beats), beats),
322                                                          sigc::bind(sigc::mem_fun(*this, &AutomationController::set_freq_beats),
323                                                                     (double)beats)));
324                         }
325                 }
326                 menu->popup(1, ev->time);
327                 return true;
328         }
329
330         return false;
331 }
332
333 void
334 AutomationController::value_changed ()
335 {
336         Gtkmm2ext::UI::instance()->call_slot (invalidator (*this), boost::bind (&AutomationController::display_effective_value, this));
337 }
338
339 /** Stop updating our value from our controllable */
340 void
341 AutomationController::stop_updating ()
342 {
343         _screen_update_connection.disconnect ();
344 }
345
346 void
347 AutomationController::disable_vertical_scroll ()
348 {
349         AutomationBarController* bar = dynamic_cast<AutomationBarController*>(_widget);
350         if (bar) {
351                 bar->set_tweaks (
352                         Gtkmm2ext::PixFader::Tweaks(bar->tweaks() |
353                                                     Gtkmm2ext::PixFader::NoVerticalScroll));
354         }
355 }