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

        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	"archi.h"
#include	"base.h"
#include	"base_ct.h"
#include	"EditString.h"
#include	"edstring.h"
#include	"gtracker.h"
#include	"intrface.h"
#include	"log.h"
#include	"mixp.h"
#include	"MixPreset.h"
#include	"PatEdMenuMix.h"
#include	"player.h"
#include	"Popup.h"
#include	"resource.h"
#include	"rsc01.h"
#include	"tracks.h"

#include <algorithm>

#include <cassert>
#include <cmath>



/*\\\ CONSTANTES PUBLIQUES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



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

/* Nom des types de pistes, en fonction egalement du dry/wet */
const char	*const PatEdMenuMix::track_type_name_0_ptr [5] [2] =
{
	{ "Samples", "Samples" },
	{ "Audio In", "Audio In" },
	{ "FX wet", "FX Dry" },
	{ "MIDI", "MIDI" },
	{ "AudioOut", "AudioOut" }
};



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

typedef struct
{
	BYTE		track_type;		/* Numero du type de piste source */
	int		dry_state;		/* Pour les pistes d'effet, 0 = wet, 1 = dry */
	WORD		track;			/* Voie d'entree */
	UWORD		volume;			/* 0..1000..FFFF */
	WORD		balance;			/* 0..800..FFF */
	int		stereo;
	int		active_state;
} PatEdMenuMix_SOURCE_CONF;



/*\\\ DEFINITION DES VARIABLES DE CLASSE PUBLIQUES \\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ DEFINITION DES VARIABLES DE CLASSE PRIVEES \\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ METHODES PUBLIQUES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*==========================================================================*/
/*      Nom: (constructeur)                                                 */
/*      Description: Initialise                                             */
/*      Parametres en entree:                                               */
/*      Parametres en sortie:                                               */
/*      Parametres en entree/sortie:                                        */
/*==========================================================================*/

PatEdMenuMix::PatEdMenuMix (void)
{
	const SLID_INFO	source_slider =
	{
		RSC_OBJ_MP_SUBM_MIX_IN_LIFT_SBAR,
		RSC_OBJ_MP_SUBM_MIX_IN_LIFT_SBAR_SLIDER,
		0, 0, 0, 0, 0, -1,
		SLID_DIRECTION_VERTICAL,
		false, false, 0, 0
	};

	_rsc_object_ptr = NULL;

	_source_slider = source_slider;
	_dest_track_type = GTK_TRACK_TYPE_AOU;
	_dest_track_nbr = 0;
	_dest_dry_flag = false;
	_disp_pos = 0;
	_nbr_disp_lines = 6;

	_rsc_object_ptr = new PatEdMenuMix_OBJECT_LINE [1];
	_rsc_object_ptr [0].vol = RSC_OBJ_MP_SUBM_MIX_IN_VOL_SL;
	_rsc_object_ptr [0].pan = RSC_OBJ_MP_SUBM_MIX_IN_PAN_SL;
	_nbr_rsc_lines = 1;
}



/*==========================================================================*/
/*      Nom: (destructeur)                                                  */
/*      Description: Detruit                                                */
/*==========================================================================*/

PatEdMenuMix::~PatEdMenuMix (void)
{
	if (_rsc_object_ptr != NULL)
	{
		delete [] _rsc_object_ptr;
		_rsc_object_ptr = NULL;
	}
}



/*==========================================================================*/
/*      Nom: check_ok                                                       */
/*      Description: Verifie l'intergrite de l'objet.                       */
/*      Retour: 0 si l'objet parait correct.                                */
/*==========================================================================*/

signed int	PatEdMenuMix::check_ok (void) const
{
	if (this == NULL)
	{
		LOG_printf ("PatEdMenuMix::check_ok: Error: \"this\" pointer is NULL.\n");
		return (-1);
	}

	return (0);
}



/*==========================================================================*/
/*      Nom: self_display                                                   */
/*      Description: Affiche "textuellement" le contenu courant de l'objet. */
/*==========================================================================*/

void	PatEdMenuMix::self_display (void) const
{

	/*** A faire ***/

}



/*==========================================================================*/
/*      Nom: redraw                                                         */
/*      Description: Affiche la composante d'interface.                     */
/*==========================================================================*/

void	PatEdMenuMix::redraw (void)
{
	int		line;
	int		object;
	PatEdMenuMix_OBJECT_LINE	*temp_ptr;

	/* Hauteur de la fenetre des entrees */
	_nbr_disp_lines =   RSC_get_height (RSC_OBJ_MP_SUBM_MIX_IN_TYPE)
	                  / RSC_CHAR_H;

	/* Fabrique les objets de chaque ligne */
	if (_nbr_rsc_lines < _nbr_disp_lines)
	{
		temp_ptr = new PatEdMenuMix_OBJECT_LINE [_nbr_disp_lines];
		for (int cnt = 0; cnt < _nbr_rsc_lines; ++ cnt)
		{
			temp_ptr [cnt] = _rsc_object_ptr [cnt];
		}
		delete [] _rsc_object_ptr;
		_rsc_object_ptr = temp_ptr;

		for (line = _nbr_rsc_lines; line < _nbr_disp_lines; line ++)
		{
			object = RSC_duplicate_object (_rsc_object_ptr [0].vol);
			_rsc_object_ptr [line].vol = object;
			RSC_set_relative_object_position (object, 0, line * RSC_CHAR_H);
			RSC_object_ptr [_rsc_object_ptr [line - 1].vol]->brother.number = object;

			object = RSC_duplicate_object (_rsc_object_ptr [0].pan);
			_rsc_object_ptr [line].pan = object;
			RSC_set_relative_object_position (object, 0, line * RSC_CHAR_H);
			RSC_object_ptr [_rsc_object_ptr [line - 1].pan]->brother.number = object;
		}

		_nbr_rsc_lines = _nbr_disp_lines;
	}

	clear_lines ();
	RSC_display_object (RSC_OBJ_MP_SUBM);

	refresh ();
}



/*==========================================================================*/
/*      Nom: refresh                                                        */
/*      Description: Rafraichit l'affichage des donnees.                    */
/*==========================================================================*/

void	PatEdMenuMix::refresh (void)
{
	char		nbr3_0 [3+1];
	char		preset_name_0 [MixPreset_NAME_LEN+1];

	/* Verifie que la piste destination existe bien */
	if (GTK_nbr_tracks [_dest_track_type] <= 0)
	{
		_dest_track_type = GTK_TRACK_TYPE_AOU;
		_dest_track_nbr = 0;
	}
	else
	{
		_dest_track_nbr = MIN (_dest_track_nbr, GTK_nbr_tracks [_dest_track_type]);
		_dest_track_nbr = MAX (_dest_track_nbr, 0);
	}

	/* Numero du preset */
	sprintf (nbr3_0, INTR_base_song_3, GTK_mix_nbr);
	RSC_set_string (RSC_OBJ_MP_SUBM_MIX_COM_PRESET_VAL, nbr3_0);
	RSC_display_object (RSC_OBJ_MP_SUBM_MIX_COM_PRESET_VAL);

	/* Nom du preset */
	MIXP_get_preset_name (GTK_mix_nbr, preset_name_0);
	RSC_set_string (RSC_OBJ_MP_SUBM_MIX_COM_PRESET_NAME, preset_name_0);
	RSC_display_object (RSC_OBJ_MP_SUBM_MIX_COM_PRESET_NAME);

	refresh_dynamic (true);
}



/*==========================================================================*/
/*      Nom: refresh_dynamic                                                */
/*      Description: Rafraichit l'affichage des donnees dynamiques.         */
/*        - force_flag: true indique qu'on doit forcer le rafraichissement. */
/*==========================================================================*/

void	PatEdMenuMix::refresh_dynamic (bool force_flag)
{
	/* Piste destination */
	update_dest_track (force_flag);

	/* Pistes sources */
	update_source_tracks (force_flag);
}



