mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-26 22:43:01 +00:00
FDS: possibly final work
the last thing left to do is the filter, but everything works now
This commit is contained in:
parent
4ba50b433a
commit
280cbb3e39
4 changed files with 122 additions and 6 deletions
26
papers/doc/7-systems/fds.md
Normal file
26
papers/doc/7-systems/fds.md
Normal 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
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue