diff --git a/TODO.md b/TODO.md index 80357e3ef..907e1aeef 100644 --- a/TODO.md +++ b/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) \ No newline at end of file +- add mono/poly note preview button \ No newline at end of file diff --git a/papers/format.md b/papers/format.md index 42488719b..7ab678002 100644 --- a/papers/format.md +++ b/papers/format.md @@ -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 diff --git a/src/engine/engine.h b/src/engine/engine.h index 629bedaea..e9a5db2e0 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -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); diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 7b09ca7d3..8c93ef5bd 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -19,6 +19,7 @@ #include "engine.h" #include "../ta-log.h" +#include "instrument.h" #include "song.h" #include #include @@ -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; iDIV_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; i256) { + logE("too many instruments/out of range!"); + lastError="too many instruments/out of range"; + delete[] file; + return false; + } + + for (int i=0; i=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=2)?96:72; + for (int j=0; jamiga.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; jfm.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; jstd.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; jstd.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; jstd.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"; diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index f9a39724a..8b8a1fb03 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -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; } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index d660c5796..dc1b0b7f5 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -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; /** diff --git a/src/engine/platform/sound/ymfm/ymfm_fm.ipp b/src/engine/platform/sound/ymfm/ymfm_fm.ipp index 4e851737a..d532d7b2e 100644 --- a/src/engine/platform/sound/ymfm/ymfm_fm.ipp +++ b/src/engine/platform/sound/ymfm/ymfm_fm.ipp @@ -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 diff --git a/src/engine/platform/sound/ymfm/ymfm_opz.cpp b/src/engine/platform/sound/ymfm/ymfm_opz.cpp index cc7a7c9d5..94123bf62 100644 --- a/src/engine/platform/sound/ymfm/ymfm_opz.cpp +++ b/src/engine/platform/sound/ymfm/ymfm_opz.cpp @@ -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(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); } diff --git a/src/engine/platform/tx81z.cpp b/src/engine/platform/tx81z.cpp index 056eeaacb..c3f7e9991 100644 --- a/src/engine/platform/tx81z.cpp +++ b/src/engine/platform/tx81z.cpp @@ -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)); diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 981358ed4..9f07c0149 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -53,7 +53,7 @@ std::vector& 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; i0) ret+=" + "; + ret+=getSystemName(song.system[i]); + } + + return ret; } const char* DivEngine::getSystemName(DivSystem sys) { diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp index 0e2d32450..0de552054 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -23,6 +23,7 @@ #include "plot_nolerp.h" #include "guiConst.h" #include +#include 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) { diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index fc95c7ec9..d966b797e 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -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; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 2653153c1..700e55d09 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -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!"); } diff --git a/src/gui/gui.h b/src/gui/gui.h index f4566b552..cfbf90e3f 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -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(""), diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index ed776524d..99c5f4dd6 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -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; isong.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); } diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index b7b07cc43..87ed1fa1f 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -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