
/*       Convert Hifi AIFF or RAW PCM data to ADPCM format      */
/* Written in 1997 by Christian Buchner. This is Public Domain. */

/* Note: TAB SIZE = 4 */

/* History:

   V1.0: first release

*/

/* Includes */

#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/intuition.h>
#include <clib/alib_stdio_protos.h>
#include <libraries/dos.h>
#include <intuition/intuition.h>
#include <exec/io.h>
#include <exec/memory.h>
#include <devices/scsidisk.h>
#include <string.h>
#include <stdarg.h>

/* Version string */

UBYTE Version[]="$VER: Hifi2ADPCM 1.0 "__AMIGADATE__" by Christian Buchner";


#define MAKE_ID(a,b,c,d)	\
	((ULONG) (a)<<24 | (ULONG) (b)<<16 | (ULONG) (c)<<8 | (ULONG) (d))


/* Constants */

#define BLOCKSIZE 1024			/* size of one audio block */



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

typedef struct
{
	unsigned short	exponent;			// Exponent, bit #15 is sign bit for mantissa
	unsigned long	mantissa[2];		// 64 bit mantissa
} extended;

struct Comm
{
	UWORD Tracks;
	ULONG Unknown1;
	UWORD BitsPerSample;
	extended Frequency;
};


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

BOOL OpenSource(UBYTE *FromFile, ULONG Buffers, ULONG *Freq, ULONG *FreqDiv, ULONG *Tracks, ULONG *BitsPerSample, ULONG *Size);
void CloseSource(void);
void ConvertFile(UBYTE *ToFile, ULONG Buffers, ULONG Freq, ULONG FreqDiv, ULONG Tracks, ULONG BitsPerSample, ULONG Size, ULONG Bits, BOOL Maximize, LONG Boost, BOOL Intel);
LONG SourceMax(UBYTE *src, ULONG inlen, LONG MaxVal, ULONG Got, LONG FreqDiv, LONG Tracks, LONG BitsPerSample, BOOL Intel);
ULONG Source2Octet(UBYTE *src, ULONG inlen, UBYTE *dest, ULONG outlen, ULONG Got, LONG FreqDiv, LONG Tracks, LONG BitsPerSample, LONG MaxVal, LONG ScaleFact, BOOL Intel);
static long extended2long(extended *ex);

extern __asm ULONG CompressADPCM2(		register __a0 UBYTE *Source,
										register __d0 ULONG Length,
										register __a1 UBYTE *Destination,
										register __d1 ULONG JoinCode	);

extern __asm ULONG CompressADPCM3(		register __a0 UBYTE *Source,
										register __d0 ULONG Length,
										register __a1 UBYTE *Destination,
										register __d1 ULONG JoinCode	);

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


/* Library bases */

struct DosLibrary *DOSBase;
struct IntuitionBase *IntuitionBase;


UBYTE *Template="FROM/A,FREQ=FREQUENCY/K/N,TRK=TRACKS/K/N,BPS=BITSPERSAMPLE/K/N,TO/K/A,BITS/K/N,FD=FREQDIV/K/N,BUF=BUFFERS/K/N,MAX=MAXIMIZE/S,BOOST/K/N,INTEL/S";

struct ArgArray
{
	UBYTE *aa_From;
	ULONG *aa_Freq;
	ULONG *aa_Tracks;
	ULONG *aa_BitsPerSample;
	UBYTE *aa_To;
	ULONG *aa_Bits;
	ULONG *aa_FreqDiv;
	ULONG *aa_Buffers;
	ULONG  aa_Maximize;
	ULONG *aa_Boost;
	ULONG  aa_Intel;
};

struct ArgArray AA;
struct RDArgs *RDArgs;
UBYTE ProgName[60];

BYTE *ReadBuffer;
BYTE *OctetBuffer;
UBYTE *ADPCMBuffer;

BPTR SourceStream;
ULONG SourceStart;
ULONG SourceLen;


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


/* void __saveds main() */

