/*
 * Copyright 2019-2020 NXP
 * This software is owned or controlled by NXP and may only be used strictly
 * in accordance with the applicable license terms.  By expressly accepting
 * such terms or by downloading, installing, activating and/or otherwise using
 * the software, you are agreeing that you have read, and that you agree to
 * comply with and are bound by, such license terms.  If you do not agree to
 * be bound by the applicable license terms, then you may not retain, install,
 * activate or otherwise use the software.
 */

#ifndef AUDIO_MIXER_H_
#define AUDIO_MIXER_H_

#include <stdint.h>
#include "error.h"
#include "audio.h"

/**
 * @defgroup KL_MODULES_AUDIOSERVICE_MIXER_API Mixer API
 * @ingroup KL_MODULES_AUDIOSERVICE
 *
 * The Audio Mixer module mixes multiple input interfaces into one output interface, while taking into account
 * a mixing factor for each input interface.
 *
 * The mixer is designed to be used in an **asynchronous** way. That is, the user is allowed to write different
 * amounts of samples to the different input interfaces (@ref AUDIO_MIXER_WriteSamples) at any time, and the
 * user is allowed to get any number of mixed output samples (@ref AUDIO_MIXER_GetMixedSamples) at any time.
 * To be able to cope with such fluctuations, the mixer implements a buffer of @ref MIXER_INPUT_RING_BUFFER_SIZE bytes
 * for each input interface, and only starts mixing an input interface as soon as its buffer reached the target filling
 * level (@ref MIXER_TARGET_BUFFER_FILLING). From that moment on, it is the user's responsibility to implement some sort of
 * synchronization mechanism, to avoid buffer overflows/underruns while repeatedly writing and getting samples.
 *
 * Ofcourse, the mixer can easily be used in a **synchronous** way. That is, first the user must write the same number
 * of samples to each input interface and reach the target buffer filling level (@ref MIXER_TARGET_BUFFER_FILLING). From that
 * moment on, the user must write the same number of samples to each input interface and get that same amount of mixed output
 * samples. The exact number of samples may vary with each iteration, but the number of written and read samples needs to be
 * equal during one iteration.
 *
 * The below image depicts the most important functions and the internal workings of the Audio Mixer.
 * @image html audio_mixer.svg
 *
 * \n\n\n The typical usage of the Audio Mixer takes the following steps.
 *
 * @par 0. Initialization/configuration:
 * - The user has to define the number of input interfaces using MIXER_INPUT_INTERFACE_CNT.
 * - Each input interface has its own amplification factor, which can be set using @ref AUDIO_MIXER_SetMixingFactors.
 *   Changing these values during mixing has an immediate effect.
 * - A @ref mixer_state_changed_cb callback should be registered using @ref AUDIO_MIXER_RegisterStateCb.
 *   The mixer uses this callback to signal when it becomes (not) ready to mix.
 * .
 *
 * @par 1. Buffering:
 * - Repeatedly write audio samples to each input interface, using @ref AUDIO_MIXER_WriteSamples.
 * - Internally, the mixer allocates a buffer for each input interface to keep these samples.
 * - As soon as any of these buffers gets half-full, the mixer becomes ready to mix.
 *   This is signaled to the user by calling the @ref mixer_state_changed_cb callback.
 * .
 *
 * @par 2. Mixing:
 * - The actual mixing happens in a polling fashion: the user has to ask for the mixed samples
 *   by calling @ref AUDIO_MIXER_GetMixedSamples.
 * - Repeatedly write audio samples to the input interfaces (@ref AUDIO_MIXER_WriteSamples),
 *   and get the the mixed output samples back (@ref AUDIO_MIXER_GetMixedSamples).
 * .
 *
 * @par 3. Stop:
 * - Stop all input interfaces of the mixer by calling @ref AUDIO_MIXER_StopAllInterfaces. This will trigger
 *   the @ref mixer_state_changed_cb callback, to signal that the mixer is no longer ready to
 *   output mixed samples.
 * .
 *
 * @note
 * - During mixing, the user has to make sure that getting mixed samples (output) and writing samples
 *   (input) happens at approximately the same rates. Using the internal buffers, the mixer can only
 *   cope with small fluctuations for a limited time.
 * - The computational complexity of calculating a mixed output sample increases with the number of input
 *   interfaces. Therefore, the user has to make sure that the time it takes to write and mix one sample,
 *   is less than playback duration of that sample.
 * .
 *
 * @{
 */

