示例#1
0
int32_t Rangemap_vstate_render_voice(
        Voice_state* vstate,
        Proc_state* proc_state,
        const Device_thread_state* proc_ts,
        const Au_state* au_state,
        const Work_buffers* wbs,
        int32_t frame_count,
        double tempo)
{
    rassert(vstate == NULL);
    rassert(proc_state != NULL);
    rassert(proc_ts != NULL);
    rassert(au_state != NULL);
    rassert(wbs != NULL);
    rassert(frame_count > 0);
    rassert(isfinite(tempo));
    rassert(tempo > 0);

    const Proc_rangemap* rangemap =
        (const Proc_rangemap*)proc_state->parent.device->dimpl;

    float mul = 0;
    float add = 0;
    get_scalars(
            &mul,
            &add,
            rangemap->from_min, rangemap->from_max,
            rangemap->min_to, rangemap->max_to);

    const double range_min = min(rangemap->min_to, rangemap->max_to);
    const double range_max = max(rangemap->min_to, rangemap->max_to);

    const float min_val = (float)(rangemap->clamp_dest_min ? range_min : -INFINITY);
    const float max_val = (float)(rangemap->clamp_dest_max ? range_max : INFINITY);

    Work_buffer* in_wb = Proc_get_voice_input_2ch(proc_ts, 0, frame_count);
    if (in_wb == NULL)
    {
        in_wb = Work_buffers_get_buffer_mut(wbs, RANGEMAP_WB_FIXED_INPUT, 2);
        Work_buffer_clear_all(in_wb, 0, frame_count);
    }

    Work_buffer* out_wb = Proc_get_voice_output_2ch(proc_ts, 0);
    rassert(out_wb != NULL);

    apply_range(in_wb, out_wb, frame_count, mul, add, min_val, max_val);

    return frame_count;
}
示例#2
0
static int32_t Padsynth_vstate_render_voice(
        Voice_state* vstate,
        Proc_state* proc_state,
        const Device_thread_state* proc_ts,
        const Au_state* au_state,
        const Work_buffers* wbs,
        int32_t buf_start,
        int32_t buf_stop,
        double tempo)
{
    rassert(vstate != NULL);
    rassert(proc_state != NULL);
    rassert(proc_ts != NULL);
    rassert(au_state != NULL);
    rassert(wbs != NULL);
    rassert(tempo > 0);

    if (buf_start == buf_stop)
        return buf_start;

    const Device_state* dstate = &proc_state->parent;

    Padsynth_vstate* ps_vstate = (Padsynth_vstate*)vstate;

    const Proc_padsynth* ps = (const Proc_padsynth*)dstate->device->dimpl;
    if (ps->sample_map == NULL)
    {
        vstate->active = false;
        return buf_start;
    }

    // Get frequencies
    Work_buffer* freqs_wb = Device_thread_state_get_voice_buffer(
            proc_ts, DEVICE_PORT_TYPE_RECV, PORT_IN_PITCH);
    Work_buffer* pitches_wb = freqs_wb;

    if (isnan(ps_vstate->init_pitch))
        ps_vstate->init_pitch =
            (pitches_wb != NULL) ? Work_buffer_get_contents(pitches_wb)[buf_start] : 0;

    if (freqs_wb == NULL)
        freqs_wb = Work_buffers_get_buffer_mut(wbs, PADSYNTH_WB_FIXED_PITCH);
    Proc_fill_freq_buffer(freqs_wb, pitches_wb, buf_start, buf_stop);
    const float* freqs = Work_buffer_get_contents(freqs_wb);

    // Get volume scales
    Work_buffer* scales_wb = Device_thread_state_get_voice_buffer(
            proc_ts, DEVICE_PORT_TYPE_RECV, PORT_IN_FORCE);
    Work_buffer* dBs_wb = scales_wb;
    if (scales_wb == NULL)
        scales_wb = Work_buffers_get_buffer_mut(wbs, PADSYNTH_WB_FIXED_FORCE);
    Proc_fill_scale_buffer(scales_wb, dBs_wb, buf_start, buf_stop);
    const float* scales = Work_buffer_get_contents(scales_wb);

    // Get output buffer for writing
    float* out_bufs[2] = { NULL };
    Proc_state_get_voice_audio_out_buffers(
            proc_ts, PORT_OUT_AUDIO_L, PORT_OUT_COUNT, out_bufs);

    // Choose our sample
    const Padsynth_sample_entry* entry =
        Padsynth_sample_map_get_entry(ps->sample_map, ps_vstate->init_pitch);
    if (entry == NULL)
    {
        vstate->active = false;
        return buf_start;
    }

    // Render audio
    const float* sample_buf = Sample_get_buffer(entry->sample, 0);
    const int32_t length = Padsynth_sample_map_get_sample_length(ps->sample_map);
    const int32_t sample_rate = PADSYNTH_DEFAULT_AUDIO_RATE;
    const double sample_freq = cents_to_Hz(entry->center_pitch);

    const double audio_rate = dstate->audio_rate;

    const double init_pos = fmod(ps_vstate->pos, length); // the length may have changed
    bool is_state_pos_updated = false;

    for (int32_t ch = 0; ch < 2; ++ch)
    {
        float* out_buf = out_bufs[ch];
        if (out_buf == NULL)
            continue;

        double pos = init_pos;
        if (ps->is_stereo_enabled && is_state_pos_updated)
        {
            pos += (length / 2);
            if (pos >= length)
                pos -= length;
        }

        for (int32_t i = buf_start; i < buf_stop; ++i)
        {
            const float freq = freqs[i];
            const float scale = scales[i];

            const int32_t pos1 = (int32_t)pos;
            const int32_t pos2 = pos1 + 1;
            const double lerp_val = pos - floor(pos);

            const float item1 = sample_buf[pos1];
            const float item2 = sample_buf[pos2];
            const float value = (float)lerp(item1, item2, lerp_val);

            out_buf[i] = value * scale;

            pos += (freq / sample_freq) * (sample_rate / audio_rate);

            while (pos >= length)
                pos -= length;
        }

        if (!ps->is_stereo_enabled || !is_state_pos_updated)
        {
            ps_vstate->pos = pos;
            is_state_pos_updated = true;
        }
    }


    if (ps->is_ramp_attack_enabled)
        Proc_ramp_attack(vstate, 2, out_bufs, buf_start, buf_stop, dstate->audio_rate);

    return buf_stop;
}
示例#3
0
static int32_t Add_vstate_render_voice(
        Voice_state* vstate,
        Proc_state* proc_state,
        const Device_thread_state* proc_ts,
        const Au_state* au_state,
        const Work_buffers* wbs,
        int32_t buf_start,
        int32_t buf_stop,
        double tempo)
{
    rassert(vstate != NULL);
    rassert(proc_state != NULL);
    rassert(proc_ts != NULL);
    rassert(au_state != NULL);
    rassert(wbs != NULL);
    rassert(tempo > 0);

    const Device_state* dstate = &proc_state->parent;
    const Proc_add* add = (Proc_add*)proc_state->parent.device->dimpl;
    Add_vstate* add_state = (Add_vstate*)vstate;
    rassert(is_p2(ADD_BASE_FUNC_SIZE));

    // Get frequencies
    Work_buffer* freqs_wb = Device_thread_state_get_voice_buffer(
            proc_ts, DEVICE_PORT_TYPE_RECV, PORT_IN_PITCH);
    Work_buffer* pitches_wb = freqs_wb;
    if (freqs_wb == NULL)
        freqs_wb = Work_buffers_get_buffer_mut(wbs, ADD_WORK_BUFFER_FIXED_PITCH);
    Proc_fill_freq_buffer(freqs_wb, pitches_wb, buf_start, buf_stop);
    const float* freqs = Work_buffer_get_contents(freqs_wb);

    // Get volume scales
    Work_buffer* scales_wb = Device_thread_state_get_voice_buffer(
            proc_ts, DEVICE_PORT_TYPE_RECV, PORT_IN_FORCE);
    Work_buffer* dBs_wb = scales_wb;
    if (scales_wb == NULL)
        scales_wb = Work_buffers_get_buffer_mut(wbs, ADD_WORK_BUFFER_FIXED_FORCE);
    Proc_fill_scale_buffer(scales_wb, dBs_wb, buf_start, buf_stop);
    const float* scales = Work_buffer_get_contents(scales_wb);

    // Get output buffer for writing
    float* out_bufs[2] = { NULL };
    Proc_state_get_voice_audio_out_buffers(
            proc_ts, PORT_OUT_AUDIO_L, PORT_OUT_COUNT, out_bufs);

    // Get phase modulation signal
    const Work_buffer* mod_wbs[] =
    {
        Device_thread_state_get_voice_buffer(
                proc_ts, DEVICE_PORT_TYPE_RECV, PORT_IN_PHASE_MOD_L),
        Device_thread_state_get_voice_buffer(
                proc_ts, DEVICE_PORT_TYPE_RECV, PORT_IN_PHASE_MOD_R),
    };

    for (int ch = 0; ch < 2; ++ch)
    {
        if (mod_wbs[ch] == NULL)
        {
            Work_buffer* zero_buf = Work_buffers_get_buffer_mut(
                    wbs, (Work_buffer_type)(ADD_WORK_BUFFER_MOD_L + ch));
            Work_buffer_clear(zero_buf, buf_start, buf_stop);
            mod_wbs[ch] = zero_buf;
        }
    }

    // Add base waveform tones
    const double inv_audio_rate = 1.0 / dstate->audio_rate;

    const float* base = Sample_get_buffer(add->base, 0);

    for (int h = 0; h < add_state->tone_limit; ++h)
    {
        const Add_tone* tone = &add->tones[h];
        const double pitch_factor = tone->pitch_factor;
        const double volume_factor = tone->volume_factor;

        if ((pitch_factor <= 0) || (volume_factor <= 0))
            continue;

        const double pannings[] =
        {
            -tone->panning,
            tone->panning,
        };

        const double pitch_factor_inv_audio_rate = pitch_factor * inv_audio_rate;

        Add_tone_state* tone_state = &add_state->tones[h];

        for (int32_t ch = 0; ch < 2; ++ch)
        {
            float* out_buf_ch = out_bufs[ch];
            if (out_buf_ch == NULL)
                continue;

            const double panning_factor = 1 + pannings[ch];
            const float* mod_values_ch = Work_buffer_get_contents(mod_wbs[ch]);

            double phase = tone_state->phase[ch];

            int32_t res_slice_start = buf_start;
            while (res_slice_start < buf_stop)
            {
                int32_t res_slice_stop = buf_stop;

                // Get current pitch range
                const float first_mod_shift =
                    mod_values_ch[res_slice_start] - add_state->prev_mod[ch];
                const float first_phase_shift_abs = (float)fabs(
                        first_mod_shift +
                        (freqs[res_slice_start] * pitch_factor_inv_audio_rate));
                int shift_exp = 0;
                const float shift_norm = frexpf(first_phase_shift_abs, &shift_exp);
                const float min_phase_shift_abs = ldexpf(0.5f, shift_exp);
                const float max_phase_shift_abs = min_phase_shift_abs * 2.0f;

                // Choose appropriate waveform resolution for current pitch range
                int32_t cur_size = ADD_BASE_FUNC_SIZE;
                if (isfinite(shift_norm) && (shift_norm > 0.0f))
                {
                    cur_size = (int32_t)ipowi(2, max(-shift_exp + 1, 3));
                    cur_size = min(cur_size, ADD_BASE_FUNC_SIZE * 2);
                    rassert(is_p2(cur_size));
                }
                const uint32_t cur_size_mask = (uint32_t)cur_size - 1;
                const int base_offset = (ADD_BASE_FUNC_SIZE * 4 - cur_size * 2);
                rassert(base_offset >= 0);
                rassert(base_offset < (ADD_BASE_FUNC_SIZE * 4) - 1);
                const float* cur_base = base + base_offset;

                // Get length of input compatible with current waveform resolution
                const int32_t res_check_stop = min(res_slice_stop,
                        max(Work_buffer_get_const_start(freqs_wb),
                            Work_buffer_get_const_start(mod_wbs[ch])) + 1);
                for (int32_t i = res_slice_start + 1; i < res_check_stop; ++i)
                {
                    const float cur_mod_shift = mod_values_ch[i] - mod_values_ch[i - 1];
                    const float cur_phase_shift_abs = (float)fabs(
                            cur_mod_shift +
                            (freqs[i] * pitch_factor_inv_audio_rate));
                    if (cur_phase_shift_abs < min_phase_shift_abs ||
                            cur_phase_shift_abs > max_phase_shift_abs)
                    {
                        res_slice_stop = i;
                        break;
                    }
                }

                for (int32_t i = res_slice_start; i < res_slice_stop; ++i)
                {
                    const float freq = freqs[i];
                    const float vol_scale = scales[i];
                    const float mod_val = mod_values_ch[i];

                    // Note: + mod_val is specific to phase modulation
                    const double actual_phase = phase + mod_val;
                    const double pos = actual_phase * cur_size;

                    // Note: direct cast of negative doubles to uint32_t is undefined
                    const uint32_t pos1 = (uint32_t)(int32_t)floor(pos) & cur_size_mask;
                    const uint32_t pos2 = (pos1 + 1) & cur_size_mask;

                    const float item1 = cur_base[pos1];
                    const float item_diff = cur_base[pos2] - item1;
                    const double lerp_val = pos - floor(pos);
                    const double value =
                        (item1 + (lerp_val * item_diff)) * volume_factor * panning_factor;

                    out_buf_ch[i] += (float)value * vol_scale;

                    phase += freq * pitch_factor_inv_audio_rate;

                    // Normalise to range [0, 1)
                    if (phase >= 1)
                    {
                        phase -= 1;

                        // Don't bother updating the phase if our frequency is too high
                        if (phase >= 1)
                            phase = tone_state->phase[ch];
                    }
                }

                rassert(res_slice_start < res_slice_stop);
                add_state->prev_mod[ch] = mod_values_ch[res_slice_stop - 1];

                res_slice_start = res_slice_stop;
            }

            tone_state->phase[ch] = phase;
        }
    }

    if (add->is_ramp_attack_enabled)
        Proc_ramp_attack(vstate, 2, out_bufs, buf_start, buf_stop, dstate->audio_rate);

    return buf_stop;
}
示例#4
0
static int32_t Compress_vstate_render_voice(
        Voice_state* vstate,
        Proc_state* proc_state,
        const Device_thread_state* proc_ts,
        const Au_state* au_state,
        const Work_buffers* wbs,
        int32_t buf_start,
        int32_t buf_stop,
        double tempo)
{
    rassert(vstate != NULL);
    rassert(proc_state != NULL);
    rassert(proc_ts != NULL);
    rassert(au_state != NULL);
    rassert(wbs != NULL);
    rassert(buf_start >= 0);
    rassert(buf_stop >= 0);
    rassert(isfinite(tempo));
    rassert(tempo > 0);

    const Device_state* dstate = &proc_state->parent;
    const Proc_compress* compress =
        (const Proc_compress*)proc_state->parent.device->dimpl;

    Compress_vstate* cvstate = (Compress_vstate*)vstate;

    // Get audio input buffers
    const Work_buffer* in_wbs[2] =
    {
        Device_thread_state_get_voice_buffer(
                proc_ts, DEVICE_PORT_TYPE_RECV, PORT_IN_AUDIO_L),
        Device_thread_state_get_voice_buffer(
                proc_ts, DEVICE_PORT_TYPE_RECV, PORT_IN_AUDIO_R),
    };

    // Get audio output buffers
    Work_buffer* out_wbs[2] =
    {
        Device_thread_state_get_voice_buffer(
                proc_ts, DEVICE_PORT_TYPE_SEND, PORT_OUT_AUDIO_L),
        Device_thread_state_get_voice_buffer(
                proc_ts, DEVICE_PORT_TYPE_SEND, PORT_OUT_AUDIO_R),
    };

    // Get level buffers
    Work_buffer* level_wbs[2] =
    {
        Work_buffers_get_buffer_mut(wbs, COMPRESS_WB_LEVEL_L),
        Work_buffers_get_buffer_mut(wbs, COMPRESS_WB_LEVEL_R),
    };

    // Get gain buffer
    Work_buffer* gain_wb = Device_thread_state_get_voice_buffer(
            proc_ts, DEVICE_PORT_TYPE_SEND, PORT_OUT_GAIN);
    if (gain_wb == NULL)
        gain_wb = Work_buffers_get_buffer_mut(wbs, COMPRESS_WB_GAIN);

    Compress_states_update(
            cvstate->cstates,
            compress,
            gain_wb,
            level_wbs,
            in_wbs,
            buf_start,
            buf_stop,
            dstate->audio_rate);

    write_audio(out_wbs, gain_wb, in_wbs, buf_start, buf_stop);

    return buf_stop;
}
示例#5
0
int32_t Noise_vstate_render_voice(
        Voice_state* vstate,
        Proc_state* proc_state,
        const Device_thread_state* proc_ts,
        const Au_state* au_state,
        const Work_buffers* wbs,
        int32_t frame_count,
        double tempo)
{
    rassert(vstate != NULL);
    rassert(proc_state != NULL);
    rassert(proc_ts != NULL);
    rassert(au_state != NULL);
    rassert(wbs != NULL);
    rassert(frame_count > 0);
    rassert(tempo > 0);

//    double max_amp = 0;
//  fprintf(stderr, "bufs are %p and %p\n", ins->bufs[0], ins->bufs[1]);

    // Get volume scales
    Work_buffer* scales_wb = Device_thread_state_get_voice_buffer(
            proc_ts, DEVICE_PORT_TYPE_RECV, PORT_IN_FORCE, NULL);
    Work_buffer* dBs_wb = scales_wb;
    if ((dBs_wb != NULL) &&
            Work_buffer_is_valid(dBs_wb, 0) &&
            Work_buffer_is_final(dBs_wb, 0) &&
            (Work_buffer_get_const_start(dBs_wb, 0) == 0) &&
            (Work_buffer_get_contents(dBs_wb, 0)[0] == -INFINITY))
    {
        // We are only getting silent force from this point onwards
        vstate->active = false;
        return 0;
    }

    if ((scales_wb == NULL) || !Work_buffer_is_valid(scales_wb, 0))
        scales_wb = Work_buffers_get_buffer_mut(wbs, NOISE_WB_FIXED_FORCE, 1);
    Proc_fill_scale_buffer(scales_wb, dBs_wb, frame_count);
    const float* scales = Work_buffer_get_contents(scales_wb, 0);

    Noise_pstate* noise_state = (Noise_pstate*)proc_state;
    Noise_vstate* noise_vstate = (Noise_vstate*)vstate;

    // Get output buffer
    Work_buffer* out_wb = Proc_get_voice_output_2ch(proc_ts, PORT_OUT_AUDIO_L);
    if (out_wb == NULL)
    {
        vstate->active = false;
        return 0;
    }

    float* out_buffer = Work_buffer_get_contents_mut(out_wb, 0);

    const int order = noise_state->order;
    if (order >= 0)
    {
        float* out = out_buffer;
        for (int32_t i = 0; i < frame_count; ++i)
        {
            *out++ = scales[i] * (float)dc_zero_filter(
                    order,
                    noise_vstate->buf[0],
                    Random_get_float_signal(&noise_vstate->rands[0]));

            *out++ = scales[i] * (float)dc_zero_filter(
                    order,
                    noise_vstate->buf[1],
                    Random_get_float_signal(&noise_vstate->rands[1]));
        }
    }
    else
    {
        float* out = out_buffer;
        for (int32_t i = 0; i < frame_count; ++i)
        {
            *out++ = scales[i] * (float)dc_pole_filter(
                    -order,
                    noise_vstate->buf[0],
                    Random_get_float_signal(&noise_vstate->rands[0]));

            *out++ = scales[i] * (float)dc_pole_filter(
                    -order,
                    noise_vstate->buf[1],
                    Random_get_float_signal(&noise_vstate->rands[1]));
        }
    }

#if 0
    for (int ch = 0; ch < 2; ++ch)
    {
        float* out_buffer = out_buffers[ch];
        if (out_buffer == NULL)
            continue;

        if (noise_state->order >= 0)
        {
            for (int32_t i = 0; i < frame_count; ++i)
            {
                const double val = dc_zero_filter(
                        noise_state->order,
                        noise_vstate->buf[ch],
                        Random_get_float_signal(&noise_vstate->rands[ch]));
                out_buffer[i] = (float)val * scales[i];
            }
        }
        else
        {
            for (int32_t i = 0; i < frame_count; ++i)
            {
                const double val = dc_pole_filter(
                        -noise_state->order,
                        noise_vstate->buf[ch],
                        Random_get_float_signal(&noise_vstate->rands[ch]));
                out_buffer[i] = (float)val * scales[i];
            }
        }
    }
#endif

    const int32_t audio_rate = proc_state->parent.audio_rate;
    Proc_ramp_attack(vstate, out_wb, frame_count, audio_rate);

//  fprintf(stderr, "max_amp is %lf\n", max_amp);
    return frame_count;
}
示例#6
0
static void Freeverb_pstate_render_mixed(
        Device_state* dstate,
        const Work_buffers* wbs,
        int32_t buf_start,
        int32_t buf_stop,
        double tempo)
{
    assert(dstate != NULL);
    assert(wbs != NULL);
    assert(buf_start >= 0);
    assert(tempo > 0);

    Freeverb_pstate* fstate = (Freeverb_pstate*)dstate;

    Proc_freeverb* freeverb = (Proc_freeverb*)dstate->device->dimpl;

    // Get reflectivity parameter stream
    float* refls = Device_state_get_audio_buffer_contents_mut(
            dstate, DEVICE_PORT_TYPE_RECEIVE, PORT_IN_REFL);
    if (refls == NULL)
    {
        refls = Work_buffers_get_buffer_contents_mut(wbs, FREEVERB_WB_FIXED_REFL);
        const float fixed_refl = exp2(-5 / freeverb->reflect_setting);
        for (int32_t i = buf_start; i < buf_stop; ++i)
            refls[i] = fixed_refl;
    }
    else
    {
        // Convert reflectivity to the domain of our algorithm
        static const float max_param_inv = -5.0 / 200.0;
        static const float min_param_inv = -5.0 / 0.001;
        for (int32_t i = buf_start; i < buf_stop; ++i)
        {
            const double orig_refl = refls[i];
            const double param_inv = -5.0 / max(0, orig_refl);
            const float refl = fast_exp2(clamp(param_inv, min_param_inv, max_param_inv));
            refls[i] = refl;
        }
    }

    // Get damp parameter stream
    float* damps = Device_state_get_audio_buffer_contents_mut(
            dstate, DEVICE_PORT_TYPE_RECEIVE, PORT_IN_DAMP);
    if (damps == NULL)
    {
        damps = Work_buffers_get_buffer_contents_mut(wbs, FREEVERB_WB_FIXED_DAMP);
        const float fixed_damp = freeverb->damp_setting * 0.01;
        for (int32_t i = buf_start; i < buf_stop; ++i)
            damps[i] = fixed_damp;
    }
    else
    {
        for (int32_t i = buf_start; i < buf_stop; ++i)
        {
            const float scaled_damp = damps[i] * 0.01f;
            damps[i] = clamp(scaled_damp, 0, 1);
        }
    }

    Work_buffer* in_wbs[] =
    {
        Device_state_get_audio_buffer(dstate, DEVICE_PORT_TYPE_RECEIVE, PORT_IN_AUDIO_L),
        Device_state_get_audio_buffer(dstate, DEVICE_PORT_TYPE_RECEIVE, PORT_IN_AUDIO_R),
    };

    Work_buffer* out_wbs[] =
    {
        Device_state_get_audio_buffer(dstate, DEVICE_PORT_TYPE_SEND, PORT_OUT_AUDIO_L),
        Device_state_get_audio_buffer(dstate, DEVICE_PORT_TYPE_SEND, PORT_OUT_AUDIO_R),
    };

    // TODO: figure out a cleaner way of dealing with the buffers
    Work_buffer* workspace[] =
    {
        Work_buffers_get_buffer_mut(wbs, FREEVERB_WB_LEFT),
        Work_buffers_get_buffer_mut(wbs, FREEVERB_WB_RIGHT),
    };

    // Get input data
    if ((in_wbs[0] != NULL) && (in_wbs[1] != NULL))
    {
        Work_buffer_copy(workspace[0], in_wbs[0], buf_start, buf_stop);
        Work_buffer_copy(workspace[1], in_wbs[1], buf_start, buf_stop);
    }
    else if ((in_wbs[0] == NULL) != (in_wbs[1] == NULL))
    {
        const Work_buffer* existing = (in_wbs[0] != NULL) ? in_wbs[0] : in_wbs[1];
        Work_buffer_copy(workspace[0], existing, buf_start, buf_stop);
        Work_buffer_copy(workspace[1], existing, buf_start, buf_stop);
    }
    else
    {
        Work_buffer_clear(workspace[0], buf_start, buf_stop);
        Work_buffer_clear(workspace[1], buf_start, buf_stop);
    }

    float* ws[] =
    {
        Work_buffer_get_contents_mut(workspace[0]),
        Work_buffer_get_contents_mut(workspace[1]),
    };

    // Apply reverb
    {
        float* comb_input =
            Work_buffers_get_buffer_contents_mut(wbs, FREEVERB_WB_COMB_INPUT);
        for (int32_t i = buf_start; i < buf_stop; ++i)
            comb_input[i] = (ws[0][i] + ws[1][i]) * freeverb->gain;

        for (int ch = 0; ch < 2; ++ch)
        {
            float* ws_buf = ws[ch];
            for (int32_t i = buf_start; i < buf_stop; ++i)
                ws_buf[i] = 0;

            for (int comb = 0; comb < FREEVERB_COMBS; ++comb)
                Freeverb_comb_process(
                        fstate->combs[ch][comb],
                        ws_buf,
                        comb_input,
                        refls,
                        damps,
                        buf_start,
                        buf_stop);

            for (int allpass = 0; allpass < FREEVERB_ALLPASSES; ++allpass)
                Freeverb_allpass_process(
                        fstate->allpasses[ch][allpass], ws_buf, buf_start, buf_stop);
        }

        for (int32_t i = buf_start; i < buf_stop; ++i)
        {
            ws[0][i] = ws[0][i] * freeverb->wet1 + ws[1][i] * freeverb->wet2;
            ws[1][i] = ws[1][i] * freeverb->wet1 + ws[0][i] * freeverb->wet2;
        }
    }

    // Copy results to outputs that exist
    for (int ch = 0; ch < 2; ++ch)
    {
        if (out_wbs[ch] != NULL)
            Work_buffer_copy(out_wbs[ch], workspace[ch], buf_start, buf_stop);
    }

    return;
}