Import libfluidsynth into the Ardour codebase
[ardour.git] / libs / fluidsynth / src / fluid_sys.c
diff --git a/libs/fluidsynth/src/fluid_sys.c b/libs/fluidsynth/src/fluid_sys.c
new file mode 100644 (file)
index 0000000..600b04e
--- /dev/null
@@ -0,0 +1,1296 @@
+/* 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 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 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
+ * 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., 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
+
+/* 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 */
+#define WIN32_SOCKET_FLAG       0x40000000
+
+/* 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;
+  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 char fluid_errbuf[512];  /* buffer for error message */
+
+static fluid_log_function_t fluid_log_function[LAST_LOG_LEVEL];
+static void* fluid_log_user_data[LAST_LOG_LEVEL];
+static int fluid_log_initialized = 0;
+
+static char* fluid_libname = "fluidsynth";
+
+
+void fluid_sys_config()
+{
+  fluid_log_config();
+}
+
+
+unsigned int fluid_debug_flags = 0;
+
+#if DEBUG
+/*
+ * fluid_debug
+ */
+int fluid_debug(int level, char * fmt, ...)
+{
+  if (fluid_debug_flags & level) {
+    fluid_log_function_t fun;
+    va_list args;
+
+    va_start (args, fmt);
+    vsnprintf(fluid_errbuf, sizeof (fluid_errbuf), fmt, args);
+    va_end (args);
+
+    fun = fluid_log_function[FLUID_DBG];
+    if (fun != NULL) {
+      (*fun)(level, fluid_errbuf, fluid_log_user_data[FLUID_DBG]);
+    }
+  }
+  return 0;
+}
+#endif
+
+/**
+ * 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, char* message, void* data)
+{
+  FILE* out;
+
+#if defined(WIN32)
+  out = stdout;
+#else
+  out = stderr;
+#endif
+
+  if (fluid_log_initialized == 0) {
+    fluid_log_config();
+  }
+
+  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:
+#if DEBUG
+    FLUID_FPRINTF(out, "%s: debug: %s\n", fluid_libname, message);
+#endif
+    break;
+  default:
+    FLUID_FPRINTF(out, "%s: %s\n", fluid_libname, message);
+    break;
+  }
+  fflush(out);
+}
+
+/*
+ * fluid_init_log
+ */
+void
+fluid_log_config(void)
+{
+  if (fluid_log_initialized == 0) {
+
+    fluid_log_initialized = 1;
+
+    if (fluid_log_function[FLUID_PANIC] == NULL) {
+      fluid_set_log_function(FLUID_PANIC, fluid_default_log_function, NULL);
+    }
+
+    if (fluid_log_function[FLUID_ERR] == NULL) {
+      fluid_set_log_function(FLUID_ERR, fluid_default_log_function, NULL);
+    }
+
+    if (fluid_log_function[FLUID_WARN] == NULL) {
+      fluid_set_log_function(FLUID_WARN, fluid_default_log_function, NULL);
+    }
+
+    if (fluid_log_function[FLUID_INFO] == NULL) {
+      fluid_set_log_function(FLUID_INFO, fluid_default_log_function, NULL);
+    }
+
+    if (fluid_log_function[FLUID_DBG] == NULL) {
+      fluid_set_log_function(FLUID_DBG, fluid_default_log_function, NULL);
+    }
+  }
+}
+
+/**
+ * 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, ...)
+{
+  fluid_log_function_t fun = NULL;
+
+  va_list args;
+  va_start (args, fmt);
+  vsnprintf(fluid_errbuf, sizeof (fluid_errbuf), fmt, args);
+  va_end (args);
+
+  if ((level >= 0) && (level < LAST_LOG_LEVEL)) {
+    fun = fluid_log_function[level];
+    if (fun != NULL) {
+      (*fun)(level, fluid_errbuf, fluid_log_user_data[level]);
+    }
+  }
+  return FLUID_FAILED;
+}
+
+/**
+ * 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 get's 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, char *delim)
+{
+  char *s, *d, *token;
+  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;
+}
+
+/*
+ * fluid_error
+ */
+char*
+fluid_error()
+{
+  return fluid_errbuf;
+}
+
+/**
+ * Check if a file is a MIDI file.
+ * @param filename Path to the file to check
+ * @return TRUE if it could be a MIDI file, FALSE otherwise
+ *
+ * The current implementation only checks for the "MThd" header in the file.
+ * It is useful only to distinguish between SoundFont and MIDI files.
+ */
+int
+fluid_is_midifile(const char *filename)
+{
+  FILE* fp = fopen(filename, "rb");
+  char id[4];
+
+  if (fp == NULL) {
+    return 0;
+  }
+  if (fread((void*) id, 1, 4, fp) != 4) {
+    fclose(fp);
+    return 0;
+  }
+  fclose(fp);
+
+  return strncmp(id, "MThd", 4) == 0;
+}
+
+/**
+ * Check if a file is a SoundFont file.
+ * @param filename Path to the file to check
+ * @return TRUE if it could be a SoundFont, FALSE otherwise
+ *
+ * The current implementation only checks for the "RIFF" header in the file.
+ * It is useful only to distinguish between SoundFont and MIDI files.
+ */
+int
+fluid_is_soundfont(const char *filename)
+{
+  FILE* fp = fopen(filename, "rb");
+  char id[4];
+
+  if (fp == NULL) {
+    return 0;
+  }
+  if (fread((void*) id, 1, 4, fp) != 4) {
+    fclose(fp);
+    return 0;
+  }
+  fclose(fp);
+
+  return strncmp(id, "RIFF", 4) == 0;
+}
+
+/**
+ * Get time in milliseconds to be used in relative timing operations.
+ * @return Unix time in milliseconds.
+ */
+unsigned int fluid_curtime(void)
+{
+  static glong initial_seconds = 0;
+  GTimeVal timeval;
+
+  if (initial_seconds == 0) {
+    g_get_current_time (&timeval);
+    initial_seconds = timeval.tv_sec;
+  }
+
+  g_get_current_time (&timeval);
+
+  return (unsigned int)((timeval.tv_sec - initial_seconds) * 1000.0 + timeval.tv_usec / 1000.0);
+}
+
+/**
+ * Get time in microseconds to be used in relative timing operations.
+ * @return Unix time in microseconds.
+ */
+double
+fluid_utime (void)
+{
+  GTimeVal timeval;
+
+  g_get_current_time (&timeval);
+
+  return (timeval.tv_sec * 1000000.0 + timeval.tv_usec);
+}
+
+
+#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. http://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
+
+fluid_profile_data_t fluid_profile_data[] =
+{
+  { FLUID_PROF_WRITE,            "fluid_synth_write_*             ", 1e10, 0.0, 0.0, 0},
+  { FLUID_PROF_ONE_BLOCK,        "fluid_synth_one_block           ", 1e10, 0.0, 0.0, 0},
+  { FLUID_PROF_ONE_BLOCK_CLEAR,  "fluid_synth_one_block:clear     ", 1e10, 0.0, 0.0, 0},
+  { FLUID_PROF_ONE_BLOCK_VOICE,  "fluid_synth_one_block:one voice ", 1e10, 0.0, 0.0, 0},
+  { FLUID_PROF_ONE_BLOCK_VOICES, "fluid_synth_one_block:all voices", 1e10, 0.0, 0.0, 0},
+  { FLUID_PROF_ONE_BLOCK_REVERB, "fluid_synth_one_block:reverb    ", 1e10, 0.0, 0.0, 0},
+  { FLUID_PROF_ONE_BLOCK_CHORUS, "fluid_synth_one_block:chorus    ", 1e10, 0.0, 0.0, 0},
+  { FLUID_PROF_VOICE_NOTE,       "fluid_voice:note                ", 1e10, 0.0, 0.0, 0},
+  { FLUID_PROF_VOICE_RELEASE,    "fluid_voice:release             ", 1e10, 0.0, 0.0, 0},
+  { FLUID_PROF_LAST, "last", 1e100, 0.0, 0.0, 0}
+};
+
+
+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_PROF_LAST; 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);
+    }
+  }
+}
+
+
+#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;
+  GError *err = NULL;
+
+  g_return_val_if_fail (func != NULL, NULL);
+
+#if OLD_GLIB_THREAD_API
+  /* Make sure g_thread_init has been called.
+   * FIXME - 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
+  }
+#if NEW_GLIB_THREAD_API
+  else thread = g_thread_try_new (name, (GThreadFunc)func, data, &err);
+#else
+  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);
+    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 void
+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) g_usleep (delay * 1000);
+  }
+
+  FLUID_LOG (FLUID_DBG, "Timer thread finished");
+
+  if (timer->auto_destroy)
+    FLUID_FREE (timer);
+
+  return;
+}
+
+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 */
+
+  return timer;
+}
+
+int
+delete_fluid_timer (fluid_timer_t *timer)
+{
+  int 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);
+
+  return FLUID_OK;
+}
+
+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;
+}
+
+
+/***************************************************************
+ *
+ *               Sockets and I/O
+ *
+ */
+
+/**
+ * Get standard in stream handle.
+ * @return Standard in stream.
+ */
+fluid_istream_t
+fluid_get_stdin (void)
+{
+  return STDIN_FILENO;
+}
+
+/**
+ * Get standard output stream handle.
+ * @return Standard out stream.
+ */
+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;
+
+    snprintf(buf, len, "%s", line);
+    buf[len - 1] = 0;
+
+    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 & WIN32_SOCKET_FLAG))
+    {
+      n = read(in, &c, 1);
+      if (n == -1) return -1;
+    }
+    else
+    {
+      n = recv(in & ~WIN32_SOCKET_FLAG, &c, 1, 0);
+      if (n == SOCKET_ERROR) return -1;
+    }
+#endif
+
+    if (n == 0)
+    {
+      *buf++ = 0;
+      return 0;
+    }
+
+    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, char* format, ...)
+{
+  char buf[4096];
+  va_list args;
+  int len;
+
+  va_start (args, format);
+  len = 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, strlen (buf));
+#else
+  {
+    int retval;
+
+    /* Handle write differently depending on if its a socket or file descriptor */
+    if (!(out & WIN32_SOCKET_FLAG))
+      return write(out, buf, strlen (buf));
+
+    /* Socket */
+    retval = send (out & ~WIN32_SOCKET_FLAG, buf, strlen (buf), 0);
+
+    return retval != SOCKET_ERROR ? retval : -1;
+  }
+#endif
+}
+
+int fluid_server_socket_join(fluid_server_socket_t *server_socket)
+{
+  return fluid_thread_join (server_socket->thread);
+}
+
+
+#ifndef WIN32           // Not win32?
+
+#define SOCKET_ERROR -1
+
+fluid_istream_t fluid_socket_get_istream (fluid_socket_t sock)
+{
+  return sock;
+}
+
+fluid_ostream_t fluid_socket_get_ostream (fluid_socket_t sock)
+{
+  return sock;
+}
+
+void fluid_socket_close(fluid_socket_t sock)
+{
+  if (sock != INVALID_SOCKET)
+    close (sock);
+}
+
+static void
+fluid_server_socket_run (void *data)
+{
+  fluid_server_socket_t *server_socket = (fluid_server_socket_t *)data;
+  fluid_socket_t client_socket;
+#ifdef IPV6
+  struct sockaddr_in6 addr;
+  char straddr[INET6_ADDRSTRLEN];
+#else
+  struct sockaddr_in addr;
+  char straddr[INET_ADDRSTRLEN];
+#endif
+  socklen_t addrlen = sizeof (addr);
+  int retval;
+  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");
+
+      server_socket->cont = 0;
+      return;
+    } else {
+#ifdef IPV6
+      inet_ntop(AF_INET6, &addr.sin6_addr, straddr, sizeof(straddr));
+#else
+      inet_ntop(AF_INET, &addr.sin_addr, straddr, sizeof(straddr));
+#endif
+      retval = server_socket->func (server_socket->data, client_socket,
+                                    straddr);
+
+      if (retval != 0)
+       fluid_socket_close(client_socket);
+    }
+  }
+
+  FLUID_LOG(FLUID_DBG, "Server closing");
+}
+
+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
+  struct sockaddr_in6 addr;
+#else
+  struct sockaddr_in addr;
+#endif
+  fluid_socket_t sock;
+
+  g_return_val_if_fail (func != NULL, NULL);
+#ifdef IPV6
+  sock = socket(AF_INET6, SOCK_STREAM, 0);
+  if (sock == INVALID_SOCKET) {
+    FLUID_LOG(FLUID_ERR, "Failed to create server socket");
+    return NULL;
+  }
+
+  FLUID_MEMSET((char *)&addr, 0, sizeof(struct sockaddr_in6));
+  addr.sin6_family = AF_INET6;
+  addr.sin6_addr = in6addr_any;
+  addr.sin6_port = htons(port);
+#else
+
+  sock = socket(AF_INET, SOCK_STREAM, 0);
+  if (sock == INVALID_SOCKET) {
+    FLUID_LOG(FLUID_ERR, "Failed to create server socket");
+    return NULL;
+  }
+
+  FLUID_MEMSET((char *)&addr, 0, sizeof(struct sockaddr_in));
+  addr.sin_family = AF_INET;
+  addr.sin_addr.s_addr = htonl(INADDR_ANY);
+  addr.sin_port = htons(port);
+#endif
+  if (bind(sock, (const struct sockaddr *) &addr, sizeof(addr)) == SOCKET_ERROR) {
+    FLUID_LOG(FLUID_ERR, "Failed to bind server socket");
+    fluid_socket_close(sock);
+    return NULL;
+  }
+
+  if (listen(sock, 10) == SOCKET_ERROR) {
+    FLUID_LOG(FLUID_ERR, "Failed listen on server socket");
+    fluid_socket_close(sock);
+    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);
+    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);
+    return NULL;
+  }
+
+  return server_socket;
+}
+
+int delete_fluid_server_socket(fluid_server_socket_t* server_socket)
+{
+  server_socket->cont = 0;
+  if (server_socket->socket != INVALID_SOCKET) {
+    fluid_socket_close(server_socket->socket);
+  }
+  if (server_socket->thread) {
+    delete_fluid_thread(server_socket->thread);
+  }
+  FLUID_FREE(server_socket);
+  return FLUID_OK;
+}
+
+
+#else           // Win32 is "special"
+
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+
+fluid_istream_t fluid_socket_get_istream (fluid_socket_t sock)
+{
+  return sock | WIN32_SOCKET_FLAG;
+}
+
+fluid_ostream_t fluid_socket_get_ostream (fluid_socket_t sock)
+{
+  return sock | WIN32_SOCKET_FLAG;
+}
+
+void fluid_socket_close (fluid_socket_t sock)
+{
+  if (sock != INVALID_SOCKET)
+    closesocket (sock);
+}
+
+static void fluid_server_socket_run (void *data)
+{
+  fluid_server_socket_t *server_socket = (fluid_server_socket_t *)data;
+  fluid_socket_t client_socket;
+#ifdef IPV6
+  struct sockaddr_in6 addr;
+  char straddr[INET6_ADDRSTRLEN];
+#else
+  struct sockaddr_in addr;
+  char straddr[INET_ADDRSTRLEN];
+#endif
+  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: %ld", WSAGetLastError ());
+
+      server_socket->cont = 0;
+      return;
+    }
+    else
+    {
+#ifdef IPV6
+      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);
+      if (r != 0)
+       fluid_socket_close (client_socket);
+    }
+  }
+
+  FLUID_LOG (FLUID_DBG, "Server closing");
+}
+
+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
+  struct sockaddr_in6 addr;
+#else
+  struct sockaddr_in addr;
+#endif
+
+  fluid_socket_t sock;
+  WSADATA wsaData;
+  int retval;
+
+  g_return_val_if_fail (func != NULL, NULL);
+
+  // Win32 requires initialization of winsock
+  retval = WSAStartup (MAKEWORD (2,2), &wsaData);
+
+  if (retval != 0)
+  {
+    FLUID_LOG(FLUID_ERR, "Server socket creation error: WSAStartup failed: %d", retval);
+    return NULL;
+  }
+#ifdef IPV6
+  sock = socket (AF_INET6, SOCK_STREAM, 0);
+  if (sock == INVALID_SOCKET)
+  {
+    FLUID_LOG (FLUID_ERR, "Failed to create server socket: %ld", WSAGetLastError ());
+    WSACleanup ();
+    return NULL;
+  }
+  addr.sin6_family = AF_INET6;
+  addr.sin6_port = htons (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: %ld", WSAGetLastError ());
+    WSACleanup ();
+    return NULL;
+  }
+
+  addr.sin_family = AF_INET;
+  addr.sin_port = htons (port);
+  addr.sin_addr.s_addr = htonl (INADDR_ANY);
+#endif
+  retval = bind (sock, (struct sockaddr *)&addr, sizeof (addr));
+
+  if (retval == SOCKET_ERROR)
+  {
+    FLUID_LOG (FLUID_ERR, "Failed to bind server socket: %ld", WSAGetLastError ());
+    fluid_socket_close (sock);
+    WSACleanup ();
+    return NULL;
+  }
+
+  if (listen (sock, SOMAXCONN) == SOCKET_ERROR)
+  {
+    FLUID_LOG (FLUID_ERR, "Failed to listen on server socket: %ld", WSAGetLastError ());
+    fluid_socket_close (sock);
+    WSACleanup ();
+    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);
+    WSACleanup ();
+    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);
+    WSACleanup ();
+    return NULL;
+  }
+
+  return server_socket;
+}
+
+int delete_fluid_server_socket(fluid_server_socket_t *server_socket)
+{
+  server_socket->cont = 0;
+
+  if (server_socket->socket != INVALID_SOCKET)
+    fluid_socket_close (server_socket->socket);
+
+  if (server_socket->thread)
+    delete_fluid_thread (server_socket->thread);
+
+  FLUID_FREE (server_socket);
+
+  WSACleanup ();        // Should be called the same number of times as WSAStartup
+
+  return FLUID_OK;
+}
+
+#endif