目次


仕事中に口笛を吹いて、コンピューターにコマンドを実行させる

オープンソース・ソフトウェアとマイクを装備したラップトップを使って、特定の音声シーケンスを聞き取り、コマンドを実行する

Comments

コンピューター・ユーザーは長年の間、プロセッサー負荷の高い音声認識アプリケーションを実行してきました。このアプリケーションは、音声処理に基づいてコマンドを実行し、高度な構成を必要としていました。最近の処理能力とアルゴリズムの進歩によって、エラー・レートが低くユーザーに依存しない音声認識を実現できるようになり、単純なトーン・パターンに基づく認識システムの可能性が大きく広がってきています。

最近リリースされた sndpeek プログラムを使って高度な処理を行うことにより、単純な Perl プログラムを実行して容易にトーン・コードを生成することができます。ここでは、ユーザーがトーンの入力と検出の環境をカスタマイズできるように、Perl スクリプトを紹介します。

要件

ハードウェア

まず、サウンド入力を処理できるシステムが必要です。できれば内蔵マイクからの入力が望ましいのですが、不連続のトーン・イベントを生成できる音源であれば、どんなものでも構いません。例えば、コンピューターに向かって口笛を吹くことで、効果的に (キーボードとマウス以外の) 第 3 の入力チャネルを追加できますが、適切に構成すれば、ライン入力ジャックから MP3 プレーヤーの出力を入力して再生しても同じ結果を得ることができます。この記事のコードは、900-MHz のプロセッサーと 1 GB の RAM を搭載した IBM® ThinkPad® T42pで開発、テストされています。これよりももっと能力の低いシステムであっても、この記事で示すコードを容易に利用できるはずです。主にリソースを消費するのは sndpeek であり、このプログラムは非常に効率が高いためです。

ソフトウェア

マイクのハードウェアを利用するためには、適切に動作する音声処理ソフトウェア環境が必要です。音声に関する構成やトラブルシューティングはこの記事の範囲外ですが、このコードを Vector Linux Live CD でテストするとよいかもしれません。この CD の中には、さまざまな種類の音声ハードウェアを機能設定するために必要なドライバーとコンポーネントの大部分が含まれています。また sndpeek プログラムを、少なくとも部分的には動作するようにインストールしておく必要があります (3D 表示部分のコードは必要ありません)。

「参考文献」には、sndpeek の Web ページへのリンクがあります。そのページからコードをダウンロードすると、src/sndpeek/sndpeek.cpp というファイルの中に次のようなセクションがあります。

リスト 1. sndpeek を修正する
    fprintf( stdout, "%.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f ", 
         mfcc(0), mfcc(1), mfcc(2), mfcc(3), mfcc(4), mfcc(5), mfcc(6),
         mfcc(7), mfcc(8), mfcc(9), mfcc(10), mfcc(11), mfcc(12) );
    fprintf( stdout, "\n" );

出力を吐き出すことで、プログラムの出力が実際に各出力ウィンドウの最後に書かれていることを確認します。上記のコードを次のように変更します。

リスト 2. sndpeek をもう 1 度修正する
    fprintf( stdout, "%.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f ", 
         mfcc(0), mfcc(1), mfcc(2), mfcc(3), mfcc(4), mfcc(5), mfcc(6),
         mfcc(7), mfcc(8), mfcc(9), mfcc(10), mfcc(11), mfcc(12) );
    fprintf( stdout, "\n" );
    fflush(stdout);

今度はおなじみの ./configure; make; make install コマンドを実行し、Linux 上で sndpeek をビルドし、インストールします。Windows の場合、プログラムが正しくビルドされることを確認します。Windows にはプログラムをビルドするためのオプションが数多くありますが、それについてはこの記事では説明しません。もし cmdWhistle の実際の動作を見たいのであれば、Vector Linux Live CD を使うようにお勧めします。

次は、コマンド・ラインによるウィンドウ・コントロールのための xwit アプリケーションのインストールです。「参考文献」のリンクから xwit をダウンロードし、ソースコードを何も変更せずにインストールします。これで単純なトーンを作成する準備が整いました。

設定と構成の例

単純なトーン・シーケンスの作成

ソースコードをダウンロードし、cmdWhistle.pl スクリプトを見つけます。このスクリプトがメインの Perl プログラムです。このプログラムを使うとトーン・シーケンスを作成でき、また特定の音声シーケンスを聞き取り、コマンドを run することができます。この記事では、最初に cmdWhistle.pl プログラムでのユーザー空間の使い方と構成を説明し、その後、このプログラムのさまざまな機能について説明します。

cmdWhistle.pl プログラムを、sndpeek --print --nodisplay | perl cmdWhistle.pl -cというコマンドで実行します。これによって sndpeek プログラムがマイクを聞き取り始め、その出力をこの Perl プログラムに出力します。プログラムが実行を始めたら、単純な口笛を吹いてみます。3 分の 1 秒間隔で区切った連続するトーン、あるいはだんだん高くなるローンを出します。このプログラムは、比較的雑音のない環境で実行する必要があることに注意してください。そのため、ヘッドホンを使い、また CD ドライブが停止していることを確認します。もしラップトップのバッテリーが火を噴くようであれば、このプログラムを実行する前に煙感知器を外しておく必要があります。

さまざまなペースとトーンを試し、cmdWhistle プログラムで捉えることができるイベントの精度の感覚をつかみます。このプログラムのトーン検出プロセスの微妙さを経験することは、反復可能で複雑なトーン・シーケンスを作成する上で重要です。最初のトーン・シーケンスは単純なものにすべきです。0.5 秒の間隔で 2 つの低いトーンを出します。sndpeek --print --nodisplay | perl cmdWhistle.pl -c を実行し、「enter a tone sequence」が表示されたら、0.5 秒の間隔で口笛を 2 度鳴らします。4 秒 (変更可能) 後に自動タイムアウトが発生し、25.00 25.00 _#_ 0 500000 _#_ <command here> _#_ <comment here> のようなトーン・シーケンスが出力されます。

トーン・シーケンスのコマンドと検出を設定する

この行を詳しく見てみましょう。トーンの値と、区切り、時間の値、区切り、コマンド領域、区切り、そしてコメント領域があります。次のステップは、この行を、cmdWhistle.pl プログラムのデフォルトのコンフィギュレーション・ファイル、{$HOME}/.toneFile (おそらく /home/<username>/.toneFile のはずです) にコピーします。上記のトーン・シーケンス行を持つ ~/.toneFile を作成したら、プログラムを実行するように、この行を変更します。コマンド領域のテキストを /bin/echo "two low" に変更し、コメント領域をもっとわかりやすいもの、例えば 25.00 25.00 _#_ 0 500000 _#_ /usr/bin/echo "two low" _#_ two low tones のように変更します。

通知を出力するようにコンフィギュレーション・ファイルを変更できたので、コマンド、sndpeek --print --nodisplay | perl cmdWhistle.pl を使って cmdWhistle.pl スクリプトをデーモン・モードで実行します。このプログラムは、 ~/.toneFile リストにある任意のイベントを待って、黙ってバックグラウンドで聞き取ります。先ほどと同じ間隔と調子で低いトーンの口笛を 2 回吹くと、画面上に「two low」というテキストが表示されるのが見えるはずです。cmdWhistle.pl スクリプトの動作を詳細に見たい場合には、コマンドに sndpeek --print --nodisplay | perl cmdWhistle.pl -v を使ってデーモン・モードで実行します。皆さんのシステムがグラフィックス・ディスプレイをサポートしている場合には、--nodisplay オプションを削除すれば、素晴らしい 3D 表示でサウンド入力を見ることができます。

xwit を使ったウィンドウ管理の設定例

raise、lower、iconify シーケンスの作成

