The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Audio::PSID - Perl class to handle PlaySID files (Commodore-64 music files), commonly known as SID files.

SYNOPSIS

    use Audio::PSID;

    $myPSID = new Audio::PSID ("Test.sid") or die "Whoops!";

    print "Name = " . $myPSID->get('name') . "\n";

    print "MD5 = " . $myPSID->getMD5();

    $myPSID->set(author => 'LaLa',
                 name => 'Test2',
                 copyright => '2001 Hungarian Music Crew');

    $myPSID->validate();
    $myPSID->write("Test2.sid") or die "Couldn't write file!";

    @array = $myPSID->getFieldNames();
    print "Fieldnames = " . join(' ', @array) . "\n";

DESCRIPTION

This class is designed to handle PlaySID files (usually bearing a .SID extension), which are music player and data routines converted from the Commodore-64 computer with an additional informational header prepended. For further details about the exact file format, the description of all PSID fields and for about SID tunes in general, see the excellent SIDPLAY homepage at: http://www.geocities.com/SiliconValley/Lakes/5147/ (You can find literally thousands of SID tunes in the High Voltage SID Collection at: http://www.hvsc.c64.org)

This class can handle both version 1 and version 2 PSID files. The class was designed primarily to make it easier to look at and change the PSID header fields, so many of the methods are geared towards that. Use the getFieldNames method to find out the exact names of the fields currently recognized by this class. Please note that fieldnames are case-sensitive!

Methods

PACKAGE->new([SCALAR]) or PACKAGE->new([FILEHANDLE])

Returns a newly created PSID object. If neither SCALAR nor FILEHANDLE is specified, the object is initialized with default values. See $OBJECT->initalize() below.

If SCALAR or FILEHANDLE is specified, an attempt is made to open the given file as specified in $OBJECT->read() below.

OBJECT->initialize()

Initializes the object with default PSID data as follows:

    version => 2,
    dataOffset => 0x7c,
    name => '<?>',
    author => '<?>',
    copyright => '20?? <?>',
    data => '',

FILENAME is set to '' and FILESIZE is set to 0x7c. All other fields (loadAddress, initAddress, playAddress, songs, startSong, speed, flags and reserved) are set to 0. REAL_LOAD_ADDRESS is a read-only field that is always calculated on-the-fly when its value is requested, so it's not stored in the object data per se.

$OBJECT->read([SCALAR]) or $OBJECT->read([FILEHANDLE])

Reads the PSID file given by the filename SCALAR or by FILEHANDLE and populates the stored fields with the values taken from this file. If the given file is a PSID version 1 file, the fields of flags and reserved are set to be undef.

If neither SCALAR nor FILEHANDLE is specified, the value of the FILENAME field is used to determine the name of the input file.

If the file turns out to be an invalid PSID file, the class is initialized with default data only. Valid PSID files must have the ASCII string 'PSID' as their first 4 bytes, and either 0x0001 or 0x0002 as the next 2 bytes in big-endian format.

$OBJECT->write([SCALAR]) or $OBJECT->write([FILEHANDLE])

Writes the PSID file given by the filename SCALAR or by FILEHANDLE to disk. If neither SCALAR nor FILEHANDLE is specified, the value of the FILENAME field is used to determine the name of the output file. Note that SCALAR and FILEHANDLE here can be different than the value of the FILENAME field! If SCALAR or FILEHANDLE is defined, it will not overwrite the filename stored in the FILENAME field.

write will create a version 1 or version 2 PSID file depending on the value of the version field, regardless of whether the other fields are set correctly or not, or even whether they are undef'd or not. However, if $OBJECT->alwaysValidateWrite(1) was called beforehand, write will always write a valid version 2 PSID file. See below.

$OBJECT->get([SCALAR])

Retrieves the value of the field given by the name SCALAR, or returns an array (actually a hash) of all the recognized PSID fields with their values if called in an array/hash context.

If the field name given by SCALAR is unrecognized, the operation is ignored and an undef is returned. If SCALAR is not specified and get is not called from an array context, the same terrible thing will happen. So try not to do either of these.

NOTE: FILENAME, FILESIZE and REAL_LOAD_ADDRESS are special fields that are not really part of a PSID file. FILENAME is simply the name of the file read in (if changed, the $OBJECT->write() will write all data out to the new filename). FILESIZE is the the total size of all data that would be written by $OBJECT->write() if it was called right now (i.e. if you read in a version 1 file and change it in-memory to version 2, FILESIZE will reflect the size of how big the version 2 file would be). Finally, REAL_LOAD_ADDRESS indicates what is the actual Commodore-64 memory location where the PSID data is going to be loaded into. If loadAddress is non-zero, then REAL_LOAD_ADDRESS = loadAddress, otherwise it's the first two bytes of data (read from there in little-endian format).

$OBJECT->set(field => value [, field => value, ...] )

Given one or more field-value pairs it sets the PSID fields given by field to have value. The read-only fields that cannot be set under any circumstance are FILESIZE and REAL_LOAD_ADDRESS, as these fields are set automatically or are implicit.

If you try to set a field that is unrecognized, that particular field-value pair will be ignored. The same happens if you try to change one of the above read-only fields. Trying to set the version field to anything else than 1 or 2 will result in criminal prosecution, expulsion, and possibly death... Actually, it won't harm you, but the invalid value will be ignored.

Whenever the version number is set to 1, the flags and reserved fields are automatically set to be undef'd, and the dataOffset field is reset to 0x0076. If you try to set flags or reserved when version is not 2, the values will be ignored. Trying to set dataOffset when version is 1 will always be reset its value to 0x0076, and dataOffset can't be set to lower than 0x007C if version is 2. You can set it higher, though, in which case either the relevant portion of the original extra padding bytes between the PSID header and the data will be preserved, or additional 0x00 bytes will be added between the PSID header and the data if necessary.

