///////////////////////////////////////////////////
// filename:WAVEAudio.cpp
// author: Chafumi Touji
// version: 1.0.1
// date: 2018/09/27
//		 2020/02/04
///////////////////////////////////////////////////

#include "ChaSound.h"

CWAVEAudio::CWAVEAudio(){

	hmmio = NULL;
	lpDSoundBufferSecondary = NULL;
	ZeroMemory( &WFE_Secondary, sizeof( WAVEFORMATEX ));
	Thread = NULL;
	SampleBuffer = NULL;
	Pos = 0;
	WaveSize = 0;
	WaveOffset = 0;
	LoadSize = 0;
	TotalTime = 0;
	Loop = true;
	Volume = 0.5;
	PlayingPos = 0;
	Status = AUDIO_STATUS_CLOSE;
	ParentWnd = NULL;
	ID = 0;
	StartID = 0;
	PlayOnce = false;
}

CWAVEAudio::~CWAVEAudio(){

	Close();
	if (SampleBuffer != NULL)
		delete[] SampleBuffer;
	SampleBuffer = NULL;
}

bool CWAVEAudio::Open(CPCMAudio *audio, wchar_t *filename, HWND parent_wnd, int id, int start_id ){

	if (Status != AUDIO_STATUS_CLOSE)
		return false;

	if (CreateSoundBuffer(audio->lpDSound, &lpDSoundBufferSecondary, filename) == true){
		ParentWnd = parent_wnd;
		ID = id;
		StartID = start_id;
		Status = AUDIO_STATUS_INIT;
		return true;
	}

	return false;
}

bool CWAVEAudio::Play(){

	if (lpDSoundBufferSecondary == NULL)
		return false;

	if (Status == AUDIO_STATUS_STOP){
		Status = AUDIO_STATUS_INIT;
		lpDSoundBufferSecondary->SetCurrentPosition(0);
		mmioSeek(hmmio, WaveOffset, SEEK_SET);
		return true;
	}

	if (Status == AUDIO_STATUS_PAUSE){
		Status = AUDIO_STATUS_PLAYING;
		return true;
	}

	if (Status == AUDIO_STATUS_INIT){
		BeginStream();
	}

	return true;
}

bool CWAVEAudio::Pause(){

	if (Status == AUDIO_STATUS_INIT || Status == AUDIO_STATUS_CLOSE)
		return false;

	if (Status == AUDIO_STATUS_PAUSE)
		return true;

	if (Status == AUDIO_STATUS_STOP)
		return true;

	Status = AUDIO_STATUS_PAUSE;

	return true;
}

bool CWAVEAudio::Stop(){

	if (Status == AUDIO_STATUS_INIT || Status == AUDIO_STATUS_CLOSE)
		return false;

	if (lpDSoundBufferSecondary == NULL)
		return false;

	if (Status == AUDIO_STATUS_PAUSE){
		lpDSoundBufferSecondary->SetCurrentPosition(0);
	}
	Status = AUDIO_STATUS_STOP;

	return true;
}

bool CWAVEAudio::Seek(double pos){

	int seek_pos = (int)(((double)WaveSize) * pos);
	int num = seek_pos / WFE_Secondary->nAvgBytesPerSec;
	seek_pos = num * WFE_Secondary->nAvgBytesPerSec;
	mmioSeek(hmmio, seek_pos + WaveOffset, SEEK_SET);
	LoadSize = seek_pos;

	return true;
}

bool CWAVEAudio::Close(){

	Status = AUDIO_STATUS_CLOSE;
	mmioClose(hmmio, 0);
	SAFE_RELEASE(lpDSoundBufferSecondary);
	Pos = 0;

	return true;
}

int CWAVEAudio::GetStatus(){

	return Status;
}

int CWAVEAudio::GetTotalTime(){

	if (Status == AUDIO_STATUS_CLOSE)
		return 0;

	return TotalTime;
}

double CWAVEAudio::GetPlayingTime(){

	return ((double)TotalTime * ((double)Pos / (double)WaveSize));
}

