DOSBox-X
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
src/hardware/ps1_sound.cpp
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