Subservient Cat (Video)

Subservient Cat is a sort of “virtual pet” app. Unlike most cats, the subservient cat can actually obey commands! However, users have to figure out what commands the cat will respond to. It’s a bit of a game, because users must keep guessing to see how many commands they can discover.

This app uses video footage of a black cat (whose real name is Boo) as its primary user interface. Therefore, this is the perfect app for learning about MediaElement, the element for playing video inside apps.

Playing Video with MediaElement

If you want to enable users to play, pause, and otherwise control videos, your best option is to use the Media Player launcher. However, if you want to play video inline on your own page, MediaElement enables you to do so.

MediaElement is a UI element that renders the video specified via its Source property, for example:

[code]

<MediaElement Source=”cat.wmv”/>

[/code]

The source URI can point to a file included in your project or to an online video. By default, MediaElement automatically starts playing the video as soon as the element is rendered (or as soon as enough of the video has been downloaded, in the online case), but you can change this by setting its AutoPlay property to false. MediaElement has Play, Pause, and Stop methods that can be used from code-behind. It also has a Position property that reveals the current playback position (as a time span). If the video supports seeking, you can also set Position to move playback to that point in time. MediaElement can be transformed and clipped just like other Silverlight elements, and it can blended amongst other elements.

On the surface, it appears that MediaElement is straightforward to use. It has tons of caveats, however. Several warnings follow that describe some of the biggest caveats.

An app’s frame can only contain one MediaElement!

Attempting to use more than one MediaElement is not supported and fails in various ways.Note that the limitation is more strict than one per page; only one can be attached to the frame at any time. (It doesn’t matter if they’re stopped, paused, or playing.) Therefore, multiple pages can each have a MediaElement only if they never reside on the navigation stack at the same time. Otherwise, if you need to play multiple videos, you’ll either need to reuse the same MediaElement or remove the unused MediaElement from the element tree.

When including a video file in your app, make sure its Build Action is set to Content, not Resource!

This improves the performance of starting the video. When the video is embedded as a resource, it is first extracted and temporarily saved to isolated storage before it is played! (The same warning applies to audio files played by MediaElement, although this is not normally done.)

When MediaElement starts playing, any background audio (such as music playing from Zune) is paused!

This is the main reason that MediaElement should never be used for playing sound effects. Note that this is true even if your video contains no audio.

MediaElement doesn’t work in the emulator under the light theme!

It’s strange, but true.To test your use of a MediaElement on the emulator, you must ensure that it is running under the dark theme.Don’t worry; this bug doesn’t affect real phones.

MediaElement doesn’t render fully opaque!

If any elements exist underneath a MediaElement, you can see them clearly through the video, even when MediaElement’s Opacity property is set to 1 (as it is by default)! This is an anomaly with the composition between the phone’s media player (which internally renders MediaElement’s video) and the rest of Silverlight.

See “Supported Media Codecs forWindows Phone” (http://goo.gl/6NhuD) for details about the video formats supported by MediaElement and the “Recommended Video Encoding Settings” section of http://goo.gl/ttPkO for more details about what encodings work best. If you use Expression Encoder, you can encode videos with a preconfigured profile optimized for Windows Phone (and Zune HD).

The User Interface

The Subservient Cat app uses a single page—MainPage—in addition to its instructions page (not shown in this chapter). The XAML for MainPage is shown in Listing 33.1. The root grid contains three distinct pieces of user interface, all shown in Figure 33.1:

  • The MediaElement that contains the video
  • A simple “intro screen” that introduces each command done by the cat before the corresponding video clip plays
  • A panel with a text box for guessing new commands
The three main components of the user interface on the main page.
FIGURE 33.1 The three main components of the user interface on the main page.

LISTING 33.1 MainPage.xaml—The User Interface for Subservient Cat’s Main Page

[code]

<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”
FontFamily=”{StaticResource PhoneFontFamilyNormal}”
FontSize=”{StaticResource PhoneFontSizeNormal}”
Foreground=”{StaticResource PhoneForegroundBrush}”
SupportedOrientations=”Landscape” Orientation=”Landscape”>
<!– The application bar–>
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar Opacity=”.8”>
<shell:ApplicationBarIconButton Text=”command”
IconUri=”/Shared/Images/appbar.command.png”
Click=”CommandButton_Click”/>
<shell:ApplicationBarIconButton Text=”instructions”
IconUri=”/Shared/Images/appbar.instructions.png”
Click=”InstructionsButton_Click”/>
<shell:ApplicationBarIconButton Text=”discovered”
IconUri=”/Shared/Images/appbar.1.png”
Click=”DiscoveredButton_Click”/>
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
<Grid>
<!– The video, scaled to fill the page without visible letterboxing –>
<MediaElement x:Name=”MediaElement” Source=”cat.wmv” Stretch=”None”
Volume=”1” MediaOpened=”MediaElement_MediaOpened”
MediaFailed=”MediaElement_MediaFailed”>
<MediaElement.RenderTransform>
<CompositeTransform TranslateY=”-115” TranslateX=”-150”
ScaleX=”1.68” ScaleY=”1.68”/>
</MediaElement.RenderTransform>
</MediaElement>
<!– Something to show on top of the video during seeking –>
<Border x:Name=”IntroScreen” Background=”#6E5962” Visibility=”Collapsed”>
<Grid>
<Image Source=”Images/paw.png” Stretch=”None” HorizontalAlignment=”Left”
VerticalAlignment=”Top” Margin=”100,100,0,0”/>
<Image Source=”Images/paw.png” Stretch=”None” HorizontalAlignment=”Left”
VerticalAlignment=”Top” Margin=”230,50,0,0”/>
<Image Source=”Images/paw.png” Stretch=”None” HorizontalAlignment=”Left”
VerticalAlignment=”Top” Margin=”628,300,0,0”/>
<Image Source=”Images/paw.png” Stretch=”None” HorizontalAlignment=”Left”
VerticalAlignment=”Top” Margin=”528,350,0,0”/>
<TextBlock x:Name=”NextCommandTextBlock” Foreground=”Black” FontSize=”40”
VerticalAlignment=”Center” HorizontalAlignment=”Center”/>
</Grid>
</Border>
<!– The user interface for typing new commands –>
<Grid x:Name=”CommandPanel”
Background=”#A000”
Visibility=”Collapsed” VerticalAlignment=”Bottom”>
<Grid.RowDefinitions>
<RowDefinition/>
<!– A bottom margin, so the auto-scroll while the textbox has focus
doesn’t show extra space below the video –>
<RowDefinition Height=”84”/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=”Auto”/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image Source=”/Shared/Images/appbar.command.png” Stretch=”None”
Margin=”70,0,0,0”/>
<Rectangle Grid.Column=”1” Fill=”White” Margin=”12,12,82,12”/>
<TextBox x:Name=”CommandTextBox” Grid.Column=”1” InputScope=”Text”
Margin=”0,0,70,0” TextChanged=”CommandTextBox_TextChanged”
LostFocus=”CommandTextBox_LostFocus”/>
</Grid>
</Grid>
</phone:PhoneApplicationPage>

[/code]

  • The video is landscape-oriented, so this is a landscape-only page. However, because a text box is used for guessing new commands, the code-behind temporarily changes SupportedOrientations to allow any orientation while the text box has focus. This way, phones with portrait hardware keyboards still get a good experience.
  • The application bar has three buttons: one for revealing the command input panel, one for navigating to the instructions page, and one whose icon reveals how many commands have been discovered (updated in code-behind). Tapping this last button also tells you whether there are more commands to be discovered, as the total number is a secret. The application bar menu, dynamically filled from code-behind, contains the list of discovered commands and makes the cat perform each one when tapped. This is shown in Figure 33.2.
The application bar menu provides quick access to the list of already-discovered commands, such as “yawn.”
FIGURE 33.2 The application bar menu provides quick access to the list of already-discovered commands, such as “yawn.”
  • Although the app appears to play several different short videos, it actually uses a single longer video (cat.wmv) for performance reasons. The code-behind is responsible for playing the appropriate segments of the video at the appropriate times.
  • The MediaElement is moved and enlarged with a CompositeTransform because the source cat.wmv file has black bars along the top and bottom that we don’t want to see on the screen.
  • MediaElement’s Volume property (a value from 0 to 1) is set to 1 (the loudest possible volume) because the default value is .85. Although the sound can only be as loud as the user’s volume level, this helps ensure that the tiny bit of audio in this app’s video (a short meow) can be heard.

Be sure to give your MediaElement a name!

If you don’t, it’s possible that the marketplace publishing process won’t detect your use of MediaElement, and therefore will not grant your app the “media library” capability that is necessary for your app to work.

The Code-Behind

Listing 33.2 contains the code-behind for the main page.

LISTING 33.2 MainPage.xaml.cs—The Code-Behind for Subservient Cat’s Main Page

[code]

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Navigation;
using System.Windows.Threading;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
namespace WindowsPhoneApp
{
public partial class MainPage : PhoneApplicationPage
{
// Captures the slice in the big video where each command resides
class VideoClip
{
public TimeSpan Start;
public TimeSpan End;
}
DispatcherTimer videoTimer = new DispatcherTimer();
DispatcherTimer delayTimer = new DispatcherTimer();
VideoClip pendingNewClip;
Dictionary<string, VideoClip> possibleCommands =
new Dictionary<string, VideoClip>();
Dictionary<string, string> aliases = new Dictionary<string, string>();
// Start users off with one known command: yawn
Setting<List<string>> discoveredCommands =
new Setting<List<string>>(“DiscoveredCommands”,
new List<string>(new string[] { “yawn” }));
IApplicationBarIconButton discoveredButton;
public MainPage()
{
InitializeComponent();
this.discoveredButton = this.ApplicationBar.Buttons[2]
as IApplicationBarIconButton;
this.videoTimer.Tick += VideoTimer_Tick;
this.delayTimer.Tick += DelayTimer_Tick;
// All the commands and their positions in the video
this.possibleCommands.Add(“yawn”, new VideoClip {
Start = TimeSpan.FromSeconds(98.36),
End = TimeSpan.FromSeconds(101.28) });
this.possibleCommands.Add(“meow”, new VideoClip {
Start = TimeSpan.FromSeconds(79.4),
End = TimeSpan.FromSeconds(81.863) });

// Permitted variations for each command
this.aliases.Add(“yawn”, “yawn”);
this.aliases.Add(“meow”, “meow”);
this.aliases.Add(“speak”, “meow”);
this.aliases.Add(“talk”, “meow”);
this.aliases.Add(“say your name”, “meow”);

}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Fill the application bar menu with all previously-discovered commands
this.ApplicationBar.MenuItems.Clear();
foreach (string command in this.discoveredCommands.Value)
{
ApplicationBarMenuItem item = new ApplicationBarMenuItem(command);
item.Click += ApplicationBarMenuItem_Click;
this.ApplicationBar.MenuItems.Add(item);
}
// Show how many commands have been discovered on the button imag
this.discoveredButton.IconUri = new Uri(“/Shared/Images/appbar.”
+ this.discoveredCommands.Value.Count + “.png”, UriKind.Relative);
}
void MediaElement_MediaOpened(object sender, RoutedEventArgs e)
{
// Play a short intro clip
this.videoTimer.Interval = TimeSpan.FromSeconds(1.48);
this.videoTimer.Start();
}
void MediaElement_MediaFailed(object sender, ExceptionRoutedEventArgs e)
{
MessageBox.Show(“To see the subservient cat, please disconnect your “ +
“phone from Zune.”, “Please Disconnect”, MessageBoxButton.OK);
}
void PlayClip(string title, TimeSpan beginTime, TimeSpan endTime)
{
// Set up the timer to stop playback approximately when endTime is reached
this.videoTimer.Stop();
this.videoTimer.Interval = endTime – beginTime;
// Hide the video and show the intro screen
this.MediaElement.Pause();
this.NextCommandTextBlock.Text = title;
this.IntroScreen.Visibility = Visibility.Visible;
// Give the intro screen a chance to show before doing the following work
this.Dispatcher.BeginInvoke(delegate()
{
// Delay the reappearance of the video until after the seek completes
this.delayTimer.Interval = TimeSpan.FromSeconds(2);
this.delayTimer.Start();
// Seek to the correct spot in the video
this.MediaElement.Position = beginTime;
});
}
void VideoTimer_Tick(object sender, EventArgs e)
{
// We’ve reached the end of the current clip, so pause the video
this.MediaElement.Pause();
// Prevent the timer from continuing to tick
this.videoTimer.Stop();
}
void DelayTimer_Tick(object sender, EventArgs e)
{
// This timer is used for two reasons, either to delay the execution of a
// new command after typing it in, or to delay the beginning of playing a
// video clip after the intro screen is shown.
if (this.IntroScreen.Visibility == Visibility.Collapsed)
{
// This is the execution of a new command
string text = this.CommandTextBox.Text;
this.CommandTextBox.Foreground = new SolidColorBrush(Colors.Black);
this.CommandTextBox.Text = “”;
this.Focus(); // Closes the command input panel
PlayClip(text.ToLowerInvariant(), this.pendingNewClip.Start,
this.pendingNewClip.End);
}
else
{
// We’re ready to actually play the video clip
this.videoTimer.Start();
this.MediaElement.Play();
this.IntroScreen.Visibility = Visibility.Collapsed;
}
// Prevent the timer from continuing to tick
this.delayTimer.Stop();
}
void CommandTextBox_LostFocus(object sender, RoutedEventArgs e)
{
// Restore the page to landscape-only and hide the command input panel
this.SupportedOrientations = SupportedPageOrientation.Landscape;
this.CommandPanel.Visibility = Visibility.Collapsed;
}
void CommandTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
string text = this.CommandTextBox.Text.Trim().ToLowerInvariant();
// Don’t bother checking when the text is shorter than any valid command
if (text.Length < 3)
return;
if (this.aliases.ContainsKey(text))
{
string commandName = this.aliases[text];
// Only acknowledge the command if it’s not already in the list
if (!this.discoveredCommands.Value.Contains(text))
{
// Signal a successful guess
this.CommandTextBox.Foreground =
Application.Current.Resources[“PhoneAccentBrush”] as Brush;
this.pendingNewClip = this.possibleCommands[commandName];
// Append the new command to the application bar menu
ApplicationBarMenuItem item = new ApplicationBarMenuItem(text);
item.Click += ApplicationBarMenuItem_Click;
this.ApplicationBar.MenuItems.Add(item);
// Record the discovered command
this.discoveredCommands.Value.Add(text);
this.discoveredButton.IconUri = new Uri(“/Shared/Images/appbar.” +
this.discoveredCommands.Value.Count + “.png”, UriKind.Relative);
// Wait a second before hiding the text box and showing the intro screen
this.delayTimer.Interval = TimeSpan.FromSeconds(1);
this.delayTimer.Start();
}
}
}
// Application bar handlers
void ApplicationBarMenuItem_Click(object sender, EventArgs e)
{
IApplicationBarMenuItem item = sender as IApplicationBarMenuItem;
// Grab the right clip based on the menu item’s text
VideoClip command = this.possibleCommands[this.aliases[item.Text]];
this.Focus(); // In case the command input panel is currently showing
PlayClip(item.Text, command.Start, command.End);
}
void CommandButton_Click(object sender, EventArgs e)
{
// Temporarily allow a portrait orientation while the text box is in use
this.SupportedOrientations = SupportedPageOrientation.PortraitOrLandscape;
this.CommandPanel.Visibility = Visibility.Visible;
// Enable automatic deployment of the software keyboard
this.CommandTextBox.Focus();
this.CommandTextBox.SelectAll();
}
void InstructionsButton_Click(object sender, EventArgs e)
{
this.NavigationService.Navigate(new Uri(“/InstructionsPage.xaml”,
UriKind.Relative));
}
void DiscoveredButton_Click(object sender, EventArgs e)
{
// Pause, otherwise the video will keep playing while the message box is
// shown and the timer Tick handler won’t be raised to stop it!
this.MediaElement.Pause();
if (this.discoveredCommands.Value.Count == this.possibleCommands.Count)
{
MessageBox.Show(“Congratulations! You have discovered all the commands!”,
“Discovered Commands”, MessageBoxButton.OK);
}
else if (this.discoveredCommands.Value.Count == 1)
{
MessageBox.Show(“You have discovered only one command, and it was given” +
“ to you! Keep trying! (You can see and use your command by tapping” +
“ ”…”)”, “Discovered Commands”, MessageBoxButton.OK);
}
else
{
MessageBox.Show(“You have discovered “ +
this.discoveredCommands.Value.Count + “ commands. (You can see them” +
“ and use them by tapping ”…”) There are more to discover!”,
“Discovered Commands”, MessageBoxButton.OK);
}
}
}
}

[/code]

  • The constructor populates possibleCommands with the list of commands along with their starting and ending times in the cat.wmv video. Because guessing each command by its exact name can be incredibly hard, an aliases dictionary is used that enables alternate forms of some commands, such as “speak” instead of “meow.”
  • In OnNavigatedTo, the application bar menu is filled with all previously discovered commands. These are stored in discoveredCommands, which is a Setting so it always gets persisted.
  • In order to show the number of discovered commands in the application bar button’s icon, several distinct images are included in this project: appbar.1.png, appbar.2.png, appbar.3.png, and so on. The right one is selected based on the Count of the discoveredCommands collection.
  • The video starts automatically playing when the page is loaded (because AutoPlay was not set to false in Listing 33.1), but we do not want the entire video to play and reveal all the cat’s actions. Instead, only the first second and a half should play. Therefore, in MediaElement’s MediaOpened event handler (raised when the media has loaded and is ready to start playing), videoTimer is used to pause the video after 1.48 seconds have elapsed. The pausing is done in videoTimer’s Tick event handler, VideoTimer_Tick.

You cannot call Play on a MediaElement until its MediaOpened event is raised!

When a MediaElement’s Source is set (either in XAML or code-behind), you cannot instantly begin interacting with the media. Instead, you must wait for MediaOpened to be raised. If the media cannot be loaded for some reason, a MediaFailed event is raised instead. Subservient Cat avoids the need to manually call Play because it uses the auto-play feature, but if it didn’t use auto-play, then it should call Play inside MediaElement_MediaOpened.

Why doesn’t my video play on a physical phone while it is connected to a computer running Zune?

This is due to the same problem discussed in the preceding chapter.The Zune desktop program locks the media library, which prevents MediaElement from loading its media. Remember, if you need to debug a part of your app that depends on video actually playing, you can use the Windows Phone Connect Tool that ships with the Windows Phone Developer Tools to connect to your phone without Zune running. Subservient Cat detects this situation by handling the MediaFailed event. It assumes the failure is due to the Zune connection, which is a pretty safe assumption because the source video is local to the app.

  • The PlayClip method ensures that the video is paused, seeks to the specified beginTime, and reinitializes videoTimer so it pauses the video at endTime. However, because setting MediaElement’s Position has an annoying effect of seeing the video quickly fastforward or rewind to the desired spot (rather than an instant jump), the intro screen is shown to hide the video during the transition. (We don’t want to reveal yet-to-be-discovered parts of the video!) The setting of Position is done inside a BeginInvoke callback so the showing of the intro screen has a chance to take effect. Without this, you still see the unwanted behavior. Two seconds is chosen as the length of the delay, during which the user can read the command text on the intro screen. We don’t have a way to know when seek has actually finished, but two seconds is long enough.

When you set MediaElement’s Position, the change is not instantaneous!

Instead, you see a little bit of the video before or after the new position, as if you’re watching the video in fast-forward or rewind mode.The workaround used by Subservient Cat is to temporarily obscure the video with other elements.

In the current version of Windows Phone, MediaElement does not support markers.The use of markers to designate when individual clips inside cat.wmv begin and end would have been ideal, and would have greatly reduced the amount of code needed in Listing 33.2. However, using a DispatcherTimer to be notified when the relevant clip has ended is a reasonable workaround.There are two things to be aware of, however:

  • The timer doesn’t give you frame-by-frame accuracy.The video used by this app has a little bit of a buffer at the end of each clip, in case videoTimer’s Tick event gets raised slightly late.
  • If you show a message box, the video will keep playing in the background but the timer’s Tick event handler cannot be called until the message box is dismissed, no matter how much time has elapsed. (MessageBox.Show is a blocking operation.) This is why DiscoveredButton_Click in Listing 33.2 pauses the video first.

When I first wrote Subservient Cat, I called MediaElement’s Stop method inside OnNavigatedFrom because I was worried about the performance impact of unnecessarily playing video while the instructions page is shown and the main page is on the back stack. However, this is unnecessary because MediaElement is automatically paused when a page navigates away. If you don’t want this behavior (perhaps because you want to hear the audio from the video continue while other pages are shown), the MediaElement must be attached to the frame rather than to a specific page.

MediaElement and Files in Isolated Storage

If you want to play a video from isolated storage (presumably because your app downloaded it and then saved it there), you can call MediaElement’s SetSource method that expects a stream rather than a URI. With this, you can pass an appropriate IsolatedStorageFileStream.

The Finished Product

Subservient Cat (Video)

 

Local FM Radio (Radio Tuner)

Local FM Radio provides a unique interface to your phone’s built-in FM radio tuner. Unlike the built-in radio app in the Music + Videos hub, this app enables you to directly type the frequency of your desired station. It also shows your current signal strength, which is an interesting little validation of any static you might be experiencing.

The purpose of this app is to demonstrate the phone’s simple but limited radio tuner API: the FMRadio class in the Microsoft.Devices.Radio namespace. Although the functionality exposed is very minimal, it comes with some nice perks, such as automatic integration into the Music + Video hub’s history/now playing lists.

The User Interface

As you can see from the screenshot to the right, this app’s user interface is a cross between Tip Calculator and Alarm Clock. Listing 32.1 contains the XAML for this app’s one and only page.

LISTING 32.1 MainPage.xaml—The User Interface for Local FM Radio

[code]

<phone:PhoneApplicationPage
x:Class=”WindowsPhoneApp.MainPage” x:Name=”Page”
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=”Portrait” shell:SystemTray.IsVisible=”True”>
<phone:PhoneApplicationPage.Resources>
<!– Style for calculator buttons –>
<Style x:Key=”CalculatorButtonStyle” TargetType=”Button”>
<Setter Property=”FontSize” Value=”36”/>
<Setter Property=”FontFamily”
Value=”{StaticResource PhoneFontFamilySemiLight}”/>
<Setter Property=”BorderThickness” Value=”0”/>
<Setter Property=”Width” Value=”132”/>
<Setter Property=”Height” Value=”108”/>
</Style>
</phone:PhoneApplicationPage.Resources>
<StackPanel Style=”{StaticResource PhoneTitlePanelStyle}”>
<TextBlock Text=”FM RADIO”
Style=”{StaticResource PhoneTextTitle0Style}”/>
<!– A user control much like the time display from Alarm Clock –>
<local:FrequencyDisplay x:Name=”FrequencyDisplay” Margin=”0,48”
HorizontalAlignment=”Center” FontSize=”220”/>
<!– The same calculator buttons from Tip Calculator, but with a
power button instead of a 00 button –>
<Canvas x:Name=”ButtonPanel” Height=”396” Margin=”-24,0”>
<Button Style=”{StaticResource CalculatorButtonStyle}”
Background=”{Binding CalculatorMainBrush, ElementName=Page}”
Content=”7” Canvas.Left=”-6” Canvas.Top=”-1”/>
<Button Style=”{StaticResource CalculatorButtonStyle}”
Background=”{Binding CalculatorMainBrush, ElementName=Page}”
Content=”8” Canvas.Left=”114” Canvas.Top=”-1”/>
<Button Style=”{StaticResource CalculatorButtonStyle}”
Background=”{Binding CalculatorMainBrush, ElementName=Page}”
Content=”9” Canvas.Left=”234” Canvas.Top=”-1”/>
<Button Style=”{StaticResource CalculatorButtonStyle}”
Background=”{Binding CalculatorMainBrush, ElementName=Page}”
Content=”4” Canvas.Top=”95” Canvas.Left=”-6”/>
<Button Style=”{StaticResource CalculatorButtonStyle}”
Background=”{Binding CalculatorMainBrush, ElementName=Page}”
Content=”5” Canvas.Top=”95” Canvas.Left=”114”/>
<Button Style=”{StaticResource CalculatorButtonStyle}”
Background=”{Binding CalculatorMainBrush, ElementName=Page}”
Content=”6” Canvas.Top=”95” Canvas.Left=”234”/>
<Button Style=”{StaticResource CalculatorButtonStyle}”
Background=”{Binding CalculatorMainBrush, ElementName=Page}”
Content=”1” Canvas.Top=”191” Canvas.Left=”-6”/>
<Button Style=”{StaticResource CalculatorButtonStyle}”
Background=”{Binding CalculatorMainBrush, ElementName=Page}”
Content=”2” Canvas.Top=”191” Canvas.Left=”114”/>
<Button Style=”{StaticResource CalculatorButtonStyle}”
Background=”{Binding CalculatorMainBrush, ElementName=Page}”
Content=”3” Canvas.Top=”191” Canvas.Left=”234”/>
<Button Style=”{StaticResource CalculatorButtonStyle}”
Background=”{Binding CalculatorMainBrush, ElementName=Page}”
Content=”0” Canvas.Top=”287” Canvas.Left=”-6”/>
<Button Style=”{StaticResource CalculatorButtonStyle}”
Background=”{Binding CalculatorMainBrush, ElementName=Page}”
Content=”power” Width=”252” Canvas.Top=”287” Canvas.Left=”114”/>
<Button Style=”{StaticResource CalculatorButtonStyle}” FontSize=”32”
FontFamily=”{StaticResource PhoneFontFamilySemiBold}”
Background=”{Binding CalculatorSecondaryBrush, ElementName=Page}”
Content=”C” Height=”204” Canvas.Top=”-1” Canvas.Left=”354”/>
<Button x:Name=”BackspaceButton” Height=”204”
Style=”{StaticResource CalculatorButtonStyle}”
Background=”{Binding CalculatorSecondaryBrush, ElementName=Page}”
Canvas.Top=”191” Canvas.Left=”354”>
<!– The “X in an arrow” backspace drawing –>
<Canvas Width=”48” Height=”32”>
<Path x:Name=”BackspaceXPath” Data=”M24,8 39,24 M39,8 24,24”
Stroke=”{StaticResource PhoneForegroundBrush}”
StrokeThickness=”4”/>
<Path x:Name=”BackspaceBorderPath” StrokeThickness=”2”
Data=”M16,0 47,0 47,31 16,31 0,16.5z”
Stroke=”{StaticResource PhoneForegroundBrush}”/>
</Canvas>
</Button>
</Canvas>
<!– A signal strength display –>
<TextBlock x:Name=”SignalStrengthTextBlock” Margin=”24”
HorizontalAlignment=”Center” />
</StackPanel>
</phone:PhoneApplicationPage>

[/code]

The Code-Behind

Listing 32.2 contains the code-behind for Local FM Radio.

LISTING 32.2 MainPage.xaml.cs—The Code-Behind for Local FM Radio

[code]

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using Microsoft.Devices.Radio;
using Microsoft.Phone.Controls;
namespace WindowsPhoneApp
{
public partial class MainPage : PhoneApplicationPage
{
double frequency;
DateTime? lastDigitButtonTap;
// Two theme-specific custom brushes
public Brush CalculatorMainBrush { get; set; }
public Brush CalculatorSecondaryBrush { get; set; }
public MainPage()
{
InitializeComponent();
// Ensure the radio is on
StartRadio();
// Work around having no radio events by polling
DispatcherTimer timer = new DispatcherTimer {
Interval = TimeSpan.FromSeconds(2) };
timer.Tick += delegate(object sender, EventArgs e) {
// Update the signal strength every two seconds
this.SignalStrengthTextBlock.Text = “signal strength: “ +
(FMRadio.Instance.SignalStrength * 100).ToString(“##0”);
};
timer.Start();
// A single handler for all calculator button taps
this.ButtonPanel.AddHandler(Button.MouseLeftButtonUpEvent,
new MouseButtonEventHandler(CalculatorButton_MouseLeftButtonUp),
true /* handledEventsToo */);
// Handlers to ensure that the backspace button’s vector content changes
// color appropriately when the button is pressed
this.BackspaceButton.AddHandler(Button.MouseLeftButtonDownEvent,
new MouseButtonEventHandler(BackspaceButton_MouseLeftButtonDown),
true /* handledEventsToo */);
this.BackspaceButton.AddHandler(Button.MouseLeftButtonUpEvent,
new MouseButtonEventHandler(BackspaceButton_MouseLeftButtonUp),
true /* handledEventsToo */);
this.BackspaceButton.MouseMove += BackspaceButton_MouseMove;
// Set the colors of the two custom brushes based on whether
// we’re in the light theme or dark theme
if ((Visibility)Application.Current.Resources[“PhoneLightThemeVisibility”]
== Visibility.Visible)
{
this.CalculatorMainBrush = new SolidColorBrush(
Color.FromArgb(0xFF, 0xEF, 0xEF, 0xEF));
this.CalculatorSecondaryBrush = new SolidColorBrush(
Color.FromArgb(0xFF, 0xDE, 0xDF, 0xDE));
}
else
{
this.CalculatorMainBrush = new SolidColorBrush(
Color.FromArgb(0xFF, 0x18, 0x1C, 0x18));
this.CalculatorSecondaryBrush = new SolidColorBrush(
Color.FromArgb(0xFF, 0x31, 0x30, 0x31));
}
// Grab the current frequency from the device’s radio
this.frequency = FMRadio.Instance.Frequency;
UpdateFrequencyDisplay();
}
void StartRadio()
{
try
{
// This would throw a RadioDisabledException if the app weren’t given
// the ID_CAP_MEDIALIB capability, but we’re worried instead about an
// UnauthorizedAccessException thrown when the phone is connected to Zune
FMRadio.Instance.PowerMode = RadioPowerMode.On;
}
catch
{
// Show a message explaining the limitation while connected to Zune
MessageBox.Show(“Be sure that your phone is disconnected from your “ +
“computer.”, “Cannot turn radio on”, MessageBoxButton.OK);
return;
}
if (FMRadio.Instance.SignalStrength == 0)
{
// Show a message similar to the built-in radio app
MessageBox.Show(“This phone uses your headphones as an FM radio “ +
“antenna. To listen to radio, connect your headphones.”, “No antenna”,
MessageBoxButton.OK);
}
}
void StopRadio()
{
try { FMRadio.Instance.PowerMode = RadioPowerMode.Off; }
catch {} // Ignore exception from being connected to Zune
}
// A single handler for all calculator button taps
void CalculatorButton_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
// Although sender is the canvas, the OriginalSource is the tapped button
Button button = e.OriginalSource as Button;
if (button == null)
return;
string content = button.Content.ToString();
// Determine what to do based on the string content of the tapped button
double digit;
if (content == “power”)
{
if (FMRadio.Instance.PowerMode == RadioPowerMode.On)
StopRadio();
else
StartRadio();
}
else if (double.TryParse(content, out digit)) // double so division works
{
// If there are already four digits (including the decimal place), or if
// the user hasn’t recently typed digits, clear the frequency first
if (this.frequency > 100 || this.lastDigitButtonTap == null ||
DateTime.Now – this.lastDigitButtonTap > TimeSpan.FromSeconds(3))
this.frequency = 0;
// Append the digit
this.frequency *= 10;
this.frequency += digit / 10;
this.lastDigitButtonTap = DateTime.Now;
}
else if (content == “C”)
{
// Clear the frequency
this.frequency = 0;
}
else // The backspace button
{
// Chop off the last digit (the decimal place) with a cast
int temp = (int)this.frequency;
// Shift right by 1 place
this.frequency = (double)temp / 10;
}
UpdateFrequencyDisplay();
}
void UpdateFrequencyDisplay()
{
try
{
this.FrequencyDisplay.Foreground =
Application.Current.Resources[“PhoneAccentBrush”] as Brush;
// Update the display
this.FrequencyDisplay.Frequency = this.frequency;
// Update the radio
FMRadio.Instance.Frequency = this.frequency;
}
catch
{
if (FMRadio.Instance.PowerMode == RadioPowerMode.On)
{
// Caused by an invalid frequency value, which easily
// happens while typing a valid frequency
this.FrequencyDisplay.Foreground = new SolidColorBrush(Colors.Red);
}
}
}
// Change the color of the two paths inside the backspace button when pressed
void BackspaceButton_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
this.BackspaceXPath.Stroke =
Application.Current.Resources[“PhoneBackgroundBrush”] as Brush;
this.BackspaceBorderPath.Stroke =
Application.Current.Resources[“PhoneBackgroundBrush”] as Brush;
}
// Change the color of the two paths back when no longer pressed
void BackspaceButton_MouseLeftButtonUp(object sender, MouseEventArgs e)
{
this.BackspaceXPath.Stroke =
Application.Current.Resources[“PhoneForegroundBrush”] as Brush;
this.BackspaceBorderPath.Stroke =
Application.Current.Resources[“PhoneForegroundBrush”] as Brush;
}
// Workaround for when the finger has not yet been released but the color
// needs to change back because the finger is no longer over the button
void BackspaceButton_MouseMove(object sender, MouseEventArgs e)
{
// this.BackspaceButton.IsMouseOver lies when it has captured the mouse!
// Use GetPosition instead:
Point relativePoint = e.GetPosition(this.BackspaceButton);
// We can get away with this simple check because
// the button is in the bottom-right corner
if (relativePoint.X < 0 || relativePoint.Y < 0)
BackspaceButton_MouseLeftButtonUp(null, null); // Not over the button
else
BackspaceButton_MouseLeftButtonDown(null, null); // Still over the button
}
}
}

[/code]

  • You can get an instance of the FMRadio class via the static FMRadio.Instance property. The instance exposes three read-write properties that control the phone’s single radio tuner exposed to all apps:
    • Frequency, a double value representing the current radio station.
    • PowerMode, which is either On or Off.
    • CurrentRegion, which can be UnitedStates, Japan, or Europe. The latter really means “every place in the world that isn’t the U.S. or Japan.”

It also exposes a read-only SignalStrength double property that exposes the received signal strength indicator (RSSI). The range of this value has not been documented, but it appears to be a value from 0 (no signal) to 1 (best signal), at least on the hardware I’ve tested.

Why doesn’t the radio work while my phone is connected to a computer running Zune?

The Zune desktop program locks the media library, which prevents any functionality that requires the ID_CAP_MEDIALIB capability from working. (This is the same reason you can’t use the phone’s Marketplace app while your phone is connected to Zune.) If you need to debug a part of your app that depends on such functionality, you can use the Windows Phone Connect Tool that ships with the Windows Phone Developer Tools to connect to your phone without Zune running.

There’s nothing that users of your app can do about this limitation other than closing Zune or disconnecting the phone, but it’s a good idea to detect this situation so you can explain what’s going on. Local FM Radio detects this situation inside its StartRadio method. It assumes that a failure to set PowerMode is due to the Zune connection, which is a pretty safe assumption.

Another way to detect this situation would be to see if the value of NetworkInterface. InterfaceType (in the Microsoft.Phone.Net.NetworkInformation namespace) is Ethernet, which should only happen when connected to Zune.However, this is a very bad property, because it performs long, blocking work before returning its value. If you decide to use it, be sure to do so on a background thread!

FMRadio’s Frequency property is a global setting!

If you change the radio station, this affects the built-in radio app (and any other third-party app consuming the radio tuner). On the one hand, this is handy, because an app doesn’t need to remember the last-used frequency when launched. (Indeed, the Local FM Radio app doesn’t persist anything to isolated storage.) On the other hand, you must do extra work if you wish to provide any sort of isolation from the built-in radio settings.

  • Because the signal strength can constantly vary, but there are no radio-specific events exposed, MainPage’s constructor uses a timer to refresh the signal strength display every two seconds. Although this kind of polling is bad for battery life, it’s unlikely that a user will be running this app for very long. That’s because the radio can keep playing after the app has been exited (and, importantly, this app does not run under the lock screen). The constructor also initializes the frequency variable to whatever frequency the radio was previously set.
  • The StartRadio and StopRadio methods toggle the value of the PowerMode property. StartRadio also shows a message similar to what the built-in radio app displays if the signal strength is zero, as shown in Figure 32.1. This app assumes that the user’s headphones must not be plugged in when this happens, as the headphones act as the FM antenna for every current phone model.

FMRadio’s PowerMode property is buggy in Version 7.0 of Windows Phone!

At least on some phones, setting PowerMode to Off stops the radio for a fraction of a second, but then the audio keeps playing even though the value of PowerMode doesn’t change back! This makes it impossible to show an on/off button that is in sync with the actual state of the radio. Instead, the power button in this app ends up acting strangely. Given this state of affairs, it’s only useful as a way to start the radio in case it couldn’t be started when the app launched (due to being connected to Zune).

  • When the radio is on, setting the frequency to an invalid value throws an exception. Valid and invalid values are based on the current region, which would be complicated to detect with your own logic. Therefore, this app takes the easy way out and leverages the exception to turn the frequency display red. (Of course, if the current accent color is already red, then this behavior is not noticeable.)
  • After the user has left the app (or while the app is still running), he/she can still control the radio by tapping the volume-up or volumedown button. This brings up the top overlay shown in Figure 32.2. Interestingly, this overlay enables seeking to the previous/next valid station (with the rewind and fastforward buttons), so the current station can get out-of-sync with the app’s own frequency display if this is done while Local FM Radio is running. Although this app could attempt to detect and correct this situation inside its timer’s Tick event handler, this is a minorenough issue to warrant leaving things as-is.
When the phone’s headphones are not connected, the phone cannot pick up any FM signal.
FIGURE 32.1 When the phone’s headphones are not connected, the phone cannot pick up any FM signal.

Can I retrieve the current radio station’s call letters, name, or “now playing” information?

No, although the built-in radio app can display this information, it is not exposed via any available APIs built into the phone.

In addition to changing the volume, the volume control overlay enables starting/ stopping the radio, and even seeking to the previous or next frequency with a strong-enough signal.
FIGURE 32.2 In addition to changing the volume, the volume control overlay enables starting/ stopping the radio, and even seeking to the previous or next frequency with a strong-enough signal.

The Finished Product

Local FM Radio (Radio Tuner)

Trombone (Sound Manipulation)

Trombone is a much more sophisticated musical instrument app than the preceding chapter’s Cowbell app. You can move the slide up and down to different positions to play any note. (Other than starting at F, the slide positions bear little resemblance to real trombone slide positions!) This app supports two different sliding modes. If you use the left side of the screen, you can freely move the slide. If you use the right side of the screen, the slide snaps to the closest note line. Besides being an easier way to play this instrument, this means you could also use this app as a pitch pipe.

This trombone can play its notes in two octaves; to raise the sound by an octave, place a second finger anywhere on the screen. The most fun part about this app is that, like with a real trombone, you must actually blow on your phone to produce sound!

These last two app features require phone features discussed in later chapters (multi-touch and using the microphone) so that portion of the code is not explained in this chapter. Instead, the focus is on manipulating a single sound effect’s pitch and duration to create all the audio needed by this app.

The User Interface

The main page, pictured in Figure 31.1 in its initial state, contains the moveable trombone slide, note guide lines, and buttons that link to the other two pages. Listing 31.1 contains the XAML.

The main page simulates the appearance of a real trombone.
FIGURE 31.1 The main page simulates the appearance of a real trombone.

LISTING 31.1 MainPage.xaml—The User Interface for Trombone’s Main Page

[code]

<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”
SupportedOrientations=”Portrait”>
<Canvas x:Name=”LayoutRoot”>
<!– The stationary inner slide –>
<Image Canvas.Left=”72” Source=”Images/innerSlide.png”/>
<!– The moveable outer slide –>
<Image x:Name=”SlideImage” Canvas.Left=”72” Canvas.ZIndex=”1”
Source=”Images/outerSlide.png”/>
<!– An instructions button –>
<Rectangle Canvas.Left=”18” Canvas.Top=”30” Canvas.ZIndex=”1”
Width=”48” Height=”48” Fill=”{StaticResource PhoneForegroundBrush}”
MouseLeftButtonUp=”InstructionsButton_Click”>
<Rectangle.OpacityMask>
<ImageBrush ImageSource=”/Shared/Images/normal.instructions.png”/>
</Rectangle.OpacityMask>
</Rectangle>
<!– A settings button –>
<Rectangle Canvas.Left=”18” Canvas.Top=”94” Canvas.ZIndex=”1”
Width=”48” Height=”48” Fill=”{StaticResource PhoneForegroundBrush}”
MouseLeftButtonUp=”SettingsButton_Click”>
<Rectangle.OpacityMask>
<ImageBrush ImageSource=”/Shared/Images/normal.settings.png”/>
</Rectangle.OpacityMask>
</Rectangle>
</Canvas>
</phone:PhoneApplicationPage>

[/code]

  • The note guide lines pictured in Figure 31.1 are added in this page’s code-behind.
  • An application bar would get in the way of this user interface, so two rectangles acting as buttons are used instead. They use the familiar opacity mask trick to ensure they appear as expected for any theme.
  • The trombone slide consists of two images, one on top of the other. These two images are shown in Figure 31.2.
The slide consists of a moving image on top of a stationary image.
FIGURE 31.2 The slide consists of a moving image on top of a stationary image.

The Code-Behind

Listing 31.2 contains the code-behind for the main page.

LISTING 31.2 MainPage.xaml.cs—The Code-Behind for Trombone’s Main Page

[code]

using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Navigation;
using System.Windows.Resources;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
using Microsoft.Xna.Framework.Audio; // For SoundEffect
namespace WindowsPhoneApp
{
public partial class MainPage : PhoneApplicationPage
{
// The single sound effect instance
SoundEffectInstance soundEffectInstance;
string[] notes = { “G ”, “G”, “A ”, “A”, “B ”, “B”,
“C”, “D ”, “D”, “E ”, “E”, “F” };
// The relative distance of each note’s pitch,
// where 0 is the initial F and -1 is one octave lower
double[] pitches = { -.9 /*G */, -.82 /*G*/, -.75 /*A */, -.68 /*A*/,
-.6 /*B */, -.5 /*B*/, -.4 /*C*/, -.35 /*D */,
-.25 /*D*/, -.18 /*E */, -.08 /*E*/, 0 /*F*/ };
// For microphone processing
byte[] buffer;
int currentVolume;
// For several calculations
const int TOP_NOTE_POSITION = 20;
const int BOTTOM_NOTE_POSITION = 780;
const int OCTAVE_RANGE = 844;
public MainPage()
{
InitializeComponent();
// Load the single sound file used by this app: the sound of F
StreamResourceInfo info = App.GetResourceStream(
new Uri(“Audio/F.wav”, UriKind.Relative));
SoundEffect effect = SoundEffect.FromStream(info.Stream);
// Enables manipulation of the sound effect while it plays
this.soundEffectInstance = effect.CreateInstance();
// The source .wav file has a loop region, so exploit it
this.soundEffectInstance.IsLooped = true;
// Add each of the note guide lines
for (int i = 0; i < this.pitches.Length; i++)
{
double position = BOTTOM_NOTE_POSITION + this.pitches[i] * OCTAVE_RANGE;
// Add a line at the right position
Line line = new Line { X2 = 410,
Stroke = Application.Current.Resources[“PhoneAccentBrush”] as Brush,
StrokeThickness = 5, Opacity = .8 };
Canvas.SetTop(line, position);
this.LayoutRoot.Children.Add(line);
// Add the note label next to the line
TextBlock label = new TextBlock {
Text = this.notes[i][0].ToString(), // Ignore the , use 0th char only
Foreground = Application.Current.Resources[“PhoneAccentBrush”] as Brush,
FontSize = 40 };
Canvas.SetLeft(label, line.X2 + 12);
Canvas.SetTop(label, position – 20);
this.LayoutRoot.Children.Add(label);
// Add the  separately, simulating a superscript so it looks better
if (this.notes[i].EndsWith(“”))
{
TextBlock flat = new TextBlock { Text = “”, FontSize = 25,
FontWeight = FontWeights.Bold, Foreground =
Application.Current.Resources[“PhoneAccentBrush”] as Brush };
Canvas.SetLeft(flat, line.X2 + label.ActualWidth + 6);
Canvas.SetTop(flat, position – 21);
this.LayoutRoot.Children.Add(flat);
}
}
// Configure the microphone
Microphone.Default.BufferDuration = TimeSpan.FromSeconds(.1);
Microphone.Default.BufferReady += Microphone_BufferReady;
// Initialize the buffer for holding microphone data
int size = Microphone.Default.GetSampleSizeInBytes(
Microphone.Default.BufferDuration);
buffer = new byte[size];
// Start listening
Microphone.Default.Start();
CompositionTarget.Rendering += delegate(object sender, EventArgs e)
{
// Required for XNA Sound Effect API to work
Microsoft.Xna.Framework.FrameworkDispatcher.Update();
// Play the sound whenever the blowing into the microphone is loud enough
if (this.currentVolume > Settings.VolumeThreshold.Value)
{
if (soundEffectInstance.State != SoundState.Playing)
soundEffectInstance.Play();
}
else if (soundEffectInstance.State == SoundState.Playing)
{
// Rather than stopping immediately, the “false” makes the sound break
// out of the loop region and play the remainder
soundEffectInstance.Stop(false);
}
};
// Call also once at the beginning
Microsoft.Xna.Framework.FrameworkDispatcher.Update();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Subscribe to the touch/multi-touch event.
// This is application-wide, so only do this when on this page.
Touch.FrameReported += Touch_FrameReported;
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
// Unsubscribe from this application-wide event
Touch.FrameReported -= Touch_FrameReported;
}
void Touch_FrameReported(object sender, TouchFrameEventArgs e)
{
TouchPoint touchPoint = e.GetPrimaryTouchPoint(this);
if (touchPoint != null)
{
// Get the Y position of the primary finger
double position = touchPoint.Position.Y;
// If the finger is on the right side of the screen, snap to the
// closest note
if (touchPoint.Position.X > this.ActualWidth / 2)
{
// Search for the current offset, expressed as a negative value from
// 0-1, in the pitches array.
double percentage = (-BOTTOM_NOTE_POSITION + position) / OCTAVE_RANGE;
int index = Array.BinarySearch<double>(this.pitches, percentage);
if (index < 0)
{
// An exact match wasn’t found (which should almost always be the
// case), so BinarySearch has returned a negative number that is the
// bitwise complement of the index of the next value that is larger
// than percentage (or the array length if there’s no larger value).
index = ~index;
if (index < this.pitches.Length)
{
// Don’t always use the index of the larger value. Also check the
// closest smallest value (if there is one) and snap to it instead
// if it’s closer to the current value.
if (index > 0 &&
Math.Abs(percentage – this.pitches[index]) >
Math.Abs(percentage – this.pitches[index – 1]))
index–;
// Snap the position to the new location, expressed in pixels
position = BOTTOM_NOTE_POSITION +
this.pitches[index] * OCTAVE_RANGE;
}
}
}
// Place the outer slide to match the finger position or snapped position
Canvas.SetTop(this.SlideImage, position – this.ActualHeight – 40);
// See how many fingers are in contact with the screen
int numPoints =
(from p in e.GetTouchPoints(this)
where
p.Action != TouchAction.Up
select p).Count();
// 1 represents one octave higher (-1 represents one octave lower)
int startingPitch = (numPoints > 1) ? 1 : 0;
// Express the position as a delta from the bottom position, and
// clamp it to the valid range. This gives a little margin on both
// ends of the screen because it can be difficult for the user to move
// the slide all the way to either end.
double offset = BOTTOM_NOTE_POSITION –
Math.Max(TOP_NOTE_POSITION, Math.Min(BOTTOM_NOTE_POSITION,
touchPoint.Position.Y));
// Whether it’s currently playing or not, change the sound’s pitch based
// on the current slide position and whether the octave has been raised
this.soundEffectInstance.Pitch =
(float)(startingPitch – (offset / OCTAVE_RANGE));
}
}
void Microphone_BufferReady(object sender, EventArgs e)
{
int size = Microphone.Default.GetData(buffer);
if (size > 0)
this.currentVolume = GetAverageVolume(size);
}
// Returns the average value among all the values in the buffer
int GetAverageVolume(int numBytes)
{
long total = 0;
// Although buffer is an array of bytes, we want to examine each
// 2-byte value.
// [SampleDuration for 1 sec (32000) / SampleRate (16000) = 2 bytes]
// Therefore, we iterate through the array 2 bytes at a time.
for (int i = 0; i < numBytes; i += 2)
{
// Cast from short to int to prevent -32768 from overflowing Math.Abs:
int value = Math.Abs((int)BitConverter.ToInt16(buffer, i));
total += value;
}
return (int)(total / (numBytes / 2));
}
// Button handlers
void SettingsButton_Click(object sender, MouseButtonEventArgs e)
{
this.NavigationService.Navigate(
new Uri(“/SettingsPage.xaml”, UriKind.Relative));
}
void InstructionsButton_Click(object sender, MouseButtonEventArgs e)
{
this.NavigationService.Navigate(
new Uri(“/InstructionsPage.xaml”, UriKind.Relative));
}
}
}

[/code]

  • The single sound file used by this app is a recording of an F being played on a trombone. The different notes are created by dynamically altering the pitch of the F as it plays.
  • Rather than directly use the SoundEffect object, as in the preceding chapter, this app calls its CreateInstance method to get a SoundEffectInstance object. SoundEffectInstance provides a few more features compared to SoundEffect and, because it is tied to a single instance of the sound, it enables manipulation of the sound after it has already started to play. Trombone requires SoundEffectInstance for its looping behavior and its ability to modify the pitch of an already-playing sound.
  • SoundEffectInstance exposes an IsLooped property (false by default) that enables you to loop the audio indefinitely until Stop is called. This can behave in one of two ways, depending on the source audio file:
    • For a plain audio file, the looping applies to the entire duration, so the sound will seamlessly restart from the beginning each time it reaches the end.
    • For an audio file with a loop region, the sound will play from the beginning the first time through, but then only the loop region will loop indefinitely. Calling the default overload of Stop stops the sound immediately, but calling an overload and passing false for its immediate parameter finishes the current iteration of the loop, and then breaks out of the loop and plays the remainder of the sound.

Figure 31.3 demonstrates these two different behaviors. The latter behavior is perfect for this app, because it enables a realisticsounding trombone note of any length, complete with a beginning and end that makes a smooth transition to and from silence. Therefore, the F.wav sound file included with this app defines a loop region. Although the sound file is less than a third of a second long, the loop region enables it to last for as long as the user can sustain his or her blowing.

Be careful about the length of your loop region!

If you don’t want to stop a sound immediately, but want it to gracefully stop fairly quickly with Stop(false) as with this app’s sound effect, your loop region (and remainder of the sound) must be very short.Otherwise, the process of finishing the current loop iteration could be too time-consuming.

Wavosaur (www.wavosaur.com) is a free and very powerful sound editor that enables you to create a loop region inside a .wav file. Simply highlight a region of the sound; then select Tools, Loop, Create loop points.The exported .wav file will still play straight through under normal circumstances, but playing it with SoundEffectInstance and IsLooped set to true will leverage your custom loop region.

SoundEffect versus SoundEffectInstance

Whereas SoundEffect only enables you to play sounds, SoundEffectInstance enables you to pause/resume/stop the particular sound instance with its Pause, Resume, and Stop methods. Whereas each call to SoundEffect’s Play method starts playing a fresh instance of the sound that can’t be stopped (and may overlap sounds from earlier calls), a call to SoundEffectInstance’s Play method does nothing if that instance of the sound is currently playing. Because SoundEffectInstance is tied to a specific sound instance, it is also able to expose a State property that reveals whether the sound is playing, paused, or stopped. In addition to the IsLooped property, SoundEffectInstance exposes three properties for controlling the resulting sound.These can be set at any time, even in the middle of playback:

  • Volume (default=1)—A value from 0 (muted) to 1 (full volume).
  • Pitch (default=0)—A value from –1 (one octave lower) to 1 (one octave higher). A value of 0 plays the sound at its natural pitch.
  • Pan (default=0)—A value from –1 (all the way to the left speaker) to 1 (all the way to the right speaker). A value of 0 centers the sound.

SoundEffect also enables controlling these three values with a Play overload that accepts volume, pitch, and pan parameters.However, these values always apply for the duration of the sound. (SoundEffect’s parameterless Play method, used in the preceding chapter, uses a volume of 1 and a pitch and pan of 0.)

SoundEffectInstance also exposes two overloads of an Apply3D method that enables you to apply 3D positioning to the sound playback.This feature is most interesting for Xbox and PC games. For phones, 3D positioning (and even custom pan values) is likely to be overkill.

Regular Sound File

Regular Sound FileSound File with a Looping Region

Sound File with a Looping Region

FIGURE 31.3 Options for looping with SoundEffectInstance.IsLooped set to true.

  • In the CompositionTarget.Rendering event handler, the current volume from the microphone is continually compared against a threshold setting (adjustable on the settings page). If it’s loud enough and the sound isn’t already playing, Play is called. (The State check isn’t strictly necessary because, unlike SoundEffect.Play, SoundEffectInstance.Play does nothing if the sound is already playing.) If the sound is playing and the volume is no longer loud enough, then Stop(false) is called to break out of the loop and play the end of the sound.
  • Inside Touch_FrameReported, which detects where the primary finger is in contact with the screen and whether a second finger is touching the screen (as discussed in Part VII, “Touch and Multi- Touch”), the sound’s pitch is adjusted. The startingPitch variable tracks which octave the base F note is in (0 for the natural octave or 1 for an octave higher); then the distance that the finger is from the bottom of the screen determines how much lower the pitch is adjusted. As you can see from the values in the pitches array at the beginning of the listing, a D is produced by lowering the pitch of the F by 25% (producing a value of -.25 or .75 depending on the octave), and a B is produced by lowering the pitch of the F by half (producing a value of -.5 or .5 depending on the octave).

Can I make my audio heard when the phone’s master volume is muted, or can I play audio louder than the master volume level?

No, the user is empowered to choose the maximum volume level for any sound that could be made by their phone.The 0–1 volume level of a sound effect is relative to the master volume.Note that SoundEffect exposes a static MasterVolume property that enables you to simultaneously adjust the volume of all your sounds (whether played from SoundEffect or SoundEffectInstance), but this does not enable you to get any louder than the user’s chosen volume level.

The Finished Product

Trombone (Sound Manipulation)

 

Qualities of a Personal Injury Law Firm to Retain

It is but reasonable for you to expect justice after sustaining injuries caused by unexpected circumstances, which cannot be considered providence or your fault. You have many reasons to hire a lawyer specializing in personal injury cases. These kinds of expert lawyers can be found in distinguished law firms with personal injury as their primary specialty area of law.As such, as soon as it is physically possible for you to do so, as a victim, you need to find the lawyer to help you attain justice for your case.However, finding the best lawyer in Los Angeles County could be tough. Avoid further catastrophe by knowing specifically what to look for before retaining a personal injury law firm.The qualities you can expect in a Los Angeles County law firm specializing in personal injury cases are the following:o Aggressive and knowledgeable enough to work up your injury claims and maximize the settlements you would receive.o Have the extensive experience in dealing with representatives of insurance companies. The attorneys of such law firm must be accustomed enough in negotiating with the negotiators of the insurance companies.You must find considerable evidences that the law firm and its lawyers are experts in dealing with adjustors who are crafty and skilled enough to devalue your insurance claims and force you into an unfair settlement.o Personal injury law firms in Los Angeles County abiding to a payment system of contingency fee or the “no recovery, no fee” or “no winning settlement or verdict, no fee,” are the best ones to retain.You are liable to pay for basic expenses according to the law. Nevertheless, a law firm with compassionate and competent lawyers will not oblige you to pay unless they have recovered substantial payment of damages for you to be secured even after the catastrophic effects of your injury.o Aside from specialized knowledge in the particular injury you have suffered, you must also look for a law firm with attorneys who have considerable experience as litigators. They must be highly skilled in doing the necessary motions. They must also be proficient in legal paperwork that insurance companies and the courts expect to see in insurance claims and civil cases.o The law firm with considerable experience also retains private investigators whenever it is necessary. This is because their attorneys would want to dig deeper unto the backgrounds of those liable for your injury. Private investigators will also be valuable help in unearthing and protecting evidences that can protect and solidify your case.Be aware of these other important qualities, too, in seeking out the best possible personal injury law firm in Los Angeles County.- Being objective- Have a coalition of experts of big-time cases- Know how to give a qualified estimated value of your case- Have achieved fair and just jury verdicts

Bad Credit and Home Equity Loans

One of the quickest, easiest, and fastest ways of getting cash for debt payments is through the use of home equity loans. Many people may think that their loan may not get approved because of a poor credit rating. There are lenders willing to make loans for these situations.You could refinance a home mortgage loan even if your credit score is low. The terms of the loan may be less flexible than what you would desire. Also finding an institution that offers low interest rates, great terms, no hidden extra charges, or fees is tough.Many institutions rely on FICO score in order to make lending decisions. The FICO score is based upon many factors. The 3 major credit bureaus are TransUnion, Equifax and Experian. They collect information from various sources and provides credit information about individuals, consumers, and organizations for various reasons. It’s mainly designed to determine credit worthiness or ability to pay debt in a timely manner.A credit score could range from 300 to 900 and is determined by a variety of factors such as open debts, past financial activity, new credit applications and several other factors.Most lenders seek to lend in the average range. If the credit score is below 600, the credit industry considers that individual in the high risk bracket.It is critically important to always read and review the terms of the home equity loan contract before signing the paperwork. Never hesitate to ask should a question arise.It is still possible to get a home equity loan if you have a low credit score. The interest rates may be high and may accumulate into the thousands during the life of the loan. A way to get around this is to refinance for a better rate later on. The ability to refinance the loan may depend upon whether your credit improves or not.

Home Refinancing – How Long You Should Wait to Refinance Your Home?

The amount of time you should wait to refinance your home depends on some key factors. Some deal with your type of mortgage and the lender you are currently with. Other factors depend on your financial situation and the market at the time you are considering to refinance your home. The type of mortgage you already have on your home determines refinancing timing more than anything else, so lets start there.Questions to Ask Regarding Your Current Mortgage
Does it have a seasoning period? A seasoning period is the time the mortgage company has written into the mortgage documents before which you cannot refinance your home. Some people buy foreclosure homes planning on refinancing once they get it re-appraised with enough value to eliminated the Private Mortgage Insurance. Many banks have time lines on mortgages that help them determine when they break even and will not allow the mortgage to be refinanced before then. If there is no seasoning period then any time is good.
Does your current mortgage have early payoff fees? Most mortgages now have eliminated these clauses because consumers caught on to them. However, if your home was mortgaged some time ago, it may have an early payoff penalty. This means if you pay off the mortgage early (even with a refinancing loan) you will pay a fee. If there is no payoff fee then you do not have to factor in those costs.
Is your current home loan fixed or variable? A fixed term mortgage gives you indefinite security with the current interest rate you have. You can wait out the market and your own personal financial situation to determine when the best time to mortgage will be. If you have a variable or adjustable rate mortgage (ARM) then when the fixed term portion of the mortgage (5 year ARM is 5 years fixed) is up, you may have a significant percentage increase if you do not refinance. If your ARM is getting ready to become variable it is a good time to refinance.
What is your current loan interest rate? Don’t look at the APR (annual percentage rate) since that counts in to your mortgage one time costs you have already paid and cannot recover (closing costs, points, etc). Look at the loan interest rate (the lower number you were likely quoted). Once you have figured out the above factors, and have the loan interest rate in hand you can move onto the next set of factors.Questions to Ask Regarding Your Financial Situation and the MarketThe mortgage market fluctuates interest rates for home loans daily. When that interacts with your personal financial situation and its fluctuations, a few months’ time can make thousands of dollars of difference. Here are some pieces of information you need to make an informed decision about when to refinance your home.
What is your FICO credit score? Do not pay or the vantage credit score, since it is not as well known and likely is not what banks will look at. Get your FICO credit score with a number. If your FICO score is above 700 then it may be a good time to refinance your home loan. If your FICO score is below 700, then you will likely want to repair your FICO score before refinancing since it could save you thousands of dollars to do so.
How much cash do you have on hand? To refinance a home loan you often need cash to cover closing costs (average cost is around $2500). You need to be sure you can cover the closing costs and still have a 3-6 months emergency fund in hand.
How long will you be in the home? If you plan on staying only a short time then the refinancing loan will have to pay for itself quickly. If you plan on being in the home for the foreseeable future then you can afford to take a few years for the refinance to pay for itself. Divide the total costs of refinancing by the savings per month over your old loan to determine how long it will take to pay or itself.
Is the APR for a new loan less than the loan rate for the current loan? You basically want to be sure that the costs of closing (assessment, points, origination fee and so on) when considered as part of the interest rate of the loan save you money over the loan interest rate you already have. Again, don’t look at the APR of your current home loan, that includes previous closing costs that you can’t change.

Save Even More Money on Black Friday

After scouring the ads on Thanksgiving, you may be wondering if there is a way to get more savings than just the advertised specials.Check the items you have circled. Then go online to see if there are any coupons offered. While most businesses won’t offer coupons, keep your eyes open for rebate offers and also offers such as cash back at the register, or cash back off your next purchase at that same store, which is a good as cash in your hand. Also some places will offer, for example, $5 off every $25 purchase.Black Friday is a great day to buy gifts for Christmas, but it can also be a great day to purchase other items which are more essential. Great deals can be found on blue jeans, t-shirts, bedding and at drug stores or mart stores, you can find specials on shampoo, makeup, and even food items.If you are struggling to make ends meet, then try buying grocery items on this day as well as presents. Again, check online to see if there are coupons to make these grocery items cost as little as possible, or maybe even get it for free! Comparing the sale prices on groceries with your saved Sunday coupons will also add up to great savings.An alternative way to make ends meet is if you find an extraordinary special deal on Black Friday, buy the item and sell it on eBay, Craigslist or even at a yard sale. Every little bit of extra money comes in handy at this time of year.

Debt Consolidation Loan – The Most Effective Solution to Get Out of Debt

debt consolidation loan refers to a debt management device that helps you when you are unable to manage your finances on your own due to multiple debts. You are not the only person who is facing this kind of financial trouble. A lot of people all over the world are experiencing similar fiscal problems. A great number of people among them have already used this technique to clear debt successfully.Debt Consolidation CompaniesRealizing the fact that solving the financial troubles of others can also be a very good business opportunity, plethora of debt consolidation companies have come into existence. These companies offer various types of solution to cater to the needs of different individuals. What you should do to get out of debt trap is to find a suitable company that can provide just the right kind of assistance to come out of financial dilemma.Although majority of such companies are there in the market to help you in difficult times yet you have to be careful while selecting one for you. This is because in the process of locating a helping hand, you may come across some people with ill intentions. The sole purpose of these mischievous companies is to make money out of your troubles. That is why you should be careful not to get caught into any such trap.Credit Card Debt ConsolidationThe main reason for this type of financial debacle is having multiple debts. If you have several credit card debts then you should look for a company that has expertise in handling this type of issues. It can suggest you effective methods to manage multiple credit card debts. One of the most effective technique is to consolidate all your existing debts into a larger loan with lower interest rate. This single step can save you from many problems that you are facing due to financial crunch.First, your monthly installment comes down to such an extent that you can easily manage it. This gives you some breathing period and you can plan your financial matters in a better way with cool mind. Second, you are saved from handling a so many creditors every month, as your responsibility is limited to only one lending company. Moreover, You can live a peaceful life, as there will be no more frustrating telephone calls from creditors or collection agencies.

Student Loan Consolidation Fraud – Beware of Scams

Since most college students are known for not having a lot of money, it would seem unwise for a thief to target them. Still, some people are willing to steal even from college students who are already strapped for cash. Thanks to lots of good press in recent years, the many benefits of student loan consolidation have been clearly presented. You can successfully avoid becoming involved in student loan consolidation fraud by using some common sense.Student Loan Consolidation Fraud: Social Security Number ScamOne scam that people use to commit student loan consolidation fraud uses emails and phone calls that supposedly come from well established financial institutions. They contact students, claiming to work with student loans. They often ask for background information and sometimes are able to get names, phone numbers, and email addresses of students who have applied for student loans. These scams will ask you to verify their information with part of, if not all of, your social security number.This scam is not new. For many years a similar scam has been spammed to countless email addresses, asking recipients to “contact our security department immediately”. The emails these scammers use can look quite official. These tricks have worked before, most famously with PayPal and eBay users.What to Watch Out ForStudent loan consolidation scams also utilize emails, mailings, and phone calls that contain the following signs of fraud:- Impossibly low interest rates- Spelling and grammar errors- Letters or emails written exclusively with capital letters- The address of a company you have never contacted beforeWhat to Do If You Have Been ScammedThe first thing you should do if you believe that you have been scammed by student loan consolidation fraud is to right down details regarding your interactions with the company. Then, contact your creditors and the Better Business Bureau so that they become aware of the scam.

Federal Student Loan Help – One Wacky Way to Pay it Off!

A federal student loan can be a pain to pay off, but you’ll find a fast way to do it below. And after the quick method, some good ideas in case you still need them.Recently you have probably seen in the news how bailouts have saved banks and some other companies. You might even be asking when you get your student loan bailout.As luck would have it, so far no one has created that bailout. You may find the idea below helpful in your quest for freedom from your past loans, and all those pizzas and lattes in college.Have you looked at real estate?Property values have really sunk, and people have defaulted in record numbers. If your neighborhood looks like mine, you have a few more vacant houses than you used to, and sales aren’t going so fast.That’s where this idea shines. Now you can put those houses to work for you!First, find a foreclosed property or a even a fixer upper house.Make the best deal you can to get it for way below market, 50% or less if you can. Now apply for a HELOC (home equity line of credit). The HELOC should match or exceed the amount of federal student loans you have.If not, don’t worry, just do it twice.Now, if you feel exceptionally unethical, you can use the money to pay off your student loan debt.To finish up the concept, sell the house for more than you bought it, including the new loan. Or some people might default. You get the picture.At about this time, you might think, “what a lousy idea! I’m not going to buy a house in this market and then hope to sell or lose my shirt!”Okay, let’s look at the more ethical options available to you.Bonus IdeasYou aren’t the first to worry about a big federal student loan looming over your future. With time and focus, you can pay it off.- You can move in with your parents or grandparents for a couple of short years. Send your rent money to pay down that loan. That right there could be $12,000 per year.- Go back to mowing lawns for extra money, and shoveling walks in the winter until you get your student loan paid off.- Sell your RV and use the payment to get your debt paid off.- Ditto the four wheelers, the boat, the jet skis, and any other toys.Painful, I know. Paying off your federal student loans can take some time and diligence. If you’ll make a few sacrifices and throw extra money at this problem, you can pay it off fast.It might take extra effort, but once it’s gone, you’ll feel better, and you can move out of grandma’s house, and buy back all those toys.