diff --git a/papers/export-tech.md b/papers/export-tech.md index 9f4a7242..95dd7c76 100644 --- a/papers/export-tech.md +++ b/papers/export-tech.md @@ -21,20 +21,62 @@ size | description 4 | "FCS\0" format magic 4 | channel count 4?? | pointers to channel data - 2?? | preset delays + 1?? | preset delays | - 16 values 1?? | speed dial commands | - 16 values + ??? | channel data ``` -read channel, command and values. - -if channel is 80 or higher, then it is a special command: +read command and values (if any). +the list of commands follows. ``` -fb xx xx xx xx: set tick rate -fc xx xx: wait xxxx ticks -fd xx: wait xx ticks -fe: wait one tick -ff: stop +hex | description +----|------------------------------------ + 00 | note on: C-(-5) + 01 | note on: C#(-5) + 02 | note on: D-(-5) + .. | ... + b1 | note on: A-9 + b2 | note on: A#9 + b3 | note on: B-9 + b4 | note on: null +----|------------------------------------ + b5 | note off + b6 | note off env + b7 | env release + b8 | instrument // (ins, force) + be | panning // (left, right) + c0 | pre porta // (inporta, isportaorslide) + c2 | vibrato // (speed, depth) + c3 | vibrato range // (range) + c4 | vibrato shape // (shape) + c5 | pitch // (pitch) + c6 | arpeggio // (note1, note2) + c7 | volume // (vol) + c8 | vol slide // (amount, onetick) + c9 | porta // (target, speed) + ca | legato // (note) +----|------------------------------------ + d0 | speed dial command 0 + d1 | speed dial command 1 + .. | ... + df | speed dial command 15 +----|------------------------------------ + e0 | preset delay 0 + e1 | preset delay 1 + .. | ... + ef | preset delay 15 +----|------------------------------------ + f7 | full command (command and data follows) + f8 | go to sub-block (offset follows) + f9 | return from sub-block + fa | jump (offset follows) + fb | set tick rate (4 bytes) + fc | wait (16-bit) + fd | wait (8-bit) + fe | wait one tick + ff | stop ``` + diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 433bdc2d..c813c4a0 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -282,9 +282,10 @@ double DivEngine::benchmarkSeek() { chanStream[x]->writeC(0xfc); \ chanStream[x]->writeS(tick-lastTick[x]); \ } else if (tick-lastTick[x]>1) { \ + delayPopularity[tick-lastTick[x]]++; \ chanStream[x]->writeC(0xfd); \ chanStream[x]->writeC(tick-lastTick[x]); \ - } else { \ + } else if (tick-lastTick[x]>0) { \ chanStream[x]->writeC(0xfe); \ } \ lastTick[x]=tick; \ @@ -297,9 +298,37 @@ double DivEngine::benchmarkSeek() { } void writePackedCommandValues(SafeWriter* w, const DivCommand& c) { - w->writeC(c.cmd); switch (c.cmd) { case DIV_CMD_NOTE_ON: + if (c.value==DIV_NOTE_NULL) { + w->writeC(0xb4); + } else { + w->writeC(CLAMP(c.value+60,0,0xb3)); + } + break; + case DIV_CMD_NOTE_OFF: + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + case DIV_CMD_INSTRUMENT: + case DIV_CMD_PANNING: + case DIV_CMD_PRE_PORTA: + case DIV_CMD_HINT_VIBRATO: + case DIV_CMD_HINT_VIBRATO_RANGE: + case DIV_CMD_HINT_VIBRATO_SHAPE: + case DIV_CMD_HINT_PITCH: + case DIV_CMD_HINT_ARPEGGIO: + case DIV_CMD_HINT_VOLUME: + case DIV_CMD_HINT_PORTA: + case DIV_CMD_HINT_VOL_SLIDE: + case DIV_CMD_HINT_LEGATO: + w->writeC((unsigned char)c.cmd+0xb4); + break; + default: + w->writeC(0xf0); // unoptimized extended command + w->writeC(c.cmd); + break; + } + switch (c.cmd) { case DIV_CMD_HINT_LEGATO: if (c.value==DIV_NOTE_NULL) { w->writeC(0xff); @@ -307,6 +336,7 @@ void writePackedCommandValues(SafeWriter* w, const DivCommand& c) { w->writeC(c.value+60); } break; + case DIV_CMD_NOTE_ON: case DIV_CMD_NOTE_OFF: case DIV_CMD_NOTE_OFF_ENV: case DIV_CMD_ENV_RELEASE: @@ -316,6 +346,21 @@ void writePackedCommandValues(SafeWriter* w, const DivCommand& c) { case DIV_CMD_HINT_VIBRATO_SHAPE: case DIV_CMD_HINT_PITCH: case DIV_CMD_HINT_VOLUME: + w->writeC(c.value); + break; + case DIV_CMD_PANNING: + case DIV_CMD_HINT_VIBRATO: + case DIV_CMD_HINT_ARPEGGIO: + case DIV_CMD_HINT_PORTA: + w->writeC(c.value); + w->writeC(c.value2); + break; + case DIV_CMD_PRE_PORTA: + w->writeC((c.value?0x80:0)|(c.value2?0x40:0)); + break; + case DIV_CMD_HINT_VOL_SLIDE: + w->writeS(c.value); + break; case DIV_CMD_SAMPLE_MODE: case DIV_CMD_SAMPLE_FREQ: case DIV_CMD_SAMPLE_BANK: @@ -351,12 +396,9 @@ void writePackedCommandValues(SafeWriter* w, const DivCommand& c) { case DIV_CMD_AY_NOISE_MASK_AND: case DIV_CMD_AY_NOISE_MASK_OR: case DIV_CMD_AY_AUTO_ENVELOPE: + w->writeC(1); // length w->writeC(c.value); break; - case DIV_CMD_PANNING: - case DIV_CMD_HINT_VIBRATO: - case DIV_CMD_HINT_ARPEGGIO: - case DIV_CMD_HINT_PORTA: case DIV_CMD_FM_TL: case DIV_CMD_FM_AM: case DIV_CMD_FM_AR: @@ -378,25 +420,26 @@ void writePackedCommandValues(SafeWriter* w, const DivCommand& c) { case DIV_CMD_FM_FINE: case DIV_CMD_AY_IO_WRITE: case DIV_CMD_AY_AUTO_PWM: + w->writeC(2); // length w->writeC(c.value); w->writeC(c.value2); break; - case DIV_CMD_PRE_PORTA: - w->writeC((c.value?0x80:0)|(c.value2?0x40:0)); - break; - case DIV_CMD_HINT_VOL_SLIDE: case DIV_CMD_C64_FINE_DUTY: case DIV_CMD_C64_FINE_CUTOFF: + w->writeC(2); // length w->writeS(c.value); break; case DIV_CMD_FM_FIXFREQ: + w->writeC(2); // length w->writeS((c.value<<12)|(c.value2&0x7ff)); break; case DIV_CMD_NES_SWEEP: + w->writeC(1); // length w->writeC((c.value?8:0)|(c.value2&0x77)); break; default: logW("unimplemented command %s!",cmdName[c.cmd]); + w->writeC(0); // length break; } } @@ -413,13 +456,27 @@ SafeWriter* DivEngine::saveCommand(bool binary) { int loopEnd=0; walkSong(loopOrder,loopRow,loopEnd); logI("loop point: %d %d",loopOrder,loopRow); + + int cmdPopularity[256]; + int delayPopularity[256]; + + int sortedCmdPopularity[16]; + int sortedDelayPopularity[16]; + unsigned char sortedCmd[16]; + unsigned char sortedDelay[16]; SafeWriter* chanStream[DIV_MAX_CHANS]; unsigned int chanStreamOff[DIV_MAX_CHANS]; bool wroteTick[DIV_MAX_CHANS]; + memset(cmdPopularity,0,256*sizeof(int)); + memset(delayPopularity,0,256*sizeof(int)); memset(chanStream,0,DIV_MAX_CHANS*sizeof(void*)); memset(chanStreamOff,0,DIV_MAX_CHANS*sizeof(unsigned int)); + memset(sortedCmdPopularity,0,16*sizeof(int)); + memset(sortedDelayPopularity,0,16*sizeof(int)); + memset(sortedCmd,0,16); + memset(sortedDelay,0,16); SafeWriter* w=new SafeWriter; w->init(); @@ -434,6 +491,10 @@ SafeWriter* DivEngine::saveCommand(bool binary) { chanStream[i]->init(); w->writeI(0); } + // preset delays and speed dial + for (int i=0; i<32; i++) { + w->writeC(0); + } } else { w->writeText("# Furnace Command Stream\n\n"); @@ -508,6 +569,7 @@ SafeWriter* DivEngine::saveCommand(bool binary) { default: WRITE_TICK(i.chan); if (binary) { + cmdPopularity[i.cmd]++; writePackedCommandValues(chanStream[i.chan],i); } else { w->writeText(fmt::sprintf(" %d: %s %d %d\n",i.chan,cmdName[i.cmd],i.value,i.value2)); @@ -521,9 +583,162 @@ SafeWriter* DivEngine::saveCommand(bool binary) { cmdStreamEnabled=oldCmdStreamEnabled; if (binary) { + int sortCand=-1; + int sortPos=0; + while (sortPos<16) { + sortCand=-1; + for (int i=DIV_CMD_SAMPLE_MODE; i<256; i++) { + if (cmdPopularity[i]) { + if (sortCand==-1) { + sortCand=i; + } else if (cmdPopularity[sortCand]writeC(0xff); + // optimize stream + SafeWriter* oldStream=chanStream[i]; + SafeReader* reader=oldStream->toReader(); + chanStream[i]=new SafeWriter; + chanStream[i]->init(); + while (1) { + try { + unsigned char next=reader->readC(); + switch (next) { + case 0xb8: // instrument + case 0xc0: // pre porta + case 0xc3: // vibrato range + case 0xc4: // vibrato shape + case 0xc5: // pitch + case 0xc7: // volume + case 0xca: // legato + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + break; + case 0xbe: // panning + case 0xc2: // vibrato + case 0xc6: // arpeggio + case 0xc8: // vol slide + case 0xc9: // porta + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + break; + case 0xf0: { // full command (pre) + unsigned char cmd=reader->readC(); + bool foundShort=false; + for (int j=0; j<16; j++) { + if (sortedCmd[j]==cmd) { + chanStream[i]->writeC(0xd0+j); + foundShort=true; + break; + } + } + if (!foundShort) { + chanStream[i]->writeC(0xf7); // full command + chanStream[i]->writeC(cmd); + } + + unsigned char cmdLen=reader->readC(); + logD("cmdLen: %d",cmdLen); + for (unsigned char j=0; jreadC(); + chanStream[i]->writeC(next); + } + break; + } + case 0xfb: // tick rate + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + break; + case 0xfc: { // 16-bit wait + unsigned short delay=reader->readS(); + bool foundShort=false; + for (int j=0; j<16; j++) { + if (sortedDelay[j]==delay) { + chanStream[i]->writeC(0xe0+j); + foundShort=true; + break; + } + } + if (!foundShort) { + chanStream[i]->writeC(next); + chanStream[i]->writeS(delay); + } + break; + } + case 0xfd: { // 8-bit wait + unsigned char delay=reader->readC(); + bool foundShort=false; + for (int j=0; j<16; j++) { + if (sortedDelay[j]==delay) { + chanStream[i]->writeC(0xe0+j); + foundShort=true; + break; + } + } + if (!foundShort) { + chanStream[i]->writeC(next); + chanStream[i]->writeC(delay); + } + break; + } + default: + chanStream[i]->writeC(next); + break; + } + } catch (EndOfFileException& e) { + break; + } + } + + oldStream->finish(); + delete oldStream; + } + + for (int i=0; itell(); logI("- %d: off %x size %ld",i,chanStreamOff[i],chanStream[i]->size()); w->write(chanStream[i]->getFinalBuf(),chanStream[i]->size()); @@ -535,6 +750,18 @@ SafeWriter* DivEngine::saveCommand(bool binary) { for (int i=0; iwriteI(chanStreamOff[i]); } + + logD("delay popularity:"); + for (int i=0; i<16; i++) { + w->writeC(sortedDelay[i]); + if (sortedDelayPopularity[i]) logD("- %d: %d",sortedDelay[i],sortedDelayPopularity[i]); + } + + logD("command popularity:"); + for (int i=0; i<16; i++) { + w->writeC(sortedCmd[i]); + if (sortedCmdPopularity[i]) logD("- %s: %d",cmdName[sortedCmd[i]],sortedCmdPopularity[i]); + } } else { if (!playing) { w->writeText(">> END\n");