diff options
Diffstat (limited to 'libs/fluidsynth/src/fluid_rvoice_mixer.c')
-rw-r--r-- | libs/fluidsynth/src/fluid_rvoice_mixer.c | 974 |
1 files changed, 974 insertions, 0 deletions
diff --git a/libs/fluidsynth/src/fluid_rvoice_mixer.c b/libs/fluidsynth/src/fluid_rvoice_mixer.c new file mode 100644 index 0000000000..cc633f510b --- /dev/null +++ b/libs/fluidsynth/src/fluid_rvoice_mixer.c @@ -0,0 +1,974 @@ +/* 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 + mixer->thread_ready = new_fluid_cond(); + mixer->wakeup_threads = new_fluid_cond(); + mixer->thread_ready_m = new_fluid_cond_mutex(); + mixer->wakeup_threads_m = new_fluid_cond_mutex(); + if (!mixer->thread_ready || !mixer->wakeup_threads || + !mixer->thread_ready_m || !mixer->wakeup_threads_m) { + 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) + delete_fluid_cond(mixer->thread_ready); + if (mixer->wakeup_threads) + delete_fluid_cond(mixer->wakeup_threads); + if (mixer->thread_ready_m) + delete_fluid_cond_mutex(mixer->thread_ready_m); + if (mixer->wakeup_threads_m) + 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->rvoices); + 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); +} + +void fluid_rvoice_mixer_reset_reverb(fluid_rvoice_mixer_t* mixer) +{ + fluid_revmodel_reset(mixer->fx.reverb); +} + +void fluid_rvoice_mixer_reset_chorus(fluid_rvoice_mixer_t* mixer) +{ + 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 + char name[16]; + 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); + g_snprintf (name, sizeof (name), "mixer%d", i); + b->thread = new_fluid_thread(name, 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; +} |