Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package ft2-clone for openSUSE:Factory checked in at 2026-07-02 20:10:03 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/ft2-clone (Old) and /work/SRC/openSUSE:Factory/.ft2-clone.new.1982 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "ft2-clone" Thu Jul 2 20:10:03 2026 rev:44 rq:1363051 version:2.20 Changes: -------- --- /work/SRC/openSUSE:Factory/ft2-clone/ft2-clone.changes 2026-05-04 12:54:24.247669987 +0200 +++ /work/SRC/openSUSE:Factory/.ft2-clone.new.1982/ft2-clone.changes 2026-07-02 20:13:18.997416350 +0200 @@ -1,0 +2,16 @@ +Wed Jul 1 17:38:50 UTC 2026 - Martin Hauke <[email protected]> + +- Update to version 2.20 + * Sinc interpolation now uses up 74kB RAM instead of 2.4MB and + thus is less hard on the CPU cache. In some cases this means + even faster rendering. The way this change works is that the + polyphase look-up tables now use 256 phases/branches instead of + 8192, coupled with linear interpolation between the phases when + in use. This method is equal to having many more pre-computed + phases than 8192, which in theory also means better precision. + * Cubic spline interpolation now doesn't use a look-up table. + Again, this is less hard on the CPU cache. + * (Hopefully) fixed "Alt Gr" key not working on some systems. + * "About" screen rendering has been optimized (big whoop). + +------------------------------------------------------------------- Old: ---- ft2-clone-2.19.tar.gz New: ---- ft2-clone-2.20.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ ft2-clone.spec ++++++ --- /var/tmp/diff_new_pack.bIf9cm/_old 2026-07-02 20:13:19.505433937 +0200 +++ /var/tmp/diff_new_pack.bIf9cm/_new 2026-07-02 20:13:19.509434076 +0200 @@ -17,7 +17,7 @@ Name: ft2-clone -Version: 2.19 +Version: 2.20 Release: 0 Summary: Fasttracker II clone License: BSD-3-Clause AND CC-BY-NC-SA-4.0 ++++++ ft2-clone-2.19.tar.gz -> ft2-clone-2.20.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ft2-clone-2.19/src/ft2_about.c new/ft2-clone-2.20/src/ft2_about.c --- old/ft2-clone-2.19/src/ft2_about.c 2026-05-03 13:01:36.000000000 +0200 +++ new/ft2-clone-2.20/src/ft2_about.c 2026-07-01 16:33:52.000000000 +0200 @@ -11,21 +11,28 @@ #include "ft2_random.h" #define NUM_STARS 1500 -#define STARSHINE_ALPHA_PERCENTAGE 33 +#define STARSHINE_ALPHA_PERCENTAGE 31 #define ABOUT_SCREEN_X 3 #define ABOUT_SCREEN_Y 3 #define ABOUT_SCREEN_W 626 #define ABOUT_SCREEN_H 167 #define ABOUT_LOGO_W 541 #define ABOUT_LOGO_H 78 +#define BG_COLORKEY 0xFF000000 +#define STAR_PIXEL_ID 0xFE000000 typedef struct { - float x, y, z; + int16_t x, y, z; } vector_t; typedef struct { + float x, y, z; +} rotate_t; + +typedef struct +{ vector_t x, y, z; } matrix_t; @@ -35,10 +42,32 @@ static char customText3[128]; static int16_t customText0X, customText0Y, customText1X, customText1Y; static int16_t customText2X, customText2Y, customText3X, customText3Y; +static int32_t lastStarPos[NUM_STARS]; static const uint16_t starShineAlpha16 = (65535 * STARSHINE_ALPHA_PERCENTAGE) / 100; -static vector_t starPoints[NUM_STARS], starRotation; +static vector_t starPoints[NUM_STARS]; +static rotate_t starRotation; static matrix_t starMatrix; +static void clearPixel(uint32_t x, uint32_t y) +{ + if (x >= ABOUT_SCREEN_X+ABOUT_SCREEN_W || y >= ABOUT_SCREEN_Y+ABOUT_SCREEN_H) + return; + + const uint32_t pos = (y * SCREEN_W) + x; + if ((video.frameBuffer[pos] & 0xFF000000) == STAR_PIXEL_ID) + video.frameBuffer[pos] = BG_COLORKEY; +} + +static void putPixel(uint32_t x, uint32_t y, uint32_t color) +{ + if (x >= ABOUT_SCREEN_X+ABOUT_SCREEN_W || y >= ABOUT_SCREEN_Y+ABOUT_SCREEN_H) + return; + + const uint32_t pos = (y * SCREEN_W) + x; + if (video.frameBuffer[pos] == BG_COLORKEY) + video.frameBuffer[pos] = color | STAR_PIXEL_ID; +} + static void blendPixelsXY(uint32_t x, uint32_t y, uint32_t pixelB_r, uint32_t pixelB_g, uint32_t pixelB_b, uint16_t alpha) { uint32_t *p = &video.frameBuffer[(y * SCREEN_W) + x]; @@ -49,7 +78,8 @@ const int32_t g = ((RGB32_G(pixelA) * invAlpha) + (pixelB_g * alpha)) >> 16; const int32_t b = ((RGB32_B(pixelA) * invAlpha) + (pixelB_b * alpha)) >> 16; - *p = RGB32(r, g, b); + if (*p == BG_COLORKEY) + *p = RGB32(r, g, b) | STAR_PIXEL_ID; } static void starfield(void) @@ -57,25 +87,48 @@ vector_t *star = starPoints; for (int16_t i = 0; i < NUM_STARS; i++, star++) { - star->z += 0.0001f; - if (star->z >= 0.5f) - star->z -= 1.0f; + // erase previous star + if (lastStarPos[i] != -1) + { + const int32_t x = lastStarPos[i] >> 16, y = lastStarPos[i] & 0xFFFF; + lastStarPos[i] = -1; + + // center + clearPixel(x, y); + + // sides + clearPixel(x-1, y); + clearPixel(x+1, y); + clearPixel(x, y-1); + clearPixel(x, y+1); + } + + star->z += 3; + if (star->z > 16384) + star->z -= 32768; - const float z = (starMatrix.x.z * star->x) + (starMatrix.y.z * star->y) + (starMatrix.z.z * star->z) + 0.5f; - if (z <= 0.0f) + int32_t z = (((starMatrix.x.z * star->x) + (starMatrix.y.z * star->y) + (starMatrix.z.z * star->z)) >> 15) + 16384; + if (z <= 0) + continue; + + int32_t y = ((((starMatrix.x.y * star->x) + (starMatrix.y.y * star->y) + (starMatrix.z.y * star->z)) >> 15) * 400) / z; + y += ABOUT_SCREEN_H / 2; + if ((uint32_t)y >= ABOUT_SCREEN_H) continue; - float y = (((starMatrix.x.y * star->x) + (starMatrix.y.y * star->y) + (starMatrix.z.y * star->z)) / z) * 400.0f; - const int32_t outY = (ABOUT_SCREEN_Y+(ABOUT_SCREEN_H/2)) + (int32_t)y; - if (outY < ABOUT_SCREEN_Y || outY >= ABOUT_SCREEN_Y+ABOUT_SCREEN_H) + int32_t x = ((((starMatrix.x.x * star->x) + (starMatrix.y.x * star->y) + (starMatrix.z.x * star->z)) >> 15) * 400) / z; + x += ABOUT_SCREEN_W / 2; + if ((uint32_t)x >= ABOUT_SCREEN_W) continue; - float x = (((starMatrix.x.x * star->x) + (starMatrix.y.x * star->y) + (starMatrix.z.x * star->z)) / z) * 400.0f; - const int32_t outX = (ABOUT_SCREEN_X+(ABOUT_SCREEN_W/2)) + (int32_t)x; - if (outX < ABOUT_SCREEN_X || outX >= ABOUT_SCREEN_X+ABOUT_SCREEN_W) + const int32_t outX = ABOUT_SCREEN_X + x; + const int32_t outY = ABOUT_SCREEN_Y + y; + + // only draw star if it's not over logo and text + if (video.frameBuffer[(outY * SCREEN_W) + outX] != BG_COLORKEY) continue; - int32_t intensity255 = (int32_t)(z * 256.0f); + int32_t intensity255 = z >> 7; if (intensity255 > 255) intensity255 = 255; intensity255 ^= 255; @@ -94,6 +147,9 @@ if (b > 255) b = 255; + // plot center pixel + putPixel(outX, outY, RGB32(r, g, b)); + // plot and blend sides of star (basic shine effect) if (outX-1 >= ABOUT_SCREEN_X) @@ -108,69 +164,58 @@ if (outY+1 < ABOUT_SCREEN_Y+ABOUT_SCREEN_H) blendPixelsXY(outX, outY+1, r, g, b, starShineAlpha16); - // plot center pixel - video.frameBuffer[(outY * SCREEN_W) + outX] = RGB32(r, g, b); + lastStarPos[i] = (outX << 16) | outY; } } static void rotateStarfieldMatrix(void) { #define F_2PI (float)(2.0 * PI) +#define SCALE 32767.0f - const float xrad = starRotation.x * F_2PI; - const float xsin = sinf(xrad); - const float xcos = cosf(xrad); - - const float yrad = starRotation.y * F_2PI; - const float ysin = sinf(yrad); - const float ycos = cosf(yrad); - - const float zrad = starRotation.z * F_2PI; - const float zsin = sinf(zrad); - const float zcos = cosf(zrad); + const float rx2p = starRotation.x * F_2PI; + const float xsin = sinf(rx2p); + const float xcos = cosf(rx2p); + + const float ry2p = starRotation.y * F_2PI; + const float ysin = sinf(ry2p); + const float ycos = cosf(ry2p); + + const float rz2p = starRotation.z * F_2PI; + const float zsin = sinf(rz2p); + const float zcos = cosf(rz2p); // x - starMatrix.x.x = (xcos * zcos) + (zsin * xsin * ysin); - starMatrix.y.x = xsin * ycos; - starMatrix.z.x = (zcos * xsin * ysin) - (xcos * zsin); + starMatrix.x.x = (int16_t)(SCALE * ((xcos * zcos) + (zsin * xsin * ysin))); + starMatrix.y.x = (int16_t)(SCALE * (xsin * ycos)); + starMatrix.z.x = (int16_t)(SCALE * ((zcos * xsin * ysin) - (xcos * zsin))); // y - starMatrix.x.y = (zsin * xcos * ysin) - (xsin * zcos); - starMatrix.y.y = xcos * ycos; - starMatrix.z.y = (xsin * zsin) + (zcos * xcos * ysin); + starMatrix.x.y = (int16_t)(SCALE * ((zsin * xcos * ysin) - (xsin * zcos))); + starMatrix.y.y = (int16_t)(SCALE * (xcos * ycos)); + starMatrix.z.y = (int16_t)(SCALE * ((xsin * zsin) + (zcos * xcos * ysin))); // z - starMatrix.x.z = ycos * zsin; - starMatrix.y.z = 0.0f - ysin; - starMatrix.z.z = ycos * zcos; + starMatrix.x.z = (int16_t)(SCALE * (ycos * zsin)); + starMatrix.y.z = (int16_t)(SCALE * (0.0f - ysin)); + starMatrix.z.z = (int16_t)(SCALE * (ycos * zcos)); } void renderAboutScreenFrame(void) { - clearRect(ABOUT_SCREEN_X, ABOUT_SCREEN_Y, ABOUT_SCREEN_W, ABOUT_SCREEN_H); - - // 3D starfield - starfield(); starRotation.x -= 0.0003f; starRotation.y -= 0.0002f; starRotation.z += 0.0001f; rotateStarfieldMatrix(); - // FT2 logo - blit32((SCREEN_W - ABOUT_LOGO_W) / 2, 30, bmp.ft2AboutLogo, ABOUT_LOGO_W, ABOUT_LOGO_H); - - // render static texts - textOut(customText0X, customText0Y, PAL_FORGRND, customText0); - textOut(customText1X, customText1Y, PAL_FORGRND, customText1); - textOut(customText2X, customText2Y, PAL_FORGRND, customText2); - textOut(customText3X, customText3Y, PAL_FORGRND, customText3); - showPushButton(PB_EXIT_ABOUT); // yes, we also have to redraw the exit button per frame :) } void showAboutScreen(void) // called once when about screen is opened { + memset(lastStarPos, -1, NUM_STARS * sizeof (int32_t)); + if (ui.extendedPatternEditor) exitPatternEditorExtended(); @@ -179,6 +224,21 @@ drawFramework(0, 0, 632, 173, FRAMEWORK_TYPE1); drawFramework(2, 2, 628, 169, FRAMEWORK_TYPE2); + for (int32_t y = ABOUT_SCREEN_Y; y < ABOUT_SCREEN_Y+ABOUT_SCREEN_H; y++) + { + for (int32_t x = ABOUT_SCREEN_X; x < ABOUT_SCREEN_X+ABOUT_SCREEN_W; x++) + video.frameBuffer[(y * SCREEN_W) + x] = BG_COLORKEY; + } + + // FT2 logo + blit32((SCREEN_W - ABOUT_LOGO_W) / 2, 30, bmp.ft2AboutLogo, ABOUT_LOGO_W, ABOUT_LOGO_H); + + // render static texts + textOut(customText0X, customText0Y, PAL_FORGRND, customText0); + textOut(customText1X, customText1Y, PAL_FORGRND, customText1); + textOut(customText2X, customText2Y, PAL_FORGRND, customText2); + textOut(customText3X, customText3Y, PAL_FORGRND, customText3); + showPushButton(PB_EXIT_ABOUT); ui.aboutScreenShown = true; } @@ -188,9 +248,9 @@ vector_t *s = starPoints; for (int32_t i = 0; i < NUM_STARS; i++, s++) { - s->x = (float)((randoml(INT32_MAX) - (INT32_MAX/2)) * (1.0 / INT32_MAX)); - s->y = (float)((randoml(INT32_MAX) - (INT32_MAX/2)) * (1.0 / INT32_MAX)); - s->z = (float)((randoml(INT32_MAX) - (INT32_MAX/2)) * (1.0 / INT32_MAX)); + s->x = (int16_t)(randoml(32768) - 16384); + s->y = (int16_t)(randoml(32768) - 16384); + s->z = (int16_t)(randoml(32768) - 16384); } sprintf(customText3, "v%s (%s)", PROG_VER_STR, __DATE__); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ft2-clone-2.19/src/ft2_audio.c new/ft2-clone-2.20/src/ft2_audio.c --- old/ft2-clone-2.19/src/ft2_audio.c 2026-05-03 13:01:36.000000000 +0200 +++ new/ft2-clone-2.20/src/ft2_audio.c 2026-07-01 16:33:52.000000000 +0200 @@ -840,7 +840,7 @@ uint32_t samplesLeft = len; while (samplesLeft > 0) { - if (audio.tickSampleCounter == 0) // new replayer tick + if (audio.tickSampleCounter <= 0) // new replayer tick { replayerBusy = true; if (!musicPaused) // important, don't remove this check! (also used for safety) @@ -850,7 +850,9 @@ tickReplayer(); updateVoices(); - fillVisualsSyncBuffer(); + + if (audio.samplesPerTickInt != 0) + fillVisualsSyncBuffer(); } replayerBusy = false; @@ -864,13 +866,13 @@ } } - uint32_t samplesToMix = samplesLeft; - if (samplesToMix > audio.tickSampleCounter) + int32_t samplesToMix = samplesLeft; + if (audio.tickSampleCounter > 0 && samplesToMix > audio.tickSampleCounter) samplesToMix = audio.tickSampleCounter; doChannelMixing(bufferPosition, samplesToMix); bufferPosition += samplesToMix; - + audio.tickSampleCounter -= samplesToMix; samplesLeft -= samplesToMix; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ft2-clone-2.19/src/ft2_audio.h new/ft2-clone-2.20/src/ft2_audio.h --- old/ft2-clone-2.19/src/ft2_audio.h 2026-05-03 13:01:36.000000000 +0200 +++ new/ft2-clone-2.20/src/ft2_audio.h 2026-07-01 16:33:52.000000000 +0200 @@ -41,7 +41,8 @@ int32_t inputDeviceNum, outputDeviceNum, lastWorkingAudioFreq, lastWorkingAudioBits; uint32_t quickVolRampSamples, freq; - uint32_t tickSampleCounter, samplesPerTickInt, samplesPerTickIntTab[(MAX_BPM-MIN_BPM)+1]; + int32_t tickSampleCounter; + uint32_t samplesPerTickInt, samplesPerTickIntTab[(MAX_BPM-MIN_BPM)+1]; uint64_t tickSampleCounterFrac, samplesPerTickFrac, samplesPerTickFracTab[(MAX_BPM-MIN_BPM)+1]; uint32_t audLatencyPerfValInt, tickTimeIntTab[(MAX_BPM-MIN_BPM)+1]; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ft2-clone-2.19/src/ft2_header.h new/ft2-clone-2.20/src/ft2_header.h --- old/ft2-clone-2.19/src/ft2_header.h 2026-05-03 13:01:36.000000000 +0200 +++ new/ft2-clone-2.20/src/ft2_header.h 2026-07-01 16:33:52.000000000 +0200 @@ -14,7 +14,7 @@ #endif #include "ft2_replayer.h" -#define PROG_VER_STR "2.19" +#define PROG_VER_STR "2.20" // do NOT change these! It will only mess things up... diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ft2-clone-2.19/src/ft2_keyboard.c new/ft2-clone-2.20/src/ft2_keyboard.c --- old/ft2-clone-2.19/src/ft2_keyboard.c 2026-05-03 13:01:36.000000000 +0200 +++ new/ft2-clone-2.20/src/ft2_keyboard.c 2026-07-01 16:33:52.000000000 +0200 @@ -388,6 +388,7 @@ break; // play pattern + case SDLK_MODE: // Alt Gr is SDLK_MODE on some keyboards/layouts case SDLK_RALT: { if (!keyb.leftCtrlPressed) // kludge for Mac (toggle fullscreen) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ft2-clone-2.19/src/ft2_main.c new/ft2-clone-2.20/src/ft2_main.c --- old/ft2-clone-2.19/src/ft2_main.c 2026-05-03 13:01:36.000000000 +0200 +++ new/ft2-clone-2.20/src/ft2_main.c 2026-07-01 16:33:52.000000000 +0200 @@ -157,7 +157,7 @@ #ifdef __APPLE__ osxSetDirToProgramDirFromArgs(argv); #endif - if (!setupExecutablePath() || !loadBMPs() || !setupMixerInterpolationTables()) + if (!setupExecutablePath() || !loadBMPs() || !setupWindowedSincTables()) { cleanUpAndExit(); return 1; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ft2-clone-2.19/src/ft2_replayer.c new/ft2-clone-2.20/src/ft2_replayer.c --- old/ft2-clone-2.19/src/ft2_replayer.c 2026-05-03 13:01:36.000000000 +0200 +++ new/ft2-clone-2.20/src/ft2_replayer.c 2026-07-01 16:33:52.000000000 +0200 @@ -27,7 +27,7 @@ #include "ft2_sample_loader.h" #include "ft2_tables.h" #include "ft2_structs.h" -#include "mixer/ft2_mix_interpolation.h" +#include "mixer/ft2_windowed_sinc.h" static uint32_t logTab[4*12*16], frequencyMulFactor, frequencyDivFactor; static uint64_t songTickDuration52fp[(MAX_BPM-MIN_BPM)+1]; @@ -2900,7 +2900,7 @@ instr[131] = NULL; } - freeMixerInterpolationTables(); + freeWindowedSincTables(); } void calcMiscReplayerVars(void) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ft2-clone-2.19/src/ft2_replayer.h new/ft2-clone-2.20/src/ft2_replayer.h --- old/ft2-clone-2.19/src/ft2_replayer.h 2026-05-03 13:01:36.000000000 +0200 +++ new/ft2-clone-2.20/src/ft2_replayer.h 2026-07-01 16:33:52.000000000 +0200 @@ -3,7 +3,7 @@ #include <stdint.h> #include <stdbool.h> #include "ft2_unicode.h" -#include "mixer/ft2_mix_interpolation.h" +#include "mixer/ft2_windowed_sinc.h" enum { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ft2-clone-2.19/src/ft2_sample_ed.c new/ft2-clone-2.20/src/ft2_sample_ed.c --- old/ft2-clone-2.19/src/ft2_sample_ed.c 2026-05-03 13:01:36.000000000 +0200 +++ new/ft2-clone-2.20/src/ft2_sample_ed.c 2026-07-01 16:33:52.000000000 +0200 @@ -30,7 +30,6 @@ #include "ft2_random.h" #include "ft2_replayer.h" #include "ft2_smpfx.h" -#include "mixer/ft2_mix_interpolation.h" // SINC_TAPS, SINC_NEGATIVE_TAPS static const char sharpNote1Char[12] = { 'C', 'C', 'D', 'D', 'E', 'F', 'F', 'G', 'G', 'A', 'A', 'B' }; static const char sharpNote2Char[12] = { '-', '#', '-', '#', '-', '-', '#', '-', '#', '-', '#', '-' }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ft2-clone-2.19/src/mixer/ft2_mix_interpolation.c new/ft2-clone-2.20/src/mixer/ft2_mix_interpolation.c --- old/ft2-clone-2.19/src/mixer/ft2_mix_interpolation.c 2026-05-03 13:01:36.000000000 +0200 +++ new/ft2-clone-2.20/src/mixer/ft2_mix_interpolation.c 1970-01-01 01:00:00.000000000 +0100 @@ -1,182 +0,0 @@ -// mixer interpolation LUT generator - -#include <stdint.h> -#include <stdbool.h> -#include <stdlib.h> -#include <math.h> -#include "../ft2_header.h" -#include "../ft2_video.h" // showErrorMsgBox() -#include "ft2_mix_interpolation.h" - -typedef struct -{ - double kaiserBeta, sincCutoff; -} sincKernel_t; - -static sincKernel_t sincKernelConfig[SINC_KERNELS] = -{ - /* These parameters have been borrowed from the OpenMPT project - ** (with cutoff edit for kernel #1). - ** - ** It's quite difficult to tweak these numbers without causing either - ** ringing or aliasing in some cases. If anyone has good experience on - ** designing low-tap windowed-sinc kernels for a use case like this, and - ** knows of a good way to figure out what numbers to use here, PLEASE - ** contact me (contact details can be found at the bottom of 16-bits.org). - */ - - // beta, cutoff - { 9.6377, 1.000 }, // kernel #1 - { 8.5000, 0.500 }, // kernel #2 - { 7.0000, 0.425 } // kernel #3 -}; - -// globalized -float *fCubicSplineLUT, *fSinc[SINC_KERNELS], *fSinc8[SINC_KERNELS], *fSinc16[SINC_KERNELS]; -uint64_t sincRatio1, sincRatio2; -// ---------- - -static void calcPolyphaseSincLUT(float *fOut, int32_t numTaps, double kaiserBeta, double sincCutoff); - -bool setupMixerInterpolationTables(void) -{ - // cubic spline (4-point) - - fCubicSplineLUT = (float *)malloc(INTRP_PHASES * CUBIC_SPLINE_TAPS * sizeof (float)); - if (fCubicSplineLUT == NULL) - { - showErrorMsgBox("Not enough memory!"); - return false; - } - - float *fPtr = fCubicSplineLUT; - for (int32_t i = 0; i < INTRP_PHASES; i++) - { - const double x1 = i * (1.0 / INTRP_PHASES); - const double x2 = x1 * x1; // x^2 - const double x3 = x2 * x1; // x^3 - - // Catmull-Rom algorithm (has unity gain) - const double t1 = (x1 * -0.5) + (x2 * 1.0) + (x3 * -0.5); - const double t2 = (x2 * -2.5) + (x3 * 1.5) + 1.0; - const double t3 = (x1 * 0.5) + (x2 * 2.0) + (x3 * -1.5); - const double t4 = (x2 * -0.5) + (x3 * 0.5); - - *fPtr++ = (float)t1; // tap #1 at sample offset -1 - *fPtr++ = (float)t2; // tap #2 at sample offset 0 (center) - *fPtr++ = (float)t3; // tap #3 at sample offset 1 - *fPtr++ = (float)t4; // tap #4 at sample offset 2 - } - - // windowed-sinc (8-point/16-point) - - sincKernel_t *k = sincKernelConfig; - for (int32_t i = 0; i < SINC_KERNELS; i++, k++) - { - fSinc8[i] = (float *)malloc(INTRP_PHASES * SINC8_TAPS * sizeof (float)); - fSinc16[i] = (float *)malloc(INTRP_PHASES * SINC16_TAPS * sizeof (float)); - - if (fSinc8[i] == NULL || fSinc16[i] == NULL) - { - showErrorMsgBox("Not enough memory!"); - return false; - } - - calcPolyphaseSincLUT( fSinc8[i], SINC8_TAPS, k->kaiserBeta, k->sincCutoff); - calcPolyphaseSincLUT(fSinc16[i], SINC16_TAPS, k->kaiserBeta, k->sincCutoff); - } - - // resampling ratios for sinc kernel selection - sincRatio1 = (uint64_t)(1.1875 * MIXER_FRAC_SCALE); // fSinc[0] if <= - sincRatio2 = (uint64_t)(1.5000 * MIXER_FRAC_SCALE); // fSinc[1] if <=, else fSinc[2] if > - - return true; -} - -void freeMixerInterpolationTables(void) -{ - if (fCubicSplineLUT != NULL) - { - free(fCubicSplineLUT); - fCubicSplineLUT = NULL; - } - - for (int32_t i = 0; i < SINC_KERNELS; i++) - { - if (fSinc8[i] != NULL) - { - free(fSinc8[i]); - fSinc8[i] = NULL; - } - - if (fSinc16[i] != NULL) - { - free(fSinc16[i]); - fSinc16[i] = NULL; - } - } -} - -// zeroth-order modified Bessel function of the first kind (series approximation) -static inline double besselI0(double z) -{ - double s = 1.0, ds = 1.0, d = 2.0; - const double zz = z * z; - - do - { - ds *= zz / (d * d); - s += ds; - d += 2.0; - } - while (ds > s*(1E-12)); - - return s; -} - -static inline double sinc(double x, double cutoff) -{ - if (x == 0.0) - { - return cutoff; - } - else - { - x *= PI; - return sin(cutoff * x) / x; - } -} - -// note: numTaps must be even! -static void calcPolyphaseSincLUT(float *fOut, int32_t numTaps, double kaiserBeta, double sincCutoff) -{ - double tapBuffer[MAX_TAPS]; - const double besselI0BetaMul = 1.0 / besselI0(kaiserBeta); - const int32_t centerPoint = (numTaps / 2) - 1; - const double xMul = 1.0 / (numTaps / 2); - - for (int32_t i = 0; i < INTRP_PHASES; i++) - { - const double phase = i * (1.0 / INTRP_PHASES); - - double tapSum = 0.0; - for (int32_t j = 0; j < numTaps; j++) - { - const double x = (j - centerPoint) - phase; - - // Kaiser-Bessel window - const double n = x * xMul; - const double window = besselI0(kaiserBeta * sqrt(1.0 - n * n)) * besselI0BetaMul; - - const double wsinc = sinc(x, sincCutoff) * window; - tapBuffer[j] = wsinc; - - tapSum += wsinc; - } - - // normalize for unity gain before storing taps - const double tapMul = 1.0 / tapSum; - for (int32_t j = 0; j < numTaps; j++) - *fOut++ = (float)(tapBuffer[j] * tapMul); - } -} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ft2-clone-2.19/src/mixer/ft2_mix_interpolation.h new/ft2-clone-2.20/src/mixer/ft2_mix_interpolation.h --- old/ft2-clone-2.19/src/mixer/ft2_mix_interpolation.h 2026-05-03 13:01:36.000000000 +0200 +++ new/ft2-clone-2.20/src/mixer/ft2_mix_interpolation.h 1970-01-01 01:00:00.000000000 +0100 @@ -1,38 +0,0 @@ -#pragma once - -#include <stdint.h> -#include <stdbool.h> -#include "ft2_mix.h" // MIXER_FRAC_BITS - -#define SINC_KERNELS 3 -#define SINC8_TAPS 8 -#define SINC16_TAPS 16 -#define CUBIC_SPLINE_TAPS 4 - -// 8192 phases has a decent trade-off between low aliasing and cache usage -#define INTRP_PHASES 8192 - -// log2(INTRP_PHASES) -#define INTRP_PHASES_BITS 13 - -// log2(CUBIC_SPLINE_TAPS) -#define CUBIC_SPLINE_TAPS_BITS 2 - -// log2(SINC8_TAPS) -#define SINC8_TAPS_BITS 3 - -// log2(SINC16_TAPS) -#define SINC16_TAPS_BITS 4 - -#define CUBIC_SPLINE_FRACSHIFT (MIXER_FRAC_BITS-(INTRP_PHASES_BITS+CUBIC_SPLINE_TAPS_BITS)) -#define CUBIC_SPLINE_FRACMASK ((CUBIC_SPLINE_TAPS*INTRP_PHASES)-CUBIC_SPLINE_TAPS) -#define SINC8_FRACSHIFT (MIXER_FRAC_BITS-(INTRP_PHASES_BITS+SINC8_TAPS_BITS)) -#define SINC8_FRACMASK ((SINC8_TAPS*INTRP_PHASES)-SINC8_TAPS) -#define SINC16_FRACSHIFT (MIXER_FRAC_BITS-(INTRP_PHASES_BITS+SINC16_TAPS_BITS)) -#define SINC16_FRACMASK ((SINC16_TAPS*INTRP_PHASES)-SINC16_TAPS) - -extern float *fCubicSplineLUT, *fSinc[SINC_KERNELS], *fSinc8[SINC_KERNELS], *fSinc16[SINC_KERNELS]; -extern uint64_t sincRatio1, sincRatio2; - -bool setupMixerInterpolationTables(void); -void freeMixerInterpolationTables(void); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ft2-clone-2.19/src/mixer/ft2_mix_macros.h new/ft2-clone-2.20/src/mixer/ft2_mix_macros.h --- old/ft2-clone-2.19/src/mixer/ft2_mix_macros.h 2026-05-03 13:01:36.000000000 +0200 +++ new/ft2-clone-2.20/src/mixer/ft2_mix_macros.h 2026-07-01 16:33:52.000000000 +0200 @@ -130,13 +130,23 @@ /* CUBIC SPLINE INTERPOLATION */ /* ----------------------------------------------------------------------- */ +// Catmull-Rom algorithm #define CUBIC_SPLINE_INTERPOLATION(s, f, scale) \ { \ - const float *t = fCubicSplineLUT + (((uint32_t)(f) >> CUBIC_SPLINE_FRACSHIFT) & CUBIC_SPLINE_FRACMASK); \ - fSample = ((s[-1] * t[0]) + \ - ( s[0] * t[1]) + \ - ( s[1] * t[2]) + \ - ( s[2] * t[3])) * (1.0f / scale); \ + const int32_t frac31 = (uint32_t)f >> 1; /* reduce from uint32_t to int32_t for fast SIMD usage */ \ + const float x = frac31 * (1.0f / ((float)INT32_MAX+1.0f)); \ + \ + const float v0 = s[-1]; \ + const float v1 = s[0]; \ + const float v2 = s[1]; \ + const float v3 = s[2]; \ + \ + const float c1 = v1; \ + const float c2 = 0.5f * (v2 - v0); \ + const float c3 = v0 - (2.5f * v1) + (2.0f * v2) - (0.5f * v3); \ + const float c4 = 0.5f * (v3 - v0) + 1.5f * (v1 - v2); \ + \ + fSample = (((c4 * x + c3) * x + c2) * x + c1) * (1.0f / scale); \ } #define RENDER_8BIT_SMP_CINTRP \ @@ -173,36 +183,50 @@ #define WINDOWED_SINC8_INTERPOLATION(s, f, scale) \ { \ - const float *t = v->fSincLUT + (((uint32_t)(f) >> SINC8_FRACSHIFT) & SINC8_FRACMASK); \ - fSample = ((s[-3] * t[0]) + \ - (s[-2] * t[1]) + \ - (s[-1] * t[2]) + \ - ( s[0] * t[3]) + \ - ( s[1] * t[4]) + \ - ( s[2] * t[5]) + \ - ( s[3] * t[6]) + \ - ( s[4] * t[7])) * (1.0f / scale); \ + const uint32_t frac32 = (uint32_t)f; \ + const uint32_t lutPhase = frac32 >> INTRP_PHASE_SHIFT; \ + const float fIntrpFrac = (int32_t)(frac32 & INTRP_PHASE_MASK) * (1.0f / INTRP_PHASE_SCALE); \ + \ + /* it may look like we go out of bounds for fSinc_2, but we have an extra phase after LUT */ \ + const float *fSinc_1 = v->fSincLUT + ( lutPhase << SINC8_TAPS_BITS); \ + const float *fSinc_2 = v->fSincLUT + ((lutPhase+1) << SINC8_TAPS_BITS); \ + \ + float fSum = 0.0f; \ + for (int32_t j = 0; j < SINC8_TAPS; j++) \ + { \ + /* do linear interpolation between phases */ \ + const float y1 = fSinc_1[j]; \ + const float y2 = fSinc_2[j]; \ + \ + /* out of bounds for s[] is safe here and returns the correct samples */ \ + fSum += s[j-((SINC8_TAPS/2)-1)] * (y1 + ((y2 - y1) * fIntrpFrac)); \ + } \ + \ + fSample = fSum * (1.0f / scale); \ } #define WINDOWED_SINC16_INTERPOLATION(s, f, scale) \ { \ - const float *t = v->fSincLUT + (((uint32_t)(f) >> SINC16_FRACSHIFT) & SINC16_FRACMASK); \ - fSample = (( s[-7] * t[0]) + \ - ( s[-6] * t[1]) + \ - ( s[-5] * t[2]) + \ - ( s[-4] * t[3]) + \ - ( s[-3] * t[4]) + \ - ( s[-2] * t[5]) + \ - ( s[-1] * t[6]) + \ - ( s[0] * t[7]) + \ - ( s[1] * t[8]) + \ - ( s[2] * t[9]) + \ - ( s[3] * t[10]) + \ - ( s[4] * t[11]) + \ - ( s[5] * t[12]) + \ - ( s[6] * t[13]) + \ - ( s[7] * t[14]) + \ - ( s[8] * t[15])) * (1.0f / scale); \ + const uint32_t frac32 = (uint32_t)f; \ + const uint32_t lutPhase = frac32 >> INTRP_PHASE_SHIFT; \ + const float fIntrpFrac = (int32_t)(frac32 & INTRP_PHASE_MASK) * (1.0f / INTRP_PHASE_SCALE); \ + \ + /* it may look like we go out of bounds for fSinc_2, but we have an extra phase after LUT */ \ + const float *fSinc_1 = v->fSincLUT + ( lutPhase << SINC16_TAPS_BITS); \ + const float *fSinc_2 = v->fSincLUT + ((lutPhase+1) << SINC16_TAPS_BITS); \ + \ + float fSum = 0.0f; \ + for (int32_t j = 0; j < SINC16_TAPS; j++) \ + { \ + /* do linear interpolation between phases */ \ + const float y1 = fSinc_1[j]; \ + const float y2 = fSinc_2[j]; \ + \ + /* out of bounds for s[] is safe here and returns the correct samples */ \ + fSum += s[j-((SINC16_TAPS/2)-1)] * (y1 + ((y2 - y1) * fIntrpFrac)); \ + } \ + \ + fSample = fSum * (1.0f / scale); \ } #define RENDER_8BIT_SMP_S8INTRP \ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ft2-clone-2.19/src/mixer/ft2_windowed_sinc.c new/ft2-clone-2.20/src/mixer/ft2_windowed_sinc.c --- old/ft2-clone-2.19/src/mixer/ft2_windowed_sinc.c 1970-01-01 01:00:00.000000000 +0100 +++ new/ft2-clone-2.20/src/mixer/ft2_windowed_sinc.c 2026-07-01 16:33:52.000000000 +0200 @@ -0,0 +1,172 @@ +// polyphase windowed-sinc LUT generator + +#include <stdint.h> +#include <stdbool.h> +#include <stdlib.h> +#include <math.h> +#include "../ft2_header.h" +#include "../ft2_video.h" // showErrorMsgBox() +#include "ft2_windowed_sinc.h" + +typedef struct +{ + double kaiserBeta, sincCutoff; +} sincKernel_t; + +static sincKernel_t sincKernelConfig[SINC_KERNELS] = +{ + /* These parameters have been borrowed from the OpenMPT project + ** (with cutoff edit for kernel #1). + ** + ** It's quite difficult to tweak these numbers without causing either + ** ringing or aliasing in some cases. If anyone has good experience on + ** designing low-tap windowed-sinc kernels for a use case like this, and + ** knows of a good way to figure out what numbers to use here, PLEASE + ** contact me (contact details can be found at the bottom of 16-bits.org). + */ + + // beta, cutoff + { 9.6377, 1.000 }, // kernel #1 + { 8.5000, 0.500 }, // kernel #2 + { 7.0000, 0.425 } // kernel #3 +}; + +// globalized +float *fSinc[SINC_KERNELS], *fSinc8[SINC_KERNELS], *fSinc16[SINC_KERNELS]; +uint64_t sincRatio1, sincRatio2; +// ---------- + +static bool calcPolyphaseSincLUT(float *fOut, int32_t numTaps, int32_t numPhases, double kaiserBeta, double sincCutoff); + +bool setupWindowedSincTables(void) +{ + sincKernel_t *k = sincKernelConfig; + for (int32_t i = 0; i < SINC_KERNELS; i++, k++) + { + fSinc8[i] = (float *)malloc((SINC_OVERSAMPLING+1) * SINC8_TAPS * sizeof (float)); + fSinc16[i] = (float *)malloc((SINC_OVERSAMPLING+1) * SINC16_TAPS * sizeof (float)); + + if (fSinc8[i] == NULL || fSinc16[i] == NULL) + { + showErrorMsgBox("Not enough memory!"); + return false; + } + + if (!calcPolyphaseSincLUT(fSinc8[i], SINC8_TAPS, SINC_OVERSAMPLING, k->kaiserBeta, k->sincCutoff)) + { + showErrorMsgBox("Not enough memory!"); + return false; + } + + if (!calcPolyphaseSincLUT(fSinc16[i], SINC16_TAPS, SINC_OVERSAMPLING, k->kaiserBeta, k->sincCutoff)) + { + showErrorMsgBox("Not enough memory!"); + return false; + } + } + + // fixed-point resampling ratios for sinc kernel selection (to get a gradual cut-off curve) + sincRatio1 = (uint64_t)(1.1875 * MIXER_FRAC_SCALE); // fSinc[0] if <= + sincRatio2 = (uint64_t)(1.5000 * MIXER_FRAC_SCALE); // fSinc[1] if <=, else fSinc[2] if > + + return true; +} + +void freeWindowedSincTables(void) +{ + for (int32_t i = 0; i < SINC_KERNELS; i++) + { + if (fSinc8[i] != NULL) + { + free(fSinc8[i]); + fSinc8[i] = NULL; + } + + if (fSinc16[i] != NULL) + { + free(fSinc16[i]); + fSinc16[i] = NULL; + } + } +} + +// zeroth-order modified Bessel function of the first kind (series approximation) +static double besselI0(double z) +{ + double s = 1.0, ds = 1.0, d = 2.0; + const double zz = z * z; + + do + { + ds *= zz / (d * d); + s += ds; + d += 2.0; + } + while (ds > s*(1E-12)); + + return s; +} + +static double sinc(double x, double cutoff) +{ + if (x == 0.0) + { + return cutoff; + } + else + { + x *= PI; + return sin(cutoff * x) / x; + } +} + +/* Polyphase sinc LUT generator w/ Kaiser-Bessel window. +** +** Note #1: The 'sincCutoff' parameter ranges from 0.0 to 1.0, where 1.0 is no cutoff. +** Note #2: The 'numTaps' parameter must be an even number. +*/ +static bool calcPolyphaseSincLUT(float *fOut, int32_t numTaps, int32_t numPhases, double kaiserBeta, double sincCutoff) +{ + double *tapBuffer = (double *)malloc(numTaps * sizeof (double)); + if (tapBuffer == NULL) + return false; + + const double besselI0BetaMul = 1.0 / besselI0(kaiserBeta); + const int32_t centerPoint = (numTaps / 2) - 1; + const double phaseMul = 1.0 / numPhases; + const double kaiserXMul = 1.0 / (numTaps / 2); + + float *fOutPtr = fOut; + for (int32_t i = 0; i < numPhases; i++) + { + const double phase = i * phaseMul; + + double tapSum = 0.0; + for (int32_t j = 0; j < numTaps; j++) + { + const double x = (j - centerPoint) - phase; + + // Kaiser-Bessel window + const double n = x * kaiserXMul; + const double window = besselI0(kaiserBeta * sqrt(1.0 - n * n)) * besselI0BetaMul; + + const double wsinc = sinc(x, sincCutoff) * window; + tapBuffer[j] = wsinc; + + tapSum += wsinc; + } + + // normalize for unity gain before storing taps + const double tapMul = 1.0 / tapSum; + for (int32_t j = 0; j < numTaps; j++) + *fOutPtr++ = (float)(tapBuffer[j] * tapMul); + } + + // store inverted wrap-around taps after end of LUT (for phase interpolation) + float *fEnd = &fOut[numTaps * numPhases]; + for (int32_t i = 0; i < numTaps; i++) + fEnd[i] = fOut[(numTaps-1) - i]; + + free(tapBuffer); + return true; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ft2-clone-2.19/src/mixer/ft2_windowed_sinc.h new/ft2-clone-2.20/src/mixer/ft2_windowed_sinc.h --- old/ft2-clone-2.19/src/mixer/ft2_windowed_sinc.h 1970-01-01 01:00:00.000000000 +0100 +++ new/ft2-clone-2.20/src/mixer/ft2_windowed_sinc.h 2026-07-01 16:33:52.000000000 +0200 @@ -0,0 +1,31 @@ +#pragma once + +#include <stdint.h> +#include <stdbool.h> +#include "ft2_mix.h" // MIXER_FRAC_BITS + +#define SINC_KERNELS 3 +#define SINC8_TAPS 8 +#define SINC16_TAPS 16 + +// 256 phases + linear interpolation = near perfect (and low CPU cache usage) +#define SINC_OVERSAMPLING 256 + +// log2(SINC_OVERSAMPLING) +#define SINC_OVERSAMPLING_BITS 8 + +// log2(SINC8_TAPS) +#define SINC8_TAPS_BITS 3 + +// log2(SINC16_TAPS) +#define SINC16_TAPS_BITS 4 + +#define INTRP_PHASE_SHIFT (MIXER_FRAC_BITS-SINC_OVERSAMPLING_BITS) +#define INTRP_PHASE_SCALE (1L << INTRP_PHASE_SHIFT) +#define INTRP_PHASE_MASK (INTRP_PHASE_SCALE-1) + +extern float *fSinc[SINC_KERNELS], *fSinc8[SINC_KERNELS], *fSinc16[SINC_KERNELS]; +extern uint64_t sincRatio1, sincRatio2; + +bool setupWindowedSincTables(void); +void freeWindowedSincTables(void); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ft2-clone-2.19/vs2019_project/ft2-clone/ft2-clone.vcxproj new/ft2-clone-2.20/vs2019_project/ft2-clone/ft2-clone.vcxproj --- old/ft2-clone-2.19/vs2019_project/ft2-clone/ft2-clone.vcxproj 2026-05-03 13:01:36.000000000 +0200 +++ new/ft2-clone-2.20/vs2019_project/ft2-clone/ft2-clone.vcxproj 2026-07-01 16:33:52.000000000 +0200 @@ -345,7 +345,7 @@ <ClCompile Include="..\..\src\gfxdata\ft2_bmp_instr.c" /> <ClCompile Include="..\..\src\gfxdata\ft2_bmp_looppins.c" /> <ClCompile Include="..\..\src\gfxdata\ft2_bmp_scopes.c" /> - <ClCompile Include="..\..\src\mixer\ft2_mix_interpolation.c" /> + <ClCompile Include="..\..\src\mixer\ft2_windowed_sinc.c" /> <ClCompile Include="..\..\src\mixer\ft2_mix.c" /> <ClCompile Include="..\..\src\mixer\ft2_silence_mix.c" /> <ClCompile Include="..\..\src\modloaders\ft2_load_digi.c" /> @@ -423,7 +423,7 @@ <ClInclude Include="..\..\src\ft2_unicode.h" /> <ClInclude Include="..\..\src\ft2_video.h" /> <ClInclude Include="..\..\src\ft2_wav_renderer.h" /> - <ClInclude Include="..\..\src\mixer\ft2_mix_interpolation.h" /> + <ClInclude Include="..\..\src\mixer\ft2_windowed_sinc.h" /> <ClInclude Include="..\..\src\mixer\ft2_mix.h" /> <ClInclude Include="..\..\src\mixer\ft2_mix_macros.h" /> <ClInclude Include="..\..\src\mixer\ft2_silence_mix.h" /> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ft2-clone-2.19/vs2019_project/ft2-clone/ft2-clone.vcxproj.filters new/ft2-clone-2.20/vs2019_project/ft2-clone/ft2-clone.vcxproj.filters --- old/ft2-clone-2.19/vs2019_project/ft2-clone/ft2-clone.vcxproj.filters 2026-05-03 13:01:36.000000000 +0200 +++ new/ft2-clone-2.20/vs2019_project/ft2-clone/ft2-clone.vcxproj.filters 2026-07-01 16:33:52.000000000 +0200 @@ -130,7 +130,7 @@ </ClCompile> <ClCompile Include="..\..\src\ft2_random.c" /> <ClCompile Include="..\..\src\ft2_smpfx.c" /> - <ClCompile Include="..\..\src\mixer\ft2_mix_interpolation.c"> + <ClCompile Include="..\..\src\mixer\ft2_windowed_sinc.c"> <Filter>mixer</Filter> </ClCompile> <ClCompile Include="..\..\src\smploaders\ft2_load_mp3.c"> @@ -294,7 +294,7 @@ <ClInclude Include="..\..\src\ft2_smpfx.h"> <Filter>headers</Filter> </ClInclude> - <ClInclude Include="..\..\src\mixer\ft2_mix_interpolation.h"> + <ClInclude Include="..\..\src\mixer\ft2_windowed_sinc.h"> <Filter>mixer</Filter> </ClInclude> <ClInclude Include="..\..\src\smploaders\minimp3.h">
