|  | /*
 * DISTRHO Nekobi Plugin, based on Nekobee by Sean Bolton and others.
 * Copyright (C) 2004 Sean Bolton and others
 * Copyright (C) 2013 Filipe Coelho <falktx@falktx.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or any later version.
 *
 * This program 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 General Public License for more details.
 *
 * For a full copy of the GNU General Public License see the doc/GPL.txt file.
 */
#include "DistrhoPluginNekobi.hpp"
#ifdef CARLA_EXPORT
# include "CarlaUtils.hpp"
#else
# define CARLA_SAFE_ASSERT_INT2(...)
#endif
extern "C" {
#include "nekobee-src/nekobee_synth.c"
#include "nekobee-src/nekobee_voice.c"
#include "nekobee-src/nekobee_voice_render.c"
#include "nekobee-src/minblep_tables.c"
// -----------------------------------------------------------------------
// mutual exclusion
bool dssp_voicelist_mutex_trylock(nekobee_synth_t* synth)
{
    /* Attempt the mutex lock */
    if (pthread_mutex_trylock(&synth->voicelist_mutex) != 0)
    {
        synth->voicelist_mutex_grab_failed = 1;
        return false;
    }
    /* Clean up if a previous mutex grab failed */
    if (synth->voicelist_mutex_grab_failed)
    {
        nekobee_synth_all_voices_off(synth);
        synth->voicelist_mutex_grab_failed = 0;
    }
    return true;
}
bool dssp_voicelist_mutex_lock(nekobee_synth_t* synth)
{
    return (pthread_mutex_lock(&synth->voicelist_mutex) == 0);
}
bool dssp_voicelist_mutex_unlock(nekobee_synth_t *synth)
{
    return (pthread_mutex_unlock(&synth->voicelist_mutex) == 0);
}
// -----------------------------------------------------------------------
// nekobee_handle_raw_event
void nekobee_handle_raw_event(nekobee_synth_t* synth, uint8_t size, const uint8_t* data)
{
    if (size != 3)
        return;
    switch (data[0] & 0xf0)
    {
    case 0x80:
        nekobee_synth_note_off(synth, data[1], data[2]);
        break;
    case 0x90:
        if (data[2] > 0)
            nekobee_synth_note_on(synth, data[1], data[2]);
        else
            nekobee_synth_note_off(synth, data[1], 64); /* shouldn't happen, but... */
        break;
    case 0xB0:
        nekobee_synth_control_change(synth, data[1], data[2]);
        break;
    default:
        break;
    }
}
} /* extern "C" */
START_NAMESPACE_DISTRHO
// -----------------------------------------------------------------------
DistrhoPluginNekobi::DistrhoPluginNekobi()
    : Plugin(paramCount, 0, 0) // 0 programs, 0 states
{
    nekobee_init_tables();
    // init synth
    fSynth = new nekobee_synth_t;
    fSynth->sample_rate = d_getSampleRate();
    fSynth->deltat = 1.0f / (float)d_getSampleRate();
    fSynth->nugget_remains = 0;
    fSynth->note_id = 0;
    fSynth->polyphony = XSYNTH_DEFAULT_POLYPHONY;
    fSynth->voices = XSYNTH_DEFAULT_POLYPHONY;
    fSynth->monophonic = XSYNTH_MONO_MODE_ONCE;
    fSynth->glide = 0;
    fSynth->last_noteon_pitch = 0.0f;
    fSynth->vcf_accent = 0.0f;
    fSynth->vca_accent = 0.0f;
    for (int i=0; i<8; ++i)
      fSynth->held_keys[i] = -1;
    fSynth->voice = nekobee_voice_new();
    fSynth->voicelist_mutex_grab_failed = 0;
    pthread_mutex_init(&fSynth->voicelist_mutex, nullptr);
    fSynth->channel_pressure = 0;
    fSynth->pitch_wheel_sensitivity = 0;
    fSynth->pitch_wheel = 0;
    for (int i=0; i<128; ++i)
    {
        fSynth->key_pressure[i] = 0;
        fSynth->cc[i] = 0;
    }
    fSynth->cc[7] = 127; // full volume
    fSynth->mod_wheel  = 1.0f;
    fSynth->pitch_bend = 1.0f;
    fSynth->cc_volume  = 1.0f;
    // Default values
    fParams.waveform = 0.0f;
    fParams.tuning = 0.0f;
    fParams.cutoff = 25.0f;
    fParams.resonance = 25.0f;
    fParams.envMod = 50.0f;
    fParams.decay  = 75.0f;
    fParams.accent = 25.0f;
    fParams.volume = 75.0f;
    // Internal stuff
    fSynth->waveform  = 0.0f;
    fSynth->tuning    = 1.0f;
    fSynth->cutoff    = 5.0f;
    fSynth->resonance = 0.8f;
    fSynth->envmod    = 0.3f;
    fSynth->decay     = 0.0002f;
    fSynth->accent    = 0.3f;
    fSynth->volume    = 0.75f;
    // reset
    d_deactivate();
}
DistrhoPluginNekobi::~DistrhoPluginNekobi()
{
    std::free(fSynth->voice);
    delete fSynth;
}
// -----------------------------------------------------------------------
// Init
void DistrhoPluginNekobi::d_initParameter(uint32_t index, Parameter& parameter)
{
    switch (index)
    {
    case paramWaveform:
        parameter.hints      = PARAMETER_IS_AUTOMABLE|PARAMETER_IS_BOOLEAN;
        parameter.name       = "Waveform";
        parameter.symbol     = "waveform";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = 0.0f;
        parameter.ranges.max = 1.0f;
        break;
    case paramTuning:
        parameter.hints      = PARAMETER_IS_AUTOMABLE; // was 0.5 <-> 2.0, log
        parameter.name       = "Tuning";
        parameter.symbol     = "tuning";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -12.0f;
        parameter.ranges.max = 12.0f;
        break;
    case paramCutoff:
        parameter.hints      = PARAMETER_IS_AUTOMABLE; // modified x2.5
        parameter.name       = "Cutoff";
        parameter.symbol     = "cutoff";
        parameter.unit       = "%";
        parameter.ranges.def = 25.0f;
        parameter.ranges.min = 0.0f;
        parameter.ranges.max = 100.0f;
        break;
    case paramResonance:
        parameter.hints      = PARAMETER_IS_AUTOMABLE; // modified x100
        parameter.name       = "VCF Resonance";
        parameter.symbol     = "resonance";
        parameter.unit       = "%";
        parameter.ranges.def = 25.0f;
        parameter.ranges.min = 0.0f;
        parameter.ranges.max = 95.0f;
        break;
    case paramEnvMod:
        parameter.hints      = PARAMETER_IS_AUTOMABLE; // modified x100
        parameter.name       = "Env Mod";
        parameter.symbol     = "env_mod";
        parameter.unit       = "%";
        parameter.ranges.def = 50.0f;
        parameter.ranges.min = 0.0f;
        parameter.ranges.max = 100.0f;
        break;
    case paramDecay:
        parameter.hints      = PARAMETER_IS_AUTOMABLE; // was 0.000009 <-> 0.0005, log
        parameter.name       = "Decay";
        parameter.symbol     = "decay";
        parameter.unit       = "%";
        parameter.ranges.def = 75.0f;
        parameter.ranges.min = 0.0f;
        parameter.ranges.max = 100.0f;
        break;
    case paramAccent:
        parameter.hints      = PARAMETER_IS_AUTOMABLE; // modified x100
        parameter.name       = "Accent";
        parameter.symbol     = "accent";
        parameter.unit       = "%";
        parameter.ranges.def = 25.0f;
        parameter.ranges.min = 0.0f;
        parameter.ranges.max = 100.0f;
        break;
    case paramVolume:
        parameter.hints      = PARAMETER_IS_AUTOMABLE; // modified x100
        parameter.name       = "Volume";
        parameter.symbol     = "volume";
        parameter.unit       = "%";
        parameter.ranges.def = 75.0f;
        parameter.ranges.min = 0.0f;
        parameter.ranges.max = 100.0f;
        break;
    }
}
// -----------------------------------------------------------------------
// Internal data
float DistrhoPluginNekobi::d_getParameterValue(uint32_t index) const
{
    switch (index)
    {
    case paramWaveform:
        return fParams.waveform;
    case paramTuning:
        return fParams.tuning;
    case paramCutoff:
        return fParams.cutoff;
    case paramResonance:
        return fParams.resonance;
    case paramEnvMod:
        return fParams.envMod;
    case paramDecay:
        return fParams.decay;
    case paramAccent:
        return fParams.accent;
    case paramVolume:
        return fParams.volume;
    }
    return 0.0f;
}
void DistrhoPluginNekobi::d_setParameterValue(uint32_t index, float value)
{
    switch (index)
    {
    case paramWaveform:
        fParams.waveform = value;
        fSynth->waveform = value;
        CARLA_SAFE_ASSERT_INT2(fSynth->waveform == 0.0f || fSynth->waveform == 1.0f, fSynth->waveform, value);
        break;
    case paramTuning:
        fParams.tuning = value;
        fSynth->tuning = (value+12.0f)/24.0f * 1.5 + 0.5f; // FIXME: log?
        CARLA_SAFE_ASSERT_INT2(fSynth->tuning >= 0.5f && fSynth->tuning <= 2.0f, fSynth->tuning, value);
        break;
    case paramCutoff:
        fParams.cutoff = value;
        fSynth->cutoff = value/2.5f;
        CARLA_SAFE_ASSERT_INT2(fSynth->cutoff >= 0.0f && fSynth->cutoff <= 40.0f, fSynth->cutoff, value);
        break;
    case paramResonance:
        fParams.resonance = value;
        fSynth->resonance = value/100.0f;
        CARLA_SAFE_ASSERT_INT2(fSynth->resonance >= 0.0f && fSynth->resonance <= 0.95f, fSynth->resonance, value);
        break;
    case paramEnvMod:
        fParams.envMod = value;
        fSynth->envmod = value/100.0f;
        CARLA_SAFE_ASSERT_INT2(fSynth->envmod >= 0.0f && fSynth->envmod <= 1.0f, fSynth->envmod, value);
        break;
    case paramDecay:
        fParams.decay = value;
        fSynth->decay = value/100.0f * 0.000491f + 0.000009f; // FIXME: log?
        CARLA_SAFE_ASSERT_INT2(fSynth->decay >= 0.000009f && fSynth->decay <= 0.0005f, fSynth->decay, value);
        break;
    case paramAccent:
        fParams.accent = value;
        fSynth->accent = value/100.0f;
        CARLA_SAFE_ASSERT_INT2(fSynth->accent >= 0.0f && fSynth->accent <= 1.0f, fSynth->accent, value);
        break;
    case paramVolume:
        fParams.volume = value;
        fSynth->volume = value/100.0f;
        CARLA_SAFE_ASSERT_INT2(fSynth->volume >= 0.0f && fSynth->volume <= 1.0f, fSynth->volume, value);
        break;
    }
}
// -----------------------------------------------------------------------
// Process
void DistrhoPluginNekobi::d_activate()
{
    fSynth->nugget_remains = 0;
    fSynth->note_id = 0;
    if (fSynth->voice != nullptr)
        nekobee_synth_all_voices_off(fSynth);
}
void DistrhoPluginNekobi::d_deactivate()
{
    if (fSynth->voice != nullptr)
        nekobee_synth_all_voices_off(fSynth);
}
void DistrhoPluginNekobi::d_run(float**, float** outputs, uint32_t frames, const MidiEvent* midiEvents, uint32_t midiEventCount)
{
    uint32_t framesDone = 0;
    uint32_t curEventIndex = 0;
    uint32_t burstSize;
    float* out = outputs[0];
    if (fSynth->voice == nullptr || ! dssp_voicelist_mutex_trylock(fSynth))
    {
        for (uint32_t i=0; i < frames; ++i)
            *out++ = 0.0f;
        return;
    }
    while (framesDone < frames)
    {
        if (fSynth->nugget_remains == 0)
            fSynth->nugget_remains = XSYNTH_NUGGET_SIZE;
        /* process any ready events */
        while (curEventIndex < midiEventCount && framesDone == midiEvents[curEventIndex].frame)
        {
            nekobee_handle_raw_event(fSynth, midiEvents[curEventIndex].size, midiEvents[curEventIndex].buf);
            curEventIndex++;
        }
        /* calculate the sample count (burstSize) for the next nekobee_voice_render() call to be the smallest of:
         * - control calculation quantization size (XSYNTH_NUGGET_SIZE, in samples)
         * - the number of samples remaining in an already-begun nugget (synth->nugget_remains)
         * - the number of samples until the next event is ready
         * - the number of samples left in this run
         */
        burstSize = XSYNTH_NUGGET_SIZE;
        /* we're still in the middle of a nugget, so reduce the burst size
         * to end when the nugget ends */
        if (fSynth->nugget_remains < burstSize)
            burstSize = fSynth->nugget_remains;
        /* reduce burst size to end when next event is ready */
        if (curEventIndex < midiEventCount && midiEvents[curEventIndex].frame - framesDone < burstSize)
            burstSize = midiEvents[curEventIndex].frame - framesDone;
        /* reduce burst size to end at end of this run */
        if (frames - framesDone < burstSize)
            burstSize = frames - framesDone;
        /* render the burst */
        nekobee_synth_render_voices(fSynth, out + framesDone, burstSize, (burstSize == fSynth->nugget_remains));
        framesDone += burstSize;
        fSynth->nugget_remains -= burstSize;
    }
    dssp_voicelist_mutex_unlock(fSynth);
}
// -----------------------------------------------------------------------
Plugin* createPlugin()
{
    return new DistrhoPluginNekobi();
}
// -----------------------------------------------------------------------
END_NAMESPACE_DISTRHO
 |