/*
 *	clipdirs.c
 *
 *	演出シーケンサ
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2015-2018 Naoyuki Sawa
 *
 *	* Sun Feb 08 19:01:54 JST 2015 Naoyuki Sawa
 *	- 1st リリース。
 *	* Thu Apr 23 21:42:53 JST 2015 Naoyuki Sawa
 *	- DirSeq_Stop(),DirSeqCh_Stop()を追加しました。
 *	  演出テーブルをクリアせずに、現在実行中の演出シーケンスを停止したい場合に使用します。
 *	  例えば、'あるトリガ'から??秒後に'ある表示'を行うような演出シーケンスを開始した後に、何らかの条件でその予約を取り消す場合等です。
 *	* Tue May 05 17:15:43 JST 2015 Naoyuki Sawa
 *	- DirSeqCh_SetTblNo()を追加しました。
 *	* Sun Nov 22 21:02:41 JST 2015 Naoyuki Sawa
 *	- DirTrg_setとDirTrg_clrのシンボル定義を追加しました。
 *	  2015/11/20のdDirSeqC.exeの変更により、DirTrg_setとDirTrg_clr以外のトリガ番号のシンボルはdDirSeqC.exeが出力するようになりました。
 *	  dDirSeqC.exeが出力しない固定のトリガ番号のシンボルのみ、当モジュール内で定義する事にしました。
 *	* Tue Aug 02 22:11:15 JST 2016 Naoyuki Sawa
 *	- 演出実行関数(fnAct)の引数仕様を変更しました。
 *	  【変更前】void fnAct(int ActNo);				ActNo = [31]=オーバーフロー,[30:24]=未定義,[23:0]=演出番号
 *	  【変更後】void fnAct(int iCh, int ActNo, int bOverFlow);	iCh = 演出シーケンサチャネル番号, ActNo = 演出番号, bOverFlow = オーバーフロー
 *	- 今回の変更により、演出実行関数に演出シーケンサチャネル番号も通知されるようになりました。
 *	  アプリケーション側で、演出シーケンサチャネル番号も参照して、演出実行処理を行えるようになりました。
 *	- 今回の変更のために、演出番号バッファ(ST_DirSeq.pActBuf)をスタックフレーム上に確保するサイズが増加しました。(clipdirs.cの_DirSeq_SetActBuf())
 *	  (DirSeq_ActBufSize=256)の場合、変更前は(256×3)=768バイトだったのに対し、変更後は(256×4)=1024バイトになりました。
 *	  アプリケーションのMakefileにて、スタックサイズを十分確保するよう、注意して下さい。
 *	* Sun Sep 04 21:41:17 JST 2016 Naoyuki Sawa
 *	- 「#pragma warning(disable:...)」～「#pragma warning(default:...)」を、「#pragma warning(push)」「#pragma warning(disable:...)」～「#pragma warning(pop)」に変更しました。
 *	  詳細は、clipstd.h の同日のコメントを参照して下さい。
 *	* Mon Sep 05 21:24:05 JST 2016 Naoyuki Sawa
 *	- 64ビット対応のために、_DirSeq_SetActBuf()経由で呼び出される事が有る関数の第二パラメータ(arg2)に相当する引数の型を、(int)⇒(intptr_t)に変更しました。
 *	  _DirSeq_SetActBuf()経由で呼び出される事が有る関数の一つである、_DirSeqCh_SetTbl()の第二パラメータがポインタ型なので、その他の関数の第二パラメータも同じ変数サイズに揃える必要が有るためです。
 *	  尚、32ビットでは(int)も(intptr_t)も同じなので、既存の32ビットコードには影響有りません。
 *	* Fri Sep 09 21:13:53 JST 2016 Naoyuki Sawa
 *	- 演出シーケンサを、複数インスタンスに対応しました。
 *	  当モジュールの関数を呼び出している既存のアプリケーションも、修正が必要になります。
 *	  変更点は、以下の通りです:
 *	  ①clipdirs.cのローカル変数stDirSeqを廃止しました。
 *	    │今後は、アプリケーション側でST_DirSeq構造体のメモリを確保するか、ST_DirSeq型のグローバル変数を定義して下さい。
 *	  ②既存の各関数に、pDirSeq引数を追加しました。
 *	    │上記で確保したST_DirSeqインスタンスのポインタを、pDirSeq引数に指定して下さい。
 *	  ③演出実行関数(fnAct)に、pDirSeq引数を追加しました。
 *	    │どの演出シーケンサのインスタンスから呼び出されたかを、判断するための引数です。
 *	    │例えば、一つのオブジェクトに一つの演出シーケンサを関連付ける場合は、オブジェクトの中にST_DirSeq構造体を埋め込んでCONTAINING_RECORD()を使うか、又は、g_dataset_set_data()等でアドレスを関連付けて下さい。
 *	- 今回の変更と一緒に、ロックバッファサイズ(DirSeqCh_LockBufSize),スタッサイズ(DirSeqCh_StackSize),演出シーケンサチャネル番号の最大値(DirSeqCh_Max)等も、
 *	  定数ではなく、実行時に各インスタンス毎に指定出来るようにしようかとも考えたのですが、やっぱり、今回はそれは行わない事にしました。
 *	  一度に変更すると、変更箇所が多すぎて、デバッグが難しくなると思ったからです。
 *	  まずは、今回の変更が安定して動作する事を確認してから、上記の変更も行いたいと思います。							⇒{{2016/09/10コメント追記:演出シーケンサチャネル数を、定数でなく、実行時に各インスタンス毎に指定出来るようにしました。}}
 *	  特に、今回、複数インスタンスに対応した事によって、各インスタンスの演出シーケンサチャネル番号の最大値(DirSeqCh_Max)はもっと少なくて済むようになった可能性が高いので、
 *	  演出シーケンサチャネル番号の最大値(DirSeqCh_Max)を、定数でなく、実行時に各インスタンス毎に指定出来るようにする変更を、優先して行うべきです。	⇒{{2016/09/10コメント追記:演出シーケンサチャネル数を、定数でなく、実行時に各インスタンス毎に指定出来るようにしました。}}
 *	* Sat Sep 10 21:46:35 JST 2016 Naoyuki Sawa
 *	- 演出シーケンサチャネル数を、定数でなく、実行時に各インスタンス毎に指定出来るようにしました。
 *	  この変更を行った理由については、clipdirs.hの'Fri Sep 09 21:13:53 JST 2016'のコメントを参照して下さい。
 *	* Mon Sep 12 21:10:58 JST 2016 Naoyuki Sawa
 *	- 演出シーケンスが動作中であるかを取得する関数を追加しました。
 *	* Fri May 12 09:53:14 JST 2017 Naoyuki Sawa
 *	- これまでにコメントアウトして残していた変更前のコードを、全て削除しました。
 *	  変更前のコードを参照する必要が生じた場合は、前回のアーカイブを参照して下さい。
 *	  近々、個々の演出シーケンサチャネル内で複数のコンテキストを同時に実行出来るようにする変更を行う予定なので、そのために一旦ソース整理して単純化するためです。
 *	  今回はコメントを削除しただけで、処理は変更していないので、まだ動作は変わっていません。
 *	* Sat May 13 21:44:35 JST 2017 Naoyuki Sawa
 *	- fork命令を追加しました。
 *	  一つのチャネルの中で、複数のコンテキストを同時進行出来るようになりました。
 *	- 今回の変更は、当モジュール内の処理としては大幅な変更でしたが、アプリケーション関数の仕様変更は有りません。
 *	  新しいfork命令を使用しなければ、既存のアプリケーションには影響有りません。
 *	* Tue May 23 23:35:30 JST 2017 Naoyuki Sawa
 *	- hide命令を追加しました。
 *	  当該チャネルにコンテキストが残っていても、DirSeq_GetStat(),及び,DirSeqCh_GetStat()の観点からは、当該チャネルが停止中であるように見せかけるための命令です。
 *	  この命令を追加した理由は、以下の通りです。
 *	- forkとrepeatを使って、副次的な演出の無限ループを組みたい事が時々有ります。
 *	  □スクリプトの例
 *	  │	Test1
 *	  │	  fork					//┐
 *	  │	    repeat				//│
 *	  │	      TapLogCh_Play_(Se1,左足の音)	//│
 *	  │	      wait SEC(1.0)			//│一秒ごとに足音を鳴らす無限ループ
 *	  │	      TapLogCh_Play_(Se1,右足の音)	//│
 *	  │	      wait SEC(1.0)			//│
 *	  │	    end					//│
 *	  │	  end					//┘
 *	  │	  act 道を歩く
 *	  │	  wait SEC(3.0)
 *	  │	  act 建物の中へ入って行く
 *	  │	  wait SEC(3.0)
 *	  │	end
 *	  プログラム側では、DirSeqCh_GetStat()を使って、この演出の完了を待ちます。
 *	  しかし実際には、この演出は完了しません。
 *	  最初に開始したコンテキストが6秒間で完了した後も、足音を鳴らすコンテキストが無限ループで実行され続けているからです。
 *	  これまでは、上記のような演出を行うためには、サウンド自体をループにしてforkを使わないようにする等の方法を取らざるを得ず、forkが充分に活用出来ませんでした。
 *	- そこで今回、hide命令を追加して、hide命令を実行したコンテキストは、DirSeqCh_GetStat()の観点からは存在しないように見せかける事にしました。
 *	  変更後のスクリプトは、以下のようになります。
 *	  □スクリプトの例
 *	  │	Test1
 *	  │	  fork					//┐
 *	  │	    hide	//←───────────│──────注目!!
 *	  │	    repeat				//│
 *	  │	      TapLogCh_Play_(Se1,左足の音)	//│
 *	  │	      wait SEC(1.0)			//│一秒ごとに足音を鳴らす無限ループ
 *	  │	      TapLogCh_Play_(Se1,右足の音)	//│
 *	  │	      wait SEC(1.0)			//│
 *	  │	    end					//│
 *	  │	  end					//┘
 *	  │	  act 道を歩く
 *	  │	  wait SEC(3.0)
 *	  │	  act 建物の中へ入って行く
 *	  │	  wait SEC(3.0)
 *	  │	end
 *	  プログラム側の変更は不要で、DirSeqCh_GetStat()を使えば、足音を鳴らすコンテキスト(hideを実行したコンテキスト)が無限ループで残っていても、最初に開始したコンテキスト(hideを実行していないコンテキスト)が完了すれば、当該チャネルは完了したと判断されます。
 *	  尚、forkで開始したコンテキストでしかhideが使えないと言う訳ではなく、最初に開始したコンテキストでhideを実行すれば最初に開始したコンテキストが隠れコンテキストとなります。
 *	  また、上記のスクリプトの例ではforkの直後でhideを実行していますが必然ではなく、途中でhideを呼び出しても構いません。直観通り、hideを呼び出すまでは通常コンテキストで、hideを呼び出した時点から隠れコンテキストとなります。
 *	- 実装上の注意点として、もし今後当モジュールの処理を変更する場合、当モジュールの内部からは、DirSeqCh_GetStat()を使って当該チャネルが停止しているかどうかを判断してはいけません。
 *	  隠れコンテキストが残っていても当該チャネルを停止と見なして良いのは、あくまでもアプリケーションの観点からのみであり、当モジュールの実行処理(DirSeq_Exec())や停止処理(DirSeq_Stop())の観点からは、隠れコンテキストも通常のコンテキストと同様に、実行,及び,停止がなされなければならないからです。
 *	  現在の所、当モジュールの内部処理でDirSeqCh_GetStat()を使用している箇所は無いので問題ありませんが、今後当モジュールの処理を変更する場合も、エンバグしないように注意して下さい。
 *	* Wed May 24 21:26:30 JST 2017 Naoyuki Sawa
 *	- fork命令で開始したコンテキストは、元コンテキストの隠れコンテキストフラグを継承するようにしました。
 *	  変更した理由は、以下の通りです。
 *	- 変更前は、fork命令で開始したコンテキストも含めて、全てのコンテキストは初期状態で'通常のコンテキスト'(=隠れコンテキストではない)としていました。
 *	  しかしそれでは、既に隠れコンテキストであるコンテキストがfork命令を実行して新たなコンテキストを開始した場合に、問題が生じる可能性が有る事に気付きました。
 *	  例えば、以下のようなケースです。
 *	  当該チャネルに属するコンテキストが全て隠れコンテキストである場合、アプリケーションの観点からは(=DirSeq_GetStat(),及び,DirSeqCh_GetStat()の観点からは)、そのチャネルは擬似的に停止中と見なします。
 *	  その後、隠れコンテキストがfork命令を実行した場合、(昨日までの動作では)開始した新たなコンテキストが通常コンテキストなので、そのチャネルが動作中に戻る事になってしまっていました。
 *	  アプリケーションの観点から、停止中のチャネルが能動的に動作中に戻るのは明らかに不自然であり、DirSeq_GetStat()やDirSeqCh_GetStat()を呼び出すタイミングによって結果が予測し辛い問題を生じてしまいます。
 *	- 昨日の動作のままで上記の問題を回避することも、一応は可能でした。
 *	  隠れコンテキストのfork命令で開始したブロックは、必ず先頭でhideするようにスクリプトで明示的に指定する事です。
 *	  とは言え、スクリプトの全ての部分について隠れコンテキストであるかどうかを認識して組むのは管理が困難ですし、もしhideを書き忘れた場合に見つけ辛いバグとなる恐れが有ります。
 *	  だいたい、隠れコンテキストがなんらかの処理を行うためにfork命令で別のコンテキストを生成したい場合、そのコンテキストも隠れコンテキストとしたいであろう事はまず間違いないと思うので、利便性の観点からも明示的にhideを書く必要が有るのは問題が有ります。
 *	- そこで本日、動作を変更して、隠れコンテキストがfork命令で開始したコンテキストは、初期状態で隠れコンテキストとする事にしました。
 *	  要するに、fork命令で開始したコンテキストは、元コンテキストの隠れコンテキストフラグを継承するようにしました。
 *	  もう、前述の、擬似的に停止中になったチャネルが能動的に動作中に戻るような、望ましくない動作が発生する心配は有りません。
 *	  また、隠れコンテキストがfork命令で新たなコンテキストを開始した後に、そのコンテキストで明示的にhide命令を書く必要も有りません。
 *	  今回の変更によって、変更前よりも直観的な動作になったと思うので、変更後の動作について特に意識する必要は無いと思います。
 *	- 尚、今回の変更はあくまでもスクリプト内でfork命令によって開始したコンテキストに関するものであり、DirSeqCh_SendTrig()等によってプログラムから明示的に開始したコンテキストが初期状態で'通常のコンテキスト'(=隠れコンテキストではない)である点については、これまでと変わりません。
 *	* Tue Jun 20 21:14:16 JST 2017 Naoyuki Sawa
 *	- join命令を追加しました。
 *	  自分以外の全てのコンテキストが終了するまで待ちます。
 *	  詳細は、_DirSeqCtx_Exec1()内の、join命令を処理している箇所のコメントを参照して下さい。
 *	- join命令を追加した理由は、主コンテキストが複数の副コンテキストを起動し、全ての副コンテキストの終了を待って(=同期して)次へ進むような処理を、記述し易くしたいと思ったからです。(尚、当モジュールの機能自体には、主コンテキスト・副コンテキストという概念は有りません。説明上の、コンテキストの役割としての主・副と理解して下さい。)
 *	  □スクリプトの例
 *	  │	Test1
 *	  │	  fork				//一つ目の副コンテキストを起動する。
 *	  │	    repeat 3
 *	  │	      TapLogCh_Play_(Se1,Foo)
 *	  │	      wait SEC(1.0)
 *	  │	    end
 *	  │	  end
 *	  │	  fork				//二つ目の副コンテキストを起動する。
 *	  │	    repeat 5
 *	  │	      TapLogCh_Play_(Se2,Bar)
 *	  │	      wait SEC(0.7)
 *	  │	    end
 *	  │	  end
 *	  │	  fork				//三つ目の副コンテキストを起動する。
 *	  │	    repeat 7
 *	  │	      TapLogCh_Play_(Se3,Baz)
 *	  │	      wait SEC(0.3)
 *	  │	    end
 *	  │	  end
 *	  │	  join				//全ての副コンテキストが終了するのを待つ。
 *	  │	  TapLogCh_Play_(Se1,Qux)
 *	  │	end
 *	  上記の例の場合、一つ目の副コンテキストは3.0秒、二つ目の副コンテキストは3.5秒、三つ目の副コンテキストは2.1秒掛かりますから、
 *	  │	  ～前略～
 *	  │	  wait SEC(3.5)
 *	  │	  TapLogCh_Play_(Se1,Qux)
 *	  │	end
 *	  と書いても同じではあるのですが、主コンテキストが明示的にwaitで待つよりも、joinで待つ方がスクリプトが読み易いという利点が有ります。
 *	  また、もし、副コンテキストのタイミングを調整して、別の副コンテキストの時間が一番長くなった場合でも、主コンテキストの処理を書き換えなくても良いという利点が有ります。
 *	* Sun Sep 03 23:36:18 JST 2017 Naoyuki Sawa
 *	- if命令を追加しました。
 *	  □スクリプトの例
 *	  │	Test1
 *	  │	  if Cond1
 *	  │	    act  条件'Cond1'が成立した場合だけ実行する演出
 *	  │	    wait SEC(1.0)
 *	  │	  end
 *	  │	  act    常に実行する演出
 *	  │	end
 *	* Sun Oct 29 23:41:29 JST 2017 Naoyuki Sawa
 *	- ロックバッファのロジックにバグが見つかりましたが、修正していません。
 *	  詳細は、clipsnrs.cの同日のコメントを参照して下さい。
 *	* Tue Apr 10 23:59:59 JST 2018 Naoyuki Sawa
 *	- 雛形プロジェクトでの演出シーケンサ使用を廃止し、シナリオシーケンサへ移行しました。
 *	  演出シーケンサのモジュール(clipdirs.h,clipdirs.c)は、ソースを参考にする時のためにビルドに含めて残してあります。
 *	  ただし、雛形プロジェクト(test.pce,test.win,test.x64)からは完全に削除したので、今後、使用する事は無いと思います。
 *	  もしまた、演出シーケンサを使用する必要が生じた場合は、前回までのアーカイブ内の雛形プロジェクトを参照して下さい。
 */
