use a note tracker to resolve notes cut off during render by the end of the region
[ardour.git] / libs / ardour / automation_watch.cc
1 /*
2  * Copyright (C) 2012-2017 Paul Davis <paul@linuxaudiosystems.com>
3  * Copyright (C) 2015-2018 Robin Gareus <robin@gareus.org>
4  * Copyright (C) 2015 Ben Loftis <ben@harrisonconsoles.com>
5  * Copyright (C) 2015 Nick Mainsbridge <mainsbridge@gmail.com>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include <iostream>
23
24 #include <glibmm/timer.h>
25
26 #include "pbd/compose.h"
27 #include "pbd/pthread_utils.h"
28
29 #include "ardour/audioengine.h"
30 #include "ardour/automation_control.h"
31 #include "ardour/automation_watch.h"
32 #include "ardour/debug.h"
33 #include "ardour/session.h"
34
35 using namespace ARDOUR;
36 using namespace PBD;
37
38 AutomationWatch* AutomationWatch::_instance = 0;
39
40 AutomationWatch&
41 AutomationWatch::instance ()
42 {
43         if (_instance == 0) {
44                 _instance = new AutomationWatch;
45         }
46         return *_instance;
47 }
48
49 AutomationWatch::AutomationWatch ()
50         : _thread (0)
51         , _last_time (0)
52         , _run_thread (false)
53 {
54
55 }
56
57 AutomationWatch::~AutomationWatch ()
58 {
59         if (_thread) {
60                 _run_thread = false;
61                 _thread->join ();
62                 _thread = 0;
63         }
64
65         Glib::Threads::Mutex::Lock lm (automation_watch_lock);
66         automation_watches.clear ();
67         automation_connections.clear ();
68 }
69
70 void
71 AutomationWatch::add_automation_watch (boost::shared_ptr<AutomationControl> ac)
72 {
73         Glib::Threads::Mutex::Lock lm (automation_watch_lock);
74         DEBUG_TRACE (DEBUG::Automation, string_compose ("now watching control %1 for automation, astate = %2\n", ac->name(), enum_2_string (ac->automation_state())));
75         std::pair<AutomationWatches::iterator, bool> r = automation_watches.insert (ac);
76
77         if (!r.second) {
78                 return;
79         }
80
81         /* if an automation control is added here while the transport is
82          * rolling, make sure that it knows that there is a write pass going
83          * on, rather than waiting for the transport to start.
84          */
85
86         if (_session && _session->transport_rolling() && ac->alist()->automation_write()) {
87                 DEBUG_TRACE (DEBUG::Automation, string_compose ("\ttransport is rolling @ %1, audible = %2so enter write pass\n",
88                                                                 _session->transport_speed(), _session->audible_sample()));
89                 /* add a guard point since we are already moving */
90                 ac->list()->set_in_write_pass (true, true, _session->audible_sample());
91         }
92
93         /* we can't store shared_ptr<Destructible> in connections because it
94          * creates reference cycles. we don't need to make the weak_ptr<>
95          * explicit here, but it helps to remind us what is going on.
96          */
97
98         boost::weak_ptr<AutomationControl> wac (ac);
99         ac->DropReferences.connect_same_thread (automation_connections[ac], boost::bind (&AutomationWatch::remove_weak_automation_watch, this, wac));
100 }
101
102 void
103 AutomationWatch::remove_weak_automation_watch (boost::weak_ptr<AutomationControl> wac)
104 {
105         boost::shared_ptr<AutomationControl> ac = wac.lock();
106
107         if (!ac) {
108                 return;
109         }
110
111         remove_automation_watch (ac);
112 }
113
114 void
115 AutomationWatch::remove_automation_watch (boost::shared_ptr<AutomationControl> ac)
116 {
117         Glib::Threads::Mutex::Lock lm (automation_watch_lock);
118         DEBUG_TRACE (DEBUG::Automation, string_compose ("remove control %1 from automation watch\n", ac->name()));
119         automation_watches.erase (ac);
120         automation_connections.erase (ac);
121         ac->list()->set_in_write_pass (false);
122 }
123
124 void
125 AutomationWatch::transport_stop_automation_watches (samplepos_t when)
126 {
127         DEBUG_TRACE (DEBUG::Automation, "clear all automation watches\n");
128
129         AutomationWatches tmp;
130
131         {
132                 Glib::Threads::Mutex::Lock lm (automation_watch_lock);
133                 /* copy automation watches */
134                 tmp = automation_watches;
135                 /* clear existing container so that each
136                    ::remove_automation_watch() call from
137                    AutomationControl::stop_touch() is faster.
138                 */
139
140                 automation_watches.clear ();
141                 automation_connections.clear ();
142         }
143
144         for (AutomationWatches::iterator i = tmp.begin(); i != tmp.end(); ++i) {
145                 (*i)->stop_touch (when);
146         }
147 }
148
149 gint
150 AutomationWatch::timer ()
151 {
152         if (!_session || !_session->transport_rolling()) {
153                 return TRUE;
154         }
155
156         {
157                 Glib::Threads::Mutex::Lock lm (automation_watch_lock);
158
159                 samplepos_t time = _session->audible_sample ();
160                 if (time > _last_time) {  //we only write automation in the forward direction; this fixes automation-recording in a loop
161                         for (AutomationWatches::iterator aw = automation_watches.begin(); aw != automation_watches.end(); ++aw) {
162                                 if ((*aw)->alist()->automation_write()) {
163                                         double val = (*aw)->user_double();
164                                         boost::shared_ptr<SlavableAutomationControl> sc = boost::dynamic_pointer_cast<SlavableAutomationControl> (*aw);
165                                         if (sc) {
166                                                 val = sc->reduce_by_masters (val, true);
167                                         }
168                                         (*aw)->list()->add (time, val, true);
169                                 }
170                         }
171                 } 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)
172                         for (AutomationWatches::iterator aw = automation_watches.begin(); aw != automation_watches.end(); ++aw) {
173                                 DEBUG_TRACE (DEBUG::Automation, string_compose ("%1: transport in rewind, speed %2, in write pass ? %3 writing ? %4\n",
174                                                                                 (*aw)->name(), _session->transport_speed(), _session->transport_rolling(),
175                                                                                 (*aw)->alist()->automation_write()));
176                                 (*aw)->list()->set_in_write_pass (false);
177                                 if ( (*aw)->alist()->automation_write() ) {
178                                         (*aw)->list()->set_in_write_pass (true, time);
179                                 }
180                         }
181                 }
182
183                 _last_time = time;
184         }
185
186         return TRUE;
187 }
188
189 void
190 AutomationWatch::thread ()
191 {
192         pbd_set_thread_priority (pthread_self(), PBD_SCHED_FIFO, AudioEngine::instance()->client_real_time_priority() - 3);
193         while (_run_thread) {
194                 Glib::usleep ((gulong) floor (Config->get_automation_interval_msecs() * 1000));
195                 timer ();
196         }
197 }
198
199 void
200 AutomationWatch::set_session (Session* s)
201 {
202         transport_connection.disconnect ();
203
204         if (_thread) {
205                 _run_thread = false;
206                 _thread->join ();
207                 _thread = 0;
208         }
209
210         SessionHandlePtr::set_session (s);
211
212         if (_session) {
213                 _run_thread = true;
214                 _thread = Glib::Threads::Thread::create (boost::bind (&AutomationWatch::thread, this));
215
216                 _session->TransportStateChange.connect_same_thread (transport_connection, boost::bind (&AutomationWatch::transport_state_change, this));
217         }
218 }
219
220 void
221 AutomationWatch::transport_state_change ()
222 {
223         if (!_session) {
224                 return;
225         }
226
227         bool rolling = _session->transport_rolling();
228
229         _last_time = _session->audible_sample ();
230
231         {
232                 Glib::Threads::Mutex::Lock lm (automation_watch_lock);
233
234                 for (AutomationWatches::iterator aw = automation_watches.begin(); aw != automation_watches.end(); ++aw) {
235                         DEBUG_TRACE (DEBUG::Automation, string_compose ("%1: transport state changed, speed %2, in write pass ? %3 writing ? %4\n",
236                                                                         (*aw)->name(), _session->transport_speed(), rolling,
237                                                                         (*aw)->alist()->automation_write()));
238                         if (rolling && (*aw)->alist()->automation_write()) {
239                                 (*aw)->list()->set_in_write_pass (true);
240                         } else {
241                                 (*aw)->list()->set_in_write_pass (false);
242                         }
243                 }
244         }
245 }