/*	
 *	clipwin.c
 *
 *	Windows専用ルーチンを集めました。
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2017 Naoyuki Sawa
 *
 *	* Wed Aug 16 22:59:27 JST 2017 Naoyuki Sawa
 *	- 既存のclipwin.h,clipwin.cを破棄して、同じ名前で全く別のモジュールとして、clipwin.h,clipwin.cを作成開始しました。
 *	  詳細は、以下の通りです。
 *	- 既存のclipwin.h,clipwin.cは、2002年当時に、P/ECE APIと互換のAPIをWindows上で実装する目的で作成していたものです。
 *	  しかしその後、ほとんど作成が進まないまま、放棄していました。
 *	  また、実際のWindows用プログラムにおいても、P/ECE APIと互換のAPIをWindows上で使用したい場面は、有りませんでした。
 *	  なぜなら、P/ECE APIの仕様では低水準の機能過ぎて、Windows用プログラムで使用するには少々使い辛いからです。
 *	  結局、既存のclipwin.h,clipwin.cは、使用もせず,libclipにも含めず,コンパイルすらもせずに、残されていただけでした。
 *	- 実際の所、P/ECE APIと互換のAPIよりも、clippce.cモジュールに存在するP/ECE特有の機能をWindows上で使用したいケースの方が多いです。
 *	  そこで、clippce.cモジュールに存在するP/ECE特有の機能の内、Windows上で使用したい機能を実装するモジュールとして、clipwin.h,clipwin.cを位置付ける事にしました。
 *	- 現在の所、特に、Windows上で使用したい機能は、stream_play(),stream_stop()です。
 *	  stream_play(),stream_stop()が使用出来れば、それに依存するサウンドドライバ(clipmod.cやclipadp.cなど)が、Windows上でも使えるようになる事が期待出来るからです。
 *	  まずは、stream_play(),stream_stop()を実装し、その後、clippce.c内のその他の機能の内、使いたい機能を、必要に応じて、当モジュール内に実装して行こうと思います。
 *	- 尚、変更を開始する前に、既存のclipwin.h,clipwin.cを、「22_clipwin_h_clipwin_c_用途変更前保存.20170816」に保存しておきました。
 *	  前述の通り、既存のclipwin.h,clipwin.cはこれまでも使っていなかったので、参照する必要が生じる事は無いと思いますが、念のためです。
 *	  また同様に、clip.dsp,clip.dswも使っていなかったので、「22_clipwin_h_clipwin_c_用途変更前保存.20170816」に保存しておきました。
 *	  clip.dsp,clip.dswは、2002年当時にCLiPライブラリをWindows用にビルドするために一時期使用していたVC++6.0用プロジェクトですが、その後すぐに使用しなくなり長い間メンテナンスもしておらずもう使えなくなっていました。
 *	  現在は、Windows用のCLiPライブラリのビルドは、libclip/,libclip.x64/,libclip.net/で行っており、clip.dsp,clip.dswを使用する事はもう有りません。
 *	  今後、clip.dsp,clip.dswを参照する必要が生じる事は無いと思いますが、念のため、これらも上記のフォルダに保存しておく事にしました。
 */
