vkdoom_m/src/oplsynth/opl_mus_player.cpp
Randy Heit f6866df0e6 - Made the maximum number of TiMidity voices configurable through the
timidity_voices cvar.
- Added stats lines for the OPL and Timidity MIDI devices.
- Completely changed the way TiMidity volume calculations are done. It
  should now be extremely close to the output a real GUS would produce with
  its official MIDI player (excepting where TiMidity normalizes sample
  volumes). The new equations more closely match what is specified by the DLS
  and SF2 specs (but not quite), so I presume it's also more musically
  correct than what TiMidity (and TiMidity++) do.


SVN r925 (trunk)
2008-04-19 05:57:09 +00:00

407 lines
7.8 KiB
C++

#ifdef _WIN32
#include <io.h>
#endif
#include <string.h>
#include <assert.h>
#include "opl_mus_player.h"
#include "doomtype.h"
#include "fmopl.h"
#include "w_wad.h"
#include "templates.h"
#include "c_cvars.h"
#include "i_system.h"
#include "stats.h"
#define IMF_RATE 700.0
EXTERN_CVAR (Bool, opl_onechip)
OPLmusicBlock::OPLmusicBlock()
{
scoredata = NULL;
NextTickIn = 0;
TwoChips = !opl_onechip;
Looping = false;
io = NULL;
io = new OPLio;
}
OPLmusicBlock::~OPLmusicBlock()
{
delete io;
}
void OPLmusicBlock::ResetChips ()
{
TwoChips = !opl_onechip;
ChipAccess.Enter();
io->OPLdeinit ();
io->OPLinit (TwoChips + 1);
ChipAccess.Leave();
}
void OPLmusicBlock::Restart()
{
OPLstopMusic ();
OPLplayMusic (127);
MLtime = 0;
playingcount = 0;
}
OPLmusicFile::OPLmusicFile (FILE *file, char *musiccache, int len)
: ScoreLen (len)
{
if (io == NULL)
{
return;
}
scoredata = new BYTE[len];
if (file)
{
if (fread (scoredata, 1, len, file) != (size_t)len)
{
delete[] scoredata;
scoredata = NULL;
return;
}
}
else
{
memcpy(scoredata, &musiccache[0], len);
}
if (io->OPLinit (TwoChips + 1))
{
delete[] scoredata;
scoredata = NULL;
return;
}
// Check for RDosPlay raw OPL format
if (((DWORD *)scoredata)[0] == MAKE_ID('R','A','W','A') &&
((DWORD *)scoredata)[1] == MAKE_ID('D','A','T','A'))
{
RawPlayer = RDosPlay;
if (*(WORD *)(scoredata + 8) == 0)
{ // A clock speed of 0 is bad
*(WORD *)(scoredata + 8) = 0xFFFF;
}
SamplesPerTick = LittleShort(*(WORD *)(scoredata + 8)) / ADLIB_CLOCK_MUL;
}
// Check for DosBox OPL dump
else if (((DWORD *)scoredata)[0] == MAKE_ID('D','B','R','A') &&
((DWORD *)scoredata)[1] == MAKE_ID('W','O','P','L') &&
((DWORD *)scoredata)[2] == MAKE_ID(0,0,1,0))
{
RawPlayer = DosBox;
SamplesPerTick = OPL_SAMPLE_RATE / 1000;
ScoreLen = MIN<int>(len - 24, LittleLong(((DWORD *)scoredata)[4]));
}
// Check for modified IMF format (includes a header)
else if (((DWORD *)scoredata)[0] == MAKE_ID('A','D','L','I') &&
scoredata[4] == 'B' && scoredata[5] == 1)
{
int songlen;
BYTE *max = scoredata + ScoreLen;
RawPlayer = IMF;
SamplesPerTick = OPL_SAMPLE_RATE / IMF_RATE;
score = scoredata + 6;
// Skip track and game name
for (int i = 2; i != 0; --i)
{
while (score < max && *score++ != '\0') {}
}
if (score < max) score++; // Skip unknown byte
if (score + 8 > max)
{ // Not enough room left for song data
delete[] scoredata;
scoredata = NULL;
return;
}
songlen = LittleLong(*(DWORD *)score);
if (songlen != 0 && (songlen +=4) < ScoreLen - (score - scoredata))
{
ScoreLen = songlen + int(score - scoredata);
}
}
Restart ();
}
OPLmusicFile::~OPLmusicFile ()
{
if (scoredata != NULL)
{
io->OPLdeinit ();
delete[] scoredata;
scoredata = NULL;
}
}
bool OPLmusicFile::IsValid () const
{
return scoredata != NULL;
}
void OPLmusicFile::SetLooping (bool loop)
{
Looping = loop;
}
void OPLmusicFile::Restart ()
{
OPLmusicBlock::Restart();
WhichChip = 0;
if (RawPlayer == RDosPlay)
{
score = scoredata + 10;
SamplesPerTick = LittleShort(*(WORD *)(scoredata + 8)) / ADLIB_CLOCK_MUL;
}
else if (RawPlayer == DosBox)
{
score = scoredata + 24;
SamplesPerTick = OPL_SAMPLE_RATE / 1000;
}
else if (RawPlayer == IMF)
{
score = scoredata + 6;
// Skip track and game name
for (int i = 2; i != 0; --i)
{
while (*score++ != '\0') {}
}
score++; // Skip unknown byte
if (*(DWORD *)score != 0)
{
score += 4; // Skip song length
}
}
io->SetClockRate(SamplesPerTick);
}
bool OPLmusicBlock::ServiceStream (void *buff, int numbytes)
{
float *samples = (float *)buff;
float *samples1;
int numsamples = numbytes / sizeof(float);
bool prevEnded = false;
bool res = true;
samples1 = samples;
memset(buff, 0, numbytes);
ChipAccess.Enter();
while (numsamples > 0)
{
double ticky = NextTickIn;
int tick_in = int(NextTickIn);
int samplesleft = MIN(numsamples, tick_in);
if (samplesleft > 0)
{
YM3812UpdateOne (0, samples1, samplesleft);
if (TwoChips)
{
YM3812UpdateOne (1, samples1, samplesleft);
}
assert(NextTickIn == ticky);
NextTickIn -= samplesleft;
assert (NextTickIn >= 0);
numsamples -= samplesleft;
samples1 += samplesleft;
}
if (NextTickIn < 1)
{
int next = PlayTick();
assert(next >= 0);
if (next == 0)
{ // end of song
if (!Looping || prevEnded)
{
if (numsamples > 0)
{
YM3812UpdateOne (0, samples1, numsamples);
if (TwoChips)
{
YM3812UpdateOne (1, samples1, numsamples);
}
}
res = false;
break;
}
else
{
// Avoid infinite loops from songs that do nothing but end
prevEnded = true;
Restart ();
}
}
else
{
prevEnded = false;
io->WriteDelay(next);
NextTickIn += SamplesPerTick * next;
assert (NextTickIn >= 0);
MLtime += next;
}
}
}
ChipAccess.Leave();
return res;
}
int OPLmusicFile::PlayTick ()
{
BYTE reg, data;
if (RawPlayer == RDosPlay)
{
while (score < scoredata + ScoreLen)
{
data = *score++;
reg = *score++;
switch (reg)
{
case 0: // Delay
if (data != 0)
{
return data;
}
break;
case 2: // Speed change or OPL3 switch
if (data == 0)
{
SamplesPerTick = LittleShort(*(WORD *)(score)) / ADLIB_CLOCK_MUL;
io->SetClockRate(SamplesPerTick);
score += 2;
}
else if (data == 1)
{
WhichChip = 0;
}
else if (data == 2)
{
WhichChip = 1;
}
break;
case 0xFF: // End of song
if (data == 0xFF)
{
return 0;
}
break;
default: // It's something to stuff into the OPL chip
if (WhichChip == 0 || TwoChips)
{
io->OPLwriteReg(WhichChip, reg, data);
}
break;
}
}
}
else if (RawPlayer == DosBox)
{
while (score < scoredata + ScoreLen)
{
reg = *score++;
if (reg == 4)
{
reg = *score++;
data = *score++;
}
else if (reg == 0)
{ // One-byte delay
return *score++ + 1;
}
else if (reg == 1)
{ // Two-byte delay
int delay = score[0] + (score[1] << 8) + 1;
score += 2;
return delay;
}
else if (reg == 2)
{ // Select OPL chip 0
WhichChip = 0;
continue;
}
else if (reg == 3)
{ // Select OPL chip 1
WhichChip = 1;
continue;
}
else
{
data = *score++;
}
if (WhichChip == 0 || TwoChips)
{
io->OPLwriteReg(WhichChip, reg, data);
}
}
}
else if (RawPlayer == IMF)
{
WORD delay = 0;
while (delay == 0 && score + 4 - scoredata <= ScoreLen)
{
if (*(DWORD *)score == 0xFFFFFFFF)
{ // This is a special value that means to end the song.
return 0;
}
reg = score[0];
data = score[1];
delay = LittleShort(((WORD *)score)[1]);
score += 4;
io->OPLwriteReg (0, reg, data);
}
return delay;
}
return 0;
}
ADD_STAT (opl)
{
return YM3812GetVoiceString ();
}
OPLmusicFile::OPLmusicFile(const OPLmusicFile *source, const char *filename)
{
ScoreLen = source->ScoreLen;
scoredata = new BYTE[ScoreLen];
memcpy(scoredata, source->scoredata, ScoreLen);
SamplesPerTick = source->SamplesPerTick;
RawPlayer = source->RawPlayer;
score = source->score;
TwoChips = source->TwoChips;
WhichChip = 0;
if (io != NULL)
{
delete io;
}
io = new DiskWriterIO(filename);
io->OPLinit(TwoChips);
Restart();
}
void OPLmusicFile::Dump()
{
int time;
time = PlayTick();
while (time != 0)
{
io->WriteDelay(time);
time = PlayTick();
}
}