mirror of
https://github.com/ZDoom/gzdoom-gles.git
synced 2024-11-11 15:22:15 +00:00
- 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:
parent
4f67dc4f01
commit
ba5721f98a
18 changed files with 1233 additions and 1196 deletions
|
@ -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
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
*/
|
||||
|
||||
#include "i_musicinterns.h"
|
||||
#include "oplsynth/muslib.h"
|
||||
#include "oplsynth/musicblock.h"
|
||||
#include "oplsynth/opl.h"
|
||||
|
||||
static bool OPL_Active;
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
#include "doomtype.h"
|
||||
#include "../opl.h"
|
||||
#include "../muslib.h"
|
||||
#include "../musicblock.h"
|
||||
#include <math.h>
|
||||
#include "m_random.h"
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
47
src/sound/oplsynth/genmidi.h
Normal file
47
src/sound/oplsynth/genmidi.h
Normal 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
|
||||
};
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
454
src/sound/oplsynth/musicblock.cpp
Normal file
454
src/sound/oplsynth/musicblock.cpp
Normal 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);
|
||||
}
|
||||
}
|
58
src/sound/oplsynth/musicblock.h
Normal file
58
src/sound/oplsynth/musicblock.h
Normal 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;
|
||||
|
||||
};
|
|
@ -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_
|
|
@ -30,7 +30,7 @@
|
|||
//version 1.6
|
||||
|
||||
#include "opl.h"
|
||||
#include "muslib.h"
|
||||
#include "musicblock.h"
|
||||
|
||||
typedef uintptr_t Bitu;
|
||||
typedef intptr_t Bits;
|
||||
|
|
|
@ -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
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#include "critsec.h"
|
||||
#include "muslib.h"
|
||||
|
||||
class FileReader;
|
||||
|
||||
class OPLmusicBlock : public musicBlock
|
||||
{
|
||||
public:
|
||||
|
|
493
src/sound/oplsynth/oplio.cpp
Normal file
493
src/sound/oplsynth/oplio.cpp
Normal 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
110
src/sound/oplsynth/oplio.h
Normal 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;
|
||||
};
|
Loading…
Reference in a new issue