05707cd16957eeffe537b9fe68f9dc55a5daa446
[terminatorX.git] / src / tX_engine.cc
1 /*
2     terminatorX - realtime audio scratching software
3     Copyright (C) 1999-2003  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, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  
19     File: tX_engine.c
20  
21     Description: Contains the code that does the real "Scratching
22                  business": XInput, DGA, Mouse and Keyboardgrabbing
23                  etc.
24
25     02 Jun 1999: Implemented high-priority/rt-FIFO-Scheduling use for
26                  engine-thread.
27                  
28     04 Jun 1999: Changed warp-feature behaviour: still connected to
29                  mouse-speed (should be changed to maybe) but now
30                  depends on sample size -> you can warp through all
31                  samples with the same mouse-distance.
32                  
33     12 Aug 2002: Complete rewrite - tX_engine is now a class and the thread
34         is created on startup and kept alive until termination
35 */    
36
37 #include <config.h>
38 #include "tX_types.h"
39 #include "tX_engine.h"
40 #include "tX_audiodevice.h"
41 #include "tX_mouse.h"
42 #include "tX_vtt.h"
43 #include <pthread.h>
44 #include <gtk/gtk.h>
45 #include <gdk/gdkprivate.h>
46 #include "tX_mastergui.h"
47 #include "tX_global.h"
48 #include "tX_tape.h"
49 #include "tX_widget.h"
50 #include <config.h>
51 #include "tX_sequencer.h"
52 #include <errno.h>
53
54 #include <sys/time.h>
55 #include <sys/resource.h>
56
57 tX_engine *tX_engine :: engine=NULL;
58
59 tX_engine *tX_engine :: get_instance() {
60         if (!engine) {
61                 engine=new tX_engine();
62         }
63         
64         return engine;
65 }
66
67 void tX_engine :: set_grab_request() {
68         grab_request=true;
69 }
70
71 int16_t* tX_engine :: render_cycle() {
72         /* Checking whether to grab or not  */
73         if (grab_request!=grab_active) {
74                 if (grab_request) {
75                         /* Activating grab... */
76                         int result=mouse->grab(); 
77                         if (result!=0) {
78                                 tX_error("tX_engine::loop(): failed to grab mouse - error %i", result);
79                                 grab_active=false;
80                                 /* Reseting grab_request, too - doesn't help keeping it, does it ? ;) */
81                                 grab_request=false;
82                                 mouse->ungrab();
83                                 grab_off();
84                         } else {
85                                 grab_active=true;
86                         }
87                 } else {
88                         /* Deactivating grab... */
89                         mouse->ungrab();
90                         grab_active=false;
91                         grab_off(); // for the mastergui this is...
92                 }
93         }
94
95         /* Handling input events... */
96         if (grab_active) {
97                 if (mouse->check_event()) {
98                         /* If we're here the user pressed ESC */
99                         grab_request=false;
100                 }
101         }
102
103         /* Forward the sequencer... */
104         sequencer.step();
105
106         /* Render the next block... */
107         int16_t *data=vtt_class::render_all_turntables();
108         
109         /* Record the audio if necessary... */
110         if (is_recording()) tape->eat(data);
111         
112         return  data;
113 }
114
115 void tX_engine :: loop() {
116         while (!thread_terminate) {
117                 /* Waiting for the trigger */
118                 pthread_mutex_lock(&start);
119                 loop_is_active=true;
120                 pthread_mutex_unlock(&start);
121
122                 if (!stop_flag) device->start(); // Hand flow control over to the device
123                 
124                 /* Stopping engine... */
125                 loop_is_active=false;
126         }
127 }
128
129 void *engine_thread_entry(void *engine_void) {
130         tX_engine *engine=(tX_engine*) engine_void;
131         int result;
132         
133         /* Dropping root privileges for the engine thread - if running suid. */
134         
135         if ((!geteuid()) && (getuid() != geteuid())) {
136                 
137 #ifndef ALLOW_SUID_ROOT
138                 tX_error("This binary doesn't support running suid-root.");
139                 tX_error("Reconfigure with --enable-suidroot if you really want that.");
140                 exit(-1);
141 #endif          
142                 tX_debug("engine_thread_entry() - Running suid root - dropping privileges.");
143                 
144                 result=setuid(getuid());
145                 
146                 if (result!=0) {
147                         tX_error("engine_thread_entry() - Failed to drop root privileges.");
148                         exit(2);
149                 }
150         }
151         
152 #ifdef USE_JACK
153         /* Create the client now, so the user has something to connect to. */
154         tX_jack_client::get_instance();
155 #endif  
156         
157         engine->loop();
158         
159         tX_debug("engine_thread_entry() - Engine thread terminating.");
160         
161         pthread_exit(NULL);
162 }
163
164 tX_engine :: tX_engine() {
165         int result;
166         
167         pthread_mutex_init(&start, NULL);
168         pthread_mutex_lock(&start);
169         thread_terminate=false;
170         
171         /* Creating the actual engine thread.. */
172 #ifdef USE_SCHEDULER    
173         if (!geteuid()) {
174                 pthread_attr_t pattr;
175                 struct sched_param sparm;
176                 
177                 tX_debug("tX_engine() - Have root privileges - using SCHED_FIFO.");
178                 
179                 pthread_attr_init(&pattr);
180                 pthread_attr_setdetachstate(&pattr, PTHREAD_CREATE_JOINABLE);
181                 pthread_attr_setschedpolicy(&pattr, SCHED_FIFO);
182         
183                 sched_getparam(getpid(), &sparm);
184                 sparm.sched_priority=sched_get_priority_max(SCHED_FIFO);
185         
186                 pthread_attr_setschedparam(&pattr, &sparm);
187                 pthread_attr_setinheritsched(&pattr, PTHREAD_EXPLICIT_SCHED);
188                 pthread_attr_setscope(&pattr, PTHREAD_SCOPE_SYSTEM);
189                 
190                 result=pthread_create(&thread, &pattr, engine_thread_entry, (void *) this);
191         } else {
192                 tX_debug("tX_engine() - Lacking root privileges - no realtime scheduling.");
193 #endif          
194                 result=pthread_create(&thread, NULL, engine_thread_entry, (void *) this);
195 #ifdef USE_SCHEDULER            
196         }
197 #endif
198         
199         if (result!=0) {
200                 tX_error("tX_engine() - Failed to create engine thread. Errno is %i.", errno);
201                 exit(1);
202         }
203         
204         /* Dropping root privileges for the main thread - if running suid. */
205         
206         if ((!geteuid()) && (getuid() != geteuid())) {
207                 tX_debug("tX_engine() - Running suid root - dropping privileges.");
208                 
209                 result=setuid(getuid());
210                 
211                 if (result!=0) {
212                         tX_error("tX_engine() - Failed to drop root privileges.");
213                         exit(2);
214                 }
215         }
216         
217         mouse=new tx_mouse();
218 #ifdef USE_ALSA_MIDI_IN 
219         midi=new tX_midiin();
220 #endif  
221         tape=new tx_tapedeck();
222
223         device=NULL;
224         recording=false;
225         recording_request=false;
226         loop_is_active=false;
227         grab_request=false;
228         grab_active=false;
229 }
230
231 void tX_engine :: set_recording_request (bool recording) {
232         this->recording_request=recording;
233 }
234
235 tX_engine_error tX_engine :: run() {
236         list <vtt_class *> :: iterator vtt;
237         
238         if (loop_is_active) return ERROR_BUSY;
239         
240         switch (globals.audiodevice_type) {
241 #ifdef USE_OSS  
242                 case OSS:
243                         device=new tX_audiodevice_oss(); 
244                 break;
245 #endif                  
246
247 #ifdef USE_ALSA                 
248                 case ALSA:
249                         device=new tX_audiodevice_alsa(); 
250                 break;
251 #endif
252
253 #ifdef USE_JACK
254                 case JACK:
255                         device=new tX_audiodevice_jack();
256                 break;
257 #endif
258                 
259                 default:
260                         device=NULL; return ERROR_AUDIO;
261         }
262         
263         if (device->open()) {
264                 if (device->get_is_open()) device->close();
265                 delete device;
266                 device=NULL;            
267                 return ERROR_AUDIO;
268         }       
269
270         vtt_class::set_sample_rate(device->get_sample_rate());
271         
272         if (recording_request) {
273                 if (tape->start_record(globals.record_filename, vtt_class::get_mix_buffer_size(), device->get_sample_rate())) {
274                         device->close();
275                         delete device;
276                         device=NULL;                    
277                         return ERROR_TAPE;                      
278                 } else {
279                         recording=true;
280                 }
281         }
282         
283         for (vtt=vtt_class::main_list.begin(); vtt!=vtt_class::main_list.end(); vtt++) {
284                 (*vtt)->sync_countdown=0;
285                 if ((*vtt)->autotrigger) (*vtt)->trigger();
286         }
287
288         sequencer.forward_to_start_timestamp(1);        
289         stop_flag=false;
290         /* Trigger the engine thread... */
291         pthread_mutex_unlock(&start);
292         
293         return NO_ERROR;
294 }
295
296 void tX_engine :: stop() {
297         list <vtt_class *> :: iterator vtt;
298         
299         if (!loop_is_active) {
300                 tX_error("tX_engine::stop() - but loop's not running?");
301         }
302         
303         pthread_mutex_lock(&start);
304         stop_flag=true;
305         
306         tX_debug("tX_engine::stop() - waiting for loop to stop.");
307         
308         while (loop_is_active) {
309                 /* Due to gtk+ signal handling this can cause a deadlock
310                    on the seqpars' update list. So we need to handle events
311                    while waiting...                     
312                 */
313                 while (gtk_events_pending()) gtk_main_iteration();
314                 usleep(50);
315         }
316         
317         tX_debug("tX_engine::stop() - loop has stopped.");
318
319         if (device->get_is_open()) device->close();
320         delete device;
321         device=NULL;
322         
323         for (vtt=vtt_class::main_list.begin(); vtt!=vtt_class::main_list.end(); vtt++) {
324                 (*vtt)->stop();
325                 (*vtt)->ec_clear_buffer();
326         }
327         
328         if (is_recording()) tape->stop_record();
329         recording=false;
330 }
331
332 tX_engine :: ~tX_engine() {
333         void *dummy;
334                 
335         thread_terminate=true;
336         stop_flag=true;
337         pthread_mutex_unlock(&start);
338         tX_debug("~tX_engine() - Waiting for engine thread to terminate.");
339         pthread_join(thread, &dummy);   
340         
341         delete mouse;
342 #ifdef USE_ALSA_MIDI_IN         
343         delete midi;
344 #endif  
345         delete tape;    
346 }