LONG __saveds main()
{
	ULONG Bits=2;
	LONG Boost=100;
	ULONG Freq=44100;
	ULONG FreqDiv=1;
	ULONG Tracks=2;
	ULONG BitsPerSample=16;
	ULONG Size=0;
	ULONG Buffers=64;
	BOOL Intel=FALSE;
	
	if (DOSBase=(struct DosLibrary*)OpenLibrary("dos.library",37))
	{
		if (!GetProgramName(ProgName,sizeof(ProgName))) strcpy(ProgName,"Hifi2ADPCM");
		
		if (!(RDArgs=ReadArgs(Template,(LONG *)&AA,0)))
		{
			PrintFault(IoErr(),ProgName);
		}
		else
		{
			if (AA.aa_Bits)    Bits   = *AA.aa_Bits;
			if (AA.aa_Boost)   Boost  = *AA.aa_Boost;
			if (AA.aa_Buffers) Buffers= *AA.aa_Buffers;
							   Intel  =  AA.aa_Intel;
			
			if (Bits != 2 && Bits != 3)
			{
				Printf("Illegal bit number. Use 2 for ADPCM2 and 3 for ADPCM3.\n");
			}
			else
			{
				if (OpenSource(AA.aa_From, Buffers, &Freq, &FreqDiv, &Tracks, &BitsPerSample, &Size))
				{
					if (AA.aa_Freq)   Freq  = *AA.aa_Freq;
					if (AA.aa_FreqDiv) FreqDiv= *AA.aa_FreqDiv;
					if (AA.aa_Tracks) Tracks= *AA.aa_Tracks;
					if (AA.aa_BitsPerSample) BitsPerSample=*AA.aa_BitsPerSample;
					
					Printf("Frequency:     %ld Hz\n",Freq);
					Printf("Freq division: %ld\n",FreqDiv);
					Printf("Tracks:        %ld\n",Tracks);
					Printf("BitsPerSample: %ld\n",BitsPerSample);
					
					if (!(Tracks<3 && ((BitsPerSample==8) || BitsPerSample==16)))
					{
						Printf("Cannot convert a sample with these specs!\n");
					}
					else
					{
						ConvertFile(AA.aa_To, Buffers, Freq, FreqDiv, Tracks, BitsPerSample, Size, Bits, (BOOL)AA.aa_Maximize, Boost, Intel);
					}
					
					CloseSource();
				}
			}
			FreeArgs(RDArgs);
		}
		CloseLibrary(DOSBase);
	}
}


/* Open source file */

BOOL OpenSource(UBYTE *FromFile, ULONG Buffers, ULONG *Freq, ULONG *FreqDiv, ULONG *Tracks, ULONG *BitsPerSample, ULONG *Size)
{
	ULONG ID;
	ULONG Len;
	
	BOOL Break  =FALSE;
	
	BOOL Success=FALSE;
	BOOL Scan   = TRUE;
	BOOL AIFF   =FALSE;
	BOOL Error  =FALSE;
	
	struct Comm COMM;
	
	if (!(SourceStream = Open(FromFile, MODE_OLDFILE)))
	{
		Printf("Error opening source file '%s'\n",FromFile);
	}
	else
	{
		if (Read(SourceStream, &ID, sizeof(ID)) == 4)
		{
			if (ID == MAKE_ID('F','O','R','M'))
			{
				Seek(SourceStream, 4, OFFSET_CURRENT);
				
				if (Read(SourceStream, &ID, sizeof(ID)) == 4)
				{
					if (ID == MAKE_ID('A','I','F','F'))
					{
						AIFF = TRUE;
						
						while(Scan && (!Error) && (!Break))
						{
							if (Read(SourceStream, &ID, sizeof(ID)) != 4) {Error = TRUE; break;}
							if (Read(SourceStream, &Len, sizeof(Len)) != 4) {Error = TRUE; break;}
							
							switch(ID)
							{
								case MAKE_ID('C','O','M','M'):
									// Printf("Found COMM chunk size %ld\n",Len);
									if (Read(SourceStream, &COMM, sizeof(COMM)) != sizeof(COMM)) {Error = TRUE; break;}
									else
									{
										*Tracks = COMM.Tracks;
										*Freq = extended2long(&COMM.Frequency);
										*BitsPerSample = COMM.BitsPerSample;
										
										Seek(SourceStream, Len-sizeof(COMM), OFFSET_CURRENT);
									}
									break;
								
								case MAKE_ID('S','S','N','D'):
									// Printf("Found SSND chunk size %ld\n",Len);
									SourceLen = Len;
									Success = TRUE;
									Scan    = FALSE;
									break;
								
								default:
									//Printf("Found %lc%lc%lc%lc chunk size %ld\n",
									//	(ULONG)((ID>>24)&0xff),
									//	(ULONG)((ID>>16)&0xff),
									//	(ULONG)((ID>> 8)&0xff),
									//	(ULONG)((ID    )&0xff),
									//	Len);
									Seek(SourceStream, Len, OFFSET_CURRENT);
									break;
							}
							if (CheckSignal(SIGBREAKF_CTRL_C))
							{
								PrintFault(ERROR_BREAK,NULL);
								Break=TRUE;
								break;
							}
						}
						
						if (Error)
						{
							Printf("Error, cannot handle this AIFF file!\n");
						}
					}
				}
			}
		}
		
		if ((!AIFF) && (!Break) && (!Error))
		{
			Seek(SourceStream, 0, OFFSET_END);
			SourceLen = Seek(SourceStream, 0, OFFSET_BEGINNING);;
			
			Success = TRUE;
		}
		
		if (Success && (!Break) && (!Error))
		{
			SourceStart = Seek(SourceStream, 0, OFFSET_CURRENT);
		}
		
		if (Break || Error) Success = FALSE;
	}
	return(Success);
}



