// Copyright (c) 2006-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of the License "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
// e32test\multimedia\t_soundwav.cpp
// Record wav to a file "t_soundwav filename [rate] [channels] [seconds]" where filename does not exist.
// Play a wav file "t_soundwav filename" where filename exists.
// 
//

/**
 @file Shared chunk sound driver WAV file player and recorder utility.
*/

#include <e32test.h>
#include <e32def.h>
#include <e32def_private.h>
#include "t_soundutils.h"
#include <f32file.h>

#define CHECK(aValue) {Test(aValue);}
#define CHECK_NOERROR(aValue) { TInt v=(aValue); if(v) { Test.Printf(_L("Error value = %d\n"),v); Test(EFalse,__LINE__); }}
#define CHECK_EQUAL(aValue1,aValue2) { TInt v1=(aValue1); TInt v2=(aValue2); if(v1!=v2) { Test.Printf(_L("Error value = %d\n"),v1); Test(EFalse,__LINE__); }}

_LIT(KSndLddFileName,"ESOUNDSC.LDD");
_LIT(KSndPddFileName,"SOUNDSC.PDD");

RTest Test(_L("T_SOUNDWAV"));
RSoundSc TxSoundDevice;
RSoundSc RxSoundDevice;

TSoundFormatsSupportedV02Buf RecordCapsBuf;
TSoundFormatsSupportedV02Buf PlayCapsBuf;
TCurrentSoundFormatV02Buf PlayFormatBuf;
TCurrentSoundFormatV02Buf RecordFormatBuf;

TBuf<256> CommandLine;
RFs Fs;

//
// This is the WAV header structure used for playing and recording files
//
struct WAVEheader
	{
	char ckID[4];				// chunk id 'RIFF'
	TUint32 ckSize;				// chunk size
	char wave_ckID[4];			// wave chunk id 'WAVE'
	char fmt_ckID[4];			// format chunk id 'fmt '
	TUint32 fmt_ckSize;			// format chunk size
	TUint16 formatTag;			// format tag currently pcm
	TUint16 nChannels;			// number of channels
	TUint32 nSamplesPerSec;		// sample rate in hz
	TUint32 nAvgBytesPerSec;	// average bytes per second
	TUint16 nBlockAlign;		// number of bytes per sample
	TUint16 nBitsPerSample;		// number of bits in a sample
	char data_ckID[4];			// data chunk id 'data'
	TUint32 data_ckSize;		// length of data chunk
	};
	
LOCAL_C TInt SamplesPerSecondToRate(TInt aRateInSamplesPerSecond,TSoundRate& aRate)
	{
	switch (aRateInSamplesPerSecond)
		{
		case 7350: 	aRate=ESoundRate7350Hz; break;
		case 8000: 	aRate=ESoundRate8000Hz; break;
		case 8820: 	aRate=ESoundRate8820Hz; break;
		case 9600: 	aRate=ESoundRate9600Hz; break;
		case 11025: aRate=ESoundRate11025Hz; break;
		case 12000: aRate=ESoundRate12000Hz; break;
		case 14700:	aRate=ESoundRate14700Hz; break;
		case 16000: aRate=ESoundRate16000Hz; break;
		case 22050: aRate=ESoundRate22050Hz; break;
		case 24000: aRate=ESoundRate24000Hz; break;
		case 29400: aRate=ESoundRate29400Hz; break;
		case 32000: aRate=ESoundRate32000Hz; break;
		case 44100: aRate=ESoundRate44100Hz; break;
		case 48000: aRate=ESoundRate48000Hz; break;
		default: return(KErrArgument);
		};
	return(KErrNone);	
	}
	
