mirror of
https://github.com/QB64-Phoenix-Edition/QB64pe.git
synced 2024-09-20 02:04:44 +00:00
Refactor Opal OPl3 emulator for easy reusing
This commit is contained in:
parent
2dcbe4be90
commit
22a8d5a7f4
5 changed files with 217 additions and 189 deletions
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -20,187 +20,8 @@
|
|||
// Additional fixes by JP Cimalando.
|
||||
// Soft panning support by Wohlstand.
|
||||
|
||||
#include <cstdint>
|
||||
#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();
|
||||
}
|
||||
|
||||
|
|
205
internal/c/parts/audio/extras/radv2/opal.h
Normal file
205
internal/c/parts/audio/extras/radv2/opal.h
Normal file
|
@ -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 <cstdint>
|
||||
|
||||
//==================================================================================================
|
||||
// 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];
|
||||
};
|
|
@ -19,7 +19,7 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#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) {
|
||||
|
|
Loading…
Reference in a new issue