2024-02-05 14:08:53 -05:00
|
|
|
/**
|
|
|
|
* 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"
|
|
|
|
|
2024-06-22 05:15:11 -04:00
|
|
|
// SBI and some other OPL containers
|
|
|
|
struct sbi_t {
|
|
|
|
uint8_t Mcharacteristics,
|
|
|
|
Ccharacteristics,
|
|
|
|
Mscaling_output,
|
|
|
|
Cscaling_output,
|
|
|
|
Meg_AD,
|
|
|
|
Ceg_AD,
|
|
|
|
Meg_SR,
|
|
|
|
Ceg_SR,
|
|
|
|
Mwave,
|
|
|
|
Cwave,
|
|
|
|
FeedConnect;
|
|
|
|
};
|
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2024-02-05 14:08:53 -05:00
|
|
|
bool DivEngine::loadS3M(unsigned char* file, size_t len) {
|
|
|
|
struct InvalidHeaderException {};
|
|
|
|
bool success=false;
|
|
|
|
char magic[4]={0,0,0,0};
|
|
|
|
SafeReader reader=SafeReader(file,len);
|
|
|
|
warnings="";
|
|
|
|
|
|
|
|
unsigned char chanSettings[32];
|
2024-06-22 03:01:26 -04:00
|
|
|
unsigned int insPtr[256];
|
|
|
|
unsigned int patPtr[256];
|
2024-02-05 14:08:53 -05:00
|
|
|
unsigned char chanPan[16];
|
|
|
|
unsigned char defVol[256];
|
|
|
|
|
2024-06-24 04:04:02 -04:00
|
|
|
unsigned char orders[256];
|
|
|
|
|
2024-06-22 05:15:11 -04:00
|
|
|
bool doesPitchSlide[32];
|
|
|
|
bool doesVibrato[32];
|
|
|
|
bool doesPanning[32];
|
|
|
|
bool doesVolSlide[32];
|
2024-06-22 17:10:49 -04:00
|
|
|
bool doesArp[32];
|
2024-06-22 05:15:11 -04:00
|
|
|
|
2024-06-24 04:04:02 -04:00
|
|
|
memset(chanSettings,0,32);
|
|
|
|
memset(insPtr,0,256*sizeof(unsigned int));
|
|
|
|
memset(patPtr,0,256*sizeof(unsigned int));
|
|
|
|
memset(chanPan,0,16);
|
|
|
|
memset(defVol,0,256);
|
|
|
|
memset(orders,0,256);
|
|
|
|
|
2024-06-22 05:15:11 -04:00
|
|
|
memset(doesPitchSlide,0,32*sizeof(bool));
|
|
|
|
memset(doesVibrato,0,32*sizeof(bool));
|
|
|
|
memset(doesPanning,0,32*sizeof(bool));
|
|
|
|
memset(doesVolSlide,0,32*sizeof(bool));
|
2024-06-22 17:10:49 -04:00
|
|
|
memset(doesArp,0,32*sizeof(bool));
|
2024-06-22 05:15:11 -04:00
|
|
|
|
2024-02-05 14:08:53 -05:00
|
|
|
try {
|
|
|
|
DivSong ds;
|
|
|
|
ds.version=DIV_VERSION_S3M;
|
2024-06-24 03:44:17 -04:00
|
|
|
ds.linearPitch=0;
|
|
|
|
ds.pitchMacroIsLinear=false;
|
2024-02-05 14:08:53 -05:00
|
|
|
ds.noSlidesOnFirstTick=true;
|
|
|
|
ds.rowResetsArpPos=true;
|
|
|
|
ds.ignoreJumpAtEnd=false;
|
2024-06-22 05:15:11 -04:00
|
|
|
ds.pitchSlideSpeed=12;
|
2024-02-05 14:08:53 -05:00
|
|
|
|
2024-06-22 03:01:26 -04:00
|
|
|
logV("Scream Tracker 3 module");
|
|
|
|
|
2024-02-05 14:08:53 -05:00
|
|
|
// 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.readString(28);
|
|
|
|
|
|
|
|
reader.readC(); // 0x1a
|
|
|
|
if (reader.readC()!=16) {
|
|
|
|
logW("type is wrong!");
|
|
|
|
}
|
|
|
|
reader.readS(); // x
|
|
|
|
|
|
|
|
unsigned short ordersLen=reader.readS();
|
|
|
|
ds.insLen=reader.readS();
|
|
|
|
|
2024-06-22 03:01:26 -04:00
|
|
|
logV("orders: %d",ordersLen);
|
|
|
|
logV("instruments: %d",ds.insLen);
|
|
|
|
|
2024-02-05 14:08:53 -05:00
|
|
|
if (ds.insLen<0 || ds.insLen>256) {
|
|
|
|
logE("invalid instrument count!");
|
|
|
|
lastError="invalid instrument count!";
|
|
|
|
delete[] file;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned short patCount=reader.readS();
|
|
|
|
|
2024-06-22 03:01:26 -04:00
|
|
|
logV("patterns: %d",patCount);
|
|
|
|
|
2024-02-05 14:08:53 -05:00
|
|
|
unsigned short flags=reader.readS();
|
|
|
|
unsigned short version=reader.readS();
|
|
|
|
bool signedSamples=(reader.readS()==1);
|
|
|
|
|
2024-06-22 03:01:26 -04:00
|
|
|
logV("flags: %x",flags);
|
|
|
|
logV("version: %x",flags);
|
|
|
|
if (signedSamples) {
|
|
|
|
logV("signed samples: yes");
|
|
|
|
} else {
|
|
|
|
logV("signed samples: no");
|
|
|
|
}
|
|
|
|
|
2024-02-05 14:08:53 -05:00
|
|
|
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();
|
2024-06-22 03:01:26 -04:00
|
|
|
ds.subsong[0]->speeds.len=1;
|
2024-06-22 05:15:11 -04:00
|
|
|
unsigned char tempo=reader.readC();
|
|
|
|
ds.subsong[0]->hz=((double)tempo)/2.5;
|
2024-02-05 14:08:53 -05:00
|
|
|
|
|
|
|
unsigned char masterVol=reader.readC();
|
|
|
|
|
|
|
|
logV("masterVol: %d",masterVol);
|
|
|
|
logV("signedSamples: %d",signedSamples);
|
|
|
|
logV("globalVol: %d",globalVol);
|
|
|
|
|
|
|
|
reader.readC(); // UC
|
2024-06-22 03:01:26 -04:00
|
|
|
unsigned char defaultPan=(unsigned char)reader.readC();
|
|
|
|
|
|
|
|
logV("defaultPan: %d",defaultPan);
|
2024-02-05 14:08:53 -05:00
|
|
|
|
|
|
|
reader.readS(); // reserved
|
|
|
|
reader.readI();
|
|
|
|
reader.readI(); // the last 2 bytes is Special. we don't read that.
|
|
|
|
|
|
|
|
reader.read(chanSettings,32);
|
|
|
|
|
2024-06-22 03:01:26 -04:00
|
|
|
logD("channel settings:");
|
|
|
|
for (int i=0; i<32; i++) {
|
|
|
|
logV("%d. %x",i,chanSettings[i]);
|
|
|
|
}
|
|
|
|
|
2024-02-05 14:08:53 -05:00
|
|
|
logD("reading orders...");
|
2024-06-22 05:15:11 -04:00
|
|
|
size_t curSubSong=0;
|
|
|
|
ds.subsong[curSubSong]->ordersLen=0;
|
2024-06-24 04:14:15 -04:00
|
|
|
bool subSongIncreased=false;
|
2024-02-05 14:08:53 -05:00
|
|
|
for (int i=0; i<ordersLen; i++) {
|
2024-06-22 05:15:11 -04:00
|
|
|
unsigned char nextOrder=reader.readC();
|
2024-06-24 04:04:02 -04:00
|
|
|
orders[i]=curOrder;
|
|
|
|
|
2024-06-22 05:15:11 -04:00
|
|
|
// skip +++ order
|
|
|
|
if (nextOrder==254) {
|
|
|
|
logV("- +++");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// next subsong
|
|
|
|
if (nextOrder==255) {
|
|
|
|
logV("- end of song");
|
2024-06-24 04:14:15 -04:00
|
|
|
if (!subSongIncreased) {
|
|
|
|
curSubSong++;
|
|
|
|
subSongIncreased=true;
|
|
|
|
}
|
2024-06-22 05:15:11 -04:00
|
|
|
curOrder=0;
|
|
|
|
continue;
|
|
|
|
}
|
2024-06-24 04:14:15 -04:00
|
|
|
subSongIncreased=false;
|
2024-06-22 05:15:11 -04:00
|
|
|
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++;
|
2024-06-24 04:04:02 -04:00
|
|
|
curOrder++;
|
2024-02-05 14:08:53 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
logD("reading ins pointers...");
|
|
|
|
for (int i=0; i<ds.insLen; i++) {
|
2024-06-22 05:15:11 -04:00
|
|
|
insPtr[i]=((unsigned short)reader.readS())*16;
|
2024-02-05 14:08:53 -05:00
|
|
|
logV("- %.2x",insPtr[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
logD("reading pat pointers...");
|
|
|
|
for (int i=0; i<patCount; i++) {
|
2024-06-22 05:15:11 -04:00
|
|
|
patPtr[i]=((unsigned short)reader.readS())*16;
|
2024-02-05 14:08:53 -05:00
|
|
|
logV("- %.2x",patPtr[i]);
|
|
|
|
}
|
|
|
|
|
2024-06-22 03:01:26 -04:00
|
|
|
if (defaultPan==252) {
|
|
|
|
logV("reading channel panning settings");
|
2024-02-05 14:08:53 -05:00
|
|
|
reader.read(chanPan,16);
|
|
|
|
} else {
|
2024-06-22 03:01:26 -04:00
|
|
|
logV("default channel pan");
|
2024-06-22 18:25:23 -04:00
|
|
|
memset(chanPan,16,16);
|
2024-02-05 14:08:53 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// determine chips to use
|
|
|
|
ds.systemLen=0;
|
|
|
|
|
|
|
|
bool hasPCM=false;
|
|
|
|
bool hasFM=false;
|
2024-06-22 18:25:23 -04:00
|
|
|
int numChans=0;
|
2024-02-05 14:08:53 -05:00
|
|
|
|
|
|
|
for (int i=0; i<32; i++) {
|
2024-06-22 03:01:26 -04:00
|
|
|
if (chanSettings[i]==255) continue;
|
2024-02-05 14:08:53 -05:00
|
|
|
if ((chanSettings[i]&127)>=32) continue;
|
|
|
|
if ((chanSettings[i]&127)>=16) {
|
|
|
|
hasFM=true;
|
|
|
|
} else {
|
|
|
|
hasPCM=true;
|
2024-06-22 18:25:23 -04:00
|
|
|
numChans++;
|
2024-02-05 14:08:53 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if (hasFM && hasPCM) break;
|
|
|
|
}
|
|
|
|
|
2024-06-24 03:44:17 -04:00
|
|
|
logV("numChans: %d",numChans);
|
|
|
|
|
2024-02-05 14:08:53 -05:00
|
|
|
ds.systemName="PC";
|
|
|
|
if (hasPCM) {
|
2024-06-24 03:44:17 -04:00
|
|
|
ds.system[ds.systemLen]=DIV_SYSTEM_ES5506;
|
2024-06-24 04:14:15 -04:00
|
|
|
ds.systemVol[ds.systemLen]=(float)globalVol/64.0;
|
2024-06-24 03:44:17 -04:00
|
|
|
ds.systemPan[ds.systemLen]=0;
|
2024-06-24 04:14:15 -04:00
|
|
|
ds.systemFlags[ds.systemLen].set("volScale",3900);
|
2024-06-24 03:44:17 -04:00
|
|
|
ds.systemFlags[ds.systemLen].set("amigaVol",true);
|
|
|
|
ds.systemFlags[ds.systemLen].set("amigaPitch",true);
|
|
|
|
ds.systemLen++;
|
2024-02-05 14:08:53 -05:00
|
|
|
}
|
|
|
|
if (hasFM) {
|
|
|
|
ds.system[ds.systemLen]=DIV_SYSTEM_OPL2;
|
|
|
|
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++) {
|
2024-06-22 03:01:26 -04:00
|
|
|
logV("reading instrument %d...",i);
|
2024-02-05 14:08:53 -05:00
|
|
|
DivInstrument* ins=new DivInstrument;
|
2024-06-22 03:01:26 -04:00
|
|
|
if (!reader.seek(insPtr[i]+0x4c,SEEK_SET)) {
|
2024-02-05 14:08:53 -05:00
|
|
|
logE("premature end of file!");
|
|
|
|
lastError="incomplete file";
|
|
|
|
delete ins;
|
|
|
|
delete[] file;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
reader.read(magic,4);
|
|
|
|
|
|
|
|
if (memcmp(magic,"SCRS",4)==0) {
|
2024-06-24 03:44:17 -04:00
|
|
|
ins->type=DIV_INS_ES5506;
|
2024-02-05 14:08:53 -05:00
|
|
|
} else if (memcmp(magic,"SCRI",4)==0) {
|
|
|
|
ins->type=DIV_INS_OPL;
|
|
|
|
} else {
|
2024-06-22 05:15:11 -04:00
|
|
|
logW("odd magic!");
|
2024-06-24 04:26:38 -04:00
|
|
|
ins->type=DIV_INS_ES5506;
|
2024-02-05 14:08:53 -05:00
|
|
|
ds.ins.push_back(ins);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2024-06-22 03:01:26 -04:00
|
|
|
if (!reader.seek(insPtr[i],SEEK_SET)) {
|
2024-02-05 14:08:53 -05:00
|
|
|
logE("premature end of file!");
|
|
|
|
lastError="incomplete file";
|
|
|
|
delete ins;
|
|
|
|
delete[] file;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-06-22 05:15:11 -04:00
|
|
|
unsigned char type=reader.readC();
|
2024-02-05 14:08:53 -05:00
|
|
|
|
2024-06-24 03:44:17 -04:00
|
|
|
if (ins->type==DIV_INS_ES5506) {
|
2024-06-22 05:15:11 -04:00
|
|
|
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.readString(12);
|
|
|
|
|
2024-06-24 03:44:17 -04:00
|
|
|
if (ins->type==DIV_INS_ES5506) {
|
2024-02-05 14:08:53 -05:00
|
|
|
unsigned int memSeg=0;
|
|
|
|
memSeg=(unsigned char)reader.readC();
|
|
|
|
memSeg|=((unsigned short)reader.readS())<<8;
|
|
|
|
|
2024-06-22 03:01:26 -04:00
|
|
|
memSeg>>=4; // ???
|
|
|
|
|
|
|
|
logV("memSeg: %x",memSeg);
|
2024-02-05 14:08:53 -05:00
|
|
|
|
|
|
|
unsigned int length=reader.readI();
|
|
|
|
|
2024-06-22 03:01:26 -04:00
|
|
|
logV("length: %x",length);
|
|
|
|
|
2024-02-05 14:08:53 -05:00
|
|
|
DivSample* s=new DivSample;
|
|
|
|
s->depth=DIV_SAMPLE_DEPTH_8BIT;
|
|
|
|
s->init(length);
|
|
|
|
|
|
|
|
s->loopStart=reader.readI();
|
|
|
|
s->loopEnd=reader.readI();
|
|
|
|
defVol[i]=reader.readC();
|
|
|
|
|
|
|
|
logV("defVol: %d",defVol[i]);
|
|
|
|
|
|
|
|
reader.readC(); // x
|
2024-06-22 03:01:26 -04:00
|
|
|
|
|
|
|
bool isPacked=reader.readC();
|
|
|
|
unsigned char flags=reader.readC();
|
|
|
|
|
|
|
|
s->centerRate=reader.readI();
|
|
|
|
|
|
|
|
// reserved
|
|
|
|
reader.readI(); // x
|
|
|
|
reader.readI();
|
|
|
|
reader.readI();
|
|
|
|
|
|
|
|
String name=reader.readString(28);
|
2024-06-22 05:15:11 -04:00
|
|
|
s->name=dosName;
|
2024-06-22 03:01:26 -04:00
|
|
|
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;
|
|
|
|
s->init(length>>(s->depth==DIV_SAMPLE_DEPTH_16BIT?1:0));
|
|
|
|
|
|
|
|
if (flags&2) {
|
|
|
|
logE("stereo sample!");
|
|
|
|
lastError="stereo sample";
|
|
|
|
delete ins;
|
|
|
|
delete s;
|
|
|
|
delete[] file;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isPacked) {
|
|
|
|
logE("ADPCM not supported!");
|
|
|
|
lastError="ADPCM sample";
|
|
|
|
delete ins;
|
|
|
|
delete s;
|
|
|
|
delete[] file;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
reader.read(s->getCurBuf(),length);
|
|
|
|
|
|
|
|
if (!signedSamples) {
|
|
|
|
if (s->depth==DIV_SAMPLE_DEPTH_16BIT) {
|
|
|
|
for (unsigned int i=0; i<s->samples; i++) {
|
|
|
|
s->data16[i]^=0x8000;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (unsigned int i=0; i<s->samples; i++) {
|
|
|
|
s->data8[i]^=0x80;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ins->amiga.initSample=ds.sample.size();
|
|
|
|
ds.sample.push_back(s);
|
2024-02-05 14:08:53 -05:00
|
|
|
} else {
|
2024-06-22 05:15:11 -04:00
|
|
|
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;
|
|
|
|
|
2024-06-22 16:35:38 -04:00
|
|
|
// unused
|
|
|
|
reader.readC();
|
|
|
|
|
2024-06-22 05:15:11 -04:00
|
|
|
defVol[i]=reader.readC();
|
|
|
|
// what?
|
|
|
|
unsigned char dsk=reader.readC();
|
|
|
|
|
|
|
|
// x
|
|
|
|
reader.readS();
|
|
|
|
|
|
|
|
// oh no, we've got a problem here...
|
|
|
|
// C-2 speed
|
|
|
|
reader.readI();
|
|
|
|
|
|
|
|
// x
|
|
|
|
reader.seek(12,SEEK_CUR);
|
|
|
|
|
|
|
|
String name=reader.readString(28);
|
|
|
|
ins->name=name;
|
|
|
|
|
|
|
|
// "SCRS"
|
|
|
|
reader.readI();
|
|
|
|
|
|
|
|
logV("defVol: %d",defVol[i]);
|
|
|
|
logV("dsk: %d",dsk);
|
2024-02-05 14:08:53 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
ds.ins.push_back(ins);
|
|
|
|
}
|
2024-06-22 03:01:26 -04:00
|
|
|
ds.sampleLen=ds.sample.size();
|
|
|
|
|
2024-06-24 03:44:17 -04:00
|
|
|
// 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;
|
|
|
|
|
|
|
|
while (reader.tell()<dataEnd) {
|
|
|
|
unsigned char what=reader.readC();
|
|
|
|
|
|
|
|
if (what==0) {
|
|
|
|
curRow++;
|
|
|
|
if (curRow>=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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-22 05:15:11 -04:00
|
|
|
// 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];
|
2024-06-22 17:10:49 -04:00
|
|
|
unsigned char arpStatus[32];
|
|
|
|
bool arpStatusChanged[32];
|
|
|
|
bool arping[32];
|
|
|
|
bool arpingOld[32];
|
2024-06-22 05:15:11 -04:00
|
|
|
bool did[32];
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
logV("length: %d",dataLen);
|
|
|
|
|
|
|
|
int curRow=0;
|
2024-06-23 04:45:53 -04:00
|
|
|
bool mustCommitInitial=true;
|
2024-06-22 05:15:11 -04:00
|
|
|
|
|
|
|
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);
|
2024-06-22 17:10:49 -04:00
|
|
|
memset(arpStatus,0,32);
|
|
|
|
memset(arpStatusChanged,0,32*sizeof(bool));
|
|
|
|
memset(arping,0,32*sizeof(bool));
|
|
|
|
memset(arpingOld,0,32*sizeof(bool));
|
2024-06-22 05:15:11 -04:00
|
|
|
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[j].getPattern(i,true);
|
|
|
|
if (vibing[j]!=vibingOld[j] || vibStatusChanged[j]) {
|
|
|
|
p->data[curRow][effectCol[j]++]=0x04;
|
|
|
|
p->data[curRow][effectCol[j]++]=vibing[j]?vibStatus[j]:0;
|
|
|
|
doesVibrato[j]=true;
|
2024-06-23 04:45:53 -04:00
|
|
|
} else if (doesVibrato[j] && mustCommitInitial) {
|
|
|
|
p->data[curRow][effectCol[j]++]=0x04;
|
|
|
|
p->data[curRow][effectCol[j]++]=0;
|
2024-06-22 05:15:11 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (volSliding[j]!=volSlidingOld[j] || volSlideStatusChanged[j]) {
|
2024-06-22 17:10:49 -04:00
|
|
|
if (volSlideStatus[j]>=0xf1 && volSliding[j]) {
|
|
|
|
p->data[curRow][effectCol[j]++]=0xf9;
|
|
|
|
p->data[curRow][effectCol[j]++]=volSlideStatus[j]&15;
|
|
|
|
volSliding[j]=false;
|
|
|
|
} else if ((volSlideStatus[j]&15)==15 && volSlideStatus[j]>=0x10 && volSliding[j]) {
|
|
|
|
p->data[curRow][effectCol[j]++]=0xf8;
|
|
|
|
p->data[curRow][effectCol[j]++]=volSlideStatus[j]>>4;
|
|
|
|
volSliding[j]=false;
|
|
|
|
} else {
|
2024-06-23 04:45:53 -04:00
|
|
|
p->data[curRow][effectCol[j]++]=0xfa;
|
2024-06-22 17:10:49 -04:00
|
|
|
p->data[curRow][effectCol[j]++]=volSliding[j]?volSlideStatus[j]:0;
|
|
|
|
}
|
2024-06-22 05:15:11 -04:00
|
|
|
doesVolSlide[j]=true;
|
2024-06-23 04:45:53 -04:00
|
|
|
} else if (doesVolSlide[j] && mustCommitInitial) {
|
|
|
|
p->data[curRow][effectCol[j]++]=0xfa;
|
|
|
|
p->data[curRow][effectCol[j]++]=0;
|
2024-06-22 05:15:11 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (porting[j]!=portingOld[j] || portaStatusChanged[j]) {
|
2024-06-22 17:10:49 -04:00
|
|
|
if (portaStatus[j]>=0xe0 && portaType[j]!=3 && porting[j]) {
|
|
|
|
p->data[curRow][effectCol[j]++]=portaType[j]|0xf0;
|
|
|
|
p->data[curRow][effectCol[j]++]=(portaStatus[j]&15)*((portaStatus[j]>=0xf0)?4:1);
|
|
|
|
porting[j]=false;
|
|
|
|
} else {
|
|
|
|
p->data[curRow][effectCol[j]++]=portaType[j];
|
|
|
|
p->data[curRow][effectCol[j]++]=porting[j]?portaStatus[j]:0;
|
|
|
|
}
|
|
|
|
doesPitchSlide[j]=true;
|
2024-06-23 04:45:53 -04:00
|
|
|
} else if (doesPitchSlide[j] && mustCommitInitial) {
|
|
|
|
p->data[curRow][effectCol[j]++]=0x01;
|
|
|
|
p->data[curRow][effectCol[j]++]=0;
|
2024-06-22 17:10:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (arping[j]!=arpingOld[j] || arpStatusChanged[j]) {
|
|
|
|
p->data[curRow][effectCol[j]++]=0x00;
|
|
|
|
p->data[curRow][effectCol[j]++]=arping[j]?arpStatus[j]:0;
|
|
|
|
doesArp[j]=true;
|
2024-06-23 04:45:53 -04:00
|
|
|
} else if (doesArp[j] && mustCommitInitial) {
|
|
|
|
p->data[curRow][effectCol[j]++]=0x00;
|
|
|
|
p->data[curRow][effectCol[j]++]=0;
|
2024-06-22 05:15:11 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if ((effectCol[j]>>1)-2>ds.subsong[0]->pat[j].effectCols) {
|
|
|
|
ds.subsong[0]->pat[j].effectCols=(effectCol[j]>>1)-1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
curRow++;
|
|
|
|
memset(effectCol,4,32);
|
|
|
|
memcpy(vibingOld,vibing,32*sizeof(bool));
|
|
|
|
memcpy(volSlidingOld,volSliding,32*sizeof(bool));
|
|
|
|
memcpy(portingOld,porting,32*sizeof(bool));
|
2024-06-22 19:21:31 -04:00
|
|
|
memcpy(arpingOld,arping,32*sizeof(bool));
|
2024-06-22 05:15:11 -04:00
|
|
|
memset(vibStatusChanged,0,32*sizeof(bool));
|
|
|
|
memset(volSlideStatusChanged,0,32*sizeof(bool));
|
|
|
|
memset(portaStatusChanged,0,32*sizeof(bool));
|
2024-06-22 19:21:31 -04:00
|
|
|
memset(arpStatusChanged,0,32*sizeof(bool));
|
2024-06-22 05:15:11 -04:00
|
|
|
memset(vibing,0,32*sizeof(bool));
|
|
|
|
memset(volSliding,0,32*sizeof(bool));
|
|
|
|
memset(porting,0,32*sizeof(bool));
|
2024-06-22 19:21:31 -04:00
|
|
|
memset(arping,0,32*sizeof(bool));
|
2024-06-22 05:15:11 -04:00
|
|
|
memset(did,0,32);
|
2024-06-23 04:45:53 -04:00
|
|
|
mustCommitInitial=false;
|
2024-06-22 05:15:11 -04:00
|
|
|
if (curRow>=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!");
|
|
|
|
} else {
|
|
|
|
did[chan]=true;
|
|
|
|
}
|
|
|
|
|
|
|
|
DivPattern* p=ds.subsong[0]->pat[chan].getPattern(i,true);
|
|
|
|
if (hasNoteIns) {
|
|
|
|
unsigned char note=reader.readC();
|
|
|
|
unsigned char ins=reader.readC();
|
|
|
|
|
|
|
|
if (note==254) { // note off
|
|
|
|
p->data[curRow][0]=100;
|
|
|
|
p->data[curRow][1]=0;
|
|
|
|
} else if (note!=255) {
|
|
|
|
p->data[curRow][0]=note&15;
|
|
|
|
p->data[curRow][1]=note>>4;
|
|
|
|
if ((note&15)==0) {
|
|
|
|
p->data[curRow][0]=12;
|
|
|
|
p->data[curRow][1]--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
p->data[curRow][2]=(short)ins-1;
|
|
|
|
}
|
|
|
|
if (hasVol) {
|
|
|
|
unsigned char vol=reader.readC();
|
|
|
|
if (vol==255) {
|
|
|
|
p->data[curRow][3]=-1;
|
|
|
|
} else {
|
2024-06-22 16:35:38 -04:00
|
|
|
// check for OPL channel
|
|
|
|
if ((chanSettings[chan]&31)>=16) {
|
|
|
|
if (vol>63) vol=63;
|
|
|
|
} else {
|
|
|
|
if (vol>64) vol=64;
|
|
|
|
}
|
2024-06-22 05:15:11 -04:00
|
|
|
p->data[curRow][3]=vol;
|
|
|
|
}
|
|
|
|
} else if (p->data[curRow][2]!=-1) {
|
|
|
|
// populate with instrument volume
|
2024-06-22 16:35:38 -04:00
|
|
|
unsigned char vol=defVol[p->data[curRow][2]&255];
|
|
|
|
if ((chanSettings[chan]&31)>=16) {
|
|
|
|
if (vol>63) vol=63;
|
|
|
|
} else {
|
|
|
|
if (vol>64) vol=64;
|
|
|
|
}
|
|
|
|
p->data[curRow][3]=vol;
|
2024-06-22 05:15:11 -04:00
|
|
|
}
|
|
|
|
if (hasEffect) {
|
|
|
|
unsigned char effect=reader.readC();
|
|
|
|
unsigned char effectVal=reader.readC();
|
|
|
|
|
|
|
|
switch (effect+'A'-1) {
|
|
|
|
case 'A': // speed
|
|
|
|
p->data[curRow][effectCol[chan]++]=0x0f;
|
|
|
|
p->data[curRow][effectCol[chan]++]=effectVal;
|
|
|
|
break;
|
|
|
|
case 'B': // go to order
|
|
|
|
p->data[curRow][effectCol[chan]++]=0x0b;
|
2024-06-24 04:04:02 -04:00
|
|
|
p->data[curRow][effectCol[chan]++]=orders[effectVal];
|
2024-06-22 05:15:11 -04:00
|
|
|
break;
|
|
|
|
case 'C': // next order
|
|
|
|
p->data[curRow][effectCol[chan]++]=0x0d;
|
|
|
|
p->data[curRow][effectCol[chan]++]=effectVal;
|
|
|
|
break;
|
|
|
|
case 'D': // vol slide
|
|
|
|
if (effectVal!=0) {
|
|
|
|
volSlideStatus[chan]=effectVal;
|
|
|
|
volSlideStatusChanged[chan]=true;
|
2024-06-24 03:50:44 -04:00
|
|
|
}
|
|
|
|
if (hasNoteIns) {
|
|
|
|
volSlideStatusChanged[chan]=true;
|
2024-06-22 05:15:11 -04:00
|
|
|
}
|
|
|
|
volSliding[chan]=true;
|
|
|
|
break;
|
|
|
|
case 'E': // pitch down
|
|
|
|
if (effectVal!=0) {
|
|
|
|
portaStatus[chan]=effectVal;
|
|
|
|
portaStatusChanged[chan]=true;
|
|
|
|
}
|
2024-06-22 16:35:38 -04:00
|
|
|
portaType[chan]=2;
|
2024-06-22 05:15:11 -04:00
|
|
|
porting[chan]=true;
|
|
|
|
break;
|
|
|
|
case 'F': // pitch up
|
|
|
|
if (effectVal!=0) {
|
|
|
|
portaStatus[chan]=effectVal;
|
|
|
|
portaStatusChanged[chan]=true;
|
|
|
|
}
|
2024-06-22 16:35:38 -04:00
|
|
|
portaType[chan]=1;
|
2024-06-22 05:15:11 -04:00
|
|
|
porting[chan]=true;
|
|
|
|
break;
|
|
|
|
case 'G': // porta
|
|
|
|
if (effectVal!=0) {
|
|
|
|
portaStatus[chan]=effectVal;
|
|
|
|
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
|
2024-06-22 17:10:49 -04:00
|
|
|
if (effectVal!=0) {
|
|
|
|
arpStatus[chan]=effectVal;
|
|
|
|
arpStatusChanged[chan]=true;
|
|
|
|
}
|
|
|
|
arping[chan]=true;
|
2024-06-22 05:15:11 -04:00
|
|
|
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
|
2024-06-22 16:35:38 -04:00
|
|
|
if (effectVal!=0) {
|
|
|
|
volSlideStatus[chan]=effectVal;
|
|
|
|
volSlideStatusChanged[chan]=true;
|
|
|
|
}
|
|
|
|
volSliding[chan]=true;
|
|
|
|
porting[chan]=true;
|
|
|
|
portaType[chan]=3;
|
2024-06-22 05:15:11 -04:00
|
|
|
break;
|
2024-06-22 17:10:49 -04:00
|
|
|
case 'M': // channel vol (extension)
|
2024-06-22 05:15:11 -04:00
|
|
|
break;
|
2024-06-22 17:10:49 -04:00
|
|
|
case 'N': // channel vol slide (extension)
|
2024-06-22 05:15:11 -04:00
|
|
|
break;
|
|
|
|
case 'O': // offset
|
2024-06-24 04:26:38 -04:00
|
|
|
p->data[curRow][effectCol[chan]++]=0x91;
|
|
|
|
p->data[curRow][effectCol[chan]++]=effectVal;
|
2024-06-22 05:15:11 -04:00
|
|
|
break;
|
2024-06-22 17:10:49 -04:00
|
|
|
case 'P': // pan slide (extension)
|
2024-06-22 05:15:11 -04:00
|
|
|
break;
|
|
|
|
case 'Q': // retrigger
|
2024-06-22 16:35:38 -04:00
|
|
|
p->data[curRow][effectCol[chan]++]=0x0c;
|
|
|
|
p->data[curRow][effectCol[chan]++]=effectVal&15;
|
2024-06-22 05:15:11 -04:00
|
|
|
break;
|
|
|
|
case 'R': // tremolo
|
|
|
|
break;
|
|
|
|
case 'S': // special...
|
|
|
|
break;
|
|
|
|
case 'T': // tempo
|
|
|
|
p->data[curRow][effectCol[chan]++]=0xf0;
|
|
|
|
p->data[curRow][effectCol[chan]++]=effectVal;
|
|
|
|
break;
|
|
|
|
case 'U': // fine vibrato
|
|
|
|
break;
|
|
|
|
case 'V': // global volume (!)
|
|
|
|
break;
|
|
|
|
case 'W': // global volume slide (!)
|
|
|
|
break;
|
2024-06-22 17:10:49 -04:00
|
|
|
case 'X': // panning (extension)
|
2024-06-22 16:35:38 -04:00
|
|
|
p->data[curRow][effectCol[chan]++]=0x80;
|
|
|
|
p->data[curRow][effectCol[chan]++]=effectVal;
|
2024-06-22 05:15:11 -04:00
|
|
|
break;
|
2024-06-22 17:10:49 -04:00
|
|
|
case 'Y': // panbrello (extension)
|
2024-06-22 05:15:11 -04:00
|
|
|
break;
|
2024-06-22 17:10:49 -04:00
|
|
|
case 'Z': // MIDI macro (extension)
|
2024-06-22 05:15:11 -04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-06-24 03:44:17 -04:00
|
|
|
}
|
2024-06-22 05:15:11 -04:00
|
|
|
|
2024-06-24 03:44:17 -04:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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[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)]==-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[j].effectCols<=k) ds.subsong[i]->pat[j].effectCols=k+1;
|
|
|
|
break;
|
|
|
|
}
|
2024-06-22 19:21:31 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-06-22 05:15:11 -04:00
|
|
|
|
2024-02-05 14:08:53 -05:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|