* Replaced Lanczos window with Hann window for the windowed sinc function in the NCSF plugin.
* Added a ring buffer specifically designed for working with SWAVs to replace the one provided by kode54, still have to give him thanks for the original though.
* Fixed clipping issue caused by invalid clamping values.
* Minor fix to using the min()/max() functions of numeric_limits because Winamp's out.h includes windows.h without my specific wrapper to redefine things.
--- a/src/in_ncsf/SSEQPlayer/Channel.cpp
+++ b/src/in_ncsf/SSEQPlayer/Channel.cpp
@@ -1,7 +1,7 @@
/*
* SSEQ Player - Channel structures
* By Naram Qashat (CyberBotX) [cyberbotx@cyberbotx.com]
- * Last modification on 2013-04-26
+ * Last modification on 2013-05-07
*
* Adapted from source code of FeOS Sound System
* By fincs
@@ -44,7 +44,7 @@
bool Channel::initializedLUTs = false;
double Channel::cosine_lut[Channel::COSINE_RESOLUTION];
-double Channel::lanczos_lut[Channel::LANCZOS_SAMPLES + 1];
+double Channel::sinc_lut[Channel::SINC_SAMPLES + 1];
#ifndef M_PI
static const double M_PI = 3.14159265358979323846;
@@ -57,16 +57,16 @@
Channel::Channel() : chnId(-1), tempReg(), state(CS_NONE), trackId(-1), prio(0), manualSweep(false), flags(), pan(0), extAmpl(0), velocity(0), extPan(0),
key(0), ampl(0), extTune(0), orgKey(0), modType(0), modSpeed(0), modDepth(0), modRange(0), modDelay(0), modDelayCnt(0), modCounter(0),
- sweepLen(0), sweepCnt(0), sweepPitch(0), attackLvl(0), sustainLvl(0x7F), decayRate(0), releaseRate(0xFFFF), noteLength(-1), vol(0), ply(nullptr), reg()
-{
- this->clearHistory();
+ sweepLen(0), sweepCnt(0), sweepPitch(0), attackLvl(0), sustainLvl(0x7F), decayRate(0), releaseRate(0xFFFF), noteLength(-1), vol(0), ply(nullptr), reg(),
+ ringBuffer()
+{
if (!this->initializedLUTs)
{
for (unsigned i = 0; i < COSINE_RESOLUTION; ++i)
this->cosine_lut[i] = (1.0 - std::cos((static_cast<double>(i) / COSINE_RESOLUTION) * M_PI)) * 0.5;
- double dx = static_cast<double>(LANCZOS_WIDTH) / LANCZOS_SAMPLES, x = 0.0;
- for (unsigned i = 0; i <= LANCZOS_SAMPLES; ++i, x += dx)
- this->lanczos_lut[i] = std::abs(x) < LANCZOS_WIDTH ? sinc(x) * sinc(x / LANCZOS_WIDTH) : 0.0;
+ double dx = static_cast<double>(SINC_WIDTH) / SINC_SAMPLES, x = 0.0;
+ for (unsigned i = 0; i <= SINC_SAMPLES; ++i, x += dx)
+ this->sinc_lut[i] = std::abs(x) < SINC_WIDTH ? sinc(x) * (0.5 * (1.0 + std::cos((M_PI * x) / SINC_WIDTH))) : 0.0;
this->initializedLUTs = true;
}
}
@@ -144,7 +144,6 @@
this->reg.ClearControlRegister();
this->vol = 0;
this->noteLength = -1;
- this->clearHistory();
}
static inline int getModFlag(int type)
@@ -604,22 +603,22 @@
double ratio = this->reg.samplePosition;
ratio -= static_cast<int32_t>(ratio);
- const auto &data = &this->sampleHistory[this->sampleHistoryPtr + 16];
-
- if (this->ply->interpolation == INTERPOLATION_LANCZOS)
- {
- double kernel[LANCZOS_WIDTH * 2], kernel_sum = 0.0;
- int i = LANCZOS_WIDTH, shift = static_cast<int>(std::floor(ratio * LANCZOS_RESOLUTION));
- int step = this->reg.sampleIncrease > 1.0 ? static_cast<int>(LANCZOS_RESOLUTION / this->reg.sampleIncrease) : LANCZOS_RESOLUTION;
- int shift_adj = shift * step / LANCZOS_RESOLUTION;
- for (; i >= -static_cast<int>(LANCZOS_WIDTH - 1); --i)
+ const auto &data = this->ringBuffer.GetBuffer();
+
+ if (this->ply->interpolation == INTERPOLATION_SINC)
+ {
+ double kernel[SINC_WIDTH * 2], kernel_sum = 0.0;
+ int i = SINC_WIDTH, shift = static_cast<int>(std::floor(ratio * SINC_RESOLUTION));
+ int step = this->reg.sampleIncrease > 1.0 ? static_cast<int>(SINC_RESOLUTION / this->reg.sampleIncrease) : SINC_RESOLUTION;
+ int shift_adj = shift * step / SINC_RESOLUTION;
+ for (; i >= -static_cast<int>(SINC_WIDTH - 1); --i)
{
int pos = i * step;
- kernel_sum += kernel[i + LANCZOS_WIDTH - 1] = this->lanczos_lut[std::abs(shift_adj - pos)];
+ kernel_sum += kernel[i + SINC_WIDTH - 1] = this->sinc_lut[std::abs(shift_adj - pos)];
}
double sum = 0.0;
- for (i = 0; i < static_cast<int>(LANCZOS_WIDTH * 2); ++i)
- sum += data[i - static_cast<int>(LANCZOS_WIDTH) + 1] * kernel[i];
+ for (i = 0; i < static_cast<int>(SINC_WIDTH * 2); ++i)
+ sum += data[i - static_cast<int>(SINC_WIDTH) + 1] * kernel[i];
return static_cast<int32_t>(sum / kernel_sum);
}
else if (this->ply->interpolation > INTERPOLATION_COSINE)
@@ -721,22 +720,59 @@
{
double samplePosition = this->reg.samplePosition + this->reg.sampleIncrease;
- if (this->reg.format != 3 && this->reg.samplePosition >= 0)
- {
- uint32_t loc = static_cast<uint32_t>(this->reg.samplePosition);
- uint32_t newloc = static_cast<uint32_t>(samplePosition);
-
- if (newloc >= this->reg.totalLength)
- newloc -= this->reg.length;
-
- while (loc != newloc)
- {
- this->sampleHistory[this->sampleHistoryPtr] = this->sampleHistory[this->sampleHistoryPtr + 32] = this->reg.source->dataptr[loc++];
-
- this->sampleHistoryPtr = (this->sampleHistoryPtr + 1) & 31;
-
- if (loc >= this->reg.totalLength)
- loc -= this->reg.length;
+ if (this->reg.format != 3)
+ {
+ if (this->reg.samplePosition < 0 && samplePosition >= 0)
+ {
+ this->ringBuffer.Clear();
+ this->ringBuffer.bufferPos += SINC_WIDTH + 1;
+ auto preData = std::vector<int16_t>(SINC_WIDTH + 1, this->reg.source->dataptr[0]);
+ this->ringBuffer.PushSamples(&preData[0], SINC_WIDTH + 1);
+ if (this->reg.totalLength < SINC_WIDTH + 1)
+ {
+ this->ringBuffer.PushSamples(&this->reg.source->dataptr[0], this->reg.totalLength);
+ if (this->reg.repeatMode == 1)
+ {
+ size_t samplesLeft = SINC_WIDTH + 1 - this->reg.totalLength;
+ while (samplesLeft)
+ {
+ size_t samplesToPush = std::min(samplesLeft, this->reg.length);
+ this->ringBuffer.PushSamples(&this->reg.source->dataptr[this->reg.loopStart], samplesToPush);
+ samplesLeft -= samplesToPush;
+ }
+ }
+ }
+ else
+ this->ringBuffer.PushSamples(&this->reg.source->dataptr[0], SINC_WIDTH + 1);
+ }
+ if (this->reg.samplePosition >= 0)
+ {
+ uint32_t loc = static_cast<uint32_t>(this->reg.samplePosition) + SINC_WIDTH + 1;
+ uint32_t newloc = static_cast<uint32_t>(samplePosition) + SINC_WIDTH + 1;
+
+ if (this->reg.repeatMode == 1)
+ {
+ if (loc >= this->reg.totalLength)
+ loc -= this->reg.length;
+ if (newloc >= this->reg.totalLength)
+ newloc -= this->reg.length;
+ }
+
+ while (loc != newloc)
+ {
+ this->ringBuffer.NextSample();
+
+ if (loc < this->reg.totalLength)
+ this->ringBuffer.PushSample(this->reg.source->dataptr[loc++]);
+ else
+ {
+ ++loc;
+ this->ringBuffer.PushSample(this->reg.source->dataptr[this->reg.totalLength - 1]);
+ }
+
+ if (this->reg.repeatMode == 1 && loc >= this->reg.totalLength)
+ loc -= this->reg.length;
+ }
}
}
@@ -754,9 +790,3 @@
}
}
-void Channel::clearHistory()
-{
- this->sampleHistoryPtr = 0;
- memset(this->sampleHistory, 0, sizeof(this->sampleHistory));
-}
-
--- a/src/in_ncsf/SSEQPlayer/Channel.h
+++ b/src/in_ncsf/SSEQPlayer/Channel.h
@@ -1,7 +1,7 @@
/*
* SSEQ Player - Channel structures
* By Naram Qashat (CyberBotX) [cyberbotx@cyberbotx.com]
- * Last modification on 2013-04-26
+ * Last modification on 2013-05-07
*
* Adapted from source code of FeOS Sound System
* By fincs
@@ -83,6 +83,89 @@
struct Player;
+/*
+ * This creates a ring buffer, which will store N samples of SWAV
+ * data, duplicated. The way it is duplicated is done as follows:
+ * the samples are stored in the center of the buffer, and on both
+ * sides is half of the data, the first half being after the data
+ * and the second half before before the data. This in essense
+ * mirrors the data while allowing a pointer to always be retrieved
+ * and no extra copies of the buffer are created. Part of the idea
+ * for this came from kode54's original buffer implementation, but
+ * this has been designed to make sure that there are no delays in
+ * accessing the SWAVs samples and also doesn't use 0s before the
+ * start of the SWAV or use 0s after the end of a non-looping SWAV.
+ */
+template<size_t N> struct RingBuffer
+{
+ int16_t buffer[N * 2];
+ size_t bufferPos, getPos;
+
+ RingBuffer() : bufferPos(N / 2), getPos(N / 2)
+ {
+ std::fill(&this->buffer[0], &this->buffer[N * 2], 0);
+ }
+ void Clear()
+ {
+ std::fill(&this->buffer[0], &this->buffer[N * 2], 0);
+ this->bufferPos = this->getPos = N / 2;
+ }
+ void PushSample(int16_t sample)
+ {
+ this->buffer[this->bufferPos] = sample;
+ if (this->bufferPos >= N)
+ this->buffer[this->bufferPos - N] = sample;
+ else
+ this->buffer[this->bufferPos + N] = sample;
+ ++this->bufferPos;
+ if (this->bufferPos >= N * 3 / 2)
+ this->bufferPos -= N;
+ }
+ void PushSamples(const int16_t *samples, size_t size)
+ {
+ if (this->bufferPos + size > N * 3 / 2)
+ {
+ size_t free = N * 3 / 2 - this->bufferPos;
+ std::copy(&samples[0], &samples[free], &this->buffer[this->bufferPos]);
+ std::copy(&samples[free], &samples[size], &this->buffer[N / 2]);
+ }
+ else
+ std::copy(&samples[0], &samples[size], &this->buffer[this->bufferPos]);
+ size_t rightFree = this->bufferPos < N ? N - this->bufferPos : 0;
+ if (rightFree < size)
+ {
+ if (!rightFree)
+ {
+ size_t leftStart = this->bufferPos - N;
+ size_t leftSize = std::min(N / 2 - leftStart, size);
+ std::copy(&samples[0], &samples[leftSize], &this->buffer[leftStart]);
+ if (leftSize < size)
+ std::copy(&samples[leftSize], &samples[size], &this->buffer[N * 3 / 2]);
+ }
+ else
+ {
+ std::copy(&samples[0], &samples[rightFree], &this->buffer[this->bufferPos + N]);
+ std::copy(&samples[rightFree], &samples[size], &this->buffer[0]);
+ }
+ }
+ else
+ std::copy(&samples[0], &samples[size], &this->buffer[this->bufferPos + N]);
+ this->bufferPos += size;
+ if (this->bufferPos >= N * 3 / 2)
+ this->bufferPos -= N;
+ }
+ const int16_t *const GetBuffer() const
+ {
+ return &this->buffer[this->getPos];
+ }
+ void NextSample()
+ {
+ ++this->getPos;
+ if (this->getPos >= N * 3 / 2)
+ this->getPos -= N;
+ }
+};
+
struct Channel
{
int8_t chnId;
@@ -125,14 +208,6 @@
const Player *ply;
NDSSoundRegister reg;
-
- /*
- * Interpolation history buffer, which contains the maximum number of
- * samples required for any given interpolation mode. Doubled to
- * simplify the case of wrapping. Thanks to kode54 for providing this.
- */
- uint32_t sampleHistoryPtr;
- int16_t sampleHistory[64];
/*
* Lookup tables for the cosine and Lanczos Sinc interpolations, to
@@ -142,11 +217,13 @@
*/
static bool initializedLUTs;
static const unsigned COSINE_RESOLUTION = 8192;
- static const unsigned LANCZOS_RESOLUTION = 8192;
- static const unsigned LANCZOS_WIDTH = 8;
- static const unsigned LANCZOS_SAMPLES = LANCZOS_RESOLUTION * LANCZOS_WIDTH;
+ static const unsigned SINC_RESOLUTION = 8192;
+ static const unsigned SINC_WIDTH = 8;
+ static const unsigned SINC_SAMPLES = SINC_RESOLUTION * SINC_WIDTH;
static double cosine_lut[COSINE_RESOLUTION];
- static double lanczos_lut[LANCZOS_SAMPLES + 1];
+ static double sinc_lut[SINC_SAMPLES + 1];
+
+ RingBuffer<SINC_WIDTH * 2> ringBuffer;
Channel();
@@ -162,7 +239,6 @@
int32_t Interpolate();
int32_t GenerateSample();
void IncrementSample();
- void clearHistory();
};
#endif
--- a/src/in_ncsf/SSEQPlayer/Player.cpp
+++ b/src/in_ncsf/SSEQPlayer/Player.cpp
@@ -1,7 +1,7 @@
/*
* SSEQ Player - Player structure
* By Naram Qashat (CyberBotX) [cyberbotx@cyberbotx.com]
- * Last modification on 2013-04-18
+ * Last modification on 2013-05-07
*
* Adapted from source code of FeOS Sound System
* By fincs
@@ -120,7 +120,6 @@
this->channels[curChnNo].ply = this;
this->channels[curChnNo].noteLength = -1;
this->channels[curChnNo].vol = 0;
- this->channels[curChnNo].clearHistory();
return curChnNo;
}
--- a/src/in_ncsf/SSEQPlayer/consts.h
+++ b/src/in_ncsf/SSEQPlayer/consts.h
@@ -1,7 +1,7 @@
/*
* SSEQ Player - Constants/Macros
* By Naram Qashat (CyberBotX) [cyberbotx@cyberbotx.com]
- * Last modification on 2013-04-26
+ * Last modification on 2013-05-07
*
* Adapted from source code of FeOS Sound System
* By fincs
@@ -59,7 +59,7 @@
INTERPOLATION_4POINTBSPLINE,
INTERPOLATION_6POINTOSCULATING,
INTERPOLATION_6POINTBSPLINE,
- INTERPOLATION_LANCZOS
+ INTERPOLATION_SINC
};
#endif
--- a/src/in_ncsf/XSFConfig_NCSF.cpp
+++ b/src/in_ncsf/XSFConfig_NCSF.cpp
@@ -1,7 +1,7 @@
/*
* xSF - NCSF configuration
* By Naram Qashat (CyberBotX) [cyberbotx@cyberbotx.com]
- * Last modification on 2013-04-26
+ * Last modification on 2013-05-07
*
* Partially based on the vio*sf framework
*/
@@ -42,7 +42,7 @@
unsigned XSFConfig::initSampleRate = 44100;
std::wstring XSFConfig::commonName = L"NCSF Decoder";
-std::wstring XSFConfig::versionNumber = L"1.7.1";
+std::wstring XSFConfig::versionNumber = L"1.8";
unsigned XSFConfig_NCSF::initInterpolation = 5;
std::wstring XSFConfig_NCSF::initMutes = L"0000000000000000";
@@ -101,7 +101,7 @@
SendMessageW(GetDlgItem(hwndDlg, idInterpolation), CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(L"4-point, 3rd-order B-spline"));
SendMessageW(GetDlgItem(hwndDlg, idInterpolation), CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(L"6-point, 5th-order Osculating"));
SendMessageW(GetDlgItem(hwndDlg, idInterpolation), CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(L"6-point, 5th-order B-spline"));
- SendMessageW(GetDlgItem(hwndDlg, idInterpolation), CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(L"Lanczos (Sinc)"));
+ SendMessageW(GetDlgItem(hwndDlg, idInterpolation), CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(L"Sinc (Hann Window)"));
SendMessageW(GetDlgItem(hwndDlg, idInterpolation), CB_SETCURSEL, this->interpolation, 0);
// Mutes
for (int x = 0, numMutes = this->mutes.size(); x < numMutes; ++x)
@@ -142,6 +142,6 @@
void XSFConfig_NCSF::About(HWND parent)
{
MessageBox(parent, (XSFConfig::commonName + L" v" + XSFConfig::versionNumber + L", using xSF Winamp plugin framework (based on the vio*sf plugins) by Naram Qashat (CyberBotX) [cyberbotx@cyberbotx.com]\n\n"
- L"Utilizes code adapted from the FeOS Sound System library by fincs, git revision 9cb9820 on GitHub, for audio playback.").c_str(), (XSFConfig::commonName + L" v" + XSFConfig::versionNumber).c_str(), MB_OK);
+ L"Utilizes code adapted from the FeOS Sound System library by fincs, git revision 5204c55 on GitHub, for audio playback.").c_str(), (XSFConfig::commonName + L" v" + XSFConfig::versionNumber).c_str(), MB_OK);
}
--- a/src/in_xsf_framework/XSFPlayer.cpp
+++ b/src/in_xsf_framework/XSFPlayer.cpp
@@ -1,7 +1,7 @@
/*
* xSF - Core Player
* By Naram Qashat (CyberBotX) [cyberbotx@cyberbotx.com]
- * Last modification on 2013-04-26
+ * Last modification on 2013-05-07
*
* Partially based on the vio*sf framework
*/
@@ -169,8 +169,8 @@
double s1 = bufLong[2 * ofs] * scale, s2 = bufLong[2 * ofs + 1] * scale;
if (!this->uses32BitSamplesClampedTo16Bit)
{
- clamp(s1, -0x7FFF, 0x8000);
- clamp(s2, -0x7FFF, 0x8000);
+ clamp(s1, std::numeric_limits<int16_t>::min(), std::numeric_limits<int16_t>::max());
+ clamp(s2, std::numeric_limits<int16_t>::min(), std::numeric_limits<int16_t>::max());
}
bufLong[2 * ofs] = static_cast<int32_t>(s1);
bufLong[2 * ofs + 1] = static_cast<int32_t>(s2);
@@ -183,8 +183,8 @@
for (unsigned ofs = 0; ofs < bufsize; ++ofs)
{
int32_t s1 = bufLong[2 * ofs], s2 = bufLong[2 * ofs + 1];
- clamp(s1, -0x7FFF, 0x8000);
- clamp(s2, -0x7FFF, 0x8000);
+ clamp(s1, std::numeric_limits<int16_t>::min(), std::numeric_limits<int16_t>::max());
+ clamp(s2, std::numeric_limits<int16_t>::min(), std::numeric_limits<int16_t>::max());
bufShort[2 * ofs] = static_cast<int16_t>(s1);
bufShort[2 * ofs + 1] = static_cast<int16_t>(s2);
}
--- a/src/in_xsf_framework/XSFPlayer.h
+++ b/src/in_xsf_framework/XSFPlayer.h
@@ -1,7 +1,7 @@
/*
* xSF - Core Player
* By Naram Qashat (CyberBotX) [cyberbotx@cyberbotx.com]
- * Last modification on 2013-04-26
+ * Last modification on 2013-05-07
*
* Partially based on the vio*sf framework
*/
@@ -12,6 +12,7 @@
#include "XSFFile.h"
#ifdef WINAMP_PLUGIN
+# include "windowsh_wrapper.h"
# include <winamp/out.h>
#endif