///////////////////////////////////////////////////////////////////////////////
//
// File      : Asura_PS3_Sound_Loader.cpp
// Author    : Philip Hall
// Created   : 11/09/2007
// Updated   : $Revision: #2 $ - $DateTime: 2007/10/01 11:50:23 $ - $Author: Philip.Hall $
//
// Copyright (C) 2007 Rebellion. All Rights Reserved
//
///////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"

#ifndef ASURA_USE_AUDIO_SYSTEM

// This file's header
#include "Sound/Asura_PS3_Sound_Loader.h"

// Asura headers
#include "Core/Asura_Endian.h"
#include "Core/Asura_String.h"
#include "FileAccess/Asura_FileAccess.h"
#include "Debugging/Asura_Assert.h"
#include "Debugging/Asura_LogFile.h"

// Asura PS3 headers
#include "FileAccess/Asura_PS3_FileSystem.h"

// SDK headers
#include <cell/mstream.h>

// copied from Multistream tools docs with correct 10 loop markers instead of 12 in there.
struct MSFHeader
{
	char header[3]; //Will be 'MSF'
	char version; //Revision of tool (Example: 0x32 = Version 3.2)
	int compressionType; //Will be the compression type of the output
	//(matches those found in mstream.h i.e
	//#define CELL_MS_16BIT_BIG (0)
	//#define CELL_MS_16BIT_LITTLE (1)
	//#define CELL_MS_32BIT_FLOAT (2)
	//#define CELL_MS_ADPCM (3)
	//#define CELL_MS_ATRAC_LOW (4)
	//#define CELL_MS_ATRAC_MEDIUM (5)
	//#define CELL_MS_ATRAC_HIGH (6)
	//#define CELL_MS_MP3 (7)
	int channels; //Number of channels in the output file
	int sampleSize; //Number of *bytes* of sample data following the
	//64 bytes of header data
	int sampleRate; //Playback sample rate
	int miscInfo; //See "More About the MSF miscInfo Field" section.
	int LoopMarkers[10]; //10 loop markers. See "MSF Loop Markers" section.
};

// WAVE loader structures and functions
struct __attribute__((packed)) WAVEFORMAT
{
	uint16	wFormatTag;        // format type
	uint16	nChannels;         // number of channels (i.e. mono, stereo...)
	uint32	nSamplesPerSec;    // sample rate
	uint32	nAvgBytesPerSec;   // for buffer estimation
	uint16	nBlockAlign;       // block size of data
	uint16	wBitsPerSample;    // Number of bits per sample of mono data 
};

// WAFE format chunk IDs
static const char szCHUNK_RIFF[] = "RIFF";
static const char szCHUNK_WAVE[] = "WAVE";
static const char szCHUNK_fmt[]  = "fmt ";
static const char szCHUNK_data[] = "data";
static const char szCHUNK_fact[] = "fact";
static const char szCHUNK_VAG[]  = "VAG";
static const char szCHUNK_MSF[]  = "MSF";

static const int VAG_HEADER_SIZE = 48;
static const int MSF_HEADER_SIZE = 64;


////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_Sound_Loader::LoadAuto
// Description : Examine the data and load the correct format of audio file 
//
// Parameters  : 
//   char* pcBuffer        - Source data
//   int iBufferSize       - Size of source data
//   char*& pSamples       - Location of sample data (within the source data)
//   Asura_PS3_Sound_Info& xSound - Informat about the loaded sound
//
// Returns     : bool - Returns true on success
///////////////////////////////////////////////////////////////////////////////
bool Asura_PS3_Sound_Loader::LoadAuto(char* pcBuffer, int iBufferSize, char*& pSamples, Asura_PS3_Sound_Info& xSoundInfo)
{
// (MH - 27.03.2008) only supporting MSF now
//	if ( FindChunkInData(pcBuffer, iBufferSize, szCHUNK_VAG) == 3 )
//	{
//		if ( LoadVAGInfo(pcBuffer, iBufferSize, xSoundInfo) )
//		{
//			pSamples = pcBuffer + xSoundInfo.m_uDataOffset;
//			return true;
//		}
//	}
//	else if ( FindChunkInData(pcBuffer, iBufferSize, szCHUNK_RIFF) == 4 )
//	{
//		if ( LoadWAVInfo(pcBuffer, iBufferSize, xSoundInfo) )
//		{
//			pSamples = pcBuffer + xSoundInfo.m_uDataOffset;
//			return true;
//		}
//	}
//	else 
	if ( FindChunkInData(pcBuffer, iBufferSize, szCHUNK_MSF) == 3 )
	{
		if ( LoadMSFInfo(pcBuffer, iBufferSize, xSoundInfo) )
		{
			pSamples = pcBuffer + xSoundInfo.GetDataOffset();
			return true;
		}
	}
	
	Asura_OutputToDebugger(("[Asura_PS3_Sound_Loader::LoadAuto] - Unable to identify audio data format\n"));
	return false;
}



