1 /////////////////////////////////////////////////////////////////////////
2 // $Id: soundlnx.cc,v 1.17 2008/07/20 08:08:23 vruppert Exp $
3 /////////////////////////////////////////////////////////////////////////
5 // Copyright (C) 2001 MandrakeSoft S.A.
9 // 75002 Paris - France
10 // http://www.linux-mandrake.com/
11 // http://www.mandrakesoft.com/
13 // This library is free software; you can redistribute it and/or
14 // modify it under the terms of the GNU Lesser General Public
15 // License as published by the Free Software Foundation; either
16 // version 2 of the License, or (at your option) any later version.
18 // This library is distributed in the hope that it will be useful,
19 // but WITHOUT ANY WARRANTY; without even the implied warranty of
20 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 // Lesser General Public License for more details.
23 // You should have received a copy of the GNU Lesser General Public
24 // License along with this library; if not, write to the Free Software
25 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26 /////////////////////////////////////////////////////////////////////////
28 // Josef Drexler coded the original version of the lowlevel sound support
29 // for Linux using OSS. The current version also supports OSS on FreeBSD and
30 // ALSA PCM output on Linux.
32 #define NO_DEVICE_INCLUDES
37 #if (defined(linux) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__)) && BX_SUPPORT_SB16
39 #define LOG_THIS bx_devices.pluginSB16Device->
44 #include <sys/ioctl.h>
45 #include <sys/soundcard.h>
47 bx_sound_linux_c::bx_sound_linux_c(bx_sb16_c
*sb16
)
48 :bx_sound_output_c(sb16
)
52 alsa_seq
.handle
= NULL
;
53 alsa_pcm
.handle
= NULL
;
61 bx_sound_linux_c::~bx_sound_linux_c()
67 int bx_sound_linux_c::waveready()
69 return BX_SOUND_OUTPUT_OK
;
72 int bx_sound_linux_c::midiready()
74 return BX_SOUND_OUTPUT_OK
;
78 int bx_sound_linux_c::alsa_seq_open(char *device
)
81 int client
, port
, ret
= 0;
82 int length
= strlen(device
) + 1;
84 mididev
= new char[length
];
87 return BX_SOUND_OUTPUT_ERR
;
89 strcpy(mididev
, device
);
90 ptr
= strtok(mididev
, ":");
92 WRITELOG(MIDILOG(2), "ALSA sequencer setup: missing client parameters");
93 return BX_SOUND_OUTPUT_ERR
;
96 ptr
= strtok(NULL
, ":");
98 WRITELOG(MIDILOG(2), "ALSA sequencer setup: missing port parameter");
99 return BX_SOUND_OUTPUT_ERR
;
105 if (snd_seq_open(&alsa_seq
.handle
, "default", SND_SEQ_OPEN_OUTPUT
, 0) < 0) {
106 WRITELOG(MIDILOG(2), "Couldn't open ALSA sequencer for midi output");
107 return BX_SOUND_OUTPUT_ERR
;
109 ret
= snd_seq_create_simple_port(alsa_seq
.handle
, NULL
,
110 SND_SEQ_PORT_CAP_WRITE
|
111 SND_SEQ_PORT_CAP_SUBS_WRITE
|
112 SND_SEQ_PORT_CAP_READ
,
113 SND_SEQ_PORT_TYPE_MIDI_GENERIC
);
115 WRITELOG(MIDILOG(2), "ALSA sequencer: error creating port %s\n", snd_strerror(errno
));
117 alsa_seq
.source_port
= ret
;
118 ret
= snd_seq_connect_to(alsa_seq
.handle
, alsa_seq
.source_port
, client
, port
);
120 WRITELOG(MIDILOG(2), "ALSA sequencer: could not connect to port %d:%d\n", client
, port
);
124 snd_seq_close(alsa_seq
.handle
);
125 return BX_SOUND_OUTPUT_ERR
;
127 return BX_SOUND_OUTPUT_OK
;
132 int bx_sound_linux_c::openmidioutput(char *device
)
134 if ((device
== NULL
) || (strlen(device
) < 1))
135 return BX_SOUND_OUTPUT_ERR
;
137 #if BX_HAVE_ALSASOUND
138 use_alsa_seq
= !strncmp(device
, "alsa:", 5);
140 return alsa_seq_open(device
+5);
144 midi
= fopen(device
,"w");
148 WRITELOG(MIDILOG(2), "Couldn't open midi output device %s: %s.",
149 device
, strerror(errno
));
150 return BX_SOUND_OUTPUT_ERR
;
153 return BX_SOUND_OUTPUT_OK
;
157 #if BX_HAVE_ALSASOUND
158 int bx_sound_linux_c::alsa_seq_output(int delta
, int command
, int length
, Bit8u data
[])
160 int cmd
, chan
, value
;
163 snd_seq_ev_clear(&ev
);
164 snd_seq_ev_set_source(&ev
, alsa_seq
.source_port
);
165 snd_seq_ev_set_subs(&ev
);
166 snd_seq_ev_set_direct(&ev
);
167 cmd
= command
& 0xf0;
168 chan
= command
& 0x0f;
171 ev
.type
= SND_SEQ_EVENT_NOTEOFF
;
172 ev
.data
.note
.channel
= chan
;
173 ev
.data
.note
.note
= data
[0];
174 ev
.data
.note
.velocity
= data
[1];
175 ev
.data
.note
.duration
= delta
;
178 ev
.type
= SND_SEQ_EVENT_NOTEON
;
179 ev
.data
.note
.channel
= chan
;
180 ev
.data
.note
.note
= data
[0];
181 ev
.data
.note
.velocity
= data
[1];
182 ev
.data
.note
.duration
= 0;
185 ev
.type
= SND_SEQ_EVENT_KEYPRESS
;
186 ev
.data
.control
.channel
= chan
;
187 ev
.data
.control
.param
= data
[0];
188 ev
.data
.control
.value
= data
[1];
191 ev
.type
= SND_SEQ_EVENT_CONTROLLER
;
192 ev
.data
.control
.channel
= chan
;
193 ev
.data
.control
.param
= data
[0];
194 ev
.data
.control
.value
= data
[1];
197 ev
.type
= SND_SEQ_EVENT_PGMCHANGE
;
198 ev
.data
.control
.channel
= chan
;
199 ev
.data
.control
.value
= data
[0];
202 ev
.type
= SND_SEQ_EVENT_CHANPRESS
;
203 ev
.data
.control
.channel
= chan
;
204 ev
.data
.control
.value
= data
[0];
207 ev
.type
= SND_SEQ_EVENT_PITCHBEND
;
208 ev
.data
.control
.channel
= chan
;
209 value
= data
[0] | (data
[1] << 7);
211 ev
.data
.control
.value
= value
;
214 WRITELOG(MIDILOG(3), "alsa_seq_output(): SYSEX not implemented, length=%d", length
);
215 return BX_SOUND_OUTPUT_ERR
;
217 WRITELOG(MIDILOG(3), "alsa_seq_output(): unknown command 0x%02x, length=%d", command
, length
);
218 return BX_SOUND_OUTPUT_ERR
;
220 snd_seq_event_output(alsa_seq
.handle
, &ev
);
221 snd_seq_drain_output(alsa_seq
.handle
);
222 return BX_SOUND_OUTPUT_OK
;
226 int bx_sound_linux_c::sendmidicommand(int delta
, int command
, int length
, Bit8u data
[])
228 #if BX_HAVE_ALSASOUND
229 if ((use_alsa_seq
) && (alsa_seq
.handle
!= NULL
)) {
230 return alsa_seq_output(delta
, command
, length
, data
);
236 fputc(command
, midi
);
237 fwrite(data
, 1, length
, midi
);
238 fflush(midi
); // to start playing immediately
240 return BX_SOUND_OUTPUT_OK
;
244 int bx_sound_linux_c::closemidioutput()
246 #if BX_HAVE_ALSASOUND
247 if ((use_alsa_seq
) && (alsa_seq
.handle
!= NULL
)) {
248 snd_seq_close(alsa_seq
.handle
);
249 return BX_SOUND_OUTPUT_OK
;
254 return BX_SOUND_OUTPUT_OK
;
258 int bx_sound_linux_c::openwaveoutput(char *device
)
260 #if BX_HAVE_ALSASOUND
261 use_alsa_pcm
= !strcmp(device
, "alsa");
263 return BX_SOUND_OUTPUT_OK
;
266 int length
= strlen(device
) + 1;
268 if (wavedevice
!= NULL
)
271 wavedevice
= new char[length
];
273 if (wavedevice
== NULL
)
274 return BX_SOUND_OUTPUT_ERR
;
276 strncpy(wavedevice
, device
, length
);
278 return BX_SOUND_OUTPUT_OK
;
281 #if BX_HAVE_ALSASOUND
282 int bx_sound_linux_c::alsa_pcm_open(int frequency
, int bits
, int stereo
, int format
)
285 snd_pcm_format_t fmt
;
286 snd_pcm_hw_params_t
*params
;
287 unsigned int size
, freq
;
288 int signeddata
= format
& 1;
292 if (alsa_pcm
.handle
== NULL
) {
293 ret
= snd_pcm_open(&alsa_pcm
.handle
, "default", SND_PCM_STREAM_PLAYBACK
, 0);
295 return BX_SOUND_OUTPUT_ERR
;
297 WRITELOG(WAVELOG(1), "ALSA: opened default PCM output device");
299 snd_pcm_hw_params_alloca(¶ms
);
300 snd_pcm_hw_params_any(alsa_pcm
.handle
, params
);
301 snd_pcm_hw_params_set_access(alsa_pcm
.handle
, params
, SND_PCM_ACCESS_RW_INTERLEAVED
);
303 if ((frequency
== oldfreq
) &&
305 (stereo
== oldstereo
) &&
306 (format
== oldformat
))
307 return BX_SOUND_OUTPUT_OK
; // nothing to do
314 freq
= (unsigned int)frequency
;
318 fmt
= SND_PCM_FORMAT_S16_LE
;
320 fmt
= SND_PCM_FORMAT_U16_LE
;
322 } else if (bits
== 8) {
324 fmt
= SND_PCM_FORMAT_S8
;
326 fmt
= SND_PCM_FORMAT_U8
;
329 return BX_SOUND_OUTPUT_ERR
;
331 if (stereo
) size
*= 2;
333 snd_pcm_hw_params_set_format(alsa_pcm
.handle
, params
, fmt
);
334 snd_pcm_hw_params_set_channels(alsa_pcm
.handle
, params
, (stereo
!= 0) ? 2 : 1);
335 snd_pcm_hw_params_set_rate_near(alsa_pcm
.handle
, params
, &freq
, &dir
);
337 alsa_pcm
.frames
= 32;
338 snd_pcm_hw_params_set_period_size_near(alsa_pcm
.handle
, params
, &alsa_pcm
.frames
, &dir
);
340 ret
= snd_pcm_hw_params(alsa_pcm
.handle
, params
);
342 return BX_SOUND_OUTPUT_ERR
;
344 snd_pcm_hw_params_get_period_size(params
, &alsa_pcm
.frames
, &dir
);
345 alsa_bufsize
= alsa_pcm
.frames
* size
;
346 WRITELOG(WAVELOG(4), "ALSA: buffer size set to %d", alsa_bufsize
);
347 if (alsa_buffer
!= NULL
) {
352 return BX_SOUND_OUTPUT_OK
;
356 int bx_sound_linux_c::startwaveplayback(int frequency
, int bits
, int stereo
, int format
)
359 int signeddata
= format
& 1;
361 #if BX_HAVE_ALSASOUND
363 return alsa_pcm_open(frequency
, bits
, stereo
, format
);
366 if ((wavedevice
== NULL
) || (strlen(wavedevice
) < 1))
367 return BX_SOUND_OUTPUT_ERR
;
370 wave
= open(wavedevice
, O_WRONLY
);
372 return BX_SOUND_OUTPUT_ERR
;
374 WRITELOG(WAVELOG(1), "OSS: opened output device %s", wavedevice
);
377 if ((frequency
== oldfreq
) &&
379 (stereo
== oldstereo
) &&
380 (format
== oldformat
))
381 return BX_SOUND_OUTPUT_OK
; // nothing to do
399 return BX_SOUND_OUTPUT_ERR
;
401 // set frequency etc.
402 ret
= ioctl(wave
, SNDCTL_DSP_RESET
);
404 WRITELOG(WAVELOG(4), "ioctl(SNDCTL_DSP_RESET): %s", strerror(errno
));
407 ret = ioctl(wave, SNDCTL_DSP_SETFRAGMENT, &fragment);
409 WRITELOG(WAVELOG(4), "ioctl(SNDCTL_DSP_SETFRAGMENT, %d): %s",
410 fragment, strerror(errno));
413 ret
= ioctl(wave
, SNDCTL_DSP_SETFMT
, &fmt
);
414 if (ret
!= 0) // abort if the format is unknown, to avoid playing noise
416 WRITELOG(WAVELOG(4), "ioctl(SNDCTL_DSP_SETFMT, %d): %s",
417 fmt
, strerror(errno
));
418 return BX_SOUND_OUTPUT_ERR
;
421 ret
= ioctl(wave
, SNDCTL_DSP_STEREO
, &stereo
);
423 WRITELOG(WAVELOG(4), "ioctl(SNDCTL_DSP_STEREO, %d): %s",
424 stereo
, strerror(errno
));
426 ret
= ioctl(wave
, SNDCTL_DSP_SPEED
, &frequency
);
428 WRITELOG(WAVELOG(4), "ioctl(SNDCTL_DSP_SPEED, %d): %s",
429 frequency
, strerror(errno
));
431 // ioctl(wave, SNDCTL_DSP_GETBLKSIZE, &fragment);
432 // WRITELOG(WAVELOG(4), "current output block size is %d", fragment);
434 return BX_SOUND_OUTPUT_OK
;
437 #if BX_HAVE_ALSASOUND
438 int bx_sound_linux_c::alsa_pcm_write()
442 if (alsa_buffer
== NULL
) {
443 alsa_buffer
= (char *)malloc(alsa_bufsize
);
445 while (audio_bufsize
>= alsa_bufsize
) {
446 memcpy(alsa_buffer
, audio_buffer
, alsa_bufsize
);
447 ret
= snd_pcm_writei(alsa_pcm
.handle
, alsa_buffer
, alsa_pcm
.frames
);
449 /* EPIPE means underrun */
450 WRITELOG(WAVELOG(3), "ALSA: underrun occurred");
451 snd_pcm_prepare(alsa_pcm
.handle
);
452 } else if (ret
< 0) {
453 WRITELOG(WAVELOG(3), "ALSA: error from writei: %s", snd_strerror(ret
));
454 } else if (ret
!= (int)alsa_pcm
.frames
) {
455 WRITELOG(WAVELOG(3), "ALSA: short write, write %d frames", ret
);
457 audio_bufsize
-= alsa_bufsize
;
458 memcpy(audio_buffer
, audio_buffer
+alsa_bufsize
, audio_bufsize
);
460 if ((audio_bufsize
== 0) && (alsa_buffer
!= NULL
)) {
465 return BX_SOUND_OUTPUT_OK
;
469 int bx_sound_linux_c::sendwavepacket(int length
, Bit8u data
[])
471 #if BX_HAVE_ALSASOUND
473 if ((audio_bufsize
+length
) <= BX_SOUND_LINUX_BUFSIZE
) {
474 memcpy(audio_buffer
+audio_bufsize
, data
, length
);
475 audio_bufsize
+= length
;
477 WRITELOG(WAVELOG(3), "ALSA: audio buffer overflow");
478 return BX_SOUND_OUTPUT_ERR
;
480 if (audio_bufsize
< alsa_bufsize
) {
481 return BX_SOUND_OUTPUT_OK
;
483 return alsa_pcm_write();
487 int ret
= write(wave
, data
, length
);
490 return BX_SOUND_OUTPUT_OK
;
492 WRITELOG(WAVELOG(3), "OSS: write error");
493 return BX_SOUND_OUTPUT_ERR
;
497 int bx_sound_linux_c::stopwaveplayback()
499 #if BX_HAVE_ALSASOUND
500 if (use_alsa_pcm
&& (audio_bufsize
> 0)) {
501 if (audio_bufsize
< alsa_bufsize
) {
502 memset(audio_buffer
+audio_bufsize
, 0, alsa_bufsize
-audio_bufsize
);
503 audio_bufsize
= alsa_bufsize
;
508 // ioctl(wave, SNDCTL_DSP_SYNC);
512 return BX_SOUND_OUTPUT_OK
;
515 int bx_sound_linux_c::closewaveoutput()
517 #if BX_HAVE_ALSASOUND
518 if (use_alsa_pcm
&& (alsa_pcm
.handle
!= NULL
)) {
519 snd_pcm_drain(alsa_pcm
.handle
);
520 snd_pcm_close(alsa_pcm
.handle
);
521 alsa_pcm
.handle
= NULL
;
524 if (wavedevice
!= NULL
)
534 return BX_SOUND_OUTPUT_OK
;