Audio profile configuration for the masses

Welcome to the second part of the “Things you may not know about Banshee” series of posts, where I highlight some cool features about Banshee that have been introduced in the 0.11.x series. I’m making up for all the blogging I haven’t done in the last 4-5 months.

Just before the GNOME Summit last year, I started working on a user friendly way to perform audio profile configuration. For example, selecting the desired bitrate for an MP3 stream. I had a few goals in mind at the time:

  1. Must be audio framework agnostic. Banshee supports both GStreamer and Helix, and this needs to work with both frameworks without any issues. The user should be able to configure profiles using the same interface and not know which audio framework will be doing the heavy lifting. Essentially, the audio profiles framework should not actually need to know anything about specific audio frameworks. Ever.

  2. Must provide a straight-forward, sensible user interface for configuring complex pipelines. The primary point here is that the user should never have to edit a raw pipeline. A user should not have to “know GStreamer” to change their desired encoding bitrate.

    The current GNOME audio profiles editor
    The current GNOME audio profiles editor :-(

  3. Multiple configurations should be supported on the same profile. Profiles are things like “MP3”, “Ogg Vorbis”, “FLAC”. The profile contains the pipeline and interface description. Configurations are sets of values that can be merged into and saved from a profile. This allows a user to configure a 128 Kbps MP3 encoding setting for their iPod and a 192 Kbps encoding setting for ripping CDs to their local library. Each configuration uses the same base profile, but its settings are different.

  4. Never show profiles that the user won’t be able to use. Not all users have the necessary components installed to be able to encode AAC or MP3 for example. Profiles for these formats should be provided, but if they won’t be able to run, they should not be shown. This means profiles should be tested against their default configuration values before ever presenting a user interface.

With all this in mind, I set out to write the beast. The user interface is defined in XML. Variables define a UI control type, possible values, etc. “Processes” are also defined in the XML with an audio framework ID, for instance “gstreamer.” For GStreamer, the process is the pipeline definition.

However, as of early this morning, the process definition is now an S-Expression. Before, it was simply a pipeline string that had $variables in it, which would be expanded based on the user configuration.

Since the GNOME Summit, I have been working with this profile stuff on and off. It’s been functional since Banshee 0.11.2 (a few months ago), but has been evolving in various ways since then. During this time it was clear that more expressiveness was needed for generating the actual process/pipeline definition. For example, in GStreamer if a user chooses to use VBR in LAME, the xingmux element should be added to the pipeline. However, xingmux is in gst-plugins-bad, and chances are not many users actually have xingmux. This means xingmux should only be appended to the pipeline if VBR is enabled and xingmux is actually available. Other reasons for needing more expressiveness are arguments for GStreamer elements that may be mutually exclusive. If I use mode X, I must provide arguments A and B but not C. If I use mode Y, I must provide arguments C but neither A nor B.

Last night I decided I needed to write an S-Expression evaluator to make this expressiveness a reality. 10 hours later, we now have SExpEngine, and it can do some really cool things. Functions are very easy to add to it and there are a number of built-in functions for logic, conditionals, comparisons, casting, arithmetic, and strings. It also supports variables, which can either be value types or a callback method that returns a tree.

I added a function to allow process S-Expressions to test sub-parts of a pipeline before merging it in to the resulting/final pipeline (think xingmux, from above). Additional GStreamer functionality can be added to build variations of a pipeline based on available elements, differences in GStreamer versions, etc. S-Expressions mean configurability (woo – fake words), reliability, and compatibility.

The result is something I’m quite happy with. For example, here is the S-Expression for the GStreamer LAME process:

+ "audioconvert ! "
"audio/x-raw-int,rate=" $sample_rate ",channels=" $channels " ! "
"audioconvert ! "
            
"lame mode=" $mode " "
            
(if (= $vbr_mode 0)
  (+ "bitrate=" $bitrate)
  (+ "vbr-mode=" $vbr_mode 
    " vbr-quality=" (- 9 $vbr_quality)
    (if (gst-element-is-available "xingmux")
      " ! xingmux"
      ""
    )
  )
) 
      
(if (gst-element-is-available "id3v2mux")
  " ! id3v2mux"
  " ! id3mux"
)

To make sense of the variables, take a look at the full XML LAME profile..

Now, getting back to the “okay, why should I, as a user, care” side of things, I’ll close the post with a screencast (ooooh, fancy, I’ve never done one of these!) that shows all of the profile stuff in action. For the sake of also demoing how the S-Expression evaluates into a proper GStreamer pipeline, I ran Banshee in debug mode for most of the screencast, which shows a text view and a “Test S-Expr” button. Rest assured, if you’re running Banshee like a normal user, you’ll never see this part of the profile configuration dialog :-).

Banshee's audio profiles
What I hope one day can replace the GNOME audio profiles editor so applications other than Banshee can take advantage of the sweetness. Click the screenshot to watch the screencast. (Ogg/Theora).

I’m still working a lot of things out with this, but it’s my hopes to some day make this work outside of Banshee. It’s written with that in mind. At the very least, I’d like to make the XML profile and S-Expression format some kind of standard.

Things you probably didn’t know about Banshee: Part 1

I have been a pretty quiet blogger for the last few months. This is due mostly to the extensive amount of work that has gone into Banshee during this time. After I would reach some milestone where blogging would be appropriate, I’d want to write about it, but just wouldn’t have been able to bring myself to do it. I’m trying to change that now, and as a start, I’ll be writing about new stuff that’s happened in Banshee land in the 0.11.x series.

