/**
 * @file audio_tables.c
 * @brief The SOUND, ENVELOPE and PLAY conversions, laid out as the tables they are.
 *
 * BASIC 7.0's sound verbs speak in SID register values: SOUND takes a frequency
 * *register*, not a pitch, and ENVELOPE takes the SID's 0-15 rate numbers, whose
 * real durations are tabulated in silicon rather than computed. The backend takes
 * hertz and milliseconds. These are the conversions between the two, and they are
 * kept in one file because each is somewhere a wrong constant produces a
 * plausible wrong pitch rather than an error somebody would notice.
 *
 * Two of the three are transcriptions. The third -- TEMPO -- is a calibration
 * choice and is labelled as one where it is made.
 */

#include <math.h>

#include <akerror.h>

#include <akbasic/audio.h>
#include <akbasic/error.h>

/**
 * @brief The SID's attack rates, register value to milliseconds.
 *
 * Non-linear, and not a curve anybody would guess: the steps are small at the
 * bottom and enormous at the top. This is the 6581/8580 table.
 */
static const int ATTACK_MS[16] = {
       2,			/*  0 */
       8,			/*  1 */
      16,			/*  2 */
      24,			/*  3 */
      38,			/*  4 */
      56,			/*  5 */
      68,			/*  6 */
      80,			/*  7 */
     100,			/*  8 */
     250,			/*  9 */
     500,			/* 10 */
     800,			/* 11 */
    1000,			/* 12 */
    3000,			/* 13 */
    5000,			/* 14 */
    8000			/* 15 */
};

/**
 * @brief The SID's decay and release rates, register value to milliseconds.
 *
 * Exactly three times the attack table -- that is a property of the chip's
 * envelope generator, not a coincidence, so it is written out rather than
 * computed. A reader checking this against a datasheet should see the datasheet's
 * numbers.
 */
static const int DECAY_MS[16] = {
       6,			/*  0 */
      24,			/*  1 */
      48,			/*  2 */
      72,			/*  3 */
     114,			/*  4 */
     168,			/*  5 */
     204,			/*  6 */
     240,			/*  7 */
     300,			/*  8 */
     750,			/*  9 */
    1500,			/* 10 */
    2400,			/* 11 */
    3000,			/* 12 */
    9000,			/* 13 */
   15000,			/* 14 */
   24000			/* 15 */
};

/*
 * SOUND's frequency register to hertz.
 *
 * The SID computes Fout = (Fn * Fclk) / 2^24. Fclk is the system clock, which is
 * 1022730 Hz on an NTSC machine and 985248 on a PAL one -- so the same listing
 * plays very slightly sharp or flat depending on which side of the Atlantic it
 * was typed on. NTSC is used here; the difference is about 0.6%, well under a
 * semitone, and picking one is unavoidable.
 */
#define SID_CLOCK_HZ		1022730.0
#define SID_ACCUMULATOR		16777216.0

/*
 * A whole note at TEMPO 1, in milliseconds.
 *
 * This one is a **calibration choice, not a transcription.** BASIC 7.0 documents
 * TEMPO as a relative duration from 1 to 255 with a default of 8 and does not
 * publish the constant. 16000 puts a default-tempo quarter note at 500 ms, which
 * is 120 beats per minute -- a moderate tempo, which is what a default should be.
 * If somebody turns up the real constant, this is the one line to change.
 */
#define WHOLE_NOTE_AT_TEMPO_1	16000

/** @brief Concert pitch, and the note the equal-tempered scale is derived from. */
#define A4_HZ			440.0
/** @brief Semitone offset of A within an octave that starts at C. */
#define A_SEMITONE		9

