1
0

Moddoc.cpp 97 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361
  1. /*
  2. * Moddoc.cpp
  3. * ----------
  4. * Purpose: Module document handling in OpenMPT.
  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 "Mainfrm.h"
  12. #include "InputHandler.h"
  13. #include "Moddoc.h"
  14. #include "ModDocTemplate.h"
  15. #include "../soundlib/mod_specifications.h"
  16. #include "../soundlib/plugins/PlugInterface.h"
  17. #include "Childfrm.h"
  18. #include "Mpdlgs.h"
  19. #include "dlg_misc.h"
  20. #include "TempoSwingDialog.h"
  21. #include "mod2wave.h"
  22. #include "ChannelManagerDlg.h"
  23. #include "MIDIMacroDialog.h"
  24. #include "MIDIMappingDialog.h"
  25. #include "StreamEncoderAU.h"
  26. #include "StreamEncoderFLAC.h"
  27. #include "StreamEncoderMP3.h"
  28. #include "StreamEncoderOpus.h"
  29. #include "StreamEncoderRAW.h"
  30. #include "StreamEncoderVorbis.h"
  31. #include "StreamEncoderWAV.h"
  32. #include "mod2midi.h"
  33. #include "../common/version.h"
  34. #include "../tracklib/SampleEdit.h"
  35. #include "../soundlib/modsmp_ctrl.h"
  36. #include "CleanupSong.h"
  37. #include "../common/mptStringBuffer.h"
  38. #include "../common/mptFileIO.h"
  39. #include <sstream>
  40. #include "../common/FileReader.h"
  41. #include "FileDialog.h"
  42. #include "ExternalSamples.h"
  43. #include "Globals.h"
  44. #include "../soundlib/OPL.h"
  45. #ifndef NO_PLUGINS
  46. #include "AbstractVstEditor.h"
  47. #endif
  48. #include "mpt/binary/hex.hpp"
  49. #include "mpt/base/numbers.hpp"
  50. #include "mpt/io/io.hpp"
  51. #include "mpt/io/io_stdstream.hpp"
  52. OPENMPT_NAMESPACE_BEGIN
  53. const TCHAR FileFilterMOD[] = _T("ProTracker Modules (*.mod)|*.mod||");
  54. const TCHAR FileFilterXM[] = _T("FastTracker Modules (*.xm)|*.xm||");
  55. const TCHAR FileFilterS3M[] = _T("Scream Tracker Modules (*.s3m)|*.s3m||");
  56. const TCHAR FileFilterIT[] = _T("Impulse Tracker Modules (*.it)|*.it||");
  57. const TCHAR FileFilterMPT[] = _T("OpenMPT Modules (*.mptm)|*.mptm||");
  58. const TCHAR FileFilterNone[] = _T("");
  59. const CString ModTypeToFilter(const CSoundFile& sndFile)
  60. {
  61. const MODTYPE modtype = sndFile.GetType();
  62. switch(modtype)
  63. {
  64. case MOD_TYPE_MOD: return FileFilterMOD;
  65. case MOD_TYPE_XM: return FileFilterXM;
  66. case MOD_TYPE_S3M: return FileFilterS3M;
  67. case MOD_TYPE_IT: return FileFilterIT;
  68. case MOD_TYPE_MPT: return FileFilterMPT;
  69. default: return FileFilterNone;
  70. }
  71. }
  72. /////////////////////////////////////////////////////////////////////////////
  73. // CModDoc
  74. IMPLEMENT_DYNCREATE(CModDoc, CDocument)
  75. BEGIN_MESSAGE_MAP(CModDoc, CDocument)
  76. //{{AFX_MSG_MAP(CModDoc)
  77. ON_COMMAND(ID_FILE_SAVE_COPY, &CModDoc::OnSaveCopy)
  78. ON_COMMAND(ID_FILE_SAVEASTEMPLATE, &CModDoc::OnSaveTemplateModule)
  79. ON_COMMAND(ID_FILE_SAVEASWAVE, &CModDoc::OnFileWaveConvert)
  80. ON_COMMAND(ID_FILE_SAVEMIDI, &CModDoc::OnFileMidiConvert)
  81. ON_COMMAND(ID_FILE_SAVEOPL, &CModDoc::OnFileOPLExport)
  82. ON_COMMAND(ID_FILE_SAVECOMPAT, &CModDoc::OnFileCompatibilitySave)
  83. ON_COMMAND(ID_FILE_APPENDMODULE, &CModDoc::OnAppendModule)
  84. ON_COMMAND(ID_PLAYER_PLAY, &CModDoc::OnPlayerPlay)
  85. ON_COMMAND(ID_PLAYER_PAUSE, &CModDoc::OnPlayerPause)
  86. ON_COMMAND(ID_PLAYER_STOP, &CModDoc::OnPlayerStop)
  87. ON_COMMAND(ID_PLAYER_PLAYFROMSTART, &CModDoc::OnPlayerPlayFromStart)
  88. ON_COMMAND(ID_VIEW_SONGPROPERTIES, &CModDoc::OnSongProperties)
  89. ON_COMMAND(ID_VIEW_GLOBALS, &CModDoc::OnEditGlobals)
  90. ON_COMMAND(ID_VIEW_PATTERNS, &CModDoc::OnEditPatterns)
  91. ON_COMMAND(ID_VIEW_SAMPLES, &CModDoc::OnEditSamples)
  92. ON_COMMAND(ID_VIEW_INSTRUMENTS, &CModDoc::OnEditInstruments)
  93. ON_COMMAND(ID_VIEW_COMMENTS, &CModDoc::OnEditComments)
  94. ON_COMMAND(ID_VIEW_EDITHISTORY, &CModDoc::OnViewEditHistory)
  95. ON_COMMAND(ID_VIEW_MIDIMAPPING, &CModDoc::OnViewMIDIMapping)
  96. ON_COMMAND(ID_VIEW_MPTHACKS, &CModDoc::OnViewMPTHacks)
  97. ON_COMMAND(ID_EDIT_CLEANUP, &CModDoc::OnShowCleanup)
  98. ON_COMMAND(ID_EDIT_SAMPLETRIMMER, &CModDoc::OnShowSampleTrimmer)
  99. ON_COMMAND(ID_PATTERN_MIDIMACRO, &CModDoc::OnSetupZxxMacros)
  100. ON_COMMAND(ID_CHANNEL_MANAGER, &CModDoc::OnChannelManager)
  101. ON_COMMAND(ID_ESTIMATESONGLENGTH, &CModDoc::OnEstimateSongLength)
  102. ON_COMMAND(ID_APPROX_BPM, &CModDoc::OnApproximateBPM)
  103. ON_COMMAND(ID_PATTERN_PLAY, &CModDoc::OnPatternPlay)
  104. ON_COMMAND(ID_PATTERN_PLAYNOLOOP, &CModDoc::OnPatternPlayNoLoop)
  105. ON_COMMAND(ID_PATTERN_RESTART, &CModDoc::OnPatternRestart)
  106. ON_UPDATE_COMMAND_UI(ID_VIEW_INSTRUMENTS, &CModDoc::OnUpdateXMITMPTOnly)
  107. ON_UPDATE_COMMAND_UI(ID_PATTERN_MIDIMACRO, &CModDoc::OnUpdateXMITMPTOnly)
  108. ON_UPDATE_COMMAND_UI(ID_VIEW_MIDIMAPPING, &CModDoc::OnUpdateHasMIDIMappings)
  109. ON_UPDATE_COMMAND_UI(ID_VIEW_EDITHISTORY, &CModDoc::OnUpdateHasEditHistory)
  110. ON_UPDATE_COMMAND_UI(ID_FILE_SAVECOMPAT, &CModDoc::OnUpdateCompatExportableOnly)
  111. //}}AFX_MSG_MAP
  112. END_MESSAGE_MAP()
  113. /////////////////////////////////////////////////////////////////////////////
  114. // CModDoc construction/destruction
  115. CModDoc::CModDoc()
  116. : m_notifyType(Notification::Default)
  117. , m_PatternUndo(*this)
  118. , m_SampleUndo(*this)
  119. , m_InstrumentUndo(*this)
  120. {
  121. // Set the creation date of this file (or the load time if we're loading an existing file)
  122. time(&m_creationTime);
  123. ReinitRecordState();
  124. CMainFrame::UpdateAudioParameters(m_SndFile, true);
  125. }
  126. CModDoc::~CModDoc()
  127. {
  128. ClearLog();
  129. }
  130. void CModDoc::SetModified(bool modified)
  131. {
  132. static_assert(sizeof(long) == sizeof(m_bModified));
  133. m_modifiedAutosave = modified;
  134. if(!!InterlockedExchange(reinterpret_cast<long *>(&m_bModified), modified ? TRUE : FALSE) != modified)
  135. {
  136. // Update window titles in GUI thread
  137. CMainFrame::GetMainFrame()->SendNotifyMessage(WM_MOD_SETMODIFIED, reinterpret_cast<WPARAM>(this), 0);
  138. }
  139. }
  140. // Return "modified since last autosave" status and reset it until the next SetModified() (as this is only used for polling during autosave)
  141. bool CModDoc::ModifiedSinceLastAutosave()
  142. {
  143. return m_modifiedAutosave.exchange(false);
  144. }
  145. BOOL CModDoc::OnNewDocument()
  146. {
  147. if (!CDocument::OnNewDocument()) return FALSE;
  148. m_SndFile.Create(FileReader(), CSoundFile::loadCompleteModule, this);
  149. m_SndFile.ChangeModTypeTo(CTrackApp::GetDefaultDocType());
  150. theApp.GetDefaultMidiMacro(m_SndFile.m_MidiCfg);
  151. m_SndFile.m_SongFlags.set((SONG_LINEARSLIDES | SONG_ISAMIGA) & m_SndFile.GetModSpecifications().songFlags);
  152. ReinitRecordState();
  153. InitializeMod();
  154. SetModified(false);
  155. return TRUE;
  156. }
  157. BOOL CModDoc::OnOpenDocument(LPCTSTR lpszPathName)
  158. {
  159. const mpt::PathString filename = lpszPathName ? mpt::PathString::FromCString(lpszPathName) : mpt::PathString();
  160. ScopedLogCapturer logcapturer(*this);
  161. if(filename.empty()) return OnNewDocument();
  162. BeginWaitCursor();
  163. {
  164. MPT_LOG_GLOBAL(LogDebug, "Loader", U_("Open..."));
  165. InputFile f(filename, TrackerSettings::Instance().MiscCacheCompleteFileBeforeLoading);
  166. if (f.IsValid())
  167. {
  168. FileReader file = GetFileReader(f);
  169. MPT_ASSERT(GetPathNameMpt().empty());
  170. SetPathName(filename, FALSE); // Path is not set yet, but loaders processing external samples/instruments (ITP/MPTM) need this for relative paths.
  171. try
  172. {
  173. if(!m_SndFile.Create(file, CSoundFile::loadCompleteModule, this))
  174. {
  175. EndWaitCursor();
  176. return FALSE;
  177. }
  178. } catch(mpt::out_of_memory e)
  179. {
  180. mpt::delete_out_of_memory(e);
  181. EndWaitCursor();
  182. AddToLog(LogError, U_("Out of Memory"));
  183. return FALSE;
  184. } catch(const std::exception &)
  185. {
  186. EndWaitCursor();
  187. return FALSE;
  188. }
  189. }
  190. MPT_LOG_GLOBAL(LogDebug, "Loader", U_("Open."));
  191. }
  192. EndWaitCursor();
  193. logcapturer.ShowLog(
  194. MPT_CFORMAT("File: {}\nLast saved with: {}, you are using OpenMPT {}\n\n")
  195. (filename, m_SndFile.m_modFormat.madeWithTracker, Version::Current()));
  196. if((m_SndFile.m_nType == MOD_TYPE_NONE) || (!m_SndFile.GetNumChannels()))
  197. return FALSE;
  198. const bool noColors = std::find_if(std::begin(m_SndFile.ChnSettings), std::begin(m_SndFile.ChnSettings) + GetNumChannels(), [](const auto &settings) {
  199. return settings.color != ModChannelSettings::INVALID_COLOR;
  200. }) == std::begin(m_SndFile.ChnSettings) + GetNumChannels();
  201. if(noColors)
  202. {
  203. SetDefaultChannelColors();
  204. }
  205. // Convert to MOD/S3M/XM/IT
  206. switch(m_SndFile.GetType())
  207. {
  208. case MOD_TYPE_MOD:
  209. case MOD_TYPE_S3M:
  210. case MOD_TYPE_XM:
  211. case MOD_TYPE_IT:
  212. case MOD_TYPE_MPT:
  213. break;
  214. default:
  215. m_SndFile.ChangeModTypeTo(m_SndFile.GetBestSaveFormat(), false);
  216. m_SndFile.m_SongFlags.set(SONG_IMPORTED);
  217. break;
  218. }
  219. // If the file was packed in some kind of container (e.g. ZIP, or simply a format like MO3), prompt for new file extension as well
  220. // Same if MOD_TYPE_XXX does not indicate actual song format
  221. if(m_SndFile.GetContainerType() != MOD_CONTAINERTYPE_NONE || m_SndFile.m_SongFlags[SONG_IMPORTED])
  222. {
  223. m_ShowSavedialog = true;
  224. }
  225. ReinitRecordState();
  226. if(TrackerSettings::Instance().rememberSongWindows)
  227. DeserializeViews();
  228. // This is only needed when opening a module with stored window positions.
  229. // The MDI child is activated before it has an active view and thus there is no CModDoc associated with it.
  230. CMainFrame::GetMainFrame()->UpdateEffectKeys(this);
  231. auto instance = CChannelManagerDlg::sharedInstance();
  232. if(instance != nullptr)
  233. {
  234. instance->SetDocument(this);
  235. }
  236. // Show warning if file was made with more recent version of OpenMPT except
  237. if(m_SndFile.m_dwLastSavedWithVersion.WithoutTestNumber() > Version::Current())
  238. {
  239. Reporting::Notification(MPT_UFORMAT("Warning: this song was last saved with a more recent version of OpenMPT.\r\nSong saved with: v{}. Current version: v{}.\r\n")(
  240. m_SndFile.m_dwLastSavedWithVersion,
  241. Version::Current()));
  242. }
  243. SetModified(false);
  244. m_bHasValidPath = true;
  245. // Check if there are any missing samples, and if there are, show a dialog to relocate them.
  246. for(SAMPLEINDEX smp = 1; smp <= GetNumSamples(); smp++)
  247. {
  248. if(m_SndFile.IsExternalSampleMissing(smp))
  249. {
  250. MissingExternalSamplesDlg dlg(*this, CMainFrame::GetMainFrame());
  251. dlg.DoModal();
  252. break;
  253. }
  254. }
  255. return TRUE;
  256. }
  257. bool CModDoc::OnSaveDocument(const mpt::PathString &filename, const bool setPath)
  258. {
  259. ScopedLogCapturer logcapturer(*this);
  260. if(filename.empty())
  261. return false;
  262. bool ok = false;
  263. BeginWaitCursor();
  264. m_SndFile.m_dwLastSavedWithVersion = Version::Current();
  265. try
  266. {
  267. mpt::SafeOutputFile sf(filename, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
  268. mpt::ofstream &f = sf;
  269. if(f)
  270. {
  271. if(m_SndFile.m_SongFlags[SONG_IMPORTED] && !(GetModType() & (MOD_TYPE_MOD | MOD_TYPE_S3M)))
  272. {
  273. // Check if any non-supported playback behaviours are enabled due to being imported from a different format
  274. const auto supportedBehaviours = m_SndFile.GetSupportedPlaybackBehaviour(GetModType());
  275. bool showWarning = true;
  276. for(size_t i = 0; i < kMaxPlayBehaviours; i++)
  277. {
  278. if(m_SndFile.m_playBehaviour[i] && !supportedBehaviours[i])
  279. {
  280. if(showWarning)
  281. {
  282. AddToLog(LogWarning, mpt::ToUnicode(mpt::Charset::ASCII, MPT_AFORMAT("Some imported Compatibility Settings that are not supported by the {} format have been disabled. Verify that the module still sounds as intended.")
  283. (mpt::ToUpperCaseAscii(m_SndFile.GetModSpecifications().fileExtension))));
  284. showWarning = false;
  285. }
  286. m_SndFile.m_playBehaviour.reset(i);
  287. }
  288. }
  289. }
  290. f.exceptions(f.exceptions() | std::ios::badbit | std::ios::failbit);
  291. FixNullStrings();
  292. switch(m_SndFile.GetType())
  293. {
  294. case MOD_TYPE_MOD: ok = m_SndFile.SaveMod(f); break;
  295. case MOD_TYPE_S3M: ok = m_SndFile.SaveS3M(f); break;
  296. case MOD_TYPE_XM: ok = m_SndFile.SaveXM(f); break;
  297. case MOD_TYPE_IT: ok = m_SndFile.SaveIT(f, filename); break;
  298. case MOD_TYPE_MPT: ok = m_SndFile.SaveIT(f, filename); break;
  299. default: MPT_ASSERT_NOTREACHED();
  300. }
  301. }
  302. } catch(const std::exception &)
  303. {
  304. ok = false;
  305. }
  306. EndWaitCursor();
  307. if(ok)
  308. {
  309. if(setPath)
  310. {
  311. // Set new path for this file, unless we are saving a template or a copy, in which case we want to keep the old file path.
  312. SetPathName(filename);
  313. }
  314. logcapturer.ShowLog(true);
  315. if(TrackerSettings::Instance().rememberSongWindows)
  316. SerializeViews();
  317. } else
  318. {
  319. ErrorBox(IDS_ERR_SAVESONG, CMainFrame::GetMainFrame());
  320. }
  321. return ok;
  322. }
  323. BOOL CModDoc::SaveModified()
  324. {
  325. if(m_SndFile.GetType() == MOD_TYPE_MPT && !SaveAllSamples())
  326. return FALSE;
  327. return CDocument::SaveModified();
  328. }
  329. bool CModDoc::SaveAllSamples(bool showPrompt)
  330. {
  331. if(showPrompt)
  332. {
  333. ModifiedExternalSamplesDlg dlg(*this, CMainFrame::GetMainFrame());
  334. return dlg.DoModal() == IDOK;
  335. } else
  336. {
  337. bool ok = true;
  338. for(SAMPLEINDEX smp = 1; smp <= GetNumSamples(); smp++)
  339. {
  340. ok &= SaveSample(smp);
  341. }
  342. return ok;
  343. }
  344. }
  345. bool CModDoc::SaveSample(SAMPLEINDEX smp)
  346. {
  347. bool success = false;
  348. if(smp > 0 && smp <= GetNumSamples())
  349. {
  350. const mpt::PathString filename = m_SndFile.GetSamplePath(smp);
  351. if(!filename.empty())
  352. {
  353. auto &sample = m_SndFile.GetSample(smp);
  354. const auto ext = filename.GetFileExt().ToUnicode().substr(1);
  355. const auto format = FromSettingValue<SampleEditorDefaultFormat>(ext);
  356. try
  357. {
  358. mpt::SafeOutputFile sf(filename, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
  359. if(sf)
  360. {
  361. mpt::ofstream &f = sf;
  362. f.exceptions(f.exceptions() | std::ios::badbit | std::ios::failbit);
  363. if(sample.uFlags[CHN_ADLIB] || format == dfS3I)
  364. success = m_SndFile.SaveS3ISample(smp, f);
  365. else if(format != dfWAV)
  366. success = m_SndFile.SaveFLACSample(smp, f);
  367. else
  368. success = m_SndFile.SaveWAVSample(smp, f);
  369. }
  370. } catch(const std::exception &)
  371. {
  372. success = false;
  373. }
  374. if(success)
  375. sample.uFlags.reset(SMP_MODIFIED);
  376. else
  377. AddToLog(LogError, MPT_UFORMAT("Unable to save sample {}: {}")(smp, filename));
  378. }
  379. }
  380. return success;
  381. }
  382. void CModDoc::OnCloseDocument()
  383. {
  384. CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
  385. if(pMainFrm) pMainFrm->OnDocumentClosed(this);
  386. CDocument::OnCloseDocument();
  387. }
  388. void CModDoc::DeleteContents()
  389. {
  390. CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
  391. if (pMainFrm) pMainFrm->StopMod(this);
  392. m_SndFile.Destroy();
  393. ReinitRecordState();
  394. }
  395. BOOL CModDoc::DoSave(const mpt::PathString &filename, bool setPath)
  396. {
  397. const mpt::PathString docFileName = GetPathNameMpt();
  398. const std::string defaultExtension = m_SndFile.GetModSpecifications().fileExtension;
  399. switch(m_SndFile.GetBestSaveFormat())
  400. {
  401. case MOD_TYPE_MOD:
  402. MsgBoxHidable(ModSaveHint);
  403. break;
  404. case MOD_TYPE_S3M:
  405. break;
  406. case MOD_TYPE_XM:
  407. MsgBoxHidable(XMCompatibilityExportTip);
  408. break;
  409. case MOD_TYPE_IT:
  410. MsgBoxHidable(ItCompatibilityExportTip);
  411. break;
  412. case MOD_TYPE_MPT:
  413. break;
  414. default:
  415. ErrorBox(IDS_ERR_SAVESONG, CMainFrame::GetMainFrame());
  416. return FALSE;
  417. }
  418. mpt::PathString ext = P_(".") + mpt::PathString::FromUTF8(defaultExtension);
  419. mpt::PathString saveFileName;
  420. if(filename.empty() || m_ShowSavedialog)
  421. {
  422. mpt::PathString drive = docFileName.GetDrive();
  423. mpt::PathString dir = docFileName.GetDir();
  424. mpt::PathString fileName = docFileName.GetFileName();
  425. if(fileName.empty())
  426. {
  427. fileName = mpt::PathString::FromCString(GetTitle()).SanitizeComponent();
  428. }
  429. mpt::PathString defaultSaveName = drive + dir + fileName + ext;
  430. FileDialog dlg = SaveFileDialog()
  431. .DefaultExtension(defaultExtension)
  432. .DefaultFilename(defaultSaveName)
  433. .ExtensionFilter(ModTypeToFilter(m_SndFile))
  434. .WorkingDirectory(TrackerSettings::Instance().PathSongs.GetWorkingDir());
  435. if(!dlg.Show()) return FALSE;
  436. TrackerSettings::Instance().PathSongs.SetWorkingDir(dlg.GetWorkingDirectory());
  437. saveFileName = dlg.GetFirstFile();
  438. } else
  439. {
  440. saveFileName = filename;
  441. }
  442. // Do we need to create a backup file ?
  443. if((TrackerSettings::Instance().CreateBackupFiles)
  444. && (IsModified()) && (!mpt::PathString::CompareNoCase(saveFileName, docFileName)))
  445. {
  446. if(saveFileName.IsFile())
  447. {
  448. mpt::PathString backupFileName = saveFileName.ReplaceExt(P_(".bak"));
  449. if(backupFileName.IsFile())
  450. {
  451. DeleteFile(backupFileName.AsNative().c_str());
  452. }
  453. MoveFile(saveFileName.AsNative().c_str(), backupFileName.AsNative().c_str());
  454. }
  455. }
  456. if(OnSaveDocument(saveFileName, setPath))
  457. {
  458. SetModified(false);
  459. m_SndFile.m_SongFlags.reset(SONG_IMPORTED);
  460. m_bHasValidPath = true;
  461. m_ShowSavedialog = false;
  462. CMainFrame::GetMainFrame()->UpdateTree(this, GeneralHint().General()); // Update treeview (e.g. filename might have changed)
  463. return TRUE;
  464. } else
  465. {
  466. return FALSE;
  467. }
  468. }
  469. void CModDoc::OnAppendModule()
  470. {
  471. FileDialog::PathList files;
  472. CTrackApp::OpenModulesDialog(files);
  473. ScopedLogCapturer logcapture(*this, _T("Append Failures"));
  474. try
  475. {
  476. auto source = std::make_unique<CSoundFile>();
  477. for(const auto &file : files)
  478. {
  479. InputFile f(file, TrackerSettings::Instance().MiscCacheCompleteFileBeforeLoading);
  480. if(!f.IsValid())
  481. {
  482. AddToLog("Unable to open source file!");
  483. continue;
  484. }
  485. try
  486. {
  487. if(!source->Create(GetFileReader(f), CSoundFile::loadCompleteModule))
  488. {
  489. AddToLog("Unable to open source file!");
  490. continue;
  491. }
  492. } catch(const std::exception &)
  493. {
  494. AddToLog("Unable to open source file!");
  495. continue;
  496. }
  497. AppendModule(*source);
  498. source->Destroy();
  499. SetModified();
  500. }
  501. } catch(mpt::out_of_memory e)
  502. {
  503. mpt::delete_out_of_memory(e);
  504. AddToLog("Out of memory.");
  505. return;
  506. }
  507. UpdateAllViews(nullptr, SequenceHint().Data().ModType());
  508. }
  509. void CModDoc::InitializeMod()
  510. {
  511. // New module ?
  512. if (!m_SndFile.m_nChannels)
  513. {
  514. switch(GetModType())
  515. {
  516. case MOD_TYPE_MOD:
  517. m_SndFile.m_nChannels = 4;
  518. break;
  519. case MOD_TYPE_S3M:
  520. m_SndFile.m_nChannels = 16;
  521. break;
  522. default:
  523. m_SndFile.m_nChannels = 32;
  524. break;
  525. }
  526. SetDefaultChannelColors();
  527. if(GetModType() == MOD_TYPE_MPT)
  528. {
  529. m_SndFile.m_nTempoMode = TempoMode::Modern;
  530. m_SndFile.m_SongFlags.set(SONG_EXFILTERRANGE);
  531. }
  532. m_SndFile.SetDefaultPlaybackBehaviour(GetModType());
  533. // Refresh mix levels now that the correct mod type has been set
  534. m_SndFile.SetMixLevels(m_SndFile.GetModSpecifications().defaultMixLevels);
  535. m_SndFile.Order().assign(1, 0);
  536. if (!m_SndFile.Patterns.IsValidPat(0))
  537. {
  538. m_SndFile.Patterns.Insert(0, 64);
  539. }
  540. Clear(m_SndFile.m_szNames);
  541. m_SndFile.m_PlayState.m_nMusicTempo.Set(125);
  542. m_SndFile.m_nDefaultTempo.Set(125);
  543. m_SndFile.m_PlayState.m_nMusicSpeed = m_SndFile.m_nDefaultSpeed = 6;
  544. // Set up mix levels
  545. m_SndFile.m_PlayState.m_nGlobalVolume = m_SndFile.m_nDefaultGlobalVolume = MAX_GLOBAL_VOLUME;
  546. m_SndFile.m_nSamplePreAmp = m_SndFile.m_nVSTiVolume = 48;
  547. for (CHANNELINDEX nChn = 0; nChn < MAX_BASECHANNELS; nChn++)
  548. {
  549. m_SndFile.ChnSettings[nChn].dwFlags.reset();
  550. m_SndFile.ChnSettings[nChn].nVolume = 64;
  551. m_SndFile.ChnSettings[nChn].nPan = 128;
  552. m_SndFile.m_PlayState.Chn[nChn].nGlobalVol = 64;
  553. }
  554. // Setup LRRL panning scheme for MODs
  555. m_SndFile.SetupMODPanning();
  556. }
  557. if (!m_SndFile.m_nSamples)
  558. {
  559. m_SndFile.m_szNames[1] = "untitled";
  560. m_SndFile.m_nSamples = (GetModType() == MOD_TYPE_MOD) ? 31 : 1;
  561. SampleEdit::ResetSamples(m_SndFile, SampleEdit::SmpResetInit);
  562. m_SndFile.GetSample(1).Initialize(m_SndFile.GetType());
  563. if ((!m_SndFile.m_nInstruments) && (m_SndFile.GetType() & MOD_TYPE_XM))
  564. {
  565. if(m_SndFile.AllocateInstrument(1, 1))
  566. {
  567. m_SndFile.m_nInstruments = 1;
  568. InitializeInstrument(m_SndFile.Instruments[1]);
  569. }
  570. }
  571. if (m_SndFile.GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_XM))
  572. {
  573. m_SndFile.m_SongFlags.set(SONG_LINEARSLIDES);
  574. }
  575. }
  576. m_SndFile.ResetPlayPos();
  577. m_SndFile.m_songArtist = TrackerSettings::Instance().defaultArtist;
  578. }
  579. bool CModDoc::SetDefaultChannelColors(CHANNELINDEX minChannel, CHANNELINDEX maxChannel)
  580. {
  581. LimitMax(minChannel, GetNumChannels());
  582. LimitMax(maxChannel, GetNumChannels());
  583. if(maxChannel < minChannel)
  584. std::swap(minChannel, maxChannel);
  585. bool modified = false;
  586. if(TrackerSettings::Instance().defaultRainbowChannelColors != DefaultChannelColors::NoColors)
  587. {
  588. const bool rainbow = TrackerSettings::Instance().defaultRainbowChannelColors == DefaultChannelColors::Rainbow;
  589. CHANNELINDEX numGroups = 0;
  590. if(rainbow)
  591. {
  592. for(CHANNELINDEX i = minChannel + 1u; i < maxChannel; i++)
  593. {
  594. if(m_SndFile.ChnSettings[i].szName.empty() || m_SndFile.ChnSettings[i].szName != m_SndFile.ChnSettings[i - 1].szName)
  595. numGroups++;
  596. }
  597. }
  598. const double hueFactor = rainbow ? (1.5 * mpt::numbers::pi) / std::max(1, numGroups - 1) : 1000.0; // Three quarters of the color wheel, red to purple
  599. for(CHANNELINDEX i = minChannel, group = minChannel; i < maxChannel; i++)
  600. {
  601. if(i > minChannel && (m_SndFile.ChnSettings[i].szName.empty() || m_SndFile.ChnSettings[i].szName != m_SndFile.ChnSettings[i - 1].szName))
  602. group++;
  603. const double hue = group * hueFactor; // 0...2pi
  604. const double saturation = 0.3; // 0...2/3
  605. const double brightness = 1.2; // 0...4/3
  606. const double r = brightness * (1 + saturation * (std::cos(hue) - 1.0));
  607. const double g = brightness * (1 + saturation * (std::cos(hue - 2.09439) - 1.0));
  608. const double b = brightness * (1 + saturation * (std::cos(hue + 2.09439) - 1.0));
  609. const auto color = RGB(mpt::saturate_round<uint8>(r * 255), mpt::saturate_round<uint8>(g * 255), mpt::saturate_round<uint8>(b * 255));
  610. if(m_SndFile.ChnSettings[i].color != color)
  611. {
  612. m_SndFile.ChnSettings[i].color = color;
  613. modified = true;
  614. }
  615. }
  616. } else
  617. {
  618. for(CHANNELINDEX i = minChannel; i < maxChannel; i++)
  619. {
  620. if(m_SndFile.ChnSettings[i].color != ModChannelSettings::INVALID_COLOR)
  621. {
  622. m_SndFile.ChnSettings[i].color = ModChannelSettings::INVALID_COLOR;
  623. modified = true;
  624. }
  625. }
  626. }
  627. return modified;
  628. }
  629. void CModDoc::PostMessageToAllViews(UINT uMsg, WPARAM wParam, LPARAM lParam)
  630. {
  631. POSITION pos = GetFirstViewPosition();
  632. while(pos != nullptr)
  633. {
  634. if(CView *pView = GetNextView(pos); pView != nullptr)
  635. pView->PostMessage(uMsg, wParam, lParam);
  636. }
  637. }
  638. void CModDoc::SendNotifyMessageToAllViews(UINT uMsg, WPARAM wParam, LPARAM lParam)
  639. {
  640. POSITION pos = GetFirstViewPosition();
  641. while(pos != nullptr)
  642. {
  643. if(CView *pView = GetNextView(pos); pView != nullptr)
  644. pView->SendNotifyMessage(uMsg, wParam, lParam);
  645. }
  646. }
  647. void CModDoc::SendMessageToActiveView(UINT uMsg, WPARAM wParam, LPARAM lParam)
  648. {
  649. if(auto *lastActiveFrame = CChildFrame::LastActiveFrame(); lastActiveFrame != nullptr)
  650. {
  651. lastActiveFrame->SendMessageToDescendants(uMsg, wParam, lParam);
  652. }
  653. }
  654. void CModDoc::ViewPattern(UINT nPat, UINT nOrd)
  655. {
  656. SendMessageToActiveView(WM_MOD_ACTIVATEVIEW, IDD_CONTROL_PATTERNS, ((nPat+1) << 16) | nOrd);
  657. }
  658. void CModDoc::ViewSample(UINT nSmp)
  659. {
  660. SendMessageToActiveView(WM_MOD_ACTIVATEVIEW, IDD_CONTROL_SAMPLES, nSmp);
  661. }
  662. void CModDoc::ViewInstrument(UINT nIns)
  663. {
  664. SendMessageToActiveView(WM_MOD_ACTIVATEVIEW, IDD_CONTROL_INSTRUMENTS, nIns);
  665. }
  666. ScopedLogCapturer::ScopedLogCapturer(CModDoc &modDoc, const CString &title, CWnd *parent, bool showLog) :
  667. m_modDoc(modDoc), m_oldLogMode(m_modDoc.GetLogMode()), m_title(title), m_pParent(parent), m_showLog(showLog)
  668. {
  669. m_modDoc.SetLogMode(LogModeGather);
  670. }
  671. void ScopedLogCapturer::ShowLog(bool force)
  672. {
  673. if(force || m_oldLogMode == LogModeInstantReporting)
  674. {
  675. m_modDoc.ShowLog(m_title, m_pParent);
  676. m_modDoc.ClearLog();
  677. }
  678. }
  679. void ScopedLogCapturer::ShowLog(const std::string &preamble, bool force)
  680. {
  681. if(force || m_oldLogMode == LogModeInstantReporting)
  682. {
  683. m_modDoc.ShowLog(mpt::ToCString(mpt::Charset::Locale, preamble), m_title, m_pParent);
  684. m_modDoc.ClearLog();
  685. }
  686. }
  687. void ScopedLogCapturer::ShowLog(const CString &preamble, bool force)
  688. {
  689. if(force || m_oldLogMode == LogModeInstantReporting)
  690. {
  691. m_modDoc.ShowLog(preamble, m_title, m_pParent);
  692. m_modDoc.ClearLog();
  693. }
  694. }
  695. void ScopedLogCapturer::ShowLog(const mpt::ustring &preamble, bool force)
  696. {
  697. if(force || m_oldLogMode == LogModeInstantReporting)
  698. {
  699. m_modDoc.ShowLog(mpt::ToCString(preamble), m_title, m_pParent);
  700. m_modDoc.ClearLog();
  701. }
  702. }
  703. ScopedLogCapturer::~ScopedLogCapturer()
  704. {
  705. if(m_showLog)
  706. ShowLog();
  707. else
  708. m_modDoc.ClearLog();
  709. m_modDoc.SetLogMode(m_oldLogMode);
  710. }
  711. void CModDoc::AddToLog(LogLevel level, const mpt::ustring &text) const
  712. {
  713. if(m_LogMode == LogModeGather)
  714. {
  715. m_Log.push_back(LogEntry(level, text));
  716. } else
  717. {
  718. if(level < LogDebug)
  719. {
  720. Reporting::Message(level, text);
  721. }
  722. }
  723. }
  724. mpt::ustring CModDoc::GetLogString() const
  725. {
  726. mpt::ustring ret;
  727. for(const auto &i : m_Log)
  728. {
  729. ret += i.message;
  730. ret += U_("\r\n");
  731. }
  732. return ret;
  733. }
  734. LogLevel CModDoc::GetMaxLogLevel() const
  735. {
  736. LogLevel retval = LogInformation;
  737. // find the most severe loglevel
  738. for(const auto &i : m_Log)
  739. {
  740. retval = std::min(retval, i.level);
  741. }
  742. return retval;
  743. }
  744. void CModDoc::ClearLog()
  745. {
  746. m_Log.clear();
  747. }
  748. UINT CModDoc::ShowLog(const CString &preamble, const CString &title, CWnd *parent)
  749. {
  750. if(!parent) parent = CMainFrame::GetMainFrame();
  751. if(GetLog().size() > 0)
  752. {
  753. LogLevel level = GetMaxLogLevel();
  754. if(level < LogDebug)
  755. {
  756. CString text = preamble + mpt::ToCString(GetLogString());
  757. CString actualTitle = (title.GetLength() == 0) ? CString(MAINFRAME_TITLE) : title;
  758. Reporting::Message(level, text, actualTitle, parent);
  759. return IDOK;
  760. }
  761. }
  762. return IDCANCEL;
  763. }
  764. void CModDoc::ProcessMIDI(uint32 midiData, INSTRUMENTINDEX ins, IMixPlugin *plugin, InputTargetContext ctx)
  765. {
  766. static uint8 midiVolume = 127;
  767. MIDIEvents::EventType event = MIDIEvents::GetTypeFromEvent(midiData);
  768. const uint8 channel = MIDIEvents::GetChannelFromEvent(midiData);
  769. const uint8 midiByte1 = MIDIEvents::GetDataByte1FromEvent(midiData);
  770. const uint8 midiByte2 = MIDIEvents::GetDataByte2FromEvent(midiData);
  771. uint8 note = midiByte1 + NOTE_MIN;
  772. int vol = midiByte2;
  773. if((event == MIDIEvents::evNoteOn) && !vol)
  774. event = MIDIEvents::evNoteOff; //Convert event to note-off if req'd
  775. PLUGINDEX mappedIndex = 0;
  776. PlugParamIndex paramIndex = 0;
  777. uint16 paramValue = 0;
  778. bool captured = m_SndFile.GetMIDIMapper().OnMIDImsg(midiData, mappedIndex, paramIndex, paramValue);
  779. // Handle MIDI messages assigned to shortcuts
  780. CInputHandler *ih = CMainFrame::GetInputHandler();
  781. if(ih->HandleMIDIMessage(ctx, midiData) != kcNull
  782. || ih->HandleMIDIMessage(kCtxAllContexts, midiData) != kcNull)
  783. {
  784. // Mapped to a command, no need to pass message on.
  785. captured = true;
  786. }
  787. if(captured)
  788. {
  789. // Event captured by MIDI mapping or shortcut, no need to pass message on.
  790. return;
  791. }
  792. switch(event)
  793. {
  794. case MIDIEvents::evNoteOff:
  795. if(m_midiSustainActive[channel])
  796. {
  797. m_midiSustainBuffer[channel].push_back(midiData);
  798. return;
  799. }
  800. if(ins > 0 && ins <= GetNumInstruments())
  801. {
  802. LimitMax(note, NOTE_MAX);
  803. if(m_midiPlayingNotes[channel][note])
  804. m_midiPlayingNotes[channel][note] = false;
  805. NoteOff(note, false, ins, m_noteChannel[note - NOTE_MIN]);
  806. return;
  807. } else if(plugin != nullptr)
  808. {
  809. plugin->MidiSend(midiData);
  810. }
  811. break;
  812. case MIDIEvents::evNoteOn:
  813. if(ins > 0 && ins <= GetNumInstruments())
  814. {
  815. LimitMax(note, NOTE_MAX);
  816. vol = CMainFrame::ApplyVolumeRelatedSettings(midiData, midiVolume);
  817. PlayNote(PlayNoteParam(note).Instrument(ins).Volume(vol).CheckNNA(m_midiPlayingNotes[channel]), &m_noteChannel);
  818. return;
  819. } else if(plugin != nullptr)
  820. {
  821. plugin->MidiSend(midiData);
  822. }
  823. break;
  824. case MIDIEvents::evControllerChange:
  825. switch(midiByte1)
  826. {
  827. case MIDIEvents::MIDICC_Volume_Coarse:
  828. midiVolume = midiByte2;
  829. break;
  830. case MIDIEvents::MIDICC_HoldPedal_OnOff:
  831. m_midiSustainActive[channel] = (midiByte2 >= 0x40);
  832. if(!m_midiSustainActive[channel])
  833. {
  834. // Release all notes
  835. for(const auto offEvent : m_midiSustainBuffer[channel])
  836. {
  837. ProcessMIDI(offEvent, ins, plugin, ctx);
  838. }
  839. m_midiSustainBuffer[channel].clear();
  840. }
  841. break;
  842. }
  843. break;
  844. }
  845. if((TrackerSettings::Instance().m_dwMidiSetup & MIDISETUP_MIDITOPLUG) && CMainFrame::GetMainFrame()->GetModPlaying() == this && plugin != nullptr)
  846. {
  847. plugin->MidiSend(midiData);
  848. // Sending midi may modify the plug. For now, if MIDI data is not active sensing or aftertouch messages, set modified.
  849. if(midiData != MIDIEvents::System(MIDIEvents::sysActiveSense)
  850. && event != MIDIEvents::evPolyAftertouch && event != MIDIEvents::evChannelAftertouch
  851. && event != MIDIEvents::evPitchBend
  852. && m_SndFile.GetModSpecifications().supportsPlugins)
  853. {
  854. SetModified();
  855. }
  856. }
  857. }
  858. CHANNELINDEX CModDoc::PlayNote(PlayNoteParam &params, NoteToChannelMap *noteChannel)
  859. {
  860. CHANNELINDEX channel = GetNumChannels();
  861. ModCommand::NOTE note = params.m_note;
  862. if(ModCommand::IsNote(ModCommand::NOTE(note)))
  863. {
  864. CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
  865. if(pMainFrm == nullptr || note == NOTE_NONE) return CHANNELINDEX_INVALID;
  866. if (pMainFrm->GetModPlaying() != this)
  867. {
  868. // All notes off when resuming paused playback
  869. m_SndFile.ResetChannels();
  870. m_SndFile.m_SongFlags.set(SONG_PAUSED);
  871. pMainFrm->PlayMod(this);
  872. }
  873. CriticalSection cs;
  874. if(params.m_notesPlaying)
  875. CheckNNA(note, params.m_instr, *params.m_notesPlaying);
  876. // Find a channel to play on
  877. channel = FindAvailableChannel();
  878. ModChannel &chn = m_SndFile.m_PlayState.Chn[channel];
  879. // reset channel properties; in theory the chan is completely unused anyway.
  880. chn.Reset(ModChannel::resetTotal, m_SndFile, CHANNELINDEX_INVALID, CHN_MUTE);
  881. chn.nNewNote = chn.nLastNote = static_cast<uint8>(note);
  882. chn.nVolume = 256;
  883. if(params.m_instr)
  884. {
  885. // Set instrument (or sample if there are no instruments)
  886. chn.ResetEnvelopes();
  887. m_SndFile.InstrumentChange(chn, params.m_instr);
  888. } else if(params.m_sample > 0 && params.m_sample <= GetNumSamples()) // Or set sample explicitely
  889. {
  890. ModSample &sample = m_SndFile.GetSample(params.m_sample);
  891. chn.pCurrentSample = sample.samplev();
  892. chn.pModInstrument = nullptr;
  893. chn.pModSample = &sample;
  894. chn.nFineTune = sample.nFineTune;
  895. chn.nC5Speed = sample.nC5Speed;
  896. chn.nLoopStart = sample.nLoopStart;
  897. chn.nLoopEnd = sample.nLoopEnd;
  898. chn.dwFlags = (sample.uFlags & (CHN_SAMPLEFLAGS & ~CHN_MUTE));
  899. chn.nPan = 128;
  900. if(sample.uFlags[CHN_PANNING]) chn.nPan = sample.nPan;
  901. chn.UpdateInstrumentVolume(&sample, nullptr);
  902. }
  903. chn.nFadeOutVol = 0x10000;
  904. chn.isPreviewNote = true;
  905. if(params.m_currentChannel != CHANNELINDEX_INVALID)
  906. chn.nMasterChn = params.m_currentChannel + 1;
  907. else
  908. chn.nMasterChn = 0;
  909. if(chn.dwFlags[CHN_ADLIB] && chn.pModSample && m_SndFile.m_opl)
  910. {
  911. m_SndFile.m_opl->Patch(channel, chn.pModSample->adlib);
  912. }
  913. m_SndFile.NoteChange(chn, note, false, true, true, channel);
  914. if(params.m_volume >= 0) chn.nVolume = std::min(params.m_volume, 256);
  915. // Handle sample looping.
  916. // Changed line to fix http://forum.openmpt.org/index.php?topic=1700.0
  917. //if ((loopstart + 16 < loopend) && (loopstart >= 0) && (loopend <= (LONG)pchn.nLength))
  918. if ((params.m_loopStart + 16 < params.m_loopEnd) && (params.m_loopStart >= 0) && (chn.pModSample != nullptr))
  919. {
  920. chn.position.Set(params.m_loopStart);
  921. chn.nLoopStart = params.m_loopStart;
  922. chn.nLoopEnd = params.m_loopEnd;
  923. chn.nLength = std::min(params.m_loopEnd, chn.pModSample->nLength);
  924. }
  925. // Handle extra-loud flag
  926. chn.dwFlags.set(CHN_EXTRALOUD, !(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_NOEXTRALOUD) && params.m_sample);
  927. // Handle custom start position
  928. if(params.m_sampleOffset > 0 && chn.pModSample)
  929. {
  930. chn.position.Set(params.m_sampleOffset);
  931. // If start position is after loop end, set loop end to sample end so that the sample starts
  932. // playing.
  933. if(chn.nLoopEnd < params.m_sampleOffset)
  934. chn.nLength = chn.nLoopEnd = chn.pModSample->nLength;
  935. }
  936. // VSTi preview
  937. if(params.m_instr > 0 && params.m_instr <= m_SndFile.GetNumInstruments())
  938. {
  939. const ModInstrument *pIns = m_SndFile.Instruments[params.m_instr];
  940. if (pIns && pIns->HasValidMIDIChannel()) // instro sends to a midi chan
  941. {
  942. PLUGINDEX nPlugin = 0;
  943. if (chn.pModInstrument)
  944. nPlugin = chn.pModInstrument->nMixPlug; // First try instrument plugin
  945. if ((!nPlugin || nPlugin > MAX_MIXPLUGINS) && params.m_currentChannel != CHANNELINDEX_INVALID)
  946. nPlugin = m_SndFile.ChnSettings[params.m_currentChannel].nMixPlugin; // Then try channel plugin
  947. if ((nPlugin) && (nPlugin <= MAX_MIXPLUGINS))
  948. {
  949. IMixPlugin *pPlugin = m_SndFile.m_MixPlugins[nPlugin - 1].pMixPlugin;
  950. if(pPlugin != nullptr)
  951. {
  952. pPlugin->MidiCommand(*pIns, pIns->NoteMap[note - NOTE_MIN], static_cast<uint16>(chn.nVolume), channel);
  953. }
  954. }
  955. }
  956. }
  957. // Remove channel from list of mixed channels to fix https://bugs.openmpt.org/view.php?id=209
  958. // This is required because a previous note on the same channel might have just stopped playing,
  959. // but the channel is still in the mix list.
  960. // Since the channel volume / etc is only updated every tick in CSoundFile::ReadNote, and we
  961. // do not want to duplicate mixmode-dependant logic here, CSoundFile::CreateStereoMix may already
  962. // try to mix our newly set up channel at volume 0 if we don't remove it from the list.
  963. auto mixBegin = std::begin(m_SndFile.m_PlayState.ChnMix);
  964. auto mixEnd = std::remove(mixBegin, mixBegin + m_SndFile.m_nMixChannels, channel);
  965. m_SndFile.m_nMixChannels = static_cast<CHANNELINDEX>(std::distance(mixBegin, mixEnd));
  966. if(noteChannel)
  967. {
  968. noteChannel->at(note - NOTE_MIN) = channel;
  969. }
  970. } else
  971. {
  972. CriticalSection cs;
  973. // Apply note cut / off / fade (also on preview channels)
  974. m_SndFile.NoteChange(m_SndFile.m_PlayState.Chn[channel], note);
  975. for(CHANNELINDEX c = m_SndFile.GetNumChannels(); c < MAX_CHANNELS; c++)
  976. {
  977. ModChannel &chn = m_SndFile.m_PlayState.Chn[c];
  978. if(chn.isPreviewNote && (chn.pModSample || chn.pModInstrument))
  979. {
  980. m_SndFile.NoteChange(chn, note);
  981. }
  982. }
  983. }
  984. return channel;
  985. }
  986. bool CModDoc::NoteOff(UINT note, bool fade, INSTRUMENTINDEX ins, CHANNELINDEX currentChn)
  987. {
  988. CriticalSection cs;
  989. if(ins != INSTRUMENTINDEX_INVALID && ins <= m_SndFile.GetNumInstruments() && ModCommand::IsNote(ModCommand::NOTE(note)))
  990. {
  991. const ModInstrument *pIns = m_SndFile.Instruments[ins];
  992. if(pIns && pIns->HasValidMIDIChannel()) // instro sends to a midi chan
  993. {
  994. PLUGINDEX plug = pIns->nMixPlug; // First try intrument VST
  995. if((!plug || plug > MAX_MIXPLUGINS) // No good plug yet
  996. && currentChn < MAX_BASECHANNELS) // Chan OK
  997. {
  998. plug = m_SndFile.ChnSettings[currentChn].nMixPlugin;// Then try Channel VST
  999. }
  1000. if(plug && plug <= MAX_MIXPLUGINS)
  1001. {
  1002. IMixPlugin *pPlugin = m_SndFile.m_MixPlugins[plug - 1].pMixPlugin;
  1003. if(pPlugin)
  1004. {
  1005. pPlugin->MidiCommand(*pIns, pIns->NoteMap[note - NOTE_MIN] + NOTE_KEYOFF, 0, currentChn);
  1006. }
  1007. }
  1008. }
  1009. }
  1010. const FlagSet<ChannelFlags> mask = (fade ? CHN_NOTEFADE : (CHN_NOTEFADE | CHN_KEYOFF));
  1011. const CHANNELINDEX startChn = currentChn != CHANNELINDEX_INVALID ? currentChn : m_SndFile.m_nChannels;
  1012. const CHANNELINDEX endChn = currentChn != CHANNELINDEX_INVALID ? currentChn + 1 : MAX_CHANNELS;
  1013. ModChannel *pChn = &m_SndFile.m_PlayState.Chn[startChn];
  1014. for(CHANNELINDEX i = startChn; i < endChn; i++, pChn++)
  1015. {
  1016. // Fade all channels > m_nChannels which are playing this note and aren't NNA channels.
  1017. if((pChn->isPreviewNote || i < m_SndFile.GetNumChannels())
  1018. && !pChn->dwFlags[mask]
  1019. && (pChn->nLength || pChn->dwFlags[CHN_ADLIB])
  1020. && (note == pChn->nNewNote || note == NOTE_NONE))
  1021. {
  1022. m_SndFile.KeyOff(*pChn);
  1023. if (!m_SndFile.m_nInstruments) pChn->dwFlags.reset(CHN_LOOP | CHN_PINGPONGFLAG);
  1024. if (fade) pChn->dwFlags.set(CHN_NOTEFADE);
  1025. // Instantly stop samples that would otherwise play forever
  1026. if (pChn->pModInstrument && !pChn->pModInstrument->nFadeOut)
  1027. pChn->nFadeOutVol = 0;
  1028. if(pChn->dwFlags[CHN_ADLIB] && m_SndFile.m_opl)
  1029. {
  1030. m_SndFile.m_opl->NoteOff(i);
  1031. }
  1032. if (note) break;
  1033. }
  1034. }
  1035. return true;
  1036. }
  1037. // Apply DNA/NNA settings for note preview. It will also set the specified note to be playing in the playingNotes set.
  1038. void CModDoc::CheckNNA(ModCommand::NOTE note, INSTRUMENTINDEX ins, std::bitset<128> &playingNotes)
  1039. {
  1040. if(ins > GetNumInstruments() || m_SndFile.Instruments[ins] == nullptr || note >= playingNotes.size())
  1041. {
  1042. return;
  1043. }
  1044. const ModInstrument *pIns = m_SndFile.Instruments[ins];
  1045. for(CHANNELINDEX chn = GetNumChannels(); chn < MAX_CHANNELS; chn++)
  1046. {
  1047. const ModChannel &channel = m_SndFile.m_PlayState.Chn[chn];
  1048. if(channel.pModInstrument == pIns && channel.isPreviewNote && ModCommand::IsNote(channel.nLastNote)
  1049. && (channel.nLength || pIns->HasValidMIDIChannel()) && !playingNotes[channel.nLastNote])
  1050. {
  1051. CHANNELINDEX nnaChn = m_SndFile.CheckNNA(chn, ins, note, false);
  1052. if(nnaChn != CHANNELINDEX_INVALID)
  1053. {
  1054. // Keep the new NNA channel playing in the same channel slot.
  1055. // That way, we do not need to touch the ChnMix array, and we avoid the same channel being checked twice.
  1056. if(nnaChn != chn)
  1057. {
  1058. m_SndFile.m_PlayState.Chn[chn] = std::move(m_SndFile.m_PlayState.Chn[nnaChn]);
  1059. m_SndFile.m_PlayState.Chn[nnaChn] = {};
  1060. }
  1061. // Avoid clicks if the channel wasn't ramping before.
  1062. m_SndFile.m_PlayState.Chn[chn].dwFlags.set(CHN_FASTVOLRAMP);
  1063. m_SndFile.ProcessRamping(m_SndFile.m_PlayState.Chn[chn]);
  1064. }
  1065. }
  1066. }
  1067. playingNotes.set(note);
  1068. }
  1069. // Check if a given note of an instrument or sample is playing from the editor.
  1070. // If note == 0, just check if an instrument or sample is playing.
  1071. bool CModDoc::IsNotePlaying(UINT note, SAMPLEINDEX nsmp, INSTRUMENTINDEX nins)
  1072. {
  1073. ModChannel *pChn = &m_SndFile.m_PlayState.Chn[m_SndFile.GetNumChannels()];
  1074. for (CHANNELINDEX i = m_SndFile.GetNumChannels(); i < MAX_CHANNELS; i++, pChn++) if (pChn->isPreviewNote)
  1075. {
  1076. if(pChn->nLength != 0 && !pChn->dwFlags[CHN_NOTEFADE | CHN_KEYOFF| CHN_MUTE]
  1077. && (note == pChn->nNewNote || note == NOTE_NONE)
  1078. && (pChn->pModSample == &m_SndFile.GetSample(nsmp) || !nsmp)
  1079. && (pChn->pModInstrument == m_SndFile.Instruments[nins] || !nins)) return true;
  1080. }
  1081. return false;
  1082. }
  1083. bool CModDoc::MuteToggleModifiesDocument() const
  1084. {
  1085. return (m_SndFile.GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_S3M)) && TrackerSettings::Instance().MiscSaveChannelMuteStatus;
  1086. }
  1087. bool CModDoc::MuteChannel(CHANNELINDEX nChn, bool doMute)
  1088. {
  1089. if (nChn >= m_SndFile.GetNumChannels())
  1090. {
  1091. return false;
  1092. }
  1093. // Mark channel as muted in channel settings
  1094. m_SndFile.ChnSettings[nChn].dwFlags.set(CHN_MUTE, doMute);
  1095. const bool success = UpdateChannelMuteStatus(nChn);
  1096. if(success && MuteToggleModifiesDocument())
  1097. {
  1098. SetModified();
  1099. }
  1100. return success;
  1101. }
  1102. bool CModDoc::UpdateChannelMuteStatus(CHANNELINDEX nChn)
  1103. {
  1104. const ChannelFlags muteType = CSoundFile::GetChannelMuteFlag();
  1105. if (nChn >= m_SndFile.GetNumChannels())
  1106. {
  1107. return false;
  1108. }
  1109. const bool doMute = m_SndFile.ChnSettings[nChn].dwFlags[CHN_MUTE];
  1110. // Mute pattern channel
  1111. if (doMute)
  1112. {
  1113. m_SndFile.m_PlayState.Chn[nChn].dwFlags.set(muteType);
  1114. if(m_SndFile.m_opl) m_SndFile.m_opl->NoteCut(nChn);
  1115. // Kill VSTi notes on muted channel.
  1116. PLUGINDEX nPlug = m_SndFile.GetBestPlugin(m_SndFile.m_PlayState, nChn, PrioritiseInstrument, EvenIfMuted);
  1117. if ((nPlug) && (nPlug<=MAX_MIXPLUGINS))
  1118. {
  1119. IMixPlugin *pPlug = m_SndFile.m_MixPlugins[nPlug - 1].pMixPlugin;
  1120. const ModInstrument* pIns = m_SndFile.m_PlayState.Chn[nChn].pModInstrument;
  1121. if (pPlug && pIns)
  1122. {
  1123. pPlug->MidiCommand(*pIns, NOTE_KEYOFF, 0, nChn);
  1124. }
  1125. }
  1126. } else
  1127. {
  1128. // On unmute alway cater for both mute types - this way there's no probs if user changes mute mode.
  1129. m_SndFile.m_PlayState.Chn[nChn].dwFlags.reset(CHN_SYNCMUTE | CHN_MUTE);
  1130. }
  1131. // Mute any NNA'd channels
  1132. for (CHANNELINDEX i = m_SndFile.GetNumChannels(); i < MAX_CHANNELS; i++)
  1133. {
  1134. if (m_SndFile.m_PlayState.Chn[i].nMasterChn == nChn + 1u)
  1135. {
  1136. if (doMute)
  1137. {
  1138. m_SndFile.m_PlayState.Chn[i].dwFlags.set(muteType);
  1139. if(m_SndFile.m_opl) m_SndFile.m_opl->NoteCut(i);
  1140. } else
  1141. {
  1142. // On unmute alway cater for both mute types - this way there's no probs if user changes mute mode.
  1143. m_SndFile.m_PlayState.Chn[i].dwFlags.reset(CHN_SYNCMUTE | CHN_MUTE);
  1144. }
  1145. }
  1146. }
  1147. return true;
  1148. }
  1149. bool CModDoc::IsChannelSolo(CHANNELINDEX nChn) const
  1150. {
  1151. if (nChn >= m_SndFile.m_nChannels) return true;
  1152. return m_SndFile.ChnSettings[nChn].dwFlags[CHN_SOLO];
  1153. }
  1154. bool CModDoc::SoloChannel(CHANNELINDEX nChn, bool bSolo)
  1155. {
  1156. if (nChn >= m_SndFile.m_nChannels) return false;
  1157. if (MuteToggleModifiesDocument()) SetModified();
  1158. m_SndFile.ChnSettings[nChn].dwFlags.set(CHN_SOLO, bSolo);
  1159. return true;
  1160. }
  1161. bool CModDoc::IsChannelNoFx(CHANNELINDEX nChn) const
  1162. {
  1163. if (nChn >= m_SndFile.m_nChannels) return true;
  1164. return m_SndFile.ChnSettings[nChn].dwFlags[CHN_NOFX];
  1165. }
  1166. bool CModDoc::NoFxChannel(CHANNELINDEX nChn, bool bNoFx, bool updateMix)
  1167. {
  1168. if (nChn >= m_SndFile.m_nChannels) return false;
  1169. m_SndFile.ChnSettings[nChn].dwFlags.set(CHN_NOFX, bNoFx);
  1170. if(updateMix) m_SndFile.m_PlayState.Chn[nChn].dwFlags.set(CHN_NOFX, bNoFx);
  1171. return true;
  1172. }
  1173. RecordGroup CModDoc::GetChannelRecordGroup(CHANNELINDEX channel) const
  1174. {
  1175. if(channel >= GetNumChannels())
  1176. return RecordGroup::NoGroup;
  1177. if(m_bsMultiRecordMask[channel])
  1178. return RecordGroup::Group1;
  1179. if(m_bsMultiSplitRecordMask[channel])
  1180. return RecordGroup::Group2;
  1181. return RecordGroup::NoGroup;
  1182. }
  1183. void CModDoc::SetChannelRecordGroup(CHANNELINDEX channel, RecordGroup recordGroup)
  1184. {
  1185. if(channel >= GetNumChannels())
  1186. return;
  1187. m_bsMultiRecordMask.set(channel, recordGroup == RecordGroup::Group1);
  1188. m_bsMultiSplitRecordMask.set(channel, recordGroup == RecordGroup::Group2);
  1189. }
  1190. void CModDoc::ToggleChannelRecordGroup(CHANNELINDEX channel, RecordGroup recordGroup)
  1191. {
  1192. if(channel >= GetNumChannels())
  1193. return;
  1194. if(recordGroup == RecordGroup::Group1)
  1195. {
  1196. m_bsMultiRecordMask.flip(channel);
  1197. m_bsMultiSplitRecordMask.reset(channel);
  1198. } else if(recordGroup == RecordGroup::Group2)
  1199. {
  1200. m_bsMultiRecordMask.reset(channel);
  1201. m_bsMultiSplitRecordMask.flip(channel);
  1202. }
  1203. }
  1204. void CModDoc::ReinitRecordState(bool unselect)
  1205. {
  1206. if(unselect)
  1207. {
  1208. m_bsMultiRecordMask.reset();
  1209. m_bsMultiSplitRecordMask.reset();
  1210. } else
  1211. {
  1212. m_bsMultiRecordMask.set();
  1213. m_bsMultiSplitRecordMask.set();
  1214. }
  1215. }
  1216. bool CModDoc::MuteSample(SAMPLEINDEX nSample, bool bMute)
  1217. {
  1218. if ((nSample < 1) || (nSample > m_SndFile.GetNumSamples())) return false;
  1219. m_SndFile.GetSample(nSample).uFlags.set(CHN_MUTE, bMute);
  1220. return true;
  1221. }
  1222. bool CModDoc::MuteInstrument(INSTRUMENTINDEX nInstr, bool bMute)
  1223. {
  1224. if ((nInstr < 1) || (nInstr > m_SndFile.GetNumInstruments()) || (!m_SndFile.Instruments[nInstr])) return false;
  1225. m_SndFile.Instruments[nInstr]->dwFlags.set(INS_MUTE, bMute);
  1226. return true;
  1227. }
  1228. bool CModDoc::SurroundChannel(CHANNELINDEX nChn, bool surround)
  1229. {
  1230. if(nChn >= m_SndFile.GetNumChannels()) return false;
  1231. if(!(m_SndFile.GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT))) surround = false;
  1232. if(surround != m_SndFile.ChnSettings[nChn].dwFlags[CHN_SURROUND])
  1233. {
  1234. // Update channel configuration
  1235. if(m_SndFile.GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) SetModified();
  1236. m_SndFile.ChnSettings[nChn].dwFlags.set(CHN_SURROUND, surround);
  1237. if(surround)
  1238. {
  1239. m_SndFile.ChnSettings[nChn].nPan = 128;
  1240. }
  1241. }
  1242. // Update playing channel
  1243. m_SndFile.m_PlayState.Chn[nChn].dwFlags.set(CHN_SURROUND, surround);
  1244. if(surround)
  1245. {
  1246. m_SndFile.m_PlayState.Chn[nChn].nPan = 128;
  1247. }
  1248. return true;
  1249. }
  1250. bool CModDoc::SetChannelGlobalVolume(CHANNELINDEX nChn, uint16 nVolume)
  1251. {
  1252. bool ok = false;
  1253. if(nChn >= m_SndFile.GetNumChannels() || nVolume > 64) return false;
  1254. if(m_SndFile.ChnSettings[nChn].nVolume != nVolume)
  1255. {
  1256. m_SndFile.ChnSettings[nChn].nVolume = nVolume;
  1257. if(m_SndFile.GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) SetModified();
  1258. ok = true;
  1259. }
  1260. m_SndFile.m_PlayState.Chn[nChn].nGlobalVol = nVolume;
  1261. return ok;
  1262. }
  1263. bool CModDoc::SetChannelDefaultPan(CHANNELINDEX nChn, uint16 nPan)
  1264. {
  1265. bool ok = false;
  1266. if(nChn >= m_SndFile.GetNumChannels() || nPan > 256) return false;
  1267. if(m_SndFile.ChnSettings[nChn].nPan != nPan || m_SndFile.ChnSettings[nChn].dwFlags[CHN_SURROUND])
  1268. {
  1269. m_SndFile.ChnSettings[nChn].nPan = nPan;
  1270. m_SndFile.ChnSettings[nChn].dwFlags.reset(CHN_SURROUND);
  1271. if(m_SndFile.GetType() & (MOD_TYPE_S3M | MOD_TYPE_IT | MOD_TYPE_MPT)) SetModified();
  1272. ok = true;
  1273. }
  1274. m_SndFile.m_PlayState.Chn[nChn].nPan = nPan;
  1275. m_SndFile.m_PlayState.Chn[nChn].dwFlags.reset(CHN_SURROUND);
  1276. return ok;
  1277. }
  1278. bool CModDoc::IsChannelMuted(CHANNELINDEX nChn) const
  1279. {
  1280. if(nChn >= m_SndFile.GetNumChannels()) return true;
  1281. return m_SndFile.ChnSettings[nChn].dwFlags[CHN_MUTE];
  1282. }
  1283. bool CModDoc::IsSampleMuted(SAMPLEINDEX nSample) const
  1284. {
  1285. if(!nSample || nSample > m_SndFile.GetNumSamples()) return false;
  1286. return m_SndFile.GetSample(nSample).uFlags[CHN_MUTE];
  1287. }
  1288. bool CModDoc::IsInstrumentMuted(INSTRUMENTINDEX nInstr) const
  1289. {
  1290. if(!nInstr || nInstr > m_SndFile.GetNumInstruments() || !m_SndFile.Instruments[nInstr]) return false;
  1291. return m_SndFile.Instruments[nInstr]->dwFlags[INS_MUTE];
  1292. }
  1293. UINT CModDoc::GetPatternSize(PATTERNINDEX nPat) const
  1294. {
  1295. if(m_SndFile.Patterns.IsValidIndex(nPat)) return m_SndFile.Patterns[nPat].GetNumRows();
  1296. return 0;
  1297. }
  1298. void CModDoc::SetFollowWnd(HWND hwnd)
  1299. {
  1300. m_hWndFollow = hwnd;
  1301. }
  1302. bool CModDoc::IsChildSample(INSTRUMENTINDEX nIns, SAMPLEINDEX nSmp) const
  1303. {
  1304. return m_SndFile.IsSampleReferencedByInstrument(nSmp, nIns);
  1305. }
  1306. // Find an instrument that references the given sample.
  1307. // If no such instrument is found, INSTRUMENTINDEX_INVALID is returned.
  1308. INSTRUMENTINDEX CModDoc::FindSampleParent(SAMPLEINDEX sample) const
  1309. {
  1310. if(sample == 0)
  1311. {
  1312. return INSTRUMENTINDEX_INVALID;
  1313. }
  1314. for(INSTRUMENTINDEX i = 1; i <= m_SndFile.GetNumInstruments(); i++)
  1315. {
  1316. const ModInstrument *pIns = m_SndFile.Instruments[i];
  1317. if(pIns != nullptr)
  1318. {
  1319. for(size_t j = 0; j < NOTE_MAX; j++)
  1320. {
  1321. if(pIns->Keyboard[j] == sample)
  1322. {
  1323. return i;
  1324. }
  1325. }
  1326. }
  1327. }
  1328. return INSTRUMENTINDEX_INVALID;
  1329. }
  1330. SAMPLEINDEX CModDoc::FindInstrumentChild(INSTRUMENTINDEX nIns) const
  1331. {
  1332. if ((!nIns) || (nIns > m_SndFile.GetNumInstruments())) return 0;
  1333. const ModInstrument *pIns = m_SndFile.Instruments[nIns];
  1334. if (pIns)
  1335. {
  1336. for (auto n : pIns->Keyboard)
  1337. {
  1338. if ((n) && (n <= m_SndFile.GetNumSamples())) return n;
  1339. }
  1340. }
  1341. return 0;
  1342. }
  1343. LRESULT CModDoc::ActivateView(UINT nIdView, DWORD dwParam)
  1344. {
  1345. CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
  1346. if (!pMainFrm) return 0;
  1347. CMDIChildWnd *pMDIActive = pMainFrm->MDIGetActive();
  1348. if (pMDIActive)
  1349. {
  1350. CView *pView = pMDIActive->GetActiveView();
  1351. if ((pView) && (pView->GetDocument() == this))
  1352. {
  1353. return ((CChildFrame *)pMDIActive)->ActivateView(nIdView, dwParam);
  1354. }
  1355. }
  1356. POSITION pos = GetFirstViewPosition();
  1357. while (pos != NULL)
  1358. {
  1359. CView *pView = GetNextView(pos);
  1360. if ((pView) && (pView->GetDocument() == this))
  1361. {
  1362. CChildFrame *pChildFrm = (CChildFrame *)pView->GetParentFrame();
  1363. pChildFrm->MDIActivate();
  1364. return pChildFrm->ActivateView(nIdView, dwParam);
  1365. }
  1366. }
  1367. return 0;
  1368. }
  1369. // Activate document's window.
  1370. void CModDoc::ActivateWindow()
  1371. {
  1372. CChildFrame *pChildFrm = GetChildFrame();
  1373. if(pChildFrm) pChildFrm->MDIActivate();
  1374. }
  1375. void CModDoc::UpdateAllViews(CView *pSender, UpdateHint hint, CObject *pHint)
  1376. {
  1377. // Tunnel our UpdateHint into an LPARAM
  1378. CDocument::UpdateAllViews(pSender, hint.AsLPARAM(), pHint);
  1379. CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
  1380. if (pMainFrm) pMainFrm->UpdateTree(this, hint, pHint);
  1381. if(hint.GetType()[HINT_MODCHANNELS | HINT_MODTYPE])
  1382. {
  1383. auto instance = CChannelManagerDlg::sharedInstance();
  1384. if(instance != nullptr && pHint != instance && instance->GetDocument() == this)
  1385. instance->Update(hint, pHint);
  1386. }
  1387. #ifndef NO_PLUGINS
  1388. if(hint.GetType()[HINT_MIXPLUGINS | HINT_PLUGINNAMES])
  1389. {
  1390. for(auto &plug : m_SndFile.m_MixPlugins)
  1391. {
  1392. auto mixPlug = plug.pMixPlugin;
  1393. if(mixPlug != nullptr && mixPlug->GetEditor())
  1394. {
  1395. mixPlug->GetEditor()->UpdateView(hint);
  1396. }
  1397. }
  1398. }
  1399. #endif
  1400. }
  1401. void CModDoc::UpdateAllViews(UpdateHint hint)
  1402. {
  1403. CMainFrame::GetMainFrame()->SendNotifyMessage(WM_MOD_UPDATEVIEWS, reinterpret_cast<WPARAM>(this), hint.AsLPARAM());
  1404. }
  1405. /////////////////////////////////////////////////////////////////////////////
  1406. // CModDoc commands
  1407. void CModDoc::OnFileWaveConvert()
  1408. {
  1409. OnFileWaveConvert(ORDERINDEX_INVALID, ORDERINDEX_INVALID);
  1410. }
  1411. void CModDoc::OnFileWaveConvert(ORDERINDEX nMinOrder, ORDERINDEX nMaxOrder, const std::vector<EncoderFactoryBase*> &encFactories)
  1412. {
  1413. ASSERT(!encFactories.empty());
  1414. CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
  1415. if ((!pMainFrm) || (!m_SndFile.GetType()) || encFactories.empty()) return;
  1416. CWaveConvert wsdlg(pMainFrm, nMinOrder, nMaxOrder, m_SndFile.Order().GetLengthTailTrimmed() - 1, m_SndFile, encFactories);
  1417. {
  1418. BypassInputHandler bih;
  1419. if (wsdlg.DoModal() != IDOK) return;
  1420. }
  1421. EncoderFactoryBase *encFactory = wsdlg.m_Settings.GetEncoderFactory();
  1422. const mpt::PathString extension = encFactory->GetTraits().fileExtension;
  1423. FileDialog dlg = SaveFileDialog()
  1424. .DefaultExtension(extension)
  1425. .DefaultFilename(GetPathNameMpt().GetFileName() + P_(".") + extension)
  1426. .ExtensionFilter(encFactory->GetTraits().fileDescription + U_(" (*.") + extension.ToUnicode() + U_(")|*.") + extension.ToUnicode() + U_("||"))
  1427. .WorkingDirectory(TrackerSettings::Instance().PathExport.GetWorkingDir());
  1428. if(!wsdlg.m_Settings.outputToSample && !dlg.Show()) return;
  1429. // will set default dir here because there's no setup option for export dir yet (feel free to add one...)
  1430. TrackerSettings::Instance().PathExport.SetDefaultDir(dlg.GetWorkingDirectory(), true);
  1431. mpt::PathString drive, dir, name, ext;
  1432. dlg.GetFirstFile().SplitPath(&drive, &dir, &name, &ext);
  1433. const mpt::PathString fileName = drive + dir + name;
  1434. const mpt::PathString fileExt = ext;
  1435. const ORDERINDEX currentOrd = m_SndFile.m_PlayState.m_nCurrentOrder;
  1436. const ROWINDEX currentRow = m_SndFile.m_PlayState.m_nRow;
  1437. int nRenderPasses = 1;
  1438. // Channel mode
  1439. std::vector<bool> usedChannels;
  1440. std::vector<FlagSet<ChannelFlags>> channelFlags;
  1441. // Instrument mode
  1442. std::vector<bool> instrMuteState;
  1443. // CHN_SYNCMUTE is used with formats where CHN_MUTE would stop processing global effects and could thus mess synchronization between exported channels
  1444. const ChannelFlags muteFlag = m_SndFile.m_playBehaviour[kST3NoMutedChannels] ? CHN_SYNCMUTE : CHN_MUTE;
  1445. // Channel mode: save song in multiple wav files (one for each enabled channels)
  1446. if(wsdlg.m_bChannelMode)
  1447. {
  1448. // Don't save empty channels
  1449. CheckUsedChannels(usedChannels);
  1450. nRenderPasses = m_SndFile.GetNumChannels();
  1451. channelFlags.resize(nRenderPasses, ChannelFlags(0));
  1452. for(CHANNELINDEX i = 0; i < m_SndFile.GetNumChannels(); i++)
  1453. {
  1454. // Save channels' flags
  1455. channelFlags[i] = m_SndFile.ChnSettings[i].dwFlags;
  1456. // Ignore muted channels
  1457. if(channelFlags[i][CHN_MUTE]) usedChannels[i] = false;
  1458. // Mute each channel
  1459. m_SndFile.ChnSettings[i].dwFlags.set(muteFlag);
  1460. }
  1461. }
  1462. // Instrument mode: Same as channel mode, but renders per instrument (or sample)
  1463. if(wsdlg.m_bInstrumentMode)
  1464. {
  1465. if(m_SndFile.GetNumInstruments() == 0)
  1466. {
  1467. nRenderPasses = m_SndFile.GetNumSamples();
  1468. instrMuteState.resize(nRenderPasses, false);
  1469. for(SAMPLEINDEX i = 0; i < m_SndFile.GetNumSamples(); i++)
  1470. {
  1471. instrMuteState[i] = IsSampleMuted(i + 1);
  1472. MuteSample(i + 1, true);
  1473. }
  1474. } else
  1475. {
  1476. nRenderPasses = m_SndFile.GetNumInstruments();
  1477. instrMuteState.resize(nRenderPasses, false);
  1478. for(INSTRUMENTINDEX i = 0; i < m_SndFile.GetNumInstruments(); i++)
  1479. {
  1480. instrMuteState[i] = IsInstrumentMuted(i + 1);
  1481. MuteInstrument(i + 1, true);
  1482. }
  1483. }
  1484. }
  1485. pMainFrm->PauseMod(this);
  1486. int oldRepeat = m_SndFile.GetRepeatCount();
  1487. const SEQUENCEINDEX currentSeq = m_SndFile.Order.GetCurrentSequenceIndex();
  1488. for(SEQUENCEINDEX seq = wsdlg.m_Settings.minSequence; seq <= wsdlg.m_Settings.maxSequence; seq++)
  1489. {
  1490. m_SndFile.Order.SetSequence(seq);
  1491. mpt::ustring fileNameAdd;
  1492. for(int i = 0; i < nRenderPasses; i++)
  1493. {
  1494. mpt::PathString thisName = fileName;
  1495. CString caption = _T("file");
  1496. fileNameAdd.clear();
  1497. if(wsdlg.m_Settings.minSequence != wsdlg.m_Settings.maxSequence)
  1498. {
  1499. fileNameAdd = MPT_UFORMAT("-{}")(mpt::ufmt::dec0<2>(seq + 1));
  1500. mpt::ustring seqName = m_SndFile.Order(seq).GetName();
  1501. if(!seqName.empty())
  1502. {
  1503. fileNameAdd += UL_("-") + seqName;
  1504. }
  1505. }
  1506. // Channel mode
  1507. if(wsdlg.m_bChannelMode)
  1508. {
  1509. // Re-mute previously processed channel
  1510. if(i > 0)
  1511. m_SndFile.ChnSettings[i - 1].dwFlags.set(muteFlag);
  1512. // Was this channel actually muted? Don't process it then.
  1513. if(!usedChannels[i])
  1514. continue;
  1515. // Add channel number & name (if available) to path string
  1516. if(!m_SndFile.ChnSettings[i].szName.empty())
  1517. {
  1518. fileNameAdd += MPT_UFORMAT("-{}_{}")(mpt::ufmt::dec0<3>(i + 1), mpt::ToUnicode(m_SndFile.GetCharsetInternal(), m_SndFile.ChnSettings[i].szName));
  1519. caption = MPT_CFORMAT("{}: {}")(i + 1, mpt::ToCString(m_SndFile.GetCharsetInternal(), m_SndFile.ChnSettings[i].szName));
  1520. } else
  1521. {
  1522. fileNameAdd += MPT_UFORMAT("-{}")(mpt::ufmt::dec0<3>(i + 1));
  1523. caption = MPT_CFORMAT("channel {}")(i + 1);
  1524. }
  1525. // Unmute channel to process
  1526. m_SndFile.ChnSettings[i].dwFlags.reset(muteFlag);
  1527. }
  1528. // Instrument mode
  1529. if(wsdlg.m_bInstrumentMode)
  1530. {
  1531. if(m_SndFile.GetNumInstruments() == 0)
  1532. {
  1533. // Re-mute previously processed sample
  1534. if(i > 0) MuteSample(static_cast<SAMPLEINDEX>(i), true);
  1535. if(!m_SndFile.GetSample(static_cast<SAMPLEINDEX>(i + 1)).HasSampleData() || !IsSampleUsed(static_cast<SAMPLEINDEX>(i + 1), false) || instrMuteState[i])
  1536. continue;
  1537. // Add sample number & name (if available) to path string
  1538. if(!m_SndFile.m_szNames[i + 1].empty())
  1539. {
  1540. fileNameAdd += MPT_UFORMAT("-{}_{}")(mpt::ufmt::dec0<3>(i + 1), mpt::ToUnicode(m_SndFile.GetCharsetInternal(), m_SndFile.m_szNames[i + 1]));
  1541. caption = MPT_CFORMAT("{}: {}")(i + 1, mpt::ToCString(m_SndFile.GetCharsetInternal(), m_SndFile.m_szNames[i + 1]));
  1542. } else
  1543. {
  1544. fileNameAdd += MPT_UFORMAT("-{}")(mpt::ufmt::dec0<3>(i + 1));
  1545. caption = MPT_CFORMAT("sample {}")(i + 1);
  1546. }
  1547. // Unmute sample to process
  1548. MuteSample(static_cast<SAMPLEINDEX>(i + 1), false);
  1549. } else
  1550. {
  1551. // Re-mute previously processed instrument
  1552. if(i > 0) MuteInstrument(static_cast<INSTRUMENTINDEX>(i), true);
  1553. if(m_SndFile.Instruments[i + 1] == nullptr || !IsInstrumentUsed(static_cast<SAMPLEINDEX>(i + 1), false) || instrMuteState[i])
  1554. continue;
  1555. if(!m_SndFile.Instruments[i + 1]->name.empty())
  1556. {
  1557. fileNameAdd += MPT_UFORMAT("-{}_{}")(mpt::ufmt::dec0<3>(i + 1), mpt::ToUnicode(m_SndFile.GetCharsetInternal(), m_SndFile.Instruments[i + 1]->name));
  1558. caption = MPT_CFORMAT("{}: {}")(i + 1, mpt::ToCString(m_SndFile.GetCharsetInternal(), m_SndFile.Instruments[i + 1]->name));
  1559. } else
  1560. {
  1561. fileNameAdd += MPT_UFORMAT("-{}")(mpt::ufmt::dec0<3>(i + 1));
  1562. caption = MPT_CFORMAT("instrument {}")(i + 1);
  1563. }
  1564. // Unmute instrument to process
  1565. MuteInstrument(static_cast<SAMPLEINDEX>(i + 1), false);
  1566. }
  1567. }
  1568. if(!fileNameAdd.empty())
  1569. {
  1570. SanitizeFilename(fileNameAdd);
  1571. thisName += mpt::PathString::FromUnicode(fileNameAdd);
  1572. }
  1573. thisName += fileExt;
  1574. if(wsdlg.m_Settings.outputToSample)
  1575. {
  1576. thisName = mpt::CreateTempFileName(P_("OpenMPT"));
  1577. // Ensure this temporary file is marked as temporary in the file system, to increase the chance it will never be written to disk
  1578. HANDLE hFile = ::CreateFile(thisName.AsNative().c_str(), GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL);
  1579. if(hFile != INVALID_HANDLE_VALUE)
  1580. {
  1581. ::CloseHandle(hFile);
  1582. }
  1583. }
  1584. // Render song (or current channel, or current sample/instrument)
  1585. bool cancel = true;
  1586. try
  1587. {
  1588. mpt::SafeOutputFile safeFileStream(thisName, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
  1589. mpt::ofstream &f = safeFileStream;
  1590. f.exceptions(f.exceptions() | std::ios::badbit | std::ios::failbit);
  1591. if(!f)
  1592. {
  1593. Reporting::Error("Could not open file for writing. Is it open in another application?");
  1594. } else
  1595. {
  1596. BypassInputHandler bih;
  1597. CDoWaveConvert dwcdlg(m_SndFile, f, caption, wsdlg.m_Settings, pMainFrm);
  1598. dwcdlg.m_bGivePlugsIdleTime = wsdlg.m_bGivePlugsIdleTime;
  1599. dwcdlg.m_dwSongLimit = wsdlg.m_dwSongLimit;
  1600. cancel = dwcdlg.DoModal() != IDOK;
  1601. }
  1602. } catch(const std::exception &)
  1603. {
  1604. Reporting::Error(_T("Error while writing file!"));
  1605. }
  1606. if(wsdlg.m_Settings.outputToSample)
  1607. {
  1608. if(!cancel)
  1609. {
  1610. InputFile f(thisName, TrackerSettings::Instance().MiscCacheCompleteFileBeforeLoading);
  1611. if(f.IsValid())
  1612. {
  1613. FileReader file = GetFileReader(f);
  1614. SAMPLEINDEX smp = wsdlg.m_Settings.sampleSlot;
  1615. if(smp == 0 || smp > GetNumSamples()) smp = m_SndFile.GetNextFreeSample();
  1616. if(smp == SAMPLEINDEX_INVALID)
  1617. {
  1618. Reporting::Error(_T("Too many samples!"));
  1619. cancel = true;
  1620. }
  1621. if(!cancel)
  1622. {
  1623. if(GetNumSamples() < smp) m_SndFile.m_nSamples = smp;
  1624. GetSampleUndo().PrepareUndo(smp, sundo_replace, "Render To Sample");
  1625. if(m_SndFile.ReadSampleFromFile(smp, file, false))
  1626. {
  1627. m_SndFile.m_szNames[smp] = "Render To Sample" + mpt::ToCharset(m_SndFile.GetCharsetInternal(), fileNameAdd);
  1628. UpdateAllViews(nullptr, SampleHint().Info().Data().Names());
  1629. if(m_SndFile.GetNumInstruments() && !IsSampleUsed(smp))
  1630. {
  1631. // Insert new instrument for the generated sample in case it is not referenced by any instruments yet.
  1632. // It should only be already referenced if the user chose to export to an existing sample slot.
  1633. InsertInstrument(smp);
  1634. UpdateAllViews(nullptr, InstrumentHint().Info().Names());
  1635. }
  1636. SetModified();
  1637. } else
  1638. {
  1639. GetSampleUndo().RemoveLastUndoStep(smp);
  1640. }
  1641. }
  1642. }
  1643. }
  1644. // Always clean up after ourselves
  1645. for(int retry = 0; retry < 10; retry++)
  1646. {
  1647. // stupid virus scanners
  1648. if(DeleteFile(thisName.AsNative().c_str()) != EACCES)
  1649. {
  1650. break;
  1651. }
  1652. Sleep(10);
  1653. }
  1654. }
  1655. if(cancel) break;
  1656. }
  1657. }
  1658. // Restore channels' flags
  1659. if(wsdlg.m_bChannelMode)
  1660. {
  1661. for(CHANNELINDEX i = 0; i < m_SndFile.GetNumChannels(); i++)
  1662. {
  1663. m_SndFile.ChnSettings[i].dwFlags = channelFlags[i];
  1664. }
  1665. }
  1666. // Restore instruments' / samples' flags
  1667. if(wsdlg.m_bInstrumentMode)
  1668. {
  1669. for(size_t i = 0; i < instrMuteState.size(); i++)
  1670. {
  1671. if(m_SndFile.GetNumInstruments() == 0)
  1672. MuteSample(static_cast<SAMPLEINDEX>(i + 1), instrMuteState[i]);
  1673. else
  1674. MuteInstrument(static_cast<INSTRUMENTINDEX>(i + 1), instrMuteState[i]);
  1675. }
  1676. }
  1677. m_SndFile.Order.SetSequence(currentSeq);
  1678. m_SndFile.SetRepeatCount(oldRepeat);
  1679. m_SndFile.GetLength(eAdjust, GetLengthTarget(currentOrd, currentRow));
  1680. m_SndFile.m_PlayState.m_nNextOrder = currentOrd;
  1681. m_SndFile.m_PlayState.m_nNextRow = currentRow;
  1682. CMainFrame::UpdateAudioParameters(m_SndFile, true);
  1683. }
  1684. void CModDoc::OnFileWaveConvert(ORDERINDEX nMinOrder, ORDERINDEX nMaxOrder)
  1685. {
  1686. WAVEncoder wavencoder;
  1687. FLACEncoder flacencoder;
  1688. AUEncoder auencoder;
  1689. OggOpusEncoder opusencoder;
  1690. VorbisEncoder vorbisencoder;
  1691. MP3Encoder mp3lame(MP3EncoderLame);
  1692. MP3Encoder mp3lamecompatible(MP3EncoderLameCompatible);
  1693. RAWEncoder rawencoder;
  1694. std::vector<EncoderFactoryBase*> encoders;
  1695. if(wavencoder.IsAvailable()) encoders.push_back(&wavencoder);
  1696. if(flacencoder.IsAvailable()) encoders.push_back(&flacencoder);
  1697. if(auencoder.IsAvailable()) encoders.push_back(&auencoder);
  1698. if(rawencoder.IsAvailable()) encoders.push_back(&rawencoder);
  1699. if(opusencoder.IsAvailable()) encoders.push_back(&opusencoder);
  1700. if(vorbisencoder.IsAvailable()) encoders.push_back(&vorbisencoder);
  1701. if(mp3lame.IsAvailable())
  1702. {
  1703. encoders.push_back(&mp3lame);
  1704. }
  1705. if(mp3lamecompatible.IsAvailable()) encoders.push_back(&mp3lamecompatible);
  1706. OnFileWaveConvert(nMinOrder, nMaxOrder, encoders);
  1707. }
  1708. void CModDoc::OnFileMidiConvert()
  1709. {
  1710. #ifndef NO_PLUGINS
  1711. CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
  1712. if ((!pMainFrm) || (!m_SndFile.GetType())) return;
  1713. mpt::PathString filename = GetPathNameMpt().ReplaceExt(P_(".mid"));
  1714. FileDialog dlg = SaveFileDialog()
  1715. .DefaultExtension("mid")
  1716. .DefaultFilename(filename)
  1717. .ExtensionFilter("MIDI Files (*.mid)|*.mid||");
  1718. if(!dlg.Show()) return;
  1719. CModToMidi mididlg(m_SndFile, pMainFrm);
  1720. BypassInputHandler bih;
  1721. if(mididlg.DoModal() == IDOK)
  1722. {
  1723. try
  1724. {
  1725. mpt::SafeOutputFile sf(dlg.GetFirstFile(), std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
  1726. mpt::ofstream &f = sf;
  1727. f.exceptions(f.exceptions() | std::ios::badbit | std::ios::failbit);
  1728. if(!f.good())
  1729. {
  1730. Reporting::Error("Could not open file for writing. Is it open in another application?");
  1731. return;
  1732. }
  1733. CDoMidiConvert doconv(m_SndFile, f, mididlg.m_instrMap);
  1734. doconv.DoModal();
  1735. } catch(const std::exception &)
  1736. {
  1737. Reporting::Error(_T("Error while writing file!"));
  1738. }
  1739. }
  1740. #else
  1741. Reporting::Error("In order to use MIDI export, OpenMPT must be built with plugin support.");
  1742. #endif // NO_PLUGINS
  1743. }
  1744. //HACK: This is a quick fix. Needs to be better integrated into player and GUI.
  1745. void CModDoc::OnFileCompatibilitySave()
  1746. {
  1747. CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
  1748. if (!pMainFrm) return;
  1749. CString pattern;
  1750. const MODTYPE type = m_SndFile.GetType();
  1751. switch(type)
  1752. {
  1753. case MOD_TYPE_IT:
  1754. pattern = FileFilterIT;
  1755. MsgBoxHidable(CompatExportDefaultWarning);
  1756. break;
  1757. case MOD_TYPE_XM:
  1758. pattern = FileFilterXM;
  1759. MsgBoxHidable(CompatExportDefaultWarning);
  1760. break;
  1761. default:
  1762. // Not available for this format.
  1763. return;
  1764. }
  1765. const std::string ext = m_SndFile.GetModSpecifications().fileExtension;
  1766. mpt::PathString filename;
  1767. {
  1768. mpt::PathString drive;
  1769. mpt::PathString dir;
  1770. mpt::PathString fileName;
  1771. GetPathNameMpt().SplitPath(&drive, &dir, &fileName, nullptr);
  1772. filename = drive;
  1773. filename += dir;
  1774. filename += fileName;
  1775. if(!strstr(fileName.ToUTF8().c_str(), "compat"))
  1776. filename += P_(".compat.");
  1777. else
  1778. filename += P_(".");
  1779. filename += mpt::PathString::FromUTF8(ext);
  1780. }
  1781. FileDialog dlg = SaveFileDialog()
  1782. .DefaultExtension(ext)
  1783. .DefaultFilename(filename)
  1784. .ExtensionFilter(pattern)
  1785. .WorkingDirectory(TrackerSettings::Instance().PathSongs.GetWorkingDir());
  1786. if(!dlg.Show()) return;
  1787. filename = dlg.GetFirstFile();
  1788. bool ok = false;
  1789. BeginWaitCursor();
  1790. try
  1791. {
  1792. mpt::SafeOutputFile sf(filename, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
  1793. mpt::ofstream &f = sf;
  1794. if(f)
  1795. {
  1796. f.exceptions(f.exceptions() | std::ios::badbit | std::ios::failbit);
  1797. ScopedLogCapturer logcapturer(*this);
  1798. FixNullStrings();
  1799. switch(type)
  1800. {
  1801. case MOD_TYPE_XM: ok = m_SndFile.SaveXM(f, true); break;
  1802. case MOD_TYPE_IT: ok = m_SndFile.SaveIT(f, filename, true); break;
  1803. default: MPT_ASSERT_NOTREACHED();
  1804. }
  1805. }
  1806. } catch(const std::exception &)
  1807. {
  1808. ok = false;
  1809. }
  1810. EndWaitCursor();
  1811. if(!ok)
  1812. {
  1813. ErrorBox(IDS_ERR_SAVESONG, CMainFrame::GetMainFrame());
  1814. }
  1815. }
  1816. void CModDoc::OnPlayerPlay()
  1817. {
  1818. CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
  1819. if (pMainFrm)
  1820. {
  1821. CChildFrame *pChildFrm = GetChildFrame();
  1822. if (strcmp("CViewPattern", pChildFrm->GetCurrentViewClassName()) == 0)
  1823. {
  1824. //User has sent play song command: set loop pattern checkbox to false.
  1825. pChildFrm->SendViewMessage(VIEWMSG_PATTERNLOOP, 0);
  1826. }
  1827. bool isPlaying = (pMainFrm->GetModPlaying() == this);
  1828. if(isPlaying && !m_SndFile.m_SongFlags[SONG_PAUSED | SONG_STEP/*|SONG_PATTERNLOOP*/])
  1829. {
  1830. OnPlayerPause();
  1831. return;
  1832. }
  1833. CriticalSection cs;
  1834. // Kill editor voices
  1835. for(CHANNELINDEX i = m_SndFile.GetNumChannels(); i < MAX_CHANNELS; i++) if (m_SndFile.m_PlayState.Chn[i].isPreviewNote)
  1836. {
  1837. m_SndFile.m_PlayState.Chn[i].dwFlags.set(CHN_NOTEFADE | CHN_KEYOFF);
  1838. if (!isPlaying) m_SndFile.m_PlayState.Chn[i].nLength = 0;
  1839. }
  1840. m_SndFile.m_PlayState.m_bPositionChanged = true;
  1841. if(isPlaying)
  1842. {
  1843. m_SndFile.StopAllVsti();
  1844. }
  1845. cs.Leave();
  1846. m_SndFile.m_SongFlags.reset(SONG_STEP | SONG_PAUSED | SONG_PATTERNLOOP);
  1847. pMainFrm->PlayMod(this);
  1848. }
  1849. }
  1850. void CModDoc::OnPlayerPause()
  1851. {
  1852. CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
  1853. if (pMainFrm)
  1854. {
  1855. if (pMainFrm->GetModPlaying() == this)
  1856. {
  1857. bool isLooping = m_SndFile.m_SongFlags[SONG_PATTERNLOOP];
  1858. PATTERNINDEX nPat = m_SndFile.m_PlayState.m_nPattern;
  1859. ROWINDEX nRow = m_SndFile.m_PlayState.m_nRow;
  1860. ROWINDEX nNextRow = m_SndFile.m_PlayState.m_nNextRow;
  1861. pMainFrm->PauseMod();
  1862. if ((isLooping) && (nPat < m_SndFile.Patterns.Size()))
  1863. {
  1864. CriticalSection cs;
  1865. if ((m_SndFile.m_PlayState.m_nCurrentOrder < m_SndFile.Order().GetLength()) && (m_SndFile.Order()[m_SndFile.m_PlayState.m_nCurrentOrder] == nPat))
  1866. {
  1867. m_SndFile.m_PlayState.m_nNextOrder = m_SndFile.m_PlayState.m_nCurrentOrder;
  1868. m_SndFile.m_PlayState.m_nNextRow = nNextRow;
  1869. m_SndFile.m_PlayState.m_nRow = nRow;
  1870. } else
  1871. {
  1872. for (ORDERINDEX nOrd = 0; nOrd < m_SndFile.Order().GetLength(); nOrd++)
  1873. {
  1874. if (m_SndFile.Order()[nOrd] == m_SndFile.Order.GetInvalidPatIndex()) break;
  1875. if (m_SndFile.Order()[nOrd] == nPat)
  1876. {
  1877. m_SndFile.m_PlayState.m_nCurrentOrder = nOrd;
  1878. m_SndFile.m_PlayState.m_nNextOrder = nOrd;
  1879. m_SndFile.m_PlayState.m_nNextRow = nNextRow;
  1880. m_SndFile.m_PlayState.m_nRow = nRow;
  1881. break;
  1882. }
  1883. }
  1884. }
  1885. }
  1886. } else
  1887. {
  1888. pMainFrm->PauseMod();
  1889. }
  1890. }
  1891. }
  1892. void CModDoc::OnPlayerStop()
  1893. {
  1894. CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
  1895. if (pMainFrm) pMainFrm->StopMod();
  1896. }
  1897. void CModDoc::OnPlayerPlayFromStart()
  1898. {
  1899. CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
  1900. if (pMainFrm)
  1901. {
  1902. CChildFrame *pChildFrm = GetChildFrame();
  1903. if (strcmp("CViewPattern", pChildFrm->GetCurrentViewClassName()) == 0)
  1904. {
  1905. //User has sent play song command: set loop pattern checkbox to false.
  1906. pChildFrm->SendViewMessage(VIEWMSG_PATTERNLOOP, 0);
  1907. }
  1908. pMainFrm->PauseMod();
  1909. CriticalSection cs;
  1910. m_SndFile.m_SongFlags.reset(SONG_STEP | SONG_PATTERNLOOP);
  1911. m_SndFile.ResetPlayPos();
  1912. //m_SndFile.visitedSongRows.Initialize(true);
  1913. m_SndFile.m_PlayState.m_bPositionChanged = true;
  1914. cs.Leave();
  1915. pMainFrm->PlayMod(this);
  1916. }
  1917. }
  1918. void CModDoc::OnEditGlobals()
  1919. {
  1920. SendMessageToActiveView(WM_MOD_ACTIVATEVIEW, IDD_CONTROL_GLOBALS);
  1921. }
  1922. void CModDoc::OnEditPatterns()
  1923. {
  1924. SendMessageToActiveView(WM_MOD_ACTIVATEVIEW, IDD_CONTROL_PATTERNS, -1);
  1925. }
  1926. void CModDoc::OnEditSamples()
  1927. {
  1928. SendMessageToActiveView(WM_MOD_ACTIVATEVIEW, IDD_CONTROL_SAMPLES, -1);
  1929. }
  1930. void CModDoc::OnEditInstruments()
  1931. {
  1932. SendMessageToActiveView(WM_MOD_ACTIVATEVIEW, IDD_CONTROL_INSTRUMENTS, -1);
  1933. }
  1934. void CModDoc::OnEditComments()
  1935. {
  1936. SendMessageToActiveView(WM_MOD_ACTIVATEVIEW, IDD_CONTROL_COMMENTS);
  1937. }
  1938. void CModDoc::OnShowCleanup()
  1939. {
  1940. CModCleanupDlg dlg(*this, CMainFrame::GetMainFrame());
  1941. dlg.DoModal();
  1942. }
  1943. void CModDoc::OnSetupZxxMacros()
  1944. {
  1945. CMidiMacroSetup dlg(m_SndFile);
  1946. if(dlg.DoModal() == IDOK)
  1947. {
  1948. if(m_SndFile.m_MidiCfg != dlg.m_MidiCfg)
  1949. {
  1950. m_SndFile.m_MidiCfg = dlg.m_MidiCfg;
  1951. SetModified();
  1952. }
  1953. }
  1954. }
  1955. // Enable menu item only module types that support MIDI Mappings
  1956. void CModDoc::OnUpdateHasMIDIMappings(CCmdUI *p)
  1957. {
  1958. if(p)
  1959. p->Enable((m_SndFile.GetModSpecifications().MIDIMappingDirectivesMax > 0) ? TRUE : FALSE);
  1960. }
  1961. // Enable menu item only for IT / MPTM / XM files
  1962. void CModDoc::OnUpdateXMITMPTOnly(CCmdUI *p)
  1963. {
  1964. if (p)
  1965. p->Enable((m_SndFile.GetType() & (MOD_TYPE_XM | MOD_TYPE_IT | MOD_TYPE_MPT)) ? TRUE : FALSE);
  1966. }
  1967. // Enable menu item only for IT / MPTM files
  1968. void CModDoc::OnUpdateHasEditHistory(CCmdUI *p)
  1969. {
  1970. if (p)
  1971. p->Enable(((m_SndFile.GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) || !m_SndFile.GetFileHistory().empty()) ? TRUE : FALSE);
  1972. }
  1973. // Enable menu item if current module type supports compatibility export
  1974. void CModDoc::OnUpdateCompatExportableOnly(CCmdUI *p)
  1975. {
  1976. if(p)
  1977. p->Enable((m_SndFile.GetType() & (MOD_TYPE_XM | MOD_TYPE_IT)) ? TRUE : FALSE);
  1978. }
  1979. static CString FormatSongLength(double length)
  1980. {
  1981. length = mpt::round(length);
  1982. double minutes = std::floor(length / 60.0), seconds = std::fmod(length, 60.0);
  1983. CString s;
  1984. s.Format(_T("%.0fmn%02.0fs"), minutes, seconds);
  1985. return s;
  1986. }
  1987. void CModDoc::OnEstimateSongLength()
  1988. {
  1989. CString s = _T("Approximate song length: ");
  1990. const auto subSongs = m_SndFile.GetAllSubSongs();
  1991. if (subSongs.empty())
  1992. {
  1993. Reporting::Information(_T("No patterns found!"));
  1994. return;
  1995. }
  1996. std::vector<uint32> songsPerSequence(m_SndFile.Order.GetNumSequences(), 0);
  1997. SEQUENCEINDEX prevSeq = subSongs[0].sequence;
  1998. for(const auto &song : subSongs)
  1999. {
  2000. songsPerSequence[song.sequence]++;
  2001. if(prevSeq != song.sequence)
  2002. prevSeq = SEQUENCEINDEX_INVALID;
  2003. }
  2004. double totalLength = 0.0;
  2005. uint32 songCount = 0;
  2006. // If there are multiple sequences, indent their subsongs
  2007. const TCHAR *indent = (prevSeq == SEQUENCEINDEX_INVALID) ? _T("\t") : _T("");
  2008. for(const auto &song : subSongs)
  2009. {
  2010. double songLength = song.duration;
  2011. if(subSongs.size() > 1)
  2012. {
  2013. totalLength += songLength;
  2014. if(prevSeq != song.sequence)
  2015. {
  2016. songCount = 0;
  2017. prevSeq = song.sequence;
  2018. if(m_SndFile.Order(prevSeq).GetName().empty())
  2019. s.AppendFormat(_T("\nSequence %u:"), prevSeq + 1u);
  2020. else
  2021. s.AppendFormat(_T("\nSequence %u (%s):"), prevSeq + 1u, mpt::ToWin(m_SndFile.Order(prevSeq).GetName()).c_str());
  2022. }
  2023. songCount++;
  2024. if(songsPerSequence[song.sequence] > 1)
  2025. s.AppendFormat(_T("\n%sSong %u, starting at order %u:\t"), indent, songCount, song.startOrder);
  2026. else
  2027. s.AppendChar(_T('\t'));
  2028. }
  2029. if(songLength != std::numeric_limits<double>::infinity())
  2030. {
  2031. songLength = mpt::round(songLength);
  2032. s += FormatSongLength(songLength);
  2033. } else
  2034. {
  2035. s += _T("Song too long!");
  2036. }
  2037. }
  2038. if(subSongs.size() > 1 && totalLength != std::numeric_limits<double>::infinity())
  2039. {
  2040. s += _T("\n\nTotal length:\t") + FormatSongLength(totalLength);
  2041. }
  2042. Reporting::Information(s);
  2043. }
  2044. void CModDoc::OnApproximateBPM()
  2045. {
  2046. if(CMainFrame::GetMainFrame()->GetModPlaying() != this)
  2047. {
  2048. m_SndFile.m_PlayState.m_nCurrentRowsPerBeat = m_SndFile.m_nDefaultRowsPerBeat;
  2049. m_SndFile.m_PlayState.m_nCurrentRowsPerMeasure = m_SndFile.m_nDefaultRowsPerMeasure;
  2050. }
  2051. m_SndFile.RecalculateSamplesPerTick();
  2052. const double bpm = m_SndFile.GetCurrentBPM();
  2053. CString s;
  2054. switch(m_SndFile.m_nTempoMode)
  2055. {
  2056. case TempoMode::Alternative:
  2057. s.Format(_T("Using alternative tempo interpretation.\n\nAssuming:\n. %.8g ticks per second\n. %u ticks per row\n. %u rows per beat\nthe tempo is approximately: %.8g BPM"),
  2058. m_SndFile.m_PlayState.m_nMusicTempo.ToDouble(), m_SndFile.m_PlayState.m_nMusicSpeed, m_SndFile.m_PlayState.m_nCurrentRowsPerBeat, bpm);
  2059. break;
  2060. case TempoMode::Modern:
  2061. s.Format(_T("Using modern tempo interpretation.\n\nThe tempo is: %.8g BPM"), bpm);
  2062. break;
  2063. case TempoMode::Classic:
  2064. default:
  2065. s.Format(_T("Using standard tempo interpretation.\n\nAssuming:\n. A mod tempo (tick duration factor) of %.8g\n. %u ticks per row\n. %u rows per beat\nthe tempo is approximately: %.8g BPM"),
  2066. m_SndFile.m_PlayState.m_nMusicTempo.ToDouble(), m_SndFile.m_PlayState.m_nMusicSpeed, m_SndFile.m_PlayState.m_nCurrentRowsPerBeat, bpm);
  2067. break;
  2068. }
  2069. Reporting::Information(s);
  2070. }
  2071. CChildFrame *CModDoc::GetChildFrame()
  2072. {
  2073. CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
  2074. if (!pMainFrm) return nullptr;
  2075. CMDIChildWnd *pMDIActive = pMainFrm->MDIGetActive();
  2076. if (pMDIActive)
  2077. {
  2078. CView *pView = pMDIActive->GetActiveView();
  2079. if ((pView) && (pView->GetDocument() == this))
  2080. return static_cast<CChildFrame *>(pMDIActive);
  2081. }
  2082. POSITION pos = GetFirstViewPosition();
  2083. while (pos != NULL)
  2084. {
  2085. CView *pView = GetNextView(pos);
  2086. if ((pView) && (pView->GetDocument() == this))
  2087. return static_cast<CChildFrame *>(pView->GetParentFrame());
  2088. }
  2089. return nullptr;
  2090. }
  2091. // Get the currently edited pattern position. Note that ord might be ORDERINDEX_INVALID when editing a pattern that is not present in the order list.
  2092. void CModDoc::GetEditPosition(ROWINDEX &row, PATTERNINDEX &pat, ORDERINDEX &ord)
  2093. {
  2094. CChildFrame *pChildFrm = GetChildFrame();
  2095. if(strcmp("CViewPattern", pChildFrm->GetCurrentViewClassName()) == 0) // dirty HACK
  2096. {
  2097. PATTERNVIEWSTATE patternViewState;
  2098. pChildFrm->SendViewMessage(VIEWMSG_SAVESTATE, (LPARAM)(&patternViewState));
  2099. pat = patternViewState.nPattern;
  2100. row = patternViewState.cursor.GetRow();
  2101. ord = patternViewState.nOrder;
  2102. } else
  2103. {
  2104. //patern editor object does not exist (i.e. is not active) - use saved state.
  2105. PATTERNVIEWSTATE &patternViewState = pChildFrm->GetPatternViewState();
  2106. pat = patternViewState.nPattern;
  2107. row = patternViewState.cursor.GetRow();
  2108. ord = patternViewState.nOrder;
  2109. }
  2110. const auto &order = m_SndFile.Order();
  2111. if(order.empty())
  2112. {
  2113. ord = ORDERINDEX_INVALID;
  2114. pat = 0;
  2115. row = 0;
  2116. } else if(ord >= order.size())
  2117. {
  2118. ord = 0;
  2119. pat = m_SndFile.Order()[ord];
  2120. }
  2121. if(!m_SndFile.Patterns.IsValidPat(pat))
  2122. {
  2123. pat = 0;
  2124. row = 0;
  2125. } else if(row >= m_SndFile.Patterns[pat].GetNumRows())
  2126. {
  2127. row = 0;
  2128. }
  2129. //ensure order correlates with pattern.
  2130. if(ord >= order.size() || order[ord] != pat)
  2131. {
  2132. ord = order.FindOrder(pat);
  2133. }
  2134. }
  2135. ////////////////////////////////////////////////////////////////////////////////////////
  2136. // Playback
  2137. void CModDoc::OnPatternRestart(bool loop)
  2138. {
  2139. CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
  2140. CChildFrame *pChildFrm = GetChildFrame();
  2141. if ((pMainFrm) && (pChildFrm))
  2142. {
  2143. if (strcmp("CViewPattern", pChildFrm->GetCurrentViewClassName()) == 0)
  2144. {
  2145. //User has sent play pattern command: set loop pattern checkbox to true.
  2146. pChildFrm->SendViewMessage(VIEWMSG_PATTERNLOOP, loop ? 1 : 0);
  2147. }
  2148. ROWINDEX nRow;
  2149. PATTERNINDEX nPat;
  2150. ORDERINDEX nOrd;
  2151. GetEditPosition(nRow, nPat, nOrd);
  2152. CModDoc *pModPlaying = pMainFrm->GetModPlaying();
  2153. CriticalSection cs;
  2154. // Cut instruments/samples
  2155. for(auto &chn : m_SndFile.m_PlayState.Chn)
  2156. {
  2157. chn.nPatternLoopCount = 0;
  2158. chn.nPatternLoop = 0;
  2159. chn.nFadeOutVol = 0;
  2160. chn.dwFlags.set(CHN_NOTEFADE | CHN_KEYOFF);
  2161. }
  2162. if ((nOrd < m_SndFile.Order().size()) && (m_SndFile.Order()[nOrd] == nPat)) m_SndFile.m_PlayState.m_nCurrentOrder = m_SndFile.m_PlayState.m_nNextOrder = nOrd;
  2163. m_SndFile.m_SongFlags.reset(SONG_PAUSED | SONG_STEP);
  2164. if(loop)
  2165. m_SndFile.LoopPattern(nPat);
  2166. else
  2167. m_SndFile.LoopPattern(PATTERNINDEX_INVALID);
  2168. // set playback timer in the status bar (and update channel status)
  2169. SetElapsedTime(nOrd, 0, true);
  2170. if(pModPlaying == this)
  2171. {
  2172. m_SndFile.StopAllVsti();
  2173. }
  2174. cs.Leave();
  2175. if(pModPlaying != this)
  2176. {
  2177. SetNotifications(m_notifyType | Notification::Position | Notification::VUMeters, m_notifyItem);
  2178. SetFollowWnd(pChildFrm->GetHwndView());
  2179. pMainFrm->PlayMod(this); //rewbs.fix2977
  2180. }
  2181. }
  2182. //SwitchToView();
  2183. }
  2184. void CModDoc::OnPatternPlay()
  2185. {
  2186. CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
  2187. CChildFrame *pChildFrm = GetChildFrame();
  2188. if ((pMainFrm) && (pChildFrm))
  2189. {
  2190. if (strcmp("CViewPattern", pChildFrm->GetCurrentViewClassName()) == 0)
  2191. {
  2192. //User has sent play pattern command: set loop pattern checkbox to true.
  2193. pChildFrm->SendViewMessage(VIEWMSG_PATTERNLOOP, 1);
  2194. }
  2195. ROWINDEX nRow;
  2196. PATTERNINDEX nPat;
  2197. ORDERINDEX nOrd;
  2198. GetEditPosition(nRow, nPat, nOrd);
  2199. CModDoc *pModPlaying = pMainFrm->GetModPlaying();
  2200. CriticalSection cs;
  2201. // Cut instruments/samples
  2202. for(CHANNELINDEX i = m_SndFile.GetNumChannels(); i < MAX_CHANNELS; i++)
  2203. {
  2204. m_SndFile.m_PlayState.Chn[i].dwFlags.set(CHN_NOTEFADE | CHN_KEYOFF);
  2205. }
  2206. if ((nOrd < m_SndFile.Order().size()) && (m_SndFile.Order()[nOrd] == nPat)) m_SndFile.m_PlayState.m_nCurrentOrder = m_SndFile.m_PlayState.m_nNextOrder = nOrd;
  2207. m_SndFile.m_SongFlags.reset(SONG_PAUSED | SONG_STEP);
  2208. m_SndFile.LoopPattern(nPat);
  2209. // set playback timer in the status bar (and update channel status)
  2210. SetElapsedTime(nOrd, nRow, true);
  2211. if(pModPlaying == this)
  2212. {
  2213. m_SndFile.StopAllVsti();
  2214. }
  2215. cs.Leave();
  2216. if(pModPlaying != this)
  2217. {
  2218. SetNotifications(m_notifyType | Notification::Position | Notification::VUMeters, m_notifyItem);
  2219. SetFollowWnd(pChildFrm->GetHwndView());
  2220. pMainFrm->PlayMod(this); //rewbs.fix2977
  2221. }
  2222. }
  2223. //SwitchToView();
  2224. }
  2225. void CModDoc::OnPatternPlayNoLoop()
  2226. {
  2227. CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
  2228. CChildFrame *pChildFrm = GetChildFrame();
  2229. if ((pMainFrm) && (pChildFrm))
  2230. {
  2231. if (strcmp("CViewPattern", pChildFrm->GetCurrentViewClassName()) == 0)
  2232. {
  2233. //User has sent play song command: set loop pattern checkbox to false.
  2234. pChildFrm->SendViewMessage(VIEWMSG_PATTERNLOOP, 0);
  2235. }
  2236. ROWINDEX nRow;
  2237. PATTERNINDEX nPat;
  2238. ORDERINDEX nOrd;
  2239. GetEditPosition(nRow, nPat, nOrd);
  2240. CModDoc *pModPlaying = pMainFrm->GetModPlaying();
  2241. CriticalSection cs;
  2242. // Cut instruments/samples
  2243. for(CHANNELINDEX i = m_SndFile.GetNumChannels(); i < MAX_CHANNELS; i++)
  2244. {
  2245. m_SndFile.m_PlayState.Chn[i].dwFlags.set(CHN_NOTEFADE | CHN_KEYOFF);
  2246. }
  2247. m_SndFile.m_SongFlags.reset(SONG_PAUSED | SONG_STEP);
  2248. m_SndFile.SetCurrentOrder(nOrd);
  2249. if(nOrd < m_SndFile.Order().size() && m_SndFile.Order()[nOrd] == nPat)
  2250. m_SndFile.DontLoopPattern(nPat, nRow);
  2251. else
  2252. m_SndFile.LoopPattern(nPat);
  2253. // set playback timer in the status bar (and update channel status)
  2254. SetElapsedTime(nOrd, nRow, true);
  2255. if(pModPlaying == this)
  2256. {
  2257. m_SndFile.StopAllVsti();
  2258. }
  2259. cs.Leave();
  2260. if(pModPlaying != this)
  2261. {
  2262. SetNotifications(m_notifyType | Notification::Position | Notification::VUMeters, m_notifyItem);
  2263. SetFollowWnd(pChildFrm->GetHwndView());
  2264. pMainFrm->PlayMod(this); //rewbs.fix2977
  2265. }
  2266. }
  2267. //SwitchToView();
  2268. }
  2269. void CModDoc::OnViewEditHistory()
  2270. {
  2271. CEditHistoryDlg dlg(CMainFrame::GetMainFrame(), *this);
  2272. dlg.DoModal();
  2273. }
  2274. void CModDoc::OnViewMPTHacks()
  2275. {
  2276. ScopedLogCapturer logcapturer(*this);
  2277. if(!HasMPTHacks())
  2278. {
  2279. AddToLog("No hacks found.");
  2280. }
  2281. }
  2282. void CModDoc::OnViewTempoSwingSettings()
  2283. {
  2284. if(m_SndFile.m_nDefaultRowsPerBeat > 0 && m_SndFile.m_nTempoMode == TempoMode::Modern)
  2285. {
  2286. TempoSwing tempoSwing = m_SndFile.m_tempoSwing;
  2287. tempoSwing.resize(m_SndFile.m_nDefaultRowsPerBeat, TempoSwing::Unity);
  2288. CTempoSwingDlg dlg(CMainFrame::GetMainFrame(), tempoSwing, m_SndFile);
  2289. if(dlg.DoModal() == IDOK)
  2290. {
  2291. SetModified();
  2292. m_SndFile.m_tempoSwing = dlg.m_tempoSwing;
  2293. }
  2294. } else if(GetModType() == MOD_TYPE_MPT)
  2295. {
  2296. Reporting::Error(_T("Modern tempo mode needs to be enabled in order to edit tempo swing settings."));
  2297. OnSongProperties();
  2298. }
  2299. }
  2300. LRESULT CModDoc::OnCustomKeyMsg(WPARAM wParam, LPARAM /*lParam*/)
  2301. {
  2302. const auto &modSpecs = m_SndFile.GetModSpecifications();
  2303. switch(wParam)
  2304. {
  2305. case kcViewGeneral: OnEditGlobals(); break;
  2306. case kcViewPattern: OnEditPatterns(); break;
  2307. case kcViewSamples: OnEditSamples(); break;
  2308. case kcViewInstruments: OnEditInstruments(); break;
  2309. case kcViewComments: OnEditComments(); break;
  2310. case kcViewSongProperties: OnSongProperties(); break;
  2311. case kcViewTempoSwing: OnViewTempoSwingSettings(); break;
  2312. case kcShowMacroConfig: OnSetupZxxMacros(); break;
  2313. case kcViewMIDImapping: OnViewMIDIMapping(); break;
  2314. case kcViewEditHistory: OnViewEditHistory(); break;
  2315. case kcViewChannelManager: OnChannelManager(); break;
  2316. case kcFileSaveAsWave: OnFileWaveConvert(); break;
  2317. case kcFileSaveMidi: OnFileMidiConvert(); break;
  2318. case kcFileSaveOPL: OnFileOPLExport(); break;
  2319. case kcFileExportCompat: OnFileCompatibilitySave(); break;
  2320. case kcEstimateSongLength: OnEstimateSongLength(); break;
  2321. case kcApproxRealBPM: OnApproximateBPM(); break;
  2322. case kcFileSave: DoSave(GetPathNameMpt()); break;
  2323. case kcFileSaveAs: DoSave(mpt::PathString()); break;
  2324. case kcFileSaveCopy: OnSaveCopy(); break;
  2325. case kcFileSaveTemplate: OnSaveTemplateModule(); break;
  2326. case kcFileClose: SafeFileClose(); break;
  2327. case kcFileAppend: OnAppendModule(); break;
  2328. case kcPlayPatternFromCursor: OnPatternPlay(); break;
  2329. case kcPlayPatternFromStart: OnPatternRestart(); break;
  2330. case kcPlaySongFromCursor: OnPatternPlayNoLoop(); break;
  2331. case kcPlaySongFromStart: OnPlayerPlayFromStart(); break;
  2332. case kcPlayPauseSong: OnPlayerPlay(); break;
  2333. case kcPlaySongFromPattern: OnPatternRestart(false); break;
  2334. case kcStopSong: OnPlayerStop(); break;
  2335. case kcPanic: OnPanic(); break;
  2336. case kcToggleLoopSong: SetLoopSong(!TrackerSettings::Instance().gbLoopSong); break;
  2337. case kcTempoIncreaseFine:
  2338. if(!modSpecs.hasFractionalTempo)
  2339. break;
  2340. [[fallthrough]];
  2341. case kcTempoIncrease:
  2342. if(auto tempo = m_SndFile.m_PlayState.m_nMusicTempo; tempo < modSpecs.GetTempoMax())
  2343. m_SndFile.m_PlayState.m_nMusicTempo = std::min(modSpecs.GetTempoMax(), tempo + TEMPO(wParam == kcTempoIncrease ? 1.0 : 0.1));
  2344. break;
  2345. case kcTempoDecreaseFine:
  2346. if(!modSpecs.hasFractionalTempo)
  2347. break;
  2348. [[fallthrough]];
  2349. case kcTempoDecrease:
  2350. if(auto tempo = m_SndFile.m_PlayState.m_nMusicTempo; tempo > modSpecs.GetTempoMin())
  2351. m_SndFile.m_PlayState.m_nMusicTempo = std::max(modSpecs.GetTempoMin(), tempo - TEMPO(wParam == kcTempoDecrease ? 1.0 : 0.1));
  2352. break;
  2353. case kcSpeedIncrease:
  2354. if(auto speed = m_SndFile.m_PlayState.m_nMusicSpeed; speed < modSpecs.speedMax)
  2355. m_SndFile.m_PlayState.m_nMusicSpeed = speed + 1;
  2356. break;
  2357. case kcSpeedDecrease:
  2358. if(auto speed = m_SndFile.m_PlayState.m_nMusicSpeed; speed > modSpecs.speedMin)
  2359. m_SndFile.m_PlayState.m_nMusicSpeed = speed - 1;
  2360. break;
  2361. case kcViewToggle:
  2362. if(auto *lastActiveFrame = CChildFrame::LastActiveFrame(); lastActiveFrame != nullptr)
  2363. lastActiveFrame->ToggleViews();
  2364. break;
  2365. default: return kcNull;
  2366. }
  2367. return wParam;
  2368. }
  2369. void CModDoc::TogglePluginEditor(UINT plugin, bool onlyThisEditor)
  2370. {
  2371. if(plugin < MAX_MIXPLUGINS)
  2372. {
  2373. IMixPlugin *pPlugin = m_SndFile.m_MixPlugins[plugin].pMixPlugin;
  2374. if(pPlugin != nullptr)
  2375. {
  2376. if(onlyThisEditor)
  2377. {
  2378. int32 posX = int32_min, posY = int32_min;
  2379. for(PLUGINDEX i = 0; i < MAX_MIXPLUGINS; i++)
  2380. {
  2381. SNDMIXPLUGIN &otherPlug = m_SndFile.m_MixPlugins[i];
  2382. if(i != plugin && otherPlug.pMixPlugin != nullptr && otherPlug.pMixPlugin->GetEditor() != nullptr)
  2383. {
  2384. otherPlug.pMixPlugin->CloseEditor();
  2385. if(otherPlug.editorX != int32_min)
  2386. {
  2387. posX = otherPlug.editorX;
  2388. posY = otherPlug.editorY;
  2389. }
  2390. }
  2391. }
  2392. if(posX != int32_min)
  2393. {
  2394. m_SndFile.m_MixPlugins[plugin].editorX = posX;
  2395. m_SndFile.m_MixPlugins[plugin].editorY = posY;
  2396. }
  2397. }
  2398. pPlugin->ToggleEditor();
  2399. }
  2400. }
  2401. }
  2402. void CModDoc::SetLoopSong(bool loop)
  2403. {
  2404. TrackerSettings::Instance().gbLoopSong = loop;
  2405. m_SndFile.SetRepeatCount(loop ? -1 : 0);
  2406. CMainFrame::GetMainFrame()->UpdateAllViews(UpdateHint().MPTOptions());
  2407. }
  2408. void CModDoc::ChangeFileExtension(MODTYPE nNewType)
  2409. {
  2410. //Not making path if path is empty(case only(?) for new file)
  2411. if(!GetPathNameMpt().empty())
  2412. {
  2413. mpt::PathString drive;
  2414. mpt::PathString dir;
  2415. mpt::PathString fname;
  2416. mpt::PathString fext;
  2417. GetPathNameMpt().SplitPath(&drive, &dir, &fname, &fext);
  2418. mpt::PathString newPath = drive + dir;
  2419. // Catch case where we don't have a filename yet.
  2420. if(fname.empty())
  2421. {
  2422. newPath += mpt::PathString::FromCString(GetTitle()).SanitizeComponent();
  2423. } else
  2424. {
  2425. newPath += fname;
  2426. }
  2427. newPath += P_(".") + mpt::PathString::FromUTF8(CSoundFile::GetModSpecifications(nNewType).fileExtension);
  2428. // Forcing save dialog to appear after extension change - otherwise unnotified file overwriting may occur.
  2429. m_ShowSavedialog = true;
  2430. SetPathName(newPath, FALSE);
  2431. }
  2432. UpdateAllViews(NULL, UpdateHint().ModType());
  2433. }
  2434. CHANNELINDEX CModDoc::FindAvailableChannel() const
  2435. {
  2436. CHANNELINDEX chn = m_SndFile.GetNNAChannel(CHANNELINDEX_INVALID);
  2437. if(chn != CHANNELINDEX_INVALID)
  2438. return chn;
  2439. else
  2440. return GetNumChannels();
  2441. }
  2442. void CModDoc::RecordParamChange(PLUGINDEX plugSlot, PlugParamIndex paramIndex)
  2443. {
  2444. ::SendNotifyMessage(m_hWndFollow, WM_MOD_RECORDPARAM, plugSlot, paramIndex);
  2445. }
  2446. void CModDoc::LearnMacro(int macroToSet, PlugParamIndex paramToUse)
  2447. {
  2448. if(macroToSet < 0 || macroToSet > kSFxMacros)
  2449. {
  2450. return;
  2451. }
  2452. // If macro already exists for this param, inform user and return
  2453. if(auto macro = m_SndFile.m_MidiCfg.FindMacroForParam(paramToUse); macro >= 0)
  2454. {
  2455. CString message;
  2456. message.Format(_T("Parameter %i can already be controlled with macro %X."), static_cast<int>(paramToUse), macro);
  2457. Reporting::Information(message, _T("Macro exists for this parameter"));
  2458. return;
  2459. }
  2460. // Set new macro
  2461. if(paramToUse < 384)
  2462. {
  2463. m_SndFile.m_MidiCfg.CreateParameteredMacro(macroToSet, kSFxPlugParam, paramToUse);
  2464. } else
  2465. {
  2466. CString message;
  2467. message.Format(_T("Parameter %i beyond controllable range. Use Parameter Control Events to automate this parameter."), static_cast<int>(paramToUse));
  2468. Reporting::Information(message, _T("Macro not assigned for this parameter"));
  2469. return;
  2470. }
  2471. CString message;
  2472. message.Format(_T("Parameter %i can now be controlled with macro %X."), static_cast<int>(paramToUse), macroToSet);
  2473. Reporting::Information(message, _T("Macro assigned for this parameter"));
  2474. return;
  2475. }
  2476. void CModDoc::OnSongProperties()
  2477. {
  2478. const bool wasUsingFrequencies = m_SndFile.PeriodsAreFrequencies();
  2479. CModTypeDlg dlg(m_SndFile, CMainFrame::GetMainFrame());
  2480. if(dlg.DoModal() == IDOK)
  2481. {
  2482. UpdateAllViews(nullptr, GeneralHint().General());
  2483. ScopedLogCapturer logcapturer(*this, _T("Conversion Status"));
  2484. bool showLog = false;
  2485. if(dlg.m_nType != GetModType())
  2486. {
  2487. if(!ChangeModType(dlg.m_nType))
  2488. return;
  2489. showLog = true;
  2490. }
  2491. CHANNELINDEX newChannels = Clamp(dlg.m_nChannels, m_SndFile.GetModSpecifications().channelsMin, m_SndFile.GetModSpecifications().channelsMax);
  2492. if(newChannels != GetNumChannels())
  2493. {
  2494. const bool showCancelInRemoveDlg = m_SndFile.GetModSpecifications().channelsMax >= m_SndFile.GetNumChannels();
  2495. if(ChangeNumChannels(newChannels, showCancelInRemoveDlg))
  2496. showLog = true;
  2497. // Force update of pattern highlights / num channels
  2498. UpdateAllViews(nullptr, PatternHint().Data());
  2499. UpdateAllViews(nullptr, GeneralHint().Channels());
  2500. }
  2501. if(wasUsingFrequencies != m_SndFile.PeriodsAreFrequencies())
  2502. {
  2503. for(auto &chn : m_SndFile.m_PlayState.Chn)
  2504. {
  2505. chn.nPeriod = 0;
  2506. }
  2507. }
  2508. SetModified();
  2509. }
  2510. }
  2511. void CModDoc::ViewMIDIMapping(PLUGINDEX plugin, PlugParamIndex param)
  2512. {
  2513. CMIDIMappingDialog dlg(CMainFrame::GetMainFrame(), m_SndFile);
  2514. if(plugin != PLUGINDEX_INVALID)
  2515. {
  2516. dlg.m_Setting.SetPlugIndex(plugin + 1);
  2517. dlg.m_Setting.SetParamIndex(param);
  2518. }
  2519. dlg.DoModal();
  2520. }
  2521. void CModDoc::OnChannelManager()
  2522. {
  2523. CChannelManagerDlg *instance = CChannelManagerDlg::sharedInstanceCreate();
  2524. if(instance != nullptr)
  2525. {
  2526. if(instance->IsDisplayed())
  2527. instance->Hide();
  2528. else
  2529. {
  2530. instance->SetDocument(this);
  2531. instance->Show();
  2532. }
  2533. }
  2534. }
  2535. // Sets playback timer to playback time at given position.
  2536. // At the same time, the playback parameters (global volume, channel volume and stuff like that) are calculated for this position.
  2537. // Sample channels positions are only updated if setSamplePos is true *and* the user has chosen to update sample play positions on seek.
  2538. void CModDoc::SetElapsedTime(ORDERINDEX nOrd, ROWINDEX nRow, bool setSamplePos)
  2539. {
  2540. if(nOrd == ORDERINDEX_INVALID) return;
  2541. double t = m_SndFile.GetPlaybackTimeAt(nOrd, nRow, true, setSamplePos && (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_SYNCSAMPLEPOS) != 0);
  2542. if(t < 0)
  2543. {
  2544. // Position is never played regularly, but we may want to continue playing from here nevertheless.
  2545. m_SndFile.m_PlayState.m_nCurrentOrder = m_SndFile.m_PlayState.m_nNextOrder = nOrd;
  2546. m_SndFile.m_PlayState.m_nRow = m_SndFile.m_PlayState.m_nNextRow = nRow;
  2547. }
  2548. CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
  2549. if(pMainFrm != nullptr) pMainFrm->SetElapsedTime(std::max(0.0, t));
  2550. }
  2551. CString CModDoc::GetPatternViewInstrumentName(INSTRUMENTINDEX nInstr,
  2552. bool bEmptyInsteadOfNoName /* = false*/,
  2553. bool bIncludeIndex /* = true*/) const
  2554. {
  2555. if(nInstr >= MAX_INSTRUMENTS || m_SndFile.GetNumInstruments() == 0 || m_SndFile.Instruments[nInstr] == nullptr)
  2556. return CString();
  2557. CString displayName, instrumentName, pluginName;
  2558. // Get instrument name.
  2559. instrumentName = mpt::ToCString(m_SndFile.GetCharsetInternal(), m_SndFile.GetInstrumentName(nInstr));
  2560. // If instrument name is empty, use name of the sample mapped to C-5.
  2561. if (instrumentName.IsEmpty())
  2562. {
  2563. const SAMPLEINDEX nSmp = m_SndFile.Instruments[nInstr]->Keyboard[NOTE_MIDDLEC - 1];
  2564. if (nSmp <= m_SndFile.GetNumSamples() && m_SndFile.GetSample(nSmp).HasSampleData())
  2565. instrumentName = _T("s: ") + mpt::ToCString(m_SndFile.GetCharsetInternal(), m_SndFile.GetSampleName(nSmp));
  2566. }
  2567. // Get plugin name.
  2568. const PLUGINDEX nPlug = m_SndFile.Instruments[nInstr]->nMixPlug;
  2569. if (nPlug > 0 && nPlug < MAX_MIXPLUGINS)
  2570. pluginName = mpt::ToCString(m_SndFile.m_MixPlugins[nPlug-1].GetName());
  2571. if (pluginName.IsEmpty())
  2572. {
  2573. if(bEmptyInsteadOfNoName && instrumentName.IsEmpty())
  2574. return TEXT("");
  2575. if(instrumentName.IsEmpty())
  2576. instrumentName = _T("(no name)");
  2577. if (bIncludeIndex)
  2578. displayName.Format(_T("%02d: %s"), nInstr, instrumentName.GetString());
  2579. else
  2580. displayName = instrumentName;
  2581. } else
  2582. {
  2583. if (bIncludeIndex)
  2584. displayName.Format(TEXT("%02d: %s (%s)"), nInstr, instrumentName.GetString(), pluginName.GetString());
  2585. else
  2586. displayName.Format(TEXT("%s (%s)"), instrumentName.GetString(), pluginName.GetString());
  2587. }
  2588. return displayName;
  2589. }
  2590. void CModDoc::SafeFileClose()
  2591. {
  2592. // Verify that the main window has the focus. This saves us a lot of trouble because active modal dialogs cannot know if their pSndFile pointers are still valid.
  2593. if(GetActiveWindow() == CMainFrame::GetMainFrame()->m_hWnd)
  2594. OnFileClose();
  2595. }
  2596. // "Panic button". This resets all VSTi, OPL and sample notes.
  2597. void CModDoc::OnPanic()
  2598. {
  2599. CriticalSection cs;
  2600. m_SndFile.ResetChannels();
  2601. m_SndFile.StopAllVsti();
  2602. }
  2603. // Before saving, make sure that every char after the terminating null char is also null.
  2604. // Else, garbage might end up in various text strings that wasn't supposed to be there.
  2605. void CModDoc::FixNullStrings()
  2606. {
  2607. // Macros
  2608. m_SndFile.m_MidiCfg.Sanitize();
  2609. }
  2610. void CModDoc::OnSaveCopy()
  2611. {
  2612. DoSave(mpt::PathString(), false);
  2613. }
  2614. void CModDoc::OnSaveTemplateModule()
  2615. {
  2616. // Create template folder if doesn't exist already.
  2617. const mpt::PathString templateFolder = TrackerSettings::Instance().PathUserTemplates.GetDefaultDir();
  2618. if (!templateFolder.IsDirectory())
  2619. {
  2620. if (!CreateDirectory(templateFolder.AsNative().c_str(), nullptr))
  2621. {
  2622. Reporting::Notification(MPT_CFORMAT("Error: Unable to create template folder '{}'")( templateFolder));
  2623. return;
  2624. }
  2625. }
  2626. // Generate file name candidate.
  2627. mpt::PathString sName;
  2628. for(size_t i = 0; i < 1000; ++i)
  2629. {
  2630. sName += P_("newTemplate") + mpt::PathString::FromUnicode(mpt::ufmt::val(i));
  2631. sName += P_(".") + mpt::PathString::FromUTF8(m_SndFile.GetModSpecifications().fileExtension);
  2632. if (!(templateFolder + sName).FileOrDirectoryExists())
  2633. break;
  2634. }
  2635. // Ask file name from user.
  2636. FileDialog dlg = SaveFileDialog()
  2637. .DefaultExtension(m_SndFile.GetModSpecifications().fileExtension)
  2638. .DefaultFilename(sName)
  2639. .ExtensionFilter(ModTypeToFilter(m_SndFile))
  2640. .WorkingDirectory(templateFolder);
  2641. if(!dlg.Show())
  2642. return;
  2643. if (OnSaveDocument(dlg.GetFirstFile(), false))
  2644. {
  2645. // Update template menu.
  2646. CMainFrame::GetMainFrame()->CreateTemplateModulesMenu();
  2647. }
  2648. }
  2649. // Create an undo point that stores undo data for all existing patterns
  2650. void CModDoc::PrepareUndoForAllPatterns(bool storeChannelInfo, const char *description)
  2651. {
  2652. bool linkUndo = false;
  2653. PATTERNINDEX lastPat = 0;
  2654. for(PATTERNINDEX pat = 0; pat < m_SndFile.Patterns.Size(); pat++)
  2655. {
  2656. if(m_SndFile.Patterns.IsValidPat(pat)) lastPat = pat;
  2657. }
  2658. for(PATTERNINDEX pat = 0; pat <= lastPat; pat++)
  2659. {
  2660. if(m_SndFile.Patterns.IsValidPat(pat))
  2661. {
  2662. GetPatternUndo().PrepareUndo(pat, 0, 0, GetNumChannels(), m_SndFile.Patterns[pat].GetNumRows(), description, linkUndo, storeChannelInfo && pat == lastPat);
  2663. linkUndo = true;
  2664. }
  2665. }
  2666. }
  2667. CString CModDoc::LinearToDecibels(double value, double valueAtZeroDB)
  2668. {
  2669. if (value == 0) return _T("-inf");
  2670. double changeFactor = value / valueAtZeroDB;
  2671. double dB = 20.0 * std::log10(changeFactor);
  2672. CString s = (dB >= 0) ? _T("+") : _T("");
  2673. s.AppendFormat(_T("%.2f dB"), dB);
  2674. return s;
  2675. }
  2676. CString CModDoc::PanningToString(int32 value, int32 valueAtCenter)
  2677. {
  2678. if(value == valueAtCenter)
  2679. return _T("Center");
  2680. CString s;
  2681. s.Format(_T("%i%% %s"), (std::abs(static_cast<int>(value) - valueAtCenter) * 100) / valueAtCenter, value < valueAtCenter ? _T("Left") : _T("Right"));
  2682. return s;
  2683. }
  2684. // Apply OPL patch changes to live playback
  2685. void CModDoc::UpdateOPLInstrument(SAMPLEINDEX smp)
  2686. {
  2687. const ModSample &sample = m_SndFile.GetSample(smp);
  2688. if(!sample.uFlags[CHN_ADLIB] || !m_SndFile.m_opl || CMainFrame::GetMainFrame()->GetModPlaying() != this)
  2689. return;
  2690. CriticalSection cs;
  2691. const auto &patch = sample.adlib;
  2692. for(CHANNELINDEX chn = 0; chn < MAX_CHANNELS; chn++)
  2693. {
  2694. const auto &c = m_SndFile.m_PlayState.Chn[chn];
  2695. if(c.pModSample == &sample && c.IsSamplePlaying())
  2696. {
  2697. m_SndFile.m_opl->Patch(chn, patch);
  2698. }
  2699. }
  2700. }
  2701. // Store all view positions t settings file
  2702. void CModDoc::SerializeViews() const
  2703. {
  2704. const mpt::PathString pathName = theApp.IsPortableMode() ? GetPathNameMpt().AbsolutePathToRelative(theApp.GetInstallPath()) : GetPathNameMpt();
  2705. if(pathName.empty())
  2706. {
  2707. return;
  2708. }
  2709. std::ostringstream f(std::ios::out | std::ios::binary);
  2710. CRect mdiRect;
  2711. ::GetClientRect(CMainFrame::GetMainFrame()->m_hWndMDIClient, &mdiRect);
  2712. const int width = mdiRect.Width();
  2713. const int height = mdiRect.Height();
  2714. const int cxScreen = GetSystemMetrics(SM_CXVIRTUALSCREEN), cyScreen = GetSystemMetrics(SM_CYVIRTUALSCREEN);
  2715. // Document view positions and sizes
  2716. POSITION pos = GetFirstViewPosition();
  2717. while(pos != nullptr && !mdiRect.IsRectEmpty())
  2718. {
  2719. CModControlView *pView = dynamic_cast<CModControlView *>(GetNextView(pos));
  2720. if(pView)
  2721. {
  2722. CChildFrame *pChildFrm = (CChildFrame *)pView->GetParentFrame();
  2723. WINDOWPLACEMENT wnd;
  2724. wnd.length = sizeof(WINDOWPLACEMENT);
  2725. pChildFrm->GetWindowPlacement(&wnd);
  2726. const CRect rect = wnd.rcNormalPosition;
  2727. // Write size information
  2728. uint8 windowState = 0;
  2729. if(wnd.showCmd == SW_SHOWMAXIMIZED) windowState = 1;
  2730. else if(wnd.showCmd == SW_SHOWMINIMIZED) windowState = 2;
  2731. mpt::IO::WriteIntLE<uint8>(f, 0); // Window type
  2732. mpt::IO::WriteIntLE<uint8>(f, windowState);
  2733. mpt::IO::WriteIntLE<int32>(f, Util::muldivr(rect.left, 1 << 30, width));
  2734. mpt::IO::WriteIntLE<int32>(f, Util::muldivr(rect.top, 1 << 30, height));
  2735. mpt::IO::WriteIntLE<int32>(f, Util::muldivr(rect.Width(), 1 << 30, width));
  2736. mpt::IO::WriteIntLE<int32>(f, Util::muldivr(rect.Height(), 1 << 30, height));
  2737. std::string s = pChildFrm->SerializeView();
  2738. mpt::IO::WriteVarInt(f, s.size());
  2739. f << s;
  2740. }
  2741. }
  2742. // Plugin window positions
  2743. for(PLUGINDEX i = 0; i < MAX_MIXPLUGINS; i++)
  2744. {
  2745. if(m_SndFile.m_MixPlugins[i].IsValidPlugin() && m_SndFile.m_MixPlugins[i].editorX != int32_min && cxScreen && cyScreen)
  2746. {
  2747. // Translate screen position into percentage (to make it independent of the actual screen resolution)
  2748. int32 editorX = Util::muldivr(m_SndFile.m_MixPlugins[i].editorX, 1 << 30, cxScreen);
  2749. int32 editorY = Util::muldivr(m_SndFile.m_MixPlugins[i].editorY, 1 << 30, cyScreen);
  2750. mpt::IO::WriteIntLE<uint8>(f, 1); // Window type
  2751. mpt::IO::WriteIntLE<uint8>(f, 0); // Version
  2752. mpt::IO::WriteVarInt(f, i);
  2753. mpt::IO::WriteIntLE<int32>(f, editorX);
  2754. mpt::IO::WriteIntLE<int32>(f, editorY);
  2755. }
  2756. }
  2757. SettingsContainer &settings = theApp.GetSongSettings();
  2758. const std::string s = f.str();
  2759. settings.Write(U_("WindowSettings"), pathName.GetFullFileName().ToUnicode(), pathName);
  2760. settings.Write(U_("WindowSettings"), pathName.ToUnicode(), mpt::encode_hex(mpt::as_span(s)));
  2761. }
  2762. // Restore all view positions from settings file
  2763. void CModDoc::DeserializeViews()
  2764. {
  2765. mpt::PathString pathName = GetPathNameMpt();
  2766. if(pathName.empty()) return;
  2767. SettingsContainer &settings = theApp.GetSongSettings();
  2768. mpt::ustring s = settings.Read<mpt::ustring>(U_("WindowSettings"), pathName.ToUnicode());
  2769. if(s.size() < 2)
  2770. {
  2771. // Try relative path
  2772. pathName = pathName.RelativePathToAbsolute(theApp.GetInstallPath());
  2773. s = settings.Read<mpt::ustring>(U_("WindowSettings"), pathName.ToUnicode());
  2774. if(s.size() < 2)
  2775. {
  2776. // Try searching for filename instead of full path name
  2777. const mpt::ustring altName = settings.Read<mpt::ustring>(U_("WindowSettings"), pathName.GetFullFileName().ToUnicode());
  2778. s = settings.Read<mpt::ustring>(U_("WindowSettings"), altName);
  2779. if(s.size() < 2) return;
  2780. }
  2781. }
  2782. std::vector<std::byte> bytes = mpt::decode_hex(s);
  2783. FileReader file(mpt::as_span(bytes));
  2784. CRect mdiRect;
  2785. ::GetWindowRect(CMainFrame::GetMainFrame()->m_hWndMDIClient, &mdiRect);
  2786. const int width = mdiRect.Width();
  2787. const int height = mdiRect.Height();
  2788. const int cxScreen = GetSystemMetrics(SM_CXVIRTUALSCREEN), cyScreen = GetSystemMetrics(SM_CYVIRTUALSCREEN);
  2789. POSITION pos = GetFirstViewPosition();
  2790. CChildFrame *pChildFrm = nullptr;
  2791. if(pos != nullptr) pChildFrm = dynamic_cast<CChildFrame *>(GetNextView(pos)->GetParentFrame());
  2792. bool anyMaximized = false;
  2793. while(file.CanRead(1))
  2794. {
  2795. const uint8 windowType = file.ReadUint8();
  2796. if(windowType == 0)
  2797. {
  2798. // Document view positions and sizes
  2799. const uint8 windowState = file.ReadUint8();
  2800. CRect rect;
  2801. rect.left = Util::muldivr(file.ReadInt32LE(), width, 1 << 30);
  2802. rect.top = Util::muldivr(file.ReadInt32LE(), height, 1 << 30);
  2803. rect.right = rect.left + Util::muldivr(file.ReadInt32LE(), width, 1 << 30);
  2804. rect.bottom = rect.top + Util::muldivr(file.ReadInt32LE(), height, 1 << 30);
  2805. size_t dataSize;
  2806. file.ReadVarInt(dataSize);
  2807. FileReader data = file.ReadChunk(dataSize);
  2808. if(pChildFrm == nullptr)
  2809. {
  2810. CModDocTemplate *pTemplate = static_cast<CModDocTemplate *>(GetDocTemplate());
  2811. ASSERT_VALID(pTemplate);
  2812. pChildFrm = static_cast<CChildFrame *>(pTemplate->CreateNewFrame(this, nullptr));
  2813. if(pChildFrm != nullptr)
  2814. {
  2815. pTemplate->InitialUpdateFrame(pChildFrm, this);
  2816. }
  2817. }
  2818. if(pChildFrm != nullptr)
  2819. {
  2820. if(!mdiRect.IsRectEmpty())
  2821. {
  2822. WINDOWPLACEMENT wnd;
  2823. wnd.length = sizeof(wnd);
  2824. pChildFrm->GetWindowPlacement(&wnd);
  2825. wnd.showCmd = SW_SHOWNOACTIVATE;
  2826. if(windowState == 1 || anyMaximized)
  2827. {
  2828. // Once a window has been maximized, all following windows have to be marked as maximized as well.
  2829. wnd.showCmd = SW_MAXIMIZE;
  2830. anyMaximized = true;
  2831. } else if(windowState == 2)
  2832. {
  2833. wnd.showCmd = SW_MINIMIZE;
  2834. }
  2835. if(rect.left < width && rect.right > 0 && rect.top < height && rect.bottom > 0)
  2836. {
  2837. wnd.rcNormalPosition = CRect(rect.left, rect.top, rect.right, rect.bottom);
  2838. }
  2839. pChildFrm->SetWindowPlacement(&wnd);
  2840. }
  2841. pChildFrm->DeserializeView(data);
  2842. pChildFrm = nullptr;
  2843. }
  2844. } else if(windowType == 1)
  2845. {
  2846. if(file.ReadUint8() != 0)
  2847. break;
  2848. // Plugin window positions
  2849. PLUGINDEX plug = 0;
  2850. if(file.ReadVarInt(plug) && plug < MAX_MIXPLUGINS)
  2851. {
  2852. int32 editorX = file.ReadInt32LE();
  2853. int32 editorY = file.ReadInt32LE();
  2854. if(editorX != int32_min && editorY != int32_min)
  2855. {
  2856. m_SndFile.m_MixPlugins[plug].editorX = Util::muldivr(editorX, cxScreen, 1 << 30);
  2857. m_SndFile.m_MixPlugins[plug].editorY = Util::muldivr(editorY, cyScreen, 1 << 30);
  2858. }
  2859. }
  2860. } else
  2861. {
  2862. // Unknown type
  2863. break;
  2864. }
  2865. }
  2866. }
  2867. OPENMPT_NAMESPACE_END