mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-12-11 13:11:07 +00:00
ceec12056a
Added ability to switch emulator and it's accuracy level ("enabling of 'run at PCM rate' reduces accuracy, and also reduces CPU usage") Added draft code for future external banks support (WOPL format for ADLMIDI and WOPN format for OPNMIDI) ADLMIDI 1.3.3 2018-06-19 * Fixed an inability to load another custom bank without of library re-initialization * Optimizing the MIDI banks management system for MultiBanks (Thanks to [Jean Pierre Cimalando](https://github.com/jpcima) for a work!) * Fixed incorrect 4-op counter which is still catch 4-op instruments on 2-op banks * Fixed an incorrect processing of auto-flags * Fixed incorrect initial MIDI tempo when MIDI file doesn't includes the tempo event * Channel and Note Aftertouch features are now supported correctly! Aftertouch is the tremolo / vibrato, NOT A VOLUME! * Updated DosBox OPL3 emulator up to r4111 of official DosBox trunk (Thanks to [Jean Pierre Cimalando](https://github.com/jpcima) for a work!) * The automatical choosing of 4 operator channels count has been improved (Thanks to [Jean Pierre Cimalando](https://github.com/jpcima) for a work!) * Added optional HQ resampler for Nuked OPL3 emulators which does usage of Zita-Resampler library (Thanks to [Jean Pierre Cimalando](https://github.com/jpcima) for a work!) ADLMIDI 1.3.2 2018-04-24 * Added ability to disable MUS and XMI converters * Added ability to disable embedded MIDI sequencer to use library as RealTime synthesizer only or use any custom MIDI sequencer plugins. * Fixed blank instruments fallback in multi-bank support. When using non-zero bank, if instrument is blank, then, instrument will be taken from a root (I.e. zero bank). * Added support for real-time switching the emulator * Added support for CC-120 - "All sound off" on the MIDI channel * Changed logic of CC-74 Brightness to affect sound only between 0 and 64 like real XG synthesizers. Ability to turn on a full-ranged brightness (to use full 0...127 range) is kept. * Added support for different output sample formats (PCM8, PCM8U, PCM16, PCM16U, PCM32, PCM32U, Float32, and Float64) (Thanks to [Jean Pierre Cimalando](https://github.com/jpcima) for a work!) * Reworked MIDI channels management to avoid any memory reallocations while music processing for a hard real time. (Thanks to [Jean Pierre Cimalando](https://github.com/jpcima) for a work!) OPNMIDI 1.3.0 2018-06-19 * Optimizing the MIDI banks management system for MultiBanks (Thanks to [Jean Pierre Cimalando](https://github.com/jpcima) for a work!) * Fixed incorrect initial MIDI tempo when MIDI file doesn't includes the tempo event * Fixed an incorrect processing of auto-flags * MAME YM2612 now results a more accurate sound as internal using of native sample rate makes more correct sound generation * Channel and Note Aftertouch features are now supported correctly! Aftertouch is the tremolo / vibrato, NOT A VOLUME! * Added optional HQ resampler for Nuked OPL3 emulators which does usage of Zita-Resampler library (Thanks to [Jean Pierre Cimalando](https://github.com/jpcima) for a work!) OPNMIDI 1.2.0 2018-04-24 * Added ability to disable MUS and XMI converters * Added ability to disable embedded MIDI sequencer to use library as RealTime synthesizer only or use any custom MIDI sequencer plugins. * Fixed blank instruments fallback in multi-bank support. When using non-zero bank, if instrument is blank, then, instrument will be taken from a root (I.e. zero bank). * Added support for real-time switching the emulator * Added support for MAME YM2612 Emulator * Added support for CC-120 - "All sound off" on the MIDI channel * Changed logic of CC-74 Brightness to affect sound only between 0 and 64 like real XG synthesizers. Ability to turn on a full-ranged brightness (to use full 0...127 range) is kept. * Added support for different output sample formats (PCM8, PCM8U, PCM16, PCM16U, PCM32, PCM32U, Float32, and Float64) (Thanks to [Jean Pierre Cimalando](https://github.com/jpcima) for a work!) * Reworked MIDI channels management to avoid any memory reallocations while music processing for a hard real time. (Thanks to [Jean Pierre Cimalando](https://github.com/jpcima) for a work!)
294 lines
7.5 KiB
C++
294 lines
7.5 KiB
C++
#include "opl_chip_base.h"
|
|
#include <cmath>
|
|
|
|
#if defined(ADLMIDI_ENABLE_HQ_RESAMPLER)
|
|
#include <zita-resampler/vresampler.h>
|
|
#endif
|
|
|
|
#if !defined(LIKELY) && defined(__GNUC__)
|
|
#define LIKELY(x) __builtin_expect((x), 1)
|
|
#elif !defined(LIKELY)
|
|
#define LIKELY(x) (x)
|
|
#endif
|
|
|
|
#if !defined(UNLIKELY) && defined(__GNUC__)
|
|
#define UNLIKELY(x) __builtin_expect((x), 0)
|
|
#elif !defined(UNLIKELY)
|
|
#define UNLIKELY(x) (x)
|
|
#endif
|
|
|
|
/* OPLChipBase */
|
|
|
|
inline OPLChipBase::OPLChipBase() :
|
|
m_id(0),
|
|
m_rate(44100)
|
|
{
|
|
}
|
|
|
|
inline OPLChipBase::~OPLChipBase()
|
|
{
|
|
}
|
|
|
|
/* OPLChipBaseT */
|
|
|
|
template <class T>
|
|
OPLChipBaseT<T>::OPLChipBaseT()
|
|
: OPLChipBase(),
|
|
m_runningAtPcmRate(false)
|
|
#if defined(ADLMIDI_AUDIO_TICK_HANDLER)
|
|
,
|
|
m_audioTickHandlerInstance(NULL)
|
|
#endif
|
|
{
|
|
#if defined(ADLMIDI_ENABLE_HQ_RESAMPLER)
|
|
m_resampler = new VResampler;
|
|
#endif
|
|
setupResampler(m_rate);
|
|
}
|
|
|
|
template <class T>
|
|
OPLChipBaseT<T>::~OPLChipBaseT()
|
|
{
|
|
#if defined(ADLMIDI_ENABLE_HQ_RESAMPLER)
|
|
delete m_resampler;
|
|
#endif
|
|
}
|
|
|
|
template <class T>
|
|
bool OPLChipBaseT<T>::isRunningAtPcmRate() const
|
|
{
|
|
return m_runningAtPcmRate;
|
|
}
|
|
|
|
template <class T>
|
|
bool OPLChipBaseT<T>::setRunningAtPcmRate(bool r)
|
|
{
|
|
if(r != m_runningAtPcmRate)
|
|
{
|
|
if(r && !static_cast<T *>(this)->canRunAtPcmRate())
|
|
return false;
|
|
m_runningAtPcmRate = r;
|
|
static_cast<T *>(this)->setRate(m_rate);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#if defined(ADLMIDI_AUDIO_TICK_HANDLER)
|
|
template <class T>
|
|
void OPLChipBaseT<T>::setAudioTickHandlerInstance(void *instance)
|
|
{
|
|
m_audioTickHandlerInstance = instance;
|
|
}
|
|
#endif
|
|
|
|
template <class T>
|
|
void OPLChipBaseT<T>::setRate(uint32_t rate)
|
|
{
|
|
uint32_t oldRate = m_rate;
|
|
m_rate = rate;
|
|
if(rate != oldRate)
|
|
setupResampler(rate);
|
|
else
|
|
resetResampler();
|
|
}
|
|
|
|
template <class T>
|
|
uint32_t OPLChipBaseT<T>::effectiveRate() const
|
|
{
|
|
return m_runningAtPcmRate ? m_rate : (uint32_t)nativeRate;
|
|
}
|
|
|
|
template <class T>
|
|
void OPLChipBaseT<T>::reset()
|
|
{
|
|
resetResampler();
|
|
}
|
|
|
|
template <class T>
|
|
void OPLChipBaseT<T>::generate(int16_t *output, size_t frames)
|
|
{
|
|
static_cast<T *>(this)->nativePreGenerate();
|
|
for(size_t i = 0; i < frames; ++i)
|
|
{
|
|
int32_t frame[2];
|
|
static_cast<T *>(this)->resampledGenerate(frame);
|
|
for (unsigned c = 0; c < 2; ++c) {
|
|
int32_t temp = frame[c];
|
|
temp = (temp > -32768) ? temp : -32768;
|
|
temp = (temp < 32767) ? temp : 32767;
|
|
output[c] = (int16_t)temp;
|
|
}
|
|
output += 2;
|
|
}
|
|
static_cast<T *>(this)->nativePostGenerate();
|
|
}
|
|
|
|
template <class T>
|
|
void OPLChipBaseT<T>::generateAndMix(int16_t *output, size_t frames)
|
|
{
|
|
static_cast<T *>(this)->nativePreGenerate();
|
|
for(size_t i = 0; i < frames; ++i)
|
|
{
|
|
int32_t frame[2];
|
|
static_cast<T *>(this)->resampledGenerate(frame);
|
|
for (unsigned c = 0; c < 2; ++c) {
|
|
int32_t temp = (int32_t)output[c] + frame[c];
|
|
temp = (temp > -32768) ? temp : -32768;
|
|
temp = (temp < 32767) ? temp : 32767;
|
|
output[c] = (int16_t)temp;
|
|
}
|
|
output += 2;
|
|
}
|
|
static_cast<T *>(this)->nativePostGenerate();
|
|
}
|
|
|
|
template <class T>
|
|
void OPLChipBaseT<T>::generate32(int32_t *output, size_t frames)
|
|
{
|
|
static_cast<T *>(this)->nativePreGenerate();
|
|
for(size_t i = 0; i < frames; ++i)
|
|
{
|
|
static_cast<T *>(this)->resampledGenerate(output);
|
|
output += 2;
|
|
}
|
|
static_cast<T *>(this)->nativePostGenerate();
|
|
}
|
|
|
|
template <class T>
|
|
void OPLChipBaseT<T>::generateAndMix32(int32_t *output, size_t frames)
|
|
{
|
|
static_cast<T *>(this)->nativePreGenerate();
|
|
for(size_t i = 0; i < frames; ++i)
|
|
{
|
|
int32_t frame[2];
|
|
static_cast<T *>(this)->resampledGenerate(frame);
|
|
output[0] += frame[0];
|
|
output[1] += frame[1];
|
|
output += 2;
|
|
}
|
|
static_cast<T *>(this)->nativePostGenerate();
|
|
}
|
|
|
|
template <class T>
|
|
void OPLChipBaseT<T>::nativeTick(int16_t *frame)
|
|
{
|
|
#if defined(ADLMIDI_AUDIO_TICK_HANDLER)
|
|
adl_audioTickHandler(m_audioTickHandlerInstance, m_id, effectiveRate());
|
|
#endif
|
|
static_cast<T *>(this)->nativeGenerate(frame);
|
|
}
|
|
|
|
template <class T>
|
|
void OPLChipBaseT<T>::setupResampler(uint32_t rate)
|
|
{
|
|
#if defined(ADLMIDI_ENABLE_HQ_RESAMPLER)
|
|
m_resampler->setup(rate * (1.0 / 49716), 2, 48);
|
|
#else
|
|
m_oldsamples[0] = m_oldsamples[1] = 0;
|
|
m_samples[0] = m_samples[1] = 0;
|
|
m_samplecnt = 0;
|
|
m_rateratio = (int32_t)((rate << rsm_frac) / 49716);
|
|
#endif
|
|
}
|
|
|
|
template <class T>
|
|
void OPLChipBaseT<T>::resetResampler()
|
|
{
|
|
#if defined(ADLMIDI_ENABLE_HQ_RESAMPLER)
|
|
m_resampler->reset();
|
|
#else
|
|
m_oldsamples[0] = m_oldsamples[1] = 0;
|
|
m_samples[0] = m_samples[1] = 0;
|
|
m_samplecnt = 0;
|
|
#endif
|
|
}
|
|
|
|
#if defined(ADLMIDI_ENABLE_HQ_RESAMPLER)
|
|
template <class T>
|
|
void OPLChipBaseT<T>::resampledGenerate(int32_t *output)
|
|
{
|
|
if(UNLIKELY(m_runningAtPcmRate))
|
|
{
|
|
int16_t in[2];
|
|
static_cast<T *>(this)->nativeTick(in);
|
|
output[0] = (int32_t)in[0] * T::resamplerPreAmplify / T::resamplerPostAttenuate;
|
|
output[1] = (int32_t)in[1] * T::resamplerPreAmplify / T::resamplerPostAttenuate;
|
|
return;
|
|
}
|
|
|
|
VResampler *rsm = m_resampler;
|
|
float scale = (float)T::resamplerPreAmplify /
|
|
(float)T::resamplerPostAttenuate;
|
|
float f_in[2];
|
|
float f_out[2];
|
|
rsm->inp_count = 0;
|
|
rsm->inp_data = f_in;
|
|
rsm->out_count = 1;
|
|
rsm->out_data = f_out;
|
|
while(rsm->process(), rsm->out_count != 0)
|
|
{
|
|
int16_t in[2];
|
|
static_cast<T *>(this)->nativeTick(in);
|
|
f_in[0] = scale * (float)in[0];
|
|
f_in[1] = scale * (float)in[1];
|
|
rsm->inp_count = 1;
|
|
rsm->inp_data = f_in;
|
|
rsm->out_count = 1;
|
|
rsm->out_data = f_out;
|
|
}
|
|
output[0] = std::lround(f_out[0]);
|
|
output[1] = std::lround(f_out[1]);
|
|
}
|
|
#else
|
|
template <class T>
|
|
void OPLChipBaseT<T>::resampledGenerate(int32_t *output)
|
|
{
|
|
if(UNLIKELY(m_runningAtPcmRate))
|
|
{
|
|
int16_t in[2];
|
|
static_cast<T *>(this)->nativeTick(in);
|
|
output[0] = (int32_t)in[0] * T::resamplerPreAmplify / T::resamplerPostAttenuate;
|
|
output[1] = (int32_t)in[1] * T::resamplerPreAmplify / T::resamplerPostAttenuate;
|
|
return;
|
|
}
|
|
|
|
int32_t samplecnt = m_samplecnt;
|
|
const int32_t rateratio = m_rateratio;
|
|
while(samplecnt >= rateratio)
|
|
{
|
|
m_oldsamples[0] = m_samples[0];
|
|
m_oldsamples[1] = m_samples[1];
|
|
int16_t buffer[2];
|
|
static_cast<T *>(this)->nativeTick(buffer);
|
|
m_samples[0] = buffer[0] * T::resamplerPreAmplify;
|
|
m_samples[1] = buffer[1] * T::resamplerPreAmplify;
|
|
samplecnt -= rateratio;
|
|
}
|
|
output[0] = (int32_t)(((m_oldsamples[0] * (rateratio - samplecnt)
|
|
+ m_samples[0] * samplecnt) / rateratio)/T::resamplerPostAttenuate);
|
|
output[1] = (int32_t)(((m_oldsamples[1] * (rateratio - samplecnt)
|
|
+ m_samples[1] * samplecnt) / rateratio)/T::resamplerPostAttenuate);
|
|
m_samplecnt = samplecnt + (1 << rsm_frac);
|
|
}
|
|
#endif
|
|
|
|
/* OPLChipBaseBufferedT */
|
|
|
|
template <class T, unsigned Buffer>
|
|
void OPLChipBaseBufferedT<T, Buffer>::reset()
|
|
{
|
|
OPLChipBaseT<T>::reset();
|
|
m_bufferIndex = 0;
|
|
}
|
|
|
|
template <class T, unsigned Buffer>
|
|
void OPLChipBaseBufferedT<T, Buffer>::nativeGenerate(int16_t *frame)
|
|
{
|
|
unsigned bufferIndex = m_bufferIndex;
|
|
if(bufferIndex == 0)
|
|
static_cast<T *>(this)->nativeGenerateN(m_buffer, Buffer);
|
|
frame[0] = m_buffer[2 * bufferIndex];
|
|
frame[1] = m_buffer[2 * bufferIndex + 1];
|
|
bufferIndex = (bufferIndex + 1 < Buffer) ? (bufferIndex + 1) : 0;
|
|
m_bufferIndex = bufferIndex;
|
|
}
|