/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "fluid_rvoice_mixer.h" #include "fluid_rvoice.h" #include "fluid_sys.h" #include "fluid_rev.h" #include "fluid_chorus.h" #include "fluid_synth.h" // 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 */ #if ENABLE_MIXER_THREADS fluid_thread_t *thread; /**< Thread object */ fluid_atomic_int_t ready; /**< Atomic: buffers are ready for mixing */ #endif fluid_rvoice_t **finished_voices; /* List of voices who have finished */ int finished_voice_count; fluid_real_t *local_buf; int buf_count; int fx_buf_count; /** buffer to store the left part of a stereo channel to. * Specifically a two dimensional array, containing \c buf_count sample buffers * (i.e. for each synth.audio-channels), of which each contains * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT audio items (=samples) * @note Each sample buffer is aligned to the FLUID_DEFAULT_ALIGNMENT * boundary provided that this pointer points to an aligned buffer. * So make sure to access the sample buffer by first aligning this * pointer using fluid_align_ptr() */ fluid_real_t *left_buf; /** dito, but for right part of a stereo channel */ fluid_real_t *right_buf; /** buffer to store the left part of a stereo effects channel to. * Specifically a two dimensional array, containing \c fx_buf_count buffers * (i.e. for each synth.effects-channels), of which each buffer contains * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT audio items (=samples) */ 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 */ }; struct _fluid_rvoice_mixer_t { fluid_mixer_fx_t *fx; fluid_mixer_buffers_t buffers; /**< Used by mixer only: own buffers */ fluid_rvoice_eventhandler_t *eventhandler; 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 */ int fx_units; 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? */ #ifdef LADSPA fluid_ladspa_fx_t *ladspa_fx; /**< Used by mixer only: Effects unit for LADSPA support. Never created or freed */ #endif #if ENABLE_MIXER_THREADS // int sleeping_threads; /**< Atomic: number of threads currently asleep */ // int active_threads; /**< Atomic: number of threads in the thread loop */ fluid_atomic_int_t threads_should_terminate; /**< Atomic: Set to TRUE when threads should terminate */ fluid_atomic_int_t 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 }; #if ENABLE_MIXER_THREADS static void delete_rvoice_mixer_threads(fluid_rvoice_mixer_t *mixer); static int fluid_rvoice_mixer_set_threads(fluid_rvoice_mixer_t *mixer, int thread_count, int prio_level); #endif static FLUID_INLINE void fluid_rvoice_mixer_process_fx(fluid_rvoice_mixer_t *mixer, int current_blockcount) { const int fx_channels_per_unit = mixer->buffers.fx_buf_count / mixer->fx_units; int i, f; void (*reverb_process_func)(fluid_revmodel_t *rev, const fluid_real_t *in, fluid_real_t *left_out, fluid_real_t *right_out); void (*chorus_process_func)(fluid_chorus_t *chorus, const fluid_real_t *in, fluid_real_t *left_out, fluid_real_t *right_out); fluid_real_t *out_rev_l, *out_rev_r, *out_ch_l, *out_ch_r; // all dry unprocessed mono input is stored in the left channel fluid_real_t *in_rev = fluid_align_ptr(mixer->buffers.fx_left_buf, FLUID_DEFAULT_ALIGNMENT); fluid_real_t *in_ch = in_rev; fluid_profile_ref_var(prof_ref); if(mixer->mix_fx_to_out) { // mix effects to first stereo channel out_ch_l = out_rev_l = fluid_align_ptr(mixer->buffers.left_buf, FLUID_DEFAULT_ALIGNMENT); out_ch_r = out_rev_r = fluid_align_ptr(mixer->buffers.right_buf, FLUID_DEFAULT_ALIGNMENT); reverb_process_func = fluid_revmodel_processmix; chorus_process_func = fluid_chorus_processmix; } else { // replace effects into respective stereo effects channel out_ch_l = out_rev_l = fluid_align_ptr(mixer->buffers.fx_left_buf, FLUID_DEFAULT_ALIGNMENT); out_ch_r = out_rev_r = fluid_align_ptr(mixer->buffers.fx_right_buf, FLUID_DEFAULT_ALIGNMENT); reverb_process_func = fluid_revmodel_processreplace; chorus_process_func = fluid_chorus_processreplace; } if(mixer->with_reverb) { for(f = 0; f < mixer->fx_units; f++) { int buf_idx = f * fx_channels_per_unit + SYNTH_REVERB_CHANNEL; for(i = 0; i < current_blockcount * FLUID_BUFSIZE; i += FLUID_BUFSIZE) { int samp_idx = buf_idx * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE + i; reverb_process_func(mixer->fx[f].reverb, &in_rev[samp_idx], mixer->mix_fx_to_out ? &out_rev_l[i] : &out_rev_l[samp_idx], mixer->mix_fx_to_out ? &out_rev_r[i] : &out_rev_r[samp_idx]); } } fluid_profile(FLUID_PROF_ONE_BLOCK_REVERB, prof_ref, 0, current_blockcount * FLUID_BUFSIZE); } if(mixer->with_chorus) { for(f = 0; f < mixer->fx_units; f++) { int buf_idx = f * fx_channels_per_unit + SYNTH_CHORUS_CHANNEL; for(i = 0; i < current_blockcount * FLUID_BUFSIZE; i += FLUID_BUFSIZE) { int samp_idx = buf_idx * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE + i; chorus_process_func(mixer->fx[f].chorus, &in_ch [samp_idx], mixer->mix_fx_to_out ? &out_ch_l[i] : &out_ch_l[samp_idx], mixer->mix_fx_to_out ? &out_ch_r[i] : &out_ch_r[samp_idx]); } } fluid_profile(FLUID_PROF_ONE_BLOCK_CHORUS, prof_ref, 0, current_blockcount * FLUID_BUFSIZE); } #ifdef LADSPA /* Run the signal through the LADSPA Fx unit. The buffers have already been * set up in fluid_rvoice_mixer_set_ladspa. */ if(mixer->ladspa_fx) { fluid_ladspa_run(mixer->ladspa_fx, current_blockcount, FLUID_BUFSIZE); fluid_check_fpe("LADSPA"); } #endif } /** * 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 *base_ptr; int i; const int fx_channels_per_unit = buffers->fx_buf_count / buffers->mixer->fx_units; const int offset = buffers->buf_count * 2; int with_reverb = buffers->mixer->with_reverb; int with_chorus = buffers->mixer->with_chorus; /* Set up the reverb and chorus buffers only when the effect is enabled or * when LADSPA is active. Nonexisting buffers are detected in the DSP loop. * Not sending the effect signals saves some time in that case. */ #ifdef LADSPA int with_ladspa = (buffers->mixer->ladspa_fx != NULL); with_reverb = (with_reverb | with_ladspa); with_chorus = (with_chorus | with_ladspa); #endif // all the dry, non-processed mono audio for effects is to be stored in the left buffers base_ptr = fluid_align_ptr(buffers->fx_left_buf, FLUID_DEFAULT_ALIGNMENT); for(i = 0; i < buffers->mixer->fx_units; i++) { int fx_idx = i * fx_channels_per_unit; outbufs[offset + fx_idx + SYNTH_REVERB_CHANNEL] = (with_reverb) ? &base_ptr[(fx_idx + SYNTH_REVERB_CHANNEL) * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT] : NULL; outbufs[offset + fx_idx + SYNTH_CHORUS_CHANNEL] = (with_chorus) ? &base_ptr[(fx_idx + SYNTH_CHORUS_CHANNEL) * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT] : NULL; } /* 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. */ base_ptr = fluid_align_ptr(buffers->left_buf, FLUID_DEFAULT_ALIGNMENT); for(i = 0; i < buffers->buf_count; i++) { outbufs[i * 2] = &base_ptr[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT]; } base_ptr = fluid_align_ptr(buffers->right_buf, FLUID_DEFAULT_ALIGNMENT); for(i = 0; i < buffers->buf_count; i++) { outbufs[i * 2 + 1] = &base_ptr[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT]; } return offset + buffers->fx_buf_count; } 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]; } } } buffers->mixer->active_voices = av; fluid_rvoice_eventhandler_finished_voice_callback(buffers->mixer->eventhandler, v); } buffers->finished_voice_count = 0; } static FLUID_INLINE void fluid_rvoice_mixer_process_finished_voices(fluid_rvoice_mixer_t *mixer) { #if 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 fluid_real_t * get_dest_buf(fluid_rvoice_buffers_t *buffers, int index, fluid_real_t **dest_bufs, int dest_bufcount) { int j = buffers->bufs[index].mapping; if(j >= dest_bufcount || j < 0) { return NULL; } return dest_bufs[j]; } /** * Mix samples down from internal dsp_buf to output buffers * * @param buffers Destination buffer(s) * @param dsp_buf Mono sample source * @param start_block starting sample in dsp_buf * @param sample_count number of samples to mix following \c start_block * @param dest_bufs Array of buffers to mixdown to * @param dest_bufcount Length of dest_bufs (i.e count of buffers) */ static void fluid_rvoice_buffers_mix(fluid_rvoice_buffers_t *buffers, const fluid_real_t *FLUID_RESTRICT dsp_buf, int start_block, int sample_count, fluid_real_t **dest_bufs, int dest_bufcount) { /* buffers count to mixdown to */ int bufcount = buffers->count; int i, dsp_i; /* if there is nothing to mix, return immediatly */ if(sample_count <= 0 || dest_bufcount <= 0) { return; } FLUID_ASSERT((uintptr_t)dsp_buf % FLUID_DEFAULT_ALIGNMENT == 0); FLUID_ASSERT((uintptr_t)(&dsp_buf[start_block * FLUID_BUFSIZE]) % FLUID_DEFAULT_ALIGNMENT == 0); /* mixdown for each buffer */ for(i = 0; i < bufcount; i++) { fluid_real_t *FLUID_RESTRICT buf = get_dest_buf(buffers, i, dest_bufs, dest_bufcount); fluid_real_t amp = buffers->bufs[i].amp; if(buf == NULL || amp == 0.0f) { continue; } FLUID_ASSERT((uintptr_t)buf % FLUID_DEFAULT_ALIGNMENT == 0); /* mixdown sample_count samples in the current buffer buf Note, that this loop could be unrolled by FLUID_BUFSIZE elements */ #pragma omp simd aligned(dsp_buf,buf:FLUID_DEFAULT_ALIGNMENT) for(dsp_i = 0; dsp_i < sample_count; dsp_i++) { // Index by blocks (not by samples) to let the compiler know that we always start accessing // buf and dsp_buf at the FLUID_BUFSIZE*sizeof(fluid_real_t) byte boundary and never somewhere // in between. // A good compiler should understand: Aha, so I don't need to add a peel loop when vectorizing // this loop. Great. buf[start_block * FLUID_BUFSIZE + dsp_i] += amp * dsp_buf[start_block * FLUID_BUFSIZE + dsp_i]; } } } /** * 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. */ static FLUID_INLINE void fluid_mixer_buffers_render_one(fluid_mixer_buffers_t *buffers, fluid_rvoice_t *rvoice, fluid_real_t **dest_bufs, unsigned int dest_bufcount, fluid_real_t *src_buf, int blockcount) { int i, total_samples = 0, last_block_mixed = 0; for(i = 0; i < blockcount; i++) { /* render one block in src_buf */ int s = fluid_rvoice_write(rvoice, &src_buf[FLUID_BUFSIZE * i]); if(s == -1) { /* the voice is silent, mix back all the previously rendered sound */ fluid_rvoice_buffers_mix(&rvoice->buffers, src_buf, last_block_mixed, total_samples - (last_block_mixed*FLUID_BUFSIZE), dest_bufs, dest_bufcount); last_block_mixed = i+1; /* future block start index to mix from */ total_samples += FLUID_BUFSIZE; /* accumulate samples count rendered */ } else { /* the voice wasn't quiet. Some samples have been rendered [0..FLUID_BUFSIZE] */ total_samples += s; if(s < FLUID_BUFSIZE) { /* voice has finished */ break; } } } /* Now mix the remaining blocks from last_block_mixed to total_sample */ fluid_rvoice_buffers_mix(&rvoice->buffers, src_buf, last_block_mixed, total_samples - (last_block_mixed*FLUID_BUFSIZE), dest_bufs, dest_bufcount); if(total_samples < blockcount * FLUID_BUFSIZE) { /* voice has finished */ fluid_finish_rvoice(buffers, rvoice); } } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_add_voice) { int i; fluid_rvoice_mixer_t *mixer = obj; fluid_rvoice_t *voice = param[0].ptr; if(mixer->active_voices < mixer->polyphony) { mixer->rvoices[mixer->active_voices++] = voice; return; // success } /* 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; } if(mixer->rvoices[i]->envlfo.volenv.section == FLUID_VOICE_ENVFINISHED) { fluid_finish_rvoice(&mixer->buffers, mixer->rvoices[i]); mixer->rvoices[i] = voice; return; // success } } /* This should never happen */ FLUID_LOG(FLUID_ERR, "Trying to exceed polyphony in fluid_rvoice_mixer_add_voice"); return; } 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 */ DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_set_polyphony) { void *newptr; fluid_rvoice_mixer_t *handler = obj; int value = param[0].i; 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*/; } #if 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 blockcount) { 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_real_t *local_buf = fluid_align_ptr(mixer->buffers.local_buf, FLUID_DEFAULT_ALIGNMENT); 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, local_buf, blockcount); fluid_profile(FLUID_PROF_ONE_BLOCK_VOICE, prof_ref, 1, blockcount * FLUID_BUFSIZE); } } static FLUID_INLINE void fluid_mixer_buffers_zero(fluid_mixer_buffers_t *buffers, int current_blockcount) { int i, size = current_blockcount * FLUID_BUFSIZE * sizeof(fluid_real_t); /* TODO: Optimize by only zero out the buffers we actually use later on. */ int buf_count = buffers->buf_count, fx_buf_count = buffers->fx_buf_count; fluid_real_t *FLUID_RESTRICT buf_l = fluid_align_ptr(buffers->left_buf, FLUID_DEFAULT_ALIGNMENT); fluid_real_t *FLUID_RESTRICT buf_r = fluid_align_ptr(buffers->right_buf, FLUID_DEFAULT_ALIGNMENT); for(i = 0; i < buf_count; i++) { FLUID_MEMSET(&buf_l[i * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE], 0, size); FLUID_MEMSET(&buf_r[i * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE], 0, size); } buf_l = fluid_align_ptr(buffers->fx_left_buf, FLUID_DEFAULT_ALIGNMENT); buf_r = fluid_align_ptr(buffers->fx_right_buf, FLUID_DEFAULT_ALIGNMENT); for(i = 0; i < fx_buf_count; i++) { FLUID_MEMSET(&buf_l[i * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE], 0, size); FLUID_MEMSET(&buf_r[i * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE], 0, size); } } static int fluid_mixer_buffers_init(fluid_mixer_buffers_t *buffers, fluid_rvoice_mixer_t *mixer) { static const int samplecount = FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT; buffers->mixer = mixer; buffers->buf_count = mixer->buffers.buf_count; buffers->fx_buf_count = mixer->buffers.fx_buf_count; /* Local mono voice buf */ buffers->local_buf = FLUID_ARRAY_ALIGNED(fluid_real_t, samplecount, FLUID_DEFAULT_ALIGNMENT); /* Left and right audio buffers */ buffers->left_buf = FLUID_ARRAY_ALIGNED(fluid_real_t, buffers->buf_count * samplecount, FLUID_DEFAULT_ALIGNMENT); buffers->right_buf = FLUID_ARRAY_ALIGNED(fluid_real_t, buffers->buf_count * samplecount, FLUID_DEFAULT_ALIGNMENT); if((buffers->local_buf == NULL) || (buffers->left_buf == NULL) || (buffers->right_buf == NULL)) { FLUID_LOG(FLUID_ERR, "Out of memory"); return 0; } /* Effects audio buffers */ buffers->fx_left_buf = FLUID_ARRAY_ALIGNED(fluid_real_t, buffers->fx_buf_count * samplecount, FLUID_DEFAULT_ALIGNMENT); buffers->fx_right_buf = FLUID_ARRAY_ALIGNED(fluid_real_t, buffers->fx_buf_count * samplecount, FLUID_DEFAULT_ALIGNMENT); if((buffers->fx_left_buf == NULL) || (buffers->fx_right_buf == 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) */ DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_set_samplerate) { fluid_rvoice_mixer_t *mixer = obj; fluid_real_t samplerate = param[1].real; // becausee fluid_synth_update_mixer() puts real into arg2 int i; for(i = 0; i < mixer->fx_units; i++) { if(mixer->fx[i].chorus) { delete_fluid_chorus(mixer->fx[i].chorus); } mixer->fx[i].chorus = new_fluid_chorus(samplerate); if(mixer->fx[i].reverb) { fluid_revmodel_samplerate_change(mixer->fx[i].reverb, samplerate); } } #if LADSPA if(mixer->ladspa_fx != NULL) { fluid_ladspa_set_sample_rate(mixer->ladspa_fx, samplerate); } #endif } /** * @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, int fx_units, fluid_real_t sample_rate, fluid_rvoice_eventhandler_t *evthandler, int extra_threads, int prio) { int i; 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->eventhandler = evthandler; mixer->fx_units = fx_units; mixer->buffers.buf_count = buf_count; mixer->buffers.fx_buf_count = fx_buf_count * fx_units; /* allocate the reverb module */ mixer->fx = FLUID_ARRAY(fluid_mixer_fx_t, fx_units); if(mixer->fx == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); goto error_recovery; } FLUID_MEMSET(mixer->fx, 0, fx_units * sizeof(*mixer->fx)); for(i = 0; i < fx_units; i++) { mixer->fx[i].reverb = new_fluid_revmodel(sample_rate); mixer->fx[i].chorus = new_fluid_chorus(sample_rate); if(mixer->fx[i].reverb == NULL || mixer->fx[i].chorus == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); goto error_recovery; } } if(!fluid_mixer_buffers_init(&mixer->buffers, mixer)) { goto error_recovery; } #if 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) { goto error_recovery; } if(fluid_rvoice_mixer_set_threads(mixer, extra_threads, prio) != FLUID_OK) { goto error_recovery; } #endif return mixer; error_recovery: delete_fluid_rvoice_mixer(mixer); return NULL; } static void fluid_mixer_buffers_free(fluid_mixer_buffers_t *buffers) { FLUID_FREE(buffers->finished_voices); /* free all the sample buffers */ FLUID_FREE(buffers->local_buf); FLUID_FREE(buffers->left_buf); FLUID_FREE(buffers->right_buf); FLUID_FREE(buffers->fx_left_buf); FLUID_FREE(buffers->fx_right_buf); } void delete_fluid_rvoice_mixer(fluid_rvoice_mixer_t *mixer) { int i; fluid_return_if_fail(mixer != NULL); #if ENABLE_MIXER_THREADS delete_rvoice_mixer_threads(mixer); 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); for(i = 0; i < mixer->fx_units; i++) { if(mixer->fx[i].reverb) { delete_fluid_revmodel(mixer->fx[i].reverb); } if(mixer->fx[i].chorus) { delete_fluid_chorus(mixer->fx[i].chorus); } } FLUID_FREE(mixer->fx); FLUID_FREE(mixer->rvoices); FLUID_FREE(mixer); } #ifdef LADSPA /** * Set a LADSPS fx instance to be used by the mixer and assign the mixer buffers * as LADSPA host buffers with sensible names */ void fluid_rvoice_mixer_set_ladspa(fluid_rvoice_mixer_t *mixer, fluid_ladspa_fx_t *ladspa_fx, int audio_groups) { mixer->ladspa_fx = ladspa_fx; if(ladspa_fx == NULL) { return; } else { fluid_real_t *main_l = fluid_align_ptr(mixer->buffers.left_buf, FLUID_DEFAULT_ALIGNMENT); fluid_real_t *main_r = fluid_align_ptr(mixer->buffers.right_buf, FLUID_DEFAULT_ALIGNMENT); fluid_real_t *rev = fluid_align_ptr(mixer->buffers.fx_left_buf, FLUID_DEFAULT_ALIGNMENT); fluid_real_t *chor = rev; rev = &rev[SYNTH_REVERB_CHANNEL * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT]; chor = &chor[SYNTH_CHORUS_CHANNEL * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT]; fluid_ladspa_add_host_ports(ladspa_fx, "Main:L", audio_groups, main_l, FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT); fluid_ladspa_add_host_ports(ladspa_fx, "Main:R", audio_groups, main_r, FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT); fluid_ladspa_add_host_ports(ladspa_fx, "Reverb:Send", 1, rev, FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT); fluid_ladspa_add_host_ports(ladspa_fx, "Chorus:Send", 1, chor, FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT); } } #endif DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_set_reverb_enabled) { fluid_rvoice_mixer_t *mixer = obj; int on = param[0].i; mixer->with_reverb = on; } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_set_chorus_enabled) { fluid_rvoice_mixer_t *mixer = obj; int on = param[0].i; mixer->with_chorus = on; } void fluid_rvoice_mixer_set_mix_fx(fluid_rvoice_mixer_t *mixer, int on) { mixer->mix_fx_to_out = on; } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_set_chorus_params) { fluid_rvoice_mixer_t *mixer = obj; int set = param[0].i; int nr = param[1].i; fluid_real_t level = param[2].real; fluid_real_t speed = param[3].real; fluid_real_t depth_ms = param[4].real; int type = param[5].i; int i; for(i = 0; i < mixer->fx_units; i++) { fluid_chorus_set(mixer->fx[i].chorus, set, nr, level, speed, depth_ms, type); } } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_set_reverb_params) { fluid_rvoice_mixer_t *mixer = obj; int set = param[0].i; fluid_real_t roomsize = param[1].real; fluid_real_t damping = param[2].real; fluid_real_t width = param[3].real; fluid_real_t level = param[4].real; int i; for(i = 0; i < mixer->fx_units; i++) { fluid_revmodel_set(mixer->fx[i].reverb, set, roomsize, damping, width, level); } } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_reset_reverb) { fluid_rvoice_mixer_t *mixer = obj; int i; for(i = 0; i < mixer->fx_units; i++) { fluid_revmodel_reset(mixer->fx[i].reverb); } } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_reset_chorus) { fluid_rvoice_mixer_t *mixer = obj; int i; for(i = 0; i < mixer->fx_units; i++) { fluid_chorus_reset(mixer->fx[i].chorus); } } int fluid_rvoice_mixer_get_bufs(fluid_rvoice_mixer_t *mixer, fluid_real_t **left, fluid_real_t **right) { *left = fluid_align_ptr(mixer->buffers.left_buf, FLUID_DEFAULT_ALIGNMENT); *right = fluid_align_ptr(mixer->buffers.right_buf, FLUID_DEFAULT_ALIGNMENT); return mixer->buffers.buf_count; } int fluid_rvoice_mixer_get_fx_bufs(fluid_rvoice_mixer_t *mixer, fluid_real_t **fx_left, fluid_real_t **fx_right) { *fx_left = fluid_align_ptr(mixer->buffers.fx_left_buf, FLUID_DEFAULT_ALIGNMENT); *fx_right = fluid_align_ptr(mixer->buffers.fx_right_buf, FLUID_DEFAULT_ALIGNMENT); return mixer->buffers.fx_buf_count; } int fluid_rvoice_mixer_get_bufcount(fluid_rvoice_mixer_t *mixer) { return FLUID_MIXER_MAX_BUFFERS_DEFAULT; } #if WITH_PROFILING int fluid_rvoice_mixer_get_active_voices(fluid_rvoice_mixer_t *mixer) { return mixer->active_voices; } #endif #if 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 fluid_thread_return_t 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; int current_blockcount = 0; fluid_real_t *local_buf = fluid_align_ptr(buffers->local_buf, FLUID_DEFAULT_ALIGNMENT); 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) { // blockcount may have changed, since thread was put to sleep current_blockcount = mixer->current_blockcount; fluid_mixer_buffers_zero(buffers, current_blockcount); bufcount = fluid_mixer_buffers_prepare(buffers, bufs); hasValidData = 1; } // then render voice to buffers fluid_mixer_buffers_render_one(buffers, rvoice, bufs, bufcount, local_buf, current_blockcount); } } return FLUID_THREAD_RETURN_VALUE; } static void fluid_mixer_buffers_mix(fluid_mixer_buffers_t *dst, fluid_mixer_buffers_t *src, int current_blockcount) { int i, j; int scount = current_blockcount * FLUID_BUFSIZE; int minbuf; fluid_real_t *FLUID_RESTRICT base_src; fluid_real_t *FLUID_RESTRICT base_dst; minbuf = dst->buf_count; if(minbuf > src->buf_count) { minbuf = src->buf_count; } base_src = fluid_align_ptr(src->left_buf, FLUID_DEFAULT_ALIGNMENT); base_dst = fluid_align_ptr(dst->left_buf, FLUID_DEFAULT_ALIGNMENT); for(i = 0; i < minbuf; i++) { #pragma omp simd aligned(base_dst,base_src:FLUID_DEFAULT_ALIGNMENT) for(j = 0; j < scount; j++) { int dsp_i = i * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE + j; base_dst[dsp_i] += base_src[dsp_i]; } } base_src = fluid_align_ptr(src->right_buf, FLUID_DEFAULT_ALIGNMENT); base_dst = fluid_align_ptr(dst->right_buf, FLUID_DEFAULT_ALIGNMENT); for(i = 0; i < minbuf; i++) { #pragma omp simd aligned(base_dst,base_src:FLUID_DEFAULT_ALIGNMENT) for(j = 0; j < scount; j++) { int dsp_i = i * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE + j; base_dst[dsp_i] += base_src[dsp_i]; } } minbuf = dst->fx_buf_count; if(minbuf > src->fx_buf_count) { minbuf = src->fx_buf_count; } base_src = fluid_align_ptr(src->fx_left_buf, FLUID_DEFAULT_ALIGNMENT); base_dst = fluid_align_ptr(dst->fx_left_buf, FLUID_DEFAULT_ALIGNMENT); for(i = 0; i < minbuf; i++) { #pragma omp simd aligned(base_dst,base_src:FLUID_DEFAULT_ALIGNMENT) for(j = 0; j < scount; j++) { int dsp_i = i * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE + j; base_dst[dsp_i] += base_src[dsp_i]; } } base_src = fluid_align_ptr(src->fx_right_buf, FLUID_DEFAULT_ALIGNMENT); base_dst = fluid_align_ptr(dst->fx_right_buf, FLUID_DEFAULT_ALIGNMENT); for(i = 0; i < minbuf; i++) { #pragma omp simd aligned(base_dst,base_src:FLUID_DEFAULT_ALIGNMENT) for(j = 0; j < scount; j++) { int dsp_i = i * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE + j; base_dst[dsp_i] += base_src[dsp_i]; } } } /** * Go through all threads and see if someone is finished for mixing */ static int fluid_mixer_mix_in(fluid_rvoice_mixer_t *mixer, int extra_threads, int current_blockcount) { 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], current_blockcount); hasmixed = 1; break; } } } while(hasmixed); return result; } static void fluid_render_loop_multithread(fluid_rvoice_mixer_t *mixer, int current_blockcount) { int i, bufcount; fluid_real_t *local_buf = fluid_align_ptr(mixer->buffers.local_buf, FLUID_DEFAULT_ALIGNMENT); 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, current_blockcount); 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, current_blockcount)) { // 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, local_buf, current_blockcount); fluid_profile(FLUID_PROF_ONE_BLOCK_VOICE, prof_ref, 1, current_blockcount * FLUID_BUFSIZE); //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", // current_blockcount, test, mixer->active_voices, waits); } static void delete_rvoice_mixer_threads(fluid_rvoice_mixer_t *mixer) { int i; 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; } /** * 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 */ static int fluid_rvoice_mixer_set_threads(fluid_rvoice_mixer_t *mixer, int thread_count, int prio_level) { char name[16]; int i; // Kill all existing threads first if(mixer->thread_count) { delete_rvoice_mixer_threads(mixer); } if(thread_count == 0) { return FLUID_OK; } // 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_FAILED; } 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_FAILED; } fluid_atomic_int_set(&b->ready, THREAD_BUF_NODATA); FLUID_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 FLUID_FAILED; } } return FLUID_OK; } #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; // Zero buffers fluid_mixer_buffers_zero(&mixer->buffers, blockcount); fluid_profile(FLUID_PROF_ONE_BLOCK_CLEAR, prof_ref, mixer->active_voices, blockcount * FLUID_BUFSIZE); #if ENABLE_MIXER_THREADS if(mixer->thread_count > 0) { fluid_render_loop_multithread(mixer, blockcount); } else #endif { fluid_render_loop_singlethread(mixer, blockcount); } fluid_profile(FLUID_PROF_ONE_BLOCK_VOICES, prof_ref, mixer->active_voices, blockcount * FLUID_BUFSIZE); // Process reverb & chorus fluid_rvoice_mixer_process_fx(mixer, blockcount); // Call the callback and pack active voice array fluid_rvoice_mixer_process_finished_voices(mixer); return blockcount; }