
/*
**
**	aiff.datatype v41
**	© 1998 by Stephan Rupprecht
**	all rights reserved
**
**	based on aiffdt by Olaf Barthel
**	ulong2extended, extended2long and the
**	aiff definitions are "stolen" from that
**	datatype.
**
*/

#define __NOLIBBASE__
#define CLIB_ALIB_PROTOS_H
#include <exec/memory.h>
#include <exec/execbase.h>
#include <datatypes/soundclass.h>
#include <datatypes/soundclassext.h>
#include <libraries/iffparse.h>

#ifdef __GNUC__
#include <inline/dos.h>
#include <inline/exec.h>
#include <inline/iffparse.h>
#include <inline/datatypes.h>
#include <inline/utility.h>
#include <inline/intuition.h>
#else
#include <pragma/dos_lib.h>
#include <pragma/exec_lib.h>
#include <pragma/iffparse_lib.h>
#include <pragma/datatypes_lib.h>
#include <pragma/utility_lib.h>
#include <pragma/intuition_lib.h>

#define __regargs
#define REG( x, a )	register __## x a

#endif

#include <clib/alib_protos.h>

#include "classbase.h"
#ifdef __GNUC__
#include "BoopsiStubs.h"

#define DOSBase		cb->cb_DOSBase
#define SysBase		cb->cb_SysBase
#define DataTypesBase	cb->cb_DataTypesBase
#define IFFParseBase	cb->cb_IFFParseBase
#define SuperClassBase	cb->cb_SuperClassBase
#define UtilityBase		cb->cb_UtilityBase
#define IntuitionBase	cb->cb_IntuitionBase
#endif

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

// Audio Interchange Format chunk data

#define ID_AIFF		MAKE_ID('A', 'I', 'F', 'F')
#define ID_AIFC		MAKE_ID('A', 'I', 'F', 'C')
#define ID_COMM 	MAKE_ID('C', 'O', 'M', 'M')
#define ID_SSND 	MAKE_ID('S', 'S', 'N', 'D')
#define ID_FVER		MAKE_ID('F', 'V', 'E', 'R')
#define ID_ADP4		MAKE_ID('A', 'D', 'P', '4') // ADPCM4

// "COMM" chunk header

typedef struct {
	WORD		numChannels;		// Number of channels
	ULONG		numSampleFrames;	// Number of sample frames
	WORD		sampleSize;		// Number of bits per sample point
	extended	sampleRate;		// Replay rate in samples per second
} CommonChunk;

typedef struct {
	WORD		numChannels;		// Number of channels
	ULONG		numSampleFrames;	// Number of sample frames
	WORD		sampleSize;		// Number of bits per sample point
	extended	sampleRate;		// Replay rate in samples per second
	ULONG		compressionType;	// Compression type
	// pascal string
	UBYTE		compressionName[0];
} ExtCommonChunk;

#define NO_COMPRESSION	MAKE_ID('N', 'O', 'N', 'E') // No sound compression

// "SSND" chunk header

typedef struct {
	ULONG	offset,				// Offset to sound data, for block alignment
			blockSize;			// Size of block data is aligned to
} SampledSoundHeader;

typedef struct {
	long			timestamp;			// Format version creation date
} FormatVersionHeader;

#define AIFCVersion1	0xA2805140

struct ImaState {
   WORD	index;			// Index into step size table
   LONG previousValue;	// Most recent sample value
};

struct ImaAdpcmApple {
	struct ClassBase *cb;
	struct IFFHandle *iff;
	WORD			channels;
	struct ImaState	state[2];
	WORD 			samples[2][64];
	WORD 		   *samplePtr[2];
	ULONG			samplesRemaining;
};

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

LONG __regargs GetObjectData(Class *, Object *, struct TagItem *);
LONG __regargs WriteObjectData(Class *, Object *, struct dtWrite *);
ULONG Dispatch(REG(a0,Class *cl), REG(a2,Object *o), REG(a1,Msg msg));

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

