Import existing source tree; original VCS history is no longer available. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1127 lines
23 KiB
C
1127 lines
23 KiB
C
/*(GPL)
|
|
---------------------------------------------------------------------------
|
|
audio.c - Public Audio Engine Interface
|
|
---------------------------------------------------------------------------
|
|
* Written for SGI DMedia API by Masanao Izumo <mo@goice.co.jp>
|
|
* Mostly rewritten by David Olofson <do@reologica.se>, 2001
|
|
*
|
|
* Copyright (C) 19??, Masanao Izumo
|
|
* Copyright (C) 2001-2003, 2007 David Olofson
|
|
*
|
|
* 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.1 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 "kobolog.h"
|
|
#include "a_globals.h"
|
|
#include "config.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <math.h>
|
|
|
|
#ifdef HAVE_OSS
|
|
#include <pthread.h>
|
|
#include <unistd.h>
|
|
#include <sys/ioctl.h>
|
|
#ifdef OSS_USE_SOUNDCARD_H
|
|
#include <soundcard.h>
|
|
#else
|
|
#include <sys/soundcard.h>
|
|
#endif
|
|
#include "audiodev.h"
|
|
#endif
|
|
|
|
#include "SDL.h"
|
|
#include "SDL_audio.h"
|
|
#include "logger.h"
|
|
|
|
#include "a_struct.h"
|
|
#include "a_commands.h"
|
|
#include "a_control.h"
|
|
#include "a_pitch.h"
|
|
#include "a_filters.h"
|
|
#include "a_limiter.h"
|
|
#include "a_midicon.h"
|
|
#include "a_midi.h"
|
|
#include "a_midifile.h"
|
|
#include "a_sequencer.h"
|
|
#include "a_agw.h"
|
|
#include "a_events.h"
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Engine stuff
|
|
----------------------------------------------------------*/
|
|
|
|
#ifdef HAVE_OSS
|
|
static audiodev_t adev;
|
|
static pthread_t engine_thread;
|
|
#endif
|
|
|
|
static int using_oss = 0;
|
|
static int using_midi = 0;
|
|
static int using_polling = 0;
|
|
|
|
int _audio_pause = 1;
|
|
|
|
/*
|
|
* Note that a_settings.buffersize is in stereo samples!
|
|
*/
|
|
static int *mixbuf = NULL;
|
|
static int *busbufs[AUDIO_MAX_BUSSES];
|
|
|
|
limiter_t limiter;
|
|
|
|
/* Silent buffer for plugins */
|
|
int *audio_silent_buffer = NULL;
|
|
|
|
#if defined(AUDIO_LOCKING) && defined(HAVE_OSS)
|
|
static pthread_mutex_t _audio_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
static int _audio_mutex_locked = 0;
|
|
#endif
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Timing/Sync code
|
|
----------------------------------------------------------*/
|
|
|
|
static int audio_last_callback = 0;
|
|
|
|
/* Running timer (milliseconds) */
|
|
static double audio_timer = 0.0;
|
|
|
|
/* Call this to nudge the current time towards 'ms'. */
|
|
static inline void sync_time(int ms)
|
|
{
|
|
if(labs((int)audio_timer - ms) > 1000)
|
|
audio_timer = (double)ms;
|
|
else
|
|
audio_timer = audio_timer * 0.95 + (double)ms * 0.05;
|
|
}
|
|
|
|
/* Advance time by 'frames' audio samples. */
|
|
static inline void advance_time(int frames)
|
|
{
|
|
audio_timer += (double)frames * 1000.0 / (double)a_settings.samplerate;
|
|
if(audio_timer >= 2147483648.0)
|
|
audio_timer -= 4294967296.0;
|
|
}
|
|
|
|
/* Get current audio time in ms, wrapping Sint32. */
|
|
static int get_time(void)
|
|
{
|
|
return (Sint32)audio_timer;
|
|
}
|
|
|
|
|
|
int audio_next_callback(void)
|
|
{
|
|
return get_time() + a_settings.buffersize * 1000 / a_settings.samplerate;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Engine code
|
|
----------------------------------------------------------*/
|
|
|
|
static int hold_until = 0;
|
|
|
|
/*
|
|
* This is where buffered asynchronous commands are
|
|
* processed. Some of them turn directly into timestamped
|
|
* events right here.
|
|
*/
|
|
static void _run_commands(void)
|
|
{
|
|
int d = hold_until - get_time();
|
|
if(labs(d) > 1000)
|
|
hold_until = get_time();
|
|
else if(d > 0)
|
|
return;
|
|
|
|
while(sfifo_used(&commands) >= sizeof(command_t))
|
|
{
|
|
command_t cmd;
|
|
if(sfifo_read(&commands, &cmd, (unsigned)sizeof(cmd)) < 0)
|
|
{
|
|
log_printf(ELOG, "audio.c: Engine failure!\n");
|
|
_audio_running = 0;
|
|
return;
|
|
}
|
|
switch(cmd.action)
|
|
{
|
|
case CMD_STOP:
|
|
DBG2(log_printf(D3LOG, "%d: CMD_STOP\n", get_time());)
|
|
(void)ce_stop(channeltab + cmd.cid, 0, cmd.tag, 32768);
|
|
break;
|
|
case CMD_STOP_ALL:
|
|
DBG2(log_printf(D3LOG, "%d: CMD_STOP_ALL\n", get_time());)
|
|
channel_stop_all();
|
|
break;
|
|
case CMD_PLAY:
|
|
DBG2(log_printf(D3LOG, "%d: CMD_PLAY\n", get_time());)
|
|
(void)ce_start(channeltab + cmd.cid, 0,
|
|
cmd.tag, cmd.arg1, cmd.arg2);
|
|
break;
|
|
case CMD_CCONTROL:
|
|
DBG2(log_printf(D3LOG, "%d: CMD_CCONTROL\n", get_time());)
|
|
(void)ce_control(channeltab + cmd.cid, 0,
|
|
cmd.tag, cmd.index, cmd.arg1);
|
|
break;
|
|
case CMD_GCONTROL:
|
|
DBG2(log_printf(D3LOG, "%d: CMD_GCONTROL\n", get_time());)
|
|
acc_group_set((unsigned)cmd.cid, cmd.index, cmd.arg1);
|
|
break;
|
|
case CMD_MCONTROL:
|
|
DBG2(log_printf(D3LOG, "%d: CMD_MCONTROL\n", get_time());)
|
|
bus_ctl_set((unsigned)cmd.cid, (unsigned)cmd.arg1,
|
|
cmd.index, cmd.arg2);
|
|
break;
|
|
case CMD_WAIT:
|
|
DBG2(log_printf(D3LOG, "%d: CMD_WAIT", get_time());)
|
|
hold_until = cmd.arg1;
|
|
DBG2(log_printf(D3LOG, " (Holding until %d)\n", hold_until);)
|
|
if(hold_until - get_time() > 0)
|
|
return;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#define CLIP_MIN -32700
|
|
#define CLIP_MAX 32700
|
|
|
|
#if 0
|
|
/*
|
|
* Convert with saturation
|
|
*/
|
|
static void _clip(Sint32 *inbuf, Sint16 *outbuf, unsigned frames)
|
|
{
|
|
unsigned i;
|
|
frames <<= 1;
|
|
for(i = 0; i < frames; i+=2)
|
|
{
|
|
int l = inbuf[i];
|
|
int r = inbuf[i+1];
|
|
if(l < CLIP_MIN)
|
|
outbuf[i] = CLIP_MIN;
|
|
else if(l > CLIP_MAX)
|
|
outbuf[i] = CLIP_MAX;
|
|
else
|
|
outbuf[i] = l;
|
|
if(r < CLIP_MIN)
|
|
outbuf[i+1] = CLIP_MIN;
|
|
else if(r > CLIP_MAX)
|
|
outbuf[i+1] = CLIP_MAX;
|
|
else
|
|
outbuf[i+1] = r;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Convert to Sint16; no saturation
|
|
*/
|
|
static void _s32tos16(Sint32 *inbuf, Sint16 *outbuf, unsigned frames)
|
|
{
|
|
unsigned i;
|
|
frames <<= 1;
|
|
for(i = 0; i < frames; i+=2)
|
|
{
|
|
int l = inbuf[i];
|
|
int r = inbuf[i+1];
|
|
outbuf[i] = (Sint16)l;
|
|
outbuf[i+1] = (Sint16)r;
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
static void _grab(Sint16 *buf, unsigned frames)
|
|
{
|
|
static int locktime = 0;
|
|
static int trig = 0;
|
|
unsigned i = 0;
|
|
if(!trig)
|
|
for(i = 0; i < frames-1; ++i)
|
|
{
|
|
int c1 = buf[i<<1] + buf[(i<<1)+1];
|
|
int c2 = buf[(i<<1)+2] + buf[(i<<1)+3];
|
|
if((c1 > 0) && (c2 < 0))
|
|
{
|
|
trig = 1;
|
|
break;
|
|
}
|
|
}
|
|
if(locktime - SDL_GetTicks() > 200)
|
|
trig = 1;
|
|
if(!trig)
|
|
return;
|
|
for( ; i < frames; ++i)
|
|
{
|
|
oscbufl[oscpos] = buf[i<<1];
|
|
oscbufr[oscpos] = buf[(i<<1)+1];
|
|
++oscpos;
|
|
if(oscpos >= oscframes)
|
|
{
|
|
oscpos = 0;
|
|
trig = 0;
|
|
locktime = SDL_GetTicks();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef PROFILE_AUDIO
|
|
# define DBGT(x) x
|
|
/*
|
|
* Replace with something else on non-x86 archs, or
|
|
* compilers that don't understand this. Replacement
|
|
* must have better resolution than 1 ms to be useful.
|
|
* Unit is not important as calculations are relative.
|
|
*/
|
|
# if defined(__GNUC__) && defined(i386)
|
|
inline int timestamp(void)
|
|
{
|
|
unsigned long long int x;
|
|
__asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
|
|
return x >> 8;
|
|
}
|
|
# else
|
|
# ifdef HAVE_GETTIMEOFDAY
|
|
# if TIME_WITH_SYS_TIME
|
|
# include <sys/time.h>
|
|
# include <time.h>
|
|
# else
|
|
# if HAVE_SYS_TIME_H
|
|
# include <sys/time.h>
|
|
# else
|
|
# include <time.h>
|
|
# endif
|
|
# endif
|
|
inline int timestamp(void)
|
|
{
|
|
struct timeval tv;
|
|
gettimeofday(&tv, NULL);
|
|
return tv.tv_sec * 1000000 + tv.tv_usec;
|
|
}
|
|
# else
|
|
inline int timestamp(void)
|
|
{
|
|
return SDL_GetTicks();
|
|
}
|
|
# endif
|
|
# endif
|
|
#else
|
|
# define DBGT(x)
|
|
#endif
|
|
|
|
#ifdef PROFILE_AUDIO
|
|
int audio_cpu_ticks = 333;
|
|
float audio_cpu_total = 0.0;
|
|
float audio_cpu_function[AUDIO_CPU_FUNCTIONS] = { 0,0,0,0,0,0,0,0,0,0 };
|
|
char audio_cpu_funcname[AUDIO_CPU_FUNCTIONS][20] = {
|
|
"Async. Commands",
|
|
"MIDI Input",
|
|
"Sequencer",
|
|
"MIDI -> Control",
|
|
"Channel/Patch Proc.",
|
|
"Voice Mixer",
|
|
"Clearing Master Buf",
|
|
"Bus & Mixdown",
|
|
"Limiter Effect",
|
|
"32 -> 16 bit Conv"
|
|
};
|
|
#endif
|
|
|
|
|
|
/*
|
|
* Engine callback for SDL_audio and OSS
|
|
*/
|
|
static void _audio_callback(void *ud, Uint8 *stream, int len)
|
|
{
|
|
#ifdef PROFILE_AUDIO
|
|
int i;
|
|
int t[AUDIO_CPU_FUNCTIONS+2];
|
|
static int avgt[AUDIO_CPU_FUNCTIONS+1];
|
|
static int avgtotal;
|
|
static int lastt = 0;
|
|
static int last_out = 0;
|
|
int adjust;
|
|
int ticks;
|
|
#define TS(x) t[x] = timestamp();
|
|
#else
|
|
#define TS(x)
|
|
#endif
|
|
unsigned remaining_frames;
|
|
Sint16 *outbuf = (Sint16 *)stream;
|
|
|
|
audio_last_callback = SDL_GetTicks();
|
|
sync_time(audio_last_callback);
|
|
|
|
if(_audio_pause)
|
|
{
|
|
memset(stream, 0, (unsigned)len);
|
|
advance_time(len / (sizeof(Sint16) * 2));
|
|
return;
|
|
}
|
|
|
|
remaining_frames = len / sizeof(Sint16) / 2;
|
|
while(remaining_frames)
|
|
{
|
|
unsigned frames;
|
|
if(remaining_frames > a_settings.buffersize)
|
|
frames = a_settings.buffersize;
|
|
else
|
|
frames = remaining_frames;
|
|
remaining_frames -= frames;
|
|
TS(0);
|
|
TS(1);
|
|
aev_client("_run_commands()");
|
|
_run_commands();
|
|
TS(2);
|
|
aev_client("midi_process()");
|
|
if(using_midi)
|
|
midi_process();
|
|
TS(3);
|
|
/* This belongs in the MIDI patch plugin. */
|
|
aev_client("sequencer_process()");
|
|
sequencer_process(frames);
|
|
TS(4);
|
|
aev_client("midicon_process()");
|
|
midicon_process(frames);
|
|
TS(5);
|
|
aev_client("channel_process_all()");
|
|
channel_process_all(frames);
|
|
TS(6);
|
|
aev_client("voice_process_all()");
|
|
voice_process_all(busbufs, frames);
|
|
TS(7);
|
|
memset(mixbuf, 0, frames * sizeof(int) * 2);
|
|
TS(8);
|
|
aev_client("bus_process_all()");
|
|
bus_process_all(busbufs, mixbuf, frames);
|
|
TS(9);
|
|
lims_process(&limiter, mixbuf, mixbuf, frames);
|
|
TS(10);
|
|
_s32tos16(mixbuf, outbuf, frames);
|
|
TS(11);
|
|
#ifdef DEBUG
|
|
_grab(outbuf, frames);
|
|
#endif
|
|
#ifdef PROFILE_AUDIO
|
|
adjust = t[1] - t[0];
|
|
|
|
avgt[0] += t[1] - lastt;
|
|
lastt = t[1];
|
|
if(!avgt[0])
|
|
avgt[0] = 1;
|
|
|
|
for(i = 1; i <= AUDIO_CPU_FUNCTIONS; ++i)
|
|
{
|
|
int tt = t[i+1] - t[i] - adjust;
|
|
if(tt > 0)
|
|
{
|
|
avgt[i] += tt;
|
|
avgtotal += tt;
|
|
}
|
|
}
|
|
ticks = SDL_GetTicks();
|
|
if((ticks-last_out) > audio_cpu_ticks)
|
|
{
|
|
for(i = 1; i <= AUDIO_CPU_FUNCTIONS; ++i)
|
|
audio_cpu_function[i-1] =
|
|
(float)avgt[i] * 100.0 / avgt[0];
|
|
audio_cpu_total = (float)avgtotal * 100.0 / avgt[0];
|
|
memset(avgt, 0, sizeof(avgt));
|
|
avgtotal = 0;
|
|
last_out = ticks;
|
|
}
|
|
#undef TS
|
|
#endif
|
|
outbuf += frames * 2;
|
|
aev_advance_timer(frames);
|
|
advance_time(frames);
|
|
}
|
|
aev_client("Unknown");
|
|
}
|
|
|
|
|
|
#ifdef HAVE_OSS
|
|
/*
|
|
* Engine thread for OSS
|
|
*/
|
|
int oss_outbufsize = 0;
|
|
Sint16 *oss_outbuf = NULL;
|
|
|
|
void *_audio_engine(void *dummy)
|
|
{
|
|
while(_audio_running)
|
|
{
|
|
# ifdef AUDIO_LOCKING
|
|
pthread_mutex_lock(&_audio_mutex);
|
|
# endif
|
|
_audio_callback(NULL, (Uint8 *)oss_outbuf, oss_outbufsize);
|
|
# ifdef AUDIO_LOCKING
|
|
pthread_mutex_unlock(&_audio_mutex);
|
|
# endif
|
|
write(adev.outfd, oss_outbuf, oss_outbufsize);
|
|
}
|
|
audiodev_close(&adev);
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
* "Driver" call for polling mode.
|
|
*/
|
|
void audio_run(void)
|
|
{
|
|
#ifdef HAVE_OSS
|
|
audio_buf_info info;
|
|
|
|
if(!(using_oss && using_polling && _audio_running))
|
|
return;
|
|
|
|
ioctl(adev.outfd, SNDCTL_DSP_GETOSPACE, &info);
|
|
while(info.bytes >= oss_outbufsize)
|
|
{
|
|
_audio_callback(NULL, (Uint8 *)oss_outbuf, oss_outbufsize);
|
|
write(adev.outfd, oss_outbuf, oss_outbufsize);
|
|
info.bytes -= oss_outbufsize;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
static int _start_oss_output()
|
|
{
|
|
#ifdef HAVE_OSS
|
|
audiodev_init(&adev);
|
|
adev.rate = a_settings.samplerate;
|
|
adev.fragmentsize = a_settings.output_buffersize *
|
|
sizeof(Sint16) * 2;
|
|
adev.fragments = OSS_FRAGMENTS;
|
|
_audio_running = 1;
|
|
if(audiodev_open(&adev) < 0)
|
|
{
|
|
log_printf(ELOG, "audio.c: Failed to open audio!\n");
|
|
_audio_running = 0;
|
|
return -2;
|
|
}
|
|
|
|
if(a_settings.output_buffersize > MAX_BUFFER_SIZE)
|
|
a_settings.buffersize = MAX_BUFFER_SIZE;
|
|
else
|
|
a_settings.buffersize = a_settings.output_buffersize;
|
|
|
|
free(mixbuf);
|
|
mixbuf = calloc(1, a_settings.buffersize * sizeof(int) * 2);
|
|
if(!mixbuf)
|
|
return -1;
|
|
|
|
|
|
oss_outbufsize = a_settings.output_buffersize * sizeof(Sint16) * 2;
|
|
oss_outbuf = calloc(1, oss_outbufsize);
|
|
if(!oss_outbuf)
|
|
{
|
|
audiodev_close(&adev);
|
|
_audio_running = 0;
|
|
log_printf(ELOG, "audio.c: Failed to allocate output buffer!\n");
|
|
return -4;
|
|
}
|
|
|
|
if(using_polling)
|
|
return 0; //That's it!
|
|
|
|
if(pthread_create(&engine_thread, NULL, _audio_engine, NULL))
|
|
{
|
|
free(oss_outbuf);
|
|
oss_outbuf = NULL;
|
|
audiodev_close(&adev);
|
|
log_printf(ELOG, "audio.c: Failed to start audio engine!\n");
|
|
return -3;
|
|
}
|
|
return 0;
|
|
#else
|
|
log_printf(ELOG, "OSS audio not compiled in!\n");
|
|
return -1;
|
|
#endif
|
|
}
|
|
|
|
|
|
static int _start_SDL_output(void)
|
|
{
|
|
SDL_AudioSpec as;
|
|
SDL_AudioSpec audiospec;
|
|
|
|
if(SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
|
|
return -2;
|
|
|
|
as.freq = a_settings.samplerate;
|
|
as.format = AUDIO_S16SYS;
|
|
as.channels = 2;
|
|
as.samples = (Uint16)a_settings.output_buffersize;
|
|
as.callback = _audio_callback;
|
|
if(SDL_OpenAudio(&as, &audiospec) < 0)
|
|
return -3;
|
|
|
|
if(audiospec.format != AUDIO_S16SYS)
|
|
{
|
|
log_printf(ELOG, "audio.c: ERROR: Only 16 bit output supported!\n");
|
|
SDL_CloseAudio();
|
|
return -4;
|
|
}
|
|
|
|
if(audiospec.channels != 2)
|
|
{
|
|
log_printf(ELOG, "audio.c: ERROR: Only stereo output supported!\n");
|
|
SDL_CloseAudio();
|
|
return -5;
|
|
}
|
|
|
|
if(a_settings.samplerate != audiospec.freq)
|
|
{
|
|
log_printf(ELOG, "audio.c: Warning: Requested fs=%d Hz, but"
|
|
"got %d Hz.\n", a_settings.samplerate,
|
|
audiospec.freq);
|
|
a_settings.samplerate = audiospec.freq;
|
|
}
|
|
|
|
if((unsigned)audiospec.samples != a_settings.output_buffersize)
|
|
{
|
|
log_printf(ELOG, "audio.c: Warning: Requested %u sample"
|
|
"buffer, but got %u samples.\n",
|
|
a_settings.output_buffersize, audiospec.samples);
|
|
a_settings.output_buffersize = audiospec.samples;
|
|
}
|
|
|
|
if(a_settings.output_buffersize > MAX_BUFFER_SIZE)
|
|
a_settings.buffersize = MAX_BUFFER_SIZE;
|
|
else
|
|
a_settings.buffersize = a_settings.output_buffersize;
|
|
|
|
free(mixbuf);
|
|
mixbuf = calloc(1, a_settings.buffersize * sizeof(int) * 2);
|
|
if(!mixbuf)
|
|
{
|
|
SDL_CloseAudio();
|
|
return -1;
|
|
}
|
|
|
|
_audio_running = 1;
|
|
SDL_PauseAudio(0);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int _mixing_open = 0;
|
|
|
|
static void _close_mixing(void)
|
|
{
|
|
int i;
|
|
if(!_mixing_open)
|
|
return;
|
|
|
|
/* KLUDGE */ lim_close(&limiter);
|
|
for(i = 0; i < AUDIO_MAX_BUSSES; ++i)
|
|
{
|
|
free(busbufs[i]);
|
|
busbufs[i] = NULL;
|
|
}
|
|
free(audio_silent_buffer);
|
|
audio_silent_buffer = NULL;
|
|
_mixing_open = 0;
|
|
}
|
|
|
|
static int _open_mixing(void)
|
|
{
|
|
unsigned bytes = a_settings.buffersize * sizeof(int) * 2;
|
|
unsigned i;
|
|
if(_mixing_open)
|
|
return 0;
|
|
|
|
for(i = 0; i < AUDIO_MAX_BUSSES; ++i)
|
|
{
|
|
busbufs[i] = calloc(1, bytes);
|
|
if(!busbufs[i])
|
|
{
|
|
_close_mixing();
|
|
return -1;
|
|
}
|
|
}
|
|
audio_silent_buffer = calloc(1, bytes);
|
|
if(!audio_silent_buffer)
|
|
{
|
|
_close_mixing();
|
|
return -2;
|
|
}
|
|
|
|
/* KLUDGE */ if(lim_open(&limiter, a_settings.samplerate) < 0)
|
|
/* KLUDGE */ {
|
|
/* KLUDGE */ _close_mixing();
|
|
/* KLUDGE */ return -3;
|
|
/* KLUDGE */ }
|
|
/* KLUDGE */ lim_control(&limiter, LIM_THRESHOLD, DEFAULT_LIM_THRESHOLD);
|
|
/* KLUDGE */ lim_control(&limiter, LIM_RELEASE, DEFAULT_LIM_RELEASE);
|
|
|
|
_mixing_open = 1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void _stop_output(void)
|
|
{
|
|
_audio_running = 0;
|
|
if(using_oss)
|
|
{
|
|
#ifdef HAVE_OSS
|
|
if(!using_polling)
|
|
{
|
|
# ifdef AUDIO_LOCKING
|
|
if(_audio_mutex_locked)
|
|
pthread_mutex_unlock(&_audio_mutex);
|
|
# endif
|
|
pthread_join(engine_thread, NULL);
|
|
# ifdef AUDIO_LOCKING
|
|
pthread_mutex_destroy(&_audio_mutex);
|
|
_audio_mutex_locked = 0;
|
|
# endif
|
|
}
|
|
free(oss_outbuf);
|
|
oss_outbuf = NULL;
|
|
#endif
|
|
}
|
|
else
|
|
SDL_CloseAudio();
|
|
free(mixbuf);
|
|
mixbuf = NULL;
|
|
}
|
|
|
|
|
|
#ifdef AUDIO_LOCKING
|
|
/*
|
|
* This sucks. The engine should *never* be locked!
|
|
*
|
|
* Using the API mostly results in single-reader/single-writer
|
|
* situations (the *API* is not guaranteed to be thread safe!),
|
|
* so we just need to do things in the right order.
|
|
*/
|
|
void audio_lock(void)
|
|
{
|
|
if(using_oss)
|
|
{
|
|
#ifdef HAVE_OSS
|
|
if(using_polling)
|
|
return;
|
|
if(!_audio_mutex_locked)
|
|
pthread_mutex_lock(&_audio_mutex);
|
|
++_audio_mutex_locked;
|
|
#endif
|
|
}
|
|
else
|
|
SDL_LockAudio();
|
|
}
|
|
|
|
|
|
void audio_unlock(void)
|
|
{
|
|
if(using_oss)
|
|
{
|
|
#ifdef HAVE_OSS
|
|
if(using_polling)
|
|
return;
|
|
if(_audio_mutex_locked)
|
|
{
|
|
--_audio_mutex_locked;
|
|
if(!_audio_mutex_locked)
|
|
pthread_mutex_unlock(&_audio_mutex);
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
SDL_UnlockAudio();
|
|
}
|
|
#endif
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Open/close code
|
|
----------------------------------------------------------*/
|
|
|
|
static int _wasinit = 0;
|
|
|
|
|
|
int audio_open(void)
|
|
{
|
|
if(_wasinit)
|
|
return 0;
|
|
|
|
aev_client("audio_open()");
|
|
|
|
audio_wave_open();
|
|
audio_patch_open();
|
|
audio_group_open();
|
|
|
|
/* NOTE: AGW will auto-initialize if used! */
|
|
|
|
_wasinit = 1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
int audio_start(int rate, int latency, int use_oss, int use_midi, int pollaudio)
|
|
{
|
|
int i, fragments;
|
|
if(audio_open() < 0)
|
|
return -100;
|
|
|
|
audio_stop();
|
|
|
|
aev_client("audio_start()");
|
|
|
|
a_settings.samplerate = rate;
|
|
using_oss = use_oss;
|
|
using_polling = pollaudio;
|
|
|
|
if(sfifo_init(&commands, sizeof(command_t) * MAX_COMMANDS) < 0)
|
|
{
|
|
log_printf(ELOG, "audio.c: Failed to set up audio engine!\n");
|
|
return -1;
|
|
}
|
|
|
|
if(ptab_init(65536) < 0)
|
|
{
|
|
log_printf(ELOG, "audio.c: Failed to set up pitch table!\n");
|
|
sfifo_close(&commands);
|
|
return -2;
|
|
}
|
|
|
|
|
|
audio_channel_open();
|
|
audio_voice_open();
|
|
audio_bus_open();
|
|
if(aev_open(AUDIO_MAX_VOICES * MAX_BUFFER_SIZE) < 0)
|
|
{
|
|
audio_stop();
|
|
return -1;
|
|
}
|
|
|
|
if(using_oss)
|
|
fragments = OSS_FRAGMENTS;
|
|
else
|
|
fragments = SDL_FRAGMENTS;
|
|
if(latency > 1000)
|
|
latency = 1000;
|
|
|
|
a_settings.output_buffersize = MIN_BUFFER_SIZE;
|
|
/* We use 707 instead of 1000 for correct rounding. */
|
|
while(fragments * a_settings.output_buffersize * 707 /
|
|
a_settings.samplerate < latency)
|
|
a_settings.output_buffersize <<= 1;
|
|
a_settings.output_buffersize >>= 1;
|
|
|
|
log_printf(DLOG, "audio.c: Requested latency %d ms ==>"
|
|
" buffer size: %d samples;"
|
|
" latency: %.2f ms (error: %.2f).\n",
|
|
latency,
|
|
a_settings.output_buffersize,
|
|
fragments * a_settings.output_buffersize * 1000.0 /
|
|
a_settings.samplerate,
|
|
fabs(latency - fragments *
|
|
a_settings.output_buffersize * 1000.0 /
|
|
a_settings.samplerate)
|
|
);
|
|
|
|
_audio_pause = 1; /* Don't touch anything yet! */
|
|
|
|
if(using_polling && !using_oss)
|
|
{
|
|
log_printf(ELOG, "WARNING: Overriding driver selection!"
|
|
" 'pollaudio' requires OSS.\n");
|
|
using_oss = 1;
|
|
}
|
|
|
|
if(using_oss)
|
|
i = _start_oss_output();
|
|
else
|
|
i = _start_SDL_output();
|
|
if(i < 0)
|
|
{
|
|
audio_stop();
|
|
return -6;
|
|
}
|
|
|
|
if(_open_mixing() < 0)
|
|
{
|
|
audio_stop();
|
|
return -5;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
oscbufl = calloc(1, OSCFRAMES*sizeof(int));
|
|
oscbufr = calloc(1, OSCFRAMES*sizeof(int));
|
|
oscframes = OSCFRAMES;
|
|
#endif
|
|
midicon_open((float)a_settings.samplerate, 16);
|
|
|
|
if(use_midi)
|
|
using_midi = midi_open(&midicon_midisock) >= 0;
|
|
else
|
|
using_midi = 0;
|
|
|
|
sequencer_open(&midicon_midisock, (float)a_settings.samplerate);
|
|
audio_wave_prepare(-1); /* Update "natural speeds" and stuff... */
|
|
aev_reset_timer(); /* Reset event system timer */
|
|
|
|
aev_client("Unknown");
|
|
_audio_pause = 0; /* GO! */
|
|
_wasinit = 1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
void audio_stop(void)
|
|
{
|
|
if(_audio_running)
|
|
{
|
|
log_printf(VLOG, "Stopping audio engine...\n");
|
|
_audio_pause = 1;
|
|
_stop_output();
|
|
}
|
|
|
|
_close_mixing();
|
|
sequencer_close();
|
|
if(using_midi)
|
|
midi_close();
|
|
midicon_close();
|
|
ptab_close();
|
|
sfifo_close(&commands);
|
|
#ifdef DEBUG
|
|
oscframes = 0;
|
|
free(oscbufl);
|
|
free(oscbufr);
|
|
oscbufl = NULL;
|
|
oscbufr = NULL;
|
|
#endif
|
|
audio_bus_close();
|
|
audio_voice_close();
|
|
audio_channel_close();
|
|
aev_close();
|
|
}
|
|
|
|
|
|
void audio_close(void)
|
|
{
|
|
if(!_wasinit)
|
|
return;
|
|
|
|
audio_stop();
|
|
|
|
agw_close();
|
|
audio_group_close();
|
|
audio_patch_close();
|
|
audio_wave_close();
|
|
|
|
_wasinit = 0;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Engine Control
|
|
----------------------------------------------------------*/
|
|
|
|
void audio_quality(audio_quality_t quality)
|
|
{
|
|
a_settings.quality = quality;
|
|
}
|
|
|
|
void audio_set_limiter(float thres, float rels)
|
|
{
|
|
int t, r;
|
|
t = (int)(32768.0 * thres);
|
|
if(t < 256)
|
|
t = 256;
|
|
r = (int)(rels * 65536.0);
|
|
lim_control(&limiter, LIM_THRESHOLD, t);
|
|
lim_control(&limiter, LIM_RELEASE, r);
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Engine Debugging Stuff
|
|
----------------------------------------------------------*/
|
|
|
|
#ifdef DEBUG
|
|
static void print_accbank(accbank_t *ctl, int is_group)
|
|
{
|
|
const char names[ACC_COUNT][8] = {
|
|
"GRP",
|
|
"PRI",
|
|
"PATCH",
|
|
|
|
"PBUS",
|
|
"SBUS",
|
|
|
|
"PAN",
|
|
"PITCH",
|
|
|
|
"VOL",
|
|
"SEND",
|
|
|
|
"MOD1",
|
|
"MOD2",
|
|
"MOD3",
|
|
|
|
"X",
|
|
"Y",
|
|
"Z"
|
|
};
|
|
int i;
|
|
if(is_group)
|
|
i = ACC_PAN;
|
|
else
|
|
i = 0;
|
|
for(; i < ACC_COUNT; ++i)
|
|
{
|
|
log_printf(DLOG, "%s=", names[i]);
|
|
if(ACC_IS_FIXEDPOINT(i))
|
|
log_printf(DLOG, "%.4g ", (double)((*ctl)[i]/65536.0));
|
|
else
|
|
log_printf(DLOG, "%d ", (*ctl)[i]);
|
|
}
|
|
}
|
|
|
|
static void print_vc(audio_voice_t *v)
|
|
{
|
|
const char names[VC_COUNT][8] = {
|
|
"WAVE",
|
|
"LOOP",
|
|
"PITCH",
|
|
|
|
"RETRIG",
|
|
"RANDTR",
|
|
|
|
"PBUS",
|
|
"SBUS"
|
|
};
|
|
const char inames[VIC_COUNT][8] = {
|
|
"LVOL",
|
|
"RVOL",
|
|
"LSEND",
|
|
"RSEND"
|
|
};
|
|
int i;
|
|
for(i = 0; i < VC_COUNT; ++i)
|
|
{
|
|
log_printf(DLOG, "%s=", names[i]);
|
|
switch(i)
|
|
{
|
|
case VC_PITCH:
|
|
case VC_RANDTRIG:
|
|
log_printf(DLOG, "%.4g ", (double)(v->c[i]/65536.0));
|
|
break;
|
|
default:
|
|
log_printf(DLOG, "%d ", v->c[i]);
|
|
break;
|
|
}
|
|
}
|
|
for(i = 0; i < VIC_COUNT; ++i)
|
|
{
|
|
log_printf(DLOG, "%s=", inames[i]);
|
|
log_printf(DLOG, "%.4g ", (double)(v->ic[i].v/(65536.0*128.0)));
|
|
}
|
|
}
|
|
|
|
static void print_voices(int channel)
|
|
{
|
|
int i;
|
|
for(i = 0; i < AUDIO_MAX_VOICES; ++i)
|
|
if(channel == -1 || (voicetab[i].channel ==
|
|
(channeltab + channel) &&
|
|
(voicetab[i].state != VS_STOPPED)) )
|
|
{
|
|
if((channel == -1) && (voicetab[i].state != VS_STOPPED))
|
|
log_printf(DLOG, " ==>");
|
|
else
|
|
log_printf(DLOG, " -");
|
|
log_printf(DLOG, "VOICE %.2d: ", i);
|
|
print_vc(&voicetab[i]);
|
|
log_printf(DLOG, "\n");
|
|
}
|
|
}
|
|
|
|
static void print_channels(int group)
|
|
{
|
|
int i;
|
|
for(i = 0; i < AUDIO_MAX_CHANNELS; ++i)
|
|
{
|
|
if(channeltab[i].ctl[ACC_GROUP] != group)
|
|
continue;
|
|
log_printf(DLOG, " -CHANNEL %.2d: ", i);
|
|
print_accbank(&channeltab[i].rctl, 0);
|
|
log_printf(DLOG, "\n");
|
|
log_printf(DLOG, " Transf.: ");
|
|
print_accbank(&channeltab[i].ctl, 0);
|
|
log_printf(DLOG, "\n");
|
|
print_voices(i);
|
|
}
|
|
}
|
|
|
|
static int group_in_use(int group)
|
|
{
|
|
int i;
|
|
for(i = 0; i < AUDIO_MAX_CHANNELS; ++i)
|
|
{
|
|
if(channeltab[i].ctl[ACC_GROUP] == group)
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void audio_print_info(void)
|
|
{
|
|
int i;
|
|
log_printf(DLOG, "--------------------------------------"
|
|
"--------------------------------------\n");
|
|
log_printf(DLOG, "Audio Engine Info:\n");
|
|
for(i = 0; i < AUDIO_MAX_GROUPS; ++i)
|
|
{
|
|
if(!group_in_use(i))
|
|
continue;
|
|
log_printf(DLOG, "-GROUP %.2d-----------------------------"
|
|
"--------------------------------------\n", i);
|
|
log_printf(DLOG, " ctl: ");
|
|
print_accbank(&grouptab[i].ctl, 1);
|
|
log_printf(DLOG, "\n def: ");
|
|
print_accbank(&grouptab[i].ctl, 1);
|
|
log_printf(DLOG, "\n");
|
|
print_channels(i);
|
|
}
|
|
log_printf(DLOG, "--------------------------------------"
|
|
"--------------------------------------\n");
|
|
#if 0
|
|
print_voices(-1);
|
|
log_printf(DLOG, "--------------------------------------"
|
|
"--------------------------------------\n");
|
|
#endif
|
|
}
|
|
#endif
|