Chapter 21. Microphone and FM Radio


In This Chapter

Using the microphone to record audio to isolated storage

Creating a helium voice app

Creating a custom FM radio app

FM radio region and frequency ranges

Using the Silverlight Toolkit LoopingSelector


Every Windows Phone device is required to have an FM radio tuner, and likewise, without a microphone, the phone would be no more than a smart camera.

This chapter begins by looking at using the XNA microphone API to record audio. You have some fun with the microphone and create a helium voice app that allows the user to record audio and to modulate the pitch of the recorded audio during playback.

The chapter then looks at the FM radio. You see how to create a custom radio app that uses a Silverlight Toolkit LoopingSelector to change the radio’s frequency.

Recording Audio with the Microphone

The XNA Microphone class allows you to capture audio data from the phone’s microphone. Microphone resides in the Microsoft.Xna.Framework.Audio namespace.

To use the Microphone class you need to perform the following two steps:

1. Add a reference to the Microsoft.Xna.Framework assembly.

2. Create a GameTimer to pump the XNA FrameworkDispatcher, discussed next.

XNA components usually rely on initialization of the XNA environment before they can be used, and the Microphone class is no exception. Creating a GameTimer to pump the XNA FrameworkDispatcher usually involves adding some code to your App class that creates a GameTimer and subscribes to its FrameAction event. When creating a Silverlight/XNA hybrid application, this plumbing is automatically put in place when using the “Windows Phone Rich Graphics Application” Visual Studio new project template.

For more information on initializing the XNA environment, see Chapter 20, “Incorporating XNA Graphics in Silverlight.”

Microphone does not have a public constructor; an instance is retrieved using the static Microphone.Default property, as shown:

Microphone microphone = Microphone.Default;

Microphone uses an event driven approach to periodically deliver audio data while it is recording. The Microphone.BufferReady event is raised as soon as the Microphone instance’s buffer is full. The size of the buffer is specified using the Microphone.BufferDuration property, like so:

microphone.BufferDuration = TimeSpan.FromSeconds(1);

The Microphone.GetSampleSizeInBytes can be used to determine the space requirements for reading the sample data when the BufferReady event is raised.

int sampleSizeInBytes
         = microphone.GetSampleSizeInBytes(microphone.BufferDuration);

This provides the opportunity to create a byte array for storing the sample bytes when the buffer has been filled:

byte[] buffer = new byte[sampleSizeInBytes];

The buffer can later be copied to a stream and saved in isolated storage, as shown:

void HandleBufferReady(object sender, EventArgs e)
{
    microphone.GetData(buffer);
    stream.Write(buffer, 0, buffer.Length);
}

The next section looks at putting this together to create a page that allows the user to record and play back audio.

Creating a Helium Voice App

They are a dime a dozen, and you may be surprised by just how simple it is to create a helium voice app, which can modulate the pitch of the user’s voice. This section creates a page that allows the user to record audio, which is saved to isolated storage, and to play back the sample with the option to change the pitch of the playback using a slider.

The code for this section is located in the Sensors/Microphone directory of the WindowsPhone7Unleashed.Examples project in the downloadable sample code.

The MicrophoneViewModel class includes the following two commands:

ToggleRecordCommand—Starts the microphone recording if not recording; otherwise, recording is stopped and the sample saved to isolated storage.

PlayCommand—Plays the previously saved sample, if it exists, from isolated storage.

MicrophoneViewModel initializes the default Microphone instance to a BufferDuration of 1 second. A byte array field is initialized using the microphone’s sample size (see Listing 21.1).

Listing 21.1. MicrophoneViewModel Class (excerpt)


public class MicrophoneViewModel : ViewModelBase
{
    readonly Microphone microphone = Microphone.Default;
    readonly byte[] buffer;
    const string fileName = "MicrophoneSample.bytes";
    MemoryStream stream = new MemoryStream();
    readonly object fileLock = new object();

    public MicrophoneViewModel() : base("microphone")
    {
        microphone.BufferDuration = TimeSpan.FromSeconds(1);
        microphone.BufferReady += HandleBufferReady;

        int sampleSizeInBytes = microphone.GetSampleSizeInBytes(
                                               microphone.BufferDuration);
        buffer = new byte[sampleSizeInBytes];

        toggleRecordCommand = new DelegateCommand(obj => ToggleRecording());
        playCommand = new DelegateCommand(obj => Play(), obj => !recording);
    }
...
}