LOCAL_C TInt RecordBufferSizeInBytes(TCurrentSoundFormatV02& aFormat)
	{
	switch (aFormat.iRate)
		{
		case ESoundRate7350Hz: return(8192);
		case ESoundRate8000Hz: return(8192);
		case ESoundRate8820Hz: return(12288);
		case ESoundRate9600Hz: return(12288);
		case ESoundRate11025Hz: return(12288);
		case ESoundRate12000Hz: return(12288);
		case ESoundRate14700Hz: return(12288);
		case ESoundRate16000Hz: return(12288);
		case ESoundRate22050Hz: return(16384);
		case ESoundRate24000Hz: return(16384);
		case ESoundRate29400Hz: return(24576);
		case ESoundRate32000Hz: return(24576);
		case ESoundRate44100Hz: return(32768);
		case ESoundRate48000Hz: return(32768);
		default: return(32768);
		};	
	}	
	
LOCAL_C TInt Load()
	{
	TInt r;

	Test.Start(_L("Load sound PDD"));
	r=User::LoadPhysicalDevice(KSndPddFileName);
	if (r==KErrNotFound)
		{
		Test.End();
		return(r);
		}
	CHECK(r==KErrNone || r==KErrAlreadyExists);
	
	Test.Next(_L("Load sound LDD"));
	r=User::LoadLogicalDevice(KSndLddFileName);
	CHECK(r==KErrNone || r==KErrAlreadyExists);

	Test.End();
	return(KErrNone);
	}

