TuningDialog.cpp 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745
  1. /*
  2. * TuningDialog.cpp
  3. * ----------------
  4. * Purpose: Alternative sample tuning configuration dialog.
  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 "Mptrack.h"
  11. #include "TuningDialog.h"
  12. #include "mpt/io/base.hpp"
  13. #include "mpt/io/io.hpp"
  14. #include "mpt/io/io_stdstream.hpp"
  15. #include "TrackerSettings.h"
  16. #include <algorithm>
  17. #include "../common/mptFileIO.h"
  18. #include "../common/misc_util.h"
  19. #include "TuningDialog.h"
  20. #include "FileDialog.h"
  21. #include "Mainfrm.h"
  22. OPENMPT_NAMESPACE_BEGIN
  23. const mpt::Charset TuningCharsetFallback = mpt::Charset::Locale;
  24. const CTuningDialog::TUNINGTREEITEM CTuningDialog::s_notFoundItemTuning = TUNINGTREEITEM();
  25. const HTREEITEM CTuningDialog::s_notFoundItemTree = NULL;
  26. using UNOTEINDEXTYPE = Tuning::UNOTEINDEXTYPE;
  27. using RATIOTYPE = Tuning::RATIOTYPE;
  28. using NOTEINDEXTYPE = Tuning::NOTEINDEXTYPE;
  29. // CTuningDialog dialog
  30. CTuningDialog::CTuningDialog(CWnd* pParent, INSTRUMENTINDEX inst, CSoundFile &csf)
  31. : CDialog(CTuningDialog::IDD, pParent),
  32. m_sndFile(csf),
  33. m_pActiveTuningCollection(NULL),
  34. m_TreeCtrlTuning(this),
  35. m_TreeItemTuningItemMap(s_notFoundItemTree, s_notFoundItemTuning),
  36. m_NoteEditApply(true),
  37. m_RatioEditApply(true),
  38. m_DoErrorExit(false)
  39. {
  40. m_TuningCollections.push_back(&(m_sndFile.GetTuneSpecificTunings()));
  41. m_TuningCollectionsNames[&(m_sndFile.GetTuneSpecificTunings())] = _T("Tunings");
  42. m_pActiveTuning = m_sndFile.Instruments[inst]->pTuning;
  43. m_RatioMapWnd.m_pTuning = m_pActiveTuning; //pTun is the tuning to show when dialog opens.
  44. }
  45. CTuningDialog::~CTuningDialog()
  46. {
  47. for(auto &tuningCol : m_TuningCollections)
  48. {
  49. if(IsDeletable(tuningCol))
  50. {
  51. delete tuningCol;
  52. tuningCol = nullptr;
  53. }
  54. }
  55. m_TuningCollections.clear();
  56. m_DeletableTuningCollections.clear();
  57. }
  58. HTREEITEM CTuningDialog::AddTreeItem(CTuningCollection* pTC, HTREEITEM parent, HTREEITEM insertAfter)
  59. {
  60. const HTREEITEM temp = m_TreeCtrlTuning.InsertItem((IsDeletable(pTC) ? CString(_T("loaded: ")) : CString()) + m_TuningCollectionsNames[pTC], parent, insertAfter);
  61. HTREEITEM temp2 = NULL;
  62. m_TreeItemTuningItemMap.AddPair(temp, TUNINGTREEITEM(pTC));
  63. for(const auto &tuning : *pTC)
  64. {
  65. temp2 = AddTreeItem(tuning.get(), temp, temp2);
  66. }
  67. m_TreeCtrlTuning.EnsureVisible(temp);
  68. return temp;
  69. }
  70. HTREEITEM CTuningDialog::AddTreeItem(CTuning* pT, HTREEITEM parent, HTREEITEM insertAfter)
  71. {
  72. const HTREEITEM temp = m_TreeCtrlTuning.InsertItem(mpt::ToCString(pT->GetName()), parent, insertAfter);
  73. m_TreeItemTuningItemMap.AddPair(temp, TUNINGTREEITEM(pT));
  74. m_TreeCtrlTuning.EnsureVisible(temp);
  75. return temp;
  76. }
  77. void CTuningDialog::DeleteTreeItem(CTuning* pT)
  78. {
  79. if(!pT)
  80. return;
  81. HTREEITEM temp = m_TreeItemTuningItemMap.GetMapping_21(TUNINGTREEITEM(pT));
  82. if(temp)
  83. {
  84. HTREEITEM nextitem = m_TreeCtrlTuning.GetNextItem(temp, TVGN_NEXT);
  85. if(!nextitem) nextitem = m_TreeCtrlTuning.GetNextItem(temp, TVGN_PREVIOUS);
  86. m_pActiveTuning = m_TreeItemTuningItemMap.GetMapping_12(nextitem).GetT();
  87. m_TreeCtrlTuning.DeleteItem(temp);
  88. //Note: Item from map is deleted 'automatically' in
  89. //OnTvnDeleteitemTreeTuning.
  90. }
  91. }
  92. void CTuningDialog::DeleteTreeItem(CTuningCollection* pTC)
  93. {
  94. if(!pTC)
  95. return;
  96. m_pActiveTuning = nullptr;
  97. const HTREEITEM temp = m_TreeItemTuningItemMap.GetMapping_21(TUNINGTREEITEM(pTC));
  98. if(temp)
  99. {
  100. TUNINGTREEITEM prevTTI = m_TreeItemTuningItemMap.GetMapping_12(m_TreeCtrlTuning.GetNextItem(temp, TVGN_PREVIOUS));
  101. TUNINGTREEITEM nextTTI = m_TreeItemTuningItemMap.GetMapping_12(m_TreeCtrlTuning.GetNextItem(temp, TVGN_NEXT));
  102. CTuningCollection* pTCprev = prevTTI.GetTC();
  103. CTuningCollection* pTCnext = nextTTI.GetTC();
  104. if(pTCnext == nullptr)
  105. pTCnext = GetpTuningCollection(nextTTI.GetT());
  106. if(pTCprev == nullptr)
  107. pTCprev = GetpTuningCollection(prevTTI.GetT());
  108. if(pTCnext != nullptr && pTCnext != m_pActiveTuningCollection)
  109. m_pActiveTuningCollection = pTCnext;
  110. else
  111. {
  112. if(pTCprev != m_pActiveTuningCollection)
  113. m_pActiveTuningCollection = pTCprev;
  114. else
  115. m_pActiveTuningCollection = NULL;
  116. }
  117. m_TreeCtrlTuning.DeleteItem(temp);
  118. //Note: Item from map is deleted 'automatically' in
  119. //OnTvnDeleteitemTreeTuning.
  120. }
  121. else
  122. {
  123. ASSERT(false);
  124. m_DoErrorExit = true;
  125. m_pActiveTuningCollection = NULL;
  126. }
  127. }
  128. BOOL CTuningDialog::OnInitDialog()
  129. {
  130. CDialog::OnInitDialog();
  131. m_EditRatioPeriod.SubclassDlgItem(IDC_EDIT_RATIOPERIOD, this);
  132. m_EditRatio.SubclassDlgItem(IDC_EDIT_RATIOVALUE, this);
  133. m_EditRatioPeriod.AllowNegative(false);
  134. m_EditRatioPeriod.AllowFractions(true);
  135. m_EditRatio.AllowNegative(false);
  136. m_EditRatio.AllowFractions(true);
  137. m_RatioMapWnd.Init(this, 0);
  138. //-->Creating treeview
  139. m_TreeItemTuningItemMap.ClearMapping();
  140. for(const auto &tuningCol : m_TuningCollections)
  141. {
  142. AddTreeItem(tuningCol, NULL, NULL);
  143. }
  144. //<-- Creating treeview
  145. m_pActiveTuningCollection = GetpTuningCollection(m_pActiveTuning);
  146. //Adding tuning type names to corresponding combobox.
  147. m_CombobTuningType.SetItemData(m_CombobTuningType.AddString(_T("General")), static_cast<uint16>(Tuning::Type::GENERAL));
  148. m_CombobTuningType.SetItemData(m_CombobTuningType.AddString(_T("GroupGeometric")), static_cast<uint16>(Tuning::Type::GROUPGEOMETRIC));
  149. m_CombobTuningType.SetItemData(m_CombobTuningType.AddString(_T("Geometric")), static_cast<uint16>(Tuning::Type::GEOMETRIC));
  150. m_CombobTuningType.EnableWindow(FALSE);
  151. m_ButtonSet.EnableWindow(FALSE);
  152. m_EditSteps.SetLimitText(2);
  153. m_EditFineTuneSteps.SetLimitText(3);
  154. if(m_pActiveTuning) m_RatioMapWnd.m_nNote = m_RatioMapWnd.m_nNoteCentre + m_pActiveTuning->GetNoteRange().first + (m_pActiveTuning->GetNoteRange().last - m_pActiveTuning->GetNoteRange().first)/2 + 1;
  155. UpdateView();
  156. return TRUE;
  157. }
  158. bool CTuningDialog::CanEdit(CTuning * pT, CTuningCollection * pTC) const
  159. {
  160. if(!pT)
  161. {
  162. return false;
  163. }
  164. if(!pTC)
  165. {
  166. return false;
  167. }
  168. if(pTC != m_TuningCollections[0])
  169. {
  170. return false;
  171. }
  172. return true;
  173. }
  174. bool CTuningDialog::CanEdit(CTuningCollection * pTC) const
  175. {
  176. if(!pTC)
  177. {
  178. return false;
  179. }
  180. if(pTC != m_TuningCollections[0])
  181. {
  182. return false;
  183. }
  184. return true;
  185. }
  186. void CTuningDialog::UpdateView(const int updateMask)
  187. {
  188. if(m_DoErrorExit)
  189. {
  190. DoErrorExit();
  191. return;
  192. }
  193. //-->Updating treeview
  194. if(updateMask != UM_TUNINGDATA)
  195. {
  196. TUNINGTREEITEM tuningitem;
  197. if(m_pActiveTuning)
  198. tuningitem.Set(m_pActiveTuning);
  199. else
  200. {
  201. if(m_pActiveTuningCollection)
  202. tuningitem.Set(m_pActiveTuningCollection);
  203. }
  204. HTREEITEM treeitem = m_TreeItemTuningItemMap.GetMapping_21(tuningitem);
  205. if(treeitem)
  206. {
  207. m_TreeCtrlTuning.Select(treeitem, TVGN_CARET);
  208. if(m_pActiveTuning)
  209. m_TreeCtrlTuning.SetItemText(treeitem, mpt::ToCString(m_pActiveTuning->GetName()));
  210. else
  211. m_TreeCtrlTuning.SetItemText(treeitem, (IsDeletable(m_pActiveTuningCollection) ? CString(_T("loaded: ")) : CString()) + m_TuningCollectionsNames[m_pActiveTuningCollection]);
  212. }
  213. }
  214. //<--Updating treeview
  215. if(m_pActiveTuningCollection == NULL)
  216. {
  217. return;
  218. }
  219. m_ButtonNew.EnableWindow(TRUE);
  220. m_ButtonImport.EnableWindow(TRUE);
  221. m_ButtonExport.EnableWindow((m_pActiveTuning || m_pActiveTuningCollection) ? TRUE : FALSE);
  222. m_ButtonRemove.EnableWindow(((m_pActiveTuning && (m_pActiveTuningCollection == m_TuningCollections[0])) || (!m_pActiveTuning && m_pActiveTuningCollection && m_pActiveTuningCollection != m_TuningCollections[0])) ? TRUE : FALSE);
  223. //Updating tuning part-->
  224. if(m_pActiveTuning != NULL && (updateMask & UM_TUNINGDATA || updateMask == 0))
  225. {
  226. UpdateTuningType();
  227. m_EditName.SetWindowText(mpt::ToCString(m_pActiveTuning->GetName()));
  228. m_EditName.Invalidate();
  229. //Finetunesteps-edit
  230. m_EditFineTuneSteps.SetWindowText(mpt::cfmt::val(m_pActiveTuning->GetFineStepCount()));
  231. m_EditFineTuneSteps.Invalidate();
  232. //Making sure that ratiomap window is showing and
  233. //updating its content.
  234. m_RatioMapWnd.ShowWindow(SW_SHOW);
  235. m_RatioMapWnd.m_pTuning = m_pActiveTuning;
  236. m_RatioMapWnd.Invalidate();
  237. UpdateRatioMapEdits(m_RatioMapWnd.GetShownCentre());
  238. const UNOTEINDEXTYPE period = m_pActiveTuning->GetGroupSize();
  239. const RATIOTYPE GroupRatio = m_pActiveTuning->GetGroupRatio();
  240. if(m_pActiveTuning->GetType() == Tuning::Type::GROUPGEOMETRIC || m_pActiveTuning->GetType() == Tuning::Type::GEOMETRIC)
  241. {
  242. m_EditSteps.EnableWindow(TRUE);
  243. m_EditRatioPeriod.EnableWindow(TRUE);
  244. m_EditSteps.SetWindowText(mpt::cfmt::val(period));
  245. m_EditRatioPeriod.SetWindowText(mpt::cfmt::flt(GroupRatio, 6));
  246. } else
  247. {
  248. m_EditSteps.EnableWindow(FALSE);
  249. m_EditRatioPeriod.EnableWindow(FALSE);
  250. m_EditSteps.SetWindowText(_T(""));
  251. m_EditRatioPeriod.SetWindowText(_T(""));
  252. }
  253. m_EditRatioPeriod.Invalidate();
  254. m_EditSteps.Invalidate();
  255. bool enableControls = CanEdit(m_pActiveTuning, m_pActiveTuningCollection);
  256. m_CombobTuningType.EnableWindow(FALSE);
  257. m_EditSteps.SetReadOnly(!enableControls);
  258. m_EditRatioPeriod.SetReadOnly(!enableControls);
  259. m_EditRatio.SetReadOnly((m_pActiveTuning->GetType() == Tuning::Type::GEOMETRIC) ? TRUE : !enableControls);
  260. m_EditNotename.SetReadOnly(!enableControls);
  261. m_EditMiscActions.SetReadOnly((m_pActiveTuning->GetType() == Tuning::Type::GEOMETRIC) ? TRUE : !enableControls);
  262. m_EditFineTuneSteps.SetReadOnly(!enableControls);
  263. m_EditName.SetReadOnly(!enableControls);
  264. m_ButtonSet.EnableWindow((m_pActiveTuning->GetType() == Tuning::Type::GEOMETRIC) ? FALSE : enableControls);
  265. m_CombobTuningType.Invalidate();
  266. m_EditSteps.Invalidate();
  267. m_EditRatioPeriod.Invalidate();
  268. }
  269. else
  270. {
  271. if(m_pActiveTuning == NULL) //No active tuning, clearing tuning part.
  272. {
  273. m_EditName.SetWindowText(_T(""));
  274. m_EditSteps.SetWindowText(_T(""));
  275. m_EditRatioPeriod.SetWindowText(_T(""));
  276. m_EditRatio.SetWindowText(_T(""));
  277. m_EditNotename.SetWindowText(_T(""));
  278. m_EditMiscActions.SetWindowText(_T(""));
  279. m_EditFineTuneSteps.SetWindowText(_T(""));
  280. m_EditName.SetWindowText(_T(""));
  281. m_CombobTuningType.SetCurSel(-1);
  282. m_RatioMapWnd.ShowWindow(SW_HIDE);
  283. m_RatioMapWnd.m_pTuning = NULL;
  284. m_RatioMapWnd.Invalidate();
  285. }
  286. }
  287. //<--Updating tuning part
  288. }
  289. void CTuningDialog::DoDataExchange(CDataExchange* pDX)
  290. {
  291. CDialog::DoDataExchange(pDX);
  292. DDX_Control(pDX, IDC_STATICRATIOMAP, m_RatioMapWnd);
  293. DDX_Control(pDX, IDC_COMBO_TTYPE, m_CombobTuningType);
  294. DDX_Control(pDX, IDC_EDIT_STEPS, m_EditSteps);
  295. DDX_Control(pDX, IDC_EDIT_NOTENAME, m_EditNotename);
  296. DDX_Control(pDX, IDC_BUTTON_SETVALUES, m_ButtonSet);
  297. DDX_Control(pDX, IDC_BUTTON_TUNING_NEW, m_ButtonNew);
  298. DDX_Control(pDX, IDC_BUTTON_IMPORT, m_ButtonImport);
  299. DDX_Control(pDX, IDC_BUTTON_EXPORT, m_ButtonExport);
  300. DDX_Control(pDX, IDC_BUTTON_TUNING_REMOVE, m_ButtonRemove);
  301. DDX_Control(pDX, IDC_EDIT_MISC_ACTIONS, m_EditMiscActions);
  302. DDX_Control(pDX, IDC_EDIT_FINETUNESTEPS, m_EditFineTuneSteps);
  303. DDX_Control(pDX, IDC_EDIT_NAME, m_EditName);
  304. DDX_Control(pDX, IDC_TREE_TUNING, m_TreeCtrlTuning);
  305. }
  306. BEGIN_MESSAGE_MAP(CTuningDialog, CDialog)
  307. ON_EN_CHANGE(IDC_EDIT_STEPS, &CTuningDialog::OnEnChangeEditSteps)
  308. ON_EN_CHANGE(IDC_EDIT_RATIOPERIOD, &CTuningDialog::OnEnChangeEditRatioperiod)
  309. ON_EN_CHANGE(IDC_EDIT_NOTENAME, &CTuningDialog::OnEnChangeEditNotename)
  310. ON_BN_CLICKED(IDC_BUTTON_SETVALUES, &CTuningDialog::OnBnClickedButtonSetvalues)
  311. ON_EN_CHANGE(IDC_EDIT_RATIOVALUE, &CTuningDialog::OnEnChangeEditRatiovalue)
  312. ON_BN_CLICKED(IDC_BUTTON_TUNING_NEW, &CTuningDialog::OnBnClickedButtonNew)
  313. ON_BN_CLICKED(IDC_BUTTON_IMPORT, &CTuningDialog::OnBnClickedButtonImport)
  314. ON_BN_CLICKED(IDC_BUTTON_EXPORT, &CTuningDialog::OnBnClickedButtonExport)
  315. ON_BN_CLICKED(IDC_BUTTON_TUNING_REMOVE, &CTuningDialog::OnBnClickedButtonRemove)
  316. ON_EN_CHANGE(IDC_EDIT_FINETUNESTEPS, &CTuningDialog::OnEnChangeEditFinetunesteps)
  317. ON_EN_KILLFOCUS(IDC_EDIT_FINETUNESTEPS, &CTuningDialog::OnEnKillfocusEditFinetunesteps)
  318. ON_EN_KILLFOCUS(IDC_EDIT_NAME, &CTuningDialog::OnEnKillfocusEditName)
  319. ON_EN_KILLFOCUS(IDC_EDIT_STEPS, &CTuningDialog::OnEnKillfocusEditSteps)
  320. ON_EN_KILLFOCUS(IDC_EDIT_RATIOPERIOD, &CTuningDialog::OnEnKillfocusEditRatioperiod)
  321. ON_EN_KILLFOCUS(IDC_EDIT_RATIOVALUE, &CTuningDialog::OnEnKillfocusEditRatiovalue)
  322. ON_EN_KILLFOCUS(IDC_EDIT_NOTENAME, &CTuningDialog::OnEnKillfocusEditNotename)
  323. ON_NOTIFY(TVN_SELCHANGED, IDC_TREE_TUNING, &CTuningDialog::OnTvnSelchangedTreeTuning)
  324. ON_NOTIFY(TVN_DELETEITEM, IDC_TREE_TUNING, &CTuningDialog::OnTvnDeleteitemTreeTuning)
  325. ON_NOTIFY(NM_RCLICK, IDC_TREE_TUNING, &CTuningDialog::OnNMRclickTreeTuning)
  326. ON_NOTIFY(TVN_BEGINDRAG, IDC_TREE_TUNING, &CTuningDialog::OnTvnBegindragTreeTuning)
  327. ON_COMMAND(ID_REMOVETUNING, &CTuningDialog::OnRemoveTuning)
  328. ON_COMMAND(ID_ADDTUNINGGENERAL, &CTuningDialog::OnAddTuningGeneral)
  329. ON_COMMAND(ID_ADDTUNINGGROUPGEOMETRIC, &CTuningDialog::OnAddTuningGroupGeometric)
  330. ON_COMMAND(ID_ADDTUNINGGEOMETRIC, &CTuningDialog::OnAddTuningGeometric)
  331. ON_COMMAND(ID_COPYTUNING, &CTuningDialog::OnCopyTuning)
  332. ON_COMMAND(ID_REMOVETUNINGCOLLECTION, &CTuningDialog::OnRemoveTuningCollection)
  333. END_MESSAGE_MAP()
  334. void CTuningDialog::DoErrorExit()
  335. {
  336. m_DoErrorExit = false;
  337. m_pActiveTuning = NULL;
  338. m_pActiveTuningCollection = NULL;
  339. Reporting::Message(LogInformation, _T("Dialog encountered an error and needs to close"), this);
  340. OnOK();
  341. }
  342. // CTuningDialog message handlers
  343. void CTuningDialog::UpdateTuningType()
  344. {
  345. if(m_pActiveTuning)
  346. {
  347. if(m_CombobTuningType.GetCount() < 3) m_DoErrorExit = true;
  348. if(m_pActiveTuning->GetType() == Tuning::Type::GEOMETRIC)
  349. m_CombobTuningType.SetCurSel(2);
  350. else
  351. if(m_pActiveTuning->GetType() == Tuning::Type::GROUPGEOMETRIC)
  352. m_CombobTuningType.SetCurSel(1);
  353. else
  354. m_CombobTuningType.SetCurSel(0);
  355. }
  356. }
  357. bool CTuningDialog::AddTuning(CTuningCollection* pTC, Tuning::Type type)
  358. {
  359. if(!pTC)
  360. {
  361. Reporting::Notification("No tuning collection chosen");
  362. return false;
  363. }
  364. std::unique_ptr<CTuning> pNewTuning;
  365. if(type == Tuning::Type::GROUPGEOMETRIC)
  366. {
  367. std::vector<Tuning::RATIOTYPE> ratios;
  368. for(Tuning::NOTEINDEXTYPE n = 0; n < 12; ++n)
  369. {
  370. ratios.push_back(std::pow(static_cast<Tuning::RATIOTYPE>(2.0), static_cast<Tuning::RATIOTYPE>(n) / static_cast<Tuning::RATIOTYPE>(12)));
  371. }
  372. pNewTuning = CTuning::CreateGroupGeometric(U_("Unnamed"), ratios, 2, 15);
  373. } else if(type == Tuning::Type::GEOMETRIC)
  374. {
  375. pNewTuning = CTuning::CreateGeometric(U_("Unnamed"), 12, 2, 15);
  376. } else
  377. {
  378. pNewTuning = CTuning::CreateGeneral(U_("Unnamed"));
  379. }
  380. CTuning *pT = pTC->AddTuning(std::move(pNewTuning));
  381. if(!pT)
  382. {
  383. Reporting::Notification("Add tuning failed");
  384. return false;
  385. }
  386. AddTreeItem(pT, m_TreeItemTuningItemMap.GetMapping_21(TUNINGTREEITEM(pTC)), NULL);
  387. m_pActiveTuning = pT;
  388. m_ModifiedTCs[pTC] = true;
  389. UpdateView();
  390. return true;
  391. }
  392. void CTuningDialog::OnEnChangeEditSteps()
  393. {
  394. }
  395. void CTuningDialog::OnEnChangeEditRatioperiod()
  396. {
  397. }
  398. void CTuningDialog::OnEnChangeEditNotename()
  399. {
  400. if(!m_NoteEditApply)
  401. {
  402. m_NoteEditApply = true;
  403. return;
  404. }
  405. if(!m_pActiveTuning)
  406. return;
  407. const NOTEINDEXTYPE currentNote = m_RatioMapWnd.GetShownCentre();
  408. CString buffer;
  409. m_EditNotename.GetWindowText(buffer);
  410. mpt::ustring str = mpt::ToUnicode(buffer);
  411. {
  412. if(str.size() > 3)
  413. str.resize(3);
  414. m_pActiveTuning->SetNoteName(currentNote, str);
  415. }
  416. m_ModifiedTCs[GetpTuningCollection(m_pActiveTuning)] = true;
  417. m_RatioMapWnd.Invalidate();
  418. }
  419. void CTuningDialog::OnEnChangeEditRatiovalue()
  420. {
  421. if(!m_RatioEditApply)
  422. {
  423. m_RatioEditApply = true;
  424. return;
  425. }
  426. if(!m_pActiveTuning)
  427. return;
  428. const NOTEINDEXTYPE currentNote = m_RatioMapWnd.GetShownCentre();
  429. double ratio = 0.0;
  430. if(m_EditRatio.GetDecimalValue(ratio))
  431. {
  432. m_pActiveTuning->SetRatio(currentNote, static_cast<RATIOTYPE>(ratio));
  433. m_ModifiedTCs[GetpTuningCollection(m_pActiveTuning)] = true;
  434. UpdateTuningType();
  435. m_RatioMapWnd.Invalidate();
  436. }
  437. }
  438. void CTuningDialog::OnBnClickedButtonSetvalues()
  439. {
  440. if(m_pActiveTuning)
  441. {
  442. if(m_EditMiscActions.GetWindowTextLength() < 1)
  443. return;
  444. CString buffer;
  445. m_EditMiscActions.GetWindowText(buffer);
  446. m_pActiveTuning->Multiply(ConvertStrTo<RATIOTYPE>(buffer));
  447. m_ModifiedTCs[GetpTuningCollection(m_pActiveTuning)] = true;
  448. m_EditMiscActions.SetWindowText(_T(""));
  449. m_RatioMapWnd.Invalidate();
  450. }
  451. }
  452. void CTuningDialog::UpdateRatioMapEdits(const NOTEINDEXTYPE& note)
  453. {
  454. if(m_pActiveTuning == NULL)
  455. return;
  456. m_RatioEditApply = false;
  457. m_EditRatio.SetWindowText(mpt::cfmt::val(m_pActiveTuning->GetRatio(note)));
  458. m_NoteEditApply = false;
  459. m_EditNotename.SetWindowText(mpt::ToCString(m_pActiveTuning->GetNoteName(note, false)));
  460. m_EditRatio.Invalidate();
  461. m_EditNotename.Invalidate();
  462. }
  463. void CTuningDialog::OnBnClickedButtonNew()
  464. {
  465. POINT point;
  466. GetCursorPos(&point);
  467. HMENU popUpMenu = CreatePopupMenu();
  468. if(popUpMenu == NULL) return;
  469. AppendMenu(popUpMenu, MF_STRING, ID_ADDTUNINGGROUPGEOMETRIC, _T("Add &GroupGeometric tuning"));
  470. AppendMenu(popUpMenu, MF_STRING, ID_ADDTUNINGGEOMETRIC, _T("Add G&eometric tuning"));
  471. AppendMenu(popUpMenu, MF_STRING, ID_ADDTUNINGGENERAL, _T("Add Ge&neral tuning"));
  472. m_CommandItemDest.Set(m_TuningCollections[0]);
  473. TrackPopupMenu(popUpMenu, TPM_LEFTALIGN|TPM_RIGHTBUTTON, point.x, point.y, 0, m_hWnd, NULL);
  474. DestroyMenu(popUpMenu);
  475. }
  476. void CTuningDialog::OnBnClickedButtonExport()
  477. {
  478. if(m_pActiveTuning == NULL && m_pActiveTuningCollection == NULL)
  479. {
  480. Reporting::Message(LogInformation, _T("Operation failed - No tuning file selected."), this);
  481. return;
  482. }
  483. bool failure = true;
  484. if(m_pActiveTuning)
  485. {
  486. const CTuning* pT = m_pActiveTuning;
  487. std::string filter;
  488. int filters = 0;
  489. int tuningFilter = -1;
  490. int sclFilter = -1;
  491. {
  492. filters++;
  493. filter += std::string("Tuning files (*") + CTuning::s_FileExtension + std::string(")|*") + CTuning::s_FileExtension + std::string("|");
  494. tuningFilter = filters;
  495. }
  496. {
  497. filters++;
  498. filter += std::string("Scala scale (*.scl)|*") + std::string(".scl")+ std::string("|");
  499. sclFilter = filters;
  500. }
  501. int filterIndex = 0;
  502. FileDialog dlg = SaveFileDialog()
  503. .DefaultExtension(CTuning::s_FileExtension)
  504. .ExtensionFilter(filter)
  505. .WorkingDirectory(TrackerSettings::Instance().PathTunings.GetWorkingDir())
  506. .FilterIndex(&filterIndex);
  507. if (!dlg.Show(this)) return;
  508. BeginWaitCursor();
  509. try
  510. {
  511. mpt::SafeOutputFile sfout(dlg.GetFirstFile(), std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
  512. mpt::ofstream &fout = sfout;
  513. fout.exceptions(fout.exceptions() | std::ios::badbit | std::ios::failbit);
  514. if(tuningFilter != -1 && filterIndex == tuningFilter)
  515. {
  516. failure = (pT->Serialize(fout) != Tuning::SerializationResult::Success);
  517. } else if(sclFilter != -1 && filterIndex == sclFilter)
  518. {
  519. failure = !pT->WriteSCL(fout, dlg.GetFirstFile());
  520. if(!failure)
  521. {
  522. if(m_pActiveTuning->GetType() == Tuning::Type::GENERAL)
  523. {
  524. Reporting::Message(LogWarning, _T("The Scala SCL file format does not contain enough information to represent General Tunings without data loss.\n\nOpenMPT exported as much information as possible, but other software as well as OpenMPT itself will not be able to re-import the just exported Scala SCL in a way that resembles the original data completely.\n\nPlease consider additionally exporting the Tuning as an OpenMPT .tun file."), _T("Tuning - Incompatible export"), this);
  525. }
  526. }
  527. }
  528. } catch(const std::exception &)
  529. {
  530. failure = true;
  531. }
  532. EndWaitCursor();
  533. } else
  534. {
  535. const CTuningCollection* pTC = m_pActiveTuningCollection;
  536. std::string filter = std::string("Multiple Tuning files (") + CTuning::s_FileExtension + std::string(")|*") + CTuning::s_FileExtension + std::string("|");
  537. mpt::PathString fileName;
  538. if(!m_TuningCollectionsFilenames[pTC].empty())
  539. {
  540. fileName = m_TuningCollectionsFilenames[pTC] + P_(" - ");
  541. }
  542. if(!m_TuningCollectionsNames[pTC].IsEmpty())
  543. {
  544. mpt::PathString name = mpt::PathString::FromUnicode(mpt::ToUnicode(m_TuningCollectionsNames[pTC]));
  545. SanitizeFilename(name);
  546. fileName += name + P_(" - ");
  547. }
  548. fileName += P_("%tuning_number% - %tuning_name%");
  549. int filterIndex = 0;
  550. FileDialog dlg = SaveFileDialog()
  551. .DefaultExtension(CTuning::s_FileExtension)
  552. .ExtensionFilter(filter)
  553. .WorkingDirectory(TrackerSettings::Instance().PathTunings.GetWorkingDir())
  554. .FilterIndex(&filterIndex);
  555. dlg.DefaultFilename(fileName);
  556. if (!dlg.Show(this)) return;
  557. BeginWaitCursor();
  558. failure = false;
  559. auto numberFmt = mpt::FormatSpec().Dec().FillNul().Width(1 + static_cast<int>(std::log10(pTC->GetNumTunings())));
  560. for(std::size_t i = 0; i < pTC->GetNumTunings(); ++i)
  561. {
  562. const CTuning & tuning = *(pTC->GetTuning(i));
  563. fileName = dlg.GetFirstFile();
  564. mpt::ustring tuningName = mpt::ToUnicode(tuning.GetName());
  565. if(tuningName.empty())
  566. {
  567. tuningName = U_("untitled");
  568. }
  569. mpt::ustring fileNameW = fileName.ToUnicode();
  570. mpt::ustring numberW = mpt::ufmt::fmt(i + 1, numberFmt);
  571. SanitizeFilename(numberW);
  572. fileNameW = mpt::String::Replace(fileNameW, U_("%tuning_number%"), numberW);
  573. mpt::ustring nameW = mpt::ToUnicode(tuningName);
  574. SanitizeFilename(nameW);
  575. fileNameW = mpt::String::Replace(fileNameW, U_("%tuning_name%"), nameW);
  576. fileName = mpt::PathString::FromUnicode(fileNameW);
  577. try
  578. {
  579. mpt::SafeOutputFile sfout(fileName, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
  580. mpt::ofstream &fout = sfout;
  581. fout.exceptions(fout.exceptions() | std::ios::badbit | std::ios::failbit);
  582. if(tuning.Serialize(fout) != Tuning::SerializationResult::Success)
  583. {
  584. failure = true;
  585. }
  586. } catch(const std::exception &)
  587. {
  588. failure = true;
  589. }
  590. }
  591. EndWaitCursor();
  592. }
  593. if(failure)
  594. {
  595. Reporting::Message(LogError, _T("Export failed"), _T("Error!"), this);
  596. }
  597. }
  598. void CTuningDialog::OnBnClickedButtonRemove()
  599. {
  600. if(m_pActiveTuning)
  601. {
  602. if(CanEdit(m_pActiveTuning, m_pActiveTuningCollection))
  603. {
  604. m_CommandItemDest.Set(m_pActiveTuning);
  605. OnRemoveTuning();
  606. }
  607. } else if(m_pActiveTuningCollection)
  608. {
  609. if(IsDeletable(m_pActiveTuningCollection))
  610. {
  611. m_CommandItemDest.Set(m_pActiveTuningCollection);
  612. OnRemoveTuningCollection();
  613. }
  614. }
  615. }
  616. template <typename Tfile, std::size_t N> static bool CheckMagic(Tfile &f, mpt::IO::Offset offset, const uint8(&magic)[N])
  617. {
  618. if(!mpt::IO::SeekAbsolute(f, offset))
  619. {
  620. return false;
  621. }
  622. uint8 buffer[N];
  623. MemsetZero(buffer);
  624. if(mpt::IO::ReadRaw(f, buffer, N).size() != N)
  625. {
  626. return false;
  627. }
  628. bool result = (std::memcmp(magic, buffer, N) == 0);
  629. mpt::IO::SeekBegin(f);
  630. return result;
  631. }
  632. void CTuningDialog::OnBnClickedButtonImport()
  633. {
  634. std::string sFilter = MPT_AFORMAT("Tuning files (*{}, *{}, *.scl)|*{};*{};*.scl|")(
  635. CTuning::s_FileExtension,
  636. CTuningCollection::s_FileExtension,
  637. CTuning::s_FileExtension,
  638. CTuningCollection::s_FileExtension);
  639. FileDialog dlg = OpenFileDialog()
  640. .AllowMultiSelect()
  641. .ExtensionFilter(sFilter)
  642. .WorkingDirectory(TrackerSettings::Instance().PathTunings.GetWorkingDir());
  643. if(!dlg.Show(this))
  644. return;
  645. TrackerSettings::Instance().PathTunings.SetWorkingDir(dlg.GetWorkingDirectory());
  646. mpt::ustring sLoadReport;
  647. const auto &files = dlg.GetFilenames();
  648. for(const auto &file : files)
  649. {
  650. mpt::PathString fileName;
  651. mpt::PathString fileExt;
  652. file.SplitPath(nullptr, nullptr, &fileName, &fileExt);
  653. const mpt::ustring fileNameExt = (fileName + fileExt).ToUnicode();
  654. const bool bIsTun = (mpt::PathString::CompareNoCase(fileExt, mpt::PathString::FromUTF8(CTuning::s_FileExtension)) == 0);
  655. const bool bIsScl = (mpt::PathString::CompareNoCase(fileExt, P_(".scl")) == 0);
  656. //const bool bIsTc = (mpt::PathString::CompareNoCase(fileExt, mpt::PathString::FromUTF8(CTuningCollection::s_FileExtension)) == 0);
  657. mpt::ifstream fin(file, std::ios::binary);
  658. // "HSCT", 0x01, 0x00, 0x00, 0x00
  659. const uint8 magicTColdV1 [] = { 'H', 'S', 'C', 'T',0x01,0x00,0x00,0x00 };
  660. // "HSCT", 0x02, 0x00, 0x00, 0x00
  661. const uint8 magicTColdV2 [] = { 'H', 'S', 'C', 'T',0x02,0x00,0x00,0x00 };
  662. // "CTRTI_B.", 0x03, 0x00
  663. const uint8 magicTUNoldV2[] = { 'C', 'T', 'R', 'T', 'I', '_', 'B', '.',0x02,0x00 };
  664. // "CTRTI_B.", 0x03, 0x00
  665. const uint8 magicTUNoldV3[] = { 'C', 'T', 'R', 'T', 'I', '_', 'B', '.',0x03,0x00 };
  666. // "228", 0x02, "TC"
  667. const uint8 magicTC [] = { '2', '2', '8',0x02, 'T', 'C' };
  668. // "228", 0x09, "CTB244RTI"
  669. const uint8 magicTUN [] = { '2', '2', '8',0x09, 'C', 'T', 'B', '2', '4', '4', 'R', 'T', 'I' };
  670. CTuningCollection *pTC = nullptr;
  671. CString tcName;
  672. mpt::PathString tcFilename;
  673. std::unique_ptr<CTuning> pT;
  674. if(bIsTun && CheckMagic(fin, 0, magicTC))
  675. {
  676. // OpenMPT since r3115 wrongly wrote .tc files instead of .tun files when exporting.
  677. // If such a file is detected and only contains a single Tuning, we can work-around that.
  678. // For .tc files containing multiple Tunings, we sadly cannot decide which one the user wanted.
  679. // In that case, we import as a Collection (an alternative might be to display a dialog in this case).
  680. pTC = new CTuningCollection();
  681. mpt::ustring name;
  682. if(pTC->Deserialize(fin, name, TuningCharsetFallback) == Tuning::SerializationResult::Success)
  683. { // success
  684. if(pTC->GetNumTunings() == 1)
  685. {
  686. Reporting::Message(LogInformation, U_("- Tuning Collection with a Tuning file extension (.tun) detected. It only contains a single Tuning, importing the file as a Tuning.\n"), this);
  687. pT = std::unique_ptr<CTuning>(new CTuning(*(pTC->GetTuning(0))));
  688. delete pTC;
  689. pTC = nullptr;
  690. // ok
  691. } else
  692. {
  693. Reporting::Message(LogNotification, U_("- Tuning Collection with a Tuning file extension (.tun) detected. It only contains multiple Tunings, importing the file as a Tuning Collection.\n"), this);
  694. // ok
  695. }
  696. } else
  697. {
  698. delete pTC;
  699. pTC = nullptr;
  700. // fail
  701. }
  702. } else if(CheckMagic(fin, 0, magicTC) || CheckMagic(fin, 0, magicTColdV2) || CheckMagic(fin, 0, magicTColdV1))
  703. {
  704. pTC = new CTuningCollection();
  705. mpt::ustring name;
  706. if(pTC->Deserialize(fin, name, TuningCharsetFallback) != Tuning::SerializationResult::Success)
  707. { // failure
  708. delete pTC;
  709. pTC = nullptr;
  710. // fail
  711. } else
  712. {
  713. tcName = mpt::ToCString(name);
  714. tcFilename = file;
  715. // ok
  716. }
  717. } else if(CheckMagic(fin, 0, magicTUNoldV3) || CheckMagic(fin, 0, magicTUNoldV2))
  718. {
  719. pT = CTuning::CreateDeserializeOLD(fin, TuningCharsetFallback);
  720. } else if(CheckMagic(fin, 0, magicTUN))
  721. {
  722. pT = CTuning::CreateDeserialize(fin, TuningCharsetFallback);
  723. } else if(bIsScl)
  724. {
  725. EnSclImport a = ImportScl(file, fileName.ToUnicode(), pT);
  726. if(a != enSclImportOk)
  727. { // failure
  728. pT = nullptr;
  729. }
  730. }
  731. bool success = false;
  732. if(pT)
  733. {
  734. CTuningCollection &tc = *m_TuningCollections.front();
  735. CTuning *activeTuning = tc.AddTuning(std::move(pT));
  736. if(!activeTuning)
  737. {
  738. if(tc.GetNumTunings() >= CTuningCollection::s_nMaxTuningCount)
  739. {
  740. sLoadReport += MPT_UFORMAT("- Failed to load file \"{}\": maximum number({}) of temporary tunings is already open.\n")(fileNameExt, static_cast<std::size_t>(CTuningCollection::s_nMaxTuningCount));
  741. } else
  742. {
  743. sLoadReport += MPT_UFORMAT("- Unable to import file \"{}\": unknown reason.\n")(fileNameExt);
  744. }
  745. } else
  746. {
  747. m_pActiveTuning = activeTuning;
  748. AddTreeItem(m_pActiveTuning, m_TreeItemTuningItemMap.GetMapping_21(TUNINGTREEITEM(&tc)), NULL);
  749. success = true;
  750. }
  751. }
  752. if(pTC)
  753. {
  754. m_TuningCollections.push_back(pTC);
  755. m_TuningCollectionsNames[pTC] = tcName;
  756. m_TuningCollectionsFilenames[pTC] = tcFilename;
  757. m_DeletableTuningCollections.push_back(pTC);
  758. AddTreeItem(pTC, NULL, NULL);
  759. success = true;
  760. }
  761. if(!success)
  762. {
  763. sLoadReport += MPT_UFORMAT("- Unable to load \"{}\": unrecognized file format.\n")(fileNameExt);
  764. }
  765. }
  766. if(sLoadReport.length() > 0)
  767. Reporting::Information(sLoadReport);
  768. UpdateView();
  769. }
  770. void CTuningDialog::OnEnChangeEditFinetunesteps()
  771. {
  772. }
  773. void CTuningDialog::OnEnKillfocusEditFinetunesteps()
  774. {
  775. if(m_pActiveTuning)
  776. {
  777. CString buffer;
  778. m_EditFineTuneSteps.GetWindowText(buffer);
  779. m_pActiveTuning->SetFineStepCount(ConvertStrTo<Tuning::USTEPINDEXTYPE>(buffer));
  780. m_EditFineTuneSteps.SetWindowText(mpt::cfmt::val(m_pActiveTuning->GetFineStepCount()));
  781. m_ModifiedTCs[GetpTuningCollection(m_pActiveTuning)] = true;
  782. m_EditFineTuneSteps.Invalidate();
  783. }
  784. }
  785. void CTuningDialog::OnEnKillfocusEditName()
  786. {
  787. if(m_pActiveTuning != NULL)
  788. {
  789. CString buffer;
  790. m_EditName.GetWindowText(buffer);
  791. m_pActiveTuning->SetName(mpt::ToUnicode(buffer));
  792. m_ModifiedTCs[GetpTuningCollection(m_pActiveTuning)] = true;
  793. UpdateView(UM_TUNINGDATA);
  794. UpdateView(UM_TUNINGCOLLECTION);
  795. }
  796. }
  797. void CTuningDialog::OnEnKillfocusEditSteps()
  798. {
  799. if(m_pActiveTuning)
  800. {
  801. CString buffer;
  802. m_EditSteps.GetWindowText(buffer);
  803. m_pActiveTuning->ChangeGroupsize(ConvertStrTo<UNOTEINDEXTYPE>(buffer));
  804. m_ModifiedTCs[GetpTuningCollection(m_pActiveTuning)] = true;
  805. UpdateView(UM_TUNINGDATA);
  806. }
  807. }
  808. void CTuningDialog::OnEnKillfocusEditRatioperiod()
  809. {
  810. double ratio = 0.0;
  811. if(m_pActiveTuning && m_EditRatioPeriod.GetDecimalValue(ratio))
  812. {
  813. m_pActiveTuning->ChangeGroupRatio(static_cast<RATIOTYPE>(ratio));
  814. m_ModifiedTCs[GetpTuningCollection(m_pActiveTuning)] = true;
  815. UpdateView(UM_TUNINGDATA);
  816. }
  817. }
  818. void CTuningDialog::OnEnKillfocusEditRatiovalue()
  819. {
  820. UpdateView(UM_TUNINGDATA);
  821. }
  822. void CTuningDialog::OnEnKillfocusEditNotename()
  823. {
  824. UpdateView(UM_TUNINGDATA);
  825. }
  826. bool CTuningDialog::GetModifiedStatus(const CTuningCollection* const pTc) const
  827. {
  828. auto iter = m_ModifiedTCs.find(pTc);
  829. if(iter != m_ModifiedTCs.end())
  830. return (*iter).second;
  831. else
  832. return false;
  833. }
  834. CTuningCollection* CTuningDialog::GetpTuningCollection(HTREEITEM ti) const
  835. {
  836. //If treeitem is that of a tuningcollection, return address of
  837. //that tuning collection. If treeitem is that of a tuning, return
  838. //the owning tuningcollection
  839. TUNINGTREEITEM tunItem = m_TreeItemTuningItemMap.GetMapping_12(ti);
  840. CTuningCollection* pTC = tunItem.GetTC();
  841. if(pTC)
  842. return pTC;
  843. else
  844. {
  845. CTuning* pT = tunItem.GetT();
  846. return GetpTuningCollection(pT);
  847. }
  848. }
  849. CTuningCollection* CTuningDialog::GetpTuningCollection(const CTuning* const pT) const
  850. {
  851. for(auto &tuningCol : m_TuningCollections)
  852. {
  853. for(const auto &tuning : *tuningCol)
  854. {
  855. if(pT == tuning.get())
  856. {
  857. return tuningCol;
  858. }
  859. }
  860. }
  861. return NULL;
  862. }
  863. void CTuningDialog::OnTvnSelchangedTreeTuning(NMHDR *pNMHDR, LRESULT *pResult)
  864. {
  865. //This methods gets called when selected item in the treeview
  866. //changes.
  867. //TODO: This gets called before killfocus messages of edits, which
  868. // can be a problem.
  869. LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
  870. TUNINGTREEITEM ti = m_TreeItemTuningItemMap.GetMapping_12(pNMTreeView->itemNew.hItem);
  871. if(ti)
  872. {
  873. int updateMask = UM_TUNINGDATA;
  874. CTuningCollection* pPrevTuningCollection = m_pActiveTuningCollection;
  875. CTuning* pT = ti.GetT();
  876. CTuningCollection* pTC = ti.GetTC();
  877. if(pTC)
  878. {
  879. m_pActiveTuningCollection = pTC;
  880. ASSERT(pT == NULL);
  881. m_pActiveTuning = NULL;
  882. }
  883. else
  884. {
  885. m_pActiveTuning = pT;
  886. m_pActiveTuningCollection = GetpTuningCollection(m_pActiveTuning);
  887. }
  888. if(m_pActiveTuningCollection != pPrevTuningCollection) updateMask |= UM_TUNINGCOLLECTION;
  889. UpdateView(updateMask);
  890. }
  891. else
  892. {
  893. m_DoErrorExit = true;
  894. }
  895. *pResult = 0;
  896. }
  897. void CTuningDialog::OnTvnDeleteitemTreeTuning(NMHDR *pNMHDR, LRESULT *pResult)
  898. {
  899. LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
  900. *pResult = 0;
  901. if(pNMTreeView->itemOld.mask & TVIF_HANDLE && pNMTreeView->itemOld.hItem)
  902. {
  903. m_TreeItemTuningItemMap.RemoveValue_1(pNMTreeView->itemOld.hItem);
  904. }
  905. else
  906. m_DoErrorExit = true;
  907. }
  908. void CTuningDialog::OnNMRclickTreeTuning(NMHDR *, LRESULT *pResult)
  909. {
  910. *pResult = 0;
  911. HTREEITEM hItem;
  912. POINT point, ptClient;
  913. GetCursorPos(&point);
  914. ptClient = point;
  915. m_TreeCtrlTuning.ScreenToClient(&ptClient);
  916. hItem = m_TreeCtrlTuning.HitTest(ptClient, NULL);
  917. if(hItem == NULL)
  918. return;
  919. m_TreeCtrlTuning.Select(hItem, TVGN_CARET);
  920. TUNINGTREEITEM tunitem = m_TreeItemTuningItemMap.GetMapping_12(hItem);
  921. if(!tunitem)
  922. {
  923. m_DoErrorExit = true;
  924. return;
  925. }
  926. HMENU popUpMenu = CreatePopupMenu();
  927. if(popUpMenu == NULL) return;
  928. CTuning* pT = tunitem.GetT();
  929. CTuningCollection* pTC = tunitem.GetTC();
  930. if(pT) //Creating context menu for tuning-item
  931. {
  932. pTC = GetpTuningCollection(pT);
  933. if(pTC != NULL)
  934. {
  935. UINT mask = MF_STRING;
  936. if(!CanEdit(pT, pTC))
  937. {
  938. mask |= MF_GRAYED;
  939. }
  940. AppendMenu(popUpMenu, mask, ID_REMOVETUNING, _T("&Remove"));
  941. m_CommandItemDest.Set(pT);
  942. }
  943. }
  944. else //Creating context menu for tuning collection item.
  945. {
  946. if(pTC != NULL)
  947. {
  948. UINT mask = MF_STRING;
  949. mask = MF_STRING;
  950. if (!CanEdit(pTC))
  951. mask |= MF_GRAYED;
  952. AppendMenu(popUpMenu, mask, ID_ADDTUNINGGROUPGEOMETRIC, _T("Add &GroupGeometric tuning"));
  953. AppendMenu(popUpMenu, mask, ID_ADDTUNINGGEOMETRIC, _T("Add G&eometric tuning"));
  954. AppendMenu(popUpMenu, mask, ID_ADDTUNINGGENERAL, _T("Add Ge&neral tuning"));
  955. mask = MF_STRING;
  956. if(!IsDeletable(pTC))
  957. mask |= MF_GRAYED;
  958. AppendMenu(popUpMenu, mask, ID_REMOVETUNINGCOLLECTION, _T("&Unload tuning collection"));
  959. m_CommandItemDest.Set(pTC);
  960. }
  961. }
  962. GetCursorPos(&point);
  963. TrackPopupMenu(popUpMenu, TPM_LEFTALIGN|TPM_RIGHTBUTTON, point.x, point.y, 0, m_hWnd, NULL);
  964. DestroyMenu(popUpMenu);
  965. }
  966. bool CTuningDialog::IsDeletable(const CTuningCollection* const pTC) const
  967. {
  968. auto iter = find(m_DeletableTuningCollections.begin(), m_DeletableTuningCollections.end(), pTC);
  969. if(iter != m_DeletableTuningCollections.end())
  970. return true;
  971. else
  972. return false;
  973. }
  974. void CTuningDialog::OnTvnBegindragTreeTuning(NMHDR *pNMHDR, LRESULT *pResult)
  975. {
  976. LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
  977. *pResult = 0;
  978. m_CommandItemDest.Reset();
  979. m_CommandItemSrc.Reset();
  980. if(pNMTreeView == NULL || pNMTreeView->itemNew.hItem == NULL) return;
  981. TUNINGTREEITEM tunitem = m_TreeItemTuningItemMap.GetMapping_12(pNMTreeView->itemNew.hItem);
  982. if(tunitem.GetT() == NULL)
  983. {
  984. Reporting::Message(LogNotification, _T("For the time being Drag and Drop is only supported for tuning instances."), this);
  985. return;
  986. }
  987. SetCursor(CMainFrame::curDragging);
  988. m_TreeCtrlTuning.SetDragging();
  989. m_DragItem = m_TreeItemTuningItemMap.GetMapping_12(pNMTreeView->itemNew.hItem);
  990. m_TreeCtrlTuning.Select(pNMTreeView->itemNew.hItem, TVGN_CARET);
  991. }
  992. CTuningCollection *CTuningDialog::CanDrop(HTREEITEM dragDestItem)
  993. {
  994. if(!m_DragItem)
  995. return nullptr;
  996. TUNINGTREEITEM destTunItem = m_TreeItemTuningItemMap.GetMapping_12(dragDestItem);
  997. if(!destTunItem)
  998. return nullptr;
  999. CTuningCollection* pTCdest = nullptr;
  1000. CTuningCollection* pTCsrc = m_DragItem.GetTC();
  1001. if(pTCsrc == nullptr)
  1002. pTCsrc = GetpTuningCollection(m_DragItem.GetT());
  1003. if(pTCsrc == NULL)
  1004. {
  1005. ASSERT(false);
  1006. return nullptr;
  1007. }
  1008. if(destTunItem.GetT()) //Item dragged on tuning
  1009. pTCdest = GetpTuningCollection(destTunItem.GetT());
  1010. else //Item dragged on tuningcollecition
  1011. pTCdest = destTunItem.GetTC();
  1012. //For now, ignoring drags within a tuning collection.
  1013. if(pTCdest == pTCsrc)
  1014. return nullptr;
  1015. return pTCdest;
  1016. }
  1017. void CTuningDialog::OnEndDrag(HTREEITEM dragDestItem)
  1018. {
  1019. SetCursor(CMainFrame::curArrow);
  1020. m_TreeCtrlTuning.SetDragging(false);
  1021. if(!m_DragItem)
  1022. return;
  1023. CTuningCollection* pTCdest = CanDrop(dragDestItem);
  1024. m_CommandItemSrc = m_DragItem;
  1025. m_DragItem.Reset();
  1026. if(!pTCdest)
  1027. return;
  1028. CTuningCollection* pTCsrc = m_CommandItemSrc.GetTC();
  1029. if(pTCsrc == nullptr)
  1030. pTCsrc = GetpTuningCollection(m_CommandItemSrc.GetT());
  1031. if(pTCdest)
  1032. {
  1033. UINT mask = MF_STRING;
  1034. HMENU popUpMenu = CreatePopupMenu();
  1035. if(popUpMenu == NULL) return;
  1036. POINT point;
  1037. GetCursorPos(&point);
  1038. if(!CanEdit(pTCdest))
  1039. {
  1040. mask |= MF_GRAYED;
  1041. }
  1042. AppendMenu(popUpMenu, mask, ID_COPYTUNING, _T("&Copy here"));
  1043. GetCursorPos(&point);
  1044. TrackPopupMenu(popUpMenu, TPM_LEFTALIGN|TPM_RIGHTBUTTON, point.x, point.y, 0, m_hWnd, NULL);
  1045. DestroyMenu(popUpMenu);
  1046. m_CommandItemDest.Set(pTCdest);
  1047. }
  1048. }
  1049. bool CTuningDialog::AddTuning(CTuningCollection* pTC, CTuning* pT)
  1050. {
  1051. //Default: pT == NULL
  1052. if(!pTC)
  1053. {
  1054. Reporting::Notification("No tuning collection chosen");
  1055. return false;
  1056. }
  1057. std::unique_ptr<CTuning> pNewTuning;
  1058. if(pT)
  1059. {
  1060. pNewTuning = std::unique_ptr<CTuning>(new CTuning(*pT));
  1061. } else
  1062. {
  1063. Reporting::Notification("Add tuning failed");
  1064. return false;
  1065. }
  1066. CTuning *pNewTuningTmp = pTC->AddTuning(std::move(pNewTuning));
  1067. if(!pNewTuningTmp)
  1068. {
  1069. Reporting::Notification("Add tuning failed");
  1070. return false;
  1071. }
  1072. AddTreeItem(pNewTuningTmp, m_TreeItemTuningItemMap.GetMapping_21(TUNINGTREEITEM(pTC)), NULL);
  1073. m_pActiveTuning = pNewTuningTmp;
  1074. m_ModifiedTCs[pTC] = true;
  1075. UpdateView();
  1076. return true;
  1077. }
  1078. void CTuningDialog::OnAddTuningGeneral()
  1079. {
  1080. if(!m_CommandItemDest.GetTC())
  1081. {
  1082. m_CommandItemDest = s_notFoundItemTuning;
  1083. return;
  1084. }
  1085. CTuningCollection* pTC = m_CommandItemDest.GetTC();
  1086. m_CommandItemDest = s_notFoundItemTuning;
  1087. m_ModifiedTCs[pTC];
  1088. AddTuning(pTC, Tuning::Type::GENERAL);
  1089. }
  1090. void CTuningDialog::OnAddTuningGroupGeometric()
  1091. {
  1092. if(!m_CommandItemDest.GetTC())
  1093. {
  1094. m_CommandItemDest = s_notFoundItemTuning;
  1095. return;
  1096. }
  1097. CTuningCollection* pTC = m_CommandItemDest.GetTC();
  1098. m_CommandItemDest = s_notFoundItemTuning;
  1099. m_ModifiedTCs[pTC];
  1100. AddTuning(pTC, Tuning::Type::GROUPGEOMETRIC);
  1101. }
  1102. void CTuningDialog::OnAddTuningGeometric()
  1103. {
  1104. if(!m_CommandItemDest.GetTC())
  1105. {
  1106. m_CommandItemDest = s_notFoundItemTuning;
  1107. return;
  1108. }
  1109. CTuningCollection* pTC = m_CommandItemDest.GetTC();
  1110. m_CommandItemDest = s_notFoundItemTuning;
  1111. m_ModifiedTCs[pTC];
  1112. AddTuning(pTC, Tuning::Type::GEOMETRIC);
  1113. }
  1114. void CTuningDialog::OnRemoveTuning()
  1115. {
  1116. CTuning* pT = m_CommandItemDest.GetT();
  1117. if(m_CommandItemDest.GetT())
  1118. {
  1119. CTuningCollection* pTC = GetpTuningCollection(pT);
  1120. if(pTC)
  1121. {
  1122. bool used = false;
  1123. for(INSTRUMENTINDEX i = 1; i <= m_sndFile.GetNumInstruments(); i++)
  1124. {
  1125. if(m_sndFile.Instruments[i]->pTuning == pT)
  1126. {
  1127. used = true;
  1128. }
  1129. }
  1130. if(used)
  1131. {
  1132. CString s = _T("Tuning '") + mpt::ToCString(pT->GetName()) + _T("' is used by instruments. Remove anyway?");
  1133. if(Reporting::Confirm(s, false, true) == cnfYes)
  1134. {
  1135. CriticalSection cs;
  1136. for(INSTRUMENTINDEX i = 1; i <= m_sndFile.GetNumInstruments(); i++)
  1137. {
  1138. if(m_sndFile.Instruments[i]->pTuning == pT)
  1139. {
  1140. m_sndFile.Instruments[i]->SetTuning(nullptr);
  1141. }
  1142. }
  1143. pTC->Remove(pT);
  1144. cs.Leave();
  1145. m_ModifiedTCs[pTC] = true;
  1146. DeleteTreeItem(pT);
  1147. UpdateView();
  1148. }
  1149. } else
  1150. {
  1151. CString s = _T("Remove tuning '") + mpt::ToCString(pT->GetName()) + _T("'?");
  1152. if(Reporting::Confirm(s) == cnfYes)
  1153. {
  1154. pTC->Remove(pT);
  1155. m_ModifiedTCs[pTC] = true;
  1156. DeleteTreeItem(pT);
  1157. UpdateView();
  1158. }
  1159. }
  1160. }
  1161. }
  1162. m_CommandItemDest = s_notFoundItemTuning;
  1163. }
  1164. void CTuningDialog::OnCopyTuning()
  1165. {
  1166. CTuningCollection* pTC = m_CommandItemDest.GetTC();
  1167. if(!pTC)
  1168. return;
  1169. m_CommandItemDest = s_notFoundItemTuning;
  1170. CTuning* pT = m_CommandItemSrc.GetT();
  1171. if(pT == nullptr)
  1172. {
  1173. return;
  1174. }
  1175. m_ModifiedTCs[pTC] = true;
  1176. AddTuning(pTC, pT);
  1177. }
  1178. void CTuningDialog::OnRemoveTuningCollection()
  1179. {
  1180. if(!m_pActiveTuningCollection)
  1181. return;
  1182. if(!IsDeletable(m_pActiveTuningCollection))
  1183. {
  1184. ASSERT(false);
  1185. return;
  1186. }
  1187. auto iter = find(m_TuningCollections.begin(), m_TuningCollections.end(), m_pActiveTuningCollection);
  1188. if(iter == m_TuningCollections.end())
  1189. {
  1190. ASSERT(false);
  1191. return;
  1192. }
  1193. auto DTCiter = find(m_DeletableTuningCollections.begin(), m_DeletableTuningCollections.end(), *iter);
  1194. CTuningCollection* deletableTC = m_pActiveTuningCollection;
  1195. //Note: Order matters in the following lines.
  1196. m_DeletableTuningCollections.erase(DTCiter);
  1197. m_TuningCollections.erase(iter);
  1198. DeleteTreeItem(m_pActiveTuningCollection);
  1199. m_TuningCollectionsNames.erase(deletableTC);
  1200. m_TuningCollectionsFilenames.erase(deletableTC);
  1201. delete deletableTC; deletableTC = 0;
  1202. UpdateView();
  1203. }
  1204. void CTuningDialog::OnOK()
  1205. {
  1206. // Prevent return-key from closing the window.
  1207. if(GetKeyState(VK_RETURN) <= -127 && GetFocus() != GetDlgItem(IDOK))
  1208. return;
  1209. else
  1210. CDialog::OnOK();
  1211. }
  1212. ////////////////////////////////////////////////////////
  1213. //***************
  1214. //CTuningTreeCtrl
  1215. //***************
  1216. ////////////////////////////////////////////////////////
  1217. BEGIN_MESSAGE_MAP(CTuningTreeCtrl, CTreeCtrl)
  1218. ON_WM_MOUSEMOVE()
  1219. ON_WM_LBUTTONUP()
  1220. END_MESSAGE_MAP()
  1221. void CTuningTreeCtrl::OnMouseMove(UINT nFlags, CPoint point)
  1222. {
  1223. if(IsDragging())
  1224. {
  1225. HTREEITEM hItem = HitTest(point, nullptr);
  1226. SetCursor((hItem == NULL || m_rParentDialog.CanDrop(hItem) == nullptr) ? CMainFrame::curNoDrop2 : CMainFrame::curDragging);
  1227. }
  1228. CTreeCtrl::OnMouseMove(nFlags, point);
  1229. }
  1230. void CTuningTreeCtrl::OnLButtonUp(UINT nFlags, CPoint point)
  1231. {
  1232. if(IsDragging())
  1233. {
  1234. HTREEITEM hItem = HitTest(point, nullptr);
  1235. m_rParentDialog.OnEndDrag(hItem);
  1236. CTreeCtrl::OnLButtonUp(nFlags, point);
  1237. }
  1238. }
  1239. ////////////////////////////////////////////////////////
  1240. //
  1241. // scl import
  1242. //
  1243. ////////////////////////////////////////////////////////
  1244. using SclFloat = double;
  1245. CString CTuningDialog::GetSclImportFailureMsg(EnSclImport id)
  1246. {
  1247. switch(id)
  1248. {
  1249. case enSclImportFailTooManyNotes:
  1250. return MPT_CFORMAT("OpenMPT supports importing scl-files with at most {} notes")(mpt::cfmt::val(s_nSclImportMaxNoteCount));
  1251. case enSclImportFailTooLargeNumDenomIntegers:
  1252. return _T("Invalid numerator or denominator");
  1253. case enSclImportFailZeroDenominator:
  1254. return _T("Zero denominator");
  1255. case enSclImportFailNegativeRatio:
  1256. return _T("Negative ratio");
  1257. case enSclImportFailUnableToOpenFile:
  1258. return _T("Unable to open file");
  1259. case enSclImportLineCountMismatch:
  1260. return _T("Note count error");
  1261. case enSclImportTuningCreationFailure:
  1262. return _T("Unknown tuning creation error");
  1263. case enSclImportAddTuningFailure:
  1264. return _T("Can't add tuning to tuning collection");
  1265. default:
  1266. return _T("");
  1267. }
  1268. }
  1269. static void SkipCommentLines(std::istream& iStrm, std::string& str)
  1270. {
  1271. std::string whitespace(" \t");
  1272. while(std::getline(iStrm, str))
  1273. {
  1274. auto start = str.find_first_not_of(whitespace);
  1275. // Lines starting with a ! are comments
  1276. if(start != std::string::npos && str[start] != '!')
  1277. return;
  1278. }
  1279. }
  1280. static inline SclFloat CentToRatio(const SclFloat& val)
  1281. {
  1282. return pow(2.0, val / 1200.0);
  1283. }
  1284. CTuningDialog::EnSclImport CTuningDialog::ImportScl(const mpt::PathString &filename, const mpt::ustring &name, std::unique_ptr<CTuning> & result)
  1285. {
  1286. MPT_ASSERT(result == nullptr);
  1287. result = nullptr;
  1288. mpt::ifstream iStrm(filename, std::ios::in | std::ios::binary);
  1289. if(!iStrm)
  1290. {
  1291. return enSclImportFailUnableToOpenFile;
  1292. }
  1293. return ImportScl(iStrm, name, result);
  1294. }
  1295. CTuningDialog::EnSclImport CTuningDialog::ImportScl(std::istream& iStrm, const mpt::ustring &name, std::unique_ptr<CTuning> & result)
  1296. {
  1297. MPT_ASSERT(result == nullptr);
  1298. result = nullptr;
  1299. std::string str;
  1300. std::string filename;
  1301. bool first = true;
  1302. std::string whitespace(" \t");
  1303. while(std::getline(iStrm, str))
  1304. {
  1305. auto start = str.find_first_not_of(whitespace);
  1306. // Lines starting with a ! are comments
  1307. if(start != std::string::npos && str[start] != '!')
  1308. break;
  1309. if(first)
  1310. {
  1311. filename = mpt::trim(str.substr(start + 1), std::string(" \t\r\n"));
  1312. }
  1313. first = false;
  1314. }
  1315. std::string description = mpt::trim(str, std::string(" \t\r\n"));
  1316. SkipCommentLines(iStrm, str);
  1317. // str should now contain number of notes.
  1318. const size_t nNotes = 1 + ConvertStrTo<size_t>(str.c_str());
  1319. if (nNotes - 1 > s_nSclImportMaxNoteCount)
  1320. return enSclImportFailTooManyNotes;
  1321. std::vector<mpt::ustring> names;
  1322. std::vector<Tuning::RATIOTYPE> fRatios;
  1323. fRatios.reserve(nNotes);
  1324. fRatios.push_back(1);
  1325. char buffer[128];
  1326. MemsetZero(buffer);
  1327. while (iStrm.getline(buffer, sizeof(buffer)))
  1328. {
  1329. LPSTR psz = buffer;
  1330. LPSTR const pEnd = psz + strlen(buffer);
  1331. // Skip tabs and spaces.
  1332. while(psz != pEnd && (*psz == ' ' || *psz == '\t'))
  1333. psz++;
  1334. // Skip empty lines, comment lines and non-text.
  1335. if (*psz == 0 || *psz == '!' || *psz < 32)
  1336. continue;
  1337. char* pNonDigit = pEnd;
  1338. // Check type of first non digit. This tells whether to read cent, ratio or plain number.
  1339. for (pNonDigit = psz; pNonDigit != pEnd; pNonDigit++)
  1340. {
  1341. if (isdigit(*pNonDigit) == 0)
  1342. break;
  1343. }
  1344. if (*pNonDigit == '.') // Reading cents
  1345. {
  1346. SclFloat fCent = ConvertStrTo<SclFloat>(psz);
  1347. fRatios.push_back(static_cast<Tuning::RATIOTYPE>(CentToRatio(fCent)));
  1348. }
  1349. else if (*pNonDigit == '/') // Reading ratios
  1350. {
  1351. *pNonDigit = 0; // Replace '/' with null.
  1352. int64 nNum = ConvertStrTo<int64>(psz);
  1353. psz = pNonDigit + 1;
  1354. int64 nDenom = ConvertStrTo<int64>(psz);
  1355. if (nNum > int32_max || nDenom > int32_max)
  1356. return enSclImportFailTooLargeNumDenomIntegers;
  1357. if (nDenom == 0)
  1358. return enSclImportFailZeroDenominator;
  1359. fRatios.push_back(static_cast<Tuning::RATIOTYPE>((SclFloat)nNum / (SclFloat)nDenom));
  1360. }
  1361. else // Plain numbers.
  1362. fRatios.push_back(static_cast<Tuning::RATIOTYPE>(ConvertStrTo<int32>(psz)));
  1363. std::string remainder = psz;
  1364. remainder = mpt::trim(remainder, std::string("\r\n"));
  1365. if(remainder.find_first_of(" \t") != std::string::npos)
  1366. {
  1367. remainder = remainder.substr(remainder.find_first_of(" \t"));
  1368. } else
  1369. {
  1370. remainder = std::string();
  1371. }
  1372. remainder = mpt::trim(remainder, std::string(" \t"));
  1373. if(!remainder.empty())
  1374. {
  1375. if(remainder[0] == '!')
  1376. {
  1377. remainder = remainder.substr(1);
  1378. remainder = mpt::trim(remainder, std::string(" \t"));
  1379. }
  1380. }
  1381. if(mpt::ToLowerCaseAscii(remainder) == "cents" || mpt::ToLowerCaseAscii(remainder) == "cent")
  1382. {
  1383. remainder = std::string();
  1384. }
  1385. names.push_back(mpt::ToUnicode(mpt::Charset::ISO8859_1, remainder));
  1386. }
  1387. if (nNotes != fRatios.size())
  1388. return enSclImportLineCountMismatch;
  1389. for(size_t i = 0; i < fRatios.size(); i++)
  1390. {
  1391. if (fRatios[i] < 0)
  1392. return enSclImportFailNegativeRatio;
  1393. }
  1394. Tuning::RATIOTYPE groupRatio = fRatios.back();
  1395. fRatios.pop_back();
  1396. mpt::ustring tuningName;
  1397. if(!description.empty())
  1398. {
  1399. tuningName = mpt::ToUnicode(mpt::Charset::ISO8859_1, description);
  1400. } else if(!filename.empty())
  1401. {
  1402. tuningName = mpt::ToUnicode(mpt::Charset::ISO8859_1, filename);
  1403. } else if(!name.empty())
  1404. {
  1405. tuningName = name;
  1406. } else
  1407. {
  1408. tuningName = MPT_UFORMAT("{} notes: {}:{}")(nNotes - 1, mpt::ufmt::fix(groupRatio), 1);
  1409. }
  1410. std::unique_ptr<CTuning> pT = CTuning::CreateGroupGeometric(tuningName, fRatios, groupRatio, 15);
  1411. if(!pT)
  1412. {
  1413. return enSclImportTuningCreationFailure;
  1414. }
  1415. bool allNamesEmpty = true;
  1416. bool allNamesValid = true;
  1417. for(NOTEINDEXTYPE note = 0; note < mpt::saturate_cast<NOTEINDEXTYPE>(names.size()); ++note)
  1418. {
  1419. if(names[note].empty())
  1420. {
  1421. allNamesValid = false;
  1422. } else
  1423. {
  1424. allNamesEmpty = false;
  1425. }
  1426. }
  1427. if(nNotes - 1 == 12 && !allNamesValid)
  1428. {
  1429. for(NOTEINDEXTYPE note = 0; note < mpt::saturate_cast<NOTEINDEXTYPE>(names.size()); ++note)
  1430. {
  1431. pT->SetNoteName(note, mpt::ustring(CSoundFile::GetDefaultNoteNames()[note]));
  1432. }
  1433. } else
  1434. {
  1435. for(NOTEINDEXTYPE note = 0; note < mpt::saturate_cast<NOTEINDEXTYPE>(names.size()); ++note)
  1436. {
  1437. if(!names[note].empty())
  1438. {
  1439. pT->SetNoteName(note, names[(note - 1 + names.size()) % names.size()]);
  1440. }
  1441. }
  1442. }
  1443. result = std::move(pT);
  1444. return enSclImportOk;
  1445. }
  1446. OPENMPT_NAMESPACE_END