#include "clip.h"
//*****************************************************************************
//	ユーティリティ：ストリーム再生
//*****************************************************************************
#define BLKN			4		//ブロック数：調整可、2以上。P/ECEでは2で充分だったが、Windowsでは2では音切れが発生した。多分、P/ECEの割り込みよりも、Windowsのコンテキストスイッチの方が、時間が掛かるからだと思う。3でぎりぎり間に合う感じだが、余裕を取って4にする事にした。
//-----------------------------------------------------------------------------
typedef struct _STREAMINFO {
	volatile unsigned char	playing;	//再生中フラグ
	HWAVEOUT		hwo;		//ウェーブフォームオーディオ出力デバイスのハンドル			P/ECEで言う所の、pceWaveDataOut()の出力chに相当する。
	int			BLKS;		//コールバック一回当りの展開サンプル数
	WAVEHDR			wh[BLKN];	//ウェーブフォームオーディオデータブロックを識別するWAVEHDR構造体	P/ECEで言う所の、PCEWAVEINFOに相当する。
	STREAMCALLBACK*		callback;	//サンプル展開コールバック
	intptr_t		param;		//コールバックへの任意パラメータ
} STREAMINFO;
static STREAMINFO		stream_info;
static STREAMFILTER*		stream_filter;
//-----------------------------------------------------------------------------
static void stream_fill(WAVEHDR* pwh) {		//stream_fill()は、P/ECEでは全ての空きブロックにデータを満たす関数でしたが、Windowsでは指定された一つの空きブロックにデータを満たす関数です。
	STREAMINFO* const si = &stream_info;
	//コールバックを呼び出して、サンプルを展開します。
	short* wbuff = (short*)pwh->lpData;
	if(!(*si->callback)(wbuff, si->param)) {
		//展開に成功したら、ストリームフィルタ関数があれば、実行します。
		if(stream_filter) { (*stream_filter)(wbuff, si->BLKS); }
	} else {
		//展開に失敗したら、無音にします。(演奏終了orデータが壊れてる？)
		memset(wbuff, 0, sizeof(short) * si->BLKS);
	}
}
//-----------------------------------------------------------------------------
static void CALLBACK waveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
	//WOM_DONEだけを処理し、WOM_OPEN,WOM_CLOSEは無視します。
	if(uMsg == WOM_DONE) {
		STREAMINFO* const si = (STREAMINFO*)dwInstance;
		WAVEHDR* const pwh = (WAVEHDR*)dwParam1;
		MMRESULT hr;
		//再生中フラグがクリアされていたら、何もしません。
		if(!si->playing) { return; }
		//このブロックにデータを満たします。
		stream_fill(pwh);
		//このブロックを、再度、送出します。
		hr = waveOutWrite(hwo, pwh, sizeof(WAVEHDR));
		if(hr) { DIE(); }
	}
}
//-----------------------------------------------------------------------------
void stream_play(int BLKS, STREAMCALLBACK* callback, intptr_t param, int flags/*Windows版では指定しても無視されます*/) {
	STREAMINFO* const si = &stream_info;
	MMRESULT hr;
	int i;
	//まず、確実に停止します。
	stream_stop();
	//全体をクリアします。
	memset(si, 0, sizeof(STREAMINFO));
	//WAVEバッファサイズ、コールバック、パラメータを格納します。
	if(BLKS % 64 != 0) { DIE(); }	//P/ECEのpceWave*()でストリーム再生を行うときは、バッファを64の倍数にしないと落ちます。WindowsのwaveOut*()にはこの制限はありませんが、互換性のためにWindowsでも同じ制限を課す事にしました。
	si->BLKS = BLKS;
	si->callback = callback;
	si->param = param;
	//提供されたウェーブフォームオーディオ出力デバイスを、再生のためにオープンします。
	{
		WAVEFORMATEX wfx;
		memset(&wfx, 0, sizeof(WAVEFORMATEX));
		wfx.wFormatTag      = WAVE_FORMAT_PCM;
		wfx.nChannels       = 1;
		wfx.nSamplesPerSec  =                 SPEAKER_FREQUENCY;
		wfx.nAvgBytesPerSec = sizeof(short) * SPEAKER_FREQUENCY;
		wfx.nBlockAlign     = sizeof(short);
		wfx.wBitsPerSample  = sizeof(short) * 8;
		hr = waveOutOpen(&si->hwo, WAVE_MAPPER, &wfx, (DWORD_PTR)waveOutProc, (DWORD_PTR)si, CALLBACK_FUNCTION);
		if(hr) { DIE(); }
	}
	//WAVEヘッダを初期化します。同時にWAVEバッファも確保します。
	for(i = 0; i < BLKN; i++) {
		WAVEHDR* pwh = &si->wh[i];
		pwh->dwBufferLength = sizeof(short) * BLKS;
		pwh->lpData = malloc( sizeof(short) * BLKS);
		if(!pwh->lpData) { DIE(); }
		hr = waveOutPrepareHeader(si->hwo, pwh, sizeof(WAVEHDR));
		if(hr) { DIE(); }
	}
	//再生開始前に、全ブロックにデータを満たしておきます。
	for(i = 0; i < BLKN; i++) {
		WAVEHDR* pwh = &si->wh[i];
		stream_fill(pwh);
	}
	//再生中フラグをセットします。
	si->playing = 1;
	//↑↓この順番必須です。waveOutWrite()を呼び出した直後からWOM_DONEがコールバックされる可能性は有り、その時点で再生中フラグがセットされていないと送出が行われないバグになります。
	//Wave再生を開始します。
	for(i = 0; i < BLKN; i++) {
		WAVEHDR* pwh = &si->wh[i];
		hr = waveOutWrite(si->hwo, pwh, sizeof(WAVEHDR));
		if(hr) { DIE(); }
	}
}
//-----------------------------------------------------------------------------
void stream_stop() {
	STREAMINFO* const si = &stream_info;
	MMRESULT hr;
	int i;
	//再生中でなければ何もしません。
	if(!stream_stat()) { return; }
	//再生中フラグをクリアします。
	si->playing = 0;
	//↑↓この順番必須です。waveOutReset()の中で未完了のブロックそれぞれに対してWOM_DONEがコールバックされ、再生中フラグがクリアされていないと再度送出して停止しないバグになります。
	//WAVE再生を停止します。
	hr = waveOutReset(si->hwo);	//この中で、未完了のブロックそれぞれに対してWOM_DONEがコールバックされますが、再生中フラグがクリアされているのでwaveOutWrite()を呼び出しません。
	if(hr) { DIE(); }
	//WAVEバッファを開放します。
	for(i = 0; i < BLKN; i++) {
		WAVEHDR* pwh = &si->wh[i];
		hr = waveOutUnprepareHeader(si->hwo, pwh, sizeof(WAVEHDR));
		if(hr) { DIE(); }
		free(pwh->lpData);
	}
	//提供されたウェーブフォームオーディオ出力デバイスをクローズします。
	hr = waveOutClose(si->hwo);
	if(hr) { DIE(); }
}
//-----------------------------------------------------------------------------
int stream_stat() {
	//再生中ならば1、停止中ならば0を返します。
	return stream_info.playing;
}
//-----------------------------------------------------------------------------
void stream_filter_install(STREAMFILTER* filter) {
	//ストリームフィルタ関数を格納します。
	stream_filter = filter;
}
