diff --git a/audio/softsynth/mt32/File.cpp b/audio/softsynth/mt32/File.cpp index 333230b61c1..07164823d30 100755 --- a/audio/softsynth/mt32/File.cpp +++ b/audio/softsynth/mt32/File.cpp @@ -26,11 +26,15 @@ namespace MT32Emu { AbstractFile::AbstractFile() : sha1DigestCalculated(false) { sha1Digest[0] = 0; + + reserved = NULL; } AbstractFile::AbstractFile(const SHA1Digest &useSHA1Digest) : sha1DigestCalculated(true) { memcpy(sha1Digest, useSHA1Digest, sizeof(SHA1Digest) - 1); sha1Digest[sizeof(SHA1Digest) - 1] = 0; // Ensure terminator char. + + reserved = NULL; } const File::SHA1Digest &AbstractFile::getSHA1() { diff --git a/audio/softsynth/mt32/File.h b/audio/softsynth/mt32/File.h index f29d4f3c42d..c9a7d582b4c 100755 --- a/audio/softsynth/mt32/File.h +++ b/audio/softsynth/mt32/File.h @@ -49,6 +49,9 @@ protected: private: bool sha1DigestCalculated; SHA1Digest sha1Digest; + + // Binary compatibility helper. + void *reserved; }; class MT32EMU_EXPORT ArrayFile : public AbstractFile { diff --git a/audio/softsynth/mt32/Part.cpp b/audio/softsynth/mt32/Part.cpp index c4d5b945512..9b5119ee8bb 100755 --- a/audio/softsynth/mt32/Part.cpp +++ b/audio/softsynth/mt32/Part.cpp @@ -376,7 +376,8 @@ void RhythmPart::noteOn(unsigned int midiKey, unsigned int velocity) { unsigned int key = midiKey; unsigned int drumNum = key - 24; int drumTimbreNum = rhythmTemp[drumNum].timbre; - if (drumTimbreNum >= 127) { // 94 on MT-32 + const int drumTimbreCount = 64 + synth->controlROMMap->timbreRCount; // 94 on MT-32, 128 on LAPC-I/CM32-L + if (drumTimbreNum == 127 || drumTimbreNum >= drumTimbreCount) { // timbre #127 is OFF, no sense to play it synth->printDebug("%s: Attempted to play unmapped key %d (velocity %d)", name, midiKey, velocity); return; } diff --git a/audio/softsynth/mt32/SampleRateConverter.cpp b/audio/softsynth/mt32/SampleRateConverter.cpp new file mode 100644 index 00000000000..70f860f3952 --- /dev/null +++ b/audio/softsynth/mt32/SampleRateConverter.cpp @@ -0,0 +1,97 @@ +/* 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 . + */ + +#include "SampleRateConverter.h" + +#if MT32EMU_WITH_LIBSOXR_RESAMPLER +#include "srchelper/SoxrAdapter.h" +#elif MT32EMU_WITH_LIBSAMPLERATE_RESAMPLER +#include "srchelper/SamplerateAdapter.h" +#else +#include "srchelper/InternalResampler.h" +#endif + +#include "Synth.h" + +using namespace MT32Emu; + +static inline void *createDelegate(Synth &synth, double targetSampleRate, SampleRateConverter::Quality quality) { +#if MT32EMU_WITH_LIBSOXR_RESAMPLER + return new SoxrAdapter(synth, targetSampleRate, quality); +#elif MT32EMU_WITH_LIBSAMPLERATE_RESAMPLER + return new SamplerateAdapter(synth, targetSampleRate, quality); +#else + return new InternalResampler(synth, targetSampleRate, quality); +#endif +} + +AnalogOutputMode SampleRateConverter::getBestAnalogOutputMode(double targetSampleRate) { + if (Synth::getStereoOutputSampleRate(AnalogOutputMode_ACCURATE) < targetSampleRate) { + return AnalogOutputMode_OVERSAMPLED; + } else if (Synth::getStereoOutputSampleRate(AnalogOutputMode_COARSE) < targetSampleRate) { + return AnalogOutputMode_ACCURATE; + } + return AnalogOutputMode_COARSE; +} + +SampleRateConverter::SampleRateConverter(Synth &useSynth, double targetSampleRate, Quality useQuality) : + synthInternalToTargetSampleRateRatio(SAMPLE_RATE / targetSampleRate), + srcDelegate(createDelegate(useSynth, targetSampleRate, useQuality)) +{} + +SampleRateConverter::~SampleRateConverter() { +#if MT32EMU_WITH_LIBSOXR_RESAMPLER + delete static_cast(srcDelegate); +#elif MT32EMU_WITH_LIBSAMPLERATE_RESAMPLER + delete static_cast(srcDelegate); +#else + delete static_cast(srcDelegate); +#endif +} + +void SampleRateConverter::getOutputSamples(float *buffer, unsigned int length) { +#if MT32EMU_WITH_LIBSOXR_RESAMPLER + static_cast(srcDelegate)->getOutputSamples(buffer, length); +#elif MT32EMU_WITH_LIBSAMPLERATE_RESAMPLER + static_cast(srcDelegate)->getOutputSamples(buffer, length); +#else + static_cast(srcDelegate)->getOutputSamples(buffer, length); +#endif +} + +void SampleRateConverter::getOutputSamples(Bit16s *outBuffer, unsigned int length) { + static const unsigned int CHANNEL_COUNT = 2; + + float floatBuffer[CHANNEL_COUNT * MAX_SAMPLES_PER_RUN]; + while (length > 0) { + const unsigned int size = MAX_SAMPLES_PER_RUN < length ? MAX_SAMPLES_PER_RUN : length; + getOutputSamples(floatBuffer, size); + float *outs = floatBuffer; + float *ends = floatBuffer + CHANNEL_COUNT * size; + while (outs < ends) { + *(outBuffer++) = Synth::convertSample(*(outs++)); + } + length -= size; + } +} + +double SampleRateConverter::convertOutputToSynthTimestamp(double outputTimestamp) const { + return outputTimestamp * synthInternalToTargetSampleRateRatio; +} + +double SampleRateConverter::convertSynthToOutputTimestamp(double synthTimestamp) const { + return synthTimestamp / synthInternalToTargetSampleRateRatio; +} diff --git a/audio/softsynth/mt32/SampleRateConverter.h b/audio/softsynth/mt32/SampleRateConverter.h new file mode 100644 index 00000000000..24bce0891b9 --- /dev/null +++ b/audio/softsynth/mt32/SampleRateConverter.h @@ -0,0 +1,78 @@ +/* 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 . + */ + +#ifndef SAMPLE_RATE_CONVERTER_H +#define SAMPLE_RATE_CONVERTER_H + +#include "globals.h" +#include "Types.h" +#include "Enumerations.h" + +namespace MT32Emu { + +class Synth; + +/* SampleRateConverter class allows to convert the synthesiser output to any desired sample rate. + * It processes the completely mixed stereo output signal as it passes the analogue circuit emulation, + * so emulating the synthesiser output signal passing further through an ADC. + * Several conversion quality options are provided which allow to trade-off the conversion speed vs. the passband width. + * All the options except FASTEST guarantee full suppression of the aliasing noise in terms of the 16-bit integer samples. + */ +class MT32EMU_EXPORT SampleRateConverter { +public: + enum Quality { + // Use this only when the speed is more important than the audio quality. + FASTEST, + FAST, + GOOD, + BEST + }; + + // Returns the value of AnalogOutputMode for which the output signal may retain its full frequency spectrum + // at the sample rate specified by the targetSampleRate argument. + static AnalogOutputMode getBestAnalogOutputMode(double targetSampleRate); + + // Creates a SampleRateConverter instance that converts output signal from the synth to the given sample rate + // with the specified conversion quality. + SampleRateConverter(Synth &synth, double targetSampleRate, Quality quality); + ~SampleRateConverter(); + + // Fills the provided output buffer with the results of the sample rate conversion. + // The input samples are automatically retrieved from the synth as necessary. + void getOutputSamples(MT32Emu::Bit16s *buffer, unsigned int length); + + // Fills the provided output buffer with the results of the sample rate conversion. + // The input samples are automatically retrieved from the synth as necessary. + void getOutputSamples(float *buffer, unsigned int length); + + // Returns the number of samples produced at the internal synth sample rate (32000 Hz) + // that correspond to the number of samples at the target sample rate. + // Intended to facilitate audio time synchronisation. + double convertOutputToSynthTimestamp(double outputTimestamp) const; + + // Returns the number of samples produced at the target sample rate + // that correspond to the number of samples at the internal synth sample rate (32000 Hz). + // Intended to facilitate audio time synchronisation. + double convertSynthToOutputTimestamp(double synthTimestamp) const; + +private: + const double synthInternalToTargetSampleRateRatio; + void * const srcDelegate; +}; // class SampleRateConverter + +} // namespace MT32Emu + +#endif // SAMPLE_RATE_CONVERTER_H diff --git a/audio/softsynth/mt32/Synth.cpp b/audio/softsynth/mt32/Synth.cpp index 162c5d8ceee..00832c5d503 100755 --- a/audio/softsynth/mt32/Synth.cpp +++ b/audio/softsynth/mt32/Synth.cpp @@ -63,16 +63,6 @@ static inline PartialState getPartialState(PartialManager *partialManager, unsig return partial->isActive() ? PARTIAL_PHASE_TO_STATE[partial->getTVA()->getPhase()] : PartialState_INACTIVE; } -#if MT32EMU_USE_FLOAT_SAMPLES -static inline Bit16s convertSample(float sample) { - return Synth::clipSampleEx(Bit32s(sample * 16384.0f)); // This multiplier takes into account the DAC bit shift -} -#else -static inline float convertSample(Bit16s sample) { - return float(sample) / 16384.0f; // This multiplier takes into account the DAC bit shift -} -#endif - class SampleFormatConverter { protected: #if MT32EMU_USE_FLOAT_SAMPLES @@ -98,7 +88,7 @@ public: } Sample *inBuffer = sampleBuffer; while (len--) { - *(outBuffer++) = convertSample(*(inBuffer++)); + *(outBuffer++) = Synth::convertSample(*(inBuffer++)); } } @@ -133,6 +123,12 @@ public: class Renderer { Synth &synth; + // These buffers are used for building the output streams as they are found at the DAC entrance. + // The output is mixed down to stereo interleaved further in the analog circuitry emulation. + Sample tmpNonReverbLeft[MAX_SAMPLES_PER_RUN], tmpNonReverbRight[MAX_SAMPLES_PER_RUN]; + Sample tmpReverbDryLeft[MAX_SAMPLES_PER_RUN], tmpReverbDryRight[MAX_SAMPLES_PER_RUN]; + Sample tmpReverbWetLeft[MAX_SAMPLES_PER_RUN], tmpReverbWetRight[MAX_SAMPLES_PER_RUN]; + public: Renderer(Synth &useSynth) : synth(useSynth) {} @@ -140,7 +136,7 @@ public: void renderStreams(SampleFormatConverter &nonReverbLeft, SampleFormatConverter &nonReverbRight, SampleFormatConverter &reverbDryLeft, SampleFormatConverter &reverbDryRight, SampleFormatConverter &reverbWetLeft, SampleFormatConverter &reverbWetRight, Bit32u len); void produceLA32Output(Sample *buffer, Bit32u len); void convertSamplesToOutput(Sample *buffer, Bit32u len); - void doRenderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sample *reverbDryLeft, Sample *reverbDryRight, Sample *reverbWetLeft, Sample *reverbWetRight, Bit32u len); + void doRenderStreams(DACOutputStreams &streams, Bit32u len); }; Bit32u Synth::getLibraryVersionInt() { @@ -527,7 +523,7 @@ bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, A return open(controlROMImage, pcmROMImage, DEFAULT_MAX_PARTIALS, analogOutputMode); } -bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, unsigned int usePartialCount, AnalogOutputMode analogOutputMode) { +bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, Bit32u usePartialCount, AnalogOutputMode analogOutputMode) { if (opened) { return false; } @@ -1698,12 +1694,8 @@ void Renderer::render(SampleFormatConverter &converter, Bit32u len) { return; } - // As in AnalogOutputMode_ACCURATE mode output is upsampled, buffer size MAX_SAMPLES_PER_RUN is more than enough. - Sample tmpNonReverbLeft[MAX_SAMPLES_PER_RUN], tmpNonReverbRight[MAX_SAMPLES_PER_RUN]; - Sample tmpReverbDryLeft[MAX_SAMPLES_PER_RUN], tmpReverbDryRight[MAX_SAMPLES_PER_RUN]; - Sample tmpReverbWetLeft[MAX_SAMPLES_PER_RUN], tmpReverbWetRight[MAX_SAMPLES_PER_RUN]; - while (len > 0) { + // As in AnalogOutputMode_ACCURATE mode output is upsampled, MAX_SAMPLES_PER_RUN is more than enough for the temp buffers. Bit32u thisPassLen = len > MAX_SAMPLES_PER_RUN ? MAX_SAMPLES_PER_RUN : len; synth.renderStreams(tmpNonReverbLeft, tmpNonReverbRight, tmpReverbDryLeft, tmpReverbDryRight, tmpReverbWetLeft, tmpReverbWetRight, synth.analog->getDACStreamsLength(thisPassLen)); synth.analog->process(converter.sampleBuffer, tmpNonReverbLeft, tmpNonReverbRight, tmpReverbDryLeft, tmpReverbDryRight, tmpReverbWetLeft, tmpReverbWetRight, thisPassLen); @@ -1771,11 +1763,12 @@ void Renderer::renderStreams( } } } - doRenderStreams( + DACOutputStreams streams = { nonReverbLeft.sampleBuffer, nonReverbRight.sampleBuffer, reverbDryLeft.sampleBuffer, reverbDryRight.sampleBuffer, - reverbWetLeft.sampleBuffer, reverbWetRight.sampleBuffer, - thisLen); + reverbWetLeft.sampleBuffer, reverbWetRight.sampleBuffer + }; + doRenderStreams(streams, thisLen); nonReverbLeft.convert(thisLen); nonReverbRight.convert(thisLen); reverbDryLeft.convert(thisLen); @@ -1870,17 +1863,14 @@ void Renderer::convertSamplesToOutput(Sample *buffer, Bit32u len) { #endif } -void Renderer::doRenderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sample *reverbDryLeft, Sample *reverbDryRight, Sample *reverbWetLeft, Sample *reverbWetRight, Bit32u len) { - // Even if LA32 output isn't desired, we proceed anyway with temp buffers - Sample tmpBufNonReverbLeft[MAX_SAMPLES_PER_RUN], tmpBufNonReverbRight[MAX_SAMPLES_PER_RUN]; - if (nonReverbLeft == NULL) nonReverbLeft = tmpBufNonReverbLeft; - if (nonReverbRight == NULL) nonReverbRight = tmpBufNonReverbRight; - - Sample tmpBufReverbDryLeft[MAX_SAMPLES_PER_RUN], tmpBufReverbDryRight[MAX_SAMPLES_PER_RUN]; - if (reverbDryLeft == NULL) reverbDryLeft = tmpBufReverbDryLeft; - if (reverbDryRight == NULL) reverbDryRight = tmpBufReverbDryRight; - +void Renderer::doRenderStreams(DACOutputStreams &streams, Bit32u len) { if (synth.activated) { + // Even if LA32 output isn't desired, we proceed anyway with temp buffers + Sample *nonReverbLeft = streams.nonReverbLeft == NULL ? tmpNonReverbLeft : streams.nonReverbLeft; + Sample *nonReverbRight = streams.nonReverbRight == NULL ? tmpNonReverbRight : streams.nonReverbRight; + Sample *reverbDryLeft = streams.reverbDryLeft == NULL ? tmpReverbDryLeft : streams.reverbDryLeft; + Sample *reverbDryRight = streams.reverbDryRight == NULL ? tmpReverbDryRight : streams.reverbDryRight; + Synth::muteSampleBuffer(nonReverbLeft, len); Synth::muteSampleBuffer(nonReverbRight, len); Synth::muteSampleBuffer(reverbDryLeft, len); @@ -1898,33 +1888,32 @@ void Renderer::doRenderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sa produceLA32Output(reverbDryRight, len); if (synth.isReverbEnabled()) { - synth.reverbModel->process(reverbDryLeft, reverbDryRight, reverbWetLeft, reverbWetRight, len); - if (reverbWetLeft != NULL) convertSamplesToOutput(reverbWetLeft, len); - if (reverbWetRight != NULL) convertSamplesToOutput(reverbWetRight, len); + synth.reverbModel->process(reverbDryLeft, reverbDryRight, streams.reverbWetLeft, streams.reverbWetRight, len); + if (streams.reverbWetLeft != NULL) convertSamplesToOutput(streams.reverbWetLeft, len); + if (streams.reverbWetRight != NULL) convertSamplesToOutput(streams.reverbWetRight, len); } else { - Synth::muteSampleBuffer(reverbWetLeft, len); - Synth::muteSampleBuffer(reverbWetRight, len); + Synth::muteSampleBuffer(streams.reverbWetLeft, len); + Synth::muteSampleBuffer(streams.reverbWetRight, len); } // Don't bother with conversion if the output is going to be unused - if (nonReverbLeft != tmpBufNonReverbLeft) { + if (streams.nonReverbLeft != NULL) { produceLA32Output(nonReverbLeft, len); convertSamplesToOutput(nonReverbLeft, len); } - if (nonReverbRight != tmpBufNonReverbRight) { + if (streams.nonReverbRight != NULL) { produceLA32Output(nonReverbRight, len); convertSamplesToOutput(nonReverbRight, len); } - if (reverbDryLeft != tmpBufReverbDryLeft) convertSamplesToOutput(reverbDryLeft, len); - if (reverbDryRight != tmpBufReverbDryRight) convertSamplesToOutput(reverbDryRight, len); + if (streams.reverbDryLeft != NULL) convertSamplesToOutput(reverbDryLeft, len); + if (streams.reverbDryRight != NULL) convertSamplesToOutput(reverbDryRight, len); } else { - // Avoid muting buffers that wasn't requested - if (nonReverbLeft != tmpBufNonReverbLeft) Synth::muteSampleBuffer(nonReverbLeft, len); - if (nonReverbRight != tmpBufNonReverbRight) Synth::muteSampleBuffer(nonReverbRight, len); - if (reverbDryLeft != tmpBufReverbDryLeft) Synth::muteSampleBuffer(reverbDryLeft, len); - if (reverbDryRight != tmpBufReverbDryRight) Synth::muteSampleBuffer(reverbDryRight, len); - Synth::muteSampleBuffer(reverbWetLeft, len); - Synth::muteSampleBuffer(reverbWetRight, len); + Synth::muteSampleBuffer(streams.nonReverbLeft, len); + Synth::muteSampleBuffer(streams.nonReverbRight, len); + Synth::muteSampleBuffer(streams.reverbDryLeft, len); + Synth::muteSampleBuffer(streams.reverbDryRight, len); + Synth::muteSampleBuffer(streams.reverbWetLeft, len); + Synth::muteSampleBuffer(streams.reverbWetRight, len); } synth.partialManager->clearAlreadyOutputed(); diff --git a/audio/softsynth/mt32/Synth.h b/audio/softsynth/mt32/Synth.h index 5561d8d5db5..fe31d99f022 100755 --- a/audio/softsynth/mt32/Synth.h +++ b/audio/softsynth/mt32/Synth.h @@ -120,6 +120,8 @@ friend class PartialManager; friend class Poly; friend class Renderer; friend class RhythmPart; +friend class SamplerateAdapter; +friend class SoxrAdapter; friend class TVA; friend class TVP; @@ -254,6 +256,14 @@ public: } } + static inline Bit16s convertSample(float sample) { + return Synth::clipSampleEx(Bit32s(sample * 16384.0f)); // This multiplier takes into account the DAC bit shift + } + + static inline float convertSample(Bit16s sample) { + return float(sample) / 16384.0f; // This multiplier takes into account the DAC bit shift + } + // Returns library version as an integer in format: 0x00MMmmpp, where: // MM - major version number // mm - minor version number diff --git a/audio/softsynth/mt32/config.h b/audio/softsynth/mt32/config.h index 779cc4d4b3b..af59f055a0b 100644 --- a/audio/softsynth/mt32/config.h +++ b/audio/softsynth/mt32/config.h @@ -18,10 +18,10 @@ #ifndef MT32EMU_CONFIG_H #define MT32EMU_CONFIG_H -#define MT32EMU_VERSION "2.0.0" +#define MT32EMU_VERSION "2.0.3" #define MT32EMU_VERSION_MAJOR 2 #define MT32EMU_VERSION_MINOR 0 -#define MT32EMU_VERSION_PATCH 0 +#define MT32EMU_VERSION_PATCH 3 #define MT32EMU_EXPORTS_TYPE 3 diff --git a/audio/softsynth/mt32/module.mk b/audio/softsynth/mt32/module.mk index 1a27492c2a3..7657f5b55f6 100644 --- a/audio/softsynth/mt32/module.mk +++ b/audio/softsynth/mt32/module.mk @@ -21,5 +21,20 @@ MODULE_OBJS := \ sha1/sha1.o \ c_interface/c_interface.o +# SampleRateConverter.o \ +# srchelper/InternalResampler.o \ +# srchelper/SamplerateAdapter.o \ +# srchelper/SoxrAdapter.o \ +# srchelper/srctools/src/FIRResampler.o \ +# srchelper/srctools/src/IIR2xResampler.o \ +# srchelper/srctools/src/LinearResampler.o \ +# srchelper/srctools/src/ResamplerModel.o \ +# srchelper/srctools/src/SincResampler.o +# TODO: The Munt SampleRateConverter requires these additional -I options. +# This is not a very nice way of doing that, though, as it adds them globally. +# INCLUDES += -I $(srcdir)/$(MODULE)/srchelper/srctools/include +# INCLUDES += -I $(srcdir)/$(MODULE)/ + + # Include common rules include $(srcdir)/rules.mk diff --git a/audio/softsynth/mt32/mt32emu.h b/audio/softsynth/mt32/mt32emu.h index 9f2b0582503..3f3b6af3449 100755 --- a/audio/softsynth/mt32/mt32emu.h +++ b/audio/softsynth/mt32/mt32emu.h @@ -77,6 +77,7 @@ #include "ROMInfo.h" #include "Synth.h" #include "MidiStreamParser.h" +#include "SampleRateConverter.h" #endif /* #if !defined(__cplusplus) || MT32EMU_API_TYPE == 1 */ diff --git a/audio/softsynth/mt32/srchelper/InternalResampler.cpp b/audio/softsynth/mt32/srchelper/InternalResampler.cpp new file mode 100644 index 00000000000..f76c7d117e9 --- /dev/null +++ b/audio/softsynth/mt32/srchelper/InternalResampler.cpp @@ -0,0 +1,74 @@ +/* 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 . + */ + +#include "InternalResampler.h" + +#include +#include + +#include "Synth.h" + +using namespace SRCTools; + +namespace MT32Emu { + +class SynthWrapper : public FloatSampleProvider { + Synth &synth; + +public: + SynthWrapper(Synth &useSynth) : synth(useSynth) + {} + + void getOutputSamples(FloatSample *outBuffer, unsigned int size) { + synth.render(outBuffer, size); + } +}; + +static FloatSampleProvider &createModel(Synth &synth, SRCTools::FloatSampleProvider &synthSource, double targetSampleRate, SampleRateConverter::Quality quality) { + static const double MAX_AUDIBLE_FREQUENCY = 20000.0; + + const double sourceSampleRate = synth.getStereoOutputSampleRate(); + if (quality != SampleRateConverter::FASTEST) { + const bool oversampledMode = synth.getStereoOutputSampleRate() == Synth::getStereoOutputSampleRate(AnalogOutputMode_OVERSAMPLED); + // Oversampled input allows to bypass IIR interpolation stage and, in some cases, IIR decimation stage + if (oversampledMode && (0.5 * sourceSampleRate) <= targetSampleRate) { + // NOTE: In the oversampled mode, the transition band starts at 20kHz and ends at 28kHz + double passband = MAX_AUDIBLE_FREQUENCY; + double stopband = 0.5 * sourceSampleRate + MAX_AUDIBLE_FREQUENCY; + ResamplerStage &resamplerStage = *SincResampler::createSincResampler(sourceSampleRate, targetSampleRate, passband, stopband, ResamplerModel::DEFAULT_DB_SNR, ResamplerModel::DEFAULT_WINDOWED_SINC_MAX_UPSAMPLE_FACTOR); + return ResamplerModel::createResamplerModel(synthSource, resamplerStage); + } + } + return ResamplerModel::createResamplerModel(synthSource, sourceSampleRate, targetSampleRate, static_cast(quality)); +} + +} // namespace MT32Emu + +using namespace MT32Emu; + +InternalResampler::InternalResampler(Synth &synth, double targetSampleRate, SampleRateConverter::Quality quality) : + synthSource(*new SynthWrapper(synth)), + model(createModel(synth, synthSource, targetSampleRate, quality)) +{} + +InternalResampler::~InternalResampler() { + ResamplerModel::freeResamplerModel(model, synthSource); + delete &synthSource; +} + +void InternalResampler::getOutputSamples(float *buffer, unsigned int length) { + model.getOutputSamples(buffer, length); +} diff --git a/audio/softsynth/mt32/srchelper/InternalResampler.h b/audio/softsynth/mt32/srchelper/InternalResampler.h new file mode 100644 index 00000000000..0a5c3233c81 --- /dev/null +++ b/audio/softsynth/mt32/srchelper/InternalResampler.h @@ -0,0 +1,42 @@ +/* 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 . + */ + +#ifndef INTERNAL_RESAMPLER_H +#define INTERNAL_RESAMPLER_H + +#include "../SampleRateConverter.h" + +#include "FloatSampleProvider.h" + +namespace MT32Emu { + +class Synth; + +class InternalResampler { +public: + InternalResampler(Synth &synth, double targetSampleRate, SampleRateConverter::Quality quality); + ~InternalResampler(); + + void getOutputSamples(float *buffer, unsigned int length); + +private: + SRCTools::FloatSampleProvider &synthSource; + SRCTools::FloatSampleProvider &model; +}; + +} // namespace MT32Emu + +#endif // INTERNAL_RESAMPLER_H diff --git a/audio/softsynth/mt32/srchelper/SamplerateAdapter.cpp b/audio/softsynth/mt32/srchelper/SamplerateAdapter.cpp new file mode 100644 index 00000000000..33fcfa81dd3 --- /dev/null +++ b/audio/softsynth/mt32/srchelper/SamplerateAdapter.cpp @@ -0,0 +1,99 @@ +/* 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 . + */ + +#include "SamplerateAdapter.h" + +#include "Synth.h" + +using namespace MT32Emu; + +static const unsigned int CHANNEL_COUNT = 2; + +long SamplerateAdapter::getInputSamples(void *cb_data, float **data) { + SamplerateAdapter *instance = static_cast(cb_data); + unsigned int length = instance->inBufferSize < 1 ? 1 : (MAX_SAMPLES_PER_RUN < instance->inBufferSize ? MAX_SAMPLES_PER_RUN : instance->inBufferSize); + instance->synth.render(instance->inBuffer, length); + *data = instance->inBuffer; + instance->inBufferSize -= length; + return length; +} + +SamplerateAdapter::SamplerateAdapter(Synth &useSynth, double targetSampleRate, SampleRateConverter::Quality quality) : + synth(useSynth), + inBuffer(new float[CHANNEL_COUNT * MAX_SAMPLES_PER_RUN]), + inBufferSize(MAX_SAMPLES_PER_RUN), + inputToOutputRatio(useSynth.getStereoOutputSampleRate() / targetSampleRate), + outputToInputRatio(targetSampleRate / useSynth.getStereoOutputSampleRate()) +{ + int error; + int conversionType; + switch (quality) { + case SampleRateConverter::FASTEST: + conversionType = SRC_LINEAR; + break; + case SampleRateConverter::FAST: + conversionType = SRC_SINC_FASTEST; + break; + case SampleRateConverter::BEST: + conversionType = SRC_SINC_BEST_QUALITY; + break; + case SampleRateConverter::GOOD: + default: + conversionType = SRC_SINC_MEDIUM_QUALITY; + break; + }; + resampler = src_callback_new(getInputSamples, conversionType, CHANNEL_COUNT, &error, this); + if (error != 0) { + synth.printDebug("SamplerateAdapter: Creation of Samplerate instance failed: %s\n", src_strerror(error)); + src_delete(resampler); + resampler = NULL; + } +} + +SamplerateAdapter::~SamplerateAdapter() { + delete[] inBuffer; + src_delete(resampler); +} + +void SamplerateAdapter::getOutputSamples(float *buffer, unsigned int length) { + if (resampler == NULL) { + Synth::muteSampleBuffer(buffer, CHANNEL_COUNT * length); + return; + } + while (length > 0) { + inBufferSize = static_cast(length * inputToOutputRatio + 0.5); + long gotFrames = src_callback_read(resampler, outputToInputRatio, long(length), buffer); + int error = src_error(resampler); + if (error != 0) { + synth.printDebug("SamplerateAdapter: Samplerate error during processing: %s > resetting\n", src_strerror(error)); + error = src_reset(resampler); + if (error != 0) { + synth.printDebug("SamplerateAdapter: Samplerate failed to reset: %s\n", src_strerror(error)); + src_delete(resampler); + resampler = NULL; + Synth::muteSampleBuffer(buffer, CHANNEL_COUNT * length); + synth.printDebug("SamplerateAdapter: Samplerate disabled\n"); + return; + } + continue; + } + if (gotFrames <= 0) { + synth.printDebug("SamplerateAdapter: got %li frames from Samplerate, weird\n", gotFrames); + } + buffer += CHANNEL_COUNT * gotFrames; + length -= gotFrames; + } +} diff --git a/audio/softsynth/mt32/srchelper/SamplerateAdapter.h b/audio/softsynth/mt32/srchelper/SamplerateAdapter.h new file mode 100644 index 00000000000..aac259b50a6 --- /dev/null +++ b/audio/softsynth/mt32/srchelper/SamplerateAdapter.h @@ -0,0 +1,46 @@ +/* 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 . + */ + +#ifndef SAMPLERATE_ADAPTER_H +#define SAMPLERATE_ADAPTER_H + +#include + +#include "../SampleRateConverter.h" + +namespace MT32Emu { + +class SamplerateAdapter { +public: + SamplerateAdapter(Synth &synth, double targetSampleRate, SampleRateConverter::Quality quality); + ~SamplerateAdapter(); + + void getOutputSamples(float *outBuffer, unsigned int length); + +private: + Synth &synth; + float * const inBuffer; + unsigned int inBufferSize; + const double inputToOutputRatio; + const double outputToInputRatio; + SRC_STATE *resampler; + + static long getInputSamples(void *cb_data, float **data); +}; + +} // namespace MT32Emu + +#endif // SAMPLERATE_ADAPTER_H diff --git a/audio/softsynth/mt32/srchelper/SoxrAdapter.cpp b/audio/softsynth/mt32/srchelper/SoxrAdapter.cpp new file mode 100644 index 00000000000..b13192be92c --- /dev/null +++ b/audio/softsynth/mt32/srchelper/SoxrAdapter.cpp @@ -0,0 +1,106 @@ +/* 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 . + */ + +#include "SoxrAdapter.h" + +#include "Synth.h" + +using namespace MT32Emu; + +static const unsigned int CHANNEL_COUNT = 2; + +size_t SoxrAdapter::getInputSamples(void *input_fn_state, soxr_in_t *data, size_t requested_len) { + unsigned int length = requested_len < 1 ? 1 : (MAX_SAMPLES_PER_RUN < requested_len ? MAX_SAMPLES_PER_RUN : requested_len); + SoxrAdapter *instance = static_cast(input_fn_state); + instance->synth.render(instance->inBuffer, length); + *data = instance->inBuffer; + return length; +} + +SoxrAdapter::SoxrAdapter(Synth &useSynth, double targetSampleRate, SampleRateConverter::Quality quality) : + synth(useSynth), + inBuffer(new float[CHANNEL_COUNT * MAX_SAMPLES_PER_RUN]) +{ + soxr_io_spec_t ioSpec = soxr_io_spec(SOXR_FLOAT32_I, SOXR_FLOAT32_I); + unsigned long qualityRecipe; + switch (quality) { + case SampleRateConverter::FASTEST: + qualityRecipe = SOXR_QQ; + break; + case SampleRateConverter::FAST: + qualityRecipe = SOXR_LQ; + break; + case SampleRateConverter::GOOD: + qualityRecipe = SOXR_MQ; + break; + case SampleRateConverter::BEST: + default: + qualityRecipe = SOXR_16_BITQ; + break; + }; + soxr_quality_spec_t qSpec = soxr_quality_spec(qualityRecipe, 0); + soxr_runtime_spec_t rtSpec = soxr_runtime_spec(1); + soxr_error_t error; + resampler = soxr_create(synth.getStereoOutputSampleRate(), targetSampleRate, CHANNEL_COUNT, &error, &ioSpec, &qSpec, &rtSpec); + if (error != NULL) { + synth.printDebug("SoxrAdapter: Creation of SOXR instance failed: %s\n", soxr_strerror(error)); + soxr_delete(resampler); + resampler = NULL; + return; + } + error = soxr_set_input_fn(resampler, getInputSamples, this, MAX_SAMPLES_PER_RUN); + if (error != NULL) { + synth.printDebug("SoxrAdapter: Installing sample feed for SOXR failed: %s\n", soxr_strerror(error)); + soxr_delete(resampler); + resampler = NULL; + } +} + +SoxrAdapter::~SoxrAdapter() { + delete[] inBuffer; + if (resampler != NULL) { + soxr_delete(resampler); + } +} + +void SoxrAdapter::getOutputSamples(float *buffer, unsigned int length) { + if (resampler == NULL) { + Synth::muteSampleBuffer(buffer, CHANNEL_COUNT * length); + return; + } + while (length > 0) { + size_t gotFrames = soxr_output(resampler, buffer, size_t(length)); + soxr_error_t error = soxr_error(resampler); + if (error != NULL) { + synth.printDebug("SoxrAdapter: SOXR error during processing: %s > resetting\n", soxr_strerror(error)); + error = soxr_clear(resampler); + if (error != NULL) { + synth.printDebug("SoxrAdapter: SOXR failed to reset: %s\n", soxr_strerror(error)); + soxr_delete(resampler); + resampler = NULL; + Synth::muteSampleBuffer(buffer, CHANNEL_COUNT * length); + synth.printDebug("SoxrAdapter: SOXR disabled\n"); + return; + } + continue; + } + if (gotFrames == 0) { + synth.printDebug("SoxrAdapter: got 0 frames from SOXR, weird\n"); + } + buffer += CHANNEL_COUNT * gotFrames; + length -= static_cast(gotFrames); + } +} diff --git a/audio/softsynth/mt32/srchelper/SoxrAdapter.h b/audio/softsynth/mt32/srchelper/SoxrAdapter.h new file mode 100644 index 00000000000..c764d9acfd6 --- /dev/null +++ b/audio/softsynth/mt32/srchelper/SoxrAdapter.h @@ -0,0 +1,43 @@ +/* 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 . + */ + +#ifndef SOXR_ADAPTER_H +#define SOXR_ADAPTER_H + +#include + +#include "../SampleRateConverter.h" + +namespace MT32Emu { + +class SoxrAdapter { +public: + SoxrAdapter(Synth &synth, double targetSampleRate, SampleRateConverter::Quality quality); + ~SoxrAdapter(); + + void getOutputSamples(float *buffer, unsigned int length); + +private: + Synth &synth; + float * const inBuffer; + soxr_t resampler; + + static size_t getInputSamples(void *input_fn_state, soxr_in_t *data, size_t requested_len); +}; + +} // namespace MT32Emu + +#endif // SOXR_ADAPTER_H diff --git a/audio/softsynth/mt32/srchelper/srctools/include/FIRResampler.h b/audio/softsynth/mt32/srchelper/srctools/include/FIRResampler.h new file mode 100644 index 00000000000..2c5d69052a2 --- /dev/null +++ b/audio/softsynth/mt32/srchelper/srctools/include/FIRResampler.h @@ -0,0 +1,67 @@ +/* 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 . + */ + +#ifndef FIR_RESAMPLER_H +#define FIR_RESAMPLER_H + +#include "ResamplerStage.h" + +namespace SRCTools { + +typedef FloatSample FIRCoefficient; + +static const unsigned int FIR_INTERPOLATOR_CHANNEL_COUNT = 2; + +class FIRResampler : public ResamplerStage { +public: + FIRResampler(const unsigned int upsampleFactor, const double downsampleFactor, const FIRCoefficient kernel[], const unsigned int kernelLength); + ~FIRResampler(); + + void process(const FloatSample *&inSamples, unsigned int &inLength, FloatSample *&outSamples, unsigned int &outLength); + unsigned int estimateInLength(const unsigned int outLength) const; + +private: + const struct Constants { + // Filter coefficients + const FIRCoefficient *taps; + // Indicates whether to interpolate filter taps + bool usePhaseInterpolation; + // Size of array of filter coefficients + unsigned int numberOfTaps; + // Upsampling factor + unsigned int numberOfPhases; + // Downsampling factor + double phaseIncrement; + // Index of last delay line element, generally greater than numberOfTaps to form a proper binary mask + unsigned int delayLineMask; + // Delay line + FloatSample(*ringBuffer)[FIR_INTERPOLATOR_CHANNEL_COUNT]; + + Constants(const unsigned int upsampleFactor, const double downsampleFactor, const FIRCoefficient kernel[], const unsigned int kernelLength); + } constants; + // Index of current sample in delay line + unsigned int ringBufferPosition; + // Current phase + double phase; + + bool needNextInSample() const; + void addInSamples(const FloatSample *&inSamples); + void getOutSamplesStereo(FloatSample *&outSamples); +}; // class FIRResampler + +} // namespace SRCTools + +#endif // FIR_RESAMPLER_H diff --git a/audio/softsynth/mt32/srchelper/srctools/include/FloatSampleProvider.h b/audio/softsynth/mt32/srchelper/srctools/include/FloatSampleProvider.h new file mode 100644 index 00000000000..03038d03ec1 --- /dev/null +++ b/audio/softsynth/mt32/srchelper/srctools/include/FloatSampleProvider.h @@ -0,0 +1,34 @@ +/* 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 . + */ + +#ifndef FLOAT_SAMPLE_PROVIDER_H +#define FLOAT_SAMPLE_PROVIDER_H + +namespace SRCTools { + +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 void getOutputSamples(FloatSample *outBuffer, unsigned int size) = 0; +}; + +} // namespace SRCTools + +#endif // FLOAT_SAMPLE_PROVIDER_H diff --git a/audio/softsynth/mt32/srchelper/srctools/include/IIR2xResampler.h b/audio/softsynth/mt32/srchelper/srctools/include/IIR2xResampler.h new file mode 100644 index 00000000000..23733e40496 --- /dev/null +++ b/audio/softsynth/mt32/srchelper/srctools/include/IIR2xResampler.h @@ -0,0 +1,100 @@ +/* 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 . + */ + +#ifndef IIR_2X_RESAMPLER_H +#define IIR_2X_RESAMPLER_H + +#include "ResamplerStage.h" + +namespace SRCTools { + +static const unsigned int IIR_RESAMPER_CHANNEL_COUNT = 2; +static const unsigned int IIR_SECTION_ORDER = 2; + +typedef FloatSample IIRCoefficient; +typedef FloatSample BufferedSample; + +typedef BufferedSample SectionBuffer[IIR_SECTION_ORDER]; + +// Non-trivial coefficients of a 2nd-order section of a parallel bank +// (zero-order numerator coefficient is always zero, zero-order denominator coefficient is always unity) +struct IIRSection { + IIRCoefficient num1; + IIRCoefficient num2; + IIRCoefficient den1; + IIRCoefficient den2; +}; + +class IIRResampler : public ResamplerStage { +public: + enum Quality { + // Used when providing custom IIR filter coefficients. + CUSTOM, + // Use fast elliptic filter with symmetric ripple: N=8, Ap=As=-99 dB, fp=0.125, fs = 0.25 (in terms of sample rate) + FAST, + // Use average elliptic filter with symmetric ripple: N=12, Ap=As=-106 dB, fp=0.193, fs = 0.25 (in terms of sample rate) + GOOD, + // Use sharp elliptic filter with symmetric ripple: N=18, Ap=As=-106 dB, fp=0.238, fs = 0.25 (in terms of sample rate) + BEST + }; + + // Returns the retained fraction of the passband for the given standard quality value + static double getPassbandFractionForQuality(Quality quality); + +protected: + explicit IIRResampler(const Quality quality); + explicit IIRResampler(const unsigned int useSectionsCount, const IIRCoefficient useFIR, const IIRSection useSections[]); + ~IIRResampler(); + + const struct Constants { + // Coefficient of the 0-order FIR part + IIRCoefficient fir; + // 2nd-order sections that comprise a parallel bank + const IIRSection *sections; + // Number of 2nd-order sections + unsigned int sectionsCount; + // Delay line per channel per section + SectionBuffer *buffer; + + Constants(const unsigned int useSectionsCount, const IIRCoefficient useFIR, const IIRSection useSections[], const Quality quality); + } constants; +}; // class IIRResampler + +class IIR2xInterpolator : public IIRResampler { +public: + explicit IIR2xInterpolator(const Quality quality); + explicit IIR2xInterpolator(const unsigned int useSectionsCount, const IIRCoefficient useFIR, const IIRSection useSections[]); + + void process(const FloatSample *&inSamples, unsigned int &inLength, FloatSample *&outSamples, unsigned int &outLength); + unsigned int estimateInLength(const unsigned int outLength) const; + +private: + FloatSample lastInputSamples[IIR_RESAMPER_CHANNEL_COUNT]; + unsigned int phase; +}; + +class IIR2xDecimator : public IIRResampler { +public: + explicit IIR2xDecimator(const Quality quality); + explicit IIR2xDecimator(const unsigned int useSectionsCount, const IIRCoefficient useFIR, const IIRSection useSections[]); + + void process(const FloatSample *&inSamples, unsigned int &inLength, FloatSample *&outSamples, unsigned int &outLength); + unsigned int estimateInLength(const unsigned int outLength) const; +}; + +} // namespace SRCTools + +#endif // IIR_2X_RESAMPLER_H diff --git a/audio/softsynth/mt32/srchelper/srctools/include/LinearResampler.h b/audio/softsynth/mt32/srchelper/srctools/include/LinearResampler.h new file mode 100644 index 00000000000..1f4dd2fcbd3 --- /dev/null +++ b/audio/softsynth/mt32/srchelper/srctools/include/LinearResampler.h @@ -0,0 +1,42 @@ +/* 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 . + */ + +#ifndef LINEAR_RESAMPLER_H +#define LINEAR_RESAMPLER_H + +#include "ResamplerStage.h" + +namespace SRCTools { + +static const unsigned int LINEAR_RESAMPER_CHANNEL_COUNT = 2; + +class LinearResampler : public ResamplerStage { +public: + LinearResampler(double sourceSampleRate, double targetSampleRate); + ~LinearResampler() {} + + unsigned int estimateInLength(const unsigned int outLength) const; + void process(const FloatSample *&inSamples, unsigned int &inLength, FloatSample *&outSamples, unsigned int &outLength); + +private: + const double inputToOutputRatio; + double position; + FloatSample lastInputSamples[LINEAR_RESAMPER_CHANNEL_COUNT]; +}; + +} // namespace SRCTools + +#endif // LINEAR_RESAMPLER_H diff --git a/audio/softsynth/mt32/srchelper/srctools/include/ResamplerModel.h b/audio/softsynth/mt32/srchelper/srctools/include/ResamplerModel.h new file mode 100644 index 00000000000..0372605e876 --- /dev/null +++ b/audio/softsynth/mt32/srchelper/srctools/include/ResamplerModel.h @@ -0,0 +1,63 @@ +/* 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 . + */ + +#ifndef RESAMPLER_MODEL_H +#define RESAMPLER_MODEL_H + +#include "FloatSampleProvider.h" + +namespace SRCTools { + +class ResamplerStage; + +/** Model consists of one or more ResampleStage instances connected in a cascade. */ +namespace ResamplerModel { + +// Seems to be a good choice for 16-bit integer samples. +static const double DEFAULT_DB_SNR = 106; + +// When using linear interpolation, oversampling factor necessary to achieve the DEFAULT_DB_SNR is about 256. +// This figure is the upper estimation, and it can be found by analysing the frequency response of the linear interpolator. +// When less SNR is desired, this value should also decrease in accordance. +static const unsigned int DEFAULT_WINDOWED_SINC_MAX_DOWNSAMPLE_FACTOR = 256; + +// In the default resampler model, the input to the windowed sinc filter is always at least 2x oversampled during upsampling, +// so oversampling factor of 128 should be sufficient to achieve the DEFAULT_DB_SNR with linear interpolation. +static const unsigned int DEFAULT_WINDOWED_SINC_MAX_UPSAMPLE_FACTOR = DEFAULT_WINDOWED_SINC_MAX_DOWNSAMPLE_FACTOR / 2; + + +enum Quality { + // Use when the speed is more important than the audio quality. + FASTEST, + // Use FAST quality setting of the IIR stage (50% of passband retained). + FAST, + // Use GOOD quality setting of the IIR stage (77% of passband retained). + GOOD, + // Use BEST quality setting of the IIR stage (95% of passband retained). + BEST +}; + +FloatSampleProvider &createResamplerModel(FloatSampleProvider &source, double sourceSampleRate, double targetSampleRate, Quality quality); +FloatSampleProvider &createResamplerModel(FloatSampleProvider &source, ResamplerStage **stages, unsigned int stageCount); +FloatSampleProvider &createResamplerModel(FloatSampleProvider &source, ResamplerStage &stage); + +void freeResamplerModel(FloatSampleProvider &model, FloatSampleProvider &source); + +} // namespace ResamplerModel + +} // namespace SRCTools + +#endif // RESAMPLER_MODEL_H diff --git a/audio/softsynth/mt32/srchelper/srctools/include/ResamplerStage.h b/audio/softsynth/mt32/srchelper/srctools/include/ResamplerStage.h new file mode 100644 index 00000000000..c0f0a0a50ab --- /dev/null +++ b/audio/softsynth/mt32/srchelper/srctools/include/ResamplerStage.h @@ -0,0 +1,38 @@ +/* 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 . + */ + +#ifndef RESAMPLER_STAGE_H +#define RESAMPLER_STAGE_H + +#include "FloatSampleProvider.h" + +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() {}; + + /** 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; + + /** Generates output samples. The arguments are adjusted in accordance with the number of samples processed. */ + virtual void process(const FloatSample *&inSamples, unsigned int &inLength, FloatSample *&outSamples, unsigned int &outLength) = 0; +}; + +} // namespace SRCTools + +#endif // RESAMPLER_STAGE_H diff --git a/audio/softsynth/mt32/srchelper/srctools/include/SincResampler.h b/audio/softsynth/mt32/srchelper/srctools/include/SincResampler.h new file mode 100644 index 00000000000..ea3f03b1122 --- /dev/null +++ b/audio/softsynth/mt32/srchelper/srctools/include/SincResampler.h @@ -0,0 +1,46 @@ +/* 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 . + */ + +#ifndef SINC_RESAMPLER_H +#define SINC_RESAMPLER_H + +#include "FIRResampler.h" + +namespace SRCTools { + +class ResamplerStage; + +namespace SincResampler { + + ResamplerStage *createSincResampler(const double inputFrequency, const double outputFrequency, const double passbandFrequency, const double stopbandFrequency, const double dbSNR, const unsigned int maxUpsampleFactor); + + namespace Utils { + void computeResampleFactors(unsigned int &upsampleFactor, double &downsampleFactor, const double inputFrequency, const double outputFrequency, const unsigned int maxUpsampleFactor); + unsigned int greatestCommonDivisor(unsigned int a, unsigned int b); + } + + namespace KaizerWindow { + double estimateBeta(double dbRipple); + unsigned int estimateOrder(double dbRipple, double fp, double fs); + double bessel(const double x); + void windowedSinc(FIRCoefficient kernel[], const unsigned int order, const double fc, const double beta, const double amp); + } + +} // namespace SincResampler + +} // namespace SRCTools + +#endif // SINC_RESAMPLER_H diff --git a/audio/softsynth/mt32/srchelper/srctools/src/FIRResampler.cpp b/audio/softsynth/mt32/srchelper/srctools/src/FIRResampler.cpp new file mode 100644 index 00000000000..2cded0c3d7c --- /dev/null +++ b/audio/softsynth/mt32/srchelper/srctools/src/FIRResampler.cpp @@ -0,0 +1,107 @@ +/* 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 . + */ + +#include +#include + +#include "FIRResampler.h" + +using namespace SRCTools; + +FIRResampler::Constants::Constants(const unsigned int upsampleFactor, const double downsampleFactor, const FIRCoefficient kernel[], const unsigned int kernelLength) { + usePhaseInterpolation = downsampleFactor != floor(downsampleFactor); + FIRCoefficient *kernelCopy = new FIRCoefficient[kernelLength]; + memcpy(kernelCopy, kernel, kernelLength * sizeof(FIRCoefficient)); + taps = kernelCopy; + numberOfTaps = kernelLength; + numberOfPhases = upsampleFactor; + phaseIncrement = downsampleFactor; + unsigned int minDelayLineLength = static_cast(ceil(double(kernelLength) / upsampleFactor)); + unsigned int delayLineLength = 2; + while (delayLineLength < minDelayLineLength) delayLineLength <<= 1; + delayLineMask = delayLineLength - 1; + ringBuffer = new FloatSample[delayLineLength][FIR_INTERPOLATOR_CHANNEL_COUNT]; + FloatSample *s = *ringBuffer; + FloatSample *e = ringBuffer[delayLineLength]; + while (s < e) *(s++) = 0; +} + +FIRResampler::FIRResampler(const unsigned int upsampleFactor, const double downsampleFactor, const FIRCoefficient kernel[], const unsigned int kernelLength) : + constants(upsampleFactor, downsampleFactor, kernel, kernelLength), + ringBufferPosition(0), + phase(constants.numberOfPhases) +{} + +FIRResampler::~FIRResampler() { + delete[] constants.ringBuffer; + delete[] constants.taps; +} + +void FIRResampler::process(const FloatSample *&inSamples, unsigned int &inLength, FloatSample *&outSamples, unsigned int &outLength) { + while (outLength > 0) { + while (needNextInSample()) { + if (inLength == 0) return; + addInSamples(inSamples); + --inLength; + } + getOutSamplesStereo(outSamples); + --outLength; + } +} + +unsigned int FIRResampler::estimateInLength(const unsigned int outLength) const { + return static_cast((outLength * constants.phaseIncrement + phase) / constants.numberOfPhases); +} + +bool FIRResampler::needNextInSample() const { + return constants.numberOfPhases <= phase; +} + +void FIRResampler::addInSamples(const FloatSample *&inSamples) { + ringBufferPosition = (ringBufferPosition - 1) & constants.delayLineMask; + for (unsigned int i = 0; i < FIR_INTERPOLATOR_CHANNEL_COUNT; i++) { + constants.ringBuffer[ringBufferPosition][i] = *(inSamples++); + } + phase -= constants.numberOfPhases; +} + +// Optimised for processing stereo interleaved streams +void FIRResampler::getOutSamplesStereo(FloatSample *&outSamples) { + FloatSample leftSample = 0.0; + FloatSample rightSample = 0.0; + unsigned int delaySampleIx = ringBufferPosition; + if (constants.usePhaseInterpolation) { + double phaseFraction = phase - floor(phase); + unsigned int maxTapIx = phaseFraction == 0 ? constants.numberOfTaps : constants.numberOfTaps - 1; + for (unsigned int tapIx = static_cast(phase); tapIx < maxTapIx; tapIx += constants.numberOfPhases) { + FIRCoefficient tap = FIRCoefficient(constants.taps[tapIx] + (constants.taps[tapIx + 1] - constants.taps[tapIx]) * phaseFraction); + leftSample += tap * constants.ringBuffer[delaySampleIx][0]; + rightSample += tap * constants.ringBuffer[delaySampleIx][1]; + delaySampleIx = (delaySampleIx + 1) & constants.delayLineMask; + } + } else { + // Optimised for rational resampling ratios when phase is always integer + for (unsigned int tapIx = static_cast(phase); tapIx < constants.numberOfTaps; tapIx += constants.numberOfPhases) { + FIRCoefficient tap = constants.taps[tapIx]; + leftSample += tap * constants.ringBuffer[delaySampleIx][0]; + rightSample += tap * constants.ringBuffer[delaySampleIx][1]; + delaySampleIx = (delaySampleIx + 1) & constants.delayLineMask; + } + } + *(outSamples++) = leftSample; + *(outSamples++) = rightSample; + phase += constants.phaseIncrement; +} diff --git a/audio/softsynth/mt32/srchelper/srctools/src/IIR2xResampler.cpp b/audio/softsynth/mt32/srchelper/srctools/src/IIR2xResampler.cpp new file mode 100644 index 00000000000..061006a1e73 --- /dev/null +++ b/audio/softsynth/mt32/srchelper/srctools/src/IIR2xResampler.cpp @@ -0,0 +1,229 @@ +/* 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 . + */ + +#include + +#include "IIR2xResampler.h" + +namespace SRCTools { + + // Avoid denormals degrading performance, using biased input + static const BufferedSample BIAS = 1e-35f; + + // Sharp elliptic filter with symmetric ripple: N=18, Ap=As=-106 dB, fp=0.238, fs = 0.25 (in terms of sample rate) + static const IIRCoefficient FIR_BEST = 0.0014313792470984f; + static const IIRSection SECTIONS_BEST[] = { + { 2.85800356692148000f,-0.2607342682253230f,-0.602478421807085f, 0.109823442522145f }, + { -4.39519408383016000f, 1.4651975326003500f,-0.533817668127954f, 0.226045921792036f }, + { 0.86638550740991800f,-2.1053851417898500f,-0.429134968401065f, 0.403512574222174f }, + { 1.67161485530774000f, 0.7963595880494520f,-0.324989203363446f, 0.580756666711889f }, + { -1.19962759276471000f, 0.5873595178851540f,-0.241486447489019f, 0.724264899930934f }, + { 0.01631779946479250f,-0.6282334739461620f,-0.182766025706656f, 0.827774001858882f }, + { 0.28404415859352400f, 0.1038619997715160f,-0.145276649558926f, 0.898510501923554f }, + { -0.08105788424234910f, 0.0781551578108934f,-0.123965846623366f, 0.947105257601873f }, + { -0.00872608905948005f,-0.0222098231712466f,-0.115056854360748f, 0.983542001125711f } + }; + + // Average elliptic filter with symmetric ripple: N=12, Ap=As=-106 dB, fp=0.193, fs = 0.25 (in terms of sample rate) + static const IIRCoefficient FIR_GOOD = 0.000891054570268146f; + static const IIRSection SECTIONS_GOOD[] = { + { 2.2650157226725700f,-0.4034180565140230f,-0.750061486095301f, 0.157801404511953f }, + { -3.2788261989161700f, 1.3952152147542600f,-0.705854270206788f, 0.265564985645774f }, + { 0.4397975114813240f,-1.3957634748753100f,-0.639718853965265f, 0.435324134360315f }, + { 0.9827040216680520f, 0.1837182774040940f,-0.578569965618418f, 0.615205557837542f }, + { -0.3759752818621670f, 0.3266073609399490f,-0.540913588637109f, 0.778264420176574f }, + { -0.0253548089519618f,-0.0925779221603846f,-0.537704370375240f, 0.925800083252964f } + }; + + // Fast elliptic filter with symmetric ripple: N=8, Ap=As=-99 dB, fp=0.125, fs = 0.25 (in terms of sample rate) + static const IIRCoefficient FIR_FAST = 0.000882837778745889f; + static const IIRSection SECTIONS_FAST[] = { + { 1.215377077431620f,-0.35864455030878000f,-0.972220718789242f, 0.252934735930620f }, + { -1.525654419254140f, 0.86784918631245500f,-0.977713689358124f, 0.376580616703668f }, + { 0.136094441564220f,-0.50414116798010400f,-1.007004471865290f, 0.584048854845331f }, + { 0.180604082285806f,-0.00467624342403851f,-1.093486919012100f, 0.844904524843996f } + }; + + static inline BufferedSample calcNumerator(const IIRSection §ion, const BufferedSample buffer1, const BufferedSample buffer2) { + return section.num1 * buffer1 + section.num2 * buffer2; + } + + static inline BufferedSample calcDenominator(const IIRSection §ion, const BufferedSample input, const BufferedSample buffer1, const BufferedSample buffer2) { + return input - section.den1 * buffer1 - section.den2 * buffer2; + } + +} // namespace SRCTools + +using namespace SRCTools; + +double IIRResampler::getPassbandFractionForQuality(Quality quality) { + switch (quality) { + case FAST: + return 0.5; + case GOOD: + return 0.7708; + case BEST: + return 0.9524; + default: + return 0; + } +} + +IIRResampler::Constants::Constants(const unsigned int useSectionsCount, const IIRCoefficient useFIR, const IIRSection useSections[], const Quality quality) { + if (quality == CUSTOM) { + sectionsCount = useSectionsCount; + fir = useFIR; + sections = useSections; + } else { + unsigned int sectionsSize; + switch (quality) { + case FAST: + fir = FIR_FAST; + sections = SECTIONS_FAST; + sectionsSize = sizeof(SECTIONS_FAST); + break; + case GOOD: + fir = FIR_GOOD; + sections = SECTIONS_GOOD; + sectionsSize = sizeof(SECTIONS_GOOD); + break; + case BEST: + fir = FIR_BEST; + sections = SECTIONS_BEST; + sectionsSize = sizeof(SECTIONS_BEST); + break; + default: + sectionsSize = 0; + break; + } + sectionsCount = (sectionsSize / sizeof(IIRSection)); + } + const unsigned int delayLineSize = IIR_RESAMPER_CHANNEL_COUNT * sectionsCount; + buffer = new SectionBuffer[delayLineSize]; + BufferedSample *s = buffer[0]; + BufferedSample *e = buffer[delayLineSize]; + while (s < e) *(s++) = 0; +} + +IIRResampler::IIRResampler(const Quality quality) : + constants(0, 0.0f, NULL, quality) +{} + +IIRResampler::IIRResampler(const unsigned int useSectionsCount, const IIRCoefficient useFIR, const IIRSection useSections[]) : + constants(useSectionsCount, useFIR, useSections, IIRResampler::CUSTOM) +{} + +IIRResampler::~IIRResampler() { + delete[] constants.buffer; +} + +IIR2xInterpolator::IIR2xInterpolator(const Quality quality) : + IIRResampler(quality), + phase() +{ + for (unsigned int chIx = 0; chIx < IIR_RESAMPER_CHANNEL_COUNT; ++chIx) { + lastInputSamples[chIx] = 0; + } +} + +IIR2xInterpolator::IIR2xInterpolator(const unsigned int useSectionsCount, const IIRCoefficient useFIR, const IIRSection useSections[]) : + IIRResampler(useSectionsCount, useFIR, useSections), + phase() +{ + for (unsigned int chIx = 0; chIx < IIR_RESAMPER_CHANNEL_COUNT; ++chIx) { + lastInputSamples[chIx] = 0; + } +} + +void IIR2xInterpolator::process(const FloatSample *&inSamples, unsigned int &inLength, FloatSample *&outSamples, unsigned int &outLength) { + static const IIRCoefficient INTERPOLATOR_AMP = 2.0; + + while (outLength > 0 && inLength > 0) { + SectionBuffer *bufferp = constants.buffer; + for (unsigned int chIx = 0; chIx < IIR_RESAMPER_CHANNEL_COUNT; ++chIx) { + const FloatSample lastInputSample = lastInputSamples[chIx]; + const FloatSample inSample = inSamples[chIx]; + BufferedSample tmpOut = phase == 0 ? 0 : inSample * constants.fir; + for (unsigned int i = 0; i < constants.sectionsCount; ++i) { + const IIRSection §ion = constants.sections[i]; + SectionBuffer &buffer = *bufferp; + // For 2x interpolation, calculation of the numerator reduces to a single multiplication depending on the phase. + if (phase == 0) { + const BufferedSample numOutSample = section.num1 * lastInputSample; + const BufferedSample denOutSample = calcDenominator(section, BIAS + numOutSample, buffer[0], buffer[1]); + buffer[1] = denOutSample; + tmpOut += denOutSample; + } else { + const BufferedSample numOutSample = section.num2 * lastInputSample; + const BufferedSample denOutSample = calcDenominator(section, BIAS + numOutSample, buffer[1], buffer[0]); + buffer[0] = denOutSample; + tmpOut += denOutSample; + } + bufferp++; + } + *(outSamples++) = FloatSample(INTERPOLATOR_AMP * tmpOut); + if (phase > 0) { + lastInputSamples[chIx] = inSample; + } + } + outLength--; + if (phase > 0) { + inSamples += IIR_RESAMPER_CHANNEL_COUNT; + inLength--; + phase = 0; + } else { + phase = 1; + } + } +} + +unsigned int IIR2xInterpolator::estimateInLength(const unsigned int outLength) const { + return outLength >> 1; +} + +IIR2xDecimator::IIR2xDecimator(const Quality quality) : + IIRResampler(quality) +{} + +IIR2xDecimator::IIR2xDecimator(const unsigned int useSectionsCount, const IIRCoefficient useFIR, const IIRSection useSections[]) : + IIRResampler(useSectionsCount, useFIR, useSections) +{} + +void IIR2xDecimator::process(const FloatSample *&inSamples, unsigned int &inLength, FloatSample *&outSamples, unsigned int &outLength) { + while (outLength > 0 && inLength > 1) { + SectionBuffer *bufferp = constants.buffer; + for (unsigned int chIx = 0; chIx < IIR_RESAMPER_CHANNEL_COUNT; ++chIx) { + BufferedSample tmpOut = inSamples[chIx] * constants.fir; + for (unsigned int i = 0; i < constants.sectionsCount; ++i) { + const IIRSection §ion = constants.sections[i]; + SectionBuffer &buffer = *bufferp; + // For 2x decimation, calculation of the numerator is not performed for odd output samples which are to be omitted. + tmpOut += calcNumerator(section, buffer[0], buffer[1]); + buffer[1] = calcDenominator(section, BIAS + inSamples[chIx], buffer[0], buffer[1]); + buffer[0] = calcDenominator(section, BIAS + inSamples[chIx + IIR_RESAMPER_CHANNEL_COUNT], buffer[1], buffer[0]); + bufferp++; + } + *(outSamples++) = FloatSample(tmpOut); + } + outLength--; + inLength -= 2; + inSamples += 2 * IIR_RESAMPER_CHANNEL_COUNT; + } +} + +unsigned int IIR2xDecimator::estimateInLength(const unsigned int outLength) const { + return outLength << 1; +} diff --git a/audio/softsynth/mt32/srchelper/srctools/src/LinearResampler.cpp b/audio/softsynth/mt32/srchelper/srctools/src/LinearResampler.cpp new file mode 100644 index 00000000000..98b9c77c73d --- /dev/null +++ b/audio/softsynth/mt32/srchelper/srctools/src/LinearResampler.cpp @@ -0,0 +1,47 @@ +/* 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 . + */ + +#include "LinearResampler.h" + +using namespace SRCTools; + +LinearResampler::LinearResampler(double sourceSampleRate, double targetSampleRate) : + inputToOutputRatio(sourceSampleRate / targetSampleRate), + position(1.0) // Preload delay line which effectively makes resampler zero phase +{} + +void LinearResampler::process(const FloatSample *&inSamples, unsigned int &inLength, FloatSample *&outSamples, unsigned int &outLength) { + if (inLength == 0) return; + while (outLength > 0) { + while (1.0 <= position) { + position--; + inLength--; + for (unsigned int chIx = 0; chIx < LINEAR_RESAMPER_CHANNEL_COUNT; ++chIx) { + lastInputSamples[chIx] = *(inSamples++); + } + if (inLength == 0) return; + } + for (unsigned int chIx = 0; chIx < LINEAR_RESAMPER_CHANNEL_COUNT; chIx++) { + *(outSamples++) = FloatSample(lastInputSamples[chIx] + position * (inSamples[chIx] - lastInputSamples[chIx])); + } + outLength--; + position += inputToOutputRatio; + } +} + +unsigned int LinearResampler::estimateInLength(const unsigned int outLength) const { + return static_cast(outLength * inputToOutputRatio); +} diff --git a/audio/softsynth/mt32/srchelper/srctools/src/ResamplerModel.cpp b/audio/softsynth/mt32/srchelper/srctools/src/ResamplerModel.cpp new file mode 100644 index 00000000000..4d2d9308372 --- /dev/null +++ b/audio/softsynth/mt32/srchelper/srctools/src/ResamplerModel.cpp @@ -0,0 +1,153 @@ +/* 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 . + */ + +#include +#include + +#include "ResamplerModel.h" + +#include "ResamplerStage.h" +#include "SincResampler.h" +#include "IIR2xResampler.h" +#include "LinearResampler.h" + +namespace SRCTools { + +namespace ResamplerModel { + +static const unsigned int CHANNEL_COUNT = 2; +static const unsigned int MAX_SAMPLES_PER_RUN = 4096; + +class CascadeStage : public FloatSampleProvider { +friend void freeResamplerModel(FloatSampleProvider &model, FloatSampleProvider &source); +public: + CascadeStage(FloatSampleProvider &source, ResamplerStage &resamplerStage); + + void getOutputSamples(FloatSample *outBuffer, unsigned int size); + +protected: + ResamplerStage &resamplerStage; + +private: + FloatSampleProvider &source; + FloatSample buffer[CHANNEL_COUNT * MAX_SAMPLES_PER_RUN]; + const FloatSample *bufferPtr; + unsigned int size; +}; + +class InternalResamplerCascadeStage : public CascadeStage { +public: + InternalResamplerCascadeStage(FloatSampleProvider &useSource, ResamplerStage &useResamplerStage) : + CascadeStage(useSource, useResamplerStage) + {} + + ~InternalResamplerCascadeStage() { + delete &resamplerStage; + } +}; + +} // namespace ResamplerModel + +} // namespace SRCTools + +using namespace SRCTools; + +FloatSampleProvider &ResamplerModel::createResamplerModel(FloatSampleProvider &source, double sourceSampleRate, double targetSampleRate, Quality quality) { + if (sourceSampleRate == targetSampleRate) { + return source; + } + if (quality == FASTEST) { + return *new InternalResamplerCascadeStage(source, *new LinearResampler(sourceSampleRate, targetSampleRate)); + } + const IIRResampler::Quality iirQuality = static_cast(quality); + const double iirPassbandFraction = IIRResampler::getPassbandFractionForQuality(iirQuality); + if (sourceSampleRate < targetSampleRate) { + ResamplerStage *iir2xInterpolator = new IIR2xInterpolator(iirQuality); + FloatSampleProvider &iir2xInterpolatorStage = *new InternalResamplerCascadeStage(source, *iir2xInterpolator); + + if (2.0 * sourceSampleRate == targetSampleRate) { + return iir2xInterpolatorStage; + } + + double passband = 0.5 * sourceSampleRate * iirPassbandFraction; + double stopband = 1.5 * sourceSampleRate; + ResamplerStage *sincResampler = SincResampler::createSincResampler(2.0 * sourceSampleRate, targetSampleRate, passband, stopband, DEFAULT_DB_SNR, DEFAULT_WINDOWED_SINC_MAX_UPSAMPLE_FACTOR); + return *new InternalResamplerCascadeStage(iir2xInterpolatorStage, *sincResampler); + } + + if (sourceSampleRate == 2.0 * targetSampleRate) { + ResamplerStage *iir2xDecimator = new IIR2xDecimator(iirQuality); + return *new InternalResamplerCascadeStage(source, *iir2xDecimator); + } + + double passband = 0.5 * targetSampleRate * iirPassbandFraction; + double stopband = 1.5 * targetSampleRate; + double sincOutSampleRate = 2.0 * targetSampleRate; + const unsigned int maxUpsampleFactor = static_cast(ceil(DEFAULT_WINDOWED_SINC_MAX_DOWNSAMPLE_FACTOR * sincOutSampleRate / sourceSampleRate)); + ResamplerStage *sincResampler = SincResampler::createSincResampler(sourceSampleRate, sincOutSampleRate, passband, stopband, DEFAULT_DB_SNR, maxUpsampleFactor); + FloatSampleProvider &sincResamplerStage = *new InternalResamplerCascadeStage(source, *sincResampler); + + ResamplerStage *iir2xDecimator = new IIR2xDecimator(iirQuality); + return *new InternalResamplerCascadeStage(sincResamplerStage, *iir2xDecimator); +} + +FloatSampleProvider &ResamplerModel::createResamplerModel(FloatSampleProvider &source, ResamplerStage **resamplerStages, unsigned int stageCount) { + FloatSampleProvider *prevStage = &source; + for (unsigned int i = 0; i < stageCount; i++) { + prevStage = new CascadeStage(*prevStage, *(resamplerStages[i])); + } + return *prevStage; +} + +FloatSampleProvider &ResamplerModel::createResamplerModel(FloatSampleProvider &source, ResamplerStage &stage) { + return *new CascadeStage(source, stage); +} + +void ResamplerModel::freeResamplerModel(FloatSampleProvider &model, FloatSampleProvider &source) { + FloatSampleProvider *currentStage = &model; + while (currentStage != &source) { + CascadeStage *cascadeStage = dynamic_cast(currentStage); + if (cascadeStage == NULL) return; + FloatSampleProvider &prevStage = cascadeStage->source; + delete currentStage; + currentStage = &prevStage; + } +} + +using namespace ResamplerModel; + +CascadeStage::CascadeStage(FloatSampleProvider &useSource, ResamplerStage &useResamplerStage) : + resamplerStage(useResamplerStage), + source(useSource), + bufferPtr(buffer), + size() +{} + +void CascadeStage::getOutputSamples(FloatSample *outBuffer, unsigned int length) { + while (length > 0) { + if (size == 0) { + size = resamplerStage.estimateInLength(length); + if (size < 1) { + size = 1; + } else if (MAX_SAMPLES_PER_RUN < size) { + size = MAX_SAMPLES_PER_RUN; + } + source.getOutputSamples(buffer, size); + bufferPtr = buffer; + } + resamplerStage.process(bufferPtr, size, outBuffer, length); + } +} diff --git a/audio/softsynth/mt32/srchelper/srctools/src/SincResampler.cpp b/audio/softsynth/mt32/srchelper/srctools/src/SincResampler.cpp new file mode 100644 index 00000000000..3ed028d2612 --- /dev/null +++ b/audio/softsynth/mt32/srchelper/srctools/src/SincResampler.cpp @@ -0,0 +1,136 @@ +/* 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 . + */ + +#include + +#ifdef SINC_RESAMPLER_DEBUG_LOG +#include +#endif + +#include "SincResampler.h" + +#ifndef M_PI +static const double M_PI = 3.1415926535897932; +#endif + +using namespace SRCTools; + +using namespace SincResampler; + +using namespace Utils; + +void Utils::computeResampleFactors(unsigned int &upsampleFactor, double &downsampleFactor, const double inputFrequency, const double outputFrequency, const unsigned int maxUpsampleFactor) { + static const double RATIONAL_RATIO_ACCURACY_FACTOR = 1E15; + + upsampleFactor = static_cast(outputFrequency); + unsigned int downsampleFactorInt = static_cast(inputFrequency); + if ((upsampleFactor == outputFrequency) && (downsampleFactorInt == inputFrequency)) { + // Input and output frequencies are integers, try to reduce them + const unsigned int gcd = greatestCommonDivisor(upsampleFactor, downsampleFactorInt); + if (gcd > 1) { + upsampleFactor /= gcd; + downsampleFactor = downsampleFactorInt / gcd; + } else { + downsampleFactor = downsampleFactorInt; + } + if (upsampleFactor <= maxUpsampleFactor) return; + } else { + // Try to recover rational resample ratio by brute force + double inputToOutputRatio = inputFrequency / outputFrequency; + for (unsigned int i = 1; i <= maxUpsampleFactor; ++i) { + double testFactor = inputToOutputRatio * i; + if (floor(RATIONAL_RATIO_ACCURACY_FACTOR * testFactor + 0.5) == RATIONAL_RATIO_ACCURACY_FACTOR * floor(testFactor + 0.5)) { + // inputToOutputRatio found to be rational within the accuracy + upsampleFactor = i; + downsampleFactor = floor(testFactor + 0.5); + return; + } + } + } + // Use interpolation of FIR taps as the last resort + upsampleFactor = maxUpsampleFactor; + downsampleFactor = maxUpsampleFactor * inputFrequency / outputFrequency; +} + +unsigned int Utils::greatestCommonDivisor(unsigned int a, unsigned int b) { + while (0 < b) { + unsigned int r = a % b; + a = b; + b = r; + } + return a; +} + +double KaizerWindow::estimateBeta(double dbRipple) { + return 0.1102 * (dbRipple - 8.7); +} + +unsigned int KaizerWindow::estimateOrder(double dbRipple, double fp, double fs) { + const double transBW = (fs - fp); + return static_cast(ceil((dbRipple - 8) / (2.285 * 2 * M_PI * transBW))); +} + +double KaizerWindow::bessel(const double x) { + static const double EPS = 1.11E-16; + + double sum = 0.0; + double f = 1.0; + for (unsigned int i = 1;; ++i) { + f *= (0.5 * x / i); + double f2 = f * f; + if (f2 <= sum * EPS) break; + sum += f2; + } + return 1.0 + sum; +} + +void KaizerWindow::windowedSinc(FIRCoefficient kernel[], const unsigned int order, const double fc, const double beta, const double amp) { + const double fc_pi = M_PI * fc; + const double recipOrder = 1.0 / order; + const double mult = 2.0 * fc * amp / bessel(beta); + for (int i = order, j = 0; 0 <= i; i -= 2, ++j) { + double xw = i * recipOrder; + double win = bessel(beta * sqrt(fabs(1.0 - xw * xw))); + double xs = i * fc_pi; + double sinc = (i == 0) ? 1.0 : sin(xs) / xs; + FIRCoefficient imp = FIRCoefficient(mult * sinc * win); + kernel[j] = imp; + kernel[order - j] = imp; + } +} + +ResamplerStage *SincResampler::createSincResampler(const double inputFrequency, const double outputFrequency, const double passbandFrequency, const double stopbandFrequency, const double dbSNR, const unsigned int maxUpsampleFactor) { + unsigned int upsampleFactor; + double downsampleFactor; + computeResampleFactors(upsampleFactor, downsampleFactor, inputFrequency, outputFrequency, maxUpsampleFactor); + double baseSamplePeriod = 1.0 / (inputFrequency * upsampleFactor); + double fp = passbandFrequency * baseSamplePeriod; + double fs = stopbandFrequency * baseSamplePeriod; + double fc = 0.5 * (fp + fs); + double beta = KaizerWindow::estimateBeta(dbSNR); + unsigned int order = KaizerWindow::estimateOrder(dbSNR, fp, fs); + const unsigned int kernelLength = order + 1; + +#ifdef SINC_RESAMPLER_DEBUG_LOG + std::clog << "FIR: " << upsampleFactor << "/" << downsampleFactor << ", N=" << kernelLength << ", NPh=" << kernelLength / double(upsampleFactor) << ", C=" << 0.5 / fc << ", fp=" << fp << ", fs=" << fs << ", M=" << maxUpsampleFactor << std::endl; +#endif + + FIRCoefficient *windowedSincKernel = new FIRCoefficient[kernelLength]; + KaizerWindow::windowedSinc(windowedSincKernel, order, fc, beta, upsampleFactor); + ResamplerStage *windowedSincStage = new FIRResampler(upsampleFactor, downsampleFactor, windowedSincKernel, kernelLength); + delete[] windowedSincKernel; + return windowedSincStage; +}