#include "global.h"
#include "PitchDetectionTest.h"
#include "PitchDetectionTestUtil.h"
#include "srpd.h"
#include "EST_filter.h"
#include "RageLog.h"
#include "RageUtil.h"
#include "NoteTypes.h"
#include <MMSystem.h>

MicrophoneStatus PitchDetectionTest::s_ms;
Microphone *PitchDetectionTest::s_pMicrophone = NULL;

#define MAX_BUFFERS	3

// http://tomscarff.tripod.com/midi_analyser/midi_note_frequency.htm
// http://musicdsp.org/showone.php?id=125
const float MIDI_A4_FREQUENCY = 440.0;
const int MIDI_A4_NUMBER = 69;
const int MIDI_C4_NUMBER = 60;	// middle C
const RString NOTE_NAME[MIDI_NOTES_PER_OCTAVE] = {"A ","A#","B ","C ","C#","D ","D#","E ","F ","F#","G ","G#"};

float MidiNoteToFreq( int iMidiNote )
{
	return MIDI_A4_FREQUENCY * powf( 2.0, ((float)iMidiNote - MIDI_A4_NUMBER) / MIDI_NOTES_PER_OCTAVE );
}

float log2f( float n )
{
	// logb (x) = (loga(x)) / (loga(B))
	return logf(n) / logf(2);
}

bool FreqToMidiNote( float fFreq, float &fMidiNoteOut )
{
	if( fFreq < 20 )	// well below A0 (MidiNote = 1)
		return false;
	fMidiNoteOut = MIDI_NOTES_PER_OCTAVE * log2f( fFreq / MIDI_A4_FREQUENCY) + MIDI_A4_NUMBER;
	return fMidiNoteOut >= 1;
}

#undef bool

bool PitchDetectionTest::MidiNoteToString( int iMidiNote, RString &sMidiNoteOut )
{
	if( iMidiNote < 1 )
		return false;
	int iDiff = iMidiNote - MIDI_A4_NUMBER;
	wrap( iDiff, MIDI_NOTES_PER_OCTAVE );
	sMidiNoteOut = NOTE_NAME[iDiff];
	return true;
}

bool MidiNoteToOctave( int iMidiNote, int &iOctaveOut )
{
	if( iMidiNote < 1 )
		return false;
	// middle C is start of octave 4.
	int iOctave = (int)floorf( ((float)iMidiNote - MIDI_C4_NUMBER) / MIDI_NOTES_PER_OCTAVE );
	iOctaveOut = iOctave + 4;
	return true;
}

bool MidiNoteToStringAndOctave( int iMidiNote, RString &sMidiNoteOut )
{
	if( !PitchDetectionTest::MidiNoteToString(iMidiNote, sMidiNoteOut) )
		return false;
	int iOctave;
	if( !MidiNoteToOctave(iMidiNote, iOctave) )
		return false;
	sMidiNoteOut += ssprintf("%d",iOctave);
	return true;
}

float PitchDetectionTest::WrapToNearestOctave( float fMidiNote, float fTargetMidiNote )
{
	float fDiff = fMidiNote - fTargetMidiNote;
	fDiff += MIDI_NOTES_PER_OCTAVE / 2;
	wrap( fDiff, (float)MIDI_NOTES_PER_OCTAVE );
	fDiff -= MIDI_NOTES_PER_OCTAVE / 2;
	float fRet = fTargetMidiNote + fDiff;
	return fRet;
}


void CALLBACK waveInProc(HWAVEIN hwi,UINT uMsg,DWORD dwInstance,DWORD dwParam1,DWORD dwParam2);

class MicrophoneImpl
{
public:
	HWAVEIN m_hWaveIn;
	WAVEFORMATEX m_stWFEX;
	WAVEHDR m_stWHDR[MAX_BUFFERS];
	DetectPitch m_dp;
	HMMIO m_hOPFile;
	MMIOINFO m_stmmIF;
	MMCKINFO m_stckOut,m_stckOutRIFF; 

	static MicrophoneImpl *s_pMicrophoneImpl;	// one and only for the callback function to use


	void Init()
	{
		m_hWaveIn=NULL;
		ZeroMemory(&m_stWFEX,sizeof(WAVEFORMATEX));
		ZeroMemory(m_stWHDR,MAX_BUFFERS*sizeof(WAVEHDR));
		s_pMicrophoneImpl = this;
	}

