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

GRAOUMF TRACKER 2

Copyright (c) 1996 - 2002 Laurent de Soras

This program 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 2
of the License, or (at your option) any later version.

This program 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, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

Contact the author : laurent@ohmforce.com
More information about this license : http://www.gnu.org/licenses/gpl.html

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



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

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

#include "archi.h"
#include "base.h"
#include "base_ct.h"
#include "d2d.h"
#include "edstring.h"
#include	"file.h"
#include "gtracker.h"
#include	"intrface.h"
#include	"log.h"
#include "memory.h"
#include "mixer.h"
#include "os.h"
#include "player.h"
#include "rsc01.h"
#include "s2d.h"
#include "Sample.h"
#include "song.h"
#include "splhandl.h"
#include "splstruc.h"
#include	"Thread.h"



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



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



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

void S2D_record_thread (void);



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



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

int	S2D_byte_resol = 2;				/* Resolution du sample, en octets: 1 = 8 bits, 2 = 16 bits, 3 = 24 bits */
int	S2D_window_width = 64;			/* Largeur de la fenetre du filtre de reconstruction */
int	S2D_stereo = 2;					/* Nombre de voies: 1 = mono, 2 = stereo */
int	S2D_songpos [2] [2] =			/* Positions de debut et de fin */
{
	{ 0, 0 },	/* Utilisateur */
	{ 0, 0 }		/* Interne */
};
int	S2D_linepos [2] [2] =			/* Lignes de debut et de fin */
{
	{ 0, 0 },	/* Utilisateur */
	{ 0, 0 }		/* Interne */
};
LWORD	S2D_frequency = 44100;			/* Frequence d'echantillonnage */
double	S2D_max_duration = -3600.0;	/* Duree maximum, en secondes. Negatif: pas de limite */
int	S2D_quality = MIX_SOUND_QUALITY_NORMAL;	/* Qualite: MIX_SOUND_QUALITY_NORMAL, MIX_SOUND_QUALITY_BEST */

volatile bool	S2D_record_thread_quit_flag = false;	/* Indique au thread qu'il doit quitter */
volatile bool	S2D_record_thread_active_flag = false;	/* Indique que le thread est actif */
volatile bool	S2D_record_thread_error_flag = false;	/* Indique si une erreur s'est produite pendant le thread */
volatile bool	S2D_record_thread_mix_flag = false;		/* Indique si on doit ou pas enregistrer le resultat */
FILE	*volatile S2D_record_thread_file_ptr = NULL;	/* Pointeur du fichier a enregistrer */
volatile long	S2D_sample_len;	/* Taille du sample enregistre, en samples */
volatile long	S2D_record_thread_rel_pos = 0;	/* Position de replay par rapport au depart */
SPLS_PRIMARY_INFO	S2D_sample_info;



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



int	S2D_get_byte_resolution (void)
{
	return (S2D_byte_resol);
}



int	S2D_get_stereo (void)
{
	return (S2D_stereo);
}



long	S2D_get_frequency (void)
{
	return (S2D_frequency);
}



signed long	S2D_get_max_duration_sample (void)
{
	return ((signed long) (S2D_max_duration * S2D_frequency));
}



int	S2D_get_songpos (int pos_type)
{
	return (S2D_songpos [0] [pos_type]);
}



int	S2D_get_linepos (int pos_type)
{
	return (S2D_linepos [0] [pos_type]);
}



int	S2D_get_quality (void)
{
	return (S2D_quality);
}



int	S2D_get_window_width (void)
{
	return (S2D_window_width);
}



void	S2D_set_byte_resolution (int val)
{
	S2D_byte_resol = val;
}



void	S2D_set_stereo (int val)
{
	S2D_stereo = val;
}



void	S2D_set_quality (int quality)
{
	S2D_quality = quality;
}



void	S2D_set_window_width (int width)
{
	S2D_window_width = width;
}