const LONG ifferr2doserr[] =
{
	0L,						// EOF
	0L,                         			// EOC
	DTERROR_INVALID_DATA,	/* No lexical scope.                             */
	ERROR_NO_FREE_STORE,	/* Insufficient memory.                          */
	ERROR_SEEK_ERROR,		/* Stream read error.                            */
	ERROR_SEEK_ERROR,		/* Stream write error.                           */
	ERROR_SEEK_ERROR,		/* Stream seek error.                            */
	DTERROR_INVALID_DATA,       /* File is corrupt.                              */
	DTERROR_INVALID_DATA,       /* IFF syntax error.                             */
	ERROR_OBJECT_WRONG_TYPE,/* Not an IFF file.                              */
	ERROR_REQUIRED_ARG_MISSING,/* Required call-back hook missing.              */
	0xDEADDEAD                  	/* Return to client. You should never see this ! */
};

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

void __regargs ulong2extended( ULONG value, extended *ex )
{
	ex->exponent = 31 + 16383;

	while( ( value & 0x80000000 ) == 0 )
	{
		value <<= 1;

		ex->exponent--;
	}

	ex->mantissa[0] = value;
	ex->mantissa[1] = 0;
}

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

LONG __regargs extended2long( const extended *ex )
{
	ULONG	mantissa;
	WORD	exponent;
	LONG	sign;
	
	mantissa = ex->mantissa[0];
	exponent = ex->exponent;
	
	sign = (exponent & (1 << 15)) ? -1 : 1;
	
	exponent = (exponent & ~(1 << 15)) - ((1 << 14) - 1);
	
	if(exponent < 0)
	{
		mantissa = 0;
	}
	else
	{
		exponent -= 31;
		mantissa = (ULONG) ( (exponent > 0) ? MAXINT : mantissa >> -exponent );
	}
	
	return( sign * (LONG) mantissa );
}

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

ULONG Dispatch(REG(a0,Class *cl), REG(a2,Object *o), REG(a1,Msg msg))
{
	ULONG  retval;
//	struct ClassBase *cb = (struct ClassBase *)cl->cl_UserData;

	if( msg->MethodID == DTM_WRITE && ((struct dtWrite *)msg)->dtw_Mode == DTWM_RAW )
	{
		retval = WriteObjectData( cl, o, (struct dtWrite *) msg );
	}	
	else if((retval = (ULONG) DoSuperMethodA(cl, o, msg)) && (msg->MethodID == OM_NEW))
	{	
		if(GetObjectData(cl, (Object *) retval, ((struct opSet *)msg)->ops_AttrList))
		{
			CoerceMethod(cl, (Object *)retval, OM_DISPOSE);
			retval = 0L;
		}
	}

	return(retval);
}

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

void setdtattrs( struct ClassBase *cb, Object *o, ULONG tags, ... )
{
	SetDTAttrsA( o, NULL, NULL, (struct TagItem *) &tags );
}

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

