#include <stdio.h>
#include "FChannel.h"
#include "FController.h"
#include "FNotes.h"
#include "FTables.h"
#include "Effects.h"

/*
 * Pre-conditions:
 *  We are reaching the end of a frame's time will "ramp" into the next one.
 *  Some channel's information is already pointing to the settings of the new frame
 *  (i.e same as the current one unless we are reaching the first frame of a new row).
 *  These are:
 *  - new_inst_note, a note number or 0 if none specified.
 *  - new_inst_id, an instrument number or 0 if none specified.
 *  - new_effects, the list of effects to apply on the channel.
 *  - delay, a delay in number of franes in the row before the new setting take effect.
 *    Until this delay is reached the settings of the old frame remain in use.
 *
 * Post-conditions:
 *  The channel's information is prepared for the processing of the new frame.
 *  These are:
 *  - new_inst_note, a note number or 0 if none specified.
 *  - new_inst_id, an instrument number or 0 if none specified.
 *  - inst_note, a note number or 0 if none specified.
 *  - inst_id, an instrument number or 0 if none specified.
 *  - pinstrument, a pointer to the instrument definition corresponding to inst_id or 0 if the instrument is invalid.
 *  - smp_note, a note number or 0 if none specified.
 *  - smp_id, an instrument number or 0 if none specified.
 *  - psample, a pointer to the sample definition corresponding to smp_if or 0 if the sample is invalid.
 *  - peffects, the list of effects to apply on the channel.
 *  - flag ch_new_note is set if (new_inst_note, new_inst_id) cause a new note to be triggered,
 *    taking count of possible portamento effect. Note: retrig doesn't affect this flag.
 *  - frame_count, set to 0 when channel becomes effective in row (cf. note delay) and increased at each frame.
 *  - last_retrig, updated if retrig present in effects.
 * The channel's note new_volume is updated (set to 0 for notes that will be cut by the next frame)
 * so that end of frame ramping may fades these notes to zero.
 */

