/* 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_rvoice_mixer.h"
#include "fluid_rvoice.h"
#include "fluid_sys.h"
#include "fluid_rev.h"
#include "fluid_chorus.h"
#include "fluidsynth_priv.h"
#include "fluid_ladspa.h"

#define SYNTH_REVERB_CHANNEL 0
#define SYNTH_CHORUS_CHANNEL 1

#define ENABLE_MIXER_THREADS 1

// If less than x voices, the thread overhead is larger than the gain,
// so don't activate the thread(s).
#define VOICES_PER_THREAD 8

typedef struct _fluid_mixer_buffers_t fluid_mixer_buffers_t;

struct _fluid_mixer_buffers_t {
  fluid_rvoice_mixer_t* mixer; /**< Owner of object */
#ifdef ENABLE_MIXER_THREADS
  fluid_thread_t* thread;     /**< Thread object */
#endif

  fluid_rvoice_t** finished_voices; /* List of voices who have finished */
  int finished_voice_count;

  int ready;             /**< Atomic: buffers are ready for mixing */

  int buf_blocks;             /**< Number of blocks allocated in the buffers */

  int buf_count;
  fluid_real_t** left_buf;
  fluid_real_t** right_buf;

  int fx_buf_count;
  fluid_real_t** fx_left_buf;
  fluid_real_t** fx_right_buf;
};

typedef struct _fluid_mixer_fx_t fluid_mixer_fx_t;

struct _fluid_mixer_fx_t {
  fluid_revmodel_t* reverb; /**< Reverb unit */
  fluid_chorus_t* chorus; /**< Chorus unit */
  int with_reverb;        /**< Should the synth use the built-in reverb unit? */
  int with_chorus;        /**< Should the synth use the built-in chorus unit? */
  int mix_fx_to_out;      /**< Should the effects be mixed in with the primary output? */
};

struct _fluid_rvoice_mixer_t {
  fluid_mixer_fx_t fx;

  fluid_mixer_buffers_t buffers; /**< Used by mixer only: own buffers */
  void (*remove_voice_callback)(void*, fluid_rvoice_t*); /**< Used by mixer only: Receive this callback every time a voice is removed */
  void* remove_voice_callback_userdata;

  fluid_rvoice_t** rvoices; /**< Read-only: Voices array, sorted so that all nulls are last */
  int polyphony; /**< Read-only: Length of voices array */
  int active_voices; /**< Read-only: Number of non-null voices */
  int current_blockcount;      /**< Read-only: how many blocks to process this time */

#ifdef LADSPA
  fluid_LADSPA_FxUnit_t* LADSPA_FxUnit; /**< Used by mixer only: Effects unit for LADSPA support. Never created or freed */
#endif

#ifdef ENABLE_MIXER_THREADS
//  int sleeping_threads;        /**< Atomic: number of threads currently asleep */
//  int active_threads;          /**< Atomic: number of threads in the thread loop */
  int threads_should_terminate; /**< Atomic: Set to TRUE when threads should terminate */
  int current_rvoice;           /**< Atomic: for the threads to know next voice to  */
  fluid_cond_t wakeup_threads; /**< Signalled when the threads should wake up */
  fluid_cond_mutex_t wakeup_threads_m; /**< wakeup_threads mutex companion */
  fluid_cond_t thread_ready; /**< Signalled from thread, when the thread has a buffer ready for mixing */
  fluid_cond_mutex_t thread_ready_m; /**< thread_ready mutex companion */

  int thread_count;            /**< Number of extra mixer threads for multi-core rendering */
  fluid_mixer_buffers_t* threads;    /**< Array of mixer threads (thread_count in length) */
#endif
};

