diff options
Diffstat (limited to 'third_party/resample/sndlib-20/audio.c')
-rw-r--r-- | third_party/resample/sndlib-20/audio.c | 9867 |
1 files changed, 9867 insertions, 0 deletions
diff --git a/third_party/resample/sndlib-20/audio.c b/third_party/resample/sndlib-20/audio.c new file mode 100644 index 00000000..32649bb3 --- /dev/null +++ b/third_party/resample/sndlib-20/audio.c @@ -0,0 +1,9867 @@ +/* Audio hardware handlers (SGI, OSS, ALSA, Sun, Windows, Mac OSX, Jack, ESD, HPUX, NetBSD) */ + +/* + * layout of this file: + * error handlers + * SGI new and old audio library + * OSS (with Sam 9407 support) + * ALSA + * Sun (has switches for OpenBSD, but they're untested) + * Windows 95/98 + * OSX + * ESD + * JACK + * HPUX + * NetBSD + * audio describers + */ + +/* + * void mus_audio_describe(void) describes the audio hardware state. + * char *mus_audio_report(void) returns the same information as a string. + * + * int mus_audio_open_output(int dev, int srate, int chans, int format, int size) + * int mus_audio_open_input(int dev, int srate, int chans, int format, int size) + * int mus_audio_write(int line, char *buf, int bytes) + * int mus_audio_close(int line) + * int mus_audio_read(int line, char *buf, int bytes) + * + * int mus_audio_mixer_read(int dev, int field, int chan, float *val) + * int mus_audio_mixer_write(int dev, int field, int chan, float *val) + * int mus_audio_initialize(void) does whatever is needed to get set up + * int mus_audio_systems(void) returns number of separate complete audio systems (soundcards essentially) + * AUDIO_SYSTEM(n) selects the nth card (counting from 0), AUDIO_SYSTEM(0) is always the default + * char *mus_audio_system_name(int system) returns some user-recognizable (?) name for the given card (don't free) + * char *mus_audio_moniker(void) returns some brief description of the overall audio setup (don't free return string). + */ + +/* error handling is tricky here -- higher levels are using many calls as probes, so + * the "error" is a sign of non-existence, not a true error. So, for nearly all + * cases, I'll use mus_print, not mus_error. + */ + +#include <mus-config.h> + +#if USE_SND && MUS_MAC_OSX && USE_MOTIF + #undef USE_MOTIF + #define USE_NO_GUI 1 + /* Xt's Boolean (/usr/include/X11/Intrinsic.h = char) collides with MacTypes.h Boolean, (actually, + * unsigned char in /Developer/SDKs/MacOSX10.4u.sdk/System/Library/Frameworks/CoreFoundation.framework/Versions/A/Headers/CFBase.h) + * but we want snd.h for other stuff, so, if Motif is in use, don't load its headers at this time + * perhaps we could use the -funsigned-char switch in gcc + */ +#endif + +#if USE_SND && MUS_MAC_OSX && HAVE_RUBY + /* if using Ruby, OpenTransport.h T_* definitions collide with Ruby's -- it isn't needed here, so... */ + #define REDEFINE_HAVE_RUBY 1 + #undef HAVE_RUBY +#endif + +#if USE_SND + #include "snd.h" +#else + #define PRINT_BUFFER_SIZE 512 + #define LABEL_BUFFER_SIZE 64 +#endif + +#if USE_SND && MUS_MAC_OSX + #define USE_MOTIF 1 + #undef USE_NO_GUI + #if REDEFINE_HAVE_RUBY + #define HAVE_RUBY 1 + #endif +#endif + +#include <math.h> +#include <stdio.h> +#if HAVE_FCNTL_H + #include <fcntl.h> +#endif +#include <errno.h> +#include <stdlib.h> +#if (defined(HAVE_LIBC_H) && (!defined(HAVE_UNISTD_H))) + #include <libc.h> +#else + #if (!(defined(_MSC_VER))) + #include <unistd.h> + #endif +#endif +#if HAVE_STRING_H + #include <string.h> +#endif + +#if HAVE_SAM_9407 + #include <sys/sam9407.h> +#endif + +#ifdef MUS_MAC_OSX +#include <CoreServices/CoreServices.h> +#include <CoreAudio/CoreAudio.h> +/* these pull in stdbool.h apparently, so they have to precede sndlib.h */ +#endif + +#include "_sndlib.h" +#include "sndlib-strings.h" + +#if (!HAVE_STRERROR) +char *strerror(int errnum) +{ + char *strerrbuf; + strerrbuf = (char *)CALLOC(LABEL_BUFFER_SIZE, sizeof(char)); + mus_snprintf(strerrbuf, LABEL_BUFFER_SIZE, "io err %d", errnum); + return(strerrbuf); +} +#endif + +#define MUS_STANDARD_ERROR(Error_Type, Error_Message) \ + mus_print("%s\n [%s[%d] %s]", Error_Message, __FILE__, __LINE__, c__FUNCTION__) + +#define MUS_STANDARD_IO_ERROR(Error_Type, IO_Func, IO_Name) \ + mus_print("%s %s: %s\n [%s[%d] %s]", IO_Func, IO_Name, strerror(errno), __FILE__, __LINE__, c__FUNCTION__) + + +static char *version_name = NULL; +static bool audio_initialized = false; + +static const char *mus_audio_device_names[] = { + S_mus_audio_default, S_mus_audio_duplex_default, S_mus_audio_adat_in, S_mus_audio_aes_in, S_mus_audio_line_out, + S_mus_audio_line_in, S_mus_audio_microphone, S_mus_audio_speakers, S_mus_audio_digital_in, S_mus_audio_digital_out, + S_mus_audio_dac_out, S_mus_audio_adat_out, S_mus_audio_aes_out, S_mus_audio_dac_filter, S_mus_audio_mixer, + S_mus_audio_line1, S_mus_audio_line2, S_mus_audio_line3, S_mus_audio_aux_input, S_mus_audio_cd, + S_mus_audio_aux_output, S_mus_audio_spdif_in, S_mus_audio_spdif_out, S_mus_audio_amp, S_mus_audio_srate, + S_mus_audio_channel, S_mus_audio_format, S_mus_audio_imix, S_mus_audio_igain, S_mus_audio_reclev, + S_mus_audio_pcm, S_mus_audio_pcm2, S_mus_audio_ogain, S_mus_audio_line, S_mus_audio_synth, + S_mus_audio_bass, S_mus_audio_treble, S_mus_audio_port, S_mus_audio_samples_per_channel, + S_mus_audio_direction +}; + +static const char *mus_audio_device_name(int dev) +{ + if (MUS_AUDIO_DEVICE_OK(dev)) + return(mus_audio_device_names[dev]); + return("invalid device"); +} + +#if (!HAVE_OSS) || (HAVE_ALSA) +static const char *mus_audio_format_names[] = { + "unknown", S_mus_bshort, S_mus_mulaw, S_mus_byte, S_mus_bfloat, S_mus_bint, S_mus_alaw, S_mus_ubyte, S_mus_b24int, + S_mus_bdouble, S_mus_lshort, S_mus_lint, S_mus_lfloat, S_mus_ldouble, S_mus_ubshort, S_mus_ulshort, S_mus_l24int, + S_mus_bintn, S_mus_lintn +}; + +static const char *mus_audio_format_name(int fr) +{ + if (MUS_DATA_FORMAT_OK(fr)) + return(mus_audio_format_names[fr]); + return("invalid format"); +} +#endif + +static char *audio_strbuf = NULL; /* previous name "strbuf" collides with Mac OSX global! */ +static void pprint(char *str); + +int device_channels(int dev); +int device_gains(int dev); + +int device_channels(int dev) +{ + float val[4]; +#if USE_SND && MUS_DEBUGGING + XEN res; + res = XEN_EVAL_C_STRING("(if (defined? 'debugging-device-channels) debugging-device-channels 0)"); + if (XEN_INTEGER_P(res)) + { + int chans; + chans = XEN_TO_C_INT(res); + if (chans > 0) return(chans); + } +#endif + mus_audio_mixer_read(dev, MUS_AUDIO_CHANNEL, 0, val); + return((int)val[0]); +} + +int device_gains(int ur_dev) +{ + float val[4]; + int err; + int dev; + dev = MUS_AUDIO_DEVICE(ur_dev); + /* to get hardware gains, read device amp_field and error = none */ + if ((dev == MUS_AUDIO_DAC_FILTER) || (dev == MUS_AUDIO_MIXER)) + { + err = mus_audio_mixer_read(ur_dev, MUS_AUDIO_CHANNEL, 0, val); +#ifdef HAVE_ALSA + if (err != MUS_NO_ERROR) return(0); +#endif + return((int)val[0]); + } + err = mus_audio_mixer_read(ur_dev, MUS_AUDIO_AMP, 0, val); + if (err != MUS_NO_ERROR) return(0); + return(device_channels(ur_dev)); +} + + + +/* ------------------------------- SGI ----------------------------------------- */ + +#ifdef MUS_SGI +#define AUDIO_OK + +#include <audio.h> + +int mus_audio_systems(void) {return(1);} /* I think more than 1 is possible, but don't have a case to test with */ + +char *mus_audio_system_name(int system) {return("SGI");} + +char *mus_audio_moniker(void) +{ +#ifdef AL_RESOURCE + return("New SGI audio"); +#else + return("Old SGI audio"); +#endif +} + +#ifndef AL_RESOURCE +static char *alGetErrorString(int err) +{ + switch (err) + { + case AL_BAD_NOT_IMPLEMENTED: return("not implemented yet"); break; + case AL_BAD_PORT: return("tried to use an invalid port"); break; + case AL_BAD_CONFIG: return("tried to use an invalid configuration"); break; + case AL_BAD_DEVICE: return("tried to use an invalid device"); break; + case AL_BAD_DEVICE_ACCESS: return("unable to access the device"); break; + case AL_BAD_DIRECTION: return("invalid direction given for port"); break; + case AL_BAD_OUT_OF_MEM: return("operation has run out of memory"); break; + case AL_BAD_NO_PORTS: return("not able to allocate a port"); break; + case AL_BAD_WIDTH: return("invalid sample width given"); break; + case AL_BAD_ILLEGAL_STATE: return("an invalid state has occurred"); break; + case AL_BAD_QSIZE: return("attempt to set an invalid queue size"); break; + case AL_BAD_FILLPOINT: return("attempt to set an invalid fillpoint"); break; + case AL_BAD_BUFFER_NULL: return("null buffer pointer"); break; + case AL_BAD_COUNT_NEG: return("negative count"); break; + case AL_BAD_PVBUFFER: return("param/val buffer doesn't make sense"); break; + case AL_BAD_BUFFERLENGTH_NEG: return("negative buffer length"); break; + case AL_BAD_BUFFERLENGTH_ODD: return("odd length parameter/value buffer"); break; + case AL_BAD_CHANNELS: return("invalid channel specifier"); break; + case AL_BAD_PARAM: return("invalid parameter"); break; + case AL_BAD_SAMPFMT: return("attempt to set invalid sample format"); break; + case AL_BAD_RATE: return("invalid sample rate token"); break; + case AL_BAD_TRANSFER_SIZE: return("invalid size for sample read/write"); break; + case AL_BAD_FLOATMAX: return("invalid size for floatmax"); break; + case AL_BAD_PORTSTYLE: return("invalid port style"); break; + default: return(""); + } +} +#endif + +static char *sgi_err_buf = NULL; +static mus_print_handler_t *old_handler = NULL; + +static void sgi_mus_print(char *msg) +{ + int oserr = oserror(); + if (oserr) + { + if (sgi_err_buf == NULL) sgi_err_buf = (char *)CALLOC(PRINT_BUFFER_SIZE, sizeof(char)); + mus_snprintf(sgi_err_buf, PRINT_BUFFER_SIZE, "%s [%s]", msg, alGetErrorString(oserr)); + (*old_handler)(sgi_err_buf); + } + else (*old_handler)(msg); +} + +static void start_sgi_print(void) +{ + if (old_handler != sgi_mus_print) + old_handler = mus_print_set_handler(sgi_mus_print); +} + +static void end_sgi_print(void) +{ + if (old_handler != sgi_mus_print) + mus_print_set_handler(old_handler); + else mus_print_set_handler(NULL); +} + + +#if AL_RESOURCE + #define al_free(Line) alFreeConfig(config[Line]) + #define al_newconfig() alNewConfig() + #define al_setsampfmt(Line, Format) alSetSampFmt(Line, Format) + #define al_setchannels(Line, Chans) alSetChannels(Line, Chans) + #define al_setwidth(Line, Width) alSetWidth(Line, Width) + #define al_setqueuesize(Line, Size) alSetQueueSize(Line, Size) + #define al_openport(Name, Flag, Line) alOpenPort(Name, Flag, Line) + #define al_getfilled(Port) alGetFilled(Port) + #define al_closeport(Port) alClosePort(Port) + #define al_freeconfig(Config) alFreeConfig(Config) +#else + #define al_free(Line) ALfreeconfig(config[Line]); + #define al_newconfig() ALnewconfig() + #define al_setsampfmt(Line, Format) ALsetsampfmt(Line, Format) + #define al_setchannels(Line, Chans) ALsetchannels(Line, Chans) + #define al_setwidth(Line, Width) ALsetwidth(Line, Width) + #define al_setqueuesize(Line, Size) ALsetqueuesize(Line, Size) + #define al_openport(Name, Flag, Line) ALopenport(Name, Flag, Line) + #define al_getfilled(Port) ALgetfilled(Port) + #define al_closeport(Port) ALcloseport(Port) + #define al_freeconfig(Config) ALfreeconfig(Config) +#endif + + +#define RETURN_ERROR_EXIT(Error_Type, Audio_Line, Ur_Error_Message) \ + do { \ + char *Error_Message; Error_Message = Ur_Error_Message; \ + if (Audio_Line != -1) al_free(Audio_Line); \ + if (Error_Message) \ + { \ + MUS_STANDARD_ERROR(Error_Type, Error_Message); FREE(Error_Message); \ + } \ + else MUS_STANDARD_ERROR(Error_Type, mus_error_type_to_string(Error_Type)); \ + end_sgi_print(); \ + return(MUS_ERROR); \ + } while (false) + + +#ifdef AL_RESOURCE + +static int check_queue_size(int size, int chans) +{ + if (size > chans * 1024) + return(size); + else return(chans * 1024); +} + +#else + +#define STEREO_QUEUE_MIN_SIZE 1024 +#define STEREO_QUEUE_MIN_CHOICE 1024 +/* docs say 510 or 512, but they die with "File size limit exceeded" %$@#!(& */ +#define MONO_QUEUE_MIN_SIZE 1019 +#define MONO_QUEUE_MIN_CHOICE 1024 +#define STEREO_QUEUE_MAX_SIZE 131069 +#define STEREO_QUEUE_MAX_CHOICE 65536 +#define MONO_QUEUE_MAX_SIZE 262139 +#define MONO_QUEUE_MAX_CHOICE 131072 +/* if these limits are not followed, the damned thing dumps core and dies */ + +static int check_queue_size(int size, int chans) +{ + if ((chans == 1) && (size > MONO_QUEUE_MAX_SIZE)) return(MONO_QUEUE_MAX_CHOICE); + if ((chans == 1) && (size < MONO_QUEUE_MIN_SIZE)) return(MONO_QUEUE_MIN_CHOICE); + if ((chans > 1) && (size > STEREO_QUEUE_MAX_SIZE)) return(STEREO_QUEUE_MAX_CHOICE); + if ((chans > 1) && (size < STEREO_QUEUE_MIN_SIZE)) return(STEREO_QUEUE_MIN_CHOICE); + return(size); +} + +static void check_quad(int device, int channels) +{ + long sr[2]; + /* if quad, make sure we are set up for it, else make sure we aren't (perhaps the latter is unnecessary) */ + /* in 4 channel mode, stereo mic and line-in are 4 inputs, headphones/speakers and stereo line-out are the 4 outputs */ + sr[0] = AL_CHANNEL_MODE; + ALgetparams(device, sr, 2); + if ((channels == 4) && (sr[1] != AL_4CHANNEL)) + { + sr[1] = AL_4CHANNEL; + ALsetparams(device, sr, 2); + } + else + { + if ((channels != 4) && (sr[1] != AL_STEREO)) + { + sr[1] = AL_STEREO; + ALsetparams(device, sr, 2); + } + } +} +#endif + +#define IO_LINES 8 +static ALconfig *config = NULL; +static ALport *port = NULL; +static int *line_in_use = NULL; +static int *channels = NULL; +static long *device = NULL; +static int *datum_size = NULL; +static int *line_out = NULL; + +int mus_audio_initialize(void) +{ + if (!audio_initialized) + { + audio_initialized = true; + config = (ALconfig *)CALLOC(IO_LINES, sizeof(ALconfig)); + port = (ALport *)CALLOC(IO_LINES, sizeof(ALport)); + line_in_use = (int *)CALLOC(IO_LINES, sizeof(int)); + channels = (int *)CALLOC(IO_LINES, sizeof(int)); + device = (long *)CALLOC(IO_LINES, sizeof(long)); + datum_size = (int *)CALLOC(IO_LINES, sizeof(int)); + line_out = (int *)CALLOC(IO_LINES, sizeof(int)); + } + return(MUS_NO_ERROR); +} + +#ifdef AL_RESOURCE +static int to_al_interface_or_device(int dev, int which) +{ + switch (dev) + { + case MUS_AUDIO_DEFAULT: + case MUS_AUDIO_DUPLEX_DEFAULT: return(AL_DEFAULT_OUTPUT); break; + case MUS_AUDIO_DAC_OUT: + case MUS_AUDIO_SPEAKERS: return(alGetResourceByName(AL_SYSTEM, "Analog Out", which)); break; + case MUS_AUDIO_MICROPHONE: return(alGetResourceByName(AL_SYSTEM, "Microphone", which)); break; + case MUS_AUDIO_ADAT_IN: return(alGetResourceByName(AL_SYSTEM, "ADAT In", which)); break; + case MUS_AUDIO_AES_IN: return(alGetResourceByName(AL_SYSTEM, "AES In", which)); break; + case MUS_AUDIO_ADAT_OUT: return(alGetResourceByName(AL_SYSTEM, "ADAT Out", which)); break; + case MUS_AUDIO_DIGITAL_OUT: + case MUS_AUDIO_AES_OUT: return(alGetResourceByName(AL_SYSTEM, "AES Out", which)); break; + case MUS_AUDIO_LINE_IN: return(alGetResourceByName(AL_SYSTEM, "Line In", which)); break; + case MUS_AUDIO_LINE_OUT: return(alGetResourceByName(AL_SYSTEM, "Line Out2", which)); break; /* ?? */ + /* case MUS_AUDIO_DIGITAL_IN: return(alGetResourceByName(AL_SYSTEM, "DAC2 In", which)); break; */ /* this is analog in ?? */ + } + return(MUS_ERROR); +} + +static int to_al_device(int dev) +{ + return(to_al_interface_or_device(dev, AL_DEVICE_TYPE)); +} + +static int to_al_interface(int dev) +{ + return(to_al_interface_or_device(dev, AL_INTERFACE_TYPE)); +} +#endif + +#include <stdio.h> + +/* just a placeholder for now */ +int find_audio_output(int chans) +{ +#ifdef AL_RESOURCE + ALvalue x[32]; + ALpv y; + int n, i; + y.param = AL_INTERFACE; + y.value.i = AL_DIGITAL_IF_TYPE; + n = alQueryValues(AL_SYSTEM, AL_DEFAULT_OUTPUT, x, 32, &y, 1); + for (i = 0; i < n; i++) + { + y.param = AL_CHANNELS; + alGetParams(x[i].i, &y, 1); + if (chans <= y.value.i) return(x[i].i); + } +#endif + return(MUS_ERROR); +} + +static int to_sgi_format(int frm) +{ + switch (frm) + { + case MUS_BYTE: + case MUS_BSHORT: + case MUS_B24INT: return(AL_SAMPFMT_TWOSCOMP); break; + case MUS_BFLOAT: return(AL_SAMPFMT_FLOAT); break; + case MUS_BDOUBLE: return(AL_SAMPFMT_DOUBLE); break; + } + return(MUS_ERROR); +} + +int mus_audio_open_output(int ur_dev, int srate, int chans, int format, int requested_size) +{ +#ifdef AL_RESOURCE + ALpv z[2]; +#endif + long sr[2]; + int i, line, size, width, sgi_format, dev; + start_sgi_print(); + dev = MUS_AUDIO_DEVICE(ur_dev); + line = -1; + for (i = 0; i < IO_LINES; i++) + if (line_in_use[i] == 0) + { + line = i; + break; + } + if (line == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_NO_LINES_AVAILABLE, line, + "no free audio lines?"); + channels[line] = chans; + line_out[line] = 1; + + if (requested_size == 0) + size = 1024 * chans; + else size = check_queue_size(requested_size, chans); + /* if (chans > 2) size = 65536; */ /* for temp adat code */ + + datum_size[line] = mus_bytes_per_sample(format); + if (datum_size[line] == 3) + width = AL_SAMPLE_24; + else + { + if (datum_size[line] == 1) + width = AL_SAMPLE_8; + else width = AL_SAMPLE_16; + } + sgi_format = to_sgi_format(format); + if (sgi_format == MUS_ERROR) + RETURN_ERROR_EXIT(MUS_AUDIO_FORMAT_NOT_AVAILABLE, -1, + mus_format("format %d (%s) not supported by SGI", + format, + mus_audio_format_name(format))); +#ifdef AL_RESOURCE + if (dev == MUS_AUDIO_DEFAULT) + device[line] = AL_DEFAULT_OUTPUT; + else device[line] = to_al_device(dev); + if (!(device[line])) + RETURN_ERROR_EXIT(MUS_AUDIO_DEVICE_NOT_AVAILABLE, -1, + mus_format("device %d (%s) not available", + dev, + mus_audio_device_name(dev))); +#if 0 + if (device_channels(dev) < chans) /* look for some device that can play this file */ + device[line] = find_audio_output(chans); + if (device[line] == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CHANNELS_NOT_AVAILABLE, -1, + mus_format("can't find %d channel device", + chans)); +#endif + if ((chans == 4) && (dev == MUS_AUDIO_DAC_OUT)) + { /* kludge around a bug in the new audio library */ + sr[0] = AL_CHANNEL_MODE; + sr[1] = AL_4CHANNEL; + ALsetparams(AL_DEFAULT_DEVICE, sr, 2); + } + z[0].param = AL_RATE; + z[0].value.ll = alDoubleToFixed((double)srate); + z[1].param = AL_MASTER_CLOCK; + /* z[1].value.i = AL_CRYSTAL_MCLK_TYPE; */ + z[1].value.i = AL_MCLK_TYPE; /* was AL_CRYSTAL_MCLK_TYPE -- digital I/O perhaps needs AL_VARIABLE_MCLK_TYPE */ + if (alSetParams(device[line], z, 2) == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_SRATE_NOT_AVAILABLE, -1, + mus_format("can't set srate of %s to %d", + mus_audio_device_name(dev), + srate)); +#else + device[line] = AL_DEFAULT_DEVICE; + check_quad(device[line], chans); + sr[0] = AL_OUTPUT_RATE; + sr[1] = srate; + if (ALsetparams(device[line], sr, 2) == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_SRATE_NOT_AVAILABLE, -1, + mus_format("can't set srate of %s to %d", + mus_audio_device_name(dev), + srate)); +#endif + + config[line] = al_newconfig(); + if (!(config[line])) + RETURN_ERROR_EXIT(MUS_AUDIO_CONFIGURATION_NOT_AVAILABLE, -1, + "can't allocate audio configuration?"); + if ((al_setsampfmt(config[line], sgi_format) == -1) || + (al_setwidth(config[line], width) == -1)) /* this is a no-op in the float and double cases */ + RETURN_ERROR_EXIT(MUS_AUDIO_FORMAT_NOT_AVAILABLE, line, + mus_format("audio format %d (%s, SGI: %d) not available on device %d (%s)", + format, mus_audio_format_name(format), sgi_format, + dev, + mus_audio_device_name(dev))); + if (al_setchannels(config[line], chans) == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CHANNELS_NOT_AVAILABLE, line, + mus_format("can't get %d channels on device %d (%s)", + chans, dev, mus_audio_device_name(dev))); + + /* set queue size probably needs a check first for legal queue sizes given the current desired device */ + /* in new AL, I'm assuming above (check_queue_size) that it needs at least 1024 per chan */ + if (al_setqueuesize(config[line], size) == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_SIZE_NOT_AVAILABLE, line, + mus_format("can't get queue size %d on device %d (%s) (chans: %d, requested_size: %d)", + size, dev, + mus_audio_device_name(dev), + chans, requested_size)); + +#ifdef AL_RESOURCE + if (alSetDevice(config[line], device[line]) == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_DEVICE_NOT_AVAILABLE, line, + mus_format("can't get device %d (%s)", + dev, + mus_audio_device_name(dev))); +#endif + + port[line] = al_openport("dac", "w", config[line]); + if (!(port[line])) + RETURN_ERROR_EXIT(MUS_AUDIO_CONFIGURATION_NOT_AVAILABLE, line, + mus_format("can't open output port on device %d (%s)", + dev, + mus_audio_device_name(dev))); + line_in_use[line] = 1; + end_sgi_print(); + return(line); +} + +int mus_audio_write(int line, char *buf, int bytes) +{ + start_sgi_print(); +#ifdef AL_RESOURCE + if (alWriteFrames(port[line], (short *)buf, bytes / (channels[line] * datum_size[line]))) +#else + if (ALwritesamps(port[line], (short *)buf, bytes / datum_size[line])) +#endif + RETURN_ERROR_EXIT(MUS_AUDIO_WRITE_ERROR, -1, + "write error"); + end_sgi_print(); + return(MUS_NO_ERROR); +} + +int mus_audio_close(int line) +{ + int err; + start_sgi_print(); + if (line_in_use[line]) + { + if (line_out[line]) + while (al_getfilled(port[line]) > 0) + sginap(1); + err = ((al_closeport(port[line])) || + (al_freeconfig(config[line]))); + line_in_use[line] = 0; + if (err) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_CLOSE, -1, + mus_format("can't close audio port %p (line %d)", + port[line], line)); + } + end_sgi_print(); + return(MUS_NO_ERROR); +} + +int mus_audio_open_input(int ur_dev, int srate, int chans, int format, int requested_size) +{ + int dev; +#ifdef AL_RESOURCE + ALpv pv; + ALpv x[2]; +#else + long sr[2]; + int resind; +#endif + int i, line, sgi_format; + start_sgi_print(); + dev = MUS_AUDIO_DEVICE(ur_dev); + line = -1; + for (i = 0; i < IO_LINES; i++) + if (line_in_use[i] == 0) + { + line = i; + break; + } + if (line == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_NO_LINES_AVAILABLE, -1, + "no free audio lines?"); + channels[line] = chans; + line_out[line] = 0; + datum_size[line] = mus_bytes_per_sample(format); +#ifdef AL_RESOURCE + if (dev == MUS_AUDIO_DEFAULT) + device[line] = AL_DEFAULT_INPUT; + else + { + int itf; + device[line] = to_al_device(dev); + itf = to_al_interface(dev); + if (itf) + { + pv.param = AL_INTERFACE; + pv.value.i = itf; + if (alSetParams(device[line], &pv, 1) == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CONFIGURATION_NOT_AVAILABLE, -1, + mus_format("can't set up device %d (%s)", + dev, + mus_audio_device_name(dev))); + } + } + if (!(device[line])) + RETURN_ERROR_EXIT(MUS_AUDIO_DEVICE_NOT_AVAILABLE, -1, + mus_format("can't get input device %d (%s)", + dev, mus_audio_device_name(dev))); + x[0].param = AL_RATE; + x[0].value.ll = alDoubleToFixed((double)srate); + x[1].param = AL_MASTER_CLOCK; + x[1].value.i = AL_MCLK_TYPE; /* AL_CRYSTAL_MCLK_TYPE; */ + if (alSetParams(device[line], x, 2) == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_SRATE_NOT_AVAILABLE, -1, + mus_format("can't set srate of %s to %d", + mus_audio_device_name(dev), + srate)); +#else + switch (dev) + { + case MUS_AUDIO_DEFAULT: + case MUS_AUDIO_DUPLEX_DEFAULT: + case MUS_AUDIO_MICROPHONE: resind = AL_INPUT_MIC; break; + case MUS_AUDIO_LINE_IN: resind = AL_INPUT_LINE; break; + case MUS_AUDIO_DIGITAL_IN: resind = AL_INPUT_DIGITAL; break; + default: + RETURN_ERROR_EXIT(MUS_AUDIO_CONFIGURATION_NOT_AVAILABLE, -1, + mus_format("audio input device %d (%s) not available", + dev, + mus_audio_device_name(dev))); + break; + } + device[line] = AL_DEFAULT_DEVICE; + sr[0] = AL_INPUT_SOURCE; + sr[1] = resind; + if (ALsetparams(device[line], sr, 2) == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CONFIGURATION_NOT_AVAILABLE, -1, + mus_format("can't set up input device %d (%s)", + dev, + mus_audio_device_name(dev))); + check_quad(device[line], chans); + sr[0] = AL_INPUT_RATE; + sr[1] = srate; + if (ALsetparams(device[line], sr, 2) == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_SRATE_NOT_AVAILABLE, -1, + mus_format("can't set srate of %s to %d", + mus_audio_device_name(dev), + srate)); +#endif + + config[line] = al_newconfig(); + if (!(config[line])) + RETURN_ERROR_EXIT(MUS_AUDIO_CONFIGURATION_NOT_AVAILABLE, -1, + "can't allocate audio configuration?"); + sgi_format = to_sgi_format(format); + if (sgi_format == MUS_ERROR) + RETURN_ERROR_EXIT(MUS_AUDIO_FORMAT_NOT_AVAILABLE, -1, + mus_format("format %d (%s) not supported by SGI", + format, + mus_audio_format_name(format))); + if ((al_setsampfmt(config[line], sgi_format) == -1) || + (al_setwidth(config[line], (datum_size[line] == 2) ? AL_SAMPLE_16 : AL_SAMPLE_8) == -1)) + RETURN_ERROR_EXIT(MUS_AUDIO_FORMAT_NOT_AVAILABLE, line, + mus_format("audio format %d (%s, SGI: %d) not available on device %d (%s)", + format, + mus_audio_format_name(format), sgi_format, + dev, + mus_audio_device_name(dev))); + if (al_setchannels(config[line], chans) == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CHANNELS_NOT_AVAILABLE, line, + mus_format("can't get %d channels on device %d (%s)", + chans, dev, + mus_audio_device_name(dev))); + +#ifdef AL_RESOURCE + if (alSetDevice(config[line], device[line]) == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_DEVICE_NOT_AVAILABLE, line, + mus_format("can't get device %d (%s)", + dev, + mus_audio_device_name(dev))); +#endif + + port[line] = al_openport("adc", "r", config[line]); + if (!(port[line])) + RETURN_ERROR_EXIT(MUS_AUDIO_CONFIGURATION_NOT_AVAILABLE, line, + mus_format("can't open input port on device %d (%s)", + dev, + mus_audio_device_name(dev))); + line_in_use[line] = 1; + end_sgi_print(); + return(line); +} + +int mus_audio_read(int line, char *buf, int bytes) +{ + start_sgi_print(); +#ifdef AL_RESOURCE + if (alReadFrames(port[line], (short *)buf, bytes / (channels[line] * datum_size[line]))) +#else + if (ALreadsamps(port[line], (short *)buf, bytes / datum_size[line])) +#endif + RETURN_ERROR_EXIT(MUS_AUDIO_READ_ERROR, -1, + "read error"); + end_sgi_print(); + return(MUS_NO_ERROR); +} + + +#ifdef AL_RESOURCE +/* borrowed from /usr/share/src/dmedia/audio/printdevs.c with modifications */ + +#define MAX_CHANNELS 8 + +static float dB_to_linear(float val) +{ + if (val == 0.0) return(1.0); + return(pow(10.0, val / 20.0)); +} + +static float dB_to_normalized(float val, float lo, float hi) +{ + float linlo; + if (hi <= lo) return(1.0); + linlo = dB_to_linear(lo); + return((dB_to_linear(val) - linlo) / (dB_to_linear(hi) - linlo)); +} + +static float normalized_to_dB(float val_norm, float lo_dB, float hi_dB) +{ + if (hi_dB <= lo_dB) return(0.0); + return(lo_dB + (hi_dB - lo_dB) * val_norm); +} + +int mus_audio_mixer_read(int ur_dev, int field, int chan, float *val) +{ + ALpv x[4]; + ALparamInfo pinf; + ALfixed g[MAX_CHANNELS]; + int rv = 0, i, dev; + start_sgi_print(); + dev = MUS_AUDIO_DEVICE(ur_dev); + if (field != MUS_AUDIO_PORT) + { + rv = to_al_device(dev); + if (!rv) + RETURN_ERROR_EXIT(MUS_AUDIO_DEVICE_NOT_AVAILABLE, -1, + mus_format("can't read %s field %d (%s)", + mus_audio_device_name(dev), + field, + mus_audio_device_name(field))); + } + switch (field) + { + case MUS_AUDIO_PORT: + /* in this case, chan == length of incoming val array. Number of devices is returned as val[0], + * and the rest of the available area (if needed) is filled with the device ids. + */ + i = 0; + if (alGetResourceByName(AL_SYSTEM, "Microphone", AL_DEVICE_TYPE) != 0) {if ((i + 1) < chan) val[i + 1] = MUS_AUDIO_MICROPHONE; i++;} + if (alGetResourceByName(AL_SYSTEM, "Analog Out", AL_DEVICE_TYPE) != 0) {if ((i + 1) < chan) val[i + 1] = MUS_AUDIO_DAC_OUT; i++;} + if (alGetResourceByName(AL_SYSTEM, "ADAT In", AL_DEVICE_TYPE) != 0) {if ((i + 1) < chan) val[i + 1] = MUS_AUDIO_ADAT_IN; i++;} + if (alGetResourceByName(AL_SYSTEM, "AES In", AL_DEVICE_TYPE) != 0) {if ((i + 1) < chan) val[i + 1] = MUS_AUDIO_AES_IN; i++;} + if (alGetResourceByName(AL_SYSTEM, "ADAT Out", AL_DEVICE_TYPE) != 0) {if ((i + 1) < chan) val[i + 1] = MUS_AUDIO_ADAT_OUT; i++;} + if (alGetResourceByName(AL_SYSTEM, "AES Out", AL_DEVICE_TYPE) != 0) {if ((i + 1) < chan) val[i + 1] = MUS_AUDIO_AES_OUT; i++;} + if (alGetResourceByName(AL_SYSTEM, "Line In", AL_DEVICE_TYPE) != 0) {if ((i + 1) < chan) val[i + 1] = MUS_AUDIO_LINE_IN; i++;} + /* if (alGetResourceByName(AL_SYSTEM, "DAC2 In", AL_DEVICE_TYPE) != 0) {if ((i + 1) < chan) val[i + 1] = MUS_AUDIO_DIGITAL_IN; i++;} */ + val[0] = i; + break; + case MUS_AUDIO_FORMAT: + val[0] = 1; + if (chan > 1) val[1] = MUS_BSHORT; + break; + case MUS_AUDIO_CHANNEL: + x[0].param = AL_CHANNELS; + if (alGetParams(rv, x, 1) == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_READ_ERROR, -1, + mus_format("can't read channel setting of %s", + mus_audio_device_name(dev))); + val[0] = (float)(x[0].value.i); + break; + case MUS_AUDIO_SRATE: + x[0].param = AL_RATE; + if (alGetParams(rv, x, 1) == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_READ_ERROR, -1, + mus_format("can't read srate setting of %s", + mus_audio_device_name(dev))); + val[0] = (float)(x[0].value.i); + break; + case MUS_AUDIO_AMP: + if (alGetParamInfo(rv, AL_GAIN, &pinf) == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_READ_ERROR, -1, + mus_format("can't read gain settings of %s", + mus_audio_device_name(dev))); + if (pinf.min.ll == pinf.max.ll) + RETURN_ERROR_EXIT(MUS_AUDIO_AMP_NOT_AVAILABLE, -1, + mus_format("%s's gain apparently can't be set", + mus_audio_device_name(dev))); + /* this ridiculous thing is in dB with completely arbitrary min and max values */ + x[0].param = AL_GAIN; + x[0].value.ptr = g; + x[0].sizeIn = MAX_CHANNELS; + alGetParams(rv, x, 1); + if (x[0].sizeOut <= chan) + RETURN_ERROR_EXIT(MUS_AUDIO_CHANNELS_NOT_AVAILABLE, -1, + mus_format("can't read gain settings of %s channel %d", + mus_audio_device_name(dev), chan)); + val[0] = dB_to_normalized(alFixedToDouble(g[chan]), + alFixedToDouble(pinf.min.ll), + alFixedToDouble(pinf.max.ll)); + break; + default: + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_READ, -1, + mus_format("can't read %s setting of %s", + mus_audio_device_name(field), + mus_audio_device_name(dev))); + break; + } + end_sgi_print(); + return(MUS_NO_ERROR); +} + +int mus_audio_mixer_write(int ur_dev, int field, int chan, float *val) +{ + /* each field coming in assumes 0.0 to 1.0 as the range */ + ALpv x[4]; + ALparamInfo pinf; + ALfixed g[MAX_CHANNELS]; + int rv, dev; + start_sgi_print(); + dev = MUS_AUDIO_DEVICE(ur_dev); + rv = to_al_device(dev); + if (!rv) RETURN_ERROR_EXIT(MUS_AUDIO_DEVICE_NOT_AVAILABLE, -1, + mus_format("can't write %s field %d (%s)", + mus_audio_device_name(dev), + field, + mus_audio_device_name(field))); + switch (field) + { + case MUS_AUDIO_SRATE: + x[0].param = AL_RATE; + x[0].value.i = (int)val[0]; + x[1].param = AL_MASTER_CLOCK; + x[1].value.i = AL_CRYSTAL_MCLK_TYPE; + alSetParams(rv, x, 2); + break; + case MUS_AUDIO_AMP: + /* need to change normalized linear value into dB between (dB) lo and hi */ + if (alGetParamInfo(rv, AL_GAIN, &pinf) == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_READ_ERROR, -1, + mus_format("can't write gain settings of %s", + mus_audio_device_name(dev))); + /* I think we need to read all channels here, change the one we care about, then write all channels */ + x[0].param = AL_GAIN; + x[0].value.ptr = g; + x[0].sizeIn = MAX_CHANNELS; + alGetParams(rv, x, 1); + if (x[0].sizeOut <= chan) + RETURN_ERROR_EXIT(MUS_AUDIO_CHANNELS_NOT_AVAILABLE, -1, + mus_format("can't write gain settings of %s channel %d", + mus_audio_device_name(dev), + chan)); + g[chan] = alDoubleToFixed(normalized_to_dB(val[0], + alFixedToDouble(pinf.min.ll), + alFixedToDouble(pinf.max.ll))); + if (alSetParams(rv, x, 1) == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_WRITE_ERROR, -1, + mus_format("can't write gain settings of %s", + mus_audio_device_name(dev))); + break; + default: + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_READ, -1, + mus_format("can't write %s setting of %s", + mus_audio_device_name(field), + mus_audio_device_name(dev))); + break; + } + end_sgi_print(); + return(MUS_NO_ERROR); +} + +#define STRING_SIZE 32 +static void dump_resources(ALvalue *x, int rv) +{ + ALpv y[4]; + ALparamInfo pinf; + ALfixed g[MAX_CHANNELS]; + char dn[STRING_SIZE]; + char dl[STRING_SIZE]; + int i, k; + ALvalue z[16]; + int nres; + for (i = 0; i < rv; i++) + { + y[0].param = AL_LABEL; + y[0].value.ptr = dl; + y[0].sizeIn = STRING_SIZE; + y[1].param = AL_NAME; + y[1].value.ptr = dn; + y[1].sizeIn = STRING_SIZE; + y[2].param = AL_CHANNELS; + y[3].param = AL_RATE; + alGetParams(x[i].i, y, 5); + if (alIsSubtype(AL_DEVICE_TYPE, x[i].i)) + { + alGetParamInfo(x[i].i, AL_GAIN, &pinf); + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "\nDevice: %s (%s), srate: %d, chans: %d", + dn, dl, + y[3].value.i, + y[2].value.i); + pprint(audio_strbuf); + if (pinf.min.ll != pinf.max.ll) + { + ALpv yy; + yy.param = AL_GAIN; + yy.value.ptr = g; + yy.sizeIn = MAX_CHANNELS; + alGetParams(x[i].i, &yy, 1); + pprint(" amps:["); + for (k = 0; k < yy.sizeOut; k++) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "%.2f", + dB_to_normalized(alFixedToDouble(g[k]), + alFixedToDouble(pinf.min.ll), + alFixedToDouble(pinf.max.ll))); + pprint(audio_strbuf); + if (k < (yy.sizeOut - 1)) pprint(" "); + } + pprint("]"); + } + pprint("\n"); + if ((nres= alQueryValues(x[i].i, AL_INTERFACE, z, 16, 0, 0)) >= 0) + dump_resources(z, nres); + else mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "query failed: %s\n", alGetErrorString(oserror())); + pprint(audio_strbuf); + } + else + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " %s (%s), chans: %d\n", dn, dl, y[2].value.i); + pprint(audio_strbuf); + } + } +} + +static void describe_audio_state_1(void) +{ + int rv; + ALvalue x[16]; + pprint("Devices and Interfaces on this system:\n"); + rv= alQueryValues(AL_SYSTEM, AL_DEVICES, x, 16, 0, 0); + if (rv > 0) + dump_resources(x, rv); +} + + +#else + +/* old audio library */ + +#define MAX_VOLUME 255 + +static int decode_field(int dev, int field, int chan) +{ + switch (dev) + { + case MUS_AUDIO_DEFAULT: + case MUS_AUDIO_DAC_OUT: + case MUS_AUDIO_DUPLEX_DEFAULT: + case MUS_AUDIO_SPEAKERS: + switch (field) + { + case MUS_AUDIO_AMP: + return((chan == 0) ? AL_LEFT_SPEAKER_GAIN : AL_RIGHT_SPEAKER_GAIN); + break; + case MUS_AUDIO_SRATE: + return(AL_OUTPUT_RATE); + break; + } + break; + case MUS_AUDIO_LINE_OUT: + switch (field) + { + case MUS_AUDIO_SRATE: + return(AL_OUTPUT_RATE); /* ? */ + break; + } + break; + case MUS_AUDIO_DIGITAL_OUT: + if (field == MUS_AUDIO_SRATE) + return(AL_OUTPUT_RATE); + break; + case MUS_AUDIO_DIGITAL_IN: + if (field == MUS_AUDIO_SRATE) + return(AL_INPUT_RATE); + break; + case MUS_AUDIO_LINE_IN: + if (field == MUS_AUDIO_AMP) + return((chan == 0) ? AL_LEFT_INPUT_ATTEN : AL_RIGHT_INPUT_ATTEN); + else + if (field == MUS_AUDIO_SRATE) + return(AL_INPUT_RATE); + break; + case MUS_AUDIO_MICROPHONE: + if (field == MUS_AUDIO_AMP) + return((chan == 0) ? AL_LEFT2_INPUT_ATTEN : AL_RIGHT2_INPUT_ATTEN); + else + if (field == MUS_AUDIO_SRATE) + return(AL_INPUT_RATE); + break; + } + return(MUS_ERROR); +} + +int mus_audio_mixer_read(int ur_dev, int field, int chan, float *val) +{ + long pb[4]; + long fld; + int dev, err = MUS_NO_ERROR; + start_sgi_print(); + dev = MUS_AUDIO_DEVICE(ur_dev); + switch (field) + { + case MUS_AUDIO_CHANNEL: + val[0] = 4; + break; + case MUS_AUDIO_FORMAT: + val[0] = 1; + if (chan > 1) val[1] = MUS_BSHORT; + break; + case MUS_AUDIO_PORT: + /* how to tell which machine we're on? */ + val[0] = 4; + if (chan > 1) val[1] = MUS_AUDIO_LINE_IN; + if (chan > 2) val[2] = MUS_AUDIO_MICROPHONE; + if (chan > 3) val[3] = MUS_AUDIO_DIGITAL_IN; + if (chan > 4) val[4] = MUS_AUDIO_DAC_OUT; + /* does this order work for digital input as well? (i.e. does it replace the microphone)? */ + break; + case MUS_AUDIO_AMP: + fld = decode_field(dev, field, chan); + if (fld != MUS_ERROR) + { + pb[0] = fld; + if (ALgetparams(AL_DEFAULT_DEVICE, pb, 2)) + RETURN_ERROR_EXIT(MUS_AUDIO_READ_ERROR, -1, + mus_format("can't read gain settings of %s", + mus_audio_device_name(dev))); + if ((fld == AL_LEFT_SPEAKER_GAIN) || + (fld == AL_RIGHT_SPEAKER_GAIN)) + val[0] = ((float)pb[1]) / ((float)MAX_VOLUME); + else val[0] = 1.0 - ((float)pb[1]) / ((float)MAX_VOLUME); + } + else err = MUS_ERROR; + break; + case MUS_AUDIO_SRATE: + fld = decode_field(dev, field, chan); + if (fld != MUS_ERROR) + { + pb[0] = fld; + if (ALgetparams(AL_DEFAULT_DEVICE, pb, 2)) + RETURN_ERROR_EXIT(MUS_AUDIO_READ_ERROR, -1, + mus_format("can't read srate setting of %s", + mus_audio_device_name(dev))); + val[0] = pb[1]; + } + else err = MUS_ERROR; + break; + default: + err = MUS_ERROR; + break; + } + if (err == MUS_ERROR) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_READ, -1, + mus_format("can't read %s setting of %s", + mus_audio_device_name(field), + mus_audio_device_name(dev))); + end_sgi_print(); + return(MUS_NO_ERROR); +} + +int mus_audio_mixer_write(int ur_dev, int field, int chan, float *val) +{ + long pb[4]; + long fld; + int dev, err = MUS_NO_ERROR; + start_sgi_print(); + dev = MUS_AUDIO_DEVICE(ur_dev); + switch (field) + { + case MUS_AUDIO_PORT: + if (dev == MUS_AUDIO_DEFAULT) + { + pb[0] = AL_CHANNEL_MODE; + pb[1] = ((chan == MUS_AUDIO_DIGITAL_IN) ? AL_STEREO : AL_4CHANNEL); + pb[2] = AL_INPUT_SOURCE; + pb[3] = ((chan == MUS_AUDIO_DIGITAL_IN) ? AL_INPUT_DIGITAL : AL_INPUT_MIC); + if (ALsetparams(AL_DEFAULT_DEVICE, pb, 4)) + RETURN_ERROR_EXIT(MUS_AUDIO_WRITE_ERROR, -1, + mus_format("can't set mode and source of %s", + mus_audio_device_name(dev))); + } + else err = MUS_ERROR; + break; + case MUS_AUDIO_CHANNEL: + if (dev == MUS_AUDIO_MICROPHONE) + { + pb[0] = AL_MIC_MODE; + pb[1] = ((chan == 2) ? AL_STEREO : AL_MONO); + if (ALsetparams(AL_DEFAULT_DEVICE, pb, 2)) + RETURN_ERROR_EXIT(MUS_AUDIO_WRITE_ERROR, -1, + mus_format("can't set microphone to be %s", + (chan == 2) ? "stereo" : "mono")); + } + else + { + if (dev == MUS_AUDIO_DEFAULT) + { + pb[0] = AL_CHANNEL_MODE; + pb[1] = ((chan == 4) ? AL_4CHANNEL : AL_STEREO); + if (ALsetparams(AL_DEFAULT_DEVICE, pb, 2)) + RETURN_ERROR_EXIT(MUS_AUDIO_WRITE_ERROR, -1, + mus_format("can't set default device to be %s", + (chan == 4) ? "quad" : "stereo")); + } + else err = MUS_ERROR; + } + break; + case MUS_AUDIO_AMP: + fld = decode_field(dev, field, chan); + if (fld != -1) + { + pb[0] = fld; + if ((fld == AL_LEFT_SPEAKER_GAIN) || + (fld == AL_RIGHT_SPEAKER_GAIN)) + pb[1] = val[0] * MAX_VOLUME; + else pb[1] = (1.0 - val[0]) * MAX_VOLUME; + if (ALsetparams(AL_DEFAULT_DEVICE, pb, 2)) + RETURN_ERROR_EXIT(MUS_AUDIO_WRITE_ERROR, -1, + mus_format("can't set gain of %s", + mus_audio_device_name(dev))); + } + else err = MUS_ERROR; + break; + case MUS_AUDIO_SRATE: + fld = decode_field(dev, field, chan); + if (fld != -1) + { + pb[0] = fld; + pb[1] = val[0]; + if (ALsetparams(AL_DEFAULT_DEVICE, pb, 2)) + RETURN_ERROR_EXIT(MUS_AUDIO_WRITE_ERROR, -1, NULL); + if (fld == AL_INPUT_RATE) + { + pb[0] = AL_OUTPUT_RATE; + pb[1] = val[0]; + if (ALsetparams(AL_DEFAULT_DEVICE, pb, 2)) + RETURN_ERROR_EXIT(MUS_AUDIO_WRITE_ERROR, -1, + mus_format("can't set srate of %s", + mus_audio_device_name(dev))); + } + } + else err = MUS_ERROR; + break; + default: + err = MUS_ERROR; + break; + } + if (err == MUS_ERROR) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_WRITE, -1, + mus_format("can't write %s setting of %s", + mus_audio_device_name(field), + mus_audio_device_name(dev))); + end_sgi_print(); + return(MUS_NO_ERROR); +} + +static void describe_audio_state_1(void) +{ + float amps[1]; + int err; + err = mus_audio_mixer_read(MUS_AUDIO_SPEAKERS, MUS_AUDIO_SRATE, 0, amps); + if (err == MUS_NO_ERROR) + {mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "srate: %.2f\n", amps[0]); pprint(audio_strbuf);} + else {fprintf(stdout, "err: %d!\n", err); fflush(stdout);} + err = mus_audio_mixer_read(MUS_AUDIO_SPEAKERS, MUS_AUDIO_AMP, 0, amps); + if (err == MUS_NO_ERROR) {mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "speakers: %.2f", amps[0]); pprint(audio_strbuf);} + err = mus_audio_mixer_read(MUS_AUDIO_SPEAKERS, MUS_AUDIO_AMP, 1, amps); + if (err == MUS_NO_ERROR) {mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " %.2f\n", amps[0]); pprint(audio_strbuf);} + err = mus_audio_mixer_read(MUS_AUDIO_LINE_IN, MUS_AUDIO_AMP, 0, amps); + if (err == MUS_NO_ERROR) {mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "line in: %.2f", amps[0]); pprint(audio_strbuf);} + err = mus_audio_mixer_read(MUS_AUDIO_LINE_IN, MUS_AUDIO_AMP, 1, amps); + if (err == MUS_NO_ERROR) {mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " %.2f\n", amps[0]); pprint(audio_strbuf);} + err = mus_audio_mixer_read(MUS_AUDIO_MICROPHONE, MUS_AUDIO_AMP, 0, amps); + if (err == MUS_NO_ERROR) {mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "microphone: %.2f", amps[0]); pprint(audio_strbuf);} + err = mus_audio_mixer_read(MUS_AUDIO_MICROPHONE, MUS_AUDIO_AMP, 1, amps); + if (err == MUS_NO_ERROR) {mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " %.2f\n", amps[0]); pprint(audio_strbuf);} + err = mus_audio_mixer_read(MUS_AUDIO_LINE_OUT, MUS_AUDIO_AMP, 0, amps); + if (err == MUS_NO_ERROR) {mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "line out: %.2f", amps[0]); pprint(audio_strbuf);} + err = mus_audio_mixer_read(MUS_AUDIO_LINE_OUT, MUS_AUDIO_AMP, 1, amps); + if (err == MUS_NO_ERROR) {mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " %.2f\n", amps[0]); pprint(audio_strbuf);} + err = mus_audio_mixer_read(MUS_AUDIO_DIGITAL_OUT, MUS_AUDIO_AMP, 0, amps); + if (err == MUS_NO_ERROR) {mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "digital out: %.2f", amps[0]); pprint(audio_strbuf);} + err = mus_audio_mixer_read(MUS_AUDIO_DIGITAL_OUT, MUS_AUDIO_AMP, 1, amps); + if (err == MUS_NO_ERROR) {mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " %.2f\n", amps[0]); pprint(audio_strbuf);} +} + +#endif +/* new or old AL */ + +#endif +/* SGI */ + + + +/* ------------------------------- OSS ----------------------------------------- */ + +#if (HAVE_OSS || HAVE_ALSA || HAVE_JACK) +/* actually it's not impossible that someday we'll have ALSA but not OSS... */ +#define AUDIO_OK + +#include <sys/ioctl.h> + +/* the system version of the soundcard header file may have no relation to the current OSS actually loaded */ +/* sys/soundcard.h is usually just a pointer to linux/soundcard.h */ + +#if (MUS_HAVE_USR_LIB_OSS) + #include "/usr/lib/oss/include/sys/soundcard.h" +#else + #if (MUS_HAVE_USR_LOCAL_LIB_OSS) + #include "/usr/local/lib/oss/include/sys/soundcard.h" + #else + #if (MUS_HAVE_OPT_OSS) + #include "/opt/oss/include/sys/soundcard.h" + #else + #if (MUS_HAVE_VAR_LIB_OSS) + #include "/var/lib/oss/include/sys/soundcard.h" + #else + #if defined(HAVE_SYS_SOUNDCARD_H) || defined(MUS_LINUX) + #include <sys/soundcard.h> + #else + #if defined(HAVE_MACHINE_SOUNDCARD_H) + #include <machine/soundcard.h> + #else + #include <soundcard.h> + #endif + #endif + #endif + #endif + #endif +#endif + +#if ((SOUND_VERSION > 360) && (defined(OSS_SYSINFO))) + #define NEW_OSS 1 +#endif + +#define DAC_NAME "/dev/dsp" +#define MIXER_NAME "/dev/mixer" +#define SYNTH_NAME "/dev/music" +/* some programs use /dev/audio */ + +/* there can be more than one sound card installed, and a card can be handled through + * more than one /dev/dsp device, so we can't use a global dac device here. + * The caller has to keep track of the various cards (via AUDIO_SYSTEM) -- + * I toyed with embedding all that in mus_audio_open_output and mus_audio_write, but + * decided it's better to keep them explicit -- the caller may want entirely + * different (non-synchronous) streams going to the various cards. This same + * code (AUDIO_SYSTEM(n)->devn) should work in Windoze (see below), and + * might work on the Mac and SGI -- something for a rainy day... + */ + +#define RETURN_ERROR_EXIT(Message_Type, Audio_Line, Ur_Message) \ + do { \ + char *Message; Message = Ur_Message; \ + if (Audio_Line != -1) \ + linux_audio_close(Audio_Line); \ + if ((Message) && (strlen(Message) > 0)) \ + { \ + mus_print("%s\n [%s[%d] %s]", \ + Message, \ + __FILE__, __LINE__, c__FUNCTION__); \ + FREE(Message); \ + } \ + else mus_print("%s\n [%s[%d] %s]", \ + mus_error_type_to_string(Message_Type), \ + __FILE__, __LINE__, c__FUNCTION__); \ + return(MUS_ERROR); \ + } while (false) + +static int FRAGMENTS = 4; +static int FRAGMENT_SIZE = 12; +static bool fragments_locked = false; + +/* defaults here are FRAGMENTS 16 and FRAGMENT_SIZE 12; these values however + * cause about a .5 second delay, which is not acceptable in "real-time" situations. + * + * this changed 22-May-01: these are causing more trouble than they're worth + */ + +static void oss_mus_oss_set_buffers(int num, int size) {FRAGMENTS = num; FRAGMENT_SIZE = size; fragments_locked = true;} + +#define MAX_SOUNDCARDS 8 +#define MAX_DSPS 8 +#define MAX_MIXERS 8 +/* there can be (apparently) any number of mixers and dsps per soundcard, but 8 is enough! */ + +static int *audio_fd = NULL; +static int *audio_open_ctr = NULL; +static int *audio_dsp = NULL; +static int *audio_mixer = NULL; +static int *audio_mode = NULL; +typedef enum {NORMAL_CARD, SONORUS_STUDIO, RME_HAMMERFALL, SAM9407_DSP, DELTA_66} audio_card_t; +/* the Sonorus Studi/o card is a special case in all regards */ +static audio_card_t *audio_type = NULL; + +static int sound_cards = 0; +static int new_oss_running = 0; +static char *dev_name = NULL; + +static int oss_mus_audio_systems(void) +{ + return(sound_cards); +} + +static char *mixer_name(int sys) +{ +#if HAVE_SAM_9407 + if((sys < sound_cards) && (audio_type[sys] == SAM9407_DSP)) + { + mus_snprintf(dev_name, LABEL_BUFFER_SIZE, "/dev/sam%d_mixer", audio_mixer[sys]); + return(dev_name); + } +#endif + if (sys < sound_cards) + { + if (audio_mixer[sys] == -2) + return(MIXER_NAME); + /* if we have /dev/dsp (not /dev/dsp0), I assume the corresponding mixer is /dev/mixer (not /dev/mixer0) */ + /* but in sam9407 driver, there is no /dev/mixer, and everything goes through /dev/dsp */ + else + { + if (audio_mixer[sys] == -3) + return(DAC_NAME); + else + { + mus_snprintf(dev_name, LABEL_BUFFER_SIZE, "%s%d", MIXER_NAME, audio_mixer[sys]); + return(dev_name); + } + } + } + return(DAC_NAME); +} + +static char *oss_mus_audio_system_name(int system) +{ +#if HAVE_SAM_9407 + if((system < sound_cards) && (audio_type[system] == SAM9407_DSP)) + { + int fd; + fd = open(mixer_name(system), O_RDONLY, 0); + if(fd != -1) + { + static SamDriverInfo driverInfo; + if(ioctl(fd, SAM_IOC_DRIVER_INFO, &driverInfo) >= 0) + { + close(fd); + return(driverInfo.hardware); + } + close(fd); + } + return("sam9407"); + } +#endif +#ifdef NEW_OSS + static mixer_info mixinfo; + int status, ignored, fd; + fd = open(mixer_name(system), O_RDONLY, 0); + if (fd != -1) + { + status = ioctl(fd, OSS_GETVERSION, &ignored); + if (status == 0) + { + status = ioctl(fd, SOUND_MIXER_INFO, &mixinfo); + if (status == 0) + { + close(fd); + return(mixinfo.name); + } + } + close(fd); + } +#endif + return("OSS"); +} + +#if HAVE_SAM_9407 +static char *oss_mus_audio_moniker(void) {return("Sam 9407");} +#else +static char *oss_mus_audio_moniker(void) +{ + char version[LABEL_BUFFER_SIZE]; + if (version_name == NULL) version_name = (char *)CALLOC(LABEL_BUFFER_SIZE, sizeof(char)); + if (SOUND_VERSION < 361) + { + mus_snprintf(version, LABEL_BUFFER_SIZE, "%d", SOUND_VERSION); + mus_snprintf(version_name, LABEL_BUFFER_SIZE, "OSS %c.%c.%c", version[0], version[1], version[2]); + } + else + mus_snprintf(version_name, LABEL_BUFFER_SIZE, "OSS %x.%x.%x", + (SOUND_VERSION >> 16) & 0xff, + (SOUND_VERSION >> 8) & 0xff, + SOUND_VERSION & 0xff); + return(version_name); +} +#endif + +static char *dac_name(int sys, int offset) +{ +#if HAVE_SAM_9407 + if ((sys < sound_cards) && (audio_type[sys] == SAM9407_DSP)) + { + mus_snprintf(dev_name, LABEL_BUFFER_SIZE, "/dev/sam%d_dsp", audio_dsp[sys]); + return(dev_name); + } +#endif + if ((sys < sound_cards) && (audio_mixer[sys] >= -1)) + { + mus_snprintf(dev_name, LABEL_BUFFER_SIZE, "%s%d", DAC_NAME, audio_dsp[sys] + offset); + return(dev_name); + } + return(DAC_NAME); +} + +#define MIXER_SIZE SOUND_MIXER_NRDEVICES +static int **mixer_state = NULL; +static int *init_srate = NULL, *init_chans = NULL, *init_format = NULL; + +static int oss_mus_audio_initialize(void) +{ + /* here we need to set up the map of /dev/dsp and /dev/mixer to a given system */ + /* since this info is not passed to us by OSS, we have to work at it... */ + /* for the time being, I'll ignore auxiliary dsp and mixer ports (each is a special case) */ + int i, fd = -1, md, err = 0; + char dname[LABEL_BUFFER_SIZE]; + int amp, old_mixer_amp, old_dsp_amp, new_mixer_amp, responsive_field; + int devmask; +#ifdef NEW_OSS + int status, ignored; + oss_sysinfo sysinfo; + static mixer_info mixinfo; + int sysinfo_ok = 0; +#endif + int num_mixers, num_dsps, nmix, ndsp; + if (!audio_initialized) + { + audio_initialized = true; + audio_fd = (int *)CALLOC(MAX_SOUNDCARDS, sizeof(int)); + audio_open_ctr = (int *)CALLOC(MAX_SOUNDCARDS, sizeof(int)); + audio_dsp = (int *)CALLOC(MAX_SOUNDCARDS, sizeof(int)); + audio_mixer = (int *)CALLOC(MAX_SOUNDCARDS, sizeof(int)); + audio_type = (audio_card_t *)CALLOC(MAX_SOUNDCARDS, sizeof(audio_card_t)); + audio_mode = (int *)CALLOC(MAX_SOUNDCARDS, sizeof(int)); + dev_name = (char *)CALLOC(LABEL_BUFFER_SIZE, sizeof(char)); + init_srate = (int *)CALLOC(MAX_SOUNDCARDS, sizeof(int)); + init_chans = (int *)CALLOC(MAX_SOUNDCARDS, sizeof(int)); + init_format = (int *)CALLOC(MAX_SOUNDCARDS, sizeof(int)); + mixer_state = (int **)CALLOC(MAX_SOUNDCARDS, sizeof(int *)); + for (i = 0; i < MAX_SOUNDCARDS; i++) mixer_state[i] = (int *)CALLOC(MIXER_SIZE, sizeof(int)); + for (i = 0; i < MAX_SOUNDCARDS; i++) + { + audio_fd[i] = -1; + audio_open_ctr[i] = 0; + audio_dsp[i] = -1; + audio_mixer[i] = -1; + audio_type[i] = NORMAL_CARD; + } +#if HAVE_SAM_9407 + { + SamApiInfo apiInfo; + SamDriverInfo driverInfo; + for (i = 0; i < MAX_SOUNDCARDS; i++) + { + mus_snprintf(dname, LABEL_BUFFER_SIZE, "/dev/sam%d_mixer", i); + fd = open(dname, O_WRONLY); + if (fd < 0) + break; + if ((ioctl(fd, SAM_IOC_API_INFO, &apiInfo) < 0) || + (apiInfo.apiClass!=SAM_API_CLASS_VANILLA) || + (ioctl(fd, SAM_IOC_DRIVER_INFO, &driverInfo) < 0) || + (!driverInfo.haveAudio)) + { + close(fd); + continue; + } + audio_type[sound_cards] = SAM9407_DSP; + audio_dsp[sound_cards] = i; + audio_mixer[sound_cards] = i; + sound_cards++; + close(fd); + } + if(sound_cards > 0) + return(0); + } +#endif + + num_mixers = MAX_MIXERS; + num_dsps = MAX_DSPS; +#ifdef NEW_OSS + fd = open(DAC_NAME, O_WRONLY | O_NONBLOCK, 0); + if (fd == -1) fd = open(SYNTH_NAME, O_RDONLY | O_NONBLOCK, 0); + if (fd == -1) fd = open(MIXER_NAME, O_RDONLY | O_NONBLOCK, 0); + if (fd != -1) + { + status = ioctl(fd, OSS_GETVERSION, &ignored); + new_oss_running = (status == 0); + if (new_oss_running) + { + status = ioctl(fd, OSS_SYSINFO, &sysinfo); + sysinfo_ok = (status == 0); + } + if ((new_oss_running) && (sysinfo_ok)) + { + num_mixers = sysinfo.nummixers; + num_dsps = sysinfo.numaudios; + } + close(fd); + } +#endif + + /* need to get which /dev/dsp lines match which /dev/mixer lines, + * find out how many separate systems (soundcards) are available, + * fill the audio_dsp and audio_mixer arrays with the system-related numbers, + * since we have no way to tell from OSS info which mixers/dsps are the + * main ones, we'll do some messing aound to try to deduce this info. + * for example, SB uses two dsp ports and two mixers per card, whereas + * Ensoniq uses 2 dsps and 1 mixer. + * + * the data we are gathering here: + * int audio_dsp[MAX_SOUNDCARDS] -> main_dsp_port[MUS_AUDIO_PACK_SYSTEM(n)] (-1 => no such system dsp) + * int audio_mixer[MAX_SOUNDCARDS] -> main_mixer_port[MUS_AUDIO_PACK_SYSTEM(n)] + * int sound_cards = 0 -> usable systems + * all auxiliary ports are currently ignored (SB equalizer, etc) + */ + sound_cards = 0; + ndsp = 0; + nmix = 0; + while ((nmix < num_mixers) && + (ndsp < num_dsps)) + { + /* for each mixer, find associated main dsp (assumed to be first in /dev/dsp ordering) */ + /* if mixer's dsp overlaps or we run out of dsps first, ignore it (aux mixer) */ + /* our by-guess-or-by-gosh method here is to try to open the mixer. + * if that fails, quit (if very first, try at least to get the dsp setup) + * find volume field, if none, go on, else read current volume + * open next unchecked dsp, try to set volume, read current, if different we found a match -- set and go on. + * if no change, move to next dsp and try again, if no more dsps, quit (checking for null case as before) + */ + mus_snprintf(dname, LABEL_BUFFER_SIZE, "%s%d", MIXER_NAME, nmix); + md = open(dname, O_RDWR, 0); + if (md == -1) + { + if (errno == EBUSY) + { + mus_print("%s is busy: can't access it [%s[%d] %s]", + dname, + __FILE__, __LINE__, c__FUNCTION__); + nmix++; + continue; + } + else break; + } + mus_snprintf(dname, LABEL_BUFFER_SIZE, "%s%d", DAC_NAME, ndsp); + fd = open(dname, O_RDWR | O_NONBLOCK, 0); + if (fd == -1) fd = open(dname, O_RDONLY | O_NONBLOCK, 0); + if (fd == -1) fd = open(dname, O_WRONLY | O_NONBLOCK, 0); /* some output devices need this */ + if (fd == -1) + { + close(md); + if (errno == EBUSY) /* in linux /usr/include/asm/errno.h */ + { + fprintf(stderr, "%s is busy: can't access it\n", dname); + ndsp++; + continue; + } + else + { + if ((errno != ENXIO) && (errno != ENODEV)) + fprintf(stderr, "%s: %s! ", dname, strerror(errno)); + break; + } + } +#ifdef NEW_OSS + /* can't change volume yet of Sonorus, so the method above won't work -- + * try to catch this case via the mixer's name + */ + status = ioctl(md, SOUND_MIXER_INFO, &mixinfo); + if ((status == 0) && + (mixinfo.name) && + (*(mixinfo.name)) && + (strlen(mixinfo.name) > 6)) + { + if (strncmp("STUDI/O", mixinfo.name, 7) == 0) + { + /* a special case in every regard */ + audio_type[sound_cards] = SONORUS_STUDIO; + audio_mixer[sound_cards] = nmix; + nmix++; + audio_dsp[sound_cards] = ndsp; + if (num_dsps >= 21) + { + ndsp += 21; + audio_mode[sound_cards] = 1; + } + else + { + ndsp += 9; + audio_mode[sound_cards] = 0; + } + sound_cards++; + close(fd); + close(md); + continue; + } + else + { + if (strncmp("RME Digi96", mixinfo.name, 10) == 0) + { + audio_type[sound_cards] = RME_HAMMERFALL; + audio_mixer[sound_cards] = nmix; + nmix++; + audio_dsp[sound_cards] = ndsp; + sound_cards++; + close(fd); + close(md); + continue; + } + else + { + if (strncmp("M Audio Delta", mixinfo.name, 13) == 0) + { + audio_type[sound_cards] = DELTA_66; + audio_mixer[sound_cards] = nmix; + nmix++; + ndsp += 6; /* just a guess */ + audio_dsp[sound_cards] = ndsp; + sound_cards++; + close(fd); + close(md); + continue; + } + } + } + } +#endif + err = ioctl(md, SOUND_MIXER_READ_DEVMASK, &devmask); + responsive_field = SOUND_MIXER_VOLUME; + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if ((1 << i) & devmask) + { + responsive_field = i; + break; + } + if (!err) + { + err = ioctl(md, MIXER_READ(responsive_field), &old_mixer_amp); + if (!err) + { + err = ioctl(fd, MIXER_READ(responsive_field), &old_dsp_amp); + if ((!err) && (old_dsp_amp == old_mixer_amp)) + { + if (old_mixer_amp == 0) amp = 50; else amp = 0; /* 0..100 */ + err = ioctl(fd, MIXER_WRITE(responsive_field), &); + if (!err) + { + err = ioctl(md, MIXER_READ(responsive_field), &new_mixer_amp); + if (!err) + { + if (new_mixer_amp == amp) + { + /* found one! */ + audio_dsp[sound_cards] = ndsp; ndsp++; + audio_mixer[sound_cards] = nmix; nmix++; + audio_type[sound_cards] = NORMAL_CARD; + sound_cards++; + } + else ndsp++; + err = ioctl(fd, MIXER_WRITE(responsive_field), &old_dsp_amp); + } + else nmix++; + } + else ndsp++; + } + else ndsp++; + } + else nmix++; + } + else nmix++; + close(fd); + close(md); + } + if (sound_cards == 0) + { + fd = open(DAC_NAME, O_WRONLY | O_NONBLOCK, 0); + if (fd != -1) + { + sound_cards = 1; + audio_dsp[0] = 0; + audio_type[0] = NORMAL_CARD; + audio_mixer[0] = -2; /* hmmm -- need a way to see /dev/dsp as lonely outpost */ + close(fd); + fd = open(MIXER_NAME, O_RDONLY | O_NONBLOCK, 0); + if (fd == -1) + audio_mixer[0] = -3; + else close(fd); + } + } + } + return(MUS_NO_ERROR); +} + +int mus_audio_reinitialize(void) +{ + /* an experiment */ + audio_initialized = false; + return(mus_audio_initialize()); +} + +static int linux_audio_open(const char *pathname, int flags, mode_t mode, int system) +{ + /* sometimes this is simply searching for a device (so failure is not a mus_error) */ + if (audio_fd[system] == -1) + { + audio_fd[system] = open(pathname, flags, mode); + audio_open_ctr[system] = 0; + } + else audio_open_ctr[system]++; + return(audio_fd[system]); +} + +static int linux_audio_open_with_error(const char *pathname, int flags, mode_t mode, int system) +{ + int fd; + fd = linux_audio_open(pathname, flags, mode, system); + if (fd == -1) + MUS_STANDARD_IO_ERROR(MUS_AUDIO_CANT_OPEN, + ((mode == O_RDONLY) ? "open read" : + (mode == O_WRONLY) ? "open write" : "open read/write"), + pathname); + return(fd); +} + +static int find_system(int line) +{ + int i; + for (i = 0; i < sound_cards; i++) + if (line == audio_fd[i]) + return(i); + return(MUS_ERROR); +} + +static int linux_audio_close(int fd) +{ + if (fd != -1) + { + int err = 0, sys; + sys = find_system(fd); + if (sys != -1) + { + if (audio_open_ctr[sys] > 0) + audio_open_ctr[sys]--; + else + { + err = close(fd); + audio_open_ctr[sys] = 0; + audio_fd[sys] = -1; + } + } + else err = close(fd); + if (err) RETURN_ERROR_EXIT(MUS_AUDIO_CANT_CLOSE, -1, + mus_format("close %d failed: %s", + fd, strerror(errno))); + } + /* is this an error? */ + return(MUS_NO_ERROR); +} + +static int to_oss_format(int snd_format) +{ + switch (snd_format) + { + case MUS_BYTE: return(AFMT_S8); break; + case MUS_BSHORT: return(AFMT_S16_BE); break; + case MUS_UBYTE: return(AFMT_U8); break; + case MUS_MULAW: return(AFMT_MU_LAW); break; + case MUS_ALAW: return(AFMT_A_LAW); break; + case MUS_LSHORT: return(AFMT_S16_LE); break; + case MUS_UBSHORT: return(AFMT_U16_BE); break; + case MUS_ULSHORT: return(AFMT_U16_LE); break; +#ifdef NEW_OSS + case MUS_LINT: return(AFMT_S32_LE); break; + case MUS_BINT: return(AFMT_S32_BE); break; +#endif + } + return(MUS_ERROR); +} + +static char sonorus_buf[LABEL_BUFFER_SIZE]; +static char *sonorus_name(int sys, int offset) +{ + mus_snprintf(sonorus_buf, LABEL_BUFFER_SIZE, "/dev/dsp%d", offset + audio_dsp[sys]); + return(sonorus_buf); +} + +static bool fragment_set_failed = false; + +static int oss_mus_audio_open_output(int ur_dev, int srate, int chans, int format, int size) +{ + /* ur_dev is in general MUS_AUDIO_PACK_SYSTEM(n) | MUS_AUDIO_DEVICE */ + int oss_format, buffer_info, audio_out = -1, sys, dev; + char *dev_name; +#ifndef NEW_OSS + int stereo; +#endif + sys = MUS_AUDIO_SYSTEM(ur_dev); + dev = MUS_AUDIO_DEVICE(ur_dev); + oss_format = to_oss_format(format); + if (oss_format == MUS_ERROR) + RETURN_ERROR_EXIT(MUS_AUDIO_FORMAT_NOT_AVAILABLE, -1, + mus_format("format %d (%s) not available", + format, + mus_data_format_name(format))); + if (audio_type[sys] == SONORUS_STUDIO) + { + /* in this case the output devices are parcelled out to the /dev/dsp locs */ + /* dev/dsp0 is always stereo */ + switch (dev) + { + case MUS_AUDIO_DEFAULT: + if (chans > 2) + audio_out = open(sonorus_name(sys, 1), O_WRONLY, 0); + else audio_out = open(sonorus_name(sys, 0), O_WRONLY, 0); + /* probably should write to both outputs */ + if (audio_out == -1) audio_out = open("/dev/dsp", O_WRONLY, 0); + break; + case MUS_AUDIO_SPEAKERS: + audio_out = open(sonorus_name(sys, 0), O_WRONLY, 0); + if (audio_out == -1) audio_out = open("/dev/dsp", O_WRONLY, 0); + break; + case MUS_AUDIO_ADAT_OUT: case MUS_AUDIO_SPDIF_OUT: + audio_out = open(sonorus_name(sys, 1), O_WRONLY, 0); + break; + case MUS_AUDIO_AES_OUT: + audio_out = open(sonorus_name(sys, 9), O_WRONLY, 0); + break; + default: + RETURN_ERROR_EXIT(MUS_AUDIO_DEVICE_NOT_AVAILABLE, audio_out, + mus_format("Sonorus device %d (%s) not available", + dev, + mus_audio_device_name(dev))); + break; + } + if (audio_out == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_OPEN, audio_out, + mus_format("can't open Sonorus output device %d (%s): %s", + dev, + mus_audio_device_name(dev), strerror(errno))); +#ifdef NEW_OSS + if (ioctl(audio_out, SNDCTL_DSP_CHANNELS, &chans) == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CHANNELS_NOT_AVAILABLE, audio_out, + mus_format("can't get %d channels for Sonorus device %d (%s)", + chans, dev, + mus_audio_device_name(dev))); +#endif + return(audio_out); + } + +#if HAVE_SAM_9407 + if (audio_type[sys] == SAM9407_DSP) + { + char dname[LABEL_BUFFER_SIZE]; + mus_snprintf(dname, LABEL_BUFFER_SIZE, "/dev/sam%d_dsp", audio_dsp[sys]); + audio_out = open(dname, O_WRONLY); + if(audio_out == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_OPEN, audio_out, + mus_format("can't open %s: %s", + dname, + strerror(errno))); + if ((ioctl(audio_out, SNDCTL_DSP_SETFMT, &oss_format) == -1) || + (oss_format != to_oss_format(format))) + RETURN_ERROR_EXIT(MUS_AUDIO_FORMAT_NOT_AVAILABLE, audio_out, + mus_format("can't set %s format to %d (%s)", + dname, format, + mus_data_format_name(format))); + if (ioctl(audio_out, SNDCTL_DSP_CHANNELS, &chans) == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CHANNELS_NOT_AVAILABLE, audio_out, + mus_format("can't get %d channels on %s", + chans, dname)); + if (ioctl(audio_out, SNDCTL_DSP_SPEED, &srate) == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_SRATE_NOT_AVAILABLE, audio_out, + mus_format("can't set srate to %d on %s", + srate, dname)); + FRAGMENT_SIZE = 14; + buffer_info = (FRAGMENTS << 16) | (FRAGMENT_SIZE); + ioctl(audio_out, SNDCTL_DSP_SETFRAGMENT, &buffer_info); + return(audio_out); + } +#endif + + if (dev == MUS_AUDIO_DEFAULT) + audio_out = linux_audio_open_with_error(dev_name = dac_name(sys, 0), + O_WRONLY, 0, sys); + else audio_out = linux_audio_open_with_error(dev_name = dac_name(sys, (dev == MUS_AUDIO_AUX_OUTPUT) ? 1 : 0), + O_RDWR, 0, sys); + if (audio_out == -1) return(MUS_ERROR); + + /* ioctl(audio_out, SNDCTL_DSP_RESET, 0); */ /* causes clicks */ + if ((fragments_locked) && + (!(fragment_set_failed)) && + ((dev == MUS_AUDIO_DUPLEX_DEFAULT) || + (size != 0))) /* only set if user has previously called set_oss_buffers */ + { + buffer_info = (FRAGMENTS << 16) | (FRAGMENT_SIZE); + if (ioctl(audio_out, SNDCTL_DSP_SETFRAGMENT, &buffer_info) == -1) + { + /* older Linuces (or OSS's?) refuse to handle the fragment reset if O_RDWR used -- + * someone at OSS forgot to update the version number when this was fixed, so + * I have no way to get around this except to try and retry... + */ + linux_audio_close(audio_out); + audio_out = linux_audio_open_with_error(dev_name = dac_name(sys, (dev == MUS_AUDIO_AUX_OUTPUT) ? 1 : 0), + O_WRONLY, 0, sys); + if (audio_out == -1) return(MUS_ERROR); + buffer_info = (FRAGMENTS << 16) | (FRAGMENT_SIZE); + if (ioctl(audio_out, SNDCTL_DSP_SETFRAGMENT, &buffer_info) == -1) + { + char *tmp; + fprintf(stderr, tmp = mus_format("can't set %s fragments to: %d x %d", + dev_name, FRAGMENTS, FRAGMENT_SIZE)); /* not an error if ALSA OSS-emulation */ + fragment_set_failed = true; + FREE(tmp); + } + } + } + if ((ioctl(audio_out, SNDCTL_DSP_SETFMT, &oss_format) == -1) || + (oss_format != to_oss_format(format))) + RETURN_ERROR_EXIT(MUS_AUDIO_FORMAT_NOT_AVAILABLE, audio_out, + mus_format("data format %d (%s) not available on %s", + format, + mus_data_format_name(format), + dev_name)); +#ifdef NEW_OSS + if (ioctl(audio_out, SNDCTL_DSP_CHANNELS, &chans) == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CHANNELS_NOT_AVAILABLE, audio_out, + mus_format("can't get %d channels on %s", + chans, dev_name)); +#else + if (chans == 2) stereo = 1; else stereo = 0; + if ((ioctl(audio_out, SNDCTL_DSP_STEREO, &stereo) == -1) || + ((chans == 2) && (stereo == 0))) + RETURN_ERROR_EXIT(MUS_AUDIO_CHANNELS_NOT_AVAILABLE, audio_out, + mus_format("can't get %d channels on %s", + chans, dev_name)); +#endif + if (ioctl(audio_out, SNDCTL_DSP_SPEED, &srate) == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_SRATE_NOT_AVAILABLE, audio_out, + mus_format("can't set srate of %s to %d", + dev_name, srate)); + /* http://www.4front-tech.com/pguide/audio.html says this order has to be followed */ + return(audio_out); +} + +static int oss_mus_audio_write(int line, char *buf, int bytes) +{ + int err; + if (line < 0) return(-1); + errno = 0; + err = write(line, buf, bytes); + if (err != bytes) + { + if (errno != 0) + RETURN_ERROR_EXIT(MUS_AUDIO_WRITE_ERROR, -1, + mus_format("write error: %s", strerror(errno))); + else RETURN_ERROR_EXIT(MUS_AUDIO_WRITE_ERROR, -1, + mus_format("wrote %d bytes of requested %d", err, bytes)); + } + return(MUS_NO_ERROR); +} + +static int oss_mus_audio_close(int line) +{ + return(linux_audio_close(line)); +} + +static int oss_mus_audio_read(int line, char *buf, int bytes) +{ + int err; + if (line < 0) return(-1); + errno = 0; + err = read(line, buf, bytes); + if (err != bytes) + { + if (errno != 0) + RETURN_ERROR_EXIT(MUS_AUDIO_READ_ERROR, -1, + mus_format("read error: %s", strerror(errno))); + else RETURN_ERROR_EXIT(MUS_AUDIO_READ_ERROR, -1, + mus_format("read %d bytes of requested %d", err, bytes)); + } + return(MUS_NO_ERROR); +} + +static char *oss_unsrc(int srcbit) +{ + if (srcbit == 0) + return(strdup("none")); + else + { + bool need_and = false; + char *buf; + buf = (char *)CALLOC(PRINT_BUFFER_SIZE, sizeof(char)); + if (srcbit & SOUND_MASK_MIC) {need_and = true; strcat(buf, "mic");} + if (srcbit & SOUND_MASK_LINE) {if (need_and) strcat(buf, " and "); need_and = true; strcat(buf, "line in");} + if (srcbit & SOUND_MASK_LINE1) {if (need_and) strcat(buf, " and "); need_and = true; strcat(buf, "line1");} + if (srcbit & SOUND_MASK_LINE2) {if (need_and) strcat(buf, " and "); need_and = true; strcat(buf, "line2");} + if (srcbit & SOUND_MASK_LINE3) {if (need_and) strcat(buf, " and "); need_and = true; strcat(buf, "line3");} + if (srcbit & SOUND_MASK_CD) {if (need_and) strcat(buf, " and "); need_and = true; strcat(buf, "cd");} + return(buf); + } +} + +static int oss_mus_audio_open_input(int ur_dev, int srate, int chans, int format, int requested_size) +{ + /* dev can be MUS_AUDIO_DEFAULT or MUS_AUDIO_DUPLEX_DEFAULT as well as the obvious others */ + int audio_fd = -1, oss_format, buffer_info, sys, dev, srcbit, cursrc, err; + bool adat_mode = false; + char *dev_name; +#ifndef NEW_OSS + int stereo; +#endif + sys = MUS_AUDIO_SYSTEM(ur_dev); + dev = MUS_AUDIO_DEVICE(ur_dev); + oss_format = to_oss_format(format); + if (oss_format == MUS_ERROR) + RETURN_ERROR_EXIT(MUS_AUDIO_FORMAT_NOT_AVAILABLE, -1, + mus_format("format %d (%s) not available", + format, + mus_data_format_name(format))); + if (audio_type[sys] == SONORUS_STUDIO) + { + adat_mode = (audio_mode[sys] == 1); + switch (dev) + { + case MUS_AUDIO_DEFAULT: + if (adat_mode) + audio_fd = open(dev_name = sonorus_name(sys, 11), O_RDONLY, 0); + else audio_fd = open(dev_name = sonorus_name(sys, 5), O_RDONLY, 0); + break; + case MUS_AUDIO_ADAT_IN: + audio_fd = open(dev_name = sonorus_name(sys, 11), O_RDONLY, 0); + break; + case MUS_AUDIO_AES_IN: + audio_fd = open(dev_name = sonorus_name(sys, 20), O_RDONLY, 0); + break; + case MUS_AUDIO_SPDIF_IN: + audio_fd = open(dev_name = sonorus_name(sys, 5), O_RDONLY, 0); + break; + default: + RETURN_ERROR_EXIT(MUS_AUDIO_DEVICE_NOT_AVAILABLE, -1, + mus_format("no %s device on Sonorus?", + mus_audio_device_name(dev))); + break; + } + if (audio_fd == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_NO_INPUT_AVAILABLE, -1, + mus_format("can't open %s (Sonorus device %s): %s", + dev_name, + mus_audio_device_name(dev), + strerror(errno))); +#ifdef NEW_OSS + if (ioctl(audio_fd, SNDCTL_DSP_CHANNELS, &chans) == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CHANNELS_NOT_AVAILABLE, audio_fd, + mus_format("can't get %d channels on %s (Sonorus device %s)", + chans, dev_name, + mus_audio_device_name(dev))); +#endif + return(audio_fd); + } + +#if HAVE_SAM_9407 + if (audio_type[sys] == SAM9407_DSP) + { + char dname[LABEL_BUFFER_SIZE]; + mus_snprintf(dname, LABEL_BUFFER_SIZE, "/dev/sam%d_dsp", audio_dsp[sys]); + audio_fd = open(dname, O_RDONLY); + if(audio_fd == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_OPEN, audio_fd, + mus_format("can't open input %s: %s", + dname, + strerror(errno))); + if ((ioctl(audio_fd, SNDCTL_DSP_SETFMT, &oss_format) == -1) || + (oss_format != to_oss_format(format))) + RETURN_ERROR_EXIT(MUS_AUDIO_FORMAT_NOT_AVAILABLE, audio_fd, + mus_format("can't set %s format to %d (%s)", + dname, format, + mus_data_format_name(format))); + if (ioctl(audio_fd, SNDCTL_DSP_CHANNELS, &chans) == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CHANNELS_NOT_AVAILABLE, audio_fd, + mus_format("can't get %d channels on %s", + chans, dname)); + if (ioctl(audio_fd, SNDCTL_DSP_SPEED, &srate) == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_SRATE_NOT_AVAILABLE, audio_fd, + mus_format("can't set srate to %d on %s", + srate, dname)); + FRAGMENT_SIZE = 14; + buffer_info = (FRAGMENTS << 16) | (FRAGMENT_SIZE); + ioctl(audio_fd, SNDCTL_DSP_SETFRAGMENT, &buffer_info); + return(audio_fd); + } +#endif + + if (((dev == MUS_AUDIO_DEFAULT) || (dev == MUS_AUDIO_DUPLEX_DEFAULT)) && (sys == 0)) + audio_fd = linux_audio_open(dev_name = dac_name(sys, 0), + O_RDWR, 0, sys); + else audio_fd = linux_audio_open(dev_name = dac_name(sys, (dev == MUS_AUDIO_AUX_INPUT) ? 1 : 0), + O_RDONLY, 0, sys); + if (audio_fd == -1) + { + if (dev == MUS_AUDIO_DUPLEX_DEFAULT) + RETURN_ERROR_EXIT(MUS_AUDIO_CONFIGURATION_NOT_AVAILABLE, -1, + mus_format("can't open %s (device %s): %s", + dev_name, mus_audio_device_name(dev), strerror(errno))); + if ((audio_fd = linux_audio_open(dev_name = dac_name(sys, (dev == MUS_AUDIO_AUX_INPUT) ? 1 : 0), + O_RDONLY, 0, sys)) == -1) + { + if ((errno == EACCES) || (errno == ENOENT)) + RETURN_ERROR_EXIT(MUS_AUDIO_NO_READ_PERMISSION, -1, + mus_format("can't open %s (device %s): %s\n to get input in Linux, we need read permission on /dev/dsp", + dev_name, + mus_audio_device_name(dev), + strerror(errno))); + else RETURN_ERROR_EXIT(MUS_AUDIO_NO_INPUT_AVAILABLE, -1, + mus_format("can't open %s (device %s): %s", + dev_name, + mus_audio_device_name(dev), + strerror(errno))); + } + } +#ifdef SNDCTL_DSP_SETDUPLEX + else + ioctl(audio_fd, SNDCTL_DSP_SETDUPLEX, &err); /* not always a no-op! */ +#endif + if (audio_type[sys] == RME_HAMMERFALL) return(audio_fd); + if (audio_type[sys] == DELTA_66) return(audio_fd); + /* need to make sure the desired recording source is active -- does this actually have any effect? */ + switch (dev) + { + case MUS_AUDIO_MICROPHONE: srcbit = SOUND_MASK_MIC; break; + case MUS_AUDIO_LINE_IN: srcbit = SOUND_MASK_LINE; break; + case MUS_AUDIO_LINE1: srcbit = SOUND_MASK_LINE1; break; + case MUS_AUDIO_LINE2: srcbit = SOUND_MASK_LINE2; break; + case MUS_AUDIO_LINE3: srcbit = SOUND_MASK_LINE3; break; /* also digital1..3 */ + case MUS_AUDIO_DUPLEX_DEFAULT: + case MUS_AUDIO_DEFAULT: srcbit = SOUND_MASK_LINE | SOUND_MASK_MIC; break; + case MUS_AUDIO_CD: srcbit = SOUND_MASK_CD; break; + default: srcbit = 0; break; + /* other possibilities: synth, radio, phonein but these apparently bypass the mixer (no gains?) */ + } + ioctl(audio_fd, MIXER_READ(SOUND_MIXER_RECSRC), &cursrc); + srcbit = (srcbit | cursrc); + ioctl(audio_fd, MIXER_WRITE(SOUND_MIXER_RECSRC), &srcbit); + ioctl(audio_fd, MIXER_READ(SOUND_MIXER_RECSRC), &cursrc); + if (cursrc != srcbit) + { + char *str1, *str2; + str1 = oss_unsrc(srcbit); + str2 = oss_unsrc(cursrc); + mus_print("weird: tried to set recorder source to %s, but got %s?", str1, str2); + FREE(str1); + FREE(str2); + } + if ((fragments_locked) && (requested_size != 0)) + { + buffer_info = (FRAGMENTS << 16) | (FRAGMENT_SIZE); + ioctl(audio_fd, SNDCTL_DSP_SETFRAGMENT, &buffer_info); + } + if ((ioctl(audio_fd, SNDCTL_DSP_SETFMT, &oss_format) == -1) || + (oss_format != to_oss_format(format))) + RETURN_ERROR_EXIT(MUS_AUDIO_FORMAT_NOT_AVAILABLE, audio_fd, + mus_format("can't set %s format to %d (%s)", + dev_name, format, + mus_data_format_name(format))); +#ifdef NEW_OSS + if (ioctl(audio_fd, SNDCTL_DSP_CHANNELS, &chans) == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CHANNELS_NOT_AVAILABLE, audio_fd, + mus_format("can't get %d channels on %s", + chans, dev_name)); +#else + if (chans == 2) stereo = 1; else stereo = 0; + if ((ioctl(audio_fd, SNDCTL_DSP_STEREO, &stereo) == -1) || + ((chans == 2) && (stereo == 0))) + RETURN_ERROR_EXIT(MUS_AUDIO_CHANNELS_NOT_AVAILABLE, audio_fd, + mus_format("can't get %d channels on %s (%s)", + chans, dev_name, + mus_audio_device_name(dev))); +#endif + if (ioctl(audio_fd, SNDCTL_DSP_SPEED, &srate) == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_SRATE_NOT_AVAILABLE, audio_fd, + mus_format("can't set srate to %d on %s (%s)", + srate, dev_name, + mus_audio_device_name(dev))); + return(audio_fd); +} + + +static int oss_mus_audio_mixer_read(int ur_dev, int field, int chan, float *val) +{ + int fd, amp, channels, err = MUS_NO_ERROR, devmask, stereodevs, ind, formats, sys, dev, srate; + char *dev_name = NULL; + sys = MUS_AUDIO_SYSTEM(ur_dev); + dev = MUS_AUDIO_DEVICE(ur_dev); + if (audio_type[sys] == SONORUS_STUDIO) + { + bool adat_mode = false; + adat_mode = (audio_mode[sys] == 1); + if (dev == MUS_AUDIO_MIXER) val[0] = 0; /* no mixer */ + else + { + if (field == MUS_AUDIO_PORT) + { + if (adat_mode) + { + val[0] = 5; + val[1] = MUS_AUDIO_ADAT_IN; + val[2] = MUS_AUDIO_ADAT_OUT; + val[3] = MUS_AUDIO_SPEAKERS; + val[4] = MUS_AUDIO_AES_IN; + val[5] = MUS_AUDIO_AES_OUT; + } + else + { + val[0] = 3; + val[1] = MUS_AUDIO_SPDIF_IN; + val[2] = MUS_AUDIO_SPDIF_OUT; + val[3] = MUS_AUDIO_SPEAKERS; + } + } + else + { + if (field == MUS_AUDIO_FORMAT) + { + val[0] = 1; + val[1] = MUS_LSHORT; + } + else + { + if (field == MUS_AUDIO_CHANNEL) + { + switch (dev) + { + case MUS_AUDIO_SPEAKERS: + channels = 2; + break; + case MUS_AUDIO_ADAT_IN: case MUS_AUDIO_ADAT_OUT: + channels = 8; + break; + case MUS_AUDIO_AES_IN: case MUS_AUDIO_AES_OUT: + channels = 2; + break; + case MUS_AUDIO_SPDIF_IN: case MUS_AUDIO_SPDIF_OUT: + channels = 4; + break; + case MUS_AUDIO_DEFAULT: + if (adat_mode) + channels = 8; + else channels = 4; + break; + default: + channels = 0; + break; + } + val[0] = channels; + } + else + { + if (field == MUS_AUDIO_SRATE) + { + val[0] = 44100; + } + } + } + } + } + return(MUS_NO_ERROR); + } + +#if HAVE_SAM_9407 + if (audio_type[sys] == SAM9407_DSP) + { + switch(field) + { + case MUS_AUDIO_PORT: + val[0] = 2; + val[1] = MUS_AUDIO_SPEAKERS; + val[2] = MUS_AUDIO_LINE_IN; + break; + case MUS_AUDIO_FORMAT: + val[0] = 1; + val[1] = MUS_LSHORT; + break; + case MUS_AUDIO_CHANNEL: + val[0] = 2; + break; + case MUS_AUDIO_AMP: + RETURN_ERROR_EXIT(MUS_AUDIO_DEVICE_NOT_AVAILABLE, -1, + mus_format("can't read %s's gains in Sam9407", + mus_audio_device_name(dev))); + break; + case MUS_AUDIO_SRATE: + val[0] = 44100; + break; + default: + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_READ, -1, + mus_format("can't read %s's %s in Sam9407", + mus_audio_device_name(dev), + mus_audio_device_name(field))); + break; + } + return(MUS_NO_ERROR); + } +#endif + + if (audio_type[sys] == RME_HAMMERFALL) + { + if (dev == MUS_AUDIO_MIXER) val[0] = 0; /* no mixer */ + else + { + if (field == MUS_AUDIO_PORT) + { + val[0] = 5; + val[1] = MUS_AUDIO_ADAT_IN; + val[2] = MUS_AUDIO_ADAT_OUT; + val[3] = MUS_AUDIO_SPEAKERS; + val[4] = MUS_AUDIO_AES_IN; + val[5] = MUS_AUDIO_AES_OUT; + } + else + { + if (field == MUS_AUDIO_FORMAT) + { + val[0] = 1; + val[1] = MUS_LSHORT; + } + else + { + if (field == MUS_AUDIO_CHANNEL) + { + switch (dev) + { + case MUS_AUDIO_SPEAKERS: + channels = 2; + break; + case MUS_AUDIO_ADAT_IN: case MUS_AUDIO_ADAT_OUT: + channels = 8; + break; + case MUS_AUDIO_AES_IN: case MUS_AUDIO_AES_OUT: + channels = 2; + break; + case MUS_AUDIO_SPDIF_IN: case MUS_AUDIO_SPDIF_OUT: + channels = 4; + break; + case MUS_AUDIO_DEFAULT: + channels = 8; + break; + default: + channels = 0; + break; + } + val[0] = channels; + } + else + { + if (field == MUS_AUDIO_SRATE) + { + val[0] = 44100; + } + } + } + } + } + return(MUS_NO_ERROR); + } + + fd = linux_audio_open(dev_name = mixer_name(sys), O_RDONLY | O_NONBLOCK, 0, sys); + if (fd == -1) + { + fd = linux_audio_open(DAC_NAME, O_RDONLY, 0, sys); + if (fd == -1) + { + fd = linux_audio_open(DAC_NAME, O_WRONLY, 0, sys); + if (fd == -1) + { + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_OPEN, -1, + mus_format("can't open input %s or %s: %s", + dev_name, DAC_NAME, + strerror(errno))); + return(MUS_ERROR); + } + else dev_name = DAC_NAME; + } + else dev_name = DAC_NAME; + } + if (ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devmask)) + RETURN_ERROR_EXIT(MUS_AUDIO_CONFIGURATION_NOT_AVAILABLE, fd, + mus_format("can't read device info from %s", + dev_name)); + err = 0; + if ((dev == MUS_AUDIO_MIXER) || + (dev == MUS_AUDIO_DAC_FILTER)) /* these give access to all the on-board analog input gain controls */ + { + amp = 0; + ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devmask); + switch (field) + { + /* also DIGITAL1..3 PHONEIN PHONEOUT VIDEO RADIO MONITOR */ + /* the digital lines should get their own panes in the recorder */ + /* not clear whether the phone et al lines are routed to the ADC */ + /* also, I've never seen a card with any of these devices */ + case MUS_AUDIO_IMIX: if (SOUND_MASK_IMIX & devmask) err = ioctl(fd, MIXER_READ(SOUND_MIXER_IMIX), &); break; + case MUS_AUDIO_IGAIN: if (SOUND_MASK_IGAIN & devmask) err = ioctl(fd, MIXER_READ(SOUND_MIXER_IGAIN), &); break; + case MUS_AUDIO_RECLEV: if (SOUND_MASK_RECLEV & devmask) err = ioctl(fd, MIXER_READ(SOUND_MIXER_RECLEV), &); break; + case MUS_AUDIO_PCM: if (SOUND_MASK_PCM & devmask) err = ioctl(fd, MIXER_READ(SOUND_MIXER_PCM), &); break; + case MUS_AUDIO_PCM2: if (SOUND_MASK_ALTPCM & devmask) err = ioctl(fd, MIXER_READ(SOUND_MIXER_ALTPCM), &); break; + case MUS_AUDIO_OGAIN: if (SOUND_MASK_OGAIN & devmask) err = ioctl(fd, MIXER_READ(SOUND_MIXER_OGAIN), &); break; + case MUS_AUDIO_LINE: if (SOUND_MASK_LINE & devmask) err = ioctl(fd, MIXER_READ(SOUND_MIXER_LINE), &); break; + case MUS_AUDIO_MICROPHONE: if (SOUND_MASK_MIC & devmask) err = ioctl(fd, MIXER_READ(SOUND_MIXER_MIC), &); break; + case MUS_AUDIO_LINE1: if (SOUND_MASK_LINE1 & devmask) err = ioctl(fd, MIXER_READ(SOUND_MIXER_LINE1), &); break; + case MUS_AUDIO_LINE2: if (SOUND_MASK_LINE2 & devmask) err = ioctl(fd, MIXER_READ(SOUND_MIXER_LINE2), &); break; + case MUS_AUDIO_LINE3: if (SOUND_MASK_LINE3 & devmask) err = ioctl(fd, MIXER_READ(SOUND_MIXER_LINE3), &); break; + case MUS_AUDIO_SYNTH: if (SOUND_MASK_SYNTH & devmask) err = ioctl(fd, MIXER_READ(SOUND_MIXER_SYNTH), &); break; + case MUS_AUDIO_BASS: if (SOUND_MASK_BASS & devmask) err = ioctl(fd, MIXER_READ(SOUND_MIXER_BASS), &); break; + case MUS_AUDIO_TREBLE: if (SOUND_MASK_TREBLE & devmask) err = ioctl(fd, MIXER_READ(SOUND_MIXER_TREBLE), &); break; + case MUS_AUDIO_CD: if (SOUND_MASK_CD & devmask) err = ioctl(fd, MIXER_READ(SOUND_MIXER_CD), &); break; + case MUS_AUDIO_CHANNEL: + if (dev == MUS_AUDIO_MIXER) + { + channels = 0; + ioctl(fd, SOUND_MIXER_READ_STEREODEVS, &stereodevs); + if (SOUND_MASK_IMIX & devmask) {if (SOUND_MASK_IMIX & stereodevs) channels += 2; else channels += 1;} + if (SOUND_MASK_IGAIN & devmask) {if (SOUND_MASK_IGAIN & stereodevs) channels += 2; else channels += 1;} + if (SOUND_MASK_RECLEV & devmask) {if (SOUND_MASK_RECLEV & stereodevs) channels += 2; else channels += 1;} + if (SOUND_MASK_PCM & devmask) {if (SOUND_MASK_PCM & stereodevs) channels += 2; else channels += 1;} + if (SOUND_MASK_ALTPCM & devmask) {if (SOUND_MASK_ALTPCM & stereodevs) channels += 2; else channels += 1;} + if (SOUND_MASK_OGAIN & devmask) {if (SOUND_MASK_OGAIN & stereodevs) channels += 2; else channels += 1;} + if (SOUND_MASK_LINE & devmask) {if (SOUND_MASK_LINE & stereodevs) channels += 2; else channels += 1;} + if (SOUND_MASK_MIC & devmask) {if (SOUND_MASK_MIC & stereodevs) channels += 2; else channels += 1;} + if (SOUND_MASK_LINE1 & devmask) {if (SOUND_MASK_LINE1 & stereodevs) channels += 2; else channels += 1;} + if (SOUND_MASK_LINE2 & devmask) {if (SOUND_MASK_LINE2 & stereodevs) channels += 2; else channels += 1;} + if (SOUND_MASK_LINE3 & devmask) {if (SOUND_MASK_LINE3 & stereodevs) channels += 2; else channels += 1;} + if (SOUND_MASK_SYNTH & devmask) {if (SOUND_MASK_SYNTH & stereodevs) channels += 2; else channels += 1;} + if (SOUND_MASK_CD & devmask) {if (SOUND_MASK_CD & stereodevs) channels += 2; else channels += 1;} + } + else + if (SOUND_MASK_TREBLE & devmask) channels = 2; else channels = 0; + val[0] = channels; + linux_audio_close(fd); + return(MUS_NO_ERROR); + break; + case MUS_AUDIO_FORMAT: /* this is asking for configuration info -- we return an array with per-"device" channels */ + ioctl(fd, SOUND_MIXER_READ_STEREODEVS, &stereodevs); + for (ind = 0; ind <= MUS_AUDIO_SYNTH; ind++) {if (chan > ind) val[ind] = 0;} + if (SOUND_MASK_IMIX & devmask) {if (chan > MUS_AUDIO_IMIX) val[MUS_AUDIO_IMIX] = ((SOUND_MASK_IMIX & stereodevs) ? 2 : 1);} + if (SOUND_MASK_IGAIN & devmask) {if (chan > MUS_AUDIO_IGAIN) val[MUS_AUDIO_IGAIN] = ((SOUND_MASK_IGAIN & stereodevs) ? 2 : 1);} + if (SOUND_MASK_RECLEV & devmask) {if (chan > MUS_AUDIO_RECLEV) val[MUS_AUDIO_RECLEV] = ((SOUND_MASK_RECLEV & stereodevs) ? 2 : 1);} + if (SOUND_MASK_PCM & devmask) {if (chan > MUS_AUDIO_PCM) val[MUS_AUDIO_PCM] = ((SOUND_MASK_PCM & stereodevs) ? 2 : 1);} + if (SOUND_MASK_ALTPCM & devmask) {if (chan > MUS_AUDIO_PCM2) val[MUS_AUDIO_PCM2] = ((SOUND_MASK_ALTPCM & stereodevs) ? 2 : 1);} + if (SOUND_MASK_OGAIN & devmask) {if (chan > MUS_AUDIO_OGAIN) val[MUS_AUDIO_OGAIN] = ((SOUND_MASK_OGAIN & stereodevs) ? 2 : 1);} + if (SOUND_MASK_LINE & devmask) {if (chan > MUS_AUDIO_LINE) val[MUS_AUDIO_LINE] = ((SOUND_MASK_LINE & stereodevs) ? 2 : 1);} + if (SOUND_MASK_MIC & devmask) {if (chan > MUS_AUDIO_MICROPHONE) val[MUS_AUDIO_MICROPHONE] = ((SOUND_MASK_MIC & stereodevs) ? 2 : 1);} + if (SOUND_MASK_LINE1 & devmask) {if (chan > MUS_AUDIO_LINE1) val[MUS_AUDIO_LINE1] = ((SOUND_MASK_LINE1 & stereodevs) ? 2 : 1);} + if (SOUND_MASK_LINE2 & devmask) {if (chan > MUS_AUDIO_LINE2) val[MUS_AUDIO_LINE2] = ((SOUND_MASK_LINE2 & stereodevs) ? 2 : 1);} + if (SOUND_MASK_LINE3 & devmask) {if (chan > MUS_AUDIO_LINE3) val[MUS_AUDIO_LINE3] = ((SOUND_MASK_LINE3 & stereodevs) ? 2 : 1);} + if (SOUND_MASK_SYNTH & devmask) {if (chan > MUS_AUDIO_SYNTH) val[MUS_AUDIO_SYNTH] = ((SOUND_MASK_SYNTH & stereodevs) ? 2 : 1);} + if (SOUND_MASK_CD & devmask) {if (chan > MUS_AUDIO_CD) val[MUS_AUDIO_CD] = ((SOUND_MASK_CD & stereodevs) ? 2 : 1);} + linux_audio_close(fd); + return(MUS_NO_ERROR); + break; + default: + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_READ, fd, + mus_format("can't read %s's (%s) %s", + mus_audio_device_name(dev), dev_name, + mus_audio_device_name(field))); + break; + } + if (chan == 0) + val[0] = ((float)(amp & 0xff)) * 0.01; + else val[0] = (((float)((amp & 0xff00) >> 8)) * 0.01); + } + else + { + switch (field) + { + case MUS_AUDIO_PORT: + ind = 1; + val[1] = MUS_AUDIO_MIXER; + if ((SOUND_MASK_MIC | SOUND_MASK_LINE | SOUND_MASK_CD) & devmask) {ind++; if (chan > ind) val[ind] = MUS_AUDIO_LINE_IN;} + /* problem here is that microphone and line_in are mixed before the ADC */ + if (SOUND_MASK_SPEAKER & devmask) {ind++; if (chan > ind) val[ind] = MUS_AUDIO_SPEAKERS;} + if (SOUND_MASK_VOLUME & devmask) {ind++; if (chan > ind) val[ind] = MUS_AUDIO_DAC_OUT;} + if (SOUND_MASK_TREBLE & devmask) {ind++; if (chan > ind) val[ind] = MUS_AUDIO_DAC_FILTER;} + /* DIGITAL1..3 as RECSRC(?) => MUS_AUDIO_DIGITAL_IN */ + val[0] = ind; + break; +#if 1 + case MUS_AUDIO_FORMAT: + linux_audio_close(fd); + fd = open(dac_name(sys, 0), O_WRONLY, 0); + if (fd == -1) fd = open(DAC_NAME, O_WRONLY, 0); + if (fd == -1) + { + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_OPEN, -1, + mus_format("can't open %s: %s", + DAC_NAME, strerror(errno))); + return(MUS_ERROR); + } + ioctl(fd, SOUND_PCM_GETFMTS, &formats); +#else + case MUS_AUDIO_FORMAT: + ioctl(fd, SOUND_PCM_GETFMTS, &formats); + /* this returns -1 and garbage?? */ + + /* from Steven Schultz: + I did discover why, in audio.c the SOUND_PCM_GETFMTS ioctl was failing. + That ioctl call can only be made against the /dev/dsp device and _not_ + the /dev/mixer device. With that change things starting working real + nice. + */ +#endif + ind = 0; + if (formats & (to_oss_format(MUS_BYTE))) {ind++; if (chan > ind) val[ind] = MUS_BYTE;} + if (formats & (to_oss_format(MUS_BSHORT))) {ind++; if (chan > ind) val[ind] = MUS_BSHORT;} + if (formats & (to_oss_format(MUS_UBYTE))) {ind++; if (chan > ind) val[ind] = MUS_UBYTE;} + if (formats & (to_oss_format(MUS_MULAW))) {ind++; if (chan > ind) val[ind] = MUS_MULAW;} + if (formats & (to_oss_format(MUS_ALAW))) {ind++; if (chan > ind) val[ind] = MUS_ALAW;} + if (formats & (to_oss_format(MUS_LSHORT))) {ind++; if (chan > ind) val[ind] = MUS_LSHORT;} + if (formats & (to_oss_format(MUS_UBSHORT))) {ind++; if (chan > ind) val[ind] = MUS_UBSHORT;} + if (formats & (to_oss_format(MUS_ULSHORT))) {ind++; if (chan > ind) val[ind] = MUS_ULSHORT;} + val[0] = ind; + break; + case MUS_AUDIO_CHANNEL: + channels = 0; + ioctl(fd, SOUND_MIXER_READ_STEREODEVS, &stereodevs); + switch (dev) + { + case MUS_AUDIO_MICROPHONE: if (SOUND_MASK_MIC & devmask) {if (SOUND_MASK_MIC & stereodevs) channels = 2; else channels = 1;} break; + case MUS_AUDIO_SPEAKERS: if (SOUND_MASK_SPEAKER & devmask) {if (SOUND_MASK_SPEAKER & stereodevs) channels = 2; else channels = 1;} break; + case MUS_AUDIO_LINE_IN: if (SOUND_MASK_LINE & devmask) {if (SOUND_MASK_LINE & stereodevs) channels = 2; else channels = 1;} break; + case MUS_AUDIO_LINE1: if (SOUND_MASK_LINE1 & devmask) {if (SOUND_MASK_LINE1 & stereodevs) channels = 2; else channels = 1;} break; + case MUS_AUDIO_LINE2: if (SOUND_MASK_LINE2 & devmask) {if (SOUND_MASK_LINE2 & stereodevs) channels = 2; else channels = 1;} break; + case MUS_AUDIO_LINE3: if (SOUND_MASK_LINE3 & devmask) {if (SOUND_MASK_LINE3 & stereodevs) channels = 2; else channels = 1;} break; + case MUS_AUDIO_DAC_OUT: if (SOUND_MASK_VOLUME & devmask) {if (SOUND_MASK_VOLUME & stereodevs) channels = 2; else channels = 1;} break; + case MUS_AUDIO_DEFAULT: if (SOUND_MASK_VOLUME & devmask) {if (SOUND_MASK_VOLUME & stereodevs) channels = 2; else channels = 1;} break; + case MUS_AUDIO_CD: if (SOUND_MASK_CD & devmask) {if (SOUND_MASK_CD & stereodevs) channels = 2; else channels = 1;} break; + case MUS_AUDIO_DUPLEX_DEFAULT: + err = ioctl(fd, SNDCTL_DSP_GETCAPS, &ind); + if (err != -1) + channels = (ind & DSP_CAP_DUPLEX); + else channels = 0; + break; + default: + RETURN_ERROR_EXIT(MUS_AUDIO_DEVICE_NOT_AVAILABLE, fd, + mus_format("can't read channel info from %s (%s)", + mus_audio_device_name(dev), dev_name)); + break; + } + val[0] = channels; + break; + case MUS_AUDIO_AMP: + amp = 0; + switch (dev) + { + case MUS_AUDIO_MICROPHONE: if (SOUND_MASK_MIC & devmask) err = ioctl(fd, MIXER_READ(SOUND_MIXER_MIC), &); break; + case MUS_AUDIO_SPEAKERS: if (SOUND_MASK_SPEAKER & devmask) err = ioctl(fd, MIXER_READ(SOUND_MIXER_SPEAKER), &); break; + case MUS_AUDIO_LINE_IN: if (SOUND_MASK_LINE & devmask) err = ioctl(fd, MIXER_READ(SOUND_MIXER_LINE), &); break; + case MUS_AUDIO_LINE1: if (SOUND_MASK_LINE1 & devmask) err = ioctl(fd, MIXER_READ(SOUND_MIXER_LINE1), &); break; + case MUS_AUDIO_LINE2: if (SOUND_MASK_LINE2 & devmask) err = ioctl(fd, MIXER_READ(SOUND_MIXER_LINE2), &); break; + case MUS_AUDIO_LINE3: if (SOUND_MASK_LINE3 & devmask) err = ioctl(fd, MIXER_READ(SOUND_MIXER_LINE3), &); break; + case MUS_AUDIO_DAC_OUT: if (SOUND_MASK_VOLUME & devmask) err = ioctl(fd, MIXER_READ(SOUND_MIXER_VOLUME), &); break; + case MUS_AUDIO_DEFAULT: if (SOUND_MASK_VOLUME & devmask) err = ioctl(fd, MIXER_READ(SOUND_MIXER_VOLUME), &); break; + case MUS_AUDIO_CD: if (SOUND_MASK_CD & devmask) err = ioctl(fd, MIXER_READ(SOUND_MIXER_CD), &); break; + default: + RETURN_ERROR_EXIT(MUS_AUDIO_DEVICE_NOT_AVAILABLE, fd, + mus_format("can't get gain info for %s (%s)", + mus_audio_device_name(dev), dev_name)); + break; + } + if (err) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_READ, fd, + mus_format("can't read %s's (%s) amp info", + mus_audio_device_name(dev), dev_name)); + if (chan == 0) + val[0] = ((float)(amp & 0xff)) * 0.01; + else val[0] = (((float)((amp & 0xff00) >> 8)) * 0.01); + break; + case MUS_AUDIO_SRATE: + srate = (int)(val[0]); + if (ioctl(fd, SNDCTL_DSP_SPEED, &srate) == -1) + { + linux_audio_close(fd); + /* see comment from Steven Schultz above */ + fd = open(dac_name(sys, 0), O_WRONLY, 0); + if (fd == -1) fd = open(DAC_NAME, O_WRONLY, 0); + if (ioctl(fd, SNDCTL_DSP_SPEED, &srate) == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_SRATE_NOT_AVAILABLE, fd, + mus_format("can't get %s's (%s) srate", + mus_audio_device_name(dev), dev_name)); + } + val[0] = (float)srate; + break; + case MUS_AUDIO_DIRECTION: + switch (dev) + { + case MUS_AUDIO_DIGITAL_OUT: case MUS_AUDIO_LINE_OUT: case MUS_AUDIO_DEFAULT: case MUS_AUDIO_ADAT_OUT: + case MUS_AUDIO_AES_OUT: case MUS_AUDIO_SPDIF_OUT: case MUS_AUDIO_SPEAKERS: case MUS_AUDIO_MIXER: + case MUS_AUDIO_DAC_FILTER: case MUS_AUDIO_AUX_OUTPUT: case MUS_AUDIO_DAC_OUT: + val[0] = 0.0; + break; + default: + val[0] = 1.0; + break; + } + default: + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_READ, fd, + mus_format("can't get %s's (%s) %s", + mus_audio_device_name(dev), dev_name, + mus_audio_device_name(field))); + break; + } + } + return(linux_audio_close(fd)); +} + +static int oss_mus_audio_mixer_write(int ur_dev, int field, int chan, float *val) +{ + int fd, err = MUS_NO_ERROR, devmask, vol, sys, dev; + char *dev_name; + float amp[1]; + sys = MUS_AUDIO_SYSTEM(ur_dev); + dev = MUS_AUDIO_DEVICE(ur_dev); + +#if HAVE_SAM_9407 + if (audio_type[sys] == SAM9407_DSP) return(MUS_NO_ERROR); /* XXX */ +#endif + + if (audio_type[sys] == SONORUS_STUDIO) return(MUS_NO_ERROR); /* there are apparently volume controls, but they're not accessible yet */ + if (audio_type[sys] == RME_HAMMERFALL) return(MUS_NO_ERROR); + if (audio_type[sys] == DELTA_66) return(MUS_NO_ERROR); + + fd = linux_audio_open(dev_name = mixer_name(sys), O_RDWR | O_NONBLOCK, 0, sys); + if (fd == -1) + { + fd = linux_audio_open_with_error(dev_name = DAC_NAME, O_WRONLY, 0, sys); + if (fd == -1) return(MUS_ERROR); + } + if ((dev == MUS_AUDIO_MIXER) || + (dev == MUS_AUDIO_DAC_FILTER)) /* these give access to all the on-board analog input gain controls */ + { + if (mus_audio_mixer_read(ur_dev, field, (chan == 0) ? 1 : 0, amp)) + { + linux_audio_close(fd); + return(MUS_ERROR); + } + if (val[0] >= 0.99) val[0] = 0.99; + if (val[0] < 0.0) val[0] = 0.0; + if (amp[0] >= 0.99) amp[0] = 0.99; + if (chan == 0) + vol = (((int)(amp[0] * 100)) << 8) + ((int)(val[0] * 100)); + else vol = (((int)(val[0] * 100)) << 8) + ((int)(amp[0] * 100)); + ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devmask); + switch (field) + { + case MUS_AUDIO_IMIX: if (SOUND_MASK_IMIX & devmask) err = ioctl(fd, MIXER_WRITE(SOUND_MIXER_IMIX), &vol); break; + case MUS_AUDIO_IGAIN: if (SOUND_MASK_IGAIN & devmask) err = ioctl(fd, MIXER_WRITE(SOUND_MIXER_IGAIN), &vol); break; + case MUS_AUDIO_RECLEV: if (SOUND_MASK_RECLEV & devmask) err = ioctl(fd, MIXER_WRITE(SOUND_MIXER_RECLEV), &vol); break; + case MUS_AUDIO_PCM: if (SOUND_MASK_PCM & devmask) err = ioctl(fd, MIXER_WRITE(SOUND_MIXER_PCM), &vol); break; + case MUS_AUDIO_PCM2: if (SOUND_MASK_ALTPCM & devmask) err = ioctl(fd, MIXER_WRITE(SOUND_MIXER_ALTPCM), &vol); break; + case MUS_AUDIO_OGAIN: if (SOUND_MASK_OGAIN & devmask) err = ioctl(fd, MIXER_WRITE(SOUND_MIXER_OGAIN), &vol); break; + case MUS_AUDIO_LINE: if (SOUND_MASK_LINE & devmask) err = ioctl(fd, MIXER_WRITE(SOUND_MIXER_LINE), &vol); break; + case MUS_AUDIO_MICROPHONE: if (SOUND_MASK_MIC & devmask) err = ioctl(fd, MIXER_WRITE(SOUND_MIXER_MIC), &vol); break; + case MUS_AUDIO_LINE1: if (SOUND_MASK_LINE1 & devmask) err = ioctl(fd, MIXER_WRITE(SOUND_MIXER_LINE1), &vol); break; + case MUS_AUDIO_LINE2: if (SOUND_MASK_LINE2 & devmask) err = ioctl(fd, MIXER_WRITE(SOUND_MIXER_LINE2), &vol); break; + case MUS_AUDIO_LINE3: if (SOUND_MASK_LINE3 & devmask) err = ioctl(fd, MIXER_WRITE(SOUND_MIXER_LINE3), &vol); break; + case MUS_AUDIO_SYNTH: if (SOUND_MASK_SYNTH & devmask) err = ioctl(fd, MIXER_WRITE(SOUND_MIXER_SYNTH), &vol); break; + case MUS_AUDIO_BASS: if (SOUND_MASK_BASS & devmask) err = ioctl(fd, MIXER_WRITE(SOUND_MIXER_BASS), &vol); break; + case MUS_AUDIO_TREBLE: if (SOUND_MASK_TREBLE & devmask) err = ioctl(fd, MIXER_WRITE(SOUND_MIXER_TREBLE), &vol); break; + case MUS_AUDIO_CD: if (SOUND_MASK_CD & devmask) err = ioctl(fd, MIXER_WRITE(SOUND_MIXER_CD), &vol); break; + default: + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_WRITE, fd, + mus_format("can't write %s's (%s) %s field", + mus_audio_device_name(dev), dev_name, + mus_audio_device_name(field))); + break; + } + } + else + { + switch (field) + { + case MUS_AUDIO_AMP: + /* need to read both channel amps, then change the one we're concerned with */ + mus_audio_mixer_read(ur_dev, field, (chan == 0) ? 1 : 0, amp); + if (val[0] >= 0.99) val[0] = 0.99; + if (val[0] < 0.0) val[0] = 0.0; + if (amp[0] >= 0.99) amp[0] = 0.99; + if (chan == 0) + vol = (((int)(amp[0] * 100)) << 8) + ((int)(val[0] * 100)); + else vol = (((int)(val[0] * 100)) << 8) + ((int)(amp[0] * 100)); + ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devmask); + switch (dev) + { + case MUS_AUDIO_MICROPHONE: if (SOUND_MASK_MIC & devmask) err = ioctl(fd, MIXER_WRITE(SOUND_MIXER_MIC), &vol); break; + case MUS_AUDIO_SPEAKERS: if (SOUND_MASK_SPEAKER & devmask) err = ioctl(fd, MIXER_WRITE(SOUND_MIXER_SPEAKER), &vol); break; + case MUS_AUDIO_LINE_IN: if (SOUND_MASK_LINE & devmask) err = ioctl(fd, MIXER_WRITE(SOUND_MIXER_LINE), &vol); break; + case MUS_AUDIO_LINE1: if (SOUND_MASK_LINE1 & devmask) err = ioctl(fd, MIXER_WRITE(SOUND_MIXER_LINE1), &vol); break; + case MUS_AUDIO_LINE2: if (SOUND_MASK_LINE2 & devmask) err = ioctl(fd, MIXER_WRITE(SOUND_MIXER_LINE2), &vol); break; + case MUS_AUDIO_LINE3: if (SOUND_MASK_LINE3 & devmask) err = ioctl(fd, MIXER_WRITE(SOUND_MIXER_LINE3), &vol); break; + case MUS_AUDIO_DAC_OUT: if (SOUND_MASK_VOLUME & devmask) err = ioctl(fd, MIXER_WRITE(SOUND_MIXER_VOLUME), &vol); break; + case MUS_AUDIO_DEFAULT: if (SOUND_MASK_VOLUME & devmask) err = ioctl(fd, MIXER_WRITE(SOUND_MIXER_VOLUME), &vol); break; + case MUS_AUDIO_CD: if (SOUND_MASK_CD & devmask) err = ioctl(fd, MIXER_WRITE(SOUND_MIXER_CD), &vol); break; + default: + RETURN_ERROR_EXIT(MUS_AUDIO_DEVICE_NOT_AVAILABLE, fd, + mus_format("device %d (%s) not available on %s", + dev, mus_audio_device_name(dev), dev_name)); + } + break; + case MUS_AUDIO_SRATE: + vol = (int)val[0]; + linux_audio_close(fd); + /* see comment from Steven Schultz above */ + fd = open(dac_name(sys, 0), O_WRONLY | O_NONBLOCK, 0); + if (fd == -1) + { + fd = open(DAC_NAME, O_WRONLY | O_NONBLOCK, 0); + if (fd == -1) return(-1); + } + err = ioctl(fd, SNDCTL_DSP_SPEED, &vol); + break; + default: + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_WRITE, fd, + mus_format("can't write %s's (%s) %s field", + mus_audio_device_name(dev), dev_name, + mus_audio_device_name(field))); + break; + /* case MUS_AUDIO_FORMAT: to force 16-bit input or give up */ + /* case MUS_AUDIO_CHANNEL: to open as stereo if possible?? */ + /* case MUS_AUDIO_PORT: to open digital out? */ + } + } + if (err) + RETURN_ERROR_EXIT(MUS_AUDIO_WRITE_ERROR, fd, + mus_format("possible write problem for %s's (%s) %s field", + mus_audio_device_name(dev), dev_name, + mus_audio_device_name(field))); + return(linux_audio_close(fd)); +} + +static char *synth_names[] = + {"", + "Adlib", "SoundBlaster", "ProAudio Spectrum", "Gravis UltraSound", "MPU 401", + "SoundBlaster 16", "SoundBlaster 16 MIDI", "6850 UART", "Gravis UltraSound 16", "Microsoft", + "Personal sound system", "Ensoniq Soundscape", "Personal sound system + MPU", "Personal/Microsoft", + "Mediatrix Pro", "MAD16", "MAD16 + MPU", "CS4232", "CS4232 + MPU", "Maui", + "Pseudo-MSS", "Gravis Ultrasound PnP", "UART 401"}; + +static char *synth_name(int i) +{ +#ifdef SNDCARD_UART401 + if ((i > 0) && (i <= SNDCARD_UART401)) +#else + if ((i > 0) && (i <= 26)) +#endif + return(synth_names[i]); + return("unknown"); +} + +static char *device_types[] = {"FM", "Sampling", "MIDI"}; + +static char *device_type(int i) +{ + if ((i >= 0) && (i <= 2)) + return(device_types[i]); + return("unknown"); +} + +static void yes_no(int condition) +{ + if (condition) + pprint(" yes "); + else pprint(" no "); +} + +static int set_dsp(int fd, int channels, int bits, int *rate) +{ + int val; + val = channels; + ioctl(fd, SOUND_PCM_WRITE_CHANNELS, &val); + if (val != channels) return(MUS_ERROR); + val = bits; + ioctl(fd, SOUND_PCM_WRITE_BITS, &val); + if (val != bits) return(MUS_ERROR); + ioctl(fd, SOUND_PCM_WRITE_RATE, rate); + return(MUS_NO_ERROR); +} + +static void oss_describe_audio_state_1(void) +{ + /* this code taken largely from "Linux Multimedia Guide" by Jeff Tranter, O'Reilly & Associates, Inc 1996 */ + /* it is explicitly released under the GPL, so I think I can use it here without elaborate disguises */ + int fd; + int status = 0, level, i, recsrc, devmask, recmask, stereodevs, caps; + int numdevs = 0, rate = 0, channels = 0, bits = 0, blocksize = 0, formats = 0, deffmt = 0, min_rate = 0, max_rate = 0; + struct synth_info sinfo; + struct midi_info minfo; + const char *sound_device_names[] = SOUND_DEVICE_LABELS; + char dsp_name[LABEL_BUFFER_SIZE]; + char version[LABEL_BUFFER_SIZE]; + int dsp_num = 0; +#ifdef NEW_OSS + mixer_info mixinfo; + oss_sysinfo sysinfo; +#endif + + if (sound_cards <= 0) mus_audio_initialize(); + memset((void *)dsp_name, 0, LABEL_BUFFER_SIZE); + memset((void *)version, 0, LABEL_BUFFER_SIZE); + +#ifdef NEW_OSS + fd = open(DAC_NAME, O_WRONLY, 0); + if (fd == -1) fd = open(SYNTH_NAME, O_RDONLY, 0); + if (fd == -1) fd = open(MIXER_NAME, O_RDONLY, 0); + if (fd != -1) + { + status = ioctl(fd, OSS_GETVERSION, &level); + new_oss_running = (status == 0); + status = ioctl(fd, OSS_SYSINFO, &sysinfo); + close(fd); + } +#endif + + if (new_oss_running) + { +#ifdef NEW_OSS + if (status == 0) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "OSS version: %s\n", sysinfo.version); + pprint(audio_strbuf); + } + else + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "OSS version: %x.%x.%x\n", (level >> 16) & 0xff, (level >> 8) & 0xff, level & 0xff); + pprint(audio_strbuf); + } +#else + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "OSS version: %x.%x.%x\n", (level >> 16) & 0xff, (level >> 8) & 0xff, level & 0xff); + pprint(audio_strbuf); +#endif + } + else + { + /* refers to the version upon compilation */ + mus_snprintf(version, LABEL_BUFFER_SIZE, "%d", SOUND_VERSION); + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "OSS version: %c.%c.%c\n", version[0], version[1], version[2]); + pprint(audio_strbuf); + } + + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "%d card%s found", sound_cards, (sound_cards != 1) ? "s" : ""); pprint(audio_strbuf); + if (sound_cards > 1) + { + pprint(": "); + for (i = 0; i < sound_cards; i++) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "/dev/dsp%d with /dev/mixer%d%s", + audio_dsp[i], + audio_mixer[i], + (i < (sound_cards - 1)) ? ", " : ""); + pprint(audio_strbuf); + } + } + pprint("\n\n"); + + fd = open(SYNTH_NAME, O_RDWR, 0); + if (fd == -1) fd = open(SYNTH_NAME, O_RDONLY, 0); + if (fd == -1) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "%s: %s\n", SYNTH_NAME, strerror(errno)); pprint(audio_strbuf); + pprint("no synth found\n"); + } + else + { + status = ioctl(fd, SNDCTL_SEQ_NRSYNTHS, &numdevs); + if (status == -1) + { + close(fd); fd = -1; + pprint("no sequencer?"); + } + else + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "/dev/sequencer: %d device%s installed\n", numdevs, (numdevs == 1) ? "" : "s"); + pprint(audio_strbuf); + for (i = 0; i < numdevs; i++) + { + sinfo.device = i; + status = ioctl(fd, SNDCTL_SYNTH_INFO, &sinfo); + if (status != -1) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " device: %d: %s, %s, %d voices\n", i, sinfo.name, device_type(sinfo.synth_type), sinfo.nr_voices); + pprint(audio_strbuf); + } + } + status = ioctl(fd, SNDCTL_SEQ_NRMIDIS, &numdevs); + if (status == -1) + { + close(fd); fd = -1; + pprint("no midi"); + } + else + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " %d midi device%s installed\n", numdevs, (numdevs == 1) ? "" : "s"); + pprint(audio_strbuf); + for (i = 0; i < numdevs; i++) + { + minfo.device = i; + status = ioctl(fd, SNDCTL_MIDI_INFO, &minfo); + if (status != -1) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " device %d: %s, %s\n", i, minfo.name, synth_name(minfo.dev_type)); + pprint(audio_strbuf); + } + } + } + } + } + if (fd != -1) close(fd); + pprint("--------------------------------\n"); + +MIXER_INFO: + mus_snprintf(dsp_name, LABEL_BUFFER_SIZE, "%s%d", MIXER_NAME, dsp_num); + fd = linux_audio_open(dsp_name, O_RDWR, 0, 0); + if (fd == -1) + { + /* maybe output only */ + fd = linux_audio_open(dsp_name, O_WRONLY, 0, 0); + if (fd == -1) + { + if (dsp_num == 0) + { + mus_snprintf(dsp_name, LABEL_BUFFER_SIZE, "%s", DAC_NAME); + fd = linux_audio_open(DAC_NAME, O_RDWR, 0, 0); + if (fd == -1) + { + /* maybe output only */ + fd = linux_audio_open(DAC_NAME, O_WRONLY, 0, 0); + if (fd == -1) + { + pprint("no audio device found\n"); + return; + } + } + } + else goto AUDIO_INFO; /* no /dev/mixern */ + } + else pprint("no audio input enabled\n"); + } + if (fd == -1) goto AUDIO_INFO; + +#ifdef NEW_OSS + if (new_oss_running) status = ioctl(fd, SOUND_MIXER_INFO, &mixinfo); +#endif + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "%s", dsp_name); + pprint(audio_strbuf); +#ifdef NEW_OSS + if ((new_oss_running) && (status == 0)) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " (%s", mixinfo.name); + pprint(audio_strbuf); + for (i = 0; i < sound_cards; i++) + { + if ((audio_mixer[i] == dsp_num) && (audio_type[i] == SONORUS_STUDIO)) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " in mode %d", audio_mode[i]); + pprint(audio_strbuf); + break; + } + } + pprint(")"); + } +#endif + status = ioctl(fd, SOUND_MIXER_READ_RECSRC, &recsrc); + if (status == -1) + { + linux_audio_close(fd); + fd = -1; + pprint(" no recsrc\n"); + } + else + { + status = ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devmask); + if ((status == -1) || (devmask == 0)) + { + linux_audio_close(fd); + fd = -1; + if (status == -1) pprint(" no devmask\n"); else pprint(" (no reported devices)"); + } + else + { + status = ioctl(fd, SOUND_MIXER_READ_RECMASK, &recmask); + if (status == -1) + { + pprint(" no recmask\n"); + recmask = 0; + } + status = ioctl(fd, SOUND_MIXER_READ_STEREODEVS, &stereodevs); + if (status == -1) + { + pprint(" no stereodevs\n"); + stereodevs = 0; + } + status = ioctl(fd, SOUND_MIXER_READ_CAPS, &caps); + if (status == -1) + { + pprint(" no caps\n"); + caps = 0; + } + pprint(":\n\n" + " mixer recording active stereo current\n" + " channel source source device level\n" + " -------- -------- -------- -------- -------- \n"); + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + { + if ((1<<i) & devmask) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " %-10s", sound_device_names[i]); + pprint(audio_strbuf); + yes_no((1 << i) & recmask); + yes_no((1 << i) & recsrc); + yes_no((1 << i) & stereodevs); + status = ioctl(fd, MIXER_READ(i), &level); + if (status != -1) + { + if ((1<<i) & stereodevs) + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " %.2f %.2f", (float)(level & 0xff) * 0.01, (float)((level & 0xff00) >> 8) * 0.01); + else mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " %.2f", (float)(level & 0xff) * 0.01); + /* can't use %% here because subsequent fprintf in pprint evaluates the %! #$@$! */ + pprint(audio_strbuf); + } + pprint("\n"); + } + } + pprint("--------------------------------\n"); + } + } + +AUDIO_INFO: + if (fd != -1) {linux_audio_close(fd); fd = -1;} + mus_snprintf(dsp_name, LABEL_BUFFER_SIZE, "%s%d", DAC_NAME, dsp_num); + fd = linux_audio_open(dsp_name, O_RDWR, 0, 0); + if ((fd == -1) && (dsp_num == 0)) fd = linux_audio_open(DAC_NAME, O_WRONLY, 0, 0); + if (fd == -1) return; + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "%s:\n\n", dsp_name); pprint(audio_strbuf); + if ((ioctl(fd, SOUND_PCM_READ_RATE, &rate) != -1) && + (ioctl(fd, SOUND_PCM_READ_CHANNELS, &channels) != -1) && + (ioctl(fd, SOUND_PCM_READ_BITS, &bits) != -1) && + (ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &blocksize) != -1)) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, + " defaults:\n sampling rate: %d, chans: %d, sample size: %d bits, block size: %d bytes", + rate, channels, bits, blocksize); + pprint(audio_strbuf); + +#ifdef SNDCTL_DSP_GETOSPACE + { + audio_buf_info abi; + ioctl(fd, SNDCTL_DSP_GETOSPACE, &abi); + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " (%d fragments)\n", abi.fragments); + pprint(audio_strbuf); + } +#else + pprint("\n"); +#endif + + deffmt = AFMT_QUERY; + if ((ioctl(fd, SOUND_PCM_SETFMT, &deffmt) != -1) && + (ioctl(fd, SOUND_PCM_GETFMTS, &formats) != -1)) + { + pprint(" supported formats:\n"); + if (formats & AFMT_MU_LAW) {pprint(" mulaw"); if (deffmt == AFMT_MU_LAW) pprint(" (default)"); pprint("\n");} + if (formats & AFMT_A_LAW) {pprint(" alaw"); if (deffmt == AFMT_A_LAW) pprint(" (default)"); pprint("\n");} + if (formats & AFMT_IMA_ADPCM) {pprint(" adpcm"); if (deffmt == AFMT_IMA_ADPCM) pprint(" (default)"); pprint("\n");} + if (formats & AFMT_U8) {pprint(" unsigned byte"); if (deffmt == AFMT_U8) pprint(" (default)"); pprint("\n");} + if (formats & AFMT_S16_LE) {pprint(" signed little-endian short"); if (deffmt == AFMT_S16_LE) pprint(" (default)"); pprint("\n");} + if (formats & AFMT_S16_BE) {pprint(" signed big-endian short"); if (deffmt == AFMT_S16_BE) pprint(" (default)"); pprint("\n");} + if (formats & AFMT_S8) {pprint(" signed byte"); if (deffmt == AFMT_S8) pprint(" (default)"); pprint("\n");} + if (formats & AFMT_U16_LE) {pprint(" unsigned little-endian short"); if (deffmt == AFMT_U16_LE) pprint(" (default)"); pprint("\n");} + if (formats & AFMT_U16_BE) {pprint(" unsigned big-endian short"); if (deffmt == AFMT_U16_BE) pprint(" (default)"); pprint("\n");} + if (formats & AFMT_MPEG) {pprint(" mpeg 2"); if (deffmt == AFMT_MPEG) pprint(" (default)"); pprint("\n");} +#ifdef NEW_OSS + if (formats & AFMT_S32_LE) {pprint(" signed little-endian int"); if (deffmt == AFMT_S32_LE) pprint(" (default)"); pprint("\n");} + if (formats & AFMT_S32_BE) {pprint(" signed big-endian int"); if (deffmt == AFMT_S32_BE) pprint(" (default)"); pprint("\n");} +#endif + status = ioctl(fd, SNDCTL_DSP_GETCAPS, &caps); + if (status != -1) + { + if (caps & DSP_CAP_DUPLEX) pprint(" full duplex\n"); + pprint(" sample srate\n channels size min max\n"); + for (channels = 1; channels <= 2; channels++) + { + for (bits = 8; bits <= 16; bits += 8) + { + min_rate = 1; + if (set_dsp(fd, channels, bits, &min_rate) == -1) continue; + max_rate = 100000; + if (set_dsp(fd, channels, bits, &max_rate) == -1) continue; + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " %4d %8d %8d %8d\n", channels, bits, min_rate, max_rate); + pprint(audio_strbuf); + } + } + } + } + } + pprint("--------------------------------\n"); + linux_audio_close(fd); + fd = -1; + dsp_num++; + if (dsp_num < 16) + { + mus_snprintf(dsp_name, LABEL_BUFFER_SIZE, "%s%d", MIXER_NAME, dsp_num); + goto MIXER_INFO; + } +} + +/* ------------------------------- ALSA, OSS, Jack ----------------------------------- */ +/* API being used */ + +static int api = ALSA_API; +int mus_audio_api(void) {return(api);} + +/* hopefully first call to sndlib will be this... */ +static int probe_api(void); +static int (*vect_mus_audio_initialize)(void); + +/* FIXME: add a suitable default for all other vectors + so that a call happening before mus_audio_initialize + can be detected */ +/* I don't think this is necessary -- documentation discusses this + * (mus_sound_initialize calls mus_audio_initialize) + */ + +/* vectors for the rest of the sndlib api */ +static void (*vect_mus_oss_set_buffers)(int num, int size); +static int (*vect_mus_audio_systems)(void); +static char* (*vect_mus_audio_system_name)(int system); +static char* (*vect_mus_audio_moniker)(void); +static int (*vect_mus_audio_open_output)(int ur_dev, int srate, int chans, int format, int size); +static int (*vect_mus_audio_open_input)(int ur_dev, int srate, int chans, int format, int requested_size); +static int (*vect_mus_audio_write)(int id, char *buf, int bytes); +static int (*vect_mus_audio_read)(int id, char *buf, int bytes); +static int (*vect_mus_audio_close)(int id); +static int (*vect_mus_audio_mixer_read)(int ur_dev, int field, int chan, float *val); +static int (*vect_mus_audio_mixer_write)(int ur_dev, int field, int chan, float *val); +static void (*vect_describe_audio_state_1)(void); + +/* vectors for the rest of the sndlib api */ +int mus_audio_initialize(void) +{ + return(probe_api()); +} + +void mus_oss_set_buffers(int num, int size) +{ + vect_mus_oss_set_buffers(num, size); +} + +int mus_audio_systems(void) +{ + return(vect_mus_audio_systems()); +} + +char* mus_audio_system_name(int system) +{ + return(vect_mus_audio_system_name(system)); +} + +#if HAVE_ALSA +static char* alsa_mus_audio_moniker(void); +#endif + +char* mus_audio_moniker(void) +{ +#if (HAVE_OSS && HAVE_ALSA) + char *both_names; + both_names = (char *)CALLOC(PRINT_BUFFER_SIZE, sizeof(char)); + /* need to be careful here since these use the same constant buffer */ + strcpy(both_names, oss_mus_audio_moniker()); + strcat(both_names, ", "); + strcat(both_names, alsa_mus_audio_moniker()); + return(both_names); /* tiny memory leak ... */ +#else + return(vect_mus_audio_moniker()); +#endif +} + +int mus_audio_open_output(int ur_dev, int srate, int chans, int format, int size) +{ + return(vect_mus_audio_open_output(ur_dev, srate, chans, format, size)); +} + +int mus_audio_open_input(int ur_dev, int srate, int chans, int format, int requested_size) +{ + return(vect_mus_audio_open_input(ur_dev, srate, chans, format, requested_size)); +} + +int mus_audio_write(int id, char *buf, int bytes) +{ + return(vect_mus_audio_write(id, buf, bytes)); +} + +int mus_audio_read(int id, char *buf, int bytes) +{ + return(vect_mus_audio_read(id, buf, bytes)); +} + +int mus_audio_close(int id) +{ + return(vect_mus_audio_close(id)); +} + +int mus_audio_mixer_read(int ur_dev, int field, int chan, float *val) +{ + return(vect_mus_audio_mixer_read(ur_dev, field, chan, val)); +} + +int mus_audio_mixer_write(int ur_dev, int field, int chan, float *val) +{ + return(vect_mus_audio_mixer_write(ur_dev, field, chan, val)); +} + +static void describe_audio_state_1(void) +{ + vect_describe_audio_state_1(); +} + +#if HAVE_JACK + static int jack_mus_audio_initialize(void); +#endif + +#if (!HAVE_ALSA) +static int probe_api(void) +{ +#if HAVE_JACK + { + int jackprobe = jack_mus_audio_initialize(); + if (jackprobe == MUS_ERROR) + { +#endif + /* go for the oss api */ + api = OSS_API; + vect_mus_audio_initialize = oss_mus_audio_initialize; + vect_mus_oss_set_buffers = oss_mus_oss_set_buffers; + vect_mus_audio_systems = oss_mus_audio_systems; + vect_mus_audio_system_name = oss_mus_audio_system_name; + vect_mus_audio_moniker = oss_mus_audio_moniker; + vect_mus_audio_open_output = oss_mus_audio_open_output; + vect_mus_audio_open_input = oss_mus_audio_open_input; + vect_mus_audio_write = oss_mus_audio_write; + vect_mus_audio_read = oss_mus_audio_read; + vect_mus_audio_close = oss_mus_audio_close; + vect_mus_audio_mixer_read = oss_mus_audio_mixer_read; + vect_mus_audio_mixer_write = oss_mus_audio_mixer_write; + vect_describe_audio_state_1 = oss_describe_audio_state_1; + return(vect_mus_audio_initialize()); +#if HAVE_JACK + } + return(jackprobe); + } +#endif +} +#endif + +#endif + + +/* ------------------------------- ALSA ----------------------------------------- */ +/* + * added HAVE_NEW_ALSA, and changed various calls to reflect the new calling sequences (all under HAVE_NEW_ALSA) + * also scheme/ruby tie-ins, and other such changes. Changed the names of the environment variables to use MUS, not SNDLIB. + * reformatted and reorganized to be like the rest of the code + * changed default device to "default" + * -- Bill 3-Feb-06 + * + * error handling (mus_error) changed by Bill 14-Nov-02 + * 0.5 support removed by Bill 24-Mar-02 + * + * changed for 0.9.x api by Fernando Lopez-Lezcano <nando@ccrma.stanford.edu> + * + * sndlib "exports" only one soundcard with two directions (if they are available), + * and only deals with the alsa library pcm's. It does not scan for available + * cards and devices at the hardware level. Which device it uses can be defined by: + * + * - setting variables in the environment (searched for in the following order): + * MUS_ALSA_PLAYBACK_DEVICE + * defines the name of the playback device + * MUS_ALSA_CAPTURE_DEVICE + * defines the name of the capture device + * MUS_ALSA_DEVICE + * defines the name of the playback and capture device + * use the first two if the playback and capture devices are different or the + * third if they are the same. + * - if no variables are found in the environment sndlib tries to probe for a + * default device named "sndlib" (in alsa 0.9 devices are configured in + * /usr/share/alsa/alsa.conf or in ~/.asoundrc) + * - if "sndlib" is not a valid device "hw:0,0" was used [but now it looks for "default"] (which by default should + * point to the first device of the first card + * + * Some default settings are controllable through the environment as well: + * MUS_ALSA_BUFFER_SIZE = size of each buffer in frames + * MUS_ALSA_BUFFERS = number of buffers + * + * changed 18-Sep-00 by Bill: new error handling: old mus_audio_error folded into + * mus_error; mus_error itself should be used only for "real" errors -- things + * that can cause a throw (a kind of global jump elsewhere); use mus_print for informational + * stuff -- in Snd, mus_print will also save everything printed in the error dialog. + * In a few cases, I tried to fix the code to unwind before mus_error, and in others + * I've changed mus_error to mus_print, but some of these may be mistaken. + * Look for ?? below for areas where I'm not sure I rewrote code correctly. + * + * changed for 0.6.x api by Paul Barton-Davis, pbd@op.net + * + * changed for 0.5.x api by Fernando Lopez-Lezcano, nando@ccrma.stanford.edu + * 04-10-2000: + * based on original 0.4.x code by Paul Barton-Davis (not much left of it :-) + * also Bill's code and Jaroslav Kysela (aplay.c and friends) + * + * Changes: + * 04/25/2000: finished major rework, snd-dac now automatically decides which + * device or devices it uses for playback. Multiple device use is + * for now restricted to only two at most (more changes in Bill's + * needed to be able to support more). Four channel playback in + * Ensoniq AudioPCI and relatives possible (with proper settings + * of the mixer) as well as using two separate cards. + * 04/11/2000: added reporting of alsa sound formats +*/ + +#if HAVE_ALSA + +#if (!HAVE_OSS) +#define AUDIO_OK +#endif + +#include <sys/ioctl.h> + +#if (!HAVE_NEW_ALSA) + #define ALSA_PCM_OLD_HW_PARAMS_API + #define ALSA_PCM_OLD_SW_PARAMS_API +#endif + +#if HAVE_ALSA_ASOUNDLIB_H + #include <alsa/asoundlib.h> +#else + #include <sys/asoundlib.h> +#endif + +#if SND_LIB_VERSION < ((0<<16)|(6<<8)|(0)) + #error ALSA version is too old -- audio.c needs 0.9 or later +#endif + +/* prototypes for the alsa sndlib functions */ +static int alsa_mus_audio_initialize(void); +static void alsa_mus_oss_set_buffers(int num, int size); +static int alsa_mus_audio_systems(void); +static char* alsa_mus_audio_system_name(int system); +static int alsa_mus_audio_open_output(int ur_dev, int srate, int chans, int format, int size); +static int alsa_mus_audio_open_input(int ur_dev, int srate, int chans, int format, int requested_size); +static int alsa_mus_audio_write(int id, char *buf, int bytes); +static int alsa_mus_audio_read(int id, char *buf, int bytes); +static int alsa_mus_audio_close(int id); +static int alsa_mus_audio_mixer_read(int ur_dev, int field, int chan, float *val); +static int alsa_mus_audio_mixer_write(int ur_dev, int field, int chan, float *val); +static void alsa_describe_audio_state_1(void); + +/* decide which api to activate */ + +static int probe_api(void) +{ +#if HAVE_JACK + int jackprobe; + jackprobe = jack_mus_audio_initialize(); + if (jackprobe == MUS_ERROR) + { +#endif + int card = -1; + if ((snd_card_next(&card) >= 0) && (card >= 0)) + { + /* the alsa library has detected one or more cards */ + api = ALSA_API; + vect_mus_audio_initialize = alsa_mus_audio_initialize; + vect_mus_oss_set_buffers = alsa_mus_oss_set_buffers; + vect_mus_audio_systems = alsa_mus_audio_systems; + vect_mus_audio_system_name = alsa_mus_audio_system_name; + vect_mus_audio_moniker = alsa_mus_audio_moniker; + vect_mus_audio_open_output = alsa_mus_audio_open_output; + vect_mus_audio_open_input = alsa_mus_audio_open_input; + vect_mus_audio_write = alsa_mus_audio_write; + vect_mus_audio_read = alsa_mus_audio_read; + vect_mus_audio_close = alsa_mus_audio_close; + vect_mus_audio_mixer_read = alsa_mus_audio_mixer_read; + vect_mus_audio_mixer_write = alsa_mus_audio_mixer_write; + vect_describe_audio_state_1 = alsa_describe_audio_state_1; + } + else + { + /* go for the oss api */ + api = OSS_API; + vect_mus_audio_initialize = oss_mus_audio_initialize; + vect_mus_oss_set_buffers = oss_mus_oss_set_buffers; + vect_mus_audio_systems = oss_mus_audio_systems; + vect_mus_audio_system_name = oss_mus_audio_system_name; + vect_mus_audio_moniker = oss_mus_audio_moniker; + vect_mus_audio_open_output = oss_mus_audio_open_output; + vect_mus_audio_open_input = oss_mus_audio_open_input; + vect_mus_audio_write = oss_mus_audio_write; + vect_mus_audio_read = oss_mus_audio_read; + vect_mus_audio_close = oss_mus_audio_close; + vect_mus_audio_mixer_read = oss_mus_audio_mixer_read; + vect_mus_audio_mixer_write = oss_mus_audio_mixer_write; + vect_describe_audio_state_1 = oss_describe_audio_state_1; + } + /* will the _real_ mus_audio_initialize please stand up? */ + return(vect_mus_audio_initialize()); +#if HAVE_JACK + } + return(jackprobe); +#endif +} + +/* convert a sndlib sample format to an alsa sample format */ + +static snd_pcm_format_t to_alsa_format(int snd_format) +{ + switch (snd_format) + { + case MUS_BYTE: return(SND_PCM_FORMAT_S8); + case MUS_UBYTE: return(SND_PCM_FORMAT_U8); + case MUS_MULAW: return(SND_PCM_FORMAT_MU_LAW); + case MUS_ALAW: return(SND_PCM_FORMAT_A_LAW); + case MUS_BSHORT: return(SND_PCM_FORMAT_S16_BE); + case MUS_LSHORT: return(SND_PCM_FORMAT_S16_LE); + case MUS_UBSHORT: return(SND_PCM_FORMAT_U16_BE); + case MUS_ULSHORT: return(SND_PCM_FORMAT_U16_LE); + case MUS_B24INT: return(SND_PCM_FORMAT_S24_BE); + case MUS_L24INT: return(SND_PCM_FORMAT_S24_LE); + case MUS_BINT: return(SND_PCM_FORMAT_S32_BE); + case MUS_LINT: return(SND_PCM_FORMAT_S32_LE); + case MUS_BINTN: return(SND_PCM_FORMAT_S32_BE); + case MUS_LINTN: return(SND_PCM_FORMAT_S32_LE); + case MUS_BFLOAT: return(SND_PCM_FORMAT_FLOAT_BE); + case MUS_LFLOAT: return(SND_PCM_FORMAT_FLOAT_LE); + case MUS_BDOUBLE: return(SND_PCM_FORMAT_FLOAT64_BE); + case MUS_LDOUBLE: return(SND_PCM_FORMAT_FLOAT64_LE); + } + return((snd_pcm_format_t)MUS_ERROR); +} + +/* FIXME: this is not taking yet into account the + * number of bits that a given alsa format is actually + * using... + */ + +static int to_mus_format(int alsa_format) +{ + /* alsa format definitions from asoundlib.h (0.9 cvs 6/27/2001) */ + switch (alsa_format) + { + case SND_PCM_FORMAT_S8: return(MUS_BYTE); + case SND_PCM_FORMAT_U8: return(MUS_UBYTE); + case SND_PCM_FORMAT_S16_LE: return(MUS_LSHORT); + case SND_PCM_FORMAT_S16_BE: return(MUS_BSHORT); + case SND_PCM_FORMAT_U16_LE: return(MUS_ULSHORT); + case SND_PCM_FORMAT_U16_BE: return(MUS_UBSHORT); + case SND_PCM_FORMAT_S24_LE: return(MUS_L24INT); + case SND_PCM_FORMAT_S24_BE: return(MUS_B24INT); + case SND_PCM_FORMAT_S32_LE: return(MUS_LINTN); /* 32bit normalized plays 24bit and 16bit files with same amplitude bound (for 24 bit cards) */ + case SND_PCM_FORMAT_S32_BE: return(MUS_BINTN); + case SND_PCM_FORMAT_FLOAT_LE: return(MUS_LFLOAT); + case SND_PCM_FORMAT_FLOAT_BE: return(MUS_BFLOAT); + case SND_PCM_FORMAT_FLOAT64_LE: return(MUS_LDOUBLE); + case SND_PCM_FORMAT_FLOAT64_BE: return(MUS_BDOUBLE); + case SND_PCM_FORMAT_MU_LAW: return(MUS_MULAW); + case SND_PCM_FORMAT_A_LAW: return(MUS_ALAW); + /* formats with no translation in snd */ + case SND_PCM_FORMAT_U24_LE: + case SND_PCM_FORMAT_U24_BE: + case SND_PCM_FORMAT_U32_LE: + case SND_PCM_FORMAT_U32_BE: + case SND_PCM_FORMAT_IEC958_SUBFRAME_LE: + case SND_PCM_FORMAT_IEC958_SUBFRAME_BE: + case SND_PCM_FORMAT_IMA_ADPCM: + case SND_PCM_FORMAT_MPEG: + case SND_PCM_FORMAT_GSM: + case SND_PCM_FORMAT_SPECIAL: + default: + return(MUS_ERROR); + } +} + +/* convert a sndlib device into an alsa device number and channel + * [has to be coordinated with following function!] + */ + +/* very simplistic approach, device mapping should also depend + * on which card we're dealing with, digital i/o devices should + * be identified as such and so on + */ + +/* NOTE: in the Delta1010 digital i/o is just a pair of channels + * in the 10 channel playback frame or 12 channel capture frame, + * how do we specify that??? + */ + +static int to_alsa_device(int dev, int *adev, snd_pcm_stream_t *achan) +{ + switch(dev) + { + /* default values are a problem because the concept does + * not imply a direction (playback or capture). This works + * fine as long as both directions of a device are symetric, + * the Midiman 1010, for example, has 10 channel frames for + * playback and 12 channel frames for capture and breaks + * the recorder (probes the default, defaults to output, + * uses the values for input). + */ + case MUS_AUDIO_DEFAULT: + case MUS_AUDIO_DUPLEX_DEFAULT: + case MUS_AUDIO_LINE_OUT: + /* analog output */ + (*adev) = 0; + (*achan) = SND_PCM_STREAM_PLAYBACK; + break; + case MUS_AUDIO_AUX_OUTPUT: + /* extra analog output */ + (*adev) = 1; + (*achan) = SND_PCM_STREAM_PLAYBACK; + break; + case MUS_AUDIO_DAC_OUT: + /* analog outputs */ + (*adev) = 2; + (*achan) = SND_PCM_STREAM_PLAYBACK; + break; + case MUS_AUDIO_MICROPHONE: + case MUS_AUDIO_LINE_IN: + /* analog input */ + (*adev) = 0; + (*achan) = SND_PCM_STREAM_CAPTURE; + break; + case MUS_AUDIO_AUX_INPUT: + /* extra analog input */ + (*adev) = 1; + (*achan) = SND_PCM_STREAM_CAPTURE; + break; + case MUS_AUDIO_DIGITAL_OUT: + case MUS_AUDIO_SPDIF_OUT: + case MUS_AUDIO_AES_OUT: + case MUS_AUDIO_ADAT_OUT: + case MUS_AUDIO_DIGITAL_IN: + case MUS_AUDIO_SPDIF_IN: + case MUS_AUDIO_AES_IN: + case MUS_AUDIO_ADAT_IN: + case MUS_AUDIO_SPEAKERS: + case MUS_AUDIO_DAC_FILTER: + case MUS_AUDIO_MIXER: + case MUS_AUDIO_LINE1: + case MUS_AUDIO_LINE2: + case MUS_AUDIO_LINE3: + case MUS_AUDIO_CD: + default: + return(MUS_ERROR); + break; + } + return(0); +} + +/* convert an alsa device into a sndlib device + * [has to be coordinated with previous function!] + * + * naming here is pretty much arbitrary. We have to have + * a bidirectional mapping between sndlib devices and + * alsa devices and that's just not possible (I think). + * This stopgap mapping ignores digital input and output + * devices - how to differentiate them in alsa? + */ + +static int to_sndlib_device(int dev, int channel) +{ + switch (channel) + { + case SND_PCM_STREAM_PLAYBACK: + switch (dev) + { + /* works only for the first three outputs */ + case 0: return(MUS_AUDIO_LINE_OUT); + case 1: return(MUS_AUDIO_AUX_OUTPUT); + case 2: return(MUS_AUDIO_DAC_OUT); + default: + return(MUS_ERROR); + } + case SND_PCM_STREAM_CAPTURE: + switch (dev) + { + case 0: return(MUS_AUDIO_LINE_IN); + case 1: return(MUS_AUDIO_AUX_INPUT); + default: + return(MUS_ERROR); + } + break; + } + return(MUS_ERROR); +} + + +static int alsa_mus_error(int type, char *message) +{ + if (message) + { + mus_print(message); + FREE(message); + } + return(MUS_ERROR); +} + +#if 0 +static void alsa_dump_hardware_params(snd_pcm_hw_params_t *params, const char *msg) +{ + snd_output_t *out; + snd_output_stdio_attach(&out, stderr, 0); + fprintf(stderr, "%s\n", msg); + snd_pcm_hw_params_dump(params, out); +} + +static void alsa_dump_software_params(snd_pcm_sw_params_t *params, const char *msg) +{ + snd_output_t *out; + snd_output_stdio_attach(&out, stderr, 0); + fprintf(stderr, "%s\n", msg); + snd_pcm_sw_params_dump(params, out); +} +#endif + + +/* dump current hardware and software configuration */ + +static void alsa_dump_configuration(char *name, snd_pcm_hw_params_t *hw_params, snd_pcm_sw_params_t *sw_params) +{ + int err; + char *str; + size_t len; + snd_output_t *buf; + +#if (SND_LIB_MAJOR == 0) || ((SND_LIB_MAJOR == 1) && (SND_LIB_MINOR == 0) && (SND_LIB_SUBMINOR < 8)) + return; /* avoid Alsa bug */ +#endif + + err = snd_output_buffer_open(&buf); + if (err < 0) + { + mus_print("could not open dump buffer: %s", snd_strerror(err)); + } + else + { + if (hw_params) + { + snd_output_puts(buf, "hw_params status of "); + snd_output_puts(buf, name); + snd_output_puts(buf, "\n"); + err = snd_pcm_hw_params_dump(hw_params, buf); + if (err < 0) + mus_print("snd_pcm_hw_params_dump: %s", snd_strerror(err)); + } + if (sw_params) + { + snd_output_puts(buf, "sw_params status of "); + snd_output_puts(buf, name); + snd_output_puts(buf, "\n"); + err = snd_pcm_sw_params_dump(sw_params, buf); + if (err < 0) + mus_print("snd_pcm_hw_params_dump: %s", snd_strerror(err)); + } + snd_output_putc(buf, '\0'); + len = snd_output_buffer_string(buf, &str); + if (len > 1) + mus_print("status of %s\n%s", name, str); + snd_output_close(buf); + } +} + +/* get hardware params for a pcm */ + +static snd_pcm_hw_params_t *alsa_get_hardware_params(const char *name, snd_pcm_stream_t stream, int mode) +{ + int err; + snd_pcm_t *handle; + if ((err = snd_pcm_open(&handle, name, stream, mode | SND_PCM_NONBLOCK)) != 0) + { + alsa_mus_error(MUS_AUDIO_CANT_OPEN, + mus_format("open pcm %s for stream %d: %s", + name, stream, snd_strerror(err))); + return(NULL); + } + else + { + snd_pcm_hw_params_t *params; + params = (snd_pcm_hw_params_t *)calloc(1, snd_pcm_hw_params_sizeof()); + if (params == NULL) + { + snd_pcm_close(handle); + alsa_mus_error(MUS_AUDIO_CONFIGURATION_NOT_AVAILABLE, + mus_format("could not allocate memory for hardware params")); + } + else + { + err = snd_pcm_hw_params_any(handle, params); + if (err < 0) + { + snd_pcm_close(handle); + alsa_mus_error(MUS_AUDIO_CONFIGURATION_NOT_AVAILABLE, + mus_format("snd_pcm_hw_params_any: pcm %s, stream %d, error: %s", + name, stream, snd_strerror(err))); + } + else + { + snd_pcm_close(handle); + return(params); + } + } + } + return(NULL); +} + +/* allocate software params structure */ + +static snd_pcm_sw_params_t *alsa_get_software_params(void) +{ + snd_pcm_sw_params_t *params = NULL; + params = (snd_pcm_sw_params_t *)calloc(1, snd_pcm_sw_params_sizeof()); + if (params == NULL) + { + alsa_mus_error(MUS_AUDIO_CONFIGURATION_NOT_AVAILABLE, + mus_format("could not allocate memory for software params")); + } + return(params); +} + +/* probe a device name against the list of available pcm devices */ + +#ifndef SND_CONFIG_GET_ID_ARGS + #define SND_CONFIG_GET_ID_ARGS 1 +#endif + +static bool alsa_probe_device_name(const char *name) +{ + snd_config_t *conf; + snd_config_iterator_t pos, next; + int err; + + err = snd_config_update(); + if (err < 0) + { + mus_print("snd_config_update: %s", snd_strerror(err)); + return(false); + } + + err = snd_config_search(snd_config, "pcm", &conf); + if (err < 0) + { + mus_print("snd_config_search: %s", snd_strerror(err)); + return(false); + } + + snd_config_for_each(pos, next, conf) + { + snd_config_t *c = snd_config_iterator_entry(pos); +#if (SND_CONFIG_GET_ID_ARGS == 2) + const char *id; + int err = snd_config_get_id(c, &id); + if (err == 0) { + int result = strncmp(name, id, strlen(id)); + if (result == 0 && + (name[strlen(id)] == '\0' || name[strlen(id)] == ':')) + { + return(true); + } + } +#else + const char *id = snd_config_get_id(c); + int result = strncmp(name, id, strlen(id)); + if (result == 0 && + (name[strlen(id)] == '\0' || name[strlen(id)] == ':')) + { + return(true); + } +#endif + } + return(false); +} + +/* check a device name against the list of available pcm devices */ + +static int alsa_check_device_name(const char *name) +{ + if (!alsa_probe_device_name(name)) + { + return(alsa_mus_error(MUS_AUDIO_CANT_READ, + mus_format("alsa could not find device \"%s\" in configuration", + name))); + } + return(MUS_NO_ERROR); +} + + +/* set scheduling priority to SCHED_FIFO + * this will only work if the program that uses sndlib is run as root or is suid root + */ + +/* whether we want to trace calls + * + * set to "1" to print function trace information in the + * snd error window + */ + +static int alsa_trace = 0; + +/* this should go away as it is oss specific */ + +static int fragment_size = 512; +static int fragments = 4; + +static void alsa_mus_oss_set_buffers(int num, int size) +{ + fragments = num; + fragment_size = size; +#if MUS_DEBUGGING + mus_print("set_oss_buffers: %d fragments or size %d", num, size); +#endif +} + +/* total number of soundcards in our setup, set by initialize_audio */ + +/* static int sound_cards = 0; */ + +/* return the number of cards that are available */ + +static int alsa_mus_audio_systems(void) +{ + return(sound_cards); +} + +/* return the type of driver we're dealing with */ + +static char *alsa_mus_audio_moniker(void) +{ + if (version_name == NULL) version_name = (char *)CALLOC(LABEL_BUFFER_SIZE, sizeof(char)); + mus_snprintf(version_name, LABEL_BUFFER_SIZE, "ALSA %s", SND_LIB_VERSION_STR); + return(version_name); +} + +/* handles for both directions of the virtual device */ + +static snd_pcm_t *handles[2] = {NULL, NULL}; + +/* hardware and software parameter sctructure pointers */ + +static snd_pcm_hw_params_t *alsa_hw_params[2] = {NULL, NULL}; /* avoid bogus free */ +static snd_pcm_sw_params_t *alsa_sw_params[2] = {NULL, NULL}; + +/* some defaults */ + +static int alsa_open_mode = SND_PCM_ASYNC; +static int alsa_buffers = 3; +/* size of buffer in number of samples per channel, + * at 44100 approximately 5.9mSecs + */ +static int alsa_samples_per_channel = 1024; +static snd_pcm_access_t alsa_interleave = SND_PCM_ACCESS_RW_INTERLEAVED; +static int alsa_max_capture_channels = 32; + +/* first default name for pcm configuration */ + +static char *alsa_sndlib_device_name = "sndlib"; + +/* second default for playback and capture: hardware pcm, first card, first device */ +/* pcms used by sndlib, playback and capture */ + +static char *alsa_playback_device_name = NULL; +static char *alsa_capture_device_name = NULL; + + +/* -------- tie these names into scheme/ruby -------- */ + +static int alsa_get_max_buffers(void) +{ + unsigned int max_periods = 0, max_rec_periods = 0; + int dir = 0; +#if HAVE_NEW_ALSA + snd_pcm_hw_params_get_periods_max(alsa_hw_params[SND_PCM_STREAM_PLAYBACK], &max_periods, &dir); +#else + max_periods = snd_pcm_hw_params_get_periods_max(alsa_hw_params[SND_PCM_STREAM_PLAYBACK], &dir); +#endif + if (alsa_hw_params[SND_PCM_STREAM_CAPTURE]) + { +#if HAVE_NEW_ALSA + snd_pcm_hw_params_get_periods_max(alsa_hw_params[SND_PCM_STREAM_CAPTURE], &max_rec_periods, &dir); +#else + max_rec_periods = snd_pcm_hw_params_get_periods_max(alsa_hw_params[SND_PCM_STREAM_CAPTURE], &dir); +#endif + if (max_periods > max_rec_periods) + max_periods = max_rec_periods; + } + return(max_periods); +} + +static int alsa_get_min_buffers(void) +{ + unsigned int min_periods = 0, min_rec_periods = 0; + int dir = 0; +#if HAVE_NEW_ALSA + snd_pcm_hw_params_get_periods_min(alsa_hw_params[SND_PCM_STREAM_PLAYBACK], &min_periods, &dir); +#else + min_periods = snd_pcm_hw_params_get_periods_min(alsa_hw_params[SND_PCM_STREAM_PLAYBACK], &dir); +#endif + if (alsa_hw_params[SND_PCM_STREAM_CAPTURE]) + { +#if HAVE_NEW_ALSA + snd_pcm_hw_params_get_periods_min(alsa_hw_params[SND_PCM_STREAM_CAPTURE], &min_rec_periods, &dir); +#else + min_rec_periods = snd_pcm_hw_params_get_periods_min(alsa_hw_params[SND_PCM_STREAM_CAPTURE], &dir); +#endif + if (min_periods < min_rec_periods) + min_periods = min_rec_periods; + } + return(min_periods); +} + +static int alsa_clamp_buffers(int bufs) +{ + int minb, maxb; + minb = alsa_get_min_buffers(); + maxb = alsa_get_max_buffers(); + if (bufs > maxb) + bufs = maxb; + if (bufs < minb) + bufs = minb; + return(bufs); +} + +static snd_pcm_uframes_t alsa_get_min_buffer_size(void) +{ + snd_pcm_uframes_t min_buffer_size = 0, min_rec_buffer_size = 0; +#if HAVE_NEW_ALSA + snd_pcm_hw_params_get_buffer_size_min(alsa_hw_params[SND_PCM_STREAM_PLAYBACK], &min_buffer_size); +#else + min_buffer_size = snd_pcm_hw_params_get_buffer_size_min(alsa_hw_params[SND_PCM_STREAM_PLAYBACK]); +#endif + if (alsa_hw_params[SND_PCM_STREAM_CAPTURE]) + { +#if HAVE_NEW_ALSA + snd_pcm_hw_params_get_buffer_size_min(alsa_hw_params[SND_PCM_STREAM_CAPTURE], &min_rec_buffer_size); +#else + min_rec_buffer_size = snd_pcm_hw_params_get_buffer_size_min(alsa_hw_params[SND_PCM_STREAM_CAPTURE]); +#endif + + if (min_buffer_size < min_rec_buffer_size) + min_buffer_size = min_rec_buffer_size; + } + return(min_buffer_size); +} + +static snd_pcm_uframes_t alsa_get_max_buffer_size(void) +{ + snd_pcm_uframes_t max_buffer_size = 0, max_rec_buffer_size = 0; +#if HAVE_NEW_ALSA + snd_pcm_hw_params_get_buffer_size_max(alsa_hw_params[SND_PCM_STREAM_PLAYBACK], &max_buffer_size); +#else + max_buffer_size = snd_pcm_hw_params_get_buffer_size_max(alsa_hw_params[SND_PCM_STREAM_PLAYBACK]); +#endif + if (alsa_hw_params[SND_PCM_STREAM_CAPTURE]) + { +#if HAVE_NEW_ALSA + snd_pcm_hw_params_get_buffer_size_max(alsa_hw_params[SND_PCM_STREAM_CAPTURE], &max_rec_buffer_size); +#else + max_rec_buffer_size = snd_pcm_hw_params_get_buffer_size_max(alsa_hw_params[SND_PCM_STREAM_CAPTURE]); +#endif + if (max_buffer_size > max_rec_buffer_size) + max_buffer_size = max_rec_buffer_size; + } + return(max_buffer_size); +} + +static int alsa_clamp_buffer_size(int buf_size) +{ + int minb, maxb; + minb = alsa_get_min_buffer_size(); + maxb = alsa_get_max_buffer_size(); + if (buf_size > maxb) + buf_size = maxb; + if (buf_size < minb) + buf_size = minb; + return(buf_size); +} + +static bool alsa_set_playback_parameters(void) +{ + /* playback stream parameters */ + if (alsa_hw_params[SND_PCM_STREAM_PLAYBACK]) free(alsa_hw_params[SND_PCM_STREAM_PLAYBACK]); + alsa_hw_params[SND_PCM_STREAM_PLAYBACK] = alsa_get_hardware_params(alsa_playback_device_name, SND_PCM_STREAM_PLAYBACK, alsa_open_mode); + if (alsa_hw_params[SND_PCM_STREAM_PLAYBACK]) + { + snd_pcm_uframes_t size; + int old_buffers; + old_buffers = alsa_buffers; + if (alsa_sw_params[SND_PCM_STREAM_PLAYBACK]) free(alsa_sw_params[SND_PCM_STREAM_PLAYBACK]); + alsa_sw_params[SND_PCM_STREAM_PLAYBACK] = alsa_get_software_params(); + sound_cards = 1; + alsa_buffers = alsa_clamp_buffers(alsa_buffers); + if (alsa_buffers <= 0) + { + alsa_buffers = old_buffers; + return(false); + } + size = alsa_clamp_buffer_size(alsa_samples_per_channel * alsa_buffers); + if (size <= 0) return(false); + alsa_samples_per_channel = size / alsa_buffers; + } + return(alsa_hw_params[SND_PCM_STREAM_PLAYBACK] && alsa_sw_params[SND_PCM_STREAM_PLAYBACK]); +} + +static bool alsa_set_capture_parameters(void) +{ + /* capture stream parameters */ + if (alsa_hw_params[SND_PCM_STREAM_CAPTURE]) free(alsa_hw_params[SND_PCM_STREAM_CAPTURE]); + alsa_hw_params[SND_PCM_STREAM_CAPTURE] = alsa_get_hardware_params(alsa_capture_device_name, SND_PCM_STREAM_CAPTURE, alsa_open_mode); + if (alsa_hw_params[SND_PCM_STREAM_CAPTURE]) + { + snd_pcm_uframes_t size; + int old_buffers; + old_buffers = alsa_buffers; + if (alsa_sw_params[SND_PCM_STREAM_CAPTURE]) free(alsa_sw_params[SND_PCM_STREAM_CAPTURE]); + alsa_sw_params[SND_PCM_STREAM_CAPTURE] = alsa_get_software_params(); + sound_cards = 1; + alsa_buffers = alsa_clamp_buffers(alsa_buffers); + if (alsa_buffers <= 0) + { + alsa_buffers = old_buffers; + return(false); + } + size = alsa_clamp_buffer_size(alsa_samples_per_channel * alsa_buffers); + if (size <= 0) return(false); + alsa_samples_per_channel = size / alsa_buffers; + } + return(alsa_hw_params[SND_PCM_STREAM_CAPTURE] && alsa_sw_params[SND_PCM_STREAM_CAPTURE]); +} + + +char *mus_alsa_playback_device(void) {return(alsa_playback_device_name);} +char *mus_alsa_set_playback_device(const char *name) +{ + if (alsa_check_device_name(name) == MUS_NO_ERROR) + { + char *old_name = alsa_playback_device_name; + alsa_playback_device_name = strdup(name); + if (!alsa_set_playback_parameters()) + { + alsa_playback_device_name = old_name; /* try to back out of the mistake */ + alsa_set_playback_parameters(); + } + } + return(alsa_playback_device_name); +} + +char *mus_alsa_capture_device(void) {return(alsa_capture_device_name);} +char *mus_alsa_set_capture_device(const char *name) +{ + if (alsa_check_device_name(name) == MUS_NO_ERROR) + { + char *old_name = alsa_capture_device_name; + alsa_capture_device_name = strdup(name); + if (!alsa_set_capture_parameters()) + { + alsa_capture_device_name = old_name; + alsa_set_capture_parameters(); + } + } + return(alsa_capture_device_name); +} + +char *mus_alsa_device(void) {return(alsa_sndlib_device_name);} +char *mus_alsa_set_device(const char *name) +{ + if (alsa_check_device_name(name) == MUS_NO_ERROR) + { + alsa_sndlib_device_name = strdup(name); + mus_alsa_set_playback_device(name); + mus_alsa_set_capture_device(name); + } + return(alsa_sndlib_device_name); +} + +int mus_alsa_buffer_size(void) {return(alsa_samples_per_channel);} +int mus_alsa_set_buffer_size(int size) +{ + snd_pcm_uframes_t bsize; + if (alsa_buffers == 0) alsa_buffers = 1; + if (size > 0) + { + bsize = alsa_clamp_buffer_size(size * alsa_buffers); + alsa_samples_per_channel = bsize / alsa_buffers; + } + return(alsa_samples_per_channel); +} + +int mus_alsa_buffers(void) {return(alsa_buffers);} +int mus_alsa_set_buffers(int num) +{ + snd_pcm_uframes_t size; + if (num > 0) + { + alsa_buffers = alsa_clamp_buffers(num); + if (alsa_buffers > 0) + { + size = alsa_clamp_buffer_size(alsa_samples_per_channel * alsa_buffers); + alsa_samples_per_channel = size / alsa_buffers; + } + } + return(alsa_buffers); +} + +static bool alsa_squelch_warning = false; +bool mus_alsa_squelch_warning(void) {return(alsa_squelch_warning);} +bool mus_alsa_set_squelch_warning(bool val) +{ + alsa_squelch_warning = val; + return(val); +} + + + + +/* return the name of a given system */ + +static char *alsa_mus_audio_system_name(int system) +{ + return(alsa_playback_device_name); +} + +/* get a device name from the environment */ + +static char *alsa_get_device_from_env(const char *name) +{ + char *string = getenv(name); + if (string) + if (alsa_check_device_name(string) == MUS_NO_ERROR) + return(string); + return(NULL); +} + +/* get an integer from the environment */ + +static int alsa_get_int_from_env(const char *name, int *value, int min, int max) +{ + char *string = getenv(name); + if (string) + { + char *end; + long int result = strtol(string, &end, 10); + if (((min != -1) && (max != -1)) && + (result < min || result > max)) + { + return(alsa_mus_error(MUS_AUDIO_CANT_READ, + mus_format("%s ignored: out of range, value=%d, min=%d, max=%d", + name, (int)result, min, max))); + } + else + { + if (errno == ERANGE) + { + return(alsa_mus_error(MUS_AUDIO_CANT_READ, + mus_format("%s ignored: strlol conversion out of range", + name))); + } + else + { + if ((*string != '\0') && (*end == '\0')) + { + *value = (int)result; + return(MUS_NO_ERROR); + } + else + { + return(alsa_mus_error(MUS_AUDIO_CANT_READ, + mus_format("%s ignored: value is \"%s\", not an integer", + name, string))); + } + } + } + } + return(MUS_ERROR); +} + +/* initialize the audio subsystem */ + +/* define environment variable names */ +#define MUS_ALSA_PLAYBACK_DEVICE_ENV_NAME "MUS_ALSA_PLAYBACK_DEVICE" +#define MUS_ALSA_CAPTURE_DEVICE_ENV_NAME "MUS_ALSA_CAPTURE_DEVICE" +#define MUS_ALSA_DEVICE_ENV_NAME "MUS_ALSA_DEVICE" +#define MUS_ALSA_BUFFERS_ENV_NAME "MUS_ALSA_BUFFERS" +#define MUS_ALSA_BUFFER_SIZE_ENV_NAME "MUS_ALSA_BUFFER_SIZE" +#define MUS_ALSA_TRACE_ENV_NAME "MUS_ALSA_TRACE" + +static int alsa_mus_audio_initialize(void) +{ + char *name = NULL; + char *pname; + char *cname; + int value = 0, alsa_buffer_size = 0; + + if (audio_initialized) + return(0); + + sound_cards = 0; + + /* get trace flag from environment */ + if (alsa_get_int_from_env(MUS_ALSA_TRACE_ENV_NAME, &value, 0, 1) == MUS_NO_ERROR) + alsa_trace = value; + + /* try to get device names from environment */ + pname = alsa_get_device_from_env(MUS_ALSA_PLAYBACK_DEVICE_ENV_NAME); + if ((pname) && (alsa_probe_device_name(pname))) + alsa_playback_device_name = pname; + + cname = alsa_get_device_from_env(MUS_ALSA_CAPTURE_DEVICE_ENV_NAME); + if ((cname) && (alsa_probe_device_name(cname))) + alsa_capture_device_name = cname; + + name = alsa_get_device_from_env(MUS_ALSA_DEVICE_ENV_NAME); + if ((name) && (alsa_probe_device_name(name))) + { + if (!alsa_playback_device_name) + alsa_playback_device_name = name; + + if (!alsa_capture_device_name) + alsa_capture_device_name = name; + + alsa_sndlib_device_name = name; + } + + /* now check that we have a plausible name */ + if (!alsa_probe_device_name(alsa_sndlib_device_name)) + { + alsa_sndlib_device_name = "default"; + if (!alsa_probe_device_name(alsa_sndlib_device_name)) + { + alsa_sndlib_device_name = "plughw:0"; + if (!alsa_probe_device_name(alsa_sndlib_device_name)) + alsa_sndlib_device_name = "hw:0"; + } + } + + /* if no device name set yet, try for special sndlib name first */ + if (!alsa_playback_device_name) + { + if (alsa_probe_device_name(alsa_sndlib_device_name)) + alsa_playback_device_name = alsa_sndlib_device_name; + else alsa_playback_device_name = "hw:0"; + } + + if (!alsa_capture_device_name) + { + if (alsa_probe_device_name(alsa_sndlib_device_name)) + alsa_capture_device_name = alsa_sndlib_device_name; + else alsa_capture_device_name = "hw:0"; + } + + alsa_get_int_from_env(MUS_ALSA_BUFFERS_ENV_NAME, &alsa_buffers, -1, -1); + alsa_get_int_from_env(MUS_ALSA_BUFFER_SIZE_ENV_NAME, &alsa_buffer_size, -1, -1); + + if ((alsa_buffer_size > 0) && (alsa_buffers > 0)) + alsa_samples_per_channel = alsa_buffer_size / alsa_buffers; + + if (!alsa_set_playback_parameters()) + { + /* somehow we got a device that passed muster with alsa_probe_device_name, but doesn't return hw params! */ + alsa_playback_device_name = "plughw:0"; + if (!alsa_set_playback_parameters()) + { + alsa_playback_device_name = "hw:0"; + if (!alsa_set_playback_parameters()) + return(MUS_ERROR); + } + } + + if (!alsa_set_capture_parameters()) + { + alsa_capture_device_name = "plughw:0"; + if (!alsa_set_capture_parameters()) + { + alsa_capture_device_name = "hw:0"; + if (!alsa_set_capture_parameters()) + return(MUS_ERROR); + } + } + + if ((!alsa_hw_params[SND_PCM_STREAM_CAPTURE]) || + (!alsa_hw_params[SND_PCM_STREAM_PLAYBACK])) + return(MUS_ERROR); + + audio_initialized = true; + return(0); +} + +/* open an input or output stream */ + +static int alsa_audio_open(int ur_dev, int srate, int chans, int format, int size) +{ + int card, device, alsa_device; + snd_pcm_format_t alsa_format; + snd_pcm_stream_t alsa_stream; + char *alsa_name; + int frames, periods; + int err; + unsigned int r; + snd_pcm_t *handle; + snd_pcm_hw_params_t *hw_params = NULL; + snd_pcm_sw_params_t *sw_params = NULL; + + if ((!audio_initialized) && + (mus_audio_initialize() != MUS_NO_ERROR)) + return(MUS_ERROR); + if (chans <= 0) return(MUS_ERROR); + + if (alsa_trace) + mus_print("%s: %x rate=%d, chans=%d, format=%d:%s, size=%d", + c__FUNCTION__, ur_dev, srate, chans, format, + mus_audio_format_name(format), size); + + card = MUS_AUDIO_SYSTEM(ur_dev); + device = MUS_AUDIO_DEVICE(ur_dev); + + if ((err = to_alsa_device(device, &alsa_device, &alsa_stream)) < 0) + { + return(alsa_mus_error(MUS_AUDIO_DEVICE_NOT_AVAILABLE, + mus_format("%s: cannot translate device %s<%d> to alsa", + snd_strerror(err), mus_audio_device_name(device), device))); + } + if ((alsa_format = to_alsa_format(format)) == (snd_pcm_format_t)MUS_ERROR) + { + return(alsa_mus_error(MUS_AUDIO_FORMAT_NOT_AVAILABLE, + mus_format("could not change %s<%d> to alsa format", + mus_audio_format_name(format), format))); + } + + alsa_name = (alsa_stream == SND_PCM_STREAM_PLAYBACK) ? alsa_playback_device_name : alsa_capture_device_name; + if ((err = snd_pcm_open(&handle, alsa_name, alsa_stream, alsa_open_mode)) != 0) + { + snd_pcm_close(handle); + return(alsa_mus_error(MUS_AUDIO_CANT_OPEN, + mus_format("open pcm %s (%s) stream %s: %s", + mus_audio_device_name(device), alsa_name, snd_pcm_stream_name(alsa_stream), + snd_strerror(err)))); + } + handles[alsa_stream] = handle; + hw_params = alsa_hw_params[alsa_stream]; + sw_params = alsa_sw_params[alsa_stream]; + if ((err = snd_pcm_hw_params_any(handle, hw_params)) < 0) + { + snd_pcm_close(handle); + handles[alsa_stream] = NULL; + alsa_dump_configuration(alsa_name, hw_params, sw_params); + return(alsa_mus_error(MUS_AUDIO_CONFIGURATION_NOT_AVAILABLE, + mus_format("%s: no parameter configurations available for %s", + snd_strerror(err), alsa_name))); + } + + err = snd_pcm_hw_params_set_access(handle, hw_params, alsa_interleave); + if (err < 0) + { + snd_pcm_close(handle); + handles[alsa_stream] = NULL; + alsa_dump_configuration(alsa_name, hw_params, sw_params); + return(alsa_mus_error(MUS_AUDIO_CONFIGURATION_NOT_AVAILABLE, + mus_format("%s: %s: access type %s not available", + snd_strerror(err), alsa_name, snd_pcm_access_name(alsa_interleave)))); + } + + periods = alsa_buffers; + err = snd_pcm_hw_params_set_periods(handle, hw_params, periods, 0); + if (err < 0) + { + unsigned int minp, maxp; + int dir; +#if HAVE_NEW_ALSA + snd_pcm_hw_params_get_periods_min(hw_params, &minp, &dir); + snd_pcm_hw_params_get_periods_max(hw_params, &maxp, &dir); +#else + minp = snd_pcm_hw_params_get_periods_min(hw_params, &dir); + maxp = snd_pcm_hw_params_get_periods_max(hw_params, &dir); +#endif + snd_pcm_close(handle); + handles[alsa_stream] = NULL; + alsa_dump_configuration(alsa_name, hw_params, sw_params); + return(alsa_mus_error(MUS_AUDIO_CONFIGURATION_NOT_AVAILABLE, + mus_format("%s: %s: cannot set number of periods to %d, min is %d, max is %d", + snd_strerror(err), alsa_name, periods, (int)minp, (int)maxp))); + } + + frames = size / chans / mus_bytes_per_sample(format); + + err = snd_pcm_hw_params_set_buffer_size(handle, hw_params, frames * periods); + if (err < 0) + { + snd_pcm_uframes_t minp, maxp; +#if HAVE_NEW_ALSA + snd_pcm_hw_params_get_buffer_size_min(hw_params, &minp); + snd_pcm_hw_params_get_buffer_size_max(hw_params, &maxp); +#else + minp = snd_pcm_hw_params_get_buffer_size_min(hw_params); + maxp = snd_pcm_hw_params_get_buffer_size_max(hw_params); +#endif + snd_pcm_close(handle); + handles[alsa_stream] = NULL; + alsa_dump_configuration(alsa_name, hw_params, sw_params); + return(alsa_mus_error(MUS_AUDIO_CONFIGURATION_NOT_AVAILABLE, + mus_format("%s: %s: cannot set buffer size to %d periods of %d frames; \ +total requested buffer size is %d frames, minimum allowed is %d, maximum is %d", + snd_strerror(err), alsa_name, periods, frames, periods * frames, (int)minp, (int)maxp))); + } + + err = snd_pcm_hw_params_set_format(handle, hw_params, alsa_format); + if (err < 0) + { + snd_pcm_close(handle); + handles[alsa_stream] = NULL; + alsa_dump_configuration(alsa_name, hw_params, sw_params); + return(alsa_mus_error(MUS_AUDIO_CONFIGURATION_NOT_AVAILABLE, + mus_format("%s: %s: cannot set format to %s", + snd_strerror(err), alsa_name, snd_pcm_format_name(alsa_format)))); + } + + err = snd_pcm_hw_params_set_channels(handle, hw_params, chans); + if (err < 0) + { + snd_pcm_close(handle); + handles[alsa_stream] = NULL; + alsa_dump_configuration(alsa_name, hw_params, sw_params); + return(alsa_mus_error(MUS_AUDIO_CONFIGURATION_NOT_AVAILABLE, + mus_format("%s: %s: cannot set channels to %d", + snd_strerror(err), alsa_name, chans))); + } +#if HAVE_NEW_ALSA + { + unsigned int new_rate; + new_rate = srate; + r = snd_pcm_hw_params_set_rate_near(handle, hw_params, &new_rate, 0); + if (r < 0) + { + snd_pcm_close(handle); + handles[alsa_stream] = NULL; + alsa_dump_configuration(alsa_name, hw_params, sw_params); + return(alsa_mus_error(MUS_AUDIO_CONFIGURATION_NOT_AVAILABLE, + mus_format("%s: %s: cannot set sampling rate near %d", + snd_strerror(r), alsa_name, srate))); + } + else + { + if ((new_rate != srate) && (!alsa_squelch_warning)) + { + mus_print("%s: could not set rate to exactly %d, set to %d instead", + alsa_name, srate, new_rate); + } + } + } +#else + r = snd_pcm_hw_params_set_rate_near(handle, hw_params, srate, 0); + if (r < 0) + { + snd_pcm_close(handle); + handles[alsa_stream] = NULL; + alsa_dump_configuration(alsa_name, hw_params, sw_params); + return(alsa_mus_error(MUS_AUDIO_CONFIGURATION_NOT_AVAILABLE, + mus_format("%s: %s: cannot set sampling rate near %d", + snd_strerror(r), alsa_name, srate))); + } + else + { + if (r != srate) + { + mus_print("%s: could not set rate to exactly %d, set to %d instead", + alsa_name, srate, r); + } + } +#endif + + err = snd_pcm_hw_params(handle, hw_params); + if (err < 0) + { + snd_pcm_close(handle); + handles[alsa_stream] = NULL; + alsa_dump_configuration(alsa_name, hw_params, sw_params); + return(alsa_mus_error(MUS_AUDIO_CONFIGURATION_NOT_AVAILABLE, + mus_format("%s: cannot set hardware parameters for %s", + snd_strerror(err), alsa_name))); + } + + snd_pcm_sw_params_current(handle, sw_params); + err = snd_pcm_sw_params(handle, sw_params); + if (err < 0) + { + snd_pcm_close(handle); + handles[alsa_stream] = NULL; + alsa_dump_configuration(alsa_name, hw_params, sw_params); + return(alsa_mus_error(MUS_AUDIO_CONFIGURATION_NOT_AVAILABLE, + mus_format("%s: cannot set software parameters for %s", + snd_strerror(err), alsa_name))); + } + + /* for now the id for the stream is the direction identifier, that is + not a problem because we only advertise one card with two devices */ + return(alsa_stream); +} + +/* sndlib support for opening output devices */ + +static int alsa_mus_audio_open_output(int ur_dev, int srate, int chans, int format, int size) +{ + return(alsa_audio_open(ur_dev, srate, chans, format, size)); +} + +/* sndlib support for opening input devices */ + +static int alsa_mus_audio_open_input(int ur_dev, int srate, int chans, int format, int size) +{ + return(alsa_audio_open(ur_dev, srate, chans, format, size)); +} + +/* sndlib support for closing a device */ + +/* to force it to stop, snd_pcm_drop */ + +static bool xrun_warned = false; + +static int alsa_mus_audio_close(int id) +{ + int err = 0; + xrun_warned = false; + if (alsa_trace) mus_print( "%s: %d", c__FUNCTION__, id); + if (handles[id]) + { + err = snd_pcm_drain(handles[id]); + if (err != 0) + mus_print("snd_pcm_drain: %s", snd_strerror(err)); + + err = snd_pcm_close(handles[id]); + if (err != 0) + return(alsa_mus_error(MUS_AUDIO_CANT_CLOSE, + mus_format("snd_pcm_close: %s", + snd_strerror(err)))); + handles[id] = NULL; + } + return(MUS_NO_ERROR); +} + +/* recover from underruns or overruns */ + +static int recover_from_xrun(int id) +{ + int err; + snd_pcm_status_t *status; + snd_pcm_state_t state; + snd_pcm_status_alloca(&status); + err = snd_pcm_status(handles[id], status); + if (err < 0) + { + mus_print("%s: snd_pcm_status: %s", c__FUNCTION__, snd_strerror(err)); + return(MUS_ERROR); + } + state = snd_pcm_status_get_state(status); + if (state == SND_PCM_STATE_XRUN) + { + if (!xrun_warned) + { + xrun_warned = true; + mus_print("[under|over]run detected"); + } + err = snd_pcm_prepare(handles[id]); + if (err < 0) + mus_print("snd_pcm_prepare: %s", snd_strerror(err)); + else return(MUS_NO_ERROR); + } + else mus_print("%s: error, current state is %s", c__FUNCTION__, snd_pcm_state_name(state)); + return(MUS_ERROR); +} + +/* sndlib support for writing a buffer to an output device */ + +static int alsa_mus_audio_write(int id, char *buf, int bytes) +{ + snd_pcm_sframes_t status; + ssize_t frames; + frames = snd_pcm_bytes_to_frames(handles[id], bytes); +#if MUS_DEBUGGING + if ((frames <= 0) || (frames > bytes)) + { + /* pcm->frame_bits not correct? */ + mus_print("audio write %d frames (%d bytes)?", bytes, frames); + abort(); + return(MUS_ERROR); + } +#endif + status = snd_pcm_writei(handles[id], buf, frames); + if ((status == -EAGAIN) || + ((status >= 0) && (status < frames))) + snd_pcm_wait(handles[id], 1000); + else + { + if (status == -EPIPE) + return(recover_from_xrun(id)); + else + { + if (status < 0) + { + mus_print("snd_pcm_writei: %s", snd_strerror(status)); + return(MUS_ERROR); + } + } + } + return(MUS_NO_ERROR); +} + +/* sndlib support for reading a buffer from an input device */ + +static int alsa_mus_audio_read(int id, char *buf, int bytes) +{ + snd_pcm_sframes_t status; + ssize_t frames; + frames = snd_pcm_bytes_to_frames(handles[id], bytes); +#if MUS_DEBUGGING + if ((frames <= 0) || (frames > bytes)) + { + mus_print("audio read %d frames (%d bytes)?", frames, bytes); + abort(); + return(MUS_ERROR); + } +#endif + status = snd_pcm_readi(handles[id], buf, frames); + if ((status == -EAGAIN) || + ((status >= 0) && (status < frames))) + snd_pcm_wait(handles[id], 1000); + else + { + if (status == -EPIPE) + return(recover_from_xrun(id)); + else + { + if (status < 0) + { + mus_print("snd_pcm_readi: %s", snd_strerror(status)); + return(MUS_ERROR); + } + } + } + return(MUS_NO_ERROR); +} + +/* read state of the audio hardware */ + +static int alsa_mus_audio_mixer_read(int ur_dev, int field, int chan, float *val) +{ + int card; + int device; + int alsa_device; + snd_pcm_stream_t alsa_stream; + int f, err; + + if ((!audio_initialized) && + (mus_audio_initialize() != MUS_NO_ERROR)) + return(MUS_ERROR); + + card = MUS_AUDIO_SYSTEM(ur_dev); + device = MUS_AUDIO_DEVICE(ur_dev); + if (alsa_trace) + mus_print( "%s: card=%d, dev=%s<%d>, field=%s<%d>, chan=%d", + c__FUNCTION__, card, mus_audio_device_name(device), device, + mus_audio_device_name(field), field, + chan); + /* for now do not implement mixer interface */ + if (device == MUS_AUDIO_MIXER) + { + val[0] = 0; + return(MUS_NO_ERROR); + } + /* MUS_AUDIO_PORT probes for devices and should not depend on the + * device which was used in the ur_dev argument, we process this + * before trying to map the device to an alsa device */ + + if (field == MUS_AUDIO_PORT) + { + /* under 0.9 we only advertise at most two devices, one for playback + and another one for capture */ + /* int dev; */ + int i = 1; + if (alsa_hw_params[SND_PCM_STREAM_PLAYBACK]) + val[i++] = (float)to_sndlib_device(0, SND_PCM_STREAM_PLAYBACK); + + if (alsa_hw_params[SND_PCM_STREAM_CAPTURE]) + val[i++] = (float)to_sndlib_device(0, SND_PCM_STREAM_CAPTURE); + + val[0]=(float)(i - 1); + return(MUS_NO_ERROR); + } + /* map the mus device to an alsa device and channel */ + if ((err = to_alsa_device(device, &alsa_device, &alsa_stream)) < 0) + { + /* FIXME: snd-dac still probes some non-existing devices, specifically + * MUS_AUDIO_DAC_FILTER, do not report error till that's fixed */ + if (alsa_trace) + { + mus_print("%s: cannot translate device %s<%d> to alsa, field=%s<%d>", + snd_strerror(err), + mus_audio_device_name(device), device, + mus_audio_device_name(field), field); + } + return(MUS_ERROR); + } + if (alsa_trace) mus_print("%s: adev=%d, achan=%d", c__FUNCTION__, alsa_device, alsa_stream); + switch (field) + { + case MUS_AUDIO_AMP: + /* amplitude value */ + val[0] = 1.0; + break; + case MUS_AUDIO_SAMPLES_PER_CHANNEL: + /* samples per channel */ + if (card > 0 || alsa_device > 0) + return(alsa_mus_error(MUS_AUDIO_CANT_READ, NULL)); + else + { + val[0] = (float)alsa_samples_per_channel; + if (chan > 1) + { +#if HAVE_NEW_ALSA + snd_pcm_uframes_t tmp = 0; + snd_pcm_hw_params_get_buffer_size_min(alsa_hw_params[alsa_stream], &tmp); + val[1] = (float)tmp; + snd_pcm_hw_params_get_buffer_size_max(alsa_hw_params[alsa_stream], &tmp); + val[2] = (float)tmp; +#else + val[1] = (float)snd_pcm_hw_params_get_buffer_size_min(alsa_hw_params[alsa_stream]); + val[2] = (float)snd_pcm_hw_params_get_buffer_size_max(alsa_hw_params[alsa_stream]); +#endif + } + } + break; + case MUS_AUDIO_CHANNEL: + /* number of channels */ + if (card > 0 || alsa_device > 0) + return(alsa_mus_error(MUS_AUDIO_CANT_READ, NULL)); + else + { + + if ((alsa_stream == SND_PCM_STREAM_CAPTURE) && + (alsa_capture_device_name) && + (strcmp(alsa_capture_device_name, "default") == 0)) + { + val[0] = 2; + } + else + { + +#if HAVE_NEW_ALSA + unsigned int max_channels = 0; + snd_pcm_hw_params_get_channels_max(alsa_hw_params[alsa_stream], &max_channels); +#else + int max_channels = snd_pcm_hw_params_get_channels_max(alsa_hw_params[alsa_stream]); +#endif + if ((alsa_stream == SND_PCM_STREAM_CAPTURE) && + (max_channels > alsa_max_capture_channels)) + { + /* limit number of capture channels to a reasonable maximum, if the user + specifies a plug pcm as the capture pcm then the returned number of channels + would be MAXINT (or whatever the name is for a really big number). At this + point there is no support in the alsa api to distinguish between default + parameters or those that have been set by a user on purpose, of for querying + the hardware pcm device that is hidden by the plug device to see what is the + real number of channels for the device we are dealing with. We could also try + to flag this as an error to the user and exit the program */ + max_channels = alsa_max_capture_channels; + } + val[0] = (float)max_channels; + if (chan > 1) + { +#if HAVE_NEW_ALSA + unsigned int tmp = 0; + snd_pcm_hw_params_get_channels_min(alsa_hw_params[alsa_stream], &tmp); + val[1] = (float)tmp; +#else + val[1] = (float)snd_pcm_hw_params_get_channels_min(alsa_hw_params[alsa_stream]); +#endif + val[2] = (float)max_channels; + } + } + } + break; + case MUS_AUDIO_SRATE: + /* supported sample rates */ + if (card > 0 || alsa_device > 0) + return(alsa_mus_error(MUS_AUDIO_CANT_READ, NULL)); + else + { + int dir = 0; + val[0] = 44100; + if (chan > 1) + { +#if HAVE_NEW_ALSA + unsigned int tmp; + snd_pcm_hw_params_get_rate_min(alsa_hw_params[alsa_stream], &tmp, &dir); + val[1] = (float)tmp; + snd_pcm_hw_params_get_rate_max(alsa_hw_params[alsa_stream], &tmp, &dir); + val[2] = (float)tmp; +#else + val[1] = (float)snd_pcm_hw_params_get_rate_min(alsa_hw_params[alsa_stream], &dir); + val[2] = (float)snd_pcm_hw_params_get_rate_max(alsa_hw_params[alsa_stream], &dir); +#endif + } + } + break; + case MUS_AUDIO_FORMAT: + /* supported formats */ + if (card > 0 || alsa_device > 0) + return(alsa_mus_error(MUS_AUDIO_CANT_READ, NULL)); + else + { + int format; + snd_pcm_format_mask_t *mask; + snd_pcm_format_mask_alloca(&mask); + snd_pcm_hw_params_get_format_mask(alsa_hw_params[alsa_stream], mask); + for (format = 0, f = 1; format < SND_PCM_FORMAT_LAST; format++) + { + err = snd_pcm_format_mask_test(mask, (snd_pcm_format_t)format); + if (err > 0) + { + if ((f < chan) && + (to_mus_format(format)!=MUS_ERROR)) + val[f++] = (float)to_mus_format(format); + } + } + val[0] = f - 1; + } + break; + case MUS_AUDIO_DIRECTION: + /* direction of this device */ + if (card > 0 || alsa_device > 0) + return(alsa_mus_error(MUS_AUDIO_CANT_READ, NULL)); + else + { + /* 0-->playback, 1-->capture */ + val[0] = (float)alsa_stream; + } + break; + default: + return(alsa_mus_error(MUS_AUDIO_CANT_READ, NULL)); + break; + } + return(MUS_NO_ERROR); +} + +static int alsa_mus_audio_mixer_write(int ur_dev, int field, int chan, float *val) +{ + return(MUS_NO_ERROR); +} + +static void alsa_describe_audio_state_1(void) +{ + int err; + char *str; + size_t len; + snd_config_t *conf; + snd_output_t *buf = NULL; +#if (SND_LIB_MAJOR == 0) || ((SND_LIB_MAJOR == 1) && (SND_LIB_MINOR == 0) && (SND_LIB_SUBMINOR < 8)) + return; /* avoid Alsa bug */ +#endif + err = snd_config_update(); + if (err < 0) + { + mus_print("snd_config_update: %s", snd_strerror(err)); + return; + } + err = snd_output_buffer_open(&buf); + if (err < 0) + mus_print("could not open dump buffer: %s", snd_strerror(err)); + else + { + err = snd_config_search(snd_config, "pcm", &conf); + if (err < 0) + { + mus_print("snd_config_search: could not find at least one pcm: %s", snd_strerror(err)); + return; + } + snd_output_puts(buf, "PCM list:\n"); + snd_config_save(conf, buf); + snd_output_putc(buf, '\0'); + len = snd_output_buffer_string(buf, &str); + if (len > 1) + pprint(str); + snd_output_close(buf); + } +} + +#endif /* HAVE_ALSA */ + + +/* -------------------------------- SUN -------------------------------- */ +/* + * Thanks to Seppo Ingalsuo for several bugfixes. + * record case improved after perusal of Snack 1.6/src/jkAudio_sun.c + */ + +/* apparently input other than 8000 is 16-bit, 8000 is (?) mulaw */ + +#if (defined(MUS_SUN) || defined(MUS_OPENBSD)) && (!(defined(AUDIO_OK))) +#define AUDIO_OK + +#include <sys/types.h> +#include <stropts.h> +#include <sys/filio.h> + +#ifdef SUNOS +#include <sun/audioio.h> +#else +#include <sys/audioio.h> +#endif +#if HAVE_SYS_MIXER_H +#include <sys/mixer.h> +#endif + +int mus_audio_initialize(void) {return(MUS_NO_ERROR);} +int mus_audio_systems(void) {return(1);} +char *mus_audio_system_name(int system) {return("Sun");} + +static int sun_default_outputs = (AUDIO_HEADPHONE | AUDIO_LINE_OUT | AUDIO_SPEAKER); + +void mus_sun_set_outputs(int speakers, int headphones, int line_out) +{ + sun_default_outputs = 0; + if (speakers) sun_default_outputs |= AUDIO_SPEAKER; + if (headphones) sun_default_outputs |= AUDIO_HEADPHONE; + if (line_out) sun_default_outputs |= AUDIO_LINE_OUT; +} + + +#ifdef MUS_OPENBSD + #define DAC_NAME "/dev/sound" +#else + #define DAC_NAME "/dev/audio" +#endif +#define AUDIODEV_ENV "AUDIODEV" + +#define RETURN_ERROR_EXIT(Error_Type, Audio_Line, Ur_Error_Message) \ + do { char *Error_Message; Error_Message = Ur_Error_Message; \ + if (Audio_Line != -1) close(Audio_Line); \ + if (Error_Message) \ + {MUS_STANDARD_ERROR(Error_Type, Error_Message); FREE(Error_Message);} \ + else MUS_STANDARD_ERROR(Error_Type, mus_error_type_to_string(Error_Type)); \ + return(MUS_ERROR); \ + } while (false) + +char *mus_audio_moniker(void) +{ +#ifndef AUDIO_DEV_AMD + struct audio_device ad; +#else + int ad; +#endif + int audio_fd, err; + char *dev_name; + if (getenv(AUDIODEV_ENV) != NULL) + dev_name = getenv(AUDIODEV_ENV); + else dev_name = DAC_NAME; + audio_fd = open(dev_name, O_RDONLY | O_NONBLOCK, 0); + if (audio_fd == -1) + { + audio_fd = open("/dev/audioctl", O_RDONLY | O_NONBLOCK, 0); + if (audio_fd == -1) return("sun probably"); + } + err = ioctl(audio_fd, AUDIO_GETDEV, &ad); + if (err == -1) + { + close(audio_fd); + return("sun?"); + } + mus_audio_close(audio_fd); +#if HAVE_SYS_MIXER_H + if (version_name == NULL) version_name = (char *)CALLOC(PRINT_BUFFER_SIZE, sizeof(char)); +#else + if (version_name == NULL) version_name = (char *)CALLOC(LABEL_BUFFER_SIZE, sizeof(char)); +#endif +#ifndef AUDIO_DEV_AMD + #if HAVE_SYS_MIXER_H + mus_snprintf(version_name, PRINT_BUFFER_SIZE, + "audio: %s (%s), %s %s %s", + ad.name, ad.version, + MIXER_NAME, MIXER_VERSION, MIXER_CONFIGURATION); + #else + mus_snprintf(version_name, LABEL_BUFFER_SIZE, "audio: %s (%s)", ad.name, ad.version); + #endif +#else + switch (ad) + { + case AUDIO_DEV_AMD: mus_snprintf(version_name, LABEL_BUFFER_SIZE, "audio: amd"); break; + #ifdef AUDIO_DEV_CS4231 + case AUDIO_DEV_CS4231: mus_snprintf(version_name, LABEL_BUFFER_SIZE, "audio: cs4231"); break; + #endif + case AUDIO_DEV_SPEAKERBOX: mus_snprintf(version_name, LABEL_BUFFER_SIZE, "audio: speakerbox"); break; + case AUDIO_DEV_CODEC: mus_snprintf(version_name, LABEL_BUFFER_SIZE, "audio: codec"); break; + default: mus_snprintf(version_name, LABEL_BUFFER_SIZE, "audio: unknown"); break; + } +#endif + return(version_name); +} + +static int to_sun_format(int format) +{ + switch (format) + { +#if MUS_LITTLE_ENDIAN + case MUS_LSHORT: /* Solaris on Intel? */ +#else + case MUS_BSHORT: +#endif +#ifdef MUS_OPENBSD + return(AUDIO_ENCODING_PCM16); +#else + return(AUDIO_ENCODING_LINEAR); +#endif + break; + case MUS_BYTE: +#if defined(AUDIO_ENCODING_LINEAR8) + return(AUDIO_ENCODING_LINEAR8); break; +#else + #ifdef MUS_OPENBSD + return(AUDIO_ENCODING_PCM8); + #else + return(AUDIO_ENCODING_LINEAR); + #endif + break; +#endif + case MUS_MULAW: return(AUDIO_ENCODING_ULAW); break; + case MUS_ALAW: return(AUDIO_ENCODING_ALAW); break; + /* there's also AUDIO_ENCODING_DVI */ + } + return(MUS_ERROR); +} + +int mus_audio_open_output(int ur_dev, int srate, int chans, int format, int size) +{ + struct audio_info info; + char *dev_name; + int encode, bits, dev; + int audio_fd, err; + dev = MUS_AUDIO_DEVICE(ur_dev); + encode = to_sun_format(format); + if (encode == MUS_ERROR) + RETURN_ERROR_EXIT(MUS_AUDIO_FORMAT_NOT_AVAILABLE, -1, + mus_format("format %d (%s) not available", + format, + mus_data_format_name(format))); + if (getenv(AUDIODEV_ENV) != NULL) + dev_name = getenv(AUDIODEV_ENV); + else dev_name = DAC_NAME; + if (dev != MUS_AUDIO_DUPLEX_DEFAULT) + audio_fd = open(dev_name, O_WRONLY, 0); + else audio_fd = open(dev_name, O_RDWR, 0); + if (audio_fd == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_OPEN, -1, + mus_format("can't open output %s: %s", + dev_name, strerror(errno))); + AUDIO_INITINFO(&info); + if (dev == MUS_AUDIO_LINE_OUT) + info.play.port = AUDIO_LINE_OUT; + else + { + if (dev == MUS_AUDIO_SPEAKERS) + /* OR may not be available */ + info.play.port = AUDIO_SPEAKER | (sun_default_outputs & AUDIO_HEADPHONE); + else + info.play.port = sun_default_outputs; + } + info.play.sample_rate = srate; + info.play.channels = chans; + bits = 8 * mus_bytes_per_sample(format); + info.play.precision = bits; + info.play.encoding = encode; + err = ioctl(audio_fd, AUDIO_SETINFO, &info); + if (err == -1) + { + ioctl(audio_fd, AUDIO_GETINFO, &info); + + if ((int)info.play.channels != chans) + RETURN_ERROR_EXIT(MUS_AUDIO_CHANNELS_NOT_AVAILABLE, audio_fd, + mus_format("can't set output %s (%s) channels to %d", + mus_audio_device_name(dev), dev_name, chans)); + + if (((int)info.play.precision != bits) || + ((int)info.play.encoding != encode)) + RETURN_ERROR_EXIT(MUS_AUDIO_FORMAT_NOT_AVAILABLE, audio_fd, + mus_format("can't set output %s (%s) format to %d bits, %d encode (%s)", + mus_audio_device_name(dev), dev_name, + bits, encode, + mus_data_format_name(format))); + + if ((int)info.play.sample_rate != srate) + RETURN_ERROR_EXIT(MUS_AUDIO_CHANNELS_NOT_AVAILABLE, audio_fd, + mus_format("can't set output %s (%s) srate to %d", + mus_audio_device_name(dev), dev_name, srate)); + } + /* man audio sez the play.buffer_size field is not currently supported */ + /* but since the default buffer size is 8180! we need ioctl(audio_fd, I_SETSIG, ...) */ + ioctl(audio_fd, I_FLUSH, FLUSHR); + return(audio_fd); +} + +int mus_audio_write(int line, char *buf, int bytes) +{ + if (write(line, buf, bytes) != bytes) + RETURN_ERROR_EXIT(MUS_AUDIO_WRITE_ERROR, -1, + mus_format("write error: %s", strerror(errno))); + return(MUS_NO_ERROR); +} + +int mus_audio_close(int line) +{ + write(line, (char *)NULL, 0); + close(line); + return(MUS_NO_ERROR); +} + +int mus_audio_read(int line, char *buf, int bytes) +{ + int total = 0; + char *curbuf; + /* ioctl(line, AUDIO_DRAIN, NULL) */ + /* this seems to return 8-12 bytes fewer than requested -- perverse! */ + /* should I buffer data internally? */ + + /* apparently we need to loop here ... */ + curbuf = buf; + while (total < bytes) + { + int bytes_available; + ioctl(line, FIONREAD, &bytes_available); + if (bytes_available > 0) + { + int bytes_read; + if ((total + bytes_available) > bytes) bytes_available = bytes - total; + bytes_read = read(line, curbuf, bytes_available); + if (bytes_read > 0) + { + total += bytes_read; + curbuf = (char *)(buf + total); + } + /* else return anyway?? */ + } + } + return(MUS_NO_ERROR); +} + +int mus_audio_open_input(int ur_dev, int srate, int chans, int format, int size) +{ + struct audio_info info; + int indev, encode, bits, dev, audio_fd, err; + char *dev_name; + dev = MUS_AUDIO_DEVICE(ur_dev); + encode = to_sun_format(format); + bits = 8 * mus_bytes_per_sample(format); + if (encode == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_FORMAT_NOT_AVAILABLE, -1, + mus_format("format %d bits, %d encode (%s) not available", + bits, encode, + mus_data_format_name(format))); + if (getenv(AUDIODEV_ENV) != NULL) + dev_name = getenv(AUDIODEV_ENV); + else dev_name = DAC_NAME; + if (dev != MUS_AUDIO_DUPLEX_DEFAULT) + audio_fd = open(dev_name, O_RDONLY, 0); + else audio_fd = open(dev_name, O_RDWR, 0); + if (audio_fd == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_OPEN, -1, + mus_format("can't open input %s: %s", + dev_name, strerror(errno))); + AUDIO_INITINFO(&info); + /* ioctl(audio_fd, AUDIO_GETINFO, &info); */ + info.record.sample_rate = srate; + info.record.channels = chans; + err = ioctl(audio_fd, AUDIO_SETINFO, &info); + if (err == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_OPEN, audio_fd, + mus_format("can't set srate %d and chans %d for input %s (%s)", + srate, chans, + dev_name, + mus_audio_device_name(dev))); + ioctl(audio_fd, AUDIO_GETINFO, &info); + if (info.record.sample_rate != (unsigned int)srate) + mus_print("%s[%d]: sampling rate: %d != %d\n", + __FILE__, __LINE__, + info.record.sample_rate, srate); + if (info.record.channels != (unsigned int)chans) + mus_print("%s[%d]: channels: %d != %d\n", + __FILE__, __LINE__, + info.record.channels, chans); + + info.record.precision = bits; /* was play, changed 10-Jul-03 thanks to Jürgen Keil */ + info.record.encoding = encode; + err = ioctl(audio_fd, AUDIO_SETINFO, &info); + if (err == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_OPEN, audio_fd, + mus_format("can't set bits %d, encode %d (format %s) for input %s (%s)", + bits, encode, mus_data_format_name(format), + dev_name, + mus_audio_device_name(dev))); + ioctl(audio_fd, AUDIO_GETINFO, &info); + + /* these cannot be OR'd */ + if (dev == MUS_AUDIO_LINE_IN) + indev = AUDIO_LINE_IN; + else + { + if (dev == MUS_AUDIO_CD) + indev = AUDIO_INTERNAL_CD_IN; + else indev = AUDIO_MICROPHONE; + } + info.record.port = indev; + err = ioctl(audio_fd, AUDIO_SETINFO, &info); + if (err == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_WRITE, audio_fd, + mus_format("can't set record.port to %d for %s (%s)", + indev, dev_name, + mus_audio_device_name(dev))); + err = ioctl(audio_fd, AUDIO_GETINFO, &info); + if (err == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_READ, audio_fd, + mus_format("can't getinfo on input %s (%s, line: %d)", + dev_name, + mus_audio_device_name(dev), audio_fd)); + else + { + if ((int)info.record.port != indev) + RETURN_ERROR_EXIT(MUS_AUDIO_DEVICE_NOT_AVAILABLE, audio_fd, + mus_format("confusion in record.port: %d != %d (%s: %s)", + (int)info.record.port, indev, + dev_name, + mus_audio_device_name(dev))); + if ((int)info.record.channels != chans) + RETURN_ERROR_EXIT(MUS_AUDIO_CHANNELS_NOT_AVAILABLE, audio_fd, + mus_format("confusion in record.channels: %d != %d (%s: %s)", + (int)info.record.channels, chans, + dev_name, + mus_audio_device_name(dev))); + if (((int)info.record.precision != bits) || + ((int)info.record.encoding != encode)) + RETURN_ERROR_EXIT(MUS_AUDIO_FORMAT_NOT_AVAILABLE, audio_fd, + mus_format("confusion in record.precision|encoding: %d != %d or %d != %d (%s: %s)", + (int)info.record.precision, bits, + (int)info.record.encoding, encode, + dev_name, + mus_audio_device_name(dev))); + } + /* this may be a bad idea */ + info.record.buffer_size = size; + err = ioctl(audio_fd, AUDIO_SETINFO, &info); + if (err == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_WRITE, audio_fd, + mus_format("can't set buffer size to %d on input %s (%s)", + size, + dev_name, + mus_audio_device_name(dev))); + return(audio_fd); +} + +int mus_audio_mixer_read(int ur_dev, int field, int chan, float *val) +{ +#ifndef AUDIO_DEV_AMD + struct audio_device ad; +#else + int ad; +#endif + int audio_fd, err; + struct audio_info info; + int dev, port; + char *dev_name; + dev = MUS_AUDIO_DEVICE(ur_dev); + AUDIO_INITINFO(&info); + if (getenv(AUDIODEV_ENV) != NULL) + dev_name = getenv(AUDIODEV_ENV); + else dev_name = DAC_NAME; + audio_fd = open(dev_name, O_RDONLY | O_NONBLOCK, 0); + if (audio_fd == -1) + { + audio_fd = open("/dev/audioctl", O_RDONLY | O_NONBLOCK); + if (audio_fd == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_READ, -1, + mus_format("can't open %s or /dev/audioctl: %s", + dev_name, strerror(errno))); + else dev_name = "/dev/audioctl"; + } + err = ioctl(audio_fd, AUDIO_GETINFO, &info); + if (err == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_READ, audio_fd, + mus_format("can't get %s (%s) info", + dev_name, + mus_audio_device_name(dev))); + if (field == MUS_AUDIO_PORT) + { + /* info.play|record have a field avail_ports */ + port = 1; + if ((chan > port) && + (info.record.avail_ports & AUDIO_MICROPHONE)) + { + val[port] = MUS_AUDIO_MICROPHONE; + port++; + } + if ((chan > port) && + (info.record.avail_ports & AUDIO_LINE_IN)) + { + val[port] = MUS_AUDIO_LINE_IN; + port++; + } +#ifndef AUDIO_DEV_AMD + if ((chan > port) && + (info.record.avail_ports & AUDIO_INTERNAL_CD_IN)) + { + /* this field lies -- there is no such port available on the Ultra */ + err = ioctl(audio_fd, AUDIO_GETDEV, &ad); + if (err == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_READ, audio_fd, + mus_format("can't get device info on %s (%s)", + dev_name, + mus_audio_device_name(dev))); + if (((ad.version) && (strcmp(ad.version, "a") == 0)) || /* is it a SparcStation? */ + ((ad.name) && (strcmp(ad.name, "SUNW,CS4231") == 0))) + { + val[port] = MUS_AUDIO_CD; + port++; + } + } +#endif + if ((chan > port) && + (info.play.avail_ports & AUDIO_SPEAKER)) + { + val[port] = MUS_AUDIO_SPEAKERS; + port++; + } + if ((chan > port) && + (info.play.avail_ports & AUDIO_LINE_OUT)) + { + val[port] = MUS_AUDIO_LINE_OUT; + port++; + } + if ((chan > port) && + (info.play.avail_ports & AUDIO_HEADPHONE)) + { + val[port] = MUS_AUDIO_DAC_OUT; + port++; + } + val[0] = port - 1; + } + else + { + if (field == MUS_AUDIO_FORMAT) /* this actually depends on the audio device */ + { + err = ioctl(audio_fd, AUDIO_GETDEV, &ad); /* SUNW, dbri|am79c30|CS4231|sbpro|sb16 */ + /* Jurgen Keil's drivers use SUNW,CS4231, but the "real" names are: + "TOOLS,sbpci" SoundBlaster PCI card + "TOOLS,EMU10Kx" SoundBlaster Live! or Audigy + "TOOLS,i810" Intel i8xx audio (and compatible) + "TOOLS,via686" VIA 686 audio + "TOOLS,via8233" VIA 8233 (and compatible) + */ + if (err == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_READ, audio_fd, + mus_format("can't get data format info for %s (%s)", + dev_name, + mus_audio_device_name(dev))); + port = 1; + if ((ad.name) && + (strcmp(ad.name, "SUNW,audio810") == 0)) + { + val[0] = 1; + val[1] = MUS_MULAW; + } + else + { +#ifndef AUDIO_DEV_AMD + if ((ad.name) && + (strcmp(ad.name, "SUNW, am79c30") != 0)) +#else + if (ad == AUDIO_DEV_AMD) +#endif + { + if (chan > port) val[port++] = MUS_BSHORT; + } +#ifndef AUDIO_DEV_AMD + if ((ad.name) && + (strcmp(ad.name, "SUNW, sbpro") != 0) && + (strcmp(ad.name, "SUNW, sb16") != 0)) + { + if (chan > port) val[port++] = MUS_ALAW; + } +#endif + if (chan > port) val[port++] = MUS_MULAW; +#if MUS_LITTLE_ENDIAN + if (chan > port) val[port++] = MUS_LSHORT; +#endif + val[0] = port - 1; + } + } + else + { + switch (dev) + { + case MUS_AUDIO_DEFAULT: + case MUS_AUDIO_DAC_OUT: + case MUS_AUDIO_SPEAKERS: + case MUS_AUDIO_LINE_OUT: + switch (field) + { + case MUS_AUDIO_AMP: + /* who knows how this really works? documentation is incomplete, actual behavior seems to be: */ + if (chan == 0) + { + if (info.play.balance <= (AUDIO_RIGHT_BALANCE / 2)) + val[0] = info.play.gain / (float)(AUDIO_MAX_GAIN); + else val[0] = info.play.gain * (AUDIO_RIGHT_BALANCE - info.play.balance) / (float)(AUDIO_MAX_GAIN * (AUDIO_RIGHT_BALANCE / 2)); + } + else + { + if (info.play.balance >= (AUDIO_RIGHT_BALANCE / 2)) + val[0] = info.play.gain / (float)(AUDIO_MAX_GAIN); + else val[0] = info.play.gain * info.play.balance / (float)(AUDIO_MAX_GAIN * (AUDIO_RIGHT_BALANCE / 2)); + } + break; + case MUS_AUDIO_CHANNEL: + val[0] = 2; + break; + /* appears to depend on data format (mulaw is mono) */ + case MUS_AUDIO_SRATE: + val[0] = (float)info.play.sample_rate; + break; + default: + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_READ, audio_fd, + mus_format("can't read %s field %d (%s)", + mus_audio_device_name(dev), + field, + mus_audio_device_name(field))); + break; + } + break; + case MUS_AUDIO_MICROPHONE: + case MUS_AUDIO_LINE_IN: + case MUS_AUDIO_DUPLEX_DEFAULT: + case MUS_AUDIO_CD: + switch (field) + { + case MUS_AUDIO_AMP: + if (chan == 0) + { + if (info.record.balance <= (AUDIO_RIGHT_BALANCE / 2)) + val[0] = info.record.gain / (float)(AUDIO_MAX_GAIN); + else val[0] = info.record.gain * (AUDIO_RIGHT_BALANCE - info.record.balance) / (float)(AUDIO_MAX_GAIN * (AUDIO_RIGHT_BALANCE / 2)); + } + else + { + if (info.record.balance >= (AUDIO_RIGHT_BALANCE / 2)) + val[0] = info.record.gain / (float)(AUDIO_MAX_GAIN); + else val[0] = info.record.gain * info.record.balance / (float)(AUDIO_MAX_GAIN * (AUDIO_RIGHT_BALANCE / 2)); + } + break; + + case MUS_AUDIO_CHANNEL: + val[0] = 1; + break; + case MUS_AUDIO_SRATE: + val[0] = (float)(info.record.sample_rate); + break; + case MUS_AUDIO_IGAIN: + val[0] = (float)(info.monitor_gain) / (float)AUDIO_MAX_GAIN; + break; + default: + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_READ, audio_fd, + mus_format("can't read %s field %d (%s)", + mus_audio_device_name(dev), + field, + mus_audio_device_name(field))); + break; + } + break; + default: + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_READ, audio_fd, + mus_format("can't read %s field %d (%s)", + mus_audio_device_name(dev), + field, + mus_audio_device_name(field))); + break; + } + } + } + return(mus_audio_close(audio_fd)); +} + +int mus_audio_mixer_write(int ur_dev, int field, int chan, float *val) +{ + struct audio_info info; + int dev, balance, gain; + float ratio, lc, rc; + int audio_fd, err; + char *dev_name; + dev = MUS_AUDIO_DEVICE(ur_dev); + AUDIO_INITINFO(&info); + if (getenv(AUDIODEV_ENV) != NULL) + dev_name = getenv(AUDIODEV_ENV); + else dev_name = DAC_NAME; + audio_fd = open(dev_name, O_RDWR | O_NONBLOCK, 0); + if (audio_fd == -1) + { + audio_fd = open("/dev/audioctl", O_RDWR | O_NONBLOCK); + if (audio_fd == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_WRITE, -1, + mus_format("can't write fields of %s (or /dev/audioctl) (%s): %s", + dev_name, + mus_audio_device_name(dev), + strerror(errno))); + else dev_name = "/dev/audioctl"; + } + err = ioctl(audio_fd, AUDIO_GETINFO, &info); + if (err == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_READ, audio_fd, + mus_format("can't get %s (%s) info", + dev_name, + mus_audio_device_name(dev))); + switch (dev) + { + case MUS_AUDIO_DEFAULT: + case MUS_AUDIO_DAC_OUT: + case MUS_AUDIO_SPEAKERS: + case MUS_AUDIO_LINE_OUT: + switch (field) + { + case MUS_AUDIO_AMP: + balance = info.play.balance; + gain = info.play.gain; + if (balance <= (AUDIO_RIGHT_BALANCE / 2)) + { + lc = gain; + rc = gain * balance / (float)(AUDIO_RIGHT_BALANCE / 2); + } + else + { + lc = gain * (AUDIO_RIGHT_BALANCE - balance) / (float)(AUDIO_RIGHT_BALANCE / 2); + rc = gain; + } + if (chan == 0) + lc = AUDIO_MAX_GAIN * val[0]; + else rc = AUDIO_MAX_GAIN * val[0]; + if ((rc + lc) == 0) + info.play.gain = 0; + else + { + ratio = (float)rc / (float)(rc + lc); + info.play.balance = (unsigned char)(AUDIO_RIGHT_BALANCE * ratio); + if (rc > lc) + info.play.gain = (int)rc; + else info.play.gain = (int)lc; + } + break; + case MUS_AUDIO_CHANNEL: + info.play.channels = (int)val[0]; + break; + /* amd device only mono */ + case MUS_AUDIO_SRATE: + info.play.sample_rate = (int)val[0]; + break; + default: + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_WRITE, audio_fd, + mus_format("can't write %s field %d (%s)", + mus_audio_device_name(dev), + field, + mus_audio_device_name(field))); + break; + } + break; + case MUS_AUDIO_MICROPHONE: + switch (field) + { + case MUS_AUDIO_AMP: + info.record.gain = (int)(AUDIO_MAX_GAIN * val[0]); + info.record.balance = 0; + break; + case MUS_AUDIO_CHANNEL: + info.record.channels = (int)val[0]; + break; + case MUS_AUDIO_SRATE: + info.record.sample_rate = (int)val[0]; + break; + case MUS_AUDIO_IGAIN: + info.monitor_gain = (int)(AUDIO_MAX_GAIN * val[0]); + break; + default: + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_WRITE, audio_fd, + mus_format("can't write %s field %d (%s)", + mus_audio_device_name(dev), + field, + mus_audio_device_name(field))); + break; + } + break; + case MUS_AUDIO_LINE_IN: + case MUS_AUDIO_DUPLEX_DEFAULT: + case MUS_AUDIO_CD: + switch (field) + { + case MUS_AUDIO_AMP: + balance = info.record.balance; + gain = info.record.gain; + lc = gain * (float)(AUDIO_RIGHT_BALANCE - balance) / (float)AUDIO_RIGHT_BALANCE; + rc = gain - lc; + if (chan == 0) + lc = AUDIO_MAX_GAIN * val[0]; + else rc = AUDIO_MAX_GAIN * val[0]; + gain = (int)(rc + lc); + if (gain == 0) + info.record.gain = 0; + else + { + info.record.balance = (unsigned char)(AUDIO_RIGHT_BALANCE * ((float)rc / (float)(rc + lc))); + if (rc > lc) + info.record.gain = (int)rc; + else info.record.gain = (int)lc; + } + break; + case MUS_AUDIO_CHANNEL: + info.record.channels = (int)val[0]; + break; + case MUS_AUDIO_SRATE: + info.record.sample_rate = (int)val[0]; + break; + case MUS_AUDIO_IGAIN: + info.monitor_gain = (int)(AUDIO_MAX_GAIN * val[0]); + break; + default: + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_WRITE, audio_fd, + mus_format("can't write %s field %d (%s)", + mus_audio_device_name(dev), + field, + mus_audio_device_name(field))); + break; + } + break; + default: + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_WRITE, audio_fd, + mus_format("can't write %s field %d (%s)", + mus_audio_device_name(dev), + field, + mus_audio_device_name(field))); + break; + } + err = ioctl(audio_fd, AUDIO_SETINFO, &info); + if (err == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_WRITE, audio_fd, + mus_format("can't write %s field %d (%s) after explicit set", + mus_audio_device_name(dev), + field, + mus_audio_device_name(field))); + return(mus_audio_close(audio_fd)); +} + +/* pause can be implemented with play.pause and record.pause */ + +static char *sun_format_name(int format) +{ + switch (format) + { +#ifdef AUDIO_ENCODING_ALAW + case AUDIO_ENCODING_ALAW: return("alaw"); break; +#endif +#ifdef AUDIO_ENCODING_ULAW + case AUDIO_ENCODING_ULAW: return("ulaw"); break; +#endif +#ifdef AUDIO_ENCODING_DVI + case AUDIO_ENCODING_DVI: return("dvi adpcm"); break; +#endif +#ifdef AUDIO_ENCODING_LINEAR8 + case AUDIO_ENCODING_LINEAR8: return("linear"); break; +#else + #ifdef AUDIO_ENCODING_PCM8 + case AUDIO_ENCODING_PCM8: return("linear"); break; + #endif +#endif +#ifdef AUDIO_ENCODING_LINEAR + case AUDIO_ENCODING_LINEAR: return("linear"); break; +#else + #ifdef AUDIO_ENCODING_PCM16 + case AUDIO_ENCODING_PCM16: return("linear"); break; + #endif +#endif +#ifdef AUDIO_ENCODING_NONE + case AUDIO_ENCODING_NONE: return("not audio"); break; /* dbri interface configured for something else */ +#endif + } + return("unknown"); +} + +static char *sun_in_device_name(int dev) +{ + if (dev == AUDIO_MICROPHONE) return("microphone"); + if (dev == AUDIO_LINE_IN) return("line in"); + if (dev == AUDIO_INTERNAL_CD_IN) return("cd"); + if (dev == (AUDIO_MICROPHONE | AUDIO_LINE_IN)) return("microphone + line in"); + if (dev == (AUDIO_MICROPHONE | AUDIO_LINE_IN | AUDIO_INTERNAL_CD_IN)) return("microphone + line in + cd"); + if (dev == (AUDIO_MICROPHONE | AUDIO_INTERNAL_CD_IN)) return("microphone + cd"); + if (dev == (AUDIO_LINE_IN | AUDIO_INTERNAL_CD_IN)) return("line in + cd"); + return("unknown"); +} + +static char *sun_out_device_name(int dev) +{ + if (dev == AUDIO_SPEAKER) return("speakers"); + if (dev == AUDIO_LINE_OUT) return("line out"); + if (dev == AUDIO_HEADPHONE) return("headphones"); + if (dev == (AUDIO_SPEAKER | AUDIO_LINE_OUT)) return("speakers + line out"); + if (dev == (AUDIO_SPEAKER | AUDIO_LINE_OUT | AUDIO_HEADPHONE)) return("speakers + line out + headphones"); + if (dev == (AUDIO_SPEAKER | AUDIO_HEADPHONE)) return("speakers + headphones"); + if (dev == (AUDIO_LINE_OUT | AUDIO_HEADPHONE)) return("line out + headphones"); + return("unknown"); +} + +static char *sun_vol_name = NULL; +static char *sun_volume_name(float vol, int balance, int chans) +{ + if (sun_vol_name == NULL) sun_vol_name = (char *)CALLOC(LABEL_BUFFER_SIZE, sizeof(char)); + if (chans != 2) + mus_snprintf(sun_vol_name, LABEL_BUFFER_SIZE, "%.3f", vol); + else + { + mus_snprintf(sun_vol_name, LABEL_BUFFER_SIZE, "%.3f %.3f", + vol * (float)(AUDIO_RIGHT_BALANCE - balance) / (float)AUDIO_RIGHT_BALANCE, + vol * (float)balance / (float)AUDIO_RIGHT_BALANCE); + } + return(sun_vol_name); +} + +static void describe_audio_state_1(void) +{ + struct audio_info info; +#ifndef AUDIO_DEV_AMD + struct audio_device ad; +#else + int ad; +#endif + int audio_fd, err; + char *dev_name; + AUDIO_INITINFO(&info); + if (getenv(AUDIODEV_ENV) != NULL) + dev_name = getenv(AUDIODEV_ENV); + else dev_name = DAC_NAME; + audio_fd = open(dev_name, O_RDONLY | O_NONBLOCK, 0); + if (audio_fd == -1) + audio_fd = open("/dev/audioctl", O_RDONLY | O_NONBLOCK, 0); + if (audio_fd == -1) return; + err = ioctl(audio_fd, AUDIO_GETINFO, &info); + if (err == -1) return; + err = ioctl(audio_fd, AUDIO_GETDEV, &ad); + if (err == -1) return; + pprint(mus_audio_moniker()); + pprint("\n"); + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "output: %s\n srate: %d, vol: %s, chans: %d, format %d-bit %s\n", + sun_out_device_name(info.play.port), + info.play.sample_rate, + sun_volume_name((float)info.play.gain / (float)AUDIO_MAX_GAIN, info.play.balance, 2), + info.play.channels, + info.play.precision, + sun_format_name(info.play.encoding)); + pprint(audio_strbuf); + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "input: %s\n srate: %d, vol: %s, chans: %d, format %d-bit %s\n", + sun_in_device_name(info.record.port), + info.record.sample_rate, + sun_volume_name((float)info.record.gain / (float)AUDIO_MAX_GAIN, info.record.balance, 2), + info.record.channels, + info.record.precision, + sun_format_name(info.record.encoding)); + pprint(audio_strbuf); + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "input->output vol: %.3f\n", (float)info.monitor_gain / (float)AUDIO_MAX_GAIN); + pprint(audio_strbuf); + if (info.play.pause) {mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "Playback is paused\n"); pprint(audio_strbuf);} + if (info.record.pause) {mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "Recording is paused\n"); pprint(audio_strbuf);} + if (info.output_muted) {mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "Output is muted\n"); pprint(audio_strbuf);} +#ifdef AUDIO_HWFEATURE_DUPLEX + if (info.hw_features == AUDIO_HWFEATURE_DUPLEX) + {mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "Simultaneous play and record supported\n"); pprint(audio_strbuf);} +#endif +#if HAVE_SYS_MIXER_H + { + int i, num = 0, choice; + #define LARGE_NUMBER 100 + am_sample_rates_t *sr = NULL; + for (choice = 0; choice < 2; choice++) + { + for (num = 4; num < LARGE_NUMBER; num += 2) + { + sr = (am_sample_rates_t *) + malloc(AUDIO_MIXER_SAMP_RATES_STRUCT_SIZE(num)); + sr->num_samp_rates = num; + sr->type = (choice == 0) ? AUDIO_PLAY : AUDIO_RECORD; + err = ioctl(audio_fd, AUDIO_MIXER_GET_SAMPLE_RATES, sr); + if ((int)(sr->num_samp_rates) <= num) break; + free(sr); + sr = NULL; + } + if (sr) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "%s srates:", (choice == 0) ? "play" : "record"); + pprint(audio_strbuf); + if (sr->type == MIXER_SR_LIMITS) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " %d to %d", sr->samp_rates[0], sr->samp_rates[sr->num_samp_rates - 1]); + pprint(audio_strbuf); + } + else + { + for (i = 0; i < (int)(sr->num_samp_rates); i++) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " %d", sr->samp_rates[i]); + pprint(audio_strbuf); + } + } + pprint("\n"); + } + free(sr); + sr = NULL; + } + } +#endif + mus_audio_close(audio_fd); +} + +#endif + + + +/* ------------------------------- WINDOZE ----------------------------------------- */ + +#if defined(MUS_WINDOZE) && (!(defined(__CYGWIN__))) +#define AUDIO_OK + +#include <windows.h> +#include <mmsystem.h> + +#define BUFFER_FILLED 1 +#define BUFFER_EMPTY 2 + +#define OUTPUT_LINE 1 +#define INPUT_LINE 2 + +#define SOUND_UNREADY 0 +#define SOUND_INITIALIZED 1 +#define SOUND_RUNNING 2 + +static int buffer_size = 1024; +static int db_state[2]; +static int sound_state = 0; +static int current_chans = 1; +static int current_datum_size = 2; +static int current_buf = 0; +WAVEHDR wh[2]; +HWAVEOUT fd; +HWAVEIN record_fd; +WAVEHDR rec_wh; +static int rec_state = SOUND_UNREADY; + +static MMRESULT win_in_err = 0, win_out_err = 0; +static char errstr[128], getstr[128]; + +static char *win_err_buf = NULL; +static mus_print_handler_t *old_handler; + +static void win_mus_print(char *msg) +{ + if ((win_in_err == 0) && (win_out_err == 0)) + (*old_handler)(msg); + else + { + if (win_in_err) + waveInGetErrorText(win_in_err, getstr, PRINT_BUFFER_SIZE); + else waveOutGetErrorText(win_out_err, getstr, PRINT_BUFFER_SIZE); + mus_snprintf(errstr, PRINT_BUFFER_SIZE, "%s [%s]", msg, getstr); + (*old_handler)(errstr); + } +} + +static void start_win_print(void) +{ + if (old_handler != win_mus_print) + old_handler = mus_print_set_handler(win_mus_print); +} + +static void end_win_print(void) +{ + if (old_handler == win_mus_print) + mus_print_set_handler(NULL); + else mus_print_set_handler(old_handler); +} + +#define RETURN_ERROR_EXIT(Error_Type, Ur_Error_Message) \ + do { char *Error_Message; Error_Message = Ur_Error_Message; \ + if (Error_Message) \ + {MUS_STANDARD_ERROR(Error_Type, Error_Message); FREE(Error_Message);} \ + else MUS_STANDARD_ERROR(Error_Type, mus_error_type_to_string(Error_Type)); \ + end_win_print(); \ + return(MUS_ERROR); \ + } while (false) + +int mus_audio_systems(void) +{ + /* this number is available -- see below (user mixer number as in linux)->mixerGetNumDevs */ + return(1); +} +char *mus_audio_system_name(int system) {return("Windoze");} + +DWORD CALLBACK next_buffer(HWAVEOUT w, UINT msg, DWORD user_data, DWORD p1, DWORD p2) +{ + if (msg == WOM_DONE) + { + db_state[current_buf] = BUFFER_EMPTY; + } + return(0); +} + +int mus_audio_open_output(int ur_dev, int srate, int chans, int format, int size) +{ + WAVEFORMATEX wf; + int dev; + start_win_print(); + dev = MUS_AUDIO_DEVICE(ur_dev); + wf.nChannels = chans; + current_chans = chans; + wf.wFormatTag = WAVE_FORMAT_PCM; + wf.cbSize = 0; + if (format == MUS_UBYTE) + { + wf.wBitsPerSample = 8; + current_datum_size = 1; + } + else + { + wf.wBitsPerSample = 16; + current_datum_size = 2; + } + wf.nSamplesPerSec = srate; + wf.nBlockAlign = chans * current_datum_size; + wf.nAvgBytesPerSec = wf.nBlockAlign * wf.nSamplesPerSec; + win_out_err = waveOutOpen(&fd, WAVE_MAPPER, &wf, (DWORD)next_buffer, 0, CALLBACK_FUNCTION); /* 0 here = user_data above, other case = WAVE_FORMAT_QUERY */ + if (win_out_err) + RETURN_ERROR_EXIT(MUS_AUDIO_DEVICE_NOT_AVAILABLE, + mus_format("can't open %d (%s)", + dev, + mus_audio_device_name(dev))); + waveOutPause(fd); + if (size <= 0) + buffer_size = 1024; + else buffer_size = size; + wh[0].dwBufferLength = buffer_size * current_datum_size; + wh[0].dwFlags = 0; + wh[0].dwLoops = 0; + wh[0].lpData = (char *)CALLOC(wh[0].dwBufferLength, sizeof(char)); + if ((wh[0].lpData) == 0) + { + waveOutClose(fd); + RETURN_ERROR_EXIT(MUS_AUDIO_SIZE_NOT_AVAILABLE, + mus_format("can't allocate buffer size %d for output %d (%s)", + buffer_size, dev, + mus_audio_device_name(dev))); + } + win_out_err = waveOutPrepareHeader(fd, &(wh[0]), sizeof(WAVEHDR)); + if (win_out_err) + { + FREE(wh[0].lpData); + waveOutClose(fd); + RETURN_ERROR_EXIT(MUS_AUDIO_CONFIGURATION_NOT_AVAILABLE, + mus_format("can't setup output 'header' for %d (%s)", + dev, + mus_audio_device_name(dev))); + } + db_state[0] = BUFFER_EMPTY; + wh[1].dwBufferLength = buffer_size * current_datum_size; + wh[1].dwFlags = 0; + wh[1].dwLoops = 0; + wh[1].lpData = (char *)CALLOC(wh[0].dwBufferLength, sizeof(char)); + if ((wh[1].lpData) == 0) + { + FREE(wh[0].lpData); + waveOutClose(fd); + RETURN_ERROR_EXIT(MUS_AUDIO_SIZE_NOT_AVAILABLE, + mus_format("can't allocate buffer size %d for output %d (%s)", + buffer_size, dev, + mus_audio_device_name(dev))); + } + win_out_err = waveOutPrepareHeader(fd, &(wh[1]), sizeof(WAVEHDR)); + if (win_out_err) + { + waveOutUnprepareHeader(fd, &(wh[0]), sizeof(WAVEHDR)); + FREE(wh[0].lpData); + FREE(wh[1].lpData); + waveOutClose(fd); + RETURN_ERROR_EXIT(MUS_AUDIO_CONFIGURATION_NOT_AVAILABLE, + mus_format("can't setup output 'header' for %d (%s)", + dev, + mus_audio_device_name(dev))); + } + db_state[1] = BUFFER_EMPTY; + sound_state = SOUND_INITIALIZED; + current_buf = 0; + end_win_print(); + return(OUTPUT_LINE); +} + +static MMRESULT fill_buffer(int dbi, char *inbuf, int instart, int bytes) +{ + int i, j; + win_out_err = 0; + if (sound_state == SOUND_UNREADY) return(0); + for (i = instart, j = 0; j < bytes; j++, i++) + wh[dbi].lpData[j] = inbuf[i]; + wh[dbi].dwBufferLength = bytes; + db_state[dbi] = BUFFER_FILLED; + if ((sound_state == SOUND_INITIALIZED) && + (dbi == 1)) + { + sound_state = SOUND_RUNNING; + win_out_err = waveOutRestart(fd); + } + return(win_out_err); +} + +static void wait_for_empty_buffer(int buf) +{ + while (db_state[buf] != BUFFER_EMPTY) + { + Sleep(1); /* in millisecs, so even this may be too much if buf = 256 bytes */ + } +} + +int mus_audio_write(int line, char *buf, int bytes) +{ + int lim, leftover, start; + start_win_print(); + if (line != OUTPUT_LINE) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_WRITE, + mus_format("write error: line %d != %d?", + line, OUTPUT_LINE)); + win_out_err = 0; + leftover = bytes; + start = 0; + if (sound_state == SOUND_UNREADY) + { + end_win_print(); + return(MUS_NO_ERROR); + } + while (leftover > 0) + { + lim = leftover; + if (lim > buffer_size) lim = buffer_size; + leftover -= lim; + wait_for_empty_buffer(current_buf); + win_out_err = fill_buffer(current_buf, buf, start, lim); + if (win_out_err) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_WRITE, + mus_format("write error on %d", + line)); + win_out_err = waveOutWrite(fd, &wh[current_buf], sizeof(WAVEHDR)); + if (win_out_err) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_WRITE, + mus_format("write error on %d", + line)); + start += lim; + current_buf++; + if (current_buf > 1) current_buf = 0; + } + return(MUS_NO_ERROR); +} + +static float unlog(unsigned short val) +{ + /* 1.0 linear is 0xffff, rest is said to be "logarithmic", whatever that really means here */ + if (val == 0) return(0.0); + return((float)val / 65536.0); + /* return(pow(2.0, amp) - 1.0); */ /* doc seems to be bogus */ +} + +#define SRATE_11025_BITS (WAVE_FORMAT_1S16 | WAVE_FORMAT_1S08 | WAVE_FORMAT_1M16 | WAVE_FORMAT_1M08) +#define SRATE_22050_BITS (WAVE_FORMAT_2S16 | WAVE_FORMAT_2S08 | WAVE_FORMAT_2M16 | WAVE_FORMAT_2M08) +#define SRATE_44100_BITS (WAVE_FORMAT_4S16 | WAVE_FORMAT_4S08 | WAVE_FORMAT_4M16 | WAVE_FORMAT_4M08) +#define SHORT_SAMPLE_BITS (WAVE_FORMAT_1S16 | WAVE_FORMAT_1M16 | WAVE_FORMAT_2S16 | WAVE_FORMAT_2M16 | WAVE_FORMAT_4S16 | WAVE_FORMAT_4M16) +#define BYTE_SAMPLE_BITS (WAVE_FORMAT_1S08 | WAVE_FORMAT_1M08 | WAVE_FORMAT_2S08 | WAVE_FORMAT_2M08 | WAVE_FORMAT_4S08 | WAVE_FORMAT_4M08) + +static char *mixer_status_name(int status) +{ + switch (status) + { + case MIXERLINE_LINEF_ACTIVE: return(", (active)"); break; + case MIXERLINE_LINEF_DISCONNECTED: return(", (disconnected)"); break; + case MIXERLINE_LINEF_SOURCE: return(", (source)"); break; + default: return(""); break; + } +} + +static char *mixer_target_name(int type) +{ + switch (type) + { + case MIXERLINE_TARGETTYPE_UNDEFINED: return("undefined"); break; + case MIXERLINE_TARGETTYPE_WAVEOUT: return("output"); break; + case MIXERLINE_TARGETTYPE_WAVEIN: return("input"); break; + case MIXERLINE_TARGETTYPE_MIDIOUT: return("midi output"); break; + case MIXERLINE_TARGETTYPE_MIDIIN: return("midi input"); break; + case MIXERLINE_TARGETTYPE_AUX: return("aux"); break; + default: return(""); break; + } +} + +static char *mixer_component_name(int type) +{ + switch (type) + { + case MIXERLINE_COMPONENTTYPE_DST_UNDEFINED: return("undefined"); break; + case MIXERLINE_COMPONENTTYPE_DST_DIGITAL: return("digital"); break; + case MIXERLINE_COMPONENTTYPE_DST_LINE: return("line"); break; + case MIXERLINE_COMPONENTTYPE_DST_MONITOR: return("monitor"); break; + case MIXERLINE_COMPONENTTYPE_DST_SPEAKERS: return("speakers"); break; + case MIXERLINE_COMPONENTTYPE_DST_HEADPHONES: return("headphones"); break; + case MIXERLINE_COMPONENTTYPE_DST_TELEPHONE: return("telephone"); break; + case MIXERLINE_COMPONENTTYPE_DST_WAVEIN: return("wave in"); break; + case MIXERLINE_COMPONENTTYPE_DST_VOICEIN: return("voice in"); break; + case MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED: return("undefined"); break; + case MIXERLINE_COMPONENTTYPE_SRC_DIGITAL: return("digital"); break; + case MIXERLINE_COMPONENTTYPE_SRC_LINE: return("line"); break; + case MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE: return("mic"); break; + case MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER: return("synth"); break; + case MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC: return("CD"); break; + case MIXERLINE_COMPONENTTYPE_SRC_TELEPHONE: return("telephone"); break; + case MIXERLINE_COMPONENTTYPE_SRC_PCSPEAKER: return("speaker"); break; + case MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT: return("wave out"); break; + case MIXERLINE_COMPONENTTYPE_SRC_AUXILIARY: return("aux"); break; + case MIXERLINE_COMPONENTTYPE_SRC_ANALOG: return("analog"); break; + default: return(""); break; + } +} + +#define MAX_DESCRIBE_CHANS 8 +#define MAX_DESCRIBE_CONTROLS 16 +/* these actually need to be big enough to handle whatever comes along, since we can't read partial states */ +/* or they need to be expanded as necessary */ + +char *mus_audio_moniker(void) {return("MS audio");} /* version number of some sort? */ + +static void describe_audio_state_1(void) +{ + int devs, dev, srate, chans, format, need_comma, maker; + MMRESULT err; + unsigned long val, rate, pitch, version; + WAVEOUTCAPS wocaps; + WAVEINCAPS wicaps; + AUXCAPS wacaps; + HWAVEOUT hd; + WAVEFORMATEX pwfx; +#ifdef MIXERR_BASE + MIXERCAPS wmcaps; + MIXERLINE mixline; + MIXERLINECONTROLS linecontrols; + MIXERCONTROL mc[MAX_DESCRIBE_CONTROLS]; + MIXERCONTROLDETAILS controldetails; + MIXERCONTROLDETAILS_LISTTEXT clist[MAX_DESCRIBE_CHANS]; + MIXERCONTROLDETAILS_BOOLEAN cbool[MAX_DESCRIBE_CHANS]; + MIXERCONTROLDETAILS_UNSIGNED cline[MAX_DESCRIBE_CHANS]; + MIXERCONTROLDETAILS_SIGNED csign[MAX_DESCRIBE_CHANS]; + HMIXER mfd; + int control, controls, dest, dests, source, happy, dest_time, chan, mina, maxa, ctype; +#endif + need_comma = 1; + chans = 1; + devs = waveOutGetNumDevs(); + if (devs > 0) + { + pprint("Output:\n"); + for (dev = 0; dev < devs; dev++) + { + err = waveOutGetDevCaps(dev, &wocaps, sizeof(wocaps)); + if (!err) + { + version = wocaps.vDriverVersion; + maker = wocaps.wMid; + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " %s: version %d.%d\n", + wocaps.szPname, version >> 8, version & 0xff); + pprint(audio_strbuf); + if (wocaps.wChannels == 2) {chans = 2; pprint(" stereo");} else {chans = 1; pprint(" mono");} + if (wocaps.dwFormats & SRATE_11025_BITS) {srate = 11025; if (need_comma) pprint(", "); pprint(" 11025"); need_comma = 1;} + if (wocaps.dwFormats & SRATE_22050_BITS) {srate = 22050; if (need_comma) pprint(", "); pprint(" 22050"); need_comma = 1;} + if (wocaps.dwFormats & SRATE_44100_BITS) {srate = 44100; if (need_comma) pprint(", "); pprint(" 44100"); need_comma = 1;} + if (wocaps.dwFormats & BYTE_SAMPLE_BITS) {format = 8; if (need_comma) pprint(", "); pprint(" unsigned byte"); need_comma = 1;} + if (wocaps.dwFormats & SHORT_SAMPLE_BITS) {format = 16; if (need_comma) pprint(", "); pprint(" little-endian short"); need_comma = 1;} + if (need_comma) pprint("\n"); + need_comma = 0; + pwfx.wFormatTag = WAVE_FORMAT_PCM; + pwfx.nChannels = chans; + pwfx.nSamplesPerSec = srate; + pwfx.nAvgBytesPerSec = srate; + pwfx.nBlockAlign = 1; + pwfx.wBitsPerSample = format; + + err = waveOutOpen(&hd, dev, &pwfx, 0, 0, WAVE_FORMAT_QUERY); + + if (wocaps.dwSupport & WAVECAPS_VOLUME) + { + err = waveOutGetVolume(hd, &val); + if (!err) + { + if (wocaps.dwSupport & WAVECAPS_LRVOLUME) + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, + " vol: %.3f %.3f", + unlog((unsigned short)(val >> 16)), + unlog((unsigned short)(val & 0xffff))); + else mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, + " vol: %.3f", + unlog((unsigned short)(val & 0xffff))); + pprint(audio_strbuf); + need_comma = 1; + } + } + if (!err) + { + /* this is just to get the hd data for subsequent info */ + if (wocaps.dwSupport & WAVECAPS_PLAYBACKRATE) + { + err = waveOutGetPlaybackRate(hd, &rate); + if (!err) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, + "%s playback rate: %.3f", + (need_comma ? ", " : ""), + (float)rate / 65536.0); + pprint(audio_strbuf); + need_comma = 1; + } + } + if (wocaps.dwSupport & WAVECAPS_PITCH) + { + err = waveOutGetPitch(hd, &pitch); + if (!err) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, + "%s pitch: %.3f", + (need_comma ? ", " : ""), + (float)pitch / 65536.0); + pprint(audio_strbuf); + need_comma = 1; + } + } + waveOutClose(hd); + } + if (need_comma) {need_comma = 0; pprint("\n");} + } + } + } + devs = waveInGetNumDevs(); + if (devs > 0) + { + pprint("Input:\n"); + for (dev = 0; dev < devs; dev++) + { + err = waveInGetDevCaps(dev, &wicaps, sizeof(wicaps)); + if (!err) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " %s", wicaps.szPname); + pprint(audio_strbuf); + if ((wicaps.wMid != maker) || (version != wicaps.vDriverVersion)) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, ": version %d.%d\n", + (wicaps.vDriverVersion >> 8), + wicaps.vDriverVersion & 0xff); + pprint(audio_strbuf); + } + else pprint("\n"); + if (wicaps.wChannels == 2) pprint(" stereo"); else pprint(" mono"); + if (wicaps.dwFormats & SRATE_11025_BITS) {pprint(", 11025"); need_comma = 1;} + if (wicaps.dwFormats & SRATE_22050_BITS) {if (need_comma) pprint(", "); pprint(" 22050"); need_comma = 1;} + if (wicaps.dwFormats & SRATE_44100_BITS) {if (need_comma) pprint(", "); pprint(" 44100"); need_comma = 1;} + if (wicaps.dwFormats & BYTE_SAMPLE_BITS) {if (need_comma) pprint(", "); pprint(" unsigned byte"); need_comma = 1;} + if (wicaps.dwFormats & SHORT_SAMPLE_BITS) {if (need_comma) pprint(", "); pprint(" little-endian short");} + pprint("\n"); + } + } + } + devs = auxGetNumDevs(); + if (devs > 0) + { + pprint("Auxiliary:\n"); + for (dev = 0; dev < devs; dev++) + { + err = auxGetDevCaps(dev, &wacaps, sizeof(wacaps)); + if (!err) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " %s", wacaps.szPname); + pprint(audio_strbuf); + if ((wacaps.wMid != maker) || (version != wacaps.vDriverVersion)) + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, ": version %d.%d%s", + (wacaps.vDriverVersion >> 8), wacaps.vDriverVersion & 0xff, + (wacaps.wTechnology & AUXCAPS_CDAUDIO) ? " (CD)" : ""); + else mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "%s\n", (wacaps.wTechnology & AUXCAPS_CDAUDIO) ? " (CD)" : ""); + pprint(audio_strbuf); + if (wacaps.dwSupport & AUXCAPS_VOLUME) + { + err = auxGetVolume(dev, &val); + if (!err) + { + if (wacaps.dwSupport & AUXCAPS_LRVOLUME) + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, + " vol: %.3f %.3f\n", + unlog((unsigned short)(val >> 16)), + unlog((unsigned short)(val & 0xffff))); + else mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, + " vol: %.3f\n", + unlog((unsigned short)(val & 0xffff))); + pprint(audio_strbuf); + } + } + } + } + } +#ifdef MIXERR_BASE + devs = mixerGetNumDevs(); + if (devs > 0) + { + pprint("Mixer:\n"); + for (dev = 0; dev < devs; dev++) + { + err = mixerGetDevCaps(dev, &wmcaps, sizeof(wmcaps)); + if (!err) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " %s", wmcaps.szPname); + pprint(audio_strbuf); + if ((wmcaps.wMid != maker) || (version != wmcaps.vDriverVersion)) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, + ": version %d.%d\n", + (wmcaps.vDriverVersion >> 8), + wmcaps.vDriverVersion & 0xff); + pprint(audio_strbuf); + } + else pprint("\n"); + dests = wmcaps.cDestinations; + + err = mixerOpen(&mfd, dev, 0, 0, CALLBACK_NULL); + if (!err) + { + dest = 0; + source = 0; + dest_time = 1; + happy = 1; + while (happy) + { + if (dest_time) + { + mixline.dwDestination = dest; + mixline.cbStruct = sizeof(MIXERLINE); + err = mixerGetLineInfo((HMIXEROBJ)mfd, &mixline, MIXER_GETLINEINFOF_DESTINATION); + } + else + { + mixline.dwSource = source; + mixline.cbStruct = sizeof(MIXERLINE); + err = mixerGetLineInfo((HMIXEROBJ)mfd, &mixline, MIXER_GETLINEINFOF_SOURCE); + } + if (!err) + { + if ((source == 0) && (!dest_time)) pprint(" Sources:\n"); + if ((dest == 0) && (dest_time)) pprint(" Destinations:\n"); + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " %s: %s (%s), %d chan%s", + mixline.szName, + mixer_component_name(mixline.dwComponentType), + mixer_target_name(mixline.Target.dwType), + + mixline.cChannels, ((mixline.cChannels != 1) ? "s" : "")); + pprint(audio_strbuf); + if (mixline.cConnections > 0) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, ", %d connection%s", + mixline.cConnections, ((mixline.cConnections != 1) ? "s" : "")); + pprint(audio_strbuf); + } + if (dest_time) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "%s\n", mixer_status_name(mixline.fdwLine)); + pprint(audio_strbuf); + } + else pprint("\n"); + if (mixline.cControls > 0) + { + linecontrols.cbStruct = sizeof(MIXERLINECONTROLS); + linecontrols.dwLineID = mixline.dwLineID; + linecontrols.dwControlID = MIXER_GETLINECONTROLSF_ONEBYID; + if (mixline.cControls > MAX_DESCRIBE_CONTROLS) + linecontrols.cControls = MAX_DESCRIBE_CONTROLS; + else linecontrols.cControls = mixline.cControls; + linecontrols.pamxctrl = mc; + linecontrols.cbmxctrl = sizeof(MIXERCONTROL); + err = mixerGetLineControls((HMIXEROBJ)mfd, &linecontrols, MIXER_GETLINECONTROLSF_ALL); + if (!err) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, + " %d control%s:\n", + linecontrols.cControls, + (linecontrols.cControls != 1) ? "s" : ""); + pprint(audio_strbuf); + controls = linecontrols.cControls; + if (controls > MAX_DESCRIBE_CONTROLS) controls = MAX_DESCRIBE_CONTROLS; + for (control = 0; control < controls; control++) + { + + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " %s", mc[control].szName); + pprint(audio_strbuf); + controldetails.cbStruct = sizeof(MIXERCONTROLDETAILS); + controldetails.dwControlID = mc[control].dwControlID; + + ctype = (mc[control].dwControlType); + if ((ctype == MIXERCONTROL_CONTROLTYPE_EQUALIZER) || + (ctype == MIXERCONTROL_CONTROLTYPE_MUX) || + (ctype == MIXERCONTROL_CONTROLTYPE_MIXER) || + (ctype == MIXERCONTROL_CONTROLTYPE_SINGLESELECT) || + (ctype == MIXERCONTROL_CONTROLTYPE_MULTIPLESELECT)) + { + controldetails.cChannels = 1; + controldetails.cMultipleItems = mc[control].cMultipleItems; + controldetails.cbDetails = sizeof(MIXERCONTROLDETAILS_LISTTEXT); + controldetails.paDetails = clist; + err = mixerGetControlDetails((HMIXEROBJ)mfd, &controldetails, MIXER_GETCONTROLDETAILSF_LISTTEXT); + if (!err) + { + for (chan = 0; chan < mixline.cChannels; chan++) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " [%s]", clist[chan].szName); + pprint(audio_strbuf); + } + } + } + if (mixline.cChannels > MAX_DESCRIBE_CHANS) + controldetails.cChannels = MAX_DESCRIBE_CHANS; + else controldetails.cChannels = mixline.cChannels; + controldetails.cMultipleItems = 0; + err = 0; + switch (mc[control].dwControlType & MIXERCONTROL_CT_UNITS_MASK) + { + case MIXERCONTROL_CT_UNITS_BOOLEAN: + controldetails.cbDetails = sizeof(MIXERCONTROLDETAILS_BOOLEAN); + controldetails.paDetails = cbool; + break; + case MIXERCONTROL_CT_UNITS_SIGNED: case MIXERCONTROL_CT_UNITS_DECIBELS: + controldetails.cbDetails = sizeof(MIXERCONTROLDETAILS_SIGNED); + controldetails.paDetails = csign; + break; + case MIXERCONTROL_CT_UNITS_UNSIGNED: case MIXERCONTROL_CT_UNITS_PERCENT: + controldetails.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED); + controldetails.paDetails = cline; + break; + default: err = 1; break; + } + if (err) + pprint("\n"); + else + { + err = mixerGetControlDetails((HMIXEROBJ)mfd, &controldetails, MIXER_GETCONTROLDETAILSF_VALUE); + if (!err) + { + chans = controldetails.cChannels; + if (chans > MAX_DESCRIBE_CHANS) chans = MAX_DESCRIBE_CHANS; + switch (mc[control].dwControlType & MIXERCONTROL_CT_UNITS_MASK) + { + case MIXERCONTROL_CT_UNITS_BOOLEAN: + for (chan = 0; chan < chans; chan++) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " %s", (cbool[chan].fValue) ? " on" : " off"); + pprint(audio_strbuf); + } + break; + case MIXERCONTROL_CT_UNITS_SIGNED: case MIXERCONTROL_CT_UNITS_DECIBELS: + mina = mc[control].Bounds.lMinimum; + maxa = mc[control].Bounds.lMaximum; + if (maxa > mina) + { + for (chan = 0; chan < chans; chan++) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " %.3f", + (float)(csign[chan].lValue - mina) / (float)(maxa - mina)); + pprint(audio_strbuf); + } + } + break; + case MIXERCONTROL_CT_UNITS_UNSIGNED: case MIXERCONTROL_CT_UNITS_PERCENT: + mina = mc[control].Bounds.dwMinimum; + maxa = mc[control].Bounds.dwMaximum; + if (maxa > mina) + { + for (chan = 0; chan < chans; chan++) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " %.3f", + (float)(cline[chan].dwValue - mina) / (float)(maxa - mina)); + pprint(audio_strbuf); + } + } + break; + default: break; + } + pprint("\n"); + } + else pprint("\n"); + } + } + } + } + } + else if (!dest_time) happy = 0; + if (dest_time) dest++; else source++; + if (dest == dests) dest_time = 0; + } + } + mixerClose(mfd); + } + } + } +#endif +} + +int mus_audio_initialize(void) +{ + return(MUS_NO_ERROR); +} + +int mus_audio_close(int line) +{ + int i; + win_out_err = 0; + win_in_err = 0; + if (line == OUTPUT_LINE) + { + /* fill with a few zeros, wait for empty flag */ + if (sound_state != SOUND_UNREADY) + { + wait_for_empty_buffer(current_buf); + for (i = 0; i < 128; i++) wh[current_buf].lpData[i] = 0; + wait_for_empty_buffer(current_buf); + win_out_err = waveOutClose(fd); + i = 0; + while (win_out_err == WAVERR_STILLPLAYING) + { + Sleep(1); + win_out_err = waveOutClose(fd); + i++; + if (i > 1024) break; + } + db_state[0] = BUFFER_EMPTY; + db_state[1] = BUFFER_EMPTY; + sound_state = SOUND_UNREADY; + waveOutUnprepareHeader(fd, &(wh[0]), sizeof(WAVEHDR)); + waveOutUnprepareHeader(fd, &(wh[1]), sizeof(WAVEHDR)); + FREE(wh[0].lpData); + FREE(wh[1].lpData); + if (win_out_err) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_CLOSE, + mus_format("close failed on %d", + line)); + } + } + else + { + if (line == INPUT_LINE) + { + if (rec_state != SOUND_UNREADY) + { + waveInReset(record_fd); + waveInClose(record_fd); + waveInUnprepareHeader(record_fd, &rec_wh, sizeof(WAVEHDR)); + if (rec_wh.lpData) + { + FREE(rec_wh.lpData); + rec_wh.lpData = NULL; + } + rec_state = SOUND_UNREADY; + } + } + else + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_CLOSE, + mus_format("can't close unrecognized line %d", + line)); + } + return(MUS_NO_ERROR); +} + + /* + * waveInAddBuffer sends buffer to get data + * MM_WIM_DATA lParam->WAVEHDR dwBytesRecorded =>how much data actually in buffer + */ + +static int current_record_chans = 0, current_record_datum_size = 0; + +DWORD CALLBACK next_input_buffer(HWAVEIN w, UINT msg, DWORD user_data, DWORD p1, DWORD p2) +{ + if (msg == WIM_DATA) + { + /* grab data */ + /* p1->dwBytesRecorded */ + } + return(0); +} + +int mus_audio_open_input(int ur_dev, int srate, int chans, int format, int size) +{ + WAVEFORMATEX wf; + int dev; + win_in_err = 0; + dev = MUS_AUDIO_DEVICE(ur_dev); + wf.nChannels = chans; + current_record_chans = chans; + + wf.wFormatTag = WAVE_FORMAT_PCM; + wf.cbSize = 0; + if (format == MUS_UBYTE) + { + wf.wBitsPerSample = 8; + current_record_datum_size = 1; + } + else + { + wf.wBitsPerSample = 16; + current_record_datum_size = 2; + } + wf.nSamplesPerSec = srate; + wf.nBlockAlign = chans * current_datum_size; + wf.nAvgBytesPerSec = wf.nBlockAlign * wf.nSamplesPerSec; + + rec_wh.dwBufferLength = size * current_record_datum_size; + rec_wh.dwFlags = 0; + rec_wh.dwLoops = 0; + rec_wh.lpData = (char *)CALLOC(rec_wh.dwBufferLength, sizeof(char)); + if ((rec_wh.lpData) == 0) + RETURN_ERROR_EXIT(MUS_AUDIO_SIZE_NOT_AVAILABLE, + mus_format("can't allocated %d bytes for input buffer of %d (%s)", + size, dev, mus_audio_device_name(dev))); + win_in_err = waveInOpen(&record_fd, WAVE_MAPPER, &wf, (DWORD)next_input_buffer, 0, CALLBACK_FUNCTION); + if (win_in_err) + { + FREE(rec_wh.lpData); + RETURN_ERROR_EXIT(MUS_AUDIO_DEVICE_NOT_AVAILABLE, + mus_format("can't open input device %d (%s)", + dev, mus_audio_device_name(dev))); + } + win_in_err = waveInPrepareHeader(record_fd, &(rec_wh), sizeof(WAVEHDR)); + if (win_in_err) + { + FREE(rec_wh.lpData); + waveInClose(record_fd); + RETURN_ERROR_EXIT(MUS_AUDIO_CONFIGURATION_NOT_AVAILABLE, + mus_format("can't prepare input 'header' for %d (%s)", + dev, mus_audio_device_name(dev))); + } + return(MUS_NO_ERROR); +} + +int mus_audio_read(int line, char *buf, int bytes) +{ + win_in_err = 0; + return(MUS_ERROR); +} + +int mus_audio_mixer_read(int ur_dev, int field, int chan, float *val) +{ + int dev, sys; + unsigned long lval; + MMRESULT err; + sys = MUS_AUDIO_SYSTEM(ur_dev); + dev = MUS_AUDIO_DEVICE(ur_dev); + if (field == MUS_AUDIO_AMP) + { + err = auxGetVolume(sys, &lval); + if (!err) + { + if (chan == 0) + val[0] = unlog((unsigned short)(lval >> 16)); + else val[0] = unlog((unsigned short)(lval & 0xffff)); + return(MUS_NO_ERROR); + } + } + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_READ, + mus_format("can't read device %d (%s) field %s", + dev, mus_audio_device_name(dev), + mus_audio_device_name(field))); +} + +int mus_audio_mixer_write(int ur_dev, int field, int chan, float *val) +{ + int dev, sys; + unsigned long lval; + MMRESULT err; + sys = MUS_AUDIO_SYSTEM(ur_dev); + dev = MUS_AUDIO_DEVICE(ur_dev); + if (field == MUS_AUDIO_AMP) + { + err = auxGetVolume(sys, &lval); + if (!err) + { + if (chan == 0) + lval = (unsigned long)((lval & 0xffff) | (((unsigned short)(val[0] * 65535)) << 16)); + else lval = (unsigned long)((lval & 0xffff0000) | ((unsigned short)(val[0] * 65535))); + err = auxSetVolume(sys, lval); + if (err) return(MUS_NO_ERROR); + } + } + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_WRITE, + mus_format("can't set device %d (%s) field %s", + dev, mus_audio_device_name(dev), + mus_audio_device_name(field))); +} + +#endif + + + +/* ------------------------------- OSX ----------------------------------------- */ + +/* this code based primarily on the CoreAudio headers and portaudio pa_mac_core.c, + * and to a much lesser extent, coreaudio.pdf and the HAL/Daisy examples. + */ + +#ifdef MUS_MAC_OSX +#define AUDIO_OK 1 + +/* +#include <CoreServices/CoreServices.h> +#include <CoreAudio/CoreAudio.h> +*/ +/* ./System/Library/Frameworks/CoreAudio.framework/Headers/CoreAudio.h */ + +static char* osx_error(OSStatus err) +{ + if (err == noErr) return("no error"); + switch (err) + { + case kAudioHardwareNoError: return("no error"); break; + case kAudioHardwareUnspecifiedError: return("unspecified audio hardware error"); break; + case kAudioHardwareNotRunningError: return("audio hardware not running"); break; + case kAudioHardwareUnknownPropertyError: return("unknown property"); break; + case kAudioHardwareBadPropertySizeError: return("bad property"); break; + case kAudioHardwareBadDeviceError: return("bad device"); break; + case kAudioHardwareBadStreamError: return("bad stream"); break; + case kAudioHardwareIllegalOperationError: return("illegal operation"); break; + case kAudioDeviceUnsupportedFormatError: return("unsupported format"); break; + case kAudioDevicePermissionsError: return("device permissions error"); break; + } + return("unknown error"); +} + +char *device_name(AudioDeviceID deviceID, int input_case) +{ + OSStatus err = noErr; + UInt32 size = 0, msize = 0, trans = 0, trans_size = 0; + char *name = NULL, *mfg = NULL, *full_name = NULL; + err = AudioDeviceGetPropertyInfo(deviceID, 0, false, kAudioDevicePropertyDeviceName, &size, NULL); + if (err == noErr) err = AudioDeviceGetPropertyInfo(deviceID, 0, false, kAudioDevicePropertyDeviceManufacturer, &msize, NULL); + if (err == noErr) + { + name = (char *)MALLOC(size + 2); + err = AudioDeviceGetProperty(deviceID, 0, input_case, kAudioDevicePropertyDeviceName, &size, name); + mfg = (char *)MALLOC(msize + 2); + err = AudioDeviceGetProperty(deviceID, 0, input_case, kAudioDevicePropertyDeviceManufacturer, &msize, mfg); + full_name = (char *)MALLOC(size + msize + 4); +#if HAVE_KAUDIODEVICEPROPERTYTRANSPORTTYPE + trans_size = sizeof(UInt32); + err = AudioDeviceGetProperty(deviceID, 0, input_case, kAudioDevicePropertyTransportType, &trans_size, &trans); + if (err != noErr) +#endif + trans = 0; + if (trans == 0) + mus_snprintf(full_name, size + msize + 4, "\n %s: %s", mfg, name); + else mus_snprintf(full_name, size + msize + 4, "\n %s: %s ('%c%c%c%c')", + mfg, name, + (char)((trans >> 24) & 0xff), (char)((trans >> 16) & 0xff), (char)((trans >> 8) & 0xff), (char)(trans & 0xff)); + FREE(name); + FREE(mfg); + } + return(full_name); +} + +static int max_chans_via_stream_configuration(AudioDeviceID device, bool input_case) +{ + /* apparently MOTU 828 has to be different (this code from portaudio) */ + UInt32 size = 0; + Boolean writable; + OSStatus err = noErr; + err = AudioDeviceGetPropertyInfo(device, 0, input_case, kAudioDevicePropertyStreamConfiguration, &size, &writable); + if (err == noErr) + { + AudioBufferList *list; + list = (AudioBufferList *)malloc(size); + err = AudioDeviceGetProperty(device, 0, input_case, kAudioDevicePropertyStreamConfiguration, &size, list); + if (err == noErr) + { + int chans = 0, i; + for (i = 0; i < list->mNumberBuffers; i++) + chans += list->mBuffers[i].mNumberChannels; + free(list); + return(chans); + } + } + return(-1); +} + +static void describe_audio_state_1(void) +{ + OSStatus err = noErr; + UInt32 num_devices = 0, msize = 0, size = 0, buffer_size = 0, mute = 0, alive = 0; + Float32 vol; + int i, j, k; + pid_t hogger = 0; + AudioDeviceID *devices = NULL; + AudioDeviceID device, default_output, default_input; + AudioStreamBasicDescription desc; + AudioStreamBasicDescription *descs = NULL; + int formats = 0, m; + bool input_case = false; + err = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &msize, NULL); + if (err != noErr) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "get property info error: %s\n", osx_error(err)); + pprint(audio_strbuf); + return; + } + num_devices = msize / sizeof(AudioDeviceID); + if (num_devices <= 0) + { + pprint("no audio devices found"); + return; + } + devices = (AudioDeviceID *)MALLOC(msize); + size = sizeof(AudioDeviceID); + err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, &size, &default_input); + if (err != noErr) default_input = 55555555; /* unsigned int -- I want some value that won't happen! */ + size = sizeof(AudioDeviceID); + err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &size, &default_output); + if (err != noErr) default_output = 55555555; + err = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &msize, (void *)devices); + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "found %d audio device%s", + (int)num_devices, (num_devices != 1) ? "s" : ""); + pprint(audio_strbuf); + for (m = 0; m < 2; m++) + { + for (i = 0; i < num_devices; i++) + { + device = devices[i]; + pprint(device_name(device, input_case)); + if (input_case) + { + if (device == default_input) + pprint(" (default input)"); + else pprint(" (input)"); + } + else + { + if (device == default_output) + pprint(" (default output)"); + else pprint(" (output)"); + } + size = sizeof(pid_t); + err = AudioDeviceGetProperty(device, 0, input_case, kAudioDevicePropertyHogMode, &size, &hogger); + if ((err == noErr) && (hogger >= 0)) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " currently owned (exclusively) by process %d", (int)hogger); + pprint(audio_strbuf); + } + size = sizeof(UInt32); + err = AudioDeviceGetProperty(device, 0, input_case, kAudioDevicePropertyDeviceIsAlive, &size, &alive); + if ((err == noErr) && (alive == 0)) + pprint(" disconnected?"); + size = sizeof(UInt32); + err = AudioDeviceGetProperty(device, 0, input_case, kAudioDevicePropertyBufferSize, &size, &buffer_size); + if (err != noErr) buffer_size = 0; + size = sizeof(AudioStreamBasicDescription); + err = AudioDeviceGetProperty(device, 0, input_case, kAudioDevicePropertyStreamFormat, &size, &desc); + if (err == noErr) + { + int config_chans; + unsigned int trans; + trans = (unsigned int)(desc.mFormatID); + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "\n srate: %d, chans: %d", + (int)(desc.mSampleRate), + (int)(desc.mChannelsPerFrame)); + pprint(audio_strbuf); + config_chans = max_chans_via_stream_configuration(device, input_case); + if ((config_chans > 0) && (config_chans != (int)(desc.mChannelsPerFrame))) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " (or %d?)", config_chans); + pprint(audio_strbuf); + } + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, ", bits/sample: %d, format: %c%c%c%c", + (int)(desc.mBitsPerChannel), + (trans >> 24) & 0xff, (trans >> 16) & 0xff, (trans >> 8) & 0xff, trans & 0xff); + pprint(audio_strbuf); + if (buffer_size > 0) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, ", buf: %d", (int)buffer_size); + pprint(audio_strbuf); + } + if ((int)(desc.mFormatFlags) != 0) /* assuming "PCM" here */ + { + int flags; + flags = ((int)(desc.mFormatFlags)); + pprint("\n flags: "); + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "%s%s%s%s%s%s", + (flags & kLinearPCMFormatFlagIsFloat) ? "float " : "", + (flags & kLinearPCMFormatFlagIsBigEndian) ? "big-endian " : "", + (flags & kLinearPCMFormatFlagIsSignedInteger) ? "signed-int " : "", + (flags & kLinearPCMFormatFlagIsPacked) ? "packed " : "", + (flags & kLinearPCMFormatFlagIsAlignedHigh) ? "aligned-high " : "", +#if HAVE_KLINEARPCMFORMATFLAGISNONINTERLEAVED + (flags & kLinearPCMFormatFlagIsNonInterleaved) ? "non-interleaved " : "" +#else + "" +#endif + ); + pprint(audio_strbuf); + } + + if ((int)(desc.mChannelsPerFrame) > 0) + { + pprint("\n vols:"); + for (j = 0; j <= (int)(desc.mChannelsPerFrame); j++) + { + size = sizeof(Float32); + err = AudioDeviceGetProperty(device, j, input_case, kAudioDevicePropertyVolumeScalar, &size, &vol); + if (err == noErr) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " %s%.3f", + (j == 0) ? "master: " : "", + vol); + pprint(audio_strbuf); + } + + if (j > 0) + { + size = sizeof(UInt32); + err = AudioDeviceGetProperty(device, j, input_case, kAudioDevicePropertyMute, &size, &mute); + if ((err == noErr) && (mute == 1)) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " (muted)"); + pprint(audio_strbuf); + } + } + } + } + } + size = 0; + err = AudioDeviceGetPropertyInfo(device, 0, input_case, kAudioDevicePropertyStreamFormats, &size, NULL); + formats = size / sizeof(AudioStreamBasicDescription); + if (formats > 1) + { + descs = (AudioStreamBasicDescription *)CALLOC(formats, sizeof(AudioStreamBasicDescription)); + size = formats * sizeof(AudioStreamBasicDescription); + err = AudioDeviceGetProperty(device, 0, input_case, kAudioDevicePropertyStreamFormats, &size, descs); + if (err == noErr) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "\n This device supports %d formats: ", formats); + pprint(audio_strbuf); + for (k = 0; k < formats; k++) + { + unsigned int trans; + trans = (unsigned int)(descs[k].mFormatID); + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "\n srate: %d, chans: %d, bits/sample: %d, format: %c%c%c%c", + (int)(descs[k].mSampleRate), + (int)(descs[k].mChannelsPerFrame), + (int)(descs[k].mBitsPerChannel), + (trans >> 24) & 0xff, (trans >> 16) & 0xff, (trans >> 8) & 0xff, trans & 0xff); + pprint(audio_strbuf); + } + } + FREE(descs); + } + pprint("\n"); + } + input_case = true; + } + if (devices) FREE(devices); +} + +#define MAX_BUFS 4 +static char **bufs = NULL; +static int in_buf = 0, out_buf = 0; + +static OSStatus writer(AudioDeviceID inDevice, + const AudioTimeStamp *inNow, + const AudioBufferList *InputData, const AudioTimeStamp *InputTime, + AudioBufferList *OutputData, const AudioTimeStamp *OutputTime, + void *appGlobals) +{ + AudioBuffer abuf; + char *aplbuf, *sndbuf; + abuf = OutputData->mBuffers[0]; + aplbuf = (char *)(abuf.mData); + sndbuf = bufs[out_buf]; + memmove((void *)aplbuf, (void *)sndbuf, abuf.mDataByteSize); + out_buf++; + if (out_buf >= MAX_BUFS) out_buf = 0; + return(noErr); +} + +static OSStatus reader(AudioDeviceID inDevice, + const AudioTimeStamp *inNow, + const AudioBufferList *InputData, const AudioTimeStamp *InputTime, + AudioBufferList *OutputData, const AudioTimeStamp *OutputTime, + void *appGlobals) +{ + AudioBuffer abuf; + char *aplbuf, *sndbuf; + abuf = InputData->mBuffers[0]; + aplbuf = (char *)(abuf.mData); + sndbuf = bufs[out_buf]; + memmove((void *)sndbuf, (void *)aplbuf, abuf.mDataByteSize); + out_buf++; + if (out_buf >= MAX_BUFS) out_buf = 0; + return(noErr); +} + + +static AudioDeviceID device = kAudioDeviceUnknown; +static bool writing = false, open_for_input = false; + +int mus_audio_close(int line) +{ + OSStatus err = noErr; + UInt32 sizeof_running; + UInt32 running; + if (open_for_input) + { + in_buf = 0; + err = AudioDeviceStop(device, (AudioDeviceIOProc)reader); + if (err == noErr) + err = AudioDeviceRemoveIOProc(device, (AudioDeviceIOProc)reader); + } + else + { + if ((in_buf > 0) && (!writing)) + { + /* short enough sound that we never got started? */ + err = AudioDeviceAddIOProc(device, (AudioDeviceIOProc)writer, NULL); + if (err == noErr) + err = AudioDeviceStart(device, (AudioDeviceIOProc)writer); /* writer will be called right away */ + if (err == noErr) + writing = true; + } + if (writing) + { + /* send out waiting buffers */ + sizeof_running = sizeof(UInt32); + while (in_buf == out_buf) + { + err = AudioDeviceGetProperty(device, 0, false, kAudioDevicePropertyDeviceIsRunning, &sizeof_running, &running); + } + while (in_buf != out_buf) + { + err = AudioDeviceGetProperty(device, 0, false, kAudioDevicePropertyDeviceIsRunning, &sizeof_running, &running); + } + in_buf = 0; + err = AudioDeviceStop(device, (AudioDeviceIOProc)writer); + if (err == noErr) + err = AudioDeviceRemoveIOProc(device, (AudioDeviceIOProc)writer); + writing = false; + } + } + device = kAudioDeviceUnknown; + if (err == noErr) + return(MUS_NO_ERROR); + return(MUS_ERROR); +} + +typedef enum {CONVERT_NOT, CONVERT_COPY, CONVERT_SKIP, CONVERT_COPY_AND_SKIP, CONVERT_SKIP_N, CONVERT_COPY_AND_SKIP_N} audio_convert_t; +static audio_convert_t conversion_choice = CONVERT_NOT; +static float conversion_multiplier = 1.0; +static int dac_out_chans, dac_out_srate; +static int incoming_out_chans = 1, incoming_out_srate = 44100; +static int fill_point = 0; +static unsigned int bufsize = 0, current_bufsize = 0; + +/* I'm getting bogus buffer sizes from the audio conversion stuff from Apple, + * and I think AudioConvert doesn't handle cases like 4->6 chans correctly + * so, I'll just do the conversions myself -- there is little need here + * for non-integer srate conversion anyway, and the rest is trivial. + */ + +int mus_audio_open_output(int dev, int srate, int chans, int format, int size) +{ + OSStatus err = noErr; + UInt32 sizeof_device, sizeof_format, sizeof_bufsize; + AudioStreamBasicDescription device_desc; + sizeof_device = sizeof(AudioDeviceID); + sizeof_bufsize = sizeof(unsigned int); + err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &sizeof_device, (void *)(&device)); + bufsize = 4096; + if (err == noErr) + err = AudioDeviceGetProperty(device, 0, false, kAudioDevicePropertyBufferSize, &sizeof_bufsize, &bufsize); + if (err != noErr) + { + fprintf(stderr,"open audio output err: %d %s\n", (int)err, osx_error(err)); + return(MUS_ERROR); + } + /* now check for srate/chan mismatches and so on */ + sizeof_format = sizeof(AudioStreamBasicDescription); + err = AudioDeviceGetProperty(device, 0, false, kAudioDevicePropertyStreamFormat, &sizeof_format, &device_desc); + if (err != noErr) + { + fprintf(stderr,"open audio output (get device format) err: %d %s\n", (int)err, osx_error(err)); + return(MUS_ERROR); + } + /* current DAC state: device_desc.mChannelsPerFrame, (int)(device_desc.mSampleRate) */ + /* apparently get stream format can return noErr but chans == 0?? */ + if ((device_desc.mChannelsPerFrame != chans) || + ((int)(device_desc.mSampleRate) != srate)) + { + /* try to match DAC settings to current sound */ + device_desc.mChannelsPerFrame = chans; + device_desc.mSampleRate = srate; + device_desc.mBytesPerPacket = chans * 4; /* assume 1 frame/packet and float32 data */ + device_desc.mBytesPerFrame = chans * 4; + sizeof_format = sizeof(AudioStreamBasicDescription); + err = AudioDeviceSetProperty(device, 0, 0, false, kAudioDevicePropertyStreamFormat, sizeof_format, &device_desc); + + /* this error is bogus in some cases -- other audio systems just ignore it, + * but in my case (a standard MacIntel with no special audio hardware), if I leave + * this block out, the sound is played back at the wrong rate, and the volume + * of outa is set to 0.0?? + */ + + if (err != noErr) + { + /* it must have failed for some reason -- look for closest match available */ + /* if srate = 22050 try 44100, if chans = 1 try 2 */ + /* the "get closest match" business appears to be completely bogus... */ + device_desc.mChannelsPerFrame = (chans == 1) ? 2 : chans; + device_desc.mSampleRate = (srate == 22050) ? 44100 : srate; + device_desc.mBytesPerPacket = device_desc.mChannelsPerFrame * 4; /* assume 1 frame/packet and float32 data */ + device_desc.mBytesPerFrame = device_desc.mChannelsPerFrame * 4; + sizeof_format = sizeof(AudioStreamBasicDescription); + err = AudioDeviceSetProperty(device, 0, 0, false, kAudioDevicePropertyStreamFormat, sizeof_format, &device_desc); + if (err != noErr) + { + sizeof_format = sizeof(AudioStreamBasicDescription); + err = AudioDeviceGetProperty(device, 0, false, kAudioDevicePropertyStreamFormatMatch, &sizeof_format, &device_desc); + if (err == noErr) + { + /* match suggests: device_desc.mChannelsPerFrame, (int)(device_desc.mSampleRate) */ + /* try to set DAC to reflect that match */ + /* a bug here in emagic 2|6 -- we can get 6 channel match, but then can't set it?? */ + sizeof_format = sizeof(AudioStreamBasicDescription); + err = AudioDeviceSetProperty(device, 0, 0, false, kAudioDevicePropertyStreamFormat, sizeof_format, &device_desc); + if (err != noErr) + { + /* no luck -- get current DAC settings at least */ + sizeof_format = sizeof(AudioStreamBasicDescription); + AudioDeviceGetProperty(device, 0, false, kAudioDevicePropertyStreamFormat, &sizeof_format, &device_desc); + } + } + } + else + { + /* nothing matches? -- get current DAC settings */ + sizeof_format = sizeof(AudioStreamBasicDescription); + AudioDeviceGetProperty(device, 0, false, kAudioDevicePropertyStreamFormat, &sizeof_format, &device_desc); + } + } + } + /* now DAC claims it is ready for device_desc.mChannelsPerFrame, (int)(device_desc.mSampleRate) */ + dac_out_chans = device_desc.mChannelsPerFrame; /* use better variable names */ + dac_out_srate = (int)(device_desc.mSampleRate); + open_for_input = false; + if ((bufs == NULL) || (bufsize > current_bufsize)) + { + int i; + if (bufs) + { + for (i = 0; i < MAX_BUFS; i++) FREE(bufs[i]); + FREE(bufs); + } + bufs = (char **)CALLOC(MAX_BUFS, sizeof(char *)); + for (i = 0; i < MAX_BUFS; i++) + bufs[i] = (char *)CALLOC(bufsize, sizeof(char)); + current_bufsize = bufsize; + } + in_buf = 0; + out_buf = 0; + fill_point = 0; + incoming_out_srate = srate; + incoming_out_chans = chans; + if (incoming_out_chans == dac_out_chans) + { + if (incoming_out_srate == dac_out_srate) + { + conversion_choice = CONVERT_NOT; + conversion_multiplier = 1.0; + } + else + { + /* here we don't get very fancy -- assume dac/2=in */ + conversion_choice = CONVERT_COPY; + conversion_multiplier = 2.0; + } + } + else + { + if (incoming_out_srate == dac_out_srate) + { + if ((dac_out_chans == 2) && (incoming_out_chans == 1)) /* the usual case */ + { + conversion_choice = CONVERT_SKIP; + conversion_multiplier = 2.0; + } + else + { + conversion_choice = CONVERT_SKIP_N; + conversion_multiplier = ((float)dac_out_chans / (float)incoming_out_chans); + } + } + else + { + if ((dac_out_chans == 2) && (incoming_out_chans == 1)) /* the usual case */ + { + conversion_choice = CONVERT_COPY_AND_SKIP; + conversion_multiplier = 4.0; + } + else + { + conversion_choice = CONVERT_COPY_AND_SKIP_N; + conversion_multiplier = ((float)dac_out_chans / (float)incoming_out_chans) * 2; + } + } + } + return(MUS_NO_ERROR); +} + +static void convert_incoming(char *to_buf, int fill_point, int lim, char *buf) +{ + int i, j, k, jc, kc, ic; + switch (conversion_choice) + { + case CONVERT_NOT: + /* no conversion needed */ + for (i = 0; i < lim; i++) + to_buf[i + fill_point] = buf[i]; + break; + case CONVERT_COPY: + /* copy sample to mimic lower srate */ + for (i = 0, j = fill_point; i < lim; i += 8, j += 16) + for (k = 0; k < 8; k++) + { + to_buf[j + k] = buf[i + k]; + to_buf[j + k + 8] = buf[i + k]; + } + break; + case CONVERT_SKIP: + /* skip sample for empty chan */ + for (i = 0, j = fill_point; i < lim; i += 4, j += 8) + for (k = 0; k < 4; k++) + { + to_buf[j + k] = buf[i + k]; + to_buf[j + k + 4] = 0; + } + break; + case CONVERT_SKIP_N: + /* copy incoming_out_chans then skip up to dac_out_chans */ + jc = dac_out_chans * 4; + ic = incoming_out_chans * 4; + for (i = 0, j = fill_point; i < lim; i += ic, j += jc) + { + for (k = 0; k < ic; k++) to_buf[j + k] = buf[i + k]; + for (k = ic; k < jc; k++) to_buf[j + k] = 0; + } + break; + case CONVERT_COPY_AND_SKIP: + for (i = 0, j = fill_point; i < lim; i += 4, j += 16) + for (k = 0; k < 4; k++) + { + to_buf[j + k] = buf[i + k]; + to_buf[j + k + 4] = 0; + to_buf[j + k + 8] = buf[i + k]; + to_buf[j + k + 12] = 0; + } + break; + case CONVERT_COPY_AND_SKIP_N: + /* copy for each active chan, skip rest */ + jc = dac_out_chans * 8; + ic = incoming_out_chans * 4; + kc = dac_out_chans * 4; + for (i = 0, j = fill_point; i < lim; i += ic, j += jc) + { + for (k = 0; k < ic; k++) + { + to_buf[j + k] = buf[i + k]; + to_buf[j + k + kc] = buf[i + k]; + } + for (k = ic; k < kc; k++) + { + to_buf[j + k] = 0; + to_buf[j + k + kc] = 0; + } + } + break; + } +} + +int mus_audio_write(int line, char *buf, int bytes) +{ + OSStatus err = noErr; + int lim, bp, out_bytes; + UInt32 sizeof_running; + UInt32 running; + char *to_buf; + to_buf = bufs[in_buf]; + out_bytes = (int)(bytes * conversion_multiplier); + if ((fill_point + out_bytes) > bufsize) + out_bytes = bufsize - fill_point; + lim = (int)(out_bytes / conversion_multiplier); + if (!writing) + { + convert_incoming(to_buf, fill_point, lim, buf); + fill_point += out_bytes; + if (fill_point >= bufsize) + { + in_buf++; + fill_point = 0; + if (in_buf == MAX_BUFS) + { + in_buf = 0; + err = AudioDeviceAddIOProc(device, (AudioDeviceIOProc)writer, NULL); + if (err == noErr) + err = AudioDeviceStart(device, (AudioDeviceIOProc)writer); /* writer will be called right away */ + if (err == noErr) + { + writing = true; + return(MUS_NO_ERROR); + } + else return(MUS_ERROR); + } + } + return(MUS_NO_ERROR); + } + if ((fill_point == 0) && (in_buf == out_buf)) + { + bp = out_buf; + sizeof_running = sizeof(UInt32); + while (bp == out_buf) + { + /* i.e. just kill time without hanging */ + err = AudioDeviceGetProperty(device, 0, false, kAudioDevicePropertyDeviceIsRunning, &sizeof_running, &running); + /* usleep(10); */ + } + } + to_buf = bufs[in_buf]; + if (fill_point == 0) memset((void *)to_buf, 0, bufsize); + convert_incoming(to_buf, fill_point, lim, buf); + fill_point += out_bytes; + if (fill_point >= bufsize) + { + in_buf++; + fill_point = 0; + if (in_buf >= MAX_BUFS) in_buf = 0; + } + return(MUS_NO_ERROR); +} + +int mus_audio_open_input(int dev, int srate, int chans, int format, int size) +{ + OSStatus err = noErr; + UInt32 sizeof_device; + UInt32 sizeof_bufsize; + sizeof_device = sizeof(AudioDeviceID); + sizeof_bufsize = sizeof(unsigned int); + err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, &sizeof_device, (void *)(&device)); + bufsize = 4096; + if (err == noErr) + err = AudioDeviceGetProperty(device, 0, true, kAudioDevicePropertyBufferSize, &sizeof_bufsize, &bufsize); + if (err != noErr) + { + fprintf(stderr,"open audio input err: %d %s\n", (int)err, osx_error(err)); + return(MUS_ERROR); + } + open_for_input = true; + /* assume for now that recorder (higher level) will enforce match */ + if ((bufs == NULL) || (bufsize > current_bufsize)) + { + int i; + if (bufs) + { + for (i = 0; i < MAX_BUFS; i++) FREE(bufs[i]); + FREE(bufs); + } + bufs = (char **)CALLOC(MAX_BUFS, sizeof(char *)); + for (i = 0; i < MAX_BUFS; i++) + bufs[i] = (char *)CALLOC(bufsize, sizeof(char)); + current_bufsize = bufsize; + } + in_buf = 0; + out_buf = 0; + fill_point = 0; + incoming_out_srate = srate; + incoming_out_chans = chans; + err = AudioDeviceAddIOProc(device, (AudioDeviceIOProc)reader, NULL); + if (err == noErr) + err = AudioDeviceStart(device, (AudioDeviceIOProc)reader); + if (err != noErr) + { + fprintf(stderr,"add open audio input err: %d %s\n", (int)err, osx_error(err)); + return(MUS_ERROR); + } + return(MUS_NO_ERROR); +} + +int mus_audio_read(int line, char *buf, int bytes) +{ + OSStatus err = noErr; + int bp; + UInt32 sizeof_running; + UInt32 running; + char *to_buf; + if (in_buf == out_buf) + { + bp = out_buf; + sizeof_running = sizeof(UInt32); + while (bp == out_buf) + { + err = AudioDeviceGetProperty(device, 0, true, kAudioDevicePropertyDeviceIsRunning, &sizeof_running, &running); + if (err != noErr) + fprintf(stderr,"wait err: %s ", osx_error(err)); + } + } + to_buf = bufs[in_buf]; + if (bytes <= bufsize) + memmove((void *)buf, (void *)to_buf, bytes); + else memmove((void *)buf, (void *)to_buf, bufsize); + in_buf++; + if (in_buf >= MAX_BUFS) in_buf = 0; + return(MUS_ERROR); +} + +static int max_chans(AudioDeviceID device, int input) +{ + int maxc = 0, formats, k, config_chans; + UInt32 size; + OSStatus err; + AudioStreamBasicDescription desc; + AudioStreamBasicDescription *descs; + size = sizeof(AudioStreamBasicDescription); + err = AudioDeviceGetProperty(device, 0, input, kAudioDevicePropertyStreamFormat, &size, &desc); + if (err == noErr) + { + maxc = (int)(desc.mChannelsPerFrame); + size = 0; + err = AudioDeviceGetPropertyInfo(device, 0, input, kAudioDevicePropertyStreamFormats, &size, NULL); + formats = size / sizeof(AudioStreamBasicDescription); + if (formats > 1) + { + descs = (AudioStreamBasicDescription *)CALLOC(formats, sizeof(AudioStreamBasicDescription)); + size = formats * sizeof(AudioStreamBasicDescription); + err = AudioDeviceGetProperty(device, 0, input, kAudioDevicePropertyStreamFormats, &size, descs); + if (err == noErr) + for (k = 0; k < formats; k++) + if ((int)(descs[k].mChannelsPerFrame) > maxc) maxc = (int)(descs[k].mChannelsPerFrame); + FREE(descs); + } + } + else fprintf(stderr, "read chans hit: %s\n", osx_error(err)); + config_chans = max_chans_via_stream_configuration(device, input); + if (config_chans > maxc) return(config_chans); + return(maxc); +} + +int mus_audio_mixer_read(int dev1, int field, int chan, float *val) +{ + AudioDeviceID dev = kAudioDeviceUnknown; + OSStatus err = noErr; + UInt32 size; + Float32 amp; + int i, curdev; + bool in_case = false; + switch (field) + { + case MUS_AUDIO_AMP: + size = sizeof(AudioDeviceID); + err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &size, &dev); + size = sizeof(Float32); + err = AudioDeviceGetProperty(dev, chan + 1, false, kAudioDevicePropertyVolumeScalar, &size, &); + if (err == noErr) + val[0] = (Float)amp; + else val[0] = 0.0; + break; + case MUS_AUDIO_CHANNEL: + curdev = MUS_AUDIO_DEVICE(dev1); + size = sizeof(AudioDeviceID); + in_case = ((curdev == MUS_AUDIO_MICROPHONE) || (curdev == MUS_AUDIO_LINE_IN)); + if (in_case) + err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, &size, &dev); + else err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &size, &dev); + if (err != noErr) fprintf(stderr, "get default: %s\n", osx_error(err)); + val[0] = max_chans(dev, in_case); + break; + case MUS_AUDIO_SRATE: + val[0] = 44100; + break; + case MUS_AUDIO_FORMAT: + /* never actually used except perhaps play.scm */ + val[0] = 1.0; +#if MUS_LITTLE_ENDIAN + val[1] = MUS_LFLOAT; +#else + val[1] = MUS_BFLOAT; +#endif + break; + case MUS_AUDIO_PORT: + i = 0; + if (1 < chan) val[1] = MUS_AUDIO_MICROPHONE; + if (2 < chan) val[2] = MUS_AUDIO_DAC_OUT; + val[0] = 2; + break; + case MUS_AUDIO_SAMPLES_PER_CHANNEL: + /* bufsize / 16: mulaw 22050 mono -> float 44100 stereo => 16:1 expansion */ + { + int bufsize = 4096; + UInt32 sizeof_bufsize; + sizeof_bufsize = sizeof(unsigned int); + curdev = MUS_AUDIO_DEVICE(dev1); + size = sizeof(AudioDeviceID); + in_case = ((curdev == MUS_AUDIO_MICROPHONE) || (curdev == MUS_AUDIO_LINE_IN)); + if (in_case) + err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, &size, &dev); + else err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &size, &dev); + if (err != noErr) + fprintf(stderr, "get samps/chan: %s\n", osx_error(err)); + else + { + err = AudioDeviceGetProperty(dev, 0, true, kAudioDevicePropertyBufferSize, &sizeof_bufsize, &bufsize); + if (err == noErr) val[0] = (float)(bufsize / 16); + } + } + break; + default: + return(MUS_ERROR); + break; + } + return(MUS_NO_ERROR); +} + +int mus_audio_mixer_write(int dev1, int field, int chan, float *val) +{ + AudioDeviceID dev = kAudioDeviceUnknown; + OSStatus err = noErr; + Boolean writable; + UInt32 size; + Float32 amp; + switch (field) + { + case MUS_AUDIO_AMP: + size = sizeof(AudioDeviceID); + err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &size, (void *)(&dev)); + err = AudioDeviceGetPropertyInfo(dev, chan + 1, false, kAudioDevicePropertyVolumeScalar, NULL, &writable); /* "false" -> output */ + amp = (Float32)(val[0]); + if ((err == kAudioHardwareNoError) && (writable)) + err = AudioDeviceSetProperty(dev, NULL, chan + 1, false, kAudioDevicePropertyVolumeScalar, sizeof(Float32), &); + break; + default: + return(MUS_ERROR); + break; + } + return(MUS_NO_ERROR); +} + +int mus_audio_initialize(void) {return(MUS_NO_ERROR);} +int mus_audio_systems(void) {return(1);} +char *mus_audio_system_name(int system) {return("Mac OSX");} + +char *mus_audio_moniker(void) {return("Mac OSX audio");} +#endif + + + +/* -------------------------------- ESD -------------------------------- */ + +/* ESD audio IO for Linux * + * Nick Bailey <nick@bailey-family.org.uk> * + * also n.bailey@elec.gla.ac.uk */ + +/* ESD is pretty well undocumented, and I've not looked at snd before, * + * but here goes... * + * * + * History: * + * 14th Nov 2000: copied SUN drivers here and started to hack. NJB. * + * */ + +#ifdef MUS_ESD +#define AUDIO_OK + +#include <esd.h> + +static int esd_play_sock = -1; +static int esd_rec_sock = -1; +static char esd_name[] = "Enlightened Sound Daemon"; +static int swap_end, resign; /* How to handle samples on write */ + +int mus_audio_initialize(void) {return(MUS_NO_ERROR);} +int mus_audio_systems(void) {return(1);} +char *mus_audio_system_name(int system) {return esd_name;} +static char our_name[LABEL_BUFFER_SIZE]; +char *mus_audio_moniker(void) +{ +#ifdef MUS_ESD_VERSION + #ifdef MUS_AUDIOFILE_VERSION + mus_snprintf(our_name, LABEL_BUFFER_SIZE, "%s: %s (Audiofile %s)", esd_name, MUS_ESD_VERSION, MUS_AUDIOFILE_VERSION); + #else + mus_snprintf(our_name, LABEL_BUFFER_SIZE, "%s: %s", esd_name, MUS_ESD_VERSION); + #endif + return(our_name); +#else + return(esd_name); +#endif +} + +int mus_audio_api(void) {return(0);} + +#define RETURN_ERROR_EXIT(Error_Type, Audio_Line, Ur_Error_Message) \ + do { char *Error_Message; Error_Message = Ur_Error_Message; \ + if (esd_play_sock != -1) close(esd_play_sock); \ + if (esd_rec_sock != -1) close(esd_rec_sock); \ + if (Error_Message) \ + {MUS_STANDARD_ERROR(Error_Type, Error_Message); FREE(Error_Message);} \ + else MUS_STANDARD_ERROR(Error_Type, mus_error_type_to_string(Error_Type)); \ + return(MUS_ERROR); \ + } while (false) + +/* No we're laughing. snd think's its talking to a real piece of hardware + so it'll only try to open it once. We can just use the socket numbers */ + +/* REVOLTING HACK! to_esd_format is called from mus_audio_open, and + /as a side effect/, sets a flag to tell the write routine whether + or not to change the endienness of the audio sample data (afaik, + esd can't do this for us). Same goes for signed-ness. + If it gets called from elsewhere, it could be nasty. */ + +static int to_esd_format(int snd_format) +{ + /* Try this on the Macs: it may be esd expects Bigendian on those */ + switch (snd_format) { /* Only some are supported */ + case MUS_UBYTE: swap_end = 0; resign = 0; return ESD_BITS8; + case MUS_LSHORT: swap_end = 0; resign = 0; return ESD_BITS16; + case MUS_BSHORT: swap_end = 1; resign = 0; return ESD_BITS16; + case MUS_ULSHORT: swap_end = 0; resign = 1; return ESD_BITS16; + case MUS_UBSHORT: swap_end = 1; resign = 1; return ESD_BITS16; + } + return MUS_ERROR; +} + +int mus_audio_open_output(int ur_dev, int srate, int chans, int format, int size) +{ + int esd_prop = ESD_STREAM; + int esd_format; + + if ((esd_format = to_esd_format(format)) == MUS_ERROR) + RETURN_ERROR_EXIT(MUS_AUDIO_FORMAT_NOT_AVAILABLE, audio_out, + mus_format("Can't handle format %d (%s) through esd", + format, mus_data_format_name(format))); + else + esd_prop |= esd_format; + + if (chans < 1 || chans > 2) + RETURN_ERROR_EXIT(MUS_AUDIO_CHANNELS_NOT_AVAILABLE, audio_out, + mus_format("Can't handle format %d channels through esd", + format)); + else + esd_prop |= chans == 1 ? ESD_MONO : ESD_STEREO; + + esd_play_sock = esd_play_stream(esd_prop, srate, + NULL, "snd playback stream"); + + if (esd_play_sock == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_DEVICE_NOT_AVAILABLE, audio_out, + mus_format("Sonorus device %d (%s) not available", + ur_dev, mus_audio_device_name(ur_dev))); + else + return esd_play_sock; +} + +int mus_audio_write(int line, char *buf, int bytes) +{ + int written; + char *to = buf; + + /* Esd can't do endianness or signed/unsigned conversion, + so it's our problem. We won't screw up the callers data */ + + if (swap_end) { + char *from = buf; + char *p; + int samps = bytes/2; + p = to = (char *)alloca(bytes); + while (samps--) { + *p++ = *(from+1); + *p++ = *(from); + from += 2; + } + } + + /* Need to do something about sign correction here */ + + do { + written = write(line, to, bytes); + if (written > 0) { + bytes -= written; + to += written; + } + else + RETURN_ERROR_EXIT(MUS_AUDIO_WRITE_ERROR, -1, + mus_format("write error: %s", strerror(errno))); + } while (bytes > 0); + return MUS_NO_ERROR; +} + +int mus_audio_close(int line) +{ + esd_close(line); + if (esd_play_sock == line) esd_play_sock = -1; + else if (esd_rec_sock == line) esd_rec_sock = -1; + return MUS_NO_ERROR; +} + +int mus_audio_read(int line, char *buf, int bytes) +{ + int bytes_read; + + do { + bytes_read = read(line, buf, bytes); + if (bytes_read > 0) { /* 0 -> EOF; we'll regard that as an error */ + bytes -= bytes_read; + buf += bytes_read; + } else + RETURN_ERROR_EXIT(MUS_AUDIO_WRITE_ERROR, -1, + mus_format("read error: %s", strerror(errno))); + } while (bytes > 0); + return MUS_NO_ERROR; +} + +int mus_audio_open_input(int ur_dev, int srate, int chans, int format, int size) +{ + int esd_prop = ESD_STREAM; + int esd_format; + + if ((esd_format = to_esd_format(format)) == MUS_ERROR) + RETURN_ERROR_EXIT(MUS_AUDIO_FORMAT_NOT_AVAILABLE, audio_out, + mus_format("Can't handle format %d (%s) through esd", + format, mus_data_format_name(format))); + else + esd_prop |= esd_format; + + if (chans < 1 || chans > 2) + RETURN_ERROR_EXIT(MUS_AUDIO_CHANNELS_NOT_AVAILABLE, audio_out, + mus_format("Can't handle format %d channels through esd", + chans)); + else + esd_prop |= chans == 1 ? ESD_MONO : ESD_STEREO; + + esd_rec_sock = esd_play_stream(esd_prop, srate, + NULL, "snd record stream"); + + if (esd_rec_sock == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_DEVICE_NOT_AVAILABLE, audio_out, + mus_format("Device %d (%s) not available", + ur_dev, mus_audio_device_name(ur_dev))); + else + return esd_rec_sock; +} + +int mus_audio_mixer_read(int ur_dev, int field, int chan, float *val) +{ + /* Not really sure what to do here. Mixer is at the other end of the + socket. Needs work. NJB */ + + /* int card = MUS_AUDIO_SYSTEM(ur_dev); */ + int device = MUS_AUDIO_DEVICE(ur_dev); + + if (device == MUS_AUDIO_MIXER) { + val[0] = 0.0; + return MUS_NO_ERROR; + } + + if (field == MUS_AUDIO_PORT) { + val[0] = 1.0; + return MUS_NO_ERROR; + } + + switch (field) { + case MUS_AUDIO_AMP: + /* amplitude value */ + val[0] = 1.0; + break; + case MUS_AUDIO_SAMPLES_PER_CHANNEL: + val[0] = 44100; + break; + case MUS_AUDIO_CHANNEL: + /* number of channels */ + val[0] = 2.0; + if (chan > 1) { + val[1] = 1.0; + val[2] = 2.0; + } + break; + case MUS_AUDIO_SRATE: + /* supported sample rates */ + val[0] = 44100; + if (chan > 1) { + val[1] = 8000; + val[2] = 48000; + } + break; + case MUS_AUDIO_FORMAT: + /* supported formats (ugly...) */ + val[0] = 3.0; + val[1] = MUS_UBYTE; + val[2] = MUS_LSHORT; + val[3] = MUS_BSHORT; + break; + + case MUS_AUDIO_DIRECTION: /* Needs sorting. NJB */ + /* 0-->playback, 1-->capture */ + val[0] = 0; + break; + + default: + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_READ, -1, NULL); + /* return(mus_error(MUS_AUDIO_CANT_READ, NULL)); */ /* Bill 14-Nov-02 -- avoid possibly uncaught throw */ + break; + } + return(MUS_NO_ERROR); +} + + +int mus_audio_mixer_write(int ur_dev, int field, int chan, float *val) +{ + /* Ditto */ + val[0] = 0.0; + return MUS_NO_ERROR; +} + +/* pause can be implemented with play.pause and record.pause */ + + +void describe_audio_state_1(void) +{ + pprint("Enlightened Sound Daemon via socket connexion to default host"); +} + +#endif + + +/* ------------------------------- JACK ----------------------------------------- */ + +/* Kjetil S. Matheussen. k.s.matheussen@notam02.no */ +/* Based on code from ceres. */ + +#if HAVE_JACK +#define AUDIO_OK +#include <jack/jack.h> +#include <samplerate.h> +#include <sys/mman.h> +#include <signal.h> + +#if MUS_LITTLE_ENDIAN +# define MUS_COMP_SHORT MUS_LSHORT +# define MUS_COMP_FLOAT MUS_LFLOAT +#else +# define MUS_COMP_SHORT MUS_BSHORT +# define MUS_COMP_FLOAT MUS_BFLOAT +#endif + +#define SRC_QUALITY SRC_SINC_BEST_QUALITY + +/*************/ +/* Jack Part */ +/*************/ + +#define SNDJACK_NUMINCHANNELS 4 + +#define SNDJACK_MAXSNDS 20 + +#define SNDJACK_BUFFERSIZE 32768 + +typedef jack_default_audio_sample_t sample_t; +typedef jack_nframes_t nframes_t; + +struct SndjackChannel{ + jack_port_t *port; + sample_t *buffer; +}; + +static jack_client_t *sndjack_client = NULL; + + +/*************************/ +/* Variables for reading */ +/*************************/ +static int sndjack_num_read_channels_allocated=0; +static int sndjack_num_read_channels_inuse=0; +static struct SndjackChannel *sndjack_read_channels=NULL; +static pthread_cond_t sndjack_read_cond= PTHREAD_COND_INITIALIZER; +static pthread_mutex_t sndjack_read_mutex= PTHREAD_MUTEX_INITIALIZER; +static int sj_r_buffersize=0; +static int sj_r_writeplace=0; +static int sj_r_readplace=0; +static int sj_r_unread=0; +static int sj_r_xrun=0; +static int sj_r_totalxrun=0; + +/*************************/ +/* Variables for writing */ +/*************************/ +static pthread_cond_t sndjack_cond= PTHREAD_COND_INITIALIZER; +static pthread_mutex_t sndjack_mutex= PTHREAD_MUTEX_INITIALIZER; + +enum{SJ_STOPPED,SJ_RUNNING,SJ_ABOUTTOSTOP}; + +// Variables for the ringbuffer: +static int sj_writeplace=0; +static int sj_readplace=0; +static int sj_unread=0; +static int sj_buffersize; +static int sj_jackbuffersize; // number of frames sent to sndjack_process. +static int sj_totalxrun=0; +static int sj_xrun=0; +static int sj_status=SJ_STOPPED; + +static int sndjack_num_channels_allocated=0; +static int sndjack_num_channels_inuse=0; +static struct SndjackChannel *sndjack_channels=NULL; +static int sndjack_read_format; + +static SRC_STATE **sndjack_srcstates; +static double sndjack_srcratio=1.0; + +static int jack_mus_watchdog_counter=0; + + +#define SJ_MAX(a,b) (((a)>(b))?(a):(b)) + +static void sndjack_read_process(jack_nframes_t nframes){ + int i,ch; + sample_t *out[sndjack_num_channels_allocated]; + + if(sndjack_num_read_channels_inuse==0) return; + + for(ch=0;ch<sndjack_num_read_channels_allocated;ch++){ + out[ch]=(sample_t*)jack_port_get_buffer(sndjack_read_channels[ch].port,nframes); + } + + for(i=0;i<nframes;i++){ + if(sj_r_unread==sj_buffersize){ + sj_r_xrun+=nframes-i; + goto exit; + } + for(ch=0;ch<sndjack_num_read_channels_inuse;ch++) + sndjack_read_channels[ch].buffer[sj_r_writeplace]=out[ch][i]; + sj_r_unread++; + sj_r_writeplace++; + if(sj_r_writeplace==sj_r_buffersize) + sj_r_writeplace=0; + } + exit: + pthread_cond_broadcast(&sndjack_read_cond); +} + + +static void sndjack_write_process(jack_nframes_t nframes){ + int ch,i; + sample_t *out[sndjack_num_channels_allocated]; + + for(ch=0;ch<sndjack_num_channels_allocated;ch++){ + out[ch]=(sample_t*)jack_port_get_buffer(sndjack_channels[ch].port,nframes); + } + + if(sj_status==SJ_STOPPED){ + for(ch=0;ch<sndjack_num_channels_allocated;ch++){ + memset(out[ch],0,nframes*sizeof(sample_t)); + } + }else{ + + // First null out unused channels, if any. + if(sndjack_num_channels_inuse==1 && sndjack_num_channels_allocated>=2){ + for(ch=2;ch<sndjack_num_channels_allocated;ch++){ + memset(out[ch],0,nframes*sizeof(sample_t)); + } + }else{ + for(ch=sndjack_num_channels_inuse;ch<sndjack_num_channels_allocated;ch++){ + memset(out[ch],0,nframes*sizeof(sample_t)); + } + } + + for(i=0;i<nframes;i++){ + if(sj_unread==0){ + if(sj_status==SJ_RUNNING) + sj_xrun+=nframes-i; + for(;i<nframes;i++){ + for(ch=0;ch<sndjack_num_channels_inuse;ch++){ + out[ch][i]=0.0f; + } + } + break; + } + + if(sndjack_num_channels_inuse==1 && sndjack_num_channels_allocated>=2){ + for(ch=0;ch<2;ch++){ + out[ch][i]=sndjack_channels[0].buffer[sj_readplace]; + } + }else{ + for(ch=0;ch<sndjack_num_channels_inuse;ch++){ + out[ch][i]=sndjack_channels[ch].buffer[sj_readplace]; + } + } + sj_unread--; + sj_readplace++; + if(sj_readplace==sj_buffersize) + sj_readplace=0; + } + + pthread_cond_broadcast(&sndjack_cond); + + if(sj_status==SJ_ABOUTTOSTOP && sj_unread==0) + sj_status=SJ_STOPPED; + } + +} + + + +static int sndjack_process(jack_nframes_t nframes, void *arg){ + sndjack_read_process(nframes); + sndjack_write_process(nframes); + return 0; +} + + +static int sndjack_read(void *buf,int bytes,int chs){ + int i,ch; + int nframes=bytes / + sndjack_read_format==MUS_COMP_FLOAT ? sizeof(float) : + sndjack_read_format==MUS_COMP_SHORT ? sizeof(short) : + 1; + float *buf_f=buf; + short *buf_s=buf; + char *buf_c=buf; + + for(i=0;i<nframes;i++){ + while(sj_r_unread==0){ + pthread_cond_wait(&sndjack_read_cond,&sndjack_read_mutex); + jack_mus_watchdog_counter++; + } + + if(sj_r_xrun>0){ + sj_r_totalxrun+=sj_r_xrun; + sj_r_xrun=0; + return -1; + } + for(ch=0;ch<chs;ch++){ + switch(sndjack_read_format){ + case MUS_BYTE: + buf_c[i*chs+ch]=sndjack_read_channels[ch].buffer[sj_r_readplace] * 127.9f; + break; + case MUS_COMP_SHORT: + buf_s[i*chs+ch]=sndjack_read_channels[ch].buffer[sj_r_readplace] * 32767.9f; + break; + case MUS_COMP_FLOAT: + buf_f[i*chs+ch]=sndjack_read_channels[ch].buffer[sj_r_readplace]; + break; + }} + sj_r_unread--; + sj_r_readplace++; + if(sj_r_readplace==sj_r_buffersize) + sj_r_readplace=0; + } + return 0; +} + +static void sndjack_write(sample_t **buf,int nframes,int latencyframes,int chs){ + int ch; + int i; + + if(sj_xrun>0){ + if(sj_status==SJ_RUNNING){ + printf("Warning. %d frames delayed.\n",sj_xrun); + sj_totalxrun+=sj_xrun; + } + sj_xrun=0; + } + + for(i=0;i<nframes;i++){ + while( + sj_status==SJ_RUNNING + && (sj_unread==sj_buffersize + || sj_unread >= SJ_MAX(sj_jackbuffersize*2, latencyframes)) + ) + { + jack_mus_watchdog_counter++; + pthread_cond_wait(&sndjack_cond,&sndjack_mutex); + } + + for(ch=0;ch<chs;ch++) + sndjack_channels[ch].buffer[sj_writeplace]=buf[ch][i]; + + sj_unread++; + sj_writeplace++; + if(sj_writeplace==sj_buffersize) + sj_writeplace=0; + } + + if(sj_status==SJ_STOPPED) + if(sj_unread>=sj_jackbuffersize) + sj_status=SJ_RUNNING; +} + +static int sndjack_buffersizecallback(jack_nframes_t nframes, void *arg){ + sj_jackbuffersize=nframes; + return 0; +} + +static int sndjack_getnumoutchannels(void){ + int lokke=0; + const char **ports=jack_get_ports(sndjack_client,NULL,NULL,JackPortIsPhysical|JackPortIsInput); + while(ports!=NULL && ports[lokke]!=NULL){ + lokke++; + } + if(lokke<2) return 2; + return lokke; +} + +static int sndjack_getnuminchannels(void){ + int lokke=0; + const char **ports=jack_get_ports(sndjack_client,NULL,NULL,JackPortIsPhysical|JackPortIsOutput); + while(ports!=NULL && ports[lokke]!=NULL){ + lokke++; + } + if(lokke<2) return 2; + return lokke; +} + + +static int sndjack_init(void){ + int ch; + int numch; + int numch_read; + int num=0; + + while(num<SNDJACK_MAXSNDS){ + char temp[500]; + sprintf(temp,"sndlib%d",num); + if ((sndjack_client=jack_client_new(temp)) != 0) { + break; + } + num++; + } + + if(sndjack_client==NULL){ + /* printf("Unable to create new jack_client\n"); */ + return -1; + } + + pthread_mutex_init(&sndjack_mutex,NULL); + pthread_cond_init(&sndjack_cond,NULL); + pthread_mutex_init(&sndjack_read_mutex,NULL); + pthread_cond_init(&sndjack_read_cond,NULL); + + jack_set_process_callback(sndjack_client,sndjack_process,NULL); + + sndjack_num_channels_allocated = numch = sndjack_getnumoutchannels(); + numch_read=sndjack_getnuminchannels(); + sndjack_num_read_channels_allocated=SJ_MAX(SNDJACK_NUMINCHANNELS,numch_read); + + sndjack_channels=calloc(sizeof(struct SndjackChannel),numch); + sndjack_read_channels=calloc(sizeof(struct SndjackChannel),sndjack_num_read_channels_allocated); + + for(ch=0;ch<numch;ch++){ + sndjack_channels[ch].buffer=calloc(sizeof(sample_t),SNDJACK_BUFFERSIZE); + } + for(ch=0;ch<sndjack_num_read_channels_allocated;ch++){ + sndjack_read_channels[ch].buffer=calloc(sizeof(sample_t),SNDJACK_BUFFERSIZE); + } + sj_buffersize=SNDJACK_BUFFERSIZE; + + for(ch=0;ch<numch;ch++){ + char temp[500]; + sprintf(temp,"out_%d",ch+1); + if((sndjack_channels[ch].port=jack_port_register( + sndjack_client, + strdup(temp), + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, + 0 + ))==NULL) + { + fprintf(stderr,"Error. Could not register jack port.\n"); + goto failed_register; + } + } + + for(ch=0;ch<sndjack_num_read_channels_allocated;ch++){ + char temp[500]; + sprintf(temp,"in_%d",ch+1); + if((sndjack_read_channels[ch].port=jack_port_register( + sndjack_client, + strdup(temp), + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsInput, + 0 + ))==NULL) + { + fprintf(stderr,"Error. Could not register jack port.\n"); + goto failed_register; + } + } + + + + + sj_jackbuffersize=jack_get_buffer_size(sndjack_client); + jack_set_buffer_size_callback(sndjack_client,sndjack_buffersizecallback,NULL); + + if (jack_activate (sndjack_client)) { + fprintf (stderr, "Error. Cannot activate jack client.\n"); + goto failed_activate; + } + + { + const char **outportnames=jack_get_ports(sndjack_client,NULL,NULL,JackPortIsPhysical|JackPortIsInput); + for(ch=0;outportnames && outportnames[ch]!=NULL && ch<numch;ch++){ + if ( + jack_connect( + sndjack_client, + jack_port_name(sndjack_channels[ch].port), + outportnames[ch] + ) + ) + { + printf ("Warning. Cannot connect jack output port %d: \"%s\".\n",ch,outportnames[ch]); + } + } + } + + { + const char **inportnames=jack_get_ports(sndjack_client,NULL,NULL,JackPortIsPhysical|JackPortIsOutput); + for(ch=0;inportnames && inportnames[ch]!=NULL && ch<numch;ch++){ + if ( + jack_connect( + sndjack_client, + inportnames[ch], + jack_port_name(sndjack_read_channels[ch].port) + ) + ) + { + printf ("Warning. Cannot connect jack input port %d: \"%s\".\n",ch,inportnames[ch]); + } + } + } + return 0; + + // failed_connect: + failed_activate: + jack_deactivate(sndjack_client); + + failed_register: + jack_client_close(sndjack_client); + sndjack_client=NULL; + + return -1; +} +static void sndjack_cleanup(void){ + int ch; + for(ch=0;ch<sndjack_num_channels_allocated;ch++){ + src_delete(sndjack_srcstates[ch]); + } + jack_deactivate(sndjack_client); + jack_client_close(sndjack_client); + +} + + + +/***************/ +/* Sndlib Part */ +/***************/ + +static int sndjack_format; +static sample_t **sndjack_buffer; +static sample_t *sndjack_srcbuffer; + +static int sndjack_dev; +static int sndjack_read_dev; + +/* prototypes for the jack sndlib functions */ +static int jack_mus_audio_initialize(void); +static void jack_mus_oss_set_buffers(int num, int size); +static int jack_mus_audio_systems(void); +static char* jack_mus_audio_system_name(int system); +static char* jack_mus_audio_moniker(void); +static int jack_mus_audio_open_output(int ur_dev, int srate, int chans, int format, int size); +static int jack_mus_audio_open_input(int ur_dev, int srate, int chans, int format, int requested_size); +static int jack_mus_audio_write(int id, char *buf, int bytes); +static int jack_mus_audio_read(int id, char *buf, int bytes); +static int jack_mus_audio_close(int id); +static int jack_mus_audio_mixer_read(int ur_dev, int field, int chan, float *val); +static int jack_mus_audio_mixer_write(int ur_dev, int field, int chan, float *val); +static void jack_describe_audio_state_1(void); + + +static int jack_mus_audio_initialize(void) { + int ch; + + if(audio_initialized){ + return MUS_NO_ERROR; + } + + if(sndjack_init()!=0) + return MUS_ERROR; + + sndjack_buffer=calloc(sizeof(sample_t*),sndjack_num_channels_allocated); + for(ch=0;ch<sndjack_num_channels_allocated;ch++) + sndjack_buffer[ch]=calloc(sizeof(sample_t),SNDJACK_BUFFERSIZE); + sndjack_srcbuffer=calloc(sizeof(sample_t),SNDJACK_BUFFERSIZE); + + sndjack_srcstates=calloc(sizeof(SRC_STATE*),sndjack_num_channels_allocated); + for(ch=0;ch<sndjack_num_channels_allocated;ch++){ + sndjack_srcstates[ch]=src_new(SRC_QUALITY,1,NULL); + } + + atexit(sndjack_cleanup); + + api = JACK_API; + vect_mus_audio_initialize = jack_mus_audio_initialize; + vect_mus_oss_set_buffers = jack_mus_oss_set_buffers; + vect_mus_audio_systems = jack_mus_audio_systems; + vect_mus_audio_system_name = jack_mus_audio_system_name; + vect_mus_audio_moniker = jack_mus_audio_moniker; + vect_mus_audio_open_output = jack_mus_audio_open_output; + vect_mus_audio_open_input = jack_mus_audio_open_input; + vect_mus_audio_write = jack_mus_audio_write; + vect_mus_audio_read = jack_mus_audio_read; + vect_mus_audio_close = jack_mus_audio_close; + vect_mus_audio_mixer_read = jack_mus_audio_mixer_read; + vect_mus_audio_mixer_write = jack_mus_audio_mixer_write; + vect_describe_audio_state_1 = jack_describe_audio_state_1; + + audio_initialized = true; + +#if 0 + + /* Locking all future memory shouldn't be that necessary, and might even freeze the machine in certain situations. */ + /* So remove MCL_FUTURE from the mlockall call. (No. We can't do that. It can screw up code using the realtime extension. -Kjetil.*/ + munlockall(); + //mlockall(MCL_CURRENT); + + // Instead we just do this: (which is not enough, but maybe better than nothing) + { + mlock(sndjack_channels,sizeof(struct SndjackChannel)*sndjack_num_channels_allocated); + mlock(sndjack_read_channels,sizeof(struct SndjackChannel)*sndjack_num_read_channels_allocated); + + for(ch=0;ch<numch;ch++){ + mlock(sndjack_channels[ch].buffer,sizeof(sample_t)*SNDJACK_BUFFERSIZE); + } + for(ch=0;ch<sndjack_num_read_channels_allocated;ch++){ + mlock(sndjack_read_channels[ch].buffer,sizeof(sample_t)*SNDJACK_BUFFERSIZE); + } + } +#endif + + return MUS_NO_ERROR; +} + +// ?? +static void jack_mus_oss_set_buffers(int num, int size){ +} + +static int jack_mus_isrunning=0; +static pid_t jack_mus_player_pid; +static pthread_t jack_mus_watchdog_thread; + +static void *jack_mus_audio_watchdog(void *arg){ + struct sched_param par; + + par.sched_priority = sched_get_priority_max(SCHED_RR); + if(sched_setscheduler(0,SCHED_RR,&par)==-1){ + fprintf(stderr,"SNDLIB: Unable to set SCHED_RR realtime priority for the watchdog thread. No watchdog.\n"); + goto exit; + } + + for(;;){ + int last=jack_mus_watchdog_counter; + sleep(1); + + if(jack_mus_isrunning && jack_mus_watchdog_counter<last+10){ + struct sched_param par; + fprintf(stderr,"SNDLIB: Setting player to non-realtime for 2 seconds.\n"); + + par.sched_priority = 0; + if(sched_setscheduler(jack_mus_player_pid,SCHED_OTHER,&par)==-1){ + fprintf(stderr,"SNDLIB: Unable to set non-realtime priority. Must kill player thread. Sorry!\n"); + while(1){ + kill(jack_mus_player_pid,SIGKILL); + sleep(2); + } + } + + sleep(2); + + if(jack_mus_isrunning){ + par.sched_priority = sched_get_priority_min(SCHED_RR)+1; + if(sched_setscheduler(jack_mus_player_pid,SCHED_RR,&par)==-1){ + fprintf(stderr,"SNDLIB: Could not set back to realtime priority...\n"); + }else + fprintf(stderr,"SNDLIB: Play thread set back to realtime priority.\n"); + } + + } + } + exit: + fprintf(stderr,"SNDLIB: Watchdog exiting\n"); + return NULL; +} + + + +static void jack_mus_audio_set_realtime(void){ + struct sched_param par; + static int watchdog_started=0; + + jack_mus_player_pid=getpid(); + + if(watchdog_started==0){ + if(pthread_create(&jack_mus_watchdog_thread,NULL,jack_mus_audio_watchdog,NULL)!=0){ + fprintf(stderr,"Could not create watchdog. Not running realtime\n"); + return; + } + watchdog_started=1; + } + + jack_mus_isrunning=1; + + par.sched_priority = sched_get_priority_min(SCHED_RR)+1; + if(sched_setscheduler(0,SCHED_RR,&par)==-1){ + fprintf(stderr,"SNDLIB: Unable to set SCHED_RR realtime priority for the player thread.\n"); + }{ + //fprintf(stderr,"Set realtime priority\n"); + } +} + +static void jack_mus_audio_set_non_realtime(void){ + struct sched_param par; + par.sched_priority = 0; + sched_setscheduler(0,SCHED_OTHER,&par); + //fprintf(stderr,"Set non-realtime priority\n"); + jack_mus_isrunning=0; +} + +int jack_mus_audio_open_output(int dev, int srate, int chans, int format, int size){ + if(sndjack_client==NULL){ + if(jack_mus_audio_initialize()==MUS_ERROR) + return MUS_ERROR; + } + + if(sndjack_num_channels_allocated<chans){ + printf("Error. Can not play back %d channels. (Only %d)\n",chans,sndjack_num_channels_allocated); + return MUS_ERROR; + } + + if(format!=MUS_BYTE && format!=MUS_COMP_SHORT && format!=MUS_COMP_FLOAT){ + printf("Error, unable to handle format %s.\n",mus_data_format_to_string(format)); + return MUS_ERROR; + } + + while(sj_status!=SJ_STOPPED) usleep(5); + + sj_unread=0; + sj_writeplace=0; + sj_readplace=0; + + + if(srate!=jack_get_sample_rate(sndjack_client)){ + int lokke; + //printf("Warning, sample-rate differs between snd and jack. Sound will not be played correctly! %d/%d\n",srate,jack_get_sample_rate(sndjack_client)); + sndjack_srcratio=(double)jack_get_sample_rate(sndjack_client)/(double)srate; + for(lokke=0;lokke<chans;lokke++){ + src_reset(sndjack_srcstates[lokke]); + } + }else{ + sndjack_srcratio=1.0; + } + + sndjack_format=format; + sndjack_num_channels_inuse=chans; + sndjack_dev=dev; + + jack_mus_audio_set_realtime(); + + return(MUS_NO_ERROR); +} + +static int sndjack_from_byte(int ch,int chs,char *buf,float *out,int bytes){ + int i; + int len=bytes/chs; + if(len>SNDJACK_BUFFERSIZE) return -1; + + for(i=0;i<len;i++){ + out[i]=MUS_BYTE_TO_SAMPLE(buf[i*chs+ch]); + } + return len; +} + +static int sndjack_from_short(int ch,int chs,short *buf,float *out,int bytes){ + int i; + int len=bytes/(sizeof(short)*chs); + if(len>SNDJACK_BUFFERSIZE) return -1; + + for(i=0;i<len;i++){ + out[i]=(float)buf[i*chs+ch]/32768.1f; + } + return len; +} + +static int sndjack_from_float(int ch,int chs,float *buf,float *out,int bytes){ + int i; + int len=bytes/(sizeof(float)*chs); + if(len>SNDJACK_BUFFERSIZE) return -1; + + for(i=0;i<len;i++){ + out[i]=buf[i*chs+ch]; + } + return len; +} + + +int jack_mus_audio_write(int line, char *buf, int bytes){ + int i; + int ch; + int outlen=0; + + for(ch=0;ch<sndjack_num_channels_inuse;ch++){ + int len = 0; + float *buf2=sndjack_srcratio==1.0?sndjack_buffer[ch]:sndjack_srcbuffer; + + switch(sndjack_format){ + case MUS_BYTE: + len=sndjack_from_byte(ch,sndjack_num_channels_inuse,buf,buf2,bytes); + break; + case MUS_COMP_SHORT: + len=sndjack_from_short(ch,sndjack_num_channels_inuse,(short *)buf,buf2,bytes); + break; + case MUS_COMP_FLOAT: + len=sndjack_from_float(ch,sndjack_num_channels_inuse,(float *)buf,buf2,bytes); + break; + } + if(len<0){ + printf("Errur. Input buffer to large for mus_audio_write.\n"); + return MUS_ERROR; + } + + if(sndjack_srcratio!=1.0){ + SRC_DATA src_data={ + buf2,sndjack_buffer[ch], + len,SNDJACK_BUFFERSIZE, + 0,0, + 0, + sndjack_srcratio + }; + int res=src_process(sndjack_srcstates[ch],&src_data); + if(res!=0){ + printf("Error while resampling. (%s)\n",src_strerror(res)); + return MUS_ERROR; + } + if(src_data.input_frames!=len){ + printf("Unsuccessfull resampling: Should have used %d bytes, used %d.",len,src_data.input_frames); + return MUS_ERROR; + } + if(ch>0 && src_data.output_frames_gen!=outlen){ + printf("Error, src_process did not output the same number of frames as previous resampled channel (%d/%d).\n" + "Please report this problem to k.s.matheussen@notam02.no. Thanks!\n",src_data.output_frames_gen,outlen); + return MUS_ERROR; + } + outlen=src_data.output_frames_gen; + }else{ + outlen=len; + } + } + + + sndjack_write(sndjack_buffer,outlen,outlen*2,sndjack_num_channels_inuse); + + return MUS_NO_ERROR; +} + +int jack_mus_audio_close(int line) +{ + jack_mus_audio_set_non_realtime(); + if(line==sndjack_dev){ + sj_status=SJ_ABOUTTOSTOP; + sndjack_num_channels_inuse=0; + } + return MUS_NO_ERROR; + } + +int jack_mus_audio_mixer_read(int dev, int field, int chan, float *val) +{ + //printf("dev: %d, field: %d, chan: %d\n",dev,field,chan); + + switch(field){ + case MUS_AUDIO_FORMAT: + val[1]=MUS_COMP_FLOAT; + val[0]=1; + break; + case MUS_AUDIO_PORT: + val[0]=1; + val[1]=MUS_AUDIO_DIGITAL_IN; + break; + case MUS_AUDIO_CHANNEL: + val[0]=sndjack_num_read_channels_allocated; + break; + case MUS_AUDIO_AMP: + val[0] = 1.0f; + break; + default: + printf("Got unknown request with field %d %d\n",field, MUS_AUDIO_AMP); + return MUS_ERROR; + } + + return MUS_NO_ERROR; +} + +int jack_mus_audio_mixer_write(int dev, int field, int chan, float *val){ + return(MUS_NO_ERROR); +} + +int jack_mus_audio_open_input(int dev, int srate, int chans, int format, int size){ + if(sndjack_client==NULL){ + if(jack_mus_audio_initialize()==MUS_ERROR) + return MUS_ERROR; + } + + if(sndjack_num_read_channels_allocated<chans){ + printf("Error. Can not record %d channels. (Only %d)\n",chans,sndjack_num_read_channels_allocated); + return MUS_ERROR; + } + + printf("dev: %d\n" ,dev); + if(format!=MUS_BYTE && format!=MUS_COMP_SHORT && format!=MUS_COMP_FLOAT){ + printf("Error, unable to handle format %s.\n",mus_data_format_to_string(format)); + return MUS_ERROR; + } + + if(srate!=jack_get_sample_rate(sndjack_client)){ + printf("Warning, jacks samplerate is %d (and not %d), and the recording will use this samplerate too.\n",jack_get_sample_rate(sndjack_client),srate); + } + + sndjack_read_format=format; + sndjack_num_read_channels_inuse=chans; + sndjack_read_dev=dev; + + return(MUS_NO_ERROR); +} + +int jack_mus_audio_read(int line, char *buf, int bytes){ + if(sndjack_read(buf,bytes,sndjack_num_read_channels_inuse)==-1) + return(MUS_ERROR); + return MUS_NO_ERROR; +} + + +static void jack_describe_audio_state_1(void) { + char temp[500]; + + pprint("jack audio:\n"); + sprintf(temp,"\tNumber of output channels: %d\n",sndjack_num_channels_allocated);pprint(temp); + sprintf(temp,"\tNumber of input channels: %d\n",sndjack_num_read_channels_allocated);pprint(temp); + sprintf(temp,"\tSamplerate: %d\n",jack_get_sample_rate(sndjack_client));pprint(temp); + sprintf(temp,"\tJack buffersize: %d\n",sj_jackbuffersize);pprint(temp); + sprintf(temp,"\tSndjack buffersize: %d\n",SNDJACK_BUFFERSIZE);pprint(temp); + sprintf(temp,"\tMax number of instances: %d\n",SNDJACK_MAXSNDS);pprint(temp); + sprintf(temp,"\tTotal number of frames delayed: %d\n",sj_totalxrun);pprint(temp); + sprintf(temp,"\tCurrent cpu-load: %f\n",jack_cpu_load(sndjack_client));pprint(temp); + sprintf(temp,"\tIs running realtime: %s\n",jack_is_realtime(sndjack_client)==1?"yes":"no");pprint(temp); + sprintf(temp,"\tResample quality (only used when needed): %s (%s)\n",src_get_name(SRC_QUALITY),src_get_description(SRC_QUALITY));pprint(temp); + sprintf(temp,"\tIs able to handle the following audio formats: %s %s %s\n",mus_data_format_to_string(MUS_BYTE),mus_data_format_to_string(MUS_COMP_SHORT),mus_data_format_to_string(MUS_COMP_FLOAT));pprint(temp); + sprintf(temp,"\tPrefered audio format: %s\n",mus_data_format_to_string(MUS_COMP_FLOAT));pprint(temp); +} + + +int jack_mus_audio_systems(void) { + return(2); +} + +char *jack_mus_audio_system_name(int system) {return("linux jack");} +char *jack_mus_audio_moniker(void) {return("jack");} +#endif + + + +/* ------------------------------- HPUX ----------------------------------------- */ + +/* if this is basically the same as the Sun case with different macro names, + * then it could perhaps be updated to match the new Sun version above -- + * Sun version changed 28-Jan-99 + */ + +#if defined(MUS_HPUX) && (!(defined(AUDIO_OK))) +#define AUDIO_OK +#include <sys/audio.h> + +#define RETURN_ERROR_EXIT(Error_Type, Audio_Line, Ur_Error_Message) \ + do { char *Error_Message; Error_Message = Ur_Error_Message; \ + if (Audio_Line != -1) close(Audio_Line); \ + if (Error_Message) \ + {MUS_STANDARD_ERROR(Error_Type, Error_Message); FREE(Error_Message);} \ + else MUS_STANDARD_ERROR(Error_Type, mus_error_type_to_string(Error_Type)); \ + return(MUS_ERROR); \ + } while (false) + +char *mus_audio_moniker(void) {return("HPUX audio");} + +int mus_audio_open_output(int ur_dev, int srate, int chans, int format, int size) +{ + int fd, i, dev; + struct audio_describe desc; + dev = MUS_AUDIO_DEVICE(ur_dev); + fd = open("/dev/audio", O_RDWR); + if (fd == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_OPEN, -1, + mus_format("can't open /dev/audio for output: %s", + strerror(errno))); + ioctl(fd, AUDIO_SET_CHANNELS, chans); + if (dev == MUS_AUDIO_SPEAKERS) + ioctl(fd, AUDIO_SET_OUTPUT, AUDIO_OUT_SPEAKER); + else + if (dev == MUS_AUDIO_LINE_OUT) + ioctl(fd, AUDIO_SET_OUTPUT, AUDIO_OUT_LINE); + else ioctl(fd, AUDIO_SET_OUTPUT, AUDIO_OUT_HEADPHONE); + if (format == MUS_BSHORT) + ioctl(fd, AUDIO_SET_DATA_FORMAT, AUDIO_FORMAT_LINEAR16BIT); + else + if (format == MUS_MULAW) + ioctl(fd, AUDIO_SET_DATA_FORMAT, AUDIO_FORMAT_ULAW); + else + if (format == MUS_ALAW) + ioctl(fd, AUDIO_SET_DATA_FORMAT, AUDIO_FORMAT_ALAW); + else + RETURN_ERROR_EXIT(MUS_AUDIO_FORMAT_NOT_AVAILABLE, fd, + mus_format("can't set output format to %d (%s) for %d (%s)", + format, mus_audio_format_name(format), + dev, + mus_audio_device_name(dev))); + ioctl(fd, AUDIO_DESCRIBE, &desc); + for(i = 0; i < desc.nrates; i++) + if(srate == desc.sample_rate[i]) + break; + if (i == desc.nrates) + RETURN_ERROR_EXIT(SRATE_NOT_AVAILABLE, fd, + mus_format("can't set srate to %d on %d (%s)", + srate, dev, + mus_audio_device_name(dev))); + ioctl(fd, AUDIO_SET_SAMPLE_RATE, srate); + return(fd); +} + +int mus_audio_write(int line, char *buf, int bytes) +{ + write(line, buf, bytes); + return(MUS_NO_ERROR); +} + +int mus_audio_close(int line) +{ + close(line); + return(MUS_NO_ERROR); +} + +static void describe_audio_state_1(void) +{ + struct audio_describe desc; + struct audio_gain gain; + int mina, maxa, fd, tmp; + int g[2]; + fd = open("/dev/audio", O_RDWR); + if (fd == -1) return; + ioctl(fd, AUDIO_GET_OUTPUT, &tmp); + switch (tmp) + { + case AUDIO_OUT_SPEAKER: pprint("output: speakers\n"); break; + case AUDIO_OUT_HEADPHONE: pprint("output: headphone\n"); break; + case AUDIO_OUT_LINE: pprint("output: line out\n"); break; + } + ioctl(fd, AUDIO_GET_INPUT, &tmp); + switch (tmp) + { + case AUDIO_IN_MIKE: pprint("input: mic\n"); break; + case AUDIO_IN_LINE: pprint("input: line in\n"); break; + } + ioctl(fd, AUDIO_GET_DATA_FORMAT, &tmp); + switch (tmp) + { + case AUDIO_FORMAT_LINEAR16BIT: pprint("format: 16-bit linear\n"); break; + case AUDIO_FORMAT_ULAW: pprint("format: mulaw\n"); break; + case AUDIO_FORMAT_ALAW: pprint("format: alaw\n"); break; + } + ioctl(fd, AUDIO_DESCRIBE, &desc); + gain.channel_mask = (AUDIO_CHANNEL_LEFT | AUDIO_CHANNEL_RIGHT); + ioctl(fd, AUDIO_GET_GAINS, &gain); + close(fd); + g[0] = gain.cgain[0].transmit_gain; + g[1] = gain.cgain[1].transmit_gain; + mina = desc.min_transmit_gain; + maxa = desc.max_transmit_gain; + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "out vols: %.3f %.3f\n", + (float)(g[0] - mina) / (float)(maxa - mina), + (float)(g[1] - mina) / (float)(maxa - mina)); + pprint(audio_strbuf); + g[0] = gain.cgain[0].receive_gain; + g[1] = gain.cgain[1].receive_gain; + mina = desc.min_receive_gain; + maxa = desc.max_receive_gain; + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "in vols: %.3f %.3f\n", + (float)(g[0] - mina) / (float)(maxa - mina), + (float)(g[1] - mina) / (float)(maxa - mina)); + pprint(audio_strbuf); + g[0] = gain.cgain[0].monitor_gain; + g[1] = gain.cgain[1].monitor_gain; + mina = desc.min_monitor_gain; + maxa = desc.max_monitor_gain; + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "monitor vols: %.3f %.3f\n", + (float)(g[0] - mina) / (float)(maxa - mina), + (float)(g[1] - mina) / (float)(maxa - mina)); + pprint(audio_strbuf); +} + +int mus_audio_mixer_read(int ur_dev, int field, int chan, float *val) +{ + struct audio_describe desc; + struct audio_gain gain; + int audio_fd = -1, srate, g, maxa, mina, dev, err = MUS_NO_ERROR; + dev = MUS_AUDIO_DEVICE(ur_dev); + if (field == MUS_AUDIO_PORT) + { + val[0] = 4; + if (chan > 1) val[1] = MUS_AUDIO_MICROPHONE; + if (chan > 2) val[2] = MUS_AUDIO_DAC_OUT; + if (chan > 3) val[3] = MUS_AUDIO_LINE_OUT; + if (chan > 4) val[4] = MUS_AUDIO_LINE_IN; + } + else + { + if (field == FORMAT_FIELD) + { + val[0] = 3; + if (chan > 1) val[1] = MUS_BSHORT; + if (chan > 2) val[2] = MUS_MULAW; + if (chan > 3) val[3] = MUS_ALAW; + } + else + { + audio_fd = open("/dev/audio", O_RDWR); + ioctl(audio_fd, AUDIO_DESCRIBE, &desc); + switch (dev) + { + case MUS_AUDIO_DEFAULT: + case MUS_AUDIO_DAC_OUT: + case MUS_AUDIO_SPEAKERS: + case MUS_AUDIO_LINE_OUT: + switch (field) + { + case MUS_AUDIO_AMP: + ioctl(audio_fd, AUDIO_GET_GAINS, &gain); + if (chan == 0) + g = gain.cgain[0].transmit_gain; + else g = gain.cgain[1].transmit_gain; + mina = desc.min_transmit_gain; + maxa = desc.max_transmit_gain; + val[0] = (float)(g - mina) / (float)(maxa - mina); + break; + case MUS_AUDIO_CHANNEL: + val[0] = 2; + break; + case MUS_AUDIO_SRATE: + ioctl(audio_fd, AUDIO_GET_SAMPLE_RATE, &srate); + val[0] = srate; + break; + default: + err = MUS_ERROR; + break; + } + break; + case MUS_AUDIO_MICROPHONE: + case MUS_AUDIO_LINE_IN: + case MUS_AUDIO_DUPLEX_DEFAULT: + switch (field) + { + case MUS_AUDIO_AMP: + ioctl(audio_fd, AUDIO_GET_GAINS, &gain); + if (chan == 0) + g = gain.cgain[0].receive_gain; + else g = gain.cgain[1].receive_gain; + mina = desc.min_receive_gain; + maxa = desc.max_receive_gain; + val[0] = (float)(g - mina) / (float)(maxa - mina); + break; + case MUS_AUDIO_CHANNEL: + val[0] = 2; + break; + case MUS_AUDIO_SRATE: + ioctl(audio_fd, AUDIO_GET_SAMPLE_RATE, &srate); + val[0] = srate; + break; + default: + err = MUS_ERROR; + break; + } + break; + default: + err = MUS_ERROR; + break; + } + } + } + if (err == MUS_ERROR) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_READ, audio_fd, + mus_format("can't read %s field of device %d (%s)", + mus_audio_device_name(field), + dev, + mus_audio_device_name(dev))); + + if (audio_fd != -1) close(audio_fd); + return(MUS_NO_ERROR); +} + +int mus_audio_mixer_write(int ur_dev, int field, int chan, float *val) +{ + struct audio_describe desc; + struct audio_gain gain; + int audio_fd = -1, srate, g, maxa, mina, dev, err = MUS_NO_ERROR; + dev = MUS_AUDIO_DEVICE(ur_dev); + audio_fd = open("/dev/audio", O_RDWR); + ioctl(audio_fd, AUDIO_DESCRIBE, &desc); + switch (dev) + { + case MUS_AUDIO_DEFAULT: + case MUS_AUDIO_DAC_OUT: + case MUS_AUDIO_SPEAKERS: + case MUS_AUDIO_LINE_OUT: + switch (field) + { + case MUS_AUDIO_AMP: + mina = desc.min_transmit_gain; + maxa = desc.max_transmit_gain; + ioctl(audio_fd, AUDIO_GET_GAINS, &gain); + g = mina + val[0] * (maxa - mina); + if (chan == 0) + gain.cgain[0].transmit_gain = g; + else gain.cgain[1].transmit_gain = g; + ioctl(audio_fd, AUDIO_SET_GAINS, &gain); + break; + case MUS_AUDIO_SRATE: + srate = val[0]; + ioctl(audio_fd, AUDIO_SET_SAMPLE_RATE, srate); + break; + default: + err = MUS_ERROR; + break; + } + break; + case MUS_AUDIO_MICROPHONE: + case MUS_AUDIO_LINE_IN: + case MUS_AUDIO_DUPLEX_DEFAULT: + switch (field) + { + case MUS_AUDIO_AMP: + mina = desc.min_receive_gain; + maxa = desc.max_receive_gain; + ioctl(audio_fd, AUDIO_GET_GAINS, &gain); + g = mina + val[0] * (maxa - mina); + if (chan == 0) + gain.cgain[0].receive_gain = g; + else gain.cgain[1].receive_gain = g; + ioctl(audio_fd, AUDIO_SET_GAINS, &gain); + break; + case MUS_AUDIO_SRATE: + srate = val[0]; + ioctl(audio_fd, AUDIO_SET_SAMPLE_RATE, srate); + break; + default: + err = MUS_ERROR; + break; + } + break; + default: + err = MUS_ERROR; + break; + } + if (err == MUS_ERROR) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_READ, audio_fd, + mus_format("can't set %s field of device %d (%s)", + mus_audio_device_name(field), + dev, + mus_audio_device_name(dev))); + + if (audio_fd != -1) close(audio_fd); + return(MUS_NO_ERROR); +} + +int mus_audio_initialize(void) {return(MUS_NO_ERROR);} + +int mus_audio_systems(void) {return(1);} +char *mus_audio_system_name(int system) {return("HPUX");} + +/* struct audio_status status_b; + * ioctl(devAudio, AUDIO_GET_STATUS, &status_b) + * not_busy = (status_b.transmit_status == AUDIO_DONE); +*/ + +int mus_audio_open_input(int ur_dev, int srate, int chans, int format, int size) +{ + int fd, i, dev; + struct audio_describe desc; + dev = MUS_AUDIO_DEVICE(ur_dev); + fd = open("/dev/audio", O_RDWR); + if (fd == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_OPEN, NULL, + mus_format("can't open /dev/audio for input: %s", + strerror(errno))); + ioctl(fd, AUDIO_SET_CHANNELS, chans); + if (dev == MUS_AUDIO_MICROPHONE) + ioctl(fd, AUDIO_SET_INPUT, AUDIO_IN_MIKE); + else ioctl(fd, AUDIO_SET_INPUT, AUDIO_IN_LINE); + if (format == MUS_BSHORT) + ioctl(fd, AUDIO_SET_DATA_FORMAT, AUDIO_FORMAT_LINEAR16BIT); + else + if (format == MUS_MULAW) + ioctl(fd, AUDIO_SET_DATA_FORMAT, AUDIO_FORMAT_ULAW); + else + if (format == MUS_ALAW) + ioctl(fd, AUDIO_SET_DATA_FORMAT, AUDIO_FORMAT_ALAW); + else + RETURN_ERROR_EXIT(MUS_AUDIO_FORMAT_NOT_AVAILABLE, fd, + mus_format("can't set input format to %d (%s) on %d (%s)", + format, mus_audio_format_name(format), + dev, + mus_audio_device_name(dev))); + ioctl(fd, AUDIO_DESCRIBE, &desc); + for(i = 0; i < desc.nrates; i++) + if(srate == desc.sample_rate[i]) + break; + if (i == desc.nrates) + RETURN_ERROR_EXIT(MUS_AUDIO_SRATE_NOT_AVAILABLE, fd, + mus_format("can't set srate to %d on %d (%s)", + srate, dev, + mus_audio_device_name(dev))); + ioctl(fd, AUDIO_SET_SAMPLE_RATE, srate); + return(fd); +} + +int mus_audio_read(int line, char *buf, int bytes) +{ + read(line, buf, bytes); + return(MUS_NO_ERROR); +} + +#endif + + + +/* ------------------------------- NETBSD ----------------------------------------- */ + +#if defined(MUS_NETBSD) && (!(defined(AUDIO_OK))) +#define AUDIO_OK +/* started from Xanim a long time ago..., bugfixes from Thomas Klausner 30-Jul-05, worked into better shape Aug-05 */ +#include <fcntl.h> +#include <sys/audioio.h> +#include <sys/ioctl.h> + +#define RETURN_ERROR_EXIT(Error_Type, Audio_Line, Ur_Error_Message) \ + do { char *Error_Message; Error_Message = Ur_Error_Message; \ + if (Audio_Line != -1) close(Audio_Line); \ + if (Error_Message) \ + {MUS_STANDARD_ERROR(Error_Type, Error_Message); FREE(Error_Message);} \ + else MUS_STANDARD_ERROR(Error_Type, mus_error_type_to_string(Error_Type)); \ + return(MUS_ERROR); \ + } while (false) + +static int bsd_format_to_sndlib(int encoding) +{ + switch (encoding) + { + case AUDIO_ENCODING_ULAW: return(MUS_MULAW); break; + case AUDIO_ENCODING_ALAW: return(MUS_ALAW); break; + case AUDIO_ENCODING_LINEAR: return(MUS_BSHORT); break; /* "sun compatible" so probably big-endian? */ + case AUDIO_ENCODING_SLINEAR: + case AUDIO_ENCODING_LINEAR8: return(MUS_BYTE); break; + case AUDIO_ENCODING_SLINEAR_LE: return(MUS_LSHORT); break; + case AUDIO_ENCODING_SLINEAR_BE: return(MUS_BSHORT); break; + case AUDIO_ENCODING_ULINEAR_LE: return(MUS_ULSHORT); break; + case AUDIO_ENCODING_ULINEAR_BE: return(MUS_UBSHORT); break; + case AUDIO_ENCODING_ULINEAR: return(MUS_UBYTE); break; + case AUDIO_ENCODING_NONE: + case AUDIO_ENCODING_ADPCM: + default: return(MUS_UNKNOWN); break; + } + return(MUS_UNKNOWN); +} + +static int sndlib_format_to_bsd(int encoding) +{ + switch (encoding) + { + case MUS_MULAW: return(AUDIO_ENCODING_ULAW); break; + case MUS_ALAW: return(AUDIO_ENCODING_ALAW); break; + case MUS_BYTE: return(AUDIO_ENCODING_SLINEAR); break; + case MUS_LSHORT: return(AUDIO_ENCODING_SLINEAR_LE); break; + case MUS_BSHORT: return(AUDIO_ENCODING_SLINEAR_BE); break; + case MUS_ULSHORT: return(AUDIO_ENCODING_ULINEAR_LE); break; + case MUS_UBSHORT: return(AUDIO_ENCODING_ULINEAR_BE); break; + case MUS_UBYTE: return(AUDIO_ENCODING_ULINEAR); break; + } + return(AUDIO_ENCODING_NONE); +} + +int mus_audio_initialize(void) +{ + return(MUS_NO_ERROR); +} + +int mus_audio_systems(void) +{ + return(1); +} + +char *mus_audio_system_name(int system) +{ + return("NetBSD"); +} + +char *mus_audio_moniker(void) +{ + return("NetBSD audio"); +} + +static int cur_chans = 1, cur_srate = 22050; + +int mus_audio_write(int line, char *buf, int bytes) +{ + /* trouble... AUDIO_WSEEK always returns 0, no way to tell that I'm about to + * hit "hiwat", but when I do, it hangs. Can't use AUDIO_DRAIN -- + * it introduces interruptions. Not sure what to do... + */ + int b = 0; + b = write(line, buf, bytes); + usleep(10000); + if ((b != bytes) && (b > 0)) /* b <= 0 presumably some sort of error, and we want to avoid infinite recursion below */ + { + /* hangs at close if we don't handle this somehow */ + if ((cur_chans == 1) || (cur_srate == 22050)) + sleep(1); + else usleep(10000); + mus_audio_write(line, (char *)(buf + b), bytes - b); + } + return(MUS_NO_ERROR); +} + +int mus_audio_close(int line) +{ + usleep(100000); + ioctl(line, AUDIO_FLUSH, 0); + close(line); + return(MUS_NO_ERROR); +} + +static int netbsd_default_outputs = (AUDIO_HEADPHONE | AUDIO_LINE_OUT | AUDIO_SPEAKER); + +void mus_netbsd_set_outputs(int speakers, int headphones, int line_out) +{ + netbsd_default_outputs = 0; + if (speakers) netbsd_default_outputs |= AUDIO_SPEAKER; + if (headphones) netbsd_default_outputs |= AUDIO_HEADPHONE; + if (line_out) netbsd_default_outputs |= AUDIO_LINE_OUT; +} + +int mus_audio_open_output(int dev, int srate, int chans, int format, int size) +{ + int line, encode; + audio_info_t a_info; + + line = open("/dev/sound", O_WRONLY | O_NDELAY); /* /dev/audio assumes mono 8-bit mulaw */ + if (line == -1) + { + if (errno == EBUSY) + return(mus_error(MUS_AUDIO_CANT_OPEN, NULL)); + else return(mus_error(MUS_AUDIO_DEVICE_NOT_AVAILABLE, NULL)); + } + AUDIO_INITINFO(&a_info); + + /* a_info.blocksize = size; */ + encode = sndlib_format_to_bsd(format); + if (encode == AUDIO_ENCODING_NONE) + RETURN_ERROR_EXIT(MUS_AUDIO_FORMAT_NOT_AVAILABLE, -1, + mus_format("format %d (%s) not available", + format, + mus_data_format_name(format))); + a_info.play.encoding = encode; + a_info.mode = AUMODE_PLAY | AUMODE_PLAY_ALL; + a_info.play.precision = mus_bytes_per_sample(format) * 8; + a_info.play.sample_rate = srate; + if (dev == MUS_AUDIO_LINE_OUT) + a_info.play.port = AUDIO_LINE_OUT; + else + { + if (dev == MUS_AUDIO_SPEAKERS) + a_info.play.port = AUDIO_SPEAKER | (netbsd_default_outputs & AUDIO_HEADPHONE); + else a_info.play.port = netbsd_default_outputs; + } + a_info.play.channels = chans; + ioctl(line, AUDIO_SETINFO, &a_info); + /* actually doesn't set the "ports" field -- always 0 */ + + ioctl(line, AUDIO_GETINFO, &a_info); + + if ((int)(a_info.play.sample_rate) != srate) + mus_print("srate: %d -> %d\n", srate, a_info.play.sample_rate); + if ((int)(a_info.play.encoding) != sndlib_format_to_bsd(format)) + mus_print("encoding: %d -> %d\n", sndlib_format_to_bsd(format), a_info.play.encoding); + if ((int)(a_info.play.channels) != chans) + mus_print("chans: %d -> %d\n", chans, a_info.play.channels); + + cur_chans = chans; + cur_srate = srate; + + return(line); +} + +int mus_audio_read(int line, char *buf, int bytes) +{ + read(line, buf, bytes); + return(MUS_NO_ERROR); +} + +static void describe_audio_state_1(void) +{ + audio_device_t dev; + int i = 0, val, err = 0; + int line; + float amp; + audio_info_t a_info; + audio_encoding_t e_info; + + pprint("NetBSD "); + line = open("/dev/sound", O_WRONLY | O_NDELAY); + if (line == -1) + return; + + pprint("/dev/sound:\n"); + err = ioctl(line, AUDIO_GETDEV, &dev); + if (err == 0) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "%s: version: %s (%s)", dev.name, dev.version, dev.config); + pprint(audio_strbuf); + } + + err = ioctl(line, AUDIO_GETPROPS, &val); + if (err == 0) + { + if (val & AUDIO_PROP_FULLDUPLEX) + pprint(" full-duplex"); + else pprint(" half-duplex"); + } + pprint("\n"); + + err = ioctl(line, AUDIO_GETINFO, &a_info); + if (err == 0) + { + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " play: srate: %d, chans: %d, format: %s (%d bits), ", + a_info.play.sample_rate, + a_info.play.channels, + mus_data_format_short_name(bsd_format_to_sndlib(a_info.play.encoding)), + a_info.play.precision); + pprint(audio_strbuf); + + amp = (float)(a_info.play.gain - AUDIO_MIN_GAIN) / (float)(AUDIO_MAX_GAIN - AUDIO_MIN_GAIN); + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "volume: %.3f %.3f (gain: %d, balance: %d)\n", + amp * (1.0 - ((float)(a_info.play.balance) / (float)(2 * AUDIO_MID_BALANCE))), + amp * ((float)(a_info.play.balance) / (float)(2 * AUDIO_MID_BALANCE)), + a_info.play.gain, a_info.play.balance); + pprint(audio_strbuf); + + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " record: srate: %d, chans: %d, format: %s (%d bits), ", + a_info.record.sample_rate, + a_info.record.channels, + mus_data_format_short_name(bsd_format_to_sndlib(a_info.record.encoding)), + a_info.record.precision); + pprint(audio_strbuf); + + amp = (float)(a_info.record.gain - AUDIO_MIN_GAIN) / (float)(AUDIO_MAX_GAIN - AUDIO_MIN_GAIN); + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "volume: %.3f %.3f (gain: %d, balance: %d)\n", + amp * (1.0 - ((float)(a_info.record.balance) / (float)(2 * AUDIO_MID_BALANCE))), + amp * ((float)(a_info.record.balance) / (float)(2 * AUDIO_MID_BALANCE)), + a_info.record.gain, a_info.record.balance); + pprint(audio_strbuf); + } + + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " available encodings:\n"); + pprint(audio_strbuf); + + for (i = 0; ; i++) + { + e_info.index = i; + err = ioctl(line, AUDIO_GETENC, &e_info); + if (err != 0) break; + mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " %s (%s, bits: %d)\n", + mus_data_format_short_name(bsd_format_to_sndlib(e_info.encoding)), + e_info.name, + e_info.precision); + pprint(audio_strbuf); + } + + close(line); + +#if 0 + /* I don't see anything useful in all this mixer data, so I'll omit it */ + fprintf(stderr,"/dev/mixer:\n"); + line = open("/dev/mixer", O_RDONLY | O_NDELAY); + if (line == -1) + return; + val = ioctl(line, AUDIO_GETDEV, &dev); + fprintf(stderr, "\n%d, name: %s, version: %s, config: %s\n", + val, dev.name, dev.version, dev.config); + for (i = 0; ; i++) + { + mdev.index = i; + val = ioctl(line, AUDIO_MIXER_DEVINFO, &mdev); + if (val != 0) break; + fprintf(stderr,"%d: name: %s ", i, mdev.label.name); + fprintf(stderr,"class: %d, type: %d, units: %s, chans: %d, delta: %d\n", + mdev.mixer_class, mdev.type, mdev.un.v.units.name, mdev.un.v.num_channels, mdev.un.v.delta); + mx.dev = i; + ioctl(line, AUDIO_MIXER_READ, &mx); + switch (mx.type) + { + case AUDIO_MIXER_CLASS: + fprintf(stderr, "mixer read: class type?\n"); + break; + case AUDIO_MIXER_ENUM: + fprintf(stderr, "mixer read: enum: %d\n", mx.un.ord); + break; + case AUDIO_MIXER_SET: + case AUDIO_MIXER_VALUE: + { + int j; + ml = mx.un.value; + fprintf(stderr, "mixer read: level: %d chans [", ml.num_channels); + for (j = 0; j < ml.num_channels; j++) + fprintf(stderr, "%d ", (int)(ml.level[j])); + fprintf(stderr, "]\n"); + } + break; + default: + fprintf(stderr, "mixer read: unknown type? %d\n", mx.type); + break; + } + } +#endif +} + +int mus_audio_mixer_read(int ur_dev, int field, int chan, float *val) +{ + int i, audio_fd, err, dev; + audio_info_t info; + bool ok = true; + + dev = MUS_AUDIO_DEVICE(ur_dev); + AUDIO_INITINFO(&info); + audio_fd = open("/dev/sound", O_RDONLY | O_NONBLOCK, 0); + if (audio_fd == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_READ, -1, + mus_format("can't open /dev/sound: %s", + strerror(errno))); + err = ioctl(audio_fd, AUDIO_GETINFO, &info); + if (err == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_READ, audio_fd, + mus_format("can't get dac info")); + + if (field == MUS_AUDIO_PORT) + { + val[0] = 1; + val[1] = MUS_AUDIO_MICROPHONE; + } + else + { + if (field == MUS_AUDIO_FORMAT) + { + audio_encoding_t e_info; + for (i = 0; ; i++) + { + e_info.index = i; + err = ioctl(audio_fd, AUDIO_GETENC, &e_info); + if (err != 0) break; + val[i + 1] = bsd_format_to_sndlib(e_info.encoding); + } + val[0] = i; + } + else + { + switch (dev) + { + case MUS_AUDIO_DEFAULT: + case MUS_AUDIO_DAC_OUT: + case MUS_AUDIO_SPEAKERS: + case MUS_AUDIO_LINE_OUT: + switch (field) + { + case MUS_AUDIO_AMP: + { + float amp; + amp = (float)(info.play.gain - AUDIO_MIN_GAIN) / (float)(AUDIO_MAX_GAIN - AUDIO_MIN_GAIN); + if (chan == 0) + val[0] = amp * (1.0 - ((float)(info.play.balance) / (float)(2 * AUDIO_MID_BALANCE))); + else val[0] = amp * ((float)(info.play.balance) / (float)(2 * AUDIO_MID_BALANCE)); + } + break; + case MUS_AUDIO_CHANNEL: + val[0] = 2; + break; + case MUS_AUDIO_SRATE: + val[0] = (float)info.play.sample_rate; + break; + default: + ok = false; + break; + } + break; + case MUS_AUDIO_MICROPHONE: + case MUS_AUDIO_LINE_IN: + case MUS_AUDIO_DUPLEX_DEFAULT: + case MUS_AUDIO_CD: + switch (field) + { + case MUS_AUDIO_AMP: + { + float amp; + amp = (float)(info.record.gain - AUDIO_MIN_GAIN) / (float)(AUDIO_MAX_GAIN - AUDIO_MIN_GAIN); + if (chan == 0) + val[0] = amp * (1.0 - ((float)(info.record.balance) / (float)(2 * AUDIO_MID_BALANCE))); + else val[0] = amp * ((float)(info.record.balance) / (float)(2 * AUDIO_MID_BALANCE)); + } + break; + case MUS_AUDIO_CHANNEL: + val[0] = 1; + break; + case MUS_AUDIO_SRATE: + val[0] = (float)(info.record.sample_rate); + break; + default: + ok = false; + break; + } + break; + default: + ok = false; + break; + } + } + } + if (!ok) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_READ, audio_fd, + mus_format("can't read %s field %d (%s)", + mus_audio_device_name(dev), + field, + mus_audio_device_name(field))); + return(mus_audio_close(audio_fd)); +} + +int mus_audio_mixer_write(int ur_dev, int field, int chan, float *val) +{ + audio_info_t info; + int dev, audio_fd, err; + bool ok = true; + + dev = MUS_AUDIO_DEVICE(ur_dev); + AUDIO_INITINFO(&info); + + audio_fd = open("/dev/sound", O_RDWR | O_NONBLOCK, 0); + if (audio_fd == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_WRITE, -1, + mus_format("can't open /dev/sound: %s", + strerror(errno))); + + err = ioctl(audio_fd, AUDIO_GETINFO, &info); + if (err == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_READ, audio_fd, + mus_format("can't get /dev/sound info")); + + switch (dev) + { + case MUS_AUDIO_DEFAULT: + case MUS_AUDIO_DAC_OUT: + case MUS_AUDIO_SPEAKERS: + case MUS_AUDIO_LINE_OUT: + switch (field) + { + case MUS_AUDIO_AMP: + info.play.balance = AUDIO_MID_BALANCE; + info.play.gain = AUDIO_MIN_GAIN + (int)((AUDIO_MAX_GAIN - AUDIO_MIN_GAIN) * val[0]); + break; + case MUS_AUDIO_CHANNEL: + info.play.channels = (int)val[0]; + break; + case MUS_AUDIO_SRATE: + info.play.sample_rate = (int)val[0]; + break; + default: + ok = false; + break; + } + break; + case MUS_AUDIO_MICROPHONE: + switch (field) + { + case MUS_AUDIO_AMP: + info.record.gain = AUDIO_MIN_GAIN + (int)((AUDIO_MAX_GAIN - AUDIO_MIN_GAIN) * val[0]); + info.record.balance = AUDIO_MID_BALANCE; + break; + case MUS_AUDIO_CHANNEL: + info.record.channels = (int)val[0]; + break; + case MUS_AUDIO_SRATE: + info.record.sample_rate = (int)val[0]; + break; + default: + ok = false; + break; + } + break; + case MUS_AUDIO_LINE_IN: + case MUS_AUDIO_DUPLEX_DEFAULT: + case MUS_AUDIO_CD: + switch (field) + { + case MUS_AUDIO_AMP: + info.record.balance = AUDIO_MID_BALANCE; + info.record.gain = AUDIO_MIN_GAIN + (int)((AUDIO_MAX_GAIN - AUDIO_MIN_GAIN) * val[0]); + break; + case MUS_AUDIO_CHANNEL: + info.record.channels = (int)val[0]; + break; + case MUS_AUDIO_SRATE: + info.record.sample_rate = (int)val[0]; + break; + default: + ok = false; + break; + } + break; + default: + ok = false; + break; + } + if (ok) + ioctl(audio_fd, AUDIO_SETINFO, &info); + else + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_WRITE, audio_fd, + mus_format("can't write %s field %d (%s)", + mus_audio_device_name(dev), + field, + mus_audio_device_name(field))); + return(mus_audio_close(audio_fd)); +} + +int mus_audio_open_input(int ur_dev, int srate, int chans, int format, int size) +{ + audio_info_t info; + int encode, bits, dev, audio_fd, err; + + dev = MUS_AUDIO_DEVICE(ur_dev); + encode = sndlib_format_to_bsd(format); + bits = 8 * mus_bytes_per_sample(format); + if (encode == AUDIO_ENCODING_NONE) + RETURN_ERROR_EXIT(MUS_AUDIO_FORMAT_NOT_AVAILABLE, -1, + mus_format("format %s not available for recording", + mus_data_format_name(format))); + + if (dev != MUS_AUDIO_DUPLEX_DEFAULT) + audio_fd = open("/dev/sound", O_RDONLY, 0); + else audio_fd = open("/dev/sound", O_RDWR, 0); + if (audio_fd == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_OPEN, -1, + mus_format("can't open /dev/sound: %s", + strerror(errno))); + + AUDIO_INITINFO(&info); + info.record.sample_rate = srate; + info.record.channels = chans; + info.record.precision = bits; + info.record.encoding = encode; + info.record.port = AUDIO_MICROPHONE; + err = ioctl(audio_fd, AUDIO_SETINFO, &info); + if (err == -1) + RETURN_ERROR_EXIT(MUS_AUDIO_CANT_WRITE, audio_fd, + mus_format("can't set up for recording")); + return(audio_fd); +} + +#endif + + + +/* ------------------------------- STUBS ----------------------------------------- */ + +#ifndef AUDIO_OK +static void describe_audio_state_1(void) {pprint("audio stubbed out");} +int mus_audio_open_output(int dev, int srate, int chans, int format, int size) {return(MUS_ERROR);} +int mus_audio_open_input(int dev, int srate, int chans, int format, int size) {return(MUS_ERROR);} +int mus_audio_write(int line, char *buf, int bytes) {return(MUS_ERROR);} +int mus_audio_close(int line) {return(MUS_ERROR);} +int mus_audio_read(int line, char *buf, int bytes) {return(MUS_ERROR);} +int mus_audio_mixer_read(int dev, int field, int chan, float *val) {return(MUS_ERROR);} +int mus_audio_mixer_write(int dev, int field, int chan, float *val) {return(MUS_ERROR);} +int mus_audio_initialize(void) {return(MUS_ERROR);} +int mus_audio_systems(void) {return(0);} +char *mus_audio_system_name(int system) {return("none");} +char *mus_audio_moniker(void) {return("no audio support");} +#endif + + + +static char *save_it = NULL; +static int print_it = 1; +static int save_it_len = 0; +static int save_it_loc = 0; + +static void pprint(char *str) +{ + int i, len; + if ((str) && (*str)) + { + if ((print_it) || (!(save_it))) + { + mus_print(str); + } + else + { + len = strlen(str); + if ((len + save_it_loc + 2) >= save_it_len) + { + save_it_len = (len + save_it_loc + 1024); + save_it = (char *)REALLOC(save_it, save_it_len * sizeof(char)); + } + for (i = 0; i < len; i++) + save_it[save_it_loc++] = str[i]; + save_it[save_it_loc] = 0; + } + } +} + +char *mus_audio_report(void) +{ + mus_audio_initialize(); + if (!(save_it)) + { + save_it_len = 1024; + save_it = (char *)CALLOC(save_it_len, sizeof(char)); + } + save_it_loc = 0; + print_it = 0; + if (!audio_strbuf) audio_strbuf = (char *)CALLOC(PRINT_BUFFER_SIZE, sizeof(char)); + describe_audio_state_1(); + return(save_it); +} + +void mus_audio_describe(void) +{ + mus_audio_initialize(); + print_it = 1; + if (!audio_strbuf) audio_strbuf = (char *)CALLOC(PRINT_BUFFER_SIZE, sizeof(char)); + describe_audio_state_1(); +} + +/* for CLM */ +void mus_reset_audio_c(void) +{ + audio_initialized = false; + save_it = NULL; + version_name = NULL; +#ifdef MUS_SUN + sun_vol_name = NULL; +#endif + save_it_len = 0; + audio_strbuf = NULL; +} + + +int mus_audio_compatible_format(int dev) +{ +#if HAVE_ALSA || HAVE_JACK + int err, i; + float val[32]; + int ival[32]; + err = mus_audio_mixer_read(dev, MUS_AUDIO_FORMAT, 32, val); + if (err != MUS_ERROR) + { + for (i = 0; i <= (int)(val[0]); i++) ival[i] = (int)(val[i]); + /* ^ this cast is vital! Memory clobbered otherwise in LinuxPPC */ + for (i = 1; i <= ival[0]; i++) + if (ival[i] == MUS_AUDIO_COMPATIBLE_FORMAT) + return(MUS_AUDIO_COMPATIBLE_FORMAT); + for (i = 1; i <= ival[0]; i++) + if ((ival[i] == MUS_BINT) || (ival[i] == MUS_LINT) || + (ival[i] == MUS_BFLOAT) || (ival[i] == MUS_LFLOAT) || + (ival[i] == MUS_BSHORT) || (ival[i] == MUS_LSHORT)) + return(ival[i]); + for (i = 1; i <= ival[0]; i++) + if ((ival[i] == MUS_MULAW) || (ival[i] == MUS_ALAW) || + (ival[i] == MUS_UBYTE) || (ival[i] == MUS_BYTE)) + return(ival[i]); + return(ival[1]); + } +#endif + return(MUS_AUDIO_COMPATIBLE_FORMAT); +} + + +/* next two added 17-Dec-02 for non-interleaved audio IO */ +static char *output_buffer = NULL; +static int output_buffer_size = 0; + +int mus_audio_write_buffers(int port, int frames, int chans, mus_sample_t **bufs, int output_format, bool clipped) +{ + int bytes; + bytes = chans * frames * mus_bytes_per_sample(output_format); + if (output_buffer_size < bytes) + { + if (output_buffer) free(output_buffer); + output_buffer = (char *)malloc(bytes); + output_buffer_size = bytes; + } + mus_file_write_buffer(output_format, 0, frames - 1, chans, bufs, output_buffer, clipped); + return(mus_audio_write(port, output_buffer, bytes)); +} + +static char *input_buffer = NULL; +static int input_buffer_size = 0; + +int mus_audio_read_buffers(int port, int frames, int chans, mus_sample_t **bufs, int input_format) +{ + int bytes; + bytes = chans * frames * mus_bytes_per_sample(input_format); + if (input_buffer_size < bytes) + { + if (input_buffer) free(input_buffer); + input_buffer = (char *)malloc(bytes); + input_buffer_size = bytes; + } + mus_audio_read(port, input_buffer, bytes); + return(mus_file_read_buffer(input_format, 0, chans, frames, bufs, input_buffer)); +} |