Yippie! fixed block size rendering, slower but way more flexibe - 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)
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         
64         while (!engine->is_stopped()) {
65                 current=current ? 0 : 1;
66                 
67                 int16_t *current_buffer=sample_buffer[current];
68                 int16_t *next_buffer=sample_buffer[current ? 0 : 1];
69                 
70                 fill_buffer(current_buffer, next_buffer);
71                 play(current_buffer);
72         }
73         
74         delete [] sample_buffer[0];
75         delete [] sample_buffer[1];
76 }
77
78 void tX_audiodevice :: fill_buffer(int16_t *target_buffer, int16_t *next_target_buffer) {
79         int vtt_buffer_size=vtt_class::get_mix_buffer_size()<<1;
80         int prefill;
81         
82         while (buffer_pos < samples_per_buffer) {
83                 int16_t *data=engine->render_cycle();
84                 
85                 int rest=samples_per_buffer-(buffer_pos+vtt_buffer_size);               
86                 
87                 if (rest>0) {
88                         memcpy(&target_buffer[buffer_pos], data, samples_per_buffer << 1);
89                 } else {
90                         rest*=-1;
91                                         
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         /* setting buffer size */       
117         buff_cfg=(globals.oss_buff_no<<16) | globals.oss_buff_size;
118         p=buff_cfg;
119
120         tX_debug("tX_adudiodevice_oss::open() - buff_no: %i, buff_size: %i, buff_cfg: %08x", globals.oss_buff_no, globals.oss_buff_size, buff_cfg);
121                 
122         i = ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &p);
123         ioctl(fd, SNDCTL_DSP_RESET, 0);         
124
125         /* 16 Bits */
126         
127         p =  16;
128         i +=  ioctl(fd, SOUND_PCM_WRITE_BITS, &p);
129
130         /* STEREO :) */
131         
132         p =  2;
133         i += ioctl(fd, SOUND_PCM_WRITE_CHANNELS, &p);
134         
135         /* 44.1 khz */
136
137         p =  globals.oss_samplerate;
138         i += ioctl(fd, SOUND_PCM_WRITE_RATE, &p);
139         
140         sample_rate=globals.oss_samplerate;
141         
142         /* Figure actual blocksize.. */
143         
144         i += ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &blocksize);
145
146         samples_per_buffer=blocksize/sizeof(int16_t);
147         globals.true_block_size=samples_per_buffer/2;
148
149         tX_debug("tX_adudiodevice_oss::open() - blocksize: %i, samples_per_buffer: %i", blocksize, samples_per_buffer);
150         
151         ioctl(fd, SNDCTL_DSP_SYNC, 0);
152
153         return i;
154 }
155
156 int tX_audiodevice_oss :: close()
157 {
158         if (!fd)
159         {       
160                 return(1);              
161         }
162         ::close(fd);
163         fd=0;
164         blocksize=0;
165                 
166         return 0;
167 }
168
169 tX_audiodevice_oss :: tX_audiodevice_oss() : tX_audiodevice(),
170 fd(0), blocksize(0) {}
171
172 double tX_audiodevice_oss :: get_latency()
173 {
174         return 0;
175 }
176
177 void tX_audiodevice_oss :: play(int16_t *buffer)
178 {
179 #ifdef BIG_ENDIAN_MACHINE
180         swapbuffer (buffer, samples_per_buffer);
181 #endif
182         int res=write(fd, buffer, blocksize);   
183         if (res==-1) {
184                 tX_error("failed to write to audiodevice: %s", strerror(errno));
185                 exit(-1);
186         }
187 }
188
189 #endif //USE_OSS
190
191 #ifdef USE_ALSA
192
193 int tX_audiodevice_alsa :: open()
194 {
195         snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK;
196         snd_pcm_hw_params_t *hw_params;
197         char pcm_name[64];
198         char foo[PATH_MAX];
199         
200         snd_pcm_hw_params_alloca(&hw_params);   
201         
202         int card;
203         int device;
204         
205         sscanf(globals.alsa_device, "%i-%i: %s", &card, &device, foo);
206         sprintf(pcm_name, "hw:%i,%i", card, device);
207         
208         if (snd_pcm_open(&pcm_handle, pcm_name, stream, 0) < 0) {
209                 tX_error("ALSA: Failed to access PCM device \"%s\"", pcm_name);
210                 snd_pcm_hw_params_free (hw_params);
211                 return -1;
212         }
213         
214         if (snd_pcm_hw_params_any(pcm_handle, hw_params) < 0) {
215                 tX_error("ALSA: Failed to configure PCM device \"%s\"", pcm_name);
216                 snd_pcm_hw_params_free (hw_params);
217                 return -1;
218         }
219         
220         /* Setting INTERLEAVED stereo... */
221         if (snd_pcm_hw_params_set_access(pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {
222                 tX_error("ALSA: Failed to set interleaved access for PCM device \"%s\"", pcm_name);
223                 snd_pcm_hw_params_free (hw_params);
224                 return -1;
225         }
226         
227         /* Make it 16 Bit LE - we handle converting from BE anyway... */
228         if (snd_pcm_hw_params_set_format(pcm_handle, hw_params, SND_PCM_FORMAT_S16_LE) < 0) {
229                 tX_error("ALSA: Error setting 16 Bit sample width for PCM device \"%s\"", pcm_name);
230                 snd_pcm_hw_params_free (hw_params);
231                 return -1;
232         }
233         
234         /* Setting sampling rate */
235         unsigned int hw_rate=(unsigned int)globals.alsa_samplerate;
236         int dir;
237         
238         int res = snd_pcm_hw_params_set_rate_near(pcm_handle, hw_params, &hw_rate, &dir);
239         
240         if (dir != 0) {
241                 tX_warning("ALSA: The PCM device \"%s\" doesnt support 44100 Hz playback - using %i instead", pcm_name, hw_rate);
242         }       
243
244         sample_rate=hw_rate;
245         
246         /* Using stereo output */
247         if (snd_pcm_hw_params_set_channels(pcm_handle, hw_params, 2) < 0) {
248                 tX_error("ALSA: PCM device \"%s\" does not support stereo operation", pcm_name);
249                 snd_pcm_hw_params_free (hw_params);
250                 return -1;
251         }
252
253         /* Setting the number of buffers... */
254         /* if (snd_pcm_hw_params_set_periods(pcm_handle, hw_params, globals.alsa_buff_no, 0) < 0) {
255                 tX_error("ALSA: Failed to set %i periods for PCM device \"%s\"", globals.alsa_buff_no, pcm_name);
256                 snd_pcm_hw_params_free (hw_params);
257                 return -1;
258         } */
259
260         /* Setting the buffersize - ALSA cripples my mind, really... */
261         long unsigned int samples;
262         long unsigned int periodsize;
263
264         periodsize = globals.alsa_buff_size * 2;
265         
266         samples = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hw_params, &periodsize);
267         if (samples < 0) {
268                 tX_error("ALSA: Failed to set buffersize %li", globals.alsa_buff_size);
269                 return -1;
270         }
271
272         samples_per_buffer=periodsize;//hw_buffsize/sizeof(int16_t);
273         
274         periodsize /= 2;
275         if (snd_pcm_hw_params_set_period_size_near(pcm_handle, hw_params, &periodsize, 0) < 0) {
276                 tX_error("ALSA: Failed to set periodsize %li", periodsize);
277                 return -1;
278         }
279
280         globals.true_block_size=periodsize;
281         
282         /* Apply all that setup work.. */
283         if (snd_pcm_hw_params(pcm_handle, hw_params) < 0) {
284                 tX_error("ALSA: Failed to apply settings to PCM device \"%s\"", pcm_name);
285                 snd_pcm_hw_params_free (hw_params);
286                 return -1;
287         }
288         
289         tX_debug("ALSA: samples_per_buffer: %i, bs: %i, period=%i", samples_per_buffer, globals.true_block_size, periodsize);
290         
291         snd_pcm_hw_params_free (hw_params);
292         return 0;
293 }
294
295 int tX_audiodevice_alsa :: close()
296 {
297         snd_pcm_close(pcm_handle);
298         
299         return 0;
300 }
301
302 double tX_audiodevice_alsa :: get_latency()
303 {
304         return 0;
305 }
306
307
308 tX_audiodevice_alsa :: tX_audiodevice_alsa() : tX_audiodevice(),
309 pcm_handle(NULL) {}
310
311 void tX_audiodevice_alsa :: play(int16_t *buffer)
312 {
313         snd_pcm_sframes_t pcmreturn;
314 #ifdef BIG_ENDIAN_MACHINE
315         swapbuffer (buffer, samples_per_buffer);
316 #endif
317         
318         pcmreturn = snd_pcm_writei(pcm_handle, buffer, samples_per_buffer/2);
319         
320         while (pcmreturn==-EPIPE) {
321                 snd_pcm_prepare(pcm_handle);
322                 pcmreturn=snd_pcm_writei(pcm_handle, buffer, samples_per_buffer/2);
323                 //tX_warning("ALSA: ** buffer underrun **");
324         }
325         
326         if (pcmreturn<0) {
327                 printf("snd_pcm_writei says: %s.\n", strerror(-1*pcmreturn));
328         }
329 }
330
331 #endif //USE_ALSA