センサーとサウンドの処理を組み込んでアプリを完成させる

2012年5月14日(月)
PROJECT KySS

処理ページのプログラミング

参照の追加

このアプリでは、センサーを利用するためにMicrosoft.Devices.Sensors名前空間を、サウンドの再生のためにMicrosoft.Xna.Framework名前空間を使うため、「プロジェクト/参照の追加」から、それらへの参照を追加しておきます(図15)。

 図15:Microsoft.Devices.Sensors とMicrosoft.Xna.Frameworkへの参照を追加する(クリックで拡大)

処理ページのロジック(MyClinometer.xaml.vb)

ここで、処理ページのプログラミングに移ります(リスト10)。

センサーとXna.Frameworkの名前空間をインポートして、レンジ設定、サウンド再生、針やメッセージの表示、加速度センサーの実行に必要な変数を宣言しておきます。

その上で、次の処理を記述します。

(1)初期設定

このページがロードされた時、レンジを「1」、サウンドを「ベル」に設定します。これをデフォルトの設定とします。加速度センサーがサポートされていない機種に対してメッセージを表示します。

(2)レンジ設定

レンジ設定スライダーの値が変化した時、設定した角度を表示し、レンジ横のWindows Phoneの絵を回転させて、設定角度を視覚的に表現します。

(3)サウンド設定

ベル、小鳥、歌の3つのボタンがそれぞれタップされた時、選択されたボタンは再タップできないようにして、他の2つのボタンはタップ可能にします。

(4)ボリューム設定

ボリュームのデフォルトは「0.3」とし、ボリューム調整スライダーの値が変化した時、ボリュームを設定し、ボリュームを表す数字を表示しているTextBlockをツマミの位置に合わせて移動させます。

(5)「Start」ボタンがタップされた時の処理

「Start」ボタンがタップされた時、Accelerometer クラスの新しいインスタンスを作成します。
傾斜表示の元になる角度、4個のTextBlockへの表示用情報、針の角度を初期化した上で、設定画面を非表示にして、結果表示ウィンドウを表示します。
加速度センサーでの情報取得を開始します。

(6)加速度センサーから取得する情報が変化した時の処理

加速度センサーからのデータを取得し、逆三角関数で角度をもとめ、プラスマイナスの付いた状態で変数MyChannelAngleX、MyChannelAngleYに格納しておきます。
4個のTextBlockに表示する数値は絶対値にして、水平より傾いている(下がっている)方向に表示します。
プラスマイナスの付いた角度から傾きの状態が分かり、再生させるサウンドを特定できるので、サウンドファイル名の部分を変数に代入しておきます。また、設定画面で設定したテーマをフォルダ名として、再生するサウンドファイル名を変数MySoundFileNameに代入します。
X方向またはY方向の角度が設定したレンジの角度以上に傾いた時、加速度取得を停止します。
音声ファイル名から針の角度を決定し、どちらの方向を上げた方がよいか(設定したレンジに対してどちらの方向が下がりすぎたか)を示すメッセージを変数MyMessageTextに代入し、針を回転させて表示します。
サウンドファイルを再生し、メッセージを表示して、「OK」ボタンを表示します。

(7)「OK」ボタンがタップされた時の処理

サウンドを停止してインスタンスを破棄し、結果表示ウィンドウを非表示にして設定画面を表示します。結果表示ウィンドウ内のパーツを非表示にして値を初期化します。

(8)ページがアンロードされた時の処理

取得される角度を代入する変数の値を初期化します。

リスト10 完成した処理ページのロジック・コード(MyClinometer.xaml.vb

  Option Strict On
  Imports Microsoft.Devices.Sensors
  Imports Microsoft.Xna.Framework
  Imports Microsoft.Xna.Framework.Audio

  Partial Public Class MyClinometer
    Inherits PhoneApplicationPage
 
    Public Sub New()
      InitializeComponent()
    End Sub
 
    '■レンジ設定用
    Dim MySetAngle As Integer '●レンジの設定角度
 
    '■サウンド再生用
    Dim MySoundDirectory As String '●サウンドファイルのディレクトリ名
    Dim MySoundTitle As String = String.Empty '●サウンドファイル名(共通)
    Dim SoundPropertyX As String = String.Empty '●サウンドファイル名のX部分(Right/Center/Left)
    Dim SoundPropertyY As String = String.Empty '●サウンドファイル名のY部分(High/Middle/Low)
    Dim MySoundFileName As String = String.Empty '●パス付きサウンドファイル名
    Dim MySound As SoundEffect = Nothing
    Dim MySoundInstance As SoundEffectInstance
 
    '■針表示用
    Dim MyAngleX, MyAngleY As Double '●丸めた傾斜角度の絶対値
    Dim MyChannelAngleX, MyChannelAngleY As Double '●丸めた傾斜角度
    Dim MyHandAngle As Integer = 0 '●描画する針の角度
 
    '■メッセージ表示用
    Dim MyMessageText As String = String.Empty '●メッセージの内容
 
    '■加速度センサー
    Dim MyAccelerometer As Accelerometer '●加速度センサー

(1)初期設定

    Private Sub MyAccelerometer_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
    MySetAngle = 1
    MySoundDirectory = "Bell"
    '●加速度センサーがサポートされていない機種への対応
    If Accelerometer.IsSupported = False Then
      MessageBox.Show("Device does not support accelerometer.")
      StartButton.IsEnabled = False
      Exit Sub
    End If
  End Sub

(2)レンジ設定

  Private Sub AngleSlider_ValueChanged(sender As Object, e As System.Windows.RoutedPropertyChangedEventArgs(Of Double)) Handles AngleSlider.ValueChanged
    MySetAngle = CInt(AngleSlider.Value)
    AngleValueText.Text = CStr(MySetAngle) & "°"
    myPhoneProjection.RotationZ = -MySetAngle
  End Sub

(3)サウンド設定

  Private Sub TweetButton_Tap(sender As Object, e As System.Windows.Input.GestureEventArgs) Handles TweetButton.Tap
    MySoundDirectory = "Tweet"
    TweetButton.Opacity = 0.3
    TweetButton.IsEnabled = False
    BellButton.Opacity = 1.0
    BellButton.IsEnabled = True
    VoiceButton.Opacity = 1.0
    VoiceButton.IsEnabled = True
  End Sub
 
  Private Sub BellButton_Tap(sender As Object, e As System.Windows.Input.GestureEventArgs) Handles BellButton.Tap
    MySoundDirectory = "Bell"
    TweetButton.Opacity = 1.0
    TweetButton.IsEnabled = True
    BellButton.Opacity = 0.3
    BellButton.IsEnabled = False
    VoiceButton.Opacity = 1.0
    VoiceButton.IsEnabled = True
  End Sub
 
  Private Sub VoiceButton_Tap(sender As Object, e As System.Windows.Input.GestureEventArgs) Handles VoiceButton.Tap
    MySoundDirectory = "Voice"
    TweetButton.Opacity = 1.0
    TweetButton.IsEnabled = True
    BellButton.Opacity = 1.0
    BellButton.IsEnabled = True
    VoiceButton.Opacity = 0.3
    VoiceButton.IsEnabled = False
  End Sub

(4)ボリューム設定

  Dim MyVolume As Double '●音声のボリューム。デフォルトは0.3とする
  Private Sub VolumeSlider_ValueChanged(sender As Object, e As System.Windows.RoutedPropertyChangedEventArgs(Of Double)) Handles VolumeSlider.ValueChanged
    ShowVolume.Text = CStr(CInt(VolumeSlider.Value))
    ShowVolume.Margin = New Thickness(VolumeSlider.Value * 32.5 - 99, 0, 0, 0)
    MyVolume = CInt(VolumeSlider.Value) / 10
  End Sub

(5)「Start」ボタンがタップされた時の処理

  Private Sub StartButton_Tap(sender As Object, e As System.Windows.Input.GestureEventArgs) Handles StartButton.Tap
 
    '●Accelerometer クラスの新しいインスタンスを作成
    MyAccelerometer = New Accelerometer
 
    '●傾斜表示の元になる角度を初期化
    MyAngleX = 0
    MyAngleY = 0
 
    '●TextBlockへの表示用情報を初期化
    Xplus.Text = String.Empty
    Xminus.Text = String.Empty
    Yplus.Text = String.Empty
    Yminus.Text = String.Empty
 
    '●針の角度を初期化
    MyHandAngle = 0
    myAngleProjection.RotationZ = MyHandAngle
 
    '●設定画面を非表示にして、結果表示ウィンドウを表示
    PreferencesPanel.Visibility = Windows.Visibility.Collapsed
    StatePanel.Visibility = Windows.Visibility.Visible
 
    '●加速度センサーでの情報取得を開始
    AddHandler MyAccelerometer.CurrentValueChanged, Sub(senderValue As Object, eValue As SensorReadingEventArgs(Of AccelerometerReading))
  
Deployment.Current.Dispatcher.BeginInvoke(Sub() CurrentValueChanged(eValue.SensorReading))
                                                       End Sub
    Try
      MyAccelerometer.Start()
    Catch
      MessageBox.Show("Unable to start accelerometer.")
      Exit Sub
    End Try
 
  End Sub

(6)加速度センサーから取得する情報が変化した時の処理

  Private Sub CurrentValueChanged(myReading As AccelerometerReading)
 
    Try
      '●加速度センサーからのデータを取得
      Dim MyAcceleration = myReading.Acceleration
 
      '●誤差を防ぐため上下をカット
      If MyAcceleration.X > 1 Then
        MyAcceleration.X = 1
      ElseIf MyAcceleration.X < -1 Then
        MyAcceleration.X = -1
      End If
 
      If MyAcceleration.Y > 1 Then
        MyAcceleration.Y = 1
      ElseIf MyAcceleration.Y < -1 Then
        MyAcceleration.Y = -1
      End If
 
      '●プラスマイナスの付いた角度を格納
      MyChannelAngleX = Math.Round(Math.Asin(MyAcceleration.X) * 180 / Math.PI)
      MyChannelAngleY = Math.Round(Math.Asin(MyAcceleration.Y) * 180 / Math.PI)
 
      '●表示用には絶対値の角度にする
      MyAngleX = Math.Abs(MyChannelAngleX)
      MyAngleY = Math.Abs(MyChannelAngleY)
 
      '●4方向のTextBlock中傾斜のある方向に絶対値の角度を表示
      '●サウンドファイル名の部分を設定
      If MyChannelAngleX > 0 Then
        SoundPropertyX = "Right"
        Xplus.Text = CStr(MyAngleX) & "°"
        Xminus.Text = String.Empty
      ElseIf MyChannelAngleX = 0 Then
        SoundPropertyX = "Center"
        Xplus.Text = String.Empty
        Xminus.Text = String.Empty
      ElseIf MyChannelAngleX < 0 Then
        SoundPropertyX = "Left"
        Xplus.Text = String.Empty
        Xminus.Text = CStr(MyAngleX) & "°"
      End If
 
      If MyChannelAngleY > 0 Then
        SoundPropertyY = "High"
        Yplus.Text = CStr(MyAngleY) & "°"
        Yminus.Text = String.Empty
      ElseIf MyChannelAngleY = 0 Then
        SoundPropertyY = "Middle"
        Yplus.Text = String.Empty
        Yminus.Text = String.Empty
      ElseIf MyChannelAngleY < 0 Then
        SoundPropertyY = "Low"
        Yplus.Text = String.Empty
        Yminus.Text = CStr(MyAngleY) & "°"
      End If
 
      '●再生するサウンドファイル名を設定
      MySoundTitle = SoundPropertyX & SoundPropertyY
      MySoundFileName = MySoundDirectory & "/" & MySoundTitle & ".wav"
 
      '●X方向またはY方向の角度がレンジ設定の角度以上に傾いた時の処理
      If MyAngleX >= MySetAngle OrElse MyAngleY >= MySetAngle Then
 
        '●センサーの加速度取得を停止
        MyAccelerometer.Stop()
        MyAccelerometer = Nothing
 
       '●角度と音声ファイル名を取得してセンサーを停止した後、
       '●表示と音声再生に利用するプラスマイナス付きの角度を初期化
       MyChannelAngleX = 0
       MyChannelAngleY = 0
 
       '●音声ファイル名によって、針の角度を決定
       Select Case MySoundTitle
         Case "RightMiddle"
           MyHandAngle = 0
           MyMessageText = "Slightly lift the right side."
           HandImage.Visibility = Windows.Visibility.Visible
           CenterMiddleImage.Visibility = Windows.Visibility.Collapsed
           Exit Select
 
         Case "RightHigh"
           If MyAngleX > MyAngleY Then
             MyHandAngle = 30
           Else
              MyHandAngle = 60
           End If
           MyMessageText = "Slightly lift the top and the right side."
           HandImage.Visibility = Windows.Visibility.Visible
           CenterMiddleImage.Visibility = Windows.Visibility.Collapsed
           Exit Select
 
         Case "CenterHigh"
           MyHandAngle = 90
           MyMessageText = "Slightly lift the top."
           HandImage.Visibility = Windows.Visibility.Visible
           CenterMiddleImage.Visibility = Windows.Visibility.Collapsed
           Exit Select
 
         Case "LeftHigh"
           If MyAngleX < MyAngleY Then
             MyHandAngle = 120
           Else
             MyHandAngle = 150
           End If
           MyMessageText = "Slightly lift the top and the left side."
           HandImage.Visibility = Windows.Visibility.Visible
           CenterMiddleImage.Visibility = Windows.Visibility.Collapsed
           Exit Select
 
         Case "LeftMiddle"
           MyHandAngle = 180
           MyMessageText = "Slightly lift the left side."
           HandImage.Visibility = Windows.Visibility.Visible
           CenterMiddleImage.Visibility = Windows.Visibility.Collapsed
           Exit Select
 
         Case "LeftLow"
           If MyAngleX > MyAngleY Then
             MyHandAngle = 210
           Else
             MyHandAngle = 240
           End If
           MyMessageText = "Slightly lift the bottom and the left side."
           HandImage.Visibility = Windows.Visibility.Visible
           CenterMiddleImage.Visibility = Windows.Visibility.Collapsed
           Exit Select
 
         Case "CenterLow"
           MyHandAngle = 270
           MyMessageText = "Slightly lift the bottom."
           HandImage.Visibility = Windows.Visibility.Visible
           CenterMiddleImage.Visibility = Windows.Visibility.Collapsed
           Exit Select
 
         Case "RightLow"
           If MyAngleX < MyAngleY Then
             MyHandAngle = 300
           Else
             MyHandAngle = 330
           End If
           MyMessageText = "Slightly lift the bottom and the right side."
           HandImage.Visibility = Windows.Visibility.Visible
           CenterMiddleImage.Visibility = Windows.Visibility.Collapsed
           Exit Select
 
         Case "CenterMiddle"
           MyHandAngle = 0
           MyMessageText = "OK. It's the origin."
           HandImage.Visibility = Windows.Visibility.Collapsed
           CenterMiddleImage.Visibility = Windows.Visibility.Visible
           Exit Select
        End Select
 
        '●決定した角度で針を表示
        myAngleProjection.RotationZ = MyHandAngle
   
        '●サウンドファイルを再生
        Dim MySoundFileUri As System.Windows.Resources.StreamResourceInfo
        MySoundFileUri = App.GetResourceStream(New Uri(MySoundFileName, UriKind.Relative))
        MySound = SoundEffect.FromStream(MySoundFileUri.Stream)
        MySoundInstance = MySound.CreateInstance()
        SoundEffect.MasterVolume = CSng(MyVolume)
        MySoundInstance.Play()
 
        '●メッセージを表示して「OK」ボタンを表示
        MessageTextBlock.Text = MyMessageText
        FinishButton.Visibility = Windows.Visibility.Visible
        FinishButton.IsEnabled = True
      End If
    Catch
      Exit Sub
    End Try
  End Sub

(7)「OK」ボタンがタップされた時の処理

  Private Sub FinishButton_Tap(sender As Object, e As System.Windows.Input.GestureEventArgs) Handles FinishButton.Tap
  
    '●サウンドを停止してインスタンスを破棄
    MySoundInstance.Stop()
    MySound.Dispose()
 
    '●「OK」ボタンを非表示にする
    FinishButton.Visibility = Windows.Visibility.Collapsed
    FinishButton.IsEnabled = False
 
    '●結果表示ウィンドウを非表示にして設定画面を表示
    StatePanel.Visibility = Windows.Visibility.Collapsed
    PreferencesPanel.Visibility = Windows.Visibility.Visible
  
    '●結果表示ウィンドウ内のパーツを非表示にして値を初期化
    HandImage.Visibility = Windows.Visibility.Collapsed
    MyHandAngle = 0
    CenterMiddleImage.Visibility = Windows.Visibility.Collapsed
    MessageTextBlock.Text = String.Empty
 
    MyChannelAngleX = 0
    MyChannelAngleY = 0
  End Sub

(8)ページがアンロードされた時の処理

  Private Sub MyAccelerometer_Unloaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Unloaded
    MyChannelAngleX = 0
    MyChannelAngleY = 0
  End Sub

End Class<

動作確認

処理が完成したところで、ヘルプページを作成する前に動作確認を行います。まず、エミュレーターで確認します。

任意のレンジを設定し、ヘッドフォンを接続し、サウンドやボリュームを設定して、センサーのシミュレーターを表示します(図16)。この例では、レンジに「35°」を設定し、サウンドは「Tweet(森の中の小鳥)」、ボリュームは「8」に設定しています。

 図16:エミュレーターのセンサー・シミュレーターを表示する(クリックで拡大)

実機の頭側を上にして見降ろした形でのシミュレートができる「Portrait Flat」モードを選択します(図17)。

 図17:「Portrait Flat」モードを選択する(クリックで拡大)

「START」ボタンをクリックして、シミュレーター内のWindows Phoneを動かします。花の絵のX軸、Y軸の傾いている方向に角度が表示されます。上下左右のいずれかの方向に、レンジに設定した角度よりも大きく傾いた時、青い花びらが傾いた方向を指し、メッセージが表示されます。

この図の例では、前掲の図17でレンジに「35°」を設定しており、左側に35°以上傾いたので、傾いた方向を青い花びらが指し、「こころもち左上を上げてください」というメッセージが表示されています。この例では、サウンドに「Tweet」を選択しており、左上に傾いているので、左チャンネルで高いピッチのTweetが再生されます。つまり、左耳に、高い小鳥のさえずりが聴こえます(図18)。

 図18:エミュレーターでWindows Phoneを動かして動作を確認する(クリックで拡大)

シミュレーターのWindows Phoneの向きをリセットし(図19)、「OK」ボタンをクリックして設定画面に戻ります。

設定はそのままで再度「START」をクリックして、いろいろな方向に傾けて確認します。また、いろいろなレンジ、いろいろなサウンドを指定して確認します。

 図19:シミュレーターのWindows Phoneの向きをリセットする(クリックで拡大)

エミュレーターで確認できたら、実機で確認します。実機をPCに接続し、ZUNEが起動して実機の電源がONになっていることを確認して、「Windows Phone」を選択してデバッグします(図20)。確認できたら、ZUNEを閉じて「ハードウェアの安全な取り外し」でIS12Tを取り外し、持ち歩いて、床や階段などの傾斜も測ってみてください。ちなみに筆者のデスクの上を測ったところ、右へ2°、手前に1°傾いていました。

 図20:エミュレーターで確認し、実機でも確認する(クリックで拡大)

ヘルプの作成と申請

最後にヘルプページ(MyClinometerHelp.xaml)をPanorama Pageで作成します。

要素の兄要素としてImageコントロールをレイアウトし、これに背景画像を指定すると(リスト7)、フリックしてどのページが表示されても、背景画像は一定を保ちます(図21)。

リスト7 要素の兄要素に背景画像を指定する(MyClinometerHelp.xaml)

  <Grid x:Name="LayoutRoot" Background="#FFFFFFFF">
    <Image Height="768" HorizontalAlignment="Left" Margin="0,0,0,0" Name="HeloBackground" Stretch="Fill" VerticalAlignment="Top" Width="480" Source="/SoundClinometer;component/Image/HelpBack.png" />
    <controls:Panorama Height="750" HorizontalAlignment="Left" Margin="10,10,0,0" Name="Panorama1" Title="" VerticalAlignment="Top" >
 図21:1枚の背景画像を設定しておけば、フリックしてどのページが表示されても同じ背景になる(クリックで拡大)

このヘルプには、テキストでの説明を補うため、図を多用しています。なぜなら、マーケットプレイス認定条件にのっとった提出用のキャプチャ方法では、エミュレーターで直接キャプチャできない説明用の図は使えないからです。

そこで、「第2回 Windows Phoneアプリを申請する流れを確認しておこう」の図12のように、処理中の画面ではなく、ヘルプの画面をキャプチャして申請しました。この方法ならば、ヘルプのページもアプリの一画面ですから、問題なく認定されます。

なお、先に解説した2本の俳句アプリは「エンターテイメント」カテゴリで申請していますが、今回は、「ツール」カテゴリで申請しました。現在のところ、海外で毎日少しずつコンスタントにダウンロードされています。

以上で、3本のアプリの解説は終わりです。Windows Phone アプリの開発の流れは、把握していただけたことと思います。皆さんも、ぜひ、気軽にアプリを開発して公開してみてください。また、今回紹介したアプリはSilverlightですが、Xna Frameworkを使ったゲームを発信するのも有望だと思います。

最終回の次回は、この12回で伝えきれなかった雑多な話題を取り上げます。

【関連リンク】

開発に役立つリンク集を紹介します。

開発者向け技術情報サイト MSDN
全ての情報は、ここから。
App Hub / Windows Phone マーケットプレイス Windows Phone フォーラム
App Hub の利用や Windows Phone マーケットプレイスに関する話題や情報交換の場です。
UX-TV
アプリケーション開発に関する情報をエバンジェリストの方々が定期的に発信しています。Windows Phone開発に関する番組が充実しています。
Windows Phone公式サイト(Microsoft)
Windows Phone 開発者向け技術情報
facebook 公式 Fan Page「Windows Phone Japan」
Windows Phone 関連ブログ
コミュニティ: Windows Phone Arch

<リンク先最終アクセス:2012.04>

<編集部より>リスト5とリスト6の名称を修正しました(処理には影響ありません)。(2012.05.14)

  • 「Sound Clinometer Ver.0.8」プロジェクト一式

四国のSOHO。薬師寺国安(VBプログラマ)と、薬師寺聖(デザイナ、エンジニア)によるコラボレーション・ユニット。1997年6月、Dynamic HTMLとDirectAnimationの普及を目的として結成。共同開発やユニット名義での執筆活動を行う。XMLおよび.NETに関する著書や連載多数。最新刊は「Silverlight実践プログラミング」両名とも、Microsoft MVP for Development Platforms - Client App Dev (Oct 2003-Sep 2012)。http://www.PROJECTKySS.NET/

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています