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)

Cowbell (Sound Effects)

Cowbell is a simple musical instrument app. With it, you can tap the screen in any rhythm, and the app makes a cowbell noise with each tap. You can even play along with songs from your music library by switching to the Music + Videos hub, starting a song or playlist, and then switching back to Cowbell. The important aspect of Cowbell is that its sole purpose is to play sound effects.

Of all the musical instruments out there, why choose a cowbell? Many people find the idea of playing a cowbell entertaining thanks to a Saturday Night Live skit in 2000 with Will Ferrell and Christopher Walken. In it, Christopher Walken repeatedly asks for “more cowbell” while Will Ferrell plays it to Blue Öyster Cult’s “(Don’t Fear) The Reaper.” With this song in your music library and this app on your phone, you can re-create the famous skit!

Playing Sound Effects

On Windows Phone, Silverlight has only one way to play audio and video: the MediaElement element. However, this element is too heavyweight for playing sound effects. When it plays, it stops any other media playback on the phone (e.g. music playing in the background from the Music + Videos hub).

The relevant XNA class is called SoundEffect, and it lives in the Microsoft.Xna.Framework.Audio namespace. To use it, you must add a reference to the Microsoft.Xna.Framework assembly in your project. In this chapter, you’ll see how to load a sound effect from an audio file and how to play it.

Using MediaElement for sound effects could cause your app to fail marketplace certification!

Because using MediaElement for sound effects results in the poor user experience of halting background media,Microsoft checks for this when certifying your app for the marketplace. If you use MediaElement for sound effects, your app will not be approved for publishing.

If you need sound effects for your app and are unable to make them yourself, here are a few good resources to check out:

  • The Freesound Project (freesound.org)
  • Partners in Rhyme (partnersinrhyme.com)
  • Soungle (soungle.com)
  • Sounddogs (sounddogs.com)
  • SoundLab, a pack of game-centric sounds from Microsoft (create.msdn.com/ en-US/education/catalog/utility/soundlab)

The User Interface

Cowbell has a main page, an instructions page, and an about page. The latter two pages aren’t interesting and therefore aren’t shown in this chapter, but Listing 30.1 contains the XAML for the main page.

LISTING 30.1 MainPage.xaml—The User Interface for Cowbell’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”
SupportedOrientations=”Portrait”>
<!– The application bar, with one button and one menu item –>
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar Opacity=”.5”>
<shell:ApplicationBarIconButton Text=”instructions”
IconUri=”/Shared/Images/appbar.instructions.png”
Click=”InstructionsButton_Click”/>
<shell:ApplicationBar.MenuItems>
<shell:ApplicationBarMenuItem Text=”about” Click=”AboutMenuItem_Click”/>
</shell:ApplicationBar.MenuItems>
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
<!– Just an image in a grid –>
<Grid Background=”Black” MouseLeftButtonDown=”Grid_MouseLeftButtonDown”>
<Image Source=”Images/cowbell.png” Stretch=”None”/>
</Grid>
</phone:PhoneApplicationPage>

[/code]

This is a simple page with an application bar and a grid with a cowbell image that handles taps with its MouseLeftButtonDown handler. For the sake of the cowbell image that has white edges, the grid is given a hard-coded black background. Therefore, this page looks the same under both themes except for the half-opaque application bar, as seen in Figure 30.1.

FIGURE 30.1 The main page looks identical on both dark and light themes, except for the application bar.
FIGURE 30.1 The main page looks identical on both dark and light themes, except for the application bar.

The Code-Behind

Listing 30.2 contains the code-behind for the main page. This is where all the soundeffect logic resides.

LISTING 30.2 MainPage.xaml.cs—The Code-Behind for Cowbell’s Main Page

[code]

using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Navigation;
using System.Windows.Resources;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using Microsoft.Xna.Framework.Audio; // For SoundEffect
namespace WindowsPhoneApp
{
public partial class MainPage : PhoneApplicationPage
{
SoundEffect cowbell;
public MainPage()
{
InitializeComponent();
// Load the sound file
StreamResourceInfo info = Application.GetResourceStream(
new Uri(“Audio/cowbell.wav”, UriKind.Relative));
// Create an XNA sound effect from the stream
cowbell = SoundEffect.FromStream(info.Stream);
// Subscribe to a per-frame callback
CompositionTarget.Rendering += CompositionTarget_Rendering;
// Required for XNA sound effects to work
Microsoft.Xna.Framework.FrameworkDispatcher.Update();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Don’t let the screen auto-lock in the middle of a musical performance!
PhoneApplicationService.Current.UserIdleDetectionMode =
IdleDetectionMode.Disabled;
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
// Restore the ability for the screen to auto-lock when on other pages
PhoneApplicationService.Current.UserIdleDetectionMode =
IdleDetectionMode.Enabled;
}
void Grid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// The screen was tapped, so play the sound
cowbell.Play();
}
void CompositionTarget_Rendering(object sender, EventArgs e)
{
// Required for XNA sound effects to work.
// Call this every frame.
Microsoft.Xna.Framework.FrameworkDispatcher.Update();
}
// Application bar handlers
void InstructionsButton_Click(object sender, EventArgs e)
{
this.NavigationService.Navigate(new Uri(“/InstructionsPage.xaml”,
UriKind.Relative));
}
void AboutMenuItem_Click(object sender, EventArgs e)
{
this.NavigationService.Navigate(new Uri(“/AboutPage.xaml”,
UriKind.Relative));
}
}
}

[/code]

  • In the constructor, the stream for this app’s .wav audio file is obtained with the static Application.GetResourceStream method.
  • The use of the CompositionTarget.Rendering event is required for sound effects to work properly. This is explained in the following warning sidebar.

When playing sound effects with XNA, you must continually call Update on XNA’s framework dispatcher!

XNA’s sound effect functionality, like some other functionality in XNA, only works if you frequently (as in several times a second) call the static FrameworkDispatcher.Update method from the Microsoft.Xna.Framework namespace.This is natural to do from XNA apps, because they are designed around a game loop that runs code every frame. (XNA even provides a base Game class that automatically does this, so developers don’t have to.) From Silverlight apps, however, which are inherently event-based, you must go out of your way to run code on a regular schedule.

To call FrameworkDispatcher.Update regularly, you could use a DispatcherTimer, as done in previous chapters.You could even use a plain System.Threading.Timer because FrameworkDispatcher.Update can be called from any thread.

However,my preferred approach is to use an event Silverlight raises before every single frame is rendered.The event is called Rendering, and it is exposed on a static class called CompositionTarget.This event is useful for doing custom animations that can’t easily be represented with Silverlight’s animation classes from Part II,“Transforms & Animations,” of this book, such as physics-based movement. In Cowbell, the event is perfect for calling FrameworkDispatcher.Update with the roughly the same frequency that an XNA app would call it. Note that the first call to FrameworkDispatcher.Update is in the page’s constructor because it takes a bit of time for the first Rendering event to be raised.

If you call Play without previously calling FrameworkDispatcher.Update within a short time span, an InvalidOperationException is thrown with the following helpful message: FrameworkDispatcher.Update has not been called. Regular FrameworkDispatcher. Update calls are necessary for fire and forget sound effects and framework events to function correctly. See http://go.microsoft.com/fwlink/?LinkId=193853 for details.

  • The code in OnNavigatedTo and OnNavigatedFrom exists to ensure that the screen doesn’t auto-lock. If the cowbell player has a long break during a performance, it would be very annoying if the screen automatically locked. And tapping the screen to keep it active isn’t a good option, because that would make an unwanted cowbell noise!
  • The sound effect is played with a simple call to SoundEffect.Play in Grid_MouseLeftButtonDown. If Play is called before the sound effect finishes playing from a previous call, the sounds overlap.

The Audio Transport Controls

When the phone’s media player plays music, this music continues playing while apps run.Users can pause, rewind, fast forward, or change songs via the 93-pixel tall top overlay that appears on top of any app when the hardware volume buttons are pressed.This functionality works great with a fun instrument app such as Cowbell. In the next release of the Windows Phone OS, due by the end of 2011, third-party apps will be able to play music in the background just like the builtin media player.

The Finished Product

Cowbell (Sound Effects)