/**
 * @file audio.h
 * @brief Declares the audio backend: where SOUND, PLAY, ENVELOPE and VOL land.
 *
 * Same reasoning as graphics.h -- the core library is free of SDL, so the sound
 * verbs call through a record of function pointers and akbasic_audio_init_akgl()
 * in the akbasic_akgl target wires that record to akgl_audio_*.
 *
 * The libakgl API this is modelled on is a tone generator, not a sample player:
 * three voices, a waveform and an ADSR envelope each, mixed to one stream. That
 * is the right shape, because BASIC 7.0's sound verbs describe notes rather than
 * recordings. What it does not have is a filter stage, which is why FILTER is
 * refused -- see TODO.md section 7.
 */

#ifndef _AKBASIC_AUDIO_H_
#define _AKBASIC_AUDIO_H_

#include <akerror.h>

#include <akbasic/types.h>

/** @brief Number of independent voices, matching the SID and matching libakgl. */
#define AKBASIC_AUDIO_VOICES	3

/** @brief How many ENVELOPE presets a program may define. BASIC 7.0 numbers them 0 through 9. */
#define AKBASIC_ENVELOPES	10

/** @brief How many notes one PLAY string may queue. */
#define AKBASIC_MAX_PLAY_NOTES	128

/** @brief TEMPO's range, as BASIC 7.0 states it. */
#define AKBASIC_TEMPO_MIN	1
#define AKBASIC_TEMPO_MAX	255
#define AKBASIC_TEMPO_DEFAULT	8

/**
 * @brief Waveform selection, numbered as BASIC 7.0's SOUND numbers it.
 *
 * Declared here rather than reused from akgl/audio.h so the core library does
 * not include a libakgl header. The akgl backend maps these across; the values
 * happen to agree today and the mapping is still written out, because two
 * enumerations agreeing by accident is not a contract.
 */
typedef enum
{
    AKBASIC_WAVE_TRIANGLE = 0,	/* SOUND waveform 0 */
    AKBASIC_WAVE_SAWTOOTH = 1,	/* SOUND waveform 1 */
    AKBASIC_WAVE_SQUARE	  = 2,	/* SOUND waveform 2, and the power-on default */
    AKBASIC_WAVE_NOISE	  = 3	/* SOUND waveform 3 */
} akbasic_Waveform;

/**
 * @brief Where the sound verbs play.
 *
 * Voices here are numbered from zero. BASIC numbers them 1 through 3 and the
 * conversion happens once, in the verb handler, so a backend never sees a BASIC
 * voice number.
 *
 * Durations are milliseconds. BASIC counts in jiffies (1/60 s) and note lengths
 * derived from TEMPO; both are converted in src/audio_tables.c before they get
 * here, so a backend never sees a jiffy either.
 */
typedef struct akbasic_AudioBackend
{
    void *self;

    /** Start a note on a voice, for a bounded duration. */
    akerr_ErrorContext AKERR_NOIGNORE *(*tone)(struct akbasic_AudioBackend *self, int voice, double hz, int ms);
    /**
     * Start a note whose pitch moves, which is what SOUND's `dir`, `min` and
     * `step` arguments ask for -- a siren, a laser, a falling bomb.
     *
     * May be NULL, and a host that leaves it so gets `SOUND` refused with
     * AKBASIC_ERR_DEVICE for a swept note and served normally for a held one.
     * That is deliberate: this arrived in libakgl 0.3.0, and a backend written
     * against an older one is still a valid backend for everything else.
     */
    akerr_ErrorContext AKERR_NOIGNORE *(*sweep)(struct akbasic_AudioBackend *self, int voice, double from_hz, double to_hz, double step_hz, int ms);
    /** Silence a voice immediately. */
    akerr_ErrorContext AKERR_NOIGNORE *(*stop)(struct akbasic_AudioBackend *self, int voice);
    /** Select the waveform a voice synthesises. */
    akerr_ErrorContext AKERR_NOIGNORE *(*waveform)(struct akbasic_AudioBackend *self, int voice, akbasic_Waveform waveform);
    /** Set a voice's ADSR envelope. Sustain is a level from 0.0 to 1.0, not a time. */
    akerr_ErrorContext AKERR_NOIGNORE *(*envelope)(struct akbasic_AudioBackend *self, int voice, int attack, int decay, double sustain, int release);
    /** Set the master output level, 0.0 to 1.0. */
    akerr_ErrorContext AKERR_NOIGNORE *(*volume)(struct akbasic_AudioBackend *self, double level);
    /** Report whether a voice is still sounding. PLAY's queue uses this to pace itself. */
    akerr_ErrorContext AKERR_NOIGNORE *(*voice_active)(struct akbasic_AudioBackend *self, int voice, bool *active);
} akbasic_AudioBackend;