キーボードのショートカットやマウスの動きを再現する便利なトーン入力としては、さまざまな選択肢があります。下記の例では、ウィンドウ管理機能を提供することに焦点を当てています。この機能を利用すると、ユーザーは手をホーム・ポジションに置いて楽しくキーをたたきながら、自在にウィンドウを配置することができます。「その他の例」のセクションでは、このように拡張された入力方法を使うことによる可能性を説明しています。

コマンド、sndpeek --print --nodisplay | perl cmdWhistle.pl -c を使って cmdWhistle.pl プログラムを「create」モードで実行します。ウィンドウ管理機能を素早く操作できるように、容易に再現可能な単純なトーンをいくつか作成する必要があります。低いトーンを「lower」用に、高いトーンを「raise」用に、2 つの中間トーンを「iconify」に使うようにお勧めします。この場合、正確に一貫して実行できるトーンを確実に選ぶようにします。トーン・シーケンスの入力に必要な精度を (ピッチと時間の両方で) コントロールするパラメーターを変更することはできますが、対象とするトーンや正確なタイミングを一致させることは、やはり簡単ではありません。カラオケ・バーで練習が必要な私達にとって、ピッチの間隔が広くあいている 3 つのトーンを用意しておくと、コマンドの柔軟性と単純さがほどよく組み合わされます。下記は、この 3 つのコマンドを持つ ~/.toneFile の例です。

リスト 3. 3 つのコマンドを持つ ~/.toneFile の例
20.00 _#_ 0 _#_ /usr/bin/xwit -pop -names rxvt _#_ raise rxvt windows
#
#
40.00 _#_ 0 _#_ /usr/bin/xwit -pop -names nathan _#_ raise xterms 
starting with nathan@localhost
#
#
25.00 25.00 _#_ 0 500000 _#_ /usr/bin/xwit -iconify -names 
nathan _#_ iconify nathan@localhost~

継続して使用する前に、xwit コマンドを echo で置き換えて練習することを考えてみてください。

xwit - シェル・スクリプトから利用できる X 関数

X ウィンドウ・システムをコマンド・ライン・ウィンドウでコントロールするための方法は数多くありますが、その中で xwit は最も単純で移植性の高い方法の 1 つであり、多くのウィンドウ・マネージャーで動作します。xwit をダウンロードしてインストールし、xwit -iconify コマンドを実行して、このプログラムが動作していることを確認します (カレント・ウィンドウが最小化されます)。xwit には「lower」関数がありませんが、他のウィンドウを手前に表示することで、その問題を回避することができます。この例の場合では、「nathan」で始まるタイトルを持つウィンドウを、1 つの高いトーン (40.00) を使って手前に表示します。通常のプログラミング・タイプのタスクでは、識別用に xterm のタイトルを設定するのは適切な方法です。xterm のタイトルを設定する方法は、通常のプログラミング・タイプのタスクに適した、ウィンドウを識別するための簡単な方法です。

ウィンドウのタイトルが rxvt で始まる場合には、1 つの低いトーン (20.00) を使うことで、そのウィンドウを手前に表示することができます。0.5 秒間隔の 2 つの中間トーン (25.00) を使うと、「nathan」で始まるタイトルを持つすべてのウィンドウをアイコン化することができます。この実際の例については「参考文献」を参照してください。

この設定による重要な利点として、1 つのウィンドウでタイプ入力を続けながら別のウィンドウを表示できることがあげられます。これは、API 情報の参照ドキュメントをフォアグラウンドに持ってくる一方で、サブルーチン構造を構成するような場合に便利です。実質的に、タイプ入力するよりも早く計算を行うことができます。つまりタイプ入力とウィンドウ環境管理を同時に行えるのです。

ウインドウを手前あるいは奥に移動するための Windows シェル・スクリプト

Microsoft のオペレーティング・システムでウィンドウをコントロールする場合、ウィンドウを手前に表示するための単純な方法として、WshShell.AppActivate コマンドを使う方法があります。例えば「gvim」アプリケーションを手前にしたい場合には、次のコードを使って「gvimActivate.vbs」というファイルを作ります。