/*==========================================================================*/
/*      Nom: S2D_set_songpos_intr                                           */
/*      Description: Change la position de debut/fin d'enregistrement, de   */
/*                   differentes manieres. L'interface est ici mise en jeu. */
/*      Parametres en entree:                                               */
/*        - type: INTR_CHGTYPE_ABS = Le parametre est une position absolue, */
/*                INTR_CHGTYPE_REL = Le parametre est une position relative,*/
/*                INTR_CHGTYPE_KBD = La position doit etre rentree au       */
/*                                   clavier.                               */
/*        - value: le parametre en question.                                */
/*        - pos_type: 0 = debut, 1 = fin.                                   */
/*==========================================================================*/

void	S2D_set_songpos_intr (int type, signed int value, int pos_type)
{
	int		new_value;
	int		linepos;
	int		height;
	char		nbr3_0 [3+1];
	int		object_list [2] =
	{
		RSC_OBJ_MP_SUBM_DISK_S2D_RANGE_START_POS_VAL,
		RSC_OBJ_MP_SUBM_DISK_S2D_RANGE_END_POS_VAL
	};

	new_value = S2D_songpos [0] [pos_type];

	switch (type)
	{
	/* Position relative */
	case	INTR_CHGTYPE_REL:
		new_value += value;
		break;

	/* Entree au clavier */
	case	INTR_CHGTYPE_KBD:
		sprintf (nbr3_0, INTR_base_song_3, new_value);
		EDIT_edit_string_base (nbr3_0, object_list [pos_type], 3, INTR_base_song);
		new_value = (int) strtol (nbr3_0, NULL, INTR_base_song);
		break;

	/* Position absolue  */
	case	INTR_CHGTYPE_ABS:
		new_value = value;
		break;
	}

	new_value = MIN (new_value, SONG_get_song_length () - 1);
	new_value = MAX (new_value, 0);
	height = PAT_get_pattern_height (SONG_get_pattern_number (new_value));
	linepos = S2D_linepos [0] [pos_type];
	linepos = MIN (linepos, height - 1);
	linepos = MAX (linepos, 0);
	S2D_songpos [0] [pos_type] = new_value;
	S2D_linepos [0] [pos_type] = linepos;

	INTR_pattern_editor_menu_ptr->refresh ();
}



/*==========================================================================*/
/*      Nom: S2D_set_linepos_intr                                           */
/*      Description: Change la ligne de debut/fin d'enregistrement, de      */
/*                   differentes manieres. L'interface est ici mise en jeu. */
/*      Parametres en entree:                                               */
/*        - type: INTR_CHGTYPE_ABS = Le parametre est une position absolue, */
/*                INTR_CHGTYPE_REL = Le parametre est une position relative,*/
/*                INTR_CHGTYPE_KBD = La position doit etre rentree au       */
/*                                   clavier.                               */
/*        - value: le parametre en question.                                */
/*        - pos_type: 0 = debut, 1 = fin.                                   */
/*==========================================================================*/

void	S2D_set_linepos_intr (int type, signed int value, int pos_type)
{
	int		new_value;
	int		height;
	char		nbr3_0 [3+1];
	int		object_list [2] =
	{
		RSC_OBJ_MP_SUBM_DISK_S2D_RANGE_START_LINE_VAL,
		RSC_OBJ_MP_SUBM_DISK_S2D_RANGE_END_LINE_VAL
	};

	new_value = S2D_linepos [0] [pos_type];

	switch (type)
	{
	/* Position relative */
	case	INTR_CHGTYPE_REL:
		new_value += value;
		break;

	/* Entree au clavier */
	case	INTR_CHGTYPE_KBD:
		sprintf (nbr3_0, INTR_base_lines_3, new_value);
		EDIT_edit_string_base (nbr3_0, object_list [pos_type], 3, INTR_base_lines);
		new_value = (int) strtol (nbr3_0, NULL, INTR_base_lines);
		break;

	/* Position absolue  */
	case	INTR_CHGTYPE_ABS:
		new_value = value;
		break;
	}

	height = PAT_get_pattern_height (SONG_get_pattern_number (S2D_songpos [0] [pos_type]));
	new_value = MIN (new_value, height - 1);
	new_value = MAX (new_value, 0);
	S2D_linepos [0] [pos_type] = new_value;

	INTR_pattern_editor_menu_ptr->refresh ();
}