#include "clip.h"
/****************************************************************************
 *	ローカル関数宣言
 ****************************************************************************/
static ST_DirSeqCh* _DirSeq_GetDirSeqCh(ST_DirSeq* pDirSeq, int iCh);
static ST_DirSeqCtx* _DirSeq_GetDirSeqCtx(ST_DirSeq* pDirSeq, int iCtx);
static void _DirSeq_SetActBuf(ST_DirSeq* pDirSeq, void (*fn)(), int arg1, intptr_t arg2);
//--- _DirSeq_SetActBuf()経由で呼び出される事が有る関数:これらの関数は第二引数を(intptr_t)と同じサイズに揃える必要が有ります。 ---
static void _DirSeq_Exec(ST_DirSeq* pDirSeq);
static void _DirSeq_ClrTbl(ST_DirSeq* pDirSeq);
static void _DirSeqCh_SetTbl(ST_DirSeq* pDirSeq, int iCh, const uint8_t* pDirTbl/*NULL=クリア*/);
static void _DirSeq_SendTrig(ST_DirSeq* pDirSeq, int iTrig);
static void _DirSeqCh_SendTrig(ST_DirSeq* pDirSeq, int iCh, intptr_t iTrig);
static void _DirSeq_Unlock(ST_DirSeq* pDirSeq);
static void _DirSeqCh_Unlock(ST_DirSeq* pDirSeq, int iCh);
static void _DirSeq_Stop(ST_DirSeq* pDirSeq);
static void _DirSeqCh_Stop(ST_DirSeq* pDirSeq, int iCh);
//--- _DirSeq_SetActBuf()経由で呼び出される事は無い関数:これらの関数は第二引数を(intptr_t)と同じサイズに揃える必要は有りません。 ---
static void _DirSeq_AddActBuf(ST_DirSeq* pDirSeq, int iCh, int ActNo);
static void _DirSeq_GetActBuf(ST_DirSeq* pDirSeq, int bOverFlow);
static void _DirSeqCh_Start(ST_DirSeq* pDirSeq, int iCh, const uint8_t* p);
static void _DirSeqCh_Exec1(ST_DirSeq* pDirSeq, int iCh, int bStop);
static void _DirSeqCtx_Exec1(ST_DirSeq* pDirSeq, int iCh, int iCtx, int bStop);
static void _DirSeqCh_Exec2(ST_DirSeq* pDirSeq, int iCh, int bUnlock);
/****************************************************************************
 *	
 ****************************************************************************/
