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

#include "ChaSound.h"

CMP3Audio::CMP3Audio(){

	hmmio = NULL;
	lpDSoundBufferSecondary = NULL;
	ZeroMemory(&WFE_MP3,  sizeof(MPEGLAYER3WAVEFORMAT));
	ZeroMemory( &WFE_Secondary, sizeof( WAVEFORMATEX ));
	Thread = NULL;
	MP3Offset = 0;
	WaveBlockSize = 0;
	TotalTime = 0;
	WaveSize = 0;
	Pos = 0;
	Loop = true;
	Volume = 0.5;
	WaveDataMargin = WAVE_DATA_MARGIN_SIZE;
	StreamBuffer = new char[MP3_STREAM_BUFFER_SIZE];
	memset(StreamBuffer, 0, MP3_STREAM_BUFFER_SIZE);
	SampleBuffer = NULL;
	ParentWnd = NULL;
	ID = 0;
	StartID = 0;
	PlayOnce = false;
}

CMP3Audio::~CMP3Audio(){

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

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

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

	return false;
}

bool CMP3Audio::Play(){

	if (lpDSoundBufferSecondary == NULL)
		return false;

	if (Status == AUDIO_STATUS_STOP){
		Seek(0);
		Status = AUDIO_STATUS_PLAYING;
		return true;
	}

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

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

	return true;
}

bool CMP3Audio::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 CMP3Audio::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 CMP3Audio::Seek(double pos){

	Pos = (int) (((double)MP3Size) * pos );
	mmioSeek(hmmio, Pos + MP3Offset, SEEK_SET);

	return true;
}

bool CMP3Audio::Close(){

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

	return true;
}

int CMP3Audio::GetStatus(){

	return Status;
}

int CMP3Audio::GetTotalTime(){

	if (Status == AUDIO_STATUS_CLOSE)
		return 0;

	return TotalTime;
}

