I have a reasonably large classical music collection, and sometimes I'm in the mood to put classical music on, but I don't want to choose exactly what. Yes, I could find some classical music radio station, either over the air or on the internet, but having grown up in an age before universal streaming, sometimes I just want to listen to my music. I'm generally a big fan of randomly shuffled music, and I like Smart Playlists in iTunes. Using smart playlists, I can set up metadata-based playlists like "play a random selection of all rock or blues tracks rated 4 stars or higher than have not been played in the last 3 months."
Classical music is different than rock, pop, blues, jazz, country or most other forms of music. Besides the obvious things, you're often more interested in the composer rather than the performer. For example, I know I like Beethoven better than Vivaldi, but I wouldn't say that I like the Boston Symphony better than the New York Philharmonic. (This isn't 100% true, particularly for solo artists - I might have particular pianists, violinists, etc. that I really like.)
You'll notice another difference with classical music if you try to listen to it by shuffling tracks. You might get the second movement of a Mendelssohn symphony, followed by the third movement of a string quartet, followed by the first movement of a piano concerto, followed by the William Tell Overture. This is not quite what I'm looking for - I'd rather have it play the entire symphony, followed by the entire string quartet, followed by the whole piano concerto, followed by the William Tell Overture.
I could shuffle by album, but that's not quite "shuffle-y" enough. Plus, occasionally I have a piece that spans two discs. I have a nice recording of Tchaikovsky's Symphonies 4, 5, and 6 on two discs, but the poor Fifth Symphony gets split across the discs.
In other words, I don't want to shuffle by track but by work. A classical work is the typical unit of composition and performance - Beethoven wrote the 5th symphony as a cohesive whole, and usually all four movements are performed together and in succession.
Fortunately, iTunes supports classical composers and works, sort of. If you right-click on a track and choose "Get Info", you'll see something like this:
There is a specific field for Composer, but what about Work? Well, it turns out that iTunes treats the Grouping field as the work, at least for tracks in the Classical genre. You used to be able to see this in the Classical Music smart playlist in older version of iTunes, but apparently they've changed things around in more recent versions so it doesn't say anything about the work there anymore.
To aid in the effort of adding composer and grouping information, I made smart playlists called "Classical with No Composer" and "Classical with No Work Grouping", which help to identify where I need to add metadata:
It's relatively easy to add composer information to everything, but quite a bit more work to add Work groupings if you have a large classical music collection. It turns out that having the Work entered for everything is nice, but not critically important, as I'll get to in a minute.
What I want, then, is to get all the tracks in the Classical genre that have a composer, group them by work, then shuffle the works, while preserving the track order within the work. Building on some earlier work coding against the iTunes API, this ultimately evolved into a high-level LINQ expression that's fairly expressive:
List<IITFileOrCDTrack > tracks =
iTunes.Library.FileTracks
.InGenre( "Classical")
.WithComposer()
.WithFile()
.ShuffleByWork()
.ToList();
This expression uses several extension methods on IEnumerable<IITFileOrCDTrack>:
public static IEnumerable< IITFileOrCDTrack> InGenre(this IEnumerable<IITFileOrCDTrack > trackCollection, string genreName)
{
return from IITFileOrCDTrack track in trackCollection
where track.Genre == genreName
select track;
}
public static IEnumerable< IITFileOrCDTrack> WithComposer(this IEnumerable<IITFileOrCDTrack > trackCollection)
{
return from IITFileOrCDTrack track in trackCollection
where !string .IsNullOrWhiteSpace(track.Composer)
select track;
}
public static IEnumerable< IITFileOrCDTrack> WithFile(this IEnumerable<IITFileOrCDTrack > trackCollection)
{
return from IITFileOrCDTrack track in trackCollection
where File .Exists(track.Location)
select track;
}
These are pretty straightforward, but obviously the ShuffleByWork method is the hard part. But first, we need a simple ClassicalWork class:
public class ClassicalWork
{
public string Composer { get; private set ; }
public string Album { get; private set ; }
public string Name { get; private set ; }
}
This class also has a constructor, plus the System.Object overrides Equals(), GetHashCode(), and ToString() methods, but nothing spectacular here.
The ShuffleByWork() method starts out by grouping all the tracks into ClassicalWork objects:
var workGroups = from filetrack in filetracks
group filetrack by new ClassicalWork(filetrack.Composer, filetrack.Album, Classical.WorkName(filetrack.Grouping, filetrack.Name));
The Classical.WorkName() static method constructs the best possible name for the work. If we have a Grouping for the particular track, well, we know that's the work name, so we go with that. If not, we look at the track name. Overtures are normally only one track, and are good to intersperse between longer works, so if the track name contains "overture" return it as the work name. Otherwise, we just an empty string, which means that this track isn't really part of a work (but we'll still include it in the shuffled playlist).
Grouping tracks in this manner is really what defines what we consider to be a work. All tracks on a particular album that have the same composer and work name (as defined above) are considered to be the same work as far as our shuffling goes. This means that all tracks on an album that have the same composer but don't have the same Grouping will still get grouped together into one pseudo-work. In practice, this is fine - if I have several tracks on an album by Sibelius, say, but don't know anything else about whether they fit together, it's perfectly OK to group them together.
So now workGroups is a list of all the works we're dealing with, each with a list of the tracks in the work. Next, we just shuffle all the work groups:
var shuffledWorkGroups = workGroups.Shuffle();
The Shuffle() extension method is perfectly generic (in both senses of the word) and could be used to shuffle any collection - a deck of cards, etc:
public static IEnumerable<T> Shuffle<T>( this IEnumerable <T> enumerable)
{
var random = new Random();
var shuffled = from item in enumerable
orderby random.Next()
select item;
return shuffled;
}
The last thing to do in the ShuffleByWork() method is to iterate through each item in our shuffled work groupings, and return the tracks in each grouping ordered by the original track number:
foreach (var workGroup in shuffledWorkGroups)
{
// Work name is workGroup.Key;
var tracks = workGroup.OrderBy(t => t.TrackNumber);
foreach (IITFileOrCDTrack track in tracks)
{
yield return track;
}
}
Next, we'll create a playlist, deleting an existing playlist if necessary:
IITUserPlaylist playlist = iTunes.FindPlaylistByName(playlistName);
if (playlist != null )
{
playlist.Delete();
}
playlist = iTunes.CreatePlaylist(playlistName);
foreach (var track in tracks)
{
playlist.AddTrack(track);
}
Once the program runs, the playlist obligingly shows up in iTunes:
Finally, we'll create an M3U playlist, which is a very simple text file and will allow non-iTunes apps to access the data.
We'd like to shuffle this on a regular basis, so I need to create a little batch file that can be run as a scheduled task every night. The essence of that is this line:
ClassicalPlaylist.exe "Classical Shuffle" "%USERPROFILE%\Music\My Playlists\Classical Shuffle.m3u"
With that done, I can point all the various music devices and services in my home (Sonos, Plex, etc.) to that .m3u file,
The project and source code is up on Github if you want to take a look.