DOSBox-X
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator
src/hardware/tandy_sound.cpp
00001 /*
00002  *  Copyright (C) 2002-2015  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 /* 
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 "hardware.h"
00031 #include "control.h"
00032 #include "sn76496.h"
00033 #include <cstring>
00034 #include <math.h>
00035 
00036 #define MAX_OUTPUT 0x7fff
00037 #define STEP 0x10000
00038 
00039 /* Formulas for noise generator */
00040 /* bit0 = output */
00041 
00042 /* noise feedback for white noise mode (verified on real SN76489 by John Kortink) */
00043 #define FB_WNOISE 0x14002       /* (16bits) bit16 = bit0(out) ^ bit2 ^ bit15 */
00044 
00045 /* noise feedback for periodic noise mode */
00046 //#define FB_PNOISE 0x10000 /* 16bit rorate */
00047 #define FB_PNOISE 0x08000   /* JH 981127 - fixes Do Run Run */
00048 
00049 /*
00050 0x08000 is definitely wrong. The Master System conversion of Marble Madness
00051 uses periodic noise as a baseline. With a 15-bit rotate, the bassline is
00052 out of tune.
00053 The 16-bit rotate has been confirmed against a real PAL Sega Master System 2.
00054 Hope that helps the System E stuff, more news on the PSG as and when!
00055 */
00056 
00057 /* noise generator start preset (for periodic noise) */
00058 #define NG_PRESET 0x0f35
00059 
00060 static struct SN76496 sn;
00061 
00062 #define TDAC_DMA_BUFSIZE 1024
00063 
00064 static struct {
00065         MixerChannel * chan;
00066         bool enabled;
00067         Bitu last_write;
00068         struct {
00069                 MixerChannel * chan;
00070                 bool enabled;
00071                 struct {
00072                         Bitu base;
00073                         Bit8u irq,dma;
00074                 } hw;
00075                 struct {
00076                         Bitu rate;
00077                         Bit8u buf[TDAC_DMA_BUFSIZE];
00078                         Bit8u last_sample;
00079                         DmaChannel * chan;
00080                         bool transfer_done;
00081                 } dma;
00082                 Bit8u mode,control;
00083                 Bit16u frequency;
00084                 Bit8u amplitude;
00085                 bool irq_activated;
00086         } dac;
00087 } tandy;
00088 
00089 void SN76496Write(struct SN76496 *R,Bitu port,Bitu data) {
00090     (void)port;//UNUSED
00091         /* update the output buffer before changing the registers */
00092 
00093         if (data & 0x80)
00094         {
00095                 int r = (data & 0x70) >> 4;
00096                 int c = r/2;
00097 
00098                 R->LastRegister = r;
00099                 R->Register[r] = (R->Register[r] & 0x3f0) | (data & 0x0f);
00100                 switch (r)
00101                 {
00102                         case 0: /* tone 0 : frequency */
00103                         case 2: /* tone 1 : frequency */
00104                         case 4: /* tone 2 : frequency */
00105                                 R->Period[c] = (int)R->UpdateStep * R->Register[r];
00106                                 if (R->Period[c] == 0) R->Period[c] = 0x3fe;
00107                                 if (r == 4)
00108                                 {
00109                                         /* update noise shift frequency */
00110                                         if ((R->Register[6] & 0x03) == 0x03)
00111                                                 R->Period[3] = 2 * R->Period[2];
00112                                 }
00113                                 break;
00114                         case 1: /* tone 0 : volume */
00115                         case 3: /* tone 1 : volume */
00116                         case 5: /* tone 2 : volume */
00117                         case 7: /* noise  : volume */
00118                                 R->Volume[c] = R->VolTable[data & 0x0f];
00119                                 break;
00120                         case 6: /* noise  : frequency, mode */
00121                                 {
00122                                         int n = R->Register[6];
00123                                         R->NoiseFB = (n & 4) ? FB_WNOISE : FB_PNOISE;
00124                                         n &= 3;
00125                                         /* N/512,N/1024,N/2048,Tone #3 output */
00126                                         R->Period[3] = (int)((n == 3) ? 2 * R->Period[2] : (int)(R->UpdateStep << (5+n)));
00127 
00128                                         /* reset noise shifter */
00129 //                                      R->RNG = NG_PRESET;
00130 //                                      R->Output[3] = R->RNG & 1;
00131                                 }
00132                                 break;
00133                 }
00134         }
00135         else
00136         {
00137                 int r = R->LastRegister;
00138                 int c = r/2;
00139 
00140                 switch (r)
00141                 {
00142                         case 0: /* tone 0 : frequency */
00143                         case 2: /* tone 1 : frequency */
00144                         case 4: /* tone 2 : frequency */
00145                                 R->Register[r] = (R->Register[r] & 0x0f) | ((data & 0x3f) << 4);
00146                                 R->Period[c] = (int)R->UpdateStep * R->Register[r];
00147                                 if (R->Period[c] == 0) R->Period[c] = 0x3fe;
00148                                 if (r == 4)
00149                                 {
00150                                         /* update noise shift frequency */
00151                                         if ((R->Register[6] & 0x03) == 0x03)
00152                                                 R->Period[3] = 2 * R->Period[2];
00153                                 }
00154                                 break;
00155                 }
00156         }
00157 }
00158 
00159 void SN76496Update(struct SN76496 *R, Bit16s *buffer, Bitu length) {
00160         int i;
00161 
00162         /* If the volume is 0, increase the counter */
00163         for (i = 0;i < 4;i++)
00164         {
00165                 if (R->Volume[i] == 0)
00166                 {
00167                         /* note that I do count += length, NOT count = length + 1. You might think */
00168                         /* it's the same since the volume is 0, but doing the latter could cause */
00169                         /* interferencies when the program is rapidly modulating the volume. */
00170                         if (R->Count[i] <= (int)length*STEP) R->Count[i] += (int)length*STEP;
00171                 }
00172         }
00173 
00174         Bitu count=length;
00175         while (count)
00176         {
00177                 int vol[4];
00178                 unsigned int out;
00179                 int left;
00180 
00181 
00182                 /* vol[] keeps track of how long each square wave stays */
00183                 /* in the 1 position during the sample period. */
00184                 vol[0] = vol[1] = vol[2] = vol[3] = 0;
00185 
00186                 for (i = 0;i < 3;i++)
00187                 {
00188                         if (R->Output[i]) vol[i] += R->Count[i];
00189                         R->Count[i] -= STEP;
00190                         /* Period[i] is the half period of the square wave. Here, in each */
00191                         /* loop I add Period[i] twice, so that at the end of the loop the */
00192                         /* square wave is in the same status (0 or 1) it was at the start. */
00193                         /* vol[i] is also incremented by Period[i], since the wave has been 1 */
00194                         /* exactly half of the time, regardless of the initial position. */
00195                         /* If we exit the loop in the middle, Output[i] has to be inverted */
00196                         /* and vol[i] incremented only if the exit status of the square */
00197                         /* wave is 1. */
00198                         while (R->Count[i] <= 0)
00199                         {
00200                                 R->Count[i] += R->Period[i];
00201                                 if (R->Count[i] > 0)
00202                                 {
00203                                         R->Output[i] ^= 1;
00204                                         if (R->Output[i]) vol[i] += R->Period[i];
00205                                         break;
00206                                 }
00207                                 R->Count[i] += R->Period[i];
00208                                 vol[i] += R->Period[i];
00209                         }
00210                         if (R->Output[i]) vol[i] -= R->Count[i];
00211                 }
00212 
00213                 left = STEP;
00214                 do
00215                 {
00216                         int nextevent;
00217 
00218 
00219                         if (R->Count[3] < left) nextevent = R->Count[3];
00220                         else nextevent = left;
00221 
00222                         if (R->Output[3]) vol[3] += R->Count[3];
00223                         R->Count[3] -= nextevent;
00224                         if (R->Count[3] <= 0)
00225                         {
00226                                 if (R->RNG & 1) R->RNG ^= (unsigned int)R->NoiseFB;
00227                                 R->RNG >>= 1;
00228                                 R->Output[3] = R->RNG & 1;
00229                                 R->Count[3] += R->Period[3];
00230                                 if (R->Output[3]) vol[3] += R->Period[3];
00231                         }
00232                         if (R->Output[3]) vol[3] -= R->Count[3];
00233 
00234                         left -= nextevent;
00235                 } while (left > 0);
00236 
00237         out = (unsigned int)
00238               (vol[0] * R->Volume[0] + vol[1] * R->Volume[1] +
00239                vol[2] * R->Volume[2] + vol[3] * R->Volume[3]);
00240 
00241                 if (out > MAX_OUTPUT * STEP) out = MAX_OUTPUT * STEP;
00242 
00243                 *(buffer++) = (Bit16s)(out / STEP);
00244 
00245                 count--;
00246         }
00247 }
00248 
00249 static void TandySN76496Write(Bitu port,Bitu data,Bitu iolen) {
00250     (void)iolen;//UNUSED
00251         struct SN76496 *R = &sn;
00252  
00253         tandy.last_write=PIC_Ticks;
00254         if (!tandy.enabled) {
00255                 tandy.chan->Enable(true);
00256                 tandy.enabled=true;
00257         }
00258 
00259         // assume state change, always.
00260         // this hack allows sample accurate rendering without enabling sample accurate mode in the mixer.
00261         tandy.chan->FillUp();
00262 
00263         SN76496Write(R,port,data);
00264 }
00265 
00266 static void TandySN76496Update(Bitu length) {
00267         struct SN76496 *R = &sn;
00268 
00269         if ((tandy.last_write+5000)<PIC_Ticks) {
00270                 tandy.enabled=false;
00271                 tandy.chan->Enable(false);
00272         }
00273  
00274         Bit16s * buffer=(Bit16s *)MixTemp;
00275         SN76496Update(R,buffer,length);
00276         tandy.chan->AddSamples_m16(length,(Bit16s *)MixTemp);
00277 }
00278 
00279 static void TandyDACWrite(Bitu port,Bitu data,Bitu iolen) {
00280     (void)iolen;//UNUSED
00281         LOG_MSG("Write tandy dac %X val %X",(int)port,(int)data);
00282 }
00283 
00284 static void SN76496_set_clock(struct SN76496 *R, int clock) {
00285         /* the base clock for the tone generators is the chip clock divided by 16; */
00286         /* for the noise generator, it is clock / 256. */
00287         /* Here we calculate the number of steps which happen during one sample */
00288         /* at the given sample rate. No. of events = sample rate / (clock/16). */
00289         /* STEP is a multiplier used to turn the fraction into a fixed point */
00290         /* number. */
00291         R->UpdateStep = (unsigned int)(((double)STEP * R->SampleRate * 16) / clock);
00292 }
00293 
00294 
00295 static void SN76496_set_gain(struct SN76496 *R, int gain) {
00296         int i;
00297         double out;
00298 
00299         gain &= 0xff;
00300 
00301         /* increase max output basing on gain (0.2 dB per step) */
00302         out = MAX_OUTPUT / 3;
00303         while (gain-- > 0)
00304                 out *= 1.023292992;     /* = (10 ^ (0.2/20)) */
00305 
00306         /* build volume table (2dB per step) */
00307         for (i = 0;i < 15;i++)
00308         {
00309                 /* limit volume to avoid clipping */
00310                 if (out > MAX_OUTPUT / 3) R->VolTable[i] = MAX_OUTPUT / 3;
00311                 else R->VolTable[i] = (int)out;
00312 
00313                 out /= 1.258925412;     /* = 10 ^ (2/20) = 2dB */
00314         }
00315         R->VolTable[15] = 0;
00316 }
00317 
00318 void SN76496Reset(struct SN76496 *R, Bitu Clock, Bitu sample_rate) {
00319         Bitu i;
00320         R->SampleRate = sample_rate;
00321         SN76496_set_clock(R,Clock);
00322         for (i = 0;i < 4;i++) R->Volume[i] = 0;
00323         R->LastRegister = 0;
00324         for (i = 0;i < 8;i+=2)
00325         {
00326                 R->Register[i] = 0;
00327                 R->Register[i + 1] = 0x0f;      /* volume = 0 */
00328         }
00329         
00330         for (i = 0;i < 4;i++)
00331         {
00332                 R->Output[i] = 0;
00333                 R->Period[i] = R->Count[i] = (int)R->UpdateStep;
00334         }
00335         R->RNG = NG_PRESET;
00336         R->Output[3] = R->RNG & 1;
00337         SN76496_set_gain(R,0x1);
00338 }
00339 
00340 bool TS_Get_Address(Bitu& tsaddr, Bitu& tsirq, Bitu& tsdma) {
00341         tsaddr=0;
00342         tsirq =0;
00343         tsdma =0;
00344         if (tandy.dac.enabled) {
00345                 tsaddr=tandy.dac.hw.base;
00346                 tsirq =tandy.dac.hw.irq;
00347                 tsdma =tandy.dac.hw.dma;
00348                 return true;
00349         }
00350         return false;
00351 }
00352 
00353 
00354 static void TandyDAC_DMA_CallBack(DmaChannel * /*chan*/, DMAEvent event) {
00355         if (event == DMA_REACHED_TC) {
00356                 tandy.dac.dma.transfer_done=true;
00357                 PIC_ActivateIRQ(tandy.dac.hw.irq);
00358         }
00359 }
00360 
00361 /* NTS: Formerly defined static, but nobody uses the function. But, this
00362  * function contains too much technical documentation within that is worth
00363  * keeping. --J.C. */
00364 void TandyDACModeChanged(void) {
00365         switch (tandy.dac.mode&3) {
00366         case 0:
00367                 // joystick mode
00368                 break;
00369         case 1:
00370                 break;
00371         case 2:
00372                 // recording
00373                 break;
00374         case 3:
00375                 // playback
00376                 tandy.dac.chan->FillUp();
00377                 if (tandy.dac.frequency!=0) {
00378                         float freq=3579545.0f/((float)tandy.dac.frequency);
00379                         tandy.dac.chan->SetFreq((Bitu)freq);
00380                         float vol=((float)tandy.dac.amplitude)/7.0f;
00381                         tandy.dac.chan->SetVolume(vol,vol);
00382                         if ((tandy.dac.mode&0x0c)==0x0c) {
00383                                 tandy.dac.dma.transfer_done=false;
00384                                 tandy.dac.dma.chan=GetDMAChannel(tandy.dac.hw.dma);
00385                                 if (tandy.dac.dma.chan) {
00386                                         tandy.dac.dma.chan->Register_Callback(TandyDAC_DMA_CallBack);
00387                                         tandy.dac.chan->Enable(true);
00388 //                                      LOG_MSG("Tandy DAC: playback started with freqency %f, volume %f",freq,vol);
00389                                 }
00390                         }
00391                 }
00392                 break;
00393         }
00394 }
00395 
00396 static Bitu TandyDACRead(Bitu port,Bitu /*iolen*/) {
00397         switch (port) {
00398         case 0xc4:
00399                 return (tandy.dac.mode&0x77u) | (tandy.dac.irq_activated ? 0x08u : 0x00u);
00400         case 0xc6:
00401                 return (Bit8u)(tandy.dac.frequency&0xffu);
00402         case 0xc7:
00403                 return (Bit8u)((((Bitu)tandy.dac.frequency>>8u)&0xfu) | (Bitu)(tandy.dac.amplitude<<5u));
00404         }
00405         LOG_MSG("Tandy DAC: Read from unknown %X",(int)port);
00406         return 0xff;
00407 }
00408 
00409 static void TandyDACGenerateDMASound(Bitu length) {
00410         if (length) {
00411                 Bitu read=tandy.dac.dma.chan->Read(length,tandy.dac.dma.buf);
00412                 tandy.dac.chan->AddSamples_m8(read,tandy.dac.dma.buf);
00413                 if (read < length) {
00414                         if (read>0) tandy.dac.dma.last_sample=tandy.dac.dma.buf[read-1];
00415                         for (Bitu ct=read; ct < length; ct++) {
00416                                 tandy.dac.chan->AddSamples_m8(1,&tandy.dac.dma.last_sample);
00417                         }
00418                 }
00419         }
00420 }
00421 
00422 static void TandyDACUpdate(Bitu length) {
00423         if (tandy.dac.enabled && ((tandy.dac.mode&0x0c)==0x0c)) {
00424                 if (!tandy.dac.dma.transfer_done) {
00425                         Bitu len = length;
00426                         TandyDACGenerateDMASound(len);
00427                 } else {
00428                         for (Bitu ct=0; ct < length; ct++) {
00429                                 tandy.dac.chan->AddSamples_m8(1,&tandy.dac.dma.last_sample);
00430                         }
00431                 }
00432         } else {
00433                 tandy.dac.chan->AddSilence();
00434         }
00435 }
00436 
00437 Bit8u BIOS_tandy_D4_flag = 0;
00438 
00439 class TANDYSOUND: public Module_base {
00440 private:
00441         IO_WriteHandleObject WriteHandler[4];
00442         IO_ReadHandleObject ReadHandler[4];
00443         MixerObject MixerChan;
00444         MixerObject MixerChanDAC;
00445 public:
00446         TANDYSOUND(Section* configuration):Module_base(configuration){
00447                 Section_prop * section=static_cast<Section_prop *>(configuration);
00448 
00449                 bool enable_hw_tandy_dac=true;
00450                 Bitu sbport, sbirq, sbdma;
00451                 if (SB_Get_Address(sbport, sbirq, sbdma)) {
00452                         enable_hw_tandy_dac=false;
00453                 }
00454 
00455                 BIOS_tandy_D4_flag = 0;
00456                 if (IS_TANDY_ARCH) {
00457                         /* enable tandy sound if tandy=true/auto */
00458                         if ((strcmp(section->Get_string("tandy"),"true")!=0) &&
00459                                 (strcmp(section->Get_string("tandy"),"on")!=0) &&
00460                                 (strcmp(section->Get_string("tandy"),"auto")!=0)) return;
00461                 } else {
00462                         /* only enable tandy sound if tandy=true */
00463                         if ((strcmp(section->Get_string("tandy"),"true")!=0) &&
00464                                 (strcmp(section->Get_string("tandy"),"on")!=0)) return;
00465 
00466                         /* ports from second DMA controller conflict with tandy ports */
00467                         CloseSecondDMAController();
00468 
00469                         if (enable_hw_tandy_dac) {
00470                                 WriteHandler[2].Install(0x1e0,TandySN76496Write,IO_MB,2);
00471                                 WriteHandler[3].Install(0x1e4,TandyDACWrite,IO_MB,4);
00472 //                              ReadHandler[3].Install(0x1e4,TandyDACRead,IO_MB,4);
00473                         }
00474                 }
00475 
00476 
00477                 Bit32u sample_rate = (unsigned int)section->Get_int("tandyrate");
00478                 tandy.chan=MixerChan.Install(&TandySN76496Update,sample_rate,"TANDY");
00479 
00480                 WriteHandler[0].Install(0xc0,TandySN76496Write,IO_MB,2);
00481 
00482                 if (enable_hw_tandy_dac) {
00483                         // enable low-level Tandy DAC emulation
00484                         WriteHandler[1].Install(0xc4,TandyDACWrite,IO_MB,4);
00485                         ReadHandler[1].Install(0xc4,TandyDACRead,IO_MB,4);
00486 
00487                         tandy.dac.enabled=true;
00488                         tandy.dac.chan=MixerChanDAC.Install(&TandyDACUpdate,sample_rate,"TANDYDAC");
00489 
00490                         tandy.dac.hw.base=0xc4;
00491                         tandy.dac.hw.irq =7;
00492                         tandy.dac.hw.dma =1;
00493                 } else {
00494                         tandy.dac.enabled=false;
00495                         tandy.dac.hw.base=0;
00496                         tandy.dac.hw.irq =0;
00497                         tandy.dac.hw.dma =0;
00498                 }
00499 
00500                 tandy.dac.control=0;
00501                 tandy.dac.mode   =0;
00502                 tandy.dac.irq_activated=false;
00503                 tandy.dac.frequency=0;
00504                 tandy.dac.amplitude=0;
00505                 tandy.dac.dma.last_sample=0;
00506 
00507 
00508                 tandy.enabled=false;
00509                 BIOS_tandy_D4_flag = 0xFF;
00510 
00511                 SN76496Reset( &sn, 3579545, sample_rate );
00512         }
00513         ~TANDYSOUND(){ }
00514 };
00515 
00516 
00517 
00518 static TANDYSOUND* test = NULL;
00519 
00520 void TANDYSOUND_ShutDown(Section* /*sec*/) {
00521     if (test) {
00522         delete test;
00523         test = NULL;
00524     }
00525 }
00526 
00527 void TANDYSOUND_OnReset(Section* sec) {
00528     (void)sec;//UNUSED
00529         if (test == NULL && !IS_PC98_ARCH) {
00530                 LOG(LOG_MISC,LOG_DEBUG)("Allocating Tandy speaker emulation");
00531                 test = new TANDYSOUND(control->GetSection("speaker"));
00532         }
00533 }
00534 
00535 void TANDYSOUND_Init() {
00536         LOG(LOG_MISC,LOG_DEBUG)("Initializing Tandy voice emulation");
00537 
00538         AddExitFunction(AddExitFunctionFuncPair(TANDYSOUND_ShutDown),true);
00539         AddVMEventFunction(VM_EVENT_RESET,AddVMEventFunctionFuncPair(TANDYSOUND_OnReset));
00540 }
00541