//{{2017/09/03変更:if命令を追加しました。
//ST_DirSeq* DirSeq_New(void (*fnAct)(struct _ST_DirSeq* pDirSeq, int iCh, int ActNo, int bOverFlow), int nCh, int nCtx) {
//↓2017/09/03変更:if命令を追加しました。
ST_DirSeq* DirSeq_New(void (*fnAct)(struct _ST_DirSeq* pDirSeq, int iCh, int ActNo, int bOverFlow),
                      int  (*fnCnd)(struct _ST_DirSeq* pDirSeq, int iCh, int CndNo), int nCh, int nCtx) {
//}}2017/09/03変更:if命令を追加しました。
	ST_DirSeq* pDirSeq;
	if((unsigned)nCh > (unsigned)nCtx) { DIE(); }	//チャネル数がコンテキスト数よりも多ければエラー停止します。全てのチャネルを同時実行するには少なくともチャネル数以上のコンテキスト数が必要であり、チャネル数がコンテキスト数よりも多いのは初期化パラメータエラーと思われるからです。ただし厳密には、管理のためだけにチャネル数を多めに用意しておき実際には全てのチャネルを同時実行しないような使い方(TAPのような使い方)をする場合はコンテキストバッファのメモリを節約するためにチャネル数がコンテキスト数よりも多くても構わないのですが、一般的にはチャネル数がコンテキスト数よりも多いのは初期化パラメータエラーと見なす方が安全だと思うのでエラー停止する事にしました。もし本当に左記のような使い方をするケースが生じた場合は、この行の検査を削除して下さい。
	//構造体のメモリを確保する。
	pDirSeq = malloc(sizeof(ST_DirSeq)			//ST_DirSeq
	              + (sizeof(ST_DirSeqCh)  * nCh)		//ST_DirSeq.TBL_DirSeqCh[nCh]
	              + (sizeof(ST_DirSeqCtx) * nCtx));		//ST_DirSeq.TBL_DirSeqCtx[nCtx]
	if(!pDirSeq) { DIE(); }	//メモリ不足
	//構造体を初期化する。
//{{2017/09/03変更:if命令を追加しました。
//	DirSeq_Init(pDirSeq, fnAct, nCh, nCtx);
//↓2017/09/03変更:if命令を追加しました。
	DirSeq_Init(pDirSeq, fnAct, fnCnd, nCh, nCtx);
//}}2017/09/03変更:if命令を追加しました。
	return pDirSeq;
}
/*--------------------------------------------------------------------------*/
//{{2017/09/03変更:if命令を追加しました。
//void DirSeq_Init(ST_DirSeq* pDirSeq, void (*fnAct)(struct _ST_DirSeq* pDirSeq, int iCh, int ActNo, int bOverFlow), int nCh, int nCtx) {
//↓2017/09/03変更:if命令を追加しました。
void DirSeq_Init(ST_DirSeq* pDirSeq, void (*fnAct)(struct _ST_DirSeq* pDirSeq, int iCh, int ActNo, int bOverFlow),
                                     int  (*fnCnd)(struct _ST_DirSeq* pDirSeq, int iCh, int CndNo), int nCh, int nCtx) {
//}}2017/09/03変更:if命令を追加しました。
	int iCh, iCtx;
	if((unsigned)nCh > (unsigned)nCtx) { DIE(); }	//チャネル数がコンテキスト数よりも多ければエラー停止します。全てのチャネルを同時実行するには少なくともチャネル数以上のコンテキスト数が必要であり、チャネル数がコンテキスト数よりも多いのは初期化パラメータエラーと思われるからです。ただし厳密には、管理のためだけにチャネル数を多めに用意しておき実際には全てのチャネルを同時実行しないような使い方(TAPのような使い方)をする場合はコンテキストバッファのメモリを節約するためにチャネル数がコンテキスト数よりも多くても構わないのですが、一般的にはチャネル数がコンテキスト数よりも多いのは初期化パラメータエラーと見なす方が安全だと思うのでエラー停止する事にしました。もし本当に左記のような使い方をするケースが生じた場合は、この行の検査を削除して下さい。
	//構造体をクリアする。
	memset(pDirSeq, 0, sizeof(ST_DirSeq)			//ST_DirSeq
	                + (sizeof(ST_DirSeqCh)  * nCh)		//ST_DirSeq.TBL_DirSeqCh[nCh]
	                + (sizeof(ST_DirSeqCtx) * nCtx));	//ST_DirSeq.TBL_DirSeqCtx[nCtx]
	//演出実行関数を格納する。
	pDirSeq->fnAct = fnAct;
//{{2017/09/03追加:if命令を追加しました。
	//条件判断関数を格納する。
	pDirSeq->fnCnd = fnCnd;
//}}2017/09/03追加:if命令を追加しました。
	//演出シーケンサチャネル数を格納する。
	pDirSeq->nCh = nCh;
	//コンテキストバッファサイズを格納する。
	pDirSeq->nCtx = nCtx;
	//演出シーケンサチャネルを初期化する。
	for(iCh = 0; iCh < pDirSeq->nCh; iCh++) {
		ST_DirSeqCh* pDirSeqCh = _DirSeq_GetDirSeqCh(pDirSeq, iCh);
		pDirSeqCh->iCtxHead = 255/*終端*/;
	}
	//コンテキストバッファを初期化する。
	for(iCtx = 0; iCtx < pDirSeq->nCtx; iCtx++) {
		ST_DirSeqCtx* pDirSeqCtx = _DirSeq_GetDirSeqCtx(pDirSeq, iCtx);
		pDirSeqCtx->iCtxNext = 255/*終端*/;
	}
}
/*--------------------------------------------------------------------------*/
void DirSeq_Free(ST_DirSeq* pDirSeq) {
	//構造体のメモリを開放する。
	free(pDirSeq);
}
/****************************************************************************
 *	
 ****************************************************************************/
