
/*
**
**	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' )

// "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

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

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);
}

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

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_AIFC, ID_FVER,
				ID_AIFC, ID_COMM,
				ID_AIFC, ID_SSND
			};
	 		ULONG	SampleType, SampleLength, Frequency;
	 		APTR	Sample = NULL;
			
			err = StopChunks( iff, Stops, sizeof( Stops ) / ( sizeof( LONG ) * 2 ) );
			
			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_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( o, NULL, NULL,
												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 );
}

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