Skip to content

File syn_signal.c

File List > dsp > syn_signal.c

Go to the documentation of this file

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

#if !defined(SYN_USE_SIGNAL) || SYN_USE_SIGNAL

#include "../util/syn_assert.h"
#include "../util/syn_qmath.h"
#include "syn_signal.h"

#include <limits.h>
#include <string.h>

/* INT32_MIN / INT32_MAX fallback for C99 */
#ifndef INT32_MIN
#define INT32_MIN (-2147483647 - 1)
#endif
#ifndef INT32_MAX
#define INT32_MAX 2147483647
#endif

void syn_signal_init(SYN_Signal *sig, int32_t *buf, size_t capacity)
{
    SYN_ASSERT(sig != NULL);
    SYN_ASSERT(buf != NULL);
    SYN_ASSERT(capacity > 0);

    memset(sig, 0, sizeof(*sig));
    sig->buf = buf;
    sig->capacity = capacity;
    memset(buf, 0, sizeof(int32_t) * capacity);
}

void syn_signal_push(SYN_Signal *sig, int32_t sample)
{
    /* If window is full, evict oldest */
    if (sig->count == sig->capacity) {
        sig->sum -= sig->buf[sig->head];
    } else {
        sig->count++;
    }

    sig->buf[sig->head] = sample;
    sig->sum += sample;

    sig->head++;
    if (sig->head >= sig->capacity)
        sig->head = 0;

    sig->cache_valid = false;
}

void syn_signal_clear(SYN_Signal *sig)
{
    sig->head = 0;
    sig->count = 0;
    sig->sum = 0;
    sig->cache_valid = false;
}

/* ── Min/Max with lazy cache ────────────────────────────────────────────── */

static void recompute_minmax(SYN_Signal *sig)
{
    if (sig->cache_valid || sig->count == 0)
        return;

    int32_t lo = INT32_MAX;
    int32_t hi = INT32_MIN;
    size_t i;

    /* Walk the valid entries */
    size_t start;
    if (sig->count < sig->capacity) {
        start = 0;
    } else {
        start = sig->head; /* oldest */
    }

    for (i = 0; i < sig->count; i++) {
        size_t idx = (start + i) % sig->capacity;
        int32_t v = sig->buf[idx];
        if (v < lo)
            lo = v;
        if (v > hi)
            hi = v;
    }

    sig->cached_min = lo;
    sig->cached_max = hi;
    sig->cache_valid = true;
}

int32_t syn_signal_min(SYN_Signal *sig)
{
    if (sig->count == 0)
        return 0;
    recompute_minmax(sig);
    return sig->cached_min;
}

int32_t syn_signal_max(SYN_Signal *sig)
{
    if (sig->count == 0)
        return 0;
    recompute_minmax(sig);
    return sig->cached_max;
}

/* ── Variance ───────────────────────────────────────────────────────────── */

int32_t syn_signal_variance_q16(const SYN_Signal *sig)
{
    if (sig->count < 2)
        return 0;

    int32_t mean = (int32_t)(sig->sum / (int64_t)sig->count);

    int64_t sum_sq = 0;
    size_t start;
    if (sig->count < sig->capacity) {
        start = 0;
    } else {
        start = sig->head;
    }

    size_t i;
    for (i = 0; i < sig->count; i++) {
        size_t idx = (start + i) % sig->capacity;
        int64_t diff = (int64_t)sig->buf[idx] - mean;
        sum_sq += diff * diff;
    }

    /* Variance in Q16.16: (sum_sq << 16) / N */
    int64_t var_q16 = (sum_sq << 16) / (int64_t)sig->count;

    /* Clamp to int32_t range */
    if (var_q16 > INT32_MAX)
        return INT32_MAX;
    if (var_q16 < INT32_MIN)
        return INT32_MIN;

    return (int32_t)var_q16;
}

/* ── Access helpers ─────────────────────────────────────────────────────── */

int32_t syn_signal_latest(const SYN_Signal *sig)
{
    if (sig->count == 0)
        return 0;
    size_t idx = (sig->head == 0) ? sig->capacity - 1 : sig->head - 1;
    return sig->buf[idx];
}

int32_t syn_signal_at(const SYN_Signal *sig, size_t index)
{
    if (index >= sig->count)
        return 0;

    size_t start;
    if (sig->count < sig->capacity) {
        start = 0;
    } else {
        start = sig->head;
    }

    size_t actual = (start + index) % sig->capacity;
    return sig->buf[actual];
}

int32_t syn_signal_delta(const SYN_Signal *sig)
{
    if (sig->count < 2)
        return 0;

    size_t latest_idx = (sig->head == 0) ? sig->capacity - 1 : sig->head - 1;
    size_t prev_idx = (latest_idx == 0) ? sig->capacity - 1 : latest_idx - 1;

    return sig->buf[latest_idx] - sig->buf[prev_idx];
}

/* ── RMS & Standard Deviation ──────────────────────────────────────────── */

int32_t syn_signal_rms_q16(const SYN_Signal *sig)
{
    if (sig->count == 0)
        return 0;

    /* RMS = sqrt( sum(x^2) / N )
     * Accumulate x^2 in 64-bit, then divide by N, shift to Q16, sqrt. */
    int64_t sum_sq = 0;
    size_t start;
    if (sig->count < sig->capacity) {
        start = 0;
    } else {
        start = sig->head;
    }

    size_t i;
    for (i = 0; i < sig->count; i++) {
        size_t idx = (start + i) % sig->capacity;
        int64_t v = (int64_t)sig->buf[idx];
        sum_sq += v * v;
    }

    /* mean_sq = sum_sq / N, then convert to Q16.16 */
    int64_t mean_sq_q16 = (sum_sq << 16) / (int64_t)sig->count;

    /* Clamp to int32_t range for q16_sqrt input */
    if (mean_sq_q16 > INT32_MAX)
        mean_sq_q16 = INT32_MAX;

    return q16_sqrt((q16_t)mean_sq_q16);
}

int32_t syn_signal_std_dev_q16(const SYN_Signal *sig)
{
    int32_t var = syn_signal_variance_q16(sig);
    if (var <= 0)
        return 0;
    return q16_sqrt(var);
}

int32_t syn_signal_power_q16(const SYN_Signal *sig)
{
    if (sig->count == 0)
        return 0;

    int64_t sum_sq = 0;
    size_t start = (sig->count < sig->capacity) ? 0 : sig->head;

    size_t i;
    for (i = 0; i < sig->count; i++) {
        size_t idx = (start + i) % sig->capacity;
        int64_t v = (int64_t)sig->buf[idx];
        sum_sq += v * v;
    }

    int64_t mean_sq_q16 = (sum_sq << 16) / (int64_t)sig->count;
    if (mean_sq_q16 > INT32_MAX)
        return INT32_MAX;

    return (int32_t)mean_sq_q16;
}

int32_t syn_signal_crest_factor_q16(SYN_Signal *sig)
{
    int32_t rms = syn_signal_rms_q16(sig);
    if (rms <= 0)
        return 0;

    int32_t peak = syn_signal_max(sig);
    int32_t min_v = syn_signal_min(sig);
    if (-min_v > peak)
        peak = -min_v;

    int64_t peak_q16 = (int64_t)peak * 65536LL;
    int64_t cf_q16 = (peak_q16 * 65536LL) / (int64_t)rms;

    if (cf_q16 > INT32_MAX)
        return INT32_MAX;

    return (int32_t)cf_q16;
}

#endif /* SYN_USE_SIGNAL */