beaTunes News

Tuesday, April 19, 2016

beaTunes 4.6: High-Resolution Display Support for Windows and Other Goodies

I'm proud to write that today beaTunes 4.6 will be available for download as a free update for existing beaTunes 4 customers. Probably the most important new feature is the much improved high-resolution display support for Windows (sometimes referred to as HD or 4k). Yay! No more squinting to read an artist's name! As Windows hardware has slowly caught up with Apple's early Retina offensive and the Windows OS has started to support HiDPI displays much better, it was definitely time for this step. What was holding me back, was Java 8's lack of HiDPI support for Windows. To this day, there is still plenty wrong with it, so I had to build a few workarounds. Still, if you're using Windows with a high-resolution display, you will eventually notice some left-over rough edges, e.g. slightly odd-looking file dialogs. Those are presented by the Java Runtime Environment and pretty impossible to fix without patching the runtime itself. Luckily, the Oracle Java team is working hard to bring better HiDPI support to Java 9 and hopefully backports some fixes in the process. It may just be a matter of time, until all the little things are resolved.

You may ask, why ship something that's not perfect? Well, in my humble opinion, before today's update beaTunes was mostly broken on Windows HiDPI displays. With today's release it's 99% fixed—offering 100% of its functionality. If you ask me, that's a much better state of affairs.

Besides the obvious user interface changes, a bunch of things under the hood were improved. Writes to the internal database are now coalesced. In essence that means that a song property change is not persisted right away, but with a short delay. If other properties of the same song are also changed during this delay, these multiple write operations are combined into a single one. Especially during analysis of many different properties (e.g. key, BPM, ...) this behavior helps to avoid unnecessary database operations and thus reduces overall analysis time.

JavaScript Support

As we're already talking technical stuff—there's good news for JavaScript hackers! beaTunes now officially supports JavaScript as plugin scripting language. You may remember that Groovy, Jython, and JRuby are already supported. JavaScript is now the fourth language. Because JavaScript uses prototypical and not class-based inheritance, plugins are created in a slightly different way. But thanks to the Nashorn extensions, it's relatively easy for your JavaScript code to interact with beaTunes. Examples can be found in the beaTlet tutorial as well as on GitHub. BTW: the shipped implementations for Groovy and JRuby have been updated.

Pretty much with every minor (not micro) beaTunes update, an inspector gets a little makeover. For beaTunes 4.6, the English capitalization rules received some well-deserved love. For those of you, who've never noticed its existence, this inspector only shows problems, if the song language is known (different language, different rules). Prior to inspection, you can let beaTunes determine song languages automatically via the corresponding analysis task. Results are more accurate, if the lyrics are available. To make a long story short—the inspector has been rewritten and the results are much better now.

Other Little Fixes

A couple of other minor things have been fixed or improved. E.g. the Get Info dialog now shows a little icon to indicate whether a song has already been fingerprinted. And the visible column dialog finally contains proper descriptions for the columns Issue, DRM, and Enabled.

Specifically for Windows, there are two other important items:

  1. The Windows installer is now properly signed (I know, this should have happened a long time ago).
  2. The 32-bit version does not depend on Apple's QuickTime anymore, which turned out to be a serious security risk.

I hope you enjoy this free update. As always, you can download it from the download section of the website.

Thank you.

Most important changes in 4.6.0

  • Fixed relative salience error on 0 BPM.
  • Fixed some English capitalization rules.
  • Fixed missing artist in Album Info pane.
  • Fixed writing of last played date on Windows.
  • Fixed sorting by Album Replay Gain.
  • Fixed DNS lookup during database ops (caused by URL.equals()).
  • Improved performance, via coalesced database writes (reduced disk IO).
  • Improved some analysis error messages.
  • Improved "Analyze New"-behavior: Use last sync as reference point.
  • Improved "Album Info" to more often show actually useful info.
  • Improved HiDPI support for Windows.
  • Added fingerprint icon to Get Info pane.
  • Added support for JavaScript beaTlets.
  • Added signature to Windows installer.
  • Added index column to visible columns popup.
  • Removed QuickTime for Windows dependency (32bit).
  • Updated to JRuby 9.0.5.0.
  • Updated to Groovy 2.4.6.
  • Updated to Hibernate 5.1.0.
  • Updated to japlscript 3.1.3.
  • Updated to jipes 0.9.12.
  • Updated to slf4j 1.7.21.

Labels: , , , , ,

Tuesday, September 22, 2015

Introducing Quick-Search

beaTunes4 logo

With increasing collection sizes, navigating the library becomes more and more difficult. Of course you can filter and search, but quickly going to an album or artist with just a couple of keystrokes is probably the easiest way to navigate. Starting with beaTunes 4.5.3, this is now possible.

Here's how:

For quick-search to work, you need to sort your library by one of the textual columns, e.g. Artist or Album. Make sure the table has the keyboard focus and then start typing the first couple of letters of what you are searching for. In an overlay, beaTunes will show those letters and—once you stop typing—scroll to the next matching row.

As mentioned above, this works for pretty much all textual fields and one additional column: BPM. So when the table is sorted by BPM, you can simply type 100 and beaTunes will scroll to the first song with 100 beats per minute.

There are a couple of other improvements and fixes. Details can be found below. As always, you can download the update from the download section of the website.

Most important changes in 4.5.3

  • Fixed exception after clearing ignored issues.
  • Fixed HiDPI drag and drop images.
  • Fixed error in bpm range coercion arithmetic.
  • Fixed exclusion of audiobooks/iTunesU/home videos from analysis.
  • Added current heap memory use info to About dialog.
  • Added quick-search popup for sorted playlists.
  • Added visual indicator for computed ratings.
  • Improved sync of folder-based-library.
  • Improved built-in Help.
  • Improved AppleScript/COM error reporting.
  • Improved handling of digital booklets.
  • Improved different album artist inspection.

Labels: , ,

Wednesday, March 4, 2015

Faster graphics, fewer crashes: Java 8u40 is finally out!

beaTunes4 logo

Yesterday, Oracle finally released Java 8 update 40. Usually, these updates are minor affairs, but this one is a bit special for me. Buried in the list of fixes, 8u40 addresses the single largest source of beaTunes crashes on OS X. And just because it is surprisingly unclear to what a crash constitutes—let's just say a beaTunes crash is an event during which beaTunes suddenly disappears, OS X offers to send a crash log, and beaTunes also offers to send logs the next time it's started.

After reporting the bug in early September 2014 it was first ignored by the Oracle bug report evaluation team, until a little digging and poking provided more information and suggestions for a fix. Eventually the bug was fixed in late October 2014, but because of the standard release process didn't make it into an official release until now.

Though this is the most important fix for me personally, 8u40 certainly has a couple more things to offer (complete list). Here are my favorites specifically for OS X:

Snapshot

It will take a little while until an official beaTunes release with this updated Java version is published. But if you want to try it out right now, you can use the current development snapshot for OS X.

Labels: , , , ,

Monday, April 29, 2013

Creating Retina-capable Images with Apple's Java 6

beaTunes2 logoFor a while now, beaTunes supports Retina displays. It does this mostly through the NSImage hack. Any images loaded using Toolkit.getDefaultToolkit().getImage("NSImage://your_image_name_without_extension") that conform to the Apple @2x-naming scheme will be drawn in high resolution, if possible. Another trick to draw in high resolution is to apply an AffineTransform of (0.5, 0.5) to your Graphics2D object before drawing. But what do you do, when you cannot access the drawing code and your image is dynamically generated? For example a custom image for a JButton. Or something you loaded from the Internet. Then you somehow have to mimic what's happening when you are using the NSImage-hack. Unfortunately, Apple's JDK does not offer a public API for what it does. But the implementing classes are nevertheless accessible. So with a little bit of reflection magic, we can take advantage of the same mechanism.

Let's see... First we need to be able to figure out, what the current scale factor is. The scale factor is the factor between logical and physical pixels. Retina displays have a factor of 2.0f, regular displays have 1.0f. Luckily the factor is stored in a desktop property, so it's easily accessible:

private static final String APPLE_AWT_CONTENT_SCALE_FACTOR = "apple.awt.contentScaleFactor";

public static float getHiDPIScaleFactor() {
    final Float scaleFactor = (Float) Toolkit.getDefaultToolkit()
        .getDesktopProperty(APPLE_AWT_CONTENT_SCALE_FACTOR);
    return scaleFactor == null ? 1.0f : scaleFactor;
}

public static boolean useHiDPIScaling() {
    return getHiDPIScaleFactor() != 1f;
}

And now the real trickery (for brevity I skipped imports and proper error handling):

// Creates Retina version of the given image.
public static Image toHiDPIResolution(final Image image) {
    if (!useHiDPIScaling()) {
        return image;
    }

    Image hiDPICapableImage = image;
    try {
        final Class cImageClass = Class.forName("apple.awt.CImage");
        final Method getCreator = cImageClass.getDeclaredMethod("getCreator");
        getCreator.setAccessible(true);
        final Object creator = getCreator.invoke(null);
        final Image nativeImage = (Image)creator.getClass()
            .getMethod("createImage", Image.class).invoke(creator, image);

        final Method getNSImage = nativeImage.getClass()
            .getDeclaredMethod("getNSImage");
        getNSImage.setAccessible(true);
        final Object pointer = getNSImage.invoke(nativeImage);
        final float scaleFactor = getHiDPIScaleFactor();
        final int scaledWidth = (int) (image.getWidth(null) / scaleFactor);
        final int scaledHeight = (int) (image.getHeight(null) / scaleFactor);
        hiDPICapableImage = (BufferedImage)creator.getClass()
            .getMethod("createImageWithSize", Long.TYPE, Integer.TYPE, Integer.TYPE)
            .invoke(creator, pointer, scaledWidth, scaledHeight);

        // dereference first image, to avoid double release
        final Field fNSImage = nativeImage.getClass().getDeclaredField("fNSImage");
        fNSImage.setAccessible(true);
        fNSImage.setLong(nativeImage, 0L);
    } catch (Exception e) {
        // in real code - throw something!
        e.printStackTrace();
    }
    return hiDPICapableImage;
}

When called on a Retina capable Mac with Apple's Java 6 (no, it won't work on Java 7/8), this code generates an Image that will be drawn in high resolution without having to apply an AffineTransform on the graphics object. Naturally, logical pixel width and height are halved, i.e. a 200x200 pixel image becomes a 100x100 image, but on a Retina display all 200x200 pixel are still drawn.

Be warned: Since this hack relies on unpublished APIs, it may become unusable at any time due to changes in Apple's JVM.

Enjoy.

Labels: , ,