diff --git a/papers/doc/7-systems/fds.md b/papers/doc/7-systems/fds.md new file mode 100644 index 00000000..ee288198 --- /dev/null +++ b/papers/doc/7-systems/fds.md @@ -0,0 +1,26 @@ +# Famicom Disk System + +the Famicom Disk System is an expansion device for the Famicom (known as NES outside Japan), a popular console from the '80's. +as it name implies, it allowed people to play games on specialized floppy disks that could be rewritten on vending machines, therefore reducing the cost of ownership and manufacturing. + +it also offers an additional wavetable sound channel with (somewhat limited) FM capabilities, which is what Furnace supports. + +# effects + +- `10xx`: change wave. +- `11xx`: set modulation depth. +- `12xy`: set modulation speed high byte and toggle on/off. + - `x` is the toggle. a value of 1 turns on the modulator. + - `y` is the speed. +- `13xx`: set modulation speed low byte. +- `14xx`: set modulator position. +- `15xx`: set modulator wave. + - `xx` points to a wavetable. it should (preferably) have a height of 7 with the values mapping to: + - 0: +0 + - 1: +1 + - 2: +2 + - 3: +3 + - 4: reset + - 5: -3 + - 6: -2 + - 7: -1 diff --git a/src/engine/platform/fds.cpp b/src/engine/platform/fds.cpp index b4103748..ede447df 100644 --- a/src/engine/platform/fds.cpp +++ b/src/engine/platform/fds.cpp @@ -63,10 +63,10 @@ const char* DivPlatformFDS::getEffectName(unsigned char effect) { return "11xx: Set modulation depth"; break; case 0x12: - return "12xy: Set modulation frequency high byte (x: enable; y: value)"; + return "12xy: Set modulation speed high byte (x: enable; y: value)"; break; case 0x13: - return "13xx: Set modulation frequency low byte"; + return "13xx: Set modulation speed low byte"; break; case 0x14: return "14xx: Set modulator position"; @@ -163,6 +163,22 @@ void DivPlatformFDS::tick() { //if (!chan[i].keyOff) chan[i].keyOn=true; } } + if (chan[i].std.hadEx1) { // mod depth + chan[i].modOn=chan[i].std.ex1; + chan[i].modDepth=chan[i].std.ex1; + rWrite(0x4084,(chan[i].modOn<<7)|0x40|chan[i].modDepth); + } + if (chan[i].std.hadEx2) { // mod speed + chan[i].modFreq=chan[i].std.ex2; + rWrite(0x4086,chan[i].modFreq&0xff); + rWrite(0x4087,chan[i].modFreq>>8); + } + if (chan[i].std.hadEx3) { // mod position + chan[i].modPos=chan[i].std.ex3; + rWrite(0x4087,0x80|chan[i].modFreq>>8); + rWrite(0x4085,chan[i].modPos); + rWrite(0x4087,chan[i].modFreq>>8); + } if (chan[i].sweepChanged) { chan[i].sweepChanged=false; if (i==0) { @@ -199,6 +215,42 @@ int DivPlatformFDS::dispatch(DivCommand c) { chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; } + if (chan[c.chan].insChanged) { + DivInstrument* ins=parent->getIns(chan[c.chan].ins); + if (ins->fds.initModTableWithFirstWave) { // compatible + if (chan[c.chan].wave==-1) { + DivWavetable* wt=parent->getWave(0); + for (int i=0; i<32; i++) { + if (wt->max<1 || wt->len<1) { + rWrite(0x4040+i,0); + } else { + int data=wt->data[i*MIN(32,wt->len)/32]*7/wt->max; + if (data<0) data=0; + if (data>7) data=7; + chan[c.chan].modTable[i]=data; + } + } + rWrite(0x4087,0x80|chan[c.chan].modFreq>>8); + for (int i=0; i<32; i++) { + rWrite(0x4088,chan[c.chan].modTable[i]); + } + rWrite(0x4087,chan[c.chan].modFreq>>8); + } + } else { // The Familiar Way + chan[c.chan].modDepth=ins->fds.modDepth; + chan[c.chan].modOn=ins->fds.modDepth; + chan[c.chan].modFreq=ins->fds.modSpeed; + rWrite(0x4084,(chan[c.chan].modOn<<7)|0x40|chan[c.chan].modDepth); + rWrite(0x4086,chan[c.chan].modFreq&0xff); + + rWrite(0x4087,0x80|chan[c.chan].modFreq>>8); + for (int i=0; i<32; i++) { + chan[c.chan].modTable[i]=ins->fds.modTable[i]&7; + rWrite(0x4088,chan[c.chan].modTable[i]); + } + rWrite(0x4087,chan[c.chan].modFreq>>8); + } + } chan[c.chan].active=true; chan[c.chan].keyOn=true; chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); @@ -256,7 +308,9 @@ int DivPlatformFDS::dispatch(DivCommand c) { break; case DIV_CMD_FDS_MOD_POS: chan[c.chan].modPos=c.value&0x7f; - // TODO + rWrite(0x4087,0x80|chan[c.chan].modFreq>>8); + rWrite(0x4085,chan[c.chan].modPos); + rWrite(0x4087,chan[c.chan].modFreq>>8); break; case DIV_CMD_FDS_MOD_WAVE: { DivWavetable* wt=parent->getWave(c.value); diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index d626a6b7..8ddbcbe3 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -2116,8 +2116,11 @@ void FurnaceGUI::drawInsEdit() { ImGui::EndTabItem(); } if (ins->type==DIV_INS_FDS) if (ImGui::BeginTabItem("FDS")) { - ImGui::Text("FDS config goes here"); - ImGui::Checkbox("Initialize modulation table with first wavetable (compatibility)",&ins->fds.initModTableWithFirstWave); + float modTable[32]; + ImGui::Checkbox("Compatibility mode",&ins->fds.initModTableWithFirstWave); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("only use for compatibility with .dmf modules!\n- initializes modulation table with first wavetable\n- does not alter modulation parameters on instrument change"); + } if (ImGui::InputInt("Modulation depth",&ins->fds.modDepth,1,32)) { if (ins->fds.modDepth<0) ins->fds.modDepth=0; if (ins->fds.modDepth>63) ins->fds.modDepth=63; @@ -2127,6 +2130,26 @@ void FurnaceGUI::drawInsEdit() { if (ins->fds.modSpeed>4095) ins->fds.modSpeed=4095; } ImGui::Text("Modulation table"); + for (int i=0; i<32; i++) { + modTable[i]=ins->fds.modTable[i]; + } + ImVec2 modTableSize=ImVec2(ImGui::GetContentRegionAvail().x,96.0f*dpiScale); + PlotCustom("ModTable",modTable,32,0,NULL,-4,3,modTableSize,sizeof(float),ImVec4(1.0f,1.0f,1.0f,1.0f),0,NULL,true); + if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { + macroDragStart=ImGui::GetItemRectMin(); + macroDragAreaSize=modTableSize; + macroDragMin=-4; + macroDragMax=3; + macroDragBitOff=0; + macroDragBitMode=false; + macroDragInitialValueSet=false; + macroDragInitialValue=false; + macroDragLen=32; + macroDragActive=true; + macroDragCTarget=(unsigned char*)ins->fds.modTable; + macroDragChar=true; + processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); \ + } ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Macros")) { @@ -2268,6 +2291,10 @@ void FurnaceGUI::drawInsEdit() { ex1Max=252; ex2Max=2; } + if (ins->type==DIV_INS_FDS) { + ex1Max=63; + ex2Max=4095; + } if (ins->type==DIV_INS_SAA1099) ex1Max=8; if (settings.macroView==0) { // modern view @@ -2296,6 +2323,8 @@ void FurnaceGUI::drawInsEdit() { NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Envelope Mode",160,ins->std.ex1MacroOpen,true,x1_010EnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); } else if (ins->type==DIV_INS_N163) { NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Waveform len.",160,ins->std.ex1MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); + } else if (ins->type==DIV_INS_FDS) { + NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Mod Depth",160,ins->std.ex1MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); } else { NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Duty",160,ins->std.ex1MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); } @@ -2305,6 +2334,8 @@ void FurnaceGUI::drawInsEdit() { NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Resonance",64,ins->std.ex2MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); } else if (ins->type==DIV_INS_N163) { NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Waveform update",64,ins->std.ex2MacroOpen,true,n163UpdateBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); + } else if (ins->type==DIV_INS_FDS) { + NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Mod Speed",160,ins->std.ex2MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); } else { NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Envelope",ex2Bit?64:160,ins->std.ex2MacroOpen,ex2Bit,ayEnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); } @@ -2327,6 +2358,9 @@ void FurnaceGUI::drawInsEdit() { NORMAL_MACRO(ins->std.fbMacro,ins->std.fbMacroLen,ins->std.fbMacroLoop,ins->std.fbMacroRel,0,252,"fb","Wave len. to Load",160,ins->std.fbMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[8],0,252,NULL,false); NORMAL_MACRO(ins->std.fmsMacro,ins->std.fmsMacroLen,ins->std.fmsMacroLoop,ins->std.fmsMacroRel,0,2,"fms","Waveform load",64,ins->std.fmsMacroOpen,true,n163UpdateBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[9],0,2,NULL,false); } + if (ins->type==DIV_INS_FDS) { + NORMAL_MACRO(ins->std.ex3Macro,ins->std.ex3MacroLen,ins->std.ex3MacroLoop,ins->std.ex3MacroRel,0,127,"ex3","Mod Position",160,ins->std.ex3MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,2,NULL,false); + } MACRO_END; } else { // classic view diff --git a/src/gui/plot_nolerp.cpp b/src/gui/plot_nolerp.cpp index 1c1cf94a..c8970c95 100644 --- a/src/gui/plot_nolerp.cpp +++ b/src/gui/plot_nolerp.cpp @@ -332,6 +332,8 @@ int PlotCustomEx(ImGuiPlotType plot_type, const char* label, float (*values_gett scale_max = v_max; } + if (blockMode) scale_max+=1.0f; + ImU32 bgColor=ImGui::GetColorU32(ImVec4(color.x,color.y,color.z,color.w*0.15)); ImGui::RenderFrame(frame_bb.Min, frame_bb.Max, ImGui::GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); @@ -372,7 +374,7 @@ int PlotCustomEx(ImGuiPlotType plot_type, const char* label, float (*values_gett float v0 = values_getter(data, (0) % values_count); float t0 = 0.0f; ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) ); // Point in the normalized space of our target rectangle - float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (1 + scale_min * inv_scale) : (scale_min < 0.0f ? 0.0f : 1.0f); // Where does the zero line stands + float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (1 + (blockMode?(scale_min-0.5):scale_min) * inv_scale) : (scale_min < 0.0f ? 0.0f : 1.0f); // Where does the zero line stands const ImU32 col_base = ImGui::GetColorU32(color); const ImU32 col_hovered = ImGui::GetColorU32(color);