2024-06-22 19:22:03 -04: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-26 21:28:02 -04:00
|
|
|
extern "C" {
|
|
|
|
#include "../../../extern/itcompress/compression.h"
|
|
|
|
}
|
|
|
|
|
2024-06-26 20:31:43 -04:00
|
|
|
static const unsigned char volPortaSlide[10]={
|
|
|
|
0, 1, 4, 8, 16, 32, 64, 96, 128, 255
|
|
|
|
};
|
|
|
|
|
2024-06-25 20:46:53 -04:00
|
|
|
void readEnvelope(SafeReader& reader, DivInstrument* ins, int env) {
|
|
|
|
unsigned char flags=reader.readC();
|
|
|
|
unsigned char numPoints=reader.readC();
|
|
|
|
unsigned char loopStart=reader.readC();
|
|
|
|
unsigned char loopEnd=reader.readC();
|
|
|
|
unsigned char susStart=reader.readC();
|
|
|
|
unsigned char susEnd=reader.readC();
|
|
|
|
|
|
|
|
if (numPoints>25) numPoints=25;
|
|
|
|
|
|
|
|
if (loopStart>=numPoints) loopStart=numPoints-1;
|
|
|
|
if (loopEnd>=numPoints) loopEnd=numPoints-1;
|
|
|
|
if (susStart>=numPoints) susStart=numPoints-1;
|
|
|
|
if (susEnd>=numPoints) susEnd=numPoints-1;
|
|
|
|
|
|
|
|
unsigned short pointTime[25];
|
|
|
|
signed char pointVal[25];
|
|
|
|
|
|
|
|
for (int i=0; i<25; i++) {
|
|
|
|
pointVal[i]=reader.readC();
|
|
|
|
pointTime[i]=reader.readS();
|
|
|
|
}
|
|
|
|
|
2024-06-26 01:25:33 -04:00
|
|
|
// x
|
|
|
|
reader.readC();
|
|
|
|
|
2024-06-25 20:46:53 -04:00
|
|
|
// don't process if there aren't any points or if the envelope is disabled
|
|
|
|
if (numPoints<1) return;
|
|
|
|
if (!(flags&1)) return;
|
|
|
|
|
|
|
|
// convert into macro, or try to
|
|
|
|
DivInstrumentMacro* target=NULL;
|
|
|
|
switch (env) {
|
|
|
|
case 0: // volume
|
|
|
|
target=&ins->std.volMacro;
|
|
|
|
break;
|
|
|
|
case 1: // panning (split later)
|
|
|
|
target=&ins->std.panLMacro;
|
|
|
|
break;
|
|
|
|
case 2: // pitch or cutoff
|
|
|
|
if (flags&128) {
|
|
|
|
target=&ins->std.ex1Macro; // ES5506 filter
|
|
|
|
} else {
|
|
|
|
target=&ins->std.pitchMacro;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
target->len=0;
|
|
|
|
int point=0;
|
|
|
|
bool pointJustBegan=true;
|
|
|
|
// mark loop end as end of envelope
|
|
|
|
if (flags&2) {
|
|
|
|
if (loopEnd<numPoints) numPoints=loopEnd+1;
|
|
|
|
}
|
|
|
|
for (int i=0; i<255; i++) {
|
|
|
|
int curPoint=MIN(point,numPoints-1);
|
|
|
|
int nextPoint=MIN(point+1,numPoints-1);
|
|
|
|
int p0=pointVal[curPoint];
|
|
|
|
int p1=pointVal[nextPoint];
|
|
|
|
while (i>pointTime[nextPoint]) {
|
|
|
|
point++;
|
|
|
|
pointJustBegan=true;
|
|
|
|
curPoint=MIN(point,numPoints-1);
|
|
|
|
nextPoint=MIN(point+1,numPoints-1);
|
|
|
|
p0=pointVal[curPoint];
|
|
|
|
p1=pointVal[nextPoint];
|
|
|
|
if ((point+1)>=numPoints) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (pointJustBegan) {
|
|
|
|
pointJustBegan=false;
|
|
|
|
if (flags&2) { // loop
|
|
|
|
if (point==loopStart && (!(flags&4) || susStart==susEnd || loopStart>=susEnd)) {
|
2024-06-27 03:27:24 -04:00
|
|
|
if (loopStart!=loopEnd) {
|
2024-06-27 03:13:53 -04:00
|
|
|
target->loop=i;
|
|
|
|
}
|
2024-06-25 20:46:53 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (flags&4) { // sustain
|
|
|
|
if (susStart!=susEnd) { // sustain loop
|
|
|
|
if (point==susStart) {
|
|
|
|
target->loop=i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (point==susEnd) {
|
|
|
|
target->rel=i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ((point+1)>=numPoints) {
|
2024-06-26 01:25:33 -04:00
|
|
|
target->len=i;
|
|
|
|
//target->val[i]=p0;
|
2024-06-25 20:46:53 -04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
int timeDiff=pointTime[nextPoint]-pointTime[curPoint];
|
|
|
|
int curTime=i-pointTime[curPoint];
|
|
|
|
if (timeDiff<1) timeDiff=1;
|
|
|
|
if (curTime<0) curTime=0;
|
|
|
|
|
|
|
|
if (env==2) {
|
2024-06-26 01:25:33 -04:00
|
|
|
if (flags&128) {
|
|
|
|
p0+=32;
|
|
|
|
p1+=32;
|
|
|
|
p0*=512;
|
|
|
|
p1*=512;
|
|
|
|
} else {
|
|
|
|
p0*=64;
|
|
|
|
p1*=64;
|
|
|
|
}
|
2024-06-25 20:46:53 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
target->len=i+1;
|
|
|
|
target->val[i]=p0+(((p1-p0)*curTime)/timeDiff);
|
|
|
|
}
|
2024-06-26 01:25:33 -04:00
|
|
|
|
|
|
|
// split L/R
|
|
|
|
if (env==1) {
|
|
|
|
for (int i=0; i<ins->std.panLMacro.len; i++) {
|
|
|
|
int val=ins->std.panLMacro.val[i];
|
|
|
|
if (val==0) {
|
|
|
|
ins->std.panLMacro.val[i]=4095;
|
|
|
|
ins->std.panRMacro.val[i]=4095;
|
|
|
|
} else if (val>0) { // pan right
|
2024-06-26 03:47:03 -04:00
|
|
|
ins->std.panLMacro.val[i]=4095*pow(1.0-((double)val/64.0),0.25);
|
2024-06-26 01:25:33 -04:00
|
|
|
ins->std.panRMacro.val[i]=4095;
|
|
|
|
} else { // pan left
|
|
|
|
ins->std.panLMacro.val[i]=4095;
|
2024-06-26 03:47:03 -04:00
|
|
|
ins->std.panRMacro.val[i]=4095*pow(1.0+((double)val/64.0),0.25);
|
2024-06-26 01:25:33 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
ins->std.panRMacro.len=ins->std.panLMacro.len;
|
|
|
|
ins->std.panRMacro.loop=ins->std.panLMacro.loop;
|
|
|
|
ins->std.panRMacro.rel=ins->std.panLMacro.rel;
|
|
|
|
}
|
2024-06-25 20:46:53 -04:00
|
|
|
}
|
|
|
|
|
2024-06-22 19:22:03 -04:00
|
|
|
bool DivEngine::loadIT(unsigned char* file, size_t len) {
|
|
|
|
struct InvalidHeaderException {};
|
|
|
|
bool success=false;
|
|
|
|
char magic[4];
|
2024-06-23 02:31:03 -04:00
|
|
|
|
|
|
|
unsigned char chanPan[64];
|
|
|
|
unsigned char chanVol[64];
|
|
|
|
|
|
|
|
unsigned char orders[256];
|
|
|
|
|
|
|
|
unsigned int insPtr[256];
|
|
|
|
unsigned int samplePtr[256];
|
|
|
|
unsigned int patPtr[256];
|
2024-06-24 17:35:02 -04:00
|
|
|
|
|
|
|
unsigned short patLen[256];
|
2024-06-25 05:38:15 -04:00
|
|
|
|
2024-06-25 18:10:48 -04:00
|
|
|
unsigned char defVol[256];
|
|
|
|
unsigned char noteMap[256][128];
|
|
|
|
|
2024-06-25 17:35:22 -04:00
|
|
|
bool doesPitchSlide[64];
|
|
|
|
bool doesVibrato[64];
|
|
|
|
bool doesPanning[64];
|
|
|
|
bool doesVolSlide[64];
|
|
|
|
bool doesArp[64];
|
|
|
|
|
|
|
|
memset(doesPitchSlide,0,64*sizeof(bool));
|
|
|
|
memset(doesVibrato,0,64*sizeof(bool));
|
|
|
|
memset(doesPanning,0,64*sizeof(bool));
|
|
|
|
memset(doesVolSlide,0,64*sizeof(bool));
|
|
|
|
memset(doesArp,0,64*sizeof(bool));
|
2024-06-23 02:31:03 -04:00
|
|
|
|
2024-06-22 19:22:03 -04:00
|
|
|
SafeReader reader=SafeReader(file,len);
|
|
|
|
warnings="";
|
|
|
|
|
2024-06-24 17:35:02 -04:00
|
|
|
memset(chanPan,0,64);
|
|
|
|
memset(chanVol,0,64);
|
|
|
|
memset(orders,0,256);
|
|
|
|
memset(patLen,0,256*sizeof(unsigned short));
|
|
|
|
|
2024-06-25 18:10:48 -04:00
|
|
|
memset(defVol,0,256);
|
|
|
|
memset(noteMap,0,256*128);
|
|
|
|
|
2024-06-22 19:22:03 -04:00
|
|
|
try {
|
|
|
|
DivSong ds;
|
2024-06-24 17:35:02 -04:00
|
|
|
ds.version=DIV_VERSION_IT;
|
2024-06-22 19:22:03 -04:00
|
|
|
ds.noSlidesOnFirstTick=true;
|
|
|
|
ds.rowResetsArpPos=true;
|
|
|
|
ds.ignoreJumpAtEnd=false;
|
2024-06-27 03:52:46 -04:00
|
|
|
ds.pitchSlideSpeed=8;
|
2024-06-23 19:53:27 -04:00
|
|
|
|
2024-06-22 19:22:03 -04:00
|
|
|
logV("Impulse Tracker module");
|
|
|
|
|
|
|
|
// load here
|
|
|
|
if (!reader.seek(0,SEEK_SET)) {
|
|
|
|
logE("premature end of file!");
|
|
|
|
lastError="incomplete file";
|
|
|
|
delete[] file;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
reader.read(magic,4);
|
|
|
|
|
|
|
|
if (memcmp(magic,DIV_IT_MAGIC,4)!=0) {
|
|
|
|
logW("the magic isn't complete");
|
|
|
|
throw EndOfFileException(&reader,reader.tell());
|
|
|
|
}
|
|
|
|
|
2024-06-28 17:13:27 -04:00
|
|
|
ds.name=reader.readStringLatin1(26);
|
2024-06-22 19:22:03 -04:00
|
|
|
|
2024-06-23 02:31:03 -04:00
|
|
|
unsigned char hilight1=reader.readC();
|
|
|
|
unsigned char hilight2=reader.readC();
|
|
|
|
logV("highlights: %d %d",hilight1,hilight2);
|
|
|
|
|
|
|
|
unsigned short ordersLen=reader.readS();
|
|
|
|
ds.insLen=reader.readS();
|
|
|
|
ds.sampleLen=reader.readS();
|
|
|
|
unsigned short patCount=reader.readS();
|
|
|
|
unsigned short usedTracker=reader.readS();
|
|
|
|
unsigned short compatTracker=reader.readS();
|
|
|
|
unsigned short flags=reader.readS();
|
|
|
|
unsigned short special=reader.readS();
|
|
|
|
|
2024-06-25 17:35:22 -04:00
|
|
|
if (ds.insLen<0 || ds.insLen>256) {
|
|
|
|
logE("too many instruments!");
|
|
|
|
lastError="too many instruments";
|
|
|
|
delete[] file;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ds.sampleLen<0 || ds.sampleLen>256) {
|
|
|
|
logE("too many samples!");
|
|
|
|
lastError="too many samples";
|
|
|
|
delete[] file;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (patCount>256) {
|
|
|
|
logE("too many patterns!");
|
|
|
|
lastError="too many patterns";
|
|
|
|
delete[] file;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-06-24 03:44:17 -04:00
|
|
|
if (flags&8) {
|
|
|
|
ds.linearPitch=2;
|
|
|
|
} else {
|
|
|
|
ds.linearPitch=0;
|
|
|
|
}
|
|
|
|
|
2024-06-23 02:31:03 -04:00
|
|
|
unsigned char globalVol=reader.readC();
|
|
|
|
unsigned char masterVol=reader.readC();
|
|
|
|
unsigned char initSpeed=reader.readC();
|
|
|
|
unsigned char initTempo=reader.readC();
|
|
|
|
unsigned char panSep=reader.readC();
|
|
|
|
reader.readC(); // PWD - unused
|
|
|
|
|
2024-06-24 17:35:02 -04:00
|
|
|
ds.subsong[0]->hz=(double)initTempo/2.5;
|
|
|
|
ds.subsong[0]->speeds.val[0]=initSpeed;
|
|
|
|
ds.subsong[0]->speeds.len=1;
|
|
|
|
|
2024-06-23 02:31:03 -04:00
|
|
|
logV("used tracker: %x",usedTracker);
|
|
|
|
logV("compatible with: %x",compatTracker);
|
|
|
|
logV("flags: %x",flags);
|
|
|
|
logV("special: %x",special);
|
|
|
|
|
|
|
|
logV("global vol: %d",globalVol);
|
|
|
|
logV("master vol: %d",masterVol);
|
|
|
|
logV("pan sep: %d",panSep);
|
|
|
|
|
|
|
|
unsigned short commentLen=reader.readS();
|
|
|
|
unsigned int commentPtr=reader.readI();
|
|
|
|
|
|
|
|
logV("comment len: %d",commentLen);
|
|
|
|
logV("comment ptr: %x",commentPtr);
|
|
|
|
|
|
|
|
reader.readI(); // reserved
|
|
|
|
|
|
|
|
reader.read(chanPan,64);
|
|
|
|
reader.read(chanVol,64);
|
|
|
|
|
2024-06-24 17:35:02 -04:00
|
|
|
logD("reading orders...");
|
|
|
|
size_t curSubSong=0;
|
2024-07-02 04:39:31 -04:00
|
|
|
int curOrder1=0;
|
2024-06-24 17:35:02 -04:00
|
|
|
ds.subsong[curSubSong]->ordersLen=0;
|
|
|
|
bool subSongIncreased=false;
|
2024-06-23 02:31:03 -04:00
|
|
|
for (int i=0; i<ordersLen; i++) {
|
2024-06-24 17:35:02 -04:00
|
|
|
unsigned char nextOrder=reader.readC();
|
2024-07-02 04:39:31 -04:00
|
|
|
orders[i]=curOrder1;
|
2024-06-24 17:35:02 -04:00
|
|
|
|
|
|
|
// skip +++ order
|
|
|
|
if (nextOrder==254) {
|
|
|
|
logV("- +++");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// next subsong
|
|
|
|
if (nextOrder==255) {
|
|
|
|
logV("- end of song");
|
|
|
|
if (!subSongIncreased) {
|
|
|
|
curSubSong++;
|
|
|
|
subSongIncreased=true;
|
|
|
|
}
|
2024-07-02 04:39:31 -04:00
|
|
|
curOrder1=0;
|
2024-06-24 17:35:02 -04:00
|
|
|
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);
|
2024-06-23 02:31:03 -04:00
|
|
|
for (int j=0; j<DIV_MAX_CHANS; j++) {
|
2024-06-24 17:35:02 -04:00
|
|
|
ds.subsong[curSubSong]->orders.ord[j][ds.subsong[curSubSong]->ordersLen]=nextOrder;
|
2024-06-23 02:31:03 -04:00
|
|
|
}
|
2024-06-24 17:35:02 -04:00
|
|
|
ds.subsong[curSubSong]->ordersLen++;
|
2024-07-02 04:39:31 -04:00
|
|
|
curOrder1++;
|
2024-06-23 02:31:03 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
for (int i=0; i<ds.insLen; i++) {
|
|
|
|
insPtr[i]=reader.readI();
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i=0; i<ds.sampleLen; i++) {
|
|
|
|
samplePtr[i]=reader.readI();
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i=0; i<patCount; i++) {
|
|
|
|
patPtr[i]=reader.readI();
|
|
|
|
}
|
|
|
|
|
|
|
|
// read instruments
|
|
|
|
for (int i=0; i<ds.insLen; i++) {
|
|
|
|
DivInstrument* ins=new DivInstrument;
|
2024-06-24 05:01:08 -04:00
|
|
|
ins->type=DIV_INS_ES5506;
|
|
|
|
|
|
|
|
if (insPtr[i]==0) {
|
|
|
|
ds.ins.push_back(ins);
|
|
|
|
continue;
|
|
|
|
}
|
2024-06-23 02:31:03 -04:00
|
|
|
|
2024-06-23 19:53:27 -04:00
|
|
|
logV("reading instrument %d...",i);
|
|
|
|
if (!reader.seek(insPtr[i],SEEK_SET)) {
|
|
|
|
logE("premature end of file!");
|
|
|
|
lastError="incomplete file";
|
|
|
|
delete ins;
|
|
|
|
delete[] file;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
reader.read(magic,4);
|
|
|
|
|
|
|
|
if (memcmp(magic,"IMPI",4)!=0) {
|
|
|
|
logE("invalid instrument header!");
|
|
|
|
lastError="invalid instrument header";
|
|
|
|
delete ins;
|
|
|
|
delete[] file;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-06-28 17:13:27 -04:00
|
|
|
String dosName=reader.readStringLatin1(12);
|
2024-06-23 19:53:27 -04:00
|
|
|
|
|
|
|
reader.readC(); // 0
|
|
|
|
|
|
|
|
unsigned char initCut=255;
|
|
|
|
unsigned char initRes=255;
|
2024-06-26 20:31:43 -04:00
|
|
|
unsigned char insVol=128;
|
2024-06-27 03:13:53 -04:00
|
|
|
unsigned short fadeOut=256;
|
2024-06-23 19:53:27 -04:00
|
|
|
|
|
|
|
if (compatTracker<0x200) { // old format
|
|
|
|
unsigned char flags=reader.readC();
|
|
|
|
unsigned char volLoopStart=reader.readC();
|
|
|
|
unsigned char volLoopEnd=reader.readC();
|
|
|
|
unsigned char susLoopStart=reader.readC();
|
|
|
|
unsigned char susLoopEnd=reader.readC();
|
|
|
|
|
|
|
|
logV("flags: %x",flags);
|
|
|
|
logV("volLoopStart: %d",volLoopStart);
|
|
|
|
logV("volLoopEnd: %d",volLoopEnd);
|
|
|
|
logV("susLoopStart: %d",susLoopStart);
|
|
|
|
logV("susLoopEnd: %d",susLoopEnd);
|
|
|
|
|
|
|
|
reader.readS(); // x
|
|
|
|
|
2024-06-27 03:13:53 -04:00
|
|
|
fadeOut=reader.readS();
|
2024-06-23 19:53:27 -04:00
|
|
|
|
|
|
|
logV("fadeOut: %d",fadeOut);
|
|
|
|
|
|
|
|
// NNA/duplicate check - not supported by Furnace...
|
|
|
|
reader.readC();
|
|
|
|
reader.readC();
|
|
|
|
|
|
|
|
// version/sample count/x
|
|
|
|
reader.readI();
|
|
|
|
} else { // new format
|
|
|
|
// NNA/duplicate check - not supported by Furnace...
|
|
|
|
reader.readC();
|
|
|
|
reader.readC();
|
|
|
|
reader.readC();
|
|
|
|
|
2024-06-27 03:13:53 -04:00
|
|
|
fadeOut=reader.readS();
|
2024-06-23 19:53:27 -04:00
|
|
|
|
|
|
|
logV("fadeOut: %d",fadeOut);
|
|
|
|
|
|
|
|
reader.readC();
|
|
|
|
reader.readC();
|
|
|
|
|
2024-06-26 20:31:43 -04:00
|
|
|
insVol=reader.readC();
|
2024-06-23 19:53:27 -04:00
|
|
|
unsigned char defPan=reader.readC();
|
|
|
|
|
|
|
|
logV("defPan: %d",defPan);
|
|
|
|
|
|
|
|
// vol/pan randomization
|
|
|
|
reader.readC();
|
|
|
|
reader.readC();
|
|
|
|
|
|
|
|
// version/sample count/x
|
|
|
|
reader.readI();
|
|
|
|
}
|
|
|
|
|
2024-06-28 17:13:27 -04:00
|
|
|
ins->name=reader.readStringLatin1(26);
|
2024-06-23 19:53:27 -04:00
|
|
|
|
|
|
|
if (compatTracker<0x200) { // old format
|
|
|
|
// x
|
|
|
|
reader.readC();
|
|
|
|
reader.readC();
|
|
|
|
reader.readC();
|
|
|
|
reader.readC();
|
|
|
|
reader.readC();
|
|
|
|
reader.readC();
|
|
|
|
} else { // new format
|
|
|
|
// filter params
|
|
|
|
initCut=reader.readC();
|
|
|
|
initRes=reader.readC();
|
|
|
|
|
|
|
|
// MIDI stuff - ignored
|
|
|
|
reader.readI();
|
|
|
|
}
|
|
|
|
|
|
|
|
logV("filter: %d/%d",initCut,initRes);
|
|
|
|
|
|
|
|
// note map
|
|
|
|
ins->amiga.useNoteMap=true;
|
|
|
|
for (int j=0; j<120; j++) {
|
|
|
|
ins->amiga.noteMap[j].freq=(unsigned char)reader.readC();
|
|
|
|
ins->amiga.noteMap[j].map=reader.readC()-1;
|
2024-06-25 18:10:48 -04:00
|
|
|
noteMap[i][j]=ins->amiga.noteMap[j].map;
|
2024-06-23 19:53:27 -04:00
|
|
|
}
|
|
|
|
|
2024-06-25 20:46:53 -04:00
|
|
|
// envelopes...
|
|
|
|
if (compatTracker<0x200) { // old format
|
|
|
|
// TODO
|
|
|
|
} else {
|
|
|
|
readEnvelope(reader,ins,0);
|
|
|
|
readEnvelope(reader,ins,1);
|
|
|
|
readEnvelope(reader,ins,2);
|
|
|
|
}
|
2024-06-23 19:53:27 -04:00
|
|
|
|
2024-06-26 20:31:43 -04:00
|
|
|
// scale envelope to global volume
|
|
|
|
if (insVol>128) insVol=128;
|
|
|
|
if (ins->std.volMacro.len==0) {
|
|
|
|
ins->std.volMacro.len=1;
|
|
|
|
ins->std.volMacro.val[0]=insVol>>1;
|
|
|
|
} else {
|
|
|
|
for (int j=0; j<ins->std.volMacro.len; j++) {
|
|
|
|
ins->std.volMacro.val[i]=(ins->std.volMacro.val[i]*insVol)>>7;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-27 03:13:53 -04:00
|
|
|
// add note fade if there isn't a release point in the volume envelope
|
2024-06-27 03:27:24 -04:00
|
|
|
if (ins->std.volMacro.len>0 && ins->std.volMacro.rel>=ins->std.volMacro.len) {
|
2024-06-27 03:13:53 -04:00
|
|
|
ins->std.volMacro.len--;
|
|
|
|
int initial=ins->std.volMacro.val[ins->std.volMacro.len];
|
|
|
|
ins->std.volMacro.rel=ins->std.volMacro.len;
|
|
|
|
for (int i=1024; ins->std.volMacro.len<255; i-=fadeOut) {
|
|
|
|
ins->std.volMacro.val[ins->std.volMacro.len++]=(initial*i)>>10;
|
|
|
|
if (i<0) break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-27 03:45:41 -04:00
|
|
|
// set filter macro to cutoff value if no filter envelope is defined
|
|
|
|
if (ins->std.ex1Macro.len==0 && initCut&0x80) {
|
|
|
|
if ((initCut&0x7f)==0x7f) {
|
|
|
|
ins->std.ex1Macro.len=1;
|
|
|
|
ins->std.ex1Macro.val[0]=65535;
|
|
|
|
} else {
|
|
|
|
ins->std.ex1Macro.len=1;
|
|
|
|
ins->std.ex1Macro.val[0]=1024+(initCut&0x7f)*400;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ins->std.ex1Macro.len=1;
|
|
|
|
ins->std.ex1Macro.val[0]=65535;
|
|
|
|
}
|
|
|
|
|
2024-06-23 02:31:03 -04:00
|
|
|
ds.ins.push_back(ins);
|
|
|
|
}
|
|
|
|
|
|
|
|
// read samples
|
|
|
|
for (int i=0; i<ds.sampleLen; i++) {
|
|
|
|
DivSample* s=new DivSample;
|
|
|
|
|
2024-06-24 05:01:08 -04:00
|
|
|
if (samplePtr[i]==0) {
|
|
|
|
ds.sample.push_back(s);
|
2024-06-26 05:06:31 -04:00
|
|
|
|
|
|
|
// does the song not use instruments?
|
|
|
|
// create instrument then
|
|
|
|
if (ds.insLen==0) {
|
|
|
|
DivInstrument* ins=new DivInstrument;
|
|
|
|
ins->type=DIV_INS_ES5506;
|
|
|
|
ds.ins.push_back(ins);
|
|
|
|
}
|
2024-06-24 05:01:08 -04:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2024-06-23 19:53:27 -04:00
|
|
|
logV("reading sample %d...",i);
|
|
|
|
if (!reader.seek(samplePtr[i],SEEK_SET)) {
|
|
|
|
logE("premature end of file!");
|
|
|
|
lastError="incomplete file";
|
|
|
|
delete s;
|
|
|
|
delete[] file;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
reader.read(magic,4);
|
|
|
|
|
|
|
|
if (memcmp(magic,"IMPS",4)!=0) {
|
2024-07-01 06:44:28 -04:00
|
|
|
logW("invalid sample header!");
|
2024-06-23 19:53:27 -04:00
|
|
|
lastError="invalid sample header";
|
|
|
|
delete s;
|
|
|
|
delete[] file;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-06-28 17:13:27 -04:00
|
|
|
String dosName=reader.readStringLatin1(12);
|
2024-06-23 19:53:27 -04:00
|
|
|
|
|
|
|
reader.readC(); // 0
|
|
|
|
|
2024-06-26 20:31:43 -04:00
|
|
|
unsigned char sampleVol=reader.readC();
|
2024-06-23 19:53:27 -04:00
|
|
|
unsigned char flags=reader.readC();
|
2024-06-25 18:10:48 -04:00
|
|
|
defVol[i]=reader.readC();
|
2024-06-23 19:53:27 -04:00
|
|
|
|
2024-06-28 17:13:27 -04:00
|
|
|
s->name=reader.readStringLatin1(26);
|
2024-06-23 19:53:27 -04:00
|
|
|
|
|
|
|
unsigned char convert=reader.readC();
|
|
|
|
unsigned char defPan=reader.readC();
|
|
|
|
|
|
|
|
logV("defPan: %d",defPan);
|
|
|
|
|
|
|
|
if (flags&2) {
|
|
|
|
s->depth=DIV_SAMPLE_DEPTH_16BIT;
|
|
|
|
} else {
|
|
|
|
s->depth=DIV_SAMPLE_DEPTH_8BIT;
|
|
|
|
}
|
|
|
|
|
2024-07-01 06:44:28 -04:00
|
|
|
unsigned int sampleLen=reader.readI();
|
|
|
|
|
|
|
|
if (sampleLen>16777216) {
|
|
|
|
logE("abnormal sample size! %x",reader.tell());
|
|
|
|
lastError="bad sample size";
|
|
|
|
delete[] file;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
s->init(sampleLen);
|
2024-06-23 19:53:27 -04:00
|
|
|
s->loopStart=reader.readI();
|
|
|
|
s->loopEnd=reader.readI();
|
|
|
|
s->centerRate=reader.readI()/2;
|
|
|
|
if (flags&16) {
|
|
|
|
s->loop=true;
|
|
|
|
if (flags&64) {
|
|
|
|
s->loopMode=DIV_SAMPLE_LOOP_PINGPONG;
|
|
|
|
} else {
|
|
|
|
s->loopMode=DIV_SAMPLE_LOOP_FORWARD;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (flags&32) {
|
|
|
|
s->loop=true;
|
|
|
|
if (flags&128) {
|
|
|
|
s->loopMode=DIV_SAMPLE_LOOP_PINGPONG;
|
|
|
|
} else {
|
|
|
|
s->loopMode=DIV_SAMPLE_LOOP_FORWARD;
|
|
|
|
}
|
|
|
|
s->loopStart=reader.readI();
|
|
|
|
s->loopEnd=reader.readI();
|
|
|
|
} else {
|
|
|
|
reader.readI();
|
|
|
|
reader.readI();
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int dataPtr=reader.readI();
|
|
|
|
|
|
|
|
unsigned char vibSpeed=reader.readC();
|
|
|
|
unsigned char vibDepth=reader.readC();
|
|
|
|
unsigned char vibRate=reader.readC();
|
|
|
|
unsigned char vibShape=reader.readC();
|
|
|
|
|
|
|
|
logV("vibrato: %d %d %d %d",vibSpeed,vibDepth,vibRate,vibShape);
|
|
|
|
|
|
|
|
if (s->samples>0) {
|
|
|
|
logD("seek to %x...",dataPtr);
|
|
|
|
if (!reader.seek(dataPtr,SEEK_SET)) {
|
|
|
|
logE("premature end of file!");
|
|
|
|
lastError="incomplete file";
|
|
|
|
delete s;
|
|
|
|
delete[] file;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
logD("seek not needed...");
|
|
|
|
}
|
|
|
|
|
2024-06-26 21:28:02 -04:00
|
|
|
if (flags&8) { // compressed sample
|
|
|
|
unsigned int ret=0;
|
|
|
|
logV("decompression begin...");
|
|
|
|
if (s->depth==DIV_SAMPLE_DEPTH_16BIT) {
|
2024-06-27 03:52:46 -04:00
|
|
|
ret=it_decompress16(s->data16,s->samples,&file[reader.tell()],len-reader.tell(),(convert&4)?1:0,(flags&4)?2:1);
|
2024-06-26 21:28:02 -04:00
|
|
|
} else {
|
2024-06-27 03:52:46 -04:00
|
|
|
ret=it_decompress8(s->data8,s->samples,&file[reader.tell()],len-reader.tell(),(convert&4)?1:0,(flags&4)?2:1);
|
2024-06-26 21:28:02 -04:00
|
|
|
}
|
|
|
|
logV("got: %d",ret);
|
|
|
|
} else {
|
|
|
|
if (s->depth==DIV_SAMPLE_DEPTH_16BIT) {
|
|
|
|
if (flags&4) { // downmix stereo
|
|
|
|
for (unsigned int i=0; i<s->samples; i++) {
|
|
|
|
short l;
|
|
|
|
if (convert&2) {
|
|
|
|
l=reader.readS_BE();
|
|
|
|
} else {
|
|
|
|
l=reader.readS();
|
|
|
|
}
|
|
|
|
if (!(convert&1)) {
|
|
|
|
l^=0x8000;
|
|
|
|
}
|
|
|
|
s->data16[i]=l;
|
2024-06-24 17:43:51 -04:00
|
|
|
}
|
2024-06-26 21:28:02 -04:00
|
|
|
for (unsigned int i=0; i<s->samples; i++) {
|
|
|
|
short r;
|
|
|
|
if (convert&2) {
|
|
|
|
r=reader.readS_BE();
|
|
|
|
} else {
|
|
|
|
r=reader.readS();
|
|
|
|
}
|
|
|
|
if (!(convert&1)) {
|
|
|
|
r^=0x8000;
|
|
|
|
}
|
|
|
|
s->data16[i]=(s->data16[i]+r)>>1;
|
2024-06-24 17:43:51 -04:00
|
|
|
}
|
2024-06-26 21:28:02 -04:00
|
|
|
} else {
|
|
|
|
for (unsigned int i=0; i<s->samples; i++) {
|
|
|
|
if (convert&2) {
|
|
|
|
s->data16[i]=reader.readS_BE()^((convert&1)?0:0x8000);
|
|
|
|
} else {
|
|
|
|
s->data16[i]=reader.readS()^((convert&1)?0:0x8000);
|
|
|
|
}
|
2024-06-23 19:53:27 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2024-06-26 21:28:02 -04:00
|
|
|
if (flags&4) { // downmix stereo
|
|
|
|
for (unsigned int i=0; i<s->samples; i++) {
|
|
|
|
signed char l=reader.readC();
|
|
|
|
if (!(convert&1)) {
|
|
|
|
l^=0x80;
|
|
|
|
}
|
|
|
|
s->data8[i]=l;
|
2024-06-23 19:53:27 -04:00
|
|
|
}
|
2024-06-26 21:28:02 -04:00
|
|
|
for (unsigned int i=0; i<s->samples; i++) {
|
|
|
|
signed char r=reader.readC();
|
|
|
|
if (!(convert&1)) {
|
|
|
|
r^=0x80;
|
|
|
|
}
|
|
|
|
s->data8[i]=(s->data8[i]+r)>>1;
|
2024-06-24 17:43:51 -04:00
|
|
|
}
|
2024-06-26 21:28:02 -04:00
|
|
|
} else {
|
|
|
|
for (unsigned int i=0; i<s->samples; i++) {
|
|
|
|
s->data8[i]=reader.readC()^((convert&1)?0:0x80);
|
2024-06-23 19:53:27 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-26 20:31:43 -04:00
|
|
|
// scale sample if necessary
|
2024-06-26 21:28:02 -04:00
|
|
|
if (s->samples>0) {
|
|
|
|
if (sampleVol>64) sampleVol=64;
|
|
|
|
if (sampleVol<64) {
|
|
|
|
// convert to 16-bit
|
|
|
|
if (s->depth==DIV_SAMPLE_DEPTH_8BIT) {
|
|
|
|
s->convert(DIV_SAMPLE_DEPTH_16BIT,0);
|
|
|
|
}
|
2024-06-26 20:31:43 -04:00
|
|
|
|
2024-06-26 21:28:02 -04:00
|
|
|
// then scale
|
|
|
|
for (unsigned int i=0; i<s->samples; i++) {
|
|
|
|
s->data16[i]=(s->data16[i]*sampleVol)>>6;
|
|
|
|
}
|
2024-06-26 20:31:43 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-26 05:06:31 -04:00
|
|
|
// does the song not use instruments?
|
|
|
|
// create instrument then
|
|
|
|
if (ds.insLen==0) {
|
|
|
|
DivInstrument* ins=new DivInstrument;
|
|
|
|
ins->name=s->name;
|
|
|
|
ins->type=DIV_INS_ES5506;
|
|
|
|
ins->amiga.initSample=i;
|
|
|
|
ds.ins.push_back(ins);
|
|
|
|
}
|
|
|
|
|
2024-06-23 02:31:03 -04:00
|
|
|
ds.sample.push_back(s);
|
|
|
|
}
|
|
|
|
|
2024-06-27 03:13:53 -04:00
|
|
|
// scan pattern data for effect use
|
2024-06-24 03:44:17 -04:00
|
|
|
int maxChan=0;
|
2024-06-27 03:13:53 -04:00
|
|
|
for (int i=0; i<patCount; i++) {
|
|
|
|
if (patPtr[i]==0) continue;
|
|
|
|
|
|
|
|
unsigned char mask[64];
|
|
|
|
unsigned char note[64];
|
|
|
|
unsigned char ins[64];
|
|
|
|
unsigned char vol[64];
|
|
|
|
unsigned char effect[64];
|
|
|
|
unsigned char effectVal[64];
|
|
|
|
int curRow=0;
|
|
|
|
|
|
|
|
memset(mask,0,64);
|
|
|
|
memset(note,0,64);
|
|
|
|
memset(ins,0,64);
|
|
|
|
memset(vol,0,64);
|
|
|
|
memset(effect,0,64);
|
|
|
|
memset(effectVal,0,64);
|
|
|
|
|
|
|
|
logV("scanning pattern %d...",i);
|
|
|
|
if (!reader.seek(patPtr[i],SEEK_SET)) {
|
|
|
|
logE("premature end of file!");
|
|
|
|
lastError="incomplete file";
|
|
|
|
delete[] file;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int dataLen=(unsigned short)reader.readS();
|
|
|
|
unsigned short patRows=reader.readS();
|
|
|
|
|
|
|
|
patLen[i]=patRows;
|
|
|
|
|
|
|
|
if (patRows>DIV_MAX_ROWS) {
|
|
|
|
logE("too many rows! %d",patRows);
|
|
|
|
lastError="too many rows";
|
|
|
|
delete[] file;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
reader.readI(); // x
|
|
|
|
|
|
|
|
dataLen+=reader.tell();
|
|
|
|
|
|
|
|
// read pattern data
|
|
|
|
while (reader.tell()<dataLen) {
|
|
|
|
unsigned char chan=reader.readC();
|
|
|
|
bool hasVol=false;
|
|
|
|
bool hasEffect=false;
|
|
|
|
|
|
|
|
if (chan==0) {
|
|
|
|
curRow++;
|
|
|
|
if (curRow>=patRows) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (chan&128) {
|
2024-06-27 03:45:41 -04:00
|
|
|
mask[(chan-1)&63]=reader.readC();
|
2024-06-27 03:13:53 -04:00
|
|
|
}
|
2024-06-27 03:45:41 -04:00
|
|
|
chan=(chan-1)&63;
|
2024-06-27 03:13:53 -04:00
|
|
|
|
|
|
|
if (chan>maxChan) maxChan=chan;
|
|
|
|
|
|
|
|
if (mask[chan]&1) {
|
|
|
|
note[chan]=reader.readC();
|
|
|
|
}
|
|
|
|
if (mask[chan]&2) {
|
|
|
|
ins[chan]=reader.readC();
|
|
|
|
}
|
|
|
|
if (mask[chan]&4) {
|
|
|
|
vol[chan]=reader.readC();
|
|
|
|
hasVol=true;
|
|
|
|
}
|
|
|
|
if (mask[chan]&8) {
|
|
|
|
effect[chan]=reader.readC();
|
|
|
|
effectVal[chan]=reader.readC();
|
|
|
|
hasEffect=true;
|
|
|
|
}
|
|
|
|
if (mask[chan]&64) {
|
|
|
|
hasVol=true;
|
|
|
|
}
|
|
|
|
if (mask[chan]&128) {
|
|
|
|
hasEffect=true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hasVol) {
|
|
|
|
if (vol[chan]>64) {
|
|
|
|
if (vol[chan]>=65 && vol[chan]<=74) { // fine vol up
|
|
|
|
doesVolSlide[chan]=true;
|
|
|
|
} else if (vol[chan]>=75 && vol[chan]<=84) { // fine vol down
|
|
|
|
doesVolSlide[chan]=true;
|
|
|
|
} else if (vol[chan]>=85 && vol[chan]<=94) { // vol slide up
|
|
|
|
doesVolSlide[chan]=true;
|
|
|
|
} else if (vol[chan]>=95 && vol[chan]<=104) { // vol slide down
|
|
|
|
doesVolSlide[chan]=true;
|
|
|
|
} else if (vol[chan]>=105 && vol[chan]<=114) { // pitch down
|
|
|
|
doesPitchSlide[chan]=true;
|
|
|
|
} else if (vol[chan]>=115 && vol[chan]<=124) { // pitch up
|
|
|
|
doesPitchSlide[chan]=true;
|
|
|
|
} else if (vol[chan]>=193 && vol[chan]<=202) { // porta
|
|
|
|
doesPitchSlide[chan]=true;
|
|
|
|
} else if (vol[chan]>=203 && vol[chan]<=212) { // vibrato
|
|
|
|
doesVibrato[chan]=true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (hasEffect) {
|
|
|
|
switch (effect[chan]+'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 'U': // fine vibrato
|
|
|
|
doesVibrato[chan]=true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// read patterns
|
2024-06-23 02:31:03 -04:00
|
|
|
for (int i=0; i<patCount; i++) {
|
2024-06-25 17:35:22 -04:00
|
|
|
unsigned char effectCol[64];
|
|
|
|
unsigned char vibStatus[64];
|
|
|
|
bool vibStatusChanged[64];
|
|
|
|
bool vibing[64];
|
|
|
|
bool vibingOld[64];
|
|
|
|
unsigned char volSlideStatus[64];
|
|
|
|
bool volSlideStatusChanged[64];
|
|
|
|
bool volSliding[64];
|
|
|
|
bool volSlidingOld[64];
|
|
|
|
unsigned char portaStatus[64];
|
|
|
|
bool portaStatusChanged[64];
|
|
|
|
bool porting[64];
|
|
|
|
bool portingOld[64];
|
|
|
|
unsigned char portaType[64];
|
|
|
|
unsigned char arpStatus[64];
|
|
|
|
bool arpStatusChanged[64];
|
|
|
|
bool arping[64];
|
|
|
|
bool arpingOld[64];
|
|
|
|
bool did[64];
|
2024-06-25 05:38:15 -04:00
|
|
|
|
2024-06-24 05:01:08 -04:00
|
|
|
if (patPtr[i]==0) continue;
|
|
|
|
|
2024-06-23 02:31:03 -04:00
|
|
|
unsigned char mask[64];
|
|
|
|
unsigned char note[64];
|
|
|
|
unsigned char ins[64];
|
|
|
|
unsigned char vol[64];
|
|
|
|
unsigned char effect[64];
|
|
|
|
unsigned char effectVal[64];
|
|
|
|
int curRow=0;
|
2024-06-25 05:38:15 -04:00
|
|
|
bool mustCommitInitial=true;
|
|
|
|
|
2024-06-25 17:35:22 -04:00
|
|
|
memset(effectCol,4,64);
|
|
|
|
memset(vibStatus,0,64);
|
|
|
|
memset(vibStatusChanged,0,64*sizeof(bool));
|
|
|
|
memset(vibing,0,64*sizeof(bool));
|
|
|
|
memset(vibingOld,0,64*sizeof(bool));
|
|
|
|
memset(volSlideStatus,0,64);
|
|
|
|
memset(volSlideStatusChanged,0,64*sizeof(bool));
|
|
|
|
memset(volSliding,0,64*sizeof(bool));
|
|
|
|
memset(volSlidingOld,0,64*sizeof(bool));
|
|
|
|
memset(portaStatus,0,64);
|
|
|
|
memset(portaStatusChanged,0,64*sizeof(bool));
|
|
|
|
memset(porting,0,64*sizeof(bool));
|
|
|
|
memset(portingOld,0,64*sizeof(bool));
|
|
|
|
memset(portaType,0,64);
|
|
|
|
memset(arpStatus,0,64);
|
|
|
|
memset(arpStatusChanged,0,64*sizeof(bool));
|
|
|
|
memset(arping,0,64*sizeof(bool));
|
|
|
|
memset(arpingOld,0,64*sizeof(bool));
|
|
|
|
memset(did,0,64*sizeof(bool));
|
2024-06-23 02:31:03 -04:00
|
|
|
|
|
|
|
memset(mask,0,64);
|
|
|
|
memset(note,0,64);
|
|
|
|
memset(ins,0,64);
|
|
|
|
memset(vol,0,64);
|
|
|
|
memset(effect,0,64);
|
|
|
|
memset(effectVal,0,64);
|
|
|
|
|
|
|
|
logV("reading pattern %d...",i);
|
|
|
|
if (!reader.seek(patPtr[i],SEEK_SET)) {
|
|
|
|
logE("premature end of file!");
|
|
|
|
lastError="incomplete file";
|
|
|
|
delete[] file;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int dataLen=(unsigned short)reader.readS();
|
|
|
|
unsigned short patRows=reader.readS();
|
|
|
|
|
2024-06-24 17:35:02 -04:00
|
|
|
patLen[i]=patRows;
|
|
|
|
|
2024-06-24 05:01:08 -04:00
|
|
|
if (patRows>DIV_MAX_ROWS) {
|
|
|
|
logE("too many rows! %d",patRows);
|
|
|
|
lastError="too many rows";
|
|
|
|
delete[] file;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-06-23 02:31:03 -04:00
|
|
|
reader.readI(); // x
|
|
|
|
|
|
|
|
dataLen+=reader.tell();
|
|
|
|
|
|
|
|
// read pattern data
|
|
|
|
while (reader.tell()<dataLen) {
|
|
|
|
unsigned char chan=reader.readC();
|
|
|
|
bool hasNote=false;
|
|
|
|
bool hasIns=false;
|
|
|
|
bool hasVol=false;
|
|
|
|
bool hasEffect=false;
|
|
|
|
|
|
|
|
if (chan==0) {
|
2024-06-25 05:38:15 -04:00
|
|
|
// commit effects
|
|
|
|
for (int j=0; j<64; 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;
|
|
|
|
} else if (doesVibrato[j] && mustCommitInitial) {
|
|
|
|
p->data[curRow][effectCol[j]++]=0x04;
|
|
|
|
p->data[curRow][effectCol[j]++]=0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (volSliding[j]!=volSlidingOld[j] || volSlideStatusChanged[j]) {
|
|
|
|
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 {
|
|
|
|
p->data[curRow][effectCol[j]++]=0xfa;
|
|
|
|
p->data[curRow][effectCol[j]++]=volSliding[j]?volSlideStatus[j]:0;
|
|
|
|
}
|
|
|
|
doesVolSlide[j]=true;
|
|
|
|
} else if (doesVolSlide[j] && mustCommitInitial) {
|
|
|
|
p->data[curRow][effectCol[j]++]=0xfa;
|
|
|
|
p->data[curRow][effectCol[j]++]=0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (porting[j]!=portingOld[j] || portaStatusChanged[j]) {
|
|
|
|
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)?1: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;
|
|
|
|
} else if (doesPitchSlide[j] && mustCommitInitial) {
|
|
|
|
p->data[curRow][effectCol[j]++]=0x01;
|
|
|
|
p->data[curRow][effectCol[j]++]=0;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
} else if (doesArp[j] && mustCommitInitial) {
|
|
|
|
p->data[curRow][effectCol[j]++]=0x00;
|
|
|
|
p->data[curRow][effectCol[j]++]=0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((effectCol[j]>>1)-2>ds.subsong[0]->pat[j].effectCols) {
|
|
|
|
ds.subsong[0]->pat[j].effectCols=(effectCol[j]>>1)-1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-23 02:31:03 -04:00
|
|
|
curRow++;
|
2024-06-25 17:35:22 -04:00
|
|
|
memset(effectCol,4,64);
|
|
|
|
memcpy(vibingOld,vibing,64*sizeof(bool));
|
|
|
|
memcpy(volSlidingOld,volSliding,64*sizeof(bool));
|
|
|
|
memcpy(portingOld,porting,64*sizeof(bool));
|
|
|
|
memcpy(arpingOld,arping,64*sizeof(bool));
|
|
|
|
memset(vibStatusChanged,0,64*sizeof(bool));
|
|
|
|
memset(volSlideStatusChanged,0,64*sizeof(bool));
|
|
|
|
memset(portaStatusChanged,0,64*sizeof(bool));
|
|
|
|
memset(arpStatusChanged,0,64*sizeof(bool));
|
|
|
|
memset(vibing,0,64*sizeof(bool));
|
|
|
|
memset(volSliding,0,64*sizeof(bool));
|
|
|
|
memset(porting,0,64*sizeof(bool));
|
|
|
|
memset(arping,0,64*sizeof(bool));
|
|
|
|
memset(did,0,64);
|
2024-06-25 05:38:15 -04:00
|
|
|
mustCommitInitial=false;
|
2024-06-25 17:35:22 -04:00
|
|
|
if (curRow>=patRows) {
|
|
|
|
if (curRow>0) {
|
|
|
|
// place end of pattern marker
|
|
|
|
DivPattern* p=ds.subsong[0]->pat[0].getPattern(i,true);
|
|
|
|
p->data[curRow-1][effectCol[0]++]=0x0d;
|
|
|
|
p->data[curRow-1][effectCol[0]++]=0;
|
|
|
|
|
|
|
|
if ((effectCol[0]>>1)-2>ds.subsong[0]->pat[0].effectCols) {
|
|
|
|
ds.subsong[0]->pat[0].effectCols=(effectCol[0]>>1)-1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2024-06-23 02:31:03 -04:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (chan&128) {
|
2024-06-27 03:45:41 -04:00
|
|
|
mask[(chan-1)&63]=reader.readC();
|
2024-06-23 02:31:03 -04:00
|
|
|
}
|
2024-06-27 03:45:41 -04:00
|
|
|
chan=(chan-1)&63;
|
2024-06-24 03:44:17 -04:00
|
|
|
|
2024-06-23 02:31:03 -04:00
|
|
|
if (mask[chan]&1) {
|
|
|
|
note[chan]=reader.readC();
|
|
|
|
hasNote=true;
|
|
|
|
}
|
|
|
|
if (mask[chan]&2) {
|
|
|
|
ins[chan]=reader.readC();
|
|
|
|
hasIns=true;
|
|
|
|
}
|
|
|
|
if (mask[chan]&4) {
|
|
|
|
vol[chan]=reader.readC();
|
|
|
|
hasVol=true;
|
|
|
|
}
|
|
|
|
if (mask[chan]&8) {
|
|
|
|
effect[chan]=reader.readC();
|
|
|
|
effectVal[chan]=reader.readC();
|
|
|
|
hasEffect=true;
|
|
|
|
}
|
|
|
|
if (mask[chan]&16) {
|
|
|
|
hasNote=true;
|
|
|
|
}
|
|
|
|
if (mask[chan]&32) {
|
|
|
|
hasIns=true;
|
|
|
|
}
|
|
|
|
if (mask[chan]&64) {
|
|
|
|
hasVol=true;
|
|
|
|
}
|
|
|
|
if (mask[chan]&128) {
|
|
|
|
hasEffect=true;
|
|
|
|
}
|
|
|
|
|
|
|
|
DivPattern* p=ds.subsong[0]->pat[chan].getPattern(i,true);
|
|
|
|
|
|
|
|
if (hasNote) {
|
2024-06-25 17:35:22 -04:00
|
|
|
if (note[chan]==255) { // note release
|
2024-06-23 02:31:03 -04:00
|
|
|
p->data[curRow][0]=101;
|
|
|
|
p->data[curRow][1]=0;
|
2024-06-25 17:35:22 -04:00
|
|
|
} else if (note[chan]==254) { // note off
|
|
|
|
p->data[curRow][0]=100;
|
|
|
|
p->data[curRow][1]=0;
|
2024-06-23 02:31:03 -04:00
|
|
|
} else if (note[chan]<120) {
|
|
|
|
p->data[curRow][0]=note[chan]%12;
|
|
|
|
p->data[curRow][1]=note[chan]/12;
|
|
|
|
if (p->data[curRow][0]==0) {
|
|
|
|
p->data[curRow][0]=12;
|
|
|
|
p->data[curRow][1]--;
|
|
|
|
}
|
2024-06-26 01:25:33 -04:00
|
|
|
} else { // note fade, but Furnace does not support that
|
2024-06-25 17:35:22 -04:00
|
|
|
p->data[curRow][0]=102;
|
|
|
|
p->data[curRow][1]=0;
|
2024-06-23 02:31:03 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (hasIns) {
|
|
|
|
p->data[curRow][2]=ins[chan]-1;
|
|
|
|
}
|
|
|
|
if (hasVol) {
|
2024-06-26 20:31:43 -04:00
|
|
|
if (vol[chan]<=64) {
|
|
|
|
p->data[curRow][3]=vol[chan];
|
|
|
|
} else { // effects in volume column
|
|
|
|
if (vol[chan]>=128 && vol[chan]<=192) { // panning
|
|
|
|
p->data[curRow][effectCol[chan]++]=0x80;
|
|
|
|
p->data[curRow][effectCol[chan]++]=CLAMP((vol[chan]-128)<<2,0,255);
|
|
|
|
} else if (vol[chan]>=65 && vol[chan]<=74) { // fine vol up
|
2024-06-26 21:31:42 -04:00
|
|
|
} else if (vol[chan]>=75 && vol[chan]<=84) { // fine vol down
|
2024-06-26 20:31:43 -04:00
|
|
|
} else if (vol[chan]>=85 && vol[chan]<=94) { // vol slide up
|
|
|
|
} else if (vol[chan]>=95 && vol[chan]<=104) { // vol slide down
|
|
|
|
} else if (vol[chan]>=105 && vol[chan]<=114) { // pitch down
|
|
|
|
} else if (vol[chan]>=115 && vol[chan]<=124) { // pitch up
|
|
|
|
} else if (vol[chan]>=193 && vol[chan]<=202) { // porta
|
|
|
|
unsigned char portaVal=volPortaSlide[vol[chan]-193];
|
|
|
|
if (portaVal!=0) {
|
|
|
|
portaStatus[chan]=portaVal;
|
|
|
|
portaStatusChanged[chan]=true;
|
|
|
|
}
|
2024-07-04 03:42:36 -04:00
|
|
|
if (portaType[chan]!=3) {
|
|
|
|
portaStatusChanged[chan]=true;
|
|
|
|
}
|
2024-06-26 20:31:43 -04:00
|
|
|
portaType[chan]=3;
|
|
|
|
porting[chan]=true;
|
|
|
|
} else if (vol[chan]>=203 && vol[chan]<=212) { // vibrato
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (hasNote && hasIns && (note[chan]<120 || ds.insLen==0) && ins[chan]>0) {
|
|
|
|
if (ds.insLen==0) {
|
|
|
|
p->data[curRow][3]=defVol[(ins[chan]-1)&255];
|
|
|
|
} else {
|
|
|
|
p->data[curRow][3]=defVol[noteMap[(ins[chan]-1)&255][note[chan]]];
|
|
|
|
}
|
2024-06-23 02:31:03 -04:00
|
|
|
}
|
|
|
|
if (hasEffect) {
|
2024-06-25 05:38:15 -04:00
|
|
|
switch (effect[chan]+'A'-1) {
|
|
|
|
case 'A': // speed
|
|
|
|
p->data[curRow][effectCol[chan]++]=0x0f;
|
|
|
|
p->data[curRow][effectCol[chan]++]=effectVal[chan];
|
|
|
|
break;
|
|
|
|
case 'B': // go to order
|
|
|
|
p->data[curRow][effectCol[chan]++]=0x0b;
|
|
|
|
p->data[curRow][effectCol[chan]++]=orders[effectVal[chan]];
|
|
|
|
break;
|
|
|
|
case 'C': // next order
|
|
|
|
p->data[curRow][effectCol[chan]++]=0x0d;
|
|
|
|
p->data[curRow][effectCol[chan]++]=effectVal[chan];
|
|
|
|
break;
|
|
|
|
case 'D': // vol slide
|
|
|
|
if (effectVal[chan]!=0) {
|
|
|
|
volSlideStatus[chan]=effectVal[chan];
|
|
|
|
volSlideStatusChanged[chan]=true;
|
|
|
|
}
|
|
|
|
if (hasIns) {
|
|
|
|
volSlideStatusChanged[chan]=true;
|
|
|
|
}
|
|
|
|
volSliding[chan]=true;
|
|
|
|
break;
|
|
|
|
case 'E': // pitch down
|
|
|
|
if (effectVal[chan]!=0) {
|
|
|
|
portaStatus[chan]=effectVal[chan];
|
|
|
|
portaStatusChanged[chan]=true;
|
|
|
|
}
|
2024-07-04 03:42:36 -04:00
|
|
|
if (portaType[chan]!=2) {
|
|
|
|
portaStatusChanged[chan]=true;
|
|
|
|
}
|
2024-06-25 05:38:15 -04:00
|
|
|
portaType[chan]=2;
|
|
|
|
porting[chan]=true;
|
|
|
|
break;
|
|
|
|
case 'F': // pitch up
|
|
|
|
if (effectVal[chan]!=0) {
|
|
|
|
portaStatus[chan]=effectVal[chan];
|
|
|
|
portaStatusChanged[chan]=true;
|
|
|
|
}
|
2024-07-04 03:42:36 -04:00
|
|
|
if (portaType[chan]!=1) {
|
|
|
|
portaStatusChanged[chan]=true;
|
|
|
|
}
|
2024-06-25 05:38:15 -04:00
|
|
|
portaType[chan]=1;
|
|
|
|
porting[chan]=true;
|
|
|
|
break;
|
|
|
|
case 'G': // porta
|
|
|
|
if (effectVal[chan]!=0) {
|
|
|
|
portaStatus[chan]=effectVal[chan];
|
|
|
|
portaStatusChanged[chan]=true;
|
|
|
|
}
|
2024-07-04 03:42:36 -04:00
|
|
|
if (portaType[chan]!=3) {
|
|
|
|
portaStatusChanged[chan]=true;
|
|
|
|
}
|
2024-06-25 05:38:15 -04:00
|
|
|
portaType[chan]=3;
|
|
|
|
porting[chan]=true;
|
|
|
|
break;
|
|
|
|
case 'H': // vibrato
|
|
|
|
if (effectVal[chan]!=0) {
|
|
|
|
vibStatus[chan]=effectVal[chan];
|
|
|
|
vibStatusChanged[chan]=true;
|
|
|
|
}
|
|
|
|
vibing[chan]=true;
|
|
|
|
break;
|
|
|
|
case 'I': // tremor (!)
|
|
|
|
break;
|
|
|
|
case 'J': // arp
|
|
|
|
if (effectVal[chan]!=0) {
|
|
|
|
arpStatus[chan]=effectVal[chan];
|
|
|
|
arpStatusChanged[chan]=true;
|
|
|
|
}
|
|
|
|
arping[chan]=true;
|
|
|
|
break;
|
|
|
|
case 'K': // vol slide + vibrato
|
|
|
|
if (effectVal[chan]!=0) {
|
|
|
|
volSlideStatus[chan]=effectVal[chan];
|
|
|
|
volSlideStatusChanged[chan]=true;
|
|
|
|
}
|
|
|
|
volSliding[chan]=true;
|
|
|
|
vibing[chan]=true;
|
|
|
|
break;
|
|
|
|
case 'L': // vol slide + porta
|
|
|
|
if (effectVal[chan]!=0) {
|
|
|
|
volSlideStatus[chan]=effectVal[chan];
|
|
|
|
volSlideStatusChanged[chan]=true;
|
|
|
|
}
|
|
|
|
volSliding[chan]=true;
|
|
|
|
porting[chan]=true;
|
|
|
|
portaType[chan]=3;
|
|
|
|
break;
|
2024-06-26 20:31:43 -04:00
|
|
|
case 'M': // channel vol
|
2024-06-25 05:38:15 -04:00
|
|
|
break;
|
2024-06-26 20:31:43 -04:00
|
|
|
case 'N': // channel vol slide
|
2024-06-25 05:38:15 -04:00
|
|
|
break;
|
|
|
|
case 'O': // offset
|
|
|
|
p->data[curRow][effectCol[chan]++]=0x91;
|
|
|
|
p->data[curRow][effectCol[chan]++]=effectVal[chan];
|
|
|
|
break;
|
2024-06-26 20:31:43 -04:00
|
|
|
case 'P': // pan slide
|
2024-06-25 05:38:15 -04:00
|
|
|
break;
|
|
|
|
case 'Q': // retrigger
|
|
|
|
p->data[curRow][effectCol[chan]++]=0x0c;
|
|
|
|
p->data[curRow][effectCol[chan]++]=effectVal[chan]&15;
|
|
|
|
break;
|
|
|
|
case 'R': // tremolo
|
|
|
|
break;
|
|
|
|
case 'S': // special...
|
|
|
|
switch (effectVal[chan]>>4) {
|
|
|
|
case 0xc:
|
|
|
|
p->data[curRow][effectCol[chan]++]=0xec;
|
|
|
|
p->data[curRow][effectCol[chan]++]=effectVal[chan]&15;
|
|
|
|
break;
|
|
|
|
case 0xd:
|
|
|
|
p->data[curRow][effectCol[chan]++]=0xed;
|
|
|
|
p->data[curRow][effectCol[chan]++]=effectVal[chan]&15;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'T': // tempo
|
|
|
|
p->data[curRow][effectCol[chan]++]=0xf0;
|
|
|
|
p->data[curRow][effectCol[chan]++]=effectVal[chan];
|
|
|
|
break;
|
|
|
|
case 'U': // fine vibrato
|
|
|
|
if (effectVal[chan]!=0) {
|
|
|
|
vibStatus[chan]=effectVal[chan];
|
|
|
|
vibStatusChanged[chan]=true;
|
|
|
|
}
|
|
|
|
vibing[chan]=true;
|
|
|
|
break;
|
|
|
|
case 'V': // global volume (!)
|
|
|
|
break;
|
|
|
|
case 'W': // global volume slide (!)
|
|
|
|
break;
|
2024-06-26 20:31:43 -04:00
|
|
|
case 'X': // panning
|
2024-06-25 05:38:15 -04:00
|
|
|
p->data[curRow][effectCol[chan]++]=0x80;
|
|
|
|
p->data[curRow][effectCol[chan]++]=effectVal[chan];
|
|
|
|
break;
|
2024-06-26 20:31:43 -04:00
|
|
|
case 'Y': // panbrello
|
2024-06-25 05:38:15 -04:00
|
|
|
break;
|
2024-06-26 20:31:43 -04:00
|
|
|
case 'Z': // MIDI macro
|
2024-06-25 05:38:15 -04:00
|
|
|
break;
|
|
|
|
}
|
2024-06-23 02:31:03 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-27 03:45:41 -04:00
|
|
|
maxChan++;
|
|
|
|
|
2024-06-26 20:31:43 -04:00
|
|
|
ds.insLen=ds.ins.size();
|
|
|
|
|
2024-06-25 17:35:22 -04:00
|
|
|
logV("maxChan: %d",maxChan);
|
|
|
|
|
2024-06-24 17:35:02 -04:00
|
|
|
// set channel visibility
|
2024-06-26 05:03:49 -04:00
|
|
|
for (int i=maxChan; i<((maxChan+32)&(~31)); i++) {
|
2024-06-24 17:35:02 -04:00
|
|
|
ds.subsong[0]->chanShow[i]=false;
|
|
|
|
ds.subsong[0]->chanShowChanOsc[i]=false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// copy patterns to the rest of subsongs
|
2024-06-25 05:38:15 -04:00
|
|
|
int copiesMade=0;
|
2024-06-24 17:35:02 -04:00
|
|
|
for (size_t i=1; i<ds.subsong.size(); i++) {
|
2024-06-25 17:35:22 -04:00
|
|
|
bool usedPat[256];
|
|
|
|
memset(usedPat,0,256*sizeof(bool));
|
|
|
|
for (int j=0; j<ds.subsong[i]->ordersLen; j++) {
|
|
|
|
usedPat[ds.subsong[i]->orders.ord[0][j]]=true;
|
|
|
|
}
|
2024-06-25 05:38:15 -04:00
|
|
|
for (int j=0; j<maxChan; j++) {
|
2024-06-24 17:35:02 -04:00
|
|
|
for (int k=0; k<patCount; k++) {
|
2024-06-25 17:35:22 -04:00
|
|
|
if (!usedPat[k]) continue;
|
2024-06-25 05:38:15 -04:00
|
|
|
if (ds.subsong[0]->pat[j].data[k]) {
|
|
|
|
ds.subsong[0]->pat[j].data[k]->copyOn(ds.subsong[i]->pat[j].getPattern(k,true));
|
|
|
|
copiesMade++;
|
|
|
|
}
|
2024-06-24 17:35:02 -04:00
|
|
|
}
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2024-06-25 05:38:15 -04:00
|
|
|
logV("copies made %d",copiesMade);
|
|
|
|
|
2024-06-25 17:35:22 -04:00
|
|
|
// set pattern lengths
|
2024-06-24 17:35:02 -04:00
|
|
|
for (size_t i=0; i<ds.subsong.size(); i++) {
|
2024-06-25 17:35:22 -04:00
|
|
|
unsigned short patLenMax=0;
|
2024-06-24 17:35:02 -04:00
|
|
|
for (int j=0; j<ds.subsong[i]->ordersLen; j++) {
|
2024-06-25 17:35:22 -04:00
|
|
|
unsigned short nextLen=patLen[ds.subsong[i]->orders.ord[0][j]];
|
|
|
|
if (patLenMax<nextLen) {
|
|
|
|
patLenMax=nextLen;
|
|
|
|
}
|
2024-06-24 17:35:02 -04:00
|
|
|
}
|
|
|
|
ds.subsong[i]->patLen=patLenMax;
|
|
|
|
}
|
|
|
|
|
|
|
|
// set systems
|
2024-06-24 03:44:17 -04:00
|
|
|
for (int i=0; i<(maxChan+32)>>5; i++) {
|
|
|
|
ds.system[i]=DIV_SYSTEM_ES5506;
|
|
|
|
ds.systemFlags[i].set("amigaVol",true);
|
|
|
|
if (ds.linearPitch!=2) {
|
|
|
|
ds.systemFlags[i].set("amigaPitch",true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ds.systemLen=(maxChan+32)>>5;
|
2024-06-23 02:31:03 -04:00
|
|
|
|
2024-06-24 19:02:31 -04:00
|
|
|
// find subsongs
|
2024-06-26 05:03:49 -04:00
|
|
|
ds.findSubSongs(maxChan);
|
2024-06-24 19:02:31 -04:00
|
|
|
|
2024-06-26 21:28:02 -04:00
|
|
|
// populate subsongs with default panning values
|
|
|
|
for (size_t i=0; i<ds.subsong.size(); i++) {
|
|
|
|
for (int j=0; j<maxChan; 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)]==0x80) {
|
|
|
|
// give up if there's a panning effect already
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (p->data[0][4+(k<<1)]==-1) {
|
|
|
|
if ((chanPan[j]&127)==100) {
|
|
|
|
// should be surround...
|
|
|
|
p->data[0][4+(k<<1)]=0x80;
|
|
|
|
p->data[0][5+(k<<1)]=0x80;
|
|
|
|
} else {
|
|
|
|
p->data[0][4+(k<<1)]=0x80;
|
|
|
|
p->data[0][5+(k<<1)]=CLAMP((chanPan[j]&127)<<2,0,255);
|
|
|
|
}
|
|
|
|
if (ds.subsong[i]->pat[j].effectCols<=k) ds.subsong[i]->pat[j].effectCols=k+1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-22 19:22:03 -04: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;
|
|
|
|
}
|
|
|
|
|