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::SID - Perl module to handle SID files (Commodore-64 music files).

SYNOPSIS

    use Audio::SID;

    $mySID = new Audio::SID('-filename' => 'Test.sid') or die "Whoops!";

    print "Title = " . $mySID->get('title') . "\n";

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

    $mySID->set(author => 'LaLa',
                 title => 'Test2',
                 released => '2001 Hungarian Music Crew');

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

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

AUTHOR

LaLa (Imre Olajos)

DESCRIPTION

This module is designed to handle SID 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, see description of all SID fields in the SID_file_format.txt file included in the module package.

This module can handle PSID version 1, PSID version 2/2NG/3/4 and RSID files. (Version 2+ files are simply v2NG files where v2NG specific fields are set to 0, RSID (RealSID) files are PSID v2NG files with the magicID set to 'RSID' and with some additional restrictions on certain field values.) The module was designed primarily to make it easier to look at and change the SID header fields, so many of the member functions are geared towards that. Use $OBJECT->getFieldNames() to find out the exact names of the fields currently recognized by this module. Please note that fieldnames are case-sensitive!

Member functions

PACKAGE->new()

Usage:

PACKAGE->new(SCALAR) or PACKAGE->new('-filename' => SCALAR) or PACKAGE->new(FILEHANDLE) or PACKAGE->new('-filehandle' => FILEHANDLE) or PACKAGE->new('-filedata' => SCALAR)

Returns a newly created Audio::SID object. If no parameters are specified, the object is initialized with default values. See $OBJECT->initalize() below for initialization details.

If SCALAR or FILEHANDLE is specified (with or without a name-value pair), an attempt is made to open the given file as specified in $OBJECT->read() below. If SCALAR is specified with '-filedata', SCALAR is assumed to contain the binary data of a SID file. Upon failure no object is created and new returns undef.

OBJECT->initialize()

Initializes the object with default SID data as follows:

    magicID => 'RSID',
    version => 4,
    dataOffset => 0x7C,
    songs => 1,
    startSong => 1,
    title => '<?>',
    author => '<?>',
    released => '20?? <?>',
    reserved => undef,
    data => '',

Every other SID field (loadAddress, initAddress, playAddress, speed, flags, startPage, pageLength, secondSIDAddress and thirdSIDAddress) is set to 0. FILENAME is set to '' and the filesize is set to 0x7C.

$OBJECT->read()

Usage:

$OBJECT->read(SCALAR) or $OBJECT->read('-filename' => SCALAR) or $OBJECT->read(FILEHANDLE) or $OBJECT->read('-filehandle' => FILEHANDLE) or $OBJECT->read('-filedata' => SCALAR)

If SCALAR or FILEHANDLE is specified (with or without a name-value pair), an attempt is made to open the given file. If SCALAR is specified with '-filedata', SCALAR is assumed to contain the binary data of a SID file.

If no parameters are specified, the value of FILENAME is used to determine the name of the input file. If that is not set, either, the module is initialized with default data and read() returns an undef. Note that SCALAR and FILEHANDLE here can be different than the value of FILENAME! If SCALAR is defined, it will overwrite the filename stored in FILENAME, otherwise it is not modified. So, watch out when passing in a FILEHANDLE, because FILENAME will not be modified!

If the file turns out to be an invalid SID file, the module is initialized with default data and read() returns an undef. Valid SID files must have the ASCII string 'PSID' or 'RSID' as their first 4 bytes, and either 0x0001, 0x0002, 0x0003 or 0x0004 as the next 2 bytes in big-endian format.

If the given file is a PSID version 1 file, the fields of flags, startPage, pageLength and reserved are set to undef.

$OBJECT->write()

Usage:

$OBJECT->write(SCALAR) or $OBJECT->write('-filename' => SCALAR) or $OBJECT->write(FILEHANDLE) or $OBJECT->write('-filehandle' => FILEHANDLE)

Writes the SID file given by the filename SCALAR or by FILEHANDLE to disk. If neither SCALAR nor FILEHANDLE is specified (with or without a name-value pair), the value of FILENAME is used to determine the name of the output file. If that is not set, either, write() returns an undef. Note that SCALAR and FILEHANDLE here can be different than the value of FILENAME! If SCALAR is defined, it will not overwrite the filename stored in FILENAME.