LOCAL_C TInt WavPlay()
	{
	RChunk chunk;
	
	// Parse the commandline and get a filename to use
	TLex l(CommandLine);
	TFileName thisfile=RProcess().FileName();
	TPtrC token=l.NextToken();
	if (token.MatchF(thisfile)==0)
		token.Set(l.NextToken());

	if (token.Length()==0)
		{
		// No args, skip to end
		Test.Printf(_L("Invalid configuration\r\n"));
		return(KErrArgument);
		}
		
	Test.Next(_L("Play Wav file"));

	// Assume that the argument is a WAV filename
	TFileName wavFilename=token;
	TInt r;
	RFile source;
	r = source.Open(Fs,wavFilename,EFileRead);
	if (r!=KErrNone)
		{
		Test.Printf(_L("Open failed(%d)\r\n"), r);
		return(r);
		}

	// Read the pcm header
	WAVEheader header;
	TPtr8 headerDes((TUint8 *)&header,sizeof(struct WAVEheader),sizeof(struct WAVEheader));
	r = source.Read(headerDes);
	if (r!=KErrNone)
		{
		source.Close();
		return(r);
		}
	Test.Printf(_L("Header Read %d bytes\r\n"),headerDes.Size());

	if (headerDes.Size() != sizeof(struct WAVEheader)) // EOF
		{
		Test.Printf(_L("Couldn't read a header(%d bytes)\r\n"),headerDes.Size());
		source.Close();
		return(KErrCorrupt);
		}

	Test.Printf(_L("Header rate:%d channels:%d tag:%d bits:%d (%d bytes/s) align %d datalen:%d fmt_ckSize:%d ckSize:%d\n"),
			header.nSamplesPerSec, header.nChannels, header.formatTag, header.nBitsPerSample,
			header.nAvgBytesPerSec, header.nBlockAlign, header.data_ckSize, header.fmt_ckSize, header.ckSize);

	if (header.formatTag != 1) // not pcm
		{
		Test.Printf(_L("Format not PCM(%d)\r\n"),header.formatTag);
		source.Close();
		return(KErrNotSupported);
		}

	if (header.nBitsPerSample != 16) // not 16 bit
		{
		Test.Printf(_L("Format not 16 bit PCM(%d bits)\r\n"),header.nBitsPerSample);
		source.Close();
		return(KErrNotSupported);
		}
		
	TSoundRate rate;	
	if (SamplesPerSecondToRate(header.nSamplesPerSec,rate)!=KErrNone)	
		{
		Test.Printf(_L("Format specifies a rate not supported(%d)\r\n"),header.nSamplesPerSec);
		source.Close();
		return(KErrNotSupported);
		}

	TxSoundDevice.AudioFormat(PlayFormatBuf);	// Read back the current setting which must be valid.
	PlayFormatBuf().iChannels = header.nChannels;
	PlayFormatBuf().iRate = rate;
	PlayFormatBuf().iEncoding = ESoundEncoding16BitPCM;
	
	// Set the play buffer configuration.
	TInt bufSize=BytesPerSecond(PlayFormatBuf())/8; 	// Large enough to hold 1/8th second of data.
	bufSize&=~(header.nBlockAlign-1);					// Keep the buffer length a multiple of the bytes per sample (assumes 16bitPCM, 1 or 2 chans).
	if (PlayCapsBuf().iRequestMinSize)
		bufSize&=~(PlayCapsBuf().iRequestMinSize-1); 	// Keep the buffer length valid for the driver.
	TTestSharedChunkBufConfig bufferConfig;
	bufferConfig.iNumBuffers=3;
	bufferConfig.iBufferSizeInBytes=bufSize;
	bufferConfig.iFlags=0;	
	PrintBufferConf(bufferConfig,Test);
	TPckg<TTestSharedChunkBufConfig> bufferConfigBuf(bufferConfig);
	r=TxSoundDevice.SetBufferChunkCreate(bufferConfigBuf,chunk);
	if (r!=KErrNone)
		{
		Test.Printf(_L("Buffer configuration not supported(%d)\r\n"),r);
		source.Close();
		return(r);
		}
	TxSoundDevice.GetBufferConfig(bufferConfigBuf);			// Read back the configuration - to get the buffer offsets
	CHECK(bufferConfig.iBufferSizeInBytes==bufSize);

	// Set the audio play configuration.
	TxSoundDevice.SetVolume(KSoundMaxVolume - (KSoundMaxVolume / 4)); // set volume to 75%
	PrintConfig(PlayFormatBuf(),Test);
	r=TxSoundDevice.SetAudioFormat(PlayFormatBuf);
	if (r!=KErrNone)
		{
		Test.Printf(_L("Format not supported\r\n"));
		source.Close();
		chunk.Close();
		return(r);
		}
	TxSoundDevice.ResetBytesTransferred();

	TInt32 bytesToPlay = header.data_ckSize;
	TTime starttime;
	starttime.HomeTime();

	TRequestStatus stat[3];
	TPtr8* tPtr[3];
	TInt i;
	for (i=0;i<3;i++)
		tPtr[i]=new TPtr8(NULL,0); 
		
	// Start off by issuing a play request for each buffer (assuming that the file is long enough). Use the full size
	// of each buffer.
	TInt stillToRead=bytesToPlay;
	TInt stillNotPlayed=bytesToPlay;
	TUint flags;
	for (i=0 ; i<3 ; i++)
		{
		// Setup the descriptor for reading in the data from the file.
		tPtr[i]->Set(chunk.Base()+bufferConfig.iBufferOffsetList[i],0,bufSize); 
		
		// If there is still data to read to play then read this into the descriptor
		// and then write it to the driver.
		if (stillToRead)
			{
			r=source.Read(*tPtr[i],Min(stillToRead,bufSize));
			if (r!=KErrNone)
				{
				Test.Printf(_L("Initial file read error(%d)\r\n"),r);
				source.Close();
				chunk.Close();
				return(r);
				}
			stillToRead-=tPtr[i]->Length();
			flags=(stillToRead>0)?0:KSndFlagLastSample;
			TxSoundDevice.PlayData(stat[i],bufferConfig.iBufferOffsetList[i],tPtr[i]->Length(),flags);
			}
		else
			stat[i]=KRequestPending;	
		}	
		
	FOREVER
		{
		// Wait for any one of the outstanding play requests to complete.
		User::WaitForAnyRequest();
	
		// Work out which buffer this applies to
		for (i=0 ; i<3 ; i++)
			{
			if (stat[i]!=KRequestPending)
				break;
			}
		if (i>=3)
			{
			Test.Printf(_L("I/O error\r\n"));
			source.Close();
			chunk.Close();
			return(KErrGeneral);
			}
	
		// Check that the transfer was succesful and whether we have now played all the file.
		if (stat[i]!=KErrNone)
			{
			Test.Printf(_L("Play error(%d)\r\n"),stat[i].Int());
			source.Close();
			chunk.Close();
			return(stat[i].Int());
			}
		Test.Printf(_L("Played %d bytes(%d) - %d\r\n"),tPtr[i]->Length(),i,stat[i].Int());
		stillNotPlayed-=tPtr[i]->Length();
		CHECK(stillNotPlayed>=0);
		if (!stillNotPlayed)
			break;
	
		// Still more to be played so read the next part of the file into the descriptor for this
		// buffer and then write it to the driver.
		if (stillToRead)
			{
			TInt len=Min(stillToRead,bufSize);
		
			// If we've got to the end of the file and the driver is particular about the request length then 
			// zero fill the entire buffer so we can play extra zeros after the last sample from the file.
			if (len<bufSize && PlayCapsBuf().iRequestMinSize)
				tPtr[i]->FillZ(bufSize);
		
			// Read the next part of the file 
			r=source.Read(*tPtr[i],len);			// This will alter the length of the descriptor
			if (r!=KErrNone)
				{
				Test.Printf(_L("File read error(%d)\r\n"),r);
				source.Close();
				chunk.Close();
				return(r);
				}
			stillToRead-=tPtr[i]->Length();
		
			// If we've got to the end of the file and the driver is particular about the request length then
			// round up the length to the next valid boundary. This is OK since we zero filled.
			if (tPtr[i]->Length() < bufSize && PlayCapsBuf().iRequestMinSize)
				{
				TUint m=PlayCapsBuf().iRequestMinSize-1;
				len=(tPtr[i]->Length() + m) & ~m;
				}
		
			// Write it to the driver.
			flags=(stillToRead>0)?0:KSndFlagLastSample;
			TxSoundDevice.PlayData(stat[i],bufferConfig.iBufferOffsetList[i],len,flags);
			}
		else
			stat[i]=KRequestPending;		
		}
	
	// Delete all the variables again.	
	for (i=0 ; i<3 ; i++)
		delete tPtr[i];
	
	TTime endtime;
	endtime.HomeTime();

	Test.Printf(_L("Done playing\r\n"));
	Test.Printf(_L("Bytes played = %d\r\n"),TxSoundDevice.BytesTransferred());
	Test.Printf(_L("Delta time = %d\r\n"),endtime.Int64()-starttime.Int64());

	chunk.Close();
	source.Close();
	return(KErrNone);
	}

