mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-23 04:55:13 +00:00
Merge branch 'master' into ay_divider
This commit is contained in:
commit
76997fd5ba
16 changed files with 577 additions and 27 deletions
3
TODO.md
3
TODO.md
|
@ -13,7 +13,6 @@
|
|||
- maybe YMU759 ADPCM channel
|
||||
- ADPCM chips
|
||||
- Game Boy envelope macro/sequence
|
||||
- option to display chip names instead of "multi-system" on title bar
|
||||
- rewrite the system name detection function anyway
|
||||
- scroll instrument/wave/sample list when selecting item
|
||||
- unified data view
|
||||
|
@ -30,4 +29,4 @@
|
|||
- Apply button in settings
|
||||
- find and replace
|
||||
- finish wave synth
|
||||
- make compact mode multi-state (note, note/ins, note/ins/vol and note/ins/vol/effects)
|
||||
- add mono/poly note preview button
|
|
@ -697,6 +697,17 @@ size | description
|
|||
1 | extra 8 macro mode
|
||||
--- | **extra C64 data** (>=89)
|
||||
1 | don't test/gate before new note
|
||||
--- | **MultiPCM data** (>=93)
|
||||
1 | attack rate
|
||||
1 | decay 1 rate
|
||||
1 | decay level
|
||||
1 | decay 2 rate
|
||||
1 | release rate
|
||||
1 | rate correction
|
||||
1 | lfo rate
|
||||
1 | vib depth
|
||||
1 | am depth
|
||||
23 | reserved
|
||||
```
|
||||
|
||||
# wavetable
|
||||
|
|
|
@ -45,8 +45,8 @@
|
|||
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
|
||||
#define BUSY_END isBusy.unlock(); softLocked=false;
|
||||
|
||||
#define DIV_VERSION "dev92" // it breaks compatiblity
|
||||
#define DIV_ENGINE_VERSION 92
|
||||
#define DIV_VERSION "dev93"
|
||||
#define DIV_ENGINE_VERSION 93
|
||||
|
||||
// for imports
|
||||
#define DIV_VERSION_MOD 0xff01
|
||||
|
@ -548,7 +548,7 @@ class DivEngine {
|
|||
DivInstrumentType getPreferInsSecondType(int ch);
|
||||
|
||||
// get song system name
|
||||
const char* getSongSystemName();
|
||||
String getSongSystemName(bool isMultiSystemAcceptable=true);
|
||||
|
||||
// get sys name
|
||||
const char* getSystemName(DivSystem sys);
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include "engine.h"
|
||||
#include "../ta-log.h"
|
||||
#include "instrument.h"
|
||||
#include "song.h"
|
||||
#include <zlib.h>
|
||||
#include <fmt/printf.h>
|
||||
|
@ -2034,11 +2035,28 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
|||
return success;
|
||||
}
|
||||
|
||||
#define CHECK_BLOCK_VERSION(x) \
|
||||
if (blockVersion>x) { \
|
||||
logE("incompatible block version %d for %s!",blockVersion,blockName); \
|
||||
lastError="incompatible block version"; \
|
||||
delete[] file; \
|
||||
return false; \
|
||||
}
|
||||
|
||||
bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
||||
SafeReader reader=SafeReader(file,len);
|
||||
warnings="";
|
||||
try {
|
||||
DivSong ds;
|
||||
String blockName;
|
||||
unsigned char expansions=0;
|
||||
unsigned int tchans=0;
|
||||
unsigned int n163Chans=0;
|
||||
bool hasSequence[256][8];
|
||||
unsigned char sequenceIndex[256][8];
|
||||
|
||||
memset(hasSequence,0,256*8*sizeof(bool));
|
||||
memset(sequenceIndex,0,256*8);
|
||||
|
||||
if (!reader.seek(18,SEEK_SET)) {
|
||||
logE("premature end of file!");
|
||||
|
@ -2046,17 +2064,327 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
|||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
ds.version=(unsigned short)reader.readS();
|
||||
ds.version=(unsigned short)reader.readI();
|
||||
logI("module version %d (0x%.4x)",ds.version,ds.version);
|
||||
|
||||
if (ds.version>0x0440) {
|
||||
if (ds.version>0x0450) {
|
||||
logE("incompatible version %x!",ds.version);
|
||||
lastError="incompatible version";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
while (true) {
|
||||
blockName=reader.readString(3);
|
||||
if (blockName=="END") {
|
||||
// end of module
|
||||
logD("end of data");
|
||||
break;
|
||||
}
|
||||
|
||||
// not the end
|
||||
reader.seek(-3,SEEK_CUR);
|
||||
blockName=reader.readString(16);
|
||||
unsigned int blockVersion=(unsigned int)reader.readI();
|
||||
unsigned int blockSize=(unsigned int)reader.readI();
|
||||
size_t blockStart=reader.tell();
|
||||
|
||||
logD("reading block %s (version %d, %d bytes)",blockName,blockVersion,blockSize);
|
||||
if (blockName=="PARAMS") {
|
||||
CHECK_BLOCK_VERSION(6);
|
||||
unsigned int oldSpeedTempo=0;
|
||||
if (blockVersion<=1) {
|
||||
oldSpeedTempo=reader.readI();
|
||||
}
|
||||
if (blockVersion>=2) {
|
||||
expansions=reader.readC();
|
||||
}
|
||||
tchans=reader.readI();
|
||||
unsigned int pal=reader.readI();
|
||||
unsigned int customHz=reader.readI();
|
||||
unsigned int newVibrato=0;
|
||||
unsigned int speedSplitPoint=0;
|
||||
if (blockVersion>=3) {
|
||||
newVibrato=reader.readI();
|
||||
}
|
||||
if (blockVersion>=4) {
|
||||
ds.hilightA=reader.readI();
|
||||
ds.hilightB=reader.readI();
|
||||
}
|
||||
if (expansions&8) if (blockVersion>=5) { // N163 channels
|
||||
n163Chans=reader.readI();
|
||||
}
|
||||
if (blockVersion>=6) {
|
||||
speedSplitPoint=reader.readI();
|
||||
}
|
||||
|
||||
logV("old speed/tempo: %d",oldSpeedTempo);
|
||||
logV("expansions: %x",expansions);
|
||||
logV("channels: %d",tchans);
|
||||
logV("PAL: %d",pal);
|
||||
logV("custom Hz: %d",customHz);
|
||||
logV("new vibrato: %d",newVibrato);
|
||||
logV("N163 channels: %d",n163Chans);
|
||||
logV("highlight 1: %d",ds.hilightA);
|
||||
logV("highlight 2: %d",ds.hilightB);
|
||||
logV("split point: %d",speedSplitPoint);
|
||||
|
||||
if (customHz!=0) {
|
||||
ds.hz=customHz;
|
||||
}
|
||||
|
||||
// initialize channels
|
||||
int systemID=0;
|
||||
ds.system[systemID++]=DIV_SYSTEM_NES;
|
||||
if (expansions&1) {
|
||||
ds.system[systemID++]=DIV_SYSTEM_VRC6;
|
||||
}
|
||||
if (expansions&2) {
|
||||
ds.system[systemID++]=DIV_SYSTEM_VRC7;
|
||||
}
|
||||
if (expansions&4) {
|
||||
ds.system[systemID++]=DIV_SYSTEM_FDS;
|
||||
}
|
||||
if (expansions&8) {
|
||||
ds.system[systemID++]=DIV_SYSTEM_MMC5;
|
||||
}
|
||||
if (expansions&16) {
|
||||
ds.system[systemID]=DIV_SYSTEM_N163;
|
||||
ds.systemFlags[systemID++]=n163Chans;
|
||||
}
|
||||
if (expansions&32) {
|
||||
ds.system[systemID]=DIV_SYSTEM_AY8910;
|
||||
ds.systemFlags[systemID++]=38; // Sunsoft 5B
|
||||
}
|
||||
ds.systemLen=systemID;
|
||||
|
||||
unsigned int calcChans=0;
|
||||
for (int i=0; i<ds.systemLen; i++) {
|
||||
calcChans+=getChannelCount(ds.system[i]);
|
||||
}
|
||||
if (calcChans!=tchans) {
|
||||
logE("channel counts do not match! %d != %d",tchans,calcChans);
|
||||
lastError="channel counts do not match";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
if (tchans>DIV_MAX_CHANS) {
|
||||
tchans=DIV_MAX_CHANS;
|
||||
logW("too many channels!");
|
||||
}
|
||||
} else if (blockName=="INFO") {
|
||||
CHECK_BLOCK_VERSION(1);
|
||||
ds.name=reader.readString(32);
|
||||
ds.author=reader.readString(32);
|
||||
ds.copyright=reader.readString(32);
|
||||
} else if (blockName=="HEADER") {
|
||||
CHECK_BLOCK_VERSION(3);
|
||||
unsigned char totalSongs=reader.readC();
|
||||
logV("%d songs:",totalSongs+1);
|
||||
for (int i=0; i<=totalSongs; i++) {
|
||||
String subSongName=reader.readString();
|
||||
logV("- %s",subSongName);
|
||||
}
|
||||
for (unsigned int i=0; i<tchans; i++) {
|
||||
unsigned char chID=reader.readC();
|
||||
logV("for channel ID %d",chID);
|
||||
for (int j=0; j<=totalSongs; j++) {
|
||||
unsigned char effectCols=reader.readC();
|
||||
if (j==0) {
|
||||
ds.pat[i].effectCols=effectCols+1;
|
||||
}
|
||||
logV("- song %d has %d effect columns",j,effectCols);
|
||||
}
|
||||
}
|
||||
} else if (blockName=="INSTRUMENTS") {
|
||||
CHECK_BLOCK_VERSION(6);
|
||||
ds.insLen=reader.readI();
|
||||
if (ds.insLen<0 || ds.insLen>256) {
|
||||
logE("too many instruments/out of range!");
|
||||
lastError="too many instruments/out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i=0; i<ds.insLen; i++) {
|
||||
DivInstrument* ins=new DivInstrument;
|
||||
ds.ins.push_back(ins);
|
||||
}
|
||||
|
||||
logV("instruments:");
|
||||
for (int i=0; i<ds.insLen; i++) {
|
||||
unsigned int insIndex=reader.readI();
|
||||
if (insIndex>=ds.ins.size()) {
|
||||
logE("instrument index %d is out of range!",insIndex);
|
||||
lastError="instrument index out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
DivInstrument* ins=ds.ins[insIndex];
|
||||
unsigned char insType=reader.readC();
|
||||
switch (insType) {
|
||||
case 1:
|
||||
ins->type=DIV_INS_STD;
|
||||
break;
|
||||
case 2: // TODO: tell VRC6 and VRC6 saw instruments apart
|
||||
ins->type=DIV_INS_VRC6;
|
||||
break;
|
||||
case 3:
|
||||
ins->type=DIV_INS_OPLL;
|
||||
break;
|
||||
case 4:
|
||||
ins->type=DIV_INS_FDS;
|
||||
break;
|
||||
case 5:
|
||||
ins->type=DIV_INS_N163;
|
||||
break;
|
||||
case 6: // 5B?
|
||||
ins->type=DIV_INS_AY;
|
||||
break;
|
||||
default: {
|
||||
logE("%d: invalid instrument type %d",insIndex,insType);
|
||||
lastError="invalid instrument type";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// instrument data
|
||||
switch (ins->type) {
|
||||
case DIV_INS_STD: {
|
||||
unsigned int totalSeqs=reader.readI();
|
||||
if (totalSeqs>5) {
|
||||
logE("%d: too many sequences!",insIndex);
|
||||
lastError="too many sequences";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (unsigned int j=0; j<totalSeqs; j++) {
|
||||
hasSequence[insIndex][j]=reader.readC();
|
||||
sequenceIndex[insIndex][j]=reader.readC();
|
||||
}
|
||||
|
||||
const int dpcmNotes=(blockVersion>=2)?96:72;
|
||||
for (int j=0; j<dpcmNotes; j++) {
|
||||
ins->amiga.noteMap[j]=(short)((unsigned char)reader.readC())-1;
|
||||
ins->amiga.noteFreq[j]=(unsigned char)reader.readC();
|
||||
if (blockVersion>=6) {
|
||||
reader.readC(); // DMC value
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_INS_VRC6: {
|
||||
unsigned int totalSeqs=reader.readI();
|
||||
if (totalSeqs>4) {
|
||||
logE("%d: too many sequences!",insIndex);
|
||||
lastError="too many sequences";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (unsigned int j=0; j<totalSeqs; j++) {
|
||||
hasSequence[insIndex][j]=reader.readC();
|
||||
sequenceIndex[insIndex][j]=reader.readC();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_INS_OPLL: {
|
||||
ins->fm.opllPreset=(unsigned int)reader.readI();
|
||||
// TODO
|
||||
break;
|
||||
}
|
||||
case DIV_INS_FDS: {
|
||||
DivWavetable* wave=new DivWavetable;
|
||||
wave->len=64;
|
||||
wave->max=64;
|
||||
for (int j=0; j<64; j++) {
|
||||
wave->data[j]=reader.readC();
|
||||
}
|
||||
ins->std.waveMacro.len=1;
|
||||
ins->std.waveMacro.val[0]=ds.wave.size();
|
||||
for (int j=0; j<32; j++) {
|
||||
ins->fds.modTable[j]=reader.readC()-3;
|
||||
}
|
||||
ins->fds.modSpeed=reader.readI();
|
||||
ins->fds.modDepth=reader.readI();
|
||||
reader.readI(); // this is delay. currently ignored. TODO.
|
||||
ds.wave.push_back(wave);
|
||||
|
||||
ins->std.volMacro.len=reader.readC();
|
||||
ins->std.volMacro.loop=reader.readI();
|
||||
ins->std.volMacro.rel=reader.readI();
|
||||
reader.readI(); // arp mode does not apply here
|
||||
for (int j=0; j<ins->std.volMacro.len; j++) {
|
||||
ins->std.volMacro.val[j]=reader.readC();
|
||||
}
|
||||
|
||||
ins->std.arpMacro.len=reader.readC();
|
||||
ins->std.arpMacro.loop=reader.readI();
|
||||
ins->std.arpMacro.rel=reader.readI();
|
||||
ins->std.arpMacro.mode=reader.readI();
|
||||
for (int j=0; j<ins->std.arpMacro.len; j++) {
|
||||
ins->std.arpMacro.val[j]=reader.readC();
|
||||
}
|
||||
|
||||
ins->std.pitchMacro.len=reader.readC();
|
||||
ins->std.pitchMacro.loop=reader.readI();
|
||||
ins->std.pitchMacro.rel=reader.readI();
|
||||
reader.readI(); // arp mode does not apply here
|
||||
for (int j=0; j<ins->std.pitchMacro.len; j++) {
|
||||
ins->std.pitchMacro.val[j]=reader.readC();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case DIV_INS_N163: {
|
||||
// TODO!
|
||||
break;
|
||||
}
|
||||
// TODO: 5B!
|
||||
default: {
|
||||
logE("%d: what's going on here?",insIndex);
|
||||
lastError="invalid instrument type";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// name
|
||||
ins->name=reader.readString((unsigned int)reader.readI());
|
||||
logV("- %d: %s",insIndex,ins->name);
|
||||
}
|
||||
} else if (blockName=="SEQUENCES") {
|
||||
CHECK_BLOCK_VERSION(6);
|
||||
} else if (blockName=="FRAMES") {
|
||||
CHECK_BLOCK_VERSION(3);
|
||||
} else if (blockName=="PATTERNS") {
|
||||
CHECK_BLOCK_VERSION(5);
|
||||
} else if (blockName=="DPCM SAMPLES") {
|
||||
CHECK_BLOCK_VERSION(1);
|
||||
} else if (blockName=="SEQUENCES_VRC6") {
|
||||
// where are the 5B and FDS sequences?
|
||||
CHECK_BLOCK_VERSION(6);
|
||||
} else if (blockName=="SEQUENCES_N163") {
|
||||
CHECK_BLOCK_VERSION(1);
|
||||
} else if (blockName=="COMMENTS") {
|
||||
CHECK_BLOCK_VERSION(1);
|
||||
} else {
|
||||
logE("block %s is unknown!",blockName);
|
||||
lastError="unknown block "+blockName;
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((reader.tell()-blockStart)!=blockSize) {
|
||||
logE("block %s is incomplete!",blockName);
|
||||
lastError="incomplete block "+blockName;
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (EndOfFileException& e) {
|
||||
logE("premature end of file!");
|
||||
lastError="incomplete file";
|
||||
|
|
|
@ -506,6 +506,20 @@ void DivInstrument::putInsData(SafeWriter* w) {
|
|||
|
||||
// C64 no test
|
||||
w->writeC(c64.noTest);
|
||||
|
||||
// MultiPCM
|
||||
w->writeC(multipcm.ar);
|
||||
w->writeC(multipcm.d1r);
|
||||
w->writeC(multipcm.dl);
|
||||
w->writeC(multipcm.d2r);
|
||||
w->writeC(multipcm.rr);
|
||||
w->writeC(multipcm.rc);
|
||||
w->writeC(multipcm.lfo);
|
||||
w->writeC(multipcm.vib);
|
||||
w->writeC(multipcm.am);
|
||||
for (int j=0; j<23; j++) { // reserved
|
||||
w->writeC(0);
|
||||
}
|
||||
}
|
||||
|
||||
DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
|
||||
|
@ -1014,6 +1028,21 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
|
|||
c64.noTest=reader.readC();
|
||||
}
|
||||
|
||||
// MultiPCM
|
||||
if (version>=93) {
|
||||
multipcm.ar=reader.readC();
|
||||
multipcm.d1r=reader.readC();
|
||||
multipcm.dl=reader.readC();
|
||||
multipcm.d2r=reader.readC();
|
||||
multipcm.rr=reader.readC();
|
||||
multipcm.rc=reader.readC();
|
||||
multipcm.lfo=reader.readC();
|
||||
multipcm.vib=reader.readC();
|
||||
multipcm.am=reader.readC();
|
||||
// reserved
|
||||
for (int k=0; k<23; k++) reader.readC();
|
||||
}
|
||||
|
||||
return DIV_DATA_SUCCESS;
|
||||
}
|
||||
|
||||
|
|
|
@ -338,6 +338,16 @@ struct DivInstrumentFDS {
|
|||
}
|
||||
};
|
||||
|
||||
struct DivInstrumentMultiPCM {
|
||||
unsigned char ar, d1r, dl, d2r, rr, rc;
|
||||
unsigned char lfo, vib, am;
|
||||
|
||||
DivInstrumentMultiPCM():
|
||||
ar(15), d1r(15), dl(0), d2r(0), rr(15), rc(15),
|
||||
lfo(0), vib(0), am(0) {
|
||||
}
|
||||
};
|
||||
|
||||
enum DivWaveSynthEffects {
|
||||
DIV_WS_NONE=0,
|
||||
// one waveform effects
|
||||
|
@ -393,6 +403,7 @@ struct DivInstrument {
|
|||
DivInstrumentAmiga amiga;
|
||||
DivInstrumentN163 n163;
|
||||
DivInstrumentFDS fds;
|
||||
DivInstrumentMultiPCM multipcm;
|
||||
DivInstrumentWaveSynth ws;
|
||||
|
||||
/**
|
||||
|
|
|
@ -480,7 +480,7 @@ if (m_choffs == 0)
|
|||
#endif
|
||||
|
||||
// early out if the envelope is effectively off
|
||||
if (m_env_attenuation > EG_QUIET)
|
||||
if (m_env_attenuation > EG_QUIET && m_cache.eg_shift == 0)
|
||||
return 0;
|
||||
|
||||
// get the absolute value of the sin, as attenuation, as a 4.8 fixed point value
|
||||
|
|
|
@ -70,16 +70,24 @@
|
|||
// OPZ supports a "fixed frequency" mode for each operator, with a 3-bit
|
||||
// range and 4-bit frequency value, plus a 1-bit enable. Not sure how that
|
||||
// works at all, so it's not implemented.
|
||||
// note by tildearrow:
|
||||
// - I have verified behavior of this mode against real hardware.
|
||||
// after applying a small fix on the existing early implementation, it matches hardware.
|
||||
// this means fixed frequency is fully implemented and working.
|
||||
//
|
||||
// There are also several mystery fields in the operators which I have no
|
||||
// clue about: "fine" (4 bits), "eg_shift" (2 bits), and "rev" (3 bits).
|
||||
// eg_shift is some kind of envelope generator effect, but how it works is
|
||||
// unknown.
|
||||
// note by tildearrow:
|
||||
// - behavior of "fine" is now confirmed and matches hardware.
|
||||
//
|
||||
// Also, according to the site above, the panning controls are changed from
|
||||
// OPM, with a "mono" bit and only one control bit for the right channel.
|
||||
// Current implementation is just a guess.
|
||||
//
|
||||
// additional modifications by tildearrow for Furnace
|
||||
//
|
||||
|
||||
namespace ymfm
|
||||
{
|
||||
|
@ -409,9 +417,6 @@ uint32_t opz_registers::lfo_am_offset(uint32_t choffs) const
|
|||
|
||||
void opz_registers::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache)
|
||||
{
|
||||
// TODO: how does fixed frequency mode work? appears to be enabled by
|
||||
// op_fix_mode(), and controlled by op_fix_range(), op_fix_frequency()
|
||||
|
||||
// TODO: what is op_rev()?
|
||||
|
||||
// set up the easy stuff
|
||||
|
@ -467,8 +472,8 @@ void opz_registers::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata
|
|||
if (reverb != 0)
|
||||
cache.eg_rate[EG_REVERB] = std::min<uint32_t>(effective_rate(reverb * 4 + 2, ksrval), cache.eg_rate[EG_REVERB]);
|
||||
|
||||
// set the envelope shift; TX81Z manual says operator 1 shift is fixed at "off"
|
||||
cache.eg_shift = ((opoffs & 0x18) == 0) ? 0 : op_eg_shift(opoffs);
|
||||
// set the envelope shift; TX81Z manual says operator 1 (actually operator 4) shift is fixed at "off"
|
||||
cache.eg_shift = ((opoffs & 0x18) == 0x18) ? 0 : op_eg_shift(opoffs);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -919,12 +919,13 @@ int DivPlatformTX81Z::dispatch(DivCommand c) {
|
|||
}
|
||||
case DIV_CMD_FM_FIXFREQ: {
|
||||
if (c.value<0 || c.value>3) break;
|
||||
printf("fixfreq %x\n",c.value2);
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
|
||||
op.egt=(c.value2>0);
|
||||
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.egt<<5)|(op.rs<<6));
|
||||
if (op.egt) {
|
||||
rWrite(baseAddr+ADDR_MULT_DT,((c.value2>>4)&15)|((c.value2>>8)&7));
|
||||
rWrite(baseAddr+ADDR_MULT_DT,((c.value2>>4)&15)|(((c.value2>>8)&7)<<4));
|
||||
rWrite(baseAddr+ADDR_WS_FINE,(c.value2&15)|(op.ws<<4));
|
||||
} else {
|
||||
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|((op.egt?(op.dt&7):dtTable[op.dt&7])<<4));
|
||||
|
|
|
@ -53,7 +53,7 @@ std::vector<DivInstrumentType>& DivEngine::getPossibleInsTypes() {
|
|||
}
|
||||
|
||||
// TODO: rewrite this function (again). it's an unreliable mess.
|
||||
const char* DivEngine::getSongSystemName() {
|
||||
String DivEngine::getSongSystemName(bool isMultiSystemAcceptable) {
|
||||
switch (song.systemLen) {
|
||||
case 0:
|
||||
return "help! what's going on!";
|
||||
|
@ -198,7 +198,15 @@ const char* DivEngine::getSongSystemName() {
|
|||
}
|
||||
break;
|
||||
}
|
||||
return "multi-system";
|
||||
if (isMultiSystemAcceptable) return "multi-system";
|
||||
|
||||
String ret="";
|
||||
for (int i=0; i<song.systemLen; i++) {
|
||||
if (i>0) ret+=" + ";
|
||||
ret+=getSystemName(song.system[i]);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
const char* DivEngine::getSystemName(DivSystem sys) {
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "plot_nolerp.h"
|
||||
#include "guiConst.h"
|
||||
#include <fmt/printf.h>
|
||||
#include <imgui.h>
|
||||
|
||||
const char* sampleNote[12]={
|
||||
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"
|
||||
|
@ -36,6 +37,7 @@ void FurnaceGUI::drawInsList() {
|
|||
}
|
||||
if (!insListOpen) return;
|
||||
if (ImGui::Begin("Instruments",&insListOpen)) {
|
||||
if (settings.unifiedDataView) settings.horizontalDataView=0;
|
||||
if (ImGui::Button(ICON_FA_PLUS "##InsAdd")) {
|
||||
doAction(GUI_ACTION_INS_LIST_ADD);
|
||||
}
|
||||
|
@ -45,7 +47,7 @@ void FurnaceGUI::drawInsList() {
|
|||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(ICON_FA_FOLDER_OPEN "##InsLoad")) {
|
||||
doAction((settings.insLoadAlwaysReplace && curIns>=0 && curIns<=(int)e->song.ins.size())?GUI_ACTION_INS_LIST_OPEN_REPLACE:GUI_ACTION_INS_LIST_OPEN);
|
||||
doAction((settings.insLoadAlwaysReplace && curIns>=0 && curIns<(int)e->song.ins.size())?GUI_ACTION_INS_LIST_OPEN_REPLACE:GUI_ACTION_INS_LIST_OPEN);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(ICON_FA_FLOPPY_O "##InsSave")) {
|
||||
|
@ -64,7 +66,12 @@ void FurnaceGUI::drawInsList() {
|
|||
doAction(GUI_ACTION_INS_LIST_DELETE);
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::BeginTable("InsListScroll",1,ImGuiTableFlags_ScrollY)) {
|
||||
int availableRows=ImGui::GetContentRegionAvail().y/ImGui::GetFrameHeight();
|
||||
if (availableRows<1) availableRows=1;
|
||||
int columns=settings.horizontalDataView?(int)(ceil((double)(e->song.ins.size()+1)/(double)availableRows)):1;
|
||||
if (columns<1) columns=1;
|
||||
if (columns>64) columns=64;
|
||||
if (ImGui::BeginTable("InsListScroll",columns,(settings.horizontalDataView?ImGuiTableFlags_ScrollX:0)|ImGuiTableFlags_ScrollY)) {
|
||||
if (settings.unifiedDataView) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
|
@ -72,6 +79,11 @@ void FurnaceGUI::drawInsList() {
|
|||
ImGui::Indent();
|
||||
}
|
||||
|
||||
if (settings.horizontalDataView) {
|
||||
ImGui::TableNextRow();
|
||||
}
|
||||
|
||||
int curRow=0;
|
||||
for (int i=-1; i<(int)e->song.ins.size(); i++) {
|
||||
String name=ICON_FA_CIRCLE_O " - None -";
|
||||
const char* insType="Bug!";
|
||||
|
@ -211,8 +223,12 @@ void FurnaceGUI::drawInsList() {
|
|||
} else {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]);
|
||||
}
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
if (!settings.horizontalDataView) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
} else if (curRow==0) {
|
||||
ImGui::TableNextColumn();
|
||||
}
|
||||
if (ImGui::Selectable(name.c_str(),(i==-1)?(curIns<0 || curIns>=e->song.insLen):(curIns==i))) {
|
||||
curIns=i;
|
||||
}
|
||||
|
@ -228,6 +244,9 @@ void FurnaceGUI::drawInsList() {
|
|||
nextWindow=GUI_WINDOW_INS_EDIT;
|
||||
}
|
||||
}
|
||||
if (settings.horizontalDataView) {
|
||||
if (++curRow>=availableRows) curRow=0;
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.unifiedDataView) {
|
||||
|
|
|
@ -465,7 +465,11 @@ void FurnaceGUI::doAction(int what) {
|
|||
break;
|
||||
case GUI_ACTION_PAT_COLLAPSE:
|
||||
if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break;
|
||||
e->song.chanCollapse[cursor.xCoarse]=!e->song.chanCollapse[cursor.xCoarse];
|
||||
if (e->song.chanCollapse[cursor.xCoarse]==0) {
|
||||
e->song.chanCollapse[cursor.xCoarse]=3;
|
||||
} else if (e->song.chanCollapse[cursor.xCoarse]>0) {
|
||||
e->song.chanCollapse[cursor.xCoarse]--;
|
||||
}
|
||||
break;
|
||||
case GUI_ACTION_PAT_INCREASE_COLUMNS:
|
||||
if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break;
|
||||
|
|
|
@ -588,7 +588,7 @@ void FurnaceGUI::updateWindowTitle() {
|
|||
}
|
||||
|
||||
if (settings.titleBarSys) {
|
||||
title+=fmt::sprintf(" (%s)",e->getSongSystemName());
|
||||
title+=fmt::sprintf(" (%s)",e->getSongSystemName(!settings.noMultiSystem));
|
||||
}
|
||||
|
||||
if (sdlWin!=NULL) SDL_SetWindowTitle(sdlWin,title.c_str());
|
||||
|
@ -2091,7 +2091,7 @@ void FurnaceGUI::editOptions(bool topMenu) {
|
|||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Values")) {
|
||||
doTranspose(transposeAmount,opMaskTransposeNote);
|
||||
doTranspose(transposeAmount,opMaskTransposeValue);
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
|
@ -3133,7 +3133,7 @@ bool FurnaceGUI::loop() {
|
|||
showWarning(e->getWarnings(),GUI_WARN_GENERIC);
|
||||
}
|
||||
if (curIns>=0 && curIns<(int)e->song.ins.size()) {
|
||||
*e->song.ins[curIns]=*instruments[curIns];
|
||||
*e->song.ins[curIns]=*instruments[0];
|
||||
} else {
|
||||
showError("...but you haven't selected an instrument!");
|
||||
}
|
||||
|
|
|
@ -858,6 +858,8 @@ class FurnaceGUI {
|
|||
int moveWindowTitle;
|
||||
int hiddenSystems;
|
||||
int insLoadAlwaysReplace;
|
||||
int horizontalDataView;
|
||||
int noMultiSystem;
|
||||
unsigned int maxUndoSteps;
|
||||
String mainFontPath;
|
||||
String patFontPath;
|
||||
|
@ -941,6 +943,8 @@ class FurnaceGUI {
|
|||
moveWindowTitle(0),
|
||||
hiddenSystems(0),
|
||||
insLoadAlwaysReplace(1),
|
||||
horizontalDataView(0),
|
||||
noMultiSystem(0),
|
||||
maxUndoSteps(100),
|
||||
mainFontPath(""),
|
||||
patFontPath(""),
|
||||
|
|
|
@ -2587,6 +2587,108 @@ void FurnaceGUI::drawInsEdit() {
|
|||
}
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ins->type==DIV_INS_MULTIPCM) {
|
||||
if (ImGui::BeginTabItem("MultiPCM")) {
|
||||
String sName;
|
||||
if (ins->amiga.initSample<0 || ins->amiga.initSample>=e->song.sampleLen) {
|
||||
sName="none selected";
|
||||
} else {
|
||||
sName=e->song.sample[ins->amiga.initSample]->name;
|
||||
}
|
||||
if (ImGui::BeginCombo("Initial Sample",sName.c_str())) {
|
||||
String id;
|
||||
for (int i=0; i<e->song.sampleLen; i++) {
|
||||
id=fmt::sprintf("%d: %s",i,e->song.sample[i]->name);
|
||||
if (ImGui::Selectable(id.c_str(),ins->amiga.initSample==i)) {
|
||||
ins->amiga.initSample=i;
|
||||
PARAMETER
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
ImVec2 sliderSize=ImVec2(20.0f*dpiScale,128.0*dpiScale);
|
||||
if (ImGui::BeginTable("MultiPCMADSRParams",7,ImGuiTableFlags_NoHostExtendX)) {
|
||||
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,sliderSize.x);
|
||||
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,sliderSize.x);
|
||||
ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed,sliderSize.x);
|
||||
ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthFixed,sliderSize.x);
|
||||
ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthFixed,sliderSize.x);
|
||||
ImGui::TableSetupColumn("c5",ImGuiTableColumnFlags_WidthFixed,sliderSize.x);
|
||||
ImGui::TableSetupColumn("c6",ImGuiTableColumnFlags_WidthStretch);
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
CENTER_TEXT("AR");
|
||||
ImGui::TextUnformatted("AR");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Attack Rate");
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
CENTER_TEXT("D1R");
|
||||
ImGui::TextUnformatted("D1R");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Decay 1 Rate");
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
CENTER_TEXT("DL");
|
||||
ImGui::TextUnformatted("DL");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Decay Level");
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
CENTER_TEXT("D2R");
|
||||
ImGui::TextUnformatted("D2R");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Decay 2 Rate");
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
CENTER_TEXT("RR");
|
||||
ImGui::TextUnformatted("RR");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Release Rate");
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
CENTER_TEXT("RC");
|
||||
ImGui::TextUnformatted("RC");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Rate Correction");
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
CENTER_TEXT("Envelope");
|
||||
ImGui::TextUnformatted("Envelope");
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
P(CWVSliderScalar("##Attack Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.ar,&_ZERO,&_FIFTEEN));
|
||||
ImGui::TableNextColumn();
|
||||
P(CWVSliderScalar("##Decay 1 Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.d1r,&_ZERO,&_FIFTEEN));
|
||||
ImGui::TableNextColumn();
|
||||
P(CWVSliderScalar("##Decay Level",sliderSize,ImGuiDataType_U8,&ins->multipcm.dl,&_ZERO,&_FIFTEEN));
|
||||
ImGui::TableNextColumn();
|
||||
P(CWVSliderScalar("##Decay 2 Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.d2r,&_ZERO,&_FIFTEEN));
|
||||
ImGui::TableNextColumn();
|
||||
P(CWVSliderScalar("##Release Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.rr,&_ZERO,&_FIFTEEN));
|
||||
ImGui::TableNextColumn();
|
||||
P(CWVSliderScalar("##Rate Correction",sliderSize,ImGuiDataType_U8,&ins->multipcm.rc,&_ZERO,&_FIFTEEN));
|
||||
ImGui::TableNextColumn();
|
||||
drawFMEnv(0,ins->multipcm.ar,ins->multipcm.d1r,ins->multipcm.d2r,ins->multipcm.rr,ins->multipcm.dl,0,0,0,127,15,ImVec2(ImGui::GetContentRegionAvail().x,sliderSize.y),ins->type);
|
||||
ImGui::EndTable();
|
||||
}
|
||||
if (ImGui::BeginTable("MultiPCMLFOParams",3,ImGuiTableFlags_SizingStretchSame)) {
|
||||
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.0);
|
||||
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0);
|
||||
ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.0);
|
||||
ImGui::TableNextColumn();
|
||||
P(CWSliderScalar("LFO Rate",ImGuiDataType_U8,&ins->multipcm.lfo,&_ZERO,&_SEVEN)); rightClickable
|
||||
ImGui::TableNextColumn();
|
||||
P(CWSliderScalar("PM Depth",ImGuiDataType_U8,&ins->multipcm.vib,&_ZERO,&_SEVEN)); rightClickable
|
||||
ImGui::TableNextColumn();
|
||||
P(CWSliderScalar("AM Depth",ImGuiDataType_U8,&ins->multipcm.am,&_ZERO,&_SEVEN)); rightClickable
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
}
|
||||
if (ins->type==DIV_INS_GB ||
|
||||
(ins->type==DIV_INS_AMIGA && ins->amiga.useWave) ||
|
||||
ins->type==DIV_INS_X1_010 ||
|
||||
|
@ -2718,7 +2820,7 @@ void FurnaceGUI::drawInsEdit() {
|
|||
if (ins->type==DIV_INS_AMIGA) {
|
||||
volMax=64;
|
||||
}
|
||||
if (ins->type==DIV_INS_FM || ins->type==DIV_INS_MIKEY || ins->type==DIV_INS_SU) {
|
||||
if (ins->type==DIV_INS_FM || ins->type==DIV_INS_MIKEY || ins->type==DIV_INS_MULTIPCM || ins->type==DIV_INS_SU) {
|
||||
volMax=127;
|
||||
}
|
||||
if (ins->type==DIV_INS_GB) {
|
||||
|
@ -2771,7 +2873,7 @@ void FurnaceGUI::drawInsEdit() {
|
|||
dutyLabel="Noise";
|
||||
dutyMax=8;
|
||||
}
|
||||
if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL || ins->type==DIV_INS_VRC6_SAW || ins->type==DIV_INS_FDS) {
|
||||
if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL || ins->type==DIV_INS_VRC6_SAW || ins->type==DIV_INS_FDS || ins->type==DIV_INS_MULTIPCM) {
|
||||
dutyMax=0;
|
||||
}
|
||||
if (ins->type==DIV_INS_VERA) {
|
||||
|
@ -2803,6 +2905,7 @@ void FurnaceGUI::drawInsEdit() {
|
|||
if (ins->type==DIV_INS_SAA1099) waveMax=2;
|
||||
if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPZ) waveMax=0;
|
||||
if (ins->type==DIV_INS_MIKEY) waveMax=0;
|
||||
if (ins->type==DIV_INS_MULTIPCM) waveMax=0;
|
||||
if (ins->type==DIV_INS_SU) waveMax=7;
|
||||
if (ins->type==DIV_INS_PET) {
|
||||
waveMax=8;
|
||||
|
@ -2863,6 +2966,11 @@ void FurnaceGUI::drawInsEdit() {
|
|||
panMin=-16;
|
||||
panMax=16;
|
||||
}
|
||||
if (ins->type==DIV_INS_MULTIPCM) {
|
||||
panMin=-7;
|
||||
panMax=7;
|
||||
panSingleNoBit=true;
|
||||
}
|
||||
if (ins->type==DIV_INS_SU) {
|
||||
panMin=-127;
|
||||
panMax=127;
|
||||
|
@ -2915,6 +3023,7 @@ void FurnaceGUI::drawInsEdit() {
|
|||
ins->type==DIV_INS_AY ||
|
||||
ins->type==DIV_INS_AY8930 ||
|
||||
ins->type==DIV_INS_SWAN ||
|
||||
ins->type==DIV_INS_MULTIPCM ||
|
||||
ins->type==DIV_INS_SU) {
|
||||
NORMAL_MACRO(ins->std.phaseResetMacro,0,1,"phaseReset","Phase Reset",32,ins->std.phaseResetMacro.open,true,NULL,false,NULL,0,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[16],0,1,NULL,false);
|
||||
}
|
||||
|
|
|
@ -1006,6 +1006,12 @@ void FurnaceGUI::drawSettings() {
|
|||
updateWindowTitle();
|
||||
}
|
||||
|
||||
bool noMultiSystemB=settings.noMultiSystem;
|
||||
if (ImGui::Checkbox("Display chip names instead of \"multi-system\" in title bar",&noMultiSystemB)) {
|
||||
settings.noMultiSystem=noMultiSystemB;
|
||||
updateWindowTitle();
|
||||
}
|
||||
|
||||
ImGui::Text("Status bar:");
|
||||
if (ImGui::RadioButton("Cursor details##sbar0",settings.statusDisplay==0)) {
|
||||
settings.statusDisplay=0;
|
||||
|
@ -1077,6 +1083,16 @@ void FurnaceGUI::drawSettings() {
|
|||
if (ImGui::Checkbox("Unified instrument/wavetable/sample list",&unifiedDataViewB)) {
|
||||
settings.unifiedDataView=unifiedDataViewB;
|
||||
}
|
||||
if (settings.unifiedDataView) {
|
||||
settings.horizontalDataView=0;
|
||||
}
|
||||
|
||||
ImGui::BeginDisabled(settings.unifiedDataView);
|
||||
bool horizontalDataViewB=settings.horizontalDataView;
|
||||
if (ImGui::Checkbox("Horizontal instrument list",&horizontalDataViewB)) {
|
||||
settings.horizontalDataView=horizontalDataViewB;
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
bool chipNamesB=settings.chipNames;
|
||||
if (ImGui::Checkbox("Use chip names instead of system names",&chipNamesB)) {
|
||||
|
@ -1837,6 +1853,8 @@ void FurnaceGUI::syncSettings() {
|
|||
settings.moveWindowTitle=e->getConfInt("moveWindowTitle",0);
|
||||
settings.hiddenSystems=e->getConfInt("hiddenSystems",0);
|
||||
settings.insLoadAlwaysReplace=e->getConfInt("insLoadAlwaysReplace",1);
|
||||
settings.horizontalDataView=e->getConfInt("horizontalDataView",0);
|
||||
settings.noMultiSystem=e->getConfInt("noMultiSystem",0);
|
||||
|
||||
clampSetting(settings.mainFontSize,2,96);
|
||||
clampSetting(settings.patFontSize,2,96);
|
||||
|
@ -1908,6 +1926,8 @@ void FurnaceGUI::syncSettings() {
|
|||
clampSetting(settings.moveWindowTitle,0,1);
|
||||
clampSetting(settings.hiddenSystems,0,1);
|
||||
clampSetting(settings.insLoadAlwaysReplace,0,1);
|
||||
clampSetting(settings.horizontalDataView,0,1);
|
||||
clampSetting(settings.noMultiSystem,0,1)
|
||||
|
||||
settings.initialSys=e->decodeSysDesc(e->getConfString("initialSys",""));
|
||||
if (settings.initialSys.size()<4) {
|
||||
|
@ -2020,6 +2040,8 @@ void FurnaceGUI::commitSettings() {
|
|||
e->setConf("hiddenSystems",settings.hiddenSystems);
|
||||
e->setConf("initialSys",e->encodeSysDesc(settings.initialSys));
|
||||
e->setConf("insLoadAlwaysReplace",settings.insLoadAlwaysReplace);
|
||||
e->setConf("horizontalDataView",settings.horizontalDataView);
|
||||
e->setConf("noMultiSystem",settings.noMultiSystem);
|
||||
|
||||
// colors
|
||||
for (int i=0; i<GUI_COLOR_MAX; i++) {
|
||||
|
|
Loading…
Reference in a new issue