Check for minimum priority.
[terminatorX.git] / src / tX_engine.cc
1 /*
2     terminatorX - realtime audio scratching software
3     Copyright (C) 1999-2006  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 #include <sched.h>
54 #include "tX_capabilities.h"
55
56 #include <sys/time.h>
57 #include <sys/resource.h>
58
59 tX_engine *tX_engine::engine=NULL;
60
61 tX_engine *tX_engine::get_instance() {
62         if (!engine) {
63                 engine=new tX_engine();
64         }
65         
66         return engine;
67 }
68
69 void tX_engine::set_grab_request() {
70         grab_request=true;
71 }
72
73 int16_t* tX_engine::render_cycle() {
74         /* Checking whether to grab or not  */
75         if (grab_request!=grab_active) {
76                 if (grab_request) {
77                         /* Activating grab... */
78                         int result=mouse->grab(); 
79                         if (result!=0) {
80                                 tX_error("tX_engine::render_cycle(): failed to grab mouse - error %i", result);
81                                 grab_active=false;
82                                 /* Reseting grab_request, too - doesn't help keeping it, does it ? ;) */
83                                 grab_request=false;
84                                 mouse->ungrab();
85                                 grab_off();
86                         } else {
87                                 grab_active=true;
88                         }
89                 } else {
90                         /* Deactivating grab... */
91                         mouse->ungrab();
92                         grab_active=false;
93                         grab_off();
94                 }
95         }
96
97         /* Handling input events... */
98         if (grab_active) {
99                 if (mouse->check_event()) {
100                         /* If we're here the user pressed ESC */
101                         grab_request=false;
102                 }
103         }
104
105         /* Forward the sequencer... */
106         sequencer.step();
107
108         /* Render the next block... */
109         int16_t *data=vtt_class::render_all_turntables();
110         
111         /* Record the audio if necessary... */
112         if (is_recording()) tape->eat(data);
113         
114         /* check for GUI thread starvation */
115 //      if (cycles_ctr++ > 100) {
116 //              tX_error("engine detected overload condition.");
117 //              overload_error=true;
118 //              stop_flag=true;
119                 // give the audio thread some air to breathe
120                 // so it can acquire the mutex
121 //              usleep(500);
122 //      }
123         
124         return  data;
125 }
126
127 void tX_engine::loop() {
128         while (!thread_terminate) {
129                 /* Waiting for the trigger */
130                 pthread_mutex_lock(&start);
131                 reset_cycles_ctr();
132                 
133 #ifdef USE_SCHEDULER
134                 pid_t pid=getpid();
135                 struct sched_param parm;
136                         
137                 if (globals.use_realtime && (globals.audiodevice_type!=JACK)) {
138                         sched_getparam(pid, &parm);
139                         parm.sched_priority=sched_get_priority_max(SCHED_FIFO);
140                                                 
141                         if (pthread_setschedparam(pthread_self(), SCHED_FIFO, &parm)) {
142                                 tX_msg("loop(): failed to set realtime priority.");
143                         } else {
144                                 tX_debug("loop(): set SCHED_FIFO.");
145                         }
146                 } else {
147                         sched_getparam(pid, &parm);
148                         parm.sched_priority=sched_get_priority_max(SCHED_OTHER);
149                                                 
150                         if (pthread_setschedparam(pthread_self(), SCHED_OTHER, &parm)) {
151                                 tX_msg("loop(): failed to set non-realtime priority.");
152                         } else {
153                                 tX_debug("loop(): set SCHED_OTHER.");
154                         }                       
155                 }
156 #endif          
157                 loop_is_active=true;
158                 pthread_mutex_unlock(&start);
159
160                 if (!stop_flag) device->start(); // Hand flow control over to the device
161
162                 // in case we got kicked out by jack we might have
163                 // to kill the mouse grab
164                 if (grab_active) {
165                         mouse->ungrab();
166                         grab_active=false;
167                         grab_off();
168                 }
169                 
170                 if (!stop_flag) {
171                         runtime_error=true;
172                         usleep(100);
173                 }
174                 /* Stopping engine... */
175                 loop_is_active=false;
176         }
177 }
178
179 void *engine_thread_entry(void *engine_void) {
180         tX_engine *engine=(tX_engine*) engine_void;
181         
182 #ifdef USE_SCHEDULER
183         pid_t pid=getpid();
184         struct sched_param parm;
185
186         if (globals.use_realtime) {
187                 sched_getparam(pid, &parm);
188                 parm.sched_priority=sched_get_priority_max(SCHED_FIFO);
189                                         
190                 if (pthread_setschedparam(pthread_self(), SCHED_FIFO, &parm)) {
191                         // we failed to get max prio, let see whether we can get a little less
192                         bool success = false;
193                         
194                         for (int i = parm.__sched_priority; i >= sched_get_priority_min(SCHED_FIFO); i--) {
195                                 parm.__sched_priority = i;
196                                 
197                                 if (!pthread_setschedparam(pthread_self(), SCHED_FIFO, &parm)) {
198                                         success = true;
199                                         break;
200                                 }
201                         }
202                         
203                         if (success) {
204                                 tX_msg("engine_thread_entry(): set SCHED_FIFO with priority %i.", parm.__sched_priority);
205                         } else {
206                                 tX_warning("engine_thread_entry(): failed to set realtime priority.");
207                         }
208                 } else {
209                         tX_debug("engine_thread_entry(): set SCHED_FIFO with maximum priority.");
210                 }
211         }
212 #endif //USE_SCHEDULER
213                 
214 #ifdef USE_JACK
215         /* Create the client now, so the user has something to connect to. */
216         tX_jack_client::get_instance();
217 #endif  
218         
219         engine->loop();
220         tX_debug("engine_thread_entry() engine thread terminating.");
221         pthread_exit(NULL);
222 }
223
224 tX_engine :: tX_engine() {
225         int result;
226         
227         pthread_mutex_init(&start, NULL);
228         pthread_mutex_lock(&start);
229         thread_terminate=false;
230         
231         result=pthread_create(&thread, NULL, engine_thread_entry, (void *) this);
232         
233         if (result!=0) {
234                 tX_error("tX_engine() - Failed to create engine thread. Errno is %i.", errno);
235                 exit(1);
236         }
237         
238         mouse=new tx_mouse();
239 #ifdef USE_ALSA_MIDI_IN 
240         midi=new tX_midiin();
241 #endif  
242         tape=new tx_tapedeck();
243
244         device=NULL;
245         recording=false;
246         recording_request=false;
247         loop_is_active=false;
248         grab_request=false;
249         grab_active=false;
250 }
251
252 void tX_engine :: set_recording_request (bool recording) {
253         this->recording_request=recording;
254 }
255
256 tX_engine_error tX_engine :: run() {
257         list <vtt_class *> :: iterator vtt;
258         
259         runtime_error=false;
260         overload_error=false;
261         
262         if (loop_is_active) return ERROR_BUSY;
263         
264         switch (globals.audiodevice_type) {
265 #ifdef USE_OSS  
266                 case OSS:
267                         device=new tX_audiodevice_oss(); 
268                 break;
269 #endif                  
270
271 #ifdef USE_ALSA                 
272                 case ALSA:
273                         device=new tX_audiodevice_alsa(); 
274                 break;
275 #endif
276
277 #ifdef USE_JACK
278                 case JACK:
279                         device=new tX_audiodevice_jack();
280                 break;
281 #endif
282                 
283                 default:
284                         device=NULL; return ERROR_AUDIO;
285         }
286         
287         if (device->open()) {
288                 if (device->get_is_open()) device->close();
289                 delete device;
290                 device=NULL;            
291                 return ERROR_AUDIO;
292         }       
293
294         vtt_class::set_sample_rate(device->get_sample_rate());
295         
296         if (recording_request) {
297                 if (tape->start_record(globals.record_filename, vtt_class::get_mix_buffer_size(), device->get_sample_rate())) {
298                         device->close();
299                         delete device;
300                         device=NULL;                    
301                         return ERROR_TAPE;                      
302                 } else {
303                         recording=true;
304                 }
305         }
306         
307         for (vtt=vtt_class::main_list.begin(); vtt!=vtt_class::main_list.end(); vtt++) {
308                 (*vtt)->sync_countdown=0;
309                 if ((*vtt)->autotrigger) (*vtt)->trigger();
310         }
311
312         sequencer.forward_to_start_timestamp(1);        
313         stop_flag=false;
314         /* Trigger the engine thread... */
315         pthread_mutex_unlock(&start);
316         
317         return NO_ERROR;
318 }
319
320 void tX_engine :: stop() {
321         list <vtt_class *> :: iterator vtt;
322         
323         if (!loop_is_active) {
324                 tX_error("tX_engine::stop() - but loop's not running?");
325         }
326         
327         pthread_mutex_lock(&start);
328         stop_flag=true;
329         
330         tX_debug("tX_engine::stop() - waiting for loop to stop.");
331         
332         while (loop_is_active) {
333                 /* Due to gtk+ signal handling this can cause a deadlock
334                    on the seqpars' update list. So we need to handle events
335                    while waiting...                     
336                 */
337                 while (gtk_events_pending()) gtk_main_iteration();
338                 usleep(50);
339         }
340         
341         tX_debug("tX_engine::stop() - loop has stopped.");
342
343         if (device->get_is_open()) device->close();
344         delete device;
345         device=NULL;
346         
347         for (vtt=vtt_class::main_list.begin(); vtt!=vtt_class::main_list.end(); vtt++) {
348                 (*vtt)->stop();
349                 (*vtt)->ec_clear_buffer();
350         }
351         
352         if (is_recording()) tape->stop_record();
353         recording=false;
354 }
355
356 tX_engine :: ~tX_engine() {
357         void *dummy;
358                 
359         thread_terminate=true;
360         stop_flag=true;
361         pthread_mutex_unlock(&start);
362         tX_debug("~tX_engine() - Waiting for engine thread to terminate.");
363         pthread_join(thread, &dummy);   
364         
365         delete mouse;
366 #ifdef USE_ALSA_MIDI_IN         
367         delete midi;
368 #endif  
369         delete tape;    
370 }