/*==========================================================================*/
/*      Nom: S2D_set_frequency_intr                                         */
/*      Description: Change la frequence d'enregistrement, de               */
/*                   differentes manieres. L'interface est ici mise en jeu. */
/*      Parametres en entree:                                               */
/*        - type: INTR_CHGTYPE_ABS = Le parametre est une position absolue, */
/*                INTR_CHGTYPE_REL = Le parametre est une position relative,*/
/*                INTR_CHGTYPE_KBD = La position doit etre rentree au       */
/*                                   clavier.                               */
/*        - value: le parametre en question.                                */
/*==========================================================================*/

void	S2D_set_frequency_intr (int type, SLWORD value)
{
	LWORD		new_value;
	char		nbr5_0 [5+1];

	new_value = S2D_frequency;

	switch (type)
	{
	/* Position relative */
	case	INTR_CHGTYPE_REL:
		new_value += value;
		break;

	/* Entree au clavier */
	case	INTR_CHGTYPE_KBD:
		sprintf (nbr5_0, "%5d", new_value);
		EDIT_edit_string_base (nbr5_0, RSC_OBJ_MP_SUBM_DISK_S2D_TIME_FREQ_VAL, 5, 10);
		new_value = (int) strtol (nbr5_0, NULL, 10);
		break;

	/* Position absolue  */
	case	INTR_CHGTYPE_ABS:
		new_value = value;
		break;
	}

	new_value = MIN (new_value, Sample_MAX_FREQ);
	new_value = MAX (new_value, Sample_MIN_FREQ);
	S2D_frequency = new_value;

	INTR_pattern_editor_menu_ptr->refresh ();
}



/*==========================================================================*/
/*      Nom: S2D_set_max_duration_intr                                      */
/*      Description: Change la duree maximum d'enregistrement, de           */
/*                   differentes manieres. L'interface est ici mise en jeu. */
/*      Parametres en entree:                                               */
/*        - type: INTR_CHGTYPE_ABS = Le parametre est une position absolue, */
/*                INTR_CHGTYPE_REL = Le parametre est une position relative,*/
/*                INTR_CHGTYPE_KBD = La position doit etre rentree au       */
/*                                   clavier.                               */
/*        - value: le parametre en question.                                */
/*==========================================================================*/

void	S2D_set_max_duration_intr (int type, signed long value)
{
	unsigned long	spl_pos;
	double	new_value;

	new_value = S2D_max_duration;

	switch (type)
	{
	/* Position relative */
	case	INTR_CHGTYPE_REL:
		if (new_value < 0)
		{
			new_value = -new_value;
		}
		else
		{
			new_value += (double)value / 1000;
		}
		break;

	/* Entree au clavier */
	case	INTR_CHGTYPE_KBD:
		spl_pos = (ULWORD) (ABS (new_value) * S2D_frequency);
		if (EDIT_edit_splpos (RSC_OBJ_MP_SUBM_DISK_S2D_TIME_MDUR_VAL, &spl_pos,
									 S2D_frequency, S2D_stereo, S2D_byte_resol) != 0)
		{
			INTR_pattern_editor_menu_ptr->refresh ();
			return;
		}
		new_value = (double)spl_pos / S2D_frequency;
		break;

	/* Position absolue  */
	case	INTR_CHGTYPE_ABS:
		new_value = (double)value / S2D_frequency;
		break;
	}

	S2D_max_duration = new_value;

	INTR_pattern_editor_menu_ptr->refresh ();
}



/*==========================================================================*/
/*      Nom: S2D_set_window_width_intr                                      */
/*      Description: Change la largeur de la fenetre du filtre de           */
/*                   reconstruction, de differentes manieres. L'interface   */
/*                   est ici mise en jeu.                                   */
/*      Parametres en entree:                                               */
/*        - type: INTR_CHGTYPE_ABS = Le parametre est une position absolue, */
/*                INTR_CHGTYPE_REL = Le parametre est une position relative,*/
/*                INTR_CHGTYPE_KBD = La position doit etre rentree au       */
/*                                   clavier.                               */
/*        - value: le parametre en question.                                */
/*==========================================================================*/