/*==========================================================================*/
/*      Nom: manage                                                         */
/*      Description: Gere la composante d'interface                         */
/*      Parametres en entree:                                               */
/*        - sel_object: objet detecte par le gestionnaire d'interface       */
/*        - sel_elder: aine de sel_object                                   */
/*==========================================================================*/

void	PatEdMenuMix::manage (int sel_object, int sel_elder)
{
	int		bar_width;
	int		bar_hot_point;
	int		line;
	int		pos_x;
	int		track;
	int		track_type;
	int		nbr_tracks;
	int		volume;
	int		balance;
	int		index;
	int		real_col;
	int		bidon;
	int		stereo;
	int		full_bar_width;
	signed int	ret_val;
	SLWORD	code;
	bool		dry_flag;
	bool		active_flag;
	Player::TrackInfoSrc::Source	*track_conf_ptr;
	PLAY_SOURCE_TRACK_CONF_BLOCK	track_conf;
	char		preset_name_0 [MixPreset_NAME_LEN+1];
	char		track_name_0 [TRK_NAME_LEN+1];
	Popup		popup_menu;

	Player &			player = Player::use_instance ();

	player.get_track_nbr_s_tracks (
		_dest_track_type, _dest_track_nbr, &nbr_tracks, &track_conf_ptr
	);

	switch (sel_object)
	{
	/* Insert */
	case	RSC_OBJ_MP_SUBM_MIX_COM_INS:
		real_col = TRK_cursor_offset + TRK_cursor_col;
		track_conf.track_type = (BYTE) TRK_preset_data [TRK_preset_nbr].track_type;
		track_conf.track_nbr = TRK_preset_data [TRK_preset_nbr].track_nbr [real_col];
		track_conf.wet_flag = true;
		track_conf.inpvol = 0x1000;
		track_conf.inppan = 0x8000U;
		{
			std::lock_guard <std::mutex>	lock (GTK_mutex);
			if (track_conf.track_type == Pattern_TYPE_FX)
			{
				if (player.check_track_is_in_sources (
					_dest_track_type,
				   _dest_track_nbr,
				   &bidon,
					&track_conf
				) == 1)
				{
					track_conf.wet_flag = false;
				}
			}
			ret_val = player.add_source_track (
				_dest_track_type,
				_dest_track_nbr,
				&track_conf,
				Player::MIX_PRESET_REPL
			);
		}
		if (ret_val == 1)
		{
			INTR_dialog_box ("ADD SOURCE TRACK",
			                 "This track is already\n"
			                 "in the source tracks.",
			                 "Cancel", 0, 0);
		}
		else if (ret_val == 2)
		{
			INTR_dialog_box ("ADD SOURCE TRACK",
			                 "Adding this track may cause an\n"
			                 "infinite loop in the effect chain.",
			                 "Cancel", 0, 0);
		}
		else if (ret_val < 0)
		{
			INTR_dialog_box ("ADD SOURCE TRACK",
			                 "Error reported.",
			                 "Cancel", 0, 0);
		}
		else
		{
			GTK_modified_flag = true;
		}
		refresh ();
		break;

	/* Load */
	case	RSC_OBJ_MP_SUBM_MIX_COM_LOAD:
		{
			std::lock_guard <std::mutex>	lock (GTK_mutex);
			if (player.load_mix_preset (
				_dest_track_type,
				_dest_track_nbr,
				GTK_mix_nbr,
				Player::MIX_PRESET_REPL
			))
			{
				INTR_dialog_box ("LOAD MIX PRESET", "Error reported.", "Cancel", 0, 0);
			}
			else
			{
				GTK_modified_flag = true;
			}
		}
		refresh ();
		break;

	/* Save */
	case	RSC_OBJ_MP_SUBM_MIX_COM_SAVE:
		{
			std::lock_guard <std::mutex>	lock (GTK_mutex);
			if (player.save_mix_preset (
				_dest_track_type,
				_dest_track_nbr,
				GTK_mix_nbr
			))
			{
				INTR_dialog_box ("SAVE MIX PRESET", "Error reported.", "Cancel", 0, 0);
			}
		}
		refresh ();
		break;

	/* Preset (numero) */
	case	RSC_OBJ_MP_SUBM_MIX_COM_PRESET_VAL:
		if (RSC_mouse_key & 2)
		{
			set_mix_preset_number (INTR_CHGTYPE_POP, 0);
		}
		else
		{
			set_mix_preset_number (INTR_CHGTYPE_KBD, 0);
		}
		break;

	/* Preset (nom) */
	case	RSC_OBJ_MP_SUBM_MIX_COM_PRESET_NAME:
		MIXP_get_preset_name (GTK_mix_nbr, preset_name_0);
		if (EDIT_edit_string (preset_name_0, RSC_OBJ_MP_SUBM_MIX_COM_PRESET_NAME,
									 MixPreset_NAME_LEN, EditString_TYPE_ALPHA) == 0)
		{
			MIXP_set_preset_name (GTK_mix_nbr, preset_name_0);
			GTK_modified_flag = true;
		}
		refresh ();
		break;

	/* Preset (up) */
	case	RSC_OBJ_MP_SUBM_MIX_COM_PRESET_UP:
		set_mix_preset_number (INTR_CHGTYPE_REL, INTR_inc_speed [0] [RSC_mouse_key]);
		break;

	/* Preset (down) */
	case	RSC_OBJ_MP_SUBM_MIX_COM_PRESET_DOWN:
		set_mix_preset_number (INTR_CHGTYPE_REL, -INTR_inc_speed [0] [RSC_mouse_key]);
		break;

	/* Piste destination: type */
	case	RSC_OBJ_MP_SUBM_MIX_OUT_TYPE:
		popup_menu.add_line ("Audio Out", GTK_TRACK_TYPE_AOU);
		popup_menu.add_line ("Effects, wet part", Pattern_TYPE_FX);
		popup_menu.add_line ("Effects, dry part", Pattern_TYPE_FX + 0x100);
		line = popup_menu.select_radio_by_code (  _dest_track_type
		                                        + (_dest_dry_flag ? 0x100 : 0x000));
		code = popup_menu.manage (line);
		if (code >= 0)
		{
			track_type = code & 0xFF;
			dry_flag = ((code & 0xFF00) != 0);
			if (GTK_nbr_tracks [track_type] > 0)
			{
				_dest_track_type = track_type;
				_dest_dry_flag = dry_flag;
				_dest_track_nbr = MIN (_dest_track_nbr,
				                       GTK_nbr_tracks [_dest_track_type] - 1);
				_dest_track_nbr = MAX (_dest_track_nbr, 0);
				if (! player.check_track_validity (_dest_track_type, _dest_track_nbr))
				{
					_dest_track_nbr = 0;
				}
				_disp_pos = 0;
				refresh ();
			}
		}
		break;

	/* Piste destination: piste */
	case	RSC_OBJ_MP_SUBM_MIX_OUT_TRACK:
		if (RSC_mouse_key & 2)
		{
			if (set_dest_track (INTR_CHGTYPE_POP, 0))
			{
				LOG_printf ("PatEdMenuMix::manage: Error: couldn't change destination track (pop-up menu).\n");
			}
		}
		else
		{
			if (set_dest_track (INTR_CHGTYPE_KBD, 0))
			{
				LOG_printf ("PatEdMenuMix::manage: Error: couldn't change destination track (keyboard).\n");
			}
		}
		break;

	/* Piste destination: nom de la piste */
	case	RSC_OBJ_MP_SUBM_MIX_OUT_TNAME:
		player.get_track_name (_dest_track_type,
		                     _dest_track_nbr, track_name_0);
		if (EDIT_edit_string (track_name_0, RSC_OBJ_MP_SUBM_MIX_OUT_TNAME,
									 TRK_NAME_LEN, EditString_TYPE_ALPHA) == 0)
		{
			player.set_track_name (_dest_track_type,
			                     _dest_track_nbr, track_name_0);
			GTK_modified_flag = true;
		}
		refresh ();
		break;

	/* Piste destination: stereo */
	case	RSC_OBJ_MP_SUBM_MIX_OUT_STEREO:
		if (! _dest_dry_flag)
		{
			stereo = player.get_track_stereo (_dest_track_type, _dest_track_nbr, false);
			if (stereo == 1)
			{
				stereo = 2;
			}
			else
			{
				stereo = 1;
			}
			{
				std::lock_guard <std::mutex>	lock (GTK_mutex);
				ret_val = player.set_track_stereo (_dest_track_type, _dest_track_nbr, stereo);
			}
			if (ret_val < 0)
			{
				INTR_dialog_box ("SET DESTINATION TRACK STEREO",
				                 "Error: failed to change track stereo.",
				                 "Cancel", 0, 0);
			}
			else if (ret_val == 1)
			{
				INTR_dialog_box ("SET DESTINATION TRACK STEREO",
				                 "This track can't have its stereo changed.",
				                 "Cancel", 0, 0);
			}
			else
			{
				GTK_modified_flag = true;
			}
			refresh ();
		}
		break;

	/* Piste destination: mute */
	case	RSC_OBJ_MP_SUBM_MIX_OUT_MUTE:
		if (_dest_track_type != GTK_TRACK_TYPE_AOU)
		{
			active_flag = ! player.get_track_onoff (_dest_track_type,
			                                      _dest_track_nbr,
			                                      _dest_dry_flag);
			player.set_track_onoff (_dest_track_type, _dest_track_nbr,
			                      _dest_dry_flag, active_flag);
			INTR_wait_mouse (true);
			TRK_display_mute ();
		}
		break;

	/* Piste destination: volume */
	case	RSC_OBJ_MP_SUBM_MIX_OUT_VOL_SL:
		{
			pos_x = INTR_mouse.x - RSC_absolute_object_pixxpos [sel_object];
			full_bar_width = RSC_get_width (sel_object);
			const double   pos  = double (pos_x) / full_bar_width;
			const double   gain = pos_to_gain (pos);
			volume = long (gain * 0x1000 + 0.5);
			volume = MIN (volume, 0xFFFF);
			volume = MAX (volume, 0x0000);
			if (_dest_track_type == GTK_TRACK_TYPE_AOU)
			{
				player.set_lin_master_volume (volume);
			}
			else
			{
				player.set_track_lin_volume (
					_dest_track_type, _dest_track_nbr, _dest_dry_flag, volume
				);
			}
			update_dest_track (false);
			GTK_modified_flag = true;
		}
		break;

	/* Piste destination: panning */
	case	RSC_OBJ_MP_SUBM_MIX_OUT_PAN:
		if (_dest_track_type != GTK_TRACK_TYPE_AOU)
		{
			pos_x = INTR_mouse.x - RSC_absolute_object_pixxpos [sel_object];
			full_bar_width = RSC_get_width (sel_object);
			bar_width = RSC_get_gfx_ptr (RSC_GFX_KNOB_1_H_G)->get_width ();
			bar_hot_point = (bar_width - 1) / 2;
			balance = ((long)(pos_x - bar_hot_point) * 0x10000) / (full_bar_width - bar_width);
			balance = MIN (balance, 0xFFFF);
			balance = MAX (balance, 0);
			player.set_track_panning (_dest_track_type,
			                        _dest_track_nbr,
			                        _dest_dry_flag, balance);
			update_dest_track (false);
			GTK_modified_flag = true;
		}
		break;

	/* Piste source: type */
	case	RSC_OBJ_MP_SUBM_MIX_IN_TYPE:
		line = (INTR_mouse.y - RSC_absolute_object_pixypos [sel_object]) / RSC_CHAR_H;
		index = line + _disp_pos;
		if (index < nbr_tracks)
		{
			/* Clic droit: effacement */
			if (RSC_mouse_key & 2)
			{
				if (nbr_tracks > 1)
				{
					if (INTR_dialog_box ("REMOVE SOURCE TRACK",
					                     "Do you really want to\n"
					                     "remove this track form\n"
					                     "the sources \?",
					                     "OK\nCancel", 0, 1) == 0)
					{
						std::lock_guard <std::mutex>	lock (GTK_mutex);

						track_conf = track_conf_ptr [index].conf;
						player.remove_source_track (_dest_track_type,
						                          _dest_track_nbr,
						                          &track_conf);
						GTK_modified_flag = true;
					}
				}
				else
				{
					INTR_dialog_box ("REMOVE SOURCE TRACK",
					                 "This track can't be removed.\n"
					                 "Number of source tracks can't be 0.",
					                 "Cancel", 0, 0);
				}
				refresh ();
			}

			/* Clic gauche: changement du type de la piste source */
			else
			{
				set_source_track_type (INTR_CHGTYPE_POP, 0, line);
			}
		}
		break;

	/* Piste source: piste */
	case	RSC_OBJ_MP_SUBM_MIX_IN_TRACK:
		line = (INTR_mouse.y - RSC_absolute_object_pixypos [sel_object]) / RSC_CHAR_H;
		index = line + _disp_pos;
		if (index < nbr_tracks)
		{
			if (RSC_mouse_key & 2)
			{
				set_source_track (INTR_CHGTYPE_POP, 0, line);
			}
			else
			{
				set_source_track (INTR_CHGTYPE_KBD, 0, line);
			}
		}
		break;

	/* Piste source: nom de la piste */
	case	RSC_OBJ_MP_SUBM_MIX_IN_TNAME:
		line = (INTR_mouse.y - RSC_absolute_object_pixypos [sel_object]) / RSC_CHAR_H;
		index = line + _disp_pos;
		if (index < nbr_tracks)
		{
			track_conf_ptr += index;
			track      = track_conf_ptr->conf.track_nbr;
			track_type = track_conf_ptr->conf.track_type;
			player.get_track_name (track_type, track, track_name_0);
			RSC_set_relative_object_position (RSC_OBJ_MP_SUBM_MIX_IN_TNAME_TEXT,
			                                  0, line * RSC_CHAR_H);
			if (EDIT_edit_string (track_name_0, RSC_OBJ_MP_SUBM_MIX_IN_TNAME_TEXT,
										 TRK_NAME_LEN, EditString_TYPE_ALPHA) == 0)
			{
				player.set_track_name (track_type, track, track_name_0);
				GTK_modified_flag = true;
			}
			refresh ();
		}
		break;

	/* Piste source: stereo */
	case	RSC_OBJ_MP_SUBM_MIX_IN_STEREO:
		line = (INTR_mouse.y - RSC_absolute_object_pixypos [sel_object]) / RSC_CHAR_H;
		index = line + _disp_pos;
		if (index < nbr_tracks)
		{
			track_conf_ptr += index;
			track      = track_conf_ptr->conf.track_nbr;
			track_type = track_conf_ptr->conf.track_type;
			stereo = player.get_track_stereo (track_type, track, false);
			if (stereo == 1)
			{
				stereo = 2;
			}
			else
			{
				stereo = 1;
			}
			{
				std::lock_guard <std::mutex>	lock (GTK_mutex);
				ret_val = player.set_track_stereo (track_type, track, stereo);
			}
			if (ret_val < 0)
			{
				INTR_dialog_box ("SET SOURCE TRACK STEREO",
				                 "Error: failed to change track stereo.",
				                 "Cancel", 0, 0);
			}
			else if (ret_val == 1)
			{
				INTR_dialog_box ("SET SOURCE TRACK STEREO",
				                 "This track can't have its stereo changed.",
				                 "Cancel", 0, 0);
			}
			else
			{
				GTK_modified_flag = true;
			}
			refresh ();
		}
		break;

	/* Piste source: mute */
	case	RSC_OBJ_MP_SUBM_MIX_IN_MUTE:
		line = (INTR_mouse.y - RSC_absolute_object_pixypos [sel_object]) / RSC_CHAR_H;
		index = line + _disp_pos;
		if (index < nbr_tracks)
		{
			track_conf_ptr += index;
			track       =   track_conf_ptr->conf.track_nbr;
			track_type  =   track_conf_ptr->conf.track_type;
			dry_flag    = ! track_conf_ptr->conf.wet_flag && track_type == Pattern_TYPE_FX;
			active_flag = ! player.get_track_onoff (track_type, track, dry_flag);
			player.set_track_onoff (track_type, track, dry_flag, active_flag);
			INTR_wait_mouse (true);
			TRK_display_mute ();
			refresh ();
		}
		break;

	/* Ascenseur */
	case	RSC_OBJ_MP_SUBM_MIX_IN_LIFT_SBAR:
	case	RSC_OBJ_MP_SUBM_MIX_IN_LIFT_SBAR_SLIDER:
		SLID_gere_slider (&_source_slider, sel_object);
		if (_source_slider.changed_pos_flag)
		{
			_disp_pos = MIN (_source_slider.virtual_pos,
			                 nbr_tracks - _nbr_disp_lines);
			_disp_pos = MAX (_disp_pos, 0);
			update_source_tracks (true);
		}
		break;

	/* Ascenseur, up */
	case	RSC_OBJ_MP_SUBM_MIX_IN_LIFT_UP:
		_disp_pos --;
		_disp_pos = MIN (_disp_pos,
		                 nbr_tracks - _nbr_disp_lines);
		_disp_pos = MAX (_disp_pos, 0);
		update_source_tracks (true);
		break;

	/* Ascenseur, down */
	case	RSC_OBJ_MP_SUBM_MIX_IN_LIFT_DOWN:
		_disp_pos ++;
		_disp_pos = MIN (_disp_pos,
		                 nbr_tracks - _nbr_disp_lines);
		_disp_pos = MAX (_disp_pos, 0);
		update_source_tracks (true);
		break;

	default:
		for (line = 0; line < _nbr_disp_lines; line ++)
		{
			index = line + _disp_pos;
			if (index < nbr_tracks)
			{
				/* Volume */
				if (sel_object == _rsc_object_ptr [line].vol)
				{
					track_conf_ptr += index;
					track      =   track_conf_ptr->conf.track_nbr;
					track_type =   track_conf_ptr->conf.track_type;
					dry_flag   = ! track_conf_ptr->conf.wet_flag && track_type == Pattern_TYPE_FX;
					pos_x = INTR_mouse.x - RSC_absolute_object_pixxpos [sel_object];
					full_bar_width = RSC_get_width (sel_object);
					const double   pos  = double (pos_x) / full_bar_width;
					const double   gain = pos_to_gain (pos);
					volume = long (gain * 0x1000 + 0.5);
					volume = MIN (volume, 0xFFFF);
					volume = MAX (volume, 0x0000);
					if (_dest_track_type == GTK_TRACK_TYPE_AOU)
					{
						player.set_track_lin_volume (track_type, track, dry_flag, volume);
					}
					else
					{
						track_conf_ptr->conf.inpvol = volume;
					}
					GTK_modified_flag = true;
					update_source_tracks (false);
					break;	// Sorite du for (line)
				}

				/* Panning */
				if (sel_object == _rsc_object_ptr [line].pan)
				{
					track_conf_ptr += index;
					track      =   track_conf_ptr->conf.track_nbr;
					track_type =   track_conf_ptr->conf.track_type;
					dry_flag   = ! track_conf_ptr->conf.wet_flag && track_type == Pattern_TYPE_FX;
					pos_x      = INTR_mouse.x - RSC_absolute_object_pixxpos [sel_object];
					full_bar_width = RSC_get_width (sel_object);
					bar_width = RSC_get_gfx_ptr (RSC_GFX_KNOB_1_H_G)->get_width ();
					bar_hot_point = (bar_width - 1) / 2;
					balance = ((long)(pos_x - bar_hot_point) * 0x10000) / (full_bar_width - bar_width);
					balance = MIN (balance, 0xFFFF);
					balance = MAX (balance, 0);
					if (_dest_track_type == GTK_TRACK_TYPE_AOU)
					{
						player.set_track_panning (track_type, track, dry_flag, balance);
					}
					else
					{
						track_conf_ptr->conf.inppan = balance;
					}
					GTK_modified_flag = true;
					update_source_tracks (false);
					break;	// Sorite du for (line)
				}
			}

		}
		break;
	}
}



