/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id:$
 *
 * Copyright (C) 2006 Michael Sevakis 
 *
 * All files in this archive are subject to the GNU General Public License.
 * See the file COPYING in the source tree root for full license agreement.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/
#include "plugin.h"

/* Button mapping */
#if CONFIG_KEYPAD == IRIVER_H100_PAD
#define EFFECT_QUIT BUTTON_ON
#elif CONFIG_KEYPAD == IAUDIO_X5_PAD
#define EFFECT_QUIT BUTTON_POWER
#endif

PLUGIN_HEADER
PLUGIN_IRAM_DECLARE

static struct plugin_api* rb;

static size_t queue_size;
/* Imporant to leave enough room behind effect_index to allow for delays like screen updates */
static volatile int wr_index = 4;
static volatile int rd_index = 0;
static int effect_index      = 4; /* one ahead to account for preincrement of wr_index */

static unsigned long effect_thread_stack[DEFAULT_STACK_SIZE] IBSS_ATTR;
static bool stop_effect = false;

#define CHUNK_SAMPLES   256 
#define CHUNK_SIZE      (CHUNK_SAMPLES*4)
#define NUM_CHUNKS      8 /* power of 2 */
#define CHUNK_MASK      (NUM_CHUNKS-1)
#define NEXT_CHUNK(x)   (((x) + 1) & CHUNK_MASK)

typedef int32_t (* buffer_t)[CHUNK_SAMPLES];
buffer_t __attribute__((aligned(16))) queue;

//long coefs[5] = { 32768, -23655, -6097, 55729, -23034 };
long coefs[5] = { 8402, -6065, -1563, 55729, -23034 };
long history[2][4];
bool deemph_enabled = true;

void eq_filter(int16_t *x, unsigned num, unsigned channels, unsigned shift)
{
    unsigned c, i;
    long long acc;

    /* Direct form 1 filtering code.
       y[n] = b0*x[i] + b1*x[i - 1] + b2*x[i - 2] + a1*y[i - 1] + a2*y[i - 2],
       where y[] is output and x[] is input.
     */

    for (c = 0; c < channels; c++) {
        for (i = 0; i < num; i++) {
            acc = (long long)x[2*i + c]*coefs[0];
            acc += (long long)history[c][0]*coefs[1];
            acc += (long long)history[c][1]*coefs[2];
            acc += (long long)history[c][2]*coefs[3];
            acc += (long long)history[c][3]*coefs[4];
            history[c][1] = history[c][0];
            history[c][0] = x[2*i + c];
            history[c][3] = history[c][2];
//            if (acc > (32767 << 15)) acc = 32767 << 15;
//            if (acc < (-32768 << 15)) acc = -32768 << 15;
            history[c][2] = x[2*i + c] = (acc) >> 15;
        }
    }
}

static void play_callback(unsigned char **start, size_t *size) ICODE_ATTR;
static void play_callback(unsigned char **start, size_t *size)
{
    rd_index = NEXT_CHUNK(rd_index);
    *start = (unsigned char *)queue[rd_index];
    *size  = CHUNK_SIZE;
}

static int record_callback(int status) ICODE_ATTR;
static int record_callback(int status)
{
    wr_index = NEXT_CHUNK(wr_index);
    rb->pcm_record_more(queue[wr_index], CHUNK_SIZE);
    return 0;
    (void)status;
}

static void effect_thread(void) ICODE_ATTR;
static void effect_thread(void)
{
    int y = 0;
    while (!stop_effect)
    {
        /* Wait for at least one chunk of data available */
        while (wr_index == effect_index)
        {
            rb->yield();
            if (stop_effect)
                goto effect_done;
        }

        do
        {
            int16_t *ptr = (int16_t *)queue[effect_index];

            if (deemph_enabled)
                eq_filter(ptr, CHUNK_SAMPLES, 2, 1);
   
            effect_index  = NEXT_CHUNK(effect_index);

            if (++y >= 8)
            {
                rb->yield();
                y = 0;
            }
        }
        while (effect_index != wr_index);
    }

effect_done:
    /* main thread waits for this to turn false again */
    stop_effect = false;
    rb->remove_thread(NULL);
}

/* this is the plugin entry point */
enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
{
    int result, selection;
    int db = 36;
    MENUITEM_STRINGLIST(
        menu, "Vinyl amp", NULL,
        "Quit",
        "RIAA deemphasis",
        "Gain",
    );

    PLUGIN_IRAM_INIT(api);
    rb = api;

    queue = (buffer_t)rb->plugin_get_audio_buffer(&queue_size);
    rb->memset(queue, 0, CHUNK_SIZE*NUM_CHUNKS);

    rb->pcm_init_recording();
    rb->pcm_set_frequency(SAMPR_44);
    rb->audio_set_output_source(AUDIO_SRC_PLAYBACK);
    rb->audio_set_input_source(AUDIO_SRC_LINEIN, SRCF_RECORDING);
    rb->pcm_apply_settings();

    rb->cpu_boost(true);

    rb->pcm_record_data(record_callback, queue[wr_index], CHUNK_SIZE);
    rb->pcm_play_data(play_callback, NULL, 0);

    rb->sound_set(SOUND_VOLUME, 0);
    rb->audio_set_recording_gain(db, db, AUDIO_GAIN_LINEIN);

    /* Start a thread for processing the audio */
    rb->create_thread(effect_thread, effect_thread_stack,
                      sizeof(effect_thread_stack), "effect thread",
                      PRIORITY_RECORDING);
    
    while (result = rb->do_menu(&menu, &selection))
    {
        if (result == 1)
        {
            rb->set_bool_options("RIAA deemphasis", &deemph_enabled,
                                 "Enabled", NULL, "Disabled", NULL, NULL);
        }
        else if (result == 2)
        {
            rb->set_int("Gain", "dB", UNIT_INT, &db, NULL, 1, 20, 60, NULL);
            rb->audio_set_recording_gain(db, db, AUDIO_GAIN_LINEIN);
        }
    }

    /* stop effect thread - it will remove itself */
    stop_effect = true;
    while (stop_effect)
        rb->yield();

    rb->pcm_play_stop();
    rb->pcm_stop_recording();
    rb->pcm_close_recording();

    rb->cpu_boost(false);

    rb->audio_set_output_source(AUDIO_SRC_PLAYBACK);
    rb->pcm_set_frequency(HW_SAMPR_DEFAULT);
    rb->audio_set_input_source(AUDIO_SRC_PLAYBACK, SRCF_PLAYBACK);
    rb->pcm_apply_settings();

    return PLUGIN_OK;
    (void)parameter;
}