void	S2D_set_window_width_intr (int type, signed int value)
{
	int		new_value;
	char		nbr3_0 [3+1];

	new_value = S2D_window_width;

	switch (type)
	{
	/* Position relative */
	case	INTR_CHGTYPE_REL:
		new_value += value * 2;
		break;

	/* Entree au clavier */
	case	INTR_CHGTYPE_KBD:
		sprintf (nbr3_0, "%3d", new_value);
		EDIT_edit_string_base (nbr3_0, RSC_OBJ_MP_SUBM_DISK_S2D_QUAL_FACT_POS_VAL, 3, 10);
		new_value = (int) strtol (nbr3_0, NULL, 10);
		break;

	/* Position absolue  */
	case	INTR_CHGTYPE_ABS:
		new_value = value;
		break;
	}

	new_value = (new_value + 1) & -2;
	new_value = MIN (new_value, 512);
	new_value = MAX (new_value, 8);
	S2D_window_width = new_value;

	INTR_pattern_editor_menu_ptr->refresh ();
}



void	S2D_set_sample_group_intr (int type, signed int value)
{
	char			string_0 [127+1];
	int			new_value = MIX_get_sample_group ();

	switch (type)
	{
	/* Position relative */
	case	INTR_CHGTYPE_REL:
		new_value += value;
		break;

	/* Entree au clavier */
	case	INTR_CHGTYPE_KBD:
		sprintf (string_0, INTR_base_song_3, new_value);
		EDIT_edit_string_base (
			string_0,
			RSC_OBJ_MP_SUBM_DISK_S2D_GRP_VAL,
			3,
			INTR_base_song
		);
		new_value = int (strtol (string_0, NULL, INTR_base_song));
		break;

	/* Position absolue  */
	case	INTR_CHGTYPE_ABS:
		new_value = value;
		break;
	}

	new_value = MAX (MIN (new_value, SAMP_NBRGROUPS_MAXI), 0);
	MIX_set_sample_group (new_value);
	INTR_pattern_editor_menu_ptr->refresh ();
}



/*==========================================================================*/
/*      Nom: S2D_record_module                                              */
/*      Description: Enregistre la portion du module dans un fichier, apres */
/*                   avoir fait choisir un fichier a l'utilisateur.         */
/*      Parametres en entree:                                               */
/*        - all_flag: Si true, on enregistre tout le module.                */
/*      Retour: 0 si tout s'est bien passe.                                 */
/*==========================================================================*/

signed int	S2D_record_module (bool all_flag)
{
	int		button;
	signed int	ret;
	String	total_name;
	String	message;

	/* Verifie que les positions de debut et de fin sont valides */
   if (! all_flag)
	{
		if (   S2D_songpos [0] [1] < S2D_songpos [0] [0]
		    || (   S2D_songpos [0] [1] == S2D_songpos [0] [0]
		        && S2D_linepos [0] [1] < S2D_linepos [0] [0]))
		{
			INTR_dialog_box ("SONG TO DISK",
			                 "End position must be after start position.",
			                 "Cancel", 0, 0);
			return (0);
		}
	}

	/* Selection du fichier de sauvegarde */
	if (SPLS_select_sample_filename (total_name,
	                                 all_flag ? "Save module in sample file" :
	                                            "Save part of module in sample file",
	                                 true))
	{
		LOG_printf ("S2D_record_module: Error: filename selection failed.\n");
		return (-1);
	}
	if (total_name == String (""))
	{
		return (0);
	}

	/* Test du format de sauvegarde */
	if (   SPLS_export_format != SPLS_FORMAT_RAW
	    && SPLS_export_format != SPLS_FORMAT_WAV
	    && SPLS_export_format != SPLS_FORMAT_AVR)
	{
		INTR_dialog_box ("SONG TO DISK",
		                 "Can't save in this sample format.\n"
		                 "Please select another format in\n"
		                 "the Sample menu.\n"
		                 "\n"
		                 "Supported formats:\n"
		                 "  RAW, WAV, AVR.",
		                 "Cancel", 0, 0);
		return (0);
	}

	/* Le fichier existe deja ? */
	if (FILE_file_exist (total_name.c_str ()))
	{
		message =   String (total_name)
		          + "\nalready exists.\nShould I overwrite existing file \?";
		button = INTR_dialog_box ("SAVE", message.c_str (), "OK\nCancel", 0, 1);
		if (button != 0)
		{
			return (0);
		}
	}

	/* Sauvegarde  */
	LOG_printf ("S2D_record_module: Mixdown started.\n");
	ret = S2D_record_data (total_name.c_str (), all_flag);

	if (ret)
	{
		INTR_dialog_box ("SAVE",
		                 "Error: couldn't save sample.",
		                 "Cancel", 0, 0);
	}
	LOG_printf ("S2D_record_module: Mixdown ended.\n");

	return (0);
}



