Browse code

* 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.

Naram Qashat authored on 2013/05/07 05:42:46
Showing 7 changed files
... ...
@@ -1,7 +1,7 @@
1 1
 /*
2 2
  * SSEQ Player - Channel structures
3 3
  * By Naram Qashat (CyberBotX) [cyberbotx@cyberbotx.com]
4
- * Last modification on 2013-04-26
4
+ * Last modification on 2013-05-07
5 5
  *
6 6
  * Adapted from source code of FeOS Sound System
7 7
  * By fincs
... ...
@@ -44,7 +44,7 @@ TempSndReg::TempSndReg() : CR(0), SOURCE(nullptr), TIMER(0), REPEAT_POINT(0), LE
44 44
 
45 45
 bool Channel::initializedLUTs = false;
46 46
 double Channel::cosine_lut[Channel::COSINE_RESOLUTION];
47
-double Channel::lanczos_lut[Channel::LANCZOS_SAMPLES + 1];
47
+double Channel::sinc_lut[Channel::SINC_SAMPLES + 1];
48 48
 
49 49
 #ifndef M_PI
50 50
 static const double M_PI = 3.14159265358979323846;
... ...
@@ -57,16 +57,16 @@ static inline double sinc(double x)
57 57
 
58 58
 Channel::Channel() : chnId(-1), tempReg(), state(CS_NONE), trackId(-1), prio(0), manualSweep(false), flags(), pan(0), extAmpl(0), velocity(0), extPan(0),
59 59
 	key(0), ampl(0), extTune(0), orgKey(0), modType(0), modSpeed(0), modDepth(0), modRange(0), modDelay(0), modDelayCnt(0), modCounter(0),
60
-	sweepLen(0), sweepCnt(0), sweepPitch(0), attackLvl(0), sustainLvl(0x7F), decayRate(0), releaseRate(0xFFFF), noteLength(-1), vol(0), ply(nullptr), reg()
60
+	sweepLen(0), sweepCnt(0), sweepPitch(0), attackLvl(0), sustainLvl(0x7F), decayRate(0), releaseRate(0xFFFF), noteLength(-1), vol(0), ply(nullptr), reg(),
61
+	ringBuffer()
61 62
 {
62
-	this->clearHistory();
63 63
 	if (!this->initializedLUTs)
64 64
 	{
65 65
 		for (unsigned i = 0; i < COSINE_RESOLUTION; ++i)
66 66
 			this->cosine_lut[i] = (1.0 - std::cos((static_cast<double>(i) / COSINE_RESOLUTION) * M_PI)) * 0.5;
67
-		double dx = static_cast<double>(LANCZOS_WIDTH) / LANCZOS_SAMPLES, x = 0.0;
68
-		for (unsigned i = 0; i <= LANCZOS_SAMPLES; ++i, x += dx)
69
-			this->lanczos_lut[i] = std::abs(x) < LANCZOS_WIDTH ? sinc(x) * sinc(x / LANCZOS_WIDTH) : 0.0;
67
+		double dx = static_cast<double>(SINC_WIDTH) / SINC_SAMPLES, x = 0.0;
68
+		for (unsigned i = 0; i <= SINC_SAMPLES; ++i, x += dx)
69
+			this->sinc_lut[i] = std::abs(x) < SINC_WIDTH ? sinc(x) * (0.5 * (1.0 + std::cos((M_PI * x) / SINC_WIDTH))) : 0.0;
70 70
 		this->initializedLUTs = true;
71 71
 	}
72 72
 }
... ...
@@ -144,7 +144,6 @@ void Channel::Kill()
144 144
 	this->reg.ClearControlRegister();
145 145
 	this->vol = 0;
146 146
 	this->noteLength = -1;
147
-	this->clearHistory();
148 147
 }
149 148
 
150 149
 static inline int getModFlag(int type)
... ...
@@ -604,22 +603,22 @@ int32_t Channel::Interpolate()
604 603
 	double ratio = this->reg.samplePosition;
605 604
 	ratio -= static_cast<int32_t>(ratio);
606 605
 
607
-	const auto &data = &this->sampleHistory[this->sampleHistoryPtr + 16];
606
+	const auto &data = this->ringBuffer.GetBuffer();
608 607
 
609
-	if (this->ply->interpolation == INTERPOLATION_LANCZOS)
608
+	if (this->ply->interpolation == INTERPOLATION_SINC)
610 609
 	{
611
-		double kernel[LANCZOS_WIDTH * 2], kernel_sum = 0.0;
612
-		int i = LANCZOS_WIDTH, shift = static_cast<int>(std::floor(ratio * LANCZOS_RESOLUTION));
613
-		int step = this->reg.sampleIncrease > 1.0 ? static_cast<int>(LANCZOS_RESOLUTION / this->reg.sampleIncrease) : LANCZOS_RESOLUTION;
614
-		int shift_adj = shift * step / LANCZOS_RESOLUTION;
615
-		for (; i >= -static_cast<int>(LANCZOS_WIDTH - 1); --i)
610
+		double kernel[SINC_WIDTH * 2], kernel_sum = 0.0;
611
+		int i = SINC_WIDTH, shift = static_cast<int>(std::floor(ratio * SINC_RESOLUTION));
612
+		int step = this->reg.sampleIncrease > 1.0 ? static_cast<int>(SINC_RESOLUTION / this->reg.sampleIncrease) : SINC_RESOLUTION;
613
+		int shift_adj = shift * step / SINC_RESOLUTION;
614
+		for (; i >= -static_cast<int>(SINC_WIDTH - 1); --i)
616 615
 		{
617 616
 			int pos = i * step;
618
-			kernel_sum += kernel[i + LANCZOS_WIDTH - 1] = this->lanczos_lut[std::abs(shift_adj - pos)];
617
+			kernel_sum += kernel[i + SINC_WIDTH - 1] = this->sinc_lut[std::abs(shift_adj - pos)];
619 618
 		}
620 619
 		double sum = 0.0;
621
-		for (i = 0; i < static_cast<int>(LANCZOS_WIDTH * 2); ++i)
622
-			sum += data[i - static_cast<int>(LANCZOS_WIDTH) + 1] * kernel[i];
620
+		for (i = 0; i < static_cast<int>(SINC_WIDTH * 2); ++i)
621
+			sum += data[i - static_cast<int>(SINC_WIDTH) + 1] * kernel[i];
623 622
 		return static_cast<int32_t>(sum / kernel_sum);
624 623
 	}
625 624
 	else if (this->ply->interpolation > INTERPOLATION_COSINE)
... ...
@@ -721,22 +720,59 @@ void Channel::IncrementSample()
721 720
 {
722 721
 	double samplePosition = this->reg.samplePosition + this->reg.sampleIncrease;
723 722
 
724
-	if (this->reg.format != 3 && this->reg.samplePosition >= 0)
723
+	if (this->reg.format != 3)
725 724
 	{
726
-		uint32_t loc = static_cast<uint32_t>(this->reg.samplePosition);
727
-		uint32_t newloc = static_cast<uint32_t>(samplePosition);
725
+		if (this->reg.samplePosition < 0 && samplePosition >= 0)
726
+		{
727
+			this->ringBuffer.Clear();
728
+			this->ringBuffer.bufferPos += SINC_WIDTH + 1;
729
+			auto preData = std::vector<int16_t>(SINC_WIDTH + 1, this->reg.source->dataptr[0]);
730
+			this->ringBuffer.PushSamples(&preData[0], SINC_WIDTH + 1);
731
+			if (this->reg.totalLength < SINC_WIDTH + 1)
732
+			{
733
+				this->ringBuffer.PushSamples(&this->reg.source->dataptr[0], this->reg.totalLength);
734
+				if (this->reg.repeatMode == 1)
735
+				{
736
+					size_t samplesLeft = SINC_WIDTH + 1 - this->reg.totalLength;
737
+					while (samplesLeft)
738
+					{
739
+						size_t samplesToPush = std::min(samplesLeft, this->reg.length);
740
+						this->ringBuffer.PushSamples(&this->reg.source->dataptr[this->reg.loopStart], samplesToPush);
741
+						samplesLeft -= samplesToPush;
742
+					}
743
+				}
744
+			}
745
+			else
746
+				this->ringBuffer.PushSamples(&this->reg.source->dataptr[0], SINC_WIDTH + 1);
747
+		}
748
+		if (this->reg.samplePosition >= 0)
749
+		{
750
+			uint32_t loc = static_cast<uint32_t>(this->reg.samplePosition) + SINC_WIDTH + 1;
751
+			uint32_t newloc = static_cast<uint32_t>(samplePosition) + SINC_WIDTH + 1;
728 752
 
729
-		if (newloc >= this->reg.totalLength)
730
-			newloc -= this->reg.length;
753
+			if (this->reg.repeatMode == 1)
754
+			{
755
+				if (loc >= this->reg.totalLength)
756
+					loc -= this->reg.length;
757
+				if (newloc >= this->reg.totalLength)
758
+					newloc -= this->reg.length;
759
+			}
731 760
 
732
-		while (loc != newloc)
733
-		{
734
-			this->sampleHistory[this->sampleHistoryPtr] = this->sampleHistory[this->sampleHistoryPtr + 32] = this->reg.source->dataptr[loc++];
761
+			while (loc != newloc)
762
+			{
763
+				this->ringBuffer.NextSample();
735 764
 
736
-			this->sampleHistoryPtr = (this->sampleHistoryPtr + 1) & 31;
765
+				if (loc < this->reg.totalLength)
766
+					this->ringBuffer.PushSample(this->reg.source->dataptr[loc++]);
767
+				else
768
+				{
769
+					++loc;
770
+					this->ringBuffer.PushSample(this->reg.source->dataptr[this->reg.totalLength - 1]);
771
+				}
737 772
 
738
-			if (loc >= this->reg.totalLength)
739
-				loc -= this->reg.length;
773
+				if (this->reg.repeatMode == 1 && loc >= this->reg.totalLength)
774
+					loc -= this->reg.length;
775
+			}
740 776
 		}
741 777
 	}
742 778
 
... ...
@@ -753,9 +789,3 @@ void Channel::IncrementSample()
753 789
 			this->Kill();
754 790
 	}
755 791
 }
756
-
757
-void Channel::clearHistory()
758
-{
759
-	this->sampleHistoryPtr = 0;
760
-	memset(this->sampleHistory, 0, sizeof(this->sampleHistory));
761
-}
... ...
@@ -1,7 +1,7 @@
1 1
 /*
2 2
  * SSEQ Player - Channel structures
3 3
  * By Naram Qashat (CyberBotX) [cyberbotx@cyberbotx.com]
4
- * Last modification on 2013-04-26
4
+ * Last modification on 2013-05-07
5 5
  *
6 6
  * Adapted from source code of FeOS Sound System
7 7
  * By fincs
... ...
@@ -83,6 +83,89 @@ struct TempSndReg
83 83
 
84 84
 struct Player;
85 85
 
86
+/*
87
+ * This creates a ring buffer, which will store N samples of SWAV
88
+ * data, duplicated. The way it is duplicated is done as follows:
89
+ * the samples are stored in the center of the buffer, and on both
90
+ * sides is half of the data, the first half being after the data
91
+ * and the second half before before the data. This in essense
92
+ * mirrors the data while allowing a pointer to always be retrieved
93
+ * and no extra copies of the buffer are created. Part of the idea
94
+ * for this came from kode54's original buffer implementation, but
95
+ * this has been designed to make sure that there are no delays in
96
+ * accessing the SWAVs samples and also doesn't use 0s before the
97
+ * start of the SWAV or use 0s after the end of a non-looping SWAV.
98
+ */
99
+template<size_t N> struct RingBuffer
100
+{
101
+	int16_t buffer[N * 2];
102
+	size_t bufferPos, getPos;
103
+
104
+	RingBuffer() : bufferPos(N / 2), getPos(N / 2)
105
+	{
106
+		std::fill(&this->buffer[0], &this->buffer[N * 2], 0);
107
+	}
108
+	void Clear()
109
+	{
110
+		std::fill(&this->buffer[0], &this->buffer[N * 2], 0);
111
+		this->bufferPos = this->getPos = N / 2;
112
+	}
113
+	void PushSample(int16_t sample)
114
+	{
115
+		this->buffer[this->bufferPos] = sample;
116
+		if (this->bufferPos >= N)
117
+			this->buffer[this->bufferPos - N] = sample;
118
+		else
119
+			this->buffer[this->bufferPos + N] = sample;
120
+		++this->bufferPos;
121
+		if (this->bufferPos >= N * 3 / 2)
122
+			this->bufferPos -= N;
123
+	}
124
+	void PushSamples(const int16_t *samples, size_t size)
125
+	{
126
+		if (this->bufferPos + size > N * 3 / 2)
127
+		{
128
+			size_t free = N * 3 / 2 - this->bufferPos;
129
+			std::copy(&samples[0], &samples[free], &this->buffer[this->bufferPos]);
130
+			std::copy(&samples[free], &samples[size], &this->buffer[N / 2]);
131
+		}
132
+		else
133
+			std::copy(&samples[0], &samples[size], &this->buffer[this->bufferPos]);
134
+		size_t rightFree = this->bufferPos < N ? N - this->bufferPos : 0;
135
+		if (rightFree < size)
136
+		{
137
+			if (!rightFree)
138
+			{
139
+				size_t leftStart = this->bufferPos - N;
140
+				size_t leftSize = std::min(N / 2 - leftStart, size);
141
+				std::copy(&samples[0], &samples[leftSize], &this->buffer[leftStart]);
142
+				if (leftSize < size)
143
+					std::copy(&samples[leftSize], &samples[size], &this->buffer[N * 3 / 2]);
144
+			}
145
+			else
146
+			{
147
+				std::copy(&samples[0], &samples[rightFree], &this->buffer[this->bufferPos + N]);
148
+				std::copy(&samples[rightFree], &samples[size], &this->buffer[0]);
149
+			}
150
+		}
151
+		else
152
+			std::copy(&samples[0], &samples[size], &this->buffer[this->bufferPos + N]);
153
+		this->bufferPos += size;
154
+		if (this->bufferPos >= N * 3 / 2)
155
+			this->bufferPos -= N;
156
+	}
157
+	const int16_t *const GetBuffer() const
158
+	{
159
+		return &this->buffer[this->getPos];
160
+	}
161
+	void NextSample()
162
+	{
163
+		++this->getPos;
164
+		if (this->getPos >= N * 3 / 2)
165
+			this->getPos -= N;
166
+	}
167
+};
168
+
86 169
 struct Channel
