mod2midi.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863
  1. /*
  2. * mod2midi.cpp
  3. * ------------
  4. * Purpose: Module to MIDI conversion (dialog + conversion code).
  5. * Notes : This code makes use of the existing MIDI plugin output functionality.
  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 "Moddoc.h"
  13. #include "../common/mptStringBuffer.h"
  14. #include "../common/mptFileIO.h"
  15. #include "mod2midi.h"
  16. #include "../soundlib/plugins/PlugInterface.h"
  17. #include "../soundlib/plugins/PluginManager.h"
  18. #include <sstream>
  19. #include "mpt/io/io.hpp"
  20. #include "mpt/io/io_stdstream.hpp"
  21. #ifndef NO_PLUGINS
  22. OPENMPT_NAMESPACE_BEGIN
  23. namespace MidiExport
  24. {
  25. // MIDI file resolution
  26. constexpr int32 ppq = 480;
  27. enum StringType : uint8
  28. {
  29. kText = 1,
  30. kCopyright = 2,
  31. kTrackName = 3,
  32. kInstrument = 4,
  33. kLyric = 5,
  34. kMarker = 6,
  35. kCue = 7,
  36. };
  37. class MidiTrack final : public IMidiPlugin
  38. {
  39. ModInstrument m_instr;
  40. const ModInstrument *const m_oldInstr;
  41. const CSoundFile &m_sndFile;
  42. const GetLengthType &m_songLength;
  43. MidiTrack *const m_tempoTrack; // Pointer to tempo track, nullptr if this is the tempo track
  44. decltype(m_MidiCh) *m_lastMidiCh = nullptr;
  45. std::array<decltype(m_instr.midiPWD), 16> m_pitchWheelDepth = { 0 };
  46. std::vector<std::array<char, 4>> m_queuedEvents;
  47. std::ostringstream f;
  48. double m_tempo = 0.0;
  49. double m_ticks = 0.0; // MIDI ticks since previous event
  50. CSoundFile::samplecount_t m_samplePos = 0; // Current sample position
  51. CSoundFile::samplecount_t m_prevEventTime = 0; // Sample position of previous event
  52. uint32 m_sampleRate;
  53. uint32 m_oldSigNumerator = 0;
  54. int32 m_oldGlobalVol = -1;
  55. const bool m_overlappingInstruments;
  56. bool m_wroteLoopStart = false;
  57. // Calculate how many MIDI ticks have passed since the last written event
  58. void UpdateTicksSinceLastEvent()
  59. {
  60. m_ticks += (m_samplePos - m_prevEventTime) * m_tempo * static_cast<double>(MidiExport::ppq) / (m_sampleRate * 60);
  61. m_prevEventTime = m_samplePos;
  62. }
  63. // Write delta tick count since last event
  64. void WriteTicks()
  65. {
  66. uint32 ticks = (m_ticks <= 0) ? 0 : mpt::saturate_round<uint32>(m_ticks);
  67. mpt::IO::WriteVarInt(f, ticks);
  68. m_ticks -= ticks;
  69. }
  70. // Update MIDI channel states in non-overlapping export mode so that all plugins have the same view
  71. void SynchronizeMidiChannelState()
  72. {
  73. if(m_tempoTrack != nullptr && !m_overlappingInstruments)
  74. {
  75. if(m_tempoTrack->m_lastMidiCh != nullptr && m_tempoTrack->m_lastMidiCh != &m_MidiCh)
  76. m_MidiCh = *m_tempoTrack->m_lastMidiCh;
  77. m_tempoTrack->m_lastMidiCh = &m_MidiCh;
  78. }
  79. }
  80. void SynchronizeMidiPitchWheelDepth(CHANNELINDEX trackerChn)
  81. {
  82. if(trackerChn >= std::size(m_sndFile.m_PlayState.Chn))
  83. return;
  84. const auto midiCh = GetMidiChannel(m_sndFile.m_PlayState.Chn[trackerChn], trackerChn);
  85. if(!m_overlappingInstruments && m_tempoTrack && m_tempoTrack->m_pitchWheelDepth[midiCh] != m_instr.midiPWD)
  86. WritePitchWheelDepth(static_cast<MidiChannel>(midiCh + MidiFirstChannel));
  87. }
  88. public:
  89. operator ModInstrument& () { return m_instr; }
  90. MidiTrack(VSTPluginLib &factory, CSoundFile &sndFile, const GetLengthType &songLength, SNDMIXPLUGIN *mixStruct, MidiTrack *tempoTrack, const mpt::ustring &name, const ModInstrument *oldInstr, bool overlappingInstruments)
  91. : IMidiPlugin(factory, sndFile, mixStruct)
  92. , m_oldInstr(oldInstr)
  93. , m_sndFile(sndFile)
  94. , m_songLength(songLength)
  95. , m_tempoTrack(tempoTrack)
  96. , m_sampleRate(sndFile.GetSampleRate())
  97. , m_overlappingInstruments(overlappingInstruments)
  98. {
  99. // Write instrument / song name
  100. WriteString(kTrackName, name);
  101. m_pMixStruct->pMixPlugin = this;
  102. }
  103. void WritePitchWheelDepth(MidiChannel midiChOverride = MidiNoChannel)
  104. {
  105. // Set up MIDI pitch wheel depth
  106. uint8 firstCh = 0, lastCh = 15;
  107. if(midiChOverride != MidiNoChannel)
  108. firstCh = lastCh = midiChOverride - MidiFirstChannel;
  109. else if(m_instr.nMidiChannel != MidiMappedChannel && m_instr.nMidiChannel != MidiNoChannel)
  110. firstCh = lastCh = m_instr.nMidiChannel - MidiFirstChannel;
  111. for(uint8 i = firstCh; i <= lastCh; i++)
  112. {
  113. const uint8 ch = 0xB0 | i;
  114. const uint8 msg[] = { ch, 0x64, 0x00, 0x00, ch, 0x65, 0x00, 0x00, ch, 0x06, static_cast<uint8>(std::abs(m_instr.midiPWD)) };
  115. WriteTicks();
  116. mpt::IO::WriteRaw(f, msg, sizeof(msg));
  117. if(m_tempoTrack)
  118. m_tempoTrack->m_pitchWheelDepth[i] = m_instr.midiPWD;
  119. }
  120. }
  121. void UpdateGlobals()
  122. {
  123. m_samplePos = m_sndFile.GetTotalSampleCount();
  124. m_sampleRate = m_sndFile.GetSampleRate();
  125. const double curTempo = m_sndFile.GetCurrentBPM();
  126. const ROWINDEX rpb = std::max(m_sndFile.m_PlayState.m_nCurrentRowsPerBeat, ROWINDEX(1));
  127. const uint32 timeSigNumerator = std::max(m_sndFile.m_PlayState.m_nCurrentRowsPerMeasure, rpb) / rpb;
  128. const bool tempoChanged = curTempo != m_tempo;
  129. const bool sigChanged = timeSigNumerator != m_oldSigNumerator;
  130. const bool volChanged = m_sndFile.m_PlayState.m_nGlobalVolume != m_oldGlobalVol;
  131. if(curTempo > 0.0)
  132. m_tempo = curTempo;
  133. m_oldSigNumerator = timeSigNumerator;
  134. m_oldGlobalVol = m_sndFile.m_PlayState.m_nGlobalVolume;
  135. if(m_tempoTrack != nullptr)
  136. return;
  137. // This is the tempo track
  138. if(tempoChanged && curTempo > 0.0)
  139. {
  140. // Write MIDI tempo
  141. WriteTicks();
  142. uint32 mspq = mpt::saturate_round<uint32>(60000000.0 / curTempo);
  143. uint8 msg[6] = { 0xFF, 0x51, 0x03, static_cast<uint8>(mspq >> 16), static_cast<uint8>(mspq >> 8), static_cast<uint8>(mspq) };
  144. mpt::IO::WriteRaw(f, msg, 6);
  145. }
  146. if(sigChanged)
  147. {
  148. // Write MIDI time signature
  149. WriteTicks();
  150. uint8 msg[7] = { 0xFF, 0x58, 0x04, static_cast<uint8>(timeSigNumerator), 2, 24, 8 };
  151. mpt::IO::WriteRaw(f, msg, 7);
  152. }
  153. if(volChanged)
  154. {
  155. // Write MIDI master volume
  156. WriteTicks();
  157. int32 midiVol = Util::muldiv(m_oldGlobalVol, 0x3FFF, MAX_GLOBAL_VOLUME);
  158. uint8 msg[9] = { 0xF0, 0x07, 0x7F, 0x7F, 0x04, 0x01, static_cast<uint8>(midiVol & 0x7F), static_cast<uint8>((midiVol >> 7) & 0x7F), 0xF7 };
  159. mpt::IO::WriteRaw(f, msg, 9);
  160. }
  161. if(!m_tempoTrack && !m_wroteLoopStart && m_sndFile.m_PlayState.m_nRow == m_songLength.lastRow && m_sndFile.m_PlayState.m_nCurrentOrder == m_songLength.lastOrder)
  162. {
  163. WriteString(kCue, U_("loopStart"));
  164. m_wroteLoopStart = true;
  165. }
  166. }
  167. void Process(float *, float *, uint32 numFrames) override
  168. {
  169. UpdateGlobals();
  170. if(m_tempoTrack != nullptr)
  171. m_tempoTrack->UpdateGlobals();
  172. for(const auto &midiData : m_queuedEvents)
  173. {
  174. WriteTicks();
  175. mpt::IO::WriteRaw(f, midiData.data(), MIDIEvents::GetEventLength(midiData[0]));
  176. }
  177. m_queuedEvents.clear();
  178. m_samplePos += numFrames;
  179. if (m_tempoTrack != nullptr)
  180. {
  181. m_tempoTrack->m_samplePos = std::max(m_tempoTrack->m_samplePos, m_samplePos);
  182. m_tempoTrack->UpdateTicksSinceLastEvent();
  183. }
  184. UpdateTicksSinceLastEvent();
  185. }
  186. // Write end marker and return the stream
  187. const std::ostringstream& Finalise()
  188. {
  189. HardAllNotesOff();
  190. UpdateTicksSinceLastEvent();
  191. if(!m_tempoTrack)
  192. WriteString(kCue, U_("loopEnd"));
  193. WriteTicks();
  194. uint8 msg[3] = { 0xFF, 0x2F, 0x00 };
  195. mpt::IO::WriteRaw(f, msg, 3);
  196. return f;
  197. }
  198. void WriteString(StringType strType, const mpt::ustring &ustr)
  199. {
  200. std::string str = mpt::ToCharset(mpt::Charset::Locale, ustr);
  201. if(!str.empty())
  202. {
  203. WriteTicks();
  204. uint8 msg[2] = { 0xFF, strType };
  205. mpt::IO::WriteRaw(f, msg, 2);
  206. mpt::IO::WriteVarInt(f, str.length());
  207. mpt::IO::WriteRaw(f, str.data(), str.length());
  208. }
  209. }
  210. void Release() override { }
  211. int32 GetUID() const override { return 0; }
  212. int32 GetVersion() const override { return 0; }
  213. void Idle() override { }
  214. uint32 GetLatency() const override { return 0; }
  215. int32 GetNumPrograms() const override { return 0; }
  216. int32 GetCurrentProgram() override { return 0; }
  217. void SetCurrentProgram(int32) override { }
  218. PlugParamIndex GetNumParameters() const override { return 0; }
  219. PlugParamValue GetParameter(PlugParamIndex) override { return 0; }
  220. void SetParameter(PlugParamIndex, PlugParamValue) override { }
  221. float RenderSilence(uint32) override { return 0.0f; }
  222. bool MidiSend(uint32 midiCode) override
  223. {
  224. std::array<char, 4> midiData;
  225. memcpy(midiData.data(), &midiCode, 4);
  226. // Note-On events go last to prevent early note-off in a situation like this:
  227. // ... ..|C-5 01
  228. // C-5 01|=== ..
  229. if(MIDIEvents::GetTypeFromEvent(midiCode) == MIDIEvents::evNoteOn)
  230. {
  231. m_queuedEvents.push_back(midiData);
  232. return true;
  233. }
  234. WriteTicks();
  235. mpt::IO::WriteRaw(f, midiData.data(), MIDIEvents::GetEventLength(midiData[0]));
  236. return true;
  237. }
  238. bool MidiSysexSend(mpt::const_byte_span sysex) override
  239. {
  240. if(sysex.size() > 1)
  241. {
  242. WriteTicks();
  243. mpt::IO::WriteIntBE<uint8>(f, 0xF0);
  244. mpt::IO::WriteVarInt(f, mpt::saturate_cast<uint32>(sysex.size() - 1));
  245. mpt::IO::WriteRaw(f, sysex.data() + 1, sysex.size() - 1);
  246. }
  247. return true;
  248. }
  249. uint8 GetMidiChannel(const ModChannel &chn, CHANNELINDEX trackChannel) const override
  250. {
  251. if(m_instr.nMidiChannel == MidiMappedChannel && trackChannel < std::size(m_sndFile.m_PlayState.Chn))
  252. {
  253. // For mapped channels, distribute tracker channels evenly over MIDI channels, but avoid channel 10 (drums)
  254. uint8 midiCh = trackChannel % 15u;
  255. if(midiCh >= 9)
  256. midiCh++;
  257. return midiCh;
  258. }
  259. return IMidiPlugin::GetMidiChannel(chn, trackChannel);
  260. }
  261. void MidiCommand(const ModInstrument &instr, uint16 note, uint16 vol, CHANNELINDEX trackChannel) override
  262. {
  263. if(note == NOTE_NOTECUT && (m_oldInstr == nullptr || !(m_oldInstr->nMixPlug != 0 && m_oldInstr->HasValidMIDIChannel())))
  264. {
  265. // The default implementation does things with Note Cut that we don't want here: it cuts all notes.
  266. note = NOTE_KEYOFF;
  267. }
  268. SynchronizeMidiChannelState();
  269. IMidiPlugin::MidiCommand(instr, note, vol, trackChannel);
  270. }
  271. void MidiPitchBendRaw(int32 pitchbend, CHANNELINDEX trackerChn) override
  272. {
  273. SynchronizeMidiChannelState();
  274. SynchronizeMidiPitchWheelDepth(trackerChn);
  275. IMidiPlugin::MidiPitchBendRaw(pitchbend, trackerChn);
  276. }
  277. void MidiPitchBend(int32 increment, int8 pwd, CHANNELINDEX trackerChn) override
  278. {
  279. SynchronizeMidiChannelState();
  280. SynchronizeMidiPitchWheelDepth(trackerChn);
  281. IMidiPlugin::MidiPitchBend(increment, pwd, trackerChn);
  282. }
  283. void MidiVibrato(int32 depth, int8 pwd, CHANNELINDEX trackerChn) override
  284. {
  285. SynchronizeMidiChannelState();
  286. SynchronizeMidiPitchWheelDepth(trackerChn);
  287. IMidiPlugin::MidiVibrato(depth, pwd, trackerChn);
  288. }
  289. bool IsNotePlaying(uint8 note, CHANNELINDEX trackerChn) override
  290. {
  291. SynchronizeMidiChannelState();
  292. return IMidiPlugin::IsNotePlaying(note, trackerChn);
  293. }
  294. void HardAllNotesOff() override
  295. {
  296. for(uint8 mc = 0; mc < m_MidiCh.size(); mc++)
  297. {
  298. PlugInstrChannel &channel = m_MidiCh[mc];
  299. for(size_t i = 0; i < std::size(channel.noteOnMap); i++)
  300. {
  301. for(auto &c : channel.noteOnMap[i])
  302. {
  303. while(c != 0)
  304. {
  305. MidiSend(MIDIEvents::NoteOff(mc, static_cast<uint8>(i), 0));
  306. c--;
  307. }
  308. }
  309. }
  310. }
  311. }
  312. void Resume() override { }
  313. void Suspend() override { }
  314. void PositionChanged() override { }
  315. bool IsInstrument() const override { return true; }
  316. bool CanRecieveMidiEvents() override { return true; }
  317. bool ShouldProcessSilence() override { return true; }
  318. #ifdef MODPLUG_TRACKER
  319. CString GetDefaultEffectName() override { return {}; }
  320. CString GetParamName(PlugParamIndex) override { return {}; }
  321. CString GetParamLabel(PlugParamIndex) override { return {}; }
  322. CString GetParamDisplay(PlugParamIndex) override { return {}; }
  323. CString GetCurrentProgramName() override { return {}; }
  324. void SetCurrentProgramName(const CString &) override { }
  325. CString GetProgramName(int32) override { return {}; }
  326. bool HasEditor() const override { return false; }
  327. #endif // MODPLUG_TRACKER
  328. int GetNumInputChannels() const override { return 0; }
  329. int GetNumOutputChannels() const override { return 0; }
  330. };
  331. class Conversion
  332. {
  333. std::vector<ModInstrument *> m_oldInstruments;
  334. std::vector<MidiTrack *> m_tracks;
  335. std::vector<SNDMIXPLUGIN> m_oldPlugins;
  336. SNDMIXPLUGIN tempoTrackPlugin;
  337. VSTPluginLib m_plugFactory;
  338. CSoundFile &m_sndFile;
  339. mpt::ofstream &m_file;
  340. const GetLengthType m_songLength;
  341. const bool m_wasInstrumentMode;
  342. public:
  343. Conversion(CSoundFile &sndFile, const InstrMap &instrMap, mpt::ofstream &file, bool overlappingInstruments, const GetLengthType &songLength)
  344. : m_oldInstruments(sndFile.GetNumInstruments())
  345. , m_plugFactory(nullptr, true, {}, {}, {})
  346. , m_sndFile(sndFile)
  347. , m_file(file)
  348. , m_songLength(songLength)
  349. , m_wasInstrumentMode(sndFile.GetNumInstruments() > 0)
  350. {
  351. m_oldPlugins.assign(std::begin(m_sndFile.m_MixPlugins), std::end(m_sndFile.m_MixPlugins));
  352. std::fill(std::begin(m_sndFile.m_MixPlugins), std::end(m_sndFile.m_MixPlugins), SNDMIXPLUGIN());
  353. for(INSTRUMENTINDEX i = 1; i <= m_sndFile.GetNumInstruments(); i++)
  354. {
  355. m_oldInstruments[i - 1] = m_sndFile.Instruments[i];
  356. }
  357. if(!m_wasInstrumentMode)
  358. {
  359. m_sndFile.m_nInstruments = std::min<INSTRUMENTINDEX>(m_sndFile.m_nSamples, MAX_INSTRUMENTS - 1u);
  360. }
  361. m_tracks.reserve(m_sndFile.GetNumInstruments() + 1);
  362. MidiTrack &tempoTrack = *(new MidiTrack(m_plugFactory, m_sndFile, m_songLength, &tempoTrackPlugin, nullptr, mpt::ToUnicode(m_sndFile.GetCharsetInternal(), m_sndFile.m_songName), nullptr, overlappingInstruments));
  363. tempoTrack.WriteString(kText, mpt::ToUnicode(m_sndFile.GetCharsetInternal(), m_sndFile.m_songMessage));
  364. tempoTrack.WriteString(kCopyright, m_sndFile.m_songArtist);
  365. m_tracks.push_back(&tempoTrack);
  366. PLUGINDEX nextPlug = 0;
  367. for(INSTRUMENTINDEX i = 1; i <= m_sndFile.GetNumInstruments(); i++)
  368. {
  369. m_sndFile.Instruments[i] = nullptr;
  370. if(!m_sndFile.GetpModDoc()->IsInstrumentUsed(i) || (m_wasInstrumentMode && m_oldInstruments[i - 1] == nullptr) || nextPlug >= MAX_MIXPLUGINS)
  371. continue;
  372. // FIXME: Having > MAX_MIXPLUGINS used instruments won't work! So in MPTM, you can only use 250 out of 255 instruments...
  373. SNDMIXPLUGIN &mixPlugin = m_sndFile.m_MixPlugins[nextPlug++];
  374. ModInstrument *oldInstr = m_wasInstrumentMode ? m_oldInstruments[i - 1] : nullptr;
  375. MidiTrack &midiInstr = *(new MidiTrack(m_plugFactory, m_sndFile, m_songLength, &mixPlugin, &tempoTrack, m_wasInstrumentMode ? mpt::ToUnicode(m_sndFile.GetCharsetInternal(), oldInstr->name) : mpt::ToUnicode(m_sndFile.GetCharsetInternal(), m_sndFile.GetSampleName(i)), oldInstr, overlappingInstruments));
  376. ModInstrument &instr = midiInstr;
  377. mixPlugin.pMixPlugin = &midiInstr;
  378. m_sndFile.Instruments[i] = &instr;
  379. m_tracks.push_back(&midiInstr);
  380. if(m_wasInstrumentMode) instr = *oldInstr;
  381. instr.nMixPlug = nextPlug;
  382. if((oldInstr != nullptr && oldInstr->nMixPlug == 0) || instr.nMidiChannel == MidiNoChannel)
  383. {
  384. instr.midiPWD = 12;
  385. }
  386. instr.nMidiChannel = instrMap[i].channel;
  387. if(instrMap[i].channel != MidiFirstChannel + 9)
  388. {
  389. // Melodic instrument
  390. instr.nMidiProgram = instrMap[i].program;
  391. } else
  392. {
  393. // Drums
  394. if(oldInstr != nullptr && oldInstr->nMidiChannel != MidiFirstChannel + 9)
  395. instr.nMidiProgram = 0;
  396. if(instrMap[i].program > 0)
  397. {
  398. for(auto &key : instr.NoteMap)
  399. {
  400. key = instrMap[i].program + NOTE_MIN - 1;
  401. }
  402. }
  403. }
  404. midiInstr.WritePitchWheelDepth();
  405. }
  406. mpt::IO::WriteRaw(m_file, "MThd", 4);
  407. mpt::IO::WriteIntBE<uint32>(m_file, 6);
  408. mpt::IO::WriteIntBE<uint16>(m_file, 1); // Type 1 MIDI - multiple simultaneous tracks
  409. mpt::IO::WriteIntBE<uint16>(m_file, static_cast<uint16>(m_tracks.size())); // Number of tracks
  410. mpt::IO::WriteIntBE<uint16>(m_file, MidiExport::ppq);
  411. }
  412. void Finalise()
  413. {
  414. for(auto track : m_tracks)
  415. {
  416. std::string data = track->Finalise().str();
  417. if(!data.empty())
  418. {
  419. const uint32 len = mpt::saturate_cast<uint32>(data.size());
  420. mpt::IO::WriteRaw(m_file, "MTrk", 4);
  421. mpt::IO::WriteIntBE<uint32>(m_file, len);
  422. mpt::IO::WriteRaw(m_file, data.data(), len);
  423. }
  424. }
  425. }
  426. ~Conversion()
  427. {
  428. for(INSTRUMENTINDEX i = 1; i <= m_sndFile.GetNumInstruments(); i++)
  429. {
  430. m_sndFile.Instruments[i] = m_wasInstrumentMode ? m_oldInstruments[i - 1] : nullptr;
  431. }
  432. if(!m_wasInstrumentMode)
  433. {
  434. m_sndFile.m_nInstruments = 0;
  435. }
  436. for(auto &plug : m_sndFile.m_MixPlugins)
  437. {
  438. plug.Destroy();
  439. }
  440. for(auto &track : m_tracks)
  441. {
  442. delete track; // Resets m_MixPlugins[i].pMixPlugin, so do it before copying back the old structs
  443. }
  444. std::move(m_oldPlugins.cbegin(), m_oldPlugins.cend(), std::begin(m_sndFile.m_MixPlugins));
  445. // Be sure that instrument pointers to our faked instruments are gone.
  446. const auto muteFlag = CSoundFile::GetChannelMuteFlag();
  447. for(CHANNELINDEX i = 0; i < MAX_CHANNELS; i++)
  448. {
  449. m_sndFile.m_PlayState.Chn[i].Reset(ModChannel::resetTotal, m_sndFile, i, muteFlag);
  450. }
  451. }
  452. };
  453. class DummyAudioTarget : public IAudioTarget
  454. {
  455. public:
  456. void Process(mpt::audio_span_interleaved<MixSampleInt>) override { }
  457. void Process(mpt::audio_span_interleaved<MixSampleFloat>) override { }
  458. };
  459. }
  460. ////////////////////////////////////////////////////////////////////////////////////
  461. //
  462. // CModToMidi dialog implementation
  463. //
  464. bool CModToMidi::s_overlappingInstruments = false;
  465. BEGIN_MESSAGE_MAP(CModToMidi, CDialog)
  466. ON_CBN_SELCHANGE(IDC_COMBO1, &CModToMidi::UpdateDialog)
  467. ON_CBN_SELCHANGE(IDC_COMBO2, &CModToMidi::OnChannelChanged)
  468. ON_CBN_SELCHANGE(IDC_COMBO3, &CModToMidi::OnProgramChanged)
  469. ON_COMMAND(IDC_CHECK1, &CModToMidi::OnOverlapChanged)
  470. ON_WM_VSCROLL()
  471. END_MESSAGE_MAP()
  472. void CModToMidi::DoDataExchange(CDataExchange *pDX)
  473. {
  474. CDialog::DoDataExchange(pDX);
  475. DDX_Control(pDX, IDC_COMBO1, m_CbnInstrument);
  476. DDX_Control(pDX, IDC_COMBO2, m_CbnChannel);
  477. DDX_Control(pDX, IDC_COMBO3, m_CbnProgram);
  478. DDX_Control(pDX, IDC_SPIN1, m_SpinInstrument);
  479. }
  480. CModToMidi::CModToMidi(CSoundFile &sndFile, CWnd *pWndParent)
  481. : CDialog(IDD_MOD2MIDI, pWndParent)
  482. , m_sndFile(sndFile)
  483. , m_instrMap((sndFile.GetNumInstruments() ? sndFile.GetNumInstruments() : sndFile.GetNumSamples()) + 1)
  484. {
  485. for (INSTRUMENTINDEX i = 1; i <= m_sndFile.GetNumInstruments(); i++)
  486. {
  487. ModInstrument *pIns = m_sndFile.Instruments[i];
  488. if(pIns != nullptr)
  489. {
  490. m_instrMap[i].channel = pIns->nMidiChannel;
  491. if(m_instrMap[i].channel != MidiFirstChannel + 9)
  492. {
  493. if(!pIns->HasValidMIDIChannel())
  494. m_instrMap[i].channel = MidiMappedChannel;
  495. m_instrMap[i].program = pIns->nMidiProgram;
  496. }
  497. }
  498. }
  499. }
  500. BOOL CModToMidi::OnInitDialog()
  501. {
  502. CString s;
  503. CDialog::OnInitDialog();
  504. // Fill instruments box
  505. m_SpinInstrument.SetRange(-1, 1);
  506. m_SpinInstrument.SetPos(0);
  507. m_currentInstr = 1;
  508. m_CbnInstrument.SetRedraw(FALSE);
  509. if(m_sndFile.GetNumInstruments())
  510. {
  511. for(INSTRUMENTINDEX nIns = 1; nIns <= m_sndFile.GetNumInstruments(); nIns++)
  512. {
  513. ModInstrument *pIns = m_sndFile.Instruments[nIns];
  514. if(pIns && m_sndFile.GetpModDoc()->IsInstrumentUsed(nIns, false))
  515. {
  516. const CString name = m_sndFile.GetpModDoc()->GetPatternViewInstrumentName(nIns);
  517. m_CbnInstrument.SetItemData(m_CbnInstrument.AddString(name), nIns);
  518. }
  519. }
  520. } else
  521. {
  522. for(SAMPLEINDEX nSmp = 1; nSmp <= m_sndFile.GetNumSamples(); nSmp++)
  523. {
  524. if(m_sndFile.GetpModDoc()->IsSampleUsed(nSmp, false))
  525. {
  526. s.Format(_T("%02d: "), nSmp);
  527. s += mpt::ToCString(m_sndFile.GetCharsetInternal(), m_sndFile.m_szNames[nSmp]);
  528. m_CbnInstrument.SetItemData(m_CbnInstrument.AddString(s), nSmp);
  529. }
  530. }
  531. }
  532. m_CbnInstrument.SetRedraw(TRUE);
  533. m_CbnInstrument.SetCurSel(0);
  534. // Fill channels box
  535. m_CbnChannel.SetRedraw(FALSE);
  536. m_CbnChannel.SetItemData(m_CbnChannel.AddString(_T("Don't Export")), MidiNoChannel);
  537. m_CbnChannel.SetItemData(m_CbnChannel.AddString(_T("Melodic (any)")), MidiMappedChannel);
  538. m_CbnChannel.SetItemData(m_CbnChannel.AddString(_T("Percussions")), MidiFirstChannel + 9);
  539. for(uint32 chn = 1; chn <= 16; chn++)
  540. {
  541. if(chn == 10)
  542. continue;
  543. s.Format(_T("Melodic %u"), chn);
  544. m_CbnChannel.SetItemData(m_CbnChannel.AddString(s), MidiFirstChannel - 1 + chn);
  545. }
  546. m_CbnChannel.SetRedraw(TRUE);
  547. m_CbnChannel.SetCurSel(1);
  548. m_currentInstr = 1;
  549. m_percussion = true;
  550. FillProgramBox(false);
  551. m_CbnProgram.SetCurSel(0);
  552. UpdateDialog();
  553. CheckDlgButton(IDC_CHECK1, s_overlappingInstruments ? BST_CHECKED : BST_UNCHECKED);
  554. return TRUE;
  555. }
  556. void CModToMidi::FillProgramBox(bool percussion)
  557. {
  558. if(m_percussion == percussion)
  559. return;
  560. m_CbnProgram.SetRedraw(FALSE);
  561. m_CbnProgram.ResetContent();
  562. if(percussion)
  563. {
  564. m_CbnProgram.SetItemData(m_CbnProgram.AddString(_T("Mapped")), 0);
  565. for(ModCommand::NOTE i = 0; i < 61; i++)
  566. {
  567. ModCommand::NOTE note = i + 24;
  568. auto s = MPT_CFORMAT("{} ({}): {}")(
  569. note,
  570. mpt::ToCString(m_sndFile.GetNoteName(note + NOTE_MIN)),
  571. mpt::ToCString(mpt::Charset::ASCII, szMidiPercussionNames[i]));
  572. m_CbnProgram.SetItemData(m_CbnProgram.AddString(s), note);
  573. }
  574. } else
  575. {
  576. m_CbnProgram.SetItemData(m_CbnProgram.AddString(_T("No Program Change")), 0);
  577. for(int i = 1; i <= 128; i++)
  578. {
  579. auto s = MPT_CFORMAT("{}: {}")(
  580. mpt::cfmt::dec0<3>(i),
  581. mpt::ToCString(mpt::Charset::ASCII, szMidiProgramNames[i - 1]));
  582. m_CbnProgram.SetItemData(m_CbnProgram.AddString(s), i);
  583. }
  584. }
  585. m_CbnProgram.SetRedraw(TRUE);
  586. m_CbnProgram.Invalidate(FALSE);
  587. m_percussion = percussion;
  588. }
  589. void CModToMidi::UpdateDialog()
  590. {
  591. m_currentInstr = static_cast<UINT>(m_CbnInstrument.GetItemData(m_CbnInstrument.GetCurSel()));
  592. const bool validInstr = (m_currentInstr > 0 && m_currentInstr < m_instrMap.size());
  593. m_CbnProgram.EnableWindow(validInstr && m_instrMap[m_currentInstr].channel != MidiNoChannel);
  594. if(!validInstr)
  595. return;
  596. uint8 nMidiCh = m_instrMap[m_currentInstr].channel;
  597. int sel;
  598. switch(nMidiCh)
  599. {
  600. case MidiNoChannel:
  601. sel = 0;
  602. break;
  603. case MidiMappedChannel:
  604. sel = 1;
  605. break;
  606. case MidiFirstChannel + 9:
  607. sel = 2;
  608. break;
  609. default:
  610. sel = nMidiCh - MidiFirstChannel + 2;
  611. if(nMidiCh < MidiFirstChannel + 9)
  612. sel++;
  613. }
  614. if(!m_percussion && (nMidiCh == MidiFirstChannel + 9))
  615. {
  616. FillProgramBox(true);
  617. } else if(m_percussion && (nMidiCh != MidiFirstChannel + 9))
  618. {
  619. FillProgramBox(false);
  620. }
  621. m_CbnChannel.SetCurSel(sel);
  622. UINT nMidiProgram = m_instrMap[m_currentInstr].program;
  623. if(m_percussion)
  624. {
  625. if(nMidiProgram >= 24 && nMidiProgram <= 84)
  626. nMidiProgram -= 23;
  627. else
  628. nMidiProgram = 0;
  629. } else
  630. {
  631. if(nMidiProgram > 127)
  632. nMidiProgram = 0;
  633. }
  634. m_CbnProgram.SetCurSel(nMidiProgram);
  635. }
  636. void CModToMidi::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
  637. {
  638. CDialog::OnVScroll(nSBCode, nPos, pScrollBar);
  639. int pos = m_SpinInstrument.GetPos32();
  640. if(pos)
  641. {
  642. m_SpinInstrument.SetPos(0);
  643. int numIns = m_CbnInstrument.GetCount();
  644. int ins = m_CbnInstrument.GetCurSel() + pos;
  645. if(ins < 0)
  646. ins = numIns - 1;
  647. if(ins >= numIns)
  648. ins = 0;
  649. m_CbnInstrument.SetCurSel(ins);
  650. UpdateDialog();
  651. }
  652. }
  653. void CModToMidi::OnChannelChanged()
  654. {
  655. uint8 midiCh = static_cast<uint8>(m_CbnChannel.GetItemData(m_CbnChannel.GetCurSel()));
  656. if(m_currentInstr >= m_instrMap.size())
  657. return;
  658. const auto oldCh = m_instrMap[m_currentInstr].channel;
  659. m_instrMap[m_currentInstr].channel = midiCh;
  660. if(midiCh == MidiNoChannel
  661. || oldCh == MidiNoChannel
  662. || (!m_percussion && midiCh == MidiFirstChannel + 9)
  663. || (m_percussion && midiCh != MidiFirstChannel + 9))
  664. {
  665. UpdateDialog();
  666. }
  667. }
  668. void CModToMidi::OnProgramChanged()
  669. {
  670. DWORD_PTR nProgram = m_CbnProgram.GetItemData(m_CbnProgram.GetCurSel());
  671. if (nProgram == CB_ERR) return;
  672. if ((m_currentInstr > 0) && (m_currentInstr < MAX_SAMPLES))
  673. {
  674. m_instrMap[m_currentInstr].program = static_cast<uint8>(nProgram);
  675. }
  676. }
  677. void CModToMidi::OnOverlapChanged()
  678. {
  679. s_overlappingInstruments = IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED;
  680. }
  681. void CModToMidi::OnOK()
  682. {
  683. for(size_t i = 1; i < m_instrMap.size(); i++)
  684. {
  685. if(m_instrMap[i].channel != MidiNoChannel)
  686. {
  687. CDialog::OnOK();
  688. return;
  689. }
  690. }
  691. auto choice = Reporting::Confirm(_T("No instruments have been selected for export. Would you still like to export the file?"), true, true);
  692. if(choice == cnfYes)
  693. CDialog::OnOK();
  694. else if(choice == cnfNo)
  695. CDialog::OnCancel();
  696. }
  697. void CDoMidiConvert::Run()
  698. {
  699. CMainFrame::GetMainFrame()->PauseMod(m_sndFile.GetpModDoc());
  700. const auto songLength = m_sndFile.GetLength(eNoAdjust).front();
  701. const double duration = songLength.duration;
  702. const uint64 totalSamples = mpt::saturate_round<uint64>(duration * m_sndFile.m_MixerSettings.gdwMixingFreq);
  703. SetRange(0, totalSamples);
  704. auto conv = std::make_unique<MidiExport::Conversion>(m_sndFile, m_instrMap, m_file, CModToMidi::s_overlappingInstruments, songLength);
  705. auto startTime = timeGetTime(), prevTime = startTime;
  706. m_sndFile.SetCurrentOrder(0);
  707. m_sndFile.GetLength(eAdjust, GetLengthTarget(0, 0));
  708. m_sndFile.m_SongFlags.reset(SONG_PATTERNLOOP);
  709. int oldRepCount = m_sndFile.GetRepeatCount();
  710. m_sndFile.SetRepeatCount(0);
  711. m_sndFile.m_bIsRendering = true;
  712. EnableTaskbarProgress();
  713. MidiExport::DummyAudioTarget target;
  714. UINT ok = IDOK;
  715. const auto fmt = MPT_TFORMAT("Rendering file... ({}mn{}s, {}mn{}s remaining)");
  716. while(m_sndFile.Read(MIXBUFFERSIZE, target) > 0)
  717. {
  718. auto currentTime = timeGetTime();
  719. if(currentTime - prevTime >= 16)
  720. {
  721. prevTime = currentTime;
  722. uint64 curSamples = m_sndFile.GetTotalSampleCount();
  723. uint32 curTime = static_cast<uint32>(curSamples / m_sndFile.m_MixerSettings.gdwMixingFreq);
  724. uint32 timeRemaining = 0;
  725. if(curSamples > 0 && curSamples < totalSamples)
  726. {
  727. timeRemaining = static_cast<uint32>(((currentTime - startTime) * (totalSamples - curSamples) / curSamples) / 1000u);
  728. }
  729. SetText(fmt(curTime / 60u, mpt::tfmt::dec0<2>(curTime % 60u), timeRemaining / 60u, mpt::tfmt::dec0<2>(timeRemaining % 60u)).c_str());
  730. SetProgress(curSamples);
  731. ProcessMessages();
  732. if(m_abort)
  733. {
  734. ok = IDCANCEL;
  735. break;
  736. }
  737. }
  738. }
  739. conv->Finalise();
  740. m_sndFile.m_bIsRendering = false;
  741. m_sndFile.SetRepeatCount(oldRepCount);
  742. EndDialog(ok);
  743. }
  744. OPENMPT_NAMESPACE_END
  745. #endif // NO_PLUGINS