mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-22 20:45:11 +00:00
split fileOps.cpp
This commit is contained in:
parent
f80d3f9eb5
commit
3ab278d236
10 changed files with 6854 additions and 6703 deletions
|
@ -613,6 +613,14 @@ src/engine/platform/oplAInterface.cpp
|
|||
src/engine/platform/ym2608Interface.cpp
|
||||
src/engine/platform/ym2610Interface.cpp
|
||||
|
||||
src/engine/fileOps/fileOpsCommon.cpp
|
||||
src/engine/fileOps/dmf.cpp
|
||||
src/engine/fileOps/fc.cpp
|
||||
src/engine/fileOps/ftm.cpp
|
||||
src/engine/fileOps/fur.cpp
|
||||
src/engine/fileOps/mod.cpp
|
||||
src/engine/fileOps/s3m.cpp
|
||||
|
||||
src/engine/blip_buf.c
|
||||
src/engine/brrUtils.c
|
||||
src/engine/safeReader.cpp
|
||||
|
@ -625,7 +633,6 @@ src/engine/configEngine.cpp
|
|||
src/engine/dispatchContainer.cpp
|
||||
src/engine/engine.cpp
|
||||
src/engine/export.cpp
|
||||
src/engine/fileOps.cpp
|
||||
src/engine/fileOpsIns.cpp
|
||||
src/engine/fileOpsSample.cpp
|
||||
src/engine/filter.cpp
|
||||
|
|
File diff suppressed because it is too large
Load diff
1906
src/engine/fileOps/dmf.cpp
Normal file
1906
src/engine/fileOps/dmf.cpp
Normal file
File diff suppressed because it is too large
Load diff
708
src/engine/fileOps/fc.cpp
Normal file
708
src/engine/fileOps/fc.cpp
Normal file
|
@ -0,0 +1,708 @@
|
|||
/**
|
||||
* 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"
|
||||
|
||||
unsigned char fcXORTriangle[32]={
|
||||
0xc0, 0xc0, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, 0x00, 0xf8, 0xf0, 0xe8, 0xe0, 0xd8, 0xd0, 0xc8,
|
||||
0xc0, 0xb8, 0xb0, 0xa8, 0xa0, 0x98, 0x90, 0x88, 0x80, 0x88, 0x90, 0x98, 0xa0, 0xa8, 0xb0, 0xb8
|
||||
};
|
||||
|
||||
unsigned char fcCustom1[32]={
|
||||
0x45, 0x45, 0x79, 0x7d, 0x7a, 0x77, 0x70, 0x66, 0x61, 0x58, 0x53, 0x4d, 0x2c, 0x20, 0x18, 0x12,
|
||||
0x04, 0xdb, 0xd3, 0xcd, 0xc6, 0xbc, 0xb5, 0xae, 0xa8, 0xa3, 0x9d, 0x99, 0x93, 0x8e, 0x8b, 0x8a
|
||||
};
|
||||
|
||||
unsigned char fcCustom2[32]={
|
||||
0x45, 0x45, 0x79, 0x7d, 0x7a, 0x77, 0x70, 0x66, 0x5b, 0x4b, 0x43, 0x37, 0x2c, 0x20, 0x18, 0x12,
|
||||
0x04, 0xf8, 0xe8, 0xdb, 0xcf, 0xc6, 0xbe, 0xb0, 0xa8, 0xa4, 0x9e, 0x9a, 0x95, 0x94, 0x8d, 0x83
|
||||
};
|
||||
|
||||
unsigned char fcTinyTriangle[16]={
|
||||
0x00, 0x00, 0x40, 0x60, 0x7f, 0x60, 0x40, 0x20, 0x00, 0xe0, 0xc0, 0xa0, 0x80, 0xa0, 0xc0, 0xe0
|
||||
};
|
||||
|
||||
void generateFCPresetWave(int index, DivWavetable* wave) {
|
||||
wave->max=255;
|
||||
wave->len=32;
|
||||
|
||||
switch (index) {
|
||||
case 0x00: case 0x01: case 0x02: case 0x03:
|
||||
case 0x04: case 0x05: case 0x06: case 0x07:
|
||||
case 0x08: case 0x09: case 0x0a: case 0x0b:
|
||||
case 0x0c: case 0x0d: case 0x0e: case 0x0f:
|
||||
// XOR triangle
|
||||
for (int i=0; i<32; i++) {
|
||||
wave->data[i]=(unsigned char)((fcXORTriangle[i]^0x80)^(((index+15)<i)?0x87:0x00));
|
||||
}
|
||||
break;
|
||||
case 0x10: case 0x11: case 0x12: case 0x13:
|
||||
case 0x14: case 0x15: case 0x16: case 0x17:
|
||||
case 0x18: case 0x19: case 0x1a: case 0x1b:
|
||||
case 0x1c: case 0x1d: case 0x1e: case 0x1f:
|
||||
// pulse
|
||||
for (int i=0; i<32; i++) {
|
||||
wave->data[i]=(index>i)?0x01:0xff;
|
||||
}
|
||||
break;
|
||||
case 0x20: case 0x21: case 0x22: case 0x23:
|
||||
case 0x24: case 0x25: case 0x26: case 0x27:
|
||||
// tiny pulse
|
||||
for (int i=0; i<32; i++) {
|
||||
wave->data[i]=((index-0x18)>(i&15))?0x01:0xff;
|
||||
}
|
||||
break;
|
||||
case 0x28:
|
||||
case 0x2e:
|
||||
// saw
|
||||
for (int i=0; i<32; i++) {
|
||||
wave->data[i]=i<<3;
|
||||
}
|
||||
break;
|
||||
case 0x29:
|
||||
case 0x2f:
|
||||
// tiny saw
|
||||
for (int i=0; i<32; i++) {
|
||||
wave->data[i]=(i<<4)&0xff;
|
||||
}
|
||||
break;
|
||||
case 0x2a:
|
||||
// custom 1
|
||||
for (int i=0; i<32; i++) {
|
||||
wave->data[i]=fcCustom1[i]^0x80;
|
||||
}
|
||||
break;
|
||||
case 0x2b:
|
||||
// custom 2
|
||||
for (int i=0; i<32; i++) {
|
||||
wave->data[i]=fcCustom2[i]^0x80;
|
||||
}
|
||||
break;
|
||||
case 0x2c: case 0x2d:
|
||||
// tiny triangle
|
||||
for (int i=0; i<32; i++) {
|
||||
wave->data[i]=fcTinyTriangle[i&15]^0x80;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
for (int i=0; i<32; i++) {
|
||||
wave->data[i]=i;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool DivEngine::loadFC(unsigned char* file, size_t len) {
|
||||
struct InvalidHeaderException {};
|
||||
bool success=false;
|
||||
char magic[4]={0,0,0,0};
|
||||
SafeReader reader=SafeReader(file,len);
|
||||
warnings="";
|
||||
bool isFC14=false;
|
||||
unsigned int patPtr, freqMacroPtr, volMacroPtr, samplePtr, wavePtr;
|
||||
unsigned int seqLen, patLen, freqMacroLen, volMacroLen, sampleLen;
|
||||
|
||||
unsigned char waveLen[80];
|
||||
//unsigned char waveLoopLen[40];
|
||||
|
||||
struct FCSequence {
|
||||
unsigned char pat[4];
|
||||
signed char transpose[4];
|
||||
signed char offsetIns[4];
|
||||
unsigned char speed;
|
||||
};
|
||||
std::vector<FCSequence> seq;
|
||||
struct FCPattern {
|
||||
unsigned char note[32];
|
||||
unsigned char val[32];
|
||||
};
|
||||
std::vector<FCPattern> pat;
|
||||
struct FCMacro {
|
||||
unsigned char val[64];
|
||||
};
|
||||
std::vector<FCMacro> freqMacros;
|
||||
std::vector<FCMacro> volMacros;
|
||||
|
||||
struct FCSample {
|
||||
unsigned short loopLen, len, loopStart;
|
||||
} sample[10];
|
||||
|
||||
try {
|
||||
DivSong ds;
|
||||
ds.tuning=436.0;
|
||||
ds.version=DIV_VERSION_FC;
|
||||
//ds.linearPitch=0;
|
||||
//ds.pitchMacroIsLinear=false;
|
||||
//ds.noSlidesOnFirstTick=true;
|
||||
//ds.rowResetsArpPos=true;
|
||||
ds.pitchSlideSpeed=8;
|
||||
ds.ignoreJumpAtEnd=false;
|
||||
|
||||
// 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_FC13_MAGIC,4)==0) {
|
||||
isFC14=false;
|
||||
} else if (memcmp(magic,DIV_FC14_MAGIC,4)==0) {
|
||||
isFC14=true;
|
||||
} else {
|
||||
logW("the magic isn't complete");
|
||||
throw EndOfFileException(&reader,reader.tell());
|
||||
}
|
||||
|
||||
ds.systemLen=1;
|
||||
ds.system[0]=DIV_SYSTEM_AMIGA;
|
||||
ds.systemVol[0]=1.0f;
|
||||
ds.systemPan[0]=0;
|
||||
ds.systemFlags[0].set("clockSel",1); // PAL
|
||||
ds.systemFlags[0].set("stereoSep",80);
|
||||
ds.systemName="Amiga";
|
||||
|
||||
seqLen=reader.readI_BE();
|
||||
if (seqLen%13) {
|
||||
logW("sequence length is not multiple of 13 (%d)",seqLen);
|
||||
//throw EndOfFileException(&reader,reader.tell());
|
||||
}
|
||||
patPtr=reader.readI_BE();
|
||||
patLen=reader.readI_BE();
|
||||
if (patLen%64) {
|
||||
logW("pattern length is not multiple of 64 (%d)",patLen);
|
||||
throw EndOfFileException(&reader,reader.tell());
|
||||
}
|
||||
freqMacroPtr=reader.readI_BE();
|
||||
freqMacroLen=reader.readI_BE();
|
||||
if (freqMacroLen%64) {
|
||||
logW("freq sequence length is not multiple of 64 (%d)",freqMacroLen);
|
||||
//throw EndOfFileException(&reader,reader.tell());
|
||||
}
|
||||
volMacroPtr=reader.readI_BE();
|
||||
volMacroLen=reader.readI_BE();
|
||||
if (volMacroLen%64) {
|
||||
logW("vol sequence length is not multiple of 64 (%d)",volMacroLen);
|
||||
//throw EndOfFileException(&reader,reader.tell());
|
||||
}
|
||||
samplePtr=reader.readI_BE();
|
||||
if (isFC14) {
|
||||
wavePtr=reader.readI_BE(); // wave len
|
||||
sampleLen=0;
|
||||
} else {
|
||||
sampleLen=reader.readI_BE();
|
||||
wavePtr=0;
|
||||
}
|
||||
|
||||
logD("patPtr: %x",patPtr);
|
||||
logD("patLen: %d",patLen);
|
||||
logD("freqMacroPtr: %x",freqMacroPtr);
|
||||
logD("freqMacroLen: %d",freqMacroLen);
|
||||
logD("volMacroPtr: %x",volMacroPtr);
|
||||
logD("volMacroLen: %d",volMacroLen);
|
||||
logD("samplePtr: %x",samplePtr);
|
||||
if (isFC14) {
|
||||
logD("wavePtr: %x",wavePtr);
|
||||
} else {
|
||||
logD("sampleLen: %d",sampleLen);
|
||||
}
|
||||
|
||||
// sample info
|
||||
logD("samples: (%x)",reader.tell());
|
||||
for (int i=0; i<10; i++) {
|
||||
sample[i].len=reader.readS_BE();
|
||||
sample[i].loopStart=reader.readS_BE();
|
||||
sample[i].loopLen=reader.readS_BE();
|
||||
|
||||
logD("- %d: %d (%d, %d)",i,sample[i].len,sample[i].loopStart,sample[i].loopLen);
|
||||
}
|
||||
|
||||
// wavetable lengths
|
||||
if (isFC14) {
|
||||
logD("wavetables:");
|
||||
for (int i=0; i<80; i++) {
|
||||
waveLen[i]=(unsigned char)reader.readC();
|
||||
|
||||
logD("- %d: %.4x",i,waveLen[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// sequences
|
||||
seqLen/=13;
|
||||
logD("reading sequences... (%d)",seqLen);
|
||||
seq.reserve(seqLen);
|
||||
for (unsigned int i=0; i<seqLen; i++) {
|
||||
FCSequence s;
|
||||
for (int j=0; j<4; j++) {
|
||||
s.pat[j]=reader.readC();
|
||||
s.transpose[j]=reader.readC();
|
||||
s.offsetIns[j]=reader.readC();
|
||||
}
|
||||
s.speed=reader.readC();
|
||||
seq.push_back(s);
|
||||
logV(
|
||||
"%.2x | %.2x%.2x%.2x %.2x%.2x%.2x %.2x%.2x%.2x %.2x%.2x%.2x | %.2x",
|
||||
i,
|
||||
s.pat[0],s.transpose[0],s.offsetIns[0],
|
||||
s.pat[1],s.transpose[1],s.offsetIns[1],
|
||||
s.pat[2],s.transpose[2],s.offsetIns[2],
|
||||
s.pat[3],s.transpose[3],s.offsetIns[3],
|
||||
s.speed
|
||||
);
|
||||
}
|
||||
|
||||
// patterns
|
||||
if (!reader.seek(patPtr,SEEK_SET)) {
|
||||
logE("premature end of file!");
|
||||
lastError="incomplete file";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
patLen/=64;
|
||||
logD("reading patterns... (%d)",patLen);
|
||||
pat.reserve(patLen);
|
||||
for (unsigned int i=0; i<patLen; i++) {
|
||||
FCPattern p;
|
||||
logV("- pattern %d",i);
|
||||
for (int j=0; j<32; j++) {
|
||||
p.note[j]=reader.readC();
|
||||
p.val[j]=reader.readC();
|
||||
//logV("%.2x | %.2x %.2x",j,p.note[j],p.val[j]);
|
||||
}
|
||||
pat.push_back(p);
|
||||
}
|
||||
|
||||
// freq sequences
|
||||
if (!reader.seek(freqMacroPtr,SEEK_SET)) {
|
||||
logE("premature end of file!");
|
||||
lastError="incomplete file";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
freqMacroLen/=64;
|
||||
logD("reading freq sequences... (%d)",freqMacroLen);
|
||||
freqMacros.reserve(freqMacroLen);
|
||||
for (unsigned int i=0; i<freqMacroLen; i++) {
|
||||
FCMacro m;
|
||||
reader.read(m.val,64);
|
||||
freqMacros.push_back(m);
|
||||
}
|
||||
|
||||
// vol sequences
|
||||
if (!reader.seek(volMacroPtr,SEEK_SET)) {
|
||||
logE("premature end of file!");
|
||||
lastError="incomplete file";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
volMacroLen/=64;
|
||||
logD("reading volume sequences... (%d)",volMacroLen);
|
||||
volMacros.reserve(volMacroLen);
|
||||
for (unsigned int i=0; i<volMacroLen; i++) {
|
||||
FCMacro m;
|
||||
reader.read(m.val,64);
|
||||
volMacros.push_back(m);
|
||||
}
|
||||
|
||||
// samples
|
||||
if (!reader.seek(samplePtr,SEEK_SET)) {
|
||||
logE("premature end of file!");
|
||||
lastError="incomplete file";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
logD("reading samples...");
|
||||
ds.sample.reserve(10);
|
||||
for (int i=0; i<10; i++) {
|
||||
DivSample* s=new DivSample;
|
||||
s->depth=DIV_SAMPLE_DEPTH_8BIT;
|
||||
if (sample[i].len>0) {
|
||||
s->init(sample[i].len*2);
|
||||
}
|
||||
s->name=fmt::sprintf("Sample %d",i+1);
|
||||
if (sample[i].loopLen>1) {
|
||||
s->loopStart=sample[i].loopStart;
|
||||
s->loopEnd=sample[i].loopStart+(sample[i].loopLen*2);
|
||||
s->loop=(s->loopStart>=0)&&(s->loopEnd>=0);
|
||||
}
|
||||
reader.read(s->data8,sample[i].len*2);
|
||||
ds.sample.push_back(s);
|
||||
}
|
||||
ds.sampleLen=(int)ds.sample.size();
|
||||
|
||||
// wavetables
|
||||
if (isFC14) {
|
||||
if (!reader.seek(wavePtr,SEEK_SET)) {
|
||||
logE("premature end of file!");
|
||||
lastError="incomplete file";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
logD("reading wavetables...");
|
||||
ds.wave.reserve(80);
|
||||
for (int i=0; i<80; i++) {
|
||||
DivWavetable* w=new DivWavetable;
|
||||
w->min=0;
|
||||
w->max=255;
|
||||
w->len=MIN(256,waveLen[i]*2);
|
||||
|
||||
for (int i=0; i<256; i++) {
|
||||
w->data[i]=128;
|
||||
}
|
||||
|
||||
if (waveLen[i]>0) {
|
||||
signed char* waveArray=new signed char[waveLen[i]*2];
|
||||
reader.read(waveArray,waveLen[i]*2);
|
||||
int howMany=waveLen[i]*2;
|
||||
if (howMany>256) howMany=256;
|
||||
for (int i=0; i<howMany; i++) {
|
||||
w->data[i]=waveArray[i]+128;
|
||||
}
|
||||
delete[] waveArray;
|
||||
} else {
|
||||
logV("empty wave %d",i);
|
||||
generateFCPresetWave(i,w);
|
||||
}
|
||||
|
||||
ds.wave.push_back(w);
|
||||
}
|
||||
} else {
|
||||
// generate preset waves
|
||||
ds.wave.reserve(48);
|
||||
for (int i=0; i<48; i++) {
|
||||
DivWavetable* w=new DivWavetable;
|
||||
generateFCPresetWave(i,w);
|
||||
ds.wave.push_back(w);
|
||||
}
|
||||
}
|
||||
ds.waveLen=(int)ds.wave.size();
|
||||
|
||||
// convert
|
||||
ds.subsong[0]->ordersLen=seqLen;
|
||||
ds.subsong[0]->patLen=32;
|
||||
ds.subsong[0]->hz=50;
|
||||
ds.subsong[0]->pat[3].effectCols=3;
|
||||
ds.subsong[0]->speeds.val[0]=3;
|
||||
ds.subsong[0]->speeds.len=1;
|
||||
|
||||
int lastIns[4];
|
||||
int lastNote[4];
|
||||
signed char lastTranspose[4];
|
||||
bool isSliding[4];
|
||||
|
||||
memset(lastIns,-1,4*sizeof(int));
|
||||
memset(lastNote,-1,4*sizeof(int));
|
||||
memset(lastTranspose,0,4);
|
||||
memset(isSliding,0,4*sizeof(bool));
|
||||
|
||||
for (unsigned int i=0; i<seqLen; i++) {
|
||||
for (int j=0; j<4; j++) {
|
||||
ds.subsong[0]->orders.ord[j][i]=i;
|
||||
DivPattern* p=ds.subsong[0]->pat[j].getPattern(i,true);
|
||||
if (j==3 && seq[i].speed) {
|
||||
p->data[0][6]=0x0f;
|
||||
p->data[0][7]=seq[i].speed;
|
||||
}
|
||||
|
||||
bool ignoreNext=false;
|
||||
|
||||
for (int k=0; k<32; k++) {
|
||||
FCPattern& fp=pat[seq[i].pat[j]];
|
||||
if (fp.note[k]>0 && fp.note[k]<0x49) {
|
||||
lastNote[j]=fp.note[k];
|
||||
short note=(fp.note[k]+seq[i].transpose[j])%12;
|
||||
short octave=2+((fp.note[k]+seq[i].transpose[j])/12);
|
||||
if (fp.note[k]>=0x3d) octave-=6;
|
||||
if (note==0) {
|
||||
note=12;
|
||||
octave--;
|
||||
}
|
||||
octave&=0xff;
|
||||
p->data[k][0]=note;
|
||||
p->data[k][1]=octave;
|
||||
if (isSliding[j]) {
|
||||
isSliding[j]=false;
|
||||
p->data[k][4]=2;
|
||||
p->data[k][5]=0;
|
||||
}
|
||||
} else if (fp.note[k]==0x49) {
|
||||
if (k>0) {
|
||||
p->data[k-1][4]=0x0d;
|
||||
p->data[k-1][5]=0;
|
||||
}
|
||||
} else if (k==0 && lastTranspose[j]!=seq[i].transpose[j]) {
|
||||
p->data[0][2]=lastIns[j];
|
||||
p->data[0][4]=0x03;
|
||||
p->data[0][5]=0xff;
|
||||
lastTranspose[j]=seq[i].transpose[j];
|
||||
|
||||
short note=(lastNote[j]+seq[i].transpose[j])%12;
|
||||
short octave=2+((lastNote[j]+seq[i].transpose[j])/12);
|
||||
if (lastNote[j]>=0x3d) octave-=6;
|
||||
if (note==0) {
|
||||
note=12;
|
||||
octave--;
|
||||
}
|
||||
octave&=0xff;
|
||||
p->data[k][0]=note;
|
||||
p->data[k][1]=octave;
|
||||
}
|
||||
if (fp.val[k]) {
|
||||
if (ignoreNext) {
|
||||
ignoreNext=false;
|
||||
} else {
|
||||
if (fp.val[k]==0xf0) {
|
||||
p->data[k][0]=100;
|
||||
p->data[k][1]=0;
|
||||
p->data[k][2]=-1;
|
||||
} else if (fp.val[k]&0xe0) {
|
||||
if (fp.val[k]&0x40) {
|
||||
p->data[k][4]=2;
|
||||
p->data[k][5]=0;
|
||||
isSliding[j]=false;
|
||||
} else if (fp.val[k]&0x80) {
|
||||
isSliding[j]=true;
|
||||
if (k<31) {
|
||||
if (fp.val[k+1]&0x20) {
|
||||
p->data[k][4]=2;
|
||||
p->data[k][5]=fp.val[k+1]&0x1f;
|
||||
} else {
|
||||
p->data[k][4]=1;
|
||||
p->data[k][5]=fp.val[k+1]&0x1f;
|
||||
}
|
||||
ignoreNext=true;
|
||||
} else {
|
||||
p->data[k][4]=2;
|
||||
p->data[k][5]=0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
p->data[k][2]=(fp.val[k]+seq[i].offsetIns[j])&0x3f;
|
||||
lastIns[j]=p->data[k][2];
|
||||
}
|
||||
}
|
||||
} else if (fp.note[k]>0 && fp.note[k]<0x49) {
|
||||
p->data[k][2]=seq[i].offsetIns[j];
|
||||
lastIns[j]=p->data[k][2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// convert instruments
|
||||
for (unsigned int i=0; i<volMacroLen; i++) {
|
||||
DivInstrument* ins=new DivInstrument;
|
||||
FCMacro& m=volMacros[i];
|
||||
|
||||
ins->type=DIV_INS_AMIGA;
|
||||
ins->name=fmt::sprintf("Instrument %d",i);
|
||||
ins->amiga.useWave=true;
|
||||
unsigned char seqSpeed=m.val[0];
|
||||
unsigned char freqMacro=m.val[1];
|
||||
unsigned char vibSpeed=m.val[2];
|
||||
unsigned char vibDepth=m.val[3];
|
||||
unsigned char vibDelay=m.val[4];
|
||||
|
||||
unsigned char lastVal=m.val[5];
|
||||
|
||||
signed char loopMap[64];
|
||||
memset(loopMap,-1,64);
|
||||
|
||||
signed char loopMapFreq[64];
|
||||
memset(loopMapFreq,-1,64);
|
||||
|
||||
signed char loopMapWave[64];
|
||||
memset(loopMapWave,-1,64);
|
||||
|
||||
// volume sequence
|
||||
ins->std.volMacro.len=0;
|
||||
ds.ins.reserve(64 - 5);
|
||||
for (int j=5; j<64; j++) {
|
||||
loopMap[j]=ins->std.volMacro.len;
|
||||
if (m.val[j]==0xe1) { // end
|
||||
break;
|
||||
} else if (m.val[j]==0xe0) { // loop
|
||||
if (++j>=64) break;
|
||||
ins->std.volMacro.loop=loopMap[m.val[j]&63];
|
||||
break;
|
||||
} else if (m.val[j]==0xe8) { // sustain
|
||||
if (++j>=64) break;
|
||||
unsigned char susTime=m.val[j];
|
||||
// TODO: <= or <?
|
||||
for (int k=0; k<=susTime; k++) {
|
||||
ins->std.volMacro.val[ins->std.volMacro.len]=lastVal;
|
||||
if (++ins->std.volMacro.len>=255) break;
|
||||
}
|
||||
if (ins->std.volMacro.len>=255) break;
|
||||
} else if (m.val[j]==0xe9 || m.val[j]==0xea) { // volume slide
|
||||
if (++j>=64) break;
|
||||
signed char slideStep=m.val[j];
|
||||
if (++j>=64) break;
|
||||
unsigned char slideTime=m.val[j];
|
||||
// TODO: <= or <?
|
||||
for (int k=0; k<=slideTime; k++) {
|
||||
if (slideStep>0) {
|
||||
lastVal+=slideStep;
|
||||
if (lastVal>63) lastVal=63;
|
||||
} else {
|
||||
if (-slideStep>lastVal) {
|
||||
lastVal=0;
|
||||
} else {
|
||||
lastVal-=slideStep;
|
||||
}
|
||||
}
|
||||
ins->std.volMacro.val[ins->std.volMacro.len]=lastVal;
|
||||
if (++ins->std.volMacro.len>=255) break;
|
||||
}
|
||||
} else {
|
||||
// TODO: replace with upcoming macro speed
|
||||
for (int k=0; k<MAX(1,seqSpeed); k++) {
|
||||
ins->std.volMacro.val[ins->std.volMacro.len]=m.val[j];
|
||||
lastVal=m.val[j];
|
||||
if (++ins->std.volMacro.len>=255) break;
|
||||
}
|
||||
if (ins->std.volMacro.len>=255) break;
|
||||
}
|
||||
}
|
||||
|
||||
// frequency sequence
|
||||
lastVal=0;
|
||||
ins->amiga.initSample=-1;
|
||||
if (freqMacro<freqMacros.size()) {
|
||||
FCMacro& fm=freqMacros[freqMacro];
|
||||
for (int j=0; j<64; j++) {
|
||||
loopMapFreq[j]=ins->std.arpMacro.len;
|
||||
loopMapWave[j]=ins->std.waveMacro.len;
|
||||
if (fm.val[j]==0xe1) {
|
||||
break;
|
||||
} else if (fm.val[j]==0xe2 || fm.val[j]==0xe4) {
|
||||
if (++j>=64) break;
|
||||
unsigned char wave=fm.val[j];
|
||||
if (wave<10) { // sample
|
||||
if (ins->amiga.initSample==-1) {
|
||||
ins->amiga.initSample=wave;
|
||||
ins->amiga.useWave=false;
|
||||
}
|
||||
} else { // waveform
|
||||
ins->std.waveMacro.val[ins->std.waveMacro.len]=wave-10;
|
||||
ins->std.waveMacro.open=true;
|
||||
lastVal=wave;
|
||||
//if (++ins->std.arpMacro.len>=255) break;
|
||||
}
|
||||
} else if (fm.val[j]==0xe0) {
|
||||
if (++j>=64) break;
|
||||
ins->std.arpMacro.loop=loopMapFreq[fm.val[j]&63];
|
||||
ins->std.waveMacro.loop=loopMapWave[fm.val[j]&63];
|
||||
break;
|
||||
} else if (fm.val[j]==0xe3) {
|
||||
logV("unhandled vibrato!");
|
||||
} else if (fm.val[j]==0xe8) {
|
||||
logV("unhandled sustain!");
|
||||
} else if (fm.val[j]==0xe7) {
|
||||
if (++j>=64) break;
|
||||
fm=freqMacros[MIN(fm.val[j],freqMacros.size()-1)];
|
||||
j=0;
|
||||
} else if (fm.val[j]==0xe9) {
|
||||
logV("unhandled pack!");
|
||||
} else if (fm.val[j]==0xea) {
|
||||
logV("unhandled pitch!");
|
||||
} else {
|
||||
if (fm.val[j]>0x80) {
|
||||
ins->std.arpMacro.val[ins->std.arpMacro.len]=(fm.val[j]-0x80+24)^0x40000000;
|
||||
} else {
|
||||
ins->std.arpMacro.val[ins->std.arpMacro.len]=fm.val[j];
|
||||
}
|
||||
if (lastVal>=10) {
|
||||
ins->std.waveMacro.val[ins->std.waveMacro.len]=lastVal-10;
|
||||
}
|
||||
ins->std.arpMacro.open=true;
|
||||
if (++ins->std.arpMacro.len>=255) break;
|
||||
if (++ins->std.waveMacro.len>=255) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// waveform width
|
||||
if (lastVal>=10 && (unsigned int)(lastVal-10)<ds.wave.size()) {
|
||||
ins->amiga.waveLen=ds.wave[lastVal-10]->len-1;
|
||||
}
|
||||
|
||||
// vibrato
|
||||
for (int j=0; j<=vibDelay; j++) {
|
||||
ins->std.pitchMacro.val[ins->std.pitchMacro.len]=0;
|
||||
if (++ins->std.pitchMacro.len>=255) break;
|
||||
}
|
||||
int vibPos=0;
|
||||
ins->std.pitchMacro.loop=ins->std.pitchMacro.len;
|
||||
do {
|
||||
vibPos+=vibSpeed;
|
||||
if (vibPos>vibDepth) vibPos=vibDepth;
|
||||
ins->std.pitchMacro.val[ins->std.pitchMacro.len]=vibPos*32;
|
||||
if (++ins->std.pitchMacro.len>=255) break;
|
||||
} while (vibPos<vibDepth);
|
||||
do {
|
||||
vibPos-=vibSpeed;
|
||||
if (vibPos<-vibDepth) vibPos=-vibDepth;
|
||||
ins->std.pitchMacro.val[ins->std.pitchMacro.len]=vibPos*32;
|
||||
if (++ins->std.pitchMacro.len>=255) break;
|
||||
} while (vibPos>-vibDepth);
|
||||
do {
|
||||
vibPos+=vibSpeed;
|
||||
if (vibPos>0) vibPos=0;
|
||||
ins->std.pitchMacro.val[ins->std.pitchMacro.len]=vibPos*32;
|
||||
if (++ins->std.pitchMacro.len>=255) break;
|
||||
} while (vibPos<0);
|
||||
|
||||
ds.ins.push_back(ins);
|
||||
}
|
||||
ds.insLen=(int)ds.ins.size();
|
||||
|
||||
// optimize
|
||||
ds.subsong[0]->optimizePatterns();
|
||||
ds.subsong[0]->rearrangePatterns();
|
||||
|
||||
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;
|
||||
}
|
||||
|
149
src/engine/fileOps/fileOpsCommon.cpp
Normal file
149
src/engine/fileOps/fileOpsCommon.cpp
Normal file
|
@ -0,0 +1,149 @@
|
|||
/**
|
||||
* 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"
|
||||
|
||||
bool DivEngine::load(unsigned char* f, size_t slen) {
|
||||
unsigned char* file;
|
||||
size_t len;
|
||||
if (slen<18) {
|
||||
logE("too small!");
|
||||
lastError="file is too small";
|
||||
delete[] f;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!systemsRegistered) registerSystems();
|
||||
|
||||
// step 1: try loading as a zlib-compressed file
|
||||
logD("trying zlib...");
|
||||
try {
|
||||
z_stream zl;
|
||||
memset(&zl,0,sizeof(z_stream));
|
||||
|
||||
zl.avail_in=slen;
|
||||
zl.next_in=(Bytef*)f;
|
||||
zl.zalloc=NULL;
|
||||
zl.zfree=NULL;
|
||||
zl.opaque=NULL;
|
||||
|
||||
int nextErr;
|
||||
nextErr=inflateInit(&zl);
|
||||
if (nextErr!=Z_OK) {
|
||||
if (zl.msg==NULL) {
|
||||
logD("zlib error: unknown! %d",nextErr);
|
||||
} else {
|
||||
logD("zlib error: %s",zl.msg);
|
||||
}
|
||||
inflateEnd(&zl);
|
||||
lastError="not a .dmf/.fur song";
|
||||
throw NotZlibException(0);
|
||||
}
|
||||
|
||||
std::vector<InflateBlock*> blocks;
|
||||
while (true) {
|
||||
InflateBlock* ib=new InflateBlock(DIV_READ_SIZE);
|
||||
zl.next_out=ib->buf;
|
||||
zl.avail_out=ib->len;
|
||||
|
||||
nextErr=inflate(&zl,Z_SYNC_FLUSH);
|
||||
if (nextErr!=Z_OK && nextErr!=Z_STREAM_END) {
|
||||
if (zl.msg==NULL) {
|
||||
logD("zlib error: unknown error! %d",nextErr);
|
||||
lastError="unknown decompression error";
|
||||
} else {
|
||||
logD("zlib inflate: %s",zl.msg);
|
||||
lastError=fmt::sprintf("decompression error: %s",zl.msg);
|
||||
}
|
||||
for (InflateBlock* i: blocks) delete i;
|
||||
blocks.clear();
|
||||
delete ib;
|
||||
inflateEnd(&zl);
|
||||
throw NotZlibException(0);
|
||||
}
|
||||
ib->blockSize=ib->len-zl.avail_out;
|
||||
blocks.push_back(ib);
|
||||
if (nextErr==Z_STREAM_END) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
nextErr=inflateEnd(&zl);
|
||||
if (nextErr!=Z_OK) {
|
||||
if (zl.msg==NULL) {
|
||||
logD("zlib end error: unknown error! %d",nextErr);
|
||||
lastError="unknown decompression finish error";
|
||||
} else {
|
||||
logD("zlib end: %s",zl.msg);
|
||||
lastError=fmt::sprintf("decompression finish error: %s",zl.msg);
|
||||
}
|
||||
for (InflateBlock* i: blocks) delete i;
|
||||
blocks.clear();
|
||||
throw NotZlibException(0);
|
||||
}
|
||||
|
||||
size_t finalSize=0;
|
||||
size_t curSeek=0;
|
||||
for (InflateBlock* i: blocks) {
|
||||
finalSize+=i->blockSize;
|
||||
}
|
||||
if (finalSize<1) {
|
||||
logD("compressed too small!");
|
||||
lastError="file too small";
|
||||
for (InflateBlock* i: blocks) delete i;
|
||||
blocks.clear();
|
||||
throw NotZlibException(0);
|
||||
}
|
||||
file=new unsigned char[finalSize];
|
||||
for (InflateBlock* i: blocks) {
|
||||
memcpy(&file[curSeek],i->buf,i->blockSize);
|
||||
curSeek+=i->blockSize;
|
||||
delete i;
|
||||
}
|
||||
blocks.clear();
|
||||
len=finalSize;
|
||||
delete[] f;
|
||||
} catch (NotZlibException& e) {
|
||||
logD("not zlib. loading as raw...");
|
||||
file=f;
|
||||
len=slen;
|
||||
}
|
||||
|
||||
// step 2: try loading as .fur or .dmf
|
||||
if (memcmp(file,DIV_DMF_MAGIC,16)==0) {
|
||||
return loadDMF(file,len);
|
||||
} else if (memcmp(file,DIV_FTM_MAGIC,18)==0) {
|
||||
return loadFTM(file,len);
|
||||
} else if (memcmp(file,DIV_FUR_MAGIC,16)==0) {
|
||||
return loadFur(file,len);
|
||||
} else if (memcmp(file,DIV_FC13_MAGIC,4)==0 || memcmp(file,DIV_FC14_MAGIC,4)==0) {
|
||||
return loadFC(file,len);
|
||||
}
|
||||
|
||||
// step 3: try loading as .mod
|
||||
if (loadMod(f,slen)) {
|
||||
delete[] f;
|
||||
return true;
|
||||
}
|
||||
|
||||
// step 4: not a valid file
|
||||
logE("not a valid module!");
|
||||
lastError="not a compatible song";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
54
src/engine/fileOps/fileOpsCommon.h
Normal file
54
src/engine/fileOps/fileOpsCommon.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* 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 "../dataErrors.h"
|
||||
#include "../engine.h"
|
||||
#include "../../ta-log.h"
|
||||
#include <zlib.h>
|
||||
#include <fmt/printf.h>
|
||||
|
||||
#define DIV_READ_SIZE 131072
|
||||
|
||||
struct InflateBlock {
|
||||
unsigned char* buf;
|
||||
size_t len;
|
||||
size_t blockSize;
|
||||
InflateBlock(size_t s) {
|
||||
buf=new unsigned char[s];
|
||||
len=s;
|
||||
blockSize=0;
|
||||
}
|
||||
~InflateBlock() {
|
||||
delete[] buf;
|
||||
len=0;
|
||||
}
|
||||
};
|
||||
|
||||
struct NotZlibException {
|
||||
int what;
|
||||
NotZlibException(int w):
|
||||
what(w) {}
|
||||
};
|
||||
|
||||
#define DIV_DMF_MAGIC ".DelekDefleMask."
|
||||
#define DIV_FUR_MAGIC "-Furnace module-"
|
||||
#define DIV_FTM_MAGIC "FamiTracker Module"
|
||||
#define DIV_FC13_MAGIC "SMOD"
|
||||
#define DIV_FC14_MAGIC "FC14"
|
||||
#define DIV_S3M_MAGIC "SCRM"
|
611
src/engine/fileOps/ftm.cpp
Normal file
611
src/engine/fileOps/ftm.cpp
Normal file
|
@ -0,0 +1,611 @@
|
|||
/**
|
||||
* 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"
|
||||
|
||||
#define CHECK_BLOCK_VERSION(x) \
|
||||
if (blockVersion>x) { \
|
||||
logW("incompatible block version %d for %s!",blockVersion,blockName); \
|
||||
}
|
||||
|
||||
const int ftEffectMap[]={
|
||||
-1, // none
|
||||
0x0f,
|
||||
0x0b,
|
||||
0x0d,
|
||||
0xff,
|
||||
-1, // volume? not supported in Furnace yet
|
||||
0x03,
|
||||
0x03, // unused?
|
||||
0x13,
|
||||
0x14,
|
||||
0x00,
|
||||
0x04,
|
||||
0x07,
|
||||
0xe5,
|
||||
0xed,
|
||||
0x11,
|
||||
0x01, // porta up
|
||||
0x02, // porta down
|
||||
0x12,
|
||||
0x90, // sample offset - not supported yet
|
||||
0xe1,
|
||||
0xe2,
|
||||
0x0a,
|
||||
0xec,
|
||||
0x0c,
|
||||
-1, // delayed volume - not supported yet
|
||||
0x11, // FDS
|
||||
0x12,
|
||||
0x13,
|
||||
0x20, // DPCM pitch
|
||||
0x22, // 5B
|
||||
0x24,
|
||||
0x23,
|
||||
0x21,
|
||||
-1, // VRC7 "custom patch port" - not supported?
|
||||
-1, // VRC7 "custom patch write"
|
||||
-1, // release - not supported yet
|
||||
0x09, // select groove
|
||||
-1, // transpose - not supported
|
||||
0x10, // Namco 163
|
||||
-1, // FDS vol env - not supported
|
||||
-1, // FDS auto FM - not supported yet
|
||||
-1, // phase reset - not supported
|
||||
-1, // harmonic - not supported
|
||||
};
|
||||
|
||||
constexpr int ftEffectMapSize=sizeof(ftEffectMap)/sizeof(int);
|
||||
|
||||
bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
||||
SafeReader reader=SafeReader(file,len);
|
||||
warnings="";
|
||||
try {
|
||||
DivSong ds;
|
||||
String blockName;
|
||||
unsigned char expansions=0;
|
||||
unsigned int tchans=0;
|
||||
unsigned int n163Chans=0;
|
||||
bool hasSequence[256][8];
|
||||
unsigned char sequenceIndex[256][8];
|
||||
unsigned int hilightA=4;
|
||||
unsigned int hilightB=16;
|
||||
double customHz=60;
|
||||
|
||||
memset(hasSequence,0,256*8*sizeof(bool));
|
||||
memset(sequenceIndex,0,256*8);
|
||||
|
||||
if (!reader.seek(18,SEEK_SET)) {
|
||||
logE("premature end of file!");
|
||||
lastError="incomplete file";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
ds.version=(unsigned short)reader.readI();
|
||||
logI("module version %d (0x%.4x)",ds.version,ds.version);
|
||||
|
||||
if (ds.version>0x0450) {
|
||||
logE("incompatible version %x!",ds.version);
|
||||
lastError="incompatible version";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (DivSubSong* i: ds.subsong) {
|
||||
i->clearData();
|
||||
delete i;
|
||||
}
|
||||
ds.subsong.clear();
|
||||
|
||||
ds.linearPitch=0;
|
||||
|
||||
while (true) {
|
||||
blockName=reader.readString(3);
|
||||
if (blockName=="END") {
|
||||
// end of module
|
||||
logD("end of data");
|
||||
break;
|
||||
}
|
||||
|
||||
// not the end
|
||||
reader.seek(-3,SEEK_CUR);
|
||||
blockName=reader.readString(16);
|
||||
unsigned int blockVersion=(unsigned int)reader.readI();
|
||||
unsigned int blockSize=(unsigned int)reader.readI();
|
||||
size_t blockStart=reader.tell();
|
||||
|
||||
logD("reading block %s (version %d, %d bytes)",blockName,blockVersion,blockSize);
|
||||
if (blockName=="PARAMS") {
|
||||
// versions 7-9 don't change anything?
|
||||
CHECK_BLOCK_VERSION(9);
|
||||
unsigned int oldSpeedTempo=0;
|
||||
if (blockVersion<=1) {
|
||||
oldSpeedTempo=reader.readI();
|
||||
}
|
||||
if (blockVersion>=2) {
|
||||
expansions=reader.readC();
|
||||
}
|
||||
tchans=reader.readI();
|
||||
unsigned int pal=reader.readI();
|
||||
if (blockVersion>=7) {
|
||||
// advanced Hz control
|
||||
int controlType=reader.readI();
|
||||
switch (controlType) {
|
||||
case 1:
|
||||
customHz=1000000.0/(double)reader.readI();
|
||||
break;
|
||||
default:
|
||||
reader.readI();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
customHz=reader.readI();
|
||||
}
|
||||
unsigned int newVibrato=0;
|
||||
bool sweepReset=false;
|
||||
unsigned int speedSplitPoint=0;
|
||||
if (blockVersion>=3) {
|
||||
newVibrato=reader.readI();
|
||||
}
|
||||
if (blockVersion>=9) {
|
||||
sweepReset=reader.readI();
|
||||
}
|
||||
if (blockVersion>=4 && blockVersion<7) {
|
||||
hilightA=reader.readI();
|
||||
hilightB=reader.readI();
|
||||
}
|
||||
if (expansions&8) if (blockVersion>=5) { // N163 channels
|
||||
n163Chans=reader.readI();
|
||||
}
|
||||
if (blockVersion>=6) {
|
||||
speedSplitPoint=reader.readI();
|
||||
}
|
||||
|
||||
if (blockVersion>=8) {
|
||||
int fineTuneCents=reader.readC()*100;
|
||||
fineTuneCents+=reader.readC();
|
||||
|
||||
ds.tuning=440.0*pow(2.0,(double)fineTuneCents/1200.0);
|
||||
}
|
||||
|
||||
logV("old speed/tempo: %d",oldSpeedTempo);
|
||||
logV("expansions: %x",expansions);
|
||||
logV("channels: %d",tchans);
|
||||
logV("PAL: %d",pal);
|
||||
logV("custom Hz: %f",customHz);
|
||||
logV("new vibrato: %d",newVibrato);
|
||||
logV("N163 channels: %d",n163Chans);
|
||||
logV("highlight 1: %d",hilightA);
|
||||
logV("highlight 2: %d",hilightB);
|
||||
logV("split point: %d",speedSplitPoint);
|
||||
logV("sweep reset: %d",sweepReset);
|
||||
|
||||
// initialize channels
|
||||
int systemID=0;
|
||||
ds.system[systemID++]=DIV_SYSTEM_NES;
|
||||
if (expansions&1) {
|
||||
ds.system[systemID++]=DIV_SYSTEM_VRC6;
|
||||
}
|
||||
if (expansions&2) {
|
||||
ds.system[systemID++]=DIV_SYSTEM_VRC7;
|
||||
}
|
||||
if (expansions&4) {
|
||||
ds.system[systemID++]=DIV_SYSTEM_FDS;
|
||||
}
|
||||
if (expansions&8) {
|
||||
ds.system[systemID++]=DIV_SYSTEM_MMC5;
|
||||
}
|
||||
if (expansions&16) {
|
||||
ds.system[systemID]=DIV_SYSTEM_N163;
|
||||
ds.systemFlags[systemID++].set("channels",(int)n163Chans);
|
||||
}
|
||||
if (expansions&32) {
|
||||
ds.system[systemID]=DIV_SYSTEM_AY8910;
|
||||
ds.systemFlags[systemID++].set("chipType",2); // Sunsoft 5B
|
||||
}
|
||||
ds.systemLen=systemID;
|
||||
|
||||
unsigned int calcChans=0;
|
||||
for (int i=0; i<ds.systemLen; i++) {
|
||||
calcChans+=getChannelCount(ds.system[i]);
|
||||
}
|
||||
if (calcChans!=tchans) {
|
||||
logE("channel counts do not match! %d != %d",tchans,calcChans);
|
||||
lastError="channel counts do not match";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
if (tchans>DIV_MAX_CHANS) {
|
||||
tchans=DIV_MAX_CHANS;
|
||||
logW("too many channels!");
|
||||
}
|
||||
} else if (blockName=="INFO") {
|
||||
CHECK_BLOCK_VERSION(1);
|
||||
ds.name=reader.readString(32);
|
||||
ds.author=reader.readString(32);
|
||||
ds.category=reader.readString(32);
|
||||
ds.systemName="NES";
|
||||
} else if (blockName=="HEADER") {
|
||||
CHECK_BLOCK_VERSION(4);
|
||||
unsigned char totalSongs=reader.readC();
|
||||
logV("%d songs:",totalSongs+1);
|
||||
ds.subsong.reserve(totalSongs);
|
||||
for (int i=0; i<=totalSongs; i++) {
|
||||
String subSongName=reader.readString();
|
||||
ds.subsong.push_back(new DivSubSong);
|
||||
ds.subsong[i]->name=subSongName;
|
||||
ds.subsong[i]->hilightA=hilightA;
|
||||
ds.subsong[i]->hilightB=hilightB;
|
||||
if (customHz!=0) {
|
||||
ds.subsong[i]->hz=customHz;
|
||||
}
|
||||
logV("- %s",subSongName);
|
||||
}
|
||||
for (unsigned int i=0; i<tchans; i++) {
|
||||
// TODO: obey channel ID
|
||||
unsigned char chID=reader.readC();
|
||||
logV("for channel ID %d",chID);
|
||||
for (int j=0; j<=totalSongs; j++) {
|
||||
unsigned char effectCols=reader.readC();
|
||||
ds.subsong[j]->pat[i].effectCols=effectCols+1;
|
||||
logV("- song %d has %d effect columns",j,effectCols);
|
||||
}
|
||||
}
|
||||
|
||||
if (blockVersion>=4) {
|
||||
for (int i=0; i<=totalSongs; i++) {
|
||||
ds.subsong[i]->hilightA=(unsigned char)reader.readC();
|
||||
ds.subsong[i]->hilightB=(unsigned char)reader.readC();
|
||||
}
|
||||
}
|
||||
} else if (blockName=="INSTRUMENTS") {
|
||||
CHECK_BLOCK_VERSION(6);
|
||||
|
||||
reader.seek(blockSize,SEEK_CUR);
|
||||
|
||||
/*
|
||||
ds.insLen=reader.readI();
|
||||
if (ds.insLen<0 || ds.insLen>256) {
|
||||
logE("too many instruments/out of range!");
|
||||
lastError="too many instruments/out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i=0; i<ds.insLen; i++) {
|
||||
DivInstrument* ins=new DivInstrument;
|
||||
ds.ins.push_back(ins);
|
||||
}
|
||||
|
||||
logV("instruments:");
|
||||
for (int i=0; i<ds.insLen; i++) {
|
||||
unsigned int insIndex=reader.readI();
|
||||
if (insIndex>=ds.ins.size()) {
|
||||
logE("instrument index %d is out of range!",insIndex);
|
||||
lastError="instrument index out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
DivInstrument* ins=ds.ins[insIndex];
|
||||
unsigned char insType=reader.readC();
|
||||
switch (insType) {
|
||||
case 1:
|
||||
ins->type=DIV_INS_NES;
|
||||
break;
|
||||
case 2: // TODO: tell VRC6 and VRC6 saw instruments apart
|
||||
ins->type=DIV_INS_VRC6;
|
||||
break;
|
||||
case 3:
|
||||
ins->type=DIV_INS_OPLL;
|
||||
break;
|
||||
case 4:
|
||||
ins->type=DIV_INS_FDS;
|
||||
break;
|
||||
case 5:
|
||||
ins->type=DIV_INS_N163;
|
||||
break;
|
||||
case 6: // 5B?
|
||||
ins->type=DIV_INS_AY;
|
||||
break;
|
||||
default: {
|
||||
logE("%d: invalid instrument type %d",insIndex,insType);
|
||||
lastError="invalid instrument type";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// instrument data
|
||||
switch (ins->type) {
|
||||
case DIV_INS_NES: {
|
||||
unsigned int totalSeqs=reader.readI();
|
||||
if (totalSeqs>5) {
|
||||
logE("%d: too many sequences!",insIndex);
|
||||
lastError="too many sequences";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (unsigned int j=0; j<totalSeqs; j++) {
|
||||
hasSequence[insIndex][j]=reader.readC();
|
||||
sequenceIndex[insIndex][j]=reader.readC();
|
||||
}
|
||||
|
||||
const int dpcmNotes=(blockVersion>=2)?96:72;
|
||||
for (int j=0; j<dpcmNotes; j++) {
|
||||
ins->amiga.noteMap[j].map=(short)((unsigned char)reader.readC())-1;
|
||||
ins->amiga.noteMap[j].freq=(unsigned char)reader.readC();
|
||||
if (blockVersion>=6) {
|
||||
reader.readC(); // DMC value
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_INS_VRC6: {
|
||||
unsigned int totalSeqs=reader.readI();
|
||||
if (totalSeqs>4) {
|
||||
logE("%d: too many sequences!",insIndex);
|
||||
lastError="too many sequences";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (unsigned int j=0; j<totalSeqs; j++) {
|
||||
hasSequence[insIndex][j]=reader.readC();
|
||||
sequenceIndex[insIndex][j]=reader.readC();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_INS_OPLL: {
|
||||
ins->fm.opllPreset=(unsigned int)reader.readI();
|
||||
// TODO
|
||||
break;
|
||||
}
|
||||
case DIV_INS_FDS: {
|
||||
DivWavetable* wave=new DivWavetable;
|
||||
wave->len=64;
|
||||
wave->max=64;
|
||||
for (int j=0; j<64; j++) {
|
||||
wave->data[j]=reader.readC();
|
||||
}
|
||||
ins->std.waveMacro.len=1;
|
||||
ins->std.waveMacro.val[0]=ds.wave.size();
|
||||
for (int j=0; j<32; j++) {
|
||||
ins->fds.modTable[j]=reader.readC()-3;
|
||||
}
|
||||
ins->fds.modSpeed=reader.readI();
|
||||
ins->fds.modDepth=reader.readI();
|
||||
reader.readI(); // this is delay. currently ignored. TODO.
|
||||
ds.wave.push_back(wave);
|
||||
|
||||
ins->std.volMacro.len=reader.readC();
|
||||
ins->std.volMacro.loop=reader.readI();
|
||||
ins->std.volMacro.rel=reader.readI();
|
||||
reader.readI(); // arp mode does not apply here
|
||||
for (int j=0; j<ins->std.volMacro.len; j++) {
|
||||
ins->std.volMacro.val[j]=reader.readC();
|
||||
}
|
||||
|
||||
ins->std.arpMacro.len=reader.readC();
|
||||
ins->std.arpMacro.loop=reader.readI();
|
||||
ins->std.arpMacro.rel=reader.readI();
|
||||
// TODO: get rid
|
||||
ins->std.arpMacro.mode=reader.readI();
|
||||
for (int j=0; j<ins->std.arpMacro.len; j++) {
|
||||
ins->std.arpMacro.val[j]=reader.readC();
|
||||
}
|
||||
|
||||
ins->std.pitchMacro.len=reader.readC();
|
||||
ins->std.pitchMacro.loop=reader.readI();
|
||||
ins->std.pitchMacro.rel=reader.readI();
|
||||
reader.readI(); // arp mode does not apply here
|
||||
for (int j=0; j<ins->std.pitchMacro.len; j++) {
|
||||
ins->std.pitchMacro.val[j]=reader.readC();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case DIV_INS_N163: {
|
||||
// TODO!
|
||||
break;
|
||||
}
|
||||
// TODO: 5B!
|
||||
default: {
|
||||
logE("%d: what's going on here?",insIndex);
|
||||
lastError="invalid instrument type";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// name
|
||||
ins->name=reader.readString((unsigned int)reader.readI());
|
||||
logV("- %d: %s",insIndex,ins->name);
|
||||
}
|
||||
*/
|
||||
} else if (blockName=="SEQUENCES") {
|
||||
CHECK_BLOCK_VERSION(6);
|
||||
reader.seek(blockSize,SEEK_CUR);
|
||||
} else if (blockName=="FRAMES") {
|
||||
CHECK_BLOCK_VERSION(3);
|
||||
|
||||
for (size_t i=0; i<ds.subsong.size(); i++) {
|
||||
DivSubSong* s=ds.subsong[i];
|
||||
|
||||
s->ordersLen=reader.readI();
|
||||
if (blockVersion>=3) {
|
||||
s->speeds.val[0]=reader.readI();
|
||||
}
|
||||
if (blockVersion>=2) {
|
||||
s->virtualTempoN=reader.readI();
|
||||
s->patLen=reader.readI();
|
||||
}
|
||||
int why=tchans;
|
||||
if (blockVersion==1) {
|
||||
why=reader.readI();
|
||||
}
|
||||
logV("reading %d and %d orders",tchans,s->ordersLen);
|
||||
|
||||
for (int j=0; j<s->ordersLen; j++) {
|
||||
for (int k=0; k<why; k++) {
|
||||
unsigned char o=reader.readC();
|
||||
logV("%.2x",o);
|
||||
s->orders.ord[k][j]=o;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (blockName=="PATTERNS") {
|
||||
CHECK_BLOCK_VERSION(6);
|
||||
|
||||
size_t blockEnd=reader.tell()+blockSize;
|
||||
|
||||
if (blockVersion==1) {
|
||||
int patLenOld=reader.readI();
|
||||
for (DivSubSong* i: ds.subsong) {
|
||||
i->patLen=patLenOld;
|
||||
}
|
||||
}
|
||||
|
||||
// so it appears .ftm doesn't keep track of how many patterns are stored in the file....
|
||||
while (reader.tell()<blockEnd) {
|
||||
int subs=0;
|
||||
if (blockVersion>=2) subs=reader.readI();
|
||||
int ch=reader.readI();
|
||||
int patNum=reader.readI();
|
||||
int numRows=reader.readI();
|
||||
|
||||
DivPattern* pat=ds.subsong[subs]->pat[ch].getPattern(patNum,true);
|
||||
for (int i=0; i<numRows; i++) {
|
||||
unsigned int row=0;
|
||||
if (blockVersion>=2 && blockVersion<6) { // row index
|
||||
row=reader.readI();
|
||||
} else {
|
||||
row=reader.readC();
|
||||
}
|
||||
|
||||
unsigned char nextNote=reader.readC();
|
||||
unsigned char nextOctave=reader.readC();
|
||||
if (nextNote==0x0d) {
|
||||
pat->data[row][0]=100;
|
||||
} else if (nextNote==0x0e) {
|
||||
pat->data[row][0]=101;
|
||||
} else if (nextNote==0x01) {
|
||||
pat->data[row][0]=12;
|
||||
pat->data[row][1]=nextOctave-1;
|
||||
} else if (nextNote==0) {
|
||||
pat->data[row][0]=0;
|
||||
} else if (nextNote<0x0d) {
|
||||
pat->data[row][0]=nextNote-1;
|
||||
pat->data[row][1]=nextOctave;
|
||||
}
|
||||
|
||||
unsigned char nextIns=reader.readC();
|
||||
if (nextIns<0x40) {
|
||||
pat->data[row][2]=nextIns;
|
||||
} else {
|
||||
pat->data[row][2]=-1;
|
||||
}
|
||||
|
||||
unsigned char nextVol=reader.readC();
|
||||
if (nextVol<0x10) {
|
||||
pat->data[row][3]=nextVol;
|
||||
} else {
|
||||
pat->data[row][3]=-1;
|
||||
}
|
||||
|
||||
int effectCols=ds.subsong[subs]->pat[ch].effectCols;
|
||||
if (blockVersion>=6) effectCols=4;
|
||||
|
||||
for (int j=0; j<effectCols; j++) {
|
||||
unsigned char nextEffect=reader.readC();
|
||||
unsigned char nextEffectVal=0;
|
||||
if (nextEffect!=0 || blockVersion<6) nextEffectVal=reader.readC();
|
||||
if (nextEffect==0 && nextEffectVal==0) {
|
||||
pat->data[row][4+(j*2)]=-1;
|
||||
pat->data[row][5+(j*2)]=-1;
|
||||
} else {
|
||||
if (nextEffect<ftEffectMapSize) {
|
||||
pat->data[row][4+(j*2)]=ftEffectMap[nextEffect];
|
||||
} else {
|
||||
pat->data[row][4+(j*2)]=-1;
|
||||
}
|
||||
pat->data[row][5+(j*2)]=nextEffectVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (blockName=="DPCM SAMPLES") {
|
||||
CHECK_BLOCK_VERSION(1);
|
||||
reader.seek(blockSize,SEEK_CUR);
|
||||
} else if (blockName=="SEQUENCES_VRC6") {
|
||||
// where are the 5B and FDS sequences?
|
||||
CHECK_BLOCK_VERSION(6);
|
||||
reader.seek(blockSize,SEEK_CUR);
|
||||
} else if (blockName=="SEQUENCES_N163") {
|
||||
CHECK_BLOCK_VERSION(1);
|
||||
reader.seek(blockSize,SEEK_CUR);
|
||||
} else if (blockName=="COMMENTS") {
|
||||
CHECK_BLOCK_VERSION(1);
|
||||
reader.seek(blockSize,SEEK_CUR);
|
||||
} else {
|
||||
logE("block %s is unknown!",blockName);
|
||||
lastError="unknown block "+blockName;
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((reader.tell()-blockStart)!=blockSize) {
|
||||
logE("block %s is incomplete!",blockName);
|
||||
lastError="incomplete block "+blockName;
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
addWarning("FamiTracker import is experimental!");
|
||||
|
||||
ds.version=DIV_VERSION_FTM;
|
||||
|
||||
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;
|
||||
}
|
||||
} catch (EndOfFileException& e) {
|
||||
logE("premature end of file!");
|
||||
lastError="incomplete file";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
delete[] file;
|
||||
return true;
|
||||
}
|
||||
|
2710
src/engine/fileOps/fur.cpp
Normal file
2710
src/engine/fileOps/fur.cpp
Normal file
File diff suppressed because it is too large
Load diff
447
src/engine/fileOps/mod.cpp
Normal file
447
src/engine/fileOps/mod.cpp
Normal file
|
@ -0,0 +1,447 @@
|
|||
/**
|
||||
* 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"
|
||||
|
||||
bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
||||
struct InvalidHeaderException {};
|
||||
bool success=false;
|
||||
int chCount=0;
|
||||
int ordCount=0;
|
||||
std::vector<int> patPtr;
|
||||
char magic[4]={0,0,0,0};
|
||||
short defaultVols[31];
|
||||
int sampLens[31];
|
||||
// 0=arp, 1=pslide, 2=vib, 3=trem, 4=vslide
|
||||
bool fxUsage[DIV_MAX_CHANS][5];
|
||||
SafeReader reader=SafeReader(file,len);
|
||||
warnings="";
|
||||
|
||||
memset(defaultVols,0,31*sizeof(short));
|
||||
memset(sampLens,0,31*sizeof(int));
|
||||
memset(fxUsage,0,DIV_MAX_CHANS*5*sizeof(bool));
|
||||
|
||||
try {
|
||||
DivSong ds;
|
||||
ds.tuning=436.0;
|
||||
ds.version=DIV_VERSION_MOD;
|
||||
ds.linearPitch=0;
|
||||
ds.noSlidesOnFirstTick=true;
|
||||
ds.rowResetsArpPos=true;
|
||||
ds.ignoreJumpAtEnd=false;
|
||||
ds.delayBehavior=0;
|
||||
|
||||
int insCount=31;
|
||||
bool bypassLimits=false;
|
||||
|
||||
// check mod magic bytes
|
||||
if (!reader.seek(1080,SEEK_SET)) {
|
||||
logD("couldn't seek to 1080");
|
||||
throw EndOfFileException(&reader,reader.tell());
|
||||
}
|
||||
if (reader.read(magic,4)!=4) {
|
||||
logD("the magic isn't complete");
|
||||
throw EndOfFileException(&reader,reader.tell());
|
||||
}
|
||||
if (memcmp(magic,"M.K.",4)==0 || memcmp(magic,"M!K!",4)==0 || memcmp(magic,"M&K!",4)==0) {
|
||||
logD("detected a ProTracker module");
|
||||
ds.systemName="Amiga";
|
||||
chCount=4;
|
||||
} else if (memcmp(magic,"CD81",4)==0 || memcmp(magic,"OKTA",4)==0 || memcmp(magic,"OCTA",4)==0) {
|
||||
logD("detected an Oktalyzer/Octalyzer/OctaMED module");
|
||||
ds.systemName="Amiga (8-channel)";
|
||||
chCount=8;
|
||||
} else if (memcmp(magic+1,"CHN",3)==0 && magic[0]>='1' && magic[0]<='9') {
|
||||
logD("detected a FastTracker module");
|
||||
ds.systemName="PC";
|
||||
chCount=magic[0]-'0';
|
||||
} else if (memcmp(magic,"FLT",3)==0 && magic[3]>='1' && magic[3]<='9') {
|
||||
logD("detected a Fairlight module");
|
||||
ds.systemName="Amiga";
|
||||
chCount=magic[3]-'0';
|
||||
} else if (memcmp(magic,"TDZ",3)==0 && magic[3]>='1' && magic[3]<='9') {
|
||||
logD("detected a TakeTracker module");
|
||||
ds.systemName="PC";
|
||||
chCount=magic[3]-'0';
|
||||
} else if ((memcmp(magic+2,"CH",2)==0 || memcmp(magic+2,"CN",2)==0) &&
|
||||
(magic[0]>='1' && magic[0]<='9' && magic[1]>='0' && magic[1]<='9')) {
|
||||
logD("detected a Fast/TakeTracker module");
|
||||
ds.systemName="PC";
|
||||
chCount=((magic[0]-'0')*10)+(magic[1]-'0');
|
||||
} else {
|
||||
insCount=15;
|
||||
logD("possibly a Soundtracker module");
|
||||
ds.systemName="Amiga";
|
||||
chCount=4;
|
||||
}
|
||||
|
||||
// song name
|
||||
if (!reader.seek(0,SEEK_SET)) {
|
||||
logD("couldn't seek to 0");
|
||||
throw EndOfFileException(&reader,reader.tell());
|
||||
}
|
||||
ds.name=reader.readString(20);
|
||||
logI("%s",ds.name);
|
||||
|
||||
// samples
|
||||
logD("reading samples... (%d)",insCount);
|
||||
ds.sample.reserve(insCount);
|
||||
for (int i=0; i<insCount; i++) {
|
||||
DivSample* sample=new DivSample;
|
||||
sample->depth=DIV_SAMPLE_DEPTH_8BIT;
|
||||
sample->name=reader.readString(22);
|
||||
logD("%d: %s",i+1,sample->name);
|
||||
int slen=((unsigned short)reader.readS_BE())*2;
|
||||
sampLens[i]=slen;
|
||||
if (slen==2) slen=0;
|
||||
signed char fineTune=reader.readC()&0x0f;
|
||||
if (fineTune>=8) fineTune-=16;
|
||||
sample->rate=(int)(pow(2.0,(double)fineTune/96.0)*8363.0);
|
||||
sample->centerRate=sample->rate;
|
||||
defaultVols[i]=reader.readC();
|
||||
int loopStart=reader.readS_BE()*2;
|
||||
int loopLen=reader.readS_BE()*2;
|
||||
int loopEnd=loopStart+loopLen;
|
||||
// bunch of checks since ProTracker abuses those for one-shot samples
|
||||
if (loopStart>loopEnd || loopEnd<4 || loopLen<4) {
|
||||
loopStart=0;
|
||||
loopLen=0;
|
||||
}
|
||||
if (loopLen>=2) {
|
||||
sample->loopStart=loopStart;
|
||||
sample->loopEnd=loopEnd;
|
||||
sample->loop=(sample->loopStart>=0)&&(sample->loopEnd>=0);
|
||||
}
|
||||
sample->init(slen);
|
||||
ds.sample.push_back(sample);
|
||||
}
|
||||
ds.sampleLen=ds.sample.size();
|
||||
|
||||
// orders
|
||||
ds.subsong[0]->ordersLen=ordCount=(unsigned char)reader.readC();
|
||||
if (ds.subsong[0]->ordersLen<1 || ds.subsong[0]->ordersLen>128) {
|
||||
logD("invalid order count!");
|
||||
throw EndOfFileException(&reader,reader.tell());
|
||||
}
|
||||
unsigned char restartPos=reader.readC(); // restart position, unused
|
||||
logD("restart position byte: %.2x",restartPos);
|
||||
if (insCount==15) {
|
||||
if (restartPos>0x60 && restartPos<0x80) {
|
||||
logD("detected a Soundtracker module");
|
||||
} else {
|
||||
logD("no Soundtracker signature found");
|
||||
throw EndOfFileException(&reader,reader.tell());
|
||||
}
|
||||
}
|
||||
|
||||
int patMax=0;
|
||||
for (int i=0; i<128; i++) {
|
||||
unsigned char pat=reader.readC();
|
||||
if (pat>patMax) patMax=pat;
|
||||
for (int j=0; j<chCount; j++) {
|
||||
ds.subsong[0]->orders.ord[j][i]=pat;
|
||||
}
|
||||
}
|
||||
|
||||
if (insCount==15) {
|
||||
if (!reader.seek(600,SEEK_SET)) {
|
||||
logD("couldn't seek to 600");
|
||||
throw EndOfFileException(&reader,reader.tell());
|
||||
}
|
||||
} else {
|
||||
if (!reader.seek(1084,SEEK_SET)) {
|
||||
logD("couldn't seek to 1084");
|
||||
throw EndOfFileException(&reader,reader.tell());
|
||||
}
|
||||
}
|
||||
|
||||
// patterns
|
||||
ds.subsong[0]->patLen=64;
|
||||
for (int ch=0; ch<chCount; ch++) {
|
||||
for (int i=0; i<5; i++) {
|
||||
fxUsage[ch][i]=false;
|
||||
}
|
||||
}
|
||||
for (int pat=0; pat<=patMax; pat++) {
|
||||
DivPattern* chpats[DIV_MAX_CHANS];
|
||||
for (int ch=0; ch<chCount; ch++) {
|
||||
chpats[ch]=ds.subsong[0]->pat[ch].getPattern(pat,true);
|
||||
}
|
||||
for (int row=0; row<64; row++) {
|
||||
for (int ch=0; ch<chCount; ch++) {
|
||||
short* dstrow=chpats[ch]->data[row];
|
||||
unsigned char data[4];
|
||||
reader.read(&data,4);
|
||||
// instrument
|
||||
short ins=(data[0]&0xf0)|(data[2]>>4);
|
||||
if (ins>0) {
|
||||
dstrow[2]=ins-1;
|
||||
dstrow[3]=defaultVols[ins-1];
|
||||
}
|
||||
// note
|
||||
int period=data[1]+((data[0]&0x0f)<<8);
|
||||
if (period>0 && period<0x0fff) {
|
||||
short note=(short)round(log2(3424.0/period)*12);
|
||||
dstrow[0]=((note-1)%12)+1;
|
||||
dstrow[1]=(note-1)/12+1;
|
||||
if (period<114) {
|
||||
bypassLimits=true;
|
||||
}
|
||||
}
|
||||
// effects are done later
|
||||
short fxtyp=data[2]&0x0f;
|
||||
short fxval=data[3];
|
||||
dstrow[4]=fxtyp;
|
||||
dstrow[5]=fxval;
|
||||
switch (fxtyp) {
|
||||
case 0:
|
||||
if (fxval!=0) fxUsage[ch][0]=true;
|
||||
break;
|
||||
case 1: case 2: case 3:
|
||||
fxUsage[ch][1]=true;
|
||||
break;
|
||||
case 4:
|
||||
fxUsage[ch][2]=true;
|
||||
break;
|
||||
case 5:
|
||||
fxUsage[ch][1]=true;
|
||||
fxUsage[ch][4]=true;
|
||||
break;
|
||||
case 6:
|
||||
fxUsage[ch][2]=true;
|
||||
fxUsage[ch][4]=true;
|
||||
break;
|
||||
case 7:
|
||||
fxUsage[ch][3]=true;
|
||||
break;
|
||||
case 10:
|
||||
if (fxval!=0) fxUsage[ch][4]=true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// samples
|
||||
size_t pos=reader.tell();
|
||||
logD("reading sample data...");
|
||||
for (int i=0; i<insCount; i++) {
|
||||
logV("- %d: %d %d %d",i,pos,ds.sample[i]->samples,sampLens[i]);
|
||||
if (!reader.seek(pos,SEEK_SET)) {
|
||||
logD("%d: couldn't seek to %d",i,pos);
|
||||
throw EndOfFileException(&reader,reader.tell());
|
||||
}
|
||||
reader.read(ds.sample[i]->data8,ds.sample[i]->samples);
|
||||
pos+=sampLens[i];
|
||||
}
|
||||
|
||||
// convert effects
|
||||
logD("converting module...");
|
||||
for (int ch=0; ch<=chCount; ch++) {
|
||||
unsigned char fxCols=1;
|
||||
for (int pat=0; pat<=patMax; pat++) {
|
||||
auto* data=ds.subsong[0]->pat[ch].getPattern(pat,true)->data;
|
||||
short lastPitchEffect=-1;
|
||||
short lastEffectState[5]={-1,-1,-1,-1,-1};
|
||||
short setEffectState[5]={-1,-1,-1,-1,-1};
|
||||
for (int row=0;row<64;row++) {
|
||||
const short fxUsageTyp[5]={0x00,0x01,0x04,0x07,0xFA};
|
||||
short effectState[5]={0,0,0,0,0};
|
||||
unsigned char curFxCol=0;
|
||||
short fxTyp=data[row][4];
|
||||
short fxVal=data[row][5];
|
||||
auto writeFxCol=[data,row,&curFxCol](short typ, short val) {
|
||||
data[row][4+curFxCol*2]=typ;
|
||||
data[row][5+curFxCol*2]=val;
|
||||
curFxCol++;
|
||||
};
|
||||
writeFxCol(-1,-1);
|
||||
curFxCol=0;
|
||||
switch (fxTyp) {
|
||||
case 0: // arp
|
||||
effectState[0]=fxVal;
|
||||
break;
|
||||
case 5: // vol slide + porta
|
||||
effectState[4]=fxVal;
|
||||
fxTyp=3;
|
||||
fxVal=0;
|
||||
// fall through
|
||||
case 1: // note slide up
|
||||
case 2: // note slide down
|
||||
case 3: // porta
|
||||
if (fxTyp==3 && fxVal==0) {
|
||||
if (setEffectState[1]<0) break;
|
||||
fxVal=setEffectState[1];
|
||||
}
|
||||
setEffectState[1]=fxVal;
|
||||
effectState[1]=fxVal;
|
||||
if ((effectState[1]!=lastEffectState[1]) ||
|
||||
(fxTyp!=lastPitchEffect) ||
|
||||
(effectState[1]!=0 && data[row][0]>0)) {
|
||||
writeFxCol(fxTyp,fxVal);
|
||||
}
|
||||
lastPitchEffect=fxTyp;
|
||||
lastEffectState[1]=fxVal;
|
||||
break;
|
||||
case 6: // vol slide + vibrato
|
||||
effectState[4]=fxVal;
|
||||
fxTyp=4;
|
||||
fxVal=0;
|
||||
// fall through
|
||||
case 4: // vibrato
|
||||
// TODO: handle 0 value?
|
||||
if (fxVal==0) {
|
||||
if (setEffectState[2]<0) break;
|
||||
fxVal=setEffectState[2];
|
||||
}
|
||||
effectState[2]=fxVal;
|
||||
setEffectState[2]=fxVal;
|
||||
break;
|
||||
case 7: // tremolo
|
||||
if (fxVal==0) {
|
||||
if (setEffectState[3]<0) break;
|
||||
fxVal=setEffectState[3];
|
||||
}
|
||||
effectState[3]=fxVal;
|
||||
setEffectState[3]=fxVal;
|
||||
break;
|
||||
case 9: // set offset
|
||||
writeFxCol(0x90,fxVal);
|
||||
break;
|
||||
case 10: // vol slide
|
||||
effectState[4]=fxVal;
|
||||
break;
|
||||
case 11: // jump to pos
|
||||
writeFxCol(fxTyp,fxVal);
|
||||
break;
|
||||
case 12: // set vol
|
||||
data[row][3]=MIN(0x40,fxVal);
|
||||
break;
|
||||
case 13: // break to row (BCD)
|
||||
writeFxCol(fxTyp,((fxVal>>4)*10)+(fxVal&15));
|
||||
break;
|
||||
case 15: // set speed
|
||||
// TODO: somehow handle VBlank tunes
|
||||
// TODO: i am so sorry
|
||||
if (fxVal>0x20 && ds.name!="klisje paa klisje") {
|
||||
writeFxCol(0xf0,fxVal);
|
||||
} else {
|
||||
writeFxCol(0x0f,fxVal);
|
||||
}
|
||||
break;
|
||||
case 14: // extended
|
||||
fxTyp=fxVal>>4;
|
||||
fxVal&=0x0f;
|
||||
switch (fxTyp) {
|
||||
case 0:
|
||||
writeFxCol(0x10,!fxVal);
|
||||
break;
|
||||
case 1: // single note slide up
|
||||
case 2: // single note slide down
|
||||
writeFxCol(fxTyp-1+0xf1,fxVal);
|
||||
break;
|
||||
case 9: // retrigger
|
||||
writeFxCol(0x0c,fxVal);
|
||||
break;
|
||||
case 10: // single vol slide up
|
||||
case 11: // single vol slide down
|
||||
writeFxCol(fxTyp-10+0xf8,fxVal);
|
||||
break;
|
||||
case 12: // note cut
|
||||
case 13: // note delay
|
||||
writeFxCol(fxTyp-12+0xec,fxVal);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
for (int i=0; i<5; i++) {
|
||||
// pitch slide and volume slide needs to be kept active on new note
|
||||
// even after target/max is reached
|
||||
if (fxUsage[ch][i] && (effectState[i]!=lastEffectState[i] || (effectState[i]!=0 && i==4 && data[row][3]>=0))) {
|
||||
writeFxCol(fxUsageTyp[i],effectState[i]);
|
||||
}
|
||||
}
|
||||
memcpy(lastEffectState,effectState,sizeof(effectState));
|
||||
if (curFxCol>fxCols) {
|
||||
fxCols=curFxCol;
|
||||
}
|
||||
}
|
||||
}
|
||||
ds.subsong[0]->pat[ch].effectCols=fxCols;
|
||||
}
|
||||
|
||||
ds.subsong[0]->hz=50;
|
||||
ds.systemLen=(chCount+3)/4;
|
||||
for(int i=0; i<ds.systemLen; i++) {
|
||||
ds.system[i]=DIV_SYSTEM_AMIGA;
|
||||
ds.systemFlags[i].set("clockSel",1); // PAL
|
||||
ds.systemFlags[i].set("stereoSep",80);
|
||||
ds.systemFlags[i].set("bypassLimits",bypassLimits);
|
||||
ds.systemFlags[i].set("chipType",(bool)(ds.systemLen>1 || bypassLimits));
|
||||
}
|
||||
for(int i=0; i<chCount; i++) {
|
||||
ds.subsong[0]->chanShow[i]=true;
|
||||
ds.subsong[0]->chanShowChanOsc[i]=true;
|
||||
ds.subsong[0]->chanName[i]=fmt::sprintf("Channel %d",i+1);
|
||||
ds.subsong[0]->chanShortName[i]=fmt::sprintf("C%d",i+1);
|
||||
}
|
||||
for(int i=chCount; i<ds.systemLen*4; i++) {
|
||||
ds.subsong[0]->pat[i].effectCols=1;
|
||||
ds.subsong[0]->chanShow[i]=false;
|
||||
ds.subsong[0]->chanShowChanOsc[i]=false;
|
||||
}
|
||||
|
||||
// instrument creation
|
||||
ds.ins.reserve(insCount);
|
||||
for(int i=0; i<insCount; i++) {
|
||||
DivInstrument* ins=new DivInstrument;
|
||||
ins->type=DIV_INS_AMIGA;
|
||||
ins->amiga.initSample=i;
|
||||
ins->name=ds.sample[i]->name;
|
||||
ds.ins.push_back(ins);
|
||||
}
|
||||
ds.insLen=ds.ins.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 info header!");
|
||||
lastError="invalid info header!";
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
261
src/engine/fileOps/s3m.cpp
Normal file
261
src/engine/fileOps/s3m.cpp
Normal file
|
@ -0,0 +1,261 @@
|
|||
/**
|
||||
* 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"
|
||||
|
||||
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];
|
||||
unsigned char ord[256];
|
||||
unsigned short insPtr[256];
|
||||
unsigned short patPtr[256];
|
||||
unsigned char chanPan[16];
|
||||
unsigned char defVol[256];
|
||||
|
||||
try {
|
||||
DivSong ds;
|
||||
ds.version=DIV_VERSION_S3M;
|
||||
ds.linearPitch=0;
|
||||
ds.pitchMacroIsLinear=false;
|
||||
ds.noSlidesOnFirstTick=true;
|
||||
ds.rowResetsArpPos=true;
|
||||
ds.ignoreJumpAtEnd=false;
|
||||
|
||||
// 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();
|
||||
|
||||
if (ds.insLen<0 || ds.insLen>256) {
|
||||
logE("invalid instrument count!");
|
||||
lastError="invalid instrument count!";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned short patCount=reader.readS();
|
||||
|
||||
unsigned short flags=reader.readS();
|
||||
unsigned short version=reader.readS();
|
||||
bool signedSamples=(reader.readS()==1);
|
||||
|
||||
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]->hz=((double)reader.readC())/2.5;
|
||||
|
||||
unsigned char masterVol=reader.readC();
|
||||
|
||||
logV("masterVol: %d",masterVol);
|
||||
logV("signedSamples: %d",signedSamples);
|
||||
logV("globalVol: %d",globalVol);
|
||||
|
||||
reader.readC(); // UC
|
||||
bool defaultPan=(((unsigned char)reader.readC())==252);
|
||||
|
||||
reader.readS(); // reserved
|
||||
reader.readI();
|
||||
reader.readI(); // the last 2 bytes is Special. we don't read that.
|
||||
|
||||
reader.read(chanSettings,32);
|
||||
|
||||
logD("reading orders...");
|
||||
for (int i=0; i<ordersLen; i++) {
|
||||
ord[i]=reader.readC();
|
||||
logV("- %.2x",ord[i]);
|
||||
}
|
||||
// should be even
|
||||
if (ordersLen&1) reader.readC();
|
||||
|
||||
logD("reading ins pointers...");
|
||||
for (int i=0; i<ds.insLen; i++) {
|
||||
insPtr[i]=reader.readS();
|
||||
logV("- %.2x",insPtr[i]);
|
||||
}
|
||||
|
||||
logD("reading pat pointers...");
|
||||
for (int i=0; i<patCount; i++) {
|
||||
patPtr[i]=reader.readS();
|
||||
logV("- %.2x",patPtr[i]);
|
||||
}
|
||||
|
||||
if (defaultPan) {
|
||||
reader.read(chanPan,16);
|
||||
} else {
|
||||
memset(chanPan,0,16);
|
||||
}
|
||||
|
||||
// determine chips to use
|
||||
ds.systemLen=0;
|
||||
|
||||
bool hasPCM=false;
|
||||
bool hasFM=false;
|
||||
|
||||
for (int i=0; i<32; i++) {
|
||||
if (!(chanSettings[i]&128)) continue;
|
||||
if ((chanSettings[i]&127)>=32) continue;
|
||||
if ((chanSettings[i]&127)>=16) {
|
||||
hasFM=true;
|
||||
} else {
|
||||
hasPCM=true;
|
||||
}
|
||||
|
||||
if (hasFM && hasPCM) break;
|
||||
}
|
||||
|
||||
ds.systemName="PC";
|
||||
if (hasPCM) {
|
||||
ds.system[ds.systemLen]=DIV_SYSTEM_ES5506;
|
||||
ds.systemVol[ds.systemLen]=1.0f;
|
||||
ds.systemPan[ds.systemLen]=0;
|
||||
ds.systemLen++;
|
||||
}
|
||||
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++) {
|
||||
DivInstrument* ins=new DivInstrument;
|
||||
if (!reader.seek(0x4c+insPtr[i]*16,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 {
|
||||
ins->type=DIV_INS_ES5506;
|
||||
ds.ins.push_back(ins);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!reader.seek(insPtr[i]*16,SEEK_SET)) {
|
||||
logE("premature end of file!");
|
||||
lastError="incomplete file";
|
||||
delete ins;
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
String dosName=reader.readString(13);
|
||||
|
||||
if (ins->type==DIV_INS_ES5506) {
|
||||
unsigned int memSeg=0;
|
||||
memSeg=(unsigned char)reader.readC();
|
||||
memSeg|=((unsigned short)reader.readS())<<8;
|
||||
|
||||
logV("memSeg: %d",memSeg);
|
||||
|
||||
unsigned int length=reader.readI();
|
||||
|
||||
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
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
ds.ins.push_back(ins);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
Loading…
Reference in a new issue