From 22a8d5a7f4fbc2a8eeac7ad1ec70ddac0b88d015 Mon Sep 17 00:00:00 2001 From: Samuel Gomes Date: Thu, 9 May 2024 15:00:12 +0530 Subject: [PATCH] Refactor Opal OPl3 emulator for easy reusing --- internal/c/parts/audio/build.mk | 2 +- internal/c/parts/audio/extras/build.mk | 6 +- internal/c/parts/audio/extras/radv2/opal.cpp | 185 +--------------- internal/c/parts/audio/extras/radv2/opal.h | 205 ++++++++++++++++++ .../c/parts/audio/extras/radv2_ma_vtable.cpp | 8 +- 5 files changed, 217 insertions(+), 189 deletions(-) create mode 100644 internal/c/parts/audio/extras/radv2/opal.h diff --git a/internal/c/parts/audio/build.mk b/internal/c/parts/audio/build.mk index 5e10a0a7d..757222a7b 100644 --- a/internal/c/parts/audio/build.mk +++ b/internal/c/parts/audio/build.mk @@ -13,7 +13,7 @@ MINIAUDIO_REAL_OBJS := $(patsubst %.cpp,$(PATH_INTERNAL_C)/parts/audio/%.o,$(MIN MINIAUDIO_STUB_OBJS := $(patsubst %.cpp,$(PATH_INTERNAL_C)/parts/audio/%.o,$(MINIAUDIO_STUB_SRCS)) ifdef DEP_AUDIO_MINIAUDIO - MINIAUDIO_OBJS := $(MINIAUDIO_REAL_OBJS) $(MA_VTABLES_OBJS) $(HIVELY_OBJS) $(LIBXMP_LIB) + MINIAUDIO_OBJS := $(MINIAUDIO_REAL_OBJS) $(MA_VTABLES_OBJS) $(HIVELY_OBJS) $(OPAL_OBJS) $(LIBXMP_LIB) ifdef DEP_AUDIO_DECODE_MIDI MINIAUDIO_OBJS += $(MIDI_MA_VTABLES_OBJS) diff --git a/internal/c/parts/audio/extras/build.mk b/internal/c/parts/audio/extras/build.mk index 8a819fcff..69d39813a 100644 --- a/internal/c/parts/audio/extras/build.mk +++ b/internal/c/parts/audio/extras/build.mk @@ -44,6 +44,10 @@ HIVELY_SRCS := hvl_replay.c HIVELY_OBJS += $(patsubst %.c,$(PATH_INTERNAL_C)/parts/audio/extras/hivelytracker/%.o,$(HIVELY_SRCS)) +OPAL_SRCS := opal.cpp + +OPAL_OBJS += $(patsubst %.cpp,$(PATH_INTERNAL_C)/parts/audio/extras/radv2/%.o,$(OPAL_SRCS)) + $(PATH_INTERNAL_C)/parts/audio/extras/hivelytracker/%.o: $(PATH_INTERNAL_C)/parts/audio/extras/hivelytracker/%.c $(CC) -O2 $(CFLAGS) -Wall $< -c -o $@ @@ -88,5 +92,5 @@ $(PATH_INTERNAL_TEMP)/soundfont.o: $(PATH_INTERNAL_TEMP)/soundfont.sf2 endif endif -CLEAN_LIST += $(LIBXMP_LIB) $(LIBXMP_OBJS) $(HIVELY_OBJS) $(MA_VTABLES_OBJS) $(MIDI_MA_VTABLES_OBJS) $(MIDI_MA_VTABLES_STUB_OBJS) +CLEAN_LIST += $(LIBXMP_LIB) $(LIBXMP_OBJS) $(HIVELY_OBJS) $(OPAL_OBJS) $(MA_VTABLES_OBJS) $(MIDI_MA_VTABLES_OBJS) $(MIDI_MA_VTABLES_STUB_OBJS) diff --git a/internal/c/parts/audio/extras/radv2/opal.cpp b/internal/c/parts/audio/extras/radv2/opal.cpp index 3f86f1cbe..51111c725 100644 --- a/internal/c/parts/audio/extras/radv2/opal.cpp +++ b/internal/c/parts/audio/extras/radv2/opal.cpp @@ -20,187 +20,8 @@ // Additional fixes by JP Cimalando. // Soft panning support by Wohlstand. -#include +#include "opal.h" -//================================================================================================== -// Opal class. -//================================================================================================== -class Opal { - - class Channel; - - // Various constants - enum { - OPL3SampleRate = 49716, - NumChannels = 18, - NumOperators = 36, - - EnvOff = -1, - EnvAtt, - EnvDec, - EnvSus, - EnvRel - }; - - // A single FM operator - class Operator { - - public: - Operator(); - void SetMaster(Opal *opal) { Master = opal; } - void SetChannel(Channel *chan) { Chan = chan; } - - int16_t Output(uint16_t keyscalenum, uint32_t phase_step, int16_t vibrato, int16_t mod = 0, int16_t fbshift = 0); - - void SetKeyOn(bool on); - void SetTremoloEnable(bool on); - void SetVibratoEnable(bool on); - void SetSustainMode(bool on); - void SetEnvelopeScaling(bool on); - void SetFrequencyMultiplier(uint16_t scale); - void SetKeyScale(uint16_t scale); - void SetOutputLevel(uint16_t level); - void SetAttackRate(uint16_t rate); - void SetDecayRate(uint16_t rate); - void SetSustainLevel(uint16_t level); - void SetReleaseRate(uint16_t rate); - void SetWaveform(uint16_t wave); - - void ComputeRates(); - void ComputeKeyScaleLevel(); - - protected: - Opal *Master; // Master object - Channel *Chan; // Owning channel - uint32_t Phase; // The current offset in the selected waveform - uint16_t Waveform; // The waveform id this operator is using - uint16_t FreqMultTimes2; // Frequency multiplier * 2 - int EnvelopeStage; // Which stage the envelope is at (see Env* enums above) - int16_t EnvelopeLevel; // 0 - $1FF, 0 being the loudest - uint16_t OutputLevel; // 0 - $FF - uint16_t AttackRate; - uint16_t DecayRate; - uint16_t SustainLevel; - uint16_t ReleaseRate; - uint16_t AttackShift; - uint16_t AttackMask; - uint16_t AttackAdd; - const uint16_t *AttackTab; - uint16_t DecayShift; - uint16_t DecayMask; - uint16_t DecayAdd; - const uint16_t *DecayTab; - uint16_t ReleaseShift; - uint16_t ReleaseMask; - uint16_t ReleaseAdd; - const uint16_t *ReleaseTab; - uint16_t KeyScaleShift; - uint16_t KeyScaleLevel; - int16_t Out[2]; - bool KeyOn; - bool KeyScaleRate; // Affects envelope rate scaling - bool SustainMode; // Whether to sustain during the sustain phase, or release instead - bool TremoloEnable; - bool VibratoEnable; - }; - - // A single channel, which can contain two or more operators - class Channel { - - public: - Channel(); - void SetMaster(Opal *opal) { Master = opal; } - void SetOperators(Operator *a, Operator *b, Operator *c, Operator *d) { - Op[0] = a; - Op[1] = b; - Op[2] = c; - Op[3] = d; - if (a) - a->SetChannel(this); - if (b) - b->SetChannel(this); - if (c) - c->SetChannel(this); - if (d) - d->SetChannel(this); - } - - void Output(int16_t &left, int16_t &right); - void SetEnable(bool on) { Enable = on; } - void SetChannelPair(Channel *pair) { ChannelPair = pair; } - - void SetFrequencyLow(uint16_t freq); - void SetFrequencyHigh(uint16_t freq); - void SetKeyOn(bool on); - void SetOctave(uint16_t oct); - void SetLeftEnable(bool on); - void SetRightEnable(bool on); - void SetPan(uint8_t pan); - void SetFeedback(uint16_t val); - void SetModulationType(uint16_t type); - - uint16_t GetFreq() const { return Freq; } - uint16_t GetOctave() const { return Octave; } - uint16_t GetKeyScaleNumber() const { return KeyScaleNumber; } - uint16_t GetModulationType() const { return ModulationType; } - Channel *GetChannelPair() const { return ChannelPair; } - - void ComputeKeyScaleNumber(); - - protected: - void ComputePhaseStep(); - - Operator *Op[4]; - - Opal *Master; // Master object - uint16_t Freq; // Frequency; actually it's a phase stepping value - uint16_t Octave; // Also known as "block" in Yamaha parlance - uint32_t PhaseStep; - uint16_t KeyScaleNumber; - uint16_t FeedbackShift; - uint16_t ModulationType; - Channel *ChannelPair; - bool Enable; - bool LeftEnable, RightEnable; - uint16_t LeftPan, RightPan; - }; - - public: - Opal(int sample_rate); - Opal(const Opal &) = delete; - Opal(Opal &&) = delete; - ~Opal(); - - void SetSampleRate(int sample_rate); - void Port(uint16_t reg_num, uint8_t val); - void Pan(uint16_t reg_num, uint8_t pan); - void Sample(int16_t *left, int16_t *right); - - protected: - void Init(int sample_rate); - void Output(int16_t &left, int16_t &right); - - int32_t SampleRate; - int32_t SampleAccum; - int16_t LastOutput[2], CurrOutput[2]; - Channel Chan[NumChannels]; - Operator Op[NumOperators]; - // uint16_t ExpTable[256]; - // uint16_t LogSinTable[256]; - uint16_t Clock; - uint16_t TremoloClock; - uint16_t TremoloLevel; - uint16_t VibratoTick; - uint16_t VibratoClock; - bool NoteSel; - bool TremoloDepth; - bool VibratoDepth; - - static const uint16_t RateTables[4][8]; - static const uint16_t ExpTable[256]; - static const uint16_t LogSinTable[256]; - static const uint16_t PanLawTable[128]; -}; // clang-format off //-------------------------------------------------------------------------------------------------- const uint16_t Opal::RateTables[4][8] = { @@ -1177,8 +998,8 @@ void Opal::Operator::SetFrequencyMultiplier(uint16_t scale) { //================================================================================================== void Opal::Operator::SetKeyScale(uint16_t scale) { - static const uint8_t kslShift[4] = {8, 1, 2, 0}; - KeyScaleShift = kslShift[scale & 3]; + static const uint8_t KeyScaleShiftTable[4] = {8, 1, 2, 0}; + KeyScaleShift = KeyScaleShiftTable[scale & 3]; ComputeKeyScaleLevel(); } diff --git a/internal/c/parts/audio/extras/radv2/opal.h b/internal/c/parts/audio/extras/radv2/opal.h new file mode 100644 index 000000000..3ec69c023 --- /dev/null +++ b/internal/c/parts/audio/extras/radv2/opal.h @@ -0,0 +1,205 @@ +/* + + The Opal OPL3 emulator (header). + + Note: this is not a complete emulator, just enough for Reality Adlib Tracker tunes. + + Missing features compared to a real OPL3: + + - Timers/interrupts + - OPL3 enable bit (it defaults to always on) + - CSW mode + - Test register + - Percussion mode + +*/ + +// This is the Opal OPL3 emulator from Reality Adlib Tracker v2.0a (http://www.3eality.com/productions/reality-adlib-tracker). +// It was released by Shayde/Reality into the public domain. +// Minor modifications to silence some warnings and fix a bug in the envelope generator have been applied. +// Additional fixes by JP Cimalando. +// Soft panning support by Wohlstand. + +#pragma once + +#include + +//================================================================================================== +// Opal class. +//================================================================================================== +class Opal { + + class Channel; + + // Various constants + enum { + OPL3SampleRate = 49716, + NumChannels = 18, + NumOperators = 36, + + EnvOff = -1, + EnvAtt, + EnvDec, + EnvSus, + EnvRel + }; + + // A single FM operator + class Operator { + + public: + Operator(); + void SetMaster(Opal *opal) { Master = opal; } + void SetChannel(Channel *chan) { Chan = chan; } + + int16_t Output(uint16_t keyscalenum, uint32_t phase_step, int16_t vibrato, int16_t mod = 0, int16_t fbshift = 0); + + void SetKeyOn(bool on); + void SetTremoloEnable(bool on); + void SetVibratoEnable(bool on); + void SetSustainMode(bool on); + void SetEnvelopeScaling(bool on); + void SetFrequencyMultiplier(uint16_t scale); + void SetKeyScale(uint16_t scale); + void SetOutputLevel(uint16_t level); + void SetAttackRate(uint16_t rate); + void SetDecayRate(uint16_t rate); + void SetSustainLevel(uint16_t level); + void SetReleaseRate(uint16_t rate); + void SetWaveform(uint16_t wave); + + void ComputeRates(); + void ComputeKeyScaleLevel(); + + protected: + Opal *Master; // Master object + Channel *Chan; // Owning channel + uint32_t Phase; // The current offset in the selected waveform + uint16_t Waveform; // The waveform id this operator is using + uint16_t FreqMultTimes2; // Frequency multiplier * 2 + int EnvelopeStage; // Which stage the envelope is at (see Env* enums above) + int16_t EnvelopeLevel; // 0 - $1FF, 0 being the loudest + uint16_t OutputLevel; // 0 - $FF + uint16_t AttackRate; + uint16_t DecayRate; + uint16_t SustainLevel; + uint16_t ReleaseRate; + uint16_t AttackShift; + uint16_t AttackMask; + uint16_t AttackAdd; + const uint16_t *AttackTab; + uint16_t DecayShift; + uint16_t DecayMask; + uint16_t DecayAdd; + const uint16_t *DecayTab; + uint16_t ReleaseShift; + uint16_t ReleaseMask; + uint16_t ReleaseAdd; + const uint16_t *ReleaseTab; + uint16_t KeyScaleShift; + uint16_t KeyScaleLevel; + int16_t Out[2]; + bool KeyOn; + bool KeyScaleRate; // Affects envelope rate scaling + bool SustainMode; // Whether to sustain during the sustain phase, or release instead + bool TremoloEnable; + bool VibratoEnable; + }; + + // A single channel, which can contain two or more operators + class Channel { + + public: + Channel(); + void SetMaster(Opal *opal) { Master = opal; } + void SetOperators(Operator *a, Operator *b, Operator *c, Operator *d) { + Op[0] = a; + Op[1] = b; + Op[2] = c; + Op[3] = d; + if (a) + a->SetChannel(this); + if (b) + b->SetChannel(this); + if (c) + c->SetChannel(this); + if (d) + d->SetChannel(this); + } + + void Output(int16_t &left, int16_t &right); + void SetEnable(bool on) { Enable = on; } + void SetChannelPair(Channel *pair) { ChannelPair = pair; } + + void SetFrequencyLow(uint16_t freq); + void SetFrequencyHigh(uint16_t freq); + void SetKeyOn(bool on); + void SetOctave(uint16_t oct); + void SetLeftEnable(bool on); + void SetRightEnable(bool on); + void SetPan(uint8_t pan); + void SetFeedback(uint16_t val); + void SetModulationType(uint16_t type); + + uint16_t GetFreq() const { return Freq; } + uint16_t GetOctave() const { return Octave; } + uint16_t GetKeyScaleNumber() const { return KeyScaleNumber; } + uint16_t GetModulationType() const { return ModulationType; } + Channel *GetChannelPair() const { return ChannelPair; } + + void ComputeKeyScaleNumber(); + + protected: + void ComputePhaseStep(); + + Operator *Op[4]; + + Opal *Master; // Master object + uint16_t Freq; // Frequency; actually it's a phase stepping value + uint16_t Octave; // Also known as "block" in Yamaha parlance + uint32_t PhaseStep; + uint16_t KeyScaleNumber; + uint16_t FeedbackShift; + uint16_t ModulationType; + Channel *ChannelPair; + bool Enable; + bool LeftEnable, RightEnable; + uint16_t LeftPan, RightPan; + }; + + public: + Opal(int sample_rate); + Opal(const Opal &) = delete; + Opal(Opal &&) = delete; + ~Opal(); + + void SetSampleRate(int sample_rate); + void Port(uint16_t reg_num, uint8_t val); + void Pan(uint16_t reg_num, uint8_t pan); + void Sample(int16_t *left, int16_t *right); + + protected: + void Init(int sample_rate); + void Output(int16_t &left, int16_t &right); + + int32_t SampleRate; + int32_t SampleAccum; + int16_t LastOutput[2], CurrOutput[2]; + Channel Chan[NumChannels]; + Operator Op[NumOperators]; + // uint16_t ExpTable[256]; + // uint16_t LogSinTable[256]; + uint16_t Clock; + uint16_t TremoloClock; + uint16_t TremoloLevel; + uint16_t VibratoTick; + uint16_t VibratoClock; + bool NoteSel; + bool TremoloDepth; + bool VibratoDepth; + + static const uint16_t RateTables[4][8]; + static const uint16_t ExpTable[256]; + static const uint16_t LogSinTable[256]; + static const uint16_t PanLawTable[128]; +}; diff --git a/internal/c/parts/audio/extras/radv2_ma_vtable.cpp b/internal/c/parts/audio/extras/radv2_ma_vtable.cpp index db2c5ecaf..4369e7bec 100644 --- a/internal/c/parts/audio/extras/radv2_ma_vtable.cpp +++ b/internal/c/parts/audio/extras/radv2_ma_vtable.cpp @@ -19,7 +19,7 @@ #include #include -#include "radv2/opal.cpp" +#include "radv2/opal.h" #define RAD_DETECT_REPEATS 1 #include "radv2/player20.cpp" #include "radv2/validate20.cpp" @@ -372,8 +372,7 @@ static ma_result ma_radv2_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell // Initialize the player // We'll use a lambda here and pass the pRadv2 pointer using 'arg' - pRadv2->player->Init( - pRadv2->tune, [](void *arg, uint16_t reg, uint8_t data) { ((ma_radv2 *)arg)->adlib->Port(reg, data); }, pRadv2); + pRadv2->player->Init(pRadv2->tune, [](void *arg, uint16_t reg, uint8_t data) { ((ma_radv2 *)arg)->adlib->Port(reg, data); }, pRadv2); // Get the playback rate if (pRadv2->player->GetHertz() < 0) { @@ -485,8 +484,7 @@ static ma_result ma_radv2_init_file(const char *pFilePath, const ma_decoding_bac // Initialize the player // We'll use a lambda here and pass the pRadv2 pointer using 'arg' - pRadv2->player->Init( - pRadv2->tune, [](void *arg, uint16_t reg, uint8_t data) { ((ma_radv2 *)arg)->adlib->Port(reg, data); }, pRadv2); + pRadv2->player->Init(pRadv2->tune, [](void *arg, uint16_t reg, uint8_t data) { ((ma_radv2 *)arg)->adlib->Port(reg, data); }, pRadv2); // Get the playback rate if (pRadv2->player->GetHertz() < 0) {