////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_Sound_Loader::LoadFileInfoAuto
// Description : Read header and load information from the formatted file 
//
// Parameters  : 
//   Asura_PS3_FileSystem::Handle xFileHandle  - Source file handle (PS3 only)
//   Asura_PS3_Sound_Info& xSoundInfo          - Sound information 
//
// Returns     : bool - Returns true on success
///////////////////////////////////////////////////////////////////////////////
bool Asura_PS3_Sound_Loader::LoadFileInfoAuto(Asura_PS3_FileSystem::Handle xFileHandle, Asura_PS3_Sound_Info& xSoundInfo)
{
	// Read approximately the right amount of header to extract information	(should catch the 'data' section)
	char pcBuffer[1024];	  
	if ( !Asura_PS3_FileSystem::ReadData(xFileHandle, sizeof(pcBuffer), pcBuffer) )
	{
		return false; // purposefully silent
	}

	// Check MSF format
	if ( FindChunkInData(pcBuffer, sizeof(pcBuffer), szCHUNK_MSF) > 0 )
	{
		return LoadMSFInfo(pcBuffer, sizeof(pcBuffer), xSoundInfo);
	}
	// (MH - 27.03.2008) only supporting MSF now
//	// Check for WAV format
//	else if ( FindChunkInData(pcBuffer, sizeof(pcBuffer), szCHUNK_RIFF) > 0 )
//	{
//		return LoadWAVInfo(pcBuffer, sizeof(pcBuffer), xSoundInfo);
//	}
//	// Check for VAG format
//	else if ( FindChunkInData(pcBuffer, sizeof(pcBuffer), szCHUNK_VAG) > 0 )
//	{
//		return LoadVAGInfo(pcBuffer, sizeof(pcBuffer), xSoundInfo);
//	}			
//	// HACK: MP3 format (MP3s are framed but often start with ID3)
//	else if ( FindChunkInData(pcBuffer, sizeof(pcBuffer), "ID3") > 0 )
//	{
//		Asura_Assert(false, "MP3s should be packaged in MSF format");
//		xSoundInfo.m_iSampleFormat = CELL_MS_MP3;
//		// HACK	(mp3 information arrives on a per-frame basis
//		xSoundInfo.m_iChannelCount = 2;
//		xSoundInfo.m_iSampleBits = 16;
//		xSoundInfo.m_iSampleRate = 44100;			  // MP3s not at this rate will sound wrong
//		xSoundInfo.m_uDataOffset = 10;				  // calculable but rather involved
//		xSoundInfo.m_uTotalDataBytes = Asura_PS3_FileSystem::GetFileSize(xFileHandle) - xSoundInfo.m_uDataOffset;
//		return true;
//	}		
	
	Asura_OutputToDebugger(("[Asura_PS3_Sound_Loader::LoadFileInfoAuto] - Unable to identify audio data format\n"));
	return false;
}






