Updated MUNT to 2.2.0.
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
#
|
||||
# Modified Makefile for Win32 (MinGW32) environment.
|
||||
#
|
||||
# Version: @(#)Makefile.mingw 1.0.34 2017/08/09
|
||||
# Version: @(#)Makefile.mingw 1.0.35 2017/08/10
|
||||
#
|
||||
# Authors: Miran Grca, <mgrca8@gmail.com>
|
||||
# Fred N. van Kempen, <decwiz@yahoo.com>
|
||||
@@ -224,8 +224,8 @@ SNDOBJ = sound.o \
|
||||
Analog.o BReverbModel.o File.o FileStream.o LA32Ramp.o \
|
||||
LA32FloatWaveGenerator.o LA32WaveGenerator.o \
|
||||
MidiStreamParser.o Part.o Partial.o PartialManager.o \
|
||||
Poly.o ROMInfo.o Synth.o Tables.o TVA.o TVF.o TVP.o \
|
||||
sha1.o c_interface.o \
|
||||
Poly.o ROMInfo.o SampleRateConverter_dummy.o Synth.o \
|
||||
Tables.o TVA.o TVF.o TVP.o sha1.o c_interface.o \
|
||||
midi_system.o \
|
||||
snd_speaker.o snd_ps1.o snd_pssj.o \
|
||||
snd_adlib.o snd_adlibgold.o snd_ad1848.o \
|
||||
|
||||
@@ -178,6 +178,7 @@ void* mt32emu_init(wchar_t *control_rom, wchar_t *pcm_rom)
|
||||
mt32emu_set_reverb_enabled(context, device_get_config_int("reverb"));
|
||||
mt32emu_set_reverb_output_gain(context, device_get_config_int("reverb_output_gain")/100.0f);
|
||||
mt32emu_set_reversed_stereo_enabled(context, device_get_config_int("reversed_stereo"));
|
||||
mt32emu_set_nice_amp_ramp_enabled(context, device_get_config_int("nice_ramp"));
|
||||
|
||||
pclog("mt32 output gain: %f\n", mt32emu_get_output_gain(context));
|
||||
pclog("mt32 reverb output gain: %f\n", mt32emu_get_reverb_output_gain(context));
|
||||
@@ -248,32 +249,11 @@ static device_config_t mt32_config[] =
|
||||
{
|
||||
.name = "output_gain",
|
||||
.description = "Output Gain",
|
||||
.type = CONFIG_SELECTION,
|
||||
.selection =
|
||||
.type = CONFIG_SPINNER,
|
||||
.spinner =
|
||||
{
|
||||
{
|
||||
.description = "100%",
|
||||
.value = 100
|
||||
},
|
||||
{
|
||||
.description = "75%",
|
||||
.value = 75
|
||||
},
|
||||
{
|
||||
.description = "50%",
|
||||
.value = 50
|
||||
},
|
||||
{
|
||||
.description = "25%",
|
||||
.value = 25
|
||||
},
|
||||
{
|
||||
.description = "0%",
|
||||
.value = 0
|
||||
},
|
||||
{
|
||||
.description = ""
|
||||
}
|
||||
.min = 0,
|
||||
.max = 100
|
||||
},
|
||||
.default_int = 100
|
||||
},
|
||||
@@ -286,32 +266,11 @@ static device_config_t mt32_config[] =
|
||||
{
|
||||
.name = "reverb_output_gain",
|
||||
.description = "Reverb Output Gain",
|
||||
.type = CONFIG_SELECTION,
|
||||
.selection =
|
||||
.type = CONFIG_SPINNER,
|
||||
.spinner =
|
||||
{
|
||||
{
|
||||
.description = "100%",
|
||||
.value = 100
|
||||
},
|
||||
{
|
||||
.description = "75%",
|
||||
.value = 75
|
||||
},
|
||||
{
|
||||
.description = "50%",
|
||||
.value = 50
|
||||
},
|
||||
{
|
||||
.description = "25%",
|
||||
.value = 25
|
||||
},
|
||||
{
|
||||
.description = "0%",
|
||||
.value = 0
|
||||
},
|
||||
{
|
||||
.description = ""
|
||||
}
|
||||
.min = 0,
|
||||
.max = 100
|
||||
},
|
||||
.default_int = 100
|
||||
},
|
||||
@@ -321,6 +280,12 @@ static device_config_t mt32_config[] =
|
||||
.type = CONFIG_BINARY,
|
||||
.default_int = 0
|
||||
},
|
||||
{
|
||||
.name = "nice_ramp",
|
||||
.description = "Nice ramp",
|
||||
.type = CONFIG_BINARY,
|
||||
.default_int = 1
|
||||
},
|
||||
{
|
||||
.type = -1
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ class Analog {
|
||||
public:
|
||||
static Analog *createAnalog(const AnalogOutputMode mode, const bool oldMT32AnalogLPF, const RendererType rendererType);
|
||||
|
||||
virtual ~Analog() {};
|
||||
virtual ~Analog() {}
|
||||
virtual unsigned int getOutputSampleRate() const = 0;
|
||||
virtual Bit32u getDACStreamsLength(const Bit32u outputLength) const = 0;
|
||||
virtual void setSynthOutputGain(const float synthGain) = 0;
|
||||
|
||||
@@ -29,7 +29,7 @@ class BReverbModel {
|
||||
public:
|
||||
static BReverbModel *createBReverbModel(const ReverbMode mode, const bool mt32CompatibleModel, const RendererType rendererType);
|
||||
|
||||
virtual ~BReverbModel() {};
|
||||
virtual ~BReverbModel() {}
|
||||
virtual bool isOpen() const = 0;
|
||||
// After construction or a close(), open() must be called at least once before any other call (with the exception of close()).
|
||||
virtual void open() = 0;
|
||||
|
||||
@@ -85,7 +85,6 @@ enum MT32EMU_DAC_INPUT_MODE_NAME {
|
||||
* Nicer overdrive characteristics than the DAC hacks (it simply clips samples within range)
|
||||
* Much less likely to overdrive than any other mode.
|
||||
* Half the volume of any of the other modes.
|
||||
* Output gain is ignored for both LA32 and reverb output.
|
||||
* Perfect for developers while debugging :)
|
||||
*/
|
||||
MT32EMU_DAC_INPUT_MODE(PURE),
|
||||
|
||||
@@ -165,12 +165,6 @@ float LA32FloatWaveGenerator::generateNextSample(const Bit32u ampVal, const Bit1
|
||||
hLen = 0.0f;
|
||||
}
|
||||
|
||||
// Ignore pulsewidths too high for given freq and cutoff
|
||||
float lLen = waveLen - hLen - 2 * cosineLen;
|
||||
if (lLen < 0.0f) {
|
||||
lLen = 0.0f;
|
||||
}
|
||||
|
||||
// Correct resAmp for cutoff in range 50..66
|
||||
if ((cutoffVal >= MIDDLE_CUTOFF_VALUE) && (cutoffVal < RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE)) {
|
||||
resAmp *= sin(FLOAT_PI * (cutoffVal - MIDDLE_CUTOFF_VALUE) / 32.0f);
|
||||
@@ -328,8 +322,13 @@ static inline float produceDistortedSample(float sample) {
|
||||
}
|
||||
|
||||
float LA32FloatPartialPair::nextOutSample() {
|
||||
// Note, LA32FloatWaveGenerator produces each sample normalised in terms of a single playing partial,
|
||||
// so the unity sample corresponds to the internal LA32 logarithmic fixed-point unity sample.
|
||||
// However, each logarithmic sample is then unlogged to a 14-bit signed integer value, i.e. the max absolute value is 8192.
|
||||
// Thus, considering that samples are further mapped to a 16-bit signed integer,
|
||||
// we apply a conversion factor 0.25 to produce properly normalised float samples.
|
||||
if (!ringModulated) {
|
||||
return masterOutputSample + slaveOutputSample;
|
||||
return 0.25f * (masterOutputSample + slaveOutputSample);
|
||||
}
|
||||
/*
|
||||
* SEMI-CONFIRMED: Ring modulation model derived from sample analysis of specially constructed patches which exploit distortion.
|
||||
@@ -340,7 +339,7 @@ float LA32FloatPartialPair::nextOutSample() {
|
||||
* Most probably the overflow is caused by limited precision of the multiplication circuit as the very similar distortion occurs with panning.
|
||||
*/
|
||||
float ringModulatedSample = produceDistortedSample(masterOutputSample) * produceDistortedSample(slaveOutputSample);
|
||||
return mixed ? masterOutputSample + ringModulatedSample : ringModulatedSample;
|
||||
return 0.25f * (mixed ? masterOutputSample + ringModulatedSample : ringModulatedSample);
|
||||
}
|
||||
|
||||
void LA32FloatPartialPair::deactivate(const PairType useMaster) {
|
||||
|
||||
@@ -56,8 +56,8 @@ We haven't fully explored:
|
||||
namespace MT32Emu {
|
||||
|
||||
// SEMI-CONFIRMED from sample analysis.
|
||||
const int TARGET_MULT = 0x40000;
|
||||
const unsigned int MAX_CURRENT = 0xFF * TARGET_MULT;
|
||||
const unsigned int TARGET_SHIFTS = 18;
|
||||
const unsigned int MAX_CURRENT = 0xFF << TARGET_SHIFTS;
|
||||
|
||||
// We simulate the delay in handling "target was reached" interrupts by waiting
|
||||
// this many samples before setting interruptRaised.
|
||||
@@ -96,7 +96,7 @@ void LA32Ramp::startRamp(Bit8u target, Bit8u increment) {
|
||||
largeIncrement++;
|
||||
}
|
||||
|
||||
largeTarget = target * TARGET_MULT;
|
||||
largeTarget = target << TARGET_SHIFTS;
|
||||
interruptCountdown = 0;
|
||||
interruptRaised = false;
|
||||
}
|
||||
@@ -152,4 +152,13 @@ void LA32Ramp::reset() {
|
||||
interruptRaised = false;
|
||||
}
|
||||
|
||||
// This is actually beyond the LA32 ramp interface.
|
||||
// Instead of polling the current value, MCU receives an interrupt when a ramp completes.
|
||||
// However, this is a simple way to work around the specific behaviour of TVA
|
||||
// when in sustain phase which one normally wants to avoid.
|
||||
// See TVA::recalcSustain() for details.
|
||||
bool LA32Ramp::isBelowCurrent(Bit8u target) const {
|
||||
return Bit32u(target << TARGET_SHIFTS) < current;
|
||||
}
|
||||
|
||||
} // namespace MT32Emu
|
||||
|
||||
@@ -39,6 +39,7 @@ public:
|
||||
Bit32u nextValue();
|
||||
bool checkInterrupt();
|
||||
void reset();
|
||||
bool isBelowCurrent(Bit8u target) const;
|
||||
};
|
||||
|
||||
} // namespace MT32Emu
|
||||
|
||||
@@ -378,22 +378,16 @@ Bit16s LA32IntPartialPair::unlogAndMixWGOutput(const LA32WaveGenerator &wg) {
|
||||
return firstSample + secondSample;
|
||||
}
|
||||
|
||||
static inline Bit16s produceDistortedSample(Bit16s sample) {
|
||||
return ((sample & 0x2000) == 0) ? Bit16s(sample & 0x1fff) : Bit16s(sample | ~0x1fff);
|
||||
}
|
||||
|
||||
Bit16s LA32IntPartialPair::nextOutSample() {
|
||||
if (!ringModulated) {
|
||||
return unlogAndMixWGOutput(master) + unlogAndMixWGOutput(slave);
|
||||
}
|
||||
|
||||
/*
|
||||
* SEMI-CONFIRMED: Ring modulation model derived from sample analysis of specially constructed patches which exploit distortion.
|
||||
* LA32 ring modulator found to produce distorted output in case if the absolute value of maximal amplitude of one of the input partials exceeds 8191.
|
||||
* This is easy to reproduce using synth partials with resonance values close to the maximum. It looks like an integer overflow happens in this case.
|
||||
* As the distortion is strictly bound to the amplitude of the complete mixed square + resonance wave in the linear space,
|
||||
* it is reasonable to assume the ring modulation is performed also in the linear space by sample multiplication.
|
||||
* Most probably the overflow is caused by limited precision of the multiplication circuit as the very similar distortion occurs with panning.
|
||||
*/
|
||||
Bit16s nonOverdrivenMasterSample = unlogAndMixWGOutput(master); // Store master partial sample for further mixing
|
||||
Bit16s masterSample = nonOverdrivenMasterSample << 2;
|
||||
masterSample >>= 2;
|
||||
Bit16s masterSample = unlogAndMixWGOutput(master); // Store master partial sample for further mixing
|
||||
|
||||
/* SEMI-CONFIRMED from sample analysis:
|
||||
* We observe that for partial structures with ring modulation the interpolation is not applied to the slave PCM partial.
|
||||
@@ -401,10 +395,17 @@ Bit16s LA32IntPartialPair::nextOutSample() {
|
||||
* is borrowed by the ring modulation circuit (or the LA32 chip has a similar lack of resources assigned to each partial pair).
|
||||
*/
|
||||
Bit16s slaveSample = slave.isPCMWave() ? LA32Utilites::unlog(slave.getOutputLogSample(true)) : unlogAndMixWGOutput(slave);
|
||||
slaveSample <<= 2;
|
||||
slaveSample >>= 2;
|
||||
Bit16s ringModulatedSample = Bit16s((Bit32s(masterSample) * Bit32s(slaveSample)) >> 13);
|
||||
return mixed ? nonOverdrivenMasterSample + ringModulatedSample : ringModulatedSample;
|
||||
|
||||
/* SEMI-CONFIRMED: Ring modulation model derived from sample analysis of specially constructed patches which exploit distortion.
|
||||
* LA32 ring modulator found to produce distorted output in case if the absolute value of maximal amplitude of one of the input partials exceeds 8191.
|
||||
* This is easy to reproduce using synth partials with resonance values close to the maximum. It looks like an integer overflow happens in this case.
|
||||
* As the distortion is strictly bound to the amplitude of the complete mixed square + resonance wave in the linear space,
|
||||
* it is reasonable to assume the ring modulation is performed also in the linear space by sample multiplication.
|
||||
* Most probably the overflow is caused by limited precision of the multiplication circuit as the very similar distortion occurs with panning.
|
||||
*/
|
||||
Bit16s ringModulatedSample = Bit16s((Bit32s(produceDistortedSample(masterSample)) * Bit32s(produceDistortedSample(slaveSample))) >> 13);
|
||||
|
||||
return mixed ? masterSample + ringModulatedSample : ringModulatedSample;
|
||||
}
|
||||
|
||||
void LA32IntPartialPair::deactivate(const PairType useMaster) {
|
||||
|
||||
@@ -119,7 +119,7 @@ void MidiStreamParserImpl::parseStream(const Bit8u *stream, Bit32u length) {
|
||||
|
||||
void MidiStreamParserImpl::processShortMessage(const Bit32u message) {
|
||||
// Adds running status to the MIDI message if it doesn't contain one
|
||||
Bit8u status = Bit8u(message);
|
||||
Bit8u status = Bit8u(message & 0xFF);
|
||||
if (0xF8 <= status) {
|
||||
midiReceiver.handleSystemRealtimeMessage(status);
|
||||
} else if (processStatusByte(status)) {
|
||||
|
||||
@@ -340,10 +340,13 @@ void RhythmPart::setPan(unsigned int midiPan) {
|
||||
void Part::setPan(unsigned int midiPan) {
|
||||
// NOTE: Panning is inverted compared to GM.
|
||||
|
||||
// CM-32L: Divide by 8.5
|
||||
patchTemp->panpot = Bit8u((midiPan << 3) / 68);
|
||||
// FIXME: MT-32: Divide by 9
|
||||
//patchTemp->panpot = Bit8u(midiPan / 9);
|
||||
if (synth->controlROMFeatures->quirkPanMult) {
|
||||
// MT-32: Divide by 9
|
||||
patchTemp->panpot = Bit8u(midiPan / 9);
|
||||
} else {
|
||||
// CM-32L: Divide by 8.5
|
||||
patchTemp->panpot = Bit8u((midiPan << 3) / 68);
|
||||
}
|
||||
|
||||
//synth->printDebug("%s (%s): Set pan to %d", name, currentInstr, panpot);
|
||||
}
|
||||
@@ -352,6 +355,10 @@ void Part::setPan(unsigned int midiPan) {
|
||||
* Applies key shift to a MIDI key and converts it into an internal key value in the range 12-108.
|
||||
*/
|
||||
unsigned int Part::midiKeyToKey(unsigned int midiKey) {
|
||||
if (synth->controlROMFeatures->quirkKeyShift) {
|
||||
// NOTE: On MT-32 GEN0, key isn't adjusted, and keyShift is applied further in TVP, unlike newer units:
|
||||
return midiKey;
|
||||
}
|
||||
int key = midiKey + patchTemp->patch.keyShift;
|
||||
if (key < 36) {
|
||||
// After keyShift is applied, key < 36, so move up by octaves
|
||||
|
||||
@@ -272,6 +272,10 @@ bool Partial::isRingModulatingSlave() const {
|
||||
return pair != NULL && structurePosition == 1 && (mixType == 1 || mixType == 2);
|
||||
}
|
||||
|
||||
bool Partial::isRingModulatingNoMix() const {
|
||||
return pair != NULL && ((structurePosition == 1 && mixType == 1) || mixType == 2);
|
||||
}
|
||||
|
||||
bool Partial::isPCM() const {
|
||||
return pcmWave != NULL;
|
||||
}
|
||||
@@ -368,7 +372,7 @@ bool Partial::doProduceOutput(Sample *leftBuf, Sample *rightBuf, Bit32u length,
|
||||
|
||||
bool Partial::produceOutput(IntSample *leftBuf, IntSample *rightBuf, Bit32u length) {
|
||||
if (floatMode) {
|
||||
synth->printDebug("Partial: Invalid call to produceOutput()!\n", synth->getSelectedRendererType());
|
||||
synth->printDebug("Partial: Invalid call to produceOutput()! Renderer = %d\n", synth->getSelectedRendererType());
|
||||
return false;
|
||||
}
|
||||
return doProduceOutput(leftBuf, rightBuf, length, static_cast<LA32IntPartialPair *>(la32Pair));
|
||||
@@ -376,7 +380,7 @@ bool Partial::produceOutput(IntSample *leftBuf, IntSample *rightBuf, Bit32u leng
|
||||
|
||||
bool Partial::produceOutput(FloatSample *leftBuf, FloatSample *rightBuf, Bit32u length) {
|
||||
if (!floatMode) {
|
||||
synth->printDebug("Partial: Invalid call to produceOutput()!\n", synth->getSelectedRendererType());
|
||||
synth->printDebug("Partial: Invalid call to produceOutput()! Renderer = %d\n", synth->getSelectedRendererType());
|
||||
return false;
|
||||
}
|
||||
return doProduceOutput(leftBuf, rightBuf, length, static_cast<LA32FloatPartialPair *>(la32Pair));
|
||||
|
||||
@@ -108,6 +108,7 @@ public:
|
||||
void startAbort();
|
||||
void startDecayAll();
|
||||
bool shouldReverb();
|
||||
bool isRingModulatingNoMix() const;
|
||||
bool hasRingModulatingSlave() const;
|
||||
bool isRingModulatingSlave() const;
|
||||
bool isPCM() const;
|
||||
|
||||
@@ -31,6 +31,7 @@ static const ROMInfo *getKnownROMInfoFromList(Bit32u index) {
|
||||
static const ROMInfo CTRL_MT32_V1_07 = {65536, "b083518fffb7f66b03c23b7eb4f868e62dc5a987", ROMInfo::Control, "ctrl_mt32_1_07", "MT-32 Control v1.07", ROMInfo::Full, NULL};
|
||||
static const ROMInfo CTRL_MT32_BLUER = {65536, "7b8c2a5ddb42fd0732e2f22b3340dcf5360edf92", ROMInfo::Control, "ctrl_mt32_bluer", "MT-32 Control BlueRidge", ROMInfo::Full, NULL};
|
||||
|
||||
static const ROMInfo CTRL_MT32_V2_04 = {131072, "2c16432b6c73dd2a3947cba950a0f4c19d6180eb", ROMInfo::Control, "ctrl_mt32_2_04", "MT-32 Control v2.04", ROMInfo::Full, NULL};
|
||||
static const ROMInfo CTRL_CM32L_V1_00 = {65536, "73683d585cd6948cc19547942ca0e14a0319456d", ROMInfo::Control, "ctrl_cm32l_1_00", "CM-32L/LAPC-I Control v1.00", ROMInfo::Full, NULL};
|
||||
static const ROMInfo CTRL_CM32L_V1_02 = {65536, "a439fbb390da38cada95a7cbb1d6ca199cd66ef8", ROMInfo::Control, "ctrl_cm32l_1_02", "CM-32L/LAPC-I Control v1.02", ROMInfo::Full, NULL};
|
||||
|
||||
@@ -43,6 +44,7 @@ static const ROMInfo *getKnownROMInfoFromList(Bit32u index) {
|
||||
&CTRL_MT32_V1_06,
|
||||
&CTRL_MT32_V1_07,
|
||||
&CTRL_MT32_BLUER,
|
||||
&CTRL_MT32_V2_04,
|
||||
&CTRL_CM32L_V1_00,
|
||||
&CTRL_CM32L_V1_02,
|
||||
&PCM_MT32,
|
||||
|
||||
60
src/SOUND/munt/SampleRateConverter_dummy.cpp
Normal file
60
src/SOUND/munt/SampleRateConverter_dummy.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
/* Copyright (C) 2015-2017 Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "SampleRateConverter.h"
|
||||
|
||||
#include "Synth.h"
|
||||
|
||||
using namespace MT32Emu;
|
||||
|
||||
static inline void *createDelegate(Synth &synth, double targetSampleRate, SamplerateConversionQuality quality) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
AnalogOutputMode SampleRateConverter::getBestAnalogOutputMode(double targetSampleRate) {
|
||||
return AnalogOutputMode_COARSE;
|
||||
}
|
||||
|
||||
SampleRateConverter::SampleRateConverter(Synth &useSynth, double targetSampleRate, SamplerateConversionQuality useQuality) :
|
||||
synthInternalToTargetSampleRateRatio(SAMPLE_RATE / targetSampleRate),
|
||||
useSynthDelegate(useSynth.getStereoOutputSampleRate() == targetSampleRate),
|
||||
srcDelegate(useSynthDelegate ? &useSynth : createDelegate(useSynth, targetSampleRate, useQuality))
|
||||
{}
|
||||
|
||||
SampleRateConverter::~SampleRateConverter() {
|
||||
}
|
||||
|
||||
void SampleRateConverter::getOutputSamples(float *buffer, unsigned int length) {
|
||||
if (useSynthDelegate) {
|
||||
static_cast<Synth *>(srcDelegate)->render(buffer, length);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void SampleRateConverter::getOutputSamples(Bit16s *outBuffer, unsigned int length) {
|
||||
if (useSynthDelegate) {
|
||||
static_cast<Synth *>(srcDelegate)->render(outBuffer, length);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
double SampleRateConverter::convertOutputToSynthTimestamp(double outputTimestamp) const {
|
||||
return outputTimestamp * synthInternalToTargetSampleRateRatio;
|
||||
}
|
||||
|
||||
double SampleRateConverter::convertSynthToOutputTimestamp(double synthTimestamp) const {
|
||||
return synthTimestamp / synthInternalToTargetSampleRateRatio;
|
||||
}
|
||||
@@ -184,7 +184,13 @@ struct SoundGroup {
|
||||
#endif
|
||||
|
||||
struct ControlROMFeatureSet {
|
||||
unsigned int quirkBasePitchOverflow : 1;
|
||||
unsigned int quirkPitchEnvelopeOverflow : 1;
|
||||
unsigned int quirkRingModulationNoMix : 1;
|
||||
unsigned int quirkTVAZeroEnvLevels : 1;
|
||||
unsigned int quirkPanMult : 1;
|
||||
unsigned int quirkKeyShift : 1;
|
||||
unsigned int quirkTVFBaseCutoffLimit : 1;
|
||||
|
||||
// Features below don't actually depend on control ROM version, which is used to identify hardware model
|
||||
unsigned int defaultReverbMT32Compatible : 1;
|
||||
|
||||
@@ -32,25 +32,50 @@
|
||||
#include "ROMInfo.h"
|
||||
#include "TVA.h"
|
||||
|
||||
#if MT32EMU_MONITOR_SYSEX > 0
|
||||
#include "mmath.h"
|
||||
#endif
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
// MIDI interface data transfer rate in samples. Used to simulate the transfer delay.
|
||||
static const double MIDI_DATA_TRANSFER_RATE = double(SAMPLE_RATE) / 31250.0 * 8.0;
|
||||
|
||||
// FIXME: there should be more specific feature sets for various MT-32 control ROM versions
|
||||
static const ControlROMFeatureSet OLD_MT32_COMPATIBLE = { true, true, true };
|
||||
static const ControlROMFeatureSet CM32L_COMPATIBLE = { false, false, false };
|
||||
static const ControlROMFeatureSet OLD_MT32_COMPATIBLE = {
|
||||
true, // quirkBasePitchOverflow
|
||||
true, // quirkPitchEnvelopeOverflow
|
||||
true, // quirkRingModulationNoMix
|
||||
true, // quirkTVAZeroEnvLevels
|
||||
true, // quirkPanMult
|
||||
true, // quirkKeyShift
|
||||
true, // quirkTVFBaseCutoffLimit
|
||||
true, // defaultReverbMT32Compatible
|
||||
true // oldMT32AnalogLPF
|
||||
};
|
||||
static const ControlROMFeatureSet CM32L_COMPATIBLE = {
|
||||
false, // quirkBasePitchOverflow
|
||||
false, // quirkPitchEnvelopeOverflow
|
||||
false, // quirkRingModulationNoMix
|
||||
false, // quirkTVAZeroEnvLevels
|
||||
false, // quirkPanMult
|
||||
false, // quirkKeyShift
|
||||
false, // quirkTVFBaseCutoffLimit
|
||||
false, // defaultReverbMT32Compatible
|
||||
false // oldMT32AnalogLPF
|
||||
};
|
||||
|
||||
static const ControlROMMap ControlROMMaps[7] = {
|
||||
static const ControlROMMap ControlROMMaps[8] = {
|
||||
// ID Features PCMmap PCMc tmbrA tmbrAO, tmbrAC tmbrB tmbrBO tmbrBC tmbrR trC rhythm rhyC rsrv panpot prog rhyMax patMax sysMax timMax sndGrp sGC
|
||||
{ "ctrl_mt32_1_04", OLD_MT32_COMPATIBLE, 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x73A6, 85, 0x57C7, 0x57E2, 0x57D0, 0x5252, 0x525E, 0x526E, 0x520A, 0x7064, 19 },
|
||||
{ "ctrl_mt32_1_05", OLD_MT32_COMPATIBLE, 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x7414, 85, 0x57C7, 0x57E2, 0x57D0, 0x5252, 0x525E, 0x526E, 0x520A, 0x70CA, 19 },
|
||||
{ "ctrl_mt32_1_06", OLD_MT32_COMPATIBLE, 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x7414, 85, 0x57D9, 0x57F4, 0x57E2, 0x5264, 0x5270, 0x5280, 0x521C, 0x70CA, 19 },
|
||||
{ "ctrl_mt32_1_07", OLD_MT32_COMPATIBLE, 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x73fe, 85, 0x57B1, 0x57CC, 0x57BA, 0x523C, 0x5248, 0x5258, 0x51F4, 0x70B0, 19 }, // MT-32 revision 1
|
||||
{"ctrl_mt32_bluer", OLD_MT32_COMPATIBLE, 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x741C, 85, 0x57E5, 0x5800, 0x57EE, 0x5270, 0x527C, 0x528C, 0x5228, 0x70CE, 19 }, // MT-32 Blue Ridge mod
|
||||
{"ctrl_mt32_2_04", CM32L_COMPATIBLE, 0x8100, 128, 0x8000, 0x8000, true, 0x8080, 0x8000, true, 0x8500, 30, 0x8580, 85, 0x4F5D, 0x4F78, 0x4F66, 0x4899, 0x489D, 0x48B6, 0x48CD, 0x5A58, 19 },
|
||||
{"ctrl_cm32l_1_00", CM32L_COMPATIBLE, 0x8100, 256, 0x8000, 0x8000, true, 0x8080, 0x8000, true, 0x8500, 64, 0x8580, 85, 0x4F65, 0x4F80, 0x4F6E, 0x48A1, 0x48A5, 0x48BE, 0x48D5, 0x5A6C, 19 },
|
||||
{"ctrl_cm32l_1_02", CM32L_COMPATIBLE, 0x8100, 256, 0x8000, 0x8000, true, 0x8080, 0x8000, true, 0x8500, 64, 0x8580, 85, 0x4F93, 0x4FAE, 0x4F9C, 0x48CB, 0x48CF, 0x48E8, 0x48FF, 0x5A96, 19 } // CM-32L
|
||||
// (Note that all but CM-32L ROM actually have 86 entries for rhythmTemp)
|
||||
// (Note that old MT-32 ROMs actually have 86 entries for rhythmTemp)
|
||||
};
|
||||
|
||||
static const PartialState PARTIAL_PHASE_TO_STATE[8] = {
|
||||
@@ -78,7 +103,7 @@ protected:
|
||||
Synth &synth;
|
||||
|
||||
void printDebug(const char *msg) const {
|
||||
synth.printDebug(msg);
|
||||
synth.printDebug("%s", msg);
|
||||
}
|
||||
|
||||
bool isActivated() const {
|
||||
@@ -168,6 +193,8 @@ public:
|
||||
class Extensions {
|
||||
public:
|
||||
RendererType selectedRendererType;
|
||||
Bit32s masterTunePitchDelta;
|
||||
bool niceAmpRamp;
|
||||
};
|
||||
|
||||
Bit32u Synth::getLibraryVersionInt() {
|
||||
@@ -222,6 +249,7 @@ Synth::Synth(ReportHandler *useReportHandler) :
|
||||
setOutputGain(1.0f);
|
||||
setReverbOutputGain(1.0f);
|
||||
setReversedStereoEnabled(false);
|
||||
setNiceAmpRampEnabled(true);
|
||||
selectRendererType(RendererType_BIT16S);
|
||||
|
||||
patchTempMemoryRegion = NULL;
|
||||
@@ -387,6 +415,14 @@ bool Synth::isReversedStereoEnabled() const {
|
||||
return reversedStereoEnabled;
|
||||
}
|
||||
|
||||
void Synth::setNiceAmpRampEnabled(bool enabled) {
|
||||
extensions.niceAmpRamp = enabled;
|
||||
}
|
||||
|
||||
bool Synth::isNiceAmpRampEnabled() const {
|
||||
return extensions.niceAmpRamp;
|
||||
}
|
||||
|
||||
bool Synth::loadControlROM(const ROMImage &controlROMImage) {
|
||||
File *file = controlROMImage.getFile();
|
||||
const ROMInfo *controlROMInfo = controlROMImage.getROMInfo();
|
||||
@@ -679,6 +715,7 @@ bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, B
|
||||
bool oldReverbOverridden = reverbOverridden;
|
||||
reverbOverridden = false;
|
||||
refreshSystem();
|
||||
resetMasterTunePitchDelta();
|
||||
reverbOverridden = oldReverbOverridden;
|
||||
|
||||
char(*writableSoundGroupNames)[9] = new char[controlROMMap->soundGroupsCount][9];
|
||||
@@ -865,13 +902,17 @@ Bit32u Synth::addMIDIInterfaceDelay(Bit32u len, Bit32u timestamp) {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
Bit32u Synth::getInternalRenderedSampleCount() const {
|
||||
return renderedSampleCount;
|
||||
}
|
||||
|
||||
bool Synth::playMsg(Bit32u msg) {
|
||||
return playMsg(msg, renderedSampleCount);
|
||||
}
|
||||
|
||||
bool Synth::playMsg(Bit32u msg, Bit32u timestamp) {
|
||||
if ((msg & 0xF8) == 0xF8) {
|
||||
reportHandler->onMIDISystemRealtime(Bit8u(msg));
|
||||
reportHandler->onMIDISystemRealtime(Bit8u(msg & 0xFF));
|
||||
return true;
|
||||
}
|
||||
if (midiQueue == NULL) return false;
|
||||
@@ -1539,6 +1580,8 @@ void Synth::writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u le
|
||||
}
|
||||
|
||||
void Synth::refreshSystemMasterTune() {
|
||||
// 171 is ~half a semitone.
|
||||
extensions.masterTunePitchDelta = ((mt32ram.system.masterTune - 64) * 171) >> 6; // PORTABILITY NOTE: Assumes arithmetic shift.
|
||||
#if MT32EMU_MONITOR_SYSEX > 0
|
||||
//FIXME:KG: This is just an educated guess.
|
||||
// The LAPC-I documentation claims a range of 427.5Hz-452.6Hz (similar to what we have here)
|
||||
@@ -1650,9 +1693,25 @@ void Synth::reset() {
|
||||
}
|
||||
}
|
||||
refreshSystem();
|
||||
resetMasterTunePitchDelta();
|
||||
isActive();
|
||||
}
|
||||
|
||||
void Synth::resetMasterTunePitchDelta() {
|
||||
// This effectively resets master tune to 440.0Hz.
|
||||
// Despite that the manual claims 442.0Hz is the default setting for master tune,
|
||||
// it doesn't actually take effect upon a reset due to a bug in the reset routine.
|
||||
// CONFIRMED: This bug is present in all supported Control ROMs.
|
||||
extensions.masterTunePitchDelta = 0;
|
||||
#if MT32EMU_MONITOR_SYSEX > 0
|
||||
printDebug(" Actual Master Tune reset to 440.0");
|
||||
#endif
|
||||
}
|
||||
|
||||
Bit32s Synth::getMasterTunePitchDelta() const {
|
||||
return extensions.masterTunePitchDelta;
|
||||
}
|
||||
|
||||
MidiEvent::~MidiEvent() {
|
||||
if (sysexData != NULL) {
|
||||
delete[] sysexData;
|
||||
@@ -2013,21 +2072,35 @@ void RendererImpl<IntSample>::convertSamplesToOutput(IntSample *buffer, Bit32u l
|
||||
}
|
||||
|
||||
static inline float produceDistortedSample(float sample) {
|
||||
if (sample < -2.0f) {
|
||||
return sample + 4.0f;
|
||||
} else if (2.0f < sample) {
|
||||
return sample - 4.0f;
|
||||
// Here we roughly simulate the distortion caused by the DAC bit shift.
|
||||
if (sample < -1.0f) {
|
||||
return sample + 2.0f;
|
||||
} else if (1.0f < sample) {
|
||||
return sample - 2.0f;
|
||||
}
|
||||
return sample;
|
||||
}
|
||||
|
||||
template <>
|
||||
void RendererImpl<FloatSample>::produceLA32Output(FloatSample *buffer, Bit32u len) {
|
||||
if (synth.getDACInputMode() == DACInputMode_GENERATION2) {
|
||||
switch (synth.getDACInputMode()) {
|
||||
case DACInputMode_NICE:
|
||||
// Note, we do not do any clamping for floats here to avoid introducing distortions.
|
||||
// This means that the output signal may actually overshoot the unity when the volume is set too high.
|
||||
// We leave it up to the consumer whether the output is to be clamped or properly normalised further on.
|
||||
while (len--) {
|
||||
*buffer = produceDistortedSample(*buffer);
|
||||
*buffer *= 2.0f;
|
||||
buffer++;
|
||||
}
|
||||
break;
|
||||
case DACInputMode_GENERATION2:
|
||||
while (len--) {
|
||||
*buffer = produceDistortedSample(2.0f * *buffer);
|
||||
buffer++;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2035,7 +2108,7 @@ template <>
|
||||
void RendererImpl<FloatSample>::convertSamplesToOutput(FloatSample *buffer, Bit32u len) {
|
||||
if (synth.getDACInputMode() == DACInputMode_GENERATION1) {
|
||||
while (len--) {
|
||||
*buffer = produceDistortedSample(*buffer);
|
||||
*buffer = produceDistortedSample(2.0f * *buffer);
|
||||
buffer++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,6 +124,7 @@ friend class RhythmPart;
|
||||
friend class SamplerateAdapter;
|
||||
friend class SoxrAdapter;
|
||||
friend class TVA;
|
||||
friend class TVF;
|
||||
friend class TVP;
|
||||
|
||||
private:
|
||||
@@ -230,6 +231,9 @@ private:
|
||||
// partNum should be 0..7 for Part 1..8, or 8 for Rhythm
|
||||
const Part *getPart(Bit8u partNum) const;
|
||||
|
||||
void resetMasterTunePitchDelta();
|
||||
Bit32s getMasterTunePitchDelta() const;
|
||||
|
||||
public:
|
||||
static inline Bit16s clipSampleEx(Bit32s sampleEx) {
|
||||
// Clamp values above 32767 to 32767, and values below -32768 to -32768
|
||||
@@ -258,11 +262,11 @@ public:
|
||||
}
|
||||
|
||||
static inline Bit16s convertSample(float sample) {
|
||||
return Synth::clipSampleEx(Bit32s(sample * 16384.0f)); // This multiplier takes into account the DAC bit shift
|
||||
return Synth::clipSampleEx(Bit32s(sample * 32768.0f)); // This multiplier corresponds to normalised floats
|
||||
}
|
||||
|
||||
static inline float convertSample(Bit16s sample) {
|
||||
return float(sample) / 16384.0f; // This multiplier takes into account the DAC bit shift
|
||||
return float(sample) / 32768.0f; // This multiplier corresponds to normalised floats
|
||||
}
|
||||
|
||||
// Returns library version as an integer in format: 0x00MMmmpp, where:
|
||||
@@ -308,6 +312,10 @@ public:
|
||||
// Returns the actual queue size being used.
|
||||
MT32EMU_EXPORT Bit32u setMIDIEventQueueSize(Bit32u);
|
||||
|
||||
// Returns current value of the global counter of samples rendered since the synth was created (at the native sample rate 32000 Hz).
|
||||
// This method helps to compute accurate timestamp of a MIDI message to use with the methods below.
|
||||
MT32EMU_EXPORT Bit32u getInternalRenderedSampleCount() const;
|
||||
|
||||
// Enqueues a MIDI event for subsequent playback.
|
||||
// The MIDI event will be processed not before the specified timestamp.
|
||||
// The timestamp is measured as the global rendered sample count since the synth was created (at the native sample rate 32000 Hz).
|
||||
@@ -382,7 +390,6 @@ public:
|
||||
// Sets output gain factor for synth output channels. Applied to all output samples and unrelated with the synth's Master volume,
|
||||
// it rather corresponds to the gain of the output analog circuitry of the hardware units. However, together with setReverbOutputGain()
|
||||
// it offers to the user a capability to control the gain of reverb and non-reverb output channels independently.
|
||||
// Ignored in DACInputMode_PURE
|
||||
MT32EMU_EXPORT void setOutputGain(float gain);
|
||||
// Returns current output gain factor for synth output channels.
|
||||
MT32EMU_EXPORT float getOutputGain() const;
|
||||
@@ -395,7 +402,6 @@ public:
|
||||
// corresponds to the level of digital capture. Although, according to the CM-64 PCB schematic,
|
||||
// there is a difference in the reverb analogue circuit, and the resulting output gain is 0.68
|
||||
// of that for LA32 analogue output. This factor is applied to the reverb output gain.
|
||||
// Ignored in DACInputMode_PURE
|
||||
MT32EMU_EXPORT void setReverbOutputGain(float gain);
|
||||
// Returns current output gain factor for reverb wet output channels.
|
||||
MT32EMU_EXPORT float getReverbOutputGain() const;
|
||||
@@ -405,6 +411,16 @@ public:
|
||||
// Returns whether left and right output channels are swapped.
|
||||
MT32EMU_EXPORT bool isReversedStereoEnabled() const;
|
||||
|
||||
// Allows to toggle the NiceAmpRamp mode.
|
||||
// In this mode, we want to ensure that amp ramp never jumps to the target
|
||||
// value and always gradually increases or decreases. It seems that real units
|
||||
// do not bother to always check if a newly started ramp leads to a jump.
|
||||
// We also prefer the quality improvement over the emulation accuracy,
|
||||
// so this mode is enabled by default.
|
||||
MT32EMU_EXPORT void setNiceAmpRampEnabled(bool enabled);
|
||||
// Returns whether NiceAmpRamp mode is enabled.
|
||||
MT32EMU_EXPORT bool isNiceAmpRampEnabled() const;
|
||||
|
||||
// Selects new type of the wave generator and renderer to be used during subsequent calls to open().
|
||||
// By default, RendererType_BIT16S is selected.
|
||||
// See RendererType for details.
|
||||
|
||||
@@ -43,7 +43,7 @@ void TVA::startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase) {
|
||||
phase = newPhase;
|
||||
ampRamp->startRamp(newTarget, newIncrement);
|
||||
#if MT32EMU_MONITOR_TVA >= 1
|
||||
partial->getSynth()->printDebug("[+%lu] [Partial %d] TVA,ramp,%d,%d,%d,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), (newIncrement & 0x80) ? -1 : 1, (newIncrement & 0x7F), newPhase);
|
||||
partial->getSynth()->printDebug("[+%lu] [Partial %d] TVA,ramp,%x,%s%x,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), newTarget, (newIncrement & 0x80) ? "-" : "+", (newIncrement & 0x7F), newPhase);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -99,10 +99,10 @@ static int calcVeloAmpSubtraction(Bit8u veloSensitivity, unsigned int velocity)
|
||||
return absVelocityMult - (velocityMult >> 8); // PORTABILITY NOTE: Assumes arithmetic shift
|
||||
}
|
||||
|
||||
static int calcBasicAmp(const Tables *tables, const Partial *partial, const MemParams::System *system, const TimbreParam::PartialParam *partialParam, const MemParams::PatchTemp *patchTemp, const MemParams::RhythmTemp *rhythmTemp, int biasAmpSubtraction, int veloAmpSubtraction, Bit8u expression) {
|
||||
static int calcBasicAmp(const Tables *tables, const Partial *partial, const MemParams::System *system, const TimbreParam::PartialParam *partialParam, const MemParams::PatchTemp *patchTemp, const MemParams::RhythmTemp *rhythmTemp, int biasAmpSubtraction, int veloAmpSubtraction, Bit8u expression, bool hasRingModQuirk) {
|
||||
int amp = 155;
|
||||
|
||||
if (!partial->isRingModulatingSlave()) {
|
||||
if (!(hasRingModQuirk ? partial->isRingModulatingNoMix() : partial->isRingModulatingSlave())) {
|
||||
amp -= tables->masterVolToAmpSubtraction[system->masterVol];
|
||||
if (amp < 0) {
|
||||
return 0;
|
||||
@@ -169,7 +169,7 @@ void TVA::reset(const Part *newPart, const TimbreParam::PartialParam *newPartial
|
||||
biasAmpSubtraction = calcBiasAmpSubtractions(partialParam, key);
|
||||
veloAmpSubtraction = calcVeloAmpSubtraction(partialParam->tva.veloSensitivity, velocity);
|
||||
|
||||
int newTarget = calcBasicAmp(tables, partial, system, partialParam, patchTemp, newRhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression());
|
||||
int newTarget = calcBasicAmp(tables, partial, system, partialParam, patchTemp, newRhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression(), partial->getSynth()->controlROMFeatures->quirkRingModulationNoMix);
|
||||
int newPhase;
|
||||
if (partialParam->tva.envTime[0] == 0) {
|
||||
// Initially go to the TVA_PHASE_ATTACK target amp, and spend the next phase going from there to the TVA_PHASE_2 target amp
|
||||
@@ -221,18 +221,29 @@ void TVA::recalcSustain() {
|
||||
}
|
||||
// We're sustaining. Recalculate all the values
|
||||
const Tables *tables = &Tables::getInstance();
|
||||
int newTarget = calcBasicAmp(tables, partial, system, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression());
|
||||
int newTarget = calcBasicAmp(tables, partial, system, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression(), partial->getSynth()->controlROMFeatures->quirkRingModulationNoMix);
|
||||
newTarget += partialParam->tva.envLevel[3];
|
||||
// Since we're in TVA_PHASE_SUSTAIN at this point, we know that target has been reached and an interrupt fired, so we can rely on it being the current amp.
|
||||
|
||||
// Although we're in TVA_PHASE_SUSTAIN at this point, we cannot be sure that there is no active ramp at the moment.
|
||||
// In case the channel volume or the expression changes frequently, the previously started ramp may still be in progress.
|
||||
// Real hardware units ignore this possibility and rely on the assumption that the target is the current amp.
|
||||
// This is OK in most situations but when the ramp that is currently in progress needs to change direction
|
||||
// due to a volume/expression update, this leads to a jump in the amp that is audible as an unpleasant click.
|
||||
// To avoid that, we compare the newTarget with the the actual current ramp value and correct the direction if necessary.
|
||||
int targetDelta = newTarget - target;
|
||||
|
||||
// Calculate an increment to get to the new amp value in a short, more or less consistent amount of time
|
||||
Bit8u newIncrement;
|
||||
if (targetDelta >= 0) {
|
||||
bool descending = targetDelta < 0;
|
||||
if (!descending) {
|
||||
newIncrement = tables->envLogarithmicTime[Bit8u(targetDelta)] - 2;
|
||||
} else {
|
||||
newIncrement = (tables->envLogarithmicTime[Bit8u(-targetDelta)] - 2) | 0x80;
|
||||
}
|
||||
if (part->getSynth()->isNiceAmpRampEnabled() && (descending != ampRamp->isBelowCurrent(newTarget))) {
|
||||
newIncrement ^= 0x80;
|
||||
}
|
||||
|
||||
// Configure so that once the transition's complete and nextPhase() is called, we'll just re-enter sustain phase (or decay phase, depending on parameters at the time).
|
||||
startRamp(newTarget, newIncrement, TVA_PHASE_SUSTAIN - 1);
|
||||
}
|
||||
@@ -260,7 +271,7 @@ void TVA::nextPhase() {
|
||||
}
|
||||
|
||||
bool allLevelsZeroFromNowOn = false;
|
||||
if (partialParam->tva.envLevel[3] == 0) {
|
||||
if (!partial->getSynth()->controlROMFeatures->quirkTVAZeroEnvLevels && partialParam->tva.envLevel[3] == 0) {
|
||||
if (newPhase == TVA_PHASE_4) {
|
||||
allLevelsZeroFromNowOn = true;
|
||||
} else if (partialParam->tva.envLevel[2] == 0) {
|
||||
@@ -283,7 +294,7 @@ void TVA::nextPhase() {
|
||||
int envPointIndex = phase;
|
||||
|
||||
if (!allLevelsZeroFromNowOn) {
|
||||
newTarget = calcBasicAmp(tables, partial, system, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression());
|
||||
newTarget = calcBasicAmp(tables, partial, system, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression(), partial->getSynth()->controlROMFeatures->quirkRingModulationNoMix);
|
||||
|
||||
if (newPhase == TVA_PHASE_SUSTAIN || newPhase == TVA_PHASE_RELEASE) {
|
||||
if (partialParam->tva.envLevel[3] == 0) {
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "LA32Ramp.h"
|
||||
#include "Partial.h"
|
||||
#include "Poly.h"
|
||||
#include "Synth.h"
|
||||
#include "Tables.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
@@ -52,7 +53,7 @@ enum {
|
||||
PHASE_DONE = 7
|
||||
};
|
||||
|
||||
static int calcBaseCutoff(const TimbreParam::PartialParam *partialParam, Bit32u basePitch, unsigned int key) {
|
||||
static int calcBaseCutoff(const TimbreParam::PartialParam *partialParam, Bit32u basePitch, unsigned int key, bool quirkTVFBaseCutoffLimit) {
|
||||
// This table matches the values used by a real LAPC-I.
|
||||
static const Bit8s biasLevelToBiasMult[] = {85, 42, 21, 16, 10, 5, 2, 0, -2, -5, -10, -16, -21, -74, -85};
|
||||
// These values represent unique options with no consistent pattern, so we have to use something like a table in any case.
|
||||
@@ -90,8 +91,14 @@ static int calcBaseCutoff(const TimbreParam::PartialParam *partialParam, Bit32u
|
||||
if (pitchDeltaThing > 0) {
|
||||
baseCutoff -= pitchDeltaThing;
|
||||
}
|
||||
} else if (baseCutoff < -2048) {
|
||||
baseCutoff = -2048;
|
||||
} else if (quirkTVFBaseCutoffLimit) {
|
||||
if (baseCutoff <= -0x400) {
|
||||
baseCutoff = -400;
|
||||
}
|
||||
} else {
|
||||
if (baseCutoff < -2048) {
|
||||
baseCutoff = -2048;
|
||||
}
|
||||
}
|
||||
baseCutoff += 2056;
|
||||
baseCutoff >>= 4; // PORTABILITY NOTE: Hmm... Depends whether it could've been below -2056, but maybe arithmetic shift assumed?
|
||||
@@ -110,7 +117,7 @@ void TVF::startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase) {
|
||||
phase = newPhase;
|
||||
cutoffModifierRamp->startRamp(newTarget, newIncrement);
|
||||
#if MT32EMU_MONITOR_TVF >= 1
|
||||
partial->getSynth()->printDebug("[+%lu] [Partial %d] TVF,ramp,%d,%d,%d,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), newTarget, (newIncrement & 0x80) ? -1 : 1, (newIncrement & 0x7F), newPhase);
|
||||
partial->getSynth()->printDebug("[+%lu] [Partial %d] TVF,ramp,%x,%s%x,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), newTarget, (newIncrement & 0x80) ? "-" : "+", (newIncrement & 0x7F), newPhase);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -122,7 +129,7 @@ void TVF::reset(const TimbreParam::PartialParam *newPartialParam, unsigned int b
|
||||
|
||||
const Tables *tables = &Tables::getInstance();
|
||||
|
||||
baseCutoff = calcBaseCutoff(newPartialParam, basePitch, key);
|
||||
baseCutoff = calcBaseCutoff(newPartialParam, basePitch, key, partial->getSynth()->controlROMFeatures->quirkTVFBaseCutoffLimit);
|
||||
#if MT32EMU_MONITOR_TVF >= 1
|
||||
partial->getSynth()->printDebug("[+%lu] [Partial %d] TVF,base,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), baseCutoff);
|
||||
#endif
|
||||
|
||||
@@ -51,13 +51,15 @@ static Bit16u keyToPitchTable[] = {
|
||||
21845, 22187, 22528, 22869
|
||||
};
|
||||
|
||||
// We want to do processing 4000 times per second. FIXME: This is pretty arbitrary.
|
||||
static const int NOMINAL_PROCESS_TIMER_PERIOD_SAMPLES = SAMPLE_RATE / 4000;
|
||||
|
||||
// The timer runs at 500kHz. This is how much to increment it after 8 samples passes.
|
||||
// We multiply by 8 to get rid of the fraction and deal with just integers.
|
||||
static const int PROCESS_TIMER_INCREMENT_x8 = 8 * 500000 / SAMPLE_RATE;
|
||||
|
||||
TVP::TVP(const Partial *usePartial) :
|
||||
partial(usePartial), system(&usePartial->getSynth()->mt32ram.system) {
|
||||
// We want to do processing 4000 times per second. FIXME: This is pretty arbitrary.
|
||||
maxCounter = SAMPLE_RATE / 4000;
|
||||
// The timer runs at 500kHz. We only need to bother updating it every maxCounter samples, before we do processing.
|
||||
// This is how much to increment it by every maxCounter samples.
|
||||
processTimerIncrement = 500000 * maxCounter / SAMPLE_RATE;
|
||||
}
|
||||
|
||||
static Bit16s keyToPitch(unsigned int key) {
|
||||
@@ -76,13 +78,15 @@ static inline Bit32s fineToPitch(Bit8u fine) {
|
||||
return (fine - 50) * 4096 / 1200; // One cent per fine offset
|
||||
}
|
||||
|
||||
static Bit32u calcBasePitch(const Partial *partial, const TimbreParam::PartialParam *partialParam, const MemParams::PatchTemp *patchTemp, unsigned int key) {
|
||||
static Bit32u calcBasePitch(const Partial *partial, const TimbreParam::PartialParam *partialParam, const MemParams::PatchTemp *patchTemp, unsigned int key, const ControlROMFeatureSet *controlROMFeatures) {
|
||||
Bit32s basePitch = keyToPitch(key);
|
||||
basePitch = (basePitch * pitchKeyfollowMult[partialParam->wg.pitchKeyfollow]) >> 13; // PORTABILITY NOTE: Assumes arithmetic shift
|
||||
basePitch += coarseToPitch(partialParam->wg.pitchCoarse);
|
||||
basePitch += fineToPitch(partialParam->wg.pitchFine);
|
||||
// NOTE:Mok: This is done on MT-32, but not LAPC-I:
|
||||
//pitch += coarseToPitch(patchTemp->patch.keyShift + 12);
|
||||
if (controlROMFeatures->quirkKeyShift) {
|
||||
// NOTE:Mok: This is done on MT-32, but not LAPC-I:
|
||||
basePitch += coarseToPitch(patchTemp->patch.keyShift + 12);
|
||||
}
|
||||
basePitch += fineToPitch(patchTemp->patch.fineTune);
|
||||
|
||||
const ControlROMPCMStruct *controlROMPCMStruct = partial->getControlROMPCMStruct();
|
||||
@@ -97,7 +101,12 @@ static Bit32u calcBasePitch(const Partial *partial, const TimbreParam::PartialPa
|
||||
basePitch += 33037;
|
||||
}
|
||||
}
|
||||
if (basePitch < 0) {
|
||||
|
||||
// MT-32 GEN0 does 16-bit calculations here, allowing an integer overflow.
|
||||
// This quirk is observable playing the patch defined for timbre "HIT BOTTOM" in Larry 3.
|
||||
if (controlROMFeatures->quirkBasePitchOverflow) {
|
||||
basePitch = basePitch & 0xffff;
|
||||
} else if (basePitch < 0) {
|
||||
basePitch = 0;
|
||||
}
|
||||
if (basePitch > 59392) {
|
||||
@@ -107,18 +116,22 @@ static Bit32u calcBasePitch(const Partial *partial, const TimbreParam::PartialPa
|
||||
}
|
||||
|
||||
static Bit32u calcVeloMult(Bit8u veloSensitivity, unsigned int velocity) {
|
||||
if (veloSensitivity == 0 || veloSensitivity > 3) {
|
||||
// Note that on CM-32L/LAPC-I veloSensitivity is never > 3, since it's clipped to 3 by the max tables.
|
||||
if (veloSensitivity == 0) {
|
||||
return 21845; // aka floor(4096 / 12 * 64), aka ~64 semitones
|
||||
}
|
||||
unsigned int reversedVelocity = 127 - velocity;
|
||||
unsigned int scaledReversedVelocity;
|
||||
if (veloSensitivity > 3) {
|
||||
// Note that on CM-32L/LAPC-I veloSensitivity is never > 3, since it's clipped to 3 by the max tables.
|
||||
// MT-32 GEN0 has a bug here that leads to unspecified behaviour. We assume it is as follows.
|
||||
scaledReversedVelocity = (reversedVelocity << 8) >> ((3 - veloSensitivity) & 0x1f);
|
||||
} else {
|
||||
scaledReversedVelocity = reversedVelocity << (5 + veloSensitivity);
|
||||
}
|
||||
// When velocity is 127, the multiplier is 21845, aka ~64 semitones (regardless of veloSensitivity).
|
||||
// The lower the velocity, the lower the multiplier. The veloSensitivity determines the amount decreased per velocity value.
|
||||
// The minimum multiplier (with velocity 0, veloSensitivity 3) is 170 (~half a semitone).
|
||||
Bit32u veloMult = 32768;
|
||||
veloMult -= (127 - velocity) << (5 + veloSensitivity);
|
||||
veloMult *= 21845;
|
||||
veloMult >>= 15;
|
||||
return veloMult;
|
||||
// The minimum multiplier on CM-32L/LAPC-I (with velocity 0, veloSensitivity 3) is 170 (~half a semitone).
|
||||
return ((32768 - scaledReversedVelocity) * 21845) >> 15;
|
||||
}
|
||||
|
||||
static Bit32s calcTargetPitchOffsetWithoutLFO(const TimbreParam::PartialParam *partialParam, int levelIndex, unsigned int velocity) {
|
||||
@@ -139,7 +152,7 @@ void TVP::reset(const Part *usePart, const TimbreParam::PartialParam *usePartial
|
||||
// FIXME: We're using a per-TVP timer instead of a system-wide one for convenience.
|
||||
timeElapsed = 0;
|
||||
|
||||
basePitch = calcBasePitch(partial, partialParam, patchTemp, key);
|
||||
basePitch = calcBasePitch(partial, partialParam, patchTemp, key, partial->getSynth()->controlROMFeatures);
|
||||
currentPitchOffset = calcTargetPitchOffsetWithoutLFO(partialParam, 0, velocity);
|
||||
targetPitchOffsetWithoutLFO = currentPitchOffset;
|
||||
phase = 0;
|
||||
@@ -166,22 +179,23 @@ Bit32u TVP::getBasePitch() const {
|
||||
void TVP::updatePitch() {
|
||||
Bit32s newPitch = basePitch + currentPitchOffset;
|
||||
if (!partial->isPCM() || (partial->getControlROMPCMStruct()->len & 0x01) == 0) { // FIXME: Use !partial->pcmWaveEntry->unaffectedByMasterTune instead
|
||||
// FIXME: masterTune recalculation doesn't really happen here, and there are various bugs not yet emulated
|
||||
// FIXME: There are various bugs not yet emulated
|
||||
// 171 is ~half a semitone.
|
||||
newPitch += ((system->masterTune - 64) * 171) >> 6; // PORTABILITY NOTE: Assumes arithmetic shift.
|
||||
newPitch += partial->getSynth()->getMasterTunePitchDelta();
|
||||
}
|
||||
if ((partialParam->wg.pitchBenderEnabled & 1) != 0) {
|
||||
newPitch += part->getPitchBend();
|
||||
}
|
||||
if (newPitch < 0) {
|
||||
|
||||
// MT-32 GEN0 does 16-bit calculations here, allowing an integer overflow.
|
||||
// This quirk is exploited e.g. in Colonel's Bequest timbres "Lightning" and "SwmpBackgr".
|
||||
if (partial->getSynth()->controlROMFeatures->quirkPitchEnvelopeOverflow) {
|
||||
newPitch = newPitch & 0xffff;
|
||||
} else if (newPitch < 0) {
|
||||
newPitch = 0;
|
||||
}
|
||||
|
||||
// Skipping this check seems about right emulation of MT-32 GEN0 quirk exploited in Colonel's Bequest timbre "Lightning"
|
||||
if (partial->getSynth()->controlROMFeatures->quirkPitchEnvelopeOverflow == 0) {
|
||||
if (newPitch > 59392) {
|
||||
newPitch = 59392;
|
||||
}
|
||||
if (newPitch > 59392) {
|
||||
newPitch = 59392;
|
||||
}
|
||||
pitch = Bit16u(newPitch);
|
||||
|
||||
@@ -284,13 +298,19 @@ void TVP::startDecay() {
|
||||
}
|
||||
|
||||
Bit16u TVP::nextPitch() {
|
||||
// FIXME: Write explanation of counter and time increment
|
||||
// We emulate MCU software timer using these counter and processTimerIncrement variables.
|
||||
// The value of nominalProcessTimerPeriod approximates the period in samples
|
||||
// between subsequent firings of the timer that normally occur.
|
||||
// However, accurate emulation is quite complicated because the timer is not guaranteed to fire in time.
|
||||
// This makes pitch variations on real unit non-deterministic and dependent on various factors.
|
||||
if (counter == 0) {
|
||||
timeElapsed += processTimerIncrement;
|
||||
timeElapsed = timeElapsed & 0x00FFFFFF;
|
||||
timeElapsed = (timeElapsed + processTimerIncrement) & 0x00FFFFFF;
|
||||
// This roughly emulates pitch deviations observed on real units when playing a single partial that uses TVP/LFO.
|
||||
counter = NOMINAL_PROCESS_TIMER_PERIOD_SAMPLES + (rand() & 3);
|
||||
processTimerIncrement = (PROCESS_TIMER_INCREMENT_x8 * counter) >> 3;
|
||||
process();
|
||||
}
|
||||
counter = (counter + 1) % maxCounter;
|
||||
counter--;
|
||||
return pitch;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ private:
|
||||
const TimbreParam::PartialParam *partialParam;
|
||||
const MemParams::PatchTemp *patchTemp;
|
||||
|
||||
int maxCounter;
|
||||
int processTimerIncrement;
|
||||
int counter;
|
||||
Bit32u timeElapsed;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "../ROMInfo.h"
|
||||
#include "../Synth.h"
|
||||
#include "../MidiStreamParser.h"
|
||||
#include "../SampleRateConverter.h"
|
||||
|
||||
#include "c_types.h"
|
||||
#include "c_interface.h"
|
||||
@@ -30,11 +31,17 @@ using namespace MT32Emu;
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
struct SamplerateConversionState {
|
||||
double outputSampleRate;
|
||||
SamplerateConversionQuality srcQuality;
|
||||
SampleRateConverter *src;
|
||||
};
|
||||
|
||||
static mt32emu_service_version getSynthVersionID(mt32emu_service_i) {
|
||||
return MT32EMU_SERVICE_VERSION_CURRENT;
|
||||
}
|
||||
|
||||
static const mt32emu_service_i_v0 SERVICE_VTABLE = {
|
||||
static const mt32emu_service_i_v2 SERVICE_VTABLE = {
|
||||
getSynthVersionID,
|
||||
mt32emu_get_supported_report_handler_version,
|
||||
mt32emu_get_supported_midi_receiver_version,
|
||||
@@ -95,7 +102,17 @@ static const mt32emu_service_i_v0 SERVICE_VTABLE = {
|
||||
mt32emu_get_partial_states,
|
||||
mt32emu_get_playing_notes,
|
||||
mt32emu_get_patch_name,
|
||||
mt32emu_read_memory
|
||||
mt32emu_read_memory,
|
||||
mt32emu_get_best_analog_output_mode,
|
||||
mt32emu_set_stereo_output_samplerate,
|
||||
mt32emu_set_samplerate_conversion_quality,
|
||||
mt32emu_select_renderer_type,
|
||||
mt32emu_get_selected_renderer_type,
|
||||
mt32emu_convert_output_to_synth_timestamp,
|
||||
mt32emu_convert_synth_to_output_timestamp,
|
||||
mt32emu_get_internal_rendered_sample_count,
|
||||
mt32emu_set_nice_amp_ramp_enabled,
|
||||
mt32emu_is_nice_amp_ramp_enabled
|
||||
};
|
||||
|
||||
} // namespace MT32Emu
|
||||
@@ -108,6 +125,7 @@ struct mt32emu_data {
|
||||
DefaultMidiStreamParser *midiParser;
|
||||
Bit32u partialCount;
|
||||
AnalogOutputMode analogOutputMode;
|
||||
SamplerateConversionState *srcState;
|
||||
};
|
||||
|
||||
// Internal C++ utility stuff
|
||||
@@ -305,7 +323,7 @@ extern "C" {
|
||||
|
||||
mt32emu_service_i mt32emu_get_service_i() {
|
||||
mt32emu_service_i i;
|
||||
i.v0 = &SERVICE_VTABLE;
|
||||
i.v2 = &SERVICE_VTABLE;
|
||||
return i;
|
||||
}
|
||||
|
||||
@@ -329,6 +347,10 @@ mt32emu_bit32u mt32emu_get_stereo_output_samplerate(const mt32emu_analog_output_
|
||||
return Synth::getStereoOutputSampleRate(static_cast<AnalogOutputMode>(analog_output_mode));
|
||||
}
|
||||
|
||||
mt32emu_analog_output_mode mt32emu_get_best_analog_output_mode(const double target_samplerate) {
|
||||
return mt32emu_analog_output_mode(SampleRateConverter::getBestAnalogOutputMode(target_samplerate));
|
||||
}
|
||||
|
||||
mt32emu_context mt32emu_create_context(mt32emu_report_handler_i report_handler, void *instance_data) {
|
||||
mt32emu_data *data = new mt32emu_data;
|
||||
data->reportHandler = (report_handler.v0 != NULL) ? new DelegatingReportHandlerAdapter(report_handler, instance_data) : new ReportHandler;
|
||||
@@ -339,12 +361,22 @@ mt32emu_context mt32emu_create_context(mt32emu_report_handler_i report_handler,
|
||||
data->partialCount = DEFAULT_MAX_PARTIALS;
|
||||
data->analogOutputMode = AnalogOutputMode_COARSE;
|
||||
|
||||
data->srcState = new SamplerateConversionState;
|
||||
data->srcState->outputSampleRate = 0.0;
|
||||
data->srcState->srcQuality = SamplerateConversionQuality_GOOD;
|
||||
data->srcState->src = NULL;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void mt32emu_free_context(mt32emu_context data) {
|
||||
if (data == NULL) return;
|
||||
|
||||
delete data->srcState->src;
|
||||
data->srcState->src = NULL;
|
||||
delete data->srcState;
|
||||
data->srcState = NULL;
|
||||
|
||||
if (data->controlROMImage != NULL) {
|
||||
delete data->controlROMImage->getFile();
|
||||
ROMImage::freeROMImage(data->controlROMImage);
|
||||
@@ -417,6 +449,16 @@ void mt32emu_set_analog_output_mode(mt32emu_context context, const mt32emu_analo
|
||||
context->analogOutputMode = static_cast<AnalogOutputMode>(analog_output_mode);
|
||||
}
|
||||
|
||||
void mt32emu_set_stereo_output_samplerate(mt32emu_context context, const double samplerate) {
|
||||
if (0.0 <= samplerate) {
|
||||
context->srcState->outputSampleRate = samplerate;
|
||||
}
|
||||
}
|
||||
|
||||
void mt32emu_set_samplerate_conversion_quality(mt32emu_context context, const mt32emu_samplerate_conversion_quality quality) {
|
||||
context->srcState->srcQuality = SamplerateConversionQuality(quality);
|
||||
}
|
||||
|
||||
void mt32emu_select_renderer_type(mt32emu_context context, const mt32emu_renderer_type renderer_type) {
|
||||
context->synth->selectRendererType(static_cast<RendererType>(renderer_type));
|
||||
}
|
||||
@@ -432,11 +474,16 @@ mt32emu_return_code mt32emu_open_synth(mt32emu_const_context context) {
|
||||
if (!context->synth->open(*context->controlROMImage, *context->pcmROMImage, context->partialCount, context->analogOutputMode)) {
|
||||
return MT32EMU_RC_FAILED;
|
||||
}
|
||||
SamplerateConversionState &srcState = *context->srcState;
|
||||
const double outputSampleRate = (0.0 < srcState.outputSampleRate) ? srcState.outputSampleRate : context->synth->getStereoOutputSampleRate();
|
||||
srcState.src = new SampleRateConverter(*context->synth, outputSampleRate, srcState.srcQuality);
|
||||
return MT32EMU_RC_OK;
|
||||
}
|
||||
|
||||
void mt32emu_close_synth(mt32emu_const_context context) {
|
||||
context->synth->close();
|
||||
delete context->srcState->src;
|
||||
context->srcState->src = NULL;
|
||||
}
|
||||
|
||||
mt32emu_boolean mt32emu_is_open(mt32emu_const_context context) {
|
||||
@@ -444,7 +491,24 @@ mt32emu_boolean mt32emu_is_open(mt32emu_const_context context) {
|
||||
}
|
||||
|
||||
mt32emu_bit32u mt32emu_get_actual_stereo_output_samplerate(mt32emu_const_context context) {
|
||||
return context->synth->getStereoOutputSampleRate();
|
||||
if (context->srcState->src == NULL) {
|
||||
return context->synth->getStereoOutputSampleRate();
|
||||
}
|
||||
return mt32emu_bit32u(0.5 + context->srcState->src->convertSynthToOutputTimestamp(SAMPLE_RATE));
|
||||
}
|
||||
|
||||
mt32emu_bit32u mt32emu_convert_output_to_synth_timestamp(mt32emu_const_context context, mt32emu_bit32u output_timestamp) {
|
||||
if (context->srcState->src == NULL) {
|
||||
return output_timestamp;
|
||||
}
|
||||
return mt32emu_bit32u(0.5 + context->srcState->src->convertOutputToSynthTimestamp(output_timestamp));
|
||||
}
|
||||
|
||||
mt32emu_bit32u mt32emu_convert_synth_to_output_timestamp(mt32emu_const_context context, mt32emu_bit32u synth_timestamp) {
|
||||
if (context->srcState->src == NULL) {
|
||||
return synth_timestamp;
|
||||
}
|
||||
return mt32emu_bit32u(0.5 + context->srcState->src->convertSynthToOutputTimestamp(synth_timestamp));
|
||||
}
|
||||
|
||||
void mt32emu_flush_midi_queue(mt32emu_const_context context) {
|
||||
@@ -460,6 +524,10 @@ void mt32emu_set_midi_receiver(mt32emu_context context, mt32emu_midi_receiver_i
|
||||
context->midiParser = (midi_receiver.v0 != NULL) ? new DelegatingMidiStreamParser(context, midi_receiver, instance_data) : new DefaultMidiStreamParser(*context->synth);
|
||||
}
|
||||
|
||||
mt32emu_bit32u mt32emu_get_internal_rendered_sample_count(mt32emu_const_context context) {
|
||||
return context->synth->getInternalRenderedSampleCount();
|
||||
}
|
||||
|
||||
void mt32emu_parse_stream(mt32emu_const_context context, const mt32emu_bit8u *stream, mt32emu_bit32u length) {
|
||||
context->midiParser->resetTimestamp();
|
||||
context->midiParser->parseStream(stream, length);
|
||||
@@ -584,12 +652,28 @@ mt32emu_boolean mt32emu_is_reversed_stereo_enabled(mt32emu_const_context context
|
||||
return context->synth->isReversedStereoEnabled() ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE;
|
||||
}
|
||||
|
||||
void mt32emu_set_nice_amp_ramp_enabled(mt32emu_const_context context, const mt32emu_boolean enabled) {
|
||||
context->synth->setNiceAmpRampEnabled(enabled != MT32EMU_BOOL_FALSE);
|
||||
}
|
||||
|
||||
mt32emu_boolean mt32emu_is_nice_amp_ramp_enabled(mt32emu_const_context context) {
|
||||
return context->synth->isNiceAmpRampEnabled() ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE;
|
||||
}
|
||||
|
||||
void mt32emu_render_bit16s(mt32emu_const_context context, mt32emu_bit16s *stream, mt32emu_bit32u len) {
|
||||
context->synth->render(stream, len);
|
||||
if (context->srcState->src != NULL) {
|
||||
context->srcState->src->getOutputSamples(stream, len);
|
||||
} else {
|
||||
context->synth->render(stream, len);
|
||||
}
|
||||
}
|
||||
|
||||
void mt32emu_render_float(mt32emu_const_context context, float *stream, mt32emu_bit32u len) {
|
||||
context->synth->render(stream, len);
|
||||
if (context->srcState->src != NULL) {
|
||||
context->srcState->src->getOutputSamples(stream, len);
|
||||
} else {
|
||||
context->synth->render(stream, len);
|
||||
}
|
||||
}
|
||||
|
||||
void mt32emu_render_bit16s_streams(mt32emu_const_context context, const mt32emu_dac_output_bit16s_streams *streams, mt32emu_bit32u len) {
|
||||
|
||||
@@ -209,6 +209,12 @@ MT32EMU_EXPORT mt32emu_bit32u mt32emu_set_midi_event_queue_size(mt32emu_const_co
|
||||
*/
|
||||
MT32EMU_EXPORT void mt32emu_set_midi_receiver(mt32emu_context context, mt32emu_midi_receiver_i midi_receiver, void *instance_data);
|
||||
|
||||
/**
|
||||
* Returns current value of the global counter of samples rendered since the synth was created (at the native sample rate 32000 Hz).
|
||||
* This method helps to compute accurate timestamp of a MIDI message to use with the methods below.
|
||||
*/
|
||||
MT32EMU_EXPORT mt32emu_bit32u mt32emu_get_internal_rendered_sample_count(mt32emu_const_context context);
|
||||
|
||||
/* Enqueues a MIDI event for subsequent playback.
|
||||
* The MIDI event will be processed not before the specified timestamp.
|
||||
* The timestamp is measured as the global rendered sample count since the synth was created (at the native sample rate 32000 Hz).
|
||||
@@ -324,7 +330,6 @@ MT32EMU_EXPORT mt32emu_midi_delay_mode mt32emu_get_midi_delay_mode(mt32emu_const
|
||||
* Sets output gain factor for synth output channels. Applied to all output samples and unrelated with the synth's Master volume,
|
||||
* it rather corresponds to the gain of the output analog circuitry of the hardware units. However, together with mt32emu_set_reverb_output_gain()
|
||||
* it offers to the user a capability to control the gain of reverb and non-reverb output channels independently.
|
||||
* Ignored in MT32EMU_DAC_PURE mode.
|
||||
*/
|
||||
MT32EMU_EXPORT void mt32emu_set_output_gain(mt32emu_const_context context, float gain);
|
||||
/** Returns current output gain factor for synth output channels. */
|
||||
@@ -339,7 +344,6 @@ MT32EMU_EXPORT float mt32emu_get_output_gain(mt32emu_const_context context);
|
||||
* corresponds to the level of digital capture. Although, according to the CM-64 PCB schematic,
|
||||
* there is a difference in the reverb analogue circuit, and the resulting output gain is 0.68
|
||||
* of that for LA32 analogue output. This factor is applied to the reverb output gain.
|
||||
* Ignored in MT32EMU_DAC_PURE mode.
|
||||
*/
|
||||
MT32EMU_EXPORT void mt32emu_set_reverb_output_gain(mt32emu_const_context context, float gain);
|
||||
/** Returns current output gain factor for reverb wet output channels. */
|
||||
@@ -350,6 +354,18 @@ MT32EMU_EXPORT void mt32emu_set_reversed_stereo_enabled(mt32emu_const_context co
|
||||
/** Returns whether left and right output channels are swapped. */
|
||||
MT32EMU_EXPORT mt32emu_boolean mt32emu_is_reversed_stereo_enabled(mt32emu_const_context context);
|
||||
|
||||
/**
|
||||
* Allows to toggle the NiceAmpRamp mode.
|
||||
* In this mode, we want to ensure that amp ramp never jumps to the target
|
||||
* value and always gradually increases or decreases. It seems that real units
|
||||
* do not bother to always check if a newly started ramp leads to a jump.
|
||||
* We also prefer the quality improvement over the emulation accuracy,
|
||||
* so this mode is enabled by default.
|
||||
*/
|
||||
MT32EMU_EXPORT void mt32emu_set_nice_amp_ramp_enabled(mt32emu_const_context context, const mt32emu_boolean enabled);
|
||||
/** Returns whether NiceAmpRamp mode is enabled. */
|
||||
MT32EMU_EXPORT mt32emu_boolean mt32emu_is_nice_amp_ramp_enabled(mt32emu_const_context context);
|
||||
|
||||
/**
|
||||
* Renders samples to the specified output stream as if they were sampled at the analog stereo output at the desired sample rate.
|
||||
* If the output sample rate is not specified explicitly, the default output sample rate is used which depends on the current
|
||||
|
||||
@@ -120,7 +120,8 @@ typedef enum {
|
||||
typedef enum {
|
||||
MT32EMU_SERVICE_VERSION_0 = 0,
|
||||
MT32EMU_SERVICE_VERSION_1 = 1,
|
||||
MT32EMU_SERVICE_VERSION_CURRENT = MT32EMU_SERVICE_VERSION_1
|
||||
MT32EMU_SERVICE_VERSION_2 = 2,
|
||||
MT32EMU_SERVICE_VERSION_CURRENT = MT32EMU_SERVICE_VERSION_2
|
||||
} mt32emu_service_version;
|
||||
|
||||
/* === Report Handler Interface === */
|
||||
@@ -297,6 +298,11 @@ typedef union mt32emu_service_i mt32emu_service_i;
|
||||
mt32emu_bit32u (*convertOutputToSynthTimestamp)(mt32emu_const_context context, mt32emu_bit32u output_timestamp); \
|
||||
mt32emu_bit32u (*convertSynthToOutputTimestamp)(mt32emu_const_context context, mt32emu_bit32u synth_timestamp);
|
||||
|
||||
#define MT32EMU_SERVICE_I_V2 \
|
||||
mt32emu_bit32u (*getInternalRenderedSampleCount)(mt32emu_const_context context); \
|
||||
void (*setNiceAmpRampEnabled)(mt32emu_const_context context, const mt32emu_boolean enabled); \
|
||||
mt32emu_boolean (*isNiceAmpRampEnabled)(mt32emu_const_context context);
|
||||
|
||||
typedef struct {
|
||||
MT32EMU_SERVICE_I_V0
|
||||
} mt32emu_service_i_v0;
|
||||
@@ -306,6 +312,12 @@ typedef struct {
|
||||
MT32EMU_SERVICE_I_V1
|
||||
} mt32emu_service_i_v1;
|
||||
|
||||
typedef struct {
|
||||
MT32EMU_SERVICE_I_V0
|
||||
MT32EMU_SERVICE_I_V1
|
||||
MT32EMU_SERVICE_I_V2
|
||||
} mt32emu_service_i_v2;
|
||||
|
||||
/**
|
||||
* Extensible interface for all the library services.
|
||||
* Union intended to view an interface of any subsequent version as any parent interface not requiring a cast.
|
||||
@@ -314,9 +326,11 @@ typedef struct {
|
||||
union mt32emu_service_i {
|
||||
const mt32emu_service_i_v0 *v0;
|
||||
const mt32emu_service_i_v1 *v1;
|
||||
const mt32emu_service_i_v2 *v2;
|
||||
};
|
||||
|
||||
#undef MT32EMU_SERVICE_I_V0
|
||||
#undef MT32EMU_SERVICE_I_V1
|
||||
#undef MT32EMU_SERVICE_I_V2
|
||||
|
||||
#endif /* #ifndef MT32EMU_C_TYPES_H */
|
||||
|
||||
@@ -61,6 +61,7 @@ mt32emu_service_i mt32emu_get_service_i();
|
||||
#define mt32emu_flush_midi_queue i.v0->flushMIDIQueue
|
||||
#define mt32emu_set_midi_event_queue_size i.v0->setMIDIEventQueueSize
|
||||
#define mt32emu_set_midi_receiver i.v0->setMIDIReceiver
|
||||
#define mt32emu_get_internal_rendered_sample_count iV2()->getInternalRenderedSampleCount
|
||||
#define mt32emu_parse_stream i.v0->parseStream
|
||||
#define mt32emu_parse_stream_at i.v0->parseStream_At
|
||||
#define mt32emu_play_short_message i.v0->playShortMessage
|
||||
@@ -90,6 +91,8 @@ mt32emu_service_i mt32emu_get_service_i();
|
||||
#define mt32emu_get_reverb_output_gain i.v0->getReverbOutputGain
|
||||
#define mt32emu_set_reversed_stereo_enabled i.v0->setReversedStereoEnabled
|
||||
#define mt32emu_is_reversed_stereo_enabled i.v0->isReversedStereoEnabled
|
||||
#define mt32emu_set_nice_amp_ramp_enabled iV2()->setNiceAmpRampEnabled
|
||||
#define mt32emu_is_nice_amp_ramp_enabled iV2()->isNiceAmpRampEnabled
|
||||
#define mt32emu_render_bit16s i.v0->renderBit16s
|
||||
#define mt32emu_render_float i.v0->renderFloat
|
||||
#define mt32emu_render_bit16s_streams i.v0->renderBit16sStreams
|
||||
@@ -213,6 +216,7 @@ public:
|
||||
void setMIDIReceiver(mt32emu_midi_receiver_i midi_receiver, void *instance_data) { mt32emu_set_midi_receiver(c, midi_receiver, instance_data); }
|
||||
void setMIDIReceiver(IMidiReceiver &midi_receiver) { setMIDIReceiver(CppInterfaceImpl::getMidiReceiverThunk(), &midi_receiver); }
|
||||
|
||||
Bit32u getInternalRenderedSampleCount() { return mt32emu_get_internal_rendered_sample_count(c); }
|
||||
void parseStream(const Bit8u *stream, Bit32u length) { mt32emu_parse_stream(c, stream, length); }
|
||||
void parseStream_At(const Bit8u *stream, Bit32u length, Bit32u timestamp) { mt32emu_parse_stream_at(c, stream, length, timestamp); }
|
||||
void playShortMessage(Bit32u message) { mt32emu_play_short_message(c, message); }
|
||||
@@ -249,6 +253,9 @@ public:
|
||||
void setReversedStereoEnabled(const bool enabled) { mt32emu_set_reversed_stereo_enabled(c, enabled ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE); }
|
||||
bool isReversedStereoEnabled() { return mt32emu_is_reversed_stereo_enabled(c) != MT32EMU_BOOL_FALSE; }
|
||||
|
||||
void setNiceAmpRampEnabled(const bool enabled) { mt32emu_set_nice_amp_ramp_enabled(c, enabled ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE); }
|
||||
bool isNiceAmpRampEnabled() { return mt32emu_is_nice_amp_ramp_enabled(c) != MT32EMU_BOOL_FALSE; }
|
||||
|
||||
void renderBit16s(Bit16s *stream, Bit32u len) { mt32emu_render_bit16s(c, stream, len); }
|
||||
void renderFloat(float *stream, Bit32u len) { mt32emu_render_float(c, stream, len); }
|
||||
void renderBit16sStreams(const mt32emu_dac_output_bit16s_streams *streams, Bit32u len) { mt32emu_render_bit16s_streams(c, streams, len); }
|
||||
@@ -271,6 +278,7 @@ private:
|
||||
|
||||
#if MT32EMU_API_TYPE == 2
|
||||
const mt32emu_service_i_v1 *iV1() { return (getVersionID() < MT32EMU_SERVICE_VERSION_1) ? NULL : i.v1; }
|
||||
const mt32emu_service_i_v2 *iV2() { return (getVersionID() < MT32EMU_SERVICE_VERSION_2) ? NULL : i.v2; }
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -421,6 +429,7 @@ static mt32emu_midi_receiver_i getMidiReceiverThunk() {
|
||||
#undef mt32emu_flush_midi_queue
|
||||
#undef mt32emu_set_midi_event_queue_size
|
||||
#undef mt32emu_set_midi_receiver
|
||||
#undef mt32emu_get_internal_rendered_sample_count
|
||||
#undef mt32emu_parse_stream
|
||||
#undef mt32emu_parse_stream_at
|
||||
#undef mt32emu_play_short_message
|
||||
@@ -450,6 +459,8 @@ static mt32emu_midi_receiver_i getMidiReceiverThunk() {
|
||||
#undef mt32emu_get_reverb_output_gain
|
||||
#undef mt32emu_set_reversed_stereo_enabled
|
||||
#undef mt32emu_is_reversed_stereo_enabled
|
||||
#undef mt32emu_set_nice_amp_ramp_enabled
|
||||
#undef mt32emu_is_nice_amp_ramp_enabled
|
||||
#undef mt32emu_render_bit16s
|
||||
#undef mt32emu_render_float
|
||||
#undef mt32emu_render_bit16s_streams
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
#ifndef MT32EMU_CONFIG_H
|
||||
#define MT32EMU_CONFIG_H
|
||||
|
||||
#define MT32EMU_VERSION "2.1.0"
|
||||
#define MT32EMU_VERSION "2.2.0"
|
||||
#define MT32EMU_VERSION_MAJOR 2
|
||||
#define MT32EMU_VERSION_MINOR 1
|
||||
#define MT32EMU_VERSION_MINOR 2
|
||||
#define MT32EMU_VERSION_PATCH 0
|
||||
|
||||
/* Library Exports Configuration
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
#define MT32EMU_MAX_STREAM_BUFFER_SIZE 32768
|
||||
|
||||
/* This should correspond to the MIDI buffer size used in real h/w devices.
|
||||
* CM-32L control ROM seems using 1000 bytes, old MT-32 isn't confirmed by now.
|
||||
* CM-32L control ROM is using 1000 bytes, and MT-32 GEN0 is using only 240 bytes (semi-confirmed by now).
|
||||
*/
|
||||
#define MT32EMU_SYSEX_BUFFER_SIZE 1000
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ typedef float FloatSample;
|
||||
/** Interface defines an abstract source of samples. It can either define a single channel stream or a stream with interleaved channels. */
|
||||
class FloatSampleProvider {
|
||||
public:
|
||||
virtual ~FloatSampleProvider() {};
|
||||
virtual ~FloatSampleProvider() {}
|
||||
|
||||
virtual void getOutputSamples(FloatSample *outBuffer, unsigned int size) = 0;
|
||||
};
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace SRCTools {
|
||||
/** Interface defines an abstract source of samples. It can either define a single channel stream or a stream with interleaved channels. */
|
||||
class ResamplerStage {
|
||||
public:
|
||||
virtual ~ResamplerStage() {};
|
||||
virtual ~ResamplerStage() {}
|
||||
|
||||
/** Returns a lower estimation of required number of input samples to produce the specified number of output samples. */
|
||||
virtual unsigned int estimateInLength(const unsigned int outLength) const = 0;
|
||||
|
||||
Reference in New Issue
Block a user