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

#include "ChaSound.h"

///////////////////////////////////////////// ogg /////////////////////////////////////////////

COggAudio::COggAudio(){
	
	lpDSoundBufferSecondary = NULL;
	ZeroMemory( &WFE_Secondary, sizeof( WAVEFORMATEX ));
	Thread = NULL;
	SampleBuffer = NULL;
	Loop = true;
	TotalTime = 0;
	Volume = 0.5;
	PlayingPos = 0;
	ZeroMemory(&OVF, sizeof(OggVorbis_File));
	ParentWnd = NULL;
	ID = 0;
	StartID = 0;
	PlayOnce = false;
}

COggAudio::~COggAudio(){

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

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

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

	return false;
}

bool COggAudio::Play(){

	if (lpDSoundBufferSecondary == NULL)
		return false;

	if (Status == AUDIO_STATUS_STOP){
		Status = AUDIO_STATUS_INIT;
		lpDSoundBufferSecondary->SetCurrentPosition(0);
		ov_time_seek(&OVF, 0);
		PlayingPos = 0;
		memset(SampleBuffer, 0, OGG_BUFFER_SIZE);
		return true;
	}

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

	if (Status == AUDIO_STATUS_INIT){
		PlayingPos = 0;
		BeginStreamOgg();
		memset(SampleBuffer, 0, OGG_BUFFER_SIZE);
	}

	return true;
}

bool COggAudio::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 COggAudio::Stop(){

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

	if (lpDSoundBufferSecondary == NULL)
		return false;

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

	return true;
}

bool COggAudio::Seek(double pos){

	ov_time_seek(&OVF, (double)pos * (double)TotalTime);

	return true;
}

bool COggAudio::Close(){

	Status = AUDIO_STATUS_CLOSE;
	SAFE_RELEASE(lpDSoundBufferSecondary);

	return true;
}

int COggAudio::GetStatus(){

	return Status;
}

int COggAudio::GetTotalTime(){

	if (Status == AUDIO_STATUS_CLOSE)
		return 0;

	return TotalTime;
}

double COggAudio::GetPlayingTime(){

	return ov_time_tell(&OVF);
}

double COggAudio::GetPlayPos(){

	if (Status == AUDIO_STATUS_CLOSE)
		return 0.0;

	if (TotalTime == 0)
		return 0.0;

	return (double)ov_time_tell(&OVF) / (double)TotalTime;
}

double COggAudio::GetVolume(){

	return Volume;
}

bool COggAudio::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 COggAudio::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 COggAudio::SetLoop(bool loop){

	Loop = loop;

	return Loop;
}

bool COggAudio::GetLoop(){

	return Loop;
}

bool COggAudio::SetPlayOnce(bool play_once){

	PlayOnce = play_once;

	if (PlayOnce == true)
		Loop = false;

	return PlayOnce;
}

bool COggAudio::GetPlayOnce(){

	return PlayOnce;
}

int COggAudio::GetBufferPos(){

	return PlayingPos;
}

bool COggAudio::CreateOggSoundBuffer(LPDIRECTSOUND lpDSound, LPDIRECTSOUNDBUFFER *lpBuffer, LPWSTR filename){

	char ogg_filename[SIZE_CHAR_MAX];
	memset(ogg_filename, 0, SIZE_CHAR_MAX);
	size_t size;
	wcstombs_s(&size, ogg_filename, SIZE_CHAR_MAX, filename, SIZE_CHAR_MAX);

	//Oggファイルを開く。
	int error = ov_fopen(ogg_filename, &OVF);
	if (error != 0){
		return false;
	}

	WFE_Secondary = (LPWAVEFORMATEX) new char[sizeof(WAVEFORMATEX)];
	if (!WFE_Secondary){
		return false;
	}
	ZeroMemory(WFE_Secondary, sizeof(WAVEFORMATEX));
	WFE_Secondary->cbSize = sizeof(WAVEFORMATEX);
	WFE_Secondary->wFormatTag = WAVE_FORMAT_PCM;
	WFE_Secondary->nChannels = 2;
	WFE_Secondary->nSamplesPerSec = 44100;
	WFE_Secondary->wBitsPerSample = 16;
	WFE_Secondary->nBlockAlign = WFE_Secondary->nChannels * WFE_Secondary->wBitsPerSample / 8;
	WFE_Secondary->nAvgBytesPerSec = WFE_Secondary->nSamplesPerSec * WFE_Secondary->nBlockAlign;

	//バッファを生成
	DSBUFFERDESC desc;
	ZeroMemory(&desc, sizeof(DSBUFFERDESC));
	desc.dwSize = sizeof(DSBUFFERDESC);
	desc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_STATIC | DSBCAPS_LOCDEFER | DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLVOLUME;
	desc.dwBufferBytes = OGG_BUFFER_SIZE;
	desc.lpwfxFormat = WFE_Secondary;
	desc.guid3DAlgorithm = DS3DALG_DEFAULT;
	HRESULT result = lpDSound->CreateSoundBuffer(&desc, lpBuffer, NULL);
	if (FAILED(result)){
		delete WFE_Secondary;
		return false;
	}

	SampleBuffer = new char[OGG_BUFFER_SIZE];
	memset(SampleBuffer, 0, OGG_BUFFER_SIZE);
	TotalTime = (int) ov_time_total(&OVF, 0);

	return true;
}

