/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id:$
 *
 * Copyright (C) 2006 Thom Johansen
 *
 * 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"

PLUGIN_HEADER
PLUGIN_IRAM_DECLARE

/* variable button definitions */
#if (CONFIG_KEYPAD == IRIVER_H100_PAD) || (CONFIG_KEYPAD == IRIVER_H300_PAD)
#define BTN_CANCEL         BUTTON_OFF

#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || (CONFIG_KEYPAD == IPOD_3G_PAD) || \
      (CONFIG_KEYPAD == IPOD_1G2G_PAD)
#define BTN_CANCEL         BUTTON_MENU

#elif (CONFIG_KEYPAD == GIGABEAT_PAD)
#define BTN_CANCEL         BUTTON_POWER

#elif (CONFIG_KEYPAD == SANSA_E200_PAD) || \
(CONFIG_KEYPAD == SANSA_C200_PAD)
#define BTN_CANCEL         BUTTON_POWER

#elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
#define BTN_CANCEL         BUTTON_POWER

#elif CONFIG_KEYPAD == IRIVER_H10_PAD
#define BTN_CANCEL         BUTTON_POWER

#elif CONFIG_KEYPAD == MROBE500_PAD
#define BTN_CANCEL         BUTTON_POWER

#endif

#define BLOCK_SIZE 256
#define SAMPLE_RATE 44100

struct buffer {
    int signal_freq;
    short data[BLOCK_SIZE*2];
};

struct plugin_api* rb;
struct buffer buffers[2] IDATA_ATTR;
long samples_done;
unsigned long long pos, delta;
int start_freq = 20, stop_freq = 16000, duration = 15, sweep_type = 0;
int cur_freq, cur_buf = 0, fill_buffer = 2;
bool finished;

/* 128 sixteen bit sine samples + guard point */
const short sinetab[] ICONST_ATTR = {
    0, 1607, 3211, 4807, 6392, 7961, 9511, 11038, 12539, 14009, 15446, 16845,
    18204, 19519, 20787, 22004, 23169, 24278, 25329, 26318, 27244, 28105, 28897,
    29621, 30272, 30851, 31356, 31785, 32137, 32412, 32609,32727, 32767, 32727,
    32609, 32412, 32137, 31785, 31356, 30851, 30272, 29621, 28897, 28105, 27244,
    26318, 25329, 24278, 23169, 22004, 20787, 19519, 18204, 16845, 15446, 14009,
    12539, 11038, 9511, 7961, 6392, 4807, 3211, 1607, 0, -1607, -3211, -4807,
    -6392, -7961, -9511, -11038, -12539, -14009, -15446, -16845, -18204, -19519,
    -20787, -22004, -23169, -24278, -25329, -26318, -27244, -28105, -28897,
    -29621, -30272, -30851, -31356, -31785, -32137, -32412, -32609, -32727,
    -32767, -32727, -32609, -32412, -32137, -31785, -31356, -30851, -30272,
    -29621, -28897, -28105, -27244, -26318, -25329, -24278, -23169, -22004,
    -20787, -19519, -18204, -16845, -15446, -14009, -12539, -11038, -9511,
    -7961, -6392, -4807, -3211, -1607, 0
};

/* Good quality sine calculated by linearly interpolating
 * a 128 sample sine table. First harmonic has amplitude of about -84 dB.
 * phase has range from 0 to 0xffffffff, representing 0 and
 * 2*pi respectively.
 * Return value is a signed value from LONG_MIN to LONG_MAX, representing
 * -1 and 1 respectively. 
 */
static short fsin(uint32_t phase) ICODE_ATTR;
static short fsin(uint32_t phase)
{
    unsigned int pos = phase >> 25;
    unsigned short frac = (phase & 0x01ffffff) >> 9;
    short diff = sinetab[pos + 1] - sinetab[pos];
    
    return sinetab[pos] + (frac*diff >> 16);
}

static void pcm_callback(unsigned char** start, size_t* size)
{
    *start = (unsigned char *)buffers[cur_buf].data;
    *size = sizeof(buffers[cur_buf].data);
    cur_buf = (cur_buf + 1) & 1;
    fill_buffer = cur_buf + 1;
}

static void generate(int bufnum) ICODE_ATTR;
static void generate(int bufnum)
{
    int i, blockend;
    short sample;
    short *data = buffers[bufnum].data;

    if (BLOCK_SIZE < duration*SAMPLE_RATE - samples_done) {
        blockend = BLOCK_SIZE;
    } else {
        blockend = duration*SAMPLE_RATE - samples_done;
        finished = true;
    }
    for (i = 0; i < blockend*2; i += 2) {
        sample = fsin((uint32_t)(pos >> 16)*(samples_done + i/2));
        data[i] = sample;
        data[i + 1] = sample;
        pos += delta;
    }
    samples_done += blockend;
}

static bool main_menu(void)
{
    int selection, result;
    bool ret = false;
    
    MENUITEM_STRINGLIST(menu,"Signal generator", NULL, "Start frequency",
                        "Stop frequency", "Duration", "Sweep type",
                        "Generate signal", "Exit");

    do {
        result = rb->do_menu(&menu, &selection);
        switch (result) {
        case 0:
            rb->set_int("Start frequency", "Hz", UNIT_HERTZ, &start_freq,
                    NULL, 10, 10, 20000, NULL);
            break;
        case 1:
            rb->set_int("Stop frequency", "Hz", UNIT_HERTZ, &stop_freq,
                    NULL, 200, 10, 20000, NULL);
            break;
        case 2:
            rb->set_int("Duration", "s", UNIT_SEC, &duration,
                    NULL, 1, 1, 120, NULL);
            break;
        case 3:
            /* TODO implement sweep type */
            break;
        case 4:
            ret = true;
            break;
        case 5:
            ret = false;
            break;
        }
    } while (result < 4);

    return ret;
}

enum plugin_status plugin_start(struct plugin_api *api, void *parameter)
{
    (void)parameter;
    rb = api;
    
    PLUGIN_IRAM_INIT(api)

    rb->pcm_play_stop();
    rb->pcm_set_frequency(SAMPLE_RATE);
    while (main_menu()) {
        finished = false;
        samples_done = 0;
        cur_freq = start_freq;
        if (sweep_type == 0) {
            //pos = 0xffff*start_freq/SAMPLE_RATE;
            //delta = 0xffff*(stop_freq - start_freq)/SAMPLE_RATE/(duration*SAMPLE_RATE);
            pos = 0xffffffffULL*((unsigned long long)start_freq << 16)/SAMPLE_RATE;
            delta = ((0xffffffffULL*((unsigned long long)(stop_freq - start_freq) << 16))/SAMPLE_RATE)/(duration*SAMPLE_RATE*2);
        } else if (sweep_type == 1) {
            /* Later */
        }
        generate(0);
        rb->pcm_play_data(pcm_callback, NULL, 0);

        while (!finished) {
            if (rb->button_get(false) == BTN_CANCEL)
                finished = true;
            if (fill_buffer) {
                generate(fill_buffer - 1);
                fill_buffer = 0;
            }
        }
        rb->pcm_play_stop();
    }
    return PLUGIN_OK;
}

