Merge branch 'newpitch'

This commit is contained in:
tildearrow 2022-04-24 14:41:12 -05:00
commit bb4bc01778
12 changed files with 118 additions and 107 deletions

View file

@ -49,12 +49,27 @@ the coding style is described here:
- `size_t` are 32-bit or 64-bit, depending on architecture.
- in float/double operations, always use decimal and `f` if single-precision.
- e.g. `1.0f` or `1.0` instead of `1`.
- prefer `NULL` over `nullptr` or any other proprietary null.
- don't use `auto` unless needed.
- use `String` for `std::string` (this is typedef'd in ta-utils.h).
- prefer using operator for String (std::string) comparisons (a=="").
some files (particularly the ones in `src/engine/platform/sound` and `extern/`) don't follow this style.
you don't have to follow this style. I will fix it after I accept your contribution.
additional guidelines:
- in general **strongly** avoid breaking compatibility.
- do not touch loadFur/saveFur unless you know what you're doing!
- new fields must be at the end of each block to ensure forward compatibility
- likewise, the instrument read/write functions in DivInstrument have to be handled carefully
- any change to the format requires a version bump (see `src/engine/engine.h`).
- do not bump the version number under any circumstances!
- if you are making major changes to the playback routine, make sure to test with older songs to ensure nothing breaks.
- I will run a test suite to make sure this is the case.
- if something breaks, you might want to add a compatibility flag (this requires changing the format though).
## Demo Songs
just put your demo song in `demos/`!

View file

@ -29,6 +29,8 @@ furthermore, an `or reserved` indicates this field is always present, but is res
the format versions are:
- 85: Furnace dev85
- 84: Furnace dev84
- 83: Furnace dev83
- 82: Furnace dev82
- 81: Furnace dev81
@ -269,7 +271,9 @@ size | description
1 | ExtCh channel state is shared (>=78) or reserved
1 | ignore DAC mode change outside of intended channel (>=83) or reserved
1 | E1xx and E2xx also take priority over Slide00 (>=83) or reserved
23 | reserved
1 | new Sega PCM (with macros and proper vol/pan) (>=84) or reserved
1 | weird f-num/block-based chip pitch slides (>=85) or reserved
21 | reserved
```
# instrument

View file

@ -426,6 +426,7 @@ class DivDispatch {
#define NOTE_PERIODIC(x) round(parent->calcBaseFreq(chipClock,CHIP_DIVIDER,x,true))
#define NOTE_PERIODIC_NOROUND(x) parent->calcBaseFreq(chipClock,CHIP_DIVIDER,x,true)
#define NOTE_FREQUENCY(x) parent->calcBaseFreq(chipClock,CHIP_FREQBASE,x,false)
#define NOTE_FNUM_BLOCK(x,bits) ((((int)parent->calcBaseFreq(chipClock,CHIP_FREQBASE,(x)%12,false))&((1<<bits)-1))|((MAX(x,0)/12)<<bits))
#define COLOR_NTSC (315000000.0/88.0)
#define COLOR_PAL (283.75*15625.0+25.0)

View file

@ -43,8 +43,8 @@
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
#define BUSY_END isBusy.unlock(); softLocked=false;
#define DIV_VERSION "dev84"
#define DIV_ENGINE_VERSION 84
#define DIV_VERSION "dev85"
#define DIV_ENGINE_VERSION 85
// for imports
#define DIV_VERSION_MOD 0xff01

View file

@ -162,6 +162,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
ds.gbInsAffectsEnvelope=true;
ds.ignoreDACModeOutsideIntendedChannel=false;
ds.e1e2AlsoTakePriority=true;
ds.fbPortaPause=true;
// 1.1 compat flags
if (ds.version>24) {
@ -993,6 +994,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
if (ds.version<84) {
ds.newSegaPCM=false;
}
if (ds.version<85) {
ds.fbPortaPause=true;
}
ds.isDMF=false;
reader.readS(); // reserved
@ -1342,7 +1346,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
} else {
reader.readC();
}
for (int i=0; i<22; i++) {
if (ds.version>=85) {
ds.fbPortaPause=reader.readC();
} else {
reader.readC();
}
for (int i=0; i<21; i++) {
reader.readC();
}
}
@ -2283,7 +2292,8 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
w->writeC(song.ignoreDACModeOutsideIntendedChannel);
w->writeC(song.e1e2AlsoTakePriority);
w->writeC(song.newSegaPCM);
for (int i=0; i<22; i++) {
w->writeC(song.fbPortaPause);
for (int i=0; i<21; i++) {
w->writeC(0);
}

View file

@ -245,15 +245,15 @@ void DivPlatformGenesis::tick(bool sysTick) {
if (chan[i].std.arp.had) {
if (!chan[i].inPorta) {
if (chan[i].std.arp.mode) {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11);
} else {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val);
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note+(signed char)chan[i].std.arp.val,11);
}
}
chan[i].freqChanged=true;
} else {
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11);
chan[i].freqChanged=true;
}
}
@ -394,9 +394,9 @@ void DivPlatformGenesis::tick(bool sysTick) {
for (int i=0; i<6; i++) {
if (i==2 && extMode) continue;
if (chan[i].freqChanged) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq));
if (chan[i].freq>262143) chan[i].freq=262143;
int freqt=toFreq(chan[i].freq)+chan[i].std.pitch.val;
chan[i].freq=((chan[i].baseFreq&0xf800)|parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4))+chan[i].std.pitch.val;
if (chan[i].freq>65535) chan[i].freq=65535;
int freqt=chan[i].freq;
immWrite(chanOffs[i]+ADDR_FREQH,freqt>>8);
immWrite(chanOffs[i]+ADDR_FREQ,freqt&0xff);
if (chan[i].furnaceDac && dacMode) {
@ -422,47 +422,6 @@ void DivPlatformGenesis::tick(bool sysTick) {
}
}
int DivPlatformGenesis::octave(int freq) {
if (freq>=82432) {
return 128;
} else if (freq>=41216) {
return 64;
} else if (freq>=20608) {
return 32;
} else if (freq>=10304) {
return 16;
} else if (freq>=5152) {
return 8;
} else if (freq>=2576) {
return 4;
} else if (freq>=1288) {
return 2;
} else {
return 1;
}
return 1;
}
int DivPlatformGenesis::toFreq(int freq) {
if (freq>=82432) {
return 0x3800|((freq>>7)&0x7ff);
} else if (freq>=41216) {
return 0x3000|((freq>>6)&0x7ff);
} else if (freq>=20608) {
return 0x2800|((freq>>5)&0x7ff);
} else if (freq>=10304) {
return 0x2000|((freq>>4)&0x7ff);
} else if (freq>=5152) {
return 0x1800|((freq>>3)&0x7ff);
} else if (freq>=2576) {
return 0x1000|((freq>>2)&0x7ff);
} else if (freq>=1288) {
return 0x800|((freq>>1)&0x7ff);
} else {
return freq&0x7ff;
}
}
void DivPlatformGenesis::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
for (int j=0; j<4; j++) {
@ -509,7 +468,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
dacPos=0;
dacPeriod=0;
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
chan[c.chan].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
chan[c.chan].freqChanged=true;
}
chan[c.chan].furnaceDac=true;
@ -576,7 +535,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
chan[c.chan].insChanged=false;
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
chan[c.chan].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
chan[c.chan].portaPause=false;
chan[c.chan].note=c.value;
chan[c.chan].freqChanged=true;
@ -656,31 +615,42 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
break;
}
case DIV_CMD_NOTE_PORTA: {
int destFreq=NOTE_FREQUENCY(c.value2);
int destFreq=NOTE_FNUM_BLOCK(c.value2,11);
int newFreq;
bool return2=false;
if (chan[c.chan].portaPause) {
chan[c.chan].baseFreq=chan[c.chan].portaPauseFreq;
}
if (destFreq>chan[c.chan].baseFreq) {
newFreq=chan[c.chan].baseFreq+c.value*octave(chan[c.chan].baseFreq);
newFreq=chan[c.chan].baseFreq+c.value;
if (newFreq>=destFreq) {
newFreq=destFreq;
return2=true;
}
} else {
newFreq=chan[c.chan].baseFreq-c.value*octave(chan[c.chan].baseFreq);
newFreq=chan[c.chan].baseFreq-c.value;
if (newFreq<=destFreq) {
newFreq=destFreq;
return2=true;
}
}
// check for octave boundary
// what the heck!
if (!chan[c.chan].portaPause) {
if (octave(chan[c.chan].baseFreq)!=octave(newFreq)) {
if ((newFreq&0x7ff)>1288) {
chan[c.chan].portaPauseFreq=(644)|((newFreq+0x800)&0xf800);
chan[c.chan].portaPause=true;
break;
}
if ((newFreq&0x7ff)<644) {
chan[c.chan].portaPauseFreq=newFreq=(1287)|((newFreq-0x800)&0xf800);
chan[c.chan].portaPause=true;
break;
}
}
chan[c.chan].baseFreq=newFreq;
chan[c.chan].portaPause=false;
chan[c.chan].freqChanged=true;
chan[c.chan].baseFreq=newFreq;
if (return2) {
chan[c.chan].inPorta=false;
return 2;
@ -699,7 +669,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
}
break;
case DIV_CMD_LEGATO: {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
chan[c.chan].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
chan[c.chan].note=c.value;
chan[c.chan].freqChanged=true;
break;

View file

@ -36,7 +36,7 @@ class DivPlatformGenesis: public DivDispatch {
DivInstrumentFM state;
DivMacroInt std;
unsigned char freqH, freqL;
int freq, baseFreq, pitch, note;
int freq, baseFreq, pitch, portaPauseFreq, note;
unsigned char ins;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, hardReset;
int vol, outVol;
@ -47,6 +47,7 @@ class DivPlatformGenesis: public DivDispatch {
freq(0),
baseFreq(0),
pitch(0),
portaPauseFreq(0),
note(0),
ins(-1),
active(false),
@ -93,9 +94,6 @@ class DivPlatformGenesis: public DivDispatch {
short oldWrites[512];
short pendingWrites[512];
int octave(int freq);
int toFreq(int freq);
friend void putDispatchChan(void*,int,int);
void acquire_nuked(short* bufL, short* bufR, size_t start, size_t len);

View file

@ -72,7 +72,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
opChan[ch].insChanged=false;
if (c.value!=DIV_NOTE_NULL) {
opChan[ch].baseFreq=NOTE_FREQUENCY(c.value);
opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
opChan[ch].portaPause=false;
opChan[ch].freqChanged=true;
}
@ -127,31 +127,49 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
break;
}
case DIV_CMD_NOTE_PORTA: {
int destFreq=NOTE_FREQUENCY(c.value2);
int destFreq=NOTE_FNUM_BLOCK(c.value2,11);
int newFreq;
bool return2=false;
if (opChan[ch].portaPause) {
opChan[ch].baseFreq=opChan[ch].portaPauseFreq;
}
if (destFreq>opChan[ch].baseFreq) {
newFreq=opChan[ch].baseFreq+c.value*octave(opChan[ch].baseFreq);
newFreq=opChan[ch].baseFreq+c.value;
if (newFreq>=destFreq) {
newFreq=destFreq;
return2=true;
}
} else {
newFreq=opChan[ch].baseFreq-c.value*octave(opChan[ch].baseFreq);
newFreq=opChan[ch].baseFreq-c.value;
if (newFreq<=destFreq) {
newFreq=destFreq;
return2=true;
}
}
// what the heck!
if (!opChan[ch].portaPause) {
if (octave(opChan[ch].baseFreq)!=octave(newFreq)) {
if ((newFreq&0x7ff)>1288) {
if (parent->song.fbPortaPause) {
opChan[ch].portaPauseFreq=(644)|((newFreq+0x800)&0xf800);
opChan[ch].portaPause=true;
break;
} else {
newFreq=(newFreq>>1)|((newFreq+0x800)&0xf800);
}
}
if ((newFreq&0x7ff)<644) {
if (parent->song.fbPortaPause) {
opChan[ch].portaPauseFreq=newFreq=(1287)|((newFreq-0x800)&0xf800);
opChan[ch].portaPause=true;
break;
} else {
newFreq=(newFreq<<1)|((newFreq-0x800)&0xf800);
}
}
}
opChan[ch].baseFreq=newFreq;
opChan[ch].portaPause=false;
opChan[ch].freqChanged=true;
opChan[ch].baseFreq=newFreq;
if (return2) return 2;
break;
}
@ -172,7 +190,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
}
break;
case DIV_CMD_LEGATO: {
opChan[ch].baseFreq=NOTE_FREQUENCY(c.value);
opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
opChan[ch].freqChanged=true;
break;
}
@ -289,35 +307,10 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
unsigned char writeMask=2;
if (extMode) for (int i=0; i<4; i++) {
if (opChan[i].freqChanged) {
opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch);
if (opChan[i].freq>262143) opChan[i].freq=262143;
if (opChan[i].freq>=82432) {
opChan[i].freqH=((opChan[i].freq>>15)&7)|0x38;
opChan[i].freqL=(opChan[i].freq>>7)&0xff;
} else if (opChan[i].freq>=41216) {
opChan[i].freqH=((opChan[i].freq>>14)&7)|0x30;
opChan[i].freqL=(opChan[i].freq>>6)&0xff;
} else if (opChan[i].freq>=20608) {
opChan[i].freqH=((opChan[i].freq>>13)&7)|0x28;
opChan[i].freqL=(opChan[i].freq>>5)&0xff;
} else if (opChan[i].freq>=10304) {
opChan[i].freqH=((opChan[i].freq>>12)&7)|0x20;
opChan[i].freqL=(opChan[i].freq>>4)&0xff;
} else if (opChan[i].freq>=5152) {
opChan[i].freqH=((opChan[i].freq>>11)&7)|0x18;
opChan[i].freqL=(opChan[i].freq>>3)&0xff;
} else if (opChan[i].freq>=2576) {
opChan[i].freqH=((opChan[i].freq>>10)&7)|0x10;
opChan[i].freqL=(opChan[i].freq>>2)&0xff;
} else if (opChan[i].freq>=1288) {
opChan[i].freqH=((opChan[i].freq>>9)&7)|0x08;
opChan[i].freqL=(opChan[i].freq>>1)&0xff;
} else {
opChan[i].freqH=(opChan[i].freq>>8)&7;
opChan[i].freqL=opChan[i].freq&0xff;
}
immWrite(opChanOffsH[i],opChan[i].freqH);
immWrite(opChanOffsL[i],opChan[i].freqL);
opChan[i].freq=(opChan[i].baseFreq&0xf800)|parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4);
if (chan[i].freq>65535) chan[i].freq=65535;
immWrite(opChanOffsH[i],opChan[i].freq>>8);
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
}
writeMask|=opChan[i].active<<(4+i);
if (opChan[i].keyOn) {

View file

@ -25,13 +25,28 @@ class DivPlatformGenesisExt: public DivPlatformGenesis {
struct OpChannel {
DivMacroInt std;
unsigned char freqH, freqL;
int freq, baseFreq, pitch;
int freq, baseFreq, pitch, portaPauseFreq;
unsigned char ins;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause;
int vol;
unsigned char pan;
OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {}
OpChannel():
freqH(0),
freqL(0),
freq(0),
baseFreq(0),
pitch(0),
portaPauseFreq(0),
ins(-1),
active(false),
insChanged(true),
freqChanged(false),
keyOn(false),
keyOff(false),
portaPause(false),
vol(0),
pan(3) {}
};
OpChannel opChan[4];
bool isOpMuted[4];

View file

@ -324,6 +324,7 @@ struct DivSong {
bool ignoreDACModeOutsideIntendedChannel;
bool e1e2AlsoTakePriority;
bool newSegaPCM;
bool fbPortaPause;
DivOrders orders;
std::vector<DivInstrument*> ins;
@ -406,7 +407,8 @@ struct DivSong {
sharedExtStat(true),
ignoreDACModeOutsideIntendedChannel(false),
e1e2AlsoTakePriority(false),
newSegaPCM(true) {
newSegaPCM(true),
fbPortaPause(false) {
for (int i=0; i<32; i++) {
system[i]=DIV_SYSTEM_NULL;
systemVol[i]=64;

View file

@ -101,6 +101,10 @@ void FurnaceGUI::drawCompatFlags() {
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("simulates a bug in where portamento does not work after sliding.");
}
ImGui::Checkbox("FM pitch slide octave boundary odd behavior",&e->song.fbPortaPause);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("if this is on, a pitch slide that crosses the octave boundary will stop for one tick and then continue from the nearest octave boundary.\nfor .dmf compatibility.");
}
ImGui::Checkbox("Apply Game Boy envelope on note-less instrument change",&e->song.gbInsAffectsEnvelope);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("if this is on, an instrument change will also affect the envelope.");

View file

@ -17,7 +17,6 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <SDL_keycode.h>
#define _USE_MATH_DEFINES
#include "gui.h"
#include "util.h"