LONG __regargs GetObjectData(Class *cl, Object *o, struct TagItem *AttrList)
{
	struct ClassBase		*cb = (struct ClassBase *)cl->cl_UserData;
	struct VoiceHeader	*vhdr;
	struct IFFHandle		*iff = NULL;
	ULONG				SrcType = 0L;
	LONG				err = 0L;
	
	/* get things we need */
	GetDTAttrs(o,
		DTA_SourceType, (ULONG)&SrcType,
		DTA_Handle, (ULONG)&iff,
		SDTA_VoiceHeader,(ULONG)&vhdr,
		TAG_DONE );
	
	if( ( SrcType == DTST_FILE ) || ( SrcType == DTST_CLIPBOARD ) )
	{
		if( ( iff ) && ( vhdr ) )
	 	{
	 		STATIC LONG Stops[] =
			{
				ID_AIFF, ID_COMM,
				ID_AIFF, ID_SSND,
				ID_AIFF, ID_ADP4,
		
				ID_AIFC, ID_FVER,
				ID_AIFC, ID_COMM,
				ID_AIFC, ID_SSND
			};
	 		ULONG	SampleType, SampleLength, Frequency;
	 		APTR	Sample = NULL;
			
			err = StopChunks( iff, Stops, 5L );			
			
			while( ! err && ! Sample)
			{	
				if( ! ( err = ParseIFF( iff, IFFPARSE_SCAN ) ) )
				{
					struct ContextNode	*cn;
					
					cn = CurrentChunk( iff );
					
					switch ( cn->cn_ID )
					{
						case ID_FVER:
						{
							FormatVersionHeader	fvh;

							if( ( err = ReadChunkBytes( iff, &fvh, sizeof( fvh ) ) ) == sizeof( fvh ) )
							{
								err = 0L;
								
								if( fvh.timestamp != AIFCVersion1 )
								{
									err = ERROR_NOT_IMPLEMENTED;
								}
							}
						}
						break;
						
						case ID_COMM:
						{
							ExtCommonChunk	ecc;
							ULONG			read;

							read = ( cn->cn_Type == ID_AIFF ) ? sizeof( CommonChunk ) : sizeof( ExtCommonChunk );
							
							if( ( err = ReadChunkBytes( iff, &ecc, read ) ) == read )
							{
								err = 0L;
								
								if( ecc.numChannels > 2 || ( ecc.sampleSize != 8 && ecc.sampleSize != 16 ) )
								{
									err = ERROR_OBJECT_WRONG_TYPE;
								}
								else
								{
									if( cn->cn_Type == ID_AIFC && ecc.compressionType != NO_COMPRESSION )
									{
										err = DTERROR_UNKNOWN_COMPRESSION;
									}
									else
									{
										SampleType = ecc.numChannels - 1; if( ecc.sampleSize != 8 ) SampleType += 2;
										Frequency = extended2long( &ecc.sampleRate );
										SampleLength = ecc.numSampleFrames;
									}
								}
							}
						}
						break;
						
						case ID_ADP4:
						{
							extern ULONG DecodeImaAdpcmApple( struct ImaAdpcmApple *hdl, WORD *outBuff, ULONG wanted );
							extern void DecompressImaAdpcmApple( struct ClassBase *cb, struct IFFHandle *iff, struct ImaAdpcmApple *hdl, WORD channels );
							
							struct ImaAdpcmApple	hdl;			
							
							DecompressImaAdpcmApple( cb, iff, &hdl, 1 );
							
							if( Sample = AllocVec( SampleLength * sizeof( WORD ) * 1, MEMF_PUBLIC ) )
							{				
								DecodeImaAdpcmApple( &hdl, (WORD *) Sample, SampleLength );
									
								vhdr->vh_OneShotHiSamples = SampleLength;
								vhdr->vh_SamplesPerSec = Frequency;
								vhdr->vh_Octaves = 1L;
								vhdr->vh_Compression = CMP_NONE;
								vhdr->vh_Volume = 64L; // a very common mistake should be 0x10000
											
								setdtattrs( cb, o,							
									SDTA_Sample, (ULONG) Sample,
									SDTA_SampleLength, SampleLength,
									SDTA_Frequency, Frequency,
									SDTA_SampleType, SampleType,
									// SDTA_Volume, 64L, dont overide the users prefered volume
									SDTA_Cycles, 1L,
									TAG_DONE );
											
								err = 0L;
							}
							else
							{
								err = ERROR_NO_FREE_STORE;
							}
						}													
						break;
											
						case ID_SSND:
						{
							SampledSoundHeader	ssh;

							if( ( err = ReadChunkBytes( iff, &ssh, sizeof( ssh ) ) ) == sizeof( ssh ) )
							{
								ULONG	size = cn->cn_Size - ssh.offset - 8L;
								
								err = 0L;
								
								if( Sample = AllocVec( size, MEMF_PUBLIC ) )
								{
									if( ssh.offset )
									{
										ULONG	skip = ssh.offset;
										
										while( skip )
										{
											LONG	read;
											
											read = ( skip > size ) ? size : skip;
											
											if( ( err = ReadChunkBytes( iff, Sample, read ) ) != read )
											{
												break;
											}
											
											skip -= read;
											err = 0L;
										}
									}
										
									if( ! err )
									{
										if( ( err = ReadChunkBytes( iff, Sample, size ) ) == size )
										{
											STRPTR	name = NULL;
											
											if( GetDTAttrs( o, DTA_Name, (ULONG) &name, TAG_DONE ) && name )
											{
												name = FilePart( name );
											}
											
											vhdr->vh_OneShotHiSamples = SampleLength;
											vhdr->vh_SamplesPerSec = Frequency;
											vhdr->vh_Octaves = 1L;
											vhdr->vh_Compression = CMP_NONE;
											vhdr->vh_Volume = 64L; // a very common mistake should be 0x10000
											
											setdtattrs( cb, o,
												DTA_ObjName, (ULONG) name,
												SDTA_Sample, (ULONG) Sample,
												SDTA_SampleLength, SampleLength,
												SDTA_Frequency, Frequency,
												SDTA_SampleType, SampleType,
												// SDTA_Volume, 64L, dont overide the users prefered volume
												SDTA_Cycles, 1L,
												TAG_DONE );
											
											err = 0L;
										}
									}
								
									if( err )
									{
										FreeVec( Sample );
									}
								}
								else 
								{
									err = ERROR_NO_FREE_STORE;
								}
							}
						}
						break;
					}
				}
			}
		}
		else
	  	{
  			err = ERROR_REQUIRED_ARG_MISSING;
  		}
	}
  	else if( SrcType != DTST_RAM )
  	{
  		err = ERROR_OBJECT_WRONG_TYPE;
  	}
  	
	if( err < 0 )
	{
		err = ifferr2doserr[ -err-1 ];
	}
	
	SetIoErr( err );
	return( err );
}

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