write will create a version 1 or version 2/2NG/3/4 SID file depending on the value of the version field, and an RSID file if the magicID is set to 'RSID', 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 validated PSID or RSID SID file. See validate.

$OBJECT->get([SCALAR])

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

If the fieldname 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.

For backwards compatibility reasons, "copyright" is always accepted as an alias for the "released" fieldname and "name" is always accepted as an alias for "title".

$OBJECT->getFileName()

Returns the current FILENAME stored in the object.

$OBJECT->getFileSize()

Returns the total size of the SID file that would be written by $OBJECT->write() if it was called right now. This means that if you read in a version 1 file and changed the version field to 2+ without actually saving the file, the size returned here will reflect the size of how big the version 2+ file would be.

$OBJECT->getRealLoadAddress()

The "real load address" indicates what is the actual Commodore-64 memory location where the SID data is going to be loaded into. If loadAddress is non-zero, then loadAddress is returned here, otherwise it's the first two bytes of data (read from there in little-endian format).

$OBJECT->getSpeed([SCALAR])

Returns the speed of the song number specified by SCALAR. If no SCALAR is specified, returns the speed of song #1. Speed can be either 0 (indicating a vertical blank interrupt (50Hz PAL, 60Hz NTSC)), or 1 (indicating CIA 1 timer interrupt (default is 60Hz)).

For PlaySID specific files the corresponding bit of speed from SCALAR modulo 32 is returned ("wraparound" behavior for songs > 32).

For all other files if SCALAR > 32, then bit #31 (MSB) of the speed field is returned ("pegged at 32" behavior for songs > 32).

$OBJECT->getMUSPlayer()

