9f47aeeedf727b5ab9638cc05a92509b79af2760
[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
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <sys/ioctl.h>
31 #include <fcntl.h>
32 #include <sys/soundcard.h>
33 #include <config.h>
34
35 #include "tX_endian.h"
36
37 #ifndef __USE_XOPEN
38 #       define __USE_XOPEN // we need this for swab()
39 #       include <unistd.h>
40 #       undef __USE_XOPEN
41 #else
42 #       include <unistd.h>
43 #endif
44
45 #include <string.h>
46 #include <errno.h>
47 #include <stdlib.h>
48
49 void tX_audiodevice :: init()
50 {
51         samples_per_buffer=0;
52         //set_buffersize_near(globals.audiodevice_buffersize);
53 }
54
55 #ifdef USE_OSS
56
57 int tX_audiodevice_oss :: open()
58 {
59         int i=0;
60         int p;
61         int buff_cfg;
62
63         if (fd) return (1);
64         fd=::open(globals.oss_device, O_WRONLY, 0);
65         
66         /* setting buffer size */       
67         buff_cfg=(globals.oss_buff_no<<16) | globals.oss_buff_size;
68         p=buff_cfg;
69
70         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);
71                 
72         i = ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &p);
73         ioctl(fd, SNDCTL_DSP_RESET, 0);         
74
75         /* 16 Bits */
76         
77         p =  16;
78         i +=  ioctl(fd, SOUND_PCM_WRITE_BITS, &p);
79
80         /* STEREO :) */
81         
82         p =  2;
83         i += ioctl(fd, SOUND_PCM_WRITE_CHANNELS, &p);
84         
85         /* 44.1 khz */
86
87         p =  globals.oss_samplerate;
88         i += ioctl(fd, SOUND_PCM_WRITE_RATE, &p);
89         
90         /* Figure actual blocksize.. */
91         
92         i += ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &blocksize);
93
94         samples_per_buffer=blocksize/sizeof(int16_t);
95         globals.true_block_size=samples_per_buffer/2;
96
97         tX_debug("tX_adudiodevice_oss::open() - blocksize: %i, samples_per_buffer: %i", blocksize, samples_per_buffer);
98         
99         ioctl(fd, SNDCTL_DSP_SYNC, 0);
100
101         return i;
102 }
103
104 int tX_audiodevice_oss :: close()
105 {
106         if (!fd)
107         {       
108                 return(1);              
109         }
110         ::close(fd);
111         fd=0;
112         blocksize=0;
113                 
114         return 0;
115 }
116
117 tX_audiodevice_oss :: tX_audiodevice_oss()
118 {
119         fd=0;
120         blocksize=0;
121         init();
122 }
123
124 double tX_audiodevice_oss :: get_latency()
125 {
126         return 0;
127 }
128
129 void tX_audiodevice_oss :: play(int16_t *buffer)
130 {
131 #ifdef BIG_ENDIAN_MACHINE
132         swapbuffer (buffer, samples_per_buffer);
133 #endif
134         int res=write(fd, buffer, blocksize);   
135         if (res==-1) {
136                 tX_error("failed to write to audiodevice: %s", strerror(errno));
137                 exit(-1);
138         }
139 }
140
141 #endif //USE_OSS
142
143 #ifdef USE_ALSA
144
145 int tX_audiodevice_alsa :: open()
146 {
147         snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK;
148         snd_pcm_hw_params_t *hw_params;
149         char pcm_name[64];
150         char foo[PATH_MAX];
151         
152         snd_pcm_hw_params_alloca(&hw_params);   
153         
154         int card;
155         int device;
156         
157         sscanf(globals.alsa_device, "%i-%i: %s", &card, &device, foo);
158         sprintf(pcm_name, "hw:%i,%i", card, device);
159         
160         if (snd_pcm_open(&pcm_handle, pcm_name, stream, 0) < 0) {
161                 tX_error("ALSA: Failed to access PCM device \"%s\"", pcm_name);
162                 snd_pcm_hw_params_free (hw_params);
163                 return -1;
164         }
165         
166         if (snd_pcm_hw_params_any(pcm_handle, hw_params) < 0) {
167                 tX_error("ALSA: Failed to configure PCM device \"%s\"", pcm_name);
168                 snd_pcm_hw_params_free (hw_params);
169                 return -1;
170         }
171         
172         /* Setting INTERLEAVED stereo... */
173         if (snd_pcm_hw_params_set_access(pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {
174                 tX_error("ALSA: Failed to set interleaved access for PCM device \"%s\"", pcm_name);
175                 snd_pcm_hw_params_free (hw_params);
176                 return -1;
177         }
178         
179         /* Make it 16 Bit LE - we handle converting from BE anyway... */
180         if (snd_pcm_hw_params_set_format(pcm_handle, hw_params, SND_PCM_FORMAT_S16_LE) < 0) {
181                 tX_error("ALSA: Error setting 16 Bit sample width for PCM device \"%s\"", pcm_name);
182                 snd_pcm_hw_params_free (hw_params);
183                 return -1;
184         }
185         
186         /* Setting sampling rate */
187         unsigned int hw_rate=(unsigned int)globals.alsa_samplerate;
188         int dir;
189         
190         int res = snd_pcm_hw_params_set_rate_near(pcm_handle, hw_params, &hw_rate, &dir);
191         
192         if (dir != 0) {
193                 tX_warning("ALSA: The PCM device \"%s\" doesnt support 44100 Hz playback - using %i instead", pcm_name, hw_rate);
194         }       
195
196         /* Using stereo output */
197         if (snd_pcm_hw_params_set_channels(pcm_handle, hw_params, 2) < 0) {
198                 tX_error("ALSA: PCM device \"%s\" does not support stereo operation", pcm_name);
199                 snd_pcm_hw_params_free (hw_params);
200                 return -1;
201         }
202
203         /* Setting the number of buffers... */
204         /* if (snd_pcm_hw_params_set_periods(pcm_handle, hw_params, globals.alsa_buff_no, 0) < 0) {
205                 tX_error("ALSA: Failed to set %i periods for PCM device \"%s\"", globals.alsa_buff_no, pcm_name);
206                 snd_pcm_hw_params_free (hw_params);
207                 return -1;
208         } */
209
210         /* Setting the buffersize - ALSA cripples my mind, really... */
211         long unsigned int samples;
212         long unsigned int periodsize;
213
214         periodsize = globals.alsa_buff_size * 2;
215         
216         samples = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hw_params, &periodsize);
217         if (samples < 0) {
218                 tX_error("ALSA: Failed to set buffersize %li", globals.alsa_buff_size);
219                 return -1;
220         }
221
222         samples_per_buffer=periodsize;//hw_buffsize/sizeof(int16_t);
223         
224         periodsize /= 2;
225         if (snd_pcm_hw_params_set_period_size_near(pcm_handle, hw_params, &periodsize, 0) < 0) {
226                 tX_error("ALSA: Failed to set periodsize %li", periodsize);
227                 return -1;
228         }
229
230         globals.true_block_size=periodsize;
231         
232         /* Apply all that setup work.. */
233         if (snd_pcm_hw_params(pcm_handle, hw_params) < 0) {
234                 tX_error("ALSA: Failed to apply settings to PCM device \"%s\"", pcm_name);
235                 snd_pcm_hw_params_free (hw_params);
236                 return -1;
237         }
238         
239         tX_debug("ALSA: samples_per_buffer: %i, bs: %i, period=%i", samples_per_buffer, globals.true_block_size, periodsize);
240         
241         snd_pcm_hw_params_free (hw_params);
242         return 0;
243 }
244
245 int tX_audiodevice_alsa :: close()
246 {
247         snd_pcm_close(pcm_handle);
248         
249         return 0;
250 }
251
252 double tX_audiodevice_alsa :: get_latency()
253 {
254         return 0;
255 }
256
257
258 tX_audiodevice_alsa :: tX_audiodevice_alsa()
259 {
260         pcm_handle=NULL;        
261         init();
262 }
263
264 void tX_audiodevice_alsa :: play(int16_t *buffer)
265 {
266         snd_pcm_sframes_t pcmreturn;
267 #ifdef BIG_ENDIAN_MACHINE
268         swapbuffer (buffer, samples_per_buffer);
269 #endif
270         
271         pcmreturn = snd_pcm_writei(pcm_handle, buffer, samples_per_buffer/2);
272         
273         while (pcmreturn==-EPIPE) {
274                 snd_pcm_prepare(pcm_handle);
275                 pcmreturn=snd_pcm_writei(pcm_handle, buffer, samples_per_buffer/2);
276                 //tX_warning("ALSA: ** buffer underrun **");
277         }
278         
279         if (pcmreturn<0) {
280                 printf("snd_pcm_writei says: %s.\n", strerror(-1*pcmreturn));
281         }
282 }
283
284 #endif //USE_ALSA