/*==========================================================================*/
/*      Nom: activate                                                       */
/*      Description: Active ou desactive le menu. Celui-ci n'est pas        */
/*                   reaffiche.                                             */
/*      Parametres en entree:                                               */
/*        - activated_flag: indique si le menu doit etre active ou          */
/*                          desactive.                                      */
/*==========================================================================*/

void	PatEdMenuMix::activate (bool activated_flag)
{
	RSC_pos_flag (RSC_OBJ_MP_SUBM_MIX, RSC_ATTR_NOTDISP, ! activated_flag);
	RSC_pos_flag (RSC_OBJ_MP_SMICN_MIX, RSC_ATTR_SELECTED, activated_flag);
}



/*\\\ METHODES PRIVEES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



void	PatEdMenuMix::clear_lines (void)
{
	for (int line = 0; line < _nbr_rsc_lines; line ++)
	{
		RSC_set_flag (_rsc_object_ptr [line].vol, RSC_ATTR_NOTDISP);
		RSC_set_flag (_rsc_object_ptr [line].pan, RSC_ATTR_NOTDISP);
	}
}



void	PatEdMenuMix::update_dest_track (bool force_flag)
{
	static signed int	old_track = -1;
	static signed int	old_track_type = -1;
	static signed int	old_balance = -1;
	static int	old_active_state = -1;
	static signed int	old_stereo = -1;
	static int	old_dry_state = -1;

	int		full_bar_width;
	int		bar_width;
	int		bar_pos;
	int		stereo;
	bool		active_flag;
	long		balance;
	char		nbr2_0 [2+1];
	char		nbr3_0 [3+1];
	char		name_0 [TRK_NAME_LEN+1];

	if (force_flag)
	{
		old_track = -1;
		old_track_type = -1;
		old_dry_state = -1;
	}

	if (   old_track != _dest_track_nbr
	    || old_track_type != _dest_track_type
		 || old_dry_state != (_dest_dry_flag ? 1 : 0))
	{
		old_track = -1;
		old_track_type = -1;
		old_balance = -1;
		old_active_state = -1;
		old_dry_state = -1;
		old_stereo = -1;
	}

	const Player &	player = Player::use_instance ();

	/* Type de piste */
	if (   _dest_track_type != old_track_type
	    || old_dry_state != (_dest_dry_flag ? 1 : 0))
	{
		old_track_type = _dest_track_type;
		old_dry_state = _dest_dry_flag ? 1 : 0;
		RSC_set_string (RSC_OBJ_MP_SUBM_MIX_OUT_TYPE,
		                track_type_name_0_ptr [_dest_track_type]
		                                      [_dest_dry_flag ? 1 : 0]);
		RSC_display_object (RSC_OBJ_MP_SUBM_MIX_OUT_TYPE);
	}

	/* Numero et nom de piste */
	if (_dest_track_nbr != old_track)
	{
		old_track = _dest_track_nbr;

		/* Numero */
		sprintf (nbr2_0, INTR_base_track_2, _dest_track_nbr + 1);
		RSC_set_string (RSC_OBJ_MP_SUBM_MIX_OUT_TRACK, nbr2_0);
		RSC_display_object (RSC_OBJ_MP_SUBM_MIX_OUT_TRACK);

		/* Nom */
		player.get_track_name (_dest_track_type,
		                     _dest_track_nbr, name_0);
		RSC_set_string (RSC_OBJ_MP_SUBM_MIX_OUT_TNAME, name_0);
		RSC_display_object (RSC_OBJ_MP_SUBM_MIX_OUT_TNAME);
	}

	/* Stereo */
	stereo = player.get_track_stereo (_dest_track_type, _dest_track_nbr, _dest_dry_flag);
	if (stereo != old_stereo)
	{
		old_stereo = stereo;
		sprintf (nbr3_0, " %1d ", stereo);
		RSC_set_string (RSC_OBJ_MP_SUBM_MIX_OUT_STEREO, nbr3_0);
		RSC_display_object (RSC_OBJ_MP_SUBM_MIX_OUT_STEREO);
	}

	/* Mute */
	active_flag =    player.get_track_onoff (_dest_track_type,
	                                       _dest_track_nbr,
	                                       _dest_dry_flag)
	              || _dest_track_type == GTK_TRACK_TYPE_AOU;
	if ((active_flag ? 1 : 0) != old_active_state)
	{
		old_active_state = active_flag ? 1 : 0;
		RSC_set_string (RSC_OBJ_MP_SUBM_MIX_OUT_MUTE,
		                active_flag ? "   " : " X ");
		RSC_display_object (RSC_OBJ_MP_SUBM_MIX_OUT_MUTE);
	}

	/* Volume */
	{
		const int      vol_int = player.get_track_lin_volume (
			_dest_track_type, _dest_track_nbr, _dest_dry_flag
		);
		const double   volume = vol_int * (1.0 / 0x1000);

		const int         global_track_nbr =
			player._track_info_list [_dest_track_type] [_dest_track_nbr];
		const Player::TrackInfo &  track_info =
			player._track_info [global_track_nbr];
		const double      peak = track_info.meter.get_peak_hold ();
		const double      rms  = track_info.meter.get_rms ();
		display_fader_gain (RSC_OBJ_MP_SUBM_MIX_OUT_VOL, peak, rms, volume);
	}

	/* Balance */
	balance = player.get_track_panning (_dest_track_type,
	                                  _dest_track_nbr,
	                                  _dest_dry_flag) >> 4;
	if (_dest_track_type == GTK_TRACK_TYPE_AOU)
	{
		balance = 0;
	}

	if (old_balance != balance)
	{
		old_balance = balance;

		if (_dest_track_type == GTK_TRACK_TYPE_AOU)
		{
			RSC_display_object (RSC_OBJ_MP_SUBM_MIX_OUT_PAN);
		}

		else
		{
			full_bar_width = RSC_get_width (RSC_OBJ_MP_SUBM_MIX_OUT_PAN);

			/* Fond */
			RSC_display_image (RSC_get_gfx_ptr (RSC_GFX_PANMETRE_112), 0, 0,
			                   full_bar_width, RSC_CHAR_H,
			                   RSC_OBJ_MP_SUBM_MIX_OUT_PAN, 0, 0);
			
			/* Potentiometre */
			bar_width = RSC_get_gfx_ptr (RSC_GFX_KNOB_1_H_G)->get_width ();
			bar_pos = (int) (balance * (full_bar_width - bar_width + 1) / 0x1000L);
			RSC_display_image (RSC_get_gfx_ptr (RSC_GFX_KNOB_1_H_G), 0, 0,
			                   bar_width, RSC_CHAR_H,
			                   RSC_OBJ_MP_SUBM_MIX_OUT_PAN, bar_pos, 0,
			                   255);
		}
	}
}



