/*****************************************************************************

        GRAOUMF TRACKER 2
        Author: Laurent de Soras, 1996-2016

--- Legal stuff ---

This program is free software. It comes without any warranty, to
the extent permitted by applicable law. You can redistribute it
and/or modify it under the terms of the Do What The Fuck You Want
To Public License, Version 2, as published by Sam Hocevar. See
http://sam.zoy.org/wtfpl/COPYING for more details.

*****************************************************************************/



/*\\\ FICHIERS INCLUDE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>
#include	<math.h>

#include "archi.h"
#include "base.h"
#include "base_ct.h"
#include	"file.h"
#include "log.h"
#include "memory.h"
#include "samp.h"
#include "spl_raw.h"
#include "spl_wav.h"
#include "splstruc.h"
#include	"WaveForm.h"



/*\\\ CONSTANTES PRIVEES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ TYPES & STRUCTURES PRIVEES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

typedef struct
{
	char		chunk_id [4];
	LWORD		chunk_size;
	char		riff_id [4];
} SPLS_WAV_HEADER_RIFF;

typedef struct
{
	char		chunk_id [4];		/* "fmt " */
	LWORD		chunk_size;
	WORD		format_tag;
	UWORD		channels;
	ULWORD	samples_per_sec;
	ULWORD	avg_bytes_per_sec;
	UWORD		block_align;
	UWORD		bits_per_sample;
} SPLS_WAV_HEADER_FMT;

typedef struct
{
	char		chunk_id [4];		/* "data" */
	LWORD		chunk_size;
} SPLS_WAV_HEADER_DATA;

typedef struct
{
	LWORD		identifier;
	LWORD		type;
	LWORD		start;
	LWORD		end;
	LWORD		fraction;
	LWORD		play_count;
} SPLS_WAV_HEADER_LOOP;

typedef struct
{
	char		chunk_id [4];		/* "smpl" */
	LWORD		chunk_size;
	LWORD		manufacturer;
	LWORD		product;
	LWORD		sample_period;
	LWORD		midi_unity_note;
	LWORD		midi_pitch_fraction;
	LWORD		smpte_format;
	LWORD		smpte_offset;
	LWORD		sample_loops;
	LWORD		sampler_data;
	SPLS_WAV_HEADER_LOOP	loops [1];
} SPLS_WAV_HEADER_SMPL;

typedef struct
{
	char		chunk_id [4];		/* "inst" */
	LWORD		chunk_size;

	UBYTE		unshifted_note;
	SBYTE		fine_tune;
	SBYTE		gain;
	UBYTE		low_note;
	UBYTE		high_note;
	UBYTE		low_velocity;
	UBYTE		high_velocity;
} SPLS_WAV_HEADER_INST;



/*\\\ PROTOTYPES DES FONCTIONS PRIVEES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ VARIABLES EXTERNES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

const char	*SPLWAV_sample_name_0 = "Microsoft wave";
const char	*SPLWAV_sample_extension_0 = "*.wav";



/*\\\ VARIABLES PRIVEES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ FONCTIONS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*==========================================================================*/
/*      Nom: SPLWAV_save_sample                                             */
/*      Description: Sauve un sample en WAV.                                */
/*      Parametres en entree:                                               */
/*        - file_ptr: pointeur du fichier a sauver.                         */
/*        - sample: numero du sample concerne.                              */
/*      Retour: 0 si tout s'est bien passe.                                 */
/*==========================================================================*/

