#include <stdio.h>
#include "Channel.h"
#include "Effects.h"
#include "FController.h"
#include "FTables.h"
#include "ISong.h"

/* Returns non-zero if apply */
static int Decode_Slide(Controller* pCtrl, const Channel* pChannel, uint32_t frame_count, int value)
{
	IGNORE(pCtrl);

	int tmp = value & (flag_cmd_slide_frame0N0 << 8);
	value &= ~(flag_cmd_slide_frame0N0 << 8);

	// Tests flags to apply on frame 0/non 0 frames
	if (tmp)
	{
		if (frame_count)
			 tmp &= ~(flag_cmd_slide_frame0 << 8);
		else tmp &= ~(flag_cmd_slide_frameN0 << 8);

		return (tmp) ? value : 0;
	}
	else if (pChannel)
	{
		if (!value) value = pCtrl->slide_speed2;
		pCtrl->slide_speed2 = value;
		// Tests apply on retrig counter
		return (pChannel->flags & ch_flags_retrig_go) ? value : 0;
	}

	return 0;
}

static int Decode_Portamento(Controller* pCtrl, const Channel* pChannel, uint32_t frame_count, int value)
{
	int tmp = value & (flag_cmd_slide_frame0N0 << 8);
	value &= ~(flag_cmd_slide_frame0N0 << 8);
	if (!value) value = pCtrl->portamento_speed;
	if (tmp) value = tmp | (value & ~(flag_cmd_slide_frame0N0 << 8));
	pCtrl->portamento_speed = value;

	return Decode_Slide(pCtrl, pChannel, frame_count, value);
}

static int Decode_Slide_Mem2(Controller* pCtrl, const Channel* pChannel, uint32_t frame_count, int value)
{
	int tmp = value & (flag_cmd_slide_frame0N0 << 8);
	value &= ~(flag_cmd_slide_frame0N0 << 8);
	if (!value) value = pCtrl->slide_speed2;
	if (tmp)
	{
		value = tmp | (value & ~(flag_cmd_slide_frame0N0 << 8));
		pCtrl->slide_speed2 = value;
	}

	return Decode_Slide(pCtrl, pChannel, frame_count, value);
}

static int Decode_Slide_Mem1(Controller* pCtrl, const Channel* pChannel, uint32_t frame_count, int value)
{
	int tmp = value & (flag_cmd_slide_frame0N0 << 8);
	value &= ~(flag_cmd_slide_frame0N0 << 8);
	if (!value) value = pCtrl->slide_speed;
	if (tmp)
	{
		value = tmp | (value & ~(flag_cmd_slide_frame0N0 << 8));
		pCtrl->slide_speed = value;
	}

	return Decode_Slide(pCtrl, pChannel, frame_count, value);
}

static int Decode_Vibrato(Controller* pCtrl, uint32_t frame_count, int value)
{
	int tmp = value & (flag_cmd_vibrato_frame0N0 << 8);
	int stmp = pCtrl->vibrato_depth & (flag_cmd_vibrato_frame0N0 << 8);
	value &= ~tmp;
	if (!value) value = pCtrl->vibrato_depth & ~stmp;

	// apply, check flags to update pos on frame 0/non 0 frames
	if (tmp)
	{
		// Store vibrato params
		pCtrl->vibrato_depth = tmp | value;

		// depth * [-255,255]
		value *= Get_VibratoValue(pCtrl->vibrato_type, pCtrl->vibrato_pos);

		// Update pos?
		if (frame_count || (tmp & (flag_cmd_vibrato_frame0 << 8)))
			pCtrl->vibrato_pos += pCtrl->vibrato_speed;

		// Use
		return value * pCtrl->vibrato_strength;
	}

	// Store vibrato params
	pCtrl->vibrato_depth = stmp | value;

	return 0;
}

static int Decode_Arpeggio(Channel* pChannel, uint32_t frame_count, int value)
{
	int type = value >> 8;
	value &= 0xff;
	if (value)
		pChannel->last_arpeggio = value;
	else
		value = pChannel->last_arpeggio;

	switch(type)
	{
		case cmd_arpeggio_type_NHL:
		{
			int fcount = frame_count % 3;
			if (fcount < 1)
				value = 0;
			else if (fcount == 1)
				value >>= 4;
			else
				value &= 0xf;
		}
		break;
		case cmd_arpeggio_type_mLNH:
		{
			int fcount = frame_count % 3;
			if (fcount == 1)
				value = 0;
			else if (fcount > 1)
				value >>= 4;
			else
				value = -(value & 0xf);
		}
		break;
		case cmd_arpeggio_type_NHNmL:
		{
			if (!(frame_count & 1))
				value = 0;
			else if (!(frame_count & 2))
				value >>= 4;
			else
				value = -(value & 0xf);
		}
		break;
		case cmd_arpeggio_type_LLN:
		default:
		{
			int fcount = frame_count % 3;
			if (fcount > 1)
				value = 0;
			else
				value &= 0xf;
		}
	}

	return value;
}

/*
 * Volume is in range [0-256], effects are in steps of 1.
 */
