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

        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	<assert.h>
#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>

#include	"archi.h"
#include	"base.h"
#include	"base_ct.h"
#include	"log.h"
#include	"memory.h"
#include	"modstruc.h"
#include	"Pattern.h"



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

const int	Pattern::NOTE_SIZE [Pattern_NBR_TYPES] =
{
	MODS_GT2_SPL_NOTE_LEN,
	MODS_GT2_AIN_NOTE_LEN,
	MODS_GT2_FX_NOTE_LEN,
	MODS_GT2_MID_NOTE_LEN,
};



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



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



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



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



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



/*==========================================================================*/
/*      Nom: (constructeur)                                                 */
/*      Description: Initialise                                             */
/*      Parametres en entree:                                               */
/*        - number: numero du pattern a creer.                              */
/*        - tracks: nombre de pistes sur le pattern.                        */
/*==========================================================================*/

Pattern::Pattern (int number, const int tracks [Pattern_NBR_TYPES])
{
	long		pat_len;

	_number = number;

	memset (_name, ' ', Pattern_NAME_LEN);
	_nbr_lines = 1;

	for (int type = 0; type < Pattern_NBR_TYPES; type ++)
	{
		_nbr_tracks [type] = tracks [type];
		pat_len = (long) NOTE_SIZE [type] * _nbr_tracks [type] * _nbr_lines;
		pat_len = MAX (pat_len, 1);
		_data_ptr [type] = (UBYTE *) MALLOC (pat_len);
		memset (_data_ptr [type], 0, pat_len);
	}
}



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

Pattern::~Pattern (void)
{
	for (int type = 0; type < Pattern_NBR_TYPES; type ++)
	{
		if (_data_ptr [type] != NULL)
		{
			FREE (_data_ptr [type]);
			_data_ptr [type] = NULL;
		}
	}
}



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

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

	for (int type = 0; type < Pattern_NBR_TYPES; type ++)
	{
		if (_data_ptr [type] == NULL)
		{
			LOG_printf ("Pattern::check_ok: Error: \"_data_ptr [%d]\" pointer is NULL.\n", type);
			return (-1);
		}
		MCHECK (_data_ptr [type]);
	}

	return (0);
}



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

void	Pattern::self_display (void) const
{

	/*** A faire ***/

}



char	*Pattern::get_pattern_name (char *name_0)
{
	BASE_copy_string (_name, name_0,
							Pattern_NAME_LEN, Pattern_NAME_LEN);
	name_0 [Pattern_NAME_LEN] = 0;

	return (name_0);
}



void	Pattern::set_pattern_name (const char *name_0)
{
	BASE_copy_string (name_0, _name, strlen (name_0), Pattern_NAME_LEN);
}



int	Pattern::get_pattern_nbr_tracks (int track_type)
{
	return (_nbr_tracks [track_type]);
}



void	Pattern::set_pattern_nbr_tracks (int track_type, int nbr_tracks)
{
	_nbr_tracks [track_type] = nbr_tracks;
}



int	Pattern::get_pattern_height (void)
{
	return (_nbr_lines);
}



signed int	Pattern::set_pattern_height (int nbrlines)
{
	int		old_height;
	long		new_len;
	void		*temp_ptr;

	old_height = get_pattern_height ();
	if (nbrlines != old_height)
	{
		for (int track_type = 0; track_type < Pattern_NBR_TYPES; track_type ++)
		{
			/* Change physiquement la taille des pattern */
			new_len =   (long)_nbr_tracks [track_type]
			          * nbrlines * NOTE_SIZE [track_type];
			new_len = MAX (new_len, 1);

			temp_ptr = REALLOC (_data_ptr [track_type], new_len);
			if (temp_ptr == NULL)
			{
				LOG_printf ("Pattern::set_pattern_height: Error: Couldn't change height of %s pattern.\n",
				            MODS_track_type_name_0_ptr [track_type]);
				return (-1);
			}
			_data_ptr [track_type] = (UBYTE *) temp_ptr;

			/* Met a 0 la fin du pattern en cas d'augmentation de la taille */
			if (nbrlines > old_height)
			{
				memset (get_note_adr_pat (track_type, old_height, 0), 0,
				          (long)_nbr_tracks [track_type]
				        * (nbrlines - old_height)
				        * NOTE_SIZE [track_type]);
			}
		}

		_nbr_lines = nbrlines;
	}

	return (0);
}



