/** * 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" extern "C" { #include "../../../extern/itcompress/compression.h" } static const unsigned char volPortaSlide[10]={ 0, 1, 4, 8, 16, 32, 64, 96, 128, 255 }; 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(); } // x reader.readC(); // 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 (loopEndpointTime[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)) { if (loopStart!=loopEnd) { target->loop=i; } } } 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) { target->len=i; //target->val[i]=p0; 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) { if (flags&128) { p0+=32; p1+=32; p0*=512; p1*=512; } else { p0*=64; p1*=64; } } target->len=i+1; target->val[i]=p0+(((p1-p0)*curTime)/timeDiff); } // split L/R if (env==1) { for (int i=0; istd.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 ins->std.panLMacro.val[i]=4095*pow(1.0-((double)val/64.0),0.25); ins->std.panRMacro.val[i]=4095; } else { // pan left ins->std.panLMacro.val[i]=4095; ins->std.panRMacro.val[i]=4095*pow(1.0+((double)val/64.0),0.25); } } ins->std.panRMacro.len=ins->std.panLMacro.len; ins->std.panRMacro.loop=ins->std.panLMacro.loop; ins->std.panRMacro.rel=ins->std.panLMacro.rel; } } bool DivEngine::loadIT(unsigned char* file, size_t len) { struct InvalidHeaderException {}; bool success=false; char magic[4]; unsigned char chanPan[64]; unsigned char chanVol[64]; unsigned char orders[256]; unsigned int insPtr[256]; unsigned int samplePtr[256]; unsigned int patPtr[256]; unsigned short patLen[256]; unsigned char defVol[256]; unsigned char defPan[256]; unsigned char defPanIns[256]; unsigned char noteMap[256][128]; bool doesPitchSlide[64]; bool doesVibrato[64]; bool doesPanning[64]; bool doesVolSlide[64]; bool doesPanSlide[64]; bool doesArp[64]; bool doesTremolo[64]; bool doesPanbrello[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(doesPanSlide,0,64*sizeof(bool)); memset(doesArp,0,64*sizeof(bool)); memset(doesTremolo,0,64*sizeof(bool)); memset(doesPanbrello,0,64*sizeof(bool)); SafeReader reader=SafeReader(file,len); warnings=""; memset(chanPan,0,64); memset(chanVol,0,64); memset(orders,0,256); memset(patLen,0,256*sizeof(unsigned short)); memset(defVol,0,256); memset(defPan,0,256); memset(defPanIns,128,256); memset(noteMap,0,256*128); try { DivSong ds; ds.version=DIV_VERSION_IT; ds.noSlidesOnFirstTick=true; ds.rowResetsArpPos=true; ds.ignoreJumpAtEnd=false; ds.pitchSlideSpeed=8; 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()); } ds.name=reader.readStringLatin1(26); 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(); 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; } if (flags&8) { ds.linearPitch=2; } else { ds.linearPitch=0; } 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 ds.subsong[0]->hz=(double)initTempo/2.5; ds.subsong[0]->speeds.val[0]=initSpeed; ds.subsong[0]->speeds.len=1; 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); logD("reading orders..."); size_t curSubSong=0; int curOrder1=0; ds.subsong[curSubSong]->ordersLen=0; bool subSongIncreased=false; for (int i=0; iordersLen=0; ds.subsong[curSubSong]->name=fmt::sprintf("Order %.2X",i); } logV("- %.2x",nextOrder); for (int j=0; jorders.ord[j][ds.subsong[curSubSong]->ordersLen]=nextOrder; } ds.subsong[curSubSong]->ordersLen++; curOrder1++; } for (int i=0; itype=DIV_INS_ES5506; if (insPtr[i]==0) { ds.ins.push_back(ins); continue; } 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; } String dosName=reader.readStringLatin1(12); reader.readC(); // 0 unsigned char initCut=255; unsigned char initRes=255; unsigned char insVol=128; unsigned short fadeOut=256; 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 fadeOut=reader.readS(); 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(); fadeOut=reader.readS(); logV("fadeOut: %d",fadeOut); reader.readC(); reader.readC(); insVol=reader.readC(); defPanIns[i]=reader.readC(); // vol/pan randomization reader.readC(); reader.readC(); // version/sample count/x reader.readI(); } ins->name=reader.readStringLatin1(26); 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; noteMap[i][j]=ins->amiga.noteMap[j].map; } // envelopes... if (compatTracker<0x200) { // old format // TODO } else { readEnvelope(reader,ins,0); readEnvelope(reader,ins,1); readEnvelope(reader,ins,2); } // 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; jstd.volMacro.len; j++) { ins->std.volMacro.val[i]=(ins->std.volMacro.val[i]*insVol)>>7; } } // add note fade if there isn't a release point in the volume envelope if (ins->std.volMacro.len>0 && ins->std.volMacro.rel>=ins->std.volMacro.len) { 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; } } // 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; } ds.ins.push_back(ins); } // read samples for (int i=0; itype=DIV_INS_ES5506; ds.ins.push_back(ins); } continue; } 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) { logW("invalid sample header!"); lastError="invalid sample header"; delete s; delete[] file; return false; } String dosName=reader.readStringLatin1(12); reader.readC(); // 0 unsigned char sampleVol=reader.readC(); unsigned char flags=reader.readC(); defVol[i]=reader.readC(); s->name=reader.readStringLatin1(26); unsigned char convert=reader.readC(); defPan[i]=reader.readC(); if (flags&2) { s->depth=DIV_SAMPLE_DEPTH_16BIT; } else { s->depth=DIV_SAMPLE_DEPTH_8BIT; } 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); 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..."); } logV("reading sample data (%d)",s->samples); if (flags&8) { // compressed sample unsigned int ret=0; logV("decompression begin... (%d)",s->samples); if (flags&4) { logW("STEREO!"); if (s->depth==DIV_SAMPLE_DEPTH_16BIT) { logV("16-bit"); short* outData=new short[s->samples*2]; ret=it_decompress16(outData,s->samples,&file[reader.tell()],len-reader.tell(),(convert&4)?1:0,(flags&4)?2:1); for (unsigned int i=0; isamples; i++) { s->data16[i]=(outData[i<<1]+outData[1+(i<<1)])>>1; } delete[] outData; } else { logV("8-bit"); signed char* outData=new signed char[s->samples*2]; ret=it_decompress8(outData,s->samples,&file[reader.tell()],len-reader.tell(),(convert&4)?1:0,(flags&4)?2:1); for (unsigned int i=0; isamples; i++) { s->data8[i]=(outData[i<<1]+outData[1+(i<<1)])>>1; } delete[] outData; } } else { if (s->depth==DIV_SAMPLE_DEPTH_16BIT) { logV("16-bit"); ret=it_decompress16(s->data16,s->samples,&file[reader.tell()],len-reader.tell(),(convert&4)?1:0,(flags&4)?2:1); } else { logV("8-bit"); ret=it_decompress8(s->data8,s->samples,&file[reader.tell()],len-reader.tell(),(convert&4)?1:0,(flags&4)?2:1); } } logV("got: %d",ret); } else { try { if (s->depth==DIV_SAMPLE_DEPTH_16BIT) { if (flags&4) { // downmix stereo for (unsigned int i=0; isamples; i++) { short l; if (convert&2) { l=reader.readS_BE(); } else { l=reader.readS(); } if (!(convert&1)) { l^=0x8000; } s->data16[i]=l; } for (unsigned int i=0; isamples; 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; } } else { for (unsigned int i=0; isamples; i++) { if (convert&2) { s->data16[i]=reader.readS_BE()^((convert&1)?0:0x8000); } else { s->data16[i]=reader.readS()^((convert&1)?0:0x8000); } } } } else { if (flags&4) { // downmix stereo for (unsigned int i=0; isamples; i++) { signed char l=reader.readC(); if (!(convert&1)) { l^=0x80; } s->data8[i]=l; } for (unsigned int i=0; isamples; i++) { signed char r=reader.readC(); if (!(convert&1)) { r^=0x80; } s->data8[i]=(s->data8[i]+r)>>1; } } else { for (unsigned int i=0; isamples; i++) { s->data8[i]=reader.readC()^((convert&1)?0:0x80); } } } } catch (EndOfFileException& e) { logW("premature end of file..."); } } // scale sample if necessary 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); } // then scale for (unsigned int i=0; isamples; i++) { s->data16[i]=(s->data16[i]*sampleVol)>>6; } } } // 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); } ds.sample.push_back(s); } // scan pattern data for effect use int maxChan=0; for (int i=0; iDIV_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()=patRows) { break; } continue; } if (chan&128) { mask[(chan-1)&63]=reader.readC(); } chan=(chan-1)&63; 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 'P': // pan slide doesPanSlide[chan]=true; break; case 'R': // tremolo doesTremolo[chan]=true; break; case 'U': // fine vibrato doesVibrato[chan]=true; break; case 'Y': // panbrello doesPanbrello[chan]=true; break; } } } } // read patterns for (int i=0; iDIV_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()pat[j].getPattern(i,true); if (vibing[j]!=vibingOld[j] || vibStatusChanged[j]) { p->data[readRow][effectCol[j]++]=0x04; p->data[readRow][effectCol[j]++]=vibing[j]?vibStatus[j]:0; doesVibrato[j]=true; } else if (doesVibrato[j] && mustCommitInitial) { p->data[readRow][effectCol[j]++]=0x04; p->data[readRow][effectCol[j]++]=0; } if (volSliding[j]!=volSlidingOld[j] || volSlideStatusChanged[j]) { if (volSlideStatus[j]>=0xf1 && volSliding[j]) { p->data[readRow][effectCol[j]++]=0xf9; p->data[readRow][effectCol[j]++]=volSlideStatus[j]&15; volSliding[j]=false; } else if ((volSlideStatus[j]&15)==15 && volSlideStatus[j]>=0x10 && volSliding[j]) { p->data[readRow][effectCol[j]++]=0xf8; p->data[readRow][effectCol[j]++]=volSlideStatus[j]>>4; volSliding[j]=false; } else { p->data[readRow][effectCol[j]++]=0xfa; p->data[readRow][effectCol[j]++]=volSliding[j]?volSlideStatus[j]:0; } doesVolSlide[j]=true; } else if (doesVolSlide[j] && mustCommitInitial) { p->data[readRow][effectCol[j]++]=0xfa; p->data[readRow][effectCol[j]++]=0; } if (porting[j]!=portingOld[j] || portaStatusChanged[j]) { if (portaStatus[j]>=0xe0 && portaType[j]!=3 && porting[j]) { p->data[readRow][effectCol[j]++]=portaType[j]|0xf0; p->data[readRow][effectCol[j]++]=(portaStatus[j]&15)*((portaStatus[j]>=0xf0)?1:1); porting[j]=false; } else { p->data[readRow][effectCol[j]++]=portaType[j]; p->data[readRow][effectCol[j]++]=porting[j]?portaStatus[j]:0; } doesPitchSlide[j]=true; } else if (doesPitchSlide[j] && mustCommitInitial) { p->data[readRow][effectCol[j]++]=0x01; p->data[readRow][effectCol[j]++]=0; } if (arping[j]!=arpingOld[j] || arpStatusChanged[j]) { p->data[readRow][effectCol[j]++]=0x00; p->data[readRow][effectCol[j]++]=arping[j]?arpStatus[j]:0; doesArp[j]=true; } else if (doesArp[j] && mustCommitInitial) { p->data[readRow][effectCol[j]++]=0x00; p->data[readRow][effectCol[j]++]=0; } if (treming[j]!=tremingOld[j] || tremStatusChanged[j]) { p->data[readRow][effectCol[j]++]=0x07; p->data[readRow][effectCol[j]++]=treming[j]?tremStatus[j]:0; doesTremolo[j]=true; } else if (doesTremolo[j] && mustCommitInitial) { p->data[readRow][effectCol[j]++]=0x07; p->data[readRow][effectCol[j]++]=0; } if (panning[j]!=panningOld[j] || panStatusChanged[j]) { p->data[readRow][effectCol[j]++]=0x84; p->data[readRow][effectCol[j]++]=panning[j]?panStatus[j]:0; doesPanbrello[j]=true; } else if (doesPanbrello[j] && mustCommitInitial) { p->data[readRow][effectCol[j]++]=0x84; p->data[readRow][effectCol[j]++]=0; } if (panSliding[j]!=panSlidingOld[j] || panSlideStatusChanged[j]) { p->data[readRow][effectCol[j]++]=0x83; p->data[readRow][effectCol[j]++]=panSliding[j]?panSlideStatus[j]:0; doesPanSlide[j]=true; } else if (doesPanSlide[j] && mustCommitInitial) { p->data[readRow][effectCol[j]++]=0x83; p->data[readRow][effectCol[j]++]=0; } if ((effectCol[j]>>1)-2>ds.subsong[0]->pat[j].effectCols) { ds.subsong[0]->pat[j].effectCols=(effectCol[j]>>1)-1; } } readRow++; memcpy(vibingOld,vibing,64*sizeof(bool)); memcpy(volSlidingOld,volSliding,64*sizeof(bool)); memcpy(portingOld,porting,64*sizeof(bool)); memcpy(arpingOld,arping,64*sizeof(bool)); memcpy(panningOld,panning,64*sizeof(bool)); memcpy(tremingOld,treming,64*sizeof(bool)); memcpy(panSlidingOld,panSliding,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(panStatusChanged,0,64*sizeof(bool)); memset(tremStatusChanged,0,64*sizeof(bool)); memset(panSlideStatusChanged,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(panning,0,64*sizeof(bool)); memset(treming,0,64*sizeof(bool)); memset(panSliding,0,64*sizeof(bool)); memset(did,0,64); mustCommitInitial=false; if (readRow>=patRows) { if (readRow>0) { // place end of pattern marker DivPattern* p=ds.subsong[0]->pat[0].getPattern(i,true); p->data[readRow-1][effectCol[0]++]=0x0d; p->data[readRow-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; } memset(effectCol,4,64); continue; } if (chan&128) { mask[(chan-1)&63]=reader.readC(); } chan=(chan-1)&63; 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) { if (note[chan]==255) { // note release p->data[readRow][0]=101; p->data[readRow][1]=0; } else if (note[chan]==254) { // note off p->data[readRow][0]=100; p->data[readRow][1]=0; } else if (note[chan]<120) { p->data[readRow][0]=note[chan]%12; p->data[readRow][1]=note[chan]/12; if (p->data[readRow][0]==0) { p->data[readRow][0]=12; p->data[readRow][1]--; } } else { // note fade, but Furnace does not support that p->data[readRow][0]=102; p->data[readRow][1]=0; } } if (hasIns) { p->data[readRow][2]=ins[chan]-1; if ((note[chan]<120 || ds.insLen==0) && ins[chan]>0) { unsigned char targetPan=0; if (ds.insLen==0) { targetPan=defPan[(ins[chan]-1)&255]; } else { targetPan=defPan[noteMap[(ins[chan]-1)&255][note[chan]]]; if (!(targetPan&128)) { targetPan=defPanIns[(ins[chan]-1)&255]^0x80; } } if (targetPan&128) { p->data[readRow][effectCol[chan]++]=0x80; p->data[readRow][effectCol[chan]++]=CLAMP((targetPan&127)<<2,0,255); } } if (hasNote && (note[chan]<120 || ds.insLen==0) && ins[chan]>0) { if (ds.insLen==0) { p->data[readRow][3]=defVol[(ins[chan]-1)&255]; } else { p->data[readRow][3]=defVol[noteMap[(ins[chan]-1)&255][note[chan]]]; } } } if (hasVol) { if (vol[chan]<=64) { p->data[readRow][3]=vol[chan]; } else { // effects in volume column if (vol[chan]>=128 && vol[chan]<=192) { // panning p->data[readRow][effectCol[chan]++]=0x80; p->data[readRow][effectCol[chan]++]=CLAMP((vol[chan]-128)<<2,0,255); } else if (vol[chan]>=65 && vol[chan]<=74) { // fine vol up } else if (vol[chan]>=75 && vol[chan]<=84) { // fine vol down } else if (vol[chan]>=85 && vol[chan]<=94) { // vol slide up if ((vol[chan]-85)!=0) { volSlideStatus[chan]=(vol[chan]-85)<<4; volSlideStatusChanged[chan]=true; } if (hasNote || hasIns) { volSlideStatusChanged[chan]=true; } volSliding[chan]=true; } else if (vol[chan]>=95 && vol[chan]<=104) { // vol slide down if ((vol[chan]-95)!=0) { volSlideStatus[chan]=vol[chan]-95; volSlideStatusChanged[chan]=true; } if (hasNote || hasIns) { volSlideStatusChanged[chan]=true; } volSliding[chan]=true; } 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; } if (portaType[chan]!=3) { portaStatusChanged[chan]=true; } portaType[chan]=3; porting[chan]=true; } else if (vol[chan]>=203 && vol[chan]<=212) { // vibrato if ((vol[chan]-203)!=0) { vibStatus[chan]&=0xf0; vibStatus[chan]|=(vol[chan]-203); vibStatusChanged[chan]=true; } vibing[chan]=true; } } } if (hasEffect) { switch (effect[chan]+'A'-1) { case 'A': // speed p->data[readRow][effectCol[chan]++]=0x0f; p->data[readRow][effectCol[chan]++]=effectVal[chan]; break; case 'B': // go to order p->data[readRow][effectCol[chan]++]=0x0b; p->data[readRow][effectCol[chan]++]=orders[effectVal[chan]]; break; case 'C': // next order p->data[readRow][effectCol[chan]++]=0x0d; p->data[readRow][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; } if (portaType[chan]!=2) { portaStatusChanged[chan]=true; } portaType[chan]=2; porting[chan]=true; break; case 'F': // pitch up if (effectVal[chan]!=0) { portaStatus[chan]=effectVal[chan]; portaStatusChanged[chan]=true; } if (portaType[chan]!=1) { portaStatusChanged[chan]=true; } portaType[chan]=1; porting[chan]=true; break; case 'G': // porta if (effectVal[chan]!=0) { portaStatus[chan]=effectVal[chan]; portaStatusChanged[chan]=true; } if (portaType[chan]!=3 || hasNote) { portaStatusChanged[chan]=true; } 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; case 'M': // channel vol break; case 'N': // channel vol slide break; case 'O': // offset p->data[readRow][effectCol[chan]++]=0x91; p->data[readRow][effectCol[chan]++]=effectVal[chan]; break; case 'P': // pan slide if (effectVal[chan]!=0) { panSlideStatus[chan]=effectVal[chan]; panSlideStatusChanged[chan]=true; } panSliding[chan]=true; break; case 'Q': // retrigger if (effectVal[chan]!=0) { lastRetrig[chan]=effectVal[chan]; } p->data[readRow][effectCol[chan]++]=0x0c; p->data[readRow][effectCol[chan]++]=lastRetrig[chan]&15; break; case 'R': // tremolo if (effectVal[chan]!=0) { tremStatus[chan]=effectVal[chan]; tremStatusChanged[chan]=true; } treming[chan]=true; break; case 'S': // special... switch (effectVal[chan]>>4) { case 0x8: p->data[readRow][effectCol[chan]++]=0x80; p->data[readRow][effectCol[chan]++]=(effectVal[chan]&15)<<4; break; case 0xc: p->data[readRow][effectCol[chan]++]=0xec; p->data[readRow][effectCol[chan]++]=effectVal[chan]&15; break; case 0xd: p->data[readRow][effectCol[chan]++]=0xed; p->data[readRow][effectCol[chan]++]=effectVal[chan]&15; break; } break; case 'T': // tempo if (effectVal[chan]>=0x20) { p->data[readRow][effectCol[chan]++]=0xf0; p->data[readRow][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; case 'X': // panning p->data[readRow][effectCol[chan]++]=0x80; p->data[readRow][effectCol[chan]++]=effectVal[chan]; break; case 'Y': // panbrello if (effectVal[chan]!=0) { panStatus[chan]=effectVal[chan]; panStatusChanged[chan]=true; } panning[chan]=true; break; case 'Z': // MIDI macro break; } } } } maxChan++; ds.insLen=ds.ins.size(); logV("maxChan: %d",maxChan); // set channel visibility for (int i=maxChan; i<((maxChan+32)&(~31)); i++) { ds.subsong[0]->chanShow[i]=false; ds.subsong[0]->chanShowChanOsc[i]=false; } // copy patterns to the rest of subsongs int copiesMade=0; for (size_t i=1; iordersLen; j++) { usedPat[ds.subsong[i]->orders.ord[0][j]]=true; } for (int j=0; jpat[j].data[k]) { ds.subsong[0]->pat[j].data[k]->copyOn(ds.subsong[i]->pat[j].getPattern(k,true)); copiesMade++; } } 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)); } logV("copies made %d",copiesMade); // set pattern lengths for (size_t i=0; iordersLen; j++) { unsigned short nextLen=patLen[ds.subsong[i]->orders.ord[0][j]]; if (patLenMaxpatLen=patLenMax; } // set systems 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; ds.systemName="PC"; // find subsongs ds.findSubSongs(maxChan); // populate subsongs with default panning values for (size_t i=0; ipat[j].getPattern(ds.subsong[i]->orders.ord[j][0],true); for (int k=0; kdata[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; } } } } 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; }