const uint8_t* VolumeCtrl
	( Controller* pCtrl
	, Channel* pChannel
	, uint32_t frame_count
	, uint32_t count
	, const uint8_t* peffects
	)
{
	int32_t eff, value;

	for(;count > 0; count--, peffects += 2)
	{
		eff = peffects[0];
		value = peffects[1] + ((eff & 0xf) << 8);
		eff &= 0xf0;

Effect:
		switch(eff)
		{
			case cmd_set:
			{
				if (!frame_count)
				{
					pCtrl->value.start =
					pCtrl->value.final = value;
				}
			}
			break;
			case cmd_setfinal:
			{
				if (!frame_count)
				{
					pCtrl->value.final = value;
				}
			}
			break;
			case cmd_up:
			{
				// Apply ?
				value = Decode_Slide(pCtrl, pChannel, frame_count, value);

				pCtrl->value.start += value;
			}
			break;
			case cmd_down:
			{
				// Apply ?
				value = Decode_Slide(pCtrl, pChannel, frame_count, value);

				pCtrl->value.start -= value;
			}
			break;
			case cmd_upmem1:
			{
				pCtrl->last_slide_effect = eff;
				// Apply ?
				value = Decode_Slide_Mem1(pCtrl, pChannel, frame_count, value);

				pCtrl->value.start += value;
			}
			break;
			case cmd_downmem1:
			{
				pCtrl->last_slide_effect = eff;
				// Apply ?
				value = Decode_Slide_Mem1(pCtrl, pChannel, frame_count, value);

				pCtrl->value.start -= value;
			}
			break;
			case cmd_upmem2:
			{
				// Apply ?
				value = Decode_Slide_Mem2(pCtrl, pChannel, frame_count, value);

				pCtrl->value.start += value;
			}
			break;
			case cmd_downmem2:
			{
				// Apply ?
				value = Decode_Slide_Mem2(pCtrl, pChannel, frame_count, value);

				pCtrl->value.start -= value;
			}
			break;
			case cmd_portamento:
			{
				// Apply ?
				value = Decode_Portamento(pCtrl, pChannel, frame_count, value);
				if (!value) break;

				// Check speed <= dist(current value, finish value)
				int tmp = pCtrl->value.final - pCtrl->value.start;
				if (tmp < 0) tmp = -tmp;
				if (value > tmp)
					value = tmp;
				// Add/substract speed to current value
				if (pCtrl->value.final >= pCtrl->value.start)
					 pCtrl->value.start += value;
				else pCtrl->value.start -= value;
				// Auto portamento? Stop if reached value
				if ((pCtrl->value.final == pCtrl->value.start)
				&&  ((pCtrl->autoslide & 0xf0) == cmd_portamento))
					pCtrl->autoslide = cmd_up | (0 << 8);
			}
			break;
			case cmd_vibrato_speed:
			{
				if (value)
					pCtrl->vibrato_speed = value;
			}
			break;
			case cmd_vibrato_depth:
			{
				value = Decode_Vibrato(pCtrl, frame_count, value);
				if (!value) break;

				// round for shift: + half a value
				value += 128;
				pCtrl->value.delta += value >> 8;
			}
			break;
			case cmd_volume_upmem1_exp:
			{
				pCtrl->last_slide_effect = eff;
				// Vol * 2^(x/256), make use of the haftone table 2^(x/(12*256))
				value = Decode_Slide_Mem1(pCtrl, pChannel, frame_count, value);
				if (!value) break;

				value = Convert_ToneToFrac((Note_Central<<8) + value*12);
				pCtrl->value.start = (pCtrl->value.start * value) >> 16;
			}
			break;
			case cmd_volume_downmem1_exp:
			{
				pCtrl->last_slide_effect = eff;
				// Vol / 2^(x/256), make use of the haftone table 2^(x/(12*256))
				value = Decode_Slide_Mem1(pCtrl, pChannel, frame_count, value);
				if (!value) break;

				value = Convert_ToneToFrac((Note_Central<<8) - value*12);
				pCtrl->value.start = (pCtrl->value.start * value) >> 16;
			}
			break;
			case 0xD0: // Tremor
			{
				value &= 0xff; // still used after to check x=y=0
				int ontime = (value & 0xf0) >> 4;
				int offtime = value & 0x0f;

				switch(peffects[0])
				{
					case cmd_volume_tremor_IT:
					case cmd_volume_tremor_ITOld:
					{
						if (value)
						{
							if (peffects[0] == cmd_volume_tremor_ITOld)
							{
								ontime++;
								offtime++;
							}
							pChannel->tremor_ontime = ontime;
							pChannel->tremor_offtime = offtime;
						}
						else
						{
							ontime = pChannel->tremor_ontime;
							offtime = pChannel->tremor_offtime;
						}

						if (pChannel->pnote && (pChannel->pnote->stream.pos != -1))
						{
							// Note: om/off values of 0 behave in the following code
							// in the same way as values 1
							int pos = (pChannel->tremor_pos & 0x7f) - 1;
							int dir = pChannel->tremor_pos & 0x80;
							if (pos <= 0)
							{
								// Was on?
								if (dir)
								{
									// Yes, switch off
									pos = offtime;
									dir = 0;
								}
								else
								{
									// No, switch on
									pos = ontime;
									dir = 0x80;
								}
							}
							pChannel->tremor_pos = pos | dir;
							// Cf. combined with config flag to clear tremor on new row
							if (dir)
								pChannel->flags &= ~ch_flags_zero_volume;
							else
								pChannel->flags |= ch_flags_zero_volume;
						}
					}
					break;
					case cmd_volume_tremor_XM:
					case cmd_volume_tremor_S3M:
					{
						int pos, dir;

						if (value)
						{
							pChannel->tremor_ontime = ontime;
							pChannel->tremor_offtime = offtime;
						}
						else
						{
							ontime = pChannel->tremor_ontime;
							offtime = pChannel->tremor_offtime;
						}

						if (peffects[0] == cmd_volume_tremor_XM)
						{
							// Resync tremor mode with zero flag
							if (pChannel->flags & ch_flags_zero_volume)
								pChannel->tremor_pos &= ~0x80;
							else
								pChannel->tremor_pos |= 0x80;

							// if new note, restart tremor from the beginning
							if (pChannel->flags & ch_flags_triggernote)
							{
								pChannel->tremor_pos = ontime | 0x80;
								pChannel->flags &= ~ch_flags_zero_volume;
							}
							pos = (pChannel->tremor_pos & 0x7f);
							if (frame_count) pos--;
						}
						else
							pos = (pChannel->tremor_pos & 0x7f) - 1;

						dir = pChannel->tremor_pos & 0x80;
						if (pos < 0)
						{
							// Was on?
							if (dir)
							{
								// Yes, switch off
								pos = offtime;
								dir = 0;
								pChannel->flags |= ch_flags_zero_volume;
							}
							else
							{
								// No, switch on
								pos = ontime;
								dir = 0x80;
								pChannel->flags &= ~ch_flags_zero_volume;
							}
						}
						pChannel->tremor_pos = pos | dir;
					}
					break;
					case cmd_volume_tremor_MDL:
					{
						if (value)
						{
							pChannel->tremor_ontime = ontime;
							pChannel->tremor_offtime = offtime;
						}
						else
						{
							ontime = pChannel->tremor_ontime;
							offtime = pChannel->tremor_offtime;
						}
						// No offtime = always on
						if (!offtime)
						{
							pChannel->flags &= ~ch_flags_zero_volume;
						}
						else
						{
							int pos, dir;

							// New row resets counter
							if (frame_count)
							{
								pos = (pChannel->tremor_pos & 0x7f) - 1;
								dir = pChannel->tremor_pos & 0x80;

								if (pos <= 0)
								{
									// Was on?
									if (dir)
									{
										// Yes, switch off
										pos = offtime;
										dir = 0;
									}
									else
									{
										// No, switch on
										pos = ontime;
										dir = 0x80;
									}
								}
							}
							else
							{
								pos = ontime;
								dir = 0x80;
							}

							pChannel->tremor_pos = pos | dir;
							// Cf. combined with config flag to clear tremor on new row
							if (dir)
								pChannel->flags &= ~ch_flags_zero_volume;
							else
								pChannel->flags |= ch_flags_zero_volume;
						}
					}
					break;
				}
			}
			break;
			case 0xE0:
			{
				value &= 0xff;

				switch(peffects[0])
				{
					case cmd_vibrato_type:
					{
						// set new type
						pCtrl->vibrato_type = value;
						// reset position
						pCtrl->vibrato_pos = 0;
					}
					break;
					case cmd_last_slide:
					{
						eff = pCtrl->last_slide_effect;
						value = (value & (flag_cmd_slide_frame0N0 << 4)) << 4;
						// was there already a previous slide?
						if (eff) goto Effect;
					}
					break;
					case cmd_vibrato_strength:
					{
						// set new strength
						pCtrl->vibrato_strength = value;
					}
					break;
				}
			}
			break;
			case 0xF0:
			{
				value &= 0xff;

				switch(peffects[0])
				{
					case cmd_volume_envelope:
					{
						Note* pNote = pChannel->pnote;
						if (!frame_count && pNote)
						{
							if (!pNote->pinstrument
							||  !pNote->pinstrument->VolumeEnvelope.Points)
								value = 0;

							if (value)
								 pNote->envelope_flags |= note_envelope_flag_vol;
							else pNote->envelope_flags &= ~note_envelope_flag_vol;
						}
					}
					break;
					case cmd_volume_envelope_pos:
					{
						Note* pNote = pChannel->pnote;
						if (!frame_count
						&&  pNote
						&&  (pNote->envelope_flags & note_envelope_flag_vol)
						&&  pNote->pinstrument
						&&	pNote->pinstrument->VolumeEnvelope.Points)
						{
							const Envelope* pEnv = &pNote->pinstrument->VolumeEnvelope;
							int i;

							for (i = 0; i < pEnv->Points; i++)
							{
								uint32_t pos = ((pEnv->pTable[i] << 16) >> 16);
								if (pos > value)
									break;
							}
							pNote->vol_envelope_pos = (i-1) + (value << 8);
						}
					}
					break;
					case cmd_volume_cut:
					{
						if (frame_count == value)
						{
							pCtrl->value.start =
							pCtrl->value.final = 0;
						}
					}
					break;
				}
			}
			break;
		}
	}

	return peffects;
}