Returns the value of the 'MUSPlayer' bit of the flags field if flags is specified (i.e. when version is 2+), or undef otherwise. The returned value is either 0 (indicating a built-in music player) or 1 (indicating that data is a Compute!'s Sidplayer MUS data and the music player must be merged).

$OBJECT->isMUSPlayerRequired()

This is an alias for $OBJECT->getMUSPlayer().

$OBJECT->getPlaySID()

Returns the value of the 'psidSpecific' bit of the flags field if flags is specified (i.e. when version is 2+) and the magicID is 'PSID'; returns 1 if version is 1; undef otherwise. The returned value is either 0 (indicating that data is Commodore-64 compatible) or 1 (indicating that data is PlaySID specific).

$OBJECT->isPlaySIDSpecific()

This is an alias for $OBJECT->getPlaySID().

$OBJECT->isRSID()

Returns 'true' if the magicID is 'RSID', 'false' otherwise.

$OBJECT->getC64BASIC()

Returns the value of the 'C64BASIC' bit of the flags field if flags is specified (i.e. when version is 2+) and the magicID is 'RSID', or undef otherwise. The returned value is either 1 (indicating that data has a BASIC executable portion, or 0 otherwise.

$OBJECT->isC64BASIC()

This is an alias for $OBJECT->getC64BASIC().

$OBJECT->getClock()

Returns the value of the 'clock' (video standard) bits of the flags field if flags is specified (i.e. when version is 2+), or undef otherwise. The returned value is one of 0 (UNKNOWN), 1 (PAL), 2 (NTSC) or 3 (EITHER).

$OBJECT->getClockByName()

Returns the textual value of the 'clock' (video standard) bits of the flags field if flags is specified (i.e. when version is 2+), or undef otherwise. The textual value will be one of UNKNOWN, PAL, NTSC or EITHER.

$OBJECT->getSIDModel([SCALAR])

Returns the value of the 'sidModel' bits of the flags field if flags is specified (i.e. when version is 2+), or undef otherwise. The returned value is one of 0 (UNKNOWN), 1 (6581), 2 (8580) or 3 (EITHER).

If SCALAR is defined and it's 1, it behaves as above. If SCALAR is 2 and version is 3 or larger, it returns the 'sidModel' bits of the flags field for the second SID, or undef otherwise. If SCALAR is 3 and version is 4 or larger, it returns the 'sidModel' bits of the flags field for the third SID, or undef otherwise.

$OBJECT->getSIDModelByName([SCALAR])

Returns the textual value of the 'sidModel' bits of the flags field if flags is specified (i.e. when version is 2+), or undef otherwise. The textual value will be one of UNKNOWN, 6581, 8580 or EITHER.

If SCALAR is defined and it's 1, it behaves as above. If SCALAR is 2 and version is 3 or larger, it returns the textual value of the 'sidModel' bits of the flags field for the second SID, or undef otherwise. If SCALAR is 3 and version is 4 or larger, it returns the textual value of the 'sidModel' bits of the flags field for the third SID, or undef otherwise.

$OBJECT->getSIDAddress([SCALAR])

Returns the integer value of the SID address for SID number SCALAR.

NOTE: This returns the full address value, not just the value of the middle 2 digits of the address (which is what's actually stored in the <secondSIDAddress> and thirdSIDAddress fields)!

If SCALAR is 1, it always returns 0xD400, as that was the fixed memory mapped address of the C64's SID chip. This is not a value stored in SID files - it's implied and is returned here just for the sake of completeness.

If SCALAR is 2 and version is 3 or greater, it returns the address for the second SID chip, undef otherwise.

If SCALAR is 3 and version is 4 or greater, it returns the address for the third SID chip, undef otherwise.

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

Given one or more field-value pairs it changes the SID fields given by field to have value.

If you try to set a field that is unrecognized, that particular field-value pair will be ignored. Trying to set the version field to anything other than 1, 2, 3 or 4, the invalid value will be ignored. The same is true for the magicID field if you try to set it to anything else but 'PSID' or 'RSID'.

Whenever the version number is changed to 1, the flags, startPage, pageLength and reserved fields are automatically set to be undef'd, the magicID is set to 'PSID' and the dataOffset field is set to 0x0076.

Whenever the version number is changed to 2, the flags, startPage, pageLength and reserved fields are zeroed out if they are not set, yet. If the version number is changed to 3, the secondSIDAddress field will also be set to 0. If the version number is changed to 4, the thirdSIDAddress field will also be set to 0 and reserved will be set to undef.

Whenever the magicID is changed from 'RSID' to 'PSID' or vice versa and flags is not specified at the same time, the psidSpecific field for PSID or the C64BASIC field for RSID is set to 0.

Whenever the magicID is set to 'RSID', the loadAddress, playAddress and speed fields are set to 0, plus the 'psidspecific' bit in the flags field is also set to 0. If loadAddress was non-zero before, its value is prepended to data.

If you try to set magicID, flags, startPage, pageLength or reserved when version is not 2+, the values will be ignored. Trying to set dataOffset when version is 1 will always 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 SID header and the data will be preserved, or additional 0x00 bytes will be added between the SID header and the data if necessary.

Note that the textual fields (title, author, or released) will always be converted to ISO 8859-1 ASCII encoding (i.e. single byte ASCII chars), even if they were Unicode to begin with. This might result in some Unicode characters without ASCII equivalents getting changed to a question mark ('?').

For backwards compatibility reasons, "copyright" is always accepted as an alias for the "released" fieldname and "name" is always accepted as an alias for "title".

$OBJECT->setFileName(SCALAR)

Sets the FILENAME to SCALAR. This filename is used by $OBJECT->read() and $OBJECT->write() when either one of them is called without any arguments. SCALAR can specify either a relative or an absolute pathname to the file - in fact, it can be anything that can be passed to a FileHandle type object as a filename.

$OBJECT->setSpeed(SCALAR1, SCALAR2)

Changes the speed of the song number specified by SCALAR1 to that of SCALAR2. SCALAR1 has to be more than 1 and less than the value of the songs field. SCALAR2 can be either 0 (indicating a vertical blank interrupt (50Hz PAL, 60Hz NTSC)), or 1 (indicating CIA 1 timer interrupt (default is 60Hz)). An undef is returned if neither was specified.

For PlaySID specific files if SCALAR1 is greater than 32, the SCALAR1 module 32 bit of the speed field will be set, overwriting whatever value was in that bit before ("wraparound" behavior for songs > 32).

For all other files if SCALAR1 is greater than 32, then bit #31 (MSB) of the speed flag will be set, overwriting whatever value was in that bit before ("pegged at 32" behavior for songs > 32).

$OBJECT->setMUSPlayer(SCALAR)

Changes the value of the 'MUSPlayer' bit of the flags field to SCALAR if flags is specified (i.e. when version is 2+), returns an undef otherwise. SCALAR must be either 0 (indicating a built-in music player) or 1 (indicating that data is a Compute!'s Sidplayer MUS data and the music player must be merged).

$OBJECT->setPlaySID(SCALAR)

Changes the value of the 'psidSpecific' bit of the flags field to SCALAR if flags is specified (i.e. when version is 2+) and the magicID is 'PSID', returns an undef otherwise. SCALAR must be either 0 (indicating that data is Commodore-64 compatible) or 1 (indicating that data is PlaySID specific).

Note that it is not possible to set v1 files to be PlaySID specific - it's implied that all v1 files are PlaySID specific.

$OBJECT->setC64BASIC(SCALAR)

Changes the value of the 'C64BASIC' bit of the flags field to SCALAR if flags is specified (i.e. when version is 2+) and the magicID is 'RSID', returns an undef otherwise. SCALAR must be either 1 (indicating that data has a C64 BASIC executable portion) or 0 otherwise. Setting this flag to 1 also sets the initAddress field to 0.

$OBJECT->setClock(SCALAR)

Changes the value of the 'clock' (video standard) bits of the flags field to SCALAR if flags is specified (i.e. when version is 2+), returns an undef otherwise. SCALAR must be one of 0 (UNKNOWN), 1 (PAL), 2 (NTSC) or 3 (EITHER).

$OBJECT->setClockByName(SCALAR)

Changes the value of the 'clock' (video standard) bits of the flags field if flags is specified (i.e. when version is 2+), returns an undef otherwise. SCALAR must be be one of UNKNOWN, NONE, NEITHER (all 3 indicating UNKNOWN), PAL, NTSC or ANY, BOTH, EITHER (all 3 indicating EITHER) and is case-insensitive.

$OBJECT->setSIDModel(SCALAR1, [SCALAR2])

Changes the value of the 'sidModel' bits of the flags field if flags is specified (i.e. when version is 2+), returns an undef otherwise. SCALAR1 must be one of 0 (UNKNOWN), 1 (6581), 2 (8580) or 3 (EITHER).

If SCALAR2 is defined and it's 1, it behaves as above. If SCALAR2 is 2 and version is 3 or larger, it sets the 'sidModel' bits of the flags field for the second SID, returns an undef otherwise. If SCALAR2 is 3 and version is 4 or larger, it sets the 'sidModel' bits of the flags field for the third SID, returns an undef otherwise.

$OBJECT->setSIDModelByName(SCALAR1, [SCALAR2])

Changes the value of the 'sidModel' bits of the flags field if flags is specified (i.e. when version is 2+), returns an undef otherwise. SCALAR1 must be be one of UNKNOWN, NONE, NEITHER (all 3 indicating UNKNOWN), 6581, 8580 or ANY, BOTH, EITHER (all 3 indicating EITHER) and is case-insensitive.

If SCALAR2 is defined and it's 1, it behaves as above. If SCALAR2 is 2 and version is 3 or larger, it sets the 'sidModel' bits of the flags field for the second SID, returns an undef otherwise. If SCALAR2 is 3 and version is 4 or larger, it sets the 'sidModel' bits of the flags field for the third SID, returns an undef otherwise.

$OBJECT->setSIDAddress(SCALAR1, SCALAR2)

Changes the value of the secondSIDAddress or thirdSIDAddress fields to SCALAR2 "intelligently", because SCALAR2 is expected to be the full address value.

NOTE: If you want to set the values of the secondSIDAddress or thirdSIDAddress fields directly (with just the value of the middle 2 digits of the address), call set instead.

If SCALAR1 is 1 ("original" C64 SID) or SCALAR1 is id greater than 3, it returns an undef. If SCALAR1 is 2 and the version field is less than 3, it returns an undef. If SCALAR1 is 3 and the version field is less than 4, it returns an undef. If SCALAR2 is undefined, it returns an undef.

SCALAR2 has to be in the range of 0xD420-0xD7E00 or 0xDE00-0xDFE0. In addition, the middle 2 digits of the address have to be even (e.g. 0xD600 is good, 0xD610 is not).

If version is 4, then the addresses of the secondSIDAddress and thirdSIDAddress fields will not be allowed to be the same - an undef is returned if that would be the case.

$OBJECT->getFieldNames()

Returns an array that contains the SID fieldnames recognized by this module, regardless of the SID version number. All fieldnames are taken from the standard SID file format specification, but do not include those fields that are themselves contained in another field, namely any field that is inside the flags field. The fieldnames FILENAME, FILESZIE and PADDING are also not returned here, since those are considered to be descriptive parameters of the SID file and are not part of the SID specification.

$OBJECT->getMD5([SCALAR])

Returns a string containing a hexadecimal representation of the 128-bit MD5 fingerprint calculated from the following SID fields: data (excluding the first 2 bytes if loadAddress is 0), initAddress, playAddress, songs, the relevant bits of speed, and the value of the clock field if it's set to NTSC and SCALAR is zero or not defined. If SCALAR is a nonzero value, the MD5 fingerprint calculation completely ignores the clock field, which provides backward compatibility with earlier MD5 fingerprints.

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 files even if the textual credit fields of the SID 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 SID data will be written to disk virtually untouched - this is also the default behavior.

$OBJECT->validate()

Regardless of how the SID fields were populated, this operation will update the stored SID data to comply with the latest SID version (PSID or RSID v2+). Thus, it may change the SID version field, and it may also change the other fields so that they take on their preferred values. Operations done by this member function include (but are not limited to):

  • setting the version field to 2 if version was 1 before (untouched otherwise),

  • if the magicID is 'RSID', setting the playAddress and speed fields to 0,

  • setting the dataOffset to 0x007C,

  • chopping the textual fields of title, author and released to their maximum length of 32 characters,

  • changing the characters of the textual fields of title, author and released to ISO 8859-1 ASCII bytes (i.e. NOT Unicode),

  • if the magicID is 'RSID', changing the initAddress to zero if it is pointing to a ROM/IO area ($0000-$07E8, $A000-$BFFF or $D000-$FFFF), or if the C64BASIC flag is set to 1, and changing the initAddress to zero if it is outside the load range of the data and the magicID is 'PSID',

  • 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 $0000-$FFFF range (since the Commodore-64 had only 64KB addressable memory), and setting them to 0 if they aren't,

  • making sure that startPage and pageLength are within the 0x00-0xFF range, 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, and setting the rest to 0,

  • setting only the recognized bits in flags, namely 'MUSPlayer', 'psidSpecific', 'clock', 'sidModel', 'second sidModel' (version 3+ only), and 'third sidModel' (bits 0-9) (version 4+ only), and setting the rest to 0,

  • setting the pageLength to 0 if startPage is 0 or 0xFF,

  • setting the startPage to 0xFF and the pageLength to 0 if the relocation range indicated by these two fields overlaps or encompasses the load range of the C64 data,

  • setting the startPage to 0xFF and the pageLength to 0 if the magicID is 'RSID' and the relocation range indicated by these two fields overlaps or encompasses the ROMs ($A000-$BFFF and $D000-$FFFF) or reserved memory ($0000-$03FF) areas,

  • setting the secondSIDAddress and thirdSIDAddress fields according to the rules described for the setSIDAddress function,

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

  • setting the reserved field to 0 if version is less than 4, and setting it to undef if version is 4+,

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.

  • Add Stefano's SID player engine recognizer code.

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

LICENSE

MIT License

Copyright 2017 LaLa (Imre Olajos)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

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

Thanks to Adam Lorentzon for showing me how to extract binary data from SID files! :-)

VERSION

Version v4.01, released to CPAN on July 12, 2017.

First version (then called Audio::PSID) created on June 11, 1999.

SEE ALSO

The SID file format specification:

http://www.hvsc.c64.org/download/C64Music/DOCUMENTS/SID_file_format.txt/

Technical description of the C64's SID chip:

https://www.c64-wiki.com/wiki/SID

The High Voltage SID Collection, the most comprehensive archive of SID tunes:

http://www.hvsc.c64.org

Dependencies:

Digest::MD5