LOCAL_C TInt WavRecord()
	{
	// Parse the commandline and get a filename to use
	TLex l(CommandLine);
	TParse destinationName;
	if (destinationName.SetNoWild(l.NextToken(),0,0)!=KErrNone)
		{
		Test.Printf(_L("No arg, skipping\r\n"));
		return(KErrArgument);
		}
	Test.Next(_L("Record Wav file"));

	// Open the file for writing
	TInt r;
	RFile destination;
	r = destination.Replace(Fs,destinationName.FullName(),EFileWrite);
	if (r!=KErrNone)
		{
		Test.Printf(_L("Open file for write failed(%d)\n"), r);
		return(r);
		}		
	Test.Printf(_L("File opened for write\r\n"));
	
	Test.Next(_L("Preparing to record"));
	
	// Get the rate
	TLex cl(l.NextToken());
	TUint32 tmpRate;
	TSoundRate rate;
	r = cl.Val(tmpRate,EDecimal);
	if (r == KErrNone && (r=SamplesPerSecondToRate(tmpRate,rate))==KErrNone)
		{
		Test.Printf(_L("Parsed rate: %d\r\n"), tmpRate);
		RecordFormatBuf().iRate = rate;
		}
	else
		{
		Test.Printf(_L("Parse rate failed(%d)\r\n"),r);
		RecordFormatBuf().iRate = ESoundRate32000Hz;
		}

	// Get number of channels
	TLex cl_chan(l.NextToken());
	TUint32 tmpChannels;
	r = cl_chan.Val(tmpChannels,EDecimal);
	if (r == KErrNone)
		{
		Test.Printf(_L("Parsed %d channels\r\n"),tmpChannels);
		RecordFormatBuf().iChannels = tmpChannels;
		}
	else
		{
		Test.Printf(_L("Parse channels failed(%d)\r\n"), r);
		RecordFormatBuf().iChannels = 2;
		}

	RecordFormatBuf().iEncoding = ESoundEncoding16BitPCM;
	
	// Set the record buffer configuration.
	RChunk chunk;
	TTestSharedChunkBufConfig bufferConfig;
	bufferConfig.iNumBuffers=4;
	bufferConfig.iBufferSizeInBytes=RecordBufferSizeInBytes(RecordFormatBuf());
	if (RecordCapsBuf().iRequestMinSize)
		bufferConfig.iBufferSizeInBytes&=~(RecordCapsBuf().iRequestMinSize-1); 	// Keep the buffer length valid for the driver.
	bufferConfig.iFlags=0;	
	PrintBufferConf(bufferConfig,Test);
	TPckg<TTestSharedChunkBufConfig> bufferConfigBuf(bufferConfig);
	r=RxSoundDevice.SetBufferChunkCreate(bufferConfigBuf,chunk);
	if (r!=KErrNone)
		{
		Test.Printf(_L("Buffer configuration not supported(%d)\r\n"),r);
		return(r);
		}
	
	// Set the audio record configuration.
	RxSoundDevice.SetVolume(KSoundMaxVolume);
	PrintConfig(RecordFormatBuf(),Test);
	r=RxSoundDevice.SetAudioFormat(RecordFormatBuf);
	if (r!=KErrNone)
		{
		Test.Printf(_L("Format not supported\r\n"));
		return(r);
		}

	// Get length in seconds
	TLex cl_seconds(l.NextToken());
	TUint32 tmpSeconds;
	r = cl_seconds.Val(tmpSeconds,EDecimal);
	if (r == KErrNone)
		{
		Test.Printf(_L("Parsed %d seconds\r\n"),tmpSeconds);
		}
	else
		{
		Test.Printf(_L("Parse seconds failed(%d)\r\n"),r);
		tmpSeconds=10;
		}
	TInt bytesToRecord = BytesPerSecond(RecordFormatBuf())*tmpSeconds;	
		
	Test.Next(_L("Recording..."));
	
	// Lay down a file header
	WAVEheader header;
	TPtr8 headerDes((TUint8 *)&header, sizeof(struct WAVEheader), sizeof(struct WAVEheader));

	// "RIFF"
	header.ckID[0] = 'R'; header.ckID[1] = 'I';
	header.ckID[2] = 'F'; header.ckID[3] = 'F';
	// "WAVE"
	header.wave_ckID[0] = 'W'; header.wave_ckID[1] = 'A';
	header.wave_ckID[2] = 'V'; header.wave_ckID[3] = 'E';
	// "fmt "
	header.fmt_ckID[0] = 'f'; header.fmt_ckID[1] = 'm';
	header.fmt_ckID[2] = 't'; header.fmt_ckID[3] = ' ';
	// "data"
	header.data_ckID[0] = 'd'; header.data_ckID[1] = 'a';
	header.data_ckID[2] = 't'; header.data_ckID[3] = 'a';

	header.nChannels		= (TUint16)RecordFormatBuf().iChannels;
	header.nSamplesPerSec	= RateInSamplesPerSecond(RecordFormatBuf().iRate);
	header.nBitsPerSample	= 16;
	header.nBlockAlign		= TUint16((RecordFormatBuf().iChannels == 2) ? 4 : 2);
	header.formatTag		= 1;	// type 1 is PCM
	header.fmt_ckSize		= 16;
	header.nAvgBytesPerSec	= BytesPerSecond(RecordFormatBuf());
	header.data_ckSize		= bytesToRecord;
	header.ckSize			= bytesToRecord + sizeof(struct WAVEheader) - 8;

	Test.Printf(_L("Header rate:%d channels:%d tag:%d bits:%d (%d bytes/s) align %d datalen:%d fmt_ckSize:%d ckSize:%d\r\n"),
			header.nSamplesPerSec, header.nChannels, header.formatTag, header.nBitsPerSample,
			header.nAvgBytesPerSec, header.nBlockAlign, header.data_ckSize, header.fmt_ckSize, header.ckSize, sizeof(struct WAVEheader));

	r = destination.Write(headerDes);

	TRequestStatus stat;
	TInt length;
	TPtrC8 buf;
	
	// Start off by issuing a record request.
	TTime starttime;
	starttime.HomeTime();
	TInt bytesRecorded = 0;
	RxSoundDevice.RecordData(stat,length);

	FOREVER
		{
		// Wait for the outstanding record request to complete.
		User::WaitForAnyRequest();
		if (stat==KRequestPending)
			return(KErrGeneral);
			
		// Check whether the record request was succesful.
		TInt retOffset=stat.Int();
		if (retOffset<0)
			{
			Test.Printf(_L("Record failed(%d)\r\n"),retOffset);
			return(retOffset);
			}
		
		// Successfully recorded another buffer so write the recorded data to the record file and release the buffer.
		buf.Set((const TUint8*)(chunk.Base()+retOffset),length);
		r=destination.Write(buf);
		if (r!=KErrNone)
			{
			Test.Printf(_L("File write failed(%d)\r\n"),r);
			return(r);
			}
		r=RxSoundDevice.ReleaseBuffer(retOffset);
		if (r!=KErrNone)
			{
			Test.Printf(_L("Release buffer failed(%d)\r\n"),r);
			return(r);
			}
		
		Test.Printf(_L("Recorded %d more bytes - %d\r\n"),length,retOffset);
		
		// Check whether we have now recorded all the data. If more to record then queue a further request
		bytesRecorded+=length;
		if (bytesRecorded<bytesToRecord)
			RxSoundDevice.RecordData(stat,length);
		else
			break;
		}
	
	RxSoundDevice.CancelRecordData();	// Stop the driver from recording.
	
	TTime endtime;
	endtime.HomeTime();

	TInt64 elapsedTime = endtime.Int64()-starttime.Int64();	// us
	Test.Printf(_L("Delta time = %d\r\n"),I64LOW(elapsedTime));
	Test.Printf(_L("Seconds in buffer: %d (%d)\r\n"), bytesRecorded / header.nAvgBytesPerSec, (bytesRecorded / header.nAvgBytesPerSec)*1000000);

	if (I64LOW(elapsedTime) <= (bytesRecorded / header.nAvgBytesPerSec)*1000000)
		{
		Test.Printf(_L("Time travelling; record took less time than it should have done\r\n"));
		return(KErrGeneral);
		}
	
	chunk.Close();
	destination.Close();

	Test.Printf(_L("Record finished\r\n"));
	return(KErrNone);
	}
	