static ST_DirSeqCh* _DirSeq_GetDirSeqCh(ST_DirSeq* pDirSeq, int iCh) {
	//演出シーケンサチャネルを取得する。
	if((unsigned)iCh >= (unsigned)pDirSeq->nCh) { DIE(); }	//バグ
	return (ST_DirSeqCh*)(pDirSeq + 1) + iCh;
}
/****************************************************************************
 *	
 ****************************************************************************/
static ST_DirSeqCtx* _DirSeq_GetDirSeqCtx(ST_DirSeq* pDirSeq, int iCtx) {
	//演出シーケンサコンテキストを取得する。
	if((unsigned)iCtx >= (unsigned)pDirSeq->nCtx) { DIE(); }	//バグ
	return (ST_DirSeqCtx*)((ST_DirSeqCh*)(pDirSeq + 1) + pDirSeq->nCh) + iCtx;
}
/****************************************************************************
 *	
 ****************************************************************************/
//コンテキストを確保して、指定されたチャネルのコンテキストチェインの先頭に追加します。
//呼び出し側にて確保されたコンテキストのインデクスを取得するには、pDirSeqCh->iCtxHeadを参照して下さい。
//当関数が、確保したコンテキストを返さないようにした理由は、呼び出し元の処理によって、ポインタが必要な場合(_DirSeqCh_Start())と、インデクスが必要な場合(_DirSeqCtx_Exec1()のforkの処理)が有るからです。
//pDirSeqCh->iCtxHeadから辿っていけば必要な情報は取得出来るので、当関数はどちらも返さない事にしました。
static void _DirSeq_AllocDirSeqCtx(ST_DirSeq* pDirSeq, int iCh) {
	int iCtx;
	//空きコンテキストを検索する。
	for(iCtx = 0; iCtx < pDirSeq->nCtx; iCtx++) {
		ST_DirSeqCtx* pDirSeqCtx = _DirSeq_GetDirSeqCtx(pDirSeq, iCtx);
		//空きコンテキストが見つかったら…
		if(!pDirSeqCtx->pDirSeq) {
			ST_DirSeqCh* pDirSeqCh = _DirSeq_GetDirSeqCh(pDirSeq, iCh);
			//このチャネルのコンテキストチェインの先頭に、確保したコンテキストを追加する。
			if(pDirSeqCtx->WaitCnt  ||				//_DirSeqCtx_Exec1()が_DirSeq_FreeDirSeqCtx()を呼び出す時に、既に(WaitCnt==0)になっているはずです。もしここでエラー停止した場合、コンテキストバッファが不正に破壊された可能性が有ります。
			   pDirSeqCtx->StackLev ||				//_DirSeqCtx_Exec1()が_DirSeq_FreeDirSeqCtx()を呼び出す時に、既に(StackLev==0)になっているはずです。もしここでエラー停止した場合、コンテキストバッファが不正に破壊された可能性が有ります。
			   pDirSeqCtx->bHide    ||				//_DirSeq_FreeDirSeqCtx()がコンテキストを解放する処理の中で、(bHide=0)を格納しているはずです。もしここでエラー停止した場合、コンテキストバッファが不正に破壊された可能性が有ります。
			  (pDirSeqCtx->iCtxNext != 255/*終端*/)) { DIE(); }	//_DirSeq_FreeDirSeqCtx()がコンテキストを解放する処理の中で、(iCtxNext=255)を格納しているはずです。もしここでエラー停止した場合、コンテキストバッファが不正に破壊された可能性が有ります。
			   pDirSeqCtx->iCtxNext = pDirSeqCh->iCtxHead;
			                          pDirSeqCh->iCtxHead = iCtx;
			return;	//ここまで
		}
	}
	DIE();	//空きコンテキストが見つかりませんでした。コンテキストバッファサイズを増やして下さい。
}
/****************************************************************************
 *	
 ****************************************************************************/
static void _DirSeq_FreeDirSeqCtx(ST_DirSeq* pDirSeq, int iCh, int iCtx) {
	ST_DirSeqCh* pDirSeqCh = _DirSeq_GetDirSeqCh(pDirSeq, iCh);
	ST_DirSeqCtx* pDirSeqCtx = _DirSeq_GetDirSeqCtx(pDirSeq, iCtx);
	//このチャネルのコンテキストチェインから、このコンテキストを取り外す。
	if(pDirSeqCtx->WaitCnt  ||		//_DirSeqCtx_Exec1()が_DirSeq_FreeDirSeqCtx()を呼び出す時に、既に(WaitCnt==0)になっているはずです。もしここでエラー停止した場合、コンテキストバッファが不正に破壊された可能性が有ります。
	   pDirSeqCtx->StackLev ||		//_DirSeqCtx_Exec1()が_DirSeq_FreeDirSeqCtx()を呼び出す時に、既に(StackLev==0)になっているはずです。もしここでエラー停止した場合、コンテキストバッファが不正に破壊された可能性が有ります。
	  !pDirSeqCtx->pDirSeq) { DIE(); }	//_DirSeqCtx_Exec1()が_DirSeq_FreeDirSeqCtx()を呼び出す時は、まだ(pDirSeq!=NULL)のままであるはずです。もしここでエラー停止した場合、_DirSeqCtx_Exec1()の処理にバグが有る可能性があります。
	//取り外すコンテキストが、コンテキストチェインの先頭ならば…
	if(pDirSeqCh->iCtxHead == iCtx) {
	   pDirSeqCh->iCtxHead = pDirSeqCtx->iCtxNext;
	//取り外すコンテキストが、コンテキストチェインの先頭でなければ…
	} else {
		int iCtxPrev = pDirSeqCh->iCtxHead;
		for(;;) {
			ST_DirSeqCtx* pCtxPrev = _DirSeq_GetDirSeqCtx(pDirSeq, iCtxPrev);	//(iCtxPrev==255/*終端*/)になったら、_DirSeq_GetDirSeqCtx()の中でエラー停止します。指定されたコンテキストが、このチャネルのコンテキストチェインの中に見つからないバグです。
			if(pCtxPrev->iCtxNext == iCtx) {
			   pCtxPrev->iCtxNext = pDirSeqCtx->iCtxNext;
			   break;
			}
			iCtxPrev = pCtxPrev->iCtxNext;
		}
	}
	//取り外したコンテキストを、空きコンテキストとする。
	pDirSeqCtx->pDirSeq = NULL/*空き*/;
	pDirSeqCtx->iCtxNext = 255/*終端*/;	//(pDirSeq==NULL)ならば、(iCtxNext==255)とします。	処理上必須ではありませんが、メモリ破壊やバグ検出のためです。
	pDirSeqCtx->bHide = 0;			//(pDirSeq==NULL)ならば、(bHide==0)とします。		処理上必須ではありませんが、メモリ破壊やバグ検出のためです。
}
/****************************************************************************
 *	
 ****************************************************************************/
