/*
 * Copyright (C) 2013, 2014 Giorgio Vazzana
 *
 * This file is part of Seren.
 *
 * Seren is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Seren is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @file
 * Header for the alsa audio module.
 * This module provides an interface to capture/playback audio using alsa.
 */

#ifndef AUDIO_H
#define AUDIO_H

#include <alsa/asoundlib.h>
#include <poll.h>
#include <stdint.h>

/**
 * Alsa audio context.
 * At the moment, some members of this structure have to be accessed directly.
 */
struct audio_data {
	const char          *device;      /**< device name */
	snd_pcm_stream_t     stream;      /**< stream type */
	snd_pcm_access_t     access;      /**< access type, set to SND_PCM_ACCESS_RW_INTERLEAVED */
	snd_pcm_format_t     format;      /**< format type, set to SND_PCM_FORMAT_S16_LE */
	snd_pcm_t           *pcm_handle;  /**< pcm handle */

	snd_pcm_hw_params_t *hw_params;   /**< hardware parameters */
	unsigned int         rate;        /**< sample rate */
	unsigned int         channels;    /**< channels */
	unsigned int         periods;     /**< periods, set to 10 */
	snd_pcm_uframes_t    period_size; /**< period size */
	unsigned int         period_time; /**< period time, set to 20ms */
	snd_pcm_uframes_t    buffer_size; /**< buffer size */
	unsigned int         buffer_time; /**< buffer time */

	snd_pcm_sw_params_t *sw_params;       /**< software parameters */
	snd_pcm_uframes_t    avail_min;       /**< avail min */
	snd_pcm_uframes_t    start_threshold; /**< start threshold, set to 2*period_size */
	snd_pcm_uframes_t    stop_threshold;  /**< stop threshold */

	/**
	 * array of poll descriptors.
	 * From alsa doc: the field values in pollfd structs may be bogus regarding the
	 * stream direction from the application perspective (POLLIN might not imply read
	 * direction and POLLOUT might not imply write), thus they need "demangling".
	 */
	struct pollfd       *pfds;
	unsigned int         nfds;    /**< count of poll descriptors */
	unsigned short       revents; /**< returned events after "demangling" */

	int16_t             *alsabuffer;     /**< read buffer */
	snd_pcm_uframes_t    alsabuffersize; /**< read buffer size in frames, set to period_size */
	snd_pcm_sframes_t    frames;         /**< number of frames read or written */
	snd_pcm_sframes_t    avail;          /**< number of frames ready to be read (capture) / written (playback) */
/*	float                peak_percent; */

	const int           *verbose; /**< verbosity level */
};

/**
 * Opens and init the PCM audio device.
 * @param[out] ad a pointer to the audio context
 * @param[in]  device alsa device name (for example "hw:0,0")
 * @param[in]  streamtype alsa stream type (SND_PCM_STREAM_CAPTURE or SND_PCM_STREAM_PLAYBACK)
 * @param[in]  rate sampling frequency
 * @param[in]  channels number of channels
 * @param[in]  verbose a pointer to a int variable that controls the verbosity
 * @return     0 on success and -1 on error
 */
int  audio_init(struct audio_data *ad, const char *device, snd_pcm_stream_t streamtype,
                unsigned int rate, unsigned int channels, const int *verbose);

/**
 * Starts the PCM audio device.
 * @param[in,out] ad a pointer to the audio context
 * @return        0 on success and -1 on error
 */
int  audio_start(struct audio_data *ad);

/**
 * Stops the PCM audio device.
 * @param[in,out] ad a pointer to the audio context
 * @return        0 on success and -1 on error
 */
int  audio_stop(struct audio_data *ad);

/**
 * Gets returned events from poll descriptors.
 * From alsa doc: this function does "demangling" of the revents mask returned from
 * the poll() syscall to correct semantics (POLLIN = read, POLLOUT = write).
 * @param[in,out] ad a pointer to the audio context
 * @return        returned (single) event. This value is also stored in ad->revents
 */
unsigned short audio_poll_descriptors_revents(struct audio_data *ad);

/**
 * Gets the number of frames ready to be read (capture) / written (playback).
 * @param[in,out] ad a pointer to the audio context
 * @param[in]     force_verbose set to a non-zero value to force verbosity
 * @return        a positive number of frames ready, otherwise a negative error code. This value is also stored in ad->avail
 */
snd_pcm_sframes_t audio_avail(struct audio_data *ad, int force_verbose);

/**
 * Reads interleaved frames from a PCM audio device.
 * A number of frames equal to ad->alsabuffersize will be read and stored in ad->alsabuffer.
 * @param[in,out] ad a pointer to the audio context
 * @param[in]     force_verbose set to a non-zero value to force verbosity
 * @return        a positive number of frames actually read, otherwise a negative error code. This value is also stored in ad->frames
 */
snd_pcm_sframes_t audio_read(struct audio_data *ad, int force_verbose);

/**
 * Writes interleaved frames to a PCM audio device.
 * @param[in,out] ad a pointer to the audio context
 * @param[in]     buffer buffer contaning the frames to be written
 * @param[in]     buffersize number of frames to write
 * @param[in]     force_verbose set to a non-zero value to force verbosity
 * @return        a positive number of frames actually written, otherwise a negative error code. This value is also stored in ad->frames
 */
snd_pcm_sframes_t audio_write(struct audio_data *ad, const int16_t *buffer, snd_pcm_uframes_t buffersize, int force_verbose);

/**
 * Recovers the stream state from an error or suspend.
 * @param[in,out] ad a pointer to the audio context
 * @param[in]     err error number
 * @param[in]     force_verbose set to a non-zero value to force verbosity
 * @return        0 when error code was handled successfuly, otherwise a negative error code
 */
int  audio_recover(struct audio_data *ad, int err, int force_verbose);

#if 0
/**
 * Find the peak in the last chunk of audio read.
 * @param[in,out] ad a pointer to the audio context
 * @return        the peak in percent (from 0.0f to 100.0f)
 */
float audio_find_peak(struct audio_data *ad);
#endif

/**
 * Closes the PCM audio device.
 * Also, frees the allocated memory and resources.
 * @param[in,out] ad a pointer to the audio context
 * @return        0 on success and -1 on error
 */
int  audio_close(struct audio_data *ad);

#endif