int CWAVEAudio::GetBufferPos(){

	return PlayingPos;
}

double CWAVEAudio::GetPlayPos(){

	if (WaveSize == 0)
		return 0.0;

	return (double)Pos / (double)WaveSize;
}

double CWAVEAudio::GetVolume(){

	return Volume;
}

bool CWAVEAudio::SetVolume( double volume, bool is_mute  ){

	if (lpDSoundBufferSecondary == NULL)
		return false;

	Volume = volume;
	if (Volume < 0.0)
		Volume = 0.0;
	if (Volume > 1.0)
		Volume = 1.0;
	if (is_mute == false){
		if (Volume > 0.0){
			double vol = 1500.0 * log(((double)Volume * 10000.0f) / 10000.0);
			lpDSoundBufferSecondary->SetVolume((LONG)vol);
		}
		else
			lpDSoundBufferSecondary->SetVolume(DSBVOLUME_MIN);
	}

	return true;
}

bool CWAVEAudio::Mute(bool do_mute){

	if (lpDSoundBufferSecondary == NULL)
		return false;

	static double back_volume = -10000000.0;
	if (do_mute == true){
		lpDSoundBufferSecondary->SetVolume(DSBVOLUME_MIN);
		back_volume = Volume;
	}
	else{
		if (back_volume > -100000.0)
			Volume = back_volume;
		if (Volume < 0.0)
			Volume = 0.0;
		if (Volume > 1.0)
			Volume = 1.0;
		if (Volume > 0.0){
			double vol = 1500.0 * log(((double)Volume * 10000.0f) / 10000.0);
			lpDSoundBufferSecondary->SetVolume((LONG)vol);
		}
		else
			lpDSoundBufferSecondary->SetVolume(DSBVOLUME_MIN);
	}

	return true;
}

bool CWAVEAudio::SetLoop(bool loop){

	Loop = loop;

	return Loop;
}

bool CWAVEAudio::GetLoop(){

	return Loop;
}

bool CWAVEAudio::SetPlayOnce(bool play_once){

	PlayOnce = play_once;

	if (PlayOnce == true)
		Loop = false;

	return PlayOnce;
}

bool CWAVEAudio::GetPlayOnce(){

	return PlayOnce;
}

bool CWAVEAudio::CreateSoundBuffer(LPDIRECTSOUND lpDSound, LPDIRECTSOUNDBUFFER *lpBuffer, LPWSTR filename){

	//WAVEファイル再生用

	hmmio = mmioOpen(filename, NULL, MMIO_READ);
	HRESULT result;

	//WAVEファイルを開く。
	if (hmmio == NULL){
		return false;
	}

	//WAVEファイルチェック。
	MMCKINFO mmckfile;
	ZeroMemory(&mmckfile, sizeof(MMCKINFO));
	mmckfile.fccType = mmioFOURCC('W', 'A', 'V', 'E');
	result = mmioDescend(hmmio, &mmckfile, NULL, MMIO_FINDRIFF);
	if (FAILED(result)){
		mmioClose(hmmio, 0);
		return false;
	}

	WaveSize = mmckfile.cksize;

	MMCKINFO mmckfmt;
	ZeroMemory(&mmckfmt, sizeof(MMCKINFO));
	mmckfmt.fccType = mmioFOURCC('f', 'm', 't', ' ');
	result = mmioDescend(hmmio, &mmckfmt, &mmckfile, MMIO_FINDCHUNK);
	if (FAILED(result)){
		mmioClose(hmmio, 0);
		return false;
	}
	int fmtsize = mmckfmt.cksize;

	int headersize = fmtsize;
	if (headersize < sizeof(WAVEFORMATEX))
		headersize = sizeof(WAVEFORMATEX);
	WFE_Secondary = (LPWAVEFORMATEX) new char[headersize];
	if (!WFE_Secondary){
		mmioClose(hmmio, 0);
		return false;
	}
	ZeroMemory(WFE_Secondary, headersize);

	mmioRead(hmmio, (char *)WFE_Secondary, mmckfmt.cksize);
	mmioAscend(hmmio, &mmckfmt, 0);

	WaveOffset = mmioSeek( hmmio, 0, SEEK_CUR );
	WaveSize -= WaveOffset;

	//WAVEデータロード
	MMCKINFO mmckdata;
	while (true){
		result = mmioDescend(hmmio, &mmckdata, &mmckfile, 0);
		if (FAILED(result)){
			delete WFE_Secondary;
			mmioClose(hmmio, 0);
			return false;
		}
		if (mmckdata.ckid == mmioStringToFOURCC(TEXT("data"), 0))
			break;
		mmioAscend(hmmio, &mmckdata, 0);
	}

	DSBUFFERDESC desc;
	ZeroMemory(&desc, sizeof(DSBUFFERDESC));
	desc.dwSize = sizeof(DSBUFFERDESC);
	desc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_STATIC | DSBCAPS_LOCDEFER | DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLVOLUME;
	desc.dwBufferBytes = WFE_Secondary->nAvgBytesPerSec * PLAY_TIME;
	desc.lpwfxFormat = WFE_Secondary;
	desc.guid3DAlgorithm = DS3DALG_DEFAULT;
	result = lpDSound->CreateSoundBuffer(&desc, lpBuffer, NULL);
	if (FAILED(result)){
		delete WFE_Secondary;
		mmioClose(hmmio, 0);
		return false;
	}

	SampleBuffer = new char[WFE_Secondary->nAvgBytesPerSec * PLAY_TIME];
	memset(SampleBuffer, 0, WFE_Secondary->nAvgBytesPerSec * PLAY_TIME);
	TotalTime = WaveSize / WFE_Secondary->nAvgBytesPerSec;

	return true;
}