signed int	SPLWAV_save_sample (FILE *file_ptr, int sample)
{
	void		*mem_data_ptr;
	FILE		*file_data_ptr;
	SPLS_PRIMARY_INFO	sample_info;

	SPLS_init_primary_info (&sample_info, sample);

	if (SAMP_get_sample_type (sample) == 1)
	{
		mem_data_ptr = NULL;
		file_data_ptr = SAMP_get_sample_file_ptr (sample);
		if (FILE_fseek (file_data_ptr, SAMP_get_sample_file_data_offset (sample), SEEK_SET) != 0)
		{
			LOG_printf ("SPLWAV_save_sample: Error: couldn't position on Direct-To-Disk sample data.\n");
			return (-1);
		}
	}
	else
	{
		mem_data_ptr = SAMP_get_sample_data_adr (sample);
		file_data_ptr = NULL;
	}
	
	if (SPLWAV_save_sample_header (file_ptr, &sample_info))
	{
		LOG_printf ("SPLWAV_save_sample: Error: couldn't save sample header.\n");
		return (-1);
	}

	sample_info.position = 0;
	sample_info.block_len = sample_info.length;

	if (SPLWAV_save_sample_data (file_ptr, &sample_info, mem_data_ptr, file_data_ptr))
	{
		LOG_printf ("SPLWAV_save_sample: Error: couldn't save sample data.\n");
		return (-1);
	}

	if (SPLWAV_save_sample_tail (file_ptr, &sample_info))
	{
		LOG_printf ("SPLWAV_save_sample: Error: couldn't save sample tail.\n");
		return (-1);
	}

	return (0);
}



/*==========================================================================*/
/*      Nom: SPLWAV_save_sample_header                                      */
/*      Description: Sauve le header du sample.                             */
/*      Parametres en entree/sortie:                                        */
/*        - file_ptr: pointeur sur le fichier a sauver.                     */
/*        - sample_info_ptr: pointeur sur les informations sur le sample,   */
/*                           qui doivent etre deja initialisees. Cette      */
/*                           routine fixe le bloc a sauver a la totalite du */
/*                           sample.                                        */
/*      Retour: 0 si tout s'est bien passe.                                 */
/*==========================================================================*/

signed int	SPLWAV_save_sample_header (FILE *file_ptr, SPLS_PRIMARY_INFO *sample_info_ptr)
{
	int            block_align   =
		sample_info_ptr->tracks * ((sample_info_ptr->nbits + 7) >> 3);
	SPLS_WAV_HEADER_RIFF riff_chunk;
	size_t         header_length = (sizeof (riff_chunk) + 1) & -2;
	int64_t        total_length  = header_length;

	/* fmt: Informations generales */
	SPLS_WAV_HEADER_FMT  fmt_chunk;
	memcpy (fmt_chunk.chunk_id, "fmt ", 4);
	fmt_chunk.chunk_size        = BASE_intel_lword (sizeof (fmt_chunk) - 8);
	fmt_chunk.format_tag        = BASE_intel_word (1);
	fmt_chunk.channels          = BASE_intel_word (sample_info_ptr->tracks);
	fmt_chunk.samples_per_sec   = BASE_intel_lword (sample_info_ptr->freq);
	fmt_chunk.bits_per_sample   = BASE_intel_word (sample_info_ptr->nbits);
	fmt_chunk.block_align       = BASE_intel_word (block_align);
	fmt_chunk.avg_bytes_per_sec = BASE_intel_lword (sample_info_ptr->freq * block_align);
	
	if (FILE_fseek (file_ptr, header_length, SEEK_SET) != 0)
	{
		LOG_printf ("SPLAVR_save_sample_header: Error: couldn't position on fmt chunk.\n");
		return (-1);
	}
	int64_t        real_size = (sizeof (fmt_chunk) + 1) & -2;
	if (fwrite (&fmt_chunk, sizeof (fmt_chunk), 1, file_ptr) != 1)
	{
		LOG_printf ("SPLWAV_save_sample_header: Error: couldn't save fmt chunk.\n");
		return (-1);
	}
	header_length += size_t (real_size);
	total_length  += real_size;

	/* data: donnees. On le met toujours apres fmt car certains editeurs de
	   samples supposent que c'est toujours le cas en chargeant des WAV. */
	SPLS_WAV_HEADER_DATA data_chunk;
	memcpy (data_chunk.chunk_id, "data", 4);
	data_chunk.chunk_size = BASE_intel_lword (sample_info_ptr->length * block_align);

	if (FILE_fseek (file_ptr, header_length, SEEK_SET) != 0)
	{
		LOG_printf ("SPLAVR_save_sample_header: Error: couldn't position on data chunk.\n");
		return (-1);
	}
	real_size = (sizeof (data_chunk) + 1) & -2;
	if (fwrite (&data_chunk, sizeof (data_chunk), 1, file_ptr) != 1)
	{
		LOG_printf ("SPLWAV_save_sample_header: Error: couldn't save data chunk.\n");
		return (-1);
	}
	header_length += size_t (real_size);
	total_length  += real_size;
	real_size = (int64_t (sample_info_ptr->length) * block_align + 1) & -2;
	total_length += real_size;

	/* smpl: bouclage. Calcul de la taille necessaire */
	if (sample_info_ptr->loop_type != WaveForm_LOOP_TYPE_NONE)
	{
		real_size = (sizeof (SPLS_WAV_HEADER_SMPL) + 1) & -2;
		total_length += real_size;
	}

	/* inst: instrument. Calcul de la taille necessaire */
	real_size = (sizeof (SPLS_WAV_HEADER_INST) + 1) & -2;
	total_length += real_size;

	/* RIFF: master chunk */
	memcpy (riff_chunk.chunk_id, "RIFF", 4);
	riff_chunk.chunk_size = BASE_intel_lword (LWORD (total_length - 8));
	memcpy (riff_chunk.riff_id, "WAVE", 4);

	if (FILE_fseek (file_ptr, 0, SEEK_SET) != 0)
	{
		LOG_printf ("SPLAVR_save_sample_header: Error: couldn't position on RIFF chunk.\n");
		return (-1);
	}
	if (fwrite (&riff_chunk, sizeof (riff_chunk), 1, file_ptr) != 1)
	{
		LOG_printf ("SPLWAV_save_sample_header: Error: couldn't save RIFF chunk.\n");
		return (-1);
	}

	sample_info_ptr->data_offset = header_length;
	sample_info_ptr->signed_flag = (sample_info_ptr->nbits > 8);
	sample_info_ptr->byte_order  = 1;

	return (0);
}



