/** * 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" static void readSbiOpData(sbi_t& sbi, SafeReader& reader) { sbi.Mcharacteristics = reader.readC(); sbi.Ccharacteristics = reader.readC(); sbi.Mscaling_output = reader.readC(); sbi.Cscaling_output = reader.readC(); sbi.Meg_AD = reader.readC(); sbi.Ceg_AD = reader.readC(); sbi.Meg_SR = reader.readC(); sbi.Ceg_SR = reader.readC(); sbi.Mwave = reader.readC(); sbi.Cwave = reader.readC(); sbi.FeedConnect = reader.readC(); } bool DivEngine::loadS3M(unsigned char* file, size_t len) { struct InvalidHeaderException {}; bool success=false; bool opl2=!getConfInt("s3mOPL3",0); char magic[4]={0,0,0,0}; SafeReader reader=SafeReader(file,len); warnings=""; unsigned char chanSettings[32]; unsigned int insPtr[256]; unsigned int patPtr[256]; unsigned char chanPan[16]; unsigned char chanMap[32]; unsigned char defVol[256]; unsigned char orders[256]; bool doesPitchSlide[32]; bool doesVibrato[32]; bool doesPanning[32]; bool doesVolSlide[32]; bool doesPanSlide[32]; bool doesArp[32]; bool doesTremolo[32]; bool doesPanbrello[32]; memset(chanSettings,0,32); memset(insPtr,0,256*sizeof(unsigned int)); memset(patPtr,0,256*sizeof(unsigned int)); memset(chanPan,0,16); memset(chanMap,0,32); memset(defVol,0,256); memset(orders,0,256); memset(doesPitchSlide,0,32*sizeof(bool)); memset(doesVibrato,0,32*sizeof(bool)); memset(doesPanning,0,32*sizeof(bool)); memset(doesVolSlide,0,32*sizeof(bool)); memset(doesPanSlide,0,32*sizeof(bool)); memset(doesArp,0,32*sizeof(bool)); memset(doesTremolo,0,32*sizeof(bool)); memset(doesPanbrello,0,32*sizeof(bool)); try { DivSong ds; ds.version=DIV_VERSION_S3M; ds.linearPitch=0; ds.pitchMacroIsLinear=false; ds.noSlidesOnFirstTick=true; ds.rowResetsArpPos=true; ds.ignoreJumpAtEnd=false; ds.pitchSlideSpeed=12; logV("Scream Tracker 3 module"); // 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.readStringLatin1(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 (ordersLen>256) { logE("invalid order count!"); lastError="invalid order count!"; delete[] file; return false; } logV("orders: %d",ordersLen); logV("instruments: %d",ds.insLen); if (ds.insLen<0 || ds.insLen>256) { logE("invalid instrument count!"); lastError="invalid instrument count!"; delete[] file; return false; } unsigned short patCount=reader.readS(); logV("patterns: %d",patCount); if (patCount>256) { logE("invalid pattern count!"); lastError="invalid pattern count!"; delete[] file; return false; } unsigned short flags=reader.readS(); unsigned short version=reader.readS(); bool signedSamples=(reader.readS()==1); logV("flags: %x",flags); logV("version: %x",version); if (signedSamples) { logV("signed samples: yes"); } else { logV("signed samples: no"); } /*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]->speeds.len=1; unsigned char tempo=reader.readC(); ds.subsong[0]->hz=((double)tempo)/2.5; unsigned char masterVol=reader.readC(); logV("masterVol: %d",masterVol); logV("signedSamples: %d",signedSamples); logV("globalVol: %d",globalVol); reader.readC(); // UC unsigned char defaultPan=(unsigned char)reader.readC(); logV("defaultPan: %d",defaultPan); reader.readS(); // reserved reader.readI(); reader.readI(); // the last 2 bytes is Special. we don't read that. reader.read(chanSettings,32); logD("channel settings:"); for (int i=0; i<32; i++) { logV("%d. %x",i,chanSettings[i]); } 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++; } logD("reading ins pointers..."); for (int i=0; i=32) continue; if ((chanSettings[ch]&127)>=16) { hasFM=true; } else { hasPCM=true; numChans++; } if (hasFM && hasPCM) break; } int pcmChan=hasFM?(opl2 ? 9 : 18):0; int fmChan=hasPCM?32:0; int invalidChan=40; for (int ch=0; ch<32; ch++) { if (chanSettings[ch]==255) { chanMap[ch]=invalidChan++; continue; } if ((chanSettings[ch]&127)>=32) { chanMap[ch]=invalidChan++; continue; } if ((chanSettings[ch]&127)>=16) { chanMap[ch]=fmChan++; } else { chanMap[ch]=pcmChan++; } } char buffer[40]; int chanIndex = 1; if (hasPCM) { for(int ch = 0; ch < pcmChan - (realNumChans - (hasFM ? 9 : 0)); ch++) { ds.subsong[0]->chanShow[ch]=false; ds.subsong[0]->chanShowChanOsc[ch]=false; } for (int ch=pcmChan; ch<32; ch++) { ds.subsong[0]->chanShow[ch]=false; ds.subsong[0]->chanShowChanOsc[ch]=false; } for(int ch = 0; ch < 32; ch++) { if(ds.subsong[0]->chanShow[ch]) { snprintf(buffer, 40, _("Channel %d"), chanIndex); ds.subsong[0]->chanName[ch] = buffer; chanIndex++; } } } if (hasFM && !opl2) { for (int ch=(hasPCM?32:0) + 9; ch<(hasPCM?32:0) + 18; ch++) { ds.subsong[0]->chanShow[ch]=false; ds.subsong[0]->chanShowChanOsc[ch]=false; } chanIndex = 1; for (int ch=(hasPCM?32:0); ch<(hasPCM?32:0) + 9; ch++) { snprintf(buffer, 40, _("FM %d"), chanIndex); ds.subsong[0]->chanName[ch] = buffer; chanIndex++; } } logV("numChans: %d",numChans); logV("realNumChans: %d",realNumChans); ds.systemName="PC"; if (hasPCM) { ds.system[ds.systemLen]=DIV_SYSTEM_ES5506; ds.systemVol[ds.systemLen]=(float)globalVol/64.0; ds.systemPan[ds.systemLen]=0; ds.systemFlags[ds.systemLen].set("volScale",3900); ds.systemFlags[ds.systemLen].set("amigaVol",true); ds.systemFlags[ds.systemLen].set("amigaPitch",true); ds.systemLen++; } if (hasFM) { ds.system[ds.systemLen]=opl2 ? DIV_SYSTEM_OPL2 : DIV_SYSTEM_OPL3; 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; itype=DIV_INS_ES5506; ds.ins.push_back(ins); DivSample* emptySample=new DivSample; ds.sample.push_back(emptySample); continue; } if (!reader.seek(insPtr[i]+0x4c,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 { logW("odd magic!"); ins->type=DIV_INS_ES5506; // read the instrument name anyway if (!reader.seek(insPtr[i]+48,SEEK_SET)) { logE("premature end of file!"); lastError="incomplete file"; delete ins; delete[] file; return false; } String name=reader.readStringLatin1(28); ins->name=name; ds.ins.push_back(ins); continue; } if (!reader.seek(insPtr[i],SEEK_SET)) { logE("premature end of file!"); lastError="incomplete file"; delete ins; delete[] file; return false; } unsigned char type=reader.readC(); if (ins->type==DIV_INS_ES5506) { if (type>1) { logE("invalid instrument type! %d",type); lastError="invalid instrument!"; delete ins; delete[] file; return false; } } else { if (type<2) { logE("invalid instrument type! %d",type); lastError="invalid instrument!"; delete ins; delete[] file; return false; } } String dosName=reader.readStringLatin1(12); if (ins->type==DIV_INS_ES5506) { unsigned int memSeg=0; memSeg=(unsigned char)reader.readC(); memSeg|=((unsigned short)reader.readS())<<8; memSeg>>=4; // ??? logV("memSeg: %x",memSeg); unsigned int length=reader.readI(); if (length>16777216) { logE("abnormal sample size! %x",reader.tell()); lastError="bad sample size"; delete[] file; return false; } DivSample* s=new DivSample; s->loopStart=reader.readI(); s->loopEnd=reader.readI(); defVol[i]=reader.readC(); reader.readC(); // x bool isPacked=reader.readC(); unsigned char flags=reader.readC(); s->centerRate=reader.readI(); // reserved reader.readI(); // x reader.readI(); reader.readI(); String name=reader.readStringLatin1(28); s->name=dosName; ins->name=name; // "SCRS" reader.readI(); // read sample data if (!reader.seek(memSeg,SEEK_SET)) { logE("premature end of file!"); lastError="incomplete file"; delete ins; delete s; delete[] file; return false; } s->loop=flags&1; s->depth=(flags&4)?DIV_SAMPLE_DEPTH_16BIT:DIV_SAMPLE_DEPTH_8BIT; if (length>0) { s->init(length); } if (isPacked) { logE("ADPCM not supported!"); lastError="ADPCM sample"; delete ins; delete s; delete[] file; return false; } if (flags&2) { // downmix stereo if (s->depth==DIV_SAMPLE_DEPTH_16BIT) { for (unsigned int i=0; isamples; i++) { short l=reader.readS(); if (!signedSamples) { l^=0x8000; } s->data16[i]=l; } for (unsigned int i=0; isamples; i++) { short r=reader.readS(); if (!signedSamples) { r^=0x8000; } s->data16[i]=(s->data16[i]+r)>>1; } } else { for (unsigned int i=0; isamples; i++) { signed char l=reader.readC(); if (!signedSamples) { l^=0x80; } s->data8[i]=l; } for (unsigned int i=0; isamples; i++) { signed char r=reader.readC(); if (!signedSamples) { r^=0x80; } s->data8[i]=(s->data8[i]+r)>>1; } } } else { if (s->depth==DIV_SAMPLE_DEPTH_16BIT) { for (unsigned int i=0; isamples; i++) { s->data16[i]=reader.readS(); if (!signedSamples) { s->data16[i]^=0x8000; } } } else { for (unsigned int i=0; isamples; i++) { s->data8[i]=reader.readC(); if (!signedSamples) { s->data8[i]^=0x80; } } } } ins->amiga.initSample=ds.sample.size(); ds.sample.push_back(s); } else { ins->fm.ops=2; // reserved reader.readC(); reader.readC(); reader.readC(); // OPL data sbi_t s3i; readSbiOpData(s3i,reader); // taken from S3I loading code DivInstrumentFM::Operator& opM=ins->fm.op[0]; DivInstrumentFM::Operator& opC=ins->fm.op[1]; ins->fm.ops = 2; opM.mult=s3i.Mcharacteristics&15; opM.ksr=((s3i.Mcharacteristics>>4)&1); opM.sus=((s3i.Mcharacteristics>>5)&1); opM.vib=((s3i.Mcharacteristics>>6)&1); opM.am=((s3i.Mcharacteristics>>7)&1); opM.tl=s3i.Mscaling_output&63; opM.ksl=((s3i.Mscaling_output>>6)&3); opM.ar=((s3i.Meg_AD >> 4)&15); opM.dr=(s3i.Meg_AD&15); opM.rr=(s3i.Meg_SR&15); opM.sl=((s3i.Meg_SR>>4)&15); opM.ws=s3i.Mwave; ins->fm.alg=(s3i.FeedConnect&1); ins->fm.fb=((s3i.FeedConnect>>1)&7); opC.mult=s3i.Ccharacteristics&15; opC.ksr=((s3i.Ccharacteristics>>4)&1); opC.sus=((s3i.Ccharacteristics>>5)&1); opC.vib=((s3i.Ccharacteristics>>6)&1); opC.am=((s3i.Ccharacteristics>>7)&1); opC.tl=s3i.Cscaling_output&63; opC.ksl=((s3i.Cscaling_output>>6)&3); opC.ar=((s3i.Ceg_AD>>4)&15); opC.dr=(s3i.Ceg_AD&15); opC.rr=(s3i.Ceg_SR&15); opC.sl=((s3i.Ceg_SR>>4)&15); opC.ws=s3i.Cwave; // unused reader.readC(); defVol[i]=reader.readC(); // what? unsigned char dsk=reader.readC(); // x reader.readS(); // C-2 speed - convert to macro int centerRate=reader.readI(); double centerNote=12.0*log2((double)centerRate/8363.0); if (round(centerNote)!=0) { ins->std.arpMacro.len=1; ins->std.arpMacro.val[0]=round(centerNote); } logV("centerRate: %d (%f)",centerRate,centerNote); // x reader.seek(12,SEEK_CUR); String name=reader.readStringLatin1(28); ins->name=name; // "SCRI" reader.readI(); logV("dsk: %d",dsk); } ds.ins.push_back(ins); } ds.sampleLen=ds.sample.size(); // scan pattern data for effect use for (int i=0; i=64) break; continue; } unsigned char chan=what&31; bool hasNoteIns=what&32; bool hasVol=what&64; bool hasEffect=what&128; if (hasNoteIns) { reader.readC(); reader.readC(); } if (hasVol) { reader.readC(); } if (hasEffect) { unsigned char effect=reader.readC(); reader.readC(); // effect val switch (effect+'A'-1) { case 'D': // vol slide doesVolSlide[chan]=true; break; case 'E': // pitch down doesPitchSlide[chan]=true; break; case 'F': // pitch up doesPitchSlide[chan]=true; break; case 'G': // porta doesPitchSlide[chan]=true; break; case 'H': // vibrato doesVibrato[chan]=true; break; case 'J': // arp doesArp[chan]=true; break; case 'K': // vol slide + vibrato doesVolSlide[chan]=true; doesVibrato[chan]=true; break; case 'L': // vol slide + porta doesVolSlide[chan]=true; doesPitchSlide[chan]=true; break; case 'P': // pan slide doesPanSlide[chan]=true; break; case 'R': // tremolo doesTremolo[chan]=true; break; case 'Y': // panbrello doesPanbrello[chan]=true; break; } } } } // load pattern data for (int i=0; ipat[chanMap[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]>=4+8*2) { logE("oh crap!"); } if ((effectCol[j]>>1)-2>ds.subsong[0]->pat[j].effectCols) { ds.subsong[0]->pat[chanMap[j]].effectCols=(effectCol[j]>>1)-1; } } readRow++; memset(effectCol,4,32); memcpy(vibingOld,vibing,32*sizeof(bool)); memcpy(volSlidingOld,volSliding,32*sizeof(bool)); memcpy(portingOld,porting,32*sizeof(bool)); memcpy(arpingOld,arping,32*sizeof(bool)); memset(vibStatusChanged,0,32*sizeof(bool)); memset(volSlideStatusChanged,0,32*sizeof(bool)); memset(portaStatusChanged,0,32*sizeof(bool)); memset(arpStatusChanged,0,32*sizeof(bool)); memset(vibing,0,32*sizeof(bool)); memset(volSliding,0,32*sizeof(bool)); memset(porting,0,32*sizeof(bool)); memset(arping,0,32*sizeof(bool)); memset(did,0,32); mustCommitInitial=false; if (readRow>=64) break; continue; } unsigned char chan=what&31; bool hasNoteIns=what&32; bool hasVol=what&64; bool hasEffect=what&128; if (did[chan]) { logW("pat %d chan %d row %d: we already populated this channel!",i,chan,readRow); } else { did[chan]=true; } DivPattern* p=ds.subsong[0]->pat[chanMap[chan]].getPattern(i,true); if (hasNoteIns) { unsigned char note=reader.readC(); unsigned char ins=reader.readC(); if (note==254) { // note off p->data[readRow][0]=100; p->data[readRow][1]=0; } else if (note!=255) { p->data[readRow][0]=note&15; p->data[readRow][1]=note>>4; if ((note&15)==0) { p->data[readRow][0]=12; p->data[readRow][1]--; } } p->data[readRow][2]=(short)ins-1; } if (hasVol) { unsigned char vol=reader.readC(); if (vol==255) { p->data[readRow][3]=-1; } else { // check for OPL channel if ((chanSettings[chan]&31)>=16) { if (vol>63) vol=63; } else { if (vol>64) vol=64; } p->data[readRow][3]=vol; } } else if (p->data[readRow][2]!=-1) { // populate with instrument volume unsigned char vol=defVol[p->data[readRow][2]&255]; if ((chanSettings[chan]&31)>=16) { if (vol>63) vol=63; } else { if (vol>64) vol=64; } p->data[readRow][3]=vol; } if (hasEffect) { unsigned char effect=reader.readC(); unsigned char effectVal=reader.readC(); switch (effect+'A'-1) { case 'A': // speed p->data[readRow][effectCol[chan]++]=0x0f; p->data[readRow][effectCol[chan]++]=effectVal; break; case 'B': // go to order p->data[readRow][effectCol[chan]++]=0x0b; logD("0B: %x %x",effectVal,orders[effectVal]); p->data[readRow][effectCol[chan]++]=orders[effectVal]; break; case 'C': // next order p->data[readRow][effectCol[chan]++]=0x0d; p->data[readRow][effectCol[chan]++]=(effectVal>>4)*10+(effectVal&15); break; case 'D': // vol slide if (effectVal!=0) { volSlideStatus[chan]=effectVal; volSlideStatusChanged[chan]=true; } if (hasNoteIns) { volSlideStatusChanged[chan]=true; } volSliding[chan]=true; break; case 'E': // pitch down if (effectVal!=0) { portaStatus[chan]=effectVal; portaStatusChanged[chan]=true; } if (hasNoteIns) { portaStatusChanged[chan]=true; } if (portaType[chan]!=2) { portaStatusChanged[chan]=true; } portaType[chan]=2; porting[chan]=true; break; case 'F': // pitch up if (effectVal!=0) { portaStatus[chan]=effectVal; portaStatusChanged[chan]=true; } if (hasNoteIns) { portaStatusChanged[chan]=true; } if (portaType[chan]!=1) { portaStatusChanged[chan]=true; } portaType[chan]=1; porting[chan]=true; break; case 'G': // porta if (effectVal!=0) { portaStatus[chan]=effectVal; portaStatusChanged[chan]=true; } if (hasNoteIns) { portaStatusChanged[chan]=true; } if (portaType[chan]!=3) { portaStatusChanged[chan]=true; } portaType[chan]=3; porting[chan]=true; break; case 'H': // vibrato if (effectVal!=0) { vibStatus[chan]=effectVal; vibStatusChanged[chan]=true; } vibing[chan]=true; break; case 'I': // tremor (!) break; case 'J': // arp if (effectVal!=0) { arpStatus[chan]=effectVal; arpStatusChanged[chan]=true; } arping[chan]=true; break; case 'K': // vol slide + vibrato if (effectVal!=0) { volSlideStatus[chan]=effectVal; volSlideStatusChanged[chan]=true; } volSliding[chan]=true; vibing[chan]=true; break; case 'L': // vol slide + porta if (effectVal!=0) { volSlideStatus[chan]=effectVal; volSlideStatusChanged[chan]=true; } volSliding[chan]=true; porting[chan]=true; portaType[chan]=3; break; case 'M': // channel vol (extension) break; case 'N': // channel vol slide (extension) break; case 'O': // offset p->data[readRow][effectCol[chan]++]=0x91; p->data[readRow][effectCol[chan]++]=effectVal; break; case 'P': // pan slide (extension) if (effectVal!=0) { panSlideStatus[chan]=effectVal; panSlideStatusChanged[chan]=true; } panSliding[chan]=true; break; case 'Q': // retrigger p->data[readRow][effectCol[chan]++]=0x0c; p->data[readRow][effectCol[chan]++]=effectVal&15; break; case 'R': // tremolo if (effectVal!=0) { tremStatus[chan]=effectVal; tremStatusChanged[chan]=true; } treming[chan]=true; break; case 'S': // special... switch (effectVal>>4) { case 0x8: p->data[readRow][effectCol[chan]++]=0x80; p->data[readRow][effectCol[chan]++]=(effectVal&15)<<4; break; case 0xc: p->data[readRow][effectCol[chan]++]=0xec; p->data[readRow][effectCol[chan]++]=effectVal&15; break; case 0xd: p->data[readRow][effectCol[chan]++]=0xed; p->data[readRow][effectCol[chan]++]=effectVal&15; break; } break; case 'T': // tempo p->data[readRow][effectCol[chan]++]=0xf0; p->data[readRow][effectCol[chan]++]=effectVal; break; case 'U': // fine vibrato if (effectVal!=0) { vibStatus[chan]=effectVal; vibStatusChanged[chan]=true; } vibing[chan]=true; break; case 'V': // global volume (!) break; case 'W': // global volume slide (!) break; case 'X': // panning (extension) if (effectVal<=0x80) { p->data[readRow][effectCol[chan]++]=0x80; p->data[readRow][effectCol[chan]++]=(effectVal&0x80)?0xff:(effectVal<<1); } break; case 'Y': // panbrello (extension) if (effectVal!=0) { panStatus[chan]=effectVal; panStatusChanged[chan]=true; } panning[chan]=true; break; case 'Z': // MIDI macro (extension) break; } } } } // copy patterns to the rest of subsongs for (size_t i=1; ipat[j].data[k]) ds.subsong[0]->pat[j].data[k]->copyOn(ds.subsong[i]->pat[j].getPattern(k,true)); } ds.subsong[i]->pat[j].effectCols=ds.subsong[0]->pat[j].effectCols; } ds.subsong[i]->speeds=ds.subsong[0]->speeds; ds.subsong[i]->hz=ds.subsong[0]->hz; 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)); } // find subsongs ds.findSubSongs(DIV_MAX_CHANS); // populate subsongs with default panning values if (masterVol&128) { // only in stereo mode for (size_t i=0; ipat[chanMap[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) { p->data[0][4+(k<<1)]=0x80; if (chanPan[j]&16) { p->data[0][5+(k<<1)]=(j&1)?0xcc:0x33; } else { p->data[0][5+(k<<1)]=(chanPan[j]&15)|((chanPan[j]&15)<<4); } if (ds.subsong[i]->pat[chanMap[j]].effectCols<=k) ds.subsong[i]->pat[chanMap[j]].effectCols=k+1; break; } } } } } ds.insLen=ds.ins.size(); ds.sampleLen=ds.sample.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 header!"); lastError="invalid header!"; } return success; }