579 lines
14 KiB
C++
Executable file
579 lines
14 KiB
C++
Executable file
/*-----------------------------------------------------------------------------
|
|
|
|
ST-Sound ( YM files player library )
|
|
|
|
Extended YM-2149 Emulator, with ATARI music demos effects.
|
|
(SID-Like, Digidrum, Sync Buzzer, Sinus SID and Pattern SID)
|
|
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
* ST-Sound, ATARI-ST Music Emulator
|
|
* Copyright (c) 1995-1999 Arnaud Carre ( http://leonard.oxg.free.fr )
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
#include "YmTypes.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include "Ym2149Ex.h"
|
|
|
|
//-------------------------------------------------------------------
|
|
// env shapes.
|
|
//-------------------------------------------------------------------
|
|
static const ymint Env00xx[8]={ 1,0,0,0,0,0,0,0 };
|
|
static const ymint Env01xx[8]={ 0,1,0,0,0,0,0,0 };
|
|
static const ymint Env1000[8]={ 1,0,1,0,1,0,1,0 };
|
|
static const ymint Env1001[8]={ 1,0,0,0,0,0,0,0 };
|
|
static const ymint Env1010[8]={ 1,0,0,1,1,0,0,1 };
|
|
static const ymint Env1011[8]={ 1,0,1,1,1,1,1,1 };
|
|
static const ymint Env1100[8]={ 0,1,0,1,0,1,0,1 };
|
|
static const ymint Env1101[8]={ 0,1,1,1,1,1,1,1 };
|
|
static const ymint Env1110[8]={ 0,1,1,0,0,1,1,0 };
|
|
static const ymint Env1111[8]={ 0,1,0,0,0,0,0,0 };
|
|
static const ymint * EnvWave[16] = { Env00xx,Env00xx,Env00xx,Env00xx,
|
|
Env01xx,Env01xx,Env01xx,Env01xx,
|
|
Env1000,Env1001,Env1010,Env1011,
|
|
Env1100,Env1101,Env1110,Env1111};
|
|
|
|
static ymint ymVolumeTable[16] =
|
|
{ 62,161,265,377,580,774,1155,1575,2260,3088,4570,6233,9330,13187,21220,32767};
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
// Very cool and fast DC Adjuster ! This is the *new* stuff of that
|
|
// package coz I get that idea working on SainT in 2004 !
|
|
// ( almost everything here is from 1995 !)
|
|
//----------------------------------------------------------------------
|
|
CDcAdjuster::CDcAdjuster()
|
|
{
|
|
Reset();
|
|
}
|
|
|
|
void CDcAdjuster::Reset(void)
|
|
{
|
|
for (ymint i=0;i<DC_ADJUST_BUFFERLEN;i++)
|
|
m_buffer[i] = 0;
|
|
|
|
m_pos = 0;
|
|
m_sum = 0;
|
|
}
|
|
|
|
void CDcAdjuster::AddSample(ymint sample)
|
|
{
|
|
m_sum -= m_buffer[m_pos];
|
|
m_sum += sample;
|
|
|
|
m_buffer[m_pos] = sample;
|
|
m_pos = (m_pos+1)&(DC_ADJUST_BUFFERLEN-1);
|
|
}
|
|
|
|
|
|
|
|
static ymu8 *ym2149EnvInit(ymu8 *pEnv,ymint a,ymint b)
|
|
{
|
|
ymint i;
|
|
ymint d;
|
|
|
|
d = b-a;
|
|
a *= 15;
|
|
for (i=0;i<16;i++)
|
|
{
|
|
*pEnv++ = (ymu8)a;
|
|
a += d;
|
|
}
|
|
return pEnv;
|
|
}
|
|
|
|
|
|
CYm2149Ex::CYm2149Ex(ymu32 masterClock,ymint prediv,ymu32 playRate)
|
|
{
|
|
ymint i,env;
|
|
|
|
|
|
m_bFilter = true;
|
|
|
|
frameCycle = 0;
|
|
if (ymVolumeTable[15]==32767) // excuse me for that bad trick ;-)
|
|
{
|
|
for (i=0;i<16;i++)
|
|
{
|
|
ymVolumeTable[i] = (ymVolumeTable[i]*2)/6;
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------
|
|
// build env shapes.
|
|
//--------------------------------------------------------
|
|
ymu8 *pEnv = &envData[0][0][0];
|
|
for (env=0;env<16;env++)
|
|
{
|
|
const ymint *pse = EnvWave[env];
|
|
for (ymint phase=0;phase<4;phase++)
|
|
{
|
|
pEnv = ym2149EnvInit(pEnv,pse[phase*2+0],pse[phase*2+1]);
|
|
}
|
|
}
|
|
|
|
internalClock = masterClock/prediv; // YM at 2Mhz on ATARI ST
|
|
replayFrequency = playRate; // DAC at 44.1Khz on PC
|
|
cycleSample = 0;
|
|
|
|
// Set volume voice pointers.
|
|
pVolA = &volA;
|
|
pVolB = &volB;
|
|
pVolC = &volC;
|
|
|
|
// Reset YM2149
|
|
reset();
|
|
|
|
|
|
}
|
|
|
|
CYm2149Ex::~CYm2149Ex()
|
|
{
|
|
}
|
|
|
|
void CYm2149Ex::setClock(ymu32 _clock)
|
|
{
|
|
internalClock = _clock;
|
|
}
|
|
|
|
|
|
ymu32 CYm2149Ex::toneStepCompute(ymu8 rHigh,ymu8 rLow)
|
|
{
|
|
|
|
ymint per = rHigh&15;
|
|
per = (per<<8)+rLow;
|
|
if (per<=5)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
#ifdef YM_INTEGER_ONLY
|
|
yms64 step = internalClock;
|
|
step <<= (15+16-3);
|
|
step /= (per * replayFrequency);
|
|
#else
|
|
ymfloat step = internalClock;
|
|
step /= ((ymfloat)per*8.0*(ymfloat)replayFrequency);
|
|
step *= 32768.0*65536.0;
|
|
#endif
|
|
ymu32 istep = (ymu32)step;
|
|
return istep;
|
|
}
|
|
|
|
ymu32 CYm2149Ex::noiseStepCompute(ymu8 rNoise)
|
|
{
|
|
|
|
|
|
ymint per = (rNoise&0x1f);
|
|
if (per<3)
|
|
return 0;
|
|
|
|
#ifdef YM_INTEGER_ONLY
|
|
yms64 step = internalClock;
|
|
step <<= (16-1-3);
|
|
step /= (per * replayFrequency);
|
|
#else
|
|
ymfloat step = internalClock;
|
|
step /= ((ymfloat)per*8.0*(ymfloat)replayFrequency);
|
|
step *= 65536.0/2.0;
|
|
#endif
|
|
|
|
return (ymu32)step;
|
|
}
|
|
|
|
ymu32 CYm2149Ex::rndCompute(void)
|
|
{
|
|
ymint rBit = (rndRack&1) ^ ((rndRack>>2)&1);
|
|
rndRack = (rndRack>>1) | (rBit<<16);
|
|
return (rBit ? 0 : 0xffff);
|
|
}
|
|
|
|
ymu32 CYm2149Ex::envStepCompute(ymu8 rHigh,ymu8 rLow)
|
|
{
|
|
|
|
|
|
ymint per = rHigh;
|
|
per = (per<<8)+rLow;
|
|
if (per<3)
|
|
return 0;
|
|
|
|
#ifdef YM_INTEGER_ONLY
|
|
yms64 step = internalClock;
|
|
step <<= (16+16-9);
|
|
step /= (per * replayFrequency);
|
|
#else
|
|
ymfloat step = internalClock;
|
|
step /= ((ymfloat)per*512.0*(ymfloat)replayFrequency);
|
|
step *= 65536.0*65536.0;
|
|
#endif
|
|
|
|
return (ymu32)step;
|
|
}
|
|
|
|
|
|
void CYm2149Ex::reset(void)
|
|
{
|
|
|
|
memset( registers, 0, sizeof(registers) );
|
|
|
|
for (int i=0;i<14;i++)
|
|
writeRegister(i,0);
|
|
|
|
writeRegister(7,0xff);
|
|
|
|
currentNoise = 0xffff;
|
|
rndRack = 1;
|
|
sidStop(0);
|
|
sidStop(1);
|
|
sidStop(2);
|
|
|
|
envShape = 0;
|
|
envPhase = 0;
|
|
envPos = 0;
|
|
|
|
m_dcAdjust.Reset();
|
|
|
|
memset(specialEffect,0,sizeof(specialEffect));
|
|
|
|
syncBuzzerStop();
|
|
|
|
m_lowPassFilter[0] = 0;
|
|
m_lowPassFilter[1] = 0;
|
|
|
|
}
|
|
|
|
|
|
void CYm2149Ex::sidVolumeCompute(ymint voice,ymint *pVol)
|
|
{
|
|
|
|
struct YmSpecialEffect *pVoice = specialEffect+voice;
|
|
|
|
if (pVoice->bSid)
|
|
{
|
|
if (pVoice->sidPos & (1<<31))
|
|
writeRegister(8+voice,pVoice->sidVol);
|
|
else
|
|
writeRegister(8+voice,0);
|
|
}
|
|
else if (pVoice->bDrum)
|
|
{
|
|
// writeRegister(8+voice,pVoice->drumData[pVoice->drumPos>>DRUM_PREC]>>4);
|
|
|
|
*pVol = (pVoice->drumData[pVoice->drumPos>>DRUM_PREC] * 255) / 6;
|
|
|
|
switch (voice)
|
|
{
|
|
case 0:
|
|
pVolA = &volA;
|
|
mixerTA = 0xffff;
|
|
mixerNA = 0xffff;
|
|
break;
|
|
case 1:
|
|
pVolB = &volB;
|
|
mixerTB = 0xffff;
|
|
mixerNB = 0xffff;
|
|
break;
|
|
case 2:
|
|
pVolC = &volC;
|
|
mixerTC = 0xffff;
|
|
mixerNC = 0xffff;
|
|
break;
|
|
}
|
|
|
|
pVoice->drumPos += pVoice->drumStep;
|
|
if ((pVoice->drumPos>>DRUM_PREC) >= pVoice->drumSize)
|
|
{
|
|
pVoice->bDrum = YMFALSE;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
int CYm2149Ex::LowPassFilter(int in)
|
|
{
|
|
const int out = (m_lowPassFilter[0]>>2) + (m_lowPassFilter[1]>>1) + (in>>2);
|
|
m_lowPassFilter[0] = m_lowPassFilter[1];
|
|
m_lowPassFilter[1] = in;
|
|
return out;
|
|
}
|
|
|
|
ymsample CYm2149Ex::nextSample(void)
|
|
{
|
|
ymint vol;
|
|
ymint bt,bn;
|
|
|
|
if (noisePos&0xffff0000)
|
|
{
|
|
currentNoise ^= rndCompute();
|
|
noisePos &= 0xffff;
|
|
}
|
|
bn = currentNoise;
|
|
|
|
volE = ymVolumeTable[envData[envShape][envPhase][envPos>>(32-5)]];
|
|
|
|
sidVolumeCompute(0,&volA);
|
|
sidVolumeCompute(1,&volB);
|
|
sidVolumeCompute(2,&volC);
|
|
|
|
//---------------------------------------------------
|
|
// Tone+noise+env+DAC for three voices !
|
|
//---------------------------------------------------
|
|
bt = ((((yms32)posA)>>31) | mixerTA) & (bn | mixerNA);
|
|
vol = (*pVolA)&bt;
|
|
bt = ((((yms32)posB)>>31) | mixerTB) & (bn | mixerNB);
|
|
vol += (*pVolB)&bt;
|
|
bt = ((((yms32)posC)>>31) | mixerTC) & (bn | mixerNC);
|
|
vol += (*pVolC)&bt;
|
|
|
|
//---------------------------------------------------
|
|
// Inc
|
|
//---------------------------------------------------
|
|
posA += stepA;
|
|
posB += stepB;
|
|
posC += stepC;
|
|
noisePos += noiseStep;
|
|
envPos += envStep;
|
|
if (0 == envPhase)
|
|
{
|
|
if (envPos<envStep)
|
|
{
|
|
envPhase = 1;
|
|
}
|
|
}
|
|
|
|
syncBuzzerPhase += syncBuzzerStep;
|
|
if (syncBuzzerPhase&(1<<31))
|
|
{
|
|
envPos = 0;
|
|
envPhase = 0;
|
|
syncBuzzerPhase &= 0x7fffffff;
|
|
}
|
|
|
|
specialEffect[0].sidPos += specialEffect[0].sidStep;
|
|
specialEffect[1].sidPos += specialEffect[1].sidStep;
|
|
specialEffect[2].sidPos += specialEffect[2].sidStep;
|
|
|
|
//---------------------------------------------------
|
|
// Normalize process
|
|
//---------------------------------------------------
|
|
m_dcAdjust.AddSample(vol);
|
|
const int in = vol - m_dcAdjust.GetDcLevel();
|
|
return (m_bFilter) ? LowPassFilter(in) : in;
|
|
}
|
|
|
|
|
|
ymint CYm2149Ex::readRegister(ymint reg)
|
|
{
|
|
if ((reg>=0) && (reg<=13)) return registers[reg];
|
|
else return -1;
|
|
}
|
|
|
|
void CYm2149Ex::writeRegister(ymint reg,ymint data)
|
|
{
|
|
|
|
switch (reg)
|
|
{
|
|
case 0:
|
|
registers[0] = data&255;
|
|
stepA = toneStepCompute(registers[1],registers[0]);
|
|
if (!stepA) posA = (1<<31); // Assume output always 1 if 0 period (for Digi-sample !)
|
|
break;
|
|
|
|
case 2:
|
|
registers[2] = data&255;
|
|
stepB = toneStepCompute(registers[3],registers[2]);
|
|
if (!stepB) posB = (1<<31); // Assume output always 1 if 0 period (for Digi-sample !)
|
|
break;
|
|
|
|
case 4:
|
|
registers[4] = data&255;
|
|
stepC = toneStepCompute(registers[5],registers[4]);
|
|
if (!stepC) posC = (1<<31); // Assume output always 1 if 0 period (for Digi-sample !)
|
|
break;
|
|
|
|
case 1:
|
|
registers[1] = data&15;
|
|
stepA = toneStepCompute(registers[1],registers[0]);
|
|
if (!stepA) posA = (1<<31); // Assume output always 1 if 0 period (for Digi-sample !)
|
|
break;
|
|
|
|
case 3:
|
|
registers[3] = data&15;
|
|
stepB = toneStepCompute(registers[3],registers[2]);
|
|
if (!stepB) posB = (1<<31); // Assume output always 1 if 0 period (for Digi-sample !)
|
|
break;
|
|
|
|
case 5:
|
|
registers[5] = data&15;
|
|
stepC = toneStepCompute(registers[5],registers[4]);
|
|
if (!stepC) posC = (1<<31); // Assume output always 1 if 0 period (for Digi-sample !)
|
|
break;
|
|
|
|
case 6:
|
|
registers[6] = data&0x1f;
|
|
noiseStep = noiseStepCompute(registers[6]);
|
|
if (!noiseStep)
|
|
{
|
|
noisePos = 0;
|
|
currentNoise = 0xffff;
|
|
}
|
|
break;
|
|
|
|
case 7:
|
|
registers[7] = data&255;
|
|
mixerTA = (data&(1<<0)) ? 0xffff : 0;
|
|
mixerTB = (data&(1<<1)) ? 0xffff : 0;
|
|
mixerTC = (data&(1<<2)) ? 0xffff : 0;
|
|
mixerNA = (data&(1<<3)) ? 0xffff : 0;
|
|
mixerNB = (data&(1<<4)) ? 0xffff : 0;
|
|
mixerNC = (data&(1<<5)) ? 0xffff : 0;
|
|
break;
|
|
|
|
case 8:
|
|
registers[8] = data&31;
|
|
volA = ymVolumeTable[data&15];
|
|
if (data&0x10)
|
|
pVolA = &volE;
|
|
else
|
|
pVolA = &volA;
|
|
break;
|
|
|
|
case 9:
|
|
registers[9] = data&31;
|
|
volB = ymVolumeTable[data&15];
|
|
if (data&0x10)
|
|
pVolB = &volE;
|
|
else
|
|
pVolB = &volB;
|
|
break;
|
|
|
|
case 10:
|
|
registers[10] = data&31;
|
|
volC = ymVolumeTable[data&15];
|
|
if (data&0x10)
|
|
pVolC = &volE;
|
|
else
|
|
pVolC = &volC;
|
|
break;
|
|
|
|
case 11:
|
|
registers[11] = data&255;
|
|
envStep = envStepCompute(registers[12],registers[11]);
|
|
break;
|
|
|
|
case 12:
|
|
registers[12] = data&255;
|
|
envStep = envStepCompute(registers[12],registers[11]);
|
|
break;
|
|
|
|
case 13:
|
|
registers[13] = data&0xf;
|
|
envPos = 0;
|
|
envPhase = 0;
|
|
envShape = data&0xf;
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
void CYm2149Ex::update(ymsample *pSampleBuffer,ymint nbSample)
|
|
{
|
|
|
|
ymsample *pBuffer = pSampleBuffer;
|
|
if (nbSample>0)
|
|
{
|
|
do
|
|
{
|
|
*pBuffer++ = nextSample();
|
|
}
|
|
while (--nbSample);
|
|
}
|
|
|
|
}
|
|
|
|
void CYm2149Ex::drumStart(ymint voice,ymu8 *pDrumBuffer,ymu32 drumSize,ymint drumFreq)
|
|
{
|
|
if ((pDrumBuffer) && (drumSize))
|
|
{
|
|
specialEffect[voice].drumData = pDrumBuffer;
|
|
specialEffect[voice].drumPos = 0;
|
|
specialEffect[voice].drumSize = drumSize;
|
|
specialEffect[voice].drumStep = (drumFreq<<DRUM_PREC)/replayFrequency;
|
|
specialEffect[voice].bDrum = YMTRUE;
|
|
}
|
|
}
|
|
|
|
void CYm2149Ex::drumStop(ymint voice)
|
|
{
|
|
specialEffect[voice].bDrum = YMFALSE;
|
|
}
|
|
|
|
void CYm2149Ex::sidStart(ymint voice,ymint timerFreq,ymint vol)
|
|
{
|
|
#ifdef YM_INTEGER_ONLY
|
|
ymu32 tmp = timerFreq * ((1<<31) / replayFrequency);
|
|
#else
|
|
ymfloat tmp = (ymfloat)timerFreq*((ymfloat)(1<<31))/(ymfloat)replayFrequency;
|
|
#endif
|
|
specialEffect[voice].sidStep = (ymu32)tmp;
|
|
specialEffect[voice].sidVol = vol&15;
|
|
specialEffect[voice].bSid = YMTRUE;
|
|
}
|
|
|
|
void CYm2149Ex::sidSinStart(ymint voice,ymint timerFreq,ymint vol)
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
void CYm2149Ex::sidStop(ymint voice)
|
|
{
|
|
specialEffect[voice].bSid = YMFALSE;
|
|
}
|
|
|
|
void CYm2149Ex::syncBuzzerStart(ymint timerFreq,ymint _envShape)
|
|
{
|
|
#ifdef YM_INTEGER_ONLY
|
|
ymu32 tmp = timerFreq * ((1<<31) / replayFrequency);
|
|
#else
|
|
ymfloat tmp = (ymfloat)timerFreq*((ymfloat)(1<<31))/(ymfloat)replayFrequency;
|
|
#endif
|
|
envShape = _envShape&15;
|
|
syncBuzzerStep = (ymu32)tmp;
|
|
syncBuzzerPhase = 0;
|
|
bSyncBuzzer = YMTRUE;
|
|
}
|
|
|
|
void CYm2149Ex::syncBuzzerStop(void)
|
|
{
|
|
bSyncBuzzer = YMFALSE;
|
|
syncBuzzerPhase = 0;
|
|
syncBuzzerStep = 0;
|
|
}
|
|
|