void Channel_PreProcess(ISong* pSong, Channel* pChannel)
{
	uint8_t new_note, new_inst, smp_note, smp_id;

	//---------
	// Step 1.  This is a new song frame, if it reaches the channel delay:
	//          - we switch to the new effects and reset the channel frame counter
	//          - we preprocess the new note/instrument info
	//          else:
	//          - we just increase the channel frame counter

	// Step 1.a Update channel frame counter and effects pointer

	const uint8_t* peffects;
	Note* pNote = pChannel->pnote;

	// Until the global frame count for the row reaches the delay we use the old effects
	// else we switch to the new effects.
	if (pChannel->delay > 0)
	{
		if (((pSong->Hdr.Flags & Song_Flag_RepeatRowNotN0Frames) ? pSong->FrameExtCount : pSong->FrameCount) == pChannel->delay)
		{
			peffects = pChannel->peffects = pChannel->p_new_effects;
			pChannel->frame_count = 0;
		}
		else
		{
			peffects = pChannel->peffects;
			pChannel->frame_count++;
		}
	}
	else
	{
		if (((pSong->Hdr.Flags & Song_Flag_RepeatRowDo0Frame) ? pSong->FrameCount : pSong->FrameExtCount) == 0)
		{
			peffects = pChannel->peffects = pChannel->p_new_effects;
			pChannel->frame_count = 0;
		}
		else
		{
			peffects = pChannel->peffects;
			pChannel->frame_count++;
		}
	}

	// Very first tick ?
	if (!pChannel->frame_count)
	{
		// Step 1.b Read new note/instrument info
		//          Determine if there is going to be a new note
		//          and memorise values going to be used in the future.
		//          - Instrument Nr.
		//          - Note Nr.
		//          Future Note: sample nr, note nr, sample ptr, instrument ptr

		// transfer retrig_curr into retrig_prev
		if (pChannel->flags & ch_flags_retrig_curr)
			 pChannel->flags |= ch_flags_retrig_prev;
		else pChannel->flags &= ~ch_flags_retrig_prev;
		pChannel->flags &= ~ch_flags_retrig_curr;

		// on some trackers, portamento on stopped notes behave as new notes
		if (pSong->Hdr.Flags & Song_Flag_NewNoteOnPortamento)
		{
			if (!pNote || (pNote->stream.pos == -1))
				pChannel->flags &= ~ch_flags_portamento;
		}

		// will trigger a new note?
		pChannel->flags &= ~ch_flags_newnote;
		// yes if new note nr specified
		new_note = pChannel->new_note_id;
		if (new_note)
		{
			if (new_note <= Note_Max)
				pChannel->flags |= ch_flags_newnote;
			else
			{
				// reset note memory
				pChannel->new_note_id =
				pChannel->inst_note =
				new_note = 0;
			}
		}
		else
			new_note = pChannel->inst_note; // use old note nr if not specified

		// new inst ?
		new_inst = pChannel->new_inst_id;
		if (new_inst)
		{
			// On IT, instrument on stopped notes behave as new notes.
			// The same for instrument on playing note with a different instrument.
			if (pSong->Hdr.Flags & Song_Flag_NewNoteOnPortamento)
			{
				if (!pNote || (pNote->stream.pos == -1) || (pNote->inst_id != new_inst))
					pChannel->flags |= ch_flags_newnote;
			}
		}
		else
			new_inst = pChannel->inst_id;

		// no new note if portamento
		if (pChannel->flags & ch_flags_portamento)
			pChannel->flags &= ~ch_flags_newnote;

		const Instrument* pInst = NULL;
		const Sample* pSample = NULL;

		// valid instrument?
		if (pSong->Hdr.Flags & Song_Flag_Instruments)
		{
			if (new_inst > 0)
			{
				if (new_inst <= pSong->Hdr.Instruments)
				{
					// get instrument ptr, sample nr and note nr
					pInst = pSong->Hdr.pInstruments + (new_inst - 1);

					if (pInst->pNotesMap)
					{
						const NoteMap* pn = pInst->pNotesMap + new_note;
						smp_note = pn->Pitch >> 8;
						smp_id = pn->SampleNr;
						if (!smp_note) smp_id = 0;
					}
					else
					{
						smp_note = 0;
						smp_id = 0;
					}
				}
			 	else
			 	{
					smp_note = 0;
					smp_id = 0;
			 	}
			 	if (!smp_id) pSample = pSong->Hdr.pSampleNoMap;
		 	}
		 	else
		 	{
				smp_note = 0;
				smp_id = 0;
		 	}
		}
		else
		{
			smp_note = new_note;
			smp_id = new_inst;
		}

		// valid sample?
		if (smp_id > pSong->Hdr.Samples)
			pSample = pSong->Hdr.pSampleDef;
		else if (smp_id > 0)
			pSample = pSong->Hdr.pSamples + (smp_id - 1);

		// Undefined samples are ignored to mimick S3M behaviour
		if (pSample && (pSample->Type & Smp_Type_Undefined))
		{
			pChannel->flags &= ~ch_flags_newnote;
			pSample = NULL;
		}

		pChannel->smp_note = smp_note;
		pChannel->smp_id = smp_id;
		pChannel->pinstrument = pInst;
		pChannel->psample = pSample;
		pChannel->inst_note = new_note;
		pChannel->inst_id = new_inst;

		// Step 1.c Triggering a note may cut others which must be volume ramped to 0
		if (pChannel->flags & ch_flags_newnote)
			Channel_PreTriggerNote(pChannel, &pSong->Notes);
	}

	//---------
	// Step 2.  PreProcess effects, note triggering or removal effects may force
	//          the volume of certain notes to be ramped to 0.
	//          This means that we are only interested in the sample controller.
	int32_t do_retrig = 0;

	if (peffects)
	{
		uint32_t ctrl, count, eff, value;
		for(;(ctrl = *peffects++) != 0;)
		{
			count = ctrl >> 4;
			ctrl &= 0xf;

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

					switch(eff)
					{
						case cmd_sample_note_retrig:
						{
							if (do_retrig)
								Channel_PreTriggerNote(pChannel, &pSong->Notes);
						}
						break;
						case (cmd_sample_note_action_cut & 0xf0):
						{
							if (((value & 0xff) == pChannel->frame_count)
							&&  pChannel->pnote)
							{
								Note_PreApplyNoteAction
									( pChannel->pnote
									, value >> 8
									);
							}
						}
						break;
						case (cmd_sample_past_note_action_cut & 0xf0):
						{
							if ((value & 0xff) == pChannel->frame_count)
							{
								Notes_PreActOnDuplicateNotes
									( &pSong->Notes
									, value >> 8
									, pChannel
									, 0
									, 0
									, 0
									, Inst_DCT_all
									);
							}
						}
						break;
						case cmd_sample_note_retrig_counter:
						{
							uint32_t type = value >> 8;
							int32_t pos;
							value &= 0xff;

							// Note: last retrig will be set here so that it
							//       will be correct in time for Channel_TriggerNote

							switch(type)
							{
								case cmd_retrig_count_type_MOD:
								{
									// delay, uses memory
									if (!value) value = pChannel->last_retrig;
									else        pChannel->last_retrig = value;
									// apply at tick 0
									if (pChannel->frame_count)
									{
										pos = pChannel->retrig_pos - 1;
										if (pos <= 0) do_retrig = 1;
									}
									else do_retrig = 1;
								}
								break;
								case cmd_retrig_count_type_IT:
								{
									// delay, uses memory
									if (!value) value = pChannel->last_retrig;
									else        pChannel->last_retrig = value;
									pos = pChannel->retrig_pos - 1;
									if (pos <= 0) do_retrig = 1;
								}
								break;
								case cmd_retrig_count_type_XM:
								{
									// delay, uses memory
									if (!value) value = pChannel->last_retrig;
									else        pChannel->last_retrig = value;
									pos = pChannel->retrig_pos_XM + 1;
									if (pos >= value) do_retrig = 1;
 								}
 								break;
								case cmd_retrig_count_type_XMOld:
								{
									// delay, no memory
									// update counter, always reset on 0-frame
									if (pChannel->frame_count)
										 pos = pChannel->retrig_pos_S3M;
									else pos = value;
									pos--;
									if (pos <= 0) do_retrig = 1;
								}
								break;
								case cmd_retrig_count_type_S3M:
								{
									// delay, uses memory
									if (!value) value = pChannel->last_retrig;
									else        pChannel->last_retrig = value;
									// do nothing if delay is 0
									// does not work on repeated rows
									if (value && (pSong->FrameCount == pSong->FrameExtCount))
									{
										// if no retrig effect in previous row, reset counter
										if (pChannel->flags & (ch_flags_retrig_curr+ch_flags_retrig_prev))
											pos = pChannel->retrig_pos_S3M + 1;
										else
											pos = 0;
										if (pos >= value) do_retrig = 1;
									}
								}
								break;
								case cmd_retrig_count_type_PTM:
								{
									// delay, memory
									if (value)  pChannel->last_retrig = value;

									// update counter
									if (value && !pChannel->frame_count)
										 pos = value;
									else
										 pos = pChannel->retrig_pos_S3M;

									pos--;
									if (pos <= 0) do_retrig = 1;
								}
								break;
							}
						}
						break;
					}
				}
			}
			else peffects += (count << 1);
		}
	}
}