double CMP3Audio::GetPlayingTime(){

	if (WaveSize == 0)
		return 0;

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

double CMP3Audio::GetPlayPos(){

	if (WaveSize == 0)
		return 0.0;

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

double CMP3Audio::GetVolume(){

	return Volume;
}

bool CMP3Audio::SetLoop(bool loop){

	Loop = loop;

	return Loop;
}

bool CMP3Audio::GetLoop(){

	return Loop;
}

bool CMP3Audio::SetPlayOnce(bool play_once){

	PlayOnce = play_once;

	if (PlayOnce == true)
		Loop = false;

	return PlayOnce;
}

bool CMP3Audio::GetPlayOnce(){

	return PlayOnce;
}

bool CMP3Audio::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 CMP3Audio::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;
}

int CMP3Audio::GetBufferPos(){

	return PlayingPos;
}

bool CMP3Audio::ReadMP3FormatInfo(MP3FORMATINFO *format_info){

	int bitrate_table[][16] = {
		{ 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 }, //MPEG1
		{ 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 } //MPEG2
	};

	int sampling_rate_table[][3] = {
		{ 44100, 48000, 32000 }, //MPEG1
		{ 22050, 24000, 16000 } //MPEG2
	};

	unsigned char MP3FrameHeader[4];
	mmioRead(hmmio, (HPSTR)MP3FrameHeader, 4);

	//同期ヘッダ確認
	if (MP3FrameHeader[0] != 0xff || (MP3FrameHeader[1] >> 4) != 0x0f){

		format_info->BitRate = bitrate_table[0][9];
		format_info->SamplingRate = sampling_rate_table[0][0];
		format_info->Padding = 0;
		format_info->ChannelMode = 2;
		mmioSeek(hmmio, -4, SEEK_CUR);

		return false;
	}

	char version = (MP3FrameHeader[1] >> 3) & 0x03;
	char bitrate = (MP3FrameHeader[2] >> 4) & 0x0f;
	char sample_rate = (MP3FrameHeader[2] >> 2) & 0x03;
	char padding = (MP3FrameHeader[2] >> 1) & 0x01;
	char channel = (MP3FrameHeader[3] & 0x03);

	if (version == 3)
		version = 1; //version1
	else if (version == 2)
		version = 2; //version2;
	else{
		mmioClose(hmmio, 0);
		return false;
	}

	format_info->BitRate = bitrate_table[version - 1][bitrate];
	format_info->SamplingRate = sampling_rate_table[version - 1][sample_rate];
	format_info->Padding = padding;
	format_info->ChannelMode = channel == 3 ? 1 : 2;

	mmioSeek(hmmio, -4, SEEK_CUR);

	return true;
}

bool CMP3Audio::InitMP3DecodeStream(WAVEFORMATEX *src_wfe, WAVEFORMATEX *dest_wfe){

	dest_wfe->wFormatTag = WAVE_FORMAT_PCM;
	acmFormatSuggest(NULL, src_wfe, dest_wfe, sizeof(WAVEFORMATEX), ACM_FORMATSUGGESTF_WFORMATTAG);

	HRESULT result = acmStreamOpen(&HAS, NULL, src_wfe, dest_wfe, NULL, 0, 0, ACM_STREAMOPENF_NONREALTIME);
	if (FAILED(result)){
		return false;
	}

	return true;
}

int CMP3Audio::mmioReadMP3(HMMIO hmmio, char *pMem, int request_size){

	DWORD source_size = 0;
	acmStreamSize(HAS, request_size, (LPDWORD)&source_size, ACM_STREAMSIZEF_DESTINATION);
	memset(StreamBuffer, 0, MP3_STREAM_BUFFER_SIZE);
	mmioRead(hmmio, (HPSTR)StreamBuffer, source_size);

	if (Status == AUDIO_STATUS_STOP)
		memset(StreamBuffer, 0, source_size);
	
	ZeroMemory(&ASH, sizeof(ACMSTREAMHEADER));
	ASH.cbStruct = sizeof(ACMSTREAMHEADER);
	ASH.pbSrc = (LPBYTE)StreamBuffer;
	ASH.cbSrcLength = source_size;
	ASH.pbDst = (LPBYTE)pMem;
	ASH.cbDstLength = request_size;

	acmStreamPrepareHeader(HAS, &ASH, 0);
	acmStreamConvert(HAS, &ASH, ACM_STREAMCONVERTF_BLOCKALIGN);
	acmStreamUnprepareHeader(HAS, &ASH, 0);

	return ASH.cbDstLengthUsed;
}

bool CMP3Audio::CreateMP3SoundBuffer(LPDIRECTSOUND lpDS, LPDIRECTSOUNDBUFFER *lpBuffer, LPWSTR filename){

	HRESULT result;
	ID3HEADER ID3HDR;
	ID3FRAMEHEADER ID3FHDR;

	//MP3ファイルを開く。
	hmmio = mmioOpen((LPWSTR)filename, NULL, MMIO_READ);
	if (hmmio == NULL){
		return false;
	}

	int mp3size = (int)mmioSeek(hmmio, 0, SEEK_END);
	mmioSeek(hmmio, 0, SEEK_SET);

	//MP3ファイルチェック。
	ZeroMemory(&ID3HDR, sizeof(ID3HDR));
	mmioRead(hmmio, (HPSTR)&ID3HDR, 10);
	char id3[16];
	int readsize = 0;
	memset(id3, 0, 16);
	memcpy(id3, ID3HDR.Identifier, 3);
	if (strcmp(id3, "ID3") == 0){

		/////////////////////////// ID3タグチェック /////////////////////////////////

		int tagsize = ID3HDR.TagSize[0] & 0x7f;
		tagsize = (tagsize << 7) + (ID3HDR.TagSize[1] & 0x7f);
		tagsize = (tagsize << 7) + (ID3HDR.TagSize[2] & 0x7f);
		tagsize = (tagsize << 7) + (ID3HDR.TagSize[3] & 0x7f);

		int datasize = 0;

		int millisec = 0;
		ID3FRAMEDATA *frame_data;
		while (readsize < tagsize){

			ZeroMemory(&ID3HDR, sizeof(ID3HDR));
			mmioRead(hmmio, (HPSTR)&ID3FHDR, sizeof(ID3HDR));
			readsize += sizeof(ID3HDR);

			int fsize = ID3FHDR.FrameSize[0];
			fsize = (fsize << 8) + ID3FHDR.FrameSize[1];
			fsize = (fsize << 8) + ID3FHDR.FrameSize[2];
			fsize = (fsize << 8) + ID3FHDR.FrameSize[3];

			if (fsize == 0){
				//無効なフレームデータ。
				mmioSeek(hmmio, tagsize - readsize, SEEK_CUR);
				readsize += (tagsize - readsize);
				break;
			}

			frame_data = (ID3FRAMEDATA *) new char[sizeof(ID3FRAMEDATA)];
			frame_data->Data = new char[fsize];
			memset(frame_data->Data, 0, fsize);
			memcpy(frame_data->FrameID, ID3FHDR.FrameID, 4);
			frame_data->FrameSize = fsize;
			mmioRead(hmmio, frame_data->Data, frame_data->FrameSize);
			readsize += frame_data->FrameSize;
			//		MP3Data->ID3List->InsTail((void *)frame_data);

			char id_tag[5];
			memset(id_tag, 0, 5);
			memcpy(id_tag, frame_data->FrameID, 4);
			if (strcmp(id_tag, "TLEN") == 0){
				for (int i = 0; i <= frame_data->FrameSize - 1; i++){
					millisec *= 10;
					char c = frame_data->Data[i];
					millisec += atoi(&c);
				}
			}
		}
	}
	else
		mmioSeek(hmmio, -10, SEEK_CUR);

	////////////////////// MP3フォーマットチェック ////////////////////////

	MP3FORMATINFO format_info;
	ZeroMemory(&format_info, sizeof(MP3FORMATINFO));
	ReadMP3FormatInfo(&format_info);

	MP3Offset = readsize + sizeof(MP3FORMATINFO);

	WFE_Secondary = (LPWAVEFORMATEX) new char[sizeof(WAVEFORMATEX)];
	if (!WFE_Secondary){
		mmioClose(hmmio, 0);
		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;

	WFE_MP3 = (LPMPEGLAYER3WAVEFORMAT) new char[sizeof(MPEGLAYER3WAVEFORMAT)];
	ZeroMemory(WFE_MP3, sizeof(MPEGLAYER3WAVEFORMAT));
	WFE_MP3->wfx.wFormatTag = WAVE_FORMAT_MPEGLAYER3;
	WFE_MP3->wfx.nChannels = format_info.ChannelMode;
	WFE_MP3->wfx.nSamplesPerSec = format_info.SamplingRate;
	WFE_MP3->wfx.nAvgBytesPerSec = (format_info.BitRate * 1000) / 8;
	WFE_MP3->wfx.wBitsPerSample = 0;
	WFE_MP3->wfx.nBlockAlign = 1;
	WFE_MP3->wfx.cbSize = MPEGLAYER3_WFX_EXTRA_BYTES;
	WFE_MP3->wID = MPEGLAYER3_ID_MPEG;
	WFE_MP3->fdwFlags = format_info.Padding ? MPEGLAYER3_FLAG_PADDING_ON : MPEGLAYER3_FLAG_PADDING_OFF;
	WFE_MP3->nBlockSize = (1152 * format_info.BitRate * 1000 / format_info.SamplingRate) / 8 + format_info.Padding;
	WFE_MP3->nFramesPerBlock = 1;
	WFE_MP3->nCodecDelay = 0x571;

	InitMP3DecodeStream(&WFE_MP3->wfx, WFE_Secondary);
	acmStreamSize(HAS, WFE_MP3->nBlockSize, (LPDWORD)&WaveBlockSize, ACM_STREAMSIZEF_SOURCE);

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

	SampleBuffer = new char[WaveBlockSize * MP3_BLOCK_NUM];
	memset(SampleBuffer, 0, WaveBlockSize * MP3_BLOCK_NUM);

	MP3Size = mmioSeek(hmmio, 0, SEEK_END);
	MP3Size -= MP3Offset;
	TotalTime = MP3Size / WFE_MP3->wfx.nAvgBytesPerSec;
	mmioSeek(hmmio, MP3Offset, SEEK_SET);

	WaveSize = 0;
	acmStreamSize(HAS, MP3Size, (LPDWORD)&WaveSize, ACM_STREAMSIZEF_SOURCE);

	return true;
}

unsigned __stdcall  CMP3Audio::ThreadMP3Stream(LPVOID pParam){

	CMP3Audio *audio = (CMP3Audio *)pParam;
	
	bool playpos_flag = false;
	DWORD point = 0;
	LPVOID pMem1, pMem2;
	DWORD size1, size2;
	DWORD insert_pos = 0;
	bool data_load = false;
	audio->Pos = 0;

	DWORD request_size = audio->WaveBlockSize * MP3_BLOCK_NUM / 2;
	char *MemTmp = new char[request_size];
	memset(MemTmp, 0, request_size);

	mmioSeek(audio->hmmio, audio->MP3Offset, SEEK_SET);
	audio->Pos = 0;
	audio->PlayingPos = 0;
	if (audio->Status == AUDIO_STATUS_INIT){
		audio->lpDSoundBufferSecondary->Play(0, 0, DSBPLAY_LOOPING);
	}

	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(MemTmp, 0, request_size);
			memset( audio->SampleBuffer, 0, request_size * 2);
			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(MemTmp, 0, request_size);
			memset(audio->SampleBuffer, 0, request_size * 2);
			audio->lpDSoundBufferSecondary->Unlock(pMem1, size1, pMem2, size2);

			mmioSeek(audio->hmmio, audio->MP3Offset, SEEK_SET);
			audio->Pos = 0;
			audio->PlayingPos = 0;
			audio->lpDSoundBufferSecondary->SetCurrentPosition(0);

			//↓データを0でバッファを更新続ける。
		}

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

		audio->lpDSoundBufferSecondary->GetCurrentPosition(&point, 0);
		audio->PlayingPos = point;
		audio->Pos = mmioSeek(audio->hmmio, 0, SEEK_CUR);
		if ( point == 0 || (insert_pos - 3000 > point && insert_pos - point < request_size) || (insert_pos + 3000 < point && insert_pos + (request_size * 2 - point) < request_size))
			data_load = true;
		else
			data_load = false;

		if (data_load == true){
			data_load = false;
			int readsize = 0;
			if (request_size * 2 - insert_pos > request_size){
				audio->lpDSoundBufferSecondary->Lock(insert_pos, insert_pos + request_size, &pMem1, &size1, &pMem2, &size2, 0);
				readsize = audio->mmioReadMP3(audio->hmmio, (char *)pMem1, request_size);
				memcpy(audio->SampleBuffer + insert_pos, pMem1, request_size);
				audio->lpDSoundBufferSecondary->Unlock(pMem1, size1, pMem2, size2);
				insert_pos += readsize;
				if (audio->Status != AUDIO_STATUS_PLAYING && audio->Status != AUDIO_STATUS_STOP ){
					audio->Status = AUDIO_STATUS_PLAYING;
				}
			}
			else{
				memset(MemTmp, 0, request_size);
				readsize = audio->mmioReadMP3(audio->hmmio, MemTmp, request_size);
				if (insert_pos + readsize > request_size * 2){
					audio->lpDSoundBufferSecondary->Lock(insert_pos, request_size * 2, &pMem1, &size1, &pMem2, &size2, 0);
					memcpy(pMem1, MemTmp, request_size * 2 - insert_pos);
					memcpy(audio->SampleBuffer + insert_pos, pMem1, request_size * 2 - insert_pos );
					audio->lpDSoundBufferSecondary->Unlock(pMem1, size1, pMem2, size2);

					audio->lpDSoundBufferSecondary->Lock(0, insert_pos + readsize - request_size * 2, &pMem1, &size1, &pMem2, &size2, 0);
					memcpy( pMem1, MemTmp + request_size * 2 - insert_pos, insert_pos + readsize - request_size * 2);
					memcpy(audio->SampleBuffer, pMem1, insert_pos + readsize - request_size * 2);
					audio->lpDSoundBufferSecondary->Unlock(pMem1, size1, pMem2, size2);
					
					insert_pos = insert_pos + readsize - request_size * 2;

					if (audio->Status != AUDIO_STATUS_PLAYING && audio->Status != AUDIO_STATUS_STOP ){
						audio->Status = AUDIO_STATUS_PLAYING;
					}
				}
				else{
					audio->lpDSoundBufferSecondary->Lock(insert_pos, insert_pos + readsize, &pMem1, &size1, &pMem2, &size2, 0);
					memcpy(pMem1, MemTmp, readsize);
					memcpy(audio->SampleBuffer + insert_pos, pMem1, readsize);
					audio->lpDSoundBufferSecondary->Unlock(pMem1, size1, pMem2, size2);

					insert_pos += readsize;

					if (audio->Status != AUDIO_STATUS_PLAYING && audio->Status != AUDIO_STATUS_STOP){
						audio->Status = AUDIO_STATUS_PLAYING;
					}
				}
			}
			if (readsize == 0){
				if (audio->Loop == true){
					audio->Pos = 0;
					mmioSeek(audio->hmmio, audio->MP3Offset, SEEK_SET);
				}
				else
					audio->Stop();
				if (audio->ParentWnd != NULL)
					::SendMessage(audio->ParentWnd, WM_COMMAND, NOTIFY_PLAY_FINISHED, audio->ID + audio->StartID );
			}
		}
		Sleep(100);
	}
	delete[] MemTmp;

	return true;
}

bool CMP3Audio::BeginStreamMP3(){

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

	return true;
}

bool CMP3Audio::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 >= (DWORD) WaveBlockSize * MP3_BLOCK_NUM){
			pad_l = WaveBlockSize * MP3_BLOCK_NUM;
		}
		else
			pad_l = 0;
		if (sample_pos + i * WFE_Secondary->nChannels + WFE_Secondary->nChannels >= (DWORD) WaveBlockSize * MP3_BLOCK_NUM){
			pad_r = WaveBlockSize * MP3_BLOCK_NUM + 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;
}