// Quick test block to write the output of the signal generator to a file.
// You'll need to comment out the reference to playdata in writetone and link
// the function into the command line processing (search on this function name).	
/*
LOCAL_C void TestWaveformGenerator()
	{
	
	Test.Next(_L("Testing waveform generator"));
	// Parse the commandline and get a filename to use
	TLex l(CommandLine);
	TParse destinationName;
	if (destinationName.SetNoWild(l.NextToken(),0,0)!=KErrNone)
		{
		Test.Printf(_L("No arg, skipping\r\n"));
		return;
		}

	// Open the file for writing
	TInt r;
	RFile destination;
	r = destination.Replace(Fs,destinationName.FullName(),EFileWrite);
	if (r!=KErrNone)
		{
		Test.Printf(_L("Open file for write failed(%d)\r\n"), r);
		}
	
	Test.Printf(_L("File opened for write\r\n"));
	Test.Next(_L("Preparing to record data"));

	// Get the rate
	TLex cl(l.NextToken());
	TUint32 tmpRate;
	TSoundRate rate;
	r = cl.Val(tmpRate,EDecimal);
	if (r == KErrNone && (r=SamplesPerSecondToRate(tmpRate,rate))==KErrNone)
		{
		Test.Printf(_L("Parsed rate: %d\r\n"), tmpRate);
		PlayFormatBuf().iRate = rate;
		}
	else
		{
		Test.Printf(_L("Parse rate failed(%d)\r\n"),r);
		PlayFormatBuf().iRate = ESoundRate32000Hz;
		}

	// Get number of channels
	TLex cl_chan(l.NextToken());
	TUint32 tmpChannels;
	r = cl_chan.Val(tmpChannels,EDecimal);
	if (r == KErrNone)
		{
		Test.Printf(_L("Parsed %d channels\r\n"),tmpChannels);
		PlayFormatBuf().iChannels = tmpChannels;
		}
	else
		{
		Test.Printf(_L("Parse channels failed(%d)\r\n"), r);
		PlayFormatBuf().iChannels = 2;
		}

	PlayFormatBuf().iEncoding = ESoundEncoding16BitPCM;
	PrintConfig(PlayFormatBuf(),Test);
	
	TInt bufferSize=BytesPerSecond(PlayFormatBuf())/8;
	TUint8* buffer = (TUint8*)User::Alloc(bufferSize*sizeof(TUint8));
	if (buffer==NULL)
		{
		Test.Printf(_L("Out of memory\r\n"));
		return;
		}
	TPtr8 bufferDes(buffer,bufferSize,bufferSize);	

	Test.Next(_L("Recording..."));
	TInt i = BytesPerSecond(PlayFormatBuf())*10/bufferSize;
	TInt bytesToRecord = i * bufferSize;

	// Lay down a file header
	WAVEheader header;
	TPtr8 headerDes((TUint8 *)&header, sizeof(struct WAVEheader), sizeof(struct WAVEheader));

	// "RIFF"
	header.ckID[0] = 'R'; header.ckID[1] = 'I';
	header.ckID[2] = 'F'; header.ckID[3] = 'F';
	// "WAVE"
	header.wave_ckID[0] = 'W'; header.wave_ckID[1] = 'A';
	header.wave_ckID[2] = 'V'; header.wave_ckID[3] = 'E';
	// "fmt "
	header.fmt_ckID[0] = 'f'; header.fmt_ckID[1] = 'm';
	header.fmt_ckID[2] = 't'; header.fmt_ckID[3] = ' ';
	// "data"
	header.data_ckID[0] = 'd'; header.data_ckID[1] = 'a';
	header.data_ckID[2] = 't'; header.data_ckID[3] = 'a';

	header.nChannels		= PlayFormatBuf().iChannels;
	header.nSamplesPerSec	= RateInSamplesPerSecond(PlayFormatBuf().iRate);
	header.nBitsPerSample	= 16;
	header.nBlockAlign		= 4;
	header.formatTag		= 1;
	header.fmt_ckSize		= 16;
	header.nAvgBytesPerSec	= BytesPerSecond(PlayFormatBuf());
	header.data_ckSize		= bytesToRecord;
	header.ckSize			= bytesToRecord + sizeof(struct WAVEheader) - 8;

	Test.Printf(_L("Header rate:%d channels:%d tag:%d bits:%d (%d bytes/s) align %d datalen:%d fmt_ckSize:%d ckSize:%d\r\n"),
			header.nSamplesPerSec, header.nChannels, header.formatTag, header.nBitsPerSample,
			header.nAvgBytesPerSec, header.nBlockAlign, header.data_ckSize, header.fmt_ckSize, header.ckSize);

	r = destination.Write(headerDes);

	MakeSineTable(PlayFormatBuf());
	SetToneFrequency(440,PlayFormatBuf()); // 'A'

	while(--i>0)
		{
		WriteTone(bufferDes,PlayFormatBuf());
		r = destination.Write(bufferDes);
		if (r!=KErrNone)
			{
			Test.Printf(_L("Write failed(%d)\r\n"),r);
			break;
			}
		}
	Test.Printf(_L("Finished\r\n"));

	delete buffer;
	destination.Close();
	}
*/
LOCAL_C void TestUnloadDrivers()
	{
	TInt r=User::FreeLogicalDevice(KDevSoundScName);
	Test.Printf(_L("Unloading %S.LDD - %d\r\n"),&KDevSoundScName,r);
	CHECK_NOERROR(r);
	
	TName pddName(KDevSoundScName);
	_LIT(KPddWildcardExtension,".*");
	pddName.Append(KPddWildcardExtension);
	TFindPhysicalDevice findPD(pddName);
	TFullName findResult;
	r=findPD.Next(findResult);
	while (r==KErrNone)
		{
		r=User::FreePhysicalDevice(findResult);
		Test.Printf(_L("Unloading %S.PDD - %d\r\n"),&findResult,r);
		CHECK_NOERROR(r);
		findPD.Find(pddName); // Reset the find handle now that we have deleted something from the container.
		r=findPD.Next(findResult);
		} 
	}
	
