/*
 *	clipadp.c
 *
 *	2-bit ADPCM Decoder
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2006 Naoyuki Sawa
 *
 *	* Wed Dec 20 00:13:10 JST 2006 Naoyuki Sawa
 *	- 作成開始。
 *	* Wed Dec 20 17:31:02 JST 2006 Naoyuki Sawa
 *	- 1st リリース。
 *	* Thu Mar 01 01:53:43 JST 2007 Naoyuki Sawa
 *	- adpcm_stat()を追加しました。
 */
#include "clip.h"

/****************************************************************************
 *	ローカル変数
 ****************************************************************************/

static ADPCMDRIVER driver;

/****************************************************************************
 *	ローカル関数
 ****************************************************************************/

static int
adpcm_stream_callback(short wbuff[/*ADPCMBUFLEN*/], int param/*NOUSE*/)
{
	static int mixbuf[ADPCMBUFLEN]; /* ミキシングバッファ、ＳＴＡＴＩＣです！ */
	//
	int i;

	/* ミキシングバッファをクリアします。 */
	memset(mixbuf, 0, sizeof mixbuf);

	/* 各チャネルについて... */
	for(i = 0; i < driver.nch; i++) {
		/* 再生中ならば... */
		if(driver.pch[i].loop) {
			/* チャネルの出力を、ミキシングバッファへ合成します。 */
			adpcm_mix1(&driver.pch[i], mixbuf);
		}
	}

	/* ミキシングバッファをクリッピングして、出力バッファへ書き込みます。 */
	adpcm_mix2(wbuff, mixbuf);

	return 0;
}

/****************************************************************************
 *	アプリケーション用関数
 ****************************************************************************/

void
adpcm_init(int nch)
{
	/* 確実に未初期化状態にします。 */
	adpcm_exit();

	/* チャネル構造体を確保します。 */
	driver.nch = nch;
	driver.pch = calloc(nch, sizeof(ADPCMCHANNEL));
	if(!driver.pch) {
		DIE(); /* メモリ不足 */
	}

	/* ストリーム再生を開始します。
	 * - スタックの使用頻度が少ないので、スタック切替を使わないことにします。
	 *   実際のアプリケーションで試してみて、どちらが速いか判断する予定です。
	 * ↓2006/12/21追記
	 * - 予測どおり、スタック切替を使わない方が高速であることを確認しました。
	 */
	stream_play(ADPCMBUFLEN, adpcm_stream_callback, 0/*NOUSE*/, 0/*スタック切換使わない*/);
}

void
adpcm_exit()
{
	/* 確実にストリーム再生を停止します。 */
	stream_stop();

	/* 確実にチャネル構造体を解放します。 */
	free(driver.pch/*NULLOK*/);
	memset(&driver, 0, sizeof driver); /*必要!!*/
}

void
adpcm_play(int ch, const void* data, int loop)
{
	ADPCMCHANNEL* pch;
	const PCEWAVEINFO* pwi;

	if((ch < 0) || (ch > driver.nch - 1)) {
		DIE(); /* 未初期化、または、チャネル番号不正 */
	}
	pch = &driver.pch[ch];

	if(memcmp(data, "MCPP", 4) != 0) {
		DIE(); /* シグネチャが不正 */
	}
	pwi = (const PCEWAVEINFO*)((const unsigned char*)data + 4/*シグネチャ*/ + 4/*サイズ*/);
	if((pwi->type & 3) != PW_TYPE_2BITADPCM) {
		DIE(); /* 2bit-ADPCMでない */
	}

	pch->loop = 0;
	/*{{この間で割り込みが発生しても、停止チャネルと見なされるので安全です*/
	pch->pcm      = 0;
	pch->data     = 0;
	pch->delta    = 16;
	pch->progress = 0;
	pch->rate     = pwi->resv;
	pch->len      = pwi->len;
	pch->ptr      = (const unsigned char*)(pwi + 1); /* "pwi->pData"ではありません!! */
	pch->init_len = pch->len;
	pch->init_ptr = pch->ptr;
	/*}}この間で割り込みが発生しても、停止チャネルと見なされるので安全です*/
	pch->loop = loop;
}

void
adpcm_stop(int ch)
{
	ADPCMCHANNEL* pch;

	if((ch < 0) || (ch > driver.nch - 1)) {
		DIE(); /* 未初期化、または、チャネル番号不正 */
	}
	pch = &driver.pch[ch];

	pch->loop = 0; /* 1ワードの書き込みだけなので、割り込み禁止は不要です */
}

int
adpcm_stat(int ch)
{
	ADPCMCHANNEL* pch;

	if((ch < 0) || (ch > driver.nch - 1)) {
		DIE(); /* 未初期化、または、チャネル番号不正 */
	}
	pch = &driver.pch[ch];

	return pch->loop; /* 1ワードの読み出しだけなので、割り込み禁止は不要です */
}

