Audio plugin host https://kx.studio/carla
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.

495 lines
17KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - Raw Material Software Limited
  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, int midiChannel)
  72. {
  73. const auto removeNote = [] (MidiChannel& ch, int noteNum)
  74. {
  75. if (ch.notes.removeAllInstancesOf (noteNum) > 0)
  76. {
  77. ch.lastNotePlayed = noteNum;
  78. return true;
  79. }
  80. return false;
  81. };
  82. if (midiChannel >= 0 && midiChannel < 17)
  83. {
  84. removeNote (midiChannels[midiChannel], noteNumber);
  85. return;
  86. }
  87. for (auto& ch : midiChannels)
  88. {
  89. if (removeNote (ch, noteNumber))
  90. return;
  91. }
  92. }
  93. void MPEChannelAssigner::allNotesOff()
  94. {
  95. for (auto& ch : midiChannels)
  96. {
  97. if (ch.notes.size() > 0)
  98. ch.lastNotePlayed = ch.notes.getLast();
  99. ch.notes.clear();
  100. }
  101. }
  102. int MPEChannelAssigner::findMidiChannelPlayingClosestNonequalNote (int noteNumber) noexcept
  103. {
  104. auto channelWithClosestNote = firstChannel;
  105. int closestNoteDistance = 127;
  106. for (auto ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement)
  107. {
  108. for (auto note : midiChannels[ch].notes)
  109. {
  110. auto noteDistance = std::abs (note - noteNumber);
  111. if (noteDistance > 0 && noteDistance < closestNoteDistance)
  112. {
  113. closestNoteDistance = noteDistance;
  114. channelWithClosestNote = ch;
  115. }
  116. }
  117. }
  118. return channelWithClosestNote;
  119. }
  120. //==============================================================================
  121. MPEChannelRemapper::MPEChannelRemapper (MPEZoneLayout::Zone zoneToRemap)
  122. : zone (zoneToRemap),
  123. channelIncrement (zone.isLowerZone() ? 1 : -1),
  124. firstChannel (zone.getFirstMemberChannel()),
  125. lastChannel (zone.getLastMemberChannel())
  126. {
  127. // must be an active MPE zone!
  128. jassert (zone.numMemberChannels > 0);
  129. zeroArrays();
  130. }
  131. void MPEChannelRemapper::remapMidiChannelIfNeeded (MidiMessage& message, uint32 mpeSourceID) noexcept
  132. {
  133. auto channel = message.getChannel();
  134. if (! zone.isUsingChannelAsMemberChannel (channel))
  135. return;
  136. if (channel == zone.getMasterChannel() && (message.isResetAllControllers() || message.isAllNotesOff()))
  137. {
  138. clearSource (mpeSourceID);
  139. return;
  140. }
  141. auto sourceAndChannelID = (((uint32) mpeSourceID << 5) | (uint32) (channel));
  142. if (messageIsNoteData (message))
  143. {
  144. ++counter;
  145. // fast path - no remap
  146. if (applyRemapIfExisting (channel, sourceAndChannelID, message))
  147. return;
  148. // find existing remap
  149. for (int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement)
  150. if (applyRemapIfExisting (chan, sourceAndChannelID, message))
  151. return;
  152. // no remap necessary
  153. if (sourceAndChannel[channel] == notMPE)
  154. {
  155. lastUsed[channel] = counter;
  156. sourceAndChannel[channel] = sourceAndChannelID;
  157. return;
  158. }
  159. // remap source & channel to new channel
  160. auto chan = getBestChanToReuse();
  161. sourceAndChannel[chan] = sourceAndChannelID;
  162. lastUsed[chan] = counter;
  163. message.setChannel (chan);
  164. }
  165. }
  166. void MPEChannelRemapper::reset() noexcept
  167. {
  168. for (auto& s : sourceAndChannel)
  169. s = notMPE;
  170. }
  171. void MPEChannelRemapper::clearChannel (int channel) noexcept
  172. {
  173. sourceAndChannel[channel] = notMPE;
  174. }
  175. void MPEChannelRemapper::clearSource (uint32 mpeSourceID)
  176. {
  177. for (auto& s : sourceAndChannel)
  178. {
  179. if (uint32 (s >> 5) == mpeSourceID)
  180. {
  181. s = notMPE;
  182. return;
  183. }
  184. }
  185. }
  186. bool MPEChannelRemapper::applyRemapIfExisting (int channel, uint32 sourceAndChannelID, MidiMessage& m) noexcept
  187. {
  188. if (sourceAndChannel[channel] == sourceAndChannelID)
  189. {
  190. if (m.isNoteOff())
  191. sourceAndChannel[channel] = notMPE;
  192. else
  193. lastUsed[channel] = counter;
  194. m.setChannel (channel);
  195. return true;
  196. }
  197. return false;
  198. }
  199. int MPEChannelRemapper::getBestChanToReuse() const noexcept
  200. {
  201. for (int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement)
  202. if (sourceAndChannel[chan] == notMPE)
  203. return chan;
  204. auto bestChan = firstChannel;
  205. auto bestLastUse = counter;
  206. for (int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement)
  207. {
  208. if (lastUsed[chan] < bestLastUse)
  209. {
  210. bestLastUse = lastUsed[chan];
  211. bestChan = chan;
  212. }
  213. }
  214. return bestChan;
  215. }
  216. void MPEChannelRemapper::zeroArrays()
  217. {
  218. for (int i = 0; i < 17; ++i)
  219. {
  220. sourceAndChannel[i] = 0;
  221. lastUsed[i] = 0;
  222. }
  223. }
  224. //==============================================================================
  225. //==============================================================================
  226. #if JUCE_UNIT_TESTS
  227. struct MPEUtilsUnitTests : public UnitTest
  228. {
  229. MPEUtilsUnitTests()
  230. : UnitTest ("MPE Utilities", UnitTestCategories::midi)
  231. {}
  232. void runTest() override
  233. {
  234. beginTest ("MPEChannelAssigner");
  235. {
  236. MPEZoneLayout layout;
  237. // lower
  238. {
  239. layout.setLowerZone (15);
  240. // lower zone
  241. MPEChannelAssigner channelAssigner (layout.getLowerZone());
  242. // check that channels are assigned in correct order
  243. int noteNum = 60;
  244. for (int ch = 2; ch <= 16; ++ch)
  245. expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch);
  246. // check that note-offs are processed
  247. channelAssigner.noteOff (60);
  248. expectEquals (channelAssigner.findMidiChannelForNewNote (60), 2);
  249. channelAssigner.noteOff (61);
  250. expectEquals (channelAssigner.findMidiChannelForNewNote (61), 3);
  251. // check that assigned channel was last to play note
  252. channelAssigner.noteOff (65);
  253. channelAssigner.noteOff (66);
  254. expectEquals (channelAssigner.findMidiChannelForNewNote (66), 8);
  255. expectEquals (channelAssigner.findMidiChannelForNewNote (65), 7);
  256. // find closest channel playing nonequal note
  257. expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
  258. expectEquals (channelAssigner.findMidiChannelForNewNote (55), 2);
  259. // all notes off
  260. channelAssigner.allNotesOff();
  261. // last note played
  262. expectEquals (channelAssigner.findMidiChannelForNewNote (66), 8);
  263. expectEquals (channelAssigner.findMidiChannelForNewNote (65), 7);
  264. expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
  265. expectEquals (channelAssigner.findMidiChannelForNewNote (55), 2);
  266. // normal assignment
  267. expectEquals (channelAssigner.findMidiChannelForNewNote (101), 3);
  268. expectEquals (channelAssigner.findMidiChannelForNewNote (20), 4);
  269. }
  270. // upper
  271. {
  272. layout.setUpperZone (15);
  273. // upper zone
  274. MPEChannelAssigner channelAssigner (layout.getUpperZone());
  275. // check that channels are assigned in correct order
  276. int noteNum = 60;
  277. for (int ch = 15; ch >= 1; --ch)
  278. expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch);
  279. // check that note-offs are processed
  280. channelAssigner.noteOff (60);
  281. expectEquals (channelAssigner.findMidiChannelForNewNote (60), 15);
  282. channelAssigner.noteOff (61);
  283. expectEquals (channelAssigner.findMidiChannelForNewNote (61), 14);
  284. // check that assigned channel was last to play note
  285. channelAssigner.noteOff (65);
  286. channelAssigner.noteOff (66);
  287. expectEquals (channelAssigner.findMidiChannelForNewNote (66), 9);
  288. expectEquals (channelAssigner.findMidiChannelForNewNote (65), 10);
  289. // find closest channel playing nonequal note
  290. expectEquals (channelAssigner.findMidiChannelForNewNote (80), 1);
  291. expectEquals (channelAssigner.findMidiChannelForNewNote (55), 15);
  292. // all notes off
  293. channelAssigner.allNotesOff();
  294. // last note played
  295. expectEquals (channelAssigner.findMidiChannelForNewNote (66), 9);
  296. expectEquals (channelAssigner.findMidiChannelForNewNote (65), 10);
  297. expectEquals (channelAssigner.findMidiChannelForNewNote (80), 1);
  298. expectEquals (channelAssigner.findMidiChannelForNewNote (55), 15);
  299. // normal assignment
  300. expectEquals (channelAssigner.findMidiChannelForNewNote (101), 14);
  301. expectEquals (channelAssigner.findMidiChannelForNewNote (20), 13);
  302. }
  303. // legacy
  304. {
  305. MPEChannelAssigner channelAssigner;
  306. // check that channels are assigned in correct order
  307. int noteNum = 60;
  308. for (int ch = 1; ch <= 16; ++ch)
  309. expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch);
  310. // check that note-offs are processed
  311. channelAssigner.noteOff (60);
  312. expectEquals (channelAssigner.findMidiChannelForNewNote (60), 1);
  313. channelAssigner.noteOff (61);
  314. expectEquals (channelAssigner.findMidiChannelForNewNote (61), 2);
  315. // check that assigned channel was last to play note
  316. channelAssigner.noteOff (65);
  317. channelAssigner.noteOff (66);
  318. expectEquals (channelAssigner.findMidiChannelForNewNote (66), 7);
  319. expectEquals (channelAssigner.findMidiChannelForNewNote (65), 6);
  320. // find closest channel playing nonequal note
  321. expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
  322. expectEquals (channelAssigner.findMidiChannelForNewNote (55), 1);
  323. // all notes off
  324. channelAssigner.allNotesOff();
  325. // last note played
  326. expectEquals (channelAssigner.findMidiChannelForNewNote (66), 7);
  327. expectEquals (channelAssigner.findMidiChannelForNewNote (65), 6);
  328. expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
  329. expectEquals (channelAssigner.findMidiChannelForNewNote (55), 1);
  330. // normal assignment
  331. expectEquals (channelAssigner.findMidiChannelForNewNote (101), 2);
  332. expectEquals (channelAssigner.findMidiChannelForNewNote (20), 3);
  333. }
  334. }
  335. beginTest ("MPEChannelRemapper");
  336. {
  337. // 3 different MPE 'sources', constant IDs
  338. const int sourceID1 = 0;
  339. const int sourceID2 = 1;
  340. const int sourceID3 = 2;
  341. MPEZoneLayout layout;
  342. {
  343. layout.setLowerZone (15);
  344. // lower zone
  345. MPEChannelRemapper channelRemapper (layout.getLowerZone());
  346. // first source, shouldn't remap
  347. for (int ch = 2; ch <= 16; ++ch)
  348. {
  349. auto noteOn = MidiMessage::noteOn (ch, 60, 1.0f);
  350. channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID1);
  351. expectEquals (noteOn.getChannel(), ch);
  352. }
  353. auto noteOn = MidiMessage::noteOn (2, 60, 1.0f);
  354. // remap onto oldest last-used channel
  355. channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID2);
  356. expectEquals (noteOn.getChannel(), 2);
  357. // remap onto oldest last-used channel
  358. channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID3);
  359. expectEquals (noteOn.getChannel(), 3);
  360. // remap to correct channel for source ID
  361. auto noteOff = MidiMessage::noteOff (2, 60, 1.0f);
  362. channelRemapper.remapMidiChannelIfNeeded (noteOff, sourceID3);
  363. expectEquals (noteOff.getChannel(), 3);
  364. }
  365. {
  366. layout.setUpperZone (15);
  367. // upper zone
  368. MPEChannelRemapper channelRemapper (layout.getUpperZone());
  369. // first source, shouldn't remap
  370. for (int ch = 15; ch >= 1; --ch)
  371. {
  372. auto noteOn = MidiMessage::noteOn (ch, 60, 1.0f);
  373. channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID1);
  374. expectEquals (noteOn.getChannel(), ch);
  375. }
  376. auto noteOn = MidiMessage::noteOn (15, 60, 1.0f);
  377. // remap onto oldest last-used channel
  378. channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID2);
  379. expectEquals (noteOn.getChannel(), 15);
  380. // remap onto oldest last-used channel
  381. channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID3);
  382. expectEquals (noteOn.getChannel(), 14);
  383. // remap to correct channel for source ID
  384. auto noteOff = MidiMessage::noteOff (15, 60, 1.0f);
  385. channelRemapper.remapMidiChannelIfNeeded (noteOff, sourceID3);
  386. expectEquals (noteOff.getChannel(), 14);
  387. }
  388. }
  389. }
  390. };
  391. static MPEUtilsUnitTests MPEUtilsUnitTests;
  392. #endif
  393. } // namespace juce