Foobar2000 “Programming”

In case you’ve never used Foobar2000, it’s actually quite the program. I remember when Peter released it in an IRC room like…20 years ago. It was very unique among players at the time in that it’s UI design was pretty basic, even by the day’s standards. But one of the crazier things is it’s internal scripting for “title formatting”. A rather robust set of commands for getting information, formatting it, doing logic operations, math, and almost everything a basic programming language does.

I’ve played it with it rarely, but I dove in to it last night. My goal? I wanted something to output the file length in 75fps “Redbook” timecode.

In the audio CD world, the smallest unit of time you can reference is a frame. There are 75 of them every second and they contain 588 total samples. This 75fps timecode is really only useful when you’re doing audio CD authoring. But, I had a reason I wanted to display the HH:MM:SS:FF information for some tracks; and I didn’t feel like installing the bloated program I used to use.

I looked over all the display options and title formatting commands and found nothing related to displaying frames. So I decided to figure out how to calculate everything out and make Foobar2000’s scripting language do it. Here’s the full script I use for a custom column:

$puts(frm,$div(%samplerate%,75))$puts(rem,$sub(%length_samples%,$mul(%length_seconds_fp%,%samplerate%)))$left(%length_ex%,$sub($len(%length_ex%),4)):$ifgreater($mod($get(rem),$get(frm)),0,$num($add($div($get(rem),$get(frm)),1),2),$num($div($get(rem),$get(frm)),2))

Okay…that looks like a lot of gibberish. Hopefully I can make it clearer.

Running The Numbers

So let’s look at some of the various options Foobar has for giving me the length of a file:

  • %length% – Hours:Minutes:Seconds rounded to nearest second
  • %length_ex% – Same as above with millisconds rounded to nearest millisecond
  • %length_seconds% – length in seconds, rounded to nearest
  • %length_seconds_fp% – length in seconds as floating point number
  • %length_samples% – length of the track in samples

All I actually need are the number of frames that don’t fill a full second, which can be easily calculated using these options:

(sample_length - (length_seconds * samplerate)) / frame size

So the very first version I slapped together actually worked, somewhat:

%length%:$div($sub(%length_samples%,$mul(%length_seconds%,2822400)),37632)

The problem is this was sometimes giving me things like negative frame numbers; and none of the values it gave matched what I did “by hand”. So what gives? Rounding. I’m using the two length fields that round to the nearest second. So my initial hours:minutes:seconds display was sometimes wrong, but when it was; the rest of the calculations were off! But we can fix that. In regards to our calculations for frames, we just have to change to this:

$div($sub(%length_samples%,$mul(%length_seconds_fp%,2822400)),37632)

Now great thing about Foobar2000 is that it automatically converts between strings and integers in the scripting language; which can be a total pain in just about anything else. The downside is it doesn’t do floating point math. In our case, this is fine; when it calculates the length based on the SACD sample rate (that’s what that is by the way), it literally just drops the decimal point. We’re no longer rounding and the number of leftover samples is whatever is in the fraction of a second.

For the initial length display, I started out with this:

$left(%length_ex%,4)

This was fine, but had problems if your file was more than 9 minutes and 59 seconds long; it would start chopping off the minutes. What I needed was someway of telling Foobar2000 to just chop off X number of characters from the right; but I couldn’t see where it had a function to do that. I could display X number of characters from the left, or X number of characters from the right. So…I came up with the functional equivalent of dropping the last 4 characters of %length_ex%:

$left(%length_ex%,$sub($len(%length_ex%),4))

Subtract 4 from the length of the string, use that for the $left command. That way no matter how long the hours:minutes:seconds length is, it’ll fully display. So now I have a script that looks like this:

$left(%length_ex%,$sub($len(%length_ex%),4)):$div($sub(%length_samples%,$mul(%length_seconds_fp%,2822400)),37632)

But there’s still a problem with this, our frame number can still be off by one. How? It’s our old friend the remainder! It’s quite easy to have a number of leftover samples that don’t fit in to the frame size. In fact one of the first tracks I did math verification on came out with a value of 12.75 frames. Well, you can’t have a partial frame.

Now if you remember what I mentioned earlier; Foobar’s math is integer. For the non programmers…that means full, whole numbers with no decimal places/fractions. It doesn’t round up, it doesn’t round down, it just drops the decimal. So how do you determine if you have any leftover frames and just how the hell are we going to solve that?

Foobar’s scripting supports if statements.

All I essentially have to do is have Foobarcheck for a remainder in the division; and if there is a remainder…add one to the final result. Easy peasy right? It actually is. This is exactly what the Modulo operation is for; it literally returns the remainder after two numbers have been divided.

5 mod 2 = 1

Make sense? When you divide 5 by 2 you get 2 with a remainder of 1; or 2.5 on your standard calculator. This makes it easy; we just modulo our calculations for frames. If it’s more than 0, we know we have a rounding situation. We can use an if statement to have the script add one to the result, or do nothing if the modulo is 0.

$ifgreater($mod($sub(%length_samples%,$mul(%length_seconds_fp%,2822400)),37632),0,$num($add($div($sub(%length_samples%,$mul(%length_seconds_fp%,2822400)),37632),1),2),$num($div($sub(%length_samples%,$mul(%length_seconds_fp%,2844200)),37632),2))

So here’s that version of the script; and now you’re probably confused. So I’ll share the command syntax and then break it down “line by line”

$ifgreater(int1,int2,then,else)
$mod(a,b)

The modulo operation is very simple, you just give it your integers. The $ifgreater isn’t that difficult either, just looks confusing. In that statement, you compare integer1 to integer2; if 1 is greater than two, evaluate the then statement; otherwise evaluate else. Here’s what it looks like in some pseudo-code

if (remaining samples % frame size > 0) {
   frames = (remaining samples / framesize)+1
}
else {
   frames = (remaining samples / framesize)
}

In Arduino…the % operation gives you the remainder same as modulo. So if you add some line-breaks to the foobar2000 script code…it might make more sense.

$ifgreater(
$mod($sub(%length_samples%,$mul(%length_seconds_fp%,2822400)),37632),
0,
$num($add($div($sub(%length_samples%,$mul(%length_seconds_fp%,2822400)),37632),1),2),
$num($div($sub(%length_samples%,$mul(%length_seconds_fp%,2844200)),37632),2))

I guess the other thing I should point out is the syntax for most math operations in Foobar is $cmd(a,b). But, what’s probably confusing, is you can specify additional variables or even entire math problems as the integer. The $num function essentially lets you pad numbers, $num(value,places), since I want my frames to have leading 0s; all the math is simply within the value of the $num statement so that a value of less than 10 has a leading 0.

So what I have now is a script that displays 75fps timecode with rounding for leftover frames. Pefrect! But there’s one…last…little…problem. I have designed this around SACD; which uses a 2822400hz sample rate with a framesize of 37632 samples. The good news is….we can make this thing completely framerate agnostic as well as clean the code up by using some variables.

$puts(frm,$div(%samplerate%,75))$puts(rem,$sub(%length_samples%,$mul(%length_seconds_fp%,%samplerate%)))

The $puts command basically puts things in variables, it’s syntax is $puts(name,value) – So the first variable we make, frm; will contain the framesize for a given framerate by dividing the sample rate by 75 ($div(%samplerate%,75)). Since I want to clean the code up; I’ll also put the remaining samples in to a variable since we call it several times. This leads us back to the script I put at the top:

$puts(frm,$div(%samplerate%,75))$puts(rem,$sub(%length_samples%,$mul(%length_seconds_fp%,%samplerate%)))$left(%length_ex%,$sub($len(%length_ex%),4)):$ifgreater($mod($get(rem),$get(frm)),0,$num($add($div($get(rem),$get(frm)),1),2),$num($div($get(rem),$get(frm)),2))

If I break it down so that things are on single lines…it looks like this, which might be easier to understand:

$puts(frm,$div(%samplerate%,75))
$puts(rem,$sub(%length_samples%,$mul(%length_seconds_fp%,%samplerate%)))
$left(%length_ex%,$sub($len(%length_ex%),4)):
$ifgreater(
  $mod($get(rem),$get(frm)),
  0,
  $num($add($div($get(rem),$get(frm)),1),2),
  $num($div($get(rem),$get(frm)),2))

And…that’s basically it. With this script it’ entirely sample rate agnostic; it will figure out the frame size and pull the files sample rate. You can also see how, at least our ifgreater statement has a lot less text using variables.

Site comments disabled. Please use Twitter.