#include <windows.h>
#include <jni.h>
#include <assert.h>
#include "gnu_javax_sound_sampled_wce_WCECallbackWindowThread.h"
#include "gnu_javax_sound_sampled_wce_WCESourceDataLine.h"
#include "gnu_javax_sound_sampled_wce_WCETargetDataLine.h"
#include "wcesound.h"

#define AudioSystem_NOT_SPECIFIED -1L

/**
 * コールバックウインドウのウインドウクラス名
 */
#define	CALLBACK_WINDOW_CLASS_NAME	_T("WCE sound callback")

/**
 * 同時にオープンできるHWAVEINの最大数
 */
#define MAX_WAVEIN_COUNT	16

/**
 * 同時にオープンできるHWAVEOUTの最大数
 */
#define MAX_WAVEOUT_COUNT	16

/**
 * 出力バッファ数
 */
#define WAVEHDR_COUNT	2

// 入力バッファは読み込み可能
#define READABLE_FLAG	(1 << 31)

// 入力バッファの先頭を示すフラグ
#define READ_HEAD_FLAG	(1 << 30)

/**
 * バッファ内の残りデータサイズを取得するためのマスク
 * dwUser & READ_REMAINS_MASK とすることで、lpData内の残りデータ量を
 * 取得することができる。
 * 以下の式で lpData のデータ開始オフセットを取得できる。
 * hdr->dwBytesRecorded - (hdr->dwUser & READ_REMAINS_MASK)
 */
#define READ_REMAINS_MASK 0x1FFFFFFF

/**
 * 読み込みバッファの最大サイズ
 * READ_OFFSET_MASKに収まる値にしなければならない
 */
#define READ_MAX_BUFFER_SIZE (READ_REMAINS_MASK - 1)

// READ_REMAINS
#define SET_READ_REMAINS(dwUser, remains) \
{ \
	assert((remains) < READ_MAX_BUFFER_SIZE); \
	(dwUser) &= ~READ_REMAINS_MASK; \
	(dwUser) |= ((remains) & READ_REMAINS_MASK); \
}

// GET_REMAINS
#define GET_READ_REMAINS(dwUser) ((dwUser) & READ_REMAINS_MASK)

// 出力バッファは書き込み可能
#define WRITABLE_FLAG	1

/**
 * 音声入出力処理用構造体
 */
typedef struct {
	struct {
		/**
		 * この構造体は使用中
		 */
		int in_use : 1;

		/**
	 	* リセット状態
	 	*/
		int reset : 1;

		/**
		 * 停止状態
	 	 */
		int stopped : 1;

		/**
		 * クローズ処理中
		 */
		int closing : 1;
	};

	/**
	 * クリティカルセクション
	 * この構造体メンバの読み書き時に使用する。
	 */
	CRITICAL_SECTION lock;

	/**
	 * WAVEHDRが利用可能になった時点で
	 * シグナル状態になるイベント
	 */
	HANDLE hHeaderEvent;

	/**
	 * 最大バッファサイズ
	 */
	int max_buffer_size;

	/**
	 * WAVEFORMATEX
	 */
	WAVEFORMATEX	waveformat;

	union {
		/**
		 * WAVE出力デバイスハンドル
		 */ 
		HWAVEOUT hWaveOut;

		/**
		 * WAVE入力デバイスハンドル
		 */
		HWAVEIN hWaveIn;
	};
	
	/**
	 * WAVEHDR構造体
	 */
	WAVEHDR headers[WAVEHDR_COUNT];

	/**
	 * WCESourceDataLine/WCETargetDataLineのインスタンス
	 */
	jobject instance;

} wave_data;

typedef wave_data	wavein_data;
typedef wave_data	waveout_data;

/**
 * WAVE入力デバイス
 */
static wavein_data g_wavein_data_array[MAX_WAVEIN_COUNT];

/**
 * WAVE出力
 */
static waveout_data g_waveout_data_array[MAX_WAVEOUT_COUNT];

// コールバックウインドウハンドル
HWND g_hwndCallback;

// グローバルロック
static CRITICAL_SECTION g_lock;

// WCEEventQueue
static jobject g_eventQueue;

// WCEEventQueue.postEvent()
static jmethodID g_postEvent_id;

// LineEvent.Type
static jobject g_LineEvent_Type_OPEN;
static jobject g_LineEvent_Type_CLOSE;
static jobject g_LineEvent_Type_START;
static jobject g_LineEvent_Type_STOP;

LRESULT CALLBACK CallbackWndProc(HWND, UINT, WPARAM, LPARAM); 

static BOOL prepare_references(JNIEnv* env, jobject instance, jobject eventQueue);
static void unprepare_references(JNIEnv* env, jobject instance, jobject eventQueue);
static BOOL create_callback_window(JNIEnv* env);
static wavein_data* get_wavein_data(HWAVEIN hWaveIn);
static waveout_data* get_waveout_data(HWAVEOUT hWaveOut);
static void close_wavein_data(waveout_data* data);
static void close_waveout_data(waveout_data* data);
static void post_LineEvent(JNIEnv* env, jobject line, jobject eventType, jlong position);
static BOOL is_wavein_active(wavein_data* data);
static BOOL is_waveout_active(waveout_data* data);
static int lock_readable_header(wavein_data* data);
static void unlock_readable_header(wavein_data* data, int index, DWORD remains);
static int lock_writable_header(waveout_data* data);
static int get_writable_header_count(waveout_data* data);

static void init_waveformat(LPWAVEFORMATEX pwfe,
							int sampleRate,
							int sampleSizeInBits,
							int channels,
							int frameSize,
							int frameRate);
static int adjust_max_buffer_size(int bufferSize, WAVEFORMATEX* wfe);
static BOOL alloc_header_data(int buffer_size, WAVEHDR* headers);
static void free_header_data(WAVEHDR* headers);

/**
 * DLLMain
 */
BOOL WINAPI DllMain(HINSTANCE hinstDLL,  // DLL モジュールのハンドル
					DWORD fdwReason,     // 関数を呼び出す理由
					LPVOID lpvReserved   // 予約済み
					) {
	switch (fdwReason) {
	case DLL_PROCESS_ATTACH:
		{
			// スレッドのアタッチ／デタッチには興味がないことを伝える
			DisableThreadLibraryCalls(hinstDLL);

			// クリティカルセクションを初期化する
			InitializeCriticalSection(&g_lock);
		}
		return TRUE;

	case DLL_PROCESS_DETACH:
		{
			int i;
			// クリティカルセクションを削除する
			DeleteCriticalSection(&g_lock);

			// 使用中のデバイスハンドルをすべてクローズする(不要？）
			for (i = 0; i < MAX_WAVEIN_COUNT; ++i) {
				wavein_data* data = &g_wavein_data_array[i];
				if (data->in_use) {
					int j;
					waveInReset(data->hWaveIn);
					for (j = 0; j < WAVEHDR_COUNT; ++j) {
						waveInUnprepareHeader(data->hWaveIn, &data->headers[j], sizeof(WAVEHDR));
					}
					waveInClose(data->hWaveIn);
				}
			}
			for (i = 0; i < MAX_WAVEOUT_COUNT; ++i) {
				waveout_data* data = &g_waveout_data_array[i];
				if (data->in_use) {
					int j;
					waveOutReset(data->hWaveOut);
					for (j = 0; j < WAVEHDR_COUNT; ++j) {
						waveOutUnprepareHeader(data->hWaveOut, &data->headers[j], sizeof(WAVEHDR));
					}
					waveOutClose(data->hWaveOut);
				}
			}
		}
		return TRUE;

	}
	return FALSE;
}

