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 /* 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 }