gzdoom/src/oplsynth/mlopl.cpp
2016-03-01 09:47:10 -06:00

485 lines
12 KiB
C++

/*
* Name: OPL2/OPL3 Music driver
* Project: MUS File Player Library
* Version: 1.67
* Author: Vladimir Arnost (QA-Software)
* Last revision: May-2-1996
* Compiler: Borland C++ 3.1, Watcom C/C++ 10.0
*
*/
/*
* Revision History:
*
* Aug-8-1994 V1.00 V.Arnost
* Written from scratch
* Aug-9-1994 V1.10 V.Arnost
* Some minor changes to improve sound quality. Tried to add
* stereo sound capabilities, but failed to -- my SB Pro refuses
* to switch to stereo mode.
* Aug-13-1994 V1.20 V.Arnost
* Stereo sound fixed. Now works also with Sound Blaster Pro II
* (chip OPL3 -- gives 18 "stereo" (ahem) channels).
* Changed code to handle properly notes without volume.
* (Uses previous volume on given channel.)
* Added cyclic channel usage to avoid annoying clicking noise.
* Aug-28-1994 V1.40 V.Arnost
* Added Adlib and SB Pro II detection.
* Apr-16-1995 V1.60 V.Arnost
* Moved into separate source file MUSLIB.C
* Jul-12-1995 V1.62 V.Arnost
* Module created and source code copied from MUSLIB.C
* Aug-08-1995 V1.63 V.Arnost
* Modified to follow changes in MLOPL_IO.C
* Aug-13-1995 V1.64 V.Arnost
* Added OPLsendMIDI() function
* Sep-8-1995 V1.65 V.Arnost
* Added sustain pedal support.
* Improved pause/unpause functions. Now all notes are made
* silent (even released notes, which haven't sounded off yet).
* Mar-1-1996 V1.66 V.Arnost
* Cleaned up the source
* May-2-1996 V1.67 V.Arnost
* Added modulation wheel (vibrato) support
*/
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <io.h>
#endif
#include "muslib.h"
#include "files.h"
#include "c_cvars.h"
#define MOD_MIN 40 /* vibrato threshold */
//#define HIGHEST_NOTE 102
#define HIGHEST_NOTE 127
musicBlock::musicBlock ()
{
memset (this, 0, sizeof(*this));
}
musicBlock::~musicBlock ()
{
if (OPLinstruments != NULL) free(OPLinstruments);
}
void musicBlock::writeFrequency(uint slot, uint note, int pitch, uint keyOn)
{
io->OPLwriteFreq (slot, note, pitch, keyOn);
}
void musicBlock::writeModulation(uint slot, struct OPL2instrument *instr, int state)
{
if (state)
state = 0x40; /* enable Frequency Vibrato */
io->OPLwriteChannel(0x20, slot,
(instr->feedback & 1) ? (instr->trem_vibr_1 | state) : instr->trem_vibr_1,
instr->trem_vibr_2 | state);
}
uint musicBlock::calcVolume(uint channelVolume, uint channelExpression, uint noteVolume)
{
noteVolume = ((ulong)channelVolume * channelExpression * noteVolume) / (127*127);
if (noteVolume > 127)
return 127;
else
return noteVolume;
}
int musicBlock::occupyChannel(uint slot, uint channel,
int note, int volume, struct OP2instrEntry *instrument, uchar secondary)
{
struct OPL2instrument *instr;
struct channelEntry *ch = &channels[slot];
ch->channel = channel;
ch->note = note;
ch->flags = secondary ? CH_SECONDARY : 0;
if (driverdata.channelModulation[channel] >= MOD_MIN)
ch->flags |= CH_VIBRATO;
ch->time = MLtime;
if (volume == -1)
volume = driverdata.channelLastVolume[channel];
else
driverdata.channelLastVolume[channel] = volume;
ch->realvolume = calcVolume(driverdata.channelVolume[channel],
driverdata.channelExpression[channel], ch->volume = volume);
if (instrument->flags & FL_FIXED_PITCH)
note = instrument->note;
else if (channel == PERCUSSION)
note = 60; // C-5
if (secondary && (instrument->flags & FL_DOUBLE_VOICE))
ch->finetune = (instrument->finetune - 0x80) >> 1;
else
ch->finetune = 0;
ch->pitch = ch->finetune + driverdata.channelPitch[channel];
if (secondary)
instr = &instrument->instr[1];
else
instr = &instrument->instr[0];
ch->instr = instr;
if (channel != PERCUSSION && !(instrument->flags & FL_FIXED_PITCH))
{
if ( (note += instr->basenote) < 0)
while ((note += 12) < 0) {}
else if (note > HIGHEST_NOTE)
while ((note -= 12) > HIGHEST_NOTE) {}
}
ch->realnote = note;
io->OPLwriteInstrument(slot, instr);
if (ch->flags & CH_VIBRATO)
writeModulation(slot, instr, 1);
io->OPLwritePan(slot, instr, driverdata.channelPan[channel]);
io->OPLwriteVolume(slot, instr, ch->realvolume);
writeFrequency(slot, note, ch->pitch, 1);
return slot;
}
int musicBlock::releaseChannel(uint slot, uint killed)
{
struct channelEntry *ch = &channels[slot];
writeFrequency(slot, ch->realnote, ch->pitch, 0);
ch->channel |= CH_FREE;
ch->time = MLtime;
ch->flags = CH_FREE;
if (killed)
{
io->OPLwriteChannel(0x80, slot, 0x0F, 0x0F); // release rate - fastest
io->OPLwriteChannel(0x40, slot, 0x3F, 0x3F); // no volume
}
return slot;
}
int musicBlock::releaseSustain(uint channel)
{
uint i;
uint id = channel;
for(i = 0; i < io->OPLchannels; i++)
{
if (channels[i].channel == id && channels[i].flags & CH_SUSTAIN)
releaseChannel(i, 0);
}
return 0;
}
int musicBlock::findFreeChannel(uint flag, uint channel, uchar note)
{
uint i;
ulong bestfit = 0;
uint bestvoice = 0;
for (i = 0; i < io->OPLchannels; ++i)
{
ulong magic;
magic = ((channels[i].flags & CH_FREE) << 24) |
((channels[i].note == note &&
channels[i].channel == channel) << 30) |
((channels[i].flags & CH_SUSTAIN) << 28) |
((MLtime - channels[i].time) & 0x1fffffff);
if (magic > bestfit)
{
bestfit = magic;
bestvoice = i;
}
}
if ((flag & 1) && !(bestfit & 0x80000000))
{ // No free channels good enough
return -1;
}
releaseChannel (bestvoice, 1);
return bestvoice;
}
struct OP2instrEntry *musicBlock::getInstrument(uint channel, uchar note)
{
uint instrnumber;
if (channel == PERCUSSION)
{
if (note < 35 || note > 81)
return NULL; /* wrong percussion number */
instrnumber = note + (128-35);
}
else
{
instrnumber = driverdata.channelInstr[channel];
}
if (OPLinstruments)
return &OPLinstruments[instrnumber];
else
return NULL;
}
// code 1: play note
CVAR (Bool, opl_singlevoice, 0, 0)
void musicBlock::OPLplayNote(uint channel, uchar note, int volume)
{
int i;
struct OP2instrEntry *instr;
if (volume == 0)
{
OPLreleaseNote (channel, note);
return;
}
if ( (instr = getInstrument(channel, note)) == NULL )
return;
if ( (i = findFreeChannel((channel == PERCUSSION) ? 2 : 0, channel, note)) != -1)
{
occupyChannel(i, channel, note, volume, instr, 0);
if ((instr->flags & FL_DOUBLE_VOICE) && !opl_singlevoice)
{
if ( (i = findFreeChannel((channel == PERCUSSION) ? 3 : 1, channel, note)) != -1)
occupyChannel(i, channel, note, volume, instr, 1);
}
}
}
// code 0: release note
void musicBlock::OPLreleaseNote(uint channel, uchar note)
{
uint i;
uint id = channel;
uint sustain = driverdata.channelSustain[channel];
for(i = 0; i < io->OPLchannels; i++)
{
if (channels[i].channel == id && channels[i].note == note)
{
if (sustain < 0x40)
releaseChannel(i, 0);
else
channels[i].flags |= CH_SUSTAIN;
}
}
}
// code 2: change pitch wheel (bender)
void musicBlock::OPLpitchWheel(uint channel, int pitch)
{
uint i;
uint id = channel;
// Convert pitch from 14-bit to 7-bit, then scale it, since the player
// code only understands sensitivities of 2 semitones.
pitch = (pitch - 8192) * driverdata.channelPitchSens[channel] / (200 * 128) + 64;
driverdata.channelPitch[channel] = pitch;
for(i = 0; i < io->OPLchannels; i++)
{
struct channelEntry *ch = &channels[i];
if (ch->channel == id)
{
ch->time = MLtime;
ch->pitch = ch->finetune + pitch;
writeFrequency(i, ch->realnote, ch->pitch, 1);
}
}
}
// code 4: change control
void musicBlock::OPLchangeControl(uint channel, uchar controller, int value)
{
uint i;
uint id = channel;
switch (controller)
{
case ctrlPatch: /* change instrument */
OPLprogramChange(channel, value);
break;
case ctrlModulation:
driverdata.channelModulation[channel] = value;
for(i = 0; i < io->OPLchannels; i++)
{
struct channelEntry *ch = &channels[i];
if (ch->channel == id)
{
uchar flags = ch->flags;
ch->time = MLtime;
if (value >= MOD_MIN)
{
ch->flags |= CH_VIBRATO;
if (ch->flags != flags)
writeModulation(i, ch->instr, 1);
} else {
ch->flags &= ~CH_VIBRATO;
if (ch->flags != flags)
writeModulation(i, ch->instr, 0);
}
}
}
break;
case ctrlVolume: /* change volume */
driverdata.channelVolume[channel] = value;
/* fall-through */
case ctrlExpression: /* change expression */
if (controller == ctrlExpression)
{
driverdata.channelExpression[channel] = value;
}
for(i = 0; i < io->OPLchannels; i++)
{
struct channelEntry *ch = &channels[i];
if (ch->channel == id)
{
ch->time = MLtime;
ch->realvolume = calcVolume(driverdata.channelVolume[channel],
driverdata.channelExpression[channel], ch->volume);
io->OPLwriteVolume(i, ch->instr, ch->realvolume);
}
}
break;
case ctrlPan: /* change pan (balance) */
driverdata.channelPan[channel] = value -= 64;
for(i = 0; i < io->OPLchannels; i++)
{
struct channelEntry *ch = &channels[i];
if (ch->channel == id)
{
ch->time = MLtime;
io->OPLwritePan(i, ch->instr, value);
}
}
break;
case ctrlSustainPedal: /* change sustain pedal (hold) */
driverdata.channelSustain[channel] = value;
if (value < 0x40)
releaseSustain(channel);
break;
case ctrlNotesOff: /* turn off all notes that are not sustained */
for (i = 0; i < io->OPLchannels; ++i)
{
if (channels[i].channel == id)
{
if (driverdata.channelSustain[id] < 0x40)
releaseChannel(i, 0);
else
channels[i].flags |= CH_SUSTAIN;
}
}
break;
case ctrlSoundsOff: /* release all notes for this channel */
for (i = 0; i < io->OPLchannels; ++i)
{
if (channels[i].channel == id)
{
releaseChannel(i, 0);
}
}
break;
case ctrlRPNHi:
driverdata.channelRPN[id] = (driverdata.channelRPN[id] & 0x007F) | (value << 7);
break;
case ctrlRPNLo:
driverdata.channelRPN[id] = (driverdata.channelRPN[id] & 0x3F80) | value;
break;
case ctrlNRPNLo:
case ctrlNRPNHi:
driverdata.channelRPN[id] = 0x3FFF;
break;
case ctrlDataEntryHi:
if (driverdata.channelRPN[id] == 0)
{
driverdata.channelPitchSens[id] = value * 100 + (driverdata.channelPitchSens[id] % 100);
}
break;
case ctrlDataEntryLo:
if (driverdata.channelRPN[id] == 0)
{
driverdata.channelPitchSens[id] = value + (driverdata.channelPitchSens[id] / 100) * 100;
}
break;
}
}
void musicBlock::OPLresetControllers(uint chan, int vol)
{
driverdata.channelVolume[chan] = vol;
driverdata.channelExpression[chan] = 127;
driverdata.channelSustain[chan] = 0;
driverdata.channelLastVolume[chan] = 64;
driverdata.channelPitch[chan] = 64;
driverdata.channelRPN[chan] = 0x3fff;
driverdata.channelPitchSens[chan] = 200;
}
void musicBlock::OPLprogramChange(uint channel, int value)
{
driverdata.channelInstr[channel] = value;
}
void musicBlock::OPLplayMusic(int vol)
{
uint i;
for (i = 0; i < CHANNELS; i++)
{
OPLresetControllers(i, vol);
}
}
void musicBlock::OPLstopMusic()
{
uint i;
for(i = 0; i < io->OPLchannels; i++)
if (!(channels[i].flags & CH_FREE))
releaseChannel(i, 1);
}
int musicBlock::OPLloadBank (FileReader &data)
{
static const uchar masterhdr[8] = { '#','O','P','L','_','I','I','#' };
struct OP2instrEntry *instruments;
uchar filehdr[8];
data.Read (filehdr, 8);
if (memcmp(filehdr, masterhdr, 8))
return -2; /* bad instrument file */
if ( (instruments = (struct OP2instrEntry *)calloc(OP2INSTRCOUNT, OP2INSTRSIZE)) == NULL)
return -3; /* not enough memory */
data.Read (instruments, OP2INSTRSIZE * OP2INSTRCOUNT);
if (OPLinstruments != NULL)
{
free(OPLinstruments);
}
OPLinstruments = instruments;
#if 0
for (int i = 0; i < 175; ++i)
{
Printf ("%3d.%-33s%3d %3d %3d %d\n", i,
(BYTE *)data+6308+i*32,
OPLinstruments[i].instr[0].basenote,
OPLinstruments[i].instr[1].basenote,
OPLinstruments[i].note,
OPLinstruments[i].flags);
}
#endif
return 0;
}