/* - Akai S1000 approximated windowed-sinc LUT generator - by 8bitbubsy, March 2026
**
** Akai S1000 has two ROMs with 4x signed 15-bit windowed-sinc kernels (6 points, 2048 phases)
** for resampling. As the kernels have 6 points/taps, I find it ulikely that Akai S1000 uses
** 8-point sinc (like a lot of people seem to state), and more likely that it uses 6-point.
**
** The sinc parameters have been found by bruteforcing towards the original sinc ROMs.
** They *could* be correct as the biggest deviation is 1/32768, and maybe the original
** algorithms Akai used had different precision or rounding? Who knows...
**
** If you find a way to generate bit-perfect kernels (like the last one), be sure to let me
** know!
**
** Note:
**  The fourth (last) kernel uses a Hann window, which is the same as Power-of-sine w/ p=2.0.
**  Hann can also be used for the other kernels (in 'raised' pow(w,p) form), but the error is
**  roughly the same, so I sticked with Power-of-sine for all of them.
*/

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdint.h>
#include <math.h>


// un-comment this if you want to dump the sinc kernels from ROM instead of generating them
//#define ROM_DUMP_MODE


// Akai S1000 values
#define SINC_TAPS 6
#define SINC_PHASES 2048
#define NUM_KERNELS 4

#ifndef ROM_DUMP_MODE

#define SINC_PHASES_BITS 11 /* log2(SINC_PHASES) */
#define PI 3.14159265358979323846264338327950288

typedef struct
{
	double power, cutoff, scale;
} sincKernel_t;

static sincKernel_t sincKernelConfig[NUM_KERNELS] =
{
	// parameters for sinc w/ Power-of-sine window

	// power, cutoff, scale
	{  2.75,  1.00,   0.94996044 }, // kernel #1 - highest deviation from ROM: 1 (acceptable)
	{  2.17,  0.77,   0.72571054 }, // kernel #2 - highest deviation from ROM: 1 (acceptable)
	{  1.82,  0.52,   0.49510344 }, // kernel #3 - highest deviation from ROM: 1 (acceptable)
	{  2.00,  0.0001, 0.31665040 }  // kernel #4 - highest deviation from ROM: 0 (perfect!)
};

static int16_t akaiS1000SincKernels[NUM_KERNELS][SINC_TAPS * SINC_PHASES];

const double sinc(double x, double cutoff)
{
	if (x == 0.0)
	{
		return cutoff;
	}
	else
	{
		x *= PI;
		return sin(x * cutoff) / x;
	}
}

int main(int argc, char *argv[])
{
// comment out this line if comparing integrity with ROM sinc dumps
#define REARRANGE_FOR_FASTER_LOGIC

	sincKernel_t *t = sincKernelConfig;
	for (int32_t i = 0; i < NUM_KERNELS; i++, t++)
	{
		for (int32_t j = 0; j < SINC_TAPS * SINC_PHASES; j++)
		{
			const double x = j * (1.0 / SINC_PHASES);
			
			const double window = pow(sin(x * (PI / SINC_TAPS)), t->power); // Power-of-sine window
			const double wsinc = ((sinc(x - (SINC_TAPS / 2), t->cutoff) * window) / t->cutoff) * t->scale;
			
			const int16_t out16 = (int16_t)round(32768.0 * wsinc);

#ifdef REARRANGE_FOR_FASTER_LOGIC
			const int32_t point = j >> SINC_PHASES_BITS;
			const int32_t phase = (SINC_PHASES-1) - (j & (SINC_PHASES-1));
			akaiS1000SincKernels[i][(phase * SINC_TAPS) + point] = out16;
		
			/*
			Now it can be used like this:
			(you have to make sure out-of-bounds sample data reads return clamped or zeroed values)
			
			// positionFrac = scaled to 0..2047 integer (SINC_PHASES)
			const int16_t *t = &akaiS1000SincKernels[numKernel][positionFrac * SINC_TAPS];
			const int16_t *s = &sampleData[positionInt];
			
			int32_t out = ((s[-2] * t[0]) +
			               (s[-1] * t[1]) +
			               ( s[0] * t[2]) +
			               ( s[1] * t[3]) +
			               ( s[2] * t[4]) +
			               ( s[3] * t[5])) >> 15; // slightly exceeds -32768..32767 because of overshoot
			
			I'm assuming 'numKernel' is picked based on the resampling ratio (samplePlaybackRate/audioOutputRate).
			Though the manual says that the interpolation varies based on the sample content (music, speech, etc),
			so maybe that's how the kernel selection is done.
			*/
#else
			akaiS1000SincKernels[i][j] = out16;
			// useful for plotting window or comparing to ROM
#endif
		}
	}
	
	// do something with akaiS1000SincKernels[0..3][x]

	return 0;
}
#else
// code for dumping windowed-sinc kernels from Akai S1000 IC11/IC12 ROMs
int main(int argc, char *argv[])
{
	FILE *lsb = NULL, *msb = NULL; // big-endian

	// don't ask me how to obtain these, but here's a hint: they do exist out there!
	lsb = fopen("S1000 Firmware LSB IC11.BIN", "rb"); if (lsb == NULL) goto error;
	msb = fopen("S1000 Firmware MSB IC12.BIN", "rb"); if (msb == NULL) goto error;

	for (int32_t i = 0; i < NUM_KERNELS; i++)
	{
		char filename[128];
		sprintf(filename, "Akai S1000 sinc kernel #%d (6 taps, 2048 phases).bin", 1 + i);
		FILE *f = fopen(filename, "wb"); if (f == NULL) goto error;

		const int32_t kernelOffset = 0x4000 * i;
		fseek(lsb, kernelOffset, SEEK_SET);
		fseek(msb, kernelOffset, SEEK_SET);

		for (int32_t i = 0; i < SINC_TAPS; i++)
		{
			for (int32_t j = 0; j < SINC_PHASES; j++)
			{
				// Phases are inverted, probably as a usage optimization. Re-invert so that the window plots naturally.
				const int32_t phaseOffset = kernelOffset + (i * SINC_PHASES) + ((SINC_PHASES-1) - j);
				fseek(lsb, phaseOffset, SEEK_SET);
				fseek(msb, phaseOffset, SEEK_SET);

				int16_t s16 = (fgetc(msb) << 8) | fgetc(lsb); // little-endian
				if (fwrite(&s16, 2, 1, f) != 1) goto error;
			}
		}

		fclose(f);
	}

	fclose(lsb);
	fclose(msb);

	printf("Dump OK.\n");
	return 0;

error:
	if (lsb != NULL) fclose(lsb);
	if (msb != NULL) fclose(msb);

	printf("General I/O error!\n");
	return 1;
}
#endif
