ギターチューナーに動作を加える
ギターチューナーの外観ができたので、次に動作を加えましょう。ここでの目標は次の3つです。
- 弦をクリックできるようにし、クリックされた場合、音を出していることを示すインジケータを表示します。
- 弦が音を出しているときに赤い停止ボタンを押すと、前項の表示を消します。
- 1項の表示があるときは該当する音を出し続け、表示が消えると音も消えるようにします。
第1の目標
まず、第1の目標です。始めに、弦と停止ボタンの上にマウスを置いたとき、それらがクリックできることがわかるようにしなければなりません。このような場合、手の形のカーソルを使うのが普通です。そこで、XAMLファイルを再度開き、すべての弦と停止ボタンにCursor属性を追加します。コードは次のようになります。
<Rectangle x:Name="LowEString" Width="315" Height="5"
Canvas.Left="15" Canvas.Top="37" Cursor="Hand">
次に、弦がクリックされたときに所定の動作をするようマウスボタンハンドラを追加します。次のように、イベント名とハンドラ関数を設定します。このハンドラ関数はすべての弦で共通して使います。残り5本についても同じように追加します。
<Rectangle x:Name="LowEString" Width="315" Height="5"
Canvas.Left="15" Canvas.Top="37" Cursor="Hand"
MouseLeftButtonDown="stringPlucked">
メモ帳で「guitartuner.js」ファイルを開き、上のコードで指定したstringPlucked関数を追加します。
function stringPlucked(sender, args)
{
}
この関数の中に必要な動作を書き込んでいきます。ここでは、弦が弾かれたら、その弦とブリッジが交差する点に緑のインジケータを点滅させます。まず、次のように、[弦の名前]Indicatorという名の要素を追加します。
<Rectangle x:Name="LowEString" Width="315" Height="5" Cursor="Hand" Canvas.Left="15" Canvas.Top="37" MouseLeftButtonDown="stringPlucked"> <Rectangle.Fill> <LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> <GradientStop Color="Brown" Offset="0.0"/> <GradientStop Color="White" Offset="0.5"/> <GradientStop Color="Brown" Offset="1.0"/> </LinearGradientBrush> </Rectangle.Fill> </Rectangle> <Rectangle x:Name="LowEStringIndicator" Height="15" Width="10" Canvas.Left="10" Canvas.Top="30" Fill="#00FF00" Opacity="0.0" > </Rectangle>
少し説明しておきましょう。Opacity属性はその要素の不透明度を表す属性です。これを0にすると要素全体が透明になり、背景が透けて見えます。デフォルトの不透明度は1.0で、要素ははっきりと完全に見えます。対応する弦が弾かれるときを除いて、このインジケータは透明にしておきます。したがって、ブラウザで再度読み込んでもこの要素は見えません。不透明度が0.0だからです。
次に、インジケータを点滅させます。そのため、stringPlucked関数が呼ばれたとき、該当するインジケータの不透明度を0から1に徐々に変化させます。これでどの弦が鳴っているかがわかり、本物のギターで調弦すべき弦がわかります。タイマーを使って変化させることもできるでしょうが、まずはXAMLの活用を検討しましょう。実際、XAMLのストーリーボードと呼ばれている方法を利用して実現することができます。ストーリーボードはストーリーを記述する方法の1つと考えればよいでしょう。ストーリーは時間軸に沿って順次何かが起こり進行していきます。ギターチューナーの場合で言えば、数秒間にわたってインジケータの不透明度が一定の割合で変化し、終わると再び始まるストーリーになります。したがって、弦が弾かれたら、対応するストーリーボードを取り出して開始すれば点滅を実現できます。これらの手順をXAMLで書くと、次のコードになります。
<Rectangle x:Name="LowEStringIndicator" Height="15" Width="10" Canvas.Left="10" Canvas.Top="30" Fill="#00FF00" Opacity="0.0" > <Rectangle.Resources> <Storyboard x:Name="LowEStringIndicatoranimation"> <DoubleAnimation Storyboard.TargetName="LowEStringIndicator" Storyboard.TargetProperty="Opacity" From="0.0" To="1.0" Duration="0:0:0.3" AutoReverse="True" RepeatBehavior="Forever" /> </Storyboard> </Rectangle.Resources> </Rectangle>
少し説明しておきましょう。まずResourcesセクションの使い方ですが、これは、ストーリーボードを要素に追加するためのXAML流の方法です。名前はLowEStringIndicatorAnimationとしてあります。このような名前にしておくと該当するストーリーボードの指定が簡単になります。重要なのはTargetNameとTargetPropertyです。TargetNameはそのストーリーの操作対象の要素を表し、TargetPropertyはその要素のどのプロパティを操作するかを表します。この例では、LowEStringIndicatorのOpacityプロパティをアニメーションで操作することになります。次に重要なのはFromとToです。アニメーションのタイプがDoubleAnimationなのは、Opacityがdouble型だからです。これはdouble型のプロパティを変更またはアニメートする際に使われます。値は0.0(完全透過)から1.0(完全な表示)に0.3秒で変化させます。Autoreverse=Trueとしてあるので、不透明度が0.0から1.0になると、今度は1.0から0.0に戻ります。RepeatBehaviorはForeverなので、これが指定されていないと再び0.0から始まることになります。
あとは、このストーリーボードのアニメーションが起動されるようにすれば終わりです。そこで、次のコードを「guitartuner.js」ファイルのstringPlucked関数に組み込みます。
function stringPlucked(sender, args) { var storyboardObj = sender.findName(sender.Name + "Indicatoranimation"); storyboardObj.begin(); }
XAMLファイルとJSファイルを保存し、ブラウザのHTMLファイルを再度読み込ませます。ここで、一番上の弦をクリックすると緑のインジケータが点滅するはずです。
ここで、この小さなJavaScriptコードについて若干補足しておきましょう。まず、とても便利なfindName関数が使われています。その引数にあるsenderは、このイベントを生成したオブジェクトを表します。ギターチューナーの場合、Plucked関数はLowEString、AString、GString、DString、BString、HighEStringstring(この6つの要素にこのハンドラを追加しました)によって生成されるので、sender.Nameはイベントを発生させた弦のx:Name属性を表すことになります。一方、命名規則によれば、アニメーションの名前は「該当する弦の名前+Indicatoranimation」です。したがって、sender.Name+"Indicatoranimation"で、実行すべきストーリーボードの名前が得られることになります。そのストーリーボードをfindNameを使って取り出しているわけです。ところで、findNameは非常に興味深い関数で、[オブジェクト].findNameという形で呼び出されることから、渡された名前の要素をツリー構造の下にあるオブジェクトから探すように見えますが、実際には木全体からその名前を探します。したがって、イベントを生成した弦で、その下にストーリーボードを持たなくても、findName関数を使って取得できるのです。ストーリーボードオブジェクトを取得したら、あとはbegin()を使ってアニメーションを開始させるだけです。
これで第1の目標は完成です。
第2の目標
次は第2の目標です。弦が鳴っているときに赤い停止ボタンが押されたら、先ほど実装したインジケータを消さければなりません。基本的に同じ方法で実装することができます。それでは、MouseLeftButtonDownハンドラを追加し、StopClickedなどといった名前を付けます。
<Ellipse x:Name="StopButtonb" Height="15" Width="15"
Canvas.Left="8" Canvas.Top="125" Stroke="Black"
StrokeThickness="1" Cursor="Hand"
MouseLeftButtonDown="StopClicked">
次に、上のコードで指定した関数をJavaScriptに追加します。この関数はどのストーリーボードが動いているのかを知る必要があります。そこで、ストーリーボードを起動したときその情報を保存しておくようにしましょう。次に示すように、storyBoardObjをグローバル変数にします。そして、stopClickedハンドラでこの変数に対してend()を実行します。
var storyboardObj = null; function stringPlucked(sender, args) { storyboardObj = sender.findName(sender.Name + "Indicatoranimation"); storyboardObj.begin(); } function StopClicked(sender, args) { if(storyboardObj != null) { //stop any animation first storyboardObj.stop(); } }
XAMLとJSファイルを保存し、ブラウザのHTMLファイルを再度読み込んでください。そして、一番上の弦をクリックします。緑のインジケータが点滅します。次に、赤い停止ボタンをクリックすると、アニメーションが止まるはずです。他の5本の弦についても、同じようにコードを追加します。インジケータとストーリーボード要素の名前やそのインジケータ要素に対するCanvas.Topの値(すぐ上の弦に対して15ピクセルずつ増やします)を間違えないようにしましょう。
これで第2の目標は完成です。
第3の目標
次は第3の目標です。インジケータが表示されたら該当する弦の音を鳴らし、インジケータが消えたら音を止めなければなりません。
これには、それぞれの弦に対応する音程のサウンドファイルが必要です。この記事の冒頭で示したサンプルファイル(media.zipファイル)をダウンロードして展開します。その中にWMAファイルがあるので、それを他のファイルと同じ場所に移します。ファイルの名前は、XAMLファイル内の弦要素の名前に拡張子.wmaを付けたものになっているはずです。
XAMLはメディア要素を扱うこともできます。そこで、XAMLファイルを開き、次の要素を追加します。
<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/ presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Height="400" Width="290" > <MediaElement x:Name="sound" Width="0" Height="0"/> <Ellipse Height="100" Width="100" Canvas.Left="30" Canvas.Top="25" Fill="Black"/>
名前が付けてあるのは、JavaScriptから指定できるようにするためです。このメディアは見せる必要がありませんから、高さと幅は0にします(必要なのは音だけです)。弦が弾かれるまではどの弦の音を出すかわからないので、ここではメディアファイルを指定しません。第3の目標は、アニメーションと同時にメディアを再生し、アニメーションの停止と同時にメディアの再生を止めることです。そこで、JavaScriptコードを次のように変更します。
function stringPlucked(sender, args) { storyboardObj = sender.findName(sender.Name + "Indicatoranimation"); storyboardObj.begin(); sender.findName("sound").Source = sender.Name + ".wma"; sender.findName("sound").Play(); } function StopClicked(sender, args) { if(storyboardObj != null) { //stop any animation first storyboardObj.stop(); sender.findName("sound").Stop(); } }
このコードは、サウンド要素を探し出し、そのPlayメソッドまたはStopメソッドを実行します。再生すべきサウンドファイルの名前は、命名規則に従って生成します(それをSourceプロパティを使って設定します)。実は、もう1つやり残したことがあります。それは、弦が鳴っている最中に他の弦が弾かれたときの処理です。2つの音が鳴らないように、そのとき鳴っていたアニメーション(インジケータとメディア)を止めなければなりません。そこで、JavaScriptのコードを次のように変更します。
function stringPlucked(sender, args) { StopClicked(sender,args); storyboardObj = sender.findName(sender.Name + "Indicatoranimation"); storyboardObj.begin(); sender.findName("sound").Source = sender.Name + ".wma"; sender.findName("sound").Play(); }
XAMLファイルとJSファイルを保存し、ブラウザのHTMLファイルを再度読み込んでください。そして、一番上の弦をクリックすると、緑のインジケータが点滅し、スピーカーかヘッドフォンに音が聞こえるはずです。赤い停止ボタンをクリックするとアニメーションとサウンドは止まり、他の弦をクリックするとその弦の音が再生されるはずです。
うまく動いていますね。しかし、これでは実用上不便なところがあります。調弦は、普通、音を鳴らしながら行うからです。つまり、音がすぐに止まってしまうのでは使いにくいのです。長いサウンドファイルを使えば音は長くなりますが、これはメディアの無駄というのものです。メディアを反復して再生すればいいのですから。反復再生は、Storyboardアニメーションをちょっと工夫して実現することもできますが、ここでは別の方法を使ってみましょう。MediaElementはMediaEndedというイベントを発生させますから、このイベントを捕捉し、そのハンドラの中でメディアの頭からもう一度再生させるのです。次のように変更します。
<MediaElement x:Name="sound" Width="0" Height="0"
MediaEnded="endofmedia"/>
function endofmedia(sender,args) { //reached end, seek to beginning and play again var mediaElement = sender.findName("sound"); var position = mediaElement.position; position.seconds = 0; mediaElement.position = position; mediaElement.Play(); }
XAMLファイルとJSファイルを保存し、ブラウザのHTMLファイルを再度読み込んでください。そして弦をクリックすると、緑のインジケータが点滅し、スピーカーかヘッドフォンに音が鳴り続け、停止ボタンをクリックすると止まるはずです。
以上で、ブラウザ上で動く簡単なギターチューナーが完成しました。