const uint8_t* VolumeCtrl2
	( Controller* pCtrl
	, Channel* pChannel
	, uint32_t frame_count
	, uint32_t count
	, const uint8_t* peffects
	)
{
	int32_t eff, value;
	IGNORE(pChannel);

	for(;count > 0; count--, peffects += 2)
	{
		eff = peffects[0];
		value = peffects[1] + ((eff & 0xf) << 8);
		eff &= 0xf0;

		switch(eff)
		{
			case (cmd_volume_auto_up & 0xff):
			{
				if (!frame_count)
				{
					uint8_t a = cmd_up + (value >> 8);
					uint8_t b = value;
					pCtrl->autoslide = a + (b << 8);
				}
			}
			break;
			case (cmd_volume_auto_down & 0xff):
			{
				if (!frame_count)
				{
					uint8_t a, b;
					if (value & ~0xc00)
					{
						a = cmd_down + (value >> 8);
						b = value;
					}
					else
					{
						a = cmd_up;
						b = 0; // stop autoslide
					}
					pCtrl->autoslide = a + (b << 8);
				}
			}
			break;
			case (cmd_volume_auto_portamento & 0xff):
			{
				if (!frame_count)
				{
					uint8_t a, b;
					if (value & ~0xc00)
					{
						a = cmd_portamento + (value >> 8);
						b = value;
					}
					else
					{
						a = cmd_up;
						b = 0; // stop autoportamento
					}
					pCtrl->autoslide = a + (b << 8);
				}
			}
			break;
			case (cmd_volume_auto_vibrato  & 0xff):
			{
				if (!frame_count)
				{
					uint8_t a, b;
					if (value & ~0xc00)
					{
						a = cmd_vibrato_depth + (value >> 8);
						b = value;
					}
					else
					{
						a = cmd_up;
						b = 0; // stop autovibrato
					}
					pCtrl->autoslide = a + (b << 8);
				}
			}
			break;
			case (cmd_volume_memslide & 0xff):
			{
				if (value) pCtrl->slide_speed = value;
			}
			break;
		}
	}

	return peffects;
}

/*
 * Panning is in range [0, 256], effects are in step of 1.
 * There is a special value 384 for surround.
 */
const uint8_t* PanningCtrl
	( Controller* pCtrl
	, Channel* pChannel
	, uint32_t frame_count
	, uint32_t count
	, const uint8_t* peffects
	)
{
	int32_t eff, value;

	for(;count > 0; count--, peffects += 2)
	{
		eff = peffects[0];
		value = peffects[1] + ((eff & 0xf) << 8);
		eff &= 0xf0;

Effect:
		switch(eff)
		{
			case cmd_set:
			{
				if (!frame_count)
				{
					pCtrl->value.start =
					pCtrl->value.final = value;
				}
			}
			break;
			case cmd_setfinal:
			{
				if (!frame_count)
				{
					pCtrl->value.final = value;
				}
			}
			break;
			case cmd_up:
			{
				// Apply ?
				value = Decode_Slide(pCtrl, pChannel, frame_count, value);

				pCtrl->value.start += value;
			}
			break;
			case cmd_down:
			{
				// Apply ?
				value = Decode_Slide(pCtrl, pChannel, frame_count, value);

				pCtrl->value.start -= value;
			}
			break;
			case cmd_upmem1:
			{
				pCtrl->last_slide_effect = eff;
				// Apply ?
				value = Decode_Slide_Mem1(pCtrl, pChannel, frame_count, value);
				if (!value) break;

				pCtrl->value.start += value;
			}
			break;
			case cmd_downmem1:
			{
				pCtrl->last_slide_effect = eff;
				// Apply ?
				value = Decode_Slide_Mem1(pCtrl, pChannel, frame_count, value);
				if (!value) break;

				pCtrl->value.start -= value;
			}
			break;
			case cmd_upmem2:
			{
				// Apply ?
				value = Decode_Slide_Mem2(pCtrl, pChannel, frame_count, value);
				if (!value) break;

				pCtrl->value.start += value;
			}
			break;
			case cmd_downmem2:
			{
				// Apply ?
				value = Decode_Slide_Mem2(pCtrl, pChannel, frame_count, value);
				if (!value) break;

				pCtrl->value.start -= value;
			}
			break;
			case cmd_portamento:
			{
				// Apply ?
				value = Decode_Portamento(pCtrl, pChannel, frame_count, value);
				if (!value) break;

				// Check speed <= dist(current value, finish value)
				int tmp = pCtrl->value.final - pCtrl->value.start;
				if (tmp < 0) tmp = -tmp;
				if (value > tmp)
					value = tmp;
				// Add/substract speed to current value
				if (pCtrl->value.final >= pCtrl->value.start)
					 pCtrl->value.start += value;
				else pCtrl->value.start -= value;
				// Auto portamento? Stop if reached value
				if ((pCtrl->value.final == pCtrl->value.start)
				&&  ((pCtrl->autoslide & 0xf0) == cmd_portamento))
					pCtrl->autoslide = cmd_up | (0 << 8);
			}
			break;
			case cmd_vibrato_speed:
			{
				if (value)
					pCtrl->vibrato_speed = value;
			}
			break;
			case cmd_vibrato_depth:
			{
				value = Decode_Vibrato(pCtrl, frame_count, value);
				if (!value) break;

				// round for shift: + half a value
				value += 128;
				pCtrl->value.delta += value >> 8;
			}
			break;
			case 0xE0:
			{
				value &= 0xff;

				switch(peffects[0])
				{
					case cmd_vibrato_type:
					{
						// set new type
						pCtrl->vibrato_type = value;
						// reset position
						pCtrl->vibrato_pos = 0;
					}
					break;
					case cmd_last_slide:
					{
						eff = pCtrl->last_slide_effect;
						value = (value & (flag_cmd_slide_frame0N0 << 4)) << 4;
						// was there already a previous slide?
						if (eff) goto Effect;
					}
					break;
					case cmd_vibrato_strength:
					{
						// set new strength
						pCtrl->vibrato_strength = value;
					}
					break;
				}
			}
			break;
			case 0xF0:
			{
				value &= 0xff;

				switch(peffects[0])
				{
					case cmd_panning_envelope:
					{
						Note* pNote = pChannel->pnote;
						if (!frame_count && pNote)
						{
							if (!pNote->pinstrument
							||  !pNote->pinstrument->PanningEnvelope.Points)
								value = 0;

							if (value)
								 pNote->envelope_flags |= note_envelope_flag_pan;
							else pNote->envelope_flags &= ~note_envelope_flag_pan;
						}
					}
					break;
					case cmd_panning_envelope_pos:
					{
						Note* pNote = pChannel->pnote;
						if (!frame_count
						&&  pNote
						&&  (pNote->envelope_flags & note_envelope_flag_pan)
						&&  pNote->pinstrument
						&&	pNote->pinstrument->PanningEnvelope.Points)
						{
							const Envelope* pEnv = &pNote->pinstrument->PanningEnvelope;
							int i;

							for (i = 0; i < pEnv->Points; i++)
							{
								uint32_t pos = ((pEnv->pTable[i] << 16) >> 16);
								if (pos > value)
									break;
							}
							pNote->pan_envelope_pos = (i-1) + (value << 8);
						}
					}
					break;
				}
			}
			break;
		}
	}

	return peffects;
}