static void _DirSeq_SetActBuf(ST_DirSeq* pDirSeq, void (*fn)(), int arg1, intptr_t arg2) {
	uint32_t ActBuf[DirSeq_ActBufSize];	//_DirSeq_SetActBuf()にてスタック上に確保したバッファのポインタをバッファが有効な期間中のみST_DirSeq.pActBufに設定する。充分なサイズ,かつ,スタックが溢れないようにDirSeq_ActBufSizeを調整せよ。
	if(pDirSeq->pActBuf) { DIE(); }		//バグ
	//演出番号バッファを設定する。
	pDirSeq->pActBuf = ActBuf;
	//引数で指定された関数を実行する。
	(*(void (*)(ST_DirSeq*, int, intptr_t))fn)(pDirSeq, arg1, arg2);	//この場合は「(*fn)(pDirSeq, arg1, arg2)」でも同じ結果になるはずですが、第二引数が(intptr_t)型である事を強調するために、関数型を明示的にキャストする事にしました。
	//演出番号を全て送出する。
	while(pDirSeq->ActBufCnt) {
		_DirSeq_GetActBuf(pDirSeq, 0/*bOverFlow*/);	//通常の送出であることを示す。
	}
	//演出番号バッファをクリアする。
	pDirSeq->pActBuf = NULL;
}
/****************************************************************************
 *	
 ****************************************************************************/
static void _DirSeq_AddActBuf(ST_DirSeq* pDirSeq, int iCh, int ActNo) {
	int i;
	if(!pDirSeq->pActBuf) { DIE(); }	//バグ
	//演出番号バッファに空きが無ければ…
	if(pDirSeq->ActBufCnt >= DirSeq_ActBufSize) {
		//演出番号を一個送出して空きを確保する。
		_DirSeq_GetActBuf(pDirSeq, 1/*bOverFlow*/);	//オーバーフロー時の送出であることを示す。
		if(pDirSeq->ActBufCnt >= DirSeq_ActBufSize) { DIE(); }	//バグ
	}
	//演出番号を演出番号バッファに追加する。
	i = pDirSeq->ActBufCnt + pDirSeq->ActBufPos;
	if(i >= DirSeq_ActBufSize) { i -= DirSeq_ActBufSize; }
	pDirSeq->pActBuf[i] = ActNo | (iCh << 24);	//[31:24]=演出シーケンサチャネル番号,[23:0]=演出番号
	pDirSeq->ActBufCnt++;
}
/****************************************************************************
 *	
 ****************************************************************************/
static void _DirSeq_GetActBuf(ST_DirSeq* pDirSeq, int bOverFlow) {
	int iCh, ActNo;
	if(!pDirSeq->pActBuf ||
	   !pDirSeq->ActBufCnt) { DIE(); }	//バグ
	//演出番号を一個送出する。
	ActNo = pDirSeq->pActBuf[pDirSeq->ActBufPos];
	iCh   = (uint8_t )(ActNo >> 24);	//[31:24]=演出シーケンサチャネル番号
	ActNo = (uint32_t)(ActNo <<  8) >> 8;	//[23: 0]=演出番号
	pDirSeq->ActBufCnt--;
	if(++pDirSeq->ActBufPos >= DirSeq_ActBufSize) { pDirSeq->ActBufPos = 0; }
	(*pDirSeq->fnAct)(pDirSeq, iCh, ActNo, bOverFlow);
}
/****************************************************************************
 *	
 ****************************************************************************/
static void _DirSeqCh_Start(ST_DirSeq* pDirSeq, int iCh, const uint8_t* p) {
	ST_DirSeqCh* pDirSeqCh = _DirSeq_GetDirSeqCh(pDirSeq, iCh);
	ST_DirSeqCtx* pDirSeqCtx;
	int c1, c2;
	//実行中の演出シーケンスが有れば停止する。
	// - 確保⇒停止の順でなく、停止⇒確保の順とする事に注意して下さい。
	//   停止によって解放したコンテキストも、確保の対象とするためです。
	_DirSeqCh_Stop(pDirSeq, iCh);
	//相対位置とロック時間を取得する。
	c1  =         *p++;
	c1 |=         *p++ <<  8;
	c1 |= (int8_t)*p++ << 16;	//符号拡張、忘れずに!!
	c2  =         *p++;
	c2 |=         *p++ <<  8;
	//コンテキストを確保する。
	_DirSeq_AllocDirSeqCtx(pDirSeq, iCh);
	pDirSeqCtx = _DirSeq_GetDirSeqCtx(pDirSeq, pDirSeqCh->iCtxHead);
	//命令位置とロック時間を設定する。
	pDirSeqCtx->pDirSeq = p + c1;
	pDirSeqCh->LockCnt  =     c2;
	//演出シーケンスの開始と同時に、演出シーケンスを1フレーム実行する。
	// - アプリケーションが演出シーケンスの開始即停止を行った場合でも、'フレーム#0'の命令は実行される方が自然だと思うからです。
	//   そうでなければこの処理は削除しても構いませんが、これまでこの方法で上手く行った実績があるので、この方が良いと思います。
	_DirSeqCh_Exec1(pDirSeq, iCh, 0/*bStop*/);
}
/****************************************************************************
 *	
 ****************************************************************************/
void DirSeq_Exec(ST_DirSeq* pDirSeq) {
	if(pDirSeq->pActBuf) {
		_DirSeq_Exec(pDirSeq);
	} else {
		_DirSeq_SetActBuf(pDirSeq, _DirSeq_Exec, 0/*dummy*/, 0/*dummy*/);
	}
}
/*--------------------------------------------------------------------------*/
static void _DirSeq_Exec(ST_DirSeq* pDirSeq) {
	int iCh;
	for(iCh = 0; iCh < pDirSeq->nCh; iCh++) {
		//演出シーケンス処理を行う。
		_DirSeqCh_Exec1(pDirSeq, iCh, 0/*bStop*/);
		//ロックバッファ処理を行う。
		_DirSeqCh_Exec2(pDirSeq, iCh, 0/*bUnlock*/);
	}
}
/*--------------------------------------------------------------------------*/
static void _DirSeqCh_Exec1(ST_DirSeq* pDirSeq, int iCh, int bStop) {
	ST_DirSeqCh* pDirSeqCh = _DirSeq_GetDirSeqCh(pDirSeq, iCh);
	int iCtx = pDirSeqCh->iCtxHead;
	//このチャネルのコンテキストチェインの、各コンテキストについて…
	while(iCtx != 255/*終端*/) {
		ST_DirSeqCtx* pDirSeqCtx = _DirSeq_GetDirSeqCtx(pDirSeq, iCtx);
		//コンテキストを実行する。
		int iCtxNext = pDirSeqCtx->iCtxNext;	//────┐
		_DirSeqCtx_Exec1(pDirSeq, iCh, iCtx, bStop);	//├_DirSeqCtx_Exec1()の中で(iCtxNext==255)になる可能性が有るので、_DirSeqCtx_Exec1()を呼び出す前にiCtxNextを取り出しておく。
		iCtx = iCtxNext;	//←───────────┘
	}
	if(bStop && (pDirSeqCh->iCtxHead != 255/*終端*/)) { DIE(); }	//停止時の処理ならば、コンテキストチェインが空になったはずです。もしここでエラー停止したら、当モジュールのバグです。
}
/*--------------------------------------------------------------------------*/
static void _DirSeqCtx_Exec1(ST_DirSeq* pDirSeq, int iCh, int iCtx, int bStop) {
	ST_DirSeqCh* pDirSeqCh = _DirSeq_GetDirSeqCh(pDirSeq, iCh);
	ST_DirSeqCtx* pDirSeqCtx = _DirSeq_GetDirSeqCtx(pDirSeq, iCtx);
//{{2017/09/03変更:if命令を追加しました。
//	int c1, c2;
//↓2017/09/03変更:if命令を追加しました。
	int c1, c2, c3;
//}}2017/09/03変更:if命令を追加しました。
	if(!pDirSeqCtx->pDirSeq) { DIE(); }	//既に(pDirSeq==NULL)であるコンテキストに対して、当関数が呼び出される事は無いはずです。もしここでエラー停止したら、当モジュールのバグです。
	for(;;) {
		//待ち時間が残っていたら…
		if(pDirSeqCtx->WaitCnt) {
			//停止時の処理でなければ…
			if(!bStop) {
				//次回のために待ち時間を減らす。
				pDirSeqCtx->WaitCnt--;
				return;	//ここまで
			//停止時の処理ならば…
			} else {
				//待ち時間をクリアする。
				pDirSeqCtx->WaitCnt = 0;
			}
		}
		//1バイト目を取得する。
		c1 = *pDirSeqCtx->pDirSeq++;
		switch(c1) {
		default:DIE();
		case DirSeq_Inst_end:		//end
			//スタックが有れば…
			if(pDirSeqCtx->StackLev) {
				//スタックを減らす。
				pDirSeqCtx->StackLev--;
				//Repeatスタックならば…
				if(pDirSeqCtx->TBL_StackRep[pDirSeqCtx->StackLev]) {
					//停止時の処理でなければ…
					if(!bStop) {
						//繰り返し回数が無限(255)か,又は,繰り返し回数を1減らしてまだ残っていたら…
						if((  pDirSeqCtx->TBL_StackRep[pDirSeqCtx->StackLev] == 255/*無限*/) ||
						   (--pDirSeqCtx->TBL_StackRep[pDirSeqCtx->StackLev] != 0)) {
							//命令位置を設定する。
							pDirSeqCtx->pDirSeq = pDirSeqCtx->TBL_StackRet[pDirSeqCtx->StackLev];
							//スタックを増やす。
							pDirSeqCtx->StackLev++;
						}
					}
				//Callスタックならば…
				} else {
					//命令位置を設定する。
					pDirSeqCtx->pDirSeq = pDirSeqCtx->TBL_StackRet[pDirSeqCtx->StackLev];
				}
			//スタックが無ければ…
			} else {
				//コンテキストを解放する。
				_DirSeq_FreeDirSeqCtx(pDirSeq, iCh, iCtx);	//この中で、(pDirSeq==NULL)になります。
				return;	//ここまで
			}
			break;
		case DirSeq_Inst_join:		//join
			//停止時の処理でなければ…
			if(!bStop) {
				//自分以外の全てのコンテキストが終了するまで待つ。
				// - joinは、隠れコンテキストフラグ(bHide)を考慮しません。
				//   通常コンテキスト(bHide==0)も隠れコンテキスト(bHide==1)も含めて、自分以外の全てのコンテキストが終了するまで待ちます。
				// - 別の案として、自分以外の通常コンテキスト(bHide==0)だけを待って、自分以外の隠れコンテキスト(bHide==1)が残っていても待たないという仕様も検討したのですが、その案は採用しない事にしました。
				//   別の案を採用しなかった理由は、join命令が自分以外の隠れコンテキストを待つのが適切かどうかについて、必然的な理由が見出だせなかったからです。
				//   また、もし、join命令が隠れコンテキストを考慮するようにすると、join命令を実行しているコンテキスト自身が隠れコンテキストだった場合に、特例とするかどうかも考慮が必要になるかも知れません。
				//   上記のように、join命令が隠れコンテキストを考慮すると、直観的でない問題が生じるので、隠れコンテキストフラグを考慮せずに自分意外の全てのコンテキストを待つ方が、単純で良いと判断しました。
				if((pDirSeqCh->iCtxHead != iCtx) ||		//自分が、このチャネルのコンテキストチェインの、最初のコンテキストでないか、又は、
				   (pDirSeqCtx->iCtxNext != 255/*終端*/)) {	//自分が、このチャネルのコンテキストチェインの、最後のコンテキストでなければ、自分以外のコンテキストが残っていると見なせる。
					pDirSeqCtx->pDirSeq--;			//命令位置を1バイト戻して、次回もこの'join'を処理するようにする。
					return;	//ここまで
				}
			}
			break;
		case DirSeq_Inst_hide:		//hide
			//隠れコンテキストフラグをセットする。
			// - 既に隠れコンテキストフラグがセットされていても、エラーにはしない事にしました。
			//   一つのコンテキストの中でhideを複数回実行しても、二回目以降のhideがダミー処理となるだけで、実行時エラーにはなりません。
			//   実際の所一つのコンテキストの中で意図的にhideを複数回実行する事はほとんど無いと思いますが、稀なケースとしてcall命令の呼び出し先でhideを実行するような場合に、呼び出し元が既に隠れコンテキストだとエラーになると不便だと思うので、一つのコンテキストの中でhideを複数回実行してもエラーにはしない事にしました。
			pDirSeqCtx->bHide = 1;
			break;
		case DirSeq_Inst_repeat:	//repeat
			//繰り返し回数を取得する。
			c2 = *pDirSeqCtx->pDirSeq++;
			if(!c2) { DIE(); }	//データバグ
			//繰り返し回数と命令位置を格納する。
			if(pDirSeqCtx->StackLev >= DirSeqCtx_StackSize) { DIE(); }	//スタック溢れ
			pDirSeqCtx->TBL_StackRep[pDirSeqCtx->StackLev] = c2;
			pDirSeqCtx->TBL_StackRet[pDirSeqCtx->StackLev] = pDirSeqCtx->pDirSeq;
			//スタックを増やす。
			pDirSeqCtx->StackLev++;
			break;
		case DirSeq_Inst_wait:		//wait
			//待ち時間を取得する。
			c2  = *pDirSeqCtx->pDirSeq++;
			c2 |= *pDirSeqCtx->pDirSeq++ << 8;
			if(!c2) { DIE(); }	//データバグ
			//待ち時間を格納する。
			pDirSeqCtx->WaitCnt = c2;
			break;
		case DirSeq_Inst_call:		//call
			//相対位置を取得する。
			c2  =         *pDirSeqCtx->pDirSeq++;
			c2 |=         *pDirSeqCtx->pDirSeq++ <<  8;
			c2 |= (int8_t)*pDirSeqCtx->pDirSeq++ << 16;	//符号拡張、忘れずに!!
			//繰り返し回数(0=Callスタック)と命令位置を格納する。
			if(pDirSeqCtx->StackLev >= DirSeqCtx_StackSize) { DIE(); }	//スタック溢れ
			pDirSeqCtx->TBL_StackRep[pDirSeqCtx->StackLev] = 0;
			pDirSeqCtx->TBL_StackRet[pDirSeqCtx->StackLev] = pDirSeqCtx->pDirSeq;
			//スタックを増やす。
			pDirSeqCtx->StackLev++;
			//命令位置を設定する。
			pDirSeqCtx->pDirSeq += c2;
			break;
		case DirSeq_Inst_fork:		//fork
			//相対位置を取得する。
			c2  =         *pDirSeqCtx->pDirSeq++;
			c2 |=         *pDirSeqCtx->pDirSeq++ <<  8;
			c2 |= (int8_t)*pDirSeqCtx->pDirSeq++ << 16;	//符号拡張、忘れずに!!
			{
				ST_DirSeqCtx* pCtxFork;
				//コンテキストを確保する。
				_DirSeq_AllocDirSeqCtx(pDirSeq, iCh);
				pCtxFork = _DirSeq_GetDirSeqCtx(pDirSeq, pDirSeqCh->iCtxHead);
				//命令位置を設定する。
				pCtxFork->pDirSeq = pDirSeqCtx->pDirSeq;
				//元コンテキストの隠れコンテキストフラグを継承する。
				// - 通常のコンテキストがfork命令を実行して新たなコンテキストを開始すると、そのコンテキストは初期状態で通常のコンテキストとなります。
				//   隠れコンテキストがfork命令を実行して新たなコンテキストを開始すると、そのコンテキストは初期状態で既に隠れコンテキストとなります。
				//   そうした理由については、clipdirs.cの'Wed May 24 21:26:30 JST 2017 Naoyuki Sawa'のコメントを参照して下さい。
				pCtxFork->bHide = pDirSeqCtx->bHide;
				//フォークと同時に、演出シーケンスを1フレーム,又は,最後まで実行する。
				// - 以下の理由により、この処理は必須です。
				// - フォークしたコンテキストは、このチャネルのコンテキストチェインの先頭に追加されます。
				//   当関数は_DirSeqCh_Exec1()から呼び出されますが、今回の_DirSeqCh_Exec1()では走査位置を過ぎているので、追加したコンテキストが実行される事は有りません。
				// - 停止時の処理でない(bStop==0)場合、フォークしてブロックの1フレーム目の命令(有れば)は、フォークを行ったフレームで実行されるのが自然だと思います。
				//   従って、ここで明示的に、演出シーケンスを1フレーム実行する事にしました。
				//   行わなくても動作はしますが、もしそうするとフォークしたブロックの実行が1フレーム遅れる動作になってしまい、良くないと思います。
				// - 停止時の処理である(bStop!=0)場合、フォークしたコンテキストが終了するまで実行する必要が有ります。
				//   しかし、前述の通り、今回の_DirSeqCh_Exec1()では走査位置を過ぎているので、追加したコンテキストが実行されません。
				//   従って、ここで明示的に、演出シーケンスを最後まで実行する必要が有ります。
				//   もしここで実行しないと、停止したチャネルのコンテキストが残ってしまうバグになってしまい、_DirSeqCh_Exec1()にてアサートします。
				_DirSeqCtx_Exec1(pDirSeq, iCh, pDirSeqCh->iCtxHead, bStop);
			}
			//命令位置を設定する。
			pDirSeqCtx->pDirSeq += c2;
			break;
		case DirSeq_Inst_act:		//act
		case DirSeq_Inst_act_flush:	//act flush
			//演出番号を取得する。
			c2 =  *pDirSeqCtx->pDirSeq++;
			c2 |= *pDirSeqCtx->pDirSeq++ <<  8;
			c2 |= *pDirSeqCtx->pDirSeq++ << 16;
			//停止時の処理でないか,又は,停止時にも実行する命令(act flush)ならば…
			if(!bStop || (c1 == DirSeq_Inst_act_flush/*act flush*/)) {
				//演出番号を演出番号バッファに追加する。
				_DirSeq_AddActBuf(pDirSeq, iCh, c2);
			}
			break;
//{{2017/09/03追加:if命令を追加しました。
		case DirSeq_Inst_if:		//if
			//条件番号を取得する。
			c2 =  *pDirSeqCtx->pDirSeq++;
			c2 |= *pDirSeqCtx->pDirSeq++ <<  8;
			c2 |= *pDirSeqCtx->pDirSeq++ << 16;
			//相対位置を取得する。
			c3  =         *pDirSeqCtx->pDirSeq++;
			c3 |=         *pDirSeqCtx->pDirSeq++ <<  8;
			c3 |= (int8_t)*pDirSeqCtx->pDirSeq++ << 16;	//符号拡張、忘れずに!!
			//条件判断関数が0以外(TRUE)を返したら…
			if((*pDirSeq->fnCnd)(pDirSeq, iCh, c2)) {
				//繰り返し回数(0=Callスタック)と命令位置を格納する。
				if(pDirSeqCtx->StackLev >= DirSeqCtx_StackSize) { DIE(); }	//スタック溢れ
				pDirSeqCtx->TBL_StackRep[pDirSeqCtx->StackLev] = 0;
				pDirSeqCtx->TBL_StackRet[pDirSeqCtx->StackLev] = pDirSeqCtx->pDirSeq + c3;	//戻り位置=if～endブロックの次の命令位置
				//スタックを増やす。
				pDirSeqCtx->StackLev++;
				//命令位置は変更不要。
				// - if命令の次の命令位置をcall命令で呼び出したのと同じです。
				//   ただし、if～endブロックの最後のend命令から、if～endブロックの次の命令位置へ戻ります。
				//   要するに、if～endブロックの最後のend命令の戻り位置は、end命令の直後の命令となります。
				/** no job **/
			//条件判断関数が0(FALSE)を返したら…
			} else {
				//命令位置を設定する。
				pDirSeqCtx->pDirSeq += c3;							//命令位置=if～endブロックの次の命令位置
			}
			break;
//}}2017/09/03追加:if命令を追加しました。
		}
	}
}
/*--------------------------------------------------------------------------*/
static void _DirSeqCh_Exec2(ST_DirSeq* pDirSeq, int iCh, int bUnlock) {
	ST_DirSeqCh* pDirSeqCh = _DirSeq_GetDirSeqCh(pDirSeq, iCh);
	const uint8_t* p;
	//ロックバッファが空になるまで…
	while((p = pDirSeqCh->TBL_LockBuf[0])) {
		//ロック時間が残っていたら…
		if(pDirSeqCh->LockCnt) {
			//ロック解除時の処理でなければ…
			if(!bUnlock) { 
				//次回のためにロック時間を減らす。
				pDirSeqCh->LockCnt--;
				return;	//ここまで
			//ロック解除時の処理ならば…
			} else { 
				//ロック時間をクリアする。
				pDirSeqCh->LockCnt = 0;
			}
		}
		//ロックバッファの先頭要素を削除する。
//{{このmemmove()の使い方は全く正しいにもかかわらずVC++6.0では警告出るので一時的に警告を抑制して回避する。
#ifdef  _MSC_VER
#pragma warning(push)
#pragma warning(disable:4090)	//'function' : 'const' 修飾子は異なります。
#pragma warning(disable:4022)	//'memmove' : 1 番目の実引数のﾎﾟｲﾝﾀの型が、仮引数のﾎﾟｲﾝﾀの型と異なります。
#endif//_MSC_VER
		memmove(&pDirSeqCh->TBL_LockBuf[0],
		        &pDirSeqCh->TBL_LockBuf[1],
		 (sizeof pDirSeqCh->TBL_LockBuf[0]) * (DirSeqCh_LockBufSize - 1));
#ifdef  _MSC_VER
#pragma warning(pop)
#endif//_MSC_VER
//}}このmemmove()の使い方は全く正しいにもかかわらずVC++6.0では警告出るので一時的に警告を抑制して回避する。
		         pDirSeqCh->TBL_LockBuf[      (DirSeqCh_LockBufSize - 1)] = NULL;
		//演出シーケンスを開始する。
		_DirSeqCh_Start(pDirSeq, iCh, p);
	}
}
/****************************************************************************
 *	
 ****************************************************************************/