static FLUID_INLINE void 
fluid_rvoice_mixer_process_fx(fluid_rvoice_mixer_t* mixer)
{
  int i;
  fluid_profile_ref_var(prof_ref);
  if (mixer->fx.with_reverb) {
    if (mixer->fx.mix_fx_to_out) {
      for (i=0; i < mixer->current_blockcount * FLUID_BUFSIZE; i += FLUID_BUFSIZE)
        fluid_revmodel_processmix(mixer->fx.reverb, 
                                  &mixer->buffers.fx_left_buf[SYNTH_REVERB_CHANNEL][i],
				  &mixer->buffers.left_buf[0][i],
				  &mixer->buffers.right_buf[0][i]);
    } 
    else {
      for (i=0; i < mixer->current_blockcount * FLUID_BUFSIZE; i += FLUID_BUFSIZE)
        fluid_revmodel_processreplace(mixer->fx.reverb, 
                                  &mixer->buffers.fx_left_buf[SYNTH_REVERB_CHANNEL][i],
				  &mixer->buffers.fx_left_buf[SYNTH_REVERB_CHANNEL][i],
				  &mixer->buffers.fx_right_buf[SYNTH_REVERB_CHANNEL][i]);
    }
    fluid_profile(FLUID_PROF_ONE_BLOCK_REVERB, prof_ref);
  }
  
  if (mixer->fx.with_chorus) {
    if (mixer->fx.mix_fx_to_out) {
      for (i=0; i < mixer->current_blockcount * FLUID_BUFSIZE; i += FLUID_BUFSIZE)
        fluid_chorus_processmix(mixer->fx.chorus, 
                                &mixer->buffers.fx_left_buf[SYNTH_CHORUS_CHANNEL][i],
			        &mixer->buffers.left_buf[0][i],
				&mixer->buffers.right_buf[0][i]);
    } 
    else {
      for (i=0; i < mixer->current_blockcount * FLUID_BUFSIZE; i += FLUID_BUFSIZE)
        fluid_chorus_processreplace(mixer->fx.chorus, 
                                &mixer->buffers.fx_left_buf[SYNTH_CHORUS_CHANNEL][i],
				&mixer->buffers.fx_left_buf[SYNTH_CHORUS_CHANNEL][i],
				&mixer->buffers.fx_right_buf[SYNTH_CHORUS_CHANNEL][i]);
    }
    fluid_profile(FLUID_PROF_ONE_BLOCK_CHORUS, prof_ref);
  }
  
#ifdef LADSPA
  /* Run the signal through the LADSPA Fx unit */
  if (mixer->LADSPA_FxUnit) {
    int j;
    FLUID_DECLARE_VLA(fluid_real_t*, left_buf, mixer->buffers.buf_count);
    FLUID_DECLARE_VLA(fluid_real_t*, right_buf, mixer->buffers.buf_count);
    FLUID_DECLARE_VLA(fluid_real_t*, fx_left_buf, mixer->buffers.fx_buf_count);
    FLUID_DECLARE_VLA(fluid_real_t*, fx_right_buf, mixer->buffers.fx_buf_count);
    for (j=0; j < mixer->buffers.buf_count; j++) {
      left_buf[j] = mixer->buffers.left_buf[j];
      right_buf[j] = mixer->buffers.right_buf[j];
    }
    for (j=0; j < mixer->buffers.fx_buf_count; j++) {
      fx_left_buf[j] = mixer->buffers.fx_left_buf[j];
      fx_right_buf[j] = mixer->buffers.fx_right_buf[j];
    }
    for (i=0; i < mixer->current_blockcount * FLUID_BUFSIZE; i += FLUID_BUFSIZE) {
      fluid_LADSPA_run(mixer->LADSPA_FxUnit, left_buf, right_buf, fx_left_buf, 
		       fx_right_buf);
      for (j=0; j < mixer->buffers.buf_count; j++) {
        left_buf[j] += FLUID_BUFSIZE;
        right_buf[j] += FLUID_BUFSIZE;
      }
      for (j=0; j < mixer->buffers.fx_buf_count; j++) {
        fx_left_buf[j] += FLUID_BUFSIZE;
        fx_right_buf[j] += FLUID_BUFSIZE;
      }
    }
    fluid_check_fpe("LADSPA");
  }
#endif
}

/**
 * During rendering, rvoices might be finished. Set this callback
 * for getting a callback any time the rvoice is finished.
 */
void fluid_rvoice_mixer_set_finished_voices_callback(
  fluid_rvoice_mixer_t* mixer,
  void (*func)(void*, fluid_rvoice_t*),
  void* userdata)
{
  mixer->remove_voice_callback_userdata = userdata;
  mixer->remove_voice_callback = func;
}



/**
 * Synthesize one voice and add to buffer.
 * NOTE: If return value is less than blockcount*FLUID_BUFSIZE, that means 
 * voice has been finished, removed and possibly replaced with another voice.
 * @return Number of samples written 
 */
static int
fluid_mix_one(fluid_rvoice_t* rvoice, fluid_real_t** bufs, unsigned int bufcount, int blockcount)
{
  int i, result = 0;

  FLUID_DECLARE_VLA(fluid_real_t, local_buf, FLUID_BUFSIZE*blockcount);

  for (i=0; i < blockcount; i++) {
    int s = fluid_rvoice_write(rvoice, &local_buf[FLUID_BUFSIZE*i]);
    if (s == -1) {
      s = FLUID_BUFSIZE; /* Voice is quiet, TODO: optimize away memset/mix */
      FLUID_MEMSET(&local_buf[FLUID_BUFSIZE*i], 0, FLUID_BUFSIZE*sizeof(fluid_real_t));
    } 
    result += s;
    if (s < FLUID_BUFSIZE) {
      break;
    }
  }
  fluid_rvoice_buffers_mix(&rvoice->buffers, local_buf, result, bufs, bufcount);

  return result;
}

/**
 * Glue to get fluid_rvoice_buffers_mix what it wants
 * Note: Make sure outbufs has 2 * (buf_count + fx_buf_count) elements before calling
 */
