mirror of
https://github.com/retro100/dosbox-wii.git
synced 2024-11-17 15:49:15 +01:00
400 lines
11 KiB
C++
400 lines
11 KiB
C++
/*
|
|
* Copyright (C) 2002-2019 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.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
|
|
#include <string.h>
|
|
#include "dosbox.h"
|
|
#include "inout.h"
|
|
#include "mixer.h"
|
|
#include "pic.h"
|
|
#include "setup.h"
|
|
|
|
#define DISNEY_BASE 0x0378
|
|
|
|
#define DISNEY_SIZE 128
|
|
|
|
typedef struct _dac_channel {
|
|
Bit8u buffer[DISNEY_SIZE]; // data buffer
|
|
Bitu used; // current data buffer level
|
|
double speedcheck_sum;
|
|
double speedcheck_last;
|
|
bool speedcheck_failed;
|
|
bool speedcheck_init;
|
|
} dac_channel;
|
|
|
|
static struct {
|
|
// parallel port stuff
|
|
Bit8u data;
|
|
Bit8u status;
|
|
Bit8u control;
|
|
// the D/A channels
|
|
dac_channel da[2];
|
|
|
|
Bitu last_used;
|
|
MixerObject * mo;
|
|
MixerChannel * chan;
|
|
bool stereo;
|
|
// which channel do we use for mono output?
|
|
// and the channel used for stereo
|
|
dac_channel* leader;
|
|
|
|
Bitu state;
|
|
Bitu interface_det;
|
|
Bitu interface_det_ext;
|
|
} disney;
|
|
|
|
#define DS_IDLE 0
|
|
#define DS_RUNNING 1
|
|
#define DS_FINISH 2
|
|
#define DS_ANALYZING 3
|
|
|
|
static void DISNEY_CallBack(Bitu len);
|
|
|
|
static void DISNEY_disable(Bitu) {
|
|
if(disney.mo) {
|
|
disney.chan->AddSilence();
|
|
disney.chan->Enable(false);
|
|
}
|
|
disney.leader = 0;
|
|
disney.last_used = 0;
|
|
disney.state = DS_IDLE;
|
|
disney.interface_det = 0;
|
|
disney.interface_det_ext = 0;
|
|
disney.stereo = false;
|
|
}
|
|
|
|
static void DISNEY_enable(Bitu freq) {
|
|
if(freq < 500 || freq > 100000) {
|
|
// try again..
|
|
disney.state = DS_IDLE;
|
|
return;
|
|
} else {
|
|
#if 0
|
|
if(disney.stereo) LOG(LOG_MISC,LOG_NORMAL)("disney enable %d Hz, stereo",freq);
|
|
else LOG(LOG_MISC,LOG_NORMAL)("disney enable %d Hz, mono",freq);
|
|
#endif
|
|
disney.chan->SetFreq(freq);
|
|
disney.chan->Enable(true);
|
|
disney.state = DS_RUNNING;
|
|
}
|
|
}
|
|
|
|
static void DISNEY_analyze(Bitu channel){
|
|
switch(disney.state) {
|
|
case DS_RUNNING: // should not get here
|
|
break;
|
|
case DS_IDLE:
|
|
// initialize channel data
|
|
for(int i = 0; i < 2; i++) {
|
|
disney.da[i].used = 0;
|
|
disney.da[i].speedcheck_sum = 0;
|
|
disney.da[i].speedcheck_failed = false;
|
|
disney.da[i].speedcheck_init = false;
|
|
}
|
|
disney.da[channel].speedcheck_last = PIC_FullIndex();
|
|
disney.da[channel].speedcheck_init = true;
|
|
|
|
disney.state = DS_ANALYZING;
|
|
break;
|
|
|
|
case DS_FINISH:
|
|
{
|
|
// detect stereo: if we have about the same data amount in both channels
|
|
Bits st_diff = disney.da[0].used - disney.da[1].used;
|
|
|
|
// find leader channel (the one with higher rate) [this good for the stereo case?]
|
|
if(disney.da[0].used > disney.da[1].used) {
|
|
//disney.monochannel=0;
|
|
disney.leader = &disney.da[0];
|
|
} else {
|
|
//disney.monochannel=1;
|
|
disney.leader = &disney.da[1];
|
|
}
|
|
|
|
if((st_diff < 5) && (st_diff > -5)) disney.stereo = true;
|
|
else disney.stereo = false;
|
|
|
|
// calculate rate for both channels
|
|
Bitu ch_speed[2];
|
|
|
|
for(Bitu i = 0; i < 2; i++) {
|
|
ch_speed[i] = (Bitu)(1.0/((disney.da[i].speedcheck_sum/1000.0) /
|
|
(float)(((float)disney.da[i].used)-1.0))); // -1.75
|
|
}
|
|
|
|
// choose the larger value
|
|
DISNEY_enable(ch_speed[0] > ch_speed[1]?
|
|
ch_speed[0]:ch_speed[1]); // TODO
|
|
break;
|
|
}
|
|
case DS_ANALYZING:
|
|
{
|
|
double current = PIC_FullIndex();
|
|
dac_channel* cch = &disney.da[channel];
|
|
|
|
if(!cch->speedcheck_init) {
|
|
cch->speedcheck_init = true;
|
|
cch->speedcheck_last = current;
|
|
break;
|
|
}
|
|
cch->speedcheck_sum += current - cch->speedcheck_last;
|
|
//LOG_MSG("t=%f",current - cch->speedcheck_last);
|
|
|
|
// sanity checks (printer...)
|
|
if((current - cch-> speedcheck_last) < 0.01 ||
|
|
(current - cch-> speedcheck_last) > 2)
|
|
cch->speedcheck_failed = true;
|
|
|
|
// if both are failed we are back at start
|
|
if(disney.da[0].speedcheck_failed && disney.da[1].speedcheck_failed) {
|
|
disney.state=DS_IDLE;
|
|
break;
|
|
}
|
|
|
|
cch->speedcheck_last = current;
|
|
|
|
// analyze finish condition
|
|
if(disney.da[0].used > 30 || disney.da[1].used > 30)
|
|
disney.state = DS_FINISH;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void disney_write(Bitu port,Bitu val,Bitu iolen) {
|
|
//LOG_MSG("write disney time %f addr%x val %x",PIC_FullIndex(),port,val);
|
|
disney.last_used=PIC_Ticks;
|
|
switch (port-DISNEY_BASE) {
|
|
case 0: /* Data Port */
|
|
{
|
|
disney.data=val;
|
|
// if data is written here too often without using the stereo
|
|
// mechanism we use the simple DAC machanism.
|
|
if(disney.state != DS_RUNNING) {
|
|
disney.interface_det++;
|
|
if(disney.interface_det > 5)
|
|
DISNEY_analyze(0);
|
|
}
|
|
if(disney.interface_det > 5) {
|
|
if(disney.da[0].used < DISNEY_SIZE) {
|
|
disney.da[0].buffer[disney.da[0].used] = disney.data;
|
|
disney.da[0].used++;
|
|
} //else LOG_MSG("disney overflow 0");
|
|
}
|
|
break;
|
|
}
|
|
case 1: /* Status Port */
|
|
LOG(LOG_MISC,LOG_NORMAL)("DISNEY:Status write %x",val);
|
|
break;
|
|
case 2: /* Control Port */
|
|
if((disney.control & 0x2) && !(val & 0x2)) {
|
|
if(disney.state != DS_RUNNING) {
|
|
disney.interface_det = 0;
|
|
disney.interface_det_ext = 0;
|
|
DISNEY_analyze(1);
|
|
}
|
|
|
|
// stereo channel latch
|
|
if(disney.da[1].used < DISNEY_SIZE) {
|
|
disney.da[1].buffer[disney.da[1].used] = disney.data;
|
|
disney.da[1].used++;
|
|
} //else LOG_MSG("disney overflow 1");
|
|
}
|
|
|
|
if((disney.control & 0x1) && !(val & 0x1)) {
|
|
if(disney.state != DS_RUNNING) {
|
|
disney.interface_det = 0;
|
|
disney.interface_det_ext = 0;
|
|
DISNEY_analyze(0);
|
|
}
|
|
// stereo channel latch
|
|
if(disney.da[0].used < DISNEY_SIZE) {
|
|
disney.da[0].buffer[disney.da[0].used] = disney.data;
|
|
disney.da[0].used++;
|
|
} //else LOG_MSG("disney overflow 0");
|
|
}
|
|
|
|
if((disney.control & 0x8) && !(val & 0x8)) {
|
|
// emulate a device with 16-byte sound FIFO
|
|
if(disney.state != DS_RUNNING) {
|
|
disney.interface_det_ext++;
|
|
disney.interface_det = 0;
|
|
if(disney.interface_det_ext > 5) {
|
|
disney.leader = &disney.da[0];
|
|
DISNEY_enable(7000);
|
|
}
|
|
}
|
|
if(disney.interface_det_ext > 5) {
|
|
if(disney.da[0].used < DISNEY_SIZE) {
|
|
disney.da[0].buffer[disney.da[0].used] = disney.data;
|
|
disney.da[0].used++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// LOG_WARN("DISNEY:Control write %x",val);
|
|
if (val&0x10) LOG(LOG_MISC,LOG_ERROR)("DISNEY:Parallel IRQ Enabled");
|
|
disney.control=val;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static Bitu disney_read(Bitu port,Bitu iolen) {
|
|
Bitu retval;
|
|
switch (port-DISNEY_BASE) {
|
|
case 0: /* Data Port */
|
|
// LOG(LOG_MISC,LOG_NORMAL)("DISNEY:Read from data port");
|
|
return disney.data;
|
|
break;
|
|
case 1: /* Status Port */
|
|
// LOG(LOG_MISC,"DISNEY:Read from status port %X",disney.status);
|
|
retval = 0x07;//0x40; // Stereo-on-1 and (or) New-Stereo DACs present
|
|
if(disney.interface_det_ext > 5) {
|
|
if (disney.leader && disney.leader->used >= 16){
|
|
retval |= 0x40; // ack
|
|
retval &= ~0x4; // interrupt
|
|
}
|
|
}
|
|
if(!(disney.data&0x80)) retval |= 0x80; // pin 9 is wired to pin 11
|
|
return retval;
|
|
break;
|
|
case 2: /* Control Port */
|
|
LOG(LOG_MISC,LOG_NORMAL)("DISNEY:Read from control port");
|
|
return disney.control;
|
|
break;
|
|
}
|
|
return 0xff;
|
|
}
|
|
|
|
static void DISNEY_PlayStereo(Bitu len, Bit8u* l, Bit8u* r) {
|
|
static Bit8u stereodata[DISNEY_SIZE*2];
|
|
for(Bitu i = 0; i < len; i++) {
|
|
stereodata[i*2] = l[i];
|
|
stereodata[i*2+1] = r[i];
|
|
}
|
|
disney.chan->AddSamples_s8(len,stereodata);
|
|
}
|
|
|
|
static void DISNEY_CallBack(Bitu len) {
|
|
if (!len) return;
|
|
|
|
// get the smaller used
|
|
Bitu real_used;
|
|
if(disney.stereo) {
|
|
real_used = disney.da[0].used;
|
|
if(disney.da[1].used < real_used) real_used = disney.da[1].used;
|
|
} else
|
|
real_used = disney.leader->used;
|
|
|
|
if (real_used >= len) { // enough data for now
|
|
if(disney.stereo) DISNEY_PlayStereo(len, disney.da[0].buffer, disney.da[1].buffer);
|
|
else disney.chan->AddSamples_m8(len,disney.leader->buffer);
|
|
|
|
// put the rest back to start
|
|
for(int i = 0; i < 2; i++) {
|
|
// TODO for mono only one
|
|
memmove(disney.da[i].buffer,&disney.da[i].buffer[len],DISNEY_SIZE/*real_used*/-len);
|
|
disney.da[i].used -= len;
|
|
}
|
|
// TODO: len > DISNEY
|
|
} else { // not enough data
|
|
if(disney.stereo) {
|
|
Bit8u gapfiller0 = 128;
|
|
Bit8u gapfiller1 = 128;
|
|
if(real_used) {
|
|
gapfiller0 = disney.da[0].buffer[real_used-1];
|
|
gapfiller1 = disney.da[1].buffer[real_used-1];
|
|
};
|
|
|
|
memset(disney.da[0].buffer+real_used,
|
|
gapfiller0,len-real_used);
|
|
memset(disney.da[1].buffer+real_used,
|
|
gapfiller1,len-real_used);
|
|
|
|
DISNEY_PlayStereo(len, disney.da[0].buffer, disney.da[1].buffer);
|
|
len -= real_used;
|
|
|
|
} else { // mono
|
|
Bit8u gapfiller = 128; //Keep the middle
|
|
if(real_used) {
|
|
// fix for some stupid game; it outputs 0 at the end of the stream
|
|
// causing a click. So if we have at least two bytes availible in the
|
|
// buffer and the last one is a 0 then ignore that.
|
|
if(disney.leader->buffer[real_used-1]==0)
|
|
real_used--;
|
|
}
|
|
// do it this way because AddSilence sounds like a gnawing mouse
|
|
if(real_used)
|
|
gapfiller = disney.leader->buffer[real_used-1];
|
|
//LOG_MSG("gapfiller %x, fill len %d, realused %d",gapfiller,len-real_used,real_used);
|
|
memset(disney.leader->buffer+real_used, gapfiller, len-real_used);
|
|
disney.chan->AddSamples_m8(len, disney.leader->buffer);
|
|
}
|
|
disney.da[0].used =0;
|
|
disney.da[1].used =0;
|
|
|
|
//LOG_MSG("disney underflow %d",len - real_used);
|
|
}
|
|
if (disney.last_used+100<PIC_Ticks) {
|
|
// disable sound output
|
|
PIC_AddEvent(DISNEY_disable,0.0001f); // I think we shouldn't delete the
|
|
// mixer while we are inside it
|
|
}
|
|
}
|
|
|
|
class DISNEY: public Module_base {
|
|
private:
|
|
IO_ReadHandleObject ReadHandler;
|
|
IO_WriteHandleObject WriteHandler;
|
|
//MixerObject MixerChan;
|
|
public:
|
|
DISNEY(Section* configuration):Module_base(configuration) {
|
|
Section_prop * section=static_cast<Section_prop *>(configuration);
|
|
if(!section->Get_bool("disney")) return;
|
|
|
|
WriteHandler.Install(DISNEY_BASE,disney_write,IO_MB,3);
|
|
ReadHandler.Install(DISNEY_BASE,disney_read,IO_MB,3);
|
|
|
|
disney.status=0x84;
|
|
disney.control=0;
|
|
disney.last_used=0;
|
|
|
|
disney.mo = new MixerObject();
|
|
disney.chan=disney.mo->Install(&DISNEY_CallBack,10000,"DISNEY");
|
|
DISNEY_disable(0);
|
|
|
|
|
|
}
|
|
~DISNEY(){
|
|
DISNEY_disable(0);
|
|
if (disney.mo)
|
|
delete disney.mo;
|
|
}
|
|
};
|
|
|
|
static DISNEY* test;
|
|
|
|
static void DISNEY_ShutDown(Section* sec){
|
|
delete test;
|
|
}
|
|
|
|
void DISNEY_Init(Section* sec) {
|
|
test = new DISNEY(sec);
|
|
sec->AddDestroyFunction(&DISNEY_ShutDown,true);
|
|
}
|