87 170
 {
88 171
 	int8_t chnId;
... ...
@@ -126,14 +209,6 @@ struct Channel
126 209
 	const Player *ply;
127 210
 	NDSSoundRegister reg;
128 211
 
129
-	/*
130
-	 * Interpolation history buffer, which contains the maximum number of
131
-	 * samples required for any given interpolation mode. Doubled to
132
-	 * simplify the case of wrapping. Thanks to kode54 for providing this.
133
-	 */
134
-	uint32_t sampleHistoryPtr;
135
-	int16_t sampleHistory[64];
136
-
137 212
 	/*
138 213
 	 * Lookup tables for the cosine and Lanczos Sinc interpolations, to
139 214
 	 * avoid the need to call the sin/cos functions all the time.
... ...
@@ -142,11 +217,13 @@ struct Channel
142 217
 	 */
143 218
 	static bool initializedLUTs;
144 219
 	static const unsigned COSINE_RESOLUTION = 8192;
145
-	static const unsigned LANCZOS_RESOLUTION = 8192;
146
-	static const unsigned LANCZOS_WIDTH = 8;
147
-	static const unsigned LANCZOS_SAMPLES = LANCZOS_RESOLUTION * LANCZOS_WIDTH;
220
+	static const unsigned SINC_RESOLUTION = 8192;
221
+	static const unsigned SINC_WIDTH = 8;
222
+	static const unsigned SINC_SAMPLES = SINC_RESOLUTION * SINC_WIDTH;
148 223
 	static double cosine_lut[COSINE_RESOLUTION];
149
-	static double lanczos_lut[LANCZOS_SAMPLES + 1];
224
+	static double sinc_lut[SINC_SAMPLES + 1];
225
+
226
+	RingBuffer<SINC_WIDTH * 2> ringBuffer;
150 227
 
151 228
 	Channel();
152 229
 
... ...
@@ -162,7 +239,6 @@ struct Channel
162 239
 	int32_t Interpolate();
163 240
 	int32_t GenerateSample();
164 241
 	void IncrementSample();
165
-	void clearHistory();
166 242
 };