/* ---------------------------------------------------------------------------- */
/* Configuration                                                                */
/* ---------------------------------------------------------------------------- */

#ifdef AUDIO_SPEAKER_CNT
/**
 * The number of input interfaces of the mixer.
 */
    #define MIXER_INPUT_INTERFACE_CNT  AUDIO_SPEAKER_CNT    /* defined in audio.h */
#endif
#define MIXER_INPUT_INTERFACE_CNT  1
/* ---------------------------------------------------------------------------- */
/* Defines                                                                      */
/* ---------------------------------------------------------------------------- */

/**
 * The resolution of an audio amplification factor. It determines the valid range of values for
 * an amplification factor, being 0 to 2^MIXER_AMPLIFIER_RESOLUTION.
 */
#define MIXER_AMPLIFIER_RESOLUTION  (5)  /* Resolution of an amplification factor -> range is [0; 2^5] */

/**
 * Minimum value of an amplification factor
 */
#define MIXER_MIN_AMP  (0)

/**
 * Maximum value for an amplification factor
 */
#define MIXER_MAX_AMP  (1 << MIXER_AMPLIFIER_RESOLUTION)

/**
 * Number of (stereo) samples to target for buffering
 * 128 samples buffer target at 48kHz introduces a delay of 20.833us * 128 samples = 2.667ms
 */
#define NR_OF_SAMPLES_TO_BUFFER  (128 * TX_AUDIO_CHANNELS)

/**
 * The maximum expected audio sample size coming into the mixer (from USB) in bytes
 */
#define MAX_AUDIO_SAMPLE_SIZE_IN      (3)
/**
 * Size of the internal ring buffer for an input interface.
 * The buffer should be at least twice the target level.
 */
#define MIXER_INPUT_RING_BUFFER_SIZE  (4096)
#if ((NR_OF_SAMPLES_TO_BUFFER * MAX_AUDIO_SAMPLE_SIZE_IN) > MIXER_INPUT_RING_BUFFER_SIZE / 2)
    #error buffer too small
#endif

/**
 * Audio mixer target buffer filling level.
 * Target for NR_OF_SAMPLES_TO_BUFFER samples
 */
#define MIXER_TARGET_BUFFER_FILLING(x)  (NR_OF_SAMPLES_TO_BUFFER * x)

/**
 * The possible states of the audio mixer.
 */
typedef enum _audio_mixer_state {
    /** Not ready to output mixed samples (i.e. none of the input interfaces has reached @ref MIXER_TARGET_BUFFER_FILLING level) */
    kAUDIO_MIXER_STATE_NotReady,
    /** Ready to output mixed samples (i.e. at least one input interface has reached @ref MIXER_TARGET_BUFFER_FILLING level) */
    kAUDIO_MIXER_STATE_Ready,
    /** Used as counter for number of possible states */
    kAUDIO_MIXER_STATE_Count,
} audio_mixer_state_t;

/**
 * Callback type for Audio Mixer's ready-state change events.
 *
 * @param state The current state of the mixer (@ref audio_mixer_state_t).
 */
typedef void (*mixer_state_changed_cb)(audio_mixer_state_t state);

/* -------------------------------------------------------------------------
 * Public functions
 * ------------------------------------------------------------------------- */

/**
 * Initialize the entire mixer component.
 */
void AUDIO_MIXER_Init(void);

/**
 * De-initialize the entire mixer component.
 */