Set WshShell = WScript.CreateObject("WScript.Shell")
WshShell.AppActivate "gvim";

上記のファイルが用意できれば、このファイルを実行するだけで「gvim」ウィンドウにフォーカスが与えられ、このウィンドウがフォアグラウンドに表示されます。Windows を実行している場合、~/.toneFile の単純な高いトーンのコマンドを、40.00 _#_ 0 _#_ gvimActivate.vbs _#_ raise gvim window に変更します。

その他の例

sndpeek プログラムと cmdWhistle.pl には、独自の方法で利用できるユーザー入力機構が他にも用意されています。スクリーンセーバーのロックを解除するコードを設定し、そして机に近づく時に口笛を吹けば、面倒なパスワード入力は必要なくなります。あるいは、口笛を吹く度に E メールの配信をチェックする、あるいは自分の携帯電話に特有のトーンを覚えさせ、電話が鳴ったときに自分自身に E メールを送る、といったこともできます。

cmdWhistle.pl のコード

歴史と戦略

高速フーリエ変換と、棒状にパーセントを表示するウィンドウ、そしてちょっとした Linux 音声プログラミングによって、皆さんの選んだ言語によるトーン認識機能を実現することができます。Ge Wang と Perry R. Cook、そして Ananya Misra によって作成された sndpeek アプリケーションは、cmdWhistle.pl がトーン・イベントを検出するために必要な情報を、移植しやすく高速な、そして UNIX® 式の方法で取得します。単純なコマンド、sndpeek --print は、現在のサウンド・ソースのテキスト分析をリアルタイムで表示し、また、素晴らしい 3D 表示も行います。sndpeek が出力するテキスト分析の 4 番目のエントリーは、sndtools ディストリビューションの「Marsyas」コンポーネントを使った sndpeek 処理の Rolloff 関数の出力です。下記は Rolloff.cpp ソースコードの記述です。

リスト 4. Rolloff.cpp ソースコードの記述
Compute Rolloff (a percentile point) of the input fvec. 
For example if perc_ is 0.90 then Rolloff would be the 
index where the sum of values before that index are 
equal to 90% of the total energy.

cmdWhistle.pl スクリプトは、この値をベース「tone」として使うことで、トーン間のさまざまな時間間隔を検出します。

パラメーターの構成

cmdWhistle.pl の先頭から始めることにしましょう。ここにはタイミングとセンサーに関して重要なパラメーターがあります。

リスト 5. タイミングとセンサーに関して重要な、cmdWhistle.pl のパラメーター
$|=1; #for non buffered standard output, useful for other programs to read 
require 'sys/syscall.ph'; # for subsecond timing

my $option = $ARGV[0] || ""; # simple option handling

my $MAX_TIMEOUT_LENGTH = 4; # maximum length in seconds of tone pattern
my $LISTEN_TIMEOUT = 2; # timeout value in seconds between tone
my $MAX_TIME_DEV = 	100000; # maximum acceptable deviation between recorded
	# pattern values and current time values
my $MAX_TONE_DEV = 2; # maximum acceptable deviation between recorded
	# pattern values and current tone values
my $MAX_AVG_TONE = 5; # maximum number of samples to be averaged

上記の変数と、それに対するコメントは、比較的単純です。これらの使い方と構成オプションの詳細については、後ほど説明します。下記は、その他のグローバル変数と、その記述です。

リスト 6. その他のグローバル変数と、その記述
my @queTones = ();	# running queue of recent tones detected 
my $prevTone = 0; 	# the last solid tone detected, used for disambiguation
my $prevInterval = 0; 	# previous interval of time
my @baseTones = (); # the currently entered tone sequence
my @baseTimes = (); # the currently entered temporal values
my %toneHash = (); 	# tones, times and commands read from ~/.toneFile
my $toneCount = 0; 	# the current count of tones entered
my $startTime = ""; 	# start of a temporal block
my $currTime = ""; 	# current time in the time out loop
my $toneAge = 0; 		# for tone count synchronization
my $timeOut = 0;		# to reset the timer window