static FLUID_INLINE int 
fluid_mixer_buffers_prepare(fluid_mixer_buffers_t* buffers, fluid_real_t** outbufs)
{
  fluid_real_t* reverb_buf, *chorus_buf;
  int i;

  /* Set up the reverb / chorus buffers only, when the effect is
   * enabled on synth level.  Nonexisting buffers are detected in the
   * DSP loop. Not sending the reverb / chorus signal saves some time
   * in that case. */
  reverb_buf = buffers->mixer->fx.with_reverb ? buffers->fx_left_buf[SYNTH_REVERB_CHANNEL] : NULL;
  chorus_buf = buffers->mixer->fx.with_chorus ? buffers->fx_left_buf[SYNTH_CHORUS_CHANNEL] : NULL;
  outbufs[buffers->buf_count*2 + SYNTH_REVERB_CHANNEL] = reverb_buf;
  outbufs[buffers->buf_count*2 + SYNTH_CHORUS_CHANNEL] = chorus_buf;

      /* The output associated with a MIDI channel is wrapped around
       * using the number of audio groups as modulo divider.  This is
       * typically the number of output channels on the 'sound card',
       * as long as the LADSPA Fx unit is not used. In case of LADSPA
       * unit, think of it as subgroups on a mixer.
       *
       * For example: Assume that the number of groups is set to 2.
       * Then MIDI channel 1, 3, 5, 7 etc. go to output 1, channels 2,
       * 4, 6, 8 etc to output 2.  Or assume 3 groups: Then MIDI
       * channels 1, 4, 7, 10 etc go to output 1; 2, 5, 8, 11 etc to
       * output 2, 3, 6, 9, 12 etc to output 3.
       */

  for (i = 0; i < buffers->buf_count; i++) {
    outbufs[i*2] = buffers->left_buf[i];
    outbufs[i*2+1] = buffers->right_buf[i];
  }
  return buffers->buf_count*2 + 2;
}


static FLUID_INLINE void
fluid_finish_rvoice(fluid_mixer_buffers_t* buffers, fluid_rvoice_t* rvoice)
{
  if (buffers->finished_voice_count < buffers->mixer->polyphony)
    buffers->finished_voices[buffers->finished_voice_count++] = rvoice;
  else
    FLUID_LOG(FLUID_ERR, "Exceeded finished voices array, try increasing polyphony");
}

static void   
fluid_mixer_buffer_process_finished_voices(fluid_mixer_buffers_t* buffers)
{
  int i,j;
  for (i=0; i < buffers->finished_voice_count; i++) {
    fluid_rvoice_t* v = buffers->finished_voices[i];
    int* av = &buffers->mixer->active_voices; 
    for (j=0; j < *av; j++) {
      if (v == buffers->mixer->rvoices[j]) {
        (*av)--;
        /* Pack the array */
        if (j < *av) 
          buffers->mixer->rvoices[j] = buffers->mixer->rvoices[*av];
      }
    }
    if (buffers->mixer->remove_voice_callback)
      buffers->mixer->remove_voice_callback(
        buffers->mixer->remove_voice_callback_userdata, v);
  }
  buffers->finished_voice_count = 0;
}

static FLUID_INLINE void fluid_rvoice_mixer_process_finished_voices(fluid_rvoice_mixer_t* mixer)
{
#ifdef ENABLE_MIXER_THREADS  
  int i;
  for (i=0; i < mixer->thread_count; i++)
    fluid_mixer_buffer_process_finished_voices(&mixer->threads[i]);
#endif
  fluid_mixer_buffer_process_finished_voices(&mixer->buffers);
}

static FLUID_INLINE void
fluid_mixer_buffers_render_one(fluid_mixer_buffers_t* buffers, 
			       fluid_rvoice_t* voice, fluid_real_t** bufs, 
			       unsigned int bufcount)
{
  int s = fluid_mix_one(voice, bufs, bufcount, buffers->mixer->current_blockcount);
  if (s < buffers->mixer->current_blockcount * FLUID_BUFSIZE) {
    fluid_finish_rvoice(buffers, voice);
  }
}
/*
static int fluid_mixer_buffers_replace_voice(fluid_mixer_buffers_t* buffers, 
			                      fluid_rvoice_t* voice)
{
  int i, retval=0;
  int fvc = buffers->finished_voice_count;
  for (i=0; i < fvc; i++)
    if (buffers->finished_voices[i] == voice) {
      fvc--;
      if (i < fvc)
        buffers->finished_voices[i] =  buffers->finished_voices[fvc];
      retval++;
    }
  fvc = buffers->finished_voice_count;
  return retval;  
}
*/