signed int	Pattern::set_nbr_tracks (int track_type, int nbr_tracks)
{
	int		old_nbr_tracks;
	long		new_len;
	void		*temp_ptr;

	old_nbr_tracks = _nbr_tracks [track_type];

	/* Conversion des donnees pour la reduction */
	if (nbr_tracks < old_nbr_tracks)
	{
		convert_pattern (track_type, old_nbr_tracks, nbr_tracks);
	}
	
	/* Changement de la taille du chunk */
	new_len = (long)nbr_tracks * get_pattern_height () * NOTE_SIZE [track_type];
	new_len = MAX (new_len, 1);

	temp_ptr = REALLOC (_data_ptr [track_type], new_len);
	if (temp_ptr == NULL)
	{
		LOG_printf ("Pattern::set_nbr_tracks: Error: Couldn't change number of tracks of %s pattern.\n",
			         MODS_track_type_name_0_ptr [track_type]);
		return (-1);
	}
	_data_ptr [track_type] = (UBYTE *)temp_ptr;

	/* Conversion des donnees pour l'agrandissement */
	if (nbr_tracks > old_nbr_tracks)
	{
		convert_pattern (track_type, old_nbr_tracks, nbr_tracks);
	}

	set_pattern_nbr_tracks (track_type, nbr_tracks);

	return (0);
}



void	*Pattern::get_note_adr_pat (int track_type, int line, int track)
{
	return (  _data_ptr [track_type]
	        + ((long)line * _nbr_tracks [track_type] + track) * NOTE_SIZE [track_type]);
}



MODS_GT2_SPL_NOTE	*Pattern::get_spl_note_adr_pat (int line, int track)
{
	return ((MODS_GT2_SPL_NOTE *) get_note_adr_pat (Pattern_TYPE_SPL, line, track));
}



MODS_GT2_AIN_NOTE	*Pattern::get_ain_note_adr_pat (int line, int track)
{
	return ((MODS_GT2_AIN_NOTE *) get_note_adr_pat (Pattern_TYPE_AIN, line, track));
}



MODS_GT2_FX_NOTE	*Pattern::get_fx_note_adr_pat (int line, int track)
{
	return ((MODS_GT2_FX_NOTE *) get_note_adr_pat (Pattern_TYPE_FX, line, track));
}



MODS_GT2_MID_NOTE	*Pattern::get_mid_note_adr_pat (int line, int track)
{
	return ((MODS_GT2_MID_NOTE *) get_note_adr_pat (Pattern_TYPE_MID, line, track));
}



UWORD	Pattern::get_track_command (int track_type, int line, int track)
{
	switch (track_type)
	{
	case	Pattern_TYPE_SPL:
		return (get_track_command_spl (line, track));
		break;
	case	Pattern_TYPE_AIN:
		return (get_track_command_ain (line, track));
		break;
	case	Pattern_TYPE_FX:
		return (get_track_command_fx (line, track));
		break;
	default:
		return (0x0000);
		break;
	}
}



UWORD	Pattern::get_track_command_spl (int line, int track)
{
	const MODS_GT2_SPL_NOTE	*note_ptr;
	
	note_ptr = get_spl_note_adr_pat (line, track);
	
	return (((UWORD)note_ptr->fxnum << 8) + note_ptr->fxparam);
}



UWORD	Pattern::get_track_command_ain (int line, int track)
{
	const MODS_GT2_AIN_NOTE	*note_ptr;
	
	note_ptr = get_ain_note_adr_pat (line, track);
	
	return (((UWORD)note_ptr->fxnum << 8) + note_ptr->fxparam);
}



UWORD	Pattern::get_track_command_fx (int line, int track)
{
	const MODS_GT2_FX_NOTE	*note_ptr;
	
	note_ptr = get_fx_note_adr_pat (line, track);
	
	return (((UWORD)note_ptr->fxnum << 8) + note_ptr->fxparam);
}



void	Pattern::set_track_command (int track_type, int line, int track, UWORD command)
{
	switch (track_type)
	{
	case	Pattern_TYPE_SPL:
		set_track_command_spl (line, track, command);
		break;
	case	Pattern_TYPE_AIN:
		set_track_command_ain (line, track, command);
		break;
	case	Pattern_TYPE_FX:
		set_track_command_fx (line, track, command);
		break;
	default:
		break;
	}
}



void	Pattern::set_track_command_spl (int line, int track, UWORD command)
{
	MODS_GT2_SPL_NOTE	*note_ptr;

	note_ptr = get_spl_note_adr_pat (line, track);
	
	note_ptr->fxnum = (UBYTE)(command >> 8);
	note_ptr->fxparam = (UBYTE) command;
}



void	Pattern::set_track_command_ain (int line, int track, UWORD command)
{
	MODS_GT2_AIN_NOTE	*note_ptr;
	
	note_ptr = get_ain_note_adr_pat (line, track);
	
	note_ptr->fxnum = (UBYTE)(command >> 8);
	note_ptr->fxparam = (UBYTE) command;
}



void	Pattern::set_track_command_fx (int line, int track, UWORD command)
{
	MODS_GT2_FX_NOTE	*note_ptr;
	
	note_ptr = get_fx_note_adr_pat (line, track);
	
	note_ptr->fxnum = (UBYTE)(command >> 8);
	note_ptr->fxparam = (UBYTE) command;
}



ULWORD	Pattern::get_fx_command (int line, int track)
{
	const MODS_GT2_FX_NOTE	*note_ptr;
	
	note_ptr = get_fx_note_adr_pat (line, track);

	return (  ((ULWORD)note_ptr->comnum1 << 24)
	        + ((ULWORD)note_ptr->comnum2 << 16)
	        + ((ULWORD)note_ptr->comparam_msb << 8)
	        + note_ptr->comparam_lsb);
}



void	Pattern::set_fx_command (int line, int track, ULWORD command)
{
	MODS_GT2_FX_NOTE	*note_ptr;
	
	note_ptr = get_fx_note_adr_pat (line, track);
	note_ptr->comnum1 = (UBYTE)(command >> 24);
	note_ptr->comnum2 = (UBYTE)(command >> 16);
	note_ptr->comparam_msb = (UBYTE)(command >> 8);
	note_ptr->comparam_lsb = (UBYTE) command;
}



void	Pattern::clear_note (int track_type, int line, int track)
{
	void		*data_ptr;

	data_ptr = get_note_adr_pat (track_type, line, track);
	assert (data_ptr != NULL);
	memset (data_ptr, 0, NOTE_SIZE [track_type]);
}



long	Pattern::estimate_memory_usage () const
{
	long				mem = sizeof (*this);

	for (int track_type = 0; track_type < Pattern_NBR_TYPES; track_type ++)
	{
		const long		nbr_cells = _nbr_lines * _nbr_tracks [track_type];
		mem += nbr_cells * NOTE_SIZE [track_type];
	}

	return (mem);
}



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



void	Pattern::convert_pattern (int track_type, int old_nbr_tracks, int new_nbr_tracks)
{
	int		nbr_lines;
	int		src_inc;
	int		dest_inc;
	BYTE		*pattern_data_ptr;
	BYTE		*src_note_ptr;
	BYTE		*dest_note_ptr;

	/* Les deux fonctions utilisees sont censees fonctionner correctement dans
	   le cadre de cette utilisation (piste 0, ligne 0). */
	nbr_lines = get_pattern_height ();
	pattern_data_ptr = (BYTE *) get_note_adr_pat (track_type, 0, 0);

	src_inc = NOTE_SIZE [track_type] * old_nbr_tracks;
	dest_inc = NOTE_SIZE [track_type] * new_nbr_tracks;

	/* Reduction */
	if (new_nbr_tracks < old_nbr_tracks)
	{
		src_note_ptr = pattern_data_ptr + src_inc;
		dest_note_ptr = pattern_data_ptr + dest_inc;
		for (int line = 1; line < nbr_lines; line ++)
		{
			memmove (dest_note_ptr, src_note_ptr, src_inc);
			src_note_ptr += src_inc;
			dest_note_ptr += dest_inc;
		}
	}

	/* Agrandissement */
	else if (new_nbr_tracks > old_nbr_tracks)
	{
		src_note_ptr = pattern_data_ptr + (long)src_inc * (nbr_lines - 1);
		dest_note_ptr = pattern_data_ptr + (long)dest_inc * (nbr_lines - 1);
		for (int line = nbr_lines - 1; line >= 0; line --)
		{
			memmove (dest_note_ptr, src_note_ptr, src_inc);
			memset (dest_note_ptr + src_inc, 0, dest_inc - src_inc);
			src_note_ptr -= src_inc;
			dest_note_ptr -= dest_inc;
		}
	}
}



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



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