/** @brief One ENVELOPE preset, in the units the backend takes. */
typedef struct
{
    int attack;			/* milliseconds					*/
    int decay;			/* milliseconds					*/
    double sustain;		/* level from 0.0 to 1.0, not a time		*/
    int release;		/* milliseconds					*/
    akbasic_Waveform waveform;
} akbasic_Envelope;

/** @brief One note waiting to be played. A rest occupies time and makes no sound. */
typedef struct
{
    int voice;
    double hz;
    int ms;
    bool rest;
} akbasic_PlayNote;

/**
 * @brief The sound verbs' own state, which lives on the runtime.
 *
 * The queue is why PLAY does not block. On a C128, PLAY holds the program until
 * the string finishes; section 1.6 forbids that, so PLAY parses the string into
 * this queue and returns, and akbasic_runtime_step() releases one note at a time
 * against the host's clock. A host keeps its frame rate and the notes still come
 * out in order, at their written lengths.
 */
typedef struct
{
    akbasic_Envelope envelopes[AKBASIC_ENVELOPES];
    akbasic_PlayNote queue[AKBASIC_MAX_PLAY_NOTES];
    int head;			/* next note to start					*/
    int count;			/* notes queued						*/
    int64_t nextms;		/* host time the head note may start at			*/
    bool sounding;		/* a queued note is currently held			*/

    int tempo;			/* TEMPO, 1 to 255					*/
    int voice;			/* PLAY's current voice, 0-based			*/
    int octave;			/* PLAY's current octave, 0 to 6			*/
    int envelope;		/* PLAY's current ENVELOPE preset			*/
    int notems;			/* PLAY's current note length in milliseconds		*/
    double level;		/* VOL, 0.0 to 1.0					*/
} akbasic_AudioState;

/**
 * @brief Reset the sound state to its power-on values.
 * @param obj Object to initialize, inspect, or modify.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `obj` is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_audio_state_init(akbasic_AudioState *obj);

/**
 * @brief Convert a SOUND frequency register value to hertz.
 *
 * BASIC's SOUND takes the number that would go into a SID frequency register,
 * not a pitch. The backend takes hertz.
 *
 * @param value Register value, 0 through 65535.
 * @param dest Output destination populated by the function.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `dest` is NULL.
 * @throws AKBASIC_ERR_BOUNDS When `value` is outside 0..65535.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_audio_register_to_hz(int value, double *dest);

/**
 * @brief Convert an ENVELOPE rate number to milliseconds.
 *
 * BASIC's ENVELOPE takes the SID's 0-15 rate numbers, whose real durations are
 * non-linear and tabulated in silicon rather than computed.
 *
 * @param rate Rate number, 0 through 15.
 * @param isattack True for the attack table, false for the decay/release one.
 * @param dest Output destination populated by the function.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `dest` is NULL.
 * @throws AKBASIC_ERR_BOUNDS When `rate` is outside 0..15.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_audio_rate_to_ms(int rate, bool isattack, int *dest);

/**
 * @brief Pitch of a note, by semitone offset from C and by octave.
 * @param semitone Offset from C within the octave, 0 through 11.
 * @param octave Octave number, 0 through 6, where 4 holds the A of 440 Hz.
 * @param dest Output destination populated by the function.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `dest` is NULL.
 * @throws AKBASIC_ERR_BOUNDS When either argument is out of range.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_audio_note_hz(int semitone, int octave, double *dest);

/**
 * @brief How long a whole note lasts at a given TEMPO, in milliseconds.
 * @param tempo TEMPO value, 1 through 255.
 * @param dest Output destination populated by the function.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `dest` is NULL.
 * @throws AKBASIC_ERR_BOUNDS When `tempo` is outside 1..255.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_audio_whole_note_ms(int tempo, int *dest);

/* --- Internal API: exposed for the PLAY verb, the step loop and the tests. --- */

struct akbasic_Runtime;

/**
 * @brief Parse a PLAY string and append its notes to the queue.
 *
 * Split out from the verb so a test can assert what a string parses *to* without
 * also having to drive the clock that drains it.
 *
 * @param obj Object to initialize, inspect, or modify.
 * @param notes A PLAY note string.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When either argument is NULL.
 * @throws AKBASIC_ERR_SYNTAX When the string contains something PLAY does not define.
 * @throws AKBASIC_ERR_BOUNDS When a setting is out of range or the queue is full.
 * @throws AKBASIC_ERR_DEVICE When the string selects the filter, which does not exist.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_play_parse(struct akbasic_Runtime *obj, const char *notes);

/**
 * @brief Release the next queued note if the one before it has run out.
 *
 * Called from akbasic_runtime_step(). This is what makes PLAY non-blocking: the
 * verb queues and returns, and the notes come out here against whatever time the
 * host last handed to akbasic_runtime_settime().
 *
 * @param obj Object to initialize, inspect, or modify.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `obj` is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_play_service(struct akbasic_Runtime *obj);

#endif // _AKBASIC_AUDIO_H_