The FILESIZE field is always recalculated, so you don't have to worry about that, even if you change dataOffset or the data portion.

$OBJECT->getFieldNames()

Returns an array that contains all the fieldnames recognized by this class, regardless of the PSID version number. All fieldnames are taken from the standard PSID file format, except FILESIZE, FILENAME and REAL_LOAD_ADDRESS, which are not actually part of the PSID header, but are considered to be descriptive of any PSID file, and are provided merely for convenience.

$OBJECT->getMD5()

Returns a string containing a hexadecimal representation of the 128-bit MD5 fingerprint calculated from the following PSID fields: data (excluding the first 2 bytes if loadAddress is 0), initAddress, playAddress, songs, and the relevant bits of speed. The MD5 fingerprint calculated this way is used, for example, to index into the songlength database, because it provides a way to uniquely identify SID tunes even if the textual credit fields of the PSID file were changed.

$OBJECT->alwaysValidateWrite([SCALAR])

If SCALAR is non-zero, $OBJECT->validate() will always be called before $OBJECT->write() actually writes a file to disk. If SCALAR is 0, this won't happen and the stored PSID data will be written to disk virtually untouched - this is also the default behavior.

$OBJECT->validate()

Regardless of how the PSID fields were populated, this operation will update the stored PSID data to comply with the latest PSID version (currently v2). It also changes the PSID version to v2 if it is not already that, and it will also change the fields so that they take on their prefered values. Operations done by this method include (but are not limited to):

  • bumping up the PSID version to v2,

  • setting the dataOffset to 0x007C,

  • setting the reserved field to 0,

  • chopping the textual fields of name, author and copyright to their maximum length of 31 characters,

  • changing the initAddress to a valid non-zero value,

  • changing the loadAddress to 0 if it is non-zero (and also prepending the data with the non-zero loadAddress)

  • making sure that loadAddress, initAddress and playAddress are within the 0x0000-0xFFFF range (since the Commodore-64 had only 64KB addressable memory), and setting them to 0 if they aren't,

  • making sure that songs is within the range of [1,256], and changing it to 1 if it less than that or to 256 if it is more than that,

  • making sure that startSong is within the range of [1,songs], and changing it to 1 if it is not,

  • setting only the relevant bits in speed, regardless of how many bits were set before,

  • removing extra bytes that may have been between the PSID header and data in the file (usually happens when dataOffset is larger than the total size of the PSID header, i.e. larger than 0x007C),

  • recalculating the FILESIZE field.

BUGS

None is known to exist at this time. If you find any bugs in this module, report them to the author (see "COPYRIGHT" below).

TO DO LIST

More or less in order of perceived priority, from most urgent to least urgent.

  • Maybe those fields not part of the PSID header should have their own getField() functions?

  • Need to think of a good way to extend the class so it can handle the songlength database and STIL info, too.

  • Extend the class to be able to handle all kinds of C64 music files (eg. MUS, .INFO, old .SID and .DAT, etc.), not just PSID .SIDs.

  • Add support for PSID v2B (aka v2NG) redefined fields (proposal is still in a state of flux):

    - flags:

    Bit 0 - specifies format of the binary data (0 = built-in music player, 1 = Compute!'s Sidplayer MUS data, music player must be merged).

    Bit 1 - specifies video standard (0 = PAL, 1 = NTSC).

    Bits 2-3 - specify the SID version (00 = unknonw, 01 = MOS6581, 10 = MOS8580).

    Bit 4 - specifies use of PlaySID samples (0 = no PlaySID samples, 1 = PlaySID samples).

    Bits 5-15 are reserved and should be set to 0.

    - speed is redefined as:

    speed is a 32 bit big endian number, starting at offset 0x12. Each bit in speed specifies the speed for the corresponding tune number, i.e. bit 0 specifies the speed for tune 1. If there are more than 32 tunes, the speed specified for tune 32 is also used for all higher numbered tunes.

    A 0 bit specifies vertical blank interrupt (50Hz PAL, 60Hz NTSC), and a 1 bit specifies CIA 1 timer interrupt default 60Hz).

    Surplus bits in speed should be set to 0.

    Note that if playAddress = 0, the bits in speed should still be set for backwards compatibility with older SID players. New SID players running in a C64 environment will ignore the speed bits in this case.

    - reserved is broken up and is redefined as follows:

    startpage is an 8 bit number, starting at offset 0x78.

    pagelength is an 8 bit number, starting at offset 0x79. If startpage = 0, pagelength should be set to 0, too.

    reserved is a 16 bit big endian number, starting at offset 0x7a. It is reserved and should be set to 0.

  • Add a get() method to retrieve individual bits from speed? (Input might be the song number.)

  • Overload '=' so two objects can be assigned to each other?

COPYRIGHT

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

PSID Perl module - (C) 1999-2001 LaLa <LaLa@C64.org> (Thanks to Adam Lorentzon for showing me how to extract binary data from PSID files! :-)

PSID MD5 calculation - (C) 2001 Michael Schwendt <sidplay@geocities.com>

VERSION

Version v1.60, released to CPAN on January 6, 2002.

First version created on June 11, 1999.

SEE ALSO

the SIDPLAY homepage for the PSID file format documentation: http://www.geocities.com/SiliconValley/Lakes/5147/

the SIDPLAY2 homepage for documents about the PSID v2NG extensions: http://sidplay2.sourceforge.net/

the High Voltage SID Collection, the most comprehensive archive of SID tunes for SID files: http://www.hvsc.c64.org

Digest::MD5