diff --git a/papers/export-tech.md b/papers/export-tech.md index 95dd7c76..ef569f93 100644 --- a/papers/export-tech.md +++ b/papers/export-tech.md @@ -69,10 +69,13 @@ hex | description .. | ... ef | preset delay 15 ----|------------------------------------ + f4 | call symbol (16-bit index follows; only used internally) + f5 | jump to sub-block (address follows) + f6 | go to sub-block (32-bit offset follows) f7 | full command (command and data follows) - f8 | go to sub-block (offset follows) + f8 | go to sub-block (16-bit offset follows) f9 | return from sub-block - fa | jump (offset follows) + fa | jump (address follows) fb | set tick rate (4 bytes) fc | wait (16-bit) fd | wait (8-bit) diff --git a/src/engine/cmdStream.cpp b/src/engine/cmdStream.cpp index 4ef2c5e2..05f5326e 100644 --- a/src/engine/cmdStream.cpp +++ b/src/engine/cmdStream.cpp @@ -21,6 +21,18 @@ #include "engine.h" #include "../ta-log.h" +bool DivCSChannelState::doCall(unsigned int addr) { + if (callStackPos>=8) { + readPos=0; + return false; + } + + callStack[callStackPos++]=readPos; + readPos=addr; + + return true; +} + void DivCSPlayer::cleanup() { delete b; } @@ -29,18 +41,25 @@ bool DivCSPlayer::tick() { bool ticked=false; for (int i=0; igetTotalChannelCount(); i++) { bool sendVolume=false; + bool sendPitch=false; if (chan[i].readPos==0) continue; ticked=true; chan[i].waitTicks--; while (chan[i].waitTicks<=0) { - stream.seek(chan[i].readPos,SEEK_SET); + if (!stream.seek(chan[i].readPos,SEEK_SET)) { + logE("%d: access violation! $%x",i,chan[i].readPos); + chan[i].readPos=0; + break; + } unsigned char next=stream.readC(); unsigned char command=0; if (next<0xb3) { // note - e->dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,next-60)); + e->dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,(int)next-60)); + logV("%d: note on (%d)",i,(int)next-60); + chan[i].vibratoPos=0; } else if (next>=0xd0 && next<=0xdf) { command=fastCmds[next&15]; } else if (next>=0xe0 && next<=0xef) { // preset delay @@ -48,6 +67,7 @@ bool DivCSPlayer::tick() { } else switch (next) { case 0xb4: // note on null e->dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,DIV_NOTE_NULL)); + chan[i].vibratoPos=0; break; case 0xb5: // note off e->dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF,i)); @@ -66,17 +86,46 @@ bool DivCSPlayer::tick() { case 0xf7: command=stream.readC(); break; - case 0xf8: - logE("TODO: CALL"); + case 0xf8: { + unsigned int callAddr=chan[i].readPos+2+stream.readS(); + if (!chan[i].doCall(callAddr)) { + logE("%d: (callb16) stack error!",i); + } break; + } + case 0xf6: { + unsigned int callAddr=chan[i].readPos+4+stream.readI(); + if (!chan[i].doCall(callAddr)) { + logE("%d: (callb32) stack error!",i); + } + break; + } + case 0xf5: { + unsigned int callAddr=stream.readI(); + if (!chan[i].doCall(callAddr)) { + logE("%d: (call) stack error!",i); + } + break; + } + case 0xf4: { + logE("%d: (callsym) not supported here!",i); + chan[i].readPos=0; + break; + } case 0xf9: - logE("TODO: RET"); + if (!chan[i].callStackPos) { + logE("%d: (ret) stack error!",i); + chan[i].readPos=0; + break; + } + chan[i].readPos=chan[i].callStack[--chan[i].callStackPos]; break; case 0xfa: - logE("TODO: JMP"); + chan[i].readPos=stream.readI(); break; case 0xfb: logE("TODO: RATE"); + stream.readI(); break; case 0xfc: chan[i].waitTicks=(unsigned short)stream.readS(); @@ -88,6 +137,11 @@ bool DivCSPlayer::tick() { chan[i].waitTicks=1; break; case 0xff: + chan[i].readPos=0; + logI("%d: stop",i); + break; + default: + logE("%d: illegal instruction $%.2x! $%.x",i,next,chan[i].readPos); chan[i].readPos=0; break; } @@ -101,15 +155,17 @@ bool DivCSPlayer::tick() { case DIV_CMD_INSTRUMENT: case DIV_CMD_HINT_VIBRATO_RANGE: case DIV_CMD_HINT_VIBRATO_SHAPE: - case DIV_CMD_HINT_PITCH: case DIV_CMD_HINT_VOLUME: arg0=(unsigned char)stream.readC(); break; + case DIV_CMD_HINT_PITCH: + arg0=(signed char)stream.readC(); + break; case DIV_CMD_PANNING: case DIV_CMD_HINT_VIBRATO: case DIV_CMD_HINT_ARPEGGIO: case DIV_CMD_HINT_PORTA: - arg0=(unsigned char)stream.readC(); + arg0=(signed char)stream.readC(); arg1=(unsigned char)stream.readC(); break; case DIV_CMD_PRE_PORTA: @@ -120,6 +176,14 @@ bool DivCSPlayer::tick() { case DIV_CMD_HINT_VOL_SLIDE: arg0=(short)stream.readS(); break; + case DIV_CMD_HINT_LEGATO: + arg0=(unsigned char)stream.readC(); + if (arg0==0xff) { + arg0=DIV_NOTE_NULL; + } else { + arg0-=60; + } + break; case DIV_CMD_SAMPLE_MODE: case DIV_CMD_SAMPLE_FREQ: case DIV_CMD_SAMPLE_BANK: @@ -218,6 +282,23 @@ bool DivCSPlayer::tick() { case DIV_CMD_HINT_VOL_SLIDE: chan[i].volSpeed=arg0; break; + case DIV_CMD_HINT_PITCH: + chan[i].pitch=arg0; + sendPitch=true; + break; + case DIV_CMD_HINT_VIBRATO: + chan[i].vibratoDepth=arg0; + chan[i].vibratoRate=arg1; + sendPitch=true; + break; + case DIV_CMD_HINT_PORTA: + chan[i].portaTarget=arg0; + chan[i].portaSpeed=arg1; + break; + case DIV_CMD_HINT_LEGATO: + chan[i].note=arg0; + e->dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); + break; default: // dispatch it e->dispatchCmd(DivCommand((DivDispatchCmds)command,i,arg0,arg1)); break; @@ -238,6 +319,18 @@ bool DivCSPlayer::tick() { e->dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); } + + if (sendPitch || chan[i].vibratoDepth!=0) { + if (chan[i].vibratoDepth>0) { + chan[i].vibratoPos+=chan[i].vibratoRate; + if (chan[i].vibratoPos>=64) chan[i].vibratoPos-=64; + } + e->dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(vibTable[chan[i].vibratoPos&63]*chan[i].vibratoDepth)/15)); + } + + if (chan[i].portaSpeed) { + e->dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed*(e->song.linearPitch==2?e->song.pitchSlideSpeed:1),chan[i].portaTarget)); + } } return ticked; @@ -273,6 +366,10 @@ bool DivCSPlayer::init() { chan[i].volume=chan[i].volMax; } + for (int i=0; i<64; i++) { + vibTable[i]=127*sin(((double)i/64.0)*(2*M_PI)); + } + return true; } diff --git a/src/engine/cmdStream.h b/src/engine/cmdStream.h index e12b0599..ac25077b 100644 --- a/src/engine/cmdStream.h +++ b/src/engine/cmdStream.h @@ -29,14 +29,41 @@ struct DivCSChannelState { unsigned int readPos; int waitTicks; + int note, pitch; int volume, volMax, volSpeed; + int vibratoDepth, vibratoRate, vibratoPos; + int portaTarget, portaSpeed; + unsigned char arp, arpStage, arpTicks; + + unsigned int callStack[8]; + unsigned char callStackPos; + + struct TraceEntry { + unsigned int addr; + unsigned char length; + unsigned char data[11]; + } trace[32]; + unsigned char tracePos; + + bool doCall(unsigned int addr); DivCSChannelState(): readPos(0), waitTicks(0), + note(-1), + pitch(0), volume(0x7f00), volMax(0), - volSpeed(0) {} + volSpeed(0), + vibratoDepth(0), + vibratoRate(0), + vibratoPos(0), + portaTarget(0), + portaSpeed(0), + arp(0), + arpStage(0), + arpTicks(0), + callStackPos(0) {} }; class DivCSPlayer { @@ -46,6 +73,8 @@ class DivCSPlayer { DivCSChannelState chan[DIV_MAX_CHANS]; unsigned char fastDelays[16]; unsigned char fastCmds[16]; + + short vibTable[64]; public: void cleanup(); bool tick(); diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index c77afaee..e9352bf9 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -510,13 +510,13 @@ void DivEngine::processRow(int i, bool afterDelay) { if (chan[i].stopOnOff) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; - dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); chan[i].stopOnOff=false; } if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; - dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); /*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) { chan[i+1].portaNote=-1; chan[i+1].portaSpeed=-1; @@ -533,13 +533,13 @@ void DivEngine::processRow(int i, bool afterDelay) { if (chan[i].stopOnOff) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; - dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); chan[i].stopOnOff=false; } if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; - dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); /*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) { chan[i+1].portaNote=-1; chan[i+1].portaSpeed=-1; @@ -632,13 +632,13 @@ void DivEngine::processRow(int i, bool afterDelay) { if (effectVal==0) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; - dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); chan[i].inPorta=false; if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); } else { chan[i].portaNote=song.limitSlides?0x60:255; chan[i].portaSpeed=effectVal; - dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); chan[i].portaStop=true; chan[i].nowYouCanStop=false; chan[i].stopOnOff=false; @@ -654,13 +654,13 @@ void DivEngine::processRow(int i, bool afterDelay) { if (effectVal==0) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; - dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); chan[i].inPorta=false; if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); } else { chan[i].portaNote=song.limitSlides?disCont[dispatchOfChan[i]].dispatch->getPortaFloor(dispatchChanOfChan[i]):-60; chan[i].portaSpeed=effectVal; - dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); chan[i].portaStop=true; chan[i].nowYouCanStop=false; chan[i].stopOnOff=false; @@ -674,7 +674,7 @@ void DivEngine::processRow(int i, bool afterDelay) { if (effectVal==0) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; - dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); chan[i].inPorta=false; dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); } else { @@ -688,7 +688,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].inPorta=true; chan[i].wasShorthandPorta=false; } - dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); chan[i].portaStop=true; if (chan[i].keyOn) chan[i].doNote=false; chan[i].stopOnOff=song.stopPortaOnNoteOff; // what?! @@ -770,7 +770,7 @@ void DivEngine::processRow(int i, bool afterDelay) { case 0xe1: // portamento up chan[i].portaNote=chan[i].note+(effectVal&15); chan[i].portaSpeed=(effectVal>>4)*4; - dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); chan[i].portaStop=true; chan[i].nowYouCanStop=false; chan[i].stopOnOff=song.stopPortaOnNoteOff; // what?! @@ -789,7 +789,7 @@ void DivEngine::processRow(int i, bool afterDelay) { case 0xe2: // portamento down chan[i].portaNote=chan[i].note-(effectVal&15); chan[i].portaSpeed=(effectVal>>4)*4; - dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); chan[i].portaStop=true; chan[i].nowYouCanStop=false; chan[i].stopOnOff=song.stopPortaOnNoteOff; // what?! @@ -947,13 +947,13 @@ void DivEngine::processRow(int i, bool afterDelay) { if (chan[i].inPorta && chan[i].keyOn && !chan[i].shorthandPorta) { if (song.e1e2StopOnSameNote && chan[i].wasShorthandPorta) { chan[i].portaSpeed=-1; - dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); if (!song.brokenShortcutSlides) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); chan[i].wasShorthandPorta=false; chan[i].inPorta=false; } else { chan[i].portaNote=chan[i].note; - dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); } } else if (!chan[i].noteOnInhibit) { dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,chan[i].note,chan[i].volume>>8)); @@ -966,7 +966,7 @@ void DivEngine::processRow(int i, bool afterDelay) { if (!chan[i].keyOn && chan[i].scheduledSlideReset) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; - dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); chan[i].scheduledSlideReset=false; chan[i].inPorta=false; } @@ -1311,7 +1311,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { if ((chan[i].keyOn || chan[i].keyOff) && chan[i].portaSpeed>0) { if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed*(song.linearPitch==2?song.pitchSlideSpeed:1),chan[i].portaNote))==2 && chan[i].portaStop && song.targetResetsSlides) { chan[i].portaSpeed=0; - dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); chan[i].oldNote=chan[i].note; chan[i].note=chan[i].portaNote; chan[i].inPorta=false; @@ -1330,13 +1330,13 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { if (chan[i].stopOnOff) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; - dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); chan[i].stopOnOff=false; } if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; - dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed)); + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); /*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) { chan[i+1].portaNote=-1; chan[i+1].portaSpeed=-1;