1
0

Paula.cpp 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. /*
  2. * Paula.cpp
  3. * ---------
  4. * Purpose: Emulating the Amiga's sound chip, Paula, by implementing resampling using band-limited steps (BLEPs)
  5. * Notes : The BLEP table generator code is a translation of Antti S. Lankila's original Python code.
  6. * Authors: OpenMPT Devs
  7. * Antti S. Lankila
  8. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  9. */
  10. #include "stdafx.h"
  11. #include "Paula.h"
  12. #include "TinyFFT.h"
  13. #include "Tables.h"
  14. #include "mpt/base/numbers.hpp"
  15. #include <complex>
  16. #include <numeric>
  17. OPENMPT_NAMESPACE_BEGIN
  18. namespace Paula
  19. {
  20. namespace
  21. {
  22. MPT_NOINLINE std::vector<double> KaiserFIR(int numTaps, double cutoff, double beta)
  23. {
  24. const double izeroBeta = Izero(beta);
  25. const double kPi = 4.0 * std::atan(1.0) * cutoff;
  26. const double xDiv = 1.0 / ((numTaps / 2) * (numTaps / 2));
  27. const int numTapsDiv2 = numTaps / 2;
  28. std::vector<double> result(numTaps);
  29. for(int i = 0; i < numTaps; i++)
  30. {
  31. double fsinc;
  32. if(i == numTapsDiv2)
  33. {
  34. fsinc = 1.0;
  35. } else
  36. {
  37. const double x = i - numTapsDiv2;
  38. const double xPi = x * kPi;
  39. // - sinc - - Kaiser window - -sinc-
  40. fsinc = std::sin(xPi) * Izero(beta * std::sqrt(1 - x * x * xDiv)) / (izeroBeta * xPi);
  41. }
  42. result[i] = fsinc * cutoff;
  43. }
  44. return result;
  45. }
  46. MPT_NOINLINE void FIR_MinPhase(std::vector<double> &table, const TinyFFT &fft)
  47. {
  48. std::vector<std::complex<double>> cepstrum(fft.Size());
  49. MPT_ASSERT(cepstrum.size() >= table.size());
  50. for(size_t i = 0; i < table.size(); i++)
  51. cepstrum[i] = table[i];
  52. // Compute the real cepstrum: fft -> abs + ln -> ifft -> real
  53. fft.FFT(cepstrum);
  54. for(auto &v : cepstrum)
  55. v = std::log(std::abs(v));
  56. fft.IFFT(cepstrum);
  57. fft.Normalize(cepstrum);
  58. // Window the cepstrum in such a way that anticausal components become rejected
  59. for(size_t i = 1; i < cepstrum.size() / 2; i++)
  60. {
  61. cepstrum[i] *= 2;
  62. cepstrum[i + cepstrum.size() / 2] *= 0;
  63. }
  64. // Now cancel the previous steps: fft -> exp -> ifft -> real
  65. fft.FFT(cepstrum);
  66. for(auto &v : cepstrum)
  67. v = std::exp(v);
  68. fft.IFFT(cepstrum);
  69. fft.Normalize(cepstrum);
  70. for(size_t i = 0; i < table.size(); i++)
  71. table[i] = cepstrum[i].real();
  72. }
  73. class BiquadFilter
  74. {
  75. double b0, b1, b2, a1, a2, x1 = 0.0, x2 = 0.0, y1 = 0.0, y2 = 0.0;
  76. double Filter(double x0)
  77. {
  78. double y0 = b0 * x0 + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2;
  79. x2 = x1;
  80. x1 = x0;
  81. y2 = y1;
  82. y1 = y0;
  83. return y0;
  84. }
  85. public:
  86. BiquadFilter(double b0_, double b1_, double b2_, double a1_, double a2_)
  87. : b0(b0_), b1(b1_), b2(b2_), a1(a1_), a2(a2_)
  88. { }
  89. std::vector<double> Run(std::vector<double> table)
  90. {
  91. x1 = 0.0;
  92. x2 = 0.0;
  93. y1 = 0.0;
  94. y2 = 0.0;
  95. // Initialize filter to stable state
  96. for(int i = 0; i < 10000; i++)
  97. Filter(table[0]);
  98. // Now run the filter
  99. for(auto &v : table)
  100. v = Filter(v);
  101. return table;
  102. }
  103. };
  104. // Observe: a and b are reversed here. To be absolutely clear:
  105. // a is the nominator and b is the denominator. :-/
  106. BiquadFilter ZTransform(double a0, double a1, double a2, double b0, double b1, double b2, double fc, double fs)
  107. {
  108. // Prewarp s - domain coefficients
  109. const double wp = 2.0 * fs * std::tan(mpt::numbers::pi * fc / fs);
  110. a2 /= wp * wp;
  111. a1 /= wp;
  112. b2 /= wp * wp;
  113. b1 /= wp;
  114. // Compute bilinear transform and return it
  115. const double bd = 4 * b2 * fs * fs + 2 * b1 * fs + b0;
  116. return BiquadFilter(
  117. (4 * a2 * fs * fs + 2 * a1 * fs + a0) / bd,
  118. (2 * a0 - 8 * a2 * fs * fs) / bd,
  119. (4 * a2 * fs * fs - 2 * a1 * fs + a0) / bd,
  120. (2 * b0 - 8 * b2 * fs * fs) / bd,
  121. (4 * b2 * fs * fs - 2 * b1 * fs + b0) / bd);
  122. }
  123. BiquadFilter MakeRCLowpass(double sampleRate, double freq)
  124. {
  125. const double omega = (2.0 * mpt::numbers::pi) * freq / sampleRate;
  126. const double term = 1 + 1 / omega;
  127. return BiquadFilter(1 / term, 0.0, 0.0, -1.0 + 1.0 / term, 0.0);
  128. }
  129. BiquadFilter MakeButterworth(double fs, double fc, double res_dB = 0)
  130. {
  131. // 2nd-order Butterworth s-domain coefficients are:
  132. //
  133. // b0 = 1.0 b1 = 0 b2 = 0
  134. // a0 = 1 a1 = sqrt(2) a2 = 1
  135. //
  136. // by tweaking the a1 parameter, some resonance can be produced.
  137. const double res = std::pow(10.0, (-res_dB / 10.0 / 2.0));
  138. return ZTransform(1, 0, 0, 1, std::sqrt(2) * res, 1, fc, fs);
  139. }
  140. MPT_NOINLINE void Integrate(std::vector<double> &table)
  141. {
  142. const double total = std::accumulate(table.begin(), table.end(), 0.0);
  143. double startVal = -total;
  144. for(auto &v : table)
  145. {
  146. startVal += v;
  147. v = startVal;
  148. }
  149. }
  150. MPT_NOINLINE void Quantize(const std::vector<double> &in, Paula::BlepArray &quantized)
  151. {
  152. MPT_ASSERT(in.size() == Paula::BLEP_SIZE);
  153. constexpr int fact = 1 << Paula::BLEP_SCALE;
  154. const double cv = fact / (in.back() - in.front());
  155. for(int i = 0; i < Paula::BLEP_SIZE; i++)
  156. {
  157. double val = in[i] * cv;
  158. #ifdef MPT_INTMIXER
  159. val = mpt::round(val);
  160. #endif
  161. quantized[i] = static_cast<mixsample_t>(-val);
  162. }
  163. }
  164. } // namespace
  165. void BlepTables::InitTables()
  166. {
  167. constexpr double sampleRate = Paula::PAULA_HZ;
  168. // Because Amiga only has 84 dB SNR, the noise floor is low enough with -90 dB.
  169. // A500 model uses slightly lower-quality kaiser window to obtain slightly
  170. // steeper stopband attenuation. The fixed filters attenuates the sidelobes by
  171. // 12 dB, compensating for the worse performance of the kaiser window.
  172. // 21 kHz stopband is not fully attenuated by 22 kHz. If the sampling frequency
  173. // is 44.1 kHz, all frequencies above 22 kHz will alias over 20 kHz, thus inaudible.
  174. // The output should be aliasingless for 48 kHz sampling frequency.
  175. auto unfilteredA500 = KaiserFIR(Paula::BLEP_SIZE, 21000.0 / sampleRate * 2.0, 8.0);
  176. auto unfilteredA1200 = KaiserFIR(Paula::BLEP_SIZE, 21000.0 / sampleRate * 2.0, 9.0);
  177. // Move filtering effects to start to allow IIRs more time to settle
  178. constexpr size_t padSize = 8;
  179. constexpr int fftSize = static_cast<int>(mpt::bit_width(size_t(Paula::BLEP_SIZE)) + mpt::bit_width(padSize) - 2);
  180. const TinyFFT fft(fftSize);
  181. FIR_MinPhase(unfilteredA500, fft);
  182. FIR_MinPhase(unfilteredA1200, fft);
  183. // Make digital models for the filters on Amiga 500 and 1200.
  184. auto filterFixed5kHz = MakeRCLowpass(sampleRate, 4900.0);
  185. // The leakage filter seems to reduce treble in both models a bit
  186. // The A500 filter seems to be well modelled only with a 4.9 kHz
  187. // filter although the component values would suggest 5 kHz filter.
  188. auto filterLeakage = MakeRCLowpass(sampleRate, 32000.0);
  189. auto filterLED = MakeButterworth(sampleRate, 3275.0, -0.70);
  190. // Apply fixed filter to A500
  191. auto amiga500Off = filterFixed5kHz.Run(unfilteredA500);
  192. // Produce the filtered outputs
  193. auto amiga1200Off = filterLeakage.Run(unfilteredA1200);
  194. // Produce LED filters
  195. auto amiga500On = filterLED.Run(amiga500Off);
  196. auto amiga1200On = filterLED.Run(amiga1200Off);
  197. // Integrate to produce blep
  198. Integrate(amiga500Off);
  199. Integrate(amiga500On);
  200. Integrate(amiga1200Off);
  201. Integrate(amiga1200On);
  202. Integrate(unfilteredA1200);
  203. // Quantize and scale
  204. Quantize(amiga500Off, WinSincIntegral[A500Off]);
  205. Quantize(amiga500On, WinSincIntegral[A500On]);
  206. Quantize(amiga1200Off, WinSincIntegral[A1200Off]);
  207. Quantize(amiga1200On, WinSincIntegral[A1200On]);
  208. Quantize(unfilteredA1200, WinSincIntegral[Unfiltered]);
  209. }
  210. const Paula::BlepArray &BlepTables::GetAmigaTable(Resampling::AmigaFilter amigaType, bool enableFilter) const
  211. {
  212. if(amigaType == Resampling::AmigaFilter::A500)
  213. return enableFilter ? WinSincIntegral[A500On] : WinSincIntegral[A500Off];
  214. if(amigaType == Resampling::AmigaFilter::A1200)
  215. return enableFilter ? WinSincIntegral[A1200On] : WinSincIntegral[A1200Off];
  216. return WinSincIntegral[Unfiltered];
  217. }
  218. // we do not initialize blepState here
  219. // cppcheck-suppress uninitMemberVar
  220. State::State(uint32 sampleRate)
  221. {
  222. double amigaClocksPerSample = static_cast<double>(PAULA_HZ) / sampleRate;
  223. numSteps = static_cast<int>(amigaClocksPerSample / MINIMUM_INTERVAL);
  224. stepRemainder = SamplePosition::FromDouble(amigaClocksPerSample - numSteps * MINIMUM_INTERVAL);
  225. remainder = SamplePosition(0);
  226. }
  227. void State::Reset()
  228. {
  229. remainder = SamplePosition(0);
  230. activeBleps = 0;
  231. firstBlep = MAX_BLEPS / 2u;
  232. globalOutputLevel = 0;
  233. }
  234. void State::InputSample(int16 sample)
  235. {
  236. if(sample != globalOutputLevel)
  237. {
  238. // Start a new blep: level is the difference, age (or phase) is 0 clocks.
  239. firstBlep = (firstBlep - 1u) % MAX_BLEPS;
  240. if(activeBleps < std::size(blepState))
  241. activeBleps++;
  242. blepState[firstBlep].age = 0;
  243. blepState[firstBlep].level = sample - globalOutputLevel;
  244. globalOutputLevel = sample;
  245. }
  246. }
  247. // Return output simulated as series of bleps
  248. int State::OutputSample(const BlepArray &WinSincIntegral)
  249. {
  250. int output = globalOutputLevel * (1 << Paula::BLEP_SCALE);
  251. uint32 lastBlep = firstBlep + activeBleps;
  252. for(uint32 i = firstBlep; i != lastBlep; i++)
  253. {
  254. const auto &blep = blepState[i % MAX_BLEPS];
  255. output -= WinSincIntegral[blep.age] * blep.level;
  256. }
  257. output /= (1 << (Paula::BLEP_SCALE - 2)); // - 2 to compensate for the fact that we reduced the input sample bit depth
  258. return output;
  259. }
  260. // Advance the simulation by given number of clock ticks
  261. void State::Clock(int cycles)
  262. {
  263. uint32 lastBlep = firstBlep + activeBleps;
  264. for(uint32 i = firstBlep; i != lastBlep; i++)
  265. {
  266. auto &blep = blepState[i % MAX_BLEPS];
  267. blep.age += static_cast<uint16>(cycles);
  268. if(blep.age >= Paula::BLEP_SIZE)
  269. {
  270. activeBleps = static_cast<uint16>(i - firstBlep);
  271. return;
  272. }
  273. }
  274. }
  275. }
  276. OPENMPT_NAMESPACE_END