//------------------------------------------------------------------------------
// Lamp : Open source game middleware
// Copyright (C) 2004  Junpei Ohtani ( Email : junpee@users.sourceforge.jp )
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//------------------------------------------------------------------------------

/** @file
 * ストリームプレーヤ実装
 * @author Junpee
 */

#include "LampBasic.h"
#include "Sound/Utility/StreamPlayer.h"
#include "Sound/System/SoundBuffer.h"
#include "Sound/Reader/SoundReader.h"
#include "Core/Thread/SynchronizedBlock.h"

namespace Lamp{

//------------------------------------------------------------------------------
// コンストラクタ
StreamPlayer::StreamPlayer() :
	directSoundNotify_(NULL), soundBuffer_(NULL), soundReader_(NULL),
	cursor_(0), loopCursor_(0), initialized_(false){
	for(int i = 0; i < eventCount_; i++){ event_[i] = NULL; }
}
//------------------------------------------------------------------------------
// デストラクタ
StreamPlayer::~StreamPlayer(){
	soundBuffer_->stop();
	// スレッド停止を待つ、SetEventは再生せずに削除する場合に必要
	requestStop();
	::SetEvent(event_[2]);
	join();
	for(int i = 0; i < eventCount_; i++){
		if(event_[i] != NULL){ CloseHandle(event_[i]); }
	}
	SafeRelease(directSoundNotify_);
}
//------------------------------------------------------------------------------
// 初期化
bool StreamPlayer::initialize(
	SoundBuffer* soundBuffer, SoundReader* soundReader){
	if(initialized_){ Assert(false); return true; }
	soundBuffer_ = soundBuffer;
	soundReader_ = soundReader;
	Assert((soundBuffer_ != NULL) && (soundReader_ != NULL));
	// 通知インターフェース初期化
	if(DirectXFailed(soundBuffer_->getSoundBuffer()->QueryInterface(
		DirectSoundNotifyInterfaceID, (void**)&directSoundNotify_))){
		ErrorOut("StreamPlayer::initialize() "
			"通知インターフェースの取得に失敗しました。");
		return false;
	}
	// イベント初期化
	for(int i = 0; i < eventCount_; i++){
		event_[i] = ::CreateEvent(NULL, false, false, NULL);
	}
	DSBPOSITIONNOTIFY cursorNotify[eventCount_];
	cursorNotify[0].dwOffset = 0;
	cursorNotify[0].hEventNotify = event_[0];
	cursorNotify[1].dwOffset = soundBuffer_->getBufferSize() / 2;
	cursorNotify[1].hEventNotify = event_[1];
	cursorNotify[2].dwOffset = DSBPN_OFFSETSTOP;
	cursorNotify[2].hEventNotify = event_[2];
	if(DirectXFailed(directSoundNotify_->SetNotificationPositions(
		eventCount_, cursorNotify))){
		ErrorOut("StreamPlayer::initialize() "
			"通知イベントの設定に失敗しました。");
	}
	// ロード前に再生されるとまずいので最初にバッファを半分読み込む
	reset();
	initialized_ = true;
	// 実行
	start();
	return true;
}
//------------------------------------------------------------------------------
// リセット
void StreamPlayer::reset(){
	bool result = writeStream(0);
	Assert(result);
}
//------------------------------------------------------------------------------
// 実行
void StreamPlayer::run(Thread* thread){
	Assert(initialized_);
	while(!isStopRequested()){
		u_long flag = ::WaitForMultipleObjects(
			eventCount_, event_, false, INFINITE);
		switch(flag){
			case WAIT_OBJECT_0 + 0:
				// 停止命令が無い状態で書き込みが成功するまでループ
				while((!isStopRequested()) && (!writeStream(1))){ yield(); }
				break;
			case WAIT_OBJECT_0 + 1:
				// 停止命令が無い状態で書き込みが成功するまでループ
				while((!isStopRequested()) && (!writeStream(0))){ yield(); }
				break;
			case WAIT_OBJECT_0 + 2:
				break;
		}
	}
}
//------------------------------------------------------------------------------
// ストリーム書き込み
bool StreamPlayer::writeStream(int offset){
	u_int size = soundBuffer_->getSize();
	bool isLoop = soundBuffer_->isLoop();
	u_int writeSize = soundBuffer_->getBufferSize() / 2;
	u_int writeOffset = writeSize * (offset & 1);
	// 最後に書き込んだバッファが再生されてから停止する
	if((!isLoop) && (cursor_ == size)){
		soundBuffer_->stop();
		return true;
	}
	// ロック
	SoundBuffer::Lock& lock = soundBuffer_->lock(writeOffset, writeSize);
	if(!lock.isValid()){ return false; }
	// バッファ後半にはロックがかからないはず
	Assert((lock.getAddress0() != NULL) && (lock.getSize0() == writeSize));
	Assert((lock.getAddress1() == NULL) && (lock.getSize1() == 0));
	// パラメータの確保
	u_int cursor, loopCursor;
	{
		SynchronizedBlock synchronizedBlock(this);
		cursor = cursor_;
		loopCursor = loopCursor_;
		cursor_ += writeSize;
		// ループ処理
		if(cursor_ > size){
			if(isLoop){ cursor_ = loopCursor + (cursor_ - size); }
			else{ cursor_ = size; }
		}
	}
	Assert(soundReader_->getCursor() == cursor);
	// ストリーム前半書き込み
	int preWriteSize = writeSize;
	int postWriteSize = (cursor + writeSize) - size;
	if(postWriteSize > 0){ preWriteSize -= postWriteSize; }
	int result = soundReader_->read(lock.getAddress0(), preWriteSize);
	Assert(result == preWriteSize);
	// ストリーム後半書き込み
	char* postWriteAddress = (((char*)lock.getAddress0()) + preWriteSize);
	if(postWriteSize > 0){
		if(isLoop){
			// ループ位置に戻る
			soundReader_->setCursor(loopCursor);
			result = soundReader_->read(postWriteAddress, postWriteSize);
			Assert(result == postWriteSize);
		}else{
			// 残りを0(16bit)か128(8bit)で埋める
			u_char value = 0;
			if(soundBuffer_->getBit() == 8){ value = 128; }
			::memset(postWriteAddress, 0, postWriteSize);
		}
	}
	soundBuffer_->unlock();
	return true;
}
//------------------------------------------------------------------------------
// 再生位置
//------------------------------------------------------------------------------
// 再生位置設定
void StreamPlayer::setCursor(u_int cursor){
	Assert(cursor <= soundBuffer_->getSize());
	// 1サンプル以下のバイト数は切り捨てる
	u_int fixedCursor =
		cursor - (cursor % soundBuffer_->getOneSampleBytes());
	{
		SynchronizedBlock synchronizedBlock(this);
		cursor_ = fixedCursor;
		soundReader_->setCursor(cursor_);
	}
}
//------------------------------------------------------------------------------
// ループ
//------------------------------------------------------------------------------
// ループ位置の設定
void StreamPlayer::setLoopCursor(u_int loopCursor){
	Assert(loopCursor <= soundBuffer_->getSize());
	// 1サンプル以下のバイト数は切り捨てる
	u_int fixedLoopCursor =
		loopCursor - (loopCursor % soundBuffer_->getOneSampleBytes());
	{
		SynchronizedBlock synchronizedBlock(this);
		loopCursor_ = fixedLoopCursor;
	}
}
//------------------------------------------------------------------------------
} // End of namespace Lamp
//------------------------------------------------------------------------------
