tuning.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011
  1. /*
  2. * tuning.cpp
  3. * ----------
  4. * Purpose: Alternative sample tuning.
  5. * Notes : (currently none)
  6. * Authors: OpenMPT Devs
  7. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  8. */
  9. #include "stdafx.h"
  10. #include "tuning.h"
  11. #include "mpt/io/io.hpp"
  12. #include "mpt/io/io_stdstream.hpp"
  13. #include "../common/serialization_utils.h"
  14. #include "../common/misc_util.h"
  15. #include <string>
  16. #include <cmath>
  17. OPENMPT_NAMESPACE_BEGIN
  18. namespace Tuning {
  19. static RATIOTYPE SanitizeGroupRatio(RATIOTYPE ratio)
  20. {
  21. return std::clamp(std::abs(ratio), 1e-15f, 1e+07f);
  22. }
  23. namespace CTuningS11n
  24. {
  25. void ReadStr(std::istream &iStrm, mpt::ustring &ustr, const std::size_t dummy, mpt::Charset charset);
  26. void ReadNoteMap(std::istream &iStrm, std::map<NOTEINDEXTYPE, mpt::ustring> &m, const std::size_t dummy, mpt::Charset charset);
  27. void ReadRatioTable(std::istream& iStrm, std::vector<RATIOTYPE>& v, const size_t);
  28. void WriteNoteMap(std::ostream &oStrm, const std::map<NOTEINDEXTYPE, mpt::ustring> &m);
  29. void WriteStr(std::ostream &oStrm, const mpt::ustring &ustr);
  30. struct RatioWriter
  31. {
  32. RatioWriter(uint16 nWriteCount = s_nDefaultWriteCount) : m_nWriteCount(nWriteCount) {}
  33. void operator()(std::ostream& oStrm, const std::vector<float>& v);
  34. uint16 m_nWriteCount;
  35. enum : uint16 { s_nDefaultWriteCount = (uint16_max >> 2) };
  36. };
  37. }
  38. using namespace CTuningS11n;
  39. /*
  40. Version history:
  41. 4->5: Lots of changes, finestep interpretation revamp, fileformat revamp.
  42. 3->4: Changed sizetypes in serialisation from size_t(uint32) to
  43. smaller types (uint8, USTEPTYPE) (March 2007)
  44. */
  45. /*
  46. Version changes:
  47. 3->4: Finetune related internal structure and serialization revamp.
  48. 2->3: The type for the size_type in the serialisation changed
  49. from default(size_t, uint32) to unsigned STEPTYPE. (March 2007)
  50. */
  51. static_assert(CTuning::s_RatioTableFineSizeMaxDefault < static_cast<USTEPINDEXTYPE>(FINESTEPCOUNT_MAX));
  52. CTuning::CTuning()
  53. : m_TuningType(Type::GENERAL)
  54. , m_FineStepCount(0)
  55. {
  56. m_RatioTable.clear();
  57. m_NoteMin = s_NoteMinDefault;
  58. m_RatioTable.resize(s_RatioTableSizeDefault, 1);
  59. m_GroupSize = 0;
  60. m_GroupRatio = 0;
  61. m_RatioTableFine.clear();
  62. }
  63. bool CTuning::CreateGroupGeometric(const NOTEINDEXTYPE &s, const RATIOTYPE &r, const NOTEINDEXTYPE &startindex)
  64. {
  65. if(s < 1 || !IsValidRatio(r) || startindex < GetNoteRange().first)
  66. {
  67. return false;
  68. }
  69. std::vector<RATIOTYPE> v;
  70. v.reserve(s);
  71. for(NOTEINDEXTYPE i = startindex; i < startindex + s; i++)
  72. {
  73. v.push_back(GetRatio(i));
  74. }
  75. return CreateGroupGeometric(v, r, GetNoteRange(), startindex);
  76. }
  77. bool CTuning::CreateGroupGeometric(const std::vector<RATIOTYPE> &v, const RATIOTYPE &r, const NoteRange &range, const NOTEINDEXTYPE &ratiostartpos)
  78. {
  79. if(range.first > range.last || v.size() == 0)
  80. {
  81. return false;
  82. }
  83. if(ratiostartpos < range.first || range.last < ratiostartpos || static_cast<UNOTEINDEXTYPE>(range.last - ratiostartpos) < static_cast<UNOTEINDEXTYPE>(v.size() - 1))
  84. {
  85. return false;
  86. }
  87. if(GetFineStepCount() > FINESTEPCOUNT_MAX)
  88. {
  89. return false;
  90. }
  91. for(size_t i = 0; i < v.size(); i++)
  92. {
  93. if(v[i] < 0)
  94. {
  95. return false;
  96. }
  97. }
  98. if(r <= 0)
  99. {
  100. return false;
  101. }
  102. m_TuningType = Type::GROUPGEOMETRIC;
  103. m_NoteMin = range.first;
  104. m_GroupSize = mpt::saturate_cast<NOTEINDEXTYPE>(v.size());
  105. m_GroupRatio = std::fabs(r);
  106. m_RatioTable.resize(range.last - range.first + 1);
  107. std::copy(v.begin(), v.end(), m_RatioTable.begin() + (ratiostartpos - range.first));
  108. for(int32 i = ratiostartpos - 1; i >= m_NoteMin && ratiostartpos > NOTEINDEXTYPE_MIN; i--)
  109. {
  110. m_RatioTable[i - m_NoteMin] = m_RatioTable[i - m_NoteMin + m_GroupSize] / m_GroupRatio;
  111. }
  112. for(int32 i = ratiostartpos + m_GroupSize; i <= range.last && ratiostartpos <= (NOTEINDEXTYPE_MAX - m_GroupSize); i++)
  113. {
  114. m_RatioTable[i - m_NoteMin] = m_GroupRatio * m_RatioTable[i - m_NoteMin - m_GroupSize];
  115. }
  116. UpdateFineStepTable();
  117. return true;
  118. }
  119. bool CTuning::CreateGeometric(const UNOTEINDEXTYPE &p, const RATIOTYPE &r)
  120. {
  121. return CreateGeometric(p, r, GetNoteRange());
  122. }
  123. bool CTuning::CreateGeometric(const UNOTEINDEXTYPE &s, const RATIOTYPE &r, const NoteRange &range)
  124. {
  125. if(range.first > range.last)
  126. {
  127. return false;
  128. }
  129. if(s < 1 || !IsValidRatio(r))
  130. {
  131. return false;
  132. }
  133. if(range.last - range.first + 1 > NOTEINDEXTYPE_MAX)
  134. {
  135. return false;
  136. }
  137. m_TuningType = Type::GEOMETRIC;
  138. m_RatioTable.clear();
  139. m_NoteMin = s_NoteMinDefault;
  140. m_RatioTable.resize(s_RatioTableSizeDefault, static_cast<RATIOTYPE>(1.0));
  141. m_GroupSize = 0;
  142. m_GroupRatio = 0;
  143. m_RatioTableFine.clear();
  144. m_NoteMin = range.first;
  145. m_GroupSize = mpt::saturate_cast<NOTEINDEXTYPE>(s);
  146. m_GroupRatio = std::fabs(r);
  147. const RATIOTYPE stepRatio = std::pow(m_GroupRatio, static_cast<RATIOTYPE>(1.0) / static_cast<RATIOTYPE>(m_GroupSize));
  148. m_RatioTable.resize(range.last - range.first + 1);
  149. for(int32 i = range.first; i <= range.last; i++)
  150. {
  151. m_RatioTable[i - m_NoteMin] = std::pow(stepRatio, static_cast<RATIOTYPE>(i));
  152. }
  153. UpdateFineStepTable();
  154. return true;
  155. }
  156. mpt::ustring CTuning::GetNoteName(const NOTEINDEXTYPE &x, bool addOctave) const
  157. {
  158. if(!IsValidNote(x))
  159. {
  160. return mpt::ustring();
  161. }
  162. if(GetGroupSize() < 1)
  163. {
  164. const auto i = m_NoteNameMap.find(x);
  165. if(i != m_NoteNameMap.end())
  166. return i->second;
  167. else
  168. return mpt::ufmt::val(x);
  169. }
  170. else
  171. {
  172. const NOTEINDEXTYPE pos = static_cast<NOTEINDEXTYPE>(mpt::wrapping_modulo(x, m_GroupSize));
  173. const NOTEINDEXTYPE middlePeriodNumber = 5;
  174. mpt::ustring rValue;
  175. const auto nmi = m_NoteNameMap.find(pos);
  176. if(nmi != m_NoteNameMap.end())
  177. {
  178. rValue = nmi->second;
  179. if(addOctave)
  180. {
  181. rValue += mpt::ufmt::val(middlePeriodNumber + mpt::wrapping_divide(x, m_GroupSize));
  182. }
  183. }
  184. else
  185. {
  186. //By default, using notation nnP for notes; nn <-> note character starting
  187. //from 'A' with char ':' as fill char, and P is period integer. For example:
  188. //C:5, D:3, R:7
  189. if(m_GroupSize <= 26)
  190. {
  191. rValue = mpt::ToUnicode(mpt::Charset::UTF8, std::string(1, static_cast<char>(pos + 'A')));
  192. rValue += UL_(":");
  193. } else
  194. {
  195. rValue = mpt::ufmt::HEX0<1>(pos % 16) + mpt::ufmt::HEX0<1>((pos / 16) % 16);
  196. if(pos > 0xff)
  197. {
  198. rValue = mpt::ToUnicode(mpt::Charset::UTF8, mpt::ToLowerCaseAscii(mpt::ToCharset(mpt::Charset::UTF8, rValue)));
  199. }
  200. }
  201. if(addOctave)
  202. {
  203. rValue += mpt::ufmt::val(middlePeriodNumber + mpt::wrapping_divide(x, m_GroupSize));
  204. }
  205. }
  206. return rValue;
  207. }
  208. }
  209. void CTuning::SetNoteName(const NOTEINDEXTYPE &n, const mpt::ustring &str)
  210. {
  211. const NOTEINDEXTYPE pos = (GetGroupSize() < 1) ? n : static_cast<NOTEINDEXTYPE>(mpt::wrapping_modulo(n, m_GroupSize));
  212. if(!str.empty())
  213. {
  214. m_NoteNameMap[pos] = str;
  215. } else
  216. {
  217. const auto iter = m_NoteNameMap.find(pos);
  218. if(iter != m_NoteNameMap.end())
  219. {
  220. m_NoteNameMap.erase(iter);
  221. }
  222. }
  223. }
  224. // Without finetune
  225. RATIOTYPE CTuning::GetRatio(const NOTEINDEXTYPE note) const
  226. {
  227. if(!IsValidNote(note))
  228. {
  229. return s_DefaultFallbackRatio;
  230. }
  231. const auto ratio = m_RatioTable[note - m_NoteMin];
  232. if(ratio <= 1e-15f)
  233. {
  234. return s_DefaultFallbackRatio;
  235. }
  236. return ratio;
  237. }
  238. // With finetune
  239. RATIOTYPE CTuning::GetRatio(const NOTEINDEXTYPE baseNote, const STEPINDEXTYPE baseFineSteps) const
  240. {
  241. const STEPINDEXTYPE fineStepCount = static_cast<STEPINDEXTYPE>(GetFineStepCount());
  242. if(fineStepCount == 0 || baseFineSteps == 0)
  243. {
  244. return GetRatio(static_cast<NOTEINDEXTYPE>(baseNote + baseFineSteps));
  245. }
  246. // If baseFineSteps is more than the number of finesteps between notes, note is increased.
  247. // So first figuring out what note and fineStep values to actually use.
  248. // Interpreting finestep==-1 on note x so that it is the same as finestep==fineStepCount on note x-1.
  249. // Note: If fineStepCount is n, n+1 steps are needed to get to next note.
  250. const NOTEINDEXTYPE note = static_cast<NOTEINDEXTYPE>(baseNote + mpt::wrapping_divide(baseFineSteps, (fineStepCount + 1)));
  251. const STEPINDEXTYPE fineStep = mpt::wrapping_modulo(baseFineSteps, (fineStepCount + 1));
  252. if(!IsValidNote(note))
  253. {
  254. return s_DefaultFallbackRatio;
  255. }
  256. if(fineStep == 0)
  257. {
  258. return m_RatioTable[note - m_NoteMin];
  259. }
  260. RATIOTYPE fineRatio = static_cast<RATIOTYPE>(1.0);
  261. if(GetType() == Type::GEOMETRIC && m_RatioTableFine.size() > 0)
  262. {
  263. fineRatio = m_RatioTableFine[fineStep - 1];
  264. } else if(GetType() == Type::GROUPGEOMETRIC && m_RatioTableFine.size() > 0)
  265. {
  266. fineRatio = m_RatioTableFine[GetRefNote(note) * fineStepCount + fineStep - 1];
  267. } else
  268. {
  269. // Geometric finestepping
  270. fineRatio = std::pow(GetRatio(note + 1) / GetRatio(note), static_cast<RATIOTYPE>(fineStep) / (fineStepCount + 1));
  271. }
  272. return m_RatioTable[note - m_NoteMin] * fineRatio;
  273. }
  274. bool CTuning::SetRatio(const NOTEINDEXTYPE& s, const RATIOTYPE& r)
  275. {
  276. if(GetType() != Type::GROUPGEOMETRIC && GetType() != Type::GENERAL)
  277. {
  278. return false;
  279. }
  280. //Creating ratio table if doesn't exist.
  281. if(m_RatioTable.empty())
  282. {
  283. m_RatioTable.assign(s_RatioTableSizeDefault, 1);
  284. m_NoteMin = s_NoteMinDefault;
  285. }
  286. if(!IsValidNote(s))
  287. {
  288. return false;
  289. }
  290. m_RatioTable[s - m_NoteMin] = std::fabs(r);
  291. if(GetType() == Type::GROUPGEOMETRIC)
  292. { // update other groups
  293. for(NOTEINDEXTYPE n = m_NoteMin; n < m_NoteMin + static_cast<NOTEINDEXTYPE>(m_RatioTable.size()); ++n)
  294. {
  295. if(n == s)
  296. {
  297. // nothing
  298. } else if(std::abs(n - s) % m_GroupSize == 0)
  299. {
  300. m_RatioTable[n - m_NoteMin] = std::pow(m_GroupRatio, static_cast<RATIOTYPE>(n - s) / static_cast<RATIOTYPE>(m_GroupSize)) * m_RatioTable[s - m_NoteMin];
  301. }
  302. }
  303. UpdateFineStepTable();
  304. }
  305. return true;
  306. }
  307. void CTuning::SetFineStepCount(const USTEPINDEXTYPE& fs)
  308. {
  309. m_FineStepCount = std::clamp(mpt::saturate_cast<STEPINDEXTYPE>(fs), STEPINDEXTYPE(0), FINESTEPCOUNT_MAX);
  310. UpdateFineStepTable();
  311. }
  312. void CTuning::UpdateFineStepTable()
  313. {
  314. if(m_FineStepCount <= 0)
  315. {
  316. m_RatioTableFine.clear();
  317. return;
  318. }
  319. if(GetType() == Type::GEOMETRIC)
  320. {
  321. if(m_FineStepCount > s_RatioTableFineSizeMaxDefault)
  322. {
  323. m_RatioTableFine.clear();
  324. return;
  325. }
  326. m_RatioTableFine.resize(m_FineStepCount);
  327. const RATIOTYPE q = GetRatio(GetNoteRange().first + 1) / GetRatio(GetNoteRange().first);
  328. const RATIOTYPE rFineStep = std::pow(q, static_cast<RATIOTYPE>(1)/(m_FineStepCount+1));
  329. for(USTEPINDEXTYPE i = 1; i<=m_FineStepCount; i++)
  330. m_RatioTableFine[i-1] = std::pow(rFineStep, static_cast<RATIOTYPE>(i));
  331. return;
  332. }
  333. if(GetType() == Type::GROUPGEOMETRIC)
  334. {
  335. const UNOTEINDEXTYPE p = GetGroupSize();
  336. if(p > s_RatioTableFineSizeMaxDefault / m_FineStepCount)
  337. {
  338. //In case fineratiotable would become too large, not using
  339. //table for it.
  340. m_RatioTableFine.clear();
  341. return;
  342. }
  343. else
  344. {
  345. //Creating 'geometric' finestepping between notes.
  346. m_RatioTableFine.resize(p * m_FineStepCount);
  347. const NOTEINDEXTYPE startnote = GetRefNote(GetNoteRange().first);
  348. for(UNOTEINDEXTYPE i = 0; i<p; i++)
  349. {
  350. const NOTEINDEXTYPE refnote = GetRefNote(startnote+i);
  351. const RATIOTYPE rFineStep = std::pow(GetRatio(refnote+1) / GetRatio(refnote), static_cast<RATIOTYPE>(1)/(m_FineStepCount+1));
  352. for(UNOTEINDEXTYPE j = 1; j<=m_FineStepCount; j++)
  353. {
  354. m_RatioTableFine[m_FineStepCount * refnote + (j-1)] = std::pow(rFineStep, static_cast<RATIOTYPE>(j));
  355. }
  356. }
  357. return;
  358. }
  359. }
  360. if(GetType() == Type::GENERAL)
  361. {
  362. //Not using table with tuning of type general.
  363. m_RatioTableFine.clear();
  364. return;
  365. }
  366. //Should not reach here.
  367. m_RatioTableFine.clear();
  368. m_FineStepCount = 0;
  369. }
  370. bool CTuning::Multiply(const RATIOTYPE r)
  371. {
  372. if(!IsValidRatio(r))
  373. {
  374. return false;
  375. }
  376. for(auto & ratio : m_RatioTable)
  377. {
  378. ratio *= r;
  379. }
  380. return true;
  381. }
  382. bool CTuning::ChangeGroupsize(const NOTEINDEXTYPE& s)
  383. {
  384. if(s < 1)
  385. return false;
  386. if(m_TuningType == Type::GROUPGEOMETRIC)
  387. return CreateGroupGeometric(s, GetGroupRatio(), 0);
  388. if(m_TuningType == Type::GEOMETRIC)
  389. return CreateGeometric(s, GetGroupRatio());
  390. return false;
  391. }
  392. bool CTuning::ChangeGroupRatio(const RATIOTYPE& r)
  393. {
  394. if(!IsValidRatio(r))
  395. return false;
  396. if(m_TuningType == Type::GROUPGEOMETRIC)
  397. return CreateGroupGeometric(GetGroupSize(), r, 0);
  398. if(m_TuningType == Type::GEOMETRIC)
  399. return CreateGeometric(GetGroupSize(), r);
  400. return false;
  401. }
  402. SerializationResult CTuning::InitDeserialize(std::istream &iStrm, mpt::Charset defaultCharset)
  403. {
  404. // Note: OpenMPT since at least r323 writes version number (4<<24)+4 while it
  405. // reads version number (5<<24)+4 or earlier.
  406. // We keep this behaviour.
  407. if(iStrm.fail())
  408. return SerializationResult::Failure;
  409. srlztn::SsbRead ssb(iStrm);
  410. ssb.BeginRead("CTB244RTI", (5 << 24) + 4); // version
  411. int8 use_utf8 = 0;
  412. ssb.ReadItem(use_utf8, "UTF8");
  413. const mpt::Charset charset = use_utf8 ? mpt::Charset::UTF8 : defaultCharset;
  414. ssb.ReadItem(m_TuningName, "0", [charset](std::istream &iStrm, mpt::ustring &ustr, const std::size_t dummy){ return ReadStr(iStrm, ustr, dummy, charset); });
  415. uint16 dummyEditMask = 0xffff;
  416. ssb.ReadItem(dummyEditMask, "1");
  417. std::underlying_type<Type>::type type = 0;
  418. ssb.ReadItem(type, "2");
  419. m_TuningType = static_cast<Type>(type);
  420. ssb.ReadItem(m_NoteNameMap, "3", [charset](std::istream &iStrm, std::map<NOTEINDEXTYPE, mpt::ustring> &m, const std::size_t dummy){ return ReadNoteMap(iStrm, m, dummy, charset); });
  421. ssb.ReadItem(m_FineStepCount, "4");
  422. // RTI entries.
  423. ssb.ReadItem(m_RatioTable, "RTI0", ReadRatioTable);
  424. ssb.ReadItem(m_NoteMin, "RTI1");
  425. ssb.ReadItem(m_GroupSize, "RTI2");
  426. ssb.ReadItem(m_GroupRatio, "RTI3");
  427. UNOTEINDEXTYPE ratiotableSize = 0;
  428. ssb.ReadItem(ratiotableSize, "RTI4");
  429. m_GroupRatio = SanitizeGroupRatio(m_GroupRatio);
  430. if(!std::isfinite(m_GroupRatio))
  431. {
  432. return SerializationResult::Failure;
  433. }
  434. for(auto ratio : m_RatioTable)
  435. {
  436. if(!std::isfinite(ratio))
  437. return SerializationResult::Failure;
  438. }
  439. // If reader status is ok and m_NoteMin is somewhat reasonable, process data.
  440. if(!((ssb.GetStatus() & srlztn::SNT_FAILURE) == 0 && m_NoteMin >= -300 && m_NoteMin <= 300))
  441. {
  442. return SerializationResult::Failure;
  443. }
  444. // reject unknown types
  445. if(m_TuningType != Type::GENERAL && m_TuningType != Type::GROUPGEOMETRIC && m_TuningType != Type::GEOMETRIC)
  446. {
  447. return SerializationResult::Failure;
  448. }
  449. if(m_GroupSize < 0)
  450. {
  451. return SerializationResult::Failure;
  452. }
  453. m_FineStepCount = std::clamp(mpt::saturate_cast<STEPINDEXTYPE>(m_FineStepCount), STEPINDEXTYPE(0), FINESTEPCOUNT_MAX);
  454. if(m_RatioTable.size() > static_cast<size_t>(NOTEINDEXTYPE_MAX))
  455. {
  456. return SerializationResult::Failure;
  457. }
  458. if((GetType() == Type::GROUPGEOMETRIC) || (GetType() == Type::GEOMETRIC))
  459. {
  460. if(ratiotableSize < 1 || ratiotableSize > NOTEINDEXTYPE_MAX)
  461. {
  462. return SerializationResult::Failure;
  463. }
  464. if(GetType() == Type::GEOMETRIC)
  465. {
  466. if(!CreateGeometric(GetGroupSize(), GetGroupRatio(), NoteRange{m_NoteMin, static_cast<NOTEINDEXTYPE>(m_NoteMin + ratiotableSize - 1)}))
  467. {
  468. return SerializationResult::Failure;
  469. }
  470. } else
  471. {
  472. if(!CreateGroupGeometric(m_RatioTable, GetGroupRatio(), NoteRange{m_NoteMin, static_cast<NOTEINDEXTYPE>(m_NoteMin + ratiotableSize - 1)}, m_NoteMin))
  473. {
  474. return SerializationResult::Failure;
  475. }
  476. }
  477. } else
  478. {
  479. UpdateFineStepTable();
  480. }
  481. return SerializationResult::Success;
  482. }
  483. template<class T, class SIZETYPE, class Tdst>
  484. static bool VectorFromBinaryStream(std::istream& inStrm, std::vector<Tdst>& v, const SIZETYPE maxSize = (std::numeric_limits<SIZETYPE>::max)())
  485. {
  486. if(!inStrm.good())
  487. return false;
  488. SIZETYPE size = 0;
  489. mpt::IO::ReadIntLE<SIZETYPE>(inStrm, size);
  490. if(size > maxSize)
  491. return false;
  492. v.resize(size);
  493. for(std::size_t i = 0; i<size; i++)
  494. {
  495. T tmp = T();
  496. mpt::IO::Read(inStrm, tmp);
  497. v[i] = tmp;
  498. }
  499. return inStrm.good();
  500. }
  501. SerializationResult CTuning::InitDeserializeOLD(std::istream &inStrm, mpt::Charset defaultCharset)
  502. {
  503. if(!inStrm.good())
  504. return SerializationResult::Failure;
  505. const std::streamoff startPos = inStrm.tellg();
  506. //First checking is there expected begin sequence.
  507. char begin[8];
  508. MemsetZero(begin);
  509. inStrm.read(begin, sizeof(begin));
  510. if(std::memcmp(begin, "CTRTI_B.", 8))
  511. {
  512. //Returning stream position if beginmarker was not found.
  513. inStrm.seekg(startPos);
  514. return SerializationResult::Failure;
  515. }
  516. //Version
  517. int16 version = 0;
  518. mpt::IO::ReadIntLE<int16>(inStrm, version);
  519. if(version != 2 && version != 3)
  520. return SerializationResult::Failure;
  521. char begin2[8];
  522. MemsetZero(begin2);
  523. inStrm.read(begin2, sizeof(begin2));
  524. if(std::memcmp(begin2, "CT<sfs>B", 8))
  525. {
  526. return SerializationResult::Failure;
  527. }
  528. int16 version2 = 0;
  529. mpt::IO::ReadIntLE<int16>(inStrm, version2);
  530. if(version2 != 3 && version2 != 4)
  531. {
  532. return SerializationResult::Failure;
  533. }
  534. //Tuning name
  535. if(version2 <= 3)
  536. {
  537. std::string tmpName;
  538. if(!mpt::IO::ReadSizedStringLE<uint32>(inStrm, tmpName, 0xffff))
  539. {
  540. return SerializationResult::Failure;
  541. }
  542. m_TuningName = mpt::ToUnicode(defaultCharset, tmpName);
  543. } else
  544. {
  545. std::string tmpName;
  546. if(!mpt::IO::ReadSizedStringLE<uint8>(inStrm, tmpName))
  547. {
  548. return SerializationResult::Failure;
  549. }
  550. m_TuningName = mpt::ToUnicode(defaultCharset, tmpName);
  551. }
  552. //Const mask
  553. int16 em = 0;
  554. mpt::IO::ReadIntLE<int16>(inStrm, em);
  555. //Tuning type
  556. int16 tt = 0;
  557. mpt::IO::ReadIntLE<int16>(inStrm, tt);
  558. m_TuningType = static_cast<Type>(tt);
  559. //Notemap
  560. uint16 size = 0;
  561. if(version2 <= 3)
  562. {
  563. uint32 tempsize = 0;
  564. mpt::IO::ReadIntLE<uint32>(inStrm, tempsize);
  565. if(tempsize > 0xffff)
  566. {
  567. return SerializationResult::Failure;
  568. }
  569. size = mpt::saturate_cast<uint16>(tempsize);
  570. } else
  571. {
  572. mpt::IO::ReadIntLE<uint16>(inStrm, size);
  573. }
  574. for(UNOTEINDEXTYPE i = 0; i<size; i++)
  575. {
  576. std::string str;
  577. int16 n = 0;
  578. mpt::IO::ReadIntLE<int16>(inStrm, n);
  579. if(version2 <= 3)
  580. {
  581. if(!mpt::IO::ReadSizedStringLE<uint32>(inStrm, str, 0xffff))
  582. {
  583. return SerializationResult::Failure;
  584. }
  585. } else
  586. {
  587. if(!mpt::IO::ReadSizedStringLE<uint8>(inStrm, str))
  588. {
  589. return SerializationResult::Failure;
  590. }
  591. }
  592. m_NoteNameMap[n] = mpt::ToUnicode(defaultCharset, str);
  593. }
  594. //End marker
  595. char end2[8];
  596. MemsetZero(end2);
  597. inStrm.read(end2, sizeof(end2));
  598. if(std::memcmp(end2, "CT<sfs>E", 8))
  599. {
  600. return SerializationResult::Failure;
  601. }
  602. // reject unknown types
  603. if(m_TuningType != Type::GENERAL && m_TuningType != Type::GROUPGEOMETRIC && m_TuningType != Type::GEOMETRIC)
  604. {
  605. return SerializationResult::Failure;
  606. }
  607. //Ratiotable
  608. if(version <= 2)
  609. {
  610. if(!VectorFromBinaryStream<IEEE754binary32LE, uint32>(inStrm, m_RatioTable, 0xffff))
  611. {
  612. return SerializationResult::Failure;
  613. }
  614. } else
  615. {
  616. if(!VectorFromBinaryStream<IEEE754binary32LE, uint16>(inStrm, m_RatioTable))
  617. {
  618. return SerializationResult::Failure;
  619. }
  620. }
  621. for(auto ratio : m_RatioTable)
  622. {
  623. if(!std::isfinite(ratio))
  624. return SerializationResult::Failure;
  625. }
  626. //Fineratios
  627. if(version <= 2)
  628. {
  629. if(!VectorFromBinaryStream<IEEE754binary32LE, uint32>(inStrm, m_RatioTableFine, 0xffff))
  630. {
  631. return SerializationResult::Failure;
  632. }
  633. } else
  634. {
  635. if(!VectorFromBinaryStream<IEEE754binary32LE, uint16>(inStrm, m_RatioTableFine))
  636. {
  637. return SerializationResult::Failure;
  638. }
  639. }
  640. for(auto ratio : m_RatioTableFine)
  641. {
  642. if(!std::isfinite(ratio))
  643. return SerializationResult::Failure;
  644. }
  645. m_FineStepCount = mpt::saturate_cast<USTEPINDEXTYPE>(m_RatioTableFine.size());
  646. // m_NoteMin
  647. int16 notemin = 0;
  648. mpt::IO::ReadIntLE<int16>(inStrm, notemin);
  649. m_NoteMin = notemin;
  650. if(m_NoteMin < -200 || m_NoteMin > 200)
  651. {
  652. return SerializationResult::Failure;
  653. }
  654. //m_GroupSize
  655. int16 groupsize = 0;
  656. mpt::IO::ReadIntLE<int16>(inStrm, groupsize);
  657. m_GroupSize = groupsize;
  658. if(m_GroupSize < 0)
  659. {
  660. return SerializationResult::Failure;
  661. }
  662. //m_GroupRatio
  663. IEEE754binary32LE groupratio = IEEE754binary32LE(0.0f);
  664. mpt::IO::Read(inStrm, groupratio);
  665. m_GroupRatio = SanitizeGroupRatio(groupratio);
  666. if(!std::isfinite(m_GroupRatio))
  667. {
  668. return SerializationResult::Failure;
  669. }
  670. char end[8];
  671. MemsetZero(end);
  672. inStrm.read(reinterpret_cast<char*>(&end), sizeof(end));
  673. if(std::memcmp(end, "CTRTI_E.", 8))
  674. {
  675. return SerializationResult::Failure;
  676. }
  677. // reject corrupt tunings
  678. if(m_RatioTable.size() > static_cast<std::size_t>(NOTEINDEXTYPE_MAX))
  679. {
  680. return SerializationResult::Failure;
  681. }
  682. if((m_GroupSize <= 0 || m_GroupRatio <= 0) && m_TuningType != Type::GENERAL)
  683. {
  684. return SerializationResult::Failure;
  685. }
  686. if(m_TuningType == Type::GROUPGEOMETRIC || m_TuningType == Type::GEOMETRIC)
  687. {
  688. if(m_RatioTable.size() < static_cast<std::size_t>(m_GroupSize))
  689. {
  690. return SerializationResult::Failure;
  691. }
  692. }
  693. // convert old finestepcount
  694. if(m_FineStepCount > 0)
  695. {
  696. m_FineStepCount -= 1;
  697. }
  698. m_FineStepCount = std::clamp(mpt::saturate_cast<STEPINDEXTYPE>(m_FineStepCount), STEPINDEXTYPE(0), FINESTEPCOUNT_MAX);
  699. UpdateFineStepTable();
  700. if(m_TuningType == Type::GEOMETRIC)
  701. {
  702. // Convert old geometric to new groupgeometric because old geometric tunings
  703. // can have ratio(0) != 1.0, which would get lost when saving nowadays.
  704. if(mpt::saturate_cast<NOTEINDEXTYPE>(m_RatioTable.size()) >= m_GroupSize - m_NoteMin)
  705. {
  706. std::vector<RATIOTYPE> ratios;
  707. for(NOTEINDEXTYPE n = 0; n < m_GroupSize; ++n)
  708. {
  709. ratios.push_back(m_RatioTable[n - m_NoteMin]);
  710. }
  711. CreateGroupGeometric(ratios, m_GroupRatio, GetNoteRange(), 0);
  712. }
  713. }
  714. return SerializationResult::Success;
  715. }
  716. Tuning::SerializationResult CTuning::Serialize(std::ostream& outStrm) const
  717. {
  718. // Note: OpenMPT since at least r323 writes version number (4<<24)+4 while it
  719. // reads version number (5<<24)+4.
  720. // We keep this behaviour.
  721. srlztn::SsbWrite ssb(outStrm);
  722. ssb.BeginWrite("CTB244RTI", (4 << 24) + 4); // version
  723. ssb.WriteItem(int8(1), "UTF8");
  724. if (m_TuningName.length() > 0)
  725. ssb.WriteItem(m_TuningName, "0", WriteStr);
  726. uint16 dummyEditMask = 0xffff;
  727. ssb.WriteItem(dummyEditMask, "1");
  728. ssb.WriteItem(mpt::to_underlying(m_TuningType), "2");
  729. if (m_NoteNameMap.size() > 0)
  730. ssb.WriteItem(m_NoteNameMap, "3", WriteNoteMap);
  731. if (GetFineStepCount() > 0)
  732. ssb.WriteItem(m_FineStepCount, "4");
  733. const Tuning::Type tt = GetType();
  734. if (GetGroupRatio() > 0)
  735. ssb.WriteItem(m_GroupRatio, "RTI3");
  736. if (tt == Type::GROUPGEOMETRIC)
  737. ssb.WriteItem(m_RatioTable, "RTI0", RatioWriter(GetGroupSize()));
  738. if (tt == Type::GENERAL)
  739. ssb.WriteItem(m_RatioTable, "RTI0", RatioWriter());
  740. if (tt == Type::GEOMETRIC)
  741. ssb.WriteItem(m_GroupSize, "RTI2");
  742. if(tt == Type::GEOMETRIC || tt == Type::GROUPGEOMETRIC)
  743. { //For Groupgeometric this data is the number of ratios in ratiotable.
  744. UNOTEINDEXTYPE ratiotableSize = static_cast<UNOTEINDEXTYPE>(m_RatioTable.size());
  745. ssb.WriteItem(ratiotableSize, "RTI4");
  746. }
  747. // m_NoteMin
  748. ssb.WriteItem(m_NoteMin, "RTI1");
  749. ssb.FinishWrite();
  750. return ((ssb.GetStatus() & srlztn::SNT_FAILURE) != 0) ? Tuning::SerializationResult::Failure : Tuning::SerializationResult::Success;
  751. }
  752. #ifdef MODPLUG_TRACKER
  753. bool CTuning::WriteSCL(std::ostream &f, const mpt::PathString &filename) const
  754. {
  755. mpt::IO::WriteTextCRLF(f, MPT_AFORMAT("! {}")(mpt::ToCharset(mpt::Charset::ISO8859_1, (filename.GetFileName() + filename.GetFileExt()).ToUnicode())));
  756. mpt::IO::WriteTextCRLF(f, "!");
  757. std::string name = mpt::ToCharset(mpt::Charset::ISO8859_1, GetName());
  758. for(auto & c : name) { if(static_cast<uint8>(c) < 32) c = ' '; } // remove control characters
  759. if(name.length() >= 1 && name[0] == '!') name[0] = '?'; // do not confuse description with comment
  760. mpt::IO::WriteTextCRLF(f, name);
  761. if(GetType() == Type::GEOMETRIC)
  762. {
  763. mpt::IO::WriteTextCRLF(f, MPT_AFORMAT(" {}")(m_GroupSize));
  764. mpt::IO::WriteTextCRLF(f, "!");
  765. for(NOTEINDEXTYPE n = 0; n < m_GroupSize; ++n)
  766. {
  767. double ratio = std::pow(static_cast<double>(m_GroupRatio), static_cast<double>(n + 1) / static_cast<double>(m_GroupSize));
  768. double cents = std::log2(ratio) * 1200.0;
  769. mpt::IO::WriteTextCRLF(f, MPT_AFORMAT(" {} ! {}")(
  770. mpt::afmt::fix(cents),
  771. mpt::ToCharset(mpt::Charset::ISO8859_1, GetNoteName((n + 1) % m_GroupSize, false))
  772. ));
  773. }
  774. } else if(GetType() == Type::GROUPGEOMETRIC)
  775. {
  776. mpt::IO::WriteTextCRLF(f, MPT_AFORMAT(" {}")(m_GroupSize));
  777. mpt::IO::WriteTextCRLF(f, "!");
  778. for(NOTEINDEXTYPE n = 0; n < m_GroupSize; ++n)
  779. {
  780. bool last = (n == (m_GroupSize - 1));
  781. double baseratio = static_cast<double>(GetRatio(0));
  782. double ratio = static_cast<double>(last ? m_GroupRatio : GetRatio(n + 1)) / baseratio;
  783. double cents = std::log2(ratio) * 1200.0;
  784. mpt::IO::WriteTextCRLF(f, MPT_AFORMAT(" {} ! {}")(
  785. mpt::afmt::fix(cents),
  786. mpt::ToCharset(mpt::Charset::ISO8859_1, GetNoteName((n + 1) % m_GroupSize, false))
  787. ));
  788. }
  789. } else if(GetType() == Type::GENERAL)
  790. {
  791. mpt::IO::WriteTextCRLF(f, MPT_AFORMAT(" {}")(m_RatioTable.size() + 1));
  792. mpt::IO::WriteTextCRLF(f, "!");
  793. double baseratio = 1.0;
  794. for(NOTEINDEXTYPE n = 0; n < mpt::saturate_cast<NOTEINDEXTYPE>(m_RatioTable.size()); ++n)
  795. {
  796. baseratio = std::min(baseratio, static_cast<double>(m_RatioTable[n]));
  797. }
  798. for(NOTEINDEXTYPE n = 0; n < mpt::saturate_cast<NOTEINDEXTYPE>(m_RatioTable.size()); ++n)
  799. {
  800. double ratio = static_cast<double>(m_RatioTable[n]) / baseratio;
  801. double cents = std::log2(ratio) * 1200.0;
  802. mpt::IO::WriteTextCRLF(f, MPT_AFORMAT(" {} ! {}")(
  803. mpt::afmt::fix(cents),
  804. mpt::ToCharset(mpt::Charset::ISO8859_1, GetNoteName(n + m_NoteMin, false))
  805. ));
  806. }
  807. mpt::IO::WriteTextCRLF(f, MPT_AFORMAT(" {} ! {}")(
  808. mpt::afmt::val(1),
  809. std::string()
  810. ));
  811. } else
  812. {
  813. return false;
  814. }
  815. return true;
  816. }
  817. #endif
  818. namespace CTuningS11n
  819. {
  820. void RatioWriter::operator()(std::ostream& oStrm, const std::vector<float>& v)
  821. {
  822. const std::size_t nWriteCount = std::min(v.size(), static_cast<std::size_t>(m_nWriteCount));
  823. mpt::IO::WriteAdaptiveInt64LE(oStrm, nWriteCount);
  824. for(size_t i = 0; i < nWriteCount; i++)
  825. mpt::IO::Write(oStrm, IEEE754binary32LE(v[i]));
  826. }
  827. void ReadNoteMap(std::istream &iStrm, std::map<NOTEINDEXTYPE, mpt::ustring> &m, const std::size_t dummy, mpt::Charset charset)
  828. {
  829. MPT_UNREFERENCED_PARAMETER(dummy);
  830. uint64 val;
  831. mpt::IO::ReadAdaptiveInt64LE(iStrm, val);
  832. LimitMax(val, 256u); // Read 256 at max.
  833. for(size_t i = 0; i < val; i++)
  834. {
  835. int16 key;
  836. mpt::IO::ReadIntLE<int16>(iStrm, key);
  837. std::string str;
  838. mpt::IO::ReadSizedStringLE<uint8>(iStrm, str);
  839. m[key] = mpt::ToUnicode(charset, str);
  840. }
  841. }
  842. void ReadRatioTable(std::istream& iStrm, std::vector<RATIOTYPE>& v, const size_t)
  843. {
  844. uint64 val;
  845. mpt::IO::ReadAdaptiveInt64LE(iStrm, val);
  846. v.resize(std::min(mpt::saturate_cast<std::size_t>(val), std::size_t(256))); // Read 256 vals at max.
  847. for(size_t i = 0; i < v.size(); i++)
  848. {
  849. IEEE754binary32LE tmp(0.0f);
  850. mpt::IO::Read(iStrm, tmp);
  851. v[i] = tmp;
  852. }
  853. }
  854. void ReadStr(std::istream &iStrm, mpt::ustring &ustr, const std::size_t dummy, mpt::Charset charset)
  855. {
  856. MPT_UNREFERENCED_PARAMETER(dummy);
  857. std::string str;
  858. uint64 val;
  859. mpt::IO::ReadAdaptiveInt64LE(iStrm, val);
  860. size_t nSize = (val > 255) ? 255 : static_cast<size_t>(val); // Read 255 characters at max.
  861. str.clear();
  862. str.resize(nSize);
  863. for(size_t i = 0; i < nSize; i++)
  864. mpt::IO::ReadIntLE(iStrm, str[i]);
  865. if(str.find_first_of('\0') != std::string::npos)
  866. { // trim \0 at the end
  867. str.resize(str.find_first_of('\0'));
  868. }
  869. ustr = mpt::ToUnicode(charset, str);
  870. }
  871. void WriteNoteMap(std::ostream &oStrm, const std::map<NOTEINDEXTYPE, mpt::ustring> &m)
  872. {
  873. mpt::IO::WriteAdaptiveInt64LE(oStrm, m.size());
  874. for(auto &mi : m)
  875. {
  876. mpt::IO::WriteIntLE<int16>(oStrm, mi.first);
  877. mpt::IO::WriteSizedStringLE<uint8>(oStrm, mpt::ToCharset(mpt::Charset::UTF8, mi.second));
  878. }
  879. }
  880. void WriteStr(std::ostream &oStrm, const mpt::ustring &ustr)
  881. {
  882. std::string str = mpt::ToCharset(mpt::Charset::UTF8, ustr);
  883. mpt::IO::WriteAdaptiveInt64LE(oStrm, str.size());
  884. oStrm.write(str.c_str(), str.size());
  885. }
  886. } // namespace CTuningS11n.
  887. } // namespace Tuning
  888. OPENMPT_NAMESPACE_END