サブルーチン

まず、getEpochSeconds と getEpochMicroSeconds というサブルーチンから始めます。これらは、トーンのテンポのパターンの状態について詳細で正確な情報を得るために使われます。

リスト 7. サブルーチン、getEpochSeconds と getEpochMicroSeconds
sub getEpochMicroSeconds {

 my $TIMEVAL_T = "LL";   # LL for microseconds
 my $timeVal = pack($TIMEVAL_T, ());

 syscall(&SYS_gettimeofday, $timeVal, 0) != -1 or die "micro seconds: $!";
 my @vals = unpack( $TIMEVAL_T, $timeVal );
 $timeVal = $vals[0] . $vals[1];
 $timeVal = substr( $timeVal, 6);

 my $padLen = 10 - length($timeVal);
 $timeVal = $timeVal . "0" x $padLen;

 return($timeVal);
}#getEpochMicroSeconds

sub getEpochSeconds {
 my $TIMEVAL_T = "LL";   # LL for microseconds
 my $start = pack($TIMEVAL_T, ());
 syscall(&SYS_gettimeofday, $start, 0) != -1 or die "seconds: $!";
 return( (unpack($TIMEVAL_T, $start))[0] );
}#getEpochSeconds

次は readTones サブルーチンです。このサブルーチンは、最初に sndpeek からの出力として Rolloff のデータ値を取得します。下記のコメント・セクションを見るとわかるとおり、このコードは最初に 5 つのサンプル・トーンを作成してそれらを相互に比較し、変位を計算するための元を作ります。トーン配列キューが一杯になると、それぞれのトーンに対する変位を計算します。キューの中のどれか 1 つのトーンを比較した結果、最大トーン変位を越えている場合には、現在の処理では認識可能なトーンをが生成されなかったことを明示します。

キューの中のすべてのトーンの変位が、受け入れ可能なしきい値以内の場合には、トーンの曖昧さを検出するテンポ・レイヤーを実行します。少しずつトーンが高くなるように口笛を吹くと、個々のトーンは受け入れ可能なしきい値からわずかに変位し、認識されたトーン・イベントを生成します。しかし、この連続的な検出は問題を起こす可能性があります。upDev 変数と downDev 変数、そして比較ロジックは、MAX_TONE_DEV 変数よりも変位が大きな場合に、こうした連続的なトーン変化を取得するように設計されています。最新のトーン・キュー・チェックと連続トーン・チェックの両方をパスしたら、後で出力したり比較したりできるように、そのトーン・イベントと時間を記録します。

こうした変数を注意深く修正することによって、特定のトーン・スタイルとテンポの変位を認識するようにプログラムを微調整することができます。周波数分析全体としての更新間隔は、sndpeek プログラムの出力によって指示されます。これら以外のパラメーターを構成することによって、もっと時間間隔の空いたトーン・イベント、つまり時間しきい値を超える複数の同時トーン・イベント、あるいはイベント間のさまざまなタイミングを検出することができます。