unsigned __stdcall  CWAVEAudio::ThreadStream(LPVOID pParam){

	CWAVEAudio *audio = (CWAVEAudio *)pParam;

	unsigned int request_size = audio->WFE_Secondary->nAvgBytesPerSec * PLAY_TIME / 2;
	bool playpos_flag = true;
	DWORD point = 0;
	LPVOID pMem1, pMem2;
	DWORD size1, size2;
	audio->Pos = 0;

	while (true){

		if (audio->Status == AUDIO_STATUS_PAUSE){
			audio->lpDSoundBufferSecondary->Lock(0, request_size * 2, &pMem1, &size1, &pMem2, &size2, 0);
			memset(pMem1, 0, request_size * 2);
			memset(audio->SampleBuffer, 0, request_size * 2);
			audio->lpDSoundBufferSecondary->Unlock(pMem1, size1, pMem2, size2);
			Sleep(100);
			continue;
		}

		if (audio->Status == AUDIO_STATUS_STOP){
			audio->Pos = 0;
			audio->lpDSoundBufferSecondary->Lock(0, request_size * 2, &pMem1, &size1, &pMem2, &size2, 0);
			memset(pMem1, 0, request_size * 2);
			memset(audio->SampleBuffer, 0, request_size * 2);
			audio->lpDSoundBufferSecondary->Unlock(pMem1, size1, pMem2, size2);
			Sleep(100);
			continue;
		}

		if (audio->Status == AUDIO_STATUS_CLOSE)
			break;

		audio->lpDSoundBufferSecondary->GetCurrentPosition(&point, 0);
		audio->PlayingPos = point;
		audio->Pos = mmioSeek( audio->hmmio, 0, SEEK_CUR ) - audio->WaveOffset;
		if ( playpos_flag == false && point > request_size){
			//バッファの前半に書き込み
			HRESULT result = audio->lpDSoundBufferSecondary->Lock(0, request_size, &pMem1, &size1, &pMem2, &size2, 0);
			int readsize = mmioRead(audio->hmmio, (char *)pMem1, request_size);
			audio->LoadSize += readsize;
			if (readsize < (int) request_size)
				memset((char *)pMem1 + readsize + 1, 0, request_size - readsize);
			memcpy(audio->SampleBuffer, pMem1, request_size);
			audio->lpDSoundBufferSecondary->Unlock(pMem1, size1, pMem2, size2);
			playpos_flag = true;
			if (audio->Status != AUDIO_STATUS_PLAYING){
				audio->lpDSoundBufferSecondary->Play(0, 0, DSBPLAY_LOOPING);
				audio->Status = AUDIO_STATUS_PLAYING;
			}
			if (audio->LoadSize >= audio->WaveSize){
				audio->LoadSize = 0;
				if (audio->Loop == true){
					audio->Pos = 0;
					mmioSeek(audio->hmmio, audio->WaveOffset, SEEK_SET);
				}
				else
					audio->Stop();
				if (audio->ParentWnd != NULL)
					::SendMessage(audio->ParentWnd, WM_COMMAND, NOTIFY_PLAY_FINISHED, audio->ID + audio->StartID);
			}
		}
		else if (playpos_flag == true && point <= request_size){
			//バッファの後半に書き込み
			HRESULT result = audio->lpDSoundBufferSecondary->Lock(request_size, request_size * 2, &pMem1, &size1, &pMem2, &size2, 0);
			int readsize = mmioRead(audio->hmmio, (char *)pMem1, request_size);
			audio->LoadSize += readsize;
			if (readsize < (int) request_size)
				memset((char *)pMem1 + readsize + 1, 0, request_size - readsize);
			memcpy(audio->SampleBuffer + request_size, pMem1, request_size);
			audio->lpDSoundBufferSecondary->Unlock(pMem1, size1, pMem2, size2);
			playpos_flag = false;
			if (audio->Status != AUDIO_STATUS_PLAYING){
				audio->lpDSoundBufferSecondary->Play(0, 0, DSBPLAY_LOOPING);
				audio->Status = AUDIO_STATUS_PLAYING;
			}
			if (audio->LoadSize >= audio->WaveSize){
				audio->LoadSize = 0;
				if (audio->Loop == true){
					audio->Pos = 0;
					mmioSeek(audio->hmmio, audio->WaveOffset, SEEK_SET);
				}
				else
					audio->Stop();
				if (audio->ParentWnd != NULL)
					::SendMessage(audio->ParentWnd, WM_COMMAND, NOTIFY_PLAY_FINISHED, audio->ID + audio->StartID);
			}
		}

		Sleep(100);
	}

	return true;
}