	void FillDevices()
	{
		WAVEINCAPS stWIC={0};

		UINT nDevices=waveInGetNumDevs();

		for(UINT nC1=0;nC1<nDevices;++nC1)
		{
			ZeroMemory(&stWIC,sizeof(WAVEINCAPS));
			MMRESULT mRes=waveInGetDevCaps(nC1,&stWIC,sizeof(WAVEINCAPS));
			if(mRes==0)
				LOG->Trace( stWIC.szPname );
			else
				FAIL_M("bad");
		}
	}

	void StartRecording()
	{
		OpenDevice();
		
		m_dp.Init( SAMPLES_PER_SEC );
		
		PrepareBuffers();
		MMRESULT mRes=waveInStart(m_hWaveIn);
		if(mRes!=0)
			FAIL_M("bad");
	}

	void StopRecording()
	{
		CloseDevice();
	}

	void OpenDevice()
	{
		MMRESULT mRes=0;

		m_stWFEX.nSamplesPerSec=SAMPLES_PER_SEC;
		m_stWFEX.nChannels=1;
		m_stWFEX.wBitsPerSample = 16;
		m_stWFEX.wFormatTag=WAVE_FORMAT_PCM;
		m_stWFEX.nBlockAlign=m_stWFEX.nChannels*m_stWFEX.wBitsPerSample/8;
		m_stWFEX.nAvgBytesPerSec=m_stWFEX.nSamplesPerSec*m_stWFEX.nBlockAlign;
		m_stWFEX.cbSize=sizeof(WAVEFORMATEX);
		int device = 0;

		WAVEINCAPS stWIC={0};
		ZeroMemory(&stWIC,sizeof(WAVEINCAPS));
		mRes=waveInGetDevCaps(device,&stWIC,sizeof(WAVEINCAPS));
		if(mRes==0)
			LOG->Trace( stWIC.szPname );
		else
			FAIL_M("bad");


		mRes=waveInOpen(&m_hWaveIn,device,&m_stWFEX,(DWORD_PTR)waveInProc,(DWORD_PTR)this,CALLBACK_FUNCTION);
		if(mRes!=MMSYSERR_NOERROR)
			FAIL_M("bad");



		const char *csT1 = "C:\\cvs\\stepmania\\speech-test.wav";
		ZeroMemory(&m_stmmIF,sizeof(MMIOINFO));
		DeleteFile((PCHAR)(LPCTSTR)csT1);
		m_hOPFile=mmioOpen((PCHAR)(LPCTSTR)csT1,&m_stmmIF,MMIO_WRITE | MMIO_CREATE);
		if(m_hOPFile==NULL)
			FAIL_M("Can not open file...");

		ZeroMemory(&m_stckOutRIFF,sizeof(MMCKINFO));
		m_stckOutRIFF.fccType = mmioFOURCC('W', 'A', 'V', 'E'); 
		mRes=mmioCreateChunk(m_hOPFile, &m_stckOutRIFF, MMIO_CREATERIFF);
		if(mRes!=MMSYSERR_NOERROR)
		{
			FAIL_M("bad");
		}
		ZeroMemory(&m_stckOut,sizeof(MMCKINFO));
		m_stckOut.ckid = mmioFOURCC('f', 'm', 't', ' ');
		m_stckOut.cksize = sizeof(m_stWFEX);
		mRes=mmioCreateChunk(m_hOPFile, &m_stckOut, 0);
		if(mRes!=MMSYSERR_NOERROR)
		{
			FAIL_M("bad");
		}
		int nT1=mmioWrite(m_hOPFile, (HPSTR) &m_stWFEX, sizeof(m_stWFEX));
		if(nT1!=sizeof(m_stWFEX))
		{
			FAIL_M("bad");
		}
		mRes=mmioAscend(m_hOPFile, &m_stckOut, 0);
		if(mRes!=MMSYSERR_NOERROR)
		{
			FAIL_M("bad");
		}
		m_stckOut.ckid = mmioFOURCC('d', 'a', 't', 'a');
		mRes=mmioCreateChunk(m_hOPFile, &m_stckOut, 0);
		if(mRes!=MMSYSERR_NOERROR)
		{
			FAIL_M("bad");
		}
	}

	void PrepareBuffers()
	{
		MMRESULT mRes=0;
		
		for(int nT1=0;nT1<MAX_BUFFERS;++nT1)
		{
			int iSampleCount = SAMPLES_PER_SEC / 60;	// 60 times per second
			int iBufferSizeBytes = iSampleCount*sizeof(short);
			m_stWHDR[nT1].lpData=(LPSTR)HeapAlloc(GetProcessHeap(),8,iBufferSizeBytes);
			m_stWHDR[nT1].dwBufferLength=iBufferSizeBytes;
			m_stWHDR[nT1].dwUser=nT1;
			mRes=waveInPrepareHeader(m_hWaveIn,&m_stWHDR[nT1],sizeof(WAVEHDR));
			if(mRes!=0)
				FAIL_M("bad");
			mRes=waveInAddBuffer(m_hWaveIn,&m_stWHDR[nT1],sizeof(WAVEHDR));
			if(mRes!=0)
				FAIL_M("bad");
		}
	}

