DOSBox-X
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
src/hardware/pc98_fm.cpp
00001 
00002 #include "dosbox.h"
00003 #include "setup.h"
00004 #include "video.h"
00005 #include "pic.h"
00006 #include "vga.h"
00007 #include "inout.h"
00008 #include "programs.h"
00009 #include "support.h"
00010 #include "setup.h"
00011 #include "timer.h"
00012 #include "mem.h"
00013 #include "util_units.h"
00014 #include "control.h"
00015 #include "pc98_cg.h"
00016 #include "pc98_dac.h"
00017 #include "pc98_gdc.h"
00018 #include "pc98_gdc_const.h"
00019 #include "joystick.h"
00020 #include "regs.h"
00021 #include "mixer.h"
00022 #include "callback.h"
00023 
00024 #include <string.h>
00025 #include <stdlib.h>
00026 #include <string>
00027 #include <stdio.h>
00028 
00029 using namespace std;
00030 
00031 #if defined(_MSC_VER)
00032 # pragma warning(disable:4244) /* const fmath::local::uint64_t to double possible loss of data */
00033 #endif
00034 
00035 #include <map>
00036 
00037 #include "np2glue.h"
00038 
00039 MixerChannel *pc98_mixer = NULL;
00040 
00041 NP2CFG pccore;
00042 
00043 extern unsigned char pc98_mem_msw_m[8];
00044 bool pc98_soundbios_rom_load = true;
00045 bool pc98_soundbios_enabled = false;
00046 
00047 extern "C" unsigned char *CGetMemBase() {
00048     return MemBase;
00049 }
00050 
00051 extern "C" void sound_sync(void) {
00052     if (pc98_mixer) pc98_mixer->FillUp();
00053 }
00054 
00055 extern "C" void _TRACEOUT(const char *fmt,...) {
00056     va_list va;
00057     char tmp[512];
00058 
00059     va_start(va,fmt);
00060 
00061     vsnprintf(tmp,sizeof(tmp),fmt,va);
00062     LOG_MSG("PC98FM TRACEOUT: %s",tmp);
00063 
00064     va_end(va);
00065 }
00066 
00067 void getbiospath(OEMCHAR *path, const OEMCHAR *fname, int maxlen) {
00068     LOG_MSG("PC98FM getbiospath fname='%s'",fname);
00069     snprintf(path,(size_t)maxlen,"%s",fname);
00070 }
00071 
00072 enum {
00073         JOY_UP_BIT                  = (1u << 0u),
00074         JOY_DOWN_BIT            = (1u << 1u),
00075         JOY_LEFT_BIT            = (1u << 2u),
00076         JOY_RIGHT_BIT           = (1u << 3u),
00077         JOY_RAPIDBTN1_BIT       = (1u << 4u),
00078         JOY_RAPIDBTN2_BIT       = (1u << 5u),
00079         JOY_BTN1_BIT            = (1u << 6u),
00080         JOY_BTN2_BIT            = (1u << 7u)
00081 };
00082 
00083 
00084 REG8 joymng_getstat(void) {
00085     unsigned char r = 0xFF;
00086 
00087     if (JOYSTICK_IsEnabled(0)) {
00088         if (JOYSTICK_GetButton(0,0))
00089             r &= ~JOY_BTN1_BIT;
00090         if (JOYSTICK_GetButton(0,1))
00091             r &= ~JOY_BTN2_BIT;
00092 
00093         float x = JOYSTICK_GetMove_X(0);
00094         float y = JOYSTICK_GetMove_Y(0);
00095 
00096         if (x >= 0.5)
00097             r &= ~JOY_RIGHT_BIT;
00098         else if (x <= -0.5)
00099             r &= ~JOY_LEFT_BIT;
00100 
00101         if (y >= 0.5)
00102             r &= ~JOY_DOWN_BIT;
00103         else if (y <= -0.5)
00104             r &= ~JOY_UP_BIT;
00105     }
00106 
00107     return r;
00108 }
00109 
00110 REG8 keystat_getjoy(void) {
00111     return 0xFF;
00112 }
00113 
00114 struct CBUS4PORT {
00115     IOINP       inp;
00116     IOOUT       out;
00117 
00118     CBUS4PORT() : inp(NULL), out(NULL) { }
00119 };
00120 
00121 static std::map<UINT,CBUS4PORT> cbuscore_map;
00122 
00123 void pc98_fm86_write(Bitu port,Bitu val,Bitu iolen) {
00124     (void)iolen;//UNUSED
00125     const auto &cbusm = cbuscore_map[port];
00126     const auto &func = cbusm.out;
00127     if (func) func(port,val);
00128 }
00129 
00130 Bitu pc98_fm86_read(Bitu port,Bitu iolen) {
00131     (void)iolen;//UNUSED
00132     const auto &cbusm = cbuscore_map[port];
00133     const auto &func = cbusm.inp;
00134     if (func) return func(port);
00135     return ~0ul;
00136 }
00137 
00138 // four I/O ports, 2 ports apart
00139 void cbuscore_attachsndex(UINT port, const IOOUT *out, const IOINP *inp) {
00140     LOG_MSG("cbuscore_attachsndex(port=0x%x)",port);
00141 
00142     for (unsigned int i=0;i < 4;i++) {
00143         auto &cbusm = cbuscore_map[port+(i*2)];
00144 
00145         IO_RegisterReadHandler(port+(i*2),pc98_fm86_read,IO_MB);
00146         cbusm.inp = inp[i];
00147 
00148         IO_RegisterWriteHandler(port+(i*2),pc98_fm86_write,IO_MB);
00149         cbusm.out = out[i];
00150     }
00151 }
00152 
00153 void pic_setirq(REG8 irq) {
00154     PIC_ActivateIRQ(irq);
00155 }
00156 
00157 void pic_resetirq(REG8 irq) {
00158     PIC_DeActivateIRQ(irq);
00159 }
00160 
00161 #include "sound.h"
00162 #include "fmboard.h"
00163 
00164 // wrapper for fmtimer events
00165 void fmport_a(NEVENTITEM item);
00166 void fmport_b(NEVENTITEM item);
00167 
00168 static void fmport_a_pic_event(Bitu val) {
00169     (void)val;//UNUSED
00170     fmport_a(NULL);
00171 }
00172 
00173 static void fmport_b_pic_event(Bitu val) {
00174     (void)val;//UNUSED
00175     fmport_b(NULL);
00176 }
00177 
00178 // save state support
00179 void *fmport_a_pic_event_PIC_Event = (void*)((uintptr_t)fmport_a_pic_event);
00180 void *fmport_b_pic_event_PIC_Event = (void*)((uintptr_t)fmport_b_pic_event);
00181 
00182 extern "C" void pc98_fm_dosbox_fmtimer_setevent(unsigned int n,double dt) {
00183     PIC_EventHandler func = n ? fmport_b_pic_event : fmport_a_pic_event;
00184 
00185     PIC_RemoveEvents(func);
00186     PIC_AddEvent(func,dt);
00187 }
00188 
00189 extern "C" void pc98_fm_dosbox_fmtimer_clearevent(unsigned int n) {
00190     PIC_RemoveEvents(n ? fmport_b_pic_event : fmport_a_pic_event);
00191 }
00192 
00193 // mixer callback
00194 
00195 static void pc98_mix_CallBack(Bitu len) {
00196     unsigned int s = len;
00197 
00198     if (s > (sizeof(MixTemp)/sizeof(Bit32s)/2))
00199         s = (sizeof(MixTemp)/sizeof(Bit32s)/2);
00200 
00201     memset(MixTemp,0,s * sizeof(Bit32s) * 2);
00202 
00203     opngen_getpcm(NULL, (SINT32*)MixTemp, s);
00204     tms3631_getpcm(&tms3631, (SINT32*)MixTemp, s);
00205 
00206     for (unsigned int i=0;i < 3;i++)
00207         psggen_getpcm(&__psg[i], (SINT32*)MixTemp, s);
00208  
00209     // NTS: _RHYTHM is a struct with the same initial layout as PCMMIX
00210     pcmmix_getpcm((PCMMIX)(&rhythm), (SINT32*)MixTemp, s);
00211 #if defined(SUPPORT_PX)
00212     pcmmix_getpcm((PCMMIX)(&rhythm2), (SINT32*)MixTemp, s);
00213     pcmmix_getpcm((PCMMIX)(&rhythm3), (SINT32*)MixTemp, s);
00214 #endif  // defined(SUPPORT_PX)
00215 
00216     adpcm_getpcm(&adpcm, (SINT32*)MixTemp, s);
00217 #if defined(SUPPORT_PX)
00218     adpcm_getpcm(&adpcm2, (SINT32*)MixTemp, s);
00219     adpcm_getpcm(&adpcm3, (SINT32*)MixTemp, s);
00220 #endif  // defined(SUPPORT_PX)
00221 
00222     pcm86gen_getpcm(NULL, (SINT32*)MixTemp, s);
00223 
00224     pc98_mixer->AddSamples_s32(s, (Bit32s*)MixTemp);
00225 }
00226 
00227 static bool pc98fm_init = false;
00228 
00229 extern "C" {
00230 UINT8 fmtimer_irq2index(const UINT8 irq);
00231 UINT8 fmtimer_index2irq(const UINT8 index);
00232 void fmboard_on_reset();
00233 void rhythm_deinitialize(void);
00234 }
00235 
00236 UINT8 board86_encodeirqidx(const unsigned char idx) {
00237     /* see board86.c to understand what this is about */
00238     return  ((idx & 1) ? 0x08 : 0x00) +
00239             ((idx & 2) ? 0x04 : 0x00);
00240 }
00241 
00242 UINT8 board26k_encodeirqidx(const unsigned char idx) {
00243     /* see board26k.c to understand what this is about */
00244     return  (idx << 6);
00245 }
00246 
00247 void PC98_FM_Destroy(Section *sec) {
00248     (void)sec;//UNUSED
00249     if (pc98fm_init) {
00250         rhythm_deinitialize();
00251     }
00252 
00253     if (pc98_mixer) {
00254         MIXER_DelChannel(pc98_mixer);
00255         pc98_mixer = NULL;
00256     }
00257 }
00258 
00259 void pc98_set_msw4_soundbios(void)
00260 {
00261     /* Set MSW4 bit 3 for sound BIOS. */
00262     if(pc98_soundbios_enabled)
00263         pc98_mem_msw_m[3/*MSW4*/] |= 0x8;
00264     else
00265         pc98_mem_msw_m[3/*MSW4*/] &= ~((unsigned char)0x8);
00266 }
00267 
00268 static CALLBACK_HandlerObject soundbios_callback;
00269 
00270 // for more information see chapter 13 of [https://ia801305.us.archive.org/8/items/PC9800TechnicalDataBookBIOS1992/PC-9800TechnicalDataBook_BIOS_1992_text.pdf]
00271 static Bitu SOUNDROM_INTD2_PC98_Handler(void) {
00272     const char *call_name = "?";
00273 
00274     switch (reg_ah) {
00275         case 0x00:  // INITIALIZE
00276             call_name = "INITIALIZE";
00277             goto unknown;
00278         case 0x01:  // PLAY
00279             call_name = "PLAY";
00280             goto unknown;
00281         case 0x02:  // CLEAR
00282             call_name = "CLEAR";
00283             goto unknown;
00284         case 0x10:  // READ REG
00285             call_name = "READ REG";
00286             goto unknown;
00287         case 0x11:  // WRITE REG
00288             call_name = "WRITE REG";
00289             goto unknown;
00290         case 0x12:  // SET TOUCH
00291             call_name = "SET TOUCH";
00292             goto unknown;
00293         case 0x13:  // NOTE
00294             call_name = "NOTE";
00295             goto unknown;
00296         case 0x14:  // SET LENGTH
00297             call_name = "SET LENGTH";
00298             goto unknown;
00299         case 0x15:  // SET TEMPO
00300             call_name = "SET TEMPO";
00301             goto unknown;
00302         case 0x16:  // SET PARA BLOCK
00303             call_name = "SET PARA BLOCK";
00304             goto unknown;
00305         case 0x17:  // READ PARA
00306             call_name = "READ PARA";
00307             goto unknown;
00308         case 0x18:  // WRITE PARA
00309             call_name = "WRITE PARA";
00310             goto unknown;
00311         case 0x19:  // ALL STOP
00312             call_name = "ALL STOP";
00313             goto unknown;
00314         case 0x1A:  // CONT PLAY
00315             call_name = "CONT PLAY";
00316             goto unknown;
00317         case 0x1B:  // MODU ON
00318             call_name = "MODU ON";
00319             goto unknown;
00320         case 0x1C:  // MODU OFF
00321             call_name = "MODU OFF";
00322             goto unknown;
00323         case 0x1D:  // SET INT COND
00324             call_name = "SET INT COND";
00325             goto unknown;
00326         case 0x1E:  // HOLD STATE
00327             call_name = "HOLD STATE";
00328             goto unknown;
00329         case 0x1F:  // SET VOLUME
00330             call_name = "SET VOLUME";
00331             goto unknown;
00332         default:
00333         unknown:
00334             LOG_MSG("PC-98 SOUND BIOS (INT D2h) call '%s' with AX=%04X BX=%04X CX=%04X DX=%04X SI=%04X DI=%04X DS=%04X ES=%04X",
00335                     call_name,
00336                     reg_ax,
00337                     reg_bx,
00338                     reg_cx,
00339                     reg_dx,
00340                     reg_si,
00341                     reg_di,
00342                     SegValue(ds),
00343                     SegValue(es));
00344             break;
00345     }
00346 
00347     // guessing, from Yksoft1's patch
00348     reg_ah = 0;
00349 
00350     return CBRET_NONE;
00351 }
00352 
00353 bool LoadSoundBIOS(void) {
00354     FILE *fp;
00355 
00356     if (!pc98_soundbios_rom_load) return false;
00357 
00358     fp = fopen("SOUND.ROM","rb");
00359     if (!fp) fp = fopen("sound.rom","rb");
00360     if (!fp) return false;
00361 
00362     if (fread(MemBase+0xCC000,0x4000,1,fp) != 1) {
00363         LOG_MSG("PC-98 SOUND.ROM failed to read 16k");
00364         fclose(fp);
00365         return false;
00366     }
00367 
00368     LOG_MSG("PC-98 SOUND.ROM loaded into memory");
00369     fclose(fp);
00370     return true;
00371 }
00372 
00373 bool PC98_FM_SoundBios_Enabled(void) {
00374     return pc98_soundbios_enabled;
00375 }
00376 
00377 void PC98_FM_OnEnterPC98(Section *sec) {
00378     (void)sec;//UNUSED
00379     Section_prop * pc98_section=static_cast<Section_prop *>(control->GetSection("pc98"));
00380         assert(pc98_section != NULL);
00381     bool was_pc98fm_init = pc98fm_init;
00382 
00383     if (!pc98fm_init) {
00384         unsigned char fmirqidx;
00385         unsigned int baseio;
00386         std::string board;
00387         int irq;
00388 
00389         soundbios_callback.Uninstall();
00390 
00391         board = pc98_section->Get_string("pc-98 fm board");
00392         if (board == "off" || board == "false") {
00393             /* Don't enable Sound BIOS if sound board itself is disabled. */
00394             pc98_soundbios_enabled = false;
00395             pc98_set_msw4_soundbios();
00396             return;             
00397         }
00398 
00399         irq = pc98_section->Get_int("pc-98 fm board irq");
00400         baseio = (unsigned int)pc98_section->Get_hex("pc-98 fm board io port");
00401 
00402         pc98_soundbios_enabled = pc98_section->Get_bool("pc-98 sound bios");
00403         pc98_soundbios_rom_load = pc98_section->Get_bool("pc-98 load sound bios rom file");
00404         pc98_set_msw4_soundbios();
00405 
00406         if (pc98_soundbios_enabled) {
00407             /* TODO: Load SOUND.ROM to CC000h - CFFFFh when Sound BIOS is enabled? 
00408              * Or simulate Sound BIOS calls ourselves? */
00409             if (LoadSoundBIOS()) {
00410                 /* good! */
00411             }
00412             else {
00413                 soundbios_callback.Install(&SOUNDROM_INTD2_PC98_Handler,CB_IRET,"Sound ROM INT D2h");
00414 
00415                 /* fake INT D2h sound BIOS entry point at CEE0:0000.
00416                  * Unlike the LIO interface there's only one interrupt (if Neko Project II code is correct) */
00417                 Bit32u ofs = 0xCEE0u << 4u;
00418                 Bit32u callback_addr = soundbios_callback.Get_RealPointer();
00419 
00420                 phys_writed(ofs+0,0x0001);      // number of entries
00421                 phys_writew(ofs+4,0xD2);        // INT D2h entry point
00422                 phys_writew(ofs+6,0x08);
00423                 phys_writeb(ofs+8,0xEA);        // JMP FAR <callback>
00424                 phys_writed(ofs+9,callback_addr);
00425             }
00426         }
00427 
00428         /* Manual testing shows PC-98 games like it when the board is on IRQ 12 */
00429         if (irq == 0) irq = 12;
00430 
00431         fmirqidx = fmtimer_irq2index(irq);
00432 
00433         pc98fm_init = true;
00434 
00435         unsigned int rate = 44100;
00436 
00437         memset(&pccore,0,sizeof(pccore));
00438         pccore.samplingrate = 44100;
00439                 pccore.baseclock = PIT_TICK_RATE;
00440                 pccore.multiple = 1;
00441         for (unsigned int i=0;i < 6;i++) pccore.vol14[i] = 0xFF;
00442         pccore.vol_fm = 128;
00443         pccore.vol_ssg = 128;
00444         pccore.vol_adpcm = 128;
00445         pccore.vol_pcm = 128;
00446         pccore.vol_rhythm = 128;
00447 
00448         //      fddmtrsnd_initialize(rate);
00449         //      beep_initialize(rate);
00450         //      beep_setvol(3);
00451         tms3631_initialize(rate);
00452         tms3631_setvol(pccore.vol14);
00453         opngen_initialize(rate);
00454         opngen_setvol(pccore.vol_fm);
00455         psggen_initialize(rate);
00456         psggen_setvol(pccore.vol_ssg);
00457         rhythm_initialize(rate);
00458         rhythm_setvol(pccore.vol_rhythm);
00459         adpcm_initialize(rate);
00460         adpcm_setvol(pccore.vol_adpcm);
00461         pcm86gen_initialize(rate);
00462         pcm86gen_setvol(pccore.vol_pcm);
00463 
00464         if (board == "board86c" || board == "auto") {
00465             if (baseio == 0 || baseio == 0x188) { /* default */
00466                 pccore.snd86opt |= 0x01;
00467                 baseio = 0x188;
00468             }
00469             else {
00470                 baseio = 0x288;
00471             }
00472 
00473             pccore.snd86opt += board86_encodeirqidx(fmirqidx);
00474 
00475             LOG_MSG("PC-98 FM board is PC-9801-86c at baseio=0x%x irq=%d",baseio,fmtimer_index2irq(fmirqidx));
00476             fmboard_reset(&np2cfg, 0x14);
00477         }
00478         else if (board == "board86") {
00479             if (baseio == 0 || baseio == 0x188) { /* default */
00480                 pccore.snd86opt |= 0x01;
00481                 baseio = 0x188;
00482             }
00483             else {
00484                 baseio = 0x288;
00485             }
00486 
00487             pccore.snd86opt += board86_encodeirqidx(fmirqidx);
00488 
00489             LOG_MSG("PC-98 FM board is PC-9801-86 at baseio=0x%x irq=%d",baseio,fmtimer_index2irq(fmirqidx));
00490             fmboard_reset(&np2cfg, 0x04);
00491         }
00492         else if (board == "board26k") {
00493             if (baseio == 0x188) {
00494                 pccore.snd26opt |= 0x10;
00495                 baseio = 0x188;
00496             }
00497             else { /* default */
00498                 baseio = 0x088;
00499             }
00500 
00501             pccore.snd26opt += board26k_encodeirqidx(fmirqidx);
00502 
00503             LOG_MSG("PC-98 FM board is PC-9801-26k at baseio=0x%x irq=%d",baseio,fmtimer_index2irq(fmirqidx));
00504             fmboard_reset(&np2cfg, 0x02);
00505         }
00506         else if (board == "board14") {
00507             /* Apparently board14 is always IRQ 12, port 88h */
00508             LOG_MSG("PC-98 FM board is PC-9801-14 at baseio=0x%x irq=%d",0x88,12);
00509             LOG_MSG("WARNING: This is not yet implemented!"); // board14 emulation requires emulation of a PIT timer (8253) on the board itself
00510             fmboard_reset(&np2cfg, 0x01);
00511         }
00512         else {
00513             if (baseio == 0 || baseio == 0x188) { /* default */
00514                 pccore.snd86opt |= 0x01;
00515                 baseio = 0x188;
00516             }
00517             else {
00518                 baseio = 0x288;
00519             }
00520 
00521             pccore.snd86opt += board86_encodeirqidx(fmirqidx);
00522 
00523             LOG_MSG("PC-98 FM board is PC-9801-86c at baseio=0x%x irq=%d",baseio,fmtimer_index2irq(fmirqidx));
00524             fmboard_reset(&np2cfg, 0x14);   // board86c, a good default
00525         }
00526 
00527         fmboard_bind();
00528         fmboard_extenable(true);
00529 
00530         // WARNING: Some parts of the borrowed code assume 44100, 22050, or 11025 and
00531         //          will misrender if given any other sample rate (especially the OPNA synth).
00532 
00533         pc98_mixer = MIXER_AddChannel(pc98_mix_CallBack, rate, "PC-98");
00534         pc98_mixer->Enable(true);
00535     }
00536 
00537     if (was_pc98fm_init) {
00538         fmboard_on_reset();
00539         fmboard_bind(); // FIXME: Re-binds I/O ports as well
00540         fmboard_extenable(true);
00541     }
00542 }
00543