int 
fluid_rvoice_mixer_add_voice(fluid_rvoice_mixer_t* mixer, fluid_rvoice_t* voice)
{
  int i;

  if (mixer->active_voices < mixer->polyphony) {
    mixer->rvoices[mixer->active_voices++] = voice;
    return FLUID_OK;
  }
  
  /* See if any voices just finished, if so, take its place.
     This can happen in voice overflow conditions. */
  for (i=0; i < mixer->active_voices; i++) {
    if (mixer->rvoices[i] == voice) {
      FLUID_LOG(FLUID_ERR, "Internal error: Trying to replace an existing rvoice in fluid_rvoice_mixer_add_voice?!");
      return FLUID_FAILED;
    }
    if (mixer->rvoices[i]->envlfo.volenv.section == FLUID_VOICE_ENVFINISHED) {
      fluid_finish_rvoice(&mixer->buffers, mixer->rvoices[i]);
      mixer->rvoices[i] = voice;
      return FLUID_OK;
    }
  }

  /* This should never happen */
  FLUID_LOG(FLUID_ERR, "Trying to exceed polyphony in fluid_rvoice_mixer_add_voice");
  return FLUID_FAILED;
}

static int 
fluid_mixer_buffers_update_polyphony(fluid_mixer_buffers_t* buffers, int value)
{
  void* newptr;

  if (buffers->finished_voice_count > value) 
    return FLUID_FAILED;
  
  newptr = FLUID_REALLOC(buffers->finished_voices, value * sizeof(fluid_rvoice_t*));
  if (newptr == NULL && value > 0) 
    return FLUID_FAILED;
  buffers->finished_voices = newptr;  
  return FLUID_OK;
}

/**
 * Update polyphony - max number of voices (NOTE: not hard real-time capable)
 * @return FLUID_OK or FLUID_FAILED
 */
int 
fluid_rvoice_mixer_set_polyphony(fluid_rvoice_mixer_t* handler, int value)
{
  void* newptr;
  if (handler->active_voices > value) 
    return FLUID_FAILED;

  newptr = FLUID_REALLOC(handler->rvoices, value * sizeof(fluid_rvoice_t*));
  if (newptr == NULL) 
    return FLUID_FAILED;
  handler->rvoices = newptr;

  if (fluid_mixer_buffers_update_polyphony(&handler->buffers, value) 
      == FLUID_FAILED)
    return FLUID_FAILED;

#ifdef ENABLE_MIXER_THREADS
  {
    int i;
    for (i=0; i < handler->thread_count; i++)
      if (fluid_mixer_buffers_update_polyphony(&handler->threads[i], value) 
          == FLUID_FAILED)
        return FLUID_FAILED;
  }
#endif

  handler->polyphony = value;
  return FLUID_OK;
}


static void 
fluid_render_loop_singlethread(fluid_rvoice_mixer_t* mixer)
{
  int i;
  FLUID_DECLARE_VLA(fluid_real_t*, bufs, 
		    mixer->buffers.buf_count * 2 + mixer->buffers.fx_buf_count * 2);
  int bufcount = fluid_mixer_buffers_prepare(&mixer->buffers, bufs);
  fluid_profile_ref_var(prof_ref);
  for (i=0; i < mixer->active_voices; i++) {
    fluid_mixer_buffers_render_one(&mixer->buffers, mixer->rvoices[i], bufs, 
				   bufcount);
    fluid_profile(FLUID_PROF_ONE_BLOCK_VOICE, prof_ref);
  }
}


static FLUID_INLINE void
fluid_mixer_buffers_zero(fluid_mixer_buffers_t* buffers)
{
  int i;
  int size = buffers->mixer->current_blockcount * FLUID_BUFSIZE * sizeof(fluid_real_t);
  /* TODO: Optimize by only zero out the buffers we actually use later on. */
  for (i=0; i < buffers->buf_count; i++) {
    FLUID_MEMSET(buffers->left_buf[i], 0, size);
    FLUID_MEMSET(buffers->right_buf[i], 0, size);
  }
  for (i=0; i < buffers->fx_buf_count; i++) {
    FLUID_MEMSET(buffers->fx_left_buf[i], 0, size);
    FLUID_MEMSET(buffers->fx_right_buf[i], 0, size);
  }
}



