mirror of
https://github.com/RetroDECK/Supermodel.git
synced 2024-11-22 22:05:38 +00:00
2177 lines
56 KiB
C++
2177 lines
56 KiB
C++
|
|
/**
|
|
** Supermodel
|
|
** A Sega Model 3 Arcade Emulator.
|
|
** Copyright 2011 Bart Trzynadlowski, Nik Henson
|
|
**
|
|
** This file is part of Supermodel.
|
|
**
|
|
** Supermodel is free software: you can redistribute it and/or modify it under
|
|
** the terms of the GNU General Public License as published by the Free
|
|
** Software Foundation, either version 3 of the License, or (at your option)
|
|
** any later version.
|
|
**
|
|
** Supermodel is distributed in the hope that it will be useful, but WITHOUT
|
|
** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
** FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
** more details.
|
|
**
|
|
** You should have received a copy of the GNU General Public License along
|
|
** with Supermodel. If not, see <http://www.gnu.org/licenses/>.
|
|
**/
|
|
|
|
/*
|
|
* SCSP.cpp
|
|
*
|
|
* WARNING: Here be dragons! Tread carefully. Enabling/disabling things may
|
|
* break save state support.
|
|
*
|
|
* SCSP (Sega Custom Sound Processor) emulation. This code was generously
|
|
* donated by ElSemi. Interfaces directly to the 68K processor through
|
|
* callbacks. Some minor interface changes were made (external global variables
|
|
* were removed).
|
|
*
|
|
* The MIDI input buffer has been increased from 8 (which I assume is the
|
|
* actual size) in order to accommodate Model 3's PowerPC/68K communication.
|
|
* There is probably tight synchronization between the CPUs, with PowerPC-side
|
|
* interrupts being generated to fill the MIDI buffer as the 68K pulls data
|
|
* out, or there may be a UART with a large FIFO buffer. This can be simulated
|
|
* by increasing the MIDI buffer (MIDI_STACK_SIZE).
|
|
*
|
|
* To-Do List
|
|
* ----------
|
|
* - Wrap up into an object. Remove any unused #ifdef pathways.
|
|
*/
|
|
|
|
|
|
/*
|
|
SEGA Custom Sound Processor (SCSP) Emulation
|
|
by ElSemi.
|
|
Driven by MC68000
|
|
*/
|
|
|
|
/* MAME SCSP conversion by Paul Prosser (aka Conversus W. Vans). Code by R.Belmont and ElSemi.
|
|
Fixes made: buggy timing, better envelope processing, better FM support, a more reasonable DSP emulation and all kinds of improvements.
|
|
But the new core isn't without its to-do list:
|
|
- Fix low sound volume when changing music in Harley-Davidson & LA Riders (Maybe the MVOL code from MAME will fix this?)
|
|
- Prevent music in Fighting Vipers 2 Bahn's stage from hanging with MAME's SCSP DSP core.
|
|
- Figure out why Sega Rally 2 navigator voice can cut off sometimes (Legacy DSP)
|
|
|
|
Since I (Paul) started this code overhaul in November 2019, I have also removed some obsolete features like the REVERB software effect.
|
|
It doesn't sound good at all.
|
|
|
|
Anyways credit to R. Belmont and ElSemi for the code, and for being awesome emulation Gods.
|
|
*/
|
|
|
|
#include "SCSP.h"
|
|
|
|
#include "Supermodel.h"
|
|
#include "SCSPDSP.h"
|
|
#include "OSD/Thread.h"
|
|
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <cmath>
|
|
|
|
|
|
static const Util::Config::Node *s_config = 0;
|
|
static bool s_multiThreaded = false;
|
|
bool legacySound; // For LegacySound (SCSP DSP) config option.
|
|
|
|
#define USEDSP
|
|
//#define RB_VOLUME
|
|
|
|
#define MAX_SCSP 2
|
|
|
|
|
|
// These globals control the operation of the SCSP, they are no longer extern and are set through SCSP_SetBuffers(). --Bart
|
|
float SoundClock; // Originally titled SysFPS; seems to be for the sound CPU.
|
|
const float Freq = 76;
|
|
signed short* bufferfl;
|
|
signed short* bufferfr;
|
|
signed short* bufferrl;
|
|
signed short* bufferrr;
|
|
int length;
|
|
int cnts;
|
|
|
|
signed int* buffertmpfl, * buffertmpfr; // these are allocated inside this file
|
|
signed int* buffertmprl, * buffertmprr; // these are allocated inside this file
|
|
|
|
unsigned int srate=44100;
|
|
|
|
|
|
#define ICLIP16(x) (x<-32768)?-32768:((x>32767)?32767:x)
|
|
#define ICLIP18(x) (x<-131072)?-131072:((x>131071)?131071:x)
|
|
|
|
|
|
|
|
|
|
//#define _DEBUG
|
|
|
|
#ifndef _DEBUG
|
|
#define ErrorLogMessage
|
|
#endif
|
|
|
|
#ifndef BYTE
|
|
#define BYTE UINT8
|
|
#endif
|
|
|
|
#ifndef WORD
|
|
#define WORD UINT16
|
|
#endif
|
|
|
|
#ifndef DWORD
|
|
#define DWORD UINT32
|
|
#endif
|
|
|
|
static CMutex *MIDILock; // for safe access to the MIDI FIFOs
|
|
static int (*Run68kCB)(int cycles);
|
|
static void (*Int68kCB)(int irq);
|
|
static void (*RetIntCB)();
|
|
static DWORD IrqTimA;
|
|
static DWORD IrqTimBC;
|
|
static DWORD IrqMidi;
|
|
|
|
unsigned short MCIEB;
|
|
unsigned short MCIPD;
|
|
|
|
#define MIDI_STACK_SIZE 0x100
|
|
#define MIDI_STACK_SIZE_MASK (MIDI_STACK_SIZE-1)
|
|
|
|
static BYTE MidiOutStack[16];
|
|
static BYTE MidiOutW=0,MidiOutR=0;
|
|
static BYTE MidiStack[MIDI_STACK_SIZE];
|
|
static BYTE MidiOutFill;
|
|
static BYTE MidiInFill;
|
|
static BYTE MidiW=0,MidiR=0;
|
|
static BYTE HasSlaveSCSP=0;
|
|
|
|
static DWORD FNS_Table[0x400];
|
|
static INT32 EG_TABLE[0x400];
|
|
|
|
#ifdef RB_VOLUME
|
|
static int volume[256 * 4]; // precalculated attenuation values with some marging for enveloppe and pan levels
|
|
static int pan_left[32], pan_right[32]; // pan volume offsets
|
|
#else
|
|
static const float SDLT[8] = { -1000000.0f,-36.0f,-30.0f,-24.0f,-18.0f,-12.0f,-6.0f,0.0f };
|
|
static int LPANTABLE[0x10000];
|
|
static int RPANTABLE[0x10000];
|
|
#endif
|
|
|
|
|
|
static int TimPris[3];
|
|
static int TimCnt[3];
|
|
|
|
#define SHIFT 12
|
|
#define FIX(v) ((UINT32) ((float) (1<<SHIFT)*(v)))
|
|
|
|
#define EG_SHIFT 16
|
|
#define FM_DELAY 0
|
|
|
|
|
|
#include "SCSPLFO.cpp"
|
|
|
|
/*
|
|
SCSP features 32 programmable slots
|
|
that can generate FM and PCM (from ROM/RAM) sound
|
|
*/
|
|
//SLOT PARAMETERS
|
|
#define KEYONEX(slot) ((slot->data[0x0] >> 0x0) & 0x1000)
|
|
#define KEYONB(slot) ((slot->data[0x0] >> 0x0) & 0x0800)
|
|
#define SBCTL(slot) ((slot->data[0x0] >> 0x9) & 0x0003)
|
|
#define SSCTL(slot) ((slot->data[0x0] >> 0x7) & 0x0003)
|
|
#define LPCTL(slot) ((slot->data[0x0] >> 0x5) & 0x0003)
|
|
#define PCM8B(slot) ((slot->data[0x0] >> 0x0) & 0x0010)
|
|
|
|
#define SA(slot) (((slot->data[0x0] & 0xF) << 16) | (slot->data[0x1]))
|
|
|
|
#define LSA(slot) (slot->data[0x2])
|
|
|
|
#define LEA(slot) (slot->data[0x3])
|
|
|
|
#define D2R(slot) ((slot->data[0x4] >> 0xB) & 0x001F)
|
|
#define D1R(slot) ((slot->data[0x4] >> 0x6) & 0x001F)
|
|
#define EGHOLD(slot) ((slot->data[0x4] >> 0x0) & 0x0020)
|
|
#define AR(slot) ((slot->data[0x4] >> 0x0) & 0x001F)
|
|
|
|
#define LPSLNK(slot) ((slot->data[0x5] >> 0x0) & 0x4000)
|
|
#define KRS(slot) ((slot->data[0x5] >> 0xA) & 0x000F)
|
|
#define DL(slot) ((slot->data[0x5] >> 0x5) & 0x001F)
|
|
#define RR(slot) ((slot->data[0x5] >> 0x0) & 0x001F)
|
|
|
|
#define STWINH(slot) ((slot->data[0x6] >> 0x0) & 0x0200)
|
|
#define SDIR(slot) ((slot->data[0x6] >> 0x0) & 0x0100)
|
|
#define TL(slot) ((slot->data[0x6] >> 0x0) & 0x00FF)
|
|
|
|
#define MDL(slot) ((slot->data[0x7] >> 0xC) & 0x001E) // A value of 30 is somehow needed for correct FM mix in VF3.
|
|
#define MDXSL(slot) ((slot->data[0x7] >> 0x6) & 0x003F)
|
|
#define MDYSL(slot) ((slot->data[0x7] >> 0x0) & 0x003F)
|
|
|
|
#define OCT(slot) ((slot->data[0x8] >> 0xB) & 0x000F)
|
|
#define FNS(slot) ((slot->data[0x8] >> 0x0) & 0x03FF)
|
|
|
|
#define LFORE(slot) ((slot->data[0x9] >> 0x0) & 0x8000)
|
|
#define LFOF(slot) ((slot->data[0x9] >> 0xA) & 0x001F)
|
|
#define PLFOWS(slot) ((slot->data[0x9] >> 0x8) & 0x0003)
|
|
#define PLFOS(slot) ((slot->data[0x9] >> 0x5) & 0x000E) // Setting this to 14 seems to make FM more precise
|
|
#define ALFOWS(slot) ((slot->data[0x9] >> 0x3) & 0x0003)
|
|
#define ALFOS(slot) ((slot->data[0x9] >> 0x0) & 0x0007)
|
|
|
|
#define ISEL(slot) ((slot->data[0xA] >> 0x3) & 0x000F)
|
|
#define IMXL(slot) ((slot->data[0xA] >> 0x0) & 0x0007)
|
|
|
|
#define DISDL(slot) ((slot->data[0xB] >> 0xD) & 0x0007)
|
|
#define DIPAN(slot) ((slot->data[0xB] >> 0x8) & 0x001F)
|
|
#define EFSDL(slot) ((slot->data[0xB] >> 0x5) & 0x0007)
|
|
#define EFPAN(slot) ((slot->data[0xB] >> 0x0) & 0x001F)
|
|
|
|
|
|
//Envelope step (fixed point)
|
|
int ARTABLE[64],DRTABLE[64];
|
|
|
|
//Envelope times in ms
|
|
double ARTimes[64]={100000/*infinity*/,100000/*infinity*/,8100.0,6900.0,6000.0,4800.0,4000.0,3400.0,3000.0,2400.0,2000.0,1700.0,1500.0,
|
|
1200.0,1000.0,860.0,760.0,600.0,500.0,430.0,380.0,300.0,250.0,220.0,190.0,150.0,130.0,110.0,95.0,
|
|
76.0,63.0,55.0,47.0,38.0,31.0,27.0,24.0,19.0,15.0,13.0,12.0,9.4,7.9,6.8,6.0,4.7,3.8,3.4,3.0,2.4,
|
|
2.0,1.8,1.6,1.3,1.1,0.93,0.85,0.65,0.53,0.44,0.40,0.35,0.0,0.0};
|
|
double DRTimes[64]={100000/*infinity*/,100000/*infinity*/,118200.0,101300.0,88600.0,70900.0,59100.0,50700.0,44300.0,35500.0,29600.0,25300.0,22200.0,17700.0,
|
|
14800.0,12700.0,11100.0,8900.0,7400.0,6300.0,5500.0,4400.0,3700.0,3200.0,2800.0,2200.0,1800.0,1600.0,1400.0,1100.0,
|
|
920.0,790.0,690.0,550.0,460.0,390.0,340.0,270.0,230.0,200.0,170.0,140.0,110.0,98.0,85.0,68.0,57.0,49.0,43.0,34.0,
|
|
28.0,25.0,22.0,18.0,14.0,12.0,11.0,8.5,7.1,6.1,5.4,4.3,3.6,3.1};
|
|
|
|
|
|
typedef enum {ATTACK,DECAY1,DECAY2,RELEASE} _STATE;
|
|
struct _EG
|
|
{
|
|
int volume; //
|
|
_STATE state;
|
|
int step;
|
|
//step vals
|
|
int AR; //Attack
|
|
int D1R; //Decay1
|
|
int D2R; //Decay2
|
|
int RR; //Release
|
|
|
|
int DL; //Decay level
|
|
BYTE EGHOLD;
|
|
BYTE LPLINK;
|
|
};
|
|
|
|
struct _SLOT
|
|
{
|
|
union
|
|
{
|
|
WORD data[0x10]; //only 0x1a bytes used
|
|
BYTE datab[0x20];
|
|
};
|
|
BYTE active; //this slot is currently playing
|
|
BYTE *base; //samples base address
|
|
DWORD cur_addr;
|
|
DWORD nxt_addr; //current play address (24.8)
|
|
DWORD step; //pitch step (24.8)
|
|
BYTE Back;
|
|
_EG EG; //Envelope
|
|
_LFO PLFO; //Phase LFO
|
|
_LFO ALFO; //Amplitude LFO
|
|
int slot;
|
|
signed short Prev; //Previous sample (for interpolation)
|
|
};
|
|
|
|
#define MEM4B(scsp) ((scsp->data[0]>>0x0)&0x0200)
|
|
#define DAC18B(scsp) ((scsp->data[0]>>0x0)&0x0100)
|
|
#define MVOL(scsp) ((scsp->data[0]>>0x0)&0x000F)
|
|
#define RBL(scsp) ((scsp->data[1]>>0x7)&0x0003)
|
|
#define RBP(scsp) ((scsp->data[1]>>0x0)&0x007F)
|
|
#define MOFULL(scsp) ((scsp->data[2]>>0x0)&0x1000)
|
|
#define MOEMPTY(scsp) ((scsp->data[2]>>0x0)&0x0800)
|
|
#define MIOVF(scsp) ((scsp->data[2]>>0x0)&0x0400)
|
|
#define MIFULL(scsp) ((scsp->data[2]>>0x0)&0x0200)
|
|
#define MIEMPTY(scsp) ((scsp->data[2]>>0x0)&0x0100)
|
|
|
|
#define SCILV0(scsp) ((scsp->data[0x24/2]>>0x0)&0xff)
|
|
#define SCILV1(scsp) ((scsp->data[0x26/2]>>0x0)&0xff)
|
|
#define SCILV2(scsp) ((scsp->data[0x28/2]>>0x0)&0xff)
|
|
|
|
#define SCIEX0 0
|
|
#define SCIEX1 1
|
|
#define SCIEX2 2
|
|
#define SCIMID 3
|
|
#define SCIDMA 4
|
|
#define SCIIRQ 5
|
|
#define SCITMA 6
|
|
#define SCITMB 7
|
|
|
|
bool HasMVOL;
|
|
|
|
struct _SCSP
|
|
{
|
|
union
|
|
{
|
|
WORD data[0x30/2];
|
|
BYTE datab[0x30];
|
|
};
|
|
_SLOT Slots[32];
|
|
signed short RINGBUF[64];
|
|
unsigned char BUFPTR;
|
|
#if FM_DELAY
|
|
signed short DELAYBUF[FM_DELAY];
|
|
BYTE DELAYPTR;
|
|
#endif
|
|
unsigned char *SCSPRAM;
|
|
UINT32 SCSPRAM_LENGTH;
|
|
char Master;
|
|
#ifdef USEDSP
|
|
_SCSPDSP DSP;
|
|
signed short *MIXBuf;
|
|
#endif
|
|
|
|
int ARTABLE[64], DRTABLE[64];
|
|
} SCSPs[MAX_SCSP],*SCSP=SCSPs;
|
|
|
|
static signed short *RBUFDST; //this points to where the sample will be stored in the RingBuf
|
|
|
|
|
|
unsigned char DecodeSCI(unsigned char irq)
|
|
{
|
|
unsigned char SCI=0;
|
|
unsigned char v;
|
|
v=(SCILV0((SCSP))&(1<<irq))?1:0;
|
|
SCI|=v;
|
|
v=(SCILV1((SCSP))&(1<<irq))?1:0;
|
|
SCI|=v<<1;
|
|
v=(SCILV2((SCSP))&(1<<irq))?1:0;
|
|
SCI|=v<<2;
|
|
return SCI;
|
|
}
|
|
|
|
|
|
|
|
void CheckPendingIRQ()
|
|
{
|
|
DWORD pend=SCSPs->data[0x20/2];
|
|
DWORD en=SCSPs->data[0x1e/2];
|
|
|
|
/*
|
|
* MIDI FIFO critical section
|
|
*
|
|
* NOTE: I don't think a mutex is really needed here, so I've disabled
|
|
* this critical section.
|
|
*/
|
|
//if (g_Config.multiThreaded)
|
|
// MIDILock->Lock();
|
|
|
|
if(MidiW!=MidiR)
|
|
{
|
|
//if (g_Config.multiThreaded)
|
|
// MIDILock->Unlock();
|
|
|
|
//SCSP.data[0x20/2]|=0x8; //Hold midi line while there are commands pending
|
|
|
|
//Int68kCB(IrqMidi);
|
|
//printf("68K: MIDI IRQ\n");
|
|
//ErrorLogMessage("Midi");
|
|
|
|
SCSP->data[0x20 / 2] |= 8;
|
|
pend |= 8;
|
|
}
|
|
|
|
//if (g_Config.multiThreaded)
|
|
// MIDILock->Unlock();
|
|
|
|
if(!pend)
|
|
return;
|
|
if(pend&0x40)
|
|
if(en&0x40)
|
|
{
|
|
Int68kCB(IrqTimA);
|
|
//ErrorLogMessage("TimA");
|
|
return;
|
|
}
|
|
if(pend&0x80)
|
|
if(en&0x80)
|
|
{
|
|
Int68kCB(IrqTimBC);
|
|
//ErrorLogMessage("TimB");
|
|
return;
|
|
}
|
|
if(pend&0x100)
|
|
if(en&0x100)
|
|
{
|
|
Int68kCB(IrqTimBC);
|
|
//ErrorLogMessage("TimC");
|
|
return;
|
|
}
|
|
if(pend&0x8)
|
|
if(en&0x8)
|
|
{
|
|
Int68kCB(IrqMidi);
|
|
SCSP->data[0x20 / 2] &= ~8;
|
|
return;
|
|
}
|
|
|
|
Int68kCB(0);
|
|
}
|
|
|
|
//void ResetInterrupts() // Can't get this to work correctly in Supermodel.
|
|
//{
|
|
// unsigned int reset = SCSP->data[0x22 / 2];
|
|
//
|
|
// if (reset & 0x40)
|
|
// {
|
|
// Int68kCB(IrqTimA);
|
|
// }
|
|
// if (reset & 0x180)
|
|
// {
|
|
// Int68kCB(IrqTimBC);
|
|
// }
|
|
// if (reset & 0x8)
|
|
// {
|
|
// Int68kCB(IrqMidi);
|
|
// }
|
|
//
|
|
// CheckPendingIRQ();
|
|
//}
|
|
|
|
|
|
int Get_AR(int base,int R)
|
|
{
|
|
int Rate=base+(R<<1);
|
|
// int Rate=(base+R)<<1;
|
|
if(Rate>63) Rate=63;
|
|
if(Rate<0) Rate=0;
|
|
return ARTABLE[Rate];
|
|
}
|
|
|
|
int Get_DR(int base,int R)
|
|
{
|
|
int Rate=base+(R<<1);
|
|
// int Rate=(base+R)<<1;
|
|
if(Rate>63) Rate=63;
|
|
if(Rate<0) Rate=0;
|
|
return DRTABLE[Rate];
|
|
}
|
|
|
|
|
|
void Compute_EG(_SLOT *slot)
|
|
{
|
|
int octave = (OCT(slot) ^ 8) - 8;
|
|
int rate;
|
|
if (KRS(slot) != 0xf)
|
|
rate = octave + 2 * KRS(slot) + ((FNS(slot) >> 9) & 1);
|
|
else
|
|
rate = 0; //rate = ((FNS(slot) >> 9) & 1);
|
|
|
|
slot->EG.volume = 0x17F << EG_SHIFT;
|
|
slot->EG.AR = Get_AR(rate, AR(slot));
|
|
slot->EG.D1R = Get_DR(rate, D1R(slot));
|
|
slot->EG.D2R = Get_DR(rate, D2R(slot));
|
|
slot->EG.RR = Get_DR(rate, RR(slot));
|
|
slot->EG.DL = 0x1f - DL(slot);
|
|
slot->EG.EGHOLD = EGHOLD(slot);
|
|
}
|
|
|
|
void SCSP_StopSlot(_SLOT *slot,int keyoff);
|
|
|
|
int EG_Update(_SLOT *slot)
|
|
{
|
|
switch (slot->EG.state)
|
|
{
|
|
case ATTACK:
|
|
slot->EG.volume += slot->EG.AR;
|
|
if (slot->EG.volume >= (0x3ff << EG_SHIFT))
|
|
{
|
|
if (!LPSLNK(slot))
|
|
{
|
|
slot->EG.state = DECAY1;
|
|
if (slot->EG.D1R >= (1024 << EG_SHIFT)) //Skip SCSP_DECAY1, go directly to SCSP_DECAY2
|
|
slot->EG.state = DECAY2;
|
|
}
|
|
slot->EG.volume = 0x3ff << EG_SHIFT;
|
|
}
|
|
if (slot->EG.EGHOLD)
|
|
return 0x3ff << (SHIFT - 10);
|
|
break;
|
|
case DECAY1:
|
|
slot->EG.volume -= slot->EG.D1R;
|
|
if (slot->EG.volume <= 0)
|
|
slot->EG.volume = 0;
|
|
if (slot->EG.volume >> (EG_SHIFT + 5) <= slot->EG.DL)
|
|
slot->EG.state = DECAY2;
|
|
break;
|
|
case DECAY2:
|
|
if (D2R(slot) == 0)
|
|
return (slot->EG.volume >> EG_SHIFT) << (SHIFT - 10);
|
|
slot->EG.volume -= slot->EG.D2R;
|
|
if (slot->EG.volume <= 0)
|
|
slot->EG.volume = 0;
|
|
|
|
break;
|
|
case RELEASE:
|
|
slot->EG.volume -= slot->EG.RR;
|
|
if (slot->EG.volume <= 0)
|
|
{
|
|
slot->EG.volume = 0;
|
|
SCSP_StopSlot(slot, 0);
|
|
//slot->EG.volume = 0x17F << EG_SHIFT;
|
|
//slot->EG.state = SCSP_ATTACK;
|
|
}
|
|
break;
|
|
default:
|
|
return 1 << SHIFT;
|
|
}
|
|
return (slot->EG.volume >> EG_SHIFT) << (SHIFT - 10);
|
|
}
|
|
|
|
|
|
DWORD SCSP_Step(_SLOT *slot)
|
|
{
|
|
//int octave=OCT(slot);
|
|
//UINT64 Fn;
|
|
//Fn=(FNS_Table[FNS(slot)]); //24.8
|
|
//if(octave&8)
|
|
// Fn>>=(16-octave);
|
|
//else
|
|
// Fn<<=octave;
|
|
|
|
|
|
//return Fn/srate;
|
|
int octave = (OCT(slot) ^ 8) - 8 + SHIFT - 10;
|
|
UINT32 Fn = FNS(slot) + (1 << 10);
|
|
if (octave >= 0)
|
|
{
|
|
Fn <<= octave;
|
|
}
|
|
else
|
|
{
|
|
Fn >>= -octave;
|
|
}
|
|
|
|
return Fn;
|
|
}
|
|
|
|
void Compute_LFO(_SLOT *slot)
|
|
{
|
|
if(PLFOS(slot)!=0)
|
|
LFO_ComputeStep(&(slot->PLFO),LFOF(slot),PLFOWS(slot),PLFOS(slot),0);
|
|
if(ALFOS(slot)!=0)
|
|
LFO_ComputeStep(&(slot->ALFO),LFOF(slot),ALFOWS(slot),ALFOS(slot),1);
|
|
}
|
|
|
|
|
|
void SCSP_StartSlot(_SLOT *slot)
|
|
{
|
|
UINT32 start_offset;
|
|
|
|
slot->active = 1;
|
|
slot->Back = 0;
|
|
slot->nxt_addr = 1 << SHIFT;
|
|
slot->cur_addr = 0;
|
|
start_offset = PCM8B(slot) ? SA(slot) : SA(slot) & 0x7FFFE;
|
|
slot->step = SCSP_Step(slot);
|
|
slot->base = SCSP->SCSPRAM + start_offset;
|
|
Compute_EG(slot);
|
|
slot->EG.state = ATTACK;
|
|
slot->EG.volume = 0x17F << EG_SHIFT;
|
|
slot->Prev = 0;
|
|
Compute_LFO(slot);
|
|
/*{
|
|
char aux[12];
|
|
static n=0;
|
|
sprintf(aux,"smp%d.raw",n);
|
|
FILE *f=fopen(aux,"wb");
|
|
fwrite(slot->base,LEA(slot),1,f);
|
|
fclose(f);
|
|
++n;
|
|
}
|
|
*/
|
|
|
|
}
|
|
|
|
void SCSP_StopSlot(_SLOT *slot,int keyoff)
|
|
{
|
|
if(keyoff)
|
|
{
|
|
slot->EG.state=RELEASE;
|
|
// return;
|
|
}
|
|
else
|
|
slot->active=0;
|
|
slot->data[0]&=~0x800;
|
|
//DebugLog("KEYOFF2 %d",slot->slot);
|
|
}
|
|
|
|
#define log2(n) (log((float) n)/log((float) 2))
|
|
|
|
bool SCSP_Init(const Util::Config::Node &config, int n)
|
|
{
|
|
s_config = &config;
|
|
s_multiThreaded = config["MultiThreaded"].ValueAs<bool>();
|
|
legacySound = config["LegacySoundDSP"].ValueAs<bool>();
|
|
SoundClock = Freq;
|
|
|
|
if(n==2)
|
|
{
|
|
SCSP=SCSPs+1;
|
|
memset(SCSP,0,sizeof(_SCSP));
|
|
SCSP->Master=0;
|
|
HasSlaveSCSP=1;
|
|
#ifdef USEDSP
|
|
SCSPDSP_Init(&SCSP->DSP);
|
|
#endif
|
|
|
|
}
|
|
SCSP=SCSPs+0;
|
|
memset(SCSP,0,sizeof(_SCSP));
|
|
#ifdef USEDSP
|
|
SCSPDSP_Init(&SCSP->DSP);
|
|
#endif
|
|
SCSP->Master=1;
|
|
SCSP->SCSPRAM_LENGTH = 512 * 1024;
|
|
SCSP->DSP.SCSPRAM = (UINT16 *)SCSP->SCSPRAM;
|
|
SCSP->DSP.SCSPRAM_LENGTH = (512 * 1024) / 2;
|
|
MidiR=MidiW=0;
|
|
MidiOutR=MidiOutW=0;
|
|
MidiOutFill=0;
|
|
MidiInFill=0;
|
|
|
|
|
|
for(int i=0;i<0x400;++i)
|
|
{
|
|
double fcent=(double) 1200.0*log2((double)(((double) 1024.0+(double)i)/(double)1024.0));
|
|
//float fcent=1.0+(float) i/1024.0;
|
|
fcent=(double) 44100.0*pow(2.0,fcent/1200.0);
|
|
FNS_Table[i]=(UINT32)((float) (1<<SHIFT) *fcent);
|
|
//FNS_Table[i]=(i>>(10-SHIFT))|(1<<SHIFT);
|
|
|
|
}
|
|
for (int i = 0; i < 0x400; ++i) {
|
|
float envDB = ((float)(3 * (i - 0x3ff))) / 32.0f;
|
|
float scale = (float)(1 << SHIFT);
|
|
EG_TABLE[i] = (INT32)(pow(10.0, envDB / 20.0)*scale);
|
|
}
|
|
|
|
#ifdef RB_VOLUME
|
|
// Volume table, 1 = -0.375dB, 8 = -3dB, 256 = -96dB
|
|
for (i = 0; i < 256; i++)
|
|
volume[i] = 65536.0*pow(2.0, (-0.375 / 6.0)*i);
|
|
for (i = 256; i < 256 * 4; i++)
|
|
volume[i] = 0;
|
|
|
|
// Pan values, units are a linear -3dB ramp, i.e. 8 places in the volume[] table.
|
|
for (i = 0; i < 16; i++)
|
|
{
|
|
pan_left[i] = i * 8;
|
|
pan_left[i + 16] = 0;
|
|
pan_right[i] = 0;
|
|
pan_right[i + 16] = i * 8;
|
|
}
|
|
// patch in the infinity values
|
|
pan_left[15] = 256;
|
|
pan_right[31] = 256;
|
|
|
|
#else
|
|
|
|
for(int i=0;i<0x10000;++i)
|
|
{
|
|
int iTL =(i>>0x0)&0xff;
|
|
int iPAN=(i>>0x8)&0x1f;
|
|
int iSDL=(i>>0xd)&0x07;
|
|
|
|
float TL=1.0;
|
|
float SegaDB=0;
|
|
if(iTL&0x01) SegaDB-=0.4f;
|
|
if(iTL&0x02) SegaDB-=0.8f;
|
|
if(iTL&0x04) SegaDB-=1.5f;
|
|
if(iTL&0x08) SegaDB-=3.0f;
|
|
if(iTL&0x10) SegaDB-=6.0f;
|
|
if(iTL&0x20) SegaDB-=12.0f;
|
|
if(iTL&0x40) SegaDB-=24.0f;
|
|
if(iTL&0x80) SegaDB-=48.0f;
|
|
|
|
TL=powf(10.0f,SegaDB/20.0f);
|
|
|
|
float PAN=1.0;
|
|
|
|
SegaDB=0;
|
|
if(iPAN&0x1) SegaDB-=3.0f;
|
|
if(iPAN&0x2) SegaDB-=6.0f;
|
|
if(iPAN&0x4) SegaDB-=12.0f;
|
|
if(iPAN&0x8) SegaDB-=24.0f;
|
|
|
|
if(iPAN==0xf) PAN=0.0;
|
|
else if(iPAN==0x1f) PAN=0.0;
|
|
else PAN=powf(10.0f,SegaDB/20.0f);
|
|
|
|
float LPAN,RPAN;
|
|
|
|
if(iPAN<0x10)
|
|
{
|
|
LPAN=PAN;
|
|
RPAN=1.0;
|
|
}
|
|
else
|
|
{
|
|
RPAN=PAN;
|
|
LPAN=1.0;
|
|
}
|
|
|
|
float SDL=1.0;
|
|
if(iSDL)
|
|
SDL=powf(10.0f,(SDLT[iSDL])/20.0f);
|
|
else
|
|
SDL=0.0;
|
|
|
|
if(iSDL==0x6)
|
|
int a=1;
|
|
if(iTL==0x3a)
|
|
int a=1;
|
|
|
|
LPANTABLE[i]=FIX((4.0*LPAN*TL*SDL));
|
|
RPANTABLE[i]=FIX((4.0*RPAN*TL*SDL));
|
|
}
|
|
#endif
|
|
ARTABLE[0]=DRTABLE[0]=0; //Infinite time
|
|
ARTABLE[1]=DRTABLE[1]=0;
|
|
for(int i=2;i<64;++i)
|
|
{
|
|
double t,step,scale;
|
|
t=ARTimes[i]; //In ms
|
|
if(t!=0.0)
|
|
{
|
|
step=(1023*1000.0)/((float) srate*t);
|
|
scale=(double) (1<<EG_SHIFT);
|
|
ARTABLE[i]=(int) (step*scale);
|
|
}
|
|
else
|
|
ARTABLE[i]=1024<<EG_SHIFT;
|
|
|
|
t=DRTimes[i]; //In ms
|
|
step=(1023*1000.0)/((float) srate*t);
|
|
scale=(double) (1<<EG_SHIFT);
|
|
DRTABLE[i]=(int) (step*scale);
|
|
}
|
|
|
|
for(int i=0;i<32;++i)
|
|
SCSPs[0].Slots[i].slot=i;
|
|
|
|
#ifdef USEDSP
|
|
//allocate 0x300 (over 1 frame) * 32 slots * 16 bit
|
|
SCSP->MIXBuf=(signed short *) malloc(0x300*32*sizeof(signed short));
|
|
#endif
|
|
|
|
LFO_Init();
|
|
buffertmpfl = NULL;
|
|
buffertmpfr = NULL;
|
|
buffertmprl = NULL;
|
|
buffertmprr = NULL;
|
|
buffertmpfl=(signed int*) malloc(44100*sizeof(signed int));
|
|
if (NULL == buffertmpfl)
|
|
return ErrorLog("Insufficient memory for internal SCSP buffers.");
|
|
buffertmpfr=(signed int*) malloc(44100*sizeof(signed int));
|
|
if (NULL == buffertmpfr)
|
|
{
|
|
free(buffertmpfl);
|
|
return ErrorLog("Insufficient memory for internal SCSP buffers.");
|
|
}
|
|
|
|
buffertmprl=(signed int*)malloc(44100*sizeof(signed int));
|
|
if (NULL == buffertmprl)
|
|
return ErrorLog("Insufficient memory for internal SCSP buffers.");
|
|
buffertmprr=(signed int*)malloc(44100*sizeof(signed int));
|
|
if (NULL == buffertmprr)
|
|
{
|
|
free(buffertmprl);
|
|
return ErrorLog("Insufficient memory for internal SCSP buffers.");
|
|
}
|
|
|
|
memset(buffertmpfl, 0, 44100*sizeof(signed int));
|
|
memset(buffertmpfr, 0, 44100*sizeof(signed int));
|
|
memset(buffertmprl, 0, 44100*sizeof(signed int));
|
|
memset(buffertmprr, 0, 44100*sizeof(signed int));
|
|
SCSPs->data[0x20 / 2] = 0;
|
|
TimCnt[0] = 0xffff;
|
|
TimCnt[1] = 0xffff;
|
|
TimCnt[2] = 0xffff;
|
|
|
|
// MIDI FIFO mutex
|
|
MIDILock = CThread::CreateMutex();
|
|
if (NULL == MIDILock)
|
|
{
|
|
free(buffertmpfl);
|
|
free(buffertmpfr);
|
|
free(buffertmprl);
|
|
free(buffertmprr);
|
|
return ErrorLog("Unable to create MIDI mutex!");
|
|
}
|
|
|
|
return OKAY;
|
|
}
|
|
|
|
void SCSP_SetRAM(int n,unsigned char *r)
|
|
{
|
|
SCSPs[n].SCSPRAM=r;
|
|
#ifdef USEDSP
|
|
SCSPs[n].DSP.SCSPRAM=(unsigned short*) r;
|
|
#endif
|
|
}
|
|
|
|
void SCSP_UpdateSlotReg(int s,int r)
|
|
{
|
|
struct _SLOT *slot = SCSP->Slots + s;
|
|
int sl;
|
|
switch (r & 0x3f)
|
|
{
|
|
case 0:
|
|
case 1:
|
|
if (KEYONEX(slot))
|
|
{
|
|
for (sl = 0; sl < 32; ++sl)
|
|
{
|
|
struct _SLOT *s2 = SCSP->Slots + sl;
|
|
{
|
|
if (KEYONB(s2) && s2->EG.state == RELEASE/*&& !s2->active*/)
|
|
{
|
|
SCSP_StartSlot(s2);
|
|
}
|
|
if (!KEYONB(s2) /*&& s2->active*/)
|
|
{
|
|
SCSP_StopSlot(s2, 1);
|
|
}
|
|
}
|
|
}
|
|
slot->data[0] &= ~0x1000;
|
|
}
|
|
break;
|
|
case 0x10:
|
|
case 0x11:
|
|
slot->step = SCSP_Step(slot);
|
|
break;
|
|
case 0xA:
|
|
case 0xB:
|
|
slot->EG.RR = Get_DR(0, RR(slot));
|
|
slot->EG.DL = 0x1f - DL(slot);
|
|
break;
|
|
case 0x12:
|
|
case 0x13:
|
|
Compute_LFO(slot);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void SCSP_UpdateReg(int reg)
|
|
{
|
|
switch(reg&0x3f)
|
|
{
|
|
case 0x0: // Need to get this working in Supermodel as well
|
|
//m_stream->set_output_gain(0, MVOL() / 15.0);
|
|
//m_stream->set_output_gain(1, MVOL() / 15.0);
|
|
//break;
|
|
case 0x2:
|
|
case 0x3:
|
|
{
|
|
#ifdef USEDSP
|
|
{
|
|
SCSP->DSP.RBL = (8 * 1024) << RBL(SCSP); // 8 / 16 / 32 / 64 kwords
|
|
SCSP->DSP.RBP = RBP(SCSP);
|
|
}
|
|
#endif
|
|
}
|
|
break;
|
|
|
|
case 0x6:
|
|
case 0x7:
|
|
SCSP_MidiOutW(SCSP->data[0x6/2]&0xff);
|
|
break;
|
|
|
|
case 8:
|
|
case 9:
|
|
SCSP->data[0x8 / 2] &= 0xf800;
|
|
break;
|
|
case 0x12:
|
|
case 0x13:
|
|
case 0x14:
|
|
case 0x15:
|
|
case 0x16:
|
|
case 0x17:
|
|
{
|
|
int a=1;
|
|
}
|
|
break;
|
|
case 0x18:
|
|
case 0x19:
|
|
if(SCSP->Master)
|
|
{
|
|
TimPris[0]=1<<((SCSPs->data[0x18/2]>>8)&0x7);
|
|
TimCnt[0]=((SCSPs->data[0x18/2]&0xfe)<<8)/*|(TimCnt[0]&0xff)*/;
|
|
}
|
|
break;
|
|
case 0x1a:
|
|
case 0x1b:
|
|
if(SCSP->Master)
|
|
{
|
|
TimPris[1]=1<<((SCSPs->data[0x1A/2]>>8)&0x7);
|
|
TimCnt[1]=((SCSPs->data[0x1A/2]&0xfe)<<8)/*|(TimCnt[1]&0xff)*/;
|
|
}
|
|
break;
|
|
case 0x1C:
|
|
case 0x1D:
|
|
if(SCSP->Master)
|
|
{
|
|
TimPris[2]=1<<((SCSPs->data[0x1C/2]>>8)&0x7);
|
|
TimCnt[2]=((SCSPs->data[0x1C/2]&0xfe)<<8)/*|(TimCnt[2]&0xff)*/;
|
|
}
|
|
break;
|
|
case 0x22: //SCIRE
|
|
case 0x23:
|
|
if(SCSP->Master)
|
|
{
|
|
SCSP->data[0x20 / 2] &= ~SCSP->data[0x22 / 2];
|
|
//ResetInterrupts();
|
|
|
|
|
|
if (TimCnt[0] == 0xffff)
|
|
{
|
|
SCSP->data[0x20 / 2] |= 0x40;
|
|
}
|
|
if (TimCnt[1] == 0xffff)
|
|
{
|
|
SCSP->data[0x20 / 2] |= 0x80;
|
|
}
|
|
if (TimCnt[2] == 0xffff)
|
|
{
|
|
SCSP->data[0x20 / 2] |= 0x100;
|
|
}
|
|
}
|
|
break;
|
|
case 0x24:
|
|
case 0x25:
|
|
case 0x26:
|
|
case 0x27:
|
|
case 0x28:
|
|
case 0x29:
|
|
if(SCSP->Master)
|
|
{
|
|
IrqTimA=DecodeSCI(SCITMA);
|
|
IrqTimBC=DecodeSCI(SCITMB);
|
|
IrqMidi=DecodeSCI(SCIMID);
|
|
}
|
|
break;
|
|
case 0x2b:
|
|
MCIEB = SCSP->data[0x2a / 2];
|
|
break;
|
|
case 0x2c:
|
|
case 0x2d:
|
|
break;
|
|
case 0x2e:
|
|
case 0x2f:
|
|
MCIPD &= ~SCSP->data[0x2e / 2];
|
|
break;
|
|
}
|
|
}
|
|
|
|
void SCSP_UpdateSlotRegR(int slot,int reg)
|
|
{
|
|
|
|
}
|
|
|
|
void SCSP_UpdateRegR(int reg)
|
|
{
|
|
switch (reg & 0x3f)
|
|
{
|
|
case 4:
|
|
case 5:
|
|
{
|
|
unsigned short v = SCSP->data[0x4 / 2];
|
|
v &= 0xff00;
|
|
|
|
/*
|
|
* MIDI FIFO critical section!
|
|
*/
|
|
if (s_multiThreaded)
|
|
MIDILock->Lock();
|
|
|
|
v |= MidiStack[MidiR];
|
|
//printf("read MIDI\n");
|
|
if (MidiR != MidiW)
|
|
{
|
|
++MidiR;
|
|
MidiR &= MIDI_STACK_SIZE_MASK;
|
|
//Int68kCB(IrqMidi);
|
|
}
|
|
|
|
MidiInFill--;
|
|
SCSP->data[0x4 / 2] = v;
|
|
|
|
if (s_multiThreaded)
|
|
MIDILock->Unlock();
|
|
}
|
|
break;
|
|
case 8:
|
|
case 9:
|
|
{
|
|
// MSLC | CA |SGC|EG
|
|
// f e d c b a 9 8 7 6 5 4 3 2 1 0
|
|
BYTE MSLC = (SCSP->data[0x8 / 2] >> 11) & 0x1f;
|
|
_SLOT *slot = SCSP->Slots + MSLC;
|
|
unsigned int SGC = (slot->EG.state) & 3;
|
|
unsigned int CA = (slot->cur_addr >> (SHIFT + 12)) & 0xf;
|
|
unsigned int EG = (0x1f - (slot->EG.volume >> (EG_SHIFT + 5))) & 0x1f;
|
|
/* note: according to the manual MSLC is write only, CA, SGC and EG read only. */
|
|
SCSP->data[0x8 / 2] = /*(MSLC << 11) |*/ (CA << 7) | (SGC << 5) | EG;
|
|
}
|
|
break;
|
|
case 0x18:
|
|
case 0x19:
|
|
break;
|
|
|
|
case 0x1a:
|
|
case 0x1b:
|
|
break;
|
|
|
|
case 0x1c:
|
|
case 0x1d:
|
|
break;
|
|
|
|
case 0x2a:
|
|
case 0x2b:
|
|
SCSP->data[0x2a / 2] = MCIEB;
|
|
break;
|
|
|
|
case 0x2c:
|
|
case 0x2d:
|
|
SCSP->data[0x2c / 2] = MCIPD;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void SCSP_w8(unsigned int addr,unsigned char val)
|
|
{
|
|
addr&=0xffff;
|
|
if(addr<0x400)
|
|
{
|
|
int slot=addr/0x20;
|
|
addr&=0x1f;
|
|
//DebugLog("Slot %02X Reg %02X write byte %04X\n",slot,addr^1,val);
|
|
//printf("\tSlot %02X Reg %02X write byte %04X\n",slot,addr^1,val);
|
|
*(unsigned char *) &(SCSP->Slots[slot].datab[addr^1]) = val;
|
|
SCSP_UpdateSlotReg(slot,(addr^1)&0x1f);
|
|
}
|
|
else if(addr<0x600)
|
|
{
|
|
*(unsigned char *) &(SCSP->datab[(addr&0xff)^1]) = val;
|
|
SCSP_UpdateReg((addr^1)&0xff);
|
|
}
|
|
else if(addr<0x700)
|
|
SCSP->RINGBUF[(addr-0x600)/2]=val;
|
|
else
|
|
{
|
|
if (legacySound == true) {
|
|
#ifdef USEDSP
|
|
//DSP
|
|
if (addr < 0x780) //COEF
|
|
((unsigned char *)SCSP->DSP.COEF)[(addr - 0x700) ^ 1] = val;
|
|
else if (addr < 0x7C0)
|
|
((unsigned char *)SCSP->DSP.MADRS)[(addr - 0x780) ^ 1] = val;
|
|
else if (addr >= 0x800 && addr < 0xC00)
|
|
((unsigned char *)SCSP->DSP.MPRO)[(addr - 0x800) ^ 1] = val;
|
|
else
|
|
int a = 1;
|
|
if (addr == 0xBF0)
|
|
{
|
|
SCSPDSP_Start(&SCSP->DSP);
|
|
}
|
|
int a = 1;
|
|
#endif
|
|
}
|
|
else {
|
|
#ifdef USEDSP
|
|
//DSP
|
|
if (addr < 0x780) //COEF
|
|
((unsigned char *)SCSP->DSP.COEF)[(addr - 0x700) ^ 1] = val;
|
|
else if (addr < 0x7C0)
|
|
((unsigned char *)SCSP->DSP.MADRS)[(addr - 0x780) ^ 1] = val;
|
|
else if (addr < 0x800)
|
|
((unsigned char *)SCSP->DSP.MADRS)[(addr - 0x7c0) ^ 1] = val;
|
|
else if (addr < 0xC00)
|
|
((unsigned char *)SCSP->DSP.MPRO)[(addr - 0x800) ^ 1] = val;
|
|
else
|
|
int a = 1;
|
|
if (addr == 0xBF0)
|
|
{
|
|
SCSPDSP_Start(&SCSP->DSP);
|
|
}
|
|
int a = 1;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
void SCSP_w16(unsigned int addr,unsigned short val)
|
|
{
|
|
addr&=0xffff;
|
|
if(addr<0x400)
|
|
{
|
|
int slot=addr/0x20;
|
|
addr&=0x1f;
|
|
//DebugLog("Slot %02X Reg %02X write word %04X\n",slot,addr,val);
|
|
//printf("\tSlot %02X Reg %02X write word %04X\n",slot,addr,val);
|
|
*(unsigned short *) &(SCSP->Slots[slot].datab[addr]) = val;
|
|
SCSP_UpdateSlotReg(slot,addr&0x1f);
|
|
}
|
|
else if(addr<0x600)
|
|
{
|
|
/**(unsigned short *) &(SCSP->datab[addr&0xff]) = val;
|
|
SCSP_UpdateReg(addr&0xff);*/
|
|
if (addr < 0x430)
|
|
{
|
|
*((unsigned short *)(SCSP->datab + ((addr & 0x3f)))) = val;
|
|
SCSP_UpdateReg(addr & 0x3f);
|
|
}
|
|
}
|
|
else if (addr < 0x700)
|
|
SCSP->RINGBUF[(addr - 0x600) / 2] = val;
|
|
else
|
|
{
|
|
if (legacySound == true) {
|
|
#ifdef USEDSP
|
|
// ElSemi's legacy DSP. For now we will need this for Fighting Vipers 2.
|
|
if (addr < 0x780) //COEF
|
|
*(unsigned short *) &(SCSP->DSP.COEF[(addr - 0x700) / 2]) = val;
|
|
else if (addr < 0x800)
|
|
*(unsigned short *) &(SCSP->DSP.MADRS[(addr - 0x780) / 2]) = val;
|
|
else if (addr < 0xC00)
|
|
*(unsigned short *) &(SCSP->DSP.MPRO[(addr - 0x800) / 2]) = val;
|
|
else
|
|
int a = 1;
|
|
if (addr == 0xBF0)
|
|
SCSPDSP_Start(&SCSP->DSP);
|
|
int a = 1;
|
|
#endif
|
|
}
|
|
else {
|
|
#ifdef USEDSP
|
|
// MAME DSP
|
|
if (addr < 0x780) //COEF
|
|
*((UINT16 *)(SCSP->DSP.COEF + (addr - 0x700) / 2)) = val;
|
|
else if (addr < 0x7c0)
|
|
*((UINT16 *)(SCSP->DSP.MADRS + (addr - 0x780) / 2)) = val;
|
|
else if (addr < 0x800) // MADRS is mirrored twice
|
|
*((UINT16 *)(SCSP->DSP.MADRS + (addr - 0x7c0) / 2)) = val;
|
|
else if (addr < 0xC00)
|
|
{
|
|
*((UINT16 *)(SCSP->DSP.MPRO + (addr - 0x800) / 2)) = val;
|
|
}
|
|
else
|
|
int a = 1;
|
|
if (addr == 0xBF0)
|
|
SCSPDSP_Start(&SCSP->DSP);
|
|
int a = 1;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
void SCSP_w32(unsigned int addr,unsigned int val)
|
|
{
|
|
addr&=0xffff;
|
|
if(addr<0x400)
|
|
{
|
|
int slot=addr/0x20;
|
|
addr&=0x1f;
|
|
//DebugLog("Slot %02X Reg %02X write dword %08X\n",slot,addr,val);
|
|
//printf("\tSlot %02X Reg %02X write dword %08X\n",slot,addr,val);
|
|
rotl(val, 16);
|
|
|
|
*(unsigned int *) &(SCSP->Slots[slot].datab[addr]) = val;
|
|
SCSP_UpdateSlotReg(slot,addr&0x1f);
|
|
SCSP_UpdateSlotReg(slot,(addr&0x1f)+2);
|
|
}
|
|
else if(addr<0x600)
|
|
{
|
|
rotl(val, 16);
|
|
|
|
*(unsigned int *) &(SCSP->datab[addr&0xff]) = val;
|
|
SCSP_UpdateReg(addr&0xff);
|
|
SCSP_UpdateReg((addr&0xff)+2);
|
|
}
|
|
else if(addr<0x700)
|
|
int a=1;
|
|
else
|
|
{
|
|
#ifdef USEDSP
|
|
//DSP
|
|
rotl(val, 16);
|
|
if(addr<0x780) //COEF
|
|
*(unsigned int *) &(SCSP->DSP.COEF[(addr-0x700)/2])=val;
|
|
else if (addr < 0x7c0)
|
|
*(unsigned int *) &(SCSP->DSP.MADRS[(addr-0x780)/2]) = val;
|
|
else if (addr < 0x800) // MADRS is mirrored twice
|
|
*(unsigned int *) &(SCSP->DSP.MADRS[(addr-0x7c0)/2]) = val;
|
|
else if(addr<0xC00)
|
|
*(unsigned int *) &(SCSP->DSP.MPRO[(addr-0x800)/2])=val;
|
|
else
|
|
int a=1;
|
|
if(addr==0xBF0)
|
|
SCSPDSP_Start(&SCSP->DSP);
|
|
int a=1;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
unsigned char SCSP_r8(unsigned int addr)
|
|
{
|
|
unsigned char v=0;
|
|
addr&=0xffff;
|
|
if(addr<0x400)
|
|
{
|
|
int slot=addr/0x20;
|
|
addr&=0x1f;
|
|
SCSP_UpdateSlotRegR(slot,(addr^1)&0x1f);
|
|
|
|
v=*(unsigned char *) &(SCSP->Slots[slot].datab[addr^1]);
|
|
//DebugLog("Slot %02X Reg %02X Read byte %02X",slot,addr^1,v);
|
|
}
|
|
else if(addr<0x600)
|
|
{
|
|
SCSP_UpdateRegR(addr&0xff);
|
|
v= *(unsigned char *) &(SCSP->datab[(addr&0xff)^1]);
|
|
//ErrorLogMessage("SCSP Reg %02X Read byte %02X",addr&0xff,v);
|
|
}
|
|
else if(addr<0x700)
|
|
v=0;
|
|
return v;
|
|
}
|
|
|
|
unsigned short SCSP_r16(unsigned int addr)
|
|
{
|
|
unsigned short v=0;
|
|
addr&=0xffff;
|
|
if(addr<0x400)
|
|
{
|
|
int slot=addr/0x20;
|
|
addr&=0x1f;
|
|
SCSP_UpdateSlotRegR(slot,addr&0x1f);
|
|
v=*(unsigned short *) &(SCSP->Slots[slot].datab[addr]);
|
|
//DebugLog("Slot %02X Reg %02X Read word %04X",slot,addr,v);
|
|
}
|
|
else if(addr<0x600)
|
|
{
|
|
//SCSP_UpdateRegR(addr&0xff);
|
|
//v= *(unsigned short *) &(SCSP->datab[addr&0xff]);
|
|
////ErrorLogMessage("SCSP Reg %02X Read word %04X",addr&0xff,v);
|
|
if (addr < 0x430)
|
|
{
|
|
SCSP_UpdateRegR(addr & 0x3f);
|
|
v = *((UINT16 *)(SCSP->datab + ((addr & 0x3f))));
|
|
}
|
|
}
|
|
else if (addr < 0x700)
|
|
v = SCSP->RINGBUF[(addr - 0x600) / 2];
|
|
else
|
|
{
|
|
// DSP stuff
|
|
if (addr < 0x780) //COEF
|
|
v = *((UINT16 *)(SCSP->DSP.COEF + (addr - 0x700) / 2));
|
|
else if (addr < 0x7c0)
|
|
v = *((UINT16 *)(SCSP->DSP.MADRS + (addr - 0x780) / 2));
|
|
else if (addr < 0x800)
|
|
v = *((UINT16 *)(SCSP->DSP.MADRS + (addr - 0x7c0) / 2));
|
|
else if (addr < 0xC00)
|
|
v = *((UINT16 *)(SCSP->DSP.MPRO + (addr - 0x800) / 2));
|
|
else if (addr < 0xE00)
|
|
{
|
|
if (addr & 2)
|
|
v = SCSP->DSP.TEMP[(addr >> 2) & 0x7f] & 0xffff;
|
|
else
|
|
v = SCSP->DSP.TEMP[(addr >> 2) & 0x7f] >> 16;
|
|
}
|
|
else if (addr < 0xE80)
|
|
{
|
|
if (addr & 2)
|
|
v = SCSP->DSP.MEMS[(addr >> 2) & 0x1f] & 0xffff;
|
|
else
|
|
v = SCSP->DSP.MEMS[(addr >> 2) & 0x1f] >> 16;
|
|
}
|
|
else if (addr < 0xEC0)
|
|
{
|
|
if (addr & 2)
|
|
v = SCSP->DSP.MIXS[(addr >> 2) & 0xf] & 0xffff;
|
|
else
|
|
v = SCSP->DSP.MIXS[(addr >> 2) & 0xf] >> 16;
|
|
}
|
|
else if (addr < 0xEE0)
|
|
v = *((UINT16 *)(SCSP->DSP.EFREG + (addr - 0xec0) / 2));
|
|
else
|
|
{
|
|
if (addr < 0xEE4)
|
|
v = *((UINT16 *)(SCSP->DSP.EXTS + (addr - 0xee0) / 2));
|
|
}
|
|
}
|
|
return v;
|
|
}
|
|
|
|
unsigned int SCSP_r32(unsigned int addr)
|
|
{
|
|
return 0xffffffff;
|
|
}
|
|
|
|
#define REVSIGN(v) ((~v)+1)
|
|
|
|
void SCSP_TimersAddTicks(int ticks)
|
|
{
|
|
if (TimCnt[0] <= 0xff00)
|
|
{
|
|
TimCnt[0] += ticks << (8 - ((SCSPs->data[0x18 / 2] >> 8) & 0x7));
|
|
if (TimCnt[0] > 0xFF00)
|
|
{
|
|
TimCnt[0] = 0xFFFF;
|
|
SCSPs->data[0x20 / 2] |= 0x40;
|
|
}
|
|
SCSPs->data[0x18 / 2] &= 0xff00;
|
|
SCSPs->data[0x18 / 2] |= TimCnt[0] >> 8;
|
|
}
|
|
|
|
if (TimCnt[1] <= 0xff00)
|
|
{
|
|
TimCnt[1] += ticks << (8 - ((SCSPs->data[0x1a / 2] >> 8) & 0x7));
|
|
if (TimCnt[1] > 0xFF00)
|
|
{
|
|
TimCnt[1] = 0xFFFF;
|
|
SCSPs->data[0x20 / 2] |= 0x80;
|
|
}
|
|
SCSPs->data[0x1a / 2] &= 0xff00;
|
|
SCSPs->data[0x1a / 2] |= TimCnt[1] >> 8;
|
|
}
|
|
|
|
if (TimCnt[2] <= 0xff00)
|
|
{
|
|
TimCnt[2] += ticks << (8 - ((SCSPs->data[0x1c / 2] >> 8) & 0x7));
|
|
if (TimCnt[2] > 0xFF00)
|
|
{
|
|
TimCnt[2] = 0xFFFF;
|
|
SCSPs->data[0x20 / 2] |= 0x100;
|
|
}
|
|
SCSPs->data[0x1c / 2] &= 0xff00;
|
|
SCSPs->data[0x1c / 2] |= TimCnt[2] >> 8;
|
|
}
|
|
}
|
|
|
|
|
|
signed int inline SCSP_UpdateSlot(_SLOT *slot)
|
|
{
|
|
signed int sample;
|
|
int step = slot->step;
|
|
DWORD addr1, addr2, addr_select;
|
|
DWORD *addr[2] = { &addr1, &addr2 };
|
|
DWORD *slot_addr[2] = { &(slot->cur_addr), &(slot->nxt_addr) };
|
|
|
|
|
|
if (SSCTL(slot) != 0)
|
|
return 0;
|
|
|
|
if (PLFOS(slot) != 0)
|
|
{
|
|
step = step * PLFO_Step(&(slot->PLFO));
|
|
step >>= (SHIFT);
|
|
}
|
|
|
|
if (PCM8B(slot)) {
|
|
addr1 = slot->cur_addr >> SHIFT;
|
|
addr2 = slot->nxt_addr >> SHIFT;
|
|
}
|
|
else {
|
|
//addr=(slot->cur_addr>>(SHIFT-1))&(~1);
|
|
addr1 = (slot->cur_addr >> (SHIFT - 1)) & 0x7fffe;
|
|
addr2 = (slot->nxt_addr >> (SHIFT - 1)) & 0x7fffe;
|
|
}
|
|
|
|
if (MDL(slot) != 0 || MDXSL(slot) != 0 || MDYSL(slot) != 0)
|
|
{
|
|
signed int smp = (SCSPs->RINGBUF[(SCSPs->BUFPTR + MDXSL(slot)) & 63] + SCSPs->RINGBUF[(SCSPs->BUFPTR + MDYSL(slot)) & 63]) / 2;
|
|
smp <<= 0xA; // associate cycle with 1024
|
|
// Here down below, a sample range of 24 is needed for VF3 to sound correct.
|
|
smp >>= 0x18 - MDL(slot); // ex. for MDL=0xF, sample range corresponds to +/- 64 pi (32=2^5 cycles) so shift by 11 (16-5 == 0x1A-0xF)
|
|
if (!PCM8B(slot)) smp <<= 1;
|
|
addr1 += smp; addr2 += smp;
|
|
if (!PCM8B(slot))
|
|
{
|
|
addr1 &= 0x7fffe; addr2 &= 0x7fffe;
|
|
}
|
|
else
|
|
{
|
|
addr1 &= 0x7ffff; addr2 &= 0x7ffff;
|
|
}
|
|
}
|
|
//if (SSCTL(slot) == 0) {
|
|
if (PCM8B(slot)) //8 bit signed
|
|
{
|
|
signed char *p1 = (signed char *) &(slot->base[addr1 ^ 1]);
|
|
signed char *p2 = (signed char *) &(slot->base[addr2 ^ 1]);
|
|
int s;
|
|
signed int fpart = slot->cur_addr&((1 << SHIFT) - 1);
|
|
//sample=(p[0])<<8;
|
|
s = (int)(p1[0] << 8)*((1 << SHIFT) - fpart) + (int)(p2[0] << 8)*fpart;
|
|
sample = (s >> SHIFT);
|
|
}
|
|
else //16 bit signed (endianness?)
|
|
{
|
|
signed short *p1 = (signed short *) &(slot->base[addr1]);
|
|
signed short *p2 = (signed short *) &(slot->base[addr2]);
|
|
int s;
|
|
signed int fpart = slot->cur_addr&((1 << SHIFT) - 1);
|
|
//sample=(p[0]);
|
|
s = (int)(p1[0])*((1 << (SHIFT)) - fpart) + (int)(p2[0])*fpart;
|
|
|
|
|
|
sample = (s >> (SHIFT));
|
|
|
|
//sample=((p[0]>>8)&0xFF)|(p[0]<<8);
|
|
//s=(int) p[0]*((1<<SHIFT)-fpart)+(int) p[1]*fpart;
|
|
//sample=s>>SHIFT;
|
|
|
|
/* if(SBCTL(slot)&1) //reverse data
|
|
sample^=0x7fff;
|
|
if(SBCTL(slot)&2) //reverse sign
|
|
sample^=0x8000;
|
|
*/
|
|
}
|
|
//}
|
|
|
|
if (SBCTL(slot) & 0x1)
|
|
sample ^= 0x7FFF;
|
|
if (SBCTL(slot) & 0x2)
|
|
sample = (INT16)(sample ^ 0x8000);
|
|
|
|
if (slot->Back)
|
|
slot->cur_addr -= step;
|
|
else
|
|
slot->cur_addr += step;
|
|
slot->nxt_addr = slot->cur_addr + (1 << SHIFT);
|
|
|
|
addr1 = slot->cur_addr >> SHIFT;
|
|
addr2 = slot->nxt_addr >> SHIFT;
|
|
|
|
if (addr1 >= LSA(slot) && !(slot->Back))
|
|
{
|
|
if (LPSLNK(slot) && slot->EG.state == ATTACK)
|
|
slot->EG.state = DECAY1;
|
|
}
|
|
|
|
|
|
for (addr_select = 0; addr_select < 2; addr_select++)
|
|
{
|
|
INT32 rem_addr;
|
|
switch (LPCTL(slot))
|
|
{
|
|
case 0: //no loop
|
|
if (*addr[addr_select] >= LSA(slot) && *addr[addr_select] >= LEA(slot))
|
|
{
|
|
//slot->active=0;
|
|
SCSP_StopSlot(slot, 0);
|
|
}
|
|
break;
|
|
case 1: //normal loop
|
|
if (*addr[addr_select] >= LEA(slot))
|
|
{
|
|
rem_addr = *slot_addr[addr_select] - (LEA(slot) << SHIFT);
|
|
*slot_addr[addr_select] = (LSA(slot) << SHIFT) + rem_addr;
|
|
}
|
|
break;
|
|
case 2: //reverse loop
|
|
if ((*addr[addr_select] >= LSA(slot)) && !(slot->Back))
|
|
{
|
|
rem_addr = *slot_addr[addr_select] - (LSA(slot) << SHIFT);
|
|
*slot_addr[addr_select] = (LEA(slot) << SHIFT) - rem_addr;
|
|
slot->Back = 1;
|
|
}
|
|
else if ((*addr[addr_select] < LSA(slot) || (*slot_addr[addr_select] & 0x80000000)) && slot->Back)
|
|
{
|
|
rem_addr = (LSA(slot) << SHIFT) - *slot_addr[addr_select];
|
|
*slot_addr[addr_select] = (LEA(slot) << SHIFT) - rem_addr;
|
|
}
|
|
break;
|
|
case 3: //ping-pong
|
|
if (*addr[addr_select] >= LEA(slot)) //reached end, reverse till start
|
|
{
|
|
rem_addr = *slot_addr[addr_select] - (LEA(slot) << SHIFT);
|
|
*slot_addr[addr_select] = (LEA(slot) << SHIFT) - rem_addr;
|
|
slot->Back = 1;
|
|
}
|
|
else if ((*addr[addr_select] < LSA(slot) || (*slot_addr[addr_select] & 0x80000000)) && slot->Back)//reached start or negative
|
|
{
|
|
rem_addr = (LSA(slot) << SHIFT) - *slot_addr[addr_select];
|
|
*slot_addr[addr_select] = (LSA(slot) << SHIFT) + rem_addr;
|
|
slot->Back = 0;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!SDIR(slot))
|
|
{
|
|
if (ALFOS(slot) != 0)
|
|
{
|
|
sample = sample * ALFO_Step(&(slot->ALFO));
|
|
sample >>= (SHIFT);
|
|
}
|
|
|
|
|
|
|
|
if (slot->EG.state == ATTACK)
|
|
sample = (sample * EG_Update(slot)) >> SHIFT;
|
|
else
|
|
sample = (sample * EG_TABLE[EG_Update(slot) >> (SHIFT - 10)]) >> SHIFT;
|
|
}
|
|
|
|
if (!STWINH(slot))
|
|
{
|
|
if (!SDIR(slot))
|
|
{
|
|
UINT16 Enc = ((TL(slot)) << 0x0) | (0x7 << 0xd);
|
|
*RBUFDST = (sample * LPANTABLE[Enc]) >> (SHIFT + 1);
|
|
}
|
|
}
|
|
|
|
|
|
return sample;
|
|
}
|
|
|
|
|
|
void SCSP_CpuRunScanline()
|
|
{
|
|
|
|
}
|
|
|
|
void SCSP_DoMasterSamples(int nsamples)
|
|
{
|
|
int slice = (int)(12000000 / (SoundClock*nsamples)); // 68K cycles/sample
|
|
static int lastdiff = 0;
|
|
|
|
/*
|
|
* Compute relative master/slave SCSP balance (note: master is often used
|
|
* for the front speakers). Equal balance is a 1.0 scale factor for both.
|
|
* When one SCSP is fully attenuated, the other's samples will be multiplied
|
|
* by 2.
|
|
*/
|
|
float balance = (float)s_config->Get("Balance").ValueAs<float>();
|
|
if (balance < -100.0f)
|
|
balance = -100.0f;
|
|
else if (balance > 100.0f)
|
|
balance = 100.0f;
|
|
balance /= 100.0f;
|
|
float masterBalance = 1.0f + balance;
|
|
float slaveBalance = 1.0f - balance;
|
|
signed short* buffl, * buffr;
|
|
signed short* bufrl, * bufrr;
|
|
|
|
INT32 sl, s, i;
|
|
|
|
buffl = bufferfl;
|
|
buffr = bufferfr;
|
|
bufrl = bufferrl;
|
|
bufrr = bufferrr;
|
|
|
|
/*
|
|
* Generate samples
|
|
*/
|
|
for (s = 0; s < nsamples; ++s)
|
|
{
|
|
signed int smpfl = 0, smpfr = 0;
|
|
signed int smprl = 0, smprr = 0;
|
|
|
|
for (sl = 0; sl < 32; ++sl)
|
|
{
|
|
#if FM_DELAY
|
|
RBUFDST = SCSPs[0].DELAYBUF + SCSPs[0].DELAYPTR;
|
|
#else
|
|
RBUFDST = SCSPs[0].RINGBUF + SCSPs[0].BUFPTR;
|
|
#endif
|
|
if (SCSPs[0].Slots[sl].active)
|
|
{
|
|
_SLOT *slot = SCSPs[0].Slots + sl;
|
|
UINT16 Enc;
|
|
|
|
signed int sample = (int)(masterBalance*(float)SCSP_UpdateSlot(slot));
|
|
|
|
|
|
|
|
Enc = ((TL(slot)) << 0x0) | ((IMXL(slot)) << 0xd);
|
|
SCSPDSP_SetSample(&SCSPs[0].DSP, (sample*LPANTABLE[Enc]) >> (SHIFT - 2), ISEL(slot), IMXL(slot));
|
|
Enc = ((TL(slot)) << 0x0) | ((DIPAN(slot)) << 0x8) | ((DISDL(slot)) << 0xd);
|
|
#ifdef RB_VOLUME
|
|
smpfl += (sample * volume[TL(slot) + pan_left[DIPAN(slot)]]) >> 17;
|
|
smpfr += (sample * volume[TL(slot) + pan_right[DIPAN(slot)]]) >> 17;
|
|
#else
|
|
{
|
|
smpfl += (sample*LPANTABLE[Enc]) >> SHIFT;
|
|
smpfr += (sample*RPANTABLE[Enc]) >> SHIFT;
|
|
}
|
|
#endif
|
|
}
|
|
#if FM_DELAY
|
|
SCSPs[0].RINGBUF[(SCSPs[0].BUFPTR + 64 - (FM_DELAY - 1)) & 63] = SCSPs[0].DELAYBUF[(SCSPs[0].DELAYPTR + FM_DELAY - (FM_DELAY - 1)) % FM_DELAY];
|
|
#endif
|
|
++SCSPs[0].BUFPTR;
|
|
SCSPs[0].BUFPTR &= 63;
|
|
#if FM_DELAY
|
|
++SCSPs[0].DELAYPTR;
|
|
if (SCSPs[0].DELAYPTR > FM_DELAY - 1) SCSPs[0].DELAYPTR = 0;
|
|
#endif
|
|
if (HasSlaveSCSP)
|
|
#if FM_DELAY
|
|
RBUFDST = SCSPs[1].DELAYBUF + SCSPs[1].DELAYPTR;
|
|
#else
|
|
RBUFDST = SCSPs[1].RINGBUF + SCSPs[1].BUFPTR;
|
|
#endif
|
|
{
|
|
if (SCSPs[1].Slots[sl].active)
|
|
{
|
|
_SLOT *slot = SCSPs[1].Slots + sl;
|
|
UINT16 Enc;
|
|
|
|
signed int sample = (int)(slaveBalance*(float)SCSP_UpdateSlot(slot));
|
|
|
|
Enc = ((TL(slot)) << 0x0) | ((IMXL(slot)) << 0xd);
|
|
SCSPDSP_SetSample(&SCSPs[1].DSP, (sample*LPANTABLE[Enc]) >> (SHIFT - 2), ISEL(slot), IMXL(slot));
|
|
Enc = ((TL(slot)) << 0x0) | ((DIPAN(slot)) << 0x8) | ((DISDL(slot)) << 0xd);
|
|
{
|
|
#ifdef RB_VOLUME
|
|
smprl += (sample * volume[TL(slot) + pan_left[DIPAN(slot)]]) >> 17;
|
|
smprr += (sample * volume[TL(slot) + pan_right[DIPAN(slot)]]) >> 17;
|
|
#else
|
|
smprl += (sample*LPANTABLE[Enc]) >> SHIFT;
|
|
smprr += (sample*RPANTABLE[Enc]) >> SHIFT;
|
|
}
|
|
#endif
|
|
}
|
|
#if FM_DELAY
|
|
SCSPs[1].RINGBUF[(SCSPs[1].BUFPTR + 64 - (FM_DELAY - 1)) & 63] = SCSPs[1].DELAYBUF[(SCSPs[1].DELAYPTR + FM_DELAY - (FM_DELAY - 1)) % FM_DELAY];
|
|
#endif
|
|
++SCSPs[1].BUFPTR;
|
|
SCSPs[1].BUFPTR &= 63;
|
|
#if FM_DELAY
|
|
++SCSPs[1].DELAYPTR;
|
|
if (SCSPs[1].DELAYPTR > FM_DELAY - 1) SCSPs[1].DELAYPTR = 0;
|
|
#endif
|
|
}
|
|
|
|
}
|
|
|
|
SCSPDSP_Step(&SCSPs[0].DSP);
|
|
if (HasSlaveSCSP)
|
|
SCSPDSP_Step(&SCSPs[1].DSP);
|
|
|
|
// smpl=0;
|
|
// smpr=0;
|
|
for (i = 0; i < 16; ++i)
|
|
{
|
|
_SLOT *slot = SCSPs[0].Slots + i;
|
|
if (legacySound == true) {
|
|
if (EFSDL(slot))
|
|
{
|
|
// For legacy option, 14 is the most reasonable value I can set at the moment for the EFSDL slot. - Paul
|
|
UINT16 Enc = ((EFPAN(slot)) << 0x8) | ((EFSDL(slot)) << 0xe);
|
|
smpfl += (int)(masterBalance*(float)(((SCSPs[0].DSP.EFREG[i] * LPANTABLE[Enc]) >> SHIFT)));
|
|
smpfr += (int)(masterBalance*(float)(((SCSPs[0].DSP.EFREG[i] * RPANTABLE[Enc]) >> SHIFT)));
|
|
}
|
|
if (HasSlaveSCSP)
|
|
{
|
|
_SLOT *slot = SCSPs[1].Slots + i;
|
|
if (EFSDL(slot))
|
|
{
|
|
UINT16 Enc = ((EFPAN(slot)) << 0x8) | ((EFSDL(slot)) << 0xe);
|
|
smprl += (int)(slaveBalance*(float)(((SCSPs[1].DSP.EFREG[i] * LPANTABLE[Enc]) >> SHIFT)));
|
|
smprr += (int)(slaveBalance*(float)(((SCSPs[1].DSP.EFREG[i] * RPANTABLE[Enc]) >> SHIFT)));
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (EFSDL(slot))
|
|
{
|
|
UINT16 Enc = ((EFPAN(slot)) << 0x8) | ((EFSDL(slot)) << 0xd);
|
|
smpfl += (int)(masterBalance*(float)(((SCSPs[0].DSP.EFREG[i] * LPANTABLE[Enc]) >> SHIFT)));
|
|
smpfr += (int)(masterBalance*(float)(((SCSPs[0].DSP.EFREG[i] * RPANTABLE[Enc]) >> SHIFT)));
|
|
}
|
|
if (HasSlaveSCSP)
|
|
{
|
|
_SLOT *slot = SCSPs[1].Slots + i;
|
|
if (EFSDL(slot))
|
|
{
|
|
UINT16 Enc = ((EFPAN(slot)) << 0x8) | ((EFSDL(slot)) << 0xd);
|
|
smprl += (int)(slaveBalance*(float)(((SCSPs[1].DSP.EFREG[i] * LPANTABLE[Enc]) >> SHIFT)));
|
|
smprr += (int)(slaveBalance*(float)(((SCSPs[1].DSP.EFREG[i] * RPANTABLE[Enc]) >> SHIFT)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (DAC18B((&SCSP[0])))
|
|
{
|
|
smpfl = ICLIP18(smpfl);
|
|
smpfr = ICLIP18(smpfr);
|
|
}
|
|
else
|
|
{
|
|
smpfl = ICLIP16(smpfl >> 2);
|
|
smpfr = ICLIP16(smpfr >> 2);
|
|
}
|
|
*buffl++ = ICLIP16(smpfl);
|
|
*buffr++ = ICLIP16(smpfr);
|
|
|
|
if (HasSlaveSCSP)
|
|
{
|
|
if (DAC18B((&SCSPs[1])))
|
|
{
|
|
smprl = ICLIP18(smprl);
|
|
smprr = ICLIP18(smprr);
|
|
}
|
|
else
|
|
{
|
|
smprl = ICLIP16(smprl >> 2);
|
|
smprr = ICLIP16(smprr >> 2);
|
|
}
|
|
}
|
|
*bufrl++ = ICLIP16(smprl);
|
|
*bufrr++ = ICLIP16(smprr);
|
|
|
|
SCSP_TimersAddTicks(1);
|
|
CheckPendingIRQ();
|
|
lastdiff = Run68kCB(slice - lastdiff);
|
|
}
|
|
}
|
|
|
|
void SCSP_Update()
|
|
{
|
|
SCSP_DoMasterSamples(length);
|
|
}
|
|
|
|
void SCSP_SetCB(int (*Run68k)(int cycles),void (*Int68k)(int irq))
|
|
{
|
|
Int68kCB=Int68k;
|
|
Run68kCB=Run68k;
|
|
}
|
|
|
|
void SCSP_MidiIn(BYTE val)
|
|
{
|
|
/*
|
|
* MIDI FIFO critical section
|
|
*/
|
|
if (s_multiThreaded)
|
|
MIDILock->Lock();
|
|
|
|
//DebugLog("Midi Buffer push %02X",val);
|
|
MidiStack[MidiW++]=val;
|
|
MidiW&=MIDI_STACK_SIZE_MASK;
|
|
MidiInFill++;
|
|
//Int68kCB(IrqMidi);
|
|
// SCSP.data[0x20/2]|=0x8;
|
|
|
|
if (s_multiThreaded)
|
|
MIDILock->Unlock();
|
|
}
|
|
|
|
void SCSP_MidiOutW(BYTE val)
|
|
{
|
|
/*
|
|
* MIDI FIFO critical section
|
|
*/
|
|
if (s_multiThreaded)
|
|
MIDILock->Lock();
|
|
|
|
//printf("68K: MIDI out\n");
|
|
//DebugLog("Midi Out Buffer push %02X",val);
|
|
MidiStack[MidiOutW++]=val;
|
|
MidiOutW&=31;
|
|
++MidiOutFill;
|
|
|
|
if (s_multiThreaded)
|
|
MIDILock->Unlock();
|
|
}
|
|
|
|
|
|
unsigned char SCSP_MidiOutR()
|
|
{
|
|
unsigned char val;
|
|
|
|
if(MidiOutR==MidiOutW) // I don't think this needs to be a critical section...
|
|
return 0xff;
|
|
|
|
/*
|
|
* MIDI FIFO critical section
|
|
*/
|
|
if (s_multiThreaded)
|
|
MIDILock->Lock();
|
|
|
|
val=MidiStack[MidiOutR++];
|
|
//DebugLog("Midi Out Buffer pop %02X",val);
|
|
MidiOutR&=31;
|
|
--MidiOutFill;
|
|
|
|
if (s_multiThreaded)
|
|
MIDILock->Unlock();
|
|
|
|
return val;
|
|
}
|
|
|
|
unsigned char SCSP_MidiOutFill()
|
|
{
|
|
unsigned char v;
|
|
|
|
/*
|
|
* MIDI FIFO critical section
|
|
*/
|
|
if (s_multiThreaded)
|
|
MIDILock->Lock();
|
|
|
|
v = MidiOutFill;
|
|
|
|
if (s_multiThreaded)
|
|
MIDILock->Unlock();
|
|
|
|
return v;
|
|
}
|
|
|
|
unsigned char SCSP_MidiInFill()
|
|
{
|
|
unsigned char v;
|
|
|
|
/*
|
|
* MIDI FIFO critical section
|
|
*/
|
|
if (s_multiThreaded)
|
|
MIDILock->Lock();
|
|
|
|
v = MidiInFill;
|
|
|
|
if (s_multiThreaded)
|
|
MIDILock->Unlock();
|
|
|
|
return v;
|
|
}
|
|
|
|
void SCSP_RTECheck()
|
|
{
|
|
/* unsigned short pend=SCSP.data[0x20/2]&0xfff;
|
|
if(pend)
|
|
{
|
|
if(pend&0x40)
|
|
{
|
|
Int68kCB(IrqTimA);
|
|
return;
|
|
}
|
|
if(pend&(0x80|0x100))
|
|
{
|
|
Int68kCB(IrqTimBC);
|
|
return;
|
|
}
|
|
if(pend&0x8)
|
|
Int68kCB(IrqMidi);
|
|
}
|
|
*/
|
|
}
|
|
|
|
|
|
|
|
void SCSP_Master_w8(unsigned int addr,unsigned char val)
|
|
{
|
|
SCSP=SCSPs+0;
|
|
SCSP_w8(addr,val);
|
|
}
|
|
|
|
void SCSP_Master_w16(unsigned int addr,unsigned short val)
|
|
{
|
|
SCSP=SCSPs+0;
|
|
SCSP_w16(addr,val);
|
|
}
|
|
|
|
void SCSP_Master_w32(unsigned int addr,unsigned int val)
|
|
{
|
|
SCSP=SCSPs+0;
|
|
SCSP_w32(addr,val);
|
|
}
|
|
|
|
void SCSP_Slave_w8(unsigned int addr,unsigned char val)
|
|
{
|
|
SCSP=SCSPs+1;
|
|
SCSP_w8(addr,val);
|
|
}
|
|
|
|
void SCSP_Slave_w16(unsigned int addr,unsigned short val)
|
|
{
|
|
SCSP=SCSPs+1;
|
|
SCSP_w16(addr,val);
|
|
}
|
|
|
|
void SCSP_Slave_w32(unsigned int addr,unsigned int val)
|
|
{
|
|
SCSP=SCSPs+1;
|
|
SCSP_w32(addr,val);
|
|
}
|
|
|
|
unsigned char SCSP_Master_r8(unsigned int addr)
|
|
{
|
|
SCSP=SCSPs+0;
|
|
return SCSP_r8(addr);
|
|
}
|
|
|
|
unsigned short SCSP_Master_r16(unsigned int addr)
|
|
{
|
|
SCSP=SCSPs+0;
|
|
return SCSP_r16(addr);
|
|
}
|
|
|
|
unsigned int SCSP_Master_r32(unsigned int addr)
|
|
{
|
|
SCSP=SCSPs+0;
|
|
return SCSP_r32(addr);
|
|
}
|
|
|
|
unsigned char SCSP_Slave_r8(unsigned int addr)
|
|
{
|
|
SCSP=SCSPs+1;
|
|
return SCSP_r8(addr);
|
|
}
|
|
|
|
unsigned short SCSP_Slave_r16(unsigned int addr)
|
|
{
|
|
SCSP=SCSPs+1;
|
|
return SCSP_r16(addr);
|
|
}
|
|
|
|
unsigned int SCSP_Slave_r32(unsigned int addr)
|
|
{
|
|
SCSP=SCSPs+1;
|
|
return SCSP_r32(addr);
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
Supermodel Interface Functions
|
|
******************************************************************************/
|
|
|
|
void SCSP_SaveState(CBlockFile *StateFile)
|
|
{
|
|
StateFile->NewBlock("SCSP x 2", __FILE__);
|
|
|
|
/*
|
|
* Save global variables.
|
|
*
|
|
* Difficult to say exactly what is necessary given that many things are
|
|
* commented out and should not be enabled but I try to save as much as
|
|
* possible.
|
|
*
|
|
* Things not saved:
|
|
*
|
|
* - FNS table (populated by SCSP_Init() and only read)
|
|
* - RB_VOLUME stuff
|
|
* - ARTABLE, DRTABLE
|
|
* - RBUFDST
|
|
*/
|
|
StateFile->Write(&IrqTimA, sizeof(IrqTimA));
|
|
StateFile->Write(&IrqTimBC, sizeof(IrqTimBC));
|
|
StateFile->Write(&IrqMidi, sizeof(IrqMidi));
|
|
StateFile->Write(MidiOutStack, sizeof(MidiOutStack));
|
|
StateFile->Write(&MidiOutW, sizeof(MidiOutW));
|
|
StateFile->Write(&MidiOutR, sizeof(MidiOutR));
|
|
StateFile->Write(MidiStack, sizeof(MidiStack));
|
|
StateFile->Write(&MidiOutFill, sizeof(MidiOutFill));
|
|
StateFile->Write(&MidiInFill, sizeof(MidiInFill));
|
|
StateFile->Write(&MidiW, sizeof(MidiW));
|
|
StateFile->Write(&MidiR, sizeof(MidiR));
|
|
StateFile->Write(TimPris, sizeof(TimPris));
|
|
StateFile->Write(TimCnt, sizeof(TimCnt));
|
|
|
|
// Save both SCSP states
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
StateFile->Write(SCSPs[i].datab, sizeof(SCSPs[i].datab));
|
|
StateFile->Write(&(SCSPs[i].BUFPTR), sizeof(SCSPs[i].BUFPTR));
|
|
StateFile->Write(&(SCSPs[i].Master), sizeof(SCSPs[i].Master));
|
|
#if FM_DELAY
|
|
StateFile->Write(&(SCSPs[i].DELAYBUF), sizeof(SCSPs[i].DELAYBUF));
|
|
StateFile->Write(&(SCSPs[i].DELAYPTR), sizeof(SCSPs[i].DELAYPTR));
|
|
#endif
|
|
|
|
// Save each slot
|
|
for (int j = 0; j < 32; j++)
|
|
{
|
|
UINT64 baseOffset;
|
|
UINT8 egState;
|
|
|
|
StateFile->Write(SCSPs[i].Slots[j].datab, sizeof(SCSPs[i].Slots[j].datab));
|
|
StateFile->Write(&(SCSPs[i].Slots[j].active), sizeof(SCSPs[i].Slots[j].active));
|
|
baseOffset = (UINT64) (SCSPs[i].Slots[j].base - SCSPs[i].SCSPRAM);
|
|
StateFile->Write(&baseOffset, sizeof(baseOffset));
|
|
StateFile->Write(&(SCSPs[i].Slots[j].cur_addr), sizeof(SCSPs[i].Slots[j].cur_addr));
|
|
StateFile->Write(&(SCSPs[i].Slots[j].nxt_addr), sizeof(SCSPs[i].Slots[j].nxt_addr));
|
|
StateFile->Write(&(SCSPs[i].Slots[j].step), sizeof(SCSPs[i].Slots[j].step));
|
|
StateFile->Write(&(SCSPs[i].Slots[j].Back), sizeof(SCSPs[i].Slots[j].Back));
|
|
StateFile->Write(&(SCSPs[i].Slots[j].slot), sizeof(SCSPs[i].Slots[j].slot));
|
|
StateFile->Write(&(SCSPs[i].Slots[j].Prev), sizeof(SCSPs[i].Slots[j].Prev));
|
|
|
|
// EG
|
|
StateFile->Write(&(SCSPs[i].Slots[j].EG.volume), sizeof(SCSPs[i].Slots[j].EG.volume));
|
|
egState = SCSPs[i].Slots[j].EG.state;
|
|
StateFile->Write(&egState, sizeof(egState));
|
|
StateFile->Write(&(SCSPs[i].Slots[j].EG.step), sizeof(SCSPs[i].Slots[j].EG.step));
|
|
StateFile->Write(&(SCSPs[i].Slots[j].EG.AR), sizeof(SCSPs[i].Slots[j].EG.AR));
|
|
StateFile->Write(&(SCSPs[i].Slots[j].EG.D1R), sizeof(SCSPs[i].Slots[j].EG.D1R));
|
|
StateFile->Write(&(SCSPs[i].Slots[j].EG.D2R), sizeof(SCSPs[i].Slots[j].EG.D2R));
|
|
StateFile->Write(&(SCSPs[i].Slots[j].EG.RR), sizeof(SCSPs[i].Slots[j].EG.RR));
|
|
StateFile->Write(&(SCSPs[i].Slots[j].EG.DL), sizeof(SCSPs[i].Slots[j].EG.DL));
|
|
StateFile->Write(&(SCSPs[i].Slots[j].EG.EGHOLD), sizeof(SCSPs[i].Slots[j].EG.EGHOLD));
|
|
StateFile->Write(&(SCSPs[i].Slots[j].EG.LPLINK), sizeof(SCSPs[i].Slots[j].EG.LPLINK));
|
|
|
|
// PLFO
|
|
StateFile->Write(&(SCSPs[i].Slots[j].PLFO.phase), sizeof(SCSPs[i].Slots[j].PLFO.phase));
|
|
StateFile->Write(&(SCSPs[i].Slots[j].PLFO.phase_step), sizeof(SCSPs[i].Slots[j].PLFO.phase_step));
|
|
|
|
// ALFO
|
|
StateFile->Write(&(SCSPs[i].Slots[j].ALFO.phase), sizeof(SCSPs[i].Slots[j].ALFO.phase));
|
|
StateFile->Write(&(SCSPs[i].Slots[j].ALFO.phase_step), sizeof(SCSPs[i].Slots[j].ALFO.phase_step));
|
|
|
|
//when loading, make sure to compute lfo
|
|
}
|
|
|
|
// DSP
|
|
StateFile->Write(&(SCSPs[i].DSP.RBP), sizeof(SCSPs[i].DSP.RBP));
|
|
StateFile->Write(&(SCSPs[i].DSP.RBL), sizeof(SCSPs[i].DSP.RBL));
|
|
StateFile->Write(SCSPs[i].DSP.COEF, sizeof(SCSPs[i].DSP.COEF));
|
|
StateFile->Write(SCSPs[i].DSP.MADRS, sizeof(SCSPs[i].DSP.MADRS));
|
|
StateFile->Write(SCSPs[i].DSP.MPRO, sizeof(SCSPs[i].DSP.MPRO));
|
|
StateFile->Write(SCSPs[i].DSP.TEMP, sizeof(SCSPs[i].DSP.TEMP));
|
|
StateFile->Write(SCSPs[i].DSP.MEMS, sizeof(SCSPs[i].DSP.MEMS));
|
|
StateFile->Write(&(SCSPs[i].DSP.DEC), sizeof(SCSPs[i].DSP.DEC));
|
|
StateFile->Write(SCSPs[i].DSP.MIXS, sizeof(SCSPs[i].DSP.MIXS));
|
|
StateFile->Write(SCSPs[i].DSP.EXTS, sizeof(SCSPs[i].DSP.EXTS));
|
|
StateFile->Write(SCSPs[i].DSP.EFREG, sizeof(SCSPs[i].DSP.EFREG));
|
|
StateFile->Write(&(SCSPs[i].DSP.Stopped), sizeof(SCSPs[i].DSP.Stopped));
|
|
StateFile->Write(&(SCSPs[i].DSP.LastStep), sizeof(SCSPs[i].DSP.LastStep));
|
|
}
|
|
}
|
|
|
|
void SCSP_LoadState(CBlockFile *StateFile)
|
|
{
|
|
if (OKAY != StateFile->FindBlock("SCSP x 2"))
|
|
{
|
|
ErrorLog("Unable to load SCSP state. Save state file is corrupt.");
|
|
return;
|
|
}
|
|
|
|
// Load global variables
|
|
StateFile->Read(&IrqTimA, sizeof(IrqTimA));
|
|
StateFile->Read(&IrqTimBC, sizeof(IrqTimBC));
|
|
StateFile->Read(&IrqMidi, sizeof(IrqMidi));
|
|
StateFile->Read(MidiOutStack, sizeof(MidiOutStack));
|
|
StateFile->Read(&MidiOutW, sizeof(MidiOutW));
|
|
StateFile->Read(&MidiOutR, sizeof(MidiOutR));
|
|
StateFile->Read(MidiStack, sizeof(MidiStack));
|
|
StateFile->Read(&MidiOutFill, sizeof(MidiOutFill));
|
|
StateFile->Read(&MidiInFill, sizeof(MidiInFill));
|
|
StateFile->Read(&MidiW, sizeof(MidiW));
|
|
StateFile->Read(&MidiR, sizeof(MidiR));
|
|
StateFile->Read(TimPris, sizeof(TimPris));
|
|
StateFile->Read(TimCnt, sizeof(TimCnt));
|
|
|
|
// Load both SCSP states
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
StateFile->Read(SCSPs[i].datab, sizeof(SCSPs[i].datab));
|
|
StateFile->Read(&(SCSPs[i].BUFPTR), sizeof(SCSPs[i].BUFPTR));
|
|
StateFile->Read(&(SCSPs[i].Master), sizeof(SCSPs[i].Master));
|
|
#if FM_DELAY
|
|
StateFile->Read(&(SCSPs[i].DELAYBUF), sizeof(SCSPs[i].DELAYBUF));
|
|
StateFile->Read(&(SCSPs[i].DELAYPTR), sizeof(SCSPs[i].DELAYPTR));
|
|
#endif
|
|
|
|
// Load each slot
|
|
for (int j = 0; j < 32; j++)
|
|
{
|
|
UINT64 baseOffset;
|
|
UINT8 egState;
|
|
|
|
StateFile->Read(SCSPs[i].Slots[j].datab, sizeof(SCSPs[i].Slots[j].datab));
|
|
StateFile->Read(&(SCSPs[i].Slots[j].active), sizeof(SCSPs[i].Slots[j].active));
|
|
StateFile->Read(&baseOffset, sizeof(baseOffset));
|
|
SCSPs[i].Slots[j].base = &(SCSPs[i].SCSPRAM[baseOffset&0xFFFFF]); // clamp to 1 MB
|
|
StateFile->Read(&(SCSPs[i].Slots[j].cur_addr), sizeof(SCSPs[i].Slots[j].cur_addr));
|
|
StateFile->Read(&(SCSPs[i].Slots[j].nxt_addr), sizeof(SCSPs[i].Slots[j].nxt_addr));
|
|
StateFile->Read(&(SCSPs[i].Slots[j].step), sizeof(SCSPs[i].Slots[j].step));
|
|
StateFile->Read(&(SCSPs[i].Slots[j].Back), sizeof(SCSPs[i].Slots[j].Back));
|
|
StateFile->Read(&(SCSPs[i].Slots[j].slot), sizeof(SCSPs[i].Slots[j].slot));
|
|
StateFile->Read(&(SCSPs[i].Slots[j].Prev), sizeof(SCSPs[i].Slots[j].Prev));
|
|
|
|
// EG
|
|
StateFile->Read(&(SCSPs[i].Slots[j].EG.volume), sizeof(SCSPs[i].Slots[j].EG.volume));
|
|
StateFile->Read(&egState, sizeof(egState));
|
|
SCSPs[i].Slots[j].EG.state = (_STATE) egState;
|
|
StateFile->Read(&(SCSPs[i].Slots[j].EG.step), sizeof(SCSPs[i].Slots[j].EG.step));
|
|
StateFile->Read(&(SCSPs[i].Slots[j].EG.AR), sizeof(SCSPs[i].Slots[j].EG.AR));
|
|
StateFile->Read(&(SCSPs[i].Slots[j].EG.D1R), sizeof(SCSPs[i].Slots[j].EG.D1R));
|
|
StateFile->Read(&(SCSPs[i].Slots[j].EG.D2R), sizeof(SCSPs[i].Slots[j].EG.D2R));
|
|
StateFile->Read(&(SCSPs[i].Slots[j].EG.RR), sizeof(SCSPs[i].Slots[j].EG.RR));
|
|
StateFile->Read(&(SCSPs[i].Slots[j].EG.DL), sizeof(SCSPs[i].Slots[j].EG.DL));
|
|
StateFile->Read(&(SCSPs[i].Slots[j].EG.EGHOLD), sizeof(SCSPs[i].Slots[j].EG.EGHOLD));
|
|
StateFile->Read(&(SCSPs[i].Slots[j].EG.LPLINK), sizeof(SCSPs[i].Slots[j].EG.LPLINK));
|
|
|
|
// PLFO
|
|
StateFile->Read(&(SCSPs[i].Slots[j].PLFO.phase), sizeof(SCSPs[i].Slots[j].PLFO.phase));
|
|
StateFile->Read(&(SCSPs[i].Slots[j].PLFO.phase_step), sizeof(SCSPs[i].Slots[j].PLFO.phase_step));
|
|
|
|
// ALFO
|
|
StateFile->Read(&(SCSPs[i].Slots[j].ALFO.phase), sizeof(SCSPs[i].Slots[j].ALFO.phase));
|
|
StateFile->Read(&(SCSPs[i].Slots[j].ALFO.phase_step), sizeof(SCSPs[i].Slots[j].ALFO.phase_step));
|
|
|
|
// Recompute LFOs
|
|
Compute_LFO(&(SCSPs[i].Slots[j]));
|
|
}
|
|
|
|
// DSP
|
|
StateFile->Read(&(SCSPs[i].DSP.RBP), sizeof(SCSPs[i].DSP.RBP));
|
|
StateFile->Read(&(SCSPs[i].DSP.RBL), sizeof(SCSPs[i].DSP.RBL));
|
|
StateFile->Read(SCSPs[i].DSP.COEF, sizeof(SCSPs[i].DSP.COEF));
|
|
StateFile->Read(SCSPs[i].DSP.MADRS, sizeof(SCSPs[i].DSP.MADRS));
|
|
StateFile->Read(SCSPs[i].DSP.MPRO, sizeof(SCSPs[i].DSP.MPRO));
|
|
StateFile->Read(SCSPs[i].DSP.TEMP, sizeof(SCSPs[i].DSP.TEMP));
|
|
StateFile->Read(SCSPs[i].DSP.MEMS, sizeof(SCSPs[i].DSP.MEMS));
|
|
StateFile->Read(&(SCSPs[i].DSP.DEC), sizeof(SCSPs[i].DSP.DEC));
|
|
StateFile->Read(SCSPs[i].DSP.MIXS, sizeof(SCSPs[i].DSP.MIXS));
|
|
StateFile->Read(SCSPs[i].DSP.EXTS, sizeof(SCSPs[i].DSP.EXTS));
|
|
StateFile->Read(SCSPs[i].DSP.EFREG, sizeof(SCSPs[i].DSP.EFREG));
|
|
StateFile->Read(&(SCSPs[i].DSP.Stopped), sizeof(SCSPs[i].DSP.Stopped));
|
|
StateFile->Read(&(SCSPs[i].DSP.LastStep), sizeof(SCSPs[i].DSP.LastStep));
|
|
}
|
|
}
|
|
|
|
void SCSP_SetBuffers(INT16 *leftBufferPtr, INT16 *rightBufferPtr, INT16* leftRearBufferPtr, INT16* rightRearBufferPtr, int bufferLength)
|
|
{
|
|
SoundClock = 76;
|
|
bufferfl = leftBufferPtr;
|
|
bufferfr = rightBufferPtr;
|
|
bufferrl = leftRearBufferPtr;
|
|
bufferrr = rightRearBufferPtr;
|
|
|
|
length = bufferLength;
|
|
cnts = 0; // what is this for? seems unimportant but need to find out
|
|
}
|
|
|
|
void SCSP_Deinit(void)
|
|
{
|
|
#ifdef USEDSP
|
|
free(SCSP->MIXBuf);
|
|
#endif
|
|
free(buffertmpfl);
|
|
free(buffertmpfr);
|
|
free(buffertmprl);
|
|
free(buffertmprr);
|
|
delete MIDILock;
|
|
buffertmpfl = NULL;
|
|
buffertmpfr = NULL;
|
|
buffertmprl = NULL;
|
|
buffertmprr = NULL;
|
|
MIDILock = NULL;
|
|
}
|