openbsd-ports/audio/snack/files/jkAudIO_sndio.c
sebastia 9c3c841519 import snack: Tcl/Tk snack sound toolkit, and its python bindings
The Snack Sound Toolkit is designed to be used with a scripting language
such as Tcl/Tk or Python.  Snack has commands for basic sound handling,
such as playback, recording, file and socket I/O.  Snack also provides
primitives for sound visualization, e.g. waveforms and spectrograms.  It
was developed mainly to handle digital recordings of speech, but is just
as useful for general audio.  Snack has also successfully been applied
to other one-dimensional signals.

thanks to jakemsr@ for making my unsuccessful attempt to port it to sndio working.
many comments from stu@, landry@, ajacoutot@

finally OK ajacoutot@
2011-03-12 08:53:52 +00:00

983 lines
22 KiB
C

/*
* Copyright (C) 1997-2004 Kare Sjolander <kare@speech.kth.se>
* Copyright (C) 2010 Jacob Meuser <jakemsr@openbsd.org>
*
* This file is part of the Snack Sound Toolkit.
* The latest version can be found at http://www.speech.kth.se/snack/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "tcl.h"
#include "jkAudIO.h"
#include "jkSound.h"
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <glob.h>
#include <poll.h>
#include <errno.h>
#include <sndio.h>
/* for mixer functions */
#include <soundcard.h>
#define MIXER_NAME "/dev/mixer"
extern void Snack_WriteLog(char *s);
extern void Snack_WriteLogInt(char *s, int n);
#ifndef min
#define min(a,b) ((a)<(b)?(a):(b))
#define max(a,b) ((a)>(b)?(a):(b))
#endif
static int mfd = 0;
static struct MixerLink mixerLinks[SOUND_MIXER_NRDEVICES][2];
static int littleEndian = 0;
void
onmove_cb(void *addr, int delta)
{
ADesc *A = addr;
A->hardpos += delta * A->bytesPerSample * A->nChannels;
if (A->debug > 9) Snack_WriteLogInt(" Leave onmove_cb\n", delta);
}
int
SnackAudioOpen(ADesc *A, Tcl_Interp *interp, char *device, int mode, int freq,
int nchannels, int encoding)
{
struct sio_par par;
char cmode[8];
unsigned smode;
A->debug = 0;
if (A->debug > 1) Snack_WriteLog(" Enter SnackAudioOpen\n");
switch (mode) {
case RECORD:
snprintf(cmode, sizeof(cmode), "record");
smode = SIO_REC;
break;
case PLAY:
snprintf(cmode, sizeof(cmode), "play");
smode = SIO_PLAY;
break;
default:
Tcl_AppendResult(interp, "Invalid mode", NULL);
return TCL_ERROR;
break;
}
// We always want to use the default device
A->hdl = sio_open(NULL, smode, 0);
if (A->hdl == NULL) {
Tcl_AppendResult(interp, "Could not open sndio device for ", cmode, NULL);
return TCL_ERROR;
}
A->mode = mode;
sio_initpar(&par);
A->convert = 0;
switch (encoding) {
case LIN16:
par.le = littleEndian ? 1 : 0;
par.sig = 1;
par.bits = 16;
par.bps = 2;
break;
case LIN24:
par.le = littleEndian ? 1 : 0;
par.sig = 1;
par.bits = 24;
par.bps = 4;
break;
case ALAW:
par.le = littleEndian ? 1 : 0;
par.sig = 1;
par.bits = 16;
par.bps = 2;
A->convert = ALAW;
break;
case MULAW:
par.le = littleEndian ? 1 : 0;
par.sig = 1;
par.bits = 16;
par.bps = 2;
A->convert = MULAW;
break;
case LIN8OFFSET:
par.sig = 0;
par.bits = 8;
par.bps = 1;
break;
case LIN8:
par.sig = 1;
par.bits = 8;
par.bps = 1;
break;
}
switch (smode) {
case SIO_REC:
par.rchan = nchannels;
break;
case SIO_PLAY:
par.pchan = nchannels;
break;
}
par.rate = freq;
if (!sio_setpar(A->hdl, &par)) {
Tcl_AppendResult(interp, "Failed setting parameters.", NULL);
return TCL_ERROR;
}
if (!sio_getpar(A->hdl, &A->par)) {
Tcl_AppendResult(interp, "Failed getting parameters.", NULL);
return TCL_ERROR;
}
if (par.bits != A->par.bits ||
par.sig != A->par.sig ||
par.le != A->par.le ||
par.bps != A->par.bps) {
Tcl_AppendResult(interp, "Format not supported.", NULL);
return TCL_ERROR;
}
if ((smode == SIO_REC && par.rchan != A->par.rchan) ||
(smode == SIO_PLAY && par.pchan != A->par.pchan)) {
Tcl_AppendResult(interp, "Number of channels not supported.", NULL);
return TCL_ERROR;
}
if (par.rate != A->par.rate) {
Tcl_AppendResult(interp, "Sample frequency not supported.", NULL);
return TCL_ERROR;
}
A->hardpos = A->softpos = 0;
sio_onmove(A->hdl, onmove_cb, A);
if (!sio_start(A->hdl)) {
Tcl_AppendResult(interp, "Could not start sndio.", NULL);
return TCL_ERROR;
}
A->frag_size = A->par.round * A->par.bps *
(mode == PLAY ? A->par.pchan : A->par.rchan);
A->nChannels = smode == SIO_REC ? A->par.rchan : A->par.pchan;
A->bytesPerSample = A->par.bps;
A->warm = 0;
if (A->debug > 1) Snack_WriteLogInt(" Exit SnackAudioOpen", A->frag_size);
return TCL_OK;
}
int
SnackAudioClose(ADesc *A)
{
if (A->debug > 1) Snack_WriteLog(" Enter SnackAudioClose\n");
sio_close(A->hdl);
if (A->debug > 1) Snack_WriteLog(" Exit SnackAudioClose\n");
return(0);
}
long
SnackAudioPause(ADesc *A)
{
long res = SnackAudioPlayed(A);
if (A->debug > 9) Snack_WriteLog(" Enter SnackAudioPause\n");
/* nothing to do */
return(res);
}
void
SnackAudioResume(ADesc *A)
{
if (A->debug > 9) Snack_WriteLog(" Enter SnackAudioResume\n");
/* nothing to do */
}
void
SnackAudioFlush(ADesc *A)
{
if (A->debug > 9) Snack_WriteLog(" Enter SnackAudioFlush\n");
/* nothing to do */
}
static char zeroBlock[16];
void
SnackAudioPost(ADesc *A)
{
int n;
if (A->debug > 1) Snack_WriteLog(" Enter SnackAudioPost\n");
if (A->warm == 1) {
int i;
for (i = 0; i < A->frag_size / (A->bytesPerSample * A->nChannels); i++) {
n = sio_write(A->hdl, zeroBlock, A->bytesPerSample * A->nChannels);
A->softpos += n;
}
A->warm = 2;
}
if (A->debug > 1) Snack_WriteLog(" Exit SnackAudioPost\n");
}
int
SnackAudioRead(ADesc *A, void *buf, int nFrames)
{
int n = 2;
if (A->debug > 1) Snack_WriteLogInt(" Enter SnackAudioRead", nFrames);
while (nFrames > n * 2) n *= 2;
nFrames = n;
if (A->convert) {
int n = 0, i, res;
short s[2];
for (i = 0; i < nFrames * A->nChannels; i += A->nChannels) {
res = sio_read(A->hdl, &s, A->nChannels * sizeof(short));
A->softpos += res;
if (res <= 0) return(n / (A->bytesPerSample * A->nChannels));
if (A->convert == ALAW) {
((unsigned char *)buf)[i] = Snack_Lin2Alaw(s[0]);
if (A->nChannels == 2) {
((unsigned char *)buf)[i+1] = Snack_Lin2Alaw(s[1]);
}
} else {
((unsigned char *)buf)[i] = Snack_Lin2Mulaw(s[0]);
if (A->nChannels == 2) {
((unsigned char *)buf)[i+1] = Snack_Lin2Mulaw(s[1]);
}
}
n += res;
}
return(n / (A->bytesPerSample * A->nChannels));
} else {
int n = sio_read(A->hdl, (unsigned char *)buf,
nFrames * A->bytesPerSample * A->nChannels);
A->softpos += n;
if (n > 0) n /= (A->bytesPerSample * A->nChannels);
if (A->debug > 1) Snack_WriteLogInt(" Exit SnackAudioRead", n);
return(n);
}
}
int
SnackAudioWrite(ADesc *A, void *buf, int nFrames)
{
if (A->debug > 1) Snack_WriteLogInt(" Enter SnackAudioWrite\n", nFrames);
if (A->warm == 0) A->warm = 1;
if (A->convert) {
int n = 0, i, res;
short s;
for (i = 0; i < nFrames * A->nChannels; i++) {
if (A->convert == ALAW) {
s = Snack_Alaw2Lin(((unsigned char *)buf)[i]);
} else {
s = Snack_Mulaw2Lin(((unsigned char *)buf)[i]);
}
res = sio_write(A->hdl, &s, sizeof(short));
A->softpos += res;
if (res <= 0) return(n / (A->bytesPerSample * A->nChannels));
n += res;
}
return(n / (A->bytesPerSample * A->nChannels));
} else {
int n = sio_write(A->hdl, buf, nFrames * A->bytesPerSample * A->nChannels);
A->softpos += n;
if (A->debug > 9) Snack_WriteLogInt(" SnackAudioWrite wrote \n", n);
if (n > 0) n /= (A->bytesPerSample * A->nChannels);
return(n);
}
}
void
SnackSndioUpdatePos(ADesc *A)
{
struct pollfd pfd;
int n, revents;
n = sio_pollfd(A->hdl, &pfd, A->mode == PLAY ? POLLOUT : POLLIN);
while (poll(&pfd, n, 0) < 0 && errno == EINTR)
; /* nothing */
revents = sio_revents(A->hdl, &pfd);
}
int
SnackAudioReadable(ADesc *A)
{
int all, used, avail;
SnackSndioUpdatePos(A);
all = A->par.bufsz * A->bytesPerSample * A->nChannels;
used = A->hardpos < 0 ? 0 : A->hardpos - A->softpos;
avail = used < all ? used : all;
/* XXX this is what the OSS backend does, not sure why */
if (avail > 60*44100*4) avail = 0;
if (A->debug > 1) Snack_WriteLogInt(" Exit SnackAudioReadable", avail);
return (avail / (A->bytesPerSample * A->nChannels));
}
int
SnackAudioWriteable(ADesc *A)
{
int all, used, avail;
SnackSndioUpdatePos(A);
all = A->par.bufsz * A->bytesPerSample * A->nChannels;
used = A->hardpos < 0 ? A->softpos: A->softpos - A->hardpos;
avail = all - used;
if (A->debug > 9) Snack_WriteLogInt(" Leave SnackAudioWriteable\n", avail);
return (avail / (A->bytesPerSample * A->nChannels));
}
long
SnackAudioPlayed(ADesc *A)
{
long res;
res = A->softpos / (A->nChannels * A->bytesPerSample);
return(res);
}
void
SnackAudioInit()
{
union {
char c[sizeof(short)];
short s;
} order;
/* Compute the byte order of this machine. */
order.s = 1;
if (order.c[0] == 1) {
littleEndian = 1;
}
if ((mfd = open(MIXER_NAME, O_RDWR, 0)) == -1) {
fprintf(stderr, "Unable to open mixer %s\n", MIXER_NAME);
}
}
void
SnackAudioFree()
{
int i, j;
for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
for (j = 0; j < 2; j++) {
if (mixerLinks[i][j].mixer != NULL) {
ckfree(mixerLinks[i][j].mixer);
}
if (mixerLinks[i][j].mixerVar != NULL) {
ckfree(mixerLinks[i][j].mixerVar);
}
}
if (mixerLinks[i][0].jack != NULL) {
ckfree(mixerLinks[i][0].jack);
}
if (mixerLinks[i][0].jackVar != NULL) {
ckfree((char *)mixerLinks[i][0].jackVar);
}
}
close(mfd);
}
void
ASetRecGain(int gain)
{
int g = min(max(gain, 0), 100);
int recsrc = 0;
g = g * 256 + g;
ioctl(mfd, SOUND_MIXER_READ_RECSRC, &recsrc);
if (recsrc & SOUND_MASK_LINE) {
ioctl(mfd, SOUND_MIXER_WRITE_LINE, &g);
} else {
ioctl(mfd, SOUND_MIXER_WRITE_MIC, &g);
}
}
void
ASetPlayGain(int gain)
{
int g = min(max(gain, 0), 100);
int pcm_gain = 25700;
g = g * 256 + g;
ioctl(mfd, SOUND_MIXER_WRITE_VOLUME, &g);
ioctl(mfd, SOUND_MIXER_WRITE_PCM, &pcm_gain);
}
int
AGetRecGain()
{
int g = 0, left, right, recsrc = 0;
ioctl(mfd, SOUND_MIXER_READ_RECSRC, &recsrc);
if (recsrc & SOUND_MASK_LINE) {
ioctl(mfd, SOUND_MIXER_READ_LINE, &g);
} else {
ioctl(mfd, SOUND_MIXER_READ_MIC, &g);
}
left = g & 0xff;
right = (g & 0xff00) / 256;
g = (left + right) / 2;
return(g);
}
int
AGetPlayGain()
{
int g = 0, left, right;
ioctl(mfd, SOUND_MIXER_READ_VOLUME, &g);
left = g & 0xff;
right = (g & 0xff00) / 256;
g = (left + right) / 2;
return(g);
}
int
SnackAudioGetEncodings(char *device)
{
struct sio_hdl *hdl;
struct sio_cap cap;
// we always want to use the default device
hdl = sio_open(NULL, SIO_PLAY, 0);
if (hdl == NULL) {
}
return(LIN16);
}
void
SnackAudioGetRates(char *device, char *buf, int n)
{
int freq, pos=0, i;
int f[] = { 8000, 11025, 16000, 22050, 32000, 44100, 48000, 96000 };
for (i = 0; i < 8; i++) {
freq = f[i];
pos += sprintf(&buf[pos], "%d ", freq);
}
}
int
SnackAudioMaxNumberChannels(char *device)
{
return(2);
}
int
SnackAudioMinNumberChannels(char *device)
{
return(1);
}
void
SnackMixerGetInputJackLabels(char *buf, int n)
{
char *jackLabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
int i, recMask, pos = 0;
if (mfd != -1) {
ioctl(mfd, SOUND_MIXER_READ_RECMASK, &recMask);
for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
if ((1 << i) & recMask) {
pos += sprintf(&buf[pos], "%s", jackLabels[i]);
pos += sprintf(&buf[pos], " ");
}
}
} else {
buf[0] = '\0';
}
buf[n-1] = '\0';
}
void
SnackMixerGetOutputJackLabels(char *buf, int n)
{
buf[0] = '\0';
}
void
SnackMixerGetInputJack(char *buf, int n)
{
char *jackLabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
int i, recSrc = 0, pos = 0;
ioctl(mfd, SOUND_MIXER_READ_RECSRC, &recSrc);
for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
if ((1 << i) & recSrc) {
pos += sprintf(&buf[pos], "%s", jackLabels[i]);
while (isspace(buf[pos-1])) pos--;
pos += sprintf(&buf[pos], " ");
}
}
if(isspace(buf[pos-1])) pos--;
buf[pos] = '\0';
/*printf("SnackMixerGetInputJack %x, %s\n", recSrc, buf);*/
}
int
SnackMixerSetInputJack(Tcl_Interp *interp, char *jack, CONST84 char *status)
{
char *jackLabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
int i, recSrc = 0, currSrc;
for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
if (strncasecmp(jack, jackLabels[i], strlen(jack)) == 0) {
recSrc = 1 << i;
break;
}
}
ioctl(mfd, SOUND_MIXER_READ_RECSRC, &currSrc);
/* printf("SnackMixerSetInputJack1 %x %s %s\n", currSrc, jack, status);*/
if (strcmp(status, "1") == 0) {
recSrc |= currSrc;
} else {
recSrc = (currSrc & ~recSrc);
}
/* printf("SnackMixerSetInputJack2 %x\n", recSrc);*/
if (ioctl(mfd, SOUND_MIXER_WRITE_RECSRC, &recSrc) == -1) {
return 1;
} else {
ioctl(mfd, SOUND_MIXER_READ_RECSRC, &recSrc);
/* printf("SnackMixerSetInputJack3 %x\n", recSrc);*/
return 0;
}
return 1;
}
void
SnackMixerGetOutputJack(char *buf, int n)
{
buf[0] = '\0';
}
void
SnackMixerSetOutputJack(char *jack, char *status)
{
}
static int dontTrace = 0;
static char *
JackVarProc(ClientData clientData, Tcl_Interp *interp, CONST84 char *name1,
CONST84 char *name2, int flags)
{
MixerLink *mixLink = (MixerLink *) clientData;
char *jackLabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
int i, recSrc = 0, status = 0;
CONST84 char *stringValue;
Tcl_Obj *obj, *var;
if (dontTrace) return (char *) NULL;
ioctl(mfd, SOUND_MIXER_READ_RECSRC, &recSrc);
/*printf("JackVarProc %x %s %s\n", recSrc, name1, name2);*/
if (flags & TCL_TRACE_UNSETS) {
if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) {
for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
if (strncasecmp(mixLink->jack, jackLabels[i], strlen(mixLink->jack))
== 0) {
if ((1 << i) & recSrc) {
status = 1;
} else {
status = 0;
}
break;
}
}
obj = Tcl_NewIntObj(status);
var = Tcl_NewStringObj(mixLink->jackVar, -1);
Tcl_ObjSetVar2(interp, var, NULL, obj, TCL_GLOBAL_ONLY | TCL_PARSE_PART1);
Tcl_TraceVar(interp, mixLink->jackVar,
TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
JackVarProc, mixLink);
}
return (char *) NULL;
}
stringValue = Tcl_GetVar(interp, mixLink->jackVar, TCL_GLOBAL_ONLY);
if (stringValue != NULL) {
SnackMixerSetInputJack(interp, mixLink->jack, stringValue);
}
ioctl(mfd, SOUND_MIXER_READ_RECSRC, &recSrc);
/*printf("JackVarProc2 %x\n", recSrc);*/
dontTrace = 1;
for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
if (mixerLinks[i][0].jackVar != NULL) {
if ((1 << i) & recSrc) {
status = 1;
} else {
status = 0;
}
obj = Tcl_NewIntObj(status);
var = Tcl_NewStringObj(mixerLinks[i][0].jackVar, -1);
Tcl_ObjSetVar2(interp, var, NULL, obj, TCL_GLOBAL_ONLY |TCL_PARSE_PART1);
}
}
dontTrace = 0;
return (char *) NULL;
}
void
SnackMixerLinkJacks(Tcl_Interp *interp, char *jack, Tcl_Obj *var)
{
char *jackLabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
int i, recSrc = 0, status;
CONST84 char *value;
ioctl(mfd, SOUND_MIXER_READ_RECSRC, &recSrc);
for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
if (strncasecmp(jack, jackLabels[i], strlen(jack)) == 0) {
if ((1 << i) & recSrc) {
status = 1;
} else {
status = 0;
}
mixerLinks[i][0].jack = SnackStrDup(jack);
mixerLinks[i][0].jackVar = SnackStrDup(Tcl_GetStringFromObj(var, NULL));
value = Tcl_GetVar(interp, mixerLinks[i][0].jackVar, TCL_GLOBAL_ONLY);
if (value != NULL) {
SnackMixerSetInputJack(interp, mixerLinks[i][0].jack, value);
} else {
Tcl_Obj *obj = Tcl_NewIntObj(status);
Tcl_ObjSetVar2(interp, var, NULL, obj,
TCL_GLOBAL_ONLY | TCL_PARSE_PART1);
}
Tcl_TraceVar(interp, mixerLinks[i][0].jackVar,
TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
JackVarProc, (ClientData) &mixerLinks[i][0]);
break;
}
}
}
void
SnackMixerGetChannelLabels(char *line, char *buf, int n)
{
char *mixLabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
int i, devMask;
ioctl(mfd, SOUND_MIXER_READ_STEREODEVS, &devMask);
for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
if (strncasecmp(line, mixLabels[i], strlen(line)) == 0) {
if (devMask & (1 << i)) {
sprintf(buf, "Left Right");
} else {
sprintf(buf, "Mono");
}
break;
}
}
}
void
SnackMixerGetVolume(char *line, int channel, char *buf, int n)
{
char *mixLabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
int i, vol = 0, devMask, isStereo = 0, left, right;
buf[0] = '\0';
for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
if (strncasecmp(line, mixLabels[i], strlen(line)) == 0) {
ioctl(mfd, MIXER_READ(i), &vol);
ioctl(mfd, SOUND_MIXER_READ_STEREODEVS, &devMask);
if (devMask & (1 << i)) {
isStereo = 1;
}
break;
}
}
left = vol & 0xff;
right = (vol & 0xff00) >> 8;
if (isStereo) {
if (channel == 0) {
sprintf(buf, "%d", left);
} else if (channel == 1) {
sprintf(buf, "%d", right);
} else if (channel == -1) {
sprintf(buf, "%d", (left + right)/2);
}
} else {
sprintf(buf, "%d", left);
}
}
void
SnackMixerSetVolume(char *line, int channel, int volume)
{
char *mixLabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
int tmp = min(max(volume, 0), 100), i, oldVol = 0;
int vol = (tmp << 8) + tmp;
if (channel == 0) {
vol = tmp;
}
if (channel == 1) {
vol = tmp << 8;
}
for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
if (strncasecmp(line, mixLabels[i], strlen(line)) == 0) {
ioctl(mfd, MIXER_READ(i), &oldVol);
if (channel == 0) {
vol = (oldVol & 0xff00) | (vol & 0x00ff);
}
if (channel == 1) {
vol = (vol & 0xff00) | (oldVol & 0x00ff);
}
ioctl(mfd, MIXER_WRITE(i), &vol);
break;
}
}
}
static char *
VolumeVarProc(ClientData clientData, Tcl_Interp *interp, CONST84 char *name1,
CONST84 char *name2, int flags)
{
MixerLink *mixLink = (MixerLink *) clientData;
CONST84 char *stringValue;
if (flags & TCL_TRACE_UNSETS) {
if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) {
Tcl_Obj *obj, *var;
char tmp[VOLBUFSIZE];
SnackMixerGetVolume(mixLink->mixer, mixLink->channel, tmp, VOLBUFSIZE);
obj = Tcl_NewIntObj(atoi(tmp));
var = Tcl_NewStringObj(mixLink->mixerVar, -1);
Tcl_ObjSetVar2(interp, var, NULL, obj, TCL_GLOBAL_ONLY | TCL_PARSE_PART1);
Tcl_TraceVar(interp, mixLink->mixerVar,
TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
VolumeVarProc, mixLink);
}
return (char *) NULL;
}
stringValue = Tcl_GetVar(interp, mixLink->mixerVar, TCL_GLOBAL_ONLY);
if (stringValue != NULL) {
SnackMixerSetVolume(mixLink->mixer, mixLink->channel, atoi(stringValue));
}
return (char *) NULL;
}
void
SnackMixerLinkVolume(Tcl_Interp *interp, char *line, int n,
Tcl_Obj *CONST objv[])
{
char *mixLabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
int i, j, channel;
CONST84 char *value;
char tmp[VOLBUFSIZE];
for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
if (strncasecmp(line, mixLabels[i], strlen(line)) == 0) {
for (j = 0; j < n; j++) {
if (n == 1) {
channel = -1;
} else {
channel = j;
}
mixerLinks[i][j].mixer = SnackStrDup(line);
mixerLinks[i][j].mixerVar = SnackStrDup(Tcl_GetStringFromObj(objv[j+3],NULL));
mixerLinks[i][j].channel = j;
value = Tcl_GetVar(interp, mixerLinks[i][j].mixerVar, TCL_GLOBAL_ONLY);
if (value != NULL) {
SnackMixerSetVolume(line, channel, atoi(value));
} else {
Tcl_Obj *obj;
SnackMixerGetVolume(line, channel, tmp, VOLBUFSIZE);
obj = Tcl_NewIntObj(atoi(tmp));
Tcl_ObjSetVar2(interp, objv[j+3], NULL, obj,
TCL_GLOBAL_ONLY | TCL_PARSE_PART1);
}
Tcl_TraceVar(interp, mixerLinks[i][j].mixerVar,
TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
VolumeVarProc, (ClientData) &mixerLinks[i][j]);
}
}
}
}
void
SnackMixerUpdateVars(Tcl_Interp *interp)
{
int i, j, recSrc, status;
char tmp[VOLBUFSIZE];
Tcl_Obj *obj, *var;
ioctl(mfd, SOUND_MIXER_READ_RECSRC, &recSrc);
for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
for (j = 0; j < 2; j++) {
if (mixerLinks[i][j].mixerVar != NULL) {
SnackMixerGetVolume(mixerLinks[i][j].mixer, mixerLinks[i][j].channel,
tmp, VOLBUFSIZE);
obj = Tcl_NewIntObj(atoi(tmp));
var = Tcl_NewStringObj(mixerLinks[i][j].mixerVar, -1);
Tcl_ObjSetVar2(interp, var, NULL, obj, TCL_GLOBAL_ONLY|TCL_PARSE_PART1);
}
}
if (mixerLinks[i][0].jackVar != NULL) {
if ((1 << i) & recSrc) {
status = 1;
} else {
status = 0;
}
obj = Tcl_NewIntObj(status);
var = Tcl_NewStringObj(mixerLinks[i][0].jackVar, -1);
Tcl_ObjSetVar2(interp, var, NULL, obj, TCL_GLOBAL_ONLY | TCL_PARSE_PART1);
}
}
}
void
SnackMixerGetLineLabels(char *buf, int n)
{
char *mixLabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
int i, devMask, pos = 0;
if (mfd != -1) {
ioctl(mfd, SOUND_MIXER_READ_DEVMASK, &devMask);
for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
if ((1 << i) & devMask && pos < n-8) {
pos += sprintf(&buf[pos], "%s", mixLabels[i]);
pos += sprintf(&buf[pos], " ");
}
}
} else {
buf[0] = '\0';
}
buf[n-1] = '\0';
}
int
SnackGetOutputDevices(char **arr, int n)
{
return SnackGetInputDevices(arr, n);
}
int
SnackGetInputDevices(char **arr, int n)
{
int i, j = 0;
glob_t globt;
glob("/dev/audio*", 0, NULL, &globt);
//glob("/dev/audio*", GLOB_APPEND, NULL, &globt);
for (i = 0; i < globt.gl_pathc; i++) {
if (j < n) {
arr[j++] = (char *) SnackStrDup("default");
}
}
globfree(&globt);
return(1);
}
int
SnackGetMixerDevices(char **arr, int n)
{
int i, j = 0;
glob_t globt;
glob("/dev/mixer*", 0, NULL, &globt);
for (i = 0; i < globt.gl_pathc; i++) {
if (j < n) {
arr[j++] = (char *) SnackStrDup(globt.gl_pathv[i]);
}
}
globfree(&globt);
return(j);
}