?define NumTracks  4
?define DefTempo   6
?define DefBpm     125
?define MidCRate   8448
?define MixBufSize 4096

//--------------------------------------------------------------------------
//               Structures.
//--------------------------------------------------------------------------
struct TrackInfo
{
	word Samples[2];
	word Position;
	word Len;
	word Repeat;
	word RepLen;
	byte Volume;
	byte Error;
	word Period;
	word Pitch;
	word Effect;
	word PortTo;
	byte PortParm;
	byte VibPos;
	byte VibParm;
	byte OldSampOfs;
	word Arp[3];
	word ArpIndex;
};

//--------------------------------------------------------------------------
//               Resources and Data.
//--------------------------------------------------------------------------
byte SinTable={0,25,50,74,98,120,142,162,180,197,212,225,
               236,244,250,254,255,254,250,244,236,225,
               212,197,180,162,142,120,98,74,50,25};
word PeriodTable={856,808,762,720,678,640,604,570,538,508,480,453,
                  428,404,381,360,339,320,302,285,269,254,240,226,
                  214,202,190,180,170,160,151,143,135,127,120,113};

byte OrderPos;
byte Tempo;
byte TempoWait;
byte Bpm;
byte Row;
byte BreakRow;
word BpmSamples;
word BufPtr;
word BufLen;
word BufRep;
dword Note;
struct TrackInfo Tracks[NumTracks];

word PitchTable[857];
byte VolTable[16640];
byte MixBuffer[MixBufSize];

//--------------------------------------------------------------------------
// BeatTrack:  Process the next beat in one track.
//  In:
//    di -  Track number.
//--------------------------------------------------------------------------
void BeatTrack()
{
	DX=DSWORD[#Tracks[0].Effect+DI];
	if(DX!=0){
		IF(DH==0){
//ARPEGGIO:
			BX=DSWORD[#Tracks[0].ArpIndex+DI];
			DSWORD[#Tracks[0].Pitch+DI]=DSWORD[#Tracks[0].Arp+BX+DI];
			BX+=2;
			IF(BX>=6)BX=0;
			DSWORD[#Tracks[0].ArpIndex+DI]=BX;
			return;
		}
		IF(DH==1){
//PORTUP:
			DH=0;
			BX=DSWORD[#Tracks[0].Period+DI]-DX;
			IF(int BX<113)BX=113;
			DSWORD[#Tracks[0].Period+DI]=BX;
			BX+=BX;
			DSWORD[#Tracks[0].Pitch+DI]=PitchTable[BX];
			return;
		}
		IF(DH==2){
//PORTDOWN:
			DH=0;
			BX=DSWORD[#Tracks[0].Period+DI]+DX;
			IF(BX>856)BX=856;
			DSWORD[#Tracks[0].Period+DI]=BX;
			BX+=BX;
			DSWORD[#Tracks[0].Pitch+DI]=PitchTable[BX];
			return;
		}
		IF(DH==5){
//PortSlide:
			VolSlide();
			DL=DSBYTE[#Tracks[0].PortParm+DI];
			GOTO TONEPORT;
		}
		IF(DH==3){
TONEPORT:
			DH=0;
			AX=DSWORD[#Tracks[0].PortTo+DI];
			BX=DSWORD[#Tracks[0].Period+DI];
			asm{
				cmp     BX,AX
				je      NOPORT
				jg      PORTTOUP
			}
			BX+=DX;
			asm{
				cmp     BX,AX
				jle     SETPORT
			}
FIXPORT:
			BX=AX;
			GOTO SETPORT;
PORTTOUP:
			BX-=DX;
			asm{
				cmp     BX,AX
				jl      FIXPORT
			}
SETPORT:
			DSWORD[#Tracks[0].Period+DI]=BX;
			BX+=BX;
			DSWORD[#Tracks[0].Pitch+DI]=PitchTable[BX];
NOPORT:
			return;
		}
		IF(DH==6)||(DH==10){
//VibSlide:
			VolSlide();
			DL=DSBYTE[#Tracks[0].VibParm+DI];
			GOTO VIBRATO;
		}
		IF(DH==4){
VIBRATO:
 			DH=DL;
			DL&=0xF;
			DH>>=4;
			DH<<=2;
			DSBYTE[#Tracks[0].VibPos+DI]+=DH;
			DH=DSBYTE[#Tracks[0].VibPos+DI];
			BL=DH>>2;
			BX&=0x1F;
			AL=SinTable[BX]*DL;
			$rol AX,1
			AL><AH;
			AH&=1;
			$test    DH,DH
			IF(MINUSFLAG)-AX;
			AX+=DSWORD[#Tracks[0].Period+DI];
			BX=AX;
			IF(int BX<113)BX=113;
			IF(int BX>856)BX=856;
			BX+=BX;
			DSWORD[#Tracks[0].Pitch+DI]=PitchTable[BX];
		}
	}
}

void VolSlide()
{
	DH=DL;
	DL&=0xF;
	DH>>=4;
	AL=DSBYTE[#Tracks[0].Volume+DI]-DL;
	$jge     NOLOVOL
	AL=0;
NOLOVOL:
	AL+=DH;
	IF(AL>64)AL=64;
	DSBYTE[#Tracks[0].Volume+DI]=AL;
}

/*--------------------------------------------------------------------------
; GetTrack:   Get the next Note from a pattern.
;  In:
;    di -  Track number.
;    es:si -  Pattern Note Address.
; Out:
;    es:si -  The Next Pattern Note address.
;--------------------------------------------------------------------------*/
void GetTrack()
{
	asm{
		ES:
		lodsw
	}
	AL><AH;
	BL=AH;
	AH&=0xF;
	CX><AX;
	$ES:
	$lodsw
	AL><AH;
	BH=AH;
	AH&=0xF;
	DSWORD[#Tracks[0].Effect+DI]=AX;
	DX><AX;
	BL&=0xF0;
	BH>>=4;
	BL|=BH;
	IF(NOTZEROFLAG){
		BH=0;
		BX=BX-1*2;
		AX=ModInfo.SampVol[BX];
		DSBYTE[#Tracks[0].Volume+DI]=AL;
		DSWORD[#Tracks[0].Samples+DI]=ModInfo.SampOfs[BX];
		DSWORD[#Tracks[0].Samples+2+DI]=ModInfo.SampSeg[BX];
		DSWORD[#Tracks[0].Len+DI]=ModInfo.SampLen[BX];
		DSWORD[#Tracks[0].Repeat+DI]=ModInfo.SampRep[BX];
		DSWORD[#Tracks[0].RepLen+DI]=ModInfo.SampRepLen[BX];
	}
	IF(CX!=0){
		DSWORD[#Tracks[0].PortTo+DI]=CX;
		IF(DH!=3){
			DSWORD[#Tracks[0].Period+DI]=CX;
			BX=CX*2;
			DSWORD[#Tracks[0].Pitch+DI]=PitchTable[BX];
			DSWORD[#Tracks[0].Position+DI]=0;
		}
	}
	if(DX!=0){
		IF(DH==11){
//PosJump
			OrderPos=DL;
			Row=64;
			return;
		}
		IF(DH==12){
//SetVolume();
			IF(DL>64)DL=64;
			DSBYTE[#Tracks[0].Volume+DI]=DL;
			return;
		}
		IF(DH==13){
//Break();
			DH=DL;
			DL&=0xF;
			DH>>=4;
			DH+=DH;
			DL+=DH;
			DH<<=2;
			DL+=DH;
			BreakRow=DL;
			Row=64;
			return;
		}
		IF(DH==15){
//SetSpeed();
			IF(DL!=0){
				IF(DL<=31){
					Tempo=DL;
					TempoWait=DL;
					return;
				}
				Bpm=DL;
				AL=103*DL;
				BL=AH;
				BH=0;
				BpmSamples=MixSpeed/BX;
			}
			return;
		}
		IF(DH==0){
//InitArpeggio;
			DH=DL;
			DL&=0xF;
			DH>>=4;
			CX=36;
			BX=0;
			AX=DSWORD[#Tracks[0].Period+DI];
			loop(CX){
				IF(AX>=PeriodTable[BX])BREAK;
				BX++; BX++;
			}
			DX+=DX;
			DH+=BL;
			DL+=BL;
			BX=PeriodTable[BX];
			BX+=BX;
			DSWORD[#Tracks[0].Arp+DI]=PitchTable[BX];
			BL=DH;
			BH=0;
			BX=PeriodTable[BX];
			BX+=BX;
			DSWORD[#Tracks[0].Arp+2+DI]=PitchTable[BX];
			BL=DL;
			BH=0;
			BX=PeriodTable[BX];
			BX+=BX;
			DSWORD[#Tracks[0].Arp+4+DI]=PitchTable[BX];
			DSWORD[#Tracks[0].ArpIndex+DI]=0;
			return;
		}
		IF(DH==3){
//InitTonePort;
			IF(DL==0)DL=DSBYTE[#Tracks[0].PortParm+DI];
			DSBYTE[#Tracks[0].PortParm+DI]=DL;
			DSWORD[#Tracks[0].Effect+DI]=DX;
			return;
		}
		IF(DH==4){
//InitVibrato;
			AL=DSBYTE[#Tracks[0].VibParm+DI];
			AH=AL;
			AX&=0xF00F;
			$test    DL,0xF
			IF(ZEROFLAG)DL|=AL;
			$test    DL,0xF0;
			IF(ZEROFLAG)DL|=AH;
			DSBYTE[#Tracks[0].VibParm+DI]=DL;
			DSWORD[#Tracks[0].Effect+DI]=DX;
			IF(CX!=0)DSBYTE[#Tracks[0].VibPos+DI]=0;
			return;
		}
		IF(DH==9){
//SampleOfs;
			IF(DL==0)DL=DSBYTE[#Tracks[0].OldSampOfs+DI];
			DSBYTE[#Tracks[0].OldSampOfs+DI]=DL;
			DH=DL;
			DL=0;
			DSWORD[#Tracks[0].Position+DI]=DX;
		}
	}
}

/*--------------------------------------------------------------------------
; UpdateTracks:  Main code to process the next tick to be played.
;--------------------------------------------------------------------------*/
void UpdateTracks()
{
	TempoWait--;
	IF(NOTZEROFLAG){
		FOR(DI=0;DI<sizeof(Tracks);DI+=sizeof(TrackInfo))BeatTrack();
		return;
	}
	TempoWait=Tempo;
	$les     SI,Note;
	$cmp     Row,64
	$jb      NOPATTWRAP
	$les     SI,ModInfo.Patterns
	BL=OrderPos;
	IF(BL>=ModInfo.OrderLen){
		BL=ModInfo.ReStart;	//если BL=0, то будет бесконечный цикл
		OrderPos=BL;
		IF(BL>=ModInfo.OrderLen){
			EndModule=TRUE;
			GOTO NOUPDATE;
		}
	}
	BH=0;
	BL=ModInfo.Order[BX];
	BX<<=10;
	SI+=BX;
	BL=BreakRow;
	Row=BL;
	BH=0;
	BreakRow=BH;
	BX<<=4;
	SI+=BX;
	DSWORD[#Note]=SI;
	DSWORD[#Note+2]=ES;
	OrderPos++;
NOPATTWRAP:
	Row++;
	$cld
	FOR(DI=0;DI<sizeof(Tracks);DI+=sizeof(TrackInfo))GetTrack();
	DSWORD[#Note]=SI;
NOUPDATE:
}

/*--------------------------------------------------------------------------
; MixTrack:  Mixes one track into a CLEAN buffer.
;  In:
;   di -  Track number.
;   ds:si -  Buffer Address.
;    cx   -  Buffer Size.
;--------------------------------------------------------------------------*/
void MixTrack()
{
	$les     DX,DSDWORD[#Tracks[0].Samples+DI];
	BX=DSWORD[#Tracks[0].Position+DI];
	$push DX,DI
	BX+=DX;
	AL=DSBYTE[#Tracks[0].Volume+DI];
	AH=DSBYTE[#Tracks[0].Error+DI];
	IF(DSWORD[#Tracks[0].RepLen+DI]<=2){
		BP=DSWORD[#Tracks[0].Len+DI];
		BP+=DX;
		DX=DSWORD[#Tracks[0].Pitch+DI];
		DI=BX;
		BH=AL;
		AL=DL;
		DL=DH;
		DH=0;
		loop(CX){
			IF(DI>=BP)BREAK;
			BL=ESBYTE[DI];
			BL=VolTable[BX];
			DSBYTE[SI]+=BL;
			SI++;
			AH+=AL;
			$adc     DI,DX
		}
	}
	ELSE{
		BP=DSWORD[#Tracks[0].RepLen+DI];
		BufRep=BP;
		BP+=DSWORD[#Tracks[0].Repeat+DI];
		BP+=DX;
		DX=DSWORD[#Tracks[0].Pitch+DI];
		DI=BX;
		BH=AL;
		AL=DL;
		DL=DH;
		DH=0;
		loop(CX){
			IF(DI>=BP)DI-=BufRep;
			BL=ESBYTE[DI];
			BL=VolTable[BX];
			DSBYTE[SI]+=BL;
			SI++;
			AH+=AL;
			$adc     DI,DX
		}
	}
	BX=DI;
	$pop     DI,DX
	BX-=DX;
	DSWORD[#Tracks[0].Position+DI]=BX;
	DSBYTE[#Tracks[0].Error+DI]=AH;
}

/*--------------------------------------------------------------------------
; GetSamples:  Returns the next chunk of samples to be played.
;  In:
;    ES:DI  - Buffer Address.
;    BX     - Buffer Size.
;--------------------------------------------------------------------------*/
void GetSamples()
{
	$cld
	do{
		IF(BufLen==0){
			$push    BX,DI,ES
			DI=#MixBuffer;
			CX=BpmSamples;
			BufPtr=DI;
			BufLen=CX;
			ES=DS;
			AL=0x80;
			$rep $stosb
			FOR(DI=0;DI<sizeof(Tracks);DI+=sizeof(TrackInfo)){
				SI=BufPtr;
				CX=BufLen;
			  MixTrack();
			}
			UpdateTracks();
			$pop     ES,DI,BX
		}
		CX=BufLen;
		IF(CX>BX)CX=BX;
		SI=BufPtr;
		BufPtr+=CX;
		BufLen-=CX;
		BX-=CX;
		$rep $movsb
	}while(BX!=0);
}

/*--------------------------------------------------------------------------
; StartPlaying: Initializes the Sound System.
;  In:
;   Module Information Resources.
;--------------------------------------------------------------------------*/
void StartPlaying()
{
	$pusha
	$push    DS,ES
	OrderPos=0;
	Tempo=DefTempo;
	TempoWait=DefTempo;
	Bpm=DefBpm;
	Row=64;
	BreakRow=0;
	BX=24*DefBpm/60;
	BpmSamples=MixSpeed/BX;
	Tracks=0;
	AX=0;
	BufPtr=AX;
	BufLen=AX;
	BX=428;
	AX=MidCRate*BX/MixSpeed;
	DH=0;
	DL=AH;
	AH=AL;
	AL=0;
	CX=857;
	BX=0;
	DI=#PitchTable;
	loop(CX){
		$push    AX,DX
		IF(DX<BX)$div     BX
		$stosw
		$pop     DX,AX
		BX++;
	}
	CX=16640;
	BX=CX;
	loop(CX){
		BX--;
		AL=char BL*BH;
		VolTable[BX]=AH;
	}
	$pop     ES,DS
	$popa
}

