innovative™️ dual-LUT system. because it sounds better for some reason.

master
zetaPRIME 2021-11-10 22:46:52 -05:00
parent a78d41b134
commit 60df49db69
5 changed files with 35 additions and 19 deletions

21
notes
View File

@ -34,16 +34,15 @@ TODO {
immediate frontburner {
... resampling matches modplug-xmms with kaiser window, not modplug's polyphase
rename resamp function?
PLAYBACK BREAKS AFTER FIRST PLAY??? {
doesn't happen in Lovely Storm; only where Capaxitor is used??
resolves itself after project switch
does NOT break preview mode??
seems to be only with the Maybe sample??? idk
fiddling with things, figured out much better sounding cases {
rate<1.0, omit window
rate>1.0, omit window AND USE HALF RATIO SINC???
rate near 1.0, windowed is probably best
triple-lut this?? fade to windowed near 1.0, pick one of the other two for main
maybe only double, windowed with a=5.5 sounds good for rate<1.0
}
distortion effect
single-selection sampler
@ -60,6 +59,12 @@ TODO {
bugs to fix {
PLAYBACK BREAKS AFTER FIRST PLAY??? {
doesn't happen in Lovely Storm; only where Capaxitor is used??
resolves itself after project switch
does NOT break preview mode??
seems to be only with the Maybe sample??? idk
}
playback after stopping immediately after a note in the first pattern played sometimes skips that note
things can apparently be hooked up cyclically, which completely breaks the queue

View File

@ -17,12 +17,13 @@ using std::cyl_bessel_i;
namespace {
const constexpr double PI = 3.141592653589793238462643383279502884197169399375105820974;
const constexpr double KAISER_ALPHA = 7.5;
// 5.5 for downrate
const constexpr double KAISER_ALPHA = 5.5; //7.5;
const constexpr double KAISER_BETA = PI * KAISER_ALPHA;
inline constexpr double sinc(double x) {
if (x == 0) return 1;
double px = x * PI;
double px = x * PI/1;
return std::sin(px) / px;
}
}
@ -30,22 +31,31 @@ namespace {
// generate
const std::array<std::array<double, LUT_TAPS>, LUT_STEPS> Xybrid::NodeLib::resamplerLUT = [] {
const std::array<std::array<std::array<double, LUT_TAPS>, 2>, LUT_STEPS> Xybrid::NodeLib::resamplerLUT = [] {
double denom = cyl_bessel_i(0, KAISER_BETA);
std::array<std::array<double, LUT_TAPS>, LUT_STEPS> t;
std::array<std::array<std::array<double, LUT_TAPS>, 2>, LUT_STEPS> t;
//t[0] = {0, 0, 0, 1, 0, 0, 0, 0}; // we already know the ideal integer step
for (size_t step = 0; step < LUT_STEPS; step++) {
double sv = static_cast<double>(step) / LUT_STEPS; // subvalue (offset of tap position)
for (size_t tap = 0; tap < LUT_TAPS; tap++) {
double x = static_cast<double>(tap) - sv; // x position of tap;
double sx = x-LUT_HTAPS;
double kaiser = cyl_bessel_i(0, KAISER_BETA * std::sqrt(1.0 - std::pow( ( (2.0*(x+1))/(LUT_TAPS) ) - 1.0, 2 ) ) ) / denom; // original kaiser window generation
//double kaiser = cyl_bessel_i(0, KAISER_BETA * std::sqrt(1.0 - std::pow( (2.0*x)/(LUT_TAPS-2) - 1.0, 2 ) ) ) / denom; // by-the-book kaiser window of length LUT_TAPS-1
//double idl = (2.0*PI)/(LUT_TAPS-1);
//double kaiser = 0.40243 - 0.49804 * std::cos(idl * x) + 0.09831 * std::cos(2.0 * idl * x) - 0.00122 * std::cos(3.0 * idl * x); // approximate
t[step][tap] = sinc(x-(LUT_TAPS/2-1)) * std::max(kaiser, 0.0); // sinc function centered on main tap, offset by subvalue, multiplied by window
if (t[step][tap] != t[step][tap]) t[step][tap] = 0; // NaN guard
//double kaiser = 1.0; // omit windowing
//kaiser = std::max(kaiser, 0.0);
t[step][0][tap] = sinc(sx) * kaiser; // sinc function centered on main tap, offset by subvalue, multiplied by window
// uprate table
t[step][1][tap] = sinc(sx*0.5) * 0.5;// * kaiser; // for some reason this sounds cleaner (nearly identical to modplug polyphase)
// NaN guards
if (t[step][0][tap] != t[step][0][tap]) t[step][0][tap] = 0;
if (t[step][1][tap] != t[step][1][tap]) t[step][1][tap] = 0;
}
}
/*t[0] = {};

View File

@ -11,7 +11,7 @@ namespace Xybrid::NodeLib {
const constexpr size_t LUT_TAPS = 8;
const constexpr ptrdiff_t LUT_HTAPS = LUT_TAPS/2-1;//static_cast<ptrdiff_t>(LUT_TAPS - (LUT_TAPS+0.5)/2);
const constexpr size_t LUT_STEPS = 1024;
extern const std::array<std::array<double, LUT_TAPS>, LUT_STEPS> resamplerLUT;
extern const std::array<std::array<std::array<double, LUT_TAPS>, 2>, LUT_STEPS> resamplerLUT;
inline Data::AudioFrame resamp(Data::Sample* smp, double pos, double rate [[maybe_unused]]) {
auto loop = smp->loopStart >= 0;
@ -21,7 +21,7 @@ namespace Xybrid::NodeLib {
auto ll = le - ls;
double ip = std::floor(pos);
auto& pt = NodeLib::resamplerLUT[static_cast<size_t>((pos - ip)*NodeLib::LUT_STEPS) % NodeLib::LUT_STEPS];
auto& pt = NodeLib::resamplerLUT[static_cast<size_t>((pos - ip)*NodeLib::LUT_STEPS) % NodeLib::LUT_STEPS][rate > 1.0 ? 1 : 0];
Data::AudioFrame out(0.0);
@ -34,13 +34,12 @@ namespace Xybrid::NodeLib {
if (loop && ii > le) ii = (ii - ls) % ll + ls;
if (ii >= 0 && ii < len) out += (*smp)[static_cast<size_t>(ii)] * (1.0*(pos-ip));//*/
} else {
//*
auto ii = static_cast<ptrdiff_t>(ip) - LUT_HTAPS;
for (size_t i = 0; i < 8; i++) {
if (loop && ii >= le) ii = ((ii - ls) % ll) + ls;
if (ii >= 0 && ii < len) out += (*smp)[static_cast<size_t>(ii)] * pt[i];
ii++;
}//*/
}
}

View File

@ -108,7 +108,7 @@ void TestSynth::process() {
auto ii = static_cast<ptrdiff_t>(ip);
for (size_t i = 0; i < 8; i++) {
auto si = ii+static_cast<ptrdiff_t>(i);
if (si >= 0 && si < static_cast<ptrdiff_t>(smp->length())) out += (*smp)[static_cast<size_t>(si)] * pt[i];
//if (si >= 0 && si < static_cast<ptrdiff_t>(smp->length())) out += (*smp)[static_cast<size_t>(si)] * pt[i];
}
(*p)[s] = out;

View File

@ -30,6 +30,7 @@ using namespace Xybrid::UI;
#include "util/strings.h"
#include <cmath>
#include <iostream>
#include <QDebug>
#include <QCborMap>
@ -93,6 +94,7 @@ void BeatPad::init() {
if (!smp) return core.deleteNote(note);
double rate = static_cast<double>(smp->sampleRate) / static_cast<double>(audioEngine->curSampleRate());
//std::cout << "rate: " << rate << std::endl;
auto start = data.config->start;
if (start < 0) start = 0;
auto end = data.config->end;