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.

juce_MPEUtils.cpp 21KB

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