void	PatEdMenuMix::update_source_tracks (bool force_flag)
{
	/* Les volumes sont ici stoques sous forme logarithmique 0..C00..FFF */
	static PatEdMenuMix_SOURCE_CONF	old_track [GTK_NBRTRACKS_MAXI];
	static int	old_nbr_tracks = 0;
	static signed int	old_disp_pos = -1;

	int		nbr_tracks;
	int		line;
	int		current_pos;
	int		current_disp_line;
	int		track_type;
	int		track;
	long		volume;
	long		balance;
	int		stereo;
	int		full_bar_width;
	int		bar_width;
	int		bar_pos;
	int		max_len;
	bool		active_flag;
	bool		dry_flag;
	PatEdMenuMix_SOURCE_CONF	*old_track_ptr;
	Player::TrackInfoSrc::Source	*track_conf_ptr;
	char		nbr2_0 [2+1];
	char		nbr3_0 [3+1];
	char		name_0 [TRK_NAME_LEN+1];

	if (force_flag)
	{
		old_nbr_tracks = 0;
	}

	Player &			player = Player::use_instance ();

	/* Recupere l'adresse des informations de la piste destination courante */
	player.get_track_nbr_s_tracks (
		_dest_track_type, _dest_track_nbr, &nbr_tracks, &track_conf_ptr
	);

	_disp_pos = MIN (_disp_pos, nbr_tracks - _nbr_disp_lines);
	_disp_pos = MAX (_disp_pos, 0);

	/* Ascenseur */
	if (   old_nbr_tracks != nbr_tracks
	    || old_disp_pos != _disp_pos)
	{
		_source_slider.virtual_len = nbr_tracks;
		_source_slider.virtual_pos = _disp_pos;
		_source_slider.virtual_win = _nbr_disp_lines;
		SLID_display_slider (&_source_slider);
	}
	
	if (_disp_pos != old_disp_pos)
	{
		old_disp_pos = _disp_pos;
		old_nbr_tracks = 0;
	}

	/* Effacage du fond */
	if (nbr_tracks < old_nbr_tracks || old_nbr_tracks <= 0)
	{
		RSC_set_string (RSC_OBJ_MP_SUBM_MIX_IN_TYPE_TEXT, "");
		RSC_set_string (RSC_OBJ_MP_SUBM_MIX_IN_TRACK_TEXT, "");
		RSC_set_string (RSC_OBJ_MP_SUBM_MIX_IN_TNAME_TEXT, "");
		RSC_set_string (RSC_OBJ_MP_SUBM_MIX_IN_STEREO_TEXT, "");
		RSC_set_string (RSC_OBJ_MP_SUBM_MIX_IN_MUTE_TEXT, "");
		RSC_set_relative_object_position (RSC_OBJ_MP_SUBM_MIX_IN_TYPE_TEXT, 0, 0);
		RSC_set_relative_object_position (RSC_OBJ_MP_SUBM_MIX_IN_TRACK_TEXT, 0, 0);
		RSC_set_relative_object_position (RSC_OBJ_MP_SUBM_MIX_IN_TNAME_TEXT, 0, 0);
		RSC_set_relative_object_position (RSC_OBJ_MP_SUBM_MIX_IN_STEREO_TEXT, 0, 0);
		RSC_set_relative_object_position (RSC_OBJ_MP_SUBM_MIX_IN_MUTE_TEXT, 0, 0);
		clear_lines ();
		RSC_display_object (RSC_OBJ_MP_SUBM_MIX_IN);
		old_nbr_tracks = 0;
	}

	current_pos = _disp_pos;
	current_disp_line = 0;
	track_conf_ptr += current_pos;
	old_track_ptr = old_track + current_pos;
	for (line = 0; line < _nbr_disp_lines; line ++)
	{
		/* Update de la ligne */
		if (current_pos < nbr_tracks)
		{
			track_type = track_conf_ptr->conf.track_type;
			dry_flag =    ! track_conf_ptr->conf.wet_flag
			           && track_type == Pattern_TYPE_FX;
			track = track_conf_ptr->conf.track_nbr;
			volume = Player::lin_to_log_volume (track_conf_ptr->conf.inpvol);
			balance = track_conf_ptr->conf.inppan;

			if (current_pos >= old_nbr_tracks)
			{
				old_track_ptr->track_type = -1;
			}

			/* type de piste */
			if (   track_type != old_track_ptr->track_type
			    || (dry_flag ? 1 : 0) != old_track_ptr->dry_state)
			{
				old_track_ptr->track_type = track_type;
				old_track_ptr->dry_state = dry_flag ? 1 : 0;
				old_track_ptr->track = -1;
				old_track_ptr->volume = 0xFFFF;
				old_track_ptr->balance = 0x1000;
				old_track_ptr->stereo = -1;
				old_track_ptr->active_state = -1;

				RSC_set_string (RSC_OBJ_MP_SUBM_MIX_IN_TYPE_TEXT,
				                track_type_name_0_ptr [track_type]
				                                      [dry_flag ? 1 : 0]);
				RSC_set_relative_object_position (RSC_OBJ_MP_SUBM_MIX_IN_TYPE_TEXT,
				                                  0, current_disp_line);
				RSC_display_object (RSC_OBJ_MP_SUBM_MIX_IN_TYPE_TEXT);
			}

			/* Numero et nom de piste */
			if (old_track_ptr->track != track)
			{
				old_track_ptr->track = track;

				/* Numero */
				sprintf (nbr2_0, INTR_base_track_2, track + 1);
				RSC_set_string (RSC_OBJ_MP_SUBM_MIX_IN_TRACK_TEXT, nbr2_0);
				RSC_set_relative_object_position (RSC_OBJ_MP_SUBM_MIX_IN_TRACK_TEXT,
				                                  0, current_disp_line);
				RSC_display_object (RSC_OBJ_MP_SUBM_MIX_IN_TRACK_TEXT);

				/* Nom */
				player.get_track_name (track_type, track, name_0);
				max_len = RSC_get_width (RSC_OBJ_MP_SUBM_MIX_IN_TNAME) / RSC_CHAR_W;
				if (max_len < TRK_NAME_LEN)
				{
					name_0 [max_len] = '\0';
				}
				RSC_set_string (RSC_OBJ_MP_SUBM_MIX_IN_TNAME_TEXT, name_0);
				RSC_set_relative_object_position (RSC_OBJ_MP_SUBM_MIX_IN_TNAME_TEXT,
				                                  0, current_disp_line);
				RSC_display_object (RSC_OBJ_MP_SUBM_MIX_IN_TNAME_TEXT);
			}

			/* Stereo */
			stereo = player.get_track_stereo (track_type, track, dry_flag);
			if (stereo != old_track_ptr->stereo)
			{
				old_track_ptr->stereo = stereo;
				sprintf (nbr3_0, " %1d ", stereo);
				RSC_set_string (RSC_OBJ_MP_SUBM_MIX_IN_STEREO_TEXT, nbr3_0);
				RSC_set_relative_object_position (RSC_OBJ_MP_SUBM_MIX_IN_STEREO_TEXT,
				                                  0, current_disp_line);
				RSC_display_object (RSC_OBJ_MP_SUBM_MIX_IN_STEREO_TEXT);
			}


			/* Mute */
			active_flag = player.get_track_onoff (track_type, track, dry_flag);
			if (old_track_ptr->active_state != (active_flag ? 1 : 0))
			{
				old_track_ptr->active_state = active_flag ? 1 : 0;
				RSC_set_string (RSC_OBJ_MP_SUBM_MIX_IN_MUTE_TEXT,
									 active_flag ? "   " : " X ");
				RSC_set_relative_object_position (RSC_OBJ_MP_SUBM_MIX_IN_MUTE_TEXT,
				                                  0, current_disp_line);
				RSC_display_object (RSC_OBJ_MP_SUBM_MIX_IN_MUTE_TEXT);
			}

			/* Volume */
			{
				RSC_clear_flag (_rsc_object_ptr [line].vol, RSC_ATTR_NOTDISP);

				int            vol_int = 0;
				if (_dest_track_type == GTK_TRACK_TYPE_AOU)
				{
					vol_int = player.get_track_lin_volume (track_type, track, dry_flag);
				}
				else
				{
					vol_int = track_conf_ptr->conf.inpvol;
				}
				old_track_ptr->volume = UWORD (vol_int);

				const double   volume = vol_int * (1.0 / 0x1000);
				const Meter *  meter_ptr = &track_conf_ptr->meter;
				if (track_type != Pattern_TYPE_SPL)
				{
					const int      global_track_nbr =
						player._track_info_list [track_type] [track];
					const Player::TrackInfo &  track_info =
						player._track_info [global_track_nbr];
					meter_ptr = &track_info.meter;
				}
				const double   peak = meter_ptr->get_peak_hold ();
				const double   rms  = meter_ptr->get_rms ();
				display_fader_gain (_rsc_object_ptr [line].vol, peak, rms, volume);
			}

			/* Panning */
			RSC_clear_flag (_rsc_object_ptr [line].pan, RSC_ATTR_NOTDISP);
			if (_dest_track_type == GTK_TRACK_TYPE_AOU)
			{
				balance = player.get_track_panning (track_type, track, dry_flag) / 0x10;
			}
			else
			{
				balance = track_conf_ptr->conf.inppan / 0x10;
			}
			if (old_track_ptr->balance != balance)
			{
				old_track_ptr->balance = (WORD)balance;

				full_bar_width = RSC_get_width (RSC_OBJ_MP_SUBM_MIX_IN_PAN);
				
				/* Fond */
				RSC_display_object (_rsc_object_ptr [line].pan);
				
				/* Potentiometre */
				bar_width = RSC_get_gfx_ptr (RSC_GFX_KNOB_1_H_G)->get_width ();
				bar_pos = (int) (balance * (full_bar_width - bar_width + 1) / 0x1000L);

				RSC_display_image (RSC_get_gfx_ptr (RSC_GFX_KNOB_1_H_G), 0, 0,
				                   bar_width, RSC_CHAR_H,
				                   _rsc_object_ptr [line].pan, bar_pos, 0, 255);
			}
		}

		current_pos ++;
		current_disp_line += RSC_CHAR_H;
		track_conf_ptr ++;
		old_track_ptr ++;
	}
	
	old_nbr_tracks = nbr_tracks;

	INTR_graph_ptr->show_mouse ();
}



