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