Sep 14 2012

Sidereal Time Calculator

Category: MobileJoel Ivory Johnson @ 04:12
Download Code (1.15 MB)
   

Introduction

There are a number of applications available on various mobile devices that allow you to aim your device at the sky and identify various heavenly bodies. I find the ability of identifying a body based on a user's location and the device's orientation fascinating. I find it even more fascinating when I see physical hardware acting on this information; the telescope I have uses GPS (to get my location and the current time) and uses this information to automatically move the telescope to the orientation needed to see selected body.

I now have a pretty good understanding of how it works. A significant amount of the calculations involved are based on time and another part is based on coordinate conversion. With the right time conversions you'll have enough information to get the orientation of the stars. (Getting the orientation of the planets and moon requires a little more work, but the stars have no apparent motion with respect to the solar system). I only want to talk about time for now, as there's more than enough information on time to fill an article.

Table of Contents

Terms and Time Units

Time is usually described in terms of some cyclic process or event where the units of time are from counting the cycles/events. This could be from the vibration of a crystal, the passing of some celestial, or some other event. Historically the apparent motion of the sun and moon have been used as the periodic event around which our time system was based. We've all used the units of time that are derived from these events; hours, minutes, seconds, years, and months along with the terms AM, PM, AD, and BC, and degrees. Let's dissect the physical events behind these terms.

Roman Calendar

The Roman calendar is said to have been invented by Romulus (the founder of Rome) around 753 years BC. This calendar had 10 months with the vernal equinox being the first month. The calendar had 304 days plus an additional number of winter days that stretched from December to the following month that were not part of any month on the calendar.

Julian Calendar

The Julian calendar is a modified version of the Roman Calendar. It has 365 days divided into 12 months. Once every 4 years a leap day is added.  It sounds very much like the civil calendar that we use today with the exception we don't have leap days on years divisible by 400 but not a millennium. With enough time solar and seasons events would begin to creep to other parts of the calendar. This was corrected with the Gregorian calendar.

Gregorian Calendar, Astronomical Year, and Julian Dates

The Gregorian calendar (also called the Western Calendar or Civil Calendar) is the name of the calendar that most of the world knows, loves, and uses today. The namesake for the calendar is Pope Gregory XIII. The number of times the earth rotates during its orbit around the sun is 365 times plus some fractional units (approximately 0.2524).   The Julian calendar made an attempt to correct this by introducing a leap year every 4 years. This contribution slowed the rate at which the seasons migrated on the calendar but didn't stop it all together. Pope Gregory XIII's contribution to the calendar was to not have a leap year if the year was divisible by 400 and is a  millennial year. The last day of the Julian calendar was Thursday 4 October 1582. The day that came after this was the first day of the Gregorian calendar; Friday 15 October 1582. The date range of 5-14 October (inclusive) doesn't exist - something that needs to be remembered for time conversions that cross this boundary.

Julian Date

Another commonly used calendar is counting the number of days since noon of 1 January 4713 B.C. This is also called a Julian date. Noon of 1 January 4713 is Julian date 0. Midnight between 1 January 4713 and 2 January 4714 is Julian date 0.5. Note that the time of day is a part of the Julian date as a fractional unit. For more recent dates the number used to express the Julian date is over 2,400,000 million. To avoid dealing with unnecessarily large numbers there's also the Modified Julian Date (MJD) which counts the number of days since midnight 17 November 1858. Note that MJD starts at midnight while JD starts at noon. So the time units in these two date expressions will have a difference of 0.5 for the digits after the decimal point. You may also hear of a Julian Date Number, which is just the integer portion of the Julian Date.  Dates of this form are of special significance to astronomical calculations.

AD, BC, and Astronomical Year

One of the oddities about the system of tracking years is that there is no year zero. The first year of the calendar , based on the reckoned conception of Jesus Christ of the Christian religion, is 1 AD (AD = Anno Domino, Latin for "Year of the lord", also written as "CE" for Common Era). The year immediately proceeding this is 1 BC (BC = Before Christ. Sometimes written as BCE for Before Common Era). When doing astronomical calculations no one wants to deal with the lack of the zero. So there is also the concept of the Astronomical Year. Astronomical years for the most part align with our current system of tracking years. So 1984 AD is also the Astronomical Year 1984. The difference is apparent when you look at years in the BC range. 1 BC is the astronomical year 0. 2 BC is the astronomical year -1, and so on.

Solar Day

A solar day is the period over which the sun apparently moves on a path and returns to it's starting point. I say apparent because while we know this phenomenon is from the earths rotation. But the movement of the sun is still described in geocentric terms (sun rise, sun set, so on). The sun's path varies slightly from one day to another so it doesn't really return to it's starting point. So the meridian is used as the starting line. The meridian is the imaginary circle around the globe stretching from the north pole to the south pole. The  sun and other bodies reach their highest point in the sky at the meridian and then go from climbing to declining. This line is also used to divide a day in half. Once the sun goes past the meridian the time is labeled as post meridian (P.M.). When it passes this line on the opposite side of the globe we say that it is in it's before meridian. The Latin word for before is "ante", so it is referred to as A.M. (for Ante Meridian). When the sun is on the meridian it is at its highest point in the sky. This is called "solar noon." Solar noon doesn't necessarily occur at the same instance in which the local time is 12:00 PM. There are slight variations in the time at which the sun reaches this point that we tend to ignore with civil clocks.

The pathway that the sun travels around an observer us usually divided into 24 units . Note that if you divide the 360 degrees of a circle by 24 you get 15. These 24 equal units are called hours (in other words, one hour of rotation is 15 degrees). These 15 degrees may also be divided into 60 units (minutes). A minute of rotation is 15/60, or 0.25 of a degree. As you may have guessed the next level of division is to divide a minute into 60 equal parts (the second) which contains 0.26/60 of a degree. An inference that you can make from using hours, minutes, and seconds (HMS) are a rotational unit is that for every hour that passes you can approximate the rotational distance that a celestial body in the night sky will travel over a unit of time; in a 2 hour period an object will travel 60 degrees. I say approximate because if you measure the distance with a high precision you'll see that the sun and moon appear to move by a slightly different amount than 60 degrees over this time period. For casual observations this difference won't be noticeable.

Sidereal Day

If you use the sun as your reference for rotation distance it appears that the earth takes 24 hours to make one full rotation. This isn't quite correct though. The earth moves about 1 degree on its orbit around the sun each day. So the sun shouldn't be used for an accurate measurement of how far the earth has rotated. Any other star will do though. The other stars are far enough away such that their apparent position is the same regardless of where the earth is on its orbit.

Difference in sidereal and solar day.

Choose a star (other than our sun, any star will do). Every time the earth rotates that star will reach the meridian. if you used a wall clock to measure the amount of time it takes for a star to reach the meridian again you'll find it isn't quite 24 hours. It is 23 hours 56 minutes and 4 seconds. Days measured using this method are sidereal days. Because these days are a bit less than 24 hours the amount of solar days in a sidereal year is about 366.25 instead of 365.25. Since a sidereal day is shorter than a solar day on any given solar day there will exist a range of sidereal times that occur twice within the solar day.

Time zones

Our universal time system is based on the time at Greenwich. Greenwich is on the zero longitude. Observations of celestial events on it's meridian was once the foundation of our timing system. It's meridian is also called the prime meridian. As a matter of convenience we also have the concept of local time, which is derived by adding some number of minutes and hours to the time at Greenwich. The earth is divided into 40 regions that share local time. These areas, or time zones, usually have a time difference by some interval of hours from the time at Greenwich (GMT). there are some time zones that are also offset by some hour interval plus 30 minutes. The difference from the most positive offset to the most negative offset is 26 hours. On average the difference between time zones can be inferred by their longitude (recall that one hour is 15 degrees of rotation). However the time zone lines are not straight. Rather than divide small geographic areas into several time zones the time zone borders will coincide with the borders of that geographical region.

The Earth's Celestia Movements

The stars are perceivably in a fixed position. For some one that wants to be extremely technical the stars are moving at speeds that we would find to be incredibly fast relative to our position or relative to the galaxy in which they rotate. But they are so far away that their movement is imperceptible to us, allowing us to treat them as stationary bodies over short periods measured in hundreds of years. There are a few factors that impact the orientation of the stars with respect to an observer on the earth.

Of these movements the one that has the most immediate impact on an observer is the rotation of the earth. Its impacts are directly observable through the apparent path that the sun, moon, and other bodies travel through our sky. If you are looking at a body with a telescope the movement becomes more apparent unless you have a motorized telescope that automatically adjust; as you look at a body it will drift out of the view of your telescope within a minute or less. This is the movement with which I am most concerned.

The Earth advances about 1 degree per day as it travels around the sun. With each day that passes the part of the celestial sphere that becomes unobservable due to competing light with the sun will slightly shift. This will mean that some stars will not be visible during certain parts of the year. While their direction can still be determined with the exception of an eclipse you won't be able to observer these stars during the day. Also note that this impacts the time of sun rise, sun set, and the number of hours in a day in which sunlight is visible (there's less hours of daylight in the winter). For now I'm not particularly concerned with what hours a star will be visible during my general case scenarios. Since I only use my telescope when time and weather unexpectedly permit I don't do much fore planning. If you've got interest in this I would suggest first explore the definitions on the various definitions of twilight (ex: civil vs. nautical vs. astronomical).

The third movement occurs over the course of about 25,700 years. It causes a slight circular drift of the direction in which the earth's rotational axis is pointing.  It can be addressed through a time dependent coordinate space adjustment. But I don't want to talk about coordinate conversions in this post. Just in case you are curious, the Earth's shift of the direction of it's rotational axis is about 1 degree every 71 years, so we can ignore this movement for now and it won't have a significant impact on our results.

Local Sidereal Time

Because of the continually varying orientation of the earth with respect to the sun we don't want to use a solar day for calculations of where stars are located with respect to the earth. The sidereal time is what we want. To get the sidereal time we need to know the Julian date. We'll get the Julian date from the civil (Gregorian) date. I've made a set of extensions for getting these dates. In calculating the Gregorian date you will need to be able to calculate how far we are into a day in decimal format. 12:00 Noon would be 0.5 into a day, 18:00 is 0.75 into a day, and so on. These can be easily calculated from a date or a time.

 static double ToFractionalDay(this TimeSpan sourceTime)
{
    return sourceTime.TotalHours / 24d;
}

 static double ToFractionalDay(this DateTime sourceDate)
 {
     return sourceDate.TimeOfDay.ToFractionalDay();
 }

These are written as extension methods because I find the calling syntax to be cleaner.  Now that we know how far we are into a day we can use that information to calculate the Julian date.

