From 90d1fd60d856ab3107d77c73b37428f93e880e05 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 18 May 2023 19:50:36 -0500 Subject: [PATCH] dev157 - new pattern format --- papers/format.md | 73 ++++----- src/engine/engine.h | 4 +- src/engine/fileOps.cpp | 326 +++++++++++++++++++++++++++++++++++------ 3 files changed, 317 insertions(+), 86 deletions(-) diff --git a/papers/format.md b/papers/format.md index 498a8fe92..2a0a045cf 100644 --- a/papers/format.md +++ b/papers/format.md @@ -32,6 +32,7 @@ these fields are 0 in format versions prior to 100 (0.6pre1). the format versions are: +- 157: Furnace dev157 - 156: Furnace dev156 - 155: Furnace dev155 - 154: Furnace dev154 @@ -1290,42 +1291,42 @@ size | description 2 | pattern index STR | pattern name (>=51) ??? | pattern data - | read a byte per row. - | if bit 7 is set, then read bit 0-6 as "skip N rows". - | if it is 0xff, end of pattern. - | if bit 7 is clear, then: - | - bit 0: note present - | - bit 1: ins present - | - bit 2: volume present - | - bit 3: effect 0 present - | - bit 4: effect value 0 present - | - bit 5: other effects present - | - bit 6: other effect values present - | if bit 5 is set, read another byte: - | - bit 0: effect 0 present - | - bit 1: effect 1 present - | - bit 2: effect 2 present - | - bit 3: effect 3 present - | - bit 4: effect 4 present - | - bit 5: effect 5 present - | - bit 6: effect 6 present - | - bit 7: effect 7 present - | if bit 6 is set, read another byte: - | - bit 0: effect value 0 present - | - bit 1: effect value 1 present - | - bit 2: effect value 2 present - | - bit 3: effect value 3 present - | - bit 4: effect value 4 present - | - bit 5: effect value 5 present - | - bit 6: effect value 6 present - | - bit 7: effect value 7 present - | then read note, ins, volume, effects and effect values depending on what is present. - | for note: - | - 0 is C-(-5) - | - 179 is B-9 - | - 180 is note off - | - 181 is note release - | - 182 is macro release + | - read a byte per row. + | - if it is 0xff, end of pattern. + | - if bit 7 is set, then read bit 0-6 as "skip N+2 rows". + | - if bit 7 is clear, then: + | - bit 0: note present + | - bit 1: ins present + | - bit 2: volume present + | - bit 3: effect 0 present + | - bit 4: effect value 0 present + | - bit 5: other effects (0-3) present + | - bit 6: other effects (4-7) present + | - if bit 5 is set, read another byte: + | - bit 0: effect 0 present + | - bit 1: effect value 0 present + | - bit 2: effect 1 present + | - bit 3: effect value 1 present + | - bit 4: effect 2 present + | - bit 5: effect value 2 present + | - bit 6: effect 3 present + | - bit 7: effect value 3 present + | - if bit 6 is set, read another byte: + | - bit 0: effect 4 present + | - bit 1: effect value 4 present + | - bit 2: effect 5 present + | - bit 3: effect value 5 present + | - bit 4: effect 6 present + | - bit 5: effect value 6 present + | - bit 6: effect 7 present + | - bit 7: effect value 7 present + | - then read note, ins, volume, effects and effect values depending on what is present. + | - for note: + | - 0 is C-(-5) + | - 179 is B-9 + | - 180 is note off + | - 181 is note release + | - 182 is macro release ``` diff --git a/src/engine/engine.h b/src/engine/engine.h index 6dcf3eb5f..3c7d39a4d 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -53,8 +53,8 @@ #define EXTERN_BUSY_BEGIN_SOFT e->softLocked=true; e->isBusy.lock(); #define EXTERN_BUSY_END e->isBusy.unlock(); e->softLocked=false; -#define DIV_VERSION "dev156" -#define DIV_ENGINE_VERSION 156 +#define DIV_VERSION "dev157" +#define DIV_ENGINE_VERSION 157 // for imports #define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_FC 0xff02 diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 62716f2b9..fde806126 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1650,6 +1650,42 @@ void DivEngine::convertOldFlags(unsigned int oldFlags, DivConfig& newFlags, DivS } } +short newFormatNotes[180]={ + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // -5 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // -4 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // -3 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // -2 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // -1 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 0 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 1 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 2 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 3 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 4 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 5 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 6 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 7 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 8 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 // 9 +}; + +short newFormatOctaves[180]={ + 250, 251, 251, 251, 251, 251, 251, 251, 251, 251, 251, 251, // -5 + 251, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, // -4 + 252, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, // -3 + 253, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, // -2 + 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // -1 + 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1 + 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 2 + 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 3 + 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 4 + 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // 5 + 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, // 6 + 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // 7 + 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // 8 + 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 9 +}; + bool DivEngine::loadFur(unsigned char* file, size_t len) { unsigned int insPtr[256]; unsigned int wavePtr[256]; @@ -2582,6 +2618,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { // read patterns for (int i: patPtr) { + bool isNewFormat=false; if (!reader.seek(i,SEEK_SET)) { logE("couldn't seek to pattern in %x!",i); lastError=fmt::sprintf("couldn't seek to pattern in %x!",i); @@ -2592,62 +2629,151 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { reader.read(magic,4); logD("reading pattern in %x...",i); if (strcmp(magic,"PATR")!=0) { - logE("%x: invalid pattern header!",i); - lastError="invalid pattern header!"; - ds.unload(); - delete[] file; - return false; + if (strcmp(magic,"PATN")!=0 || ds.version<157) { + logE("%x: invalid pattern header!",i); + lastError="invalid pattern header!"; + ds.unload(); + delete[] file; + return false; + } else { + isNewFormat=true; + } } reader.readI(); - int chan=reader.readS(); - int index=reader.readS(); - int subs=0; - if (ds.version>=95) { - subs=reader.readS(); - } else { - reader.readS(); - } - reader.readS(); + if (isNewFormat) { + int subs=(unsigned char)reader.readC(); + int chan=(unsigned char)reader.readC(); + int index=reader.readS(); - logD("- %d, %d, %d",subs,chan,index); + logD("- %d, %d, %d (new)",subs,chan,index); - if (chan<0 || chan>=tchans) { - logE("pattern channel out of range!",i); - lastError="pattern channel out of range!"; - ds.unload(); - delete[] file; - return false; - } - if (index<0 || index>(DIV_MAX_PATTERNS-1)) { - logE("pattern index out of range!",i); - lastError="pattern index out of range!"; - ds.unload(); - delete[] file; - return false; - } - if (subs<0 || subs>=(int)ds.subsong.size()) { - logE("pattern subsong out of range!",i); - lastError="pattern subsong out of range!"; - ds.unload(); - delete[] file; - return false; - } - - DivPattern* pat=ds.subsong[subs]->pat[chan].getPattern(index,true); - for (int j=0; jpatLen; j++) { - pat->data[j][0]=reader.readS(); - pat->data[j][1]=reader.readS(); - pat->data[j][2]=reader.readS(); - pat->data[j][3]=reader.readS(); - for (int k=0; kpat[chan].effectCols; k++) { - pat->data[j][4+(k<<1)]=reader.readS(); - pat->data[j][5+(k<<1)]=reader.readS(); + if (chan<0 || chan>=tchans) { + logE("pattern channel out of range!",i); + lastError="pattern channel out of range!"; + ds.unload(); + delete[] file; + return false; + } + if (index<0 || index>(DIV_MAX_PATTERNS-1)) { + logE("pattern index out of range!",i); + lastError="pattern index out of range!"; + ds.unload(); + delete[] file; + return false; + } + if (subs<0 || subs>=(int)ds.subsong.size()) { + logE("pattern subsong out of range!",i); + lastError="pattern subsong out of range!"; + ds.unload(); + delete[] file; + return false; } - } - if (ds.version>=51) { + DivPattern* pat=ds.subsong[subs]->pat[chan].getPattern(index,true); pat->name=reader.readString(); + + // read new pattern + for (int j=0; jpatLen; j++) { + unsigned char mask=reader.readC(); + unsigned short effectMask=0; + + if (mask==0xff) break; + if (mask&128) { + j+=(mask&127)+1; + continue; + } + + if (mask&32) { + effectMask|=(unsigned char)reader.readC(); + } + if (mask&64) { + effectMask|=((unsigned short)reader.readC()&0xff)<<8; + } + if (mask&8) effectMask|=1; + if (mask&16) effectMask|=2; + + if (mask&1) { // note + unsigned char note=reader.readC(); + if (note==180) { + pat->data[j][0]=100; + pat->data[j][1]=0; + } else if (note==181) { + pat->data[j][0]=101; + pat->data[j][1]=0; + } else if (note==182) { + pat->data[j][0]=102; + pat->data[j][1]=0; + } else if (note<180) { + pat->data[j][0]=newFormatNotes[note]; + pat->data[j][1]=newFormatOctaves[note]; + } else { + pat->data[j][0]=0; + pat->data[j][1]=0; + } + } + if (mask&2) { // instrument + pat->data[j][2]=(unsigned char)reader.readC(); + } + if (mask&4) { // volume + pat->data[j][3]=(unsigned char)reader.readC(); + } + for (unsigned char k=0; k<16; k++) { + if (effectMask&(1<data[j][4+k]=(unsigned char)reader.readC(); + } + } + } + } else { + int chan=reader.readS(); + int index=reader.readS(); + int subs=0; + if (ds.version>=95) { + subs=reader.readS(); + } else { + reader.readS(); + } + reader.readS(); + + logD("- %d, %d, %d (old)",subs,chan,index); + + if (chan<0 || chan>=tchans) { + logE("pattern channel out of range!",i); + lastError="pattern channel out of range!"; + ds.unload(); + delete[] file; + return false; + } + if (index<0 || index>(DIV_MAX_PATTERNS-1)) { + logE("pattern index out of range!",i); + lastError="pattern index out of range!"; + ds.unload(); + delete[] file; + return false; + } + if (subs<0 || subs>=(int)ds.subsong.size()) { + logE("pattern subsong out of range!",i); + lastError="pattern subsong out of range!"; + ds.unload(); + delete[] file; + return false; + } + + DivPattern* pat=ds.subsong[subs]->pat[chan].getPattern(index,true); + for (int j=0; jpatLen; j++) { + pat->data[j][0]=reader.readS(); + pat->data[j][1]=reader.readS(); + pat->data[j][2]=reader.readS(); + pat->data[j][3]=reader.readS(); + for (int k=0; kpat[chan].effectCols; k++) { + pat->data[j][4+(k<<1)]=reader.readS(); + pat->data[j][5+(k<<1)]=reader.readS(); + } + } + + if (ds.version>=51) { + pat->name=reader.readString(); + } } } @@ -4951,6 +5077,8 @@ DivDataErrors DivEngine::readAssetDirData(SafeReader& reader, std::vector subSongPtr; @@ -5385,6 +5513,107 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { for (PatToWrite& i: patsToWrite) { DivPattern* pat=song.subsong[i.subsong]->pat[i.chan].getPattern(i.pat,false); patPtr.push_back(w->tell()); + +#ifdef NEW_PATTERN_FORMAT + w->write("PATN",4); + blockStartSeek=w->tell(); + w->writeI(0); + + w->writeC(i.subsong); + w->writeC(i.chan); + w->writeS(i.pat); + w->writeString(pat->name,false); + + unsigned char emptyRows=0; + + for (int j=0; jpatLen; j++) { + unsigned char mask=0; + unsigned char finalNote=255; + unsigned short effectMask=0; + + if (pat->data[j][0]==100) { + finalNote=180; + } else if (pat->data[j][0]==101) { // note release + finalNote=181; + } else if (pat->data[j][0]==102) { // macro release + finalNote=182; + } else if (pat->data[j][1]==0 && pat->data[j][0]==0) { + finalNote=255; + } else { + int seek=(pat->data[j][0]+(signed char)pat->data[j][1]*12)+60; + if (seek<0 || seek>=180) { + finalNote=255; + } else { + finalNote=seek; + } + } + + if (finalNote!=255) mask|=1; // note + if (pat->data[j][2]!=-1) mask|=2; // instrument + if (pat->data[j][3]!=-1) mask|=4; // volume + for (int k=0; kpat[i.chan].effectCols*2; k+=2) { + if (k==0) { + if (pat->data[j][4+k]!=-1) mask|=8; + if (pat->data[j][5+k]!=-1) mask|=16; + } else if (k<8) { + if (pat->data[j][4+k]!=-1 || pat->data[j][5+k]!=-1) mask|=32; + } else { + if (pat->data[j][4+k]!=-1 || pat->data[j][5+k]!=-1) mask|=64; + } + + if (pat->data[j][4+k]!=-1) effectMask|=(1<data[j][5+k]!=-1) effectMask|=(2<127) { + w->writeC(128|(emptyRows-2)); + emptyRows=0; + } + } else { + if (emptyRows>1) { + w->writeC(128|(emptyRows-2)); + emptyRows=0; + } else if (emptyRows) { + w->writeC(0); + emptyRows=0; + } + + w->writeC(mask); + + if (mask&32) w->writeC(effectMask&0xff); + if (mask&64) w->writeC((effectMask>>8)&0xff); + + if (mask&1) w->writeC(finalNote); + if (mask&2) w->writeC(pat->data[j][2]); + if (mask&4) w->writeC(pat->data[j][3]); + if (mask&8) w->writeC(pat->data[j][4]); + if (mask&16) w->writeC(pat->data[j][5]); + if (mask&32) { + if (effectMask&4) w->writeC(pat->data[j][6]); + if (effectMask&8) w->writeC(pat->data[j][7]); + if (effectMask&16) w->writeC(pat->data[j][8]); + if (effectMask&32) w->writeC(pat->data[j][9]); + if (effectMask&64) w->writeC(pat->data[j][10]); + if (effectMask&128) w->writeC(pat->data[j][11]); + } + if (mask&64) { + if (effectMask&256) w->writeC(pat->data[j][12]); + if (effectMask&512) w->writeC(pat->data[j][13]); + if (effectMask&1024) w->writeC(pat->data[j][14]); + if (effectMask&2048) w->writeC(pat->data[j][15]); + if (effectMask&4096) w->writeC(pat->data[j][16]); + if (effectMask&8192) w->writeC(pat->data[j][17]); + if (effectMask&16384) w->writeC(pat->data[j][18]); + if (effectMask&32768) w->writeC(pat->data[j][19]); + } + } + } + + // stop + w->writeC(0xff); +#else w->write("PATR",4); blockStartSeek=w->tell(); w->writeI(0); @@ -5410,6 +5639,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { } w->writeString(pat->name,false); +#endif blockEndSeek=w->tell(); w->seek(blockStartSeek,SEEK_SET);