void DirSeq_ClrTbl(ST_DirSeq* pDirSeq) {
	if(pDirSeq->pActBuf) {
		_DirSeq_ClrTbl(pDirSeq);
	} else {
		_DirSeq_SetActBuf(pDirSeq, _DirSeq_ClrTbl, 0/*dummy*/, 0/*dummy*/);
	}
}
/*--------------------------------------------------------------------------*/
static void _DirSeq_ClrTbl(ST_DirSeq* pDirSeq) {
	int iCh;
	for(iCh = 0; iCh < pDirSeq->nCh; iCh++) {
		DirSeqCh_SetTbl(pDirSeq, iCh, NULL);
	}
}
/****************************************************************************
 *	
 ****************************************************************************/
void DirSeqCh_SetTbl(ST_DirSeq* pDirSeq, int iCh, const uint8_t* pDirTbl/*NULL=クリア*/) {
	if(pDirSeq->pActBuf) {
		_DirSeqCh_SetTbl(pDirSeq, iCh, pDirTbl);
	} else {
		_DirSeq_SetActBuf(pDirSeq, _DirSeqCh_SetTbl, iCh, (intptr_t)pDirTbl);
	}
}
/*--------------------------------------------------------------------------*/
static void _DirSeqCh_SetTbl(ST_DirSeq* pDirSeq, int iCh, const uint8_t* pDirTbl/*NULL=クリア*/) {
	ST_DirSeqCh* pDirSeqCh = _DirSeq_GetDirSeqCh(pDirSeq, iCh);
	//'clr'トリガを送信する。
	DirSeqCh_SendTrig(pDirSeq, iCh, DirTrg_clr);
	//演出テーブルを設定する。
	pDirSeqCh->pDirTbl = pDirTbl;
	//'set'トリガを送信する。
	DirSeqCh_SendTrig(pDirSeq, iCh, DirTrg_set);
}
/****************************************************************************
 *	
 ****************************************************************************/
void DirSeq_SendTrig(ST_DirSeq* pDirSeq, int iTrig) {
	if(pDirSeq->pActBuf) {
		_DirSeq_SendTrig(pDirSeq, iTrig);
	} else {
		_DirSeq_SetActBuf(pDirSeq, _DirSeq_SendTrig, iTrig, 0/*dummy*/);
	}
}
/*--------------------------------------------------------------------------*/
static void _DirSeq_SendTrig(ST_DirSeq* pDirSeq, int iTrig) {
	int iCh;
	for(iCh = 0; iCh < pDirSeq->nCh; iCh++) {
		DirSeqCh_SendTrig(pDirSeq, iCh, iTrig);
	}
}
/****************************************************************************
 *	
 ****************************************************************************/
