FDS: possibly final work

the last thing left to do is the filter, but everything works now
This commit is contained in:
tildearrow 2022-04-05 18:18:14 -05:00
parent 4ba50b433a
commit 280cbb3e39
4 changed files with 122 additions and 6 deletions

View file

@ -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

View file

@ -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);

View file

@ -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

View file

@ -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);