Fixing a memory corruption - and changing the way tX_midiin works. Not
[terminatorX.git] / terminatorX / 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 void tX_engine :: loop_og() {
72         int16_t *temp=NULL;
73         int result;
74         
75         while (!thread_terminate) {
76                 /* Waiting for the trigger */
77                 pthread_mutex_lock(&start);
78                 loop_is_active=true;
79                 pthread_mutex_unlock(&start);
80
81                 /* Render first block */
82                 if (!stop_flag) {
83                         sequencer.step();
84                         temp=vtt_class::render_all_turntables();
85                 }
86                 
87                 while (!stop_flag) {
88                         /* Checking whether to grab or not  */
89                         if (grab_request!=grab_active) {
90                                 if (grab_request) {
91                                         /* Activating grab... */
92                                         result=mouse->grab(); 
93                                         if (result!=0) {
94                                                 tX_error("tX_engine::loop(): failed to grab mouse - error %i", result);
95                                                 grab_active=false;
96                                                 /* Reseting grab_request, too - doesn't help keeping it, does it ? ;) */
97                                                 grab_request=false;
98                                                 mouse->ungrab();
99                                                 grab_off();
100                                         } else {
101                                                 grab_active=true;
102                                         }
103                                 } else {
104                                         /* Deactivating grab... */
105                                         mouse->ungrab();
106                                         grab_active=false;
107                                         grab_off(); // for the mastergui this is...
108                                 }
109                         }
110
111                         /* Handling input events... */
112                         if (grab_active) {
113                                 if (mouse->check_event()) {
114                                         /* If we're here the user pressed ESC */
115                                         grab_request=false;
116                                 }
117                         }
118
119 #ifdef USE_ALSA_MIDI_IN                 
120                         if (midi->get_is_open()) midi->check_event();
121 #endif                  
122                 
123                         /* Playback the audio... */
124                         device->play(temp);
125                 
126                         /* Record the audio if necessary... */
127                         if (is_recording()) tape->eat(temp);
128                         
129                         /* Forward the sequencer... */
130                         sequencer.step();
131                         
132                         /* Render the next block... */
133                         temp=vtt_class::render_all_turntables();                                        
134                 }
135                 
136                 /* Stopping engine... */
137                 loop_is_active=false;
138         }
139 }
140
141 int16_t* tX_engine :: render_cycle() {
142         /* Checking whether to grab or not  */
143         if (grab_request!=grab_active) {
144                 if (grab_request) {
145                         /* Activating grab... */
146                         int result=mouse->grab(); 
147                         if (result!=0) {
148                                 tX_error("tX_engine::loop(): failed to grab mouse - error %i", result);
149                                 grab_active=false;
150                                 /* Reseting grab_request, too - doesn't help keeping it, does it ? ;) */
151                                 grab_request=false;
152                                 mouse->ungrab();
153                                 grab_off();
154                         } else {
155                                 grab_active=true;
156                         }
157                 } else {
158                         /* Deactivating grab... */
159                         mouse->ungrab();
160                         grab_active=false;
161                         grab_off(); // for the mastergui this is...
162                 }
163         }
164
165         /* Handling input events... */
166         if (grab_active) {
167                 if (mouse->check_event()) {
168                         /* If we're here the user pressed ESC */
169                         grab_request=false;
170                 }
171         }
172
173 #ifdef USE_ALSA_MIDI_IN                 
174 //      if (midi->get_is_open()) midi->check_event();
175 #endif                  
176         /* Forward the sequencer... */
177         sequencer.step();
178
179         /* Render the next block... */
180         int16_t *data=vtt_class::render_all_turntables();
181         
182         /* Record the audio if necessary... */
183         if (is_recording()) tape->eat(data);
184         
185         return  data;
186 }
187
188 void tX_engine :: loop() {
189         int16_t *temp=NULL;
190         int result;
191         
192         while (!thread_terminate) {
193                 /* Waiting for the trigger */
194                 pthread_mutex_lock(&start);
195                 loop_is_active=true;
196                 pthread_mutex_unlock(&start);
197
198                 if (!stop_flag) device->start(); // Hand flow control over to the device
199                 
200                 /* Stopping engine... */
201                 loop_is_active=false;
202         }
203 }
204
205
206 void *engine_thread_entry(void *engine_void) {
207         tX_engine *engine=(tX_engine*) engine_void;
208         int result;
209         
210         /* Dropping root privileges for the engine thread - if running suid. */
211         
212         if ((!geteuid()) && (getuid() != geteuid())) {
213                 tX_debug("engine_thread_entry() - Running suid root - dropping privileges.");
214                 
215                 result=setuid(getuid());
216                 
217                 if (result!=0) {
218                         tX_error("engine_thread_entry() - Failed to drop root privileges.");
219                         exit(2);
220                 }
221         }
222         
223         engine->loop();
224         
225         tX_debug("engine_thread_entry() - Engine thread terminating.");
226         
227         pthread_exit(NULL);
228 }
229
230 tX_engine :: tX_engine() {
231         int result;
232         
233         pthread_mutex_init(&start, NULL);
234         pthread_mutex_lock(&start);
235         thread_terminate=false;
236         
237         /* Creating the actual engine thread.. */
238 #ifdef USE_SCHEDULER    
239         if (!geteuid()) {
240                 pthread_attr_t pattr;
241                 struct sched_param sparm;
242                 
243                 tX_debug("tX_engine() - Have root privileges - using SCHED_FIFO.");
244                 
245                 pthread_attr_init(&pattr);
246                 pthread_attr_setdetachstate(&pattr, PTHREAD_CREATE_JOINABLE);
247                 pthread_attr_setschedpolicy(&pattr, SCHED_FIFO);
248         
249                 sched_getparam(getpid(), &sparm);
250                 sparm.sched_priority=sched_get_priority_max(SCHED_FIFO);
251         
252                 pthread_attr_setschedparam(&pattr, &sparm);
253                 pthread_attr_setinheritsched(&pattr, PTHREAD_EXPLICIT_SCHED);
254                 pthread_attr_setscope(&pattr, PTHREAD_SCOPE_SYSTEM);
255                 
256                 result=pthread_create(&thread, &pattr, engine_thread_entry, (void *) this);
257         } else {
258                 tX_debug("tX_engine() - Lacking root privileges - no realtime scheduling!");
259 #endif          
260                 result=pthread_create(&thread, NULL, engine_thread_entry, (void *) this);
261 #ifdef USE_SCHEDULER            
262         }
263 #endif
264         
265         if (result!=0) {
266                 tX_error("tX_engine() - Failed to create engine thread. Errno is %i.", errno);
267                 exit(1);
268         }
269         
270         /* Dropping root privileges for the main thread - if running suid. */
271         
272         if ((!geteuid()) && (getuid() != geteuid())) {
273                 tX_debug("tX_engine() - Running suid root - dropping privileges.");
274                 
275                 result=setuid(getuid());
276                 
277                 if (result!=0) {
278                         tX_error("tX_engine() - Failed to drop root privileges.");
279                         exit(2);
280                 }
281         }
282         
283         mouse=new tx_mouse();
284 #ifdef USE_ALSA_MIDI_IN 
285         midi=new tX_midiin();
286 #endif  
287         tape=new tx_tapedeck();
288
289         device=NULL;
290         recording=false;
291         recording_request=false;
292         loop_is_active=false;
293         grab_request=false;
294         grab_active=false;
295 }
296
297 void tX_engine :: set_recording_request (bool recording) {
298         this->recording_request=recording;
299 }
300
301 tX_engine_error tX_engine :: run() {
302         list <vtt_class *> :: iterator vtt;
303         
304         if (loop_is_active) return ERROR_BUSY;
305         
306         switch (globals.audiodevice_type) {
307 #ifdef USE_OSS  
308                 case OSS:
309                         puts("Using OSS output...");
310                         device=new tX_audiodevice_oss(); 
311                 break;
312 #endif                  
313
314 #ifdef USE_ALSA                 
315                 case ALSA:
316                         puts("Using ALSA output...");
317                         device=new tX_audiodevice_alsa(); 
318                 break;
319 #endif
320                 
321                 default:
322                         device=NULL; return ERROR_AUDIO;
323         }
324         
325         if (device->open()) {
326                 device->close();
327                 delete device;
328                 device=NULL;            
329                 return ERROR_AUDIO;
330         }       
331
332         vtt_class::set_sample_rate(device->get_sample_rate());
333         
334         if (recording_request) {
335                 if (tape->start_record(globals.record_filename, device->get_buffersize()*sizeof(int16_t), device->get_sample_rate())) {
336                         device->close();
337                         delete device;
338                         device=NULL;                    
339                         return ERROR_TAPE;                      
340                 } else {
341                         recording=true;
342                 }
343         }
344         
345         for (vtt=vtt_class::main_list.begin(); vtt!=vtt_class::main_list.end(); vtt++) {
346                 if ((*vtt)->autotrigger) (*vtt)->trigger();
347         }
348
349         sequencer.forward_to_start_timestamp(1);        
350         stop_flag=false;
351         /* Trigger the engine thread... */
352         pthread_mutex_unlock(&start);
353         
354         return NO_ERROR;
355 }
356
357 void tX_engine :: stop() {
358         list <vtt_class *> :: iterator vtt;
359         
360         if (!loop_is_active) {
361                 tX_error("tX_engine::stop() - but loop's not running?");
362         }
363         
364         pthread_mutex_lock(&start);
365         stop_flag=true;
366         
367         tX_debug("tX_engine::stop() - waiting for loop to stop.");
368         
369         while (loop_is_active) {
370                 usleep(50);
371         }
372         
373         tX_debug("tX_engine::stop() - loop has stopped.");
374
375         device->close();
376         delete device;
377         device=NULL;
378         
379         for (vtt=vtt_class::main_list.begin(); vtt!=vtt_class::main_list.end(); vtt++) {
380                 (*vtt)->stop();
381                 (*vtt)->ec_clear_buffer();
382         }
383         
384         if (is_recording()) tape->stop_record();
385         recording=false;
386 }
387
388 tX_engine :: ~tX_engine() {
389         void *dummy;
390         
391         thread_terminate=true;
392         stop_flag=true;
393         pthread_mutex_unlock(&start);
394         tX_debug("~tX_engine() - Waiting for engine thread to terminate.");
395         pthread_join(thread, &dummy);   
396 }