
/*	Copyright (C) G. Jespersen
	See Licence.txt.*/

#include "stdafx.h"
#include "DsLavSplit.h"
#include "CdaRead.h"

// Some test code - not using - works - could use to copy CD Tracks to wave file.
// From:
//	https://www.codeproject.com/Articles/15725/Tutorial-on-reading-Audio-CDs
// Used while developing AsyncCD Filter.

//#include "ntddcdrm.h" problems with setup - no build enviroment set yet
//#include "devioctl.h"
//#include "ntddcdrm.h"
//#include "ntddcdvd.h"
//#include "ntdddisk.h"
//#include "ntddstor.h"
//#include "..\..\..\..\winddk\devioctl.h"
#include "winddk\ntddcdrm.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

CCdaRead::CCdaRead()
{
	m_hDrive = INVALID_HANDLE_VALUE;
	m_iTrack = 0;
}

CCdaRead::~CCdaRead()
{
	if(m_hDrive != INVALID_HANDLE_VALUE)
	{
		CloseHandle(m_hDrive);
		m_hDrive = INVALID_HANDLE_VALUE;
	}
}

// Open CD file - "E:\Track03.cda"
BOOL CCdaRead::OpenCdaFile(CString cdaFile)
{
	CString fileName = cdaFile;
	CString drive = _T("");
	int iOnes = 0, iTens = 0, iTrack = 0;

	// First char is the drive - "E".
	drive = fileName[0];

	// Get the track number.
	//	
	int index = fileName.Find(_T('.'), 0);
	if(index != -1)
	{
		CString s;
		// Get 0 - 9.
		s = fileName[index - 1];
		iOnes = _ttoi(s);

		// Get 10 - 90.
		s = fileName[index - 2];
		iTens = _ttoi(s);
	}
	else
		return FALSE;

	// Add up.
	iTrack = iTens * 10 + iOnes;

	// Ensure.
	if(iTrack < 1 || iTrack > 99)
	{
		return FALSE;		
	}
	// Set members.
	m_fileName = fileName;
	m_drive = drive;
	m_iTrack = iTrack;

	// Read drive TOC.
	ReadDrive(m_drive);

	return TRUE;	
}

// Drive param is a drive letter, i.e. 'E'.
// Track03.cda
BOOL CCdaRead::ReadDrive(CString drive)
{
	// From:
	//	https://www.codeproject.com/Articles/15725/Tutorial-on-reading-Audio-CDs
	CString path;
	path.Format(_T("\\\\.\\%s:"), drive); // "\\.\E:"

	// Get drive handle.
	m_hDrive = CreateFile(path,
						  GENERIC_READ,
						  FILE_SHARE_READ,
						  NULL, 
						  OPEN_EXISTING, 
						  0, 
						  NULL);
	if(m_hDrive == INVALID_HANDLE_VALUE)
	{
		TRACE(_T("ERROR CCdaRead::ReadDrive CreateFile failed.\n"));
		return FALSE;
	}

	// Get track-table
	ULONG nRead;
	CDROM_TOC toc;
	CDTRACK track;
	BOOL ret = FALSE;
	ret = DeviceIoControl(m_hDrive,				// handle to device
						  IOCTL_CDROM_READ_TOC, // operation
						  NULL,					// input data buffer
						  0,					// size of input data buffer
						  &toc,					// output data buffer
						  sizeof(toc),			// size of output data buffer
						  &nRead,				// byte count
						  NULL);				// overlapped information
	if(ret == 0)
	{
		CloseHandle(m_hDrive);
		m_hDrive = NULL;
		return FALSE;
	}
	// Get CDTRACK data for each track from the toc's TRACK_DATA.
	ULONG first = toc.FirstTrack - 1;
	ULONG last  = toc.LastTrack;

	for(ULONG i = first; i < last; i++)
	{
		track.Zero();
		track.Address = AddressToSectors( toc.TrackData[i].Address );
		track.Length = AddressToSectors( toc.TrackData[i + 1].Address ) - track.Address;
		track.TrackNumber = toc.TrackData[i].TrackNumber; // ones based
		
		// Add track to end of array.
		m_tracks.Add(track);

	}
	// Ensure.
	if(!m_tracks.GetSize())
		return FALSE;
	// OK.
	return TRUE;
}

ULONG CCdaRead::AddressToSectors(UCHAR Addr[4])
{
	ULONG Sectors = (Addr[1] * (CD_BLOCKS_PER_SECOND * 60) ) + 
					(Addr[2] *  CD_BLOCKS_PER_SECOND)		 + 
					Addr[3];
	return Sectors - 150;
}

