2009-05-02 23:03:37 +02:00
|
|
|
/*
|
2009-05-03 00:28:34 +02:00
|
|
|
* Copyright (C) 2002-2007 The DOSBox Team
|
2009-05-02 23:03:37 +02:00
|
|
|
*
|
|
|
|
* 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
|
2009-05-03 00:02:15 +02:00
|
|
|
* GNU General Public License for more details.
|
2009-05-02 23:03:37 +02:00
|
|
|
*
|
|
|
|
* 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 <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <math.h>
|
2009-05-03 00:02:15 +02:00
|
|
|
#include <sys/types.h>
|
|
|
|
#include <dirent.h>
|
|
|
|
|
2009-05-02 23:03:37 +02:00
|
|
|
#include "dosbox.h"
|
|
|
|
#include "inout.h"
|
|
|
|
#include "mixer.h"
|
2009-05-02 23:27:47 +02:00
|
|
|
#include "pic.h"
|
2009-05-02 23:03:37 +02:00
|
|
|
#include "hardware.h"
|
2009-05-02 23:20:05 +02:00
|
|
|
#include "setup.h"
|
2009-05-03 00:02:15 +02:00
|
|
|
#include "mapper.h"
|
|
|
|
#include "mem.h"
|
|
|
|
|
2009-05-02 23:03:37 +02:00
|
|
|
/*
|
|
|
|
Thanks to vdmsound for nice simple way to implement this
|
|
|
|
*/
|
|
|
|
|
2009-05-03 00:02:15 +02:00
|
|
|
#define logerror
|
2009-05-02 23:27:47 +02:00
|
|
|
|
2009-05-02 23:43:00 +02:00
|
|
|
#ifdef _MSC_VER
|
2009-05-02 23:03:37 +02:00
|
|
|
/* Disable recurring warnings */
|
2009-05-02 23:27:47 +02:00
|
|
|
# pragma warning ( disable : 4018 )
|
|
|
|
# pragma warning ( disable : 4244 )
|
2009-05-02 23:43:00 +02:00
|
|
|
#endif
|
2009-05-02 23:27:47 +02:00
|
|
|
|
2009-05-03 00:02:15 +02:00
|
|
|
struct __MALLOCPTR {
|
|
|
|
void* m_ptr;
|
2009-05-02 23:27:47 +02:00
|
|
|
|
2009-05-03 00:02:15 +02:00
|
|
|
__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; }
|
2009-05-02 23:03:37 +02:00
|
|
|
};
|
|
|
|
|
2009-05-03 00:02:15 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2009-05-02 23:27:47 +02:00
|
|
|
|
2009-05-03 00:02:15 +02:00
|
|
|
#define OPL2_INTERNAL_FREQ 3600000 // The OPL2 operates at 3.6MHz
|
|
|
|
#define OPL3_INTERNAL_FREQ 14400000 // The OPL3 operates at 14.4MHz
|
2009-05-02 23:27:47 +02:00
|
|
|
|
2009-05-03 00:02:15 +02:00
|
|
|
#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];
|
2009-05-03 00:08:43 +02:00
|
|
|
bool opl3;
|
|
|
|
bool dualopl2;
|
2009-05-03 00:02:15 +02:00
|
|
|
} raw;
|
|
|
|
} opl;
|
2009-05-02 23:03:37 +02:00
|
|
|
|
2009-05-03 00:02:15 +02:00
|
|
|
static void OPL_CallBack(Bitu len) {
|
2009-05-02 23:03:37 +02:00
|
|
|
/* Check for size to update and check for 1 ms updates to the opl registers */
|
2009-05-03 00:02:15 +02:00
|
|
|
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;i<len;i++) {
|
|
|
|
((Bit16s*)MixTemp)[i*2+0]=opl.mixbuf[0][i];
|
|
|
|
((Bit16s*)MixTemp)[i*2+1]=opl.mixbuf[1][i];
|
|
|
|
}
|
|
|
|
opl.chan->AddSamples_s16(len,(Bit16s*)MixTemp);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if ((PIC_Ticks-opl.last_used)>30000) {
|
|
|
|
opl.chan->Enable(false);
|
|
|
|
opl.active=false;
|
|
|
|
}
|
|
|
|
}
|
2009-05-02 23:03:37 +02:00
|
|
|
|
2009-05-03 00:02:15 +02:00
|
|
|
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;
|
2009-05-02 23:03:37 +02:00
|
|
|
}
|
|
|
|
|
2009-05-03 00:02:15 +02:00
|
|
|
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);
|
2009-05-02 23:03:37 +02:00
|
|
|
}
|
2009-05-03 00:02:15 +02:00
|
|
|
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;
|
2009-05-02 23:03:37 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-05-03 00:02:15 +02:00
|
|
|
static Bit8u dro_header[]={
|
2009-05-03 00:08:43 +02:00
|
|
|
'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 */
|
2009-05-03 00:02:15 +02:00
|
|
|
};
|
|
|
|
/* Commands
|
|
|
|
0x00 Bit8u, millisecond delay+1
|
|
|
|
0x02 none, Use the low index/data pair
|
|
|
|
0x03 none, Use the high index/data pair
|
2009-05-03 00:08:43 +02:00
|
|
|
0x10 Bit16u, millisecond delay+1
|
2009-05-03 00:02:15 +02:00
|
|
|
0xxx Bit8u, send command and data to current index/data pair
|
|
|
|
*/
|
2009-05-02 23:53:27 +02:00
|
|
|
|
2009-05-03 00:02:15 +02:00
|
|
|
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;
|
2009-05-02 23:03:37 +02:00
|
|
|
}
|
|
|
|
|
2009-05-03 00:02:15 +02:00
|
|
|
#define ADDBUF(_VAL_) opl.raw.buffer[opl.raw.used++]=_VAL_;
|
|
|
|
static void OPL_RawAdd(Bitu index,Bitu val) {
|
|
|
|
Bit8u cmd=opl.raw.cmd[index];
|
2009-05-03 00:08:43 +02:00
|
|
|
/* 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 */
|
2009-05-03 00:02:15 +02:00
|
|
|
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;
|
2009-05-02 23:03:37 +02:00
|
|
|
return;
|
2009-05-03 00:02:15 +02:00
|
|
|
}
|
|
|
|
memset(opl.raw.buffer,0,sizeof(opl.raw.buffer));
|
|
|
|
fwrite(dro_header,1,sizeof(dro_header),opl.raw.handle);
|
|
|
|
/* Check the registers to add */
|
2009-05-03 00:08:43 +02:00
|
|
|
for (i=0;i<256;i++) {
|
2009-05-03 00:02:15 +02:00
|
|
|
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;
|
2009-05-03 00:08:43 +02:00
|
|
|
/* 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;
|
2009-05-03 00:02:15 +02:00
|
|
|
if (i>=0xb0 && i<=0xb8) continue;
|
|
|
|
if (!donesecond) {
|
2009-05-03 00:08:43 +02:00
|
|
|
/* Or already have dual opl2 */
|
|
|
|
opl.raw.dualopl2 = true;
|
2009-05-03 00:02:15 +02:00
|
|
|
donesecond=true;
|
|
|
|
ADDBUF(0x3);
|
2009-05-02 23:03:37 +02:00
|
|
|
}
|
2009-05-03 00:02:15 +02:00
|
|
|
ADDBUF((Bit8u)i);
|
2009-05-03 00:08:43 +02:00
|
|
|
ADDBUF(opl.raw.regs[1][i]);
|
2009-05-03 00:02:15 +02:00
|
|
|
}
|
|
|
|
if (donesecond) ADDBUF(0x2);
|
2009-05-02 23:03:37 +02:00
|
|
|
}
|
2009-05-03 00:08:43 +02:00
|
|
|
/* 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;
|
|
|
|
}
|
2009-05-03 00:02:15 +02:00
|
|
|
/* 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();
|
2009-05-02 23:03:37 +02:00
|
|
|
}
|
|
|
|
|
2009-05-03 00:18:08 +02:00
|
|
|
static void OPL_SaveRawEvent(bool pressed) {
|
|
|
|
if (!pressed)
|
|
|
|
return;
|
2009-05-03 00:02:15 +02:00
|
|
|
/* Check for previously opened wave file */
|
|
|
|
if (opl.raw.handle) {
|
|
|
|
OPL_RawEmptyBuffer();
|
|
|
|
/* Fill in the header with useful information */
|
2009-05-03 00:08:43 +02:00
|
|
|
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);
|
2009-05-03 00:02:15 +02:00
|
|
|
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.");
|
2009-05-02 23:03:37 +02:00
|
|
|
} else {
|
2009-05-03 00:02:15 +02:00
|
|
|
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;
|
2009-05-02 23:03:37 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-05-03 00:18:08 +02:00
|
|
|
class OPL: public Module_base {
|
|
|
|
private:
|
|
|
|
IO_ReadHandleObject ReadHandler[3];
|
|
|
|
IO_WriteHandleObject WriteHandler[3];
|
|
|
|
MixerObject MixerChan;
|
|
|
|
public:
|
|
|
|
static OPL_Mode oplmode;
|
2009-05-02 23:03:37 +02:00
|
|
|
|
2009-05-03 00:18:08 +02:00
|
|
|
OPL(Section* configuration):Module_base(configuration) {
|
|
|
|
Section_prop * section=static_cast<Section_prop *>(configuration);
|
|
|
|
Bitu base = section->Get_hex("sbbase");
|
|
|
|
Bitu rate = section->Get_int("oplrate");
|
|
|
|
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);
|
|
|
|
WriteHandler[0].Install(0x388,OPL_Write,IO_MB,4);
|
|
|
|
ReadHandler[0].Install(0x388,OPL_Read,IO_MB,4);
|
|
|
|
if (oplmode>=OPL_dualopl2) {
|
|
|
|
WriteHandler[1].Install(base,OPL_Write,IO_MB,4);
|
|
|
|
ReadHandler[1].Install(base,OPL_Read,IO_MB,4);
|
|
|
|
}
|
2009-05-02 23:27:47 +02:00
|
|
|
|
2009-05-03 00:18:08 +02:00
|
|
|
WriteHandler[2].Install(base+8,OPL_Write,IO_MB,2);
|
|
|
|
ReadHandler[2].Install(base+8,OPL_Read,IO_MB,2);
|
2009-05-02 23:03:37 +02:00
|
|
|
|
2009-05-03 00:18:08 +02:00
|
|
|
opl.active=false;
|
|
|
|
opl.last_used=0;
|
|
|
|
opl.mode=oplmode;
|
|
|
|
memset(&opl.raw,0,sizeof(opl.raw));
|
|
|
|
opl.chan=MixerChan.Install(OPL_CallBack,rate,"FM");
|
|
|
|
MAPPER_AddHandler(OPL_SaveRawEvent,MK_f7,MMOD1|MMOD2,"caprawopl","Cap OPL");
|
|
|
|
}
|
|
|
|
~OPL() {
|
|
|
|
if (opl.raw.handle) OPL_SaveRawEvent(true);
|
|
|
|
OPL2::YM3812Shutdown();
|
|
|
|
THEOPL3::YMF262Shutdown();
|
|
|
|
}
|
2009-05-02 23:03:37 +02:00
|
|
|
};
|
|
|
|
|
2009-05-03 00:18:08 +02:00
|
|
|
static OPL* test;
|
|
|
|
|
|
|
|
//Initialize static members
|
|
|
|
OPL_Mode OPL::oplmode=OPL_none;
|
|
|
|
|
|
|
|
void OPL_Init(Section* sec,OPL_Mode oplmode) {
|
|
|
|
OPL::oplmode = oplmode;
|
|
|
|
test = new OPL(sec);
|
|
|
|
}
|
|
|
|
|
|
|
|
void OPL_ShutDown(Section* sec){
|
|
|
|
delete test;
|
|
|
|
}
|