void DirSeqCh_SendTrig(ST_DirSeq* pDirSeq, int iCh, int iTrig) {
	if(pDirSeq->pActBuf) {
		_DirSeqCh_SendTrig(pDirSeq, iCh, iTrig);
	} else {
		_DirSeq_SetActBuf(pDirSeq, _DirSeqCh_SendTrig, iCh, iTrig);
	}
}
/*--------------------------------------------------------------------------*/
static void _DirSeqCh_SendTrig(ST_DirSeq* pDirSeq, int iCh, intptr_t iTrig) {
	ST_DirSeqCh* pDirSeqCh = _DirSeq_GetDirSeqCh(pDirSeq, iCh);
	const uint8_t* p;
	int i, c1, c2;
	//演出テーブルを走査して…
	p = pDirSeqCh->pDirTbl;
	while(p) {
		//1バイト目を取得する。
		c1 = *p++;
		switch(c1) {
		default:DIE();
		case DirSeq_Inst_end:			//end
			p = NULL;
			break;
		case DirSeq_Inst_on_goto_lock:		//on
		case DirSeq_Inst_on_unlock_goto_lock:	//on unlock
			//トリガ番号を取得する。
			c2 = *p++;
			//トリガ番号が一致したら…
			if(c2 == iTrig) {
				//ロック解除する命令(on unlock)ならば…
				if(c1 == DirSeq_Inst_on_unlock_goto_lock/*on unlock*/) {
					//ロック解除する。
					DirSeqCh_Unlock(pDirSeq, iCh);
				}
				//ロックバッファに空きが無ければ…
				if(pDirSeqCh->TBL_LockBuf[(DirSeqCh_LockBufSize - 1)]) {
					//現在のロック時間を強制的にクリアする。
					pDirSeqCh->LockCnt = 0;
					//最低一個以上の演出シーケンスを開始し、空きを確保する。
					_DirSeqCh_Exec2(pDirSeq, iCh, 0/*bUnlock*/);
					if(pDirSeqCh->TBL_LockBuf[(DirSeqCh_LockBufSize - 1)]) { DIE(); }	//バグ
				}
				//ロックバッファが空でなければ…
				if(pDirSeqCh->TBL_LockBuf[0]) {
					//ロックバッファの最初の空き要素の位置を検索する。
					//上の処理で空きを確保済みなので、少なくとも最終要素が空きである。
					for(i = 0; i < DirSeqCh_LockBufSize - 1/*上記コメント参照*/; i++) {
						if(!pDirSeqCh->TBL_LockBuf[i]) { break; }
					}
					//ロックバッファの最初の空き要素の位置に演出シーケンスを格納する。
					pDirSeqCh->TBL_LockBuf[i] = p;
				//ロックバッファが空ならば…
				} else {
					//ロック時間が残っていたら…
					if(pDirSeqCh->LockCnt) {
						//ロックバッファの先頭に演出シーケンスを格納する。
						pDirSeqCh->TBL_LockBuf[0] = p;
					//ロック時間が残っていなければ…
					} else {
						//演出シーケンスを開始する。
						_DirSeqCh_Start(pDirSeq, iCh, p);
					}
				}
			}
			//相対位置とロック時間を読み飛ばす。
			p += (3/*相対位置*/ + 2/*ロック時間*/);
			break;
		}
	}
}
/****************************************************************************
 *	
 ****************************************************************************/
void DirSeq_Unlock(ST_DirSeq* pDirSeq) {
	if(pDirSeq->pActBuf) {
		_DirSeq_Unlock(pDirSeq);
	} else {
		_DirSeq_SetActBuf(pDirSeq, _DirSeq_Unlock, 0/*dummy*/, 0/*dummy*/);
	}
}
/*--------------------------------------------------------------------------*/
static void _DirSeq_Unlock(ST_DirSeq* pDirSeq) {
	int iCh;
	for(iCh = 0; iCh < pDirSeq->nCh; iCh++) {
		DirSeqCh_Unlock(pDirSeq, iCh);
	}
}
/****************************************************************************
 *	
 ****************************************************************************/
void DirSeqCh_Unlock(ST_DirSeq* pDirSeq, int iCh) {
	if(pDirSeq->pActBuf) {
		_DirSeqCh_Unlock(pDirSeq, iCh);
	} else {
		_DirSeq_SetActBuf(pDirSeq, _DirSeqCh_Unlock, iCh, 0/*dummy*/);
	}
}
/*--------------------------------------------------------------------------*/
static void _DirSeqCh_Unlock(ST_DirSeq* pDirSeq, int iCh) {
	_DirSeqCh_Exec2(pDirSeq, iCh, 1/*bUnlock*/);
}
/****************************************************************************
 *	
 ****************************************************************************/
void DirSeq_Stop(ST_DirSeq* pDirSeq) {
	if(pDirSeq->pActBuf) {
		_DirSeq_Stop(pDirSeq);
	} else {
		_DirSeq_SetActBuf(pDirSeq, _DirSeq_Stop, 0/*dummy*/, 0/*dummy*/);
	}
}
/*--------------------------------------------------------------------------*/
static void _DirSeq_Stop(ST_DirSeq* pDirSeq) {
	int iCh;
	for(iCh = 0; iCh < pDirSeq->nCh; iCh++) {
		DirSeqCh_Stop(pDirSeq, iCh);
	}
}
/****************************************************************************
 *	
 ****************************************************************************/
void DirSeqCh_Stop(ST_DirSeq* pDirSeq, int iCh) {
	if(pDirSeq->pActBuf) {
		_DirSeqCh_Stop(pDirSeq, iCh);
	} else {
		_DirSeq_SetActBuf(pDirSeq, _DirSeqCh_Stop, iCh, 0/*dummy*/);
	}
}
/*--------------------------------------------------------------------------*/
static void _DirSeqCh_Stop(ST_DirSeq* pDirSeq, int iCh) {
	_DirSeqCh_Exec1(pDirSeq, iCh, 1/*bStop*/);
}
/****************************************************************************
 *	
 ****************************************************************************/
//演出シーケンスが動作中であるチャネル数を返します。
int DirSeq_GetStat(ST_DirSeq* pDirSeq) {
	int iCh, nActive = 0;
	for(iCh = 0; iCh < pDirSeq->nCh; iCh++) {
		nActive += DirSeqCh_GetStat(pDirSeq, iCh);
	}
	return nActive;
}
/*--------------------------------------------------------------------------*/
//このチャネルが動作中ならば1、停止中ならば0を返します。
int DirSeqCh_GetStat(ST_DirSeq* pDirSeq, int iCh) {
	ST_DirSeqCh* pDirSeqCh = _DirSeq_GetDirSeqCh(pDirSeq, iCh);
	int iCtx = pDirSeqCh->iCtxHead;
	//このチャネルのコンテキストチェインの、各コンテキストについて…
	while(iCtx != 255/*終端*/) {
		ST_DirSeqCtx* pDirSeqCtx = _DirSeq_GetDirSeqCtx(pDirSeq, iCtx);
		//通常のコンテキストが一つでも有れば、このチャネルは動作中であると見なす。
		if(!pDirSeqCtx->bHide) { return 1; }
		iCtx = pDirSeqCtx->iCtxNext;
	}
	//コンテキストチェインが空か,又は,全て隠れコンテキストならば、このチャネルは停止中であると見なす。
	return 0;
}
/****************************************************************************
 *	dDirSeqC.exeが出力したバイナリ形式を扱うためのユーティリティ関数
 ****************************************************************************/
#ifdef  USE_DIRSEQ_BIN
void DirSeqCh_SetTblNo(ST_DirSeq* pDirSeq, int iCh, const void* pDirSeqBin, int iDirTblNo) {
	const int* const TBL_DirTblNo = pDirSeqBin;						//バイナリの先頭にアドレステーブルが有る。アドレスはデータ本体(BYTE配列)のインデクスである。
	const int nDirTblNo = TBL_DirTblNo[0];							//アドレステーブルの先頭[0]に要素数が入っている。要素数はそれ自身([0])も含む。
	const unsigned char* const TBL_DirSeq = (const unsigned char*)&TBL_DirTblNo[nDirTblNo];	//アドレステーブルの直後からデータ本体(BYTE配列)が開始する。
	if((iDirTblNo <= 0) || (iDirTblNo >= nDirTblNo)) { DIE(); }
	DirSeqCh_SetTbl(pDirSeq, iCh, &TBL_DirSeq[TBL_DirTblNo[iDirTblNo]]);
}
#endif//USE_DIRSEQ_BIN
