Fix AU preset handling
[ardour.git] / libs / ardour / automation_watch.cc
1 /*
2     Copyright (C) 2012 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
20 #include <iostream>
21
22 #include <glibmm/timer.h>
23
24 #include "pbd/compose.h"
25 #include "pbd/pthread_utils.h"
26
27 #include "ardour/audioengine.h"
28 #include "ardour/automation_control.h"
29 #include "ardour/automation_watch.h"
30 #include "ardour/debug.h"
31 #include "ardour/session.h"
32
33 using namespace ARDOUR;
34 using namespace PBD;
35
36 AutomationWatch* AutomationWatch::_instance = 0;
37
38 AutomationWatch&
39 AutomationWatch::instance ()
40 {
41         if (_instance == 0) {
42                 _instance = new AutomationWatch;
43         }
44         return *_instance;
45 }
46
47 AutomationWatch::AutomationWatch ()
48         : _thread (0)
49         , _last_time (0)
50         , _run_thread (false)
51 {
52
53 }
54
55 AutomationWatch::~AutomationWatch ()
56 {
57         if (_thread) {
58                 _run_thread = false;
59                 _thread->join ();
60                 _thread = 0;
61         }
62
63         Glib::Threads::Mutex::Lock lm (automation_watch_lock);
64         automation_watches.clear ();
65         automation_connections.clear ();
66 }
67
68 void
69 AutomationWatch::add_automation_watch (boost::shared_ptr<AutomationControl> ac)
70 {
71         Glib::Threads::Mutex::Lock lm (automation_watch_lock);
72         DEBUG_TRACE (DEBUG::Automation, string_compose ("now watching control %1 for automation, astate = %2\n", ac->name(), enum_2_string (ac->automation_state())));
73         std::pair<AutomationWatches::iterator, bool> r = automation_watches.insert (ac);
74
75         if (!r.second) {
76                 return;
77         }
78
79         /* if an automation control is added here while the transport is
80          * rolling, make sure that it knows that there is a write pass going
81          * on, rather than waiting for the transport to start.
82          */
83
84         if (_session && _session->transport_rolling() && ac->alist()->automation_write()) {
85                 DEBUG_TRACE (DEBUG::Automation, string_compose ("\ttransport is rolling @ %1, audible = %2so enter write pass\n",
86                                                                 _session->transport_speed(), _session->audible_sample()));
87                 /* add a guard point since we are already moving */
88                 ac->list()->set_in_write_pass (true, true, _session->audible_sample());
89         }
90
91         /* we can't store shared_ptr<Destructible> in connections because it
92          * creates reference cycles. we don't need to make the weak_ptr<>
93          * explicit here, but it helps to remind us what is going on.
94          */
95
96         boost::weak_ptr<AutomationControl> wac (ac);
97         ac->DropReferences.connect_same_thread (automation_connections[ac], boost::bind (&AutomationWatch::remove_weak_automation_watch, this, wac));
98 }
99
100 void
101 AutomationWatch::remove_weak_automation_watch (boost::weak_ptr<AutomationControl> wac)
102 {
103         boost::shared_ptr<AutomationControl> ac = wac.lock();
104
105         if (!ac) {
106                 return;
107         }
108
109         remove_automation_watch (ac);
110 }
111
112 void
113 AutomationWatch::remove_automation_watch (boost::shared_ptr<AutomationControl> ac)
114 {
115         Glib::Threads::Mutex::Lock lm (automation_watch_lock);
116         DEBUG_TRACE (DEBUG::Automation, string_compose ("remove control %1 from automation watch\n", ac->name()));
117         automation_watches.erase (ac);
118         automation_connections.erase (ac);
119         ac->list()->set_in_write_pass (false);
120 }
121
122 void
123 AutomationWatch::transport_stop_automation_watches (samplepos_t when)
124 {
125         DEBUG_TRACE (DEBUG::Automation, "clear all automation watches\n");
126
127         AutomationWatches tmp;
128
129         {
130                 Glib::Threads::Mutex::Lock lm (automation_watch_lock);
131                 /* copy automation watches */
132                 tmp = automation_watches;
133                 /* clear existing container so that each
134                    ::remove_automation_watch() call from
135                    AutomationControl::stop_touch() is faster.
136                 */
137
138                 automation_watches.clear ();
139                 automation_connections.clear ();
140         }
141
142         for (AutomationWatches::iterator i = tmp.begin(); i != tmp.end(); ++i) {
143                 (*i)->stop_touch (when);
144         }
145 }
146
147 gint
148 AutomationWatch::timer ()
149 {
150         if (!_session || !_session->transport_rolling()) {
151                 return TRUE;
152         }
153
154         {
155                 Glib::Threads::Mutex::Lock lm (automation_watch_lock);
156
157                 samplepos_t time = _session->audible_sample ();
158                 if (time > _last_time) {  //we only write automation in the forward direction; this fixes automation-recording in a loop
159                         for (AutomationWatches::iterator aw = automation_watches.begin(); aw != automation_watches.end(); ++aw) {
160                                 if ((*aw)->alist()->automation_write()) {
161                                         double val = (*aw)->user_double();
162                                         boost::shared_ptr<SlavableAutomationControl> sc = boost::dynamic_pointer_cast<SlavableAutomationControl> (*aw);
163                                         if (sc) {
164                                                 val = sc->reduce_by_masters (val, true);
165                                         }
166                                         (*aw)->list()->add (time, val, true);
167                                 }
168                         }
169                 } else if (time != _last_time) {  //transport stopped or reversed.  stop the automation pass and start a new one (for bonus points, someday store the previous pass in an undo record)
170                         for (AutomationWatches::iterator aw = automation_watches.begin(); aw != automation_watches.end(); ++aw) {
171                                 DEBUG_TRACE (DEBUG::Automation, string_compose ("%1: transport in rewind, speed %2, in write pass ? %3 writing ? %4\n",
172                                                                                 (*aw)->name(), _session->transport_speed(), _session->transport_rolling(),
173                                                                                 (*aw)->alist()->automation_write()));
174                                 (*aw)->list()->set_in_write_pass (false);
175                                 if ( (*aw)->alist()->automation_write() ) {
176                                         (*aw)->list()->set_in_write_pass (true, time);
177                                 }
178                         }
179                 }
180
181                 _last_time = time;
182         }
183
184         return TRUE;
185 }
186
187 void
188 AutomationWatch::thread ()
189 {
190         pbd_set_thread_priority (pthread_self(), PBD_SCHED_FIFO, AudioEngine::instance()->client_real_time_priority() - 3);
191         while (_run_thread) {
192                 Glib::usleep ((gulong) floor (Config->get_automation_interval_msecs() * 1000));
193                 timer ();
194         }
195 }
196
197 void
198 AutomationWatch::set_session (Session* s)
199 {
200         transport_connection.disconnect ();
201
202         if (_thread) {
203                 _run_thread = false;
204                 _thread->join ();
205                 _thread = 0;
206         }
207
208         SessionHandlePtr::set_session (s);
209
210         if (_session) {
211                 _run_thread = true;
212                 _thread = Glib::Threads::Thread::create (boost::bind (&AutomationWatch::thread, this));
213
214                 _session->TransportStateChange.connect_same_thread (transport_connection, boost::bind (&AutomationWatch::transport_state_change, this));
215         }
216 }
217
218 void
219 AutomationWatch::transport_state_change ()
220 {
221         if (!_session) {
222                 return;
223         }
224
225         bool rolling = _session->transport_rolling();
226
227         _last_time = _session->audible_sample ();
228
229         {
230                 Glib::Threads::Mutex::Lock lm (automation_watch_lock);
231
232                 for (AutomationWatches::iterator aw = automation_watches.begin(); aw != automation_watches.end(); ++aw) {
233                         DEBUG_TRACE (DEBUG::Automation, string_compose ("%1: transport state changed, speed %2, in write pass ? %3 writing ? %4\n",
234                                                                         (*aw)->name(), _session->transport_speed(), rolling,
235                                                                         (*aw)->alist()->automation_write()));
236                         if (rolling && (*aw)->alist()->automation_write()) {
237                                 (*aw)->list()->set_in_write_pass (true);
238                         } else {
239                                 (*aw)->list()->set_in_write_pass (false);
240                         }
241                 }
242         }
243 }