/*
 * Pre-conditions:
 *  We are processing a frame, this presues that Channel_PreProcess() as been called.
 *  The channel's information is already pointing to the settings of the frame.
 *  These are:
 *  - new_inst_note, a note number or 0 if none specified.
 *  - new_inst_id, an instrument number or 0 if none specified.
 *  - inst_note, a note number or 0 if none specified.
 *  - inst_id, an instrument number or 0 if none specified.
 *  - pinstrument, a pointer to the instrument definition corresponding to inst_id or 0 if the instrument is invalid.
 *  - smp_note, a note number or 0 if none specified.
 *  - smp_id, an instrument number or 0 if none specified.
 *  - psample, a pointer to the sample definition corresponding to smp_if or 0 if the sample is invalid.
 *  - peffects, the list of effects to apply on the channel.
 *  - flag ch_new_note is set if (new_inst_note, new_inst_id) cause a new note to be triggered,
 *    taking count of possible portamento effect. Note: retrig doesn't affect this flag.
 *  - frame_count, set to 0 when channel becomes effective in row (cf. note delay) and increased at each frame.
 *  - last_retrig, updated if retrig present in effects.
 *
 * Post-conditions:
 *  If a new instrument is specified, channel/note information is updated from it/reset as required.
 *  If a new note or retrig occurs, trigger the new note (and move previous note to virtual/unused pool)
 *  else fix note if instrument was specified (swap sample, reset envelopes, ...).
 *  Processes channel effects (note_volume, channel_volume, panning, pitch, sample_offset,
 *  may move current note to virtual/unsused pool or virtual notes to unused pool).
 *  Transfer final volume/panning/frequency to the channel's note (if present)
 *  so that final notes processing (envelopes and autovibrato) may proceed.
 */
