/*(GPL) --------------------------------------------------------------------------- audio.c - Public Audio Engine Interface --------------------------------------------------------------------------- * Written for SGI DMedia API by Masanao Izumo * Mostly rewritten by David Olofson , 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 #include #include #include #include #ifdef HAVE_OSS #include #include #include #ifdef OSS_USE_SOUNDCARD_H #include #else #include #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 # include # else # if HAVE_SYS_TIME_H # include # else # include # 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