/*
 * Note about controller values and usual effects
 *              controller value            effects value
 * linear       1/256 of semitone           1/64 semitone
 * Amiga        1/256 of Amiga period       1/4 of Amiga period
 * fraction     1/65536 of base frequency   16/65536 of base frequency
 */
const uint8_t* PeriodPitchCtrl
	( Controller* pCtrl
	, Channel* pChannel
	, uint32_t frame_count
	, uint32_t count
	, const uint8_t* peffects
	)
{
	int32_t eff, value;

	for(;count > 0; count--, peffects += 2)
	{
		eff = peffects[0];
		value = peffects[1] + ((eff & 0xf) << 8);
		eff &= 0xf0;

Effect:
		switch(eff)
		{
			case cmd_pitch_setoffset:
			{
				int32_t svalue = value << 20;
				pCtrl->value.offset = svalue >> (20 - 6);
			}
			break;
			case cmd_up:
			{
				// Apply ?
				value = Decode_Slide(pCtrl, pChannel, frame_count, value);

				pCtrl->value.start -= value << 6;
			}
			break;
			case cmd_down:
			{
				// Apply ?
				value = Decode_Slide(pCtrl, pChannel, frame_count, value);

				pCtrl->value.start += value << 6;
			}
			break;
			case cmd_upmem1:
			{
				pCtrl->last_slide_effect = eff;
				// Apply ?
				value = Decode_Slide_Mem1(pCtrl, pChannel, frame_count, value);

				pCtrl->value.start -= value << 6;
			}
			break;
			case cmd_downmem1:
			{
				pCtrl->last_slide_effect = eff;
				// Apply ?
				value = Decode_Slide_Mem1(pCtrl, pChannel, frame_count, value);

				pCtrl->value.start += value << 6;
			}
			break;
			case cmd_upmem2:
			{
				// Apply ?
				value = Decode_Slide_Mem2(pCtrl, pChannel, frame_count, value);

				pCtrl->value.start -= value << 6;
			}
			break;
			case cmd_downmem2:
			{
				// Apply ?
				value = Decode_Slide_Mem2(pCtrl, pChannel, frame_count, value);

				pCtrl->value.start += value << 6;
			}
			break;
			case cmd_portamento:
			{
				// Apply ?
				value = Decode_Portamento(pCtrl, pChannel, frame_count, value) << 6;
				if (!value) break;

				// Check speed <= dist(current value, finish value)
				int tmp = pCtrl->value.final - pCtrl->value.start;
				if (tmp < 0) tmp = -tmp;
				if (value > tmp)
					value = tmp;
				// Add/substract speed to current value
				if (pCtrl->value.final >= pCtrl->value.start)
					 pCtrl->value.start += value;
				else pCtrl->value.start -= value;
				// Auto portamento? Stop if reached value
				if ((pCtrl->value.final == pCtrl->value.start)
				&&  ((pCtrl->autoslide & 0xf0) == cmd_portamento))
					pCtrl->autoslide = cmd_up | (0 << 8);
			}
			break;
			case cmd_vibrato_speed:
			{
				if (value)
					pCtrl->vibrato_speed = value;
			}
			break;
			case cmd_vibrato_depth:
			{
				value = Decode_Vibrato(pCtrl, frame_count, value);
				if (!value) break;

				// round for shift: + half a value
				value += (128>>6);
				pCtrl->value.delta += value >> (8-6);
			}
			break;
			case cmd_pitch_arpeggio:
			{
				value = Decode_Arpeggio(pChannel, frame_count, value);

				// Get multiplication factor from pitch table
				if (value)
				{
					value = Convert_ToneToFrac((Note_Central - value) << 8);
					pCtrl->value.delta += Mul16(pCtrl->value.start, value) - pCtrl->value.start;
				}
			}
			break;
			case 0xE0:
			{
				value &= 0xff;

				switch(peffects[0])
				{
					case cmd_vibrato_type:
					{
						// set new type
						pCtrl->vibrato_type = value;
						// reset position
						pCtrl->vibrato_pos = 0;
					}
					break;
					case cmd_last_slide:
					{
						eff = pCtrl->last_slide_effect;
						value = (value & (flag_cmd_slide_frame0N0 << 4)) << 4;
						// was there already a previous slide?
						if (eff) goto Effect;
					}
					break;
					case cmd_vibrato_strength:
					{
						// set new strength
						pCtrl->vibrato_strength = value;
					}
					break;
				}
			}
			break;
			case 0xF0:
			{
				value &= 0xff;

				switch(peffects[0])
				{
					case cmd_pitch_upmem3:
					{
						if (value)
							pChannel->pitch_mem3 = value;
						else
							value = pChannel->pitch_mem3;

						if (!frame_count)
							pCtrl->value.start -= value << 6;
					}
					break;
					case cmd_pitch_downmem4:
					{
						if (value)
							pChannel->pitch_mem4 = value;
						else
							value = pChannel->pitch_mem4;

						if (!frame_count)
							pCtrl->value.start += value << 6;
					}
					break;
					case cmd_pitch_upmem5:
					{
						if (value)
							pChannel->pitch_mem5 = value;
						else
							value = pChannel->pitch_mem5;

						if (!frame_count)
							pCtrl->value.start -= value << 6;
					}
					break;
					case cmd_pitch_downmem6:
					{
						if (value)
							pChannel->pitch_mem6 = value;
						else
							value = pChannel->pitch_mem6;

						if (!frame_count)
							pCtrl->value.start += value << 6;
					}
					break;
					case cmd_pitch_semitoneup:
					{
						int tmp = value & (flag_cmd_slide_frame0N0 << 4);
						value &= ~(flag_cmd_slide_frame0N0 << 4);
						// Tests flags to apply on frame 0/non 0 frames
						if (frame_count)
							 tmp &= ~(flag_cmd_slide_frame0 << 4);
						else tmp &= ~(flag_cmd_slide_frameN0 << 4);

						if (tmp)
						{
							// Get multiplication factor from pitch table
							value = Convert_ToneToFrac((Note_Central - value) << 8);
							pCtrl->value.start = Mul16(pCtrl->value.start, value);
						}
					}
					break;
					case cmd_pitch_semitonedown:
					{
						int tmp = value & (flag_cmd_slide_frame0N0 << 4);
						value &= ~(flag_cmd_slide_frame0N0 << 4);
						// Tests flags to apply on frame 0/non 0 frames
						if (frame_count)
							 tmp &= ~(flag_cmd_slide_frame0 << 4);
						else tmp &= ~(flag_cmd_slide_frameN0 << 4);

						if (tmp)
						{
							// Get multiplication factor from pitch table
							value = Convert_ToneToFrac((Note_Central + value) << 8);
							pCtrl->value.start = Mul16(pCtrl->value.start, value);
						}
					}
					break;
					case cmd_pitch_semitoneset:
					{
						Note* pNote = pChannel->pnote;
						if (!frame_count && pNote && pNote->psample)
						{
							if (!value) value = pChannel->new_note_id;
							value <<= 8;
							// read note sample pitch (don't use channel info which may differ)
							// convert new pitch to period/freq multiplier
							pCtrl->value.start = Convert_ToneToPeriod(value + pNote->psample->RelTone);
						}
					}
					break;
				}
			}
			break;
		}
	}

	return peffects;
}

