DOSBox-X
|
00001 /* 00002 * Copyright (C) 2002-2020 The DOSBox Team 00003 * 00004 * This program is free software; you can redistribute it and/or modify 00005 * it under the terms of the GNU General Public License as published by 00006 * the Free Software Foundation; either version 2 of the License, or 00007 * (at your option) any later version. 00008 * 00009 * This program is distributed in the hope that it will be useful, 00010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00012 * GNU General Public License for more details. 00013 * 00014 * You should have received a copy of the GNU General Public License along 00015 * with this program; if not, write to the Free Software Foundation, Inc., 00016 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 00017 */ 00018 00019 #include <math.h> 00020 #include <string.h> 00021 #include "dosbox.h" 00022 #include "inout.h" 00023 #include "mixer.h" 00024 #include "mem.h" 00025 #include "setup.h" 00026 #include "pic.h" 00027 #include "dma.h" 00028 #include "mame/emu.h" 00029 #include "mame/sn76496.h" 00030 #include "control.h" 00031 00032 // FIXME: MAME updates broke this code! 00033 00034 extern bool PS1AudioCard; 00035 #define DAC_CLOCK 1000000 00036 // 950272? 00037 #define MAX_OUTPUT 0x7fff 00038 #define STEP 0x10000 00039 00040 // Powers of 2 please! 00041 #define FIFOSIZE 2048 00042 #define FIFOSIZE_MASK ( FIFOSIZE - 1 ) 00043 00044 #define FIFO_NEARLY_EMPTY_VAL 128 00045 #define FIFO_NEARLY_FULL_VAL ( FIFOSIZE - 128 ) 00046 00047 #define FRAC_SHIFT 12 // Fixed precision. 00048 00049 // Nearly full and half full flags (somewhere) on the SN74V2x5/IDT72V2x5 datasheet (just guessing on the hardware). 00050 #define FIFO_HALF_FULL 0x00 00051 #define FIFO_NEARLY_FULL 0x00 00052 00053 #define FIFO_READ_AVAILABLE 0x10 // High when the interrupt can't do anything but wait (cleared by reading 0200?). 00054 #define FIFO_FULL 0x08 // High when we can't write any more. 00055 #define FIFO_EMPTY 0x04 // High when we can write direct values??? 00056 #define FIFO_NEARLY_EMPTY 0x02 // High when we can write more to the FIFO (or, at least, there are 0x700 bytes free). 00057 #define FIFO_IRQ 0x01 // High when IRQ was triggered by the DAC? 00058 00059 struct PS1AUDIO 00060 { 00061 // Native stuff. 00062 MixerChannel * chanDAC; 00063 MixerChannel * chanSN; 00064 bool enabledDAC; 00065 bool enabledSN; 00066 Bitu last_writeDAC; 00067 Bitu last_writeSN; 00068 int SampleRate; 00069 00070 #if 0 00071 // SN76496. 00072 struct SN76496 sn; 00073 #endif 00074 00075 // "DAC". 00076 Bit8u FIFO[FIFOSIZE]; 00077 Bit16u FIFO_RDIndex; 00078 Bit16u FIFO_WRIndex; 00079 bool Playing; 00080 bool CanTriggerIRQ; 00081 Bit32u Rate; 00082 Bitu RDIndexHi; // FIFO_RDIndex << FRAC_SHIFT 00083 Bitu Adder; // Step << FRAC_SHIFT 00084 Bitu Pending; // Bytes to go << FRAC_SHIFT 00085 00086 // Regs. 00087 Bit8u Status; // 0202 RD 00088 Bit8u Command; // 0202 WR / 0200 RD 00089 Bit8u Data; // 0200 WR 00090 Bit8u Divisor; // 0203 WR 00091 Bit8u Unknown; // 0204 WR (Reset?) 00092 }; 00093 00094 static struct PS1AUDIO ps1; 00095 00096 static Bit8u PS1SOUND_CalcStatus(void) 00097 { 00098 Bit8u Status = ps1.Status & FIFO_IRQ; 00099 if( !ps1.Pending ) { 00100 Status |= FIFO_EMPTY; 00101 } 00102 if( ( ps1.Pending < ( FIFO_NEARLY_EMPTY_VAL << FRAC_SHIFT ) ) && ( ( ps1.Command & 3 ) == 3 ) ) { 00103 Status |= FIFO_NEARLY_EMPTY; 00104 } 00105 if( ps1.Pending > ( ( FIFOSIZE - 1 ) << FRAC_SHIFT ) ) { 00106 // if( ps1.Pending >= ( ( FIFOSIZE - 1 ) << FRAC_SHIFT ) ) { // OK 00107 // Should never be bigger than FIFOSIZE << FRAC_SHIFT...? 00108 Status |= FIFO_FULL; 00109 } 00110 if( ps1.Pending > ( FIFO_NEARLY_FULL_VAL << FRAC_SHIFT ) ) { 00111 Status |= FIFO_NEARLY_FULL; 00112 } 00113 if( ps1.Pending >= ( ( FIFOSIZE >> 1 ) << FRAC_SHIFT ) ) { 00114 Status |= FIFO_HALF_FULL; 00115 } 00116 return Status; 00117 } 00118 00119 static void PS1DAC_Reset(bool bTotal) 00120 { 00121 PIC_DeActivateIRQ( 7 ); 00122 ps1.Data = 0x80; 00123 memset( ps1.FIFO, 0x80, FIFOSIZE ); 00124 ps1.FIFO_RDIndex = 0; 00125 ps1.FIFO_WRIndex = 0; 00126 if( bTotal ) ps1.Rate = 0xFFFFFFFF; 00127 ps1.RDIndexHi = 0; 00128 if( bTotal ) ps1.Adder = 0; // Be careful with this, 5 second timeout and Space Quest 4! 00129 ps1.Pending = 0; 00130 ps1.Status = PS1SOUND_CalcStatus(); 00131 ps1.Playing = true; 00132 ps1.CanTriggerIRQ = false; 00133 } 00134 00135 00136 #include "regs.h" 00137 static void PS1SOUNDWrite(Bitu port,Bitu data,Bitu iolen) { 00138 (void)iolen;//UNUSED 00139 if( port != 0x0205 ) { 00140 ps1.last_writeDAC=PIC_Ticks; 00141 if (!ps1.enabledDAC) { 00142 ps1.chanDAC->Enable(true); 00143 ps1.enabledDAC=true; 00144 } 00145 } 00146 else 00147 { 00148 ps1.last_writeSN=PIC_Ticks; 00149 if (!ps1.enabledSN) { 00150 ps1.chanSN->Enable(true); 00151 ps1.enabledSN=true; 00152 } 00153 } 00154 00155 #if C_DEBUG != 0 00156 if( ( port != 0x0205 ) && ( port != 0x0200 ) ) 00157 LOG_MSG("PS1 WR %04X,%02X (%04X:%08X)",(int)port,(int)data,(int)SegValue(cs),(int)reg_eip); 00158 #endif 00159 switch(port) 00160 { 00161 case 0x0200: 00162 // Data - insert into FIFO. 00163 ps1.Data = (Bit8u)data; 00164 ps1.Status = PS1SOUND_CalcStatus(); 00165 if( !( ps1.Status & FIFO_FULL ) ) 00166 { 00167 ps1.FIFO[ ps1.FIFO_WRIndex++ ]=(Bit8u)data; 00168 ps1.FIFO_WRIndex &= FIFOSIZE_MASK; 00169 ps1.Pending += ( 1 << FRAC_SHIFT ); 00170 if( ps1.Pending > ( FIFOSIZE << FRAC_SHIFT ) ) { 00171 ps1.Pending = FIFOSIZE << FRAC_SHIFT; 00172 } 00173 } 00174 break; 00175 case 0x0202: 00176 // Command. 00177 ps1.Command = (Bit8u)data; 00178 if( data & 3 ) ps1.CanTriggerIRQ = true; 00179 // switch( data & 3 ) 00180 // { 00181 // case 0: // Stop? 00182 // ps1.Adder = 0; 00183 // break; 00184 // } 00185 break; 00186 case 0x0203: 00187 { 00188 // Clock divisor (maybe trigger first IRQ here). 00189 ps1.Divisor = (Bit8u)data; 00190 ps1.Rate = (Bit32u)( DAC_CLOCK / ( data + 1 ) ); 00191 // 22050 << FRAC_SHIFT / 22050 = 1 << FRAC_SHIFT 00192 ps1.Adder = ( ps1.Rate << FRAC_SHIFT ) / (unsigned int)ps1.SampleRate; 00193 if( ps1.Rate > 22050 ) 00194 { 00195 // if( ( ps1.Command & 3 ) == 3 ) { 00196 // LOG_MSG("Attempt to set DAC rate too high (%dhz).",ps1.Rate); 00197 // } 00198 //ps1.Divisor = 0x2C; 00199 //ps1.Rate = 22050; 00200 //ps1.Adder = 0; // Not valid. 00201 } 00202 ps1.Status = PS1SOUND_CalcStatus(); 00203 if( ( ps1.Status & FIFO_NEARLY_EMPTY ) && ( ps1.CanTriggerIRQ ) ) 00204 { 00205 // Generate request for stuff. 00206 ps1.Status |= FIFO_IRQ; 00207 ps1.CanTriggerIRQ = false; 00208 PIC_ActivateIRQ( 7 ); 00209 } 00210 } 00211 break; 00212 case 0x0204: 00213 // Reset? (PS1MIC01 sets it to 08 for playback...) 00214 ps1.Unknown = (Bit8u)data; 00215 if( !data ) 00216 PS1DAC_Reset(true); 00217 break; 00218 case 0x0205: 00219 #if 0 00220 SN76496Write(&ps1.sn,port,data); 00221 #endif 00222 break; 00223 default:break; 00224 } 00225 } 00226 00227 static Bitu PS1SOUNDRead(Bitu port,Bitu iolen) { 00228 (void)iolen;//UNUSED 00229 ps1.last_writeDAC=PIC_Ticks; 00230 if (!ps1.enabledDAC) { 00231 ps1.chanDAC->Enable(true); 00232 ps1.enabledDAC=true; 00233 } 00234 #if C_DEBUG != 0 00235 LOG_MSG("PS1 RD %04X (%04X:%08X)",(int)port,(int)SegValue(cs),(int)reg_eip); 00236 #endif 00237 switch(port) 00238 { 00239 case 0x0200: 00240 // Read last command. 00241 ps1.Status &= ~FIFO_READ_AVAILABLE; 00242 return ps1.Command; 00243 case 0x0202: 00244 { 00245 // LOG_MSG("PS1 RD %04X (%04X:%08X)",port,SegValue(cs),reg_eip); 00246 00247 // Read status / clear IRQ?. 00248 Bit8u Status = ps1.Status = PS1SOUND_CalcStatus(); 00249 // Don't do this until we have some better way of detecting the triggering and ending of an IRQ. 00250 // ps1.Status &= ~FIFO_IRQ; 00251 return Status; 00252 } 00253 case 0x0203: 00254 // Stunt Island / Roger Rabbit 2 setup. 00255 return ps1.Divisor; 00256 case 0x0205: 00257 case 0x0206: 00258 // Bush Buck detection. 00259 return 0; 00260 default:break; 00261 } 00262 return 0xFF; 00263 } 00264 00265 static void PS1SOUNDUpdate(Bitu length) 00266 { 00267 if ((ps1.last_writeDAC+5000)<PIC_Ticks) { 00268 ps1.enabledDAC=false; 00269 ps1.chanDAC->Enable(false); 00270 // Excessive? 00271 PS1DAC_Reset(false); 00272 } 00273 Bit8u * buffer=(Bit8u *)MixTemp; 00274 00275 Bits pending = 0; 00276 Bitu add = 0; 00277 Bitu pos=ps1.RDIndexHi; 00278 Bitu count=length; 00279 00280 if( ps1.Playing ) 00281 { 00282 ps1.Status = PS1SOUND_CalcStatus(); 00283 pending = (Bits)ps1.Pending; 00284 add = ps1.Adder; 00285 if( ( ps1.Status & FIFO_NEARLY_EMPTY ) && ( ps1.CanTriggerIRQ ) ) 00286 { 00287 // More bytes needed. 00288 00289 //PIC_AddEvent( ??, ??, ?? ); 00290 ps1.Status |= FIFO_IRQ; 00291 ps1.CanTriggerIRQ = false; 00292 PIC_ActivateIRQ( 7 ); 00293 } 00294 } 00295 00296 while (count) 00297 { 00298 unsigned int out; 00299 00300 if( pending <= 0 ) { 00301 pending = 0; 00302 while( count-- ) *(buffer++) = 0x80; // Silence. 00303 break; 00304 //pos = ( ( ps1.FIFO_RDIndex - 1 ) & FIFOSIZE_MASK ) << FRAC_SHIFT; // Stay on last byte. 00305 } 00306 else 00307 { 00308 out = ps1.FIFO[ pos >> FRAC_SHIFT ]; 00309 pos += add; 00310 pos &= ( ( FIFOSIZE << FRAC_SHIFT ) - 1 ); 00311 pending -= (Bits)add; 00312 } 00313 00314 *(buffer++) = out; 00315 count--; 00316 } 00317 // Update positions and see if we can clear the FIFO_FULL flag. 00318 ps1.RDIndexHi = pos; 00319 // if( ps1.FIFO_RDIndex != ( pos >> FRAC_SHIFT ) ) ps1.Status &= ~FIFO_FULL; 00320 ps1.FIFO_RDIndex = (Bit16u)(pos >> FRAC_SHIFT); 00321 if( pending < 0 ) pending = 0; 00322 ps1.Pending = (Bitu)pending; 00323 00324 ps1.chanDAC->AddSamples_m8(length,MixTemp); 00325 } 00326 00327 static void PS1SN76496Update(Bitu length) 00328 { 00329 if ((ps1.last_writeSN+5000)<PIC_Ticks) { 00330 ps1.enabledSN=false; 00331 ps1.chanSN->Enable(false); 00332 } 00333 00334 //Bit16s * buffer=(Bit16s *)MixTemp; 00335 #if 0 00336 SN76496Update(&ps1.sn,buffer,length); 00337 #endif 00338 ps1.chanSN->AddSamples_m16(length,(Bit16s *)MixTemp); 00339 } 00340 00341 #include "regs.h" 00342 //static void PS1SOUND_Write(Bitu port,Bitu data,Bitu iolen) { 00343 // LOG_MSG("Write PS1 dac %X val %X (%04X:%08X)",port,data,SegValue(cs),reg_eip); 00344 //} 00345 00346 class PS1SOUND: public Module_base { 00347 private: 00348 IO_ReadHandleObject ReadHandler[2]; 00349 IO_WriteHandleObject WriteHandler[2]; 00350 MixerObject MixerChanDAC, MixerChanSN; 00351 public: 00352 PS1SOUND(Section* configuration):Module_base(configuration){ 00353 Section_prop * section=static_cast<Section_prop *>(configuration); 00354 00355 PS1AudioCard=false; 00356 00357 if ((strcmp(section->Get_string("ps1audio"),"true")!=0) && 00358 (strcmp(section->Get_string("ps1audio"),"on")!=0) && 00359 (strcmp(section->Get_string("ps1audio"),"auto")!=0)) return; 00360 00361 PS1AudioCard=true; 00362 LOG(LOG_MISC,LOG_DEBUG)("PS/1 sound emulation enabled"); 00363 00364 // Ports 0x0200-0x0205 (let normal code handle the joystick at 0x0201). 00365 ReadHandler[0].Install(0x200,&PS1SOUNDRead,IO_MB); 00366 ReadHandler[1].Install(0x202,&PS1SOUNDRead,IO_MB,6); //5); //3); 00367 00368 WriteHandler[0].Install(0x200,PS1SOUNDWrite,IO_MB); 00369 WriteHandler[1].Install(0x202,PS1SOUNDWrite,IO_MB,4); 00370 00371 Bit32u sample_rate = (Bit32u)section->Get_int("ps1audiorate"); 00372 ps1.chanDAC=MixerChanDAC.Install(&PS1SOUNDUpdate,sample_rate,"PS1 DAC"); 00373 ps1.chanSN=MixerChanSN.Install(&PS1SN76496Update,sample_rate,"PS1 SN76496"); 00374 00375 ps1.SampleRate=(int)sample_rate; 00376 ps1.enabledDAC=false; 00377 ps1.enabledSN=false; 00378 ps1.last_writeDAC = 0; 00379 ps1.last_writeSN = 0; 00380 PS1DAC_Reset(true); 00381 00382 // > Jmk wrote: 00383 // > Judging by what I've read in that technical document, it looks like the sound chip is fed by a 4 Mhz clock instead of a ~3.5 Mhz clock. 00384 // > 00385 // > So, there's a line in ps1_sound.cpp that looks like this: 00386 // > SN76496Reset( &ps1.sn, 3579545, sample_rate ); 00387 // > 00388 // > Instead, it should look like this: 00389 // > SN76496Reset( &ps1.sn, 4000000, sample_rate ); 00390 // > 00391 // > That should fix it! Mind you, that was with the old code (it was 0.72 I worked with) which may have been updated since, but the same principle applies. 00392 // 00393 // NTS: I do not have anything to test this change! --J.C. 00394 // SN76496Reset( &ps1.sn, 3579545, sample_rate ); 00395 #if 0 00396 SN76496Reset( &ps1.sn, 4000000, sample_rate ); 00397 #endif 00398 } 00399 ~PS1SOUND(){ } 00400 }; 00401 00402 static PS1SOUND* test = NULL; 00403 00404 void PS1SOUND_ShutDown(Section* sec) { 00405 (void)sec;//UNUSED 00406 if (test) { 00407 delete test; 00408 test = NULL; 00409 } 00410 } 00411 00412 void PS1SOUND_OnReset(Section* sec) { 00413 (void)sec;//UNUSED 00414 if (test == NULL && !IS_PC98_ARCH) { 00415 LOG(LOG_MISC,LOG_DEBUG)("Allocating PS/1 sound emulation"); 00416 test = new PS1SOUND(control->GetSection("speaker")); 00417 } 00418 } 00419 00420 void PS1SOUND_Init() { 00421 LOG(LOG_MISC,LOG_DEBUG)("Initializing PS/1 sound emulation"); 00422 00423 AddExitFunction(AddExitFunctionFuncPair(PS1SOUND_ShutDown),true); 00424 AddVMEventFunction(VM_EVENT_RESET,AddVMEventFunctionFuncPair(PS1SOUND_OnReset)); 00425 } 00426