Recording Audio

The viewmodel’s ToggleRecording method either calls the StopRecording or StartRecording method, depending on the current recording state, as shown:

void ToggleRecording()
{
    if (recording)
    {
        StopRecording();
    }
    else
    {
        StartRecording();
    }
}

The viewmodel’s StartRecording method instantiates a new MemoryStream, which is used to copy the sample data each time the Microphone.BufferReady event is raised. When the Microphone object is started, the BufferReady event is raised after 1 second of recording time. See the following excerpt:

void StartRecording()
{
    stream = new MemoryStream();
    microphone.Start();
    Recording = true;
    UpdateCommands();
}

The UpdateCommands method disables the play command if playback is in progress, as shown:

void UpdateCommands()
{
    playCommand.RaiseCanExecuteChanged();
}

The viewmodel’s StopRecording method tests the state of the microphone. The Microphone.Stop method is not in the StopRecording method because doing so would prevent the last BufferReady event from being raised, which can cause the audio to be cut off. See the following excerpt:

void StopRecording()
{
    if (microphone.State != MicrophoneState.Stopped)
    {
        Recording = false;
        UpdateCommands();
    }
}

The stop request is, instead, dealt with in the viewmodel’s HandleBufferReady method, in which the sample data is read into the buffer byte array and then written to a MemoryStream. If a stop recording request has been made at this point, the stream is flushed, the microphone stopped, and the MemoryStream is written to isolated storage, as shown:

void HandleBufferReady(object sender, EventArgs e)
{
    microphone.GetData(buffer);
    stream.Write(buffer, 0, buffer.Length);

    if (!recording)
    {
        stream.Flush();
        microphone.Stop();
        WriteFile(stream);
    }
}

Writing the sample to isolated storage involves retrieving an IsolatedStorageFileStream and writing the MemoryStream to it, as shown:

void WriteFile(MemoryStream memoryStream)
{
    lock (fileLock)
    {
        using (var isolatedStorageFile
                      = IsolatedStorageFile.GetUserStoreForApplication())
        {
            using (IsolatedStorageFileStream fileStream
                  = isolatedStorageFile.OpenFile(fileName, FileMode.Create))
            {
                memoryStream.WriteTo(fileStream);
            }
        }
    }
}

Playback of the audio begins with the retrieval of the sample bytes from isolated storage, as shown:

void Play()
{
    byte[] bytes = null;

    lock (fileLock)
    {
        using (var userStore
                     = IsolatedStorageFile.GetUserStoreForApplication())
        {
            if (userStore.FileExists(fileName))
            {
                using (IsolatedStorageFileStream fileStream
                                      = userStore.OpenFile(fileName,
                                                           FileMode.Open,
                                                           FileAccess.Read))
                {
                    bytes = new byte[fileStream.Length];
                    fileStream.Read(bytes, 0, bytes.Length);
                }
            }
        }
    }

    if (bytes != null)
    {
        PlayAudio(bytes);
    }
}

Playback

An XNA SoundEffect instance is used to play the sample bytes. The microphone’s sample rate is used to match the sample rate of the SoundEffect. A SoundEffectInstance field is used to vary the pitch of the audio during playback. See the following excerpt:

SoundEffectInstance effectInstance;

void PlayAudio(byte[] audioBytes)
{
    if (audioBytes == null || audioBytes.Length == 0
        || effectInstance != null
        && effectInstance.State == SoundState.Playing)
    {
        return;
    }

    var soundEffect = new SoundEffect(audioBytes,
                                      microphone.SampleRate,
                                      AudioChannels.Mono);
    if (effectInstance != null)
    {
        effectInstance.Dispose();
    }
    effectInstance = soundEffect.CreateInstance();
    effectInstance.Pitch = (float)pitch;
    effectInstance.Play();
}

The Pitch property adjusts the playback of the SoundEffectInstance during playback, as shown:

double pitch;

public double Pitch
{
    get
    {
        return pitch;
    }
    set
    {
        Assign(() => Pitch, ref pitch, value);
        if (effectInstance != null)
        {
            effectInstance.Pitch = (float)value;
        }
    }
}

MicrophoneView

The MicrophoneView page includes a custom AppBar definition with buttons bound to the commands in the view. The first button is an AppBarToggleButton, which changes its icon according to the value of the viewmodel’s Recording property. See the following excerpt:

<u:AppBar>
    <u:AppBarToggleButton
                Command1="{Binding ToggleRecordCommand}"
                Toggled="{Binding Recording}"
                Text1="Record"
                Text2="Stop"
                Icon1Uri="/Sensors/Microphone/Icons/Record.png"
                Icon2Uri="/Sensors/Microphone/Icons/StopRecording.png" />
    <u:AppBarIconButton
                Command="{Binding PlayCommand}"
                Text="Play"
                IconUri="/Sensors/Microphone/Icons/Play.png" />
</u:AppBar>

The main content panel includes a Slider that is bound to the viewmodel’s Pitch property, as shown:

<StackPanel x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <TextBlock Text="pitch"
               Style="{StaticResource LabelTextIndentedStyle}" />
    <Slider Value="{Binding Pitch, Mode=TwoWay}" Minimum="-1" Maximum="1" />
</StackPanel>

By moving the slider to the left the pitch decreases during playback; by moving it to the right the helium voice effect is achieved (see Figure 21.1).

Image

Figure 21.1. MicrophoneView page with a Slider to control pitch

The Microphone class provides an easy way to capture audio and provides interesting opportunities for adding voice enabled services such as voice recognition and streaming communications.

Controlling the Phone’s FM Radio

The FMRadio class allows you to control the Windows Phone hardware FM radio component. It is a singleton, providing access to a single FMRadio object via its static Instance property.

The FMRadio class has the following four nonstatic properties:

PowerMode

Frequency

CurrentRegion

SignalStrength

PowerMode allows you to turn the FM radio device on or off. PowerMode is of type RadioPowerMode, an enum with two values: Off and On.

Frequency is a double property that allows you to control the stations that the radio is tuned to. The set of radio frequency bands available for tuning is determined by the CurrentRegion property, which is of type RadioRegion, an enum with the following three values:

Europe

Japan

UnitedStates

The differences in frequency ranges for each of these regions are as follows

Image

Note

The CurrentRegion property is used to determine whether a Frequency value is valid. When setting the Frequency property, the value must fall within the range as described by the CurrentRegion property. For example, if the CurrentRegion property is set to Japan and you attempt to set the Frequency above 90MHz, an ArgumentException is raised.


FMRadio.SignalStrength indicates the reception quality. The FM radio on Windows Phone devices is designed to work with the user’s headphones plugged in, which act as an aerial for the device. It is good practice to inform the user of this fact when a low signal strength is detected. This is demonstrated in the following section.

FMRadio fmRadio = FMRadio.Instance;

fmRadio.PowerMode = RadioPowerMode.On;
fmRadio.CurrentRegion = RadioRegion.UnitedStates;
fmRadio.Frequency = 99.5;

if (fmRadio.SignalStrength < 0.01)
{
    MessageBox.Show("This phone uses your headphones "
                    + "as an FM radio antenna. "
                    + "To listen to radio, connect your headphones.",
                    "No antenna", MessageBoxButton.OK);
}

To switch off the radio, set the PowerMode to Off, as shown:

FMRadio.Instance.PowerMode = RadioPowerMode.Off;

Building a Custom FM Radio App

This section looks at using the FMRadio class to create a custom FM radio app that allows the user to switch the radio on and off, to set the region and frequency of the FM radio, and to view the radio’s signal strength. You also look at the Silverlight Toolkit’s LoopSelector control and how it is used to provide an infinitely scrolling list that allows the user to set the FM radio’s frequency.

The sample code for this section resides in the /Sensors/FMRadio directory of the WindowsPhone7Unleashed.Examples directory in the downloadable sample code.

To begin, the viewmodel’s PoweredOn property controls the FMRadio’s PowerMode property (see Listing 21.2).

Setting the FMRadio’s Frequency property causes the SignalStrength property to be updated immediately. If the Frequency property is not set, you need to wait momentarily for the SignalStrength property to be updated. We therefore use another thread to wait a moment before determining whether the user needs to be informed that she should plug in headphones.

Listing 21.2. FMRadioViewModel.PoweredOn Property


public bool PoweredOn
{
    get
    {
        return FMRadio.Instance.PowerMode == RadioPowerMode.On;
    }
    set
    {
        bool state = FMRadio.Instance.PowerMode == RadioPowerMode.On;
        if (state != value)
        {
            FMRadio.Instance.PowerMode = value
                ? RadioPowerMode.On : RadioPowerMode.Off;

            if (value)
            {
                ThreadPool.QueueUserWorkItem(
                    delegate
                        {
                            Wait(2000);
                            if (SignalStrength < 0.01)
                            {
                                MessageService.ShowMessage(
                                 "This phone uses your headphones "
                                 + "as an FM radio antenna. "
                                 + "To listen to radio, connect your headphones.",
                                   "No antenna");
                            }
                        });
            }

            OnPropertyChanged(() => PoweredOn);

            if (FMRadio.Instance.PowerMode == RadioPowerMode.On)
            {
                OnPropertyChanged(() => Frequency);
            }
        }
    }
}


The custom Wait method blocks the thread without resorting to a Thread.Sleep call. For more information see Listing 15.2, “Wait Method.”

The view contains a ToggleSwitch that is bound to the viewmodel’s PoweredOn property, as shown:

<toolkit:ToggleSwitch IsChecked="{Binding PoweredOn, Mode=TwoWay}"
                      Header="Power" />

The binding is two way, so that when the user taps the switch, the viewmodel property is updated.

The viewmodel’s Frequency property sets the frequency of the FMRadio. The minimum allowed frequency is determined using the frequency range for the current region. See the following excerpt:

public double Frequency
{
    get
    {
        return FMRadio.Instance.Frequency < 1
            ? frequencyMin : FMRadio.Instance.Frequency;
    }
    set
    {
        if (Math.Abs(FMRadio.Instance.Frequency - value) >= .1)
        {
            FMRadio.Instance.Frequency = value;
            OnPropertyChanged(() => Frequency);
        }
    }
}

The viewmodel exposes the RadioRegion enum values as a property, which is bound to a Silverlight Toolkit ListPicker in the view, as shown:

<toolkit:ListPicker ItemsSource="{Binding Regions}"
                    SelectedItem="{Binding Region, Mode=TwoWay}" />

Rather than hard-coding each enum value in the viewmodel, a custom class called EnumUtility is used to create an IEnumerable<RadioRegion> (see Listing 21.3).

Listing 21.3. EnumUtility Class


public static class EnumUtility
{
    public static IEnumerable<TEnum> CreateEnumValueList<TEnum>()
    {
        Type enumType = typeof(TEnum);
        IEnumerable<FieldInfo> fieldInfos
            = enumType.GetFields().Where(x => enumType.Equals(x.FieldType));
        return fieldInfos.Select(
            fieldInfo => (TEnum)Enum.Parse(enumType,
                                           fieldInfo.Name, true)).ToList();
    }
}


The viewmodel’s Regions property and its backing field are defined as shown:

readonly IEnumerable<RadioRegion> radioRegions
                = EnumUtility.CreateEnumValueList<RadioRegion>();

public IEnumerable<RadioRegion> Regions
{
    get
    {
        return radioRegions;
    }
}

The complement of the Regions property is the viewmodel’s nonplural Region property, which exposes the FMRadio’s CurrentRegion property, as shown:

public RadioRegion Region
{
    get
    {
        return FMRadio.Instance.CurrentRegion;
    }
    set
    {
        FMRadio fmRadio = FMRadio.Instance;
        if (fmRadio.CurrentRegion != value)
        {
            fmRadio.CurrentRegion = value;
            UpdateFrequencyRanges();

            OnPropertyChanged(() => Region);
        }
    }
}

When the Region property is changed, the list of valid frequency ranges is updated using the viewmodel’s UpdateFrequencyRangesMethod.

The Frequency double value is split into two parts so that its fractional and nonfractional components can be binding sources for two controls in the view; two Silverlight Toolkit LoopingSelector controls allow the user to select the desired frequency (see Figure 21.2).

Image

Figure 21.2. Frequency is modified using LoopingSelector controls.

For information on the LoopingSelector, see Chapter 9, “Silverlight Toolkit Controls.”

The radio frequency is split into two using two custom NumericDataSource objects. The nonfractional segment is defined by the viewmodel field frequenciesNonFractional, while the fractional component is defined by the field frequenciesFractional, as shown:

readonly NumericDataSource frequenciesFractional
                    = new NumericDataSource { Minimum = 0, Maximum = 9 };

The SelectedItem of the frequenciesFractional field is set to the fractional component of the current frequency in the viewmodel constructor, like so:

frequenciesFractional.SelectedItem = (int)(Frequency % 1 * 10);

The viewmodel subscribes to each NumericDataSource object’s SelectChanged event. When the nonfractional part of the frequency is changed, the handler determines the new frequency by adding the existing fractional part (frequency modulo 1) to the selected value, as shown:

void HandleFrequencyLargeChanged(object sender, SelectionChangedEventArgs e)
{
    NumericDataSource dataSource = (NumericDataSource)sender;
    var newFrequency = Frequency % 1 + dataSource.SelectedNumber;
    if (newFrequency > frequencyMax || newFrequency < frequencyMin)
    {
        return;
    }
    Frequency = newFrequency;
}

When the fractional part of the frequency is changed, the handler determines the new frequency by removing the fractional component of the existing frequency and adding it to the selected item, as shown:

void HandleFrequencyFractionPartChanged(
        object sender, SelectionChangedEventArgs e)
{
    NumericDataSource dataSource = (NumericDataSource)sender;
    double f = Frequency;
    double newFrequency = f - f % 1 + dataSource.SelectedNumber / 10.0;
    if (newFrequency > frequencyMax || newFrequency < frequencyMin)
    {
        return;
    }
    Frequency = newFrequency;
}

When the viewmodel is instantiated, or when the Region property changes, the UpdateFrequencyRanges method is called, which creates a new NumericDataSource object for the nonfractional frequency component and sets its Minimum and Maximum properties according to the region’s frequency range (see Listing 21.4).

Listing 21.4. UpdateFrequencyRanges Method


void UpdateFrequencyRanges()
{
    if (frequenciesNonFractional != null)
    {
        frequenciesNonFractional.SelectionChanged -= HandleFrequencyLargeChanged;
    }

    NumericDataSource dataSource = new NumericDataSource();

    switch (Region)
    {
        case RadioRegion.Europe:
            FrequencyMin = 88;
            FrequencyMax = 108;
            dataSource.Minimum = 88;
            dataSource.Maximum = 108;
            break;
        case RadioRegion.Japan:
            FrequencyMin = 76;
            FrequencyMax = 90;
            dataSource.Minimum = 76;
            dataSource.Maximum = 90;
            break;
        case RadioRegion.UnitedStates:
            FrequencyMin = 88.1;
            FrequencyMax = 107.9;
            dataSource.Minimum = 88;
            dataSource.Maximum = 107;
            break;
    }

    double tempFrequency = Frequency;
    dataSource.SelectedNumber = (int)(tempFrequency - tempFrequency % 1);
    FrequenciesNonFractional = dataSource;
    dataSource.SelectionChanged += HandleFrequencyLargeChanged;
}


The viewmodel also indicates the current signal strength of the radio and contains a SignalStrength property that gets its value directly from the FMRadio instance, as shown:

public double SignalStrength
{
    get
    {
        return FMRadio.Instance.SignalStrength;
    }
}

The device FM radio can also be controlled by the user using the hardware volume buttons, which, when pressed (while the FM radio is powered on) causes an onscreen menu to be displayed. The onscreen menu allows the user to skip forward and backward between stations. The FMRadio class provides no events for monitoring changes to frequency or signal strength, nor any other property. To monitor changes to the FMRadio instance, a Timer is used, which periodically polls the FMRadio for changes.


Tip

It is generally best to avoid the use of timers within your app, as it can unduly degrade battery life. Use a Timer only if your app absolutely requires the functionality and it is not obtainable by other means.


The Timer is instantiated in the viewmodel constructor, like so:

timer = new Timer(HandleTimerTick, null, timerIntervalMs, timerIntervalMs);