const uint8_t* PitchCtrl2
	( Controller* pCtrl
	, Channel* pChannel
	, uint32_t frame_count
	, uint32_t count
	, const uint8_t* peffects
	)
{
	int32_t eff, value;
	IGNORE(pChannel);

	for(;count > 0; count--, peffects += 2)
	{
		eff = peffects[0];
		value = peffects[1] + ((eff & 0xf) << 8);
		eff &= 0xf0;

		switch(eff)
		{
			case (cmd_pitch_auto_up & 0xff):
			{
				if (!frame_count)
				{
					uint8_t a = cmd_up + (value >> 8);
					uint8_t b = value;
					pCtrl->autoslide = a + (b << 8);
				}
			}
			break;
			case (cmd_pitch_auto_down & 0xff):
			{
				if (!frame_count)
				{
					uint8_t a, b;
					if (value & ~0xc00)
					{
						a = cmd_down + (value >> 8);
						b = value;
					}
					else
					{
						a = cmd_up;
						b = 0; // stop autoslide
					}
					pCtrl->autoslide = a + (b << 8);
				}
			}
			break;
			case (cmd_pitch_auto_portamento & 0xff):
			{
				if (!frame_count)
				{
					uint8_t a, b;
					if (value & ~0xc00)
					{
						a = cmd_portamento + (value >> 8);
						b = value;
					}
					else
					{
						a = cmd_up;
						b = 0; // stop autoportamento
					}
					pCtrl->autoslide = a + (b << 8);
				}
			}
			break;
			case (cmd_pitch_auto_vibrato  & 0xff):
			{
				if (!frame_count)
				{
					uint8_t a, b;
					if (value & ~0xc00)
					{
						a = cmd_vibrato_depth + (value >> 8);
						b = value;
					}
					else
					{
						a = cmd_up;
						b = 0; // stop autovibrato
					}
					pCtrl->autoslide = a + (b << 8);
				}
			}
			break;
			case (cmd_pitch_memslide & 0xff):
			{
				if (value) pCtrl->slide_speed = value;
			}
			break;
			case (cmd_pitch_memportamento & 0xff):
			{
				if (value) pCtrl->portamento_speed = value;
			}
			break;
			case 0xF0:
			{
				value &= 0xff;

				switch(peffects[0])
				{
					case (cmd_pitch_envelope & 0xff):
					{
						Note* pNote = pChannel->pnote;
						if (!frame_count && pNote)
						{
							if (!pNote->pinstrument
							||  !pNote->pinstrument->PitchEnvelope.Points)
								value = 0;

							if (value)
								 pNote->envelope_flags |= note_envelope_flag_pitch;
							else pNote->envelope_flags &= ~note_envelope_flag_pitch;
						}
					}
					break;
					case (cmd_pitch_envelope_pos & 0xff):
					{
						Note* pNote = pChannel->pnote;
						if (!frame_count
						&&  pNote
						&&  (pNote->envelope_flags & note_envelope_flag_pitch)
						&&  pNote->pinstrument
						&&	pNote->pinstrument->PitchEnvelope.Points)
						{
							const Envelope* pEnv = &pNote->pinstrument->PitchEnvelope;
							int i;

							for (i = 0; i < pEnv->Points; i++)
							{
								uint32_t pos = ((pEnv->pTable[i] << 16) >> 16);
								if (pos > value)
									break;
							}
							pNote->pitch_envelope_pos = (i-1) + (value << 8);
						}
					}
					break;
					case (cmd_pitch_glissando & 0xff):
					{
						if (value)
							pChannel->flags |= ch_flags_glissando;
						else
							pChannel->flags &= ~ch_flags_glissando;
 					}
 					break;
					case (cmd_filter_setresonance & 0xff):
					{
						Note* pNote = pChannel->pnote;
						if (!frame_count && pNote)
							pNote->resonance = value;
					}
					break;
					case (cmd_filter_setcutoff & 0xff):
					{
						Note* pNote = pChannel->pnote;
						if (!frame_count && pNote)
							pNote->cutoff = value;
					}
					break;
					case (cmd_filter_envelope & 0xff):
					{
						Note* pNote = pChannel->pnote;
						if (!frame_count && pNote)
						{
							if (!pNote->pinstrument
							||  !pNote->pinstrument->FilterEnvelope.Points)
								value = 0;

							if (value)
								 pNote->envelope_flags |= note_envelope_flag_filter;
							else pNote->envelope_flags &= ~note_envelope_flag_filter;
						}
					}
					break;
					case (cmd_filter_envelope_pos & 0xff):
					{
						Note* pNote = pChannel->pnote;
						if (!frame_count
						&&  pNote
						&&  (pNote->envelope_flags & note_envelope_flag_filter)
						&&  pNote->pinstrument
						&&	pNote->pinstrument->FilterEnvelope.Points)
						{
							const Envelope* pEnv = &pNote->pinstrument->FilterEnvelope;
							int i;

							for (i = 0; i < pEnv->Points; i++)
							{
								uint32_t pos = ((pEnv->pTable[i] << 16) >> 16);
								if (pos > value)
									break;
							}
							pNote->filter_envelope_pos = (i-1) + (value << 8);
						}
					}
					break;
				}
			}
			break;
		}
	}

	return peffects;
}