void Channel_Process(ISong* pSong, Channel* pChannel)
{
	PitchCtrl pPitchCtrl;

	pChannel->flags &= ~(ch_flags_triggernote | ch_flags_retrig_go);

	if (pSong->Hdr.Flags & Song_Flag_Linear)
		pPitchCtrl = LinearPitchCtrl;
	else
	{
		if (pSong->Hdr.Flags & Song_Flag_PitchAsFraction)
			pPitchCtrl = FracPitchCtrl;
		else
			pPitchCtrl = PeriodPitchCtrl;
	}
	//---------
	// Step 1.  On channel frame 0, process new note/inst info

	// Very first tick ?
	if (!pChannel->frame_count)
	{
		// Step 1.a Read new note/instrument info

		// Update channel with instrument parameters ?
		if (pSong->Hdr.Flags & Song_Flag_InstNoNote_Ignore)
		{
			// Was there a new note?
			if (pChannel->flags & ch_flags_newnote)
			{
				// Trigger new note
		  		if (pChannel->new_inst_id) Channel_SetInstrument(pChannel, true);
				Channel_TriggerNote(pChannel, &pSong->Notes);
			}
		}
		else
		{
			// Was there a new note?
			if (pChannel->flags & ch_flags_newnote)
			{
				// Trigger new note
				if (pChannel->new_inst_id) Channel_SetInstrument(pChannel, true);
				Channel_TriggerNote(pChannel, &pSong->Notes);
			}
			// Update note with instrument parameters ?
			else if (pChannel->new_inst_id)
			{
				bool bFromNewInst = ((pSong->Hdr.Flags & Song_Flag_InstNoNote_Playing) == 0);
				Channel_SetInstrument(pChannel, bFromNewInst);
				Channel_FixNoteInstrument(pChannel, (pSong->Hdr.Flags & Song_Flag_InstNoNote_Swap) != 0, bFromNewInst);

			}
		}

		// Step 1.b Turn new note number into a pitch
		//          either for a new note or a portamento

		if (pChannel->new_note_id && pChannel->pnote && pChannel->pnote->psample)
		{
			// read note sample pitch (don't use channel info which may differ)
			// convert new pitch to period/freq multiplier
			int32_t pitch;
			if (pChannel->pnote->pinstrument)
				pitch = pChannel->pnote->pinstrument->pNotesMap[pChannel->inst_note].Pitch;
			else
				pitch = (pChannel->smp_note << 8);
			pitch += pChannel->pnote->psample->RelTone;
			if (pSong->Hdr.Flags & Song_Flag_Linear)
				pitch = pitch;
			else if (pSong->Hdr.Flags & Song_Flag_PitchAsFraction)
				pitch = Convert_ToneToFrac(pitch);
			else
				pitch = Convert_ToneToPeriod(pitch);
			if (pChannel->flags & ch_flags_newnote)
				pChannel->note_pitch.value.start = pitch;
			pChannel->note_pitch.value.final = pitch;
		}
	}

	//---------
	// Step 2.  Process effects
	//          Note: as effects may cut note the note ptr
	//                must be reloaded before each use

	// Step 2.a Reset non permanent controllers variations
	pChannel->note_pitch.value.delta =
	pChannel->note_volume.value.delta =
	pChannel->channel_volume.value.delta =
	pChannel->panning.value.delta = 0;

	const uint8_t* peffects = pChannel->peffects;

	if (peffects)
	{
		uint32_t ctrl, count, eff, value;

		// Step 2.b Loop on controllers

		for(;(ctrl = *peffects++) != 0;)
		{
			count = ctrl >> 4;
			ctrl &= 0xf;

			switch(ctrl)
			{
				case 0: // Sample Controller
				{
					// Sample controller
					for(;count > 0; count--, peffects += 2)
					{
						eff = peffects[0];
						value = peffects[1] + ((eff & 0xf) << 8);
						eff &= 0xf0;

						switch(eff)
						{
							case cmd_sample_set_offset:
							{
								if (!pChannel->frame_count)
								{
									uint8_t byte = value >> 8;
									value &= 0xff;

									if (byte <= 3)
									{
										// Store value, big endian unsafe
										uint8_t* p = (uint8_t*) &pChannel->last_set_offset;
										p[byte] = value;
									}
									else
									{
										Note* pNote = pChannel->pnote;
										// Apply offset
										if (pNote && (pNote->stream.pos != -1))
										{
											// reload cf high offset
											int32_t offset = pChannel->last_set_offset;

											// (byte = F offset in sample, byte = E offset in bytes)
											if (byte == 0xe)
											{
												if (pNote->psample->Type & Smp_Type_16bit)
													offset >>= 1;
												if (pNote->psample->Type & Smp_Type_Stereo)
													offset >>= 1;
											}

											if (pNote->inst_flags & note_inst_flag_invert)
											{
												offset = pNote->psample->Size
												       - 1
												       - offset;
												if (offset < 0) offset = 0;
											}
											pNote->stream.pos = offset;
											pNote->stream.fpos = 0;
											Note_SetStreamInfo(pNote);
										}
									}
								}
							}
							break;
							case cmd_sample_set_finetune:
							{
								if (!pChannel->frame_count && pChannel->pnote)
									pChannel->pnote->finetune = ((((int32_t) value) << 20) >> 20);
							}
							break;
							case cmd_sample_note_retrig:
							{
								// volume effect, if 0 use memory
								if (!value)
									value = pChannel->last_retrig_eff;
								else
									pChannel->last_retrig_eff = value;
								// apply?
								if (pChannel->flags & ch_flags_retrig_go)
								{
									// If we already trigger a note in this tick only apply volume effect.
									// The test is present to avoid triggering a 2nd note
									// and pushing the first one as a past note on a virtual channel.
									uint32_t pitch = pChannel->note_pitch.value.start;
									int32_t volume = pChannel->note_volume.value.start;
									if (!(pChannel->flags & ch_flags_triggernote))
										Channel_TriggerNote(pChannel, &pSong->Notes);
									pChannel->note_pitch.value.start = pitch;

									// Do not modify on frame 0 (MDL)?
									if (value & cmd_retrig_volume_skip_frame0)
									{
										if (!pChannel->frame_count)
											value = 0;
										else
											value &= 0xf;
									}
									// Modify note volume
									switch(value)
									{
										case 0:
											// no change
										break;
										case 1: //  -4
										case 2: //  -8
										case 3: // -16
										case 4: // -32
										case 5: // -64
											volume -= (2 << value);
										break;
										case 6: // *2/3
											volume = (volume * 2) / 3;
										break;
										case 7: // *1/2
											volume >>= 1;
										break;
										case 8:
											// no change
										break;
										case 9:  //  4
										case 10: //  8
										case 11: // 16
										case 12: // 32
										case 13: // 64
											volume += (1 << (value - 7));
										break;
										case 14: // *3/2
											volume = (volume * 3) / 2;
										break;
										case 15: // *2
											volume <<= 1;
										break;
									}

									// store without check, check is done later
									pChannel->note_volume.value.start =
									pChannel->note_volume.value.final = volume;
								}
							}
							break;
							case (cmd_sample_note_action_cut & 0xf0):
							{
								uint32_t action = value >> 8;
								value &= 0xff;

								if (value == pChannel->frame_count)
								{
									if (pChannel->pnote)
									{
										Note_ApplyNoteAction
											( pChannel->pnote
											, action
											);
									}
								}
							}
							break;
							case cmd_sample_note_delay:
								// Already processed
							break;
							case (cmd_sample_past_note_action_cut & 0xf0):
							{
								uint32_t action = value >> 8;
								value &= 0xff;

								if (value == pChannel->frame_count)
								{
									Notes_ActOnDuplicateNotes
										( &pSong->Notes
										, action
										, pChannel
										, 0
										, 0
										, 0
										, Inst_DCT_all
										);
								}
							}
							break;
							case (cmd_sample_new_note_action_cut & 0xf0):
							{
								uint32_t action = value >> 8;
								value &= 0xff;

								if ((value == pChannel->frame_count) && pChannel->pnote)
									pChannel->pnote->newnoteaction = action;
							}
							break;
							case (cmd_sample_reverse_play & 0xf0):
							{
								uint32_t mode = value >> 8;
								value &= 0xff;

								switch(mode)
								{
									case (cmd_sample_reverse_play & 0xf):
									{
										// should only be used if new note

										// ignore if no note
										if ((value == pChannel->frame_count)
										&&  (pChannel->pnote && (pChannel->pnote->stream.pos != -1)))
										{
											// mark note as backward
											pChannel->pnote->inst_flags ^= note_inst_flag_invert;
											// Invert? If we trigger a new note, reverse offset
											if (pChannel->flags & ch_flags_triggernote)
											{
												int32_t offset = pChannel->pnote->psample->Size
												               - 1
												               - pChannel->pnote->stream.pos;
												if (offset < 0) offset = 0;
												pChannel->pnote->stream.pos = offset;
												pChannel->pnote->stream.fpos = 0;
											}
											Note_SetStreamInfo(pChannel->pnote);
										}
									}
									break;
									case (cmd_sample_loop_off & 0xf):
									{
										// ignore if no note
										if ((value == pChannel->frame_count)
										&&  (pChannel->pnote && (pChannel->pnote->stream.pos != -1)))
										{
											// force absence of loop
											pChannel->pnote->inst_flags |= note_inst_flag_forceloop;
											pChannel->pnote->inst_flags &= ~(note_inst_flag_loop|note_inst_flag_loop_bidi);
											Note_SetStreamInfo(pChannel->pnote);
										}
									}
									break;
									case (cmd_sample_loop_normal & 0xf):
									{
										// ignore if no note
										if ((value == pChannel->frame_count)
										&&  (pChannel->pnote && (pChannel->pnote->stream.pos != -1)))
										{
											// force absence of loop
											pChannel->pnote->inst_flags |= (note_inst_flag_forceloop|note_inst_flag_loop);
											pChannel->pnote->inst_flags &= ~note_inst_flag_loop_bidi;
											Note_SetStreamInfo(pChannel->pnote);
										}
									}
									break;
									case (cmd_sample_loop_bidi & 0xf):
									{
										// ignore if no note
										if ((value == pChannel->frame_count)
										&&  (pChannel->pnote && (pChannel->pnote->stream.pos != -1)))
										{
											// force absence of loop
											pChannel->pnote->inst_flags |= (note_inst_flag_forceloop|note_inst_flag_loop|note_inst_flag_loop_bidi);
											Note_SetStreamInfo(pChannel->pnote);
										}
									}
									break;
								}
							}
							break;
							case cmd_sample_note_retrig_counter:
							{
								uint32_t type = value >> 8;
								int32_t pos, do_retrig = 0;
								value &= 0xff;

								switch(type)
								{
									// MOD, retrig occurs at tick 0, delay + 1, 2*delay + 1, ... of current row.
									case cmd_retrig_count_type_MOD:
									{
										// delay, memory set in pre-process
										value = pChannel->last_retrig;

										// apply at tick 0
										if (pChannel->frame_count)
										{
											pos = pChannel->retrig_pos - 1;
											if (pos <= 0)
											{
												pos = value;
												do_retrig = 1;
											}
											pChannel->retrig_pos = pos;
										}
										else
										{
											pChannel->retrig_pos = value;
											do_retrig = 1;
										}
									}
									break;
									// IT, pos reset to delay by note else decrease pos.
									//     If delay is 3, retrig will happen after 3, 6, 9 ... ticks.
									//     If delay is set to 11 then 2 on next tick, retrig will happen
									//     after 11, 13, 15, i.e change will take effect only after retrig.
									//     Retrig is not processed if no note is playing.
									case cmd_retrig_count_type_IT:
									{
										// delay, memory set in pre-process
										value = pChannel->last_retrig;

										// do nothing if we already trigger a note
										// do nothing if no note is playing
										if ((!(pChannel->flags & ch_flags_triggernote))
										&&  (pChannel->pnote && (pChannel->pnote->stream.pos != -1)))
										{
											pos = pChannel->retrig_pos - 1;
											if (pos <= 0)
											{
												pos = value;
												do_retrig = 1;
											}
											pChannel->retrig_pos = pos;
										}
									}
									break;
									// XM, pos reset to 0 by note, ALWAYS increase pos, retrig when pos >= delay
									//     If delay is 3, retrig will happen after 2, 5, 8, 11, ... ticks.
									//     x and y have independant memories.
									case cmd_retrig_count_type_XM:
									{
										// delay, memory set in pre-process
										value = pChannel->last_retrig;

										pos = pChannel->retrig_pos_XM + 1;
										if (pos >= value)
										{
											do_retrig = 1;
											pos = 0;
										}
										pChannel->retrig_pos_XM = pos;
 									}
	 								break;
									// XMOld, retrig occurs at tick delay, 2*delay, ... of current row.
									//        There will thus be no retrig if delay >= speed.
									case cmd_retrig_count_type_XMOld:
									{
										// delay, no memory
										// update counter, always reset on 0-frame
										if (pChannel->frame_count)
											 pos = pChannel->retrig_pos_S3M;
										else pos = value;
										pos--;
										if (pos <= 0)
										{
											pos = value;
											do_retrig = 1;
										}
										pChannel->retrig_pos_S3M = pos;
									}
									break;
									case cmd_retrig_count_type_S3M:
									{
										// delay, memory set in pre-process
										value = pChannel->last_retrig;

										// if no retrig effect in previous row, reset counter
										if (pChannel->flags & (ch_flags_retrig_curr+ch_flags_retrig_prev))
											pos = pChannel->retrig_pos_S3M + 1;
										else
											pos = 0;
										pChannel->flags |= ch_flags_retrig_curr;
										pChannel->retrig_pos_S3M = pos;
										// do nothing if delay is 0
										// does not work on repeated rows
										if (value && (pSong->FrameCount == pSong->FrameExtCount) && (pos >= value))
										{
											// counter is reinitialised here
											pChannel->retrig_pos_S3M = 0;
											// do nothing if no note is playing
											if (pChannel->pnote && (pChannel->pnote->stream.pos != -1))
												do_retrig = 1;
										}
									}
									break;
									// PTM, counter set by non-0 effect (on frame 0) and decreased.
									//      retrig occurs after delay-1, 2*delay-1, ....
									//      Nothing else resets the counter.
									case cmd_retrig_count_type_PTM:
									{
										// delay, memory set in pre-process

										// update counter
										if (value && !pChannel->frame_count)
											 pos = value;
										else
											 pos = pChannel->retrig_pos_S3M;

										pos--;
										if (pos <= 0)
										{
											pos = pChannel->last_retrig;
											do_retrig = 1;
										}
										pChannel->retrig_pos_S3M = pos;
									}
									break;
								}

								if (do_retrig) pChannel->flags |= ch_flags_retrig_go;
							}
							break;
							case cmd_sample_gapper:
							{
								// Only if playing note
								Note* pNote = pChannel->pnote;
								if (!pNote) break;

								int ontime = (value & 0xf0) >> 4;
								int offtime = value & 0x0f;

								if (value)
								{
									pChannel->gapper_ontime = ontime;
									pChannel->gapper_offtime = offtime;
								}
								else
								{
									ontime = pChannel->gapper_ontime;
									offtime = pChannel->gapper_offtime;
								}

								// Resync gapper mode with pause flag
								if (pNote->inst_flags & note_inst_flag_paused)
									pChannel->gapper_pos &= ~0x80;
								else
									pChannel->gapper_pos |= 0x80;

								// if new note, restart tremor from the beginning
								if (pChannel->flags & ch_flags_triggernote)
								{
									pChannel->gapper_pos = ontime | 0x80;
									pNote->inst_flags &= ~note_inst_flag_paused;
								}

								int pos = (pChannel->gapper_pos & 0x7f) - 1;
								int dir = pChannel->gapper_pos & 0x80;
								if (pos < 0)
								{
									// Was on?
									if (dir)
									{
										// Yes, switch off
										pos = offtime;
										dir = 0;
										pNote->inst_flags |= note_inst_flag_paused;
									}
									else
									{
										// No, switch on
										pos = ontime;
										dir = 0x80;
										pNote->inst_flags &= ~note_inst_flag_paused;
									}
								}
								pChannel->gapper_pos = pos | dir;
							}
							break;
							case cmd_upcall:
							{
								if (!pChannel->frame_count)
									pChannel->upcall_param = 0x80000000 | value;
							}
							break;
							case cmd_sample_hold:
							{
								if (pChannel->pnote)
									pChannel->pnote->hold = value;
							}
						}
					}
				}
				break;
				case 1: // Pitch Controller
				{
					peffects = pPitchCtrl(&pChannel->note_pitch, pChannel, pChannel->frame_count, count, peffects);
				}
				break;
				case 2: // Note Volume Controller
				{
					peffects = VolumeCtrl(&pChannel->note_volume, pChannel, pChannel->frame_count, count, peffects);
				}
				break;
				case 3: // Channel Volume Controller
				{
					peffects = VolumeCtrl(&pChannel->channel_volume, pChannel, pChannel->frame_count, count, peffects);
				}
				break;
				case 4: // Panning Controller
				{
					peffects = PanningCtrl(&pChannel->panning, pChannel, pChannel->frame_count, count, peffects);
				}
				break;
				case 5: // Pitch Controller 2
				{
					peffects = PitchCtrl2(&pChannel->note_pitch, pChannel, pChannel->frame_count, count, peffects);
				}
				break;
				case 6: // Note Volume Controller 2
				{
					peffects = VolumeCtrl2(&pChannel->note_volume, pChannel, pChannel->frame_count, count, peffects);
				}
				break;
				default:
					peffects += (count << 1);
			}
		}
	}

	//---------
	// Step 2.z Apply auto effects
	if (pChannel->note_pitch.autoslide != (cmd_up | (0 << 8)))
		pPitchCtrl(&pChannel->note_pitch, pChannel, pChannel->frame_count, 1, (uint8_t*) &pChannel->note_pitch.autoslide);
	if (pChannel->note_volume.autoslide != (cmd_up | (0 << 8)))
		VolumeCtrl(&pChannel->note_volume, pChannel, pChannel->frame_count, 1, (uint8_t*) &pChannel->note_volume.autoslide);

	//---------
	// Step 3.  Get controllers state
	//          and update channel and note parameters
	Note* pNote = pChannel->pnote;

	if (pNote && pNote->hold && (pNote->hold <= pNote->frame_count))
		Note_ApplyNoteAction(pChannel->pnote, note_action_cut);

	// Step 3.1 Apply Column Volume Cmd
	pChannel->GVolume = Controller_CheckValue(&pChannel->channel_volume);

	// Step 3.2 Apply Column Panning Cmd
	pChannel->GPanning = PanningCtrl_CheckValue(&pChannel->panning);
	if (pNote) pNote->ch_panning = pChannel->GPanning;

	// Step 3.3 Apply Pitch Cmd
	int32_t pitch = Controller_CheckValue(&pChannel->note_pitch);
	if (pitch < 0) pitch = -pitch; // for 669
	if (pNote)
	{
		if (!(pSong->Hdr.Flags & Song_Flag_Linear))
		{
			if (pSong->Hdr.Flags & Song_Flag_PitchAsFraction)
				pitch = Convert_FracToTone(pitch);
			else
				pitch = Convert_PeriodToTone(pitch);
		}

		if ((pChannel->flags & ch_flags_glissando)
		&&  (pChannel->flags & ch_flags_portamento))
		{
			if (pitch & 0x80)
				pitch += 0x100;
			pitch &= ~0xff;
		}
		pNote->ch_pitch = pitch;
	}

	// Step 3.4 Apply Note Volume Cmd
	int32_t volume = pChannel->NVolume = Controller_CheckValue(&pChannel->note_volume);
	// Silence note?
	if (pChannel->flags & ch_flags_zero_volume)
		volume = 0;
	if (pSong->Hdr.VolumeTranslation)
		volume = pSong->Hdr.VolumeTranslation[volume];

	if (pNote)
		pNote->ch_volume = volume * pChannel->GVolume;
}