akerr_ErrorContext *akbasic_audio_register_to_hz(int value, double *dest)
{
    PREPARE_ERROR(errctx);

    FAIL_ZERO_RETURN(errctx, (dest != NULL), AKERR_NULLPOINTER,
		     "NULL destination in register_to_hz");
    FAIL_ZERO_RETURN(errctx, (value >= 0 && value <= 65535), AKBASIC_ERR_BOUNDS,
		     "SOUND frequency %d out of range (0 to 65535)", value);
    *dest = ((double)value * SID_CLOCK_HZ) / SID_ACCUMULATOR;
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akbasic_audio_rate_to_ms(int rate, bool isattack, int *dest)
{
    PREPARE_ERROR(errctx);

    FAIL_ZERO_RETURN(errctx, (dest != NULL), AKERR_NULLPOINTER,
		     "NULL destination in rate_to_ms");
    FAIL_ZERO_RETURN(errctx, (rate >= 0 && rate <= 15), AKBASIC_ERR_BOUNDS,
		     "ENVELOPE rate %d out of range (0 to 15)", rate);
    *dest = isattack ? ATTACK_MS[rate] : DECAY_MS[rate];
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akbasic_audio_note_hz(int semitone, int octave, double *dest)
{
    PREPARE_ERROR(errctx);
    int steps = 0;

    FAIL_ZERO_RETURN(errctx, (dest != NULL), AKERR_NULLPOINTER,
		     "NULL destination in note_hz");
    FAIL_ZERO_RETURN(errctx, (semitone >= 0 && semitone <= 11), AKBASIC_ERR_BOUNDS,
		     "Note %d out of range (0 to 11)", semitone);
    FAIL_ZERO_RETURN(errctx, (octave >= 0 && octave <= 6), AKBASIC_ERR_BOUNDS,
		     "PLAY octave %d out of range (0 to 6)", octave);

    /*
     * Equal temperament from A4. Computed rather than tabulated because a table
     * of 84 frequencies is 84 chances to fat-finger one, and this is exact to
     * the precision anybody can hear.
     */
    steps = ((octave - 4) * 12) + (semitone - A_SEMITONE);
    *dest = A4_HZ * pow(2.0, (double)steps / 12.0);
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akbasic_audio_whole_note_ms(int tempo, int *dest)
{
    PREPARE_ERROR(errctx);

    FAIL_ZERO_RETURN(errctx, (dest != NULL), AKERR_NULLPOINTER,
		     "NULL destination in whole_note_ms");
    FAIL_ZERO_RETURN(errctx, (tempo >= AKBASIC_TEMPO_MIN && tempo <= AKBASIC_TEMPO_MAX),
		     AKBASIC_ERR_BOUNDS, "TEMPO %d out of range (%d to %d)",
		     tempo, AKBASIC_TEMPO_MIN, AKBASIC_TEMPO_MAX);
    *dest = WHOLE_NOTE_AT_TEMPO_1 / tempo;
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akbasic_audio_state_init(akbasic_AudioState *obj)
{
    PREPARE_ERROR(errctx);
    int i = 0;
    int wholems = 0;

    FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER,
		     "NULL audio state in init");

    obj->head = 0;
    obj->count = 0;
    obj->nextms = 0;
    obj->sounding = false;
    obj->tempo = AKBASIC_TEMPO_DEFAULT;
    obj->voice = 0;
    obj->octave = 4;
    obj->envelope = 0;
    obj->level = 1.0;

    PASS(errctx, akbasic_audio_whole_note_ms(obj->tempo, &wholems));
    obj->notems = wholems / 4;	/* PLAY starts on quarter notes */

    /*
     * Every preset starts as a plain square wave held at full level. A zeroed
     * envelope would have a sustain of 0.0, which is silence with nothing to
     * explain it -- the same reasoning libakgl gives for its own voice defaults.
     */
    for ( i = 0; i < AKBASIC_ENVELOPES; i++ ) {
	obj->envelopes[i].attack = 0;
	obj->envelopes[i].decay = 0;
	obj->envelopes[i].sustain = 1.0;
	obj->envelopes[i].release = 0;
	obj->envelopes[i].waveform = AKBASIC_WAVE_SQUARE;
    }
    SUCCEED_RETURN(errctx);
}