リスト 8. さらに時間間隔の空いたトーン・イベントを検出する
sub readTones
{ 
 # read the Rolloff output only, 
 my(undef, undef, undef, $currentTone ) = split " ", $_[0];

 if( @queTones == $MAX_AVG_TONE )
 { 
  my $localDiff = 0;
  # check for a solid tone by comparing against the last five tones
  # perform simple time related tonal smoothing, so if the tone
  # wavers just a bit it's still counted as one tone
  for my $chkHistTone ( @queTones )
  { 
   if( abs($currentTone - $chkHistTone) > $MAX_TONE_DEV )
   { 
    $localDiff =1;
    $prevTone = 0;
   }#if deviation less than threshold
  }#for each tone

  if( $localDiff == 0 )
  { 
   # make sure the current tone is different than the previous one, this is to 
   # ensure that long duration tones are not acquired as multiple tone events
   # this step up or down will allow you to whistle continuously and pick up the 
   # steps as discrete tone events
   my $upDev  = $currentTone + $MAX_TONE_DEV;
   my $downDev = $currentTone - $MAX_TONE_DEV;
   if( $prevTone > $upDev || $prevTone < $downDev )
   { 
    my $currVal = getEpochMicroSeconds();
    my $diffInterval = abs($prevInterval - $currVal);
    if( $option ){
     print "Tone: $currentTone ## last: [$currVal] curr: [$prevInterval] ";
     print "difference is: $diffInterval\n";
    }
    if( $toneCount == 0 ){ $diffInterval = 0 }
    push @baseTones, $currentTone;
    push @baseTimes, $diffInterval;
    $toneCount++;
    $prevInterval = $currVal;
   }#if deviation in tone

   # now set the previous tone to the current tone so a continuous tone
   # is not acquired as multiple tone events
   $prevTone = $currentTone;

  }#if a solid tone has been found

  # if enough tones to create an useful queue have been added, pop one off
  shift @queTones;

 }#if enough tones to create a useful queue

 # always push more tones on the avg
 push @queTones, $currentTone;

}#readTones

トーン・パターンが作成されると、そのパターンは ~/.toneFile ファイルの中に置かれ、次のサブルーチンによって読み取られます。

リスト 9. トーン・パターンの作成
# readToneFile reads tone sequences and commands from ~/.toneFile
# format is: tones _#_ times _#_ command _#_ comments
sub readToneFile 
{
 #give it a full path to .toneFile if on windows
 open(TONEFILE,"$ENV{HOME}/.toneFile") or die "no tone file: $!";

  while(<TONEFILE>){

   if( !/^#/ ){

    my @arrLine = split "_#_";
    $toneHash{ "$arrLine[0] $arrLine[1]" }{ tones }  = $arrLine[0];
    $toneHash{ "$arrLine[0] $arrLine[1]" }{ times }  = $arrLine[1];
    $toneHash{ "$arrLine[0] $arrLine[1]" }{ cmd }   = $arrLine[2];
    $toneHash{ "$arrLine[0] $arrLine[1]" }{ comment } = $arrLine[3];

   }#if not a comment line

  }#for each line in file

 close(TONEFILE);

}#readToneFile

readTone がトーン・パターンを取得すると、そのパターンは、readToneFile からロードされた既存のトーン・パターンと比較されます。compareToneSequences サブルーチンは、トーンの値同士のほかにも、トーンのタイミング同士の単純な差分チェックを行います。トーンの値同士、タイミング同士の差は合成されないことに注意してください。多くの音で、少しくらいタイミングやトーンが一致しないからといって、完全な一致失敗にはなりません。

トーン・ファイルの各トーンに対して、突き合わせ用のトーン配列とテンポ配列を作ります。7 トーンのシーケンスを 2 トーンのシーケンスと比較しても意味がないので、最初はトーンの数を比較します。各トーンと時間に対して、それらの値が受け入れ可能な変位パラメーター内にあるかどうかをチェックします。トーン・シーケンスを正確に (厳密に、ではありません) 突き合わせるために、トーンとテンポの最大変位は非常に重要です。トーンあるいは時間の最大変位を増やすことによって、リズミカルなタイミングあるいはトーンの生成に幅を持たせることができます。ただし設定に幅を持たせると誤検出の結果を招く可能性があるため、十分な注意と実験が必要です。例えば、テンポ変位のしきい値を100000 に保持した状態で、トーン変位のしきい値のみを 5 に増やすようにします。これによって、想定されるパターンとかけ離れたトーンであってもタイミングが正しければ入力でき、パターンの突き合わせができます。これはタイミングのみを練習したい場合に便利です。