When the interval elapses, the Timer instance calls the HandleTimerTick method. If the previous frequency value is markedly different from the current frequency value, a property changed event for the Frequency property is raised and the UI updated, as shown:

double previousFrequency;

void HandleTimerTick(object state)
{
    OnPropertyChanged(() => SignalStrength);
    double frequency = FMRadio.Instance.Frequency;
    if (Math.Abs(previousFrequency - frequency) >= 0.1)
    {
        double fraction = frequency % 1;
        Deployment.Current.Dispatcher.BeginInvoke(delegate
        {
            frequenciesFractional.SelectedNumber = (int)(fraction * 10);
            frequenciesNonFractional.SelectedNumber
                                          = (int)(frequency - fraction);
            OnPropertyChanged(() => Frequency);
        });
    }
    previousFrequency = frequency;
}

The FMRadioView page displays the raw frequency value using a TextBlock. A TextBlock and a ProgressBar are used to display the SignalStrength property (see Listing 21.5).


Note

It is critical to set a number of properties of the LoopingSelector for it to be displayed correctly, in particular, its ItemSize, Width, and Height.


Listing 21.5. FMRadioView.xaml (excerpt)


<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <StackPanel>
        <toolkit:ToggleSwitch IsChecked="{Binding PoweredOn, Mode=TwoWay}"
                              Header="Power" />

        <TextBlock Text="frequency" Style="{StaticResource LabelTextStyle}" />
        <TextBlock Text="{Binding Frequency}"
                   Style="{StaticResource ValueTextStyle}" />

        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
            <toolkitPrimitives:LoopingSelector
                DataSource="{Binding FrequenciesNonFractional}"
                Margin="12" Width="128" Height="128" ItemSize="128,128"
                ItemTemplate="{StaticResource LoopNumberTemplate}"
                IsEnabled="{Binding PoweredOn}" />
            <TextBlock Text="." FontSize="54" VerticalAlignment="Bottom"
                    FontFamily="{StaticResource PhoneFontFamilySemiBold}" />
            <toolkitPrimitives:LoopingSelector
                DataSource="{Binding FrequenciesFractional}"
                Margin="12" Width="128" Height="128" ItemSize="128,128"
                ItemTemplate="{StaticResource LoopNumberTemplate}"
                IsEnabled="{Binding PoweredOn}" />
        </StackPanel>

        <TextBlock Text="region" Style="{StaticResource LabelTextStyle}" />
        <toolkit:ListPicker ItemsSource="{Binding Regions}"
                            SelectedItem="{Binding Region, Mode=TwoWay}" />

        <TextBlock Text="signal strength"
                   Style="{StaticResource LabelTextStyle}" />
        <TextBlock Text="{Binding SignalStrength}"
                   Style="{StaticResource ValueTextStyle}" />
        <ProgressBar Value="{Binding SignalStrength}" Maximum="5" />
    </StackPanel>
</Grid>


A DataTemplate called LoopNumberTemplate is used for presenting the numbers within the LoopingSelectors. It is defined as a page resource as shown:

<phone:PhoneApplicationPage.Resources>
    <DataTemplate x:Key="LoopNumberTemplate">
        <Grid>
            <TextBlock Text="{Binding}" FontSize="54"
                       FontFamily="{StaticResource PhoneFontFamilySemiBold}"
                       HorizontalAlignment="Center"
                       VerticalAlignment="Center" />
        </Grid>
    </DataTemplate>
</phone:PhoneApplicationPage.Resources>

Figure 21.3 shows the FMRadioView page with the radio powered on and the region list expanded. The signal strength field is periodically updated.

Image

Figure 21.3. FMRadioView page

The FM radio is a core piece of phone hardware that allows users to listen to audio without having a data connection. The built-in radio app provides the bare essentials for listening, and having programmatic access to the FM radio allows you to create an advanced radio app or to enhance your audio app by including radio capabilities.

Summary

This chapter began by looking at using the XNA microphone API to record audio. You had some fun with the microphone and created a helium voice app that allows the user to record audio and to modulate the pitch of the recorded audio during playback.

The chapter then looked at the FM radio and how to create a custom radio app that uses a Silverlight Toolkit LoopingSelector to change the FM radio’s frequency.

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

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