static int 
fluid_mixer_buffers_init(fluid_mixer_buffers_t* buffers, fluid_rvoice_mixer_t* mixer)
{
  int i, samplecount;
  
  buffers->mixer = mixer;
  buffers->buf_count = buffers->mixer->buffers.buf_count;
  buffers->fx_buf_count = buffers->mixer->buffers.fx_buf_count;
  buffers->buf_blocks = buffers->mixer->buffers.buf_blocks;
  samplecount = FLUID_BUFSIZE * buffers->buf_blocks;
  
 
  /* Left and right audio buffers */

  buffers->left_buf = FLUID_ARRAY(fluid_real_t*, buffers->buf_count);
  buffers->right_buf = FLUID_ARRAY(fluid_real_t*, buffers->buf_count);

  if ((buffers->left_buf == NULL) || (buffers->right_buf == NULL)) {
    FLUID_LOG(FLUID_ERR, "Out of memory");
    return 0;
  }

  FLUID_MEMSET(buffers->left_buf, 0, buffers->buf_count * sizeof(fluid_real_t*));
  FLUID_MEMSET(buffers->right_buf, 0, buffers->buf_count * sizeof(fluid_real_t*));

  for (i = 0; i < buffers->buf_count; i++) {

    buffers->left_buf[i] = FLUID_ARRAY(fluid_real_t, samplecount);
    buffers->right_buf[i] = FLUID_ARRAY(fluid_real_t, samplecount);

    if ((buffers->left_buf[i] == NULL) || (buffers->right_buf[i] == NULL)) {
      FLUID_LOG(FLUID_ERR, "Out of memory");
      return 0;
    }
  }

  /* Effects audio buffers */

  buffers->fx_left_buf = FLUID_ARRAY(fluid_real_t*, buffers->fx_buf_count);
  buffers->fx_right_buf = FLUID_ARRAY(fluid_real_t*, buffers->fx_buf_count);

  if ((buffers->fx_left_buf == NULL) || (buffers->fx_right_buf == NULL)) {
    FLUID_LOG(FLUID_ERR, "Out of memory");
    return 0;
  }

  FLUID_MEMSET(buffers->fx_left_buf, 0, buffers->fx_buf_count * sizeof(fluid_real_t*));
  FLUID_MEMSET(buffers->fx_right_buf, 0, buffers->fx_buf_count * sizeof(fluid_real_t*));

  for (i = 0; i < buffers->fx_buf_count; i++) {
    buffers->fx_left_buf[i] = FLUID_ARRAY(fluid_real_t, samplecount);
    buffers->fx_right_buf[i] = FLUID_ARRAY(fluid_real_t, samplecount);

    if ((buffers->fx_left_buf[i] == NULL) || (buffers->fx_right_buf[i] == NULL)) {
      FLUID_LOG(FLUID_ERR, "Out of memory");
      return 0;
    }
  }
  
  buffers->finished_voices = NULL;
  if (fluid_mixer_buffers_update_polyphony(buffers, mixer->polyphony) 
      == FLUID_FAILED) {
    FLUID_LOG(FLUID_ERR, "Out of memory");
    return 0;
  }
  
  return 1;
}

/**
 * Note: Not hard real-time capable (calls malloc)
 */
void 
fluid_rvoice_mixer_set_samplerate(fluid_rvoice_mixer_t* mixer, fluid_real_t samplerate)
{
  int i;
  if (mixer->fx.chorus)
    delete_fluid_chorus(mixer->fx.chorus);
  mixer->fx.chorus = new_fluid_chorus(samplerate);
  if (mixer->fx.reverb)
	  fluid_revmodel_samplerate_change(mixer->fx.reverb, samplerate);
  for (i=0; i < mixer->active_voices; i++)
    fluid_rvoice_set_output_rate(mixer->rvoices[i], samplerate);
}


/**
 * @param buf_count number of primary stereo buffers
 * @param fx_buf_count number of stereo effect buffers
 */
fluid_rvoice_mixer_t* 
new_fluid_rvoice_mixer(int buf_count, int fx_buf_count, fluid_real_t sample_rate)
{
  fluid_rvoice_mixer_t* mixer = FLUID_NEW(fluid_rvoice_mixer_t);
  if (mixer == NULL) {
    FLUID_LOG(FLUID_ERR, "Out of memory");
    return NULL;
  }
  FLUID_MEMSET(mixer, 0, sizeof(fluid_rvoice_mixer_t));
  mixer->buffers.buf_count = buf_count;
  mixer->buffers.fx_buf_count = fx_buf_count;
  mixer->buffers.buf_blocks = FLUID_MIXER_MAX_BUFFERS_DEFAULT;
  
  /* allocate the reverb module */
  mixer->fx.reverb = new_fluid_revmodel(sample_rate);
  mixer->fx.chorus = new_fluid_chorus(sample_rate);
  if (mixer->fx.reverb == NULL) {
    FLUID_LOG(FLUID_ERR, "Out of memory");
    delete_fluid_rvoice_mixer(mixer);
    return NULL;
  }
  
  if (!fluid_mixer_buffers_init(&mixer->buffers, mixer)) {
    delete_fluid_rvoice_mixer(mixer);
    return NULL;
  }
  
#ifdef ENABLE_MIXER_THREADS
  fluid_cond_init(&mixer->thread_ready);
  fluid_cond_init(&mixer->wakeup_threads);
  fluid_cond_mutex_init(mixer->thread_ready_m);
  fluid_cond_mutex_init(mixer->wakeup_threads_m);
  if (!mixer->thread_ready.p || !mixer->wakeup_threads.p ||
      !mixer->wakeup_threads_m.p || !mixer->wakeup_threads_m.p) {
    delete_fluid_rvoice_mixer(mixer);
    return NULL;
  }
#endif
  
  return mixer;
}

static void
fluid_mixer_buffers_free(fluid_mixer_buffers_t* buffers)
{
  int i;
  
  FLUID_FREE(buffers->finished_voices);
  
  /* free all the sample buffers */
  if (buffers->left_buf != NULL) {
    for (i = 0; i < buffers->buf_count; i++) {
      if (buffers->left_buf[i] != NULL) {
	FLUID_FREE(buffers->left_buf[i]);
      }
    }
    FLUID_FREE(buffers->left_buf);
  }

  if (buffers->right_buf != NULL) {
    for (i = 0; i < buffers->buf_count; i++) {
      if (buffers->right_buf[i] != NULL) {
	FLUID_FREE(buffers->right_buf[i]);
      }
    }
    FLUID_FREE(buffers->right_buf);
  }

  if (buffers->fx_left_buf != NULL) {
    for (i = 0; i < buffers->fx_buf_count; i++) {
      if (buffers->fx_left_buf[i] != NULL) {
	FLUID_FREE(buffers->fx_left_buf[i]);
      }
    }
    FLUID_FREE(buffers->fx_left_buf);
  }

  if (buffers->fx_right_buf != NULL) {
    for (i = 0; i < buffers->fx_buf_count; i++) {
      if (buffers->fx_right_buf[i] != NULL) {
	FLUID_FREE(buffers->fx_right_buf[i]);
      }
    }
    FLUID_FREE(buffers->fx_right_buf);
  }  
}

void delete_fluid_rvoice_mixer(fluid_rvoice_mixer_t* mixer)
{
  if (!mixer) 
    return;
  fluid_rvoice_mixer_set_threads(mixer, 0, 0);
#ifdef ENABLE_MIXER_THREADS
  if (mixer->thread_ready.p)
    delete_fluid_cond(&mixer->thread_ready);
  if (mixer->wakeup_threads.p)
    delete_fluid_cond(&mixer->wakeup_threads);
  if (mixer->thread_ready_m.p)
    delete_fluid_cond_mutex(mixer->thread_ready_m);
  if (mixer->wakeup_threads_m.p)
    delete_fluid_cond_mutex(mixer->wakeup_threads_m);
#endif
  fluid_mixer_buffers_free(&mixer->buffers);
  if (mixer->fx.reverb)
    delete_fluid_revmodel(mixer->fx.reverb);
  if (mixer->fx.chorus)
    delete_fluid_chorus(mixer->fx.chorus);
  FLUID_FREE(mixer);
}


#ifdef LADSPA				    
void fluid_rvoice_mixer_set_ladspa(fluid_rvoice_mixer_t* mixer, 
				   fluid_LADSPA_FxUnit_t* ladspa)
{
  mixer->LADSPA_FxUnit = ladspa;
}
#endif

void fluid_rvoice_mixer_set_reverb_enabled(fluid_rvoice_mixer_t* mixer, int on)
{
  mixer->fx.with_reverb = on;
}

void fluid_rvoice_mixer_set_chorus_enabled(fluid_rvoice_mixer_t* mixer, int on)
{
  mixer->fx.with_chorus = on;
}

void fluid_rvoice_mixer_set_mix_fx(fluid_rvoice_mixer_t* mixer, int on)
{
  mixer->fx.mix_fx_to_out = on;
}

void fluid_rvoice_mixer_set_chorus_params(fluid_rvoice_mixer_t* mixer, int set, 
				         int nr, double level, double speed, 
				         double depth_ms, int type)
{
  fluid_chorus_set(mixer->fx.chorus, set, nr, level, speed, depth_ms, type);
}
void fluid_rvoice_mixer_set_reverb_params(fluid_rvoice_mixer_t* mixer, int set, 
					 double roomsize, double damping, 
					 double width, double level)
{
  fluid_revmodel_set(mixer->fx.reverb, set, roomsize, damping, width, level); 
}

void fluid_rvoice_mixer_reset_fx(fluid_rvoice_mixer_t* mixer)
{
  fluid_revmodel_reset(mixer->fx.reverb);
  fluid_chorus_reset(mixer->fx.chorus);
}

int fluid_rvoice_mixer_get_bufs(fluid_rvoice_mixer_t* mixer, 
				  fluid_real_t*** left, fluid_real_t*** right)
{
  *left = mixer->buffers.left_buf;
  *right = mixer->buffers.right_buf;
  return mixer->buffers.buf_count;
}


#ifdef ENABLE_MIXER_THREADS

static FLUID_INLINE fluid_rvoice_t* 
fluid_mixer_get_mt_rvoice(fluid_rvoice_mixer_t* mixer)
{
  int i = fluid_atomic_int_exchange_and_add(&mixer->current_rvoice, 1);
  if (i >= mixer->active_voices) 
    return NULL;
  return mixer->rvoices[i];
}

#define THREAD_BUF_PROCESSING 0
#define THREAD_BUF_VALID 1
#define THREAD_BUF_NODATA 2
#define THREAD_BUF_TERMINATE 3

/* Core thread function (processes voices in parallel to primary synthesis thread) */
static void
fluid_mixer_thread_func (void* data)
{
  fluid_mixer_buffers_t* buffers = data;  
  fluid_rvoice_mixer_t* mixer = buffers->mixer;
  int hasValidData = 0;
  FLUID_DECLARE_VLA(fluid_real_t*, bufs, buffers->buf_count*2 + buffers->fx_buf_count*2);
  int bufcount = 0;
  
  while (!fluid_atomic_int_get(&mixer->threads_should_terminate)) {
    fluid_rvoice_t* rvoice = fluid_mixer_get_mt_rvoice(mixer);
    if (rvoice == NULL) {
      // if no voices: signal rendered buffers, sleep
      fluid_atomic_int_set(&buffers->ready, hasValidData ? THREAD_BUF_VALID : THREAD_BUF_NODATA);
      fluid_cond_mutex_lock(mixer->thread_ready_m);
      fluid_cond_signal(&mixer->thread_ready);
      fluid_cond_mutex_unlock(mixer->thread_ready_m);
      
      fluid_cond_mutex_lock(mixer->wakeup_threads_m);
      while (1) {
        int j = fluid_atomic_int_get(&buffers->ready); 
        if (j == THREAD_BUF_PROCESSING || j == THREAD_BUF_TERMINATE)
          break;
        fluid_cond_wait(&mixer->wakeup_threads, mixer->wakeup_threads_m);
      }
      fluid_cond_mutex_unlock(mixer->wakeup_threads_m);
      
      hasValidData = 0;
    }
    else {
      // else: if buffer is not zeroed, zero buffers
      if (!hasValidData) {
        fluid_mixer_buffers_zero(buffers);
	bufcount = fluid_mixer_buffers_prepare(buffers, bufs);
	hasValidData = 1;
      }
      // then render voice to buffers
      fluid_mixer_buffers_render_one(buffers, rvoice, bufs, bufcount);
    }
  }

}

static void
fluid_mixer_buffers_mix(fluid_mixer_buffers_t* dest, fluid_mixer_buffers_t* src)
{
  int i,j;
  int scount = dest->mixer->current_blockcount * FLUID_BUFSIZE;
  int minbuf;
  
  minbuf = dest->buf_count;
  if (minbuf > src->buf_count)
    minbuf = src->buf_count;
  for (i=0; i < minbuf; i++) {
    for (j=0; j < scount; j++) {
      dest->left_buf[i][j] += src->left_buf[i][j];
      dest->right_buf[i][j] += src->right_buf[i][j];
    }
  }

  minbuf = dest->fx_buf_count;
  if (minbuf > src->fx_buf_count)
    minbuf = src->fx_buf_count;
  for (i=0; i < minbuf; i++) {
    for (j=0; j < scount; j++) {
      dest->fx_left_buf[i][j] += src->fx_left_buf[i][j];
      dest->fx_right_buf[i][j] += src->fx_right_buf[i][j];
    }
  }
}


/**
 * Go through all threads and see if someone is finished for mixing 
 */
static FLUID_INLINE int
fluid_mixer_mix_in(fluid_rvoice_mixer_t* mixer, int extra_threads)
{
  int i, result, hasmixed;
  do {
    hasmixed = 0;
    result = 0;
    for (i=0; i < extra_threads; i++) {
      int j = fluid_atomic_int_get(&mixer->threads[i].ready);
      switch (j) {
	case THREAD_BUF_PROCESSING: 
	  result = 1;
	  break;
	case THREAD_BUF_VALID:
          fluid_atomic_int_set(&mixer->threads[i].ready, THREAD_BUF_NODATA);
	  fluid_mixer_buffers_mix(&mixer->buffers, &mixer->threads[i]);
	  hasmixed = 1;
	  break;
      }
    }
  } while (hasmixed);
  return result;
}

static void 
fluid_render_loop_multithread(fluid_rvoice_mixer_t* mixer)
{
  int i, bufcount;
  //int scount = mixer->current_blockcount * FLUID_BUFSIZE;
  FLUID_DECLARE_VLA(fluid_real_t*, bufs, 
		    mixer->buffers.buf_count * 2 + mixer->buffers.fx_buf_count * 2);
  // How many threads should we start this time?
  int extra_threads = mixer->active_voices / VOICES_PER_THREAD;
  if (extra_threads > mixer->thread_count)
    extra_threads = mixer->thread_count;
  if (extra_threads == 0) {
    // No extra threads? No thread overhead!
    fluid_render_loop_singlethread(mixer);
    return;
  }

  bufcount = fluid_mixer_buffers_prepare(&mixer->buffers, bufs);
  
  // Prepare voice list
  fluid_cond_mutex_lock(mixer->wakeup_threads_m);
  fluid_atomic_int_set(&mixer->current_rvoice, 0);
  for (i=0; i < extra_threads; i++)
    fluid_atomic_int_set(&mixer->threads[i].ready, THREAD_BUF_PROCESSING);
  // Signal threads to wake up
  fluid_cond_broadcast(&mixer->wakeup_threads);
  fluid_cond_mutex_unlock(mixer->wakeup_threads_m);
  
  // If thread is finished, mix it in
  while (fluid_mixer_mix_in(mixer, extra_threads)) {
    // Otherwise get a voice and render it
    fluid_rvoice_t* rvoice = fluid_mixer_get_mt_rvoice(mixer);
    if (rvoice != NULL) {
      fluid_profile_ref_var(prof_ref);
      fluid_mixer_buffers_render_one(&mixer->buffers, rvoice, bufs, bufcount);
      fluid_profile(FLUID_PROF_ONE_BLOCK_VOICE, prof_ref);
      //test++;
    }
    else {
      // If no voices, wait for mixes. Make sure one is still processing to avoid deadlock
      int is_processing = 0;
      //waits++;
      fluid_cond_mutex_lock(mixer->thread_ready_m);
      for (i=0; i < extra_threads; i++) 
	if (fluid_atomic_int_get(&mixer->threads[i].ready) == 
	    THREAD_BUF_PROCESSING)
	  is_processing = 1;
      if (is_processing) 
        fluid_cond_wait(&mixer->thread_ready, mixer->thread_ready_m);
      fluid_cond_mutex_unlock(mixer->thread_ready_m);
    }
  }
  //FLUID_LOG(FLUID_DBG, "Blockcount: %d, mixed %d of %d voices myself, waits = %d", 
  //	    mixer->current_blockcount, test, mixer->active_voices, waits);
}