パターンが完全に一致すると、~/.toneFile で指定されたコマンドが実行され、詳細 (verbose) モードが有効になっていれば実行結果が出力されます。次のステップでは、一致が見つからない場合にはサブルーチンを終了し、あるいは一致が見つかった場合には現在のトーンと時間の記録をリセットします。

リスト 10. トーン・パターンの作成
sub compareToneSequences 
{
 my $baseCount = @baseTones;
 my $countMatch = 0; # record how many tones matched

 for my $toneFromFile ( keys %toneHash )
 {
  my @confTones = split " ", $toneHash{$toneFromFile}{tones};
  my @confTimes = split " ", $toneHash{$toneFromFile}{times};

  my $confCount = @confTones;

  next unless( $confCount == $baseCount );

  # as a learning aid, the matching and non matching portions of the
  # comparison are printed out, so at least you can see what is going 
  # wrong while trying to remember your tone codes
  my $pos =0;
  my $toneMatchFail = 0;
  for( @baseTones )
  { 
   my $tonDiff = abs($confTones[$pos] - $baseTones[$pos]);
   my $tonStr = "t $pos b $baseTones[$pos] ".
          "c $confTones[$pos] \n";

   my $timeDiff = abs($confTimes[$pos] - $baseTimes[$pos]);
   my $timStr = "t $pos b $baseTimes[$pos] ".
          "c $confTimes[$pos] d $timeDiff\n";

   if( $tonDiff > $MAX_TONE_DEV )
   { 
    $toneMatchFail = 1;
    if( $option ){ print "NOTE DISSONANCE $tonStr" }
   }else
   { 
    if( $option ){ print "NOTE MATCH $tonStr" }
   }#if tone detected outside of deviation


   # if it's an exact match, increment the matching counter
   if( $timeDiff < $MAX_TIME_DEV ){
    if( $option ){ print "TIME MATCH $timStr" }
    $countMatch++;
   }else{
    if( $option ){ print "TIME DISSONANCE $timStr" }
    last;
   }# deviation check

   $pos++;

  }# for each tone to check 

  if( $countMatch == $confCount && $toneMatchFail == 0 )
  { 
   my $cmd = $toneHash{$toneFromFile}{ cmd };
   if( $option ){ print "run: $cmd\n" }
   $cmd =`$cmd`;
   if( $option ){ print "result: $cmd\n" }
   last;

  # otherwise, make the count of matches zero, in order to not reset
  }else
  { 
   $countMatch = 0;
  }

 }#for each tone in tone file

 # if the match count is zero, exit and don't reset variables so a longer
 # tone sequence can be entered and checked
 if( $countMatch == 0 ){ return() }

 # if a match occured, reset the variables so it won't match another pattern
 $toneCount = 0;
 @baseTones = ();
 @baseTimes = ();

}#compareToneSeqeunces

メインのプログラム・ロジック

サブルーチンが用意できると、ユーザーはメインのプログラム・ロジックを使ってトーン・シーケンスを作成できます。あるいは、プログラム・ロジックはデーモン・モードで実行し、トーンを聞き取ってコマンドを実行します。最初のセクションは、ユーザーが作成モードとして「-c」オプションを指定すると実行されます。単純なタイムアウト・プロセスを使ってノック・シーケンスが終了されます。最大タイムアウト長変数を大きくし、4 秒を超えるトーン間の休止期間を許すようにします。最大タイムアウト長を 4 のままにすると、プログラムは終了し、現在入力されているトーン・シーケンスを出力します。

