diff --git a/src/dosbox.cpp b/src/dosbox.cpp index 00bd35b..947e0f3 100644 --- a/src/dosbox.cpp +++ b/src/dosbox.cpp @@ -748,6 +748,9 @@ void DOSBOX_Init(void) { Pbool = secprop->Add_bool("timed",Property::Changeable::WhenIdle,true); Pbool->Set_help("enable timed intervals for axis. Experiment with this option, if your joystick drifts (away)."); + Pbool = secprop->Add_bool("ad1000",Property::Changeable::WhenIdle,false); + Pbool->Set_help("Simulate a Real Time Devices AD1000 A/D board at ports 0x200-0x209 (replaces the game port)."); + Pbool = secprop->Add_bool("autofire",Property::Changeable::WhenIdle,false); Pbool->Set_help("continuously fires as long as you keep the button pressed."); diff --git a/src/hardware/joystick.cpp b/src/hardware/joystick.cpp index f25e832..032e067 100644 --- a/src/hardware/joystick.cpp +++ b/src/hardware/joystick.cpp @@ -26,6 +26,10 @@ #include "pic.h" #include "support.h" +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + //TODO: higher axis can't be mapped. Find out why again @@ -284,6 +288,82 @@ float JOYSTICK_GetMove_Y(Bitu which) { return stick[1].ypos; } +/* + * Simulated Real Time Devices AD1000 (12-bit, 8-channel A/D board), added for + * the OmniTron Bio-FeedBack player. Ports follow the board's sample code that + * OmniTron includes (MUX.PSL, ADC.PSL): a write to BASE+n-1 selects channel n, + * a write to BASE+8 starts a conversion, and BASE+8 / BASE+9 return the high + * 8 bits / low 4 bits (top nibble) of an offset-binary result around 0x800. + * Every signal is generated here. Channels 1-4: skin temperature that warms + * slowly as the client relaxes, mapped through OmniTron's own calibration, + * F = (6.042*R+7050)/(81.1-0.0102*R). Channel 5: finger pulse. 6-7: EMG. + */ +#define AD1000_BASE 0x200 +static IO_ReadHandleObject ad1000_read_obj; +static IO_WriteHandleObject ad1000_mux_obj, ad1000_conv_obj; +static Bitu ad1000_channel = 1; +static Bitu ad1000_result = 0x800; +static double ad1000_t0 = -1.0; +static Bit32u ad1000_rng = 0x2545F491; + +static double ad1000_uniform(void) { + ad1000_rng ^= ad1000_rng << 13; ad1000_rng ^= ad1000_rng >> 17; ad1000_rng ^= ad1000_rng << 5; + return (ad1000_rng & 0xffffff) / 16777216.0; +} + +static double ad1000_skin_temp(Bitu ch, double t) { + static const double base[4] = { 90.6, 90.3, 91.0, 90.4 }; + static const double rise[4] = { 2.6, 2.3, 2.1, 2.5 }; + static const double phase[4] = { 0.0, 1.3, 2.1, 3.4 }; + Bitu i = ch - 1; + double warm = rise[i] * (1.0 - exp(-t / 240.0)); + double sway = 0.12 * sin(2.0 * M_PI * t / 47.0 + phase[i]) + 0.05 * sin(2.0 * M_PI * t / 13.7 + 2.0 * phase[i]); + return base[i] + warm + sway; +} + +static double ad1000_counts(Bitu ch, double t) { + if (ch >= 1 && ch <= 4) { + double f = ad1000_skin_temp(ch, t); + return (81.1 * f - 7050.0) / (6.042 + 0.0102 * f) + (ad1000_uniform() - 0.5) * 2.0; + } + if (ch == 5) { + double bpm = 72.0 - 7.0 * (1.0 - exp(-t / 180.0)) + 2.5 * sin(2.0 * M_PI * t / 9.0); + double beat = fmod(t * bpm / 60.0, 1.0); + double wave = exp(-pow((beat - 0.15) / 0.06, 2.0)) + 0.35 * exp(-pow((beat - 0.42) / 0.10, 2.0)); + return 600.0 * wave - 200.0 + (ad1000_uniform() - 0.5) * 20.0; + } + if (ch == 6 || ch == 7) { + double level = 150.0 + 250.0 * exp(-t / 200.0); + return level * ad1000_uniform(); + } + return (ad1000_uniform() - 0.5) * 4.0; +} + +static void ad1000_write_mux(Bitu port, Bitu /*val*/, Bitu /*iolen*/) { + ad1000_channel = port - AD1000_BASE + 1; +} + +static void ad1000_write_conv(Bitu port, Bitu /*val*/, Bitu /*iolen*/) { + if (port != AD1000_BASE + 8) return; + double now = PIC_FullIndex() / 1000.0; + if (ad1000_t0 < 0) ad1000_t0 = now; + int code = (int)floor(ad1000_counts(ad1000_channel, now - ad1000_t0) + 0.5) + 0x800; + if (code < 0) code = 0; + if (code > 0xfff) code = 0xfff; + ad1000_result = (Bitu)code; +} + +static Bitu ad1000_read(Bitu port, Bitu /*iolen*/) { + if (port == AD1000_BASE + 8) return (ad1000_result >> 4) & 0xff; + return (ad1000_result & 0xf) << 4; +} + +static void AD1000_Install(void) { + ad1000_mux_obj.Install(AD1000_BASE, ad1000_write_mux, IO_MB, 8); + ad1000_conv_obj.Install(AD1000_BASE + 8, ad1000_write_conv, IO_MB, 2); + ad1000_read_obj.Install(AD1000_BASE + 8, ad1000_read, IO_MB, 2); +} + class JOYSTICK:public Module_base{ private: IO_ReadHandleObject ReadHandler; @@ -303,7 +383,9 @@ public: else joytype = JOY_AUTO; bool timed = section->Get_bool("timed"); - if (timed) { + if (section->Get_bool("ad1000")) { + AD1000_Install(); + } else if (timed) { ReadHandler.Install(0x201,read_p201_timed,IO_MB); WriteHandler.Install(0x201,write_p201_timed,IO_MB); } else {