How Perl helped me with my mythmas presents, part 2

Another gift I prepare for the annual winter gift-giving season [0] is a mixtape [1] containing my favorite songs of the past year. As you might know, I still buy (most) of my music, and I buy it on vinyl. So once I year I haul my second turntable into our office (where my other turntable and mixer lives), set up an impromptu studio, and spend a few hours preparing the mixtape. You can view my past mixtapes here (if you can read German..).

I also create a nice cover and write up some liner notes, maybe fiddle a bit with the raw recording (using audacity) and then burn some CDs. As my mixtape is a continuous stream of ~75 minutes of music, I want to burn the CD without gaps between tracks, i.e. using DAO (Disk At Once). My preferred tool used to be gcdmaster, which lets me load one big 80 minutes wav file, set some track marks and burn the CD.

To set track marks, you have to listen to the audio, because it is impossible [2] to see the track bounderies in the wave form rendering. Unfortunately, gcdmaster doesn't seem to work with recent linux audio interfaces (or I wasn't able to set it up). So I used audacity to set the track marks using the "label track" feature and export those labels.

Now I needed to convert those time marks into the format used by gcdmaster (or cdrdao, which gcdmaster is using). This is where Perl comes to the rescue:

audacity exports the timecodes like this:

0.000000        0.000000
173.116112      173.116112
405.816887      405.816887

i.e. seconds and milliseconds

gcdmaster wants this:

// Track 1
FILE "mix.wav" 0 02:53:08

Mind you, the last bit of this time stamp isn't milliseconds, but something apparently called 'frames', which is a value between 0 and 75. Another nasty bit is that the .toc file needed by gcdmaster needs the absolute start time (i.e. from the beginning of the CD) and the length of the track, but audacious of course only exports absolute time offset of each track, so there was some calculation and array munging needed to get the actual length.

So I had to whip up a script to convert from one to the other, which I did, and which helped me produce a nice CD on time.

Here's the code (as hacked at around 23:00 after a 2.5 hour recording session...):

use 5.010;
use strict;
use warnings;

use Path::Class;
use Getopt::Long;
my %o = ();
GetOptions (\%o,

my $wav = $o{wav};
my $labels = Path::Class::file($o{labels})->openr;
my @raw;

while (my $line = <$labels>) {
    $line =~s/=s+$//;

say "CD_DA";

for (my $i=0;$i<@raw;$i++) {
    my $start = $raw[$i];
    my $stop = $raw[$i + 1] || 4740;
    my $length = time2msf($stop - $start);
    $start = time2msf($start);

    my $count = $i +1;
    print << "EOBLOCK"
// Track $count
FILE "$wav" $start $length


sub time2msf {
    my $time = shift;

    my $sec = $+{sec};
    my $millisec = $+{millisec};
    my $min = int($sec / 60);
    $sec = $sec - ($min * 60);
    my $frame = int($millisec *.75);
    my $msf = sprintf("%02i:%02i:%02i",$min,$sec,$frame);
    return $msf;

0: See my last post for more info.
1: Which is in fact a CD
2: This being a mixtape, where one of the goals is to have seamless transitions between tracks

Leave a comment

About domm

user-pic Just in case you like to know, I'm currently full-time father of 2 kids, half-time Perl hacker, sort-of DJ, bicyclist, no longer dreadlocked and more than 34 years old but too lazy to update my profile once a year. I'm also head of, no longer maintainer of the CPANTS project, member of the TPF Grants Commitee and the YAPC Europe Foundation. I've got stuff on CPAN, held various talks and organise the Austrian Perl Workshops and YAPC::Europe 2007.