Scripting Banshee with Boo

Okay, Boo is awesome. During the Mono Summit last November, I wanted a way to be able to work with Banshee’s internal APIs inside of an already running instance. I turned to Boo, and wrote an embedded interpreter which references the loaded Banshee assemblies in the running appliciation domain. It’s called BooBuddy, and I’ll write more about that later. What I’ll share in brevity now is simply that if you’re running Banshee 0.11.2 or later, try pressing CTRL+SHIFT+S and see what happens.

With Boo integrated as an interpreter in Banshee, I decided at the same time it would be a good idea to allow the running of Boo “scripts.” That is, Banshee would load boo files at startup, compile them, and invoke the assembly entry point. Try dropping a .boo file in ~/.gnome2/banshee/scripts and see what happens [1]. In the same location, the .boo files can have normal Banshee plugins coded in them, and they will be compiled and loaded into the normal plugin system. The script’s entry point will be invoked after the script is compiled as well.

This means that it’s possible to write useful snippets of Boo that do cool things against Banshee’s core. Any public API in Banshee is accessible to these Boo scripts, just like plugins. I have been working on adding hooks into useful functionality in core APIs to allow scripts specifically to do extra useful stuff.

Just a few minutes ago, Peter Johanson was crying about spaces in file names that Banshee writes when importing audio CDs, etc. The format of the output path is configurable in the preferences dialog, but it’s very simple. I do not want to expose more complicated formatting options in the UI. I think it’s accepted at this point in time on a UNIX system that ‘File Names Like This.mp3’ are not evil. Now, I wouldn’t name a shell script or a piece of code with that kind of name, but I like my documents and music to have names like that. They look better. I care.

But other people are more hard core traditionalists, and want ‘file_names_like_this.mp3’. What to do? Write a Banshee script, of course. I quickly added a hook to the path formatter, and within seconds, generating those traditional file names with a script like the one that follows was easily possible.

Banshee.Base.FileNamePattern.Filter = { songpath as string |
    songpath.Replace(" ", "_").ToLower()
}

Update: Below is probably a more realistic example, in case you actually want to take advantage of this feature.

Banshee.Base.FileNamePattern.Filter = { songpath as string |
    @/[ ]+/.Replace(@/[^0-9A-Za-z\/ ]+/.Replace(songpath, "" ).ToLower(), "_" )
}

Without the script, for people who like pretty file names:

Unscripted

With the script, for you hard core traditionalists:

Scripted

So, now that this cool functionality has been exposed publicly on the intarweb, I hope we start seeing some cool Banshee scripts starting to float around. You can even post them on the new Banshee Community Forums.

[1] I made a few changes today that allows mixing ‘script’ code and plugins in the same .boo file. Also, before this commit, Banshee looked for an explicit public Main method with no arguments. This meant the script had to provide a def Main(): declaration for anything to happen. With the very latest SVN checkout, no def Main is needed at all, as the Boo compiler will generate an entry point automatically if none is given in code. Assembly.EntryPoint is invoked instead, which is much better than looking for a Main().

Banshee Community Forums now online

Today I finally set up the new Banshee Community Forums, following the lead of Jokosher. I’m not a fan of forums, but my opinion doesn’t really matter here and I hope they catch on in the ever-growing community of Banshee users and contributors!

As always, help, insight, and good times are always waiting for you on the Banshee IRC channel (#banshee on Gimpnet), the project wiki, and the Banshee mailing list. But what’s one more channel of support and community?

Lurk, read, sign up, ask questions, discuss ideas, and help others!

Banshee source tree reorganized… building in MonoDevelop

I spent the better part of today finally reorganizing the bulk of the source tree in Banshee. I’ve been blocking on the transition from GNOME CVS to Subversion for this moment, and things are now much more organized and clean.

A lot of the reorganization was motivated by wanting to get all of the Banshee components properly buildable in MonoDevelop, which is finally a reality. The only problem I had initially was getting Banshee to properly run from MonoDevelop. I knew this wouldn’t really be a possibility because running Banshee uninstalled requires a number of environment variables to be set, which overload where plugins and engines are loaded from, and set things like LD_LIBRARY_PATH and MONO_PATH.

Banseee running from MonoDevelop

Luckily, I’ve got some really nice build variables that come together to form all of this information, so it’s possible to simply execute ‘make run’ from src/. I decided to take advantage of this and created a MonoDevelopBootstrap project that simply creates a small Mono program:

using System;
using System.Diagnostics;

public class MonoDevelopBootstrap
{
    public static void Main()
    {
        Process process = Process.Start("make", "run");
        process.WaitForExit();
    }
}

Using the “Run” feature in MonoDevelop causes all projects in the master solution to properly build, and then MonoDevelopBootstrap will run, which in turn runs “make run” in src/. The end result is simply being able to press F5 in MonoDevelop to build and run Banshee. Hopefully this will only attract more contributors to the project, who aren’t used to dealing much with autotools.

There’s currently one drawback to this however – you can’t simply check Banshee out and build/run in MonoDevelop. You must run autogen/configure, as some files for the build are generated, and make needs to be run in ext/ and libbanshee/. ext/ has external dependencies that are bundled, and libbanshee is, well, a C library.