From ba5721f98a0efce8310d89f18ad10cf45d98bd90 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sun, 16 Apr 2017 01:36:53 +0200 Subject: [PATCH] - 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. --- src/CMakeLists.txt | 4 +- .../mididevices/music_opl_mididevice.cpp | 73 +-- .../music_opldumper_mididevice.cpp | 10 +- src/sound/musicformats/music_opl.cpp | 2 +- src/sound/oplsynth/dosbox/opl.cpp | 2 +- src/sound/oplsynth/fmopl.cpp | 14 +- src/sound/oplsynth/genmidi.h | 47 ++ src/sound/oplsynth/mlopl.cpp | 485 ----------------- src/sound/oplsynth/mlopl_io.cpp | 380 -------------- src/sound/oplsynth/musicblock.cpp | 454 ++++++++++++++++ src/sound/oplsynth/musicblock.h | 58 +++ src/sound/oplsynth/muslib.h | 267 ---------- src/sound/oplsynth/nukedopl3.h | 2 +- src/sound/oplsynth/opl.h | 2 + src/sound/oplsynth/opl_mus_player.cpp | 24 +- src/sound/oplsynth/opl_mus_player.h | 2 + src/sound/oplsynth/oplio.cpp | 493 ++++++++++++++++++ src/sound/oplsynth/oplio.h | 110 ++++ 18 files changed, 1233 insertions(+), 1196 deletions(-) create mode 100644 src/sound/oplsynth/genmidi.h delete mode 100644 src/sound/oplsynth/mlopl.cpp delete mode 100644 src/sound/oplsynth/mlopl_io.cpp create mode 100644 src/sound/oplsynth/musicblock.cpp create mode 100644 src/sound/oplsynth/musicblock.h delete mode 100644 src/sound/oplsynth/muslib.h create mode 100644 src/sound/oplsynth/oplio.cpp create mode 100644 src/sound/oplsynth/oplio.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index da79cd4086..54dce36caf 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/sound/mididevices/music_opl_mididevice.cpp b/src/sound/mididevices/music_opl_mididevice.cpp index e61a5c10d0..fe09e0448f 100644 --- a/src/sound/mididevices/music_opl_mididevice.cpp +++ b/src/sound/mididevices/music_opl_mididevice.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'; } diff --git a/src/sound/mididevices/music_opldumper_mididevice.cpp b/src/sound/mididevices/music_opldumper_mididevice.cpp index cf7000d4c6..e301a30548 100644 --- a/src/sound/mididevices/music_opldumper_mididevice.cpp +++ b/src/sound/mididevices/music_opldumper_mididevice.cpp @@ -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; } diff --git a/src/sound/musicformats/music_opl.cpp b/src/sound/musicformats/music_opl.cpp index 85f4b815b9..0f949fae00 100644 --- a/src/sound/musicformats/music_opl.cpp +++ b/src/sound/musicformats/music_opl.cpp @@ -32,7 +32,7 @@ */ #include "i_musicinterns.h" -#include "oplsynth/muslib.h" +#include "oplsynth/musicblock.h" #include "oplsynth/opl.h" static bool OPL_Active; diff --git a/src/sound/oplsynth/dosbox/opl.cpp b/src/sound/oplsynth/dosbox/opl.cpp index 25b4462109..67fcefc0be 100644 --- a/src/sound/oplsynth/dosbox/opl.cpp +++ b/src/sound/oplsynth/dosbox/opl.cpp @@ -26,7 +26,7 @@ #include "doomtype.h" #include "../opl.h" -#include "../muslib.h" +#include "../musicblock.h" #include #include "m_random.h" diff --git a/src/sound/oplsynth/fmopl.cpp b/src/sound/oplsynth/fmopl.cpp index 0e270e6f5f..1bbd74dc48 100644 --- a/src/sound/oplsynth/fmopl.cpp +++ b/src/sound/oplsynth/fmopl.cpp @@ -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() diff --git a/src/sound/oplsynth/genmidi.h b/src/sound/oplsynth/genmidi.h new file mode 100644 index 0000000000..76ac26a96d --- /dev/null +++ b/src/sound/oplsynth/genmidi.h @@ -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 +}; + diff --git a/src/sound/oplsynth/mlopl.cpp b/src/sound/oplsynth/mlopl.cpp deleted file mode 100644 index 87cd7f450f..0000000000 --- a/src/sound/oplsynth/mlopl.cpp +++ /dev/null @@ -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 -#include -#ifdef _WIN32 -#include -#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; -} diff --git a/src/sound/oplsynth/mlopl_io.cpp b/src/sound/oplsynth/mlopl_io.cpp deleted file mode 100644 index ef89bfc271..0000000000 --- a/src/sound/oplsynth/mlopl_io.cpp +++ /dev/null @@ -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 -#ifdef _WIN32 -#include -#include -#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; - } - } -} diff --git a/src/sound/oplsynth/musicblock.cpp b/src/sound/oplsynth/musicblock.cpp new file mode 100644 index 0000000000..e0a2314762 --- /dev/null +++ b/src/sound/oplsynth/musicblock.cpp @@ -0,0 +1,454 @@ +#include +#include +#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); + } +} diff --git a/src/sound/oplsynth/musicblock.h b/src/sound/oplsynth/musicblock.h new file mode 100644 index 0000000000..1f13aa01f3 --- /dev/null +++ b/src/sound/oplsynth/musicblock.h @@ -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; + +}; diff --git a/src/sound/oplsynth/muslib.h b/src/sound/oplsynth/muslib.h deleted file mode 100644 index 9d5bdfcbdc..0000000000 --- a/src/sound/oplsynth/muslib.h +++ /dev/null @@ -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_ diff --git a/src/sound/oplsynth/nukedopl3.h b/src/sound/oplsynth/nukedopl3.h index 17ebbe4d2c..3c3c65895a 100644 --- a/src/sound/oplsynth/nukedopl3.h +++ b/src/sound/oplsynth/nukedopl3.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; diff --git a/src/sound/oplsynth/opl.h b/src/sound/oplsynth/opl.h index 1a3842fd3b..d5faa136c3 100644 --- a/src/sound/oplsynth/opl.h +++ b/src/sound/oplsynth/opl.h @@ -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 \ No newline at end of file diff --git a/src/sound/oplsynth/opl_mus_player.cpp b/src/sound/oplsynth/opl_mus_player.cpp index 55dd132cef..4829f9cfae 100644 --- a/src/sound/oplsynth/opl_mus_player.cpp +++ b/src/sound/oplsynth/opl_mus_player.cpp @@ -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(); } diff --git a/src/sound/oplsynth/opl_mus_player.h b/src/sound/oplsynth/opl_mus_player.h index bc76e06823..a8ace69d2b 100644 --- a/src/sound/oplsynth/opl_mus_player.h +++ b/src/sound/oplsynth/opl_mus_player.h @@ -1,6 +1,8 @@ #include "critsec.h" #include "muslib.h" +class FileReader; + class OPLmusicBlock : public musicBlock { public: diff --git a/src/sound/oplsynth/oplio.cpp b/src/sound/oplsynth/oplio.cpp new file mode 100644 index 0000000000..68b769db48 --- /dev/null +++ b/src/sound/oplsynth/oplio.cpp @@ -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(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. +} diff --git a/src/sound/oplsynth/oplio.h b/src/sound/oplsynth/oplio.h new file mode 100644 index 0000000000..94ad8b2f0d --- /dev/null +++ b/src/sound/oplsynth/oplio.h @@ -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; +};