unsigned __stdcall  COggAudio::ThreadOggStream(LPVOID pParam){

	COggAudio *audio = (COggAudio *)pParam;
	bool playpos_flag = true;
	DWORD point = 0;
	LPVOID pMem1, pMem2;
	DWORD size1, size2;
	int bitstream = 0;

	while (true){

		unsigned int request_size = OGG_BUFFER_SIZE / 2;

		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, OGG_BUFFER_SIZE);
			audio->lpDSoundBufferSecondary->Unlock(pMem1, size1, pMem2, size2);
			Sleep(100);
			continue;
		}

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

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

		audio->lpDSoundBufferSecondary->GetCurrentPosition(&point, 0);
		audio->PlayingPos = point;
		if (playpos_flag == false && point > request_size){
			//バッファの前半に書き込み
			HRESULT result = audio->lpDSoundBufferSecondary->Lock(0, request_size, &pMem1, &size1, &pMem2, &size2, 0);
			int comsize = 0;
			int readsize = 0;
			while (true){
				readsize = ov_read(&audio->OVF, (char*)pMem1 + comsize, request_size, 0, 2, 1, &bitstream);
				comsize += readsize;
				if (comsize >= OGG_BUFFER_SIZE / 2)
					break;
				if (readsize <= 0)
					break;
				request_size = OGG_BUFFER_SIZE / 2 - comsize;
			}
			if (comsize < OGG_BUFFER_SIZE / 2){
				memset((char *)pMem1 + comsize, 0, OGG_BUFFER_SIZE / 2 - comsize);
			}
			memcpy(audio->SampleBuffer, (char *)pMem1, OGG_BUFFER_SIZE / 2);
			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 (readsize == 0){
				if (audio->Loop == true){
					ov_time_seek(&audio->OVF, 0);
					memset(audio->SampleBuffer, 0, OGG_BUFFER_SIZE);
				}
				else{
					ov_time_seek(&audio->OVF, 0);
					audio->Stop();
					memset(audio->SampleBuffer, 0, OGG_BUFFER_SIZE);
				}
				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 comsize = 0;
			int readsize = 0;
			while (true){
				readsize = ov_read(&audio->OVF, (char*)pMem1 + comsize, request_size, 0, 2, 1, &bitstream);
				comsize += readsize;
				if (comsize >= OGG_BUFFER_SIZE / 2)
					break;
				if (readsize <= 0)
					break;
				request_size = OGG_BUFFER_SIZE / 2 - comsize;
			}
			if (comsize < OGG_BUFFER_SIZE / 2){
				memset((char *)pMem1 + comsize, 0, OGG_BUFFER_SIZE / 2 - comsize);
			}
			memcpy(audio->SampleBuffer + OGG_BUFFER_SIZE / 2, (char *)pMem1, OGG_BUFFER_SIZE / 2);
			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 (readsize == 0){
				if (audio->Loop == true){
					ov_time_seek(&audio->OVF, 0);
					memset(audio->SampleBuffer, 0, OGG_BUFFER_SIZE);
				}
				else{
					ov_time_seek(&audio->OVF, 0);
					audio->Stop();
					memset(audio->SampleBuffer, 0, OGG_BUFFER_SIZE);
				}
				if (audio->ParentWnd != NULL)
					::SendMessage(audio->ParentWnd, WM_COMMAND, NOTIFY_PLAY_FINISHED, audio->ID + audio->StartID);
			}
		}

		Sleep(100);
	}

	return 1;
}

bool COggAudio::BeginStreamOgg(){

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

	return true;
}

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

	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 >= OGG_BUFFER_SIZE){
			pad_l = OGG_BUFFER_SIZE;
		}
		else
			pad_l = 0;
		if (sample_pos + i * WFE_Secondary->nChannels + WFE_Secondary->nChannels >= OGG_BUFFER_SIZE){
			pad_r = OGG_BUFFER_SIZE + 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;
}