LONG __regargs WriteObjectData(Class *cl, Object *o, struct dtWrite *dtw)
{
	struct SignalSemaphore	*lock = &((struct DTSpecialInfo *)((struct Gadget *)o)->SpecialInfo)->si_Lock;
	struct ClassBase	*cb = (struct ClassBase *) cl->cl_UserData;
	struct IFFHandle	*iff;
 	LONG			err = 0L;
 	APTR			Sample = NULL;
 	ULONG			Frequency, SampleType, SampleLength = 0L;
	// don't free memory before we're done
	ObtainSemaphoreShared( lock );
 	
 	GetDTAttrs( o,
 		SDTA_Sample, (ULONG) &Sample,
 		SDTA_Frequency, (ULONG) &Frequency,
 		SDTA_SampleLength, (ULONG) &SampleLength,
 		SDTA_SampleType, (ULONG) &SampleType,
 		TAG_DONE );
	
	if( Sample && SampleLength )
	{
		if( iff = AllocIFF( ) )
		{
			if( iff->iff_Stream = (ULONG) dtw->dtw_FileHandle )
			{
				InitIFFasDOS( iff );
				
				if( ! ( err = OpenIFF( iff, IFFF_WRITE ) ) )
				{
					STATIC ULONG		bytesPerPoint[] = { 0, 1, 1, 2 }; // shifting values !!! 
					ULONG				sampleSize = SampleLength << bytesPerPoint[ SampleType ];
					CommonChunk		cc;
					SampledSoundHeader	ssh = { 0L, 0L };
					UBYTE				pad = (UBYTE) ( sampleSize & 1 );
					
					if( ! ( err = PushChunk( iff, ID_AIFF, ID_FORM, 20L + sizeof( cc ) + sizeof( ssh ) + sampleSize + pad ) ) )
					{						
						cc.numChannels = ( SampleType & 1 ) + 1;
						cc.numSampleFrames = SampleLength;
						cc.sampleSize = ( SampleType > 1 ) ? 16 : 8;
						ulong2extended( Frequency, &cc.sampleRate );
						
						if( ! ( err = PushChunk( iff, ID_AIFF, ID_COMM, sizeof( cc ) ) ) )
						{
							if( ( err = WriteChunkBytes( iff, &cc, sizeof( cc ) ) ) == sizeof( cc ) )
							{
								if( ! ( err = PopChunk( iff ) ) )
								{
									if( ! ( err = PushChunk( iff, ID_AIFF, ID_SSND, sizeof( ssh ) + sampleSize ) ) )
									{
										if( ( err = WriteChunkBytes( iff, &ssh, sizeof( ssh ) ) ) == sizeof( ssh ) )
										{										
											if( ( err = WriteChunkBytes( iff, Sample, sampleSize ) ) == sampleSize )
											{
												if( pad )
												{
													pad=0;
													WriteChunkBytes( iff, &pad, 1L );
												}
												
												if( ! ( err = PopChunk( iff ) ) )
												{
													err = PopChunk( iff );
												}
											}
										}										
									}
								}
							}
						}
					}
				}
				
				CloseIFF( iff );
			}
			else
			{
				err = ERROR_INVALID_LOCK;
			}
		}
		else
		{
			err = ERROR_NO_FREE_STORE;
		}
	
		FreeIFF( iff );
	}
	else
	{
		err = ERROR_REQUIRED_ARG_MISSING;
	}
	
	if( err < 0 )
	{
		err = ifferr2doserr[ -err-1 ];
	}
	
	ReleaseSemaphore( lock );
	
	SetIoErr( err );
	return( err ? FALSE : TRUE );
}

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