TInt E32Main()
	{
	TInt r;

	__UHEAP_MARK;

	Test.Title();

	Test.Start(_L("Load"));
	if (Load()==KErrNotFound)
		{
		Test.Printf(_L("Shared chunk sound driver not supported - test skipped\r\n"));
		Test.End();
		Test.Close();
		__UHEAP_MARKEND;
		return(KErrNone);
		}

	__KHEAP_MARK;

	Test.Next(_L("Open playback channel"));
	r = TxSoundDevice.Open(KSoundScTxUnit0);
	if (r!=KErrNone)
		{
		Test.Printf(_L("Open playback channel error(%d)\r\n"),r);
		Test(0);
		}
	
	Test.Next(_L("Open record channel"));
	r = RxSoundDevice.Open(KSoundScRxUnit0);
	if (r!=KErrNone)
		{
		Test.Printf(_L("Open record channel error(%d)\r\n"),r);
		Test(0);
		}
	
	Test.Next(_L("Query play formats supported"));
	TxSoundDevice.Caps(PlayCapsBuf);
	TSoundFormatsSupportedV02 playCaps=PlayCapsBuf();
	PrintCaps(playCaps,Test);

	Test.Next(_L("Query record formats supported"));
	RxSoundDevice.Caps(RecordCapsBuf);
	TSoundFormatsSupportedV02 recordCaps=RecordCapsBuf();
	PrintCaps(recordCaps,Test);
	
	Test.Next(_L("Connect to the file server"));
	r = Fs.Connect();
	if (r!=KErrNone)
		{
		Test.Printf(_L("Connect to the file server error(%d)\r\n"),r);
		Test(0);
		}
		
	if (User::CommandLineLength())
		{
		User::CommandLine(CommandLine);
		TLex l(CommandLine);
		TPtrC token=l.NextToken();

		TInt count=0;
		while (token.Length()!=0)
			{
			++count;
			token.Set(l.NextToken());
			}
		Test.Printf(_L("Command line %d parameters\r\n"),count);

		if (count==1)		// If 1 parameter try playing a file
			r=WavPlay();
		else if (count)		// If there is more than 1 parameter, try recording
			r=WavRecord();
		
		//TestWaveformGenerator();
			
		}
	
	Fs.Close();
	
	Test.Next(_L("Close channels"));
	RxSoundDevice.Close();
	TxSoundDevice.Close();
	
	__KHEAP_MARKEND;

	// Now that both the channels are closed, unload the LDD and the PDDs.
	TestUnloadDrivers();

	Test(r==KErrNone);

	Test.End();
	Test.Close();

	Cleanup();
	
	__UHEAP_MARKEND;

	return(KErrNone);
	}