/*==========================================================================*/
/*      Nom: S2D_record_data                                                */
/*      Description: Enregistre la portion du module dans un fichier.       */
/*      Parametres en entree/sortie:                                        */
/*        - file_ptr: pointeur sur le fichier des donnees.                  */
/*        - all_flag: Si true, on enregistre tout le module.                */
/*      Retour: 0 si tout s'est bien passe.                                 */
/*==========================================================================*/

signed int	S2D_record_data (const char *filename_0, bool all_flag)
{
	FILE		*file_ptr;
	long		sample_len;
	int		old_songpos;
	int		old_linepos;
	long		total_pos;
	long		old_replay_freq;
	int		old_sound_quality;
	signed int	ret_val;
	signed int	func_ret_val;

	class S2D_ThreadStub
	:	public ThreadCbInterface
	{
	public:
		// ThreadCbInterface
		virtual void	do_run_thread ()
		{
			S2D_record_thread ();
		}
	};

	S2D_ThreadStub	S2D_cache_mgr_thread_stub;

	Thread	record_thread (S2D_cache_mgr_thread_stub);

	Player &			player = Player::use_instance ();

	func_ret_val = 0;

	/* Calcule les positions reelles de debut et de fin */
	if (all_flag)
	{
		S2D_songpos [1] [0] = 0;
		S2D_songpos [1] [1] = SONG_get_song_length () - 1;
		S2D_linepos [1] [0] = 0;
		S2D_linepos [1] [1] = PAT_get_pattern_height (SONG_get_pattern_number (S2D_songpos [1] [1])) - 1;
	}

	else
	{
		S2D_songpos [1] [0] = S2D_songpos [0] [0];
		S2D_songpos [1] [1] = S2D_songpos [0] [1];
		S2D_linepos [1] [0] = S2D_linepos [0] [0];
		S2D_linepos [1] [1] = S2D_linepos [0] [1];
	}

	/* Arrete toute activite temps reel */
	INTR_stop_song ();
	old_songpos = player.get_song_position ();
	old_linepos = player.get_line_position ();
	player.stop_player ();

	/* Sauvegarde et change la frequence de replay */
	old_replay_freq = MIX_replay_freq;
	player.set_replay_freq (S2D_frequency);

	/* Sauve et change la qualite */
	MIX_recons_filter_width = S2D_window_width;
	old_sound_quality = MIX_get_sound_quality ();
	if (S2D_quality == MIX_SOUND_QUALITY_BEST)
	{
		MIX_set_sound_quality (MIX_SOUND_QUALITY_BEST);
	}
	else
	{
		MIX_set_sound_quality (MIX_SOUND_QUALITY_NORMAL);
	}

/*______________________________________________
 *
 * Determine la taille du sample
 *______________________________________________
 */

	LOG_printf ("S2D_record_data: Compute final sample length.\n");

	S2D_record_thread_quit_flag = false;
	S2D_record_thread_active_flag = true;
	S2D_record_thread_error_flag = false;
	S2D_record_thread_mix_flag = false;
	S2D_record_thread_file_ptr = NULL;
	if (record_thread.spawn ())
	{
		INTR_dialog_box ("SONG TO DISK",
							  "Error: couldn't spawn\n"
							  "a thread for SongToDisk\n"
							  "file length calculating.",
							  "Cancel", 0, 0);
		LOG_printf ("S2D_record_data: Error: couldn't spawn a thread for SongToDisk file length calculating.\n");
		func_ret_val = -1;
		goto proc_end;
	}

	total_pos = S2D_songpos [1] [1] - S2D_songpos [1] [0] + 1;

	ret_val = INTR_progress_box ("Calculating file length...",
	                             &total_pos,
	                             &S2D_record_thread_rel_pos,
	                             &S2D_record_thread_active_flag,
	                             &S2D_record_thread_quit_flag);

	/* Annulation */
	if (ret_val == 1)
	{
		goto proc_end;
	}

	/* Probleme */
	else if (ret_val < 0 || S2D_record_thread_error_flag)
	{
		goto thread_error;
	}

	sample_len = S2D_sample_len;

/*______________________________________________
 *
 * Enregistre le module dans le fichier
 *______________________________________________
 */

	/* Ouverture du fichier */
	LOG_printf ("S2D_record_data: Open file %s.\n", filename_0);
	file_ptr = fopen (filename_0, "wb");
	if (file_ptr == NULL)
	{
		INTR_dialog_box ("SONG TO DISK", "Error: couldn't create file.", "Cancel", 0, 0);
		LOG_printf ("S2D_record_data: Error: couldn't create file for SongToDisk.\n");
		func_ret_val = -1;
		goto proc_end;
	}

	/* Ecriture du header */
	LOG_printf ("S2D_record_data: Write header.\n");
	SPLS_init_primary_info (&S2D_sample_info, 0);
	S2D_sample_info.freq        = S2D_frequency;
	S2D_sample_info.length      = sample_len;
	S2D_sample_info.nbits       = S2D_byte_resol << 3;
	S2D_sample_info.tracks      = S2D_stereo;
	S2D_sample_info.data_offset = 0;
	if (SPLS_save_sample_header (file_ptr, &S2D_sample_info))
	{
		fclose (file_ptr);
		INTR_dialog_box ("SONG TO DISK", "Error: couldn't write file header.", "Cancel", 0, 0);
		LOG_printf ("S2D_record_data: Error: couldn't write file header for SongToDisk.\n");
		func_ret_val = -1;
		goto proc_end;
	}

	/* Lancement du thread */
	LOG_printf ("S2D_record_data: Spawn new thread.\n");
	S2D_record_thread_quit_flag = false;
	S2D_record_thread_active_flag = true;
	S2D_record_thread_error_flag = false;
	S2D_record_thread_mix_flag = true;
	S2D_record_thread_file_ptr = file_ptr;
	S2D_sample_len = 0;
	if (record_thread.spawn ())
	{
		fclose (file_ptr);
		INTR_dialog_box ("SONG TO DISK",
							  "Error: couldn't spawn\n"
							  "a thread for SongToDisk\n"
							  "recording.",
							  "Cancel", 0, 0);
		LOG_printf ("S2D_record_data: Error: couldn't spawn a thread for SongToDisk recording.\n");
		func_ret_val = -1;
		goto proc_end;
	}

	ret_val = INTR_progress_box ("Writing to file...",
	                             &sample_len,
	                             &S2D_sample_len,
	                             &S2D_record_thread_active_flag,
	                             &S2D_record_thread_quit_flag);

	/* Ecriture du tail */
	LOG_printf ("S2D_record_data: Writing tail information.\n");
	if (SPLS_save_sample_header (file_ptr, &S2D_sample_info))
	{
		fclose (file_ptr);
		INTR_dialog_box ("SONG TO DISK", "Error: couldn't write file tail.", "Cancel", 0, 0);
		LOG_printf ("S2D_record_data: Error: couldn't write file tail for SongToDisk.\n");
		func_ret_val = -1;
		goto proc_end;
	}

	/* Fermeture du fichier */
	LOG_printf ("S2D_record_data: Closing file.\n");
	fclose (file_ptr);

	/* Annulation */
	if (ret_val == 1)
	{
		/* Destruction du fichier */
		remove (filename_0);
	}

/*______________________________________________
 *
 * Gestion des erreurs et retour a la normale
 *______________________________________________
 */

thread_error:
	if (S2D_record_thread_error_flag)
	{
		INTR_dialog_box ("SONG TO DISK", "Error reported.", "Cancel", 0, 0);
		func_ret_val = -1;
	}

proc_end:
	/* Restore la freqence de replay et la qualite de rendu. */
	MIX_set_sound_quality (old_sound_quality);
	player.set_replay_freq (old_replay_freq);

	/* Redemarre le player */
	if (player.start_player ())
	{
		LOG_printf ("S2D_record_data: Error: couldn't restart player.\n");
		return (-1);	/* Impossible de mettre le player en marche */
	}

	player.set_song_position (old_songpos);
	player.set_line_position (old_linepos);

	return (func_ret_val);
}