public static double ToJulianDate(this DateTime  sourceDate)
{
    double y, m, c;
    if (sourceDate.Month <= 2)
    {
        y = sourceDate.Year - 1;
        m = sourceDate.Month + 2;
    }
    else
    {
        y = sourceDate.Year;
        m = sourceDate.Month;
    }

    double leapDayCount  = (sourceDate > GregorianReformDate) ? (2 - Math.Floor(y / 100) + Math.Floor(y/400) ) : 0;
    if (sourceDate.Year < 0)
        c = (int)(365.25 * (double)sourceDate.Year - 0.75);
    else
        c = (int)(365.25 * (double)sourceDate.Year);
    double d = Math.Floor(30.6001 * (m + 1));
    var retVal = leapDayCount +c+ d + sourceDate.Day + 1720994.5;
    return retVal + sourceDate.ToFractionalDay();;
}

There's something I've not mentioned. All of these calculations are centric to the 0 longitude and are based on the GMT time zone without daylight savings. If you want to adjust the results to figure out the orientation of your time zone with respect to the rest of the observable universe you'll need to make an adjustment for your longitude. If your longitude is to the west of GMT express it with a negative number, otherwise use a positive number. Divide this number by 15 and add it to the sidereal time.  I live 84 degrees west of the 0 longitude. So to get the local sidereal time I do the following.

localSiderealTimeClock.CurrentTime = DateTime.Now.ToUniversalTime().ToSiderealTime().Add(TimeSpan.FromHours(-84d/15d));

The local sidereal time describes your rotational displacement from the direction of the vernal equinox (♈). While there's no up in space the direction formed by drawing a line from the sun to the earth while it is in the vernal equinox is the foundation of a couple of celestial coordinate systems (Ecliptic, which is based on the earth's orbit around the sun and equitorial which is based on the earth's rotation).

Correcting Variance's in the User's Clock

User's both intentionally and unintentionally may have their clocks set to an incorrect time. One way of avoiding problems from this is to make use of NTP (Network Time Protocol). I've written on obtaining NTP time before. You can read about it here.  While it is possible to continually poll an NTP source for the time I only grab it once every few minutes. When I get the NTP time the difference between the user's close and the NTP time source is saved and added to the value that comes from the user's clock. The expectation is that between refreshes for the NTP time the user's clock will reliably count seconds without any significant drift (if it doesn't, then the user needs a new device!).

NtpClient _ntpClient;
TimeSpan _ntpOffset;
DateTime _lastNtpRefresh = DateTime.MinValue;
TimeSpan _ntpRefreshPeriod = TimeSpan.FromMinutes(1);

public MainViewModel()
{
    _ntpClient = new NtpClient();
    _ntpClient.TimeReceived += new EventHandler<NtpClient.TimeReceivedEventArgs>(_ntpClient_TimeReceived);
    //Default the difference to zero and provisionally assume the user's
    //clock is correct until we receive information of otherwise
    _ntpOffset = TimeSpan.Zero;
}

void _ntpClient_TimeReceived(object sender, NtpClient.TimeReceivedEventArgs e)
{
    _lastNtpRefresh = DateTime.Now;
    DateTime NtpTime = e.CurrentTime;
    // NTP time is always in universal time, so we need to adjust the system clock 
    // to universal before getting the time offset. 
    _ntpOffset = NtpTime.Subtract(DateTime.Now.ToUniversalTime());
}

//Use thie method to get time adjusted for NTP offset.
DateTime GetDate()
{  
     return DateTime.Now.Add(_ntpOffset);
}

Displaying the Time

If you've looked at clocks that show the time in more than one time zone chances are the numbers shown for minutes and seconds were the same for most of the time zones. This isn't the case when looking at both civil time and sidereal time. The seconds will be out of sync. Because of personal preference (I simply find this displeasing) I'm updating the seconds simultaneously. I've made two controls for displaying the time; an analog clock and a digital clock. Both can display the time in 12 hour or 24 hour format.

 

Digital display of sidereal clock.

Displaying the 24 hour time with an analog clock may be new to many. I took a look at several 24-hour analog clocks in images online. Some started with midnight at the top of the clock and others with midnight at the bottom. I decided on having the midnight (0) hour at the bottom. This places noon at the top of the clock. Displaying 24-hour time in sidereal format is something that I'm still playing with though. While I have a circular gauge-like clock in place I'm going to change this from a user control to a templated control and expose new options on how it's to be rendered. (hints of the forthcoming changes are visible in the source code).

 
Display of analog class
 
Options screen

Help Files

In experimenting with something else I've included a Help HTML file for the application. The help file is stored in the application as content but unpackaged the first time the application is run. To prevent the unnecessary unpacking of files every time the application run it checks to see if a file already exists before unpacking it.

public class ContentUnpacker
{
   
    static string[] ContentFileList = { "About.html", "Sidereal.png", "appTimes.png", "settings.png" }; 
    public static void UnpackAllFiles()
    {
        IsolatedStorageFile sourceArchive = IsolatedStorageFile.GetUserStoreForApplication();
        if (!sourceArchive.DirectoryExists("Content"))
            sourceArchive.CreateDirectory("Content");


        foreach (string s in ContentFileList)
        {
            string targetName = String.Format("Content/{0}", s);
            string sourceName = String.Format("Content/{0}", s);
            if(!sourceArchive.FileExists(targetName))
            {
                var outStream = sourceArchive.CreateFile(targetName);
                var contentStream = Application.GetResourceStream(new Uri(sourceName, UriKind.Relative));
                using (var br = new BinaryReader(contentStream.Stream))
                {
                    var length = (int)br.BaseStream.Length;
                    outStream.Write(br.ReadBytes(length), 0, length);
                }
            }   
        }
    }
}

The about page contains only a web browser element that is given the URL to the help files. The entirity of the code that's behind the about page is below.

public partial class AboutPage : PhoneApplicationPage
{
    public AboutPage()
    {
        InitializeComponent();
    }

    private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
    {
        aboutBrowser.Navigate(new Uri("Content/About.html", UriKind.Relative));
    }
}

Where to from Here

There's a number of applications, some related to astronomy and others not that I have in mind for which this functionality will be useful. One example of something not realted to astronomy was an augmented reality application I had in mind for which I wanted the application to shade the models projected on the screen according to the location of the sun. One of the astronomy related applications is that I have access to a room with projectors and screens on all 4 walls. Just for the fun of it I wanted to to get the computers that control the projectors on all 4 walls communicating with each other and displaying a 360 view of the solar system. Getting the sidereal time is a stepping stone for some of these other applications but not the end goal itself. I plan to write on how these other ideas progress as each one of them gets implemented.

Figures and Illustrations

Revision History

  • 2012-09-14 - Initial publication

 

 

 

 

Tags: , ,

Jun 29 2012

Windows Phone 8 Marketplace for 180 Countries

Category: MobileJoel Ivory Johnson @ 07:23

We've come a long way since Microsoft initially launched it's mobile Marketplace. The original Windows Marketplace for Mobile was available to about 30 nations. If you didn't live in one of those nations and you wanted an application that was only distributed through the Marketplace then there was no way to get it. On Windows Phone 7 (unlike Windows Mobile) the Marketplace was the *only* way to load programs, so the implications were even different. At the time of this writing Microsoft has the Marketplace available in 60 countries. But they have  recently announced that the Marketplace for Windows Phone 8 will be available in 180 countries and will make in-app purchasing available (increasing your opportunity for making money). 

Source: Windows Phone Team Blog

Tags: , ,

Apr 17 2012

Compiling Same Code on Windows Phone 7 and Windows 8 Metro

Category: Desktop and Server | MobileJoel Ivory Johnson @ 12:27
Download Code (1.14 MB)

I was thinking about how to write code that will work on both Windows Phone 7 and Windows 8 (Metro). In theory some of the techniques that could be used are well known. But I wanted to try them out. So I decided to try some things out in a piece of throw away code. This gives me some liberty to experiement without worrying about the long term consequences of having not chosen the "best" way. For my throw away application I decided to make an application that would grab some one's Facebook friend list and display it on the screen. So this code will necessarily need to authenticate the user against Facebook. If you've not worked with the Facebook APIs before don't worry about it too much. You should still be able to follow along since this isn't digging deeply into what's available.

I've managed to use some of the techniques to make a Windows Metro version of RestSharp. I'll talk about that in another post.

Register your Application

Before you begin doing any coding you'll want to register your new Facebook application in the Facebook developer portal. This also means that you need to have a facebook account. Because I didn't want to bother my associates with test items in my feed I also created a secondaty facebook account for testing. The portal is available at developer.facebook.com. Clicking on menu item "Apps" at the top of the screen will take you to a page that shows all of your registered applications (if any) and will let you register more. If this is your first time using the portal you'll have no applications. I'll walk you through registering an application. I'll only draw your attention to options as necessary. Options that I don't mention can be left in their default state (which will most likely be blank).

Click on the "Create New App" button. You'll get prompted with a dialog in which you will need to enter the name of the application that you are creating. The dialog will automatically validate the name that you've entered once you've stopped typing and will let you know if a chane needs to be made. Once you have a name that the dialog considers valid click on the "Continue" button.

The next dialog is a Captcha. I really dislike Captchas. While I understand that these dialogs exists to make sure that a bot is not registering the new application the problem that I have with them is that more times than not I'm going to get the entry wrong a few times (ex: is that third character a lowercase 'L', upper case 'L', or an uppercase 'I'?).

Once you pass the Captcha you'll get an opportunity to specify the other details for your application and a chance to change the application's icon and category. It may not be obvious which option to select for "Select how your app integrates with Facebook." None of the available options has a description that you would automatically assocate with a Windows Phone or Metro application. Select "Website". During development it doesn't matter if the website that you've specified exists or not. But you will need to enter an address to a page that would  in theory process the login information. Once you've entered this information select "Save Changes." There are three pieces of information that are on this page that you'll be copying into your application later on: App ID, App Secret, and the Site URL. Keep this page open so that you can copy the information when I refer to it later.

 

 

Now to make your mobile application. At the time of this writing you'll need to use Visual 2010 for Windows Phone applications and Visual Studio 11 for Windows Metro applications. Most of what will be done will be similar enough such that I can talk about the procedure to be followind in parallel and note the occasional differences as they arise.

Creating the New Application Projects

I created both a Silverlight Application for Windows Phone and a Windows 8 Metro project. There are going to be significant blocks of code that will be identical between these two projects. When this occurs I'll only create one version of the file but will have it referenced in both projects. Linked files can easily be recognized by the arrow that shows in the lower-left corner of their icon. If you've never linked files before you can find more information on it here.

How linked files appear in Visual Studio.

Conditionally Compiled Code

There still may be sections in thee files that are specific to Windows Phone or Windows 8. These sections will be conditionally commented out depending on the platform on which the code is being compiled. For code that is specific to Windows 8 Metro applications the code is wrapped in #if NETFX_CORE/#endif blocks. For Windows 7 code I've wrapped the code in #if SILVERLIGHT/#endif blocks. I could have also used #if WINDIWS_PHONE/#endif blocks

If there is a large block of code that is specific to one platform and not the other instead of using the conditional compilation directives one could choose to just include the file containing the code in one file and not in the other. You'll see this done with most of the UI code (the XAML, while similar, is not shared between the two projects).

Creating the Authentication Page