/**
 * Javaオブジェクト等の参照を初期化する
 */
static BOOL prepare_references(JNIEnv* env, jobject instance, jobject eventQueue) {
	jfieldID open_id, close_id, start_id, stop_id;

	jclass clazz = (*env)->FindClass(env, "javax/sound/sampled/LineEvent$Type");
	if (! clazz) {
		throw_RuntimeException(env, "Failed to get LineEvent.Type");
		return FALSE;
	}
	// メソッドIDを取得しておく
	g_postEvent_id = (*env)->GetMethodID(env,
										 (*env)->GetObjectClass(env, eventQueue),
										 "postEvent",
										 "(Ljavax/sound/sampled/Line;Ljavax/sound/sampled/LineEvent$Type;J)V");
	if (! g_postEvent_id) {
		throw_RuntimeException(env, "Failed to get methodID");
		return FALSE;
	}
	
	// LineEvent.Typeのstatic変数を保持しておく
	open_id = (*env)->GetStaticFieldID(env, clazz, "OPEN", "Ljavax/sound/sampled/LineEvent$Type;");
	close_id = (*env)->GetStaticFieldID(env, clazz, "CLOSE", "Ljavax/sound/sampled/LineEvent$Type;");
	start_id = (*env)->GetStaticFieldID(env, clazz, "START", "Ljavax/sound/sampled/LineEvent$Type;");
	stop_id = (*env)->GetStaticFieldID(env, clazz, "STOP", "Ljavax/sound/sampled/LineEvent$Type;");
	g_LineEvent_Type_OPEN = (*env)->GetStaticObjectField(env, clazz, open_id);
	g_LineEvent_Type_CLOSE = (*env)->GetStaticObjectField(env, clazz, close_id);
	g_LineEvent_Type_START = (*env)->GetStaticObjectField(env, clazz, start_id);
	g_LineEvent_Type_STOP = (*env)->GetStaticObjectField(env, clazz, stop_id);
	
	if ((*env)->ExceptionOccurred(env)) {
		return FALSE;
	}
	// グローバル参照として登録しておく
	g_eventQueue = (*env)->NewGlobalRef(env, eventQueue);	
	g_LineEvent_Type_OPEN = (*env)->NewGlobalRef(env, g_LineEvent_Type_OPEN);
	g_LineEvent_Type_CLOSE = (*env)->NewGlobalRef(env, g_LineEvent_Type_CLOSE);
	g_LineEvent_Type_START = (*env)->NewGlobalRef(env, g_LineEvent_Type_START);
	g_LineEvent_Type_STOP = (*env)->NewGlobalRef(env, g_LineEvent_Type_STOP);

	return TRUE;
}

/**
 * Javaオブジェクト等の参照を初期化する
 */
static void unprepare_references(JNIEnv* env, jobject instance, jobject eventQueue) {
	// グローバル参照を解除する
	(*env)->DeleteGlobalRef(env, g_eventQueue);
	(*env)->DeleteGlobalRef(env, g_LineEvent_Type_OPEN);
	(*env)->DeleteGlobalRef(env, g_LineEvent_Type_CLOSE);
	(*env)->DeleteGlobalRef(env, g_LineEvent_Type_START);
	(*env)->DeleteGlobalRef(env, g_LineEvent_Type_STOP);
}

/**
 * コールバックウインドウを作成する
 */
