複数通貨ペアの警告音を重複しないように鳴らすには。

内容的に昨日の続きです。
通貨ペア毎に音が替えられるっっと喜んで以下のように書き換えるだけでは、実用上の不具合が生じます。

if(WarnigMode >0) PlaySound(StringSubstr(Symbol(),0,6)+".wav");

このままでは、「ユ,ポ,スイスえん」のように再生音が重なってしまい、本来は、「ユーロドル、ポンドオウジー、スイス円」の3ペアが鳴っているのに、最後のペアしか聞き取れない...という問題が起きます。特にTick更新のタイミングで毎回鳴らしていると、相場が動く時はどの通貨もそれなりに動くので非常に聞き辛くなってしまいます。


そんな時の対処法は、
・あるチャートのインジケータが音を鳴らしたら、直後2秒間は他のチャートは音を鳴らさない。
・Tick更新毎に鳴らさずに、15秒前後に1回程度鳴らすようにする。
とすれば、かなり改善されます。
(その為に、昨日アップしているwavファイルは全て2秒以下で鳴り終わるようにしてあります..


以下は、この改善を行った例です。
まず、PlaySound 関数を直接呼ぶのを止めて、 PlaySoundEX という自作の関数を呼ぶようにします。

if(WarnigMode >0) PlaySoundEX(StringSubstr(Symbol(),0,6)+".wav");


PlaySoundEX 関数のコードは以下のようにします。

void PlaySoundEX(string wavfile)
{
   static datetime atime = 0;
   datetime last = GlobalVariableGet("soundtime");//PlaySoundEXが最後に音を鳴らした時刻を取得。
   if(TimeLocal()-last <2) return;//2秒未満しか経っていない場合は鳴らさない。
   if(TimeLocal()-atime <10+MathRand()/32767.0*10) return;//このインジケータが、過去10〜20秒以内に鳴らしていたら、鳴らさない。
   GlobalVariableSet("soundtime",TimeLocal());//PlaySoundEXが音を鳴らした時刻をセット。
   PlaySound(wavfile);
   atime = TimeLocal();
}

処理内容はコメントに書いてある通りです。実用上はこれで十分だと思うので、PlaySound を多用している人は参考にしてみてください。(但し、チャートを大量に開いている人は、時間間隔を調整する必要があるかもしれません。)





以下は余談になりますが、実は、この簡易な排他制御には、2つ問題があります。
1つ目の問題は、これでも重複が避けられない場合があるコトです。

(1)チャートAのインジケータが、GlobalVariableGet で2秒経過を確認。
(2)チャートBのインジケータが、GlobalVariableGet で2秒経過を確認。
(3)チャートAのインジケータが、GlobalVariableSet で、再生時刻書き込み。
(4)チャートBのインジケータが、GlobalVariableSet で、再生時刻書き込み。
(5)チャートAのインジケータが、音を再生。
(6)チャートBのインジケータが、音を再生。(重複!!)

というような、(1)と(3)の間に(2)が行われるとNGです。
(専門用語で、アトミック性が無い..と言います。)
平常時は、(1)-(3)の間に掛かる時間は1ミリ秒以下..らしい..ので、無視して良い話です。



2つ目の問題は、音の鳴らせないペアが生じる可能性があるコトです。

(1)チャートAのインジケータが、2秒経過を確認後、音再生。
(2)チャートBのインジケータは、やむえず、再生見送り。
(3)チャートAのインジケータが、2秒経過を確認後、音再生。
(4)チャートBのインジケータは、やむえず、再生見送り。

この流れが繰り返されると、Bのインジケータはいつまでも音を鳴らすことが出来ません。
これを専門用語で、公平性が無い..と言います。(..専門用語でもなんでもないか^^;
PlaySoundEX の中では、MathRand() を使って(1)と(3)の時間間隔をランダムにすることで、不幸なループを起きにくくしています。


ただの音再生に厳密な排他制御は不要だと思うので適当に済ませていますが、本当にクリティカルな処理をする時の排他制御は、アトミック性や公平性に注意すると吉です。