リスト 11. タイムアウト・プロセス
if( $option eq "-c" ){

 print "enter a tone sequence:\n";

 $startTime = getEpochSeconds(); # reset time out start

 while( my $sndPeekOutput = <STDIN> )
 { 

  $currTime = getEpochSeconds();

  # check if there has not been a tone in a while 
  if( $currTime - $startTime > $MAX_TIMEOUT_LENGTH ){

   $timeOut = 1; # exit the loop

  }else{

   # if a tone has been entered before timeout, reset timers so
   # more tones can be entered

   if( $toneCount != $toneAge ){
    $startTime = $currTime;  # reset timer for longer delay
    $toneAge = $toneCount; # synchronize tone counts
   }# if a new tone came in

  }# if timer not reached

  readTones( $sndPeekOutput );

  if( $timeOut == 1 ){ last }
 }#while stdin

 if( @baseTones ){
  print "place the following line in $ENV{HOME}/.toneFile\n\n";
  for( @baseTones ){ print "$_ " }
  print "_#_ ";
  for( @baseTimes ){ print "$_ " }
  print "_#_ (command here) _#_ <comments here>\n\n";
 }#if tones entered

メイン・ロジックのセクション 2 は、sndpeek の --print コマンドからの出力を連続的に読み取ります。タイムアウトしきい値に達すると、別々のトーン・パターンを区別するために、トーン・グループは自動的にリセットされます。LISTEN_TIMEOUT 変数を変更してトーン入力時間を速くする、あるいはタイムアウト変数を長くして、もっと時間間隔の空いたトーン・パターンのイベントを取得する、などを検討してみてください。

リスト 12. LISTEN_TIMEOUT を変更する
}
				


}else
{ 
 # main code loop to listen for tones and run commands
 readToneFile();
 $startTime = getEpochSeconds();

 while( my $sndPeekOutput = <STDIN> )
 { 

  $currTime = getEpochSeconds();

  if( $currTime - $startTime > $LISTEN_TIMEOUT ){

   $toneCount = 0;
   @baseTones = ();
   @baseTimes = ();
   $startTime = $currTime;
   if( $option ){ print "listen timeout - resetting tones \n" }

  }else{

   if( $toneCount != $toneAge ){
    $startTime = $currTime;  # reset timer for longer delay
    $toneAge = $toneCount;  # synchronize tone counts
   }# if a new tone came in

   compareToneSequences();

  }#if not reset timeout

  readTones( $sndPeekOutput );

 }#while stdin

}#if option set

注意点とセキュリティー

cmdWhistle プログラムは、手でマウスとキーボードを操作する一方で、ユーザーからの入力をもう 1 チャネル追加で使用するのにとても適しています。ただし、システム上で認証が要求されることに cmdWhistle を使う場合には、十分な注意が必要です。

選択したトーン・シーケンスをリスナーが記録し、真似し、システム上でコマンドを実行するという明白な問題の他にも、トーンによる認証にはさまざまな要素が懸念され、まじめな用途に使用するのはあまりにも早計と言えるでしょう。トーン・シーケンスは現在、マイクロ秒での遅延を 4 桁から 9 桁で表現したものと共に、2 桁の「notes」として ~/.toneFile の中に保存されています。この「パスワード」ファイルの読み取りは比較的簡単であり、さまざまなトーン・パターンを単純に何度も試すことで、システムにアクセスできるようになります。マイクロ秒での値の精度の一部を削除することで片方向ハッシュを使うことも可能ですが、その危険性を自分の責任で試したいという人以外、そうしたことは避けた方が無難です。


ダウンロード可能なリソース


関連トピック

  • プリンストン大学が sndpeek プログラムをホストしています。
  • ibiblio.org は xwit プログラムのミラーをホストしています。
  • YouTube.com のデモ・ビデオを見てください。あるいは Google Video を見てください。
  • IBM developerWorks の PHP project resources を訪れ、PHP について学んでください。
  • developerWorks の Open source ゾーンをご覧ください。オープンソース技術を使った開発や、IBM 製品でオープンソース技術を使用するためのハウ・ツー情報やツール、プロジェクトの更新情報など、豊富な情報が用意されています。
  • 皆さんの次期オープンソース開発プロジェクトを IBM trial software で革新してください。ダウンロード、あるいは DVD で入手することができます。

コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source, Linux
ArticleID=249809
ArticleTitle=仕事中に口笛を吹いて、コンピューターにコマンドを実行させる
publish-date=01092007