/*==========================================================================*/
/*      Nom: set_mix_preset_number                                          */
/*      Description: Change le numero du preset de mixage courant de        */
/*                   differentes manieres. L'interface est ici mise en jeu. */
/*      Parametres en entree:                                               */
/*        - type: 0 = Le parametre est absolu,                              */
/*                1 = Le parametre est relatif,                             */
/*                2 = Entree au clavier,                                    */
/*                3 = Entree au pop-up.                                     */
/*        - value: le parametre en question.                                */
/*      Retour: 0 si tout s'est bien passe.                                 */
/*==========================================================================*/

signed int	PatEdMenuMix::set_mix_preset_number (int type, signed int value)
{
	int		new_value;
	signed int	line;
	signed long	code;
	char		line_0 [5 + MixPreset_NAME_LEN+1];
	char		nbr3_0 [3+1];
	Popup		popup_menu;

	new_value = GTK_mix_nbr;

	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, RSC_OBJ_MP_SUBM_MIX_COM_PRESET_VAL, 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;

	/* Pop-up */
	case	INTR_CHGTYPE_POP:
		for (int count = 1; count <= MIXP_NBRMIXP_MAXI; count ++)
		{
			sprintf (line_0, INTR_base_song_3, count);
			line_0 [3] = ':';
			line_0 [4] = ' ';
			MIXP_get_preset_name (count, line_0 + 5);
			popup_menu.add_line (line_0, count);
		}

		line = popup_menu.select_radio_by_code (new_value);
		code = popup_menu.manage (line);
		if (code >= 0)
		{
			new_value = (int) code;
		}
		break;
	}

	new_value = MIN (new_value, MIXP_NBRMIXP_MAXI);
	new_value = MAX (new_value, 1);
	GTK_mix_nbr = new_value;

	refresh ();

	return (0);
}



