- rewrote the OPL middle layer to remove the MusLib code.

The new version keeps the non-MusLib code of both files and replaces most of the rest with code from Chocolate Doom.
This commit is contained in:
Christoph Oelckers 2017-04-16 01:36:53 +02:00
parent 4f67dc4f01
commit ba5721f98a
18 changed files with 1233 additions and 1196 deletions

View file

@ -1256,8 +1256,8 @@ set (PCH_SOURCES
sound/musicformats/music_opl.cpp
sound/musicformats/music_stream.cpp
sound/oplsynth/fmopl.cpp
sound/oplsynth/mlopl.cpp
sound/oplsynth/mlopl_io.cpp
sound/oplsynth/musicblock.cpp
sound/oplsynth/oplio.cpp
sound/oplsynth/dosbox/opl.cpp
sound/oplsynth/OPL3.cpp
sound/oplsynth/nukedopl3.cpp

View file

@ -83,7 +83,11 @@ OPLMIDIDevice::OPLMIDIDevice(const char *args)
OPL_SetCore(args);
FullPan = opl_fullpan;
FWadLump data = Wads.OpenLumpName("GENMIDI");
OPLloadBank(data);
uint8_t filehdr[8];
data.Read(filehdr, 8);
if (memcmp(filehdr, "#OPL_II#", 8)) I_Error("Corrupt GENMIDI lump");
data.Read(OPLinstruments, sizeof(genmidi_instr_t) * GENMIDI_NUM_TOTAL);
SampleRate = (int)OPL_SAMPLE_RATE;
}
@ -97,15 +101,15 @@ OPLMIDIDevice::OPLMIDIDevice(const char *args)
int OPLMIDIDevice::Open(MidiCallback callback, void *userdata)
{
if (io == NULL || 0 == (NumChips = io->OPLinit(opl_numchips, FullPan, true)))
if (io == NULL || 0 == (NumChips = io->Init(opl_numchips, FullPan, true)))
{
return 1;
}
int ret = OpenStream(14, (FullPan || io->IsOPL3) ? 0 : SoundStream::Mono, callback, userdata);
if (ret == 0)
{
OPLstopMusic();
OPLplayMusic(100);
stopAllVoices();
resetAllControllers(100);
DEBUGOUT("========= New song started ==========\n", 0, 0, 0);
}
return ret;
@ -120,7 +124,7 @@ int OPLMIDIDevice::Open(MidiCallback callback, void *userdata)
void OPLMIDIDevice::Close()
{
SoftSynthMIDIDevice::Close();
io->OPLdeinit();
io->Reset();
}
//==========================================================================
@ -176,7 +180,7 @@ void OPLMIDIDevice::HandleEvent(int status, int parm1, int parm2)
int command = status & 0xF0;
int channel = status & 0x0F;
// Swap channels 9 and 15, because their roles are reversed
// Swap voices 9 and 15, because their roles are reversed
// in MUS and MIDI formats.
if (channel == 9)
{
@ -191,12 +195,12 @@ void OPLMIDIDevice::HandleEvent(int status, int parm1, int parm2)
{
case MIDI_NOTEOFF:
playingcount--;
OPLreleaseNote(channel, parm1);
noteOff(channel, parm1);
break;
case MIDI_NOTEON:
playingcount++;
OPLplayNote(channel, parm1, parm2);
noteOn(channel, parm1, parm2);
break;
case MIDI_POLYPRESS:
@ -206,26 +210,27 @@ void OPLMIDIDevice::HandleEvent(int status, int parm1, int parm2)
case MIDI_CTRLCHANGE:
switch (parm1)
{
case 0: OPLchangeControl(channel, ctrlBank, parm2); break;
case 1: OPLchangeControl(channel, ctrlModulation, parm2); break;
case 6: OPLchangeControl(channel, ctrlDataEntryHi, parm2); break;
case 7: OPLchangeControl(channel, ctrlVolume, parm2); break;
case 10: OPLchangeControl(channel, ctrlPan, parm2); break;
case 11: OPLchangeControl(channel, ctrlExpression, parm2); break;
case 38: OPLchangeControl(channel, ctrlDataEntryLo, parm2); break;
case 64: OPLchangeControl(channel, ctrlSustainPedal, parm2); break;
case 67: OPLchangeControl(channel, ctrlSoftPedal, parm2); break;
case 91: OPLchangeControl(channel, ctrlReverb, parm2); break;
case 93: OPLchangeControl(channel, ctrlChorus, parm2); break;
case 98: OPLchangeControl(channel, ctrlNRPNLo, parm2); break;
case 99: OPLchangeControl(channel, ctrlNRPNHi, parm2); break;
case 100: OPLchangeControl(channel, ctrlRPNLo, parm2); break;
case 101: OPLchangeControl(channel, ctrlRPNHi, parm2); break;
case 120: OPLchangeControl(channel, ctrlSoundsOff, parm2); break;
case 121: OPLresetControllers(channel, 100); break;
case 123: OPLchangeControl(channel, ctrlNotesOff, parm2); break;
case 126: OPLchangeControl(channel, ctrlMono, parm2); break;
case 127: OPLchangeControl(channel, ctrlPoly, parm2); break;
// some controllers here get passed on but are not handled by the player.
//case 0: changeBank(channel, parm2); break;
case 1: changeModulation(channel, parm2); break;
case 6: changeExtended(channel, ctrlDataEntryHi, parm2); break;
case 7: changeVolume(channel, parm2, false); break;
case 10: changePanning(channel, parm2); break;
case 11: changeVolume(channel, parm2, true); break;
case 38: changeExtended(channel, ctrlDataEntryLo, parm2); break;
case 64: changeSustain(channel, parm2); break;
//case 67: changeSoftPedal(channel, parm2); break;
//case 91: changeReverb(channel, parm2); break;
//case 93: changeChorus(channel, parm2); break;
case 98: changeExtended(channel, ctrlNRPNLo, parm2); break;
case 99: changeExtended(channel, ctrlNRPNHi, parm2); break;
case 100: changeExtended(channel, ctrlRPNLo, parm2); break;
case 101: changeExtended(channel, ctrlRPNHi, parm2); break;
case 120: allNotesOff(channel, parm2); break;
case 121: resetControllers(channel, 100); break;
case 123: notesOff(channel, parm2); break;
//case 126: changeMono(channel, parm2); break;
//case 127: changePoly(channel, parm2); break;
default:
DEBUGOUT("Unhandled controller: Channel %d, controller %d, value %d\n", channel, parm1, parm2);
break;
@ -233,7 +238,7 @@ void OPLMIDIDevice::HandleEvent(int status, int parm1, int parm2)
break;
case MIDI_PRGMCHANGE:
OPLprogramChange(channel, parm1);
programChange(channel, parm1);
break;
case MIDI_CHANPRESS:
@ -241,7 +246,7 @@ void OPLMIDIDevice::HandleEvent(int status, int parm1, int parm2)
break;
case MIDI_PITCHBEND:
OPLpitchWheel(channel, parm1 | (parm2 << 7));
changePitch(channel, parm1, parm2);
break;
}
}
@ -289,17 +294,17 @@ FString OPLMIDIDevice::GetStats()
{
FString out;
char star[3] = { TEXTCOLOR_ESCAPE, 'A', '*' };
for (uint32_t i = 0; i < io->OPLchannels; ++i)
for (uint32_t i = 0; i < io->NumChannels; ++i)
{
if (channels[i].flags & CH_FREE)
if (voices[i].index == -1)
{
star[1] = CR_BRICK + 'A';
}
else if (channels[i].flags & CH_SUSTAIN)
else if (voices[i].sustained)
{
star[1] = CR_ORANGE + 'A';
}
else if (channels[i].flags & CH_SECONDARY)
else if (voices[i].current_instr_voice == &voices[i].current_instr->voices[1])
{
star[1] = CR_BLUE + 'A';
}

View file

@ -327,16 +327,16 @@ DiskWriterIO::DiskWriterIO(const char *filename)
DiskWriterIO::~DiskWriterIO()
{
OPLdeinit();
Reset();
}
//==========================================================================
//
// DiskWriterIO :: OPLinit
// DiskWriterIO :: Init
//
//==========================================================================
int DiskWriterIO::OPLinit(uint32_t numchips, bool, bool initopl3)
int DiskWriterIO::Init(uint32_t numchips, bool, bool initopl3)
{
FILE *file = fopen(Filename, "wb");
if (file == NULL)
@ -357,10 +357,10 @@ int DiskWriterIO::OPLinit(uint32_t numchips, bool, bool initopl3)
{
chips[0] = new OPL_DOSBOXdump(file, numchips > 1);
}
OPLchannels = OPL2CHANNELS * numchips;
NumChannels = OPL_NUM_VOICES * numchips;
NumChips = numchips;
IsOPL3 = numchips > 1;
OPLwriteInitState(initopl3);
WriteInitState(initopl3);
return numchips;
}

View file

@ -32,7 +32,7 @@
*/
#include "i_musicinterns.h"
#include "oplsynth/muslib.h"
#include "oplsynth/musicblock.h"
#include "oplsynth/opl.h"
static bool OPL_Active;

View file

@ -26,7 +26,7 @@
#include "doomtype.h"
#include "../opl.h"
#include "../muslib.h"
#include "../musicblock.h"
#include <math.h>
#include "m_random.h"

View file

@ -1311,7 +1311,7 @@ static inline void set_sl_rr(FM_OPL *OPL,int slot,int v)
/* write a value v to register r on OPL chip */
static void OPLWriteReg(FM_OPL *OPL, int r, int v)
static void WriteRegister(FM_OPL *OPL, int r, int v)
{
OPL_CH *CH;
int slot;
@ -1523,11 +1523,11 @@ static void OPLResetChip(FM_OPL *OPL)
OPL_STATUS_RESET(OPL,0x7f);
/* reset with register write */
OPLWriteReg(OPL,0x01,0); /* wavesel disable */
OPLWriteReg(OPL,0x02,0); /* Timer1 */
OPLWriteReg(OPL,0x03,0); /* Timer2 */
OPLWriteReg(OPL,0x04,0); /* IRQ mask clear */
for(i = 0xff ; i >= 0x20 ; i-- ) OPLWriteReg(OPL,i,0);
WriteRegister(OPL,0x01,0); /* wavesel disable */
WriteRegister(OPL,0x02,0); /* Timer1 */
WriteRegister(OPL,0x03,0); /* Timer2 */
WriteRegister(OPL,0x04,0); /* IRQ mask clear */
for(i = 0xff ; i >= 0x20 ; i-- ) WriteRegister(OPL,i,0);
/* reset operator parameters */
for( c = 0 ; c < 9 ; c++ )
@ -1569,7 +1569,7 @@ public:
/* YM3812 I/O interface */
void WriteReg(int reg, int v)
{
OPLWriteReg(&Chip, reg & 0xff, v);
WriteRegister(&Chip, reg & 0xff, v);
}
void Reset()

View file

@ -0,0 +1,47 @@
#include "doomtype.h"
#pragma pack(push, 1)
struct genmidi_op_t
{
uint8_t tremolo;
uint8_t attack;
uint8_t sustain;
uint8_t waveform;
uint8_t scale;
uint8_t level;
} FORCE_PACKED;
struct genmidi_voice_t
{
genmidi_op_t modulator;
uint8_t feedback;
genmidi_op_t carrier;
uint8_t unused;
int16_t base_note_offset;
} FORCE_PACKED;
struct genmidi_instr_t
{
uint16_t flags;
uint8_t fine_tuning;
uint8_t fixed_note;
genmidi_voice_t voices[2];
} FORCE_PACKED;
#pragma pack(pop)
enum
{
GENMIDI_FLAG_FIXED = 0x0001, /* fixed pitch */
GENMIDI_FLAG_2VOICE = 0x0004 /* double voice (OPL3) */
};
enum
{
GENMIDI_NUM_INSTRS = 128,
GENMIDI_FIST_PERCUSSION = 35,
GENMIDI_NUM_PERCUSSION = 47,
GENMIDI_NUM_TOTAL = GENMIDI_NUM_INSTRS + GENMIDI_NUM_PERCUSSION
};

View file

@ -1,485 +0,0 @@
/*
* 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(uint32_t slot, uint32_t note, int pitch, uint32_t keyOn)
{
io->OPLwriteFreq (slot, note, pitch, keyOn);
}
void musicBlock::writeModulation(uint32_t 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);
}
uint32_t musicBlock::calcVolume(uint32_t channelVolume, uint32_t channelExpression, uint32_t noteVolume)
{
noteVolume = ((uint64_t)channelVolume * channelExpression * noteVolume) / (127*127);
if (noteVolume > 127)
return 127;
else
return noteVolume;
}
int musicBlock::occupyChannel(uint32_t slot, uint32_t channel,
int note, int volume, struct OP2instrEntry *instrument, uint8_t 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(uint32_t slot, uint32_t 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(uint32_t channel)
{
uint32_t i;
uint32_t 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(uint32_t flag, uint32_t channel, uint8_t note)
{
uint32_t i;
uint32_t bestfit = 0;
uint32_t bestvoice = 0;
for (i = 0; i < io->OPLchannels; ++i)
{
uint32_t 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(uint32_t channel, uint8_t note)
{
uint32_t 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(uint32_t channel, uint8_t 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(uint32_t channel, uint8_t note)
{
uint32_t i;
uint32_t id = channel;
uint32_t 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(uint32_t channel, int pitch)
{
uint32_t i;
uint32_t 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(uint32_t channel, uint8_t controller, int value)
{
uint32_t i;
uint32_t 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)
{
uint8_t 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(uint32_t 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(uint32_t channel, int value)
{
driverdata.channelInstr[channel] = value;
}
void musicBlock::OPLplayMusic(int vol)
{
uint32_t i;
for (i = 0; i < CHANNELS; i++)
{
OPLresetControllers(i, vol);
}
}
void musicBlock::OPLstopMusic()
{
uint32_t i;
for(i = 0; i < io->OPLchannels; i++)
if (!(channels[i].flags & CH_FREE))
releaseChannel(i, 1);
}
int musicBlock::OPLloadBank (FileReader &data)
{
static const uint8_t masterhdr[8] = { '#','O','P','L','_','I','I','#' };
struct OP2instrEntry *instruments;
uint8_t 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,
(uint8_t *)data+6308+i*32,
OPLinstruments[i].instr[0].basenote,
OPLinstruments[i].instr[1].basenote,
OPLinstruments[i].note,
OPLinstruments[i].flags);
}
#endif
return 0;
}

View file

@ -1,380 +0,0 @@
/*
* Name: Low-level OPL2/OPL3 I/O interface
* Project: MUS File Player Library
* Version: 1.64
* Author: Vladimir Arnost (QA-Software)
* Last revision: Mar-1-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
* Added stereo capabilities
* Aug-13-1994 V1.20 V.Arnost
* Stereo capabilities made functional
* Aug-24-1994 V1.30 V.Arnost
* Added Adlib and SB Pro II detection
* Oct-30-1994 V1.40 V.Arnost
* Added BLASTER variable parsing
* Apr-14-1995 V1.50 V.Arnost
* Some declarations moved from adlib.h to doomtype.h
* Jul-22-1995 V1.60 V.Arnost
* Ported to Watcom C
* Simplified WriteChannel() and WriteValue()
* Jul-24-1995 V1.61 V.Arnost
* DetectBlaster() moved to MLMISC.C
* Aug-8-1995 V1.62 V.Arnost
* Module renamed to MLOPL_IO.C and functions renamed to OPLxxx
* Mixer-related functions moved to module MLSBMIX.C
* Sep-8-1995 V1.63 V.Arnost
* OPLwriteReg() routine sped up on OPL3 cards
* Mar-1-1996 V1.64 V.Arnost
* Cleaned up the source
*/
#include <math.h>
#ifdef _WIN32
#include <dos.h>
#include <conio.h>
#endif
#include "muslib.h"
#include "opl.h"
#include "c_cvars.h"
const double HALF_PI = (M_PI*0.5);
EXTERN_CVAR(Int, opl_core)
extern int current_opl_core;
OPLio::~OPLio()
{
}
void OPLio::SetClockRate(double samples_per_tick)
{
}
void OPLio::WriteDelay(int ticks)
{
}
void OPLio::OPLwriteReg(int which, uint32_t reg, uint8_t data)
{
if (IsOPL3)
{
reg |= (which & 1) << 8;
which >>= 1;
}
if (chips[which] != NULL)
{
chips[which]->WriteReg(reg, data);
}
}
/*
* Write to an operator pair. To be used for register bases of 0x20, 0x40,
* 0x60, 0x80 and 0xE0.
*/
void OPLio::OPLwriteChannel(uint32_t regbase, uint32_t channel, uint8_t data1, uint8_t data2)
{
static const uint32_t op_num[OPL2CHANNELS] = {
0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12};
uint32_t which = channel / OPL2CHANNELS;
uint32_t reg = regbase + op_num[channel % OPL2CHANNELS];
OPLwriteReg (which, reg, data1);
OPLwriteReg (which, reg+3, data2);
}
/*
* Write to channel a single value. To be used for register bases of
* 0xA0, 0xB0 and 0xC0.
*/
void OPLio::OPLwriteValue(uint32_t regbase, uint32_t channel, uint8_t value)
{
uint32_t which = channel / OPL2CHANNELS;
uint32_t reg = regbase + (channel % OPL2CHANNELS);
OPLwriteReg (which, reg, value);
}
static uint16_t frequencies[] =
{
0x133, 0x133, 0x134, 0x134, 0x135, 0x136, 0x136, 0x137, 0x137, 0x138, 0x138, 0x139,
0x139, 0x13a, 0x13b, 0x13b, 0x13c, 0x13c, 0x13d, 0x13d, 0x13e, 0x13f, 0x13f, 0x140,
0x140, 0x141, 0x142, 0x142, 0x143, 0x143, 0x144, 0x144, 0x145, 0x146, 0x146, 0x147,
0x147, 0x148, 0x149, 0x149, 0x14a, 0x14a, 0x14b, 0x14c, 0x14c, 0x14d, 0x14d, 0x14e,
0x14f, 0x14f, 0x150, 0x150, 0x151, 0x152, 0x152, 0x153, 0x153, 0x154, 0x155, 0x155,
0x156, 0x157, 0x157, 0x158, 0x158, 0x159, 0x15a, 0x15a, 0x15b, 0x15b, 0x15c, 0x15d,
0x15d, 0x15e, 0x15f, 0x15f, 0x160, 0x161, 0x161, 0x162, 0x162, 0x163, 0x164, 0x164,
0x165, 0x166, 0x166, 0x167, 0x168, 0x168, 0x169, 0x16a, 0x16a, 0x16b, 0x16c, 0x16c,
0x16d, 0x16e, 0x16e, 0x16f, 0x170, 0x170, 0x171, 0x172, 0x172, 0x173, 0x174, 0x174,
0x175, 0x176, 0x176, 0x177, 0x178, 0x178, 0x179, 0x17a, 0x17a, 0x17b, 0x17c, 0x17c,
0x17d, 0x17e, 0x17e, 0x17f, 0x180, 0x181, 0x181, 0x182, 0x183, 0x183, 0x184, 0x185,
0x185, 0x186, 0x187, 0x188, 0x188, 0x189, 0x18a, 0x18a, 0x18b, 0x18c, 0x18d, 0x18d,
0x18e, 0x18f, 0x18f, 0x190, 0x191, 0x192, 0x192, 0x193, 0x194, 0x194, 0x195, 0x196,
0x197, 0x197, 0x198, 0x199, 0x19a, 0x19a, 0x19b, 0x19c, 0x19d, 0x19d, 0x19e, 0x19f,
0x1a0, 0x1a0, 0x1a1, 0x1a2, 0x1a3, 0x1a3, 0x1a4, 0x1a5, 0x1a6, 0x1a6, 0x1a7, 0x1a8,
0x1a9, 0x1a9, 0x1aa, 0x1ab, 0x1ac, 0x1ad, 0x1ad, 0x1ae, 0x1af, 0x1b0, 0x1b0, 0x1b1,
0x1b2, 0x1b3, 0x1b4, 0x1b4, 0x1b5, 0x1b6, 0x1b7, 0x1b8, 0x1b8, 0x1b9, 0x1ba, 0x1bb,
0x1bc, 0x1bc, 0x1bd, 0x1be, 0x1bf, 0x1c0, 0x1c0, 0x1c1, 0x1c2, 0x1c3, 0x1c4, 0x1c4,
0x1c5, 0x1c6, 0x1c7, 0x1c8, 0x1c9, 0x1c9, 0x1ca, 0x1cb, 0x1cc, 0x1cd, 0x1ce, 0x1ce,
0x1cf, 0x1d0, 0x1d1, 0x1d2, 0x1d3, 0x1d3, 0x1d4, 0x1d5, 0x1d6, 0x1d7, 0x1d8, 0x1d8,
0x1d9, 0x1da, 0x1db, 0x1dc, 0x1dd, 0x1de, 0x1de, 0x1df, 0x1e0, 0x1e1, 0x1e2, 0x1e3,
0x1e4, 0x1e5, 0x1e5, 0x1e6, 0x1e7, 0x1e8, 0x1e9, 0x1ea, 0x1eb, 0x1ec, 0x1ed, 0x1ed,
0x1ee, 0x1ef, 0x1f0, 0x1f1, 0x1f2, 0x1f3, 0x1f4, 0x1f5, 0x1f6, 0x1f6, 0x1f7, 0x1f8,
0x1f9, 0x1fa, 0x1fb, 0x1fc, 0x1fd, 0x1fe, 0x1ff, 0x200,
0x201, 0x201, 0x202, 0x203, 0x204, 0x205, 0x206, 0x207, 0x208, 0x209, 0x20a, 0x20b, 0x20c, 0x20d, 0x20e, 0x20f,
0x210, 0x210, 0x211, 0x212, 0x213, 0x214, 0x215, 0x216, 0x217, 0x218, 0x219, 0x21a, 0x21b, 0x21c, 0x21d, 0x21e,
0x21f, 0x220, 0x221, 0x222, 0x223, 0x224, 0x225, 0x226, 0x227, 0x228, 0x229, 0x22a, 0x22b, 0x22c, 0x22d, 0x22e,
0x22f, 0x230, 0x231, 0x232, 0x233, 0x234, 0x235, 0x236, 0x237, 0x238, 0x239, 0x23a, 0x23b, 0x23c, 0x23d, 0x23e,
0x23f, 0x240, 0x241, 0x242, 0x244, 0x245, 0x246, 0x247, 0x248, 0x249, 0x24a, 0x24b, 0x24c, 0x24d, 0x24e, 0x24f,
0x250, 0x251, 0x252, 0x253, 0x254, 0x256, 0x257, 0x258, 0x259, 0x25a, 0x25b, 0x25c, 0x25d, 0x25e, 0x25f, 0x260,
0x262, 0x263, 0x264, 0x265, 0x266, 0x267, 0x268, 0x269, 0x26a, 0x26c, 0x26d, 0x26e, 0x26f, 0x270, 0x271, 0x272,
0x273, 0x275, 0x276, 0x277, 0x278, 0x279, 0x27a, 0x27b, 0x27d, 0x27e, 0x27f, 0x280, 0x281, 0x282, 0x284, 0x285,
0x286, 0x287, 0x288, 0x289, 0x28b, 0x28c, 0x28d, 0x28e, 0x28f, 0x290, 0x292, 0x293, 0x294, 0x295, 0x296, 0x298,
0x299, 0x29a, 0x29b, 0x29c, 0x29e, 0x29f, 0x2a0, 0x2a1, 0x2a2, 0x2a4, 0x2a5, 0x2a6, 0x2a7, 0x2a9, 0x2aa, 0x2ab,
0x2ac, 0x2ae, 0x2af, 0x2b0, 0x2b1, 0x2b2, 0x2b4, 0x2b5, 0x2b6, 0x2b7, 0x2b9, 0x2ba, 0x2bb, 0x2bd, 0x2be, 0x2bf,
0x2c0, 0x2c2, 0x2c3, 0x2c4, 0x2c5, 0x2c7, 0x2c8, 0x2c9, 0x2cb, 0x2cc, 0x2cd, 0x2ce, 0x2d0, 0x2d1, 0x2d2, 0x2d4,
0x2d5, 0x2d6, 0x2d8, 0x2d9, 0x2da, 0x2dc, 0x2dd, 0x2de, 0x2e0, 0x2e1, 0x2e2, 0x2e4, 0x2e5, 0x2e6, 0x2e8, 0x2e9,
0x2ea, 0x2ec, 0x2ed, 0x2ee, 0x2f0, 0x2f1, 0x2f2, 0x2f4, 0x2f5, 0x2f6, 0x2f8, 0x2f9, 0x2fb, 0x2fc, 0x2fd, 0x2ff,
0x300, 0x302, 0x303, 0x304, 0x306, 0x307, 0x309, 0x30a, 0x30b, 0x30d, 0x30e, 0x310, 0x311, 0x312, 0x314, 0x315,
0x317, 0x318, 0x31a, 0x31b, 0x31c, 0x31e, 0x31f, 0x321, 0x322, 0x324, 0x325, 0x327, 0x328, 0x329, 0x32b, 0x32c,
0x32e, 0x32f, 0x331, 0x332, 0x334, 0x335, 0x337, 0x338, 0x33a, 0x33b, 0x33d, 0x33e, 0x340, 0x341, 0x343, 0x344,
0x346, 0x347, 0x349, 0x34a, 0x34c, 0x34d, 0x34f, 0x350, 0x352, 0x353, 0x355, 0x357, 0x358, 0x35a, 0x35b, 0x35d,
0x35e, 0x360, 0x361, 0x363, 0x365, 0x366, 0x368, 0x369, 0x36b, 0x36c, 0x36e, 0x370, 0x371, 0x373, 0x374, 0x376,
0x378, 0x379, 0x37b, 0x37c, 0x37e, 0x380, 0x381, 0x383, 0x384, 0x386, 0x388, 0x389, 0x38b, 0x38d, 0x38e, 0x390,
0x392, 0x393, 0x395, 0x397, 0x398, 0x39a, 0x39c, 0x39d, 0x39f, 0x3a1, 0x3a2, 0x3a4, 0x3a6, 0x3a7, 0x3a9, 0x3ab,
0x3ac, 0x3ae, 0x3b0, 0x3b1, 0x3b3, 0x3b5, 0x3b7, 0x3b8, 0x3ba, 0x3bc, 0x3bd, 0x3bf, 0x3c1, 0x3c3, 0x3c4, 0x3c6,
0x3c8, 0x3ca, 0x3cb, 0x3cd, 0x3cf, 0x3d1, 0x3d2, 0x3d4, 0x3d6, 0x3d8, 0x3da, 0x3db, 0x3dd, 0x3df, 0x3e1, 0x3e3,
0x3e4, 0x3e6, 0x3e8, 0x3ea, 0x3ec, 0x3ed, 0x3ef, 0x3f1, 0x3f3, 0x3f5, 0x3f6, 0x3f8, 0x3fa, 0x3fc, 0x3fe, 0x36c
};
/*
* Write frequency/octave/keyon data to a channel
* [RH] This is totally different from the original MUS library code
* but matches exactly what DMX does. I haven't a clue why there are 284
* special bytes at the beginning of the table for the first few notes.
* That last byte in the table doesn't look right, either, but that's what
* it really is.
*/
void OPLio::OPLwriteFreq(uint32_t channel, uint32_t note, uint32_t pitch, uint32_t keyon)
{
int octave = 0;
int j = (note << 5) + pitch;
if (j < 0)
{
j = 0;
}
else if (j >= 284)
{
j -= 284;
octave = j / (32*12);
if (octave > 7)
{
octave = 7;
}
j = (j % (32*12)) + 284;
}
int i = frequencies[j] | (octave << 10);
OPLwriteValue (0xA0, channel, (uint8_t)i);
OPLwriteValue (0xB0, channel, (uint8_t)(i>>8)|(keyon<<5));
}
/*
* Adjust volume value (register 0x40)
*/
inline uint32_t OPLio::OPLconvertVolume(uint32_t data, uint32_t volume)
{
static uint8_t volumetable[128] = {
0, 1, 3, 5, 6, 8, 10, 11,
13, 14, 16, 17, 19, 20, 22, 23,
25, 26, 27, 29, 30, 32, 33, 34,
36, 37, 39, 41, 43, 45, 47, 49,
50, 52, 54, 55, 57, 59, 60, 61,
63, 64, 66, 67, 68, 69, 71, 72,
73, 74, 75, 76, 77, 79, 80, 81,
82, 83, 84, 84, 85, 86, 87, 88,
89, 90, 91, 92, 92, 93, 94, 95,
96, 96, 97, 98, 99, 99, 100, 101,
101, 102, 103, 103, 104, 105, 105, 106,
107, 107, 108, 109, 109, 110, 110, 111,
112, 112, 113, 113, 114, 114, 115, 115,
116, 117, 117, 118, 118, 119, 119, 120,
120, 121, 121, 122, 122, 123, 123, 123,
124, 124, 125, 125, 126, 126, 127, 127};
return 0x3F - (((0x3F - data) *
(uint32_t)volumetable[volume <= 127 ? volume : 127]) >> 7);
}
uint32_t OPLio::OPLpanVolume(uint32_t volume, int pan)
{
if (pan >= 0)
return volume;
else
return (volume * (pan + 64)) / 64;
}
/*
* Write volume data to a channel
*/
void OPLio::OPLwriteVolume(uint32_t channel, struct OPL2instrument *instr, uint32_t volume)
{
if (instr != 0)
{
OPLwriteChannel(0x40, channel, ((instr->feedback & 1) ?
OPLconvertVolume(instr->level_1, volume) : instr->level_1) | instr->scale_1,
OPLconvertVolume(instr->level_2, volume) | instr->scale_2);
}
}
/*
* Write pan (balance) data to a channel
*/
void OPLio::OPLwritePan(uint32_t channel, struct OPL2instrument *instr, int pan)
{
if (instr != 0)
{
uint8_t bits;
if (pan < -36) bits = 0x10; // left
else if (pan > 36) bits = 0x20; // right
else bits = 0x30; // both
OPLwriteValue(0xC0, channel, instr->feedback | bits);
// Set real panning if we're using emulated chips.
int chanper = IsOPL3 ? OPL3CHANNELS : OPL2CHANNELS;
int which = channel / chanper;
if (chips[which] != NULL)
{
// This is the MIDI-recommended pan formula. 0 and 1 are
// both hard left so that 64 can be perfectly center.
// (Note that the 'pan' passed to this function is the
// MIDI pan position, subtracted by 64.)
double level = (pan <= -63) ? 0 : (pan + 64 - 1) / 126.0;
chips[which]->SetPanning(channel % chanper,
(float)cos(HALF_PI * level), (float)sin(HALF_PI * level));
}
}
}
/*
* Write an instrument to a channel
*
* Instrument layout:
*
* Operator1 Operator2 Descr.
* data[0] data[7] reg. 0x20 - tremolo/vibrato/sustain/KSR/multi
* data[1] data[8] reg. 0x60 - attack rate/decay rate
* data[2] data[9] reg. 0x80 - sustain level/release rate
* data[3] data[10] reg. 0xE0 - waveform select
* data[4] data[11] reg. 0x40 - key scale level
* data[5] data[12] reg. 0x40 - output level (bottom 6 bits only)
* data[6] reg. 0xC0 - feedback/AM-FM (both operators)
*/
void OPLio::OPLwriteInstrument(uint32_t channel, struct OPL2instrument *instr)
{
OPLwriteChannel(0x40, channel, 0x3F, 0x3F); // no volume
OPLwriteChannel(0x20, channel, instr->trem_vibr_1, instr->trem_vibr_2);
OPLwriteChannel(0x60, channel, instr->att_dec_1, instr->att_dec_2);
OPLwriteChannel(0x80, channel, instr->sust_rel_1, instr->sust_rel_2);
OPLwriteChannel(0xE0, channel, instr->wave_1, instr->wave_2);
OPLwriteValue (0xC0, channel, instr->feedback | 0x30);
}
/*
* Stop all sounds
*/
void OPLio::OPLshutup(void)
{
uint32_t i;
for(i = 0; i < OPLchannels; i++)
{
OPLwriteChannel(0x40, i, 0x3F, 0x3F); // turn off volume
OPLwriteChannel(0x60, i, 0xFF, 0xFF); // the fastest attack, decay
OPLwriteChannel(0x80, i, 0x0F, 0x0F); // ... and release
OPLwriteValue(0xB0, i, 0); // KEY-OFF
}
}
/*
* Initialize hardware upon startup
*/
int OPLio::OPLinit(uint32_t numchips, bool stereo, bool initopl3)
{
assert(numchips >= 1 && numchips <= countof(chips));
uint32_t i;
IsOPL3 = (current_opl_core == 1 || current_opl_core == 2 || current_opl_core == 3);
memset(chips, 0, sizeof(chips));
if (IsOPL3)
{
numchips = (numchips + 1) >> 1;
}
for (i = 0; i < numchips; ++i)
{
OPLEmul *chip = IsOPL3 ? (current_opl_core == 1 ? DBOPLCreate(stereo) : (current_opl_core == 2 ? JavaOPLCreate(stereo) : NukedOPL3Create(stereo))) : YM3812Create(stereo);
if (chip == NULL)
{
break;
}
chips[i] = chip;
}
NumChips = i;
OPLchannels = i * (IsOPL3 ? OPL3CHANNELS : OPL2CHANNELS);
OPLwriteInitState(initopl3);
return i;
}
void OPLio::OPLwriteInitState(bool initopl3)
{
for (uint32_t i = 0; i < NumChips; ++i)
{
int chip = i << (int)IsOPL3;
if (IsOPL3 && initopl3)
{
OPLwriteReg(chip, 0x105, 0x01); // enable YMF262/OPL3 mode
OPLwriteReg(chip, 0x104, 0x00); // disable 4-operator mode
}
OPLwriteReg(chip, 0x01, 0x20); // enable Waveform Select
OPLwriteReg(chip, 0x0B, 0x40); // turn off CSW mode
OPLwriteReg(chip, 0xBD, 0x00); // set vibrato/tremolo depth to low, set melodic mode
}
OPLshutup();
}
/*
* Deinitialize hardware before shutdown
*/
void OPLio::OPLdeinit(void)
{
for (size_t i = 0; i < countof(chips); ++i)
{
if (chips[i] != NULL)
{
delete chips[i];
chips[i] = NULL;
}
}
}

View file

@ -0,0 +1,454 @@
#include <stdlib.h>
#include <string.h>
#include "muslib.h"
#include "files.h"
#include "templates.h"
#include "c_cvars.h"
musicBlock::musicBlock ()
{
memset (this, 0, sizeof(*this));
for(auto &voice : voices) voice.index = -1; // mark all free.
}
musicBlock::~musicBlock ()
{
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
int musicBlock::releaseVoice(uint32_t slot, uint32_t killed)
{
struct OPLVoice *ch = &voices[slot];
io->WriteFrequency(slot, ch->note, ch->pitch, 0);
ch->index = -1;
if (killed) io->MuteChannel(slot);
return slot;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
int musicBlock::findFreeVoice()
{
for (uint32_t i = 0; i < io->NumChannels; ++i)
{
if (voices[i].index == -1)
{
releaseVoice(i, 1);
return i;
}
}
return -1;
}
//----------------------------------------------------------------------------
//
// When all voices are in use, we must discard an existing voice to
// play a new note. Find and free an existing voice. The channel
// passed to the function is the channel for the new note to be
// played.
//
//----------------------------------------------------------------------------
int musicBlock::replaceExistingVoice()
{
// Check the allocated voices, if we find an instrument that is
// of a lower priority to the new instrument, discard it.
// If a voice is being used to play the second voice of an instrument,
// use that, as second voices are non-essential.
// Lower numbered MIDI channels implicitly have a higher priority
// than higher-numbered channels, eg. MIDI channel 1 is never
// discarded for MIDI channel 2.
int result = 0;
for (uint32_t i = 0; i < io->NumChannels; ++i)
{
if (voices[i].current_instr_voice == &voices[i].current_instr->voices[1] ||
voices[i].index >= voices[result].index)
{
result = i;
}
}
releaseVoice(result, 1);
return result;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
void musicBlock::voiceKeyOn(uint32_t slot, uint32_t channo, genmidi_instr_t *instrument, uint32_t instrument_voice, uint32_t key, uint32_t volume)
{
struct OPLVoice *voice = &voices[slot];
auto &channel = oplchannels[channo];
genmidi_voice_t *gmvoice;
voice->index = channo;
voice->key = key;
// Program the voice with the instrument data:
voice->current_instr = instrument;
gmvoice = voice->current_instr_voice = &instrument->voices[instrument_voice];
io->WriteInstrument(slot,gmvoice, channel.Vibrato);
io->WritePan(slot, gmvoice, channel.Panning);
// Set the volume level.
voice->note_volume = volume;
io->WriteVolume(slot, gmvoice, channel.Volume, channel.Expression, volume);
// Write the frequency value to turn the note on.
// Work out the note to use. This is normally the same as
// the key, unless it is a fixed pitch instrument.
uint32_t note;
if (instrument->flags & GENMIDI_FLAG_FIXED) note = instrument->fixed_note;
else if (channo == CHAN_PERCUSSION) note = 60;
else note = key;
// If this is the second voice of a double voice instrument, the
// frequency index can be adjusted by the fine tuning field.
voice->fine_tuning = (instrument_voice != 0) ? (voice->current_instr->fine_tuning / 2) - 64 : 0;
voice->pitch = voice->fine_tuning + channel.Pitch;
if (!(instrument->flags & GENMIDI_FLAG_FIXED) && channo != CHAN_PERCUSSION)
{
note += gmvoice->base_note_offset;
}
// Avoid possible overflow due to base note offset:
while (note < 0)
{
note += 12;
}
while (note > HIGHEST_NOTE)
{
note -= 12;
}
voice->note = note;
io->WriteFrequency(slot, note, voice->pitch, 1);
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
CVAR(Bool, opl_singlevoice, 0, 0)
void musicBlock::noteOn(uint32_t channel, uint8_t key, int volume)
{
if (volume <= 0)
{
noteOff(channel, key);
return;
}
uint32_t note;
genmidi_instr_t *instrument;
// Percussion channel is treated differently.
if (channel == CHAN_PERCUSSION)
{
if (key < GENMIDI_FIST_PERCUSSION || key >= GENMIDI_FIST_PERCUSSION + GENMIDI_NUM_PERCUSSION)
{
return;
}
instrument = &OPLinstruments[key + (GENMIDI_NUM_INSTRS - GENMIDI_FIST_PERCUSSION)];
}
else
{
auto inst = oplchannels[channel].Instrument;
if (inst >= GENMIDI_NUM_TOTAL) return; // better safe than sorry.
instrument = &OPLinstruments[inst];
}
bool double_voice = ((instrument->flags) & GENMIDI_FLAG_2VOICE) && !opl_singlevoice;
int i = findFreeVoice();
if (i < 0) i = replaceExistingVoice();
if (i >= 0)
{
voiceKeyOn(i, channel, instrument, 0, key, volume);
if (double_voice)
{
i = findFreeVoice();
if (i > 0)
{
voiceKeyOn(i, channel, instrument, 1, key, volume);
}
}
}
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
void musicBlock::noteOff(uint32_t id, uint8_t note)
{
uint32_t sustain = oplchannels[id].Sustain;
for(uint32_t i = 0; i < io->NumChannels; i++)
{
if (voices[i].index == id && voices[i].key == note)
{
if (sustain >= MIN_SUSTAIN) voices[i].sustained = true;
else releaseVoice(i, 0);
}
}
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
void musicBlock::changePitch(uint32_t id, int val1, int val2)
{
// Convert pitch from 14-bit to 7-bit, then scale it, since the player
// code only understands sensitivities of 2 semitones.
int pitch = ((val1 | (val2 << 7)) - 8192) * oplchannels[id].PitchSensitivity / (200 * 128) + 64;
oplchannels[id].Pitch = pitch;
for(uint32_t i = 0; i < io->NumChannels; i++)
{
auto &ch = voices[i];
if (ch.index == id)
{
ch.pitch = ch.fine_tuning + pitch;
io->WriteFrequency(i, ch.note, ch.pitch, 1);
}
}
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
void musicBlock::changeModulation(uint32_t id, int value)
{
bool vibrato = (value >= VIBRATO_THRESHOLD);
oplchannels[id].Vibrato = vibrato;
for (uint32_t i = 0; i < io->NumChannels; i++)
{
auto &ch = voices[i];
if (ch.index == id)
{
io->WriteTremolo(i, ch.current_instr_voice, vibrato);
}
}
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
void musicBlock::changeSustain(uint32_t id, int value)
{
oplchannels[id].Sustain = value;
if (value < MIN_SUSTAIN)
{
for (uint32_t i = 0; i < io->NumChannels; i++)
{
if (voices[i].index == id && voices[i].sustained)
releaseVoice(i, 0);
}
}
}
//----------------------------------------------------------------------------
//
// Change volume or expression.
// Since both go to the same register, one function can handle both.
//
//----------------------------------------------------------------------------
void musicBlock::changeVolume(uint32_t id, int value, bool expression)
{
auto &chan = oplchannels[id];
if (!expression) chan.Volume = value;
else chan.Expression = value;
for (uint32_t i = 0; i < io->NumChannels; i++)
{
auto &ch = voices[i];
if (ch.index == id)
{
io->WriteVolume(i, ch.current_instr_voice, chan.Volume, chan.Expression, ch.note_volume);
}
}
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
void musicBlock::changePanning(uint32_t id, int value)
{
oplchannels[id].Panning = value;
for(uint32_t i = 0; i < io->NumChannels; i++)
{
auto &ch = voices[i];
if (ch.index == id)
{
io->WritePan(i, ch.current_instr_voice, value);
}
}
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
void musicBlock::notesOff(uint32_t id, int value)
{
for (uint32_t i = 0; i < io->NumChannels; ++i)
{
if (voices[i].index == id)
{
if (oplchannels[id].Sustain >= MIN_SUSTAIN) voices[i].sustained = true;
else releaseVoice(i, 0);
}
}
}
//----------------------------------------------------------------------------
//
// release all notes for this channel
//
//----------------------------------------------------------------------------
void musicBlock::allNotesOff(uint32_t id, int value)
{
for (uint32_t i = 0; i < io->NumChannels; ++i)
{
if (voices[i].index == id)
{
releaseVoice(i, 0);
}
}
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
void musicBlock::changeExtended(uint32_t id, uint8_t controller, int value)
{
switch (controller)
{
case ctrlRPNHi:
oplchannels[id].RPN = (oplchannels[id].RPN & 0x007F) | (value << 7);
break;
case ctrlRPNLo:
oplchannels[id].RPN = (oplchannels[id].RPN & 0x3F80) | value;
break;
case ctrlNRPNLo:
case ctrlNRPNHi:
oplchannels[id].RPN = 0x3FFF;
break;
case ctrlDataEntryHi:
if (oplchannels[id].RPN == 0)
{
oplchannels[id].PitchSensitivity = value * 100 + (oplchannels[id].PitchSensitivity % 100);
}
break;
case ctrlDataEntryLo:
if (oplchannels[id].RPN == 0)
{
oplchannels[id].PitchSensitivity = value + (oplchannels[id].PitchSensitivity / 100) * 100;
}
break;
}
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
void musicBlock::resetControllers(uint32_t chan, int vol)
{
auto &channel = oplchannels[chan];
channel.Volume = vol;
channel.Expression = 127;
channel.Sustain = 0;
channel.Pitch = 64;
channel.RPN = 0x3fff;
channel.PitchSensitivity = 200;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
void musicBlock::programChange(uint32_t channel, int value)
{
oplchannels[channel].Instrument = value;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
void musicBlock::resetAllControllers(int vol)
{
uint32_t i;
for (i = 0; i < NUM_CHANNELS; i++)
{
resetControllers(i, vol);
}
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
void musicBlock::stopAllVoices()
{
for (uint32_t i = 0; i < io->NumChannels; i++)
{
if (voices[i].index >= 0) releaseVoice(i, 1);
}
}

View file

@ -0,0 +1,58 @@
#pragma once
#include "doomtype.h"
#include "genmidi.h"
#include "oplio.h"
struct OPLVoice
{
int index; // Index of this voice, or -1 if not in use.
unsigned int key; // The midi key that this voice is playing.
unsigned int note; // The note being played. This is normally the same as the key, but if the instrument is a fixed pitch instrument, it is different.
unsigned int note_volume; // The volume of the note being played on this channel.
genmidi_instr_t *current_instr; // Currently-loaded instrument data
genmidi_voice_t *current_instr_voice;// The voice number in the instrument to use. This is normally set to the instrument's first voice; if this is a double voice instrument, it may be the second one
bool sustained;
int8_t fine_tuning;
int pitch;
};
struct musicBlock {
musicBlock();
~musicBlock();
uint8_t *score;
uint8_t *scoredata;
int playingcount;
OPLChannel oplchannels[NUM_CHANNELS];
OPLio *io;
struct genmidi_instr_t OPLinstruments[GENMIDI_NUM_TOTAL];
void changeModulation(uint32_t id, int value);
void changeSustain(uint32_t id, int value);
void changeVolume(uint32_t id, int value, bool expression);
void changePanning(uint32_t id, int value);
void notesOff(uint32_t id, int value);
void allNotesOff(uint32_t id, int value);
void changeExtended(uint32_t channel, uint8_t controller, int value);
void resetControllers(uint32_t channel, int vol);
void programChange(uint32_t channel, int value);
void resetAllControllers(int vol);
void changePitch(uint32_t channel, int val1, int val2);
void noteOn(uint32_t channel, uint8_t note, int volume);
void noteOff(uint32_t channel, uint8_t note);
void stopAllVoices();
protected:
OPLVoice voices[NUM_VOICES];
int findFreeVoice();
int replaceExistingVoice();
void voiceKeyOn(uint32_t slot, uint32_t channo, genmidi_instr_t *instrument, uint32_t instrument_voice, uint32_t, uint32_t volume);
int releaseVoice(uint32_t slot, uint32_t killed);
friend class Stat_opl;
};

View file

@ -1,267 +0,0 @@
/*
* Name: Main header include file
* Project: MUS File Player Library
* Version: 1.75
* Author: Vladimir Arnost (QA-Software)
* Last revision: Mar-9-1996
* Compiler: Borland C++ 3.1, Watcom C/C++ 10.0
*
*/
/* From muslib175.zip/README.1ST:
1.1 - Disclaimer of Warranties
------------------------------
#ifdef LAWYER
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.
#else
Use this software at your own risk.
#endif
1.2 - Terms of Use
------------------
This library may be used in any freeware or shareware product free of
charge. The product may not be sold for profit (except for shareware) and
should be freely available to the public. It would be nice of you if you
credited me in your product and notified me if you use this library.
If you want to use this library in a commercial product, contact me
and we will make an agreement. It is a violation of the law to make money
of this product without prior signing an agreement and paying a license fee.
This licence will allow its holder to sell any products based on MUSLib,
royalty-free. There is no need to buy separate licences for different
products once the licence fee is paid.
1.3 - Contacting the Author
---------------------------
Internet (address valid probably until the end of year 1998):
xarnos00@dcse.fee.vutbr.cz
FIDO:
2:423/36.2
Snail-mail:
Vladimir Arnost
Ceska 921
Chrudim 4
537 01
CZECH REPUBLIC
Voice-mail (Czech language only, not recommended; weekends only):
+42-455-2154
*/
#ifndef __MUSLIB_H_
#define __MUSLIB_H_
#ifndef __DEFTYPES_H_
#include "doomtype.h"
#endif
class FileReader;
/* Global Definitions */
#define CHANNELS 16 // total channels 0..CHANNELS-1
#define PERCUSSION 15 // percussion channel
/* OPL2 instrument */
struct OPL2instrument {
/*00*/ uint8_t trem_vibr_1; /* OP 1: tremolo/vibrato/sustain/KSR/multi */
/*01*/ uint8_t att_dec_1; /* OP 1: attack rate/decay rate */
/*02*/ uint8_t sust_rel_1; /* OP 1: sustain level/release rate */
/*03*/ uint8_t wave_1; /* OP 1: waveform select */
/*04*/ uint8_t scale_1; /* OP 1: key scale level */
/*05*/ uint8_t level_1; /* OP 1: output level */
/*06*/ uint8_t feedback; /* feedback/AM-FM (both operators) */
/*07*/ uint8_t trem_vibr_2; /* OP 2: tremolo/vibrato/sustain/KSR/multi */
/*08*/ uint8_t att_dec_2; /* OP 2: attack rate/decay rate */
/*09*/ uint8_t sust_rel_2; /* OP 2: sustain level/release rate */
/*0A*/ uint8_t wave_2; /* OP 2: waveform select */
/*0B*/ uint8_t scale_2; /* OP 2: key scale level */
/*0C*/ uint8_t level_2; /* OP 2: output level */
/*0D*/ uint8_t unused;
/*0E*/ int16_t basenote; /* base note offset */
};
/* OP2 instrument file entry */
struct OP2instrEntry {
/*00*/ uint16_t flags; // see FL_xxx below
/*02*/ uint8_t finetune; // finetune value for 2-voice sounds
/*03*/ uint8_t note; // note # for fixed instruments
/*04*/ struct OPL2instrument instr[2]; // instruments
};
#define FL_FIXED_PITCH 0x0001 // note has fixed pitch (see below)
#define FL_DOUBLE_VOICE 0x0004 // use two voices instead of one
#define OP2INSTRSIZE sizeof(struct OP2instrEntry) // instrument size (36 bytes)
#define OP2INSTRCOUNT (128 + 81-35+1) // instrument count
/* From MLOPL_IO.CPP */
#define OPL2CHANNELS 9
#define OPL3CHANNELS 18
#define MAXOPL2CHIPS 8
#define MAXCHANNELS (OPL2CHANNELS * MAXOPL2CHIPS)
/* Channel Flags: */
#define CH_SECONDARY 0x01
#define CH_SUSTAIN 0x02
#define CH_VIBRATO 0x04 /* set if modulation >= MOD_MIN */
#define CH_FREE 0x80
struct OPLdata {
uint32_t channelInstr[CHANNELS]; // instrument #
uint8_t channelVolume[CHANNELS]; // volume
uint8_t channelLastVolume[CHANNELS]; // last volume
int8_t channelPan[CHANNELS]; // pan, 0=normal
int8_t channelPitch[CHANNELS]; // pitch wheel, 64=normal
uint8_t channelSustain[CHANNELS]; // sustain pedal value
uint8_t channelModulation[CHANNELS]; // modulation pot value
uint16_t channelPitchSens[CHANNELS]; // pitch sensitivity, 2=default
uint16_t channelRPN[CHANNELS]; // RPN number for data entry
uint8_t channelExpression[CHANNELS]; // expression
};
struct OPLio {
virtual ~OPLio();
void OPLwriteChannel(uint32_t regbase, uint32_t channel, uint8_t data1, uint8_t data2);
void OPLwriteValue(uint32_t regbase, uint32_t channel, uint8_t value);
void OPLwriteFreq(uint32_t channel, uint32_t freq, uint32_t octave, uint32_t keyon);
uint32_t OPLconvertVolume(uint32_t data, uint32_t volume);
uint32_t OPLpanVolume(uint32_t volume, int pan);
void OPLwriteVolume(uint32_t channel, struct OPL2instrument *instr, uint32_t volume);
void OPLwritePan(uint32_t channel, struct OPL2instrument *instr, int pan);
void OPLwriteInstrument(uint32_t channel, struct OPL2instrument *instr);
void OPLshutup(void);
void OPLwriteInitState(bool initopl3);
virtual int OPLinit(uint32_t numchips, bool stereo=false, bool initopl3=false);
virtual void OPLdeinit(void);
virtual void OPLwriteReg(int which, uint32_t reg, uint8_t data);
virtual void SetClockRate(double samples_per_tick);
virtual void WriteDelay(int ticks);
class OPLEmul *chips[MAXOPL2CHIPS];
uint32_t OPLchannels;
uint32_t NumChips;
bool IsOPL3;
};
struct DiskWriterIO : public OPLio
{
DiskWriterIO(const char *filename);
~DiskWriterIO();
int OPLinit(uint32_t numchips, bool notused, bool initopl3);
void SetClockRate(double samples_per_tick);
void WriteDelay(int ticks);
FString Filename;
};
struct musicBlock {
musicBlock();
~musicBlock();
uint8_t *score;
uint8_t *scoredata;
int playingcount;
OPLdata driverdata;
OPLio *io;
struct OP2instrEntry *OPLinstruments;
uint32_t MLtime;
void OPLplayNote(uint32_t channel, uint8_t note, int volume);
void OPLreleaseNote(uint32_t channel, uint8_t note);
void OPLpitchWheel(uint32_t channel, int pitch);
void OPLchangeControl(uint32_t channel, uint8_t controller, int value);
void OPLprogramChange(uint32_t channel, int value);
void OPLresetControllers(uint32_t channel, int vol);
void OPLplayMusic(int vol);
void OPLstopMusic();
int OPLloadBank (FileReader &data);
protected:
/* OPL channel (voice) data */
struct channelEntry {
uint8_t channel; /* MUS channel number */
uint8_t note; /* note number */
uint8_t flags; /* see CH_xxx below */
uint8_t realnote; /* adjusted note number */
int8_t finetune; /* frequency fine-tune */
int pitch; /* pitch-wheel value */
uint32_t volume; /* note volume */
uint32_t realvolume; /* adjusted note volume */
struct OPL2instrument *instr; /* current instrument */
uint32_t time; /* note start time */
} channels[MAXCHANNELS];
void writeFrequency(uint32_t slot, uint32_t note, int pitch, uint32_t keyOn);
void writeModulation(uint32_t slot, struct OPL2instrument *instr, int state);
uint32_t calcVolume(uint32_t channelVolume, uint32_t channelExpression, uint32_t noteVolume);
int occupyChannel(uint32_t slot, uint32_t channel,
int note, int volume, struct OP2instrEntry *instrument, uint8_t secondary);
int releaseChannel(uint32_t slot, uint32_t killed);
int releaseSustain(uint32_t channel);
int findFreeChannel(uint32_t flag, uint32_t channel, uint8_t note);
struct OP2instrEntry *getInstrument(uint32_t channel, uint8_t note);
friend class Stat_opl;
};
enum MUSctrl {
ctrlPatch = 0,
ctrlBank,
ctrlModulation,
ctrlVolume,
ctrlPan,
ctrlExpression,
ctrlReverb,
ctrlChorus,
ctrlSustainPedal,
ctrlSoftPedal,
ctrlRPNHi,
ctrlRPNLo,
ctrlNRPNHi,
ctrlNRPNLo,
ctrlDataEntryHi,
ctrlDataEntryLo,
ctrlSoundsOff,
ctrlNotesOff,
ctrlMono,
ctrlPoly,
};
#define ADLIB_CLOCK_MUL 24.0
#endif // __MUSLIB_H_

View file

@ -30,7 +30,7 @@
//version 1.6
#include "opl.h"
#include "muslib.h"
#include "musicblock.h"
typedef uintptr_t Bitu;
typedef intptr_t Bits;

View file

@ -24,6 +24,8 @@ OPLEmul *NukedOPL3Create(bool stereo);
#define OPL_SAMPLE_RATE 49716.0
#define CENTER_PANNING_POWER 0.70710678118 /* [RH] volume at center for EQP */
#define ADLIB_CLOCK_MUL 24.0
#endif

View file

@ -38,16 +38,15 @@ OPLmusicBlock::~OPLmusicBlock()
void OPLmusicBlock::ResetChips ()
{
ChipAccess.Enter();
io->OPLdeinit ();
NumChips = io->OPLinit(MIN(*opl_numchips, 2), FullPan);
io->Reset ();
NumChips = io->Init(MIN(*opl_numchips, 2), FullPan);
ChipAccess.Leave();
}
void OPLmusicBlock::Restart()
{
OPLstopMusic ();
OPLplayMusic (127);
MLtime = 0;
stopAllVoices ();
resetAllControllers (127);
playingcount = 0;
LastOffset = 0;
}
@ -69,7 +68,7 @@ fail: delete[] scoredata;
return;
}
if (0 == (NumChips = io->OPLinit(NumChips)))
if (0 == (NumChips = io->Init(NumChips)))
{
goto fail;
}
@ -161,7 +160,7 @@ OPLmusicFile::~OPLmusicFile ()
{
if (scoredata != NULL)
{
io->OPLdeinit ();
io->Reset ();
delete[] scoredata;
scoredata = NULL;
}
@ -280,7 +279,6 @@ bool OPLmusicBlock::ServiceStream (void *buff, int numbytes)
io->WriteDelay(next);
NextTickIn += SamplesPerTick * next;
assert (NextTickIn >= 0);
MLtime += next;
}
}
}
@ -407,7 +405,7 @@ int OPLmusicFile::PlayTick ()
break;
default: // It's something to stuff into the OPL chip
io->OPLwriteReg(WhichChip, reg, data);
io->WriteRegister(WhichChip, reg, data);
break;
}
}
@ -447,7 +445,7 @@ int OPLmusicFile::PlayTick ()
{
data = *score++;
}
io->OPLwriteReg(WhichChip, reg, data);
io->WriteRegister(WhichChip, reg, data);
}
break;
@ -477,7 +475,7 @@ int OPLmusicFile::PlayTick ()
}
else if (code < to_reg_size)
{
io->OPLwriteReg(which, to_reg[code], data);
io->WriteRegister(which, to_reg[code], data);
}
}
}
@ -495,7 +493,7 @@ int OPLmusicFile::PlayTick ()
data = score[1];
delay = LittleShort(((uint16_t *)score)[1]);
score += 4;
io->OPLwriteReg (0, reg, data);
io->WriteRegister (0, reg, data);
}
return delay;
}
@ -524,7 +522,7 @@ OPLmusicFile::OPLmusicFile(const OPLmusicFile *source, const char *filename)
delete io;
}
io = new DiskWriterIO(filename);
NumChips = io->OPLinit(NumChips);
NumChips = io->Init(NumChips);
Restart();
}

View file

@ -1,6 +1,8 @@
#include "critsec.h"
#include "muslib.h"
class FileReader;
class OPLmusicBlock : public musicBlock
{
public:

View file

@ -0,0 +1,493 @@
/*
** oplio.cpp
** low level OPL code
**
**---------------------------------------------------------------------------
** Copyright 1998-2008 Randy Heit
** 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.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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 "genmidi.h"
#include "oplio.h"
#include "opl.h"
#include "c_cvars.h"
#include "templates.h"
const double HALF_PI = (M_PI*0.5);
EXTERN_CVAR(Int, opl_core)
extern int current_opl_core;
OPLio::~OPLio()
{
}
void OPLio::SetClockRate(double samples_per_tick)
{
}
void OPLio::WriteDelay(int ticks)
{
}
//----------------------------------------------------------------------------
//
// Initialize OPL emulator
//
//----------------------------------------------------------------------------
int OPLio::Init(uint32_t numchips, bool stereo, bool initopl3)
{
assert(numchips >= 1 && numchips <= countof(chips));
uint32_t i;
IsOPL3 = (current_opl_core == 1 || current_opl_core == 2 || current_opl_core == 3);
memset(chips, 0, sizeof(chips));
if (IsOPL3)
{
numchips = (numchips + 1) >> 1;
}
for (i = 0; i < numchips; ++i)
{
OPLEmul *chip = IsOPL3 ? (current_opl_core == 1 ? DBOPLCreate(stereo) : (current_opl_core == 2 ? JavaOPLCreate(stereo) : NukedOPL3Create(stereo))) : YM3812Create(stereo);
if (chip == NULL)
{
break;
}
chips[i] = chip;
}
NumChips = i;
NumChannels = i * (IsOPL3 ? OPL3_NUM_VOICES : OPL_NUM_VOICES);
WriteInitState(initopl3);
return i;
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
void OPLio::WriteInitState(bool initopl3)
{
for (uint32_t k = 0; k < NumChips; ++k)
{
int chip = k << (int)IsOPL3;
if (IsOPL3 && initopl3)
{
WriteRegister(chip, OPL_REG_OPL3_ENABLE, 1);
WriteRegister(chip, OPL_REG_4OPMODE_ENABLE, 0);
}
WriteRegister(chip, OPL_REG_WAVEFORM_ENABLE, WAVEFORM_ENABLED);
WriteRegister(chip, OPL_REG_PERCUSSION_MODE, 0); // should be the default, but cannot verify for some of the cores.
}
// Reset all channels.
for (uint32_t k = 0; k < NumChannels; k++)
{
MuteChannel(k);
WriteValue(OPL_REGS_FREQ_2, k, 0);
}
}
//----------------------------------------------------------------------------
//
// Deinitialize emulator before shutdown
//
//----------------------------------------------------------------------------
void OPLio::Reset(void)
{
for (size_t i = 0; i < countof(chips); ++i)
{
if (chips[i] != NULL)
{
delete chips[i];
chips[i] = NULL;
}
}
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
void OPLio::WriteRegister(int chipnum, uint32_t reg, uint8_t data)
{
if (IsOPL3)
{
reg |= (chipnum & 1) << 8;
chipnum >>= 1;
}
if (chips[chipnum] != nullptr)
{
chips[chipnum]->WriteReg(reg, data);
}
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
void OPLio::WriteValue(uint32_t regbase, uint32_t channel, uint8_t value)
{
WriteRegister (channel / OPL_NUM_VOICES, regbase + (channel % OPL_NUM_VOICES), value);
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
static const int voice_operators[OPL_NUM_VOICES] = { 0x00, 0x01, 0x02, 0x08, 0x09, 0x0a, 0x10, 0x11, 0x12 };
void OPLio::WriteOperator(uint32_t regbase, uint32_t channel, int index, uint8_t data2)
{
WriteRegister(channel / OPL_NUM_VOICES, regbase + voice_operators[channel % OPL_NUM_VOICES] + 3*index, data2);
}
//----------------------------------------------------------------------------
//
// Write frequency/octave/keyon data to a channel
//
// [RH] This is totally different from the original MUS library code
// but matches exactly what DMX does. I haven't a clue why there are 284
// special bytes at the beginning of the table for the first few notes.
// That last byte in the table doesn't look right, either, but that's what
// it really is.
//
//----------------------------------------------------------------------------
static const uint16_t frequencies[] = { // (this is the table from Chocolate Doom, which contains the same values as ZDoom's original one but is better formatted.
0x133, 0x133, 0x134, 0x134, 0x135, 0x136, 0x136, 0x137, // -1
0x137, 0x138, 0x138, 0x139, 0x139, 0x13a, 0x13b, 0x13b,
0x13c, 0x13c, 0x13d, 0x13d, 0x13e, 0x13f, 0x13f, 0x140,
0x140, 0x141, 0x142, 0x142, 0x143, 0x143, 0x144, 0x144,
0x145, 0x146, 0x146, 0x147, 0x147, 0x148, 0x149, 0x149, // -2
0x14a, 0x14a, 0x14b, 0x14c, 0x14c, 0x14d, 0x14d, 0x14e,
0x14f, 0x14f, 0x150, 0x150, 0x151, 0x152, 0x152, 0x153,
0x153, 0x154, 0x155, 0x155, 0x156, 0x157, 0x157, 0x158,
// These are used for the first seven MIDI note values:
0x158, 0x159, 0x15a, 0x15a, 0x15b, 0x15b, 0x15c, 0x15d, // 0
0x15d, 0x15e, 0x15f, 0x15f, 0x160, 0x161, 0x161, 0x162,
0x162, 0x163, 0x164, 0x164, 0x165, 0x166, 0x166, 0x167,
0x168, 0x168, 0x169, 0x16a, 0x16a, 0x16b, 0x16c, 0x16c,
0x16d, 0x16e, 0x16e, 0x16f, 0x170, 0x170, 0x171, 0x172, // 1
0x172, 0x173, 0x174, 0x174, 0x175, 0x176, 0x176, 0x177,
0x178, 0x178, 0x179, 0x17a, 0x17a, 0x17b, 0x17c, 0x17c,
0x17d, 0x17e, 0x17e, 0x17f, 0x180, 0x181, 0x181, 0x182,
0x183, 0x183, 0x184, 0x185, 0x185, 0x186, 0x187, 0x188, // 2
0x188, 0x189, 0x18a, 0x18a, 0x18b, 0x18c, 0x18d, 0x18d,
0x18e, 0x18f, 0x18f, 0x190, 0x191, 0x192, 0x192, 0x193,
0x194, 0x194, 0x195, 0x196, 0x197, 0x197, 0x198, 0x199,
0x19a, 0x19a, 0x19b, 0x19c, 0x19d, 0x19d, 0x19e, 0x19f, // 3
0x1a0, 0x1a0, 0x1a1, 0x1a2, 0x1a3, 0x1a3, 0x1a4, 0x1a5,
0x1a6, 0x1a6, 0x1a7, 0x1a8, 0x1a9, 0x1a9, 0x1aa, 0x1ab,
0x1ac, 0x1ad, 0x1ad, 0x1ae, 0x1af, 0x1b0, 0x1b0, 0x1b1,
0x1b2, 0x1b3, 0x1b4, 0x1b4, 0x1b5, 0x1b6, 0x1b7, 0x1b8, // 4
0x1b8, 0x1b9, 0x1ba, 0x1bb, 0x1bc, 0x1bc, 0x1bd, 0x1be,
0x1bf, 0x1c0, 0x1c0, 0x1c1, 0x1c2, 0x1c3, 0x1c4, 0x1c4,
0x1c5, 0x1c6, 0x1c7, 0x1c8, 0x1c9, 0x1c9, 0x1ca, 0x1cb,
0x1cc, 0x1cd, 0x1ce, 0x1ce, 0x1cf, 0x1d0, 0x1d1, 0x1d2, // 5
0x1d3, 0x1d3, 0x1d4, 0x1d5, 0x1d6, 0x1d7, 0x1d8, 0x1d8,
0x1d9, 0x1da, 0x1db, 0x1dc, 0x1dd, 0x1de, 0x1de, 0x1df,
0x1e0, 0x1e1, 0x1e2, 0x1e3, 0x1e4, 0x1e5, 0x1e5, 0x1e6,
0x1e7, 0x1e8, 0x1e9, 0x1ea, 0x1eb, 0x1ec, 0x1ed, 0x1ed, // 6
0x1ee, 0x1ef, 0x1f0, 0x1f1, 0x1f2, 0x1f3, 0x1f4, 0x1f5,
0x1f6, 0x1f6, 0x1f7, 0x1f8, 0x1f9, 0x1fa, 0x1fb, 0x1fc,
0x1fd, 0x1fe, 0x1ff, 0x200, 0x201, 0x201, 0x202, 0x203,
// First note of looped range used for all octaves:
0x204, 0x205, 0x206, 0x207, 0x208, 0x209, 0x20a, 0x20b, // 7
0x20c, 0x20d, 0x20e, 0x20f, 0x210, 0x210, 0x211, 0x212,
0x213, 0x214, 0x215, 0x216, 0x217, 0x218, 0x219, 0x21a,
0x21b, 0x21c, 0x21d, 0x21e, 0x21f, 0x220, 0x221, 0x222,
0x223, 0x224, 0x225, 0x226, 0x227, 0x228, 0x229, 0x22a, // 8
0x22b, 0x22c, 0x22d, 0x22e, 0x22f, 0x230, 0x231, 0x232,
0x233, 0x234, 0x235, 0x236, 0x237, 0x238, 0x239, 0x23a,
0x23b, 0x23c, 0x23d, 0x23e, 0x23f, 0x240, 0x241, 0x242,
0x244, 0x245, 0x246, 0x247, 0x248, 0x249, 0x24a, 0x24b, // 9
0x24c, 0x24d, 0x24e, 0x24f, 0x250, 0x251, 0x252, 0x253,
0x254, 0x256, 0x257, 0x258, 0x259, 0x25a, 0x25b, 0x25c,
0x25d, 0x25e, 0x25f, 0x260, 0x262, 0x263, 0x264, 0x265,
0x266, 0x267, 0x268, 0x269, 0x26a, 0x26c, 0x26d, 0x26e, // 10
0x26f, 0x270, 0x271, 0x272, 0x273, 0x275, 0x276, 0x277,
0x278, 0x279, 0x27a, 0x27b, 0x27d, 0x27e, 0x27f, 0x280,
0x281, 0x282, 0x284, 0x285, 0x286, 0x287, 0x288, 0x289,
0x28b, 0x28c, 0x28d, 0x28e, 0x28f, 0x290, 0x292, 0x293, // 11
0x294, 0x295, 0x296, 0x298, 0x299, 0x29a, 0x29b, 0x29c,
0x29e, 0x29f, 0x2a0, 0x2a1, 0x2a2, 0x2a4, 0x2a5, 0x2a6,
0x2a7, 0x2a9, 0x2aa, 0x2ab, 0x2ac, 0x2ae, 0x2af, 0x2b0,
0x2b1, 0x2b2, 0x2b4, 0x2b5, 0x2b6, 0x2b7, 0x2b9, 0x2ba, // 12
0x2bb, 0x2bd, 0x2be, 0x2bf, 0x2c0, 0x2c2, 0x2c3, 0x2c4,
0x2c5, 0x2c7, 0x2c8, 0x2c9, 0x2cb, 0x2cc, 0x2cd, 0x2ce,
0x2d0, 0x2d1, 0x2d2, 0x2d4, 0x2d5, 0x2d6, 0x2d8, 0x2d9,
0x2da, 0x2dc, 0x2dd, 0x2de, 0x2e0, 0x2e1, 0x2e2, 0x2e4, // 13
0x2e5, 0x2e6, 0x2e8, 0x2e9, 0x2ea, 0x2ec, 0x2ed, 0x2ee,
0x2f0, 0x2f1, 0x2f2, 0x2f4, 0x2f5, 0x2f6, 0x2f8, 0x2f9,
0x2fb, 0x2fc, 0x2fd, 0x2ff, 0x300, 0x302, 0x303, 0x304,
0x306, 0x307, 0x309, 0x30a, 0x30b, 0x30d, 0x30e, 0x310, // 14
0x311, 0x312, 0x314, 0x315, 0x317, 0x318, 0x31a, 0x31b,
0x31c, 0x31e, 0x31f, 0x321, 0x322, 0x324, 0x325, 0x327,
0x328, 0x329, 0x32b, 0x32c, 0x32e, 0x32f, 0x331, 0x332,
0x334, 0x335, 0x337, 0x338, 0x33a, 0x33b, 0x33d, 0x33e, // 15
0x340, 0x341, 0x343, 0x344, 0x346, 0x347, 0x349, 0x34a,
0x34c, 0x34d, 0x34f, 0x350, 0x352, 0x353, 0x355, 0x357,
0x358, 0x35a, 0x35b, 0x35d, 0x35e, 0x360, 0x361, 0x363,
0x365, 0x366, 0x368, 0x369, 0x36b, 0x36c, 0x36e, 0x370, // 16
0x371, 0x373, 0x374, 0x376, 0x378, 0x379, 0x37b, 0x37c,
0x37e, 0x380, 0x381, 0x383, 0x384, 0x386, 0x388, 0x389,
0x38b, 0x38d, 0x38e, 0x390, 0x392, 0x393, 0x395, 0x397,
0x398, 0x39a, 0x39c, 0x39d, 0x39f, 0x3a1, 0x3a2, 0x3a4, // 17
0x3a6, 0x3a7, 0x3a9, 0x3ab, 0x3ac, 0x3ae, 0x3b0, 0x3b1,
0x3b3, 0x3b5, 0x3b7, 0x3b8, 0x3ba, 0x3bc, 0x3bd, 0x3bf,
0x3c1, 0x3c3, 0x3c4, 0x3c6, 0x3c8, 0x3ca, 0x3cb, 0x3cd,
// The last note has an incomplete range, and loops round back to
// the start. Note that the last value is actually a buffer overrun
// and does not fit with the other values.
0x3cf, 0x3d1, 0x3d2, 0x3d4, 0x3d6, 0x3d8, 0x3da, 0x3db, // 18
0x3dd, 0x3df, 0x3e1, 0x3e3, 0x3e4, 0x3e6, 0x3e8, 0x3ea,
0x3ec, 0x3ed, 0x3ef, 0x3f1, 0x3f3, 0x3f5, 0x3f6, 0x3f8,
0x3fa, 0x3fc, 0x3fe, 0x36c,
};
void OPLio::WriteFrequency(uint32_t channel, uint32_t note, uint32_t pitch, uint32_t keyon)
{
int octave = 0;
int j = (note << 5) + pitch;
if (j < 0)
{
j = 0;
}
else if (j >= 284)
{
j -= 284;
octave = j / (32*12);
if (octave > 7)
{
octave = 7;
}
j = (j % (32*12)) + 284;
}
int i = frequencies[j] | (octave << 10);
WriteValue (OPL_REGS_FREQ_1, channel, (uint8_t)i);
WriteValue (OPL_REGS_FREQ_2, channel, (uint8_t)(i>>8)|(keyon<<5));
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
static uint8_t volumetable[128] = {
0, 1, 3, 5, 6, 8, 10, 11,
13, 14, 16, 17, 19, 20, 22, 23,
25, 26, 27, 29, 30, 32, 33, 34,
36, 37, 39, 41, 43, 45, 47, 49,
50, 52, 54, 55, 57, 59, 60, 61,
63, 64, 66, 67, 68, 69, 71, 72,
73, 74, 75, 76, 77, 79, 80, 81,
82, 83, 84, 84, 85, 86, 87, 88,
89, 90, 91, 92, 92, 93, 94, 95,
96, 96, 97, 98, 99, 99, 100, 101,
101, 102, 103, 103, 104, 105, 105, 106,
107, 107, 108, 109, 109, 110, 110, 111,
112, 112, 113, 113, 114, 114, 115, 115,
116, 117, 117, 118, 118, 119, 119, 120,
120, 121, 121, 122, 122, 123, 123, 123,
124, 124, 125, 125, 126, 126, 127, 127};
void OPLio::WriteVolume(uint32_t channel, struct genmidi_voice_t *voice, uint32_t vol1, uint32_t vol2, uint32_t vol3)
{
if (voice != nullptr)
{
uint32_t full_volume = volumetable[MIN<uint32_t>(127, (uint32_t)((uint64_t)vol1*vol2*vol3) / (127 * 127))];
int reg_volume2 = ((0x3f - voice->carrier.level) * full_volume) / 128;
reg_volume2 = (0x3f - reg_volume2) | voice->carrier.scale;
WriteOperator(OPL_REGS_LEVEL, channel, 1, reg_volume2);
int reg_volume1;
if (voice->feedback & 0x01)
{
// Chocolate Doom says:
// If we are using non-modulated feedback mode, we must set the
// volume for both voices.
// Note that the same register volume value is written for
// both voices, always calculated from the carrier's level
// value.
// But Muslib does it differently than the comment above states. Which one is correct?
reg_volume1 = ((0x3f - voice->modulator.level) * full_volume) / 128;
reg_volume1 = (0x3f - reg_volume1) | voice->modulator.scale;
}
else
{
reg_volume1 = voice->modulator.level | voice->modulator.scale;
}
WriteOperator(OPL_REGS_LEVEL, channel, 0,reg_volume1);
}
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
void OPLio::WritePan(uint32_t channel, struct genmidi_voice_t *voice, int pan)
{
if (voice != 0)
{
WriteValue(OPL_REGS_FEEDBACK, channel, voice->feedback | (pan >= 28 ? 0x20 : 0) | (pan <= 100 ? 0x10 : 0));
// Set real panning if we're using emulated chips.
int chanper = IsOPL3 ? OPL3_NUM_VOICES : OPL_NUM_VOICES;
int which = channel / chanper;
if (chips[which] != NULL)
{
// This is the MIDI-recommended pan formula. 0 and 1 are
// both hard left so that 64 can be perfectly center.
double level = (pan <= 1) ? 0 : (pan - 1) / 126.0;
chips[which]->SetPanning(channel % chanper,
(float)cos(HALF_PI * level), (float)sin(HALF_PI * level));
}
}
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
void OPLio::WriteTremolo(uint32_t channel, struct genmidi_voice_t *voice, bool vibrato)
{
int val1 = voice->modulator.tremolo, val2 = voice->carrier.tremolo;
if (vibrato)
{
if (voice->feedback & 1) val1 |= 0x40;
val2 |= 0x40;
}
WriteOperator(OPL_REGS_TREMOLO, channel, 1, val2);
WriteOperator(OPL_REGS_TREMOLO, channel, 0, val2);
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
void OPLio::MuteChannel(uint32_t channel)
{
WriteOperator(OPL_REGS_LEVEL, channel, 1, NO_VOLUME);
WriteOperator(OPL_REGS_ATTACK, channel, 1, MAX_ATTACK_DECAY);
WriteOperator(OPL_REGS_SUSTAIN, channel, 1, NO_SUSTAIN_MAX_RELEASE);
WriteOperator(OPL_REGS_LEVEL, channel, 0, NO_VOLUME);
WriteOperator(OPL_REGS_ATTACK, channel, 0, MAX_ATTACK_DECAY);
WriteOperator(OPL_REGS_SUSTAIN, channel, 0, NO_SUSTAIN_MAX_RELEASE);
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
void OPLio::LoadOperatorData(uint32_t channel, int op_index, genmidi_op_t *data, bool max_level, bool vibrato)
{
// The scale and level fields must be combined for the level register.
// For the carrier wave we always set the maximum level.
int level = data->scale;
if (max_level) level |= 0x3f;
else level |= data->level;
int tremolo = data->tremolo;
if (vibrato) tremolo |= 0x40;
WriteOperator(OPL_REGS_LEVEL, channel, op_index, level);
WriteOperator(OPL_REGS_TREMOLO, channel, op_index, tremolo);
WriteOperator(OPL_REGS_ATTACK, channel, op_index, data->attack);
WriteOperator(OPL_REGS_SUSTAIN, channel, op_index, data->sustain);
WriteOperator(OPL_REGS_WAVEFORM, channel, op_index, data->waveform);
}
//----------------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------------
void OPLio::WriteInstrument(uint32_t channel, struct genmidi_voice_t *voice, bool vibrato)
{
bool modulating = (voice->feedback & 0x01) == 0;
// Doom loads the second operator first, then the first.
// The carrier is set to minimum volume until the voice volume
// is set later. If we are not using modulating mode, we must set both to minimum volume.
LoadOperatorData(channel, 1, &voice->carrier, true, vibrato);
LoadOperatorData(channel, 0, &voice->modulator, !modulating, vibrato && modulating);
// The feedback register is written by the calling code.
}

110
src/sound/oplsynth/oplio.h Normal file
View file

@ -0,0 +1,110 @@
#pragma once
enum
{
// Operator registers (21 of each):
OPL_REGS_TREMOLO = 0x20,
OPL_REGS_LEVEL = 0x40,
OPL_REGS_ATTACK = 0x60,
OPL_REGS_SUSTAIN = 0x80,
OPL_REGS_WAVEFORM = 0xE0,
// Voice registers (9 of each):
OPL_REGS_FREQ_1 = 0xA0,
OPL_REGS_FREQ_2 = 0xB0,
OPL_REGS_FEEDBACK = 0xC0,
};
enum
{
OPL_REG_WAVEFORM_ENABLE = 0x01,
OPL_REG_TIMER1 = 0x02,
OPL_REG_TIMER2 = 0x03,
OPL_REG_TIMER_CTRL = 0x04,
OPL_REG_FM_MODE = 0x08,
OPL_REG_PERCUSSION_MODE = 0xBD,
OPL_REG_OPL3_ENABLE = 0x105,
OPL_REG_4OPMODE_ENABLE = 0x104,
};
enum
{
NO_VOLUME = 0x3f,
MAX_ATTACK_DECAY = 0xff,
NO_SUSTAIN_MAX_RELEASE = 0xf,
WAVEFORM_ENABLED = 0x20
};
enum
{
OPL_NUM_VOICES = 9,
OPL3_NUM_VOICES = 18,
MAXOPL2CHIPS = 8,
NUM_VOICES = (OPL_NUM_VOICES * MAXOPL2CHIPS),
NUM_CHANNELS = 16,
CHAN_PERCUSSION = 15,
VIBRATO_THRESHOLD = 40,
MIN_SUSTAIN = 0x40,
HIGHEST_NOTE = 127,
};
struct genmidi_voice_t;
struct OPLio
{
virtual ~OPLio();
void WriteOperator(uint32_t regbase, uint32_t channel, int index, uint8_t data2);
void LoadOperatorData(uint32_t channel, int op_index, genmidi_op_t *op_data, bool maxlevel, bool vibrato);
void WriteValue(uint32_t regbase, uint32_t channel, uint8_t value);
void WriteFrequency(uint32_t channel, uint32_t freq, uint32_t octave, uint32_t keyon);
void WriteVolume(uint32_t channel, genmidi_voice_t *voice, uint32_t v1, uint32_t v2, uint32_t v3);
void WritePan(uint32_t channel, genmidi_voice_t *voice, int pan);
void WriteTremolo(uint32_t channel, genmidi_voice_t *voice, bool vibrato);
void WriteInstrument(uint32_t channel, genmidi_voice_t *voice, bool vibrato);
void WriteInitState(bool opl3);
void MuteChannel(uint32_t chan);
void StopPlayback();
virtual int Init(uint32_t numchips, bool stereo = false, bool initopl3 = false);
virtual void Reset();
virtual void WriteRegister(int which, uint32_t reg, uint8_t data);
virtual void SetClockRate(double samples_per_tick);
virtual void WriteDelay(int ticks);
class OPLEmul *chips[OPL_NUM_VOICES];
uint32_t NumChannels;
uint32_t NumChips;
bool IsOPL3;
};
struct DiskWriterIO : public OPLio
{
DiskWriterIO(const char *filename);
~DiskWriterIO();
int Init(uint32_t numchips, bool notused, bool initopl3);
void SetClockRate(double samples_per_tick);
void WriteDelay(int ticks);
FString Filename;
};
struct OPLChannel
{
uint32_t Instrument;
uint8_t Volume;
uint8_t Panning;
int8_t Pitch;
uint8_t Sustain;
bool Vibrato;
uint8_t Expression;
uint16_t PitchSensitivity;
uint16_t RPN;
};