13
0
livetrax/libs/fluidsynth/src/fluid_sys.c

1803 lines
50 KiB
C

/* FluidSynth - A Software Synthesizer
*
* Copyright (C) 2003 Peter Hanappe and others.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser 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 library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA
*/
#include "fluid_sys.h"
#if WITH_READLINE
#include <readline/readline.h>
#include <readline/history.h>
#endif
#ifdef DBUS_SUPPORT
#include "fluid_rtkit.h"
#endif
#if HAVE_PTHREAD_H && !defined(WIN32)
// Do not include pthread on windows. It includes winsock.h, which collides with ws2tcpip.h from fluid_sys.h
// It isn't need on Windows anyway.
#include <pthread.h>
#endif
/* WIN32 HACK - Flag used to differentiate between a file descriptor and a socket.
* Should work, so long as no SOCKET or file descriptor ends up with this bit set. - JG */
#ifdef _WIN32
#define FLUID_SOCKET_FLAG 0x40000000
#else
#define FLUID_SOCKET_FLAG 0x00000000
#define SOCKET_ERROR -1
#define INVALID_SOCKET -1
#endif
/* SCHED_FIFO priority for high priority timer threads */
#define FLUID_SYS_TIMER_HIGH_PRIO_LEVEL 10
typedef struct
{
fluid_thread_func_t func;
void *data;
int prio_level;
} fluid_thread_info_t;
struct _fluid_timer_t
{
long msec;
// Pointer to a function to be executed by the timer.
// This field is set to NULL once the timer is finished to indicate completion.
// This allows for timed waits, rather than waiting forever as fluid_timer_join() does.
fluid_timer_callback_t callback;
void *data;
fluid_thread_t *thread;
int cont;
int auto_destroy;
};
struct _fluid_server_socket_t
{
fluid_socket_t socket;
fluid_thread_t *thread;
int cont;
fluid_server_func_t func;
void *data;
};
static int fluid_istream_gets(fluid_istream_t in, char *buf, int len);
static fluid_log_function_t fluid_log_function[LAST_LOG_LEVEL] =
{
fluid_default_log_function,
fluid_default_log_function,
fluid_default_log_function,
fluid_default_log_function,
#ifdef DEBUG
fluid_default_log_function
#else
NULL
#endif
};
static void *fluid_log_user_data[LAST_LOG_LEVEL] = { NULL };
static const char fluid_libname[] = "fluidsynth";
/**
* Installs a new log function for a specified log level.
* @param level Log level to install handler for.
* @param fun Callback function handler to call for logged messages
* @param data User supplied data pointer to pass to log function
* @return The previously installed function.
*/
fluid_log_function_t
fluid_set_log_function(int level, fluid_log_function_t fun, void *data)
{
fluid_log_function_t old = NULL;
if((level >= 0) && (level < LAST_LOG_LEVEL))
{
old = fluid_log_function[level];
fluid_log_function[level] = fun;
fluid_log_user_data[level] = data;
}
return old;
}
/**
* Default log function which prints to the stderr.
* @param level Log level
* @param message Log message
* @param data User supplied data (not used)
*/
void
fluid_default_log_function(int level, const char *message, void *data)
{
FILE *out;
#if defined(WIN32)
out = stdout;
#else
out = stderr;
#endif
switch(level)
{
case FLUID_PANIC:
FLUID_FPRINTF(out, "%s: panic: %s\n", fluid_libname, message);
break;
case FLUID_ERR:
FLUID_FPRINTF(out, "%s: error: %s\n", fluid_libname, message);
break;
case FLUID_WARN:
FLUID_FPRINTF(out, "%s: warning: %s\n", fluid_libname, message);
break;
case FLUID_INFO:
FLUID_FPRINTF(out, "%s: %s\n", fluid_libname, message);
break;
case FLUID_DBG:
FLUID_FPRINTF(out, "%s: debug: %s\n", fluid_libname, message);
break;
default:
FLUID_FPRINTF(out, "%s: %s\n", fluid_libname, message);
break;
}
fflush(out);
}
/**
* Print a message to the log.
* @param level Log level (#fluid_log_level).
* @param fmt Printf style format string for log message
* @param ... Arguments for printf 'fmt' message string
* @return Always returns #FLUID_FAILED
*/
int
fluid_log(int level, const char *fmt, ...)
{
if((level >= 0) && (level < LAST_LOG_LEVEL))
{
fluid_log_function_t fun = fluid_log_function[level];
if(fun != NULL)
{
char errbuf[1024];
va_list args;
va_start(args, fmt);
FLUID_VSNPRINTF(errbuf, sizeof(errbuf), fmt, args);
va_end(args);
(*fun)(level, errbuf, fluid_log_user_data[level]);
}
}
return FLUID_FAILED;
}
void* fluid_alloc(size_t len)
{
void* ptr = malloc(len);
#if defined(DEBUG) && !defined(_MSC_VER)
// garbage initialize allocated memory for debug builds to ease reproducing
// bugs like 44453ff23281b3318abbe432fda90888c373022b .
//
// MSVC++ already garbage initializes allocated memory by itself (debug-heap).
//
// 0xCC because
// * it makes pointers reliably crash when dereferencing them,
// * floating points are still some valid but insanely huge negative number, and
// * if for whatever reason this allocated memory is executed, it'll trigger
// INT3 (...at least on x86)
if(ptr != NULL)
{
memset(ptr, 0xCC, len);
}
#endif
return ptr;
}
/**
* Open a file with a UTF-8 string, even in Windows
* @param filename The name of the file to open
* @param mode The mode to open the file in
*/
FILE *fluid_fopen(const char *filename, const char *mode)
{
#if defined(WIN32)
wchar_t *wpath = NULL, *wmode = NULL;
FILE *file = NULL;
int length;
if ((length = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, filename, -1, NULL, 0)) == 0)
{
FLUID_LOG(FLUID_ERR, "Unable to perform MultiByteToWideChar() conversion for filename '%s'. Error was: '%s'", filename, fluid_get_windows_error());
errno = EINVAL;
goto error_recovery;
}
wpath = FLUID_MALLOC(length * sizeof(wchar_t));
if (wpath == NULL)
{
FLUID_LOG(FLUID_PANIC, "Out of memory.");
errno = EINVAL;
goto error_recovery;
}
MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, filename, -1, wpath, length);
if ((length = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, mode, -1, NULL, 0)) == 0)
{
FLUID_LOG(FLUID_ERR, "Unable to perform MultiByteToWideChar() conversion for mode '%s'. Error was: '%s'", mode, fluid_get_windows_error());
errno = EINVAL;
goto error_recovery;
}
wmode = FLUID_MALLOC(length * sizeof(wchar_t));
if (wmode == NULL)
{
FLUID_LOG(FLUID_PANIC, "Out of memory.");
errno = EINVAL;
goto error_recovery;
}
MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, mode, -1, wmode, length);
file = _wfopen(wpath, wmode);
error_recovery:
FLUID_FREE(wpath);
FLUID_FREE(wmode);
return file;
#else
return fopen(filename, mode);
#endif
}
/**
* Wrapper for free() that satisfies at least C90 requirements.
*
* @param ptr Pointer to memory region that should be freed
*
* @note Only use this function when the API documentation explicitly says so. Otherwise use
* adequate \c delete_fluid_* functions.
*
* @warning Calling ::free() on memory that is advised to be freed with fluid_free() results in undefined behaviour!
* (cf.: "Potential Errors Passing CRT Objects Across DLL Boundaries" found in MS Docs)
*
* @since 2.0.7
*/
void fluid_free(void* ptr)
{
free(ptr);
}
/**
* An improved strtok, still trashes the input string, but is portable and
* thread safe. Also skips token chars at beginning of token string and never
* returns an empty token (will return NULL if source ends in token chars though).
* NOTE: NOT part of public API
* @internal
* @param str Pointer to a string pointer of source to tokenize. Pointer gets
* updated on each invocation to point to beginning of next token. Note that
* token char gets overwritten with a 0 byte. String pointer is set to NULL
* when final token is returned.
* @param delim String of delimiter chars.
* @return Pointer to the next token or NULL if no more tokens.
*/
char *fluid_strtok(char **str, const char *delim)
{
char *s, *token;
const char *d;
char c;
if(str == NULL || delim == NULL || !*delim)
{
FLUID_LOG(FLUID_ERR, "Null pointer");
return NULL;
}
s = *str;
if(!s)
{
return NULL; /* str points to a NULL pointer? (tokenize already ended) */
}
/* skip delimiter chars at beginning of token */
do
{
c = *s;
if(!c) /* end of source string? */
{
*str = NULL;
return NULL;
}
for(d = delim; *d; d++) /* is source char a token char? */
{
if(c == *d) /* token char match? */
{
s++; /* advance to next source char */
break;
}
}
}
while(*d); /* while token char match */
token = s; /* start of token found */
/* search for next token char or end of source string */
for(s = s + 1; *s; s++)
{
c = *s;
for(d = delim; *d; d++) /* is source char a token char? */
{
if(c == *d) /* token char match? */
{
*s = '\0'; /* overwrite token char with zero byte to terminate token */
*str = s + 1; /* update str to point to beginning of next token */
return token;
}
}
}
/* we get here only if source string ended */
*str = NULL;
return token;
}
/**
* Suspend the execution of the current thread for the specified amount of time.
* @param milliseconds to wait.
*/
void fluid_msleep(unsigned int msecs)
{
g_usleep(msecs * 1000);
}
/**
* Get time in milliseconds to be used in relative timing operations.
* @return Monotonic time in milliseconds.
*/
unsigned int fluid_curtime(void)
{
float now;
static float initial_time = 0;
if(initial_time == 0)
{
initial_time = (float)fluid_utime();
}
now = (float)fluid_utime();
return (unsigned int)((now - initial_time) / 1000.0f);
}
/**
* Get time in microseconds to be used in relative timing operations.
* @return time in microseconds.
* Note: When used for profiling we need high precision clock given
* by g_get_monotonic_time()if available (glib version >= 2.53.3).
* If glib version is too old and in the case of Windows the function
* uses high precision performance counter instead of g_getmonotic_time().
*/
double
fluid_utime(void)
{
double utime;
#if GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION >= 28
/* use high precision monotonic clock if available (g_monotonic_time().
* For Winfdows, if this clock is actually implemented as low prec. clock
* (i.e. in case glib is too old), high precision performance counter are
* used instead.
* see: https://bugzilla.gnome.org/show_bug.cgi?id=783340
*/
#if defined(WITH_PROFILING) && defined(WIN32) &&\
/* glib < 2.53.3 */\
(GLIB_MINOR_VERSION <= 53 && (GLIB_MINOR_VERSION < 53 || GLIB_MICRO_VERSION < 3))
/* use high precision performance counter. */
static LARGE_INTEGER freq_cache = {0, 0}; /* Performance Frequency */
LARGE_INTEGER perf_cpt;
if(! freq_cache.QuadPart)
{
QueryPerformanceFrequency(&freq_cache); /* Frequency value */
}
QueryPerformanceCounter(&perf_cpt); /* Counter value */
utime = perf_cpt.QuadPart * 1000000.0 / freq_cache.QuadPart; /* time in micros */
#else
utime = g_get_monotonic_time();
#endif
#else
/* fallback to less precise clock */
GTimeVal timeval;
g_get_current_time(&timeval);
utime = (timeval.tv_sec * 1000000.0 + timeval.tv_usec);
#endif
return utime;
}
#if defined(WIN32) /* Windoze specific stuff */
void
fluid_thread_self_set_prio(int prio_level)
{
if(prio_level > 0)
{
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
}
}
#elif defined(__OS2__) /* OS/2 specific stuff */
void
fluid_thread_self_set_prio(int prio_level)
{
if(prio_level > 0)
{
DosSetPriority(PRTYS_THREAD, PRTYC_REGULAR, PRTYD_MAXIMUM, 0);
}
}
#else /* POSIX stuff.. Nice POSIX.. Good POSIX. */
void
fluid_thread_self_set_prio(int prio_level)
{
struct sched_param priority;
if(prio_level > 0)
{
memset(&priority, 0, sizeof(priority));
priority.sched_priority = prio_level;
if(pthread_setschedparam(pthread_self(), SCHED_FIFO, &priority) == 0)
{
return;
}
#ifdef DBUS_SUPPORT
/* Try to gain high priority via rtkit */
if(fluid_rtkit_make_realtime(0, prio_level) == 0)
{
return;
}
#endif
FLUID_LOG(FLUID_WARN, "Failed to set thread to high priority");
}
}
#ifdef FPE_CHECK
/***************************************************************
*
* Floating point exceptions
*
* The floating point exception functions were taken from Ircam's
* jMax source code. https://www.ircam.fr/jmax
*
* FIXME: check in config for i386 machine
*
* Currently not used. I leave the code here in case we want to pick
* this up again some time later.
*/
/* Exception flags */
#define _FPU_STATUS_IE 0x001 /* Invalid Operation */
#define _FPU_STATUS_DE 0x002 /* Denormalized Operand */
#define _FPU_STATUS_ZE 0x004 /* Zero Divide */
#define _FPU_STATUS_OE 0x008 /* Overflow */
#define _FPU_STATUS_UE 0x010 /* Underflow */
#define _FPU_STATUS_PE 0x020 /* Precision */
#define _FPU_STATUS_SF 0x040 /* Stack Fault */
#define _FPU_STATUS_ES 0x080 /* Error Summary Status */
/* Macros for accessing the FPU status word. */
/* get the FPU status */
#define _FPU_GET_SW(sw) __asm__ ("fnstsw %0" : "=m" (*&sw))
/* clear the FPU status */
#define _FPU_CLR_SW() __asm__ ("fnclex" : : )
/* Purpose:
* Checks, if the floating point unit has produced an exception, print a message
* if so and clear the exception.
*/
unsigned int fluid_check_fpe_i386(char *explanation)
{
unsigned int s;
_FPU_GET_SW(s);
_FPU_CLR_SW();
s &= _FPU_STATUS_IE | _FPU_STATUS_DE | _FPU_STATUS_ZE | _FPU_STATUS_OE | _FPU_STATUS_UE;
if(s)
{
FLUID_LOG(FLUID_WARN, "FPE exception (before or in %s): %s%s%s%s%s", explanation,
(s & _FPU_STATUS_IE) ? "Invalid operation " : "",
(s & _FPU_STATUS_DE) ? "Denormal number " : "",
(s & _FPU_STATUS_ZE) ? "Zero divide " : "",
(s & _FPU_STATUS_OE) ? "Overflow " : "",
(s & _FPU_STATUS_UE) ? "Underflow " : "");
}
return s;
}
/* Purpose:
* Clear floating point exception.
*/
void fluid_clear_fpe_i386(void)
{
_FPU_CLR_SW();
}
#endif // ifdef FPE_CHECK
#endif // #else (its POSIX)
/***************************************************************
*
* Profiling (Linux, i586 only)
*
*/
#if WITH_PROFILING
/* Profiling interface between profiling command shell and audio rendering API
(FluidProfile_0004.pdf- 3.2.2).
Macros are in defined in fluid_sys.h.
*/
/*
-----------------------------------------------------------------------------
Shell task side | Profiling interface | Audio task side
-----------------------------------------------------------------------------
profiling | Internal | | | Audio
command <---> |<-- profling -->| Data |<--macros -->| <--> rendering
shell | API | | | API
*/
/* default parameters for shell command "prof_start" in fluid_sys.c */
unsigned short fluid_profile_notes = 0; /* number of generated notes */
/* preset bank:0 prog:16 (organ) */
unsigned char fluid_profile_bank = FLUID_PROFILE_DEFAULT_BANK;
unsigned char fluid_profile_prog = FLUID_PROFILE_DEFAULT_PROG;
/* print mode */
unsigned char fluid_profile_print = FLUID_PROFILE_DEFAULT_PRINT;
/* number of measures */
unsigned short fluid_profile_n_prof = FLUID_PROFILE_DEFAULT_N_PROF;
/* measure duration in ms */
unsigned short fluid_profile_dur = FLUID_PROFILE_DEFAULT_DURATION;
/* lock between multiple-shell */
fluid_atomic_int_t fluid_profile_lock = 0;
/**/
/*----------------------------------------------
Profiling Data
-----------------------------------------------*/
unsigned char fluid_profile_status = PROFILE_STOP; /* command and status */
unsigned int fluid_profile_end_ticks = 0; /* ending position (in ticks) */
fluid_profile_data_t fluid_profile_data[] = /* Data duration */
{
{"synth_write_* ------------>", 1e10, 0.0, 0.0, 0, 0, 0},
{"synth_one_block ---------->", 1e10, 0.0, 0.0, 0, 0, 0},
{"synth_one_block:clear ---->", 1e10, 0.0, 0.0, 0, 0, 0},
{"synth_one_block:one voice->", 1e10, 0.0, 0.0, 0, 0, 0},
{"synth_one_block:all voices>", 1e10, 0.0, 0.0, 0, 0, 0},
{"synth_one_block:reverb --->", 1e10, 0.0, 0.0, 0, 0, 0},
{"synth_one_block:chorus --->", 1e10, 0.0, 0.0, 0, 0, 0},
{"voice:note --------------->", 1e10, 0.0, 0.0, 0, 0, 0},
{"voice:release ------------>", 1e10, 0.0, 0.0, 0, 0, 0}
};
/*----------------------------------------------
Internal profiling API
-----------------------------------------------*/
/* logging profiling data (used on synthesizer instance deletion) */
void fluid_profiling_print(void)
{
int i;
printf("fluid_profiling_print\n");
FLUID_LOG(FLUID_INFO, "Estimated times: min/avg/max (micro seconds)");
for(i = 0; i < FLUID_PROFILE_NBR; i++)
{
if(fluid_profile_data[i].count > 0)
{
FLUID_LOG(FLUID_INFO, "%s: %.3f/%.3f/%.3f",
fluid_profile_data[i].description,
fluid_profile_data[i].min,
fluid_profile_data[i].total / fluid_profile_data[i].count,
fluid_profile_data[i].max);
}
else
{
FLUID_LOG(FLUID_DBG, "%s: no profiling available",
fluid_profile_data[i].description);
}
}
}
/* Macro that returns cpu load in percent (%)
* @dur: duration (micro second).
* @sample_rate: sample_rate used in audio driver (Hz).
* @n_amples: number of samples collected during 'dur' duration.
*/
#define fluid_profile_load(dur,sample_rate,n_samples) \
(dur * sample_rate / n_samples / 10000.0)
/* prints cpu loads only
*
* @param sample_rate the sample rate of audio output.
* @param out output stream device.
*
* ------------------------------------------------------------------------------
* Cpu loads(%) (sr: 44100 Hz, sp: 22.68 microsecond) and maximum voices
* ------------------------------------------------------------------------------
* nVoices| total(%)|voices(%)| reverb(%)|chorus(%)| voice(%)|estimated maxVoices
* -------|---------|---------|----------|---------|---------|-------------------
* 250| 41.544| 41.544| 0.000| 0.000| 0.163| 612
*/
static void fluid_profiling_print_load(double sample_rate, fluid_ostream_t out)
{
unsigned int n_voices; /* voices number */
static const char max_voices_not_available[] = " not available";
const char *pmax_voices;
char max_voices_available[20];
/* First computes data to be printed */
double total, voices, reverb, chorus, all_voices, voice;
/* voices number */
n_voices = fluid_profile_data[FLUID_PROF_ONE_BLOCK_VOICES].count ?
fluid_profile_data[FLUID_PROF_ONE_BLOCK_VOICES].n_voices /
fluid_profile_data[FLUID_PROF_ONE_BLOCK_VOICES].count : 0;
/* total load (%) */
total = fluid_profile_data[FLUID_PROF_WRITE].count ?
fluid_profile_load(fluid_profile_data[FLUID_PROF_WRITE].total, sample_rate,
fluid_profile_data[FLUID_PROF_WRITE].n_samples) : 0;
/* reverb load (%) */
reverb = fluid_profile_data[FLUID_PROF_ONE_BLOCK_REVERB].count ?
fluid_profile_load(fluid_profile_data[FLUID_PROF_ONE_BLOCK_REVERB].total,
sample_rate,
fluid_profile_data[FLUID_PROF_ONE_BLOCK_REVERB].n_samples) : 0;
/* chorus load (%) */
chorus = fluid_profile_data[FLUID_PROF_ONE_BLOCK_CHORUS].count ?
fluid_profile_load(fluid_profile_data[FLUID_PROF_ONE_BLOCK_CHORUS].total,
sample_rate,
fluid_profile_data[FLUID_PROF_ONE_BLOCK_CHORUS].n_samples) : 0;
/* total voices load: total - reverb - chorus (%) */
voices = total - reverb - chorus;
/* One voice load (%): all_voices / n_voices. */
all_voices = fluid_profile_data[FLUID_PROF_ONE_BLOCK_VOICES].count ?
fluid_profile_load(fluid_profile_data[FLUID_PROF_ONE_BLOCK_VOICES].total,
sample_rate,
fluid_profile_data[FLUID_PROF_ONE_BLOCK_VOICES].n_samples) : 0;
voice = n_voices ? all_voices / n_voices : 0;
/* estimated maximum voices number */
if(voice > 0)
{
FLUID_SNPRINTF(max_voices_available, sizeof(max_voices_available),
"%17d", (unsigned int)((100.0 - reverb - chorus) / voice));
pmax_voices = max_voices_available;
}
else
{
pmax_voices = max_voices_not_available;
}
/* Now prints data */
fluid_ostream_printf(out,
" ------------------------------------------------------------------------------\n");
fluid_ostream_printf(out,
" Cpu loads(%%) (sr:%6.0f Hz, sp:%6.2f microsecond) and maximum voices\n",
sample_rate, 1000000.0 / sample_rate);
fluid_ostream_printf(out,
" ------------------------------------------------------------------------------\n");
fluid_ostream_printf(out,
" nVoices| total(%%)|voices(%%)| reverb(%%)|chorus(%%)| voice(%%)|estimated maxVoices\n");
fluid_ostream_printf(out,
" -------|---------|---------|----------|---------|---------|-------------------\n");
fluid_ostream_printf(out,
"%8d|%9.3f|%9.3f|%10.3f|%9.3f|%9.3f|%s\n", n_voices, total, voices,
reverb, chorus, voice, pmax_voices);
}
/*
* prints profiling data (used by profile shell command: prof_start).
* The function is an internal profiling API between the "profile" command
* prof_start and audio rendering API (see FluidProfile_0004.pdf - 3.2.2).
*
* @param sample_rate the sample rate of audio output.
* @param out output stream device.
*
* When print mode is 1, the function prints all the information (see below).
* When print mode is 0, the function prints only the cpu loads.
*
* ------------------------------------------------------------------------------
* Duration(microsecond) and cpu loads(%) (sr: 44100 Hz, sp: 22.68 microsecond)
* ------------------------------------------------------------------------------
* Code under profiling |Voices| Duration (microsecond) | Load(%)
* | nbr| min| avg| max|
* ---------------------------|------|--------------------------------|----------
* synth_write_* ------------>| 250| 3.91| 2188.82| 3275.00| 41.544
* synth_one_block ---------->| 250| 1150.70| 2273.56| 3241.47| 41.100
* synth_one_block:clear ---->| 250| 3.07| 4.62| 61.18| 0.084
* synth_one_block:one voice->| 1| 4.19| 9.02| 1044.27| 0.163
* synth_one_block:all voices>| 250| 1138.41| 2259.11| 3217.73| 40.839
* synth_one_block:reverb --->| no profiling available
* synth_one_block:chorus --->| no profiling available
* voice:note --------------->| no profiling available
* voice:release ------------>| no profiling available
* ------------------------------------------------------------------------------
* Cpu loads(%) (sr: 44100 Hz, sp: 22.68 microsecond) and maximum voices
* ------------------------------------------------------------------------------
* nVoices| total(%)|voices(%)| reverb(%)|chorus(%)| voice(%)|estimated maxVoices
* -------|---------|---------|----------|---------|---------|-------------------
* 250| 41.544| 41.544| 0.000| 0.000| 0.163| 612
*/
void fluid_profiling_print_data(double sample_rate, fluid_ostream_t out)
{
int i;
if(fluid_profile_print)
{
/* print all details: Duration(microsecond) and cpu loads(%) */
fluid_ostream_printf(out,
" ------------------------------------------------------------------------------\n");
fluid_ostream_printf(out,
" Duration(microsecond) and cpu loads(%%) (sr:%6.0f Hz, sp:%6.2f microsecond)\n",
sample_rate, 1000000.0 / sample_rate);
fluid_ostream_printf(out,
" ------------------------------------------------------------------------------\n");
fluid_ostream_printf(out,
" Code under profiling |Voices| Duration (microsecond) | Load(%%)\n");
fluid_ostream_printf(out,
" | nbr| min| avg| max|\n");
fluid_ostream_printf(out,
" ---------------------------|------|--------------------------------|----------\n");
for(i = 0; i < FLUID_PROFILE_NBR; i++)
{
unsigned int count = fluid_profile_data[i].count;
if(count > 0)
{
/* data are available */
if(FLUID_PROF_WRITE <= i && i <= FLUID_PROF_ONE_BLOCK_CHORUS)
{
double load = fluid_profile_load(fluid_profile_data[i].total, sample_rate,
fluid_profile_data[i].n_samples);
fluid_ostream_printf(out, " %s|%6d|%10.2f|%10.2f|%10.2f|%8.3f\n",
fluid_profile_data[i].description, /* code under profiling */
fluid_profile_data[i].n_voices / count, /* voices number */
fluid_profile_data[i].min, /* minimum duration */
fluid_profile_data[i].total / count, /* average duration */
fluid_profile_data[i].max, /* maximum duration */
load); /* cpu load */
}
else
{
/* note and release duration */
fluid_ostream_printf(out, " %s|%6d|%10.0f|%10.0f|%10.0f|\n",
fluid_profile_data[i].description, /* code under profiling */
fluid_profile_data[i].n_voices / count,
fluid_profile_data[i].min, /* minimum duration */
fluid_profile_data[i].total / count, /* average duration */
fluid_profile_data[i].max); /* maximum duration */
}
}
else
{
/* data aren't available */
fluid_ostream_printf(out,
" %s| no profiling available\n", fluid_profile_data[i].description);
}
}
}
/* prints cpu loads only */
fluid_profiling_print_load(sample_rate, out);/* prints cpu loads */
}
/*
Returns true if the user cancels the current profiling measurement.
Actually this is implemented using the <ENTER> key. To add this functionality:
1) Adds #define FLUID_PROFILE_CANCEL in fluid_sys.h.
2) Adds the necessary code inside fluid_profile_is_cancel().
When FLUID_PROFILE_CANCEL is not defined, the function return FALSE.
*/
int fluid_profile_is_cancel_req(void)
{
#ifdef FLUID_PROFILE_CANCEL
#if defined(WIN32) /* Windows specific stuff */
/* Profile cancellation is supported for Windows */
/* returns TRUE if key <ENTER> is depressed */
return(GetAsyncKeyState(VK_RETURN) & 0x1);
#elif defined(__OS2__) /* OS/2 specific stuff */
/* Profile cancellation isn't yet supported for OS2 */
/* For OS2, replaces the following line with the function that returns
true when the keyboard key <ENTER> is depressed */
return FALSE; /* default value */
#else /* POSIX stuff */
/* Profile cancellation is supported for Linux */
/* returns true is <ENTER> is depressed */
{
/* Here select() is used to poll the standard input to see if an input
is ready. As the standard input is usually buffered, the user
needs to depress <ENTER> to set the input to a "ready" state.
*/
struct timeval tv;
fd_set fds; /* just one fds need to be polled */
tv.tv_sec = 0; /* Setting both values to 0, means a 0 timeout */
tv.tv_usec = 0;
FD_ZERO(&fds); /* reset fds */
FD_SET(STDIN_FILENO, &fds); /* sets fds to poll standard input only */
select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv); /* polling */
return (FD_ISSET(0, &fds)); /* returns true if standard input is ready */
}
#endif /* OS stuff */
#else /* FLUID_PROFILE_CANCEL not defined */
return FALSE; /* default value */
#endif /* FLUID_PROFILE_CANCEL */
}
/**
* Returns status used in shell command "prof_start".
* The function is an internal profiling API between the "profile" command
* prof_start and audio rendering API (see FluidProfile_0004.pdf - 3.2.2).
*
* @return status
* - PROFILE_READY profiling data are ready.
* - PROFILE_RUNNING, profiling data are still under acquisition.
* - PROFILE_CANCELED, acquisition has been cancelled by the user.
* - PROFILE_STOP, no acquisition in progress.
*
* When status is PROFILE_RUNNING, the caller can do passive waiting, or other
* work before recalling the function later.
*/
int fluid_profile_get_status(void)
{
/* Checks if user has requested to cancel the current measurement */
/* Cancellation must have precedence over other status */
if(fluid_profile_is_cancel_req())
{
fluid_profile_start_stop(0, 0); /* stops the measurement */
return PROFILE_CANCELED;
}
switch(fluid_profile_status)
{
case PROFILE_READY:
return PROFILE_READY; /* profiling data are ready */
case PROFILE_START:
return PROFILE_RUNNING;/* profiling data are under acquisition */
default:
return PROFILE_STOP;
}
}
/**
* Starts or stops profiling measurement.
* The function is an internal profiling API between the "profile" command
* prof_start and audio rendering API (see FluidProfile_0004.pdf - 3.2.2).
*
* @param end_tick end position of the measure (in ticks).
* - If end_tick is greater then 0, the function starts a measure if a measure
* isn't running. If a measure is already running, the function does nothing
* and returns.
* - If end_tick is 0, the function stops a measure.
* @param clear_data,
* - If clear_data is 0, the function clears fluid_profile_data before starting
* a measure, otherwise, the data from the started measure will be accumulated
* within fluid_profile_data.
*/
void fluid_profile_start_stop(unsigned int end_ticks, short clear_data)
{
if(end_ticks) /* This is a "start" request */
{
/* Checks if a measure is already running */
if(fluid_profile_status != PROFILE_START)
{
short i;
fluid_profile_end_ticks = end_ticks;
/* Clears profile data */
if(clear_data == 0)
for(i = 0; i < FLUID_PROFILE_NBR; i++)
{
fluid_profile_data[i].min = 1e10;/* min sets to max value */
fluid_profile_data[i].max = 0; /* maximum sets to min value */
fluid_profile_data[i].total = 0; /* total duration microsecond */
fluid_profile_data[i].count = 0; /* data count */
fluid_profile_data[i].n_voices = 0; /* voices number */
fluid_profile_data[i].n_samples = 0;/* audio samples number */
}
fluid_profile_status = PROFILE_START; /* starts profiling */
}
/* else do nothing when profiling is already started */
}
else /* This is a "stop" request */
{
/* forces the current running profile (if any) to stop */
fluid_profile_status = PROFILE_STOP; /* stops profiling */
}
}
#endif /* WITH_PROFILING */
/***************************************************************
*
* Threads
*
*/
#if OLD_GLIB_THREAD_API
/* Rather than inline this one, we just declare it as a function, to prevent
* GCC warning about inline failure. */
fluid_cond_t *
new_fluid_cond(void)
{
if(!g_thread_supported())
{
g_thread_init(NULL);
}
return g_cond_new();
}
#endif
static gpointer
fluid_thread_high_prio(gpointer data)
{
fluid_thread_info_t *info = data;
fluid_thread_self_set_prio(info->prio_level);
info->func(info->data);
FLUID_FREE(info);
return NULL;
}
/**
* Create a new thread.
* @param func Function to execute in new thread context
* @param data User defined data to pass to func
* @param prio_level Priority level. If greater than 0 then high priority scheduling will
* be used, with the given priority level (used by pthreads only). 0 uses normal scheduling.
* @param detach If TRUE, 'join' does not work and the thread destroys itself when finished.
* @return New thread pointer or NULL on error
*/
fluid_thread_t *
new_fluid_thread(const char *name, fluid_thread_func_t func, void *data, int prio_level, int detach)
{
GThread *thread;
fluid_thread_info_t *info = NULL;
GError *err = NULL;
g_return_val_if_fail(func != NULL, NULL);
#if OLD_GLIB_THREAD_API
/* Make sure g_thread_init has been called.
* Probably not a good idea in a shared library,
* but what can we do *and* remain backwards compatible? */
if(!g_thread_supported())
{
g_thread_init(NULL);
}
#endif
if(prio_level > 0)
{
info = FLUID_NEW(fluid_thread_info_t);
if(!info)
{
FLUID_LOG(FLUID_ERR, "Out of memory");
return NULL;
}
info->func = func;
info->data = data;
info->prio_level = prio_level;
#if NEW_GLIB_THREAD_API
thread = g_thread_try_new(name, fluid_thread_high_prio, info, &err);
#else
thread = g_thread_create(fluid_thread_high_prio, info, detach == FALSE, &err);
#endif
}
else
{
#if NEW_GLIB_THREAD_API
thread = g_thread_try_new(name, (GThreadFunc)func, data, &err);
#else
thread = g_thread_create((GThreadFunc)func, data, detach == FALSE, &err);
#endif
}
if(!thread)
{
FLUID_LOG(FLUID_ERR, "Failed to create the thread: %s",
fluid_gerror_message(err));
g_clear_error(&err);
FLUID_FREE(info);
return NULL;
}
#if NEW_GLIB_THREAD_API
if(detach)
{
g_thread_unref(thread); // Release thread reference, if caller wants to detach
}
#endif
return thread;
}
/**
* Frees data associated with a thread (does not actually stop thread).
* @param thread Thread to free
*/
void
delete_fluid_thread(fluid_thread_t *thread)
{
/* Threads free themselves when they quit, nothing to do */
}
/**
* Join a thread (wait for it to terminate).
* @param thread Thread to join
* @return FLUID_OK
*/
int
fluid_thread_join(fluid_thread_t *thread)
{
g_thread_join(thread);
return FLUID_OK;
}
static fluid_thread_return_t
fluid_timer_run(void *data)
{
fluid_timer_t *timer;
int count = 0;
int cont;
long start;
long delay;
timer = (fluid_timer_t *)data;
/* keep track of the start time for absolute positioning */
start = fluid_curtime();
while(timer->cont)
{
cont = (*timer->callback)(timer->data, fluid_curtime() - start);
count++;
if(!cont)
{
break;
}
/* to avoid incremental time errors, calculate the delay between
two callbacks bringing in the "absolute" time (count *
timer->msec) */
delay = (count * timer->msec) - (fluid_curtime() - start);
if(delay > 0)
{
fluid_msleep(delay);
}
}
FLUID_LOG(FLUID_DBG, "Timer thread finished");
timer->callback = NULL;
if(timer->auto_destroy)
{
FLUID_FREE(timer);
}
return FLUID_THREAD_RETURN_VALUE;
}
fluid_timer_t *
new_fluid_timer(int msec, fluid_timer_callback_t callback, void *data,
int new_thread, int auto_destroy, int high_priority)
{
fluid_timer_t *timer;
timer = FLUID_NEW(fluid_timer_t);
if(timer == NULL)
{
FLUID_LOG(FLUID_ERR, "Out of memory");
return NULL;
}
timer->msec = msec;
timer->callback = callback;
timer->data = data;
timer->cont = TRUE ;
timer->thread = NULL;
timer->auto_destroy = auto_destroy;
if(new_thread)
{
timer->thread = new_fluid_thread("timer", fluid_timer_run, timer, high_priority
? FLUID_SYS_TIMER_HIGH_PRIO_LEVEL : 0, FALSE);
if(!timer->thread)
{
FLUID_FREE(timer);
return NULL;
}
}
else
{
fluid_timer_run(timer); /* Run directly, instead of as a separate thread */
if(auto_destroy)
{
/* do NOT return freed memory */
return NULL;
}
}
return timer;
}
void
delete_fluid_timer(fluid_timer_t *timer)
{
int auto_destroy;
fluid_return_if_fail(timer != NULL);
auto_destroy = timer->auto_destroy;
timer->cont = 0;
fluid_timer_join(timer);
/* Shouldn't access timer now if auto_destroy enabled, since it has been destroyed */
if(!auto_destroy)
{
FLUID_FREE(timer);
}
}
int
fluid_timer_join(fluid_timer_t *timer)
{
int auto_destroy;
if(timer->thread)
{
auto_destroy = timer->auto_destroy;
fluid_thread_join(timer->thread);
if(!auto_destroy)
{
timer->thread = NULL;
}
}
return FLUID_OK;
}
int
fluid_timer_is_running(const fluid_timer_t *timer)
{
// for unit test usage only
return timer->callback != NULL;
}
long fluid_timer_get_interval(const fluid_timer_t * timer)
{
// for unit test usage only
return timer->msec;
}
/***************************************************************
*
* Sockets and I/O
*
*/
/**
* Get standard in stream handle.
* @return Standard in stream.
*/
static fluid_istream_t
fluid_get_stdin(void)
{
return STDIN_FILENO;
}
/**
* Get standard output stream handle.
* @return Standard out stream.
*/
static fluid_ostream_t
fluid_get_stdout(void)
{
return STDOUT_FILENO;
}
/**
* Read a line from an input stream.
* @return 0 if end-of-stream, -1 if error, non zero otherwise
*/
int
fluid_istream_readline(fluid_istream_t in, fluid_ostream_t out, char *prompt,
char *buf, int len)
{
#if WITH_READLINE
if(in == fluid_get_stdin())
{
char *line;
line = readline(prompt);
if(line == NULL)
{
return -1;
}
FLUID_SNPRINTF(buf, len, "%s", line);
buf[len - 1] = 0;
if(buf[0] != '\0')
{
add_history(buf);
}
free(line);
return 1;
}
else
#endif
{
fluid_ostream_printf(out, "%s", prompt);
return fluid_istream_gets(in, buf, len);
}
}
/**
* Reads a line from an input stream (socket).
* @param in The input socket
* @param buf Buffer to store data to
* @param len Maximum length to store to buf
* @return 1 if a line was read, 0 on end of stream, -1 on error
*/
static int
fluid_istream_gets(fluid_istream_t in, char *buf, int len)
{
char c;
int n;
buf[len - 1] = 0;
while(--len > 0)
{
#ifndef WIN32
n = read(in, &c, 1);
if(n == -1)
{
return -1;
}
#else
/* Handle read differently depending on if its a socket or file descriptor */
if(!(in & FLUID_SOCKET_FLAG))
{
// usually read() is supposed to return '\n' as last valid character of the user input
// when compiled with compatibility for WinXP however, read() may return 0 (EOF) rather than '\n'
// this would cause the shell to exit early
n = read(in, &c, 1);
if(n == -1)
{
return -1;
}
}
else
{
#ifdef NETWORK_SUPPORT
n = recv(in & ~FLUID_SOCKET_FLAG, &c, 1, 0);
if(n == SOCKET_ERROR)
#endif
{
return -1;
}
}
#endif
if(n == 0)
{
*buf = 0;
// return 1 if read from stdin, else 0, to fix early exit of shell
return (in == STDIN_FILENO);
}
if(c == '\n')
{
*buf = 0;
return 1;
}
/* Store all characters excluding CR */
if(c != '\r')
{
*buf++ = c;
}
}
return -1;
}
/**
* Send a printf style string with arguments to an output stream (socket).
* @param out Output stream
* @param format printf style format string
* @param ... Arguments for the printf format string
* @return Number of bytes written or -1 on error
*/
int
fluid_ostream_printf(fluid_ostream_t out, const char *format, ...)
{
char buf[4096];
va_list args;
int len;
va_start(args, format);
len = FLUID_VSNPRINTF(buf, 4095, format, args);
va_end(args);
if(len == 0)
{
return 0;
}
if(len < 0)
{
printf("fluid_ostream_printf: buffer overflow");
return -1;
}
buf[4095] = 0;
#ifndef WIN32
return write(out, buf, FLUID_STRLEN(buf));
#else
{
int retval;
/* Handle write differently depending on if its a socket or file descriptor */
if(!(out & FLUID_SOCKET_FLAG))
{
return write(out, buf, (unsigned int)FLUID_STRLEN(buf));
}
#ifdef NETWORK_SUPPORT
/* Socket */
retval = send(out & ~FLUID_SOCKET_FLAG, buf, (int)FLUID_STRLEN(buf), 0);
return retval != SOCKET_ERROR ? retval : -1;
#else
return -1;
#endif
}
#endif
}
#ifdef NETWORK_SUPPORT
int fluid_server_socket_join(fluid_server_socket_t *server_socket)
{
return fluid_thread_join(server_socket->thread);
}
static int fluid_socket_init(void)
{
#ifdef _WIN32
WSADATA wsaData;
int res = WSAStartup(MAKEWORD(2, 2), &wsaData);
if(res != 0)
{
FLUID_LOG(FLUID_ERR, "Server socket creation error: WSAStartup failed: %d", res);
return FLUID_FAILED;
}
#endif
return FLUID_OK;
}
static void fluid_socket_cleanup(void)
{
#ifdef _WIN32
WSACleanup();
#endif
}
static int fluid_socket_get_error(void)
{
#ifdef _WIN32
return (int)WSAGetLastError();
#else
return errno;
#endif
}
fluid_istream_t fluid_socket_get_istream(fluid_socket_t sock)
{
return sock | FLUID_SOCKET_FLAG;
}
fluid_ostream_t fluid_socket_get_ostream(fluid_socket_t sock)
{
return sock | FLUID_SOCKET_FLAG;
}
void fluid_socket_close(fluid_socket_t sock)
{
if(sock != INVALID_SOCKET)
{
#ifdef _WIN32
closesocket(sock);
#else
close(sock);
#endif
}
}
static fluid_thread_return_t fluid_server_socket_run(void *data)
{
fluid_server_socket_t *server_socket = (fluid_server_socket_t *)data;
fluid_socket_t client_socket;
#ifdef IPV6_SUPPORT
struct sockaddr_in6 addr;
#else
struct sockaddr_in addr;
#endif
#ifdef HAVE_INETNTOP
#ifdef IPV6_SUPPORT
char straddr[INET6_ADDRSTRLEN];
#else
char straddr[INET_ADDRSTRLEN];
#endif /* IPV6_SUPPORT */
#endif /* HAVE_INETNTOP */
socklen_t addrlen = sizeof(addr);
int r;
FLUID_MEMSET((char *)&addr, 0, sizeof(addr));
FLUID_LOG(FLUID_DBG, "Server listening for connections");
while(server_socket->cont)
{
client_socket = accept(server_socket->socket, (struct sockaddr *)&addr, &addrlen);
FLUID_LOG(FLUID_DBG, "New client connection");
if(client_socket == INVALID_SOCKET)
{
if(server_socket->cont)
{
FLUID_LOG(FLUID_ERR, "Failed to accept connection: %d", fluid_socket_get_error());
}
server_socket->cont = 0;
return FLUID_THREAD_RETURN_VALUE;
}
else
{
#ifdef HAVE_INETNTOP
#ifdef IPV6_SUPPORT
inet_ntop(AF_INET6, &addr.sin6_addr, straddr, sizeof(straddr));
#else
inet_ntop(AF_INET, &addr.sin_addr, straddr, sizeof(straddr));
#endif
r = server_socket->func(server_socket->data, client_socket,
straddr);
#else
r = server_socket->func(server_socket->data, client_socket,
inet_ntoa(addr.sin_addr));
#endif
if(r != 0)
{
fluid_socket_close(client_socket);
}
}
}
FLUID_LOG(FLUID_DBG, "Server closing");
return FLUID_THREAD_RETURN_VALUE;
}
fluid_server_socket_t *
new_fluid_server_socket(int port, fluid_server_func_t func, void *data)
{
fluid_server_socket_t *server_socket;
#ifdef IPV6_SUPPORT
struct sockaddr_in6 addr;
#else
struct sockaddr_in addr;
#endif
fluid_socket_t sock;
fluid_return_val_if_fail(func != NULL, NULL);
if(fluid_socket_init() != FLUID_OK)
{
return NULL;
}
#ifdef IPV6_SUPPORT
sock = socket(AF_INET6, SOCK_STREAM, 0);
if(sock == INVALID_SOCKET)
{
FLUID_LOG(FLUID_ERR, "Failed to create server socket: %d", fluid_socket_get_error());
fluid_socket_cleanup();
return NULL;
}
FLUID_MEMSET(&addr, 0, sizeof(addr));
addr.sin6_family = AF_INET6;
addr.sin6_port = htons((uint16_t)port);
addr.sin6_addr = in6addr_any;
#else
sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock == INVALID_SOCKET)
{
FLUID_LOG(FLUID_ERR, "Failed to create server socket: %d", fluid_socket_get_error());
fluid_socket_cleanup();
return NULL;
}
FLUID_MEMSET(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons((uint16_t)port);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
#endif
if(bind(sock, (const struct sockaddr *) &addr, sizeof(addr)) == SOCKET_ERROR)
{
FLUID_LOG(FLUID_ERR, "Failed to bind server socket: %d", fluid_socket_get_error());
fluid_socket_close(sock);
fluid_socket_cleanup();
return NULL;
}
if(listen(sock, SOMAXCONN) == SOCKET_ERROR)
{
FLUID_LOG(FLUID_ERR, "Failed to listen on server socket: %d", fluid_socket_get_error());
fluid_socket_close(sock);
fluid_socket_cleanup();
return NULL;
}
server_socket = FLUID_NEW(fluid_server_socket_t);
if(server_socket == NULL)
{
FLUID_LOG(FLUID_ERR, "Out of memory");
fluid_socket_close(sock);
fluid_socket_cleanup();
return NULL;
}
server_socket->socket = sock;
server_socket->func = func;
server_socket->data = data;
server_socket->cont = 1;
server_socket->thread = new_fluid_thread("server", fluid_server_socket_run, server_socket,
0, FALSE);
if(server_socket->thread == NULL)
{
FLUID_FREE(server_socket);
fluid_socket_close(sock);
fluid_socket_cleanup();
return NULL;
}
return server_socket;
}
void delete_fluid_server_socket(fluid_server_socket_t *server_socket)
{
fluid_return_if_fail(server_socket != NULL);
server_socket->cont = 0;
if(server_socket->socket != INVALID_SOCKET)
{
fluid_socket_close(server_socket->socket);
}
if(server_socket->thread)
{
fluid_thread_join(server_socket->thread);
delete_fluid_thread(server_socket->thread);
}
FLUID_FREE(server_socket);
// Should be called the same number of times as fluid_socket_init()
fluid_socket_cleanup();
}
#endif // NETWORK_SUPPORT
FILE* fluid_file_open(const char* path, const char** errMsg)
{
static const char ErrExist[] = "File does not exist.";
static const char ErrRegular[] = "File is not regular, refusing to open it.";
static const char ErrNull[] = "File does not exists or insufficient permissions to open it.";
FILE* handle = NULL;
if(!g_file_test(path, G_FILE_TEST_EXISTS))
{
if(errMsg != NULL)
{
*errMsg = ErrExist;
}
}
else if(!g_file_test(path, G_FILE_TEST_IS_REGULAR))
{
if(errMsg != NULL)
{
*errMsg = ErrRegular;
}
}
else if((handle = FLUID_FOPEN(path, "rb")) == NULL)
{
if(errMsg != NULL)
{
*errMsg = ErrNull;
}
}
return handle;
}
fluid_long_long_t fluid_file_tell(FILE* f)
{
#ifdef WIN32
// On Windows, long is only a 32 bit integer. Thus ftell() does not support to handle files >2GiB.
// We should use _ftelli64() in this case, however its availability depends on MS CRT and might not be
// availble on WindowsXP, Win98, etc.
//
// The web recommends to fallback to _telli64() in this case. However, it's return value differs from
// _ftelli64() on Win10: https://github.com/FluidSynth/fluidsynth/pull/629#issuecomment-602238436
//
// Thus, we use fgetpos().
fpos_t pos;
if(fgetpos(f, &pos) != 0)
{
return (fluid_long_long_t)-1L;
}
return pos;
#else
return ftell(f);
#endif
}
#ifdef WIN32
// not thread-safe!
char* fluid_get_windows_error(void)
{
static TCHAR err[1024];
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
GetLastError(),
MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
err,
sizeof(err)/sizeof(err[0]),
NULL);
#ifdef _UNICODE
static char ascii_err[sizeof(err)];
WideCharToMultiByte(CP_UTF8, 0, err, -1, ascii_err, sizeof(ascii_err)/sizeof(ascii_err[0]), 0, 0);
return ascii_err;
#else
return err;
#endif
}
#endif