167 243
 
168 244
 #endif
... ...
@@ -1,7 +1,7 @@
1 1
 /*
2 2
  * SSEQ Player - Player structure
3 3
  * By Naram Qashat (CyberBotX) [cyberbotx@cyberbotx.com]
4
- * Last modification on 2013-04-18
4
+ * Last modification on 2013-05-07
5 5
  *
6 6
  * Adapted from source code of FeOS Sound System
7 7
  * By fincs
... ...
@@ -120,7 +120,6 @@ int Player::ChannelAlloc(int type, int priority)
120 120
 	this->channels[curChnNo].ply = this;
121 121
 	this->channels[curChnNo].noteLength = -1;
122 122
 	this->channels[curChnNo].vol = 0;
123
-	this->channels[curChnNo].clearHistory();
124 123
 	return curChnNo;
125 124
 }
126 125
 
... ...
@@ -1,7 +1,7 @@
1 1
 /*
2 2
  * SSEQ Player - Constants/Macros
3 3
  * By Naram Qashat (CyberBotX) [cyberbotx@cyberbotx.com]
4
- * Last modification on 2013-04-26
4
+ * Last modification on 2013-05-07
5 5
  *
6 6
  * Adapted from source code of FeOS Sound System
7 7
  * By fincs
... ...
@@ -59,7 +59,7 @@ enum Interpolation
59 59
 	INTERPOLATION_4POINTBSPLINE,
60 60
 	INTERPOLATION_6POINTOSCULATING,
61 61
 	INTERPOLATION_6POINTBSPLINE,
62
-	INTERPOLATION_LANCZOS
62
+	INTERPOLATION_SINC
63 63
 };
64 64
 
65 65
 #endif
... ...
@@ -1,7 +1,7 @@
1 1
 /*
2 2
  * xSF - NCSF configuration
3 3
  * By Naram Qashat (CyberBotX) [cyberbotx@cyberbotx.com]
4
- * Last modification on 2013-04-26
4
+ * Last modification on 2013-05-07
5 5
  *
6 6
  * Partially based on the vio*sf framework
7 7
  */