/*==========================================================================*/
/*      Nom: SPLWAV_save_sample_data                                        */
/*      Description: Sauve les donnees d'un sample                          */
/*      Parametres en entree:                                               */
/*        - sample_info_ptr: pointeur sur la structure d'informations sur   */
/*                           le sample. Les champs position et block_len    */
/*                           indiquent la partie du sample a sauver.        */
/*        - mem_data_ptr: pointeur sur le bloc a sauver. NULL si le sample  */
/*                        est sur disque.                                   */
/*        - file_data_ptr: pointeur sur le fichier qui contient le bloc     */
/*                         a sauver, positionne au debut du bloc. NULL si   */
/*                         le sample est en memoire.                        */
/*      Parametres en entree/sortie:                                        */
/*        - file_ptr: pointeur sur le fichier a sauver. Celui-ci est        */
/*                    place automatiquement au bon endroit.                 */
/*        - sample_info_ptr: pointeur sur la structure d'informations sur   */
/*                           le sample. Les champs position et block_len    */
/*                           indiquent la partie du sample a sauver.        */
/*      Retour: 0 si tout s'est bien passe.                                 */
/*==========================================================================*/

signed int	SPLWAV_save_sample_data (FILE *file_ptr, SPLS_PRIMARY_INFO *sample_info_ptr, void *mem_data_ptr, FILE *file_data_ptr)
{
	return (SPLRAW_save_sample_data (file_ptr, sample_info_ptr, mem_data_ptr, file_data_ptr));
}



/*==========================================================================*/
/*      Nom: SPLWAV_save_sample_tail                                        */
/*      Description: Sauve les informations finales d'un sample.            */
/*      Parametres en entree:                                               */
/*        - sample_info_ptr: pointeur sur la structure d'informations sur   */
/*                           le sample.                                     */
/*      Parametres en entree/sortie:                                        */
/*        - file_ptr: pointeur sur le fichier. Celui-ci est positionne au   */
/*                    bon endroit.                                          */
/*      Retour: 0 si tout s'est bien passe.                                 */
/*==========================================================================*/

signed int	SPLWAV_save_sample_tail (FILE *file_ptr, const SPLS_PRIMARY_INFO *sample_info_ptr)
{
	int		block_align;
	SPLS_WAV_HEADER_SMPL	smpl_chunk;
	SPLS_WAV_HEADER_INST	inst_chunk;

	block_align = sample_info_ptr->tracks * (sample_info_ptr->nbits >> 3);
	int64_t        current_pos =
		(  sample_info_ptr->data_offset
	    + int64_t (sample_info_ptr->length) * block_align + 1) & -2;

	/* smpl: bouclage */
	if (sample_info_ptr->loop_type != WaveForm_LOOP_TYPE_NONE)
	{
		memcpy (smpl_chunk.chunk_id, "smpl", 4);
		smpl_chunk.chunk_size           = BASE_intel_lword (sizeof (smpl_chunk) - 8);
		smpl_chunk.manufacturer         = BASE_intel_lword (0);
		smpl_chunk.product              = BASE_intel_lword (0);
		smpl_chunk.sample_period        = BASE_intel_lword (1000000000L / sample_info_ptr->freq);
		smpl_chunk.midi_unity_note      = BASE_intel_lword (  sample_info_ptr->midi_note
		                                                    - (sample_info_ptr->finetune < 0));
		smpl_chunk.midi_pitch_fraction  = BASE_intel_lword ((LWORD)(sample_info_ptr->finetune & 0x7) << 29);
		smpl_chunk.smpte_format         = BASE_intel_lword (0);
		smpl_chunk.smpte_offset         = BASE_intel_lword (0);
		smpl_chunk.sample_loops         = BASE_intel_lword (1);
		smpl_chunk.sampler_data         = BASE_intel_lword (0);
		smpl_chunk.loops [0].identifier = BASE_intel_lword (0);
		smpl_chunk.loops [0].type       = BASE_intel_lword (sample_info_ptr->loop_type == WaveForm_LOOP_TYPE_PP);
		smpl_chunk.loops [0].start      = BASE_intel_lword (sample_info_ptr->repeat);
		smpl_chunk.loops [0].end        = BASE_intel_lword (sample_info_ptr->repeat + sample_info_ptr->replen - 1);
		smpl_chunk.loops [0].fraction   = BASE_intel_lword (0);
		smpl_chunk.loops [0].play_count = BASE_intel_lword (0);

		if (FILE_fseek (file_ptr, current_pos, SEEK_SET) != 0)
		{
			LOG_printf ("SPLAVR_save_sample_header: Error: couldn't position on smpl chunk.\n");
			return (-1);
		}
		size_t         real_size = (sizeof (smpl_chunk) + 1) & -2;
		if (fwrite (&smpl_chunk, sizeof (smpl_chunk), 1, file_ptr) != 1)
		{
			LOG_printf ("SPLWAV_save_sample_header: Error: couldn't save smpl chunk.\n");
			return (-1);
		}
		current_pos += real_size;
	}

	/* inst: instrument */
	memcpy (inst_chunk.chunk_id, "inst", 4);
	inst_chunk.chunk_size     = BASE_intel_lword (sizeof (inst_chunk) - 8);
	inst_chunk.unshifted_note = (UBYTE)sample_info_ptr->midi_note;
	inst_chunk.fine_tune      = (SBYTE)(floor ((double)(sample_info_ptr->finetune * 100) / 8.0 + 0.5));
	inst_chunk.gain           = (SBYTE)(floor (log ((double)sample_info_ptr->volume / 0x100) * 20.0 + 0.5));
	inst_chunk.low_note       = 0;
	inst_chunk.high_note      = 127;
	inst_chunk.low_velocity   = 1;
	inst_chunk.high_velocity  = 127;

	if (FILE_fseek (file_ptr, current_pos, SEEK_SET) != 0)
	{
		LOG_printf ("SPLAVR_save_sample_header: Error: couldn't position on inst chunk.\n");
		return (-1);
	}
	size_t         real_size = (sizeof (inst_chunk) + 1) & -2;
	if (fwrite (&inst_chunk, sizeof (inst_chunk), 1, file_ptr) != 1)
	{
		LOG_printf ("SPLWAV_save_sample_header: Error: couldn't save inst chunk.\n");
		return (-1);
	}
	current_pos += real_size;

	/* Finalisation */
	if (fflush (file_ptr))
	{
		LOG_printf ("SPLWAV_save_sample_tail: Error: couldn't flush file buffer.\n");
		return (-1);	/* Erreur d'ecriture */
	}

	return (0);
}