static BOOL create_callback_window(JNIEnv* env) {
	HINSTANCE hInst = GetModuleHandle(NULL);
	WNDCLASS wndclass = {0};

	// コールバックウインドウを作成する
	wndclass.lpfnWndProc = CallbackWndProc;
	wndclass.hInstance = hInst;
	wndclass.lpszClassName = CALLBACK_WINDOW_CLASS_NAME;

	if (! RegisterClass(&wndclass)) {
		throw_RuntimeException(env, "RegisterClass() failed");
		return FALSE;
	}

	g_hwndCallback = CreateWindow(CALLBACK_WINDOW_CLASS_NAME,
							  NULL,
							  WS_DISABLED,
							  0,
							  0,
							  0,
							  0,
							  NULL,
							  NULL,
							  hInst,
							  NULL);
	if (! g_hwndCallback) {
		throw_RuntimeException(env, "CreateWindow() failed");
		return FALSE;
	}

	// JNIEnv を登録しておく
	SetWindowLong(g_hwndCallback, GWL_USERDATA, (LONG) env);

	return TRUE;
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCECallbackWindowThread
 * Method:    messageLoop
 * Signature: (Lgnu/javax/sound/sampled/wce/WCEEventQueue;)V
 */
JNIEXPORT void JNICALL Java_gnu_javax_sound_sampled_wce_WCECallbackWindowThread_messageLoop
(JNIEnv *env, jobject instance, jobject eventQueue) {

	// オブジェクト関連の初期化を行う
	if (! prepare_references(env, instance, eventQueue)) {
		return;
	}
	
	// コールバックウインドウを作成する
	if (! create_callback_window(env)) {
		return;
	}


	// 初期化が終わったことを通知する
	{
		jmethodID mid = (*env)->GetMethodID(env,
											(*env)->GetObjectClass(env, instance),
											"nativeWindowInitialized",
											"()V");
		if (! mid) {
			throw_RuntimeException(env, "Failed to get methodID");
			return;
		}
		(*env)->CallVoidMethod(env, instance, mid);
	}

	// メッセージループを開始する
	{
		MSG msg;
		while (GetMessage(&msg, g_hwndCallback, 0, 0) > 0) {
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	// 参照を解除する
	unprepare_references(env, instance, eventQueue);
}

/**
 * コールバックウインドウプロシージャ
 */
LRESULT CALLBACK CallbackWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {

	switch (uMsg) {
	case MM_WIM_OPEN:
		// 入力デバイスがオープンされた
		{
			// JNIEnvを取得しておく
			JNIEnv* env = (JNIEnv*) GetWindowLong(hwnd, GWL_USERDATA);
			HWAVEIN hWaveIn = (HWAVEIN) wParam;
			wavein_data* data = get_wavein_data(hWaveIn);
			if (data) {
				// メッセージを通知する
				post_LineEvent(env,
								 data->instance,
								 g_LineEvent_Type_OPEN,
								 AudioSystem_NOT_SPECIFIED);
			}
		}
		break;

	case MM_WIM_CLOSE:
		// 入力デバイスがクローズされた
		{
			// JNIEnvを取得しておく
			JNIEnv* env = (JNIEnv*) GetWindowLong(hwnd, GWL_USERDATA);
			HWAVEIN hWaveIn = (HWAVEIN) wParam;
			waveout_data* data = get_wavein_data(hWaveIn);
			if (data) {
				// CLOSEメッセージを通知する
				post_LineEvent(env,
							   data->instance,
							   g_LineEvent_Type_CLOSE,
							   AudioSystem_NOT_SPECIFIED);

				// wavein_dataを閉じる
				close_wavein_data(data);
			}
		}
		break;

	case MM_WIM_DATA:
		// 入力バッファが満杯になった
		{
			// JNIEnvを取得しておく
			JNIEnv* env = (JNIEnv*) GetWindowLong(hwnd, GWL_USERDATA);
			HWAVEIN hWaveIn = (HWAVEIN) wParam;
			wavein_data* data = get_wavein_data(hWaveIn);
			if (data) {
				int i;

				// クリティカルセクションに入る
				EnterCriticalSection(&data->lock);

				if (data->reset) {
					// リセットされた場合
					data->reset = FALSE;
					for (i = 0; i < WAVEHDR_COUNT; ++i) {
						WAVEHDR* hdr = (WAVEHDR*) lParam;
						if (data->closing) {
							// クローズ処理中
							// 読み取り可能フラグを立てる（読み出し可能バイト数は０）
							SET_READ_REMAINS(hdr->dwUser, 0);
							hdr->dwUser |= READABLE_FLAG;
						} else {
							MMRESULT mmresult;
							// 読み込み可能フラグをクリアする
							hdr->dwUser &= ~READABLE_FLAG;
							// 再度キューに入れる
							mmresult = waveInAddBuffer(data->hWaveIn,
														hdr,
														sizeof(WAVEHDR));
							if (mmresult != MMSYSERR_NOERROR) {
								throw_MMRESULT_Exception(env, mmresult);
							}
						}
					}
				} else {
					// バッファが満杯になった場合、以下の処理を行う
					// o バッファの「残りバイト数」をdwBytesRecordedに設定する
					// o 「読み取り可能」フラグを立てる
					WAVEHDR* hdr = (WAVEHDR*) lParam;

					SET_READ_REMAINS(hdr->dwUser, hdr->dwBytesRecorded);
					// 読み取り可能フラグを立てる
					hdr->dwUser |= READABLE_FLAG;
				}
				
				// イベントを通知する
				if (! SetEvent(data->hHeaderEvent)) {
					throw_RuntimeException(env, "SetEvent() failed");
				}

				// アクティブかどうかを調べる
				if (! is_wavein_active(data)) {
					// アクティブ状態でなくなった場合は、
					// STOPイベントを通知する
					post_LineEvent(env,
							 data->instance,
							 g_LineEvent_Type_STOP,
							 AudioSystem_NOT_SPECIFIED);
				}

				// クリティカルセクションから抜ける
				LeaveCriticalSection(&data->lock);
			}
		}
		break;

	case MM_WOM_OPEN:
		{
			// JNIEnvを取得しておく
			JNIEnv* env = (JNIEnv*) GetWindowLong(hwnd, GWL_USERDATA);
			HWAVEOUT hWaveOut = (HWAVEOUT) wParam;
			waveout_data* data = get_waveout_data(hWaveOut);
			if (data) {
				// メッセージを通知する
				post_LineEvent(env,
								 data->instance,
								 g_LineEvent_Type_OPEN,
								 AudioSystem_NOT_SPECIFIED);
			}
		}
		break;

	case MM_WOM_CLOSE:
		{
			// JNIEnvを取得しておく
			JNIEnv* env = (JNIEnv*) GetWindowLong(hwnd, GWL_USERDATA);
			HWAVEOUT hWaveOut = (HWAVEOUT) wParam;
			waveout_data* data = get_waveout_data(hWaveOut);
			if (data) {
				// メッセージを通知する
				post_LineEvent(env,
							   data->instance,
							   g_LineEvent_Type_CLOSE,
							   AudioSystem_NOT_SPECIFIED);

				// waveout_dataを閉じる
				close_waveout_data(data);
			}
		}
		break;
	

	case MM_WOM_DONE:
		{
			// JNIEnvを取得しておく
			JNIEnv* env = (JNIEnv*) GetWindowLong(hwnd, GWL_USERDATA);
			HWAVEOUT hWaveOut = (HWAVEOUT) wParam;
			waveout_data* data = get_waveout_data(hWaveOut);
			if (data) {
				int i;
				WAVEHDR* whdr = (WAVEHDR*) lParam;

				// クリティカルセクションに入る
				EnterCriticalSection(&data->lock);

				if (data->reset) {
					// リセットされた場合
					data->reset = FALSE;
					// すべてのヘッダの状態を更新する
					for (i = 0; i < WAVEHDR_COUNT; ++i) {
						if (! (whdr->dwUser & WRITABLE_FLAG)) {
							// 書き込み可能フラグを立てる
							whdr->dwUser |= WRITABLE_FLAG;
						}
					}
				} else {
					// バッファの再生が正常に完了した場合
					// 書き込み可能ビットを立てる
					whdr->dwUser |= WRITABLE_FLAG;
				}
				
				// イベントを通知する
				if (! SetEvent(data->hHeaderEvent)) {
					throw_RuntimeException(env, "SetEvent() failed");
				}

				// アクティブかどうかを調べる
				if (! is_waveout_active(data)) {
					// アクティブ状態でなくなった場合は、
					// STOPイベントを通知する
					post_LineEvent(env,
							 data->instance,
							 g_LineEvent_Type_STOP,
							 AudioSystem_NOT_SPECIFIED);
				}

				// クリティカルセクションから抜ける
				LeaveCriticalSection(&data->lock);
			}
		}
		break;

	default:
		return DefWindowProc(hwnd, uMsg, wParam, lParam);
	}

	return 0;
}

/**
 * 指定されたHWAVEINに対応する wavein_data構造体へのポインタを返す。
 * NULLを指定された場合、新しいwavein_data構造体へのポインタを返す。
 */
static wavein_data* get_wavein_data(HWAVEIN hWaveIn) {
	int i;
	wavein_data* result = NULL;
	EnterCriticalSection(&g_lock);
	if (! hWaveIn) {
		for (i = 0; i < MAX_WAVEIN_COUNT; ++i) {
			if (! g_wavein_data_array[i].in_use) {
				// 空きスロット
				result = &g_wavein_data_array[i];
				// 使用中かつ停止状態に設定する
				result->in_use = TRUE;
				result->stopped = TRUE;
				break;
			}
		}
	} else {
		for (i = 0; i < MAX_WAVEIN_COUNT; ++i) {
			if (g_wavein_data_array[i].hWaveIn == hWaveIn
					&& g_wavein_data_array[i].in_use) {
				result = &g_wavein_data_array[i];
				break;
			}
		}
	}
	LeaveCriticalSection(&g_lock);

	return result;
}

/**
 * 指定されたHWAVEOUTに対応する waveout_data構造体へのポインタを返す。
 * NULLを指定された場合、新しいwaveout_data構造体へのポインタを返す。
 */
static waveout_data* get_waveout_data(HWAVEOUT hWaveOut) {
	int i;
	waveout_data* result = NULL;
	EnterCriticalSection(&g_lock);
	if (! hWaveOut) {
		for (i = 0; i < MAX_WAVEOUT_COUNT; ++i) {
			if (! g_waveout_data_array[i].in_use) {
				// 空きスロット
				result = &g_waveout_data_array[i];
				// 使用中かつ停止状態に設定する
				result->in_use = TRUE;
				result->stopped = TRUE;
				break;
			}
		}
	} else {
		for (i = 0; i < MAX_WAVEOUT_COUNT; ++i) {
			if (g_waveout_data_array[i].hWaveOut == hWaveOut
					&& g_waveout_data_array[i].in_use) {
				result = &g_waveout_data_array[i];
				break;
			}
		}
	}
	LeaveCriticalSection(&g_lock);

	return result;
}

/**
 * 指定されたwavein_data構造体をクローズする。
 */
static void close_wavein_data(wavein_data* data) {
	int i;
	if (! data) {
		return;
	}
	EnterCriticalSection(&g_lock);
	for (i = 0; i < MAX_WAVEIN_COUNT; ++i) {
		if (&g_wavein_data_array[i] == data) {
			// 割り当てられたリソースをすべて開放する
			DeleteCriticalSection(&data->lock);
			CloseHandle(data->hHeaderEvent);
			free_header_data(data->headers);

			// 内容を0クリアしておく
			memset(data, 0, sizeof(wavein_data));
			break;
		}
	}
	LeaveCriticalSection(&g_lock);
}

/**
 * 指定されたwaveout_data構造体をクローズする。
 */
static void close_waveout_data(waveout_data* data) {
	int i;

	if (! data) {
		return;
	}

	EnterCriticalSection(&g_lock);
	for (i = 0; i < MAX_WAVEOUT_COUNT; ++i) {
		if (&g_waveout_data_array[i] == data) {
			// 割り当てられたリソースをすべて開放する
			DeleteCriticalSection(&data->lock);
			CloseHandle(data->hHeaderEvent);
			free_header_data(data->headers);

			// 内容を0クリアしておく
			memset(data, 0, sizeof(waveout_data));
			break;
		}
	}
	LeaveCriticalSection(&g_lock);
}

/**
 * イベントをpostする
 */
static void post_LineEvent(JNIEnv* env, jobject line, jobject eventType, jlong position) {
	(*env)->CallVoidMethod(env,
						   g_eventQueue,
						   g_postEvent_id,
						   line,
						   eventType,
						   position);
}

/**
 * TargetDataLineがアクティブかを調べる
 */
static BOOL is_wavein_active(wavein_data* data) {
	int i;
	BOOL active = FALSE;
	EnterCriticalSection(&data->lock);
	for (i = 0; i < WAVEHDR_COUNT; ++i) {
		if ((! data->stopped) && (! (data->headers[i].dwUser & READABLE_FLAG))) {
			// 読み込み不可能なバッファがあれば
			// アクティブ状態にあると判断する
			active = TRUE;
			break;
		}
	}
	LeaveCriticalSection(&data->lock);
	return active;
}

/**
 * SourceDataLineがアクティブかを調べる
 */
static BOOL is_waveout_active(waveout_data* data) {
	int i;
	BOOL active = FALSE;
	EnterCriticalSection(&data->lock);
	for (i = 0; i < WAVEHDR_COUNT; ++i) {
		if (! (data->headers[i].dwUser & WRITABLE_FLAG)) {
			// 書き込み不可能なバッファがあれば
			// アクティブ状態にあると判断する
			active = TRUE;
			break;
		}
	}
	LeaveCriticalSection(&data->lock);
	return active;
}

/**
 * 読み出し用にバッファをロックし、そのインデックス値を返す。
 * ロックできない場合には負数を返す。
 */
static int lock_readable_header(wavein_data* data) {
	int result = -1;
	int i;
	EnterCriticalSection(&data->lock);
	for (i = 0; i < WAVEHDR_COUNT; ++i) {
		// 以下の条件を満たすWAVEHDRを探す
		// o 読み出し可能である
		// o 次に読み出すべきバッファである
		DWORD dwUser = data->headers[i].dwUser;
		if ((dwUser & READABLE_FLAG) && (dwUser & READ_HEAD_FLAG)) {
			// READABLE_FLAGをクリアする
			dwUser &= ~READABLE_FLAG;
			data->headers[i].dwUser = dwUser;
			result = i;
			break;
		}
	}
	LeaveCriticalSection(&data->lock);
	return result;
}

/**
 * 指定されたインデックス位置のバッファをアンロックする。
 * その際に「残りバイト数」を指定された値に更新する。
 * 残りバイト数が0になった場合、READ_HEAD_FLAGはクリアされ、次のWAVEHDRの
 * READ_HEAD_FLAGが立てられる。
 */
static void unlock_readable_header(wavein_data* data,
			     int index,
			     DWORD remains) {
	WAVEHDR* hdr = &data->headers[index];

	// クリティカルセクションに入る
	EnterCriticalSection(&data->lock);

	// デバッグ用
	assert(! (hdr->dwUser & READABLE_FLAG));
	assert(hdr->dwUser & READ_HEAD_FLAG);
	assert(remains <= READ_MAX_BUFFER_SIZE);

	// remainsを下位ビットに入れる
	SET_READ_REMAINS(hdr->dwUser, remains);
	if (! remains) {
		// バッファの残りがなくなった場合、READ_HEAD_FLAGをクリアして、
		// 次のWAVEHDRのREAD_HEAD_FLAGを立てる
		int next_index = (index + 1) >= WAVEHDR_COUNT
				? 0
				: index + 1;
		data->headers[next_index].dwUser |= READ_HEAD_FLAG;
		hdr->dwUser &= ~READ_HEAD_FLAG;

	} else {
		// まだバッファに残りがある場合は、
		// READABLE_FLAGを再度立てる
		hdr->dwUser |= READABLE_FLAG;
	}
	
	// クリティカルセクションから抜ける
	LeaveCriticalSection(&data->lock);
}


/**
 * 書き込み可能バッファをロックし、インデックスを返す。
 */
static int lock_writable_header(waveout_data* data) {
	int i, result = -1;
	EnterCriticalSection(&data->lock);
	for (i = 0; i < WAVEHDR_COUNT; ++i) {
		if (data->headers[i].dwUser & WRITABLE_FLAG) {
			data->headers[i].dwUser &= ~WRITABLE_FLAG;
			result = i;
			break;
		}
	}
	LeaveCriticalSection(&data->lock);
	return result;
}

/**
 * 書き込み可能バッファ数を返す
 */
static int get_writable_header_count(waveout_data* data) {
	int i, result = 0;
	EnterCriticalSection(&data->lock);
	for (i = 0; i < WAVEHDR_COUNT; ++i) {
		if (data->headers[i].dwUser & WRITABLE_FLAG) {
			result++;
		}
	}
	LeaveCriticalSection(&data->lock);
	return result;
}

/**
 * WAVEFORMATEX構造体を初期化する
 */
static void init_waveformat(LPWAVEFORMATEX pwfe,
							int sampleRate,
							int sampleSizeInBits,
							int channels,
							int frameSize,
							int frameRate) {
	pwfe->wFormatTag = WAVE_FORMAT_PCM;
	pwfe->nChannels = (WORD) channels;
	pwfe->wBitsPerSample = (WORD) sampleSizeInBits;
	pwfe->nSamplesPerSec = (DWORD) sampleRate;
	pwfe->nBlockAlign = (WORD) frameSize;
	pwfe->nAvgBytesPerSec = (DWORD) (frameSize * frameRate);
}

/**
 * 最大バッファサイズを調整し、その値を返す。
 */
static int adjust_max_buffer_size(int bufferSize, WAVEFORMATEX* wfe) {
	int max_buffer_size = 0;

	// バッファサイズを決定する
	if (bufferSize && (bufferSize % wfe->nBlockAlign) == 0) {
		// バッファサイズがブロックアラインの倍数になっている場合
		// そのバッファサイズを使用する
		max_buffer_size = bufferSize;
	} else {
		// 指定されたブロックサイズをブロックアラインにあわせる
		int n = bufferSize / wfe->nBlockAlign;
		if (! n) {
			n = wfe->nBlockAlign;
		}
		max_buffer_size = wfe->nSamplesPerSec * n;
	}
	return max_buffer_size;
}

/**
 * WAVEHDR.lpDataにメモリを割り当てる
 */
static BOOL alloc_header_data(int max_buffer_size, WAVEHDR* headers) {
	int i;
	BOOL result = TRUE;
	for (i = 0; i < WAVEHDR_COUNT; ++i) {
		LPSTR buff = (LPSTR) malloc(max_buffer_size);
		if (! buff) {
			// メモリ不足
			int j;
			for (j = i - 1; j >= 0; --j) {
				// それまでに割り当てたメモリを開放する
				free(headers[j].lpData);
			}
			result = FALSE;
			break;
		}
		headers[i].lpData = buff;
		headers[i].dwBufferLength = max_buffer_size;
	}
	return result;
}

/**
 * WAVEHDR.lpDataに割り当てられたメモリを開放する
 */
static void free_header_data(WAVEHDR* headers) {
	int i;
	for (i = 0; i < WAVEHDR_COUNT; ++i) {
		free(headers[i].lpData);
	}
}

/**
 * RuntimeExceptionをthrowする
 */
void throw_RuntimeException(JNIEnv* env, const char* msg) {
	jclass clazz = (*env)->FindClass(env, "java/lang/RuntimeException");
	if (clazz) {
		(*env)->ThrowNew(env, clazz, msg);
	}
}

void throw_LineUnavailableException(JNIEnv* env, const char* msg) {
	jclass clazz = (*env)->FindClass(env, "javax/sound/sampled/LineUnavailableException");
	if (clazz) {
		(*env)->ThrowNew(env, clazz, msg);
	}
}

void throw_MMRESULT_Exception(JNIEnv* env, MMRESULT mmresult) {
	jclass clazz;
	jstring msg;
	_TCHAR buff[256];
	
	// メッセージを取得する
	waveOutGetErrorText(mmresult, buff, sizeof(buff) / sizeof(buff[0]));

	// メッセージを指定して、例外をthrowする。
	msg = (*env)->NewString(env, buff, _tcslen(buff));
	if (msg) {
		clazz = (*env)->FindClass(env, "javax/sound/sampled/LineUnavailableException");
		if (clazz) {
			jmethodID mid = (*env)->GetMethodID(env, clazz, "<init>", "(Ljava/lang/String;)V");
			jthrowable t = (*env)->NewObject(env, clazz, mid, msg);
			(*env)->Throw(env, t);
		}
	}
}

//----------------------------------------------------
//--- これ以降は SourceDataLine ----------------------
//----------------------------------------------------

/*
 * Class:     gnu_javax_sound_sampled_wce_WCESourceDataLine
 * Method:    openNative
 * Signature: (ILjavax/sound/sampled/AudioFormat$Encoding;FIIIFZ)I
 */
JNIEXPORT jint JNICALL Java_gnu_javax_sound_sampled_wce_WCESourceDataLine_openNative
						(JNIEnv *env,
						 jobject instance,
						 jint bufferSize,
						 jfloat sampleRate,
						 jint sampleSizeInBits,
						 jint channels,
						 jint frameSize,
						 jfloat frameRate,
						 jboolean bigEndian) {
	int i;
	MMRESULT mmresult;
	
	// waveout_dataを確保する
	waveout_data* data = get_waveout_data(NULL);
	if (! data) {
		throw_LineUnavailableException(env, "Too many waveout_data");
		return 0;
	}
	
	// 確保した構造体の内容を初期化する
	InitializeCriticalSection(&data->lock);
	data->hHeaderEvent = CreateEvent(NULL,
									     FALSE,
										 FALSE,
										 NULL);
	data->instance = instance;

	// フォーマットの内容から、WAVEFORMATEX構造体を初期化する
	init_waveformat(&data->waveformat,
					(int) sampleRate,
					sampleSizeInBits,
					channels,
					frameSize,
					(int) frameRate);

	// バッファサイズを決定する
	data->max_buffer_size = adjust_max_buffer_size(bufferSize, &data->waveformat);

	// バッファを確保する
	// バッファを確保する
	if (! alloc_header_data(data->max_buffer_size, data->headers)) {
		close_waveout_data(data);
		throw_LineUnavailableException(env, "Not enough memory");
		return 0;
	}

	// 出力デバイスをオープンする
	mmresult = waveOutOpen(&data->hWaveOut,
						   WAVE_MAPPER,
						   &data->waveformat,
						   (DWORD) g_hwndCallback,
						   0,
						   CALLBACK_WINDOW);
	if (mmresult != MMSYSERR_NOERROR) {
		// エラー処理
		close_waveout_data(data);
		throw_MMRESULT_Exception(env, mmresult);
		return 0;
	}

	{
		// ボリューム取得テスト
		DWORD volume;
		waveOutGetVolume(data->hWaveOut, &volume);
		volume = volume;
	}

	// WAVEHDRを準備状態にする
	for (i = 0; i < WAVEHDR_COUNT; ++i) {
		data->headers[i].dwLoops = 1;

		mmresult = waveOutPrepareHeader(data->hWaveOut,
						&data->headers[i],
						sizeof(WAVEHDR));
		if (mmresult != MMSYSERR_NOERROR) {
			waveOutClose(data->hWaveOut);
			close_waveout_data(data);
			throw_MMRESULT_Exception(env, mmresult);
			return 0;
		}
		// 書き込み可能フラグを立てておく
		data->headers[i].dwUser |= WRITABLE_FLAG;
	}

	return (jint) data;
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCESourceDataLine
 * Method:    writeNative
 * Signature: (I[BII)I
 */
JNIEXPORT jint JNICALL Java_gnu_javax_sound_sampled_wce_WCESourceDataLine_writeNative
(JNIEnv *env, jobject instance, jint nativeData, jbyteArray b, jint off, jint len) {
	char* buff;
	int bytes_left;
	char* p;

	// waveout_data構造体のポインタを得る
	waveout_data* data = (waveout_data*) nativeData;
	if (! data) {
		return 0;
	}

	// 指定されたバイト長を、バッファのアラインに合わせる
	len -= (len % data->waveformat.nBlockAlign);
	if (len <= 0) {
		return 0;
	}

	// バッファのポインタを得る
	buff = (*env)->GetByteArrayElements(env, b, NULL);
	bytes_left = len;
	p = buff + off;

	// すべてのバイトを書き込むまで以下の処理を繰り返す
	while (bytes_left > 0) {
		// アクティブ状態を調べておく
		BOOL active = is_waveout_active(data);
		WAVEHDR* hdr = NULL;
		int write_count, writable_header_index;

		// 書き込み可能ヘッダを取得する
		while ((writable_header_index = lock_writable_header(data)) < 0) {
			// イベント通知を待つ
			if (WaitForSingleObject(data->hHeaderEvent, INFINITE) != WAIT_OBJECT_0) {
				// エラー発生
				// throw_RuntimeException(env,  "WaitForSingleObject() failed");
				goto END;
			}
			// この時点で/stop()のいずれかが呼び出されていたら
			// 即座にリターンする
			// ToDo: flush()でもリターンするようにする
			if (data->stopped) {
				break;
			}
		}
		if (data->stopped) {
			goto END;
		}

		if (writable_header_index < 0) {
			// 何らかの原因でロックできなかった場合はリターンする
			goto END;
		}

		// クリティカルセクションに入る
		EnterCriticalSection(&data->lock);

		// 書き込むバッファを決定する
		hdr = &data->headers[writable_header_index];
		// 書き込むべきバイト数を計算し、バッファにコピーする
		write_count = (bytes_left <= data->max_buffer_size)
						? bytes_left
						: data->max_buffer_size;
		memcpy(hdr->lpData, p, write_count);
		hdr->dwBufferLength = write_count;

		// クリティカルセクションから抜ける
		LeaveCriticalSection(&data->lock);

		// 再生キューに入れる
		if (waveOutWrite(data->hWaveOut, hdr, sizeof(WAVEHDR))
				== MMSYSERR_NOERROR) {
			if (! active) {
				// アクティブ状態が変化する場合は
				// STARTイベントを送信する
				post_LineEvent(env,
						 data->instance,
						 g_LineEvent_Type_START,
						 AudioSystem_NOT_SPECIFIED);
			}
		} else {
			throw_RuntimeException(env, "waveOutWrite() failed");
			break;
		}

		// カウンタとポインタを更新する
		p += write_count;
		bytes_left -= write_count;

	}

END:
	(*env)->ReleaseByteArrayElements(env, b, buff, 0);
	// キューに入れたバイト数を返す
	return len - bytes_left;
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCESourceDataLine
 * Method:    nativeClose
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_gnu_javax_sound_sampled_wce_WCESourceDataLine_nativeClose
(JNIEnv *env, jobject instance, jint nativeData) {
	waveout_data* data = (waveout_data*) nativeData;
	int i;
	MMRESULT mmresult;

	if (! data) {
		return;
	}

	// この関数が呼び出されて時点で、すでにwaveOutReset()が呼び出されている

	// ヘッダをクリーンアップする
	for (i = 0; i < WAVEHDR_COUNT; ++i) {
		mmresult = waveOutUnprepareHeader(data->hWaveOut, &data->headers[i], sizeof(WAVEHDR));
		if (mmresult != MMSYSERR_NOERROR) {
			throw_MMRESULT_Exception(env, mmresult);
			break;
		}
	}

	mmresult = waveOutClose(data->hWaveOut);
	if (mmresult != MMSYSERR_NOERROR) {
		throw_MMRESULT_Exception(env, mmresult);
	}
}


/*
 * Class:     gnu_javax_sound_sampled_wce_WCESourceDataLine
 * Method:    drainNative
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_gnu_javax_sound_sampled_wce_WCESourceDataLine_drainNative
(JNIEnv *env, jobject instance, jint nativeData) {
	// waveout_data構造体のポインタを得る
	waveout_data* data = (waveout_data*) nativeData;
	if (! data) {
		return;
	}

	// すべてのヘッダが書き込み可能になるまで待つ
	while (get_writable_header_count(data) < WAVEHDR_COUNT) {
		if (WaitForSingleObject(data->hHeaderEvent, INFINITE) != WAIT_OBJECT_0) {
			// 例外をthrow
			(*env)->ThrowNew(env, "java/lang/RuntimeException", "WaitForSingleObject() failed");
			break;
		}
	}
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCESourceDataLine
 * Method:    flushNative
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_gnu_javax_sound_sampled_wce_WCESourceDataLine_flushNative
(JNIEnv *env, jobject instance, jint nativeData) {
	// waveout_data構造体のポインタを得る
	waveout_data* data = (waveout_data*) nativeData;
	if (! data) {
		return;
	}

	// リセットフラグを設定しておく
	EnterCriticalSection(&data->lock);
	data->reset = TRUE;
	LeaveCriticalSection(&data->lock);

	// リセットする
	if (waveOutReset(data->hWaveOut) != MMSYSERR_NOERROR) {
		(*env)->ThrowNew(env, "java/lang/RuntimeError", "waveOutReset() failed");
		return;
	}
}


/*
 * Class:     gnu_javax_sound_sampled_wce_WCESourceDataLine
 * Method:    isNativeActive
 * Signature: (I)Z
 */
JNIEXPORT jboolean JNICALL Java_gnu_javax_sound_sampled_wce_WCESourceDataLine_isNativeActive
(JNIEnv *env, jobject instance, jint nativeData) {
	if (! nativeData) {
		return JNI_FALSE;
	}

	return is_waveout_active((waveout_data*) nativeData) ? JNI_TRUE : JNI_FALSE;
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCESourceDataLine
 * Method:    getNativeBufferSize
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_gnu_javax_sound_sampled_wce_WCESourceDataLine_getNativeBufferSize
(JNIEnv *env, jobject instance, jint nativeData) {
	// バッファサイズを返す
	if (! nativeData) {
		return AudioSystem_NOT_SPECIFIED;
	}

	return (jint) (((waveout_data*) nativeData)->max_buffer_size);
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCESourceDataLine
 * Method:    availableNative
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_gnu_javax_sound_sampled_wce_WCESourceDataLine_availableNative
(JNIEnv *env, jobject instance, jint nativeData) {
	int i;
	waveout_data* data = (waveout_data*) nativeData;
	jint result = 0;
	if (! data) {
		return 0;
	}

	EnterCriticalSection(&data->lock);
	for (i = 0; i < WAVEHDR_COUNT; ++i) {
		// バッファが１つあいている場合、そのサイズを返す
		if (data->headers[i].dwUser & WRITABLE_FLAG) {
			result = data->max_buffer_size;
			break;
		}
	}
	LeaveCriticalSection(&data->lock);
	return result;
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCESourceDataLine
 * Method:    startNative
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_gnu_javax_sound_sampled_wce_WCESourceDataLine_startNative
(JNIEnv *env, jobject instance, jint nativeData) {
	// waveout_data構造体のポインタを得る
	waveout_data* data = (waveout_data*) nativeData;
	if (! data) {
		return;
	}

	if (waveOutRestart(data->hWaveOut) != MMSYSERR_NOERROR) {
		(*env)->ThrowNew(env, "java/lang/RuntimeException", "waveOutRestart() failed");
		return;
	}

	EnterCriticalSection(&data->lock);
	data->stopped = FALSE;
	LeaveCriticalSection(&data->lock);

	// 再生中のバッファが存在する場合、STARTイベントを送信する
	if (is_waveout_active(data)) {
		post_LineEvent(env,
				 data->instance,
				 g_LineEvent_Type_START,
				 AudioSystem_NOT_SPECIFIED);
	}
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCESourceDataLine
 * Method:    stopNative
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_gnu_javax_sound_sampled_wce_WCESourceDataLine_stopNative
(JNIEnv *env, jobject instance, jint nativeData) {
	// waveout_data構造体のポインタを得る
	waveout_data* data = (waveout_data*) nativeData;
	if (! data) {
		return;
	}

	// stoppedフラグを設定する
	EnterCriticalSection(&data->lock);
	data->stopped = TRUE;
	LeaveCriticalSection(&data->lock);

	if (waveOutPause(data->hWaveOut) != MMSYSERR_NOERROR) {
		(*env)->ThrowNew(env, "java/lang/RuntimeException", "waveOutPause() failed");
		return;
	}

	// STOPイベントを送信する
	post_LineEvent(env,
			 data->instance,
			 g_LineEvent_Type_STOP,
			 AudioSystem_NOT_SPECIFIED);
	return;
}

//----------------------------------------------------
//--- ここまでが SourceDataLine ----------------------
//----------------------------------------------------


//----------------------------------------------------
//--- これ以降は TargetDataLine ----------------------
//----------------------------------------------------

/*
 * Class:     gnu_javax_sound_sampled_wce_WCETargetDataLine
 * Method:    openNative
 * Signature: (IFIIIFZ)I
 */
JNIEXPORT jint JNICALL Java_gnu_javax_sound_sampled_wce_WCETargetDataLine_openNative
						(JNIEnv *env,
						 jobject instance,
						 jint bufferSize,
						 jfloat sampleRate,
						 jint sampleSizeInBits,
						 jint channels,
						 jint frameSize,
						 jfloat frameRate,
						 jboolean bigEndian) {
	int i;
	MMRESULT mmresult;

	// wavein_dataを確保する
	wavein_data* data = get_wavein_data(NULL);
	if (! data) {
		throw_LineUnavailableException(env, "Too many wavein_data");
		return 0;
	}
	
	// 確保した構造体の内容を初期化する
	InitializeCriticalSection(&data->lock);
	data->hHeaderEvent = CreateEvent(NULL,
									 FALSE,
									 FALSE,
									 NULL);
	data->instance = instance;

	// フォーマットの内容から、WAVEFORMATEX構造体を初期化する
	// フォーマットの内容から、WAVEFORMATEX構造体を初期化する
	init_waveformat(&data->waveformat,
					(int) sampleRate,
					sampleSizeInBits,
					channels,
					frameSize,
					(int) frameRate);

	// バッファサイズを決定する
	data->max_buffer_size = adjust_max_buffer_size(bufferSize, &data->waveformat);

	// バッファを確保する
	if (! alloc_header_data(data->max_buffer_size, data->headers)) {
		close_waveout_data(data);
		throw_LineUnavailableException(env, "Not enough memory");
		return 0;
	}

	// 入力デバイスをオープンする
	mmresult = waveInOpen(&data->hWaveIn,
						   WAVE_MAPPER,
						   &data->waveformat,
						   (DWORD) g_hwndCallback,
						   0,
						   CALLBACK_WINDOW);
	if (mmresult != MMSYSERR_NOERROR) {
		// エラー処理
		close_wavein_data(data);
		throw_MMRESULT_Exception(env, mmresult);
		return 0;
	}

	// WAVEHDRを準備状態にする
	for (i = 0; i < WAVEHDR_COUNT; ++i) {
		mmresult = waveInPrepareHeader(data->hWaveIn,
						&data->headers[i],
						sizeof(WAVEHDR));
		if (mmresult != MMSYSERR_NOERROR) {
			waveInClose(data->hWaveIn);
			close_wavein_data(data);
			throw_MMRESULT_Exception(env, mmresult);
			return 0;
		}
	}

	// 先頭のヘッダにマークをつける
	data->headers[0].dwUser |= READ_HEAD_FLAG;

	// システムキューに入れる
	for (i = 0; i < WAVEHDR_COUNT; ++i) {
		mmresult = waveInAddBuffer(data->hWaveIn,
								   &data->headers[i],
								   sizeof(WAVEHDR));
		if (mmresult != MMSYSERR_NOERROR) {
			waveInClose(data->hWaveIn);
			close_wavein_data(data);
			throw_MMRESULT_Exception(env, mmresult);
			return 0;
		}
	}

	return (jint) data;
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCETargetDataLine
 * Method:    readNative
 * Signature: (I[BII)I
 */
JNIEXPORT jint JNICALL Java_gnu_javax_sound_sampled_wce_WCETargetDataLine_readNative
(JNIEnv *env, jobject instance, jint nativeData, jbyteArray b, jint off, jint len) {
	wavein_data* data = (wavein_data*) nativeData;
	int buff_left;
	char* p;
	char* org_p;
	char* end_p;

	if (! data) {
		return 0;
	}

	// 指定されたバイト長を、バッファのアラインに合わせる
	len -= (len % data->waveformat.nBlockAlign);
	if (len <= 0) {
		return 0;
	}
	buff_left = len;
	p = (*env)->GetByteArrayElements(env, b, NULL);
	if (! p) {
		return 0;
	}
	org_p = p;
	end_p = p + len;

	// コピー開始位置
	p += off;

	// 指定されたバイト数分コピーするまで、処理を繰り返す
	while (p < end_p) {
		int index;
		DWORD bytes_remains;
		WAVEHDR* hdr;
		DWORD copy_len;
		MMRESULT mmresult;
		BOOL active;

		while ((index = lock_readable_header(data)) < 0) {
			// ヘッダ内のデータが読み取り可能になるまで待機する

			if (WaitForSingleObject(data->hHeaderEvent, INFINITE) != WAIT_OBJECT_0) {
				// 待機中にエラー発生
				// throw_RuntimeException(env, "WaitForSingleObject() failed");
				goto END;
			}

			if (data->stopped || data->closing) {
				// 待機中にstop()された
				// ToDo: flush()された場合にもすぐにリターンするように修正する
				break;
			}
		}
		if (data->stopped || data->closing) {
			// 停止された場合はすぐにリターンする
			// ToDo: flush()された場合にもすぐにリターンするように修正する
			goto END;
		}

		assert (0 <= index && index < WAVEHDR_COUNT);
		
		// この時点でアクティブかを調べる
		active = is_wavein_active(data);

		// 読み出し可能なWAVEHDRを得る
		hdr = &data->headers[index];

		// データをバッファにコピーする
		bytes_remains = GET_READ_REMAINS(hdr->dwUser);
		assert(bytes_remains <= hdr->dwBytesRecorded);
		copy_len = (p + bytes_remains >= end_p)
				? (end_p - p)
				: bytes_remains;
		memcpy(p,
		       hdr->lpData + (hdr->dwBytesRecorded - bytes_remains),
		       copy_len);

		
		// 読み出しバッファをアンロックする
		bytes_remains -= copy_len;
		unlock_readable_header(data,
				       index,
				       bytes_remains);
		if ((bytes_remains <= 0)
				&& (! data->closing)) {
			// 全てのデータを読み込んだ場合は、
			// 再度入力キューに入れなおす
			mmresult = waveInAddBuffer(data->hWaveIn, hdr, sizeof(WAVEHDR));
			if (mmresult != MMSYSERR_NOERROR) {
				// エラー発生
				throw_MMRESULT_Exception(env, mmresult);
				goto END;
			}
		}
		
		// アクティブ状態に移行した場合、STARTイベントを送る
		if (! active) {
			post_LineEvent(env,
					 data->instance,
					 g_LineEvent_Type_START,
					 AudioSystem_NOT_SPECIFIED);
		}

		// コピー位置を更新する
		p += copy_len;
	}
END:
	(*env)->ReleaseByteArrayElements(env, b, org_p, 0);
	// バッファにコピーしたバイト数を返す
	return p - org_p;
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCETargetDataLine
 * Method:    closeNative
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_gnu_javax_sound_sampled_wce_WCETargetDataLine_closeNative
(JNIEnv *env, jobject instance, jint nativeData) {
	int i;
	MMRESULT mmresult;
	wavein_data* data = (wavein_data*) nativeData;
	if (! data) {
		return;
	}

	EnterCriticalSection(&data->lock);
	data->closing = TRUE;
	LeaveCriticalSection(&data->lock);
	
	// waveInReset(), waveInUnprepareHeader(), waveInClose()の順に呼び出す
	mmresult = waveInReset(data->hWaveIn);
	SetEvent(data->hHeaderEvent);

	if (mmresult == MMSYSERR_NOERROR) {
		for (i = 0; i < WAVEHDR_COUNT; ++i) {
			mmresult = waveInUnprepareHeader(data->hWaveIn, &data->headers[i], sizeof(WAVEHDR));
		}
		mmresult = waveInClose(data->hWaveIn);
	}

	if (mmresult != MMSYSERR_NOERROR) {
		throw_MMRESULT_Exception(env, mmresult);
	}
}
/*
 * Class:     gnu_javax_sound_sampled_wce_WCETargetDataLine
 * Method:    drainNative
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_gnu_javax_sound_sampled_wce_WCETargetDataLine_drainNative
(JNIEnv *env, jobject instance, jint nativeData) {
	wavein_data* data = (wavein_data*) nativeData;
	if (! data) {
		return;
	}

	// １つ以上のWAVEHDRが読み出し可能になるまでじっと待つ
	for (;;) {
		int i;
		BOOL readable_buffer_exists = FALSE;

		EnterCriticalSection(&data->lock);
		for (i = 0; i < WAVEHDR_COUNT; ++i) {
			// 読み出し可能なバッファがあるか調べる
			if (data->headers[i].dwUser & READABLE_FLAG) {
				readable_buffer_exists = TRUE;
				break;
			}
		}
		LeaveCriticalSection(&data->lock);

		if (readable_buffer_exists) {
			// 読み出し可能なバッファが1つ以上ある
			break;
		}

		// イベントが発生するまで待機する
		if (WaitForSingleObject(data->hHeaderEvent, INFINITE) != WAIT_OBJECT_0) {
			// 待機中にエラー発生
			throw_RuntimeException(env, "WaitForSingleObject() failed.");
			break;
		}
	}
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCETargetDataLine
 * Method:    flushNative
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_gnu_javax_sound_sampled_wce_WCETargetDataLine_flushNative
(JNIEnv *env, jobject instance, jint nativeData) {
	wavein_data* data = (wavein_data*) nativeData;
	if (! data) {
		return;
	}

	// リセットフラグを立てる
	EnterCriticalSection(&data->lock);
	data->reset = TRUE;
	LeaveCriticalSection(&data->lock);

	// システムにバッファのリセットを指示する
	waveInReset(data->hWaveIn);

	// この後、非同期にMM_WIM_DATA メッセージが来る
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCETargetDataLine
 * Method:    isNativeActive
 * Signature: (I)Z
 */
JNIEXPORT jboolean JNICALL Java_gnu_javax_sound_sampled_wce_WCETargetDataLine_isNativeActive
(JNIEnv *env, jobject instance, jint nativeData) {
	wavein_data* data = (wavein_data*) nativeData;
	if (! data) {
		return JNI_FALSE;
	}

	return (jboolean) is_wavein_active(data);
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCETargetDataLine
 * Method:    getNativeBufferSize
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_gnu_javax_sound_sampled_wce_WCETargetDataLine_getNativeBufferSize
(JNIEnv *env, jobject instance, jint nativeData) {
	wavein_data* data = (wavein_data*) nativeData;
	if (! data) {
		return AudioSystem_NOT_SPECIFIED;
	}
	return (jint) data->max_buffer_size;
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCETargetDataLine
 * Method:    availableNative
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_gnu_javax_sound_sampled_wce_WCETargetDataLine_availableNative
(JNIEnv *env, jobject instance, jint nativeData) {
	wavein_data* data = (wavein_data*) nativeData;
	int i;
	jint size = 0;
	if (! data) {
		return 0;
	}

	EnterCriticalSection(&data->lock);
	for (i = 0; i < WAVEHDR_COUNT; ++i) {
		DWORD dwUser = data->headers[i].dwUser;
		if (dwUser & (READABLE_FLAG | READ_HEAD_FLAG)) {
			size = GET_READ_REMAINS(data->headers[i].dwUser);
			break;
		}
	}
	return size;
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCETargetDataLine
 * Method:    startNative
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_gnu_javax_sound_sampled_wce_WCETargetDataLine_startNative
(JNIEnv *env, jobject instance, jint nativeData) {
	wavein_data* data = (wavein_data*) nativeData;
	if (! data) {
		return;
	}

	EnterCriticalSection(&data->lock);
	data->stopped = FALSE;
	data->reset = FALSE;;
	LeaveCriticalSection(&data->lock);

	// 音声入力を開始する
	waveInStart(data->hWaveIn);

	// 再生中のバッファが存在する場合、STARTイベントを送信する
	if (is_wavein_active(data)) {
		post_LineEvent(env,
				 data->instance,
				 g_LineEvent_Type_START,
				 AudioSystem_NOT_SPECIFIED);
	}
}

/*
 * Class:     gnu_javax_sound_sampled_wce_WCETargetDataLine
 * Method:    stopNative
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_gnu_javax_sound_sampled_wce_WCETargetDataLine_stopNative
(JNIEnv *env, jobject instance, jint nativeData) {
	wavein_data* data = (wavein_data*) nativeData;
	if (! data) {
		return;
	}

	EnterCriticalSection(&data->lock);
	data->stopped = TRUE;
	data->reset = FALSE;
	LeaveCriticalSection(&data->lock);

	// 音声入力をストップする
	waveInStop(data->hWaveIn);

	// 待機しているスレッドがいるかもしれないので、
	// イベントを通知する
	SetEvent(data->hHeaderEvent);

	// STOPイベントを送信する
	post_LineEvent(env,
			 data->instance,
			 g_LineEvent_Type_STOP,
			 AudioSystem_NOT_SPECIFIED);
}