////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_Sound_Loader::LoadMSFInfo
// Description : Load information from an MSF format header
//
// Parameters  : 
//   char* pcBuffer   - Source data buffer
//   int iBufferSize  - Size of data buffer
//   Asura_PS3_Sound_Info& xSoundInfo - Sound information
//
// Returns     : bool - Returns true on success
///////////////////////////////////////////////////////////////////////////////
bool Asura_PS3_Sound_Loader::LoadMSFInfo(void* pBuffer, int iBufferSize, Asura_PS3_Sound_Info& xSoundInfo)
{
	Asura_Assert(iBufferSize >= MSF_HEADER_SIZE, "buffer too small for MSF");
	const MSFHeader* pxMSFHeader = reinterpret_cast<MSFHeader*>(pBuffer);

	xSoundInfo.SetSampleFormat(pxMSFHeader->compressionType);
	xSoundInfo.SetChannelCount(pxMSFHeader->channels);
	xSoundInfo.SetTotalDataBytes(pxMSFHeader->sampleSize);
	
	// Sample rate - ATRAC always uses 48kHz, otherwise use info from header
	if ( xSoundInfo.GetSampleFormat() < CELL_MS_ATRAC_LOW || xSoundInfo.GetSampleFormat() > CELL_MS_ATRAC_HIGH )
	{	
		xSoundInfo.SetSampleRate(pxMSFHeader->sampleRate);
	}
	else
	{
		xSoundInfo.SetSampleRate(48000);
	}

	if (xSoundInfo.GetSampleFormat() == CELL_MS_MP3)
	{
		// code based on https://ps3.scedev.net/forums/thread/41486
		CellMSMP3FrameHeader xMP3Hdr;
		int iRet=cellMSMP3GetFrameInfo(const_cast<MSFHeader*>(pxMSFHeader + 1),&xMP3Hdr); 
		if (iRet==-1)
		{
			Asura_Assert(false, "Invalid MP3 header");
			return false;
		}
		const int aiSampsMPG2[3]={192,1152,576};
		const int aiSampsMPG1[3]={384,1152,1152};
		const int iMPEGver = xMP3Hdr.Sync&(1<<19);
		if (iMPEGver == 0) // MPEG Version 2 or 2.5
		{
			xSoundInfo.SetSamplesPerMP3Frame(aiSampsMPG2[xMP3Hdr.Layer-1]); // should be 576
		}
		else // MPEG Version 1
		{
			xSoundInfo.SetSamplesPerMP3Frame(aiSampsMPG1[xMP3Hdr.Layer-1]); // should be 1152
		}
		xSoundInfo.SetMP3FrameSize(xMP3Hdr.PacketSize);
	}
	
	// loop offset
	xSoundInfo.SetLoopOffset(0);

	// Data start (constant)
	xSoundInfo.SetDataOffset(MSF_HEADER_SIZE);
	return true;
}


////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_Sound_Loader::LoadWAVInfo
// Description : Load WAV audio info
//
// Parameters  : 
//   char* pcBuffer   - Source data buffer
//   int iBufferSize  - Size of data buffer
//   Asura_PS3_Sound_Info& xSoundInfo - Sound information
//
// Returns     : bool - Returns true on success
///////////////////////////////////////////////////////////////////////////////
// (MH - 27.03.2008) no longer supporting WAV
//bool Asura_PS3_Sound_Loader::LoadWAVInfo(char* pcBuffer, int iBufferSize, Asura_PS3_Sound_Info& xSoundInfo)
//{
//	// Check for wave header
//	if ( FindChunkInData(pcBuffer, iBufferSize, szCHUNK_WAVE) < 0 )
//	{
//		Asura_OutputToDebugger(("[Asura_PS3_Sound_Loader::LoadWAVInfo] - mising 'WAVE' chunk\n"));
//		return false;
//	}
//
//	// Check for format header
//	int iOffset = FindChunkInData(pcBuffer, iBufferSize, szCHUNK_fmt);
//	if ( iOffset < 0 )
//	{
//		Asura_OutputToDebugger(("[Asura_PS3_Sound_Loader::LoadWAVInfo] - mising 'fmt ' chunk\n"));
//		return false;
//	}
//
//// Debugging
////	uint32 uFmtSize = Asura_Endian::SwapBytes( *((uint32*)(pcBuffer + iOffset)) );
//
//	// Extract ATRAC wave information
//	WAVEFORMAT* pxWaveFormat = reinterpret_cast<WAVEFORMAT*>( pcBuffer + iOffset + sizeof(uint32) );
//	xSoundInfo.m_iChannelCount = static_cast<int>( Asura_Endian::SwapBytes(pxWaveFormat->nChannels) );
//	xSoundInfo.m_iSampleRate   = static_cast<int>( Asura_Endian::SwapBytes(pxWaveFormat->nSamplesPerSec) );
//	xSoundInfo.m_iSampleBits   = static_cast<int>( Asura_Endian::SwapBytes(pxWaveFormat->wBitsPerSample) );
//
//	// Determine format type (should probably check the fact chunk really)
//	u_short uFormatTag = static_cast<u_short>( Asura_Endian::SwapBytes(pxWaveFormat->wFormatTag) );
//	switch ( uFormatTag )
//	{
//		case 0x0001: xSoundInfo.m_iSampleFormat = CELL_MS_16BIT_LITTLE; break;
//		case 0x0002: xSoundInfo.m_iSampleFormat = CELL_MS_ADPCM; break;
//		case 0x0270: xSoundInfo.m_iSampleFormat = CELL_MS_ATRAC_HIGH; break;	// assumed rate!
//		default: Asura_OutputToDebugger(("[Asura_PS3_Sound_Loader::LoadWAVInfo] - Unknown format type 0x%04X\n", uFormatTag)); break;
//	}
//
//	// Seek to data
//	iOffset	= FindChunkInData(pcBuffer, iBufferSize, szCHUNK_data);
//	if ( iOffset < 0 )
//	{
//		// (If you get this assert we may not have read the 'data' chunk in - just make the read buffer bigger)
//		Asura_OutputToDebugger(("[Asura_PS3_Sound_Loader::LoadWAVInfo] - mising 'data' chunk\n"));
//		return false;
//	}
//
//	xSoundInfo.m_uTotalDataBytes = Asura_Endian::SwapBytes( *(reinterpret_cast<uint32*>(pcBuffer + iOffset)) );
//	xSoundInfo.m_uDataOffset = static_cast<u_int>(iOffset + sizeof(uint32));
//	return true;
//}



