1
0

MidiInOut.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. /*
  2. * MidiInOut.cpp
  3. * -------------
  4. * Purpose: A plugin for sending and receiving MIDI data.
  5. * Notes : (currently none)
  6. * Authors: Johannes Schultz (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 "MidiInOut.h"
  11. #include "MidiInOutEditor.h"
  12. #include "../../common/FileReader.h"
  13. #include "../../soundlib/Sndfile.h"
  14. #include "../Reporting.h"
  15. #include <algorithm>
  16. #include <sstream>
  17. #ifdef MODPLUG_TRACKER
  18. #include "../Mptrack.h"
  19. #endif
  20. #include "mpt/io/io.hpp"
  21. #include "mpt/io/io_stdstream.hpp"
  22. OPENMPT_NAMESPACE_BEGIN
  23. IMixPlugin* MidiInOut::Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
  24. {
  25. try
  26. {
  27. return new (std::nothrow) MidiInOut(factory, sndFile, mixStruct);
  28. } catch(RtMidiError &)
  29. {
  30. return nullptr;
  31. }
  32. }
  33. MidiInOut::MidiInOut(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
  34. : IMidiPlugin(factory, sndFile, mixStruct)
  35. , m_inputDevice(m_midiIn)
  36. , m_outputDevice(m_midiOut)
  37. #ifdef MODPLUG_TRACKER
  38. , m_programName(_T("Default"))
  39. #endif // MODPLUG_TRACKER
  40. {
  41. m_mixBuffer.Initialize(2, 2);
  42. InsertIntoFactoryList();
  43. }
  44. MidiInOut::~MidiInOut()
  45. {
  46. MidiInOut::Suspend();
  47. }
  48. uint32 MidiInOut::GetLatency() const
  49. {
  50. // There is only a latency if the user-provided latency value is greater than the negative output latency.
  51. return mpt::saturate_round<uint32>(std::min(0.0, m_latency + GetOutputLatency()) * m_SndFile.GetSampleRate());
  52. }
  53. void MidiInOut::SaveAllParameters()
  54. {
  55. auto chunk = GetChunk(false);
  56. if(chunk.empty())
  57. return;
  58. m_pMixStruct->defaultProgram = -1;
  59. m_pMixStruct->pluginData.assign(chunk.begin(), chunk.end());
  60. }
  61. void MidiInOut::RestoreAllParameters(int32 program)
  62. {
  63. IMixPlugin::RestoreAllParameters(program); // First plugin version didn't use chunks.
  64. SetChunk(mpt::as_span(m_pMixStruct->pluginData), false);
  65. }
  66. enum ChunkFlags
  67. {
  68. kLatencyCompensation = 0x01, // Implicit in current plugin version
  69. kLatencyPresent = 0x02, // Latency value is present as double-precision float
  70. kIgnoreTiming = 0x04, // Do not send timing and sequencing information
  71. kFriendlyInputName = 0x08, // Preset also stores friendly name of input device
  72. kFriendlyOutputName = 0x10, // Preset also stores friendly name of output device
  73. };
  74. IMixPlugin::ChunkData MidiInOut::GetChunk(bool /*isBank*/)
  75. {
  76. const std::string programName8 = mpt::ToCharset(mpt::Charset::UTF8, m_programName);
  77. uint32 flags = kLatencyCompensation | kLatencyPresent | (m_sendTimingInfo ? 0 : kIgnoreTiming);
  78. #ifdef MODPLUG_TRACKER
  79. const std::string inFriendlyName = (m_inputDevice.index == MidiDevice::NO_MIDI_DEVICE) ? m_inputDevice.name : mpt::ToCharset(mpt::Charset::UTF8, theApp.GetFriendlyMIDIPortName(mpt::ToUnicode(mpt::Charset::UTF8, m_inputDevice.name), true, false));
  80. const std::string outFriendlyName = (m_outputDevice.index == MidiDevice::NO_MIDI_DEVICE) ? m_outputDevice.name : mpt::ToCharset(mpt::Charset::UTF8, theApp.GetFriendlyMIDIPortName(mpt::ToUnicode(mpt::Charset::UTF8, m_outputDevice.name), false, false));
  81. if(inFriendlyName != m_inputDevice.name)
  82. {
  83. flags |= kFriendlyInputName;
  84. }
  85. if(outFriendlyName != m_outputDevice.name)
  86. {
  87. flags |= kFriendlyOutputName;
  88. }
  89. #endif
  90. std::ostringstream s;
  91. mpt::IO::WriteRaw(s, "fEvN", 4); // VST program chunk magic
  92. mpt::IO::WriteIntLE< int32>(s, GetVersion());
  93. mpt::IO::WriteIntLE<uint32>(s, 1); // Number of programs
  94. mpt::IO::WriteIntLE<uint32>(s, static_cast<uint32>(programName8.size()));
  95. mpt::IO::WriteIntLE<uint32>(s, m_inputDevice.index);
  96. mpt::IO::WriteIntLE<uint32>(s, static_cast<uint32>(m_inputDevice.name.size()));
  97. mpt::IO::WriteIntLE<uint32>(s, m_outputDevice.index);
  98. mpt::IO::WriteIntLE<uint32>(s, static_cast<uint32>(m_outputDevice.name.size()));
  99. mpt::IO::WriteIntLE<uint32>(s, flags);
  100. mpt::IO::WriteRaw(s, programName8.c_str(), programName8.size());
  101. mpt::IO::WriteRaw(s, m_inputDevice.name.c_str(), m_inputDevice.name.size());
  102. mpt::IO::WriteRaw(s, m_outputDevice.name.c_str(), m_outputDevice.name.size());
  103. mpt::IO::WriteIntLE<uint64>(s, IEEE754binary64LE(m_latency).GetInt64());
  104. #ifdef MODPLUG_TRACKER
  105. if(flags & kFriendlyInputName)
  106. {
  107. mpt::IO::WriteIntLE<uint32>(s, static_cast<uint32>(inFriendlyName.size()));
  108. mpt::IO::WriteRaw(s, inFriendlyName.c_str(), inFriendlyName.size());
  109. }
  110. if(flags & kFriendlyOutputName)
  111. {
  112. mpt::IO::WriteIntLE<uint32>(s, static_cast<uint32>(outFriendlyName.size()));
  113. mpt::IO::WriteRaw(s, outFriendlyName.c_str(), outFriendlyName.size());
  114. }
  115. #endif
  116. m_chunkData = s.str();
  117. return mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(m_chunkData));
  118. }
  119. // Try to match a port name against stored name or friendly name (preferred)
  120. static void FindPort(MidiDevice::ID &id, unsigned int numPorts, const std::string &name, const std::string &friendlyName, MidiDevice &midiDevice, bool isInput)
  121. {
  122. bool foundFriendly = false;
  123. for(unsigned int i = 0; i < numPorts; i++)
  124. {
  125. try
  126. {
  127. auto portName = midiDevice.GetPortName(i);
  128. bool deviceNameMatches = (portName == name);
  129. #ifdef MODPLUG_TRACKER
  130. if(!friendlyName.empty() && friendlyName == mpt::ToCharset(mpt::Charset::UTF8, theApp.GetFriendlyMIDIPortName(mpt::ToUnicode(mpt::Charset::UTF8, portName), isInput, false)))
  131. {
  132. // Preferred match
  133. id = i;
  134. foundFriendly = true;
  135. if(deviceNameMatches)
  136. {
  137. return;
  138. }
  139. }
  140. #else
  141. MPT_UNREFERENCED_PARAMETER(friendlyName)
  142. #endif
  143. if(deviceNameMatches && !foundFriendly)
  144. {
  145. id = i;
  146. }
  147. } catch(const RtMidiError &)
  148. {
  149. }
  150. }
  151. }
  152. void MidiInOut::SetChunk(const ChunkData &chunk, bool /*isBank*/)
  153. {
  154. FileReader file(chunk);
  155. if(!file.CanRead(9 * sizeof(uint32))
  156. || !file.ReadMagic("fEvN") // VST program chunk magic
  157. || file.ReadInt32LE() > GetVersion() // Plugin version
  158. || file.ReadUint32LE() < 1) // Number of programs
  159. return;
  160. uint32 nameStrSize = file.ReadUint32LE();
  161. MidiDevice::ID inID = file.ReadUint32LE();
  162. uint32 inStrSize = file.ReadUint32LE();
  163. MidiDevice::ID outID = file.ReadUint32LE();
  164. uint32 outStrSize = file.ReadUint32LE();
  165. uint32 flags = file.ReadUint32LE();
  166. std::string progName, inName, outName, inFriendlyName, outFriendlyName;
  167. file.ReadString<mpt::String::maybeNullTerminated>(progName, nameStrSize);
  168. m_programName = mpt::ToCString(mpt::Charset::UTF8, progName);
  169. file.ReadString<mpt::String::maybeNullTerminated>(inName, inStrSize);
  170. file.ReadString<mpt::String::maybeNullTerminated>(outName, outStrSize);
  171. if(flags & kLatencyPresent)
  172. m_latency = file.ReadDoubleLE();
  173. else
  174. m_latency = 0.0f;
  175. m_sendTimingInfo = !(flags & kIgnoreTiming);
  176. if(flags & kFriendlyInputName)
  177. file.ReadString<mpt::String::maybeNullTerminated>(inFriendlyName, file.ReadUint32LE());
  178. if(flags & kFriendlyOutputName)
  179. file.ReadString<mpt::String::maybeNullTerminated>(outFriendlyName, file.ReadUint32LE());
  180. // Try to match an input port name against stored name or friendly name (preferred)
  181. FindPort(inID, m_midiIn.getPortCount(), inName, inFriendlyName, m_inputDevice, true);
  182. FindPort(outID, m_midiOut.getPortCount(), outName, outFriendlyName, m_outputDevice, false);
  183. SetParameter(MidiInOut::kInputParameter, DeviceIDToParameter(inID));
  184. SetParameter(MidiInOut::kOutputParameter, DeviceIDToParameter(outID));
  185. }
  186. void MidiInOut::SetParameter(PlugParamIndex index, PlugParamValue value)
  187. {
  188. value = mpt::safe_clamp(value, 0.0f, 1.0f);
  189. MidiDevice::ID newDevice = ParameterToDeviceID(value);
  190. OpenDevice(newDevice, (index == kInputParameter));
  191. // Update selection in editor
  192. MidiInOutEditor *editor = dynamic_cast<MidiInOutEditor *>(GetEditor());
  193. if(editor != nullptr)
  194. editor->SetCurrentDevice((index == kInputParameter), newDevice);
  195. }
  196. float MidiInOut::GetParameter(PlugParamIndex index)
  197. {
  198. const MidiDevice &device = (index == kInputParameter) ? m_inputDevice : m_outputDevice;
  199. return DeviceIDToParameter(device.index);
  200. }
  201. #ifdef MODPLUG_TRACKER
  202. CString MidiInOut::GetParamName(PlugParamIndex param)
  203. {
  204. if(param == kInputParameter)
  205. return _T("MIDI In");
  206. else
  207. return _T("MIDI Out");
  208. }
  209. // Parameter value as text
  210. CString MidiInOut::GetParamDisplay(PlugParamIndex param)
  211. {
  212. const MidiDevice &device = (param == kInputParameter) ? m_inputDevice : m_outputDevice;
  213. return mpt::ToCString(mpt::Charset::UTF8, device.name);
  214. }
  215. CAbstractVstEditor *MidiInOut::OpenEditor()
  216. {
  217. try
  218. {
  219. return new MidiInOutEditor(*this);
  220. } catch(mpt::out_of_memory e)
  221. {
  222. mpt::delete_out_of_memory(e);
  223. return nullptr;
  224. }
  225. }
  226. #endif // MODPLUG_TRACKER
  227. // Processing (we don't process any audio, only MIDI messages)
  228. void MidiInOut::Process(float *, float *, uint32 numFrames)
  229. {
  230. if(m_midiOut.isPortOpen())
  231. {
  232. mpt::lock_guard<mpt::mutex> lock(m_mutex);
  233. // Send MIDI clock
  234. if(m_nextClock < 1)
  235. {
  236. if(m_sendTimingInfo)
  237. {
  238. m_outQueue.push_back(Message(GetOutputTimestamp(), 0xF8));
  239. }
  240. double bpm = m_SndFile.GetCurrentBPM();
  241. if(bpm > 0.0)
  242. {
  243. m_nextClock += 2.5 * m_SndFile.GetSampleRate() / bpm;
  244. }
  245. }
  246. m_nextClock -= numFrames;
  247. double now = m_clock.Now() * (1.0 / 1000.0);
  248. auto message = m_outQueue.begin();
  249. while(message != m_outQueue.end() && message->m_time <= now)
  250. {
  251. try
  252. {
  253. m_midiOut.sendMessage(message->m_message, message->m_size);
  254. } catch(const RtMidiError &)
  255. {
  256. }
  257. message++;
  258. }
  259. m_outQueue.erase(m_outQueue.begin(), message);
  260. }
  261. }
  262. void MidiInOut::InputCallback(double /*deltatime*/, std::vector<unsigned char> &message)
  263. {
  264. // We will check the bypass status before passing on the message, and not before entering the function,
  265. // because otherwise we might read garbage if we toggle bypass status in the middle of a SysEx message.
  266. bool isBypassed = IsBypassed();
  267. if(message.empty())
  268. {
  269. return;
  270. } else if(!m_bufferedInput.empty())
  271. {
  272. // SysEx message (continued)
  273. m_bufferedInput.insert(m_bufferedInput.end(), message.begin(), message.end());
  274. if(message.back() == 0xF7)
  275. {
  276. // End of message found!
  277. if(!isBypassed)
  278. ReceiveSysex(mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(m_bufferedInput)));
  279. m_bufferedInput.clear();
  280. }
  281. } else if(message.front() == 0xF0)
  282. {
  283. // Start of SysEx message...
  284. if(message.back() != 0xF7)
  285. m_bufferedInput.insert(m_bufferedInput.end(), message.begin(), message.end()); // ...but not the end!
  286. else if(!isBypassed)
  287. ReceiveSysex(mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(message)));
  288. } else if(!isBypassed)
  289. {
  290. // Regular message
  291. uint32 msg = 0;
  292. memcpy(&msg, message.data(), std::min(message.size(), sizeof(msg)));
  293. ReceiveMidi(msg);
  294. }
  295. }
  296. // Resume playback
  297. void MidiInOut::Resume()
  298. {
  299. // Resume MIDI I/O
  300. m_isResumed = true;
  301. m_nextClock = 0;
  302. m_outQueue.clear();
  303. m_clock.SetResolution(1);
  304. OpenDevice(m_inputDevice.index, true);
  305. OpenDevice(m_outputDevice.index, false);
  306. if(m_midiOut.isPortOpen() && m_sendTimingInfo)
  307. {
  308. MidiSend(0xFA); // Start
  309. }
  310. }
  311. // Stop playback
  312. void MidiInOut::Suspend()
  313. {
  314. // Suspend MIDI I/O
  315. if(m_midiOut.isPortOpen() && m_sendTimingInfo)
  316. {
  317. try
  318. {
  319. unsigned char message[1] = { 0xFC }; // Stop
  320. m_midiOut.sendMessage(message, 1);
  321. } catch(const RtMidiError &)
  322. {
  323. }
  324. }
  325. //CloseDevice(inputDevice);
  326. CloseDevice(m_outputDevice);
  327. m_clock.SetResolution(0);
  328. m_isResumed = false;
  329. }
  330. // Playback discontinuity
  331. void MidiInOut::PositionChanged()
  332. {
  333. if(m_sendTimingInfo)
  334. {
  335. MidiSend(0xFC); // Stop
  336. MidiSend(0xFA); // Start
  337. }
  338. }
  339. void MidiInOut::Bypass(bool bypass)
  340. {
  341. if(bypass)
  342. {
  343. mpt::lock_guard<mpt::mutex> lock(m_mutex);
  344. m_outQueue.clear();
  345. }
  346. IMidiPlugin::Bypass(bypass);
  347. }
  348. bool MidiInOut::MidiSend(uint32 midiCode)
  349. {
  350. if(!m_midiOut.isPortOpen() || IsBypassed())
  351. {
  352. // We need an output device to send MIDI messages to.
  353. return true;
  354. }
  355. mpt::lock_guard<mpt::mutex> lock(m_mutex);
  356. m_outQueue.push_back(Message(GetOutputTimestamp(), &midiCode, 3));
  357. return true;
  358. }
  359. bool MidiInOut::MidiSysexSend(mpt::const_byte_span sysex)
  360. {
  361. if(!m_midiOut.isPortOpen() || IsBypassed())
  362. {
  363. // We need an output device to send MIDI messages to.
  364. return true;
  365. }
  366. mpt::lock_guard<mpt::mutex> lock(m_mutex);
  367. m_outQueue.push_back(Message(GetOutputTimestamp(), sysex.data(), sysex.size()));
  368. return true;
  369. }
  370. void MidiInOut::HardAllNotesOff()
  371. {
  372. const bool wasSuspended = !IsResumed();
  373. if(wasSuspended)
  374. {
  375. Resume();
  376. }
  377. for(uint8 mc = 0; mc < std::size(m_MidiCh); mc++) //all midi chans
  378. {
  379. PlugInstrChannel &channel = m_MidiCh[mc];
  380. channel.ResetProgram();
  381. SendMidiPitchBend(mc, EncodePitchBendParam(MIDIEvents::pitchBendCentre)); // centre pitch bend
  382. MidiSend(MIDIEvents::CC(MIDIEvents::MIDICC_AllSoundOff, mc, 0)); // all sounds off
  383. for(size_t i = 0; i < std::size(channel.noteOnMap); i++)
  384. {
  385. for(auto &c : channel.noteOnMap[i])
  386. {
  387. while(c != 0)
  388. {
  389. MidiSend(MIDIEvents::NoteOff(mc, static_cast<uint8>(i), 0));
  390. c--;
  391. }
  392. }
  393. }
  394. }
  395. if(wasSuspended)
  396. {
  397. Suspend();
  398. }
  399. }
  400. // Open a device for input or output.
  401. void MidiInOut::OpenDevice(MidiDevice::ID newDevice, bool asInputDevice)
  402. {
  403. MidiDevice &device = asInputDevice ? m_inputDevice : m_outputDevice;
  404. if(device.index == newDevice && device.stream.isPortOpen())
  405. {
  406. // No need to re-open this device.
  407. return;
  408. }
  409. CloseDevice(device);
  410. device.index = newDevice;
  411. device.stream.closePort();
  412. if(device.index == kNoDevice)
  413. {
  414. // Dummy device
  415. device.name = "<none>";
  416. return;
  417. }
  418. device.name = device.GetPortName(newDevice);
  419. //if(m_isResumed)
  420. {
  421. mpt::lock_guard<mpt::mutex> lock(m_mutex);
  422. try
  423. {
  424. device.stream.openPort(newDevice);
  425. if(asInputDevice)
  426. {
  427. m_midiIn.setCallback(InputCallback, this);
  428. m_midiIn.ignoreTypes(false, true, true);
  429. }
  430. } catch(RtMidiError &error)
  431. {
  432. device.name = "Unavailable";
  433. MidiInOutEditor *editor = dynamic_cast<MidiInOutEditor *>(GetEditor());
  434. if(editor != nullptr)
  435. {
  436. Reporting::Error("MIDI device cannot be opened. Is it open in another application?\n\n" + error.getMessage(), "MIDI Input / Output", editor);
  437. }
  438. }
  439. }
  440. }
  441. // Close an active device.
  442. void MidiInOut::CloseDevice(MidiDevice &device)
  443. {
  444. if(device.stream.isPortOpen())
  445. {
  446. mpt::lock_guard<mpt::mutex> lock(m_mutex);
  447. device.stream.closePort();
  448. }
  449. }
  450. // Calculate the current output timestamp
  451. double MidiInOut::GetOutputTimestamp() const
  452. {
  453. return m_clock.Now() * (1.0 / 1000.0) + GetOutputLatency() + m_latency;
  454. }
  455. // Get a device name
  456. std::string MidiDevice::GetPortName(MidiDevice::ID port)
  457. {
  458. return stream.getPortName(port);
  459. }
  460. OPENMPT_NAMESPACE_END