/*==========================================================================*/
/*      Nom: set_dest_track                                                 */
/*      Description: Change le numero de piste destination de               */
/*                   differentes manieres. L'interface est ici mise en jeu. */
/*      Parametres en entree:                                               */
/*        - type: 0 = Le parametre est absolu,                              */
/*                1 = Le parametre est relatif,                             */
/*                2 = Entree au clavier,                                    */
/*                3 = Entree au pop-up.                                     */
/*        - value: le parametre en question.                                */
/*      Retour: 0 si tout s'est bien passe.                                 */
/*==========================================================================*/

signed int	PatEdMenuMix::set_dest_track (int type, signed int value)
{
	int		new_value;
	signed int	line;
	signed long	code;
	char		nbr2_0 [2+1];
	char		line_0 [4 + TRK_NAME_LEN+1];
	int		stereo;
	Popup		popup_menu;

	const Player &	player = Player::use_instance ();

	new_value = _dest_track_nbr;

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

	/* Entree au clavier */
	case	INTR_CHGTYPE_KBD:
		sprintf (nbr2_0, INTR_base_track_2, new_value + 1);
		EDIT_edit_string_base (nbr2_0, RSC_OBJ_MP_SUBM_MIX_OUT_TRACK, 2, INTR_base_track);
		new_value = (int) strtol (nbr2_0, NULL, INTR_base_track) - 1;
		break;

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

	/* Pop-up */
	case	INTR_CHGTYPE_POP:
		for (int track = 0; track < GTK_nbr_tracks [_dest_track_type]; track ++)
		{
			sprintf (line_0, INTR_base_track_2, track + 1);
			line_0 [2] = ':';
			line_0 [3] = ' ';
			player.get_track_name (_dest_track_type, track, line_0 + 4);
			popup_menu.add_line (line_0, track);
		}

		if (_dest_track_type == GTK_TRACK_TYPE_AOU)
		{
			for (int track = 0; track < GTK_nbr_tracks [GTK_TRACK_TYPE_AOU]; track ++)
			{
				stereo = player.get_track_stereo (GTK_TRACK_TYPE_AOU, track, false) - 1;
				while (stereo > 0)
				{
					track ++;
					if (popup_menu.disable_by_code (track, true) < 0)
					{
						LOG_printf ("PatEdMenuMix::set_dest_track: Warning: line of Audio Out track # %d not found in pop-up menu.\n",
						            track);
					}
					stereo --;
				}
			}
		}

		line = popup_menu.select_radio_by_code (_dest_track_nbr);
		code = popup_menu.manage (line);
		if (code >= 0)
		{
			new_value = (int) code;
		}
		break;
	}

	new_value = MIN (new_value, GTK_nbr_tracks [_dest_track_type] - 1);
	new_value = MAX (new_value, 0);
	if (player.check_track_validity (_dest_track_type, new_value))
	{
		_dest_track_nbr = new_value;
		_disp_pos = 0;
	}

	refresh ();

	return (0);
}



