244 lines
6.4 KiB
C++
244 lines
6.4 KiB
C++
/***************************************************************************
|
|
copyright : (C) 2011 by Mathias Panzenböck
|
|
email : grosser.meister.morti@gmx.net
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* This library is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU Lesser General Public License version *
|
|
* 2.1 as published by the Free Software Foundation. *
|
|
* *
|
|
* This library is distributed in the hope that it will be useful, but *
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
|
* Lesser General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU Lesser General Public *
|
|
* License along with this library; if not, write to the Free Software *
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
|
|
* MA 02110-1301 USA *
|
|
***************************************************************************/
|
|
|
|
#include "s3mfile.h"
|
|
#include "tstringlist.h"
|
|
#include "tdebug.h"
|
|
#include "modfileprivate.h"
|
|
#include "tpropertymap.h"
|
|
|
|
#include <iostream>
|
|
|
|
using namespace TagLib;
|
|
using namespace S3M;
|
|
|
|
class S3M::File::FilePrivate
|
|
{
|
|
public:
|
|
FilePrivate(AudioProperties::ReadStyle propertiesStyle)
|
|
: properties(propertiesStyle)
|
|
{
|
|
}
|
|
|
|
Mod::Tag tag;
|
|
S3M::Properties properties;
|
|
};
|
|
|
|
S3M::File::File(FileName file, bool readProperties,
|
|
AudioProperties::ReadStyle propertiesStyle) :
|
|
Mod::FileBase(file),
|
|
d(new FilePrivate(propertiesStyle))
|
|
{
|
|
if(isOpen())
|
|
read(readProperties);
|
|
}
|
|
|
|
S3M::File::File(IOStream *stream, bool readProperties,
|
|
AudioProperties::ReadStyle propertiesStyle) :
|
|
Mod::FileBase(stream),
|
|
d(new FilePrivate(propertiesStyle))
|
|
{
|
|
if(isOpen())
|
|
read(readProperties);
|
|
}
|
|
|
|
S3M::File::~File()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
Mod::Tag *S3M::File::tag() const
|
|
{
|
|
return &d->tag;
|
|
}
|
|
|
|
PropertyMap S3M::File::properties() const
|
|
{
|
|
return d->tag.properties();
|
|
}
|
|
|
|
PropertyMap S3M::File::setProperties(const PropertyMap &properties)
|
|
{
|
|
return d->tag.setProperties(properties);
|
|
}
|
|
|
|
S3M::Properties *S3M::File::audioProperties() const
|
|
{
|
|
return &d->properties;
|
|
}
|
|
|
|
bool S3M::File::save()
|
|
{
|
|
if(readOnly()) {
|
|
debug("S3M::File::save() - Cannot save to a read only file.");
|
|
return false;
|
|
}
|
|
// note: if title starts with "Extended Module: "
|
|
// the file would look like an .xm file
|
|
seek(0);
|
|
writeString(d->tag.title(), 27);
|
|
// string terminating NUL is not optional:
|
|
writeByte(0);
|
|
|
|
seek(32);
|
|
|
|
ushort length = 0;
|
|
ushort sampleCount = 0;
|
|
|
|
if(!readU16L(length) || !readU16L(sampleCount))
|
|
return false;
|
|
|
|
seek(28, Current);
|
|
|
|
int channels = 0;
|
|
for(int i = 0; i < 32; ++ i) {
|
|
uchar setting = 0;
|
|
if(!readByte(setting))
|
|
return false;
|
|
// or if(setting >= 128)?
|
|
// or channels = i + 1;?
|
|
// need a better spec!
|
|
if(setting != 0xff) ++ channels;
|
|
}
|
|
|
|
seek(channels, Current);
|
|
|
|
StringList lines = d->tag.comment().split("\n");
|
|
// write comment as sample names:
|
|
for(ushort i = 0; i < sampleCount; ++ i) {
|
|
seek(96L + length + ((long)i << 1));
|
|
|
|
ushort instrumentOffset = 0;
|
|
if(!readU16L(instrumentOffset))
|
|
return false;
|
|
seek(((long)instrumentOffset << 4) + 48);
|
|
|
|
if(i < lines.size())
|
|
writeString(lines[i], 27);
|
|
else
|
|
writeString(String::null, 27);
|
|
// string terminating NUL is not optional:
|
|
writeByte(0);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void S3M::File::read(bool)
|
|
{
|
|
if(!isOpen())
|
|
return;
|
|
|
|
READ_STRING(d->tag.setTitle, 28);
|
|
READ_BYTE_AS(mark);
|
|
READ_BYTE_AS(type);
|
|
|
|
READ_ASSERT(mark == 0x1A && type == 0x10);
|
|
|
|
seek(32);
|
|
|
|
READ_U16L_AS(length);
|
|
READ_U16L_AS(sampleCount);
|
|
|
|
d->properties.setSampleCount(sampleCount);
|
|
|
|
READ_U16L(d->properties.setPatternCount);
|
|
READ_U16L(d->properties.setFlags);
|
|
READ_U16L(d->properties.setTrackerVersion);
|
|
READ_U16L(d->properties.setFileFormatVersion);
|
|
|
|
READ_ASSERT(readBlock(4) == "SCRM");
|
|
|
|
READ_BYTE(d->properties.setGlobalVolume);
|
|
READ_BYTE(d->properties.setBpmSpeed);
|
|
READ_BYTE(d->properties.setTempo);
|
|
|
|
READ_BYTE_AS(masterVolume);
|
|
d->properties.setMasterVolume(masterVolume & 0x7f);
|
|
d->properties.setStereo((masterVolume & 0x80) != 0);
|
|
|
|
// I've seen players who call the next two bytes
|
|
// "ultra click" and "use panning values" (if == 0xFC).
|
|
// I don't see them in any spec, though.
|
|
// Hm, but there is "UltraClick-removal" and some other
|
|
// variables in ScreamTracker IIIs GUI.
|
|
|
|
seek(12, Current);
|
|
|
|
int channels = 0;
|
|
for(int i = 0; i < 32; ++ i) {
|
|
READ_BYTE_AS(setting);
|
|
// or if(setting >= 128)?
|
|
// or channels = i + 1;?
|
|
// need a better spec!
|
|
if(setting != 0xff) ++ channels;
|
|
}
|
|
d->properties.setChannels(channels);
|
|
|
|
seek(96);
|
|
ushort realLength = 0;
|
|
for(ushort i = 0; i < length; ++ i) {
|
|
READ_BYTE_AS(order);
|
|
if(order == 255) break;
|
|
if(order != 254) ++ realLength;
|
|
}
|
|
d->properties.setLengthInPatterns(realLength);
|
|
|
|
seek(channels, Current);
|
|
|
|
// Note: The S3M spec mentions samples and instruments, but in
|
|
// the header there are only pointers to instruments.
|
|
// However, there I never found instruments (SCRI) but
|
|
// instead samples (SCRS).
|
|
StringList comment;
|
|
for(ushort i = 0; i < sampleCount; ++ i) {
|
|
seek(96L + length + ((long)i << 1));
|
|
|
|
READ_U16L_AS(sampleHeaderOffset);
|
|
seek((long)sampleHeaderOffset << 4);
|
|
|
|
READ_BYTE_AS(sampleType);
|
|
READ_STRING_AS(dosFileName, 13);
|
|
READ_U16L_AS(sampleDataOffset);
|
|
READ_U32L_AS(sampleLength);
|
|
READ_U32L_AS(repeatStart);
|
|
READ_U32L_AS(repeatStop);
|
|
READ_BYTE_AS(sampleVolume);
|
|
|
|
seek(1, Current);
|
|
|
|
READ_BYTE_AS(packing);
|
|
READ_BYTE_AS(sampleFlags);
|
|
READ_U32L_AS(baseFrequency);
|
|
|
|
seek(12, Current);
|
|
|
|
READ_STRING_AS(sampleName, 28);
|
|
// The next 4 bytes should be "SCRS", but I've found
|
|
// files that are otherwise ok with 4 nils instead.
|
|
// READ_ASSERT(readBlock(4) == "SCRS");
|
|
|
|
comment.append(sampleName);
|
|
}
|
|
|
|
d->tag.setComment(comment.toString("\n"));
|
|
d->tag.setTrackerName("ScreamTracker III");
|
|
}
|