	void ProcessHeader(WAVEHDR * pHdr)
	{
		//LOG->Trace("%d",pHdr->dwUser);
		if(WHDR_DONE==(WHDR_DONE &pHdr->dwFlags))
		{
			//LOG->Trace("Got %d bytes of data", pHdr->dwBytesRecorded );
			int iSampleCount = pHdr->dwBytesRecorded / sizeof(short);
			m_dp.ReadOne( (short*)pHdr->lpData, iSampleCount );

			mmioWrite(m_hOPFile,pHdr->lpData,pHdr->dwBytesRecorded);
			MMRESULT mRes = waveInAddBuffer(m_hWaveIn,pHdr,sizeof(WAVEHDR));
			if(mRes!=0)
				FAIL_M("bad");
		}
	}

	void CloseDevice()
	{
		MMRESULT mRes=0;
		
		if(m_hWaveIn)
		{
			UnPrepareBuffers();
			mRes=waveInClose(m_hWaveIn);
		}
		if(m_hOPFile)
		{
			mRes=mmioAscend(m_hOPFile, &m_stckOut, 0);
			if(mRes!=MMSYSERR_NOERROR)
			{
				FAIL_M("bad");
			}
			mRes=mmioAscend(m_hOPFile, &m_stckOutRIFF, 0);
			if(mRes!=MMSYSERR_NOERROR)
			{
				FAIL_M("bad");
			}
			mmioClose(m_hOPFile,0);
			m_hOPFile=NULL;
		}
		m_hWaveIn=NULL;
	}

	void UnPrepareBuffers()
	{
		MMRESULT mRes=0;

		if(m_hWaveIn)
		{
			mRes=waveInStop(m_hWaveIn);
			for(int nT1=0;nT1<3;++nT1)
			{
				if(m_stWHDR[nT1].lpData)
				{
					mRes=waveInUnprepareHeader(m_hWaveIn,&m_stWHDR[nT1],sizeof(WAVEHDR));
					HeapFree(GetProcessHeap(),0,m_stWHDR[nT1].lpData);
					ZeroMemory(&m_stWHDR[nT1],sizeof(WAVEHDR));
				}
			}
		}
	}
	
	void GetStatus( MicrophoneStatus &out )
	{
		return m_dp.GetStatus( out );
	}
};

MicrophoneImpl *MicrophoneImpl::s_pMicrophoneImpl = NULL;

void CALLBACK waveInProc(HWAVEIN hwi,UINT uMsg,DWORD dwInstance,DWORD dwParam1,DWORD dwParam2)
{
	switch(uMsg)
	{
		case WIM_CLOSE:
			break;

		case WIM_DATA:
			{
				MicrophoneImpl::s_pMicrophoneImpl->ProcessHeader( (WAVEHDR *)dwParam1 );
			}
			break;

		case WIM_OPEN:
			break;

		default:
			break;
	}
}


void PitchDetectionTest::Update()
{
	s_pMicrophone->GetStatus( s_ms );
}

void DoPitchDetectionTest()
{
	MicrophoneImpl mr;
	mr.Init();
	mr.StartRecording();

	//srpd2();		// do f0 tracking
}



Microphone::Microphone()
{
	m_pImpl = new MicrophoneImpl;
	m_pImpl->Init();
	m_pImpl->StartRecording();
}
Microphone::~Microphone()
{
	m_pImpl->StopRecording();
	SAFE_DELETE( m_pImpl );
}

void Microphone::GetStatus( MicrophoneStatus &out )
{
	m_pImpl->GetStatus( out );
	if( !FreqToMidiNote(out.fFreq, out.fMidiNote) )
		out.fMidiNote = 0;
	int iMidiNote = (int)roundf( out.fMidiNote );
	if( !MidiNoteToStringAndOctave( iMidiNote, out.sMidiNote ) )
		out.sMidiNote = "";
}


/*
 * (c) 2007 Chris Danford
 * All rights reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, and/or sell copies of the Software, and to permit persons to
 * whom the Software is furnished to do so, provided that the above
 * copyright notice(s) and this permission notice appear in all copies of
 * the Software and that both the above copyright notice(s) and this
 * permission notice appear in supporting documentation.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
 * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
 * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
 * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
 * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */
