In my last Windows Phone 7 blog entry, we took a look at making a very simple Windows Phone 7 application. This time around, our goal is to make an app that uses the microphone on the phone and reads and writes to the phone’s isolated storage.
Let’s fire up Microsoft Visual Studio 2010 Express for Windows Phone and go File–>new Project–>Visual C#–>Silverlight for Windows Phone–>Windows Phone Application. Pick a Location for the Solution and name it SoundRecorder.
The first thing we need to do after the project is created is to add a reference to Microsoft.Xna.Framework.dll. Right click on References in the Solution Explorer and select “Add Reference”. Find Microsoft.Xna.Framework.dll, select it, and click “Ok”. That will reference the Xna Framework (even though this is a Silverlight application) and give us easy access to the microphone.
Now, let’s get our XAML set up. Type / copy-paste / set up the code you see below into MainPage.xaml. There isn’t much to see here besides three buttons (one of them disabled at the start) and our app’s title. Due to space constraints and the desire to be very “2.0”, I had to shorten the application name in the title to “Sound Recordr” from “Sound Recorder”.
<phoneNavigation:PhoneApplicationPage x:Class="SoundRecorder.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:phoneNavigation="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Navigation" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="800" FontFamily="{StaticResource PhoneFontFamilyNormal}" FontSize="{StaticResource PhoneFontSizeNormal}" Foreground="{StaticResource PhoneForegroundBrush}"> <Grid x:Name="LayoutRoot" Background="{StaticResource PhoneBackgroundBrush}"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Grid x:Name="TitleGrid" Grid.Row="0"> <TextBlock Text="Pete on Software" x:Name="textBlockPageTitle" Style="{StaticResource PhoneTextPageTitle1Style}"/> <TextBlock Text="Sound Recordr" x:Name="textBlockListTitle" Style="{StaticResource PhoneTextPageTitle2Style}" Margin="6,43,0,0" /> </Grid> <Grid x:Name="ContentGrid" Grid.Row="1"> <Button Content="Start" Height="70" HorizontalAlignment="Left" Margin="165,55,0,0" Name="startButton" VerticalAlignment="Top" Width="160" Click="startButton_Click" /> <Button Content="Stop" Height="70" HorizontalAlignment="Left" Margin="165,166,0,0" Name="stopButton" VerticalAlignment="Top" Width="160" Click="stopButton_Click" /> <Button Content="Play Existing File" Height="70" HorizontalAlignment="Left" Margin="126,321,0,0" Name="playButton" VerticalAlignment="Top" Width="249" IsEnabled="False" Click="playButton_Click" /> </Grid> </Grid> </phoneNavigation:PhoneApplicationPage>
Now, in the code behind (MainPage.xaml.cs), we should have the following:
using System; using System.IO; using System.IO.IsolatedStorage; using System.Windows; using Microsoft.Phone.Controls; using Microsoft.Xna.Framework.Audio; namespace SoundRecorder { public partial class MainPage : PhoneApplicationPage { private Microphone mic = Microphone.Default; private MemoryStream stream; private const string FILE_NAME = "recording.ps"; byte[] buffer; public MainPage() { InitializeComponent(); SupportedOrientations = SupportedPageOrientation.Portrait | SupportedPageOrientation.Landscape; // Some necessary setup for our Microsoft.Xna.Framework.Audio.Microphone mic.BufferDuration = TimeSpan.FromSeconds(1); buffer = new byte[mic.GetSampleSizeInBytes(mic.BufferDuration)]; // Create the event handler. I could have done an anonymous // delegate here if I had so desired. mic.BufferReady += handleBufferReady; } /// This is called every "buffer duration" (in our case 1 second) and copies out what the mic has /// in the buffer array to the memory stream. private void handleBufferReady(object sender, EventArgs e) { mic.GetData(buffer); stream.Write(buffer, 0, buffer.Length); } /// Initializes a new memory stream for new playtime. Starts the mic. private void startButton_Click(object sender, RoutedEventArgs e) { stream = new MemoryStream(); mic.Start(); startButton.IsEnabled = false; } /// Stops the mic and writes out the file. private void stopButton_Click(object sender, RoutedEventArgs e) { mic.Stop(); writeFile(stream); startButton.IsEnabled = true; } /// Writes our the file, using isolated storage. private void writeFile(MemoryStream s) { using (var userStore = IsolatedStorageFile.GetUserStoreForApplication()) { if (userStore.FileExists(FILE_NAME)) { userStore.DeleteFile(FILE_NAME); } using (var file = userStore.OpenFile(FILE_NAME, FileMode.CreateNew)) { s.WriteTo(file); playButton.IsEnabled = true; } } } /// Playing our file with another piece of the Xna Framework private void playFile(byte[] file) { if (file == null || file.Length == 0) return; var se = new SoundEffect(file, mic.SampleRate, AudioChannels.Mono); SoundEffect.MasterVolume = 0.7f; se.Play(); } /// A play is requested. Get the file from isolated storage and send /// its bytes to the playFile method. private void playButton_Click(object sender, RoutedEventArgs e) { using (var userStore = IsolatedStorageFile.GetUserStoreForApplication()) { if (userStore.FileExists(FILE_NAME)) { var file = userStore.OpenFile(FILE_NAME, FileMode.Open, FileAccess.Read); var bytes = new byte[file.Length]; file.Read(bytes, 0, bytes.Length); playFile(bytes); } } } } }
Examine this particular line:
private Microphone mic = Microphone.Default;
This code gives us a new Microsoft.Xna.Framework.Audio.Microphone object. We call for Microphone.Default and that gives us the default microphone on the system. The great thing about this is that on the phone is is the phone’s microphone, headset, etc, but in our simulator – your computer’s microphone is used. All of the other things that are done with this class are just the built-in XNA magic (documentation).
The only other piece we use here is the isolated storage. I get a reference to it in this line:
var userStore = IsolatedStorageFile.GetUserStoreForApplication()
After that, I basically use my variable userStore just like I would any other component in the System.IO namespace. That is really the power of Windows Phone 7 development. All of this code should be very familiar to .Net developers and behaves just like the phone was a little computer (which of course it is).
When we run our application, we see the following:
We cannot play the file yet because we haven’t recorded one. If we click “Start” and just talk or sing or make noise for the mic and then click “Stop”, we see that the “Play Existing File” button becomes activated.
If you click “Play Existing File”, the file will be retrieved out of storage and played back with another excellent class from Microsoft.Xna.Framework.Audio, the SoundEffect class.
This is definitely demo code (read: it has bugs), and is only guaranteed to work on my machine and with the April CTP bits that I have loaded. Disclaimers aside, this definitely shows that developing for Windows Phone 7 should be extremely easy for anyone who knows or can learn .Net.