/* * Copyright (C) 2002-2004 The DOSBox Team * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include "dosbox.h" #include "inout.h" #include "mixer.h" #include "pic.h" #include "hardware.h" #include "setup.h" #include "mapper.h" #include "mem.h" /* Thanks to vdmsound for nice simple way to implement this */ #define logerror #ifdef _MSC_VER /* Disable recurring warnings */ # pragma warning ( disable : 4018 ) # pragma warning ( disable : 4244 ) #endif struct __MALLOCPTR { void* m_ptr; __MALLOCPTR(void) : m_ptr(NULL) { } __MALLOCPTR(void* src) : m_ptr(src) { } void* operator=(void* rhs) { return (m_ptr = rhs); } operator int*() const { return (int*)m_ptr; } operator int**() const { return (int**)m_ptr; } operator char*() const { return (char*)m_ptr; } }; namespace OPL2 { #define HAS_YM3812 1 #include "fmopl.c" void TimerOver(Bitu val){ YM3812TimerOver(val>>8,val & 0xff); } void TimerHandler(int channel,double interval_Sec) { if (interval_Sec==0.0) return; PIC_AddEvent(TimerOver,1000.0f*interval_Sec,channel); } } #undef OSD_CPU_H #undef TL_TAB_LEN namespace THEOPL3 { #define HAS_YMF262 1 #include "ymf262.c" void TimerOver(Bitu val){ YMF262TimerOver(val>>8,val & 0xff); } void TimerHandler(int channel,double interval_Sec) { if (interval_Sec==0.0) return; PIC_AddEvent(TimerOver,1000.0f*interval_Sec,channel); } } #define OPL2_INTERNAL_FREQ 3600000 // The OPL2 operates at 3.6MHz #define OPL3_INTERNAL_FREQ 14400000 // The OPL3 operates at 14.4MHz #define RAW_SIZE 1024 static struct { bool active; OPL_Mode mode; MixerChannel * chan; Bit32u last_used; Bit16s mixbuf[2][128]; struct { FILE * handle; bool capturing; Bit32u start; Bit32u last; Bit8u index; Bit8u buffer[RAW_SIZE+8]; Bit8u regs[2][256]; Bit32u used; Bit32u done; Bit8u cmd[2]; bool opl3; bool dualopl2; } raw; } opl; static void OPL_CallBack(Bitu len) { /* Check for size to update and check for 1 ms updates to the opl registers */ Bitu i; switch(opl.mode) { case OPL_opl2: OPL2::YM3812UpdateOne(0,(OPL2::INT16 *)MixTemp,len); opl.chan->AddSamples_m16(len,(Bit16s*)MixTemp); break; case OPL_opl3: THEOPL3::YMF262UpdateOne(0,(OPL2::INT16 *)MixTemp,len); opl.chan->AddSamples_s16(len,(Bit16s*)MixTemp); break; case OPL_dualopl2: OPL2::YM3812UpdateOne(0,(OPL2::INT16 *)opl.mixbuf[0],len); OPL2::YM3812UpdateOne(1,(OPL2::INT16 *)opl.mixbuf[1],len); for (i=0;iAddSamples_s16(len,(Bit16s*)MixTemp); break; } if ((PIC_Ticks-opl.last_used)>30000) { opl.chan->Enable(false); opl.active=false; } } static Bitu OPL_Read(Bitu port,Bitu iolen) { Bitu addr=port & 3; switch (opl.mode) { case OPL_opl2: return OPL2::YM3812Read(0,addr); case OPL_dualopl2: return OPL2::YM3812Read(addr>>1,addr); case OPL_opl3: return THEOPL3::YMF262Read(0,addr); } return 0xff; } static void OPL_RawAdd(Bitu index,Bitu val); void OPL_Write(Bitu port,Bitu val,Bitu iolen) { opl.last_used=PIC_Ticks; if (!opl.active) { opl.active=true; opl.chan->Enable(true); } port&=3; if (port&1) { Bitu index=port>>1; opl.raw.regs[index][opl.raw.cmd[index]]=val; if (opl.raw.capturing) OPL_RawAdd(index,val); } else opl.raw.cmd[port>>1]=val; if (!port) adlib_commandreg=val; switch (opl.mode) { case OPL_opl2: OPL2::YM3812Write(0,port,val); break; case OPL_opl3: THEOPL3::YMF262Write(0,port,val); break; case OPL_dualopl2: OPL2::YM3812Write(port>>1,port,val); break; } } static Bit8u dro_header[]={ 'D','B','R','A', /* 0x00, Bit32u ID */ 'W','O','P','L', /* 0x04, Bit32u ID */ 0x0,0x00, /* 0x08, Bit16u version low */ 0x1,0x00, /* 0x09, Bit16u version high */ 0x0,0x0,0x0,0x0, /* 0x0c, Bit32u total milliseconds */ 0x0,0x0,0x0,0x0, /* 0x10, Bit32u total data */ 0x0,0x0,0x0,0x0 /* 0x14, Bit32u Type 0=opl2,1=opl3,2=dual-opl2 */ }; /* Commands 0x00 Bit8u, millisecond delay+1 0x02 none, Use the low index/data pair 0x03 none, Use the high index/data pair 0x10 Bit16u, millisecond delay+1 0xxx Bit8u, send command and data to current index/data pair */ static void OPL_RawEmptyBuffer(void) { fwrite(opl.raw.buffer,1,opl.raw.used,opl.raw.handle); opl.raw.done+=opl.raw.used; opl.raw.used=0; } #define ADDBUF(_VAL_) opl.raw.buffer[opl.raw.used++]=_VAL_; static void OPL_RawAdd(Bitu index,Bitu val) { Bit8u cmd=opl.raw.cmd[index]; /* check for cmd's we use for special meaning These only control timers or are unused */ if (cmd == 2 || cmd == 3 || cmd == 0x10) return; if (cmd == 4 && !index) return; /* Check if we have yet to start */ if (!opl.raw.handle) { if (cmd<0xb0 || cmd>0xb8) return; if (!(val&0x20)) return; Bitu i; opl.raw.last=PIC_Ticks; opl.raw.start=PIC_Ticks; opl.raw.handle=OpenCaptureFile("Raw Opl",".dro"); if (!opl.raw.handle) { opl.raw.capturing=false; return; } memset(opl.raw.buffer,0,sizeof(opl.raw.buffer)); fwrite(dro_header,1,sizeof(dro_header),opl.raw.handle); /* Check the registers to add */ for (i=0;i<256;i++) { if (!opl.raw.regs[0][i]) continue; if (i>=0xb0 && i<=0xb8) continue; ADDBUF((Bit8u)i); ADDBUF(opl.raw.regs[0][i]); } bool donesecond=false; /* Check if we already have an opl3 enable bit logged */ if (opl.raw.regs[1][5] & 1) opl.raw.opl3 = true; for (i=0;i<256;i++) { if (!opl.raw.regs[1][i]) continue; if (i>=0xb0 && i<=0xb8) continue; if (!donesecond) { /* Or already have dual opl2 */ opl.raw.dualopl2 = true; donesecond=true; ADDBUF(0x3); } ADDBUF((Bit8u)i); ADDBUF(opl.raw.regs[1][i]); } if (donesecond) ADDBUF(0x2); } /* Check if we enable opl3 or access dual opl2 mode */ if (cmd == 5 && index && (val & 1)) { opl.raw.opl3 = true; } if (index && val && cmd>=0xb0 && cmd<=0xb8) { opl.raw.dualopl2 = true; } /* Check how much time has passed, Allow an extra 5 milliseconds? */ if (PIC_Ticks>(opl.raw.last+5)) { Bitu passed=PIC_Ticks-opl.raw.last; opl.raw.last=PIC_Ticks; while (passed) { passed-=1; if (passed<256) { ADDBUF(0x00); //8bit delay ADDBUF((Bit8u)passed); //8bit delay passed=0; } else if (passed<0x10000) { ADDBUF(0x01); //16bit delay ADDBUF((Bit8u)(passed & 0xff)); //lo-byte ADDBUF((Bit8u)(passed >> 8)); //hi-byte passed=0; } else { ADDBUF(0x01); //16bit delay ADDBUF(0xff); //lo-byte ADDBUF(0xff); //hi-byte passed-=0xffff; } } } /* Check if we're still sending to the correct index */ if (opl.raw.index != index) { opl.raw.index=index; ADDBUF(opl.raw.index ? 0x3 : 0x2); } ADDBUF(cmd); ADDBUF(val); if (opl.raw.used>=RAW_SIZE) OPL_RawEmptyBuffer(); } static void OPL_SaveRawEvent(void) { /* Check for previously opened wave file */ if (opl.raw.handle) { OPL_RawEmptyBuffer(); /* Fill in the header with useful information */ host_writed(&dro_header[0x0c],opl.raw.last-opl.raw.start); host_writed(&dro_header[0x10],opl.raw.done); if (opl.raw.opl3 && opl.raw.dualopl2) host_writed(&dro_header[0x14],0x1); else if (opl.raw.dualopl2) host_writed(&dro_header[0x14],0x2); else host_writed(&dro_header[0x14],0x0); fseek(opl.raw.handle,0,0); fwrite(dro_header,1,sizeof(dro_header),opl.raw.handle); fclose(opl.raw.handle); opl.raw.handle=0; } if (opl.raw.capturing) { opl.raw.capturing=false; LOG_MSG("Stopping Raw OPL capturing."); } else { LOG_MSG("Preparing to capture Raw OPL, will start with first note played."); opl.raw.capturing=true; opl.raw.index=0; opl.raw.used=0; opl.raw.done=0; opl.raw.start=0; opl.raw.last=0; } } static void OPL_Stop(Section* sec) { if (opl.raw.handle) OPL_SaveRawEvent(); } void OPL_Init(Section* sec,Bitu base,OPL_Mode oplmode,Bitu rate) { Section_prop * section=static_cast(sec); if (OPL2::YM3812Init(2,OPL2_INTERNAL_FREQ,rate)) { E_Exit("Can't create OPL2 Emulator"); }; OPL2::YM3812SetTimerHandler(0,OPL2::TimerHandler,0); OPL2::YM3812SetTimerHandler(1,OPL2::TimerHandler,256); if (THEOPL3::YMF262Init(1,OPL3_INTERNAL_FREQ,rate)) { E_Exit("Can't create OPL3 Emulator"); }; THEOPL3::YMF262SetTimerHandler(0,THEOPL3::TimerHandler,0); IO_RegisterWriteHandler(0x388,OPL_Write,IO_MB,4); IO_RegisterReadHandler(0x388,OPL_Read,IO_MB,4); if (oplmode>=OPL_dualopl2) { IO_RegisterWriteHandler(base,OPL_Write,IO_MB,4); IO_RegisterReadHandler(base,OPL_Read,IO_MB,4); } IO_RegisterWriteHandler(base+8,OPL_Write,IO_MB,2); IO_RegisterReadHandler(base+8,OPL_Read,IO_MB,2); opl.active=false; opl.last_used=0; opl.mode=oplmode; memset(&opl.raw,0,sizeof(opl.raw)); opl.chan=MIXER_AddChannel(OPL_CallBack,rate,"FM"); MAPPER_AddHandler(OPL_SaveRawEvent,MK_f7,MMOD1|MMOD2,"caprawopl","Cap OPL"); sec->AddDestroyFunction(&OPL_Stop); };