Files
Konrad Beckmann 20d7fefaf9 Import mikmod
2021-08-02 02:19:41 +02:00

449 lines
10 KiB
C

/*** drv_nas.c --- MikMod sound library NAS output driver -*- C -*- */
/** $Id$ */
/*** Copyright (C) 2004 Ivan Shmakov */
/** This library is free software; you can redistribute it and/or modify
it under the terms of the GNU Library 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
Library General Public License for more details. */
/** You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA. */
/*** Code: */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "mikmod_internals.h"
#ifdef DRV_NAS
/* !!! HACK: avoid BOOL typedef clash */
#define BOOL NAS_BOOL
#include <audio/audiolib.h>
#undef BOOL /* HACK !!! */
#include <errno.h>
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#define MALLOC_VAR(x) ((x = (typeof (x))MikMod_malloc (sizeof (*(x)))))
#define MALLOC_ARY(x, y) ((x = (typeof (x))MikMod_malloc ((y) * sizeof (*(x)))))
/* slow network configuration by-default: */
#define NAS_DEFAULT_BUFFER 5000
#define NAS_DEFAULT_LOW_WM 75
struct my_info {
AuServer *server;
AuDeviceID device;
AuFlowID sole_flow;
AuEventHandlerRec *handler;
/* buffer */
struct {
char *ptr;
size_t size, pend, want;
} buf;
int stopped_p;
};
struct my_info *nas_info = NULL;
/* command line params.: */
static char *audioserver = NULL;
static long buffer_msec = NAS_DEFAULT_BUFFER;
static long buffer_low = NAS_DEFAULT_LOW_WM;
static AuDeviceID
nas_find_device (AuServer *s, int channels)
{
int i, max = AuServerNumDevices (s);
for (i = 0; i < max; i++) {
if ((AuDeviceKind (AuServerDevice (s, i))
== AuComponentKindPhysicalOutput)
&& AuDeviceNumTracks (AuServerDevice (s, i)) == channels)
return AuDeviceIdentifier (AuServerDevice (s, i));
}
return AuNone;
}
/*** Data Sending and Event Handling */
static void
nas_send_data (struct my_info *i)
{
AuServer *s = i->server;
AuFlowID flow = i->sole_flow;
char *ptr = i->buf.ptr;
const size_t bytes = i->buf.want;
const size_t pending = i->buf.pend;
i->buf.want = 0;
if (bytes < pending) {
AuWriteElement (s, flow, 0, bytes, ptr, AuFalse, NULL);
memmove (ptr, ptr + bytes, pending - bytes);
i->buf.pend -= bytes;
} else {
AuWriteElement (s, flow, 0, pending, ptr,
bytes > pending ? AuTrue : AuFalse,
NULL);
i->buf.pend = 0;
}
}
static AuBool
nas_event_handler (AuServer *s, AuEvent *ev, AuEventHandlerRec *handler)
{
struct my_info *i = (struct my_info *)(handler->data);
switch (ev->type) {
case AuEventTypeElementNotify:
{
AuElementNotifyEvent *eev = (AuElementNotifyEvent *)ev;
switch (eev->kind) {
case AuElementNotifyKindLowWater:
/* FIXME: am I right? */
i->buf.want = (size_t)(eev->num_bytes);
break;
case AuElementNotifyKindState:
switch (eev->cur_state) {
case AuStatePause:
i->stopped_p = 0;
if (eev->reason != AuReasonUser)
i->buf.want = (size_t)(eev->num_bytes);
break;
case AuStateStop:
i->stopped_p = 1;
break;
}
break;
}
}
break;
}
return AuTrue;
}
static void
nas_check_event (AuServer *s)
{
AuEvent e;
AuNextEvent (s, AuTrue, &e);
AuDispatchEvent (s, &e);
}
/*** Initialization */
static int
nas_init_flow (struct my_info *i)
{
AuServer *s = i->server;
AuDeviceID device;
AuFlowID flow;
AuElement elt[2];
unsigned char format;
const int rate = md_mixfreq;
const int chans = (md_mode & DMODE_STEREO) ? 2 : 1;
const AuInt32 samples = (AuInt32)1 * rate * buffer_msec / 1000;
const AuInt32 samples_low = samples * buffer_low / 100;
AuEventHandlerRec *handler;
if ((device = nas_find_device (s, chans)) == AuNone
|| (flow = AuCreateFlow (s, NULL)) == AuNone) {
_mm_errno = MMERR_OPENING_AUDIO;
return -1;
}
format = (md_mode & DMODE_16BITS)
? ((((char) *(short *)"x") == 'x') /* FIXME: too ugly? */
? AuFormatLinearSigned16LSB
: AuFormatLinearSigned16MSB)
: AuFormatLinearUnsigned8;
AuMakeElementImportClient (&elt[0], /* element */
rate, format, chans, /* format, etc. */
AuTrue, /* discard */
samples, samples_low, /* buf size */
0, NULL); /* actions */
AuMakeElementExportDevice (&elt[1], /* element */
0, device,
rate, AuUnlimitedSamples,
0, NULL); /* actions */
AuSetElements (s, flow, AuTrue, 2, elt, NULL);
/* FIXME: check for errors? */
handler
= AuRegisterEventHandler (s,
AuEventHandlerIDMask, 0, flow,
nas_event_handler, (AuPointer)i);
{
const size_t bufsz
= samples * chans * AuSizeofFormat (format);
if ((MALLOC_ARY (i->buf.ptr, bufsz)) == NULL)
return -1;
i->buf.size = bufsz;
i->buf.pend = i->buf.want = 0;
}
i->device = device;
i->sole_flow = flow;
i->handler = handler;
i->stopped_p = 1;
return 0;
}
static int
nas_init_server (struct my_info *i)
{
AuServer *s;
if (!(s = AuOpenServer (audioserver, 0, NULL, 0, NULL, NULL))) {
_mm_errno = MMERR_OPENING_AUDIO;
return -1;
}
i->server = s;
if (nas_init_flow (i) < 0) {
/* _mm_errno should be set in nas_init_flow */
AuCloseServer (s);
return -1;
}
return 0;
}
static int
NAS_init (void)
{
struct my_info *i;
/* FIXME: assert here? */
if (nas_info != NULL) {
_mm_errno = MMERR_OPENING_AUDIO;
return 1;
}
if (MALLOC_VAR (i) == NULL)
return 1;
if (nas_init_server (i) < 0
|| VC_Init () != 0) {
MikMod_free (i);
return 1;
}
nas_info = i;
return 0;
}
/*** Deinitialization & Reset */
static void
NAS_exit (void)
{
struct my_info *i = nas_info;
AuServer *s;
if (i == NULL)
return;
VC_Exit ();
s = i->server;
AuUnregisterEventHandler (s, i->handler);
AuDestroyFlow (s, i->sole_flow, NULL);
AuCloseServer (s);
MikMod_free (i->buf.ptr);
MikMod_free (i);
nas_info = NULL;
}
static int
NAS_reset (void)
{
NAS_exit ();
return NAS_init ();
}
/*** Flow Control */
static int
NAS_flow_start (void)
{
struct my_info *i = nas_info;
i->buf.pend = i->buf.want = 0;
AuStartFlow (i->server, i->sole_flow, NULL);
while (i->stopped_p)
nas_check_event (i->server);
if (VC_PlayStart () != 0)
return 1;
return 0;
}
static void
NAS_flow_stop (void)
{
struct my_info *i = nas_info;
VC_PlayStop ();
while (!i->stopped_p) {
nas_check_event (i->server);
if (i->buf.want > 0)
nas_send_data (i);
}
i->buf.pend = i->buf.want = 0;
}
static void
NAS_flow_pause (void)
{
struct my_info *i = nas_info;
AuPauseFlow (i->server, i->sole_flow, NULL);
/* nas_check_event (i->server); */
}
/*** Update routine */
static void
NAS_update (void)
{
struct my_info *i = nas_info;
const size_t bufsz = i->buf.size;
if (i->stopped_p) {
/* underrun? */
AuStartFlow (i->server, i->sole_flow, NULL);
while (i->stopped_p)
nas_check_event (i->server);
}
if (i->buf.pend < bufsz) {
size_t bytes
= (size_t)VC_WriteBytes ((SBYTE *)(i->buf.ptr + i->buf.pend),
(ULONG)(i->buf.size - i->buf.pend));
i->buf.pend += bytes;
}
while ((i->buf.want == 0) && !i->stopped_p)
nas_check_event (i->server);
if (!i->stopped_p)
nas_send_data (i);
}
/*** Miscellaneous API routines */
static void
NAS_cmd_line (const CHAR *s)
{
CHAR *p;
if ((p = MD_GetAtom ("audioserver", s, 0))) {
MikMod_free (audioserver);
audioserver = p;
}
if ((p = MD_GetAtom ("buffer", s, 0))) {
long v = atol (p);
MikMod_free (p);
buffer_msec = (v < 50 || v > 10000) ? NAS_DEFAULT_BUFFER : v;
}
if ((p = MD_GetAtom ("buffer_low", s, 0))) {
long v = atol (p);
MikMod_free (p);
buffer_low = (v < 10 || v > 90) ? NAS_DEFAULT_LOW_WM : v;
}
}
static BOOL
NAS_there_p (void)
{
AuServer *s;
if (!(s = AuOpenServer (NULL, 0, NULL, 0, NULL, NULL)))
return 0;
AuCloseServer (s);
return 1;
}
/*** MDRIVER record */
MIKMODAPI MDRIVER drv_nas = {
NULL, /* next */
"Network Audio System", /* Name */
"Network Audio System driver v0.1", /* Version */
0, 255, /* NB: 0 hardware voices? */
"nas", /* Alias */
"audioserver:t::NAS server name\n"
"buffer:r:50:10000:Audio buffer size, ms\n"
"buffer_low:r:10:90:Audio buffer low water mark, percents\n",
NAS_cmd_line, /* CommandLine */
NAS_there_p, /* IsPresent */
VC_SampleLoad, /* SampleLoad */
VC_SampleUnload, /* SampleUnload */
VC_SampleSpace, /* FreeSampleSpace */
VC_SampleLength, /* RealSampleLength */
NAS_init, /* Init */
NAS_exit, /* Exit */
NAS_reset, /* Reset */
VC_SetNumVoices, /* SetNumVoices */
NAS_flow_start, /* PlayStart */
NAS_flow_stop, /* PlayStop */
NAS_update, /* Update */
NAS_flow_pause, /* Pause */
VC_VoiceSetVolume, /* VoiceSetVolume */
VC_VoiceGetVolume, /* VoiceGetVolume */
VC_VoiceSetFrequency, /* VoiceSetFrequency */
VC_VoiceGetFrequency, /* VoiceGetFrequency */
VC_VoiceSetPanning, /* VoiceSetPanning */
VC_VoiceGetPanning, /* VoiceGetPanning */
VC_VoicePlay, /* VoicePlay */
VC_VoiceStop, /* VoiceStop */
VC_VoiceStopped, /* VoiceStopped */
VC_VoiceGetPosition, /* VoiceGetPosition */
VC_VoiceRealVolume /* VoiceRealVolume */
};
#else /* DRV_NAS */
MISSING(drv_nas);
#endif /* DRV_NAS */
/*** Emacs stuff */
/** Local variables: */
/** fill-column: 72 */
/** indent-tabs-mode: nil */
/** mode: outline-minor */
/** outline-regexp: "[/]\\*\\*\\*" */
/** End: */
/*** drv_nas.c ends here */