/*==========================================================================*/
/*      Nom: SPLWAV_get_primary_info                                        */
/*      Description: Recupere les informations de base sur un sample WAV.   */
/*                   Ne modifie que ce qui est donne par le header mais     */
/*                   peut utiliser le contenu entrant du bloc d'infos (ex:  */
/*                   RAW).                                                  */
/*      Parametres en entree:                                               */
/*        - file_ptr: pointeur de fichier du sample.                        */
/*      Parametres en entree/sortie:                                        */
/*        - sample_info_ptr: pointeur sur le bloc d'information a	remplir.  */
/*      Retour: 0 si tout s'est bien passe.                                 */
/*              1 si le format n'est pas supporte.                          */
/*              -1 si erreur.                                               */
/*==========================================================================*/

signed int	SPLWAV_get_primary_info (FILE *file_ptr, SPLS_PRIMARY_INFO *sample_info_ptr)
{
	signed int	return_value;
	int		fmt_count;
	int		data_count;
	int		smpl_count;
	int		bytes_per_sample;
	int		loop_section;
	SPLS_WAV_HEADER_FMT	*fmt_ptr;
	SPLS_WAV_HEADER_DATA	*data_ptr;
	SPLS_WAV_HEADER_SMPL	*smpl_ptr;

	return_value = 0;
	fmt_count = 0;
	data_count = 0;
	smpl_count = 0;

	sample_info_ptr->repeat = 0;
	sample_info_ptr->replen = 0;
	sample_info_ptr->loop_type = WaveForm_LOOP_TYPE_NONE;
	sample_info_ptr->special = 0;

	void *         header_ptr = MALLOC (4096);
	if (header_ptr == NULL)
	{
		LOG_printf ("SPLWAV_get_primary_info: Error: couldn't allocate a buffer for file header.\n");
		return (-1);
	}

	int64_t        file_length = FILE_get_file_length (file_ptr);
	int64_t        chunk_offset = sizeof (SPLS_WAV_HEADER_RIFF);	/* Offset du premier chunk contenu dans le master-chunk RIFF. */

	/* Recuperation des informations */
	do
	{
		/* Lecture du header du chunk */
		memset (header_ptr, 0, 4096);
		size_t         header_length = size_t (MIN (4096, file_length - chunk_offset));
		if (FILE_fseek (file_ptr, chunk_offset, SEEK_SET))
		{
			LOG_printf ("SPLWAV_get_primary_info: Error: couldn't position on chunk header.\n");
			FREE (header_ptr);
			return (-1);
		}
		if ((LWORD) fread (header_ptr, 1, header_length, file_ptr) != header_length)
		{
			LOG_printf ("SPLWAV_get_primary_info: Error: couldn't read chunk header.\n");
			FREE (header_ptr);
			return (-1);
		}

		/* Identification du chunk */

		/* Chunk fmt */
		if (BASE_compare_id4 (header_ptr, "fmt "))
		{
			fmt_ptr = (SPLS_WAV_HEADER_FMT *) header_ptr;
			fmt_count ++;

			if (BASE_intel_word (fmt_ptr->format_tag) != 1)
			{
				sample_info_ptr->pack_type = SPLS_PACK_TYPE_UNSUP;
				return_value = MAX (return_value, 1);	/* La compression n'est pas supportee */
			}
			else
			{
				sample_info_ptr->pack_type = SPLS_PACK_TYPE_PCM;
			}

			sample_info_ptr->tracks = BASE_intel_word (fmt_ptr->channels);
			sample_info_ptr->freq = BASE_intel_lword (fmt_ptr->samples_per_sec);
			sample_info_ptr->nbits = BASE_intel_word (fmt_ptr->bits_per_sample);
			sample_info_ptr->signed_flag = (sample_info_ptr->nbits != 8);
			bytes_per_sample = BASE_intel_word (fmt_ptr->block_align);
		}

		/* Chunk data */
		else if (BASE_compare_id4 (header_ptr, "data"))
		{
			data_ptr = (SPLS_WAV_HEADER_DATA *) header_ptr;
			data_count ++;

			/* Exprime en octet pour l'instant */
			sample_info_ptr->length = BASE_intel_lword (data_ptr->chunk_size);
			sample_info_ptr->byte_order = 1;
			sample_info_ptr->data_offset = chunk_offset + sizeof (SPLS_WAV_HEADER_DATA);
		}

		/* Chunk smpl */
		else if (BASE_compare_id4 (header_ptr, "smpl"))
		{
			smpl_ptr = (SPLS_WAV_HEADER_SMPL *) header_ptr;
			smpl_count ++;

			sample_info_ptr->midi_note = BASE_intel_lword (smpl_ptr->midi_unity_note);
			sample_info_ptr->finetune = (BASE_intel_lword (smpl_ptr->midi_pitch_fraction) >> 29) & 0x7;

			for (loop_section = 0; loop_section < BASE_intel_lword (smpl_ptr->sample_loops); loop_section ++)
			{
				if (BASE_intel_lword (smpl_ptr->loops [loop_section].play_count) == 1)
				{
					continue;
				}

				sample_info_ptr->repeat = BASE_intel_lword (smpl_ptr->loops [loop_section].start);
				sample_info_ptr->replen =   BASE_intel_lword (smpl_ptr->loops [loop_section].end)
												  - BASE_intel_lword (smpl_ptr->loops [loop_section].start) + 1;
				if (BASE_intel_lword (smpl_ptr->loops [loop_section].type) == 1)
				{
					sample_info_ptr->loop_type = WaveForm_LOOP_TYPE_PP;
					break;
				}
				else
				{
					sample_info_ptr->loop_type = WaveForm_LOOP_TYPE_FWD;
					break;
				}
			}
		}

		/* Chunk suivant */
		chunk_offset += ((BASE_intel_lword (fmt_ptr->chunk_size) + 8) + 1) & -2;
	}
	while (chunk_offset + 8 < file_length);

	/* Liberation de la memoire allouee pour le header */
	FREE (header_ptr);

	/* Derniers controles: les chunks indispensables
		sont-ils bien la une et une seule fois ? */
	if (   fmt_count != 1
		 || data_count != 1
		 || bytes_per_sample <= 0)
	{
		return_value = 2;
	}

	/* Convertit la longueur en samples. */
	sample_info_ptr->length /= bytes_per_sample;

	sample_info_ptr->position = 0;
	sample_info_ptr->block_len = sample_info_ptr->length;

	return (return_value);
}



/*==========================================================================*/
/*      Nom: SPLWAV_load_sample                                             */
/*      Description: Charge un sample en WAV.                               */
/*      Parametres en entree:                                               */
/*        - file_ptr: pointeur du fichier a charger.                        */
/*        - sample: numero du sample concerne.                              */
/*        - sample_info_ptr: pointeur sur les informations du sample.       */
/*      Retour: 0 si tout s'est bien passe.                                 */
/*==========================================================================*/

signed int	SPLWAV_load_sample (FILE *file_ptr, int sample, SPLS_PRIMARY_INFO	*sample_info_ptr)
{
	if (SPLRAW_load_sample (file_ptr, sample, sample_info_ptr))
	{
		LOG_printf ("SPLWAV_load_sample: Error: couldn't load data.\n");
		return (-1);
	}

	return (0);
}



/****************************************************************************/
/*                                                                          */
/*                                                                          */
/*                                                                          */
/****************************************************************************/



/*==========================================================================*/
/*      Nom:                                                                */
/*      Description:                                                        */
/*      Parametres en entree:                                               */
/*      Parametres en sortie:                                               */
/*      Parametres en entree/sortie:                                        */
/*      Retour:                                                             */
/*==========================================================================*/