void S2D_record_thread (void)
{
	double	start_tempo;
	int		start_speed;
	int		try_nbr;
	long		work_length;
	long		max_sample_len;
	bool		last_pos_flag;
	const float *	s_buffer_1_ptr;
	const float *	s_buffer_2_ptr;
	void		*d_buffer_ptr;

	LOG_printf ("S2D_record_thread: Preparing mixdown.\n");

	/* Reserve un buffer pour le sample destination */
	if (S2D_record_thread_mix_flag)
	{
		/* On reserve de la stereo car le buffer est d'abord ramene en 8/16
		   bits stereo, puis converti sur place en mono si necessaire. */
		d_buffer_ptr = MALLOC (MIX_MAX_FRAME_LENGTH * 2 * S2D_byte_resol);
		if (d_buffer_ptr == NULL)
		{
			LOG_printf ("S2D_record_thread: Error: couldn't allocate memory for Song To Disk buffer.\n");
			S2D_record_thread_error_flag = true;
			S2D_record_thread_active_flag = false;
			return;	/* Erreur d'allocation memoire */
		}
	}

	if (S2D_max_duration >= 0)
	{
		max_sample_len = (long) (S2D_max_duration * S2D_frequency);
	}
	else
	{
		max_sample_len = LONG_MAX;	/* 2 Gs, Ca devrait etre suffisant... */
	}

	last_pos_flag = false;
	S2D_sample_len = 0;
	
	Player &			player = Player::use_instance ();

	player.set_play_mode (Player::MODE_STOP_ALL, -1, -1, false);
	player.main_interrupt ();
	MIX_do_mix ();

	player.set_song_position (S2D_songpos [1] [0]);
	while (player.get_song_position () != S2D_songpos [1] [0])
	{
		player.main_interrupt ();
	}
	player.set_line_position (S2D_linepos [1] [0]);
	while (player.get_line_position () != S2D_linepos [1] [0])
	{
		player.main_interrupt ();
	}
	player.set_play_mode (Player::MODE_SONG, -1, -1, false);

	/* Sauve le tempo et la vitesse de debut */
	start_tempo = player.get_tempo ();
	start_speed = player.get_speed ();

	if (S2D_record_thread_mix_flag)
	{
		LOG_printf ("S2D_record_thread: generating and recording audio data.\n");
	}
	else
	{
		LOG_printf ("S2D_record_thread: scanning score.\n");
	}

	/* Boucle d'enregistrement, frame par frame */
	while (   S2D_sample_len < max_sample_len
	       && ! S2D_record_thread_quit_flag)
	{
		S2D_record_thread_rel_pos = player.get_song_position () - S2D_songpos [1] [0];

		/* Gere la partition et fixe une nouvelle longueur de frame */
		player.main_interrupt ();

		/* Si on est passe apres la derniere position, on peut s'arreter. */
		if (   (   player.get_song_position () != S2D_songpos [1] [1]
		        || player.get_line_position () != S2D_linepos [1] [1])
		    && last_pos_flag)
		{
			break;
		}
		else if (   SONG_get_song_length () <= 1
		         && PAT_get_pattern_height (SONG_get_pattern_number (0)) <= 1
		         && last_pos_flag
		         && player.get_current_tick () <= 0)
		{
			break;
		}

		work_length = MIN (MIX_frame_length, max_sample_len - S2D_sample_len);

		if (! S2D_record_thread_mix_flag)
		{
			MIX_do_not_mix ();
		}

		else
		{
			/* Charge les buffers de D2D si necessaire */
			for (try_nbr = 0; try_nbr < 10; try_nbr ++)
			{
				if (D2D_cache_manager_routine () == 0)
				{
					break;
				}
			}
			if (try_nbr >= 10)
			{
				LOG_printf ("Error: couldn't load Direct-2-disk buffers for making Song-2-Disk file.\n");
				S2D_record_thread_error_flag = true;	/* 10 tentatives max, apres c'est l'erreur. */
				break;
			}

			/* Fait le mixage */
			MIX_do_mix ();

			s_buffer_1_ptr = MIX_driver_out_buffer_ptr [0];
			const Player::TrackInfo &	track_info =
				player._track_info [player._track_info_list [GTK_TRACK_TYPE_AOU] [0]];

			/* Piste stereo */
			if (track_info.stereo == 2)
			{
				if (S2D_byte_resol == 3)
				{
					SPLH_copy_mem_flt_stereo_2_24c_stereo (
						(SWord24 *) d_buffer_ptr,
					   s_buffer_1_ptr,
					   work_length
					);
				}
				else if (S2D_byte_resol == 2)
				{
					SPLH_copy_mem_flt_stereo_2_16_stereo (
						(SWORD *) d_buffer_ptr,
					   s_buffer_1_ptr,
					   work_length
					);
				}
				else
				{
					SPLH_copy_mem_flt_stereo_2_8_stereo (
						(SBYTE *) d_buffer_ptr,
						s_buffer_1_ptr,
						work_length
					);
				}
			}

			/* Pistes mono */
			else
			{
				s_buffer_2_ptr = MIX_driver_out_buffer_ptr [0];
				if (S2D_byte_resol == 3)
				{
					SPLH_copy_mem_flt_bimono_2_24c_stereo (
						(SWord24 *) d_buffer_ptr,
						s_buffer_1_ptr,
						s_buffer_2_ptr,
						work_length
					);
				}
				else if (S2D_byte_resol == 2)
				{
					SPLH_copy_mem_flt_bimono_2_16_stereo (
						(SWORD *) d_buffer_ptr,
						s_buffer_1_ptr,
						s_buffer_2_ptr,
						work_length);
				}
				else
				{
					SPLH_copy_mem_flt_bimono_2_8_stereo (
						(SBYTE *) d_buffer_ptr,
						s_buffer_1_ptr,
						s_buffer_2_ptr,
						work_length
					);
				}
			}

			/* Destination mono */
			if (S2D_stereo == 1)
			{
				SPLH_convert_mem_stereo_2_mono (d_buffer_ptr, work_length, S2D_byte_resol);
			}

			/* Sauvegarde dans le fichier */
			S2D_sample_info.block_len = work_length;
			S2D_sample_info.position = S2D_sample_len;
			if (SPLS_save_sample_data (S2D_record_thread_file_ptr,
			                           &S2D_sample_info,
			                           d_buffer_ptr, NULL))
			{
				LOG_printf ("Error: couldn't write to SongToDisk file (pos = %ld).\n",
				            S2D_sample_len);
				S2D_record_thread_error_flag = true;
				break;
			}
		}

		S2D_sample_len += work_length;

		/* On a atteint la derniere ligne ? */
		if (   player.get_song_position () == S2D_songpos [1] [1]
		    && player.get_line_position () == S2D_linepos [1] [1])
		{
			last_pos_flag = true;
		}
	}

	LOG_printf ("S2D_record_thread: restoring context.\n");

	/* Restaure le tempo et la vitesse de debut */
	player.set_tempo (start_tempo);
	player.set_speed (start_speed);

	player.set_play_mode (Player::MODE_STOP_ALL, -1, -1, false);
	player.main_interrupt ();
	MIX_do_mix ();

	if (S2D_record_thread_mix_flag)
	{
		FREE (d_buffer_ptr);
	}

	S2D_record_thread_active_flag = false;
}



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



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