void AUDIO_MIXER_Deinit(void);

/**
 * Get the current amplification factors of the input interfaces of the mixer.
 *
 * @param[out] amps User-provided array of size MIXER_INPUT_INTERFACE_CNT in which the amplification
 *             factor of each input interface will be stored.
 */
void AUDIO_MIXER_GetMixingFactors(uint8_t *amps);

/**
 * Set the amplification factors for the input interfaces of the mixer.
 * Each amplification factor needs to be valid (a value between MIXER_MIN_AMP and MIXER_MAX_AMP),
 * and their sum must be equal to MIXER_MAX_AMP (to avoid changing the amplitude of the output signal).
 *
 * @param[in] amps Array of size MIXER_INPUT_INTERFACE_CNT specifying the amplification factor for each input interface.
 * @return A result code:
 *         - kERROR_Ok: indicates success.
 *         - kERROR_Fail: indicates invalid amplification factor(s) were chosen.
 *         .
 */
error_t AUDIO_MIXER_SetMixingFactors(uint8_t *amps);

/**
 * Configure the callback to be used when the ready-state of the mixer changes; mixer did (not)
 * become ready to output mixed samples.
 *
 * @param cb The callback function to be used.
 */
void AUDIO_MIXER_RegisterStateCb(mixer_state_changed_cb cb);

/**
 * Writes data into the buffer of an input interface of the mixer.
 * @note The mixer implements buffering; it will only start outputting audio samples
 * as soon as one of the input interfaces has received plenty of samples.
 *
 * @param interfaceIdx Index of input interface that is providing the samples.
 * @param pBufferInput Buffer that is going to be written.
 * @param nbSamples Number of audio samples being written.
 * @param bufferInputFormat Data format of pBufferInput buffer.
 */
void AUDIO_MIXER_WriteSamples(uint8_t interfaceIdx, uint8_t *pBufferInput, uint16_t nbSamples, uint8_t bufferInputFormat);

/**
 * Write a certain number of output samples (i.e. mixed samples) to a buffer.
 * @note Only input interfaces that have enough samples buffered, will be mixed according to the mixing
 * factors. If the mixer was not ready to output mixed samples (@ref kAUDIO_MIXER_STATE_NotReady), the
 * output samples will be all zeroes.
 *
 * @param[out] outBuffer Pointer to buffer to which the mixed samples will be written.
 * @param[in]  nbSamples Number of audio samples to be mixed.
 */
void AUDIO_MIXER_GetMixedSamples(void *outBuffer, uint16_t nbSamples);

/**
 * Write a certain number of empty samples (i.e. all zeroes) to a buffer.
 *
 * @param[out] outBuffer Pointer to buffer to which the empty samples will be written.
 * @param[in]  nbSamples Number of audio samples to be generated.
 */
void AUDIO_MIXER_GetEmptySamples(void *outBuffer, uint16_t nbSamples);

/**
 * Stop an input interface of the mixer.
 * @note The mixer will stop outputting audio samples as soon as all input interfaces have been stopped.
 *
 * @param interfaceIdx Index of input interface to be stopped.
 */
void AUDIO_MIXER_StopInterface(uint8_t interfaceIdx);

/**
 * Stop all input interfaces of the mixer.
 */
void AUDIO_MIXER_StopAllInterfaces(void);

/**
 * Returns the buffer size of an input interface of the mixer.
 *
 * @param interfaceIdx Index of the input interface.
 * @return The buffer size of the input interface.
 */
uint32_t AUDIO_MIXER_GetBufferSize(uint8_t interfaceIdx);

/**
 * Returns the current filling level (space in use) of an input interface's buffer.
 *
 * @param interfaceIdx Index of the input interface.
 * @return The current filling level (in bytes) of the input interface.
 */
uint32_t AUDIO_MIXER_GetBufferFilling(uint8_t interfaceIdx);

/** @} */

#endif /* AUDIO_MIXER_H_ */