// Write a track to file.
// iTrack is ones based (1 - 99).
// - works fine - not used
BOOL CCdaRead::ExtractTrack(int iTrack, CString filePath)
{
	if(m_hDrive == NULL)
		return FALSE;

	// Check bounds.
	if(iTrack > m_tracks.GetSize())
		return FALSE;

	// Get CDTRACK ref to track we're writing.
	CDTRACK & track = m_tracks[iTrack - 1];

	// Create output file.
	HANDLE hFile = CreateFile(filePath, 
							  GENERIC_WRITE, 
							  0, 
							  NULL, 
							  CREATE_ALWAYS, 
							  FILE_ATTRIBUTE_NORMAL, 
							  NULL);
	if(hFile == INVALID_HANDLE_VALUE)
	{
		return FALSE;
	}

	// Create WAVE header data.
	CWaveFileHeader WaveFileHeader(44100, 16, 2, track.Length * RAW_SECTOR_SIZE);

	// Write header data to file.
	ULONG Dummy;
	DWORD bytesToWrite = sizeof(WaveFileHeader); // 44 bytes
	WriteFile(hFile, &WaveFileHeader, bytesToWrite, &Dummy, NULL );

	// Allocate buffer.for one read - 47040 bytes.
	CBuf<char> Buf(SECTORS_AT_READ * RAW_SECTOR_SIZE ); // 20 * 2352 = 47,040

	// Init RAW_READ_INFO struct. Passed to cdrom to describe the 
	// raw read, ie. Mode 2, Form 2, CDDA - ntddcdrm.h
	RAW_READ_INFO Info;
	Info.TrackMode = CDDA;
	Info.SectorCount = SECTORS_AT_READ;
	ULONG nChunks = track.Length / SECTORS_AT_READ;
	ULONG i = 0;

	// Write whole chunks.
	for(i = 0; i < nChunks; i++)
	{
		// Fetch.
		Info.DiskOffset.QuadPart = (track.Address + i * SECTORS_AT_READ) * CD_SECTOR_SIZE;
		if(!DeviceIoControl(m_hDrive, 
							IOCTL_CDROM_RAW_READ, 
							&Info, 
							sizeof(Info), 
							Buf, 
							SECTORS_AT_READ * RAW_SECTOR_SIZE, 
							&Dummy, 
							(LPOVERLAPPED)NULL) )
		{
			TRACE(_T("ERROR CCdaRead::ExtractTrack DeviceIoControl failed @ %d.\n"), i);
			CloseHandle(hFile);
			return FALSE;
		}
		// Write.
		WriteFile(hFile, Buf, Buf.Size(), &Dummy, NULL );
	}

	// Get last remaining partial chunk.
	Info.SectorCount = track.Length % SECTORS_AT_READ;
	Info.DiskOffset.QuadPart = (track.Address + i * SECTORS_AT_READ) * CD_SECTOR_SIZE;
	if(!DeviceIoControl(m_hDrive, 
						IOCTL_CDROM_RAW_READ, 
						&Info, 
						sizeof(Info), 
						Buf, 
						Info.SectorCount * RAW_SECTOR_SIZE, &Dummy, 
						NULL))
	{
		TRACE(_T("ERROR CCdaRead::ExtractTrack DeviceIoControl failed final chunk\n"));
		return FALSE;
	}
	// Write.
	WriteFile(hFile, Buf, Info.SectorCount * RAW_SECTOR_SIZE, &Dummy, NULL);

	// Done.
	if(CloseHandle(hFile))
		return TRUE;
	else
	{
		TRACE(_T("ERROR CCdaRead::ExtractTrack CloseHandle failed.\n"));
		return FALSE;
	}
}

/////////////////////////////////////////////////////////////
// CWaveFileHeader - a tiny class that helps us creating the 
// header of a wave-file.

CWaveFileHeader::CWaveFileHeader()
{
	ZeroMemory( this, sizeof(*this) );
}

CWaveFileHeader::CWaveFileHeader(ULONG SamplingRate, USHORT BitsPerSample, 
								 USHORT Channels, ULONG DataSize)
{
	Set(SamplingRate, BitsPerSample, Channels, DataSize);
}

void CWaveFileHeader::Set(ULONG SamplingRate, USHORT BitsPerSample, 
						  USHORT Channels, ULONG DataSize)
{
	// Set Riff-Chunk
	CopyMemory( m_Riff_ID, "RIFF", 4 );
	m_Riff_Size = DataSize + 44;
	CopyMemory( m_Riff_Type, "WAVE", 4 );

	// Set Fmt-Chunk
	CopyMemory( m_Fmt_ID, "fmt ", 4 );
	m_Fmt_Length = 16;
	m_Fmt_Format = WAVE_FORMAT_PCM;
	m_Fmt_Channels = Channels;
	m_Fmt_SamplingRate = SamplingRate;
	m_Fmt_BlockAlign = Channels*BitsPerSample/8;
	m_Fmt_DataRate = Channels*BitsPerSample*SamplingRate/8;
	m_Fmt_BitsPerSample = BitsPerSample;

	// Set Data-Chunk
	CopyMemory( m_Data_ID, "data", 4 );
	m_Data_DataSize = DataSize;
}