... ...
@@ -42,7 +42,7 @@ public:
42 42
 
43 43
 unsigned XSFConfig::initSampleRate = 44100;
44 44
 std::wstring XSFConfig::commonName = L"NCSF Decoder";
45
-std::wstring XSFConfig::versionNumber = L"1.7.1";
45
+std::wstring XSFConfig::versionNumber = L"1.8";
46 46
 unsigned XSFConfig_NCSF::initInterpolation = 5;
47 47
 std::wstring XSFConfig_NCSF::initMutes = L"0000000000000000";
48 48
 
... ...
@@ -101,7 +101,7 @@ INT_PTR CALLBACK XSFConfig_NCSF::ConfigDialogProc(HWND hwndDlg, UINT uMsg, WPARA
101 101
 			SendMessageW(GetDlgItem(hwndDlg, idInterpolation), CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(L"4-point, 3rd-order B-spline"));
102 102
 			SendMessageW(GetDlgItem(hwndDlg, idInterpolation), CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(L"6-point, 5th-order Osculating"));
103 103
 			SendMessageW(GetDlgItem(hwndDlg, idInterpolation), CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(L"6-point, 5th-order B-spline"));
104
-			SendMessageW(GetDlgItem(hwndDlg, idInterpolation), CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(L"Lanczos (Sinc)"));
104
+			SendMessageW(GetDlgItem(hwndDlg, idInterpolation), CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(L"Sinc (Hann Window)"));
105 105
 			SendMessageW(GetDlgItem(hwndDlg, idInterpolation), CB_SETCURSEL, this->interpolation, 0);
106 106
 			// Mutes
107 107
 			for (int x = 0, numMutes = this->mutes.size(); x < numMutes; ++x)
... ...
@@ -142,5 +142,5 @@ void XSFConfig_NCSF::CopySpecificConfigToMemory(XSFPlayer *xSFPlayer, bool)
142 142
 void XSFConfig_NCSF::About(HWND parent)
143 143
 {
144 144
 	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"
145
-		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);
145
+		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);
146 146
 }
