Skip to content

File syn_ramp.c

File List > src > syntropic > util > syn_ramp.c

Go to the documentation of this file

#if __has_include("syn_config.h")
#include "syn_config.h"
#endif

#if !defined(SYN_USE_RAMP) || SYN_USE_RAMP

#include "../util/syn_assert.h"
#include "syn_ramp.h"

#include <string.h>

void syn_ramp_init(SYN_Ramp *ramp, int32_t initial)
{
    if (ramp == NULL) {
        return;
    }
    SYN_ASSERT(ramp != NULL);

    memset(ramp, 0, sizeof(*ramp));
    ramp->current = initial;
    ramp->target = initial;
    ramp->done = true;
}

void syn_ramp_set_target(SYN_Ramp *ramp, int32_t target, int32_t rate)
{
    if (ramp == NULL) {
        return;
    }
    SYN_ASSERT(ramp != NULL);
    if (rate <= 0)
        rate = 1;
    SYN_ASSERT(rate > 0);

    ramp->target = target;
    ramp->rate = rate;
    ramp->velocity = 0;
    ramp->accel = 0;
    ramp->mode = (uint8_t)SYN_RAMP_LINEAR;
    ramp->done = (ramp->current == target);
}

void syn_ramp_set_target_trapezoid(SYN_Ramp *ramp, int32_t target, int32_t max_rate, int32_t accel)
{
    if (ramp == NULL) {
        return;
    }
    SYN_ASSERT(ramp != NULL);
    if (max_rate <= 0)
        max_rate = 1;
    if (accel <= 0)
        accel = 1;
    SYN_ASSERT(max_rate > 0);
    SYN_ASSERT(accel > 0);

    ramp->target = target;
    ramp->rate = max_rate;
    ramp->accel = accel;
    ramp->velocity = 0;
    ramp->frac_accum = 0;
    ramp->frac_bits = 0;
    ramp->mode = (uint8_t)SYN_RAMP_TRAPEZOID;
    ramp->done = (ramp->current == target);
}

void syn_ramp_set_target_trapezoid_fp(SYN_Ramp *ramp, int32_t target, int32_t max_rate,
                                      int32_t accel, uint8_t frac_bits)
{
    if (ramp == NULL) {
        return;
    }
    SYN_ASSERT(ramp != NULL);
    if (max_rate <= 0)
        max_rate = 1;
    if (accel <= 0)
        accel = 1;
    if (frac_bits > 16)
        frac_bits = 16;
    SYN_ASSERT(max_rate > 0);
    SYN_ASSERT(accel > 0);
    SYN_ASSERT(frac_bits <= 16);

    ramp->target = target;
    ramp->rate = max_rate;
    ramp->accel = accel;
    ramp->velocity = 0;
    ramp->frac_accum = 0;
    ramp->frac_bits = frac_bits;
    ramp->mode = (uint8_t)SYN_RAMP_TRAPEZOID;
    ramp->done = (ramp->current == target);
}

static int32_t update_linear(SYN_Ramp *ramp)
{
    int32_t diff = ramp->target - ramp->current;

    if (diff == 0) {
        ramp->done = true;
        return ramp->current;
    }

    if (diff > 0) {
        ramp->current += (diff > ramp->rate) ? ramp->rate : diff;
    } else {
        ramp->current += (diff < -ramp->rate) ? -ramp->rate : diff;
    }

    ramp->done = (ramp->current == ramp->target);
    return ramp->current;
}

static int32_t update_trapezoid(SYN_Ramp *ramp)
{
    int32_t diff = ramp->target - ramp->current;

    if (diff == 0 && ramp->velocity == 0) {
        ramp->frac_accum = 0;
        ramp->done = true;
        return ramp->current;
    }

    /* Determine direction */
    int32_t dir = (diff > 0) ? 1 : -1;

    /*
     * Deceleration distance: v²/(2*a)
     * When using fixed-point, velocity is in Qn. The decel distance
     * must be in integer units, so: decel_dist = v² / (2*a) >> frac_bits.
     * Use 64-bit to avoid overflow in v².
     */
    int32_t abs_vel = SYN_ABS(ramp->velocity);
    int32_t abs_diff = SYN_ABS(diff);
    int32_t decel_dist = 0;
    if (ramp->accel > 0) {
        int64_t v2 = (int64_t)abs_vel * abs_vel;
        int64_t denom = 2 * (int64_t)ramp->accel;
        if (ramp->frac_bits > 0) {
            decel_dist = (int32_t)((v2 / denom) >> ramp->frac_bits);
        } else {
            decel_dist = (int32_t)(v2 / denom);
        }
    }

    if (abs_diff <= decel_dist || (dir * ramp->velocity < 0)) {
        /* Decelerate (or reverse) */
        if (ramp->velocity > 0) {
            ramp->velocity -= ramp->accel;
            if (ramp->velocity < 0)
                ramp->velocity = 0;
        } else if (ramp->velocity < 0) {
            ramp->velocity += ramp->accel;
            if (ramp->velocity > 0)
                ramp->velocity = 0;
        }
    } else {
        /* Accelerate toward target */
        ramp->velocity += dir * ramp->accel;
        ramp->velocity = SYN_CLAMP(ramp->velocity, -ramp->rate, ramp->rate);
    }

    /* Apply velocity to position.
     * In Q-mode: accumulate fractional bits, carry whole units to current. */
    if (ramp->frac_bits > 0) {
        ramp->frac_accum += ramp->velocity;
        int32_t whole = ramp->frac_accum >> ramp->frac_bits;
        ramp->frac_accum -= whole << ramp->frac_bits;
        ramp->current += whole;
    } else {
        ramp->current += ramp->velocity;
    }

    /* Snap to target if we've overshot or are very close */
    int32_t new_diff = ramp->target - ramp->current;
    if ((diff > 0 && new_diff <= 0) || (diff < 0 && new_diff >= 0)) {
        ramp->current = ramp->target;
        ramp->velocity = 0;
        ramp->frac_accum = 0;
        ramp->done = true;
    }

    return ramp->current;
}

int32_t syn_ramp_update(SYN_Ramp *ramp)
{
    SYN_ASSERT(ramp != NULL);

    if (ramp->done)
        return ramp->current;

    switch ((SYN_RampMode)ramp->mode) {
    case SYN_RAMP_TRAPEZOID:
        return update_trapezoid(ramp);
    default:
        return update_linear(ramp);
    }
}

void syn_ramp_jump(SYN_Ramp *ramp, int32_t value)
{
    SYN_ASSERT(ramp != NULL);
    ramp->current = value;
    ramp->target = value;
    ramp->velocity = 0;
    ramp->frac_accum = 0;
    ramp->done = true;
}

#endif /* SYN_USE_RAMP */