furnace/src/engine/fileOps/s3m.cpp
2024-08-17 18:51:05 -05:00

1225 lines
37 KiB
C++

/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2024 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "fileOpsCommon.h"
static void readSbiOpData(sbi_t& sbi, SafeReader& reader) {
sbi.Mcharacteristics = reader.readC();
sbi.Ccharacteristics = reader.readC();
sbi.Mscaling_output = reader.readC();
sbi.Cscaling_output = reader.readC();
sbi.Meg_AD = reader.readC();
sbi.Ceg_AD = reader.readC();
sbi.Meg_SR = reader.readC();
sbi.Ceg_SR = reader.readC();
sbi.Mwave = reader.readC();
sbi.Cwave = reader.readC();
sbi.FeedConnect = reader.readC();
}
bool DivEngine::loadS3M(unsigned char* file, size_t len) {
struct InvalidHeaderException {};
bool success=false;
bool opl2=!getConfInt("s3mOPL3",0);
char magic[4]={0,0,0,0};
SafeReader reader=SafeReader(file,len);
warnings="";
unsigned char chanSettings[32];
unsigned int insPtr[256];
unsigned int patPtr[256];
unsigned char chanPan[16];
unsigned char chanMap[32];
unsigned char defVol[256];
unsigned char orders[256];
bool doesPitchSlide[32];
bool doesVibrato[32];
bool doesPanning[32];
bool doesVolSlide[32];
bool doesPanSlide[32];
bool doesArp[32];
bool doesTremolo[32];
bool doesPanbrello[32];
memset(chanSettings,0,32);
memset(insPtr,0,256*sizeof(unsigned int));
memset(patPtr,0,256*sizeof(unsigned int));
memset(chanPan,0,16);
memset(chanMap,0,32);
memset(defVol,0,256);
memset(orders,0,256);
memset(doesPitchSlide,0,32*sizeof(bool));
memset(doesVibrato,0,32*sizeof(bool));
memset(doesPanning,0,32*sizeof(bool));
memset(doesVolSlide,0,32*sizeof(bool));
memset(doesPanSlide,0,32*sizeof(bool));
memset(doesArp,0,32*sizeof(bool));
memset(doesTremolo,0,32*sizeof(bool));
memset(doesPanbrello,0,32*sizeof(bool));
try {
DivSong ds;
ds.version=DIV_VERSION_S3M;
ds.linearPitch=0;
ds.pitchMacroIsLinear=false;
ds.noSlidesOnFirstTick=true;
ds.rowResetsArpPos=true;
ds.ignoreJumpAtEnd=false;
ds.pitchSlideSpeed=12;
logV("Scream Tracker 3 module");
// load here
if (!reader.seek(0x2c,SEEK_SET)) {
logE("premature end of file!");
lastError="incomplete file";
delete[] file;
return false;
}
reader.read(magic,4);
if (memcmp(magic,DIV_S3M_MAGIC,4)!=0) {
logW("the magic isn't complete");
throw EndOfFileException(&reader,reader.tell());
}
if (!reader.seek(0,SEEK_SET)) {
logE("premature end of file!");
lastError="incomplete file";
delete[] file;
return false;
}
ds.name=reader.readStringLatin1(28);
reader.readC(); // 0x1a
if (reader.readC()!=16) {
logW("type is wrong!");
}
reader.readS(); // x
unsigned short ordersLen=reader.readS();
ds.insLen=reader.readS();
if (ordersLen>256) {
logE("invalid order count!");
lastError="invalid order count!";
delete[] file;
return false;
}
logV("orders: %d",ordersLen);
logV("instruments: %d",ds.insLen);
if (ds.insLen<0 || ds.insLen>256) {
logE("invalid instrument count!");
lastError="invalid instrument count!";
delete[] file;
return false;
}
unsigned short patCount=reader.readS();
logV("patterns: %d",patCount);
if (patCount>256) {
logE("invalid pattern count!");
lastError="invalid pattern count!";
delete[] file;
return false;
}
unsigned short flags=reader.readS();
unsigned short version=reader.readS();
bool signedSamples=(reader.readS()==1);
logV("flags: %x",flags);
logV("version: %x",version);
if (signedSamples) {
logV("signed samples: yes");
} else {
logV("signed samples: no");
}
/*if ((flags&64) || version==0x1300) {
ds.noSlidesOnFirstTick=false;
}*/
reader.readI(); // "SCRM"
unsigned char globalVol=reader.readC();
ds.subsong[0]->speeds.val[0]=(unsigned char)reader.readC();
ds.subsong[0]->speeds.len=1;
unsigned char tempo=reader.readC();
ds.subsong[0]->hz=((double)tempo)/2.5;
unsigned char masterVol=reader.readC();
logV("masterVol: %d",masterVol);
logV("signedSamples: %d",signedSamples);
logV("globalVol: %d",globalVol);
reader.readC(); // UC
unsigned char defaultPan=(unsigned char)reader.readC();
logV("defaultPan: %d",defaultPan);
reader.readS(); // reserved
reader.readI();
reader.readI(); // the last 2 bytes is Special. we don't read that.
reader.read(chanSettings,32);
logD("channel settings:");
for (int i=0; i<32; i++) {
logV("%d. %x",i,chanSettings[i]);
}
logD("reading orders...");
size_t curSubSong=0;
int curOrder1=0;
ds.subsong[curSubSong]->ordersLen=0;
bool subSongIncreased=false;
for (int i=0; i<ordersLen; i++) {
unsigned char nextOrder=reader.readC();
orders[i]=curOrder1;
// skip +++ order
if (nextOrder==254) {
logV("- +++");
continue;
}
// next subsong
if (nextOrder==255) {
logV("- end of song");
if (!subSongIncreased) {
curSubSong++;
subSongIncreased=true;
}
curOrder1=0;
continue;
}
subSongIncreased=false;
if (ds.subsong.size()<=curSubSong) {
ds.subsong.push_back(new DivSubSong);
ds.subsong[curSubSong]->ordersLen=0;
ds.subsong[curSubSong]->name=fmt::sprintf("Order %.2X",i);
}
logV("- %.2x",nextOrder);
for (int j=0; j<DIV_MAX_CHANS; j++) {
ds.subsong[curSubSong]->orders.ord[j][ds.subsong[curSubSong]->ordersLen]=nextOrder;
}
ds.subsong[curSubSong]->ordersLen++;
curOrder1++;
}
logD("reading ins pointers...");
for (int i=0; i<ds.insLen; i++) {
insPtr[i]=((unsigned short)reader.readS())*16;
logV("- %.2x",insPtr[i]);
}
logD("reading pat pointers...");
for (int i=0; i<patCount; i++) {
patPtr[i]=((unsigned short)reader.readS())*16;
logV("- %.2x",patPtr[i]);
}
if (defaultPan==252) {
logV("reading channel panning settings");
reader.read(chanPan,16);
} else {
logV("default channel pan");
memset(chanPan,16,16);
}
// determine chips to use
ds.systemLen=0;
bool hasPCM=false;
bool hasFM=false;
int numChans=0;
int realNumChans=0;
for (int ch=0; ch<32; ch++) {
if (chanSettings[ch]!=255) realNumChans++;
}
for (int ch=0; ch<32; ch++) {
if (chanSettings[ch]==255) continue;
if ((chanSettings[ch]&127)>=32) continue;
if ((chanSettings[ch]&127)>=16) {
hasFM=true;
} else {
hasPCM=true;
numChans++;
}
if (hasFM && hasPCM) break;
}
int pcmChan=hasFM?(opl2 ? 9 : 18):0;
int fmChan=hasPCM?32:0;
int invalidChan=40;
for (int ch=0; ch<32; ch++) {
if (chanSettings[ch]==255) {
chanMap[ch]=invalidChan++;
continue;
}
if ((chanSettings[ch]&127)>=32) {
chanMap[ch]=invalidChan++;
continue;
}
if ((chanSettings[ch]&127)>=16) {
chanMap[ch]=fmChan++;
} else {
chanMap[ch]=pcmChan++;
}
}
char buffer[40];
int chanIndex = 1;
if (hasPCM) {
for(int ch = 0; ch < pcmChan - (realNumChans - (hasFM ? 9 : 0)); ch++)
{
ds.subsong[0]->chanShow[ch]=false;
ds.subsong[0]->chanShowChanOsc[ch]=false;
}
for (int ch=pcmChan; ch<32; ch++) {
ds.subsong[0]->chanShow[ch]=false;
ds.subsong[0]->chanShowChanOsc[ch]=false;
}
for(int ch = 0; ch < 32; ch++)
{
if(ds.subsong[0]->chanShow[ch])
{
snprintf(buffer, 40, _("Channel %d"), chanIndex);
ds.subsong[0]->chanName[ch] = buffer;
chanIndex++;
}
}
}
if (hasFM && !opl2) {
for (int ch=(hasPCM?32:0) + 9; ch<(hasPCM?32:0) + 18; ch++) {
ds.subsong[0]->chanShow[ch]=false;
ds.subsong[0]->chanShowChanOsc[ch]=false;
}
chanIndex = 1;
for (int ch=(hasPCM?32:0); ch<(hasPCM?32:0) + 9; ch++) {
snprintf(buffer, 40, _("FM %d"), chanIndex);
ds.subsong[0]->chanName[ch] = buffer;
chanIndex++;
}
}
logV("numChans: %d",numChans);
logV("realNumChans: %d",realNumChans);
ds.systemName="PC";
if (hasPCM) {
ds.system[ds.systemLen]=DIV_SYSTEM_ES5506;
ds.systemVol[ds.systemLen]=(float)globalVol/64.0;
ds.systemPan[ds.systemLen]=0;
ds.systemFlags[ds.systemLen].set("volScale",3900);
ds.systemFlags[ds.systemLen].set("amigaVol",true);
ds.systemFlags[ds.systemLen].set("amigaPitch",true);
ds.systemLen++;
}
if (hasFM) {
ds.system[ds.systemLen]=opl2 ? DIV_SYSTEM_OPL2 : DIV_SYSTEM_OPL3;
ds.systemVol[ds.systemLen]=1.0f;
ds.systemPan[ds.systemLen]=0;
ds.systemLen++;
}
// load instruments/samples
ds.ins.reserve(ds.insLen);
for (int i=0; i<ds.insLen; i++) {
logV("reading instrument %d...",i);
DivInstrument* ins=new DivInstrument;
if (insPtr[i]==0) {
ins->type=DIV_INS_ES5506;
ds.ins.push_back(ins);
DivSample* emptySample=new DivSample;
ds.sample.push_back(emptySample);
continue;
}
if (!reader.seek(insPtr[i]+0x4c,SEEK_SET)) {
logE("premature end of file!");
lastError="incomplete file";
delete ins;
delete[] file;
return false;
}
reader.read(magic,4);
if (memcmp(magic,"SCRS",4)==0) {
ins->type=DIV_INS_ES5506;
} else if (memcmp(magic,"SCRI",4)==0) {
ins->type=DIV_INS_OPL;
} else {
logW("odd magic!");
ins->type=DIV_INS_ES5506;
// read the instrument name anyway
if (!reader.seek(insPtr[i]+48,SEEK_SET)) {
logE("premature end of file!");
lastError="incomplete file";
delete ins;
delete[] file;
return false;
}
String name=reader.readStringLatin1(28);
ins->name=name;
ds.ins.push_back(ins);
continue;
}
if (!reader.seek(insPtr[i],SEEK_SET)) {
logE("premature end of file!");
lastError="incomplete file";
delete ins;
delete[] file;
return false;
}
unsigned char type=reader.readC();
if (ins->type==DIV_INS_ES5506) {
if (type>1) {
logE("invalid instrument type! %d",type);
lastError="invalid instrument!";
delete ins;
delete[] file;
return false;
}
} else {
if (type<2) {
logE("invalid instrument type! %d",type);
lastError="invalid instrument!";
delete ins;
delete[] file;
return false;
}
}
String dosName=reader.readStringLatin1(12);
if (ins->type==DIV_INS_ES5506) {
unsigned int memSeg=0;
memSeg=(unsigned char)reader.readC();
memSeg|=((unsigned short)reader.readS())<<8;
memSeg>>=4; // ???
logV("memSeg: %x",memSeg);
unsigned int length=reader.readI();
if (length>16777216) {
logE("abnormal sample size! %x",reader.tell());
lastError="bad sample size";
delete[] file;
return false;
}
DivSample* s=new DivSample;
s->loopStart=reader.readI();
s->loopEnd=reader.readI();
defVol[i]=reader.readC();
reader.readC(); // x
bool isPacked=reader.readC();
unsigned char flags=reader.readC();
s->centerRate=reader.readI();
// reserved
reader.readI(); // x
reader.readI();
reader.readI();
String name=reader.readStringLatin1(28);
s->name=dosName;
ins->name=name;
// "SCRS"
reader.readI();
// read sample data
if (!reader.seek(memSeg,SEEK_SET)) {
logE("premature end of file!");
lastError="incomplete file";
delete ins;
delete s;
delete[] file;
return false;
}
s->loop=flags&1;
s->depth=(flags&4)?DIV_SAMPLE_DEPTH_16BIT:DIV_SAMPLE_DEPTH_8BIT;
if (length>0) {
s->init(length);
}
if (isPacked) {
logE("ADPCM not supported!");
lastError="ADPCM sample";
delete ins;
delete s;
delete[] file;
return false;
}
if (flags&2) {
// downmix stereo
if (s->depth==DIV_SAMPLE_DEPTH_16BIT) {
for (unsigned int i=0; i<s->samples; i++) {
short l=reader.readS();
if (!signedSamples) {
l^=0x8000;
}
s->data16[i]=l;
}
for (unsigned int i=0; i<s->samples; i++) {
short r=reader.readS();
if (!signedSamples) {
r^=0x8000;
}
s->data16[i]=(s->data16[i]+r)>>1;
}
} else {
for (unsigned int i=0; i<s->samples; i++) {
signed char l=reader.readC();
if (!signedSamples) {
l^=0x80;
}
s->data8[i]=l;
}
for (unsigned int i=0; i<s->samples; i++) {
signed char r=reader.readC();
if (!signedSamples) {
r^=0x80;
}
s->data8[i]=(s->data8[i]+r)>>1;
}
}
} else {
if (s->depth==DIV_SAMPLE_DEPTH_16BIT) {
for (unsigned int i=0; i<s->samples; i++) {
s->data16[i]=reader.readS();
if (!signedSamples) {
s->data16[i]^=0x8000;
}
}
} else {
for (unsigned int i=0; i<s->samples; i++) {
s->data8[i]=reader.readC();
if (!signedSamples) {
s->data8[i]^=0x80;
}
}
}
}
ins->amiga.initSample=ds.sample.size();
ds.sample.push_back(s);
} else {
ins->fm.ops=2;
// reserved
reader.readC();
reader.readC();
reader.readC();
// OPL data
sbi_t s3i;
readSbiOpData(s3i,reader);
// taken from S3I loading code
DivInstrumentFM::Operator& opM=ins->fm.op[0];
DivInstrumentFM::Operator& opC=ins->fm.op[1];
ins->fm.ops = 2;
opM.mult=s3i.Mcharacteristics&15;
opM.ksr=((s3i.Mcharacteristics>>4)&1);
opM.sus=((s3i.Mcharacteristics>>5)&1);
opM.vib=((s3i.Mcharacteristics>>6)&1);
opM.am=((s3i.Mcharacteristics>>7)&1);
opM.tl=s3i.Mscaling_output&63;
opM.ksl=((s3i.Mscaling_output>>6)&3);
opM.ar=((s3i.Meg_AD >> 4)&15);
opM.dr=(s3i.Meg_AD&15);
opM.rr=(s3i.Meg_SR&15);
opM.sl=((s3i.Meg_SR>>4)&15);
opM.ws=s3i.Mwave;
ins->fm.alg=(s3i.FeedConnect&1);
ins->fm.fb=((s3i.FeedConnect>>1)&7);
opC.mult=s3i.Ccharacteristics&15;
opC.ksr=((s3i.Ccharacteristics>>4)&1);
opC.sus=((s3i.Ccharacteristics>>5)&1);
opC.vib=((s3i.Ccharacteristics>>6)&1);
opC.am=((s3i.Ccharacteristics>>7)&1);
opC.tl=s3i.Cscaling_output&63;
opC.ksl=((s3i.Cscaling_output>>6)&3);
opC.ar=((s3i.Ceg_AD>>4)&15);
opC.dr=(s3i.Ceg_AD&15);
opC.rr=(s3i.Ceg_SR&15);
opC.sl=((s3i.Ceg_SR>>4)&15);
opC.ws=s3i.Cwave;
// unused
reader.readC();
defVol[i]=reader.readC();
// what?
unsigned char dsk=reader.readC();
// x
reader.readS();
// C-2 speed - convert to macro
int centerRate=reader.readI();
double centerNote=12.0*log2((double)centerRate/8363.0);
if (round(centerNote)!=0) {
ins->std.arpMacro.len=1;
ins->std.arpMacro.val[0]=round(centerNote);
}
logV("centerRate: %d (%f)",centerRate,centerNote);
// x
reader.seek(12,SEEK_CUR);
String name=reader.readStringLatin1(28);
ins->name=name;
// "SCRI"
reader.readI();
logV("dsk: %d",dsk);
}
ds.ins.push_back(ins);
}
ds.sampleLen=ds.sample.size();
// scan pattern data for effect use
for (int i=0; i<patCount; i++) {
logV("scanning pattern %d...",i);
if (!reader.seek(patPtr[i],SEEK_SET)) {
logE("premature end of file!");
lastError="incomplete file";
ds.unload();
delete[] file;
return false;
}
unsigned short dataLen=reader.readS();
unsigned int dataEnd=reader.tell()+dataLen;
int readRow=0;
while (reader.tell()<dataEnd) {
unsigned char what=reader.readC();
if (what==0) {
readRow++;
if (readRow>=64) break;
continue;
}
unsigned char chan=what&31;
bool hasNoteIns=what&32;
bool hasVol=what&64;
bool hasEffect=what&128;
if (hasNoteIns) {
reader.readC();
reader.readC();
}
if (hasVol) {
reader.readC();
}
if (hasEffect) {
unsigned char effect=reader.readC();
reader.readC(); // effect val
switch (effect+'A'-1) {
case 'D': // vol slide
doesVolSlide[chan]=true;
break;
case 'E': // pitch down
doesPitchSlide[chan]=true;
break;
case 'F': // pitch up
doesPitchSlide[chan]=true;
break;
case 'G': // porta
doesPitchSlide[chan]=true;
break;
case 'H': // vibrato
doesVibrato[chan]=true;
break;
case 'J': // arp
doesArp[chan]=true;
break;
case 'K': // vol slide + vibrato
doesVolSlide[chan]=true;
doesVibrato[chan]=true;
break;
case 'L': // vol slide + porta
doesVolSlide[chan]=true;
doesPitchSlide[chan]=true;
break;
case 'P': // pan slide
doesPanSlide[chan]=true;
break;
case 'R': // tremolo
doesTremolo[chan]=true;
break;
case 'Y': // panbrello
doesPanbrello[chan]=true;
break;
}
}
}
}
// load pattern data
for (int i=0; i<patCount; i++) {
unsigned char effectCol[32];
unsigned char vibStatus[32];
bool vibStatusChanged[32];
bool vibing[32];
bool vibingOld[32];
unsigned char volSlideStatus[32];
bool volSlideStatusChanged[32];
bool volSliding[32];
bool volSlidingOld[32];
unsigned char portaStatus[32];
bool portaStatusChanged[32];
bool porting[32];
bool portingOld[32];
unsigned char portaType[32];
unsigned char arpStatus[32];
bool arpStatusChanged[32];
bool arping[32];
bool arpingOld[32];
unsigned char panStatus[32];
bool panStatusChanged[32];
bool panning[32];
bool panningOld[32];
unsigned char tremStatus[32];
bool tremStatusChanged[32];
bool treming[32];
bool tremingOld[32];
unsigned char panSlideStatus[32];
bool panSlideStatusChanged[32];
bool panSliding[32];
bool panSlidingOld[32];
bool did[32];
if (patPtr[i]==0) continue;
logV("reading pattern %d...",i);
if (!reader.seek(patPtr[i],SEEK_SET)) {
logE("premature end of file!");
lastError="incomplete file";
ds.unload();
delete[] file;
return false;
}
unsigned short dataLen=reader.readS();
unsigned int dataEnd=reader.tell()+dataLen;
int readRow=0;
logV("length: %d",dataLen);
bool mustCommitInitial=true;
memset(effectCol,4,32);
memset(vibStatus,0,32);
memset(vibStatusChanged,0,32*sizeof(bool));
memset(vibing,0,32*sizeof(bool));
memset(vibingOld,0,32*sizeof(bool));
memset(volSlideStatus,0,32);
memset(volSlideStatusChanged,0,32*sizeof(bool));
memset(volSliding,0,32*sizeof(bool));
memset(volSlidingOld,0,32*sizeof(bool));
memset(portaStatus,0,32);
memset(portaStatusChanged,0,32*sizeof(bool));
memset(porting,0,32*sizeof(bool));
memset(portingOld,0,32*sizeof(bool));
memset(portaType,0,32);
memset(arpStatus,0,32);
memset(arpStatusChanged,0,32*sizeof(bool));
memset(arping,0,32*sizeof(bool));
memset(arpingOld,0,32*sizeof(bool));
memset(panStatus,0,32);
memset(panStatusChanged,0,32*sizeof(bool));
memset(panning,0,32*sizeof(bool));
memset(panningOld,0,32*sizeof(bool));
memset(tremStatus,0,32);
memset(tremStatusChanged,0,32*sizeof(bool));
memset(treming,0,32*sizeof(bool));
memset(tremingOld,0,32*sizeof(bool));
memset(panSlideStatus,0,32);
memset(panSlideStatusChanged,0,32*sizeof(bool));
memset(panSliding,0,32*sizeof(bool));
memset(panSlidingOld,0,32*sizeof(bool));
memset(did,0,32*sizeof(bool));
while (reader.tell()<dataEnd) {
unsigned char what=reader.readC();
if (what==0) {
// commit effects
for (int j=0; j<32; j++) {
DivPattern* p=ds.subsong[0]->pat[chanMap[j]].getPattern(i,true);
if (vibing[j]!=vibingOld[j] || vibStatusChanged[j]) {
p->data[readRow][effectCol[j]++]=0x04;
p->data[readRow][effectCol[j]++]=vibing[j]?vibStatus[j]:0;
doesVibrato[j]=true;
} else if (doesVibrato[j] && mustCommitInitial) {
p->data[readRow][effectCol[j]++]=0x04;
p->data[readRow][effectCol[j]++]=0;
}
if (volSliding[j]!=volSlidingOld[j] || volSlideStatusChanged[j]) {
if (volSlideStatus[j]>=0xf1 && volSliding[j]) {
p->data[readRow][effectCol[j]++]=0xf9;
p->data[readRow][effectCol[j]++]=volSlideStatus[j]&15;
volSliding[j]=false;
} else if ((volSlideStatus[j]&15)==15 && volSlideStatus[j]>=0x10 && volSliding[j]) {
p->data[readRow][effectCol[j]++]=0xf8;
p->data[readRow][effectCol[j]++]=volSlideStatus[j]>>4;
volSliding[j]=false;
} else {
p->data[readRow][effectCol[j]++]=0xfa;
p->data[readRow][effectCol[j]++]=volSliding[j]?volSlideStatus[j]:0;
}
doesVolSlide[j]=true;
} else if (doesVolSlide[j] && mustCommitInitial) {
p->data[readRow][effectCol[j]++]=0xfa;
p->data[readRow][effectCol[j]++]=0;
}
if (porting[j]!=portingOld[j] || portaStatusChanged[j]) {
if (portaStatus[j]>=0xe0 && portaType[j]!=3 && porting[j]) {
p->data[readRow][effectCol[j]++]=portaType[j]|0xf0;
p->data[readRow][effectCol[j]++]=(portaStatus[j]&15)*((portaStatus[j]>=0xf0)?1:1);
porting[j]=false;
} else {
p->data[readRow][effectCol[j]++]=portaType[j];
p->data[readRow][effectCol[j]++]=porting[j]?portaStatus[j]:0;
}
doesPitchSlide[j]=true;
} else if (doesPitchSlide[j] && mustCommitInitial) {
p->data[readRow][effectCol[j]++]=0x01;
p->data[readRow][effectCol[j]++]=0;
}
if (arping[j]!=arpingOld[j] || arpStatusChanged[j]) {
p->data[readRow][effectCol[j]++]=0x00;
p->data[readRow][effectCol[j]++]=arping[j]?arpStatus[j]:0;
doesArp[j]=true;
} else if (doesArp[j] && mustCommitInitial) {
p->data[readRow][effectCol[j]++]=0x00;
p->data[readRow][effectCol[j]++]=0;
}
if (treming[j]!=tremingOld[j] || tremStatusChanged[j]) {
p->data[readRow][effectCol[j]++]=0x07;
p->data[readRow][effectCol[j]++]=treming[j]?tremStatus[j]:0;
doesTremolo[j]=true;
} else if (doesTremolo[j] && mustCommitInitial) {
p->data[readRow][effectCol[j]++]=0x07;
p->data[readRow][effectCol[j]++]=0;
}
if (panning[j]!=panningOld[j] || panStatusChanged[j]) {
p->data[readRow][effectCol[j]++]=0x84;
p->data[readRow][effectCol[j]++]=panning[j]?panStatus[j]:0;
doesPanbrello[j]=true;
} else if (doesPanbrello[j] && mustCommitInitial) {
p->data[readRow][effectCol[j]++]=0x84;
p->data[readRow][effectCol[j]++]=0;
}
if (panSliding[j]!=panSlidingOld[j] || panSlideStatusChanged[j]) {
p->data[readRow][effectCol[j]++]=0x83;
p->data[readRow][effectCol[j]++]=panSliding[j]?panSlideStatus[j]:0;
doesPanSlide[j]=true;
} else if (doesPanSlide[j] && mustCommitInitial) {
p->data[readRow][effectCol[j]++]=0x83;
p->data[readRow][effectCol[j]++]=0;
}
if (effectCol[j]>=4+8*2) {
logE("oh crap!");
}
if ((effectCol[j]>>1)-2>ds.subsong[0]->pat[j].effectCols) {
ds.subsong[0]->pat[chanMap[j]].effectCols=(effectCol[j]>>1)-1;
}
}
readRow++;
memset(effectCol,4,32);
memcpy(vibingOld,vibing,32*sizeof(bool));
memcpy(volSlidingOld,volSliding,32*sizeof(bool));
memcpy(portingOld,porting,32*sizeof(bool));
memcpy(arpingOld,arping,32*sizeof(bool));
memset(vibStatusChanged,0,32*sizeof(bool));
memset(volSlideStatusChanged,0,32*sizeof(bool));
memset(portaStatusChanged,0,32*sizeof(bool));
memset(arpStatusChanged,0,32*sizeof(bool));
memset(vibing,0,32*sizeof(bool));
memset(volSliding,0,32*sizeof(bool));
memset(porting,0,32*sizeof(bool));
memset(arping,0,32*sizeof(bool));
memset(did,0,32);
mustCommitInitial=false;
if (readRow>=64) break;
continue;
}
unsigned char chan=what&31;
bool hasNoteIns=what&32;
bool hasVol=what&64;
bool hasEffect=what&128;
if (did[chan]) {
logW("pat %d chan %d row %d: we already populated this channel!",i,chan,readRow);
} else {
did[chan]=true;
}
DivPattern* p=ds.subsong[0]->pat[chanMap[chan]].getPattern(i,true);
if (hasNoteIns) {
unsigned char note=reader.readC();
unsigned char ins=reader.readC();
if (note==254) { // note off
p->data[readRow][0]=100;
p->data[readRow][1]=0;
} else if (note!=255) {
p->data[readRow][0]=note&15;
p->data[readRow][1]=note>>4;
if ((note&15)==0) {
p->data[readRow][0]=12;
p->data[readRow][1]--;
}
}
p->data[readRow][2]=(short)ins-1;
}
if (hasVol) {
unsigned char vol=reader.readC();
if (vol==255) {
p->data[readRow][3]=-1;
} else {
// check for OPL channel
if ((chanSettings[chan]&31)>=16) {
if (vol>63) vol=63;
} else {
if (vol>64) vol=64;
}
p->data[readRow][3]=vol;
}
} else if (p->data[readRow][2]!=-1) {
// populate with instrument volume
unsigned char vol=defVol[p->data[readRow][2]&255];
if ((chanSettings[chan]&31)>=16) {
if (vol>63) vol=63;
} else {
if (vol>64) vol=64;
}
p->data[readRow][3]=vol;
}
if (hasEffect) {
unsigned char effect=reader.readC();
unsigned char effectVal=reader.readC();
switch (effect+'A'-1) {
case 'A': // speed
p->data[readRow][effectCol[chan]++]=0x0f;
p->data[readRow][effectCol[chan]++]=effectVal;
break;
case 'B': // go to order
p->data[readRow][effectCol[chan]++]=0x0b;
logD("0B: %x %x",effectVal,orders[effectVal]);
p->data[readRow][effectCol[chan]++]=orders[effectVal];
break;
case 'C': // next order
p->data[readRow][effectCol[chan]++]=0x0d;
p->data[readRow][effectCol[chan]++]=(effectVal>>4)*10+(effectVal&15);
break;
case 'D': // vol slide
if (effectVal!=0) {
volSlideStatus[chan]=effectVal;
volSlideStatusChanged[chan]=true;
}
if (hasNoteIns) {
volSlideStatusChanged[chan]=true;
}
volSliding[chan]=true;
break;
case 'E': // pitch down
if (effectVal!=0) {
portaStatus[chan]=effectVal;
portaStatusChanged[chan]=true;
}
if (hasNoteIns) {
portaStatusChanged[chan]=true;
}
if (portaType[chan]!=2) {
portaStatusChanged[chan]=true;
}
portaType[chan]=2;
porting[chan]=true;
break;
case 'F': // pitch up
if (effectVal!=0) {
portaStatus[chan]=effectVal;
portaStatusChanged[chan]=true;
}
if (hasNoteIns) {
portaStatusChanged[chan]=true;
}
if (portaType[chan]!=1) {
portaStatusChanged[chan]=true;
}
portaType[chan]=1;
porting[chan]=true;
break;
case 'G': // porta
if (effectVal!=0) {
portaStatus[chan]=effectVal;
portaStatusChanged[chan]=true;
}
if (hasNoteIns) {
portaStatusChanged[chan]=true;
}
if (portaType[chan]!=3) {
portaStatusChanged[chan]=true;
}
portaType[chan]=3;
porting[chan]=true;
break;
case 'H': // vibrato
if (effectVal!=0) {
vibStatus[chan]=effectVal;
vibStatusChanged[chan]=true;
}
vibing[chan]=true;
break;
case 'I': // tremor (!)
break;
case 'J': // arp
if (effectVal!=0) {
arpStatus[chan]=effectVal;
arpStatusChanged[chan]=true;
}
arping[chan]=true;
break;
case 'K': // vol slide + vibrato
if (effectVal!=0) {
volSlideStatus[chan]=effectVal;
volSlideStatusChanged[chan]=true;
}
volSliding[chan]=true;
vibing[chan]=true;
break;
case 'L': // vol slide + porta
if (effectVal!=0) {
volSlideStatus[chan]=effectVal;
volSlideStatusChanged[chan]=true;
}
volSliding[chan]=true;
porting[chan]=true;
portaType[chan]=3;
break;
case 'M': // channel vol (extension)
break;
case 'N': // channel vol slide (extension)
break;
case 'O': // offset
p->data[readRow][effectCol[chan]++]=0x91;
p->data[readRow][effectCol[chan]++]=effectVal;
break;
case 'P': // pan slide (extension)
if (effectVal!=0) {
panSlideStatus[chan]=effectVal;
panSlideStatusChanged[chan]=true;
}
panSliding[chan]=true;
break;
case 'Q': // retrigger
p->data[readRow][effectCol[chan]++]=0x0c;
p->data[readRow][effectCol[chan]++]=effectVal&15;
break;
case 'R': // tremolo
if (effectVal!=0) {
tremStatus[chan]=effectVal;
tremStatusChanged[chan]=true;
}
treming[chan]=true;
break;
case 'S': // special...
switch (effectVal>>4) {
case 0x8:
p->data[readRow][effectCol[chan]++]=0x80;
p->data[readRow][effectCol[chan]++]=(effectVal&15)<<4;
break;
case 0xc:
p->data[readRow][effectCol[chan]++]=0xec;
p->data[readRow][effectCol[chan]++]=effectVal&15;
break;
case 0xd:
p->data[readRow][effectCol[chan]++]=0xed;
p->data[readRow][effectCol[chan]++]=effectVal&15;
break;
}
break;
case 'T': // tempo
p->data[readRow][effectCol[chan]++]=0xf0;
p->data[readRow][effectCol[chan]++]=effectVal;
break;
case 'U': // fine vibrato
if (effectVal!=0) {
vibStatus[chan]=effectVal;
vibStatusChanged[chan]=true;
}
vibing[chan]=true;
break;
case 'V': // global volume (!)
break;
case 'W': // global volume slide (!)
break;
case 'X': // panning (extension)
if (effectVal<=0x80) {
p->data[readRow][effectCol[chan]++]=0x80;
p->data[readRow][effectCol[chan]++]=(effectVal&0x80)?0xff:(effectVal<<1);
}
break;
case 'Y': // panbrello (extension)
if (effectVal!=0) {
panStatus[chan]=effectVal;
panStatusChanged[chan]=true;
}
panning[chan]=true;
break;
case 'Z': // MIDI macro (extension)
break;
}
}
}
}
// copy patterns to the rest of subsongs
for (size_t i=1; i<ds.subsong.size(); i++) {
for (int j=0; j<DIV_MAX_CHANS; j++) {
for (int k=0; k<patCount; k++) {
if (ds.subsong[0]->pat[j].data[k]) ds.subsong[0]->pat[j].data[k]->copyOn(ds.subsong[i]->pat[j].getPattern(k,true));
}
ds.subsong[i]->pat[j].effectCols=ds.subsong[0]->pat[j].effectCols;
}
ds.subsong[i]->speeds=ds.subsong[0]->speeds;
ds.subsong[i]->hz=ds.subsong[0]->hz;
memcpy(ds.subsong[i]->chanShow,ds.subsong[0]->chanShow,DIV_MAX_CHANS*sizeof(bool));
memcpy(ds.subsong[i]->chanShowChanOsc,ds.subsong[0]->chanShowChanOsc,DIV_MAX_CHANS*sizeof(bool));
}
// find subsongs
ds.findSubSongs(DIV_MAX_CHANS);
// populate subsongs with default panning values
if (masterVol&128) { // only in stereo mode
for (size_t i=0; i<ds.subsong.size(); i++) {
for (int j=0; j<16; j++) {
DivPattern* p=ds.subsong[i]->pat[chanMap[j]].getPattern(ds.subsong[i]->orders.ord[j][0],true);
for (int k=0; k<DIV_MAX_EFFECTS; k++) {
if (p->data[0][4+(k<<1)]==0x80) {
// give up if there's a panning effect already
break;
}
if (p->data[0][4+(k<<1)]==-1) {
p->data[0][4+(k<<1)]=0x80;
if (chanPan[j]&16) {
p->data[0][5+(k<<1)]=(j&1)?0xcc:0x33;
} else {
p->data[0][5+(k<<1)]=(chanPan[j]&15)|((chanPan[j]&15)<<4);
}
if (ds.subsong[i]->pat[chanMap[j]].effectCols<=k) ds.subsong[i]->pat[chanMap[j]].effectCols=k+1;
break;
}
}
}
}
}
ds.insLen=ds.ins.size();
ds.sampleLen=ds.sample.size();
if (active) quitDispatch();
BUSY_BEGIN_SOFT;
saveLock.lock();
song.unload();
song=ds;
changeSong(0);
recalcChans();
saveLock.unlock();
BUSY_END;
if (active) {
initDispatch();
BUSY_BEGIN;
renderSamples();
reset();
BUSY_END;
}
success=true;
} catch (EndOfFileException& e) {
//logE("premature end of file!");
lastError="incomplete file";
} catch (InvalidHeaderException& e) {
//logE("invalid header!");
lastError="invalid header!";
}
return success;
}