const uint8_t* LinearPitchCtrl
	( Controller* pCtrl
	, Channel* pChannel
	, uint32_t frame_count
	, uint32_t count
	, const uint8_t* peffects
	)
{
	int32_t eff, value;

	for(;count > 0; count--, peffects += 2)
	{
		eff = peffects[0];
		value = peffects[1] + ((eff & 0xf) << 8);
		eff &= 0xf0;

Effect:
		switch(eff)
		{
			case cmd_pitch_setoffset:
			{
				int32_t svalue = value << 20;
				pCtrl->value.offset = svalue >> (20 - 2);
			}
			break;
			case cmd_up:
			{
				// Apply ?
				value = Decode_Slide(pCtrl, pChannel, frame_count, value);

				pCtrl->value.start += value << 2;
			}
			break;
			case cmd_down:
			{
				// Apply ?
				value = Decode_Slide(pCtrl, pChannel, frame_count, value);

				pCtrl->value.start -= value << 2;
			}
			break;
			case cmd_upmem1:
			{
				pCtrl->last_slide_effect = eff;
				// Apply ?
				value = Decode_Slide_Mem1(pCtrl, pChannel, frame_count, value);

				pCtrl->value.start += value << 2;
			}
			break;
			case cmd_downmem1:
			{
				pCtrl->last_slide_effect = eff;
				// Apply ?
				value = Decode_Slide_Mem1(pCtrl, pChannel, frame_count, value);

				pCtrl->value.start -= value << 2;
			}
			break;
			case cmd_upmem2:
			{
				// Apply ?
				value = Decode_Slide_Mem2(pCtrl, pChannel, frame_count, value);

				pCtrl->value.start += value << 2;
			}
			break;
			case cmd_downmem2:
			{
				// Apply ?
				value = Decode_Slide_Mem2(pCtrl, pChannel, frame_count, value);

				pCtrl->value.start -= value << 2;
			}
			break;
			case cmd_portamento:
			{
				// Apply ?
				value = Decode_Portamento(pCtrl, pChannel, frame_count, value) << 2;
				if (!value) break;

				// Check speed <= dist(current value, finish value)
				int tmp = pCtrl->value.final - pCtrl->value.start;
				if (tmp < 0) tmp = -tmp;
				if (value > tmp)
					value = tmp;
				// Add/substract speed to current value
				if (pCtrl->value.final >= pCtrl->value.start)
					 pCtrl->value.start += value;
				else pCtrl->value.start -= value;
				// Auto portamento? Stop if reached value
				if ((pCtrl->value.final == pCtrl->value.start)
				&&  ((pCtrl->autoslide & 0xf0) == cmd_portamento))
					pCtrl->autoslide = cmd_up | (0 << 8);
			}
			break;
			case cmd_vibrato_speed:
			{
				if (value)
					pCtrl->vibrato_speed = value;
			}
			break;
			case cmd_vibrato_depth:
			{
				value = Decode_Vibrato(pCtrl, frame_count, value);
				if (!value) break;

				// round for shift: + half a value
				value += (128>>2);
				pCtrl->value.delta -= value >> (8-2);
			}
			break;
			case cmd_pitch_arpeggio:
			{
				value = Decode_Arpeggio(pChannel, frame_count, value);

				// Get multiplication factor from pitch table
				pCtrl->value.delta += value << 8;
			}
			break;
			case 0xE0:
			{
				value &= 0xff;

				switch(peffects[0])
				{
					case cmd_vibrato_type:
					{
						// set new type
						pCtrl->vibrato_type = value;
						// reset position
						pCtrl->vibrato_pos = 0;
					}
					break;
					case cmd_last_slide:
					{
						eff = pCtrl->last_slide_effect;
						value = (value & (flag_cmd_slide_frame0N0 << 4)) << 4;
						// was there already a previous slide?
						if (eff) goto Effect;
					}
					break;
					case cmd_vibrato_strength:
					{
						// set new strength
						pCtrl->vibrato_strength = value;
					}
					break;
				}
			}
			break;
			case 0xF0:
			{
				value &= 0xff;

				switch(peffects[0])
				{
					case cmd_pitch_upmem3:
					{
						if (value)
							pChannel->pitch_mem3 = value;
						else
							value = pChannel->pitch_mem3;

						if (!frame_count)
							pCtrl->value.start += value << 2;
					}
					break;
					case cmd_pitch_downmem4:
					{
						if (value)
							pChannel->pitch_mem4 = value;
						else
							value = pChannel->pitch_mem4;

						if (!frame_count)
							pCtrl->value.start -= value << 2;
					}
					break;
					case cmd_pitch_upmem5:
					{
						if (value)
							pChannel->pitch_mem5 = value;
						else
							value = pChannel->pitch_mem5;

						if (!frame_count)
							pCtrl->value.start += value << 2;
					}
					break;
					case cmd_pitch_downmem6:
					{
						if (value)
							pChannel->pitch_mem6 = value;
						else
							value = pChannel->pitch_mem6;

						if (!frame_count)
							pCtrl->value.start -= value << 2;
					}
					break;
					case cmd_pitch_semitoneup:
					{
						int tmp = value & (flag_cmd_slide_frame0N0 << 4);
						value &= ~(flag_cmd_slide_frame0N0 << 4);
						// Tests flags to apply on frame 0/non 0 frames
						if (frame_count)
							 tmp &= ~(flag_cmd_slide_frame0 << 4);
						else tmp &= ~(flag_cmd_slide_frameN0 << 4);

						if (tmp)
						{
							pCtrl->value.start += value << 8;
						}
					}
					break;
					case cmd_pitch_semitonedown:
					{
						int tmp = value & (flag_cmd_slide_frame0N0 << 4);
						value &= ~(flag_cmd_slide_frame0N0 << 4);
						// Tests flags to apply on frame 0/non 0 frames
						if (frame_count)
							 tmp &= ~(flag_cmd_slide_frame0 << 4);
						else tmp &= ~(flag_cmd_slide_frameN0 << 4);

						if (tmp)
						{
							pCtrl->value.start -= value << 8;
						}
					}
					break;
					case cmd_pitch_semitoneset:
					{
						Note* pNote = pChannel->pnote;
						if (!frame_count && pNote && pNote->psample)
						{
							if (!value) value = pChannel->new_note_id;
							value <<= 8;
							// read note sample pitch (don't use channel info which may differ)
							// convert new pitch to period/freq multiplier
							pCtrl->value.start = value + pNote->psample->RelTone;
						}
					}
					break;
				}
			}
			break;
		}
	}

	return peffects;
}

const uint8_t* FracPitchCtrl
	( Controller* pCtrl
	, Channel* pChannel
	, uint32_t frame_count
	, uint32_t count
	, const uint8_t* peffects
	)
{
	int32_t eff, value;

	for(;count > 0; count--, peffects += 2)
	{
		eff = peffects[0];
		value = peffects[1] + ((eff & 0xf) << 8);
		eff &= 0xf0;

Effect:
		switch(eff)
		{
			case cmd_pitch_setoffset:
			{
				int32_t svalue = value << 20;
				pCtrl->value.offset = svalue >> (20 - 4);
			}
			break;
			case cmd_up:
			{
				// Apply ?
				value = Decode_Slide(pCtrl, pChannel, frame_count, value);

				pCtrl->value.start += value << 4;
			}
			break;
			case cmd_down:
			{
				// Apply ?
				value = Decode_Slide(pCtrl, pChannel, frame_count, value);

				pCtrl->value.start -= value << 4;
			}
			break;
			case cmd_upmem1:
			{
				pCtrl->last_slide_effect = eff;
				// Apply ?
				value = Decode_Slide_Mem1(pCtrl, pChannel, frame_count, value);

				pCtrl->value.start += value << 4;
			}
			break;
			case cmd_downmem1:
			{
				pCtrl->last_slide_effect = eff;
				// Apply ?
				value = Decode_Slide_Mem1(pCtrl, pChannel, frame_count, value);

				pCtrl->value.start -= value << 4;
			}
			break;
			case cmd_upmem2:
			{
				// Apply ?
				value = Decode_Slide_Mem2(pCtrl, pChannel, frame_count, value);

				pCtrl->value.start += value << 4;
			}
			break;
			case cmd_downmem2:
			{
				// Apply ?
				value = Decode_Slide_Mem2(pCtrl, pChannel, frame_count, value);

				pCtrl->value.start -= value << 4;
			}
			break;
			case cmd_portamento:
			{
				// Apply ?
				value = Decode_Portamento(pCtrl, pChannel, frame_count, value) << 4;
				if (!value) break;

				// Check speed <= dist(current value, finish value)
				int tmp = pCtrl->value.final - pCtrl->value.start;
				if (tmp < 0) tmp = -tmp;
				if (value > tmp)
					value = tmp;
				// Add/substract speed to current value
				if (pCtrl->value.final >= pCtrl->value.start)
					 pCtrl->value.start += value;
				else pCtrl->value.start -= value;
				// Auto portamento? Stop if reached value
				if ((pCtrl->value.final == pCtrl->value.start)
				&&  ((pCtrl->autoslide & 0xf0) == cmd_portamento))
					pCtrl->autoslide = cmd_up | (0 << 8);
			}
			break;
			case cmd_vibrato_speed:
			{
				if (value)
					pCtrl->vibrato_speed = value;
			}
			break;
			case cmd_vibrato_depth:
			{
				value = Decode_Vibrato(pCtrl, frame_count, value);
				if (!value) break;

				// round for shift: + half a value
				value += (128>>4);
				pCtrl->value.delta -= value >> (8-4);
			}
			break;
			case cmd_pitch_arpeggio:
			{
				value = Decode_Arpeggio(pChannel, frame_count, value);

				// Get multiplication factor from pitch table
				if (value)
				{
					// Get multiplication factor from pitch table
					value = Convert_ToneToFrac((Note_Central + value) << 8);
					pCtrl->value.delta += Mul16(pCtrl->value.start, value) - pCtrl->value.start;
				}
			}
			break;
			case 0xE0:
			{
				value &= 0xff;

				switch(peffects[0])
				{
					case cmd_vibrato_type:
					{
						// set new type
						pCtrl->vibrato_type = value;
						// reset position
						pCtrl->vibrato_pos = 0;
					}
					break;
					case cmd_last_slide:
					{
						eff = pCtrl->last_slide_effect;
						value = (value & (flag_cmd_slide_frame0N0 << 4)) << 4;
						// was there already a previous slide?
						if (eff) goto Effect;
					}
					break;
					case cmd_vibrato_strength:
					{
						// set new strength
						pCtrl->vibrato_strength = value;
					}
					break;
				}
			}
			break;
			case 0xF0:
			{
				value &= 0xff;

				switch(peffects[0])
				{
					case cmd_pitch_upmem3:
					{
						if (value)
							pChannel->pitch_mem3 = value;
						else
							value = pChannel->pitch_mem3;

						if (!frame_count)
							pCtrl->value.start += value << 4;
					}
					break;
					case cmd_pitch_downmem4:
					{
						if (value)
							pChannel->pitch_mem4 = value;
						else
							value = pChannel->pitch_mem4;

						if (!frame_count)
							pCtrl->value.start -= value << 4;
					}
					break;
					case cmd_pitch_upmem5:
					{
						if (value)
							pChannel->pitch_mem5 = value;
						else
							value = pChannel->pitch_mem5;

						if (!frame_count)
							pCtrl->value.start += value << 4;
					}
					break;
					case cmd_pitch_downmem6:
					{
						if (value)
							pChannel->pitch_mem6 = value;
						else
							value = pChannel->pitch_mem6;

						if (!frame_count)
							pCtrl->value.start -= value << 4;
					}
					break;
					case cmd_pitch_semitoneup:
					{
						int tmp = value & (flag_cmd_slide_frame0N0 << 4);
						value &= ~(flag_cmd_slide_frame0N0 << 4);
						// Tests flags to apply on frame 0/non 0 frames
						if (frame_count)
							 tmp &= ~(flag_cmd_slide_frame0 << 4);
						else tmp &= ~(flag_cmd_slide_frameN0 << 4);

						if (tmp)
						{
							// Get multiplication factor from pitch table
							value = Convert_ToneToFrac((Note_Central + value) << 8);
							pCtrl->value.start = Mul16(pCtrl->value.start, value);
						}
					}
					break;
					case cmd_pitch_semitonedown:
					{
						int tmp = value & (flag_cmd_slide_frame0N0 << 4);
						value &= ~(flag_cmd_slide_frame0N0 << 4);
						// Tests flags to apply on frame 0/non 0 frames
						if (frame_count)
							 tmp &= ~(flag_cmd_slide_frame0 << 4);
						else tmp &= ~(flag_cmd_slide_frameN0 << 4);

						if (tmp)
						{
							// Get multiplication factor from pitch table
							value = Convert_ToneToFrac((Note_Central - value) << 8);
							pCtrl->value.start = Mul16(pCtrl->value.start, value);
						}
					}
					break;
					case cmd_pitch_semitoneset:
					{
						Note* pNote = pChannel->pnote;
						if (!frame_count && pNote && pNote->psample)
						{
							if (!value) value = pChannel->new_note_id;
							value <<= 8;
							// read note sample pitch (don't use channel info which may differ)
							// convert new pitch to period/freq multiplier
							pCtrl->value.start = Convert_ToneToFrac(value + pNote->psample->RelTone);
						}
					}
					break;
				}
			}
			break;
		}
	}

	return peffects;
}

const uint8_t* TempoCtrl
	( Controller* pCtrl
	, ISong* pSong
	, uint32_t frame_count
	, uint32_t count
	, const uint8_t* peffects
	)
{
	int32_t eff, value;

	for(;count > 0; count--, peffects += 2)
	{
		eff = peffects[0];
		value = peffects[1] + ((eff & 0xf) << 8);
		eff &= 0xf0;

Effect:
		switch(eff)
		{
			case cmd_set:
			{
				if (!frame_count)
				{
					pCtrl->value.start =
					pCtrl->value.final = value;
					pSong->Status &= ~swrk_status_tempoisbpm;
				}
			}
			break;
			case cmd_setfinal:
			{
				if (!frame_count)
				{
					pCtrl->value.final = value;
				}
			}
			break;
			case cmd_up:
			{
				// Apply ?
				value = Decode_Slide(pCtrl, NULL, frame_count, value);

				pCtrl->value.start += value;
			}
			break;
			case cmd_down:
			{
				// Apply ?
				value = Decode_Slide(pCtrl, NULL, frame_count, value);

				pCtrl->value.start -= value;
			}
			break;
			case cmd_upmem1:
			{
				pCtrl->last_slide_effect = eff;
				// Apply ?
				value = Decode_Slide_Mem1(pCtrl, NULL, frame_count, value);

				pCtrl->value.start += value;
			}
			break;
			case cmd_downmem1:
			{
				pCtrl->last_slide_effect = eff;
				// Apply ?
				value = Decode_Slide_Mem1(pCtrl, NULL, frame_count, value);

				pCtrl->value.start -= value;
			}
			break;
			case cmd_upmem2:
			{
				// Apply ?
				value = Decode_Slide_Mem2(pCtrl, NULL, frame_count, value);

				pCtrl->value.start += value;
			}
			break;
			case cmd_downmem2:
			{
				// Apply ?
				value = Decode_Slide_Mem2(pCtrl, NULL, frame_count, value);

				pCtrl->value.start -= value;
			}
			break;
			case cmd_portamento:
			{
				// Apply ?
				value = Decode_Portamento(pCtrl, NULL, frame_count, value);
				if (!value) break;

				// Check speed <= dist(current value, finish value)
				int tmp = pCtrl->value.final - pCtrl->value.start;
				if (tmp < 0) tmp = -tmp;
				if (value > tmp)
					value = tmp;
				// Add/substract speed to current value
				if (pCtrl->value.final >= pCtrl->value.start)
					 pCtrl->value.start += value;
				else pCtrl->value.start -= value;
				// Auto portamento? Stop if reached value
				if ((pCtrl->value.final == pCtrl->value.start)
				&&  ((pCtrl->autoslide & 0xf0) == cmd_portamento))
					pCtrl->autoslide = cmd_up | (0 << 8);
			}
			break;
			case gcmd_tempo_BPM:
			{
				if (!frame_count)
				{
					pCtrl->value.start =
					pCtrl->value.final = value;
					pSong->Status |= swrk_status_tempoisbpm;
				}
			}
			break;
			case gcmd_tempo_TPB:
			{
				if (!frame_count)
				{
					pSong->TicksPerBeat = value;
				}
			}
			break;
			case gcmd_tempo_setlarge:
			{
				if (!frame_count)
				{	pCtrl->value.start =
					pCtrl->value.final = value << 4;
					pSong->Status &= ~swrk_status_tempoisbpm;
				}
			}
			break;
			case gcmd_tempo_setfinall:
			{
				if (!frame_count)
				{
					pCtrl->value.final = value << 4;
				}
			}
			break;
			case 0xE0:
			{
				value &= 0xff;

				if (peffects[0] == cmd_last_slide)
				{
					eff = pCtrl->last_slide_effect;
					value = (value & (flag_cmd_slide_frame0N0 << 4)) << 4;
					// was there already a previous slide?
					if (eff) goto Effect;
				}
			}
			break;
		}
	}

	return peffects;
}

int Controller_CheckValue(Controller* pCtrl)
{
	// check target value [min, max]
	int val = pCtrl->value.start + pCtrl->value.delta + pCtrl->value.offset;
	if (val < pCtrl->value.min)
		val = pCtrl->value.min;
	else if (val > pCtrl->value.max)
		val = pCtrl->value.max;
	// check start value [min, max]
	if (pCtrl->value.start < pCtrl->value.min)
		pCtrl->value.start = pCtrl->value.min;
	else if (pCtrl->value.start > pCtrl->value.max)
		pCtrl->value.start = pCtrl->value.max;

	return val;
}

int PanningCtrl_CheckValue(Controller* pCtrl)
{
	// surround ?
	if (pCtrl->value.start == Panning_Surround)
	{
		if (!pCtrl->value.delta)
			return pCtrl->value.start;

		pCtrl->value.start = 128;
	}

	// check target value [min, max]
	int val = pCtrl->value.start + pCtrl->value.delta + pCtrl->value.offset;
	if (val < pCtrl->value.min)
		val = pCtrl->value.min;
	else if (val > pCtrl->value.max)
		val = pCtrl->value.max;
	// check start value [min, max]
	if (pCtrl->value.start < pCtrl->value.min)
		pCtrl->value.start = pCtrl->value.min;
	else if (pCtrl->value.start > pCtrl->value.max)
		pCtrl->value.start = pCtrl->value.max;

	return val;
}
