/*
 * filter_audiolevel.c -- get the audio level of each channel
 * Copyright (C) 2002 Steve Harris
 * Copyright (C) 2010 Marco Gittler <g.marco@freenet.de>
 * Copyright (C) 2012-2023 Dan Dennedy <dan@dennedy.org>
 *
 * 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
 * (at your option) 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include <framework/mlt_filter.h>
#include <framework/mlt_frame.h>
#include <framework/mlt_log.h>

#include <math.h>
#include <stdlib.h>

#define AMPTODBFS(n) (log10(n) * 20.0)

//----------------------------------------------------------------------------
// IEC standard dB scaling -- as borrowed from meterbridge (c) Steve Harris

static inline double IEC_Scale(double dB)
{
    double fScale = 1.0f;

    if (dB < -70.0f)
        fScale = 0.0f;
    else if (dB < -60.0f) //  0.0  ..   2.5
        fScale = (dB + 70.0f) * 0.0025f;
    else if (dB < -50.0f) //  2.5  ..   7.5
        fScale = (dB + 60.0f) * 0.005f + 0.025f;
    else if (dB < -40.0) //  7.5  ..  15.0
        fScale = (dB + 50.0f) * 0.0075f + 0.075f;
    else if (dB < -30.0f) // 15.0  ..  30.0
        fScale = (dB + 40.0f) * 0.015f + 0.15f;
    else if (dB < -20.0f) // 30.0  ..  50.0
        fScale = (dB + 30.0f) * 0.02f + 0.3f;
    else if (dB < -0.001f || dB > 0.001f) // 50.0  .. 100.0
        fScale = (dB + 20.0f) * 0.025f + 0.5f;

    return fScale;
}

static int filter_get_audio(mlt_frame frame,
                            void **buffer,
                            mlt_audio_format *format,
                            int *frequency,
                            int *channels,
                            int *samples)
{
    mlt_filter filter = mlt_frame_pop_audio(frame);

    // Get the properties from the filter
    mlt_properties filter_props = MLT_FILTER_PROPERTIES(filter);

    int iec_scale = mlt_properties_get_int(filter_props, "iec_scale");
    int dbPeak = mlt_properties_get_int(filter_props, "dbpeak");
    *format = mlt_audio_s16;
    int error = mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples);
    if (error || !buffer || !buffer[0])
        return error;

    int num_channels = *channels;
    int num_samples = *samples > 200 ? 200 : *samples;
    int num_oversample = 0;
    int c, s;
    char key[50];
    int16_t *pcm = (int16_t *) *buffer;

    for (c = 0; c < *channels; c++) {
        double level = 0.0;
        if (dbPeak) {
            int16_t peakVal = 0;
            for (s = 0; s < num_samples; s++) {
                int16_t sample = abs(pcm[c + s * num_channels]);
                if (sample > peakVal)
                    peakVal = sample;
            }
            if (peakVal == 0)
                level = -100;
            else
                level = AMPTODBFS((double) peakVal / (double) INT16_MAX);

            if (iec_scale)
                level = IEC_Scale(level);
        } else {
            double val = 0;
            for (s = 0; s < num_samples; s++) {
                double sample = fabs(pcm[c + s * num_channels] / 128.0);
                val += sample;
                if (sample == 128)
                    num_oversample++;
                else
                    num_oversample = 0;
                // 10 samples @max => show max signal
                if (num_oversample > 10) {
                    level = 1.0;
                    break;
                }
                // if 3 samples over max => 1 peak over 0 db (0 dB = 40.0)
                if (num_oversample > 3)
                    level = 41.0 / 42.0;
            }
            // max amplitude = 40/42, 3to10  oversamples=41, more then 10 oversamples=42
            if (level == 0.0 && num_samples > 0)
                level = val / num_samples * 40.0 / 42.0 / 127.0;
            if (iec_scale)
                level = IEC_Scale(AMPTODBFS(level));
        }
        sprintf(key, "meta.media.audio_level.%d", c);
        mlt_properties_set_double(MLT_FRAME_PROPERTIES(frame), key, level);
        sprintf(key, "_audio_level.%d", c);
        mlt_properties_set_double(filter_props, key, level);
        mlt_log_debug(MLT_FILTER_SERVICE(filter), "channel %d level %f\n", c, level);
    }
    mlt_properties_set_position(filter_props, "_position", mlt_filter_get_position(filter, frame));

    return error;
}

/** Filter processing.
*/

static mlt_frame filter_process(mlt_filter filter, mlt_frame frame)
{
    mlt_frame_push_audio(frame, filter);
    mlt_frame_push_audio(frame, filter_get_audio);
    return frame;
}

/** Constructor for the filter.
*/

mlt_filter filter_audiolevel_init(mlt_profile profile,
                                  mlt_service_type type,
                                  const char *id,
                                  char *arg)
{
    mlt_filter filter = mlt_filter_new();
    if (filter) {
        filter->process = filter_process;
        mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "iec_scale", 1);
    }
    return filter;
}
