DOSBox-X
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
src/hardware/tandy_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 /* 
00020         Based of sn76496.c of the M.A.M.E. project
00021 */
00022 
00023 #include "dosbox.h"
00024 #include "inout.h"
00025 #include "mixer.h"
00026 #include "mem.h"
00027 #include "setup.h"
00028 #include "pic.h"
00029 #include "dma.h"
00030 #include "control.h"
00031 #include "hardware.h"
00032 #include <cstring>
00033 #include <math.h>
00034 #include "mame/emu.h"
00035 #include "mame/sn76496.h"
00036 
00037 
00038 #define SOUND_CLOCK (14318180 / 4)
00039 
00040 #define TDAC_DMA_BUFSIZE 1024
00041 
00042 static struct {
00043         MixerChannel * chan;
00044         bool enabled;
00045         Bitu last_write;
00046         struct {
00047                 MixerChannel * chan;
00048                 bool enabled;
00049                 struct {
00050                         Bitu base;
00051                         Bit8u irq,dma;
00052                 } hw;
00053                 struct {
00054                         Bitu rate;
00055                         Bit8u buf[TDAC_DMA_BUFSIZE];
00056                         Bit8u last_sample;
00057                         DmaChannel * chan;
00058                         bool transfer_done;
00059                 } dma;
00060                 Bit8u mode,control;
00061                 Bit16u frequency;
00062                 Bit8u amplitude;
00063                 bool irq_activated;
00064         } dac;
00065 } tandy;
00066 
00067 static sn76496_device device_sn76496(machine_config(), 0, 0, SOUND_CLOCK );
00068 static ncr8496_device device_ncr8496(machine_config(), 0, 0, SOUND_CLOCK);
00069 
00070 static sn76496_base_device* activeDevice = &device_ncr8496;
00071 #define device (*activeDevice)
00072 
00073 static void SN76496Write(Bitu /*port*/,Bitu data,Bitu /*iolen*/) {
00074         tandy.last_write=PIC_Ticks;
00075         if (!tandy.enabled) {
00076                 tandy.chan->Enable(true);
00077                 tandy.enabled=true;
00078         }
00079 
00080         // assume state change, always.
00081         // this hack allows sample accurate rendering without enabling sample accurate mode in the mixer.
00082         tandy.chan->FillUp();
00083 
00084         device.write((uint8_t)data);
00085 
00086 //      LOG_MSG("3voice write %X at time %7.3f",data,PIC_FullIndex());
00087 }
00088 
00089 static void SN76496Update(Bitu length) {
00090         //Disable the channel if it's been quiet for a while
00091         if ((tandy.last_write+5000)<PIC_Ticks) {
00092                 tandy.enabled=false;
00093                 tandy.chan->Enable(false);
00094                 return;
00095         }
00096         const Bitu MAX_SAMPLES = 2048;
00097         if (length > MAX_SAMPLES)
00098                 return;
00099         Bit16s buffer[MAX_SAMPLES];
00100         Bit16s* outputs = buffer;
00101 
00102         device_sound_interface::sound_stream stream;
00103         static_cast<device_sound_interface&>(device).sound_stream_update(stream, 0, &outputs, (int)length);
00104         tandy.chan->AddSamples_m16(length, buffer);
00105 }
00106 
00107 bool TS_Get_Address(Bitu& tsaddr, Bitu& tsirq, Bitu& tsdma) {
00108         tsaddr=0;
00109         tsirq =0;
00110         tsdma =0;
00111         if (tandy.dac.enabled) {
00112                 tsaddr=tandy.dac.hw.base;
00113                 tsirq =tandy.dac.hw.irq;
00114                 tsdma =tandy.dac.hw.dma;
00115                 return true;
00116         }
00117         return false;
00118 }
00119 
00120 
00121 static void TandyDAC_DMA_CallBack(DmaChannel * /*chan*/, DMAEvent event) {
00122         if (event == DMA_REACHED_TC) {
00123                 tandy.dac.dma.transfer_done=true;
00124                 PIC_ActivateIRQ(tandy.dac.hw.irq);
00125         }
00126 }
00127 
00128 static void TandyDACModeChanged(void) {
00129         switch (tandy.dac.mode&3) {
00130         case 0:
00131                 // joystick mode
00132                 break;
00133         case 1:
00134                 break;
00135         case 2:
00136                 // recording
00137                 break;
00138         case 3:
00139                 // playback
00140                 tandy.dac.chan->FillUp();
00141                 if (tandy.dac.frequency!=0) {
00142                         float freq=3579545.0f/((float)tandy.dac.frequency);
00143                         tandy.dac.chan->SetFreq((Bitu)freq);
00144                         float vol=((float)tandy.dac.amplitude)/7.0f;
00145                         tandy.dac.chan->SetVolume(vol,vol);
00146                         if ((tandy.dac.mode&0x0c)==0x0c) {
00147                                 tandy.dac.dma.transfer_done=false;
00148                                 tandy.dac.dma.chan=GetDMAChannel(tandy.dac.hw.dma);
00149                                 if (tandy.dac.dma.chan) {
00150                                         tandy.dac.dma.chan->Register_Callback(TandyDAC_DMA_CallBack);
00151                                         tandy.dac.chan->Enable(true);
00152 //                                      LOG_MSG("Tandy DAC: playback started with freqency %f, volume %f",freq,vol);
00153                                 }
00154                         }
00155                 }
00156                 break;
00157         }
00158 }
00159 
00160 static void TandyDACDMAEnabled(void) {
00161         TandyDACModeChanged();
00162 }
00163 
00164 static void TandyDACDMADisabled(void) {
00165 }
00166 
00167 static void TandyDACWrite(Bitu port,Bitu data,Bitu /*iolen*/) {
00168         switch (port) {
00169         case 0xc4: {
00170                 Bitu oldmode = tandy.dac.mode;
00171                 tandy.dac.mode = (Bit8u)(data&0xff);
00172                 if ((data&3)!=(oldmode&3)) {
00173                         TandyDACModeChanged();
00174                 }
00175                 if (((data&0x0c)==0x0c) && ((oldmode&0x0c)!=0x0c)) {
00176                         TandyDACDMAEnabled();
00177                 } else if (((data&0x0c)!=0x0c) && ((oldmode&0x0c)==0x0c)) {
00178                         TandyDACDMADisabled();
00179                 }
00180                 }
00181                 break;
00182         case 0xc5:
00183                 switch (tandy.dac.mode&3) {
00184                 case 0:
00185                         // joystick mode
00186                         break;
00187                 case 1:
00188                         tandy.dac.control = (Bit8u)(data&0xff);
00189                         break;
00190                 case 2:
00191                         break;
00192                 case 3:
00193                         // direct output
00194                         break;
00195                 }
00196                 break;
00197         case 0xc6:
00198                 tandy.dac.frequency = (tandy.dac.frequency & 0xf00) | (Bit8u)(data & 0xff);
00199                 switch (tandy.dac.mode&3) {
00200                 case 0:
00201                         // joystick mode
00202                         break;
00203                 case 1:
00204                 case 2:
00205                 case 3:
00206                         TandyDACModeChanged();
00207                         break;
00208                 }
00209                 break;
00210         case 0xc7:
00211                 tandy.dac.frequency = (tandy.dac.frequency & 0x00ff) | (((Bit8u)(data & 0xf)) << 8);
00212                 tandy.dac.amplitude = (Bit8u)(data>>5);
00213                 switch (tandy.dac.mode&3) {
00214                 case 0:
00215                         // joystick mode
00216                         break;
00217                 case 1:
00218                 case 2:
00219                 case 3:
00220                         TandyDACModeChanged();
00221                         break;
00222                 }
00223                 break;
00224         }
00225 }
00226 
00227 static Bitu TandyDACRead(Bitu port,Bitu /*iolen*/) {
00228         switch (port) {
00229         case 0xc4:
00230                 return (tandy.dac.mode&0x77) | (tandy.dac.irq_activated ? 0x08 : 0x00);
00231         case 0xc6:
00232                 return (Bit8u)(tandy.dac.frequency&0xff);
00233         case 0xc7:
00234                 return (Bit8u)(((tandy.dac.frequency>>8)&0xf) | (tandy.dac.amplitude<<5));
00235         }
00236         LOG_MSG("Tandy DAC: Read from unknown %X", (unsigned int)port);
00237         return 0xff;
00238 }
00239 
00240 static void TandyDACGenerateDMASound(Bitu length) {
00241         if (length) {
00242                 Bitu read = tandy.dac.dma.chan->Read(length,tandy.dac.dma.buf);
00243                 if (read > 0) {
00244                         tandy.dac.chan->AddSamples_m8(read,tandy.dac.dma.buf);
00245                         tandy.dac.dma.last_sample = tandy.dac.dma.buf[read - 1u];
00246                 }
00247 
00248                 /* repeat the last sample to fill output if not enough */
00249                 if (read < length) {
00250                         for (Bitu ct=read; ct < length; ct++) {
00251                                 tandy.dac.chan->AddSamples_m8(1,&tandy.dac.dma.last_sample);
00252                         }
00253                 }
00254         }
00255 }
00256 
00257 static void TandyDACUpdate(Bitu length) {
00258         if (tandy.dac.enabled && ((tandy.dac.mode&0x0c)==0x0c)) {
00259                 if (!tandy.dac.dma.transfer_done) {
00260                         Bitu len = length;
00261                         TandyDACGenerateDMASound(len);
00262                 } else {
00263                         for (Bitu ct=0; ct < length; ct++) {
00264                                 tandy.dac.chan->AddSamples_m8(1,&tandy.dac.dma.last_sample);
00265                         }
00266                 }
00267                 /* even if the DAC is disabled hold the last sample and SLOWLY restore DC.
00268                  * jumping to silence quickly here causes loud annoying pops in Prince of Persia
00269                  * when sound effects play. Prince of Persia seems to emit some very non-zero DC
00270                  * value (like 0x08) when not playing sound effects, so this is needed to
00271                  * recover from that without pops. This behavior also seems to match DAC behavior
00272                  * with Prince of Persia according to real Tandy 1000 TL/3 hardware, complete
00273                  * with the 8-bit DAC stepping noise on return to zero. In any case, this
00274                  * modification makes the Tandy DAC sound effects in Prince of Persia much
00275                  * more enjoyable to listen to and match the apparent sound of real hardware. */
00276                 /* NTS: This doesn't quite make the DAC step noise heard on real hardware, but
00277                  *      it's good enough for now. */
00278         } else if (tandy.dac.dma.last_sample != 128) {
00279                 for (Bitu ct=0; ct < length; ct++) {
00280                         tandy.dac.chan->AddSamples_m8(1,&tandy.dac.dma.last_sample);
00281                         if (tandy.dac.dma.last_sample != 128)
00282                                 tandy.dac.dma.last_sample = (Bit8u)(((((int)tandy.dac.dma.last_sample - 128) * 63) / 64) + 128);
00283                 }
00284         } else {
00285                 tandy.dac.chan->AddSilence();
00286         }
00287 }
00288 
00289 Bit8u BIOS_tandy_D4_flag = 0;
00290 
00291 class TANDYSOUND: public Module_base {
00292 private:
00293         IO_WriteHandleObject WriteHandler[4];
00294         IO_ReadHandleObject ReadHandler[4];
00295         MixerObject MixerChan;
00296         MixerObject MixerChanDAC;
00297 public:
00298         TANDYSOUND(Section* configuration):Module_base(configuration){
00299                 Section_prop * section=static_cast<Section_prop *>(configuration);
00300 
00301                 bool enable_hw_tandy_dac=true;
00302                 Bitu sbport, sbirq, sbdma;
00303                 if (SB_Get_Address(sbport, sbirq, sbdma)) {
00304                         enable_hw_tandy_dac=false;
00305                 }
00306 
00307                 BIOS_tandy_D4_flag = 0;
00308 
00309                 //Select the correct tandy chip implementation
00310                 if (machine == MCH_PCJR) activeDevice = &device_sn76496;
00311                 else activeDevice = &device_ncr8496;
00312 
00313                 if (IS_TANDY_ARCH) {
00314                         /* enable tandy sound if tandy=true/auto */
00315                         if ((strcmp(section->Get_string("tandy"),"true")!=0) &&
00316                                 (strcmp(section->Get_string("tandy"),"on")!=0) &&
00317                                 (strcmp(section->Get_string("tandy"),"auto")!=0)) return;
00318                 } else {
00319                         /* only enable tandy sound if tandy=true */
00320                         if ((strcmp(section->Get_string("tandy"),"true")!=0) &&
00321                                 (strcmp(section->Get_string("tandy"),"on")!=0)) return;
00322 
00323                         if (enable_hw_tandy_dac) {
00324                                 WriteHandler[2].Install(0x1e0,SN76496Write,IO_MB,2);
00325                                 WriteHandler[3].Install(0x1e4,TandyDACWrite,IO_MB,4);
00326 //                              ReadHandler[3].Install(0x1e4,TandyDACRead,IO_MB,4);
00327                         }
00328                 }
00329 
00330                 /* ports from second DMA controller conflict with tandy ports at 0xC0.
00331                  * Furthermore, the default I/O handlers after de-registration are needed
00332                  * to ensure the SN76496 is writeable at port 0xC0 whether you're doing
00333                  * normal 8-bit I/O or your a weirdo like Prince of Persia using 16-bit
00334                  * I/O to write frequency values. (bugfix for Tandy mode of Prince of
00335                  * Persia). */
00336                 CloseSecondDMAController();
00337 
00338                 Bit32u sample_rate = section->Get_int("tandyrate");
00339                 tandy.chan=MixerChan.Install(&SN76496Update,sample_rate,"TANDY");
00340 
00341                 WriteHandler[0].Install(0xc0,SN76496Write,IO_MB,2);
00342 
00343                 if (enable_hw_tandy_dac) {
00344                         // enable low-level Tandy DAC emulation
00345                         WriteHandler[1].Install(0xc4,TandyDACWrite,IO_MB,4);
00346                         ReadHandler[1].Install(0xc4,TandyDACRead,IO_MB,4);
00347 
00348                         tandy.dac.enabled=true;
00349                         tandy.dac.chan=MixerChanDAC.Install(&TandyDACUpdate,sample_rate,"TANDYDAC");
00350                         tandy.dac.chan->SetLowpassFreq(6000);
00351                         tandy.dac.chan->SetSlewFreq(22050);
00352 
00353                         tandy.dac.hw.base=0xc4;
00354                         tandy.dac.hw.irq =7;
00355                         tandy.dac.hw.dma =1;
00356                 } else {
00357                         tandy.dac.enabled=false;
00358                         tandy.dac.hw.base=0;
00359                         tandy.dac.hw.irq =0;
00360                         tandy.dac.hw.dma =0;
00361                 }
00362 
00363                 tandy.dac.control=0;
00364                 tandy.dac.mode   =0;
00365                 tandy.dac.irq_activated=false;
00366                 tandy.dac.frequency=0;
00367                 tandy.dac.amplitude=0;
00368                 tandy.dac.dma.last_sample=128;
00369 
00370 
00371                 tandy.enabled=false;
00372                 BIOS_tandy_D4_flag = 0xFF;
00373 
00374                 ((device_t&)device).device_start();
00375                 device.convert_samplerate(sample_rate);
00376 
00377         }
00378         ~TANDYSOUND(){ }
00379 };
00380 
00381 
00382 
00383 static TANDYSOUND* test = NULL;
00384 
00385 void TANDYSOUND_ShutDown(Section* /*sec*/) {
00386     if (test) {
00387         delete test;
00388         test = NULL;
00389     }
00390 }
00391 
00392 void TANDYSOUND_OnReset(Section* sec) {
00393     (void)sec;//UNUSED
00394         if (test == NULL && !IS_PC98_ARCH) {
00395                 LOG(LOG_MISC,LOG_DEBUG)("Allocating Tandy speaker emulation");
00396                 test = new TANDYSOUND(control->GetSection("speaker"));
00397         }
00398 }
00399 
00400 void TANDYSOUND_Init() {
00401         LOG(LOG_MISC,LOG_DEBUG)("Initializing Tandy voice emulation");
00402 
00403         AddExitFunction(AddExitFunctionFuncPair(TANDYSOUND_ShutDown),true);
00404         AddVMEventFunction(VM_EVENT_RESET,AddVMEventFunctionFuncPair(TANDYSOUND_OnReset));
00405 }
00406 
00407 // save state support
00408 void *TandyDAC_DMA_CallBack_Func = (void*)((uintptr_t)TandyDAC_DMA_CallBack);
00409 
00410 void POD_Save_Tandy_Sound( std::ostream& stream )
00411 {
00412         const char pod_name[32] = "Tandy";
00413 
00414         if( stream.fail() ) return;
00415         if( !test ) return;
00416         if( !tandy.chan ) return;
00417 
00418 
00419         WRITE_POD( &pod_name, pod_name );
00420 
00421         //*******************************************
00422         //*******************************************
00423         //*******************************************
00424 
00425         Bit8u dma_idx;
00426 
00427 
00428         dma_idx = 0xff;
00429         for( int lcv=0; lcv<8; lcv++ ) {
00430                 if( tandy.dac.dma.chan == GetDMAChannel(lcv) ) { dma_idx = lcv; break; }
00431         }
00432 
00433         // *******************************************
00434         // *******************************************
00435         // *******************************************
00436 
00437         // - near-pure data
00438         WRITE_POD( &tandy, tandy );
00439 
00440         // - reloc ptr
00441         WRITE_POD( &dma_idx, dma_idx );
00442 
00443         // *******************************************
00444         // *******************************************
00445         // *******************************************
00446 
00447     activeDevice->SaveState(stream);
00448 
00449         tandy.chan->SaveState(stream);
00450         tandy.dac.chan->SaveState(stream);
00451 }
00452 
00453 void POD_Load_Tandy_Sound( std::istream& stream )
00454 {
00455         char pod_name[32] = {0};
00456 
00457         if( stream.fail() ) return;
00458         if( !test ) return;
00459         if( !tandy.chan ) return;
00460 
00461 
00462         // error checking
00463         READ_POD( &pod_name, pod_name );
00464         if( strcmp( pod_name, "Tandy" ) ) {
00465                 stream.clear( std::istream::failbit | std::istream::badbit );
00466                 return;
00467         }
00468 
00469         //************************************************
00470         //************************************************
00471         //************************************************
00472 
00473         Bit8u dma_idx;
00474         MixerChannel *chan_old, *dac_chan_old;
00475 
00476         // - save static ptrs
00477         chan_old = tandy.chan;
00478         dac_chan_old = tandy.dac.chan;
00479 
00480         // *******************************************
00481         // *******************************************
00482         // *******************************************
00483 
00484         // - near-pure data
00485         READ_POD( &tandy, tandy );
00486 
00487         // - reloc ptr
00488         READ_POD( &dma_idx, dma_idx );
00489 
00490         // *******************************************
00491         // *******************************************
00492         // *******************************************
00493         tandy.dac.dma.chan = NULL;
00494         if( dma_idx != 0xff ) tandy.dac.dma.chan = GetDMAChannel(dma_idx);
00495 
00496         // *******************************************
00497         // *******************************************
00498         // *******************************************
00499 
00500         // - restore static ptrs
00501         tandy.chan = chan_old;
00502         tandy.dac.chan = dac_chan_old;
00503     activeDevice->LoadState(stream);
00504 
00505         tandy.chan->LoadState(stream);
00506         tandy.dac.chan->LoadState(stream);
00507 }