Before I can call any method in Facebook I need to authenticate the user. Facebook uses a form of open authentication. So we will need to use a web browser in the application. Add a new page to your project caled "AuthenticationPage.xaml." The only element that is necessary in this page will be the web browser.  Many of the differences that you will encounter between Metro and Windows Phone 7 are in UI related code. There's a slight difference in the element that needs to be used for representing a browser element.

 <WebView 
     x:Name="AuthenticationWindow" 
     HorizontalAlignment="Stretch" 
     VerticalAlignment="Stretch"
     LoadCompleted="AuthenticationWindow_LoadCompleted" 
/>
 
<phone:WebBrowser  
   Name="AuthenticationWindow" 
   HorizontalAlignment="Stretch"
   VerticalAlignment="Stretch" 
   Navigated="AuthenticationWindow_Navigated" 
/>
Browser element for Windows Metro
 
Browser element for Windows Phone 7

The Application Constants

There are a few elements of data to which we will need. They are all in the entries for the app that you registered with Facebook. I'll need to use these in more than one place. The logic needed for authentication is simple and I left it in the UI code.  I separated the logic that will be shared in the Metro and WP7 versions of this application into a partial class definition. That

static class ApplicationConstants
{
	const string REDIRECT_URL = "https://mysite.com/SomePostAuthenticationPage.html";
	const string APP_ID = "_YOUR_APP_ID_";
	const string APP_SECRET = "_YOUR_APP_SECRET_";
	const string PERMISSIONS = "read_friendlists";
}

You'll need to substitute the values for your own application in these constants (except for the PERMISSIONS constant).

Creating the Authentication Page

An extremely high level overview of what occurs in the code-behind for the authentication page is that it will load the Facebook Open Authentication page and then monitor the path to which the user navigates. If the user has authenticated the browser will be directed to the site that had been registered for the application earlier. When this occurs there will be an authentication code appended to the URL. That code will be parsed out and exchanged for a token. There's not much the UI is really doing. So the codebehind looks simple. 

public partial class AuthenticationPage : PhoneApplicationPage
{
   public AuthenticationPage()
   {
      InitializeComponent();
      ViewModel = (App.Current as J2i.Net.FacebookAuthenticationTest.App).ViewModel;
      ViewModel.AuthenticationAttempted = true;
   }

   private void AuthenticationWindow_LoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e)
   {
   }

   private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
   {  
      PrepareForAuthentication();
   }

   private void AuthenticationWindow_Navigated(object sender, System.Windows.Navigation.NavigationEventArgs e)
   {
   }

   private void AuthenticationWindow_Navigating(object sender, NavigatingEventArgs e)
   {
      ProcessWebPageLoad(e.Uri.ToString());
   }
}
Code-behind for the Windows Phone version of the application

Handling Code Differences with Partial Classes

Where there is a slight difference in the code behind for the two pages the rest of the logic is the same. Instead of duplicating it I put the common logic in a partial class definition. The partial class has the same name as the class for the page. So at compile time the code from the code-behind and the partial class will be compiled into a single class. The common code is in AuthenticationPage.xaml.shared.cs.  The first method that the code-behind references from the shared file is PrepareForAuthentication(). This method will build the authentication URL which contains parameters to allow the application to identify itself, what permissions are needed, and a random string that should be passed for security reasone. If you want to see more information on these parameters they can be found in the Facebook authentication documentation.

void PrepareForAuthentication()
{
    _unique = Guid.NewGuid().ToString();

    string targetUrl = String.Format("https://www.facebook.com/dialog/oauth?" +
                                        "client_id={0}" +
                                        "&redirect_uri={1}" +
                                        "&scope={2}" +
                                        "&state={3}",
                                        // for touch/phone friendly version of the authentication page
                                        //"&display=touch",
                                        Uri.EscapeUriString(ApplicationConstants.APP_ID),
                                        Uri.EscapeUriString(ApplicationConstants.REDIRECT_URL),
                                        Uri.EscapeUriString(ApplicationConstants.PERMISSIONS),
                                        Uri.EscapeUriString(_unique));
    AuthenticationWindow.Navigate(new Uri(targetUrl));
}

The ProcessWebPage() method looks at the URL to which the browser is navigating to see whether or not it is our redirect URL. If it's not then nothing happens. If it is then the query parameters are extracted. Theparameters we are most interested in are code and state since these will indicate a successful authentication attempt. The code will be needed to request an access token for making Facebook API calls.

void ProcessWebPageLoad(string targetUriString)
{
   if (targetUriString.IndexOf(ApplicationConstants.REDIRECT_URL) == 0)
   {

      string _code = GetQueryParam(targetUriString, "code");
      string _state = GetQueryParam(targetUriString, "state");
      string _error = GetQueryParam(targetUriString, "error");
      string _errorReason = GetQueryParam(targetUriString, "error_description");
      string _errorDescription = GetQueryParam(targetUriString, "error_reason");

      if (
         (!String.IsNullOrEmpty(_code)) &&
         (!String.IsNullOrEmpty(_state)) &&
         (_unique.Equals(_state))
      )
      {
         ExchangeCodeForAccessToken(_code);
      }
   }
}

Requessting the access token is another web call, but it's not one for which we need the browser. It doesn't return anything user friendly so we can make the call without using the browser. Getting the access token is just a matter of building a URL and grabbing the token from the results.

void ExchangeCodeForAccessToken(string code)
{
    //Building the Request URL
    string targetUrl = String.Format("https://graph.facebook.com/oauth/access_token?" +
                                        "client_id={0}" +
                                        "&redirect_uri={1}" +
                                        "&client_secret={2}" +
                                        "&code={3}",
                                        Uri.EscapeUriString(ApplicationConstants.APP_ID),
                                        Uri.EscapeUriString(ApplicationConstants.REDIRECT_URL),
                                        Uri.EscapeUriString(ApplicationConstants.APP_SECRET),
                                        Uri.EscapeUriString(code));
    var webRequest = HttpWebRequest.CreateHttp(targetUrl);

    //Request the URL
    webRequest.BeginGetResponse((o) =>
    {
        //Stuff the response into a string
        var response = webRequest.EndGetResponse(o);
        var rs = response.GetResponseStream();
        StreamReader sr = new StreamReader(rs);
        var responseString = sr.ReadToEnd();

        //Get the access token and its expiration date
        string accessToken = GetQueryParam(responseString,"access_token");
        DateTime expirationDate = DateTime.MinValue;
        string expiresString = GetQueryParam(responseString, "expires");

        if (!String.IsNullOrEmpty(expiresString))
        {
            int expireTime;
            if (int.TryParse(expiresString, out expireTime))
            {
                expirationDate = DateTime.Now.AddSeconds(expireTime);
            }
        }

        //If we successfully got a token and expiration save them, start grabbing the
        //friend list, and then navigate to the previous page
        if (!String.IsNullOrEmpty(accessToken) && (expirationDate != DateTime.MinValue))
        {
            ViewModel.AccessInfo = new AccessInfo() { ExpirationDate = expirationDate, Token = accessToken };
            ViewModel.SaveAccessToken();
            ViewModel.UpdateFriendList();
            ReturnToPreviousPage();
        }
    }, null);
}

Navigation is a little different on Windows Phone 7 and Windows 8. So I added a ReturnToPreviousPage() method in both the code-behind for the Metro and Windows Phone 7 versions of the application. Each of which contains the appropriate version of the navigation code.

void ReturnToPreviousPage()
{
   ViewModel.DispatchInvoke(() =>
   {
      (Window.Current.Content as Frame).GoBack();

   }
   );
}
 
void ReturnToPreviousPage()
{
   ViewModel.DispatchInvoke(() =>
   {
       NavigationService.GoBack();
   }
   );
}
Windows 8 Metro Version
 
Windows Phone Version

Retreiving the Friend List

The code for retrieving the friend list looks identical on both platforms. Facebook may not return the entire friend list in one call. If there are more friends to be retrieved in the return structure from the call there will be an address in a member in the return structure named Next (nested in Paging) which contains the address to the next page of the results. I'm not parsing the results myself but am instead making use of JSON.Net by James Newton-King. The results are parsed and accumulated into a temporary list. Once I know that I have all the results they are moved to the bindable list that drives the list box on the UI.

public void UpdateFriendList(string targetUrl = null, List previousUserList = null)
{
    if (AccessInfo == null)
        return;
    if (previousUserList == null)
        previousUserList = new List();

    var request = System.Net.HttpWebRequest.CreateHttp(targetUrl ?? String.Format(FRIEND_LIST_REQUEST, AccessInfo.Token));
    request.BeginGetResponse((o) =>
        {
            var response = request.EndGetResponse(o);
            var responseStreamReader = new StreamReader(response.GetResponseStream());
            string friendListText = responseStreamReader.ReadToEnd();
            Newtonsoft.Json.JsonSerializer s = new JsonSerializer();
            Data.FriendListResponse v = Newtonsoft.Json.JsonConvert.DeserializeObject(friendListText);
            if ((v != null) && (v.Data != null))
            {
                foreach (var user in v.Data)
                {
                    previousUserList.Add(user);
                }
            }
            if ((v!=null) && (v.Paging != null) && (v.Paging.Next != null))
                UpdateFriendList(v.Paging.Next, previousUserList);
            else
            {
                previousUserList.Sort((a1, a2) => { return a1.Name.CompareTo(a2.Name); });
                DispatchInvoke(() =>
                    {
                        FriendList.Clear();
                        foreach (var u in previousUserList)
                            FriendList.Add(u);
                    }
                );
            }

        }, null);
}

Dispatching Differences

There is a rule in Windows Platforms that you cannot modify the UI from a seconday thread. On the XAML based platforms you can ensure calls that modify the UI are marshalled to the UI thread by making use of an object called the Dispatcher. The way that you use this object differs on Windows Phone 7 and Windows 8. I abstracted away the differences by making a method called DispatchInvoke(). It compiles different on each platform and will call the appropriate code on each platform.

public void DispatchInvoke(Action a)
{
#if SILVERLIGHT
    if (MainViewModel.Dispatcher == null)
        a();
    else
        Dispatcher.BeginInvoke(a);
#else
    if ((Dispatcher != null) && (!Dispatcher.HasThreadAccess))
    {
        Dispatcher.InvokeAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, (obj, invokedArgs) => { a(); }, this, null);
    }
    else
        a();
#endif
}

Saving Data

For saving simple data types I tend to use a simple component that I made for Windows Phone 7 (it can be found here). On Windows 8 all file IO is asynchronous. So the component had to be modified to work on Metro. I've shown the details of the changes on another page. Since the Windows Phone 7 version uses a blocking call and the Metro version is asynchronous the calls could not be made to look the same. But this difference was compatmentalized to a single method.

public void SaveAccessToken()
{
   if (this.AccessInfo != null)
   {
#if NETFX_CORE
      DataSaver<AccessInfo>.SaveMyDataAsync(this.AccessInfo, "_accessToken.xml");
#endif
#if SILVERLIGHT
     DataSaver<AccessInfo>.SaveMyData(AccessInfo, "_accessToken.xml");
#endif
   }
}

Most of the code for this project is in the MainViewModel class. The entirity of the code for it is below. It shouldn't contain anything that you don't recognize from above. The using directives at the top contain conditional compilation directives since the namespaces in which some of the classes exists differs on Windows Phone 7 and on Windows 8 Metro.

using System;
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using Newtonsoft.Json;
using J2i.Net.FacebookAuthenticationTest.Data;
using J2i.Net.FacebookAuthenticationTest.Utility;


#if SILVERLIGHT
using System.Windows.Threading;
#endif

#if NETFX_CORE
using System.Threading.Tasks;
using J2i.Net.FacebookAuthenticationTest.Data;
using Windows.UI.Xaml;

#endif

namespace J2i.Net.FacebookAuthenticationTest.ViewModel
{
    public class MainViewModel: INotifyPropertyChanged 
    {
        const string FRIEND_LIST_REQUEST = "https://graph.facebook.com/me/friends?access_token={0}";


        public MainViewModel()
        {
#if SILVERLIGHT
            var a = DataSaver<AccessInfo>.LoadMyData("_accessToken.xml");
            if(a !=null)
            {
                this.AccessInfo = a;
                this.UpdateFriendList();
            }
#endif

#if NETFX_CORE
            DataSaver<AccessInfo>.LoadDataAsync("_accessToken.xml", (info, exc)=>
            {
                if (info != null)
                {
                    this.AccessInfo = info;
                    this.UpdateFriendList();
                }
            });
#endif
        }

        ObservableCollection<FacebookUser> _friendList;
        public ObservableCollection<FacebookUser> FriendList
        {
            get { return (_friendList) ?? (_friendList = new ObservableCollection<FacebookUser>()); }
            set { _friendList = value; }
        }
 

        string _friendListText = String.Empty;
        public string FriendListText
        {
            get { return _friendListText; }
            set 
            {
                if (value != _friendListText)
                {
                    _friendListText = value;
                    RaisePropertyChanged("FriendListText");
                }
            }
        }


        public void SaveAccessToken()
        {
            if (this.AccessInfo != null)
            {
#if NETFX_CORE
                DataSaver<AccessInfo>.SaveMyDataAsync(this.AccessInfo, "_accessToken.xml");
#endif
#if SILVERLIGHT
               DataSaver<AccessInfo>.SaveMyData(AccessInfo, "_accessToken.xml");
#endif
            }
        }

        AccessInfo _accessInfo;
        public AccessInfo AccessInfo
        {
            get { return (_accessInfo) ?? (_accessInfo = new AccessInfo()); }
            set 
            {
                if (_accessInfo != value)
                {
                    _accessInfo = value;

                }
            }
        }

        bool _authenticationAttempted = false;
        public bool AuthenticationAttempted
        {
            get { return _authenticationAttempted || (!String.IsNullOrEmpty(AccessInfo.Token)); }
            set { _authenticationAttempted = value; }
        }

#if NETFX_CORE
        static Windows.UI.Core.CoreDispatcher _dispatcher;
        public static Windows.UI.Core.CoreDispatcher Dispatcher
        {
            get
            {
                if (_dispatcher != null)
                    return _dispatcher;
                if ((Window.Current==null)||(Window.Current.Content == null))
                    return null;
                return Window.Current.Content.Dispatcher; 
            }
            set { _dispatcher = value; }
        }
#endif

#if SILVERLIGHT
        static Dispatcher _dispatcher;
        public static Dispatcher Dispatcher
        {
            get
            {
                if (_dispatcher!=null)
                    return _dispatcher;
                var app = (App.Current as J2i.Net.FacebookAuthenticationTest.App);
                if (app.RootFrame == null)
                    return null;
                return (app.RootFrame.Dispatcher);
            }
            set
            {
                _dispatcher = value;
            }
        }    
#endif


        public void UpdateFriendList(string targetUrl = null, List<FacebookUser> previousUserList = null)
        {
            if (previousUserList == null)
                previousUserList = new List<FacebookUser>();

            var request = System.Net.HttpWebRequest.CreateHttp(targetUrl ?? String.Format(FRIEND_LIST_REQUEST, AccessInfo.Token));
            request.BeginGetResponse((o) =>
                {
                    var response = request.EndGetResponse(o);
                    var responseStreamReader = new StreamReader(response.GetResponseStream());
                    string friendListText = responseStreamReader.ReadToEnd();
                    Newtonsoft.Json.JsonSerializer s = new JsonSerializer();
                  Data.FriendListResponse v = Newtonsoft.Json.JsonConvert.DeserializeObject<Data.FriendListResponse>(friendListText);
                  if ((v != null) && (v.Data != null))
                  {
                      foreach (var user in v.Data)
                      {
                          previousUserList.Add(user);
                      }
                  }
                  if ((v!=null) && (v.Paging != null) && (v.Paging.Next != null))
                      UpdateFriendList(v.Paging.Next, previousUserList);
                  else
                  {
                      previousUserList.Sort((a1, a2) => { return a1.Name.CompareTo(a2.Name); });
                      DispatchInvoke(() =>
                          {
                              FriendList.Clear();
                              foreach (var u in previousUserList)
                                  FriendList.Add(u);
                          }
                      );
                  }

                }, null);
        }


        void DispatchInvoke(Action a)
        {
#if SILVERLIGHT
            if (MainViewModel.Dispatcher == null)
                a();
            else
                Dispatcher.BeginInvoke(a);
#else
            if ((Dispatcher != null) && (!Dispatcher.HasThreadAccess))
            {
                Dispatcher.InvokeAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, (obj, invokedArgs) => { a(); }, this, null);
            }
            else
                a();
#endif
        }
        protected void RaisePropertyChanged(String propertyName)
        {
            if (PropertyChanged != null)
            {
                DispatchInvoke(()=>
                    {
                        RaisePropertyChanged(propertyName);
                    });
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}

Tags: , , ,

Apr 15 2012

Data Serializer updated for Windows Metro

Category: Microsoft | MobileJoel Ivory Johnson @ 12:46

About a year ago I shared a simple utility class for saving serializable data on Windows Phone 7. I just updated the component for Windows 8 Metro. It still retains compatibility with Windows Phone 7 through the use of conditional compiler directives. On Windows Phone 7 I've changed the class so that it is static (so no need to instantiate it). 

For Metro the methos are asynchronous.  For saving an action can be passed that will be called once the save operation is complete. When loading data you'll want to pass an action that will received the loaded data and an exception parameter that will be populated if the data could not be loaded. 

 

As an example of how the code works (and the platform dependent differences in its usage) here is a method from a program I have that is using the code. The program compiles on both Windows Phone 7 and Windows Metro.

        public void LoadAccess()
        {
#if SILVERLIGHT
            AccessInfo = DataSaver<AccessInfo>.LoadMyData("_accessToken.xml");
#endif

#if NETFX_CORE
            DataSaver<AccessInfo>.LoadDataAsync("_accessToken.xml", (info, exc)=>
            {
                if (info != null)
                {
                    this.AccessInfo = info;
                }
            });
#endif
        }

        public void SaveAccessToken()
        {
            if (this.AccessInfo != null)
            {
#if NETFX_CORE
                DataSaver<AccessInfo>.SaveMyDataAsync(this.AccessInfo, "_accessToken.xml");
#endif
#if SILVERLIGHT
               DataSaver<AccessInfo>.SaveMyData(AccessInfo, "_accessToken.xml");
#endif
            }
        }

If you've never seen the #if/#endif directives before it is used to essentially conditionally comment out sections of code based on some condition. In this case the condition is certain compiler constants being defined. Some constants are automatically created for various project types. If you create a windows phone project the WINDOWS_PHONE and SILVERLIGHT constants are defined. For a Windows 8 Metro project the NETFX_CORE constant is defined. When you are viewing the code in the Visual Studio IDE it will gray out any code that is going to be ignored because of the conditional compilation statements. 

Below is the code for the serializer. 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;

using System.IO;
#if SILVERLIGHT
using System.IO.IsolatedStorage;
#endif

#if NETFX_CORE
using System.Threading.Tasks;
using Windows.Storage;
using Windows.Storage.Streams;
#endif

namespace J2i.Net.FacebookAuthenticationTest.Utility
{


    public class DataSaver<MyDataType>
    {

        static List<Type> _knownTypeList = new List<Type>();
        public static List KnownTypeList
        {
            get
            {
                return _knownTypeList;
            }
        }
#if SILVERLIGHT
        private static IsolatedStorageFile _isoFile;
        static IsolatedStorageFile IsoFile
        {
            get
            {
                if (_isoFile == null)
                    _isoFile = System.IO.IsolatedStorage.IsolatedStorageFile.GetUserStoreForApplication();
                return _isoFile;
            }
        }

         public static void SaveMyData(MyDataType sourceData, String targetFileName)
        {
            try
            {
                using (var targetFile = IsoFile.CreateFile(targetFileName))
                {
                    var d = new DataContractSerializer(typeof(MyDataType), KnownTypeList);
                    d.WriteObject(targetFile, sourceData);
                }
            }
            catch (Exception )
            {
                IsoFile.DeleteFile(targetFileName);
            }
        }


        public static MyDataType LoadMyData(string sourceName)
        {
            MyDataType retVal = default(MyDataType);
            if (IsoFile.FileExists(sourceName))
                using (var sourceStream = IsoFile.OpenFile(sourceName, FileMode.Open))
                {
                    var d = new DataContractSerializer(typeof(MyDataType), KnownTypeList);
                    retVal = (MyDataType)d.ReadObject(sourceStream);
                }
            return retVal;
        }
#endif
        public DataSaver()
        {
        }
#if NETFX_CORE
        public static async void SaveMyDataAsync(
            MyDataType sourceData, 
            String targetFileName, 
            Action<MyDataType,String, Exception> OnSaved = null)
        {
            try
            {
                StorageFile file = await ApplicationData.Current.LocalFolder.CreateFileAsync(
                    targetFileName, CreationCollisionOption.ReplaceExisting
                    );
                IRandomAccessStream raStream = await file.OpenAsync(FileAccessMode.ReadWrite);
                IOutputStream outStream = raStream.GetOutputStreamAt(0);

                DataContractSerializer serializer = new DataContractSerializer(typeof(MyDataType), KnownTypeList);
                serializer.WriteObject(outStream.AsStreamForWrite(), sourceData);
                await outStream.FlushAsync();
                if(OnSaved!=null)
                    OnSaved(sourceData, targetFileName, null);
            }
            catch (Exception exc)
            {
                if (OnSaved != null)
                {
                    OnSaved(sourceData, targetFileName, exc);
                }
            }
        }

        public static async void LoadDataAsync(string fileName, Action<MyDataType, Exception> loadAction)
        {
            if (loadAction == null)
                return;
            try
            {
                StorageFile file = await ApplicationData.Current.LocalFolder.GetFileAsync(fileName);
                if (file == null) return;
                IInputStream inStream = await file.OpenSequentialReadAsync();

                // Deserialize the Session State. 
                DataContractSerializer serializer = new DataContractSerializer(typeof(MyDataType), KnownTypeList);
                MyDataType data = (MyDataType)serializer.ReadObject(inStream.AsStreamForRead());
                loadAction(data, null);
            }
            catch (Exception e)
            {
                loadAction(default(MyDataType), e);
            }
        }
#endif

    }
}

Tags: ,

Mar 27 2012

New Meetup Presentation Series: Getting Started with Windows Phone

Category: Microsoft | MobileJoel Ivory Johnson @ 06:45

Over the next few weeks the Atlanta Windows Phone Developer Meetup group will be having a series of presentations on getting started with Windows Phone development. Many of the presentations will be done by Glen Gordon. I will be doing a few of the presentations too. These presentations are targeting developers that haven't yet gotten started with Windows Phone. So if you've not done any WP development before no worries, these presentations should be fine for you. If you are interested you can find information on the next and future meetings on the Meetup page here: http://www.meetup.com/Win-Phone-7-Developers-Atlanta/

 

Side note: For the presentations I'm doing the functionality works almost exactly the same for Windows Phone and Windows 8. I'm tempted to make an online presentation for both and put it on the web. But the real world presentation will concentrate only on Windows Phone. 

 

Tags: ,

Feb 7 2012

How Big is this Object on Windows Phone

Category: MobileJoel Ivory Johnson @ 06:44

I wanted to validate some of my understanding on the amound of memory that a .Net instance occupies on Windows Phone and sent a request to Abhinaba Basu of the .Net Compact Framework team for more information. He promptly responded with a nice explanation of how a .Net object is laid out in memory. Instead of reposting his work I'd like to refer you to his blog and the article he wrote explaining this. I'll be making some references to it in the near future. 

image

http://blogs.msdn.com/b/abhinaba/archive/2012/02/02/wp7-clr-managed-object-overhead.aspx

Tags: ,

Jan 22 2012

Adjusting Microsoft Translator WAVE Volume

Category: MobileJoel Ivory Johnson @ 14:30

Video Entry
Download Code (32 Kb)

 

The code in this article was inspired by some questions on Windows Phone 7, but it's generic enough to be used on other .Net based platforms. In the Windows Phone AppHub forums there was a question about altering the volume of the WAVE file that the Microsoft translator service returns. In the StackOverflow forums there was a question about mixing two WAVE files together. I started off working on a solution for the volume question and when I stepped back to examine it I realized I wasn't far away from a solution for the other question. So I have both solutions implemented in the same code. In this first post I'm showing what I needed to do to alter the volume of the WAVE stream that comes from the Microsoft Translation service.

I've kept the code generic enough so that if you want to apply other algorithms to the code you can do so. I've got some ideas on how the memory buffer for the sound data can be better handled that would allow large recordings to be manipulated without keeping the entire recording in memory and allowing the length of the recording to be more easily altered.  But the code as presented demonstrates three things:

  1. Loading a WAVE file from a stream
  2. Alter the WAVE file contents in memory
  3. Save WAVE files files back to a stream

The code for saving a WAVE file is a modified version of the code that I demonstrated some time ago for writing a proper WAVE file for the content that comes from the Microphone buffer.

Prerequisites

I'm making the assumption that you know what a WAVE file and a sample are.I am also assuming that you know how to use the Microsoft Translator web service.

Loading a Wave File

The formats for WAVE files is pretty well documented. There's more than one encoding that can be used in WAVE files, but I'm concentrating on PCM encoded WAVE files and will for now ignore all of the other possible encodings. The document that I used can be found here.  There are a few variants from the document that I found when dealing with real WAVE files and I'll comment on those variants in a moment. In general most of what you'll find in the header are 8, 16, and 32-bit integers and strings. I read the entire header into a byte array and extract the information from that byte array into an appropriate type. To extract a string from the byte array you need to know the starting index for the string and the number of characters it contains. You can then use Encoding.UTF8.GetString to extract the string. If you understand how numbers are encoded (little endian) decoding them is fairly easy. If you want to get a better understanding see the Wikipedia article on the encoding.

Integer Size Extraction Code
8-bit data[i]
16-bit (data[i])|(data[i+1]<<0x08)
32-bit (data[i])|(data[i+1]<<0x08)|(data[i+2]<<0x10)|(data[i+3]<<0x18)

Offset Title Size Type Description
0 ChunkID 4 string(4) literal string "RIFF"
4 ChunkSize 4 int32 Size of the entire file minus eight bytes
8 Format 8 string(4) literal string "WAVE"
12 SubChunkID 4 string(4) literal string "fmt "
16 SubChunk1Size 4 int32 size of the rest of the subchunk
20 AudioFormat 2 int16 Should be 1 for PCM encoding. 
22 Channel Count 2 int16 1 for mono, 2 for stereo,...
24 SampleRate 4 int32  
28 ByteRate 4 int32 (SampleRate)*(Channel Count)*(Bits Per Sample)/8
32 Block Align 2 int16 (Channel Count)*(Bits Per Sample)/8
34 BitsPerSample 2 int16  
  ExtraParamSize 2 int16 possibly not there
  ExtraParams ? ? possibly not there
36+x SubChunk2ID 4 int32 literal string "data"
40+x SubChunk2Size 4 int32  
44+x data SubChunk2Size byte[SubChunk2Size]  
         

The header will always be at least 44 bytes long. So I start off reading the first 44 bytes of the stream. The SubChunk1Size will normally contain the value 16. If it's greater than 16 then the header is greater than 44 bytes and I read the rest. I've allowed for a header size of up to 64 bytes (which is much larger than I have encountered). A header size of larger than 44 bytes will generally mean that there is an extra parameter at the end of SubChunk1. For what I'm doing the contents of the extra parameters don't matter. But I still need to account for the space that they consume to properly read the header.

To my surprise the contents of the fields in the header are not always populated. Some audio editors leave some of the fields zeroed out. My first attempt to read a WAVE file was with a file that came from the open source audio editor Audacity. Among other fields the BitsPerSample field was zeroed. I'm not sure if this is allowed by the format or not. It certainly is not in any of the spec sheets that I've found. But when I encounter this I assume a value of 16.

Regardless of whether a WAVE file contains 8-bit, 16-bit-, or 32-bit samples when read in I store the value in an array of doubles. I chose to do this because double works out better for some of the math operations I have in mind.

public void ReadWaveData(Stream sourceStream, bool normalizeAmplitude = false)
{
    //In general I should only need 44 bytes. I'm allocating extra memory because of a variance I've seen in some WAV files. 
    byte[] header = new byte[60];
    int bytesRead = sourceStream.Read(header, 0, 44);
    if(bytesRead!=44)
        throw new InvalidDataException(String.Format("This can't be a wave file. It is only {0} bytes long!",bytesRead));

    int audioFormat = ChannelCount = (header[20]) | (header[21] << 8);
    if (audioFormat != 1)
        throw new Exception("Only PCM Waves are supported (AudioFormat=1)");

    #region mostless useless code
    string chunkID = Encoding.UTF8.GetString(header, 0, 4);
    if (!chunkID.Equals("RIFF"))
    {
        throw new InvalidDataException(String.Format("Expected a ChunkID of 'RIFF'. Received a chunk ID of {0} instead.", chunkID));
    }
    int chunkSize = (header[4]) | (header[5] << 8) | (header[6] << 16) | (header[7] << 24);
    string format = Encoding.UTF8.GetString(header, 8, 4);
    if (!format.Equals("WAVE"))
    {
        throw new InvalidDataException(String.Format("Expected a format of 'WAVE'. Received a chunk ID of {0} instead.", format));
    }
    string subChunkID = Encoding.UTF8.GetString(header, 12, 4);
    if (!format.Equals("fmt "))
    {
        throw new InvalidDataException(String.Format("Expected a subchunkID of 'fmt '. Received a chunk ID of {0} instead.", subChunkID));
    }
    int subChunkSize = (header[16]) | (header[17] << 8) | (header[18] << 16) | (header[19] << 24);
    #endregion

    if (subChunkSize > 16)
    {
        var bytesNeeded = subChunkSize - 16;
        if(bytesNeeded+44 > header.Length)
            throw new InvalidDataException("The WAV header is larger than expected. ");
        sourceStream.Read(header, 44, subChunkSize - 16);
    }

    ChannelCount = (header[22]) | (header[23] << 8);
    SampleRate = (header[24]) | (header[25] << 8) | (header[26] << 16) | (header[27] << 24);
    #region Useless Code
    int byteRate = (header[28]) | (header[29] << 8) | (header[30] << 16) | (header[31] << 24);
    int blockAlign = (header[32]) | (header[33] << 8);
    #endregion
    BitsPerSample = (header[34]) | (header[35] << 8);

    #region Useless Code
    string subchunk2ID = Encoding.UTF8.GetString(header, 20 + subChunkSize, 4);
    #endregion

    var offset = 24 + subChunkSize;
    int dataLength = (header[offset+0]) | (header[offset+1] << 8) | (header[offset+2] << 16) | (header[offset+3] << 24);

    //I can't find any documentation stating that I should make the following inference, but I've
    //seen wave files that have 0 in the bits per sample field. These wave files were 16-bit, so 
    //if bits per sample isn't specified I will assume 16 bits. 
    if (BitsPerSample == 0)
    {
        BitsPerSample = 16;
    }

    byte[] dataBuffer = new byte[dataLength];

    bytesRead = sourceStream.Read(dataBuffer, 0, dataBuffer.Length);


    Debug.Assert(bytesRead == dataLength);


    if (BitsPerSample == 8)
    {
        byte[] unadjustedSoundData = new byte[dataBuffer.Length / (BitsPerSample / 8)];
        Buffer.BlockCopy(dataBuffer, 0, unadjustedSoundData, 0, dataBuffer.Length);

        SoundData = new double[unadjustedSoundData.Length];
        for (var i = 0; i < (unadjustedSoundData.Length); ++i)
        {
            SoundData[i] = 128d*(double)unadjustedSoundData[i];
        }

    }
    if (BitsPerSample == 16)
    {
        short[] unadjustedSoundData = new short[dataBuffer.Length / (BitsPerSample / 8)];
        Buffer.BlockCopy(dataBuffer, 0, unadjustedSoundData, 0, dataBuffer.Length);


        SoundData = new double[unadjustedSoundData.Length];
        for (var i = 0; i < (unadjustedSoundData.Length); ++i)
        {
            SoundData[i] = (double) unadjustedSoundData[i];
        }
    }
    else if(BitsPerSample==32)
    {
        int[] unadjustedSoundData = new int[dataBuffer.Length / (BitsPerSample / 8)];
        Buffer.BlockCopy(dataBuffer, 0, unadjustedSoundData, 0, dataBuffer.Length);

        SoundData = new double[unadjustedSoundData.Length];
        for (var i = 0; i < (unadjustedSoundData.Length); ++i)
        {
            SoundData[i] = (double)unadjustedSoundData[i];
        }
    }

    Channels = new PcmChannel[ChannelCount];
    for (int i = 0; i < ChannelCount;++i )
    {
        Channels[i]=new PcmChannel(this,i);
    }
        if (normalizeAmplitude )
            NormalizeAmplitude();

}

Mono vs Stereo

In a mono (single channel) file the samples are ordered one after another, no mystery there. For stereo files the data stream will contain the first sample for channel 0, then the first sample for channel 1, then the second sample for channel 0, second sample for channel 1, and so on. Every other sample will be for the left channel or right channel. The sample data is stored in memory in the same way. in an array called SampleData. To work exclusively with one channel or the other there is also a property named Channels (of type PcmChannel) that can be used to access that one channel.

public class PcmChannel
{
    internal PcmChannel(PcmData parent, int channel)
    {
        Channel = channel;
        Parent = parent;
    }
    protected PcmData Parent { get; set;  }
    public int Channel { get; protected set; }
    public int Length
    {
        get { return (int)(Parent.SoundData.Length/Parent.ChannelCount);  }
    }
    public double this[int index]
    {
        get { return Parent.SoundData[index*Parent.ChannelCount + Channel]; }
        set { Parent.SoundData[index*Parent.ChannelCount + Channel] = value; }
    }
}

//The following is a simplified interface definition for how the PcmChannel
//data type is relevant to our PCM data. The actual PcmData class has more 
//more members than what follows.
public class PcmData
{
   public double[] SoundData { get; set; }
   public int ChannelCount { get; set; }
   public PcmChannel[] Channels { get; set; }
}

Where's 24-bit support

Yes, there do exists 24-bit WAVE files. I'm not supporting them (yet) because there's more code required to handle them and most of the scenarios I have in mind are going to use 8 and 16-bit files. Adding support for 32-bit files was only 5 more lines of code. I'll be handing 24-bit files in a forthcoming code.

Altering the Sound Data

Changes made to the values in the SoundData[] array will alter the sound data. There are some constrains on how the data can be modified. Since I'm writing this to a 16-bit WAVE file the maximum and minimum values that can be written out are 32,768 and -32,767. The double data type has a range significantly larger than this. The properties, AdjustmentFactor and AdjustmentOffset are used to alter the sound data when it is being prepared to be written back to a file. They are used to apply a linear transformation to the sound data (remember y=mx+b?). Finding the right values for these is done for you through the NormalizeAmplitude method. Calling this method after you've altered your sound data will result in appropriate values being chose. By default this method will try to normalize the sound data to 99% of maximum amplitude. You can pass an argument to this method between the values of 0 and 1 for some other amplitude.

public void NormalizeAmplitude( double percentMax = 0.99d)
{
    var max = SoundData.Max();
    var min = SoundData.Min();

    double rangeSize = max - min+1 ;
    AdjustmentFactor = ((percentMax * (double)short.MaxValue) - percentMax * (double)short.MinValue) / (double)rangeSize;
    AdjustmentOffset = (percentMax * (double)short.MinValue) - (min * AdjustmentFactor);

    int maxExpected = (int)(max * AdjustmentFactor + AdjustmentOffset);
    int minExpected = (int)(min * AdjustmentFactor + AdjustmentOffset);
}

Saving WAVE Data

To save the WAVE data I'm using a variant of something I used to save the stream that comes from the Microphone. The original form of the code had a bug that makes a difference when working with a stream with multiple channels. The microsphone produces a single channel stream and wasn't impacted by this bug (but it's fixed here). The code for writing the wave produces a header from the parameters it is given, then it writes out the WAVE data. The WAVE data must be converted from the double[] array to a byte[] array containing 16-bit integers in little endian format.

public class PcmData
{
    public void Write(Stream destinationStream)
    {
        byte[] writeData = new byte[SoundData.Length*2];
        short[] conversionData = new short[SoundData.Length];

        //convert the double[] data back to int16[] data
        for(int i=0;i<SoundData.Length;++i)
        {
            double sample = ((SoundData[i]*AdjustmentFactor)+AdjustmentOffset);
            //if the value goes outside of range then clip it
            sample = Math.Min(sample, (double) short.MaxValue);
            sample = Math.Max(sample, short.MinValue);
            conversionData[i] = (short) sample;
        }
        int max = conversionData.Max();
        int min = conversionData.Min();
        //put the int16[] data into a byte[] array
        Buffer.BlockCopy(conversionData, 0, writeData, 0, writeData.Length);

        WaveHeaderWriter.WriteHeader(destinationStream,writeData.Length,ChannelCount,SampleRate);
        destinationStream.Write(writeData,0,writeData.Length);
    }
}

public class WaveHeaderWriter
{
    static byte[] RIFF_HEADER = new byte[] { 0x52, 0x49, 0x46, 0x46 };
    static byte[] FORMAT_WAVE = new byte[] { 0x57, 0x41, 0x56, 0x45 };
    static byte[] FORMAT_TAG = new byte[] { 0x66, 0x6d, 0x74, 0x20 };
    static byte[] AUDIO_FORMAT = new byte[] { 0x01, 0x00 };
    static byte[] SUBCHUNK_ID = new byte[] { 0x64, 0x61, 0x74, 0x61 };
    private const int BYTES_PER_SAMPLE = 2;

    public static void WriteHeader(
            System.IO.Stream targetStream,
            int byteStreamSize,
            int channelCount,
            int sampleRate)
    {

        int byteRate = sampleRate * channelCount * BYTES_PER_SAMPLE;
        int blockAlign =  BYTES_PER_SAMPLE;

        targetStream.Write(RIFF_HEADER, 0, RIFF_HEADER.Length);
        targetStream.Write(PackageInt(byteStreamSize + 36, 4), 0, 4);

        targetStream.Write(FORMAT_WAVE, 0, FORMAT_WAVE.Length);
        targetStream.Write(FORMAT_TAG, 0, FORMAT_TAG.Length);
        targetStream.Write(PackageInt(16, 4), 0, 4);//Subchunk1Size    

        targetStream.Write(AUDIO_FORMAT, 0, AUDIO_FORMAT.Length);//AudioFormat   
        targetStream.Write(PackageInt(channelCount, 2), 0, 2);
        targetStream.Write(PackageInt(sampleRate, 4), 0, 4);
        targetStream.Write(PackageInt(byteRate, 4), 0, 4);
        targetStream.Write(PackageInt(blockAlign, 2), 0, 2);
        targetStream.Write(PackageInt(BYTES_PER_SAMPLE * 8), 0, 2);
        //targetStream.Write(PackageInt(0,2), 0, 2);//Extra param size
        targetStream.Write(SUBCHUNK_ID, 0, SUBCHUNK_ID.Length);
        targetStream.Write(PackageInt(byteStreamSize, 4), 0, 4);
    }

    static byte[] PackageInt(int source, int length = 2)
    {
        if ((length != 2) && (length != 4))
            throw new ArgumentException("length must be either 2 or 4", "length");
        var retVal = new byte[length];
        retVal[0] = (byte)(source & 0xFF);
        retVal[1] = (byte)((source >> 8) & 0xFF);
        if (length == 4)
        {
            retVal[2] = (byte)((source >> 0x10) & 0xFF);
            retVal[3] = (byte)((source >> 0x18) & 0xFF);
        }
        return retVal;
    }
}

Using the Code

Once you've gotten the wave stream only a few lines of code are needed to do the work. For the example program I am downloading a spoken phrase from the Microsoft Translation service, amplifying it, and then writing both the original and amplified versions to a file.

static void Main(string[] args)
{
    PcmData pcm;

    //Download the WAVE stream
    MicrosoftTranslatorService.LanguageServiceClient client = new LanguageServiceClient();            
    string waveUrl = client.Speak(APP_ID, "this is a volume test", "en", "audio/wav","");
    WebClient wc = new WebClient();
    var soundData = wc.DownloadData(waveUrl);

          
    //Load the WAVE stream and let it's amplitude be adjusted to 99% maximum
    using (var ms = new MemoryStream(soundData))
    {
        pcm = new PcmData(ms, true);               
    }

    //Write the amplified stream to a file
    using (Stream s = new FileStream("amplified.wav", FileMode.Create, FileAccess.Write))
    {
        pcm.Write(s);
    }

    //write the original unaltered stream to a file
    using (Stream s = new FileStream("original.wav", FileMode.Create, FileAccess.Write))
    {
        s.Write(soundData,0,soundData.Length);
    }
}

The End Result

The code works as designed, but I found a few scenarios that can make it ineffective. One scenario is that not all phones have the same response frequency for their speakers. Frequencies that comes through loud and clear on one phone may come through sounding quieter on another. The other scenario is that the source files may have a sample that goes to the maximum or minimum reading even though a majority of the other samples may come no where near to the same level of amplitude. When this occurs the spurious sample will limit the amount of amplification that is applied to the file. I opened an original and amplified WAVE file in audacity to see my results and I was pleased to see that the amplified WAVE does actually look louder when I view it's graph in audacity.

Part 2 - Overlaying Wave Files

The other problem that this code can solve is combining wave files together in various ways. I'll be putting that up in the next post. Between now and then I've got a presentation at the Windows Phone Developers Atlanta meeting this week (if you are in the Atlanta area come on out!) and will get back to this code after the presentation.

Tags: , , ,

Jan 17 2012

Announcing Windows Phone Developers Atlanta

Category: Joel Ivory Johnson @ 08:03

I wanted to take a moment to announce a meetup group for Windows Phone 7 developers based out of Atlanta. I'm one of the cofounders and organizers for this group. We've had several meetings but I wanted to wait until we got into a routine before making an announcement. I've been pretty comfortable with how things are going so I'm sharing with you now. 

The information for our meetings is posted on Meetup.com ( http://www.meetup.com/Win-Phone-7-Developers-Atlanta/ ). We meet one Tuesday per month (except for December, for which we've decided to have no meetings because of end-of-the year demands and holidays). Our next meeting will be Tuesday 24 January 2012. This month I'll be presenting on Windows Azure for Windows Phone developers and we may have one other person presenting on Localization. After the meeting I'll share the notes (and possibly a recording of the presentation).  

Tags: , ,

Jan 13 2012

Referencing Pages in Other Assemblies

Category: Joel Ivory Johnson @ 11:07

I''ve been working with Google APIs recently and most of the ones that I've used require OAuth2 authentication so that the user can grant the application access to their data. Rather than copy-and-paste the OAUTH2 implementation to the different projects that I'm using I decided to make a class library that included the authentication code. From a usage standpoint I wanted to to do something similar to the the tasks and choosers where you create an object, call a method, and after the component does its magic you get back the item that the user chose. 

The format for the URI to another assembly that you would use in Silverlight looked like the following

new Uri("{assemblyName};component/{pagePath.xaml}", UriKind.Relative);

Of course here {assemblyName} and {pagePath.xaml} are placeholders for your actual assembly namd and pathway to the page and not literal values here. It took me more time than I care to admit to figure out why this would not work on Windows Phone 7. The correct format to use on Windows Phone 7 is as follows:

new Uri("/{assemblyName};component/{pagePath.xaml}", UriKind.Relative);

See the difference? It's subtle, but the difference is the little forward slash at the begining of the string. Had I paid closer attention to the exception message that was returned I would have realized this. 

The one thing I don't like about this method is when one navigates away from the page any program state that is saved in the page's code-behind is going to be lost. I also plan to provide an alternative method for showing the OAUTH in a user control. Either method has its advantages and disadvantages. I'm alsmost done with the code and will be posting it here later this week. 

Tags:

Nov 15 2011

Using Windows Phone Location Services

Category: MobileJoel Ivory Johnson @ 10:11

Introduction

I was assisting some college students at a Microsoft event on the Georgia Tech campus. Some questions about location services came up. A few days later I was assisting some other college students in the Microsoft Windows Phone Development Forums and some of the same questions came up. So I've decided to do a quick write up on location services to have as a reference the next time the question comes up.

I'll cover what needs to be done to get the user's location and formatting it for the screen.

How Does Windows Phone Acquire my Location

Windows Phone uses three technologies for finding your location; GPS, WiFi based location, and Cell Tower based location. The technology with which most are familiar is GPS. In a nutshell GPS technology relies on satellites in orbit and the phone's ability to receive signals from at least three of these satellites. These satellites are all transmitting the time from synchronized clocks. But because of the satellites being different distances from the phone the time received from the satellites will be slightly different (since the further away the satellite is from the user the more time it takes for the signal to be received). A user's position can be calulated from these time differences.

Cell Tower based location and WiFi based location are similar in how they work. When you use either one of these the phone will try to get the identifiers for near by WiFi access points and cell towers. The identifier is sent to a Microsoft service which keeps records of the general area in which those WiFi access points or cell towers have been seen and returns that information to the device. This information isn't static; cell towers can be taken down and new towers can be built and when people move to new apartments and houses they take their access points with them. To keep their information up to date Microsoft acquires new information from vehicles driving around as a part of the StreetSide Project and also receives data from other phones that are using location services. If a phone is able to get location from GPS and also sees a new access point then that access point is reported as having been seen at that location.

I've got a bad habit of generically referring to any type of location service as GPS. This isn't correct since GPS is one type of location technology that may or may not be used to acquire the user's location. Also GPS is also the name for the location system that makes use of satellites launched by the USA. There are other systems such as GLONASS ( Globalnaya navigatsionnaya sputnikovaya sistema), Galileo, and so on (The Samsung Focus S supports both GPS and GLONASS).

Accuracy or Battery Friendly

Each one of these location technologies differs in the accuracy of the location that it provides. GPS is the most accurate with the margin of error being around 10 meters (32 feet) in favourable conditions. The next most precise location technology is WiFi based location. WiFi access points don't transmit nearly as far as cell towers. When you detect one you usually are literally within stone throwing distance of it. Cell tower based location is the least precise.

Generally speaking the more precise location technology (GPS) also has a more significant impact on battery life. When you need to acquire location information you can specify low or high accuracy mode. To preserve the user's battery life only request high accuracy information if you need it. If you only need a general location and don't expect your user to move around much during a session in your program it's a good idea to turn off location services after you've retrieved their location. Note: If you need the user's speed or elevation you will need to request a high accuracy location.

Once you have the user's location if you do not need to continually retrieve updated location information then turn off your location watcher. Doing so will power down the radios involved in retrieving the user's location and save battery life.

Getting Permission to Retrieve Location

Before retrieving the user's location you need to have permission to do so. There are two levels on which you'll need to retrieve location; on the device level and on the user level. On the user level it's just a matter of asking the user for permission to get their location. If precise location isn't absolutely needed then you may also want to give the user the option to select their location from a list of cities. You will also want to display a privacy notice to the user so that he or she knows how their location information will be used. There are some scenarios where you don't need to do this, but for now assume that this is always required. Retrieving the user location without getting permission and transmitting it over the Internet is a sure fire way to fail certification.

On the device level there are two things that you must do to have permission to use location services. You'll need to ensure that your application has requested the location services capability in the WMAppManifest.xml file. To do this ensure that you have the line <Capability Name="ID_CAP_LOCATION"/> in the <Capabilities/> section. Without this your application will not be able to make use of location services. Also note that the presence of this declarative capability request will also cause your application to be listed as an application that requires location services in the Marketplace.

Even though you've requested the location services capability a user can completely disable location services on their phone. I'll talk about how to deal with this in a moment.

What's in a Location

Location information is returned in the GeoCoordinate class. Let's take a look at the properties that class exposes

Member Type Description
IsUnknown bool Indicates whether or not the location object contains latitude/longitude data
Latitude double User's latitude
Longitude double User's longitude
HorizontalAccuracy double The accuracy of the latitude and longitude in meters
Altitude double User's altitude in meters
VerticalAccuracy double Accuracy in meters
Speed double User's speed in meter's per second

I listed IsUnknown first because if it is true then there's no use in looking at the other fields; they will contain no data of interest. I think that Latitude and Longitude explain themselves. The HorizontalAccuracy property tells you how far off the user's actual coordinates may be in meters while VerticalAccuracy tells how far off the altitude may be in meters. Those living in North America will want to take note that the Altitude and Speed properties user meters and meters per second. The Imperial measurement system is not being used here. If you need to display something in Imperial units remember to do a conversion first.

Getting the Location

Let's break away from discussing concepts and go through some code to retrieve the user's location. To retrieve the user's location you'll need to create a geocoordinate watcher, subscribe to a few events, and call it's Start() method.

private GeoCoordinateWatcher _watcher = null;
public GeoCoordinate MostRecentPosition { get; set; }
	
void StartWatcher()
{
 if(_watcher == null)
  {
    _watcher = new GeoCoordinateWatcher(GeoPositionAccuracy.High);
    _watcher.PositionChanged += new EventHandler<GeoPositionChangedEventArgs<GeoCoordinate>>(watcher_PositionChanged);
    _watcher.StatusChanged += new EventHandler<GeoPositionStatusChangedEventArgs>(watcher_StatusChanged);
    } 
   _watcher.Start();
   if(_watcher.Permission == GeoPositionPermission.Denied)
   {
      //Location services is disable on the phone. Show a message to the user.
   }
}
void watcher_PositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
{
   MostRecentPosition = e.Position.Location;
}
 void watcher_StatusChanged(object sender, GeoPositionStatusChangedEventArgs e)
{

}
	

When the phone has more updated location information the PositionChanged event is fired. I'm placing the updated position in a property called MostRecentPosition and all subsequent code will work using the value in that property. Something to keep in mind is that Location Services operates on a thread separate from the UI thread. In many Windows based technologies (including Windows Phone and Silverlight) you cannot interact directly with UI elements from seconday threads. If you wanted to update a UI element it is necessary to use a Dispatcher to execute code back on the UI thread.

void watcher_PositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
{
   MostRecentPosition = e.Position.Location;
   this.Dispatcher.BeginInvoke(()=>
   {
      MyTextBox.Text = "Received a location!";
   }
}

Location Services Status

The geocoordinate watcher can have various status defined in the GeoPositionStatus enumeration. When the status changes the StatusChanged event is fired (also on a secondary thread).

GeoPositionStatus Enumeration
Value Meaning
Disabled Location services has been disabled
NoData None of the location providers have any location information
Initializing Location services is still initializing (such as looking for GPS Satellite)
Ready Location services has data ready to be provided

Before Mango (Windows Phone 7.1) one would only expect the Disabled status if location services had been disabled before the program began to run; if it was available when the program started up then you would not need to check for it again during the life time of the program. Under Mango location services can become disabled at any time. While your program is running the user can press the Start button and go to settings (during which your program is still alive in the background) and then return to your program (waking it up) and location services will be disabled.

Formatting the Location Data

The members on the GeoCoordinate object that you get back that contain the latitude and longitude are of type double. You can format these fields the same way you would any other set of doubles. For example, lets say that you wanted to display the location with three figures past the decimal point. You could do it with the following.

string formatedLocation = String.Format("Lat/Long: {0:0.000},{1:0.000}",position.Latitude, position.Longitude);

Another way of displaying these numbers is using degrees, minutes, and seconds. As you might guess there is a relationship between these units and the units you might use to describe time on a clock. Every 24 hours the earth rotates 360 degrees (it's actually every 23 hours 56 minutes, but I won't get into the specifics of that). The amount that the earth rotates in one hour can be found by dividing 360 by 24. It is 15 degrees. So when some one mentions one hour of rotation that means the same as 15 degrees. There are 60 minutes in an hour. So how many degrees are in a minute of rotation? That can be found by dividing the 15 degrees of an hour of rotation by 60 which results in 0.25 degrees. To get degrees in a second of rotation divide the 0.25 degrees by 60. Given a rotational measurement you could use the following to perform the same steps to convert a measurement to degrees, minutes, seconds:

public static string DegreesToDegreesMinutesSecondsString(double source)
{
    var d = Math.Floor(source);
    var remainder = (source - d) * 60;
    var m = Math.Floor(remainder);
    remainder = (remainder - m) * 60;
    var s = remainder;

    return String.Format("{0:0}° {1:00}' {2:00}\"", d, m, s);
}

Meeting Certification Requirements

When you are using location services there are a few things you must do to pass certification. Most importantly you must inform the user how you plan to use their location data. If the location data will never leave the device then let the user know this. If you need to call public web services with their location data but don't plan to transmit identifying information then let the user know this.

Your program must also be able to handle scenarios in which location services is disabled gracefully. Depending on the nature of your program you may be able to provide the user with a list of cities or allow the user to enter location information (such as a postal code) to manually set their location. If your program absolutely must have location services information (such as a speedometer application) then let the user know that the program needs location services is needed for operation.

Example Program

The application attached to this posting retrieves location information and displays it on the screen. It also will allow you to send a link to your location via e-mail and will show you how to to convert information retrieved to feet and miles per hour(for those that use the emperial system) and kilometers per hour. The program uses databinding to display information on the screen but I don't make much use of converters. Since the information can also be sent via e-mail I do formatting within code and both bind it to the screen and send it in an e-mail.

Things to Remember

  • Request the Location Services capability in your WMAppManifest.xml
  • Ask the user for permission to get their location
  • Be prepared for the possibility of location services being disabled on the phone
  • Don't transmit the user's location to any service without their permission
  • Only use high accuracy location when needed
  • Turn off location services when it is nolonger needed

Tags: ,

Nov 5 2011

Chevron Labs is Open for Unlocking Your Phone

Category: MobileJoel Ivory Johnson @ 10:20

If you want to do development on your phone and don't want to register for the Marketplace you have an alternative to paying the full 99 USD to be able to deploy to your phone. You can also register with Chevron Labs 10 USD to get deployment abilities. For the 10 USD fee your phone is unlocked and ready to run your own applications. When you are prepared to deploy to the Marketplace you will need to do the full 99 USD registration, but until then this is a nice start.

Find out more here

Tags:

Nov 5 2011

No Video Screenshots?

Category: MobileJoel Ivory Johnson @ 05:27

Presently if you need a screenshot of your application on Windows Phone 7 you will need to use the emulator. Recently I came across a developer that found this would not work for him. He needed a screenshot of his application playing video and the video looks lower quality in the emulator. For this scenario you'll need to simulate the screen playing video. It only took me a few minutes to figure out how to do this. I'll start off showing the finished product:

 

To make this I started off recording a video in the emulator. Since there is no real camera in the emulator you get a video of a black square going around the screen. That was fine for my purposes. 

 

Using the image editor of your choice (I used Paint.Net, which is free!) erase the white area in the top of the image. The rest of the image needs to be divided into three layers.

The top layer is going to be made of the transparent part of the image. Using the Magic Wand tool hold down the CONTROL button and click on the transparent overlay above and below the progress bar. Also select the parts of it that are in the play/rewind/forward buttons. Of the time indicator on your screenshot has any numbers with closed shapes (0,4,6,8, or 9) then you will also need to select the inside of the closed area. Once everything is selected go to the edit menu and select "cut." Create a new layer and paste the transparent part into it. You may need to move it around on the screen to get it back into it's original position. 

Right now the transparent isn't really transparent. If you edit the layer properties you can change the transparency. I set the transparency to 196/255. I also changed the color of the overlay to be slightly lighter. 

The opaque parts of the image will still be on the original layer. Create a third layer and in this layer place the image that you want to represent the video. You may need to reorder your layers but after that you are done, just save the image. 

When I did the above I saved my work. So you can download the Paint.Net file and just change the image. If Paint.Net isn't your image editor of choice I also exported the layers into PNG file. You can download them from Skydrive

 

Tags:

Nov 4 2011

Enable 32-bit Color if Your Application Supports It

Category: MobileJoel Ivory Johnson @ 04:56

The first generation Windows Phones had hardware that used both 16-bit and 32-bit color, but the operating system would force the display to use 16 color regardless. If you have graphics with smooth gradients then then you would end up seeing color bands on a 16-bit display. On Mango if a phone supports 32-bit color you can take advantage of it, but only if you ask! If you want to enable 32-bit color you will need to edit your WMAppManifest.xml. In the <App /> element add an attribute called BitsPerPixel setting its value to 32. That's all you need to do. After doing that when your application runs on a device with 32-bit graphics hardware it will display in 32-bit color mode. 

 

Tags:

Oct 15 2011

Augmented Reality Part 1: Getting Orientation Data

Category: MobileJoel Ivory Johnson @ 07:03
Download Code (382 Kb)

A few days ago I started developing a program for controlling my computerized telescope with my phone. Before I knew it I found myself in topics that are all a part of augmented reality. I thought it may be helpful to others if I collected my notes together to share. I'm trying something new with this post also. In addition to the blog post and the code I've also made this information available in video form at the links above. The video and this post cover the same information.

Before getting started with Augmented Reality you will want to make sure that you have a device that has the supported features. A lot of the concepts that I share in this series could be applied to other phones, but I will be concentrating on Windows Phone and taking advantage of the features and functionality that it has to provide. If you want to make use the information these posts on other devices you may have to find or make implementations for some high level functionality if your device and development environment does not already provide it.

To get started you will need a computer with the Windows Phone Developer Tools installed and a Windows Phone. Not all Windows Phones will work though. The phone will need to have GPS hardware and a compass/magnometer. All windows phones GPS hardware but not all phones have the magnometer. There are some devices that have a magnometer but don't have the driver needed to make it available to third party developers. At the time I am writing this I have two phones that have Mango installed. My HD7 has a compass but it does not have the necessary driver. My Samsung Focus does have the necessary driver. Before we go any further let's ensure that your device supports the magnometer. If it doesn't you'll need to find a device that does before you can proceed any further with development.

Does My Phone Support the Needed APIs

To test your device create a new Windows Phone project and add a reference to Microsoft.Devices.Sensors. You only need one line of code to check whether or not your device has a mangometer.

bool compassFound = Motion.IsMotionAvailable;

Set a break point after that line and run the program on your phone (at present it will always return "false" on the emulator). Hopefully it will return "true" for you.

Getting the Device's Orientation

Once you have a device that supports the compass let's get the device's orientation. At the very least your device has a magnometer and an accelerometer in it. It may also have a gyrometer in it too if it is a more recent device. You could get the readings from all of these sensor's individually but we'll rely on the Motion API to get the information from the sensors that it finds present and let it perform the math needed to get the data in an easy-to-consume form. The general pattern that you will use when interacting with the sensor APIs is that you'll create an object to represent a sensor, subscribe to an event to get notifications about the reading changing, and then call a Start() method to turn on the sensor and to start receiving data from it.

The compass (And thus the Motion API) requires calibration from time to time. In addition to an event that is called when there is sensor data there is also an event for notification that the device requires calibration. If this event is fired you'll need to tell the user to move his or her phone in a figure 8. Once the phone is calibrated your application will start to receive readings and you can remove the notification.

bool IsMotionAvailable {get; set; }
private Motion _motion;

IsMotionAvailable = Motion.IsSupported; 
if (IsMotionAvailable)
{
    _motion = new Motion();
    _motion.Calibrate += new EventHandler(motion_Calibrate);
    _motion.CurrentValueChanged += new EventHandler>(motion_CurrentValueChanged);
    _motion.Start();
}

The information that comes back from the Motion API tells us both the device's current orientation and movement/acceleration. For now we are only concerned with the device's orientation and will ignore the other data that is available. For now the fields of interest are the Pitch, Yaw, Roll, and Quanternion. The fist three figures are also used when describing the motion of an aircraft. If an aircraft is changing it's pitch that means that the front of the plan is being tilted up or down. If the airplanes wings remained level but it started moving to the left or right then its yaw is changing. And finally if the plane starts to tilt to the left or right then we would say the plane is rolling. These terms are applied to the phone in a similar way. If you have the device laying face up on a level table with the top of the device facing north than it's pitch, yaw, and roll are all set to zero. (I will call this the "zero position"). As you change the device's orientation these fields will change accordingly. The Motion API returns rotational measurements in radians. This makes sense given that the math functions available from the .Net framework also work with radians. But when displaying them on the screen it is easier to work with degrees. So for display only I have radian to degree converter.

public class RadToDegreeConverter : IValueConverter
{

    public object Convert(object value, Type targetType, 
                                      object parameter, 
                                      System.Globalization.CultureInfo culture)
    {
        double v;
        if (value is float)
            v = (float)value;
        else if (value is double)
            v = (double)value;
        else if (value is decimal)
            v = (double)(decimal)value;
        else
            return String.Empty;
        v = v * 180d / Math.PI;
        return v.ToString("000.0");

    }

    public object ConvertBack(object value, 
                                             Type targetType, 
                                             object parameter, 
                                             System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

The Quanternion figure that comes back also contains rotational information and is consumable by the XNA vector classes. I use the Yaw, Pitch, and Roll for display purposes only but use the Quanternion field in actual algorithms. Your augmented reality application will want to know the direction hat the camera on the phone is facing. Assuming that you are using the rear facing camera that means you will want to know the direction that the rear of the device is facing. To get this information start with creating a Vector3 that represents the direction that the rear of the device is facing when it is at the zero position. This vector will get rotated with the data that comes back from the Motion API and the resulting vector tells us which way that the device is facing.

Vector3 DeviceFaceVector = new Vector3(0,0,-10); 
Vector CurrentDirectionVector { get; set; } 

void motion_CurrentValueChanged(object sender, SensorReadingEventArgs e) 
{
 var attitude = e.SensorReading.Attitude;
 this.Dispatcher.BeginInvoke(() => 
  { 
     IsCalibrated = true; 
     Pitch = attitude.Pitch; 
     Yaw = attitude.Yaw; 
     Roll = attitude.Roll; 
     CurrentDirectionVector = Vector3.Transform(DeviceFaceVector, attitude.Quaternion); 
   }); 
} 

The CurrentDirectionVector will tell us what direction that the back of the device is facing. Let's convert it from a Cartesian (x,y,z) coordinate to a polar coordinate so that we can display the direction the phone is facing (measured as degrees from north) and the angle towards the sky or ground that the phone is tilted. Only a few function calls are needed to do this conversion

void CalculateAltaAzimuth()
{
    double x = CurrentDirectionVector.X;
    double y = CurrentDirectionVector.Y;
    double z = CurrentDirectionVector.Z;

    ViewingAzimuth = Math.Atan2(x, y);
    ViewingAltitude = Math.Atan2(z, Math.Sqrt(x*x + y*y));
}

Displaying the Values on the Screen

To display these values on the screen I will make use of data binding. But I also want to have the camera's point of view used as the program background. Eventually we will be overlaying images on this background. To have the image as a background create a framework element (grid, rectangle, canvas, or some other element) that is stretched over the portion of the screen in which you want the background to display. I'll have it stretched over the entire screen.

<Canvas Width="800" Height="480" x:Name="RealityOverlay">

In the code-behind I need to make a video brush that will be used to paint this surface. The video-brush will have the camera set as it's source.

_photoCamera = new PhotoCamera(CameraType.Primary);
VideoBrush vb = new VideoBrush();
vb.SetSource(_photoCamera);
PhotoBackground.Fill = vb;

This get's us as far as knowing how to tell the device's position. In the next post I'll show how to get the distance and direction to points of interest. Then I'll show how to project those points of interest onto the screen so that they appear to be part of the environment.

Video Explaining the Above

3 minute Code Walkthrough

Tags: ,

Oct 13 2011

I seem to have Stumbled Into Augmented Reality...

Category: Joel Ivory Johnson @ 10:07

At this time of the year the best time to get a picture of the planet Jupiter from where I live is around 3:00am. I've got my telescope by the door so that at first opportunity I can quickly take it outside to perform setup and start taking pictures. But I didn't get a chance to do this Saturday on account of weather. It was cloudy outside and rain was in the forecast, so I knew I wouldn't have an opportunity. So I instead decided to turn my efforts towards something that would be of assistance to my telescope usage in the future. The telescope has a hand control device but in an age with so many smart phones I thought I could do better. So I decided to start on implementing my own hand control with my Windows Phone. 

My first goal was to simply make something that I could use to control the telescope within 24 hours. For the initial run there is a laptop connected to the RS232 port on my telescope. It will eventually be replaced with a .Net Micro Framework device, but for a prototype with 24-hour turn around trying to get custom hardware up and running was not possible. I made a client for my phone that would be my primary point of control for the telescope. I was successful at getting my goal achieved but at the end of it I came to realize that I had started to get into some augmented reality concepts without trying.

I had been taking notes and video logs of my progress (one video log entry is below) and I'll be breaking them down into a form that is more applicable to general applications. Some of the math involved can be scary, so to keep it understandable by more people I will by be making use of the math routines available in XNA and allow the reader to look further into XNA if he or she wants to know what it is doing behind the scenes. For example, you will see how to use an XNA matrix to do rotation calculations, but I won't be explaining what XNA is doing behind the scenes to perform such operation.

The first of several post that comes from my weekend efforts will come tomorrow evening after I proof a few things.

Video

Tags: ,