]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - snd_alsa.c
snd_alsa: MIDI input support! MIDI events get mapped to MIDINOTE<n> events (n = 0...
[xonotic/darkplaces.git] / snd_alsa.c
1 /*
2         Copyright (C) 2006  Mathieu Olivier
3
4         This program is free software; you can redistribute it and/or
5         modify it under the terms of the GNU General Public License
6         as published by the Free Software Foundation; either version 2
7         of the License, or (at your option) any later version.
8
9         This program is distributed in the hope that it will be useful,
10         but WITHOUT ANY WARRANTY; without even the implied warranty of
11         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13         See the 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:
17
18                 Free Software Foundation, Inc.
19                 59 Temple Place - Suite 330
20                 Boston, MA  02111-1307, USA
21
22 */
23
24 // ALSA module, used by Linux
25
26 #include "quakedef.h"
27
28 #include <alsa/asoundlib.h>
29
30 #include "snd_main.h"
31
32
33 #define NB_PERIODS 2
34
35 static snd_pcm_t* pcm_handle = NULL;
36 static snd_pcm_sframes_t expected_delay = 0;
37 static unsigned int alsasoundtime;
38
39 static snd_seq_t* seq_handle = NULL;
40
41 /*
42 ====================
43 SndSys_Init
44
45 Create "snd_renderbuffer" with the proper sound format if the call is successful
46 May return a suggested format if the requested format isn't available
47 ====================
48 */
49 qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested)
50 {
51         const char* pcm_name, *seq_name;
52         int i, err, seq_client, seq_port;
53         snd_pcm_hw_params_t* hw_params = NULL;
54         snd_pcm_format_t snd_pcm_format;
55         snd_pcm_uframes_t buffer_size;
56
57         Con_Print ("SndSys_Init: using the ALSA module\n");
58
59         seq_name = NULL;
60         i = COM_CheckParm ("-sndseqin"); // TODO turn this into a cvar, maybe
61         if (i != 0 && i < com_argc - 1)
62                 seq_name = com_argv[i + 1];
63         if(seq_name)
64         {
65                 seq_client = atoi(seq_name);
66                 seq_port = 0;
67                 if(strchr(seq_name, ':'))
68                         seq_port = atoi(strchr(seq_name, ':') + 1);
69                 Con_Printf ("SndSys_Init: seq input port has been set to \"%d:%d\". Enabling sequencer input...\n", seq_client, seq_port);
70                 err = snd_seq_open (&seq_handle, "default", SND_SEQ_OPEN_INPUT, 0);
71                 if (err < 0)
72                 {
73                         Con_Print ("SndSys_Init: can't open seq device\n");
74                         goto seqdone;
75                 }
76                 err = snd_seq_set_client_name(seq_handle, gamename);
77                 if (err < 0)
78                 {
79                         Con_Print ("SndSys_Init: can't set name of seq device\n");
80                         goto seqerror;
81                 }
82                 err = snd_seq_create_simple_port(seq_handle, gamename, SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE, SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION);
83                 if(err < 0)
84                 {
85                         Con_Print ("SndSys_Init: can't create seq port\n");
86                         goto seqerror;
87                 }
88                 err = snd_seq_connect_from(seq_handle, 0, seq_client, seq_port);
89                 if(err < 0)
90                 {
91                         Con_Printf ("SndSys_Init: can't connect to seq port \"%d:%d\"\n", seq_client, seq_port);
92                         goto seqerror;
93                 }
94                 err = snd_seq_nonblock(seq_handle, 1);
95                 if(err < 0)
96                 {
97                         Con_Print ("SndSys_Init: can't make seq nonblocking\n");
98                         goto seqerror;
99                 }
100
101                 goto seqdone;
102
103 seqerror:
104                 snd_seq_close(seq_handle);
105                 seq_handle = NULL;
106         }
107
108 seqdone:
109         // Check the requested sound format
110         if (requested->width < 1 || requested->width > 2)
111         {
112                 Con_Printf ("SndSys_Init: invalid sound width (%hu)\n",
113                                         requested->width);
114
115                 if (suggested != NULL)
116                 {
117                         memcpy (suggested, requested, sizeof (*suggested));
118
119                         if (requested->width < 1)
120                                 suggested->width = 1;
121                         else
122                                 suggested->width = 2;
123
124                         Con_Printf ("SndSys_Init: suggesting sound width = %hu\n",
125                                                 suggested->width);
126                 }
127
128                 return false;
129     }
130
131         if (pcm_handle != NULL)
132         {
133                 Con_Print ("SndSys_Init: WARNING: Init called before Shutdown!\n");
134                 SndSys_Shutdown ();
135         }
136
137         // Determine the name of the PCM handle we'll use
138         switch (requested->channels)
139         {
140                 case 4:
141                         pcm_name = "surround40";
142                         break;
143                 case 6:
144                         pcm_name = "surround51";
145                         break;
146                 case 8:
147                         pcm_name = "surround71";
148                         break;
149                 default:
150                         pcm_name = "default";
151                         break;
152         }
153 // COMMANDLINEOPTION: Linux ALSA Sound: -sndpcm <devicename> selects which pcm device to us, default is "default"
154         i = COM_CheckParm ("-sndpcm");
155         if (i != 0 && i < com_argc - 1)
156                 pcm_name = com_argv[i + 1];
157
158         // Open the audio device
159         Con_Printf ("SndSys_Init: PCM device is \"%s\"\n", pcm_name);
160         err = snd_pcm_open (&pcm_handle, pcm_name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
161         if (err < 0)
162         {
163                 Con_Printf ("SndSys_Init: can't open audio device \"%s\" (%s)\n",
164                                         pcm_name, snd_strerror (err));
165                 return false;
166         }
167
168         // Allocate the hardware parameters
169         err = snd_pcm_hw_params_malloc (&hw_params);
170         if (err < 0)
171         {
172                 Con_Printf ("SndSys_Init: can't allocate hardware parameters (%s)\n",
173                                         snd_strerror (err));
174                 goto init_error;
175         }
176         err = snd_pcm_hw_params_any (pcm_handle, hw_params);
177         if (err < 0)
178         {
179                 Con_Printf ("SndSys_Init: can't initialize hardware parameters (%s)\n",
180                                         snd_strerror (err));
181                 goto init_error;
182         }
183
184         // Set the access type
185         err = snd_pcm_hw_params_set_access (pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
186         if (err < 0)
187         {
188                 Con_Printf ("SndSys_Init: can't set access type (%s)\n",
189                                         snd_strerror (err));
190                 goto init_error;
191         }
192
193         // Set the sound width
194         if (requested->width == 1)
195                 snd_pcm_format = SND_PCM_FORMAT_U8;
196         else
197                 snd_pcm_format = SND_PCM_FORMAT_S16;
198         err = snd_pcm_hw_params_set_format (pcm_handle, hw_params, snd_pcm_format);
199         if (err < 0)
200         {
201                 Con_Printf ("SndSys_Init: can't set sound width to %hu (%s)\n",
202                                         requested->width, snd_strerror (err));
203                 goto init_error;
204         }
205
206         // Set the sound channels
207         err = snd_pcm_hw_params_set_channels (pcm_handle, hw_params, requested->channels);
208         if (err < 0)
209         {
210                 Con_Printf ("SndSys_Init: can't set sound channels to %hu (%s)\n",
211                                         requested->channels, snd_strerror (err));
212                 goto init_error;
213         }
214
215         // Set the sound speed
216         err = snd_pcm_hw_params_set_rate (pcm_handle, hw_params, requested->speed, 0);
217         if (err < 0)
218         {
219                 Con_Printf ("SndSys_Init: can't set sound speed to %u (%s)\n",
220                                         requested->speed, snd_strerror (err));
221                 goto init_error;
222         }
223
224         buffer_size = requested->speed / 5;
225         err = snd_pcm_hw_params_set_buffer_size_near (pcm_handle, hw_params, &buffer_size);
226         if (err < 0)
227         {
228                 Con_Printf ("SndSys_Init: can't set sound buffer size to %lu (%s)\n",
229                                         buffer_size, snd_strerror (err));
230                 goto init_error;
231         }
232
233         buffer_size /= NB_PERIODS;
234         err = snd_pcm_hw_params_set_period_size_near(pcm_handle, hw_params, &buffer_size, 0);
235         if (err < 0)
236         {
237                 Con_Printf ("SndSys_Init: can't set sound period size to %lu (%s)\n",
238                                         buffer_size, snd_strerror (err));
239                 goto init_error;
240         }
241
242         err = snd_pcm_hw_params (pcm_handle, hw_params);
243         if (err < 0)
244         {
245                 Con_Printf ("SndSys_Init: can't set hardware parameters (%s)\n",
246                                         snd_strerror (err));
247                 goto init_error;
248         }
249
250         snd_pcm_hw_params_free (hw_params);
251
252         snd_renderbuffer = Snd_CreateRingBuffer(requested, 0, NULL);
253         expected_delay = 0;
254         alsasoundtime = 0;
255         if (snd_channellayout.integer == SND_CHANNELLAYOUT_AUTO)
256                 Cvar_SetValueQuick (&snd_channellayout, SND_CHANNELLAYOUT_ALSA);
257
258         return true;
259
260
261 // It's not very clean, but it avoids a lot of duplicated code.
262 init_error:
263
264         if (hw_params != NULL)
265                 snd_pcm_hw_params_free (hw_params);
266
267         snd_pcm_close(pcm_handle);
268         pcm_handle = NULL;
269
270         return false;
271 }
272
273
274 /*
275 ====================
276 SndSys_Shutdown
277
278 Stop the sound card, delete "snd_renderbuffer" and free its other resources
279 ====================
280 */
281 void SndSys_Shutdown (void)
282 {
283         if (seq_handle != NULL)
284         {
285                 snd_seq_close(seq_handle);
286                 seq_handle = NULL;
287         }
288
289         if (pcm_handle != NULL)
290         {
291                 snd_pcm_close(pcm_handle);
292                 pcm_handle = NULL;
293         }
294
295         if (snd_renderbuffer != NULL)
296         {
297                 Mem_Free(snd_renderbuffer->ring);
298                 Mem_Free(snd_renderbuffer);
299                 snd_renderbuffer = NULL;
300         }
301 }
302
303
304 /*
305 ====================
306 SndSys_Recover
307
308 Try to recover from errors
309 ====================
310 */
311 static qboolean SndSys_Recover (int err_num)
312 {
313         int err;
314
315         // We can only do something on underrun ("broken pipe") errors
316         if (err_num != -EPIPE)
317                 return false;
318
319         err = snd_pcm_prepare (pcm_handle);
320         if (err < 0)
321         {
322                 Con_Printf ("SndSys_Recover: unable to recover (%s)\n",
323                                          snd_strerror (err));
324
325                 // TOCHECK: should we stop the playback ?
326
327                 return false;
328         }
329
330         return true;
331 }
332
333
334 /*
335 ====================
336 SndSys_Write
337 ====================
338 */
339 static snd_pcm_sframes_t SndSys_Write (const unsigned char* buffer, unsigned int nbframes)
340 {
341         snd_pcm_sframes_t written;
342
343         written = snd_pcm_writei (pcm_handle, buffer, nbframes);
344         if (written < 0)
345         {
346                 if (developer.integer >= 1000 && vid_activewindow)
347                         Con_Printf ("SndSys_Write: audio write returned %ld (%s)!\n",
348                                                  written, snd_strerror (written));
349
350                 if (SndSys_Recover (written))
351                 {
352                         written = snd_pcm_writei (pcm_handle, buffer, nbframes);
353                         if (written < 0)
354                                 Con_DPrintf ("SndSys_Write: audio write failed again (error %ld: %s)!\n",
355                                                          written, snd_strerror (written));
356                 }
357         }
358         if (written > 0)
359         {
360                 snd_renderbuffer->startframe += written;
361                 expected_delay += written;
362         }
363
364         return written;
365 }
366
367
368 /*
369 ====================
370 SndSys_Submit
371
372 Submit the contents of "snd_renderbuffer" to the sound card
373 ====================
374 */
375 void SndSys_Submit (void)
376 {
377         unsigned int startoffset, factor;
378         snd_pcm_uframes_t limit, nbframes;
379         snd_pcm_sframes_t written;
380
381         if (pcm_handle == NULL ||
382                 snd_renderbuffer->startframe == snd_renderbuffer->endframe)
383                 return;
384
385         startoffset = snd_renderbuffer->startframe % snd_renderbuffer->maxframes;
386         factor = snd_renderbuffer->format.width * snd_renderbuffer->format.channels;
387         limit = snd_renderbuffer->maxframes - startoffset;
388         nbframes = snd_renderbuffer->endframe - snd_renderbuffer->startframe;
389
390         if (nbframes > limit)
391         {
392                 written = SndSys_Write (&snd_renderbuffer->ring[startoffset * factor], limit);
393                 if (written < 0 || (snd_pcm_uframes_t)written != limit)
394                         return;
395
396                 nbframes -= limit;
397                 startoffset = 0;
398         }
399
400         written = SndSys_Write (&snd_renderbuffer->ring[startoffset * factor], nbframes);
401         if (written < 0)
402                 return;
403 }
404
405
406 /*
407 ====================
408 SndSys_GetSoundTime
409
410 Returns the number of sample frames consumed since the sound started
411 ====================
412 */
413 unsigned int SndSys_GetSoundTime (void)
414 {
415         snd_pcm_sframes_t delay, timediff;
416         int err;
417
418         if (pcm_handle == NULL)
419                 return 0;
420
421         err = snd_pcm_delay (pcm_handle, &delay);
422         if (err < 0)
423         {
424                 if (developer.integer >= 1000 && vid_activewindow)
425                         Con_DPrintf ("SndSys_GetSoundTime: can't get playback delay (%s)\n",
426                                                  snd_strerror (err));
427
428                 if (! SndSys_Recover (err))
429                         return 0;
430
431                 err = snd_pcm_delay (pcm_handle, &delay);
432                 if (err < 0)
433                 {
434                         Con_DPrintf ("SndSys_GetSoundTime: can't get playback delay, again (%s)\n",
435                                                  snd_strerror (err));
436                         return 0;
437                 }
438         }
439
440         if (expected_delay < delay)
441         {
442                 Con_DPrintf ("SndSys_GetSoundTime: expected_delay(%ld) < delay(%ld)\n",
443                                          expected_delay, delay);
444                 timediff = 0;
445         }
446         else
447                 timediff = expected_delay - delay;
448         expected_delay = delay;
449
450         alsasoundtime += (unsigned int)timediff;
451
452         return alsasoundtime;
453 }
454
455
456 /*
457 ====================
458 SndSys_LockRenderBuffer
459
460 Get the exclusive lock on "snd_renderbuffer"
461 ====================
462 */
463 qboolean SndSys_LockRenderBuffer (void)
464 {
465         // Nothing to do
466         return true;
467 }
468
469
470 /*
471 ====================
472 SndSys_UnlockRenderBuffer
473
474 Release the exclusive lock on "snd_renderbuffer"
475 ====================
476 */
477 void SndSys_UnlockRenderBuffer (void)
478 {
479         // Nothing to do
480 }
481
482 /*
483 ====================
484 SndSys_SendKeyEvents
485
486 Send keyboard events originating from the sound system (e.g. MIDI)
487 ====================
488 */
489 void SndSys_SendKeyEvents(void)
490 {
491         snd_seq_event_t *event;
492         if(!seq_handle)
493                 return;
494         for(;;)
495         {
496                 if(snd_seq_event_input(seq_handle, &event) <= 0)
497                         break;
498                 if(event)
499                 {
500                         switch(event->type)
501                         {
502                                 case SND_SEQ_EVENT_NOTEON:
503                                         if(event->data.note.velocity)
504                                         {
505                                                 Key_Event(K_MIDINOTE0 + event->data.note.note, 0, true);
506                                                 break;
507                                         }
508                                 case SND_SEQ_EVENT_NOTEOFF:
509                                         Key_Event(K_MIDINOTE0 + event->data.note.note, 0, false);
510                                         break;
511                         }
512                 }
513         }
514 }