The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

479 lines
17KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. The code included in this file is provided under the terms of the ISC license
  8. http://www.isc.org/downloads/software-support-policy/isc-license. Permission
  9. To use, copy, modify, and/or distribute this software for any purpose with or
  10. without fee is hereby granted provided that the above copyright notice and
  11. this permission notice appear in all copies.
  12. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  13. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  14. DISCLAIMED.
  15. ==============================================================================
  16. */
  17. namespace juce
  18. {
  19. MPEChannelAssigner::MPEChannelAssigner (MPEZoneLayout::Zone zoneToUse)
  20. : zone (new MPEZoneLayout::Zone (zoneToUse)),
  21. channelIncrement (zone->isLowerZone() ? 1 : -1),
  22. numChannels (zone->numMemberChannels),
  23. firstChannel (zone->getFirstMemberChannel()),
  24. lastChannel (zone->getLastMemberChannel()),
  25. midiChannelLastAssigned (firstChannel - channelIncrement)
  26. {
  27. // must be an active MPE zone!
  28. jassert (numChannels > 0);
  29. }
  30. MPEChannelAssigner::MPEChannelAssigner (Range<int> channelRange)
  31. : isLegacy (true),
  32. channelIncrement (1),
  33. numChannels (channelRange.getLength()),
  34. firstChannel (channelRange.getStart()),
  35. lastChannel (channelRange.getEnd() - 1),
  36. midiChannelLastAssigned (firstChannel - channelIncrement)
  37. {
  38. // must have at least one channel!
  39. jassert (! channelRange.isEmpty());
  40. }
  41. int MPEChannelAssigner::findMidiChannelForNewNote (int noteNumber) noexcept
  42. {
  43. if (numChannels == 1)
  44. return firstChannel;
  45. for (auto ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement)
  46. {
  47. if (midiChannels[ch].isFree() && midiChannels[ch].lastNotePlayed == noteNumber)
  48. {
  49. midiChannelLastAssigned = ch;
  50. midiChannels[ch].notes.add (noteNumber);
  51. return ch;
  52. }
  53. }
  54. for (auto ch = midiChannelLastAssigned + channelIncrement; ; ch += channelIncrement)
  55. {
  56. if (ch == lastChannel + channelIncrement) // loop wrap-around
  57. ch = firstChannel;
  58. if (midiChannels[ch].isFree())
  59. {
  60. midiChannelLastAssigned = ch;
  61. midiChannels[ch].notes.add (noteNumber);
  62. return ch;
  63. }
  64. if (ch == midiChannelLastAssigned)
  65. break; // no free channels!
  66. }
  67. midiChannelLastAssigned = findMidiChannelPlayingClosestNonequalNote (noteNumber);
  68. midiChannels[midiChannelLastAssigned].notes.add (noteNumber);
  69. return midiChannelLastAssigned;
  70. }
  71. void MPEChannelAssigner::noteOff (int noteNumber)
  72. {
  73. for (auto& ch : midiChannels)
  74. {
  75. if (ch.notes.removeAllInstancesOf (noteNumber) > 0)
  76. {
  77. ch.lastNotePlayed = noteNumber;
  78. return;
  79. }
  80. }
  81. }
  82. void MPEChannelAssigner::allNotesOff()
  83. {
  84. for (auto& ch : midiChannels)
  85. {
  86. if (ch.notes.size() > 0)
  87. ch.lastNotePlayed = ch.notes.getLast();
  88. ch.notes.clear();
  89. }
  90. }
  91. int MPEChannelAssigner::findMidiChannelPlayingClosestNonequalNote (int noteNumber) noexcept
  92. {
  93. auto channelWithClosestNote = firstChannel;
  94. int closestNoteDistance = 127;
  95. for (auto ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement)
  96. {
  97. for (auto note : midiChannels[ch].notes)
  98. {
  99. auto noteDistance = std::abs (note - noteNumber);
  100. if (noteDistance > 0 && noteDistance < closestNoteDistance)
  101. {
  102. closestNoteDistance = noteDistance;
  103. channelWithClosestNote = ch;
  104. }
  105. }
  106. }
  107. return channelWithClosestNote;
  108. }
  109. //==============================================================================
  110. MPEChannelRemapper::MPEChannelRemapper (MPEZoneLayout::Zone zoneToRemap)
  111. : zone (zoneToRemap),
  112. channelIncrement (zone.isLowerZone() ? 1 : -1),
  113. firstChannel (zone.getFirstMemberChannel()),
  114. lastChannel (zone.getLastMemberChannel())
  115. {
  116. // must be an active MPE zone!
  117. jassert (zone.numMemberChannels > 0);
  118. zeroArrays();
  119. }
  120. void MPEChannelRemapper::remapMidiChannelIfNeeded (MidiMessage& message, uint32 mpeSourceID) noexcept
  121. {
  122. auto channel = message.getChannel();
  123. if (! zone.isUsingChannelAsMemberChannel (channel))
  124. return;
  125. if (channel == zone.getMasterChannel() && (message.isResetAllControllers() || message.isAllNotesOff()))
  126. {
  127. clearSource (mpeSourceID);
  128. return;
  129. }
  130. auto sourceAndChannelID = (((uint32) mpeSourceID << 5) | (uint32) (channel));
  131. if (messageIsNoteData (message))
  132. {
  133. ++counter;
  134. // fast path - no remap
  135. if (applyRemapIfExisting (channel, sourceAndChannelID, message))
  136. return;
  137. // find existing remap
  138. for (int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement)
  139. if (applyRemapIfExisting (chan, sourceAndChannelID, message))
  140. return;
  141. // no remap necessary
  142. if (sourceAndChannel[channel] == notMPE)
  143. {
  144. lastUsed[channel] = counter;
  145. sourceAndChannel[channel] = sourceAndChannelID;
  146. return;
  147. }
  148. // remap source & channel to new channel
  149. auto chan = getBestChanToReuse();
  150. sourceAndChannel[chan] = sourceAndChannelID;
  151. lastUsed[chan] = counter;
  152. message.setChannel (chan);
  153. }
  154. }
  155. void MPEChannelRemapper::reset() noexcept
  156. {
  157. for (auto& s : sourceAndChannel)
  158. s = notMPE;
  159. }
  160. void MPEChannelRemapper::clearChannel (int channel) noexcept
  161. {
  162. sourceAndChannel[channel] = notMPE;
  163. }
  164. void MPEChannelRemapper::clearSource (uint32 mpeSourceID)
  165. {
  166. for (auto& s : sourceAndChannel)
  167. {
  168. if (uint32 (s >> 5) == mpeSourceID)
  169. {
  170. s = notMPE;
  171. return;
  172. }
  173. }
  174. }
  175. bool MPEChannelRemapper::applyRemapIfExisting (int channel, uint32 sourceAndChannelID, MidiMessage& m) noexcept
  176. {
  177. if (sourceAndChannel[channel] == sourceAndChannelID)
  178. {
  179. if (m.isNoteOff())
  180. sourceAndChannel[channel] = notMPE;
  181. else
  182. lastUsed[channel] = counter;
  183. m.setChannel (channel);
  184. return true;
  185. }
  186. return false;
  187. }
  188. int MPEChannelRemapper::getBestChanToReuse() const noexcept
  189. {
  190. for (int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement)
  191. if (sourceAndChannel[chan] == notMPE)
  192. return chan;
  193. auto bestChan = firstChannel;
  194. auto bestLastUse = counter;
  195. for (int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement)
  196. {
  197. if (lastUsed[chan] < bestLastUse)
  198. {
  199. bestLastUse = lastUsed[chan];
  200. bestChan = chan;
  201. }
  202. }
  203. return bestChan;
  204. }
  205. void MPEChannelRemapper::zeroArrays()
  206. {
  207. for (int i = 0; i < 17; ++i)
  208. {
  209. sourceAndChannel[i] = 0;
  210. lastUsed[i] = 0;
  211. }
  212. }
  213. //==============================================================================
  214. //==============================================================================
  215. #if JUCE_UNIT_TESTS
  216. struct MPEUtilsUnitTests : public UnitTest
  217. {
  218. MPEUtilsUnitTests()
  219. : UnitTest ("MPE Utilities", "MIDI/MPE")
  220. {}
  221. void runTest() override
  222. {
  223. beginTest ("MPEChannelAssigner");
  224. {
  225. MPEZoneLayout layout;
  226. // lower
  227. {
  228. layout.setLowerZone (15);
  229. // lower zone
  230. MPEChannelAssigner channelAssigner (layout.getLowerZone());
  231. // check that channels are assigned in correct order
  232. int noteNum = 60;
  233. for (int ch = 2; ch <= 16; ++ch)
  234. expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch);
  235. // check that note-offs are processed
  236. channelAssigner.noteOff (60);
  237. expectEquals (channelAssigner.findMidiChannelForNewNote (60), 2);
  238. channelAssigner.noteOff (61);
  239. expectEquals (channelAssigner.findMidiChannelForNewNote (61), 3);
  240. // check that assigned channel was last to play note
  241. channelAssigner.noteOff (65);
  242. channelAssigner.noteOff (66);
  243. expectEquals (channelAssigner.findMidiChannelForNewNote (66), 8);
  244. expectEquals (channelAssigner.findMidiChannelForNewNote (65), 7);
  245. // find closest channel playing nonequal note
  246. expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
  247. expectEquals (channelAssigner.findMidiChannelForNewNote (55), 2);
  248. // all notes off
  249. channelAssigner.allNotesOff();
  250. // last note played
  251. expectEquals (channelAssigner.findMidiChannelForNewNote (66), 8);
  252. expectEquals (channelAssigner.findMidiChannelForNewNote (65), 7);
  253. expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
  254. expectEquals (channelAssigner.findMidiChannelForNewNote (55), 2);
  255. // normal assignment
  256. expectEquals (channelAssigner.findMidiChannelForNewNote (101), 3);
  257. expectEquals (channelAssigner.findMidiChannelForNewNote (20), 4);
  258. }
  259. // upper
  260. {
  261. layout.setUpperZone (15);
  262. // upper zone
  263. MPEChannelAssigner channelAssigner (layout.getUpperZone());
  264. // check that channels are assigned in correct order
  265. int noteNum = 60;
  266. for (int ch = 15; ch >= 1; --ch)
  267. expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch);
  268. // check that note-offs are processed
  269. channelAssigner.noteOff (60);
  270. expectEquals (channelAssigner.findMidiChannelForNewNote (60), 15);
  271. channelAssigner.noteOff (61);
  272. expectEquals (channelAssigner.findMidiChannelForNewNote (61), 14);
  273. // check that assigned channel was last to play note
  274. channelAssigner.noteOff (65);
  275. channelAssigner.noteOff (66);
  276. expectEquals (channelAssigner.findMidiChannelForNewNote (66), 9);
  277. expectEquals (channelAssigner.findMidiChannelForNewNote (65), 10);
  278. // find closest channel playing nonequal note
  279. expectEquals (channelAssigner.findMidiChannelForNewNote (80), 1);
  280. expectEquals (channelAssigner.findMidiChannelForNewNote (55), 15);
  281. // all notes off
  282. channelAssigner.allNotesOff();
  283. // last note played
  284. expectEquals (channelAssigner.findMidiChannelForNewNote (66), 9);
  285. expectEquals (channelAssigner.findMidiChannelForNewNote (65), 10);
  286. expectEquals (channelAssigner.findMidiChannelForNewNote (80), 1);
  287. expectEquals (channelAssigner.findMidiChannelForNewNote (55), 15);
  288. // normal assignment
  289. expectEquals (channelAssigner.findMidiChannelForNewNote (101), 14);
  290. expectEquals (channelAssigner.findMidiChannelForNewNote (20), 13);
  291. }
  292. // legacy
  293. {
  294. MPEChannelAssigner channelAssigner;
  295. // check that channels are assigned in correct order
  296. int noteNum = 60;
  297. for (int ch = 1; ch <= 16; ++ch)
  298. expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch);
  299. // check that note-offs are processed
  300. channelAssigner.noteOff (60);
  301. expectEquals (channelAssigner.findMidiChannelForNewNote (60), 1);
  302. channelAssigner.noteOff (61);
  303. expectEquals (channelAssigner.findMidiChannelForNewNote (61), 2);
  304. // check that assigned channel was last to play note
  305. channelAssigner.noteOff (65);
  306. channelAssigner.noteOff (66);
  307. expectEquals (channelAssigner.findMidiChannelForNewNote (66), 7);
  308. expectEquals (channelAssigner.findMidiChannelForNewNote (65), 6);
  309. // find closest channel playing nonequal note
  310. expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
  311. expectEquals (channelAssigner.findMidiChannelForNewNote (55), 1);
  312. // all notes off
  313. channelAssigner.allNotesOff();
  314. // last note played
  315. expectEquals (channelAssigner.findMidiChannelForNewNote (66), 7);
  316. expectEquals (channelAssigner.findMidiChannelForNewNote (65), 6);
  317. expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
  318. expectEquals (channelAssigner.findMidiChannelForNewNote (55), 1);
  319. // normal assignment
  320. expectEquals (channelAssigner.findMidiChannelForNewNote (101), 2);
  321. expectEquals (channelAssigner.findMidiChannelForNewNote (20), 3);
  322. }
  323. }
  324. beginTest ("MPEChannelRemapper");
  325. {
  326. // 3 different MPE 'sources', constant IDs
  327. const int sourceID1 = 0;
  328. const int sourceID2 = 1;
  329. const int sourceID3 = 2;
  330. MPEZoneLayout layout;
  331. {
  332. layout.setLowerZone (15);
  333. // lower zone
  334. MPEChannelRemapper channelRemapper (layout.getLowerZone());
  335. // first source, shouldn't remap
  336. for (int ch = 2; ch <= 16; ++ch)
  337. {
  338. auto noteOn = MidiMessage::noteOn (ch, 60, 1.0f);
  339. channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID1);
  340. expectEquals (noteOn.getChannel(), ch);
  341. }
  342. auto noteOn = MidiMessage::noteOn (2, 60, 1.0f);
  343. // remap onto oldest last-used channel
  344. channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID2);
  345. expectEquals (noteOn.getChannel(), 2);
  346. // remap onto oldest last-used channel
  347. channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID3);
  348. expectEquals (noteOn.getChannel(), 3);
  349. // remap to correct channel for source ID
  350. auto noteOff = MidiMessage::noteOff (2, 60, 1.0f);
  351. channelRemapper.remapMidiChannelIfNeeded (noteOff, sourceID3);
  352. expectEquals (noteOff.getChannel(), 3);
  353. }
  354. {
  355. layout.setUpperZone (15);
  356. // upper zone
  357. MPEChannelRemapper channelRemapper (layout.getUpperZone());
  358. // first source, shouldn't remap
  359. for (int ch = 15; ch >= 1; --ch)
  360. {
  361. auto noteOn = MidiMessage::noteOn (ch, 60, 1.0f);
  362. channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID1);
  363. expectEquals (noteOn.getChannel(), ch);
  364. }
  365. auto noteOn = MidiMessage::noteOn (15, 60, 1.0f);
  366. // remap onto oldest last-used channel
  367. channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID2);
  368. expectEquals (noteOn.getChannel(), 15);
  369. // remap onto oldest last-used channel
  370. channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID3);
  371. expectEquals (noteOn.getChannel(), 14);
  372. // remap to correct channel for source ID
  373. auto noteOff = MidiMessage::noteOff (15, 60, 1.0f);
  374. channelRemapper.remapMidiChannelIfNeeded (noteOff, sourceID3);
  375. expectEquals (noteOff.getChannel(), 14);
  376. }
  377. }
  378. }
  379. };
  380. static MPEUtilsUnitTests MPEUtilsUnitTests;
  381. #endif
  382. } // namespace juce