bool CWAVEAudio::BeginStream(){

	Thread = (HANDLE)_beginthreadex(NULL, 0, ThreadStream, this, 0, NULL);

	return true;
}

bool CWAVEAudio::GetSample(int *sample_left, int *sample_right, int sample_count){

	if (lpDSoundBufferSecondary != NULL){
		DWORD point;
		lpDSoundBufferSecondary->GetCurrentPosition(&point, 0);
		PlayingPos = point;
	}

	DWORD sample_pos = PlayingPos;

	int pad_l = 0;
	int pad_r = 0;
	for (int i = 0; i <= sample_count - 1; i++){
		if (sample_pos + i * WFE_Secondary->nChannels >= WFE_Secondary->nAvgBytesPerSec * PLAY_TIME){
			pad_l = WFE_Secondary->nAvgBytesPerSec * PLAY_TIME;
		}
		else
			pad_l = 0;
		if (sample_pos + i * WFE_Secondary->nChannels + WFE_Secondary->nChannels >= WFE_Secondary->nAvgBytesPerSec * PLAY_TIME){
			pad_r = WFE_Secondary->nAvgBytesPerSec * PLAY_TIME + WFE_Secondary->nChannels;
		}
		else
			pad_r = 0;
		short left_f = 0;
		short right_f = 0;
		memcpy(&left_f, &SampleBuffer[sample_pos + i * WFE_Secondary->nChannels - pad_l] + WFE_Secondary->nChannels, WFE_Secondary->wBitsPerSample / 8);
		memcpy(&right_f, &SampleBuffer[sample_pos + i * WFE_Secondary->nChannels - pad_r], WFE_Secondary->wBitsPerSample / 8);
		sample_left[i] = 0;
		sample_right[i] = 0;
		sample_left[i] = left_f;
		sample_right[i] = right_f;
	}

	return true;
}
