Consistent samplerate handling for playback and recording - 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
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         sample_rate=globals.oss_samplerate;
91         
92         /* Figure actual blocksize.. */
93         
94         i += ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &blocksize);
95
96         samples_per_buffer=blocksize/sizeof(int16_t);
97         globals.true_block_size=samples_per_buffer/2;
98
99         tX_debug("tX_adudiodevice_oss::open() - blocksize: %i, samples_per_buffer: %i", blocksize, samples_per_buffer);
100         
101         ioctl(fd, SNDCTL_DSP_SYNC, 0);
102
103         return i;
104 }
105
106 int tX_audiodevice_oss :: close()
107 {
108         if (!fd)
109         {       
110                 return(1);              
111         }
112         ::close(fd);
113         fd=0;
114         blocksize=0;
115                 
116         return 0;
117 }
118
119 tX_audiodevice_oss :: tX_audiodevice_oss()
120 {
121         fd=0;
122         blocksize=0;
123         init();
124 }
125
126 double tX_audiodevice_oss :: get_latency()
127 {
128         return 0;
129 }
130
131 void tX_audiodevice_oss :: play(int16_t *buffer)
132 {
133 #ifdef BIG_ENDIAN_MACHINE
134         swapbuffer (buffer, samples_per_buffer);
135 #endif
136         int res=write(fd, buffer, blocksize);   
137         if (res==-1) {
138                 tX_error("failed to write to audiodevice: %s", strerror(errno));
139                 exit(-1);
140         }
141 }
142
143 #endif //USE_OSS
144
145 #ifdef USE_ALSA
146
147 int tX_audiodevice_alsa :: open()
148 {
149         snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK;
150         snd_pcm_hw_params_t *hw_params;
151         char pcm_name[64];
152         char foo[PATH_MAX];
153         
154         snd_pcm_hw_params_alloca(&hw_params);   
155         
156         int card;
157         int device;
158         
159         sscanf(globals.alsa_device, "%i-%i: %s", &card, &device, foo);
160         sprintf(pcm_name, "hw:%i,%i", card, device);
161         
162         if (snd_pcm_open(&pcm_handle, pcm_name, stream, 0) < 0) {
163                 tX_error("ALSA: Failed to access PCM device \"%s\"", pcm_name);
164                 snd_pcm_hw_params_free (hw_params);
165                 return -1;
166         }
167         
168         if (snd_pcm_hw_params_any(pcm_handle, hw_params) < 0) {
169                 tX_error("ALSA: Failed to configure PCM device \"%s\"", pcm_name);
170                 snd_pcm_hw_params_free (hw_params);
171                 return -1;
172         }
173         
174         /* Setting INTERLEAVED stereo... */
175         if (snd_pcm_hw_params_set_access(pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {
176                 tX_error("ALSA: Failed to set interleaved access for PCM device \"%s\"", pcm_name);
177                 snd_pcm_hw_params_free (hw_params);
178                 return -1;
179         }
180         
181         /* Make it 16 Bit LE - we handle converting from BE anyway... */
182         if (snd_pcm_hw_params_set_format(pcm_handle, hw_params, SND_PCM_FORMAT_S16_LE) < 0) {
183                 tX_error("ALSA: Error setting 16 Bit sample width for PCM device \"%s\"", pcm_name);
184                 snd_pcm_hw_params_free (hw_params);
185                 return -1;
186         }
187         
188         /* Setting sampling rate */
189         unsigned int hw_rate=(unsigned int)globals.alsa_samplerate;
190         int dir;
191         
192         int res = snd_pcm_hw_params_set_rate_near(pcm_handle, hw_params, &hw_rate, &dir);
193         
194         if (dir != 0) {
195                 tX_warning("ALSA: The PCM device \"%s\" doesnt support 44100 Hz playback - using %i instead", pcm_name, hw_rate);
196         }       
197
198         sample_rate=hw_rate;
199         
200         /* Using stereo output */
201         if (snd_pcm_hw_params_set_channels(pcm_handle, hw_params, 2) < 0) {
202                 tX_error("ALSA: PCM device \"%s\" does not support stereo operation", pcm_name);
203                 snd_pcm_hw_params_free (hw_params);
204                 return -1;
205         }
206
207         /* Setting the number of buffers... */
208         /* if (snd_pcm_hw_params_set_periods(pcm_handle, hw_params, globals.alsa_buff_no, 0) < 0) {
209                 tX_error("ALSA: Failed to set %i periods for PCM device \"%s\"", globals.alsa_buff_no, pcm_name);
210                 snd_pcm_hw_params_free (hw_params);
211                 return -1;
212         } */
213
214         /* Setting the buffersize - ALSA cripples my mind, really... */
215         long unsigned int samples;
216         long unsigned int periodsize;
217
218         periodsize = globals.alsa_buff_size * 2;
219         
220         samples = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hw_params, &periodsize);
221         if (samples < 0) {
222                 tX_error("ALSA: Failed to set buffersize %li", globals.alsa_buff_size);
223                 return -1;
224         }
225
226         samples_per_buffer=periodsize;//hw_buffsize/sizeof(int16_t);
227         
228         periodsize /= 2;
229         if (snd_pcm_hw_params_set_period_size_near(pcm_handle, hw_params, &periodsize, 0) < 0) {
230                 tX_error("ALSA: Failed to set periodsize %li", periodsize);
231                 return -1;
232         }
233
234         globals.true_block_size=periodsize;
235         
236         /* Apply all that setup work.. */
237         if (snd_pcm_hw_params(pcm_handle, hw_params) < 0) {
238                 tX_error("ALSA: Failed to apply settings to PCM device \"%s\"", pcm_name);
239                 snd_pcm_hw_params_free (hw_params);
240                 return -1;
241         }
242         
243         tX_debug("ALSA: samples_per_buffer: %i, bs: %i, period=%i", samples_per_buffer, globals.true_block_size, periodsize);
244         
245         snd_pcm_hw_params_free (hw_params);
246         return 0;
247 }
248
249 int tX_audiodevice_alsa :: close()
250 {
251         snd_pcm_close(pcm_handle);
252         
253         return 0;
254 }
255
256 double tX_audiodevice_alsa :: get_latency()
257 {
258         return 0;
259 }
260
261
262 tX_audiodevice_alsa :: tX_audiodevice_alsa()
263 {
264         pcm_handle=NULL;        
265         init();
266 }
267
268 void tX_audiodevice_alsa :: play(int16_t *buffer)
269 {
270         snd_pcm_sframes_t pcmreturn;
271 #ifdef BIG_ENDIAN_MACHINE
272         swapbuffer (buffer, samples_per_buffer);
273 #endif
274         
275         pcmreturn = snd_pcm_writei(pcm_handle, buffer, samples_per_buffer/2);
276         
277         while (pcmreturn==-EPIPE) {
278                 snd_pcm_prepare(pcm_handle);
279                 pcmreturn=snd_pcm_writei(pcm_handle, buffer, samples_per_buffer/2);
280                 //tX_warning("ALSA: ** buffer underrun **");
281         }
282         
283         if (pcmreturn<0) {
284                 printf("snd_pcm_writei says: %s.\n", strerror(-1*pcmreturn));
285         }
286 }
287
288 #endif //USE_ALSA