Friday, January 23, 2015

AB Jukebox FAQ English

1. A simple tutorial

1) Download music information at first launch

2) Manually start/stop download in Settings.

3) You can download single's song's information in Library.

4) Enable/disable Download when launching in Settings.

5) Choose horizontal/vertical features (mood, timbre, rhythm)

6) 10 features available

7) Choose songs by drawing

 8) Play!

9) You can watch this simple tutorial in app Settings.

2. Browse song information in Wikip├ędia

1) Tap a song point on feature pad, then tap its title.

2) Swipe left a song cell in Playlist.

3) Swipe left a song cell in Library.

4) You can search Wikip├ędia by song title, artist name or album name.

3. What's the two points on the right of a song in Library?

The upper point is an indicator of MusicBrainz (, the lower point is an indicator of AcousticBrainz ( 

The blue color means this song's information has been found in the database. The gray color means its information has not been found in the database.

4. Why I can't find some songs (or all of them) on the feature pad?

1) One possible reason is your unfound music files are not tagged properly. AcousticBrainz Jukebox queries song information from database by matching its song title, artist name and album title (optional).

You can download music tagging software Picard, and tag your unfound music files. A tutorial of tagging music file can be found here.

2) If you still can't find these songs on pad, it's probably that the information of these music don't exist in database. You can use this software to calculate the information and then upload it to the database.

Tuesday, January 13, 2015

Hide UITableView index of empty sections iOS

In iOS, we use "UILocalizedIndexedCollation" to partition music library song objects into sections to display them in table view. The "sectionTitles" array of "currentCollation" instance contains all letters in alphabet from A to Z if our system preferred language is English.

UILocalizedIndexedCollation *collation = [UILocalizedIndexedCollation currentCollation];
NSArray *sectionTitles = [collation sectionTitles];

Sometimes, due to lack of song titles which begin with certain letters, we have empty sections. For the sake of redundancy, we don't want display their titles and indexes in table view.

To hide them, we could declare a NSMutableArray to store those sections which are not empty:

// declare this in @interface
@property (nonatomic, strong) NSMutableArray *sectionIndexTitles;

// put this snippet in objects partition function
int i = 0;
for (NSMutableArray *section in unsortedSections) {
    if ([section count] != 0) {
        [sections addObject:[collation sortedArrayFromArray:section collationStringSelector:selector]];
        [self.sectionIndexTitles addObject:[[collation sectionIndexTitles] objectAtIndex:i]];

Finally we set up the table view delegate methods by using our "sectionIndexTitles":

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return [self.sectionIndexTitles count];

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    return [self.sectionIndexTitles objectAtIndex:section];

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
    return self.sectionIndexTitles;

// touching the index jump to corresponding section
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
    return [self.sectionIndexTitles indexOfObject:title];

Read this article to know how to configure UITableView by UILocalizedIndexedCollation.

Background download iOS

1. Setup background download session 

To setup a background download session in iOS is quite straightforward. Firstly setup a configuration, and use this configuration to declare a NSURLSession.

NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfiguration:identifier];
_downloadSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];

Then we declare a NSURLSessionDownloadTask, and start this task by calling resume:
NSURLSessionDownloadTask *task = [fh.downloadSession downloadTaskWithURL:aNSURL];
[task resume];

After that, one should also add four delegate methods which are:
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error

One could collect the download results in the third method "didFinishDownloadingToURL" by using:
NSData *aData = [NSData dataWithContentsOfURL:location];

Notice that the fourth method "didCompleteWithError" will always be called. If the task is finished without error, it returns nil. Otherwise, it returns some error code for us to catch the error.

The task started in foreground will continue its downloading after entering to background.

2. Batch background download data from MusicBrainz 

MusicBrainz is a music meta-information database, one can find and download these information by searching song title, artist name, or album title, etc. It offers a public API for developers to query its database.

But there is a query/request rate limit which is about 1 request/second. That is to say if we launch, say , 100 request tasks at the same time, the server will reject them all. To achieve a successful query without being rejected, I schedule the tasks in a queue by "dispatch_after":

for (MPMediaItem *item in songLibrary) {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // start background music meta-data download task here.

The problem arises when we enter to background, even our download task support background download mode, but our dispatch_after block will stop running. To deal with this problem, I manage to ask for more running time after the app entering to background:

- (void)applicationDidEnterBackground:(UIApplication *)application {
    _backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
    [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTask];
    _backgroundTask = UIBackgroundTaskInvalid;

The method "beginBackgroundTaskWithExpirationHandler" will be called when our _backgroundTask remaining time expired. In my iPhone 4, the system leaves 178 seconds for background task executing.

This method is far from good and needs to be improved.