... ...
@@ -1,7 +1,7 @@
1 1
 /*
2 2
  * xSF - Core Player
3 3
  * By Naram Qashat (CyberBotX) [cyberbotx@cyberbotx.com]
4
- * Last modification on 2013-04-26
4
+ * Last modification on 2013-05-07
5 5
  *
6 6
  * Partially based on the vio*sf framework
7 7
  */
... ...
@@ -169,8 +169,8 @@ bool XSFPlayer::FillBuffer(std::vector<uint8_t> &buf, unsigned &samplesWritten)
169 169
 			double s1 = bufLong[2 * ofs] * scale, s2 = bufLong[2 * ofs + 1] * scale;
170 170
 			if (!this->uses32BitSamplesClampedTo16Bit)
171 171
 			{
172
-				clamp(s1, -0x7FFF, 0x8000);
173
-				clamp(s2, -0x7FFF, 0x8000);
172
+				clamp(s1, std::numeric_limits<int16_t>::min(), std::numeric_limits<int16_t>::max());
173
+				clamp(s2, std::numeric_limits<int16_t>::min(), std::numeric_limits<int16_t>::max());
174 174
 			}
175 175
 			bufLong[2 * ofs] = static_cast<int32_t>(s1);
176 176
 			bufLong[2 * ofs + 1] = static_cast<int32_t>(s2);
