It's 2020...
[terminatorX.git] / src / tX_engine.cc
1 /*
2     terminatorX - realtime audio scratching software
3     Copyright (C) 1999-2020  Alexander K├Ânig
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, see <http://www.gnu.org/licenses/>.
17  
18     File: tX_engine.c
19  
20     Description: Contains the code that does the real "Scratching
21                  business": XInput, DGA, Mouse and Keyboardgrabbing
22                  etc.
23 */    
24
25 #include <config.h>
26 #include "tX_types.h"
27 #include "tX_engine.h"
28 #include "tX_audiodevice.h"
29 #include "tX_mouse.h"
30 #include "tX_vtt.h"
31 #include <pthread.h>
32 #include <gtk/gtk.h>
33 #include <gdk/gdkprivate.h>
34 #include "tX_mastergui.h"
35 #include "tX_global.h"
36 #include "tX_tape.h"
37 #include "tX_widget.h"
38 #include <config.h>
39 #include "tX_sequencer.h"
40 #include <errno.h>
41 #include <sched.h>
42 #include "tX_capabilities.h"
43
44 #include <sys/time.h>
45 #include <sys/resource.h>
46
47 tX_engine *tX_engine::engine=NULL;
48
49 tX_engine *tX_engine::get_instance() {
50         if (!engine) {
51                 engine=new tX_engine();
52         }
53         
54         return engine;
55 }
56
57 int16_t* tX_engine::render_cycle() {
58         /* Forward the sequencer... */
59         sequencer.step();
60
61         /* Render the next block... */
62         int16_t *data=vtt_class::render_all_turntables();
63         
64         /* Record the audio if necessary... */
65         if (is_recording()) tape->eat(data);
66         
67         return  data;
68 }
69
70 void tX_engine::loop() {
71         while (!thread_terminate) {
72                 /* Waiting for the trigger */
73                 pthread_mutex_lock(&start);
74                 reset_cycles_ctr();
75                 
76 #ifdef USE_SCHEDULER
77                 pid_t pid=getpid();
78                 struct sched_param parm;
79                         
80                 if (globals.use_realtime && (globals.audiodevice_type!=JACK)) {
81                         sched_getparam(pid, &parm);
82                         parm.sched_priority=sched_get_priority_max(SCHED_FIFO);
83                                                 
84                         if (pthread_setschedparam(pthread_self(), SCHED_FIFO, &parm)) {
85                                 tX_msg("loop(): failed to set realtime priority.");
86                         } else {
87                                 tX_debug("loop(): set SCHED_FIFO.");
88                         }
89                 } else {
90                         sched_getparam(pid, &parm);
91                         parm.sched_priority=sched_get_priority_max(SCHED_OTHER);
92                                                 
93                         if (pthread_setschedparam(pthread_self(), SCHED_OTHER, &parm)) {
94                                 tX_msg("loop(): failed to set non-realtime priority.");
95                         } else {
96                                 tX_debug("loop(): set SCHED_OTHER.");
97                         }                       
98                 }
99 #endif          
100                 loop_is_active=true;
101                 pthread_mutex_unlock(&start);
102
103                 if (!stop_flag) device->start(); // Hand flow control over to the device
104
105                 // in case we got kicked out by jack we might have
106                 // to kill the mouse grab
107 /*              if (grab_active) {
108                         mouse->ungrab();
109                         grab_active=false;
110                         grab_off();
111                 } */
112
113                 if (!stop_flag) {
114                         runtime_error=true;
115                         usleep(100);
116                 }
117                 /* Stopping engine... */
118                 loop_is_active=false;
119         }
120 }
121
122 void *engine_thread_entry(void *engine_void) {
123         tX_engine *engine=(tX_engine*) engine_void;
124         
125 #ifdef USE_SCHEDULER
126         pid_t pid=getpid();
127         struct sched_param parm;
128
129         if (globals.use_realtime) {
130                 sched_getparam(pid, &parm);
131                 parm.sched_priority=sched_get_priority_max(SCHED_FIFO);
132                                         
133                 if (pthread_setschedparam(pthread_self(), SCHED_FIFO, &parm)) {
134                         // we failed to get max prio, let see whether we can get a little less
135                         bool success = false;
136                         
137                         for (int i = parm.__sched_priority; i >= sched_get_priority_min(SCHED_FIFO); i--) {
138                                 parm.__sched_priority = i;
139                                 
140                                 if (!pthread_setschedparam(pthread_self(), SCHED_FIFO, &parm)) {
141                                         success = true;
142                                         break;
143                                 }
144                         }
145                         
146                         if (success) {
147                                 tX_msg("engine_thread_entry(): set SCHED_FIFO with priority %i.", parm.__sched_priority);
148                         } else {
149                                 tX_warning("engine_thread_entry(): failed to set realtime priority.");
150                         }
151                 } else {
152                         tX_debug("engine_thread_entry(): set SCHED_FIFO with maximum priority.");
153                 }
154         }
155 #endif //USE_SCHEDULER
156                 
157 #ifdef USE_JACK
158         /* Create the client now, so the user has something to connect to. */
159         tX_jack_client::get_instance();
160 #endif  
161         
162         engine->loop();
163         tX_debug("engine_thread_entry() engine thread terminating.");
164         pthread_exit(NULL);
165 }
166
167 tX_engine :: tX_engine() {
168         int result;
169         
170         pthread_mutex_init(&start, NULL);
171         pthread_mutex_lock(&start);
172         thread_terminate=false;
173         
174         result=pthread_create(&thread, NULL, engine_thread_entry, (void *) this);
175         
176         if (result!=0) {
177                 tX_error("tX_engine() - Failed to create engine thread. Errno is %i.", errno);
178                 exit(1);
179         }
180         
181 #ifdef USE_ALSA_MIDI_IN 
182         midi=new tX_midiin();
183 #endif  
184         tape=new tx_tapedeck();
185
186         device=NULL;
187         recording=false;
188         recording_request=false;
189         loop_is_active=false;
190 }
191
192 void tX_engine :: set_recording_request (bool recording) {
193         this->recording_request=recording;
194 }
195
196 tX_engine_error tX_engine :: run() {
197         list <vtt_class *> :: iterator vtt;
198         
199         runtime_error=false;
200         overload_error=false;
201         
202         if (loop_is_active) return ERROR_BUSY;
203         
204         switch (globals.audiodevice_type) {
205 #ifdef USE_OSS  
206                 case OSS:
207                         device=new tX_audiodevice_oss(); 
208                         break;
209 #endif                  
210
211 #ifdef USE_ALSA                 
212                 case ALSA:
213                         device=new tX_audiodevice_alsa(); 
214                         break;
215 #endif
216
217 #ifdef USE_JACK
218                 case JACK:
219                         device=new tX_audiodevice_jack();
220                         break;
221 #endif
222
223 #ifdef USE_PULSE
224                 case PULSE:
225                         device=new tX_audiodevice_pulse();
226                         break;
227 #endif
228                 
229                 default:
230                         device=NULL; return ERROR_BACKEND;
231         }
232         
233         if (device->open()) {
234                 if (device->get_is_open()) device->close();
235                 delete device;
236                 device=NULL;            
237                 return ERROR_AUDIO;
238         }       
239
240         vtt_class::set_sample_rate(device->get_sample_rate());
241         
242         if (recording_request) {
243                 if (tape->start_record(globals.record_filename, vtt_class::get_mix_buffer_size(), device->get_sample_rate())) {
244                         device->close();
245                         delete device;
246                         device=NULL;                    
247                         return ERROR_TAPE;                      
248                 } else {
249                         recording=true;
250                 }
251         }
252         
253         for (vtt=vtt_class::main_list.begin(); vtt!=vtt_class::main_list.end(); vtt++) {
254                 (*vtt)->sync_countdown=0;
255                 if ((*vtt)->autotrigger) (*vtt)->trigger();
256         }
257
258         sequencer.forward_to_start_timestamp(1);        
259         stop_flag=false;
260         /* Trigger the engine thread... */
261         pthread_mutex_unlock(&start);
262         
263         return NO_ERROR;
264 }
265
266 void tX_engine :: stop() {
267         list <vtt_class *> :: iterator vtt;
268         
269         if (!loop_is_active) {
270                 tX_error("tX_engine::stop() - but loop's not running?");
271         }
272         
273         pthread_mutex_lock(&start);
274         stop_flag=true;
275         
276         tX_debug("tX_engine::stop() - waiting for loop to stop.");
277         
278         while (loop_is_active) {
279                 /* Due to gtk+ signal handling this can cause a deadlock
280                    on the seqpars' update list. So we need to handle events
281                    while waiting...                     
282                 */
283                 while (gtk_events_pending()) gtk_main_iteration();
284                 usleep(50);
285         }
286         
287         tX_debug("tX_engine::stop() - loop has stopped.");
288
289         if (device->get_is_open()) device->close();
290         delete device;
291         device=NULL;
292         
293         for (vtt=vtt_class::main_list.begin(); vtt!=vtt_class::main_list.end(); vtt++) {
294                 (*vtt)->stop();
295                 (*vtt)->ec_clear_buffer();
296         }
297         
298         if (is_recording()) tape->stop_record();
299         recording=false;
300 }
301
302 tX_engine :: ~tX_engine() {
303         void *dummy;
304                 
305         thread_terminate=true;
306         stop_flag=true;
307         pthread_mutex_unlock(&start);
308         tX_debug("~tX_engine() - Waiting for engine thread to terminate.");
309         pthread_join(thread, &dummy);   
310         
311 #ifdef USE_ALSA_MIDI_IN         
312         delete midi;
313 #endif  
314         delete tape;    
315 }