Let the Music Play! (Matt Gertz)
Oh, boy. Sorry I haven’t written any posts lately, but I’ve transitioned to a new job within Visual Studio and have been getting my sea legs there. One of the job tasks is getting Visual Studio 2008 out to you folks, and while I’ve always been involved with that aspect of the product in the past, it was always to a lesser degree. I could best describe each day now as “23 hours of nervous tension followed by one hour of utter panic” as we knock down the last few things that would otherwise keep us from shipping on time.
(Incidentally, in the spirit of making sure that the product works properly before we ship it, I’ve switched to writing code against Visual Studio 2008 using a build from last week. Everything in this post, though, will still work using Visual Studio 2005, provided you have the current Windows Media Player.)
So, anyway, going forward I hope to do a post or two each month. We’ll see how it goes, but it’s awfully hard to keep me from writing about Visual Basic, so I’m optimistic. J And on that note, on to today’s topic!
The problem with shuffling music
I’m a big music fan, with quite a large collection of songs that I’ve ripped onto my computer. One of the things that I really appreciate about playing music on my PC is that I can listen to random tracks as if I were listening to a radio station (a radio station that always plays what I like and has no chatter) – I don’t hear the same old thing in the same old order every time. The shuffle function gets a lot of use from me.
There’s a difference between “shuffle” and “random” function, of course. “Shuffle” generates a random list from an existing list and then plays that temporary list from beginning to end (so as to avoid replays), whereas with “random,” the next song is calculated on the fly. Unfortunately, statistics being what they are, you’ve got a good chance in the latter case of hearing the same song twice (or more) in a session before all of the songs have been heard.
Most good playback engines on PCs and cars do “shuffle” instead of “random” these days because of this, but shuffle isn’t a panacea, either. The problem is that there are songs which are meant to be played adjacent to another song or else they don’t make sense. The disc authors will break up two songs on a track which (in the listener’s opinion) are really the part of same song. Take, for example, Pink Floyd’s excellent “Dark Side of the Moon” album, which is replete with this sort of issue. Really, is there anyone out there who would listen to track 8 (“Brain Damage”) without listening to track 9 (“Eclipse”)? Not likely; it’s a jarring experience to just have the first track end abruptly.
So, what happens? I throw my favorite songs from various artists into a playlist. I sync that to my music player and head out to mow the lawn, hitting “shuffle” so I’m not always hearing the same thing. Sure enough, after a few minutes, I’m enjoying the last few lines of Jackson Browne’s “The Load-Out”:
But we’ll be scheduled to appear
A thousand miles away from here…
Is this the real life? Is this just fantasy?
Caught in a landslide…
Mentally, I was expecting the track to continue into Jackson Browne’s “Stay” instead of randomly switching to Queen’s “Bohemian Rhapsody,” and, although I do like the latter song, the transition is pretty darn jarring.
What I really want is a way to shuffle my tracks to get that nice sense of randomness, but also keep certain songs together to prevent any jarring playback from inappropriate segues. I’ll code that solution up in this blog, but first I’ll need to cover some of the basics of how you write code against the Windows Media Player.
Windows Media Player coding concepts
The Windows Media Player control is a very powerful control with a lot of functionality, but understanding its object model involves a bit of a learning curve because there’s quite a lot to it. Fortunately, once you understand the basics, it’s pretty easy to work with. There are four really important concepts:
1. The player itself. Being a COM control, it gets wrapped for .NET so that you can write code against it without resorting to Declares, etc., and ends up being an object of type AxWMPLib.AxWindowsMediaPlayer. Properties on the player include which of its controls show and its visibility, whether it auto-plays (via its “Settings” property), actions such as Play and Stop (via the Ctlcontrols property), and so on.
2. The playlist. This is the object that tells the player what media it should play. You don’t “Dim” or “New” a playlist; instead, you ask the player for a new or existing playlist, and it returns an interface of type WMPLib.IWMPPlaylist to the result, on which you can then call methods.
3. The media. Again, these are not objects that you create yourself. There are methods on the player and the playlist which will return interface values (WMPLib.IWMPMedia) for them. The playlist is made up of media objects.
Once you’ve got that in hand, the rest is pretty easy – honest! Let’s run through some examples here.
Part 1: Playing a song
The “Hello, world!” of a music player would be, of course, playing a song. First, create a new Windows Application and make sure that the toolbox is displayed for the resulting form. We’ll need to add the Windows Media control to the toolbox, so right-click on the toolbox and select “Choose Items…” In the resulting dialog, navigate to the “COM Components” tab, scroll down towards the bottom, and check the box to the left of the Windows Media Player. Press “OK,” and the Windows Media Player (which I will henceforth refer to as the WMP) will be added to your toolbox. Drag an instance of it over to your form and size it however you like. (By the way, I’m using the most recent version of WMP, which is version 11 and which you can download for free from Microsoft if you don’t already have it. Earlier versions of the Windows Media Player might not support this all of this code, depending on how old they are.)
While the WMP is still selected, go to the property grid and change its name to “Player” (as the default name is quite a mouthful otherwise). Now, double-click on the form background (not the WMP) to create the “Form1_Load” event.
In Form1_Load, we’ll create a new playlist for the WMP to play:
Dim playlist As WMPLib.IWMPPlaylist
playlist = Me.Player.newPlaylist(“My groovy playlist”, “”)
The first argument to newPlaylist is the name of the new playlist, and the second is a URL to an existing Playlist with whose contents we want to initialize it. I’ve left it as the empty string because in this example I want to start with an empty playlist. (Note that the new playlist is not automatically added to your library, so don’t worry about cluttering up your library here. To actually make the playlist permanent, you’d need to call either importPlaylist or newPlaylist from the IWMPPlaylistCollection returned from Player.playlistCollection property.)
Now, I want to add a song to the new playlist:
Dim item As WMPLib.IWMPMedia = Player.newMedia(“file:///C:UsersMattMusicDead Can DanceSpleen and Ideal8 Avatar.wma”)
The argument to newMedia is a URL, so if I’m using a file from disk, I need to use a URL format (basically, prepend “file:///” to the absolute path in this case). Of course, I normally wouldn’t hardcode this path; I’d read the file name from a file dialog, but I’ve already covered file dialog usage in a previous post so I’ll skip it here for clarity’s sake.
Next, I’ll add the playlist to the player:
Player.currentPlaylist = playlist
Now, let’s press F5. The application launches and, assuming that there’s no mistake in URL we specified, the music automatically starts playing. You can use the WMP controls to control volume and so forth.
Now, it’s possible that you don’t want the music to play until the user actually pushes the “Play” button. This is easy enough to do – simply add this line somewhere *before* you assign the playlist to the player:
Player.settings.autoStart = False
And now you’ve got a way to play any media file (or set of media files) from inside your Windows application, with the user in full control of the playback. Cool, huh?
Part 2: A smarter shuffle
Let’s start out with the following code in Form1_Load:
Player.settings.autoStart = False ‘ Otherwise, playlists will automatically play when added to the player
‘ Create a new playlist
Dim oldplaylist As WMPLib.IWMPPlaylist
Dim newplaylist As WMPLib.IWMPPlaylist
oldplaylist = Me.Player.newPlaylist(“Original Sorted Playlist”, “file:///c:UsersMattMusicPlaylistsOne True Playlist.wpl”)
newplaylist = Me.Player.newPlaylist(“Smart Shuffled Playlist”, “”)
Note that I’m creating two playlists here – one which identically matches a favorite playlist of mine, and one which is empty. I’ll use the empty one to store my smartly-shuffled playlist. Note that I could use Player.playlistCollection.getByName(“One True Playlist”).Item(0) to point to the existing playlist instead of a copy, but since I’m going to be removing media items from one list and moving them to another, that would be destructive to the original. (I could also have sorted within one copied list like I did with card shuffling in an earlier blog post, and avoid even using a second list, but since I’m reusing most of the memory here anyway – that is, the media objects – I’m opting for a more readable implementation this time. Either would work.) Again, I would normally use a file dialog to browse to the playlist rather than hard-coding it – I’m just trying to keep it simple here.
I’ll need a random number generator to pick songs to pull over. If you’ve read my earlier Euchre blog post, you’ll know I usually use a complex one to guarantee the best distribution I can get; however, this being just for a music player, I’ll go with plain-old Random() for brevity’s sake. For the range of the random number, I’ll need to know how many songs we’ll be moving, which I can get from the playlist count. For this first attempt, I’ll just shuffle without regard to disjointed songs:
‘ Randomize the values using system time as a seed
‘ Get the number of songs to use
Dim numberOfSongs As Integer = oldplaylist.count
‘ The value i will keep track of the number of songs left to copy,
‘ which in turn helps us keep track of the range for valid random numbers.
For songsRemaining As Integer = numberOfSongs – 1 To 0 Step -1
‘ Pick a random song from whatever remains in the old list:
Dim SongToCopy As Integer = Microsoft.VisualBasic.Rnd() * songsRemaining
Dim mediaItem = oldplaylist.Item(SongToCopy)
‘ Append it to the new list
‘ Remove it from the old list, which will have its count decrease
Player.currentPlaylist = newplaylist
The variable “songsRemaining” is pulling double-duty here – it makes sure (via the For loop) that I copy over exactly as many songs as possible, and it also constrains the random variable to however songs are remaining in the initial list.
That code works fine for a simple shuffle (go ahead & try it, using one of your own playlists!), but it doesn’t do anything that Windows Media Player can’t already do. So, now I want to massage this code into something which keeps certain songs together. My general plan will be to have some sort of “tag” on songs to indicate that they below with another song, and then if I encounter a random media item which is part of that duo (or trio, or whatever), I’ll bring the others along as well, in the proper order. The trick will be figuring out what tag to use. Fortunately, there are some custom fields associated with media items that I can leverage here.
Ideally, the information that gets downloaded with songs would pre-populate some field which would indicate that one song always belongs with another, but tha