void CloseSource(void)
{
	if (SourceStream)
	{
		Close(SourceStream);
		SourceStream=NULL;
	}
}




/* Convert source file to ADPCM */

void ConvertFile(UBYTE *ToFile, ULONG Buffers, ULONG Freq, ULONG FreqDiv, ULONG Tracks, ULONG BitsPerSample, ULONG Size, ULONG Bits, BOOL Maximize, LONG Boost, BOOL Intel)
{
	BPTR File;
	ULONG ReadLen;
	ULONG OctetLen;
	ULONG ADPCMLen;
	ULONG JoinCode=0;
	BOOL Break=FALSE;
	LONG MaxVal=32768;
	LONG ScaleFact=Boost*1024/100;
	ULONG Position;
	ULONG Wanted;
	ULONG Got;
	ULONG Octets, ADPCMData;
	
	/* Calculate buffer sizes based on buffer size */
	ReadLen =Buffers*BLOCKSIZE;
	OctetLen=ReadLen/( (BitsPerSample==16 ? 2:1) * Tracks * FreqDiv);
	
	if (Bits==2) ADPCMLen=(OctetLen+3)/4;
	if (Bits==3) ADPCMLen=(OctetLen+7)/8*3;
	
	if (!(File=Open(ToFile,MODE_NEWFILE)))
	{
		Printf("Error cannot open '%s'.\n",ToFile);
	}
	else
	{
		if (Bits==2) Write(File,"ADPCM2", 6);
		if (Bits==3) Write(File,"ADPCM3", 6);
		
		Freq /= FreqDiv;
		Write(File,&Freq, sizeof(Freq));
		
		if (ReadBuffer=AllocVec(ReadLen,MEMF_CLEAR))
		{
			if (OctetBuffer=AllocVec(OctetLen,MEMF_CLEAR))
			{
				if (ADPCMBuffer=AllocVec(ADPCMLen,MEMF_CLEAR))
				{
					if (Maximize)
					{
						MaxVal=0;
						
						for ( Position = 0 ; Position < SourceLen ; Position += Got )
						{
							Printf("\rMaximize run (%02ld%%)",100*(Position/10)/(SourceLen/10));
							
							Wanted = ReadLen;
							if (Wanted > (SourceLen-Position))
								Wanted = SourceLen-Position;
							
							if ( (Got=Read(SourceStream, ReadBuffer, Wanted )) != Wanted)
							{
								Printf("Error, short read! Wanted: %ld, got %ld\n", Wanted, Got);
								break;
							}
							
							if ( Got < ReadLen ) memset(ReadBuffer+Got, 0, ReadLen-Got);
							
							MaxVal = SourceMax(ReadBuffer, ReadLen, MaxVal, Got, FreqDiv, Tracks, BitsPerSample, Intel);
							
							if (CheckSignal(SIGBREAKF_CTRL_C))
							{
								Printf("\n");
								PrintFault(ERROR_BREAK,NULL);
								Break=TRUE;
								break;
							}
						}
						Seek(SourceStream, SourceStart, OFFSET_BEGINNING);
					}
					
					if (!Break)
					{
						if (MaxVal!=32768)
						{
							Printf("\rMaximizing by %ld%%.\n",(ULONG)100*32768/MaxVal);
						}
						
						for ( Position = 0 ; Position < SourceLen ; Position += Got )
						{
							Printf("\rLoading/compressing (%02ld%%)",100*(Position/10)/(SourceLen/10));
							
							Wanted = ReadLen;
							if (Wanted > (SourceLen-Position))
								Wanted = SourceLen-Position;
							
							if ( (Got=Read(SourceStream, ReadBuffer, Wanted )) != Wanted)
							{
								Printf("Error, short read! Wanted: %ld, got %ld\n", Wanted, Got);
								break;
							}
							
							if ( Got < ReadLen ) memset(ReadBuffer+Got, 0, ReadLen-Got);
							
							Octets = Source2Octet(ReadBuffer, ReadLen, OctetBuffer, OctetLen, Got, FreqDiv, Tracks, BitsPerSample, MaxVal, ScaleFact, Intel);
							
							if (Bits==2) JoinCode=CompressADPCM2(OctetBuffer, Octets, ADPCMBuffer, JoinCode);
							if (Bits==3) JoinCode=CompressADPCM3(OctetBuffer, Octets, ADPCMBuffer, JoinCode);
							
							if (Bits==2) ADPCMData=(Octets+3)/4;
							if (Bits==3) ADPCMData=(Octets+7)/8*3;
							
							Write(File,ADPCMBuffer,ADPCMData);
							
							if (CheckSignal(SIGBREAKF_CTRL_C))
							{
								Printf("\n");
								PrintFault(ERROR_BREAK,NULL);
								Break=TRUE;
								break;
							}
						}
					}
					if (!Break) Printf("\rLoading/compressing finished\n");
					
					FreeVec(ADPCMBuffer);
				}
				FreeVec(OctetBuffer);
			}
			FreeVec(ReadBuffer);
		}
		Close(File);
	}
}



LONG SourceMax(UBYTE *src, ULONG inlen, LONG MaxVal, ULONG Got, LONG FreqDiv, LONG Tracks, LONG BitsPerSample, BOOL Intel)
{
	UWORD d,t;
	UBYTE *srcstart = src;
	
	while ( (src < (srcstart+Got)) )
	{
		LONG sample = 0;
		
		if (BitsPerSample==16)
		{
			for ( d=0 ; d<FreqDiv ; d++ )
			{
				for ( t=0 ; t<Tracks ; t++ )
				{
					if (Intel)
						sample += ( (WORD)(*(src+1)<<8) | *(src+0) );
					else
						sample += ( (WORD)(*(src+0)<<8) | *(src+1) );
					src += 2;
				}
			}
			
			if ((Tracks*FreqDiv) != 1) sample /= (Tracks*FreqDiv);
			
			if (sample < 0) sample = -sample;
			if (sample > MaxVal) MaxVal = sample;
		}
		
		if (BitsPerSample==8)
		{
			for ( d=0 ; d<FreqDiv ; d++ )
			{
				for ( t=0 ; t<Tracks ; t++ )
				{
					sample += ( (BYTE)(*src++) );
				}
			}
			
			if ((Tracks*FreqDiv) != 1) sample /= (Tracks*FreqDiv);
			
			if (sample < 0) sample = -sample;
			sample = sample << 8;
			if (sample > MaxVal) MaxVal = sample;
		}
	}
	
	if (src > srcstart+inlen)
	{
		Printf("WARNING: source buffer too small!\n");
	}
	
	return(MaxVal);
}



ULONG Source2Octet(UBYTE *src, ULONG inlen, UBYTE *dest, ULONG outlen, ULONG Got, LONG FreqDiv, LONG Tracks, LONG BitsPerSample, LONG MaxVal, LONG ScaleFact, BOOL Intel)
{
	UWORD d,t;
	ULONG octets = 0;
	UBYTE *srcstart = src;
	UBYTE *deststart = dest;
	
	while ( (src < (srcstart+Got)) && (dest < (deststart+outlen)) )
	{
		LONG sample = 0;
		
		if (BitsPerSample==16)
		{
			for ( d=0 ; d<FreqDiv ; d++ )
			{
				for ( t=0 ; t<Tracks ; t++ )
				{
					if (Intel)
						sample += ( (WORD)(*(src+1)<<8) | *(src+0) );
					else
						sample += ( (WORD)(*(src+0)<<8) | *(src+1) );
					src += 2;
				}
			}
			
			if ((Tracks*FreqDiv) != 1) sample /= (Tracks*FreqDiv);
			
			if (MaxVal != 32768)
			{
				sample = (sample << 15) / MaxVal;
			}
			
			if (ScaleFact != 1024)
			{
				sample = (sample*ScaleFact) >> 10;
			}
			
			sample = sample >> 8;
		}
		
		if (BitsPerSample==8)
		{
			for ( d=0 ; d<FreqDiv ; d++ )
			{
				for ( t=0 ; t<Tracks ; t++ )
				{
					sample += ( (BYTE)(*src++) );
				}
			}
			
			if ((Tracks*FreqDiv) != 1) sample /= (Tracks*FreqDiv);
			
			if (MaxVal != 32768)
			{
				sample = (sample << 15) / MaxVal;
			}
			
			if (ScaleFact != 1024)
			{
				sample = (sample*ScaleFact) >> 10;
			}
		}
		
		if (sample >  127) sample=  127;
		if (sample < -128) sample= -128;
		*dest++ = sample;
		octets++;
	}
	
	if (src > srcstart+inlen)
	{
		Printf("WARNING: source buffer too small!\n");
	}
	
	if (dest > dest+outlen)
	{
		Printf("WARNING: destination buffer too small!\n");
	}
	
	return(octets);
}



/*
**	From: AIFF DataType
**
**	Written by Olaf `Olsen' Barthel <olsen@sourcery.han.de>
*/

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

	/* extended2long(extended *ex):
	 *
	 *	Convert an 80 bit IEEE Standard 754 floating point number
	 *	into an integer value.
	 */

static long extended2long(extended *ex)
{
	unsigned long mantissa;
	short exponent;
	long sign;
	
	// We only need 32 bits precision
	
	mantissa = ex->mantissa[0];
	
	// Is the mantissa positive or negative?
	
	exponent = ex->exponent;
	
	if(exponent & (1 << 15))
		sign = -1;
	else
		sign =	1;
	
	// Unbias the exponent (strip the sign bit; the
	// exponent is 15 bits wide)
	
	exponent = (exponent & ~(1 << 15)) - ((1 << 14) - 1);
	
	// If the exponent is negative, set the mantissa to zero.
	// We cannot represent integer values >0 and <1.
	
	if(exponent < 0)
		mantissa = 0;
	else
	{
		// We used only the upper 32 bits of the mantissa,
		// which is what we have to make up for. Subtracting
		// 31 from the exponent is actually dividing by
		// 2^32 (i.e. x / (1L<<31)).
		
		exponent -= 31;
		
		// If the exponent is not negative, then the value
		// the number represents will be larger than the
		// original 64 bits of the mantissa would hold.
		
		if(exponent > 0)
			mantissa = (1L << 31) - 1;	// == MAXINT
		else
			mantissa >>= -exponent;		// Keep the integer part of the number
	}
	
	// That's all...
	
	return(sign * (long)mantissa);
}