#endif

/**
 * Update amount of extra mixer threads. 
 * @param thread_count Number of extra mixer threads for multi-core rendering
 * @param prio_level real-time prio level for the extra mixer threads
 */
void 
fluid_rvoice_mixer_set_threads(fluid_rvoice_mixer_t* mixer, int thread_count, 
  			       int prio_level)
{
#ifdef ENABLE_MIXER_THREADS
  int i;
 
  // Kill all existing threads first
  if (mixer->thread_count) {
    fluid_atomic_int_set(&mixer->threads_should_terminate, 1);
    // Signal threads to wake up
    fluid_cond_mutex_lock(mixer->wakeup_threads_m);
    for (i=0; i < mixer->thread_count; i++)
      fluid_atomic_int_set(&mixer->threads[i].ready, THREAD_BUF_TERMINATE);
    fluid_cond_broadcast(&mixer->wakeup_threads);
    fluid_cond_mutex_unlock(mixer->wakeup_threads_m);
  
    for (i=0; i < mixer->thread_count; i++) {
      if (mixer->threads[i].thread) {
        fluid_thread_join(mixer->threads[i].thread);
        delete_fluid_thread(mixer->threads[i].thread);
      }
      fluid_mixer_buffers_free(&mixer->threads[i]);
    }
    FLUID_FREE(mixer->threads);
    mixer->thread_count = 0;
    mixer->threads = NULL;
  }
  
  if (thread_count == 0) 
    return;
  
  // Now prepare the new threads
  fluid_atomic_int_set(&mixer->threads_should_terminate, 0);
  mixer->threads = FLUID_ARRAY(fluid_mixer_buffers_t, thread_count);
  if (mixer->threads == NULL) {
    FLUID_LOG(FLUID_ERR, "Out of memory");
    return;
  }
  FLUID_MEMSET(mixer->threads, 0, thread_count*sizeof(fluid_mixer_buffers_t));
  mixer->thread_count = thread_count;
  for (i=0; i < thread_count; i++) {
    fluid_mixer_buffers_t* b = &mixer->threads[i]; 
    if (!fluid_mixer_buffers_init(b, mixer))
      return;
    fluid_atomic_int_set(&b->ready, THREAD_BUF_NODATA);
    b->thread = new_fluid_thread(fluid_mixer_thread_func, b, prio_level, 0);
    if (!b->thread)
      return;
  }

#endif
}

/**
 * Synthesize audio into buffers
 * @param blockcount number of blocks to render, each having FLUID_BUFSIZE samples 
 * @return number of blocks rendered
 */
int 
fluid_rvoice_mixer_render(fluid_rvoice_mixer_t* mixer, int blockcount)
{
  fluid_profile_ref_var(prof_ref);
  
  mixer->current_blockcount = blockcount > mixer->buffers.buf_blocks ? 
      mixer->buffers.buf_blocks : blockcount;

  // Zero buffers
  fluid_mixer_buffers_zero(&mixer->buffers);
  fluid_profile(FLUID_PROF_ONE_BLOCK_CLEAR, prof_ref);
  
#ifdef ENABLE_MIXER_THREADS
  if (mixer->thread_count > 0)
    fluid_render_loop_multithread(mixer);
  else
#endif
    fluid_render_loop_singlethread(mixer);
  fluid_profile(FLUID_PROF_ONE_BLOCK_VOICES, prof_ref);
    

  // Process reverb & chorus
  fluid_rvoice_mixer_process_fx(mixer);

  // Call the callback and pack active voice array
  fluid_rvoice_mixer_process_finished_voices(mixer);

  return mixer->current_blockcount;
}