const WORD indexAdjustTable[16] = {
   -1, -1, -1, -1,  // +0 - +3, decrease the step size
    2, 4, 6, 8,     // +4 - +7, increase the step size
   -1, -1, -1, -1,  // -0 - -3, decrease the step size
    2, 4, 6, 8,     // -4 - -7, increase the step size
};

const WORD stepSizeTable[89] = {
   7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34,
   37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143,
   157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494,
   544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552,
   1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660, 4026,
   4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442,
   11487, 12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623,
   27086, 29794, 32767
};

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

WORD ImaAdpcmDecode( UBYTE deltaCode, struct ImaState *state ) 
{
   // Get the current step size
   WORD step = stepSizeTable[ state->index ];
   
   // Construct the difference by scaling the current step size
   // This is approximately: difference = (deltaCode+.5)*step/4
   WORD difference = step>>3;
   if ( deltaCode & 1 ) difference += step>>2;
   if ( deltaCode & 2 ) difference += step>>1;
   if ( deltaCode & 4 ) difference += step;
   if ( deltaCode & 8 ) difference = -difference;

   // Build the new sample
   state->previousValue += difference;
   if (state->previousValue > 32767) state->previousValue = 32767;
   else if (state->previousValue < -32768) state->previousValue = -32768;

   // Update the step for the next sample
   state->index += indexAdjustTable[deltaCode];
   if (state->index < 0) state->index = 0;
   else if (state->index > 88) state->index = 88;

   return state->previousValue;
}

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

void DecompressImaAdpcmApple( struct ClassBase *cb, struct IFFHandle *iff, struct ImaAdpcmApple *hdl, WORD channels )
{
	hdl->cb = cb;
	hdl->iff = iff;
	hdl->samplesRemaining = 0;
	hdl->state[0].previousValue = 0;
	hdl->state[0].index = 0;
	hdl->state[1] = hdl->state[0];
	hdl->channels = channels;
}

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

ULONG NextPacket( struct ClassBase *cb, struct IFFHandle *iff, WORD *sampleBuffer, struct ImaState *state )
{
	WORD 	packet[34], lastSample, i;
	ULONG	bytesRead;
	
	// Pull in the packet and check the header
	bytesRead = ReadChunkBytes( iff, packet, 34 );
	if( bytesRead < 34 ) return 0L;

	// Check the decompressor state
	state->index = packet[1] & 0x7f;

	// Recover upper nine bits of last sample
	lastSample = ((((char *)packet)[0])*0x100 + (((char *)packet)[1])) & 0xff80;

	// If ours doesn't match, reset to the value in the file
	if ((state->previousValue & 0xff80) != lastSample)
		state->previousValue = lastSample;

	// Decompress nybbles
	for( i = 0; i < 32; i++ )
	{
		*sampleBuffer++ = ImaAdpcmDecode( packet[i+2] & 0xf, state );
		*sampleBuffer++ = ImaAdpcmDecode( ( packet[i+2] >> 4 ) & 0xf, state );
	}
   
	return 64;
}

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

ULONG DecodeImaAdpcmApple( struct ImaAdpcmApple *hdl, WORD *outBuff, ULONG wanted )
{
	ULONG remaining = wanted;
	
	while( remaining > 0 ) 
	{
		if( hdl->samplesRemaining == 0 )
		{
			LONG i;
			
			for( i = 0; i < hdl->channels; i++ )
			{
				hdl->samplesRemaining = NextPacket( hdl->cb, hdl->iff, hdl->samples[i], &hdl->state[i] );
				if( hdl->samplesRemaining == 0 ) { return wanted-remaining; }
				hdl->samplePtr[i] = hdl->samples[i];
         	}
      	}
      	
		switch( hdl->channels )
		{
			case 1:
				while( ( hdl->samplesRemaining > 0 ) && ( remaining > 0 ) )
				{
					*outBuff++ = *hdl->samplePtr[0]++;
					hdl->samplesRemaining--;
					remaining--;
         		}
	         break;
	         
			case 2:
				while( ( hdl->samplesRemaining > 0 ) && ( remaining > 0 ) )
				{
		            *outBuff++ = *hdl->samplePtr[0]++; // Left
        		    *outBuff++ = *hdl->samplePtr[1]++; // Right
		            hdl->samplesRemaining--;
        		    remaining -= 2;
				}
			break;
		}
	}
   
	return wanted - remaining;
}

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