Chapter 46. Noise Maker

image

image

Noise Maker makes a loud, annoying noise when you shake the phone. This app is meant for sporting events, much like the vuvuzelas that became infamous during the 2010 FIFA World Cup. You can use it to help cheer on your team, or to distract the opposition. The main lesson of this chapter—figuring out when the phone is being shaken—can be useful for many apps. You might wish to use the same shake-detection algorithm for more serious apps, perhaps as a gesture to refresh data.

The Main Page

Noise Maker has a main page, a settings page, and an about page (not shown in this chapter). Listing 46.1 contains the XAML for the main page. This produces the user interface shown to the right. Listing 46.2 contains its code-behind.

Listing 46.1 MainPage.xaml—The User Interface for Noise Maker’s Main Page


<phone:PhoneApplicationPage
    x:Class="WindowsPhoneApp.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    SupportedOrientations="Portrait">

  <!-- The application bar, with two menu items -->
  <phone:PhoneApplicationPage.ApplicationBar>
    <shell:ApplicationBar>
      <shell:ApplicationBar.MenuItems>
        <shell:ApplicationBarMenuItem Text="settings"
                                      Click="SettingsMenuItem_Click"/>
        <shell:ApplicationBarMenuItem Text="about" Click="AboutMenuItem_Click"/>
      </shell:ApplicationBar.MenuItems>
    </shell:ApplicationBar>
  </phone:PhoneApplicationPage.ApplicationBar>

  <StackPanel>
    <!-- An accent-colored image -->
    <Rectangle Fill="{StaticResource PhoneAccentBrush}" Width="456" Height="423"
               Margin="{StaticResource PhoneMargin}">
      <Rectangle.OpacityMask>
        <ImageBrush ImageSource="Images/logo.png"/>
      </Rectangle.OpacityMask>
    </Rectangle>
    <!-- Accent-colored rotated text -->
    <TextBlock FontFamily="Segoe WP Black" Text="SHAKE TO MAKE NOISE!"
               FontSize="100" Foreground="{StaticResource PhoneAccentBrush}"
               TextWrapping="Wrap" TextAlignment="Center" Margin="0,20,0,0"
               LineHeight="80" LineStackingStrategy="BlockLineHeight"
               RenderTransformOrigin=".5,.5">
      <TextBlock.RenderTransform>
        <RotateTransform Angle="-10"/>
      </TextBlock.RenderTransform>
    </TextBlock>
  </StackPanel>
</phone:PhoneApplicationPage>


Listing 46.2 MainPage.xaml.cs—The Code-Behind for Noise Maker’s Main Page


using System;
using System.Windows;
using System.Windows.Navigation;
using Microsoft.Devices.Sensors;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;

namespace WindowsPhoneApp
{
  public partial class MainPage : PhoneApplicationPage
  {
    Accelerometer accelerometer;

    public MainPage()
    {
      InitializeComponent();

      // Initialize the accelerometer
      this.accelerometer = new Accelerometer();
      this.accelerometer.ReadingChanged += Accelerometer_ReadingChanged;

      SoundEffects.Initialize();

      // Allow the app to run (producing sounds) even when the phone is locked.
      // Once disabled, you cannot re-enable the default behavior!
      PhoneApplicationService.Current.ApplicationIdleDetectionMode =
        IdleDetectionMode.Disabled;
    }

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
      base.OnNavigatedTo(e);

      // Start the accelerometer
      try
      {
        this.accelerometer.Start();
      }
      catch
      {
        MessageBox.Show(
        "Unable to start your accelerometer. Please try running this app again.",
        "Accelerometer Error", MessageBoxButton.OK);
      }
    }

    protected override void OnNavigatedFrom(NavigationEventArgs e)
    {
      base.OnNavigatedTo(e);

      // Stop the accelerometer
      try
      {
        this.accelerometer.Stop();
      }
      catch { /* Nothing to do */ }
    }

    // Process data coming from the accelerometer
    void Accelerometer_ReadingChanged(object sender,
                                      AccelerometerReadingEventArgs e)
    {
      if (ShakeDetection.JustShook(e))
      {
        // We're on a different thread, so transition to the UI thread
        this.Dispatcher.BeginInvoke(delegate()
        {
          // Play each sound, which builds on top
          // of previously-playing sound effects
          if (Settings.IsLowChosen.Value)
            SoundEffects.Low.Play();
          if (Settings.IsMediumChosen.Value)
            SoundEffects.Medium.Play();
          if (Settings.IsHighChosen.Value)
            SoundEffects.High.Play();
        });
      }
    }

    // Application bar handlers

    void SettingsMenuItem_Click(object sender, EventArgs e)
    {
      this.NavigationService.Navigate(new Uri("/SettingsPage.xaml",
        UriKind.Relative));
    }

    void AboutMenuItem_Click(object sender, EventArgs e)
    {
        this.NavigationService.Navigate(new Uri("/AboutPage.xaml",
          UriKind.Relative));
    }
  }
}


Notes:

→ This listing makes use of three persisted settings defined in a separate Settings.cs file as follows:

public static class Settings
{
  public static readonly Setting<bool> IsLowChosen =
    new Setting<bool>("IsLowChosen", false);
  public static readonly Setting<bool> IsMediumChosen =
    new Setting<bool>("IsMediumChosen", true);
  public static readonly Setting<bool> IsHighChosen =
    new Setting<bool>("IsHighChosen", false);
}

→ The SoundEffects class used by this app is exactly like the one used in Chapter 44, “Boxing Glove,” but with Low, Medium, and High properties that expose the audio from three .wav files with the same names.

→ This app supports playing one, two, or three sounds simultaneously when a shake is detected. Any previously playing sound effects are not stopped by the calls to Play. The resulting additive effect means that shaking more vigorously (technically, more frequently) causes the sound to be louder.

→ The shake detection is done with a simple JustShook method defined in Listing 46.3.

Listing 46.3 ShakeDetection.cs—The Shake Detection Algorithm


using System;
using Microsoft.Devices.Sensors;

namespace WindowsPhoneApp
{
  public static class ShakeDetection
  {
    static AccelerometerReadingEventArgs previousData;
    static int numShakes;

    // Two properties for controlling the algorithm
    public static int RequiredConsecutiveShakes { get; set; }
    public static double Threshold { get; set; }

    static ShakeDetection()
    {
      RequiredConsecutiveShakes = 1;
      Threshold = .7;
    }

    // Call this with the accelerometer data
    public static bool JustShook(AccelerometerReadingEventArgs e)
    {
      if (previousData != null)
      {
        if (IsShaking(previousData, e, Threshold))
        {
          numShakes++;
          if (numShakes == RequiredConsecutiveShakes)
          {
            // Just shook!
            numShakes = 0;
            return true;
          }
        }
        else if (!IsShaking(previousData, e, .2))
          numShakes = 0;
      }

      previousData = e;
      return false;
    }

    // It's a shake if the values in at least two dimensions
    // are different enough from the previous values
    static bool IsShaking(AccelerometerReadingEventArgs previous,
      AccelerometerReadingEventArgs current, double threshold)
    {
      double deltaX = Math.Abs(previous.X - current.X);
      double deltaY = Math.Abs(previous.Y - current.Y);
      double deltaZ = Math.Abs(previous.Z - current.Z);

      return (deltaX > threshold && deltaY > threshold) ||
             (deltaY > threshold && deltaZ > threshold) ||
             (deltaX > threshold && deltaZ > threshold);
    }
  }
}


→ The core part of the algorithm—the IsShaking method—simply checks for two out of the three data points being sufficiently different from the previous set of data points. “Sufficiently different” is determined by a threshold that defaults to .7 but can be changed by the consumer of this class.

JustShook keeps track of the previous data and calls IsShaking. It uses the RequiredConsecutiveShakes property to support specifying a number of consecutive shakes required before considering that a shake has happened.

→ Although it’s a good idea for apps to provide calibration functionality when the accelerometer is used, it’s not really necessary for something as coarse as shake detection. Any differences between phones are not likely to be noticed.

The Settings Page

The settings page, shown in Figure 46.1, enables the user to turn on/off any of the three noises with simple check boxes. Listing 46.4 contains the XAML, and Listing 46.5 contains the code-behind.

Figure 46.1 The settings page enables customization of the sound effects.

image

Listing 46.4 SettingsPage.xaml—The User Interface for Noise Maker’s Settings Page


<phone:PhoneApplicationPage
    x:Class="WindowsPhoneApp.SettingsPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:local="clr-namespace:WindowsPhoneApp"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="PortraitOrLandscape" shell:SystemTray.IsVisible="True">
  <Grid Background="{StaticResource PhoneBackgroundBrush}">
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <!-- The standard settings header -->
    <StackPanel Grid.Row="0" Style="{StaticResource PhoneTitlePanelStyle}">
      <TextBlock Text="SETTINGS" Style="{StaticResource PhoneTextTitle0Style}"/>
      <TextBlock Text="noise maker"
                 Style="{StaticResource PhoneTextTitle1Style}"/>
    </StackPanel>

    <ScrollViewer Grid.Row="1">
      <StackPanel Margin="{StaticResource PhoneMargin}">
        <TextBlock Text="Choose one, two, or three noises"
                   Foreground="{StaticResource PhoneSubtleBrush}"
                   Margin="{StaticResource PhoneMargin}"/>
        <CheckBox x:Name="LowCheckBox" Content="low"
          Checked="CheckBox_IsCheckedChanged"
          Unchecked="CheckBox_IsCheckedChanged" local:Tilt.IsEnabled="True"/>
        <CheckBox x:Name="MediumCheckBox" Content="medium"
          Checked="CheckBox_IsCheckedChanged"
          Unchecked="CheckBox_IsCheckedChanged" local:Tilt.IsEnabled="True"/>
        <CheckBox x:Name="HighCheckBox" Content="high"
          Checked="CheckBox_IsCheckedChanged"
          Unchecked="CheckBox_IsCheckedChanged" local:Tilt.IsEnabled="True"/>

        <!-- A warning for when no sounds are checked -->
        <StackPanel x:Name="WarningPanel" Visibility="Collapsed"
                    Orientation="Horizontal" Margin="12,4,0,0">
          <!-- Use the image as an opacity mask for the rectangle, so the image
               visible in both dark and light themes -->
          <Rectangle Fill="{StaticResource PhoneForegroundBrush}"
                     Width="48" Height="48">
            <Rectangle.OpacityMask>
              <ImageBrush ImageSource="Shared/Images/normal.error.png"/>
            </Rectangle.OpacityMask>
          </Rectangle>

          <TextBlock Text="No sounds will be made unless you check at least one!"
                     TextWrapping="Wrap" Width="350"
                     Margin="{StaticResource PhoneMargin}"/>
        </StackPanel>
      </StackPanel>
    </ScrollViewer>
  </Grid>
</phone:PhoneApplicationPage>


Listing 46.5 SettingsPage.xaml.cs—The Code-Behind for Noise Maker’s Settings Page


using System.Windows;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;

namespace WindowsPhoneApp
{
  public partial class SettingsPage : PhoneApplicationPage
  {
    public SettingsPage()
    {
      InitializeComponent();
      ShowOrHideWarning();
    }

    protected override void OnNavigatedFrom(NavigationEventArgs e)
    {
      base.OnNavigatedFrom(e);
      // Save the settings
      Settings.IsLowChosen.Value = this.LowCheckBox.IsChecked.Value;
      Settings.IsMediumChosen.Value = this.MediumCheckBox.IsChecked.Value;
      Settings.IsHighChosen.Value = this.HighCheckBox.IsChecked.Value;
    }

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
      base.OnNavigatedTo(e);
      // Respect the settings
      this.LowCheckBox.IsChecked = Settings.IsLowChosen.Value;
      this.MediumCheckBox.IsChecked = Settings.IsMediumChosen.Value;
      this.HighCheckBox.IsChecked = Settings.IsHighChosen.Value;
    }

    void CheckBox_IsCheckedChanged(object sender, RoutedEventArgs e)
    {
      ShowOrHideWarning();
    }

    void ShowOrHideWarning()
    {
      if (!this.LowCheckBox.IsChecked.Value &&
          !this.MediumCheckBox.IsChecked.Value &&
          !this.HighCheckBox.IsChecked.Value)
        this.WarningPanel.Visibility = Visibility.Visible;
      else
        this.WarningPanel.Visibility = Visibility.Collapsed;
    }
  }
}


Other than a straightforward mapping of three check boxes to three settings, this page contains a warning that gets shown when all three check boxes are unchecked. This is shown in Figure 46.2.

Figure 46.2 The settings page shows a warning if all check boxes are unchecked.

image

The Finished Product

image

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.105.108