/*==========================================================================*/
/*      Nom: set_source_track_type                                          */
/*      Description: Change le type de piste source de                      */
/*                   differentes manieres. L'interface est ici mise en jeu. */
/*                   Le numero de piste source peut eventuellement etre     */
/*                   change.                                                */
/*      Parametres en entree:                                               */
/*        - type: 0 = Le parametre est absolu,                              */
/*                1 = Le parametre est relatif,                             */
/*                3 = Entree au pop-up.                                     */
/*        - value: le parametre en question.                                */
/*        - win_line: numero de la ligne de la fenetre sur laquelle on a    */
/*                    clique.                                               */
/*      Retour: 0 si tout s'est bien passe.                                 */
/*==========================================================================*/

signed int	PatEdMenuMix::set_source_track_type (int type, signed int value, int win_line)
{
	signed int	ret_val = 0;
	signed int	line;
	signed long	code;
	int		index;
	int		nbr_tracks;
	int		old_value;
	Player::TrackInfoSrc::Source	*track_conf_ptr;
	PLAY_SOURCE_TRACK_CONF_BLOCK	new_track_conf;
	Popup		popup_menu;

	{
		std::lock_guard <std::mutex>	lock (GTK_mutex);

		Player &			player = Player::use_instance ();

		player.get_track_nbr_s_tracks (
			_dest_track_type, _dest_track_nbr, &nbr_tracks, &track_conf_ptr
		);
		index = win_line + _disp_pos;
		new_track_conf = track_conf_ptr [index].conf;
		new_track_conf.wet_flag =    new_track_conf.wet_flag
		                          || new_track_conf.track_type != Pattern_TYPE_FX;

		old_value = new_track_conf.track_type;

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

		/* Position absolue  */
		case	INTR_CHGTYPE_ABS:
			new_track_conf.track_type = value;
			break;

		/* Pop-up */
		case	INTR_CHGTYPE_POP:
			popup_menu.add_line ("Samples", Pattern_TYPE_SPL);
			popup_menu.add_line ("Effects, wet part", Pattern_TYPE_FX);
			popup_menu.add_line ("Effects, dry part", Pattern_TYPE_FX + 0x100);
			popup_menu.add_line ("Audio In", Pattern_TYPE_AIN);
			line = popup_menu.select_radio_by_code (  new_track_conf.track_type
																 + (new_track_conf.wet_flag ? 0x000 : 0x100));
			code = popup_menu.manage (line);
			if (code >= 0)
			{
				new_track_conf.track_type = static_cast <BYTE> (code & 0xFF);
				new_track_conf.wet_flag = ((code & 0xFF00) == 0);
			}
			break;
		}

		/* Verification de la validite de la nouvelle piste source */
		if (new_track_conf.track_type != old_value)
		{
			std::lock_guard <std::mutex>	lock (GTK_mutex);

			int find_ret_val = player.find_track_to_add (_dest_track_type, _dest_track_nbr, &new_track_conf);
			if (find_ret_val == 0 || find_ret_val == 1)
			{
				track_conf_ptr [index].conf = new_track_conf;
				GTK_modified_flag = true;
			}
			else if (find_ret_val < 0)
			{
				ret_val = -1;
			}
		}
	}

	if (ret_val == -1)
	{
		LOG_printf ("PatEdMenuMix::set_source_track_type: Error: couldn't change source track number.\n");
	}
	refresh ();

	return ret_val;
}



/*==========================================================================*/
/*      Nom: set_source_track                                               */
/*      Description: Change le numero d'une piste source de                 */
/*                   differentes manieres. L'interface est ici mise en jeu. */
/*      Parametres en entree:                                               */
/*        - type: 0 = Le parametre est absolu,                              */
/*                1 = Le parametre est relatif,                             */
/*                2 = Entree au clavier,                                    */
/*                3 = Entree au pop-up.                                     */
/*        - value: le parametre en question.                                */
/*        - win_line: numero de la ligne de la fenetre sur laquelle on a    */
/*                    clique.                                               */
/*      Retour: 0 si tout s'est bien passe.                                 */
/*==========================================================================*/

signed int	PatEdMenuMix::set_source_track (int type, signed int value, int win_line)
{
	signed int	line;
	signed int	ret_val;
	signed long	code;
	char		nbr2_0 [2+1];
	char		line_0 [4 + TRK_NAME_LEN+1];
	int		stereo;
	int		index;
	int		nbr_tracks;
	Player::TrackInfoSrc::Source	*track_conf_ptr;
	PLAY_SOURCE_TRACK_CONF_BLOCK	new_track_conf;
	PLAY_SOURCE_TRACK_CONF_BLOCK	old_track_conf;
	Popup		popup_menu;

	std::lock_guard <std::mutex>	lock (GTK_mutex);

	Player &			player = Player::use_instance ();

	player.get_track_nbr_s_tracks (
		_dest_track_type, _dest_track_nbr, &nbr_tracks, &track_conf_ptr
	);
	index = win_line + _disp_pos;
	new_track_conf = track_conf_ptr [index].conf;
	new_track_conf.wet_flag =    new_track_conf.wet_flag
	                          || new_track_conf.track_type != Pattern_TYPE_FX;
	old_track_conf = new_track_conf;

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

	/* Entree au clavier */
	case	INTR_CHGTYPE_KBD:
		RSC_set_relative_object_position (RSC_OBJ_MP_SUBM_MIX_IN_TRACK_TEXT,
		                                  0, win_line * RSC_CHAR_H);
		sprintf (nbr2_0, INTR_base_track_2, new_track_conf.track_nbr + 1);
		EDIT_edit_string_base (nbr2_0, RSC_OBJ_MP_SUBM_MIX_IN_TRACK_TEXT, 2, INTR_base_track);
		new_track_conf.track_nbr = (int) strtol (nbr2_0, NULL, INTR_base_track) - 1;
		break;

	/* Position absolue  */
	case	INTR_CHGTYPE_ABS:
		new_track_conf.track_nbr = value;
		break;

	/* Pop-up */
	case	INTR_CHGTYPE_POP:
		for (int track = 0; track < GTK_nbr_tracks [new_track_conf.track_type]; track ++)
		{
			sprintf (line_0, INTR_base_track_2, track + 1);
			line_0 [2] = ':';
			line_0 [3] = ' ';
			player.get_track_name (new_track_conf.track_type, track, line_0 + 4);
			popup_menu.add_line (line_0, track);
		}

		if (new_track_conf.track_type == Pattern_TYPE_AIN)
		{
			for (int track = 0; track < GTK_nbr_tracks [Pattern_TYPE_AIN]; track ++)
			{
				stereo = player.get_track_stereo (Pattern_TYPE_AIN, track, false) - 1;
				while (stereo > 0)
				{
					track ++;
					if (popup_menu.disable_by_code (track, true) < 0)
					{
						LOG_printf ("PatEdMenuMix::set_source_track: Warning: line of Audio In track # %d not found in pop-up menu.\n",
						            track);
					}
					stereo --;
				}
			}
		}

		line = popup_menu.select_radio_by_code (new_track_conf.track_nbr);
		code = popup_menu.manage (line);
		if (code >= 0)
		{
			new_track_conf.track_nbr = (int) code;
		}
		break;
	}

	new_track_conf.track_nbr = MIN (new_track_conf.track_nbr, GTK_nbr_tracks [new_track_conf.track_type] - 1);
	new_track_conf.track_nbr = MAX (new_track_conf.track_nbr, 0);

	{
		std::lock_guard <std::mutex>	lock (GTK_mutex);

		ret_val = player.find_track_to_add (
			_dest_track_type,
	      _dest_track_nbr,
	      &new_track_conf
		);
		if (ret_val == 0)
		{
			if (nbr_tracks > 1)
			{
				if (player.remove_source_track (_dest_track_type, _dest_track_nbr, &old_track_conf) < 0)
				{
					LOG_printf ("PatEdMenuMix::set_source_track: Error: couldn't remove the old source track config.\n");
					refresh ();
					return (-1);
				}
				GTK_modified_flag = true;
			}

			if (player.add_source_track (
				_dest_track_type,
				_dest_track_nbr,
				&new_track_conf,
				Player::MIX_PRESET_REPL
			) < 0)
			{
				LOG_printf ("PatEdMenuMix::set_source_track: Error: couldn't add the new source track config.\n");
				refresh ();
				return (-1);
			}
			GTK_modified_flag = true;

			if (nbr_tracks <= 1)
			{
				if (player.remove_source_track (_dest_track_type, _dest_track_nbr, &old_track_conf) < 0)
				{
					LOG_printf ("PatEdMenuMix::set_source_track: Error: couldn't remove the source track config.\n");
					refresh ();
					return (-1);
				}
			}
		}

		else if (ret_val < 0)
		{
			LOG_printf ("PatEdMenuMix::set_source_track: Error: couldn't change source track number.\n");
			refresh ();
			return (-1);
		}
	}

	refresh ();

	return (0);
}



void	PatEdMenuMix::display_fader_gain (int obj, double peak, double rms, double volume)
{
	assert (peak >= 0);
	assert (rms >= 0);
	assert (volume >= 0);

	// Background
	RSC_display_object (obj);

	const int		full_bar_width =
		RSC_get_gfx_ptr (RSC_GFX_VUMETRE_H_BRIGHT)->get_width ();

	// Levels
	const double      scale   = 1.0 / (1UL << 23);
	const double      peak_pos = gain_to_pos (peak * scale);
	if (peak_pos > 0)
	{
		const int         peak_pix = std::max (std::min (
			int (peak_pos * full_bar_width + 0.5),
			full_bar_width
		), 0);
		const int         pos_l = std::max (peak_pix - 1, 0);
		const int         pos_r = std::min (peak_pix + 1, full_bar_width);
		RSC_display_image (
			RSC_get_gfx_ptr (RSC_GFX_VUMETRE_H_BRIGHT),
			pos_l, 0,
			pos_r - pos_l, RSC_CHAR_H,
			obj,
			pos_l, 0
		);
	}

	const double      rms_pos = gain_to_pos (rms * scale);
	const int         rms_pix = std::max (std::min (
		int (rms_pos * full_bar_width + 0.5),
		full_bar_width
	), 0);
	if (rms_pix > 0)
	{
		RSC_display_image (
			RSC_get_gfx_ptr (RSC_GFX_VUMETRE_H_BRIGHT),
			0, 0,
			rms_pix, RSC_CHAR_H,
			obj,
			0, 0
		);
	}

	// Fader
	const double   fader_pos = gain_to_pos (volume);
	int            fader_pix = int (fader_pos * full_bar_width + 0.5);
	const int      fader_width =
		RSC_get_gfx_ptr (RSC_GFX_KNOB_1_H_G)->get_width ();
	const int      fader_hot_point = (fader_width - 1) / 2;
	fader_pix = std::min (fader_pix, full_bar_width - fader_width + fader_hot_point);
	fader_pix = std::max (fader_pix, fader_hot_point);

	RSC_display_image (
		RSC_get_gfx_ptr (RSC_GFX_KNOB_1_H_G),
		0, 0,
		fader_width, RSC_CHAR_H,
	   obj,
		fader_pix - fader_hot_point, 0,
		255
	);
}



double	PatEdMenuMix::pos_to_gain (double pos)
{
	double         db = _fader_map [0]._gain_db;
	if (pos >= _fader_map [FADER_MAP_SIZE - 1]._pos)
	{
		db = _fader_map [FADER_MAP_SIZE - 1]._gain_db;
	}
	else if (pos > _fader_map [0]._pos)
	{
		bool           found_flag = false;
		for (int elt = 1; ! found_flag && elt < FADER_MAP_SIZE; ++elt)
		{
			if (pos <= _fader_map [elt]._pos)
			{
				const double   p0    = _fader_map [elt - 1]._pos;
				const double   p1    = _fader_map [elt    ]._pos;
				const double   ratio = (pos - p0) / (p1 - p0);
				const double   db0   = _fader_map [elt - 1]._gain_db;
				const double   db1   = _fader_map [elt    ]._gain_db;
				db = db0 + (db1 - db0) * ratio;
				found_flag = true;
			}
		}
	}
	const double   g = db_to_gain (db);

	return (g);
}



double	PatEdMenuMix::gain_to_pos (double g)
{
	assert (g >= 0);

	double         pos = _fader_map [0]._pos;
	const double   db = gain_to_db (g);
	if (db >= _fader_map [FADER_MAP_SIZE - 1]._gain_db)
	{
		pos = _fader_map [FADER_MAP_SIZE - 1]._pos;
	}
	else if (db > _fader_map [0]._gain_db)
	{
		bool           found_flag = false;
		for (int elt = 1; ! found_flag && elt < FADER_MAP_SIZE; ++elt)
		{
			if (db <= _fader_map [elt]._gain_db)
			{
				const double   db0   = _fader_map [elt - 1]._gain_db;
				const double   db1   = _fader_map [elt    ]._gain_db;
				const double   ratio = (db - db0) / (db1 - db0);
				const double   p0    = _fader_map [elt - 1]._pos;
				const double   p1    = _fader_map [elt    ]._pos;
				pos = p0 + (p1 - p0) * ratio;
				found_flag = true;
			}
		}
	}

	return (pos);
}



const double	PatEdMenuMix::db_to_gain (double db)
{
	return (pow (10, db / 20));
}



const double	PatEdMenuMix::gain_to_db (double g)
{
	return ((g < 1e-10) ? -200 : 20 * log10 (g));
}



const PatEdMenuMix::Point	PatEdMenuMix::_fader_map [FADER_MAP_SIZE] =
{
/*
	{       0, -24 },
	{ 2.0/3.0,   0 },
	{       1, +12 }
*/
	{   0/192.0, -72 },
	{  84/192.0, -24 },
	{ 116/192.0, -12 },
	{ 176/192.0,  +6 },
	{ 192/192.0, +12 }
};



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



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