... ...
@@ -183,8 +183,8 @@ bool XSFPlayer::FillBuffer(std::vector<uint8_t> &buf, unsigned &samplesWritten)
183 183
 		for (unsigned ofs = 0; ofs < bufsize; ++ofs)
184 184
 		{
185 185
 			int32_t s1 = bufLong[2 * ofs], s2 = bufLong[2 * ofs + 1];
186
-			clamp(s1, -0x7FFF, 0x8000);
187
-			clamp(s2, -0x7FFF, 0x8000);
186
+			clamp(s1, std::numeric_limits<int16_t>::min(), std::numeric_limits<int16_t>::max());
187
+			clamp(s2, std::numeric_limits<int16_t>::min(), std::numeric_limits<int16_t>::max());
188 188
 			bufShort[2 * ofs] = static_cast<int16_t>(s1);
189 189
 			bufShort[2 * ofs + 1] = static_cast<int16_t>(s2);
190 190
 		}
... ...
@@ -1,7 +1,7 @@
1 1
 /*
2 2
  * xSF - Core Player
3 3
  * By Naram Qashat (CyberBotX) [cyberbotx@cyberbotx.com]
4
- * Last modification on 2013-04-26
4
+ * Last modification on 2013-05-07
5 5
  *
6 6
  * Partially based on the vio*sf framework
7 7
  */
... ...
@@ -12,6 +12,7 @@
12 12
 #include "XSFFile.h"
13 13
 
14 14
 #ifdef WINAMP_PLUGIN
15
+# include "windowsh_wrapper.h"
15 16
 # include <winamp/out.h>
16 17
 #endif
17 18