ALSA fix + deactivating run_adding - Alex
[terminatorX.git] / src / tX_audiodevice.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_aduiodevice.cc
20  
21     Description: Implements Audiodevice handling... 
22 */    
23
24 #define ALSA_PCM_NEW_HW_PARAMS_API
25
26 #include "tX_audiodevice.h"
27 #include "tX_vtt.h"
28
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <sys/ioctl.h>
32 #include <fcntl.h>
33 #include <sys/soundcard.h>
34 #include <config.h>
35
36 #include "tX_endian.h"
37
38 #ifndef __USE_XOPEN
39 #       define __USE_XOPEN // we need this for swab()
40 #       include <unistd.h>
41 #       undef __USE_XOPEN
42 #else
43 #       include <unistd.h>
44 #endif
45
46 #include <string.h>
47 #include <errno.h>
48 #include <stdlib.h>
49 #include "tX_engine.h"
50
51 tX_audiodevice :: tX_audiodevice() : samples_per_buffer(0),
52 current_buffer(0), buffer_pos(0), is_open(false)
53 {
54         sample_buffer[0]=NULL;
55         sample_buffer[1]=NULL;
56         engine=tX_engine::get_instance();
57 }
58
59 void tX_audiodevice :: start() {
60         sample_buffer[0]=new int16_t[samples_per_buffer*2];
61         sample_buffer[1]=new int16_t[samples_per_buffer*2];
62         int current=0;
63         vtt_buffer_size=vtt_class::get_mix_buffer_size()<<1;
64         
65         buffer_pos=0;
66         
67         while (!engine->is_stopped()) {
68                 current=current ? 0 : 1;
69                 
70                 int16_t *current_buffer=sample_buffer[current];
71                 int16_t *next_buffer=sample_buffer[current ? 0 : 1];
72                 
73                 fill_buffer(current_buffer, next_buffer);
74                 play(current_buffer);
75         }
76         
77         delete [] sample_buffer[0];
78         delete [] sample_buffer[1];
79 }
80
81 void tX_audiodevice :: fill_buffer(int16_t *target_buffer, int16_t *next_target_buffer) {
82         int prefill=0;
83         
84         while (buffer_pos <= samples_per_buffer) {
85                 int16_t *data=engine->render_cycle();
86                 
87                 int rest=(buffer_pos+vtt_buffer_size)-samples_per_buffer;
88                 
89                 if (rest<=0) {
90                         memcpy(&target_buffer[buffer_pos], data, vtt_buffer_size << 1);
91                 } else {
92                         memcpy(&target_buffer[buffer_pos], data, (vtt_buffer_size-rest) << 1);
93                         memcpy(next_target_buffer, &data[vtt_buffer_size-rest], rest << 1);
94                         prefill=rest;
95                 }
96                 
97                 buffer_pos+=vtt_buffer_size;
98         }
99         
100         buffer_pos=prefill;
101 }
102
103 /* Driver Specific Code follows.. */
104
105 #ifdef USE_OSS
106
107 int tX_audiodevice_oss :: open()
108 {
109         int i=0;
110         int p;
111         int buff_cfg;
112
113         if (fd) return (1);
114         fd=::open(globals.oss_device, O_WRONLY, 0);
115         
116         if (fd==-1) {
117                 tX_error("tX_audiodevice_oss::open() can't open device: %s", strerror(errno));
118                 return -1;
119         }
120         
121         is_open=true;
122         
123         /* setting buffer size */       
124         buff_cfg=(globals.oss_buff_no<<16) | globals.oss_buff_size;
125         p=buff_cfg;
126
127         tX_debug("tX_audiodevice_oss::open() - buff_no: %i, buff_size: %i, buff_cfg: %08x", globals.oss_buff_no, globals.oss_buff_size, buff_cfg);
128                 
129         i = ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &p);
130         ioctl(fd, SNDCTL_DSP_RESET, 0);         
131
132         /* 16 Bits */
133         
134         p =  16;
135         i +=  ioctl(fd, SOUND_PCM_WRITE_BITS, &p);
136
137         /* STEREO :) */
138         
139         p =  2;
140         i += ioctl(fd, SOUND_PCM_WRITE_CHANNELS, &p);
141         
142         /* 44.1 khz */
143
144         p =  globals.oss_samplerate;
145         i += ioctl(fd, SOUND_PCM_WRITE_RATE, &p);
146         
147         sample_rate=globals.oss_samplerate;
148         
149         /* Figure actual blocksize.. */
150         
151         i += ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &blocksize);
152
153         samples_per_buffer=blocksize/sizeof(int16_t);
154
155         tX_debug("tX_adudiodevice_oss::open() - blocksize: %i, samples_per_buffer: %i", blocksize, samples_per_buffer);
156         
157         ioctl(fd, SNDCTL_DSP_SYNC, 0);
158
159         return i;
160 }
161
162 int tX_audiodevice_oss :: close()
163 {
164         if (!fd)
165         {       
166                 return(1);              
167         }
168         is_open=false;
169         ::close(fd);
170         fd=0;
171         blocksize=0;
172                 
173         return 0;
174 }
175
176 tX_audiodevice_oss :: tX_audiodevice_oss() : tX_audiodevice(),
177 fd(0), blocksize(0) {}
178
179 double tX_audiodevice_oss :: get_latency()
180 {
181         return 0;
182 }
183
184 void tX_audiodevice_oss :: play(int16_t *buffer)
185 {
186 #ifdef BIG_ENDIAN_MACHINE
187         swapbuffer (buffer, samples_per_buffer);
188 #endif
189         int res=write(fd, buffer, blocksize);   
190         if (res==-1) {
191                 tX_error("failed to write to audiodevice: %s", strerror(errno));
192                 exit(-1);
193         }
194 }
195
196 #endif //USE_OSS
197
198 #ifdef USE_ALSA
199
200 int tX_audiodevice_alsa :: open()
201 {
202         snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK;
203         snd_pcm_hw_params_t *hw_params;
204         char pcm_name[64];
205         char foo[PATH_MAX];
206         
207         
208         int card;
209         int device;
210         
211         sscanf(globals.alsa_device, "%i-%i: %s", &card, &device, foo);
212         sprintf(pcm_name, "hw:%i,%i", card, device);
213         
214         if (snd_pcm_open(&pcm_handle, pcm_name, stream, NULL) < 0) {
215                 tX_error("ALSA: Failed to access PCM device \"%s\"", pcm_name);
216                 return -1;
217         }
218         
219         is_open=true;
220
221         snd_pcm_hw_params_alloca(&hw_params);   
222         
223         if (snd_pcm_hw_params_any(pcm_handle, hw_params) < 0) {
224                 tX_error("ALSA: Failed to configure PCM device \"%s\"", pcm_name);
225                 snd_pcm_hw_params_free (hw_params);
226                 return -1;
227         }
228         
229         /* Setting INTERLEAVED stereo... */
230 #ifdef USE_ALSA_MEMCPY
231         if (snd_pcm_hw_params_set_access(pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {
232 #else
233         if (snd_pcm_hw_params_set_access(pcm_handle, hw_params, SND_PCM_ACCESS_MMAP_INTERLEAVED) < 0) {
234 #endif  
235                 tX_error("ALSA: Failed to set interleaved access for PCM device \"%s\"", pcm_name);
236                 snd_pcm_hw_params_free (hw_params);
237                 return -1;
238         }
239         
240         /* Make it 16 Bit LE - we handle converting from BE anyway... */
241         if (snd_pcm_hw_params_set_format(pcm_handle, hw_params, SND_PCM_FORMAT_S16_LE) < 0) {
242                 tX_error("ALSA: Error setting 16 Bit sample width for PCM device \"%s\"", pcm_name);
243                 snd_pcm_hw_params_free (hw_params);
244                 return -1;
245         }
246         
247         /* Setting sampling rate */
248         unsigned int hw_rate=(unsigned int)globals.alsa_samplerate;
249         int dir;
250         
251         if (snd_pcm_hw_params_set_rate_near(pcm_handle, hw_params, &hw_rate, &dir) < 0) {
252                 tX_error("ALSA: Failed setting sample rate: %i", globals.alsa_samplerate);
253                 snd_pcm_hw_params_free (hw_params);
254                 return -1;
255         }
256         
257         if (dir != 0) {
258                 tX_warning("ALSA: The PCM device \"%s\" doesnt support 44100 Hz playback - using %i instead", pcm_name, hw_rate);
259         }       
260
261         sample_rate=hw_rate;
262         
263         /* Using stereo output */
264         if (snd_pcm_hw_params_set_channels(pcm_handle, hw_params, 2) < 0) {
265                 tX_error("ALSA: PCM device \"%s\" does not support stereo operation", pcm_name);
266                 snd_pcm_hw_params_free (hw_params);
267                 return -1;
268         }
269         
270         unsigned int buffer_time=globals.alsa_buffer_time;
271         unsigned int period_time=globals.alsa_period_time;
272         
273         if (snd_pcm_hw_params_set_buffer_time_near(pcm_handle, hw_params, &buffer_time, &dir) < 0) {
274                 tX_error("ALSA: failed to set the buffer time opf %i usecs", globals.alsa_buffer_time);
275                 return -1;
276         }
277
278         long unsigned int buffer_size;
279
280         if (snd_pcm_hw_params_get_buffer_size(hw_params, &buffer_size) < 0) {
281                 tX_error("ALSA: failed to retreive buffer size");
282                 return -1;
283         }
284         
285         tX_debug("ALSA: buffer size is %lu", buffer_size);
286         
287         if (snd_pcm_hw_params_set_period_time_near(pcm_handle, hw_params, &period_time, &dir) < 0) {
288                 tX_error("ALSA: failed to set period time %i", globals.alsa_period_time);
289                 return -1;
290         }
291         
292         if (snd_pcm_hw_params_get_period_size(hw_params, &period_size, &dir)<0) {
293                 tX_error("ALSA: failed to retreive period size");
294                 return -1;
295         }
296         
297         samples_per_buffer=period_size;
298         
299         /* Apply all that setup work.. */
300         if (snd_pcm_hw_params(pcm_handle, hw_params) < 0) {
301                 tX_error("ALSA: Failed to apply settings to PCM device \"%s\"", pcm_name);
302                 snd_pcm_hw_params_free (hw_params);
303                 return -1;
304         }
305         
306         if (globals.alsa_free_hwstats) {
307                 snd_pcm_hw_params_free (hw_params);
308         }
309         
310         return 0; //snd_pcm_prepare(pcm_handle);
311 }
312
313 int tX_audiodevice_alsa :: close()
314 {
315         if (is_open) {
316                 snd_pcm_close(pcm_handle);
317         }
318         is_open=false;
319         
320         return 0;
321 }
322
323 double tX_audiodevice_alsa :: get_latency()
324 {
325         return 0;
326 }
327
328 tX_audiodevice_alsa :: tX_audiodevice_alsa() : tX_audiodevice(),
329 pcm_handle(NULL) {}
330
331 void tX_audiodevice_alsa :: play(int16_t *buffer)
332 {
333         snd_pcm_sframes_t pcmreturn;
334 #ifdef BIG_ENDIAN_MACHINE
335         swapbuffer (buffer, samples_per_buffer);
336 #endif
337         
338 #ifdef USE_ALSA_MEMCPY
339         pcmreturn = snd_pcm_writei(pcm_handle, buffer, samples_per_buffer >> 1);
340 #else   
341         pcmreturn = snd_pcm_mmap_writei(pcm_handle, buffer, samples_per_buffer >> 1);
342 #endif
343         
344         while (pcmreturn==-EPIPE) {
345                 snd_pcm_prepare(pcm_handle);
346                 
347 #ifdef USE_ALSA_MEMCPY
348                 pcmreturn = snd_pcm_writei(pcm_handle, buffer, samples_per_buffer >> 1);
349 #else   
350                 pcmreturn = snd_pcm_mmap_writei(pcm_handle, buffer, samples_per_buffer >> 1);
351 #endif
352                 //tX_warning("ALSA: ** buffer underrun **");
353         }
354         
355         if (pcmreturn<0) {
356                 printf("snd_pcm_writei says: %s.\n", strerror(-1*pcmreturn));
357         }
358 }
359
360 #endif //USE_ALSA