//------------------------------------------------------------------------------
// 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/System/SoundBuffer.h"
#include "Sound/System/LampSound.h"
#include "Sound/System/SoundManager.h"
#include "Sound/System/SoundDefinition.h"

namespace Lamp{

//------------------------------------------------------------------------------
// 生成、破棄
//------------------------------------------------------------------------------
// コンストラクタ
SoundBuffer::SoundBuffer(DirectSoundBuffer* soundBuffer) :
	Sound(), soundBuffer_(soundBuffer){
	lock_.clear();
	// 初期データの取得
	long decibelVolume;
	if(DirectXFailed(soundBuffer->GetVolume(&decibelVolume))){
		ErrorOut("SoundBuffer::SoundBuffer() ボリュームの取得に失敗しました。");
	}
	volume_ = decibelToVolume(decibelVolume);
	frequency_ = getBufferFrequency();
	fadeStartVolume_ = fadeEndVolume_ = 0.f;
	isFading_ = false;
	setPriority(priorityDefault);
	setOwnership(true);
	setLoop(false);
	isSuspended_ = false;
}
//------------------------------------------------------------------------------
// 初期化
void SoundBuffer::initialize(
	u_int size, int sample, int channel, int bit, Focus focus){
	size_ = size;
	sample_ = sample;
	channel_ = channel;
	bit_ = bit;
	focus_ = focus;
}
//------------------------------------------------------------------------------
// デストラクタ
SoundBuffer::~SoundBuffer(){
	// フェード取り消し
	stopFade();
	stop();
	SafeRelease(soundBuffer_);
}
//------------------------------------------------------------------------------
// サウンドバッファデータのコピー
void SoundBuffer::copySoundBufferData(SoundBuffer* destination){
	destination->setName(getName());
	destination->initialize(
		getBufferSize(), getSample(), getChannel(), getBit(), getFocus());
	destination->setPriority(getPriority());
	destination->setLoop(isLoop());
}
//------------------------------------------------------------------------------
// 再生
//------------------------------------------------------------------------------
// 再生
bool SoundBuffer::play(){
	// 再生時にsetCursor(0)するとストリーム先読みとの相性が悪くなる
	isSuspended_ = false;
	return playBuffer();
}
//------------------------------------------------------------------------------
// 停止
void SoundBuffer::stop(){
	isSuspended_ = false;
	stopBuffer();
	setCursor(0);
}
//------------------------------------------------------------------------------
// 再生の一時停止
void SoundBuffer::suspend(){
	State state = getState();
	// 停止中なら停止したまま
	if(state == stateStop){ return; }
	isSuspended_ = true;
	stopBuffer();
}
//------------------------------------------------------------------------------
// 再生再開
bool SoundBuffer::resume(){
	State state = getState();
	// 停止中なら停止したまま
	if(state == stateStop){ return true; }
	// バッファを再生してから中断フラグをクリアする
	bool result = playBuffer();
	isSuspended_ = false;
	return result;
}
//------------------------------------------------------------------------------
// 状態の取得
SoundBuffer::State SoundBuffer::getState() const{
	u_long status;
	if(DirectXFailed(soundBuffer_->GetStatus(&status))){
		ErrorOut("SoundBuffer::getState() ステータスの取得に失敗しました。");
	}
	if((status & DSBSTATUS_BUFFERLOST) != 0){
		// ロストならリストアして再度ステート取得
		if(DirectXFailed(soundBuffer_->Restore())){
			ErrorOut("SoundBuffer::getState() バッファのリストアに失敗しました。");
		}
		if(DirectXFailed(soundBuffer_->GetStatus(&status))){
			ErrorOut("SoundBuffer::getState() ステータスの取得に失敗しました。");
		}
		if((status & DSBSTATUS_BUFFERLOST) != 0){ return stateLost; }
	}
	if((status & DSBSTATUS_PLAYING) != 0){ return statePlay; }
	if(isSuspended_){ return stateSuspend; }
	return stateStop;
}
//------------------------------------------------------------------------------
// バッファの再生
bool SoundBuffer::playBuffer(){
	u_int priority = (u_int)(getPriority() + 32768);
	u_int flag = getPlayFlag();
	// 再生を行う
	HRESULT result = soundBuffer_->Play(0, priority, flag);
	if(DirectXSucceeded(result)){ return true; }
	// メモリが足りない、長いサウンドをたくさん同時発音させると起こることがある
	if(result == E_OUTOFMEMORY){ Assert(false); return false; }
	// バッファロストならリストアしてみる
	if(result != DSERR_BUFFERLOST){
		ErrorOut("SoundBuffer::playBuffer() バッファの再生に失敗しました。");
		return false;
	}
	result = soundBuffer_->Restore();
	if(DirectXFailed(result)){ return false; }
	// 再度再生を行う
	result = soundBuffer_->Play(0, priority, flag);
	if(DirectXSucceeded(result)){ return true; }
	return false;
}
//------------------------------------------------------------------------------
// バッファの停止
void SoundBuffer::stopBuffer(){
	if(DirectXFailed(soundBuffer_->Stop())){
		ErrorOut("SoundBuffer::stopBuffer() サウンドの停止に失敗しました。");
	}
}
//------------------------------------------------------------------------------
// 再生位置
//------------------------------------------------------------------------------
// 再生位置設定
void SoundBuffer::setCursor(u_int cursor){
	Assert((cursor >= 0) && (cursor < getSize()));
	if(DirectXFailed(soundBuffer_->SetCurrentPosition(cursor))){
		ErrorOut("SoundBuffer::setCursor() 再生位置の設定に失敗しました。");
	}
}
//------------------------------------------------------------------------------
// 再生位置取得
u_int SoundBuffer::getCursor() const{
	u_long result;
	if(DirectXFailed(soundBuffer_->GetCurrentPosition(&result, NULL))){
		ErrorOut("SoundBuffer::getCursor() 再生位置の取得に失敗しました。");
	}
	return result;
}
//------------------------------------------------------------------------------
// ボリューム
//------------------------------------------------------------------------------
// ボリュームの設定
void SoundBuffer::setVolume(float volume){
	if(volume_ == volume){ return; }
	int db = volumeToDecibel(volume);
	if(DirectXFailed(soundBuffer_->SetVolume(db))){
		ErrorOut("SoundBuffer::setVolume() ボリュームの設定に失敗しました。");
	}
	volume_ = volume;
}
//------------------------------------------------------------------------------
// 周波数
//------------------------------------------------------------------------------
// 周波数の設定
void SoundBuffer::setFrequency(int frequency){
	if(frequency_ == frequency){ return; }
	Assert(frequency >= DSBFREQUENCY_MIN);
	Assert(frequency <= 100000);
	if(DirectXFailed(soundBuffer_->SetFrequency(frequency))){
		ErrorOut("SoundBuffer::setFrequency() 周波数の設定に失敗しました。");
	}
	frequency_ = frequency;
}
//------------------------------------------------------------------------------
// オリジナル周波数の設定
void SoundBuffer::setOriginalFrequency(){
	if(DirectXFailed(soundBuffer_->SetFrequency(DSBFREQUENCY_ORIGINAL))){
		ErrorOut("SoundBuffer::resetFrequency() 周波数の設定に失敗しました。");
	}
	frequency_ = getBufferFrequency();
}
//------------------------------------------------------------------------------
// バッファ周波数の取得
int SoundBuffer::getBufferFrequency() const{
	u_long frequency;
	if(DirectXFailed(soundBuffer_->GetFrequency(&frequency))){
		ErrorOut("SoundBuffer::getBufferFrequency() "
			"バッファ周波数の取得に失敗しました。");
	}
	Assert(frequency >= DSBFREQUENCY_MIN);
	Assert(frequency <= 100000);
	return frequency;
}
//------------------------------------------------------------------------------
// フェード
//------------------------------------------------------------------------------
// フェード
void SoundBuffer::fade(float millisecond, float startVolume, float endVolume){
	Assert(millisecond >= 0.f);
	if(isFading_){
		LampSound::getSoundManager()->removeUpdateSound(this);
		isFading_ = false;
	}
	if(millisecond <= Math::epsilon){ return; }
	fadeStartTime_ = Timer::getTick();
	fadePeriod_ = millisecond;
	fadeStartVolume_ = startVolume;
	fadeEndVolume_ = endVolume;
	isFading_ = true;
	setVolume(fadeStartVolume_);
	play();
	LampSound::getSoundManager()->addUpdateSound(this);
}
//------------------------------------------------------------------------------
// アップデート
bool SoundBuffer::update(){
	Assert(isFading());
	float rate = Timer::getInterval(fadeStartTime_) / fadePeriod_;
	// フェード終了
	if(rate >= 1.f){
		setVolume(fadeEndVolume_);
		if(fadeEndVolume_ <= Math::epsilon){ stop(); }
		isFading_ = false;
		return true;
	}
	float volume =
		(fadeEndVolume_ - fadeStartVolume_) * rate + fadeStartVolume_;
	setVolume(volume);
	return false;
}
//------------------------------------------------------------------------------
// ロック
//------------------------------------------------------------------------------
// ロック
SoundBuffer::Lock& SoundBuffer::lock(){
	Assert(!lock_.isValid());
	HRESULT result = soundBuffer_->Lock(0, 0,
		&lock_.address0_, &lock_.size0_, NULL, NULL, DSBLOCK_ENTIREBUFFER);
	if(DirectXSucceeded(result)){ return lock_; }
	// バッファロストならリストアしてみる
	lock_.clear();
	if(result != DSERR_BUFFERLOST){
		ErrorOut("SoundBuffer::lock() バッファのロックに失敗しました。");
		return lock_;
	}
	result = soundBuffer_->Restore();
	if(DirectXFailed(result)){ return lock_; }
	// 再度ロックを行う
	result = soundBuffer_->Lock(0, 0,
		&lock_.address0_, &lock_.size0_, NULL, NULL, DSBLOCK_ENTIREBUFFER);
	if(DirectXFailed(result)){ lock_.clear(); }
	return lock_;
}
//------------------------------------------------------------------------------
// ロック
SoundBuffer::Lock& SoundBuffer::lock(u_int offset, u_int bytes){
	Assert(!lock_.isValid());
	HRESULT result = soundBuffer_->Lock(offset, bytes,
		&lock_.address0_, &lock_.size0_, &lock_.address1_, &lock_.size1_, 0);
	if(DirectXSucceeded(result)){ return lock_; }
	// バッファロストならリストアしてみる
	lock_.clear();
	if(result != DSERR_BUFFERLOST){
		ErrorOut("SoundBuffer::lock() バッファのロックに失敗しました。");
		return lock_;
	}
	result = soundBuffer_->Restore();
	if(DirectXFailed(result)){ return lock_; }
	// 再度ロックを行う
	result = soundBuffer_->Lock(0, 0,
		&lock_.address0_, &lock_.size0_, &lock_.address1_, &lock_.size1_, 0);
	if(DirectXFailed(result)){ lock_.clear(); }
	return lock_;
}
//------------------------------------------------------------------------------
// アンロック
void SoundBuffer::unlock(){
	// 有効なロックが行われていなければ何もしない
	if(!lock_.isValid()){ return; }
	if(DirectXFailed(soundBuffer_->Unlock(
		lock_.address0_, lock_.size0_, lock_.address1_, lock_.size1_))){
		ErrorOut("SoundBuffer::unlock() バッファのアンロックに失敗しました。");
	}
	lock_.clear();
}
//------------------------------------------------------------------------------
} // End of namespace Lamp
//------------------------------------------------------------------------------