////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_Sound_Loader::LoadVAGInfo
// Description : Load Sony VAG format data
//
// Parameters  : 
//   char* pcBuffer        - Source data
//   int iBufferSize       - Size of source data
//   Asura_PS3_Sound_Info& xSoundInfo - Informat about the loaded sound
//
// Returns     : bool - 
///////////////////////////////////////////////////////////////////////////////
// (MH - 27.03.2008) no longer supporting VAG
//bool Asura_PS3_Sound_Loader::LoadVAGInfo(char* pcBuffer, int iBufferSize, Asura_PS3_Sound_Info& xSoundInfo)
//{
//	int32* piVAGHeader = reinterpret_cast<int32*>(pcBuffer + sizeof(uint32) );
//
//	xSoundInfo.m_iSampleFormat = CELL_MS_ADPCM;
//	piVAGHeader++;
//	piVAGHeader++;
//	
//	xSoundInfo.m_uTotalDataBytes = *piVAGHeader;
//	piVAGHeader++;
//	xSoundInfo.m_iSampleRate = *piVAGHeader;
//	piVAGHeader++;
//
//	xSoundInfo.m_iSampleBits   = 16;			   // This isn't in the VAG header so assumed
//	xSoundInfo.m_iChannelCount = 1;
//	xSoundInfo.m_uDataOffset   = VAG_HEADER_SIZE;  // VAG head is 48 bytes long (apparently) 
//	return true;
//}


////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_Sound_Loader::FindChunkInData
// Description : Find the named chunk within in the given data buffer
//
// Parameters  : 
//   const char* pcData  - Data to search for named chunk
//   int iDataSize       - Size of data to look through (in bytes)
//   const char *szChunkName - name of chunk to locate (must be 4 chars long)
//
// Returns     : int - Index directly after chunk if found, -1 on failure
///////////////////////////////////////////////////////////////////////////////
int Asura_PS3_Sound_Loader::FindChunkInData(const char* pcData, int iDataSize, const char *szChunkName)
{
	const char* pcRead = pcData;
	int iChunkNameLen = strlen(szChunkName);

	// Seach all data until chunk name located
	while ( iDataSize > 0 )
	{
		// Found first character of chunk name (check remainder of chunk) 
		if ( *pcRead == *szChunkName)
		{
			// Look for chunk name
			int iCount = 0;
			while ( (pcRead[iCount] == szChunkName[iCount]) && (iCount < iChunkNameLen) ) { iCount++; }
			if ( iCount == iChunkNameLen ) { return iCount + int( pcRead ) - int( pcData ); }  // All counts used therefore chunk matched
		}						   
		pcRead++;
		iDataSize--;
	}

	return -1;
}

#endif // #ifndef ASURA_USE_AUDIO_SYSTEM

//eof
