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